Alle Beiträge von admin

Seitenaufrufe anzeigen mit dem Statistik-Modul

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:

node_counter
node_counter

Die Anzahl der Aufrufe sollte jetzt auch unterhalb jedes Inhalts erscheinen.

aufrufe
aufrufe

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:

beliebte_inhalte
beliebte_inhalte

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.

Drupal Bewertungsformular mit AJAX

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:

bewertungformular1
bewertungformular1

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:

drupal_bewertungen
drupal_bewertungen

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+"&note="+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:

bewertungsformular2
bewertungsformular2

Tipp: damit man sich beim Testen nicht schwarz ärgert wenn was nicht gleich funktioniert: Cache leeren nach jeder Script-Änderung!

Rezeptsuche mit AJAX Autocomplete 2: das Javascript und die PHP-Datei

Das Javascript

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.

Wichtig ist jetzt noch der open-Aufruf:

xmlhttp.open("GET","http://localhost/drumenus/sites/all/modules/autocom/rezeptesuche.php?q="+inhalt,true);

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:

salat
salat

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:

link_zum_rezept
link_zum_rezept

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.

Rezeptsuche in Drupal mit AJAX Autocomplete

Einleitung

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:

  1. 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.
  2. 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“
  3. Innerhalb der Datalist steckt eine Div mit der id= ‚liste‘, in die schreibt nachher unser AJAX-Call sein Ergebnis, dazu gleich mehr.
  4. 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:

formular_leer
formular_leer

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.

 

Kleiner Exkurs: Datalist in HTML 5

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:

quantum1
quantum1

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:

quantum_ba
quantum_ba

Nehmen wir mal noch ein paar Buchstaben und suchen nach backen:

quantum_backen
quantum_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:

chrome_b
chrome_b

Erst nach Eingabe weiterer Buchstaben wird klar, dass auch hier nach einer im Titel enthaltenen Kombination gesucht wird.

chrome_back
chrome_back

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:

ie_b
ie_b

Diese Liste kann man mit der Eingabe weiterer Buchstaben genauer eingrenzen, ba liefert brav alle Rezepte die mit dieser Kombi anfangen:

ie_ba
ie_ba

Tscha, so unterschiedlich wird die Datalist von den Browsern umgesetzt. Wenn man’s weiss…

Es geht auch ohne Views: Anzahl Rezepte zu einer Kategorie ausgeben

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.

dropdown_kategorien
dropdown_kategorien

Wenn jetzt der Anwender eine Kategorie ausgewählt hat, muss er noch auf den Button „Anzahl ausgeben“ klicken, dann legen wir los.

auswahl
auswahl

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:

111vegetarisch
111vegetarisch

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:

global $base_url;
        
$html_string = '<a href=' . $base_url . '/taxonomy/term/'.$akt_katid.'>'.$_POST['Name'].'</a>';
    echo $html_string;

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.

mit_link_zur_kategorie
mit_link_zur_kategorie

 

Geht manches wirklich nur mit Views?

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:

nur_joschiscocktailbar
nur_joschiscocktailbar

Create a Block, Continue&edit.

Advanced->use aggregation->aggregate->apply

Fields Inhalt(Titel)->Aggregation settings->Anzahl->apply.

Das sollte jetzt so aussehen:

aggregation
aggregation

Die Ausgabe wäre dann:

7_joschi
7 joschi

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.

Ein einfaches Drupal-Theme erstellen 2: das Styling

Noch sieht unser Theme etwas traurig aus, aber das werden wir gleich ändern.

ohnestyles
ohnestyles

Ich passe erstmal die Schriftarten an und vergebe für h1-h3 Kapitälchen:

body {
  margin: 0;
  padding: 0;
  font-family: Helvetica;
  font-size: 14px;    
  font-weight: normal;
  line-height: 24px;
  color: #43473b;
}


h1, h2, h3, h4, h5, h6 {
  font-family: Times;
  font-weight: normal;
  color: #43473b;
}

h1 { font-size: 45px; font-variant:small-caps;}
h2 { font-size: 30px; font-variant:small-caps;}
h3 { font-size: 22px; font-variant:small-caps;}

a { color: #43473b; 
    text-decoration: none;
}
a:hover { color: #fc737b; 
        }

Dann gehts an die page.tpl.php, aus der entnehmen wir nämlich, welche divs für welche Komponenten vorgesehen sind.

Anfangen tut das Ganze mit gleich zwei umgebenden divs für die Seite:

<div id="page-wrapper"><div id="page">

Ich setze mal nur den page-wrapper auf 1260px. Dann kommt die div header, da wirds schon interessanter:

