Kategorie: PHP

Wie kann ich die Standard-PHP-Version auf meinem Synology NAS ändern?

– ggfs. SSH in der Systemsteuerung des NAS aktivieren
– via Putty oder Bitvise oder einen anderen SSH-Client auf dem NAS einloggen
– folgende Befehle abschicken

Auf einem Synology NAS unter DSM lassen sich per Webserver-Script keine Dateien anlegen

Wenn sich bspw. per PHP auf dem Webserver eines Synology NAS keine Dateien anlegen lassen, ist der Grund meist eine mangelnde Rechtezuweisung für die Gruppe “http”.

Meist gibt es dann hübsche Fehlermeldung Warning: fopen(...): failed to open stream: Permission denied in /volume1/web/xyz.php

Um das Problem zu beheben, sind nur wenige Schritte notwendig:

  1. im Backend des NAS die Systemsteuerung öffnen
  2. “Gemeinsamer Ordner” anklicken
  3. in der Liste den betreffenden Ordner anklicken
  4. Button “Bearbeiten” anklicken
  5. im sich öffnenden Fenster den Reiter “Berechtigungen” anklicken
  6. in der Selectbox oben links “Lokale Gruppen” auswählen
  7. für “http” die Checkbox “Lesen/Schreiben” markieren
  8. den Button “OK” anklicken

Nunmehr sollte es keine Probleme mehr geben.

Ein paar wissenswerte Anmerkungen zu IMAP mit PHP

Da ich kürzlich ein Hostingpaket eines Kunden von einem Hoster zum nächsten umziehen sollte, stellte sich das Problem der Postfach-Migration.

Da der Kunde alle E-Mails per IMAP abruft, waren alle Mails auf den Servern des Alt-Hosters gespeichert. Es handelte sich um eine hohe zweistellige Anzahl an Postfächern, von denen viele eine fünfstellige Anzahl an E-Mails beherbergten und Größen im zweistelligen GB-Bereich erreichten.

Früher hätte man das mit IMAPSize relativ einfach erledigen können, aber seit SSL üblich ist, funktioniert IMAPSize halt meist nicht mehr; so auch in diesem Fall. Thunderbird & Co. strecken bei solchen Postfächern ebenfalls meist die Hufe.

Eine Suche nach “IMAP Mail Migration / Umzug” brachte als brauchbare Lösung nur Mailstore Server zu Tage. Allerdings bei dieser Anzahl an Postfächern auch nicht direkt zu einem kleinen Kurs für einen kleineren Gewerbetreibenden.

Nun gut, dann halt per Script. Da gibt es ja bestimmt etwas brauchbares. In Perl gibt es tatsächlich etwas, aber Perl gehört nicht zu meinen üblichen Spielwiesen. Und es gibt auch etwas in PHP. Allerdings war das relativ grob zusammengeschustert und tat nur ansatzweise, was es sollte. Also blieb nur, die Anregungen aus den vorhandenen Scripten aufzunehmen und selbst etwas zu bauen.

Aufgrund der Größe der Postfächer war klar, dass es mit den begrenzten Scriptlaufzeiten auf dem Webserver sicherlich keinen Erfolg geben wird. Also muss man auf die Shell ausweichen.

Ohne zu sehr ins Detail gehen zu wollen, ergaben sich dabei ein paar interessante Probleme:

1. Die Message-IDs innerhalb einer Mailbox (nicht innerhalb des Postfachs) waren nicht eineindeutig, obwohl das so sein sollte.
Warum das wichtig ist, wenn man die Mails doch nur von einem Postfach ins andere Schaufeln möchte? Nun, der Umzug eines Hostingpakets hängt zeitlich von vielen Faktoren ab. Aufgrund der Größe der Postfächer wurden die Postfächer schon im Vorfeld kopiert. Anschließend sollten nur noch inkrementelle Updates gefahren werden.
Um inkrementelle Updates zu realisieren, muss man die E-Mails im alten und neuen Postfach jedoch eindeutig identifizieren können.
Na dann halt eine Kombination aus Message-ID und Zeitstempel im Header der E-Mail. Lustig, denn damit kommen wir zum zweiten Problem:

