Rezeptecounter revisited: für WordPress-Puristen

Ich arbeite ja gern mit eigenen Tabellen, aber die WordPress-Puristen schreien da immer gleich Zetermordio und wollen eine WordPress-konforme Lösung sehen. Deswegen stricke ich die Logik für das Wegschreiben des Rezeptezählers nochmal um und verstaue die relevanten Daten in der wp_options. Ich habe zu jedem Datensatz drei relevante Kennzahlen: die ID des Rezeptes, den Titel und den Zählerstand – wobei man den Titel auch nachträglich über die ID dazujoinen könnte, der ist hier eigentlich redundant. Diese drei packe ich in ein Array, und mit diesem Array füttere ich die Option. Das Ganze sieht dann so aus:

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

Genau genommen könnte man sich die if-else-Konstruktion sparen und in jedem Fall einen Update mit Zähler+1 machen, da WordPress mit update_option die Option neu anlegt, falls sie noch nicht existieren sollte.

WordPress macht aus den Arrays serialisierte Strings, da sieht dann der option_value zum Beispiel so aus:

a:3:{s:7:"zaehler";i:4;s:2:"id";i:1392;s:5:"titel";s:20:"Test für Backwerk";}

Das kann man Klartext lesen:

a für Array, Länge drei{s für string: Länge7:wert „zaehler“; i für integer:Wert 4…

… und so weiter. Um jetzt zum Beispiel an den Wert des Zählers heranzukommen, verwendet man folgende Syntax:

$meineOption = get_option('[name_der_option]');
 $zaehler = $meineOption['zaehler'];

Da $meineOption hier ein Array zurückgibt, könnte man auch über die Einträge iterieren und alle ausgeben, wenn man es braucht.

Jedenfalls haben wir jetzt unsere relevanten Daten WordPress-konform in der wp_options gespeichert. Wie aber kommen wir jetzt an die Auswertung? Mit einem SQL sind die serialisierten Werte nicht zu packen, da muss eine andere Lösung her. Na ja, pack’mas.

Auswertung des Rezeptecounters aus der wp_options

Dafür holt man sich mit einem Select.. WHERE option_name like ‚zaehler_%‘ alle relevanten Einträge aus der wp_options. Die Werte kann man schon mal mit einem foreach ausgeben:

function el_opcounter_hitparade(){
 
 //Bisher gespeicherte Rezepte holen
 global $wpdb;
 $alleposts = $wpdb->get_results( "SELECT * from iii_wpoptions where option_name like 'zaehler_%'");
 $anzahl = $wpdb->num_rows;
 echo "Anzahl gefunden= ".$anzahl."<br>";
 
 
 foreach($alleposts as $einpost){
 
 //Optionswerte auslesen 
 $meineOption = get_option($einpost->option_name);
 $o_zaehler = $meineOption['zaehler'];
 $o_titel = $meineOption['titel'];
 $o_id = $meineOption['id'];
 echo $o_id." ".$o_titel." ".$o_zaehler."<br>";
 }
 
}

Damit kriegt man schon mal die Liste.

unsortiert

unsortiert

Die hätte ich jetzt aber gern nach der Anzahl der Aufrufe absteigend sortiert, aber hier geht nix mit Order by, mit SQL ist das leider nicht zu packen. Das muss mit PHP, und das ist nicht ohne, dafür muss man einen multisort bemühen. Für den lesen wir unsere Daten in ein Array ein, das machen wir so:

Einlesen der Rohdaten in ein Array

Dafür kann unser Select nochmal herhalten:

//Bisher gespeicherte Rezepte holen
 global $wpdb;
 $alleposts = $wpdb->get_results( "SELECT * from iii_wpoptions where option_name like 'zaehler_%'");
 $anzahl = $wpdb->num_rows;
 echo "Anzahl gefunden= ".$anzahl."<br>";
 
 $rohdaten = array();
 $i=0;
 foreach($alleposts as $einpost){
 
 //Optionswerte auslesen 
 $meineOption = get_option($einpost->option_name);
 $o_zaehler = $meineOption['zaehler'];
 $o_titel = $meineOption['titel'];
 $o_id = $meineOption['id'];
 //debug unsortierte Ausgabe
 //echo $o_titel." ".$o_zaehler."<br>";
 
 //Array befüllen
 $rohdaten[$i]['zaehler']= $o_zaehler;
 $rohdaten[$i]['titel']= $o_titel;
 $rohdaten[$i]['id']= $o_id;
 $i=$i+1;
 }

Jetzt wirds ein bisschen tricky. Ich hab mir folgende Lösung ergooglet:

Array als Parameter für den multisort

Unsere Daten stecken jetzt in dem Array $rohdaten. Jetzt brauchen wir aber noch ein zweites Array, um den array_multisort (s. PHP-Handbuch) damit zu bedienen, und zwar muss dieses die Spalte enthalten, nach der sortiert werden soll. Das Ganze sieht dann so aus:

$counter = array();
foreach ($rohdaten as $key => $row)
{
 $counter[$key] = $row['zaehler'];
}
array_multisort($counter, SORT_DESC, $rohdaten);

Damit werden die Rohdaten nach dem Zähler sortiert, und zwar DESC also absteigend. Jetzt können wir die ganze Chose als Hitparade ausgeben:

foreach($rohdaten as $roh){
 
 echo $roh['titel']." (".$roh['zaehler'].")<br>";
 
 }

Das sieht jetzt schon ganz gut aus.

hitparade

hitparade

Natürlich kann man auch hier anhand der Rezept-IDs einen Link zum Rezept mit dem get_permalink einbauen, aber das spare ich mir jetzt. Wenn man nur die ersten X Rezepte ausgeben möchte, den SQL um ein LIMIT X ergänzen. Achtung, Denkfehler! Das klappt natürlich nicht, da der Select die Daten nicht vorsortiert ausgeben kann. Da muss man dann nur die ersten X Zeilen des Arrays ausgeben, sonst wird das nix. Ich pack das Ding doch noch in ein Widget mit Benutzereingabe für die X auszugebenden Rezepte! Kurze Lösung: man verwendet ein array_slice:

$hilf = array_slice($rohdaten, 0, 3);
foreach($hilf as $roh){
        
            echo $roh['titel']." (".$roh['zaehler'].")<br>";
            
            }

Ich finde den Aufwand zum Auswerten der serialisierten Daten schon recht kopflastig, das geht über die eigene Tabelle wesentlich komfortabler. Na ja, kann jeder selber entscheiden, welche Lösung er einsetzen möchte.

Noch eine Anmerkung am Schluss

Ich bin gefragt worden, warum ich den Code für das Wegschreiben des Rezeptezählers nicht in einen Filter gepackt habe, sondern als Shortcode in die single.php eingefügt habe. Das hat einen guten Grund: ich habe mit komplexeren Filtern bei WordPress schon schlechte Erfahrungen gemacht, insbesondre bei eingebauten Datenbankabfragen kann es da zu kuriosen Nebenwirkungen kommen. Jedenfalls ist der Shortcode-Aufruf in der single.php eine unkomplizierte Lösung und liefert korrekte Ergebnisse, damit kann ich ganz gut leben.