Versionen von Entwickler.Trails

Unwichtige Korrekturen ausblenden - Änderungen im Wiki Quelltext

 
 
01.04.2011 23:29 Uhr von tthelen -
Zeilen 1-2 hinzugefügt:

(:redirect 'http://docs.studip.de/develop/Entwickler/Trails':)

 
 
19.09.2010 20:25 Uhr von pthienel -
Zeile 25 bearbeitet:

Ein Controller sollte nie direkt Zugriff auf die Datenbank nehmen und auch sonst so wenig wie möglich and Datenstrukturen generieren. Er ist dafür zuständig, die korrekten Daten aus dem richtigen model in die view zu schaffen. Models liegen im passenden Verzeichnis, nämlich 'app/models' und müssen zur Verwendung mittels require_once im Controller inkludiert werden.

geändert in:

Ein Controller sollte nie direkt Zugriff auf die Datenbank nehmen und auch sonst so wenig wie möglich Datenstrukturen generieren. Er ist dafür zuständig, die korrekten Daten aus dem richtigen model in die view zu schaffen. Models liegen im passenden Verzeichnis, nämlich 'app/models' und müssen zur Verwendung mittels require_once im Controller inkludiert werden.

 
 
19.09.2010 20:11 Uhr von pthienel -
Zeile 10 bearbeitet:

Es gibt auch einen Foliensatz, der das Konzept hinter Trails und die Konfigration von Trails für Stud.IP zeigt:\\

geändert in:

Es gibt auch einen Foliensatz, der das Konzept hinter Trails und die Konfiguration von Trails für Stud.IP zeigt:\\

 
 
29.03.2010 14:36 Uhr von tgloeggl -
Zeile 32 bearbeitet:
geändert in:
 
 
29.03.2010 14:31 Uhr von tgloeggl -
Zeilen 31-32 bearbeitet:

Um das ganze Konzept und die Möglichkeiten zu veranschaulichen wird im Folgenden en detail eine Beispiel-Trails-Seite erklärt, die komplette Seite gibt es auch als ZIP: Attach:trails_example.zip

geändert in:

Um das ganze Konzept und die Möglichkeiten zu veranschaulichen wird im Folgenden en detail eine Beispiel-Trails-Seite erklärt, die komplette Seite gibt es auch als ZIP:
Attach:trails_example.zip

 
 
29.03.2010 14:29 Uhr von tgloeggl -
Zeile 13 bearbeitet:

Trails & Stud.IP

geändert in:

Trails & Stud.IP

 
 
29.03.2010 14:29 Uhr von tgloeggl -
Zeile 3 bearbeitet:

(:toc=3:)

geändert in:

(:toc:)

 
 
29.03.2010 14:26 Uhr von tgloeggl -
Zeile 3 bearbeitet:

(:toc:)

geändert in:

(:toc=3:)

 
 
29.03.2010 14:25 Uhr von tgloeggl -
Zeilen 2-3 hinzugefügt:

(:toc:)

 
 
29.03.2010 14:25 Uhr von tgloeggl -
Zeile 34 hinzugefügt:
Zeile 59 hinzugefügt:
Zeile 61 hinzugefügt:
Zeile 68 hinzugefügt:
Zeile 70 hinzugefügt:
Zeile 103 hinzugefügt:
Zeile 105 hinzugefügt:
Zeile 108 hinzugefügt:
Zeile 110 hinzugefügt:
Zeile 115 hinzugefügt:
Zeile 117 hinzugefügt:
 
 
29.03.2010 14:24 Uhr von tgloeggl -
Zeile 33 bearbeitet:
geändert in:

Nur in Stud.IP Eingeloggte oder frei verfügbar?

Zeile 46 hinzugefügt:

Die index_action - Wichtigste Action im Controller

Zeile 58 hinzugefügt:

Das Url-Schema

Zeile 65 hinzugefügt:

Templates für Actions

Zeilen 71-73 hinzugefügt:

Manipulation des Kontrollflusses - I

Zeile 98 hinzugefügt:

Routing in Trails

Zeile 101 hinzugefügt:

Persistente Werte

Zeile 106 hinzugefügt:

Manipulation des Kontrollflusses - II

Zeile 148 hinzugefügt:
Zeilen 151-152 bearbeitet:

Die folgende Datei ist die view üfr unser Beispiel.

geändert in:

Die folgende Datei ist die view für unser Beispiel.

Infobox

Zeilen 175-177 hinzugefügt:

Variablenzugriff und Partials

Zeilen 190-192 hinzugefügt:

var_dump($daten) gibt das aus, was wir im Controller mittels $this->daten= zugewiesen haben. Auf diese Art und Weise gelangen vorbelegt Variablen ins Template.

Zeilen 196-198 hinzugefügt:

URLs zu Aktionen in Controllern

Zeilen 210-211 hinzugefügt:

Zugriff auf persistente Werte im Template

 
 
29.03.2010 14:17 Uhr von tgloeggl -
 
 
29.03.2010 14:14 Uhr von tgloeggl -
Zeilen 73-78 hinzugefügt:

function redirect_action() {

    $this->redirect('example/asite/helloworld/Hallo Welt! Dieses mal sogar weitergeleitet von redirect!');

} @]

(:source lang=php linenum:)[@

Zeile 81 bearbeitet:
    // delete something
geändert in:
    // do something
Zeilen 90-92 bearbeitet:

Diese Action beinhaltet zwei der wohl wichtigsten Möglichkeiten von Trails.
Mit $this->redirect(pfad_zum_controller/name_des_controllers/action[/parameter]

geändert in:

Diese Action beinhaltet zwei der wohl wichtigsten Möglichkeiten von Trails.

Zum einen kann man mit $this->redirect(pfad_zum_controller/name_des_controllers/action[/parameter]); auf eine andere Action in einem beliebigen anderen Controller weiterleiten. Das ermöglicht es einem Actions zu haben, die keine eigene Ausgabe brauchen, da sie z.B. nur einen Eintrag löschen und danach die selbe Seite wieder anzeigen. So muss man auch nicht in der index_action irgendwelche $cmd, $command oder sonstige Variablen auswerten. Gibt es eine neue Aktion, baut man einfach eine weitere Action ein und leitet dann passend weiter.

Die Möglichkeit des routens führt uns direkt zu einem weiteren Aspekt von Trails. Was nun, wenn so eine "verdeckt" operierende Aktion eine Statusmeldung auf der Hauptseite, zu der sie hin-routet haben möchte? Für diesen und ähnliche Zweck gibt es die spezielle Variabel flash.
Dieser Variablen kann man direkt einen Wert oder einen Wert an einer Stelle in einem Array zuweisen (wie im Beispiel verwendet). Dieser Wert bleibt nun solange in der Variable $flash gespeichert, bis er ausgelesen wird.
In einer Action kann man dann dort mittels $this->flash zugreifen, im Template einfach $flash.

Außer $this->redirect gibt es noch weitere Möglichkeiten zum Eingriff in den Kontrollfluß.

Zeilen 100-104 gelöscht:

function redirect_action() {

    $this->redirect('example/asite/helloworld/Hallo Welt! Dieses mal sogar weitergeleitet von redirect!');

}

(:source lang=php linenum:)[@

Zeilen 110-111 hinzugefügt:

Ruft man $this->render_text(…) auf so wird nur der angegebene Text ohne jeglichen Stud.IP-Kontext ausgegeben.

Zeilen 119-120 hinzugefügt:

$this->render_action(action) ruft das Template für eine Action in diesem Controller auf und gibt es mit Stud.IP-Kontext aus.

Zeilen 128-129 hinzugefügt:

$this->render_template(pfad_zum_controller/name_des_controllers/name_des_templates) gibt das angebgene Template ohne Stud.IP-Kontext aus.

Zeilen 137-138 hinzugefügt:

Mit $this->render_nothing() sagt man Trails: Bitte kein Template ausgeben.

Zeilen 141-142 hinzugefügt:

Die folgende Datei ist die view üfr unser Beispiel.

Zeilen 145-146 bearbeitet:

// Füllt man die Variable $infobox, se erhält man eine Infobox

geändert in:

// Füllt man die Variable $infobox, so erhält man eine Infobox

Zeilen 158-159 bearbeitet:
geändert in:

@]

Die automagische Variable Infobox wird wie eine klassische Infobox mit einem Array gefüllt und dann automatisch ausgegeben. Tut man dies nicht, so wird die Infobox in der Ausgabe als fehlend angezeigt.

(:source lang=php linenum:)[@

Zeilen 171-176 gelöscht:

<br> So erhält man einen Pfad zu einem Controller:<br> $controller->url_for('example/asite/backendwithmessage');<br> <br> <a href="<?= $controller->url_for('example/asite/backendwithmessage') ?>"><button>Ausprobieren</button></a>

Zeilen 174-175 bearbeitet:

app/views/example/asite/_feedback.php

geändert in:

$this->render_partial(template) kennt man schon von den normalen Templates. Es erlaubt einem, innerhalb eines Templates ein Subtemplate, ein sogenanntes partial zu inkludieren und anzeigen. Der Inhalt dieser partials wird weiter unten erklärt.
Besonders beachten sollte man, das render_partial einem einen String zurückliefert, den man erst noch ausgeben muss. In unserem Beispiel geschiet dies mittels <?=.

Zeilen 178-190 hinzugefügt:

<br> So erhält man einen Pfad zu einem Controller:<br> $controller->url_for('example/asite/backendwithmessage');<br> <br> <a href="<?= $controller->url_for('example/asite/backendwithmessage') ?>"><button>Ausprobieren</button></a> @]

$controller->url_for(path_to_action) ist die wohl wichtigste Funktion innerhalb eines Templates. Wie der Name schon andeutet, erhält man hier eine URL zu einer bestimmten Action in einem bestimmten Controller.
Dies ist die URL die man in Formular, Links, etc. hineinsteckt, wenn man sich innerhalb von Trails bewegen möchte.

app/views/example/asite/_feedback.php (:source lang=php linenum:)[@

Zeilen 196-198 bearbeitet:

@]

geändert in:

@]

Dies ist das oben bereits genannte partial. Partials haben automatisch Zugriff auf alle Variablen ihres Eltern-Templates (dort wo render_partial gesagt wurde). In diesem speziellen Fall wird auf die automagische Variable $flash zugegriffen, die in der backendwithmessage_action definiert hatten.

 
 
29.03.2010 13:22 Uhr von tgloeggl -
Zeile 71 hinzugefügt:
Zeilen 84-86 hinzugefügt:

Diese Action beinhaltet zwei der wohl wichtigsten Möglichkeiten von Trails.
Mit $this->redirect(pfad_zum_controller/name_des_controllers/action[/parameter]

Zeile 125 bearbeitet:

app/views/example/asite/_feedback.php

geändert in:

app/views/example/asite/index.php

Zeilen 126-134 gelöscht:

if ($flash['nachricht']['message']) {

    foreach ($flash['nachricht']['message'] as $nachricht) {
        echo MessageBox::info($nachricht);
    }

} @]

app/views/example/asite/index.php (:source lang=php linenum:)[@

Zeilen 157-166 hinzugefügt:

@]

app/views/example/asite/_feedback.php (:source lang=php linenum:)[@ if ($flash['nachricht']['message']) {

    foreach ($flash['nachricht']['message'] as $nachricht) {
        echo MessageBox::info($nachricht);
    }

}

 
 
29.03.2010 13:20 Uhr von tgloeggl -
Zeilen 55-56 bearbeitet:

Hierbei handelt es sich nun um eine Action. Davon kann es in jedem Controller beliebig viele geben. die index_action hat dabei einen kleinen Sonderstatus, wurd nämlich in der URL keine Action angegeben, so dient diese als Fallback.

geändert in:

Hierbei handelt es sich nun um eine Action. Davon kann es in jedem Controller beliebig viele geben. die index_action hat dabei einen kleinen Sonderstatus, wird nämlich in der URL keine Action angegeben, so dient diese als Fallback.

Zeilen 60-63 bearbeitet:

Diese Url hat folgendes Schema:
http://irgendeinstudip/dispatch.php/pfad_zum_controller/name_des_controllers/action

geändert in:

Diese Url hat dabei folgendes Schema:
http://irgendeinstudip/dispatch.php/pfad_zum_controller/name_des_controllers/name_der_action[/parameter1][/parameter2][…]

Das besondere am Trails-Framework ist, dass man sich nicht erst aus der Template-Factory ein Template holen muss, sondern dass (solange man nichts anderes sagt) implizit ein Template, welches zur Action gehört, anzeigt.
Diese Templates liegen unter 'app/views' und dort in diesem Fall unter 'example/asite/index.php'.

Variablen an dieses Template übergibt man, indem sie mittels $this setzt. Im Beispiel oben sieht man, dass $this->daten ein Array erhält. Im Template hat man dann automagisch eine Variable $daten zur Hand, die eben die im Controller zugewiesenen Werte enthält, dazu weiter unten im Ausgabetemplate mehr.

Eine Action kann bei Trails mehr tun, als nur Daten an ein automagisch geladenes Template zu übergeben, sie kann auch auf den Kontrollfluss einfluss nehmen.

Dazu folgende Beispiel-Actions:

Zeilen 72-105 bearbeitet:
    function backendwithmessage_action() {
        // delete something
        $this->flash['nachricht'] = array('message' => array('Diese Nachricht wurde bereits in der delete_action in der reservierten Variable flash gespeichert!'));

        // return to index-action
        $this->redirect('example/asite/index');

    }

    function redirect_action() {
        $this->redirect('example/asite/helloworld/Hallo Welt! Dieses mal sogar weitergeleitet von redirect!');
    }

    function helloworld_action($text = 'Hallo Welt!') {
        $this->render_text(
            'helloworld, $this->render_text(\''. htmlReady(urldecode($text)) .'\')<br>' .
            'Hier wird das einfach nur Text ausgegeben, ohne Layout<br><br>' .
            htmlReady(urldecode($text))
        );
    }

    function index2_action() {
        $this->daten = array('index2, $this->render_action(\'index\')', 'Hier wird das Template für eine Action in diesem Controller gerendert, mit Layout');
        $this->render_action('index');
    }

    function index3_action() {
        $this->daten = array('index3: $this->render_template(\'example/asite/index\')', 'Hier wird nur ein Template aus view gerendert, ohne Layout');
        $this->render_template('example/asite/index');
    }

    function nihilist_action() {
        $this->render_nothing();
    }
geändert in:

function backendwithmessage_action() {

    // delete something
    $this->flash['nachricht'] = array('message' => array('Diese Nachricht wurde bereits in der delete_action in der reservierten Variable flash gespeichert!'));

    // return to index-action
    $this->redirect('example/asite/index');

} @]

(:source lang=php linenum:)
function redirect_action() {
    $this->redirect('example/asite/helloworld/Hallo Welt! Dieses mal sogar weitergeleitet von redirect!');
}

(:source lang=php linenum:)[@
function helloworld_action($text = 'Hallo Welt!') {
    $this->render_text(
        'helloworld, $this->render_text(\''. htmlReady(urldecode($text)) .'\')<br>' .
        'Hier wird das einfach nur Text ausgegeben, ohne Layout<br><br>' .
        htmlReady(urldecode($text))
    );
}
(:source lang=php linenum:)
function index2_action() {
    $this->daten = array('index2, $this->render_action(\'index\')', 'Hier wird das Template für eine Action in diesem Controller gerendert, mit Layout');
    $this->render_action('index');
}
(:source lang=php linenum:)
function index3_action() {
    $this->daten = array('index3: $this->render_template(\'example/asite/index\')', 'Hier wird nur ein Template aus view gerendert, ohne Layout');
    $this->render_template('example/asite/index');
}

(:source lang=php linenum:)[@ function nihilist_action() {

    $this->render_nothing();
 
 
29.03.2010 13:11 Uhr von tgloeggl -
Zeile 39 gelöscht:
Zeilen 47-49 bearbeitet:
    function index_action($param1 = false, $param2 = false) {
        // Daten besorgen
geändert in:
    function index_action($param1 = false, $param2 = false)
    {
        // Daten holen
Zeilen 55-56 bearbeitet:

Hierbei handelt es sich nun um eine Action. Davon kann es in jedem Controller beliebig viele geben. die index_action hat dabei einen kleinen Sonderstatus, wurd nämlich in der URL keine Action angegeben, so dient diese als Fallback.

geändert in:

Hierbei handelt es sich nun um eine Action. Davon kann es in jedem Controller beliebig viele geben. die index_action hat dabei einen kleinen Sonderstatus, wurd nämlich in der URL keine Action angegeben, so dient diese als Fallback.
Diese Action kann in Stud.IP nun wie folgt aufgerufen werden:
http://irgendeinstudip/dispatch.php/example/asite/index

Diese Url hat folgendes Schema:
http://irgendeinstudip/dispatch.php/pfad_zum_controller/name_des_controllers/action

Zeilen 139-144 hinzugefügt:

<br> So erhält man einen Pfad zu einem Controller:<br> $controller->url_for('example/asite/backendwithmessage');<br> <br> <a href="<?= $controller->url_for('example/asite/backendwithmessage') ?>"><button>Ausprobieren</button></a>

 
 
29.03.2010 13:05 Uhr von tgloeggl -
Zeile 33 hinzugefügt:
Zeilen 35-36 gelöscht:

<?php

Zeilen 37-38 bearbeitet:
geändert in:

require_once 'app/controllers/studip_controller.php';

Zeilen 40-47 hinzugefügt:

@]

Die erste Entscheidung, die man treffen muss, ist, ob man diesen Controllern nur als eingeloggter Nutzer sehen kann oder auch wenn man nicht eingeloggt ist. Dafür entscheidet man sich einfach für eine von zwei Klassen, von denen man erbt.
Wie der Name schon sagt, ist die Klasse 'AuthenticatedController' diejenige, die dafür sorgt, dass nur eingeloggt Nutzer diesen Trails-Controller aufrufen können. Erbt man von 'StudipController', so ist eben (erstmal) kein einloggen nötig. Das müsste der Controller dann bei Bedarf selbst tun.
Der Klassenname des Controllers muss dabei folgenden Aufbau folgen: Pfadangabe1_Pfadangabe2_ … DateinameController

(:source lang=php linenum:)[@

Zeilen 53-57 bearbeitet:
geändert in:

@]

Hierbei handelt es sich nun um eine Action. Davon kann es in jedem Controller beliebig viele geben. die index_action hat dabei einen kleinen Sonderstatus, wurd nämlich in der URL keine Action angegeben, so dient diese als Fallback.

(:source lang=php linenum:)[@

Zeilen 98-99 gelöscht:

<?php

Zeilen 107-108 gelöscht:

<?php

 
 
29.03.2010 12:49 Uhr von tgloeggl -
Zeilen 32-33 bearbeitet:

Das Model

Die View

geändert in:

app/controllers/example/asite.php

(:source lang=php linenum:)
<?php

require_once 'app/controllers/authenticated_controller.php';

class Example_AsiteController extends AuthenticatedController {
    function index_action($param1 = false, $param2 = false) {
        // Daten besorgen

        $this->daten = array('index', 'Hier wird automagisch das in views/exmaple/asite/index.php hinterlegte Template verwendet. Der Dateiname des Templates ist immer gleich der Action');
    }

    function backendwithmessage_action() {
        // delete something
        $this->flash['nachricht'] = array('message' => array('Diese Nachricht wurde bereits in der delete_action in der reservierten Variable flash gespeichert!'));

        // return to index-action
        $this->redirect('example/asite/index');

    }

    function redirect_action() {
        $this->redirect('example/asite/helloworld/Hallo Welt! Dieses mal sogar weitergeleitet von redirect!');
    }

    function helloworld_action($text = 'Hallo Welt!') {
        $this->render_text(
            'helloworld, $this->render_text(\''. htmlReady(urldecode($text)) .'\')<br>' .
            'Hier wird das einfach nur Text ausgegeben, ohne Layout<br><br>' .
            htmlReady(urldecode($text))
        );
    }

    function index2_action() {
        $this->daten = array('index2, $this->render_action(\'index\')', 'Hier wird das Template für eine Action in diesem Controller gerendert, mit Layout');
        $this->render_action('index');
    }

    function index3_action() {
        $this->daten = array('index3: $this->render_template(\'example/asite/index\')', 'Hier wird nur ein Template aus view gerendert, ohne Layout');
        $this->render_template('example/asite/index');
    }

    function nihilist_action() {
        $this->render_nothing();
    }
}

Die View

app/views/example/asite/_feedback.php

(:source lang=php linenum:)
<?php

if ($flash['nachricht']['message']) {
    foreach ($flash['nachricht']['message'] as $nachricht) {
        echo MessageBox::info($nachricht);
    }
}

app/views/example/asite/index.php

(:source lang=php linenum:)
<?php

// Füllt man die Variable $infobox, se erhält man eine Infobox

$infobox['picture'] = 'modules.jpg';        // Das Bild der Infobox

$infobox['content'] = array(                // Die Elemente der Infobox
    array(
        'kategorie' => _('Informationen:'),
        'eintrag'   => array(
            array('icon' => 'ausruf_small.gif', 'text' => _('Diese Einträge erhält man durch Füllen der Variable $infobox in einem Template (einer View)')),
            array('icon' => 'link_intern.gif',  'text' => 'Man kann natürlich auch mehrere Einträge haben.')
        )
    )
);


// Ausgeben der im Controller gesetzten Variable
var_dump($daten);
?>

<!-- Ein wenig Text/HTML -->
<b>Huhu</b>

<!-- Ein partial-Template -->
<?= $this->render_partial('example/asite/_feedback'); ?>
 
 
29.03.2010 12:42 Uhr von tgloeggl -
Zeilen 23-26 bearbeitet:
geändert in:

Ein Controller sollte nie direkt Zugriff auf die Datenbank nehmen und auch sonst so wenig wie möglich and Datenstrukturen generieren. Er ist dafür zuständig, die korrekten Daten aus dem richtigen model in die view zu schaffen. Models liegen im passenden Verzeichnis, nämlich 'app/models' und müssen zur Verwendung mittels require_once im Controller inkludiert werden.

Ausgaben passieren prinzipiell nur innerhalb der view in Templates. Die Templates liegen dabei in 'app/views' und haben darunter folgende Pfadstruktur '/pfad_zum_controller/name_des_controllers/name_der_action.php'

Zeile 29 bearbeitet:

Im Folgenden wird im Detail eine Beispiel-Trails-Seite erklärt, die komplette Seite gibt es auch als ZIP: Attach:trails_example.zip

geändert in:

Um das ganze Konzept und die Möglichkeiten zu veranschaulichen wird im Folgenden en detail eine Beispiel-Trails-Seite erklärt, die komplette Seite gibt es auch als ZIP: Attach:trails_example.zip

 
 
29.03.2010 12:35 Uhr von tgloeggl -
 
 
29.03.2010 12:06 Uhr von tgloeggl -
Zeilen 17-30 hinzugefügt:

Die Struktur

Der Controller ist der Dreh- und Angelpunkt für eine Seite.

Einen neuen Controller erstellt man im Verzeichnis 'app/controllers'. Im einfachsten Fall erstellt man dort direkt eine PHP-Datei. Handelt es sich um eine größere Sammlung von Controllern, kann man auch Unterverzeichnisse erstellen. Die dortige Pfadstruktur überträgt sich dabei 1 zu 1 auf die URL.

Beispiel

Im Folgenden wird im Detail eine Beispiel-Trails-Seite erklärt, die komplette Seite gibt es auch als ZIP: Attach:trails_example.zip

Der Controller

Das Model

Die View

 
 
29.03.2010 11:50 Uhr von tgloeggl -
Zeilen 9-16 bearbeitet:

http://luniki.github.com/trails/trails.pdf

geändert in:

http://luniki.github.com/trails/trails.pdf

Trails & Stud.IP

Im Folgenden gibt es eine kleine Einführung zur Entwicklung von Trails-Seiten in Stud.IP.

Trails folgt dem MVC-Paradigma. Diesem Paradigma folgend gibt es in Stud.IP im Hauptverzeichnis einen Ordnern namens 'app', welcher drei Unterordner hat, 'controllers', 'models' & 'views' besitzt.

 
 
29.03.2010 11:46 Uhr von tgloeggl -
Zeilen 8-9 bearbeitet:

Es gibt auch einen Foliensatz, der die Grundlagen von Trails

geändert in:

Es gibt auch einen Foliensatz, der das Konzept hinter Trails und die Konfigration von Trails für Stud.IP zeigt:
http://luniki.github.com/trails/trails.pdf

 
 
29.03.2010 11:45 Uhr von tgloeggl -
Zeilen 1-8 bearbeitet:

Trails

geändert in:

Trails

Trails ist ein eigenständiges MVC-Framework, welches in Stud.IP fertig konfiguriert zur Verfügung steht.

Die API-Dokumentation findet man hier:
http://luniki.github.com/trails/doc/index.html

Es gibt auch einen Foliensatz, der die Grundlagen von Trails

 
 
07.11.2008 12:05 Uhr von ansgar -
Zeile 1 hinzugefügt:

Trails

 

 

Quelle: Basis-Wiki-Hilfe | Letzte Änderung: 01.04.2011 23:29 Uhr, tthelen | Local view: Basis-Hilfe