Page MenuHomeDevCentral

D1442.diff
No OneTemporary

D1442.diff

diff --git a/.env.example b/.env.example
new file mode 100644
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,5 @@
+MEDIAWIKI_ENTRY_POINT="/srv/mediawiki/index.php"
+MEDIAWIKI_SECRET_KEY="generate a 64 characters hexadecimal key"
+DB_HOST="localhost"
+DB_USER="root"
+DB_PASS=""
diff --git a/.gitignore b/.gitignore
new file mode 100644
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+# Composer
+/vendor/
+composer.lock
+
+# DotEnv
+.env
diff --git a/LocalSettings.php b/LocalSettings.php
new file mode 100644
--- /dev/null
+++ b/LocalSettings.php
@@ -0,0 +1,91 @@
+<?php
+
+# -------------------------------------------------------------
+# Configuration for Nasqueron MediaWiki SaaS
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Project: Nasqueron
+# Created: 2018-03-19
+# License: Trivial work, not eligible to copyright
+# -------------------------------------------------------------
+
+use Nasqueron\SAAS\MediaWiki\Configuration\CommonSettings;
+use Nasqueron\SAAS\MediaWiki\Service;
+use Nasqueron\SAAS\MediaWiki\Environment;
+
+require_once __DIR__ . '/vendor/autoload.php';
+
+# -------------------------------------------------------------
+# Load service and configuration
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+Environment::load();
+$service = Service::preload();
+$service->run();
+$serviceConfiguration = $service->getConfiguration();
+
+$wgLocalDatabases = $serviceConfiguration->getLocalDatabases();
+
+$wgConf->wikis = $wgLocalDatabases;
+$wgConf->localVHosts = [ 'localhost' ];
+$wgConf->settings = $serviceConfiguration->getSettings();
+$wgConf->suffixes = $wgLocalDatabases;
+$wgConf->siteParamsCallback = 'Nasqueron\SAAS\MediaWiki\Hooks::onSiteParameters';
+
+# -------------------------------------------------------------
+# Database settings
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+$wgDBname = $serviceConfiguration->getSelectedDatabase();
+
+$wgDBserver = $_ENV['DB_HOST'];
+$wgDBuser = $_ENV['DB_USER'];
+$wgDBpassword = $_ENV['DB_PASS'];
+
+# -------------------------------------------------------------
+# Fixes needed before extracting settings
+#
+# :: Fix executable paths, by default Linux-centric
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+if (Environment::isBSD()) {
+ CommonSettings::fixExecutablePaths($wgConf->settings, "/usr/local/bin");
+}
+
+# -------------------------------------------------------------
+# Populate the global spaces with settings
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+$wgConf->extractAllGlobals( $wgDBname );
+
+# -------------------------------------------------------------
+# Fixes needed after extractings ettings
+#
+# :: Group permissions
+# :: Settings with common configuration
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+CommonSettings::fixGroupPermissions($saasExtraGroupPermissions);
+
+if ($saasUseScribunto) {
+ CommonSettings::enableScribunto();
+}
+
+CommonSettings::enableLog();
+
+# -------------------------------------------------------------
+# Paths and settings defined in enviroment
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+$wgScript = "{$wgScriptPath}/index.php";
+$wgSecretKey = $_ENV["MEDIAWIKI_SECRET_KEY"];
+
+$wgCacheDirectory = $serviceConfiguration->getCacheDir();
+$wgFileCacheDirectory = $wgCacheDirectory . "/pages";
+$wgUploadDirectory = $serviceConfiguration->getDataStoreDir() . "/images";
+
+# -------------------------------------------------------------
+# Load extensions and skins
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+wfLoadExtensions($serviceConfiguration->getResources('Extension'));
+wfLoadSkins($serviceConfiguration->getResources('Skin'));
diff --git a/bin/saas-mediawiki-get-host.php b/bin/saas-mediawiki-get-host.php
new file mode 100755
--- /dev/null
+++ b/bin/saas-mediawiki-get-host.php
@@ -0,0 +1,9 @@
+#!/usr/bin/env php
+<?php
+
+use Nasqueron\SAAS\MediaWiki\Utilities\GetHost;
+
+require_once __DIR__ . '/../vendor/autoload.php';
+
+$exitCode = GetHost::run($argc, $argv);
+exit($exitCode);
diff --git a/composer.json b/composer.json
new file mode 100644
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,29 @@
+{
+ "name": "nasqueron/saas-mediawiki",
+ "description": "SaaS configuration entry point for MediaWiki",
+ "keywords": [
+ "nasqueron",
+ "SAAS",
+ "mediawiki",
+ "farm"
+ ],
+ "type": "project",
+ "license": "BSD-2-Clause",
+ "authors": [
+ {
+ "name": "Sébastien Santoro",
+ "email": "dereckson@espace-win.org"
+ }
+ ],
+ "require": {
+ "vlucas/phpdotenv": "dev-master",
+ "nasqueron/saas-service": "^0.0.1"
+ },
+ "autoload": {
+ "psr-4": {
+ "Nasqueron\\SAAS\\MediaWiki\\": "src/",
+ "Nasqueron\\SAAS\\MediaWiki\\Configuration\\": "config/",
+ "Nasqueron\\SAAS\\MediaWiki\\Tests\\": "tests/"
+ }
+ }
+}
diff --git a/config/CommonSettings.php b/config/CommonSettings.php
new file mode 100644
--- /dev/null
+++ b/config/CommonSettings.php
@@ -0,0 +1,105 @@
+<?php
+
+namespace Nasqueron\SAAS\MediaWiki\Configuration;
+
+use Nasqueron\SAAS\ConfigurationException;
+use Nasqueron\SAAS\MediaWiki\WithExecutablesPathsFix;
+use Nasqueron\SAAS\MediaWiki\WithLog;
+use Nasqueron\SAAS\MediaWiki\WithScribunto;
+
+class CommonSettings {
+
+ use WithExecutablesPathsFix;
+ use WithScribunto;
+ use WithLog;
+
+ ///
+ /// Individual set of settings
+ ///
+
+ /**
+ * @throws ConfigurationException
+ */
+ private static function getRightsSettings (array $licenses) : array {
+ $settings = [];
+ foreach ($licenses as $key => $license) {
+ switch ($license) {
+ case 'CC-BY 4.0':
+ $settings['wgRightsUrl'][$key] = 'http://creativecommons.org/licenses/by/4.0/';
+ $settings['wgRightsText'][$key] = 'Creative Commons Attribution 4.0 International License';
+ $settings['wgRightsIcon'][$key] = 'https://i.creativecommons.org/l/by/4.0/88x31.png';
+ break;
+
+ default:
+ throw new ConfigurationException("License unknown: $license");
+ }
+ }
+ return $settings;
+ }
+
+ private static function getUrlSettings (array $schemes) : array {
+ $settings = [];
+ foreach ($schemes as $key => $scheme) {
+ switch ($scheme) {
+ case 'root':
+ $settings['wgArticlePath'][$key] = '/$1';
+ $settings['wgScriptPath'][$key] = '';
+ break;
+
+ case 'wiki':
+ $settings['wgArticlePath'][$key] = '/wiki/$1';
+ $settings['wgScriptPath'][$key] = '/w';
+ break;
+
+ default:
+ throw new ConfigurationException(
+ "Unknown URL scheme: $scheme"
+ );
+ }
+ }
+ return $settings;
+ }
+
+ /**
+ * Replace default permissions by custom permissions.
+ */
+ public static function fixGroupPermissions ($groupPermissionsToOverride) {
+ // Code from WMF wmf-config/CommonSettings.php (groupOverrides)
+
+ // PHP array merge keep value already defined, but here we want to
+ // override those values by the new ones.
+
+ global $wgGroupPermissions;
+
+ foreach ($groupPermissionsToOverride as $group => $permissions) {
+ if (!array_key_exists( $group, $wgGroupPermissions)) {
+ $wgGroupPermissions[$group] = [];
+ }
+
+ $wgGroupPermissions[$group] = $permissions
+ + $wgGroupPermissions[$group];
+ }
+ }
+
+ ///
+ /// Helper methods to apply those settings fix
+ ///
+
+ /**
+ * @throws ConfigurationException
+ */
+ public static function mapSettings (array &$settings) : void {
+ $settings += self::getMappedSettings($settings);
+ }
+
+ /**
+ * @throws ConfigurationException
+ */
+ public static function getMappedSettings (array $settings) : array {
+ $mappedSettings = [];
+ $mappedSettings += self::getRightsSettings($settings['saasLicense']);
+ $mappedSettings += self::getUrlSettings($settings['saasUrlScheme']);
+ return $mappedSettings;
+ }
+
+}
diff --git a/config/Instances.php b/config/Instances.php
new file mode 100644
--- /dev/null
+++ b/config/Instances.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Nasqueron\SAAS\MediaWiki\Configuration;
+
+use Nasqueron\SAAS\MediaWiki\InstancesRepository;
+
+class Instances extends InstancesRepository {
+
+ static public function getList () : array {
+ return [
+ // Format: => database name
+
+ "agora.nasqueron.org" => "nasqueron_wiki",
+ "arsmagica.espace-win.org" => "arsmagica",
+ "utopia.espace-win.org" => "utopia",
+ "www.wolfplex.org" => "wolfplexdb",
+ ];
+ }
+
+ static public function getAliases () : array {
+ return [
+ // Format: Database => [ hosts ]
+
+ "wolfplexdb" => [
+ "www.wolfplex.be",
+ "wiki.wolfplex.org",
+ "wiki.wolfplex.be",
+ ]
+ ];
+ }
+
+}
diff --git a/config/MappableSettings.php b/config/MappableSettings.php
new file mode 100644
--- /dev/null
+++ b/config/MappableSettings.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Nasqueron\SAAS\MediaWiki\Configuration;
+
+///
+/// Temporary hack to get clean configuration.
+///
+/// Plan is to deploy the MediaWiki SaaS on a dedicated node.
+///
+/// Meanwhile, as we share the main Nasqueron MySQL database,
+/// with an history of databases going back to 2001, we need
+/// to map nicely named site key to actual databases names.
+
+abstract class MappableSettings {
+
+ abstract static public function getDatabaseMap() : array;
+ abstract static public function getMappedSettings() : array;
+
+ static public function getSettings () : array {
+ $settings = [];
+ foreach (static::getMappedSettings() as $setting => $values) {
+ $settings[$setting] = self::mapDatabases($values);
+ }
+ return $settings;
+ }
+
+ static private function mapDatabases ($items) {
+ $setting = [];
+ foreach ($items as $key => $value) {
+ $mappedKey = self::mapDatabase($key);
+ $setting[$mappedKey] = $value;
+ }
+ return $setting;
+ }
+
+ static private function mapDatabase ($key) {
+ foreach (static::getDatabaseMap() as $canonical => $actual) {
+ if ($key === $canonical) {
+ return $actual;
+ }
+ }
+
+ return $key;
+ }
+
+}
diff --git a/config/Settings.php b/config/Settings.php
new file mode 100644
--- /dev/null
+++ b/config/Settings.php
@@ -0,0 +1,170 @@
+<?php
+
+namespace Nasqueron\SAAS\MediaWiki\Configuration;
+
+class Settings extends MappableSettings {
+
+ static public function getDatabaseMap () : array {
+ return [
+ 'agora' => 'nasqueron_wiki',
+ 'wolfplex' => 'wolfplexdb',
+ ];
+ }
+
+ static public function getMappedSettings () : array {
+ return [
+
+ ///
+ /// MediaWiki Core
+ ///
+
+ 'wgDBprefix' => [
+ 'default' => '',
+
+ // Legacy installations
+ 'arsmagica' => 'arsm_',
+ 'utopia' => 'wiki_',
+ 'wolfplex' => 'mw_', // shared database
+ ],
+
+ 'wgDefaultSkin' => [
+ 'default' => 'vector',
+ 'agora' => 'timeless',
+ // TODO: find utopia skin
+ ],
+
+ 'wgEnableCreativeCommonsRdf' => [
+ 'default' => true,
+ ],
+
+ 'wgEnableDublinCoreRdf' => [
+ 'default' => true,
+ ],
+
+ 'wgEnableUploads' => [
+ 'default' => false,
+ 'agora' => true,
+ 'wolfplex' => true,
+ ],
+
+ 'wgExtraNamespaces' => [
+ 'wolfplex' => [
+ 320 => 'Bulletin',
+ 322 => 'Event',
+ ]
+ ],
+
+ 'wgUseInstantCommons' => [
+ 'default' => true,
+ ],
+
+ 'wgLanguageCode' => [
+ 'default' => 'en',
+ 'arsmagica' => 'fr',
+ 'utopia' => 'fr',
+ 'wolfplex' => 'fr',
+ ],
+
+ 'wgLogo' => [
+ 'default' => '/images/b/bc/Wiki.png',
+ 'agora' => 'https://assets.nasqueron.org/logos/logo-main-133px.png',
+ 'wolfplex' => '/img/logo135.png',
+ ],
+
+ 'wgMetaNamespace' => [
+ 'default' => false,
+ 'utopia' => 'Utopia',
+ 'wolfplex' => 'Wolfplex',
+ ],
+
+ 'wgNamespacesWithSubpages' => [
+ 'wolfplex' => [
+ NS_MAIN => true,
+ ],
+ 'agora' => [
+ NS_MAIN => true,
+ ],
+ ],
+
+ 'wgPasswordSender' => [
+ 'default' => 'mediawiki-saas-no-reply@nasqueron.org',
+ ],
+
+ 'wgSitename' => [
+ 'default' => 'Wiki',
+ 'agora' => 'Nasqueron Agora',
+ 'arsmagica' => 'Ars Magica',
+ 'utopia' => 'Utopia',
+ 'wolfplex' => 'Wolfplex',
+ ],
+
+ 'wgUseFileCache' => [
+ 'default' => false,
+ ],
+
+ 'wgUseGzip' => [
+ 'default' => true,
+ ],
+
+ 'wgUseImageMagick' => [
+ 'default' => true,
+ ],
+
+ 'wgUsePathInfo' => [
+ // Per https://www.dereckson.be/blog/2013/10/24/mediawiki-nginx-configuration-file/
+ 'default' => true,
+ ],
+
+ 'saasExtraGroupPermissions' => [
+ 'default' => [
+ '*' => [
+ 'edit' => false,
+ 'createaccount' => false,
+ 'foo' => true,
+ ],
+ ]
+ ],
+
+ 'saasLicense' => [
+ 'default' => 'CC-BY 4.0',
+ ],
+
+ 'saasUrlScheme' => [
+ 'default' => 'wiki',
+ 'agora' => 'root',
+ 'arsmagica' => 'root',
+ ],
+
+ ///
+ /// Skins and extensions
+ ///
+
+ 'saasUseExtensionCite' => [
+ 'default' => true,
+ ],
+
+ 'saasUseExtensionParserFunctions' => [
+ 'default' => true,
+ ],
+
+ 'saasUseSkinMonoBook' => [
+ 'default' => true,
+ ],
+
+ 'saasUseSkinVector' => [
+ 'default' => true,
+ ],
+
+ 'saasUseSkinTimeless' => [
+ 'default' => true,
+ ],
+
+ 'saasUseScribunto' => [
+ 'default' => false,
+ 'agora' => true,
+ 'wolfplex' => true,
+ ],
+ ];
+ }
+
+}
diff --git a/index.php b/index.php
new file mode 100644
--- /dev/null
+++ b/index.php
@@ -0,0 +1,21 @@
+<?php
+
+use Nasqueron\SAAS\MediaWiki\Service;
+use Nasqueron\SAAS\MediaWiki\Environment;
+
+/**
+ * The Composer autoloader is used to load required libraries.
+ * This service follows PSR-4 conventions.
+ *
+ * The environment is read from environment variables or an .env file.
+ *
+ * This entry point check the host and call MediaWiki if it exists.
+ * If not, it serves a 404.
+ */
+
+require_once __DIR__ . '/vendor/autoload.php';
+
+Environment::load();
+$service = Service::preload();
+
+require $service->getEntryPoint();
diff --git a/src/Configuration.php b/src/Configuration.php
new file mode 100644
--- /dev/null
+++ b/src/Configuration.php
@@ -0,0 +1,76 @@
+<?php
+
+namespace Nasqueron\SAAS\MediaWiki;
+
+use Nasqueron\SAAS\MediaWiki\Configuration\CommonSettings;
+use Nasqueron\SAAS\MediaWiki\Configuration\Instances;
+use Nasqueron\SAAS\MediaWiki\Configuration\Settings;
+
+class Configuration {
+
+ /**
+ * @var string
+ */
+ private $host;
+
+ public function __construct (string $host) {
+ $this->host = $host;
+ }
+
+ public function getLocalDatabases () : array {
+ return array_values(Instances::getList());
+ }
+
+ public function getSettings () : array {
+ // wg… keys
+ $settings = Settings::getSettings();
+
+ // saas… → wg… keys
+ CommonSettings::mapSettings($settings);
+
+ return $settings;
+ }
+
+ public function getResources (string $type) : array {
+ // saasUse<type><resource name>
+ // e.g. saasUseExtensionCite or saasUseSkinTimeless
+
+ $resources = [];
+
+ $prefix = "saasUse" . $type;
+ $len = strlen($prefix);
+ foreach ($GLOBALS as $key => $value) {
+ if (substr($key, 0, $len) === $prefix) {
+ $resources[] = substr($key, $len);
+ }
+ }
+
+ return $resources;
+ }
+
+ public function getCacheDir () {
+ $cacheRootDir = Environment::get("CACHE_DIRECTORY", "/var/cache/mediawiki");
+ return $cacheRootDir . '/' . $this->host;
+ }
+
+ public function getDataStoreDir () {
+ $dataStoreDir = Environment::get("DATASTORE_DIRECTORY", "/var/dataroot");
+ return $dataStoreDir . '/' . $this->host;
+ }
+
+ /**
+ * @throws \Nasqueron\SAAS\InstanceNotFoundException
+ */
+ public function getSelectedDatabase () {
+ return Instances::getDatabaseFromHost($this->host);
+ }
+
+ ///
+ /// Helper methods
+ ///
+
+ public static function isSelectedWiki (string $wiki, string $suffix) : bool {
+ return substr($wiki, -strlen($suffix)) == $suffix;
+ }
+
+}
diff --git a/src/Environment.php b/src/Environment.php
new file mode 100644
--- /dev/null
+++ b/src/Environment.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace Nasqueron\SAAS\MediaWiki;
+
+use Dotenv\Dotenv;
+use Dotenv\Exception\ValidationException;
+
+class Environment {
+
+ /**
+ * @var bool
+ */
+ static private $isLoaded = false;
+
+ /**
+ * Loads the environment, if it hasn't been loaded before.
+ */
+ public static function load () : void {
+ if (!self::$isLoaded) {
+ $directory = self::getDirectory();
+ $dotenv = new Dotenv($directory);
+ $dotenv->safeLoad();
+ try {
+ $dotenv->required(self::getRequiredVariables());
+ } catch (ValidationException $exception) {
+ Service::serveInternalErrorResponse($exception);
+ }
+ self::$isLoaded = true;
+ }
+ }
+
+ public static function isLoaded () : bool {
+ return self::$isLoaded;
+ }
+
+ public static function get ($variableName, $defaultValue = "") : string {
+ return $_ENV[$variableName] ?? $defaultValue;
+ }
+
+ private static function getDirectory () : string {
+ return dirname(__DIR__);
+ }
+
+ private static function getRequiredVariables () : array {
+ return [
+ 'MEDIAWIKI_ENTRY_POINT',
+ 'MEDIAWIKI_SECRET_KEY',
+ 'DB_HOST',
+ 'DB_USER',
+ 'DB_PASS',
+ ];
+ }
+
+ public static function isBSD () : bool {
+ static $system;
+ $system = php_uname("s");
+ return $system == "FreeBSD" || $system == "OpenBSD" || $system == "NetBSD";
+ }
+}
diff --git a/src/Hooks.php b/src/Hooks.php
new file mode 100644
--- /dev/null
+++ b/src/Hooks.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Nasqueron\SAAS\MediaWiki;
+
+class Hooks {
+
+ public static function onSiteParameters ($conf, $wiki) {
+ $site = null;
+ $lang = null;
+
+ foreach ($conf->suffixes as $suffix) {
+ if (Configuration::isSelectedWiki($wiki, $suffix)) {
+ $site = $suffix;
+ $lang = substr( $wiki, 0, -strlen( $suffix ) );
+ break;
+ }
+ }
+
+ return [
+ 'suffix' => $site,
+ 'lang' => $lang,
+ 'params' => [
+ 'lang' => $lang,
+ 'site' => $site,
+ 'wiki' => $wiki,
+ ],
+ 'tags' => [],
+ ];
+ }
+
+}
diff --git a/src/InstancesRepository.php b/src/InstancesRepository.php
new file mode 100644
--- /dev/null
+++ b/src/InstancesRepository.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace Nasqueron\SAAS\MediaWiki;
+
+use Nasqueron\SAAS\InstanceNotFoundException;
+
+abstract class InstancesRepository {
+
+ ///
+ /// Repository data methods
+ ///
+
+ abstract static public function getList () : array;
+ abstract static public function getAliases () : array;
+
+ ///
+ /// Helper methods
+ ///
+
+ /**
+ * @throws InstanceNotFoundException
+ */
+ public static function getCanonicalHost (string $host) : string {
+ $canonicalList = static::getList();
+
+ // Case 1 - the host is canonical
+ if (array_key_exists($host, $canonicalList)) {
+ return $host;
+ }
+
+ // Case 2 - the host is an alias
+ foreach (static::getAliases() as $database => $vhosts) {
+ if (in_array($host, $vhosts)) {
+ return array_search($database, $canonicalList);
+ }
+ }
+
+ throw new InstanceNotFoundException($host);
+ }
+
+ /**
+ * @throws InstanceNotFoundException
+ */
+ public static function getDatabaseFromHost (string $host) : string {
+ $canonicalHost = self::getCanonicalHost($host);
+ return static::getList()[$canonicalHost];
+ }
+
+}
diff --git a/src/Service.php b/src/Service.php
new file mode 100644
--- /dev/null
+++ b/src/Service.php
@@ -0,0 +1,124 @@
+<?php
+
+namespace Nasqueron\SAAS\MediaWiki;
+
+use Nasqueron\SAAS\InstanceNotFoundException;
+use Nasqueron\SAAS\SaaSException;
+use Nasqueron\SAAS\Service as BaseService;
+use Nasqueron\SAAS\MediaWiki\Configuration\Instances;
+
+use Exception;
+
+class Service extends BaseService {
+
+ /**
+ * @var string
+ */
+ private $host;
+
+ /**
+ * @var string
+ */
+ private $canonicalHost = "";
+
+ /**
+ * @var Configuration
+ */
+ private $configuration = null;
+
+ /**
+ * @var Service
+ */
+ private static $service = null;
+
+ public function __construct (string $host = '') {
+ $this->host = $host ?: self::getServerHost();
+ }
+
+ /**
+ * @return Service
+ */
+ public static function preload()
+ {
+ if (self::$service === null) {
+
+ self::$service = new self;
+ try {
+ self::$service
+ ->handleAliveRequest()
+ ->handleNotExistingSite();
+ } catch (SaaSException $exception) {
+ self::$service->serveNotAvailableResponse();
+ }
+ }
+
+ return self::$service;
+
+ }
+
+ public function getHost () : string {
+ return $this->host;
+ }
+
+ public function run () : void {
+ $this->decorateHeaders();
+ }
+
+ public function isExisting () : bool {
+ try {
+ Instances::getDatabaseFromHost($this->host);
+ } catch (InstanceNotFoundException $exception) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private function decorateHeaders () : void {
+ header("SaaS-Host: " . $this->host);
+ header("SaaS-Canonical-Host: " . $this->getCanonicalHost());
+ header("SaaS-App: MediaWiki");
+ }
+
+ public function serveNotExistingResponse() : void {
+ header("HTTP/1.0 404 Not Found");
+
+ require __DIR__ . '/../views/404.php';
+ die;
+ }
+
+ public function serveNotAvailableResponse() : void {
+ header("HTTP/1.0 503 Service Unavailable");
+
+ require __DIR__ . '/../views/503.php';
+ die;
+ }
+
+ public static function serveInternalErrorResponse(Exception $exception) : void {
+ header("HTTP/1.0 500 Internal Error");
+
+ require __DIR__ . '/../views/500.php';
+ die;
+ }
+
+ public function getEntryPoint() : string {
+ return $_ENV['MEDIAWIKI_ENTRY_POINT'];
+ }
+
+ public function getConfiguration() : Configuration {
+ if ($this->configuration === null) {
+ $this->configuration = new Configuration($this->getCanonicalHost());
+ }
+
+ return $this->configuration;
+ }
+
+ public function getCanonicalHost () : string {
+ if ($this->canonicalHost === "") {
+ $this->canonicalHost = Instances::getCanonicalHost($this->getHost());
+ }
+
+ return $this->canonicalHost;
+ }
+
+}
diff --git a/src/Utilities/Command.php b/src/Utilities/Command.php
new file mode 100644
--- /dev/null
+++ b/src/Utilities/Command.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Nasqueron\SAAS\MediaWiki\Utilities;
+
+abstract class Command {
+
+ public const EXIT_SUCCESS = 0;
+ public const EXIT_FAILURE = 1;
+
+ /**
+ * @var int
+ */
+ protected $argc;
+
+ /**
+ * @var int
+ */
+ protected $argv;
+
+ /**
+ * @var \Nasqueron\SAAS\MediaWiki\Utilities\Display
+ */
+ protected $display;
+
+ public function __construct (int $argc, array $argv, Display $display = null) {
+ $this->argc = $argc;
+ $this->argv = $argv;
+
+ if ($display === null) {
+ $display = new OutputDisplay();
+ }
+ $this->display = $display;
+ }
+
+ public static function run (int $argc, array $argv) : int {
+ $command = new static($argc, $argv);
+ return $command->main();
+ }
+
+ public function getCommandName () : string {
+ return $this->argv[0];
+ }
+
+ ///
+ /// Methods to implement
+ ///
+
+ public abstract function main () : int;
+
+}
diff --git a/src/Utilities/Display.php b/src/Utilities/Display.php
new file mode 100644
--- /dev/null
+++ b/src/Utilities/Display.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace Nasqueron\SAAS\MediaWiki\Utilities;
+
+abstract class Display {
+
+ abstract function out (string $message) : void;
+ abstract function error (string $message) : void;
+
+}
diff --git a/src/Utilities/GetHost.php b/src/Utilities/GetHost.php
new file mode 100644
--- /dev/null
+++ b/src/Utilities/GetHost.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace Nasqueron\SAAS\MediaWiki\Utilities;
+
+use Nasqueron\SAAS\MediaWiki\Configuration\Instances;
+use Nasqueron\SAAS\MediaWiki\Configuration\Settings;
+use Nasqueron\SAAS\InstanceNotFoundException;
+
+class GetHost extends Command {
+
+ public const EXIT_HOST_NOT_FOUND = 2;
+
+ public function main () : int {
+ if ($this->argc < 2) {
+ self::usage();
+ return 1;
+ }
+
+ try {
+ $this->display->out($this->search($this->argv[1]));
+ } catch (InstanceNotFoundException $exception) {
+ $this->display->error("Host not found.");
+ return self::EXIT_HOST_NOT_FOUND;
+ }
+
+ return 0;
+ }
+
+ public function search (string $needle) : string {
+ if (self::isDomain($needle)) {
+ return Instances::getCanonicalHost($needle);
+ }
+
+ return self::getHostFromAlias($needle);
+ }
+
+ public static function isDomain ($string) : bool {
+ return strpos($string, ".") !== false;
+ }
+
+ public static function getHostFromAlias (string $alias) : string {
+ // Map alias to database
+ $map = Settings::getDatabaseMap();
+ if (isset($map[$alias])) {
+ $alias = $map[$alias];
+ }
+
+ // Get database from host
+ $host = array_search($alias, Instances::getList());
+ if ($host === false) {
+ throw new InstanceNotFoundException($alias);
+ }
+
+ return $host;
+ }
+
+ public function usage () : void {
+ $commandName = $this->getCommandName();
+ $this->display->error("Usage: $commandName <wiki host or alias>");
+ }
+}
diff --git a/src/Utilities/OutputDisplay.php b/src/Utilities/OutputDisplay.php
new file mode 100644
--- /dev/null
+++ b/src/Utilities/OutputDisplay.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Nasqueron\SAAS\MediaWiki\Utilities;
+
+class OutputDisplay extends Display {
+
+ public function out (string $message) : void {
+ echo $message, "\n";
+ }
+
+ public function error (string $message) : void {
+ fwrite(STDERR, $message . "\n");
+ }
+
+}
diff --git a/src/WithExecutablesPathsFix.php b/src/WithExecutablesPathsFix.php
new file mode 100644
--- /dev/null
+++ b/src/WithExecutablesPathsFix.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Nasqueron\SAAS\MediaWiki;
+
+trait WithExecutablesPathsFix {
+
+ /**
+ * Add settings to set binary paths for executables.
+ *
+ * By default MediaWiki hardcodes Linux-centric paths for binaries.
+ * Other OSes could store elsewhere binaries, like in /usr/local/bin
+ *
+ * @param array $settings
+ * @param string $path
+ */
+ public static function fixExecutablePaths (array &$settings, string $path) {
+ foreach (self::getExecutables() as $setting => $executable) {
+ $settings[$setting]['default'] = $path . '/' . $executable;
+ }
+ }
+
+ private static function getExecutables (): array {
+ return [
+ "wgExiftool" => "exiftool",
+ "wgExiv2Command" => "exiv2",
+ "wgGitBin" => "git",
+ "wgImageMagickConvertCommand" => "convert",
+ "wgJpegTran" => "jpegtran",
+ "wgPhpCli" => "php",
+ ];
+ }
+}
diff --git a/src/WithLog.php b/src/WithLog.php
new file mode 100644
--- /dev/null
+++ b/src/WithLog.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace Nasqueron\SAAS\MediaWiki;
+
+trait WithLog {
+
+ public static function enableLog () : void {
+ $GLOBALS['wgMWLoggerDefaultSpi'] = self::getLoggerConfiguration();
+ }
+
+ private static function getLogPath () : string {
+ return "/var/log/mediawiki/error.log";
+ }
+
+ private static function getLoggerConfiguration () : array {
+ // See https://www.mediawiki.org/wiki/Manual:MonologSpi
+ return [
+ 'class' => '\\MediaWiki\\Logger\\MonologSpi',
+ 'args' => [[
+ 'loggers' => [
+ '@default' => [
+ 'processors' => [
+ 'wiki',
+ 'psr',
+ 'web',
+ // Disable introspection if you use an handler like
+ // FingersCrossedHandler with several log entries.
+ 'introspection',
+ ],
+ 'handlers' => [
+ 'stream',
+ ],
+ ],
+ ],
+ 'processors' => [
+ 'wiki' => [
+ 'class' => '\\MediaWiki\\Logger\\Monolog\\WikiProcessor',
+ ],
+ 'psr' => [
+ 'class' => '\\Monolog\\Processor\\PsrLogMessageProcessor',
+ ],
+ 'web' => [
+ 'class' => '\\Monolog\\Processor\\WebProcessor',
+ ],
+ 'introspection' => [
+ 'class' => '\\Monolog\\Processor\\IntrospectionProcessor',
+ ],
+ ],
+ 'handlers' => [
+ 'stream' => [
+ 'class' => '\\Monolog\\Handler\\StreamHandler',
+ 'args' => [ self::getLogPath() ],
+ 'formatter' => 'line',
+ ],
+ ],
+ 'formatters' => [
+ 'line' => [
+ 'class' => '\\Monolog\\Formatter\\LineFormatter'
+ ],
+ ],
+ ]],
+ ];
+ }
+
+}
diff --git a/src/WithScribunto.php b/src/WithScribunto.php
new file mode 100644
--- /dev/null
+++ b/src/WithScribunto.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Nasqueron\SAAS\MediaWiki;
+
+trait WithScribunto {
+
+ public static function enableScribunto () : void {
+ self::registerScribuntoExtensions();
+ self::registerScribuntoConfiguration();
+ }
+
+ private function registerScribuntoConfiguration () : void {
+ foreach (self::getScribuntoConfiguration() as $setting => $value) {
+ // TODO: use ??= here when available.
+ if (!isset($GLOBALS['wg' . $setting])) {
+ $GLOBALS['wg' . $setting] = $value;
+ }
+ }
+
+ $GLOBALS['wgDefaultUserOptions']['usebetatoolbar'] = true;
+
+ if (Environment::isBSD()) {
+ $GLOBALS['wgScribuntoEngineConf']['luastandalone']['luaPath']
+ = '/usr/local/bin/lua51';
+ }
+ }
+
+ private static function registerScribuntoExtensions () : void {
+ foreach (self::getScribuntoExtensions() as $extension) {
+ $GLOBALS['saasUseExtension' . $extension] = true;
+ }
+ }
+
+ private static function getScribuntoConfiguration () : array {
+ return [
+ 'ScribuntoDefaultEngine' => 'luastandalone',
+ 'ScribuntoUseGeSHi' => true,
+ 'ScribuntoUseCodeEditor' => true,
+ ];
+ }
+
+ private static function getScribuntoExtensions () : array {
+ return [
+ 'WikiEditor',
+ 'SyntaxHighlight_GeSHi',
+ 'CodeEditor',
+ 'Scribunto',
+ ];
+ }
+
+}
diff --git a/views/404.php b/views/404.php
new file mode 100644
--- /dev/null
+++ b/views/404.php
@@ -0,0 +1,27 @@
+<!doctype html>
+<html class="no-js" lang="en">
+<head>
+ <meta charset="utf-8">
+ <meta http-equiv="x-ua-compatible" content="ie=edge">
+ <title>SaaS :: Instance not found</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+</head>
+<body>
+
+<h1>Service not found</h1>
+<p>The <?= $this->getHost() ?> instance of your service is not available.</p>
+<p>This generally means the domain name has been configured to point to
+ this server, but is not declared in the configuration.</p>
+<h2>Is this a new wiki?</h2>
+<p>This entry point serves several wikis. If you're creating a new wiki, this message means
+ you've successfully configured the DNS and the front-end web server.</p>
+<h3>What should I do now?</h3>
+<p>The next step is to declare your wiki to the <strong>saas-mediawiki</strong> repository.</p>
+<p>Create the wiki database if still not done and
+ declare the host / database pair to the <kbd>config/Instances.php</kbd> file.</p>
+<p>This repository is also where you can customize the wiki settings,
+in the <kbd>config/Settings.php</kbd> file.<br />You probably want to customize site name there.</p>
+<p>You've done all those steps? Reload php-fpm to reset caching or troubleshoot those files.</p>
+
+</body>
+</html>
diff --git a/views/500.php b/views/500.php
new file mode 100644
--- /dev/null
+++ b/views/500.php
@@ -0,0 +1,26 @@
+<!doctype html>
+<html class="no-js" lang="en">
+<head>
+ <meta charset="utf-8">
+ <meta http-equiv="x-ua-compatible" content="ie=edge">
+ <title>SaaS :: Server internal error</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+</head>
+<body>
+
+<h1>Service entry point error</h1>
+<h2>Service configuration issue</h2>
+<p>The following exception occurred at the SaaS MediaWiki entry point:</p>
+<p style="font-weight: bold; color: darkred;"><?= $exception->getMessage() ?></p>
+<h2>What I can do?</h2>
+<h3>You're a visitor?</h3>
+<p>This issue could be temporary and means the service is restarting. Try again in a few moment.</p>
+<p>If this error persists, you can report this issue on IRC FreeNode #nasqueron-ops or our
+ <a href="https://devcentral.nasqueron.org/maniphest/task/edit/form/4/">issue tracker</a><br />
+ Include the text of the exception above.
+</p>
+<h3>You're maintainer?</h3>
+<p>This application code is not a part of Mediawiki, but of the SaaS service calling it.<br />It can be found in the <a href="https://devcentral.nasqueron.org/source/saas-mediawiki/">saas-mediawiki</a> repository.</p>
+
+</body>
+</html>
diff --git a/views/503.php b/views/503.php
new file mode 100644
--- /dev/null
+++ b/views/503.php
@@ -0,0 +1,14 @@
+<!doctype html>
+<html class="no-js" lang="en">
+<head>
+ <meta charset="utf-8">
+ <meta http-equiv="x-ua-compatible" content="ie=edge">
+ <title>SaaS :: Instance not available</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+</head>
+<body>
+
+<p>The service is not available. Please retry in a few moment.</p>
+
+</body>
+</html>

File Metadata

Mime Type
text/plain
Expires
Mon, Nov 25, 05:33 (21 h, 35 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2261941
Default Alt Text
D1442.diff (36 KB)

Event Timeline