%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 ];
}
}