Page Menu
Home
DevCentral
Search
Configure Global Search
Log In
Files
F3773692
D1442.id3764.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
36 KB
Referenced Files
None
Subscribers
None
D1442.id3764.diff
View Options
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
Details
Attached
Mime Type
text/plain
Expires
Mon, Nov 25, 08:08 (20 h, 52 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2261941
Default Alt Text
D1442.id3764.diff (36 KB)
Attached To
Mode
D1442: Provide configuration for wikis
Attached
Detach File
Event Timeline
Log In to Comment