Minimalistisches CMS

An den großen Contentmanagement-Systemen hat mich immer der enorme Umfang genervt. Da werden Unmengen an MBs auf den Webserver geladen und diejenigen, die im Alltag die Inhaltspflege übernehmen sollen, müssen aufwendig angelernt werden. Mal ehrlich: es ist fast einfacher, HTML, CSS und JavaScript von der Pike auf zu erlernen als die Redaktionsoberfäche von Typo3  (oder WordPress 😉 ) zu bedienen. Das hat mich dazu animiert, das onefile_cms zu schreiben. Eine einzige kleine PHP-Script-Datei und ein HTML-Template mit nur 5 Variablen sind die Zutaten. Natürlich kann damit kein Enterprise-Portal entwickelt werden, aber für eine mehrseitige kleine Homepage mit 1-stufigem Menü reicht’s.

<?php

// onefile_cms by G. Drinkmann Ver 0.12 (29.05.2013)
// Contentmanagement-System mit Wiki-Dialekt zur Contentbearbeitung
// Dieser Quelltext kann völlig frei verwendet werden

error_reporting(E_ALL);

// ############################ HTML-Template ##########################################

// das Template sollte folgendes enthalten:

// Variablen im Template:
// <!-- {sitetitle} -->
//    wird ersetzt durch Homepage-Titel
// <!-- {menutitle} -->
//    der Menüname der angezeigten Seite
// <!-- {ul-menu} -->
//    Menü als automatisch erzeugte ul-Liste (CSS s.u.)
// <!-- {content-width} -->
//    wird ersetzt durch Content-Breite
// <!-- {content} -->
//    der eigentliche Seiteninhalt aus der Redaktionsseite

// CSS:
// #menu   id des ul-Elements
// #menu li   z.B.{display:inline; list-style:none;}
// #menu a.menunormal   class-Attribut des a-Elements im Menü
// #menu a.menucurrent   class-Attribut des aktiven a-Elements im Menü
// img.Lfloat   class-Attribut von linksbündig formatierten Bildern mit Text-Umlauf
// img.Rfloat   class-Attribut von rechtsbündig formatierten Bildern mit Text-Umlauf

// Konvention:
// Namen der Bilder des Templates sollten mit "ONEFILECMS_" beginnen

// ------- HTML-Template-Datei ---------------------
$ggtplfile = "tpl1.html";

// ############################ Script-Variablen #######################################

// ------- Homepage-Titel --------------------------------------------------------------
$ggsitetitel = "Kiosk JOJO";

// ------- md5-Passwort (zu erstellen bei z.B. http://www.functions-online.com/md5.html)
$ggpass = "e6b3cd51a7afb493493d0c4a0bb0c8b2"; // jo33jo (bitte comment löschen)

// ------- Edit-URL-Parameter (m=) zum Aufruf der Redaktionsseite ----------------------
$ggeditpara = "edit"; // ohne Leer- oder Sonderzeichen !

// ------- Template ist XHTML ----------------------------------------------------------
$ggisxhtml = false;

// ------- Content-Breite --------------------------------------------------------------
$ggcontwidth = "750px";

