diff --git a/400_xowa/src/gplx/xowa/mediawiki/_XOMW_TODO.txt b/400_xowa/src/gplx/xowa/mediawiki/_XOMW_TODO.txt new file mode 100644 index 000000000..75014e49f --- /dev/null +++ b/400_xowa/src/gplx/xowa/mediawiki/_XOMW_TODO.txt @@ -0,0 +1,68 @@ +change XophpCallbackOwner from Object... to Object +add XomwHookMsg / XomwHookWkr +convert Database classes: https://github.com/wikimedia/mediawiki/tree/master/includes/libs/rdbms/database +convert Loadbalances classes + +## XomwHookMsg / XomwHookWkr: +strongly-typed hook b/c run can pass variables by reference and contract should be enforced + +// cur +ObjectWrapperRef pageWrapper = new ObjectWrapperRef(); +if (!XomwHooks.run("WikiPageFactory", XophpArray.New(title, pageWrapper))) { + return (XomwWikiPage)pageWrapper.Val(); +} +else { + page = (XomwWikiPage)pageWrapper.Val(); +} + +// new XO way +WikiPageFactoryHookMsg msg = new WikiPageFactoryHookMsg(title); +if (!XomwHooks.run(msg) { + return msg.Page(); +} +else { + page = msg.Page(); +} + +interface XomwHookMsg { + String Key(); + XophpArray Array(); + String Deprecation(); +} +class WikiPageFactoryHookMsg implements XomwHookMsg { + String Key() {return WikiPageFactoryHookWkr.KEY;} + public XophpArray Array(); + String Deprecation(); + + public WikiPageFactoryHookMsg(XomwTitle title) { + } + public XomwTitle Title(); + public XomwPage Page(); +} + +interface XomwHookMsg extends CallbackOwner { + String Key(); +} +class WikiPageFactoryHookWkr implements XomwHookWkr { + public WikiPageFactoryHookData() {} + String Key() {return "WikiPageFactory";} + + public Object Call(Object arg) { + XomwPageFactoryHookMsg msg = (XomwPageFactoryHookMsg)arg; + // do stuff + msg.Page = "value"; + return true; + } +} + +public static boolean run(XomwHookMsg msg) { + return run(msg.Key(), msg.Array(), msg.Deprecation()); +} +public static void register(XomwHookWkr wkr) { + handlers.Xet_by_ary(wkr.Name()).Add(wkr); +} + +// new WM way +interface WikiPageFactoryHook { + bool onWikiPageFactory(title, page) +} diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/XomwDefaultSettings.java b/400_xowa/src/gplx/xowa/mediawiki/includes/XomwDefaultSettings.java index 4a86f05db..688b4130c 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/XomwDefaultSettings.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/XomwDefaultSettings.java @@ -18,10 +18,17 @@ package gplx.xowa.mediawiki.includes; import gplx.Hash_adp; import gplx.Hash_adp_; import gplx.xowa.mediawiki.XophpArray; +import gplx.xowa.mediawiki.XophpCallback; // MW.SRC:1.33.1 // XO.NOTE:MW has these as individual global variables public class XomwDefaultSettings { + // XO:infrastructure for globals + public static XomwDefaultSettings Instance = new XomwDefaultSettings(); + public XomwDefaultSettings() { + // NOTE: all "wg..." variables must be registered here! + XomwGlobals.Instance.Add("wgServiceWiringFiles", wgServiceWiringFiles); + } // /** // * Default values for MediaWiki configuration settings. // * @@ -7516,21 +7523,21 @@ public class XomwDefaultSettings { */ public static XophpArray wgHooks = XophpArray.New(); // -///** -// * List of service wiring files to be loaded by the default instance of MediaWikiServices. -// * Each file listed here is expected to return an associative array mapping service names -// * to instantiator functions. Extensions may add wiring files to define their own services. -// * However, this cannot be used to replace existing services - use the MediaWikiServices -// * hook for that. -// * -// * @see MediaWikiServices -// * @see ServiceContainer::loadWiringFiles() for details on loading service instantiator functions. -// * @see docs/injection.txt for an overview of dependency injection in MediaWiki. -// */ -// $wgServiceWiringFiles = [ -// __DIR__ . '/ServiceWiring.php' -// ]; -// +/** + * List of service wiring files to be loaded by the default instance of MediaWikiServices. + * Each file listed here is expected to return an associative array mapping service names + * to instantiator functions. Extensions may add wiring files to define their own services. + * However, this cannot be used to replace existing services - use the MediaWikiServices + * hook for that. + * + * @see MediaWikiServices + * @see ServiceContainer::loadWiringFiles() for details on loading service instantiator functions. + * @see docs/injection.txt for an overview of dependency injection in MediaWiki. + */ + public XophpArray> wgServiceWiringFiles = XophpArray.New( + new XomwServiceWiring().GetCallbacks() + ); + ///** // * Maps jobs to their handlers; extensions // * can add to this to provide custom jobs. diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/XomwGlobals.java b/400_xowa/src/gplx/xowa/mediawiki/includes/XomwGlobals.java new file mode 100644 index 000000000..fd299353a --- /dev/null +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/XomwGlobals.java @@ -0,0 +1,28 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2020 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx.xowa.mediawiki.includes; + +import gplx.xowa.mediawiki.XophpArray; + +// Any MW-specific globals which aren't specific to a class go here +// For now, just use it for PHP $GLOBALS +public class XomwGlobals { + public static final XomwGlobals Instance = new XomwGlobals(); + public XophpArray GLOBALS = new XophpArray<>(); + public void Add(String key, Object val) { + GLOBALS.Add(key, val); + } +} diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/XomwMediaWikiServices.java b/400_xowa/src/gplx/xowa/mediawiki/includes/XomwMediaWikiServices.java index 350dfbe8b..417e7b221 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/XomwMediaWikiServices.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/XomwMediaWikiServices.java @@ -16,7 +16,13 @@ Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt package gplx.xowa.mediawiki.includes; import gplx.xowa.mediawiki.XomwEnv; +import gplx.xowa.mediawiki.XophpArray; +import gplx.xowa.mediawiki.XophpCallback; +import gplx.xowa.mediawiki.XophpString_; +import gplx.xowa.mediawiki.includes.config.XomwConfig; +import gplx.xowa.mediawiki.includes.config.XomwGlobalVarConfig; import gplx.xowa.mediawiki.includes.interwiki.XomwInterwikiLookup; +import gplx.xowa.mediawiki.includes.libs.services.XomwServiceContainer; import gplx.xowa.mediawiki.includes.title.XomwMediaWikiTitleCodec; import gplx.xowa.mediawiki.languages.XomwLanguage; @@ -25,8 +31,8 @@ import gplx.xowa.mediawiki.languages.XomwLanguage; * MediaWikiServices is the service locator for the application scope of MediaWiki. * Its implemented as a simple configurable DI container. * MediaWikiServices acts as a top level factory/registry for top level services, and builds - * the network of service objects that defines MediaWiki's application logic. - * It acts as an entry point to MediaWiki's dependency injection mechanism. + * the network of service objects that defines MediaWiki"s application logic. + * It acts as an entry point to MediaWiki"s dependency injection mechanism. * * Services are defined in the "wiring" array passed to the constructor, * or by calling defineService(). @@ -34,10 +40,10 @@ import gplx.xowa.mediawiki.languages.XomwLanguage; * @see docs/injection.txt for an overview of using dependency injection in the * MediaWiki code base. */ -public class XomwMediaWikiServices { // extends ServiceContainer +public class XomwMediaWikiServices extends XomwServiceContainer { // XO.MW.SKIP:remove global getInstance(). See XomwEnv - private final XomwMediaWikiTitleCodec titleParser; - private final XomwInterwikiLookup interwikiLookup; + private XomwMediaWikiTitleCodec titleParser; + private XomwInterwikiLookup interwikiLookup; public XomwEnv env; public XomwMediaWikiServices(XomwEnv env, XomwInterwikiLookup interwikiLookup, XomwLanguage language, byte[][] localInterwikis) { @@ -46,37 +52,37 @@ public class XomwMediaWikiServices { // extends ServiceContainer this.titleParser = new XomwMediaWikiTitleCodec(this, language, localInterwikis); } -// /** -// * @var MediaWikiServices|null -// */ -// private static $instance = null; -// -// /** -// * Returns the global default instance of the top level service locator. -// * -// * @since 1.27 -// * -// * The default instance is initialized using the service instantiator functions -// * defined in ServiceWiring.php. -// * -// * @note This should only be called by static functions! The instance returned here -// * should not be passed around! Objects that need access to a service should have -// * that service injected into the constructor, never a service locator! -// * -// * @return MediaWikiServices -// */ -// public static function getInstance() { -// if ( self::$instance === null ) { -// // NOTE: constructing GlobalVarConfig here is not particularly pretty, -// // but some information from the global scope has to be injected here, -// // even if it's just a file name or database credentials to load -// // configuration from. -// $bootstrapConfig = new GlobalVarConfig(); -// self::$instance = self::newInstance( $bootstrapConfig, 'load' ); -// } -// -// return self::$instance; -// } + /** + * @var MediaWikiServices|null + */ + private static XomwMediaWikiServices instance = null; + + /** + * Returns the global default instance of the top level service locator. + * + * @since 1.27 + * + * The default instance is initialized using the service instantiator functions + * defined in ServiceWiring.php. + * + * @note This should only be called by static functions! The instance returned here + * should not be passed around! Objects that need access to a service should have + * that service injected into the constructor, never a service locator! + * + * @return MediaWikiServices + */ + public static XomwMediaWikiServices getInstance() { + if (instance == null) { + // NOTE: constructing GlobalVarConfig here is not particularly pretty, + // but some information from the global scope has to be injected here, + // even if it"s just a file name or database credentials to load + // configuration from. + XomwGlobalVarConfig bootstrapConfig = new XomwGlobalVarConfig(); + instance = newInstance(bootstrapConfig, "load"); + } + + return instance; + } // // /** // * Replaces the global MediaWikiServices instance. @@ -87,19 +93,19 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * // * @throws MWException if called outside of PHPUnit tests. // * -// * @param MediaWikiServices $services The new MediaWikiServices object. +// * @param MediaWikiServices services The new MediaWikiServices object. // * // * @return MediaWikiServices The old MediaWikiServices object, so it can be restored later. // */ -// public static function forceGlobalInstance( MediaWikiServices $services ) { -// if ( !defined( 'MW_PHPUNIT_TEST' ) ) { -// throw new MWException( __METHOD__ . ' must not be used outside unit tests.' ); +// public static function forceGlobalInstance(MediaWikiServices services) { +// if (!defined("MW_PHPUNIT_TEST")) { +// throw new MWException(__METHOD__ . " must not be used outside unit tests."); // } // -// $old = self::getInstance(); -// self::$instance = $services; +// old = getInstance(); +// instance = services; // -// return $old; +// return old; // } // // /** @@ -127,102 +133,103 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @see resetGlobalInstance() // * @see resetBetweenTest() // * -// * @param Config|null $bootstrapConfig The Config object to be registered as the -// * 'BootstrapConfig' service. This has to contain at least the information -// * needed to set up the 'ConfigFactory' service. If not given, the bootstrap +// * @param Config|null bootstrapConfig The Config object to be registered as the +// * "BootstrapConfig" service. This has to contain at least the information +// * needed to set up the "ConfigFactory" service. If not given, the bootstrap // * config of the old instance of MediaWikiServices will be re-used. If there // * was no previous instance, a new GlobalVarConfig object will be used to // * bootstrap the services. // * -// * @param string $quick Set this to "quick" to allow expensive resources to be re-used. +// * @param string quick Set this to "quick" to allow expensive resources to be re-used. // * See SalvageableService for details. // * // * @throws MWException If called after MW_SERVICE_BOOTSTRAP_COMPLETE has been defined in // * Setup.php (unless MW_PHPUNIT_TEST or MEDIAWIKI_INSTALL or RUN_MAINTENANCE_IF_MAIN // * is defined). // */ -// public static function resetGlobalInstance( Config $bootstrapConfig = null, $quick = '' ) { -// if ( self::$instance === null ) { +// public static function resetGlobalInstance(Config bootstrapConfig = null, quick = "") { +// if (instance === null) { // // no global instance yet, nothing to reset // return; // } // -// self::failIfResetNotAllowed( __METHOD__ ); +// failIfResetNotAllowed(__METHOD__); // -// if ( $bootstrapConfig === null ) { -// $bootstrapConfig = self::$instance->getBootstrapConfig(); +// if (bootstrapConfig === null) { +// bootstrapConfig = instance.getBootstrapConfig(); // } // -// $oldInstance = self::$instance; +// oldInstance = instance; // -// self::$instance = self::newInstance( $bootstrapConfig, 'load' ); -// self::$instance->importWiring( $oldInstance, [ 'BootstrapConfig' ] ); +// instance = newInstance(bootstrapConfig, "load"); +// instance.importWiring(oldInstance, [ "BootstrapConfig" ]); // -// if ( $quick === 'quick' ) { -// self::$instance->salvage( $oldInstance ); +// if (quick === "quick") { +// instance.salvage(oldInstance); // } else { -// $oldInstance->destroy(); +// oldInstance.destroy(); // } // } // // /** -// * Salvages the state of any salvageable service instances in $other. +// * Salvages the state of any salvageable service instances in other. // * -// * @note $other will have been destroyed when salvage() returns. +// * @note other will have been destroyed when salvage() returns. // * -// * @param MediaWikiServices $other +// * @param MediaWikiServices other // */ -// private function salvage( self $other ) { -// foreach ( this.getServiceNames() as $name ) { +// private function salvage(self other) { +// foreach (this.getServiceNames() as name) { // // The service could be new in the new instance and not registered in the // // other instance (e.g. an extension that was loaded after the instantiation of // // the other instance. Skip this service in this case. See T143974 // try { -// $oldService = $other->peekService( $name ); -// } catch ( NoSuchServiceException $e ) { +// oldService = other.peekService(name); +// } catch (NoSuchServiceException e) { // continue; // } // -// if ( $oldService instanceof SalvageableService ) { -// /** @var SalvageableService $newService */ -// $newService = this.getService( $name ); -// $newService->salvage( $oldService ); +// if (oldService instanceof SalvageableService) { +// /** @var SalvageableService newService */ +// newService = this.getService(name); +// newService.salvage(oldService); // } // } // -// $other->destroy(); +// other.destroy(); // } -// -// /** -// * Creates a new MediaWikiServices instance and initializes it according to the -// * given $bootstrapConfig. In particular, all wiring files defined in the -// * ServiceWiringFiles setting are loaded, and the MediaWikiServices hook is called. -// * -// * @param Config|null $bootstrapConfig The Config object to be registered as the -// * 'BootstrapConfig' service. -// * -// * @param string $loadWiring set this to 'load' to load the wiring files specified -// * in the 'ServiceWiringFiles' setting in $bootstrapConfig. -// * -// * @return MediaWikiServices -// * @throws MWException -// * @throws \FatalError -// */ -// private static function newInstance( Config $bootstrapConfig, $loadWiring = '' ) { -// $instance = new self( $bootstrapConfig ); -// -// // Load the default wiring from the specified files. -// if ( $loadWiring === 'load' ) { -// $wiringFiles = $bootstrapConfig->get( 'ServiceWiringFiles' ); -// $instance->loadWiringFiles( $wiringFiles ); -// } -// -// // Provide a traditional hook point to allow extensions to configure services. -// Hooks::run( 'MediaWikiServices', [ $instance ] ); -// -// return $instance; -// } -// + + /** + * Creates a new MediaWikiServices instance and initializes it according to the + * given bootstrapConfig. In particular, all wiring files defined in the + * ServiceWiringFiles setting are loaded, and the MediaWikiServices hook is called. + * + * @param Config|null bootstrapConfig The Config object to be registered as the + * "BootstrapConfig" service. + * + * @param string loadWiring set this to "load" to load the wiring files specified + * in the "ServiceWiringFiles" setting in bootstrapConfig. + * + * @return MediaWikiServices + * @throws MWException + * @throws \FatalError + */ + private static XomwMediaWikiServices newInstance(XomwConfig bootstrapConfig) {return newInstance(bootstrapConfig, "");} + private static XomwMediaWikiServices newInstance(XomwConfig bootstrapConfig, String loadWiring) { + instance = new XomwMediaWikiServices(bootstrapConfig); + + // Load the default wiring from the specified files. + if (XophpString_.eq(loadWiring, "load")) { + XophpArray> wiringFiles = (XophpArray>)bootstrapConfig.get("ServiceWiringFiles"); + instance.loadWiringFiles(wiringFiles); + } + + // Provide a traditional hook point to allow extensions to configure services. + XomwHooks.run("MediaWikiServices", XophpArray.New(instance)); + + return instance; + } + // /** // * Disables all storage layer services. After calling this, any attempt to access the // * storage layer will result in an error. Use resetGlobalInstance() to restore normal @@ -240,11 +247,11 @@ public class XomwMediaWikiServices { // extends ServiceContainer // */ // public static function disableStorageBackend() { // // TODO: also disable some Caches, JobQueues, etc -// $destroy = [ 'DBLoadBalancer', 'DBLoadBalancerFactory' ]; -// $services = self::getInstance(); +// destroy = [ "DBLoadBalancer", "DBLoadBalancerFactory" ]; +// services = getInstance(); // -// foreach ( $destroy as $name ) { -// $services->disableService( $name ); +// foreach (destroy as name) { +// services.disableService(name); // } // // ObjectCache::clear(); @@ -252,7 +259,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // // /** // * Resets any services that may have become stale after a child process -// * returns from after pcntl_fork(). It's also safe, but generally unnecessary, +// * returns from after pcntl_fork(). It"s also safe, but generally unnecessary, // * to call this method from the parent process. // * // * @since 1.28 @@ -263,13 +270,13 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @see disableStorageBackend() // */ // public static function resetChildProcessServices() { -// // NOTE: for now, just reset everything. Since we don't know the interdependencies -// // between services, we can't do this more selectively at this time. -// self::resetGlobalInstance(); +// // NOTE: for now, just reset everything. Since we don"t know the interdependencies +// // between services, we can"t do this more selectively at this time. +// resetGlobalInstance(); // // // Child, reseed because there is no bug in PHP: // // https://bugs.php.net/bug.php?id=42465 -// mt_srand( getmypid() ); +// mt_srand(getmypid()); // } // // /** @@ -286,19 +293,19 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * should not be needed. It is provided to allow tests that pollute global service // * instances to clean up. // * -// * @param string $name -// * @param bool $destroy Whether the service instance should be destroyed if it exists. +// * @param string name +// * @param bool destroy Whether the service instance should be destroyed if it exists. // * When set to false, any existing service instance will effectively be detached // * from the container. // * // * @throws MWException if called outside of PHPUnit tests. // */ -// public function resetServiceForTesting( $name, $destroy = true ) { -// if ( !defined( 'MW_PHPUNIT_TEST' ) && !defined( 'MW_PARSER_TEST' ) ) { -// throw new MWException( 'resetServiceForTesting() must not be used outside unit tests.' ); +// public function resetServiceForTesting(name, destroy = true) { +// if (!defined("MW_PHPUNIT_TEST") && !defined("MW_PARSER_TEST")) { +// throw new MWException("resetServiceForTesting() must not be used outside unit tests."); // } // -// this.resetService( $name, $destroy ); +// this.resetService(name, destroy); // } // // /** @@ -310,7 +317,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * // * This method will throw an exception if: // * -// * - self::$resetInProgress is false (to allow all services to be reset together +// * - resetInProgress is false (to allow all services to be reset together // * via resetGlobalInstance) // * - and MEDIAWIKI_INSTALL is not defined (to allow services to be reset during installation) // * - and MW_PHPUNIT_TEST is not defined (to allow services to be reset during testing) @@ -320,7 +327,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * defined here in the MediaWikiServices services class to have a central place // * for managing service bootstrapping and resetting. // * -// * @param string $method the name of the caller method, as given by __METHOD__. +// * @param string method the name of the caller method, as given by __METHOD__. // * // * @throws MWException if called outside bootstrap mode. // * @@ -328,30 +335,30 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @see forceGlobalInstance() // * @see disableStorageBackend() // */ -// public static function failIfResetNotAllowed( $method ) { -// if ( !defined( 'MW_PHPUNIT_TEST' ) -// && !defined( 'MW_PARSER_TEST' ) -// && !defined( 'MEDIAWIKI_INSTALL' ) -// && !defined( 'RUN_MAINTENANCE_IF_MAIN' ) -// && defined( 'MW_SERVICE_BOOTSTRAP_COMPLETE' ) +// public static function failIfResetNotAllowed(method) { +// if (!defined("MW_PHPUNIT_TEST") +// && !defined("MW_PARSER_TEST") +// && !defined("MEDIAWIKI_INSTALL") +// && !defined("RUN_MAINTENANCE_IF_MAIN") +// && defined("MW_SERVICE_BOOTSTRAP_COMPLETE") // ) { -// throw new MWException( $method . ' may only be called during bootstrapping and unit tests!' ); +// throw new MWException(method . " may only be called during bootstrapping and unit tests!"); // } // } // -// /** -// * @param Config $config The Config object to be registered as the 'BootstrapConfig' service. -// * This has to contain at least the information needed to set up the 'ConfigFactory' -// * service. -// */ -// public function __construct( Config $config ) { -// parent::__construct(); -// + /** + * @param Config config The Config object to be registered as the "BootstrapConfig" service. + * This has to contain at least the information needed to set up the "ConfigFactory" + * service. + */ + public XomwMediaWikiServices(XomwConfig config) { + super(); + // // Register the given Config object as the bootstrap config service. -// this.defineService( 'BootstrapConfig', function () use ( $config ) { -// return $config; -// } ); -// } +// this.defineService("BootstrapConfig", function () use (config) { +// return config; +// }); + } // // // CONVENIENCE GETTERS //////////////////////////////////////////////////// // @@ -360,7 +367,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return ActorMigration // */ // public function getActorMigration() { -// return this.getService( 'ActorMigration' ); +// return this.getService("ActorMigration"); // } // // /** @@ -368,7 +375,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return BlobStore // */ // public function getBlobStore() { -// return this.getService( '_SqlBlobStore' ); +// return this.getService("_SqlBlobStore"); // } // // /** @@ -376,7 +383,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return BlobStoreFactory // */ // public function getBlobStoreFactory() { -// return this.getService( 'BlobStoreFactory' ); +// return this.getService("BlobStoreFactory"); // } // // /** @@ -384,7 +391,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return BlockRestrictionStore // */ // public function getBlockRestrictionStore() : BlockRestrictionStore { -// return this.getService( 'BlockRestrictionStore' ); +// return this.getService("BlockRestrictionStore"); // } // // /** @@ -401,7 +408,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return Config // */ // public function getBootstrapConfig() { -// return this.getService( 'BootstrapConfig' ); +// return this.getService("BootstrapConfig"); // } // // /** @@ -409,7 +416,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return NameTableStore // */ // public function getChangeTagDefStore() { -// return this.getService( 'NameTableStoreFactory' )->getChangeTagDef(); +// return this.getService("NameTableStoreFactory").getChangeTagDef(); // } // // /** @@ -417,7 +424,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return CommentStore // */ // public function getCommentStore() { -// return this.getService( 'CommentStore' ); +// return this.getService("CommentStore"); // } // // /** @@ -425,7 +432,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return ConfigFactory // */ // public function getConfigFactory() { -// return this.getService( 'ConfigFactory' ); +// return this.getService("ConfigFactory"); // } // // /** @@ -433,7 +440,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return ConfigRepository // */ // public function getConfigRepository() { -// return this.getService( 'ConfigRepository' ); +// return this.getService("ConfigRepository"); // } // // /** @@ -441,7 +448,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return \ConfiguredReadOnlyMode // */ // public function getConfiguredReadOnlyMode() { -// return this.getService( 'ConfiguredReadOnlyMode' ); +// return this.getService("ConfiguredReadOnlyMode"); // } // // /** @@ -449,7 +456,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return \Language // */ // public function getContentLanguage() { -// return this.getService( 'ContentLanguage' ); +// return this.getService("ContentLanguage"); // } // // /** @@ -457,7 +464,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return NameTableStore // */ // public function getContentModelStore() { -// return this.getService( 'NameTableStoreFactory' )->getContentModels(); +// return this.getService("NameTableStoreFactory").getContentModels(); // } // // /** @@ -465,7 +472,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return CryptHKDF // */ // public function getCryptHKDF() { -// return this.getService( 'CryptHKDF' ); +// return this.getService("CryptHKDF"); // } // // /** @@ -474,8 +481,8 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return CryptRand // */ // public function getCryptRand() { -// wfDeprecated( __METHOD__, '1.32' ); -// return this.getService( 'CryptRand' ); +// wfDeprecated(__METHOD__, "1.32"); +// return this.getService("CryptRand"); // } // // /** @@ -483,7 +490,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return LoadBalancer The main DB load balancer for the local wiki. // */ // public function getDBLoadBalancer() { -// return this.getService( 'DBLoadBalancer' ); +// return this.getService("DBLoadBalancer"); // } // // /** @@ -491,7 +498,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return LBFactory // */ // public function getDBLoadBalancerFactory() { -// return this.getService( 'DBLoadBalancerFactory' ); +// return this.getService("DBLoadBalancerFactory"); // } // // /** @@ -499,7 +506,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return EventRelayerGroup // */ // public function getEventRelayerGroup() { -// return this.getService( 'EventRelayerGroup' ); +// return this.getService("EventRelayerGroup"); // } // // /** @@ -507,7 +514,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return \ExternalStoreFactory // */ // public function getExternalStoreFactory() { -// return this.getService( 'ExternalStoreFactory' ); +// return this.getService("ExternalStoreFactory"); // } // // /** @@ -515,7 +522,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return GenderCache // */ // public function getGenderCache() { -// return this.getService( 'GenderCache' ); +// return this.getService("GenderCache"); // } // // /** @@ -523,7 +530,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return HttpRequestFactory // */ // public function getHttpRequestFactory() { -// return this.getService( 'HttpRequestFactory' ); +// return this.getService("HttpRequestFactory"); // } /** @@ -531,7 +538,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer * @return InterwikiLookup */ public XomwInterwikiLookup getInterwikiLookup() { -// return this.getService( 'InterwikiLookup' ); +// return this.getService("InterwikiLookup"); return interwikiLookup; } @@ -540,7 +547,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return LinkCache // */ // public function getLinkCache() { -// return this.getService( 'LinkCache' ); +// return this.getService("LinkCache"); // } // // /** @@ -551,7 +558,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return LinkRenderer // */ // public function getLinkRenderer() { -// return this.getService( 'LinkRenderer' ); +// return this.getService("LinkRenderer"); // } // // /** @@ -559,7 +566,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return LinkRendererFactory // */ // public function getLinkRendererFactory() { -// return this.getService( 'LinkRendererFactory' ); +// return this.getService("LinkRendererFactory"); // } // // /** @@ -567,7 +574,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return \BagOStuff // */ // public function getLocalServerObjectCache() { -// return this.getService( 'LocalServerObjectCache' ); +// return this.getService("LocalServerObjectCache"); // } // // /** @@ -575,7 +582,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return MagicWordFactory // */ // public function getMagicWordFactory() { -// return this.getService( 'MagicWordFactory' ); +// return this.getService("MagicWordFactory"); // } // // /** @@ -586,7 +593,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return Config // */ // public function getMainConfig() { -// return this.getService( 'MainConfig' ); +// return this.getService("MainConfig"); // } // // /** @@ -594,7 +601,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return \BagOStuff // */ // public function getMainObjectStash() { -// return this.getService( 'MainObjectStash' ); +// return this.getService("MainObjectStash"); // } // // /** @@ -602,7 +609,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return \WANObjectCache // */ // public function getMainWANObjectCache() { -// return this.getService( 'MainWANObjectCache' ); +// return this.getService("MainWANObjectCache"); // } // // /** @@ -610,7 +617,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return MediaHandlerFactory // */ // public function getMediaHandlerFactory() { -// return this.getService( 'MediaHandlerFactory' ); +// return this.getService("MediaHandlerFactory"); // } // // /** @@ -618,7 +625,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return MimeAnalyzer // */ // public function getMimeAnalyzer() { -// return this.getService( 'MimeAnalyzer' ); +// return this.getService("MimeAnalyzer"); // } // // /** @@ -626,14 +633,14 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return NameTableStoreFactory // */ // public function getNameTableStoreFactory() { -// return this.getService( 'NameTableStoreFactory' ); +// return this.getService("NameTableStoreFactory"); // } // // /** // * @return OldRevisionImporter // */ // public function getOldRevisionImporter() { -// return this.getService( 'OldRevisionImporter' ); +// return this.getService("OldRevisionImporter"); // } // // /** @@ -641,7 +648,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return Parser // */ // public function getParser() { -// return this.getService( 'Parser' ); +// return this.getService("Parser"); // } // // /** @@ -649,7 +656,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return ParserCache // */ // public function getParserCache() { -// return this.getService( 'ParserCache' ); +// return this.getService("ParserCache"); // } // // /** @@ -657,7 +664,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return ParserFactory // */ // public function getParserFactory() { -// return this.getService( 'ParserFactory' ); +// return this.getService("ParserFactory"); // } // // /** @@ -665,7 +672,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return PasswordFactory // */ // public function getPasswordFactory() { -// return this.getService( 'PasswordFactory' ); +// return this.getService("PasswordFactory"); // } // // /** @@ -673,7 +680,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return StatsdDataFactoryInterface // */ // public function getPerDbNameStatsdDataFactory() { -// return this.getService( 'PerDbNameStatsdDataFactory' ); +// return this.getService("PerDbNameStatsdDataFactory"); // } // // /** @@ -681,7 +688,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return PermissionManager // */ // public function getPermissionManager() { -// return this.getService( 'PermissionManager' ); +// return this.getService("PermissionManager"); // } // // /** @@ -689,7 +696,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return PreferencesFactory // */ // public function getPreferencesFactory() { -// return this.getService( 'PreferencesFactory' ); +// return this.getService("PreferencesFactory"); // } // // /** @@ -697,7 +704,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return ProxyLookup // */ // public function getProxyLookup() { -// return this.getService( 'ProxyLookup' ); +// return this.getService("ProxyLookup"); // } // // /** @@ -705,7 +712,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return \ReadOnlyMode // */ // public function getReadOnlyMode() { -// return this.getService( 'ReadOnlyMode' ); +// return this.getService("ReadOnlyMode"); // } // // /** @@ -713,7 +720,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return ResourceLoader // */ // public function getResourceLoader() { -// return this.getService( 'ResourceLoader' ); +// return this.getService("ResourceLoader"); // } // // /** @@ -721,7 +728,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return RevisionFactory // */ // public function getRevisionFactory() { -// return this.getService( 'RevisionFactory' ); +// return this.getService("RevisionFactory"); // } // // /** @@ -729,7 +736,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return RevisionLookup // */ // public function getRevisionLookup() { -// return this.getService( 'RevisionLookup' ); +// return this.getService("RevisionLookup"); // } // // /** @@ -737,7 +744,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return RevisionRenderer // */ // public function getRevisionRenderer() { -// return this.getService( 'RevisionRenderer' ); +// return this.getService("RevisionRenderer"); // } // // /** @@ -745,7 +752,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return RevisionStore // */ // public function getRevisionStore() { -// return this.getService( 'RevisionStore' ); +// return this.getService("RevisionStore"); // } // // /** @@ -753,7 +760,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return RevisionStoreFactory // */ // public function getRevisionStoreFactory() { -// return this.getService( 'RevisionStoreFactory' ); +// return this.getService("RevisionStoreFactory"); // } // // /** @@ -762,7 +769,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // */ // public function newSearchEngine() { // // New engine object every time, since they keep state -// return this.getService( 'SearchEngineFactory' )->create(); +// return this.getService("SearchEngineFactory").create(); // } // // /** @@ -770,7 +777,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return SearchEngineConfig // */ // public function getSearchEngineConfig() { -// return this.getService( 'SearchEngineConfig' ); +// return this.getService("SearchEngineConfig"); // } // // /** @@ -778,7 +785,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return SearchEngineFactory // */ // public function getSearchEngineFactory() { -// return this.getService( 'SearchEngineFactory' ); +// return this.getService("SearchEngineFactory"); // } // // /** @@ -786,7 +793,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return CommandFactory // */ // public function getShellCommandFactory() { -// return this.getService( 'ShellCommandFactory' ); +// return this.getService("ShellCommandFactory"); // } // // /** @@ -794,7 +801,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return SiteLookup // */ // public function getSiteLookup() { -// return this.getService( 'SiteLookup' ); +// return this.getService("SiteLookup"); // } // // /** @@ -802,7 +809,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return SiteStore // */ // public function getSiteStore() { -// return this.getService( 'SiteStore' ); +// return this.getService("SiteStore"); // } // // /** @@ -810,7 +817,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return SkinFactory // */ // public function getSkinFactory() { -// return this.getService( 'SkinFactory' ); +// return this.getService("SkinFactory"); // } // // /** @@ -818,7 +825,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return SlotRoleRegistry // */ // public function getSlotRoleRegistry() { -// return this.getService( 'SlotRoleRegistry' ); +// return this.getService("SlotRoleRegistry"); // } // // /** @@ -826,7 +833,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return NameTableStore // */ // public function getSlotRoleStore() { -// return this.getService( 'NameTableStoreFactory' )->getSlotRoles(); +// return this.getService("NameTableStoreFactory").getSlotRoles(); // } // // /** @@ -834,7 +841,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return SpecialPageFactory // */ // public function getSpecialPageFactory() : SpecialPageFactory { -// return this.getService( 'SpecialPageFactory' ); +// return this.getService("SpecialPageFactory"); // } // // /** @@ -842,7 +849,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return IBufferingStatsdDataFactory // */ // public function getStatsdDataFactory() { -// return this.getService( 'StatsdDataFactory' ); +// return this.getService("StatsdDataFactory"); // } /** @@ -850,7 +857,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer * @return TitleFormatter */ // public XomwTitleFormatter getTitleFormatter() { -// return this.getService( 'TitleFormatter' ); +// return this.getService("TitleFormatter"); public XomwMediaWikiTitleCodec getTitleFormatter() { return titleParser; } @@ -860,7 +867,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer * @return TitleParser */ // public XomwTitleParser getTitleParser() { -// return this.getService( 'TitleParser' ); +// return this.getService("TitleParser"); public XomwMediaWikiTitleCodec getTitleParser() { return titleParser; } @@ -870,7 +877,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return UploadRevisionImporter // */ // public function getUploadRevisionImporter() { -// return this.getService( 'UploadRevisionImporter' ); +// return this.getService("UploadRevisionImporter"); // } // // /** @@ -878,7 +885,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return VirtualRESTServiceClient // */ // public function getVirtualRESTServiceClient() { -// return this.getService( 'VirtualRESTServiceClient' ); +// return this.getService("VirtualRESTServiceClient"); // } // // /** @@ -886,7 +893,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return WatchedItemQueryService // */ // public function getWatchedItemQueryService() { -// return this.getService( 'WatchedItemQueryService' ); +// return this.getService("WatchedItemQueryService"); // } // // /** @@ -894,7 +901,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return WatchedItemStoreInterface // */ // public function getWatchedItemStore() { -// return this.getService( 'WatchedItemStore' ); +// return this.getService("WatchedItemStore"); // } // // /** @@ -902,7 +909,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return \OldRevisionImporter // */ // public function getWikiRevisionOldRevisionImporter() { -// return this.getService( 'OldRevisionImporter' ); +// return this.getService("OldRevisionImporter"); // } // // /** @@ -910,7 +917,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return \OldRevisionImporter // */ // public function getWikiRevisionOldRevisionImporterNoUpdates() { -// return this.getService( 'WikiRevisionOldRevisionImporterNoUpdates' ); +// return this.getService("WikiRevisionOldRevisionImporterNoUpdates"); // } // // /** @@ -918,7 +925,7 @@ public class XomwMediaWikiServices { // extends ServiceContainer // * @return \UploadRevisionImporter // */ // public function getWikiRevisionUploadImporter() { -// return this.getService( 'UploadRevisionImporter' ); +// return this.getService("UploadRevisionImporter"); // } } diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/XomwServiceWiring.java b/400_xowa/src/gplx/xowa/mediawiki/includes/XomwServiceWiring.java index 37cefcb24..8bcce9df5 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/XomwServiceWiring.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/XomwServiceWiring.java @@ -16,13 +16,41 @@ Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt package gplx.xowa.mediawiki.includes; import gplx.Err_; +import gplx.xowa.mediawiki.XophpArray; import gplx.xowa.mediawiki.XophpCallback; import gplx.xowa.mediawiki.XophpCallbackOwner; -import gplx.xowa.mediawiki.includes.config.XomwConfig; import gplx.xowa.mediawiki.languages.XomwLanguage; // MW.SRC:1.33.1 public class XomwServiceWiring implements XophpCallbackOwner { + // XO:infrastructure to register the multiple wiring methods + interface XomwServiceWiringMethod { + String Key(); + Object Call(XomwMediaWikiServices mediaWikiServices); + } + private final XophpArray methods = new XophpArray<>(); + public XomwServiceWiring() { + InitMethod(new InterwikiLookup()); + } + private void InitMethod(XomwServiceWiringMethod method) { + methods.Add(method.Key(), method); + } + public XophpArray GetCallbacks() { + XophpArray rv = new XophpArray<>(); + for (XomwServiceWiringMethod method : methods) { + rv.Add(this.NewCallback(method.Key())); + } + return rv; + } + @Override public Object Call(String methodName, Object... args) { + XomwMediaWikiServices services = (XomwMediaWikiServices)args[0]; + XomwServiceWiringMethod method = methods.Get_by(methodName); + if (method == null) { + throw Err_.new_unhandled_default(methodName); + } + return method.Call(services); + }; + //return [ // "ActorMigration" => function (MediaWikiServices services) : ActorMigration { // return new ActorMigration( @@ -75,9 +103,12 @@ public class XomwServiceWiring implements XophpCallbackOwner { // return new ConfiguredReadOnlyMode(services.getMainConfig()); // }, - private XomwLanguage newContentLanguage(XomwMediaWikiServices services) { + class ContentLanguage implements XomwServiceWiringMethod { + @Override public String Key() {return "ContentLanguage";} + @Override public Object Call(XomwMediaWikiServices services) { // return XomwLanguage.factory(services.getMainConfig().get("LanguageCode")); return null; + } } // // "CryptHKDF" => function (MediaWikiServices services) : CryptHKDF { @@ -151,8 +182,9 @@ public class XomwServiceWiring implements XophpCallbackOwner { // return new \MediaWiki\Http\HttpRequestFactory(); // }, - - private Object newInterwikiLoopup(XomwMediaWikiServices services) { + class InterwikiLookup implements XomwServiceWiringMethod { + @Override public String Key() {return "InterwikiLookup";} + @Override public Object Call(XomwMediaWikiServices services) { // XomwConfig config = services.getMainConfig(); // return new ClassicInterwikiLookup( // services.getContentLanguage(), @@ -162,21 +194,9 @@ public class XomwServiceWiring implements XophpCallbackOwner { // config.get("InterwikiScopes"), // config.get("InterwikiFallbackSite") // ); - return null; - } - - @Override - public Object Call(String method, Object... args) { - XomwMediaWikiServices services = (XomwMediaWikiServices)args[0]; - switch (method) { - case "InterwikiLookup": - return newInterwikiLoopup(services); - case "ContentLanguage": - return newContentLanguage(services); - default: - throw Err_.new_unhandled_default(method); + return null; } - }; + } // "LinkCache" => function (MediaWikiServices services) : LinkCache { // return new LinkCache( diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/cache/XomwMessageCache.java b/400_xowa/src/gplx/xowa/mediawiki/includes/cache/XomwMessageCache.java index 0c6071c30..cdab8ebaa 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/cache/XomwMessageCache.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/cache/XomwMessageCache.java @@ -1,1282 +1,1407 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ -package gplx.xowa.mediawiki.includes.cache; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; -/** -* Message cache -* Performs various MediaWiki namespace-related functions -* @ingroup Cache +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2020 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ +package gplx.xowa.mediawiki.includes.cache; + +import gplx.xowa.mediawiki.XophpArray; +import gplx.xowa.mediawiki.includes.XomwMediaWikiServices; +import gplx.xowa.mediawiki.includes.parsers.XomwParser; +import gplx.xowa.mediawiki.includes.parsers.XomwParserOptions; + +// MW.SRC:1.33.1 +/** + * Cache of messages that are defined by MediaWiki namespace pages or by hooks + * + * Performs various MediaWiki namespace-related functions + * @ingroup Cache + */ public class XomwMessageCache { -// static final FOR_UPDATE = 1; // force message reload + private static final int FOR_UPDATE = 1; // force message reload + + /** How long to wait for memcached locks */ + private static final int WAIT_SEC = 15; + /** How long memcached locks last */ + private static final int LOCK_TTL = 30; + + /** + * Process cache of loaded messages that are defined in MediaWiki namespace + * + * @var MapCacheLRU Map of (language code => key => " " or "!TOO BIG" or "!ERROR") + */ +// protected cache; + + /** + * Map of (lowercase message key => index) for all software defined messages + * + * @var array + */ + protected XophpArray overridable; + + /** + * @var bool[] Map of (language code => boolean) + */ + protected XophpArray cacheVolatile = new XophpArray<>(); + + /** + * Should mean that database cannot be used, but check + * @var bool mDisable + */ + protected boolean mDisable; + + /** + * Lifetime for cache, used by object caching. + * Set on construction, see __construct(). + */ +// protected mExpiry; + + /** + * Message cache has its own parser which it uses to transform messages + * @var ParserOptions + */ + protected XomwParserOptions mParserOptions; + /** @var Parser */ + protected XomwParser mParser; + + /** + * @var bool mInParser + */ + protected boolean mInParser = false; + +// /** @var WANObjectCache */ +// protected wanCache; +// /** @var BagOStuff */ +// protected clusterCache; +// /** @var BagOStuff */ +// protected srvCache; +// /** @var Language */ +// protected contLang; // -// /** How long to wait for memcached locks */ -// static final WAIT_SEC = 15; -// /** How long memcached locks last */ -// static final LOCK_TTL = 30; +// /** +// * Track which languages have been loaded by load(). +// * @var array +// */ +// private loadedLanguages = []; + + /** + * Singleton instance + * + * @var MessageCache instance + */ + private static XomwMessageCache instance; + + /** + * Get the signleton instance of this class + * + * @since 1.18 + * @return MessageCache + */ + public static XomwMessageCache singleton() { + if (instance == null) { +// global wgUseDatabaseMessages, wgMsgCacheExpiry, wgUseLocalMessageCache; + XomwMediaWikiServices services = XomwMediaWikiServices.getInstance(); + instance = new XomwMessageCache( +// services.getMainWANObjectCache(), +// wfGetMessageCacheStorage(), +// wgUseLocalMessageCache +// ? services.getLocalServerObjectCache() +// : new EmptyBagOStuff(), +// wgUseDatabaseMessages, +// wgMsgCacheExpiry, +// services.getContentLanguage() + ); + } + + return instance; + } + +// /** +// * Destroy the singleton instance +// * +// * @since 1.18 +// */ +// public static function destroyInstance() { +// self.instance = null; +// } // -// /** -// * Process local cache of loaded messages that are defined in -// * MediaWiki namespace. First array level is a language code, -// * second level is message key and the values are either message -// * content prefixed with space, or !NONEXISTENT for negative -// * caching. -// * @var array $mCache -// */ -// protected $mCache; -// -// /** -// * @var boolean[] Map of (language code => boolean) -// */ -// protected $mCacheVolatile = []; -// -// /** -// * Should mean that database cannot be used, but check -// * @var boolean $mDisable -// */ -// protected $mDisable; -// -// /** -// * Lifetime for cache, used by Object caching. -// * Set on construction, see __construct(). -// */ -// protected $mExpiry; -// -// /** -// * Message cache has its own parser which it uses to transform messages -// * @var ParserOptions -// */ -// protected $mParserOptions; -// /** @var Parser */ -// protected $mParser; -// -// /** -// * Variable for tracking which variables are already loaded -// * @var array $mLoadedLanguages -// */ -// protected $mLoadedLanguages = []; -// -// /** -// * @var boolean $mInParser -// */ -// protected $mInParser = false; -// -// /** @var WANObjectCache */ -// protected $wanCache; -// /** @var BagOStuff */ -// protected $clusterCache; -// /** @var BagOStuff */ -// protected $srvCache; -// -// /** -// * Singleton instance -// * -// * @var MessageCache $instance -// */ -// private static $instance; -// -// /** -// * Get the signleton instance of this class -// * -// * @since 1.18 -// * @return MessageCache -// */ -// public static function singleton() { -// if ( self::$instance === null ) { -// global $wgUseDatabaseMessages, $wgMsgCacheExpiry, $wgUseLocalMessageCache; -// self::$instance = new self( -// MediaWikiServices::getInstance()->getMainWANObjectCache(), -// wfGetMessageCacheStorage(), -// $wgUseLocalMessageCache -// ? MediaWikiServices::getInstance()->getLocalServerObjectCache() -// : new EmptyBagOStuff(), -// $wgUseDatabaseMessages, -// $wgMsgCacheExpiry -// ); -// } -// -// return self::$instance; +// /** +// * Normalize message key input +// * +// * @param string key Input message key to be normalized +// * @return string Normalized message key +// */ +// public static function normalizeKey(key) { +// lckey = strtr(key, ' ', '_'); +// if (ord(lckey) < 128) { +// lckey[0] = strtolower(lckey[0]); +// } else { +// lckey = MediaWikiServices.getInstance().getContentLanguage().lcfirst(lckey); // } // -// /** -// * Destroy the singleton instance -// * -// * @since 1.18 -// */ -// public static function destroyInstance() { -// self::$instance = null; +// return lckey; +// } +// +// /** +// * @param WANObjectCache wanCache +// * @param BagOStuff clusterCache +// * @param BagOStuff serverCache +// * @param bool useDB Whether to look for message overrides (e.g. MediaWiki: pages) +// * @param int expiry Lifetime for cache. @see mExpiry. +// * @param Language|null contLang Content language of site +// */ +// public function __construct( +// WANObjectCache wanCache, +// BagOStuff clusterCache, +// BagOStuff serverCache, +// useDB, +// expiry, +// Language contLang = null +// ) { +// this.wanCache = wanCache; +// this.clusterCache = clusterCache; +// this.srvCache = serverCache; +// +// this.cache = new MapCacheLRU(5); // limit size for sanity +// +// this.mDisable = !useDB; +// this.mExpiry = expiry; +// this.contLang = contLang ?? MediaWikiServices.getInstance().getContentLanguage(); +// } +// +// /** +// * ParserOptions is lazy initialised. +// * +// * @return ParserOptions +// */ +// function getParserOptions() { +// global wgUser; +// +// if (!this.mParserOptions) { +// if (!wgUser.isSafeToLoad()) { +// // wgUser isn't unstubbable yet, so don't try to get a +// // ParserOptions for it. And don't cache this ParserOptions +// // either. +// po = ParserOptions.newFromAnon(); +// po.setAllowUnsafeRawHtml(false); +// po.setTidy(true); +// return po; +// } +// +// this.mParserOptions = new ParserOptions; +// // Messages may take parameters that could come +// // from malicious sources. As a precaution, disable +// // the parser tag when parsing messages. +// this.mParserOptions.setAllowUnsafeRawHtml(false); +// // For the same reason, tidy the output! +// this.mParserOptions.setTidy(true); // } // -// /** -// * Normalize message key input -// * -// * @param String $key Input message key to be normalized -// * @return String Normalized message key -// */ -// public static function normalizeKey( $key ) { -// global $wgContLang; +// return this.mParserOptions; +// } // -// $lckey = strtr( $key, ' ', '_' ); -// if ( ord( $lckey ) < 128 ) { -// $lckey[0] = strtolower( $lckey[0] ); -// } else { -// $lckey = $wgContLang->lcfirst( $lckey ); -// } +// /** +// * Try to load the cache from APC. +// * +// * @param string code Optional language code, see documentation of load(). +// * @return array|bool The cache array, or false if not in cache. +// */ +// protected function getLocalCache(code) { +// cacheKey = this.srvCache.makeKey(__CLASS__, code); // -// return $lckey; +// return this.srvCache.get(cacheKey); +// } +// +// /** +// * Save the cache to APC. +// * +// * @param string code +// * @param array cache The cache array +// */ +// protected function saveToLocalCache(code, cache) { +// cacheKey = this.srvCache.makeKey(__CLASS__, code); +// this.srvCache.set(cacheKey, cache); +// } +// +// /** +// * Loads messages from caches or from database in this order: +// * (1) local message cache (if wgUseLocalMessageCache is enabled) +// * (2) memcached +// * (3) from the database. +// * +// * When successfully loading from (2) or (3), all higher level caches are +// * updated for the newest version. +// * +// * Nothing is loaded if member variable mDisable is true, either manually +// * set by calling code or if message loading fails (is this possible?). +// * +// * Returns true if cache is already populated or it was successfully populated, +// * or false if populating empty cache fails. Also returns true if MessageCache +// * is disabled. +// * +// * @param string code Language to which load messages +// * @param int|null mode Use MessageCache.FOR_UPDATE to skip process cache [optional] +// * @throws InvalidArgumentException +// * @return bool +// */ +// protected function load(code, mode = null) { +// if (!is_string(code)) { +// throw new InvalidArgumentException("Missing language code"); // } // -// /** -// * @param WANObjectCache $wanCache WAN cache instance -// * @param BagOStuff $clusterCache Cluster cache instance -// * @param BagOStuff $srvCache Server cache instance -// * @param boolean $useDB Whether to look for message overrides (e.g. MediaWiki: pages) -// * @param int $expiry Lifetime for cache. @see $mExpiry. -// */ -// public function __construct( -// WANObjectCache $wanCache, -// BagOStuff $clusterCache, -// BagOStuff $srvCache, -// $useDB, -// $expiry -// ) { -// $this->wanCache = $wanCache; -// $this->clusterCache = $clusterCache; -// $this->srvCache = $srvCache; -// -// $this->mDisable = !$useDB; -// $this->mExpiry = $expiry; +// # Don't do double loading... +// if (isset(this.loadedLanguages[code]) && mode != self.FOR_UPDATE) { +// return true; // } // -// /** -// * ParserOptions is lazy initialised. -// * -// * @return ParserOptions -// */ -// function getParserOptions() { -// global $wgUser; +// this.overridable = array_flip(Language.getMessageKeysFor(code)); // -// if ( !$this->mParserOptions ) { -// if ( !$wgUser->isSafeToLoad() ) { -// // $wgUser isn't unstubbable yet, so don't try to get a -// // ParserOptions for it. And don't cache this ParserOptions -// // either. -// $po = ParserOptions::newFromAnon(); -// $po->setEditSection( false ); -// return $po; -// } -// -// $this->mParserOptions = new ParserOptions; -// $this->mParserOptions->setEditSection( false ); -// } -// -// return $this->mParserOptions; -// } -// -// /** -// * Try to load the cache from APC. -// * -// * @param String $code Optional language code, see documenation of load(). -// * @return array|boolean The cache array, or false if not in cache. -// */ -// protected function getLocalCache( $code ) { -// $cacheKey = wfMemcKey( __CLASS__, $code ); -// -// return $this->srvCache->get( $cacheKey ); -// } -// -// /** -// * Save the cache to APC. -// * -// * @param String $code -// * @param array $cache The cache array -// */ -// protected function saveToLocalCache( $code, $cache ) { -// $cacheKey = wfMemcKey( __CLASS__, $code ); -// $this->srvCache->set( $cacheKey, $cache ); -// } -// -// /** -// * Loads messages from caches or from database in this order: -// * (1) local message cache (if $wgUseLocalMessageCache is enabled) -// * (2) memcached -// * (3) from the database. -// * -// * When succesfully loading from (2) or (3), all higher level caches are -// * updated for the newest version. -// * -// * Nothing is loaded if member variable mDisable is true, either manually -// * set by calling code or if message loading fails (is this possible?). -// * -// * Returns true if cache is already populated or it was succesfully populated, -// * or false if populating empty cache fails. Also returns true if MessageCache -// * is disabled. -// * -// * @param String $code Language to which load messages -// * @param integer $mode Use MessageCache::FOR_UPDATE to skip process cache [optional] -// * @throws MWException -// * @return boolean -// */ -// protected function load( $code, $mode = null ) { -// if ( !is_string( $code ) ) { -// throw new InvalidArgumentException( "Missing language code" ); -// } -// -// # Don't do double loading... -// if ( isset( $this->mLoadedLanguages[$code] ) && $mode != self::FOR_UPDATE ) { -// return true; -// } -// -// # 8 lines of code just to say (once) that message cache is disabled -// if ( $this->mDisable ) { -// static $shownDisabled = false; -// if ( !$shownDisabled ) { -// wfDebug( __METHOD__ . ": disabled\n" ); -// $shownDisabled = true; -// } -// -// return true; -// } -// -// # Loading code starts -// $success = false; # Keep track of success -// $staleCache = false; # a cache array with expired data, or false if none has been loaded -// $where = []; # Debug info, delayed to avoid spamming debug log too much -// -// # Hash of the contents is stored in memcache, to detect if data-center cache -// # or local cache goes out of date (e.g. due to replace() on some other server) -// list( $hash, $hashVolatile ) = $this->getValidationHash( $code ); -// $this->mCacheVolatile[$code] = $hashVolatile; -// -// # Try the local cache and check against the cluster hash key... -// $cache = $this->getLocalCache( $code ); -// if ( !$cache ) { -// $where[] = 'local cache is empty'; -// } elseif ( !isset( $cache['HASH'] ) || $cache['HASH'] !== $hash ) { -// $where[] = 'local cache has the wrong hash'; -// $staleCache = $cache; -// } elseif ( $this->isCacheExpired( $cache ) ) { -// $where[] = 'local cache is expired'; -// $staleCache = $cache; -// } elseif ( $hashVolatile ) { -// $where[] = 'local cache validation key is expired/volatile'; -// $staleCache = $cache; -// } else { -// $where[] = 'got from local cache'; -// $success = true; -// $this->mCache[$code] = $cache; -// } -// -// if ( !$success ) { -// $cacheKey = wfMemcKey( 'messages', $code ); # Key in memc for messages -// # Try the global cache. If it is empty, try to acquire a synchronized. If -// # the synchronized can't be acquired, wait for the other thread to finish -// # and then try the global cache a second time. -// for ( $failedAttempts = 0; $failedAttempts <= 1; $failedAttempts++ ) { -// if ( $hashVolatile && $staleCache ) { -// # Do not bother fetching the whole cache blob to avoid I/O. -// # Instead, just try to get the non-blocking $statusKey synchronized -// # below, and use the local stale value if it was not acquired. -// $where[] = 'global cache is presumed expired'; -// } else { -// $cache = $this->clusterCache->get( $cacheKey ); -// if ( !$cache ) { -// $where[] = 'global cache is empty'; -// } elseif ( $this->isCacheExpired( $cache ) ) { -// $where[] = 'global cache is expired'; -// $staleCache = $cache; -// } elseif ( $hashVolatile ) { -// # DB results are replica DB lag prone until the holdoff TTL passes. -// # By then, updates should be reflected in loadFromDBWithLock(). -// # One thread renerates the cache while others use old values. -// $where[] = 'global cache is expired/volatile'; -// $staleCache = $cache; -// } else { -// $where[] = 'got from global cache'; -// $this->mCache[$code] = $cache; -// $this->saveToCaches( $cache, 'local-only', $code ); -// $success = true; -// } -// } -// -// if ( $success ) { -// # Done, no need to retry -// break; -// } -// -// # We need to call loadFromDB. Limit the concurrency to one process. -// # This prevents the site from going down when the cache expires. -// # Note that the DB slam protection synchronized here is non-blocking. -// $loadStatus = $this->loadFromDBWithLock( $code, $where, $mode ); -// if ( $loadStatus === true ) { -// $success = true; -// break; -// } elseif ( $staleCache ) { -// # Use the stale cache while some other thread constructs the new one -// $where[] = 'using stale cache'; -// $this->mCache[$code] = $staleCache; -// $success = true; -// break; -// } elseif ( $failedAttempts > 0 ) { -// # Already blocked once, so avoid another synchronized/unlock cycle. -// # This case will typically be hit if memcached is down, or if -// # loadFromDB() takes longer than LOCK_WAIT. -// $where[] = "could not acquire status key."; -// break; -// } elseif ( $loadStatus === 'cantacquire' ) { -// # Wait for the other thread to finish, then retry. Normally, -// # the memcached get() will then yeild the other thread's result. -// $where[] = 'waited for other thread to complete'; -// $this->getReentrantScopedLock( $cacheKey ); -// } else { -// # Disable cache; $loadStatus is 'disabled' -// break; -// } -// } -// } -// -// if ( !$success ) { -// $where[] = 'loading FAILED - cache is disabled'; -// $this->mDisable = true; -// $this->mCache = false; -// wfDebugLog( 'MessageCacheError', __METHOD__ . ": Failed to load $code\n" ); -// # This used to throw an exception, but that led to nasty side effects like -// # the whole wiki being instantly down if the memcached server died -// } else { -// # All good, just record the success -// $this->mLoadedLanguages[$code] = true; -// } -// -// $info = implode( ', ', $where ); -// wfDebugLog( 'MessageCache', __METHOD__ . ": Loading $code... $info\n" ); -// -// return $success; -// } -// -// /** -// * @param String $code -// * @param array $where List of wfDebug() comments -// * @param integer $mode Use MessageCache::FOR_UPDATE to use DB_MASTER -// * @return boolean|String True on success or one of ("cantacquire", "disabled") -// */ -// protected function loadFromDBWithLock( $code, array &$where, $mode = null ) { -// # If cache updates on all levels fail, give up on message overrides. -// # This is to avoid easy site outages; see $saveSuccess comments below. -// $statusKey = wfMemcKey( 'messages', $code, 'status' ); -// $status = $this->clusterCache->get( $statusKey ); -// if ( $status === 'error' ) { -// $where[] = "could not load; method is still globally disabled"; -// return 'disabled'; -// } -// -// # Now let's regenerate -// $where[] = 'loading from database'; -// -// # Lock the cache to prevent conflicting writes. -// # This synchronized is non-blocking so stale cache can quickly be used. -// # Note that load() will call a blocking getReentrantScopedLock() -// # after this if it really need to wait for any current thread. -// $cacheKey = wfMemcKey( 'messages', $code ); -// $scopedLock = $this->getReentrantScopedLock( $cacheKey, 0 ); -// if ( !$scopedLock ) { -// $where[] = 'could not acquire main synchronized'; -// return 'cantacquire'; -// } -// -// $cache = $this->loadFromDB( $code, $mode ); -// $this->mCache[$code] = $cache; -// $saveSuccess = $this->saveToCaches( $cache, 'all', $code ); -// -// if ( !$saveSuccess ) { -// /** -// * Cache save has failed. -// * -// * There are two main scenarios where this could be a problem: -// * - The cache is more than the maximum size (typically 1MB compressed). -// * - Memcached has no space remaining in the relevant slab class. This is -// * unlikely with recent versions of memcached. -// * -// * Either way, if there is a local cache, nothing bad will happen. If there -// * is no local cache, disabling the message cache for all requests avoids -// * incurring a loadFromDB() overhead on every request, and thus saves the -// * wiki from complete downtime under moderate traffic conditions. -// */ -// if ( $this->srvCache instanceof EmptyBagOStuff ) { -// $this->clusterCache->set( $statusKey, 'error', 60 * 5 ); -// $where[] = 'could not save cache, disabled globally for 5 minutes'; -// } else { -// $where[] = "could not save global cache"; -// } +// # 8 lines of code just to say (once) that message cache is disabled +// if (this.mDisable) { +// static shownDisabled = false; +// if (!shownDisabled) { +// wfDebug(__METHOD__ . ": disabled\n"); +// shownDisabled = true; // } // // return true; // } // -// /** -// * Loads cacheable messages from the database. Messages bigger than -// * $wgMaxMsgCacheEntrySize are assigned a special value, and are loaded -// * on-demand from the database later. -// * -// * @param String $code Language code -// * @param integer $mode Use MessageCache::FOR_UPDATE to skip process cache -// * @return array Loaded messages for storing in caches -// */ -// protected function loadFromDB( $code, $mode = null ) { -// global $wgMaxMsgCacheEntrySize, $wgLanguageCode, $wgAdaptiveMessageCache; +// # Loading code starts +// success = false; # Keep track of success +// staleCache = false; # a cache array with expired data, or false if none has been loaded +// where = []; # Debug info, delayed to avoid spamming debug log too much // -// $dbr = wfGetDB( ( $mode == self::FOR_UPDATE ) ? DB_MASTER : DB_REPLICA ); +// # Hash of the contents is stored in memcache, to detect if data-center cache +// # or local cache goes out of date (e.g. due to replace() on some other server) +// list(hash, hashVolatile) = this.getValidationHash(code); +// this.cacheVolatile[code] = hashVolatile; // -// $cache = []; +// # Try the local cache and check against the cluster hash key... +// cache = this.getLocalCache(code); +// if (!cache) { +// where[] = 'local cache is empty'; +// } elseif (!isset(cache['HASH']) || cache['HASH'] !== hash) { +// where[] = 'local cache has the wrong hash'; +// staleCache = cache; +// } elseif (this.isCacheExpired(cache)) { +// where[] = 'local cache is expired'; +// staleCache = cache; +// } elseif (hashVolatile) { +// where[] = 'local cache validation key is expired/volatile'; +// staleCache = cache; +// } else { +// where[] = 'got from local cache'; +// this.cache.set(code, cache); +// success = true; +// } // -// # Common conditions -// $conds = [ -// 'page_is_redirect' => 0, -// 'page_namespace' => NS_MEDIAWIKI, -// ]; -// -// $mostused = []; -// if ( $wgAdaptiveMessageCache && $code !== $wgLanguageCode ) { -// if ( !isset( $this->mCache[$wgLanguageCode] ) ) { -// $this->load( $wgLanguageCode ); +// if (!success) { +// cacheKey = this.clusterCache.makeKey('messages', code); +// # Try the global cache. If it is empty, try to acquire a lock. If +// # the lock can't be acquired, wait for the other thread to finish +// # and then try the global cache a second time. +// for (failedAttempts = 0; failedAttempts <= 1; failedAttempts++) { +// if (hashVolatile && staleCache) { +// # Do not bother fetching the whole cache blob to avoid I/O. +// # Instead, just try to get the non-blocking statusKey lock +// # below, and use the local stale value if it was not acquired. +// where[] = 'global cache is presumed expired'; +// } else { +// cache = this.clusterCache.get(cacheKey); +// if (!cache) { +// where[] = 'global cache is empty'; +// } elseif (this.isCacheExpired(cache)) { +// where[] = 'global cache is expired'; +// staleCache = cache; +// } elseif (hashVolatile) { +// # DB results are replica DB lag prone until the holdoff TTL passes. +// # By then, updates should be reflected in loadFromDBWithLock(). +// # One thread regenerates the cache while others use old values. +// where[] = 'global cache is expired/volatile'; +// staleCache = cache; +// } else { +// where[] = 'got from global cache'; +// this.cache.set(code, cache); +// this.saveToCaches(cache, 'local-only', code); +// success = true; +// } // } -// $mostused = array_keys( $this->mCache[$wgLanguageCode] ); -// foreach ( $mostused as $key => $value ) { -// $mostused[$key] = "$value/$code"; +// +// if (success) { +// # Done, no need to retry +// break; +// } +// +// # We need to call loadFromDB. Limit the concurrency to one process. +// # This prevents the site from going down when the cache expires. +// # Note that the DB slam protection lock here is non-blocking. +// loadStatus = this.loadFromDBWithLock(code, where, mode); +// if (loadStatus === true) { +// success = true; +// break; +// } elseif (staleCache) { +// # Use the stale cache while some other thread constructs the new one +// where[] = 'using stale cache'; +// this.cache.set(code, staleCache); +// success = true; +// break; +// } elseif (failedAttempts > 0) { +// # Already blocked once, so avoid another lock/unlock cycle. +// # This case will typically be hit if memcached is down, or if +// # loadFromDB() takes longer than LOCK_WAIT. +// where[] = "could not acquire status key."; +// break; +// } elseif (loadStatus === 'cantacquire') { +// # Wait for the other thread to finish, then retry. Normally, +// # the memcached get() will then yield the other thread's result. +// where[] = 'waited for other thread to complete'; +// this.getReentrantScopedLock(cacheKey); +// } else { +// # Disable cache; loadStatus is 'disabled' +// break; // } // } +// } // -// if ( count( $mostused ) ) { -// $conds['page_title'] = $mostused; -// } elseif ( $code !== $wgLanguageCode ) { -// $conds[] = 'page_title' . $dbr->buildLike( $dbr->anyString(), '/', $code ); +// if (!success) { +// where[] = 'loading FAILED - cache is disabled'; +// this.mDisable = true; +// this.cache.set(code, []); +// wfDebugLog('MessageCacheError', __METHOD__ . ": Failed to load code\n"); +// # This used to throw an exception, but that led to nasty side effects like +// # the whole wiki being instantly down if the memcached server died +// } else { +// # All good, just record the success +// this.loadedLanguages[code] = true; +// } +// +// if (!this.cache.has(code)) { // sanity +// throw new LogicException("Process cache for 'code' should be set by now."); +// } +// +// info = implode(', ', where); +// wfDebugLog('MessageCache', __METHOD__ . ": Loading code... info\n"); +// +// return success; +// } +// +// /** +// * @param string code +// * @param array &where List of wfDebug() comments +// * @param int|null mode Use MessageCache.FOR_UPDATE to use DB_MASTER +// * @return bool|string True on success or one of ("cantacquire", "disabled") +// */ +// protected function loadFromDBWithLock(code, array &where, mode = null) { +// # If cache updates on all levels fail, give up on message overrides. +// # This is to avoid easy site outages; see saveSuccess comments below. +// statusKey = this.clusterCache.makeKey('messages', code, 'status'); +// status = this.clusterCache.get(statusKey); +// if (status === 'error') { +// where[] = "could not load; method is still globally disabled"; +// return 'disabled'; +// } +// +// # Now let's regenerate +// where[] = 'loading from database'; +// +// # Lock the cache to prevent conflicting writes. +// # This lock is non-blocking so stale cache can quickly be used. +// # Note that load() will call a blocking getReentrantScopedLock() +// # after this if it really need to wait for any current thread. +// cacheKey = this.clusterCache.makeKey('messages', code); +// scopedLock = this.getReentrantScopedLock(cacheKey, 0); +// if (!scopedLock) { +// where[] = 'could not acquire main lock'; +// return 'cantacquire'; +// } +// +// cache = this.loadFromDB(code, mode); +// this.cache.set(code, cache); +// saveSuccess = this.saveToCaches(cache, 'all', code); +// +// if (!saveSuccess) { +// /** +// * Cache save has failed. +// * +// * There are two main scenarios where this could be a problem: +// * - The cache is more than the maximum size (typically 1MB compressed). +// * - Memcached has no space remaining in the relevant slab class. This is +// * unlikely with recent versions of memcached. +// * +// * Either way, if there is a local cache, nothing bad will happen. If there +// * is no local cache, disabling the message cache for all requests avoids +// * incurring a loadFromDB() overhead on every request, and thus saves the +// * wiki from complete downtime under moderate traffic conditions. +// */ +// if (this.srvCache instanceof EmptyBagOStuff) { +// this.clusterCache.set(statusKey, 'error', 60 * 5); +// where[] = 'could not save cache, disabled globally for 5 minutes'; // } else { -// # Effectively disallows use of '/' character in NS_MEDIAWIKI for uses -// # other than language code. -// $conds[] = 'page_title NOT' . $dbr->buildLike( $dbr->anyString(), '/', $dbr->anyString() ); +// where[] = "could not save global cache"; // } +// } // -// # Conditions to fetch oversized pages to ignore them -// $bigConds = $conds; -// $bigConds[] = 'page_len > ' . intval( $wgMaxMsgCacheEntrySize ); +// return true; +// } // -// # Load titles for all oversized pages in the MediaWiki namespace -// $res = $dbr->select( -// 'page', -// [ 'page_title', 'page_latest' ], -// $bigConds, -// __METHOD__ . "($code)-big" -// ); -// foreach ( $res as $row ) { -// $cache[$row->page_title] = '!TOO BIG'; -// // At least include revision ID so page changes are reflected in the hash -// $cache['EXCESSIVE'][$row->page_title] = $row->page_latest; +// /** +// * Loads cacheable messages from the database. Messages bigger than +// * wgMaxMsgCacheEntrySize are assigned a special value, and are loaded +// * on-demand from the database later. +// * +// * @param string code Language code +// * @param int|null mode Use MessageCache.FOR_UPDATE to skip process cache +// * @return array Loaded messages for storing in caches +// */ +// protected function loadFromDB(code, mode = null) { +// global wgMaxMsgCacheEntrySize, wgLanguageCode, wgAdaptiveMessageCache; +// +// // (T164666) The query here performs really poorly on WMF's +// // contributions replicas. We don't have a way to say "any group except +// // contributions", so for the moment let's specify 'api'. +// // @todo: Get rid of this hack. +// dbr = wfGetDB((mode == self.FOR_UPDATE) ? DB_MASTER : DB_REPLICA, 'api'); +// +// cache = []; +// +// mostused = []; // list of "/" +// if (wgAdaptiveMessageCache && code !== wgLanguageCode) { +// if (!this.cache.has(wgLanguageCode)) { +// this.load(wgLanguageCode); // } +// mostused = array_keys(this.cache.get(wgLanguageCode)); +// foreach (mostused as key => value) { +// mostused[key] = "value/code"; +// } +// } // -// # Conditions to load the remaining pages with their contents -// $smallConds = $conds; -// $smallConds[] = 'page_latest=rev_id'; -// $smallConds[] = 'rev_text_id=old_id'; -// $smallConds[] = 'page_len <= ' . intval( $wgMaxMsgCacheEntrySize ); +// // Get the list of software-defined messages in core/extensions +// overridable = array_flip(Language.getMessageKeysFor(wgLanguageCode)); // -// $res = $dbr->select( -// [ 'page', 'revision', 'text' ], -// [ 'page_title', 'old_text', 'old_flags' ], -// $smallConds, -// __METHOD__ . "($code)-small" -// ); +// // Common conditions +// conds = [ +// 'page_is_redirect' => 0, +// 'page_namespace' => NS_MEDIAWIKI, +// ]; +// if (count(mostused)) { +// conds['page_title'] = mostused; +// } elseif (code !== wgLanguageCode) { +// conds[] = 'page_title' . dbr.buildLike(dbr.anyString(), '/', code); +// } else { +// # Effectively disallows use of '/' character in NS_MEDIAWIKI for uses +// # other than language code. +// conds[] = 'page_title NOT' . +// dbr.buildLike(dbr.anyString(), '/', dbr.anyString()); +// } // -// foreach ( $res as $row ) { -// $text = Revision::getRevisionText( $row ); -// if ( $text === false ) { -// // Failed to fetch data; possible ES errors? -// // Store a marker to fetch on-demand as a workaround... -// // TODO Use a differnt marker -// $entry = '!TOO BIG'; +// // Set the stubs for oversized software-defined messages in the main cache map +// res = dbr.select( +// 'page', +// [ 'page_title', 'page_latest' ], +// array_merge(conds, [ 'page_len > ' . intval(wgMaxMsgCacheEntrySize) ]), +// __METHOD__ . "(code)-big" +// ); +// foreach (res as row) { +// // Include entries/stubs for all keys in mostused in adaptive mode +// if (wgAdaptiveMessageCache || this.isMainCacheable(row.page_title, overridable)) { +// cache[row.page_title] = '!TOO BIG'; +// } +// // At least include revision ID so page changes are reflected in the hash +// cache['EXCESSIVE'][row.page_title] = row.page_latest; +// } +// +// // Set the text for small software-defined messages in the main cache map +// revisionStore = MediaWikiServices.getInstance().getRevisionStore(); +// revQuery = revisionStore.getQueryInfo([ 'page', 'user' ]); +// res = dbr.select( +// revQuery['tables'], +// revQuery['fields'], +// array_merge(conds, [ +// 'page_len <= ' . intval(wgMaxMsgCacheEntrySize), +// 'page_latest = rev_id' // get the latest revision only +// ]), +// __METHOD__ . "(code)-small", +// [], +// revQuery['joins'] +// ); +// foreach (res as row) { +// // Include entries/stubs for all keys in mostused in adaptive mode +// if (wgAdaptiveMessageCache || this.isMainCacheable(row.page_title, overridable)) { +// try { +// rev = revisionStore.newRevisionFromRow(row); +// content = rev.getContent(MediaWiki\Revision\SlotRecord.MAIN); +// text = this.getMessageTextFromContent(content); +// } catch (Exception ex) { +// text = false; +// } +// +// if (!is_string(text)) { +// entry = '!ERROR'; // wfDebugLog( // 'MessageCache', // __METHOD__ -// . ": failed to load message page text for {$row->page_title} ($code)" +// . ": failed to load message page text for {row.page_title} (code)" // ); // } else { -// $entry = ' ' . $text; +// entry = ' ' . text; // } -// $cache[$row->page_title] = $entry; +// cache[row.page_title] = entry; +// } else { +// // T193271: cache object gets too big and slow to generate. +// // At least include revision ID so page changes are reflected in the hash. +// cache['EXCESSIVE'][row.page_title] = row.page_latest; // } -// -// $cache['VERSION'] = MSG_CACHE_VERSION; -// ksort( $cache ); -// -// # Hash for validating local cache (APC). No need to take into account -// # messages larger than $wgMaxMsgCacheEntrySize, since those are only -// # stored and fetched from memcache. -// $cache['HASH'] = md5( serialize( $cache ) ); -// $cache['EXPIRY'] = wfTimestamp( TS_MW, time() + $this->mExpiry ); -// -// return $cache; // } // -// /** -// * Updates cache as necessary when message page is changed -// * -// * @param String $title Message cache key with initial uppercase letter. -// * @param String|boolean $text New contents of the page (false if deleted) -// */ -// public function replace( $title, $text ) { -// global $wgLanguageCode; +// cache['VERSION'] = MSG_CACHE_VERSION; +// ksort(cache); // -// if ( $this->mDisable ) { -// return; +// # Hash for validating local cache (APC). No need to take into account +// # messages larger than wgMaxMsgCacheEntrySize, since those are only +// # stored and fetched from memcache. +// cache['HASH'] = md5(serialize(cache)); +// cache['EXPIRY'] = wfTimestamp(TS_MW, time() + this.mExpiry); +// unset(cache['EXCESSIVE']); // only needed for hash +// +// return cache; +// } +// +// /** +// * @param string name Message name (possibly with /code suffix) +// * @param array overridable Map of (key => unused) for software-defined messages +// * @return bool +// */ +// private function isMainCacheable(name, array overridable) { +// // Convert first letter to lowercase, and strip /code suffix +// name = this.contLang.lcfirst(name); +// msg = preg_replace('/\/[a-z0-9-]{2,}/', '', name); +// // Include common conversion table pages. This also avoids problems with +// // Installer.parse() bailing out due to disallowed DB queries (T207979). +// return (isset(overridable[msg]) || strpos(name, 'conversiontable/') === 0); +// } +// +// /** +// * Updates cache as necessary when message page is changed +// * +// * @param string title Message cache key with initial uppercase letter +// * @param string|bool text New contents of the page (false if deleted) +// */ +// public function replace(title, text) { +// global wgLanguageCode; +// +// if (this.mDisable) { +// return; +// } +// +// list(msg, code) = this.figureMessage(title); +// if (strpos(title, '/') !== false && code === wgLanguageCode) { +// // Content language overrides do not use the / suffix +// return; +// } +// +// // (a) Update the process cache with the new message text +// if (text === false) { +// // Page deleted +// this.cache.setField(code, title, '!NONEXISTENT'); +// } else { +// // Ignore wgMaxMsgCacheEntrySize so the process cache is up to date +// this.cache.setField(code, title, ' ' . text); +// } +// +// // (b) Update the shared caches in a deferred update with a fresh DB snapshot +// DeferredUpdates.addUpdate( +// new MessageCacheUpdate(code, title, msg), +// DeferredUpdates.PRESEND +// ); +// } +// +// /** +// * @param string code +// * @param array[] replacements List of (title, message key) pairs +// * @throws MWException +// */ +// public function refreshAndReplaceInternal(code, array replacements) { +// global wgMaxMsgCacheEntrySize; +// +// // Allow one caller at a time to avoid race conditions +// scopedLock = this.getReentrantScopedLock( +// this.clusterCache.makeKey('messages', code) +// ); +// if (!scopedLock) { +// foreach (replacements as list(title)) { +// LoggerFactory.getInstance('MessageCache').error( +// __METHOD__ . ': could not acquire lock to update {title} ({code})', +// [ 'title' => title, 'code' => code ]); // } // -// list( $msg, $code ) = $this->figureMessage( $title ); -// if ( strpos( $title, '/' ) !== false && $code === $wgLanguageCode ) { -// // Content language overrides do not use the / suffix -// return; -// } +// return; +// } // -// // (a) Update the process cache with the new message text -// if ( $text === false ) { -// // Page deleted -// $this->mCache[$code][$title] = '!NONEXISTENT'; +// // Load the existing cache to update it in the local DC cache. +// // The other DCs will see a hash mismatch. +// if (this.load(code, self.FOR_UPDATE)) { +// cache = this.cache.get(code); +// } else { +// // Err? Fall back to loading from the database. +// cache = this.loadFromDB(code, self.FOR_UPDATE); +// } +// // Check if individual cache keys should exist and update cache accordingly +// newTextByTitle = []; // map of (title => content) +// newBigTitles = []; // map of (title => latest revision ID), like EXCESSIVE in loadFromDB() +// foreach (replacements as list(title)) { +// page = WikiPage.factory(Title.makeTitle(NS_MEDIAWIKI, title)); +// page.loadPageData(page.READ_LATEST); +// text = this.getMessageTextFromContent(page.getContent()); +// // Remember the text for the blob store update later on +// newTextByTitle[title] = text; +// // Note that if text is false, then cache should have a !NONEXISTANT entry +// if (!is_string(text)) { +// cache[title] = '!NONEXISTENT'; +// } elseif (strlen(text) > wgMaxMsgCacheEntrySize) { +// cache[title] = '!TOO BIG'; +// newBigTitles[title] = page.getLatest(); // } else { -// // Ignore $wgMaxMsgCacheEntrySize so the process cache is up to date -// $this->mCache[$code][$title] = ' ' . $text; +// cache[title] = ' ' . text; // } -// -// // (b) Update the shared caches in a deferred update with a fresh DB snapshot -// DeferredUpdates::addCallableUpdate( -// function () use ( $title, $msg, $code ) { -// global $wgContLang, $wgMaxMsgCacheEntrySize; -// // Allow one caller at a time to avoid race conditions -// $scopedLock = $this->getReentrantScopedLock( wfMemcKey( 'messages', $code ) ); -// if ( !$scopedLock ) { -// LoggerFactory::getInstance( 'MessageCache' )->error( -// __METHOD__ . ': could not acquire synchronized to update {title} ({code})', -// [ 'title' => $title, 'code' => $code ] ); -// return; -// } -// // Load the messages from the master DB to avoid race conditions -// $this->loadFromDB( $code, self::FOR_UPDATE ); -// // Load the process cache values and set the per-title cache keys -// $page = WikiPage::factory( Title::makeTitle( NS_MEDIAWIKI, $title ) ); -// $page->loadPageData( $page::READ_LATEST ); -// $text = $this->getMessageTextFromContent( $page->getContent() ); -// // Check if an individual cache key should exist and update cache accordingly -// $titleKey = $this->wanCache->makeKey( -// 'messages-big', $this->mCache[$code]['HASH'], $title ); -// if ( is_string( $text ) && strlen( $text ) > $wgMaxMsgCacheEntrySize ) { -// $this->wanCache->set( $titleKey, ' ' . $text, $this->mExpiry ); -// } -// // Mark this cache as definitely being "latest" (non-volatile) so -// // load() calls do try to refresh the cache with replica DB data -// $this->mCache[$code]['LATEST'] = time(); -// // Pre-emptively update the local datacenter cache so things like edit filter and -// // blacklist changes are reflect immediately, as these often use MediaWiki: pages. -// // The datacenter handling replace() calls should be the same one handling edits -// // as they require HTTP POST. -// $this->saveToCaches( $this->mCache[$code], 'all', $code ); -// // Release the synchronized now that the cache is saved -// ScopedCallback::consume( $scopedLock ); -// -// // Relay the purge. Touching this check key expires cache contents -// // and local cache (APC) validation hash across all datacenters. -// $this->wanCache->touchCheckKey( wfMemcKey( 'messages', $code ) ); -// // Also delete cached sidebar... just in case it is affected -// // @TODO: shouldn't this be $code === $wgLanguageCode? -// if ( $code === 'en' ) { -// // Purge all language sidebars, e.g. on ?action=purge to the sidebar messages -// $codes = array_keys( Language::fetchLanguageNames() ); -// } else { -// // Purge only the sidebar for this language -// $codes = [ $code ]; -// } -// foreach ( $codes as $code ) { -// $this->wanCache->delete( wfMemcKey( 'sidebar', $code ) ); -// } -// -// // Purge the message in the message blob store -// $resourceloader = RequestContext::getMain()->getOutput()->getResourceLoader(); -// $blobStore = $resourceloader->getMessageBlobStore(); -// $blobStore->updateMessage( $wgContLang->lcfirst( $msg ) ); -// -// Hooks::run( 'MessageCacheReplace', [ $title, $text ] ); -// }, -// DeferredUpdates::PRESEND +// } +// // Update HASH for the new key. Incorporates various administrative keys, +// // including the old HASH (and thereby the EXCESSIVE value from loadFromDB() +// // and previous replace() calls), but that doesn't really matter since we +// // only ever compare it for equality with a copy saved by saveToCaches(). +// cache['HASH'] = md5(serialize(cache + [ 'EXCESSIVE' => newBigTitles ])); +// // Update the too-big WAN cache entries now that we have the new HASH +// foreach (newBigTitles as title => id) { +// // Match logic of loadCachedMessagePageEntry() +// this.wanCache.set( +// this.bigMessageCacheKey(cache['HASH'], title), +// ' ' . newTextByTitle[title], +// this.mExpiry // ); // } +// // Mark this cache as definitely being "latest" (non-volatile) so +// // load() calls do not try to refresh the cache with replica DB data +// cache['LATEST'] = time(); +// // Update the process cache +// this.cache.set(code, cache); +// // Pre-emptively update the local datacenter cache so things like edit filter and +// // blacklist changes are reflected immediately; these often use MediaWiki: pages. +// // The datacenter handling replace() calls should be the same one handling edits +// // as they require HTTP POST. +// this.saveToCaches(cache, 'all', code); +// // Release the lock now that the cache is saved +// ScopedCallback.consume(scopedLock); // -// /** -// * Is the given cache array expired due to time passing or a version change? -// * -// * @param array $cache -// * @return boolean -// */ -// protected function isCacheExpired( $cache ) { -// if ( !isset( $cache['VERSION'] ) || !isset( $cache['EXPIRY'] ) ) { -// return true; -// } -// if ( $cache['VERSION'] != MSG_CACHE_VERSION ) { -// return true; -// } -// if ( wfTimestampNow() >= $cache['EXPIRY'] ) { -// return true; -// } +// // Relay the purge. Touching this check key expires cache contents +// // and local cache (APC) validation hash across all datacenters. +// this.wanCache.touchCheckKey(this.getCheckKey(code)); // +// // Purge the messages in the message blob store and fire any hook handlers +// resourceloader = RequestContext.getMain().getOutput().getResourceLoader(); +// blobStore = resourceloader.getMessageBlobStore(); +// foreach (replacements as list(title, msg)) { +// blobStore.updateMessage(this.contLang.lcfirst(msg)); +// Hooks.run('MessageCacheReplace', [ title, newTextByTitle[title] ]); +// } +// } +// +// /** +// * Is the given cache array expired due to time passing or a version change? +// * +// * @param array cache +// * @return bool +// */ +// protected function isCacheExpired(cache) { +// if (!isset(cache['VERSION']) || !isset(cache['EXPIRY'])) { +// return true; +// } +// if (cache['VERSION'] != MSG_CACHE_VERSION) { +// return true; +// } +// if (wfTimestampNow() >= cache['EXPIRY']) { +// return true; +// } +// +// return false; +// } +// +// /** +// * Shortcut to update caches. +// * +// * @param array cache Cached messages with a version. +// * @param string dest Either "local-only" to save to local caches only +// * or "all" to save to all caches. +// * @param string|bool code Language code (default: false) +// * @return bool +// */ +// protected function saveToCaches(array cache, dest, code = false) { +// if (dest === 'all') { +// cacheKey = this.clusterCache.makeKey('messages', code); +// success = this.clusterCache.set(cacheKey, cache); +// this.setValidationHash(code, cache); +// } else { +// success = true; +// } +// +// this.saveToLocalCache(code, cache); +// +// return success; +// } +// +// /** +// * Get the md5 used to validate the local APC cache +// * +// * @param string code +// * @return array (hash or false, bool expiry/volatility status) +// */ +// protected function getValidationHash(code) { +// curTTL = null; +// value = this.wanCache.get( +// this.wanCache.makeKey('messages', code, 'hash', 'v1'), +// curTTL, +// [ this.getCheckKey(code) ] +// ); +// +// if (value) { +// hash = value['hash']; +// if ((time() - value['latest']) < WANObjectCache.TTL_MINUTE) { +// // Cache was recently updated via replace() and should be up-to-date. +// // That method is only called in the primary datacenter and uses FOR_UPDATE. +// // Also, it is unlikely that the current datacenter is *now* secondary one. +// expired = false; +// } else { +// // See if the "check" key was bumped after the hash was generated +// expired = (curTTL < 0); +// } +// } else { +// // No hash found at all; cache must regenerate to be safe +// hash = false; +// expired = true; +// } +// +// return [ hash, expired ]; +// } +// +// /** +// * Set the md5 used to validate the local disk cache +// * +// * If cache has a 'LATEST' UNIX timestamp key, then the hash will not +// * be treated as "volatile" by getValidationHash() for the next few seconds. +// * This is triggered when cache is generated using FOR_UPDATE mode. +// * +// * @param string code +// * @param array cache Cached messages with a version +// */ +// protected function setValidationHash(code, array cache) { +// this.wanCache.set( +// this.wanCache.makeKey('messages', code, 'hash', 'v1'), +// [ +// 'hash' => cache['HASH'], +// 'latest' => cache['LATEST'] ?? 0 +// ], +// WANObjectCache.TTL_INDEFINITE +// ); +// } +// +// /** +// * @param string key A language message cache key that stores blobs +// * @param int timeout Wait timeout in seconds +// * @return null|ScopedCallback +// */ +// protected function getReentrantScopedLock(key, timeout = self.WAIT_SEC) { +// return this.clusterCache.getScopedLock(key, timeout, self.LOCK_TTL, __METHOD__); +// } +// +// /** +// * Get a message from either the content language or the user language. +// * +// * First, assemble a list of languages to attempt getting the message from. This +// * chain begins with the requested language and its fallbacks and then continues with +// * the content language and its fallbacks. For each language in the chain, the following +// * process will occur (in this order): +// * 1. If a language-specific override, i.e., [[MW:msg/lang]], is available, use that. +// * Note: for the content language, there is no /lang subpage. +// * 2. Fetch from the static CDB cache. +// * 3. If available, check the database for fallback language overrides. +// * +// * This process provides a number of guarantees. When changing this code, make sure all +// * of these guarantees are preserved. +// * * If the requested language is *not* the content language, then the CDB cache for that +// * specific language will take precedence over the root database page ([[MW:msg]]). +// * * Fallbacks will be just that: fallbacks. A fallback language will never be reached if +// * the message is available *anywhere* in the language for which it is a fallback. +// * +// * @param string key The message key +// * @param bool useDB If true, look for the message in the DB, false +// * to use only the compiled l10n cache. +// * @param bool|string|object langcode Code of the language to get the message for. +// * - If string and a valid code, will create a standard language object +// * - If string but not a valid code, will create a basic language object +// * - If boolean and false, create object from the current users language +// * - If boolean and true, create object from the wikis content language +// * - If language object, use it as given +// * +// * @throws MWException When given an invalid key +// * @return string|bool False if the message doesn't exist, otherwise the +// * message (which can be empty) +// */ +// function get(key, useDB = true, langcode = true) { +// if (is_int(key)) { +// // Fix numerical strings that somehow become ints +// // on their way here +// key = (string)key; +// } elseif (!is_string(key)) { +// throw new MWException('Non-string key given'); +// } elseif (key === '') { +// // Shortcut: the empty key is always missing // return false; // } // -// /** -// * Shortcut to update caches. -// * -// * @param array $cache Cached messages with a version. -// * @param String $dest Either "local-only" to save to local caches only -// * or "all" to save to all caches. -// * @param String|boolean $code Language code (default: false) -// * @return boolean -// */ -// protected function saveToCaches( array $cache, $dest, $code = false ) { -// if ( $dest === 'all' ) { -// $cacheKey = wfMemcKey( 'messages', $code ); -// $success = $this->clusterCache->set( $cacheKey, $cache ); -// $this->setValidationHash( $code, $cache ); -// } else { -// $success = true; -// } +// // Normalise title-case input (with some inlining) +// lckey = self.normalizeKey(key); // -// $this->saveToLocalCache( $code, $cache ); +// Hooks.run('MessageCache.get', [ &lckey ]); // -// return $success; -// } +// // Loop through each language in the fallback list until we find something useful +// message = this.getMessageFromFallbackChain( +// wfGetLangObj(langcode), +// lckey, +// !this.mDisable && useDB +// ); // -// /** -// * Get the md5 used to validate the local APC cache -// * -// * @param String $code -// * @return array (hash or false, boolean expiry/volatility status) -// */ -// protected function getValidationHash( $code ) { -// $curTTL = null; -// $value = $this->wanCache->get( -// $this->wanCache->makeKey( 'messages', $code, 'hash', 'v1' ), -// $curTTL, -// [ wfMemcKey( 'messages', $code ) ] -// ); -// -// if ( $value ) { -// $hash = $value['hash']; -// if ( ( time() - $value['latest'] ) < WANObjectCache::TTL_MINUTE ) { -// // Cache was recently updated via replace() and should be up-to-date. -// // That method is only called in the primary datacenter and uses FOR_UPDATE. -// // Also, it is unlikely that the current datacenter is *now* secondary one. -// $expired = false; -// } else { -// // See if the "check" key was bumped after the hash was generated -// $expired = ( $curTTL < 0 ); +// // If we still have no message, maybe the key was in fact a full key so try that +// if (message === false) { +// parts = explode('/', lckey); +// // We may get calls for things that are http-urls from sidebar +// // Let's not load nonexistent languages for those +// // They usually have more than one slash. +// if (count(parts) == 2 && parts[1] !== '') { +// message = Language.getMessageFor(parts[0], parts[1]); +// if (message === null) { +// message = false; // } -// } else { -// // No hash found at all; cache must regenerate to be safe -// $hash = false; -// $expired = true; // } -// -// return [ $hash, $expired ]; // } // -// /** -// * Set the md5 used to validate the local disk cache -// * -// * If $cache has a 'LATEST' UNIX timestamp key, then the hash will not -// * be treated as "volatile" by getValidationHash() for the next few seconds. -// * This is triggered when $cache is generated using FOR_UPDATE mode. -// * -// * @param String $code -// * @param array $cache Cached messages with a version -// */ -// protected function setValidationHash( $code, array $cache ) { -// $this->wanCache->set( -// $this->wanCache->makeKey( 'messages', $code, 'hash', 'v1' ), +// // Post-processing if the message exists +// if (message !== false) { +// // Fix whitespace +// message = str_replace( // [ -// 'hash' => $cache['HASH'], -// 'latest' => isset( $cache['LATEST'] ) ? $cache['LATEST'] : 0 +// # Fix for trailing whitespace, removed by textarea +// ' ', +// # Fix for NBSP, converted to space by firefox +// ' ', +// ' ', +// '­' // ], -// WANObjectCache::TTL_INDEFINITE +// [ +// ' ', +// "\\u{00A0}", +// "\\u{00A0}", +// "\\u{00AD}" +// ], +// message // ); // } // -// /** -// * @param String $key A language message cache key that stores blobs -// * @param integer $timeout Wait timeout in seconds -// * @return null|ScopedCallback -// */ -// protected function getReentrantScopedLock( $key, $timeout = self::WAIT_SEC ) { -// return $this->clusterCache->getScopedLock( $key, $timeout, self::LOCK_TTL, __METHOD__ ); +// return message; +// } +// +// /** +// * Given a language, try and fetch messages from that language. +// * +// * Will also consider fallbacks of that language, the site language, and fallbacks for +// * the site language. +// * +// * @see MessageCache.get +// * @param Language|StubObject lang Preferred language +// * @param string lckey Lowercase key for the message (as for localisation cache) +// * @param bool useDB Whether to include messages from the wiki database +// * @return string|bool The message, or false if not found +// */ +// protected function getMessageFromFallbackChain(lang, lckey, useDB) { +// alreadyTried = []; +// +// // First try the requested language. +// message = this.getMessageForLang(lang, lckey, useDB, alreadyTried); +// if (message !== false) { +// return message; // } // -// /** -// * Get a message from either the content language or the user language. -// * -// * First, assemble a list of languages to attempt getting the message from. This -// * chain begins with the requested language and its fallbacks and then continues with -// * the content language and its fallbacks. For each language in the chain, the following -// * process will occur (in this order): -// * 1. If a language-specific override, i.e., [[MW:msg/lang]], is available, use that. -// * Note: for the content language, there is no /lang subpage. -// * 2. Fetch from the static CDB cache. -// * 3. If available, check the database for fallback language overrides. -// * -// * This process provides a number of guarantees. When changing this code, make sure all -// * of these guarantees are preserved. -// * * If the requested language is *not* the content language, then the CDB cache for that -// * specific language will take precedence over the root database page ([[MW:msg]]). -// * * Fallbacks will be just that: fallbacks. A fallback language will never be reached if -// * the message is available *anywhere* in the language for which it is a fallback. -// * -// * @param String $key The message key -// * @param boolean $useDB If true, look for the message in the DB, false -// * to use only the compiled l10n cache. -// * @param boolean|String|Object $langcode Code of the language to get the message for. -// * - If String and a valid code, will create a standard language Object -// * - If String but not a valid code, will create a basic language Object -// * - If boolean and false, create Object from the current users language -// * - If boolean and true, create Object from the wikis content language -// * - If language Object, use it as given -// * @param boolean $isFullKey Specifies whether $key is a two part key "msg/lang". -// * -// * @throws MWException When given an invalid key -// * @return String|boolean False if the message doesn't exist, otherwise the -// * message (which can be empty) -// */ -// function get( $key, $useDB = true, $langcode = true, $isFullKey = false ) { -// if ( is_int( $key ) ) { -// // Fix numerical strings that somehow become ints -// // on their way here -// $key = (String)$key; -// } elseif ( !is_string( $key ) ) { -// throw new MWException( 'Non-String key given' ); -// } elseif ( $key === '' ) { -// // Shortcut: the empty key is always missing +// // Now try checking the site language. +// message = this.getMessageForLang(this.contLang, lckey, useDB, alreadyTried); +// return message; +// } +// +// /** +// * Given a language, try and fetch messages from that language and its fallbacks. +// * +// * @see MessageCache.get +// * @param Language|StubObject lang Preferred language +// * @param string lckey Lowercase key for the message (as for localisation cache) +// * @param bool useDB Whether to include messages from the wiki database +// * @param bool[] alreadyTried Contains true for each language that has been tried already +// * @return string|bool The message, or false if not found +// */ +// private function getMessageForLang(lang, lckey, useDB, &alreadyTried) { +// langcode = lang.getCode(); +// +// // Try checking the database for the requested language +// if (useDB) { +// uckey = this.contLang.ucfirst(lckey); +// +// if (!isset(alreadyTried[langcode])) { +// message = this.getMsgFromNamespace( +// this.getMessagePageName(langcode, uckey), +// langcode +// ); +// if (message !== false) { +// return message; +// } +// alreadyTried[langcode] = true; +// } +// } else { +// uckey = null; +// } +// +// // Check the CDB cache +// message = lang.getMessage(lckey); +// if (message !== null) { +// return message; +// } +// +// // Try checking the database for all of the fallback languages +// if (useDB) { +// fallbackChain = Language.getFallbacksFor(langcode); +// +// foreach (fallbackChain as code) { +// if (isset(alreadyTried[code])) { +// continue; +// } +// +// message = this.getMsgFromNamespace( +// this.getMessagePageName(code, uckey), code); +// +// if (message !== false) { +// return message; +// } +// alreadyTried[code] = true; +// } +// } +// +// return false; +// } +// +// /** +// * Get the message page name for a given language +// * +// * @param string langcode +// * @param string uckey Uppercase key for the message +// * @return string The page name +// */ +// private function getMessagePageName(langcode, uckey) { +// global wgLanguageCode; +// +// if (langcode === wgLanguageCode) { +// // Messages created in the content language will not have the /lang extension +// return uckey; +// } else { +// return "uckey/langcode"; +// } +// } +// +// /** +// * Get a message from the MediaWiki namespace, with caching. The key must +// * first be converted to two-part lang/msg form if necessary. +// * +// * Unlike self.get(), this function doesn't resolve fallback chains, and +// * some callers require this behavior. LanguageConverter.parseCachedTable() +// * and self.get() are some examples in core. +// * +// * @param string title Message cache key with initial uppercase letter +// * @param string code Code denoting the language to try +// * @return string|bool The message, or false if it does not exist or on error +// */ +// public function getMsgFromNamespace(title, code) { +// // Load all MediaWiki page definitions into cache. Note that individual keys +// // already loaded into cache during this request remain in the cache, which +// // includes the value of hook-defined messages. +// this.load(code); +// +// entry = this.cache.getField(code, title); +// +// if (entry !== null) { +// // Message page exists as an override of a software messages +// if (substr(entry, 0, 1) === ' ') { +// // The message exists and is not '!TOO BIG' or '!ERROR' +// return (string)substr(entry, 1); +// } elseif (entry === '!NONEXISTENT') { +// // The text might be '-' or missing due to some data loss // return false; // } -// -// // For full keys, get the language code from the key -// $pos = strrpos( $key, '/' ); -// if ( $isFullKey && $pos !== false ) { -// $langcode = substr( $key, $pos + 1 ); -// $key = substr( $key, 0, $pos ); -// } -// -// // Normalise title-case input (with some inlining) -// $lckey = MessageCache::normalizeKey( $key ); -// -// Hooks::run( 'MessageCache::get', [ &$lckey ] ); -// -// // Loop through each language in the fallback list until we find something useful -// $lang = wfGetLangObj( $langcode ); -// $message = $this->getMessageFromFallbackChain( -// $lang, -// $lckey, -// !$this->mDisable && $useDB +// // Load the message page, utilizing the individual message cache. +// // If the page does not exist, there will be no hook handler fallbacks. +// entry = this.loadCachedMessagePageEntry( +// title, +// code, +// this.cache.getField(code, 'HASH') // ); -// -// // If we still have no message, maybe the key was in fact a full key so try that -// if ( $message === false ) { -// $parts = explode( '/', $lckey ); -// // We may get calls for things that are http-urls from sidebar -// // Let's not load nonexistent languages for those -// // They usually have more than one slash. -// if ( count( $parts ) == 2 && $parts[1] !== '' ) { -// $message = Language::getMessageFor( $parts[0], $parts[1] ); -// if ( $message === null ) { -// $message = false; -// } -// } -// } -// -// // Post-processing if the message exists -// if ( $message !== false ) { -// // Fix whitespace -// $message = str_replace( -// [ -// # Fix for trailing whitespace, removed by textarea -// ' ', -// # Fix for NBSP, converted to space by firefox -// ' ', -// ' ', -// '­' -// ], -// [ -// ' ', -// "\xc2\xa0", -// "\xc2\xa0", -// "\xc2\xad" -// ], -// $message +// } else { +// // Message page either does not exist or does not override a software message +// if (!this.isMainCacheable(title, this.overridable)) { +// // Message page does not override any software-defined message. A custom +// // message might be defined to have content or settings specific to the wiki. +// // Load the message page, utilizing the individual message cache as needed. +// entry = this.loadCachedMessagePageEntry( +// title, +// code, +// this.cache.getField(code, 'HASH') // ); // } -// -// return $message; -// } -// -// /** -// * Given a language, try and fetch messages from that language. -// * -// * Will also consider fallbacks of that language, the site language, and fallbacks for -// * the site language. -// * -// * @see MessageCache::get -// * @param Language|StubObject $lang Preferred language -// * @param String $lckey Lowercase key for the message (as for localisation cache) -// * @param boolean $useDB Whether to include messages from the wiki database -// * @return String|boolean The message, or false if not found -// */ -// protected function getMessageFromFallbackChain( $lang, $lckey, $useDB ) { -// global $wgContLang; -// -// $alreadyTried = []; -// -// // First try the requested language. -// $message = $this->getMessageForLang( $lang, $lckey, $useDB, $alreadyTried ); -// if ( $message !== false ) { -// return $message; -// } -// -// // Now try checking the site language. -// $message = $this->getMessageForLang( $wgContLang, $lckey, $useDB, $alreadyTried ); -// return $message; -// } -// -// /** -// * Given a language, try and fetch messages from that language and its fallbacks. -// * -// * @see MessageCache::get -// * @param Language|StubObject $lang Preferred language -// * @param String $lckey Lowercase key for the message (as for localisation cache) -// * @param boolean $useDB Whether to include messages from the wiki database -// * @param boolean[] $alreadyTried Contains true for each language that has been tried already -// * @return String|boolean The message, or false if not found -// */ -// private function getMessageForLang( $lang, $lckey, $useDB, &$alreadyTried ) { -// global $wgContLang; -// -// $langcode = $lang->getCode(); -// -// // Try checking the database for the requested language -// if ( $useDB ) { -// $uckey = $wgContLang->ucfirst( $lckey ); -// -// if ( !isset( $alreadyTried[ $langcode ] ) ) { -// $message = $this->getMsgFromNamespace( -// $this->getMessagePageName( $langcode, $uckey ), -// $langcode -// ); -// -// if ( $message !== false ) { -// return $message; -// } -// $alreadyTried[ $langcode ] = true; +// if (entry === null || substr(entry, 0, 1) !== ' ') { +// // Message does not have a MediaWiki page definition; try hook handlers +// message = false; +// Hooks.run('MessagesPreLoad', [ title, &message, code ]); +// if (message !== false) { +// this.cache.setField(code, title, ' ' . message); +// } else { +// this.cache.setField(code, title, '!NONEXISTENT'); // } -// } else { -// $uckey = null; -// } // -// // Check the CDB cache -// $message = $lang->getMessage( $lckey ); -// if ( $message !== null ) { -// return $message; -// } -// -// // Try checking the database for all of the fallback languages -// if ( $useDB ) { -// $fallbackChain = Language::getFallbacksFor( $langcode ); -// -// foreach ( $fallbackChain as $code ) { -// if ( isset( $alreadyTried[ $code ] ) ) { -// continue; -// } -// -// $message = $this->getMsgFromNamespace( -// $this->getMessagePageName( $code, $uckey ), $code ); -// -// if ( $message !== false ) { -// return $message; -// } -// $alreadyTried[ $code ] = true; -// } -// } -// -// return false; -// } -// -// /** -// * Get the message page name for a given language -// * -// * @param String $langcode -// * @param String $uckey Uppercase key for the message -// * @return String The page name -// */ -// private function getMessagePageName( $langcode, $uckey ) { -// global $wgLanguageCode; -// -// if ( $langcode === $wgLanguageCode ) { -// // Messages created in the content language will not have the /lang extension -// return $uckey; -// } else { -// return "$uckey/$langcode"; +// return message; // } // } // -// /** -// * Get a message from the MediaWiki namespace, with caching. The key must -// * first be converted to two-part lang/msg form if necessary. -// * -// * Unlike self::get(), this function doesn't resolve fallback chains, and -// * some callers require this behavior. LanguageConverter::parseCachedTable() -// * and self::get() are some examples in core. -// * -// * @param String $title Message cache key with initial uppercase letter. -// * @param String $code Code denoting the language to try. -// * @return String|boolean The message, or false if it does not exist or on error -// */ -// public function getMsgFromNamespace( $title, $code ) { -// $this->load( $code ); -// -// if ( isset( $this->mCache[$code][$title] ) ) { -// $entry = $this->mCache[$code][$title]; -// if ( substr( $entry, 0, 1 ) === ' ' ) { -// // The message exists, so make sure a String is returned. -// return (String)substr( $entry, 1 ); -// } elseif ( $entry === '!NONEXISTENT' ) { -// return false; -// } elseif ( $entry === '!TOO BIG' ) { -// // Fall through and try invididual message cache below -// } -// } else { -// // XXX: This is not cached in process cache, should it? -// $message = false; -// Hooks::run( 'MessagesPreLoad', [ $title, &$message, $code ] ); -// if ( $message !== false ) { -// return $message; -// } -// -// return false; -// } -// -// // Try the individual message cache -// $titleKey = $this->wanCache->makeKey( 'messages-big', $this->mCache[$code]['HASH'], $title ); -// -// if ( $this->mCacheVolatile[$code] ) { -// $entry = false; +// if (entry !== false && substr(entry, 0, 1) === ' ') { +// if (this.cacheVolatile[code]) { // // Make sure that individual keys respect the WAN cache holdoff period too -// LoggerFactory::getInstance( 'MessageCache' )->debug( +// LoggerFactory.getInstance('MessageCache').debug( // __METHOD__ . ': loading volatile key \'{titleKey}\'', -// [ 'titleKey' => $titleKey, 'code' => $code ] ); +// [ 'titleKey' => title, 'code' => code ]); // } else { -// $entry = $this->wanCache->get( $titleKey ); +// this.cache.setField(code, title, entry); // } +// // The message exists, so make sure a string is returned +// return (string)substr(entry, 1); +// } // -// if ( $entry !== false ) { -// if ( substr( $entry, 0, 1 ) === ' ' ) { -// $this->mCache[$code][$title] = $entry; -// // The message exists, so make sure a String is returned -// return (String)substr( $entry, 1 ); -// } elseif ( $entry === '!NONEXISTENT' ) { -// $this->mCache[$code][$title] = '!NONEXISTENT'; +// this.cache.setField(code, title, '!NONEXISTENT'); // -// return false; -// } else { -// // Corrupt/obsolete entry, delete it -// $this->wanCache->delete( $titleKey ); -// } -// } +// return false; +// } // -// // Try loading the message from the database -// $dbr = wfGetDB( DB_REPLICA ); -// $cacheOpts = Database::getCacheSetOptions( $dbr ); -// // Use newKnownCurrent() to avoid querying revision/user tables -// $titleObj = Title::makeTitle( NS_MEDIAWIKI, $title ); -// if ( $titleObj->getLatestRevID() ) { -// $revision = Revision::newKnownCurrent( -// $dbr, -// $titleObj->getArticleID(), -// $titleObj->getLatestRevID() -// ); -// } else { -// $revision = false; -// } +// /** +// * @param string dbKey +// * @param string code +// * @param string hash +// * @return string Either " " or "!NONEXISTANT" +// */ +// private function loadCachedMessagePageEntry(dbKey, code, hash) { +// fname = __METHOD__; +// return this.srvCache.getWithSetCallback( +// this.srvCache.makeKey('messages-big', hash, dbKey), +// IExpiringStore.TTL_MINUTE, +// function () use (code, dbKey, hash, fname) { +// return this.wanCache.getWithSetCallback( +// this.bigMessageCacheKey(hash, dbKey), +// this.mExpiry, +// function (oldValue, &ttl, &setOpts) use (dbKey, code, fname) { +// // Try loading the message from the database +// dbr = wfGetDB(DB_REPLICA); +// setOpts += Database.getCacheSetOptions(dbr); +// // Use newKnownCurrent() to avoid querying revision/user tables +// title = Title.makeTitle(NS_MEDIAWIKI, dbKey); +// revision = Revision.newKnownCurrent(dbr, title); +// if (!revision) { +// // The wiki doesn't have a local override page. Cache absence with normal TTL. +// // When overrides are created, self.replace() takes care of the cache. +// return '!NONEXISTENT'; +// } +// content = revision.getContent(); +// if (content) { +// message = this.getMessageTextFromContent(content); +// } else { +// LoggerFactory.getInstance('MessageCache').warning( +// fname . ': failed to load page text for \'{titleKey}\'', +// [ 'titleKey' => dbKey, 'code' => code ] +// ); +// message = null; +// } // -// if ( $revision ) { -// $content = $revision->getContent(); -// if ( $content ) { -// $message = $this->getMessageTextFromContent( $content ); -// if ( is_string( $message ) ) { -// $this->mCache[$code][$title] = ' ' . $message; -// $this->wanCache->set( $titleKey, ' ' . $message, $this->mExpiry, $cacheOpts ); +// if (!is_string(message)) { +// // Revision failed to load Content, or Content is incompatible with wikitext. +// // Possibly a temporary loading failure. +// ttl = 5; +// +// return '!NONEXISTENT'; +// } +// +// return ' ' . message; // } -// } else { -// // A possibly temporary loading failure -// LoggerFactory::getInstance( 'MessageCache' )->warning( -// __METHOD__ . ': failed to load message page text for \'{titleKey}\'', -// [ 'titleKey' => $titleKey, 'code' => $code ] ); -// $message = null; // no negative caching -// } +// ); +// } +// ); +// } +// +// /** +// * @param string message +// * @param bool interface +// * @param Language|null language +// * @param Title|null title +// * @return string +// */ +// public function transform(message, interface = false, language = null, title = null) { +// // Avoid creating parser if nothing to transform +// if (strpos(message, '{{') === false) { +// return message; +// } +// +// if (this.mInParser) { +// return message; +// } +// +// parser = this.getParser(); +// if (parser) { +// popts = this.getParserOptions(); +// popts.setInterfaceMessage(interface); +// popts.setTargetLanguage(language); +// +// userlang = popts.setUserLang(language); +// this.mInParser = true; +// message = parser.transformMsg(message, popts, title); +// this.mInParser = false; +// popts.setUserLang(userlang); +// } +// +// return message; +// } +// +// /** +// * @return Parser +// */ +// public function getParser() { +// global wgParser, wgParserConf; +// +// if (!this.mParser && isset(wgParser)) { +// # Do some initialisation so that we don't have to do it twice +// wgParser.firstCallInit(); +// # Clone it and store it +// class = wgParserConf['class']; +// if (class == ParserDiffTest.class) { +// # Uncloneable +// this.mParser = new class(wgParserConf); // } else { -// $message = false; // negative caching -// } -// -// if ( $message === false ) { // negative caching -// $this->mCache[$code][$title] = '!NONEXISTENT'; -// $this->wanCache->set( $titleKey, '!NONEXISTENT', $this->mExpiry, $cacheOpts ); -// } -// -// return $message; -// } -// -// /** -// * @param String $message -// * @param boolean $interface -// * @param String $language Language code -// * @param Title $title -// * @return String -// */ -// function transform( $message, $interface = false, $language = null, $title = null ) { -// // Avoid creating parser if nothing to transform -// if ( strpos( $message, '{{' ) === false ) { -// return $message; -// } -// -// if ( $this->mInParser ) { -// return $message; -// } -// -// $parser = $this->getParser(); -// if ( $parser ) { -// $popts = $this->getParserOptions(); -// $popts->setInterfaceMessage( $interface ); -// $popts->setTargetLanguage( $language ); -// -// $userlang = $popts->setUserLang( $language ); -// $this->mInParser = true; -// $message = $parser->transformMsg( $message, $popts, $title ); -// $this->mInParser = false; -// $popts->setUserLang( $userlang ); -// } -// -// return $message; -// } -// -// /** -// * @return Parser -// */ -// function getParser() { -// global $wgParser, $wgParserConf; -// -// if ( !$this->mParser && isset( $wgParser ) ) { -// # Do some initialisation so that we don't have to do it twice -// $wgParser->firstCallInit(); -// # Clone it and store it -// $class = $wgParserConf['class']; -// if ( $class == 'ParserDiffTest' ) { -// # Uncloneable -// $this->mParser = new $class( $wgParserConf ); -// } else { -// $this->mParser = clone $wgParser; -// } -// } -// -// return $this->mParser; -// } -// -// /** -// * @param String $text -// * @param Title $title -// * @param boolean $linestart Whether or not this is at the start of a line -// * @param boolean $interface Whether this is an interface message -// * @param Language|String $language Language code -// * @return ParserOutput|String -// */ -// public function parse( $text, $title = null, $linestart = true, -// $interface = false, $language = null -// ) { -// global $wgTitle; -// -// if ( $this->mInParser ) { -// return htmlspecialchars( $text ); -// } -// -// $parser = $this->getParser(); -// $popts = $this->getParserOptions(); -// $popts->setInterfaceMessage( $interface ); -// -// if ( is_string( $language ) ) { -// $language = Language::factory( $language ); -// } -// $popts->setTargetLanguage( $language ); -// -// if ( !$title || !$title instanceof Title ) { -// wfDebugLog( 'GlobalTitleFail', __METHOD__ . ' called by ' . -// wfGetAllCallers( 6 ) . ' with no title set.' ); -// $title = $wgTitle; -// } -// // Sometimes $wgTitle isn't set either... -// if ( !$title ) { -// # It's not uncommon having a null $wgTitle in scripts. See r80898 -// # Create a ghost title in such case -// $title = Title::makeTitle( NS_SPECIAL, 'Badtitle/title not set in ' . __METHOD__ ); -// } -// -// $this->mInParser = true; -// $res = $parser->parse( $text, $title, $popts, $linestart ); -// $this->mInParser = false; -// -// return $res; -// } -// -// function disable() { -// $this->mDisable = true; -// } -// -// function enable() { -// $this->mDisable = false; -// } -// -// /** -// * Whether DB/cache usage is disabled for determining messages -// * -// * If so, this typically indicates either: -// * - a) load() failed to find a cached copy nor query the DB -// * - b) we are in a special context or error mode that cannot use the DB -// * If the DB is ignored, any derived HTML output or cached objects may be wrong. -// * To avoid long-term cache pollution, TTLs can be adjusted accordingly. -// * -// * @return boolean -// * @since 1.27 -// */ -// public function isDisabled() { -// return $this->mDisable; -// } -// -// /** -// * Clear all stored messages. Mainly used after a mass rebuild. -// */ -// function clear() { -// $langs = Language::fetchLanguageNames( null, 'mw' ); -// foreach ( array_keys( $langs ) as $code ) { -// # Global and local caches -// $this->wanCache->touchCheckKey( wfMemcKey( 'messages', $code ) ); -// } -// -// $this->mLoadedLanguages = []; -// } -// -// /** -// * @param String $key -// * @return array -// */ -// public function figureMessage( $key ) { -// global $wgLanguageCode; -// -// $pieces = explode( '/', $key ); -// if ( count( $pieces ) < 2 ) { -// return [ $key, $wgLanguageCode ]; -// } -// -// $lang = array_pop( $pieces ); -// if ( !Language::fetchLanguageName( $lang, null, 'mw' ) ) { -// return [ $key, $wgLanguageCode ]; -// } -// -// $message = implode( '/', $pieces ); -// -// return [ $message, $lang ]; -// } -// -// /** -// * Get all message keys stored in the message cache for a given language. -// * If $code is the content language code, this will return all message keys -// * for which MediaWiki:msgkey exists. If $code is another language code, this -// * will ONLY return message keys for which MediaWiki:msgkey/$code exists. -// * @param String $code Language code -// * @return array Array of message keys (strings) -// */ -// public function getAllMessageKeys( $code ) { -// global $wgContLang; -// -// $this->load( $code ); -// if ( !isset( $this->mCache[$code] ) ) { -// // Apparently load() failed -// return null; -// } -// // Remove administrative keys -// $cache = $this->mCache[$code]; -// unset( $cache['VERSION'] ); -// unset( $cache['EXPIRY'] ); -// unset( $cache['EXCESSIVE'] ); -// // Remove any !NONEXISTENT keys -// $cache = array_diff( $cache, [ '!NONEXISTENT' ] ); -// -// // Keys may appear with a capital first letter. lcfirst them. -// return array_map( [ $wgContLang, 'lcfirst' ], array_keys( $cache ) ); -// } -// -// /** -// * Purge message caches when a MediaWiki: page is created, updated, or deleted -// * -// * @param Title $title Message page title -// * @param Content|null $content New content for edit/create, null on deletion -// * @since 1.29 -// */ -// public function updateMessageOverride( Title $title, Content $content = null ) { -// global $wgContLang; -// -// $msgText = $this->getMessageTextFromContent( $content ); -// if ( $msgText === null ) { -// $msgText = false; // treat as not existing -// } -// -// $this->replace( $title->getDBkey(), $msgText ); -// -// if ( $wgContLang->hasVariants() ) { -// $wgContLang->updateConversionTable( $title ); +// this.mParser = clone wgParser; // } // } // -// /** -// * @param Content|null $content Content or null if the message page does not exist -// * @return String|boolean|null Returns false if $content is null and null on error -// */ -// private function getMessageTextFromContent( Content $content = null ) { -// // @TODO: could skip pseudo-messages like js/css here, based on content model -// if ( $content ) { -// // Message page exists... -// // XXX: Is this the right way to turn a Content Object into a message? -// // NOTE: $content is typically either WikitextContent, JavaScriptContent or -// // CssContent. MessageContent is *not* used for storing messages, it's -// // only used for wrapping them when needed. -// $msgText = $content->getWikitextForTransclusion(); -// if ( $msgText === false || $msgText === null ) { -// // This might be due to some kind of misconfiguration... -// $msgText = null; -// LoggerFactory::getInstance( 'MessageCache' )->warning( -// __METHOD__ . ": message content doesn't provide wikitext " -// . "(content model: " . $content->getModel() . ")" ); -// } -// } else { -// // Message page does not exist... -// $msgText = false; -// } +// return this.mParser; +// } // -// return $msgText; +// /** +// * @param string text +// * @param Title|null title +// * @param bool linestart Whether or not this is at the start of a line +// * @param bool interface Whether this is an interface message +// * @param Language|string|null language Language code +// * @return ParserOutput|string +// */ +// public function parse(text, title = null, linestart = true, +// interface = false, language = null +// ) { +// global wgTitle; +// +// if (this.mInParser) { +// return htmlspecialchars(text); // } +// +// parser = this.getParser(); +// popts = this.getParserOptions(); +// popts.setInterfaceMessage(interface); +// +// if (is_string(language)) { +// language = Language.factory(language); +// } +// popts.setTargetLanguage(language); +// +// if (!title || !title instanceof Title) { +// wfDebugLog('GlobalTitleFail', __METHOD__ . ' called by ' . +// wfGetAllCallers(6) . ' with no title set.'); +// title = wgTitle; +// } +// // Sometimes wgTitle isn't set either... +// if (!title) { +// # It's not uncommon having a null wgTitle in scripts. See r80898 +// # Create a ghost title in such case +// title = Title.makeTitle(NS_SPECIAL, 'Badtitle/title not set in ' . __METHOD__); +// } +// +// this.mInParser = true; +// res = parser.parse(text, title, popts, linestart); +// this.mInParser = false; +// +// return res; +// } +// +// public function disable() { +// this.mDisable = true; +// } +// +// public function enable() { +// this.mDisable = false; +// } +// +// /** +// * Whether DB/cache usage is disabled for determining messages +// * +// * If so, this typically indicates either: +// * - a) load() failed to find a cached copy nor query the DB +// * - b) we are in a special context or error mode that cannot use the DB +// * If the DB is ignored, any derived HTML output or cached objects may be wrong. +// * To avoid long-term cache pollution, TTLs can be adjusted accordingly. +// * +// * @return bool +// * @since 1.27 +// */ +// public function isDisabled() { +// return this.mDisable; +// } +// +// /** +// * Clear all stored messages in global and local cache +// * +// * Mainly used after a mass rebuild +// */ +// public function clear() { +// langs = Language.fetchLanguageNames(null, 'mw'); +// foreach (array_keys(langs) as code) { +// this.wanCache.touchCheckKey(this.getCheckKey(code)); +// } +// this.cache.clear(); +// this.loadedLanguages = []; +// } +// +// /** +// * @param string key +// * @return array +// */ +// public function figureMessage(key) { +// global wgLanguageCode; +// +// pieces = explode('/', key); +// if (count(pieces) < 2) { +// return [ key, wgLanguageCode ]; +// } +// +// lang = array_pop(pieces); +// if (!Language.fetchLanguageName(lang, null, 'mw')) { +// return [ key, wgLanguageCode ]; +// } +// +// message = implode('/', pieces); +// +// return [ message, lang ]; +// } +// +// /** +// * Get all message keys stored in the message cache for a given language. +// * If code is the content language code, this will return all message keys +// * for which MediaWiki:msgkey exists. If code is another language code, this +// * will ONLY return message keys for which MediaWiki:msgkey/code exists. +// * @param string code Language code +// * @return array Array of message keys (strings) +// */ +// public function getAllMessageKeys(code) { +// this.load(code); +// if (!this.cache.has(code)) { +// // Apparently load() failed +// return null; +// } +// // Remove administrative keys +// cache = this.cache.get(code); +// unset(cache['VERSION']); +// unset(cache['EXPIRY']); +// unset(cache['EXCESSIVE']); +// // Remove any !NONEXISTENT keys +// cache = array_diff(cache, [ '!NONEXISTENT' ]); +// +// // Keys may appear with a capital first letter. lcfirst them. +// return array_map([ this.contLang, 'lcfirst' ], array_keys(cache)); +// } +// +// /** +// * Purge message caches when a MediaWiki: page is created, updated, or deleted +// * +// * @param Title title Message page title +// * @param Content|null content New content for edit/create, null on deletion +// * @since 1.29 +// */ +// public function updateMessageOverride(Title title, Content content = null) { +// msgText = this.getMessageTextFromContent(content); +// if (msgText === null) { +// msgText = false; // treat as not existing +// } +// +// this.replace(title.getDBkey(), msgText); +// +// if (this.contLang.hasVariants()) { +// this.contLang.updateConversionTable(title); +// } +// } +// +// /** +// * @param string code Language code +// * @return string WAN cache key usable as a "check key" against language page edits +// */ +// public function getCheckKey(code) { +// return this.wanCache.makeKey('messages', code); +// } +// +// /** +// * @param Content|null content Content or null if the message page does not exist +// * @return string|bool|null Returns false if content is null and null on error +// */ +// private function getMessageTextFromContent(Content content = null) { +// // @TODO: could skip pseudo-messages like js/css here, based on content model +// if (content) { +// // Message page exists... +// // XXX: Is this the right way to turn a Content object into a message? +// // NOTE: content is typically either WikitextContent, JavaScriptContent or +// // CssContent. MessageContent is *not* used for storing messages, it's +// // only used for wrapping them when needed. +// msgText = content.getWikitextForTransclusion(); +// if (msgText === false || msgText === null) { +// // This might be due to some kind of misconfiguration... +// msgText = null; +// LoggerFactory.getInstance('MessageCache').warning( +// __METHOD__ . ": message content doesn't provide wikitext " +// . "(content model: " . content.getModel() . ")"); +// } +// } else { +// // Message page does not exist... +// msgText = false; +// } +// +// return msgText; +// } +// +// /** +// * @param string hash Hash for this version of the entire key/value overrides map +// * @param string title Message cache key with initial uppercase letter +// * @return string +// */ +// private function bigMessageCacheKey(hash, title) { +// return this.wanCache.makeKey('messages-big', hash, title); +// } } diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/config/XomwConfig.java b/400_xowa/src/gplx/xowa/mediawiki/includes/config/XomwConfig.java index c322b66ab..86fd6fd00 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/config/XomwConfig.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/config/XomwConfig.java @@ -1,3 +1,18 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2020 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ package gplx.xowa.mediawiki.includes.config; // MW.SRC:1.33.1 diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/config/XomwConfigException.java b/400_xowa/src/gplx/xowa/mediawiki/includes/config/XomwConfigException.java new file mode 100644 index 000000000..4e1b185c7 --- /dev/null +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/config/XomwConfigException.java @@ -0,0 +1,28 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2020 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx.xowa.mediawiki.includes.config; + +import gplx.xowa.mediawiki.XophpException; + +// MW.SRC:1.33.1 +/** + * Accesses configuration settings from GLOBALS + * + * @since 1.23 + */ +public class XomwConfigException extends XophpException { + public XomwConfigException(String message) {super(message, 0, null);} +} diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/config/XomwGlobalVarConfig.java b/400_xowa/src/gplx/xowa/mediawiki/includes/config/XomwGlobalVarConfig.java new file mode 100644 index 000000000..c5e01c840 --- /dev/null +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/config/XomwGlobalVarConfig.java @@ -0,0 +1,87 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2020 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx.xowa.mediawiki.includes.config; + +import gplx.String_; +import gplx.xowa.mediawiki.XophpArray; +import gplx.xowa.mediawiki.includes.XomwGlobals; + +// MW.SRC:1.33.1 +/** + * Accesses configuration settings from GLOBALS + * + * @since 1.23 + */ +public class XomwGlobalVarConfig implements XomwConfig { + /** + * Prefix to use for configuration variables + * @var string + */ + private String prefix; + + /** + * Default builder function + * @return GlobalVarConfig + */ + public static XomwGlobalVarConfig newInstance() { + return new XomwGlobalVarConfig(); + } + + public XomwGlobalVarConfig() {this("wg");} + public XomwGlobalVarConfig(String prefix) { + this.prefix = prefix; + } + + /** + * @inheritDoc + */ + public Object get(String name) { + if (!this.has(name)) { + throw new XomwConfigException(String_.Format("get: undefined option: '{0}'", name)); + } + return this.getWithPrefix(this.prefix, name); + } + + /** + * @inheritDoc + */ + public boolean has(String name) { + return this.hasWithPrefix(this.prefix, name); + } + + /** + * Get a variable with a given prefix, if not the defaults. + * + * @param string prefix Prefix to use on the variable, if one. + * @param string name Variable name without prefix + * @return mixed + */ + protected Object getWithPrefix(String prefix, String name) { + return XomwGlobals.Instance.GLOBALS.Get_by(prefix + name); + } + + /** + * Check if a variable with a given prefix is set + * + * @param string prefix Prefix to use on the variable + * @param string name Variable name without prefix + * @return bool + */ + protected boolean hasWithPrefix(String prefix, String name) { + String var = prefix + name; + return XophpArray.array_key_exists(var, XomwGlobals.Instance.GLOBALS); + } +} diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/XomwParserOptions.java b/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/XomwParserOptions.java index 1f1b9b28c..b03e4f6bc 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/XomwParserOptions.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/XomwParserOptions.java @@ -1,6 +1,6 @@ /* XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com +Copyright (C) 2012-2020 gnosygnu@gmail.com XOWA is licensed under the terms of the General Public License (GPL) Version 3, or alternatively under the terms of the Apache License Version 2.0. @@ -13,921 +13,1424 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ -package gplx.xowa.mediawiki.includes.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; -public class XomwParserOptions { - public XomwParserOptions() { - this.mThumbSize = 220; - initialiseFromUser(); - } -// /** -// * Interlanguage links are removed and returned in an array -// */ -// private $mInterwikiMagic; -// -// /** -// * Allow external images inline? -// */ -// private $mAllowExternalImages; -// -// /** -// * If not, any exception? -// */ -// private $mAllowExternalImagesFrom; -// -// /** -// * If not or it doesn't match, should we check an on-wiki whitelist? -// */ -// private $mEnableImageWhitelist; -// -// /** -// * Date format index -// */ -// private $mDateFormat = null; -// -// /** -// * Create "edit section" links? -// */ -// private $mEditSection = true; -// -// /** -// * Allow inclusion of special pages? -// */ -// private $mAllowSpecialInclusion; -// -// /** -// * Use tidy to cleanup output HTML? -// */ -// private $mTidy = false; -// -// /** -// * Which lang to call for PLURAL and GRAMMAR -// */ -// private $mInterfaceMessage = false; -// -// /** -// * Overrides $mInterfaceMessage with arbitrary language -// */ -// private $mTargetLanguage = null; -// - /** - * Maximum size of template expansions, in bytes - */ - private int mMaxIncludeSize; - - /** - * Maximum number of nodes touched by PPFrame::expand() - */ - private int mMaxPPNodeCount; - -// /** -// * Maximum number of nodes generated by Preprocessor::preprocessToObj() -// */ -// private $mMaxGeneratedPPNodeCount; - - /** - * Maximum recursion depth in PPFrame::expand() - */ - private int mMaxPPExpandDepth; - - /** - * Maximum recursion depth for templates within templates - */ - private int mMaxTemplateDepth; - -// /** -// * Maximum number of calls per parse to expensive parser functions -// */ -// private $mExpensiveParserFunctionLimit; -// - /** - * Remove HTML comments. ONLY APPLIES TO PREPROCESS OPERATIONS - */ - private boolean mRemoveComments = true; -// -// /** -// * @var callable Callback for current revision fetching; first argument to call_user_func(). -// */ -// private $mCurrentRevisionCallback = -// [ 'Parser', 'statelessFetchRevision' ]; -// -// /** -// * @var callable Callback for template fetching; first argument to call_user_func(). -// */ -// private $mTemplateCallback = -// [ 'Parser', 'statelessFetchTemplate' ]; -// -// /** -// * @var callable|null Callback to generate a guess for {{REVISIONID}} -// */ -// private $mSpeculativeRevIdCallback; -// -// /** -// * Enable limit report in an HTML comment on output -// */ -// private $mEnableLimitReport = false; -// -// /** -// * Timestamp used for {{CURRENTDAY}} etc. -// */ -// private $mTimestamp; -// -// /** -// * Target attribute for external links -// */ -// private $mExternalLinkTarget; -// -// /** -// * Clean up signature texts? -// * @see Parser::cleanSig -// */ -// private $mCleanSignatures; -// -// /** -// * Transform wiki markup when saving the page? -// */ -// private $mPreSaveTransform = true; -// -// /** -// * Whether content conversion should be disabled -// */ -// private $mDisableContentConversion; -// -// /** -// * Whether title conversion should be disabled -// */ -// private $mDisableTitleConversion; -// -// /** -// * Automatically number headings? -// */ -// private $mNumberHeadings; - - /** - * Thumb size preferred by the user. - */ - private int mThumbSize; - -// /** -// * Maximum article size of an article to be marked as "stub" -// */ -// private $mStubThreshold; -// -// /** -// * Language Object of the User language. -// */ -// private $mUserLang; -// -// /** -// * @var User -// * Stored user Object -// */ -// private $mUser; -// -// /** -// * Parsing the page for a "preview" operation? -// */ -// private $mIsPreview = false; -// -// /** -// * Parsing the page for a "preview" operation on a single section? -// */ -// private $mIsSectionPreview = false; -// -// /** -// * Parsing the printable version of the page? -// */ -// private $mIsPrintable = false; -// -// /** -// * Extra key that should be present in the caching key. -// */ -// private $mExtraKey = ''; -// -// /** -// * Are magic ISBN links enabled? -// */ -// private $mMagicISBNLinks = true; -// -// /** -// * Are magic PMID links enabled? -// */ -// private $mMagicPMIDLinks = true; -// -// /** -// * Are magic RFC links enabled? -// */ -// private $mMagicRFCLinks = true; -// -// /** -// * Function to be called when an option is accessed. -// */ -// private $onAccessCallback = null; -// -// /** -// * If the page being parsed is a redirect, this should hold the redirect -// * target. -// * @var Title|null -// */ -// private $redirectTarget = null; -// -// public function getInterwikiMagic() { -// return this.mInterwikiMagic; -// } -// -// public function getAllowExternalImages() { -// return this.mAllowExternalImages; -// } -// -// public function getAllowExternalImagesFrom() { -// return this.mAllowExternalImagesFrom; -// } -// -// public function getEnableImageWhitelist() { -// return this.mEnableImageWhitelist; -// } -// -// public function getEditSection() { -// return this.mEditSection; -// } -// -// public function getNumberHeadings() { -// this.optionUsed( 'numberheadings' ); -// -// return this.mNumberHeadings; -// } -// -// public function getAllowSpecialInclusion() { -// return this.mAllowSpecialInclusion; -// } -// -// public function getTidy() { -// return this.mTidy; -// } -// -// public function getInterfaceMessage() { -// return this.mInterfaceMessage; -// } -// -// public function getTargetLanguage() { -// return this.mTargetLanguage; -// } -// - public int getMaxIncludeSize() { - return this.mMaxIncludeSize; - } - - public int getMaxPPNodeCount() { - return this.mMaxPPNodeCount; - } - -// public function getMaxGeneratedPPNodeCount() { -// return this.mMaxGeneratedPPNodeCount; -// } - - public int getMaxPPExpandDepth() { - return this.mMaxPPExpandDepth; - } - - public int getMaxTemplateDepth() { - return this.mMaxTemplateDepth; - } - -// /* @since 1.20 */ -// public function getExpensiveParserFunctionLimit() { -// return this.mExpensiveParserFunctionLimit; -// } -// - public boolean getRemoveComments() { - return this.mRemoveComments; - } -// -// /* @since 1.24 */ -// public function getCurrentRevisionCallback() { -// return this.mCurrentRevisionCallback; -// } -// -// public function getTemplateCallback() { -// return this.mTemplateCallback; -// } -// -// /** @since 1.28 */ -// public function getSpeculativeRevIdCallback() { -// return this.mSpeculativeRevIdCallback; -// } -// -// public function getEnableLimitReport() { -// return this.mEnableLimitReport; -// } -// -// public function getCleanSignatures() { -// return this.mCleanSignatures; -// } -// -// public function getExternalLinkTarget() { -// return this.mExternalLinkTarget; -// } -// -// public function getDisableContentConversion() { -// return this.mDisableContentConversion; -// } -// -// public function getDisableTitleConversion() { -// return this.mDisableTitleConversion; -// } - - public int getThumbSize() { - // this.optionUsed( 'thumbsize' ); - - return this.mThumbSize; - } - -// public function getStubThreshold() { -// this.optionUsed( 'stubthreshold' ); -// -// return this.mStubThreshold; -// } -// -// public function getIsPreview() { -// return this.mIsPreview; -// } -// -// public function getIsSectionPreview() { -// return this.mIsSectionPreview; -// } -// -// public function getIsPrintable() { -// this.optionUsed( 'printable' ); -// -// return this.mIsPrintable; -// } -// -// public function getUser() { -// return this.mUser; -// } -// -// public function getPreSaveTransform() { -// return this.mPreSaveTransform; -// } -// -// public function getDateFormat() { -// this.optionUsed( 'dateformat' ); -// if ( !isset( this.mDateFormat ) ) { -// this.mDateFormat = this.mUser->getDatePreference(); -// } -// return this.mDateFormat; -// } -// -// public function getTimestamp() { -// if ( !isset( this.mTimestamp ) ) { -// this.mTimestamp = wfTimestampNow(); -// } -// return this.mTimestamp; -// } -// -// /** -// * Get the user language used by the parser for this page and split the parser cache. -// * -// * @warning: Calling this causes the parser cache to be fragmented by user language! -// * To avoid cache fragmentation, output should not depend on the user language. -// * Use Parser::getFunctionLang() or Parser::getTargetLanguage() instead! -// * -// * @note This function will trigger a cache fragmentation by recording the -// * 'userlang' option, see optionUsed(). This is done to avoid cache pollution -// * when the page is rendered based on the language of the user. -// * -// * @note When saving, this will return the default language instead of the user's. -// * {{int: }} uses this which used to produce inconsistent link tables (bug 14404). -// * -// * @return Language -// * @since 1.19 -// */ -// public function getUserLangObj() { -// this.optionUsed( 'userlang' ); -// return this.mUserLang; -// } -// -// /** -// * Same as getUserLangObj() but returns a String instead. -// * -// * @warning: Calling this causes the parser cache to be fragmented by user language! -// * To avoid cache fragmentation, output should not depend on the user language. -// * Use Parser::getFunctionLang() or Parser::getTargetLanguage() instead! -// * -// * @see getUserLangObj() -// * -// * @return String Language code -// * @since 1.17 -// */ -// public function getUserLang() { -// return this.getUserLangObj()->getCode(); -// } -// -// /** -// * @since 1.28 -// * @return boolean -// */ -// public function getMagicISBNLinks() { -// return this.mMagicISBNLinks; -// } -// -// /** -// * @since 1.28 -// * @return boolean -// */ -// public function getMagicPMIDLinks() { -// return this.mMagicPMIDLinks; -// } -// /** -// * @since 1.28 -// * @return boolean -// */ -// public function getMagicRFCLinks() { -// return this.mMagicRFCLinks; -// } -// public function setInterwikiMagic( $x ) { -// return wfSetVar( this.mInterwikiMagic, $x ); -// } -// -// public function setAllowExternalImages( $x ) { -// return wfSetVar( this.mAllowExternalImages, $x ); -// } -// -// public function setAllowExternalImagesFrom( $x ) { -// return wfSetVar( this.mAllowExternalImagesFrom, $x ); -// } -// -// public function setEnableImageWhitelist( $x ) { -// return wfSetVar( this.mEnableImageWhitelist, $x ); -// } -// -// public function setDateFormat( $x ) { -// return wfSetVar( this.mDateFormat, $x ); -// } -// -// public function setEditSection( $x ) { -// return wfSetVar( this.mEditSection, $x ); -// } -// -// public function setNumberHeadings( $x ) { -// return wfSetVar( this.mNumberHeadings, $x ); -// } -// -// public function setAllowSpecialInclusion( $x ) { -// return wfSetVar( this.mAllowSpecialInclusion, $x ); -// } -// -// public function setTidy( $x ) { -// return wfSetVar( this.mTidy, $x ); -// } -// -// public function setInterfaceMessage( $x ) { -// return wfSetVar( this.mInterfaceMessage, $x ); -// } -// -// public function setTargetLanguage( $x ) { -// return wfSetVar( this.mTargetLanguage, $x, true ); -// } -// -// public function setMaxIncludeSize( $x ) { -// return wfSetVar( this.mMaxIncludeSize, $x ); -// } -// -// public function setMaxPPNodeCount( $x ) { -// return wfSetVar( this.mMaxPPNodeCount, $x ); -// } -// -// public function setMaxGeneratedPPNodeCount( $x ) { -// return wfSetVar( this.mMaxGeneratedPPNodeCount, $x ); -// } -// -// public function setMaxTemplateDepth( $x ) { -// return wfSetVar( this.mMaxTemplateDepth, $x ); -// } -// -// /* @since 1.20 */ -// public function setExpensiveParserFunctionLimit( $x ) { -// return wfSetVar( this.mExpensiveParserFunctionLimit, $x ); -// } -// -// public function setRemoveComments( $x ) { -// return wfSetVar( this.mRemoveComments, $x ); -// } -// -// /* @since 1.24 */ -// public function setCurrentRevisionCallback( $x ) { -// return wfSetVar( this.mCurrentRevisionCallback, $x ); -// } -// -// /** @since 1.28 */ -// public function setSpeculativeRevIdCallback( $x ) { -// return wfSetVar( this.mSpeculativeRevIdCallback, $x ); -// } -// -// public function setTemplateCallback( $x ) { -// return wfSetVar( this.mTemplateCallback, $x ); -// } -// -// public function enableLimitReport( $x = true ) { -// return wfSetVar( this.mEnableLimitReport, $x ); -// } -// -// public function setTimestamp( $x ) { -// return wfSetVar( this.mTimestamp, $x ); -// } -// -// public function setCleanSignatures( $x ) { -// return wfSetVar( this.mCleanSignatures, $x ); -// } -// -// public function setExternalLinkTarget( $x ) { -// return wfSetVar( this.mExternalLinkTarget, $x ); -// } -// -// public function disableContentConversion( $x = true ) { -// return wfSetVar( this.mDisableContentConversion, $x ); -// } -// -// public function disableTitleConversion( $x = true ) { -// return wfSetVar( this.mDisableTitleConversion, $x ); -// } -// -// public function setUserLang( $x ) { -// if ( is_string( $x ) ) { -// $x = Language::factory( $x ); -// } -// -// return wfSetVar( this.mUserLang, $x ); -// } -// -// public function setThumbSize( $x ) { -// return wfSetVar( this.mThumbSize, $x ); -// } -// -// public function setStubThreshold( $x ) { -// return wfSetVar( this.mStubThreshold, $x ); -// } -// -// public function setPreSaveTransform( $x ) { -// return wfSetVar( this.mPreSaveTransform, $x ); -// } -// -// public function setIsPreview( $x ) { -// return wfSetVar( this.mIsPreview, $x ); -// } -// -// public function setIsSectionPreview( $x ) { -// return wfSetVar( this.mIsSectionPreview, $x ); -// } -// -// public function setIsPrintable( $x ) { -// return wfSetVar( this.mIsPrintable, $x ); -// } -// -// /** -// * Set the redirect target. -// * -// * Note that setting or changing this does not *make* the page a redirect -// * or change its target, it merely records the information for reference -// * during the parse. -// * -// * @since 1.24 -// * @param Title|null $title -// */ -// function setRedirectTarget( $title ) { -// this.redirectTarget = $title; -// } -// -// /** -// * Get the previously-set redirect target. -// * -// * @since 1.24 -// * @return Title|null -// */ -// function getRedirectTarget() { -// return this.redirectTarget; -// } -// -// /** -// * Extra key that should be present in the parser cache key. -// * @param String $key -// */ -// public function addExtraKey( $key ) { -// this.mExtraKey .= '!' . $key; -// } -// -// /** -// * Constructor -// * @param User $user -// * @param Language $lang -// */ -// public function __construct( $user = null, $lang = null ) { -// if ( $user === null ) { -// global $wgUser; -// if ( $wgUser === null ) { -// $user = new User; -// } else { -// $user = $wgUser; -// } -// } -// if ( $lang === null ) { -// global $wgLang; -// if ( !StubObject::isRealObject( $wgLang ) ) { -// $wgLang->_unstub(); -// } -// $lang = $wgLang; -// } -// this.initialiseFromUser( $user, $lang ); -// } -// -// /** -// * Get a ParserOptions Object for an anonymous user -// * @since 1.27 -// * @return ParserOptions -// */ -// public static function newFromAnon() { -// global $wgContLang; -// return new ParserOptions( new User, $wgContLang ); -// } -// -// /** -// * Get a ParserOptions Object from a given user. -// * Language will be taken from $wgLang. -// * -// * @param User $user -// * @return ParserOptions -// */ -// public static function newFromUser( $user ) { -// return new ParserOptions( $user ); -// } -// -// /** -// * Get a ParserOptions Object from a given user and language -// * -// * @param User $user -// * @param Language $lang -// * @return ParserOptions -// */ -// public static function newFromUserAndLang( User $user, Language $lang ) { -// return new ParserOptions( $user, $lang ); -// } -// -// /** -// * Get a ParserOptions Object from a IContextSource Object -// * -// * @param IContextSource $context -// * @return ParserOptions -// */ -// public static function newFromContext( IContextSource $context ) { -// return new ParserOptions( $context->getUser(), $context->getLanguage() ); -// } - - /** - * Get user options - * - * @param User $user - * @param Language $lang - */ - // private function initialiseFromUser( $user, $lang ) { - private void initialiseFromUser() { -// global $wgInterwikiMagic, $wgAllowExternalImages, -// $wgAllowExternalImagesFrom, $wgEnableImageWhitelist, $wgAllowSpecialInclusion, -// $wgMaxArticleSize, $wgMaxPPNodeCount, $wgMaxTemplateDepth, $wgMaxPPExpandDepth, -// $wgCleanSignatures, $wgExternalLinkTarget, $wgExpensiveParserFunctionLimit, -// $wgMaxGeneratedPPNodeCount, $wgDisableLangConversion, $wgDisableTitleConversion, -// $wgEnableMagicLinks; - - // *UPDATE* ParserOptions::matches() if any of this changes as needed -// this.mInterwikiMagic = $wgInterwikiMagic; -// this.mAllowExternalImages = $wgAllowExternalImages; -// this.mAllowExternalImagesFrom = $wgAllowExternalImagesFrom; -// this.mEnableImageWhitelist = $wgEnableImageWhitelist; -// this.mAllowSpecialInclusion = $wgAllowSpecialInclusion; - this.mMaxIncludeSize = XomwDefaultSettings.wgMaxArticleSize * 1024; - this.mMaxPPNodeCount = XomwDefaultSettings.wgMaxPPNodeCount; -// this.mMaxGeneratedPPNodeCount = $wgMaxGeneratedPPNodeCount; - this.mMaxPPExpandDepth = XomwDefaultSettings.wgMaxPPExpandDepth; - this.mMaxTemplateDepth = XomwDefaultSettings.wgMaxTemplateDepth; -// this.mExpensiveParserFunctionLimit = $wgExpensiveParserFunctionLimit; -// this.mCleanSignatures = $wgCleanSignatures; -// this.mExternalLinkTarget = $wgExternalLinkTarget; -// this.mDisableContentConversion = $wgDisableLangConversion; -// this.mDisableTitleConversion = $wgDisableLangConversion || $wgDisableTitleConversion; -// this.mMagicISBNLinks = $wgEnableMagicLinks['ISBN']; -// this.mMagicPMIDLinks = $wgEnableMagicLinks['PMID']; -// this.mMagicRFCLinks = $wgEnableMagicLinks['RFC']; -// -// this.mUser = $user; -// this.mNumberHeadings = $user->getOption( 'numberheadings' ); -// this.mThumbSize = $user->getOption( 'thumbsize' ); -// this.mStubThreshold = $user->getStubThreshold(); -// this.mUserLang = $lang; - } -// -// /** -// * Check if these options match that of another options set -// * -// * This ignores report limit settings that only affect HTML comments -// * -// * @param ParserOptions $other -// * @return boolean -// * @since 1.25 -// */ -// public function matches( ParserOptions $other ) { -// $fields = array_keys( get_class_vars( __CLASS__ ) ); -// $fields = array_diff( $fields, [ -// 'mEnableLimitReport', // only effects HTML comments -// 'onAccessCallback', // only used for ParserOutput option tracking -// ] ); -// foreach ( $fields as $field ) { -// if ( !is_object( this.$field ) && this.$field !== $other->$field ) { -// return false; -// } -// } -// // Check the Object and lazy-loaded options -// return ( -// this.mUserLang->equals( $other->mUserLang ) && -// this.getDateFormat() === $other->getDateFormat() -// ); -// } -// -// /** -// * Registers a callback for tracking which ParserOptions which are used. -// * This is a private API with the parser. -// * @param callable $callback -// */ -// public function registerWatcher( $callback ) { -// this.onAccessCallback = $callback; -// } -// -// /** -// * Called when an option is accessed. -// * Calls the watcher that was set using registerWatcher(). -// * Typically, the watcher callback is ParserOutput::registerOption(). -// * The information registered that way will be used by ParserCache::save(). -// * -// * @param String $optionName Name of the option -// */ -// public function optionUsed( $optionName ) { -// if ( this.onAccessCallback ) { -// call_user_func( this.onAccessCallback, $optionName ); -// } -// } -// -// /** -// * Returns the full array of options that would have been used by -// * in 1.16. -// * Used to get the old parser cache entries when available. -// * @return array -// */ -// public static function legacyOptions() { -// return [ -// 'stubthreshold', -// 'numberheadings', -// 'userlang', -// 'thumbsize', -// 'editsection', -// 'printable' -// ]; -// } -// -// /** -// * Generate a hash String with the values set on these ParserOptions -// * for the keys given in the array. -// * This will be used as part of the hash key for the parser cache, -// * so users sharing the options with vary for the same page share -// * the same cached data safely. -// * -// * Extensions which require it should install 'PageRenderingHash' hook, -// * which will give them a chance to modify this key based on their own -// * settings. -// * -// * @since 1.17 -// * @param array $forOptions -// * @param Title $title Used to get the content language of the page (since r97636) -// * @return String Page rendering hash -// */ -// public function optionsHash( $forOptions, $title = null ) { -// global $wgRenderHashAppend; -// -// // FIXME: Once the cache key is reorganized this argument -// // can be dropped. It was used when the math extension was -// // part of core. -// $confstr = '*'; -// -// // Space assigned for the stubthreshold but unused -// // since it disables the parser cache, its value will always -// // be 0 when this function is called by parsercache. -// if ( in_array( 'stubthreshold', $forOptions ) ) { -// $confstr .= '!' . this.mStubThreshold; -// } else { -// $confstr .= '!*'; -// } -// -// if ( in_array( 'dateformat', $forOptions ) ) { -// $confstr .= '!' . this.getDateFormat(); -// } -// -// if ( in_array( 'numberheadings', $forOptions ) ) { -// $confstr .= '!' . ( this.mNumberHeadings ? '1' : '' ); -// } else { -// $confstr .= '!*'; -// } -// -// if ( in_array( 'userlang', $forOptions ) ) { -// $confstr .= '!' . this.mUserLang->getCode(); -// } else { -// $confstr .= '!*'; -// } -// -// if ( in_array( 'thumbsize', $forOptions ) ) { -// $confstr .= '!' . this.mThumbSize; -// } else { -// $confstr .= '!*'; -// } -// -// // add in language specific options, if any -// // @todo FIXME: This is just a way of retrieving the url/user preferred variant -// if ( !is_null( $title ) ) { -// $confstr .= $title->getPageLanguage()->getExtraHashOptions(); -// } else { -// global $wgContLang; -// $confstr .= $wgContLang->getExtraHashOptions(); -// } -// -// $confstr .= $wgRenderHashAppend; -// -// // @note: as of Feb 2015, core never sets the editsection flag, since it uses -// // tags to inject editsections on the fly. However, extensions -// // may be using it by calling ParserOption::optionUsed resp. ParserOutput::registerOption -// // directly. At least Wikibase does at this point in time. -// if ( !in_array( 'editsection', $forOptions ) ) { -// $confstr .= '!*'; -// } elseif ( !this.mEditSection ) { -// $confstr .= '!edit=0'; -// } -// -// if ( this.mIsPrintable && in_array( 'printable', $forOptions ) ) { -// $confstr .= '!printable=1'; -// } -// -// if ( this.mExtraKey != '' ) { -// $confstr .= this.mExtraKey; -// } -// -// // Give a chance for extensions to modify the hash, if they have -// // extra options or other effects on the parser cache. -// Hooks::run( 'PageRenderingHash', [ &$confstr, this.getUser(), &$forOptions ] ); -// -// // Make it a valid memcached key fragment -// $confstr = str_replace( ' ', '_', $confstr ); -// -// return $confstr; -// } -// -// /** -// * Sets a hook to force that a page exists, and sets a current revision callback to return -// * a revision with custom content when the current revision of the page is requested. -// * -// * @since 1.25 -// * @param Title $title -// * @param Content $content -// * @param User $user The user that the fake revision is attributed to -// * @return ScopedCallback to unset the hook -// */ -// public function setupFakeRevision( $title, $content, $user ) { -// $oldCallback = this.setCurrentRevisionCallback( -// function ( -// $titleToCheck, $parser = false ) use ( $title, $content, $user, &$oldCallback -// ) { -// if ( $titleToCheck->equals( $title ) ) { -// return new Revision( [ -// 'page' => $title->getArticleID(), -// 'user_text' => $user->getName(), -// 'user' => $user->getId(), -// 'parent_id' => $title->getLatestRevID(), -// 'title' => $title, -// 'content' => $content -// ] ); -// } else { -// return call_user_func( $oldCallback, $titleToCheck, $parser ); -// } -// } -// ); -// -// global $wgHooks; -// $wgHooks['TitleExists'][] = -// function ( $titleToCheck, &$exists ) use ( $title ) { -// if ( $titleToCheck->equals( $title ) ) { -// $exists = true; -// } -// }; -// end( $wgHooks['TitleExists'] ); -// $key = key( $wgHooks['TitleExists'] ); -// LinkCache::singleton()->clearBadLink( $title->getPrefixedDBkey() ); -// return new ScopedCallback( function () use ( $title, $key ) { -// global $wgHooks; -// unset( $wgHooks['TitleExists'][$key] ); -// LinkCache::singleton()->clearLink( $title ); -// } ); -// } -} +package gplx.xowa.mediawiki.includes.parsers; + +// MW.SRC:1.33.1 +/** + * @brief Set options of the Parser + * + * How to add an option in core: + * 1. Add it to one of the arrays in ParserOptions.setDefaults() + * 2. If necessary, add an entry to ParserOptions.$inCacheKey + * 3. Add a getter and setter in the section for that. + * + * How to add an option in an extension: + * 1. Use the "ParserOptionsRegister" hook to register it. + * 2. Where necessary, use $popt.getOption() and $popt.setOption() + * to access it. + * + * @ingroup Parser + */ +public class XomwParserOptions { +// +// /** +// * Flag indicating that newCanonical() accepts an IContextSource or the string "canonical", for +// * back-compat checks from extensions. +// * @since 1.32 +// */ +// const HAS_NEWCANONICAL_FROM_CONTEXT = 1; +// +// /** +// * Default values for all options that are relevant for caching. +// * @see self.getDefaults() +// * @var array|null +// */ +// private static $defaults = null; +// +// /** +// * Lazy-loaded options +// * @var callable[] +// */ +// private static $lazyOptions = [ +// "dateformat" => [ __CLASS__, "initDateFormat" ], +// "speculativeRevId" => [ __CLASS__, "initSpeculativeRevId" ], +// ]; +// +// /** +// * Specify options that are included in the cache key +// * @var array +// */ +// private static $inCacheKey = [ +// "dateformat" => true, +// "numberheadings" => true, +// "thumbsize" => true, +// "stubthreshold" => true, +// "printable" => true, +// "userlang" => true, +// ]; +// +// /** +// * Current values for all options that are relevant for caching. +// * @var array +// */ +// private $options; +// +// /** +// * Timestamp used for {{CURRENTDAY}} etc. +// * @var string|null +// * @note Caching based on parse time is handled externally +// */ +// private $mTimestamp; +// +// /** +// * Stored user object +// * @var User +// * @todo Track this for caching somehow without fragmenting the cache insanely +// */ +// private $mUser; +// +// /** +// * Function to be called when an option is accessed. +// * @var callable|null +// * @note Used for collecting used options, does not affect caching +// */ +// private $onAccessCallback = null; +// +// /** +// * If the page being parsed is a redirect, this should hold the redirect +// * target. +// * @var Title|null +// * @todo Track this for caching somehow +// */ +// private $redirectTarget = null; +// +// /** +// * Appended to the options hash +// */ +// private $mExtraKey = ""; +// +// /** +// * @name Option accessors +// * @{ +// */ +// +// /** +// * Fetch an option, generically +// * @since 1.30 +// * @param string $name Option name +// * @return mixed +// */ +// public function getOption($name) { +// if (!array_key_exists($name, this.options)) { +// throw new InvalidArgumentException("Unknown parser option $name"); +// } +// +// if (isset(self.$lazyOptions[$name]) && this.options[$name] === null) { +// this.options[$name] = call_user_func(self.$lazyOptions[$name], this, $name); +// } +// if (!empty(self.$inCacheKey[$name])) { +// this.optionUsed($name); +// } +// return this.options[$name]; +// } +// +// /** +// * Set an option, generically +// * @since 1.30 +// * @param string $name Option name +// * @param mixed $value New value. Passing null will set null, unlike many +// * of the existing accessors which ignore null for historical reasons. +// * @return mixed Old value +// */ +// public function setOption($name, $value) { +// if (!array_key_exists($name, this.options)) { +// throw new InvalidArgumentException("Unknown parser option $name"); +// } +// $old = this.options[$name]; +// this.options[$name] = $value; +// return $old; +// } +// +// /** +// * Legacy implementation +// * @since 1.30 For implementing legacy setters only. Don"t use this in new code. +// * @deprecated since 1.30 +// * @param string $name Option name +// * @param mixed $value New value. Passing null does not set the value. +// * @return mixed Old value +// */ +// protected function setOptionLegacy($name, $value) { +// if (!array_key_exists($name, this.options)) { +// throw new InvalidArgumentException("Unknown parser option $name"); +// } +// return wfSetVar(this.options[$name], $value); +// } +// +// /** +// * Whether to extract interlanguage links +// * +// * When true, interlanguage links will be returned by +// * ParserOutput.getLanguageLinks() instead of generating link HTML. +// * +// * @return bool +// */ +// public function getInterwikiMagic() { +// return this.getOption("interwikiMagic"); +// } +// +// /** +// * Specify whether to extract interlanguage links +// * @param bool|null $x New value (null is no change) +// * @return bool Old value +// */ +// public function setInterwikiMagic($x) { +// return this.setOptionLegacy("interwikiMagic", $x); +// } +// +// /** +// * Allow all external images inline? +// * @return bool +// */ +// public function getAllowExternalImages() { +// return this.getOption("allowExternalImages"); +// } +// +// /** +// * Allow all external images inline? +// * @param bool|null $x New value (null is no change) +// * @return bool Old value +// */ +// public function setAllowExternalImages($x) { +// return this.setOptionLegacy("allowExternalImages", $x); +// } +// +// /** +// * External images to allow +// * +// * When self.getAllowExternalImages() is false +// * +// * @return string|string[] URLs to allow +// */ +// public function getAllowExternalImagesFrom() { +// return this.getOption("allowExternalImagesFrom"); +// } +// +// /** +// * External images to allow +// * +// * When self.getAllowExternalImages() is false +// * +// * @param string|string[]|null $x New value (null is no change) +// * @return string|string[] Old value +// */ +// public function setAllowExternalImagesFrom($x) { +// return this.setOptionLegacy("allowExternalImagesFrom", $x); +// } +// +// /** +// * Use the on-wiki external image whitelist? +// * @return bool +// */ +// public function getEnableImageWhitelist() { +// return this.getOption("enableImageWhitelist"); +// } +// +// /** +// * Use the on-wiki external image whitelist? +// * @param bool|null $x New value (null is no change) +// * @return bool Old value +// */ +// public function setEnableImageWhitelist($x) { +// return this.setOptionLegacy("enableImageWhitelist", $x); +// } +// +// /** +// * Automatically number headings? +// * @return bool +// */ +// public function getNumberHeadings() { +// return this.getOption("numberheadings"); +// } +// +// /** +// * Automatically number headings? +// * @param bool|null $x New value (null is no change) +// * @return bool Old value +// */ +// public function setNumberHeadings($x) { +// return this.setOptionLegacy("numberheadings", $x); +// } +// +// /** +// * Allow inclusion of special pages? +// * @return bool +// */ +// public function getAllowSpecialInclusion() { +// return this.getOption("allowSpecialInclusion"); +// } +// +// /** +// * Allow inclusion of special pages? +// * @param bool|null $x New value (null is no change) +// * @return bool Old value +// */ +// public function setAllowSpecialInclusion($x) { +// return this.setOptionLegacy("allowSpecialInclusion", $x); +// } +// +// /** +// * Use tidy to cleanup output HTML? +// * @return bool +// */ +// public function getTidy() { +// return this.getOption("tidy"); +// } +// +// /** +// * Use tidy to cleanup output HTML? +// * @param bool|null $x New value (null is no change) +// * @return bool Old value +// */ +// public function setTidy($x) { +// return this.setOptionLegacy("tidy", $x); +// } +// +// /** +// * Parsing an interface message? +// * @return bool +// */ +// public function getInterfaceMessage() { +// return this.getOption("interfaceMessage"); +// } +// +// /** +// * Parsing an interface message? +// * @param bool|null $x New value (null is no change) +// * @return bool Old value +// */ +// public function setInterfaceMessage($x) { +// return this.setOptionLegacy("interfaceMessage", $x); +// } +// +// /** +// * Target language for the parse +// * @return Language|null +// */ +// public function getTargetLanguage() { +// return this.getOption("targetLanguage"); +// } +// +// /** +// * Target language for the parse +// * @param Language|null $x New value +// * @return Language|null Old value +// */ +// public function setTargetLanguage($x) { +// return this.setOption("targetLanguage", $x); +// } + + /** + * Maximum size of template expansions, in bytes + * @return int + */ + public int getMaxIncludeSize() { + return 1024 * 1024; +// return this.getOption("maxIncludeSize"); + } + +// /** +// * Maximum size of template expansions, in bytes +// * @param int|null $x New value (null is no change) +// * @return int Old value +// */ +// public function setMaxIncludeSize($x) { +// return this.setOptionLegacy("maxIncludeSize", $x); +// } + + /** + * Maximum number of nodes touched by PPFrame.expand() + * @return int + */ + public int getMaxPPNodeCount() { +// return this.getOption("maxPPNodeCount"); + return 1024; + } + +// /** +// * Maximum number of nodes touched by PPFrame.expand() +// * @param int|null $x New value (null is no change) +// * @return int Old value +// */ +// public function setMaxPPNodeCount($x) { +// return this.setOptionLegacy("maxPPNodeCount", $x); +// } +// +// /** +// * Maximum number of nodes generated by Preprocessor.preprocessToObj() +// * @return int +// */ +// public function getMaxGeneratedPPNodeCount() { +// return this.getOption("maxGeneratedPPNodeCount"); +// } +// +// /** +// * Maximum number of nodes generated by Preprocessor.preprocessToObj() +// * @param int|null $x New value (null is no change) +// * @return int +// */ +// public function setMaxGeneratedPPNodeCount($x) { +// return this.setOptionLegacy("maxGeneratedPPNodeCount", $x); +// } + + /** + * Maximum recursion depth in PPFrame.expand() + * @return int + */ + public int getMaxPPExpandDepth() { +// return this.getOption("maxPPExpandDepth"); + return 1024; + } + + /** + * Maximum recursion depth for templates within templates + * @return int + */ + public int getMaxTemplateDepth() { +// return this.getOption("maxTemplateDepth"); + return 64; + } + +// /** +// * Maximum recursion depth for templates within templates +// * @param int|null $x New value (null is no change) +// * @return int Old value +// */ +// public function setMaxTemplateDepth($x) { +// return this.setOptionLegacy("maxTemplateDepth", $x); +// } +// +// /** +// * Maximum number of calls per parse to expensive parser functions +// * @since 1.20 +// * @return int +// */ +// public function getExpensiveParserFunctionLimit() { +// return this.getOption("expensiveParserFunctionLimit"); +// } +// +// /** +// * Maximum number of calls per parse to expensive parser functions +// * @since 1.20 +// * @param int|null $x New value (null is no change) +// * @return int Old value +// */ +// public function setExpensiveParserFunctionLimit($x) { +// return this.setOptionLegacy("expensiveParserFunctionLimit", $x); +// } + + /** + * Remove HTML comments + * @warning Only applies to preprocess operations + * @return bool + */ + public boolean getRemoveComments() { +// return this.getOption("removeComments"); + return false; + } + +// /** +// * Remove HTML comments +// * @warning Only applies to preprocess operations +// * @param bool|null $x New value (null is no change) +// * @return bool Old value +// */ +// public function setRemoveComments($x) { +// return this.setOptionLegacy("removeComments", $x); +// } +// +// /** +// * Enable limit report in an HTML comment on output +// * @return bool +// */ +// public function getEnableLimitReport() { +// return this.getOption("enableLimitReport"); +// } +// +// /** +// * Enable limit report in an HTML comment on output +// * @param bool|null $x New value (null is no change) +// * @return bool Old value +// */ +// public function enableLimitReport($x = true) { +// return this.setOptionLegacy("enableLimitReport", $x); +// } +// +// /** +// * Clean up signature texts? +// * @see Parser.cleanSig +// * @return bool +// */ +// public function getCleanSignatures() { +// return this.getOption("cleanSignatures"); +// } +// +// /** +// * Clean up signature texts? +// * @see Parser.cleanSig +// * @param bool|null $x New value (null is no change) +// * @return bool Old value +// */ +// public function setCleanSignatures($x) { +// return this.setOptionLegacy("cleanSignatures", $x); +// } +// +// /** +// * Target attribute for external links +// * @return string +// */ +// public function getExternalLinkTarget() { +// return this.getOption("externalLinkTarget"); +// } +// +// /** +// * Target attribute for external links +// * @param string|null $x New value (null is no change) +// * @return string Old value +// */ +// public function setExternalLinkTarget($x) { +// return this.setOptionLegacy("externalLinkTarget", $x); +// } +// +// /** +// * Whether content conversion should be disabled +// * @return bool +// */ +// public function getDisableContentConversion() { +// return this.getOption("disableContentConversion"); +// } +// +// /** +// * Whether content conversion should be disabled +// * @param bool|null $x New value (null is no change) +// * @return bool Old value +// */ +// public function disableContentConversion($x = true) { +// return this.setOptionLegacy("disableContentConversion", $x); +// } +// +// /** +// * Whether title conversion should be disabled +// * @return bool +// */ +// public function getDisableTitleConversion() { +// return this.getOption("disableTitleConversion"); +// } +// +// /** +// * Whether title conversion should be disabled +// * @param bool|null $x New value (null is no change) +// * @return bool Old value +// */ +// public function disableTitleConversion($x = true) { +// return this.setOptionLegacy("disableTitleConversion", $x); +// } + + /** + * Thumb size preferred by the user. + * @return int + */ + public int getThumbSize() { +// return this.getOption("thumbsize"); + return 220; + } + +// /** +// * Thumb size preferred by the user. +// * @param int|null $x New value (null is no change) +// * @return int Old value +// */ +// public function setThumbSize($x) { +// return this.setOptionLegacy("thumbsize", $x); +// } +// +// /** +// * Thumb size preferred by the user. +// * @return int +// */ +// public function getStubThreshold() { +// return this.getOption("stubthreshold"); +// } +// +// /** +// * Thumb size preferred by the user. +// * @param int|null $x New value (null is no change) +// * @return int Old value +// */ +// public function setStubThreshold($x) { +// return this.setOptionLegacy("stubthreshold", $x); +// } +// +// /** +// * Parsing the page for a "preview" operation? +// * @return bool +// */ +// public function getIsPreview() { +// return this.getOption("isPreview"); +// } +// +// /** +// * Parsing the page for a "preview" operation? +// * @param bool|null $x New value (null is no change) +// * @return bool Old value +// */ +// public function setIsPreview($x) { +// return this.setOptionLegacy("isPreview", $x); +// } +// +// /** +// * Parsing the page for a "preview" operation on a single section? +// * @return bool +// */ +// public function getIsSectionPreview() { +// return this.getOption("isSectionPreview"); +// } +// +// /** +// * Parsing the page for a "preview" operation on a single section? +// * @param bool|null $x New value (null is no change) +// * @return bool Old value +// */ +// public function setIsSectionPreview($x) { +// return this.setOptionLegacy("isSectionPreview", $x); +// } +// +// /** +// * Parsing the printable version of the page? +// * @return bool +// */ +// public function getIsPrintable() { +// return this.getOption("printable"); +// } +// +// /** +// * Parsing the printable version of the page? +// * @param bool|null $x New value (null is no change) +// * @return bool Old value +// */ +// public function setIsPrintable($x) { +// return this.setOptionLegacy("printable", $x); +// } +// +// /** +// * Transform wiki markup when saving the page? +// * @return bool +// */ +// public function getPreSaveTransform() { +// return this.getOption("preSaveTransform"); +// } +// +// /** +// * Transform wiki markup when saving the page? +// * @param bool|null $x New value (null is no change) +// * @return bool Old value +// */ +// public function setPreSaveTransform($x) { +// return this.setOptionLegacy("preSaveTransform", $x); +// } +// +// /** +// * Date format index +// * @return string +// */ +// public function getDateFormat() { +// return this.getOption("dateformat"); +// } +// +// /** +// * Lazy initializer for dateFormat +// * @param ParserOptions $popt +// * @return string +// */ +// private static function initDateFormat(ParserOptions $popt) { +// return $popt.mUser.getDatePreference(); +// } +// +// /** +// * Date format index +// * @param string|null $x New value (null is no change) +// * @return string Old value +// */ +// public function setDateFormat($x) { +// return this.setOptionLegacy("dateformat", $x); +// } +// +// /** +// * Get the user language used by the parser for this page and split the parser cache. +// * +// * @warning Calling this causes the parser cache to be fragmented by user language! +// * To avoid cache fragmentation, output should not depend on the user language. +// * Use Parser.getFunctionLang() or Parser.getTargetLanguage() instead! +// * +// * @note This function will trigger a cache fragmentation by recording the +// * "userlang" option, see optionUsed(). This is done to avoid cache pollution +// * when the page is rendered based on the language of the user. +// * +// * @note When saving, this will return the default language instead of the user"s. +// * {{int: }} uses this which used to produce inconsistent link tables (T16404). +// * +// * @return Language +// * @since 1.19 +// */ +// public function getUserLangObj() { +// return this.getOption("userlang"); +// } +// +// /** +// * Same as getUserLangObj() but returns a string instead. +// * +// * @warning Calling this causes the parser cache to be fragmented by user language! +// * To avoid cache fragmentation, output should not depend on the user language. +// * Use Parser.getFunctionLang() or Parser.getTargetLanguage() instead! +// * +// * @see getUserLangObj() +// * +// * @return string Language code +// * @since 1.17 +// */ +// public function getUserLang() { +// return this.getUserLangObj().getCode(); +// } +// +// /** +// * Set the user language used by the parser for this page and split the parser cache. +// * @param string|Language $x New value +// * @return Language Old value +// */ +// public function setUserLang($x) { +// if (is_string($x)) { +// $x = Language.factory($x); +// } +// +// return this.setOptionLegacy("userlang", $x); +// } +// +// /** +// * Are magic ISBN links enabled? +// * @since 1.28 +// * @return bool +// */ +// public function getMagicISBNLinks() { +// return this.getOption("magicISBNLinks"); +// } +// +// /** +// * Are magic PMID links enabled? +// * @since 1.28 +// * @return bool +// */ +// public function getMagicPMIDLinks() { +// return this.getOption("magicPMIDLinks"); +// } +// +// /** +// * Are magic RFC links enabled? +// * @since 1.28 +// * @return bool +// */ +// public function getMagicRFCLinks() { +// return this.getOption("magicRFCLinks"); +// } +// +// /** +// * If the wiki is configured to allow raw html ($wgRawHtml = true) +// * is it allowed in the specific case of parsing this page. +// * +// * This is meant to disable unsafe parser tags in cases where +// * a malicious user may control the input to the parser. +// * +// * @note This is expected to be true for normal pages even if the +// * wiki has $wgRawHtml disabled in general. The setting only +// * signifies that raw html would be unsafe in the current context +// * provided that raw html is allowed at all. +// * @since 1.29 +// * @return bool +// */ +// public function getAllowUnsafeRawHtml() { +// return this.getOption("allowUnsafeRawHtml"); +// } +// +// /** +// * If the wiki is configured to allow raw html ($wgRawHtml = true) +// * is it allowed in the specific case of parsing this page. +// * @see self.getAllowUnsafeRawHtml() +// * @since 1.29 +// * @param bool|null $x Value to set or null to get current value +// * @return bool Current value for allowUnsafeRawHtml +// */ +// public function setAllowUnsafeRawHtml($x) { +// return this.setOptionLegacy("allowUnsafeRawHtml", $x); +// } +// +// /** +// * Class to use to wrap output from Parser.parse() +// * @since 1.30 +// * @return string|bool +// */ +// public function getWrapOutputClass() { +// return this.getOption("wrapclass"); +// } +// +// /** +// * CSS class to use to wrap output from Parser.parse() +// * @since 1.30 +// * @param string $className Class name to use for wrapping. +// * Passing false to indicate "no wrapping" was deprecated in MediaWiki 1.31. +// * @return string|bool Current value +// */ +// public function setWrapOutputClass($className) { +// if ($className === true) { // DWIM, they probably want the default class name +// $className = "mw-parser-output"; +// } +// if ($className === false) { +// wfDeprecated(__METHOD__ . "(false)", "1.31"); +// } +// return this.setOption("wrapclass", $className); +// } +// +// /** +// * Callback for current revision fetching; first argument to call_user_func(). +// * @since 1.24 +// * @return callable +// */ +// public function getCurrentRevisionCallback() { +// return this.getOption("currentRevisionCallback"); +// } +// +// /** +// * Callback for current revision fetching; first argument to call_user_func(). +// * @since 1.24 +// * @param callable|null $x New value (null is no change) +// * @return callable Old value +// */ +// public function setCurrentRevisionCallback($x) { +// return this.setOptionLegacy("currentRevisionCallback", $x); +// } +// +// /** +// * Callback for template fetching; first argument to call_user_func(). +// * @return callable +// */ +// public function getTemplateCallback() { +// return this.getOption("templateCallback"); +// } +// +// /** +// * Callback for template fetching; first argument to call_user_func(). +// * @param callable|null $x New value (null is no change) +// * @return callable Old value +// */ +// public function setTemplateCallback($x) { +// return this.setOptionLegacy("templateCallback", $x); +// } +// +// /** +// * A guess for {{REVISIONID}}, calculated using the callback provided via +// * setSpeculativeRevIdCallback(). For consistency, the value will be calculated upon the +// * first call of this method, and re-used for subsequent calls. +// * +// * If no callback was defined via setSpeculativeRevIdCallback(), this method will return false. +// * +// * @since 1.32 +// * @return int|false +// */ +// public function getSpeculativeRevId() { +// return this.getOption("speculativeRevId"); +// } +// +// /** +// * Callback registered with ParserOptions.$lazyOptions, triggered by getSpeculativeRevId(). +// * +// * @param ParserOptions $popt +// * @return bool|false +// */ +// private static function initSpeculativeRevId(ParserOptions $popt) { +// $cb = $popt.getOption("speculativeRevIdCallback"); +// $id = $cb ? $cb() : null; +// +// // returning null would result in this being re-called every access +// return $id ?? false; +// } +// +// /** +// * Callback to generate a guess for {{REVISIONID}} +// * @since 1.28 +// * @deprecated since 1.32, use getSpeculativeRevId() instead! +// * @return callable|null +// */ +// public function getSpeculativeRevIdCallback() { +// return this.getOption("speculativeRevIdCallback"); +// } +// +// /** +// * Callback to generate a guess for {{REVISIONID}} +// * @since 1.28 +// * @param callable|null $x New value (null is no change) +// * @return callable|null Old value +// */ +// public function setSpeculativeRevIdCallback($x) { +// this.setOption("speculativeRevId", null); // reset +// return this.setOptionLegacy("speculativeRevIdCallback", $x); +// } +// +// /**@}*/ +// +// /** +// * Timestamp used for {{CURRENTDAY}} etc. +// * @return string +// */ +// public function getTimestamp() { +// if (!isset(this.mTimestamp)) { +// this.mTimestamp = wfTimestampNow(); +// } +// return this.mTimestamp; +// } +// +// /** +// * Timestamp used for {{CURRENTDAY}} etc. +// * @param string|null $x New value (null is no change) +// * @return string Old value +// */ +// public function setTimestamp($x) { +// return wfSetVar(this.mTimestamp, $x); +// } +// +// /** +// * Create "edit section" links? +// * @deprecated since 1.31, use ParserOutput.getText() options instead. +// * @return bool +// */ +// public function getEditSection() { +// wfDeprecated(__METHOD__, "1.31"); +// return true; +// } +// +// /** +// * Create "edit section" links? +// * @deprecated since 1.31, use ParserOutput.getText() options instead. +// * @param bool|null $x New value (null is no change) +// * @return bool Old value +// */ +// public function setEditSection($x) { +// wfDeprecated(__METHOD__, "1.31"); +// return true; +// } +// +// /** +// * Set the redirect target. +// * +// * Note that setting or changing this does not *make* the page a redirect +// * or change its target, it merely records the information for reference +// * during the parse. +// * +// * @since 1.24 +// * @param Title|null $title +// */ +// function setRedirectTarget($title) { +// this.redirectTarget = $title; +// } +// +// /** +// * Get the previously-set redirect target. +// * +// * @since 1.24 +// * @return Title|null +// */ +// function getRedirectTarget() { +// return this.redirectTarget; +// } +// +// /** +// * Extra key that should be present in the parser cache key. +// * @warning Consider registering your additional options with the +// * ParserOptionsRegister hook instead of using this method. +// * @param string $key +// */ +// public function addExtraKey($key) { +// this.mExtraKey .= "!" . $key; +// } +// +// /** +// * Current user +// * @return User +// */ +// public function getUser() { +// return this.mUser; +// } +// +// /** +// * @warning For interaction with the parser cache, use +// * WikiPage.makeParserOptions() or ParserOptions.newCanonical() instead. +// * @param User|null $user +// * @param Language|null $lang +// */ +// public function __construct($user = null, $lang = null) { +// if ($user === null) { +// global $wgUser; +// if ($wgUser === null) { +// $user = new User; +// } else { +// $user = $wgUser; +// } +// } +// if ($lang === null) { +// global $wgLang; +// if (!StubObject.isRealObject($wgLang)) { +// $wgLang._unstub(); +// } +// $lang = $wgLang; +// } +// this.initialiseFromUser($user, $lang); +// } +// +// /** +// * Get a ParserOptions object for an anonymous user +// * @warning For interaction with the parser cache, use +// * WikiPage.makeParserOptions() or ParserOptions.newCanonical() instead. +// * @since 1.27 +// * @return ParserOptions +// */ +// public static function newFromAnon() { +// return new ParserOptions(new User, +// MediaWikiServices.getInstance().getContentLanguage()); +// } +// +// /** +// * Get a ParserOptions object from a given user. +// * Language will be taken from $wgLang. +// * +// * @warning For interaction with the parser cache, use +// * WikiPage.makeParserOptions() or ParserOptions.newCanonical() instead. +// * @param User $user +// * @return ParserOptions +// */ +// public static function newFromUser($user) { +// return new ParserOptions($user); +// } +// +// /** +// * Get a ParserOptions object from a given user and language +// * +// * @warning For interaction with the parser cache, use +// * WikiPage.makeParserOptions() or ParserOptions.newCanonical() instead. +// * @param User $user +// * @param Language $lang +// * @return ParserOptions +// */ +// public static function newFromUserAndLang(User $user, Language $lang) { +// return new ParserOptions($user, $lang); +// } +// +// /** +// * Get a ParserOptions object from a IContextSource object +// * +// * @warning For interaction with the parser cache, use +// * WikiPage.makeParserOptions() or ParserOptions.newCanonical() instead. +// * @param IContextSource $context +// * @return ParserOptions +// */ +// public static function newFromContext(IContextSource $context) { +// return new ParserOptions($context.getUser(), $context.getLanguage()); +// } +// +// /** +// * Creates a "canonical" ParserOptions object +// * +// * For historical reasons, certain options have default values that are +// * different from the canonical values used for caching. +// * +// * @since 1.30 +// * @since 1.32 Added string and IContextSource as options for the first parameter +// * @param IContextSource|string|User|null $context +// * - If an IContextSource, the options are initialized based on the source"s User and Language. +// * - If the string "canonical", the options are initialized with an anonymous user and +// * the content language. +// * - If a User or null, the options are initialized for that User (or $wgUser if null). +// * "userlang" is taken from the $userLang parameter, defaulting to $wgLang if that is null. +// * @param Language|StubObject|null $userLang (see above) +// * @return ParserOptions +// */ +// public static function newCanonical($context = null, $userLang = null) { +// if ($context instanceof IContextSource) { +// $ret = self.newFromContext($context); +// } elseif ($context === "canonical") { +// $ret = self.newFromAnon(); +// } elseif ($context instanceof User || $context === null) { +// $ret = new self($context, $userLang); +// } else { +// throw new InvalidArgumentException( +// "$context must be an IContextSource, the string "canonical", a User, or null" +// ); +// } +// +// foreach (self.getCanonicalOverrides() as $k => $v) { +// $ret.setOption($k, $v); +// } +// return $ret; +// } +// +// /** +// * Get default option values +// * @warning If you change the default for an existing option (unless it"s +// * being overridden by self.getCanonicalOverrides()), all existing parser +// * cache entries will be invalid. To avoid bugs, you"ll need to handle +// * that somehow (e.g. with the RejectParserCacheValue hook) because +// * MediaWiki won"t do it for you. +// * @return array +// */ +// private static function getDefaults() { +// global $wgInterwikiMagic, $wgAllowExternalImages, +// $wgAllowExternalImagesFrom, $wgEnableImageWhitelist, $wgAllowSpecialInclusion, +// $wgMaxArticleSize, $wgMaxPPNodeCount, $wgMaxTemplateDepth, $wgMaxPPExpandDepth, +// $wgCleanSignatures, $wgExternalLinkTarget, $wgExpensiveParserFunctionLimit, +// $wgMaxGeneratedPPNodeCount, $wgDisableLangConversion, $wgDisableTitleConversion, +// $wgEnableMagicLinks; +// +// if (self.$defaults === null) { +// // *UPDATE* ParserOptions.matches() if any of this changes as needed +// self.$defaults = [ +// "dateformat" => null, +// "tidy" => true, +// "interfaceMessage" => false, +// "targetLanguage" => null, +// "removeComments" => true, +// "enableLimitReport" => false, +// "preSaveTransform" => true, +// "isPreview" => false, +// "isSectionPreview" => false, +// "printable" => false, +// "allowUnsafeRawHtml" => true, +// "wrapclass" => "mw-parser-output", +// "currentRevisionCallback" => [ Parser.class, "statelessFetchRevision" ], +// "templateCallback" => [ Parser.class, "statelessFetchTemplate" ], +// "speculativeRevIdCallback" => null, +// "speculativeRevId" => null, +// ]; +// +// Hooks.run("ParserOptionsRegister", [ +// &self.$defaults, +// &self.$inCacheKey, +// &self.$lazyOptions, +// ]); +// +// ksort(self.$inCacheKey); +// } +// +// // Unit tests depend on being able to modify the globals at will +// return self.$defaults + [ +// "interwikiMagic" => $wgInterwikiMagic, +// "allowExternalImages" => $wgAllowExternalImages, +// "allowExternalImagesFrom" => $wgAllowExternalImagesFrom, +// "enableImageWhitelist" => $wgEnableImageWhitelist, +// "allowSpecialInclusion" => $wgAllowSpecialInclusion, +// "maxIncludeSize" => $wgMaxArticleSize * 1024, +// "maxPPNodeCount" => $wgMaxPPNodeCount, +// "maxGeneratedPPNodeCount" => $wgMaxGeneratedPPNodeCount, +// "maxPPExpandDepth" => $wgMaxPPExpandDepth, +// "maxTemplateDepth" => $wgMaxTemplateDepth, +// "expensiveParserFunctionLimit" => $wgExpensiveParserFunctionLimit, +// "externalLinkTarget" => $wgExternalLinkTarget, +// "cleanSignatures" => $wgCleanSignatures, +// "disableContentConversion" => $wgDisableLangConversion, +// "disableTitleConversion" => $wgDisableLangConversion || $wgDisableTitleConversion, +// "magicISBNLinks" => $wgEnableMagicLinks["ISBN"], +// "magicPMIDLinks" => $wgEnableMagicLinks["PMID"], +// "magicRFCLinks" => $wgEnableMagicLinks["RFC"], +// "numberheadings" => User.getDefaultOption("numberheadings"), +// "thumbsize" => User.getDefaultOption("thumbsize"), +// "stubthreshold" => 0, +// "userlang" => MediaWikiServices.getInstance().getContentLanguage(), +// ]; +// } +// +// /** +// * Get "canonical" non-default option values +// * @see self.newCanonical +// * @warning If you change the override for an existing option, all existing +// * parser cache entries will be invalid. To avoid bugs, you"ll need to +// * handle that somehow (e.g. with the RejectParserCacheValue hook) because +// * MediaWiki won"t do it for you. +// * @return array +// */ +// private static function getCanonicalOverrides() { +// global $wgEnableParserLimitReporting; +// +// return [ +// "enableLimitReport" => $wgEnableParserLimitReporting, +// ]; +// } +// +// /** +// * Get user options +// * +// * @param User $user +// * @param Language $lang +// */ +// private function initialiseFromUser($user, $lang) { +// this.options = self.getDefaults(); +// +// this.mUser = $user; +// this.options["numberheadings"] = $user.getOption("numberheadings"); +// this.options["thumbsize"] = $user.getOption("thumbsize"); +// this.options["stubthreshold"] = $user.getStubThreshold(); +// this.options["userlang"] = $lang; +// } +// +// /** +// * Check if these options match that of another options set +// * +// * This ignores report limit settings that only affect HTML comments +// * +// * @param ParserOptions $other +// * @return bool +// * @since 1.25 +// */ +// public function matches(ParserOptions $other) { +// // Populate lazy options +// foreach (self.$lazyOptions as $name => $callback) { +// if (this.options[$name] === null) { +// this.options[$name] = call_user_func($callback, this, $name); +// } +// if ($other.options[$name] === null) { +// $other.options[$name] = call_user_func($callback, $other, $name); +// } +// } +// +// // Compare most options +// $options = array_keys(this.options); +// $options = array_diff($options, [ +// "enableLimitReport", // only affects HTML comments +// ]); +// foreach ($options as $option) { +// $o1 = this.optionToString(this.options[$option]); +// $o2 = this.optionToString($other.options[$option]); +// if ($o1 !== $o2) { +// return false; +// } +// } +// +// // Compare most other fields +// $fields = array_keys(get_class_vars(__CLASS__)); +// $fields = array_diff($fields, [ +// "defaults", // static +// "lazyOptions", // static +// "inCacheKey", // static +// "options", // Already checked above +// "onAccessCallback", // only used for ParserOutput option tracking +// ]); +// foreach ($fields as $field) { +// if (!is_object(this.$field) && this.$field !== $other.$field) { +// return false; +// } +// } +// +// return true; +// } +// +// /** +// * Registers a callback for tracking which ParserOptions which are used. +// * This is a private API with the parser. +// * @param callable $callback +// */ +// public function registerWatcher($callback) { +// this.onAccessCallback = $callback; +// } +// +// /** +// * Called when an option is accessed. +// * Calls the watcher that was set using registerWatcher(). +// * Typically, the watcher callback is ParserOutput.registerOption(). +// * The information registered that way will be used by ParserCache.save(). +// * +// * @param string $optionName Name of the option +// */ +// public function optionUsed($optionName) { +// if (this.onAccessCallback) { +// call_user_func(this.onAccessCallback, $optionName); +// } +// } +// +// /** +// * Return all option keys that vary the options hash +// * @since 1.30 +// * @return string[] +// */ +// public static function allCacheVaryingOptions() { +// // Trigger a call to the "ParserOptionsRegister" hook if it hasn"t +// // already been called. +// if (self.$defaults === null) { +// self.getDefaults(); +// } +// return array_keys(array_filter(self.$inCacheKey)); +// } +// +// /** +// * Convert an option to a string value +// * @param mixed $value +// * @return string +// */ +// private function optionToString($value) { +// if ($value === true) { +// return "1"; +// } elseif ($value === false) { +// return "0"; +// } elseif ($value === null) { +// return ""; +// } elseif ($value instanceof Language) { +// return $value.getCode(); +// } elseif (is_array($value)) { +// return "[" . implode(",", array_map([ this, "optionToString" ], $value)) . "]"; +// } else { +// return (string)$value; +// } +// } +// +// /** +// * Generate a hash string with the values set on these ParserOptions +// * for the keys given in the array. +// * This will be used as part of the hash key for the parser cache, +// * so users sharing the options with vary for the same page share +// * the same cached data safely. +// * +// * @since 1.17 +// * @param string[] $forOptions +// * @param Title|null $title Used to get the content language of the page (since r97636) +// * @return string Page rendering hash +// */ +// public function optionsHash($forOptions, $title = null) { +// global $wgRenderHashAppend; +// +// $inCacheKey = self.allCacheVaryingOptions(); +// +// // Resolve any lazy options +// foreach (array_intersect($forOptions, $inCacheKey, array_keys(self.$lazyOptions)) as $k) { +// if (this.options[$k] === null) { +// this.options[$k] = call_user_func(self.$lazyOptions[$k], this, $k); +// } +// } +// +// $options = this.options; +// $defaults = self.getCanonicalOverrides() + self.getDefaults(); +// +// // We only include used options with non-canonical values in the key +// // so adding a new option doesn"t invalidate the entire parser cache. +// // The drawback to this is that changing the default value of an option +// // requires manual invalidation of existing cache entries, as mentioned +// // in the docs on the relevant methods and hooks. +// $values = []; +// foreach (array_intersect($inCacheKey, $forOptions) as $option) { +// $v = this.optionToString($options[$option]); +// $d = this.optionToString($defaults[$option]); +// if ($v !== $d) { +// $values[] = "$option=$v"; +// } +// } +// +// $confstr = $values ? implode("!", $values) : "canonical"; +// +// // add in language specific options, if any +// // @todo FIXME: This is just a way of retrieving the url/user preferred variant +// if (!is_null($title)) { +// $confstr .= $title.getPageLanguage().getExtraHashOptions(); +// } else { +// $confstr .= +// MediaWikiServices.getInstance().getContentLanguage().getExtraHashOptions(); +// } +// +// $confstr .= $wgRenderHashAppend; +// +// if (this.mExtraKey != "") { +// $confstr .= this.mExtraKey; +// } +// +// // Give a chance for extensions to modify the hash, if they have +// // extra options or other effects on the parser cache. +// Hooks.run("PageRenderingHash", [ &$confstr, this.getUser(), &$forOptions ]); +// +// // Make it a valid memcached key fragment +// $confstr = str_replace(" ", "_", $confstr); +// +// return $confstr; +// } +// +// /** +// * Test whether these options are safe to cache +// * @since 1.30 +// * @return bool +// */ +// public function isSafeToCache() { +// $defaults = self.getCanonicalOverrides() + self.getDefaults(); +// foreach (this.options as $option => $value) { +// if (empty(self.$inCacheKey[$option])) { +// $v = this.optionToString($value); +// $d = this.optionToString($defaults[$option]); +// if ($v !== $d) { +// return false; +// } +// } +// } +// return true; +// } +// +// /** +// * Sets a hook to force that a page exists, and sets a current revision callback to return +// * a revision with custom content when the current revision of the page is requested. +// * +// * @since 1.25 +// * @param Title $title +// * @param Content $content +// * @param User $user The user that the fake revision is attributed to +// * @return ScopedCallback to unset the hook +// */ +// public function setupFakeRevision($title, $content, $user) { +// $oldCallback = this.setCurrentRevisionCallback( +// function ( +// $titleToCheck, $parser = false) use ($title, $content, $user, &$oldCallback +// ) { +// if ($titleToCheck.equals($title)) { +// return new Revision([ +// "page" => $title.getArticleID(), +// "user_text" => $user.getName(), +// "user" => $user.getId(), +// "parent_id" => $title.getLatestRevID(), +// "title" => $title, +// "content" => $content +// ]); +// } else { +// return call_user_func($oldCallback, $titleToCheck, $parser); +// } +// } +// ); +// +// global $wgHooks; +// $wgHooks["TitleExists"][] = +// function ($titleToCheck, &$exists) use ($title) { +// if ($titleToCheck.equals($title)) { +// $exists = true; +// } +// }; +// end($wgHooks["TitleExists"]); +// $key = key($wgHooks["TitleExists"]); +// $linkCache = MediaWikiServices.getInstance().getLinkCache(); +// $linkCache.clearBadLink($title.getPrefixedDBkey()); +// return new ScopedCallback(function () use ($title, $key, $linkCache) { +// global $wgHooks; +// unset($wgHooks["TitleExists"][$key]); +// $linkCache.clearLink($title); +// }); +// } +} + +/** + * For really cool vim folding this needs to be at the end: + * vim: foldmarker=@{,@} foldmethod=marker + */