diff --git a/includes/SmartLine/SmartLine.php b/includes/SmartLine/SmartLine.php index 5e31b77..7df4e2d 100644 --- a/includes/SmartLine/SmartLine.php +++ b/includes/SmartLine/SmartLine.php @@ -1,558 +1,558 @@ <?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 contains the errors, + * 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 ($expression) { //Does nothing if blank line if (!$expression) { return; } //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"); try { $result = $this->commands[$command]->run($argv, $argc); } catch (Exception $ex) { $this->puts("<pre>$ex</pre>", STDERR); } 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]); } } /** * 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 Array an array of string, each item a message from the specified output queue */ public function gets_all ($output = STDOUT, $prefix = '<p>', $suffix = '</p>') { $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 = []; //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 9a0d682..0fd355b 100644 --- a/includes/SmartLine/ZedCommands.php +++ b/includes/SmartLine/ZedCommands.php @@ -1,538 +1,538 @@ <?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 /// $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']['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; 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); } require_once("includes/geo/location.php"); require_once("includes/travel/travel.php"); $here = new GeoLocation($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 GeoLocation($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 GeoLocation($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; $command = ($argc > 1) ? strtolower($argv[1]) : ''; switch ($command) { case 'list': $codes = Invite::get_invites_from($CurrentPerso->id); 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($CurrentUser->id, $CurrentPerso->id); $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; $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 the the requests page. + * 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; $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 the the settings page. + * 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 { private static function getGitHash (string $gitFolder = '.git') : string { $head = trim(file_get_contents("$gitFolder/HEAD")); if (str_starts_with($head, "ref: ")) { // Follows reference $ref = substr($head, 5); return file_get_contents("$gitFolder/$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; require_once("includes/geo/location.php"); $place = new GeoLocation($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; $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/api/cerbere.php b/includes/api/cerbere.php index 666706d..8c48823 100644 --- a/includes/api/cerbere.php +++ b/includes/api/cerbere.php @@ -1,125 +1,125 @@ <?php /** * API security * * Zed. The immensity of stars. The HyperShip. The people. * * (c) 2010, Dereckson, some rights reserved. * Released under BSD license. * * This file provides a cerbere function, to assert the user is correctly * authenticated in the API call. * * @package Zed * @subpackage API * @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 */ /** * Determines if localhost calls could be passed. * * If true, any call from localhost is valid. Otherwise, normal security rules are applied. */ define('ALLOW_LOCALHOST', false); /** * Determines if error should be printed. * * If true, the error will be printed according the FORMAT_ERROR setting. Otherwise, a blank page will be served. */ define('OUTPUT_ERROR', true); /** * Determines if the error must be formatted. * * If true, any error will be sent to api_output ; otherwise, it will be printed as is. */ define('FORMAT_ERROR', false); if (!defined('TABLE_API_KEYS')) { /** * The table where are located the API keys */ define('TABLE_API_KEYS', 'api_keys'); } /** * Checks if credentials are okay and exits if not * - * If the credentials aren't valid, it will prints an error message if + * If the credentials aren't valid, it will print an error message if * OUTPUT_ERROR is defined and true. * * This error message will be formatted through the api_output function if * FORMAT_ERROR is defined and true ; otherwise, it will be print as is. * * To help debug, you can also define ALLOW_LOCALHOST. If this constant is * defined and true, any call from localhost will be accepted, without checking * the key. * * @see cerbere_die */ function cerbere () { //If ALLOW_LOCALHOST is true, we allow 127.0.0.1 queries //If you use one of your local IP in your webserver vhost like 10.0.0.3 //it could be easier to create yourself a test key if (ALLOW_LOCALHOST && $_SERVER['REMOTE_ADDR'] == '127.0.0.1') { return; } $guid = $_REQUEST['key'] ?? ""; //No key, no authentication if ($guid === "") { cerbere_die('You must add credentials to your request.'); } //Authenticates user global $db; $guid = $db->escape($guid); $sql = "SELECT key_active FROM " . TABLE_API_KEYS . " WHERE key_guid like '$guid'"; if (!$result = $db->query($sql)) { message_die(SQL_ERROR, "Can't get key", '', __LINE__, __FILE__, $sql); } if ($row = $db->fetchRow($result)) { if ($row['key_active']) { //key_hits++ $sql = "UPDATE " . TABLE_API_KEYS . " SET key_hits = key_hits + 1" . " WHERE key_guid like '$guid'"; if (!$db->query($sql)) { message_die(SQL_ERROR, "Can't record api call", '', __LINE__, __FILE__, $sql); } } else { cerbere_die("Key disabled."); } } else { cerbere_die("Key doesn't exist."); } } /** * Prints a message in raw or API format, then exits. * * The error message will be formatted through api_output if the constant * FORMAT_ERROR is defined and true. Otherwise, it will be printed as is. * * @param string $message The error message to print */ function cerbere_die ($message) { if (OUTPUT_ERROR) { if (FORMAT_ERROR) { api_output($message, 'error'); } else { echo $message; } } exit; } diff --git a/includes/auth/IAuthentication.php b/includes/auth/IAuthentication.php index 9456389..213fd93 100644 --- a/includes/auth/IAuthentication.php +++ b/includes/auth/IAuthentication.php @@ -1,55 +1,55 @@ <?php /** * Authentication method interface. * * Zed. The immensity of stars. The HyperShip. The people. * * (c) 2013, Dereckson, some rights reserved. * Released under BSD license. * * @package Zed * @subpackage Auth * @author Sébastien Santoro aka Dereckson <dereckson@espace-win.org> * @copyright 2013 Sébastien Santoro aka Dereckson * @license http://www.opensource.org/licenses/bsd-license.php BSD * @link http://scherzo.dereckson.be/doc/zed * @link http://zed.dereckson.be/ * @filesource */ /** * Authentication method interface. */ interface IAuthentication { /** * Determines if an user has been authenticated. * * @return boolean true if the user has successfully been authenticated; otherwise, false. */ public function isValid (); /** * Gets the last authentication error * * @return string The last authentication error */ public function getError (); /** * Gets the user_id matching the authentication method * * @return int the authenticated user ID */ public function getUserID (); /** * Determines if the next authentication method could be tried if this one failed. * - * This allow when a method has failed in such a way the user must be warned to warn it, + * This allows when a method has failed in such a way the user must be warned to warn it, * returning false. * * @return bool true if authentication can go on to the next method; otherwise, false */ public function canTryNextAuthenticationMethod (); } diff --git a/includes/auth/YubiCloudAuthentication.php b/includes/auth/YubiCloudAuthentication.php index a9554ca..9ee2d58 100644 --- a/includes/auth/YubiCloudAuthentication.php +++ b/includes/auth/YubiCloudAuthentication.php @@ -1,192 +1,192 @@ <?php /** * YubiCloud authentication class. * * Zed. The immensity of stars. The HyperShip. The people. * * (c) 2013, Dereckson, some rights reserved. * Released under BSD license. * * @package Zed * @subpackage Auth * @author Sébastien Santoro aka Dereckson <dereckson@espace-win.org> * @copyright 2013 Sébastien Santoro aka Dereckson * @license http://www.opensource.org/licenses/bsd-license.php BSD * @link http://scherzo.dereckson.be/doc/zed * @link http://zed.dereckson.be/ * @filesource */ require_once('Auth/Yubico.php'); /** * YubiCloudAuthentication class * * Authenticates a user through YubiCloud */ class YubiCloudAuthentication implements IAuthentication { /** * The key * @var string */ private $key; /** * The username who should match the key * @var string */ private $username; /** * The user_id * @var int */ private $user_id; /** * Indicates if the error MUST be returned to the user * @var string */ private $mustThrowError = false; /** * The last validation error * @var string */ public $error; /** * Initializes a new instance of the key * * @param string $key The key */ public function __construct ($key, $username = null) { $this->username = $username; $this->key = $key; } /** * Validates the specified key's characters to determine if it looks like an OTP * * @return boolean true if the input seems an OTP key; otherwise, false. */ function looksValidOTP () { return preg_match("/^[cbdefghijklnrtuv]{32,48}$/", $this->key); } /** * Gets public identity * * @return string Public identity */ function getPublicIdentity () { return substr($this->key, 0, 12); } /** * Validates an OTP key against the YubiCloud servers * * @return boolean true if the input is a valid OTP key; otherwise, false. */ function isValid () { global $Config; //No need to lost time to query server if format is incorrect. if (!$this->looksValidOTP()) { $this->error = "Not the expected YubiKey OTP format."; return false; } //Query YubiCloud. We stop validation tests if that fails. $yubi = new Auth_Yubico( $Config['YubiCloud']['ClientID'], $Config['YubiCloud']['SecretKey'] ); $auth = $yubi->verify($this->key); if (@PEAR::isError($auth)) { $this->error = $auth->getMessage(); return false; } //Note: We first query the YubiCloud server, then we check if we can use the key - // as the key is an OTP (*one time* password), this allow to invalidate it. + // as the key is an OTP (*one time* password), this allows to invalidate it. // If we wouldn't do that, an attacker can reuse this password for another site. if (!$this->computeUserID()) { $this->error = "Valid YubiKey OTP. But the key doesn't match any account."; $this->mustThrowError = true; return false; } //Finally, if someone puts also a login, we'll check this user ID match this username if ($this->username) { $user = User::get($this->user_id); if ($this->username != $user->name) { $this->error = "Valid YubiKey OTP but fix or remove your username."; $this->mustThrowError = true; return false; } } return true; } /** * Gets the user_id matching the username * * You first need to validate the username, calling the isValid method. */ function computeUserID () { global $db; /** * Here a MySQL record for a valid OTP * +---------+-----------+---------------+-----------------+---------+ * | auth_id | auth_type | auth_identity | auth_properties | user_id | * +---------+-----------+---------------+-----------------+---------+ * | 2 | YubiKey | cccccccccccc | NULL | 1234 | * +---------+-----------+---------------+-----------------+---------+ */ $authentication_identity = $this->getPublicIdentity(); $sql = "SELECT user_id FROM " . TABLE_USERS_AUTH . " WHERE auth_type = 'YubiKey' AND auth_identity = '$authentication_identity'"; if (!$result = $db->query($sql)) { message_die(SQL_ERROR, "Can't query users authentication table.", '', __LINE__, __FILE__, $sql); } if ($row = $db->fetchRow($result)) { $this->user_id = $row['user_id']; return true; } return false; } /** * Gets the last authentication error * * @return string The last authentication error */ function getError () { return $this->error; } /** * Gets the user_id matching the key * * You first need to query the authentication table, calling the computeUserID method. * This is automatically done by IsValid, as we need to validate key matches someone. * * @return int the user ID */ function getUserID () { return $this->user_id; } /** * Determines if the next authentication method could be tried if this one failed. * * @return bool true if authentication can go on to the next method; otherwise, false */ function canTryNextAuthenticationMethod () { return !$this->mustThrowError; } } diff --git a/includes/cache/cache.php b/includes/cache/cache.php index 46f59ef..d9cde11 100644 --- a/includes/cache/cache.php +++ b/includes/cache/cache.php @@ -1,89 +1,89 @@ <?php /** * Cache calling class. * * Zed. The immensity of stars. The HyperShip. The people. * * (c) 2010, Dereckson, some rights reserved. * Released under BSD license. * * This file provides a calling class, which read the configuration, ensures * the cache class for the cache engine given in config exists and initializes * it. * * You'll find a sample of implementation in the CacheMemcached. * @see CacheMemcached * * If no caching mechanism, a "blackhole" void cache will be used. * @see CacheVoid. * * The class to call is determined from the following preference: * <code> * $Config['cache']['engine'] = 'memcached'; //will use CacheMemcached class. * </code> * * 0.1 2010-07-06 22:45 Initial version [DcK] * * @package Zed * @subpackage Cache * @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 */ /** * Cache caller */ class Cache { /** * Gets the cache instance, initializing it if needed * * The correct cache instance to initialize will be determined from the * $Config['cache']['engine'] preference. * * The class cache to use will be Cache + (preference engine, capitalized) * - * This method will creates an instance of the specified object, + * This method will create an instance of the specified object, * calling the load static method from this object class. * * Example: * <code> * $Config['cache']['engine'] = 'quux'; * $cache = Cache::load(); //Cache:load() will call CacheQuux:load(); * </code> * * @return Cache the cache instance, or null if nothing is cached */ static function load () { global $Config; if ( !array_key_exists('cache', $Config) || !array_key_exists('engine', $Config['cache']) ) { //cache is not configured or engine is not specified $engine = 'void'; } else { //engine is specified in the configuration $engine = $Config['cache']['engine']; } $engine_file = 'includes/cache/' . $engine . '.php'; $engine_class = 'Cache' . ucfirst($engine); if (!file_exists($engine_file)) { message_die(GENERAL_ERROR, "Can't initialize $engine cache engine.<br />$engine_file not found.", 'Cache'); } require_once($engine_file); if (!class_exists($engine_class)) { message_die(GENERAL_ERROR, "Can't initialize $engine cache engine.<br />$engine_class class not found.", 'Cache'); } return call_user_func([$engine_class, 'load']); } } diff --git a/includes/config.php b/includes/config.php index a3298c2..c3a69a8 100644 --- a/includes/config.php +++ b/includes/config.php @@ -1,297 +1,297 @@ <?php /** * Autogenerable configuration file * * 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 Dotenv\Dotenv; use Keruald\OmniTools\OS\Environment; use Keruald\Database\Engines\MySQLiEngine; //////////////////////////////////////////////////////////////////////////////// /// /// /// 0. DotEnv - read environment from .env /// /// /// //////////////////////////////////////////////////////////////////////////////// function loadEnvironment() { $directory = dirname(__DIR__); $dotenv = Dotenv::createImmutable($directory); $dotenv->safeLoad(); } loadEnvironment(); //////////////////////////////////////////////////////////////////////////////// /// /// /// I. SQL configuration /// /// /// //////////////////////////////////////////////////////////////////////////////// //SQL configuration $Config['database']['engine'] = MySQLiEngine::class; $Config['database']['host'] = $_ENV["DB_HOST"] ?? "localhost"; $Config['database']['username'] = $_ENV["DB_USER"] ?? "zed"; $Config['database']['password'] = $_ENV["DB_PASSWORD"] ?? "zed"; $Config['database']['database'] = $_ENV["DB_NAME"] ?? "zed"; $Config['database']['dontThrowExceptions'] = true; //SQL tables $prefix = ''; define('TABLE_API_KEYS', $prefix . 'api_keys'); define('TABLE_COMMENTS', $prefix . 'comments'); define('TABLE_CONTENT_FILES', $prefix . 'content_files'); define('TABLE_CONTENT_LOCATIONS', $prefix . 'content_locations'); define('TABLE_CONTENT_ZONES', $prefix . 'content_zones'); define('TABLE_CONTENT_ZONES_LOCATIONS', $prefix . 'content_zones_locations'); define('TABLE_LOG', $prefix . 'log'); define('TABLE_LOG_SMARTLINE', $prefix . 'log_smartline'); define('TABLE_MESSAGES', $prefix . 'messages'); define('TABLE_MOTD', $prefix . 'motd'); define('TABLE_PAGES', $prefix . 'pages'); define('TABLE_PAGES_EDITS', $prefix . 'pages_edits'); define('TABLE_PERSOS', $prefix . 'persos'); define('TABLE_PERSOS_FLAGS', $prefix . 'persos_flags'); define('TABLE_PERSOS_NOTES', $prefix . 'persos_notes'); define('TABLE_PORTS', $prefix . 'ports'); define('TABLE_PROFILES', $prefix . 'profiles'); define('TABLE_PROFILES_COMMENTS', $prefix . 'profiles_comments'); define('TABLE_PROFILES_PHOTOS', $prefix . 'profiles_photos'); define('TABLE_PROFILES_TAGS', $prefix . 'profiles_tags'); define('TABLE_REGISTRY', $prefix . 'registry'); define('TABLE_REQUESTS', $prefix . 'requests'); define('TABLE_REQUESTS_REPLIES', $prefix . 'requests_replies'); define('TABLE_SESSIONS', $prefix . 'sessions'); define('TABLE_SHIPS', $prefix . 'ships'); define('TABLE_USERS', $prefix . 'users'); define('TABLE_USERS_INVITES', $prefix . 'users_invites'); define('TABLE_USERS_AUTH', $prefix . 'users_auth'); //Geo tables define('TABLE_BODIES', $prefix . 'geo_bodies'); define('TABLE_LOCATIONS', $prefix . 'geo_locations'); //Well... it's a view define('TABLE_PLACES', $prefix . 'geo_places'); //////////////////////////////////////////////////////////////////////////////// /// /// /// II. Site configuration /// /// /// //////////////////////////////////////////////////////////////////////////////// //Default theme $Config['DefaultTheme'] = "Zed"; //Dates date_default_timezone_set("UTC"); //Secret key, used for some verification hashes in URLs or forms. $Config['SecretKey'] = $_ENV["ZED_SECRET_KEY"] ?? 'Lorem ipsum dolor'; //When reading files, buffer size define('BUFFER_SIZE', 4096); //////////////////////////////////////////////////////////////////////////////// /// /// /// III. Script URLs /// /// /// //////////////////////////////////////////////////////////////////////////////// /* * Apache httpd, without mod_rewrite: * * Subdirectory: * - $Config['SiteURL'] = 'http://zed.dereckson.be/hypership/index.php'; * - $Config['BaseURL'] = '/hypership/index.php'; * * Root directory: * - $Config['SiteURL'] = 'http://zed.dereckson.be/index.php'; * - $Config['BaseURL'] = '/index.php'; * * Apache httpd, with mod_rewrite: * * Subdirectory: * - $Config['SiteURL'] = 'http://zed.dereckson.be/hypership'; * - $Config['BaseURL'] = '/hypership'; * * In .htaccess or your vhost definition: * RewriteEngine On * RewriteBase /hypership/ * RewriteCond %{REQUEST_FILENAME} !-f * RewriteCond %{REQUEST_FILENAME} !-d * RewriteRule . /hypership/index.php [L] * * Root directory: * - $Config['SiteURL'] = 'http://zed.dereckson.be'; * - $Config['BaseURL'] = ''; * * In .htaccess or your vhost definition: * RewriteEngine On * RewriteBase / * RewriteCond %{REQUEST_FILENAME} !-f * RewriteCond %{REQUEST_FILENAME} !-d * RewriteRule . /index.php [L] * * nginx: * * Use same config.php settings than Apache httpd, with mod_rewrite. * * In your server block: * location / { - * #Serves static files if they exists, with one month cache + * #Serves static files if they exist, with one month cache * if (-f $request_filename) { * expires 30d; * break; * } * - * #Sends all non existing file or directory requests to index.php + * #Sends all non-existing file or directory requests to index.php * if (!-e request_filename) { * rewrite ^(.+)$ /index.php last; * #Or if you use a subdirectory: * #rewrite ^(.+)$ /hypership/index.php last; * } * } * * location ~ \.php$ { * #Your instructions to pass query to your FastCGI process, like: * fastcgi_pass 127.0.0.1:9000; * fastcgi_param SCRIPT_FILENAME /var/www/zed$fastcgi_script_name; * include fastcgi_params; * } * * * If you don't want to specify the server domain, you can use get_server_url: * $Config['SiteURL'] = get_server_url() . '/hypership'; * $Config['SiteURL'] = get_server_url(); * * * * !!! No trailing slash !!! * */ $Config['SiteURL'] = get_server_url(); $Config['BaseURL'] = ''; //AJAX callbacks URL $Config['DoURL'] = $Config['SiteURL'] . "/do.php"; //////////////////////////////////////////////////////////////////////////////// /// /// /// IV. Static content /// /// /// //////////////////////////////////////////////////////////////////////////////// //Where the static content is located? //Static content = 4 directories: js, css, img and content //On default installation, those directories are at site root. //To improve site performance, you can use a CDN for that. // //Recommended setting: $Config['StaticContentURL'] = $Config['SiteURL']; //Or if Zed is the site root: $Config['StaticContentURL'] = ''; //With CoralCDN: $Config['StaticContentURL'] = . '.nyud.net'; // $Config['StaticContentURL'] = ''; //$Config['StaticContentURL'] = get_server_url() . '.nyud.net'; //Site content define('CONTENT_DIR', Environment::getOr('CONTENT_DIR', 'content')); define('CONTENT_USERS_DIR', Environment::getOr( 'CONTENT_USERS_DIR', CONTENT_DIR . '/users' )); //Scenes define('SCENE_DIR', CONTENT_DIR . "/scenes"); define('SCENE_URL', $Config['StaticContentURL'] . '/content/scenes'); //Stories define('STORIES_DIR', CONTENT_DIR . "/stories"); //Profile's photos define('PHOTOS_DIR', CONTENT_DIR . "/users/_photos"); define('PHOTOS_URL', $Config['StaticContentURL'] . '/content/users/_photos'); //ImageMagick paths //Be careful on Windows platform convert could match the NTFS convert command. $Config['ImageMagick']['convert'] = 'convert'; $Config['ImageMagick']['mogrify'] = 'mogrify'; $Config['ImageMagick']['composite'] = 'composite'; $Config['ImageMagick']['identify'] = 'identify'; //////////////////////////////////////////////////////////////////////////////// /// /// /// V. Caching /// /// /// //////////////////////////////////////////////////////////////////////////////// /* * Some data (Smarty, OpenID and sessions) are cached in the cache directory. * * Security tip: you can move this cache directory outside the webserver tree. */ define('CACHE_DIR', Environment::getOr('CACHE_DIR', 'cache')); /* * Furthermore, you can also enable a cache engine, like memcached, to store * data from heavy database queries, or frequently accessed stuff. * * To use memcached: * - $Config['cache']['engine'] = 'memcached'; * - $Config['cache']['server'] = 'localhost'; * - $Config['cache']['port'] = 11211; * * To disable cache: * - $Config['cache']['engine'] = 'void'; * (or don't write nothing at all) */ $Config['cache']['engine'] = 'void'; //////////////////////////////////////////////////////////////////////////////// /// /// /// VI. Sessions and authentication code /// /// /// //////////////////////////////////////////////////////////////////////////////// //If you want to use a common table of sessions / user handling //with several websites, specify a different resource id for each site. $Config['ResourceID'] = 21; //Enable OpenID authentication //$Config['OpenID'] = true; //Enable YubiKey authentication //API 12940 //For YubiCloud API key - create yours at https://upgrade.yubico.com/getapikey/ //$Config['YubiCloud']['ClientID'] = 12345; //$Config['YubiCloud']['SecretKey'] = 'Base64SecretKeyHere'; //PHP variables ini_set('session.save_path', CACHE_DIR . '/sessions'); //4 days, for week-end story pause and continue url ini_set('session.gc_maxlifetime', 345600); //////////////////////////////////////////////////////////////////////////////// /// /// /// VII. Builder /// /// /// //////////////////////////////////////////////////////////////////////////////// //Zed can invoke a slightly modified version of HOTGLUE to build zones. $Config['builder']['hotglue']['enable'] = true; $Config['builder']['hotglue']['URL'] = '/apps/hotglue/index.php'; diff --git a/includes/content/zone.php b/includes/content/zone.php index c9c0dc3..8986505 100644 --- a/includes/content/zone.php +++ b/includes/content/zone.php @@ -1,203 +1,203 @@ <?php /** * Zone class * * Zed. The immensity of stars. The HyperShip. The people. * * (c) 2010, Dereckson, some rights reserved. * Released under BSD license. * * @package Zed * @subpackage Content * @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 */ /** * Content zone class * - * A zone is a physical place, independent from the location. + * A zone is a physical place, independent of the location. * This mechanism allows to more easily move zones. * * This class maps the content_zones table. */ class ContentZone { public $id; public $title; public $type; public $params; public $deleted = false; /** * Initializes a new instance of a zone object * * @param int $id The zone ID */ function __construct ($id = '') { if ($id) { $this->id = $id; return $this->load_from_database(); } } /** * Loads the object zone (ie fill the properties) from the $_POST array */ function load_from_form () { if (array_key_exists('title', $_POST)) { $this->user_id = $_POST['title']; } if (array_key_exists('type', $_POST)) { $this->user_id = $_POST['type']; } if (array_key_exists('params', $_POST)) { $this->user_id = $_POST['params']; } if (array_key_exists('deleted', $_POST)) { $this->user_id = $_POST['deleted']; } } /** * Loads the object zone (ie fill the properties) from the $row array */ function load_from_row ($row) { $this->id = $row['zone_id']; $this->title = $row['zone_title']; $this->type = $row['zone_type']; $this->params = $row['zone_params']; $this->deleted = (bool)$row['zone_deleted']; } /** * Loads the object zone (ie fill the properties) from the database */ function load_from_database () { global $db; $id = $db->escape($this->id); $sql = "SELECT * FROM " . TABLE_CONTENT_ZONES . " WHERE zone_id = '" . $id . "'"; if (!$result = $db->query($sql)) { message_die(SQL_ERROR, 'Unable to query content_zones', '', __LINE__, __FILE__, $sql); } if (!$row = $db->fetchRow($result)) { $this->lastError = 'Zone unknown: ' . $this->id; return false; } $this->load_from_row($row); return true; } /** * Saves the object to the database */ function save_to_database () { global $db; $id = $this->id ? "'" . $db->escape($this->id) . "'" : 'NULL'; $title = $db->escape($this->title); $type = $db->escape($this->type); $params = $db->escape($this->params); $deleted = $this->deleted ? 1 : 0; $sql = "REPLACE INTO " . TABLE_CONTENT_ZONES . " (`zone_id`, `zone_title`, `zone_type`, `zone_params`, `zone_deleted`) VALUES ($id, '$title', '$type', '$params', $deleted)"; if (!$db->query($sql)) { message_die(SQL_ERROR, "Unable to save", '', __LINE__, __FILE__, $sql); } if (!$this->id) { $this->id = $db->nextId(); } } /** * Assigns the zone at the specified location. * * @param string $location_global the global location * @param string $location_global the local location * @param bool $delete_old_locations if true, delete old locations */ function assign_to ($location_global, $location_local, $delete_old_locations = true) { if (!$this->id) { $this->save_to_database(); } global $db; if ($delete_old_locations) { $sql = "DELETE FROM " . TABLE_CONTENT_ZONES_LOCATIONS . " WHERE zone_id = " . $this->id; if (!$db->query($sql)) { message_die(SQL_ERROR, "Unable to delete", '', __LINE__, __FILE__, $sql); } } $g = $db->escape($location_global); $l = $db->escape($location_local); $sql = "REPLACE INTO " . TABLE_CONTENT_ZONES_LOCATIONS . " (location_global, location_local, zone_id) VALUES ('$g', '$l', $this->id)"; if (!$db->query($sql)) { message_die(SQL_ERROR, "Unable to set zone location", '', __LINE__, __FILE__, $sql); } } /** * Gets the zone at specified location * * @param string $location_global the global location * @param string $location_local the local location * @param bool $create if the zone doesn't exist, create it [optional] [default value: false] * @return ContentZone the zone, or null if the zone doesn't exist and $create is false */ static function at ($location_global, $location_local, $create = false) { global $db; $g = $db->escape($location_global); $l = $db->escape($location_local ?? ""); $sql = "SELECT * FROM " . TABLE_CONTENT_ZONES_LOCATIONS . " WHERE location_global = '$g' AND location_local = '$l'"; if (!$result = $db->query($sql)) { message_die(SQL_ERROR, "Unable to set zone location", '', __LINE__, __FILE__, $sql); } if ($row = $db->fetchRow($result)) { return new ContentZone($row['zone_id']); } elseif ($create) { $zone = new ContentZone(); $zone->assign_to($location_global, $location_local); return $zone; } else { return null; } } /** * Gets all the zones matching the specified location queries * * @param string $location_global_query the global location query * @param string $location_local_query the local location query * @return Array a ContentZone array, with each item a zone found * * [SECURITY] They are passed as is in SQL [R]LIKE queries, you can't inject users expression. * * The following properties are added to the ContentZone items of the returned array: * - location_global * - location_local */ static function search ($location_global_query, $location_local_query, $use_regexp_for_local = false) { global $db; $zones = []; $op = $use_regexp_for_local ? 'RLIKE' : 'LIKE'; $sql = "SELECT * FROM " . TABLE_CONTENT_ZONES_LOCATIONS . " WHERE location_global LIKE '$location_global_query' AND location_local $op '$location_local_query'"; if (!$result = $db->query($sql)) { message_die(SQL_ERROR, "Unable to set zone location", '', __LINE__, __FILE__, $sql); } while ($row = $db->fetchRow($result)) { $zone = new ContentZone($row['zone_id']); $zone->location_global = $row['location_global']; $zone->location_local = $row['location_local']; $zones[] = $zone; } return $zones; } } diff --git a/includes/error.php b/includes/error.php index ee960d6..58c0679 100644 --- a/includes/error.php +++ b/includes/error.php @@ -1,197 +1,197 @@ <?php /** * Error handler * * Zed. The immensity of stars. The HyperShip. The people. * * (c) 2010, Dereckson, some rights reserved. * Released under BSD license. * * This error handler uses the same idea and message_die method signature * of the phpBB 2 one. * * @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 * * @todo delete old_message_die method and write alternative HTML textual output * in the message_die method */ /// /// Error constants /// /** * SQL_ERROR is the constant meaning the error is a SQL error. * * As a message_die function parameter, it allows to add SQL specific debug information. */ define ("SQL_ERROR", 65); /** * HACK_ERROR is the constant meaning access is non authorized to the resource. * * It encompasses two problematics: * the URL points to a resource belonging to another user or for the current user have no access right (for malformed URL, pick instead GENERAL_ERROR) ; * the user is anonymous, instead to be logged in. * * A suggested way to handle the second problematic is to store in hidden input * fields or better in the session the previous form data, and to print a login * form. * - * If you implement this, you don't even need to distinguishes between the two + * If you implement this, you don't even need to distinguish between the two * cases, as once logged in, the regular HACK_ERROR could also be printed. */ define ("HACK_ERROR", 99); /** * GENERAL_ERROR is the constant meaning the error is general, ie not covered by * another more specific error constant. */ define ("GENERAL_ERROR", 117); /// /// Error helper functions /// /** * Output a general error, with human-readable information about the specified * expression as error message ; terminates the current script. * * @see message_die * * @param mixed $expression the expression to be printed * @param string $title the message title (optional, default will be 'Debug') */ function dieprint_r ($expression, $title = '') : never { if (!$title) { $title = 'Debug'; //if title is omitted or false/null, default title } message_die(GENERAL_ERROR, '<pre>' . print_r($expression, true) .'</pre>', $title); } /** * Outputs an error message and terminates the current script. * * Error will be output through Smarty one of the following templates : * error_block.tpl if the header have already been printed ; * error.tpl if the error occurred before the header were called and printed. * * If smarty couldn't be loaded, old_message_die method will be called, which * produces a table output. * * @param int $msg_code an integer constant identifying the error (HACK_ERROR, SQL_ERROR, GENERAL_ERROR) * @param string $msg_text the error message text (optional, but recommended) * @param string $msg_title the error message title (optional) * @param int $err_line the line number of the file where the error occurred (optional, suggested value is __LINE__) * @param string $err_line the path of file where the error occurred (optional, suggested value is __FILE__) * @param string $sql the SQL query (optional, used only if msg_code is SQL_ERROR) */ function message_die ($msg_code, $msg_text = '', $msg_title = '', $err_line = '', $err_file = '', $sql = '') : never { global $smarty, $db; if ($smarty) { $debug_text = $msg_text; if ($err_line && $err_file) { $debug_text .= ' — ' . $err_file. ', ' . lang_get('line') . ' ' . $err_line ; } switch ($msg_code) { case HACK_ERROR: $smarty->assign('TITLE', lang_get('UnauthorizedAccess')); break; case SQL_ERROR: $smarty->assign('TITLE', lang_get('SQLError')); $sql_error = $db->error(); if ($sql_error['message'] != '') { $debug_text .= '<br />' . lang_get('Error') . ' n° ' . $sql_error['code'] . lang_get('_t') . ' ' .$sql_error['message']; } $debug_text .= "</p><h2>Query:</h2><p>$sql"; break; default: $smarty->assign('WAP', "Message code error.<br />Expected: HACK_ERROR, SQL_ERROR, GENERAL_ERROR"); //Falls to GENERAL_ERROR case GENERAL_ERROR: if ($msg_title) { $smarty->assign('TITLE', $msg_title); } else { $smarty->assign('TITLE', lang_get('GeneralError')); } break; } $smarty->assign('ERROR_TEXT', $debug_text); $template = (defined('HEADER_PRINTED') && HEADER_PRINTED) ? "error_block.tpl" : "error.tpl"; $smarty->display($template); exit; } else { old_message_die($msg_code, $msg_text, $msg_title, $err_line, $err_file, $sql); } } /** * Outputs an error message and terminates the current script. * * This is the message_die method from Espace Win, used on Zed as fallback if Smarty isn't initialized yet. * Former "german style" error HTML markups have been removed. * * @param int $msg_code an integer constant identifying the error (HACK_ERROR, SQL_ERROR, GENERAL_ERROR) * @param string $msg_text the error message text (optional, but recommended) * @param string $msg_title the error message title (optional) * @param int $err_line the line number of the file where the error occurred (optional, suggested value is __LINE__) * @param string $err_line the path of file where the error occurred (optional, suggested value is __FILE__) * @param string $sql the SQL query (optional, used only if msg_code is SQL_ERROR) * * @deprecated since 0.1 */ function old_message_die($msg_code, $msg_text = '', $msg_title = '', $err_line = '', $err_file = '', $sql = '') : never { global $db, $Utilisateur; $sql_store = $sql; if ($msg_code == HACK_ERROR && $Utilisateur[user_id] < 1000) { die("You must be logged in to access to this resource."); } elseif ($msg_code == HACK_ERROR) { $title = "You aren't allowed to access this resource."; $debug_text = $msg_text; } elseif ($msg_code == SQL_ERROR) { $title = "SQL error"; $sql_error = $db->error(); $debug_text = $msg_text; if ($err_line != '' && $err_file != '') { $debug_text .= ' in ' . $err_file. ', line ' . $err_line ; } if ($sql_error['message'] != '') { $debug_text .= '<br />Error #' . $sql_error['code'] . ': ' . $sql_error['message']; } if ($sql_store != '') { $debug_text .= "<br /><strong>$sql_store</strong>"; } } elseif ($msg_code == GENERAL_ERROR) { $title = $msg_title; $debug_text = $msg_text; if ($err_line && $err_file) { $debug_text .= "<br />$err_file, line $err_line"; } } echo '<div id="fatal_error"><h2 class="fatal_error_title">'; echo $title; echo '</h2><p class="fatal_error_message">'; echo $debug_text; echo '</p></div'; die; } diff --git a/includes/geo/location.php b/includes/geo/location.php index 6cd1dbd..8c9c187 100644 --- a/includes/geo/location.php +++ b/includes/geo/location.php @@ -1,446 +1,446 @@ <?php /** * Geo location class. * * Zed. The immensity of stars. The HyperShip. The people. * * (c) 2010, Dereckson, some rights reserved. * Released under BSD license. * * 0.1 2010-01-28 18:52 DcK * * @package Zed * @subpackage Geo * @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; require_once('body.php'); require_once('place.php'); require_once('includes/objects/ship.php'); /** * Geo location class * * This class contains properties to get, set or compare a location and * explore the geo classes linked to. * - * It quickly allow to parse through the location classes in templates and + * It quickly allows to parse through the location classes in templates and * controllers. * * @todo Initialize $point3D from $body or $ship own locations * @todo Improve GeoLocation documentation (especially magic properties) */ class GeoLocation { /** * An array of strings containing location data. * * In the current class implementation, * the first element is the global location * and the second element is the local location. * * @var Array */ private $data; /** * A body object * * It contains a GeoBody value when the global location is a body * ie if $this->data[0][0] == 'B' * * Otherwise, its value is null. * * @var GeoBody */ public $body = null; /** * A place object * * It contains a GeoPlacevalue when the global location is a place * ie if $this->data[0][0] == 'B' && strlen($this->data[0]) == 9 * * Otherwise, its value is null. * * @var GeoPlace */ public $place = null; /** * A point identified by x, y, z coordinates */ public Point3D|null $point3D = null; /** * A ship object * * It contains a Ship value when the global location is a ship * ie if $this->data[0][0] == 'S' * * Otherwise, its value is null. * * @var Ship */ public $ship = null; /** * Initializes a new location instance * * @param string $global the global location * @param string local the locallocation * * @todo Improve local location handling */ function __construct ($global = null, $local = null) { if (!$global) { $this->data = []; } elseif (preg_match("/[BS][0-9]{5}[0-9]{3}/", $global)) { $this->data[0] = $global; } elseif (preg_match("/[BS][0-9]{5}/", $global)) { $this->data[0] = $global; } elseif (preg_match("/^xyz\:/", $global)) { $coords = sscanf($global, "xyz: [%d, %d, %d]"); if (count($coords) == 3) { $this->data[0] = $global; } else { throw new Exception("Invalid expression: $global"); } } else { global $db; $name = $db->escape($global); $sql = "SELECT location_code FROM " . TABLE_LOCATIONS . " WHERE location_name LIKE '$name'"; $code = $db->queryScalar($sql); if ($code) { $this->data[0] = $code; return; } throw new Exception("Invalid expression: $global"); } //TODO: handle $local in a better way: from the global location, gets //a local location handler. Or a some inheritance, like a class //HypershipGeoLocation extending GeoLocation. if ($local !== null) { $this->data[1] = $local; } $this->load_classes(); } /** * Gets $place, $body and $ship instances if they're needed */ function load_classes () { //No data, no class to load if (!count($this->data)) { return; } //Loads global classes $global = $this->data[0]; $code = substr($global, 1, 5); switch ($global[0]) { case 'B': switch (strlen($global)) { case 9: $this->place = GeoPlace::from_code($global); case 6: $this->body = new GeoBody($code); break; } break; case 'S': $this->ship = new Ship($code); break; case 'x': $coords = sscanf($global, "xyz: [%f, %f, %f]"); if (count($coords) == 3) { $this->point3D = new Point3D(...$coords); } break; } } /** * Magic method called when a unknown property is get. * * Handles $global, $local, $type, $body_code, $ship_code, $place_code, * $body_kind, $containsGlobalLocation, $containsLocalLocation. */ function __get ($variable) { switch ($variable) { /* main variables */ case 'global': return $this->data[0]; break; case 'local': return (count($this->data) > 1) ? $this->data[1] : null; break; /* global location */ case 'type': return $this->data[0][0]; case 'body_code': if ($this->data[0][0] == 'B') { return substr($this->data[0], 1, 5); } return null; case 'place_code': if ($this->data[0][0] == 'B') { return substr($this->data[0], 6, 3); } return null; case 'ship_code': if ($this->data[0][0] == 'S') { return substr($this->data[0], 1, 5); } return null; case 'body_kind': if ($this->data[0][0] == 'B' && $this->body != null) { if ($kind = $this->body->kind()) { return $kind; } } elseif ($this->data[0][0] == 'S') { return 'ship'; } return 'place'; case 'containsGlobalLocation': return count($this->data) > 0; case 'containsLocalLocation': return count($this->data) > 1; default: throw new Exception("Unknown variable: $variable"); break; } } /** * Checks if the place exists * * @return bool true if the place exists ; otherwise, false. * * @todo Handle alias */ function exists () { $n = count($this->data); //If not defined, it doesn't exist if ($n == 0) { return false; } //Checks global location switch ($this->data[0][0]) { case 'B': switch (strlen($this->data[0])) { case 9: if (!$place = GeoPlace::from_code($this->data[0])) { return false; } break; case 6: $body = new GeoBody(substr($this->data[0], 1)); if ($body->lastError) { return false; } break; default: message_die(GENERAL_ERROR, "Invalid global location expression size: " . $this->data[0], "GeoLocation exists method", __LINE__, __FILE__); } break; case 'S': $ship = new Ship(substr($this->data[0], 1)); if ($body->lastError) { return false; } break; default: message_die(GENERAL_ERROR, "Invalid global location expression size: " . $this->data[0], "GeoLocation exists method", __LINE__, __FILE__); return false; } if ($n > 1) { if (!isset($place)) { message_die(GENERAL_ERROR, "Can't check if a local place exists for the following location: " . $this->data[0], "GeoLocation exists method", __LINE__, __FILE__); } if (!$place->is_valid_local_location($this->data[1])) { return false; } } return true; } /** * Checks if the place is equals at the specified expression or place * * @return bool true if the places are equals ; otherwise, false. * * @todo Create a better set of rules to define when 2 locations are equal. */ function equals ($expression) { //Are global location equals? //TODO: create a better set of rules to define when 2 locations are equal. if (is_a($expression, 'GeoLocation')) { if (!$this->equals($expression->data[0])) { return false; } if (count($expression->data) + count($this->data) > 2) { return $expression->data[1] == $this->data[1]; } } if ($expression == $this->data[0]) { return true; } $n1 = strlen($expression); $n2 = strlen($this->data[0]); if ($n1 > $n2) { return substr($expression, 0, $n2) == $this->data[0]; } return false; } /** * Represents the current location instance as a string * * @return string a string representing the current location */ function __toString () { if (!$this->data[0]) { return ""; } switch ($this->data[0][0]) { case 'S': $ship = new Ship($this->ship_code); $location[] = $ship->name; break; case 'B': $body = new GeoBody($this->body_code); $location[] = $body->name ?: lang_get('UnknownBody'); if (strlen($this->data[0]) == 9) { $place = GeoPlace::from_code($this->data[0]); $location[] = $place->name ?: lang_get('UnknownPlace'); } break; case 'x': $pt = $this->point3D->toSpherical(); return sprintf("(%d, %d°, %d°)", $pt[0], $pt[1], $pt[2]); default: message_die(GENERAL_ERROR, "Unknown location identifier: $type.<br />Expected: B or S."); } return implode(", ", array_reverse($location)); } /** * Magic method called when a unknown property is set. * * Handles $global, $local, $type, $body_code, $ship_code, $place_code */ function __set ($variable, $value) { switch ($variable) { /* main variables */ case 'global': $this->data[0] = $value; break; case 'local': $this->data[1] = $value; break; /* global location */ case 'type': if ($value == 'B' || $value == 'S') { if (!$this->data[0]) { $this->data[0] = $value; } else { $this->data[0][0] = $value; } } break; case 'body_code': if (preg_match("/[0-9]{1,5}/", $value)) { $value = sprintf("%05d", $value); if (!$this->data[0]) { $this->data[0] = "B" . $value; return; } elseif ($this->data[0][0] == 'B') { $this->data[0] = "B" . $value . substr($this->data[0], 6); return; } throw new Exception("Global location isn't a body."); } throw new Exception("$value isn't a valid body code"); case 'ship_code': if (preg_match("/[0-9]{1,5}/", $value)) { $value = sprintf("%05d", $value); if (!$this->data[0]) { $this->data[0] = "S" . $value; return; } elseif ($this->data[0][0] == 'S') { $this->data[0] = "S" . $value . substr($this->data[0], 6); return; } throw new Exception("Global location isn't a ship."); } throw new Exception("$value isn't a valid ship code"); case 'place_code': if (!preg_match("/[0-9]{1,3}/", $value)) { throw new Exception("$value isn't a valid place code"); } $value = sprintf("%03d", $value); if ($this->data[0][0] == 'B') { $this->data[0] = substr($this->data[0], 0, 6) . $value; } throw new Exception("Global location isn't a body."); default: throw new Exception("Unknown variable: $variable"); break; } } } diff --git a/includes/geo/scene.php b/includes/geo/scene.php index 2de8687..6d6c881 100644 --- a/includes/geo/scene.php +++ b/includes/geo/scene.php @@ -1,212 +1,212 @@ <?php /** * Geo scene class. * * Zed. The immensity of stars. The HyperShip. The people. * * (c) 2010, Dereckson, some rights reserved. * Released under BSD license. * * @package Zed * @subpackage Geo * @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 */ require_once('location.php'); require_once('sceneindex.php'); if (!defined('SCENE_DIR')) { /** * The directory containing scenes files */ define('SCENE_DIR', 'content/scenes'); } /** * Geo scene class * * This class provides methods to determine and renders the local scene. */ class GeoScene { /** * Last error or warning * * @var string */ public $lastError = ""; /** * File scene to serve * * @var string */ public $sceneFile; /** * The location of the scene to print * * @var GeoLocation */ public $location; /** * Initializes a new GeoScene instance * * @param GeoLocation $location location the scene is to print */ function __construct ($location) { $this->location = $location; //Gets local scene if ($location->containsLocalLocation) { if ($this->get_local_scene()) { return; } } //Gets global scene if ($location->containsGlobalLocation) { if ($this->get_global_scene()) { return; } } //If not scene found, let's set a warning $this->lastError = "No scene found."; } /** * Gets local scene * * @return boolean true if a scene have been found ; otherwise, false. */ private function get_local_scene () { //From the index $index = GeoSceneIndex::Load(SCENE_DIR); if ($tpl = $index->get_local_template($this->location->global, $this->location->local)) { $this->sceneFile = SCENE_DIR . '/' . $tpl; return true; } //From filename $expression = $this->location->global . ' ' . $this->location->local; if ($this->try_get_scene($expression)) { return true; } return false; } /** * Gets global scene * * @return boolean true if a scene have been found ; otherwise, false. */ private function get_global_scene () { $location = $this->location; if ($location->place) { if ($this->try_get_scene($location->global)) { return true; } } if ($location->body) { if ($this->try_get_scene('B' . $location->body->code)) { return true; } } return false; } /** * Gets file extension * * @param string $file the file path * @return string the file extension */ public static function get_file_extension (string $file) : string { $pathinfo = pathinfo($file); return $pathinfo['extension'] ?? ""; } /** * Renders the file */ public function render () { if ($file = $this->sceneFile) { switch ($ext = GeoScene::get_file_extension($file)) { case 'png': case 'jpg': case 'gif': case 'bmp': echo "<img src=\"$file\" />"; break; case 'tpl': global $smarty, $Config; //$this->location is the object reference //Some objects like the hypership move, so we also need to know where there are. //From the template, this object location is assigned to $location //To get $this->location from template, use $CurrentPerso->location if ($this->location->body) { $smarty->assign("location", new GeoLocation($this->location->body->location)); } elseif ($this->location->ship) { $smarty->assign("location", new GeoLocation($this->location->ship->location)); } //Gets zone information require_once('includes/content/zone.php'); if ($zone = ContentZone::at($this->location->global, $this->location->local)) { $smarty->assign('zone', $zone); } //Scene-specific variables $smarty->assign("SCENE_URL", defined('SCENE_URL') ? SCENE_URL : '/' . SCENE_DIR); if ($Config['builder']['hotglue']['enable']) { $smarty->assign("HOTGLUE", $Config['builder']['hotglue']['URL']); } lang_load('scenes.conf', $this->location->global); //Displays scene $smarty->display($file); break; case 'php': message_die(HACK_ERROR, ".php scene files not allowed without review", '', __LINE__, __FILE__); default: message_die(GENERAL_ERROR, "Can't handle $ext extension for $file scene", 'GeoScene render error', __LINE__, __FILE__); } echo "\n\n"; } } /** * Tries to get the scene file. * - * It will tries to find in the scene directory a file with $code as name, + * It will try to find in the scene directory a file with $code as name, * and .tpl .png .gif .bmp .swf .html or .php as extension. * * @param string the location code (and filename) * @return bool true if a scene file have been found and set ; otherwise, false. */ private function try_get_scene ($code) { $file = SCENE_DIR . "/$code"; $extensions = ['tpl', 'png', 'jpg', 'gif', 'bmp', 'swf', 'html', 'php']; foreach ($extensions as $ext) { if (file_exists("$file.$ext")) { $this->sceneFile = "$file.$ext"; return true; } } return false; } } diff --git a/includes/geo/sceneindex.php b/includes/geo/sceneindex.php index bd8c8f8..1b24e1d 100644 --- a/includes/geo/sceneindex.php +++ b/includes/geo/sceneindex.php @@ -1,222 +1,222 @@ <?php /** * Geo scene index class. * * Zed. The immensity of stars. The HyperShip. The people. * * (c) 2010, Dereckson, some rights reserved. * Released under BSD license. * * @package Zed * @subpackage Geo * @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 * * This class implements a singleton pattern. */ require_once('includes/cache/cache.php'); /** * Geo scene class * * This class provides an index of available scene template files. */ class GeoSceneIndex { /** * Global location templates array * * Keys are global location codes. * Values the relevant template file. * * @var Array */ public $global_templates; /** * Local location templates 2D array * * Keys are global and local location codes. * Values the relevant template file. * * e.g. $local_templates['B00017001']['(10, 50, 8)'] => 'B00017_port.tpl' * * @var Array */ public $local_templates; /** * Time of the last updated file in the scenes directory * * @var int */ public $updated; /** * The directory where templates are stored * * @var string */ public $directory; /** * The current index instance array * * Keys are scene directories (string) * Items are GeoSceneIndex instances * * @var Array */ static $instance = []; /** * Gets the index instance, initializing it if needed * * @return index the index instance */ static function load ($directory) { //Creates the index object if needed if (!array_key_exists($directory, self::$instance)) { self::$instance[$directory] = new GeoSceneIndex($directory); } return self::$instance[$directory]; } /** * Initializes a new GeoSceneIndex instance * * @param string $directory the scene templates directory */ public function __construct ($directory) { $this->directory = $directory; if (!$this->get_cached_information() || !$this->is_up_to_date()) { $this->refresh_information(); $this->set_cached_information(); } } /** * Caches index data */ public function set_cached_information () { $cache = Cache::load(); $cache->set('zed_sceneindex', serialize($this)); } /** * Gets index from cache */ public function get_cached_information () { $cache = Cache::load(); $cached_index = $cache->get('zed_sceneindex'); if ($cached_index === false) { return false; } $index = unserialize($cached_index); $this->global_templates = $index->global_templates; $this->local_templates = $index->local_templates; $this->updated = $index->updated; $this->directory = $index->directory; return true; } /** * Reads scene templates and indexes information */ public function refresh_information () { $this->global_templates = []; $this->local_templates = []; $this->updated = filemtime($this->directory); if ($handle = opendir($this->directory)) { while (false !== ($file = readdir($handle))) { if (GeoScene::get_file_extension($file) == 'tpl') { $template = file_get_contents($this->directory . '/' . $file, false, null, 0, 1024); $location = self::get_template_location($template); if ($location[1] !== null) { $this->local_templates[$location[0]][$location[1]] = $file; } elseif ($location[0] != null) { $this->global_templates[$location[0]] = $file; } } } closedir($handle); } } /** * Determines if the information is still up to date * * @return bool true if the information is up to date ; otherwise, false. */ public function is_up_to_date () { return ($this->updated == filemtime($this->directory)); } /** * Gets template location * * @return Array an string array of the location (two items; global, local) * At key 0, a string with global location, or NULL if not specified * At key 1, a string with local location, or NULL if not specified */ private static function get_template_location ($template) { $location = [null, null]; //Gets global location $pos1 = strpos($template, "Global location: "); if ($pos1 === false) { - throw new Exception("No location in template. Any template file must contain a comment line with the string 'Global location: ' followed by the global location matching the template. It should also contains a line 'Local location: ' when applicable."); + throw new Exception("No location in template. Any template file must contain a comment line with the string 'Global location: ' followed by the global location matching the template. It should also contain a line 'Local location: ' when applicable."); } $pos1 += 17; $pos2 = strpos($template, "\n", $pos1); $location[0] = trim(substr($template, $pos1, $pos2 - $pos1)); //Gets local location $pos1 = strpos($template, "Local location: "); if ($pos1 !== false) { $pos1 += 16; $pos2 = strpos($template, "\n", $pos1); $location[1] = trim(substr($template, $pos1, $pos2 - $pos1)); } return $location; } /** * Gets local template file from index * * @param string $location_global the global location * @param string $location_global the local location * @return string the relevant template scene file, or NULL if not existing */ public function get_local_template ($location_global, $location_local) { if (isset($this->local_templates[$location_global][$location_local])) { return $this->local_templates[$location_global][$location_local]; } return null; } /** * Gets global template file from index * * @param string $location_global the global location * @return string the relevant template scene file, or NULL if not existing */ public function get_global_template ($location_global) { if (isset($this->global_templates[$location_global])) { return $this->global_templates[$location_global]; } return null; } } diff --git a/includes/objects/perso.php b/includes/objects/perso.php index 22de763..3727cde 100644 --- a/includes/objects/perso.php +++ b/includes/objects/perso.php @@ -1,612 +1,612 @@ <?php /** * Perso class * * Zed. The immensity of stars. The HyperShip. The people. * * (c) 2010, Dereckson, some rights reserved. * Released under BSD license. * * 0.1 2010-01-27 00:39 Autogenerated by Pluton Scaffolding * 0.2 2010-01-29 14:39 Adding flags support * 0.3 2010-02-06 17:50 Adding static perso hashtable * 0.4 2012-07-04 11:37 Refactoring: moving code from index.php * * @package Zed * @subpackage Model * @author Sébastien Santoro aka Dereckson <dereckson@espace-win.org> * @copyright 2010, 2012 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 */ require_once("includes/geo/location.php"); /** * Perso class * * This class maps the persos table. * * The class also provides methods * to move or locate a perso, * to gets and sets perso's flags and notes (tables persos_flags and persos_notes), * to gets user's perso or check if a perso is online, * to handle on select and logout events. * */ class Perso { public $id; public $user_id; public $name; public $nickname; public $race; public $sex; public string $avatar = ""; public $location; public $location_global; public $location_local; public $flags; public string $lastError = ""; public static $hashtable_id = []; public static $hashtable_name = []; /** * Initializes a new instance * * @param mixed $data perso ID or nickname */ function __construct ($data = null) { if ($data) { if (is_numeric($data)) { $this->id = $data; } else { $this->nickname = $data; } if (!$this->load_from_database()) { message_die(GENERAL_ERROR, $this->lastError, "Can't authenticate perso"); } } else { $this->generate_id(); } } /** * Initializes a new Perso instance if needed or get already available one. * * @param mixed $data perso ID or nickname * @return Perso the perso instance */ static function get ($data = null) : Perso { if ($data) { //Checks in the hashtables if we already have loaded this instance if (is_numeric($data)) { if (array_key_exists($data, Perso::$hashtable_id)) { return Perso::$hashtable_id[$data]; } } else { if (array_key_exists($data, Perso::$hashtable_name)) { return Perso::$hashtable_name[$data]; } } } return new Perso($data); } /** * Loads the object Perso (ie fill the properties) from the $_POST array */ function load_from_form () { if (array_key_exists('user_id', $_POST)) { $this->user_id = $_POST['user_id']; } if (array_key_exists('name', $_POST)) { $this->name = $_POST['name']; } if (array_key_exists('nickname', $_POST)) { $this->nickname = $_POST['nickname']; } if (array_key_exists('race', $_POST)) { $this->race = $_POST['race']; } if (array_key_exists('sex', $_POST)) { $this->sex = $_POST['sex']; } if (array_key_exists('avatar', $_POST)) { $this->avatar = $_POST['avatar']; } if (array_key_exists('location_global', $_POST)) { $this->location_global = $_POST['location_global']; } if (array_key_exists('location_local', $_POST)) { $this->location_local = $_POST['location_local']; } } /** * Loads the object Perso (ie fill the properties) from the database */ function load_from_database () { global $db; //Gets perso $sql = "SELECT * FROM " . TABLE_PERSOS; if ($this->id) { $id = $db->escape($this->id); $sql .= " WHERE perso_id = '" . $id . "'"; } else { $nickname = $db->escape($this->nickname); $sql .= " WHERE perso_nickname = '" . $nickname . "'"; } if ( !($result = $db->query($sql)) ) { message_die(SQL_ERROR, "Unable to query persos", '', __LINE__, __FILE__, $sql); } if (!$row = $db->fetchRow($result)) { $this->lastError = "Perso unknown: " . $this->id; return false; } $this->id = $row['perso_id']; $this->user_id = $row['user_id']; $this->name = $row['perso_name']; $this->nickname = $row['perso_nickname']; $this->race = $row['perso_race']; $this->sex = $row['perso_sex']; $this->avatar = $row['perso_avatar']; $this->location_global = $row['location_global']; $this->location_local = $row['location_local']; //Gets flags $sql = "SELECT flag_key, flag_value FROM " . TABLE_PERSOS_FLAGS . " WHERE perso_id = $this->id"; if (!$result = $db->query($sql)) { message_die(SQL_ERROR, "Can't get flags", '', __LINE__, __FILE__, $sql); } while ($row = $db->fetchRow($result)) { $this->flags[$row["flag_key"]] = $row["flag_value"]; } //Gets location $this->location = new GeoLocation( $this->location_global, $this->location_local ); //Puts object in hashtables Perso::$hashtable_id[$this->id] = $this; Perso::$hashtable_name[$this->nickname] = $this; return true; } /** * Saves to database */ function save_to_database () { global $db; $id = $this->id ? "'" . $db->escape($this->id) . "'" : 'NULL'; $user_id = $db->escape($this->user_id); $name = $db->escape($this->name); $nickname = $db->escape($this->nickname); $race = $db->escape($this->race); $sex = $db->escape($this->sex); $avatar = $db->escape($this->avatar); $location_global = $this->location_global ? "'" . $db->escape($this->location_global) . "'" : 'NULL'; $location_local = $this->location_local ? "'" . $db->escape($this->location_local) . "'" : 'NULL'; //Updates or inserts $sql = "REPLACE INTO " . TABLE_PERSOS . " (`perso_id`, `user_id`, `perso_name`, `perso_nickname`, `perso_race`, `perso_sex`, `perso_avatar`, `location_global`, `location_local`) VALUES ($id, '$user_id', '$name', '$nickname', '$race', '$sex', '$avatar', $location_global, $location_local)"; if (!$db->query($sql)) { message_die(SQL_ERROR, "Unable to save", '', __LINE__, __FILE__, $sql); } if (!$id) { //Gets new record id value $this->id = $db->nextId(); } } /** * Updates the specified field in the database record * * @param string $field The field to save */ function save_field ($field) { global $db; if (!$this->id) { message_die(GENERAL_ERROR, "You're trying to update a perso record not yet saved in the database: $field"); } $id = $db->escape($this->id); $value = $db->escape($this->$field); $sql = "UPDATE " . TABLE_PERSOS . " SET `$field` = '$value' WHERE perso_id = '$id'"; if (!$db->query($sql)) { message_die(SQL_ERROR, "Unable to save $field field", '', __LINE__, __FILE__, $sql); } } /** * Gets perso location * * @return string The location names */ public function where () { return $this->location->__toString(); } /** * Moves the perso to a new location * * @param string $global the global target location * @param string $global the local target location */ public function move_to ($global = null, $local = null) { //Sets global location if ($global != null) { $this->location_global = $global; } //Sets local location if ($local != null) { $this->location_local = $local; } //Updates database record if ($global != null && $local != null) { global $db; $perso_id = $db->escape($this->id); $g = $db->escape($this->location_global); $l = $db->escape($this->location_local); $sql = "UPDATE " . TABLE_PERSOS . " SET location_global = '$g', location_local = '$l'" . " WHERE perso_id = '$perso_id'"; if (!$db->query($sql)) { message_die(SQL_ERROR, "Can't save new $global $local location.", '', __LINE__, __FILE__, $sql); } } elseif ($global != null) { $this->save_field('location_global'); } elseif ($local != null) { $this->save_field('location_local'); } //Updates location member $this->location = new GeoLocation( $this->location_global, $this->location_local ); } /** * Gets the specified flag value * * @param string $key flag key * @param mixed $defaultValue default value if the flag doesn't exist * @return mixed the flag value (string) or null if not existing */ public function get_flag ($key, $defaultValue = null) { return $this->flag_exists($key) ? $this->flags[$key] : $defaultValue; } /** * Determines if the specified flag exists * * @param string $key the flag key to check * @return boolean true if the specified flag exists ; otherwise, false. */ public function flag_exists ($key) { return array_key_exists($key, $this->flags); } /** * Sets the specified flag * * @param string $key flag key * @param string $value flag value (optional, default value: 1) */ public function set_flag ($key, $value = 1) { //Checks if flag isn't already set at this value if ($this->flags != null && array_key_exists($key, $this->flags) && $this->flags[$key] === $value) { return; } //Saves flag to database global $db; $id = $db->escape($this->id); $key = $db->escape($key); $value = $db->escape($value); $sql = "REPLACE " . TABLE_PERSOS_FLAGS . " SET perso_id = '$id', flag_key = '$key', flag_value = '$value'"; if (!$db->query($sql)) { message_die(SQL_ERROR, "Can't save flag", '', __LINE__, __FILE__, $sql); } //Sets flag in this perso instance $this->flags[$key] = $value; } /** * Deletes the specified flag * * @param string $key flag key */ public function delete_flag ($key) { global $db; if (!array_key_exists($key, $this->flags)) { return; } $id = $db->escape($this->id); $key = $db->escape($key); $sql = "DELETE FROM " . TABLE_PERSOS_FLAGS . " WHERE flag_key = '$key' AND perso_id = '$id' LIMIT 1"; if (!$db->query($sql)) { message_die(SQL_ERROR, "Can't delete flag", '', __LINE__, __FILE__, $sql); } } /** * Ensures the current perso have the specified flag or exits. * * * @param string $flag the flag to assert * @param int $threshold value the flags must strictly be greater than (optional, the default value is 0) * * Example: * <code> * $perso->set_flag('quux.foo', 1); * //The perso wants to read quux, which we allow with the flag quux.foo * $perso->request_flag('quux.foo'); //will be okay * * //The perso wants also to write quux, which we all allow if quux.foo = 2 * //The threshold will so be 1, as 2 > 1 * $perso->request_flag('quux.foo', 1); //Will exits, with a "You don't have quux.foo permission" message * </code> */ public function request_flag ($flag, $threshold = 0) { if (!array_key_exists($flag, $this->flags) || $this->flags[$flag] <= $threshold) { message_die(HACK_ERROR, "You don't have $flag permission.", "Permissions"); } } /** * Gets the specified note * * @param string $code the note code * @return string the note content */ public function get_note ($code) { global $db; $id = $db->escape($this->id); $code = $db->escape($code); $sql = "SELECT note_text FROM " . TABLE_PERSOS_NOTES . " WHERE perso_id = '$id' AND note_code LIKE '$code'"; return $db->queryScalar($sql); } /** * Sets the specified note * * @param string $code the note code * @param string $text the note content */ public function set_note ($code, $text) { global $db; $id = $db->escape($this->id); $code = $db->escape($code); $text = $db->escape($text); $sql = "REPLACE INTO " . TABLE_PERSOS_NOTES . " (perso_id, note_code, note_text) VALUES ('$id', '$code', '$text')"; if (!$db->query($sql)) { message_die(SQL_ERROR, "Can't save note", '', __LINE__, __FILE__, $sql); } } /** * Counts the amount of notes the perso have saved * - * @return int the amount of notes assigned to the this perso + * @return int the amount of notes assigned to the perso */ public function count_notes () { global $db; $id = $db->escape($this->id); $sql = "SELECT COUNT(*) FROM " . TABLE_PERSOS_NOTES . " WHERE perso_id = '$id'"; return $db->queryScalar($sql); } /* * Determines if the specified ID is available * * @param integer $id The perso ID to check * @return boolean true if the specified ID is available ; otherwise, false */ public static function is_available_id ($id) { global $db; $sql = "SELECT COUNT(*) FROM " . TABLE_PERSOS . " WHERE perso_id = $id LOCK IN SHARE MODE"; if (!$result = $db->query($sql)) { message_die(SQL_ERROR, "Can't access users table", '', __LINE__, __FILE__, $sql); } $row = $db->fetchRow($result); return ($row[0] == 0); } /** * Generates a unique ID for the current object */ private function generate_id () { do { $this->id = rand(2001, 5999); } while (!Perso::is_available_id($this->id)); } /** * Checks if the nickname is available * * @param string $nickname the nickname to check */ public static function is_available_nickname ($nickname) { global $db; $nickname = $db->escape($nickname); $sql = "SELECT COUNT(*) FROM " . TABLE_PERSOS . " WHERE perso_nickname LIKE '$nickname' LOCK IN SHARE MODE;"; if (!$result = $db->query($sql)) { message_die(SQL_ERROR, "Utilisateurs non parsable", '', __LINE__, __FILE__, $sql); } $row = $db->fetchRow($result); return ($row[0] == 0); } /** * Counts the perso a user have * * @param int user_id the user ID * @return int the user's perso count */ public static function get_persos_count ($user_id) : int { global $db; $sql = "SELECT COUNT(*) FROM " . TABLE_PERSOS . " WHERE user_id = $user_id"; return (int)$db->queryScalar($sql); } /** * Gets an array with all the perso of the specified user * * @param int $user_id the user ID */ public static function get_persos (int $user_id) : array { global $db; $user_id = $db->escape($user_id); $sql = "SELECT perso_id FROM " . TABLE_PERSOS . " WHERE user_id = $user_id"; if (!$result = $db->query($sql)) { message_die(SQL_ERROR, "Can't get persos", '', __LINE__, __FILE__, $sql); } $persos = []; while ($row = $db->fetchRow($result)) { $persos[] = Perso::get($row['perso_id']); } return $persos; } /** * Gets the first perso a user have * (typically to be used when get_persos_count returns 1 to autoselect) * * @param int user_id the user ID */ public static function get_first_perso ($user_id) { global $db; $sql = "SELECT perso_id FROM " . TABLE_PERSOS ." WHERE user_id = $user_id LIMIT 1"; if ($perso_id = $db->queryScalar($sql)) { return new Perso($perso_id); } } /** * Determines whether the perso is online * * @return bool true if the perso is online ; otherwise, false. */ public function is_online () { global $db; $id = $db->escape($this->id); $sql = "SELECT MAX(online) FROM " . TABLE_SESSIONS ." WHERE perso_id = $id"; if (!$result = $db->query($sql)) { message_die(SQL_ERROR, "Unable to query the table", '', __LINE__, __FILE__, $sql); } $row = $db->fetchRow($result); return ($row[0] == 1); } /** * This event method is called when the user selects a new perso */ public function on_select () { //Session set_info('perso_id', $this->id); $this->set_flag("site.lastlogin", $_SERVER['REQUEST_TIME']); define("PersoSelected", true); } /** * This event method is called when the user logs off its account or perso */ public function on_logout () { //Clears perso information in $_SESSION and session table set_info('perso_id', null); clean_session(); } /** * This event method is called when the perso is created */ public function on_create () { //Notifies host $this->notify_inviter(); } /** * Creates a new perso, from a parameter form * * @param User $user The user to attach the perso to * @param Perso $perso A reference to the created perso (don't initialize it, give it a null value) * @param array $errors A reference to the arrays containing errors (should be an empty array, or the method will always return false) * @return boolean true if the perso has ben created ; otherwise, false */ public static function create_perso_from_form (User $user, &$perso, &$errors) { $perso = new Perso(); $perso->load_from_form(); $perso->user_id = $user->id; //Validates forms if (!$perso->name) { $errors[] = lang_get("NoFullnameSpecified"); } if (!$perso->race) { $errors[] = lang_get("NoRaceSpecified"); $perso->race = "being"; } if (!$perso->sex) { $errors[] = lang_get("NoSexSpecified"); } if (!$perso->nickname) { $errors[] = lang_get("NoNicknameSpecified"); } elseif (!Perso::is_available_nickname($perso->nickname)) { $errors[] = lang_get("UnavailableNickname"); } if (count($errors)) { return false; } //Creates perso $perso->save_to_database(); $perso->on_create(); return true; } /** * Notifies the person having invited this perso */ public function notify_inviter() { require_once('includes/objects/message.php'); require_once('includes/objects/invite.php'); $message = new Message(); $message->from = 0; $message->to = invite::who_invited($this->id); $message->text = sprintf( lang_get('InvitePersoCreated'), $this->name, get_server_url() . get_url('who', $this->nickname) ); $message->send(); } } diff --git a/includes/story/choice.php b/includes/story/choice.php index cfff352..472d856 100644 --- a/includes/story/choice.php +++ b/includes/story/choice.php @@ -1,110 +1,110 @@ <?php /** * Story choice. * * Zed. The immensity of stars. The HyperShip. The people. * * (c) 2010, Dereckson, some rights reserved. * Released under BSD license. * * @package Zed * @subpackage Story * @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 */ /** * Story choice class * * This class is a PHP mapping from the Story XML format's <choice> tag. * * <code> * $xml = "<choice goto=\"city.entry\">Descendre vers le centre ville</choice>"; * $parser = new SimpleXmlElement($xml); * $choice = StoryChoice::from_xml($parser); * * echo $choice->text; //That will output Descendre vers le centre ville * echo $choice->goto; //That will output city.entry * echo $choice->guid; //That will output any guid generated for this instance * * $thesamechoice = StoryChoice::from_xml($parser); * echo $thesamechoice->guid; //That will output another guid * </code> */ class StoryChoice { /** - * The section key this choices links to + * The section key this choice links to * * @var string */ public $goto; /** * The choice text * * @var string */ public $text; /** * The choice GUID * * It will be automatically generated by the constructor, and so is an * ephemeral data for this StoryChoice instance. * * @see new_guid * * @var string */ public $guid; /** * Constructor */ function __construct () { //The guid allows to build temporary URLs to get to right choice $this->guid = new_guid(); } /** * Gets the story text as a string representation of the class * * @return string The story text */ function __toString () { return $this->text; } /** * Initializes a new instance of StoryChoice class from a XML element * * @param SimpleXMLElement the xml element to parse * @return StoryChoice the story choice class */ static function from_xml ($xml) { $choice = new StoryChoice(); //Parses attribute foreach ($xml->attributes() as $key => $value) { switch ($key) { case 'goto': $choice->$key = (string)$value; break; default: message_die(GENERAL_ERROR, "Unknown attribute: $key = \"$value\"", "Story error"); } } //Parses content $choice->text = (string)$xml; return $choice; } } diff --git a/includes/travel/place.php b/includes/travel/place.php index d8b3989..ca67540 100644 --- a/includes/travel/place.php +++ b/includes/travel/place.php @@ -1,150 +1,150 @@ <?php /** * TravelPlace class * * Zed. The immensity of stars. The HyperShip. The people. * * (c) 2010, Dereckson, some rights reserved. * Released under BSD license. * * 0.1 2010-07-19 22:10 DcK * * @package Zed * @subpackage Travel * @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 */ /** * TravelPlace class * - * The TravelPlace class is a set of rules determining which moves are valid + * The TravelPlace class is a set of rules determining what moves are valid * in a specific place. * * @see GeoPlace * */ class TravelPlace { /** * The place code * * @var string */ public $code; /** * Determines if any local location move is valid * * @var bool */ public $freeLocalMove = false; /** * Array of strings, each item another place reachable * * This matches GlobalTravelTo XML tags. * * @var Array */ public $globalTravelTo = []; /** * Array of array, containing [location, alias, name] entries * * This matches LocalMove XML tags. * * @var Array */ public $localMoves = []; /** * Array of array, containing [expression, global_location, local_location] entries * * This matches RewriteRule XML tags. * * @var Array */ public $rewriteRules = []; /** * Initializes a new TravelPlace instance, from the specified XML fragment * * @param string $xml the XML fragment to parse * @return TravelPlace the TravelPlace instance matching the specified XML fragment */ static function from_xml ($xml) { $travelPlace = new TravelPlace(); //Reads attributes: <TravelPlace code="B00001001" freeLocalMove="true"> foreach ($xml->attributes() as $key => $value) { switch ($key) { case 'code': $travelPlace->code = (string)$value; break; case 'freeLocalMove': $travelPlace->freeLocalMove = (boolean)$value; break; } } //<GlobalTravelTo code="B00001002" /> foreach ($xml->GlobalTravelTo as $globalTravelToXml) { foreach ($globalTravelToXml->attributes() as $key => $value) { if ($key == "code") { $travelPlace->globalTravelTo[] = (string)$value; } } } //<LocalMove local_location="(0, 0, 0)" alias="C0" name="Core" /> foreach ($xml->LocalMove as $localMoveXml) { $localMove = [null, null, null]; foreach ($localMoveXml->attributes() as $key => $value) { switch ($key) { case 'local_location': $localMove[0] = (string)$value; break; case 'alias': $localMove[1] = (string)$value; break; case 'name': $localMove[2] = (string)$value; break; } } $travelPlace->localMoves[] = $localMove; } //<RewriteRule expression="/^T([1-9][0-9]*)$/" global_location="B00001001" local_location="T$1C1" /> foreach ($xml->RewriteRule as $rewriteRuleXml) { $rewriteRule = [null, null, null]; foreach ($rewriteRuleXml->attributes() as $key => $value) { switch ($key) { case 'expression': $rewriteRule[0] = (string)$value; break; case 'global_location': $rewriteRule[1] = (string)$value; break; case 'local_location': $rewriteRule[2] = (string)$value; break; } } $travelPlace->rewriteRules[] = $rewriteRule; } return $travelPlace; } } diff --git a/includes/travel/travel.php b/includes/travel/travel.php index d86010b..1fbe4e9 100644 --- a/includes/travel/travel.php +++ b/includes/travel/travel.php @@ -1,173 +1,173 @@ <?php /** * Travel helper class * * Zed. The immensity of stars. The HyperShip. The people. * * (c) 2010, Dereckson, some rights reserved. * Released under BSD license. * * 0.1 2010-07-18 22:05 DcK * * @package Zed * @subpackage Travel * @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 */ require_once('place.php'); /** * Travel helper class * * The Travel class reads content/travel.xml to get travel special rules * - * It so be able to provide methods determining if a move is or not valid. + * It's so able to provide methods determining if a move is or not valid. * * This class implements a singleton pattern. */ class Travel { /** * Array of TravelPlace, each one a custom travel rule * * This array is indexed by TravelPlace code. * * @var Array */ public $globalTravelTo; /** * Constructor */ function __construct () { //Initializes array $this->globalTravelTo = []; } /** * Gets and initializes if needed the Travel instance * * @return Travel the Travel instance */ static function load () { require_once('includes/cache/cache.php'); $cache = Cache::load(); if (!$travel = $cache->get('zed_travel')) { //Initializes resource and caches it $travel = new Travel(); $travel->load_xml(CONTENT_DIR . "/travel.xml"); $cache->set('zed_travel', serialize($travel)); return $travel; } return unserialize($travel); } /** * Loads a travel configuration XML file * * @param string the path to the travel XML file */ function load_xml ($file) { $xml = simplexml_load_file($file); foreach ($xml->TravelPlace as $travelPlaceXml) { $travelPlace = TravelPlace::from_xml($travelPlaceXml); $this->globalTravelTo[$travelPlace->code] = $travelPlace; } } /** * Tries to parse the specified expression, according the rewrite rules * (for example defined by the <RewriteRule> xml tags) * * @param string $expression the expression to parse * @param GeoLocation the location where the perso is * @param GeoLocation the location where the perso wants to go * * @return boolean true if the expression have been parsed ; otherwise, false. */ function try_parse_rewrite_rule ($expression, $from, &$to) { - //Relevant write rules depends from the location the perso is ($from) + //Relevant write rules depends on the location the perso is ($from) if (!array_key_exists($from->global, $this->globalTravelTo)) { return false; } $travelPlace = $this->globalTravelTo[$from->global]; foreach ($travelPlace->rewriteRules as $rule) { //$rule is an array [expression, global_location, local_location] $subpatterns = []; $result = preg_match($rule[0], $expression, $subpatterns); if ($result > 0) { //$subpatterns is an array with: // - at indice 0, the full matched regexp // - from 1 to n, the (groups) inside the regexp //We need so to replace $1 by $subpatterns[1] and so on. for ($i = count($subpatterns) - 1 ; $i > 0 ; $i--) { $rule[1] = str_replace('$' . $i, $subpatterns[$i], $rule[1]); $rule[2] = str_replace('$' . $i, $subpatterns[$i], $rule[2]); } $to = new GeoLocation($rule[1], $rule[2]); return true; } } return false; } /** * Determines if a perso can travel from $from to $to * * If an alias have been used for $to local location, set correct location. * * @param GeoLocation the location where the perso is * @param GeoLocation the location where the perso wants to go * @return boolean true if the travel move is valid ; otherwise, false. * * @todo From B00001002, goto C1 doesn't work. Alias seems ignored. */ function can_travel ($from, &$to) { if ($from->global != $to->global) { //Checks if we can locally from $from to $to place if (!array_key_exists($from->global, $this->globalTravelTo)) { return false; } $travelPlace = $this->globalTravelTo[$from->global]; if (!in_array($to->global, $travelPlace->globalTravelTo)) { return false; } } if ($to->containsLocalLocation) { //Determines if we've custom rules about local moves in $to if (!array_key_exists($to->global, $this->globalTravelTo)) { return false; } $travelPlace = $this->globalTravelTo[$to->global]; //Is it's an especially allowed movement? foreach ($travelPlace->localMoves as $move) { //move is a [location, alias, name] array //If any of those 3 parameters matches $to->local, it's okay if (in_array($to->local, $move)) { $to->local = $move[0]; return true; } } if ($travelPlace->freeLocalMove) { //We can move freely, perfect return true; } //Local move not allowed return false; } return true; } }