Archiv der Kategorie: Formulare

Es geht voran mit dem Stichwortregister-Plugin: aus drei mach zwei

Ich hab mir heute nochmal die Erstellung von Admin-Menüs in WordPress zu Gemüte geführt, und beschlossen aus den drei Plugins zur Erstellung eines Stichwortregisters doch zwei zu machen.  Es sind ja auch getrennte Funktionalitäten, einmal wird die Stichwortliste als CSV aus der wp_posts erstellt, eine Ausgabeseite angelegt und in die wp_options eingetragen, und der Shortcode erzeugt, und zum zweiten wird die Negativliste gepflegt. Ausserdem bräuchte ich dann für die Negativliste eine Menütiefe von 3, und da wirds tricky, das kann WordPress nicht so ohne weiteres.

Man könnte die CSV-Datei, die Ausgabeseite und die Tabelle für die Negativliste auch bei Aktivierung des entsprechenden Plugins automatisch erzeugen, aber irgendwie ist es mir sympathischer, das manuell anzustossen. Muss man halt eine recht ausführliche Anleitung mitliefern, aber das krieg ich auch noch gebacken 😉

Jetzt erst mal weiter mit dem Plugin-Zusammenfassen. Läuft ganz gut, ich hab wohl recht sauber programmiert, Morgen mehr!

Update: Das war ja einfach. Ich habe ein schönes Tutorial bei Honar Systems für die Erstellung von WordPress Admin Pages mit Sub Pages gefunden, damit war die Hauptarbeit schon erledigt. Es gibt jetzt einen Menüeintrag „Stichwortregister“ mit zwei Untermenüs „Konfiguration“ und „CSV Datei“. Es gibt des weiteren einen eigenen Menüpunkt „Negativliste bearbeiten“. Hier mal nur ein kurzes Snippet für die Erzeugung der Menüs:

 function stichwortregister_admin_menu() {
add_menu_page(
__( 'Stichwortregister', 'de_DE' ),
__( 'Stichwortregister', 'de_DE' ),
'manage_options',
'stichwort-page',
'stichwortregister_admin_page_contents',
'dashicons-plugins-checked',
3
);
add_submenu_page( 'stichwort-page',
__( 'Stichwortregister', 'de_DE' ),
__( 'CSV-Datei', 'de_DE' ),
'manage_options',
'stichwort-page-sub-menu-csv',
'stichwortregister_sub_menu_admin_page_csv_contents');

add_submenu_page( 'stichwort-page',
__( 'Stichwortregister', 'de_DE' ),
__( 'Konfiguration', 'de_DE' ),
'manage_options',
'stichwort-page-sub-menu',
'stichwortregister_sub_menu_admin_page_contents');


}
add_action( 'admin_menu', 'stichwortregister_admin_menu' );

Die Funktionalitäten haben sich nicht geändert, ich hab sie nur ein bisschen anders einsortiert. Lief problemlos, ich hab da echt sauber gearbeitet. So, jetzt muss es aber gut sein. Zwei Plugins für ein Stichwortregister, damit leben wir einfach 😉

Perfektionisten würden jetzt noch die Stichwortliste in eine MySQL Tabelle packen, aber mir gefällt das mit dem CSV eigentlich ganz gut, vielleicht will man die Liste auch mal extern bearbeiten oder ergänzen. Ich lass es jetzt mal so.

Stichwortregister revisited: aus drei mach eins

Jetzt hab ich drei Plugins, um ein Stichwortregister zu erzeugen, das ist irgendwie nicht so zufriedenstellend, das könnte man sicher auch zusammenfassen.  Ich mach mal ein Brainstorming und schau was dabei rauskommt.

  • Bei Aktivierung des Plugins sollen ein paar Aufgaben erledigt werden: die Tabelle negativliste mit einigen (wenigen) Beispieldaten soll angelegt werden, einige Setup-Informationen sollen in die wp_options geschrieben werden. Das geht wahrscheinlich mit dem
    register_activation_hook( __FILE__, array( $this, 'example_activate' ) );
    
  • die Erstellung der CSV-Datei mit den Stichworten (aus dem Feld post_title erzeugt) soll manuell angestossen werden, damit man hier bessere Kontrolle hat.
  • Der Abgleich mit der Negativliste soll live bei Erstellung des Stichwortregisters erfolgen.
  • Fleißaufgabe: statt der CSV-Datei könnte man auch eine Custom Tabelle hernehmen, aber dabei ist mir der Webserver zu oft abgeraucht, da hab ich keine Lust drauf.

Das wird natürlich ein ewig langer Rattenschwanz, wenn man das alles in ein einziges Plugin packt, da müsste ich gucken ob man den Ablauf nicht besser straffen und verschlanken kann. Mal schauen ob mich die Arbeitswut noch packt… ich zweifle, die Rohfassung läuft ja ganz hübsch, das langt mir immer 😉

Update am Folgetag: Ich versuch mal meinen inneren Schweinehund zu überwinden und die drei Plugins doch zu einem Paket zusammenzufassen. Ich hab mir dazu mal eine lokale Kopie meines Praxis Dr. Inselfisch Blogs angelegt, auf der ich auf einer „leeren Wiesn“ entwickeln kann. Mal sehen wie weit ich komme.

Update drei Tage später: ich prokrastiniere. Mir ist das einfach zu fad, bereits funktionierende Code Snippets nochmal komplett neu zusammen zu fassen. Na, ich muss mal schauen. wahrscheinlich geh ich eh nochmal drüber, wenn ich das Stichwortverzeichnis auf einem Life-Blog einsetzen möchte. Erster Kandidat ist dieser hier 😉

Der Tabelleneditor für die Negativliste: die Screenshots

Die Negativliste bearbeiten geht nur auf der Admin-Seite, das hab ich mal so festgelegt. Ich habe einen neuen Admin Menüpunkt “ Negativliste bearbeiten“ angelegt, wenn man den aufruft sieht die Startseite so aus:

Negativliste-bearbeiten

Die bereits vorhandenen Worte werden tabellarisch angezeigt, Man kann sie Löschen, dann öffnet sich das Unterformular und man muss nochmal auf den Button „Löschen“ klicken:

Wort-loeschen

Erst dann wird der Delete aktiv:

 Wort-loeschen-OK

 

…oder man kann einen neuen Eintrag anlegen.

Wort-eintragen

Ein kleines, aber rundes Feature, finden sie nicht auch? 🙂

Was mich allerdings beim Entwickeln ein bisschen ausgebremst hat: die Routinen für den Insert und Create und Delete haben mir ein paar Mal den Webserver abgeschossen bis sie fehlerfrei durchgelaufen sind, da half nur ein kompletter Neustart von Apache, MySQL und Xampp. Das darf natürlich in einer „richtigen“ WordPress-Umgebung nicht passieren, da muss man noch Fehlerbehandlungsroutinen einbauen. ich hab da jetzt aber keine Lust mehr dazu.

So, was fehlt noch? Ach ja, die Negativliste wartet noch auf ihren Einsatz beim Aufbau des Stichwortregisters. Das überleg ich mir morgen, wo man da am Geschicktesten einhakt.

Stichwortverzeichnis Negativliste: wir basteln uns einen Tabelleneditor

Ich bin mir ziemlich sicher dass ich sowas in der Art schon mal gemacht habe, aber ich finde es nicht mehr. Na ja, das übt und ist eine hübsche kleine Fingerübung. Ich mach mal einen kurzen PAP:

  • Die Tabelle für die Negativliste wird beim Setup des Stichwort-Plugins mit angelegt und mit einigen wenigen Beispieleinträgen befüllt. Jedenfalls gehe ich davon aus, dass sie schon existiert.
  • Es gibt einen neuen Admin-Menüpunkt Negativliste bearbeiten. Hier wird die existierende Liste tabellarisch angezeigt.
  • Es gibt bei jedem Datensatz einen Button „Löschen“ (mit Rückfrage)
  • Es gibt unterhalb der Tabelle einen Button „Neues Wort eintragen“
  • Die Buttons machen jeweils eine Unterseite auf, auf der die gewählte Aktion nochmal bestätigt wird.