2. Nicht jede E-Mail hat einen Zeitstempel (Eigenschaft “Date / date” im Header).
Also war eine eindeutige Identifizierung über eine Kombination aus Message-ID und Zeitstempel auch nicht ohne weiteres möglich.
Glücklicherweise verhalten sich die Mail-Server verlässlicher als die absendenden Mail-Clients und -Generatoren. Sie legen tatsächlich im Header jeweils einen “Received”-Eintrag an. Leider geben die PHP-IMAP-Funktionen genau diesen Wert (bzw. diese Werte, da es mehrere Received-Einträge im Header gaben kann) nicht zurück. Es bleibt also nur der Weg, den Header selbst zu zerlegen und den letzten Received-Zeitpunkt zu extrahieren.
Nun kann man anhand der Message-ID und des Zeitstempels alle Mails im Ursprungs- und Ziel-Postfach eindeutig identifizieren und verhindern, dass E-Mails mehrfach übertragen werden.

Zumindest bei E-Mails, die keinen Date-Eintrag im Header haben, sollte man dann beim Aufruf von “imap_append” den Zeitstempel mit übergeben. Interessanter Weise wird im RFC 3501 immer auf RFC 2822 verwiesen. Im Header sind alle Datumswerte auch ganz offensichtlich gemäß der PHP-Konstante DATE_RFC2822 formatiert. Wenn man nun versucht, imap_append mit einem Zeitstempel gemäß RFC 2822 zu füttern, bekommt man die höfliche Notiz, dass diese Angabe ungültig ist. Auch ein reiner Unix-Zeitstempel führt nicht zum Erfolg.
Stattdess muss es ein String gemäß dem Muster date( "d-M-Y H:i:s O", #Unix-Zeitstempel# ); sein. Falls das irgendwo brauchbar dokumentiert ist, war ich jedenfalls nicht in der Lage, es zu finden.

3. Ein weiterer Spaß ist die Kodierung und Struktur der Mailboxnamen (für gewöhnlich als Ordner wahrgenommen). Gemäß RFC 3501 wird eine spezielle Version von UTF-7 für die Kodierung verwendet. Das ist immer dann von Bedeutung, wenn auf dem Quellserver Umlaute für die Mailboxen verwendet werden. Im deutschsprachigen Raum ist das relativ häufig der Fall, da oftmals für die Standard-Mailbox “Drafts” das deutsche “Entwürfe” verwendet wird.
Auch der Delimiter (Hierarchie-Trenner) verdient Beachtung. Während bei der Anlage neuer Mailboxen grundsätzlicher der Slash “/” als Trenner verwendet werden kann, gilt dies auf dem Quellserver für den lesenden Zugriff nicht. Hier heißt es unbedingt mit “imap_getmailboxes” die Mailboxen abzufragen, wobei die Eigenschaft “delimiter” mit übergeben wird, welche man dann zwingend als Trenner auf dem Quellserver verwenden muss.

4. Die Funktion “imap_rfc822_parse_headers” schmiert sehr elegant mit einem “Fatal error” ab, wenn der Header einer E-Mail die Größe von 16 KB übersteigt. Üblicherweise sind Header eher um die 4 KB groß, aber ganz gelegentlich gibt es halt mal Ausreißer. Da hilft nur, den zu übergebenden Header abzuschneiden…

Nachdem alle diese Fallstricke umgangen waren, stand einer erfolgreichen Mail-Migration nichts mehr im Weg. ABER!

Tatsächlich war bei einigen E-Mails der Aufruf von imap_append nicht erfolgreich. Es gab die freundliche Warnung “Can’t save a zero byte message (0.001 + 0.000 secs)”. Zu Analysezwecken habe ich diese Mails dann in EML-Dateien geschrieben und näher angeschaut. Es handelte sich bei diesen E-Mails ausschließlich um Spam-Mails, die nicht wohlgeformt waren.
Damit der Kunde die Diskrepanz zwischen Quell- und Zielpostfach versteht, ließ ich also ein Logfile mitlaufen und exportierte nicht migrierbare in EML-Dateien, um diese zu übergeben.

Wie kann ich aus einem WordPress-Plugin heraus auf eine andere Datenbank zugreifen

Gelegentlich möchte man auf eine andere Datenbank zugreifen als auf die, auf der WordPress läuft.

Glücklicherweise kann man sehr leicht eine neue Instanz von wpdb anlegen:

WordPress WPDB (wp-db.php) public functions

public function init_charset()

public function determine_charset( $charset, $collate )

public function set_charset( $dbh, $charset = null, $collate = null )

public function set_sql_mode( $modes = array() )

public function set_prefix( $prefix, $set_table_names = true )

public function set_blog_id( $blog_id, $network_id = 0 )

public function get_blog_prefix( $blog_id = null )

public function tables( $scope = ‘all’, $prefix = true, $blog_id = 0 )

public function select( $db, $dbh = null )

public function _escape( $data )

public function escape_by_ref( &$string )

public function prepare( $query, $args )

public function esc_like( $text )

public function print_error( $str = ” )

public function show_errors( $show = true )

public function hide_errors()

public function suppress_errors( $suppress = true )

public function flush()

public function db_connect( $allow_bail = true )

public function parse_db_host( $host )

public function check_connection( $allow_bail = true )

public function query( $query )

public function placeholder_escape()

public function add_placeholder_escape( $query )

public function remove_placeholder_escape( $query )

public function insert( $table, $data, $format = null ) {

public function replace( $table, $data, $format = null )

public function update( $table, $data, $where, $format = null, $where_format = null )

public function delete( $table, $where, $where_format = null )

public function get_var( $query = null, $x = 0, $y = 0 )

public function get_row( $query = null, $output = OBJECT, $y = 0 )

public function get_col( $query = null , $x = 0 )

public function get_results( $query = null, $output = OBJECT )

public function get_col_charset( $table, $column )

public function get_col_length( $table, $column )

public function strip_invalid_text_for_column( $table, $column, $value )

public function get_col_info( $info_type = ‘name’, $col_offset = -1 )

public function timer_start()

public function timer_stop()

public function bail( $message, $error_code = ‘500’ )

public function close()

public function check_database_version()

public function supports_collation()

public function get_charset_collate()

public function has_cap( $db_cap )

public function get_caller()

public function db_version()

 

$wpdb->insert funktioniert nicht ohne erkennbaren Grund

Die Insert-Funktion der WordPress-eigenen Datenbankklasse ist bekanntermaßen sehr nützlich.
Allerdings gibt es eine wenig dokumentierte Einschränkung, welche einen an den Rand der Verzweiflung treiben kann.

Sofern man nämlich bspw. an ein als VARCHAR definiertes Feld mit einem String über die $wpdb->insert-Funktion füllen möchte, darf dieser String nicht länger als sein, als in der MySQL-Tabelle definiert. Ansonsten tut WordPress nämlich einfach gar nichts; keine Fehlermeldung, keine last_query, niente!

Zu allgemein? Wenn ich ein Feld “plz” mit VARCHAR(5) definiert habe und über $wpdb->insert “123456” zu schreiben versuche, passiert einfach gar nichts.

Nun kann man natürlich danach schreien, dass man doch alle Daten vor dem Schreiben validieren muss, aber MySQL selbst hat sich da gar nicht so. MySQL schneidet den String einfach ab und gut ist. Ein Insert über $wpdb->query funktionert natürlich, da es ja nur ein Wrapper für eine MySQL-Abfrage ist.

Wie kann man bei DomainFactory auf die Datenbank in einem anderen Auftrag per PHP zugreifen?

Bei vielen Hostern kann man auf alle Datenbanken des Hosters, so man die Zugangsdaten hat, zugreifen. Bei einigen Hostern kann man sogar externen Zugriff auf die Datenbanken gestatten.
Bei DomainFactory geht das leider nicht ohne Weiteres. Auch wenn eine Konfigurierbarkeit der Zugriffsmöglichkeiten wünschenswert wäre, muss man doch auf die etwas unhandliche Variante des SSH-Tunnels zurückgreifen.

Sofern man auf der Konsole per SSH arbeitet, ist das nicht weiter schwierig. Auf Server A einfach

eingeben, dass passende Kennwort für den SSH-Benutzer auf Server B eingeben und fertig.

Wenn man jetzt auch gleich noch den Datenbankzugriff auf eine MySql5-DB auf Server B haben möchte, sieht das dann so aus:

Anschließend lässt sich über den Host 127.0.0.1 auf Port 3307 auf die Datenbanken auf Server B zugreifen.

Wenn man nun das Ganze in PHP abbilden möchte, lernt man schnell, dass man zwar mit shell_exec() die SSH-Verbindung anstoßen kann, aber eben nicht das Kennwort übergeben kann.

Die Lösung ist eigentlich ganz einfach. Man generiert entsprechende Schlüssel und anschließend funktioniert der Zugriff ohne Eingabe eines Kennwortes.

Folgendes ist zu tun.

Auf Server A (von dem aus auf Server B zugegriffen werden soll) auf der Konsole:

eingeben und alle Abfragen mit [RETURN] bestätigen.
Es werden dann im Verzeichnis .ssh zwei Dateien abgelegt. Der private Schlüssel id_rsa und der öffentliche Schlüssel id_rsa.pub.

Die Datei id_rsa.pub muss anschließend auf den Server B in das Verzeichnis .ssh kopiert werden und anschließend in authorized_keys umbenannt werden. Sollte die Datei authorized_keys schon bestehen, so ist die id_rsa.pub mit der authorized_keys zu konkatenieren.
Anschließend muss das Verzeichnis .ssh noch auf die Rechte 0700 und die Datei authorized_keys auf die Rechte 0600 gesetzt werden.

Ein Test auf der Konsole von Server A mit

sollte nunmehr eine SSH-Vergbindung mit Server B ohne Rückfrage nach einem Passwort aufbauen.

Sofern das alles funktioniert, kann man nunmehr per PHP den SSH-Tunnel aufbauen und sich dann mit der Datenbank verbinden:

Das “2>&1” kann man weglassen. Es sorgt lediglich dafür, dass in Verbindung mit der Option -v die Ausgaben der Konsole tatsächlich zurückgegeben werden.
Bei der mysqli-Verbindung muss zwingend 127.0.0.1 statt localhost angegeben werden, da ansonsten versucht wird, eine Socket-Verbindung statt einer Verbindung über Port 3307 aufzubauen, was natürlich nicht funktioniert.

Wie kann ich eine WordPress-Installation möglichst schnell und elegant umziehen?

Neben der “klassischen” Methode (Dateien einzeln per FTP herunterladen, DB-Backup per PHPMyAdmin erstellen) gibt es auch noch den etwas eleganteren Umweg über die Kommandozeile.

Oftmals hat man jedoch in einfacheren Hostingpaketen keinen Zugriff per Telnet oder SSH. Allerdings lassen sich fast immer trotzdem Systembefehle absetzen.

Diesen Umstand kann man sich für eine schnelle Erstellung von Backups von Code und DB zu nutze machen.

Backup DB:
system("mysqldump --opt -Q -h DB-ALT-HOST -u DB-ALT-USER -p DB-ALT-NAME --password=\"DB-ALT-PASSWORT\" > ./dbdump.sql",$ret);

Backup Code:
system("tar -zcf ./backup.tar.gz ./*",$ret);

Anschließend werden die beiden erzeugten Dateien per (S)FTP heruntergeladen und auf den neuen Server hochgeladen.

DB einspielen:
system("mysql -h DB-NEU-HOST -u DB-NEU-USER --password=\"DB-NEU-PASSWORT\" DB-NEU-NAME < ./dbdump.sql");

Code entpacken:
system("tar -xvzf ./backup.tar.gz ",$ret);

Anschließend müssen in der Datei “wp-config.php” noch die neuen DB-Daten eingetragen werden.

Hinter den DB-Daten müssen dann noch die beiden Zeilen für die neue Domain eingetragen werden:
define('WP_SITEURL', 'http://www.neue-adresse.de');
define('WP_HOME', 'http://www.neue-adresse.de');

Weiterhin sollte noch folgende Zeile eigefügt werden:
define('RELOCATE', true);

Anschließend muss die Login-Seite aufgerufen werden:
http://www.neue-adresse.de/wp-login.php

Diese Zeile wird noch vor dem Ausfüllen des Login-Formulars wieder entfernt.

Im Dashboard dann ein Plugin, z.B. “Better Search and Replace”, installieren und die alten URLs in der Datenbank durch die neuen URLs ersetzen.

WordPress zeigt nach dem Umzug keine Stylesheets oder Bilder an oder beim Versuch auf wp-admin zuzugreifen wirft der Server einen Fehler 403

Insbesondere beim Umzug zu einem neuen Hoster kann es vorkommen, dass beim Kopieren oder Entpacken der Dateien die Verzeichnisrechte nicht korrekt übernommen werden.

Dieses Problem lässt sich relativ leicht beheben, indem in einem FTP-Programm alle Verzeichnisse (rekursiv!) die Rechte 755 und alle Dateien die Rechte 644 zugewiesen bekommen.

Sollte man Zugriff auf die Serverkonsole, bspw. per SSH, haben, kann das Problem deutlich schneller gelöst werden, indem im WP-Stammverzeichnis folgende Befehle ausgeführt werden:
find ./ -type d -exec chmod 755 {} +
find ./ -type f -exec chmod 644 {} +

Mails per PHP von einer Synology Diskstation verschicken

mail() funktionert ja bekanntlich unter Synology DSM auf einer Diskstation nicht.

Relativ komfortabel kann man jedoch Mails mit dem PEAR-Mail-Paket via SMTP versenden.
Dazu sind folgende Schritte nötig:

  1. Über den Paketmanager PEAR installieren
  2. Per telnet oder ssh auf der Diskstation eine Konsole öffnen und abschicken
  3. Eine Mail per Mail verschicken:

Update für DSM6

Der PEAR-Pfad muss an zwei Stellen ergänzt werden:

1. Hauptmenü => WebStation => PHP-Einstellungen => Häkchen bei „PHP open_basedir benutzerspezifisch anpassen“ setzen und bei „open_basedir:“ am Ende :/volume1/@appstore/PEAR/ ergänzen

2. auf „Erweiterte Einstellungen“ klicken => Reiter „Kern“ anklicken => den Wert für „include_path“ auf „.:/volume1/@appstore/PEAR/“ anpassen und „OK“ klicken

Ggfls. muss der Webserver noch per SSH neu gestartet werden:
sudo synoservice --restart pkgctl-WebStation

WordPress per PHP direkt von wordpress.org als Zip-Datei auf Server herunterladen und entpacken

Um Prototypen für Kunden zu bauen oder auch für eigene Bastelprojekte muss ich immer mal wieder WordPress neu aufsetzen.

Bei vielen Hostern geht das heute auch als App oder Service relativ vollautomatisch, aber oftmals hat man dann nicht volle Schreibrechte in den WordPress-Verzeichnissen. Also installiere ich lieber selbst.

Da das Herunterladen auf den eigenen Rechner, das lokale Entpacken der Zip-Datei und der anschließende Upload der Daten relativ unspannend und zumindest im FTP-Transfer auch zeitaufwändig ist, lässt sich dieser Vorgang mit einem kleinen Skript weitestgehend automatisieren.

  1. aktuelle deutsche Version von WordPress als Zip-Datei mit cURL direkt auf den Server ziehen
  2. Zip-Datei entpacken
  3. Verzeichnisse und Dateien aus dem Unterordner “wordpress” auf die aktuelle Verzeichnisebene verschieben
  4. WordPress-Installation anstoßen

Als Skript sieht das dann so aus:

Noch ein paar Worte zur Erläuterung:

  • Die Klasse ZipArchive sollte ab PHP 5.2 zur Verfügung stehen.
  • Das exec-Kommando kann bei anderen Hostern anders heißen, bspw. system etc.