Dynamisches Laden von Komponenten in Vue

Hier ein Beispielcode um zur Laufzeit eine Vue Komponente dynamisch dem Dom Tree hinzuzufügen am Beispiel von VueClazyLoad (Image LazyLoading) mit Slots und Properties.

let image_container      = this.$refs.image_container;
let image_path           = 'https://unsplash.it/500';
let clazy_load_component = Vue.extend(VueClazyLoad);
let clazy_load_instance  = new clazy_load_component({
    propsData: { src: image_path }
});
clazy_load_instance.$slots.default = [ clazy_load_instance.$createElement(
                'img',
                {
                    props: {
                        src: image_path
                    }
                })
 ];
clazy_load_instance.$mount();
image_container.appendChild(clazy_load_instance.$el)

Testing in Laravel: File Uploads & Fake Storage Disks

Das Testen von Uploads ist in Laravel hier eigentlich sehr gut dokumentiert. Wichtig zu beachten ist allerdings, das der Name der Fake Storage-Disk dem getesteten Upload Handler zur Verfügung gestellt wird. Wenn man die Default Disk nutzt kann man hierfür in der phpunit.xml z.B. folgenden Eintrag hinzufügen:

<env name="FILESYSTEM_DRIVER" value="test_disk"/>

Im Test muss dann natürlich die Fake Storage-Disk mit dem Namen ‚test_disk‘ verwendet werden:

$user = factory(User::class)->create();
$disk = 'test_disk';
Storage::fake('test_disk');
$file = UploadedFile::fake()->image('avatar.jpg');
$response = $this->actingAs($user)->json('POST', route('uploads.store'), [
    'file' => $file,
]);
$response->assertStatus(200);
Storage::disk('test_disk')->assertExists($file->hashName());

Devilbox, XDebug und Visual Studio Code

  • Unter MacOSX einen Host Alias für das Interface anlegen
    • sudo curl -o /Library/LaunchDaemons/org.devilbox.docker_10254_alias.plist https://raw.githubusercontent.com/devilbox/xdebug/master/osx/org.devilbox.docker_10254_alias.plist
    • Plist aktivieren: sudo launchctl load /Library/LaunchDaemons/org.devilbox.docker_10254_alias.plist
  • XDebug bei Devilbox aktivieren
    • In das devilbox Verzeichnis cfg/php-ini-(PHPVERSION)/ wechseln
    • Die Datei devilbox-php.ini-xdebug kopieren devilbox-custom.ini und danach anpassen (siehe unten )
    • Devilbox neu starten
    • Unter http://localhost/info_php.php sieht man wenn xdebug aktiviert ist
devilbox-custom.ini:

xdebug.default_enable      = On
xdebug.profiler_enable     = On
xdebug.remote_enable       = On
xdebug.remote_autostart    = On
xdebug.remote_handler      = dbgp
xdebug.remote_port         = 9000
xdebug.idekey              = VSCODE
xdebug.remote_log          = /var/log/php/xdebug.log
xdebug.remote_connect_back = 0
xdebug.remote_host         = 10.254.254.254
  • In Visual Studio Code
    • Die Erweiterung ‚php debug‘ aktivieren
    • Links das Debug Panel öffnen und oben auf das Zahnrad klicken und PHP auswählen
    • Danach kann man mit einem Klick auf das grüne Play Symbol den Debugger starten

MAMP Ersatz: Erste Erfahrungen mit Devilbox unter Mac OS X

Bei Devilbox handelt es sich um einen LAMP Stack auf Docker Basis. Für mich ist die Software besonders durch die Vielfalt an integrierten Datenbanken in mehreren Versionen sowie die automatische VHost inklusive Zertifikatsverwaltung extrem praktisch.

Installation

Die Installation einfach wie auf der Webseite beschrieben durchführen. Folgende Anpassungen habe ich in der .env Datei vorgenommen:

  • Bind Port auf 53 geändert, damit ich später auf meine Vhosts ohne Änderungen an der /etc/hosts zugreifen kann (HOST_PORT_BIND=53)
  • Geschmackssache: Top-Level Domain auf „.test“ geändert (TLD_SUFFIX=test)
  • NEW_UID und NEW_GID angepasst

Symlinks zu bestehenden Projekten ermöglichen

Im Devilbox Verzeichnis eine ‚docker-compose.override.yml‘ anlegen mit folgendem Inhalt:

version: '2.1'
services:
  php:
    volumes:
      - $HOME:$HOME
  httpd:
    volumes:
      - $HOME:$HOME

