CMS im Eigenbau

Download Report

Transcript CMS im Eigenbau

CMS im Eigenbau
Vom Konzept zum fertigen
Content Management System.
Ein Vortrag von Markus-Hermann Koch
1. Warum _noch_ ein CMS?
Was spricht dagegen?
• Es gibt bereits eine Vielzahl guter, zum Teil sogar
freier CMS. Joomla, Wordpress, Typo 3, ...
• Diese werden seit Jahren entwickelt, bieten für
vieles gute Standardlösungen und verfügen über
ein ausgefeiltes Back End.
• Ihre Installation ist mitunter verblüffend einfach.
• Der Zeitaufwand einer Neuentwicklung ist hoch.
1. Motivation
2
Was spricht dafür?
• Umsetzung selbstgewählter Standards.
Nicht nur in punkto HTML, sondern auch in CSS,
JavaScript, PHP und allen anderen Komponenten.
• Das System wird auf die Inhalte der Site angepasst
und nicht andersherum.
• Sie verstehen Ihr System und wissen welche
Änderungen für einen gewünschten Effekt
erforderlich sind.
• Man kann dabei einiges lernen!
1. Motivation
3
Warum das Original verwerfen?
1. Motivation
4
1. Motivation
5
Richtlinien für die Neufassung
Konsequenzen aus dem Original:
• Wartbarkeit. Sämtlicher Quellcode soll mit Hilfe normaler
Texteditoren vernünftig lesbar und bearbeitbar sein.
• Verzeichnisse sollen automatisch erzeugt werden.
• Trennung von Struktur und Textinhalt.
• Vermeidung von Redundanz.
Insbesondere soll es eine einfach gestrickte, leere HTMLVorlagendatei geben, die die immer gleiche Grundseite enthält
und in die die eigentlichen Textinhalte über PHP eingefügt
werden.
1. Motivation
6
2. Aufbau der HTML-Vorlage
CSS-Zen-Garden: http://www.csszengarden.com/tr/deutsch/
<?php
• Einbinden der Initialisierungsskripte und der benötigten Objektklassen.
• Auswerten der _GET und _POST-Parameter. Davon abhängig:
Welche CSS sollen eingebunden werden?
Welche Inhalte werden aus der Datenbank/dem Cache geladen?
Ggf. Datenbankinhalte in HTML umsetzen.
Alle diese Daten in werden in Variablen abgelegt.
(Das hält den HTML-Teil sauber und lässt den HTTP-Header lange variabel.)
?>
<html>DIV-orientierter Aufbau in den die Variablen eingefügt werden.</html>
2. Die Vorlage index.php5
7
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/init.php5';
require_once $i_root . $i_scripts . $i_kDB_fname;
... [Weitere benötigte Objektklassen]
... [Auswerten der _GET- und ggf. _POST-Parameter.
Ggf. Auführung zusätzlicher Skripte]
... [Laden der Linklisten, des Hauptinhaltes, inhaltsabhängiger
CSS-Anweisungen und ggf. der Werbung aus der Datenbank und/oder
dem Cache. Diese Daten werden in Variablen abgelegt]
... [Ggf. Nachbearbeitung dieser Inhalte. Bis zu diesem Zeitpunkt
wurde noch kein einziges Zeichen an den Browser geschickt.]
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
2. Die Vorlage index.php5
8
<head>
... [Metadaten]
<!-- Eingebunden werden: common.css; main.css aus htDisplay stammendes CSS -->
<link rel='shortcut icon' type='image/x-icon'
href='<?php echo $i_protocol.$_SERVER['HTTP_HOST']; ?>/favicon.ico' />
<link rel='stylesheet' type='text/css'
href='<?php echo $i_protocol.$i_html.$i_styles_karriere.$i_common_css_fname; ?>'
media='all' />
<link rel='stylesheet' type='text/css'
href='<?php echo $i_protocol.$i_html.$i_styles_karriere.$i_main_css_fname; ?>'
media='all' />
<!--[if IE 6]>
<link rel='stylesheet' type='text/css' href='<?php echo $i_protocol.$i_html.$i_styles_karriere.
$i_mainIE6_css_fname; ?>' media='<?php echo $i_media; ?>' />
<![endif]-->
2. Die Vorlage index.php5
9
<?if($i_media!=$i_std_media):?>
... [Einbinden von Media-bezogenem CSS]
<?endif?>
<?php echo $dp->getCssLink(); ?>
<title>KARRIERE handbuch - Koch Management Consulting</title>
<!-- Java Script dessen einziger Zweck die vertikale Dehnung des Master-Div -->
<!-- auf Bildschirmhoehe ist. Nein, height: 100%; tut's nicht! -->
<script src='<?php echo $i_protocol.$i_html.$i_scripts.$i_vScale_fname; ?>'
type='text/javascript'></script>
</head>
<body onload='setHeight()'>
<div id='master'> <!-- Saemtliche Inhalte umschliessender Master Container -->
<div id='title'> <!-- Container fuer das Titelbanner -->
<?php print($dp->replaceAliases('{{IMG'.$i_gif_head.';kh}}')); ?>
</div>
2. Die Vorlage index.php5
10
<div id='content'> <!-- Container fuer den zentralen Inhalt -->
<?php print($cContent); ?>
</div>
<div id='left'> <!-- Linkblock auf der linken Seite. -->
<?php print(staticTools::linkList(1,20,array(2,7,11,14),$activeLink,'kh',
NULL,$addLinksLeft,$db)); ?>
</div>
<div id='right'> <!-- Linkblock auf der rechten Seite. -->
<?php print(staticTools::doImpressum($db,$dp,$activeLink)); ?>
<?php print(staticTools::linkList(23,40,array(1,4,7),$activeLink,'kh',
NULL,$addLinksRight,$db)); ?>
</div>
<div id='bottom'> <!-- Container fuer die Fussnote -->
<?php ... [Logo und Suchfeld einbinden / Debug-Code] ?>
</div>
2. Die Vorlage index.php5
11
</div> <!-- End of: master -->
<!-... [Einige Kommentare zum Code]
Die Vorlage fuer dieses Template lieferte der Css Zen Garden.
Eine wunderschoene Site, die sich der Verbreitung von CSS gewidmet hat.
Da Sie offenbar Interesse an Quellcode haben, schauen Sie ruhig auch dort einmal vorbei:
http://www.csszengarden.com/
P.S.: Diese Site verwendet keine Frames. Es ist also unerheblich
ob Frames von Ihrem Browser unterstuetzt werden. ;-)
-->
</body>
</html>
2. Die Vorlage index.php5
12
<div>s wie im Zen-Garden
CSS-Zen-Garden: http://www.csszengarden.com/tr/deutsch/
2. Die Vorlage index.php5
13
Interessante CSS-Eigenschaften
Inhalte sollen gut aussehen auf IE 7+, Firefox 2+, Lynx.
margin: auto: Zentriert relativ positionierte Blockelemente.
float, clear: Erlauben und steuern das horizontale
Nebeneinander von relativ positionierten Blockelementen.
position: Ob relative, absolute, static oder fixed, mit position
lassen sich die Blockelemente im Browser positionieren.
overflow: Scrollbars in Blockelementen.
Sehr empfehlenswert: http://de.selfhtml.org/
2. Die Vorlage index.php5
14
3. Das wichtigste PHP
Die vier zentralen Bausteine des CMS
init.php5: Immer zuerst ausgeführtes Config-Script. Definiert globale, das
CMS beschreibende Variablen und ruft seinerseits ein weiteres Script
init<Plattform>.php5 auf, welches Daten zur Serverumgebung enthält.
class Kdb: Ein eigenes Datenbankobjekt. Sämtliche DB-Zugriffe erfolgen über
Instanzen dieses Objektes.
class htDisplay und Ableitungen: Verarbeiten die Datenbankinhalte zu
HTML-Code oder PDF-Dokumenten.
class khAction/fgAction: Enthalten Code für Funktionen die
dynamischen Inhalt erzeugen. Diese Klassen werden aktiv, wenn ein GET/POSTParameter ´action´ vorliegt und als Wert den Namen einer definierten Methode
enthält.
3. Die Programmstruktur
15
4. Die Datenbankstruktur
4. Die Datenbankstruktur
16
Es kommen vier Arten von Inhalten vor:
Standardinhalte, Werbeinhalte, statische Inhalte und dynamische Inhalte.
a) Standardinhalte. Aus den SQL-Tabellen index_<clt> und content_<lng>_<clt>
Diese bilden das Gros der Inhalte der Site. Die index_<clt>-Tabellen stellen eine
inhaltslose Baumstruktur voneinander abhängiger Index-Knoten zur Verfügung.
Jedem Knoten ist eine cid (Content-Id) zugeordnet.
Die cid entspricht einer Zeilennummer in den Tabellen content_<lng>_<clt>.
In den sprachbezogenen Content-Tabellen liegen die eigentlichen Überschriften
und Textinhalte.
4. Die Datenbankstruktur
17
Auszüge aus der Index-Content-Struktur.
Auszug aus index_kh
Auszug aus content_de_kh
4. Die Datenbankstruktur
18
Interessante SQL-Kommandos
SHOW COLUMNS FROM <Tabellenname>: Liefert eine Tabelle mit
Informationen über die Spalten der Zieltabelle zurück: Datentyp, Defaultwert,
Primärschlüssel, ...
<Wert1> REGEXP <Wert2>: SQL kennt durchaus reguläre Ausdrücke!
<Tabelle1> INNER JOIN <Tabelle2>: Nimmt beide Datensätze und liefert (als
Menge betrachtet) ihr kartesisches Produkt zurück. So wird z.B. aus den
Datensätzen (a,b,c) und (1,2) der Datensatz (a1,a2,b1,b2,c1,c2). Das ist toll! Bsp.:
SELECT ‘title‘ FROM `index_kh` INNER JOIN `content_kh` ON `index_kh`.
`cid` = `content_<lng>_kh`. `id` WHERE `index_kh`.`id` = <con>;
Liefert für beliebige <lng> den Title eines beliebigen <con> des kh zurück.
Sehr empfehlenswert: http://www.sqldocu.com/
Zeigt sehr viele seiner Kommandos auch als Code an: PHP-MyAdmin.
4. Die Datenbankstruktur
19
5. Die Darstellungsklassen <cconv>Disp
In den Datenbankeinträgen und im Quellcode so wenig HTML wie möglich.
• HTML unterliegt schwankenden Standards.
Keine Lust 10.000 Datenbankeinträge nachzubearbeiten!
• HTML-Code ist relativ umfangreich und Datenbankeinträge sollten
einigermaßen „schlank“ ausfallen.
Meine Lösung:
• Trage Artikel in vereinfachter, am besten Fließtextform in die Datenbank ein.
• Weise dem Artikel eine „Art“ zu. Z.B. twocol für zweispaltige Linklisten oder
tabular für Inhalte, die Tabellen enthalten sollen.
• Schreibe Ableitungen twocolDisp und tabularDisp der Darstellungsklasse
htDisplay, welche alle Fähigkeiten von htDisplay erben, aber diejenige
Methode, die dort einfach nur die Inhalte aus der Datenbank holt, überschreibt:
htDisplay->conTpl(..) liest einen Datenbankeintrag ein und liefert ihn zurück.
twocolDisp->conTpl(..) liest ihn ein und verarbeitet ihn vor dem Zurückliefern.
• Bei Bedarf zusätzlich: Verfassen und Einbinden von twocolCon.css
5. Die Darstellungsklassen
20
Aus der Datenbank auf den Monitor: Eine twocol-Linkliste
Ebenfalls aus der Db: Der Content Converter für diesen Artikel ist twocol
twocolDisp ergänzt den HTML-Header auch um einen Link auf twocolCon.css
5. Die Darstellungsklassen
21
Manchmal praktisch: Ein Artikel - Zwei Displayer
Der Artikel, wie er in der Datenbank steht.
Für den Browser interpretiert von einem Objekt der Klasse standardDisp:
5. Die Darstellungsklassen
22
Für den Acrobat Reader interpretiert von einem Objekt der Klasse standardPdf:
Anmerkung: So etwas geht zügig und vergleichsweise einfach mit der ToolKlasse des PHP-Open Source Projektes fpdf: http://www.fpdf.de/
5. Die Darstellungsklassen
23
Interessante PHP5-Kommandos
$cconv=‘twocolDisp‘; $displayer = new $cconv(...);
... ist ein durchaus funktionierendes Codefragment! Sehr nützlich, wenn
man den Namen der zu instanzierenden Klasse vorher nicht kennt.
preg_replace(...) und eine Reihe verwandter Funktionen bringen die
Möglichkeiten von Perl Regular Expressions nach PHP. (250 mal)
header(...) muss aufgerufen werden, bevor das erste Zeichen an den
Browser geschickt wurde. Erlaubt die Ergänzung des HTTP-Headers und
somit z.B. Weiterleitungen (Location), veränderte MIME-Types (ContentType), Browser-Cache-Kontrolle (Expires, Cache-Control, Pragma: nocache) und vieles mehr.
mb_detect_encoding(...), mb_convert_encoding(...):
Umkodieren von Textinhalten zum Beispiel von UTF-8 in ASCII.
Äußerst empfehlenswert: Programmieren mit PHP von Rasmus Lerdorf et Al.
Erschienen im O‘Reilly-Verlag. ISBN-10 3-89721-473-3.
5. Die Darstellungsklassen
24
6. Performance-Fragen
Ich bezeichne einen Vorgang als teuer, wenn der Server für seine
Bearbeitung stark beansprucht wird.
6. Performance-Fragen
25
Festplatten-Caching:
• Nachdem der an den Client zu sendende Inhalt bestimmt wurde diesen
parallel auch in einer Datei auf der Festplatte ablegen.
• Jedes mal wenn eine Anfrage an den Server geht erst überprüfen, ob der
geforderte Inhalt nicht bereits in einer noch einigermaßen aktuellen
Cache-Datei vorliegt und ggf. einfach diesen Inhalt laden.
Meine Lösung:
Die Cache-Dateien für die Hauptinhalte heißen
content_<id>_<lng>_<clt>_<media>.cch
Sie sind per Default für 24h aktiv und gliedern sich intern in 2 Blöcke:
HTML für den Header<CSS==CACHE==CONTENT>
HTML für den eigentlichen Inhalt
6. Performance-Fragen
26
Am Rande: Die Standardausgabe von PHP ist der Zielbrowser. PHP
bietet aber einige Funktionen, die es erlauben die Ausgabe erst einmal in
Variablen abzufangen.
PHP-Stichworte dazu: ob_start(), ob_get_contents(), ob_end_clean()
Anwendungsbeispiel auf Karrierehandbuch.de: Das Karrierehandbuch
verfügt über ein auf bbPress laufendes Forum, dessen Funktionen dazu
neigen, ihre Ausgaben direkt per print(...) auszugeben.
Mit den ob_-Tools kann ich diese Ausgaben in Variablen abfangen, sie
z.B. mit $displayer->replaceAliases(...) nachbearbeiten und erst dann
anzeigen lassen.
Ohne die gesamte BB-Press-Source überarbeiten zu müssen.
BB-Press: http://bbpress.org/
Auf KH: http://www.karrierehandbuch.de/Extern/BBPress/index.php
6. Performance-Fragen
27
Speicher-Caching: Insbesondere die Ergebnisse wiederholt auftretender
SQL-Queries (etwa bei einfachen Aliasen) oder auch die Ausgaben
komplexerer Methoden können parallel im Speicher abgelegt werden.
Wird das Query oder die Methode aufgerufen schaut das Programm
immer zuerst, ob bereits eine geeignete Variable mit dem Inhalt vorliegt.
Es gibt viele kleine Informationsmethoden im CMS die immer wieder
aufgerufen werden. Ohne Cache führt das bei einigermaßen komplexen
Seiten schnell zu bis zu ca. 800 winzigen SQL-Queries. Allein durch
konsequentes Speichercaching gelang es mir, die Geschwindigkeit der
Site mehr als zu verdoppeln.
6. Performance-Fragen
28
Objekt-Recycling:
Das CMS enthält einige recht umfangreiche Objektklassen deren
Instanzierung bereits teuer ist.
Das ist besonders bei kleinen Funktionen, die zuweilen auch ein
Displayer- oder ein Datenbankobjekt benötigen, schmerzhaft.
Eine Lösung ist die optionale Übergabe einer Objektreferenz, die ggf.
das teure new einspart:
function tool(...,$dp=NULL,$db=NULL)
// ...
{
if (is_null($dp)) $dp = new htDisplay(..);
if (is_null($db)) $db = new Kdb(..);
...
return $res;
}
6. Performance-Fragen
29
Objektorientierung nutzen! Schlanke Versionen von Klassen:
Beispiel: Anlegen einer Log-Klasse
• Wird einfach nur ein Inhalt abgerufen, muss die Log-Klasse Ihrer Site
nicht viel können. Es genügt, wenn Sie eine Zeile in einer Logdatei
richtig zu schreiben vermag.
• Lediglich im Backend Ihres CMS benötigen Sie eine deutlich
aufwändigere Log-Klasse, die Log-Dateien auch auswerten kann.
Programmieren sie diese als Ableitung der einfachen Write-OnlyKlasse!
6. Performance-Fragen
30
Einige von meiner Erfahrung belegte, subjektive Feststellungen:
• PHP hat mehr Möglichkeiten, aber SQL arbeitet schneller.
• Der Flaschenhals ist die Schnittstelle zwischen PHP und SQL.
10 einfache, SQL-Kommandos sind teurer als
1 „zehnfaches“ SQL-Kommando mit recht komplexem Query.
• Die Performance von SQL-JOINS in Kombination mit SQL-REGEXPAusdrücken ist durchaus eindrucksvoll.
Beispiel: Die interne Suchmaschine des Karrierehandbuchs verwendet
SQL und PHP parallel: Zunächst wird mit einem REGEXP-JOIN eine
einfache Vorauswahl an möglichen Artikeln getroffen. Auf diese
überschaubare Datenmenge werden dann die mächtigeren preg_Methoden von PHP angewandt, die die Treffer auch bewerten.
6. Performance-Fragen
31
7. All user input is evil
Das CMS soll Dritten dienen, eigene Inhalte und ggf. sogar Dateien zu
präsentieren. Dabei wird zwangsläufig die Sicherheitsfrage aufgeworfen.
Bedrohung: SQL-Injection. Unter SQL-Injection versteht man den Versuch eines
Users, durch kunstvolle Eingabe von Daten SQL-Kommandos zu manipulieren.
Ein Beispiel:
Das Programm erzeugt eine harmlose Anfrage aus einem Parameter <which>
SELECT `<which>` FROM `content_de_kh`;
Der User übergibt aber:
passwd` FROM `userdata` WHERE `login`=´Admin´; -SELECT `passwd` FROM `userdata` WHERE `login`=´Admin´; -- ` FROM
`content_de_kh`;
7. All user input is evil
32
Was kann man tun?
• Automatisch: Die PHP-Funktion mysql_query(..) lässt nur einen Befehl
auf einmal zu.
• Gefährliche Zeichen “`´\ -- nur kodiert in die SQL-Maschine einfüttern.
• Eben diese Zeichen für die genutzten Queries einsetzen. Also
SELECT `broetchen` FROM `baecker`;
anstelle von
SELECT broetchen FROM baecker;
• Regelmäßig Datenbank-Backups ziehen.
Hinweis: PHP bietet für die SQL-Schutzkodierung eigens die Kommandos
addslashes($text) und stripslashes($text) an.
Unicode-Fest ist mysql_real_escape_string($text,$dbResourceId)
http://shiflett.org/blog/2006/jan/addslashes-versus-mysql-real-escape-string
7. All user input is evil
33
Bedrohung: Cross Site Scripting. XSS (um es nicht mit CSS zu
verwechseln) ist der Versuch eines bösartigen Autors z.B. Java-ScriptFragmente in seine Beiträge einfließen zu lassen, die etwa die Cookies
eines diese Beiträge lesenden Admins auslesen und an den Hacker
weiterleiten. Letzterer kopiert dann diese Cookies in seine eigenen HTTPRequests und sieht für das CMS nun wie der Admin aus.
Was kann man tun?
• Javascript aus User-Artikeln herausfiltern.
• Cookies an IPs knüpfen. Wer ein Cookie anfordert wird mitsamt seiner
IP registriert ($_SERVER['REMOTE_ADDR']). Kommt nun eine
Anfrage an den Server wird nicht nur der Wert des Cookies sondern
auch die Korrektheit der IP überprüft.
Das ist keine Garantie, aber schnell umgesetzt.
7. All user input is evil
34
Bedrohung:
User stellen Schadskripte in Ihren Downloadbereichen bereit.
Was kann man tun?
User-eigene Downloads nur in dafür vorgesehenen Verzeichnissen
bereitstellen lassen.
Auf Apache-Servern kann man in jedem Verzeichnis eine Datei
.htaccess hinterlegen, in der man unter anderem festlegen kann,
wie der Server mit zum Beispiel Perl - Dateien verfahren soll.
7. All user input is evil
35
Beispiel für eine solche .htaccess-Datei:
<FilesMatch "\.(cgi|pl|...)$">
ForceType application/octet-stream
</FilesMatch>
Dieses Code-Fragment (die Regexp ist unvollständig) sorgt dafür, dass
Scriptdateien bei Aufruf nicht ausgeführt, sondern einfach als Text an den
Client gesendet werden.
7. All user input is evil
36
Bedrohung: Abhören des Client beim Austausch vertraulicher Daten.
Was kann man tun?
• Falls der Server-Dienst dies anbietet:
Die Übertragung auf SSL (Secure Socket Layer)
setzen und den Client seine Anfragen verschlüsseln lassen.
• Die meisten modernen Browser können sich ohne Aufwand für den
User von der SSL anbietenden Site einen Public Key herunterladen,
um damit zum Beispiel Formulardaten nach RSA zu verschlüsseln.
Dazu genügt besucherseitig meist schon das Stellen der
Anfrage als https://... anstelle von http://...
• Umsetzung: Auf meinem CMS gibt es eine Variable
$i_protocol = ‚http://‘
die bei der Erzeugung der internen Links eine wichtige Rolle spielt
und deren Wert bei Bedarf auf https:// angepasst wird.
FAQ zum Thema SSL: http://www.ccc.de/https/faq?language=de
Kostenlose Zertifizierung: http://www.cacert.org/
Nette Einführung: Geheime Botschaften von Simon Singh.
7. All user input is evil
37
8. Dinge, die ich gerne vorher gewusst hätte
Regular Expressions sind das Werkzeug für die Bearbeitung von Strings.
SQL bietet das Vergleichszeichen REGEXP an.
PHP bietet ereg_..()- und preg_..()-Tools. „preg“ steht für Perl Regular
Expressions. Die preg-Tools sind besser als die ereg-Varianten.
Die Anzahl der tatsächlich ausgeführten SQL Queries klein halten!
Speicher- und Festplatten-Caching ist schnell programmiert und super!
PEAR-DB wird von Rasmus Lerdorf empfohlen, ist aber bei der Verarbeitung von Strings buggy und führt bei Belastung zu Serverabstürzen.
Die mysql_..()-Kommandos sind meiner Erfahrung nach zuverlässig.
Auf Apache-Servern lohnt es sich, einmal die Datei httpd.conf
entspannt durchzulesen.
.htaccess-Dateien erlauben die Konfiguration des Serververhaltens in
beliebigen Verzeichnissen
8. Dinge, die ich gerne vorher
gewusst hätte
38
Vielen Dank für Ihre
Aufmerksamkeit!
CMS im Eigenbau
39