Man kann auch in Drupal anzeigen lassen, wie oft eine Seite schon aufgerufen wurde, das ist nur ein bisschen versteckt. Zuerst einmal muss das Statistik-Modul aktiviert werden, das geht unter Module/Statistics. Anhaken, Speichern, dann unter Konfiguration die Option Inhaltsabrufe zählen aktivieren. Dann in die Berechtigungen gehen und falls gewünscht unter Statistics/Inhaltsaufrufe sehen diese Option auch für Gast und authentifizierten Benutzer aktivieren. Das sollte es gewesen sein.
Kurzer Blick auf die Datenbank: Drupal protokolliert jetzt in der Tabelle node_counter mit, welche nid wie oft, wie oft am aktuellen Tag und wann zuletzt aufgerufen wurde:
Die Anzahl der Aufrufe sollte jetzt auch unterhalb jedes Inhalts erscheinen.
Nicht ins Bockshorn jagen lassen, beim ersten Aufruf erscheint hier noch nichts, klassischer Offset-by-one-Fehler. Erst beim zweiten Aufruf der selben Seite taucht der Zähler auf.
Komplettes Zugriffsprotokoll
Man kann auch unter Konfiguration/Statistik das komplette Zugriffsprotokoll aktivieren, aber da sollte man sich auf einer Seite mit einigermassen Traffic auch sicher sein, dass man diese Informationen auch wirklich braucht, sonst bläht das die Tabelle accesslog auf wie nix. Mir reicht die kleine Lösung oben, die in der node_counter protokolliert wird, völlig aus.
Anzeige der beliebtesten Inhalte
Dafür gibts einen fertig konfigurierten View „Beliebte Inhalte“, der aktiviert werden kann, wenn das Statistik-Modul eingeschaltet ist. Das sieht dann so aus:
Ich habe zwar noch nicht rausgefunden, ob und wie man die Anzahl der angezeigten Inhalte steuern kann, aber das ist doch schon mal ganz nett. Man kann sich natürlich auch einen individuellen View basteln, in dem man die Anzahl der anzuzeigenden Inhalte selber konfigurieren kann, die Anzahl der Aufrufe ist über das Feld „Content statistics: Total views“ verfügbar.
Was in WordPress so schön funktioniert hat, sollte doch auch in Drupal zu machen sein: ein Bewertungsformular, das sich beim Abschicken einer neuen Bewertung selbst updated, eine genaue Beschreibung dazu findet man in diesem Artikel im Blog zum schwarzen Pinguin. Man kann Noten von sehr gut bis ungenügend vergeben, und es gibt einen Button zum Abschicken der Bewertung. Dazu werden die evtl. bereits vorhandenen Bewertungen und die Durchschnittsnote angezeigt, aussehen tut das Ganze so:
Voraussetzungen in Drupal
Ich werde dieses Formular nur für einen ausgesuchten Inhaltstypen anlegen, ich nehm mal die Artikel. Dazu brauchen wir einen Template Override – das hört sich schlimmer an als es ist.
Um die Bildschirmanzeige für alle Inhalte vom Typ Artikel zu ändern, klemmt man sich aus dem Verzeichnis ../themes/[dein theme]/templates die node.tpl.php und macht eine Kopie davon. Diese benennt man um in node–article.tpl.php (doppelter Bindestrich!). Dann sucht man sich die richtige Stelle nach dem Content aus, das ist in meinem Fall nach dem Block:
<div class="content clearfix"<?php print $content_attributes; ?>>
<?php
// We hide the comments and links now so that we can render them later.
hide($content['comments']);
hide($content['links']);
print render($content);
?>
</div>
Da ich das Formular nur in der Einzelansicht anzeigen möchte, füge ich hier eine if-Abfrage ein, die sieht erstmal so aus:
<?php
if ($view_mode == 'full'){
echo "<h1>Hier kommt das Formular hin</h1>";
}
?>
Cache leeren nicht vergessen, und nachgucken ob der Text an der richtigen Stelle angezeigt wird. Dann kann man hier den Funktionsaufruf reinpacken:
if ($view_mode == 'full'){
el_bew_ajax();
}
Ein eigenes Modul für das Formular
Das Formular selber packe ich in ein selbstgebautes Modul, zur Erstellung eines einfachen Moduls in Drupal kann man in diesem Artikel nachschlagen. Zuerst kommt mal das HTML für die Anzeige des Formulars rein, das wird in eine Funktion gesteckt:
function el_bew_ajax(){
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' onclick='el_bew_ajax()'/>
</div>
</fieldset>";
echo "</form>";
}//ende function el_bew_ajax
Das habe ich 1:1 aus der oben zitierten WordPress-Lösung kopiert, funktioniert hier genauso. Das Javascript wird bei Klick auf den ‚absenden‘-Button aufgerufen, dazu gleich mehr.
Eine Tabelle für die Bewertungen
Auch diese sieht genau wie in der WordPress-Lösung aus und enthält drei Felder, eine Autowert-ID, ein Int für die nid und ein Int für die Note:
Die Logik für die Anzeige der bereits vorhandenen Bewertungen
Diese musste ich nur ein wenig auf Drupal-Verhältnisse anpassen, die Datenbankabfrage funktioniert hier mit db_query. Die aktuelle Beitragsid ist ein bisschen sperrig abzufragen, da suche ich noch eine hübschere Lösung:
//nid des aktuellen Beitrags bestimmen und in span schreiben
$nid = 0;
if (arg(0) == 'node' && is_numeric(arg(1))) {
$nid = arg(1);
}
echo 'NID dieses Beitrags: <span id = "beitragsid">'.$nid.'</span>';
//******aktuelle Benotungen abholen und Durchschnitt berechnen
$result = db_query('SELECT * FROM {bewertungen} WHERE nid = '.$nid.'');
$gefunden = $result->rowCount();
$durchschnitt = 0;
foreach ($result as $record) {
$durchschnitt = $durchschnitt + $record->note;
}
if ($gefunden != 0){
$durchschnitt = $durchschnitt / $gefunden;
}
echo "<span id = 'ausgabe'>".$gefunden." Bewertungen gefunden, Durchschnitt: ".$durchschnitt."<span>";
Das Javascript
…wird am Anfang der .module-Datei eingebunden, und zwar so:
function ajax_el_init() {
drupal_add_js(drupal_get_path('module', 'ajax_el') . '/test_ajax_el.js');
}
Die js-Datei liegt bei mir einfach im selben Verzeichnis wie das Modul. Sie sieht wird in einen Wrapper eingebunden, damit man $ für jQuery verwenden kann:
(function ($) {
function el_bew_ajax(){
//Hier kommt der Code
}//end function el_bew_ajax
})(jQuery);
Die ganze Funktion für den Ajax-Call sieht folgendermassen aus:
(function ($) {
function el_bew_ajax(){
//Beitragsid aus span holen
var akt_id = document.getElementById("beitragsid").innerHTML;
//Note aus den Radiobuttons holen
var akt_note = $("input:radio[name=note]:checked").val();
//Debug-Ausgabe
//alert(akt_note + ' ' + akt_id);
//Check ob keine Note angewählt, Abbruch
if (typeof akt_note === 'undefined'){
alert('Bitte eine Note wählen!');
return;}
//***********************ajax-call
if (window.XMLHttpRequest)
{
// AJAX nutzen mit IE7+, Chrome, Firefox, Safari, Opera
xmlhttp=new XMLHttpRequest();
}
else
{
// AJAX mit IE6, IE5
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.onreadystatechange=function()
{
if (xmlhttp.readyState==4 && xmlhttp.status==200)
{
document.getElementById("ausgabe").innerHTML = xmlhttp.responseText;
}
}
xmlhttp.open("GET","../sites/all/modules/ajax_el/callback_ajax_el.php?id="+akt_id+"¬e="+akt_note,true);
xmlhttp.send();
//**********************end ajax call
}//end function el_bew_ajax
})(jQuery);
Ich hole mir am Anfang die nid des Beitrags und die aktuelle Note aus dem Formular und gebe eine Meldung aus falls keine Benotung angewählt wurde.
Die Funktionalität für den Ajax-Call ist die selbe wie die, die wir schon für den Ajax-Autocomplete in diesem Beitrag verwendet haben, das drösel ich hier jetzt nicht nochmal auf. Der einzige Unterschied ist, dass hier am Ende zwei Parameter statt nur einem an die Callbackfunktion übergeben werden.
Die Callback-Funktion
ist auch recht einfach. Ich hole mir die zwei Werte für die Note und die nid des Beitrags, mache einen Insert auf die Tabelle bewertungen und berechne den neuen Durchschnitt sowie die Anzahl der Bewertungen. Zurückgegeben werden die neuen Werte, die kriegt unser Script als response und schreibt sie in die div ‚ausgabe‘ im Formular.
$nid = '';
if ( isset($_GET['id']) )
{
$nid = $_GET['id'];
}
$note = '';
if ( isset($_GET['note']) )
{
$note = $_GET['note'];
}
$pdo = new PDO('mysql:host=localhost;dbname=druneu', '******', '*******');
//neue Note erstmal einfügen
$sql = "INSERT INTO bewertungen (nid, note) VALUES (".$nid.",".$note.")";
$neu = $pdo->query($sql);
//Suche nach aktueller nid
$sql = "SELECT * FROM bewertungen where nid = ".$nid."";
$records = $pdo->query($sql);
$gefunden = $records->rowcount();
$durchschnitt = 0;
foreach ($records as $row) {
$durchschnitt = $durchschnitt + $row['note'];
}
if ($gefunden != 0){
$durchschnitt = $durchschnitt / $gefunden;
}
echo $gefunden." Bewertungen vorhanden, Durchschnittsnote = ".$durchschnitt;
Das war schon der ganze Zauber! Man kann jetzt noch die Durchschnittsnoten runden und per switch case die entsprechenden Bezeichnungen vergeben (1=sehr gut… 6=ungenügend), aber das ist Feintuning, das spare ich mir jetzt.
Aussehen tut das Ganze jetzt so:
Tipp: damit man sich beim Testen nicht schwarz ärgert wenn was nicht gleich funktioniert: Cache leeren nach jeder Script-Änderung!
Wir haben ja im vorigen Beitrag unsere Javascriptdatei rezepte_suche.js geladen, jetzt stelle ich sie hier mal in aller Kürze vor. Die ist nicht auf meinem Mist gewachsen, das ist eine Standardprozedur für einen AJAX-Call die ich mir zusammengegooglet und nach meinen Bedürfnissen angepaßt habe. Sie enthält genau eine Funktion, nämlich die rezepte_suche mit dem Parameter inhalt, in dem steckt der vom Benutzer eingegebene Inhalt des Textfeldes aus dem Formular:
function rezepte_suche(inhalt)
{
if (inhalt=="")
{
return;
}
if (window.XMLHttpRequest)
{
// AJAX nutzen mit IE7+, Chrome, Firefox, Safari, Opera
xmlhttp=new XMLHttpRequest();
}
else
{
// AJAX mit IE6, IE5
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.onreadystatechange=function()
{
if (xmlhttp.readyState==4 && xmlhttp.status==200)
{
document.getElementById("liste").innerHTML=xmlhttp.responseText;
}
}
xmlhttp.open("GET","http://localhost/drumenus/sites/all/modules/autocom/rezeptesuche.php?q="+inhalt,true);
xmlhttp.send();
}
Was passiert hier? Zuerst wird abgefragt, ob inhalt leer ist und in dem Fall nur ein return ausgelöst.
Wenn inhalt nicht leer ist, greifen zwei IF-Abfragen, die Browserabhängig die Variable xmlhttp unterschiedlich belegen. Wir schauen mal nur den ersten Zweig an:
xmlhttp=new XMLHttpRequest();
Hier wird ein neues Objekt vom Typ XMLHttpRequest angelegt, das wird im Folgenden weiterverarbeitet. Wer dazu Genaueres wissen möchte dem sei diese Seite der wikibooks empfohlen.
Wenn sich am Status des Formularfeldes etwas ändert, feuert folgende Funktion:
xmlhttp.onreadystatechange=function()
{
if (xmlhttp.readyState==4 && xmlhttp.status==200)
{
document.getElementById("liste").innerHTML=xmlhttp.responseText;
}
}
Die div mit der id liste kriegt einen neuen Inhalt, nämlich den Rückgabewert des Requests. Wir der aussieht, wird in unserer PHP-Datei geregelt, dazu gleich mehr.
Ich hab hier den Pfad zur PHP-Datei noch fest verdrahtet, das läßt sich sicher auch eleganter lösen, aber für Testzwecke tuts das. Die PHP-Datei kriegt als Parameter unseren inhalt mit, das ist immer noch der Inhalt des Formularfeldes so wie ihn der Benutzer eingegeben hat. Fehlt nur noch der Auslöser:
xmlhttp.send();
Und Schuß! Das setzt den Request ab.
Die aufgrufene PHP-Datei
Die benimmt sich nicht anders, als wenn sie von einem Formular aus aufgerufen würde, und sie übernimmt den Parameter inhalt in der Variablen q. Ansonsten ist sie recht übersichtlich:
<?php
$rezept = '';
if ( isset($_GET['q']) )
{
$rezept = $_GET['q'];
}
$pdo = new PDO('mysql:host=localhost;dbname=drumenus', '*****', '*****');
//Suche nach enthaltenem String %suchstring%
$sql = "SELECT * FROM node where title like '%".$rezept."%' and type like 'rezept' order by title";
$records = $pdo->query($sql);
foreach ($records as $row) {
echo "<option>".$row['title']." #".$row['nid']."</option><br>";
}
?>
Ich hole mir erstmal den Inhalt von q auf die Variable $rezept. Dann verbinde ich mich mit der Drupal-Datenbank und hole mir aus der node-Tabelle mit dem Select alle Datensätze der Rezepte (type like ‚rezept‘), die den Inhalt von $rezept im Titel enthalten. Innerhalb der Foreach-Schleife wirds dann nochmal interessant, ich klemme vor und nach dem Titel des Rezeptes die <option>-Tags dran, die brauchen wir nachher im Formular für die Datalist. Ausserdem nehme ich noch die nid mit, die brauchen wir dann für die Ausgabe des Rezeptes. Das wars dann aber auch schon.
Das Formular ist jetzt scharfgeschaltet
Wenn der Benutzer jetzt zum Beispiel salat in das Textfeld eingibt, erhält er eine Liste aller Rezepttitel, in denen der String salat vorkommt:
Jetzt muss nur noch etwas passieren, wenn er einen der Einträge auswählt und auf Return drückt. Ich hab das so gelöst: in der autocom.module kommt nach dem Formular noch folgender Code:
if (isset($_POST['ausgesucht'])){
echo "Vielen Dank! Ihre Auswahl: <h2>".$_POST['ausgesucht']."</h2><br>";
$text = strstr($_POST['ausgesucht'], '#');
$text=substr($text,1);
$options = array('absolute' => TRUE);
$nid = $text; // Node ID
$url = url('node/' . $nid, $options);
echo "<h2><a href = ".$url.">Link zum Rezept</a></h2>";
} //ende von if isset
Wenn der Benutzer einen Eintrag ausgewählt hat (If (isset…)), zerlege ich mir die Auswahl und hole mir nur die nid nach dem #. Zu der wiederum hole ich mir die URL des betreffenden Nodes, und gebe damit einen Link zum Rezept aus. Das wars! Aussehen tut das Ganze so:
Ich hab hier jetzt mal mit dem Link gearbeitet, man könnte hier natürlich auch mit der nid in die Tabelle field_data_body gehen und sich den body_value ausgeben lassen, das wäre der komplette Text des ausgewählten Rezeptes, aber das ist reine Geschmackssache.
Drupal-Puristen werden mich jetzt wahrscheinlich steinigen, weil man „das so nicht machen kann“, aber der Autocomplete funktioniert jedenfalls ohne allzu grossen Aufwand. Es gibt sicher hier und da noch ein paar Optimierungspotentiale, was man unbedingt noch angehen sollte wäre der fest verdrahtete Pfad zu der PHP-Datei in der rezepte_suche.js, das ist sicher nicht so besonders gut gelöst, aber für Demozwecke tuts das, finde ich. mir hats Spaß gemacht, und das Ergebnis ist ganz ansehnlich.
Wahrscheinlich werden da jetzt alle Drupal-Puristen Zetermordio schreien, dass man das so nicht machen kann, aber wenns funktioniert… ich bin da nicht so pingelig.
Das Vorhaben: Suche mit Autocomplete
Ich hab ja über 300 Rezepte in meinem Drupal-Kochbuch, und was in WordPress funktioniert hat, sollte auch in Drupal machbar sein: eine Suche mit Autocomplete bzw. auf Deutsch Auto-Vervollständigung. Das heisst, wenn der Benutzer in das Suchfeld einige Buchstaben eingibt, sollen per AJAX gematchte Einträge als Vorschläge in der Liste auftauchen. Dabei verwende ich die HTML5-Datalist, auch wenn die browserabhängig unterschiedliche Ergebnisse liefert, darüber habe ich mich im letzten Beitrag näher ausgelassen. Macht mir aber jetzt nicht wirklich viel aus, man muss es halt nur wissen.
Wir bauen uns ein Modul
Wir packen die ganze Sache natürlich in ein Modul, und das kriegt einen eigenen Menüeintrag, so gehts am Einfachsten. Zur Modulerstellung in Drupal sag ich jetzt nicht mehr viel, das hatten wir schon ausführlich in diesem Artikel und anderswo.
Das Modul heisst bei mir autocom, dazu brauchts erstmal die autocom.info, die sieht bei mir so aus:
name = Autocom
description = Dieses Modul soll Autocompleten
core = 7.x
package = "MyFunctions"
version = "7.1-1.1"
Und dann brauchen wir natürlich noch die autocom.module, zu der ist etwas mehr zu sagen, in der laden wir uns nämlich erstmal die für AJAX benötigte Javascript-Datei. Das passiert ganz oben im File:
function autocom_init() {
drupal_add_js(drupal_get_path('module', 'autocom') . '/rezepte_suche.js');
}
Mit der Syntax [modulname]_init wird Drupal angewiesen, diese Anweisung beim Laden des Moduls auszuführen. drupal_add_js() ist für das Laden von Javascript zuständig, das drupal_get_path( ‚module‘, ‚[modulname]‘) holt uns den Pfad zum angegebenen Modul, und an den hängen wir unsere js-Datei mit dran, weil die mit im Modulverzeichnis liegt. Was die Scriptdatei macht, dazu später mehr.
Die Funktion für das Suchformular kommt als nächstes in die autocom.module, die sieht erstmal so aus:
function evi_auto(){
echo "<h1>Beispiel Rezeptsuche mit AJAX</h1>";
echo "<form action='#' method='post'>";
echo "<input type='text' size='80' name = 'ausgesucht' value='' autocomplete='off' onkeyup='rezepte_suche(this.value)' list = 'suchliste'>";
echo "<datalist id='suchliste'>";
echo "<div id =liste>";
echo "</div>";
echo "</datalist>";
echo "</form>";
}
Egentlich ein ganz normales Formular mit einem Eingabefeld vom Typ Text. Aber hier passieren noch drei eminent wichtige Dinge:
onkeyup = ruft die js-Funktion rezepte_suche() auf, die steckt in der rezepte_suche.js, die wir vorher geladen haben. Die Funktion kriegt als Parameter this.value mit, also den aktuellen Wert des Textfeldes. Onkeyup feuert, wenn der Benutzer eine Taste gedrückt und wieder losgelassen hat.
Der Parameter list = ‚suchliste‚ des Textfeldes definiert, aus welchem HTML-Element die Datenliste für das Dropdownfeld genommen werden soll, nämlich aus der Datalist mit der id = „suchliste“
Innerhalb der Datalist steckt eine Div mit der id= ‚liste‘, in die schreibt nachher unser AJAX-Call sein Ergebnis, dazu gleich mehr.
Wichtige Ergänzung: das Texteingabefeld sollte unbedingt den Parameter autocomplete = ‚off‘ mitkriegen, sonst pfuscht der Browser mit Einträgen hinein, die er aus anderen Formularen gecached hat.
Wenn man das Modul angelegt und aktiviert hat, sollte das Formular jetzt etwa so aussehen:
Es passiert natürlich noch nichts wenn man da etwas eingibt, da uns die js-Funktion und die zugehörige PHP-Datei noch fehlen, aber dazu gibts einen neuen Beitrag.
Einleitung – es geht erstmal nicht um Drupal direkt
Eigentlich wollte ich nur eine etwas komfortablere Rezeptsuche realisieren und hab auch mal ein bisschen mit AJAX experimentiert, aber dann bin ich an einem neuen HTML5-Feature hängengeblieben und habe da erstaunliche Ergebnisse erzielt, die will ich euch nicht vorenthalten. Das hat jetzt erstmal noch nix direkt mit Drupal zu tun, aber es ist eine Voraussetzung für die Realisierung des nächsten Projekterls mit dem Arbeitstitel „Rezeptsuche mit Ajax und Autocomplete“, das kommt dann im nächsten Beitrag.
HTML5 Datalist
Die Spezifikation für die Datalist kann man z.B. hier bei w3schools nachlesen, es ist eigentlich ganz einfach und funktioniert prinzipiell wie ein normales Dropdown-Feld mit der zusätzlichen Funktionalität Auto-Vervollständigen. Man kann die Datalist auch prima aus der Datenbank füttern, das hab ich hier mal mit meinen Rezepten in Drupal gemacht. Man gibt dem Input-Feld die Id der Liste mit, und schließt die Liste der Optionen in die <datalist>-Tags ein:
$query = db_query("SELECT * FROM node WHERE type like 'rezept' AND status = 1 ORDER BY title");
$anzahl = $query->rowCount();
echo "<h2>".$anzahl." Rezepte insgesamt </h2><br>";
$records = $query->fetchAll();
//Beginn Formular
echo "<form action='#form' method='post'>";
echo "<input type = 'text' name='kategorien' list='kategorien' />";
echo "<datalist id='kategorien'>";
foreach ($records as $record){
echo "<option value='".$record->title."'>";
}
echo "</datalist>";
echo "</form>";
//End Formular
Das Ganze sieht jetzt in jedem Browser unterschiedlich aus und hat auch unterschiedliche Funktionalitäten.
Firefox Quantum
Ich habe zunächst nur mal den Buchstaben b eingegeben. Es klappt ein mickriges Listenfeld aus, aus dessen Inhalt zunächst noch nicht klar wird, nach welchen Kriterien da gesucht wird, es fängt mit Rezepten mit dem Anfangsbuchstaben a an:
Erst wenn man einen zweiten Buchstaben eingibt, ich nehm hier mal b, erhellt sich die Lage etwas. Anscheinend werden alle Einträge angezeigt, die den String ba enthalten:
Nehmen wir mal noch ein paar Buchstaben und suchen nach backen:
In der Tat, alle Rezepte in deren Titel backen vorkommt. Das heisst aber, dass man im Zweifelsfall verdammt viele Buchstaben eingeben muss, bis man die Auswahl auf ein Rezept eingrenzen kann. Gewöhnungsbedürftig, um es mal milde auszudrücken.
Chrome 65.0.3325.181
Hier siehts am Anfang auch ganz schön verwirrend aus, man bekommt nach Eingabe des b erstmal das letzte Ende der Liste zu sehen:
Erst nach Eingabe weiterer Buchstaben wird klar, dass auch hier nach einer im Titel enthaltenen Kombination gesucht wird.
Wenigstens kriegt man hier die Liste in einer übersichtlichen Breite angezeigt, das ist doch immerhin etwas.
IE 10
Machts wieder ganz anders. Eingabe des Buchstaben b bringt alle Rezepte, die mit b anfangen:
Diese Liste kann man mit der Eingabe weiterer Buchstaben genauer eingrenzen, ba liefert brav alle Rezepte die mit dieser Kombi anfangen:
Tscha, so unterschiedlich wird die Datalist von den Browsern umgesetzt. Wenn man’s weiss…
Ich hab ja redlich versucht, mehr über die Parameterübergabe an Views herauszufinden, aber ganz ehrlich: das war mir alles zu esoterisch, und viel zu kompliziert – man muss ja nicht unbedingt mit Kanonen auf Spatzen schiessen. Ich möchte nach wie vor eine Ausgabe so in der Art:
316 Rezepte insgesamt
111 davon Vegetarisch
Jetzt hab ich einfach die Anforderung ein bisschen geändert: ich lasse den Benutzer auswählen, zu welcher Kategorie er die Anzahl der Rezepte ausgegeben haben möchte. Also dann, auf gehts!
Wie man Drupal eigenen PHP-Code unterschiebt
.. hab ich in diesem Artikel beschrieben. Dafür erstellt man sich ein einfaches Modul, in dem man seine eigenen Funktionen unterbringt. Dann kan man sich zum Beispiel auch einen eigenen Block definieren, in dem man schlicht und ergreifend eine eigene PHP-Funktion aufruft, und den kann man dann positionieren wo man ihn gern haben möchte.
Die Gesamtzahl der Rezepte
Das ist easy, wir selektieren alle Rezepte mit Status1 = veröffentlicht und holen uns die Anzahl mit rowCount:
//Datenbankabfrage für die Gesamtzahl der Rezepte
$query = db_query("SELECT * FROM node WHERE type like 'rezept' AND status = 1");
$anzahl = $query->rowCount();
echo "<h2>".$anzahl." Rezepte insgesamt </h2><br>";
Auswahl der Kategorien über Dropdown-Feld
Dafür basteln wir uns ein Formular mit einer Options-Liste und füttern diese mit einer Datenbankabfrage.
//Datenbankabfrage für das Optionsfeld
$query = db_query("SELECT * FROM taxonomy_term_data WHERE vid = 2");
$records = $query->fetchAll();
Ich habe hier die Kategorie Rezepte mit der vid = 2 fest verdrahtet. Das Abfrageergebnis schieben wir so in das Formular rein:
//Beginn Formular
echo "<h3>Kategorie auswählen</h3>";
echo "<form action='#' method='post'>";
//Beginn des Dropdownmenü
echo '<select name="Name">';
//hier kommen nun die Werte für die Options rein
foreach ($records as $record){ echo '<option>'.$record->name.'</option>'; } echo "</select>";
echo "<input type='submit' name='absenden' value='Anzahl ausgeben'>";
echo "</form>";
//End Formular
Am Ende kommt noch ein Submit-Button, und das war, wir haben jetzt unsere Dropdown-Liste mit den Kategorienamen gefüllt.
Wenn jetzt der Anwender eine Kategorie ausgewählt hat, muss er noch auf den Button „Anzahl ausgeben“ klicken, dann legen wir los.
Mit einem if(isset)… fragen wir ab, ob der Submit-Button gedrückt wurde. Dann holen wir uns den Wert aus dem Dropdown-Feld mit $_POST[‚Name‘] und machen zwei kleine Datenbankabfragen. In der ersten holen wir uns die tid zum ausgewählten Kategorienamen, in der zweiten zählen wir, wie oft diese tid mit einem Rezept verknüpft wurde:
if (isset($_POST['absenden'])){
// tid zum Namen der Kategorie holen
$query = db_query("Select tid from taxonomy_term_data where name like '".$_POST['Name']."'");
$akt_katid = $query->fetchField();
//Zählen, wieviele Rezepte zur tid vorhanden sind
$query = db_query("SELECT * FROM taxonomy_index where tid = ".$akt_katid."");
$anzahl = $query->rowCount();
echo $anzahl." Rezepte zur Kategorie ".$_POST['Name']."<br>";
} //End if isset absenden
Und am Ende geben wir unsere Ergebnisse noch aus.
Wenn man es ganz genau machen wollte, müsste man in die Abfrage für die Anzahl noch einen Join über die nid (Node ID) der Rezepte auf die Tabelle nodes machen und nur alle selektieren, die den Status 1=veröffentlicht haben, aber ich lass es mal so stehen. Meine Ausgabe sieht dann so aus:
Damit das ganze ein bisschen mehr Nährwert kriegt, legen wir auf den Namen der Kategorie jetzt noch einen Link, der uns dann die Seite der gewählten Kategorie aufruft. Die URL unserer Seite holen wir mit global $base_url; daraus bauen wir dann den Link:
Das wars jetzt aber, das lass ich so stehen. Ich finde es für einen Block sogar recht gut, weil es nicht so viel Platz wegnimmt wie die View mit allen Kategorien untereinander, das Dropdown-Feld ist da deutlich kompakter.
Ich habe ja schon für WordPress ein Widget und für Joomla ein kleines Modul erstellt, die jeweils die Gesamtzahl der veröffentlichten Rezepte und dazu die Anzahl der Rezepte einer frei wählbaren Kategorie anzeigen, so nach dem Muster:
317 Rezepte insgesamt
89 davon vegetarisch
Jetzt hab ich mich mal dahintergeklemmt, ob sowas auch in Drupal zu realisieren ist, und ich sags ganz ehrlich: ich hab mir einen Frust gegooglet. Es ist anscheinend nicht möglich, einem Block benutzerdefinierte Variable mitzugeben, und damit fällt das ganze Konzept auf die Nase.
Mit Views gehts – ja aber…
Es ist relativ einfach, mit Views einen Block zu erstellen, der die Gesamtzahl der veröffentlichten Rezepte ausgibt. Und es ist auch nicht viel schwieriger, einen Block zu erstellen, der die Gesamtzahl der vegetarischen Rezepte ausgibt. Ich kann aber meinem durchschnittlichen Benutzer nicht zumuten, sich in Views einzuarbeiten und da z.B. statt dem Tag „vegetarisch“ das Tag „Joschis Cocktailbar“ einzusetzen, no way José. Zudem müsste man bei jedem anderen Tag die Überschrift des Blocks auch manuell anpassen, also das kanns echt nicht gewesen sein. Da ich aber nichts anderes gefunden habe, schreibe ich hier mal kurz wie man die Views aufsetzt.
Nur Rezepte aus Joschis Cocktailbar
Struktur->Views->add new view, Namen vergeben. Hier kann man gleich mal den Inhalt of type Rezept tagged with Joschis Cocktailbar auswählen:
Die View für die Anzahl der Rezepte insgesamt erstellt man genauso, nur läßt man ganz am Anfang das tagged with leer. Das wars auch schon, aber ich finde es eine sehr unbefriedigende Lösung. Wie gesagt, ich kann es meinen Anwender nicht zumuten, selber mit Views herumzuhantieren, da suche ich immer noch einen einfacheren Weg. Mal sehen, ob ich noch was finde.
Zuerst wird das Logo ausgegeben, das ist unser Header-Bild. Dann kommt der Name der Seite und falls vorhanden der Slogan. Man darf sich hier ein wenig über das div-Gestrüpp wundern, das geht sicher auch einfacher, aber wir lassens jetzt mal so wie es ist. Ich setze mal nur eine Hintergrundfarbe für den Header:
#header {background-color: #bfd0ea;
}
Und ich schiebe den Block für die Suche nach rechts, wie der heißt habe ich mit Chrome rausgefieselt:
Das zugehörige css ist:
#block-search-form{float: right;
padding: 10px;}
Jetzt kriegt der header noch eine Hintergrundfarbe:
Was mich noch stört: wenn man auf eine andere Seite als die Startseite wechselt, wird der Seitentitel klein geschrieben, das hätte ich gern noch anders. Dafür genügt ein kleiner Eingriff in der page.tpl.php, in der ist hinterlegt, dass der Seitentitel nur in h1 formatiert wird, wenn der Content Title leer ist
<?php if ($site_name): ?>
<?php if ($title): ?>
<div id="site-name"><strong>
<a href="<?php print $front_page; ?>" title="<?php print t('Home'); ?>" rel="home"><span><?php print $site_name; ?></span></a>
</strong></div>
<?php else: /* Use h1 when the content title is empty */ ?>
<h1 id="site-name">
<a href="<?php print $front_page; ?>" title="<?php print t('Home'); ?>" rel="home"><span><?php print $site_name; ?></span></a>
</h1>
<?php endif; ?>
<?php endif; ?>
Dafür editiere ich den ersten Block hinter der der Zeile
Zu diesem Thema gibt es einige gute Tutorials im Netz, ich schreibs aber hier trotzdem nochmal komprimiert auf, nur die Basics, es ist nämlich nicht besonders schwierig. Ich baue hier so in etwa das Layout von WordPress TwentyFourteen nach, das ist mein Lieblings-Theme und schön schlicht und ohne Schnickschnack.
Die Ordner-Struktur
Man legt in [drupalinstallation]/sites/all/themes einen neuen Ordner an und benennt ihn mit dem Namen des neuen Themes (ohne Umlauts und Sonderzeichen), bei mir heisst er evi2014. In diesem Ordner werden zwei Unterordner angelegt, css und templates.
Die unbedingt notwendigen Dateien
Als Seitenvorlage kopiert man sich aus [drupalinstallation]/modules/system die Datei page.tpl.php, die kommt in den Ordner templates. In den Ordner css legt man eine style.css, die kann fürs erste noch leer sein.
In den Ordner evi2014 kommt jetzt die Datei evi2014.info, die hat Minimum folgenden Inhalt:
name = Evis2014
description = Angelehnt an mein Lieblings-WordPress-Theme TwentyFourteen.
core = 7.x
screenshot = screenshot.png
stylesheets[all][] = css/style.css
Ist eigentlich selbsterklärend, name und description sind frei wählbar, und die Drupalversion core= sollte stimmen. Wichtig ist hierbei, dass der Verweispfad auf die style.css stimmt, und dass die screenshot.png im selben Verzeichnis liegt.
Jetzt brauchen wir noch die tatsächliche Datei screenshot.png, die sollte ca.291×150 px gross sein und eine Vorschau unseres Themes enthalten.
Dann fehlt noch die logo.png, die brauchen wir für das Headerbild, bei mir ist sie 1260 px breit weil ich dafür die ganze Breite haben möchte.
Das wars schon! Jetzt sollte unser neues Theme bereits unter Design->Themes auftauchen und kann aktiviert werden.
Das sieht jetzt alles noch ein bisschen dürftig aus, unsere style.css ist ja noch leer.
Aber unsere Hauptkomponenten sind schon vorhanden, als da wären Das Hauptmenü (selbst erstellt), das Sekundärmenü (default von Drupal), und weiter unten (im Screenshot leider nicht zu sehen) unsere Blöcke. Die Blockvorschau sieht so aus:
Diese Einteilung kommt aus der page.tpl.php, mit der arbeiten wir weiter. Wo wir hinwollen, ist dieses dreispaltige Layout:
In die linke Seitenleiste kommen meine neuesten Rezepte (ein View), in die Mitte der Content, und rechts meine Rezeptkategorien (auch ein View.
Und der Weg dahin führt über die style.css. Das machen wir aber in einem neuen Beitrag.
Dabei sind die Ersterwert/Letzterwert Felder die term_ids aus WordPress. Ich vegebe mal noch hübschere Feldnamen und hole zum Testen nur eine Handvoll Datensätze:
Wir bauen jetzt unser Import-Plugin entsprechend um. Der Select bleibt gleich, der Tabellenname liegt auf der Variablen $akt_import:
global $wpdb;
$allezeilen = $wpdb->get_results( "SELECT * from ".$akt_import."");
Innerhalb der Foreach-Schleife konstruieren wir uns jetzt unsere neuen Beiträge:
Erst wird das Array für das neue Beitragsobjekt mit den Datenfeldern aus der aktuellen Zeile gefüllt, und dann mit wp_insert_post der neue Beitrag angelegt. Das wars schon! Alle Rezepte mitsamt Kategorien werden erzeugt.
Natürlich muss man an der Logik noch ein bisschen rumschrauben, wenn man mehr als zwei Kategorien pro Rezept importieren will, aber prinzipiell funktioniert die Sache so, das geht eigentlich ganz unkompliziert, wenn man die Export-Tabelle einmal konstruiert hat.