]>
&package; internals @DATE@ &package; &firstname;&surname; Code and documentation.
&email;
2012 2013 2014 2015 &firstname; &surname; <&email;> 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. The DeforaOS Project
Warning: work in progress These notes are based on a development version of &package; 2.
Introduction notes
Where to find &package; First, make sure that you have downloaded the latest stable version of &package; 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 &package; 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.
Relevant documentation In this document, the reader is expected to be familiar with the installation of &package;. This process is documented in Installing DaPortal, which contains important information about the organization of files and directories relevant to &package;.
Entry point &package; has a single entry-point for all of its applications, found in src/daportal.php. Its contents are simple enough to be reproduced here: load('../daportal.conf'); if(($engine = Engine::attachDefault()) !== FALSE) { $request = $engine->getRequest(); $response = $engine->process($request); $engine->render($response); } 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 &package;. 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: The relevant implementation of the Engine class is loaded, The user's request is determined and obtained, 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.
Engine selection The actual processing engine for &package; 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 &package; (file formats, templates...).
Processing requests
About requests 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 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.
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.
Authentication User authentication within &package; 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 &package; 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.
Idempotence 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 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.
Within modules
About modules 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 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 &package;, 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.
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 this method, which is then responsible for handling the request, given the resources provided by the Engine. Calls are divided into two categories: internal and public. Public calls are issued by users directly, and required to return instances of the Response object. Content is typically formatted and delivered back to the user by building pages through PageResponse objects, as described below in . 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).
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 &package;, 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.
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.
Output and rendering
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.
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.
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 &package;'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 &package;, 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: Again, the final decision on which rendering backend to choose can be enforced by the Engine instance.