Ohne diese Änderung war bei mir kein Zugriff auf das Host Home Verzeichnis via Symlink möglich

CA-Zertifikat installieren und vertrauen

  1. Schlüsselbundverwaltung öffnen
  2. System-Schlüsselbund auswählen
  3. Ablage / Objekt importieren
  4. Im Devilbox Verzeichnis: Verzeichnis „ca“ – devilbox-ca.crt auswählen
  5. Danach auf das neu importierte Zertifikat klicken: Unter „Vertrauen“ Immer Vertrauen auswählen

Auto-DNS einrichten

  1. In der .env Datei den Bind-Port auf 53 ändern
  2. Unter Einstellungen / Netzwerk / Wlan bzw. Ethernet: Weitere Einstellungen
  3. Oben auf den Raster DNS
  4. Als ersten Nameserver den Localhost hinzufügen (127.0.0.1)

Quickfix

Bei Problemen oder nach Updates sollte man immer zuerst die Docker Container löschen und neu anlegen – folgende Serie an Befehlen ist hier sehr hilfreich:

docker-compose down
docker-compose kill
docker-compose rm -f
docker-compose up

Fazit

Bis jetzt ist Devilbox ein würdiger Ersatz für die von mir bisher verwendete MAMP Lösung. Sie bietet alle Vorteile einer Docker Lösung – der einzige Nachteil bis jetzt für mich ist die aufwändigere Installation – Updates sind jedoch vermutlich wesentlich einfacher mit dieser Lösung als bei MAMP.

localhost Zertifikat (https) in MAMP

Um eine Oauth Login Applikation zu testen, habe ich auf meinem lokalen Mac Testsystem unter MAMP eine HTTPS localhost URL benötigt. Die Konfiguration war erstaunlich einfach.

1.) Zertifikat anlegen – z.B. in /Applications/MAMP/conf/apache
openssl req -days 900 -x509 -out server.crt -keyout server.key \
-newkey rsa:2048 -nodes -sha256 \
-subj '/CN=localhost' -extensions EXT -config <( \ printf "[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth")

2.) SSL im Apache konfigurieren:
2.) a) In /Applications/MAMP/conf/apache/httpd.conf das Laden der Extra-Konfigurationsdatei einkommentieren:
# Secure (SSL/TLS) connections
Include /Applications/MAMP/conf/apache/extra/httpd-ssl.conf

2.) b) in /Applications/MAMP/conf/apache/extra/httpd-ssl.conf den Default Vhost auf folgende Zeile ändern:

<VirtualHost localhost:8890>

3.) Zertifikat der Mac Schlüsselbundverwaltung hinzufügen: Einfach die Datei "/Applications/MAMP/conf/apache/server.crt" in die Schlüsselbundverwaltung hineinziehen und dann die Vertrauenseinstellung ändern

4.) Server neu starten

Das wars.

Quelle: https://letsencrypt.org/docs/certificates-for-localhost/

Update: Port geändert auf 8890 weil 8889 Default Mysql Port von MAMP ist

Instragram per Chrome vom Desktop aus posten

Nachdem ich nun für einen Kunden immer wieder Bilder auf Instagram posten muss und der Weg über die Handy App meiner Meinung nach zu umständlich ist und ich kein Facebook Business Instagram oder irgendwelche externen Tools verwenden will, hier ein Kurztipp um mit Chrome Bilder hochzuladen:

  1. Entwicklertools aktivieren (Mac. Command + Option + I)
  2. Auf die Spalte „Network“ wechseln
  3. Unten bei den Tabs wo auch Console steht auf die 3 Punkte gehen und den Reiter „Network Conditions“ hinzufügen
  4. User Agent IPhone auswählen
  5. Zu www.instagram.com wechseln, die nun die mobile Version samt Uploadmöglichkeit anzeigt

Natürlich gibt es sicher noch weitere, einfachere Möglichkeiten (z.B. mit einem Chrome User Agent Plugin), aber für mich ist diese Variante ausreichend komfortabel.

MYSQL Count Query returniert 0, trotz passender Records

Ausgangspunkt für dieses Problem war eine Liste von Datensätzen die auf einer MYSQL Datenbank basierte. Die Liste wurde auch korrekt ausgegeben, die Anzahl der Records wurde jedoch als 0 angezeigt, was in weiterer Folge zu Problemen mit der Paginierung führte.

Das eigentliche MYSQL Problem

mysql> SELECT Count(*) FROM companies WHERE b_deleted = 0 AND b_derivation = 0;
+----------+
| Count(*) |
+----------+
| 0 |
+----------+
1 row in set (0,02 sec)