Das wars eigentlich schon. Dann wollen wir mal. Zuerst kommen die Einträge für das Admin-Menü:

add_action('admin_menu', 'negativliste_plugin_setup_menu');

function negativliste_plugin_setup_menu(){
add_menu_page( 'Negativliste', 'Negativliste bearbeiten', 'manage_options', 'negativliste', 'negativliste_init' );
add_submenu_page('Negativliste', 'Eintragen', 'Eintragen', 'manage_options', 'eintragen', 'eintragen_function');
add_submenu_page('Negativliste', 'Loeschen', 'Loeschen', 'manage_options', 'loeschen', 'loeschen_function');
}

Schauen wir uns als erstes mal die Funktion negativliste_init an. Zuerst wird gecheckt, ob die Tabelle negativliste schon vorhanden ist:

//Überprüfen ob Tabelle schon existiert
$table_exists = $wpdb->get_var( "SHOW TABLES LIKE 'negativliste'" ); 

if ($table_exists == "") {
echo "Tabelle negativliste existiert noch nicht";
... (hier kann die Tabelle neu erstellt werden)


Da muss ich nochmal ran, eigentlich sollte die Tabelle beim Initialisieren des Plugins Stichworttabelle mit angelegt werden. Hier nur mal Interessehalber der Code zum Erstellen:

//Begin function tabelle_erzeugen - legt die Tabelle negativliste mit einem Feld "wort" an
function tabelle_erzeugen(){

global $wpdb;

$charset_collate = $wpdb->get_charset_collate();

$sql = "CREATE TABLE negativliste (
wort text
) $charset_collate;";

require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
dbDelta( $sql );

} //End function tabelle_erzeugen

Interessant wirds im Else-Zweig der Funktion, da  kommt die Datenbankabfrage und der Aufbau der Tabelle zur Anzeige. Ich schau erst mal nach, ob die Tabelle überhaupt Datensätze hat und baue die Tabelle nur auf, wenn es mehr als 0 Datensätze sind:

function tabelle_bearbeiten(){

global $wpdb;
//Datensätze zählen & Ausgabe Anzahl
$count_query = "select count(*) from negativliste";
$num = $wpdb->get_var($count_query);
echo $num."&nbsp Einträge gefunden</br>";

if ($count_query <> 0) {

$anzeige_query = $wpdb->get_results("SELECT wort FROM negativliste ORDER BY wort");
...

So wird der Tabellenhead zusammengebaut:

echo "<table border='1' cellpadding='5'>
<tr>
<th>Wort</th>
<th>delete</th>
</tr>";

Danach steppe ich durch alle gefundenen Datensätze durch und baue ein Formular mit einem Löschen-Button zusammen. Der Button ruft die Unterseite loeschen auf und gibt als Parameter das aktuelle Wort mit.

echo "<form method='post'>";
foreach ($anzeige_query as $dsatz) {
echo "<tr>";
$id = $dsatz->wort;

echo "<td>" . $dsatz->wort . "</td>";
echo "</td>" .
"<td><input type='submit' name='löschen' formaction='admin.php?page=loeschen&aktuell=".$id."' value='löschen'></td>" .

"</tr>";
}// ende von foreach dsatz

Jetzt kommt nur noch ein Link zur der Seite auf der man einen neuen Datensatz eintragen kann, dann wars das auch schon:

echo "<p><input type='submit' name='eintragen' formaction='admin.php?page=eintragen' value='Neues Wort eintragen'></p></form> ";

Jetzt fehlen noch die Funktionen zum Eintragen eines neuen Datensatzes und zum Löschen des gewählten Datensatzes. Zuerst die zum Eintragen:

Zuerst wird ein kleines Formular aufgebaut: es gibt ein Textfeld, in das man das neue Wort eintragen kann, und einen Button zum Speichern. Wenn auf diesen Button geklickt wird, wird der Insert aufgerufen und kriegt das Wort als Parameter mit.

if (isset($_POST['neuesWortEintragen'])){
			$aktWort = $_POST["wort"];
			echo "Neues Wort ".$aktWort." wird eingetragen";
			
	// Insert-Anweisung erstellen
    $check = $wpdb->insert( 'negativliste', array( 'wort' => ''.$aktWort.'' ) );

    if($check) {
        echo "Ein neuer Datensatz erfolgreich hinzugefügt";
    }
	} // ende von isset 

Dann kommt noch ein Link zurück zum Hauptformular, und das wars.

 

echo "</form>";

echo "<a href='admin.php?page=negativliste'>Zurück zur Übersicht</a>";

Das Formular zum Löschen eines Datensatzes sieht ganz ähnlich aus, das Textfeld ist allerdings schreibgeschützt, es dient nur zur Anzeige des als Parameter übergebenen Worts.

function loeschen_function(){

global $wpdb;

$aktWort = $_GET['aktuell'];
echo $aktWort." wird gelöscht";

echo "<h2><Löschen</h2>";

echo "<form action='admin.php?page=loeschen&aktuell='".$aktWort."' method='post'>";
echo "<p><input name='wort' type='text' value = '$aktWort' readonly></p>";
echo "<p><input type='submit' name='WortLoeschen' value='Wort löschen'></p>";
...

Wenn auf den Button „Wort Löschen“ geklickt wird, triggert der Delete-Befehl:

if (isset($_POST['WortLoeschen'])){
$aktWort = $_POST["wort"];
echo "Wort ".$aktWort." wird gelöscht</br>";

// Delete-Anweisung erstellen
$check=$wpdb->query("DELETE FROM negativliste WHERE wort = '$aktWort'");

if($check) {
echo "Ein Datensatz erfolgreich gelöscht";
}
echo "</form>";

Auch hier kommt noch der Link zurück zur Übersicht, und das wars.  Jetzt fehlen noch ein paar Screenshots, aber dafür gibts einen neuen Beitrag.

Darf ich vorstellen: Stichwort Plugin Teil 1, CSV-Datei erzeugen

Da ich die ganze Mechanik schon mal programmiert habe, in Access mit Visual Basic, hab ich mir relativ leicht getan es auch in PHP zu lösen. Was gar nicht schön war: bei komplexeren Datenbankoperationen ist mir x-mal der Webserver abgeraucht. Deswegen hab ich dann die Notbremse gezogen und bin auf eine CSV-Datei ausgewichen. Nicht die schlechteste Lösung, was Stabilität und Performance angeht.

Was hab ich gemacht? Ich geh da mal im Schnelldurchlauf durch, haben wir alles schon mal so oder in ähnlicher Form gehabt. Trotzdem, das eine oder andere ist vielleicht gut zu wissen.

Also,, zunächst wird ein Plugin mit einem Eintrag für das Admin-Menü erstellt. Dazu legt man eine Datei an, die folgendermassen aussieht:

/*
Plugin Name: Stichworttabelle
Description: Erzeugt eine neue Stichworttabelle aus den Titeln der Beiträge (post_title)
Author: Evi Leu
Version: 0.1
*/
    add_action('admin_menu', 'stichworttabelle_plugin_setup_menu');
	
	function stichworttabelle_plugin_setup_menu(){
    add_menu_page( 'Stichworttabelle', 'Stichworttabelle Plugin', 'manage_options', 'stichworttabelle', 'stichworttabelle_init' );
}
 
function stichworttabelle_init(){... HIER GEHTS MIT DER MAIN FUNCTION LOS

Diese Datei kommt in ein eigenes Unterverzeichnis „Stichworttabelle“ im Plugin-Verzeichnis deiner WP-Installation und heißt Stichworttabelle.php. Sie kann jetzt in der Liste der installierten Plugins aktiviert werden. Sie tut zunächst mal nichts ausser einen Eintrag im Admin-Menü zu erzeugen, der heißt „Stichworttabelle Plugin“ und ist erstmal noch eine leere Seite.

Die Menüseite wird in der Funktion function stichworttabelle_init() mit Leben gefüllt, erst kommt ein bisschen Infotex, dann wird der Name der Datenbank ermittelt und ausgegeben:

echo "<h1>Stichworttabelle neu erstellen</h1>";
	echo "Das Plugin hat zwei Funktionalitäten: </br></br>
	1. Diesen Admin-Menüpunkt Stichworttabelle Plugin, in dem die Stichworttabelle neu aufgebaut werden kann.</br>
	Aus Performancegründe und wegen der Runtime-Stabilität wurde die Stichwortbasis in eine externe CSV-Datei ausgelagert. Diese wird neu erstellt, falls sie nicht schon vorhanden ist. Falls sie schon vorhanden sein sollte, wird sie überschrieben. Man kann die Datei beliebig oft neu erzeugen, z.B. wenn es grössere Mengen neuer Beiträge gibt.</br></br>
	
	2. einen Shortcode [stichwortverzeichnis]. der an beliebiger Stelle in einem Beitrag oder einer Seite eingesetzt werden kann und dort ein Stichwortverzeichnis erzeugt.</br></br>";
	global $wpdb;
	
	//Datenbankname ermitteln
	$mydatabase=$wpdb->dbname;
	echo "Sie arbeiten auf der Datenbank: ".$mydatabase."</br>";
	echo "Stichworte werden aus den Titeln ihrer Beiträge erzeugt</br>";

Dann kommt ein kleines Formular, das aus genau einem Button besteht:

//***************Begin Formular
//Formular mit Button
//"
// Stichwortliste Datei neu erzeugen startet die array-Erzeugung fuellen und befüllt die Datei stichwortliste.csv wieder

echo "<form action = '#' method = 'post'>";
	
	echo "<input type='submit' id='el_button2' name='ButtonFuellen' value='Stichwortliste Datei neu erzeugen'>";
	echo "</form>";
	
	if (isset($_POST['ButtonFuellen'])){
			
			return tabelle_fuellen();
		}
//*****************End Formular

Wenn auf den Knopf gedrückt wird, wird die Funktion tabelle_fuellen aufgerufen. Jetzt wirds interessant:

function tabelle_fuellen(){
	
	$neuesArray=array_erzeugen();
	erzeuge_csv($neuesArray);
}

Die Variable $neuesArray wird mit Hilfe der Funktion array_erzeugen() befüllt. Diese erstellt eine Stichwortliste aus den Titeln aller Beiträge in der Tabelle wp_posts, dazu gleich mehr.

Dann wird die Funktion erzeuge_csv aufgerufen, sie kriegt als Parameter unser Array mit und schreibt die Einträge zeilenweise in eine Datei.

Frischauf, wir sehen uns die Funktion array_erzeugen() mal näher an. Der erste Teil mit den nötigen MySQL-Abfragen sieht so aus:

global $wpdb;

//Beginn Originalcode
$table_name = $wpdb->prefix.'posts';

	//Datensätze zählen & Ausgabe Anzahl
	$count_query = "select count(*) from $table_name where post_status='publish' and post_type = 'post'";
	$num = $wpdb->get_var($count_query);
	echo $num."&nbsp Beiträge gefunden</br>";
	
//******************

//Alle Datensätze vom Typ post und published ausgeben
$alleposts = $wpdb->get_results( "SELECT * FROM ".$table_name."
								where post_status='publish' and post_type = 'post' order by post_title");


Das übliche Spiel wenn man die veröffentlichten Beiträge ausgeben will, man braucht in der Where-Klausel die Bedingung post_status=’publish‘ and post_type = ‚post‘. Und zugegeben, man könnte statt select * auch select post_title verwenden, das fällt mir erst jetzt auf.

Jetzt stecken alle veröffentlichten Beiträge als Array in der Variablen $alleposts. Durch dieses Array steppe ich jetzt mit foreach durch und nehme mir die einzelnen Einträge vor, die werden mit Hilfe der Funktion explode() am Leerzeichen gesplittet, mit preg_replace() von Sonderzeichen bereinigt und mit ctype_upper auf Groß/Kleinschreibung überprüft, ich nehme nur die groß geschriebenen Einträge. Das ist willkürlich festgelegt, produziert aber eine sehr brauchbare Stichwortliste. Schließlich wird der gefundene Eintrag mit array_push() in die Variable $stichwortliste weggeschrieben.

$stichwortliste = array(); 
//Durch alle gefundenen Datensätze durchsteppen
$zaehler = 0;
foreach ( $alleposts as $einpost ) 
{ 
  //ersten gefundenen Titel in array aufsplitten
  $liste = explode(" ", $einpost->post_title);
  
  //Durch das Array durchsteppen
   foreach ($liste as $einwort)
  {
	  //Prüfen, ob Wort groß geschrieben ist
	  $wortanfang = substr($einwort,0,1);
	  
	  //Sonderzeichen entfernen (nach Bedarf editieren)
	  $einwort = preg_replace('/[0-9\@\.\;\" "\(\)\:\?\!\,]+/', '', $einwort);
	  
	  //nur ausgeben wenn Groß geschrieben
	  if (ctype_upper($wortanfang)){
		 
		  $zaehler = $zaehler+1;
		  
		  //Hier kommt der Knackpunkt: Neues Stichwort in Array schreiben
		  //***********************************
		  array_push($stichwortliste, $einwort);
		  //***********************************
	  }// ende von ctype_upper
  }// ende von liste as einwort

  
}//ende von alleposts as einpost und array befüllen

Es folgt noch ein bisschen Kosmetik, und ganz am Ende gibt unsere Funktion das gebrauchsfertige Array zurück:

//Dubletten entfernen
$stichwortliste= array_unique($stichwortliste);

//Array sortieren
sort($stichwortliste);

//Ausgabe Anzahlen erzeugter Stichwörter
echo "Anzahl Stichwörter in den Rohdaten: ".$zaehler."</br>";
echo "Grösse des sortierten und Dubletten-bereinigten Arrays: ".count($stichwortliste)."</br>";
echo "<h2>Erzeuge neue Stichwortliste aus der Tabelle: ".$table_name."</h2>";

return $stichwortliste;
}// ende array erzeugen_function

Noch alle mit mir beieinander? Fehlt noch was? Ach ja, die Erzeugung der CSV-Datei mit Hilfe der Funktion erzeuge_csv(), damit halte ich mich jetzt nicht lange auf, die ist einigermassen selbsterklärend:

function erzeuge_csv($liste){
	
	global $wpdb;
	
	echo "Ich erzeuge jetzt ein csv: ";
	
	//***************
	// Verzeichis des aktuellen Plugins ermitteln
	$dir = plugin_dir_path( __FILE__ );
	$aktVerzeichnis = $dir;
	//Dateiname fest verdrahtet	
	$fileName = $aktVerzeichnis.'stichwortliste.csv';
	echo $fileName."</br>";
	
if(file_exists($fileName)){
	echo "Die alte Datei wird überschrieben</br>";
	}
    
	//Gnadenlos überschreiben, der Parameter 'w' ersetzt den alten Dateiinhalt
	
    $csvFile = fopen($fileName,'w');
    $head = ["Wort"];
    fputcsv($csvFile,$head);

// Variable mit den Listeneinträgen befüllen
foreach ($liste as $einwort){
$data = [
    ["$einwort"],
    
];

//Durch alle data-Einträge durchsteppen und in Datei schreiben

foreach($data as $row){
    fputcsv($csvFile,$row);
}
}
fclose($csvFile);

 //Debug-Ausgabe aller Stichworte
$anzahl = sizeof($liste);
echo "<h2>Testausgabe: ".$anzahl." Stichwörter erzeugt</h2>";

foreach ($liste as $stichwort){
	
	echo $stichwort."</br>";
	
}
		
}// Ende function erzeuge csv
//*****************************************

Falls die Datei nicht existiert, wird sie neu angelegt. Falls sie schon existiert, wird der Inhalt überschrieben. So das wars. Jetzt einen Kaffee und sacken lassen. Und dann Hurra auf zu neuen Ufern, jetzt kommt der Teil mit dem Shortcode. Aber dazu gibts einen neuen Beitrag.

Konform zu WCAG: Kontrastfarben automatisch berechnen

Ich bin darauf hingewiesen worden, dass mein Color Tool für barrierefreie Kontrastfarben im WEB nicht der aktuellsten Version der WCAG entspricht. OK, hab ich ein bisschen recherchiert und umgestrickt. Was früher als Brightness gehandelt wurde, heißt jetzt Luminescence und wird etwas anders berechnet. Zum Vergleich der Schrift- und Hintergrundfarbe wird eine Ratio herangezogen, 4.5:1 entspricht AA, 7:1 enspricht AAA. Die Berechnung ist im Ganzen etwas komplexer geworden, aber doch kein Hexenwerk. Nachlesen kann man die nötigen Formeln hier:
https://www.w3.org/WAI/GL/wiki/Relative_luminance

Das Kernstück ist die Formel zur Umrechnung des RGB-Wertes aus dem Colorpicker in WCAG-konforme Lumineszenz, die sieht so aus:

function Luminescence($r, $g, $b) //neuere Definition nach wcag s. a. https://www.w3.org/WAI/GL/wiki/Relative_luminance

 {
 $rs = $r/255;
 $gs = $g/255;
 $bs = $b/255;
 
 if ($rs <= 0.03928)
 {
 $R = $rs/12.92;
 }
 else {
 $R = (($rs+0.055)/1.055) ** 2.4;}
 
 if ($gs <= 0.03928)
 { $G = $gs/12.92;
 }
 else {
 $G = (($gs+0.055)/1.055) ** 2.4;
 }
 
 if ($bs <= 0.03928){
 $B = $bs/12.92 ;}
 else 
 {$B = (($bs+0.055)/1.055) ** 2.4;}

 $Lumi = 0.2126 * $R + 0.7152 * $G + 0.0722 * $B;
 
 return $Lumi;
 
 
 }

Dann braucht man noch die Formel für die Contrast Ratio, die findet sich hier im WCAG-Wiki. Damit kann man sich einstellen, ob man AA oder AAA kompatibel sein möchte, und das wars dann auch im Grossen und Ganzen.

if ($input_luminescence >= $row_luminescence){
  $ratio = ($input_luminescence+0.05)/($row_luminescence+0.05);
  }
 else
 {
 $ratio = ($row_luminescence+0.05)/($input_luminescence+0.05);
 }

 if ($ratio>4.5){ //Nur die Farben nehmen bei denen die Ratio grösser als 4.5 ist
...


Viel Spaß beim Ausprogrammieren!

Barrierefreie Farbkontraste 2: der Sourcecode

OK jetzt gilts: man kann eine beliebige RGB-Farbe wählen und sich ausgeben lassen, welche der 140 benannten HTML-Farben einen guten (barrierefreien) Kontrast dazu liefert. Das kann man nicht nur für Schriften verwenden, sondern auch für Logos und Icons, die auf einen farbigen Hintergrund gesetzt werden sollen.

Voraussetzung ist eine Tabelle, in der die 140 Farben mit Color Group (Farbfamilie), Namen und Hex- und RGB-Notation gespeichert sind, ich zeig hier noch mal einen Ausschnitt:

mysql_farbtabelle

mysql_farbtabelle

Wie die Farben da reinkommen beschreibe ich jetzt hier nicht, das ging über ein Excel-Makro und ist letzendlich völlig wurst für die Funktionalität. Also, wir haben unsere Farbtabelle.

Jetzt brauchen wir eine Datenbankverbindung, die beschreibe ich hier auch nicht mehr extra. Dann muss wieder mal ein Formular her, für die Auswahl der Basisfarbe bietet sich der Input-Type Color an, bei dem man in den meisten modernen Browsern einen schicken Colorpicker angezeigt bekommt. Man könnte auch ein Eingabefeld für den Hex- oder RGB-Wert nehmen, das ist letztlich Geschmackssache. Also, los gehts, mit einer Datei namens farbnamen.php, die sieht zunächst so aus:

<!DOCTYPE html>
<html lang="de">
<style type="text/css">
h1,h2,h3{
    font-family:Arial;
}
</style>
<head>
<title>Colors Arbeitspapier</title>
</head>

<body>
<h1>Beispiel Contrast Colors mit HTML Named Colors</h1>

<form action="farbnamen.php" method="post">

      <h2>Bitte eine Farbe auswählen:</h2>
      <input type="color" name="farbe" value=""><br>
      <h2>Bitte eine Farbfamilie für die Kontrastfarbe wählen:</h2>
    <select name="farbfamilien">
        <option value="BLUE">Blau</option>
        <option value="GREEN">Grün</option>
        <option value="PURPLE">Violett</option>
        <option value="WHITE">Weiß</option>
        <option value="BROWN">Braun</option>
        <option value="YELLOW">Gelb</option>
        <option value="GRAY">Grau</option>
        <option value="RED">Rot</option>
        <option value="PINK">Pink</option>
        <option value="ORANGE">Orange</option>
      </select>
     <br><br>
    <input type="submit" name = "senden">
  
</form>

Das ist erstmal straightes HTML, zuerst kommt der Color Picker, dann das Dropdownfeld für unsere Farbfamilien, und das wars auch schon. Man könnte den Select auch mit einem Distinct aus der Datenbank füttern, das hab ich mir jetzt gespart und die 10 Einträge so reingeschrieben.

Los geht es wie immer wenn auf den Submit-Button geklickt wurde. Der Colorpicker liefert den Hex-Wert der gewählten Farbe, das Dropdownfeld den Namen der Farbfamilie. Den Hex-Wert zerlege ich in die einzelnen Komponenten, die brauche ich später um meine Funktion zur Berechnung der wahrgenommenen relativen Helligkeit damit zu füttern, dazu weiter unten mehr. Das Ganze sieht jetzt erstmal so aus:

startbildschirm

startbildschirm

Bei Klick auf das schwarze Farbkästchen poppt der Colorpicker auf, in Chrome beispielsweise sieht der so aus:

chrome_colorpicker

chrome_colorpicker

Das Dropdownfeld ist auch keine Überraschung:

dropdown

dropdown

Aber jetzt wirds gleich interessant, ich habe nämlich für die Zerlegung des Hex-Wertes in seine RGB-Komponenten eine schicke Funktion gefunden, das geht mit sscanf():

<?php
require_once('config.php');

if(isset($_POST['senden'])){
    
    
    echo "Sie haben diese Farbe gewählt:<span style='background-color:".$_POST['farbe']."'>Hexadezimalwert: ".$_POST['farbe']."</span><br>";
    
    $akt_farbe = $_POST['farbe'];
    
    //RGB-Werte aus hex holen
    $hex = $akt_farbe;
    list($r, $g, $b) = sscanf($hex, "#%02x%02x%02x");
        
    echo "<h3>Die relative Helligkeit beträgt: ".Brightness($r,$g,$b)."</h3>";
    echo "<h3>Kontrastfarbe aus der Farbfamilie ".$_POST['farbfamilien']."</h3>";
    echo "<hr>";
    $input_brightness = Brightness($r,$g,$b);

Mit der Funktion Brightness wird wie gesagt die wahrgenommene Helligkeit der gewählten Farbe bestimmt, Input-Parameter sind die drei RGB-Komponenten. Die Funktion selber sieht so aus:

//relative (wahrgenommene) Helligkeit aus RGB berechnen
function Brightness($r, $g, $b)
    {
       return sqrt(
          $r * $r * .241 + 
          $g * $g * .691 + 
          $b * $b * .068);
    }

Jetzt hole ich mir aus der Datenbank alle Farben, die zu der gewählten Farbfamilie gehören. Zuerst setze ich noch einen Flag $gefunden, der bleibt auf False wenn in der gewählten Farbfamilie keine Farbe mit einem ausreichenden Kontrastwert gefunden wurde und dient dann weiter unten zur Info-Ausgabe.

Dann gehe ich mit einem While über alle gefunden Farben und gebe nur diejenigen aus, bei denen eine Differenz > 130 zur relativen Helligkeit der vom Benutzer vorgegebenen Farbe gefunden wird.

//alle passenden Einträge der Farbtabelle ausgeben
        //Flag default  
        $gefunden = false;
        
        //Nur die Einträge der gewählten Farbfamilie auslesen
          $abfrage = mysqli_query($conn,"SELECT * FROM $table 
                    WHERE familie LIKE '".$_POST['farbfamilien']."' "); 
          while($row = mysqli_fetch_array($abfrage,MYSQLI_ASSOC)) 
          { 
            //RGB-Werte aus hex holen
            $hex = $row['hex'];
            list($r, $g, $b) = sscanf($hex, "#%02x%02x%02x");
            //Helligkeit bestimmen
            $row_brightness = Brightness($r,$g,$b);
            
            //Kontrastwert berechnen
            $differenz = abs($row_brightness - $input_brightness);
            
            //Nur Kontrast höher als 130 ausgeben (Wert ggf anpassen)
            if ($differenz > 130){
                //flag setzen wenn mindestens ein Kontrast > 130 gefunden wurde
                $gefunden = true;
            echo ' <span style="color:'.$row['farbname'].'">'.$row['farbname'].'</span>.'.$row['hex'].'rel. Helligkeit: '.$row_brightness.' Differenz: '.$differenz.'<br>'; 
            echo "<h1><span style='color:".$row['farbname']."; background-color:".$akt_farbe."'>".$row['farbname']." ist ein guter Kontrast</span></h1>";
            echo "<hr>";
            }
            
        } // ende von while $row...
if($gefunden==false){echo "Keine passende Kontrastfarbe in der Farbfamilie ".$_POST['farbfamilien']." gefunden";}

} //Ende von isset senden

Für die Ausgabe der passenden Kontrastfarben verwende ich Spans und formatiere sie mit den entsprechenden Farbwerten. Falls keine Farbe mit ausreichendem Kontrast gefunden wurde, gebe ich dies als Info aus. Das war’s!

Hier noch ein Screenshot der Ausgabe, als Besipiel habe ich als Grundfarbe Gelb und für die Kontrastfarbe die Farbfamilie Blau gewählt:

farben_ausgabe

farben_ausgabe

Fertig ist das Werkzeug zur Kontrastfarbenbestimmung. Zugegeben könnte man es noch wesentlich hübscher formatieren, aber die Funktionalität haut hin, das genügt mir jetzt erstmal. Schlanke noch nicht mal 100 Zeilen Code, das ist mal wieder was für mich und meine minimalistischen Freunde – viel Spaß beim Nachbauen!

Nachtrag

Jetzt konnte ich mir es doch nicht verkneifen, die ganze Sache noch ein bißchen ansehnlicher zu gestalten. Der Prototyp sieht jetzt so aus:

aufgehuebscht

aufgehuebscht

Die Farben hole ich mir natürlich aus der Datenbank, wenn ich dazukomme, schreib ich noch einen neuen Artikel darüber. Aber hier ist jetzt mal Schlussende, der Beitrag ist lang genug

 


 

 

Ein kleiner Chat in PHP – wieder mal was für Minimalisten

Da ich mich derzeit viel im Chat in meinem Lieblings-Handarbeitsforum herumtreibe, dachte ich mir: so einen kleinen, eher spartanischen Chatroom solltest du eigentlich auch selber programmieren können, das kann so schwierig nicht sein. Bei Google gibt es -zig Beispiele, wie man so etwas angehen könnte, ich hab mich auf die Suche nach etwas Einfachem gemacht und bin hier bei php-einfach.de fündig geworden.

Ich hab allerdings ein paar Fehler bereinigt, auf PHP 7 umgeschrieben, die Ajax-Funktionalität eliminiert (warum kompliziert wenns auch einfach geht?), die MySQL-Tabelle komplett anders strukturiert, eine Teilnehmerliste hinzugefügt und sämtliche Formatierungen umgekrempelt, so dass letztendlich eine komplett umgeschmissene Logik dabei herausgekommen ist. Wie gesagt, die Grundidee stammt von php-einfach.de, und ich bedanke mich auch artig für den Denkanstoß. Aber ich denke, es ist genug kreative Eigenleistung dazugekommen, dass ich den Source hier als auf meinem Mist gewachsen einstellen kann.

Die Voraussetzungen

Viel ist nicht dran: man soll seinen Nick und seine Nachricht eingeben können, auf den Senden!-Button klicken, und dann soll im Chatfenster die Nachricht für alle sichtbar am unteren Ende des Fensters auftauchen. Ich hab noch die Uhrzeit wann die Nachricht geschrieben wurde mit dazugenommen, und protokolliere im Hintergrund auch das Datum mit, da ich in meinem Chat immer nur die Beiträge des heutigen Tages anzeigen möchte.

Aussehen tut das Ganze so:

screenshot_chat

screenshot_chat

Man kann seinen Nick eingeben (der bleibt auch nach dem Abschicken stehen), man kann seine Nachricht eingeben, und man kann auf den Senden!-Button klicken. Im Chatfenster scrollen die älteren Einträge nach oben weg, der neueste Eintrag landet immer ganz unten, und es wird noch die Uhrzeit zu jedem Beitrag angezeigt. Es werden nur die Einträge des heutigen Tages angezeigt, den Rest habe ich natürlich auch gebunkert, daraus könnte man noch ein Chat-Archiv erstellen. Rechts kriegt man noch eine Liste angezeigt, wer heute schon aller im Chat war, und das wars.

Im Hintergrund: natürlich eine MySQL-Tabelle

Die ist recht einfach strukturiert und heißt bei mir chat_daten:

chat_daten

chat_daten

Eine AutoIncrement-id als Primärschlüssel, und vier Textfelder für nick, eintrag, uhrzeit und datum, das war schon alles.

Der Sourcecode

Wir packen die Connect-Daten in eine externe Datei:

<?php 
$server    = 'localhost'; 
$user    = 'root'; 
$pass    = ''; 
$db    = 'chat'; 
$table    = 'chat_daten'; 
$conn=mysqli_connect($server, $user,$pass); 
mysqli_select_db($conn,$db); 
?>

Die Felder für Nick und Eintrag und den Senden-Button packen wir in ein Formular:

<?php  
//Config auslesen 
require_once('inc/config.php'); 
  
$heute = date("d.m.Y",time());
echo '<img src="el_logo.jpg">';
echo '<h1>Evis Chat live am '.$heute.'</h1>';

echo ' 
 <form action="index.php" method="post"> 
    <table border="0"> 
        <tr> ';
//Falls ein Nick eingegeben wurde, diesen als default in die Textbox setzen
if(!empty($_POST['nick'])){
    $akt_nick = $_POST['nick'];
}
else{
    $akt_nick='';
};
    
echo '<th>Nickname:</th><td><input type="text" name="nick" value="'.$akt_nick.'" id="textbox1"></td></tr>                 
        <tr> 
            <th>Nachricht:</th><td><input type="text" name="eintrag" value="" id="textbox">&nbsp;<input type="submit" name="eintragen" value="Senden!" id="button">&nbsp;</td> 
        </tr> 
    </table> ';

Damit man den Nick nicht immer neu eingeben muss, frage ich ab ob das Feld schon gesetzt wurde und schreibe dann den Wert als Default rein.

Dann starte ich, wenn auf den Button Senden geklickt wurde, und schreibe mir Nick, Eintrag, Datum und Uhrzeit in die Datenbank:

if(isset($_POST['eintragen'])){ 

if(empty($_POST['nick']) or empty($_POST['eintrag'])){ 
    echo '<script>alert("Bitte Nick UND Message eingeben")</script>'; 
}else{ 
    //Variablen definieren und mit "POST" Daten füllen (Mit htmlspecialchars filtern, Apostrophe maskieren..) 
    $nick = addslashes(htmlspecialchars($_POST['nick'])); 
    $eintrag = addslashes(htmlspecialchars($_POST['eintrag'])); 
    $uhrzeit = date("H:i",time());
    $datum = date("d.m.Y",time());

    
//Nick + Eintrag + datum+uhrzeit in die Datenbank schreiben 
    mysqli_query($conn,"INSERT INTO $table  
                      (nick,eintrag,uhrzeit, datum) VALUES 
                      ('$nick', '$eintrag', '$uhrzeit', '$datum')"); 
    } 

}

Falls kein Nick oder keine Nachricht eingegeben wurden, gibts einen Alert, andernfalls mache ich den Insert mit den aktuellen Daten.

Für die Ausgabe des Chats baue ich mir eine Div mit zwei darin enthaltenen Divs, weil ich Nick und Nachricht linksbündig, die Uhrzeit aber rechtsbündig ausgeben möchte. Das CSS dazu gibt es weiter unten, hier erstmal die Divs für das Chatfenster:

//alle Einträge von heute ausgeben
echo '<div id="chat_fenster" >';
        
        //Nur die Einträge von heute auslesen
          $abfrage = mysqli_query($conn,"SELECT * FROM $table WHERE datum LIKE '$datum'"); 
          while($row = mysqli_fetch_array($abfrage,MYSQLI_ASSOC)) 
          { 
            echo '<div id ="nick_und_message"><span style="color:red">'.$row['nick'].':</span><span style ="color:black">'.$row['eintrag'].'</span></div>
            <div id = "uhrzeit"><span style ="color:green">'.$row['uhrzeit'].'</span></div><br>'; 
          }
echo '</div>';

Als kleines Extra gebe ich rechts neben dem Chatfenster noch eine Liste aller Teilnehmer von heute aus:

//alle Teilnehmer von heute ausgeben
echo '<div id="teilnehmer_fenster" >';
        echo '<h2>Heute im Chat:</h2>';
        //Nur die Teilnehmer von heute auslesen
          $abfrage = mysqli_query($conn,"SELECT DISTINCT nick FROM $table WHERE datum LIKE '$datum'"); 
          while($row = mysqli_fetch_array($abfrage,MYSQLI_ASSOC)) 
          { 
            echo $row['nick'].'<br>'; 
          }
echo '</div>';

Jetzt noch ein wenig CSS-Kosmetik:

<style type="text/css"> 
    
    #textbox{ 
        width:600px; 
        border:1px solid blue; 
    } 
     
    #textbox1{ 
        width:80px; 
        border:1px solid blue; 
    }
    
    #button{ 

        border:1px solid #FF1493; 
        cursor:pointer; 
    } 
     
    #button:hover{ 
        border:1px solid #3399FF; 
    } 
    
    #nick_und_message{
        width:80%;
        float:left;
    }
    #uhrzeit {
        width:20%;
        float:right;
        text-align: right;
    }
    #chat_fenster{
        border:2px solid blue;
        width:750px; 
        height:350px; 
        padding:3px; 
        overflow: auto;
        float:left;
    }
    
    #teilnehmer_fenster{
        float:right;
        width:200px;
    }
