tas2580
Blog über Webentwicklung

Eigene Seite per Extension erstellen

Oft will man mit einer Extension das Forum um eine eigene Seite erweitern. In diesem Tutorial wird gezeigt wie man eine eigene einfache HTML Seite mit Hilfe eines Controllers erstellt.

In dem Beispiel wird foo\bar als Extension Name verwendet, das sollte man durch den Namen seiner Extension austauschen.

Controller anlegen

Für den PHP Code seiner Seite braucht man einen Controller in den sämtliche PHP Befehle die auf der Seite ausgeführt werden kommen. Auch wenn man kein eigenes PHP ausführen will benötigt man ein Grundgerüst das die Seite ausgibt. Ein Controller kann mehrere Modi haben und so gleich mehrere Seiten in einer Datei ausgeben.
Für jeden Modus wird eine Methode mit dem Namen des Modus benötigt die als Return $this->helper->render() aufruft. Eine minimale Methode die einfach Variablen an das Template sendet sieht folgendermaßen aus:

	public function handle()
	{
		$this->template->assign_vars(array(
			'FOOBAR'		=> 'foobar',
			'DATE'			=> date(DATE_RFC822),
			'U_LINK'		=> $this->helper->route('foo_bar_variable', array('id' => 'meine_id')),
		));
		return $this->helper->render('my_page.html', $this->user->lang['MY_PAGE']);
	}

Mit $template->assign_vars(array()); werden die Variablen FOOBAR, DATE und U_LINK an das Template gesendet. Hier wird einmal der String "foobar", einmal das Datum nach RFC822 und ein Link zu einer Route an das Template gesendet. Natürlich kann man hier je nach Bedarf jede beliebige PHP Variable vom Typ String verwenden und an das Template ausgeben.
Am Ende wird mit return $this->helper->render('my_page.html', $this->user->lang['MY_PAGE']); angegeben welche Template Datei verwendet werden soll (hier my_page.html) und wie der Titel im Browser lauten soll, dazu wird in dem Beispiel die Sprachvariable MY_PAGE verwendet.

Seite mit Variabler URL

Oft will man eine Variable wie eine ID oder ähnliches an seine Seite per URL übergeben. Dazu bauen wir in den Controller eine zweite Methode die später einen Teil der URL als Variable benutzt.

	public function variable($id)
	{
		$this->user->add_lang_ext('foo/bar', 'common');
		$this->template->assign_vars(array(
			'ID'		=> $id,
		));
		return $this->helper->render('my_variable.html', $this->user->lang['MY_PAGE']);
	}

Der Aufbau der Methode ist im Prinzip der selbe wie bei der ersten Methode, nur dass hier die Variable $id als Argument übergeben wird. In $id ist später der Inhalt der per URL übergeben wird enthalten und kann nach belieben im PHP Code verwendet werden. Da es sich dabei um eine beliebige Benutzer Eingabe handelt sollte man den Inhalt noch prüfen um sicher zu stellen dass hier keine bösartigen Werte übergeben werden. Da hier gezeigte Beispiel wäre anfällig für XSS da der Benutzer in $id auch HTML oder Javascript übergeben könnte. Da das aber nur als Beispiel wie man Variablen aus der URL nutzt dienen soll, will ich darauf aber hier nicht näher eingehen. Die Variable wird nun einer Template Variable zugewiesen um sie später im Template auszugeben zu können.
Auch hier wird zusätzlich noch die Sprachdatei geladen und im Unterschied zur ersten Methode my_variable.html als Template Datei benutzt.

Jetzt braucht man noch einen Konstruktor in dem man die benötigten Objekte und Variablen lädt.

	public function __construct(\phpbb\controller\helper $helper, \phpbb\template\template $template, \phpbb\user $user)
	{
		$this->helper = $helper;
		$this->template = $template;
		$this->user = $user;
	}

Da wir in unseren Methoden die Objekte $helper, $template und $user verwenden fragen wir im Konstruktor die drei Objekte ab und übergeben sie an lokalen Variablen innerhalb der Klasse. Wenn weitere Objekte oder Variablen benötigt werden muss der Konstruktor entsprechend erweitert werden.

Wenn man Sprach-Variablen in der Datei verwenden möchte kann man mit $this->user->add_lang_ext('foo/bar', 'common'); eine Sprachdatei laden. Dabei wird vorne der Name der Extension (foo/bar) und hinten der Name der Datei ohne Endung angegeben (common).

Zum Schluss muss man noch den Namespace anpassen namespace foo\bar\controller;

Die komplette Datei /controller/main.php
<?php
/**
*
* @package phpBB Extension - bar
* @copyright (c) 2015 foo (https://example.com)
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
*
*/
namespace foo\bar\controller;

class main
{
	/** @var \phpbb\controller\helper */
	protected $helper;

	/** @var \phpbb\template\template */
	protected $template;

	/** @var \phpbb\user */
	protected $user;

