Barrierefreie Farbkontraste 2: der Sourcecode

OK jetzt gilts: man kann eine beliebige RGB-Farbe wählen und sich ausgeben lassen, welche der 140 benannten HTML-Farben einen guten (barrierefreien) Kontrast dazu liefert. Das kann man nicht nur für Schriften verwenden, sondern auch für Logos und Icons, die auf einen farbigen Hintergrund gesetzt werden sollen.

Voraussetzung ist eine Tabelle, in der die 140 Farben mit Color Group (Farbfamilie), Namen und Hex- und RGB-Notation gespeichert sind, ich zeig hier noch mal einen Ausschnitt:

mysql_farbtabelle

mysql_farbtabelle

Wie die Farben da reinkommen beschreibe ich jetzt hier nicht, das ging über ein Excel-Makro und ist letzendlich völlig wurst für die Funktionalität. Also, wir haben unsere Farbtabelle.

Jetzt brauchen wir eine Datenbankverbindung, die beschreibe ich hier auch nicht mehr extra. Dann muss wieder mal ein Formular her, für die Auswahl der Basisfarbe bietet sich der Input-Type Color an, bei dem man in den meisten modernen Browsern einen schicken Colorpicker angezeigt bekommt. Man könnte auch ein Eingabefeld für den Hex- oder RGB-Wert nehmen, das ist letztlich Geschmackssache. Also, los gehts, mit einer Datei namens farbnamen.php, die sieht zunächst so aus:

<!DOCTYPE html>
<html lang="de">
<style type="text/css">
h1,h2,h3{
    font-family:Arial;
}
</style>
<head>
<title>Colors Arbeitspapier</title>
</head>

<body>
<h1>Beispiel Contrast Colors mit HTML Named Colors</h1>

<form action="farbnamen.php" method="post">

      <h2>Bitte eine Farbe auswählen:</h2>
      <input type="color" name="farbe" value=""><br>
      <h2>Bitte eine Farbfamilie für die Kontrastfarbe wählen:</h2>
    <select name="farbfamilien">
        <option value="BLUE">Blau</option>
        <option value="GREEN">Grün</option>
        <option value="PURPLE">Violett</option>
        <option value="WHITE">Weiß</option>
        <option value="BROWN">Braun</option>
        <option value="YELLOW">Gelb</option>
        <option value="GRAY">Grau</option>
        <option value="RED">Rot</option>
        <option value="PINK">Pink</option>
        <option value="ORANGE">Orange</option>
      </select>
     <br><br>
    <input type="submit" name = "senden">
  
</form>

Das ist erstmal straightes HTML, zuerst kommt der Color Picker, dann das Dropdownfeld für unsere Farbfamilien, und das wars auch schon. Man könnte den Select auch mit einem Distinct aus der Datenbank füttern, das hab ich mir jetzt gespart und die 10 Einträge so reingeschrieben.

Los geht es wie immer wenn auf den Submit-Button geklickt wurde. Der Colorpicker liefert den Hex-Wert der gewählten Farbe, das Dropdownfeld den Namen der Farbfamilie. Den Hex-Wert zerlege ich in die einzelnen Komponenten, die brauche ich später um meine Funktion zur Berechnung der wahrgenommenen relativen Helligkeit damit zu füttern, dazu weiter unten mehr. Das Ganze sieht jetzt erstmal so aus:

startbildschirm

startbildschirm

Bei Klick auf das schwarze Farbkästchen poppt der Colorpicker auf, in Chrome beispielsweise sieht der so aus:

chrome_colorpicker

chrome_colorpicker

Das Dropdownfeld ist auch keine Überraschung:

dropdown

dropdown

Aber jetzt wirds gleich interessant, ich habe nämlich für die Zerlegung des Hex-Wertes in seine RGB-Komponenten eine schicke Funktion gefunden, das geht mit sscanf():

<?php
require_once('config.php');

