jenkins-bot has submitted this change and it was merged. Change subject: Migrated to single page config ......................................................................
Migrated to single page config * All settings are loaded from the common JSON page * Config data is validated and cached * Removed lots of legacy code to load and parse config pages * Added sample json configuration page Change-Id: Ie61a3675ec6185cfecd5ff3468069977ebe95a33 --- A SampleConfigPage.json M ZeroRatedMobileAccess.php A includes/CarrierConfig.php M includes/PageRenderingHooks.php 4 files changed, 505 insertions(+), 468 deletions(-) Approvals: Dr0ptp4kt: Verified; Looks good to me, approved jenkins-bot: Verified diff --git a/SampleConfigPage.json b/SampleConfigPage.json new file mode 100644 index 0000000..c471129 --- /dev/null +++ b/SampleConfigPage.json @@ -0,0 +1,65 @@ +{ + "enabled":true, + "partnerId":1019, + "messageId":"dialog-sri-lanka", + "showLangs":[ + "en", + "kg" + ], + "langNameOverrides":{ + "kg":"Kikongo" + }, + "whitelistedLangs":[ + "en", + "ru", + "fr" + ], + "foreground":"#735005", + "background":"#F4A83D", + "fontSize":"0.9em", + "bannerWarning":true, + "showImages":false, + "showZeroPage":true, + "bannerUrl":"http://www.dialog.lk/personal/broadband/hspa/", + "ips":[ + "192.168.0.0/16" + ], + "name":{ + "en": "Dialog Sri Lanka", + "id": "Dialog Sri Lanka", + "ja": "ダイアログ スリランカ", + "mk": "Dialog Шри Ланка", + "si": "ඩයලොග්", + "ta": "Dialog Sri Lanka" + }, + "banner":{ + "ar": "{{SITENAME}} بلا مقابل من $1", + "ast": "{{SITENAME}} @ TARIFA CERO dende $1", + "cs": "{{SITENAME}} BEZPLATNĚ od $1", + "de": "{{SITENAME}} ohne Gebühren von $1", + "en": "{{SITENAME}} @ ZERO CHARGE from $1", + "es": "{{SITENAME}} gratis desde $1", + "fi": "{{SITENAME}} MAKSUTTA, tarjoaa $1", + "fr": "{{SITENAME}} @ ZÉRO CHARGE depuis $1", + "frp": "{{SITENAME}} @ ZÉRÔ CHARGE dês $1", + "gl": "{{SITENAME}} gratis desde $1", + "he": "{{SITENAME}} חינם־חינם מבית $1", + "hsb": "{{SITENAME}} bjez popłatkow wot $1", + "id": "{{SITENAME}} BEBAS PULSA dari $1", + "it": "{{SITENAME}} gratuito da $1", + "ja": "{{SITENAME}} @ ZERO CHARGE、$1 から", + "ko": "$1에서 {{SITENAME}} @ ZERO CHARGE", + "ksh": "{{SITENAME}} <span style='text-convsert:uppercase'>der ohne Koßte</span> vun $1", + "mk": "{{SITENAME}} без наплата од $1", + "ml": "{{SITENAME}} @ $1 നൽകുന്ന സീറോ ചാർജ്", + "ms": "{{SITENAME}} PERCUMA mulai $1", + "nl": "{{SITENAME}} ZONDER KOSTEN van $1", + "pms": "{{SITENAME}} @ TARIFA ZERO da $1", + "ro": "{{SITENAME}} @ FĂRĂ COSTURI de la $1", + "si": "$1 සමගින් නොමිලේ විකිපීඩියා බලන්න", + "ta": "$1 Wikipedia க்கு கட்டணங்கள் இல்லை", + "vi": "{{SITENAME}} MIỄN PHÍ từ $1", + "zh-hans": "由$1提供的免费{{SITENAME}}访问", + "zh-hant": "$1提供{{SITENAME}}免費訪問連線" + } +} diff --git a/ZeroRatedMobileAccess.php b/ZeroRatedMobileAccess.php index 3b69088..857a96b 100644 --- a/ZeroRatedMobileAccess.php +++ b/ZeroRatedMobileAccess.php @@ -33,6 +33,7 @@ $autoloadClasses = array ( 'PageRenderingHooks' => 'PageRenderingHooks', 'ZeroSpecialPage' => 'ZeroSpecialPage', + 'CarrierConfig' => 'CarrierConfig', ); $ns = 'Extensions\ZeroRatedMobileAccess\\'; @@ -65,6 +66,7 @@ $wgEnableZeroRatedMobileAccessTesting = false; $wgZeroRatedMobileAccessConfigIndexUri = false; +$wgZeroRatedMobileAccessDisableCache = false; $wgHooks['ResourceLoaderTestModules'][] = $ns . 'PageRenderingHooks::onResourceLoaderTestModules'; $wgHooks['BeforePageDisplayMobile'][] = $ns . 'PageRenderingHooks::onBeforePageDisplay'; diff --git a/includes/CarrierConfig.php b/includes/CarrierConfig.php new file mode 100644 index 0000000..2aa44fc --- /dev/null +++ b/includes/CarrierConfig.php @@ -0,0 +1,379 @@ +<?php + +namespace Extensions\ZeroRatedMobileAccess; +use FormatJson; +use Http; +use ObjectCache; +use stdClass; + +/** + * Represents a json blob on a remote wiki. + * Handles retrieval (via HTTP) and local caching. + * This code was adapted from extensions/EventLogging/includes/RemoteSchema.php + * @note When we switch to PHP 5.4, add 'implements JsonSerializable' + */ +class CarrierConfig { + + const LOCK_TIMEOUT = 20; + + var $carrierId; + var $cache; + var $http; + var $key; + var $content = false; + + + /** + * Constructor. + * @param string $carrierId Telecom ID (X-CS Header) + * @param ObjectCache $cache: (optional) cache client. + * @param Http $http: (optional) HTTP client. + */ + function __construct( $carrierId, $cache = NULL, $http = NULL ) { + $this->carrierId = $carrierId; + $this->cache = $cache ?: wfGetCache( CACHE_ANYTHING ); + $this->http = $http ?: new Http(); + $this->key = 'ZeroRatedMobileAccess:' . $carrierId; + } + + + /** + * Retrieves content. + * @return array|bool: Content array or false if irretrievable. + */ + public function get() { + global $wgZeroRatedMobileAccessDisableCache; + + if ( $this->content ) { + return $this->content; + } + + if ( !$wgZeroRatedMobileAccessDisableCache ) { + $this->content = $this->memcGet(); + if ( $this->content ) { + return $this->content; + } + } + + $this->content = $this->httpGetAndValidate(); + + if ( !$wgZeroRatedMobileAccessDisableCache && $this->content ) { + $this->memcSet(); + } + + return $this->content; + } + + + /** + * Retrieves content from memcached. + * @return array|bool Carrier config or false if not in cache. + */ + private function memcGet() { + return $this->cache->get( $this->key ); + } + + + /** + * Store content in memcached. + */ + private function memcSet() { + return $this->cache->set( $this->key, $this->content ); + } + + + /** + * Delete any cached information related to this config + */ + public function resetCache() { + $this->cache->delete( $this->key ); + $this->cache->delete( $this->key . ':lock' ); + } + + + /** + * Acquire a mutex lock for HTTP retrieval. + * @return bool: Whether lock was successfully acquired. + */ + private function lock() { + return $this->cache->add( $this->key . ':lock', 1, self::LOCK_TIMEOUT ); + } + + + /** + * Constructs URI for retrieving configuration from remote wiki. + * @return string: URI. + */ + private function getUri() { + global $wgZeroRatedMobileAccessConfigIndexUri; + + if ( $wgZeroRatedMobileAccessConfigIndexUri === false ) { + wfWarn( '$wgZeroRatedMobileAccessConfigIndexUri is not set' ); + return false; + } + + $q = array( + 'title' => 'Zero:' . $this->carrierId, + 'action' => 'raw', + ); + + return wfAppendQuery( $wgZeroRatedMobileAccessConfigIndexUri, $q ); + } + + + /** + * Returns an object containing serializable properties. + * @implements JsonSerializable + */ + public function jsonSerialize() { + return array( + 'Zero' => $this->get() ?: new stdClass(), + ); + } + + + /** + * Retrieves the config using HTTP. + * Uses a memcached lock to avoid cache stampedes. + * @return array|boolean: config or false if unable to fetch. + */ + private function httpGetAndValidate() { + if ( !$this->lock() ) { + return false; + } + $uri = $this->getUri(); + if ( $uri === false ) { + return false; + } + $raw = $this->http->get( $uri, self::LOCK_TIMEOUT * 0.8 ); + list ( $content, $issues ) = $this->validateConfig( $raw ); + if ( array_key_exists( 'error', $issues ) ) { + return false; + } + return $content; + } + + + static function isArray( $arr, $isAssoc ) { + if ( !is_array( $arr ) ) { + return false; + } + $strCount = count( array_filter( array_keys( $arr ), 'is_string') ); + return ( $isAssoc && $strCount === count( $arr ) ) || + ( !$isAssoc && $strCount === 0 ); + } + + + static function isArrayOfStrings( $arr ) { + return count( $arr ) === count( array_filter( $arr, 'is_string' ) ); + } + + + static function isArrayOfLangs( $arr ) { + $filter = function( $v ) { return \Language::isValidCode( $v ); }; + return count( $arr ) === count( array_filter( $arr, $filter ) ); + } + + public function validateConfig( $raw ) { + + $config = array(); + $issues = array(); + + $json = FormatJson::decode( $raw, true ) ?: false; + if ( !is_array( $json ) ) { + $issues['error'] = 'Unable to parse JSON configuration. Please check the syntax.'; + return array( $config, $issues ); + } + + $validateBool = function( $v ) { + return is_bool( $v ) ? null : 'Must be true or false (optional)'; + }; + $validateStr = function( $v ) { + return is_string( $v ) ? null : 'Must be a string (optional)'; + }; + + + //'enabled' => true, // Config is enabled + $this->check( $config, $json, 'enabled', true, $issues, $validateBool ); + + //'name' => null, // Map of localized partner names + $this->check( $config, $json, 'name', null, $issues, + function( $v ) { + return CarrierConfig::isArray( $v, true ) + && CarrierConfig::isArrayOfLangs( array_keys( $v ) ) + && CarrierConfig::isArrayOfStrings( $v ) + ? null : 'Must be a dictionary of valid language codes to strings'; + } ); + + // Integer Carrier ID + $this->check( $config, $json, 'partnerId', 0, $issues, + function( $v ) { + return is_int( $v ) ? null : 'Must be an integer (optional)'; + } ); + + // Integer Carrier ID + $this->check( $config, $json, 'messageId', null, $issues, + function( $v ) { + return is_string( $v ) + && strlen( $v ) > 0 + && strtolower( $v ) === $v + && strpos( $v, ' ' ) === false + ? null + : "Must be a nonempty lowercase string with no spaces"; + } ); + + //'showLangs' => null, // List of language codes to show on Zero page + $this->check( $config, $json, 'showLangs', null, $issues, + function( $v ) { + return CarrierConfig::isArray( $v, false ) + && CarrierConfig::isArrayOfLangs( $v ) + && count( $v ) > 0 + ? null : 'Must be a non-empty list of valid language codes'; + } ); + + // Orange Congo wanted to be able to override the 'kg' language name to 'Kikongo' + $this->check( $config, $json, 'langNameOverrides', array(), $issues, + function( $v ) { + return CarrierConfig::isArray( $v, true ) + && CarrierConfig::isArrayOfLangs( array_keys( $v ) ) + && CarrierConfig::isArrayOfStrings( $v ) + ? null : 'Optional, must be a dictionary of valid language codes to strings'; + } ); + + //'whitelistedLangs' => null, // List of language codes to show banner on, or empty list to allow on all languages + $this->check( $config, $json, 'whitelistedLangs', null, $issues, + function( $v ) { + return CarrierConfig::isArray( $v, false ) + && CarrierConfig::isArrayOfLangs( $v ) + ? null : 'Must be a list of valid language codes (could be empty)'; + } ); + + //'banner' => null, // Map of localized banner texts with {{PARTNER}} placeholder + $this->check( $config, $json, 'banner', null, $issues, + function( $v ) { + return CarrierConfig::isArray( $v, true ) + && CarrierConfig::isArrayOfLangs( array_keys( $v ) ) + && CarrierConfig::isArrayOfStrings( $v ) + ? null : 'Must be a dictionary of valid language codes to strings'; + } ); + + //'bannerWarning' => true, // Show "non-zero navigation" warning when clicking the banner + $this->check( $config, $json, 'bannerWarning', true, $issues, $validateBool ); + + // Background banner color + $this->check( $config, $json, 'background', '#E31230', $issues, $validateStr ); + + // Foreground banner color + $this->check( $config, $json, 'foreground', '#551011', $issues, $validateStr ); + + // Banner font size override + $this->check( $config, $json, 'fontSize', '', $issues, $validateStr ); + + // + $this->check( $config, $json, 'showImages', true, $issues, $validateBool ); + + // Partner URL, do not link by default + $this->check( $config, $json, 'bannerUrl', '', $issues, + function( $v ) { + return + $v === '' || false !== filter_var( $v, FILTER_VALIDATE_URL ) + ? null : 'Optional, must be a valid URL'; + } ); + + //'sites' => 'both', // Which sites are whitelisted? Could be zero, m, both, redirect + $this->check( $config, $json, 'sites', 'both', $issues, + function( $v ) { + $validValues = array( 'zero', 'm', 'both', 'redirect' ); + return is_string( $v ) + && in_array( strtolower( $v ), $validValues ) + ? null + : "Optional, must be one of these values: '" . implode( "', '", $validValues ) . "'"; + } ); + $config['sites'] = strtolower( $config['sites'] ); + + //'ips' => null, + $this->check( $config, $json, 'ips', '', $issues, + function( $v ) { + $msg = 'Must be an array of valid CIDR blocks'; + if ( !CarrierConfig::isArray( $v, false ) + || !CarrierConfig::isArrayOfStrings( $v ) ) { + return $msg; + } + foreach ( $v as $cidr ) { + $parts = explode( '/', $cidr, 3 ); + if ( count( $parts ) > 2 ) { + return $msg; + } + // @TODO: Finish CIDR validation + } + return null; + } ); + + // default = ( count( 'showLangs' ) > 1 ) + $this->check( $config, $json, 'showZeroPage', true, $issues, $validateBool ); + if ( isset( $issues['defaults']['showZeroPage'] ) ) { + $config['showZeroPage'] = count( $config['showLangs'] ) > 1; + } + + if ( array_key_exists( 'errors', $issues ) ) { + $errCount = count( $issues['errors'] ); + $issues['error'] = + $errCount . + ( $errCount > 1 ? ' errors were' : ' error was' ) . + ' found in configuration data'; + } + + return array( $config, $issues ); + } + + + private function check( &$config, $json, $field, $default, &$issues, $validator ) { + $res = $this->getValue( $json, $field, $issues ); + $useDflt = $res === null; + if ( $useDflt ) { + $res = $default; + } + $err = call_user_func( $validator, $res ); + if ( $err !== null ) { + // Invalid value + $issues['valid'] = false; + $issues['errors'][$field] = $err; + } else { + $config[$field] = $res; + if ( $useDflt ) { + // default value was used + $issues['defaults'][$field] = true; + } + } + } + + + private function getValue( $json, $field, &$issues ) { + $value = null; + $dupl = array(); + + // check for exact field name match + if ( array_key_exists( $field, $json ) ) { + $dupl[] = $field; + $value = $json[$field]; + unset( $json[$field] ); + } + + // check for other casing of the field name + foreach ( $json as $k => $v ) { + if ( 0 === strcasecmp( $k, $field ) ) { + $dupl[] = $k; + $value = $json[$k]; + unset( $json[$k] ); + $issues['normalized'][$k] = $field; + } + } + + if ( count( $dupl ) > 1 ) { + $issues['duplicates'] = array_merge( $issues['duplicates'], $dupl ); + return null; + } else { + return $value; + } + } +} diff --git a/includes/PageRenderingHooks.php b/includes/PageRenderingHooks.php index 3bd5493..e64e6c7 100644 --- a/includes/PageRenderingHooks.php +++ b/includes/PageRenderingHooks.php @@ -4,9 +4,6 @@ use DOMDocument; use DOMElement; use DOMXPath; -use DateTimeZone; -use DateTime; -use Http; use Html; use FormatJson; use Language; @@ -33,6 +30,7 @@ private static $isFilePage = false; public static $isMainPage = false; private static $acceptBilling; + private static $carrierId; private static $carrier; private static $renderZeroRatedRedirect; private static $forceClickToViewImages; @@ -56,8 +54,7 @@ * @return bool */ public static function getMobileUrl( &$subdomainTokenReplacement ) { - global $wgRequest, $wgZeroDisableImages; - $carrier = $wgRequest->getHeader( 'X-CARRIER' ); + global $wgZeroDisableImages; $xSubDomain = isset( $_SERVER['HTTP_X_SUBDOMAIN'] ) ? $_SERVER['HTTP_X_SUBDOMAIN'] : ''; if ( $xSubDomain === 'ZERO' ) { if ( $wgZeroDisableImages === 1 ) { @@ -106,15 +103,17 @@ self::$isMainPage = true; } - $carrier = $wgRequest->getHeader( 'X-CARRIER' ); - - if ( $carrier !== '(null)' && $carrier ) { + $carrierId = $wgRequest->getHeader( 'X-CS' ); + if ( $carrierId !== '(null)' && $carrierId ) { self::$renderZeroRatedBanner = true; + self::$carrierId = $carrierId; + } else { + self::$carrierId = ''; } $output = ''; - if ( $xSubDomain === 'ZERO' && empty( $carrier ) ) { + if ( $xSubDomain === 'ZERO' && self::$carrierId === '' ) { $ip = $wgRequest->getVal( 'ip', $wgRequest->getIP() ); // @todo FIXME: Unescaped UI text output as HTML in next 3 lines. $bannerText = wfMessage( 'zero-rated-mobile-access-sorry' )->text(); @@ -169,14 +168,14 @@ } if ( self::$renderZeroRatedBanner === true ) { - self::$carrier = $this->lookupCarrier( $carrier ); - if ( self::$isFilePage && isset( self::$carrier['images'] ) && self::$carrier['images'] ) { + self::$carrier = $this->lookupCarrier( self::$carrierId ); + if ( self::$isFilePage && self::$carrier['showImages'] ) { self::$renderWarning = false; } $options = array(); $options['toggle_view_desktop'] = '&renderZeroRatedBanner=true&renderwarning=yes&returnto='; - $options['supported_languages'] = isset( self::$carrier['languages'] ) ? self::$carrier['languages'] : array(); + $options['supported_languages'] = self::$carrier['showLangs']; } if ( self::$renderWarning && self::$acceptBilling !== 'yes' && self::$renderZeroRatedBanner === true ) { @@ -243,8 +242,10 @@ $parsedHtml = $this->parseLinksForZeroQueryString( $html ); $out->clearHTML(); $out->addHTML( $parsedHtml ); - $carrierLink = ( isset( self::$carrier['link'] ) ) ? self::$carrier['link'] : ''; - if ( isset( self::$carrier['bannerWarning'] ) && !self::$carrier['bannerWarning'] && !empty( $carrierLink ) ) { + $carrierLink = self::getCarrierLink(); + + + if ( !self::$carrier['bannerWarning'] && !empty( $carrierLink ) ) { preg_match( '/<a href="(.+)">/', $carrierLink, $match ); if ( isset( $match[1] ) ) { $originalHref = $match[1]; @@ -258,17 +259,13 @@ } } } - $customStyle = ''; - $customStyleNotifyClose = ''; - if ( isset( self::$carrier['bannerPalette']['foreground'] ) && isset( self::$carrier['bannerPalette']['background'] ) ) { - $customStyle = 'background:' . self::$carrier['bannerPalette']['background'] . - ';color:' . self::$carrier['bannerPalette']['foreground'] . ';'; - $customStyleFontSize = ( $carrier === 'Saudi Telecom' ) ? ' font-size:0.9em;' : ''; - $carrierLink = str_replace( '<a href="', '<a style="color:' . self::$carrier['bannerPalette']['foreground'] . - ';" href="', $carrierLink ); - $customStyleNotifyClose = 'background-color:' . self::$carrier['bannerPalette']['background'] . - ';border: 2px solid ' . self::$carrier['bannerPalette']['foreground'] . ';'; - } + $customStyle = 'background:' . self::$carrier['background'] . + ';color:' . self::$carrier['foreground'] . ';'; + $customStyleFontSize = self::$carrier['fontSize'] !== '' ? ' font-size:' . self::$carrier['fontSize'] . ';' : ''; + $carrierLink = str_replace( '<a href="', '<a style="color:' . self::$carrier['foreground'] . + ';" href="', $carrierLink ); + $customStyleNotifyClose = 'background-color:' . self::$carrier['background'] . + ';border: 2px solid ' . self::$carrier['foreground'] . ';'; $bannerText = Html::rawElement( 'span', array( 'class' => 'mw-mf-message', @@ -291,18 +288,20 @@ $out->clearHTML(); $out->setPageTitle( null ); $languageNames = Language::fetchLanguageNames(); - $languageNames['kg'] = 'Kikongo'; // special-case as requested by Orange Congo - $languageOptionsForCarriers = self::createLanguageOptionsFromWikiTextForCarrier(); - $carrierName = strtoupper( self::$carrier['name'] ); - $languageOptionsForCarrier = ( isset( $languageOptionsForCarriers[$carrierName] ) ) ? - $languageOptionsForCarriers[$carrierName] : null; + if ( self::$carrier ) { + $overrides = self::$carrier['langNameOverrides']; + // Allow language name overrides, as requested by Orange Congo ('kg' => 'Kikongo) + // Do it one at a time to keep the original order of the array + foreach ( $overrides as $l => $n ) { + $languageNames[$l] = $n; + } - if ( is_array( $languageOptionsForCarrier ) ) { - $sizeOfLanguagesForCountry = sizeof( $languageOptionsForCarrier ); - for ( $i = 0; $i < $sizeOfLanguagesForCountry; $i++ ) { - $languageName = $languageNames[$languageOptionsForCarrier[$i]['language']]; - $languageCode = $languageOptionsForCarrier[$i]['language']; + foreach ( self::$carrier['showLangs'] as $languageCode ) { + if ( !array_key_exists( $languageCode, $languageNames ) ) { + continue; + } + $languageName = $languageNames[$languageCode]; $languageUrl = sprintf( self::$formatMobileUrl, $languageCode ); $languageLink = Html::element( 'a', array( 'id' => 'lang_' . $languageCode, @@ -310,7 +309,7 @@ wfMessage( 'zero-rated-mobile-access-home-page-selection', ucfirst( $languageName ) )->inLanguage( $languageCode ) ); - if ( $wgZeroDisableImages === 1 ) { + if ( $wgZeroDisableImages === 1 ) { $languageLink = str_replace( '.m.wikipedia.org', '.zero.wikipedia.org', $languageLink ); } else { $languageLink = str_replace( '.zero.wikipedia.org', '.m.wikipedia.org', $languageLink ); @@ -354,6 +353,25 @@ return true; } + private static function getCarrierLink() { + global $wgRequest; + $url = self::$carrier['bannerUrl']; + $messageId = self::$carrier['messageId']; + + $name = ucwords( + wfMessage( 'zero-rated-mobile-access-banner-carrier-name-' . $messageId )->escaped() + ); + $linkText = wfMessage( 'zero-rated-mobile-access-banner-text-' . $messageId ) + ->rawParams( $name )->escaped(); + $billingURL = $wgRequest->appendQuery( + 'renderZeroRatedBanner=true&renderwarning=yes&returnto=' . urlencode( $url ) + ); + $carrierLink = Html::rawElement( 'a', + array( 'href' => $billingURL ), + $linkText ); + return $carrierLink; + } + /** * ResourceLoaderTestModules hook handler * @see https://www.mediawiki.org/wiki/Manual:Hooks/ResourceLoaderTestModules @@ -377,52 +395,21 @@ return true; } - /** - * @return Array - */ - private function mergeCarrierData() { - $allCarrierLinkData = $this->createCarrierOptionsFromWikiText(); - $allCarrierSupportedLanguageData = $this->getLanguageOptionForWikiFromWikiText(); - - if ( is_array( $allCarrierLinkData ) && is_array( $allCarrierSupportedLanguageData ) ) { - foreach ( $allCarrierLinkData as $key => $value ) { - if ( is_array( $value ) && array_key_exists( 'partnerId', $value ) ) { - foreach ( $value as $subKey => $subValue ) { - if ( $subKey !== 'partnerId' ) { - continue; - } - if ( isset( $allCarrierSupportedLanguageData[$subValue][0] ) && - is_array( $allCarrierSupportedLanguageData[$subValue][0] ) ) { - $allCarrierLinkData[$key]['languages'] = $allCarrierSupportedLanguageData[$subValue][0]; - } else { - $allCarrierLinkData[$key]['languages'] = 'all'; - } - } - } - } - } - return $allCarrierLinkData; - } /** * Returns information about carrier * - * @param String $carrier: Name of carrier e.g., "Verizon Wireless" + * @param String $carrier: carrier ID e.g., "250-99" * @return Array */ private function lookupCarrier( $carrier ) { wfProfileIn( __METHOD__ ); - $carrierLinkData = array(); - $carrier = strtoupper( $carrier ); - $allCarrierLinkData = $this->mergeCarrierData(); - - if ( is_array( $allCarrierLinkData ) && isset( $allCarrierLinkData[$carrier] ) ) { - $carrierLinkData = $allCarrierLinkData[$carrier]; - } + $conf = new CarrierConfig( $carrier ); + $data = $conf->get(); wfProfileOut( __METHOD__ ); - return $carrierLinkData; + return $data; } /** @@ -478,7 +465,7 @@ $zeroPartnerUrl = wfAppendQuery( $zeroRatedLinkHref, array( 'zeropartner' => $partnerId, 'renderZeroRatedBanner' => 'true' ) ); if ( $zeroPartnerUrl ) { - if ( isset( self::$carrier['images'] ) && !self::$carrier['images'] ) { + if ( !self::$carrier['showImages'] ) { $zeroRatedLink->setAttribute( 'href', $zeroPartnerUrl ); } } @@ -503,402 +490,6 @@ $output = $doc->saveXML( null, LIBXML_NOEMPTYTAG ); wfProfileOut( __METHOD__ ); return $output; - } - - /** - * @param $formatter array - * @param $wikiText string - * @param $nChild bool - * @return array - */ - public function parseWikiTextToArray( Array $formatter, $wikiText, $nChild = false ) { - $options = array(); - if ( !is_array( $formatter ) ) { - return $options; - } - wfProfileIn( __METHOD__ ); - $data = explode( PHP_EOL, $wikiText ); - if ( $nChild ) { - $arrayKeys = array_keys( $formatter ); - $keyCount = count( $arrayKeys ); - $index = 0; - foreach ( $data as $key => $rawData ) { - $index = ( intval( $key ) % $keyCount === 0 ) ? 0 : $index + 1; - if ( strpos( $rawData, '*' ) === 0 && strpos( $rawData, '**' ) !== 0 && $key >= 0 ) { - $data = trim( str_replace( '*', '', $rawData ) ); - $prefixName = strtoupper( $data ); - $options[$prefixName] = ''; - } elseif ( strpos( $rawData, '**' ) === 0 && $key > 0 ) { - $data = trim( str_replace( '*', '', $rawData ) ); - if ( !is_array( $formatter ) ) { - $options[$prefixName][] = $data; - continue; - } - if ( !isset( $formatter[$index]['callback'] ) ) { - continue; - } - $callback = $formatter[$index]['callback']; - if ( method_exists( $this, $callback ) ) { - $data = $this->$callback( $data ); - if ( $data ) { - $options[$prefixName][] = $data; - } - } - } - } - wfProfileOut( __METHOD__ ); - return $options; - } - - $arrayKeys = array_keys( $formatter ); - $keyCount = count( $arrayKeys ); - $index = 0; - foreach ( $data as $key => $rawData ) { - $index = ( intval( $key ) % $keyCount === 0 ) ? 0 : $index + 1; - if ( !in_array( $index, $arrayKeys ) ) { - continue; - } - $data = trim( str_replace( '*', '', $rawData ) ); - if ( is_array( $formatter[$index] ) ) { - $name = $formatter[$index]['name']; - if ( !isset( $formatter[$index]['callback'] ) ) { - continue; - } - $callback = $formatter[$index]['callback']; - if ( !method_exists( $this, $callback ) ) { - continue; - } - if ( isset( $formatter[$index]['parameters'] ) ) { - if ( is_array( $formatter[$index]['parameters'] ) ) { - $parameters = array(); - foreach ( $formatter[$index]['parameters'] as $parameter ) { - if ( isset( $options[$prefixName][$parameter] ) ) { - $parameters[$parameter] = $options[$prefixName][$parameter]; - } - } - $data = $this->$callback( $data, $parameters ); - } else { - $parameter = $formatter[$index]['parameters']; - if ( isset( $options[$prefixName][$parameter] ) ) { - $parameterValue = $options[$prefixName][$parameter]; - $data = $this->$callback( $data, $parameterValue ); - } - } - } else { - $data = $this->$callback( $data ); - } - } else { - $name = $formatter[$index]; - } - if ( $index === 0 ) { - $prefixName = strtoupper( $data ); - } - $options[$prefixName][$name] = $data; - } - wfProfileOut( __METHOD__ ); - return $options; - } - - /** - * @param $data array - * @return array - */ - public function commaSeparatedCallback( $data ) { - return explode( ',', str_replace( ' ', '', $data ) ); - } - - /** - * @param $url string - * @param $name string - * @return string - */ - public function createUrlCallback( $url, $name ) { - global $wgRequest; - $carrier = strtolower( $name ); - $posSpace = strpos( $carrier, ' ' ); - if ( $posSpace === false ) { - } else { - $carrier = str_replace( ' ', '-', $carrier ); - } - - $name = ucwords( - wfMessage( 'zero-rated-mobile-access-banner-carrier-name-' . $carrier )->escaped() - ); - $linkText = wfMessage( 'zero-rated-mobile-access-banner-text-' . $carrier ) - ->rawParams( $name )->escaped(); - $billingURL = $wgRequest->appendQuery( - 'renderZeroRatedBanner=true&renderwarning=yes&returnto=' . urlencode( $url ) - ); - $carrierLink = Html::rawElement( 'a', - array( 'href' => $billingURL ), - $linkText ); - return $carrierLink; - } - - /** - * @param $int string|int - * @return int - */ - public function intValCallback( $int ) { - return intval( $int ); - } - - /** - * @param $images string - * @return boolean - */ - public function booleanImagesOnOff( $images ) { - if ( $images === 'IMAGES_ON' ) { - return true; - } - return false; - } - - /** - * @param $colors string - * @return array - */ - public function arrayBannerPalette( $colors ) { - $tempColorArray = explode( ':' , $colors ); - if ( isset( $tempColorArray[0] ) && isset( $tempColorArray[1] ) ) { - $colorArray = array( - 'foreground' => $tempColorArray[0], - 'background' => $tempColorArray[1], - ); - } else { - $colorArray = array(); - } - - return $colorArray; - } - - /** - * @param $banner - * @return boolean - */ - public function booleanBannerWarning( $banner ) { - if ( $banner === 'BANNER_WARNING_ON' ) { - return true; - } - return false; - } - - /** - * Returns the carrier options array parsed from a valid wiki page - * - * @return Array - */ - private function createCarrierOptionsFromWikiText() { - wfProfileIn( __METHOD__ ); - $carrierOptionsWikiPage = wfMessage( 'zero-rated-mobile-access-carrier-options-wiki-page' ) - ->inContentLanguage()->text(); - list( $revId, $rev ) = self::getOptionsFromForeignWiki( $carrierOptionsWikiPage ); - $carrierOptions = null; - - if ( !$carrierOptions ) { - if ( $rev ) { - $formatter = array( - 0 => 'name', - 1 => array( 'name' => 'link', - 'callback' => 'createUrlCallback', - 'parameters' => 'name', - ), - 2 => array( 'name' => 'partnerId', - 'callback' => 'intValCallback', - ), - 3 => array( 'name' => 'images', - 'callback' => 'booleanImagesOnOff', - ), - 4 => array( 'name' => 'bannerPalette', - 'callback' => 'arrayBannerPalette', - ), - 5 => array( 'name' => 'bannerWarning', - 'callback' => 'booleanBannerWarning', - ), - - ); - $carrierOptions = $this->parseWikiTextToArray( $formatter, $rev ); - } - } - wfProfileOut( __METHOD__ ); - return $carrierOptions; - } - - /** - * Returns the foreign wiki options array from a valid wiki page - * - * @param $pageName string - * @return Array - */ - private static function getOptionsFromForeignWiki( $pageName ) { - global $wgMemc; - wfProfileIn( __METHOD__ ); - - $key = null; - $rev = null; - - if ( $pageName ) { - $day = gmdate( 'Ymd' ); - $memcKey = wfMemcKey( 'zero-rated-mobile-access-foreign-options-', md5( $pageName ), $day ); - $foreignOptions = $wgMemc->get( $memcKey ); - - if ( !$foreignOptions ) { - $url = 'http://en.wikipedia.org/w/api.php?action=query&prop=revisions&&rvlimit=1&rvprop=content&format=json&titles=MediaWiki:' . $pageName; - $ret = Http::get( $url ); - - if ( !$ret ) { - wfProfileOut( __METHOD__ ); - return array( $key, $rev ); - } - - $jsonData = FormatJson::decode( $ret, true ); - - if ( isset( $jsonData['query']['pages'] ) ) { - $key = key( $jsonData['query']['pages'] ); - if ( !is_int( $key ) ) { - $key = null; - } - - foreach ( $jsonData['query']['pages'] as $pages ) { - if ( isset( $pages['revisions'][0]['*'] ) ) { - $rev = $pages['revisions'][0]['*']; - } - } - } - - if ( $key && $rev ) { - $wgMemc->set( $memcKey, array( $key, $rev ), self::getMaxAge() ); - } - } else { - list ( $key, $rev ) = $foreignOptions; - } - } - - wfProfileOut( __METHOD__ ); - return array( $key, $rev ); - } - - /** - * @param $data array - * @return array|string - */ - public function languagePercentageCallback( $data ) { - $languageArray = array(); - $lineParts = explode( '#', $data ); - $language = ( isset( $lineParts[0] ) ) ? trim( $lineParts[0] ) : trim( $data ); - if ( $language !== 'portal' && $language !== 'other' ) { - $languageArray = ( isset( $lineParts[1] ) ) ? - array( 'language' => $language, - 'percentage' => intval( str_replace( '%', '', trim( $lineParts[1] ) ) ) ) : - $language; - } - return $languageArray; - } - -/** - * Returns the language options array parsed from a valid wiki page - * - * @return Array - */ - private function createLanguageOptionsFromWikiTextForCarrier() { - global $wgMemc; - wfProfileIn( __METHOD__ ); - $languageOptionsWikiPage = wfMessage( 'zero-rated-mobile-access-language-options-wiki-page-for-carrier' )->inContentLanguage()->text(); - list( $revId, $rev ) = self::getOptionsFromForeignWiki( $languageOptionsWikiPage ); - if ( $rev ) { - $key = wfMemcKey( 'zero-rated-mobile-access-language-options-for-carrier', $revId ); - $languageOptions = $wgMemc->get( $key ); - } else { - $languageOptions = null; - } - - if ( !$languageOptions ) { - $languageOptions = array(); - if ( $rev ) { - $formatter = array( - 0 => array( 'name' => 'partnerId', - 'callback' => 'languagePercentageCallback' - ), - ); - $languageOptions = $this->parseWikiTextToArray( $formatter, $rev, true ); - } - $wgMemc->set( $key, $languageOptions, self::getMaxAge() ); - } - wfProfileOut( __METHOD__ ); - return $languageOptions; - } - - /** - * @return array|mixed|null - */ - private function getLanguageOptionForWikiFromWikiText() { - global $wgMemc; - wfProfileIn( __METHOD__ ); - $languageOptionsWikiPage = wfMessage( 'zero-rated-mobile-access-carrier-options-supported-wikis-wiki-page' ) - ->inContentLanguage()->text(); - - list( $revId, $rev ) = self::getOptionsFromForeignWiki( $languageOptionsWikiPage ); - - if ( $rev ) { - $key = wfMemcKey( 'zero-rated-mobile-access-carrier-options-supported-wikis-wiki-page', $revId ); - $languageOptions = $wgMemc->get( $key ); - } else { - $languageOptions = null; - } - - if ( !$languageOptions ) { - $languageOptions = array(); - if ( $rev ) { - $formatter = array( - 0 => array( 'name' => 'partnerId', - 'callback' => 'intValCallback' - ), - 1 => array( 'name' => 'languages', - 'callback' => 'commaSeparatedCallback' - ), - ); - $languageOptions = $this->parseWikiTextToArray( $formatter, $rev, true ); - } - $wgMemc->set( $key, $languageOptions, self::getMaxAge() ); - } - wfProfileOut( __METHOD__ ); - return $languageOptions; - } - - /** - * Returns the Unix timestamp of current day's first second - * - * @return int: Timestamp - */ - private static function todaysStart() { - wfProfileIn( __METHOD__ ); - static $time = false; - if ( !$time ) { - global $wgLocaltimezone; - if ( isset( $wgLocaltimezone ) ) { - $tz = new DateTimeZone( $wgLocaltimezone ); - } else { - $tz = new DateTimeZone( date_default_timezone_get() ); - } - $dt = new DateTime( 'now', $tz ); - $dt->setTime( 0, 0, 0 ); - $time = $dt->getTimestamp(); - } - wfProfileOut( __METHOD__ ); - return $time; - } - - /** - * Returns the number of seconds an item should stay in cache - * - * @return int: Time in seconds - */ - private static function getMaxAge() { - wfProfileIn( __METHOD__ ); - // add 10 seconds to cater for the time deviation between servers - $expiry = self::todaysStart() + 24 * 3600 - wfTimestamp() + 10; - wfProfileOut( __METHOD__ ); - return min( $expiry, 900 ); } public function getVersion() { -- To view, visit https://gerrit.wikimedia.org/r/58252 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: Ie61a3675ec6185cfecd5ff3468069977ebe95a33 Gerrit-PatchSet: 3 Gerrit-Project: mediawiki/extensions/ZeroRatedMobileAccess Gerrit-Branch: master Gerrit-Owner: Yurik <yu...@wikimedia.org> Gerrit-Reviewer: Brion VIBBER <br...@wikimedia.org> Gerrit-Reviewer: Dfoy <d...@wikimedia.org> Gerrit-Reviewer: Dr0ptp4kt <ab...@wikimedia.org> Gerrit-Reviewer: Jdlrobson <jrob...@wikimedia.org> Gerrit-Reviewer: jenkins-bot _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits