Entwicklung/Codedoku

Aus Opencaching-Wiki
Wechseln zu: Navigation, Suche

Dies ist eine Materialsammlung zum Opencaching-Programmcode. Sie soll neuen Entwicklern einen Überblick über die Grundstruktur und wichtigsten Bestandteile des OC-Codes geben. Mit "OC-Code" ist alles gemeint, was sich im Git-Repository befindet.

Weitere Dokumentationen gibt es auch in den Codeverzeichnissen doc und htdocs/doc. Die Datei doc/directories.txt enthält eine Übersicht der Repository-Verzeichnisstruktur. (Grüne Pfad- und Dateinamen sind anklickbar.)

Alle die bereits an Opencaching mitprogrammiert haben, sind eingeladen, ihr Wissen beizusteuern! Wenn du dir zusätzliche Erläuterungen wünschst, kannst du sie auf der Diskussionsseite anfragen – vielleicht findet sich jemand, der den Artikel ergänzt.

Siehe auch: Style Guidelines

Dateiformat

Alle OC-Quelltextdateien haben UTF-8-Format. Im Kopf der Quelltextdateien befindet sich ein japanisches Wort ("Unicode Reminder"), mit dem geprüft werden kann ob die Zeichencodierung intakt ist. Sollte versehentlich nach ISO-8859-1 o.ä. umcodiert worden sein, erscheinen dort z.B. zwei Fragezeichen oder gar nichts.

local/tools/find_bad_encodings.php prüft alle Quelltexte auf fehlende oder beschädigte Unicode Reminder.

Die Zeilenenden der Quelltexte bestehen grundsätzlich aus einem einfachen LF (line feed). Ausnahmen gibt es z.B. bei Email-Templates; hier ist CR/LF vorgeschrieben.

Hauptmodule

Mit "lib1" und "lib2" sind im Folgenden die OC-eigenen Codebibliotheken in htdocs/lib und htdocs/lib2 gemeint. Alle Teile des OC-Codes bauen auf einer dieser beiden Bibliotheken auf.

Der OC-Software besteht aus ...

  • Den darstellbaren Seiten der OC-Website, z.B. Startseite (index) oder Cachelisting-Anzeige (viewcache). Diese bestehen jeweils aus einem PHP-Script im Verzeichnis htdocs und einem Template in htdocs/templates2/ocstyle (lib1: htdocs/lang/de/ocstyle). Das PHP-Script erzeugt die darzustellenden Inhalte, das Template stellt sie als HTML-Seite dar.
  • Kommandozeilentools für verschiedene Zwecke, z.B. Wartungstools die per Crojob oder von Hand aufgerufen werden. Sie befinden sich in an verschiedenen Stellen:
    • bin (Update-Scripte)
    • htdocs/util (lib1, Tools für Produktivbetrieb)
    • htdocs/util2 (lib2, Tools für Produktivbetrieb)
    • local (spezielle Tools für lokale Verwendung)
  • Apache-Rewrites. Dies sind Konfigurationsdateien mit dem Name .htaccess, die vom Apache-Webserver ausgelesen werden. Sie interpretieren die übergebene URL und leiten auf ein PHP-Script um. Apache-Rewrites kommen an zwei Stellen zum Einsatz:

Bibliotheken

Die einzelnen OC-Module verwenden verschiedene Code-Bibliotheken:

Eigene Bibliotheken

lib1

Die alte selbstgeschriebene Bibliothek von OC-Codeversion 1.0, in htdocs/lib. Sie enthält z.B. Funktionen für Datenbankzugriff, Login, Menüs, geographische Berechnungen und ein einfaches Template-System.

lib1 ist veraltet und wird auf OC.de nur noch für das Bearbeiten von Logs und Cachebeschschreibungen, für Benachrichtigungen über beobachtete und neue Caches und für die XML-Schnittstelle verwendet. Mittelfristig soll sie ganz durch lib2 ersetzt werden. (Alle auf OC.pl-Code basierenden Opencaching-Seiten verwenden weiterhin ausschließlich die lib1).

lib1 wird eingebunden mit

require 'lib/common.inc.php'

lib2

Die Bibliothek der OC.de-Codversion 2.0 und höher in htdocs/lib2, Nachfolger von lib1. Sie ist wesentlich umfangreicher. Der Code ist aufgeteilt in das "Framework" mit allgemeinen Dingen wie Datenbankzugriff, Authentifizierung, Lokalisierung, Templates etc. und die sogenannte "Business Logic" in htdocs/lib2/logic mit allen Geocaching-spezifischen Klassen und Funktionen.

lib2 wird in Bestandteile der Website eingebunden mit

require 'lib2/web.inc.php'

und in Kommandozeilentools mit

require 'lib2/cli.inc.php'
gemeinsamer lib1- und lib2-Code

Manche Bibliotheksmodule werden versionsübergreifend verwendet, z.B. für Übersetzungen und Sprachauswahl. Sie sind in htdocs/lib2 abgelegt.

libse

Eine Klassenbibliothek von Opencaching.se, in htdocs/libse. Im Zuge der Codezusammenführung von OC.de und OC.se wurde sie 2011 mit in den OC.de-Code übernommen. Sie wird für zusätzliche Wegpunkte und persönliche Notizen im Cachelisting verwendet.

Autoload

Für die Klassen aller drei Bibliotheken sind Autoloader installiert. Das heißt wenn man z.B. ein Cache-Objekt benötigt, muss man dafür nicht lib2/logic/cache.class.php einbinden sondern man erzeugt einfach direkt das Objekt:

$cache = new cache();

cache.class.php wird dann automatisch geladen.

Fremdbibliotheken

Die OC-Software macht ausgiebig Gebrauch von fertigen PHP- und JavaScript-Codebibliotheken. Eine vollständige Aufstellung incl. Codepfade, Bezugsquellen etc. enthält doc/license.txt. Hier seien nur die wichtigsten davon erwähnt:

  • Smarty wird als "Template-Engine" für lib2 verwendet. OC-spezifische Erweiterungen befinden sich in der Klasse OcSmarty und in lib2/smarty/ocplugins. Mehr dazu im Abschnitt Templates.
  • HTML Purifier dient zum "Säubern" von HTML-Code, den der Benutzer z.B. in Cachebeschreibungen eingeben kann.Er wird (auch in lib1) über die lib2-Klasse OcHTMLPurifier eingebunden und kann dort angepasst werden.
  • TinyMCE ist ein leistungsfähiger JavaScript-Wysiwyg-Texteditor. Er dient zum Bearbeiten von Cachebeschreibungen, Logs, Profiltexten und Cachelisten-Beschreibungen. lib1 und lib2 verwenden beide die TinyMCE-Installation in htdocs/resource2/tinymce. OC-spezifische Konfigurationsdaten gibt es in tinymce/config; außerdem wurden die Einstellunge in tinymce/themes/advanced/langs angepasst. Damit Änderungen am TinyMCE-Source-Code wirksam werden, müssen u.U. die .gz-Cachedateien im tinymce-Verzeichnis gelöscht werden.

Von lib2-Code verwendete Bibliotheken sind in htdocs/lib2 (PHP) und htdocs/resource2 (JavaScript) abgelegt. Bibliotheken für lib1 befinden sich an verschiedenen Stellen.

OC-spezifische Anpassungen sind in doc/customized-libs.txt dokumentiert.

Konfigurationsdateien

lib1-Konfiguration

Die Einstellungen für lib1-Code befinden sich überwiegend in

htdocs/lib/settings.inc.php

Diese Datei ist nur lokal vorhanden. Als Vorlage für das Aufsetzen einer neuen Produktiv-Installation gibt es die Datei settings-dist.inc.php, die nach settings.inc.php zu kopieren und anzupassen ist.

Für Entwicklersysteme gibt es entsprechend die Vorlage settings-sample-dev.inc.php. Sie verwendet die Datei settings-dev.inc.php, in der die Grundeinstellungen für Entwicklersysteme zusammengefasst sind.

Daneben gibt es noch zwei Konfigurationsdateien settings.inc.php für die Benachrichtigungs-Cronjobs, in htdocs/util/notification und htdocs/util/watchlist.

Weiterhin verwendet lib1 auch Teile der lib2-Konfiguration, insbesondere locale.inc.php.

lib2-Konfiguration

Die lib2-Einstellungen befinden sich alle in htdocs/config2. Sie werden alle in folgender Reihenfolge eingebunden, sobald man web.inc.php oder cli.inc.php einbindet:

Auch hier sind Vorlagen für Produktiv- (settings-sample.inc.php) und Entwicklersystem (settings-sample-dev.inc.php) vorhanden, die nach settings.inc.php zu kopieren und and die lokale Systeminstallation anzupassen sind.

Der Rootpath

Wenn man in PHP-Scripte mit include oder require weitere Dateien einbindet, geschieht dies relativ zum Verzeichnispfad des Hauptscripts. (Daneben kann man auch weitere Pfade festlegen, in denen PHP nach passenden Scripten sucht.)

Wie unter Hauptmodule erläutert, befinden sich die Module der OC-Software auf verschiedenen Verzeichnisebenen Die Startseite liegt z.B. in htdocs, das lib2-Cronjob-Modul dagegen in htdocs/util2/cron. Alle diese Module binden lib1- oder lib2-Bibliothekscode ein, der weitere Unterscripte einbindet. Damit das alles funktioniert, wird im OC-Code ein Basispfad für alle Includes festgelegt. Dieser heißt

$opt['rootpath']

in lib2 und

$rootpath

in lib1.

Sofern man keine weiteren Vorkehrungen trifft, wird dieser Rootpath automatisch als Relativpfad "./" gesetzt. Scripte in Unterverzeichnissen müssen ihn dagegen selbst setzen, z.B. "../" wenn das Script eine Ebene tiefer liegt, oder absolut als dirname(__FILE__).'../', wenn nicht bekannt ist, aus welchem aktuellen Verzeichnis ein Kommandozeilentool aufgerufen wird.

Konstanten

Globale Konstanten sind an folgenden Stellen definiert:

Die lib2-Konstanten werden auch teilweise auch in lib1-Code verwendet.

Datenbank

Die OC-Software ist nur für die Verwendung von MySQL ausgelegt. Zwar werden in lib2 keine MySQL-Funktionen mehr direkt aufgerufen, aber zahlreiche SQL-Statements verlassen sich auf MySQL-Syntax.

Grundlegende Datenbankfunktionen

Für lib2 befinden sich alle Funktionen in lib2/db.inc.php und sind dort kurz dokumentiert, für lib1 in lib/clicompatbase.inc.php. Datenbankverbindungen werden automatisch entsprechend den Konfigurationseinstellungen hergestellt, sobald man eine Query absetzt.

Die wichtigsten Funktionen seien hier anhand von Beispielen erläutert:

sql("UPDATE `caches` SET `size`='&1' WHERE `cache_id`='&2'",
    $newsize, $cacheid);

Hier wird die Größe eines Caches geändert. Variablen werden als fortlaufend numerierte Platzhalter &1, &2 etc. übergeben. Die Funktion sql ruft dann intern für jede Variable mysql_real_escape_string() auf, was spezielle Zeichen wie " codiert und gleichzeitig SQL-Injections verhindert.

Niemals dürfen Variablen direkt in SQL-Statements eingebaut werden! Wann immer möglich sind Platzhalter zu verwenden. Notfalls kann auch direkt mit sql_escape() codiert werden, was allerdings Probleme verursacht, wenn ein "&" in den Daten enthalten ist: Die Funktion sql() interpretiert es anschließend als Startzeichen eines Platzhalters.

$cachesize = sql_value("
   SELECT `size` FROM `caches`
   WHERE `cache_id`='&1'",
   null, $cacheid);

Hier wird die Größe eines Caches abgefragt. Falls die Cache-ID nicht gefunden wird, wird der Defaultwert null zurückgeliefert. Denke immer daran, den Defaultwert als ersten Parameter anzugeben! Ihn zu vergessen ist eine häufige Fehlerquelle.

In der lib1 heißt die Funktion anders und kann nicht mit Platzhaltern umgehen; der entsprechende Code lautet dann:

$cachesize = sqlValue("
   SELECT `size` FROM `caches`
   WHERE `cache_id`='" . sql_escape($cacheid) . "'",
   null);

Das folgende Beispiel demonstriert, wie der Inhalt einer Tabelle ausgelesen wird:

 $rs = sql("
   SELECT `cache_id`, `name`, `type` `size` FROM `caches`
   WHERE `cache_id`='&1'",
   $cacheid);
 while ($r = sql_fetch_assoc($rs))
 {
   // ... Weiterverarbeitung von $r['cache_id'], $r['name'] etc.
 }
 sql_free_result($rs);

Slave-Server

Beide OC-Libraries sehen eine Lastverteilung auf einen Haupt-Datenbankserver (Master) und einen oder mehrere "Slaves" vor, die replizierte Daten des Masters enthalten; siehe doc/replication.txt. Dazu gibt es SQL-Funktionen mit dem Zusatz "_slave", z.B. sql_slave(), die von "datenbankintensiven" Programmmodulen wie z.B. der Suche (search.php) anstatt der Basisfunktionen verwendet werden.

Wenn per settings.inc.php keine Slave-Server konfiguriert sind, laufen alle Zugriffe über den Master. Zurzeit betreibt kein Opencaching-Knoten Slave-Server; die Kapazität des Masters reicht überall aus.

Temporärtabellen

Da Subqueries bei MySQL recht ineffizient sind, sind manchmal temporäre Tabellen für Zwischenergebnisse nötig.

lib2 sieht einen speziellen Mechanismus zur Verwaltung von Temporärtabellen vor. Sie werden in einer eigenen Datenbank abgelegt, für die erweiterte Zugriffsrechte (Tabellen anlegen/löschen) vergeben werden, und werden per Cronjob automatisch aufgeräumt, wenn sie nicht gleich wieder gelöscht wurden.

Ein (nicht unbedingt sinnvolles) Beispiel für die Verwendung von lib2-Temporärtabellen. Die temporäre Tabelle heißt "tmp_caches" und wird in SQL-Statements über einen Platzhalter referenziert:

 sql_temp_table('tmp_caches');  // Tabelle registrieren
 sql("                          // Tabelle anlegen
   CREATE TEMPORARY TABLE &tmp_caches ENGINE=MEMORY
   SELECT `cache_id` FROM `caches`
   WHERE `user`.`user_id` = `caches`.`user_id`
     AND `user`.`country` = '&1'",
   $country);

 $rs = sql("                    // Tabelle auslesen
   SELECT * FROM `cache_desc`, &tmp_caches
   WHERE `cache_desc`.`cache_id` = &tmp_caches.`cache_id`");
 // ... Daten weiterverarbeiten
 sql_free_result($rs);

 sql_drop_temp_table('tmp_caches');  // Tabelle löschen

Datenbankstruktur; statische Daten

Datenbankstruktur und -inhalte werden an folgenden Stellen unter htdocs/doc/sql definiert:

  • Tabellen, Felder und Indizes in tables
  • Stored Functions, Procedures und Trigger in stored-proc
  • statische Daten wie z.B. Menüs, Cachetypen und sprachabhängige Texte in static-data/data.sql.

Es gibt weitere (geographische) "statische Daten", die aufgrund ihres Umfangs nicht im Git-Repository enthalten sind; siehe static-data/static-data.txt. Für Entwickler steht auf Anfrage ein Datenbankdump zur Verfügung, der eine vollständig initialisierte OC-Datenbank enthält (Veröffentlichung ist geplant). Weitere Updates können dann per

bin/dbupdate.php

eingepflegt werden, also man muss die oben aufgelisteten Dateien nicht von Hand weiterverarbeiten.

Siehe auch: Datenbankversionierung

OKAPI

Die OKAPI-Tabellen werden zwar in der gleichen Datenbank abgelegt, sind aber unabhängig von den OC-Tabellen und daher nicht in htdocs/doc/sql definiert. Bei Installation der OKAPI werden sie automatisch angelegt und bei OKAPI-Updates automatisch aktualisiert. dbupdate.php führt auch OKAPI-Updates durch, falls die OKAPI installiert ist.

Auch der OKAPI-Code ist unabhängig vom OC-Code, d.h. sämtliche Datenbankzugriffe sind dort redundant implementiert. Bei Änderungen an Datenstrukturen oder -inhalten ist daher immer zu prüfen, ob eine Anpassung der OKAPI nötig ist!

Trigger & Stored Procedures

Der Opencaching-Code macht reichlich Gebrauch von gespeicherten Datenbank-Funktionen und Triggern. Sie werden dazu eingesetzt, um die Konsistenz der Datenbankinhalte zu gewährleisten. Vor dem Hintergrund, dass drei unabhängige Frameworks parallel auf die Datenbank zugreifen – lib1, lib2 und OKAPI (und manchmal noch ein Administrator von Hand) – ist diese Vorgehensweise ratsam. Sie macht die OC.de-Datenbank ausgesprochen robust.

Alle Datenbankfunktionen sind in

htdocs/doc/sql/stored-proc

definiert. Der aktuelle Stand befinden sich in maintain-current.inc.php; für die Datenbankversionierung können zusätzliche ältere Stände bzw. Update-Scripte existieren.

Der Inhalt des maintain-Scripts lässt sich grob untergliedern in

  • allgemeine Funktionen für komplexe Berechnungen, z.B. DECTOWP für die Berechnung neuer OC-Wegpunkte
  • Update-Funktionen für bestimmte Felder (sp_update_*) die entweder von verschiedenen Triggern aufgerufen werden oder zur Datenbankwartung aus dbmaint.php (Menü Admin|Datenbankwartung)
  • Trigger-Prozeduren

Eine Besonderheit sind globale Variablen die zur Parameterübergabe von PHP-Scripten an Datenbankfunktionen oder zwischen Datenbankfunktionen verwendet werden. Sie sind in maintain-current.inc.php erläutert.

SQL-Debugger

lib1 und lib2 enthalten einen nützlichen SQL-Debugger (lib/sqldebugger.inc.php bzw. lib2/sqldebugger.class.php). Er zeigt für jedes ausgeführte SQL-Statment

  • das Statement selbst,
  • ein EXPLAIN,
  • die ersten zurückgelieferten Datensätze, falls vorhanden,
  • die Ausführungszeit,
  • eventuelle MySQL-Warnungen.

Der SQL-Debugger muss in den Settings aktiviert sein – für lib1 per $sql_debug = true, in lib2 durch Setzen des Flags DEBUG_SQLDEBUGGER in $opt['debug']. Wahlweise kann man diese Flags auch nur in einzelnen zu debuggenden PHP-Quelltexten setzen.

Anschließend ist die zu debuggende Seite mit dem URL-Parameter sqldebug=1 aufzurufen.

Temporärdaten und Inhalte im Dateisystem

Manche Dinge werden in Dateien statt in der Datenbank abgelegt.

  • Hochgeladene Bilder befinden sich üblicherweise in htdocs/images/uploads (in den Settings änderbar), die Thumbnails darunter in thumbs. Generierte Statistikbilder werden in htdocs/images/statpics abgelegt.
  • Herunterzuladende, gepackte Ausgaben der XML-Schnittstelle werden in htdocs/download/zip zwischengespeichert (ebenfalls konfigurierbar).
  • htdocs/cache2 enthält zwischengespeicherte Templates, Übersetzungsdateien für gettext und verschiedene Temporärdaten. Ein guter Teil dieser Daten wird von bin/clear-webcache.php neu generiert.
  • htdocs/var enthält zwischengespeicherte OKAPI-Daten (die OKAPI legt allerdings auch eine Menge "gecachte" Daten in der Datenbank ab) und Fehlerprotokolle.
  • htdocs/cache enthält vermutlich nur noch temporäre Prozess-Synchronisationsdateien (.pid-Dateien) der lib1-cronjobs für Log- und Cache-Benachrichtigungen.

Templates

Templates dienen in Webanwendungen dazu, die Generierung von aus- und einzugebenden Daten von deren HTML-Darstellung zu trennen. HTML- und JavaScript-Code wird komplett in den Template-Dateien untergebracht, PHP-Code soweit wie irgend möglich in PHP-Scripten.

Die Einbindung der Templates in den PHP-Programmablauf geschieht wie folgt:

  1. Festlegung, welche Templatedatei verwendet wird
  2. Zuweisung von variablen Inhalten an das Template
  3. Darstellung des Templates

Für alle drei Punkte gibt es entsprechende Bibliotheksfunktionen.

lib1-Templates

Die alte Library enthält ein selbstgemachtes, simples Templatesystem. Die Templatedateien befinden sich in htdocs/lang/de/ocstyle und enthalten den auszugebenden HTML- und JavaScript-Code. Der Name der Template-Datei wird im PHP-Code über die globale Variable $tplname festgelegt, z.B.

$tplname = 'newcache';

Für alle variablen Inhalte werden im Template Platzhalter wie z.B. {cachename} verwendet; die PHP-Variable wird dann übergeben mit

tpl_set_var('cachename', $cachename);

Sofern der variable Teil auch HTML-Code oder etwas zu Übersetzendes enthält, wird dies in speziellen Include-Dateien abgelegt, z.B.

$cachelink = '<a href="viewcache.php?cacheid={cacheid}">{cachename}</a>';
$outputformat_notexist = t('The selected output format is unknown!');

Die Include-Datei wird im PHP-Hauptmodul eingebunden und die Variablen per mb_ereg_replace ersetzt, bevor das Ganze per tpl_set_var() ans Template übergeben wird.

tpl_BuildTemplate();

stellt schließlich das Template dar und beendet das PHP-Script.

Alle Opencaching-Seiten außer OC.de und OC.se/no verwenden nur das lib1-Templatesystem.

lib2-Templates (Smarty)

Grundlagen

Die neue Bibliothek verwendet die leistungsfähige Smarty-Engine. Smarty-Templates bestehen neben dem HTML/JavaScript-Code im Wesentlichen aus Kontrollstrukturen und Variableneinbindungen. Die Templates sind in htdocs/templates2/ocstyle abgelegt.

Die lib2 definiert die globale Variable

$tpl

über die alle Zugriffe auf Smarty und die Templates laufen. Am Beginn des PHP-Moduls wird jeweils das Template initialisiert:

$tpl->name = 'newemail';   // Name der Template-Datei
$tpl->menuitem = MNU_MYPROFILE_DATA;  // Menü-ID, siehe doc/sql/static-data/data.sql
$tpl->caching = false;     // nicht zwischenspeichern

Der caching-Parameter legt fest, ob das Ergebnis der Templateausgabe zwischengespeichert wird; die Dauer der Zwischenspeicherung ist dann ggf. per

$tpl->caching_lifetime = 1800;  // halbe Stunde Caching

festzulegen. Dies empfiehlt sich bei häufig aufgerufenen, rechenintensiven Seiten. Seiten mit ständig wechselndem bzw. benutzerabhängigem Inhalt dürfen nicht "gecacht" werden!

Neben Caching der Template-Ausgaben gibt es auch eine Vorcompilierung der Templates selbst. Diese erfolgt

  • einmalig für alle Templates per bin/clear-webcache.php,
  • automatisch, wenn ein geändertes Template abgerufen wird,
  • jedesmal bei jedem Template-Aufruf, falls in den Settings der Debug-Schalter DEBUG_TEMPLATES gesetzt ist.

Die zwischengespeicherten Templates werden in htdocs/cache2/smarty abgelegt.


Auf die Initialisierung des Templates folgen Variablen-Zuweisungen (s.u.) und schließlich die Darstellung mit

$tpl->display();

die das PHP-Script beendet.


Smarty-Syntax

Im Folgenden sind ein paar grundlegende Smarty-Elemente erklärt; ansonsten sei auf die Smarty-Dokumentation verwiesen und auf die vielen Bespiele in templates2/ocstyle. (Achtung: Der OC-Code verwendet eine ältere Smarty-Version; nicht alles was in der Doku steht ist hier auch schon verfügbar!)

Alle Smarty-Elemente stehen in geschweiften Klammern {}. Wenn das Template solche Klammern für andere Zwecke verwendet, z.B. für JavaScript-Kontrollstrukturen, müssen diese von der Smarty-Interpretation ausgeklammert werden:

 {literal}
 function js_function()
 {
   ...
 }
 {/literal}

Variablen

Variablen werden z.B. per

$tpl->assign('cachename', $cache['name']);

an das Template übergeben und dort als

{$cachename}

verwendet. Es können auch Arrays übergeben werden, deren Elemente statt [] mit einem Punkt referenziert werden.

{$cache.name}

steht z.B. für $caches['name'], übergeben per $tpl->assign('caches', ...). Dies funktioniert auch mit numerischen Indizes – z.B. {$user.0} für das erste Element eines $user-Arrays – und mit mehr als zwei Ebenen.

Auch simple Berechnung sind möglich, z.B. {$waylength*2}.

Zusätzlich zu dem vom PHP-Code übergebenen Variablen kann man auch Variablen direkt im Template definieren, z.B.

{assign var=counter value=1}

Mit {capture} lassen sich u.U. auch komplexere Ausdrücke in einer Variable unterbringen.

Kontrollstrukturen

Bedingte Einbindung von Template-Komponenten ist mit {if} … {elseif} … {else} … {/if}-Anweisungen möglich. Dabei können auch komplexere PHP-Ausdrucke verwendet werden, die als Boolean ausgewertet werden:

 {if $show_statistics && ($statdata|@count > 0)}
   ... Statistik ausgeben ...
 {/if}

(@count ist eine benutzerdefinierte Funktion)

Eine weiter häufig verwendete Struktur ist {foreach} zum iterieren von Arrayelementen:

 {foreach from=$caches item=cache}
   <td>{$cache.name|escape}</td>
 {/foreach}

|escape ist ein Modifizierer, der den Variableninhalt weiterverarbeitet. In diesem Fall werden HTML-Entitäten "escaped".

Mit {strip}...{/strip} lassen sich alle Whitespaces (Leerzeichen, Tabs, Zeilenumbrüche) in einem Abschnitt herausfiltern, was die auszugebende Datenmenge reduziert. Aber Vorsicht, falls Whitespaces als Trennzeichen in Ausgaben benötigt werden!

Benutzerdefinierte Erweiterungen

Smarty lässt sich um eigene

  • Elemente ("Blocks"),
  • Modifizierer und
  • Funktionen

erweitern. Der Opencaching-Code enthält eine Reihe solcher eigener Erweiterungen in lib2/smarty/ocplugins.

Die wichtigste Erweiterung ist das {t}-Element zum Übersetzen von Texten. Alle Texte in den Templates werden in Englisch geschrieben und in {t} ... {/t} eingeschlossen; sie werden dadurch automatisch dem Übersetzungssystem zugeführt. Mehr dazu im Kapitel Übersetzungen.

Systemtemplate und Menüsystem

Was oben über einzelne Templates geschrieben wurde betrifft nur den Teil der Seite, der im Content-Container dargestellt wird – also dem Teil unterhalb und rechts des Menüsystems und oberhalb der Fußzeile. Alles "drumherum" wird vom Systemtemplate erzeugt.

Folgende Codedateien sind daran in der lib1 beteiligt ...

... und in der lib2:

Zusätzliche Hauptmenü-Punkte sind via Konfigurationsdateien erzeugbar.

Email-Templates

Auch der Inhalt (body) von Emails wird über Templates erzeugt.

lib1-Email-Templates befinden sich in htdocs/lang/de/ocstyle/email, htdocs/util/notification und htdocs/util/watchlist und werden von den entsprechenden PHP-Modulen direkt per mb_ereg_replace() aufgelöst.

lib2-Email-Templates liegen in htdocs/templates2/mail und werden von mail.class.php per Smarty verarbeitet.

OKAPI

Die OKAPI hat ein eigenes Templatesystem; mehr dazu im Abschnitt OKAPI.

CSS Style Sheets

Die Style Sheets befinden sich in htdocs/resource2/ocstyle/css.

Grafiken

Grafiken für Icons und andere Dinge verteilen sich – soweit es keine Drittbibliotheken betrifft – über drei Verzeichnisse:

Hier herrscht etwas Chaos. Manches ist mehrfach vorhanden - auch innerhalb einer Library - oder inkonsistent abgelegt, manche der Grafiken werden nicht verwendet; die Dateitypen wechseln zwischen GIF und PNG. Wenn dies glattgezogen werden soll, müssen die alten Versionen der Grafiken stehenbleibem weil sie auch gerne von anderer Stelle aus verlinkt werden, z.B. aus Cachelistings - teils sogar von anderen Plattformen aus. Schiebt man die Grafiken einfach weg oder ändert das Format, zerstört es unbemerkt Cachebeschreibungen.

Zur Kompatibilität mit alten Cachebeschreibungen und Logs liegen in folgenden Verzeichnissen Grafiken, die anderweitig nicht mehr verwendet werden:

Siehe auch: Übersicht der Opencaching.de-Icons

Übersetzung

Funktionsweise

Der OC-Code ist mehrsprachig und vollständig übersetzbar, mit Ausnahme des Garmin-Download-Dialogs (garmin.php), dessen untere Hälfte es teils nur in Englisch und teils in Deutsch und Englisch gibt (siehe htdocs/resource2/ocstyle/js/GarminDisplay.js).

Übersetzungen und Lokalisierungen befinden sich an folgender Stelle:


Die Übersetzung je nach gewählter Sprache erfolgt grundsätzlich mit folgenden Mechanismen:

  • In Templates über die Smarty-Tags {t}English Text{/t}. Hierbei können auch Variablen eingesetzt werden, z.B.
    {t 1=$cachename 2=$username}The geocache %1 has been found by %2{/t}
  • Notfalls direkt im PHP-Code durch Aufruf von $translate->t(), z.B.
    $text = $translate->t('New password code', '', basename(__FILE__), __LINE__)
  • Bei Artikelseiten, die via htdocs/articles.php dargestellt werden, per Ablage der Templates in Unterverzeichnissen von htdocs/templates2/ocstyle/articles.


Das Übersetzungssystem arbeitet intern mit gettext(). Die Strings dafür befinden sich in der Tabellen sys_trans und sys_trans_text. Man kann sie direkt via data.sql einpflegen, oder mit dem "Schnellübersetzungsmodus" + "SQL-Export" von translate.php (Menü Admin|Übersetzung). Letzteres kann allerdings jede Menge Git-Merge-Konflikte beim Zusammenführen verschiedener Branches erzeugen, daher werden OC.de-Übersetzungen derzeit nur direkt in data.sql eingepflegt.

Achtung: Auch für die englische Seitendarstellung läuft nochmal alles durch den Übersetzer, Artikel ausgenommen. Die englischen Originaltexte in den Templates oder im PHP-Code sind also nur eine Basis für die weitere Übersetzung. Kleinere Fehler dort müssen nicht korrigiert werden, solange die Texte verständlich sind. Wenn dort etwas korrigiert wird, muss es gleichzeitig auch in der Tabelle sys_trans in data.sql geändert werden!

Opencaching.de gibt es zurzeit in Deutsch, Englisch, Italienisch und Spanisch; Französisch ist in Arbeit. Eine niederländische Übersetzung ist ansatzweise vorhanden (wer mag sie fertigstellen?). Die Übersetzungen werden durch den von Opencaching Deutschland eingesetzten Code Maintainer organisiert.

Stolperfallen

Vorsicht bei der Korrektur englischer Originaltexte, siehe oben.

Die Text-Exportfunktionen im Übersetzungsinterface geben zurzeit die englischen Originale aus der Tabelle sys_trans aus, die teilweise veraltet sind. Dies sollte vor der nächsten Neuübersetzung unbedingt geändert werden.

Die Funktionen "Quelltext durchsuchen" und "IDs neu sortieren" im Übersetzungsinterface (translate.php) mit anschließendem Datenexport können umfangreichen Merge-Konflikten zur Folge haben. Nur verwenden wenn nötig und möglichst in Abstimmung mit anderen Entwicklern.

Satzzeichen sollten wann immer möglich in den übersetzten Texten enthalten sein. Beispielsweise müssen bei Französisch Leerzeichen von :;?! eingefügt werden. (Für Letzteres kann man alternativ auch {t}#colonspace#{/t} (in HTML) oder {$opt.format.colonspace} (in plain text) verwenden.)

Bei der Einbettung in JavaScript sind Texte zu escapen; dies geht mit dem Smarty-Modifizierer escape=js. Es gibt jedoch Spezialfälle in denen dies nicht im Template möglich ist; in dem Fall sind HTML-Entities (' " &) in data.sql doppelt zu escapen, z.B. Didn\\\'t find für den String Didn't find. Das erste Escaping wird beim SQL-Import von data.sql aufgelöst, das zweite verhindet Kollissionen mit der JavaScript-Stringsyntax.

Beim Aufruf von $translate->t(...) muss der zu übersetzend String in einfachen Kochkommata ('') stehen. Doppelte Hochkommata würden von translate.php -> Quelltexte durchsuchen nicht erkannt.

Fehlerbehandlung

Der OC-Code verwendet kein Exception Handling (mit Ausnahme der OKAPI). Wenn etwas fehlschlägt, wird dies durch den Rückgabewert der betreffenden Funktion signalisiert und entsprechend reagiert. Würde man den betroffenen Funktionen noch einen Namenszusatz spendieren, der die mögliche Fehlerrückgabe kennzeichnet, wäre dieses System transparenter und in sich weniger fehleranfällig als ein strukturiertes Exception Handling.

fehlerhafte Benutzereingaben

Wenn es Probleme mit einer Benutzereingabe gibt, wird das betreffende Template nochmals angezeigt und an passender Stelle eine mit dem CSS-Stil errormsg formatierte Meldung eingeblendet. Dies ist jeweils entsprechend zu implementieren.

erkanntes Problem

Wenn anderweitig ein nicht handhhabbares Problem erkannt wird, wird das Fehlertemplate angezeigt. In lib1 befindet sich dies in lang/de/ocstyle/error.tpl.php und wird aufgerufen per

tpl_errorMsg('original_templatename', $fehlermeldung);

z.B.

$error_desc_not_found = t('(internal error) The description is not available.');
...
tpl_errorMsg('editdesc', $error_desc_not_found);

Das lib2-Fehlertemplate befindet sich in bzw. templates2/ocstyle/error.tpl und wird verwendet mit

$tpl->error(Fehlernummer)

oder

$tpl->error($fehlermeldung);

Die Fehlernummern sind in lib2/error.inc.php definiert.

SQL-Fehler

Bei MySQL-Fehlern wird ein Fehlertemplate anzeigt und eine Admin-Email versandt, sofern in den Settings eine Mailadresse hinterlegt ist. In der lib1 ist sie in

$sql_errormail

einzutragen, in der lib2 in

$opt['db']['error']['mail'].

Entwicklersysteme werden so konfiguriert, dass das komplette SQL-Statement auch direkt angezeigt wird.

Zusätzlich kann man per $sql_warntime (lib1) bzw. $opt['db']['warn']['time'] (lib2) eine Warnschwelle für zu lange SQL-Ausführungszeiten festlegen. Bei Überschreitung werden dann Admin-Mails erzeugt.

unerkanntes Problem

lib2/errorhandler.inc.php enthält ein noch in der Testphase befindlichen Handler für PHP-Fehler, der auch in lib1 eingebunden ist. Er wird bei PHP-Fehlern aktiv, die (abhängig von der error_reporting-Einstellung in der PHP-Konfiguration) zu einem Abbruch des Scripts führen. Es wird dann eine Fehlermeldung anzeigt und ggf. eine Admin-Mail erzeugt. Es wird die gleiche Emailkonfiguration wie bei SQL-Problemen (s.o.) verwendet.

Dieser PHP-Errorhandler ist derzeit nur aktiviert, wenn in der Konfiguration der Debug-Modus eingeschaltet ist. Ansonsten erscheint bei Fehlern eine leere Seite.

OKAPI

Die Opencaching-API ist ein eigenständiges PHP-Projekt, dessen Code auf verschiedenen OC-Seiten zum Einsatz kommt. Die Projekt-Homepage ist http://code.google.com/p/opencaching-api/. Beiträge, die nicht vom Code-Maintainer selbst kommen, werden üblicherweise in Branches hochgeladen und laufen dann durch ein Code Review.

Die OKAPI wird – erfolgreich! – mit einem wesentlich höheren Anspruch an Codequalität und -Stil entwickelt als die OC-Länderseiten. Dokumentation und Codestruktur sind herausragend. Gleichzeitig steht sie bei einigen Details wie Template-Engine, Datenbank-Library und Konstanten-Definitionen hinter dem OC.de-Code zurück. Der OKAPI-Code ist nur für Webanwendungen geschrieben; Einbindung in Kommandozeilentools kann insbesondere am Exception-Handling scheitern.


Der OKAPI-Code wird aus dem OKAPI-Projekt in die lokalen OC-Code-Repositories (nach htdocs/okapi) repliziert. Bei Opencaching.pl und .nl geschieht dies automatisch über einen SVN-"Commit-Hook", bei Opencaching.de manuell durch den Code-Maintainer nach einem Security Review.

OKAPI-Code kann über die "Facade"-Schnittstelle in OC-Code eingebunden werden (okapi/facade.php). OC.pl macht davon insbesondere bei der Karte Gebrauch, die von der OKAPI generiert wird. OC.de verwendet die Facade bislang nur (experimentell) im Update-Script bin/okapi-update.php, das von dbupdate.php aufgerufen wird.

Sämtliche OKAPI-Seitenzugriffe laufen über eine Rewrite-Engine (okapi/.htaccessokapi/controller.php). Man kann daher die PHP-Scripte nicht direkt über ihre URLs idenitifizieren sondern muss schonmal etwas suchen. Methoden befinden sich in okapi/services, Seiten mit HTML-Output in okapi/views.


Für die Dokumentation verwendet die OKAPI ein eigenes XML-Format, das von einem Parser aufbereitet wird.

Templates für Methoden, die HTML- oder sonstiges XML-Output liefern, sind in .tpl.php-Dateien zusammen mit den PHP-Quelltexten abgelegt. Variablen werden per PHP-Code eingebunden, z.B.

<?php if (($vars['my_notes'] == 'desc:text') && ($c['my_notes'] != null)) { /* Does user want us to include personal notes? */ ?>
  <p><b><?= _("Personal notes") ?>:</b> <?= Okapi::xmlescape($c['my_notes']) ?></p>
<?php } ?>