// ############################ ab hier Chefsache !! ###################################
$ggcontfile = "content1file.txt";
$ggxsl = ($ggisxhtml) ? " /" : "";
function ggerror($ggmsg) {
    global $ggcontent, $ggsitetitel, $ggxsl;
    $ggcontent[0]['mtitle'] = "FEHLER";
    $ggcontent[0]['read'] = "<i>".$ggmsg."</i><br".$ggxsl."><br".$ggxsl.">Die Seite \"".$ggsitetitel."\" ist momentan nicht verf&uuml;gbar.<br".$ggxsl.">Versuchen Sie es bitte sp&auml;ter nocheinmal.<br".$ggxsl.">Danke !";
}
if (isset($_GET['m'])) { // Check des GET-Parameter
    if ($_GET['m'] === $ggeditpara) $ggpar = "e";
    else $ggpar = intval($_GET['m']);
} else $ggpar = 0;
if ($ggpar === "e") { // Seite editieren
    if (md5($_POST['pass']) === $ggpass && isset($_POST['content'])) {
        file_put_contents("./".$ggcontfile, urldecode($_POST['content']));
        if (isset($_FILES['pic'])) {
            if (preg_match("/\.(jpg|gif|png)$/i", $_FILES['pic']['name']) || ($ggtplfile === $_FILES['pic']['name'])) {
                move_uploaded_file($_FILES['pic']['tmp_name'], "./".$_FILES['pic']['name']);
            }
        }
        if (isset($_POST['delimg'])) {
            for ($i=0; $i<count($_POST['delimg']); $i++) {
                @unlink("./".urldecode($_POST['delimg'][$i]));
            }
        }
    }
    if (file_exists("./".$ggcontfile)) {
        $ggfstr = file_get_contents("./".$ggcontfile);
    } else $ggfstr = utf8_encode("; Bitte mit Inhalt füllen");
    $ggpiclis = ""; // vorhandene Bilder listen
    if ($gghandle = opendir('.')) {
        while (false !== ($ggfile = readdir($gghandle))) {
            if (preg_match("/\.(jpg|gif|png)$/i", $ggfile) && (!preg_match("/^ONEFILECMS_/", $ggfile))) {
                $ggpiclis .= "{{".$ggfile."}}<input type='checkbox' name='delimg[]' value='".$ggfile."' title='Bild l&ouml;schen'><img onmouseover='document.getElementById(\"preview\").src = this.src; document.getElementById(\"preview\").className=\"gvisible\"' onmouseout='document.getElementById(\"preview\").className=\"ghidden\"' class='pici' src='".$ggfile."'> &nbsp;&nbsp;";
            }
        }
        closedir($gghandle);
    }
    if (!$ggpiclis) $ggpiclis = "<i>Keine</i>";
    $ggaction = $_SERVER['PHP_SELF']."?m=".$ggeditpara;
    $ggeditsite = <<<EOTE
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>{$ggsitetitel} editieren</title>
<style type="text/css">
* {font-family:arial}
img.pici {border:0;padding:0;margin:0;height:18px}
img.gvisible {visibility:visible}
img.ghidden {visibility:hidden}
</style>
</head>
<body style="background-color:#ebebeb">
<h2>Website <a href="{$_SERVER['PHP_SELF']}" style="color:red" target="_blank"><i>{$ggsitetitel}</i></a> editieren (Inhalts-Breite <span style="color:red">{$ggcontwidth}</span>):</h2>
<p style="font-size:10px">Zeile mit ; am Anfang = Kommentar<br>
M--- am Anfang der Zeile = neuer Men&uuml;titel<br>
HTML-Tags werden ausgef&uuml;hrt<br>
== am Anfang der Zeile = gro&szlig;e &Uuml;berschrift<br>
=== am Anfang der Zeile = kleinere &Uuml;berschrift<br>
| am Anfang der Zeile = Tabelle (weitere Zellen mit | trennen)<br>
* am Anfang der Zeile = Liste<br>
{{*.jpg oder gif oder png}} = Bild in Originalgr&ouml;&szlig;e<br>
{{*.jpg oder gif oder png|L oder R}} = Bild links bzw. rechts mit Text-Umlauf<br>
[[Men&uuml;index|Linktext oder -bild]] = interner Link (Index beginnt bei 0)<br>
[[Linkadresse|Linktext oder -bild]] = externer Link<br>
**W&ouml;rter** = Fett<br>
//W&ouml;rter// = Kursiv<br>
---- = horizontale Linie<br>
((x)) am Anfang = Text-Umlauf (Float) beenden</p>
<form name="f1" action="{$ggaction}" method="post" enctype="multipart/form-data">
<p><b>Inhalt</b><br>
<textarea name="content" style="width: 600px; height: 300px; font-size: 12pt">{$ggfstr}</textarea></p>
<p id="piclist"><b>Hochgeladene Bilder:</b><br>
{$ggpiclis}</p>
<p><b>Datei hochladen:</b> jpg, gif, png oder Template <i>{$ggtplfile}</i><br>
<input type="file" name="pic" size="61"></p>
<p>Passwort <input type="password" onkeydown="document.getElementsByName('OK')[0].disabled=false" name="pass" style="width: 200px;">
<input style="font-weight:bold" type="submit" name="OK" value="Seite speichern" disabled="disabled"></p>
</form>
<img class="ghidden" id="preview" src="nixda" style="position:absolute; left:3px; top:3px; border:solid #808080 30px;">
</body>
</html>
EOTE;
    echo $ggeditsite;
} else { // normaler Seitenaufruf
    if (file_exists("./".$ggtplfile)) {
        $ggsitestr = file_get_contents("./".$ggtplfile);
    } else die("TEMPLATE NICHT VORHANDEN !");
    $ggcontent = array(); // Initialisierung des Content-Array
    $ggcount=-1;
    if (file_exists("./".$ggcontfile)) { // Steuerdatei ins Content-Array einlesen
        $ggf = fopen("./".$ggcontfile, 'rb');
        while (!feof($ggf)) {
            $ggtmp = rtrim(fgets($ggf));
            if (substr(ltrim($ggtmp),0,4)==="M---") {
                $ggcount++;
                $ggcontent[$ggcount]['mtitle'] = ltrim(substr(ltrim($ggtmp),4));
                $ggcontent[$ggcount]['mtitleclass'] = "menunormal";
                $ggcontent[$ggcount]['read'] = "";
            } elseif (substr(ltrim($ggtmp),0,1)!==";") {
                $ggcontent[$ggcount]['read'] .= $ggtmp."<brbr>";
            }
        }
        fclose($ggf);
        if ($ggcount === -1) ggerror("Fehler: Kein Men&uuml; in Steuer-Datei eingetragen");
    } else ggerror("Fehler: Steuer-Datei nicht vorhanden");

    if ($ggpar>$ggcount) $ggpar = 0;
    $ggcontent[$ggpar]['mtitleclass'] = "menucurrent";
    $ggsitestr = str_replace("<!-- {sitetitle} -->", $ggsitetitel, $ggsitestr);
    $ggsitestr = str_replace("<!-- {menutitle} -->", $ggcontent[$ggpar]['mtitle'], $ggsitestr);
    $ggsitestr = str_replace("<!-- {content-width} -->", $ggcontwidth, $ggsitestr);
    // --- Menü zusammenbauen ---
    $ggmenustr = "<ul id=\"menu\">\n"; 
    for ($i=0; $i<count($ggcontent); $i++) {
        $ggmenustr .= "<li><a href=\"".$_SERVER['PHP_SELF']."?m=".$i."\" class=\"".$ggcontent[$i]['mtitleclass']."\" target=\"_self\">".$ggcontent[$i]['mtitle']."</a></li>\n";
    }
    $ggmenustr .= "</ul>\n";
    $ggsitestr = str_replace("<!-- {ul-menu} -->", $ggmenustr, $ggsitestr);
    // --- Content zusammenbauen ---
    $ggcont = $ggcontent[$ggpar]['read'];
    $ggcont = explode("<brbr>",$ggcont);
    $ggcontstr = "";
    $istab = 0; // Tabelle
    $isul = false; // Bullet-Liste
    $ggcleardiv = "<div style='clear:both;visibility:hidden;width:0;height:0;margin:0;padding:0;border:0;'></div>\n";
    for ($i=0; $i<count($ggcont); $i++) {
        if (substr(ltrim($ggcont[$i]),0,5)==="((x))") {
            $ggisclear = " style='clear:both'";
            $ggcont[$i] = substr(ltrim($ggcont[$i]),5);
        } else $ggisclear = "";
        if (substr(ltrim($ggcont[$i]),0,1)==="|") {
            $ggtra = explode("|",substr(ltrim($ggcont[$i]),1));
            if (!$istab) {
                $ggcontstr .= "<table".$ggisclear.">\n";
            } elseif ($istab !== count($ggtra)) {
                $ggcontstr .= "</table>\n<table".$ggisclear.">\n";
            }
            $istab = count($ggtra);
            $ggcontstr .= "<tr>";
            for ($j=0; $j<$istab; $j++) {
                $ggcontstr .= "<td>".trim($ggtra[$j])."</td>";
            }
            $ggcontstr .= "</tr>";
        } else {
            if ($istab) $ggcontstr .= "</table>\n";
            $istab = 0;
            if ((substr(ltrim($ggcont[$i]),0,1)==="*") && (substr(ltrim($ggcont[$i]),1,1)!=="*")) {
                if (!$isul) $ggcontstr .= "<ul".$ggisclear.">\n";
                $isul = true;
                $ggcontstr .= "<li>".trim(substr(ltrim($ggcont[$i]),1))."</li>";
            } else {
                if ($isul) $ggcontstr .= "</ul>\n";
                $isul = false;
                if (substr(ltrim($ggcont[$i]),0,3)==="===") {
                    $ggcontstr .= "<h2".$ggisclear.">".substr(ltrim($ggcont[$i]),3)."</h2>";
                } elseif (substr(ltrim($ggcont[$i]),0,2)==="==") {
                    $ggcontstr .= "<h1".$ggisclear.">".substr(ltrim($ggcont[$i]),2)."</h1>";
                } elseif (trim($ggcont[$i])==="----") {
                    $ggcontstr .= "<hr".$ggisclear.">";
                } elseif ($ggisclear) {
                    $ggcontstr .= $ggcleardiv.$ggcont[$i]."<br".$ggxsl.">";
                } else $ggcontstr .= $ggcont[$i]."<br".$ggxsl.">";
            }
        }
        $ggcontstr .= "\n";
    }
    $ggcontstr = preg_replace("/{{(\S.*\.(jpg|JPG|gif|GIF|png|PNG))\|L}}/", "<img src=\"$1\" style=\"float:left\" class=\"Lfloat\"".$ggx.">", $ggcontstr); // Bilder mit float links
    $ggcontstr = preg_replace("/{{(\S.*\.(jpg|JPG|gif|GIF|png|PNG))\|R}}/", "<img src=\"$1\" style=\"float:right\" class=\"Rfloat\"".$ggx.">", $ggcontstr); // Bilder mit float rechts
    $ggcontstr = preg_replace("/{{(\S.*\.(jpg|gif|png))}}/i", "<img src=\"$1\"".$ggx.">", $ggcontstr); // Bilder
    $ggcontstr = preg_replace("/\[\[(\d+)\|(\S.*\S|\S)]]/", "<a href=\"".$_SERVER['PHP_SELF']."?m=$1\" target=\"_self\">$2</a>", $ggcontstr); // interne Links
    $ggcontstr = preg_replace("/\[\[(\S+)\|(\S.*\S|\S)]]/", "<a href=\"$1\" target=\"_blank\" title=\"Link in neuem Fenster\">$2</a>", $ggcontstr); // externe Links
    $ggcontstr = preg_replace("/ /", "&nbsp;", $ggcontstr); // Leerzeichen
    $ggcontstr = preg_replace("/&nbsp;(\S)/", " $1", $ggcontstr);
    $ggcontstr = preg_replace("/\*\*(\S.*\S|\S)\*\*/", "<strong>$1</strong>", $ggcontstr); // Fett
    $ggcontstr = preg_replace("#//(\S.*\S|\S)//#", "<em>$1</em>", $ggcontstr); // Kursiv
    $ggsitestr = str_replace("<!-- {content} -->", $ggcontstr, $ggsitestr);
    $ggsitestr .= $ggcleardiv;
    echo $ggsitestr;
}
?>

