DaPortal internals

Pierre Pronchery

Code and documentation. 

This guide was written for the DeforaOS project (and may be used by others).

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.


Table of Contents

1. Introduction notes
1.1. Where to find DaPortal
1.2. Relevant documentation
2. Entry point
3. Engine selection
4. Processing requests
4.1. About requests
4.2. Database
4.3. Authentication
4.4. Idempotence
5. Within modules
5.1. About modules
5.2. Calls
5.3. Helpers
5.4. Internal requests
6. Output and rendering
6.1. Building pages
6.2. Applying templates
6.3. File formats

Warning: work in progress

These notes are based on a development version of DaPortal 2.

1. Introduction notes

1.1. Where to find DaPortal

First, make sure that you have downloaded the latest stable version of DaPortal 2. It can be found there: http://www.defora.org/os/project/download/12/DaPortal. This guide will assume the resulting archive to be called DaPortal.tar.gz.

Alternatively, you may choose to track the development of DaPortal 2, or any given branch from the Source Code Management system. Some instructions to do so can be found here: http://www.defora.org/os/project/download/12/DaPortal.

1.2. Relevant documentation

In this document, the reader is expected to be familiar with the installation of DaPortal. This process is documented in Installing DaPortal, which contains important information about the organization of files and directories relevant to DaPortal.

2. Entry point

DaPortal has a single entry-point for all of its applications, found in src/daportal.php. Its contents are simple enough to be reproduced here fully:

<?php
require_once('./system/config.php');
global $config;
$config = new Config();
$config->load('../daportal.conf');

require_once('./system/engine.php');
if(($engine = Engine::attachDefault()) !== FALSE
		&& ($request = $engine->getRequest()) !== FALSE)
{
	$page = $engine->process($request);
	$engine->render($page);
}
unset($engine);

?>

First, the code reads configuration data from the ../daportal.conf 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 DaPortal.

Next, the engine.php file is loaded from the system sub-directory. This directory contains generic, library code that can freely be used by the different modules. After including this file:

  1. The relevant implementation of the Engine class is loaded,

  2. The user's request is determined and obtained,

  3. The output (or page) is rendered and delivered to the user.

Finally, the engine loaded is explicitely free'd; this was found to be necessary in some cases (like with the GtkEngine) as otherwise, some functionalities of PHP no longer work as expected while executing the engine's destructor routines.

In the rest of this documentation, source files will be mentioned relatively to the src directory.

3. Engine selection

The actual processing engine for DaPortal is determined at run-time, or possibly enforced in the daportal.conf configuration file (through the backend variable in the [engine] section).

When auto-detecting the engine, every PHP file found in the engines directory is loaded in sequence. The file is expected to provide a predictable class name, such as FileNameEngine for filename.php. It should implement the abstract Engine class.

Two methods are used when detecting the adequate engine:

  • match(): returns an integer value, representing a score at the relevance of the engine in the context detected (typically between 0 and 100)

  • attach(): returns a boolean value, TRUE in case of success or FALSE otherwise.

This auto-detection methodology is found in multiple sub-systems of DaPortal (file formats, templates...).

4. Processing requests

4.1. About requests

Requests represent messages, typically sent from the end user and directed at a given part of the DaPortal installation. Requests can be created directly within the code, or obtained from user input (typically interpreted by the Engine instance).

The following parameters define a request:

  • module: the module to be selected to handle this request (required)

  • action: the action to be triggered within the given module (optional)

  • ID and title: both can be supplied independently, and their actual signification is up to the module (and action) selected;

  • optionally, an arbitrary number of additional parameters, defined through a name and value.

Requests implement the Request class defined in system/request.php. They are always processed through a specific Engine instance.

4.2. Database

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 getDatabase() method of the Engine instance.

Much like the main engine, the backend is selected as configured in the [database] section of the daportal.conf configuration file (or can be automatically detected in some cases). The different classes implementing the Database abstract class (described in system/database.php) are loaded from individual files, expected in the database directory.

