diff --git a/do.php b/do.php
index 62c7395..93e927a 100644
--- a/do.php
+++ b/do.php
@@ -1,473 +1,484 @@
 <?php
 
 /**
  * AJAX callbacks
  *
  * Zed. The immensity of stars. The HyperShip. The people.
  *
  * (c) 2010, Dereckson, some rights reserved.
  * Released under BSD license.
  *
  * As main controller could potentially be interrupted (e.g. if site.requests
  * flag is at 1, user is redirected to controllers/userrequest.php), all AJAX
  * queries should be handled by this script and not directly by the controllers.
  *
  * Standard return values:
  *  -7  user is logged but perso isn't selected,
  *  -9  user is not logged.
  *
  * @package     Zed
  * @subpackage  EntryPoints
  * @author      Sébastien Santoro aka Dereckson <dereckson@espace-win.org>
  * @copyright   2010 Sébastien Santoro aka Dereckson
  * @license     http://www.opensource.org/licenses/bsd-license.php BSD
  * @version     0.1
  * @link        http://scherzo.dereckson.be/doc/zed
  * @link        http://zed.dereckson.be/
  * @filesource
  */
 
 use Hypership\Geo\Point3D;
+use Keruald\Database\DatabaseEngine;
 use Zed\Engines\Database\Database;
 use Zed\Engines\Templates\Smarty\Engine as SmartyEngine;
 use Zed\Models\Geo\Location;
 use Zed\Models\Objects\Content;
 use Zed\Models\Objects\Perso;
 use Zed\Models\Objects\User;
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
 /// Constants
 ///
 
 //We define one negative number constant by standard erroneous return value.
 
 /**
  * Magic number which indicates the user is not logged in.
  */
 define('USER_NOT_LOGGED', -9);
 
 /**
  * Magic number which indicates the user is logged in, but haven't selected its perso.
  */
 define('PERSO_NOT_SELECTED', -7);
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
 /// Initialization
 ///
 
 include('includes/core.php');
 
 //Database
 $db = Database::load($Config['database']);
 
 //Session
 $IP = $_SERVER["REMOTE_ADDR"];
 require_once('includes/story/story.php'); //this class can be stored in session
 session_start();
 $_SESSION['ID'] = session_id();
 session_update(); //updates or creates the session
 
 //Gets current perso
 $CurrentUser = get_logged_user(); //Gets current user infos
 if ($perso_id = $CurrentUser->session['perso_id']) {
     $CurrentPerso = new Perso($db, $perso_id);
 }
 
 //Requires user and perso
 if ($CurrentUser->id < 1000) {
     echo USER_NOT_LOGGED;
     exit;
 }
 if (!$CurrentPerso) {
     echo PERSO_NOT_SELECTED;
     exit;
 }
 
 //Loads Smarty (as it handles l10n, it will be used by lang_get)
 $smarty = SmartyEngine::load()->getSmarty();
 
 //Loads language files
 initialize_lang();
 lang_load('core.conf');
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
 /// Actions definitions
 ///
 
 /**
  * Actions class
  *
  * Each method is called by first part of your URL, other parts are arguments
  * e.g. /do.php/validate_quux_request/52 = Actions::validate_quux_request(52);
  *
  * You can also use $_GET, $_POST or better $_REQUEST.
  *
  * Don't print the value but return it, so we can in the future implement custom
  * formats like api_output();
  */
 class Actions {
+
+    ///
+    /// Properties
+    ///
+
+    public Perso $CurrentPerso;
+
+    public User $CurrentUser;
+
+    ///
+    /// Available actions
+    ///
+    public DatabaseEngine $db;
+
     /**
      * Checks the arguments hash and determines whether it is valid.
      *
      * @param Array $args the arguments, the last being the hash
      * @return boolean true if the hash is valid ; otherwise, false.
      */
     static private function is_hash_valid ($args) {
         global $Config;
         return array_pop($args) == md5($_SESSION['ID'] . $Config['SecretKey'] . implode('', $args));
     }
 
     /**
      * Handles a allow/deny perso request.
      *
      * @param string $request_flag the request flag to clear
      * @param string $store 'perso' or 'registry'
      * @param string $key the perso flag or registry key
      * @param string $value the value to store
      * @param string $hash the security hash
      * @return boolean true if the request is valid and have been processed ; otherwise, false.
      */
-    static function perso_request ($request_flag, $store, $key, $value, $hash) {
-        global $CurrentPerso;
-
+    public function perso_request ($request_flag, $store, $key, $value, $hash) : bool {
         //Ensures we've the correct amount of arguments
         if (func_num_args() < 4) {
             return false;
         }
 
         //Checks hash
         $args = func_get_args();
         if (!self::is_hash_valid($args)) {
             return false;
         }
 
         //Sets flag
         switch ($store) {
             case 'perso':
-                $CurrentPerso->set_flag($key, $value);
+                $this->CurrentPerso->set_flag($key, $value);
                 break;
 
             case 'registry':
-                registry_set($key, $value);
+                registry_set($this->db, $key, $value);
                 break;
 
             default:
                 //Unknown storage location
                 return false;
         }
 
         //Clears request flag
         if ((string)$request_flag !== "0") {
-            $CurrentPerso->delete_flag($request_flag);
+            $this->CurrentPerso->delete_flag($request_flag);
         }
 
         return true;
     }
 
     /**
      * Sets current perso's local location.
      *
      * We don't require a security hash. If the users want to play with it, no problem.
      * You generally move inside a global location as you wish.
      * So, if you write a story capturing a perso, use flags to handle this escape!
      *
      * @param string $location_local the local location
      * @return Location the current perso's Location object
      */
-    static function set_local_location ($location_local) {
-        global $CurrentPerso;
-
+    public function set_local_location ($location_local) {
         //Ensures we've the correct amount of arguments
         if (func_num_args() < 1) {
             return null;
         }
 
         //Moves current perso to specified location
         $location_local = urldecode($location_local);
-        $CurrentPerso->move_to(null, $location_local);
+        $this->CurrentPerso->move_to(null, $location_local);
 
         //Returns Location relevant instance
-        return $CurrentPerso->location;
+        return $this->CurrentPerso->location;
     }
 
     /**
      * Moves the current perso's, setting a new local location.
      *
      * We don't require a security hash. If the users want to play with it, no problem.
      * You generally move inside a global location as you wish.
      * So, if you write a story capturing a perso, use flags to handle this escape!
      *
      * @param string $move the move (coordinates or direction)
      * @param int $factor a number multiplying the specified move [optional]
      * @return Location the current perso's Location object
      *
      * e.g. to move from 2 units to east, you can use one of those instructions:
      *  local_move('east', 2);
      *  local_move('2,0,0');
      *  local_move('1,0,0', 2);
      *
      * Valid moves string are north, east, south, west, up and down.
      * Valid moves coordinates are x,y,z (3 integers, comma as separator)
      */
-    static function local_move ($move, $factor = 1) {
-        global $CurrentPerso;
-
+    public function local_move ($move, $factor = 1) {
         //Ensures we've the correct amount of arguments
         if (func_num_args() < 1) {
             return null;
         }
 
         //Parses $move
         switch ($move) {
             case 'north':
                 $move = [0, 1, 0];
                 break;
 
             case 'east':
                 $move = [1, 0, 0];
                 break;
 
             case 'south':
                 $move = [0, -1, 0];
                 break;
 
             case 'west':
                 $move = [-1, 0, 0];
                 break;
 
             case 'up':
                 $move = [0, 0, 1];
                 break;
 
             case 'down':
                 $move = [0, 0, -1];
                 break;
 
             default:
                 $move = explode(',', $move, 3);
                 foreach ($move as $coordinate) {
                     if (!is_numeric($coordinate)) {
                         return null;
                     }
                 }
         }
 
         //Moves current perso to specified location
-        if ($location_local = Point3D::fromString($CurrentPerso->location->local)) {
+        if ($location_local = Point3D::fromString($this->CurrentPerso->location->local)) {
             $location_local->translate(
                 (float)$move[0] * $factor,
                 (float)$move[1] * $factor,
                 (float)$move[2] * $factor
             );
-            $CurrentPerso->move_to(null, $location_local->sprintf("(%d, %d, %d)"));
+            $this->CurrentPerso->move_to(null, $location_local->sprintf("(%d, %d, %d)"));
 
             //Returns Location relevant instance
-            return $CurrentPerso->location;
+            return $this->CurrentPerso->location;
         }
 
         //Old local location weren't a Point3D
         return null;
     }
 
     /**
      * Moves the current perso's, setting a new local location, using polar+z coordinates.
      * Polar+z coordinates are polar coordinates, plus a cartesian z dimension.
      *
      * We don't require a security hash. If the users want to play with it, no problem.
      * You generally move inside a global location as you wish.
      * So, if you write a story capturing a perso, use flags to handle this escape!
      *
      * @param string $move the move (coordinates or direction)
      * @param int $factor a number multiplying the specified move [optional]
      * @return Location the current perso's Location object
      *
      * Valid moves string are cw, ccw, out, in, up and down.
      *  r: out = +12   in  = -12
      *  °: cw  = +20°  ccw = -20
      * Valid moves coordinates are r,°,z (3 integers, comma as separator)
      *                                   (the medium value can also be integer + °)
      *
      * e.g. to move of two units (the unit is 20°) clockwise:
      *  polarz_local_move('cw', 2);
      *  polarz_local_move('(0, 20°, 0)', 2);
      *  polarz_local_move('(0, 40°, 0)');
      * Or if you really want to use radians (PI/9 won't be parsed):
      *  polarz_local_move('(0, 0.6981317007977318, 0)';
      *
      */
-    static function polarz_local_move ($move, $factor = 1) {
-        global $CurrentPerso;
-
+    public function polarz_local_move ($move, $factor = 1) {
         //Ensures we've the correct amount of arguments
         if (func_num_args() < 1) {
             return null;
         }
 
         //Parses $move
         $move = urldecode($move);
         switch ($move) {
             case 'cw':
                 $move = [0, '20°', 0];
                 break;
 
             case 'ccw':
                 $move = [0, '-20°', 0];
                 break;
 
             case 'in':
                 $move = [+12, 0, 0];
                 break;
 
             case 'out':
                 $move = [-12, 0, 0];
                 break;
 
             case 'up':
                 $move = [0, 0, 1];
                 break;
 
             case 'down':
                 $move = [0, 0, -1];
                 break;
 
             default:
                 $move = explode(',', $move, 3);
                 foreach ($move as $coordinate) {
                     if (!is_numeric($coordinate) && !preg_match("/^[0-9]+ *°$/", $coordinate)) {
                         return null;
                     }
                 }
         }
         dieprint_r($move);
 
         //Moves current perso to specified location
         throw new Exception("Move is not implemented.");
 
         //Old local location weren't a 3D point
         return null;
     }
 
     /**
      * Moves the current perso's, setting a new global and local location.
      *
      * @param string $location_global The global location
      * @param string $location_local The local location
      * @return Location the current perso's Location object
      */
-    static function global_move ($location_global, $location_local = null) {
+    public function global_move ($location_global, $location_local = null) {
         //Ensures we've the correct amount of arguments
         if (func_num_args() < 1) {
             return null;
         }
 
         //Checks hash
         $args = func_get_args();
         if (!self::is_hash_valid($args)) {
             return false;
         }
 
         //Moves
-        global $CurrentPerso;
-        $CurrentPerso->move_to($location_global, $location_local);
-        return $CurrentPerso->location;
+        $this->CurrentPerso->move_to($location_global, $location_local);
+        return $this->CurrentPerso->location;
     }
 
     /**
      * Handles upload content form.
      *
      * @return string new content path
      */
-    static function upload_content () {
-        global $CurrentPerso, $CurrentUser;
-
+    public function upload_content () {
         //Initializes a new content instance
         $content = new Content($db);
         $content->load_from_form();
-        $content->user_id = $CurrentUser->id;
-        $content->perso_id = $CurrentPerso->id;
-        $content->location_global = $CurrentPerso->location_global;
+        $content->user_id = $this->CurrentUser->id;
+        $content->perso_id = $this->CurrentPerso->id;
+        $content->location_global = $this->CurrentPerso->location_global;
 
         //Saves file
         if ($content->handle_uploaded_file($_FILES['artwork'])) {
             $content->save_to_database();
             $content->generate_thumbnail();
             return true;
         }
 
         return false;
     }
 
     /**
      * Gets multimedia content for the specified location
      *
      * @param string $location_global The global location (local is to specified in ?location_local parameter)
      * @return Array an array of Content instances
      */
-    static function get_content ($location_global) {
+    public function get_content ($location_global) {
         //Ensures we've the correct amount of arguments
         if (func_num_args() < 1) {
             return null;
         }
 
         //Checks hash
         $args = func_get_args();
         if (!self::is_hash_valid($args)) {
             return false;
         }
 
         //Checks local location is specified somewhere (usually in $_GET)
         if (!array_key_exists('location_local', $_REQUEST)) {
             return false;
         }
 
         //Gets content
-        return Content::get_local_content($location_global, $_REQUEST['location_local']);
-        return Content::get_local_content($db, $location_global, $_REQUEST['location_local']);
+        return Content::get_local_content($this->db, $location_global, $_REQUEST['location_local']);
     }
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
 /// Handles request
 ///
 
 //Parses URL
 $Config['SiteURL'] = get_server_url() . $_SERVER["PHP_SELF"];
 $args = get_current_url_fragments();
 
 $method = array_shift($args);
 
+$actions = new Actions();
+$actions->db = $db;
+$actions->CurrentPerso = $CurrentPerso;
+$actions->CurrentUser = $CurrentUser;
+
 $debug = $_REQUEST['debug'] ?? $_SESSION['debug'] ?? false;
 if ($debug) {
     //Debug version
     //Most of E_STRICT errors are evaluated at the compile time thus such errors
     //are not reported
     ini_set('display_errors', 'stderr');
     error_reporting(-1);
-    if (method_exists('Actions', $method)) {
-        $result = call_user_func_array(['Actions', $method], $args);
+    if (method_exists($actions, $method)) {
+        $result = call_user_func_array([$actions, $method], $args);
+
+        header('Content-type: application/json; charset=utf-8');
         echo json_encode($result);
     } else {
         echo "<p>Method doesn't exist: $method</p>";
     }
 
     if (array_key_exists('redirectTo', $_REQUEST)) {
         //If user JS disabled, you can add ?redirectTo= followed by an URL
         echo "<p>Instead to print a callback value, redirects to <a href=\"$_REQUEST[redirectTo]\">$_REQUEST[redirectTo]</a></p>";
     }
 } else {
     //Prod version doesn't prints warning <== silence operator
-    if (method_exists('Actions', $method)) {
-        $result = @call_user_func_array(['Actions', $method], $args);
+    if (method_exists($actions, $method)) {
+        $result = @call_user_func_array([$actions, $method], $args);
 
         if (array_key_exists('redirectTo', $_REQUEST)) {
             //If user JS disabled, you can add ?redirectTo= followed by an URL
             header("location: " . $_REQUEST['redirectTo']);
         } else {
+            header('Content-type: application/json; charset=utf-8');
             echo json_encode($result);
         }
     }
 }
diff --git a/includes/SmartLine/SmartLine.php b/includes/SmartLine/SmartLine.php
index 17dff10..ae572cf 100644
--- a/includes/SmartLine/SmartLine.php
+++ b/includes/SmartLine/SmartLine.php
@@ -1,562 +1,565 @@
 <?php
 
 /**
  * SmartLine 0.1
  *
  * Zed. The immensity of stars. The HyperShip. The people.
  *
  * (c) 2010, Dereckson, some rights reserved.
  * Released under BSD license.
  *
  * 0.1    2007-07-28 01:36 [DcK]    Initial release
  *        2010-07-02 00:39 [Dck]    Documentation
  *
  * @package     Zed
  * @subpackage  SmartLine
  * @author      Sébastien Santoro aka Dereckson <dereckson@espace-win.org>
  * @copyright   2007 Sébastien Santoro aka Dereckson
  * @license     http://www.opensource.org/licenses/bsd-license.php BSD
  * @version     0.1
  * @link        http://scherzo.dereckson.be/doc/zed
  * @link        http://zed.dereckson.be/
  * @link        http://bitbucket.org/dereckson/smartline
  * @filesource
 
 ///////////////////////////////////////////////////////////////////////////////
 // SECTION I - INITIALIZATION
 ///////////////////////////////////////////////////////////////////////////////
 
 //Constants
 
 /**
  * The standard, regular output (like STDOUT on POSIX systems)
  */
 if (!defined('STDOUT')) {
     define('STDOUT', 1);
 }
 
 /**
  * The error output (like STDERR on POSIX systems)
  */
 if (!defined('STDERR')) {
     define('STDERR', -1);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 // SECTION Ibis - L10n
 ///////////////////////////////////////////////////////////////////////////////
 
 //Ensures $lang is a standard array
 if (empty($lang) || !is_array($lang)) {
     $lang = [];
 }
 
 $lang = array_merge($lang, [
     //Errors
     'InvalidCommand' => "Invalid command %s. Use <strong>showcommands</strong> to show all commands.",
     'RegisteredButNotExistingCommand' => "[CRITICAL ERROR] The command %s has correctly been registered but its method or class doesn't exist.",
     'NotYetHelpForThiscommand' => "This command hasn't been documented yet.",
 
     //Help
     'DefaultHelp' => "This SmartLine is a command line interface.
                       <br /><br /><strong>showcommands</strong> prints the list.
                       <br /><strong>help &lt;command&gt;</strong> prints help for this command.",
     'Help' => [
         'help' => "<strong>help &lt;command&gt;</strong> prints command help.",
         'showcommands' => 'show available commands'
     ]
 ]);
 
 ///////////////////////////////////////////////////////////////////////////////
 // SECTION II - HELPERS FUNCTIONS
 ///////////////////////////////////////////////////////////////////////////////
 
 /**
  * Error handler called during SmartLine command execution.
  *
  * Any error occurring during command execution will be set in STDERR.
  *
  * To get an array with all the errors:
  * <code>$errors = $yourSmartLine->gets_all(STDERR)</code>
  *
  * Or to prints all the error:
  * <code>$yourSmartLine->prints_all(STDERR)</code>
  *
  * Or to pops (gets and deletes) only the last error:
  * <code>$lastError = $yourSmartLine->gets(STDERR)</code>
  *
  * @link http://www.php.net/manual/en/function.set-error-handler.php set_error_handler, PHP manual
  * @link http://www.php.net/manual/en/errorfunc.examples.php Error handling examples, PHP manual
  *
  * @param int $level The PHP error level
  * @param string $error The error description
  * @param string $file The script where the error occurred
  * @param int $line The line where the error occurred
  */
 function SmartLineHandler($level, $error, $file, $line) {
     switch ($level) {
        case E_NOTICE:
         $type = 'Notice';
         break;
 
        case E_WARNING:
         $type = 'Warning';
         break;
 
        case E_ERROR:
         $type = 'Error';
         break;
 
        default:
         $type = "#$level";
     }
     $_SESSION['SmartLineOutput'][STDERR][] = "[PHP $type] $error in $file line $line.";
     return true;
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 // SECTION III - BASE CLASSES
 ///////////////////////////////////////////////////////////////////////////////
 
 //SmartLineCommand is a class implementing a SmartLine command.
 //If you want to create a more complex command, extends this class.
 
 /**
  * The SmartLine command base class.
  *
  * To add a command, create an instance of the class, like:
  * <code>
  * class HelloWorldSmartLineCommand extends SmartLineCommand {
  *   public function run ($argv, $argc) {
  *       $this->SmartLine->puts('Hello World!');
  *    }
  * }
  * </code>
  *
  * Then, registers your command:
  * <code>
  * $yourSmartLine->register_object('hello', 'HelloWorldSmartLineCommand');
  * </code>
  *
  * @see SmartLine::register_object
  */
 class SmartLineCommand {
     /**
      * Initializes a new instance of the SmartLine Command
      *
      * @param SmartLine $SmartLine the SmartLine the command belongs
      */
     public function __construct ($SmartLine) {
         $this->SmartLine = $SmartLine;
     }
 
     /**
      * Gets the command help text or indicates help should be fetched from $lang array
      *
      * @return string|bool a string containing the command help or the bool value false, to enable the default behavior (ie prints $lang['help']['nameOfTheCommand'])
      */
     public function help () {
         return false;
     }
 
     /**
      * Runs the command
      *
      * @param array $argv an array of string, each item a command argument
      * @param int $argc the number of arguments
      */
     public function run ($argv, $argc) {
 
     }
 
     static protected function parseBoolean (string $arg) : bool {
         return match (strtolower($arg)) {
             "on", "enable", "enabled", "1", "up", "true" => true,
             "off", "disable", "disabled", "0", "down", "false" => false,
 
             default => throw new InvalidArgumentException(
                 "Boolean string expected, got '$arg' instead."
             ),
         };
     }
 
     /**
      * The SmartLine where this instance of the command is registered
      *
      * @var SmartLine
      */
     public $SmartLine;
 }
 
 /**
  * This class represents a SmartLine instance
  *
  * If you use only register_object, you can use it directly.
  * If you use register_method, extends this class in your SmartLine.
  */
 class SmartLine {
     /**
      * Initializes a new instance of the SmartLine object.
      */
     public function __construct () {
         //Assumes we've an empty array where store registered commands.
         $this->commands = [];
 
         //Let's register standard commands
         $this->register_object('showcommands', 'ShowCommandsSmartLineCommand');
         $this->register_object('help', 'HelpSmartLineCommand');
     }
 
     /**
      * Registers a private method as command.
      *
      * @param string $command The name of the command to register
      * @param string $method The method to register [OPTIONAL]. If omitted, the method registered will be the method having the same name as the command.
      * @param bool $useArgvArgc If true, indicates the method uses $argv, $argc as parameters. If false, indicates the method uses its parameters (default behavior). [OPTIONAL]
      *
      * @return bool true if the command have successfully been registered ; otherwise, false.
      */
     public function register_method ($command, $method = null, $useArgvArgc = false) {
         if (is_null($function)) {
             $method = $command;
         }
 
         if (!method_exists($this, $method)) {
             $this->lastError = "Registration failed. Unknown method $method";
             return false;
         }
 
         $className = ucfirst($method) . 'SmartLineCommand';
         //If class exists, add a uniqid after function
         while (class_exists($method)) {
             $className = uniqid(ucfirst($method)) . 'SmartLineCommand';
         }
         //Creates the class
         if ($useArgvArgc) {
             $call = "$this->SmartLine->$method(\$argv, \$argc);";
         } else {
             //We don't know how many args we've, so we use call_user_func_array
             $call = "array_shift(\$argv);
 		             call_user_func_array(
 		                array(&\$this->SmartLine, '$method'),
 		                \$argv
 		             );";
         }
         $code = "class $className extends SmartLineCommand {
     public function run (\$argv, \$argc) {
         $call
     }
 }";
         eval($code);
         $this->register_object($command, $className);
         return true;
     }
 
     /**
      * Registers an object extending SmartLineCommand as command.
      *
      * @param string $command The name of the command to register
      * @param SmartLineCommand|string $object The object extending SmartLineCommand. This can be the name of the class (string) or an instance already initialized of the object (SmartLineCommand).
      * @return bool true if the command have successfully been registered ; otherwise, false.
      */
     public function register_object ($command, $object) {
         if (is_object($object)) {
             //Sets SmartLine property
             $object->SmartLine = $this;
         } elseif (is_string($object) && class_exists($object)) {
             //Creates a new instance of $object
             $object = new $object($this);
         } else {
             $this->lastError = "Registration failed. register_object second parameter must be a class name (string) or an already initialized instance of such class (object) and not a " . gettype($object);
             return false;
         }
         if (!$this->caseSensitive) {
             $command = strtolower($command);
         }
         $this->commands[$command] = $object;
         return true;
     }
 
     /**
      * Determines whether the specified command have been registered.
      *
      * @param string $command The name of the command to check
      * @return true if the specified command have been registered ; otherwise, false.
      */
     public function isRegistered ($command) {
         if (!$this->caseSensitive) {
             $command = strtolower($command);
         }
         return array_key_exists($command, $this->commands);
     }
 
     /**
      * Executes the specified expression.
      *
      * If an error occurs during the command execution:
      *     the STDERR output will contain the errors,
      *     the value returned by this methods will be false.
      *
      * To execute the command and prints error:
      * <code>
      * $fooSmartLine = new SmartLine();
      * //...
      * $result = $fooSmartLine->execute($expression);
      * $fooSmartLine->prints_all();
      * if (!$result) {
      *     //Errors!
      *     echo "<h3>Errors</h3>";
      *     $fooSmartLine->prints_all(STDERR);
      * }
      * </code>
      *
      * @param string $expression The expression containing the command to execute
      * @return bool true if the command have been successfully executed ; otherwise, false.
      */
     public function execute (string $expression) : bool {
         //Does nothing if blank line
         if (!$expression) {
             return true;
         }
 
         //Prepares $argv and $argc
         $argv = $this->expression2argv($expression);
         $argc = count($argv);
 
         //Gets command
         $command = $this->caseSensitive ? $argv[0] : strtolower($argv[0]);
 
         //If command doesn't exist, throws an error
         if (!array_key_exists($command, $this->commands)) {
             global $lang;
             $this->puts(sprintf($lang['InvalidCommand'], $command), STDERR);
             return false;
         }
 
         //Executes command, intercepting error and returns result
         set_error_handler("SmartLineHandler");
         $result = true;
         try {
             $result = $this->commands[$command]->run($argv, $argc);
         } catch (Exception $ex) {
             $this->puts("<pre>$ex</pre>", STDERR);
             $result = false;
         }
         restore_error_handler();
         return $result;
     }
 
     /**
      * Adds a message to the specified output queue.
      *
      * @param string $message the message to queue
      * @param int $output The output queue (common values are STDERR and STDOUT constants). It's an optional parameter ; if omitted, the default value will be STDOUT.
      */
     public function puts ($message, $output = STDOUT) {
         //
         $_SESSION['SmartLineOutput'][$output][] = $message;
     }
 
     /**
      * Truncates the specified output queue.
      *
      * @param int $output The output queue (common values are STDERR and STDOUT constants). It's an optional parameter ; if omitted, the default value will be STDOUT.
      */
     public function truncate ($output = STDOUT) {
         unset($_SESSION['SmartLineOutput'][$output]);
     }
 
     /**
      * Pops (gets and clears) the first message from the specified output queue.
      *
      * @param int $output The output queue (common values are STDERR and STDOUT constants). It's an optional parameter ; if omitted, the default value will be STDOUT.
      * @return string the message
      */
     public function gets ($output = STDOUT) {
         if ($this->count($output) > 0) {
             return array_pop($_SESSION['SmartLineOutput'][$output]);
         }
 
         return "";
     }
 
     /**
      * Gets the number of messages in the specified output queue.
      *
      * @param int $output The output queue (common values are STDERR and STDOUT constants). It's an optional parameter ; if omitted, the default value will be STDOUT.
      */
     public function count ($output = STDOUT) {
         return isset($_SESSION['SmartLineOutput'][$output])
                ? count($_SESSION['SmartLineOutput'][$output])
                : 0;
     }
 
     /**
      * Gets all the message from the specified output queue.
      *
      * @param int $output The output queue (common values are STDERR and STDOUT constants). It's an optional parameter ; if omitted, the default value will be STDOUT.
      * @param string $prefix The string to prepend each message with. It's an optional parameter ; if omitted, '<p>'.
      * @param string $suffix The string to append each message with. It's an optional parameter ; if omitted, '</p>'.
      * @return string HTML representation of the messages from the specified output queue
      */
     public function gets_all ($output = STDOUT, $prefix = '<p>', $suffix = '</p>') : string {
         $count = $this->count($output);
         if ($count == 0) {
             return "";
         }
 
         $buffer = "";
         for ($i = 0 ; $i < $count ; $i++) {
             $buffer .= $prefix . $_SESSION['SmartLineOutput'][$output][$i] . $suffix;
         }
         unset ($_SESSION['SmartLineOutput'][$output]);
         return $buffer;
     }
 
     /**
      * Prints all the message from the specified output queue.
      *
      * @param int $output The output queue (common values are STDERR and STDOUT constants). It's an optional parameter ; if omitted, the default value will be STDOUT.
      * @param string $prefix The string to prepend each message with. It's an optional parameter ; if omitted, '<p>'.
      * @param string $suffix The string to append each message with. It's an optional parameter ; if omitted, '</p>'.
      */
     public function prints_all ($output = STDOUT, $prefix = '<p>', $suffix = '</p>') {
         $count = $this->count($output);
         if ($count == 0) {
             return;
         }
         for ($i = 0 ; $i < $count ; $i++) {
             echo $prefix, $_SESSION['SmartLineOutput'][$output][$i], $suffix;
         }
         unset ($_SESSION['SmartLineOutput'][$output]);
     }
 
     /**
      * Gets the command help
      *
      * @param string $command The command to get help from
      * @param string The command help
      */
     public function gethelp ($command) {
         return $this->commands[$command]->help();
     }
 
     /**
      * Gets an an argv array from the specified expression
      *
      * @param string $expression The expression to transform into a argv array
      * @return Array An array of string, the first item the command, the others those arguments.
      */
     private function expression2argv ($expression) {
         //Checks if expression contains "
         $pos1 = strpos($expression, '"');
 
         //We isolate "subexpression"
         if ($pos1 !== false) {
             $pos2 = $pos1;
             do {
                 $pos2 = strpos($expression, '"', $pos2 + 1);
             } while ($pos2 !== false && ($expression[$pos2 - 1] == "\\" && $expression[$pos2 - 2] != "\\"));
 
             if ($pos2 === false) {
                 //If final quote is missing, throws a warning and autoadds it.
                 $this->puts("[Warning] Final \" missing in $expression.", STDERR);
                 $argv = $this->expression2argv(substr($expression, 0, $pos1));
                 $argv[] = substr($expression, $pos1 + 1);
                 return $argv;
             }
             return array_merge(
                 $this->expression2argv(substr($expression, 0, $pos1)),
                 [substr($expression, $pos1 + 1, $pos2 - $pos1 - 1)],
                 $this->expression2argv(substr($expression, $pos2 + 1))
             );
         }
 
         //Standard expression (ie without ")
         $argv = [];
         $items = explode(' ', $expression);
         foreach ($items as $item) {
             $item = trim($item);
             if (!$item) {
                 //blank, we ignore
                 continue;
             }
             $argv[] = $item;
         }
         return $argv;
     }
 
     //The list of commands
     public array $commands = [];
 
+    //The services exposed to commands
+    public array $services = [];
+
     //Contains last error
     public $lastError = '';
 
     //If true, command isn't equal to Command
     public $caseSensitive = true;
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 // SECTION IV - STANDARD COMMANDS
 ///////////////////////////////////////////////////////////////////////////////
 
 /*
  * These commands are available in all default smartlines instance
  */
 
 /**
  * The standard command "showcommands"
  *
  * This command returns a list, with all the available commands
  */
 class ShowCommandsSmartLineCommand extends SmartLineCommand {
     /**
      * Runs the command
      *
      * @param array $argv an array of string, each item a command argument
      * @param int $argc the number of arguments
      */
     public function run ($argv, $argc) {
         $commands = array_keys($this->SmartLine->commands);
         sort($commands);
         $this->SmartLine->puts(implode(' ', $commands));
     }
 }
 
 /**
  * The standard command "help"
  *
  * This command prints command help.
  *
  * Help could be defined
  *    in the command classes, as a return value from the help method ;
  *    in the $lang['Help'] array, at the command key (e.g. $lang['Help']['quux'] for the quux command).
  */
 class HelpSmartLineCommand extends SmartLineCommand {
     /**
      * Runs the command
      *
      * @param array $argv an array of string, each item a command argument
      * @param int $argc the number of arguments
      */
     public function run ($argv, $argc) {
         global $lang;
         if ($argc == 1) {
             $this->SmartLine->puts($lang['DefaultHelp']);
         } elseif (!$this->SmartLine->isRegistered($argv[1])) {
             $this->SmartLine->puts(sprintf($lang['InvalidCommand'], str_replace(' ', '&nbsp;', $argv[1])), STDERR);
         } else {
             $command = strtolower($argv[1]);
             if (!$help = $this->SmartLine->gethelp($command)) {
                 if (array_key_exists($command, $lang['Help'])) {
                     $help = $lang['Help'][$command];
                 } else {
                     $help = $lang['NotYetHelpForThiscommand'];
                 }
             }
             $this->SmartLine->puts($help);
         }
     }
 }
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/includes/SmartLine/ZedCommands.php b/includes/SmartLine/ZedCommands.php
index 05b5ed0..ed4fab7 100644
--- a/includes/SmartLine/ZedCommands.php
+++ b/includes/SmartLine/ZedCommands.php
@@ -1,540 +1,545 @@
 <?php
 
 /**
  * Zed SmartLine commands.
  *
  * Zed. The immensity of stars. The HyperShip. The people.
  *
  * (c) 2010, Dereckson, some rights reserved.
  * Released under BSD license.
  *
  * This is the SmartLine subcontroller.
  *
  * The SmartLine is a widget allowing to add some basic CLI capability.
  *
  * It executes any command given in GET or POST request (parameter C).
  *
  * This files also provides SmartLine history helper: a method log_C to log
  * a SmartLine command and some procedural code assigning a SmartLineHistory.
  *
  * This code is inspired from Viper, a corporate PHP intranet I wrote in 2004.
  * There, the SmartLine allowed to change color theme or to find quickly user,
  * account, order or server information in a CRM context.
  *
  * @package     Zed
  * @subpackage  SmartLine
  * @author      Sébastien Santoro aka Dereckson <dereckson@espace-win.org>
  * @copyright   2010 Sébastien Santoro aka Dereckson
  * @license     http://www.opensource.org/licenses/bsd-license.php BSD
  * @version     0.1
  * @link        http://scherzo.dereckson.be/doc/zed
  * @link        http://zed.dereckson.be/
  * @filesource
  *
  * @todo SettingsSmartLineCommand - understand why dojo floating pane isn't rendered if we est $controller instead to redirect
  */
 
 ///
 /// Register commands
 ///
 
 use Zed\Models\Geo\Location;
 use Zed\Models\Objects\Invite;
 
 $smartLine->register_object('debug',    DebugSmartLineCommand::class);
 $smartLine->register_object('goto',     'GotoSmartLineCommand');
 $smartLine->register_object('guid',     'GUIDSmartLineCommand');
 $smartLine->register_object('invite',   'InviteSmartLineCommand');
 $smartLine->register_object('invites',  'InviteSmartLineCommand');
 $smartLine->register_object('list',     'ListSmartLineCommand');
 $smartLine->register_object('requests', 'RequestsSmartLineCommand');
 $smartLine->register_object('settings', 'SettingsSmartLineCommand');
 $smartLine->register_object('unixtime', 'UnixTimeSmartLineCommand');
 $smartLine->register_object('version',  'VersionSmartLineCommand');
 $smartLine->register_object('whereami', 'WhereAmISmartLineCommand');
 $smartLine->register_object('whoami',   'WhoAmISmartLineCommand');
 
 ///
 /// Help (todo: move $lang array in lang folder)
 ///
 
 $lang['Help']['debug'] = "Enable or disable debugger";
 $lang['Help']['goto'] = "Go to a location";
 $lang['Help']['guid'] = "Generate a GUID";
 $lang['Help']['invite'] = "Generate an invite. To see the generated invites, invite list.";
 $lang['Help']['list'] = "Lists specified objects (bodies, locations, or places)";
 $lang['Help']['requests'] = "Checks if there are waiting requests";
 $lang['Help']['settings'] = 'Go to settings page';
 $lang['Help']['unixtime'] = "Prints current unixtime (seconds elapsed since 1970-01-01 00:00, UTC) or the specified unixtime date.";
 $lang['Help']['version'] = "Gets Zed's software version info (Mercurial repository version, node id and if you're on the dev or prod site)";
 $lang['Help']['whereami'] = "Where am I?";
 $lang['Help']['whoami'] = "Who am I?";
 
 /**
  * Debugger command
  */
 class DebugSmartLineCommand extends SmartLineCommand {
 
     /**
      * Runs the command
      *
      * @param array $argv an array of string, each item a command argument
      * @param int $argc the number of arguments
      */
     public function run ($argv, $argc) {
         if ($argc > 1) {
             $_SESSION['debug'] = self::parseBoolean($argv[1]);
         }
 
         $this->SmartLine->puts("Debugger " . $this->getStatus());
     }
 
     private function getStatus () : string {
         return $this->isEnabled() ? "enabled" : "disabled";
     }
 
     private function isEnabled () : bool {
         return $_SESSION['debug'] ?? false;
     }
 
 }
 
 /**
  * The goto command
  *
  * Moves to the current perso to the specified location.
  */
 class GotoSmartLineCommand extends SmartLineCommand {
     /**
      * Runs the command
      *
      * @param array $argv an array of string, each item a command argument
      * @param int $argc the number of arguments
      *
      * @todo allow .goto global local (e.g. .goto B0001001 T2C3)
      * @todo determine if we allow rewrite rules to bypass can_travel rules
      */
     public function run ($argv, $argc) {
-        global $CurrentPerso;
+        $db = $this->SmartLine->services["db"];
+        $CurrentPerso = $this->SmartLine->services["CurrentPerso"];
 
         if ($argc == 1) {
             $this->SmartLine->puts("Where do you want to go?", STDERR);
             return;
         }
 
         if ($argc > 2) {
             $ignored_string = implode(" ", array_slice($argv, 2));
             $this->SmartLine->puts("Warning: ignoring $ignored_string", STDERR);
         }
 
         $here = new Location($db, $CurrentPerso->location_global, $CurrentPerso->location_local);
         $travel = Travel::load(); //maps content/travel.xml
 
         //Parses the expression, by order of priority, as :
         //  - a rewrite rule
         //  - a new global location
         //  - a new local location (inside the current global location)
         if (!$travel->try_parse_rewrite_rule($argv[1], $here, $place)) {
             try {
                 $place = new Location($db, $argv[1]);
 
                 if ($place->equals($CurrentPerso->location_global)) {
                     $this->SmartLine->puts("You're already there.");
                     return;
                 }
             } catch (Exception $ex) {
                 //Global location failed, trying local location
                 try {
                     $place = new Location($db, $CurrentPerso->location_global, $argv[1]);
                 } catch (Exception $ex) {
                     $this->SmartLine->puts($ex->getMessage(), STDERR);
                     return;
                 }
 
                 if ($place->equals($here)) {
                     $this->SmartLine->puts("You're already there.");
                     return;
                 }
             }
         }
 
         //Could we really go there?
         if (!$travel->can_travel($here, $place)) {
             $this->SmartLine->puts("You can't reach that location.");
             return;
         }
 
         //Moves
         $CurrentPerso->move_to($place->global, $place->local);
         $this->SmartLine->puts("You travel to that location.");
         return;
     }
 }
 
 /**
  * The GUID command
  *
  * Prints a new GUID.
  *
  * guid 8 will print 8 guid
  */
 class GUIDSmartLineCommand extends SmartLineCommand {
     /**
      * Runs the command
      *
      * @param array $argv an array of string, each item a command argument
      * @param int $argc the number of arguments
      */
     public function run ($argv, $argc) {
         if ($argc > 1 && is_numeric($argv[1])) {
             for ($i = 0 ; $i < $argv[1] ; $i++) {
                  $this->SmartLine->puts(new_guid());
             }
             return;
         }
 
         $this->SmartLine->puts(new_guid());
     }
 }
 
 /**
  * The invite command
  *
  * Manages invites.
  *
  * invite [add]
  *     creates a new invite code
  *
  * invite del <invite code>
  *     deletes the specified invite
  *
  * invite list
  *     prints current invite codes
  */
 class InviteSmartLineCommand extends SmartLineCommand {
     /**
      * Runs the command
      *
      * @param array $argv an array of string, each item a command argument
      * @param int $argc the number of arguments
      */
     public function run ($argv, $argc) {
-        require_once('includes/objects/invite.php');
-        global $CurrentUser, $CurrentPerso;
+        $db = $this->SmartLine->services["db"];
+        $CurrentPerso =  $this->SmartLine->services["CurrentPerso"];
 
         $command = ($argc > 1) ? strtolower($argv[1]) : '';
         switch ($command) {
             case 'list':
                 $codes = Invite::get_invites_from($db, $CurrentPerso);
                 if (!count($codes)) {
                     $this->SmartLine->puts("No invite code.");
                 } else {
                     foreach ($codes as $code) {
                         $this->SmartLine->puts($code);
                     }
                 }
                 break;
 
             case 'add':
             case '':
                 $code = Invite::create($db, $CurrentPerso);
                 $url = get_server_url() . get_url('invite', $code);
                 $this->SmartLine->puts("New invite code created: $code<br />Invite URL: $url");
                 break;
 
             case 'del':
                 $code = $argv[2];
                 if (!preg_match("/^([A-Z]){3}([0-9]){3}$/i", $code)) {
                     $this->SmartLine->puts("Invalid code format. Use invite list to get all your invite codes.", STDERR);
                 } else {
                     $invite = new Invite($code);
                     if ($CurrentPerso->id == $invite->from_perso_id) {
                         $invite->delete();
                         $this->SmartLine->puts("Deleted");
                     } else {
                         $this->SmartLine->puts("Invalid code. Use invite list to get all your invite codes.", STDERR);
                     }
                 }
                 break;
 
             default:
                 $this->SmartLine->puts("Usage: invite [add|list|del <code>]", STDERR);
                 break;
         }
 
     }
 }
 
 /**
  * The list command
  *
  * Prints a list of bodies, locations or places.
  *
  * This can easily be extended to output any list from any table.
  */
 class ListSmartLineCommand extends SmartLineCommand {
     /**
      * Runs the command
      *
      * @param array $argv an array of string, each item a command argument
      * @param int $argc the number of arguments
      */
     public function run ($argv, $argc) {
         if ($argc == 1) {
             $this->SmartLine->puts("Available lists: bodies, locations, places");
             return;
         }
 
         switch ($objects = $argv[1]) {
             case 'bodies':
                 $list = $this->get_list(TABLE_BODIES, "CONCAT('B', body_code)", "body_name");
                 $this->SmartLine->puts($list);
                 break;
 
             case 'locations':
                 $list = $this->get_list(TABLE_LOCATIONS, "location_code", "location_name");
                 $this->SmartLine->puts($list);
                 break;
 
             case 'places':
                 if ($argv[2] == "-a" || $argv[2] == "--all") {
                     //Global bodies places list
                     $list = $this->get_list(TABLE_PLACES, "CONCAT('B', body_code, place_code)", "place_name");
                 } else {
                     //Local places (or equivalent) list
                     global $CurrentPerso;
                     switch ($CurrentPerso->location_global[0]) {
                         case 'B':
                             $body_code = substr($CurrentPerso->location_global, 1, 5);
                             $list = $this->get_list(TABLE_PLACES, "CONCAT('B', body_code, place_code)", "place_name", "body_code = $body_code");
                             break;
 
                         case 'S':
                             $this->SmartLine->puts("I don't have a map of the spaceship.", STDERR);
                             return;
 
                         default:
                             $this->SmartLine->puts("Unknown location type. Can only handle B or S.", STDERR);
                             return;
                     }
                 }
                 $this->SmartLine->puts($list);
                 break;
 
             default:
                 $this->SmartLine->puts("Unknown objects to list: $objects", STDERR);
         }
 
     }
 
     /**
      * Gets a custom list from the specified table and fields.
      *
      * The list will be sorted by the specified key, using ascending order.
      *
      * @param $table the table to query from the database
      * @param $key the first field to fetch, as key
      * @param $value the second field to fetch, as value
      * @param $where the WHERE clause, without the WHERE keyword (optional)
      */
     public function get_list ($table, $key, $value, $where = null) {
-        global $db;
+        $db = $this->SmartLine->services["db"];
+
         $sql = "SELECT $key as `key`, $value as value FROM $table ";
         if ($where) {
             $sql .= "WHERE $where ";
         }
         $sql .= "ORDER BY `key` ASC";
         if (!$result = $db->query($sql)) {
             message_die(SQL_ERROR, "Unable to fetch list", '', __LINE__, __FILE__, $sql);
         }
         while ($row = $db->fetchRow($result)) {
             $rows .= "<tr><td>$row[key]</td><td>$row[value]</td></tr>";
         }
         $this->SmartLine->truncate(STDERR); //kludge
         return "<table cellspacing=\"8\"><thead style=\"color: white\" scope=\"row\"><tr><th>Key</th><th>Value</th></thead><tbody>$rows</tbody></table>";
     }
 }
 
 
 /**
  * The requests command
  *
  * Redirects user to the requests page.
  *
  * By default only redirect if a flag indicates there's a new request.
  *
  * To forcefully goes to the request page, requests --force
  */
 class RequestsSmartLineCommand extends SmartLineCommand {
     /**
      * Runs the command
      *
      * @param array $argv an array of string, each item a command argument
      * @param int $argc the number of arguments
      */
     public function run ($argv, $argc) {
-        global $CurrentPerso;
+        $CurrentPerso = $this->SmartLine->services["CurrentPerso"];
+
         $force = ($argc > 1) && ($argv[1] == "-f" || $argv[1] == "--force");
         if ($force || (array_key_exists('site.requests', $CurrentPerso->flags) && $CurrentPerso->flags['site.requests'])) {
             global $controller;
             $controller = 'controllers/persorequest.php';
         } else {
             $this->SmartLine->puts("No request waiting.");
         }
     }
 }
 
 /**
  * The settings command
  *
  * Redirects user to the settings page.
  */
 class SettingsSmartLineCommand extends SmartLineCommand {
     /**
      * Runs the command
      *
      * @param array $argv an array of string, each item a command argument
      * @param int $argc the number of arguments
      */
     public function run ($argv, $argc) {
         if (headers_sent()) {
             global $controller;
             $controller = 'controllers/settings.php';
         } else {
             header('location: ' . get_url('settings'));
         }
     }
 }
 
 /**
  * The unixtime command
  *
  * Prints current unixtime (seconds elapsed since 1970-01-01 00:00, UTC)
  * or if an unixtime is specified as argument, the matching date.
  */
 class UnixTimeSmartLineCommand extends SmartLineCommand {
     /**
      * Runs the command
      *
      * @param array $argv an array of string, each item a command argument
      * @param int $argc the number of arguments
      */
     public function run ($argv, $argc) {
         date_default_timezone_set('UTC');
         if ($argc == 1) {
             $this->SmartLine->puts(time());
         } elseif ($argc == 2 && is_numeric($argv[1])) {
             $this->SmartLine->puts(strftime("%Y-%m-%d %X", $argv[1]));
             $this->SmartLine->puts(get_hypership_time($argv[1]));
         } else {
             array_shift($argv);
             $date = implode(' ', $argv);
             if ($time = strtotime($date) !== false) {
                 $this->SmartLine->puts("Unixtime from $date: <span class=\"highlight\">$time</span>");
             } else {
                 $this->SmartLine->puts("$date isn't a unixtime nor a valid date strtotime is able to parse.", STDERR);
             }
         }
     }
 }
 
 /**
  * The version command
  *
  * Prints current hg revision, if we're in prod or dev environment and
  * the current revision's hash.
  *
  * The version and env information is extracted from
  *      .hg/tags.cache (indicating we're in a Mercurial repo and so in a dev environment), or from
  *      version.txt file (indicating we've deployed code in a production environment)
  *
  * e.g. r130 (development environment)
  *      Hash: 057bf394741706fd2136541e3bb07c9e60b4963d
  */
 class VersionSmartLineCommand extends SmartLineCommand {
 
     const GIT_FOLDER = ".git";
 
     private static function getGitHash () : string {
         $head = trim(file_get_contents(self::GIT_FOLDER . "/HEAD"));
 
         if (str_starts_with($head, "ref: ")) {
              // Follows reference
              $ref = substr($head, 5);
              return file_get_contents(self::GIT_FOLDER . "/$ref");
         }
 
         return $head;
     }
 
     /**
      * Runs the command
      *
      * @param array $argv an array of string, each item a command argument
      * @param int $argc the number of arguments
      */
     public function run ($argv, $argc) {
         if (file_exists('.hg/tags.cache')) {
             //Gets .hg revision
             $content = file_get_contents('.hg/tags.cache');
             $info = explode(' ', $content, 2);
             $info[] = "development environment";
 
             $this->SmartLine->puts("r$info[0] ($info[2])");
             $this->SmartLine->puts("Hash: $info[1]");
         } elseif (file_exists('.git/HEAD')) {
             $hash = self::getGitHash();
             $this->SmartLine->puts("Hash: $hash");
         } elseif (file_exists('version.txt')) {
             $content = file('version.txt');
             foreach ($content as $line) {
                 $this->SmartLine->puts($line);
             }
         } else {
             $this->SmartLine->puts("No version information available.", STDERR);
             return false;
         }
 
         return true;
     }
 }
 
 /**
  * The whereami (Where am I?) command
  *
  * Prints current position, e.g. B00001001 - Tour, Hypership
  */
 class WhereAmISmartLineCommand extends SmartLineCommand {
     /**
      * Runs the command
      *
      * @param array $argv an array of string, each item a command argument
      * @param int $argc the number of arguments
      */
     public function run ($argv, $argc) {
-        global $CurrentPerso;
+        $db = $this->SmartLine->services["db"];
+        $CurrentPerso = $this->SmartLine->services["CurrentPerso"];
 
         $place = new Location($db, $CurrentPerso->location_global);
         $this->SmartLine->puts($CurrentPerso->location_global . ' - ' . $place);
     }
 }
 
 
 /**
  * The whoami (Who am I?) command
  *
  * Prints current position, e.g. B00001001 - Tour, Hypership
  */
 class WhoAmISmartLineCommand extends SmartLineCommand {
     /**
      * Runs the command
      *
      * @param array $argv an array of string, each item a command argument
      * @param int $argc the number of arguments
      */
     public function run ($argv, $argc) {
-        global $CurrentPerso;
+        $CurrentPerso = $this->SmartLine->services["CurrentPerso"];
+
         $reply = "<span id=\"whoami.nickname\">$CurrentPerso->nickname</span> (<span id=\"whoami.name\">$CurrentPerso->name</span>), <span id=\"whoami.race\">$CurrentPerso->race</span>";
         $this->SmartLine->puts($reply);
     }
 }
diff --git a/includes/SmartLine/ZedSmartLine.php b/includes/SmartLine/ZedSmartLine.php
index 616789c..a0107aa 100644
--- a/includes/SmartLine/ZedSmartLine.php
+++ b/includes/SmartLine/ZedSmartLine.php
@@ -1,112 +1,117 @@
 <?php
 
 /**
  * The Zed SmartLine subcontroller.
  *
  * Zed. The immensity of stars. The HyperShip. The people.
  *
  * (c) 2010, Dereckson, some rights reserved.
  * Released under BSD license.
  *
  * This is the SmartLine subcontroller.
  *
  * The SmartLine is a widget allowing to add some basic CLI capability.
  *
  * It executes any command given in GET or POST request (parameter C).
  *
  * This files also provides SmartLine history helper: a method log_C to log
  * a SmartLine command and some procedural code assigning a SmartLineHistory.
  *
  * This code is inspired from Viper, a corporate PHP intranet I wrote in 2004.
  * There, the SmartLine allowed to change color theme or to find quickly user,
  * account, order or server information in a CRM context.
  *
  * @package     Zed
  * @subpackage  SmartLine
  * @author      Sébastien Santoro aka Dereckson <dereckson@espace-win.org>
  * @copyright   2010 Sébastien Santoro aka Dereckson
  * @license     http://www.opensource.org/licenses/bsd-license.php BSD
  * @version     0.1
  * @link        http://scherzo.dereckson.be/doc/zed
  * @link        http://zed.dereckson.be/
  * @filesource
  *
  * @todo Caches SmartLine history
  */
 
 ///
 /// Helpers
 ///
 
 /**
  * Logs a Smartline command
  *
  * @param string $command the command to log
  * @param bool $isError indicates if the command is an error
  */
 function log_C ($command, $isError = false) {
     global $db, $CurrentPerso;
     $isError = $isError ? 1 : 0;
     $command = $db->escape($command);
     $sql = "INSERT INTO "  . TABLE_LOG_SMARTLINE . " (perso_id, command_time, command_text, isError)
             VALUES ($CurrentPerso->id, UNIX_TIMESTAMP(), '$command', $isError)";
     if (!$db->query($sql)) {
         message_die(SQL_ERROR, "Can't log SmartLine command", '', __LINE__, __FILE__, $sql);
     }
 }
 
 ///
 /// Executes command
 ///
 
 if (isset($_REQUEST['C'])) {
     $command = $_REQUEST['C'];
 
     //Initializes SmartLine object
     require_once("SmartLine.php");
     $smartLine = new SmartLine();
+    $smartLine->services = [
+        "db" => $db,
+        "CurrentUser" => $CurrentUser,
+        "CurrentPerso" => $CurrentPerso,
+    ];
 
     require_once("ZedCommands.php");
 
     //Executes SmartLine
     $controller = '';
     $smartLine->execute($command);
 
     $error = $smartLine->count(STDERR) > 0;
 
     if ($smartLine->count(STDOUT) > 0) {
         $smarty->assign("SmartLine_STDOUT", $smartLine->gets_all(STDOUT, '', '<br />'));
     }
 
     if ($error) {
         $smarty->assign("SmartLine_STDERR", $smartLine->gets_all(STDERR, '', '<br />'));
     }
 
     if ($controller != '') {
         include($controller);
     }
 
     log_C($command, $error);
 }
 
 ///
 /// Gets SmartLine history
 ///
 
 $perso_id = $db->escape($CurrentPerso->id);
 $sql = "SELECT command_time, command_text FROM log_smartline
         WHERE isError = 0 AND perso_id = '$perso_id'
         ORDER BY command_time DESC LIMIT 100";
 if (!$result = $db->query($sql)) {
     message_die(SQL_ERROR, "Can't get SmartLine history", '', __LINE__, __FILE__, $sql);
 }
 
 $commands = [];
 while ($row = $db->fetchRow($result)) {
     $commands[] = [
         "time" => get_hypership_time($row['command_time']),
         "text" =>  $row['command_text'],
     ];
 }
 
 $smarty->assign("SmartLineHistory", $commands);
diff --git a/includes/core.php b/includes/core.php
index 76d9d24..ea7ced5 100644
--- a/includes/core.php
+++ b/includes/core.php
@@ -1,654 +1,651 @@
 <?php
 
 /**
  * Core: helper methods and main libraries loader
  *
  * Zed. The immensity of stars. The HyperShip. The people.
  *
  * (c) 2010, Dereckson, some rights reserved.
  * Released under BSD license.
  *
  * @package     Zed
  * @subpackage  Keruald
  * @author      Sébastien Santoro aka Dereckson <dereckson@espace-win.org>
  * @copyright   2010 Sébastien Santoro aka Dereckson
  * @license     http://www.opensource.org/licenses/bsd-license.php BSD
  * @version     0.1
  * @link        http://scherzo.dereckson.be/doc/zed
  * @link        http://zed.dereckson.be/
  * @filesource
  */
 
+use Keruald\Database\DatabaseEngine;
 use Keruald\OmniTools\Collections\TraversableUtilities;
 
 ////////////////////////////////////////////////////////////////////////////////
 ///                                                                          ///
 /// Configures PHP and loads site-wide used libraries                        ///
 ///                                                                          ///
 ////////////////////////////////////////////////////////////////////////////////
 
 require_once(__DIR__ . "/../vendor/autoload.php");
 include_once("autoload.php");
 
 error_reporting(E_ALL & ~E_NOTICE);
 include_once("config.php");
 include_once("error.php");
 
 include_once("sessions.php");
 
 ////////////////////////////////////////////////////////////////////////////////
 ///                                                                          ///
 /// Information helper methods                                               ///
 ///                                                                          ///
 ////////////////////////////////////////////////////////////////////////////////
 
 /**
  * Gets the nickname from the specified perso ID
  *
  * @param integer $perso_id The specified perso ID
  * @return string The perso's nickname
  */
 function get_name ($perso_id) {
     global $db;
     $perso_id = $db->escape($perso_id);
     $sql = 'SELECT perso_nickname FROM '. TABLE_PERSOS . " WHERE perso_id = '$perso_id'";
     if (!$result = $db->query($sql)) {
         message_die(SQL_ERROR, "Can't query persos table.", '', __LINE__, __FILE__, $sql);
     }
     $row = $db->fetchRow($result);
     return $row['perso_nickname'];
 }
 
 /**
  * Gets the user ID from the specified username
  *
  * @param string $username The username
  * @return integer the user ID
  */
 function get_userid ($username) {
     global $db;
     $username = $db->escape($username);
     $sql = 'SELECT user_id FROM '. TABLE_USERS . " WHERE username LIKE '$username'";
     if (!$result = $db->query($sql)) {
         message_die(SQL_ERROR, "Can't query users table.", '', __LINE__, __FILE__, $sql);
     }
     $row = $db->fetchRow($result);
     return $row['user_id'];
 }
 
 /**
  * Gets an information from the application global registry
- *
- * @param string $key the registry's key
- * @return string The key value
  */
-function registry_get ($key) {
-    global $db;
+function registry_get (DatabaseEngine $db, string $key) : string {
     $key = $db->escape($key);
     $sql = "SELECT registry_value FROM " . TABLE_REGISTRY . " WHERE registry_key = '$key'";
     if (!$result = $db->query($sql)) {
         message_die(SQL_ERROR, "Can't read registry.", '', __LINE__, __FILE__, $sql);
     }
 
     $row = $db->fetchRow($result);
     return $row['registry_value'];
 }
 
 /**
  * Sets an information in the application global registry
  *
+ * @param DatabaseEngine $db
  * @param string $key the registry key
  * @param string $value the value to store at the specified registry key
  */
-function registry_set ($key, $value) {
-    global $db;
+function registry_set (DatabaseEngine $db, string $key, string $value) : void {
     $key = $db->escape($key);
     $value = $db->escape($value);
     $sql = "REPLACE INTO " . TABLE_REGISTRY . " (registry_key, registry_value) VALUES ('$key', '$value')";
     if (!$db->query($sql)) {
         message_die(SQL_ERROR, "Can't update registry", '', __LINE__, __FILE__, $sql);
     }
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///                                                                          ///
 /// Misc helper methods                                                      ///
 ///                                                                          ///
 ////////////////////////////////////////////////////////////////////////////////
 
 //Plural management
 
 /**
  * Returns "s" when the $amount request a plural
  * This function is a French plural helper.
  *
  * @return string 's' if $amount implies a plural ; '' if it implies a singular.
  */
 function s (int $amount) : string {
     if ($amount >= 2 || $amount <= -2) {
         return "s";
     }
 
     return "";
 }
 
 /**
  * Returns "x" when the $amount request a plural
  * This function is a French plural helper.
  *
  * @return string 'x' if $amount implies a plural ; '' if it implies a singular.
  */
 function x (int $amount) : string {
     if ($amount >= 2 || $amount <= -2) {
         return "x";
     }
 
     return "";
 }
 
 //Debug
 
 /**
  * Prints human-readable information about a variable.
  *
  * It behaves like the print_r command, but the output is enclosed in pre tags,
  * to have a preformatted HTML output.
  *
  * @param mixed $expression The expression to be printed
  */
 function dprint_r ($expression) {
     echo '<pre>';
     print_r($expression);
     echo '</pre>';
 }
 
 //GUID
 
 /**
  * Generates a GUID, or more precisely an UUID
  * @link http://en.wikipedia.org/wiki/Universally_Unique_Identifier Wikipedia, Universally Unique Identifier.
  *
  * A UUID is a 36 chars string of 32 hexadecimal and 4 dashes, with a
  * very high probability to be unique.
  *
  * @return string the UUID
  */
 function new_guid() {
     $characters = explode(",", "a,b,c,d,e,f,0,1,2,3,4,5,6,7,8,9");
     $guid = "";
     for ($i = 0 ; $i < 36 ; $i++) {
         if ($i == 8 || $i == 13 || $i == 18 || $i == 23) {
             $guid .= "-";
         } else {
             $guid .= $characters[mt_rand() % sizeof($characters)];
         }
     }
     return $guid;
 }
 
 /**
  * Determines if the expression is a valid UUID (a guid without {}).
  * @see new_guid
  *
  * @param string $expression the expression to check
  * @return boolean true if the specified expression is a valid UUID ; otherwise, false.
  */
 function is_guid ($expression) {
     //We avoid regexp to speed up the check
     //A guid is a 36 characters string
     if (strlen($expression) != 36) {
         return false;
     }
 
     $expression = strtolower($expression);
     for ($i = 0 ; $i < 36 ; $i++) {
         if ($i == 8 || $i == 13 || $i == 18 || $i == 23) {
             //with dashes
             if ($expression[$i] != "-") {
                 return false;
             }
         } else {
             //and numbers
             if (!is_numeric($expression[$i]) && $expression[$i] != 'a' && $expression[$i] != 'b' && $expression[$i] != 'c' && $expression[$i] != 'd' && $expression[$i] != 'e' && $expression[$i] != 'f' ) {
                 return false;
             }
         }
     }
     return true;
 }
 
 /**
  * Gets file extension
  *
  * @param string $file the file to get the extension
  * @return string the extension from the specified file
  */
 function get_extension ($file) {
     $dotPosition = strrpos($file, ".");
     return substr($file, $dotPosition + 1);
 }
 
 /**
  * Inserts a message into the supralog
  *
  * @param string $category the entry category
  * @param string $message the message to log
  * @param string|null $source the entry source.
  */
 function supralog (string $category, string $message, ?string $source = null) {
     global $db, $CurrentUser, $CurrentPerso;
     $category = $db->escape($category);
     $message = $db->escape($message);
     $source = $db->escape($source ?: $_SERVER['SERVER_ADDR']);
     $ip = $_SERVER['REMOTE_ADDR'];
     $sql = "INSERT INTO " . TABLE_LOG .
            " (entry_ip, user_id, perso_id, entry_category, entry_message, entry_source) VALUES
              ('$ip', $CurrentUser->id, $CurrentPerso->id, '$category', '$message', '$source')";
     if ( !($result = $db->query($sql)) ) {
         message_die(SQL_ERROR, "Can't log this entry.", '', __LINE__, __FILE__, $sql);
     }
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///                                                                          ///
 /// Localization (l10n)                                                      ///
 ///                                                                          ///
 ////////////////////////////////////////////////////////////////////////////////
 
 /**
  * Defines the LANG constant, to lang to print
  *
  * This information is contained in the session, or if not yet defined,
  * it's to determine according the user's browser preferences.
  * @see find_lang
  */
 function initialize_lang () : void {
     //If $_SESSION['lang'] doesn't exist yet, find a common language
     if (!array_key_exists('lang', $_SESSION)) {
         $_SESSION['lang'] = find_lang();
 
     }
 
     define('LANG', $_SESSION['lang']);
 }
 
 /**
  * Gets a common lang spoken by the site and the user's browser
  *
  * @param string|null $fallback The language when no common language is found
  * @return string The language to use
  * @see get_http_accept_languages
  */
 function find_lang (string $fallback = null) : string {
     if (!file_exists('lang') || !is_dir('lang')) {
         throw new RuntimeException(
             "A lang/ folder is expected with by language subdirectories"
         );
     }
 
     //Gets lang/ subdirectories: this is the list of available languages
     $handle = opendir('lang');
     $langs = [];
     while ($file = readdir($handle)) {
         if ($file != '.' && $file != '..' && is_dir("lang/$file")) {
             $langs[] = $file;
         }
     }
 
     if (count($langs) === 0) {
         throw new RuntimeException(
             "A lang/ folder is expected with by language subdirectories"
         );
     }
 
     //The array $langs contains now the language available.
     //Gets the langs the user should want:
     if (!$userlangs = get_http_accept_languages()) {
         return TraversableUtilities::first($langs);
     }
 
     //Gets the intersection between the both languages arrays
     //If it matches, returns first result
     $intersect = array_intersect($userlangs, $langs);
     if (count($intersect)) {
         return TraversableUtilities::first($intersect);
     }
 
     //Now it's okay with Opera and Firefox but Internet Explorer will
     //by default return en-US and not en or fr-BE and not fr, so second pass
     $userlangs_simplified = [];
     foreach ($userlangs as $userlang) {
         $lang = explode('-', $userlang);
         if (count($lang) > 1) {
             $userlangs_simplified[] = $lang[0];
         }
     }
     $intersect = array_intersect($userlangs_simplified, $langs);
     if (count($intersect)) {
         return TraversableUtilities::first($intersect);
     }
 
     // It's not possible to find a common language, return the first one.
     return $fallback ?? TraversableUtilities::first($langs);
 }
 
 /**
  * Gets the languages accepted by the browser, by order of priority.
  *
  * This will read the HTTP_ACCEPT_LANGUAGE variable sent by the browser in the
  * HTTP request.
  *
  * @return string[] list of the languages accepted by browser
  */
 function get_http_accept_languages () : array {
     //What language to print is sent by browser in HTTP_ACCEPT_LANGUAGE var.
     //This will be something like en,fr;q=0.8,fr-fr;q=0.5,en-us;q=0.3
 
     if (!array_key_exists('HTTP_ACCEPT_LANGUAGE', $_SERVER)) {
         return [];
     }
 
     $http_accept_language = explode(',', $_SERVER["HTTP_ACCEPT_LANGUAGE"]);
     foreach ($http_accept_language as $language) {
         $userlang = explode(';q=', $language);
         if (count($userlang) == 1) {
             $userlangs[] = [1, $language];
         } else {
             $userlangs[] = [$userlang[1], $userlang[0]];
         }
     }
     rsort($userlangs);
 
     $result = [];
     foreach ($userlangs as $userlang) {
         $result[] = $userlang[1];
     }
     return $result;
 }
 
 /**
  * Loads specified language Smarty configuration file
  *
  * @param string $file the file to load
  * @param mixed|null $sections array of section names, single section or null
  */
 function lang_load (string $file, mixed $sections = null) : void {
     global $smarty;
 
     //Loads English file as fallback if some parameters are missing
     if (file_exists("lang/en/$file")) {
         $smarty->configLoad("lang/en/$file", $sections);
     }
 
     //Loads wanted file (if it exists and a language have been defined)
     if (defined('LANG') && LANG != 'en' && file_exists('lang/' . LANG . '/' . $file)) {
         $smarty->configLoad('lang/' . LANG . '/' . $file, $sections);
     }
 }
 
 /**
  * Gets a specified language expression defined in configuration file
  *
  * @param string $key the configuration key matching the value to get
  * @return string The value in the configuration file
  */
 function lang_get (string $key) : string {
     global $smarty;
 
     $smartyConfValue = $smarty->config_vars[$key];
     return $smartyConfValue ?: "#$key#";
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///                                                                          ///
 /// Zed date and time helper methods                                         ///
 ///                                                                          ///
 ////////////////////////////////////////////////////////////////////////////////
 
 /**
  * Converts a YYYYMMDD or YYYY-MM-DD timestamp to unixtime
  * @link http://en.wikipedia.org/wiki/Unix_time Unix time
  *
  * @param string $timestamp the timestamp to convert
  * @return integer the unixtime
  */
 function to_unixtime ($timestamp) {
     switch (strlen($timestamp)) {
         case 8:
         //YYYYMMDD
         return mktime(0, 0, 0, substr($timestamp, 4, 2), substr($timestamp, 6, 2), substr($timestamp, 0, 4));
 
         case 10:
         //YYYY-MM-DD
         return mktime(0, 0, 0, substr($timestamp, 5, 2), substr($timestamp, 8, 2), substr($timestamp, 0, 4));
 
         default:
         throw new Exception("timestamp is not a valid YYYYMMDD or YYYY-MM-DD timestamp: $timestamp");
     }
 }
 
 /**
  * Converts a unixtime to the YYYYMMDD or YYYY-MM-DD timestamp format
  * @see to_unixtime
  *
  * @param int $unixtime the time to convert
  * @param int $format 8 or 10. If 8 (default), will output YYYYMMDD. If 10, YYYY-MM-DD.
  * @return string the timestamp
  */
 function to_timestamp ($unixtime = null, $format = 8) {
     //If no parameter is specified (or null, or false), current time is used
     //==== allows to_timestamp(0) to return correct 1970-1-1 value.
     if ($unixtime === null || $unixtime === false) {
         $unixtime = time();
     }
 
     switch ($format) {
         case 8:
         //YYYYMMDD
         return date('Ymd', $unixtime);
 
         case 10:
         //YYYY-MM-DD
         return date('Y-m-d', $unixtime);
 
         default:
         throw new Exception("format must be 8 (YYYYMMDD) or 10 (YYYY-MM-DD) and not $format.");
     }
 }
 
 /**
  * Converts a unixtime to the Hypership time format or gets the current hypership time.
  * @link http://en.wikipedia.org/wiki/Unix_time
  * @link http://www.purl.org/NET/Zed/blog/HyperShipTime
  *
  * @param int $unixtime The unixtime to convert to HyperShip time. If omitted, the current unixtime.
  * @return string The HyperShip time
  */
 function get_hypership_time ($unixtime = null) {
     //If unixtime is not specified, it's now
     if ($unixtime === null) {
         $unixtime = time();
     }
 
     //Hypership time is a count of days since launch @ 2010-07-03 00:00:00
     //Followed by a fraction of the current day /1000, like the internet time
     //but in UTC timezone and not Switzerland CET/CEST.
     //We don't need to use floor(), as we output the result at int, truncating
     //automatically decimal values instead of round it (like in C).
     $seconds = $unixtime - 1278115200;
     $days = $seconds / 86400;
     $fraction = (abs($seconds) % 86400) / 86.4;
     return sprintf("%d.%03d", $days, $fraction);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///                                                                          ///
 /// URL helpers functions                                                    ///
 ///                                                                          ///
 ////////////////////////////////////////////////////////////////////////////////
 
 /**
  * Gets the URL matching the specified resource.
  *
  * Example:
  * <code>
  * $url = get_url('ship', $ship);
  * echo $url; //if $ship contains S00001, this should print /ship/S00001
  * </code>
  *
  * @param string $resource,... the resources
  * @return string the URL matching the specified resource
  */
 function get_url () {
     global $Config;
     if (func_num_args() > 0) {
         $pieces = func_get_args();
         return $Config['BaseURL'] . '/' . implode('/', $pieces);
     } elseif ($Config['BaseURL'] == "" || $Config['BaseURL'] == $_SERVER["PHP_SELF"]) {
         return "/";
     } else {
         return $Config['BaseURL'];
     }
 }
 
 /**
  * Gets the current page URL
  *
  * @return string the current page URL
  */
 function get_page_url () {
     $url = $_SERVER['SCRIPT_NAME'] . $_SERVER['PATH_INFO'];
     if (substr($url, -10) == $_SERVER["PHP_SELF"]) {
         return substr($url, 0, -9);
     }
     return $url;
 }
 
 /**
  * Gets the server URL
  * @todo find a way to detect https:// on non standard port
  *
  * @return string the server URL
  */
 function get_server_url () {
     switch ($port = $_SERVER['SERVER_PORT']) {
         case '80':
             return "http://$_SERVER[SERVER_NAME]";
 
         case '443':
             return "https://$_SERVER[SERVER_NAME]";
 
         default:
             return "http://$_SERVER[SERVER_NAME]:$_SERVER[SERVER_PORT]";
     }
 }
 
 /**
  * Gets $_SERVER['PATH_INFO'] or computes the equivalent if not defined.
  *
  * This function allows the entry point controllers to get the current URL
  * in a consistent way, for any redirection configuration
  *
  * So with /foo/bar, /index.php/foo/bar, /zed/index.php/foo/bar or /zed/foo/bar
  * get_current_url will return /foo/bar
  *
  * @return string the relevant URL part
  */
 function get_current_url () {
     global $Config;
 
     //Gets relevant URL part from relevant $_SERVER variables
     if (array_key_exists('PATH_INFO', $_SERVER)) {
         //Without mod_rewrite, and url like /index.php/controller
         //we use PATH_INFO. It's the easiest case.
         return $_SERVER["PATH_INFO"];
     }
 
     //In other cases, we'll need to get the relevant part of the URL
     $current_url = get_server_url() . $_SERVER['REQUEST_URI'];
 
     //Relevant URL part starts after the site URL
     $len = strlen($Config['SiteURL']);
 
     //We need to assert it's the correct site
     if (substr($current_url, 0, $len) != $Config['SiteURL']) {
         dieprint_r( "Edit includes/config.php and specify the correct site URL<br /><strong>Current value:</strong> $Config[SiteURL]<br /><strong>Expected value:</strong> a string starting by " . get_server_url(), "Setup");
     }
 
     if (array_key_exists('REDIRECT_URL', $_SERVER)) {
         //With mod_rewrite, we can use REDIRECT_URL
         //We takes the end of the URL, ie *FROM* $len position
         return substr(get_server_url() . $_SERVER["REDIRECT_URL"], $len);
     }
 
     //Last possibility: use REQUEST_URI, but remove QUERY_STRING
     //If you need to edit here, use $_SERVER['REQUEST_URI']
     //but you need to discard $_SERVER['QUERY_STRING']
 
     //We takes the end of the URL, ie *FROM* $len position
     $url = substr(get_server_url() . $_SERVER["REQUEST_URI"], $len);
 
     //But if there are a query string (?action=... we need to discard it)
     if ($_SERVER['QUERY_STRING']) {
         return substr($url, 0, strlen($url) - strlen($_SERVER['QUERY_STRING']) - 1);
     }
 
     return $url;
 }
 
 /**
  * Gets an array of url fragments to be processed by controller
  * @see get_current_url
  *
  * This method is used by the controllers entry points to know the URL and
  * call relevant subcontrollers.
  *
  * @return Array an array of string, one for each URL fragment
  */
 function get_current_url_fragments () {
     return explode('/', substr(get_current_url(), 1));
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ///                                                                          ///
 /// URL xmlHttpRequest helpers functions                                     ///
 ///                                                                          ///
 ////////////////////////////////////////////////////////////////////////////////
 
 /**
  * Gets an hash value to check the integrity of URLs in /do.php calls
  *
  * @param Array $args the args to compute the hash
  * @return string the hash parameter for your xmlHttpRequest url
  */
 function get_xhr_hash (array $args) : string {
     global $Config;
 
     array_shift($args);
     return md5($_SESSION['ID'] . $Config['SecretKey'] . implode('', $args));
 }
 
 /**
  * Gets the URL to call do.php, the xmlHttpRequest controller
  *
  * @return string the xmlHttpRequest url, with an integrity hash
  */
 function get_xhr_hashed_url () : string {
     global $Config;
 
     $args = func_get_args();
     $args[] = get_xhr_hash($args);
     return $Config['DoURL'] . '/' . implode('/', $args);
 }
 
 /**
  * Gets the URL to call do.php, the xmlHttpRequest controller
  *
  * @return string the xmlHttpRequest url
  */
 function get_xhr_url () : string {
     global $Config;
 
     $args = func_get_args();
     return $Config['DoURL'] . '/' .implode('/', $args);
 }