</style>

Wichtig ist hier der Eintrag overflow:auto für das Chatfenster, der sorgt für den Scrollbalken. Damit jetzt die bereits vorhandenen Einträge nach oben wegscrollen und der letzte Eintrag immer im Chatfenster ganz unten auftaucht, noch ein kleiner Javascript-Trick:

<script>
window.onload = function()
{
//Chatfenster nach oben wegscrollen, damit immer die neuesten Einträge sichtbar sind
document.getElementById('chat_fenster').scrollTop = 9999999;
}
</script>

Man setzt einfach einen sehr hohen Wert für das Scrollen nach oben, damit ist die Liste immer nach oben weggescrollt und das Ende mit den neuesten Beiträgen bleibt im sichtbaren Bereich.

Das wars – viel Spaß beim Nachbauen! Das liesse sich natürlich noch beliebig erweitern, in einem CMS könnte man z.B. den Chat nur für eingeloggte User freischalten, dann entfällt das Abfragen des Nicks. Man könnte auch noch ein Chat-Archiv anlegen, in dem man die Beiträge der letzten Tage einsehen kann, und man könnte sonst noch allerhand Spielereien veranstalten, da kann sich jeder selber austoben. Ich lass es mal so stehen, als wirklich simple Lösung.

preg_replace & Co. im praktischen Einsatz

