Alternative zum Besucherzähler: der Rezeptecounter

Tscha, der nette kleine Visitor Counter auf meinen Webseiten musste leider weg, weil der die IP-Adressen der Besucher bunkert, und das ist nach der neuen Datenschutz-Verordnung nicht mehr erlaubt, IP-Adressen zählen als schutzwürdige persönliche Daten und dürfen nicht mehr ungecrypted gespeichert werden. Mich interessiert aber trotzdem, was die Besucher auf meinen Seiten anzieht, und ich hab mir fürs Inselfisch-Kochbuch eine „kleine“ Alternative gebastelt.

Rezeptecounter ganz minimalistisch

Eigentlich ist es für mich am Interessantesten, welche Rezepte am öftesten aufgerufen werden, die Aufrufe der wenigen statischen Seiten interessieren mich eher weniger. Das könnte ich mir zwar aus den Logfiles meines Providers herausfieseln, aber der Aufwand ist doch relativ hoch. Das geht auch einfacher, dachte ich mir, da klemme ich eine kleine PHP-Funktion ans Ende jedes Rezeptes und schreibe mir die Aufrufe in eine eigene Tabelle. Damit habe ich eine prima kleine Rezepte-Hitparade und kann z.B. die 10 beliebtesten Rezepte ausgeben oder sonstwas damit anstellen.

Damit kann ich in der single.php ansetzen und mir da einen kleinen Zähler einbauen, der mir in die Datenbank bunkert wie oft das jeweilige Rezept schon aufgerufen wurde. Die Funktion kommt in einen Shortcode, und der wiederum kommt in ein Plugin, der Plugin-Aufruf kommt ins Child-Theme in die single.php nach dem Content.

Die Tabelle counter

ist ganz einfach strukturiert:

tabelle_counter

tabelle_counter

Eine Autowert-ID, und drei Felder für Rezept-ID, Titel und der Zähler für die Aufrufe, das wars schon.

Die Funktionalität des Shortcodes

… ist auch nicht weiter kompliziert. Ich hole mir ID und Titel des aktuellen Rezeptes ab und gehe mit der ID auf die Tabelle counter. Falls die ID da noch nicht vorhanden ist, mache ich einen Insert, falls sie schon da ist, setze ich den Zähler um eins hoch und mache einen Update. Das wars schon!

function el_rezeptcounter() {
    
  if(is_single()) {
      
      //id und titel abholen
      global $wpdb;
      $akt_id = get_the_id();
      $akt_titel = get_the_title();
      
      //Bisherige Anzahl holen
        $alleposts = $wpdb->get_results( "SELECT * from counter where rezeptid = ".$akt_id."");
        $anzahl = $wpdb->num_rows;
        
        $zaehler = 0;
        foreach($alleposts as $einpost){
            $zaehler = $einpost->zaehler;
        }
        
        echo "Dieses Rezept wurde bisher ".$zaehler." mal aufgerufen";
        
        if($anzahl == 0){
            
            //Neuen Aufruf eintragen
            $wpdb->insert('counter', array(
            'rezeptid' => $akt_id,
            //Sonderbehandlung für - und " usw.
            'titel' => html_entity_decode($akt_titel),
            'zaehler' => 1
            ));
            
        }else{
            
            //Zähler updaten
            $zaehler = $zaehler +1;
            
            $wpdb->update( 
                'counter', 
                array( 
                    'zaehler' => $zaehler  // int
                  ), 
                array( 'rezeptid' => $akt_id )
            );
                    
        }
        
  } //End von if( is_single)
  
}
add_shortcode ('counter', 'el_rezeptcounter');

Dabei kann man sich überlegen, ob man bei der Ausgabe der bereits erfolgten Aufrufe bei 0 oder bei 1 das zählen anfängt, das ist Geschmackssache. Korrekter ist es wahrscheinlich, mit dem aktuell erfolgten Aufruf bei 1 anzufangen, Dann sähe die Zeile mit dem Zähler für die Ausgabe so aus:

if ($o_zaehler == ''){$zahl = 1;}else{$zahl = $o_zaehler+1;}

Den html_entity_decode($akt_titel) brauchts, weil ich in meinen Titeln auch mal Zeichen wie – oder “ verwende, damit das in der Datenbank sauber ankommt.

Der Aufruf des Shortcodes in der single.php

… wird nach Wunsch vor oder nach dem Content platziert:

       <!--Shortcode für den Rezepte-Counter-->
       <?php echo do_shortcode("[counter]"); ?>

Und so sieht die Ausgabe aus:

4malaufgerufen

4malaufgerufen

Update und Tipp:

Man kann die Counter-Funktionalität statt in einen Shortcode auch in einen Filter packen. Ich bin zwar mit komplexen Filtern in WordPress schon manchmal auf die Nase gefallen, aber hier scheint es stabil zu funktionieren. Der Code bleibt gleich, nur setze ich dann den Filter auf the_content:

function el_rezeptcounter($content) {
    
  if(is_single()) {
  ...
  ...(Hier kommt der Code wie oben)
  ...
      .
  } //End von if( is_single)
  return $content;
}
add_filter( 'the_content', 'el_rezeptcounter' );

Damit kann man sich den Umbau der single.php sparen.

Die Counter-Tabelle

Die Tabelle counter ist sehr übersichtlich, man kann sie sich gleich mal nach dem Zähler sortieren und sieht sofort, welche Rezepte die meisten Aufrufe haben.

counter_mit_eintraegen

counter_mit_eintraegen

Das kann man natürlich hübsch für eine Ausgabe z.B. als Rezept-Hitparade verwenden, dafür basteln wir uns:

Den Hitparaden-Shortcode

kann man sich selber dahin platzieren wo man ihn am liebsten hat, die Konstruktion ist denkbar einfach:

function el_rezepthitparade(){
    global $wpdb;
    echo "<h2>Die beliebtesten Rezepte</h2>";
    $alleposts = $wpdb->get_results( "SELECT * FROM counter ORDER BY zaehler DESC");
    foreach ($alleposts as $einpost){
        
        echo $einpost->titel." (".$einpost->zaehler.")<br>";
    }
}
add_shortcode('hitparade','el_rezepthitparade');

Ausgabe:

diebeliebtestenzezepte

diebeliebtestenzezepte

Man kann jetzt noch die Anzahl der ausgegebenen Zeilen mit einem LIMIT steuern, aber man kann auch noch ganz was anderes machen, nämlich das Ganze in ein Widget packen und die Anzahl der auszugebenden Zeilen als Benutzereingabe abfragen. Aber das ist dann doch recht aufwendig, ich nehme hier mal lieber eine kleine Lösung:

Shortcode in Text-Widget packen

Dafür zieht man sich einfach ein Text-Widget an die passende Stelle und gibt hier nur den Shortcode ein, z.B.

Die beliebtesten Beiträge

Ein kleiner Chat in PHP – wieder mal was für Minimalisten (6520)
Kraut und Rüben auf der Datenbank: wo wooCommerce die Produktdaten speichert (4308)
Noch mehr postmeta: benutzerdefinierte Felder in wooCommerce (4266)
Kontrastfarbe automatisch berechnen mit jquery/Javascript (4120)
Joomla-Template 2: die index.php (4028)
Schmankerl für alte Datenbanker: Zugriff auf externe Daten mit dem wpdb-Objekt (3914)
Alphabetische Paginierung aus Array: die Tücken des PHP-sort() (3280)
Ein einfaches Joomla-Template erstellen (2793)
PHP 5.6 forever? (2764)
HTML5 Datalist Value – ganz so einfach ist es nicht (2749)
. Wenn das nicht funktioniert, sind die Shortcodes für Textwidgets noch nicht enabled, das geht aber mit einer Zeile im Plugin oder in der functions.php des Child-Themes:

add_filter('widget_text', 'do_shortcode');

Das sollte es gewesen sein. Wir hübschen die Ausgabe noch mit Links zu den Rezepten auf, das ist auch nicht weiter schwierig, wir haben ja die IDs und packen das mit in den foreach:

foreach ($alleposts as $einpost){
        
        $pfad = get_the_permalink($einpost->rezeptid);
        echo "<a href = '".$pfad."'>".$einpost->titel."</a> (".$einpost->zaehler.")<br>";
    }

Jetzt ist es aber schön genug!

widgetmit_links

widgetmit_links

Nachschlag für WordPress-Puristen

Ich bin darauf hingewiesen worden, dass man die Anzahlen der Rezeptaufrufe auch in der wp_options speichern könnte, das wäre die sauberere Lösung. Ja bittesehr, kann man, ist nicht besonders schwierig. Man muss sich halt überlegen, wie man die Options benennt, damit man sie nachher auch auswerten kann. Ich hab da mal einen Versuch gemacht und die Optionsnamen nach dem Muster zaehler_[beitragsid] zusammengeschraubt. D:ie Funktion zum Wegschreiben ist recht übersichtlich geworden:

function el_rezeptcounter() {
    
  if(is_single()) {
      
      //id und titel abholen
      $akt_id = get_the_id();
      $akt_titel = get_the_title();
      
      //option nachschauen
      $o_zaehler = get_option('zaehler_'.$akt_id.'');
        
        if ($o_zaehler == ''){$zahl = 0;}else{$zahl = $o_zaehler;}
        
        echo "Dieses Rezept wurde bisher ".$zahl." mal aufgerufen";
        
        if($o_zaehler == ''){
            
            update_option('zaehler_'.$akt_id, 1);
            
        }else{
            
            //Zähler updaten
            $o_zaehler = $o_zaehler +1;    
            update_option('zaehler_'.$akt_id, $o_zaehler);        
        }
        
  } //End von if( is_single)
  
}

Man kann hier ohne Gefahr update_option() verwenden, da es eine Option einfach neu anlegt, falls sie noch nicht vorhanden sein sollte. Das funktioniert soweit ganz gut und hat hier zum Testen etliche Einträge korrekt in meiner wp_options hinterlassen:

wp_options

wp_options

Eine Übersicht bekäme man im ersten Ansatz mit diesem Select heraus:

