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 <command></strong> prints help for this command.", 'Help' => [ 'help' => "<strong>help <command></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(' ', ' ', $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); }