A limited implementation of the Database class, called DatabaseDummy, is returned by the engine when it was unable to attach a functional Database instance. It will simply fail to obtain any data.

4.3. Authentication

User authentication within DaPortal is also delegated to a distinct backend, as its proper operation typically depends on the actual Engine instance selected. These backends are expected in the auth sub-directory, and should implement the Auth class from system/auth.php. Auto-detection of the authentication backend to be loaded can be circumvented through the [auth] section of the daportal.conf configuration file.

The credentials with DaPortal include the following information, much like found on a typical UNIX system:

  • a user ID: either the default value, 0 (meaning anonymous, least privileges) or a positive integer (actual users, with equal privileges by default)

  • a username, which must be unique through the system;

  • a group ID: defaulting to 0 (meaning nogroup), they can be used by modules to assign differing privileges, or otherwise distinguishing groups of users;

  • a group name: the name of the default group of the user;

  • an additional list of groups that the user may be part of;

  • an administrative flag: it grants complete privileges to the current user, with the actual signification depending on the current module.

4.4. Idempotence

The concept of idempotence is also explicitly implemented (and hopefully enforced) throughout the code. Its actual meaning in the context of DaPortal 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 safe in this regard or not.

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.

5. Within modules

5.1. About modules

Modules are the most essential part of the DaPortal engine: they implement the visible functionality to the end user, and provide the operation logic. Each module has a distinct folder inside the modules directory, where it should at least provide a module.php file; this file is automatically loaded by the Engine instance when required. It should implement the Module class, as defined in system/module.php.

A few modules are provided by a stock installation of DaPortal, among which:

  • user: assists with user registration, authentication, password resets and profile management;

  • admin: provides a user interface dedicated to administration duties;

  • article, news, wiki...: extend the abstract ContentModule in modules/content/module.php, declining some content-management functionality in differing contexts;

  • search: provides a generic way to search through the site's contents.

In turn, these modules can be extended and altered individually.

5.2. Calls

The modules react to requests through calls, more precisely the call() public method of the Module class. The relevant Engine instance along with the request are passed to the module, which is then responsible for handling the request, given the resources provided by the Engine.

These calls are expected to offer a significant value in return of an operation; this is however not mandatory. Content is typically formatted and delivered back to the user by building pages, as described below in Section 6.1, “Building pages”.

5.3. Helpers

Common functionality can be provided on demand to the modules, typically through the inclusion of specific files in the system directory. Some helpers are provided by a stock installation of DaPortal, among which:

  • content: provides some functionality around the handling of content (creation, modification, deletion...)

  • locale: assists in the translation of the user interface (typically using the gettext PHP extension)

  • mail: helps sending e-mail;

  • user: to lookup and manage user information.

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.

5.4. Internal requests

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.

6. Output and rendering

6.1. Building pages

The content generated through processing the requests is expected to be composed of a series of chained elements, called PageElement, each of which can also embed properties or other instances of PageElement. For clarity, an additional class, simply called Page, is also available and meant to represent a top-level element.

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.

The PageElement and Page classes are both defined in the system/page.php file.

6.2. Applying templates

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 templates directory, and should implement the Template class, as defined in system/template.php.

The template to choose can be determined by the engine at run-time, or can also be enforced within the daportal.conf configuration file, through the template section.

6.3. File formats

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 Engine instance is then responsible for their proper handling.

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 Engine instance. This instance may use DaPortal's own set of classes and routines to perform this, as provided by the Format and FormatElements classes in system/format.php, and then implemented in the formats directory.

As usual within DaPortal, the choice of formatting backends and their respective preferences can be influenced through the daportal.conf configuration file, through the [format] set of sections. The difference here, is that backends can be specified per MIME type, as set through the Engine instance. This is illustrated here:

[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

Again, the final decision on which rendering backend to choose can be enforced by the Engine instance.