Archiv der Kategorie: Programmierung

IT-Karriere: Software Developer (Backend: Java/Spring-Microservices; Frontend: React.JS)

Suchst du eine neue Stelle als Software Developer und hast Interesse in einem tollen Team (mit mir, Hurra!) in Berlin-Kreuzberg zu arbeiten? Ein Glück! Das Quarters-Development-Team sucht dich, wenn du Erfahrung in React.JS (und Webpack, SASS, HTML5, Bootstrap usw. usf. was noch so zu Frontendentwicklung gehört) und/oder wenn dein Herz eher fürs Backend schlägt und du Spring (und Java, JPA, Hibernate, PostgreSQL usw. usf.) beherrschst.

IT-Karriere: Software Developer (Backend: Java/Spring-Microservices; Frontend: React.JS) weiterlesen

Symfony Guard component without using the whole framework

Implementing authentication in Symfony can be quite complicated. Even more so, if you attempt to use only the Security component without the whole framework. In this post I’ll show you, how you can use the Symfony Guard component with a form login and a logout link. So here’s what you can expect from implementing the code from this blog entry

  • Login with username and password by form
  • Stay logged in by using a session
  • Logout via Hyperlink „/logout“

My aim is to use as little components from Symfony as possible in this tutorial. Every project is different and I don’t know which components you might want to use.

Symfony Guard component without using the whole framework weiterlesen

HTML-Tags inkl. Inhalt mit DOMDocument aus String entfernen (PHP)

Häufig muss man aus einem Stück HTML bestimmte andere HTML-Tags entfernen. In einem aktuellen Fall nutze ich im Backend eines CMS einen WYSIWYG-Editor, der für Abbildungen <figure><img …><figcaption>Bildunterschrift</figcaption></figure> einsetzt. Auf einer Übersichtsseite benötige ich den von diesem Editor generierten Text allerdings ohne Abbildungen.

Ich halte es für einen Fehler, in diesem Fall Regular Expressions (REGEXPs) zu verwenden. Ja, sie sind schnell, aber damit sie auch verschachtelte HTML-Tags greifen muss man unglaublich komplizierten Code einsetzen, der recht schnell zur Wartungshölle wird.

Stattdessen finde ich für die Manipulation von HTML DOMDocument sinnvoll. Es gibt allerdings einige Fallstricke:

  1. Das von DOMDocument eingesetzte libxml kann mit HTML5 nicht richtig umgehen und spuckt Warnungen aus
  2. UTF8-Zeichen werden zerstört, scheint ein Fehler von DOMDocument/PHP/libxml zu sein, müsste ich mal näher recherchieren
  3. PHP ergänzt automatisch eine DOCTYPE-Deklaration, sowie <html> und <body>-Tags, die man nicht benötigt, wenn man nur mit einem HTML-Fragment arbeitet

Die gute Nachricht: All diese Probleme lassen sich lösen! 🙂

So habe ich es gemacht:

/**
 * Entfernt die HTML-Node des HTML-Tags $tag aus dem HTML-Fragment $text
 * 
 * @param string $text
 * @param string $tag
 * @return string
 */
function removeTag($text, $tag = 'figure') {
   // Wichtig, denn bei leerem Input wirft loadHTML eine Fehlermeldung
   if(empty($text)) return $text;

   $dom = new DOMDocument;

   // Olle libxml-Warnungen unterdrücken
   libxml_use_internal_errors(true);
        
   /* UTF-8-Problematik lösen - wenn das aus irgendwelchen Gründen
      NICHT klappen sollte, könnte auch ein voranstellen von
      <!--?xml version="1.0" encoding="UTF-8"?--> vor $text helfen - habe ich
      allerdings nie ausprobiert */
   $dom->loadHTML(mb_convert_encoding($text, 'HTML-ENTITIES', 'UTF-8'));
        
   /* loadHTML fügt eine DOCTYPE-Deklaration, sowie und ein, die DOCTYPE-Deklaration kann sehr leicht entfernt werden, denn sie ist immer das erste Child */
   $dom->removeChild($dom->firstChild);

   /* Ich baue zuerst einen Array mit allen zu entfernenden Elementen
      Sie direkt im foreach zu entfernen scheiterte, ich nehme an, es
      hat irgendwas mit internen Indizes zu tun */
   $domElemsToRemove = array();
   $figureElements = $dom->getElementsByTagName($tag);
   foreach($figureElements as $figureElement) {
      $domElemsToRemove[] = $figureElement;
   }
   
   /* Hier werden die Elemente entfernt. Nicht das parentNode vergessen,
      ist mir zuerst passiert, kann ja nicht klappen ... */
   foreach($domElemsToRemove as $domElem) {
      $domElem->parentNode->removeChild($domElem);
   }
        
   $text = $dom->saveHTML();
                
   libxml_clear_errors();

   /* $text ist nach wie vor von... umschlossen
    * Hierfür gibt es vielfältige Lösungen, darunter str_replace, REGEXPs,
    * strip_tags(), oder replaceChild() - ich selbst brauchte 
    * GAR kein HTML-Tags in meiner Ausgabe und konnte daher strip_tags 
    * verwenden */
   return trim(strip_tags($text));
}

