diff options
author | Yaco <franco@reevo.org> | 2020-06-04 11:01:00 -0300 |
---|---|---|
committer | Yaco <franco@reevo.org> | 2020-06-04 11:01:00 -0300 |
commit | fc7369835258467bf97eb64f184b93691f9a9fd5 (patch) | |
tree | daabd60089d2dd76d9f5fb416b005fbe159c799d /www/wiki/includes/context |
first commit
Diffstat (limited to 'www/wiki/includes/context')
-rw-r--r-- | www/wiki/includes/context/ContextSource.php | 184 | ||||
-rw-r--r-- | www/wiki/includes/context/DerivativeContext.php | 301 | ||||
-rw-r--r-- | www/wiki/includes/context/IContextSource.php | 137 | ||||
-rw-r--r-- | www/wiki/includes/context/MutableContext.php | 67 | ||||
-rw-r--r-- | www/wiki/includes/context/RequestContext.php | 616 |
5 files changed, 1305 insertions, 0 deletions
diff --git a/www/wiki/includes/context/ContextSource.php b/www/wiki/includes/context/ContextSource.php new file mode 100644 index 00000000..03fb9e2b --- /dev/null +++ b/www/wiki/includes/context/ContextSource.php @@ -0,0 +1,184 @@ +<?php +/** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @author Happy-melon + * @file + */ +use MediaWiki\MediaWikiServices; + +/** + * The simplest way of implementing IContextSource is to hold a RequestContext as a + * member variable and provide accessors to it. + * + * @since 1.18 + */ +abstract class ContextSource implements IContextSource { + /** + * @var IContextSource + */ + private $context; + + /** + * Get the base IContextSource object + * @since 1.18 + * @return IContextSource + */ + public function getContext() { + if ( $this->context === null ) { + $class = static::class; + wfDebug( __METHOD__ . " ($class): called and \$context is null. " . + "Using RequestContext::getMain() for sanity\n" ); + $this->context = RequestContext::getMain(); + } + + return $this->context; + } + + /** + * @since 1.18 + * @param IContextSource $context + */ + public function setContext( IContextSource $context ) { + $this->context = $context; + } + + /** + * @since 1.23 + * @return Config + */ + public function getConfig() { + return $this->getContext()->getConfig(); + } + + /** + * @since 1.18 + * @return WebRequest + */ + public function getRequest() { + return $this->getContext()->getRequest(); + } + + /** + * @since 1.18 + * @return Title|null + */ + public function getTitle() { + return $this->getContext()->getTitle(); + } + + /** + * Check whether a WikiPage object can be get with getWikiPage(). + * Callers should expect that an exception is thrown from getWikiPage() + * if this method returns false. + * + * @since 1.19 + * @return bool + */ + public function canUseWikiPage() { + return $this->getContext()->canUseWikiPage(); + } + + /** + * Get the WikiPage object. + * May throw an exception if there's no Title object set or the Title object + * belongs to a special namespace that doesn't have WikiPage, so use first + * canUseWikiPage() to check whether this method can be called safely. + * + * @since 1.19 + * @return WikiPage + */ + public function getWikiPage() { + return $this->getContext()->getWikiPage(); + } + + /** + * @since 1.18 + * @return OutputPage + */ + public function getOutput() { + return $this->getContext()->getOutput(); + } + + /** + * @since 1.18 + * @return User + */ + public function getUser() { + return $this->getContext()->getUser(); + } + + /** + * @since 1.19 + * @return Language + */ + public function getLanguage() { + return $this->getContext()->getLanguage(); + } + + /** + * @since 1.18 + * @return Skin + */ + public function getSkin() { + return $this->getContext()->getSkin(); + } + + /** + * @since 1.27 + * @return Timing + */ + public function getTiming() { + return $this->getContext()->getTiming(); + } + + /** + * @deprecated since 1.27 use a StatsdDataFactory from MediaWikiServices (preferably injected) + * + * @since 1.25 + * @return IBufferingStatsdDataFactory + */ + public function getStats() { + return MediaWikiServices::getInstance()->getStatsdDataFactory(); + } + + /** + * Get a Message object with context set + * Parameters are the same as wfMessage() + * + * @since 1.18 + * @param string|string[]|MessageSpecifier $key Message key, or array of keys, + * or a MessageSpecifier. + * @param mixed $args,... + * @return Message + */ + public function msg( $key /* $args */ ) { + $args = func_get_args(); + + return call_user_func_array( [ $this->getContext(), 'msg' ], $args ); + } + + /** + * Export the resolved user IP, HTTP headers, user ID, and session ID. + * The result will be reasonably sized to allow for serialization. + * + * @return array + * @since 1.21 + */ + public function exportSession() { + return $this->getContext()->exportSession(); + } +} diff --git a/www/wiki/includes/context/DerivativeContext.php b/www/wiki/includes/context/DerivativeContext.php new file mode 100644 index 00000000..f7a1815d --- /dev/null +++ b/www/wiki/includes/context/DerivativeContext.php @@ -0,0 +1,301 @@ +<?php +/** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @author Daniel Friesen + * @file + */ +use MediaWiki\MediaWikiServices; + +/** + * An IContextSource implementation which will inherit context from another source + * but allow individual pieces of context to be changed locally + * eg: A ContextSource that can inherit from the main RequestContext but have + * a different Title instance set on it. + * @since 1.19 + */ +class DerivativeContext extends ContextSource implements MutableContext { + /** + * @var WebRequest + */ + private $request; + + /** + * @var Title + */ + private $title; + + /** + * @var WikiPage + */ + private $wikipage; + + /** + * @var OutputPage + */ + private $output; + + /** + * @var User + */ + private $user; + + /** + * @var Language + */ + private $lang; + + /** + * @var Skin + */ + private $skin; + + /** + * @var Config + */ + private $config; + + /** + * @var Timing + */ + private $timing; + + /** + * @param IContextSource $context Context to inherit from + */ + public function __construct( IContextSource $context ) { + $this->setContext( $context ); + } + + /** + * @param Config $config + */ + public function setConfig( Config $config ) { + $this->config = $config; + } + + /** + * @return Config + */ + public function getConfig() { + if ( !is_null( $this->config ) ) { + return $this->config; + } else { + return $this->getContext()->getConfig(); + } + } + + /** + * @deprecated since 1.27 use a StatsdDataFactory from MediaWikiServices (preferably injected) + * + * @return IBufferingStatsdDataFactory + */ + public function getStats() { + return MediaWikiServices::getInstance()->getStatsdDataFactory(); + } + + /** + * @return Timing + */ + public function getTiming() { + if ( !is_null( $this->timing ) ) { + return $this->timing; + } else { + return $this->getContext()->getTiming(); + } + } + + /** + * @param WebRequest $request + */ + public function setRequest( WebRequest $request ) { + $this->request = $request; + } + + /** + * @return WebRequest + */ + public function getRequest() { + if ( !is_null( $this->request ) ) { + return $this->request; + } else { + return $this->getContext()->getRequest(); + } + } + + /** + * @param Title $title + */ + public function setTitle( Title $title ) { + $this->title = $title; + } + + /** + * @return Title|null + */ + public function getTitle() { + if ( !is_null( $this->title ) ) { + return $this->title; + } else { + return $this->getContext()->getTitle(); + } + } + + /** + * Check whether a WikiPage object can be get with getWikiPage(). + * Callers should expect that an exception is thrown from getWikiPage() + * if this method returns false. + * + * @since 1.19 + * @return bool + */ + public function canUseWikiPage() { + if ( $this->wikipage !== null ) { + return true; + } elseif ( $this->title !== null ) { + return $this->title->canExist(); + } else { + return $this->getContext()->canUseWikiPage(); + } + } + + /** + * @since 1.19 + * @param WikiPage $wikiPage + */ + public function setWikiPage( WikiPage $wikiPage ) { + $this->wikipage = $wikiPage; + } + + /** + * Get the WikiPage object. + * May throw an exception if there's no Title object set or the Title object + * belongs to a special namespace that doesn't have WikiPage, so use first + * canUseWikiPage() to check whether this method can be called safely. + * + * @since 1.19 + * @return WikiPage + */ + public function getWikiPage() { + if ( !is_null( $this->wikipage ) ) { + return $this->wikipage; + } else { + return $this->getContext()->getWikiPage(); + } + } + + /** + * @param OutputPage $output + */ + public function setOutput( OutputPage $output ) { + $this->output = $output; + } + + /** + * @return OutputPage + */ + public function getOutput() { + if ( !is_null( $this->output ) ) { + return $this->output; + } else { + return $this->getContext()->getOutput(); + } + } + + /** + * @param User $user + */ + public function setUser( User $user ) { + $this->user = $user; + } + + /** + * @return User + */ + public function getUser() { + if ( !is_null( $this->user ) ) { + return $this->user; + } else { + return $this->getContext()->getUser(); + } + } + + /** + * @param Language|string $language Language instance or language code + * @throws MWException + * @since 1.19 + */ + public function setLanguage( $language ) { + if ( $language instanceof Language ) { + $this->lang = $language; + } elseif ( is_string( $language ) ) { + $language = RequestContext::sanitizeLangCode( $language ); + $obj = Language::factory( $language ); + $this->lang = $obj; + } else { + throw new MWException( __METHOD__ . " was passed an invalid type of data." ); + } + } + + /** + * @return Language + * @since 1.19 + */ + public function getLanguage() { + if ( !is_null( $this->lang ) ) { + return $this->lang; + } else { + return $this->getContext()->getLanguage(); + } + } + + /** + * @param Skin $skin + */ + public function setSkin( Skin $skin ) { + $this->skin = clone $skin; + $this->skin->setContext( $this ); + } + + /** + * @return Skin + */ + public function getSkin() { + if ( !is_null( $this->skin ) ) { + return $this->skin; + } else { + return $this->getContext()->getSkin(); + } + } + + /** + * Get a message using the current context. + * + * This can't just inherit from ContextSource, since then + * it would set only the original context, and not take + * into account any changes. + * + * @param string|string[]|MessageSpecifier $key Message key, or array of keys, + * or a MessageSpecifier. + * @param mixed $args,... Arguments to wfMessage + * @return Message + */ + public function msg( $key ) { + $args = func_get_args(); + + return call_user_func_array( 'wfMessage', $args )->setContext( $this ); + } +} diff --git a/www/wiki/includes/context/IContextSource.php b/www/wiki/includes/context/IContextSource.php new file mode 100644 index 00000000..6e48e1eb --- /dev/null +++ b/www/wiki/includes/context/IContextSource.php @@ -0,0 +1,137 @@ +<?php +/** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @since 1.18 + * + * @author Happy-melon + * @file + */ + +/** + * Interface for objects which can provide a MediaWiki context on request + * + * Context objects contain request-dependent objects that manage the core + * web request/response logic for essentially all requests to MediaWiki. + * The contained objects include: + * a) Key objects that depend (for construction/loading) on the HTTP request + * b) Key objects used for response building and PHP session state control + * c) Performance metric deltas accumulated from request execution + * d) The site configuration object + * All of the objects are useful for the vast majority of MediaWiki requests. + * The site configuration object is included on grounds of extreme + * utility, even though it should not actually depend on the web request. + * + * More specifically, the scope of the context includes: + * a) Objects that represent the HTTP request/response and PHP session state + * b) Object representing the MediaWiki user (as determined by the HTTP request) + * c) Primary MediaWiki output builder objects (OutputPage, user skin object) + * d) The language object for the user/request + * e) The title and wiki page objects requested via URL (if any) + * f) Performance metric deltas accumulated from request execution + * g) The site configuration object + * + * This class is not intended as a service-locator nor a service singleton. + * Objects that only depend on site configuration do not belong here (aside + * from Config itself). Objects that represent persistent data stores do not + * belong here either. Session state changes should only be propagated on + * shutdown by separate persistence handler objects, for example. + */ +interface IContextSource extends MessageLocalizer { + + /** + * @return WebRequest + */ + public function getRequest(); + + /** + * @return Title|null + */ + public function getTitle(); + + /** + * Check whether a WikiPage object can be get with getWikiPage(). + * Callers should expect that an exception is thrown from getWikiPage() + * if this method returns false. + * + * @since 1.19 + * @return bool + */ + public function canUseWikiPage(); + + /** + * Get the WikiPage object. + * May throw an exception if there's no Title object set or the Title object + * belongs to a special namespace that doesn't have WikiPage, so use first + * canUseWikiPage() to check whether this method can be called safely. + * + * @since 1.19 + * @return WikiPage + */ + public function getWikiPage(); + + /** + * @return OutputPage + */ + public function getOutput(); + + /** + * @return User + */ + public function getUser(); + + /** + * @return Language + * @since 1.19 + */ + public function getLanguage(); + + /** + * @return Skin + */ + public function getSkin(); + + /** + * Get the site configuration + * + * @since 1.23 + * @return Config + */ + public function getConfig(); + + /** + * @deprecated since 1.27 use a StatsdDataFactory from MediaWikiServices (preferably injected) + * + * @since 1.25 + * @return IBufferingStatsdDataFactory + */ + public function getStats(); + + /** + * @since 1.27 + * @return Timing + */ + public function getTiming(); + + /** + * Export the resolved user IP, HTTP headers, user ID, and session ID. + * The result will be reasonably sized to allow for serialization. + * + * @return array + * @since 1.21 + */ + public function exportSession(); +} diff --git a/www/wiki/includes/context/MutableContext.php b/www/wiki/includes/context/MutableContext.php new file mode 100644 index 00000000..56ec9601 --- /dev/null +++ b/www/wiki/includes/context/MutableContext.php @@ -0,0 +1,67 @@ +<?php +/** + * Request-dependant objects containers. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @since 1.26 + * + * @file + */ + +interface MutableContext { + + /** + * @param Config $config + */ + public function setConfig( Config $config ); + + /** + * @param WebRequest $request + */ + public function setRequest( WebRequest $request ); + + /** + * @param Title $title + */ + public function setTitle( Title $title ); + + /** + * @param WikiPage $wikiPage + */ + public function setWikiPage( WikiPage $wikiPage ); + + /** + * @param OutputPage $output + */ + public function setOutput( OutputPage $output ); + + /** + * @param User $user + */ + public function setUser( User $user ); + + /** + * @param Language|string $language Language instance or language code + */ + public function setLanguage( $language ); + + /** + * @param Skin $skin + */ + public function setSkin( Skin $skin ); + +} diff --git a/www/wiki/includes/context/RequestContext.php b/www/wiki/includes/context/RequestContext.php new file mode 100644 index 00000000..db3a7a96 --- /dev/null +++ b/www/wiki/includes/context/RequestContext.php @@ -0,0 +1,616 @@ +<?php +/** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @since 1.18 + * + * @author Alexandre Emsenhuber + * @author Daniel Friesen + * @file + */ + +use MediaWiki\Logger\LoggerFactory; +use MediaWiki\MediaWikiServices; +use Wikimedia\ScopedCallback; + +/** + * Group all the pieces relevant to the context of a request into one instance + */ +class RequestContext implements IContextSource, MutableContext { + /** + * @var WebRequest + */ + private $request; + + /** + * @var Title + */ + private $title; + + /** + * @var WikiPage + */ + private $wikipage; + + /** + * @var OutputPage + */ + private $output; + + /** + * @var User + */ + private $user; + + /** + * @var Language + */ + private $lang; + + /** + * @var Skin + */ + private $skin; + + /** + * @var Timing + */ + private $timing; + + /** + * @var Config + */ + private $config; + + /** + * @var RequestContext + */ + private static $instance = null; + + /** + * @param Config $config + */ + public function setConfig( Config $config ) { + $this->config = $config; + } + + /** + * @return Config + */ + public function getConfig() { + if ( $this->config === null ) { + // @todo In the future, we could move this to WebStart.php so + // the Config object is ready for when initialization happens + $this->config = MediaWikiServices::getInstance()->getMainConfig(); + } + + return $this->config; + } + + /** + * @param WebRequest $request + */ + public function setRequest( WebRequest $request ) { + $this->request = $request; + } + + /** + * @return WebRequest + */ + public function getRequest() { + if ( $this->request === null ) { + global $wgCommandLineMode; + // create the WebRequest object on the fly + if ( $wgCommandLineMode ) { + $this->request = new FauxRequest( [] ); + } else { + $this->request = new WebRequest(); + } + } + + return $this->request; + } + + /** + * @deprecated since 1.27 use a StatsdDataFactory from MediaWikiServices (preferably injected) + * + * @return IBufferingStatsdDataFactory + */ + public function getStats() { + return MediaWikiServices::getInstance()->getStatsdDataFactory(); + } + + /** + * @return Timing + */ + public function getTiming() { + if ( $this->timing === null ) { + $this->timing = new Timing( [ + 'logger' => LoggerFactory::getInstance( 'Timing' ) + ] ); + } + return $this->timing; + } + + /** + * @param Title|null $title + */ + public function setTitle( Title $title = null ) { + $this->title = $title; + // Erase the WikiPage so a new one with the new title gets created. + $this->wikipage = null; + } + + /** + * @return Title|null + */ + public function getTitle() { + if ( $this->title === null ) { + global $wgTitle; # fallback to $wg till we can improve this + $this->title = $wgTitle; + wfDebugLog( + 'GlobalTitleFail', + __METHOD__ . ' called by ' . wfGetAllCallers( 5 ) . ' with no title set.' + ); + } + + return $this->title; + } + + /** + * Check, if a Title object is set + * + * @since 1.25 + * @return bool + */ + public function hasTitle() { + return $this->title !== null; + } + + /** + * Check whether a WikiPage object can be get with getWikiPage(). + * Callers should expect that an exception is thrown from getWikiPage() + * if this method returns false. + * + * @since 1.19 + * @return bool + */ + public function canUseWikiPage() { + if ( $this->wikipage ) { + // If there's a WikiPage object set, we can for sure get it + return true; + } + // Only pages with legitimate titles can have WikiPages. + // That usually means pages in non-virtual namespaces. + $title = $this->getTitle(); + return $title ? $title->canExist() : false; + } + + /** + * @since 1.19 + * @param WikiPage $wikiPage + */ + public function setWikiPage( WikiPage $wikiPage ) { + $pageTitle = $wikiPage->getTitle(); + if ( !$this->hasTitle() || !$pageTitle->equals( $this->getTitle() ) ) { + $this->setTitle( $pageTitle ); + } + // Defer this to the end since setTitle sets it to null. + $this->wikipage = $wikiPage; + } + + /** + * Get the WikiPage object. + * May throw an exception if there's no Title object set or the Title object + * belongs to a special namespace that doesn't have WikiPage, so use first + * canUseWikiPage() to check whether this method can be called safely. + * + * @since 1.19 + * @throws MWException + * @return WikiPage + */ + public function getWikiPage() { + if ( $this->wikipage === null ) { + $title = $this->getTitle(); + if ( $title === null ) { + throw new MWException( __METHOD__ . ' called without Title object set' ); + } + $this->wikipage = WikiPage::factory( $title ); + } + + return $this->wikipage; + } + + /** + * @param OutputPage $output + */ + public function setOutput( OutputPage $output ) { + $this->output = $output; + } + + /** + * @return OutputPage + */ + public function getOutput() { + if ( $this->output === null ) { + $this->output = new OutputPage( $this ); + } + + return $this->output; + } + + /** + * @param User $user + */ + public function setUser( User $user ) { + $this->user = $user; + } + + /** + * @return User + */ + public function getUser() { + if ( $this->user === null ) { + $this->user = User::newFromSession( $this->getRequest() ); + } + + return $this->user; + } + + /** + * Accepts a language code and ensures it's sane. Outputs a cleaned up language + * code and replaces with $wgLanguageCode if not sane. + * @param string $code Language code + * @return string + */ + public static function sanitizeLangCode( $code ) { + global $wgLanguageCode; + + // BCP 47 - letter case MUST NOT carry meaning + $code = strtolower( $code ); + + # Validate $code + if ( !$code || !Language::isValidCode( $code ) || $code === 'qqq' ) { + $code = $wgLanguageCode; + } + + return $code; + } + + /** + * @param Language|string $language Language instance or language code + * @throws MWException + * @since 1.19 + */ + public function setLanguage( $language ) { + if ( $language instanceof Language ) { + $this->lang = $language; + } elseif ( is_string( $language ) ) { + $language = self::sanitizeLangCode( $language ); + $obj = Language::factory( $language ); + $this->lang = $obj; + } else { + throw new MWException( __METHOD__ . " was passed an invalid type of data." ); + } + } + + /** + * Get the Language object. + * Initialization of user or request objects can depend on this. + * @return Language + * @throws Exception + * @since 1.19 + */ + public function getLanguage() { + if ( isset( $this->recursion ) ) { + trigger_error( "Recursion detected in " . __METHOD__, E_USER_WARNING ); + $e = new Exception; + wfDebugLog( 'recursion-guard', "Recursion detected:\n" . $e->getTraceAsString() ); + + $code = $this->getConfig()->get( 'LanguageCode' ) ?: 'en'; + $this->lang = Language::factory( $code ); + } elseif ( $this->lang === null ) { + $this->recursion = true; + + global $wgContLang; + + try { + $request = $this->getRequest(); + $user = $this->getUser(); + + $code = $request->getVal( 'uselang', 'user' ); + if ( $code === 'user' ) { + $code = $user->getOption( 'language' ); + } + $code = self::sanitizeLangCode( $code ); + + Hooks::run( 'UserGetLanguageObject', [ $user, &$code, $this ] ); + + if ( $code === $this->getConfig()->get( 'LanguageCode' ) ) { + $this->lang = $wgContLang; + } else { + $obj = Language::factory( $code ); + $this->lang = $obj; + } + + unset( $this->recursion ); + } + catch ( Exception $ex ) { + unset( $this->recursion ); + throw $ex; + } + } + + return $this->lang; + } + + /** + * @param Skin $skin + */ + public function setSkin( Skin $skin ) { + $this->skin = clone $skin; + $this->skin->setContext( $this ); + } + + /** + * @return Skin + */ + public function getSkin() { + if ( $this->skin === null ) { + $skin = null; + Hooks::run( 'RequestContextCreateSkin', [ $this, &$skin ] ); + $factory = SkinFactory::getDefaultInstance(); + + // If the hook worked try to set a skin from it + if ( $skin instanceof Skin ) { + $this->skin = $skin; + } elseif ( is_string( $skin ) ) { + // Normalize the key, just in case the hook did something weird. + $normalized = Skin::normalizeKey( $skin ); + $this->skin = $factory->makeSkin( $normalized ); + } + + // If this is still null (the hook didn't run or didn't work) + // then go through the normal processing to load a skin + if ( $this->skin === null ) { + if ( !in_array( 'skin', $this->getConfig()->get( 'HiddenPrefs' ) ) ) { + # get the user skin + $userSkin = $this->getUser()->getOption( 'skin' ); + $userSkin = $this->getRequest()->getVal( 'useskin', $userSkin ); + } else { + # if we're not allowing users to override, then use the default + $userSkin = $this->getConfig()->get( 'DefaultSkin' ); + } + + // Normalize the key in case the user is passing gibberish + // or has old preferences (T71566). + $normalized = Skin::normalizeKey( $userSkin ); + + // Skin::normalizeKey will also validate it, so + // this won't throw an exception + $this->skin = $factory->makeSkin( $normalized ); + } + + // After all that set a context on whatever skin got created + $this->skin->setContext( $this ); + } + + return $this->skin; + } + + /** + * Get a Message object with context set + * Parameters are the same as wfMessage() + * + * @param string|string[]|MessageSpecifier $key Message key, or array of keys, + * or a MessageSpecifier. + * @param mixed $args,... + * @return Message + */ + public function msg( $key ) { + $args = func_get_args(); + + return call_user_func_array( 'wfMessage', $args )->setContext( $this ); + } + + /** + * Get the RequestContext object associated with the main request + * + * @return RequestContext + */ + public static function getMain() { + if ( self::$instance === null ) { + self::$instance = new self; + } + + return self::$instance; + } + + /** + * Get the RequestContext object associated with the main request + * and gives a warning to the log, to find places, where a context maybe is missing. + * + * @param string $func + * @return RequestContext + * @since 1.24 + */ + public static function getMainAndWarn( $func = __METHOD__ ) { + wfDebug( $func . ' called without context. ' . + "Using RequestContext::getMain() for sanity\n" ); + + return self::getMain(); + } + + /** + * Resets singleton returned by getMain(). Should be called only from unit tests. + */ + public static function resetMain() { + if ( !( defined( 'MW_PHPUNIT_TEST' ) || defined( 'MW_PARSER_TEST' ) ) ) { + throw new MWException( __METHOD__ . '() should be called only from unit tests!' ); + } + self::$instance = null; + } + + /** + * Export the resolved user IP, HTTP headers, user ID, and session ID. + * The result will be reasonably sized to allow for serialization. + * + * @return array + * @since 1.21 + */ + public function exportSession() { + $session = MediaWiki\Session\SessionManager::getGlobalSession(); + return [ + 'ip' => $this->getRequest()->getIP(), + 'headers' => $this->getRequest()->getAllHeaders(), + 'sessionId' => $session->isPersistent() ? $session->getId() : '', + 'userId' => $this->getUser()->getId() + ]; + } + + /** + * Import an client IP address, HTTP headers, user ID, and session ID + * + * This sets the current session, $wgUser, and $wgRequest from $params. + * Once the return value falls out of scope, the old context is restored. + * This method should only be called in contexts where there is no session + * ID or end user receiving the response (CLI or HTTP job runners). This + * is partly enforced, and is done so to avoid leaking cookies if certain + * error conditions arise. + * + * This is useful when background scripts inherit context when acting on + * behalf of a user. In general the 'sessionId' parameter should be set + * to an empty string unless session importing is *truly* needed. This + * feature is somewhat deprecated. + * + * @note suhosin.session.encrypt may interfere with this method. + * + * @param array $params Result of RequestContext::exportSession() + * @return ScopedCallback + * @throws MWException + * @since 1.21 + */ + public static function importScopedSession( array $params ) { + if ( strlen( $params['sessionId'] ) && + MediaWiki\Session\SessionManager::getGlobalSession()->isPersistent() + ) { + // Sanity check to avoid sending random cookies for the wrong users. + // This method should only called by CLI scripts or by HTTP job runners. + throw new MWException( "Sessions can only be imported when none is active." ); + } elseif ( !IP::isValid( $params['ip'] ) ) { + throw new MWException( "Invalid client IP address '{$params['ip']}'." ); + } + + if ( $params['userId'] ) { // logged-in user + $user = User::newFromId( $params['userId'] ); + $user->load(); + if ( !$user->getId() ) { + throw new MWException( "No user with ID '{$params['userId']}'." ); + } + } else { // anon user + $user = User::newFromName( $params['ip'], false ); + } + + $importSessionFunc = function ( User $user, array $params ) { + global $wgRequest, $wgUser; + + $context = RequestContext::getMain(); + + // Commit and close any current session + if ( MediaWiki\Session\PHPSessionHandler::isEnabled() ) { + session_write_close(); // persist + session_id( '' ); // detach + $_SESSION = []; // clear in-memory array + } + + // Get new session, if applicable + $session = null; + if ( strlen( $params['sessionId'] ) ) { // don't make a new random ID + $manager = MediaWiki\Session\SessionManager::singleton(); + $session = $manager->getSessionById( $params['sessionId'], true ) + ?: $manager->getEmptySession(); + } + + // Remove any user IP or agent information, and attach the request + // with the new session. + $context->setRequest( new FauxRequest( [], false, $session ) ); + $wgRequest = $context->getRequest(); // b/c + + // Now that all private information is detached from the user, it should + // be safe to load the new user. If errors occur or an exception is thrown + // and caught (leaving the main context in a mixed state), there is no risk + // of the User object being attached to the wrong IP, headers, or session. + $context->setUser( $user ); + $wgUser = $context->getUser(); // b/c + if ( $session && MediaWiki\Session\PHPSessionHandler::isEnabled() ) { + session_id( $session->getId() ); + Wikimedia\quietCall( 'session_start' ); + } + $request = new FauxRequest( [], false, $session ); + $request->setIP( $params['ip'] ); + foreach ( $params['headers'] as $name => $value ) { + $request->setHeader( $name, $value ); + } + // Set the current context to use the new WebRequest + $context->setRequest( $request ); + $wgRequest = $context->getRequest(); // b/c + }; + + // Stash the old session and load in the new one + $oUser = self::getMain()->getUser(); + $oParams = self::getMain()->exportSession(); + $oRequest = self::getMain()->getRequest(); + $importSessionFunc( $user, $params ); + + // Set callback to save and close the new session and reload the old one + return new ScopedCallback( + function () use ( $importSessionFunc, $oUser, $oParams, $oRequest ) { + global $wgRequest; + $importSessionFunc( $oUser, $oParams ); + // Restore the exact previous Request object (instead of leaving FauxRequest) + RequestContext::getMain()->setRequest( $oRequest ); + $wgRequest = RequestContext::getMain()->getRequest(); // b/c + } + ); + } + + /** + * Create a new extraneous context. The context is filled with information + * external to the current session. + * - Title is specified by argument + * - Request is a FauxRequest, or a FauxRequest can be specified by argument + * - User is an anonymous user, for separation IPv4 localhost is used + * - Language will be based on the anonymous user and request, may be content + * language or a uselang param in the fauxrequest data may change the lang + * - Skin will be based on the anonymous user, should be the wiki's default skin + * + * @param Title $title Title to use for the extraneous request + * @param WebRequest|array $request A WebRequest or data to use for a FauxRequest + * @return RequestContext + */ + public static function newExtraneousContext( Title $title, $request = [] ) { + $context = new self; + $context->setTitle( $title ); + if ( $request instanceof WebRequest ) { + $context->setRequest( $request ); + } else { + $context->setRequest( new FauxRequest( $request ) ); + } + $context->user = User::newFromName( '127.0.0.1', false ); + + return $context; + } +} |