Ich bin immer noch an den exportierten Posts aus WordPress, die wir im Q&D-Editor schonmal manuell nachbearbeitet haben. Dabei sind sicher etliche Dinge aufgefallen, die man programmatisch korrigieren könnte, und wie man das praktisch angeht, schreib ich hier mal auf.

Wir haben immer noch die Ausgangstabelle rezepte, in der nur die ID, der post_content und der post_title drinstehen. Damit wir unsere Rohdaten nicht verhunzen, legen wir uns erstmal eine leere Kopie dieser Tabelle an, das geht im phpmyadmin unter „Operationen“, kopiere Tabelle, nur Struktur, oder mit dem simplen SQL:

CREATE TABLE rezepte_arbeit LIKE rezepte

Das kopiert die Struktur inklusive evtl. vorhandener Indizes etc. und wird unser Arbeitspferd. (Falls man mal die Daten mitkopieren möchte, CREATE TABLE table2 SELECT * FROM table1, aber das nur als Anmerkung am Rande)

Zur Kontrolle des Ergebnisses klemmt man sich die (noch leere) Arbeitstabelle in den Q&D-Editor. Spätestens jetzt macht es Sinn, den Namen der Tabelle auf eine Variable zu legen und die beiden SQLs entsprechend anzupassen.

//Arbeitstabelle festlegen
$arbeitstabelle = "arbeit_rezepte";
echo "<h1>Tabelle: ".$arbeitstabelle."</h1>";
...
$sql = "SELECT * FROM ".$arbeitstabelle." WHERE post_content LIKE '%".$filter."%'";
...
$sql = ('SELECT * FROM '.$arbeitstabelle.' WHERE post_content LIKE "%'.$filter.'%" 
LIMIT '.$limit.', '.$ergebnisse_pro_seite.'');

Wer es ganz genau haben möchte, legt auch noch den Dateinamen des Q&D-Editor Skripts auf eine Variable und paßt die Navigationslinks entsprechend an, aber das ist eigentlich schon Fleißarbeit:

//Dateinamen auf Variable legen
$datei = "arbeitspferd.php";
...
if($limit>0){echo '<a href="'.$datei.'?seite_nr='.($limit-1).'"><button type="button">Voriger Datensatz</button></a>';}
echo '<a href="'.$datei.'?seite_nr='.($limit+2).'"><button type="button">Nächster Datensatz</button></a><br>';
...
 //Nav Links alle Seiten
 for ($i=1; $i<=$row_total; $i++) { 
    echo "<a href='".$datei."?seite_nr=".$i."'>".$i."</a> "; 
};

Da die Tabelle noch leer ist, kriegt man erstmal gar nichts angezeigt:

arbeitstabelle_leer

arbeitstabelle_leer

Aber das ist ja OK so und wird sich gleich ändern.

Das Gerüst für die geplanten Aktionen

Ich bastle mir ein kleines Formular mit einem Button zum Starten des Skripts, und gebe der Übersicht halber mal alle Datensätze tabellarisch aus. Das sieht so aus:

<?php
//file:skript_starten.php
header('Content-Type: text/html; charset=utf-8');
require 'connection.php';

//Quell-und Zieltabelle festlegen
$quelltabelle = "rezepte";
$zieltabelle = "arbeit_rezepte";

echo "<h1>Skript starten</h1>";
echo "<h2> Quelle: ".$quelltabelle." Ziel: ".$zieltabelle."</h2>";

//Formular mit Button für Start
echo "<form action ='#' method = 'post'>";
echo "<input type = 'submit' name = 'absenden' value = 'Skript starten'>";
echo "</form>";

if(isset($_POST['absenden'])){
    
    echo "<script type=\"text/javascript\">alert('Skript gestartet');</script>";
    
    //Hier kommt die Action hin
    
    
    }

$sql = "SELECT * FROM ".$quelltabelle."";
$rs_result = $conn->query($sql);
$row_total = $rs_result->num_rows;

echo "Datensätze gesamt: ".$row_total."<br>";

 //Ausgabe in Tabelle - nur Arbeitsversion, kann man für den Betrieb rausnehmen
echo "<table border='1' cellpadding='4'>";
echo "<tr><th>ID</th><th>Titel</th><th>Content</th></tr>";
 
 while($row = $rs_result->fetch_assoc()) {
            
            echo "<tr><td valign='top'>".$row["ID"]."</td>";
            echo "<td valign='top'>".utf8_encode($row["post_title"])."</td>";
            //HTML Source in Textarea ausgeben
            echo "<td valign='top'><textarea rows='5' cols='80'>".utf8_encode($row["post_content"])."</textarea>";
            echo "</td></tr>";
            
 } //Ende von while row = rs_result
 
 echo "</table>";
 
?>

Ich hole mir mit dem require die Connection, lege Quell-Und Zieltabelle auf Variable und baue mir den Button für das Starten des Skripts. Die Kontrollausgabe der aktuellen Quelltabelle ist nicht unbedingt notwendig, das dient nur der Übersicht.

Jetzt gehts zur Sache

Der Ablauf ist folgender: die Datensätze aus der Quelltabelle werden selektiert und die Felder auf Variablen gelegt. An den Variablen nimmt man dann die nötigen Manipulationen vor, und die fertig bearbeiteten Datenfelder werden in ein Array geschrieben. Aus dem Array füllt man dann mit Insert die Zieltabelle. Das Ergebnis kann man dann gleich mal im Q&D-Editor überprüfen. Ich mach dann gleich noch ein Beispiel, hier zuerst mal der Source:

if(isset($_POST['absenden'])){
    
    echo "<script type=\"text/javascript\">alert('Skript gestartet');</script>";
    
    //Hier kommt die Action hin
    
    $sql = "SELECT * FROM ".$quelltabelle."";
    $rs_result = $conn->query($sql);
    
      
    $i=0;
    $temp = array();
    
    while($row = $rs_result->fetch_assoc()) {
            
            $akt_id =$row["ID"];
            $title = $row["post_title"];
            $content = $row["post_content"];
            
            // Hier kann man beliebige Stringfunktionen auf die Felder anwenden
            $content = strip_tags($content);
            
            //Behandelte Strings in Array einlesen
            $temp[$i][id] = $akt_id;
            $temp[$i][titel] = $title;
            $temp[$i][content]= $content;
            $i = $i+1;
            
    }
            
 }
$len_temp = count($temp);

//Array zeilenweise in Zieltabelle wegschreiben
for ($i = 0; $i < $len_temp; $i++){    
  
          $temp_id = $temp[$i][id];
          $temp_titel = $temp[$i][titel];
          $temp_content = $temp[$i][content];
          
          //addslashes ist nötig weil sonst der Insert bei jedem Hochkomma aussteigt
          $sql_insert = "INSERT INTO ".$zieltabelle." (ID, post_title, post_content) 
          VALUES (".$temp_id." ,'".addslashes($temp_titel)."' , '".addslashes($temp_content)."')";
          
          if (mysqli_query($conn, $sql_insert)) {
                      echo "Insert erfolgreich<br>";
                      
                    }
                      
                    else {
                      echo "Fehler beim Update: " . mysqli_error($conn);
                      
                    }
} // end for

Das wars schon – das Skript braucht ein paar Sekunden, nicht ungeduldig werden. Wichtig ist, dass man die einzufügenden Felder beim Insert noch mit einem addslashes() behandelt, weil sonst bei jedem Anführungszeichen im Text das Skript aussteigt.

Ich habe oben nur eine Ersetzung ausgeführt, nämlich das:

$content = strip_tags($content);

Das führt in der Praxis evtl. zu unerwünschten Effekten, weil es wirklich gnadenlos alle HTML-Tags raushaut. Für meine Zwecke besser geeignet ist der strip_tags mit Ausnahmen:

$content = strip_tags($content,'<h2>, <h3>, <ol>, <ul>, <li>');

Das läßt meine Überschriften und die Listen drin, sieht schon besser aus.

mit_liste

mit_liste

Aber da muss jeder selber tüfteln, was für seine Zwecke am besten geeignet ist. Fröhliches googlen nach praktikablen Stringbehandlungen! Und nicht vergessen vor erneutem Starten des Skripts die Zieltabelle mit Truncate zu leeren, sonst fällt der Insert wegen doppelter IDs auf die Nase.

Nachtrag: den Truncate kann man natürlich auch ins Skript mit einbauen, einfach die Action um folgenden Code ergänzen:

//Zieltabelle leeren
    $sql_zap="TRUNCATE TABLE ".$zieltabelle."";
    if (mysqli_query($conn, $sql_zap)) {
                      echo "Truncate erfolgreich<br>";
                      
                    }
                      
                    else {
                      echo "Fehler beim Truncate: " . mysqli_error($conn);
                      
                    }

Das muss natürlich vor dem Insert rein, am Besten setzt man es ganz an den Anfang innerhalb der if(isset($_POST…)-Bedingung. Dann spart man sich das manuelle Leeren der Zieltabelle.

Quick&Dirty Editor Sourcecode

Der Editor kommt mit erstaunlich wenig Code aus, das sind gerade mal knapp 90 Zeilen. Ich gehe das hier mal der Reihe nach durch. Meine Ausgangstabelle heißt rezepte und sieht so aus:

ausgangstabelle

ausgangstabelle

Sie enthält nur die rudimentären Daten meiner Rezepte aus dem Inselfisch-Kochbuch, und zwar die ID, den post_content und den post_title.

tabelle_rezepte

tabelle_rezepte

Wir ändern nur den post_content, Titel und ID bleiben unangetastet.

Zuerst bastle ich mir natürlich die Datenbankconnection:

<?php
//file:connection.php
error_reporting(0);
$servername = "localhost";
$username = "root";
$password = "";
$dbname = "conversion";

 
// Create connection
$conn = new mysqli($servername, $username, $password, $dbname);
// Check connection
if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
} else {echo "Datenbank Connection OK<br>";}
?>

Die wird per require reingeholt, und dann kanns losgehen. Ich habe hier den Algorithmus aus der nummerischen Pagination zweckentfremdet und lasse mir pro Seite genau einen Datensatz anzeigen.

<?php
//file:seitenweise.php
header('Content-Type: text/html; charset=utf-8');
require 'connection.php';


$sql = "SELECT * FROM rezepte";
$rs_result = $conn->query($sql);
$row_total = $rs_result->num_rows;
$gesamte_anzahl = $row_total;

echo "Datensätze gesamt: ".$row_total."<br>";

/* 1 Datensatz pro Seite festlegen*/
$ergebnisse_pro_seite = 1;
$gesamt_seiten = ceil($gesamte_anzahl/$ergebnisse_pro_seite);

if (empty($_GET['seite_nr'])) {
    $seite = 1;
} else {
    $seite = $_GET['seite_nr'];
    if ($seite > $gesamt_seiten) {
        $seite = 1;
    }
}

$limit = ($seite*$ergebnisse_pro_seite)-$ergebnisse_pro_seite;
if($limit<0){$limit =0;}

Zuerst zähle ich die Datensätze, dann lege ich fest, dass pro Seite genau ein Datensatz angezeigt werden soll. Defaultmässig landet der Editor bei Seite 1. Dann berechne ich mir das Limit, also beim wievielten Datensatz die Ausgabe beginnen soll. Das verwenden wir gleich mal für die Navigationsbuttons „Voriger Datensatz“ und „Nächster Datensatz“:

//Debug-Ausgaben und Nav-Buttons
echo "Datensatz = ".($limit+1)." von ".$row_total."<br>";
echo "Datensätze pro Seite = ".$ergebnisse_pro_seite."<br>";
if($limit>0){echo '<a href="seitenweise.php?seite_nr='.($limit-1).'" style="font-weight: bold;"><button type="button">Voriger Datensatz</button></a>';}
echo '<a href="seitenweise.php?seite_nr='.($limit+2).'" style="font-weight: bold;"><button type="button">Nächster Datensatz</button></a><br>';

Dann hole ich mir auch schon den Datensatz mit der aktuellen Seitennummer:

//Selektiere Datensatz mit der aktuellen Seitennummer
$sql = ('SELECT * FROM rezepte LIMIT '.$limit.', '.$ergebnisse_pro_seite);
$result = $conn->query($sql);

Da meine Variable $ergebnisse_pro_seite den Wert 1 hat, wird ein Datensatz pro Seite ausgegeben. Den zeige ich mir in einer while-Schleife in einer zweispaltigen Tabelle an. Die Tabelle enthält eine textarea, in die kommt der HTML-Code. In der zweiten Spalte wird die Vorschau ausgegeben. Zuerst schubse ich mal die ID des aktuellen Datensatzes in eine Variable, die brauchen wir später für den Update.

Dann frage ich ab, ob der „Änderungen speichern“-Button gedrückt wurde. Wenn ja, zeige ich den geänderten Inhalt des Editorfensters in der Tabelle an, wenn nein einfach den Originalcontent so wie er aus der Abfrage kommt.

//Ausgabe in Tabelle mit Formular
 while($row = $result->fetch_assoc()) {
            $akt_id = $row["ID"];
            echo "Aktuelle Datensatzid: ".$akt_id."<br>";
            
            echo "<form action ='#' method = 'post'>";
            echo "<input type = 'submit' name = 'absenden' value = 'Änderungen speichern'>";
            echo "<table border='1' cellpadding='4'>";
            echo "<th>".utf8_encode($row['post_title'])." HTML</th><th>Vorschau</th></tr>";
            
            //Wenn Speichern gedrückt wurde, editierten Content aus der Textarea ausgeben 
            if(isset($_POST['absenden'])){$inhalt = $_POST['content'];}
            else {$inhalt = utf8_encode($row['post_content']);}
                        
            echo "<tr>";
            //HTML Source in Textarea ausgeben
            echo "<td valign='top'><textarea rows='50' cols='80' name='content' id='content'>".$inhalt."</textarea>";
            echo "</td>";
            
            //HTML Vorschau ausgeben
            echo "<td valign='top'>".$inhalt."</td>";
            echo "</tr>";
            echo "</table>";
            echo "</form>";

Jetzt fehlt nur noch der Update, der wird beim Klicken des „Änderungen speichern“-Buttons ausgelöst:

if (isset($_POST['absenden'])){
                            
            //***********Action
            
            $sql = "UPDATE rezepte SET post_content =
'".utf8_decode(addslashes($_POST['content']))."' WHERE ID=".$akt_id."";
            //*************End Action
            
           if (mysqli_query($conn, $sql)) {
              echo "Record updated successfully";
              echo "<script type=\"text/javascript\">alert('Änderungen erfolgreich gespeichert');</script>";
            }
              
            else {
              echo "Error updating record: " . mysqli_error($conn);
              echo "<script type=\"text/javascript\">alert('Fehler beim Speichern');</script>";
            }    
    
    } //Ende von isset absenden
 } //Ende von while row = result

Im Update überschreibe ich den Inhalt von post_content mit dem Inhalt des Editorfensters, die steckt ja in der Variablen:

$_POST['content']

und zwar im Datensatz mit der aktuellen ID. Wegen der deutschen Umlaute muss ich hier mit utf8- encode und -decode arbeiten, das muß man bei einer anderen Datenbankkollation evtl anpassen. Ausserdem brauche ich noch einen addslashes(), weil meine Texte Anführungszeichen enthalten können. Dann mache ich noch eine kleine Fehlerbehandlung und gebe einen Javascript-Alert aus, ob der Update funktioniert hat oder nicht. Das wars auch schon – jetzt fehlt nur noch die nummerische Pagination zum anklicken des i-ten Datensatzes:

 //Nav Links alle Seiten
 for ($i=1; $i<=$row_total; $i++) { 
    echo "<a href='seitenweise.php?seite_nr=".$i."'>".$i."</a> "; 
};

Bitteschön, fertig ist der Q&D-Editor!

vanillekipferl_im_editor

vanillekipferl_im_editor

Tipp, auch quick&dirty:

Man kann ganz schnell eine (primitive) Filterfunktion basteln, wenn man die beiden SQL-Strings um eine „LIKE“-Klausel ergänzt:

$sql = "SELECT * FROM rezepte WHERE post_content LIKE '%".$filter."%'";
...
//Selektiere Datensatz mit der aktuellen Seitennummer
$sql = ('SELECT * FROM rezepte WHERE post_content LIKE "%'.$filter.'%" LIMIT '.$limit.', '.$ergebnisse_pro_seite.'');

Die Variable $filter setzt man am Anfang des Skripts auf den gewünschten Wert, zum Beispiel auf „caption“ oder „<ul>“ oder was auch immer. Dabei muss man aber berücksichtigen, was die aktuelle Datenbankkollation als Suchkriterien akzeptiert, bei Umlauten und Sonderzeichen kommt man da sonst zu unerwarteten Ergebnissen. Aber so als kurze Arbeitshilfe ist das schon mal ganz nützlich.