Dateiuploadfenster in tinyMCE4 aufrufen, ohne HTML der Seite zu ändern

Heute wollte ich in tinyMCE4 einen Dateiupload einbauen, der direkt nach Klick auf das Ordnersymbol (im Bild neben dem Eingabefeld von Quelle, rechts) das jeweilige Dateiuploadfenster des Browsers/Systems aufruft und Dateien hochläd.

Bild einfügen Dialog von tinyMCE4

D. h. ohne Zwischenschritte (wie weitere Popups oder externe Bibliotheken) soll nach dem Klick auf das Ordner-mit-Lupe-Symbol direkt dass hier angezeigt werden:

Dateiuploaddialog von Firefox auf Ubuntu Linux

Als zusätzliche Hürde kam hinzu, dass ich den Quellcode der Seite ungern verändern wollte (sonst hätte ich u. A. Code für die Validierung der dort vorhandenen Formulare umschreiben müssen). Dieser Fall ist sicherlich nicht besonders häufig, aber vielleicht geht es ja auch anderen so. Glücklicherweise gibt es eine sehr einfache Lösung via AJAX. So habe ich das ganze umgesetzt:

  1. Bevor es losgeht wird das jquery-iframe-transport-plugin benötigt, da XHR1 normalerweise keine Dateiuploads unterstützt, muss dieser Umweg gegangen werden. Einfach runterladen und einbinden
  2. Im Initialisierungscode von tinyMCE ($(„textarea“).tinymce({…}); kommt ein file_browser_callback hinzu, damit der Button überhaupt angezeigt wird, Code siehe unten
  3. Dann hole ich mir ein Formular mit dem <input type=“file“>-Button via $.get (HTML-Quellcode des Formulars weiter unten) und füge es an die aktuelle Seite an; falls es bereits existiert, spare ich den Ajax-Call ein und es geht weiter zu 3. (Übrigens: AJAX ist für die 3 Zeilen HTML-Code natürlich Overkill, man kann sie auch einfach per JavaScript direkt einfügen – allerdings werde ich in Zukunft vermutlich doch noch ein bisschen mehr serverseitige Logik hinter dem Formular brauchen, daher hab ich das so gelöst)
  4. Dann triggere ich einen Klick auf den versteckten Upload-Button, was den Dateiuploaddialog aufruft
  5. Sobald eine Datei ausgewählt wurde, wird das Formular an ein serverseitiges Skript geschickt, es gibt ggf. noch ein bisschen Errorhandling – ich lasse Fehlercodes und sonstige Daten per JSON zurückliefern, kann man natürlich auch anders machen
  6. Per win.document.getElementById(field_name).value = $(„#fupload“).val(); wird der Dateiname noch in das tinyMCE-Formularfeld gesetzt – ggf. ist es notwendig stattdessen einen Dateinamen zu verwenden, der vom serverseitigen Skript zurückgeliefert wird, je nach Situation
  7. Am Schluss wird das temporäre Formular wieder aus dem Dokument gelöscht und hinterlässt keine Spuren. (Falls jemand den Dateiuploaddialog abbricht, findet dieser Schritt nicht statt)

Der tinyMCE-Initialisierungscode

file_browser_callback: function(field_name, url, type, win) {
   if($("#fuploadform").length) {
      $("#fupload").click();
   } else { 
      $.get('uploadform.html', function(data) {
         $("body").append(data);
         $("#fupload").click();
         $("#fupload").on("change", function() {
            $.ajax({
               type: "POST",
               url: 'fileupload.php',
               files: $("#fupload", this),
               dataType: 'json',
               iframe: true,
               success: function(data) {
                  if(data.error=='none') win.document.getElementById(field_name).value = data.filename;
                  else alert('Ein Fehler ist beim Upload aufgetreten: '+data.error);
               }
            });
            $("#fuploadform").remove();
         });
      });
   }
}

Das HTML-Formular in uploadform.html

Nicht vergessen, den enctype zu setzen, sonst wird das mit dem Upload nix. 😉 Und natürlich per display: none; dafür sorgen, dass das temporäre Formular nicht angezeigt wird. Ggf. kann es auch noch sinnvoll sein mit dem accept-Attribut die möglichen MIME-Types zu setzen, habe ich hier nicht gemacht.

<form id="fuploadform" enctype="multipart/form-data" method="post">
   <input id="fupload" style="display: none;" name="fupload" type="file" />
</form>

Zu guter letzt noch die fileupload.php

(Ich verwende ein anderes serverseitiges Skript, als hier dargestellt, aber es wäre für dieses Beispiel zu kompliziert, daher zitiere ich einfach mal aus teilen der PHP-Dokumentation) – natürlich könnte man noch weitere Fehler hier abfangen (Datei zu groß, falscher Dateityp, usw. usf.).

<?php 
/* Verzeichnis wo die Datei hinsoll hier einstellen */
$uploaddir = '/var/www/uploads/';

$uploadfile = $uploaddir . basename($_FILES['fupload']['name']);

if (move_uploaded_file($_FILES['fupload']['tmp_name'], $uploadfile)) {
    $error = 'none';
} else {
    $error = 'Möglicherweise eine Dateiupload-Attacke';
}

print(json_encode(array('error', $error)));

Ich hoffe, dieses Beispiel hilft dem ein oder anderen weiter, wie immer freue ich mich über Kommentare.

In WordPress mittels wpdb Objekte in die Datenbank schreiben

Die wpdb-Klasse in WordPress kam mir schon immer ein bisschen seltsam vor, da ich an PDO gewöhnt war und lange danach gesucht habe, wie ich denn nun meine Objekte in die Datenbank schreibe. Die einzige Lösung, die ich bisher gefunden habe, ist per Reflection, vielleicht hat ja jemand eine bessere Idee? Ich freue mich über Kommentare! Hier der Code:

 

    /**
     *  
     *
     * @param string $table Die Tabelle, in die per INSERT geschrieben werden soll
     * @param object $object Das Objekt
     * @return int|false
     */
   function insertObject($table, $object) {
        if(!is_object($object)) return false;

        $reflection = new ReflectionObject($object);
        // getProperties() als Parameter ReflectionProperty::IS_PUBLIC
        // übergeben, falls nur public-member des Objekts
        // in die Datenbank geschrieben werden sollen
        $properties = $reflection->getProperties();
        $dataArray = array();
        foreach($properties as $property) {
            // Die folgende Zeile löschen, falls nur public-member des Objekts in die Datenbank geschrieben werden sollen
            // Für beides gibt es meiner Meinung nach sinnvolle Use Cases, ggf. einen entsprechenden Parameter hinzufügen
            $property->setAccessible(true);
            $dataArray[$property->getName()] = $property->getValue($object);
        }

        global $wpdb;
        return $wpdb->insert($table, $dataArray);
    }

Üblicherweise verzichte ich in meinem Code auf den $table-Parameter um es noch zu vereinfachen und verwende entweder den Klassennamen als Tabellennamen oder habe eine statische Konstante const TABLE = ‚tabellenname‘; in der jeweiligen Klasse.

Agavi Form Population Filter (FPF) und HTML5

Ich arbeite gelegentlich mit dem PHP-Framework Agavi. Um hier Formularfelder vorauszufüllen und insbesondere auch um Fehlermeldungen nach dem Ausfüllen von Formularen anzeigen zu können, setzt Agavi den Form Population Filter (FPF) ein. Die Idee ist, dass das Framework den DOM-Baum analysiert und die Meldungen direkt an die entsprechende Stelle einfügt, die der Entwickler somit nur einmal global spezifizieren muss (z. B. immer vor dem jeweiligen Feld). Soweit die Theorie.

In der Praxis ist der Form Population Filter einer der größten Teufeleien, die ich jemals erlebt habe, er verursacht alle möglichen ärgerlichen Seiteneffekte, da er ja das Markup der ausgegebenen Seite massiv verändert und funktioniert häufiger nicht, als er es tut.  So führt zum Beispiel eine nicht-wohlgeformte Seite (falsche Tags, illegale Entities etc.) dazu, dass der FPF entweder seinen Dienst komplett einstellt, der zugrunde liegenden libxml sei Dank, oder im Besten Fall die Seite mit falsch codierten Entities wieder ausgibt. Insbesondere bei HTML5-Seiten ist dies ein Problem, da libxml kein HTML5 unterstützt (oder zumindest der FPF nicht). Laut Dokumentation sollte es ausreichen, dem FPF mit dem Parameter ignore_parse_errors klar zu machen, dass er eben dies tun solle, dies funktioniert jedoch nicht.

Wer das gleiche Problem hat, so konnte ich mir helfen:

<ae:parameter name="ignore_parse_errors">true</ae:parameter>
 <ae:parameter name="force_output_mode">xhtml</ae:parameter>
 <ae:parameter name="parse_xhtml_as_xml">false</ae:parameter>

Durch force_output_mode wird der FPF gezwungen, anstatt HTML XHTML auszugeben. Das ist natürlich großer Unsinn und führt zu allen möglichen Fehlern, die man jedoch wieder abfangen kann, indem man parse_xhtml_as_xml abstellt, sodass intern wieder loadHtml anstatt loadXml verwendet wird. Alle verbleibenden Fehler werden mit ignore_parse_errors ignoriert (was kurioserweise beim output_mode xhtml funktioniert, nicht jedoch bei html). Bei ignore_parse_errors sind neben true und false auch noch LIBXML_ERR_… möglich, siehe Release Notes von Agavi 1.0.5.

Man beachte, dass diese Methode ebenfalls gewisse Seiteneffekte nach sich zieht (aus <script src=“foo“></script> wird z. B. <script src=“foo“/>, was aus XHTML-Sicht völlig korrekt ist, aber, zumindest bei meinem Browser in Verbindung mit HTML5  nicht mehr funktioniert).

Liste aller Blogs in einem WordPress-Netzwerk (Multisite)

Ich verwende ungern Funktionen, die als deprecated (veraltet) markiert sind, wie in WordPress schon seit langer Zeit die Funktion get_blog_list(). Eine Alternative zu schreiben ist allerdings recht problemlos möglich, hier der Code inkl. Kommentaren (ich habe bewusst darauf verzichtet, die Liste direkt aus der Datenbank zu holen, da ich im Zweifelsfall lieber die API verwende – mir ist bewusst, dass die Performance ggf. – gerade bei größeren Netzwerken – bei meiner Methode nicht besonders gut ist):

 /**
* Liefert einen Array mit allen Blogs in einem Netzwerk zurück
*
* @returns array
*/
function listBlogs() {
   $blogList = array();        

   // Array aller Super-Admins - das sind die Netzwerk-Administratoren, die die Blogs anlegen
   $superAdmins = get_super_admins();

   foreach($superAdmins as $admin) {
      // Leider liefert get_super_admins() einen Array von login-Namen (und nicht IDs oder WP_User-Objekten), auch die keys des Arrays sind keine IDs
      // Daher wird für jeden Eintrag zunächst das zugehörige User-Objekt und daraus die ID gesucht
      $admin = get_user_by('login', $admin);

      // get_blogs_of_user() ist im Gegensatz zu get_blog_list() nicht als deprecated markiert und kann also verwendet werden
      $blogList = array_merge($blogList, get_blogs_of_user($admin->ID));
   }

   return $blogList;
}

 

Wichtig: Das größte Problem mit dieser Funktion ist, dass die Ergebnisliste nun nach den anlegenden Admins (und nicht etwa alphabetisch etc.) sortiert ist. In meinem Anwendungsfall war das egal, wer eine andere Sortierung benötigt, muss die Funktion entsprechend umschreiben

jQuery-Datatables mit serverSide und columnFilter Plugin

DataTables sind bekanntlich die beste Erfindung seit geschnittenem Brot. Und um das Brot zu belegen, äh, die Tabellen zu filtern, ist Jovan Popovics großartiges DataTables ColumnFilter Plugin nutzenswert. Beides habe ich in eigenen Projekten im Einsatz und es funktioniert großartig. Als ich vor kurzem eine DataTable auf serverseitige Daten umgestellt habe (serverSide: true) habe ich allerdings festgestellt, dass meine columnFilter nicht mehr funktionieren. Die Lösung war einfach: Ich hatte diese versehentlich mehrfach initialisiert, d. h. $("foo").dataTable().columnFilter(...); wurde mehr als einmal aufgerufen, was wohl bei nicht-serverseitigen Daten keine negative Auswirkung hat, bei serverseitigen jedoch zu einem dieser lustigen „oSettings not defined“ Fehler führt. Vielleicht hilfts ja jemandem weiter.

Schulung zum Thema Webseiten erstellen geplant

Ich spiele mit dem Gedanken Schulungen zum Thema „Wie erstelle ich meine eigene (dynamische) Webseite und bringe sie ins Internet?“ anzubieten. Es soll sich um einen Anfängerkurs handeln, der Grundlagen von HTML, CSS und PHP vermittelt, vielleicht noch ein bisschen MySQL. Ganz gerne würde ich für Freunde/Verwandte/Bekannte einen „Probelauf“ zum halben Preis anbieten, um erstmal Feedback zu sammeln (z. B. ist meine Planung für Einsteiger zu umfangreich? Oder im Gegenteil viel zu wenig? Fehlt noch etwas wichtiges? Usw.). Es stellt sich die Frage, ob überhaupt jemand von diesen an einer solchen Schulung Interesse hätte, daher hiermit der Aufruf: Schreibt mir einen Kommentar oder eine Nachricht, wenn ihr bei sowas mitmachen würdet.

Schulung zum Thema Webseiten erstellen geplant weiterlesen