if(isset($_POST['senden'])){
    
    
    echo "Sie haben diese Farbe gewählt:<span style='background-color:".$_POST['farbe']."'>Hexadezimalwert: ".$_POST['farbe']."</span><br>";
    
    $akt_farbe = $_POST['farbe'];
    
    //RGB-Werte aus hex holen
    $hex = $akt_farbe;
    list($r, $g, $b) = sscanf($hex, "#%02x%02x%02x");
        
    echo "<h3>Die relative Helligkeit beträgt: ".Brightness($r,$g,$b)."</h3>";
    echo "<h3>Kontrastfarbe aus der Farbfamilie ".$_POST['farbfamilien']."</h3>";
    echo "<hr>";
    $input_brightness = Brightness($r,$g,$b);

Mit der Funktion Brightness wird wie gesagt die wahrgenommene Helligkeit der gewählten Farbe bestimmt, Input-Parameter sind die drei RGB-Komponenten. Die Funktion selber sieht so aus:

//relative (wahrgenommene) Helligkeit aus RGB berechnen
function Brightness($r, $g, $b)
    {
       return sqrt(
          $r * $r * .241 + 
          $g * $g * .691 + 
          $b * $b * .068);
    }

Jetzt hole ich mir aus der Datenbank alle Farben, die zu der gewählten Farbfamilie gehören. Zuerst setze ich noch einen Flag $gefunden, der bleibt auf False wenn in der gewählten Farbfamilie keine Farbe mit einem ausreichenden Kontrastwert gefunden wurde und dient dann weiter unten zur Info-Ausgabe.

Dann gehe ich mit einem While über alle gefunden Farben und gebe nur diejenigen aus, bei denen eine Differenz > 130 zur relativen Helligkeit der vom Benutzer vorgegebenen Farbe gefunden wird.

//alle passenden Einträge der Farbtabelle ausgeben
        //Flag default  
        $gefunden = false;
        
        //Nur die Einträge der gewählten Farbfamilie auslesen
          $abfrage = mysqli_query($conn,"SELECT * FROM $table 
                    WHERE familie LIKE '".$_POST['farbfamilien']."' "); 
          while($row = mysqli_fetch_array($abfrage,MYSQLI_ASSOC)) 
          { 
            //RGB-Werte aus hex holen
            $hex = $row['hex'];
            list($r, $g, $b) = sscanf($hex, "#%02x%02x%02x");
            //Helligkeit bestimmen
            $row_brightness = Brightness($r,$g,$b);
            
            //Kontrastwert berechnen
            $differenz = abs($row_brightness - $input_brightness);
            
            //Nur Kontrast höher als 130 ausgeben (Wert ggf anpassen)
            if ($differenz > 130){
                //flag setzen wenn mindestens ein Kontrast > 130 gefunden wurde
                $gefunden = true;
            echo ' <span style="color:'.$row['farbname'].'">'.$row['farbname'].'</span>.'.$row['hex'].'rel. Helligkeit: '.$row_brightness.' Differenz: '.$differenz.'<br>'; 
            echo "<h1><span style='color:".$row['farbname']."; background-color:".$akt_farbe."'>".$row['farbname']." ist ein guter Kontrast</span></h1>";
            echo "<hr>";
            }
            
        } // ende von while $row...
if($gefunden==false){echo "Keine passende Kontrastfarbe in der Farbfamilie ".$_POST['farbfamilien']." gefunden";}

} //Ende von isset senden

Für die Ausgabe der passenden Kontrastfarben verwende ich Spans und formatiere sie mit den entsprechenden Farbwerten. Falls keine Farbe mit ausreichendem Kontrast gefunden wurde, gebe ich dies als Info aus. Das war’s!

Hier noch ein Screenshot der Ausgabe, als Besipiel habe ich als Grundfarbe Gelb und für die Kontrastfarbe die Farbfamilie Blau gewählt:

farben_ausgabe

farben_ausgabe

Fertig ist das Werkzeug zur Kontrastfarbenbestimmung. Zugegeben könnte man es noch wesentlich hübscher formatieren, aber die Funktionalität haut hin, das genügt mir jetzt erstmal. Schlanke noch nicht mal 100 Zeilen Code, das ist mal wieder was für mich und meine minimalistischen Freunde – viel Spaß beim Nachbauen!

Nachtrag

Jetzt konnte ich mir es doch nicht verkneifen, die ganze Sache noch ein bißchen ansehnlicher zu gestalten. Der Prototyp sieht jetzt so aus:

aufgehuebscht

aufgehuebscht

Die Farben hole ich mir natürlich aus der Datenbank, wenn ich dazukomme, schreib ich noch einen neuen Artikel darüber. Aber hier ist jetzt mal Schlussende, der Beitrag ist lang genug

 


 

 

Barrierefreie Farbkontraste – ich hab da so ne Idee

Also, wie man ausrechnet ob auf einem farbigen Hintergrund eine schwarze oder weiße Schriftfarbe besser lesbar ist, das hatten wir ja gerade eben schon mal. Jetzt ist der Kunde aber König, und man kommt auch mal in die Verlegenheit, eine farbige Schrift auf farbigem Grund darstellen zu sollen. Die Anforderung könnte also heissen: es soll eine grüne Schrift auf rotem Grund ausgegeben werden, und die soll möglichst gut lesbar sein. Da kann man einen Colorpicker nehmen und so ungefähr schauen was besser lesbar ist, aber das ist ja doch bloß Trial&Error. Es gibt auch Formeln, mit denen man so etwas berechnen kann, fündig wird man z.B. beim W3C, oder auf dieser Seite von nbtech. Nach deren Algorithmus kann man die „perceived brightness“, also die wahrgenommene Helligkeit einer Farbe als Wert zwischen 0=schwarz und 255=weiß berechnen. Das ist nicht trivial, weil z.B. grüne Töne vom menschlichen Auge als heller wahrgenommen werden als rote Schattierungen. Darum berechnet man die jeweilige wahrgenommene Helligkeit, und vergleicht die Farben dahingehend. Ein Unterschied von ca. 120 zwischen den zwei Farben wird nach W3C als ausreichender Kontrast angesehen. Die Formel lautet wie folgt:

brightness  =  sqrt( .241 R2 + .691 G2 + .068 B2 )

sqrt ist die Quadratwurzel, R, G, B sind unsere guten alten Bekannten Rot- Grün- und Blauanteil aus dem RGB-Farbraum, wie wir sie in HTML so gerne verwenden (ausführliche Infos hierzu z.B. hier bei W3Schools)

Gar nicht so einfach: was ist Rot, was ist Grün?

Auch das ist nicht trivial, weil von der Meinung des Betrachters abhängig. Ein dunkles Rotbraun mag für einen noch als Rot durchgehen, der nächste ordnet es schon den braunen Tönen zu. Ein sonniges Gelb kann auch als Orange gelten, und ein blaustichiges Grün als Blauton herhalten. Sowas kann man nicht berechnen, da kann man sich nur eine Krücke suchen und daran festhalten. Bei den 16,7 Mio vorhandenen RGB-Farben ist es auch ein Ding der Unmöglichkeit, überall die korrekte Farbfamilie zu bestimmen. Wir nehmen ein paar Farben weniger, das tuts als Arbeitshypothese auch.

Eine Untermenge: die benannten HTML-Farben

Ich habe da eine interessante Zusammenstellung der benannten HTML-Farben im Web gefunden. Bei https://htmlcolorcodes.com/color-names/ gibt es eine Tabelle der von allen modernen Browsern unterstützten benamsten Farben, die nach Farbfamilien (Color Groups) gruppiert ist. Auch bei w3schools gibt es eine gut gestaltete Seite über die Color Groups, nur sind sie da ein bißchen anders zusammengefaßt.

Die Seite von htmlcolorcodes.com habe ich mir mal als Arbeitsgrundlage geklemmt und in eine Datenbanktabelle gejagt. Es sollten eigentlich 140 Farben sein, auf der Seite sind aber 143 Farben aufgeführt. Die Tabelle enthält Duplikate, da werden Farben unter zwei verschiedenen Farbfamilien genannt. Ist jetzt aber nicht so schlimm, letztendlich ist die Information wertvoll, welche Farbtöne als Rot, Blau, Gelb usw. empfunden werden. Es gibt eine Liste von 10 Farbfamilien. Ich werde bei den englischen Bezeichnungen bleiben, schliesslich sind auch die HTML-Farbnamen in Englisch.

Hier mal die Liste der Farbfamilien, mit der jeweiligen Anzahl der zugeordneten Farben:

YELLOW    11
WHITE    17
RED    9
PURPLE    19
PINK    6
ORANGE    6
GREEN    23
GRAY    10
BROWN    17
BLUE    25

Wie gesagt, die Summe ist hier 143, nicht 140, aber das vernachlässigen wir jetzt mal. Dass Blue die meisten Farbtöne zugeordnet hat verwundert auch nicht weiter, da Blau nunmal eine sehr beliebte Farbe bei Webdesignern und Grafikern ist und im Web sehr häufig verwendet wird.

Wer sagt, dass das Rot ist?

Unter der Farbfamilie „Red“ sind 9 benannte Farben zu finden:

family_red

family_red

Da sieht man schon, daß die Zuordnung der einzelnen Farbtöne zu denen Farbfamilien ausgesprochen subjektiv und diskutabel ist, Lightcoral würde ich eher unter Pink einordnen. Wenn man sich die RGB-Werte dieser Farben anschaut, wird man auch nicht schlauer.

red_family_rgb

red_family_rgb

Es ist mitnichten so, dass diese Farben alle einen besonders hohen R-Anteil haben, Darkred ist zum Beispiel so ein Ausreisser mit 139 als R-Wert, dafür sind die G-und B-Werte Null. Ich hab lange rumgesucht, aber bisher keine praktikable Berechnungsmethode gefunden, mit der man x-beliebige Farbtöne einer der obengenannten Farbfamilien zuordnen kann. Deswegen verwende ich die Zuordnungen von htmlcolocodes.com, da hat man zumindest mal einen Anhaltspunkt.

Und wozu das Ganze?

Ganz einfach: mir schwebt sowas wie ein spezialisierter Colorpicker vor, in dem man eine frei wählbare Farbe vorgibt. Dann wählt man aus, aus welcher Farbfamilie die Kontrastfarbe kommen soll, und erhält einen oder mehrere Vorschläge, welche Farbtöne einen guten Kontrast bieten würden. Das als Arbeitshypothese. Mal sehen, wie weit ich damit komme.

Die Voraussetzung: eine MySQL-Tabelle mit allen 140 benannten Farben

Diese enthält die Farbfamilie, den Farbnamen, den Hex-Wert und den RGB-Wert. Die habe ich natürlich nicht per Hand eingehackt, da habe ich einen kleinen Umweg über ein Excel-Makro genommen, mehr wird nicht verraten. Die Tabelle sieht so aus, die Feldnamen sind selbsterklärend:

mysql_farbtabelle

mysql_farbtabelle

Das wird meine Ausgangsbasis. Und ich komme mit unter 100 Zeilen Code aus. Den Source gibt es dann im nächsten Beitrag.

Das kann ich einfacher: Kontrastfarbe berechnen

Heute war in The Whip (Ausgabe 759) ein Artikel darüber, dass Lyft ihren ColorBox-Algorithmus zur Berechnung der optimalen Kontrastfarbe Open Source zur Verfügung gestellt hat, hier mal der Link zum Artikel:

Lyft Open Sources ColorBox Algorithm for Building Accessible Color Systems

Ziel der Sache ist, schwarze oder weiße Schrift auf farbigem Grund mit einem Farbkontrast darzustellen, der möglichst gut lesbar und damit barrierefrei ist.

Ich hab mir den Source auf Github natürlich gleich mal angesehen, schließlich habe ich vor nicht allzulanger Zeit hier ein kleines Javascript vorgestellt (Kontrastfarbe automatisch berechnen) das genau diesen Zweck erfüllt. Was soll ich sagen, das Skript von Lyft ist recht eindrucksvoll und etliche Seiten lang. Ich mach das selbe, mit weniger als 80 Zeilen Sourcecode, und davon ist noch die Hälfte für die HTML-Ausgabe. Warum umständlich, wenns auch einfach geht 🙂

Ein kleiner Chat in PHP – wieder mal was für Minimalisten

Da ich mich derzeit viel im Chat in meinem Lieblings-Handarbeitsforum herumtreibe, dachte ich mir: so einen kleinen, eher spartanischen Chatroom solltest du eigentlich auch selber programmieren können, das kann so schwierig nicht sein. Bei Google gibt es -zig Beispiele, wie man so etwas angehen könnte, ich hab mich auf die Suche nach etwas Einfachem gemacht und bin hier bei php-einfach.de fündig geworden.

Ich hab allerdings ein paar Fehler bereinigt, auf PHP 7 umgeschrieben, die Ajax-Funktionalität eliminiert (warum kompliziert wenns auch einfach geht?), die MySQL-Tabelle komplett anders strukturiert, eine Teilnehmerliste hinzugefügt und sämtliche Formatierungen umgekrempelt, so dass letztendlich eine komplett umgeschmissene Logik dabei herausgekommen ist. Wie gesagt, die Grundidee stammt von php-einfach.de, und ich bedanke mich auch artig für den Denkanstoß. Aber ich denke, es ist genug kreative Eigenleistung dazugekommen, dass ich den Source hier als auf meinem Mist gewachsen einstellen kann.

Die Voraussetzungen

Viel ist nicht dran: man soll seinen Nick und seine Nachricht eingeben können, auf den Senden!-Button klicken, und dann soll im Chatfenster die Nachricht für alle sichtbar am unteren Ende des Fensters auftauchen. Ich hab noch die Uhrzeit wann die Nachricht geschrieben wurde mit dazugenommen, und protokolliere im Hintergrund auch das Datum mit, da ich in meinem Chat immer nur die Beiträge des heutigen Tages anzeigen möchte.

Aussehen tut das Ganze so:

screenshot_chat

screenshot_chat

Man kann seinen Nick eingeben (der bleibt auch nach dem Abschicken stehen), man kann seine Nachricht eingeben, und man kann auf den Senden!-Button klicken. Im Chatfenster scrollen die älteren Einträge nach oben weg, der neueste Eintrag landet immer ganz unten, und es wird noch die Uhrzeit zu jedem Beitrag angezeigt. Es werden nur die Einträge des heutigen Tages angezeigt, den Rest habe ich natürlich auch gebunkert, daraus könnte man noch ein Chat-Archiv erstellen. Rechts kriegt man noch eine Liste angezeigt, wer heute schon aller im Chat war, und das wars.

Im Hintergrund: natürlich eine MySQL-Tabelle

Die ist recht einfach strukturiert und heißt bei mir chat_daten:

chat_daten

chat_daten

Eine AutoIncrement-id als Primärschlüssel, und vier Textfelder für nick, eintrag, uhrzeit und datum, das war schon alles.

Der Sourcecode

Wir packen die Connect-Daten in eine externe Datei:

<?php 
$server    = 'localhost'; 
$user    = 'root'; 
$pass    = ''; 
$db    = 'chat'; 
$table    = 'chat_daten'; 
$conn=mysqli_connect($server, $user,$pass); 
mysqli_select_db($conn,$db); 
?>

Die Felder für Nick und Eintrag und den Senden-Button packen wir in ein Formular:

<?php  
//Config auslesen 
require_once('inc/config.php'); 
  
$heute = date("d.m.Y",time());
echo '<img src="el_logo.jpg">';
echo '<h1>Evis Chat live am '.$heute.'</h1>';

echo ' 
 <form action="index.php" method="post"> 
    <table border="0"> 
        <tr> ';
//Falls ein Nick eingegeben wurde, diesen als default in die Textbox setzen
if(!empty($_POST['nick'])){
    $akt_nick = $_POST['nick'];
}
else{
    $akt_nick='';
};
    
echo '<th>Nickname:</th><td><input type="text" name="nick" value="'.$akt_nick.'" id="textbox1"></td></tr>                 
        <tr> 
            <th>Nachricht:</th><td><input type="text" name="eintrag" value="" id="textbox">&nbsp;<input type="submit" name="eintragen" value="Senden!" id="button">&nbsp;</td> 
        </tr> 
    </table> ';

Damit man den Nick nicht immer neu eingeben muss, frage ich ab ob das Feld schon gesetzt wurde und schreibe dann den Wert als Default rein.

Dann starte ich, wenn auf den Button Senden geklickt wurde, und schreibe mir Nick, Eintrag, Datum und Uhrzeit in die Datenbank:

if(isset($_POST['eintragen'])){ 

if(empty($_POST['nick']) or empty($_POST['eintrag'])){ 
    echo '<script>alert("Bitte Nick UND Message eingeben")</script>'; 
}else{ 
    //Variablen definieren und mit "POST" Daten füllen (Mit htmlspecialchars filtern, Apostrophe maskieren..) 
    $nick = addslashes(htmlspecialchars($_POST['nick'])); 
    $eintrag = addslashes(htmlspecialchars($_POST['eintrag'])); 
    $uhrzeit = date("H:i",time());
    $datum = date("d.m.Y",time());

    
//Nick + Eintrag + datum+uhrzeit in die Datenbank schreiben 
    mysqli_query($conn,"INSERT INTO $table  
                      (nick,eintrag,uhrzeit, datum) VALUES 
                      ('$nick', '$eintrag', '$uhrzeit', '$datum')"); 
    } 

}

Falls kein Nick oder keine Nachricht eingegeben wurden, gibts einen Alert, andernfalls mache ich den Insert mit den aktuellen Daten.

Für die Ausgabe des Chats baue ich mir eine Div mit zwei darin enthaltenen Divs, weil ich Nick und Nachricht linksbündig, die Uhrzeit aber rechtsbündig ausgeben möchte. Das CSS dazu gibt es weiter unten, hier erstmal die Divs für das Chatfenster:

//alle Einträge von heute ausgeben
echo '<div id="chat_fenster" >';
        
        //Nur die Einträge von heute auslesen
          $abfrage = mysqli_query($conn,"SELECT * FROM $table WHERE datum LIKE '$datum'"); 
          while($row = mysqli_fetch_array($abfrage,MYSQLI_ASSOC)) 
          { 
            echo '<div id ="nick_und_message"><span style="color:red">'.$row['nick'].':</span><span style ="color:black">'.$row['eintrag'].'</span></div>
            <div id = "uhrzeit"><span style ="color:green">'.$row['uhrzeit'].'</span></div><br>'; 
          }
echo '</div>';

Als kleines Extra gebe ich rechts neben dem Chatfenster noch eine Liste aller Teilnehmer von heute aus:

//alle Teilnehmer von heute ausgeben
echo '<div id="teilnehmer_fenster" >';
        echo '<h2>Heute im Chat:</h2>';
        //Nur die Teilnehmer von heute auslesen
          $abfrage = mysqli_query($conn,"SELECT DISTINCT nick FROM $table WHERE datum LIKE '$datum'"); 
          while($row = mysqli_fetch_array($abfrage,MYSQLI_ASSOC)) 
          { 
            echo $row['nick'].'<br>'; 
          }
echo '</div>';

Jetzt noch ein wenig CSS-Kosmetik:

<style type="text/css"> 
    
    #textbox{ 
        width:600px; 
        border:1px solid blue; 
    } 
     
    #textbox1{ 
        width:80px; 
        border:1px solid blue; 
    }
    
    #button{ 

        border:1px solid #FF1493; 
        cursor:pointer; 
    } 
     
    #button:hover{ 
        border:1px solid #3399FF; 
    } 
    
    #nick_und_message{
        width:80%;
        float:left;
    }
    #uhrzeit {
        width:20%;
        float:right;
        text-align: right;
    }
    #chat_fenster{
        border:2px solid blue;
        width:750px; 
        height:350px; 
        padding:3px; 
        overflow: auto;
        float:left;
    }
    
    #teilnehmer_fenster{
        float:right;
        width:200px;
    }
