Page MenuHomeDevCentral

No OneTemporary

diff --git a/workspaces/src/Engines/Apps/ApplicationConfiguration.php b/workspaces/src/Engines/Apps/ApplicationConfiguration.php
index 12b8639..92889e3 100644
--- a/workspaces/src/Engines/Apps/ApplicationConfiguration.php
+++ b/workspaces/src/Engines/Apps/ApplicationConfiguration.php
@@ -1,81 +1,80 @@
<?php
/**
* _, __, _, _ __, _ _, _, _
* / \ |_) (_ | | \ | /_\ |\ |
* \ / |_) , ) | |_/ | | | | \|
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*
* Application configuration class
*
* @package ObsidianWorkspaces
* @subpackage Apps
* @author Sébastien Santoro aka Dereckson <dereckson@espace-win.org>
* @license http://www.opensource.org/licenses/bsd-license.php BSD
* @filesource
*/
namespace Waystone\Workspaces\Engines\Apps;
+use Waystone\Workspaces\Engines\I18n\Message;
use Waystone\Workspaces\Engines\Serialization\ArrayDeserializable;
-use Message;
-
/**
* Application configuration class
*
* This class describes an application configuration.
*
* It can be serialized into a workspace.conf application entry
*/
class ApplicationConfiguration implements ArrayDeserializable {
/**
* @var string The URL the application is bound to, without an initial slash.
*/
public string $bind;
/**
* @var Message The navigation entry in the application menu.
*/
public Message $nav;
/**
* @var string The application icon name
*/
public string $icon;
/**
* @var string The application class name
*
* This must be a class name that extends Application
*/
public string $name;
/**
* @var array<string,string> The collections to use.
* Keys are collections roles, values collections names.
*/
public array $useCollections = [];
/**
* Loads an ApplicationConfiguration instance from an associative array
*
* @param array $data The associative array to deserialize
*
* @return ApplicationConfiguration The deserialized instance
*/
public static function loadFromArray (array $data) : self {
$instance = new static;
foreach ($data as $key => $value) {
$instance->$key = match ($key) {
"nav" => new Message($value),
default => $value,
};
}
return $instance;
}
}
diff --git a/workspaces/src/Engines/Auth/AuthenticationMethod.php b/workspaces/src/Engines/Auth/AuthenticationMethod.php
index 4908408..9f66744 100644
--- a/workspaces/src/Engines/Auth/AuthenticationMethod.php
+++ b/workspaces/src/Engines/Auth/AuthenticationMethod.php
@@ -1,290 +1,289 @@
<?php
/**
* _, __, _, _ __, _ _, _, _
* / \ |_) (_ | | \ | /_\ |\ |
* \ / |_) , ) | |_/ | | | | \|
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*
* Authentication method class
*
* @package ObsidianWorkspaces
* @subpackage Auth
* @author Sébastien Santoro aka Dereckson <dereckson@espace-win.org>
* @license http://www.opensource.org/licenses/bsd-license.php BSD
* @filesource
*/
namespace Waystone\Workspaces\Engines\Auth;
use Waystone\Workspaces\Engines\Auth\Actions\AddToGroupUserAction;
use Waystone\Workspaces\Engines\Auth\Actions\GivePermissionUserAction;
use Waystone\Workspaces\Engines\Framework\Context;
+use Waystone\Workspaces\Engines\I18n\Language;
+use Waystone\Workspaces\Engines\I18n\Message;
use Waystone\Workspaces\Engines\Serialization\ArrayDeserializableWithContext;
use Waystone\Workspaces\Engines\Users\User;
use Keruald\OmniTools\DataTypes\Option\None;
use Keruald\OmniTools\DataTypes\Option\Option;
use Keruald\OmniTools\DataTypes\Option\Some;
-use Language;
-use Message;
-
use Exception;
use InvalidArgumentException;
/**
* Authentication method class
*
* This class has to be extended to implement custom authentication methods.
*/
abstract class AuthenticationMethod implements ArrayDeserializableWithContext {
/**
* @var User The local user matching the authentication
*/
public $localUser;
/**
* @var string The username
*/
public $name;
/**
* @var string The e-mail address
*/
public $email;
/**
* @var string The authentication method identifiant
*/
public $id;
/**
* @var string The remote identity provider user identifiant
*/
public $remoteUserId;
/**
* @var Message The localized authentication login message
*/
public $loginMessage;
/**
* @var boolean Determines if the authentication method could be used to
* register new users
*/
public $canCreateUser = false;
/**
* @var Array Actions to execute if a user is created, each instance a
* member of UserAction
*/
public $createUserActions = [];
/**
* @var Context The site context
*/
public $context;
/**
* @var Message The localized authentication error message
*/
public $loginError;
/**
* Gets authentication link for this method
*/
public abstract function getAuthenticationLink ();
/**
* Handles request
*/
public abstract function handleRequest ();
/**
* Runs actions planned on user create
*/
protected function runCreateUserActions () {
foreach ($this->createUserActions as $action) {
$action->context = $this->context;
$action->targetUser = $this->localUser;
$action->run();
}
}
/**
* Finds user from available data
*
* @return Option<User> the user if a user has been found; otherwise, false.
*/
private function findUser () : Option {
$users = $this->context->resources->users;
if ($this->remoteUserId != '') {
$user = $users->getUserFromRemoteIdentity(
$this->id, $this->remoteUserId,
);
if ($user->isSome()) {
return $user;
}
}
if ($this->email != '') {
$user = $users->getUserFromEmail($this->email);
if ($user->isSome()) {
return $user;
}
}
return new None;
}
/**
* Signs in or creates a new user
*
* @return boolean true if user has been successfully logged in; otherwise,
* false.
*/
public function signInOrCreateUser () {
// At this stage, if we don't already have a user instance,
// we're fetching it by remote user id or mail.
//
// If no result is returned, we're creating a new user if needed.
//
// Finally, we proceed to log in.
if ($this->localUser === null) {
$user = $this->findUser();
if ($user->isSome()) {
$this->localUser = $user->getValue();
}
}
if ($this->localUser === null) {
if (!$this->canCreateUser) {
$this->loginError =
Language::get("ExternalLoginCantCreateAccount");
return false;
} else {
$this->createUser();
if ($this->localUser === null) {
throw new Exception("Can't sign in: after correct remote authentication, an error occurred creating locally a new user.");
}
}
}
$this->signIn($this->localUser);
return true;
}
/**
* Signs in the specified user
*
* @param User The user to log in
*/
public function signIn (User $user) {
$this->context->session->user_login($user->id);
}
/**
* Creates a new user based on the authentication provisioning information
*
* @return User The user created
*/
public function createUser () {
if (!$this->canCreateUser) {
throw new Exception("Can't create user: the canCreateUser property is set at false.");
}
$users = $this->context->resources->users;
$user = $users->create();
$user->name = $this->name;
$user->email = $this->email;
$users->saveToDatabase($user);
$users->setRemoteIdentity(
$user, $this->id, $this->remoteUserId,
);
$this->localUser = $user;
$this->runCreateUserActions();
}
/**
* Gets authentication method from ID
*
* @param string $id The authentication method id
* @param Context $context The site context
*
* @return AuthenticationMethod The authentication method matching the id
*/
public static function getFromId ($id, $context) {
if ($context->workspace != null) {
foreach (
$context->workspace->configuration->authenticationMethods as
$authenticationMethod
) {
if ($authenticationMethod->id == $id) {
return $authenticationMethod;
}
}
}
return null;
}
/**
* Loads an AuthenticationMethod instance from a generic array.
* Typically used to deserialize a configuration.
*
* @param array $data The associative array to deserialize
* @param mixed $context The application context
*
* @return AuthenticationMethod The deserialized instance
* @throws InvalidArgumentException|Exception
*/
public static function loadFromArray (array $data, mixed $context) : self {
$instance = new static;
$instance->context = $context;
if (!array_key_exists("id", $data)) {
throw new InvalidArgumentException("Authentication method id is required.");
}
$instance->id = $data["id"];
$message = $data["loginMessage"] ?? Language::get("SignIn");
$instance->loginMessage = new Message($message);
if (array_key_exists("createUser", $data)) {
$createUser = $data["createUser"];
if (array_key_exists("enabled", $createUser)) {
$instance->canCreateUser = ($createUser["enabled"] === true);
}
$addToGroups = $createUser["addToGroups"] ?? [];
foreach ($addToGroups as $actionData) {
$instance->createUserActions[] =
AddToGroupUserAction::loadFromArray($actionData);
}
$givePermissions = $createUser["givePermissions"] ?? [];
foreach ($createUser["givePermissions"] as $actionData) {
$instance->createUserActions[] =
GivePermissionUserAction::loadFromArray($actionData);
}
}
return $instance;
}
}
diff --git a/workspaces/src/Engines/Auth/Methods/AzharProvider.php b/workspaces/src/Engines/Auth/Methods/AzharProvider.php
index f9225c0..724b91c 100644
--- a/workspaces/src/Engines/Auth/Methods/AzharProvider.php
+++ b/workspaces/src/Engines/Auth/Methods/AzharProvider.php
@@ -1,226 +1,225 @@
<?php
/**
* _, __, _, _ __, _ _, _, _
* / \ |_) (_ | | \ | /_\ |\ |
* \ / |_) , ) | |_/ | | | | \|
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*
* Azhàr provider authentication method class
*
* @package ObsidianWorkspaces
* @subpackage Auth
* @author Sébastien Santoro aka Dereckson <dereckson@espace-win.org>
* @license http://www.opensource.org/licenses/bsd-license.php BSD
* @filesource
*/
namespace Waystone\Workspaces\Engines\Auth\Methods;
use Waystone\Workspaces\Engines\Auth\AuthenticationMethod;
-
-use Language;
+use Waystone\Workspaces\Engines\I18n\Language;
use stdClass;
/**
* Azhàr provider authentication method class
*
* Azhàr sends a document providing authentication and registration of new
* users. It's signed by a shared secret key.
*/
class AzharProvider extends AuthenticationMethod {
/**
* @var string Shared secret key
*/
public $secretKey;
/**
* @var string Client key, to identify the consumer application.
*/
public $clientKey;
/**
* @var string The Azhàr identity provider login URL
*/
public $url;
/**
* Handles user login request
*/
public function handleRequest () {
$action = array_key_exists('action', $_GET) ? $_GET['action'] : '';
$sessionKey =
array_key_exists('sessionKey', $_GET) ? $_GET['sessionKey'] : '';
if ($action == "user.login.azhar.initialize") {
//Redirects user to Azhàr SSO service
$callbackUrl =
get_server_url() . get_url($this->context->workspace->code)
. '?action=user.login.azhar.success&authenticationMethodId='
. $this->id;
$url = $this->url . '?mode=provider&key=' . $this->clientKey
. '&sessionKey=' . $this->getSessionKey()
. '&url=' . urlencode($callbackUrl);
header('Location: ' . $url);
exit;
} elseif ($action == "user.login.azhar.success") {
//User claims to have logged in, we can get authentication information
$reply = $this->fetchInformation();
if (!$this->isDocumentLegit($reply)) {
$this->loginError = Language::get('ExternalLoginNotLegitReply');
return;
}
if ($reply->status == "SUCCESS") {
//Creates user or login
$this->name = $reply->username;
$this->email = $reply->email;
$this->remoteUserId = $reply->localUserId;
$this->signInOrCreateUser();
return;
} elseif ($reply->status == "ERROR_USER_SIDE") {
switch ($reply->code) {
case 'NO_USER_VISIT':
case 'NOT_LOGGED_IN':
$this ->loginError = Language::get('ExternalLoginNotRemotelyLoggedIn');
return;
}
} elseif ($reply->status == "ERROR_BETWEEN_US") {
switch ($reply->code) {
case 'SESSION_BADSECRET':
$this->loginError = sprintf(Language::get('ExternalLoginTechnicalDifficulty'), $reply->code);
return;
}
}
$this->loginError = '<p>An unknown error has been received:</p><pre>' . print_r($reply, true) . '</pre><p>Please notify technical support about this new error message, so we can handle it in the future.</p>';
} else {
$this->loginError = '<p>Unknown action: $action</p>';
}
}
/**
* Gets Azhàr provider session key
*
* This key allows us as consumer to fetch information, and Azhàr as provider to store it.
*
* @return string the session key
*/
public function getSessionKey () {
$hash = md5($this->id);
if (!isset($_SESSION['Auth-$hash']['SessionKey'])) {
$url = $this->url . '?mode=provider.announce&key=' . $this->clientKey
. '&url=n/a';
$reply = self::query($url);
$this->setSessionSecret($reply->sessionSecret);
$_SESSION['Auth-$hash']['SessionKey'] = $reply->sessionKey;
}
return $_SESSION['Auth-$hash']['SessionKey'];
}
/**
* Gets Azhàr provider session secret
*
* @return string the session secret
*/
private function getSessionSecret () {
$hash = md5($this->id);
return $_SESSION['Auth-$hash']['SessionSecret'];
}
/**
* Sets Azhàr provider session secret
*
* @param string $secret the session secret
*/
private function setSessionSecret ($secret) {
$hash = md5($this->id);
$_SESSION['Auth-$hash']['SessionSecret'] = $secret;
}
/**
* Gets Azhàr external authentication link
*
* @retrun string the login link
*/
public function getAuthenticationLink () {
$url = get_server_url() . get_url($this->context->workspace->code)
. '?action=user.login.azhar.initialize&authenticationMethodId=' . $this->id;
return $url;
}
/**
* Determines if the document received has been signed by the correct shared secret key.
*
* @return boolean true if the document is legit; otherwise, false.
*/
function isDocumentLegit ($document) {
$hash = '';
$claimedHash = null;
foreach ($document as $key => $value) {
if ($key == 'hash') {
$claimedHash = $value;
continue;
}
$hash .= md5($key . $value);
}
$salt = '$2y$10$' . substr($this->secretKey, 0, 22);
$computedHash = crypt($hash, $salt);
return $claimedHash === $computedHash;
}
/**
* Fetches information document
*
* @return stdClass The Azhàr identity provider information about the current login operation
*/
function fetchInformation () {
$url = $this->url . '?mode=provider.fetch&key=' . $this->clientKey
. '&sessionSecret=' . $this->getSessionSecret()
. '&sessionKey=' . $this->getSessionKey()
. '&url=n/a';
return self::query($url);
}
/**
* Gets the contents of the specified URL and decode the JSON reply
*
* @param string $url The URL to the JSON document to query.
*
* @return stdClass The reply
*/
public static function query ($url) {
$data = file_get_contents($url);
return json_decode($data);
}
/**
* Loads an AzharProvider instance from a generic array.
* Typically used to deserialize a configuration.
*
* @param array $data The associative array to deserialize
* @param mixed $context The application context
*
* @return AzharProvider The deserialized instance
*/
public static function loadFromArray (array $data, mixed $context) : self {
$instance = parent::loadFromArray($data, $context);
$instance->url = $data["url"];
$instance->secretKey = $data["secretKey"];
$instance->clientKey = $data["clientKey"];
return $instance;
}
}
diff --git a/workspaces/src/includes/i18n/Language.php b/workspaces/src/Engines/I18n/Language.php
similarity index 96%
rename from workspaces/src/includes/i18n/Language.php
rename to workspaces/src/Engines/I18n/Language.php
index 3da1604..8f69f55 100644
--- a/workspaces/src/includes/i18n/Language.php
+++ b/workspaces/src/Engines/I18n/Language.php
@@ -1,218 +1,227 @@
<?php
/**
* _, __, _, _ __, _ _, _, _
* / \ |_) (_ | | \ | /_\ |\ |
* \ / |_) , ) | |_/ | | | | \|
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*
* Localization (l10n) language class
*
* @package ObsidianWorkspaces
* @subpackage I18n
* @author Sébastien Santoro aka Dereckson <dereckson@espace-win.org>
* @license http://www.opensource.org/licenses/bsd-license.php BSD
* @filesource
*/
+namespace Waystone\Workspaces\Engines\I18n;
+
use Waystone\Workspaces\Engines\Controller\LoadableWithContext;
use Waystone\Workspaces\Engines\Framework\Context;
+use Smarty\Smarty;
+
+use InvalidArgumentException;
+
/**
* Language services
*/
class Language implements LoadableWithContext {
///
/// Properties
///
/**
* @var
*/
const FALLBACK = 'en';
/**
* @var Smarty the template engine
*/
private $templateEngine;
///
/// Singleton pattern. Constructor.
///
/**
* @var Language The loaded Language instance
*/
private static $instance;
/**
* Loads an instance of the class
*
* @param ?Context $context The context
* @return Language An instance of the Language class
*/
public static function Load (?Context $context = null) {
if (static::$instance === null) {
//Initializes an instance
if ($context === null) {
throw new InvalidArgumentException("A context is required to load this class for the first time.");
}
if ($context->templateEngine === null) {
throw new InvalidArgumentException("A context is required to load this class for the first time. You provided one, but the template engine isn't initiliazed. This is required, as the languages files are managed by the template engine.");
}
static::$instance = new static($context->templateEngine);
}
return static::$instance;
}
/**
* Initializes a new instance of the Language class
*/
public function __construct ($templateEngine) {
$this->templateEngine = $templateEngine;
}
///
/// Static helper methods
///
/**
* Defines the LANG constant, to lang to print
*
* This information is contained in the session, or if not yet defined,
* it's to determine according the user's browser preferences.
* @see findLanguage
*/
public static function initialize () {
//If $_SESSION['lang'] doesn't exist yet, find a common language
if (!array_key_exists('lang', $_SESSION)) {
$lang = static::findLanguage();
$_SESSION['lang'] = $lang ? $lang : '-';
}
if ($_SESSION['lang'] != '-') {
define('LANG', $_SESSION['lang']);
}
}
/**
* Gets a common lang spoken by the site and the user's browser
- * @see Language::getHttpAcceptLanguages
*
* @return string the language
+ * @see Language::getHttpAcceptLanguages
+ *
*/
public static function findLanguage () {
if (file_exists('lang') && is_dir('lang')) {
//Gets lang/ subdirectories: this is the list of available languages
$handle = opendir('lang');
while ($file = readdir($handle)) {
if ($file != '.' && $file != '..' && is_dir("lang/$file")) {
$langs[] = $file;
}
}
//The array $langs contains now the language available.
//Gets the langs the user should want:
- if (!$userlangs = static::getHttpAcceptLanguages())
+ if (!$userlangs = static::getHttpAcceptLanguages()) {
return;
+ }
//Gets the intersection between the both languages arrays
//If it matches, returns first result
$intersect = array_intersect($userlangs, $langs);
if (count($intersect)) {
return array_shift($intersect);
}
//Now it's okay with Opera and Firefox but Internet Explorer will
//by default return en-US and not en or fr-BE and not fr, so second pass
foreach ($userlangs as $userlang) {
$lang = explode('-', $userlang);
- if (count($lang) > 1)
+ if (count($lang) > 1) {
$userlangs2[] = $lang[0];
+ }
}
$intersect = array_intersect($userlangs2, $langs);
if (count($intersect)) {
return array_shift($intersect);
}
}
}
/**
* Gets the languages accepted by the browser, by order of priority.
*
* This will read the HTTP_ACCEPT_LANGUAGE variable sent by the browser in the
* HTTP request.
*
* @return Array an array of string, each item a language accepted by browser
*/
public static function getHttpAcceptLanguages () {
//What language to print is sent by browser in HTTP_ACCEPT_LANGUAGE var.
//This will be something like en,fr;q=0.8,fr-fr;q=0.5,en-us;q=0.3
if (!isset($_SERVER) || !array_key_exists('HTTP_ACCEPT_LANGUAGE', $_SERVER)) {
return null;
}
$http_accept_language = explode(',', $_SERVER["HTTP_ACCEPT_LANGUAGE"]);
foreach ($http_accept_language as $language) {
$userlang = explode(';q=', $language);
if (count($userlang) == 1) {
$userlangs[] = array(1, $language);
} else {
$userlangs[] = array($userlang[1], $userlang[0]);
}
}
rsort($userlangs);
foreach ($userlangs as $userlang) {
$result[] = $userlang[1];
}
return $result;
}
public static function get ($key) {
return static::load()->getConfigVar($key);
}
///
/// Methods
///
/**
* Loads specified language Smarty configuration file
*
* @param Smarty $templateEngine the template engine
* @param string $file the file to load
* @param mixed $sections array of section names, single section or null
*/
public function configLoad ($file, $sections = null) {
$fallback = static::FALLBACK;
//Loads English file as fallback if some parameters are missing
if (file_exists("lang/$fallback/$file")) {
$this->templateEngine->configLoad("lang/$fallback/$file", $sections);
}
//Loads wanted file (if it exists and a language have been defined)
if (defined('LANG') && LANG != '$fallback' && file_exists('lang/' . LANG . '/' . $file)) {
$this->templateEngine->configLoad('lang/' . LANG . '/' . $file, $sections);
}
}
/**
* Gets a specified language expression defined in configuration file
*
* @param string $key the configuration key matching the value to get
* @return string The value in the configuration file
*/
private function getConfigVar ($key) {
if (array_key_exists($key, $this->templateEngine->config_vars)) {
return $this->templateEngine->config_vars[$key];
}
trigger_error("The l10n key '$key' doesn't exist.", E_USER_NOTICE);
return "#$key#";
}
}
diff --git a/workspaces/src/includes/i18n/Message.php b/workspaces/src/Engines/I18n/Message.php
similarity index 96%
rename from workspaces/src/includes/i18n/Message.php
rename to workspaces/src/Engines/I18n/Message.php
index 5af2f12..887442a 100644
--- a/workspaces/src/includes/i18n/Message.php
+++ b/workspaces/src/Engines/I18n/Message.php
@@ -1,69 +1,74 @@
<?php
/**
* _, __, _, _ __, _ _, _, _
* / \ |_) (_ | | \ | /_\ |\ |
* \ / |_) , ) | |_/ | | | | \|
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*
* Message class
*
* @package ObsidianWorkspaces
* @subpackage I18n
* @author Sébastien Santoro aka Dereckson <dereckson@espace-win.org>
* @license http://www.opensource.org/licenses/bsd-license.php BSD
* @filesource
*/
+namespace Waystone\Workspaces\Engines\I18n;
+
+use Exception;
+
/**
* Represents a localizable message
*/
class Message {
+
/**
* @var Array the localized message
*/
public $localizations = [];
/**
* Initializes a new instance of the Message class
*
* @param mixed $expression unique string or localizations Array
*/
public function __construct ($expression) {
if (is_array($expression)) {
if (count($expression) && is_array($expression[0])) {
foreach ($expression as $msg) {
$this->localizations[$msg[0]] = $msg[1];
}
} else {
$this->localizations = $expression;
}
} elseif (is_string($expression)) {
$this->localizations = [
Language::FALLBACK => $expression
];
} else {
throw new Exception("Expression must be a string or a l10n array");
}
}
/**
* Gets a string representation of the message
*
* @return string The message string representation
*/
public function __toString () {
if (!count($this->localizations)) {
return "";
}
if (!defined('LANG') || !array_key_exists(LANG, $this->localizations)) {
if (array_key_exists(Language::FALLBACK, $this->localizations)) {
return $this->localizations[Language::FALLBACK];
}
return array_values($this->localizations)[0];
}
return $this->localizations[LANG];
}
}
diff --git a/workspaces/src/includes/i18n/TextFileMessage.php b/workspaces/src/Engines/I18n/TextFileMessage.php
similarity index 97%
rename from workspaces/src/includes/i18n/TextFileMessage.php
rename to workspaces/src/Engines/I18n/TextFileMessage.php
index 53416a8..8672f92 100644
--- a/workspaces/src/includes/i18n/TextFileMessage.php
+++ b/workspaces/src/Engines/I18n/TextFileMessage.php
@@ -1,76 +1,81 @@
<?php
/**
* _, __, _, _ __, _ _, _, _
* / \ |_) (_ | | \ | /_\ |\ |
* \ / |_) , ) | |_/ | | | | \|
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*
* Text file Message class
*
* @package ObsidianWorkspaces
* @subpackage I18n
* @author Sébastien Santoro aka Dereckson <dereckson@espace-win.org>
* @license http://www.opensource.org/licenses/bsd-license.php BSD
* @filesource
*/
+namespace Waystone\Workspaces\Engines\I18n;
+
+use Exception;
+
/**
* Represents a localizable message stored in a plain text file
*/
class TextFileMessage extends Message {
+
/**
* @var string The folder where the message is stored.
*/
public $folder;
/**
* @var string The message filename, without extension or language suffix.
*/
public $filename;
/**
* Initializes a new instance of the TextFileMessage class.
*
* @param string $folder The folder where the message is stored.
* @param string $filename The message filename, without extension or language suffix.
*/
public function __construct ($folder, $filename) {
$this->folder = $folder;
$this->folder = $filename;
//Finds relevant files
$files = scandir($folder);
foreach ($files as $file) {
if (string_starts_with($file, $filename . '-') && get_extension($file) == 'txt') {
$lang = substr($file, strlen($filename) + 1, -4);
if (strpos($lang, '-') !== false) {
//The user have quux-lang.txt and quux-foo-lang.txt files
continue;
}
$file = $folder . DIRECTORY_SEPARATOR . $file;
$this->localizations[$lang] = file_get_contents($file);
}
}
//Fallback if only one file is offered
$file = $folder . DIRECTORY_SEPARATOR . $filename . '.txt';
if (file_exists($file)) {
if (count($this->localizations)) {
if (array_key_exists(MESSAGE_FALLBACK_LANG, $this->localizations)) {
trigger_error("Ignored file: $filename.txt, as $filename-" . MESSAGE_FALLBACK_LANG . ".txt already exists and is used for fallback purpose", E_USER_NOTICE);
return;
}
trigger_error("You have $filename.txt and $filename-<lang>.txt files; you should have one or the other, but not both", E_USER_NOTICE);
}
$this->localizations[MESSAGE_FALLBACK_LANG] = file_get_contents($file);
return;
}
if (!count($this->localizations)) {
throw new Exception("TextFileMessage not found: $filename");
}
}
}
diff --git a/workspaces/src/Engines/Workspaces/Workspace.php b/workspaces/src/Engines/Workspaces/Workspace.php
index 195cd44..a9bb21a 100644
--- a/workspaces/src/Engines/Workspaces/Workspace.php
+++ b/workspaces/src/Engines/Workspaces/Workspace.php
@@ -1,301 +1,301 @@
<?php
/**
* _, __, _, _ __, _ _, _, _
* / \ |_) (_ | | \ | /_\ |\ |
* \ / |_) , ) | |_/ | | | | \|
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*
* Workspace class
*
* @package ObsidianWorkspaces
* @subpackage Workspaces
* @author Sébastien Santoro aka Dereckson <dereckson@espace-win.org>
* @license http://www.opensource.org/licenses/bsd-license.php BSD
* @filesource
*/
namespace Waystone\Workspaces\Engines\Workspaces;
use Waystone\Workspaces\Engines\Errors\ErrorHandling;
use Waystone\Workspaces\Engines\Framework\Context;
+use Waystone\Workspaces\Engines\I18n\Language;
use Waystone\Workspaces\Engines\Users\User;
use Keruald\OmniTools\Collections\Vector;
use Cache;
-use Language;
use Exception;
use LogicException;
/**
* Workspace class
*
* This class maps the workspaces table.
*/
class Workspace {
public $id;
public $code;
public $name;
public $created;
public $description;
/**
* @var WorkspaceConfiguration The workspace configuration
*/
public $configuration;
///
/// Constructors
///
/**
* Initializes a new instance
*
* @param int $id the primary key
*/
function __construct ($id = null) {
if ($id) {
$this->id = $id;
$this->load_from_database();
}
}
public static function fromRow ($row) : self {
$workspace = new self;
$workspace->load_from_row($row);
return $workspace;
}
///
/// Load data
///
/**
* Loads the object Workspace (ie fill the properties) from the $_POST array
*/
function load_from_form () {
if (array_key_exists('code', $_POST)) {
$this->code = $_POST['code'];
}
if (array_key_exists('name', $_POST)) {
$this->name = $_POST['name'];
}
if (array_key_exists('created', $_POST)) {
$this->created = $_POST['created'];
}
if (array_key_exists('description', $_POST)) {
$this->description = $_POST['description'];
}
}
/**
* Loads the object zone (ie fill the properties) from the $row array
*/
function load_from_row ($row) {
$this->id = $row['workspace_id'];
$this->code = $row['workspace_code'];
$this->name = $row['workspace_name'];
$this->created = $row['workspace_created'];
$this->description = $row['workspace_description'];
}
/**
* Loads the specified workspace from code
*
* @param string $code The workspace code
*
* @return Workspace The specified workspace instance
*/
public static function fromCode ($code) {
global $db;
$code = $db->escape($code);
$sql = "SELECT * FROM " . TABLE_WORKSPACES . " WHERE workspace_code = '"
. $code . "'";
if (!$result = $db->query($sql)) {
ErrorHandling::messageAndDie(SQL_ERROR,
"Unable to query workspaces", '', __LINE__, __FILE__, $sql);
}
if (!$row = $db->fetchRow($result)) {
throw new Exception("Workspace unknown: " . $code);
}
$workspace = new Workspace();
$workspace->load_from_row($row);
return $workspace;
}
/**
* Loads the object Workspace (ie fill the properties) from the database
*/
function load_from_database () {
global $db;
$id = $db->escape($this->id);
$sql = "SELECT * FROM " . TABLE_WORKSPACES . " WHERE workspace_id = '"
. $id . "'";
if (!$result = $db->query($sql)) {
ErrorHandling::messageAndDie(SQL_ERROR,
"Unable to query workspaces", '', __LINE__, __FILE__, $sql);
}
if (!$row = $db->fetchRow($result)) {
$this->lastError = "Workspace unknown: " . $this->id;
return false;
}
$this->load_from_row($row);
return true;
}
/**
* Saves to database
*/
function save_to_database () {
global $db;
$id = $this->id ? "'" . $db->escape($this->id) . "'" : 'NULL';
$code = $db->escape($this->code);
$name = $db->escape($this->name);
$created = $db->escape($this->created);
$description = $db->escape($this->description);
//Updates or inserts
$sql = "REPLACE INTO " . TABLE_WORKSPACES
. " (`workspace_id`, `workspace_code`, `workspace_name`, `workspace_created`, `workspace_description`) VALUES ('$id', '$code', '$name', '$created', '$description')";
if (!$db->query($sql)) {
ErrorHandling::messageAndDie(SQL_ERROR, "Unable to save", '',
__LINE__, __FILE__, $sql);
}
if (!$this->id) {
//Gets new record id value
$this->id = $db->nextId();
}
}
/**
* Determines if the specified user has access to the current workspace
*
* @param User $user The user to check
*
* @return boolean true if the user has access to the current workspace ;
* otherwise, false.
*/
public function userCanAccess (User $user) {
if ($this->id === false || $this->id === null || $this->id === '') {
throw new LogicException("The workspace must has a valid id before to call userCanAccess.");
}
foreach ($user->get_workspaces() as $workspace) {
if ($workspace->id == $this->id) {
return true;
}
}
return false;
}
/**
* Loads configuration
*
* @param Context $context The site context
*/
public function loadConfiguration (Context $context) {
global $Config;
$dir = $Config['Content']['Workspaces'] . '/' . $this->code;
// Load JSON configuration file
$file = $dir . '/workspace.conf';
if (file_exists($file)) {
$this->configuration =
WorkspaceConfiguration::loadFromFile($file, $context);
return;
}
// Load YAML configuration file
$file = $dir . '/workspace.yml';
if (file_exists($file)) {
$this->configuration =
WorkspaceConfiguration::loadFromYamlFile($file, $context);
return;
}
$exceptionMessage =
sprintf(Language::get('NotConfiguredWorkspace'), $file);
throw new Exception($exceptionMessage);
}
/**
* Gets workspaces specified user has access to.
*
* @param int $user_id The user to get his workspaces
*
* @return Workspace[] A list of workspaces
*/
public static function get_user_workspaces ($user_id) {
//Gets the workspaces list from cache, as this complex request could take 100ms
//and is called on every page.
$cache = Cache::load();
$cachedWorkspaces = $cache->get("workspaces-$user_id");
if ($cachedWorkspaces !== null && $cachedWorkspaces !== false) {
return unserialize($cachedWorkspaces);
}
$workspaces = self::loadWorkspacesForUser($user_id);
$cache->set("workspaces-$user_id", serialize($workspaces));
return $workspaces;
}
/**
* @return self[]
*/
private static function loadWorkspacesForUser (int $user_id) : array {
global $db;
$clause = User::get_permissions_clause_from_user_id($user_id, $db);
$sql = "SELECT DISTINCT w.*
FROM " . TABLE_PERMISSIONS . " p, " . TABLE_WORKSPACES . " w
WHERE p.target_resource_type = 'W' AND
p.target_resource_id = w.workspace_id AND
p.permission_name = 'accessLevel' AND
p.permission_flag > 0 AND
($clause)";
if (!$result = $db->query($sql)) {
ErrorHandling::messageAndDie(SQL_ERROR,
"Can't get user workspaces", '', __LINE__, __FILE__, $sql);
}
return Vector::from($result)
->map(fn($row) => self::fromRow($row))
->toArray();
}
/**
* Determines if a string matches an existing workspace code.
*
* @param string $code The workspace code to check
*
* @return boolean If the specified code matches an existing workspace,
* true; otherwise, false.
*/
public static function is_workspace ($code) {
global $db;
$code = $db->escape($code);
$sql = "SELECT count(*) FROM " . TABLE_WORKSPACES
. " WHERE workspace_code = '$code'";
if (!$result = $db->query($sql)) {
ErrorHandling::messageAndDie(SQL_ERROR,
"Can't check workspace code", '', __LINE__, __FILE__, $sql);
}
$row = $db->fetchRow($result);
return ($row[0] == 1);
}
}
diff --git a/workspaces/src/apps/documents/DocumentsApplication.php b/workspaces/src/apps/documents/DocumentsApplication.php
index ed45256..99a54e3 100644
--- a/workspaces/src/apps/documents/DocumentsApplication.php
+++ b/workspaces/src/apps/documents/DocumentsApplication.php
@@ -1,132 +1,133 @@
<?php
/**
* _, __, _, _ __, _ _, _, _
* / \ |_) (_ | | \ | /_\ |\ |
* \ / |_) , ) | |_/ | | | | \|
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*
* Documents application class
*
* @package ObsidianWorkspaces
* @subpackage HelloWorld
* @author Sébastien Santoro aka Dereckson <dereckson@espace-win.org>
* @license http://www.opensource.org/licenses/bsd-license.php BSD
* @filesource
*/
use Waystone\Workspaces\Engines\Apps\Application;
use Waystone\Workspaces\Engines\Errors\ErrorHandling;
+use Waystone\Workspaces\Engines\I18n\Language;
/**
* Documents application class
*/
class DocumentsApplication extends Application {
/**
* @var string the application name
*/
public static $name = "Documents";
/**
* Gets path to a document file
*/
private function getFilePath ($file) {
global $Config;
return $Config['Content']['Workspaces']
. DIRECTORY_SEPARATOR
. $this->context->workspace->code
. DIRECTORY_SEPARATOR
. $this->context->configuration->path
. DIRECTORY_SEPARATOR
. $file;
}
/**
* Gets documents list
*
* @return array The documents list
*/
public function getDocumentsList () {
$dir = $this->getFilePath('');
$files = scandir($dir);
$documents = [];
foreach ($files as $file) {
if (get_extension($file) == 'json') {
$documents[] = get_filename($file);
}
}
return $documents;
}
/**
* Gets document
*
* @param string $docId the document identifier
* @return stdClass the document JSON representation
*/
public function getDocument ($docId) {
$file = $this->getFilePath($docId . '.json');
$data = file_get_contents($file);
$data = json_decode($data);
if ($data === null) {
ErrorHandling::messageAndDie(
GENERAL_ERROR,
json_last_error_msg(),
"Can't parse JSON to load $docId"
);
}
if (property_exists($data, "type")) {
$data->type = DocumentType::from(ucfirst($data->type));
}
$mapper = new JsonMapper();
return $mapper->map($data, new Document);
}
public static function getDocumentType (DocumentType $type) : string {
$key = 'DocumentType' . $type->value;
return Language::get($key);
}
/**
* Handles controller request
*/
public function handleRequest () {
//Reference to URL fragments and Smarty engine
$url = $this->context->url;
$smarty = $this->context->templateEngine;
//Gets resources for HTML output
if (count($url) == 1) {
//Prints the list of the documents
$documents = $this->getDocumentsList();
$smarty->assign('documents', $documents);
$smarty->assign(
"docs_url",
get_url($this->context->workspace->code, "docs")
);
$template = 'documents_list.tpl';
} else {
//Prints a document
$docId = $url[1];
$document = $this->getDocument($docId);
$smarty->assign('documentId', $docId);
$smarty->assign('documentType', self::getDocumentType($document->type));
$smarty->assign('document', $document);
$template = 'documents_view.tpl';
}
//Serves header
$smarty->assign('PAGE_TITLE', "Documents");
HeaderController::run($this->context);
//Serves body
$smarty->display('apps/documents/' . $template);
//Serves footer
FooterController::run($this->context);
}
}
diff --git a/workspaces/src/controllers/help.php b/workspaces/src/controllers/help.php
index 79792dd..bb33eb7 100755
--- a/workspaces/src/controllers/help.php
+++ b/workspaces/src/controllers/help.php
@@ -1,43 +1,45 @@
<?php
/**
* _, __, _, _ __, _ _, _, _
* / \ |_) (_ | | \ | /_\ |\ |
* \ / |_) , ) | |_/ | | | | \|
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*
* Controller
*
* @package ObsidianWorkspaces
* @subpackage Controllers
* @author Sébastien Santoro aka Dereckson <dereckson@espace-win.org>
* @license http://www.opensource.org/licenses/bsd-license.php BSD
* @filesource
*
*/
//
// HTML output
//
+use Waystone\Workspaces\Engines\I18n\Language;
+
$file = $Config['Content']['Help'] . DIRECTORY_SEPARATOR;
$file .= (count($context->url) > 1) ? $context->url[1] : 'index';
$file .= '.html';
if (!file_exists($file)) {
ErrorPageController::show($context, 404);
exit;
}
//Header
$context->templateEngine->assign('PAGE_TITLE', Language::get('Help'));
$context->templateEngine->assign('help_url', get_url("help"));
$context->templateEngine->assign('controller_custom_nav', 'nav_help.tpl');
HeaderController::run($context);
//Help page
$context->templateEngine->assign('help_file', $file);
$context->templateEngine->display('help.tpl');
//Footer
FooterController::run($context);
diff --git a/workspaces/src/controllers/home.php b/workspaces/src/controllers/home.php
index caf6243..a97dbdb 100644
--- a/workspaces/src/controllers/home.php
+++ b/workspaces/src/controllers/home.php
@@ -1,86 +1,87 @@
<?php
/**
* _, __, _, _ __, _ _, _, _
* / \ |_) (_ | | \ | /_\ |\ |
* \ / |_) , ) | |_/ | | | | \|
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*
* Controller for homepage content
*
* @package ObsidianWorkspaces
* @subpackage Controllers
* @author Sébastien Santoro aka Dereckson <dereckson@espace-win.org>
* @license http://www.opensource.org/licenses/bsd-license.php BSD
* @filesource
*
*/
use Waystone\Workspaces\Engines\Controller\Controller;
+use Waystone\Workspaces\Engines\I18n\Language;
/**
* Homepage controller
*/
class HomepageController extends Controller {
/**
* Handles controller request
*/
public function handleRequest () {
$smarty = $this->context->templateEngine;
$workspace = $this->context->workspace;
if ($workspace == null) {
//We need a list of workspaces to allow user
//to select the one he wishes to access.
//The header has already grabbed it for us.
if (array_key_exists('workspaces', $smarty->tpl_vars)) {
$workspaces = $smarty->tpl_vars['workspaces']->value;
} else {
$workspaces = $this->context->user->get_workspaces();
$smarty->assign('workspaces', $workspaces);
}
switch (count($workspaces)) {
case 0:
//No workspace error message
$smarty->assign('PAGE_TITLE', Language::get("Home"));
$template = "home_noworkspace.tpl";
break;
case 1:
//Autoselect workspace
$this->context->workspace = $workspaces[0];
$workspace = $workspaces[0];
$this->context->workspace->loadConfiguration($this->context);
break;
default:
//Select workspace template
$smarty->assign('PAGE_TITLE', Language::get("PickWorkspace"));
$template = "home_pickworkspace.tpl";
}
}
if ($workspace != null) {
$smarty->assign('PAGE_TITLE', $workspace->name);
$template = "home_workspace.tpl";
if (count($workspace->configuration->disclaimers)) {
$disclaimers = [];
foreach ($workspace->configuration->disclaimers as $disclaimer) {
$disclaimers[] = Disclaimer::get($disclaimer);
}
$smarty->assign('disclaimers', $disclaimers);
}
}
//Serves header
HeaderController::run($this->context);
//Serves relevant template
$smarty->display($template);
//Serves footer
FooterController::run($this->context);
}
}
diff --git a/workspaces/src/includes/autoload.php b/workspaces/src/includes/autoload.php
index e285a2f..92e632e 100644
--- a/workspaces/src/includes/autoload.php
+++ b/workspaces/src/includes/autoload.php
@@ -1,65 +1,61 @@
<?php
/**
* _, __, _, _ __, _ _, _, _
* / \ |_) (_ | | \ | /_\ |\ |
* \ / |_) , ) | |_/ | | | | \|
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*
* Classes and interfaces auto loader
*
* @package ObsidianWorkspaces
* @filesource
*/
/**
* This SPL autoloader method is called when a class or an interface can't be loaded.
*/
function obsidian_autoload ($name) {
$dir = dirname(__DIR__);
///
/// Applications
///
if ($name == 'Document') { require $dir . '/apps/documents/Document.php'; return true; }
if ($name == 'DocumentsApplication') { require $dir . '/apps/documents/DocumentsApplication.php'; return true; }
if ($name == 'DocumentsApplicationConfiguration') { require $dir . '/apps/documents/DocumentsApplicationConfiguration.php'; return true; }
if ($name == 'DocumentType') { require $dir . '/apps/documents/DocumentType.php'; return true; }
if ($name == 'HelloWorldApplication') { require $dir . '/apps/helloworld/HelloWorldApplication.php'; return true; }
if ($name == 'MediaWikiMirrorApplication') { require $dir . '/apps/mediawikimirror/MediaWikiMirrorApplication.php'; return true; }
if ($name == 'MediaWikiMirrorApplicationConfiguration') { require $dir . '/apps/mediawikimirror/MediaWikiMirrorApplicationConfiguration.php'; return true; }
if ($name == 'StaticContentApplication') { require $dir . '/apps/staticcontent/StaticContentApplication.php'; return true; }
if ($name == 'StaticContentApplicationConfiguration') { require $dir . '/apps/staticcontent/StaticContentApplicationConfiguration.php'; return true; }
///
/// Core controllers
///
if ($name == 'ErrorPageController') { require $dir . '/controllers/errorpage.php'; return true; }
if ($name == 'FooterController') { require $dir . '/controllers/footer.php'; return true; }
if ($name == 'HeaderController') { require $dir . '/controllers/header.php'; return true; }
if ($name == 'HomepageController') { require $dir . '/controllers/home.php'; return true; }
///
/// Keruald and Obsidian Workspaces libraries
///
if ($name == 'Cache') { require $dir . '/includes/cache/cache.php'; return true; }
if ($name == 'CacheMemcached') { require $dir . '/includes/cache/memcached.php'; return true; }
if ($name == 'CacheVoid') { require $dir . '/includes/cache/void.php'; return true; }
- if ($name == 'Language') { require $dir . '/includes/i18n/Language.php'; return true; }
- if ($name == 'Message') { require $dir . '/includes/i18n/Message.php'; return true; }
- if ($name == 'TextFileMessage') { require $dir . '/includes/i18n/TextFileMessage.php'; return true; }
-
if ($name == 'Disclaimer') { require $dir . '/includes/objects/Disclaimer.php'; return true; }
if ($name == 'UserGroup') { require $dir . '/includes/objects/usergroup.php'; return true; }
return false;
}
spl_autoload_register('obsidian_autoload');
diff --git a/workspaces/src/includes/objects/Disclaimer.php b/workspaces/src/includes/objects/Disclaimer.php
index 6ddec2a..04f1298 100644
--- a/workspaces/src/includes/objects/Disclaimer.php
+++ b/workspaces/src/includes/objects/Disclaimer.php
@@ -1,58 +1,61 @@
<?php
/**
* _, __, _, _ __, _ _, _, _
* / \ |_) (_ | | \ | /_\ |\ |
* \ / |_) , ) | |_/ | | | | \|
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*
* Disclaimer class
*
* @package ObsidianWorkspaces
* @subpackage Model
* @author Sébastien Santoro aka Dereckson <dereckson@espace-win.org>
* @license http://www.opensource.org/licenses/bsd-license.php BSD
* @filesource
*
*/
+use Waystone\Workspaces\Engines\I18n\Language;
+use Waystone\Workspaces\Engines\I18n\TextFileMessage;
+
/**
* Disclaimer class
*/
class Disclaimer {
public $id;
public $title;
public $text;
public function __construct ($id) {
$this->id = $id;
}
public static function get ($id) {
global $Config;
$instance = new Disclaimer($id);
try {
$message = new TextFileMessage(
$Config['Content']['Disclaimers'],
$id
);
$data = (string)$message;
$pos = strpos($data, "\n");
if ($pos !== false) {
$instance->title = substr($data, 0, $pos);
$instance->text = trim(substr($data, $pos));
} else {
$instance->title = ucfirst($id);
$instance->text = $data;
}
} catch (Exception $ex) {
$instance->title = ucfirst($id);
$instance->text = Language::get('NoSuchDisclaimer');
}
return $instance;
}
}
\ No newline at end of file
diff --git a/workspaces/src/index.php b/workspaces/src/index.php
index 252000e..79e6e82 100755
--- a/workspaces/src/index.php
+++ b/workspaces/src/index.php
@@ -1,107 +1,108 @@
<?php
global $Config;
/**
* _, __, _, _ __, _ _, _, _
* / \ |_) (_ | | \ | /_\ |\ |
* \ / |_) , ) | |_/ | | | | \|
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*
* Main web application entry point
*
* @package ObsidianWorkspaces
* @subpackage Controllers
* @author Sébastien Santoro aka Dereckson <dereckson@espace-win.org>
* @license http://www.opensource.org/licenses/bsd-license.php BSD
* @filesource
*
*/
use Waystone\Workspaces\Engines\Apps\ApplicationContext;
use Waystone\Workspaces\Engines\Errors\ErrorHandling;
use Waystone\Workspaces\Engines\Framework\Application;
+use Waystone\Workspaces\Engines\I18n\Language;
use Waystone\Workspaces\Engines\Workspaces\Workspace;
////////////////////////////////////////////////////////////////////////////////
///
/// Initialization
///
require_once("includes/autoload_vendor.php");
Application::init();
include('includes/core.php');
//Prepares the site context
$context = Application::getContext($Config);
$db = $context->db;
//Loads language files
Language::initialize();
Language::load($context)->configLoad('core.conf');
//Loads workspace
try {
if (Workspace::is_workspace($context->url[0])) {
$context->workspace = Workspace::fromCode(array_shift($context->url));
$context->workspace->loadConfiguration($context);
}
} catch (Exception $ex) {
ErrorHandling::messageAndDie(GENERAL_ERROR, $ex->getMessage(), Language::get('CantLoadWorkspace'));
}
//Handles login or logout
include("includes/login.php");
//Gets current user information
$context->user = $context->session->get_logged_user();
////////////////////////////////////////////////////////////////////////////////
///
/// Serves the requested page
///
//If the user isn't logged in (is anonymous), prints login/invite page & dies.
if ($context->user->id == ANONYMOUS_USER) {
//Anonymous user
include('controllers/anonymous.php');
exit;
}
//If a workspace has been selected, ensures the current logged in user has access to it.
if ($context->workspace && !$context->workspace->userCanAccess($context->user)) {
ErrorHandling::messageAndDie(HACK_ERROR, "You don't have access to this workspace.", 'Access control');
}
$controller = count($context->url) > 0 ? $context->url[0] : '';
switch ($controller) {
case '':
//Calls homepage controller
HomepageController::run($context);
break;
case 'help':
case 'reports':
//Calls requested controller
include("controllers/$controller.php");
break;
default:
//Current workspace application controller?
if ($context->workspace != null) {
$workspaceConfig = $context->workspace->configuration;
$applicationConfiguration = null;
if ($workspaceConfig != null && $workspaceConfig->hasControllerBind($controller, $applicationConfiguration)) {
//Runs controller
$controllerClass = $applicationConfiguration->name;
$appContext = ApplicationContext::loadFromContext($context, $applicationConfiguration);
$controllerClass::run($appContext);
break;
}
}
//Not a workspace, nor a controller toponym
ErrorPageController::show($context, 404);
exit;
}

File Metadata

Mime Type
text/x-diff
Expires
Wed, Mar 18, 13:13 (1 d, 2 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3539960
Default Alt Text
(57 KB)

Event Timeline