DaPortal

<?xml version="1.0" encoding="iso-8859-15"?>
<!-- $Id$ -->
<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
<!ENTITY firstname "Pierre">
<!ENTITY surname "Pronchery">
<!ENTITY email "khorben@defora.org">
<!ENTITY package "DaPortal">
<!ENTITY name "daportal">
<!ENTITY title "DaPortal Programmer Manual">
<!ENTITY purpose "DaPortal internals">
]>
<article>
<info>
<title>&package; internals</title>
<date>@DATE@</date>
<productname>&package;</productname>
<authorgroup>
<author>
<personname><firstname>&firstname;</firstname><surname>&surname;</surname></personname>
<contrib>Code and documentation.</contrib>
<address><email>&email;</email></address>
</author>
</authorgroup>
<copyright>
<year>2012</year>
<year>2013</year>
<year>2014</year>
<year>2015</year>
<holder>&firstname; &surname; &lt;&email;&gt;</holder>
</copyright>
<legalnotice>
<para>This guide was written for the DeforaOS project (and may be used by
others).</para>
<para>Permission is granted to copy, distribute and/or modify this document
under the terms of the GNU General Public License, Version 3 as published by
the Free Software Foundation.</para>
</legalnotice>
<publisher><publishername>The DeforaOS Project</publishername></publisher>
</info>
<warning>
<title>Warning: work in progress</title>
<para>These notes are based on a development version of &package; 2.</para>
</warning>
<section>
<title>Introduction notes</title>
<section>
<title>Where to find &package;</title>
<para>First, make sure that you have downloaded the latest stable version of
&package; 2. It can be found there: <ulink
url="http://www.defora.org/os/project/download/12/DaPortal">http://www.defora.org/os/project/download/12/DaPortal</ulink>.
This guide will assume the resulting archive to be called
<filename>DaPortal.tar.gz</filename>.</para>
<para>Alternatively, you may choose to track the development of &package;
2, or any given branch from the Source Code Management system. Some
instructions to do so can be found here: <ulink
url="http://www.defora.org/os/project/download/12/DaPortal">http://www.defora.org/os/project/download/12/DaPortal</ulink>.</para>
</section>
<section>
<title>Relevant documentation</title>
<para>In this document, the reader is expected to be familiar with the
installation of &package;. This process is documented in <ulink
url="install.html">Installing DaPortal</ulink>, which contains important
information about the organization of files and directories relevant to
&package;.</para>
</section>
</section>
<section>
<title>Entry point</title>
<para>&package; has a single entry-point for all of its applications, found
in <filename>src/daportal.php</filename>. Its contents are simple enough to
be reproduced here:</para>
<programlisting><![CDATA[<?php
require_once('./system/autoload.php');
global $config;
$config = new Config();
$config->load('../daportal.conf');
if(($engine = Engine::attachDefault()) !== FALSE)
{
$request = $engine->getRequest();
$response = $engine->process($request);
$engine->render($response);
}
unset($engine);
?>]]></programlisting>
<para>First, the code reads configuration data from the <ulink
url="daportal.conf.html"><filename>../daportal.conf</filename></ulink> file.
This file is organized as an ini-style configuration file, with series of
variables (one per line) found in separate sections. This file is currently
used for most of the configureable aspects of &package;.</para>
<para>Next, the <filename>engine.php</filename> file is loaded from the
<filename>system</filename> sub-directory. This directory contains generic,
library code that can freely be used by the different modules. After
including this file:<orderedlist>
<listitem><para>The relevant implementation of the
<classname>Engine</classname> class is loaded,</para></listitem>
<listitem><para>The user's request is determined and
obtained,</para></listitem>
<listitem><para>The output (or <quote>page</quote>) is rendered and
delivered to the user.</para></listitem>
</orderedlist></para>
<para>Finally, the engine loaded is explicitely free'd; this was found to be
necessary in some cases (like with the <classname>GtkEngine</classname>) as
otherwise, some functionalities of PHP no longer work as expected while
executing the engine's destructor routines.</para>
<para>In the rest of this documentation, source files will be mentioned
relatively to the <filename>src</filename> directory.</para>
</section>
<section>
<title>Engine selection</title>
<para>The actual processing engine for &package; is determined at run-time, or
possibly enforced in the <filename>daportal.conf</filename> configuration
file (through the <varname>backend</varname> variable in the
<varname>[engine]</varname> section).</para>
<para>When auto-detecting the engine, every PHP file found in the
<filename>engines</filename> directory is loaded in sequence. The file is
expected to provide a predictable class name, such as
<classname>FileNameEngine</classname> for
<filename>filename.php</filename>. It should implement the abstract
<classname>Engine</classname> class.</para>
<para>Two methods are used when detecting the adequate engine:<itemizedlist>
<listitem><para><function>match()</function>: returns an integer value,
representing a score at the relevance of the engine in the context
detected (typically between 0 and 100)</para></listitem>
<listitem><para><function>attach()</function>: returns a boolean value,
<returnvalue>TRUE</returnvalue> in case of success or
<returnvalue>FALSE</returnvalue> otherwise.</para></listitem>
</itemizedlist></para>
<para>This auto-detection methodology is found in multiple sub-systems of
&package; (file formats, templates...).</para>
</section>
<section>
<title>Processing requests</title>
<section>
<title>About requests</title>
<para>Requests represent messages, typically sent from the end user and
directed at a given part of the &package; installation. Requests can be
created directly within the code, or obtained from user input (typically
interpreted by the <classname>Engine</classname> instance).</para>
<para>The following parameters define a request:<itemizedlist>
<listitem><para><varname>module</varname>: the module to be selected to
handle this request (required)</para></listitem>
<listitem><para><varname>action</varname>: the action to be triggered
within the given module (optional)</para></listitem>
<listitem><para><varname>ID</varname> and <varname>title</varname>: both
can be supplied independently, and their actual signification is up to
the module (and action) selected;</para></listitem>
<listitem><para>optionally, an arbitrary number of additional parameters,
defined through a name and value.</para></listitem>
</itemizedlist></para>
<para>Requests implement the <classname>Request</classname> class defined in
<filename>system/request.php</filename>. They are always processed through a
specific <classname>Engine</classname> instance.</para>
</section>
<section>
<title>Database</title>
<para>The storage database is accessed through the processing engine, with
the actual storage backend being loaded automatically when it is required.
This typically happens when invoking the <function>getDatabase()</function>
method of the <classname>Engine</classname> instance.</para>
<para>Much like the main engine, the backend is selected as configured in the
<varname>[database]</varname> section of the
<filename>daportal.conf</filename> configuration file (or can be
automatically detected in some cases). The different classes implementing
the <classname>Database</classname> abstract class (described in
<filename>system/database.php</filename>) are loaded from individual files,
expected in the <filename>database</filename> directory.</para>
<para>A limited implementation of the <classname>Database</classname> class,
called <classname>DatabaseDummy</classname>, is returned by the engine when
it was unable to attach a functional <classname>Database</classname>
instance. It will simply fail to obtain any data.</para>
</section>
<section>
<title>Authentication</title>
<para>User authentication within &package; is also delegated to a distinct
backend, as its proper operation typically depends on the actual
<classname>Engine</classname> instance selected. These backends are expected
in the <filename>auth</filename> sub-directory, and should implement the
<classname>Auth</classname> class from <filename>system/auth.php</filename>.
Auto-detection of the authentication backend to be loaded can be
circumvented through the <varname>[auth]</varname> section of the
<filename>daportal.conf</filename> configuration file.</para>
<para>The credentials with &package; include the following
information, much like found on a typical UNIX system:<itemizedlist>
<listitem><para>a user ID: either the default value, 0 (meaning anonymous,
least privileges) or a positive integer (actual users, with equal
privileges by default)</para></listitem>
<listitem><para>a username, which must be unique through the
system;</para></listitem>
<listitem><para>a group ID: defaulting to 0 (meaning nogroup), they can be
used by modules to assign differing privileges, or otherwise
distinguishing groups of users;</para></listitem>
<listitem><para>a group name: the name of the default group of the
user;</para></listitem>
<listitem><para>an additional list of groups that the user may be part
of;</para></listitem>
<listitem><para>an administrative flag: it grants complete privileges to
the current user, with the actual signification depending on the current
module.</para></listitem>
</itemizedlist></para>
</section>
<section>
<title>Idempotence</title>
<para>The concept of idempotence is also explicitly implemented (and
hopefully enforced) throughout the code. Its actual meaning in the context
of &package; is to prevent actions to be performed without the explicit
consent of the user; this is the default for newly-created requests. It is
up to each individual module to determine whether the current operation is
<quote>safe</quote> in this regard or not.</para>
<para>The main rationale behind this concept is to prevent CSRF attacks with
session-based, disconnected authentication mechanisms (like when using
cookies over HTTP). It is also worth mentioning that some requests may be
set to expire over time for increased security.</para>
</section>
</section>
<section>
<title>Within modules</title>
<section>
<title>About modules</title>
<para>Modules are the most essential part of the &package; engine: they
implement the visible functionality to the end user, and provide the
operation logic. Each module has a distinct folder inside the
<filename>modules</filename> directory, where it should at least provide a
<filename>module.php</filename> file; this file is automatically loaded by
the <classname>Engine</classname> instance when required. It should
implement the <classname>Module</classname> class, as defined in
<filename>system/module.php</filename>.</para>
<para>A few modules are provided by a stock installation of
&package;, among which:<itemizedlist>
<listitem><para><quote>user</quote>: assists with user registration,
authentication, password resets and profile management;</para></listitem>
<listitem><para><quote>admin</quote>: provides a user interface dedicated
to administration duties;</para></listitem>
<listitem><para><quote>article</quote>, <quote>news</quote>,
<quote>wiki</quote>...: extend the abstract
<classname>ContentModule</classname> in
<filename>modules/content/module.php</filename>, declining some
content-management functionality in differing contexts;</para></listitem>
<listitem><para><quote>search</quote>: provides a generic way to search
through the site's contents.</para></listitem>
</itemizedlist></para>
<para>In turn, these modules can be extended and altered
individually.</para>
</section>
<section>
<title>Calls</title>
<para>The modules react to requests through calls, more precisely the
<function>call()</function> public method of the
<classname>Module</classname> class. The relevant
<classname>Engine</classname> instance along with the request are passed to
this method, which is then responsible for handling the request, given the
resources provided by the <classname>Engine</classname>.</para>
<para>Calls are divided into two categories: <emphasis>internal</emphasis>
and <emphasis>public</emphasis>.</para>
<para>Public calls are issued by users directly, and required to return
instances of the <classname>Response</classname> object. Content is
typically formatted and delivered back to the user by building pages through
<classname>PageResponse</classname> objects, as described below in <xref
linkend="pages"/>.</para>
<para>Internal calls on the other hand are meant for inter-module
communication, and can be used as an internal API. There are no restrictions
on return values (or their type).</para>
</section>
<section>
<title>Helpers</title>
<para>Common functionality can be provided on demand to the modules,
typically through the inclusion of specific files in the
<filename>system</filename> directory. Some helpers are provided by a stock
installation of &package;, among which:<itemizedlist>
<listitem><para><quote>content</quote>: provides some functionality around
the handling of content (creation, modification,
deletion...)</para></listitem>
<listitem><para><quote>locale</quote>: assists in the translation of the
user interface (typically using the gettext PHP
extension)</para></listitem>
<listitem><para><quote>mail</quote>: helps sending
e-mail;</para></listitem>
<listitem><para><quote>user</quote>: to lookup and manage user
information.</para></listitem>
</itemizedlist></para>
<para>Modules themselves may be organized so as to allow easier integration
and alteration of functionality, particularly when inheriting them. This is
typically performed through the introduction of helper functions, specific
to the given module (which are therefore declared as protected). Their
implementation details are specific to each and every module, although
consistency is hopefully preserved among most.</para>
</section>
<section>
<title>Internal requests</title>
<para>Cooperation between modules is sometimes helpful, or even necessary:
this should however be performed without exposing additional, undesired
functionality to the end user. A special flag can therefore be applied to
requests when sending them through an engine for processing: they are then
considered internal, and can be adequately treated as such by the target
module.</para>
</section>
</section>
<section>
<title>Output and rendering</title>
<section id="pages">
<title>Building pages</title>
<para>The content generated through processing the requests is expected to be
composed of a series of chained elements, called
<classname>PageElement</classname>, each of which can also embed properties
or other instances of <classname>PageElement</classname>. For clarity, an
additional class, simply called <classname>Page</classname>, is also
available and meant to represent a top-level element.</para>
<para>Most importantly, each element has a given type, representing an
expected layout or behavior when rendered. These types are largely inspired
by graphical toolkits, such as Gtk+, and typically represent label widgets,
file choosers, toolbars, menus, and so on.</para>
<para>The <classname>PageElement</classname> and <classname>Page</classname>
classes are both defined in the <filename>system/page.php</filename>
file.</para>
</section>
<section>
<title>Applying templates</title>
<para>Single requests are expected to only output the data that is directly
relevant to them; the final content delivered to the user may however
require more information, additional cosmetics, or even some reformatting or
editing. This is exactly the task of templates; again, they are found in the
<filename>templates</filename> directory, and should implement the
<classname>Template</classname> class, as defined in
<filename>system/template.php</filename>.</para>
<para>The template to choose can be determined by the engine at run-time, or
can also be enforced within the <filename>daportal.conf</filename>
configuration file, through the <varname>template</varname> section.</para>
</section>
<section>
<title>File formats</title>
<para>Rendering of the current page is the last step performed by the engine
(if at all necessary) before delivering its contents to the end user.
Indeed, some module calls may return data of any nature, like resources
(such as file or stream descriptors). The <classname>Engine</classname>
instance is then responsible for their proper handling.</para>
<para>However, in the case of pages (or page elements) the contents may be
formatted, transformed or otherwise converted in differing formats, as
available through the <classname>Engine</classname> instance. This instance
may use &package;'s own set of classes and routines to perform this, as
provided by the <classname>Format</classname> and
<classname>FormatElements</classname> classes in
<filename>system/format.php</filename>, and then implemented in the
<filename>formats</filename> directory.</para>
<para>As usual within &package;, the choice of formatting backends and their
respective preferences can be influenced through the
<filename>daportal.conf</filename> configuration file, through the
<varname>[format]</varname> set of sections. The difference here, is that
backends can be specified per MIME type, as set through the
<classname>Engine</classname> instance. This is illustrated here:</para>
<programlisting><![CDATA[[format]
#this defines the default backend if nothing else matched
#backend=html
#backend=html5
backend=plain
[format::text/html]
#this defines the default backend for content explicitly set to be HTML
#backend=html
backend=html5
[format::html]
#preferences for the html backend
favicon=/favicon.png
]]></programlisting>
<para>Again, the final decision on which rendering backend to choose can be
enforced by the <classname>Engine</classname> instance.</para>
</section>
</section>
</article>
<!-- vim: set noet ts=1 sw=1 sts=1 tw=80: -->