Archiv der Kategorie: PHP

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.

 

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!

 

 

Alphabetische Paginierung aus Array: die Tücken des PHP-sort()

Eigentlich hats mich ja nur interessiert, ob das recht aufwendig ist, aber das hielt sich in Grenzen, deswegen mach ich es noch der Vollständigkeit halber. Voraussetzung ist wie im letzten Beitrag, dass die auszugebenden Daten in Form eines Arrays vorliegen, ich nehme wieder meine 334 Kochrezepte als JSON, so wie im letzten Beitrag zur nummerischen Pagination. Nach dem json_decode sieht die Struktur des Arrays wie gehabt wie folgt aus:

array(1) 
{ ["rezepte"]=> array(334) {
 [0]=> array(3) { ["ID"]=> string(4) "1828" ["post_title"]=> string(31) "Pudding nach Art des Hauses Leu" ["post_content"]=> string(37) "Vanille oder Schoko oder Johannisbeer" }
 [1]=> array(3) { ["ID"]=> string(4) "1827" ["post_title"]=> string(5) "Milch" ["post_content"]=> string(23) "reicht nicht bis morgen" } 
 [2]=> array(3) { ["ID"]=> string(4) "1825" ["post_title"]=> string(14) "Petersilwurzel" ["post_content"]=> string(29) "hab ich abgeerntet, ist nicht" }

Jetzt interessieren mich aber eigentlich nur die post_title, die krieg ich mit:

$akt_titel = $dataObject['rezepte'][$i]['post_title'];

Und die Titel der Rezepte möchte ich nach Buchstaben seitenweise sortiert ausgeben. Also, pack’mas.

Das Formular mit den Buttons

Dafür fülle ich mir erst einmal ein Array mit den Buchstaben a-z, daraus baue ich dann das Formular:

//Buchstaben a-z in Array schreiben
$letters = array();
for ($i = 'a', $j = 1; $j <= 26; $i++, $j++) {
    $letters[$j] = $i;    
}
//Formular mit Buttons a-z erzeugen
echo "<form action = '#' method = 'post'>";

for ($i=1; $i <=26; $i++){
    echo "<input type='submit' id='el_button' name='".$letters[$i]."' value='".$letters[$i]."'>";
}
    echo "</form>";

Das sieht jetzt erstmal so aus:

alfa_buttons

alfa_buttons

Dann ermittle ich, welcher Buchstabe angeklickt wurde, und rufe mit diesem Buchstaben als Parameter meine Ausgabefunktion auf:

//Ermitteln welcher Buchstabe angeklickt wurde und Ausgabefunktion aufrufen
for ($j = 1; $j <= 26; $j++){    
        if (isset($_POST[''.$letters[$j].''])){
            
            return el_aufruf("".$letters[$j]."");
        }
        
    }

In der Ausgabefunktion hole ich mir mein Array mit allen Daten, steppe es ganz durch und picke mir mit dem if nur die Einträge heraus, wo der post_title mit dem als Parameter übergebenen Buchstaben anfängt. Diese werden mit array_push einer nach dem anderen an mein Hilfsarray $ausgabe angehängt.

function el_aufruf($stabe){
            
    //JSON alle Rezepte abholen und in Array umwandeln
    $json = file_get_contents('http://localhost/kleine_api/rezepte/rezeptelesen.php');
    $dataObject = json_decode($json,true);
    $anzahl = count($dataObject['rezepte']);
    echo "Rezepte insgesamt = ".$anzahl."<br>";
            
    //Hilfsarray für Ausgabe     
    $ausgabe = array();
        
        for ($i=0; $i<$anzahl; $i++){
            //Ersten Buchstaben vergleichen
            if (strtoupper(substr(($dataObject['rezepte'][$i]['post_title']), 0, 1))== strtoupper($stabe)){
                //Hilfsarray mit Titeln zum aktuellen Buchstaben füllen
                array_push($ausgabe, $dataObject['rezepte'][$i]['post_title']);
            }
        }

Das Hilfsarray sortiere ich mir noch mit dem einfachen sort() und gebe es zeilenweise aus:

//Array sortieren und ausgeben
    sort($ausgabe);
    foreach($ausgabe as $out){
        echo $out."<br>";
    }
} //end function el_aufruf

Die Ausgabe sieht dann beispielsweise so aus:

buchstabe_c

buchstabe_c

Kleiner Pferdefuß: die Umlauts und Sonderzeichen

Der PHP sort() ist nicht dazu zu bewegen, die deutschen Umlaute anderswo als am Ende einer Liste nach dem Buchstaben Z einzusortieren. Auch Sonderzeichen wie z.B. mit Accents (Béchamel) landen gnadenlos am Ende. Das sieht man ganz deutlich am Ende der Liste zum Buchstaben B:

buchstabe_b

buchstabe_b

Da müsste man jetzt hingehen und eine eigene sort-Funktion schreiben, die aus den deutschen Umlauts Diphtonge mach (ä=ae, ö=oe…) und erst danach sortieren, vor der Ausgabe aber die Umlaute wieder einsetzen. Ach nö Leute, das ist mir echt zu stressig. Ich lebe mal mit der Ausgabe der Umlaute am Ende, vielleicht begegnet mir ja beim Googlen noch irgendwo eine elegantere Lösung.

Und prompt hab ich was gefunden: Sortierung der Umlauts wie im Telefonbuch

Auf der Webseite von Marco Krings habe ich folgende interessante Sortierfunktion gefunden:

http://www.marcokrings.de/arrays-sortieren-mit-umlauten/

Er arbeitet mit zwei Arrays für die Umlaute und die entsprechenden Diphtonge. Diese Arrays könnte man jetzt noch um die Accent-Zeichen wie z.B. é ergänzen, ich hab das mal ausprobiert:

$aSearch   = array("Ä","ä","Ö","ö","Ü","ü","ß","-","é");
    $aReplace  = array("Ae","ae","Oe","oe","Ue","ue","ss"," ","e");

Funktioniert eins a!

Meine Ausgabe wird jetzt noch um Marcos Funktion ArraySort() erweitert:

//Array sortieren und ausgeben
    //sort($ausgabe);
    $umlauts = ArraySort($ausgabe);
    foreach($umlauts as $out){
        echo $out."<br>";
    }

Jetzt paßt die Sache. die Umlaute und das é werden richtig einsortiert:

e_accent

e_accent

Das ist ausbaufähig! Vielen Dank an Marco für die geniale Vorlage.

Nachtrag: woher nehm ich jetzt die ID?

Ich habe in meiner alfabetischen Ausgabe bis jetzt nur die Titel der Rezepte verarbeitet, das reicht aber unter Umständen nicht. Für manche Zwecke wäre es schick, wenn man auch noch die ID mitnehmen könnte, zum Beispiel in WordPress für die Ausgabe des Permalinks. Man könnte jetzt auf die Idee kommen, in der for-Schleife den array_push umzustricken, so dass ein mehrdimensionales Array mit Titel und ID aufgebaut wird… ja Pustekuchen, dann fällt unser schöner Sortieralgorithmus auf die Nase, das geht leider nicht. Ich hab mir da eine quick&dirty-Lösung einfallen lassen: ich hänge die ID einfach an den Titel mit dran, und setze dazwischen als Trennzeichen ein #. Kann ich so machen, weil in meinen Rezepttiteln das Zeichen # sonst garantiert nirgendwo vorkommt. Die for-Schleife sieht dann so aus:

for ($i=0; $i<$anzahl; $i++){
  //Ersten Buchstaben vergleichen
if (strtoupper(substr(($dataObject['rezepte'][$i]['post_title']), 0, 1))== strtoupper($stabe)){
//Hilfsarray mit Titeln und ID zum aktuellen Buchstaben füllen (Trennzeichen#)
array_push($ausgabe, $dataObject['rezepte'][$i]['post_title']."#".$dataObject['rezepte'][$i]['ID']);
   }
}

Dann sieht die Ausgabe erstmal so aus:

id_angehaengt

id_angehaengt

Das zerpflücke ich mir jetzt noch in die Teilstrings vor und nach dem #, gebe nur den Titel aus und kann dann mit der ID noch was anderes anfangen. Die Ausgabeschleife sieht dann so aus:

foreach($umlauts as $out){
        
        //alle Zeichen vor dem # = Titel
        $hilf_titel = substr($out, 0, strpos($out, '#', 0));
        echo $hilf_titel."<br>";
        
        //alle Zeichen nach dem # = ID
        $hilf_id = substr(strrchr($out, "#"), 1);
        //hier kann man mit der ID weiterarbeiten
        }

Wie gesagt, das ist ein wenig quick&dirty, aber es funktioniert, und ich kann den schicken Sortieralgorithmus von Marco einwandfrei weiterverwenden.

Nummerische Pagination mit Werten aus Array (pure PHP)

Das hat jetzt mit WordPress nur noch insofern etwas zu tun, als es einem auch hier mal unterkommen kann, dass die Daten für eine seitenweise nummerierte Ausgabe in Form eines Arrays ankommen und nicht direkt mit SQL aus der Datenbank abgefragt werden. Typischerweise passiert sowas, wenn man über eine API auf die Datenbank zugreift und die Ergebnisse der Abfrage als JSON zurückgegeben werden. Das kann man mit Hilfe von json_decode() flugs in ein Array umwandeln und mit einem foreach() wie gehabt seitenweise ausgeben. Wenn allerdings die Datensätze pro Seite einen Rest ergeben, fliegt PHP bei der letzten Seite auf die Nase und spuckt so etwas aus:

Notice: Undefined offset: 334 in C:\xampp\htdocs\kleine_api\err_num_page.php on line 74

Die kleine Tücke: der Rest

Wir haben, sagen wir mal, 334 Datensätze. Und wir legen die Anzahl der Datensätze pro Seite auf 10 fest. Das gibt 33 ganze Seiten a 10 Datensätze, und auf der letzten Seite haben wir nur noch vier Datensätze. Da muss man jetzt eine Sonderbehandlung für den Rest einbauen, weil unser Array ja nur einen Index bis 333 zuläßt – Offset by one, den 334. Array-Entrag gibt es nicht mehr. Dieses Dilemma hat man nicht, wenn man direkt auf die Datenbank zugreift und mit einem SQL-Limit arbeiten kann, da ist es egal wenn am Ende die Seite nicht ganz voll wird, es gibt keine Fehlermeldung.

Aber, wir arbeiten ja mit einem Array, und da muss man die ganze Logik ein bisschen umstricken. Also, frisch ans Werk!

Das Array

… enthält 334 Kochrezepte und sieht nach einem json_decode in etwa so aus:

array(1) 
{ ["rezepte"]=> array(334) {
 [0]=> array(3) { ["ID"]=> string(4) "1828" ["post_title"]=> string(31) "Pudding nach Art des Hauses Leu" ["post_content"]=> string(37) "Vanille oder Schoko oder Johannisbeer" }
 [1]=> array(3) { ["ID"]=> string(4) "1827" ["post_title"]=> string(5) "Milch" ["post_content"]=> string(23) "reicht nicht bis morgen" } 
 [2]=> array(3) { ["ID"]=> string(4) "1825" ["post_title"]=> string(14) "Petersilwurzel" ["post_content"]=> string(29) "hab ich abgeerntet, ist nicht" }

Das heißt, um beispielsweise an die ID und den Titel eines Datensatzes heranzukommen, muss ich folgende Syntax verwenden:

$akt_id = $dataObject['rezepte'][$i]['ID'];
$akt_titel = $dataObject['rezepte'][$i]['post_title'];

Das ist jetzt noch nicht weiter tragisch, ich muss nur daran denken dass mein Array bei 0 anfängt, das heißt bei einer Anzahl Datensätze pro Seite von X darf ich nur von 0 bis X-1 ausgeben. Praktisch sieht das z.B.bei 10 Datensätzen pro Seite so aus:

Seite 1: Datensätze 0…9
Seite 2: Datensätze 10…19
Seite 3: Datensätze 20…29

letzte Seite: der Rest

Das heisst aber, ich muss für meine ganzen Seiten eine andere Logik anwenden als für die letzte Seite, auf der auch weniger als 10 Datensätze stehen können.

Aber fangen wir erstmal vorne an:

Ermitteln der Seitenzahl und des Restes

Mein Array mit den 334 Datensätzen heißt $dataObject und wurde aus einem JSON in ein Array umgewandelt. Jetzt rechne ich erstmal aus, wieviele ganze Seiten das ergibt, wenn ich hier beispielsweise 10 Datensätze pro Seite ausgebe, und welcher Rest bleibt.

<?php

//JSON alle Rezepte abholen und in Array umwandeln
$json = file_get_contents('http://localhost/kleine_api/rezepte/rezeptelesen.php');
$dataObject = json_decode($json,true);

//Rezepte pro Seite ausrechnen
$pro_seite = 10; //Wert kann beliebig geändert werden
$anzahl = count($dataObject['rezepte']);
echo "Datens&auml;tze gesamt = ".$anzahl."<br>";
echo "Das macht ".floor($anzahl/$pro_seite)." ganze Seiten
 bei ".$pro_seite." Rezepten pro Seite, Rest: ".$anzahl%$pro_seite."<br>";

Ich habe mit Absicht eine sehr geschwätzige echo-Ausgabe eingebaut, zur besseren Kontrolle. Der Output sieht jetzt mal so aus:

echo_kennzahlen

echo_kennzahlen

Damit kann ich mir jetzt schon mal die Buttons für die einzelnen Seiten zusammenbauen. Das mache ich wieder mit einem Formular. Ich zähle mein i von Seite 1 bis zur letzten Seite inklusive hoch, als Value kriegt jeder Button den aktuellen Wert von i. Ausserdem kriegt jeder Button noch eine ID, die brauchen wir später noch.

//Formular mit Buttons für die Seitenzahlen, Start 1, Ende: nächsthöhere Ganzzahl
for ($i=1; $i <=ceil($anzahl/$pro_seite); $i++){
    echo "<form action = 'array_num_page.php' method = 'get'>";
    echo "<input type='submit' id='el_num_button_".$i."' name=akt_i value='".$i."'>";
}
echo "</form>";

Das sieht jetzt schonmal so aus:

buttons

buttons

Jetzt gehts daran zu ermitteln, welche Seite gerade aktuell ist. Ich starte als Default mit der 1, und je nachdem welchen Button der Anwender angeklickt hat hole ich mir den Wert aus dem Formular.

//Checken ob eine Seite geklickt wurde
$seitenstart=1;
if (isset($_GET['akt_i'])){
    $seitenstart = $_GET['akt_i'];
    }else{
    $seitenstart=1;
    }
//versteckte Ausgabe für farbige Markierung der aktuellen Seite mit Script    
echo "<div id ='seitenstart' style='display:none;'>".$seitenstart."</div>";

Meinen Seitenstart schreibe ich mir noch in eine versteckte Div, die brauche ich nachher für die farbige Markierung der aktuellen Seite.

Jetzt gehts erst mal an die Ausgabe.

Die ganzen Seiten

Hier darf ich nur bis zur letzten ganzen Seite hochzählen, die letzte Seite mit den weniger Datensätzen kommt später dran:

//Ausgabe aller ganzen Seiten von 1 bis zum letzten ganzzahligen Vielfachen
if (($seitenstart > 0) and ($seitenstart < floor($anzahl/$pro_seite))){

//Tabelle für Ausgabe aller Rezepte zusammenbauen
echo "<table border : 1px>";
echo "<th>ID</th><th>Titel</th><th>Content</th> 
/*Ausgabe als Tabelle*/

Jetzt wird es ein bisschen knifflig. Ich bleibe mal bei einem Wert pro Seite von 10 Datensätzen, es klappt aber auch mit jedem anderen Wert.

Auf Seite eins muss ich bei 0 anfangen und bis eins weniger als die Anzahl der Datensätze pro Seite hochzählen, also bis 9. Auf Seite zwei muss ich bei 10 anfangen und bis 19 hochzählen, usw.:

//Bei 0 anfangen, bis max. eins weniger als die Anzahl der ganzen Seiten hochzählen (0..9, 10..19 usw...)
for ($i=($seitenstart-1)*$pro_seite; $i <= $pro_seite*$seitenstart-1; $i++){

Die ganze for-Schleife mit der Ausgabe der einzelnen Werte aus dem Array in Tabellenzeilen sieht so aus:

//Bei 0 anfangen, bis max. eins weniger als die Anzahl der ganzen Seiten hochzählen (0..9, 10..19 usw...)
for ($i=($seitenstart-1)*$pro_seite; $i <= $pro_seite*$seitenstart-1; $i++){

echo "<tr>";
//Werte aus dem Array auf Var. legen
$akt_id = $dataObject['rezepte'][$i]['ID'];
$akt_titel = $dataObject['rezepte'][$i]['post_title'];
//Content tags raus und auf 100 Zeichen kürzen
$akt_content = substr(strip_tags($dataObject['rezepte'][$i]['post_content']),0,200);

echo "<td>".$akt_id."</td><td>".$akt_titel."</td><td>".$akt_content."</td>";
echo "</tr>";
}

Damit haben wir die Ausgabe aller ganzen Seiten erledigt. Jetzt kümmern wir uns um den Rest. Da nehmen wir nur noch die Indizes aus dem Array, die grösser als der letzte Wert vor dem letzten ganzzahligen Vielfachen der Seitenzahl sind, und marschieren nach oben durch bis 1 vor der Gesamtzahl der Datensätze.

//****Ausgabe Rest wenn Seitenzahl grösser als das letzte ganzzahlige Vielfache ist
if($seitenstart > floor($anzahl/$pro_seite)){
...

//starten beim letzten ganzzahligen Vielfachen der Gesamt-Seitenzahl, 
//stoppen eins unter Gesamtzahl
for ($i=floor($anzahl/$pro_seite)*$pro_seite; $i < $anzahl; $i++){

Voila, das wars! Der ganze Code mit Ausgabe der Tabelle sieht so aus:

//**************************Ausgabe Rest wenn Seitenzahl grösser als das letzte ganzzahlige Vielfache ist
if($seitenstart > floor($anzahl/$pro_seite)){
    

//Tabelle für Ausgabe des Rests zusammenbauen
echo "<table border : 1px>";
echo "<th>ID</th><th>Titel</th><th>Content</th>";

//starten beim letzten ganzzahligen Vielfachen der Gesamt-Seitenzahl, stoppen eins unter Gesamtzahl
for ($i=floor($anzahl/$pro_seite)*$pro_seite; $i < $anzahl; $i++){

echo "<tr>";
//Werte aus dem Array auf Var. legen
$akt_id = $dataObject['rezepte'][$i]['ID'];
$akt_titel = $dataObject['rezepte'][$i]['post_title'];
//Content tags raus und auf 100 Zeichen kürzen
$akt_content = substr(strip_tags($dataObject['rezepte'][$i]['post_content']),0,200);

echo "<td>".$akt_id."</td><td>".$akt_titel."</td><td>".$akt_content."</td>";
echo "</tr>";
}
echo "</table>";
    
}

Noch ein Javascript-Zuckerl: aktuelle Seite farbig markieren

Ich hab mir ja zu jedem Seitenzahl-Button eine ID generiert, das sah so aus:

echo "<input type='submit' id='el_num_button_".$i."' name=akt_i value='".$i."'>";

Und ich habe mir den Wert der aktuellen Seite (default 1) in eine versteckte div geschrieben:

//versteckte Ausgabe für farbige Markierung der aktuellen Seite mit Script    
echo "<div id ='seitenstart' style='display:none;'>".$seitenstart."</div>";

Damit habe ich mir ein kleines Javascripterl gebastelt, das die aktuelle Seite farbig markiert:

<script>

    $(document).ready(function() {
    var dieid = document.getElementById('seitenstart').innerHTML;
    //alert(dieid);
    document.getElementById('el_num_button_'+dieid).style.backgroundColor = "lightgreen";
    }); 


</script>

Ich klaube mir die aktuelle Seitenzahl aus der verstecken Div und spreche damit die zugehörige Button-ID an, die style ich mir nach Belieben, das war auch schon alles. Hübscher kleiner Effekt!

seite_farbig

seite_farbig

 

Ne kleine Fingerübung: Beitragshitparade mit AJAX-Refresh

Weil ich mich dabei erwischt habe, dass ich immer wieder F5 drücke, wenn ich meine Beitragshitparade anschaue, bin ich auf die Idee gekommen, den Refresh der Anzeige alle x Sekunden automatisch einzubauen. Das geht in WordPress ganz flott, hatten wir auch alles so ähnlich schonmal, aber weil man so etwas immer wieder mal brauchen kann, hier der Code.

Wir basteln uns dafür natürlich ein Plugin

In das kommt zuallererst ein sehr kurzer Shortcode, der macht eigentlich nichts anderes als eine benannte Div  für die Ausgabe bereitzustellen.

<?php
/*
Plugin Name: Ajax Hitparade
Plugin URI: http://localhost/wp_ajax/wp-content/plugins/wp-ajax
Description: Ausgabe der beliebtesten Beiträge mit Ajax Refresh
Version: 1.0
Author: Evi Leu
Author URI: http://www.evileu.de
*/


 function a_ausgabe(){
  return "Die 10 beliebtesten Rezepte<br><div id='ajax_ausgabe'>...</div>";
  
 }
 add_shortcode('ajax_ausgabe', 'a_ausgabe');

Dann geben wir WordPress bekannt, dass wir einen Ajax-Call ausführen wollen:

/**************ajax-action für wordpress definieren*/ 
add_action( 'wp_ajax_hitparade_action', 'hitparade_action' );
add_action( 'wp_ajax_nopriv_hitparade_action', 'hitparade_action' );


Das Javascript

Jetzt binden wir unsere js-Datei mit dem Script ein, und geben ihr mit dem wp_localize_script den Pfad zur Ajax-URL von WordPress mit:

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

Die Script-Datei wp_ajax_hitparade.js liegt in unserem Plugin-Verzeichnis und sieht so aus:

$(document).ready(function() {
     setInterval(myFunction, 1000)  
       
    });
    
        
        function myFunction(){
                
        
        var data = {
            'action': 'hitparade_action',
            
            };

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

Der Witz ist hier natürlich das setInterval(), das führt unsere Funktion alle X Millisekunden aus. Ansonsten geben wir der Funktion nur mit, dass sie die Callback-Funktion hitparade_action ausführen soll, es gibt keine zu übergebenden Parameter ausser der Ajax-URL. Das Ergebnis des Calls wird in die div ajax_ausgabe geschrieben.

Die Callback-Funktion

Kommt als Letztes in unser Plugin, sie ist sehr simpel und besteht nur aus einer Query auf die Tabelle Counter und einem foreach für die Ausgabe:

/********Callback-Funktion**************/
function hitparade_action() {
    global $wpdb; 
  
    $alleposts = $wpdb->get_results( "SELECT * from counter ORDER BY zaehler DESC limit 10");
    foreach($alleposts as $einpost){
        
        echo $einpost->titel." ".$einpost->zaehler."</br>";
}
    /**********************************************/
    
    
    wp_die(); // this is required to terminate immediately and return a proper response
} //**********End function hitparade_action

Das sollts gewesen sein! Das Script checkt alle 1000 Millisekunden die Datenbank neu ab und aktualisiert die Anzeige dementsprechend. Viel Spaß beim Nachbauen!

PHP 5.6 forever? Und kleine Auswertung des Rezeptecounters

Also, ich geb zu das hat mit WordPress nur am Rande was zu tun, aber ich denke mir dass andere Leute vor dem selben Dilemma stehen und werde deswegen ein paar Worte zu dem Thema verlieren.

Man ist ja mittlerweile bei PHP 7.x angelangt, und es hat sich seit den 5er Versionen einiges getan, besonders in Sachen Datenbankzugriff. PDO und mysqli hat mysql abgelöst, und wir sind nicht abwärtskompatibel. Das würde mir noch nicht mal viel ausmachen, weil ich auf meiner evileu.de kaum PHP-Skripte für den Datenbankzugriff benutze, das läuft eigentlich alles über WordPress. Aber an manchen Stellen kneifts dann doch, und besonders für meine kleinen SEO-Statistiken hätt ich dann doch das eine oder andere PHP-Script gebraucht. Lokal (XAMPP) arbeite ich schon lang mit PHP 7 und hab eigentlich gar nicht mehr darüber nachgedacht ob ich die 5er Version nochmal brauchen könnte.

Aber die Live-Seite evileu de läuft bei meinem Provider (Strato) noch auf PHP 5.6, und das wird wohl auch so bleiben. Ein Anruf bei der Hotline hat ergeben, daß von einer Umstellung auf PHP 7.x dringendst abgeraten wird, weil es keine Garantie dafür gibt, dass vorhandene Webseiten dann noch laufen. Der Hotliner meinte sogar, dass in den meisten Fällen die Webseiten neu aufgesetzt werden müssten… Vielleicht bin ich da auch nur übervorsichtig, WordPress sollte ja eigentlich mit den neuesten PHP-Versionen klarkommen, aber „der Teufel ist ein Oachkatzl“ – weiss man’s genau?

Jedenfalls rät Strato von einer Umstellung dringend ab, und ich bekam auch die Auskunft dass bei denen noch 99,9% aller Webseiten auf PHP 5.6 laufen. Tscha, da werde ich mich wohl der Mehrheit anschliessen, schon auch weil es bei Umstellungsproblemen der PHP-Version von Strato keinen Support gibt, jedenfalls nicht an der kostenfreien Hotline. Bin bloss gespannt wie lange WordPress die Grätsche noch mitmacht – irgendwann wird es eine Version geben, die nur noch auf PHP 7.x läuft, und dann wirds echt spannend.

Was mach ich bis dahin? Plugins und Shortcodes statt PHP-Scripte nutzen, wo immer es möglich ist, weil ich beim Programmieren Knoten in die Tippfinger kriege, wenn ich ständig zwischen PHP 5 und 7 wechseln muss. Gefallen tut mir das nicht, aber ich hab ja etwa ein Dutzend WordPress-Blogs am Laufen, und da will ich nicht das geringste Risiko eingehen. Also, PHP 5.6 forever – obs mir gefällt oder nicht.

PHP-5-Script für die Auswertung des Rezeptecounters

Für alle, die es auch nicht mehr parat haben, wie das mit dem mysql_connect damals so ging, hier ein kleines Script zur Auswertung der counter-Tabelle (siehe dieser Beitrag zum Rezeptecounter) Die Tabelle ist ja recht einfach aufgebaut:

tabelle_counter

tabelle_counter

Fehlte eigentlich nur noch die Aufsummierung der Gesamtzahl der Aufrufe, das erledigen wir hier gleich in einem Aufwasch:

$dbh = mysql_connect("[hostname]", "[db user]", "[password]");
$query = "[db name]";
if (!mysql_query($query, $dbh)) die("Fehler bei der Datenbankverbindung.");

//Gesamtaufrufe summieren***********************
$abfrage = "select sum(zaehler) as summe from counter";
$ergebnis = mysql_query($abfrage,$dbh);

while($row = mysql_fetch_assoc($ergebnis))
{
echo "<h2>Seitenaufrufe seit 20.6.2018: ".$row['summe']."</h2>";
}

//*********************

$sql = "SELECT * FROM counter ORDER BY zaehler DESC";

$result = mysql_query($sql);

while ($row = mysql_fetch_assoc($result)) {
    echo $row["cid"]." ";
    echo $row["rezeptid"]." ";
    echo $row["titel"]." ";
    echo "Z&auml;hler: ".$row["zaehler"]."<br>";
}

mysql_free_result($result);
?>

Das liefert einen ganz netten Überblick, hier die Zahlen von hier im Bistro seit gestern:

ausgabe_rezeptecounter

ausgabe_rezeptecounter

Ich gebe zu man könnte es schöner formatieren, aber mir langt das für einen groben Überblick. Ist echt eine Alternative zum Visitor Counter, der ja leider wegen der DSGVO weg musste.

Der Shortcode für die Rezepthitparade

… musste auch nur um zwei Zeilen ergänzt werden, damit er die Gesamtzahl der Aufrufe mit ausgibt:

function el_rezepthitparade(){
    global $wpdb;
    echo "<h2>Die 10 beliebtesten Rezepte</h2>";
    
    $gesamt = $wpdb->get_var("SELECT SUM(zaehler) FROM counter");
    echo $gesamt." Rezeptaufrufe seit Beginn der Zählung Juni 2018<br>";
    
    $alleposts = $wpdb->get_results( "SELECT * FROM counter 
    ORDER BY zaehler DESC LIMIT 10");
    foreach ($alleposts as $einpost){
        
        $pfad = get_the_permalink($einpost->rezeptid);
        echo "<a href = '".$pfad."'>".$einpost->titel."</a> (".$einpost->zaehler.")<br>";
    }
}
add_shortcode('hitparade','el_rezepthitparade');

Den get_var kann man hier einwandfrei nutzen, weil nur ein Wert von der Query zurückgegeben wird, den kann man dann auch gleich direkt mit echo ausgeben.

Nachtrag für Zählung der Seitenaufrufe

Mein Rezeptecounter ist schon fleissig im Einsatz, und ich bin mit den aussagekräftigen Ergebnissen sehr zufrieden. Nur noch eine kleine Ergänzung: in meinem Handarbeitsblog ist es für mich auch interessant zu wissen, welche statischen Seiten wie oft aufgerufen werden. Die kommen mit einer ganz kleinen Ergänzung der Zähler-Function mit in die counter-Tabelle. Statt nur if (is_single())… frage ich ab if(is:single() or is_page()), und schon sind die statischen Seiten auch mit in der Statistik.

Noch’n Nachschlag: Aufrufe pro Tag

Ich hab in meinem Archiv gekramt und eine Funktion aus dem Selfphp-Forum ausgebuddelt, mit der man die exakte Anzahl von Tagen zwischen zwei gegebenen Tagesdaten ermitteln kann. Die nutzen wir doch gleich mal um einen Tagesdurchschnitt auszurechnen. Das Startdatum der Zählung wird einfach fest eingegeben, das Enddatum (aktueller Tag) mit der date()-Funktion erzeugt, die Gesamtzahl der Aufrufe haben wir ja aus der Query, die liegt auf der Variablen $gesamt.

$gesamt = $row['summe'];
$timeA = '20.06.2018'; 
 $timeB = date("d.m.Y"); 

 $differenz = seDay($timeA,$timeB,"dmY","."); 
 
echo '<h3>In ' . $differenz . ' Tagen '.$gesamt.
" Aufrufe, das macht im Durchschnitt ".round(($gesamt/$differenz))." Aufrufe pro Tag</h3>";

Die Funktion seDay könnt ihr hier nachschlagen:  http://www.selfphp.de/kochbuch/kochbuch.php?code=11

Viel Spaß beim Nachbauen!

WordPress Beiträge: einfache nummerische Pagination

Dies ist als Ableger des vorigen Beitrags entstanden, eine simple nummerische Pagination der Rezepte ist auch nicht wesentlich schwieriger als die alphabetische Pagination und kann ganz ähnlich konstruiert werden. Ich mache das wieder mit einem Shortcode in einem Plugin. Zuerst wird die Anzahl der pro Seite auszugebenden Rezepte festgelegt und ermittelt, wie viele Seiten das werden:

function el_num_pagination(){

//Anzahl Rezepte pro Seite festlegen
$pro_seite = 15;
    
//Anzahl aller veröffentlichten Rezepte bestimmen
global $wpdb;

         $alleposts = $wpdb->get_results( "SELECT * from wp_posts 
         where post_type like 'post' and post_status like 'publish'");
        
        $gefunden = $wpdb->num_rows;
        echo $gefunden." Rezepte insgesamt<br>";
        
        //ceil: nächsthöhere Ganzzahl
        $anzahl_seiten = ceil($gefunden/$pro_seite);
        //Debug-Ausgabe
        echo "Das sind: ".$anzahl_seiten." Seiten bei ".$pro_seite." Rezepten pro Seite";

Dann baue ich mir das Formular mit den Buttons für die Seitenzahlen zusammen:

//Formular mit Buttons
echo "<form action = '#' method = 'post'>";

for ($i=1; $i <=$anzahl_seiten; $i++){
    echo "<input type='submit' id='el_num_button' name='".$i."' value='".$i."'>";
}
echo "</form>";

Die Buttons werden in der style.css noch ein bisschen hübscher formatiert, die müssen nur ein wenig breiter werden:

#el_num_button{
    height:30px;
    width:24px;
    padding:2px;
    border: 2px solid white;
    margin 2px;
    padding: 1px 1px 1px;
}

Dann laufe ich wieder durch alle Seitenzahlen durch und frage mit dem if(isset()) ab, ob eine Seitenzahl angeklickt wurde. Falls ja, wird die Ausgabefunktion mit zwei Parametern aufgerufen, der Nummer der aktuellen Seite und der Anzahl der Rezepte pro Seite:

for ($j = 1; $j <= $anzahl_seiten; $j++){    
        if (isset($_POST[''.$j.''])){
            
            return el_num_aufruf($j,$pro_seite);
        }
    }

In der Ausgabefunktion nutze ich die Tatsache, dass man dem SQL LIMIT einen Offset mitgeben kann. Dieser muss bei 0 (Null) auf der Seite 1 anfangen, auf Seite 2 ist er dann einmal die Anzahl der Beiträge pro Seite, auf Seite drei zweimal etc… deswegen die Konstruktion mit dem $akt_seite-1:

function el_num_aufruf($akt_seite, $aufderseite){
    
        
    global $wpdb;
        
        //Limit Offset eins weniger als aktuelle Seite
        $hilf = ($akt_seite-1)*$aufderseite;
        
        $alleposts = $wpdb->get_results( "SELECT * from wp_posts 
         where post_type like 'post' 
         and post_status like 'publish'
         LIMIT ".$hilf.", ".$aufderseite." ");
        $gefunden = $wpdb->num_rows;
        
        foreach ($alleposts as $einpost){
            
            $pfad = get_the_permalink($einpost->ID);
            echo "<a href = '".$pfad."'>".$einpost->post_title."</a><br>";
            
        }
    
} //end function el_num_aufruf

Am Ende gebe ich die Rezepte mit den Links wieder mit einem Foreach aus. Das wars!

15_pro_seite

15_pro_seite

Das funktioniert auch mit anderen Werten für die Ausgabe pro Seite, ich nehm mal 7:

7_pro_seite

7_pro_seite

Oder wesentlich mehr, 30, da sind wir komplett flexibel:

30_pro_seite

30_pro_seite

Man könnte jetzt natürlich noch ein Auswahlfeld für den Benutzer einbauen, so dass er selbst wählen kann wieviele Rezepte pro Seite er angezeigt haben möchte, aber damit kann sich jeder selber amüsieren. Ich werde eh die alfabetische Pagination nehmen, die taugt mir besser für meine Anwender.

WordPress Beiträge: alphabetische Pagination ganz spartanisch

Ich habe mich in einem kleinen PHP-Projekt kürzlich ausführlich mit seitenweiser und alfabetischer Pagination bei der Ausgabe von Datenbankabfragen beschäftigt, und bin dabei auf eine Idee gekommen, wie man das in WordPress relativ einfach umsetzen kann. Ich geh mal wieder auf meine Rezepte los, das sind über 300 Stück, da ist das alte Inhaltsverzeichnis doch inzwischen ein wenig lang.

Die Aufgabenstellung

Ich möchte eine Anzeige aller Buchstaben A-Z, und wenn man auf einen Buchstaben klickt, soll eine Liste aller Rezepte ausgegeben werden, die mit diesem Buchstaben anfangen. Klingt simpel, ist auch nicht arg schwierig unzusetzen. Ich mache ein Plugin daraus, und packe die ganze Sache in einen Shortcode.

Die Anzeige: ich nehme ein Formular

Dafür konstruiere ich mir ein Hilfs-Array, in das erstmal alle Buchstaben von a-z reinkommen. Groß/Kleinschreibung ist uninteressant, da MySQL-LIKE nicht case sensitive ist. Das Formular sieht erstmal so aus:

//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' id='el_button' name='".$letters[$i]."' value='".$letters[$i]."'>";
}
    echo "</form>";

Ich steppe durch mein Buchstabenarray durch und lege für jeden Buchstaben einen Button mit dem Namen des aktuellen Buchstabens und der id el_button an. Das erzeugt 26 Buttons, die sehen erstmal noch recht häßlich aus:

buttons_ungestylt

buttons_ungestylt

Ein kleiner Eingriff in die style.css des Child Themes verschönert das Ganze beträchtlich.

#el_button{
    height:30px;
    width:18px;
    padding:2px;
    border: 2px solid white;
    margin 2px;
    padding: 1px 1px 1px;
}

Das Ergebnis kann sich schon besser sehen lassen:

buttons_gestylt

buttons_gestylt

Jetzt klemme ich mir wieder mein Buchstabenarray und steppe es wieder von 1 bis 26 durch. Für jeden Buchstaben frage ich mit einem if isset(…) ab, ob der entsprechende Button angeklickt wurde, und rufe falls ja meine Ausgabefunktion auf, die kriegt den aktuellen Buchstaben als Parameter übergeben.

for ($j = 1; $j <= 26; $j++){    
        if (isset($_POST[''.$letters[$j].''])){
            
            return el_aufruf("".$letters[$j]."");
        }
    }

Die Ausgabefunktion

… geht mit dem übergebenen Buchstaben auf die Tabelle wp_posts los und holt mir mit dem post_title LIKE ‚x%‘ alle veröffentlichten Rezepte, die mit diesem Buuchstaben anfangen. Ausgegeben wird die Sache wieder mal mit einem foreach, und ich hole mir gleich noch den Permalink und bastle einen Link zum Rezept daraus.

function el_aufruf($stabe){
    global $wpdb;
         $alleposts = $wpdb->get_results( "SELECT * from wp_posts 
         where post_title like '".$stabe."%' 
         and post_type like 'post' 
         and post_status like 'publish'
         order by post_title");
        $gefunden = $wpdb->num_rows;
        
        echo "<h2>".$gefunden." Rezepte zum Buchstaben ".strtoupper($stabe)."</h2>";
        
        foreach ($alleposts as $einpost){
            
            $pfad = get_the_permalink($einpost->ID);
            echo "<a href = '".$pfad."'>".$einpost->post_title."</a><br>";
            
        }
    
} //end function el_aufruf

Das wars schon! Hier als Beispiel die Ausgabe zum Buchstaben M:

buchstabe_m

buchstabe_m

Kleiner Aufwand, praktisch brauchbares Ergebnis. Wenn man unterschiedliche Post Types hat, könnte man die Ausgabe auch hierauf noch einschränken, das ist vielleicht mal ganz nützlich. Ich hab aber nur die einfachen Posts als Rezepte, das ist für meine Zwecke völlig ausreichend.

Rezeptecounter: so wird ein Widget draus

Wie man in WordPress ein eigenes Widget erstellt, habe ich in diesem Beitrag: Widget-Basteln macht Spaß schon mal ausführlich vorgestellt, deswegen machen wir das hier im Schnelldurchgang. Der Code läßt sich relativ leicht auf die Anforderungen für die Rezepthitparade anpassen. Wir brauchen zwei Eingabefelder, eines für den Titel des Widgets, und eins für die auszugebende Anzahl an Rezepten. In den OpCode des Widgets kommt unsere Ausgabelogik, die können wir 1:1 aus dem vorigen Beitrag übernehmen.

Nur kurz zur Erinnerung: Widgets werden von der Struktur her genau wie Plugins in einer eigenen PHP-Datei angelegt, die kommt auch ins Plugins-Verzeichnis. Hier mal der Code ohne die Ausgabefunktionalität:

<?php
/*
Plugin Name: Hitparaden Widget
Plugin URI: http://evileu.de/wordpress
Description: Gibt die am öftesten aufgerufenen Rezepte aus, Anzahl vom Benutzer wählbar
Author: Evi Leu
Version: 1.0
Author URI: http://evileu.de
*/


class HitparadenWidget extends WP_Widget
{
  function HitparadenWidget()
  {
    $widget_ops = array('classname' => 'HitparadenWidget', 'description' => 'Zeigt die am öftesten aufgerufenen Rezepte an' );
    $this->WP_Widget('HitparadenWidget', 'Hitparade', $widget_ops);
  }
 
  function form($instance)
  {
    $instance = wp_parse_args( (array) $instance, array( 'title' => '' ) );
    $title = $instance['title'];
        
    $instance = wp_parse_args( (array) $instance, array( 'anzahl' => '' ) );
    $anzahl = $instance['anzahl'];
    
    
?>
  <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('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>
  
  
<?php
  }
 
  function update($new_instance, $old_instance)
  {
    $instance = $old_instance;
    $instance['title'] = $new_instance['title'];
    $instance['anzahl'] = $new_instance['anzahl'];
    return $instance;
  }
 
  function widget($args, $instance)
  {
    extract($args, EXTR_SKIP);
 
    echo $before_widget;
    
    $title = empty($instance['title']) ? ' ' : apply_filters('widget_title', $instance['title']);
    $anzahl    = empty( $instance['anzahl'] ) ? '' : esc_attr( $instance['anzahl'] );
   
    // WIDGET CODE GOES HERE
    /**********************/    
    //END WIDGET CODE
    
    echo $after_widget;
  }
 
}
add_action( 'widgets_init', create_function('', 'return register_widget("HitparadenWidget");') );?>

Das ist das Gerippe für das Widget, es werden zwei Eingabefelder erzeugt und deren Werte auf die Variablen $title und $anzahl gelegt. Mit denen kann man weiterarbeiten, hier kommt der Code aus dem vorigen Beitrag nahezu unverändert rein:

// WIDGET CODE GOES HERE
    echo "<h2>".$title."</h2>";
    echo "<h3>Die ".$anzahl." beliebtesten Rezepte</h3>"; 
    
        //Bisher gespeicherte Rezepte holen
    global $wpdb;
        $alleposts = $wpdb->get_results( "SELECT * from wp_options where option_name like 'zaehler_%'");
        
        
        $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;
        }
        //echo "<h2>Hitparade:</h2>";
    
        $counter = array();
        foreach ($rohdaten as $key => $row)
        {
            $counter[$key] = $row['zaehler'];
        }
        array_multisort($counter, SORT_DESC, $rohdaten);


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

    
    //END WIDGET CODE

Ich verwende nur noch den $title für die Überschrift des Widgets und die $anzahl für den array_slice, um die Anzahl der ausgegebenen Datensätze zu begrenzen. Das wars auch schon! Nach erfolgreicher Aktivierung präsentiert sich das Widget so:

hitparadenwidget

hitparadenwidget

Die Ausgabe sieht so aus:

hitparade_ausgabe

hitparade_ausgabe

Funkt, kann man so lassen.

 

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.