mirror of https://github.com/gnosygnu/xowa
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
614 lines
20 KiB
614 lines
20 KiB
/*
|
|
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.context;
|
|
|
|
import gplx.xowa.mediawiki.XophpObject_;
|
|
import gplx.xowa.mediawiki.XophpString_;
|
|
import gplx.xowa.mediawiki.includes.XomwOutputPage;
|
|
import gplx.xowa.mediawiki.includes.XomwTitle;
|
|
import gplx.xowa.mediawiki.includes.XomwWebRequest;
|
|
import gplx.xowa.mediawiki.includes.config.XomwConfig;
|
|
import gplx.xowa.mediawiki.includes.page.XomwWikiPage;
|
|
import gplx.xowa.mediawiki.includes.user.XomwUser;
|
|
import gplx.xowa.mediawiki.languages.XomwLanguage;
|
|
|
|
/**
|
|
* Group all the pieces relevant to the context of a request into one instance
|
|
*/
|
|
public class XomwRequestContext { // implements IContextSource, MutableContext
|
|
/**
|
|
* @var WebRequest
|
|
*/
|
|
private XomwWebRequest request;
|
|
|
|
/**
|
|
* @var Title
|
|
*/
|
|
private XomwTitle title;
|
|
|
|
/**
|
|
* @var WikiPage
|
|
*/
|
|
private XomwWikiPage wikipage;
|
|
|
|
/**
|
|
* @var OutputPage
|
|
*/
|
|
private XomwOutputPage output;
|
|
|
|
/**
|
|
* @var User
|
|
*/
|
|
private XomwUser user;
|
|
|
|
/**
|
|
* @var Language
|
|
*/
|
|
private XomwLanguage lang;
|
|
|
|
// /**
|
|
// * @var Skin
|
|
// */
|
|
// private $skin;
|
|
//
|
|
// /**
|
|
// * @var Timing
|
|
// */
|
|
// private $timing;
|
|
|
|
/**
|
|
* @var Config
|
|
*/
|
|
private XomwConfig config;
|
|
|
|
/**
|
|
* @var RequestContext
|
|
*/
|
|
private static XomwRequestContext instance = null;
|
|
|
|
/**
|
|
* @param Config $config
|
|
*/
|
|
public void setConfig(XomwConfig cconfig) {
|
|
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 XomwWebRequest getRequest() {
|
|
if (XophpObject_.is_null(this.request)) {
|
|
// XOMW:skip
|
|
// global $wgCommandLineMode;
|
|
// // create the WebRequest object on the fly
|
|
// if ($wgCommandLineMode) {
|
|
// this.request = new FauxRequest([]);
|
|
// } else {
|
|
this.request = new XomwWebRequest();
|
|
// }
|
|
}
|
|
|
|
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;
|
|
// // Invalidate cached user interface language
|
|
// this.lang = null;
|
|
// }
|
|
|
|
/**
|
|
* @return User
|
|
*/
|
|
public XomwUser getUser() {
|
|
if (XophpObject_.is_null(this.user)) {
|
|
this.user = XomwUser.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
|
|
*/
|
|
private boolean recursion = false; // XOMW:appears to be auto-declared in MW
|
|
public XomwLanguage 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;
|
|
|
|
try {
|
|
request = this.getRequest();
|
|
user = this.getUser();
|
|
|
|
String code = request.getVal("uselang", "user");
|
|
if (XophpString_.eq(code, "user")) {
|
|
// code = user.getOption("language");
|
|
}
|
|
// $code = self::sanitizeLangCode($code);
|
|
//
|
|
// XomwHooks.run("UserGetLanguageObject", XophpArray.New(user, &$code, this));
|
|
//
|
|
// if ($code === this.getConfig().get("LanguageCode")) {
|
|
// this.lang = MediaWikiServices::getInstance().getContentLanguage();
|
|
// } else {
|
|
// $obj = Language::factory($code);
|
|
// this.lang = $obj;
|
|
// }
|
|
} finally {
|
|
this.recursion = false; // XOMW:`unset(this.recursion);`
|
|
}
|
|
// }
|
|
//
|
|
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 wfMessage(...$args).setContext($this);
|
|
// }
|
|
|
|
/**
|
|
* Get the RequestContext object associated with the main request
|
|
*
|
|
* @return RequestContext
|
|
*/
|
|
public static XomwRequestContext getMain() {
|
|
if (instance == null) {
|
|
instance = new XomwRequestContext();
|
|
}
|
|
|
|
return 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!');
|
|
// }
|
|
// 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;
|
|
// }
|
|
}
|