( "SELECT * from wp_options where option_name like 'zaehler_%' 
ORDER BY option_value DESC");

Was mir an dieser Lösung allerdings nicht gefällt: wie wertet man das jetzt aus und kriegt die Rezepttitel mit zu den Zählerständen? Als Optionsnamen nur die Rezept-ID allein (ohne das zaehler_ vorneweg) zu vergeben wäre ein Lösungsansatz, aber das gefällt mir nicht so recht.

Man könnte natürlich in den option_value ein Array mit ID, Zähler und Titel packen und WordPress die Daten serialisieren lassen (hier bei wpengineer.com ein nettes Tutorial zu dem Thema), aber das ist mir eigentlich zu umständlich, da hat man dann das G’frett damit, die Daten wieder einzeln rauszufieseln. Da bleibe ich lieber bei meinem „kurzen Dienstweg“ mit der eigenen Tabelle.

Update

Ich bin gefragt worden, ob ich nicht ein Beispiel für das Wegschreiben der Options als Array bringen könnte. Ja OK, machen wir, aber dazu gibts einen neuen Beitrag.

WordPress Bewertungsformular mit AJAX

Einleitung

Sie kennen wahrscheinlich mein Bewertungsformular, es steht am Ende jedes Artikels und erlaubt eine Benotung von sehr gut bis ungenügend. Bislang ist es so, dass ich eine Nachricht erhalte, wenn jemand eine Bewertung abgeschickt hat, und die Information nur für mich privat relevant ist. Jetzt dachte ich mir, das geht auch anders: erstens möchte ich am Ende jedes Artikels angezeigt bekommen, ob schon Bewertungen vorhanden sind. Und zweitens soll sich die Anzeige automatisch aktualisieren, wenn jemand eine neue Bewertung abgibt. Dazu brauchts a bisserl AJAX, und das geht mit WordPress eigentlich ganz einfach, wenn man das Prinzip einmal überrissen hat.

Formular am Ende jedes Artikels anzeigen

Dafür basteln wir uns ein Plugin, und in das kommt als Erstes mal das Formular und ein Filter, der das ganze ans Ende jedes Contents hängt. Wahlweise kann man das auch in eine Abfrage klemmen, die das Formular nur in der Einzelansicht anzeigt, das fragt man mit dem if(is_single() ab. Sonst ist nichts weiter dabei. Ansonsten gibt es 6 Radiobuttons mit den Noten und einen Button zum absenden des Formulars.

function el_insertText($content) {
    echo $content."<br><hr>";
  // if(is_single()) {
      
      echo '<div style="border:1px dotted #000; text-align:center; padding:10px;">';
      echo '<h4>Dir gefällt dieser Beitrag?</h4>';
      echo '<p>Dann bewerte ihn!</p>';
      echo '</div>';
      
     echo " <form action = '' method='post'>";
  
      echo "<fieldset>
        <div id = 'noten' class = 'noten' style = 'border:2px solid blue; padding :4px;'>
        <input type='radio'  name='note' value='1'>sehr gut
        <input type='radio' name='note' value='2'>gut
        <input type='radio' name='note' value='3'>befriedigend<br>
        <input type='radio'  name='note' value='4'>ausreichend
        <input type='radio' name='note' value='5'>mangelhaft
        <input type='radio' name='note' value='6'>ungenügend
        
        <input type ='button' name = 'absenden' class = 'bew-button' value = 'abschicken'>
        </div>
      </fieldset>";
    echo "</form>";
          
  // } //End von is_single
  
}
add_filter ('the_content', 'el_insertText');

Was brauchen wir für die Verarbeitung?

Eine Tabelle zum Speichern der Noten

Erstens eine Tabelle, in der die Bewertungen gespeichert werden. Ich hab mir da eine kleine Lösung im PHPmyAdmin angelegt, die Tabelle heisst bewertungen und hat drei Felder, die id als Autowert, die beitrags_id und die note.

tabelle_bewertungen

tabelle_bewertungen

WordPress-Puristen schreien hier wahrscheinlich schon Zetermordio, man könnte natürlich die Bewertungen in die wp_options reinklemmen, aber das war mir jetzt zu umständlich, ich arbeite lieber mit einer eigenen Tabelle.

Die aktuelle Beitrags-ID

Zweitens brauchen wir die ID des bewerteten Beitrags, und drittens natürlich die vergebene Note. Die ID holt man sich mit get_the_ID(), und ich schreibe sie als unsichtbaren Text mit rein, das geht so:

echo "<span id = 'beitragsid' style='display:none;'>".get_the_ID()."</span><br>";

Dann kann ich mir nachher anhand der Span-id im Javascript die ID herauspflücken. Die Note hole ich mir auch mit dem Script, dazu gleich mehr.

Eine Logik zur Ausgabe der bereits vorhandenen Bewertungen

Drittens brauchen wir für die Anzeige der schon vorhandenen Bewertungen eine Abfrage auf unsere Tabelle bewertungen und eine Ausgabe der berechneten Durchschnittsnote sowie der Anzahl der vorhandenen Bewertungen. Das ist nicht weiter wild, mit $wpdb und der BeitragsID ist das fix erledigt.

global $wpdb;
    $alleposts = $wpdb->get_results( "SELECT * from bewertungen 
    where beitrags_id = ".get_the_ID()."");
    
    $gefunden = $wpdb->num_rows;
        
    $durchschnitt = 0;
    foreach($alleposts as $einpost){
        $durchschnitt = $durchschnitt + $einpost->note;
    }
    
    if ($gefunden != 0){        
    $durchschnitt = $durchschnitt / $gefunden;
    }
        
    $durchschnitt= round($durchschnitt);

Damit aus dem gerundeten Notenwert auch ein Wort wird, klemmen wir noch einen switch case dahinter:

    $wort = '';
switch ($durchschnitt) {
    case 0:
        $wort = "keine Bewertung";
        break;
    case 1:
        $wort = "sehr gut";
        break;
    case 2:
        $wort =  "gut";
        break;
    case 3:
        $wort = "befriedigend";
        break;
    case 4:
        $wort =  "ausreichend";
        break;
    case 5:
        $wort =  "mangelhaft";
        break;
    case 6:
        $wort =  "ungenügend";
        break;
}
    
    echo "<span id = 'ausgabe'>".$gefunden." Bewertungen gefunden, Durchschnitt: ".$wort."<span>";

Die Ausgabe-Span kriegt gleich noch eine id verpasst, hier schreiben wir nachher unsere aktualisierten Werte rein.

Das zugehörige Javascript

…kommt mit ins Plugin-Verzeichnis, und wird mit enqueue script geladen. Wir schicken mit dem wp_localize_script gleich noch die URL zur admin-ajax.php mit.

add_action( 'wp_enqueue_scripts', 'el_bew_assets' );
function el_bew_assets() {
    
        wp_enqueue_script( 'bew', plugins_url( '/el_bewertung.js', __FILE__ ), array('jquery'), '1.0', true );
        wp_localize_script( 'bew', 'bewertungen', array(
        'ajax_url' => admin_url( 'admin-ajax.php' )));

    }

Wir packen unser gesamtes Script in eine document-ready Function, weil sonst JQuery noch nicht geladen sein könnte, wenn wir unser Script ausführen.  Die Function rennt los, sobald jemand den Bewertungs-Button geklickt hat. Zuerst werden die benötigten Daten auf Variable gelegt, und dann noch abgecheckt ob auch eine Benotung anggeklickt wurde.

jQuery(document).ready(function($){
jQuery( document ).on( 'click', '.bew-button', function() {
            
            
            // Ajax url aus localize script
            var a_url = bewertungen.ajax_url;
                        
            //Note aus den Radiobuttons holen
            var selectedOption = $("input:radio[name=note]:checked").val();
            
            //Beitragsid aus span holen
            var akt_id = document.getElementById("beitragsid").innerHTML;
            
            //Check ob keine Note angewählt, Abbruch
            if (typeof selectedOption === 'undefined'){
            alert('Bitte eine Note wählen!');
            return;}
                
            var post_note = selectedOption;
            
            alert('Vielen Dank, ihre Benotung wurde gespeichert');
            
            //Ajax-Call        
            jQuery.ajax({
                url : a_url,
                type : 'post',
                data : {
                    action : 'bew_add_bewertung',
                    post_id : akt_id,
                    note : post_note
                },
                success : function( response ) {
                    document.getElementById("ausgabe").innerHTML = response;
                
                }
    });
            
})
});

Der AJAX-Call ist recht einfach, es wird die ajax-admin-URL mitgegeben und als Request-Typ ‚post‘ definiert. In den data schicken wir zuerst mit, welche Funktion ausgeführt werden soll (dazu gleich mehr), und übergeben die Beitrags-ID sowie die Note. In der success-Function schreiben wir den Rückgabewert des Calls in die Span mit der ID ausgabe. Das wars schon!

Jetzt müssen wir WordPress nur noch anweisen, die Callback-Function auch zuzuordnen, dazu ergänzen wir unser Plugin um folgende Zeilen:

add_action( 'wp_ajax_nopriv_bew_add_bewertung', 'bew_add_bewertung' );
add_action( 'wp_ajax_bew_add_bewertung', 'bew_add_bewertung' );

Das erste Argument mit dem nopriv gilt fürs Frontend, das Zweite könnte ich mir eigentlich sparen, das wäre für die Admin-View und steht hier nur der Vollständigkeit halber.

Die Callback-Funktion

Macht ganz etwas Ähnliches wie unsere Bildschrimausgabe von oben, sie ermittelt anhand der übergebenen Beitrags-ID und der Note die aktuellen Zahlen. Dafür wird zuerst einmal die neue Bewertung in die Tabelle bewertungen eingetragen (Insert), dann wird neu berechnet und ausgegeben. Den switch case könnte man auch in eine eigene Funktion auslagern, ich habs mit copy&paste gemacht, ist genau dasselbe wie oben.

function bew_add_bewertung(){
    
    global $wpdb;
    
    //Neue Benotung eintragen
    $wpdb->insert('bewertungen', array(
            'beitrags_id' => $_POST['post_id'],
            'note' => $_POST['note']
            ));
            
    //gtNeue Anzahl holen
        $alleposts = $wpdb->get_results( "SELECT * from bewertungen 
    where beitrags_id = ".$_POST['post_id']."");
    
    $anzahl = $wpdb->num_rows;
    $durchschnitt = 0;
    foreach($alleposts as $einpost){
        $durchschnitt = $durchschnitt + $einpost->note;
    }
    
    if ($anzahl != 0){        
    $durchschnitt = $durchschnitt / $anzahl;
    }
    
    $durchschnitt= round($durchschnitt);
     $wort='';
    switch ($durchschnitt) {
    case 0:
        $wort = "keine Bewertung";
        break;
    case 1:
        $wort = "sehr gut";
        break;
    case 2:
        $wort =  "gut";
        break;
    case 3:
        $wort = "befriedigend";
        break;
    case 4:
        $wort =  "ausreichend";
        break;
    case 5:
        $wort =  "mangelhaft";
        break;
    case 6:
        $wort =  "ungenügend";
        break;
}
    
    $rueckgabe = $anzahl." Bewertungen, Durchschnittsnote: ".$wort;    
    echo $rueckgabe;
    
    die();
    
    
} //ende function bew_add_bewertung

Das die() am Ende braucht WordPress, um den Call korrekt auszuführen. Aber damit sind wir auch schon fertig. Die bereits vorhandenen Bewertungen werden angezeigt:

bewertungformular1

bewertungformular1

Wenn jetzt ein Besucher eine Bewertung abgibt, wird die Anzeige nach Klick auf den absenden-Button automatisch aktualisiert:

bewertungsformular2

bewertungsformular2

Man könnte an der Optik noch ein bisschen was verbessern, ich gebs ja zu, aber funktionieren tut die Sache einwandfrei. Wie gesagt, wenn man sich mit der WordPress-eigenen Ajax-Logik einmal auseinandergesetzt hat, ist so etwas nicht wirklich schwierig zu realisieren.

WordPress Stichwortsuche mit Treffer-Highlighting

Habt ihr euch auch schon manchmal gewünscht, WordPress würde die Suchergebnisse irgendwie kennzeichnen – zum Beispiel durch eine farbige Hinterlegung der gefundenen Begriffe? Manchmal ist es nämlich gar nicht so leicht herauszufinden, wo im Text sich der gesuchte Begriff (oder die gesuchten Begriffe) verstecken. Das eine durchaus übliche Sache, und ich hab mir mal ein paar Gedanken zu dem Thema gemacht und mich im Inselfisch-Kochbuch an die Umsetzung gemacht. Dabei ist ein Plugin herausgekommen, das sicher noch ein bisschen Optimierungspotential hat, aber so im Prinzip funktioniert die Sache. Wurde eh schon lang wieder einmal Zeit für ein bisschen Spaß auf der Datenbank!

Die Anforderungen

  • Es sollen ein oder mehrere Suchbegriffe (durch Leerzeichen getrennt) eingegeben werden können.
  • Zuerst sollen alle Rezepte ausgegeben werden, bei denen die gesuchten Begriffe schon im Titel vorkommen. Die Treffer werden farbig hinterlegt.
  • Dann sollen noch alle Rezepte nachfolgen, bei denen die Suchbegriffe im Rezepttext = post content vorkommen, auch hier farbig hinterlegt
  • Durch Klick auf den Rezepttitel soll man direkt zum Rezepttext gelangen.

Wir fangen natürlich mit einem Formular an

Das Plugin fängt mit einem einfachen Eingabefeld an:

/*
Plugin Name: Evis Suche mit Highlighting
Plugin URI: http://localhost/wp_ajax/wp-content/plugins/suche
Description: Eigenes Suchmodul mit Highlighting
Version: 1.0
Author: Evi Leu
Author URI: http://www.evileu.de
*/

//Formular als Shortcode einbinden
 function suche_form(){
     
    echo "<h3>Rezeptsuche mit Highlighting</h3>";
    echo "<p>Mehrere Stichworte mit Leerzeichen trennen</p>";
    
    
    echo "<form action='#' method='post'>";
    echo "<input type='text' size='100' name = 'suchstring' >";
    echo "</form>";
    

Der Inhalt des Textfeldes wird übernommen, wenn der Anwender auf Return drückt. Dann legen wir los:

if (isset($_POST['suchstring'])){
    
    //***ein oder mehrere Leerzeichen im String?
    if (strpos($_POST['suchstring']," ")>0){
        
        $suchsql = "";
        $suchsql_content = "";
        //string am leerzeichen trennen
        $strings = explode(" ",$_POST['suchstring']);
        $anzahl = count($strings);
        $ersterstring = $strings[0];
        for ($i=1; $i < $anzahl; $i++){
            //suchstring für post_title zusammenbauen
            $suchsql = $suchsql." AND post_title like '%".$strings[$i]."%' ";
            //suchstring für post_content zusammenbauen
            $suchsql_content = $suchsql_content." AND post_content like '%".$strings[$i]."%' ";
        }    
    }
    //***kein Leerzeichen im String
    else{$ersterstring = $_POST['suchstring'];
        
    }

Was hab ich gemacht? Wenn ein oder mehrere Leerzeichen im Suchstring vorhanden sind, zerlege ich den String mit explode() in ein Array. Dann baue ich mir aus allen vorkommenden Einzelstrings ein SQL-Snippet zusammen, eins für die Abfrage des post_title und eins für die Abfrage des post_content. Man kann die AND-Klauseln einfach hintereinanderhängen, es sollen ja alle Suchstrings gefunden werden. Groß/Kleinschreibung ist egal, der MySQL-Like ist nicht case sensitive. Wenn kein Leerzeichen im Suchbegriff vorkommt, übernehme ich ihn im Ganzen.

Die erste Abfrage geht auf den Titel

Mit dem zusammengebauten SQL-Snippet von oben starte ich die erste Abfrage: Dann gebe ich erstmal aus, wieviele Rezepte mit den Suchbegriffen im Titel gefunden wurden.

global $wpdb; 

    
    /******Suchstring im Titel?****/
    $alleposts = $wpdb->get_results( "SELECT * from ".$wpdb->prefix."posts 
where post_title like '%".$ersterstring."%' ".$suchsql."
    and post_type like 'post' and post_status like 'publish' order by post_title");
    
    $gefunden = $wpdb->num_rows;
    if ($gefunden == 1){
        
        echo $gefunden." Rezept mit ".$_POST['suchstring']." im Titel:";
    }
    if ($gefunden > 1){
        
        echo $gefunden." Rezepte mit ".$_POST['suchstring']." im Titel:";
    }

Dann klappere ich mit einem foreach alle Ergebnisse des ersten Selects ab und mache mit PHP ein paar Ersetzungen für das Highlighting der Suchbegriffe. Dafür benutze ich den str_ireplace, der ist nicht case sensitive. Dabei geht der erste Suchebegriff extra, für alle weiteren steppe ich mit der for-Anweisung durch unser Array von oben.

foreach($alleposts as $einpost){
        
        
        //**** ersten Suchstring im Titel highlighten
        $replace = '<span style="background-color: #FF0;">' . $ersterstring. '</span>'; // create replacement
        $content = str_ireplace( $ersterstring, $replace, $einpost->post_title );
        //***** ersten Suchstring im content highlighten
        $rezepttext = str_ireplace( $ersterstring, $replace, $einpost->post_content );
        
        //****alle weiteren Suchstrings highlighten
        for ($j=1; $j<$anzahl;$j++){
            
            $replace = '<span style="background-color: #FF0;">' . $strings[$j]. '</span>'; // create replacement
            $content = str_ireplace( $strings[$j], $replace, $content );
            $rezepttext = str_ireplace( $strings[$j], $replace, $rezepttext );
        }
        //als Accordeon ausgeben
        echo "<details><summary>".$content."</summary>";
        echo $rezepttext."</details>";
        echo "<hr>";
            
    }

Ganz am Ende benutze ich eine HTML5-Syntax für die Ausgabe des Rezepttitels als anklickbares Detail/Summary Element, auch Accordeon genannt. Das sieht dann so aus:

accordeon

accordeon

Wenn man auf das Dreieckssymbol klickt, wird der Rezepttext eingeblendet — mit Highlighting:

schweinefilet_highlighted

schweinefilet_highlighted

Das funktioniert auch mit mehreren Stichworten (es können beliebig viele sein), ich hab hier mal „butter tomate“ eingegeben:

butter_tomate

butter_tomate

Und jetzt das ganz nochmal auf den Content:

Der SQL ist nur minimal verändert und geht jetzt auf den post_content, das Highlighting funktioniert genauso wie oben, auch die Ausgabe als Accordeon:

//*******Suchstring im Content */
    
    $alleposts = $wpdb->get_results( "SELECT * from ".$wpdb->prefix."posts where 
         post_content like '%".$ersterstring."%' ".$suchsql_content." 
    and post_type like 'post' and post_status like 'publish' order by post_title");
    
    foreach($alleposts as $einpost){
        
        //**** ersten Suchstring highlighten
        $replace = '<span style="background-color: #FF0;">' . $ersterstring. '</span>'; // create replacement
        $rezepttext = str_ireplace( $ersterstring, $replace, $einpost->post_content );
        
        //****alle weiteren Suchstrings highlighten
        for ($j=1; $j<$anzahl;$j++){
            
            $replace = '<span style="background-color: #FF0;">' . $strings[$j]. '</span>'; // create replacement
            $rezepttext = str_ireplace( $strings[$j], $replace, $rezepttext );
        }
        //als Accordeon ausgeben
        echo "<details><summary>".$einpost->post_title."</summary>";
        echo $rezepttext."</details>";
        echo "<hr>";
        //}
        
    }

Das wars schon! Jetzt könnte man höchstens noch anmeckern, dass manche Rezepte zweimal auftauchen, weil sie einmal mit der Suche im Titel und ein zweites Mal mit der Suche im Content gefunden wurden, aber auch dafür sollte es eine Lösung  geben, das ist Feintuning.

Viel Spaß beim Nachbauen! Hier noch ein Beispiel, dass es auch mit mehreren Stichworten funktioniert, ich habe „butter tomate olivenöl hackfleisch knoblauch“ eingegeben:

5_suchworte

5_suchworte

Bemerkung nach Testbetrieb: immer diese Umlauts

Darauf bin ich erst später gekommen: der MySQL-Like ignoriert in der Kollation utf8_general_ci die deutschen Umlaute. So wird zum Beispiel zum Stichwort „huhn“ auch „hühnchen“ gefunden, da ü wird als u behandelt. Das wäre ja soweit noch nicht weiter tragisch, aber der PHP str_ireplace() macht sehr wohl einen Unterschied zwischen Umlauten und aeoui, da fällt das Highlighting auf die Nase… na ja, ich finde das jetzt nicht wirklich schlimm, es ist halt ein kleines Manko. Deswegen jetzt mit der Datenbankkollation rumzuexperimentieren erspare ich mir, das wäre mit Kanonen auf Spatzen geschossen.

Doppelt vorkommende Rezepte ausblenden

Bislang kamen Rezepte, die sowohl im Titel als auch im Content die eingegebenen Begriffe enthielten, doppelt vor. Das läßt sich relativ einfach unterbinden, ich merke mir einfach in einem Array, welche Rezept-IDs bei der Stichwortsuche im Titel schon gefunden wurden, und baue daraus einen String für eine not-in-Klausel. Dafür kommt noch folgender Code nach dem ersten SQL rein:

//merken, welche post ids schon gefunden wurden
    $merken = array();
    
    //Falls ein oder mehrere Rezepte gefunden wurden, IDs merken
    if ($gefunden>0){
        
            $k=0;
            foreach ($alleposts as $einpost){
            $merken[$k] = $einpost->ID;
            $k=$k+1;
            }
        }else{
            //Falls kein Rezept gefunden wurde, Dummy Eintrag mit ID 0
            $gemerkt = "(0)";
            }
    
    //Klammer für den not in aufbauen
    $gemerkt = "(";
    for ($l=0; $l < $gefunden; $l++){
   
    $gemerkt = $gemerkt.$merken[$l].", ";
    
    }
    //Klammer für den "not in" mit Dummy-Eintrag 0 abschliessen
    $gemerkt = $gemerkt."0)";

Den Dummy-Eintrag mit der 0 brauchts, damit der „not in“ nicht auf eine leere Klammer rennt und gar nichts ausgibt. Mit dem jetzt in der Variable $gemerkt gespeicherten String mit den IDs gehe ich in den zweiten SQL, der im Content sucht, und füge noch eine „not in“-Klausel  hinzu:

$alleposts = $wpdb->get_results( "SELECT * from ".$wpdb->prefix."posts where 
         post_content like '%".$ersterstring."%' ".$suchsql_content." 
    and post_type like 'post' and post_status like 'publish' 
and ID not in ".$gemerkt." order by post_title");

Das filtert alle Rezepte heraus, die bereits in der ersten SQL-Abfrage gefunden wurden.

 

HTML5 Datalist Value – ganz so einfach ist es nicht

Eigentlich gibt es in HTML ja die Option, einem Dropdownfeld zusätzlich zur Bezeichnung einen Value mitzugeben, der dann bei der Auswahl weitergegeben wird. In meinem Fall wäre die Bezeichnung der Titel des Rezeptes, und der Value die ID aus der Datenbank. Das würde dann etwa so aussehen:

...
<option value = '345'>Dampfnudeln wie bei Oma</option>"
...

Sobald der Benutzer den Eintrag Dampfnudeln ausgewählt und auf Return gedrückt hat, wird der Wert 345 übergeben – sollte zumindest. Diese Option funktioniert aber bei einer Datalist browserabhängig ganz unterschiedlich, wäre ja zu einfach.

In Chrome

…kriege ich die numerischen IDs in der Auswahlliste mit angezeigt, das ist nicht besonders schön, funktioniert aber wenigstens.

chrome_value

chrome_value

Es wird auch richtig bei der Auswahl die ID weitergegeben.

In Firefox Quantum

wird der Value-Wert einfach ignoriert, ich bekomme nur die Titel angezeigt und bei der Auswahl ausgegeben.

firefox_value

firefox_value

Im Internet Explorer 10

wirds besonders g’schmackig, da kann man gar nicht mehr nach dem Titel auswählen, der Autocomplete funktioniert erst wieder, wenn ich den Option Value ganz herausnehme.

Was nun? Ich mach mal kurzen Prozeß

Bevor ich mir hier für die unterschiedlichen Varianten der einzelnen Browserausgaben einen Wolf programmiere, verzichte ich ganz auf die Übergabe der ID und suche meine Rezepte schlicht nach dem Titel aus der Datenbank. Das kann ich mir erlauben, weil a) ich nur ein-eindeutige Titel habe und b) die Schreibweise aus dem Dropdownfeld identisch ist mit der Schreibweise in der wp_posts. Falls der User dann noch zusätzliche Zeichen eingibt (woran ihn nieman hindert) wird halt kein Rezept gefunden, aber damit kann ich leben. Um c) auszuschliessen, dass Revisions oder attachments mit reinrutschen, erweitere ich meinen Select einfach um post_type like ‚post‘ and post_status like ‚publish‘, aber das reicht dann.

Das Ganze klemme ich in eine if(isset($_POST)…Abfrage und packe es in den Shortcode für das Formular mit rein, das sieht dann so aus:

//*********Formular als Shortcode einbinden
 function asuche(){ 
    echo "<form action='#' method='post'>";
    echo "<input type = 'text' name = 'ausgesucht' size = '100' id = 'textfeld' autocomplete = 'off' onkeyup = 'myFunction(this.value);' list = 'suchliste'>";
    
    echo "<datalist id='suchliste'>";
    echo "<div id = 'liste'>";
    echo "</div>";    
    echo "</datalist>";
    echo "</form>";
     if (isset($_POST['ausgesucht'])){
             
    /*********Action*/     
    global $wpdb;
    $alleposts = $wpdb->get_results( "SELECT * from ".$wpdb->prefix."posts 
where post_title like '".$_POST['ausgesucht']."'
 and post_status like 'publish' and post_type like 'post'");
    foreach($alleposts as $einpost){
        
        echo "<h1>".$einpost->post_title."</h1>";
        echo $einpost->post_content;
    }
    /*********End Action*/
    }    
    
 }
 add_shortcode('asuche', 'asuche');

Damit wird folgende Funktionalität realisiert: der User kann aus der Autocomplete-Liste einen Eintrag auswählen und mit Return in das Textfeld übernehmen. Bei nochmaligem Drücken von Return wird der komplette Text des ausgewählten Rezeptes angezeigt. Das ist mir schön genug, und eingängig in der Bedienung.

Nur der Internet-Explorer macht noch Zicken, der schlägt beharrlich nur Rezepte vor, die mit dem eingegebenen Suchbegriff anfangen, nicht alle in denen der Begriff irgendwo im Titeltext vorkommt. Sei es drum, in Chrome und Firefox funktioniert es jedenfalls wie beabsichtigt.

WordPress AJAX Autocomplete für Minimalisten

Ich bin ja bekanntermassen ziemlich hartnäckig, und obwohl ich die Lösung für das Autocomplete-Feld von David Nash toll finde, hats mir doch keine Ruhe gelassen. Das muss doch noch ein bisschen einfacher gehen, dachte ich mir und hab den Codex durchforstet. Zum Thema Using Ajax in Plugins bin ich fündig geworden, da stand doch ein (Codex-unüblich nachvollziehbares) Beispiel drin, über das habe ich mich hergemacht. Das Javascript für den Ajax-Call wird hier nicht als externes Script enqueued, sondern in die Plugin-Datei integriert. Ist aber nicht schlimm, das Script ist nämlich sehr überschaubar – aber jetzt mal der Reihe nach.

Die Vorgabe

Ich möchte gern ein Textfeld, in das ich Suchbegriffe eingeben kann, und das soll mir dann aus der Datenbank alle Post Titles ausgeben, in denen der Suchbegriff vorkommt. Das soll etwa so aussehen:

schoko

schoko

Wir basteln uns ein Plugin

Natürlich brauchen wir dazu wieder ein Formular, und ich werde auch wieder die HTML5-Datalist verwenden. Wir packen das Ganze in ein Plugin, damit wir vom Theme unabhängig sind, und tun Formular und Javascript in einen Shortcode, damit wir das ganze in einem beliebigen Beitrag oder auf einer beliebigen Seite auch ausgeben können.

Zuerst mal nur das Plugin ohne JScript und Ajax-Funktion:

<?php
/*
Plugin Name: WordPressAjax Testplugin
Plugin URI: http://localhost/wp_ajax/wp-content/plugins/wp-ajax
Description: Zum Testen von Ajax mit WordPress
Version: 1.0
Author: Evi Leu
Author URI: http://www.evileu.de
*/


/*************************************************************/
//*********Formular als Shortcode einbinden
 function asuche(){
    
    echo "<input type = 'text' id = 'textfeld' autocomplete = 'off'
 onkeyup = 'myFunction(this.value);' list = 'suchliste'>";
    
    echo "<datalist id='suchliste'>";
    echo "<div id = 'liste'>";
    echo "</div>";    
    echo "</datalist>";
    
 }
 add_shortcode('asuche', 'asuche');

Ich hab hier schon mal den Funktionsaufruf bei onkeyup mit drin, der kriegt als Parameter den Inhalt des Textfeldes mit, Script kommt gleich. Die Verbindung vom Textfeld zur Datalist geht über die id suchliste. Autocomplete = ‚off‘ damit der Browser nicht reinpfuscht. In der Datalist hab ich eine weitere Div mit der id liste angelegt, in die schreibt nachher mein Ajax-Call seine Rückgabewerte.

Das integrierte Javascript

Wie gesagt, das Scripterl ist so kurz, dass man es nicht in eine externe Datei auslagern muss, wir nehmen es in die Funktion asuche mit rein, das sieht dann so aus:

function asuche(){ ?>
     
    <script>
        function myFunction(wert){
              
        var aktwert = wert;
        var data = {
            'action': 'my_action',
            'whatever': aktwert
            
        };

        // since 2.8 ajaxurl is always defined in the admin header and points to admin-ajax.php
        // wir sind aber nicht auf den Admin-Pages, deswegen der volle Pfad
        jQuery.post('http://localhost/wp_ajax/wp-admin/admin-ajax.php', data, function(response) {
        //    jQuery.post(ajaxurl, data, function(response) {
            
            document.getElementById("liste").innerHTML = response;
            
        });
        } //*********************End myFunction
        
    </script>
    <?php
...

Was passiert hier? Zuerstmal wird der als Parameter übergebene Wert des Textfeldes auf die Variable aktwert gelegt, diese kommt in die data-Parameter des Ajaxcalls mit rein. Der erste Parameter mit dem ‚action‘ definiert, welche Funktion nachher aufgerufen werden soll, die kommt gleich im Anschluss. Mit dem JQuery.post(…) wird die Anfrage an den admin-ajax.php weitergeleitet und die response definiert. Die macht nichts anderes als die Div mit der id liste mit dem Rückgabewert der aufgerufenen Funktion zu belegen, konkret füllt sie die Options-Liste unserer Datalist.

Was weniger schön ist: die Url zur admin-ajax.php steht hier noch als langer Rattenschwanz mit drin, das könnte man sicher schöner lösen. Wenn man zum Beispiel das JQuery-Script auslagern und per enqueue laden würde, könnte man mit wp_localize_script den Pfad als Variable mitgeben – aber das sind jetzt schon Feinheiten.

Damit der Funktionsaufruf klappt: Add Action

Ich habs zweimal drin, sowohl für die Admin-Ansicht als auch für nicht eingeloggte User:

add_action( 'wp_ajax_my_action', 'my_action' );
add_action( 'wp_ajax_nopriv_my_action', 'my_action' );

Jetzt fehlt uns noch die tatsächliche Funktion, die den Übergebenen Parameter aktwert entgegennimmt und und die passenden  Werte aus der Datenbank abruft.

Die PHP-Funktion

Da wir uns im Kontext von WordPress bewegen, brauchen wir keinen externen Datenbankzugriff mit mysqli oder PDO, sondern können wie gewohnt mit dem $wpdb-Objekt arbeiten. Die Funktion sieht so aus:

function my_action() {
    global $wpdb; 

    $whatever = $_POST['whatever'];
    
    /**********************************************/
    $alleposts = $wpdb->get_results( "SELECT * from ".$wpdb->prefix."posts 
where post_title like '%".$whatever."%' 
    and post_type like 'post' and post_status like 'publish'");
    foreach($alleposts as $einpost){
        
        echo "<option>".$einpost->post_title."</option>";
}
    /**********************************************/
    
    
    wp_die(); // this is required to terminate immediately and return a proper response
} //**********End function my_action

Unsere übergebene Variable whatever wird nicht anders verarbeitet, als ob sie aus einem Formular direkt käme, dafür sorgt der Call mit dem jQuery.post(…). Der Rest ist wirklich simpel, ich suche mir aus der Tabelle wp_posts alle Einträge, die unseren Suchbegriff matchen, gebe die gefundenen Post Titel mit der Foreach-Schleife aus und klebe noch die Option-Tags für unsere Datalist dran. Fertig ist unser Autocomplete-Feld!

suchwort_back

suchwort_back

Nachtrag: JS ausgelagert und Pfad übergeben

Wie ich oben schon angemerkt habe, ist es nicht so ideal dass im Javascript der volle Pfad zur admin-ajax.php steht. Das geht auch besser. Das Script kommt aus der Plugin-Datei raus, ich nenne es mal wp_ajax.js, und es sieht so aus:

function myFunction(wert){
                
        var aktwert = wert;
        var data = {
            'action': 'my_action',
            'whatever': aktwert
            };

        // der volle Pfad zur admin-ajax.php liegt auf my_ajaxurl
        jQuery.post(my_ajaxurl, data, function(response) {
                    
            document.getElementById("liste").innerHTML = response;
            
        });
        } //*********************End myFunction

Die Variable my_ajaxurl kriegt ihren Wert über wp_localize_script, das geht in einem Aufwasch mit dem Einbinden der externen Javascript-Datei, dazu stellt man folgenden Code an den Anfang des Plugins:

/*********Externes Script einbinden und Pfad zur admin-ajax.php übergeben*/
function mysite_js() {
      
    wp_enqueue_script('mysite-js', plugins_url().'/wp_ajax/wp_ajax.js', array('jquery'), false, false);
    
    wp_localize_script( 'mysite-js', 'my_ajaxurl', admin_url( 'admin-ajax.php' ) );
 
   }
add_action('wp_enqueue_scripts', 'mysite_js');

Dann kann das JS aus der Plugindatei raus, das sorgt für mehr Übersicht und ist wesentlich eleganter gelöst.

Was ich jetzt noch gern hätte

Wenn man einen Rezepttitel angewählt und auf Return gedrückt hat, möchte ich gerne das gewählte Rezept anzeigen lassen. Aber dazu muss ich mir noch ein, zwei Gedanken machen, meine bisherige Lösung mit der angehängten Rezept-ID ist nämlich eine ziemliche Krücke, das müsste eleganter gehen. Ich geh mal forschen…. und dafür gibts dann einen neuen Beitrag.

WordPress Autocomplete AJAX deutsch – Übersetzung des Tutorials von David Nash

Übersetzung des Tutorials von David Nash

Wie ich im vorigen Artikel schon erwähnt hatte, hat David Nash in seinem Blog ein tolles Tutorial über Autocomplete (Auto-Vervollständigen) mit Hilfe von AJAX in WordPress geschrieben. Ich habe von David die Erlaubnis, es zu übersetzen und auf dieser Seite zur Verfügung zu stellen. Das mache ich doch mit dem grössten Vergnügen – viel Spaß beim Nachbauen, und viel Erfolg!

Einleitung

Dies wurde für ein Projekt mit einer umfangreichen Datenbank realisiert, mit ca. 14.000 eingetragenen Geschäftsadressen. Es gibt eine Anmeldungsseite, auf der die Benutzer auswählen können, für welche Firma sie arbeiten. 14.000 sind viel zu viele Optionen für ein SELECT (drop-down) Feld, und ich wollte auch nicht so viel Last auf der MySQL Datenbank erzeugen.

Meine Lösung: ein Autocomplete-Textfeld, das die WordPress-Datenbank ausliest.

Ich gehe schrittweise vor, und stelle sicher dass jeder Schritt korrekt läuft, ehe ich den nächsten beginne. Hier ist das schrittweise Vorgehen:

Front-End JavaScript

Wir benutzen ein Child-Theme, weil wir ein Premium-Theme anpassen. In die functions.php kommt folgender Code, um die JavaScript-Datei zu laden:

Wir legen im Child-Theme Verzeichnis die Datei /js/mysite.js an, die sieht so aus:

Wenn man jetzt irgendeine Seite auf der Site neu lädt, kommt „jQuery loaded“ in einer JavaScript-Alertbox, das funktioniert also. Man hätte auch console.log(‘hello’) benutzen und die Developer Console in Chrome überpüfen können.

Als nächstes habe ich mir jQuery-autocomplete heruntergeladen. Ich hätte auch das jQuery-UI autocomplete benutzen können, aber ich benutze es sonst nicht und belasse die Sache lieber leichtgewichtig.

Aus dem Download kopiere ich mir die Dateien jquery.auto-complete.css und jquery-autocomplete.js nach /js/ – das selbe Unterverzeichnis, in dem mysite.js liegt.

Dann wird die functions.php angepasst um die Bibliothek zu laden:

function mysite_js() {

wp_enqueue_script('autocomplete', get_stylesheet_directory_uri().'/js/jquery.auto-complete.js', array('jquery')); 

wp_enqueue_script('mysite-js', get_stylesheet_directory_uri().'/js/mysite.js', array('jquery', 'autocomplete')); 

wp_enqueue_style('autocomplete.css', get_stylesheet_directory_uri().'/js/jquery.auto-complete.css');
}

add_action('wp_enqueue_scripts', 'mysite_js');

Jetzt die Seite neu laden, und sicherstellen dass die Library in der Chrome Developer Console unter dem „Sources“-Tab auftaucht.

Der AJAX Code

Als nächstes wird die mysite.js upgedatet, um etwas sinnvolles zu tun:

jQuery(document).ready(function($) { 

$('#company_works_at').autoComplete({
source: function(name, response) {

$.ajax({
type: 'POST',
dataType: 'json',
url: '/wp-admin/admin-ajax.php',
data: 'action=get_listing_names&name='+name,
success: function(data) {

response(data);

}

});

}
});
});

Dabei ist #company_works_at das Textfeld auf der Registrierungsseite, das den Autocomplete bekommen soll. Ich bin dabei der Dokumentation auf der Autocomplete-Seite gefolgt – die Source-Funktion braucht ein „response“-Callback um zu funktionieren, dies ist die „success“-Funktion im Aufruf von jQuery $.ajax().

Das PHP, das die AJAX Daten von WordPress schickt

In der functions.php kommt Folgendes hinzu:

//get listings for 'works at' on submit listing page

add_action('wp_ajax_nopriv_get_listing_names', 'ajax_listings');
add_action('wp_ajax_get_listing_names', 'ajax_listings');

function ajax_listings() {

global $wpdb; //get access to the WordPress database object variable

//get names of all businesses

$name = $wpdb->esc_like(stripslashes($_POST['name'])).'%'; //escape for use in LIKE statement

$sql = "select post_title 

 from $wpdb->posts 

 where post_title like %s 

 and post_type='job_listing' and post_status='publish'"; 

$sql = $wpdb->prepare($sql, $name);

$results = $wpdb->get_results($sql); 

//copy the business titles to a simple array

$titles = array();

foreach( $results as $r )

$titles[] = addslashes($r->post_title); 

echo json_encode($titles); //encode into JSON format and output 

die(); //stop "0" from being output

}

Das JavaScript, das den AJAX Call absetzt schickt die Daten in „name“. Ich benutze stripslashes() damit Namen mit Apostrophen etc. auch funtionieren. Ich benutze esc_like() aus Sicherheitsgründen. Das ‚%‘ am Ende bewirkt, dass nur auf die führenden Buchstaben gematcht wird. Wenn zum Beipsiel ein Benutzer „The“ eingibt möchte ich „The Best Business“ sehen, und nicht „Not the best business“. Aber wenn „Not“ einegegeben wird, möchte ich „Not the best business“ sehen.

Update: Wenn man alle Posts sehen will, das „and post_type=‚job_listing'“ aus dem $sql oben entfernen.

Testen, ob es funktioniert

Ich klicke auf das Autocomplete-Feld, tippe drei Buchstaben und bekomme eine ziemlich rasche Reaktion. Es schlägt mehrere Firmen vor, aus denen der Benutzer auswählen kann.

Dies ist nur ein Textfeld, und es erlaubt es dem Benutzer auch einen Namen einer Firma einzugeben, die nicht in der Datenbank ist. Wenn man möchte könnte man mehr Restriktionen anlegen, und statt dem Text auch die ID der Firma speichern. Man könnte dann auch in einem anderen Template einen Link auf die Firma einfügen.

Das war’s – so sollte es es klappen

Soweit das Tutorial von David Nash. Hier noch einmal der Link zu seinem Blog. Es lohnt sich, dort auch einmal durch die Kommentare durchzuschauen, da sind noch viele wertvolle Tipps drin.

 

 

WordPress und AJAX für Autocomplete – eine erste Annäherung

Einleitung

Ich hab mich kürzlich in einem Projekt mal ein bisschen mit den Grundlagen von AJAX beschäftigt, und finde es bietet wirklich faszinierende und in der Praxis gut anwendbare Möglichkeiten. Besonders g’wandt ist die Möglichkeit, zum Suchen bestimmter Datenbankeinträge ein Autocomplete oder Auto-Vervollständigen einzubauen. Der Benutzer tippt ein paar Buchstaben (in der Regel drei) in ein Textfeld ein, und man holt via AJAX die gematchten Einträge aus der Datenbank. Das, so hab ich mir gedacht, hätte ich gerne für eine komfortable Suche nach Rezepten im Inselfisch-Kochbuch, das sind nämlich mittlerweile über dreihundert und somit zuviel für ein Dropdown-Feld. Das könnte in etwa so aussehen:

form_leer

form_leer

Man stellt ein kleines Formular zur Verfügung, in das der Benutzer Text eingeben kann. Sobald in der Datenbank passende Einträge gefunden werden, werden diese als Auswahlliste angezeigt. Ich geb mal sal ein, hier ist das Ergebnis:

suche_sal

suche_sal

Das ist doch schon mal ganz schick, oder? Und nicht über die Nummern (#152) wundern, die brauchen wir später noch. Wenn der Benutzer einen Eintrag ausgewählt hat und auf Return drückt, soll dann das Rezept angezeigt werden, aber bis dahin ist es noch ein Stückchen weit. WordPress stellt nämlich eine ganz eigene Logik für die Verwendung von AJAX zur Verfügung, und die ist ein bisschen widerborstig. Aber professionelle Hilfe ist geboten:

Super Tutorial zum Thema

…von David Nash: https://davidnash.com.au/create-an-auto-complete-field-in-wordpress/

David erklärt wirklich Step by Step und fantastisch nachvollziehbar, auf was es ankommt. Das kann sich wirklich jeder selber reinziehen, das funktioniert direkt auf Anhieb.

Benötigtes Helferlein: jquery autocomplete

Kann man sich hier herunterladen: https://goodies.pixabay.com/jquery/auto-complete/demo.html

Man braucht nur die beiden Dateien jquery.auto-complete.css und jquery-autocomplete.js.

Kleine Ergänzungen zu Davids Tutorial

Das Eingabeformular

Um das Ganze auch ausprobieren zu können, braucht man natürlich ein Eingabeformular. Ich hab eins als Shortcode angelegt, da sieht in meiner functions.php so aus:

 function auto_form(){
    
    echo "<h3>Beispiel Rezeptsuche Autocomplete</h3>";
    
    echo "<form action='#' method='post'>";
    echo "<input type='text' size='40' name = 'auswahl' id = 'rezepte_auswahl'>";
    
    echo "</form>";
    
        
    if (isset($_POST['auswahl'])){
    echo "Vielen Dank! Ihre Auswahl :".$_POST['auswahl']."<br>";
    
   
    } //end if isset
 }
 add_shortcode('a_form', 'auto_form');

Wichtig ist hier die id des Eingabefeldes, die wird dann in unserer js-Datei referenziert:

jQuery(document).ready(function($) {    
    
    $('#rezepte_auswahl').autoComplete({
        source: function(name, response) {
            $.ajax({
                type: 'POST',
                dataType: 'json',
                url: '/wordpress/wp-admin/admin-ajax.php',
                data: 'action=get_listing_names&name='+name,
                success: function(data) {
                    response(data);
                }
            });
        }
    });
 
});

Wichtig ist hier auch die URL der admin-ajax.php, die muss man gegebenenfalls anpassen.

Nachtrag: ich hab mir eine andere Lösung ergooglet

Man kann mithilfe der WordPress-Funktion wp_localize_script() den Pfad zum Admin-Verzeichnis als Variable an das Script übergeben. Dazu ergänzt man die function mysite_js() wie folgt:

function mysite_js() {
    wp_enqueue_script('autocomplete', get_stylesheet_directory_uri().'/js/jquery.auto-complete.js', array('jquery'), false, false);
    
    wp_enqueue_script('mysite-js', get_stylesheet_directory_uri().'/js/mysite.js', array('jquery', 'autocomplete'), false, false);
    
    wp_localize_script( 'mysite-js', 'my_ajaxurl', admin_url( 'admin-ajax.php' ) );
 
    wp_enqueue_style('autocomplete.css', get_stylesheet_directory_uri().'/js/jquery.auto-complete.css');
 
}
add_action('wp_enqueue_scripts', 'mysite_js');

Dann kann man im Script mysite.js für die URL einfach die Variable my_ajaxurl einsetzen:

jQuery(document).ready(function($) {    
    
    $('#rezepte_auswahl').autoComplete({
        source: function(name, response) {
            $.ajax({
                type: 'POST',
                dataType: 'json',
                url: my_ajaxurl,
                //url: '/wordpress/wp-admin/admin-ajax.php',
                data: 'action=get_listing_names&name='+name,
                success: function(data) {
                    response(data);
                }
            });
        }
    });
 
});

Das aber nur als kleiner Tipp am Rande.

Ich hole mir noch die Post-ID

Dazu passe ich die Funktion ajax_listings() ein bisschen an:

function ajax_listings() {
    global $wpdb; //get access to the WordPress database object variable
 
    //get names of all businesses
    $name = $wpdb->esc_like(stripslashes($_POST['name'])).'%'; //escape for use in LIKE statement
    
    $sql = "select post_title, ID 
        from $wpdb->posts 
        where post_title like %s 
        and post_type='post' and post_status='publish'";
 
    $sql = $wpdb->prepare($sql, $name);
    
    $results = $wpdb->get_results($sql);
 
    //copy the business titles to a simple array
    $titles = array();
    foreach( $results as $r )
        $titles[] = addslashes($r->post_title." #".$r->ID);
        
    echo json_encode($titles); //encode into JSON format and output
    
    die(); //stop "0" from being output
}

In den Select kommt noch die ID mit rein, und die hänge ich in der Foreach-Schleife auch noch mit an die Ausgabe dran, mit einem # vorneweg.

Extrahieren der ID im Formular

Wenn der Benutzer einen Listeneintrag ausgewählt und auf Return gedrückt hat, soll ja etwas passieren: nämlich das gewählte Rezept ausgegeben werden. Das „Return-Drücken“ erwischt man mit einem If (ISSET…), dann zerlege ich mir die entsprechende Variable und hole mir nur die Zahl nach dem #.

function auto_form(){
    echo "<a name='form'></a>"; 
    echo "<h3>Beispiel Rezeptsuche Autocomplete</h3>";
    
    echo "<form action='#form' method='post'>";
    echo "<input type='text' size='40' name = 'auswahl' id = 'rezepte_auswahl'>";
   
    echo "</form>";
    
        
    if (isset($_POST['auswahl'])){
    echo "Vielen Dank! Ihre Auswahl :".$_POST['auswahl']."<br>";
    
    //***********Action
    $text = strstr($_POST['auswahl'], '#');
    $text=substr($text,1); 
    echo "Ich bin die Nummer ".$text;
    ///*****end Action
    } //end if isset
 }

Jetzt liegt die ID des gewählten Rezeptes auf der Variablen $text, mit der arbeiten wir weiter und geben einfach den post_content aus:

global $wpdb;
    $sql = "select post_content, ID 
        from $wpdb->posts 
        where ID like ".$text." 
        ";
    
    $results = $wpdb->get_results($sql);
    
    foreach ($results as $r){echo $r->post_content;}

Damit wird das Rezept angezeigt, sobald der Benutzer seine Auswahl getroffen und auf Return gedrückt hat:

salatbowle

salatbowle

Man könnte natürlich auch einen Link zum Rezept einbauen, aber ich lasse es mal so, dann muss der Benutzer nicht woanders hin wechseln, wenn er noch ein Rezept suchen möchte.

Man könnte auch noch die Suche ein bisschen in der Funktionalität verändern, wenn man zum Beispiel nicht nur nach Einträgen suchen möchte, die mit der eingegebenen Zeichenfolge beginnen, sondern alle, in denen die Zeichenfolge enthalten ist. Dann kann man in der function ajax_listings() die Suchvariable einfach anpassen und die Wildcard % noch vornedran setzen:

$name = '%'.$wpdb->esc_like(stripslashes($_POST['name'])).'%';

Das findet z.B. alle Einträge mit salat im Titel, auch nicht unpraktisch:

instring_salat

instring_salat

Alles in allem: eine superpraktische Funktionalität, und dank des grossartigen Tutorials von David Nash einfach umzusetzen. Gefällt mir ausserordentlich gut!

Kleiner Tipp am Ende:

Wenn es zwischendurch so aussieht, als ob WordPress die eigentlich geladenen js-Scripts nicht mehr kennt – Browsercache löschen!

 

Joomla Modul mit SQL-Formfield

Da ich keinen vorgefertigten Formularfeldtyp gefunden habe, der mir die Tags (Schlagwörter) ausgibt, hab ichs mal über den Typ SQL form field type probiert, mal sehen wie weit wir damit kommen. Aber erst noch mal ein paar Gedanken zu den Anforderungen:

Ich schraubs mal ein bisschen runter

Im vorigen Artikel hatte ich ja für den Anwender die Möglichkeit geschaffen, eine bestimmte Beitragskategorie auszuwählen. Das hat ganz hübsch funktioniert, aber eigentlich ist es für meinen Zweck nicht ganz das Richtige. Ich brauche ja auch noch mein zweites Auswahlkriterium, nämlich die Tags=Schlagwörter. Kurze Erinnerung, was ausgegeben werden soll:

X Rezepte insgesamt

davon Y mit dem Tag Z

Ich habe aber in meinem Joomla-Kochbuch die Einschränkung gemacht, dass Tags nur für die Kategeorie Rezepte verwendet werden, in den anderen Kategorien (Kochbücher etc.) hab ich sie schlicht und ergreifend nicht eingesetzt. Das heisst aber, unsere Tagauswahl macht nur Sinn, wenn die Beitragskategorie 8 für Rezept ist, und dann kann ich es auch gleich fest verdrahten. Sonst dürfte ich die Tag-Auswahl nur anzeigen, wenn der Anwender die Kategorie Rezepte gewählt hat, und das geht mir dann doch ein bisschen zu weit.

Der Formularfeldtyp SQL

Ist wahrscheinlich am leichtesten zu verstehen, wenn man es am konkreten Beispiel sieht. Die Felddefinition in meiner XML-Datei sieht so aus:

<field
            name="title"
            type="sql"
            default="10"
            label="Einen Tag auswählen"
            query="SELECT id AS value, title FROM #__tags where title not like 'ROOT'"
            />

Dabei ist der Alias „AS value“ wichtig, da über diesen der Rückgabewert des Feldes definiert wird. Den Select hab ich gleich ein bisschen angepasst, wir gehen in die tabelle #_tags und schliessen hier den Systemeintrag ROOT aus, weil der in der Auswahlliste gar nicht auftauchen darf.

Zum Aufbau der Query zitiere ich mal die Joomla-Doku:

  • query (mandatory if not using the sql_* attributes) is the SQL query which will provide the data for the drop-down list. The query must return two columns; one called ‚value‘ (unless overridden by the key_field attribute) which will hold the values of the list items; the other called the same as the value of the name attribute (unless overridden by the value_field attribute) containing the text to be shown in the drop-down list.

Also, mal ganz langsam.

  • die query liefert die Daten für unser Dropdown-Feld
  • die query muss zwei Spalten zurückgeben
  • die erste Spalte der Rückgabe muss value heissen, das passiert hier mit dem Alias, und liefert die Rückgabewerte für das Formularfeld (in unserem Fall die numerische ID des Tags)
  • die zweite Spalte muss genauso heissen wie das Feld in der XML-Datei, ich bin hier bei title geblieben. Die zweite Spalte liefert die Einträge für das Dropdown-Feld.

Alles klar? Jedenfalls funktionierts, und mein Dropdownfeld sieht schon mal ganz gut aus:

dropdown_tags

dropdown_tags

Den Wert, den der Benutzer ausgewählt hat, holen wir uns in der mod_helloworld.php wie gehabt über den Namen des Feldes:

$titel = $params->get('title');

Wir erweitern unser Standardobjekt für die Parameterübergabe an die Helper-Klasse entsprechend:

$data = new stdClass();
//Objekt füllen
$data->kat = $kat;
$data->variable = $variable;
$data->titel = $titel;

Und können jetzt in der helper.php damit weiterarbeiten, da haben wir jetzt die numerische ID des gewählten Tags auf der Variablen $params->titel.

Jetzt müssen wir nur noch zählen, wieviele Rezepte dem gewählten Tag zugeordnet sind, und dafür brauchen wir die Tabelle #__content_item_tag_map, da schauen wir einfach nach, wie oft die aktuelle Tag-ID auftaucht.

//Anzahl der Tags zur TagID holen
        $db = JFactory::getDbo();
        $db->setQuery("SELECT * FROM #__contentitem_tag_map WHERE tag_id = ".$params->titel.""); 
        $db->execute();
        $my_count = $db->getNumRows();
        echo $my_count;

Schlußendlich fehlt uns noch der Name des Tags für die Ausgabe, den holen wir uns mit der ID aus der Tabelle #__tags:

//Name des Tags zur Tag-ID holen
        $db = JFactory::getDBO();
        $db->setQuery("SELECT title FROM #__tags WHERE id=".$params->titel);
        $db->execute();
        $tagName = $db->loadResult();

Das wars, fehlt nur noch die Ausgabe:

echo $my_count." davon zum Thema ".$tagName;

 Bingo, das hat hingehauen! Meine Ausgabe sieht so aus:

tag_kat_ausgabe

tag_kat_ausgabe

Passt einwandfrei. Man könnte es jetzt noch perfektionieren und einen Join auf die #__content über die Kategorie-ID einbauen, damit man nur die Tags angezeigt bekommt, die auch zu Rezepten zugeordnet sind, aber ich wills mal nicht übertreiben. Die Vorgabe war ja, dass Tags nur in der Kategorie Rezepte verwendet werden, das muss reichen.

Fazit

Ich finde, die Formularfelddefinition ist über die XML-Datei übersichtlich und komfortabel gelöst, und die vordefinierten Formularfeldtypen nehmen einem in vielen Fällen einen Haufen Arbeit ab. Das macht es wieder wett, dass die Modulerstellung am Anfang ein bisschen schwer zu durchblicken ist, aber wenn man einmal ein Basic Modul geschrieben hat, geht auch das leicht von der Hand. Modulbasteln in Joomla macht Spaß!

Joomla-Modul ein bisschen aufgebohrt – mit Benutzereingaben

Ich hab ja in diesem Artikel ein WordPress-Widget erstellt, das die Gesamtzahl der veröffentlichten Rezepte sowie die Anzahl der Rezepte einer frei wählbaren Kategorie anzeigt. So nach dem Muster:

312 Rezepte insgesamt

46 davon Vegetarisch

Ob das mit unserem Joomla-Modul auch hinzukriegen ist?

Anzahl der Beiträge der Kategorie Rezepte

Meine Rezeptkategorie hat die ID 8, das kann man in der Tabelle #__categories nachschauen. Dazu hab ich folgendes Code Snippet gefunden:

$model = JModelLegacy::getInstance('Articles', 'ContentModel');
        $model->setState('filter.category_id', 8); // Set category ID here
        $articles = $model->getItems();

        $num_articles = count($articles); // Returns the number of articles in category

Kommt mir ein bisschen arg umständlich vor, aber wenns funktioniert…

Ich möchte jetzt aber natürlich auswählen können, zu welcher Kategorie die Gesamtzahl der Beiträge ausgegeben werden soll, weil ich in Joomla so schöne Möglichkeiten habe, über die Kategorisierung meine Ausgabe auf der Webseite zu strukturieren. Das heißt aber, wir brauchen ein Eingabefeld (Dropdown), in dem der Benutzer anwählen kann, welche Kategorie ausgegeben werden soll.

Eingabefelder für eigene Module in Joomla

Sind nicht arg schwer zu realisieren, die Joomla-Doku zu dem Thema ist schon mal recht gut, schaut mal hier rein:

https://docs.joomla.org/J3.x:Creating_a_simple_module/Adding_Form_Fields

Ich machs aber hier nochmal step by step, damit man es auch nachvollziehen kann. Wir setzen auf unser Hallo-Welt Modul aus dem vorigen Beitrag auf und erweitern die mod_helloworld.xml, in der kann man ein einfaches Texteingabefeld so erstellen:

 <config>
    <fields name="params">
        <fieldset name="basic">
            <field name="param1" type="text" default="" label="Bitte Titel eingeben" description=""></field>
           </fieldset>
    </fields>
    </config>

Die Felddefinition muss innerhalb der config-Tags stehen. Wenn man sich jetzt mal das Modul aschaut, müsste das Feld eigentlich schon da sein:

texteingabefeld

texteingabefeld

Wie kommt man jetzt an den Inhalt des Feldes ran, den unser Benutzer eiingegeben hat? Das passiert in der mod_helloworld.php, und zwar so:

$variable = $params->get('param1');

Die $variable kann man jetzt an die helper-Klasse übergeben:

$hello = modHelloWorldHelper::getHello($variable);

Und da kann man dann damit weiterarbeiten. Bevor ich da noch weiter darauf eingehe, erstmal:

Die Anforderungen

Den Titel des Moduls kann der Anwender beim Erstellen unter Erweiterungen->Module->neu->Mein Hallo-Welt Modul selber eingeben, den fragen wir nicht nochmal extra ab. Dafür brauchen wir aber ein Dropdown-Feld, in dem die Kategorie (z.B. „Rezepte“) ausgewählt werden kann, für die die Gesamtzahl der Beiträge ausgegeben soll. Und wir brauchen noch ein zweites Dropdownfeld für das „featured tag“ (z.B. „Vegetarisch“, aber dazu kommen wir später. Jetzt werfen wir erstmal einen Blick auf:

Die vordefinierten Form Field Types

Da bietet Joomla etwas wirklich praktisches an, es gibt eine ganz Latte von vordefinierten Formularfeldern, hier die Doku dazu: https://docs.joomla.org/Standard_form_field_types

Da klemmen wir uns gleich mal den Formularfeldtyp „Kategorie„, der macht nämlich genau das was wir brauchen. In der XML-Datei sieht das so aus:

<field name="mycategory" type="category" extension="com_content" label="Eine Kategorie auswählen" description="" />

In der mod_helloworld.php holen wir uns den Inhalt mit:

$kat = $params->get('mycategory');

Und im Modul-Backend kriegen wir unsere Dropdown-Liste mit den Kategorien:

dropdown_kategorien

dropdown_kategorien

In der übergebenen Variablen steckt übrigens die numerische ID der Kategorie Rezepte, die können wir gleich direkt weiterverwerten, und zwar in der helper.php:

public static function getHello($params)
    {
        $model = JModelLegacy::getInstance('Articles', 'ContentModel');
        $model->setState('filter.category_id', $params); // Set category ID here
        $articles = $model->getItems();

        $num_articles = count($articles); // Returns the number of articles in category
        
        echo "Insgesamt ".$num_articles." Rezepte<br>";    
        
        
        
    }

Voraussetzung dafür ist natürlich, dass wir sie in der mod_helloworld.php auch als Parameter an die Helper-Klasse übergeben haben, aber dazu gleich mehr, zuerst schauen wir uns mal die Ausgabe an:

insgesamt_rezepte

insgesamt_rezepte

Das paßt soweit, jetzt müssen wir nur noch den Ausgabetext dynamisch anpassen, es kann ja auch jemand die Kategorie „Kochbücher“ angewählt haben, dann darf da nicht „Rezepte“ stehen. Das machen wir so:

//Name der Kategorie zur ID holen
        $db = JFactory::getDbo();
        $db->setQuery("SELECT cat.title FROM #__categories cat WHERE cat.id='".$params->kat."'"); 
        $category_title = $db->loadResult();
        
        echo "Insgesamt ".$num_articles." ".$category_title."<br>";

Nicht dran stören dass hier „.$params->kat.“ steht, das kommt daher, dass ich ein Array von Parametern übergeben habe, mehr dazu gleich. Jedenfalls wird jetzt auch der richtige Kategoriename angezeigt:

2kochbuecher

2kochbuecher

Mehr als ein Formularfeld, mehrere Parameter zum Übergeben

Wir brauchen aber noch ein zweites Formularfeld für die Tags, und dazu werfen wir einen kurzen Blick darauf, wie wir mehr als eine Variable an die Helper-Klasse übergeben können. Kurze Erinnerung, wie die zwei Felddefinitionen in der XML-Datei aussehen:

<fields name="params">
        <fieldset name="basic">
            <field name="param1" type="text" default="" label="Bitte Text eingeben" description=""></field>
            
                
            <field name="mycategory" type="category" extension="com_content" label="Eine Kategorie auswählen" description="" />
            
           </fieldset>
    </fields>

Die Inhalte der Felder holen wir uns in der mod_helloworld.php über die Feldnamen:

$kat = $params->get('mycategory');
$variable = $params->get('param1');

Dann packen wir sie in ein Standardobjekt und übergeben dieses an die Helper-Klasse:

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

        
$hello = modHelloWorldHelper::getHello($data);

In der helper.php holen wir uns die übergebenen Variablen, die ja in unserem Objekt stecken, mit ihrem Namen, also z.B. $params->kat. Das $params stammt aus der Funktionsdefinition der public static function getHello($params)) wer da etwas anderes stehen hat, muss das entsprechend anpassen. Hier nochmal die komplette function:

public static function getHello($params)
    {
        //Anzahl der Artikel zur übergebenen Kategorie-ID holen
        $model = JModelLegacy::getInstance('Articles', 'ContentModel');
        $model->setState('filter.category_id', $params->kat); // Set category ID here
        $articles = $model->getItems();

        $num_articles = count($articles); // Returns the number of articles in category
        
        //Name der Kategorie zur ID holen
        $db = JFactory::getDbo();
        $db->setQuery("SELECT cat.title FROM #__categories cat WHERE cat.id='".$params->kat."'"); 
        $category_title = $db->loadResult();
            
        echo "Insgesamt ".$num_articles." ".$category_title."<br>";
        
        
    }

Das sieht jetzt schon ziemlich gut aus. Leider habe ich für die Tags keinen vordefinierten Formularfeldtyp gefunden, da müssen wir was basteln, aber dazu gibt es einen neuen Beitrag.

WordPress, Joomla und Drupal: Theme-Erstellung im Vergleich

Also, ich hab ja kürzlich das Anlegen eigener Templates/Themes in allen drei CMS ein bisschen angerissen und die Basics zusammengeschrieben. Da bietet es sich an ein Resumee zu ziehen, was ich hiermit versuche. Mein völlig subjektiver Winner ist:

Joomla – so einfach kann es sein

Sobald man den Zusammenhang der definierenden XML-Datei templateDetails.xml und den Modulpositionen in der index.php überrissen hat, kann man schon richtig loslegen. Kein Gewühle in unverständlichem PHP-Code, kein umständliches Einbinden externer Skripte. Dadurch, dass man in Joomla über die Kategorien und Module im Backend nahezu unendliche Konfigurationsmöglichkeiten hat, ist es gar nicht notwendig tiefer in die Template-Logik einzusteigen, man kann fast alles über die Administrationsoberfläche einstellen. Dabei kann man sich allerdings auch ganz schön verzetteln – eine gute Doku ist hier wirklich unabdingbar, sonst kann man nie nachvollziehen warum, wie und weshalb wer an welchen Schräubchen gedreht hat.

Was ein bisschen übersichtlicher sein könnte: die Modulzuordnung in der Admin-Oberfläche. Da fehlt eine Möglichkeit, sich nur die Modulpositionen eines bestimmten Templates anzeigen zu lassen (oder ich hab sie nicht gefunden). So wie es ist muss man sich bei der Modulpositionierung durch eine fusselige Liste aller Templates mit allen Modulpositionen durchscrollen, da sind Fehlklicks vorprogrammiert.

Das wird allerdings beinahe ausgeglichen durch die hervorragende Möglichkeit, sich die Modulpositionen auf der Webseite anzeigen zu lassen. Das geht, indem man im Browser an die URL den Parameter ?tp=1 anhängt, das sieht dann z. B. so aus: http://localhost/jsite/?tp=1, damit kriegt man einen anschaulichen Überblick, wo man auch gleich erkennt wenn am css noch was nicht stimmt.

joomla_tp

joomla_tp

Alles in allem ist die Erstellung eines eigenen Templates in Joomla ein recht vergnüglicher Prozess, das Positionieren der benötigten Module geht recht flott von der Hand und die Feineinstellungsmöglichkeiten über die Adminoberfläche sind sehr weitreichend, ohne dass man in den Templatecode eingreifen muss. Ich kann auch jedem die kurze und knackige Joomla-Dokumentation zum Thema ein einfaches Template erstellen empfehlen, damit geht echt ein Schnellstart!

Ein guter zweiter Platz: Drupal mit Views

Ohne Views geht nämlich nahezu gar nichts, wenn man seine Inhalte auch übersichtlich ausgeben möchte. A propos Inhalte: die selbstdefinierten Inhaltstypen sind eine der grossen Stärken von Drupal, wenn man die vernünftig nutzt spart man sich einen Haufen Template-Gefiddel. Ein eigenes Template anzulegen ist jetzt eigentlich auch nicht weiter schwierig, wenn man als Vorlage die generische page.tpl.php nutzt und keine Scheu davor hat, darin Elemente herumzuschubsen und umzugruppieren. Auch in Drupal kommt einem eine Vorschau der Template-Elemente zu Hilfe, unter Struktur->Blöcke->Blockregionen veranschaulichen kann man schnell überprüfen ob das Layout passt:

blockregionen

blockregionen

Da man alle Views auch als Blöcke definieren und ins Layout einpassen kann, ist man in der Ausgabe der gewünschten Daten nahezu unendlich flexibel, da bleibt so gut wie kein Wunsch offen. Besonders die Multiblog-Funktionalität über die unterschiedlichen Inhaltstypen und Taxonomien ist easy in der Realisierung und macht es einem leicht, die Seite sauber zu strukturieren.

Ich hätte mir höchstens eine einfachere Möglichkeit des Seitendesigns gewünscht als die page.tpl.php anzupassen, die ist doch ein bisschen unübersichtlich und tendiert zum div-Wildwuchs. Und noch etwas, was wirklich nervt: man kommt bei der Template-Entwicklung nicht darum herum, ständig den Cache zu leeren, weil man seine Änderungen sonst im Zweifelsfall erstmal nicht sieht. Das kostet Zeit und nervt, da wäre es wünschenswert, wenn man eine Möglichkeit hätte so etwas wie einen Entwicklermodus einzuschalten, in dem das Caching abgeschaltet werden kann.

Das gute alte WordPress auf Rang drei

Da merkt man halt schon, dass die Software etwas in die Jahre gekommen ist. Mit einem einfachen Seitenaufbau mit einem Kopfbereich, einer Inhaltszone mit ein paar statischen Seiten, einem Blog und einer Sidebar ist man recht schnell dabei, aber wenn man mehr will, gehts gleich ans Eingemachte. Entweder man arbeitet sich selber in die WordPress-API ein (und die ist eine Wissenschaft für sich) oder man verläßt sich auf die Funktionalitäten zusätzlicher Plugins. Von denen gibt es zwar unzählige für jeden Zweck und Geschmack, aber ich kauf nicht so gern die Katze im Sack, und die meisten Plugins sind grottenschlecht bis gar nicht dokumentiert. Da hat man leicht mal eins installiert, das einem die ganze Site lahmlegt oder unerklärliche Fehlfunktionen auslöst, und dann hat man das Gfrett herauszufinden wer denn jetzt wirklich der schwarze Peter war.

Auch eine komfortable Datenbank-Abfragemöglichkeit wie Drupals Views oder eine fein differenzierte Einstellmöglichkeit nahezu aller Parameter für die Anzeige wie in Joomla sucht man vergebens, man muss sich im Zweifelsfall selber in die vorhandenen Theme-Dateien einarbeiten und sie nach eigenem Bedarf anpassen, und eigene Plugins und Widgets entwickeln. Das war vor ein paar Jahren noch akzeptabel, jetzt aber laufen die beiden jüngeren CMS dem alten WordPress da um Längen den Rang ab.

Nur einen nicht so leicht zu schlagenden Vorteil hat WordPress: für kleinere, einfachere Webseiten ist es immer noch das Anwenderfreundlichste, am Leichtesten zu bedienende CMS auf dem Markt. Start Blogging – das ist kein leerer Werbespruch, das ist nach wie vor WordPress‘ grosse Stärke! Für den Einzelunternehmer, Handwerker, Arzt oder Anwalt, der seine Firma mit einer Visitenkarte im Web vorstellen möchte, ist WordPress nach wie vor die ideale Lösung. Auch für mittlere Benutzergruppen wie Vereine, kleinere Behörden und soziale Dienstleister ist es ideal, weil kostengünstig in der Realisierung und leicht zu pflegen. Der Markt für WordPress als „kleine“ Lösung für den Webauftritt ist riesengross und wird es aller Voraussicht nach noch lange bleiben.