</style>

Wichtig ist hier der Eintrag overflow:auto für das Chatfenster, der sorgt für den Scrollbalken. Damit jetzt die bereits vorhandenen Einträge nach oben wegscrollen und der letzte Eintrag immer im Chatfenster ganz unten auftaucht, noch ein kleiner Javascript-Trick:

<script>
window.onload = function()
{
//Chatfenster nach oben wegscrollen, damit immer die neuesten Einträge sichtbar sind
document.getElementById('chat_fenster').scrollTop = 9999999;
}
</script>

Man setzt einfach einen sehr hohen Wert für das Scrollen nach oben, damit ist die Liste immer nach oben weggescrollt und das Ende mit den neuesten Beiträgen bleibt im sichtbaren Bereich.

Das wars – viel Spaß beim Nachbauen! Das liesse sich natürlich noch beliebig erweitern, in einem CMS könnte man z.B. den Chat nur für eingeloggte User freischalten, dann entfällt das Abfragen des Nicks. Man könnte auch noch ein Chat-Archiv anlegen, in dem man die Beiträge der letzten Tage einsehen kann, und man könnte sonst noch allerhand Spielereien veranstalten, da kann sich jeder selber austoben. Ich lass es mal so stehen, als wirklich simple Lösung.

preg_replace & Co. im praktischen Einsatz

Ich bin immer noch an den exportierten Posts aus WordPress, die wir im Q&D-Editor schonmal manuell nachbearbeitet haben. Dabei sind sicher etliche Dinge aufgefallen, die man programmatisch korrigieren könnte, und wie man das praktisch angeht, schreib ich hier mal auf.

Wir haben immer noch die Ausgangstabelle rezepte, in der nur die ID, der post_content und der post_title drinstehen. Damit wir unsere Rohdaten nicht verhunzen, legen wir uns erstmal eine leere Kopie dieser Tabelle an, das geht im phpmyadmin unter „Operationen“, kopiere Tabelle, nur Struktur, oder mit dem simplen SQL:

CREATE TABLE rezepte_arbeit LIKE rezepte

Das kopiert die Struktur inklusive evtl. vorhandener Indizes etc. und wird unser Arbeitspferd. (Falls man mal die Daten mitkopieren möchte, CREATE TABLE table2 SELECT * FROM table1, aber das nur als Anmerkung am Rande)

Zur Kontrolle des Ergebnisses klemmt man sich die (noch leere) Arbeitstabelle in den Q&D-Editor. Spätestens jetzt macht es Sinn, den Namen der Tabelle auf eine Variable zu legen und die beiden SQLs entsprechend anzupassen.

