Wer kennt das nicht? Man schreibt einen tollen Beitrag und was fehlt? Genau: Ein Titelbild. Wenn man dann nicht selbst zum Fotoapparat greifen will (wie ich das hier oft tue), stellt sich die Frage: Wie findet man passende Bilder und wie baut man diese rechtssicher in sein Blog oder seine Website ein? Und da ich gerade auf der re:publica war und dort vom Lizenzhinweis-Generator gehört habe, schien es mir sinnvoll, auf das Thema Bilder und Internet noch einmal einzugehen.
Wo finde ich Bilder?
Zunächst also die Frage: Wo finde ich Bilder? Und wie jeder weiß, lautet die Antwort: Im Internet.
Dass bei der Nutzung von Bildern aus dem Netz allerlei rechtliche Gefahren drohen, wissen dabei sicherlich auch schon viele Leute. Bekannt dürfte auch sein, dass man in solchen Fällen nach Bildern mit einer Creative Commons (CC)-Lizenz Ausschau halten sollte. Denn Bilder mit CC-Lizenz dürfen unter bestimmten Bedingungen frei verwendet werden. Eine der Quellen, wo es viele solcher CC-lizensierten Bilder gibt, ist Wikimedia Commons.
Primär dient Wikimedia Commons als Mediendatenbank für Wikipedia und angeschlossene Projekte. Da die Mediendateien dort aber nicht speziell für Wikimedia lizensiert sind, sondern eben als Creative Commons, können sie natürlich auch woanders eingebunden werden. Und mit über 31 Mio. Mediendateien ist die Auswahl nicht gerade klein.
Einzig das Finden kann ein Problem sein. Eine Möglichkeit ist, zunächst die Medienart zu wählen, und dann die entsprechende Kategorie:
Wie man aber sieht, ist dies nicht die zeitgemäßeste Art, Bilder zu suchen. Moderner wäre hier ein visuelles Tool. Zum Glück gibt es das auch und es nennt sich Google.
Bezogen auf Wikimedia Commons geht das so: Will ich dort nach Robotern suchen, dann gehe ich zunächst zur Google Bilder-Suche (kommt jetzt etwas überraschend, ich weiß). Dann gebe ich zur Suche nach „robot“ Folgendes ein:
site:commons.wikimedia.org robot
site:
ist dabei das Zauberwort, denn dadurch wird Google angewiesen, nur solche Ergebnisse auszugeben, die auf der Website mit dieser Domain vorkommen. Und so finden wir also schließlich viele, viele Roboter.
Wie baue ich Bilder rechtssicher ein?
Wer jetzt meint: „Super, bau ich das Bild mal einfach ein, ist ja CC-lizensiert!“ der wird sich ggf. später über eine Abmahnung freuen. Denn Bild einbauen ist nicht gleich Bild einbauen.
Wie Henning Krieg und Thorsten Feldmann in ihrem Jahresrückblick Social Media Recht 2016 auf der re:publica berichteten, soll es wohl auch Fotografen geben, die ihre Bilder auf Wikimedia Commons hochladen und es darauf anlegen, dass ein Bild falsch eingebaut wird, um diese Personen dann abzumahnen. Uncool, aber zumindest in Deutschland erlaubt.
Lizenz beachten
Damit das nicht passiert, muss man die Lizenz beachten. Das beginnt damit, dass es überhaupt die richtige Lizenz für die eigene Website ist. Bei Creative Commons kann man als Urheber nämlich seine eigene Lizenz aus einem Baukasten zusammenbauen. Und dabei kann man auch entscheiden, ob das Werk auch kommerziell oder nur nicht-kommerziell genutzt werden. Auch kann man angeben, ob es verändert werden darf.
Hast du also eine kommerzielle Website, darfst du schon keine Bilder verwenden, die nur für die nicht-kommerzielle Nutzung freigegeben sind. Willst du ein Bild in eine Collage einbauen, dann solltest du darauf achten, dass man es auch verändern darf. All dies steht in den Lizenzvereinbarungen und die Creative-Commons-Website hilft zudem dabei, die CC-Icons zu identifizieren.
Bei Wikimedia Commons scheint es nur Werke zu geben, die relativ frei sind, also auch kommerziell genutzt werden dürfen. Dennoch sollte man auf Nummer sicher gehen. Das gilt erst recht, wenn man Bilder aus anderen Quellen nutzen will. Auch hierbei kann Google wieder helfen, denn man kann Google anweisen, die Suche auf eine bestimmte Lizenz zu begrenzen.
Klickt man nämlich auf „Suchoptionen“, erscheinen weitere Sucheinschränkungen und man kann hier die gewünschten Nutzungsrechte selektieren:
Natürlich sollte man bei einem Bild danach immer noch sicher gehen, dass es auch wirklich unter der gewünschten Lizenz veröffentlicht wurde, denn Google kann sich ja auch mal vertun.
(Im Übrigen sind auch die anderen Suchoptionen hilfreich. So hilft die Auswahl von „transparent“ unter „Farbe“ z.B. dabei, Logos mit einem transparenten Hintergrund zu finden)
Lizenzhinweis richtig einbauen
Wenn du das richtige Bild mit der richtigen Lizenz gefunden hast, bleibt noch der richtige Einbau des Lizenzhinweises. Wie oben schon erwähnt, kann man sonst abgemahnt werden (wie z.B. dieser Fall zeigt). Doch wie genau der Lizenzhinweis aussehen muss, ist vielleicht nicht immer ganz klar.
Für Wikimedia Commons kann da der auch auf der re:publica vorgestellte Lizenzhinweis-Generator helfen. Dieser funktioniert wie folgt:
- Man ruft lizenzhinweisgenerator.de auf
- Man gibt die URL einer Mediendatei auf Wikimedia Commons oder eines Wikipedia-Artikels ein
- Ist es ein Wikipedia-Artikel, zeigt der Generator alle dort verwendeten Bilder an und man selektiert das gewünschte Bild.
- Man selektiert eine Nutzungsart (Online oder Print)
- Man selektiert, ob es in einer Collage/Galerie/etc. oder einzeln verwendet werden soll
- Man gibt an, ob man es noch verändern will
Am Ende erscheint dann der fertige Lizenzhinweis in verschiedenen Formaten. Baut man diesen entsprechend ein, sollte man dann vor Abmahnungen gefeit sein.
So sieht das am Ende dann aus:
Und dies ist der dafür notwendige Lizenzhinweis (der hiermit dann auch eingebaut wäre):
Fazit
Wie man sieht, ist es gar nicht so schwer, interessante Bilder für sein Blog oder seine Website zu finden. Und wenn man dennoch kein passendes Bild findet, helfen verwandte Bilder vielleicht dabei, mit etwas Kreativität doch noch ein passendes Titelbild selbst zu erstellen. Dies muss man bei abstrakteren Themen ja eh tun (z.B. beim Thema Verkehrswege-Planungsbeschleunigungsgesetz).
Und dank des Lizenzhinweis-Generators ist auch die rechtssichere Einbindung dann hoffentlich kein Problem mehr.
Bildnachweis Titelbild:
Rama; edited by user Jaybear, Leica-II-Camera-1932 cropped, Komposition von Christian Scholz, CC BY-SA 3.0
Wie letztens berichtet, hat Facebook auf der diesjährigen f8-Entwicklerkonferenz ein neues Feature präsentiert: Facebook Messenger-Bots. Zeit also, sich einmal anzuschauen, was man mit diesen Chat-Bots so machen kann. In diesem Tutorial beschreibe ich daher zunächst das Setup und anschließend eine Beispiel-Implementierung eines einfachen Bots mit Hilfe von Flask und Python. Dies in einer anderen Sprache zu implementieren sollte dabei aber kaum komplizierter sein. Alles, was du können musst, ist, einen Web-Server aufzusetzen, JSON zu verarbeiten und HTTP-Requests abzusenden. Die Facebook-Dokumentation findest du ansonsten hier.
Was du für einen Facebook Messenger-Bot brauchst
Um einen Facebook Messenger-Bot zu implementieren brauchst du nicht viel. Hauptsächlich benötigst du einen Web-Server mit SSL-Unterstützung, auf dem der Bot dann laufen soll. Weiterhin bedarf es einer Facebook-Seite und einer Facebook-App. Es können dazu auch bestehende Seiten und Apps genutzt werden. Beides muss nicht zwangsläufig veröffentlicht sein, um den Bot damit betreiben zu können.
Die Seite dient dabei als Ankerpunkt des Bots, denn auf Facebook sendet man Nachrichten ja entweder an Personen oder an Seiten. Die App dient dazu, die notwendige Infrastruktur (wie den Webhook) bereitzustellen. Damit kann dann ein Mensch über die Seite mit deiner App und damit deinem Server kommunizieren.
Für dieses Tutorial benötigst du außerdem noch Python 3, flask und die requests-Library. Python-Kenntnisse sind sicherlich auch hilfreich. Wie oben schon gesagt, sollten die Konzept aber recht einfach auf andere Sprachen zu übertragen sein.
Python aufsetzen
Zunächst setzen wir die Server-Seite auf. Ich gehe davon aus, dass Python 3 installiert ist. Python 2.7 tut es sicherlich auch, nur die Syntax ist dann leicht anders. Zudem braucht man das venv-Paket. Wechsel dann in das Verzeichnis, in dem der Bot leben soll. Dort erstellst Du dann ein virtual environment und installierst die notwendigen Pakete:
pyvenv . source bin/activate pip install flask requests
Danach erstellst Du zum Testen eine einfache Flask-App mit dem Namen bot1.py
. Diese dient zunächst nur zum Testen des Servers.
Danach kann man den Server mit python bot1.py
starten.
Als nächstes musst du dann deinen Web-Server so konfigurieren, dass er diesen Python-Server unter einer öffentlichen URL bereitstellt. Wichtig ist dabei, dass er unter https
, also verschlüsselt, erreichbar sein muss. Da dies hier ein bisschen zu weit führen würde, verweise ich dazu mal auf Google. (Dort findet man z.B. eine Anleitung für nginx mit uwsgi). Ich persönlich habe das einfach auf unserer Domain laufen lassen (denn die hat schon SSL) und unter nginx wie folgt konfiguriert:
location /testbot { proxy_pass http://127.0.0.1:8888; }
Wenn alles funktioniert hat, sollte der Server dann unter dem Pfad /testbot
mit Hello, World!
antworten.
Seite und App einrichten
Wenn du noch keine Seite hast, die du mit dem Bot assoziieren willst, musst du eine erstellen. Dies geht unter diesem Link. Der Typ dürfte egal sein, für meinen Test habe ich einfach Community genommen. Die Seite selbst muss dazu nicht veröffentlich werden, sie muss aber existieren.
Auch bei der App kann man entweder eine bestehende App nutzen oder eine neue erstellen. Dies geht mit Hilfe dieses Links. Ich habe dazu den Typ „Website“ gewählt und das sieht dann wie folgt aus:
Als nächstes musst du eine Kontakt-E-Mail-Adresse angeben und die Kategorie auf „Apps für Seiten“ stellen. Test-Version bleibt ausgeschaltet. Nach Absenden des Formulars bist du an sich fertig, du kannst dann auf „Skip Quickstart“ oben rechts klicken. Danach erscheint das App-Dashbord.
Seite und App für den Facebook Messenger-Bot verknüpfen
Im App-Dashboard gibt es jetzt einen neuen Menüpunkt „Messenger“. Klickt man auf diesen zum ersten Mal, erscheint eine Info-Box, die man bestätigen muss. Danach erscheint dann folgender Screen:
Dort kannst du diese App mit der entsprechenden Seite verknüpfen. Wähle dazu in dem oberen Bereich (hier blau umrahmt) die zu verknüpfende Seite aus (ggf. gehen dabei ein paar Facebook-Popups auf, die nach weiteren Berechtigungen fragen). Ist dies geschehen, erscheint ein Access-Token neben der Seitenauswahl. Klicke darauf, um es zu kopieren. Beachte, dass dies dort nicht stehen bleibt. Wenn du es also erneut brauchst, musst du erneut die Seite auswählen und es erscheint wieder (bleibt wohl auch immer gleich).
Dieses Token wird dann im folgenden Aufruf genutzt, den du in einer Shell tätigen musst:
curl -ik -X POST "https://graph.facebook.com/v2.6/me/subscribed_apps?access_token=<token>"
Der Aufruf sollte ein {"success":true}
zurückgeben.
Damit erhält dann die App Updates von der Seite (denn normalerweise empfängt ja eine Seite Chat-Nachrichten und keine App). Wenn Du das nicht tust, dann wird nie eine Nachricht an die Seite bei der App und damit bei deinem Bot/Server ankommen. Das Token ist zudem nur für genau diese App- und Seiten-Kombination gültig.
Webhooks einrichten
Damit nun die App auch Nachrichten empfängt, muss ein Webhook eingerichtet werden. Dies ist der Server-Endpunkt auf deiner Seite, wo Facebook dann die vom Benutzer empfangenen Nachrichten hinschickt.
Dazu kannst du folgendes flask-Programm nutzen, das du unter bot2.py
speichern kannst.
Wichtig dabei ist die Zeichenkette cd7czs87czds8chsiuh9eh3k3bcjhdbjhb
. Diese kannst du selbst wählen und dient dazu, dass Facebook weiß, dass dieser Server und die App vom selben Autor kommen (also ein shared secret). Diese Zeichenkette muss sowohl in deinem Programm als auch im Webhook-Dialog eingetragen sein.
Sobald der Bot mit python bot2.py
dann läuft, kannst du auf den Button „Webhooks“ im Messenger-Bereich der App klicken (nicht den Menüpunkt „Webhooks“).
Folgendes erscheint und hier trägst du dieselbe Zeichenkette wie im Script ein:
Am besten nutzt du natürlich ein eigenes Token. Die Berechtigungen klickst du am besten alle an.
Wenn alles funktioniert hat, sollte nach Absenden des Formulars ein „Abgeschlossen“ im Webhook-Bereich erscheinen.
Und damit haben wir jetzt die Seite und die App verknüpft sowie die App mit dem Server.
Nachrichten empfangen und senden
Das heißt, dass wir jetzt bereit sind, Nachrichten zu empfangen. Dazu braucht es natürlich noch etwas Code.
Regeln wir zunächst den Empfang. Sendet ein Benutzer nun etwas an den Bot (bzw. der Seiten-Administrator, solange der Bot noch nicht von Facebook freigeschaltet wurde), so kommt dies an dem Webhook an, der auch die Verifizierung geregelt hat. Es ist also dieselbe URL, nur ein POST-
statt einem GET-Request
.
Die Nachricht selbst ist JSON-kodiert und sieht bei einem „Hallo Bot!“ wie folgt aus:
Wichtig ist der messaging
-Teil, der eine Liste von Nachrichten enthält. Diese wiederum enthalten eine Message-ID (mid
), eine Sequenz-Nummer (mir ist nicht bekannt, ob die auch gemischt angekommen können), den Empfänger (die Seiten-ID) sowie den Sender als ID und einen Timestamp. Die Sender-ID ist dabei nicht die Facebook-ID des Benutzers, sondern eine eigene ID für diese Anwendung.
Mit bot3.py
können wir dies nun lesen und mit einer einfachen Textnachricht antworten:
Wie man sieht, braucht man zum Senden ein Page-Token. Dies ist dasselbe Token, das man oben im curl
-Befehl benutzt hat. Du findest es wie gehabt im Menüpunkt „Messenger“ im App-Dashboard ganz oben nach Auswahl der verknüpften Seite. Solange PAGE_TOKEN
nicht das richtige Token enthält, wird der Bot nicht antworten.
Mit Hilfe dieses Tokens kann dann die Graph-API angesprochen werden. Der Bot sendet die Nachricht also nicht als Antwort direkt an den Webhook zurück, sondern asynchron. Dies hat den Vorteil, dass man auch später noch Nachrichten schicken kann, ohne dass der Benutzer erst wieder etwas sagen muss. Man sollte dabei nur die Facebook-Richtlinien und den gesunden Menschenverstand beachten. So dürfen z.B. keine reinen Werbenachrichten darüber versendet werden.
Das Format der Nachrichten werde ich ausführlicher in einem separaten Artikel beschreiben (zusammen mit den strukturierten Nachrichten). Im Prinzip sieht eine Text-Nachricht aber wie folgt aus:
Die Recipient-ID ist dabei die Sender-ID aus der Anfrage.
Ein Bild senden
Ein Bild sagt mehr als 1000 Worte! Wie also sendet man ein Bild? Ironischerweise, indem man gefühlt 1000 Worte schreibt, denn der Code für ein Bild ist schon ein bisschen größer. Hier ist die Methode send_image()
:
Diese kann man einfach in bot3.py
einfügen und dann wie folgt aufrufen:
Hier wird also zusätzlich zum Text noch eine weitere Text-Nachricht und dann ein Bild gesendet. Das sieht im Messenger dann wie folgt aus:
Die Struktur der Nachricht ist dabei so komplex, da es schon in Richtung strukturierter Nachrichten geht. Da dieser Artikel aber schon ein wenig lang ist, gibt es weitere Details und eine Beispiel-Implementierung im nächsten Artikel.
Troubleshooting
Wenn man einen Bot implementiert, funktioniert natürlich nicht immer alles. Ein paar der Probleme, die ich hin und wieder hatte und wie man diese behebt, findest du hier:
Der Bot bekommt keine Nachrichten
Wenn der Bot keine Requests bekommt, kann es sein, dass man vergessen hat, die Page-App-Verbindung herzustellen. Dazu führt man einfach den oben beschriebenen curl-Befehl mit dem Page-Access-Token aus.
Die Webhooks konnten nicht (mehr) erreicht werden
Es kann manchmal bei der Entwicklung sein, dass der Server nicht immer läuft. Facebook merkt das ggf. und beendet dann die Webhook-Verbindung. Dies sieht man dann im Menüpunkt „Benachrichtigungen“ im App-Dashboard. Der einfachste Weg, diese wieder zu aktivieren, ist eine erneute Aktivierung des Webhooks. Dazu entfernt man den bestehenden Webhook im Menüpunkt „Webhooks“ im App-Dashboard. Danach kann man den ihn mit dem Verifizierungs-Token im Menüpunkt „Messenger“, wie oben beschrieben, neu erstellen. Danach sollte wieder alles funktionieren (soweit der Server funktioniert).
Ein Fehler im Script ist aufgetreten
Hat man einen Fehler in seinem Server und antwortet dieser daher mit etwas anderem als Status-Code 200, gibt es ein kleines Timeout bei Facebook. D.h. nach Fixen des Fehlers muss man ca. 1 Minute warten, bis Facebook wieder Nachrichten an den Server schickt. Leider etwas nervig und es wäre schön, wenn unveröffentlichte Bots diese Einschränkung nicht hätten.
Ich hoffe, diese Tutorial war hilfreich. Im nächsten Artikel beschreibe ich dann, wie man strukturierte Nachrichten sendet, wie diese strukturiert sind und wie man einen Willkommens-Screen erstellt.
Bildnachweis:
D J Shin, S.H Horikawa – Star Strider Robot (スターストライダーロボット) – Front, D J Shin, QSH Tin Wind Up Mechanical Robot (Giant Easelback Robot) Side, Collage von Christian Scholz, CC BY-SA 3.0
Layouts mit Cards oder Boxen sieht man ja immer noch recht viel. Man braucht dazu nur auf unsere Blog-Startseite zu schauen oder die verwandten Artikel in diesem Artikel. Je nach Inhalt haben diese Boxen dann aber gerne mal unterschiedliche Höhen. In diesem Artikel erfährst Du, wie man Boxen mit gleicher Höhe nur in CSS implementiert.
Schauen wir das Problem mal im Detail an: Nimmt man einfach eine panel
-Klasse von Bootstrap, eine feste Breite und einen float:left
, sieht das so aus:
Dies ist eine Test-Headline, die ein bisschen länger ist, da sie viel Text enthält
Dies ist kurze Headline
Dies ist eine Test-Headline, die ein bisschen länger ist, da sie viel Text enthält
Dies ist eine Test-Headline, die ein bisschen länger ist, da sie viel Text enthält
Dies ist kurze Headline
Der Code dazu:
<style> .panel-test { width: 30%; padding: 20px; float: left; margin: 10px; border: 1px solid #aaa; } </style> <div> <h3>Dies ist eine Test-Headline, die ein bisschen länger ist, da sie viel Text enthält</h3> </div> <div> <h3>Dies ist kurze Headline</h3> </div> <div> <h3>Dies ist eine Test-Headline, die ein bisschen länger ist, da sie viel Text enthält</h3> </div>
Recht schön sieht das nicht aus (zumindest nicht, wenn dann große Löcher entstehen).
Eine Abhilfe kann hier die Definition einer festen Höhe sein. Dann muss man aber sehr genau darauf achten, dass man nicht zu viel Text schreibt, da dieser sonst aus der Box läuft oder abgeschnitten würde.
Boxen mit gleicher Höhe dank flexbox
Zum Glück gibt es ein CSS-Konstrukt, das hier Abhilfe schafft und inzwischen auch vom Großteil der Browser unterstützt wird.
Gemeint ist das flexbox-Modell (hier geht es zum Standard). Dies ist eine Layout-Methode, die Boxen innerhalb eines Containers effizient verteilen und ausrichten kann, auch wenn die Größen unterschiedlich/unbekannt sind.
Die Grundidee ist, dass Boxen innerhalb eines Containers anhand einer Achse ausgerichtet und verteilt werden. Dies kann dann mit entsprechenden Direktiven genauer gesteuert werden.
Allerdings soll dies jetzt keine komplette Einführung sein, sondern nur ein Beispiel, wie man das obige Problem mit Hilfe des Flexbox-Modus lösen kann.
Dazu brauchen wir zunächst einen Container und die Boxen dann innerhalb des Containers:
<div id="flex-container"> <div> <h3>Dies ist eine Test-Headline, die ein bisschen länger ist, da sie viel Text enthält</h3> </div> <div> <h3>Dies ist kurze Headline</h3> </div> <div> <h3>Dies ist eine Test-Headline, die ein bisschen länger ist, da sie viel Text enthält</h3> </div> <div> <h3>Dies ist eine Test-Headline, die ein bisschen länger ist, da sie viel Text enthält</h3> </div> <div> <h3>Dies ist eine Test-Headline, die ein bisschen länger ist, da sie viel Text enthält</h3> </div> </div>
Als nächstes muss das CSS eigentlich nur noch um die entsprechenden Flex-Direktiven erweitert werden:
<style> #flex-container { display: -webkit-flex; display: -ms-flex; display: flex; -webkit-flex-wrap: wrap; -ms-flex-wrap: wrap; flex-wrap: wrap; } .panel-test2 { display: -webkit-flex; display: -ms-flexbox; display: flex; width: 30%; padding: 20px; float: left; margin: 10px; border: 1px solid #aaa; } </style>
Wie man sieht, wird hier das Flex-Layout mit display: flex
sowohl für den Container als auch die Child-Elemente eingeschaltet. Dies führt schon automatisch dazu, dass alle Boxen die gleiche Höhe bekommen. Die Höhe ist dabei die des größten Elements.
Allerdings würden dann alle Boxen in einer Zeile erscheinen und entsprechend schmaler werden. D.h. die definierte Breite würde ignoriert werden.
Damit stattdessen die vordefinierte Breite von 30% genutzt wird, wird flex-wrap: wrap
eingesetzt. Dies sagt dem Container, dass „überschüssige“ Boxen in die nächste Zeile rutschen sollen.
Das Ergebnis sieht dann so aus:
Dies ist eine Test-Headline, die ein bisschen länger ist, da sie viel Text enthält
Dies ist kurze Headline
Dies ist eine Test-Headline, die ein bisschen länger ist, da sie viel Text enthält
Dies ist eine Test-Headline, die ein bisschen länger ist, da sie viel Text enthält
Dies ist eine Test-Headline, die ein bisschen länger ist, da sie viel Text enthält
Wie man sieht, ist die Lösung für dieses Problem relativ einfach.
Man sollte dabei natürlich auf Browser-Kompatibilitäten achten. So gibt es Flexbox in allen modernen Browsern und im IE ab Version 11. Testen sollte man es aber natürlich dennoch.
Als ich dieses Blog aufsetzte, war ich natürlich auch auf der Suche nach einem Plugin, mit dem ich verwandte Artikel verlinken kann. Es gibt diverse Plugins für WordPress, die das automatisiert machen können, wie z.B. Yet Another Related Posts Plugin oder Contextual Related Posts – ich aber will diese Artikel manuell aussuchen. So habe ich mehr Kontrolle und bin mir sicher, dass die Artikel auch Sinn machen.
Die Anforderungen
Die Anforderungen waren für mich im Detail:
- Ich will bei einem Artikel zu anderen Artikeln angeben
- Bis zu 3 Verweise sollen unter dem Artikel erscheinen
- Verlinke ich einen Artikel, soll dieser Artikel auch automatisch einen Backlink bekommen.
- Trotzdem sollen insgesamt nur 3 Artikel in der Liste angezeigt werden, wobei die ausgehenden Links Vorrang haben sollen.
Ich habe mir zunächst das Manual Related Posts Plugin angeschaut, das allerdings seit 7 Monaten nicht mehr aktualisiert wurde. Zudem hatte ich auch ein paar Probleme mit dem Styling, da er z.B. feste style-Angaben in den Code schreibt, die man zwar theoretisch per Filter ändern kann, was aber für mich nicht so richtig funktioniert hat.
Die nächste Idee war dann, das direkt selbst im Theme zu implementieren. Problem hierbei: Benutzer-Interfaces in WordPress zu implementieren ist nicht gerade ein Spaß (zumindest out of the box). Und an dieser Stelle kam dann das Advanced Custom Fields Plugin ins Spiel. Hier muss man zwar immer noch selbst ein bisschen Code schreiben, aber die UI-Arbeit ist damit zum Glück schon erledigt. (Dank geht an Claudia von Chilliscope, die mich wieder dran erinnerte, dass es dieses Plugin ja auch noch gibt).
Was ist das Advanced Custom Fields Plugin (ACF)?
Kurz gesagt, lassen sich mit dem Advanced Custom Fields Plugin neue Metadaten-Felder für Artikel, Seiten und Custom Content Types definieren. Diese Felder sind dabei deutlich ausgereifter im UI als die Standard-WordPress-Implementierung. Hinzu kommt eben, dass man sich um das Benutzer-Interface dann gar nicht mehr selbst kümmern muss.
Allerdings muss man schon etwas programmieren können, um ACF nutzen zu können, denn es fragt nur die Daten ab, zeigt sie aber zunächst nicht wieder an. Dazu muss man das Theme entsprechend anpassen, was PHP-Kenntnisse benötigt.
Verwandte Artikel mit Advanced Custom Fields implementieren
Wie geht man also nun vor, um eine manuelle Verlinkung von Artikeln zu implementieren? Am Anfang steht die Definition des benötigten Feldes, in dem man die Artikel aussucht, die man verlinken will. Dazu legt man unter dem Menüpunkt Eigene Felder zunächst eine neue Gruppe an, die man beliebig nennen kann. Bei mir heißt sie „Post Links“. Direkt darunter kann man dann ein neues Feld erstellen. Als Titel habe ich „Verwandte Artikel“ gewählt, als internen Namen „related_posts“ und als Feld-Typ Beziehung. Man hätte auch den Artikel-Typ nehmen können, aber dann bekommt man nur eine lange Liste von Artikeln angezeigt. Das Beziehungs-Feld dagegen hat noch eine Suche eingebaut ähnlich dem Link-Dialog.
Wichtig für mich war weiterhin, dass ich nur Posts erlaube, dass man nicht zwingend etwas auswählen muss, und dass man mehrere Artikel selektieren kann.
Insgesamt sieht das dann so aus:
Bei den Optionen kann man dann noch angeben, dass die Gruppe unter dem Inhalt („normal“) angezeigt werden soll und dass der Stil „Standard“ sein soll (also mit Rahmen). Und bitte nicht vergessen, vorher auch das Feld hinzuzufügen, die Optionen gelten nämlich für die ganze Gruppe.
Hat man das alles gemacht, erscheint rechts in der Seitenleiste bei den Artikeln eine neue Box namens „Post Links“, wo man dann beliebig viele Artikel selektieren kann (von denen wir später aber nur maximal 3 anzeigen).
Optional: Feld-Liste als PHP exportieren
Ich persönlich habe dies zunächst auf meiner Test-Installation gemacht und habe danach unter „Eigene Felder“ den Punkt „Export“ und dann „Export als PHP“ gewählt. Heraus kommt PHP-Code, der in die functions.php
im Theme eingebaut werden kann.
Der Vorteil: Ich muss die Feld-Definition nicht bei jedem Blog vornehmen, sondern sie ist fest im Theme verankert (ACF muss aber installiert sein). In meinem Fall kann ich einfach das Theme auf die Live-Site deployen und schon erscheinen die Felder. So würde ich mir das bei allen Plugins wünschen.
Man sollte allerdings die Gruppe im Backend wieder löschen, nachdem man den PHP-Code ins Theme eingebaut hat, da ansonsten die Felder doppelt erscheinen könnten.
Verwandte Artikel ausgeben
Jetzt können wir Artikel selektieren, jedoch werden sie noch nicht ausgegeben. Gehen wir dazu am besten Schritt für Schritt vor. Dazu habe ich in der Datei single.php
im HTML einen neuen Bereich für die Artikel-Liste geschaffen und folgenden Code für die Liste selbst genutzt:
<?php $related = get_field("related_posts"); // output all the related articles if( $related ): ?> <h2>Vielleicht auch total interessant:</h2> <div class="row"> <?php foreach( $related as $post): setup_postdata($post); ?> <div class="col-md-4"> <div class="panel"> <a href="<?php the_permalink(); ?>" title="<?php echo esc_attr( the_title_attribute( 'echo=0' ) ); ?>" rel="bookmark"> <?php echo the_post_thumbnail("featured-wide", array( 'class' => 'img-responsive') ); ?> <div class="panel-heading"> <h5 class="entry-title clearfix"> <?php the_title(); ?> </h5> </div> </a> </div> </div> <?php endforeach; ?> </div> <?php wp_reset_postdata(); endif; ?>
Noch erfüllt das allerdings nicht alle Anforderungen, vor allem werden die auf diesen Artikel zeigenden Artikel noch nicht dargestellt. Außerdem werden mehr als 3 Artikel angezeigt, wenn sie vorhanden sind. Darauf kann man natürlich im Backend achten, wenn aber noch die eingehenden Links dazukommen, können es auch deutlich mehr als 3 werden.
Eingehende Links darstellen
Um die eingehenden Artikel-Verlinkungen auch darstellen zu können, muss man die Datenbank befragen. Dies geht mit folgendem Code-Schnipsel:
$incoming = get_posts(array( 'post_type' => 'post', 'meta_query' => array( array( 'key' => 'related_posts', // name of custom field 'value' => '"' . get_the_ID() . '"', // matches exaclty "123", not just 123. This prevents a match for "1234" 'compare' => 'LIKE' ) ) ));
Es wird hier wieder das Feld related_posts
genutzt, allerdings wird gefragt, welche Artikel die ID des aktuellen Artikels in diesem Feld beinhalten. Die Liste dieser Artikel wird dann in $incoming
abgespeichert.
Als nächstes müssen wir das noch mit den ausgehenden Links zusammenführen und auf 3 Artikel insgesamt begrenzen:
if (!$related) { $related = array(); } $related = array_merge($related, $incoming); // merge own and incoming together // limit to 3 articles, own links always take precedence $related = array_slice($related, 0, 3);
Wichtig ist auch der erste Teil, wo wir sicherstellen, dass die $related
-Variable auch wirklich ein Array ist, damit array_merge
keinen Fehler wirft.
Insgesamt kommen wir dann auf folgenden Code:
<?php $related = get_field("related_posts"); // merge in articles pointing to this post $incoming = get_posts(array( 'post_type' => 'post', 'meta_query' => array( array( 'key' => 'related_posts', // name of custom field 'value' => '"' . get_the_ID() . '"', // matches exaclty "123", not just 123. This prevents a match for "1234" 'compare' => 'LIKE' ) ) )); if (!$related) { $related = array(); } $related = array_merge($related, $incoming); // merge own and incoming together // limit to 3 articles, own links always take precedence $related = array_slice($related, 0, 3); // output all the related articles if( $related ): ?> <h2>Vielleicht auch total interessant:</h2> <div class="row"> <?php foreach( $related as $post): setup_postdata($post); ?> <div class="col-md-4"> <div class="panel"> <a href="<?php the_permalink(); ?>" title="<?php echo esc_attr( the_title_attribute( 'echo=0' ) ); ?>" rel="bookmark"> <?php echo the_post_thumbnail("featured-wide", array( 'class' => 'img-responsive') ); ?> <div class="panel-heading"> <h5 class="entry-title clearfix"> <?php the_title(); ?> </h5> </div> </a> </div> </div> <?php endforeach; ?> </div> <?php wp_reset_postdata(); endif; ?>
Und das ist auch der Code, der jetzt hier auf der Site im Einsatz ist. Dank Advanced Custom Fields war das recht flott zu implementieren und erfüllt alle meine Anforderungen. Ein ingesamt kleines Beispiel, aber man sieht gut daran, dass hier mit etwas Fantasie noch sehr viel mehr möglich ist.
Was habt ihr denn so damit gebaut?
Wer kennt das nicht: Man hat ein frisches WordPress-Blog und ein nettes Theme gefunden, doch irgendwie fehlen dem Theme noch 1-2 kleine Features, wie eine Lightbox oder ähnliches. Gibt es für das Problem kein passendes WordPress-Plugin, sondern nur ein jQuery-Plugin, stellt sich dann die Frage, wie man dies in das Theme einbaut.
Daher will ich in dieser Anleitung zeigen, wie man ein jQuery-Script in 2 einfachen Schritten in das eigene oder ein bestehendes Theme einbauen kann.
1. Das jQuery-Plugin im Theme-Verzeichnis ablegen
Dazu erstellt man, wenn es noch nicht vorhanden ist, ein neues Verzeichnis namens js/
innerhalb des Theme-Ordners. Dort kopiert man das jQuery-Script hinein, z.B. /js/query.plugin.min.js
. Auch eigene Scripts kann man dort ablegen.
2. Das jQuery-Plugin aktivieren
Damit das jQuery-Plugin auch genutzt wird, muss man WordPress noch sagen, wo es liegt und wie es eingebunden werden soll. Dies geschieht in der functions.php
des Themes.
Dort schreibst Du jetzt einfach folgenden Code rein, wobei Du den Dateinamen des Plugins natürlich entsprechend anpassen musst:
add_action( 'wp_enqueue_scripts', 'add_my_scripts' );
function add_my_scripts () {
wp_enqueue_script(
'my-plugin', // eigener Name
get_template_directory_uri() . '/js/jquery.plugin.min.js', // Pfad
array('jquery') // Abhängigkeiten
);
}
Folgendes passiert hier:
add_action
sagt WordPress, dass es für die Aktionwp_enqueue_scripts
auch noch unsere Funktionadd_my_scripts
aufrufen soll. Diese ist in der 2. Zeile ff. definiert.- Wenn WordPress die Seite darstellen will, geht es u.a. auch alle registrierten Funktionen für diese Aktion durch und ruft dann unsere Funktion auf
- Mit
wp_enqueue_script
(Nicht verwechseln mit dem Aktionsnamen) sagen wir dann WordPress, welche JavaScript-Dateien auf der Seite zusätzlich auftauchen sollen (Plugins und das Theme selbst können auch schon welche definiert haben). - Dieser Funktion übergibt man als Parameter den Namen des Scripts, den Pfad zur eigentlichen JS-Datei und eine Liste von Abhängigkeiten (optional).
Hierbei musst Du auf folgende Sachen achten:
- Der Name muss einmalig sein (hier „my-plugin“). Dieses identifiziert das Plugin gegenüber WordPress. Der Name sollte am besten klein geschrieben sein und keine Leerzeichen enthalten.
- Der Pfad muss natürlich stimmen (hier
/js/jquery.plugin.min.js
). - Die Abhängigkeiten müssen stimmen, in diesem Fall normalerweise einfach
array("jquery")
. Schließlich binden wir ja ein jQuery-Plugin ein und das benötigt sicherlich jQuery als Grundlage. WordPress weiß dadurch, dass es erst (das mitgelieferte) jQuery laden soll und dann unser Script.
Achtung: Child-Themes
Wenn Du ein Child-Theme nutzt (z.B. um das Original-Theme nicht ändern zu müssen), dann ändert sich der Aufruf wie folgt:
wp_enqueue_script( 'my-plugin', // eigener Name
get_stylesheet_directory_uri() . '/js/jquery.plugin.min.js', // Pfad array('jquery') // Abhängigkeiten );
Es wird also get_template_directory_uri
durch get_stylesheet_directory_uri
ersetzt, damit WordPress im richtigen Verzeichnis nachschaut (im Child-Theme und nicht im Haupt-Theme).
Und damit sollte das Plugin erfolgreich eingebunden sein.
Optional: Eigenes JavaScript als Datei einbinden
Oftmals muss man die Funktionen, die das jQuery-Plugin bereitstellt, auch noch aufrufen. Dies geschieht entweder direkt im Footer der Seite (also wahrscheinlich dann in footer.php
) oder man lagert dies in ein eigenes Script aus. Gerade wenn es mehr Code wird, macht dies aus Organisations- und Performance-Sicht Sinn.
Nehmen wir also an, Du legst den JavaScript-Code in /js/scripts.js
ab, dann musst Du noch einen weiteren wp_enqueue_script
-Aufruf zu add_my_scripts
hinzufügen:
add_action( 'wp_enqueue_scripts', 'add_my_scripts' ); function add_my_scripts () { wp_enqueue_script( 'my-plugin', // eigener Name get_template_directory_uri() . '/js/jquery.plugin.min.js', // Pfad array('jquery') // Abhängigkeiten );
// jetzt noch eigenes Script laden
wp_enqueue_script( 'my-script', // eigener Name get_template_directory_uri() . '/js/scripts.js', // Pfad array('my-plugin') // Abhängigkeiten );
}
Hier siehst Du folgende Änderungen im Aufruf:
- Das Script hat einen anderen Namen (
my-script
) - Der Pfad ist entsprechend anders (dieselben Regeln für ein Child-Theme gelten)
- Die Abhängigkeit ist nicht mehr
jquery
, sondern jetztmy-plugin
, also das Plugin, welches Du in Deinem Script benutzt. Man hätte auch nochjquery
zusätzlich angeben können (dann alsarray('jquery', 'my-plugin')
), aber damy-plugin
schonjquery
als Abhängigkeit hat, ist das nicht unbedingt notwendig.
Script-Versionen definieren
Der obige Aufruf von wp_enqueue_script
funktioniert super, solange man immer dieselbe Version eines Scripts/Plugins nutzt. Aktualisiert man das Script aber, kann es je nach Caching-Setup dazu kommen, dass Benutzer noch die alte Version ausgeliefert bekommen. Um dies zu vermeiden, kann man an den Aufruf noch eine Versionsnummer übergeben, z.B. so:
wp_enqueue_script(
'my-script', // eigener Name
get_template_directory_uri() . '/js/scripts.js', // Pfad
array('my-plugin'), // Abhängigkeiten
'20160323'
);
Wie man sieht, wurde mangels offizieller Versionsnummer einfach ein Datum als Versions-String genutzt. Lädt man fertige Plugins aus dem Netz herunter, sollte man die offizielle Versionsnummer dort eintragen (immer als String).
Script im Footer laden
Nicht alles muss im Header der Seite geladen werden. Genauer genommen sollte eigentlich alles, was möglich ist, im Footer geladen werden. Dadurch kann eine Seite teilweise deutlich schneller angezeigt werden.
Um ein Script von WordPress in den Footer laden zu lassen, kannst Du einen weiteren Parameter an wp_enqueue_script
anhängen:
wp_enqueue_script(
'my-script', // eigener Name
get_template_directory_uri() . '/js/scripts.js', // Pfad
array('my-plugin'), // Abhängigkeiten
'20160323', // Versionsnummer
true // lade es im Footer
);
Also einfach noch true
anhängen und schon wird es im Footer geladen.
Scripts im Admin-Bereich laden
Wenn ihr das so, wie oben beschrieben, einbaut, dann werden die angegebenen Scripts nur im Frontend geladen. Normalerweise reicht das auch, denn es geht ja um das Theme und damit um die öffentliche Ansicht.
Wenn ihr aber jetzt Plugin- oder Theme-Entwickler seid, dann wollt ihr bestimmte JavaScript-Dateien ggf. auch im Admin-Bereich laden. Dazu nutzt ihr einfach eine andere Aktion, nämlich statt wp_enqueue_scripts dann
admin_enqueue_scripts
. Wichtig: Es geht hier um den Aktionsnamen, nicht etwa um die Funktion zum Definieren der Scripts, diese ist weiterhin wp_enqueue_script
(Singular).
Das erste Beispiel würde also für den Admin-Bereich wie folgt aussehen:
add_action( 'admin_enqueue_scripts', 'add_my_scripts' );
function add_my_scripts () {
wp_enqueue_script(
'my-plugin', // eigener Name
get_template_directory_uri() . '/js/jquery.plugin.min.js', // Pfad
array('jquery') // Abhängigkeiten
);
}
Ich hoffe, euch damit geholfen zu haben. Fragen könnt ihr gerne in den Kommentaren hinterlassen. Generell hilft auch, die Doku zu wp_enqueue_script und der zugehörigen Aktion wp_enqueue_scripts zu lesen oder aber Dinge einfach auszuprobieren.
Wie in diesem Artikel beschrieben, pusht Google gerade massiv ihr Accelerated Mobile Pages-Projekt (AMP). Kurz gesagt, geht es dabei darum, mobile Webseiten schneller ausliefern zu können. Bei AMP geschieht das dadurch, dass einerseits die Webseite auf das Wesentliche reduziert wird (kein JavaScript, wenig CSS, definierte Lade-Reihenfolge) und die Seiten durch Google’s CDN ausgeliefert werden.
Wichtig ist AMP vor allem inzwischen für Nachrichtensites und Blogs, da AMP-Seiten seit Februar in der Suche prominent ganz oben dargestellt werden (wenn man als Top-Story qualifiziert). Aus SEO-Sicht kommt man also eigentlich um AMP nicht herum.
Wenn Du also ein WordPress-Blog hast, dann solltest Du zusehen, dass Du AMP aktivierst, was zum Glück nicht wirklich schwer ist.
AMP in WordPress dank Plugin
Wie immer bei WordPress gibt es natürlich schon längst ein Plugin namens AMP, diesmal sogar auch direkt aus dem Hause Automattic
Installiert und aktiviert man dies, so werden alle Artikel (nicht Seiten) automatisch mit einer AMP-Variante versehen. Ansehen kann man diese, indem man einfach /amp/
an die URL anhängt, also z.B. https://comlounge.net/lead-generierung-call-to-actions/amp/
. Es wird dabei nur der Post-Content genutzt und in einen einfachen Layout-Rahmen gesteckt. Hat man ein Site-Icon definiert, so wird dies ebenfalls im Header angezeigt.
Diese AMP-Seite wird dabei vom eigentlichen Beitrag automatisch über <link rel="amphtml" href="..." />
verlinkt und ist somit durch Google auffindbar.
AMP anpassen
Da das Plugin selbst keinerlei UI hat und somit keine Design-Änderungen zulässt, muss man sich anders behelfen. Wer das Yoast SEO-Plugin nutzt, kann z.B. das Yoast SEO AMP Glue plugin installieren, das mehr Möglichkeiten eröffnet. Damit kann man AMP nicht nur für Artikel, sondern auch für Seiten und Medienobjekte aktivieren. Vor allem aber kann man die Farben und auch das Site-Icon ändern oder ein Default-Featured Image setzen. Ein solches Bild ist nämlich wichtig, damit Google die Seite richtig indiziert.
Wer ein Site-Icon nutzt, wird sich vielleicht ärgern, dass es rund ausgeschnitten wird, was sich nicht für jedes Logo eignet. Zum Glück kann man dies recht einfach im Custom CSS-Feld mit folgendem Code ausschalten:
nav.amp-wp-title-bar .amp-wp-site-icon { border-radius: 0; }
Das Yoast-Plugin kümmert sich weiterhin automatisch um die Einbindung von Analytics in die AMP-Seite, wenn man Google Analytics von Yoast installiert hat. Es kann aber auch ein manueller Tracking-Code angegeben werden.
Eine Alternative zu diesem Plugin, was ja sehr in das Yoast-Ökosystem integriert ist, wäre das Plugin von Pagefrog, was nicht nur AMP-Versionen erstellt, sondern auch Versionen für den Facebook Instant Articles oder Apple News. Allerdings stellt man sich im Forum schon die Frage, ob es noch weiterentwickelt wird, da man von den Entwicklern nichts mehr hört. Da die letzte Version aber nur 2 Wochen alt ist (beim Yoast-Plugin gerade aber ähnlich), sollte man das vielleicht erstmal beobachten. Im Zweifel sollte man sicherlich beide Plugins mal ausprobieren und schauen, was für einen selbst besser funktionieret.
Featured Image anzeigen
Wenn vom Featured Image die Rede ist, so muss man unterscheiden zwischen dem, was Google indiziert und dem, was angezeigt wird. Standardmäßig wird nämlich das Bild gar nicht ausgegeben, sondern nur in den Metadaten aufgelistet. Das ist für Google ausreichend, um es im News-Karussell anzuzeigen, schön wäre aber dennoch, das auch auf der Seite sehen zu können.
Hat man sein eigenes Theme, kann man dies wie folgt in der functions.php lösen:
add_action( 'pre_amp_render_post', 'xyz_amp_add_custom_actions' ); function xyz_amp_add_custom_actions() { add_filter( 'the_content', 'xyz_amp_add_featured_image' ); } function xyz_amp_add_featured_image( $content ) { if ( has_post_thumbnail() ) { // Just add the raw <img /> tag; our sanitizer will take care of it later. $image = sprintf( '%s', get_the_post_thumbnail() ); $content = $image . $content; } return $content; }
Weitere Möglichkeiten, um seine AMP-Seite anzupassen, sind in diesem README aufgelistet.
Post-Inhalte mit Shortcodes anreichern
Noch ein kleiner Tipp zum Ende: Die AMP-Version einer Seite beinhaltet wirklich nur den Artikelinhalt und keine anderen Elemente (z.B. Ausgaben von anderen Plugins, die über oder unter einem Artikel erscheinen, wie z.B. verwandte Beiträge oder Werbung). Wer dies in der AMP-Version (für RSS gilt wohl dasselbe) mehr als nur den Inhalt ausliefern möchte, muss dies in den Artikel direkt reinschreiben. Dies kann z.B. mit Shortcodes gelöst werden, wobei man aber beim Schreiben dran denken muss, es auch wirklich manuell reinzuschreiben. Da der Shortcode Teil des Beitrags ist, erscheint die Ausgabe dann auch in der AMP-Version.
Die Shortcodes selbst können dabei entweder von bestehenden Plugins kommen oder man kann sie als Entwickler selbst im Theme definieren. Wichtig ist dabei nur, dass kein JavaScript funktioniert und das CSS entweder über das Custom CSS-Feld des Yoast-Plugins oder aber auch in der functions.php speziell für AMP eingebunden werden muss. Für Werbung wird wahrscheinlich ein Bild mit Link am besten funktionieren.
Fazit
Generell ist die Aktivierung von AMP dank des Plugins recht einfach. Man sollte allerdings in der Suchkonsole darauf achten, dass Google die AMP-Seiten auch richtig indiziert. Zudem steckt AMP und vor die Wordpress-Integration noch ein bisschen in den Kinderschuhen. Da wird sicherlich in Zukunft noch einiges passieren, sowohl am Standard als auch an der WordPress-Implementierung. Wir werden euch auf dem Laufenden halten!
In meinem ersten Tutorial auf diesem Blog will ich darstellen, wie man Gemeindegrenzen auf einer Auf Open Street Map basierenden Karte darstellen kann. Die Gemeindegrenzen (in diesem Fall NRW) selbst nehme ich dabei nicht direkt von OSM, sondern aus einem Shapefile, das ich in diesem Fall von der Seite zur Landtagswahl des Innenministeriums herunterlade. Als Grundlage für die OSM-Darstellung nehme ich die JavaScript-Library leaflet.js.
Shapefile in GeoJSON überführen
Die Hauptaufgabe ist nun, das Shapefile mit den Gemeindegrenzen in das GeoJSON-Format zu überführen, denn dies ist das Format, das leaflet erwartet. Lädt man die Geometrie der Gemeinden (direkter Downloadlink) herunter und entpackt das ZIP-File, so erhält man folgende laut zu einem Shapefile zugehörigen Dateien:
DVG1_Gemeinden_utm.dbf DVG1_Gemeinden_utm.prj DVG1_Gemeinden_utm.shp DVG1_Gemeinden_utm.shx
Wichtig ist dabei im Prinzip nur die Datei mit Endung .shp, da diese die eigentlichen Geometrien enthält.
Um diese Datei in GeoJSON umzuwandeln gibt es zum Glück ein Tool, dass sich ogr2ogr
nennt und im GDAL-Paket zu finden ist. Unter OSX kann man dies mit macports wie folgt installieren:
sudo port install gdal +geos
Bevor man die Konvertierung startet, muss man zwei Dinge beachten. Zunächst einmal die Art der Projektion. So gibt es verschiedene Verfahren, die Erdkugel auf eine rechteckige Karte zu projizieren. Das Shapefile benutzt UTM (wie im Dateinamen zu erkennen), leaflet bzw. OSM mag aber lieber EPSG:3857. Hier muss man also entweder umrechnen oder in leaflet die Projektion umstellen, was mit dem Plugin Proj4Leaflet geht. Ich werde hier aber die Daten schon vorher auf die Ziel-Projektion umrechnen.
Das zweite Problem ist die Datenmenge. Die Definition der Gemeindegrenzen ist nämlich sehr genau, was bedeutet, das die Polygone sehr viele Punkte beinhalten. Dies bedeutet aber, dass die Karte eher behäbig daherkommt. Da es bei der Darstellung aber eher auf die Flächen ankommt und man sowieso nicht bis auf den letzten Stein herunter zoomen kann, können und sollten diese Polygone vereinfacht werden.
Beide Probleme lassen sich glücklicherweise direkt mit ogr2ogr lösen und der zugehörige Aufruf sieht wie folgt aus:
ogr2ogr -f geojson -simplify 20 -t_srs EPSG:4326 -a_srs EPSG:4326 gemeinden.json DVG1_Gemeinden_utm.shp
Die einzelnen Argumente bedeuten dabei folgendes:
- -f geojson definiert das Zielformat, das wir wünschen
- -simplify 20 vereinfacht die Polygone. Mit dem Faktor 20 erhält man dabei statt einer 43 MB-Datei eine nur 4,9 MB grosse Datei. Den Wert habe ich dabei durch Ausprobieren ermittelt, indem ich mehrere Werte angegeben habe und auf der Karte geschaut habe, wo die Polygone zu grob wurden.
- – t_srs EPSG:4326 definiert die Ziel-Projektion
- -a_srs EPSG:4326 definiert die Ausgabe-Projektion (was genau der Unterschied zu oben ist, ist mir leider nicht so klar, aber so funktioniert es)
Eine Besonderheit von ogr2ogr ist dabei, dass zunächst die Zieldatei angegeben wird und dann erst die Quelldatei. Also aufpassen!
Man beachte auch die Projektion, denn entgegen meiner vorherigen Behauptung mit EPSG:3857 arbeitet leaflet dann wohl doch lieber mit EPSG:4326 (wenn ich das richtig verstehe ist letztere der Koordinatensatz auf einer Kugel, während 3857 die projizierte Version ist. Ob leaflet das dann automatisch umrechnet, ist mir nicht so ganz klar, mit 4326 funktioniert es aber auf jeden Fall).
Hat man nun die Datei, muss man sie nur noch anzeigen. Dazu initialisiert man zunächst leaflet (und jquery gleich mit). Laden wir zunächst die notwendigen Dateien vom CDN:
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.5/leaflet.css" /> <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script> <script src="http://code.jquery.com/jquery-migrate-1.1.1.min.js"></script> <script src="http://cdn.leafletjs.com/leaflet-0.5/leaflet.js"></script>
Dann noch ein bisschen CSS für eine Fullscreen-Map:
<style> html, body, #map { height: 100%; } </style>
Die Karte selbst ist nur ein DIV:
<div id="map"></div>
Dann brauchen wir noch Code zum Initialisieren der Karte grob auf NRW (ausserdem benötigt man noch einen API-Key von cloudmade.com, wobei man zum Experimentieren aber alternativ auf die Tiles von OSM nutzen kann. Die Nutzung ist dort allerdings eingeschränkt):
<script> $(document).ready(function() { var map = L.map('map').setView([51.463, 7.18], 10); L.tileLayer('http://{s}.tile.cloudmade.com/API-KEY/{z}/{x}/{y}.png', { attribution: 'Map data © <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="http://cloudmade.com">CloudMade</a>', maxZoom: 18 }).addTo(map); </script>
Nun müssen wir nur noch das GeoJSON-Objekt laden und anzeigen. Dies geschieht per getJSON-Aufruf:
$.getJSON("/gemeinden/gemeinden.json", function(data) { L.geoJson(data, { style: function(feature) { switch (feature.properties.KN) { default: return { fillColor: '#00'+(0x1000000+(Math.random())*0xffffff).toString(16).substr(1,2)+"00", fillOpacity: 0.5, weight: 2, color: '#111', opacity: 0.7, dashArray: '4' } } } }).addTo(map); });
Wichtig ist dabei hauptsächlich der Aufruf von L.geoJSON und der Angabe der Daten. Mit der style-Funktion ist man zusätzlich noch in der Lage einzelne Flächen einzufärben (hier per random-Grün), die Grenzen zu stylen usw. Mehr Informationen erhält man hier.
Ein komplettes Demo gibt es auch!
Heute habe ich dank des Ausfalls unseres Mailservers bei Strato den dort befindlichen Mailman auf unserem neuen Mailserver (kein Strato mehr) installiert. Da dabei ein paar Besonderheiten anfallen was die Kombination mit nginx betrifft, will ich das hier kurz dokumentieren, damit ich das beim nächsten Server schnell wieder parat habe. Ich gehe dabei davon aus, dass nginx schon installiert ist.
Zunächst einmal muss man mailman selbst installieren:
apt-get install mailman
thttpd
Da mailman auf cgi-bin aufbaut, nginx aber kein cgi-bin unterstützt, scheint der einfachste Weg zu sein, noch thttpd zu installieren (fastcgi geht theoretisch auch, wollte aber aus mir unbekannten Gründen kein PATH_INFO an Mailman weiterleiten):
apt-get install thttpd
Bei thttpd handelt es sich dabei um einen sehr kleinen HTTP-Server, der cgi-bin unterstützt und den wir als Proxy benutzen werden.
Dazu muss thttpd natürlich konfiguriert werden, was in der Datei /etc/thttpd/thttpd.conf
passiert. Hier die wichtigsten Zeilen:
port=7999 dir=/usr/lib/cgi-bin nochroot user=www-data cgipat=/** host=127.0.0.1
Wichtig ist also, dass wir auf einem anderen Port als 80 laufen, dass wir nur auf 127.0.0.1 hören, dass wir kein chroot machen, unter www-data laufen und dass wir alle URLs als cgi-Scripts interpretieren und nicht nur die mit Prefix /cgi-bin/
Dann muss der Daemon noch ggf. in /etc/default/thttpd
eingeschaltet werden und mit
/etc/init.d/thttpd start
gestartet.
nginx
Für nginx brauchen wir ggf. einen neuen virtual host, in den wir folgendes eintragen:
server { listen 80; server_name meinedomain; access_log /var/log/nginx/domain.access.log; error_log /var/log/nginx/domain.net.error.log; location /mailman/ { proxy_pass http://127.0.0.1:7999/mailman/; proxy_set_header Host $host; } location /images/mailman { alias /usr/share/images/mailman; } location /pipermail { alias /var/lib/mailman/archives/public; autoindex on; } }
Wir richten also einen ganz normalen reverse-Proxy ein.
Schliesslich müssen wir mailman nur noch sagen, dass er statt /cgi-bin/mailman
jetzt auf /mailman/
hört. Dazu müssen wir die Datei /usr/lib/mailman/Mailman/mm_cfg.py
anpassen, so dass die folgenden Zeilen darin vorkommen:
DEFAULT_URL_PATTERN = 'http://%s/mailman/' PRIVATE_ARCHIVE_URL = '/mailman/private' IMAGE_LOGOS = '/images/mailman/'
Nach Neustart von nginx sollte dann das Mailman-Interface unter http://domain/mailman/admin/[meineliste]
zu finden sein.
Das funktioniert natürlich nur, wenn Mailman und die Mailingliste auch richtig eingerichtet wurde. Dazu gibt es hier weiterführende Doku.
Weitere Hinweise zur Mailman-Migration
Da ich bei der Migration auch die URL von cgi-bin/mailman
auf /mailman/
geändert habe, müssen die Listen noch angepasst werden. Dies geschieht in /var/lib/mailman
über den Befehl
bin/withlist -l -a -r fix_url
Der geht alle Listen durch und trägt dort das Default-URL-Pattern ein.
Foto CC-BY-NC-SA von Éole Wind
Am Freitag und Samstag letzter Woche war ich in Düsseldorf und habe am Videocamp und Webvideopreis teilgenommen (meine persönlichen Eindrücke hier und hier). Ich habe dort zusammen mit Michael Praetorius eine Session zum Thema Livestreaming gegeben und Alex Knopf hat es dankenswerterweise aufgenommen. Hier das Video:
Die Slides wird es dann später auch noch geben.