Yurik has uploaded a new change for review. https://gerrit.wikimedia.org/r/189592
Change subject: Warn on HTTPS - take 2 ...................................................................... Warn on HTTPS - take 2 A somewhat different, hopefully a bit cleaner implementation of the warnHttps gray banner Change-Id: I5eb5a56b395bd8493e4454cbfb89a8596800f224 (cherry picked from commit ccf0164905b091d2c7133f71db78841c70acdf4a) --- M ZeroBanner.php M i18n/en.json M i18n/qqq.json M includes/ApiZeroBanner.php M includes/MccMncLogging.php M includes/PageRendering.php M includes/ZeroConfig.php M includes/ZeroSpecialPage.php M modules/ZeroInfo.js 9 files changed, 149 insertions(+), 120 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/ZeroBanner refs/changes/92/189592/1 diff --git a/ZeroBanner.php b/ZeroBanner.php index a7d664f..18b1768 100644 --- a/ZeroBanner.php +++ b/ZeroBanner.php @@ -98,6 +98,7 @@ 'zero-interstitial-title', 'zero-info-title', 'zero-info-intro', + 'zero-info-https-warn', 'zero-info-buttonText', 'zero-info-url', 'zero-info-url-free', diff --git a/i18n/en.json b/i18n/en.json index 1c799f0..e8968f2 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -12,6 +12,7 @@ "zero-interstitial-title": "Warning", "zero-info-title": "Wikipedia Zero Details", "zero-info-intro": "$1 is providing access to {{SITENAME}} free of charge.", + "zero-info-https-warn": "$1 is providing access to {{SITENAME}} free of charge for HTTP traffic only. You are viewing {{SITENAME}} via secure HTTPS connection, standard data charges may apply.", "zero-info-buttonText": "More Information", "zero-info-url": "More information is available from your operator", "zero-info-url-free": "(free)", @@ -34,6 +35,7 @@ "zero-dont-ask": "Do not warn me about potential charges in the future", "zero-go-back": "Go back", "zero-accept": "Accept", + "zero-no-https": "Data not free on encrypted HTTPS", "zero-click-to-view-image": "Click to view image of \"$1\"...", "zero-dismiss-notification": "dismiss this notification", "zero-config-name": "Parameter \"$1\" must be an object that maps valid language codes to strings. e.g. { \"en\":\"English\", ... }", diff --git a/i18n/qqq.json b/i18n/qqq.json index 119fa84..f0a2595 100644 --- a/i18n/qqq.json +++ b/i18n/qqq.json @@ -28,6 +28,7 @@ "zero-interstitial-title": "Title of the warning dialog when user is about to leave zero-rated access\n{{Identical|Warning}}", "zero-info-title": "Title of the dialog describing details about Wikipedia Zero", "zero-info-intro": "Short explanation that the user is browsing Wikipedia without being charged for bandwidth.\n\nParameters:\n* $1 - localized partner name", + "zero-info-https-warn": "Short explanation that if the user was browsing Wikipedia via non-secure HTTP, they would not be charged for bandwidth, whereas the user is using secure HTTPS, which this partner does not zero-rate.\n\nParameters:\n* $1 - localized partner name", "zero-info-buttonText": "Text to describe button with link to more information\n{{Identical|More information}}", "zero-info-url": "Show a link to the operator's own description of the Zero service.\nParameters:\n* $1 - operator name as an external link", "zero-info-url-free": "An indicator right after {{msg-mw|zero-info-url}} that operator's info page visit is also zero-rated.\n{{Identical|Free}}", @@ -50,6 +51,7 @@ "zero-dont-ask": "Shown next to a checkbox that allows user to always skip the following warning:\n* {{msg-mw|Zero-charge-auth}}", "zero-go-back": "Text for going back (in browser history).\n{{Identical|Go back}}", "zero-accept": "Text for accepting (e.g. charges).\n{{Identical|Accept}}", + "zero-no-https": "Text concisely explaining encrypted HTTPS is not zero-rated, but HTTP is zero-rated", "zero-click-to-view-image": "Text for viewing an image link. Parameters:\n* $1 - the alt text of the image that can be viewed\n----\n“Zero rated mobile access only displays text. For images, the value of the \"alt\" [text] attribute is displayed. In case a user may want to the see images, they can click a link, but data charges may then apply. That's the complete context.” [[Thread:Support/About MediaWiki:Zero-rated-mobile-access-click-to-view-image/en/reply|Siebrand – 14:04, 27 February 2012]]", "zero-dismiss-notification": "Text for dismissing banner on top of screen", "zero-config-name": "Aמn error message shown when the \"name\" parameter is invalid. Parameters:\n* $1 - field name\n{{Related|Zeroconfig}}", diff --git a/includes/ApiZeroBanner.php b/includes/ApiZeroBanner.php index 5e8e947..f4f85ab 100644 --- a/includes/ApiZeroBanner.php +++ b/includes/ApiZeroBanner.php @@ -58,7 +58,7 @@ $config = $state->getZeroConfig(); if ( $config ) { $result->addValue( $moduleName, 'enabled', true ); - $result->addValue( $moduleName, 'id', $state->getConfigId() ); + $result->addValue( $moduleName, 'id', $state->getXcs()->xcs ); // todo: use ->id and ->ipset instead $result->addValue( $moduleName, 'showImages', $config->showImages() ); $result->addValue( $moduleName, 'whitelistedLangs', $config->whitelistedLangs() ); $result->addValue( $moduleName, 'enableHttps', $config->enableHttps() ); diff --git a/includes/MccMncLogging.php b/includes/MccMncLogging.php index 42facc3..295fd8b 100644 --- a/includes/MccMncLogging.php +++ b/includes/MccMncLogging.php @@ -19,7 +19,6 @@ */ public static function onAPIAfterExecute( ApiBase &$module ) { if ( is_callable( 'EventLogging::logEvent' ) ) { - wfProfileIn( __METHOD__ ); $req = $module->getRequest(); $mccMnc = $req->getHeader( 'X-MCCMNC' ); $matches = null; @@ -39,7 +38,6 @@ } } - wfProfileOut( __METHOD__ ); } return true; diff --git a/includes/PageRendering.php b/includes/PageRendering.php index d20b6ad..416f968 100644 --- a/includes/PageRendering.php +++ b/includes/PageRendering.php @@ -13,6 +13,7 @@ use OutputPage; use RawMessage; use SpecialPage; +use stdClass; use Title; use TitleValue; use WebRequest; @@ -70,13 +71,52 @@ } /** + * @param string $banner Raw banner HTML + * @param string $foreground Banner foreground color (CSS) + * @param string $background Banner background color (CSS) + * @param string $style Additional banner text style + * @param bool $undismissible Show dismiss button + * @return string Banner HTML + */ + public static function makeBannerRaw( $banner, $foreground, $background, $style, + $undismissible = false ) { + $attr = array( + 'class' => 'mw-mf-message', + 'id' => 'zero-rated-banner-text', + ); + if ( $style !== '' ) { + $attr['style'] = $style; + } + $banner = Html::rawElement( 'span', $attr, $banner ); + if ( !$undismissible ) { + $dismiss = Html::rawElement( 'button', array( + 'class' => 'notify-close', + 'style' => "background:$background;", + 'title' => wfMessage( 'zero-dismiss-notification' )->text() + ), Html::rawElement( 'span', array( + 'class' => 'notify-close-x', + 'style' => "background:$background;border-color:$foreground;color:$foreground;" + ), 'x' ) ); + } else { + $dismiss = ''; + } + $banner = Html::rawElement( 'div', array( + 'id' => 'zero-rated-banner', + 'class' => 'mw-mf-banner', + 'style' => "background:$background;color:$foreground;" + ), $dismiss . $banner ); + return $banner; + } + + /** * @param \ContextSource|\SpecialPage $ctxSrc * @param string $id * @param null|ZeroConfig $config * @param bool $enabled + * @param bool $warnHttps * @return string */ - public static function getJsConfigBlock( $ctxSrc, $id, $config, $enabled ) { + public static function getJsConfigBlock( $ctxSrc, $id, $config, $enabled, $warnHttps = false ) { $cfg = array( 'enabled' => $enabled, 'id' => $id, @@ -95,6 +135,7 @@ if ( $config->testInfoScreen() ) { $cfg['testInfoScreen'] = true; } + $cfg['warnHttps'] = $warnHttps; } return 'window.zeroGlobalConfig=' . Xml::encodeJsVar( $cfg ) . ';'; } @@ -124,7 +165,7 @@ if ( !$this->isZeroSite() ) { return true; } - $unified = $this->isUnifiedZero(); + $unified = $this->getXcs()->isUnified; $config = $unified ? null : $this->getZeroConfig(); if ( !$unified && !$config ) { return true; @@ -133,7 +174,6 @@ return true; } - wfProfileIn( __METHOD__ ); $isFilePage = $this->getTitle()->inNamespace( NS_FILE ); $isZerodotNonFilePage = !$isFilePage && $this->isZeroSubdomain; $isLowQualityImg = @@ -225,7 +265,6 @@ ->response() ->setcookie( 'ZeroOpts', $cVal, $cExp, array( 'httpOnly' => false, 'path' => '/', 'prefix' => '' ) ); - wfProfileOut( __METHOD__ ); return true; } @@ -271,9 +310,7 @@ return true; } - wfProfileIn( __METHOD__ ); - - $isZeroTraffic = $this->isUnifiedZero() || $this->getZeroConfig(); + $isZeroTraffic = $this->getXcs()->isUnified || $this->getZeroConfig(); $this->logWarnings( $isZeroTraffic ); @@ -285,7 +322,8 @@ $out->addVaryHeader( 'X-CS' ); $out->addVaryHeader( 'X-Subdomain' ); - if ( !( $this->isUnifiedZero() ) && $this->getConfigId() !== null ) { + if ( $this->getXcs()->id ) { + // Unified does not depend on these. Only use it for specific carrier $out->addVaryHeader( 'X-Forwarded-By' ); $out->addVaryHeader( 'X-Forwarded-Proto' ); } @@ -301,8 +339,6 @@ $out->redirect( $url ); $out->output(); } - - wfProfileOut( __METHOD__ ); return true; } @@ -342,9 +378,12 @@ * @return bool */ public function onSpecialMobileEditWatchlist_images( &$images ) { - if ( $this->isZeroSubdomain() && !$this->getZeroConfig()->showImages() ) { - $images = array(); - return false; + if ( $this->isZeroSubdomain() ) { + $config = $this->getZeroConfig(); + if ( $config && !$config->showImages() ) { + $images = array(); + return false; + } } return true; } @@ -358,7 +397,7 @@ return ''; } $isFilePage = $title->inNamespace( NS_FILE ); - if ( $this->isUnifiedZero() ) { + if ( $this->getXcs()->isUnified ) { $query = array( 'zcmd' => 'js-banner' ); if ( $isFilePage ) { @@ -386,7 +425,7 @@ return ''; } } - $cfg = self::getJsConfigBlock( $this, $this->getConfigId(), $config, true ); + $cfg = self::getJsConfigBlock( $this, $this->getXcs()->xcs, $config, true ); return self::renderBanner( $this, $config, null, null, $isFilePage, $this->isZeroSpecial ) . Html::rawElement( 'script', array(), $cfg ); } @@ -437,44 +476,11 @@ $banner = Html::rawElement( 'a', $attr, $banner ); } - $attr = array( - 'class' => 'mw-mf-message', - 'id' => 'zero-rated-banner-text', - ); $style = $config->fontSize() !== '' ? 'font-size:' . $config->fontSize() . ';' : ''; if ( !$bannerUrl ) { $style .= "color:$foreground;"; } - if ( $style !== '' ) { - $attr['style'] = $style; - } - $banner = Html::rawElement( 'span', $attr, $banner ); - if ( !$undismissible ) { - $dismissTitle = wfMessage( 'zero-dismiss-notification' )->escaped(); - $dismiss = Html::rawElement( - 'button', - array( 'class' => 'notify-close', - 'style' => "background:$background;", - 'title' => $dismissTitle ), - Html::rawElement( - 'span', - array( - 'class' => 'notify-close-x', - 'style' => "background:$background;border-color:$foreground;color:$foreground;" ), - 'x' - ) - ); - } else { - $dismiss = ''; - } - $banner = Html::rawElement( - 'div', - array( - 'id' => 'zero-rated-banner', - 'class' => 'mw-mf-banner', - 'style' => "background:$background;color:$foreground;" ), - $dismiss . $banner ); - return $banner; + return self::makeBannerRaw( $banner, $foreground, $background, $style, $undismissible ); } /** @@ -510,8 +516,6 @@ * @return bool|String */ public static function getBannerText( $config, $isFilePage = false, $langCode = null, $sitename = null ) { - wfProfileIn( __METHOD__ ); - if ( !$config || ( $isFilePage && !$config->showImages() ) ) { return false; } @@ -523,7 +527,6 @@ } $name = self::pickLocalizedString( $config->name(), $lang ); if ( $name === false ) { - wfProfileOut( __METHOD__ ); return false; } $nameMsg = new RawMessage( $name ); @@ -532,47 +535,47 @@ if ( array_key_exists( $lang->getCode(), $banners ) ) { $linkText = $banners[$lang->getCode()]; } else { - $linkText = wfMessage( 'zero-banner-text' )->inLanguage( $lang )->plain(); + $linkText = wfMessage( 'zero-banner-text' )->inLanguage( $lang )->text(); } if ( $sitename !== null ) { // Rendering for the zero-config page, fake {{SITENAME}} $linkText = str_replace( '{{SITENAME}}', $sitename, $linkText ); } $linkTextMsg = new RawMessage( $linkText ); - $res = $linkTextMsg->inLanguage( $lang )->rawParams( $nameMsg->inLanguage( $lang )->escaped() )->escaped(); - - wfProfileOut( __METHOD__ ); - return $res; + return $linkTextMsg->inLanguage( $lang )->rawParams( $nameMsg->inLanguage( $lang )->escaped() )->escaped(); } /** - * Return true if the request is from a zero network, but not to a Zero-specific. - * Varnish sends us X-CS == "ON" instead of the code - * @return true + * Returns an object with xcs, id, ipset, isUnified, and isDebug fields. + * @return stdClass */ - public function isUnifiedZero() { - return $this->getConfigId() === "ON"; - } - - /** - * Return Carrier ID (X-CS) value or null if no value was set - * @return null|string - */ - public function getConfigId() { - if ( $this->configId === false ) { + public function getXcs() { + if ( $this->parsedXcs === false ) { + $res = new stdClass(); // Allow URL override of the X-CS parameter for testing purposes - $id = $this->getRequest()->getVal( 'X-CS' ); - if ( $id === null ) { - $id = $this->getRequest()->getHeader( 'X-CS' ); + $xcs = $this->getRequest()->getVal( 'X-CS' ); + $res->isDebug = $xcs !== null; + if ( !$res->isDebug ) { + $xcs = $this->getRequest()->getHeader( 'X-CS' ); } - if ( $id === '(null)' || !$id ) { - $id = null; + if ( $xcs === '(null)' || !$xcs ) { + $xcs = null; } - $this->configId = $id; + $res->xcs = $xcs; + $res->isUnified = $xcs === 'ON'; + if ( $xcs && !$res->isUnified ) { + $xcsParts = explode( '|', $xcs, 2 ); + $res->id = $xcsParts[0]; + $res->ipset = count( $xcsParts ) > 1 ? $xcsParts[1] : ''; + } else { + $res->id = null; + $res->ipset = ''; + } + $this->parsedXcs = $res; } - return $this->configId; + return $this->parsedXcs; } - private $configId = false; + private $parsedXcs = false; /** * Returns cached carrier configuration based on X-CS header or query parameter @@ -582,37 +585,26 @@ public function getZeroConfig( $mode = 0 ) { // Cached configuration or null if missing. if ( $this->config === false || ( $this->config && $this->config->contextMode() !== $mode ) ) { - wfProfileIn( __METHOD__ ); - - $ipset = ''; - $xcs = $this->getConfigId(); - // Unified is treated as if there is no config - we don't know the actual ID - if ( $xcs !== null && !$this->isUnifiedZero() ) { - $xcsParts = explode( '|', $xcs, 2 ); - $id = $xcsParts[0]; - if ( count( $xcsParts ) > 1 ) { - $ipset = $xcsParts[1]; - } - } else { - $id = null; - } - - if ( $id !== null && $this->config === false ) { - $this->config = JCSingleton::getContent( new TitleValue( NS_ZERO, $id ) ); - } else { + $xcs = $this->getXcs(); + if ( $this->config === false ) { $this->config = null; + $id = $xcs->id; + if ( $id ) { + $cfg = JCSingleton::getContent( new TitleValue( NS_ZERO, $id ) ); + if ( $cfg ) { + $this->config = $cfg; + } + } } - if ( $this->config ) { - if ( $this->getRequest()->getCheck( 'X-CS' ) ) { + if ( $xcs->isDebug ) { $mode |= ZeroConfig::ignoreDisabled; } list( $lang, $subdomain, $project ) = $this->getWikiInfo(); - $this->config->setContext( $ipset, $this->getRequest()->getHeader( 'X-FORWARDED-BY' ), + $this->config->setContext( $xcs->ipset, + $this->getRequest()->getHeader( 'X-FORWARDED-BY' ), $this->isHttps(), $subdomain . '.' . $project, $lang, $mode ); } - - wfProfileOut( __METHOD__ ); } return ( !$this->config || $this->config->isDisabled() ) ? null : $this->config; } @@ -947,12 +939,12 @@ * @return string */ public function getLandingRedirect() { - // !!!BEWARE!!! We're only varying on Accept-Language for the webroot + // !!!BEWARE!!! We're only varying on Accept-Language for the web root // We don't want the cached object pool to get huge. $this->getOutput()->addVaryHeader( 'Accept-Language' ); $protocol = PROTO_CURRENT; // In case the user hit zero.wikipedia.org/ and yet only mdot is supported - // we choose to ignore the site array ([m.wikipedia]) in the subcalls + // we choose to ignore the site array ([m.wikipedia]) in the sub-calls // TODO: When expanding mediawiki-config/mobilelanding.php (see puppet repo's // wwwportals.conf for rewrite rule) to cover more than mobile Wikipedias, // revisit this logic. @@ -1105,7 +1097,9 @@ * @return bool */ public function isHttps() { - return $this->getRequest()->getProtocol() === 'https'; + // it's handy to have the $isHttps variable here for local debug simulation of HTTPS + $isHttps = $this->getRequest()->getProtocol() === 'https'; + return $isHttps; } /** diff --git a/includes/ZeroConfig.php b/includes/ZeroConfig.php index 52fa8f1..a7faa73 100644 --- a/includes/ZeroConfig.php +++ b/includes/ZeroConfig.php @@ -58,7 +58,7 @@ $this->mode = null; $this->ipset = $ipset; $this->proxy = $proxy; // Keep original casing - $ipset = $ipset !== '' ? $ipset : 'default'; + $ipset = $ipset ?: 'default'; $proxy = $proxy !== false ? strtoupper( $proxy ) : 'DIRECT'; $all = $this->getDataWithDefaults(); @@ -194,6 +194,10 @@ return $this->config->enableHttps; } + public function warnHttps() { + return $this->config->warnHttps; + } + public function ipsetNames() { return $this->config->ipsets; } @@ -319,6 +323,8 @@ $this->test( array( 'configs', $k, 'whitelistedLangs' ), self::getWhitelistedLangsValidator() ); $this->test( array( 'configs', $k, 'proxies' ), self::getProxiesValidator() ); $this->testOptional( array( 'configs', $k, 'enableHttps' ), false, $isBool ); + // Show HTTPS-to-HTTP downgrades when user is on HTTPS and HTTPS isn't zero-rated + $this->testOptional( array( 'configs', $k, 'warnHttps' ), false, $isBool ); $this->testOptional( array( 'configs', $k, 'disableApps' ), false, $isBool ); $this->testOptional( array( 'configs', $k, 'bannerWarning' ), false, $isBool ); } diff --git a/includes/ZeroSpecialPage.php b/includes/ZeroSpecialPage.php index ad8ca1a..133f15c 100644 --- a/includes/ZeroSpecialPage.php +++ b/includes/ZeroSpecialPage.php @@ -153,8 +153,8 @@ } $languageLink = Html::element( 'a', array( 'href' => $state->getStartPageUrl( $languageCode ) ), - wfMessage( 'zero-home-page-selection', - ucfirst( $languageNames[$languageCode] ) )->inLanguage( $languageCode ) + wfMessage( 'zero-home-page-selection', ucfirst( $languageNames[$languageCode] ) ) + ->inLanguage( $languageCode )->parse() ); $html .= Html::rawElement( $tag, null, $languageLink ); } @@ -382,15 +382,14 @@ $out->addVaryHeader( 'X-Forwarded-Proto' ); // HTTPS $response->header( $out->getVaryHeader() ); - $id = $state->getConfigId(); $isFilePage = $request->getCheck( 'file' ); $errors = false; + // @todo: this is very bad code, need to join logic for js & img if ( $isJsBanner ) { // // JavaScript banner together with configuration (or a local redirect) // - $response->header( 'Content-type: application/javascript; charset=UTF-8' ); $banner = ''; if ( $state->isZeroSubdomain() && !$state->getZeroConfig() ) { // If zerodot isn't supported here and the user isn't already on @@ -401,15 +400,29 @@ $url = $state->getStartPageUrl( $info[0], $flags ); $banner = "window.location='$url?from='+encodeURIComponent(window.location)"; } else { + $cfg = false; $config = $state->getZeroConfig(); + $xcs = $state->getXcs()->xcs; if ( $config && $config->enabled() ) { - $bannerHtml = PageRendering::renderBanner( $state, $config, null, null, $isFilePage ); - $cfg = PageRendering::getJsConfigBlock( $this, $id, $config, (bool)$bannerHtml ); - if ( $bannerHtml ) { - $banner = 'document.write(' . Xml::encodeJsVar( $bannerHtml ) . ');'; + // Zero-rated request + $banner = PageRendering::renderBanner( $state, $config, null, null, $isFilePage ); + $cfg = PageRendering::getJsConfigBlock( $this, $xcs, $config, (bool)$banner ); + } elseif ( $state->isHttps() && !$config ) { + $config = $state->getZeroConfig( ZeroConfig::ignoreHttps ); + if ( $config && $config->warnHttps() ) { + // This request would have been zero-rated if it was HTTP instead of HTTPS + // Hardcode colors here, easier to treat as a banner + $banner = + PageRendering::makeBannerRaw( wfMessage( 'zero-no-https' )->text(), + '#000000', '#DCDCDC', 'color:#000000;' ); + $cfg = PageRendering::getJsConfigBlock( $this, $xcs, $config, false, true ); } - } else { - $cfg = PageRendering::getJsConfigBlock( $this, $id, $config, false ); + } + if ( !$cfg ) { + $cfg = PageRendering::getJsConfigBlock( $this, $xcs, $config, false ); + } + if ( $banner ) { + $banner = 'document.write(' . Xml::encodeJsVar( $banner ) . ');'; } $banner = $cfg . $banner; } @@ -417,7 +430,6 @@ // // Returning content of a GIF image // - $response->header( 'Content-type: image/gif' ); $lang = $this->getRequest()->getVal( 'zlang' ); // If lang was given (showing in the portal), ignore everything except language $config = @@ -437,6 +449,14 @@ $banner = self::createImageBanner( 'red', 'white', $bannerText, true ); $errors = !$banner; + } elseif ( $state->isHttps() && !$config ) { + $config = $state->getZeroConfig( ZeroConfig::ignoreHttps ); + if ( $config && $config->warnHttps() ) { + // This request would have been zero-rated if it was HTTP instead of HTTPS + // Hardcode colors here, easier to treat as a banner + $banner = self::createImageBanner( '#DCDCDC', '#000000', + wfMessage( 'zero-no-https' )->text(), true ); + } } if ( !$banner ) { // This tiny file resides in the same dir @@ -451,6 +471,11 @@ $this->setCacheControl( $request ); } + if ( $isJsBanner ) { + $response->header( 'Content-type: application/javascript; charset=UTF-8' ); + } else { + $response->header( 'Content-type: image/gif' ); + } echo $banner; return true; } diff --git a/modules/ZeroInfo.js b/modules/ZeroInfo.js index 1d85ffa..2f5ba93 100644 --- a/modules/ZeroInfo.js +++ b/modules/ZeroInfo.js @@ -21,8 +21,9 @@ ProcessDialog.super.prototype.initialize.apply( this, arguments ); var conf = ( window && window.zeroGlobalConfig ) || false, + warnHttps = conf && conf.warnHttps, tplData = { - intro: mw.msg( 'zero-info-intro', conf.name ), + intro: mw.msg( warnHttps ? 'zero-info-https-warn' : 'zero-info-intro', conf.name ), url: conf.bannerUrl, moreInfo: mw.msg( 'zero-info-buttonText' ) }; -- To view, visit https://gerrit.wikimedia.org/r/189592 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I5eb5a56b395bd8493e4454cbfb89a8596800f224 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/ZeroBanner Gerrit-Branch: wmf/1.25wmf15 Gerrit-Owner: Yurik <yu...@wikimedia.org> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits