postfix/smtp[30458]: 93DE398058A: to=<***@yahoo.de>, relay=mx-eu.mail.am0.yahoodns.net[188.125.69.79]:25, delay=139000, delays=138999/0.07/0.43/0, dsn=4.7.1, status=deferred (host mx-eu.mail.am0.yahoodns.net[188.125.69.79] refused to talk to me: 421 4.7.1 [TS03] All messages from 178.238.228.114 will be permanently deferred; Retrying will NOT succeed. See http://postmaster.yahoo.com/errors/421-ts03.html)
Ich habe diese IP erst seit rund vier Wochen und habe bisher zwei (!!) E-Mails an Yahoo verschickt, welche übrigens NIE ankamen.
Ich habe LANGE gelesen und nicht wirklich Hilfe gefunden, erst google konnte helfen, dabei traf ich auf diese Hilfe-Seite eines amerikanischen Anbieters für virtuelle Server: https://www.intovps.com/client/knowledgebase/15/Yahoo-rejects-your-e-mail-messages–421-.html
Kurz und bündig: Im Text behauptet Yahoo, sie melden diesen Fehler NUR, wenn man Spam versendet hat, was in 99% der Fälle nicht stimmt. Sie lehnen auch NEUE IP-Adressen ab.
Was soll das bitte?
Voraussichtliche Lösung des Problems: Dieses Formular wahrheitsgetreu ausfüllen und hoffen
http://help.yahoo.com/l/us/yahoo/mail/postmaster/bulk.html
Was mich an der ganzen Sache extrem stört: Status Codes im Bereich von 400-499 werden einfach “erneut versucht”. Diese Bedeuten normalerweise “da klappt GERADE etwas nicht, versuchs später wieder”. Das heißt, hätte ich nicht zufällig ins Log geschaut bzw. bescheid bekommen, dass jemand auf meine Mail wartet, hätte ich NIE davon Wind bekommen. Was soll das bitte?
]]>Das, nennen wir es “Projekt”, war innerhalb kurzer Zeit als erster Prototype fertig, habe es dann noch ein wenig “aufgebohrt”, es bei GitHub hochgeladen und auf NPM veröffentlicht.
Es gibt (bisher) lediglich zwei Abhängigkeiten: Async, um einige Abläufe zu parallelisieren und easy-gd, um die Bilder zu beschneiden und das Wasserzeichen draufzulegen.
Die Lösung ist sehr schnell (wenige Sekunden für 30 Bilder auf meinem MacBook Pro I7), absolut zufriedenstellend und via Konsole parametrisierbar. Verbesserungsvorschläge sind via pull-request jederzeit willkommen!
Solltest Du es ausprobieren wollen, benötigst du ein installiertes “gd” (unter MacOSX am besten via “brew install gd”) und ein installiertes NodeJS (>=0.10.0), die “globale” Installation erfolgt dann einfach via “npm install -g node-watermarker” und der Aufruf im Terminal via “nodewatermarker”. Syntax bzw. Konsolenparameter werden dann ausgegeben.
]]>Thunderbird bietet die Möglichkeit, unter der Domain “autoconfig.domain.de” eine XML Datei unter dem Pfad /mail/config-v1.1.xml, für genau diese Einstellungen anzulegen. Andere Mail-Client beachten diese Datei mittlerweile auch (fragt mich bitte nicht welche).
Also, schnell die XML Zusammengebaut (der Block lässt sich mehrfach, einer für jede Domain, wiederholen).
<clientConfig version="1.1"> <emailProvider id="xfragger.de"> <domain>xfragger.de</domain> <displayName>xfragger.de Mail</displayName> <displayShortName>xfragger.de</displayShortName> <incomingServer type="imap"> <hostname>mail.xfragger.de</hostname> <port>993</port> <socketType>SSL</socketType> <authentication>password-cleartext</authentication> <username>%EMAILADDRESS%</username> </incomingServer> <outgoingServer type="smtp"> <hostname>mail.xfragger.de</hostname> <port>25</port> <socketType>STARTTLS</socketType> <authentication>password-cleartext</authentication> <username>%EMAILADDRESS%</username> </outgoingServer> </emailProvider> </clientConfig> |
Nun noch dem nginx beibringen, dass er diese Datei für jeden erdenklichen Request an die entsprechende Subdomain ausliefert.
server { root /usr/share/nginx/www/autoconfig; server_name autoconfig.xfragger.de; server_name autoconfig.domain.de; try_files $uri /config-v1.1.xml; }
Hilfreiche Links/Quellen
developer.mozilla.org/en-US/docs/Thunderbird/Autoconfiguration
developer.mozilla.org/en-US/docs/Thunderbird/Autoconfiguration/FileFormat/HowTo
Beginnt man nun sein E-Mail-Konto im Thunderbird einzurichten, bekommt man, nach Eingabe von E-Mail-Adresse und Passwort, folgendes zu sehen:
]]>Wenn man sich auf der Konsole befindet, ruft man ein php-Skript entweder via “php ./filename.php” auf oder fügt in der ersten Zeile eine Interpreterdefinition ein: “#!/usr/bin/php”, danach beginnt das Skript, wie gewohnt, mit
Wenn man die Datei nun ausführbar macht (chmod +x filename.php) kann man sie direkt via ./filename.php aufrufen, genau das ist es, was ich mit normalerweise mit cron-jobs mache.
Nun habe ich die genannte Zeile bei rund 20 Skripten eingefügt, die Dateien ausführbar gemacht und bei ca. jeder zweiten, war das Ergebnis das im Titel des Beitrag genannte, ohne dass das Skript wirklich ausgeführt wurde. Recherchen im Netz ergaben, dass das kein unbekanntes Problem sei, aber entweder existierte der StackOverflow-Artikel nicht mehr oder es gab keine Antworten auf die Frage in diversen Foren.
Die Lösung ist ganz einfach: DOS-Zeilen-Enden
OS X und Unix-Systeme nutzen nur einen “LineFeed” (\n), Window dagegen ein CarriageReturn und einen LineFeed (\r\n) um eine Zeile zu beenden. Ist die Datei nun im Window-Format, taucht oben genannter Fehler auf, wenn man versucht, das Skript auf einem Unix-System auszuführen.
Für das Problem gibt es diverse Lösungen, da “dos2unix” nicht zur Verfügung stand, schrieb ich mein eigenes Shellskript, welches alle \r-Zeichen aus einer übergebenen Datei löscht. Ein Konsolenbefehl hätte es getan, aber ich wollte das Ganze während des Deployvorgangs auslösen (und zwar nur für den cron-Ordner).
#!/bin/bash
tr -d '\r' < $1 > /tmp/dos2unix
cat /tmp/dos2unix > $1
rm /tmp/dos2unix
Im Deployskript folgte dann noch dieser Aufruf:
find $pathToCron -type f -name "*.php" -exec [$pathToDos2Unix.sh] \'{}\' \;
In diesem speziellen Fall, geht es darum, dass ein hochgeladen wird und die Seitenverhältnisse einer “Box” angepasst werden müssen.
Es gibt zwei Faktoren, die das ein wenig komplizieren.
1. Das Bild ist in Pixeln definiert, die Box in mm, die “einfache Variante” (maxWidth und maxHeight als Input) geht also nicht
2. Die “Box” darf weder breiter noch höher, nur schmaler oder flacher werden
$imageRatio = $imageWidth / $imageHeight; //pixel $elementRatio = $element->getWidth() / $element->getHeight(); //mm if ($imageRatio == $elementRatio) { continue; } elseif (($imageRatio > 1 && $elementRatio > 1) || ($imageRatio < 1 && $elementRatio < 1)) { //landscape+landscape OR portrait+portrait if ($imageRatio > $elementRatio) { $newHeight = $element->getWidth() / $imageRatio; } else { $newWidth = $element->getHeight() * $imageRatio; } } elseif ($imageRatio >= 1 && $elementRatio <= 1) { //landscape/square + portrait/square $newHeight = $element->getWidth() / $imageRatio; } elseif ($imageRatio <= 1 && $elementRatio >= 1) { //portrait/square + landscape/square $newWidth = $element->getHeight() * $imageRatio; } $element->setWidth($newWidth); $element->setHeight($newHeight); |
Ich Schätze das ist um 2004 entstanden und lieferte Termine bzw. Events von einem bestimmten Tag, das ganze für einen Webkalender ala Google-Calendar. Was ich noch erwähnen sollte…. die Funktion wurde für jeden sichtbaren Kalendertag aufgerufen!
function getevents($year,$month,$day){ global $SELF; $month = (strlen($month) < 2) ? '0'.$month : $month; $day = (strlen($day) < 2) ? '0'.$day : $day; $str = "$year-$month-$day"; $strx = "$month-$day"; $sql = " SELECT e.name as ename, e.description as edescr, e.id as eid, t.color as tcolor, e.time_start as time_start, e.time_end as time_end, e.remember as rem, e.date_start as date_start, e.date_end as date_end, t.description as tdescription, t.showtime as showtime FROM cal_entry e, cal_types t WHERE e.types_id = t.id AND (e.date_start <= '$str' AND ( e.date_end >= '$str' AND e.date_end IS NOT NULL OR e.date_start = '$str' AND e.date_end IS NULL ) OR(t.id = 1 AND right(e.date_start,5) = '$strx') ) ORDER BY e.time_start, t.prio "; $res = mysql_query($sql); $out = "<u><a href=\"$SELF?show=new&date=$year-$month-$day\">$day.$month.</a></u>"; while($row = mysql_fetch_array($res)){ $tmp = "<a href=\"$SELF?show=edit&eid=".$row["eid"]."&ym=$year-$month\">"; $tmp .= "<font class=\"entry\" color=\"".$row["tcolor"]."\"><li>"; $tmp .= $row["ename"]; $starta = explode("-",$row["date_start"]); $outstart = $starta[2].".".$starta[1].".".$starta[0]; $enda = explode("-",$row["date_end"]); $outend = $enda[2].".".$enda[1].".".$enda[0]; $divbody = "<table border=0 class=entry><tr>"; if($row["date_end"] != '' && $row["date_start"] != $row["date_end"]){ $divbody .= "<td>Von:</td><td>$outstart</td></tr><tr><td>Bis:</td><td>$outend</td></tr>"; }else{ $divbody .= "<td>Am:</td><td>$outstart</td></tr>"; } if($row["showtime"]) { if(($row["date_end"] == $row["date_start"] || $row["date_end"] == '') && $row["time_end"] != ''){ $divbody .= "<tr><td>Von:</td><td>".$row["time_start"]." Uhr</td></tr>"; $divbody .= "<tr><td>Bis:</td><td>".$row["time_end"]." Uhr</td></tr>"; }elseif($row["date_start"] < $row["date_end"] && $row["date_start"] == $str && $row["time_start"] != ''){ $divbody .= "<tr><td>Ab:</td><td>".$row["time_start"]." Uhr</td></tr>"; }elseif($row["date_start"] < $row["date_end"] && $row["date_end"] == $str && $row["time_end"] != ''){ $divbody .= "<tr><td>Bis:</td><td>".$row["time_end"]." Uhr</td></tr>"; }elseif(($row["date_end"] == $row["date_start"] || $row["date_end"] == '') && $row["time_end"] == ''){ $divbody .= "<tr><td>Ab:</td><td>".$row["time_start"]." Uhr</td></tr>"; } } $rem = ($row["rem"] == true) ? "<font color=green>EMail Erinnerung aktiv</font>" : "<font color=red>EMail Erinnerung inaktiv</font>"; $divbody .= "</table>"; #$div = "<div title=\"header=[".$row["ename"]."] body=[".$row["edescr"].$divbody"]\">"; $div = '<div title="header=['.$row["ename"].'] body=['.nl2br($row["edescr"]).$divbody.$rem.']">'; $tmp .= "</div></li></font></a>"; $out .= $div.$tmp; } return $out; } |
Aus Googlesicht ist das gut, weil so doppelter Inhalt vermieden wird und für den Benutzer ist es einfach schöner.
Im Internet habe ich einige Beispiele gefunden, wie man www.example.com auf example.com und umgekehrt umleitet, aber keine generelle Lösung. Die Hostdefinition matcht *example.com und *.example.de, weitere sind ohne weiteres möglich.
$HTTP["host"] =~ "^(.*example\.(com|de)$" { server.document-root = "/var/www/example.com" server.error-handler-404 = "/index.php" $HTTP["host"] !~ "^(www\.(.*)\.(.*))$"{ #nur wenn es nicht schon www. ist $HTTP["host"] =~ "^(.*)\.(.*)\.(.*)$" { #falls es sich um xxx.*.* handelt url.redirect = ( ".*" => "http://www.%2.%3" ) } $HTTP["host"] =~ "^([^.]*)\.([^.]*)$" { #falls es sich um *.* handelt (ohne Subdomain) url.redirect = ( ".*" => "http://www.%1.%2" ) } } } |
Will man nun noch forum.example.de (nur die .de Variante) erlauben, muss man diese Expression “^(www\.(.*)\.(.*))$” auf “^(www\.(.*)|forum\.example\.de)$” abändern.
]]>Das Tool nutzt Webkit im internen Backend und kann sogar CSS3 rendern. Die Installation unter MaxOSX und Linux ist absolut simpel: runterladen, ablegen, fertig. Unter Windows wird hier noch ein Installationsprozess durchgeführt, über den ich aktuell leider nichts sagen kann (bisher nicht getestet).
Um das Tool auf einer Webseite zu nutzen, habe ich mir ne kleine Klasse geschrieben, die HTML-Code als Input akzeptiert und mir den Pfad der fertigen PDF-Datei zurückliefert.
class HtmlToPdf { const TMP = '/tmp/'; const EXEC = '/usr/local/bin/wkhtmltopdf-amd64'; //anpassen! private $executable; private $htmlcode; private $tmphtmlfile; private $targetFilename; private $options = array( '--ignore-load-errors' ); //Kommandozeilenoptionen für wkhtmltopdf public function __construct($targetFilename = ''){ $this->targetFilename = $targetFilename; } public function setHtmlCode($htmlcode){ $this->htmlcode = $htmlcode; } public function setTargetFilename($targetFilename){ $this->targetFilename = $targetFilename; } public function generate(){ $this->createTempHtmlFile(); $this->convertHtmlfileToPdf(); return self::TMP.$this->targetFilename; } private function createTempHtmlFile(){ $filename = tempnam(self::TMP); File::move($filename, $filename.'.html'); //nötig, damit die Datei als Input akzeptiert wird $filename .= '.html'; file_put_contents($filename, $this->htmlcode); $this->tmphtmlfile = $filename; } private function convertHtmlfileToPdf(){ if(!$this->targetFilename){ $this->generateTargetFilename(); } $cmd = self::EXEC.' '.implode(' ', $this->options).' '.$this->tmphtmlfile.' '.self::TMP.$this->targetFilename; shell_exec($cmd); File::delete($this->tmphtmlfile); } private function generateTargetFilename(){ $this->targetFilename = uniqueid().'.pdf'; } } |
Und so nutzt man das Ganze:
$converter = new HtmlToPdf('output.pdf'); $converter->setHtmlCode($htmlcodeWithCSS); $pdfFile = $converter->generate() |
Sehr praktisch ist es, wenn man eine Templateengine (z.B. Smarty) nutzt, hier kann man erst den kompletten Code generieren und ihn dann via $smarty->fetch() in den Konverter übergeben. Über einen Parameter kann man dann noch dafür sorgen, dass der CSS Style nicht ausgelagert, sondern im head-Block ausgegeben wird, da das Tool sonst nicht damit klarkommt.
]]>Reproduzierbar ist er folgendermaßen:
$from = new DateTime(); $from->setTime(0, 0, 0); $from->setISODate(2010, 28, 1); //Montag der 28ten Woche 2010 echo $from->format('d.m.Y H:i'); //A echo $from->getTimestamp(); //B echo date('Y-m-d H:i', $from->getTimestamp()); //C |
A: das ausgegeben Datum stimmt, es ist der 12.07.2010
B: der Timestamp ist viel zu hoch: 1291849200
C: der Beweis, der Timestamp gehört zum 29.05.2011 00:00
Wir haben lange versucht herauszufinden, warum dies passiert. Ein hässlicher Workaround ist dieser:
$from->add(new DateInterval('P0D')); |
Wir fügen also ein Interval von 0 Tagen (oder Stunden, Minuten etc) hinzu, wodurch sich eigentlich nichts ändern sollte. Danach stimmt das Ergebnis von ->getTimestamp() allerdings, nämlich 1278885600.
Nachdem wir absolut keinen Grund feststellen konnten, haben wir das Vertrauen verloren und nutzen seitdem Zend_Date, welches, meiner Meinung nach, besser und einfacher zu bedienen ist. Das ZendFramework ist sowieso schon im aktuellen Projekt eingebunden (nicht als Framework an sich, eher als Klassensammlung), als sprach es auch nicht dagegen, einen riesen Klotz an Dateien/Klassen zu integrieren, um nur eine Klasse zu nutzen.
$date = new Zend_Date(); $date->setWeek(28)->setYear(2010)->setWeekday(1); $date->setHour(0)->setMinute(0)->setSecond(0); echo $date->getTimestamp(); |
Liefert das erwartete Ergebnis: 1278885600
Ist das nun wirklich ein riesen DateTime Bug oder liegt ein Denkfehler unsererseits vor?
]]>class MyClass implements Interator { private $array; } $obj = new MyClass(); foreach($obj as $key => $val){ ... } |
Damit dieses Konstrukt funktioniert, benötigt die Klasse “MyClass” ein paar weitere Methoden und ein Attribut, welche vom Interface vorgeschrieben werden:
public function rewind(){ $this->pointer = 0; } public function current(){ return $this->array[$this->pointer]; } public function key(){ return $this->pointer; } public function next(){ $this->pointer++; } public function valid(){ return isset($this->array[$this->pointer]); } |
Nun geht das ganze aber auch viel einfacher und mit saubererem Code, mit dem IteratorAggregate Interface und einem schon vorhandenem Iterator (wie dem ArrayIterator). Das Interface ist ziemlich Ähnlich und implementiert ebenfalls Traversable.
class MyClass implements IteratorAggregate { private $array; public function getIterator(){ return new ArrayIterator($this->array); } } $obj = new MyClass(); foreach($obj as $key => $value){ ... } |