%PDF- %PDF-
Direktori : /www/varak.net/wiki.varak.net/extensions/MobileFrontend/includes/ |
Current File : /www/varak.net/wiki.varak.net/extensions/MobileFrontend/includes/MobileContext.php |
<?php use MediaWiki\MediaWikiServices; use MobileFrontend\Devices\DeviceDetectorService; use MobileFrontend\WMFBaseDomainExtractor; /** * Provide various request-dependant methods to use in mobile context */ class MobileContext extends ContextSource { const MODE_BETA = 'beta'; const MODE_STABLE = 'stable'; const OPTIN_COOKIE_NAME = 'optin'; const STOP_MOBILE_REDIRECT_COOKIE_NAME = 'stopMobileRedirect'; const USEFORMAT_COOKIE_NAME = 'mf_useformat'; const USER_MODE_PREFERENCE_NAME = 'mfMode'; const LOGGER_CHANNEL = 'mobile'; /** * Saves the testing mode user has opted in: 'beta' or 'stable' * @var string $mobileMode */ protected $mobileMode; /** * Whether to show the first paragraph before the infobox in the lead section * @var boolean $showFirstParagraphBeforeInfobox */ protected $showFirstParagraphBeforeInfobox; /** * Save explicitly requested format * @var string $useFormat */ protected $useFormat; /** * Save whether current page is blacklisted from displaying in mobile view * @var boolean $blacklistedPage */ protected $blacklistedPage; /** * Key/value pairs of things to add to X-Analytics response header for anlytics * @var array */ protected $analyticsLogItems = []; /** * The memoized result of `MobileContext#isMobileDevice`. * * This defaults to `null`, meaning that `MobileContext#isMobileDevice` has * yet to be called. * * @see MobileContext#isMobileDevice * * @var bool|null $isMobileDevice */ private $isMobileDevice = null; /** * @var string $action MediaWiki 'action' */ protected $action; /** * Saves requested Mobile action * @var string $mobileAction */ protected $mobileAction; /** * Save whether mobile view is explicity requested * @var boolean $forceMobileView */ private $forceMobileView = false; /** * Save whether content should be transformed to better suit mobile devices * @var boolean $contentTransformations */ private $contentTransformations = true; /** * Save whether or not we should display the mobile view * @var boolean $mobileView */ private $mobileView = null; /** * Have we already checked for desktop/mobile view toggling? * @var boolean $toggleViewChecked */ private $toggleViewChecked = false; /** * Save an instance of this class * @var MobileContext $instance */ private static $instance = null; /** * @var string What to switch the view to */ private $viewChange = ''; /** * @var String Domain to use for the stopMobileRedirect cookie */ public static $mfStopRedirectCookieHost = null; /** * @var String Stores the actual mobile url template. */ private $mobileUrlTemplate = false; /** * Returns the actual MobileContext Instance or create a new if no exists * @return MobileContext */ public static function singleton() { if ( !self::$instance ) { self::$instance = new MobileContext( RequestContext::getMain() ); } return self::$instance; } /** * Resets the singleton instance. */ public static function resetInstanceForTesting() { self::$instance = null; } /** * Set the IontextSource Object * @param IContextSource $context The IContextSource Object has to set */ protected function __construct( IContextSource $context ) { $this->setContext( $context ); } /** * Get MobileFrontend's config object. * @return Config */ public function getMFConfig() { /** @var Config $config */ $config = MediaWikiServices::getInstance()->getService( 'MobileFrontend.Config' ); return $config; } /** * Detects whether the UA is sending the request from a device and, if so, * whether to display the mobile view to that device. * * The mobile view will always be displayed to mobile devices. However, it * will only be displayed to tablet devices if `$wgMFShowMobileViewToTablets` * is truthy. * * @fixme This should be renamed to something more appropriate, e.g. * `shouldDisplayMobileViewToDevice`. * * @see MobileContext::shouldDisplayMobileView * * @return bool */ public function isMobileDevice() { if ( $this->isMobileDevice !== null ) { return $this->isMobileDevice; } $this->isMobileDevice = false; $config = $this->getMFConfig(); $properties = DeviceDetectorService::factory( $config ) ->detectDeviceProperties( $this->getRequest(), $_SERVER ); if ( $properties ) { $showMobileViewToTablets = $config->get( 'MFShowMobileViewToTablets' ); $this->isMobileDevice = $properties->isMobileDevice() || ( $properties->isTabletDevice() && $showMobileViewToTablets ); } return $this->isMobileDevice; } /** * Save whether mobile view should always be enforced * @param bool $value should mobile view be enforced? */ public function setForceMobileView( $value ) { $this->forceMobileView = $value; } /** * Whether mobile view should always be enforced * @return bool is mobile view enforced? */ public function getForceMobileView() { return $this->forceMobileView; } /** * Whether content should be transformed to better suit mobile devices * @param bool $value should content be transformed? */ public function setContentTransformations( $value ) { $this->contentTransformations = $value; } /** * Whether content should be transformed to better suit mobile devices * @return bool is content being transformed? */ public function getContentTransformations() { return $this->contentTransformations; } /** * Sets the value of $this->mobileMode property to the value of the 'optin' cookie. * If the cookie is not set the value will be an empty string. */ private function loadMobileModeCookie() { $this->mobileMode = $this->getRequest()->getCookie( self::OPTIN_COOKIE_NAME, '' ); } /** * Returns the testing mode user has opted in: 'beta' or any other value for stable * @return string */ private function getMobileMode() { $enableBeta = $this->getMFConfig()->get( 'MFEnableBeta' ); if ( !$enableBeta ) { return ''; } if ( is_null( $this->mobileMode ) ) { $mobileAction = $this->getMobileAction(); if ( $mobileAction === self::MODE_BETA || $mobileAction === self::MODE_STABLE ) { $this->mobileMode = $mobileAction; } else { $user = $this->getUser(); if ( $user->isAnon() ) { $this->loadMobileModeCookie(); } else { $mode = $user->getOption( self::USER_MODE_PREFERENCE_NAME ); $this->mobileMode = $mode; // Edge case where preferences are corrupt or the user opted // in before change. if ( $mode === null ) { // Should we set the user option here? $this->loadMobileModeCookie(); } } } } return $this->mobileMode; } /** * Sets testing group membership, both cookie and this class variables * @param string $mode Mode to set */ public function setMobileMode( $mode ) { if ( $mode !== self::MODE_BETA ) { $mode = ''; } // Update statistics if ( $mode === self::MODE_BETA ) { wfIncrStats( 'mobile.opt_in_cookie_set' ); } if ( !$mode ) { wfIncrStats( 'mobile.opt_in_cookie_unset' ); } $this->mobileMode = $mode; $user = $this->getUser(); if ( $user->getId() ) { $user->setOption( self::USER_MODE_PREFERENCE_NAME, $mode ); DeferredUpdates::addCallableUpdate( function () use ( $user, $mode ) { if ( wfReadOnly() ) { return; } $latestUser = $user->getInstanceForUpdate(); $latestUser->setOption( self::USER_MODE_PREFERENCE_NAME, $mode ); $latestUser->saveSettings(); } ); } $this->getRequest()->response()->setCookie( self::OPTIN_COOKIE_NAME, $mode, 0, [ 'prefix' => '', 'domain' => $this->getCookieDomain() ] ); } /** * Wether user is Beta group member * @return bool */ public function isBetaGroupMember() { return $this->getMobileMode() === self::MODE_BETA; } /** * Determine whether or not we should display the mobile view * * Step through the hierarchy of what should or should not trigger * the mobile view. * * Primacy is given to the page action - we will never show mobile view * for page edits or page history. 'userformat' request param is then * honored, followed by cookie settings, then actual device detection, * finally falling back on false. * @return bool */ public function shouldDisplayMobileView() { if ( !is_null( $this->mobileView ) ) { return $this->mobileView; } // check if we need to toggle between mobile/desktop view $this->checkToggleView(); $this->mobileView = $this->shouldDisplayMobileViewInternal(); if ( $this->mobileView ) { $this->redirectMobileEnabledPages(); Hooks::run( 'EnterMobileMode', [ $this ] ); } return $this->mobileView; } /** * If a page has an equivalent but different mobile page redirect to it */ private function redirectMobileEnabledPages() { $request = $this->getRequest(); $title = $this->getTitle(); $redirectUrl = null; if ( $request->getCheck( 'diff' ) && MobileFrontendHooks::shouldMobileFormatSpecialPages( $this->getUser() ) ) { $redirectUrl = SpecialMobileDiff::getMobileUrlFromDesktop(); } if ( $request->getVal( 'action' ) === 'history' && // IContextSource::getTitle() can be null $title !== null && // check, if SpecialMobileHistory supports the history action set for this title // content model SpecialMobileHistory::shouldUseSpecialHistory( $title, $this->getUser() ) ) { $values = $this->getRequest()->getValues(); // avoid infinite redirect loops unset( $values['action'] ); // Avoid multiple history parameters unset( $values['title'] ); $redirectUrl = SpecialPage::getTitleFor( 'History', $this->getTitle() )-> getLocalURL( $values ); } if ( $redirectUrl ) { $this->getOutput()->redirect( $redirectUrl ); } } /** * Value for shouldDisplayMobileView() * @return bool */ private function shouldDisplayMobileViewInternal() { // May be overridden programmatically if ( $this->forceMobileView ) { return true; } // always display desktop or mobile view if it's explicitly requested $useFormat = $this->getUseFormat(); if ( $useFormat == 'desktop' ) { return false; } elseif ( $useFormat == 'mobile' ) { return true; } /** * If a user is accessing the site from a mobile domain, then we should * always display the mobile version of the site (otherwise, the cache * may get polluted). See * https://bugzilla.wikimedia.org/show_bug.cgi?id=46473 */ if ( $this->usingMobileDomain() ) { return true; } // check cookies for what to display $useMobileFormat = $this->getUseFormatCookie(); if ( $useMobileFormat == 'true' ) { return true; } $stopMobileRedirect = $this->getStopMobileRedirectCookie(); if ( $stopMobileRedirect == 'true' ) { return false; } // do device detection if ( $this->isMobileDevice() ) { return true; } return false; } /** * Checks whether current page is blacklisted from displaying mobile view * @return bool */ public function isBlacklistedPage() { if ( is_null( $this->blacklistedPage ) ) { $this->blacklistedPage = $this->isBlacklistedPageInternal(); } return $this->blacklistedPage; } /** * Value for isBlacklistedPage() * @return bool */ private function isBlacklistedPageInternal() { $config = $this->getMFConfig(); $noMobilePages = $config->get( 'MFNoMobilePages' ); $noMobileCategory = $config->get( 'MFNoMobileCategory' ); // Check for blacklisted category membership $title = $this->getTitle(); if ( $noMobileCategory && $title ) { $id = $title->getArticleID(); if ( $id ) { $dbr = wfGetDB( DB_REPLICA ); if ( $dbr->selectField( 'categorylinks', 'cl_from', [ 'cl_from' => $id, 'cl_to' => $noMobileCategory ], __METHOD__ ) ) { return true; } } } // ...and individual page blacklisting if ( $noMobilePages && $title && in_array( $title->getPrefixedText(), $noMobilePages ) ) { return true; } return false; } /** * Get requested mobile action * @return string */ public function getMobileAction() { if ( is_null( $this->mobileAction ) ) { $this->mobileAction = $this->getRequest()->getText( 'mobileaction' ); } return $this->mobileAction; } /** * Gets the value of the `useformat` query string parameter. This can be * overridden using the `MobileContext#setUseFormat`. * * @return string */ public function getUseFormat() { if ( !isset( $this->useFormat ) ) { $useFormat = $this->getRequest()->getText( 'useformat' ); $this->setUseFormat( $useFormat ); } return $this->useFormat; } /** * Overrides the value of `MobileContext#getUseFormat`. * * @param string $useFormat new value */ public function setUseFormat( $useFormat ) { $this->useFormat = $useFormat; } /** * Set Cookie to stop automatically redirect to mobile page * @param int|null $expiry Expire time of cookie */ public function setStopMobileRedirectCookie( $expiry = null ) { if ( is_null( $expiry ) ) { $expiry = $this->getUseFormatCookieExpiry(); } $this->getRequest()->response()->setcookie( self::STOP_MOBILE_REDIRECT_COOKIE_NAME, 'true', $expiry, [ 'domain' => $this->getStopMobileRedirectCookieDomain(), 'prefix' => '', 'secure' => false, ] ); } /** * Remove cookie and continue automatic redirect to mobile page */ public function unsetStopMobileRedirectCookie() { if ( is_null( $this->getStopMobileRedirectCookie() ) ) { return; } $expire = $this->getUseFormatCookieExpiry( time(), -3600 ); $this->setStopMobileRedirectCookie( $expire ); } /** * Read cookie for stop automatic mobile redirect * @return string */ public function getStopMobileRedirectCookie() { $stopMobileRedirectCookie = $this->getRequest() ->getCookie( self::STOP_MOBILE_REDIRECT_COOKIE_NAME, '' ); return $stopMobileRedirectCookie; } /** * Get the useformat cookie * * This cookie can determine whether or not a user should see the mobile * version of a page. * * @return string|null */ public function getUseFormatCookie() { $useFormatFromCookie = $this->getRequest()->getCookie( self::USEFORMAT_COOKIE_NAME, '' ); return $useFormatFromCookie; } /** * Return the base level domain or IP address * * @return string */ public function getCookieDomain() { $helper = new WMFBaseDomainExtractor(); return $helper->getCookieDomain( $this->getMFConfig()->get( 'Server' ) ); } /** * Determine the correct domain to use for the stopMobileRedirect cookie * * Will use $wgMFStopRedirectCookieHost if it's set, otherwise will use * result of getCookieDomain() * @return string */ public function getStopMobileRedirectCookieDomain() { $mfStopRedirectCookieHost = $this->getMFConfig()->get( 'MFStopRedirectCookieHost' ); if ( !$mfStopRedirectCookieHost ) { self::$mfStopRedirectCookieHost = $this->getCookieDomain(); } else { self::$mfStopRedirectCookieHost = $mfStopRedirectCookieHost; } return self::$mfStopRedirectCookieHost; } /** * Set the mf_useformat cookie * * This cookie can determine whether or not a user should see the mobile * version of pages. * * @param string $cookieFormat should user see mobile version of pages? * @param null $expiry Expiration of cookie */ public function setUseFormatCookie( $cookieFormat = 'true', $expiry = null ) { if ( is_null( $expiry ) ) { $expiry = $this->getUseFormatCookieExpiry(); } $this->getRequest()->response()->setcookie( self::USEFORMAT_COOKIE_NAME, $cookieFormat, $expiry, [ 'prefix' => '', 'httpOnly' => false, ] ); wfIncrStats( 'mobile.useformat_' . $cookieFormat . '_cookie_set' ); } /** * Remove cookie based saved useformat value */ public function unsetUseFormatCookie() { if ( is_null( $this->getUseFormatCookie() ) ) { return; } // set expiration date in the past $expire = $this->getUseFormatCookieExpiry( time(), -3600 ); $this->setUseFormatCookie( '', $expire ); } /** * Get the expiration time for the mf_useformat cookie * * @param int|null $startTime The base time (in seconds since Epoch) from which to calculate * cookie expiration. If null, time() is used. * @param int|null $cookieDuration The time (in seconds) the cookie should last * @return int The time (in seconds since Epoch) that the cookie should expire */ protected function getUseFormatCookieExpiry( $startTime = null, $cookieDuration = null ) { // use $cookieDuration if it's valid if ( intval( $cookieDuration ) === 0 ) { $cookieDuration = $this->getUseFormatCookieDuration(); } // use $startTime if it's valid if ( intval( $startTime ) === 0 ) { $startTime = time(); } $expiry = $startTime + $cookieDuration; return $expiry; } /** * Determine the duration the cookie should last. * * If $wgMobileFrontendFormatcookieExpiry has a non-0 value, use that * for the duration. Otherwise, fall back to $wgCookieExpiration. * * @return int The number of seconds for which the cookie should last. */ public function getUseFormatCookieDuration() { $mobileFrontendFormatCookieExpiry = $this->getMFConfig()->get( 'MobileFrontendFormatCookieExpiry' ); $cookieExpiration = $this->getConfig()->get( 'CookieExpiration' ); $cookieDuration = ( abs( intval( $mobileFrontendFormatCookieExpiry ) ) > 0 ) ? $mobileFrontendFormatCookieExpiry : $cookieExpiration; return $cookieDuration; } /** * Take a URL Host Template and return the mobile token portion * * Eg if a desktop domain is en.wikipedia.org, but the mobile variant is * en.m.wikipedia.org, the mobile token is 'm.' * * @param string $mobileUrlHostTemplate URL host * @return string */ public function getMobileHostToken( $mobileUrlHostTemplate ) { return preg_replace( '/%h[0-9]\.{0,1}/', '', $mobileUrlHostTemplate ); } /** * Get the template for mobile URLs. * @see $wgMobileUrlTemplate * @return string */ public function getMobileUrlTemplate() { if ( !$this->mobileUrlTemplate ) { $this->mobileUrlTemplate = $this->getMFConfig()->get( 'MobileUrlTemplate' ); } return $this->mobileUrlTemplate; } /** * Take a URL and return a copy that conforms to the mobile URL template * @param string $url URL to convert * @param bool $forceHttps should force HTTPS? * @return string|bool */ public function getMobileUrl( $url, $forceHttps = false ) { if ( $this->shouldDisplayMobileView() ) { $subdomainTokenReplacement = null; if ( Hooks::run( 'GetMobileUrl', [ &$subdomainTokenReplacement, $this ] ) ) { if ( !empty( $subdomainTokenReplacement ) ) { $mobileUrlHostTemplate = $this->parseMobileUrlTemplate( 'host' ); $mobileToken = $this->getMobileHostToken( $mobileUrlHostTemplate ); $this->mobileUrlTemplate = str_replace( $mobileToken, $subdomainTokenReplacement, $this->getMobileUrlTemplate() ); } } } $parsedUrl = wfParseUrl( $url ); // if parsing failed, maybe it's a local Url, try to expand and reparse it - task T107505 if ( !$parsedUrl ) { $expandedUrl = wfExpandUrl( $url ); if ( $expandedUrl ) { $parsedUrl = wfParseUrl( $expandedUrl ); } if ( !$expandedUrl || !$parsedUrl ) { return false; } } $this->updateMobileUrlHost( $parsedUrl ); if ( $forceHttps ) { $parsedUrl['scheme'] = 'https'; $parsedUrl['delimiter'] = '://'; } $assembleUrl = wfAssembleUrl( $parsedUrl ); return $assembleUrl; } /** * If a mobile-domain is specified by the $wgMobileUrlTemplate and * there's a mobile header, then we assume the user is accessing * the site from the mobile-specific domain (because why would the * desktop site set the header?). * @return bool */ public function usingMobileDomain() { $config = $this->getMFConfig(); $mobileHeader = $config->get( 'MFMobileHeader' ); return ( $config->get( 'MobileUrlTemplate' ) && $mobileHeader && $this->getRequest()->getHeader( $mobileHeader ) !== false ); } /** * Take a URL and return a copy that removes any mobile tokens * @param string $url representing a page on the mobile domain e.g. `https://en.m.wikipedia.org/` * @return string (absolute url) */ public function getDesktopUrl( $url ) { $parsedUrl = wfParseUrl( $url ); $this->updateDesktopUrlHost( $parsedUrl ); $this->updateDesktopUrlQuery( $parsedUrl ); $desktopUrl = wfAssembleUrl( $parsedUrl ); return $desktopUrl; } /** * Update host of given URL to conform to mobile URL template. * @param array &$parsedUrl Result of parseUrl() or wfParseUrl() */ protected function updateMobileUrlHost( &$parsedUrl ) { if ( IP::isIPAddress( $parsedUrl['host'] ) ) { // Do not update host when IP is used return; } $mobileUrlHostTemplate = $this->parseMobileUrlTemplate( 'host' ); if ( !strlen( $mobileUrlHostTemplate ) ) { return; } $parsedHostParts = explode( ".", $parsedUrl['host'] ); $templateHostParts = explode( ".", $mobileUrlHostTemplate ); $targetHostParts = []; foreach ( $templateHostParts as $key => $templateHostPart ) { if ( strstr( $templateHostPart, '%h' ) ) { $parsedHostPartKey = substr( $templateHostPart, 2 ); if ( !array_key_exists( $parsedHostPartKey, $parsedHostParts ) ) { // invalid pattern for this host, ignore return; } $targetHostParts[$key] = $parsedHostParts[$parsedHostPartKey]; } elseif ( isset( $parsedHostParts[$key] ) && $templateHostPart == $parsedHostParts[$key] ) { $targetHostParts = $parsedHostParts; break; } else { $targetHostParts[$key] = $templateHostPart; } } $parsedUrl['host'] = implode( ".", $targetHostParts ); } /** * Update the host of a given URL to strip out any mobile tokens * @param array &$parsedUrl Result of parseUrl() or wfParseUrl() */ protected function updateDesktopUrlHost( &$parsedUrl ) { $server = $this->getConfig()->get( 'Server' ); $mobileUrlHostTemplate = $this->parseMobileUrlTemplate( 'host' ); if ( !strlen( $mobileUrlHostTemplate ) ) { return; } $parsedWgServer = wfParseUrl( $server ); $parsedUrl['host'] = $parsedWgServer['host']; } /** * Update the query portion of a given URL to remove any 'useformat' params * @param array &$parsedUrl Result of parseUrl() or wfParseUrl() */ protected function updateDesktopUrlQuery( &$parsedUrl ) { if ( isset( $parsedUrl['query'] ) && strpos( $parsedUrl['query'], 'useformat' ) !== false ) { $query = wfCgiToArray( $parsedUrl['query'] ); unset( $query['useformat'] ); $parsedUrl['query'] = wfArrayToCgi( $query ); } } /** * Update path of given URL to conform to mobile URL template. * * NB: this is not actually being used anywhere at the moment. It will * take some magic to get MW to properly handle path modifications like * this is intended to provide. This will hopefully be implemented someday * in the not to distant future. * * @param array &$parsedUrl Result of parseUrl() or wfParseUrl() */ protected function updateMobileUrlPath( &$parsedUrl ) { $scriptPath = $this->getConfig()->get( 'ScriptPath' ); $mobileUrlPathTemplate = $this->parseMobileUrlTemplate( 'path' ); // if there's no path template, no reason to continue. if ( !strlen( $mobileUrlPathTemplate ) ) { return; } // find out if we already have a templated path $templatePathOffset = strpos( $mobileUrlPathTemplate, '%p' ); $templatePathSansToken = substr( $mobileUrlPathTemplate, 0, $templatePathOffset ); if ( substr_compare( $parsedUrl[ 'path' ], $scriptPath . $templatePathSansToken, 0 ) > 0 ) { return; } $scriptPathLength = strlen( $scriptPath ); // the "+ 1" removes the preceding "/" from the path sans $wgScriptPath $pathSansScriptPath = substr( $parsedUrl[ 'path' ], $scriptPathLength + 1 ); $parsedUrl[ 'path' ] = $scriptPath . $templatePathSansToken . $pathSansScriptPath; } /** * Parse mobile URL template into its host and path components. * * Optionally specify which portion of the template you want returned. * @param string|null $part which part to return? * @return mixed */ public function parseMobileUrlTemplate( $part = null ) { $mobileUrlTemplate = $this->getMobileUrlTemplate(); $pathStartPos = strpos( $mobileUrlTemplate, '/' ); /** * This if/else block exists because of an annoying aspect of substr() * Even if you pass 'null' or 'false' into the 'length' param, it * will return an empty string. * http://www.stopgeek.com/wp-content/uploads/2007/07/sense.jpg */ if ( $pathStartPos === false ) { $host = substr( $mobileUrlTemplate, 0 ); } else { $host = substr( $mobileUrlTemplate, 0, $pathStartPos ); } $path = substr( $mobileUrlTemplate, $pathStartPos ); if ( $part == 'host' ) { return $host; } elseif ( $part == 'path' ) { return $path; } else { return [ 'host' => $host, 'path' => $path ]; } } /** * Toggles view to one specified by the user * * If a user has requested a particular view (eg clicked 'Desktop' from * a mobile page), set the requested view for this particular request * and set a cookie to keep them on that view for subsequent requests. * * @param string $view User requested particular view */ public function toggleView( $view ) { $this->viewChange = $view; if ( !strlen( trim( $this->getMobileUrlTemplate() ) ) ) { $this->setUseFormat( $view ); } } /** * Performs view change as requested vy toggleView() */ public function doToggling() { $mobileUrlTemplate = $this->getMobileUrlTemplate(); if ( !$this->viewChange ) { return; } $query = $this->getRequest()->getQueryValues(); unset( $query['mobileaction'] ); unset( $query['useformat'] ); unset( $query['title'] ); $url = $this->getTitle()->getFullURL( $query, false, PROTO_CURRENT ); if ( $this->viewChange == 'mobile' ) { // unset stopMobileRedirect cookie // @TODO is this necessary with unsetting the cookie via JS? $this->unsetStopMobileRedirectCookie(); // if no mobileurl template, set mobile cookie if ( !strlen( trim( $mobileUrlTemplate ) ) ) { $this->setUseFormatCookie(); } else { // else redirect to mobile domain $mobileUrl = $this->getMobileUrl( $url ); $this->getOutput()->redirect( $mobileUrl, 301 ); } } elseif ( $this->viewChange == 'desktop' ) { // set stopMobileRedirect cookie $this->setStopMobileRedirectCookie(); // unset useformat cookie if ( $this->getUseFormatCookie() == "true" ) { $this->unsetUseFormatCookie(); } if ( strlen( trim( $mobileUrlTemplate ) ) ) { // if mobileurl template, redirect to desktop domain $desktopUrl = $this->getDesktopUrl( $url ); $this->getOutput()->redirect( $desktopUrl, 301 ); } } } /** * Determine whether or not we need to toggle the view, and toggle it */ public function checkToggleView() { if ( !$this->toggleViewChecked ) { $this->toggleViewChecked = true; $mobileAction = $this->getMobileAction(); if ( $mobileAction == 'toggle_view_desktop' ) { $this->toggleView( 'desktop' ); } elseif ( $mobileAction == 'toggle_view_mobile' ) { $this->toggleView( 'mobile' ); } } } /** * Determine whether or not a given URL is local * * @param string $url URL to check against * @return bool */ public function isLocalUrl( $url ) { $parsedTarget = wfParseUrl( $url ); $parsedServer = wfParseUrl( $this->getMFConfig()->get( 'Server' ) ); return $parsedTarget['host'] === $parsedServer['host']; } /** * Add key/value pairs for analytics purposes to $this->analyticsLogItems * @param string $key for <key> in `X-Analytics: <key>=<value>` * @param string $val for <value> in `X-Analytics: <key>=<value>` */ public function addAnalyticsLogItem( $key, $val ) { $key = trim( $key ); $val = trim( $val ); $this->analyticsLogItems[$key] = $val; } /** * Read key/value pairs for analytics purposes from $this->analyticsLogItems * @return array */ public function getAnalyticsLogItems() { return $this->analyticsLogItems; } /** * Get HTTP header string for X-Analytics * * This is made up of key/value pairs and is used for analytics purposes. * * @return string|bool */ public function getXAnalyticsHeader() { $response = $this->getRequest()->response(); $currentHeader = method_exists( $response, 'getHeader' ) ? $response->getHeader( 'X-Analytics' ) : ''; parse_str( preg_replace( '/; */', '&', $currentHeader ), $logItems ); $logItems += $this->getAnalyticsLogItems(); if ( count( $logItems ) ) { $xanalytics_items = []; foreach ( $logItems as $key => $val ) { $xanalytics_items[] = urlencode( $key ) . "=" . urlencode( $val ); } $headerValue = implode( ';', $xanalytics_items ); return "X-Analytics: $headerValue"; } else { return false; } } /** * Take a key/val pair in string format and add it to $this->analyticsLogItems * * @param string $xanalytics_item In the format key=value */ public function addAnalyticsLogItemFromXAnalytics( $xanalytics_item ) { list( $key, $val ) = explode( '=', $xanalytics_item, 2 ); $this->addAnalyticsLogItem( urldecode( $key ), urldecode( $val ) ); } /** * Adds analytics log items if the user is in beta mode * * Invoked from MobileFrontendHooks::onRequestContextCreateSkin() * * Making changes to what this method logs? Make sure you update the * documentation for the X-Analytics header: https://wikitech.wikimedia.org/wiki/X-Analytics */ public function logMobileMode() { if ( $this->isBetaGroupMember() ) { $this->addAnalyticsLogItem( 'mf-m', 'b' ); } } /** * Process-local override for MFStripResponsiveImages, used by * the mobileview API request. */ private $stripResponsiveImagesOverride = null; /** * Should image thumbnails in pages remove the high-density additions * during this request? * * @return bool */ public function shouldStripResponsiveImages() { if ( $this->stripResponsiveImagesOverride === null ) { return $this->shouldDisplayMobileView() && $this->getMFConfig()->get( 'MFStripResponsiveImages' ); } else { return $this->stripResponsiveImagesOverride; } } /** * Config override for responsive image strip mode. * * @param bool $val New value */ public function setStripResponsiveImages( $val ) { $this->stripResponsiveImagesOverride = $val; } /** * Gets whether Wikibase descriptions should be shown in search results, including nearby search, * and watchlists; or as taglines on article pages. * Doesn't take into account whether the wikidata descriptions * feature has been enabled. * * @param string $feature which description to show? * @return bool * @throws DomainException If `feature` isn't one that shows Wikidata descriptions. See the * `wgMFDisplayWikibaseDescriptions` configuration variable for detail */ public function shouldShowWikibaseDescriptions( $feature ) { $config = $this->getMFConfig(); $displayWikibaseDescriptions = $config->get( 'MFDisplayWikibaseDescriptions' ); if ( !isset( $displayWikibaseDescriptions[ $feature ] ) ) { throw new DomainException( "\"{$feature}\" isn't a feature that shows Wikidata descriptions." ); } return $displayWikibaseDescriptions[ $feature ]; } }