<div id="header"><div class="section clearfix">

      <?php if ($logo): ?>
        <a href="<?php print $front_page; ?>" title="<?php print t('Home'); ?>" rel="home" id="logo">
          <img src="<?php print $logo; ?>" alt="<?php print t('Home'); ?>" />
        </a>
      <?php endif; ?>

      <?php if ($site_name || $site_slogan): ?>
        <div id="name-and-slogan">
          <?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; ?>

          <?php if ($site_slogan): ?>
            <div id="site-slogan"><?php print $site_slogan; ?></div>
          <?php endif; ?>
        </div> <!-- /#name-and-slogan -->
      <?php endif; ?>

      <?php print render($page['header']); ?>

    </div></div> <!-- /.section, /#header -->

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:

block-search-form
block-search-form

Das zugehörige css ist:

#block-search-form{float: right;
                    padding: 10px;}

Jetzt kriegt der header noch eine Hintergrundfarbe:

#navigation {    background-color:#bfd0ea;    
                }

Und beim Hauptmenü passe ich noch die Schrift an:

#main-menu {
    font-family: Times;
    font-variant:small-caps;
    font-size:24px;
    
    }

Das Sekundärmenü möchte ich in der Fußzeile haben, da packe ich mir den ganzen Block in der page.tpl,php und schiebe ihn nach der div footer hin:

<div id="footer"><div class="section">
      <?php print render($page['footer']); ?>
    </div>
    
    <?php print theme('links__system_secondary_menu', array('links' => $secondary_menu, 'attributes' => array('id' => 'secondary-menu', 'class' => array('links', 'inline', 'clearfix')), 'heading' => t('Secondary menu'))); ?>
      
    </div> <!-- /.section, /#footer -->

Die sidebar-first sitzt noch nach dem content, die klemme ich mir auch im Ganzen:

<?php if ($page['sidebar_first']): ?>
        <div id="sidebar-first" class="column sidebar"><div class="section">
          <?php print render($page['sidebar_first']); ?>
        </div></div> <!-- /.section, /#sidebar-first -->
      <?php endif; ?>

und schiebe sie vor die div content. Jetzt positionieren wir noch sidebar-first, content und sidebar-second nebeneinander:

#content {        padding: 20px;
                width: 600px;
                float:left;
                
}                
                
#sidebar-first {width: 300px;
                float:left;
                padding: 10px;
                }
                
#sidebar-second{
    width:300px;
    float: left;
}

Das sieht jetzt schon im Ganzen gut aus:

seitentitel-kleingeschrieben
seitentitel-kleingeschrieben

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

<?php if ($title): ?>

und setze einen h1-Tag:

<div id="site-name"><strong>
                <h1><a href="<?php print $front_page; ?>" title="<?php print t('Home'); ?>" rel="home"><span><?php print $site_name; ?></span></a></h1>
              </strong></div>

Das wars! Jetzt wird der Seitentitel immer h1 formatiert.

Mir ist das Styling jetzt schön genug, ich lass es so, es sieht meinem TwentyFourteen ähnlich genug.

finish
finish

Ich hoffe, ihr habt soviel Spaß beim Nachbauen wie ich beim Basteln!

Ein einfaches Drupal-Theme erstellen

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.

aktiviert
aktiviert

Das sieht jetzt alles noch ein bisschen dürftig aus, unsere style.css ist ja noch leer.

ungestylt
ungestylt

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:

vorschau
vorschau

Diese Einteilung kommt aus der page.tpl.php, mit der arbeiten wir weiter. Wo wir hinwollen, ist dieses dreispaltige Layout:

dreispaltig
dreispaltig

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.

Drupal nach WordPress: Beiträge mit Kategorien erzeugen

Kurze Wiederholung: unsere Import-Tabelle sieht folgendermassen aus:

ersterwert_letzterwert
ersterwert_letzterwert

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:

neue_feldnamen
neue_feldnamen

 

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:

foreach ($allezeilen as $zeile){
        
        $akt_titel = $zeile->title;
        $akt_body_value = $zeile->body_value;
        $akt_term_id1 = $zeile->term_id1;
        $akt_term_id2 = $zeile->term_id2;

        // Beitragsobjekt anlegen
        $my_post = array();
        $my_post['post_title']    = $akt_titel;
        $my_post['post_content']  = $akt_body_value;
        $my_post['post_status']   = 'publish';
        $my_post['post_author']   = 1;
        $my_post['post_category'] = array( $akt_term_id1,$akt_term_id2 );

        // Beitrag in Datenbank einfügen
        $neue_id = wp_insert_post( $my_post );
        echo "Beitrag mit neuer ID: ".$neue_id." angelegt <br><br>";
        
    } //end foreach

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.

import_komplett
import_komplett

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.