Wenn man allerdings

mysql> SELECT * FROM companies WHERE b_deleted = 0 AND b_derivation = 0;
.
.
.
10711 rows in set (1,18 sec)

aufruft, werden alle gesuchten Records korrekt angezeigt.

Lösung

Weitergeholfen hat mir dann ein Eintrag in der MYSQL Bug Datenbank. Des Rätsels Lösung: Man muss einfach den optimizer_switch index_merge_intersection deaktivieren:

mysql> set session optimizer_switch="index_merge_intersection=off";SELECT Count(*) FROM companies WHERE b_deleted = 0 AND b_derivation = 0;
Query OK, 0 rows affected (0,00 sec)

+----------+
| Count(*) |
+----------+
| 10711 |
+----------+
1 row in set (0,32 sec)

Es scheint so, als gäbe es öfter Optimierungen oder Probleme die auf diesen optimizer_switch zurückzuführen sind (z.B. https://www.percona.com/blog/2012/12/14/the-optimization-that-often-isnt-index-merge-intersection/), deswegen habe ich dieses Thema mal auf meine Rechercheliste gegeben.

WordPress Multilanguage: Advanced Custom Fields übersetzen

Damit die Advanced Custom Fields in einer WordPress Multilanguage Umgebung auch im Backend übersetzt sind, muss die Textdomain sehr zeitig geladen sein. Dazu wird die Init Action um folgenden Code erweitert: ($this->locale ist eine von mir definierte Klassenvariable, welche die Textdomain enthaltet.)

load_theme_textdomain($this->locale, get_template_directory() . '/languages');

Danach folgende Hooks hinzufügen:

add_action( 'current_screen', function($current_screen) {
 if(!isset($current_screen->post_type) || $current_screen->post_type != 'acf-field-group') {
 add_filter('acf/settings/l10n', function() { return true; });
 add_filter('acf/settings/l10n_textdomain', function() { return $this->locale; });
 }
 });

POT-Datei automatisch befüllen

Um die Felder zu übersetzen, habe ich meine gettext Bash Script aus dem ersten Teil meiner Multilanguage Anleitung um einen extrem hässlichen Hack erweitert. Dies funktioniert allerdings nur, wenn man die ACF Felder als PHP Code (Pfade müssen natürlich angepasst werden) extrahiert hat – eine Anpassung an einen JSON Export sollte natürlich auch kein besonderer Mehraufwand sein.

echo '<?php ' > gettext_acf.php;
grep -oE "label' ?=> ?'.*'" ../acf/fields.php | sed "s/label' => '/__('/g;s/'$/');/g" >> gettext_acf.php
grep -oE "button_label' ?=> ?'.*'" ../acf/fields.php | sed "s/button_label' => '/__('/g;s/'$/');/g" >> gettext_acf.php
grep -oE "title' ?=> ?'.*'" ../acf/fields.php | sed "s/title' => '/__('/g;s/'$/');/g" >> gettext_acf.php
grep -oE "instructions' ?=> ?'.+'" ../acf/fields.php | sed "s/instructions' => '/__('/g;s/'$/');/g" >> gettext_acf.php

WordPress Multilanguage: Teil 2 – Polylang

Lang ist es her seit Teil 1, inzwischen habe ich mehrere Projekte mit WPML realisiert und nun versuche ich mich das erste Mal mit Polylang.

Hier möchte im Laufe des Projektes meine Erkenntnisse sammeln, vielleicht helfen Sie ja dem einen oder anderem (oder meinem zukünftigen Ich) schneller ans Ziel zu kommen.

Generelle Informationen zu Polylang

Die anwenderseitige Dokumentation von Polylang ist teilweise sehr ausführlich, die API ist jedoch relativ spärlich und weder vollständig noch ausführlich dokumentiert. Auf hookr bekommt man allerdings eine Auflistung aller Hooks und Filter: http://hookr.io/plugins/polylang/2.0.5/.

Die Installation und Konfiguration von Polylang ist eigentlich selbsterklärend und hat allen Anforderungen, die ich an das System hatte, entsprochen.

Um in Polylang Inhalte aus anderen Sprachen abzufragen, kann man den Query Parameter „lang“ auf einen (oder auch mehrere) gewünschten Language-Slug setzen, um eine Abfrage in allen Sprachen durchzuführen setzt man den Parameter auf einen leeren String.

Polylang kümmert sich bei Ajax Requests selbst um das Handling der Sprache, falls das Interface aber trotzdem nicht vollständig übersetzt ist könnte dieser Tipp eventuell weiterhelfen.

Übrigens: Die Backend Sprache des Benutzers kann man im Benutzerprofil einstellen.

Polylang Sprache aktiv im Code setzen

Ein kleiner Hack um die Polylang Sprache aktiv zu setzen wäre:

function setLanguage($language) {
	if(function_exists('PLL')) {
		PLL()->curlang = PLL()->model->get_language( $language);
	}
}

Ich denke zwar nicht, dass eine Änderung der Sprache auf diese Art Best-Practice ist, aber es hat mir ein Problem gelöst und vielleicht hilft es ja auch anderen.

ACF: Dropdown mit den aktiven Polylang Sprachen

Falls man eine Advanced Custom Fields Select Box mit den aktiven Sprachen benötigt, kann man das folgendermaßen lösen:

public function getLanguages() { 
	$output = array();
	if(function_exists('pll_languages_list')) {
		$translations = pll_languages_list(array("fields" => false));
		foreach($translations as $translation) {
			$output[$translation->slug] = $translation->name;
		}
	}
	return $output;
}
public function acfLanguageField($field) {
	$field['choices'] = array("" => "- " . __("Keine", $this->locale) . " -");
	foreach($this->getLanguages() as $slug =>$language) {
		$field['choices'][$slug] = $language;
	}
	return $field;
}
add_filter('acf/load_field/name=language', array($this,'acfLanguageField'));

 

Copy/Sync von Advanced Custom Fields / Post Meta Werten

Dafür stellt Polylang einen Filter pll_copy_post_metas zur Verfügung, der kontrolliert, welche Felder kopiert oder gesynct werden sollen. Dadurch können zu kopierende Felder ergänzt oder entfernt werden.

Definition des Filters:
add_filter('pll_copy_post_metas', 'polylang_sync_fields', 20,5);

Der Filter sieht dann zum Beispiel so aus:

function polylang_sync_fields($keys, $sync, $from, $to, $lang) {
	$multilanguage_copy_fields = array(
		"page" => array(
			"header_gallery"         => "header_gallery"
		),
		"my_event" => array(
			"multiple_days" => "multiple_days",
			"from_date"     => "from_date",
			"from_time"     => "from_time",
			"to_date"       => "to_date",
			"to_time"       => "to_time",
			"date"          => "date",
			"time"          => "time",
		)
	);
	$post_type = get_post_type($from);
	$keys      = array_merge($keys, $multilanguage_copy_fields(get_post_type($from)));
	return $keys;
}

In diesem Beispiel habe ich ein Array mit zu kopierenden Feldern abhängig vom Post Type definiert. Um den Fakt, dass diese Felder kopiert werden, auch für den Benutzer ersichtlich zu machen habe ich das Array als Klassenvariable gesetzt und noch folgenden ACF Hook ergänzt:

if(function_exists('PLL')) {
add_filter('acf/prepare_field',array($this,'acfPrepareField'));
}
public function acfPrepareField($field) {
$fields = $this->multilanguage_copy_fields(get_post_type());
if(isset($fields[$field['_name']])) {
$field['instructions'] .=' (Multilanguage)';
}
return $field;
}

Dadurch sieht auch der Admin sofort welche Felder zwischen den Sprachen kopiert werden und welche nicht.

Wie man die Advanced Custom Fields auch im Backend übersetzt, habe ich hier zusammengefasst.

Zusammenfassung

Die Umsetzung des Projektes mit Polylang war eigentlich relativ problemlos und insgesamt weniger aufwändig als mit WPML. Für Anfänger ist der Einstieg in Polylang sicher schwieriger als mit WPML, den Mehraufwand ist es aber meines Erachtens definitiv wert. Insgesamt empfinde ich derzeit die Konfiguration aber auch die Hooks/Filter Struktur als aufgeräumter und logischer. Die perfekte, problemlose Integration von ACF ist für mich sicherlich hauptausschlaggebend, das nächste WordPress Multilanguage Projekt wieder mit Polylang umzusetzen.

Das wars erstmal mit meinen Polylang Erkenntnissen, nun steht als nächstes die Übersetzung eines WooCommerce Webshops mit Polylang auf dem Plan.

WordPress: Ajax Requests verwenden die falsche Sprache

Da Admin Requests als der Benutzer ausgeführt werden, der eingeloggt ist, werden auch dessen Spracheinstellungen verwendet. Um Strings in der Sprache der Seite anzuzeigen muss man also zuerst switch_to_locale( get_locale() ); aufrufen.

Nähere Infos dazu findet man unter https://make.wordpress.org/core/2016/11/07/user-admin-languages-and-locale-switching-in-4-7/