	/**
	* Constructor
	*
	* @param \phpbb\controller\helper		$helper			Controller helper object
	* @param \phpbb\template\template		$template		Template object
	* @param \phpbb\user			    	$user			User object
	*/
	public function __construct(\phpbb\controller\helper $helper, \phpbb\template\template $template, \phpbb\user $user)
	{
		$this->helper = $helper;
		$this->template = $template;
		$this->user = $user;
	}

	/**
	* Controller for route /my_page/
	*
	* @return \Symfony\Component\HttpFoundation\Response A Symfony Response object
	*/
	public function handle()
	{
		$this->user->add_lang_ext('foo/bar', 'common');
		$this->template->assign_vars(array(
			'FOOBAR'		=> 'foobar',
			'DATE'			=> date(DATE_RFC822),
			'U_LINK'		=> $this->helper->route('foo_bar_variable', array('id' => 'meine_id')),
		));
		return $this->helper->render('my_page.html', $this->user->lang['MY_PAGE']);
	}
	
	/**
	* Controller for route /my_variable/$id
	*
	* @return \Symfony\Component\HttpFoundation\Response A Symfony Response object
	*/
	public function variable($id)
	{
		$this->user->add_lang_ext('foo/bar', 'common');
		$this->template->assign_vars(array(
			'ID'		=> $id,
		));
		return $this->helper->render('my_variable.html', $this->user->lang['MY_PAGE']);
	}
}

Da die Datei main.php heißt muss auch die Klasse main heißen (class main), wenn man die Datei anders nennt muss auch die Klasse entsprechend umbenannt werden.

Services an den Controller übergeben

Damit die Objekte $helper $template und $user die der Controller verwendet verfügbar sind müssen sie an den Konstruktor übergeben werden. Dazu wird die Datei config/services.yml benötigt. Der Inhalt der Datei sieht folgendermaßen aus:

Die komplette Datei config/services.yml
services:
    foo.bar.main:
        class: foo\bar\controller\main
        arguments:
            - @controller.helper
            - @template
            - @user

Oben wird der Name der Extension und der Datei durch Punkte getrennt angegeben, unter class: muss man den Namespace der Controller Klasse angeben. Jetzt kann man unter arguments: alle benötigten Objekte und Variablen auflisten.
Wichtig ist das man in der Datei richtig einrückt und dabei nur Leerzeichen und keine Tabs verwendet. Eine Liste der verfügbaren Services gibt es hier.

Das Routing

Damit die Seite aufgerufen werden kann muss noch eine Route angelegt werden. In der Route wird festgelegt welche Methode für eine bestimmte URL geladen werden soll. Routen werden in der Datei config/routing.yml inerhalb der Extension angelegt. Wenn man mehrere Routen benötigt kann man sie einfach untereinander schreiben.

Die komplette Datei config/routing.yml
foo_bar_index:
    pattern: /my_page/
    defaults: { _controller: foo.bar.main:handle }
foo_bar_variable:
    pattern: /variable/{id}
    defaults: { _controller: foo.bar.main:variable }
   

In der ersten Zeile wird der Name der Route festgelegt über den später mit $this->helper->route('foo_bar_index', array()) in anderen Dateien auf die Route zugegriffen werden kann. In der zweiten Zeile wird die URL angegeben über die man die Route aufrufen möchte. In der dritten Zeile wird dann noch angegeben welche Methode über die Route aufgerufen werden soll. Dabei ist foo.bar der Name der Extension, main der Name der Datei und handle der Name der Methode.

Darunter gibt es eine zweite Route die eine Variable übergibt und auf Seitenaufrufe wie variabke/1 hört. Der Aufbau ist im Prinzip der selbe wie bei der ersten Route, nur das eben noch die Variable in der URL angegeben wird. Der Name der Variable muss hier gleich wie das Argument in der entsprechenden Methode sein, in dem Fall also {id} da wir in unserem Controler auch $id verwenden (public function variable($id)). Um die zweite Route in einer PHP Datei aufzurufen kann man $this->helper->route('foo_bar_variable', array('id' => 'meine_id')) verwenden. In dem zweiten Parameter wird also ein Array mit dem Namen und dem Wert der Variable übergeben.

Wenn man noch weitere Routen in einer Datei behandeln möchte kann man einfach eine weitere Methode in den Controller einfügen und sie über eine zusätzliche Route ansprechen.

Die Template Datei

Damit die Seite funktioniert wird noch die im Controller angegebene Template Datei (my_page.html) benötigt. Die Datei legt man einfach in styles/prosilver/template an und füllt sie mit seinem HTML Code. In der Datei können jetzt auch die Template Variablen die man im Controller ausgibt verwendet werden. Variablen aus Sprachdateien kann man verwenden indem man ein L_ voranstellt.

Die komplette Datei styles/prosilver/template/my_page.html
<!-- INCLUDE overall_header.html -->
<strong>{L_FOOBAR}</strong> <a href="{U_LINK}">{FOOBAR}</a><br>
<strong>{L_DATE}</strong> {DATE}<br>
<!-- INCLUDE overall_footer.html -->

Auch für den zweiten Controller wird eine Template Datei benötigt in der die an das Template zugewiesene Variable ausgegeben wird.

