Die Überschrift ist etwas irreführend. Es geht darum, dass referenzierte Templates nach dem Muster
bundle:section:template.format.engine
aufgebaut sein müssen. Der Teil “template” muss (?) dabei den relativen Pfad enthalten, zB
MyProjectBundle::pages/testpage.html.twig
, andernfalls kommt es zu der Fehlermeldung aus der Überschrift. Nun will man vielleicht nicht an drölf Stellen diesen Pfad (oder das Bundle!) angeben (sei es der Zentralisierung wegen, sei es aus Faulheit beim Tippen). Lösung: FilesystemLoader, bzw. das geerbte Twig_Loader_Filesystem->addPath(…):
Ein Hook ist simpel: Von der bekannten Modulstruktur benötigt man nur die /config/config.php, in der man Klasse und Funktion an den gewünschten Hook hängt:
Direkt analog zur Erweiterung bestehender Content Elemente mit neuen Feldern funktioniert das Erstellen eigener Content Elemente. Man hängt sich nur in andere (bzw. weitere) Arrays. Ich orientiere mich im Folgenden an diesem Tutorial, sowie an dieser Erweiterung.
Also, gegeben sei dieselbe Dateistruktur wie für neue Felder, in einem eigenen Modul. In /config/config.php deklariert man sein neues Content-Element:
[Disclaimer: Ja, TYPOlight heißt schon einige Zeit Contao – die Installation, die ich hier vor mir habe, heißt aber noch TYPOlight. Aktuell ist 3.3.3, Beta ist 4.0, ich habe 2.8.1. Eine Übersicht über alle Versionen gibt es hier.]
Es gibt eine Doku zum Hinzufügen von Feldern. Mein Problem damit: Die Anleitung bezieht sich auf die Mitgliederverwaltung. Die Mitgliederverwaltung hat aber eine eigene Tabelle (tl_member) in der Datenbank, sowie eine eigene Palette. Interessanter ist es, eine neues Feld bsplw. dem Content Element “Image” hinzuzufügen, den alle Content Elemente teilen sich die Tabelle tl_content, sowie eine gemeinsame Palette. Wie bekommt man ein Feld, das nur für Images angezeigt wird?
Analog zum oben verlinkten Beispiel: Über ein Modul, und zwar mit folgender Dateistruktur:
tl_content_custom/
config/
config.php
database.php
dca/
tl_content.php
languages/
de/
tl_content.php
en/
tl_content.php
templates/
ce_image_custom.tpl
ContentImageCustom.php
Im ersten Schritt hängt man sich in das CMS:
In config/config.php registriert man eine eigene Klasse für den Typ “Image”:
In config/database.sql definiert man die zusätzlich benötigten Felder der Tabelle tl_content – und man darf sich nicht daran stören, dass das in einem CREATE TABLE passiert:
1
2
3
4
CREATETABLE`tl_content`(
`is_retina`char(1)NOT NULLdefault'',
PRIMARY KEY(`id`)
)ENGINE=MyISAMDEFAULTCHARSET=utf8;
Die Felder werden dann über http://mydomain.tld/typolight/install.php unter dem Punkt “Tabellen aktualisieren” aktualisiert; dort wird das CREATE in ein ALTER geändert.
In dca/tl_content.php deklariert man, wann das neue Feld angezeigt wird (“dca” steht für “Data Container Array“), also dass es nur für Images verwendet werden soll. Dazu ergänzt man “die Palette” und “die Felder”. Die Palette ist quasi ein Sub-Set aller für Content-Elemente verfügbaren Felder. Wenn wir also das Content Element “Image” nur um vorhandene Felder erweitern wollten, dann bräuchten wir die Felder nicht ergänzen. Das ist hier nicht der Fall (wir haben oben ein neues Feld in die Datenbank eingefügt), deshalb konfigurieren wir das neue Feld is_retina und hängen es (ja: per str_replace ^^) an die kommaseparierte Liste von erlaubten Feldern für den Typ “Image”. Dabei kann man sich an /system/modules/backend/dca/tl_content.php orientieren, dort sind die Original-Deklarationen der Felder und Paletten zu finden.
In languages/de/tl_content.php und analog in languages/en/tl_content.php kommen alle benötigten (=in der Feldkonfiguration referenzierten) Texte, das ist hier nur einer:
1
$GLOBALS['TL_LANG']['tl_content']['is_retina']=array('ist ein Retinabild','Bitte legen Sie fest, ob es sich um ein Retinabild handelt.');
Im zweiten Schritt erstellt man dann endlich die eigene Logik. In config/config.php haben wir die Klasse ContentImageCustom referenziert (s.o.). Die wird jetzt direkt im Modulordner angelegt; von dort wird sie automatisch geladen. Der Einfachheit halber erweitern wir ContentImage, und der einzig nötige Code ist die Referenzierung unseres eigenen Templates, in dem wir dann auf das neue Feld reagieren:
1
2
3
4
classContentImageCustomextendsContentImage
{
protected$strTemplate='ce_image_custom';
}
Das genannte Template ist templates/ce_image_custom.tpl, und darin sieht’s dann so aus:
(Das ist natürlich nur ein Snippet; insgesamt habe ich den Code von /system/modules/frontend/templates/ce_image.tpl geklaut, und mit obigem Snippet erweitert.)
Wie oft habe ich schon die Email-Klasse verwendet, nur um festzustellen, dass sie nicht funktioniert? Sicher… zwei mal 🙂 Deshalb: Sie funktioniert nicht. Stattdessen verwende ich PHPMailer, bzw. folgende Dateien davon:
Eingebunden werden die ein einem Helper (/application/helpers/email_helper.php):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?phpif(!defined('BASEPATH'))exit('No direct script access allowed');
Über die FB-API bekommt man vergleichsweise kryptische Image-URLs, nämlich die Direktlinks in deren CDN. Diese haben drei Probleme:
1. Die URL zeigt auf die gering aufgelöste Version des Bildes, zu erkennen am “_s.jpg” (“s” für “small). Der gängige Ansatz, um an die hochauflösende Version zu kommen, besagt, dass man “_s” durch “_n” (für “normal?) zu ersetzen (einige empfehlen auch “_o” für “original”). Das funktioniert meistens, aber eben nicht immer. Korrekt scheint dieser (selbstverständlich undokumentierte) Ansatz, der quasi analog zur Profilbild-URL funktioniert:
<object_id> kommt aus der API. Achtung: Das funktioniert nur für Posts vom Typ “photo”; Posts vom Typ “link” müssen nach wie vor über die URL im Feld “picture” gehen! Und damit kommen wir direkt zum nächsten Problem:
2. Facebook nutzt Proxy-Skripte zur Generierung der niedrig aufgelösten Bilder. Zu erkennen an …/safe_image.php?…&url=<url_codiert> oder …/app_full_proxy.php?…&src=<url_codiert>. Diese muss man rausfilter, die tatsächliche URL dekodieren:
3. Profilbilder von altersbeschränkten Seiten sind ebenfalls altersbeschränkt 🙂 Für Zugriff muss man den Access Token anhängen (Quelle finde ich gerade nicht mehr wieder):
Es gibt im Internetz verschiedene Ansätze für dieses Problem; die meisten basieren auf einem eigenen Loader. Meiner Erfahrung anch funktionieren die alle nicht (in allen Situationen), und leider scheint die einzige echte Alternative zu sein, alle Models ohne Namen zu laden. Also lieber
MySQL kann UTF-8… falsch. Besser gesagt: Ungenau. MySQL kann UTF-8, aber das heißt da anders:
Ein Zeichen in UTF-8 ist (bis zu) vier Bytes lang, “UTF-8” in MySQL kann aber nur bis zu drei. Deswegen fällt der Unterschied oft gar nicht auf, oder erst beim ersten Zeichen, das vier Bytes braucht. Will man echtes UTF-8, muss man einiges umkonfigurieren; in Kurzform:
SQL muss in der Version 5.3.3+ vorhanden sein, siehe Link oben
Jede Datenbank, jede Tabelle (ggf. jede Spalte) müssen auf “CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci” umgestellt werden
Dabei zu beachten: InnoDB kann wohl nur 767 Bytes pro Index Schlüssel. Wenn man vier (statt drei) Bytes pro Zeichen rechnet, sind das bsplw. nur 191 (statt 255) Zeichen!
Das gilt auch für Typen wie TINYTEXT, die wohl nur 255 Bytes = 63 Zeichen bei 4 Byte/Zeichen speichern können. Ggf. ist hier ein anderer Typ zu wählen
ggf. Server, Client, etc. umstellen (dabei hilft “SHOW VARIABLES WHERE Variable_name LIKE 'character\_set\_%' OR Variable_name LIKE 'collation%';“)
ggf. Datenbank reparieren und optimieren (war bei mir nicht nötig, ich habe alle Tabellen neu angelegt):
Insbesondere bei Punkt 3 hängt es bei CodeIgniter, btw. Wenn man in der Datenbank-Config
1
2
$db['default']['char_set']='utf8';
$db['default']['dbcollat']='utf8_unicode_ci';
stehen hat, dann ist das ebenfalls nicht das echte UTF-8. Stattdessen korrekt ist:
1
2
$db['default']['char_set']='utf8mb4';
$db['default']['dbcollat']='utf8mb4_unicode_ci';
Update
Das Problem betrifft auch mysqldump: Will man die eine utf8mb4-Datenbank dumpen, muss man das explizit sagen, denn mysqldump nutzt per Default nur utf8 (Quelle):
Auf xuad.net gibt es eine entsprechende Anleitung für einen DDNS-Service, auf GitHub liegen die Sourcen. Dabei ist einiges zu beachten:
Man muss dieses Skript unter einer anderen (Sub-) Domain als das DDNS-“Ziel” laufen lassen, klar. Sobald man die DDNS-Domain dynamisch um-mapped, ist das Skript sonst nicht mehr erreichbar.
Wenn man die IP auslesen möchte, statt sie zu übergeben (siehe Comments in dem Post), und der Server nur schon IPv6 spricht, sollte man den hier beschriebenen “Fix” verwenden, denn INWX erwartet eine v4-Adresse:
Darüber hinaus muss bei INWX ein expliziter Eintrag für die Subdomain angelegt sein. Das Skript ruft diesen bestehenden Eintrag ab, extrahiert eine ID, und updated dann über diese ID. Gibt es keinen Eintrag, gibt es auch keine ID, und das Update kann nicht durchgeführt werden. Ein Wildcard-Eintrag genügt nicht.
Dann sollte das klappen. Falls nicht, kann man im DDNSManager.php Debugging einschalten, um Request und Response an INWX ausgeben zu lassen:
1
$domrobot->setDebug(true);
Und/oder PHP-Fehler anzeigen, klar:
1
2
error_reporting(E_ALL);
ini_set("display_errors",1);
Den Service aufrufen (lassen)
Interessanter ist die Frage, wie und wann sich das NAS dann registriert. Der hauseigene DDNS-Client bietet zwar drölfzig Anbieter zur Auswahl, aber kein Feld für custom Skripte 🙁
Erste Idee: cron. Zwar bringen die Synologys cron mit, allerdings will man sich mit einem regelmäßigen Cronjob ja nicht seinen Ruhezustand versauen.
Hier wäre übrigens möglich, die IP zu übergeben, dann muss man sie nicht auslesen: Neben __USERNAME__ und __PASSWORD__ sind __HOSTNAME__ und __MYIP__ möglich. Trotzdem: Die IPv6-Übersetzung scheint angebracht, wie gesagt wegen INWX.
htaccess-Authentifizierung habe ich hier nicht hinbekommen, sobald sich das ändert, gibt es hier ein Update.
Nun kann man im DSM seinen neuen DDNS-Service auswählen; “Hostname” kann in diesem Fall mit irgendwas gefüllt werden, da er in der Query-URL ja nicht verwendet wird