Also, man nehme den Quelltext, erstelle daraus eine Datei names index.php, fülle die Variablen aus, erstelle eine Template-Datei (mit dem Namen wie bei den Variablen angegeben) und lade beide Dateien (inklusive der zum Template gehörigen Bilder) auf den Webserver. Die index.php-Datei beinhaltet beides: die Maschinerie der eigentlichen Website für den Betrachter und die der Redaktion. Die Redaktionsseite wird über den Link <DeineURL>/index.php?m=<Variable URL-Parameter> aufgerufen. Ok, es sind mehr als nur eine Datei im Spiel, doch das eigentliche Programm besteht in der Tat nur aus dieser einen PHP-Datei.

Das Template sollte eine ganz normale HTML-Seite sein. Die Namen der Templatebilder sollten mit „ONEFILECMS_“ beginnen. Folgende Variablen können in diesem Template plaziert sein:

  • <!– {sitetitle} –> der Homepagetitel
  • <!– {menutitle} –> der Menüname der angezeigten Seite
  • <!– {ul-menu} –> Seiten-Menü als automatisch erzeugte ul-Liste
  • <!– {content-width} –> Breite des Inhaltbereichs
  • <!– {content} –> der eigentliche Seiteninhalt

Hier ist eine kleine Testseite zur Anschauung: der Kiosk des (fiktiven) Jojo

kioskjojo

Das zugehörige Template ist die Datei tpl1.html. Im Quelltext dieser Seite sind die Variablen sichtbar.

Jojo’s Kiosk kann mit der Edit-Seite bearbeitet werden.

kioskjojoedit

Dabei wird zur leicht erlernbaren Bearbeitung ein Wiki-Dialekt angewendet. Hat Ähnlichkeit mit Creole. Zum Speichern wird JEDESMAL das Paßwort abgefragt. Das ist ein wenig lästig, aber sicher.