//Arbeitstabelle festlegen
$arbeitstabelle = "arbeit_rezepte";
echo "<h1>Tabelle: ".$arbeitstabelle."</h1>";
...
$sql = "SELECT * FROM ".$arbeitstabelle." WHERE post_content LIKE '%".$filter."%'";
...
$sql = ('SELECT * FROM '.$arbeitstabelle.' WHERE post_content LIKE "%'.$filter.'%" 
LIMIT '.$limit.', '.$ergebnisse_pro_seite.'');

Wer es ganz genau haben möchte, legt auch noch den Dateinamen des Q&D-Editor Skripts auf eine Variable und paßt die Navigationslinks entsprechend an, aber das ist eigentlich schon Fleißarbeit:

//Dateinamen auf Variable legen
$datei = "arbeitspferd.php";
...
if($limit>0){echo '<a href="'.$datei.'?seite_nr='.($limit-1).'"><button type="button">Voriger Datensatz</button></a>';}
echo '<a href="'.$datei.'?seite_nr='.($limit+2).'"><button type="button">Nächster Datensatz</button></a><br>';
...
 //Nav Links alle Seiten
 for ($i=1; $i<=$row_total; $i++) { 
    echo "<a href='".$datei."?seite_nr=".$i."'>".$i."</a> "; 
};

Da die Tabelle noch leer ist, kriegt man erstmal gar nichts angezeigt:

arbeitstabelle_leer

arbeitstabelle_leer

Aber das ist ja OK so und wird sich gleich ändern.

Das Gerüst für die geplanten Aktionen

Ich bastle mir ein kleines Formular mit einem Button zum Starten des Skripts, und gebe der Übersicht halber mal alle Datensätze tabellarisch aus. Das sieht so aus:

<?php
//file:skript_starten.php
header('Content-Type: text/html; charset=utf-8');
require 'connection.php';

//Quell-und Zieltabelle festlegen
$quelltabelle = "rezepte";
$zieltabelle = "arbeit_rezepte";

echo "<h1>Skript starten</h1>";
echo "<h2> Quelle: ".$quelltabelle." Ziel: ".$zieltabelle."</h2>";

//Formular mit Button für Start
echo "<form action ='#' method = 'post'>";
echo "<input type = 'submit' name = 'absenden' value = 'Skript starten'>";
echo "</form>";

if(isset($_POST['absenden'])){
    
    echo "<script type=\"text/javascript\">alert('Skript gestartet');</script>";
    
    //Hier kommt die Action hin
    
    
    }

$sql = "SELECT * FROM ".$quelltabelle."";
$rs_result = $conn->query($sql);
$row_total = $rs_result->num_rows;

echo "Datensätze gesamt: ".$row_total."<br>";

 //Ausgabe in Tabelle - nur Arbeitsversion, kann man für den Betrieb rausnehmen
echo "<table border='1' cellpadding='4'>";
echo "<tr><th>ID</th><th>Titel</th><th>Content</th></tr>";
 
 while($row = $rs_result->fetch_assoc()) {
            
            echo "<tr><td valign='top'>".$row["ID"]."</td>";
            echo "<td valign='top'>".utf8_encode($row["post_title"])."</td>";
            //HTML Source in Textarea ausgeben
            echo "<td valign='top'><textarea rows='5' cols='80'>".utf8_encode($row["post_content"])."</textarea>";
            echo "</td></tr>";
            
 } //Ende von while row = rs_result
 
 echo "</table>";
 
?>

Ich hole mir mit dem require die Connection, lege Quell-Und Zieltabelle auf Variable und baue mir den Button für das Starten des Skripts. Die Kontrollausgabe der aktuellen Quelltabelle ist nicht unbedingt notwendig, das dient nur der Übersicht.

Jetzt gehts zur Sache

Der Ablauf ist folgender: die Datensätze aus der Quelltabelle werden selektiert und die Felder auf Variablen gelegt. An den Variablen nimmt man dann die nötigen Manipulationen vor, und die fertig bearbeiteten Datenfelder werden in ein Array geschrieben. Aus dem Array füllt man dann mit Insert die Zieltabelle. Das Ergebnis kann man dann gleich mal im Q&D-Editor überprüfen. Ich mach dann gleich noch ein Beispiel, hier zuerst mal der Source:

if(isset($_POST['absenden'])){
    
    echo "<script type=\"text/javascript\">alert('Skript gestartet');</script>";
    
    //Hier kommt die Action hin
    
    $sql = "SELECT * FROM ".$quelltabelle."";
    $rs_result = $conn->query($sql);
    
      
    $i=0;
    $temp = array();
    
    while($row = $rs_result->fetch_assoc()) {
            
            $akt_id =$row["ID"];
            $title = $row["post_title"];
            $content = $row["post_content"];
            
            // Hier kann man beliebige Stringfunktionen auf die Felder anwenden
            $content = strip_tags($content);
            
            //Behandelte Strings in Array einlesen
            $temp[$i][id] = $akt_id;
            $temp[$i][titel] = $title;
            $temp[$i][content]= $content;
            $i = $i+1;
            
    }
            
 }
$len_temp = count($temp);

//Array zeilenweise in Zieltabelle wegschreiben
for ($i = 0; $i < $len_temp; $i++){    
  
          $temp_id = $temp[$i][id];
          $temp_titel = $temp[$i][titel];
          $temp_content = $temp[$i][content];
          
          //addslashes ist nötig weil sonst der Insert bei jedem Hochkomma aussteigt
          $sql_insert = "INSERT INTO ".$zieltabelle." (ID, post_title, post_content) 
          VALUES (".$temp_id." ,'".addslashes($temp_titel)."' , '".addslashes($temp_content)."')";
          
          if (mysqli_query($conn, $sql_insert)) {
                      echo "Insert erfolgreich<br>";
                      
                    }
                      
                    else {
                      echo "Fehler beim Update: " . mysqli_error($conn);
                      
                    }
} // end for

Das wars schon – das Skript braucht ein paar Sekunden, nicht ungeduldig werden. Wichtig ist, dass man die einzufügenden Felder beim Insert noch mit einem addslashes() behandelt, weil sonst bei jedem Anführungszeichen im Text das Skript aussteigt.

Ich habe oben nur eine Ersetzung ausgeführt, nämlich das:

$content = strip_tags($content);

Das führt in der Praxis evtl. zu unerwünschten Effekten, weil es wirklich gnadenlos alle HTML-Tags raushaut. Für meine Zwecke besser geeignet ist der strip_tags mit Ausnahmen:

$content = strip_tags($content,'<h2>, <h3>, <ol>, <ul>, <li>');

Das läßt meine Überschriften und die Listen drin, sieht schon besser aus.

mit_liste

mit_liste

Aber da muss jeder selber tüfteln, was für seine Zwecke am besten geeignet ist. Fröhliches googlen nach praktikablen Stringbehandlungen! Und nicht vergessen vor erneutem Starten des Skripts die Zieltabelle mit Truncate zu leeren, sonst fällt der Insert wegen doppelter IDs auf die Nase.

Nachtrag: den Truncate kann man natürlich auch ins Skript mit einbauen, einfach die Action um folgenden Code ergänzen:

//Zieltabelle leeren
    $sql_zap="TRUNCATE TABLE ".$zieltabelle."";
    if (mysqli_query($conn, $sql_zap)) {
                      echo "Truncate erfolgreich<br>";
                      
                    }
                      
                    else {
                      echo "Fehler beim Truncate: " . mysqli_error($conn);
                      
                    }

Das muss natürlich vor dem Insert rein, am Besten setzt man es ganz an den Anfang innerhalb der if(isset($_POST…)-Bedingung. Dann spart man sich das manuelle Leeren der Zieltabelle.

Quick&Dirty Editor Sourcecode

Der Editor kommt mit erstaunlich wenig Code aus, das sind gerade mal knapp 90 Zeilen. Ich gehe das hier mal der Reihe nach durch. Meine Ausgangstabelle heißt rezepte und sieht so aus:

ausgangstabelle

ausgangstabelle

Sie enthält nur die rudimentären Daten meiner Rezepte aus dem Inselfisch-Kochbuch, und zwar die ID, den post_content und den post_title.

tabelle_rezepte

tabelle_rezepte

Wir ändern nur den post_content, Titel und ID bleiben unangetastet.

Zuerst bastle ich mir natürlich die Datenbankconnection:

<?php
//file:connection.php
error_reporting(0);
$servername = "localhost";
$username = "root";
$password = "";
$dbname = "conversion";

 
// Create connection
$conn = new mysqli($servername, $username, $password, $dbname);
// Check connection
if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
} else {echo "Datenbank Connection OK<br>";}
?>

Die wird per require reingeholt, und dann kanns losgehen. Ich habe hier den Algorithmus aus der nummerischen Pagination zweckentfremdet und lasse mir pro Seite genau einen Datensatz anzeigen.

<?php
//file:seitenweise.php
header('Content-Type: text/html; charset=utf-8');
require 'connection.php';


$sql = "SELECT * FROM rezepte";
$rs_result = $conn->query($sql);
$row_total = $rs_result->num_rows;
$gesamte_anzahl = $row_total;

echo "Datensätze gesamt: ".$row_total."<br>";

/* 1 Datensatz pro Seite festlegen*/
$ergebnisse_pro_seite = 1;
$gesamt_seiten = ceil($gesamte_anzahl/$ergebnisse_pro_seite);

if (empty($_GET['seite_nr'])) {
    $seite = 1;
} else {
    $seite = $_GET['seite_nr'];
    if ($seite > $gesamt_seiten) {
        $seite = 1;
    }
}

$limit = ($seite*$ergebnisse_pro_seite)-$ergebnisse_pro_seite;
if($limit<0){$limit =0;}

Zuerst zähle ich die Datensätze, dann lege ich fest, dass pro Seite genau ein Datensatz angezeigt werden soll. Defaultmässig landet der Editor bei Seite 1. Dann berechne ich mir das Limit, also beim wievielten Datensatz die Ausgabe beginnen soll. Das verwenden wir gleich mal für die Navigationsbuttons „Voriger Datensatz“ und „Nächster Datensatz“:

//Debug-Ausgaben und Nav-Buttons
echo "Datensatz = ".($limit+1)." von ".$row_total."<br>";
echo "Datensätze pro Seite = ".$ergebnisse_pro_seite."<br>";
if($limit>0){echo '<a href="seitenweise.php?seite_nr='.($limit-1).'" style="font-weight: bold;"><button type="button">Voriger Datensatz</button></a>';}
echo '<a href="seitenweise.php?seite_nr='.($limit+2).'" style="font-weight: bold;"><button type="button">Nächster Datensatz</button></a><br>';

Dann hole ich mir auch schon den Datensatz mit der aktuellen Seitennummer:

//Selektiere Datensatz mit der aktuellen Seitennummer
$sql = ('SELECT * FROM rezepte LIMIT '.$limit.', '.$ergebnisse_pro_seite);
$result = $conn->query($sql);

Da meine Variable $ergebnisse_pro_seite den Wert 1 hat, wird ein Datensatz pro Seite ausgegeben. Den zeige ich mir in einer while-Schleife in einer zweispaltigen Tabelle an. Die Tabelle enthält eine textarea, in die kommt der HTML-Code. In der zweiten Spalte wird die Vorschau ausgegeben. Zuerst schubse ich mal die ID des aktuellen Datensatzes in eine Variable, die brauchen wir später für den Update.

Dann frage ich ab, ob der „Änderungen speichern“-Button gedrückt wurde. Wenn ja, zeige ich den geänderten Inhalt des Editorfensters in der Tabelle an, wenn nein einfach den Originalcontent so wie er aus der Abfrage kommt.

//Ausgabe in Tabelle mit Formular
 while($row = $result->fetch_assoc()) {
            $akt_id = $row["ID"];
            echo "Aktuelle Datensatzid: ".$akt_id."<br>";
            
            echo "<form action ='#' method = 'post'>";
            echo "<input type = 'submit' name = 'absenden' value = 'Änderungen speichern'>";
            echo "<table border='1' cellpadding='4'>";
            echo "<th>".utf8_encode($row['post_title'])." HTML</th><th>Vorschau</th></tr>";
            
            //Wenn Speichern gedrückt wurde, editierten Content aus der Textarea ausgeben 
            if(isset($_POST['absenden'])){$inhalt = $_POST['content'];}
            else {$inhalt = utf8_encode($row['post_content']);}
                        
            echo "<tr>";
            //HTML Source in Textarea ausgeben
            echo "<td valign='top'><textarea rows='50' cols='80' name='content' id='content'>".$inhalt."</textarea>";
            echo "</td>";
            
            //HTML Vorschau ausgeben
            echo "<td valign='top'>".$inhalt."</td>";
            echo "</tr>";
            echo "</table>";
            echo "</form>";

Jetzt fehlt nur noch der Update, der wird beim Klicken des „Änderungen speichern“-Buttons ausgelöst:

if (isset($_POST['absenden'])){
                            
            //***********Action
            
            $sql = "UPDATE rezepte SET post_content =
'".utf8_decode(addslashes($_POST['content']))."' WHERE ID=".$akt_id."";
            //*************End Action
            
           if (mysqli_query($conn, $sql)) {
              echo "Record updated successfully";
              echo "<script type=\"text/javascript\">alert('Änderungen erfolgreich gespeichert');</script>";
            }
              
            else {
              echo "Error updating record: " . mysqli_error($conn);
              echo "<script type=\"text/javascript\">alert('Fehler beim Speichern');</script>";
            }    
    
    } //Ende von isset absenden
 } //Ende von while row = result

Im Update überschreibe ich den Inhalt von post_content mit dem Inhalt des Editorfensters, die steckt ja in der Variablen:

$_POST['content']

und zwar im Datensatz mit der aktuellen ID. Wegen der deutschen Umlaute muss ich hier mit utf8- encode und -decode arbeiten, das muß man bei einer anderen Datenbankkollation evtl anpassen. Ausserdem brauche ich noch einen addslashes(), weil meine Texte Anführungszeichen enthalten können. Dann mache ich noch eine kleine Fehlerbehandlung und gebe einen Javascript-Alert aus, ob der Update funktioniert hat oder nicht. Das wars auch schon – jetzt fehlt nur noch die nummerische Pagination zum anklicken des i-ten Datensatzes:

 //Nav Links alle Seiten
 for ($i=1; $i<=$row_total; $i++) { 
    echo "<a href='seitenweise.php?seite_nr=".$i."'>".$i."</a> "; 
};

Bitteschön, fertig ist der Q&D-Editor!

vanillekipferl_im_editor

vanillekipferl_im_editor

Tipp, auch quick&dirty:

Man kann ganz schnell eine (primitive) Filterfunktion basteln, wenn man die beiden SQL-Strings um eine „LIKE“-Klausel ergänzt:

$sql = "SELECT * FROM rezepte WHERE post_content LIKE '%".$filter."%'";
...
//Selektiere Datensatz mit der aktuellen Seitennummer
$sql = ('SELECT * FROM rezepte WHERE post_content LIKE "%'.$filter.'%" LIMIT '.$limit.', '.$ergebnisse_pro_seite.'');

Die Variable $filter setzt man am Anfang des Skripts auf den gewünschten Wert, zum Beispiel auf „caption“ oder „<ul>“ oder was auch immer. Dabei muss man aber berücksichtigen, was die aktuelle Datenbankkollation als Suchkriterien akzeptiert, bei Umlauten und Sonderzeichen kommt man da sonst zu unerwarteten Ergebnissen. Aber so als kurze Arbeitshilfe ist das schon mal ganz nützlich.

Quick&Dirty Editor für HTML-Posts mit Vorschaufunktion

Wer viel mit CMS umgeht, kennt das zur Genüge: in der Datenbank landet im Feld für den Inhalt eines beliebigen Posts oder einer Seite Text mit jeder Menge HTML-Tags, schließlich arbeitet man ja mit dem TinyMCE oder einem seiner Verwandten.Das sieht zum Beispiel in WordPress so aus:

<h2>Einleitung</h2>
In München-Haidhausen gibt es einen wunderbaren Laden, den "Liquid", da kann man erlesene Alkoholika auch in kleinen Mengen (ab 0,1 l) kaufen. Daher gibt es bei mir die Scaloppine mal mit Marsala, mal mit Sherry, oder ich bereite sie mit Portwein zu, ganz nach Lust und Laune. Wichtig ist, daß der Wein eher von der lieblichen Sorte sein soll, nur dann bekommt die Sauce den schönen vollmundigen Geschmack.
<h2>Zutaten</h2>
Für 4 Personen:

4 Kalbsschnitzel oder 8 dünn geschnittene Minutensteaks, alle Fettränder und Häutchen sorgfältig abgeschnitten, Mehl zum wenden. Je 1 El Butter und gutes Olivenöl, Salz, Pfeffer.
Zum Ablöschen: 1 kleines Glas lieblicher Südwein.
Noch 1 El. eiskalte Butter.
<h2>Zubereitung</h2>
Siehe <a href="http://evileu.de/inselfisch-kochbuch/2017/01/12/schnitzel-mit-zitrone-scaloppine-al-limone/">Scaloppine Grundrezept</a>, zum Ablöschen ein kleines Glas Südwein (s.oben) nehmen. Man kann die Scaloppine auch noch mit etwas feingehackter Petersilie bestreuen, aber das muß nicht unbedingt sein.

Das ist der post_content eines Rezeptes für Scaloppine. Im Inselfisch-Kochbuch sieht das so aus:

scaloppine

scaloppine

Recht und schön, aber im richtigen Leben kommt es immer wieder mal vor, dass man Content aus einem CMS abzieht und in einem anderen CMS weiterverwenden möchte. Das geht in den seltensten Fällen ohne umfangreiche Nacharbeiten, und nicht alles kann man programmgesteuert erledigen.

Man kann mit PHP natürlich alle HTML-Tags entfernen (siehe strip_tags Doku) und dabei sogar Ausnahmen definieren, welche Tags drinbleiben dürfen, und man kann sich auch mit preg_replace mehr oder weniger geniale Ersetzungen einfallen lassen. Aber das hat auch so seine Nachteile.

In dem Scaloppine-HTML ist zum Beipiel ein Link drin, der auf das Inselfisch-Kochbuch auf evileu.de verweist, das kann natürlich nicht so bleiben. Schmeißt man alle Links programmatisch komplett raus, ergibt der Text keinen Sinn mehr, da fehlt dann für das ganze Rezept die Zubereitung. Hier müßte man, wenn man es ganz richtig machen will, dem Link nachgehen und den entsprechenden Zubereitungstext rauskopieren. Das ist natürlich aufwendig, aber im Zweifelsfall die einzig richtige Lösung.

Tscha, was macht man jetzt, wenn man vor ein paar Hundert Posts sitzt, die manuell überarbeitet werden müssen? Man überlegt sich eine Arbeitserleichterung.

Voraussetzungen

Ich hab mir mal aus der WordPress posts-Tabelle nur die minimalsten Rohdaten abgezogen, nämlich die ID, den Titel und den Content, gefiltert nach post_type = post und post_status=publish, das sind die Kerndaten aller veröffentlichten Kochrezepte. Wenn ich sonst noch was brauchen sollte, den Autor oder das Datum oder sogar die Kategorien oder sonstwas, das kann ich mir später über die ID wieder dazulinken. Das selbe könnte ich auch mit Joomla-Beiträgen oder Drupal-Nodes machen, Hauptsache die ID kommt mit und der Titel und Content passen. Und das Ganze ist eigentlich nur sinnvoll, wenn die Posts nicht allzu lang sind, aber meine Kochrezepte gehen selten über eine Bildschirmseite hinaus.

Wir basteln uns einen Quick&Dirty Editor

Damit man jetzt nicht immer direkt in der Datenbank rumpfuschen muss, wenn man in einem HTML-Post etwas ändern will, habe ich mir eine zwar nur als Arbeitspferd geeignete, aber durchaus zeitsparende Lösung überlegt. Ich habe bei meinen ganzen Snippets zur Pagination gekupfert und mir ein PHP-Script gebaut, in dem pro Seite ein Datensatz dargestellt wird. Es gibt eine rudimentäre Navigation (voriger/nächster Datensatz), das reicht mir für die Arbeit, schließlich gucke ich im Zweifelsfall einen Datensatz nach dem anderen durch. Dann habe ich mit Hilfe einer Tabelle zwei Bildschirmbereiche konstruiert, im ersten wird der HTML-Code dargestellt und kann editiert werden, im zweiten gibt es eine Vorschau des Ergebnisses. Noch ein Knöpfchen zum Speichern der Änderungen, und es kann losgehen.

qe_scaloppine

qe_scaloppine

Für den Fall dass ich zu einem bestimmten Datensatz springen möchte habe ich ganz unten auch noch eine nummerische Navigation eingebaut, das ist bei über 300 Datensätzen ganz nützlich.

seitenpagination

Seitenpagination

Wie man mit dem Editor arbeitet

Wenn man in einem Post etwas entdeckt hat, das man ändern möchte, kann man dies direkt im HTML-Fenster tun. Hier habe ich zum Beispiel ein Bild drin, das wird WordPress-typisch in einen caption-Shortcode eingeklemmt, der sorgt in WordPress für die korrekte Darstellung der Bildbeschriftung.

caption

caption

Ein anderes CMS kennt aber den Shortcode nicht, also muss er raus. Das sieht man auch an der Darstellung im Vorschau-Fenster, der Browser kann natürlich ohne WordPress mit dem Shortcode nix anfangen und stellt ihn als Text dar:

caption_vorschau

caption_vorschau

Um das zu korrigieren, schmeisse ich im HTML-Fenster die caption-Tags raus und platziere noch ein paar <br> an den geeigneten Stellen, mache gleichzeitig noch einige kleine Textkorrekturen:

ohne_caption

ohne_caption

Klick auf den Button „Änderungen speichern“, dann kommt erst mal eine kurze Meldung, ob der Update erfolgreich war:

meldung

meldung

Und dann kriegt man das Ergebnis im Vorschaufenster angezeigt:

vorschau

vorschau

Na bitte, das geht doch ratzfatz!

Ohne Netz und doppelten Boden

Da ich beim Klicken auf den „Änderungen speichern“-Button direkt einen Update auf die Datenbank fahre, sind die Änderungen natürlich nicht mehr rückgängig zu machen, aber das kann ich verschmerzen. Schlimmstenfalls muss ich mir einen Datensatz aus der Originaltabelle wieder herholen, wenn ich ihn total verhunzt habe, aber das ist mir eigentlich noch nicht passiert. Im richtigen Leben wird sowas eh der Praktikant machen, und der kann HTML und weiß was er tut, wenn er Tags rausschmeisst oder korrigiert.

Der Q&D-Editor ist ja auch nur als Arbeitshilfe für Entwickler gedacht, und nicht für den Endanwender. Er soll auch nicht den gezielten Einsatz von programmatischen preg_replace-Aktionen ersetzen, die haben ja durchaus auch einen Sinn. Mir gehts immer so, dass ich beim Arbeiten mit dem Editor meistens ganz schnell merke, welche Elemente programmgesteuert rausgeschmissen werden können, weil ich immer die selben Tags editiere – und dann ist natürlich ein bisschen zusätzliches PHP angesagt. In der Praxis wird man immer eine Kombination aus manuellem Editieren und programmgesteuerten Ersetzungen fahren, damit bin ich eigentlich bis jetzt recht gut klargekommen.

Und jetzt gibts den Sourcecode des Q&D-Editors, in einem neuen Beitrag.

 

Joomla Modul für alfabetische Pagination

Weil es doch ein bißchen anders geht als in WordPress, hab ich hier nochmal die alfabetische Pagination von Beiträgen aufgegriffen und in ein Joomla-Modul gepackt. Nur mal kurz zur Erinnerung, das Ganze soll so aussehen:

alfabetisch

alfabetisch

Eigentlich selbsterklärend… wenn man auf einen Buchstaben klickt, werden alle Beiträge mit diesem Anfangsbuchstaben aufgelistet, natürlich als Links. Interessant ist hier, dass der Joomla-Datenbanktreiber die Sortierung aus dem „order by title“ ohne weitere Kunstgriffe auch mit deutschen Umlauten und Accents richtig macht, in WordPress musste man da etwas tricksen. Aber das nur als Randnotiz.

Da in Joomla die Beitragskategorien so elementar wichtig sind, habe ich ins Modul eine Auswahl der Kategorie mit aufgenommen. Hier wurde die Kategorie „Rezepte“ gewählt, es geht aber auch jede andere Kategorie, da kommt das Standardformularfeld vom Type „category“ gerade recht.

Und hiermit fangen wir auch an, die XML-Datei für unser Modul sieht so aus:

<?xml version="1.0" encoding="utf-8"?>
<extension type="module" version="3.1.0" client="site" method="upgrade">
    <name>Alfapage Modul</name>
    <author>Evi Leu</author>
    <version>1.0.0</version>
    <description>Alfabetische Pagination</description>
    <files>
        <filename>mod_alfapage.xml</filename>
        <filename module="mod_alfapage">mod_alfapage.php</filename>
        <filename>index.html</filename>
        <filename>helper.php</filename>
        <filename>tmpl/default.php</filename>
        <filename>tmpl/index.html</filename>
    </files>
    <config>
    <fields name="params">
        <fieldset name="basic">
            <field name="mycategory" type="category" extension="com_content" label="Kategorie auswählen" description="" />
        </fieldset>
    </fields>
    </config>
</extension>

Die XML-Defi enthält genau ein Feld für Benutzereingaben (grün markiert), hier kann man aus den vorhandenen Kategorien eine auswählen.

alfapage_modul

alfapage_modul

Das Modul habe ich mod_alfapage genannt, bitte Dateinamen entsprechend anpassen nicht vergessen.

Dann gehts auch schon zur Sache. Die mod_alfapage.php sieht so aus:

<?php
/**
 * Basic Module Entry Point
 */

// No direct access
defined('_JEXEC') or die;
// Include the syndicate functions only once
require_once dirname(__FILE__) . '/helper.php';

//Werte aus dem Fieldset abholen
$kategorie = $params->get('mycategory');


$data = new stdClass();
//Objekt füllen
$data->kategorie = $kategorie;

        
$basic_ausgabe = modBasicHelper::getAusgabe($data);
require JModuleHelper::getLayoutPath('mod_alfapage');

Hier wird genau ein Parameter an die Helper-Klasse übergeben, nämlich die ID der gewählten Kategorie aus dem Dropdown-Feld im Modul. Nicht vergessen: in der default.php den Variablennamen anpassen!

<?php 
// No direct access
defined('_JEXEC') or die; ?>
<?php echo $basic_ausgabe; ?>

Weiter geht es in der helper.php, hier kann man einiges vom WordPress-Plugincode übernehmen, aber eben nicht alles. Erster Stolperstein: ich möchte den Namen (nicht die numerische ID) der gewählten Kategorie ausgeben. Dafür behelfe ich mir so:

<?php
/**
 * Helper class für Basic Modul
 
 */
class ModBasicHelper
{
    /*
     * @param   variable  $params containing the module parameter
     *
     * @access public          */    
          
          
    
    public static function getAusgabe($params)
    {
        $akt_kat = $params->kategorie;
        
        //Kategorietitel holen
        $db = JFactory::getDbo();
        //get current category id
        $id = $akt_kat; 
        //match id to cat.id from _categories set to variable $id
        $db->setQuery("SELECT cat.title FROM #__categories cat WHERE cat.id='$id'"); 
        $category_title = $db->loadResult();
        
        echo "<h2>Alfabetisches Verzeichnis der Kategorie ".$category_title."</h2>";

Ich hab keine schönere Methode gefunden, also gehe ich mit dem Select auf die categories-Tabelle und hole mir den Kategorienamen zur übergebenen ID.

Dann konstruiere ich mir genau wie in WordPress Submit-Buttons von a-z und frage mit einem if isset() ab, welcher denn geklickt wurde.

//***************************************
        //Buchstaben a-z in Array schreiben
            $letters = array();
            for ($i = 'a', $j = 1; $j <= 26; $i++, $j++) {
                $letters[$j] = $i;    
            }
            //Formular mit Buttons
            echo "<form action = '#' method = 'post'>";
            
            for ($i=1; $i <=26; $i++){
                echo "<input type='submit' name='".$letters[$i]."' value='".$letters[$i]."'>";
            }
                echo "</form>";
               
        for ($j = 1; $j <= 26; $j++){    
        if (isset($_POST[''.$letters[$j].''])){
            
            return el_aufruf("".$letters[$j]."",$akt_kat);
            
        }

Der angeklickte Buchstabe und die aktuelle Kategorie-ID werden an eine Hilfsfunktion zur Ausgabe übergeben, die steht in der helper.php nach der Klassendefinition (Wenn man sie innerhalb der Klassendefinition mit reinpackt, Aufruf mit modBasicHelper::el_aufruf()).

Hier hole ich mir zunächst aus der Tabelle #__content alle Einträge, die mit dem übergebenen Buchstaben anfangen. Ausserdem filtere ich nach state = 1 (published) und der übergebenen Kategorie-ID:

function el_aufruf($stabe, $kat){
    $db =JFactory::getDBO();
    $suchstabe = $stabe."%";
    $query = "SELECT * FROM #__content where title like '".$suchstabe."' and state = 1 and catid = ".$kat." order by title";
    $db->setQuery($query);
    $db->execute();
    $gefunden = $db->getNumRows();
    $results = $db->loadObjectList();   
     
        
        echo "<h3>".$gefunden." Beiträge zum Buchstaben ".strtoupper($stabe)."</h3>";

Durch die auf der Variable $results liegende Objektliste mit dem Abfrageergebnis steppen wir nun wie gewohnt mit einem foreach durch und geben alle Titel der gefundenen Beiträge aus. Um auch einen Link zum Beitrag konstruieren zu können, muss man in Joomla ein bißchen umständlich operieren, das geht so:

foreach ($results as $einpost){
            
            $article = JControllerLegacy::getInstance('Content')
            ->getModel('Article')->getItem($einpost->id);

            $url =  JRoute::_(ContentHelperRoute::getArticleRoute($einpost->id, 
                      $article->catid, 
                      $article->language));
            
            echo "<a href = '".$url."'>".$einpost->title."</a><br>";
            
        }
    
} //end function el_aufruf

Damit wird eine Linkliste zu den gefundenen Beiträgen ausgegeben. Das wars – alfabetische Pagination in Joomla für Beiträge einer frei wählbaren Kategorie.

buchstabe_k

buchstabe_k

Pedanten werden anmerken, das die Sortierung nicht ganz Telefonbuch-konform ist, die Umlaute werden nicht nach ihren Diphtongen einsortiert (ä als ae), sondern nach ihrem Basis-Buchstaben (ä als a). Aber damit kann ich leben, da mach ich jetzt nix.

Wo Joomla die Benutzereingaben von Modulen speichert

Ich hab gerade in einem kleinen Projekt ein Modul mit mehrfachen Benutzereingaben gebraucht, und dafür kamen mir die Standard Form Field Types von Joomla gerade recht. Wer dazu eine kurze Auffrischung braucht, in diesem Artikel habe ich mich näher über Benutzereingaben in Joomla-Modulen ausgelassen.

Ich brauchte ein Textfeld, eine Zahl, eine Dropdown-Liste und drei Radiobuttons sowie eine Textarea, und das sieht in der XML-Datei des Moduls so aus:

<config>
    <fields name="params">
        <fieldset name="basic">
            <field name="param1" type="text" default="" label="Bitte Text eingeben" description="Auszugebender Text"></field>
            <field name="param2" type="number" default="" label="Bitte Zahl eingeben" description="Auszugebende Zahl"></field>
            <field name="meineoption" type="list" default="" label="Eine Option wählen" description="">
                <option value="1">Option 1</option>
                <option value="2">Option 2</option>
            </field>
            <field name="meinradio" type="radio" default="" label="Bitte auswählen" description="" class="btn-group">
                <option value="Ja">Ja</option>
                <option value="Nein">Nein</option>
                <option value="Weissnicht">Weissnicht</option>
            </field>
            <field name="meinetextarea" type="textarea" default="" label="Längeren Text eingeben" description="" rows="10" cols="5" filter="raw"/>
        </fieldset>
    </fields>
    </config>

Das erzeugt die folgenden Eingabemöglichkeiten im Modul:

basicmodul

basicmodul

Erhob sich bei mir ganz schnell die Frage: wo werden denn diese Benutzereingaben gespeichert?

Kurzes Googlen ergab des Rätsels Lösung. In der Tabelle #__modules wird für jedes neu erzeugte Modul ein eigener Eintrag angelegt, man suche im Feld title nach dem Titel des Moduls, bei mir heißt es „Basic“. (Wenn man ganz sichergehen will, guckt man sich auch noch das Feld module an, da sollte der Name des betreffenden Moduls drinstehen, bei mir ist das mod_basic)

In diesem Datensatz findet sich im Feld params ein längerer String, der sämtliche Benutzereingaben enthält, der sieht etwa so aus:

{„param1“:“Hallo ich bin dein Basic-Modul „,“param2″:“199″,“meineoption“:“2″,“meinradio“:“Weissnicht“,“meinetextarea“:“<h1>Lorem ipsum<\/h1> w\u00e4r jetzt praktisch mal schauen was der mit dem Umbruch macht“,“module_tag“:“div“,“bootstrap_size“:“0″,“header_tag“:“h3″,“header_class“:““,“style“:“0″}

Aber, das ist doch… ja klar! JSON ist das, und dem kommen wir ganz einfach bei. Ich möchte mal Beispielhaft den param2 (die Zahl) ausgeben, in Joomla klappt das so:

$db =JFactory::getDBO();
$query = 'SELECT params FROM #__modules WHERE title="Basic"';
$db->setQuery($query);
$result = $db->loadResult();
$meinarray = json_decode($result);
echo "Hier kommt param2: ".$meinarray->param2;

Dabei entspricht der Name der Array-Position dem Namen des Eingabefeldes in der XML-Definition, so einfach ist das. Ist vielleicht ganz nützlich zu wissen, falls man diese Werte mal an anderer Stelle brauchen sollte.

Sag niemals nie: Widget für die X neuesten Beiträge eigener Post Types

Ich hab ja gesagt, mir ist das zu kompliziert, aber jetzt hab ich mir doch ein Widget geschrieben, das die X neuesten Beiträge eines wählbaren Post Types anzeigt. Man kann dem Widget einen Titel geben, man kann eingeben wieviele Beiträge angezeigt werden sollen, man kann auch noch anwählen ob das Datum mit angezeigt werden soll oder nicht. Aussehen tut das Ganze so:

post_type_widget

post_type_widget

Und die Ausgabe in der Sidebar sieht zum Beispiel so aus:

widget_posts

widget_posts

Hier hab ich als Post Type „post“ angegeben (das sind meine Rezepte), das Limit auf 5 gesetzt und Datum ausgeben angekreuzt.

Das Widget wird ganz normal im Plugins-Verzeichnis angelegt, der Header mit der Klassendeklaration sieht so aus:

/*
Plugin Name: Post Type Widget
Plugin URI: http://evileu.de/wordpress
Description: Zeigt die neuesten X Beiträge des gewählten Post Types an
Author: Evi Leu
Version: 1.0
Author URI: http://evileu.de
*/


class PostTypeWidget extends WP_Widget
{
  function PostTypeWidget()
  {
    $widget_ops = array('classname' => 'PostTypeWidget', 'description' => 'Zeigt die neuesten X Beiträge des gewählten Post Types an' );
    $this->WP_Widget('PostTypeWidget', 'Post Type', $widget_ops);
  }

Interessant wird es in der Formulardefinition, da habe ich mir ein Dropdownfeld mit meinen vorhandenen Post Types gebastelt, ich picke das mal heraus und markiere die anzupassenden Stellen rot:

<p>
    <label for="<?php echo $this->get_field_id( 'posttype' ); ?>"><?php _e( 'Select Post Type', 'textdomain' ); ?>:</label>
    <p>Post Type auswählen
   <select id="<?php echo $this->get_field_id('posttype'); ?>" name="<?php echo $this->get_field_name('posttype'); ?>" class="widefat" style="width:100%;">
    <option <?php selected( $instance['posttype'], 'post'); ?> value="post">Post</option>
    <option <?php selected( $instance['posttype'], 'kochbuch'); ?> value="kochbuch">Kochbuch</option>   
    <option <?php selected( $instance['posttype'], 'projects'); ?> value="projects">Projects</option> 
    </select>
   </p>

Man hätte auch die vorhandenen Post Types aus der Datenbank fischen können, aber das war mit zu viel Heckmeck, da müsste man ja noch die WordPress-eigenen Types und evtl. auch die von irgendwelchen Plugins angelegten herausfiltern, ich fand es so herum entschieden einfacher. Wer mehr Post Types hat, ergänzt einfach den Select entsprechend.

Nachtrag: alle Post Types aus der wp_posts anzeigen

Jetzt hab ichs doch noch ausprobiert, wer sich alle vorhandenen Post Types aus der wp_posts im Dropdownfeld anzeigen lassen will, kann für den Select folgende Konstruktion verwenden:

 <p>Post Type auswählen
   <select id="<?php echo $this->get_field_id('posttype'); ?>" name="<?php echo $this->get_field_name('posttype'); ?>" class="widefat" style="width:100%;">
    <?php
    global $wpdb;
    $alleposts = $wpdb->get_results( "SELECT DISTINCT post_type 
                                      from ".$wpdb->prefix."posts");
    
    foreach($alleposts as $einpost){      
      echo "<option ".selected( $instance['posttype'], $einpost->post_type)." value=".$einpost->post_type.">".$einpost->post_type."</option>";
    }
    ?>
    
    </select>
   </p>

Da kriegt man halt auch jeden Schrott angezeigt, aber es funktioniert:

alle_post_types

alle_post_types

Bleibt jedem selber überlassen, ob er diese Option verwenden will oder den statischen Select von oben. Mir ist die statische Dropdownliste lieber, weil sie den Benutzer nicht in Versuchung bringt hier eine sinnfreie Option wie z.B revisions anzuwählen. Natürlich könnte man den SQL entsprechend aufblasen und noch einige „WHERE post_type NOT LIKE ‚xyz'“ dranflicken, aber das ist mir entschieden zu umständlich.

Die Checkbox für das Datum

Für die Checkbox zum Datum anzeigen habe ich folgende Konstruktion verwendet:

<p>
    <input class="checkbox" type="checkbox" <?php checked( $instance[ 'your_checkbox_var' ], 'on' ); ?> id="<?php echo $this->get_field_id( 'your_checkbox_var' ); ?>" name="<?php echo $this->get_field_name( 'your_checkbox_var' ); ?>" /> 
    <label for="<?php echo $this->get_field_id( 'your_checkbox_var' ); ?>">Datum anzeigen</label>
</p>

Die ganze Funktion form($instance) sieht so aus:

function form($instance)
  {
    $instance = wp_parse_args( (array) $instance, array( 'title' => '' ) );
    $title = $instance['title'];
    
    $instance = wp_parse_args( (array) $instance, array( 'posttype' => '' ) );
    $posttype = $instance['posttype'];
    
    $instance = wp_parse_args( (array) $instance, array( 'anzahl' => '' ) );
    $anzahl = $instance['anzahl'];
    
    $instance = wp_parse_args( (array) $instance, array( 'your_checkbox_var' => '' ) );
    $your_checkbox_var = $instance['your_checkbox_var'];
    
    
    
?>
  <p><label for="<?php echo $this->get_field_id('title'); ?>">Titel: <input class="widefat" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" type="text" value="<?php echo attribute_escape($title); ?>" /></label></p>
  
   <p>
    <label for="<?php echo $this->get_field_id( 'posttype' ); ?>"><?php _e( 'Select Post Type', 'textdomain' ); ?>:</label>
    <p>Post Type auswählen
   <select id="<?php echo $this->get_field_id('posttype'); ?>" name="<?php echo $this->get_field_name('posttype'); ?>" class="widefat" style="width:100%;">
    <option <?php selected( $instance['posttype'], 'post'); ?> value="post">Post</option>
    <option <?php selected( $instance['posttype'], 'kochbuch'); ?> value="kochbuch">Kochbuch</option>   
    <option <?php selected( $instance['posttype'], 'projects'); ?> value="projects">Projects</option> 
    </select>
   </p>
  <p><label for="<?php echo $this->get_field_id('anzahl'); ?>">Anzahl: <input id="<?php echo $this->get_field_id('anzahl'); ?>" 
name="<?php echo $this->get_field_name('anzahl'); ?>" type="number" value="<?php echo attribute_escape($anzahl); ?>" /></label></p>
<p>
    <input class="checkbox" type="checkbox" <?php checked( $instance[ 'your_checkbox_var' ], 'on' ); ?> id="<?php echo $this->get_field_id( 'your_checkbox_var' ); ?>" name="<?php echo $this->get_field_name( 'your_checkbox_var' ); ?>" /> 
    <label for="<?php echo $this->get_field_id( 'your_checkbox_var' ); ?>">Datum anzeigen</label>
</p>
  
  
  
<?php
  }

Dann noch die Funktion update() anpassen:

function update($new_instance, $old_instance)
  {
    $instance = $old_instance;
    $instance['title'] = $new_instance['title'];
    $instance['posttype'] = $new_instance['posttype'];
    $instance['anzahl'] = $new_instance['anzahl'];
    $instance[ 'your_checkbox_var' ] = $new_instance[ 'your_checkbox_var' ];
    return $instance;
  }

Und schliesslich in der Funktion widget() die Variablen abholen:

function widget($args, $instance)
  {
    extract($args, EXTR_SKIP);
 
    echo $before_widget;
    $your_checkbox_var = $instance[ 'your_checkbox_var' ] ? 'true' : 'false';
    $title = empty($instance['title']) ? ' ' : apply_filters('widget_title', $instance['title']);
    $posttype    = empty( $instance['posttype'] ) ? '' : esc_attr( $instance['posttype'] );
    $anzahl    = empty( $instance['anzahl'] ) ? '' : esc_attr( $instance['anzahl'] );

Der eigentliche OpCode des Widgets ist nicht weiter kompliziert, die Hauptsache ist die SQL-Query, in die ich die Variablen für den Post Type und für die Anzahl einsetze:

global $wpdb;
    $alleposts = $wpdb->get_results( "SELECT * from ".$wpdb->prefix."posts 
where post_type like '".$posttype."' 
and post_status like 'publish' 
ORDER BY post_date DESC
 LIMIT ".$anzahl."");
    

Das ganze wird noch nach dem post_status=publish gefiltert und absteigend nach Datum sortiert und kann jetzt in einem foreach ausgegeben werden. Dabei baue ich noch die Abfrage ein, ob das Datum mit ausgegeben werden soll:

foreach($alleposts as $einpost){
        
        $dt = new DateTime($einpost->post_date);
        echo $einpost->post_title." ";
        if ($your_checkbox_var == 'true'){ echo $dt->format('d.m.Y');}
        echo "<br>";
    }

Hier könnte man wie schon öfter gehabt mit dem get_permalink() über die ID gleich noch den Link zum entsprechenden Beitrag mit einbauen, das spare ich mir jetzt. Wem das jetzt zu schnell ging, den ganzen Code des Widgets gibt es hier gezippt: post-type-widget

Viel Spaß beim Nachbauen!