Die komplette Datei styles/prosilver/template/my_variable.html
<!-- INCLUDE overall_header.html -->
<strong>{L_MY_VARIABLE}</strong> {ID}
<!-- INCLUDE overall_footer.html -->

Die Sprachdatei

Damit die Sprach-Variablen gefüllt werden braucht man noch eine Sprachdatei die man innerhalb seiner Extension im Ordner language/de anlegt. Der Dateiname muss so wie im Controller angegeben lauten, hier also common.php

Die komplette Datei language/de/common.php
&lt;?php
/**
*
* @package phpBB Extension - bar
* @copyright (c) 2015 foo (https://example.com)
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
*
*/

if (!defined('IN_PHPBB'))
{
	exit;
}

if (empty($lang) || !is_array($lang))
{
	$lang = array();
}
// DEVELOPERS PLEASE NOTE
//
// All language files should use UTF-8 as their encoding and the files must not contain a BOM.
//
// Placeholders can now contain order information, e.g. instead of
// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows
// translators to re-order the output of data while ensuring it remains correct
//
// You do not need this where single placeholders are used, e.g. 'Message %d' is fine
// equally where a string contains only two placeholders which are used to wrap text
// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine
//
// Some characters you may want to copy&amp;paste:
// ’ » “ ” …
//
$lang = array_merge($lang, array(
	'FOOBAR'		=> 'Foo Bar',
	'DATE'			=> 'Datum',
	'MY_PAGE'		=> 'Meine Seite',
	'MY_VARIABLE'	=> 'Meine Variable',
));

Eigene CSS oder JS Datei einbinden

Oft will man für seine erstellte Seite ein eigenes CSS oder JavaScript einbinden, das geht am besten über ein Template Event. Aber zu erst muss man seine Datei anlegen, für eine CSS Datei legt man unter /styles/prosilver/theme/style.css seine CSS Datei an und füllt sie mit dem gewünschten CSS Code.
Danach legt man unter /styles/prosilver/template/event/overall_header_head_append.html ein Template Event an um die CSS Datei einzubinden. Das Event fügt HTML Code in den Head jeder Seite ein und eignet sich deshalb perfekt für das einbinden von CSS Dateien. In die Datei schreibt man dann:

<!-- INCLUDECSS @foo_bar/style.css -->

Mit INCLUDECSS gibt man an das man eine CSS Datei einbinden möchte, mit @foo_bar gibt man den Namen der Extension an, am Ende muss dann nur noch der Dateiname der CSS Datei stehen.

Die Datei wird jetzt allerdings auf allen Seiten und nicht nur innerhalb der Extension eingebunden. Wenn man möchte, dass die Datei nur auf Seiten die zur Extension gehören eingebunden wird muss man dazu eine Template Variable definieren die angibt ob man sich innerhalb der Extension befindet oder nicht. Dazu fügt man folgenden Code in die Controller Klasse die für die jeweilige Seite zuständig ist ein:

$this->template->assign_vars(array(
	'S_IN_FOOBAR'		=> true,
));

Falls man mehrere Seiten über einen Controller laufen lässt und das CSS auf jeder Seite einbinden möchte muss man die Template Variable auch in jeder Controller Klasse ausgeben oder alternativ den Code in den Konstroktor (unter __construct) des Controllers einfügen. Wenn man schon einen Block hat in dem man Variablen an das Template übergibt kann man den natürlich auch einfach erweitern. Dadurch wird die Template Variable S_IN_FOOBAR auf true gesetzt. Wie man die Template Variable nennt ist eigentlich egal so lange man nichts verwendet was schon von phpBB verwendet wird. Solche Switch Variablen sollte man immer mit S_ beginnen lassen damit klar ist dass es sich hier um einen Switch handelt.
Jetzt muss man im Template Event wo die CSS Datei eingebunden wird nur noch prüfen ob die Variable gesetzt ist oder nicht, dazu ändert man in /styles/prosilver/template/event/overall_header_head_append.html einfach den Code in:

<!-- IF S_IN_FOOBAR -->
	<!-- INCLUDECSS @foo_bar/style.css -->
<!-- ENDIF -->

So wird die CSS Datei nur noch dann eingebunden wenn sie auch wirklich benötigt wird, das verhindert dass viele Dateien eingebunden werden wenn man viele Extensions installiert hat. Außerdem sinkt so die Change das man mit mehreren CSS Dateien sich gegenseitig irgendwas überschreibt.

Mit einer JS Datei geht das genau so, nur das man hier statt INCLUDECSS eben INCLUDEJS verwenden muss.

Jetzt kann man nachdem man die Extension installiert hat unter /my_page/ seine eigene Seite aufrufen und dort auf den Link zur zweiten Seite mit der Variable klicken, wenn man die Variable in der URL verändert sieht man das sich auch die Ausgabe auf der Seite ändert. Das ganze sieht natürlich noch nicht wirklich schön aus und muss nach Bedarf angepasst werden.

Wenn man einen Link zu seiner Seite in die Navigation des Forums einfügen möchte kann man sich das Tutorial anschauen.