jenkins-bot has submitted this change and it was merged.

Change subject: Split MobileFormatter to separate classes for HTML and WML
......................................................................


Split MobileFormatter to separate classes for HTML and WML

Also, remove the pointless check for the "search" URL parameter,
this functionality is not used.

Change-Id: I155335305197909e51dfc7744da3fc9ba0dcb13c
---
M MobileFrontend.php
D includes/MobileFormatter.php
M includes/api/ApiMobileView.php
M includes/api/ApiParseExtender.php
R includes/formatters/HtmlFormatter.php
A includes/formatters/MobileFormatter.php
A includes/formatters/MobileFormatterHTML.php
A includes/formatters/MobileFormatterWML.php
M tests/MobileFormatterTest.php
9 files changed, 494 insertions(+), 467 deletions(-)

Approvals:
  awjrichards: Verified; Looks good to me, approved
  jenkins-bot: Verified



diff --git a/MobileFrontend.php b/MobileFrontend.php
index 054ab37..afee385 100644
--- a/MobileFrontend.php
+++ b/MobileFrontend.php
@@ -38,11 +38,14 @@
        'MobileFrontendHooks' => 'MobileFrontend.hooks',
 
        'DeviceDetection' => 'DeviceDetection',
-       'HtmlFormatter' => 'HtmlFormatter',
        'MobileContext' => 'MobileContext',
-       'MobileFormatter' => 'MobileFormatter',
        'WmlContext' => 'WmlContext',
 
+       'HtmlFormatter' => 'formatters/HtmlFormatter',
+       'MobileFormatter' => 'formatters/MobileFormatter',
+       'MobileFormatterHTML' => 'formatters/MobileFormatterHTML',
+       'MobileFormatterWML' => 'formatters/MobileFormatterWML',
+
        'ApiMobileView' => 'api/ApiMobileView',
        'ApiParseExtender' => 'api/ApiParseExtender',
        'ApiQueryExtracts' => 'api/ApiQueryExtracts',
diff --git a/includes/MobileFormatter.php b/includes/MobileFormatter.php
deleted file mode 100644
index 484f454..0000000
--- a/includes/MobileFormatter.php
+++ /dev/null
@@ -1,445 +0,0 @@
-<?php
-
-/**
- * Converts HTML into a mobile-friendly version
- */
-class MobileFormatter extends HtmlFormatter {
-       const WML_SECTION_SEPARATOR = 
'***************************************************************************';
-
-       protected $format;
-
-       /**
-        * @var Title
-        */
-       protected $title;
-
-       protected $expandableSections = false;
-       protected $mainPage = false;
-       protected $backToTopLink = true;
-
-       private $headings = 0;
-
-       /**
-        * @var WmlContext
-        */
-       protected $wmlContext;
-
-       private $defaultItemsToRemove = array(
-               'table.toc',
-               'div.stub',
-               '#search', // remove search form element from Special:Search
-               'div.sister-project',
-               'div.magnify',
-               '.editsection', // FIXME: deprecate in favour of mw-editsection
-               '.mw-editsection', // Edit links in section headings
-               'span.t',
-               '.portal',
-               '#protected-icon',
-               '.boilerplate',
-               '#id-articulo-destacado', // FA star on eswiki, @todo: remove 
class="metadata topicon" instead
-               '.hiddenStructure',
-               '.medialist',
-               '.mw-search-createlink',
-               '#ogg_player_1',
-               '#ogg_player_2',
-               '.nomobile',
-       );
-
-       /**
-        * Constructor
-        *
-        * @param string $html: Text to process
-        * @param Title $title: Title to which $html belongs
-        * @param string $format: 'HTML' or 'WML'
-        * @param WmlContext $wmlContext: Context for creation of WML cards, 
can be omitted if $format == 'HTML'
-        * @throws MWException
-        */
-       public function __construct( $html, $title, $format, WmlContext 
$wmlContext = null ) {
-               parent::__construct( $html );
-
-               $this->title = $title;
-               $this->format = $format;
-               if ( !$wmlContext && $format == 'WML' ) {
-                       throw new MWException( __METHOD__ . '(): WML context 
not set' );
-               }
-               $this->wmlContext = $wmlContext;
-               $this->flattenRedLinks();
-       }
-
-       /**
-        * Creates and returns a MobileFormatter
-        *
-        * @param MobileContext $context
-        * @param string $html
-        *
-        * @return MobileFormatter
-        */
-       public static function newFromContext( $context, $html ) {
-               wfProfileIn( __METHOD__ );
-
-               $wmlContext = $context->getContentFormat() == 'WML' ? new 
WmlContext( $context ) : null;
-               $title = $context->getTitle();
-               $formatter = new MobileFormatter( self::wrapHTML( $html ), 
$title,
-                       $context->getContentFormat(), $wmlContext
-               );
-               if ( $context->isBetaGroupMember() ) {
-                       $formatter->disableBackToTop();
-               }
-
-               $isMainPage = $title->isMainPage();
-               $isFilePage = $title->inNamespace( NS_FILE );
-
-               if ( !$context->isAlphaGroupMember() ) {
-                       $formatter->setIsMainPage( $isMainPage );
-               }
-
-               if ( $context->getContentFormat() == 'HTML'
-                       && $context->getRequest()->getText( 'search' ) == '' )
-               {
-                       $formatter->enableExpandableSections( !$isMainPage );
-               }
-               if ( $context->getContentTransformations() && !$isFilePage ) {
-                       $formatter->removeImages( $context->imagesDisabled() );
-               }
-
-               wfProfileOut( __METHOD__ );
-               return $formatter;
-       }
-
-       /**
-        * @return string: Output format
-        */
-       public function getFormat() {
-               return $this->format;
-       }
-
-       /**
-        * @todo: kill with fire when there will be minimum of pre-1.1 app 
users remaining
-        * @param bool $flag 
-        */
-       public function enableExpandableSections( $flag = true ) {
-               $this->expandableSections = $flag;
-       }
-
-       /**
-        * @todo: kill with fire when dynamic sections in production
-        */
-       public function disableBackToTop() {
-               $this->backToTopLink = false;
-       }
-
-       public function setIsMainPage( $value = true ) {
-               $this->mainPage = $value;
-       }
-
-       /**
-        * Removes content inappropriate for mobile devices
-        * @param bool $removeDefaults: Whether default settings at 
self::$defaultItemsToRemove should be used
-        */
-       public function filterContent( $removeDefaults = true ) {
-               global $wgMFRemovableClasses;
-
-               if ( $removeDefaults ) {
-                       $this->remove( $this->getDefaultItemsToRemove() );
-                       $this->remove( $wgMFRemovableClasses );
-               }
-               parent::filterContent();
-       }
-
-       /**
-        * Performs final transformations to mobile format and returns 
resulting HTML/WML
-        *
-        * @param DOMElement|string|null $element: ID of element to get HTML 
from or false to get it from the whole tree
-        * @return string: Processed HTML
-        */
-       public function getText( $element = null ) {
-               wfProfileIn( __METHOD__ );
-               if ( $this->mainPage ) {
-                       $element = $this->parseMainPage( $this->getDoc() );
-               }
-               $html = parent::getText( $element );
-               wfProfileOut( __METHOD__ );
-               return $html;
-       }
-
-       /**
-        * Getter for $this->defaultItemsToRemove
-        * @return array
-        */
-       public function getDefaultItemsToRemove() {
-               return $this->defaultItemsToRemove;
-       }
-
-       /**
-        * Setter for $this->defaultItemsToRemove
-        * @param array $defaultItemsToRemove: Indexed array of HTML elements 
to be stripped during mobile formatting
-        * @throws MWException
-        * @return array
-        */
-       public function setDefaultItemsToRemove( $defaultItemsToRemove ) {
-               if ( !is_array( $defaultItemsToRemove ) ) {
-                       throw new MWException( __METHOD__ . '(): 
defaultItemsToRemove must be an array.' );
-               }
-               $this->defaultItemsToRemove = $defaultItemsToRemove;
-       }
-
-       protected function onHtmlReady( $html ) {
-               switch ( $this->format ) {
-                       case 'HTML':
-                               if ( $this->expandableSections && strlen( $html 
) > 4000 ) {
-                                       $html = $this->headingTransform( $html 
);
-                               }
-                               break;
-                       case 'WML':
-                               $html = $this->headingTransform( $html );
-                               // Content removal for WML rendering
-                               $this->flatten( array( 'span', 'div', 'sup', 
'h[1-6]', 'sup', 'sub' ) );
-                               // Content wrapping
-                               $html = $this->createWMLCard( $html );
-                               break;
-               }
-               return $html;
-       }
-
-       /**
-        * Callback for headingTransform()
-        * @param array $matches
-        * @return string
-        */
-       private function headingTransformCallbackWML( $matches ) {
-               wfProfileIn( __METHOD__ );
-               $this->headings++;
-
-               $base = self::WML_SECTION_SEPARATOR .
-                               "<h2 class='section_heading' 
id='section_{$this->headings}'>{$matches[2]}</h2>";
-
-               wfProfileOut( __METHOD__ );
-               return $base;
-       }
-
-       /**
-        * generates a back top link for a given section number
-        * @param int $headingNumber: The number corresponding to the section 
heading
-        * @return string
-        */
-       private function backToTopLink( $headingNumber ) {
-               return Html::rawElement( 'a',
-                               array( 'id' => 'anchor_' . $headingNumber,
-                                       'href' => '#section_' . $headingNumber,
-                                       'class' => 'section_anchors' ),
-                               '&#8593;' . $this->msg( 
'mobile-frontend-back-to-top-of-section' ) );
-       }
-       /**
-        * Callback for headingTransform()
-        * @param array $matches
-        * @return string
-        */
-       private function headingTransformCallbackHTML( $matches ) {
-               wfProfileIn( __METHOD__ );
-               if ( isset( $matches[0] ) ) {
-                       preg_match( '/id="([^"]*)"/', $matches[0], 
$headlineMatches );
-               }
-
-               $headlineId = ( isset( $headlineMatches[1] ) ) ? 
$headlineMatches[1] : '';
-
-               $this->headings++;
-               // Back to top link
-               if ( $this->backToTopLink ) {
-                       $backToTop = $this->backToTopLink( intval( 
$this->headings - 1 ) );
-               } else {
-                       $backToTop = '';
-               }
-
-               // generate the HTML we are going to inject
-               if ( $this->headings === 1 ) {
-                       $base = '</div>'; // close up content_0 section
-               } else {
-                       $base = '';
-               }
-               $base .= Html::openElement( 'div', array( 'class' => 'section' 
) );
-               $base .= Html::openElement( 'h2',
-                               array( 'class' => 'section_heading', 'id' => 
'section_' . $this->headings )
-                       );
-               $base .=
-                       Html::rawElement( 'span',
-                                       array( 'id' => $headlineId ),
-                                       $matches[2]
-                               )
-                               . Html::closeElement( 'h2' )
-                               . Html::openElement( 'div',
-                                       array( 'class' => 'content_block', 'id' 
=> 'content_' . $this->headings )
-                               );
-
-               if ( $this->headings > 1 ) {
-                       // Close it up here
-                       $base = '</div>' // <div class="content_block">
-                               . $backToTop
-                               . "</div>" // <div class="section">
-                               . $base;
-               }
-
-               wfProfileOut( __METHOD__ );
-               return $base;
-       }
-
-       /**
-        * Creates a WML card from input
-        * @param string $s: Raw WML
-        * @return string: WML card
-        */
-       protected function createWMLCard( $s ) {
-               wfProfileIn( __METHOD__ );
-               $segments = explode( self::WML_SECTION_SEPARATOR, $s );
-               $card = '';
-               $idx = 0;
-               $requestedSegment = htmlspecialchars( 
$this->wmlContext->getRequestedSegment() );
-               $title = htmlspecialchars( $this->title->getText() );
-               $segmentText = $this->wmlContext->getOnlyThisSegment()
-                       ? str_replace( self::WML_SECTION_SEPARATOR, '', $s )
-                       : $segments[$requestedSegment];
-
-               $card .= "<card id='s{$idx}' 
title='{$title}'><p>{$segmentText}</p>";
-               $idx = intval( $requestedSegment ) + 1;
-               $segmentsCount = $this->wmlContext->getOnlyThisSegment()
-                       ? $idx + 1 // @todo: when using from API we don't have 
the total section count
-                       : count( $segments );
-               $card .= "<p>" . $idx . "/" . $segmentsCount . "</p>";
-
-               $useFormatParam = ( $this->wmlContext->getUseFormat() )
-                       ? '&amp;useformat=' . $this->wmlContext->getUseFormat()
-                       : '';
-
-               // Title::getLocalUrl doesn't work at this point since PHP 
5.1.x, all objects have their destructors called
-               // before the output buffer callback function executes.
-               // Thus, globalized objects will not be available as expected 
in the function.
-               // This is stated to be intended behavior, as per the 
following: [http://bugs.php.net/bug.php?id=40104]
-               $defaultQuery = wfCgiToArray( preg_replace( '/^.*?(\?|$)/', '', 
$this->wmlContext->getCurrentUrl() ) );
-               unset( $defaultQuery['seg'] );
-               unset( $defaultQuery['useformat'] );
-
-               $qs = wfArrayToCgi( $defaultQuery );
-               $delimiter = ( !empty( $qs ) ) ? '?' : '';
-               $basePageParts = wfParseUrl( $this->wmlContext->getCurrentUrl() 
);
-               $basePage = $basePageParts['scheme'] . 
$basePageParts['delimiter'] . $basePageParts['host'] . $basePageParts['path'] . 
$delimiter . $qs;
-               $appendDelimiter = ( $delimiter === '?' ) ? '&amp;' : '?';
-
-               if ( $idx < $segmentsCount ) {
-                       $card .= "<p><a 
href=\"{$basePage}{$appendDelimiter}seg={$idx}{$useFormatParam}\">"
-                               . $this->msg( 'mobile-frontend-wml-continue' ) 
. "</a></p>";
-               }
-
-               if ( $idx > 1 ) {
-                       $back_idx = $requestedSegment - 1;
-                       $card .= "<p><a 
href=\"{$basePage}{$appendDelimiter}seg={$back_idx}{$useFormatParam}\">"
-                               . $this->msg( 'mobile-frontend-wml-back' ) . 
"</a></p>";
-               }
-
-               $card .= '</card>';
-               wfProfileOut( __METHOD__ );
-               return $card;
-       }
-
-       /**
-        * Prepares headings in WML mode, makes sections expandable in HTML mode
-        * @param string $s
-        * @return string
-        */
-       protected function headingTransform( $s ) {
-               wfProfileIn( __METHOD__ );
-               $callback = "headingTransformCallback{$this->format}";
-
-               // Closures are a PHP 5.3 feature.
-               // MediaWiki currently requires PHP 5.2.3 or higher.
-               // So, using old style for now.
-               $s = '<div id="content_0" class="content_block openSection">'
-                       . preg_replace_callback(
-                       '%<h2(.*)<span class="mw-headline" 
[^>]*>(.+)</span>[\s\r\n]*</h2>%sU',
-                       array( $this, $callback ),
-                       $s
-               );
-
-               // if we had any, make sure to close the whole thing!
-               if ( $this->headings > 0 ) {
-                       if ( $this->backToTopLink ) {
-                               $bt = $this->backToTopLink( intval( 
$this->headings ) );
-                       } else {
-                               $bt = '';
-                       }
-                       $s .= '</div>' // <div class="content_block">
-                               . $bt
-                               . "\n</div>"; // <div class="section">
-               }
-               wfProfileOut( __METHOD__ );
-               return $s;
-       }
-
-       /**
-        * Returns interface message text
-        * @param string $key: Message key
-        * @return string Wiki text
-        */
-       protected function msg( $key ) {
-               return wfMessage( $key )->text();
-       }
-
-       /**
-        * Performs transformations specific to main page
-        * @param DOMDocument $mainPage: Tree to process
-        * @return DOMElement
-        */
-       protected function parseMainPage( DOMDocument $mainPage ) {
-               wfProfileIn( __METHOD__ );
-
-               // FIXME: Move to ZeroRatedMobileAccess extension
-               $zeroLandingPage = $mainPage->getElementById( 
'zero-landing-page' );
-               $featuredArticle = $mainPage->getElementById( 'mp-tfa' );
-               $newsItems = $mainPage->getElementById( 'mp-itn' );
-
-               $xpath = new DOMXpath( $mainPage );
-               $elements = $xpath->query( '//*[starts-with(@id, "mf-")]' );
-
-               $commonAttributes = array( 'mp-tfa', 'mp-itn' );
-
-               $content = $mainPage->createElement( 'div' );
-               $content->setAttribute( 'id', 'mainpage' );
-
-               // FIXME: Move to ZeroRatedMobileAccess extension
-               if ( $zeroLandingPage ) {
-                       $content->appendChild( $zeroLandingPage );
-               }
-
-               if ( $featuredArticle ) {
-                       $h2FeaturedArticle = $mainPage->createElement( 'h2', 
$this->msg( 'mobile-frontend-featured-article' ) );
-                       $content->appendChild( $h2FeaturedArticle );
-                       $content->appendChild( $featuredArticle );
-               }
-
-               if ( $newsItems ) {
-                       $h2NewsItems = $mainPage->createElement( 'h2', 
$this->msg( 'mobile-frontend-news-items' ) );
-                       $content->appendChild( $h2NewsItems );
-                       $content->appendChild( $newsItems );
-               }
-
-               /** @var $element DOMElement */
-               foreach ( $elements as $element ) {
-                       if ( $element->hasAttribute( 'id' ) ) {
-                               $id = $element->getAttribute( 'id' );
-                               if ( !in_array( $id, $commonAttributes ) ) {
-                                       $sectionTitle = $element->hasAttribute( 
'title' ) ? $element->getAttribute( 'title' ) : '';
-                                       if( $sectionTitle !== '' ) {
-                                               $element->removeAttribute( 
'title' );
-                                               $h2UnknownMobileSection = 
$mainPage->createElement( 'h2', $sectionTitle );
-                                               $content->appendChild( 
$h2UnknownMobileSection );
-                                       }
-                                       $br = $mainPage->createElement( 'br' );
-                                       $br->setAttribute( 'clear', 'all' );
-                                       $content->appendChild( $element );
-                                       $content->appendChild( $br );
-                               }
-                       }
-               }
-
-               wfProfileOut( __METHOD__ );
-               return $content;
-       }
-}
diff --git a/includes/api/ApiMobileView.php b/includes/api/ApiMobileView.php
index c95e2be..a48cbd8 100644
--- a/includes/api/ApiMobileView.php
+++ b/includes/api/ApiMobileView.php
@@ -164,16 +164,13 @@
                }
 
                wfProfileIn( __METHOD__ . '-MobileFormatter' );
-               $mf = new MobileFormatter( MobileFormatter::wrapHTML( $html ),
-                       $title,
-                       'HTML'
-               );
-               $mf->removeImages( $noImages );
                if ( !$this->noTransform ) {
+                       $mf = new MobileFormatterHTML( 
MobileFormatter::wrapHTML( $html ), $title );
+                       $mf->removeImages( $noImages );
                        $mf->filterContent();
                        $mf->setIsMainPage( $this->mainPage );
+                       $html = $mf->getText();
                }
-               $html = $mf->getText();
                wfProfileOut( __METHOD__ . '-MobileFormatter' );
 
                if ( $this->mainPage || $this->file ) {
diff --git a/includes/api/ApiParseExtender.php 
b/includes/api/ApiParseExtender.php
index c3dbf81..1298b5b 100644
--- a/includes/api/ApiParseExtender.php
+++ b/includes/api/ApiParseExtender.php
@@ -70,19 +70,20 @@
                                $result->reset();
 
                                $title = Title::newFromText( 
$data['parse']['title'] );
-                               $context = new WmlContext();
-                               $context->setCurrentUrl( 
$title->getCanonicalURL() );
-                               $context->setRequestedSegment( isset( 
$params['section'] )
-                                       ? $params['section'] + 1 // Segment 
numbers start from 1
-                                       : 0
-                               );
-                               $context->setUseFormat( 'wml' ); // Force WML 
links just in case
-                               $context->setOnlyThisSegment( isset( 
$params['section'] ) );
-                               $mf = new MobileFormatter( 
MobileFormatter::wrapHTML( $data['parse']['text']['*'] ),
-                                       $title,
-                                       MobileContext::parseContentFormat( 
$params['mobileformat'] ),
-                                       $context
-                               );
+                               $html = MobileFormatter::wrapHTML( 
$data['parse']['text']['*'] );
+                               if ( MobileContext::parseContentFormat( 
$params['mobileformat'] ) === 'WML' ) {
+                                       $context = new WmlContext();
+                                       $context->setCurrentUrl( 
$title->getCanonicalURL() );
+                                       $context->setRequestedSegment( isset( 
$params['section'] )
+                                                       ? $params['section'] + 
1 // Segment numbers start from 1
+                                                       : 0
+                                       );
+                                       $context->setUseFormat( 'wml' ); // 
Force WML links just in case
+                                       $context->setOnlyThisSegment( isset( 
$params['section'] ) );
+                                       $mf = new MobileFormatterWML( $html, 
$title, $context );
+                               } else {
+                                       $mf = new MobileFormatterHTML( $html, 
$title );
+                               }
                                $mf->removeImages( $params['noimages'] );
                                $mf->setIsMainPage( $params['mainpage'] );
                                $mf->enableExpandableSections( 
!$params['mainpage'] );
diff --git a/includes/HtmlFormatter.php b/includes/formatters/HtmlFormatter.php
similarity index 100%
rename from includes/HtmlFormatter.php
rename to includes/formatters/HtmlFormatter.php
diff --git a/includes/formatters/MobileFormatter.php 
b/includes/formatters/MobileFormatter.php
new file mode 100644
index 0000000..af8e3a6
--- /dev/null
+++ b/includes/formatters/MobileFormatter.php
@@ -0,0 +1,287 @@
+<?php
+
+/**
+ * Converts HTML into a mobile-friendly version
+ */
+abstract class MobileFormatter extends HtmlFormatter {
+       /**
+        * @var Title
+        */
+       protected $title;
+
+       protected $expandableSections = false;
+       protected $mainPage = false;
+       protected $backToTopLink = true;
+
+       protected $headings = 0;
+
+       private $defaultItemsToRemove = array(
+               'table.toc',
+               'div.stub',
+               '#search', // remove search form element from Special:Search
+               'div.sister-project',
+               'div.magnify',
+               '.editsection', // FIXME: deprecate in favour of mw-editsection
+               '.mw-editsection', // Edit links in section headings
+               'span.t',
+               '.portal',
+               '#protected-icon',
+               '.boilerplate',
+               '#id-articulo-destacado', // FA star on eswiki, @todo: remove 
class="metadata topicon" instead
+               '.hiddenStructure',
+               '.medialist',
+               '.mw-search-createlink',
+               '#ogg_player_1',
+               '#ogg_player_2',
+               '.nomobile',
+       );
+
+       /**
+        * Constructor
+        *
+        * @param string $html: Text to process
+        * @param Title $title: Title to which $html belongs
+        */
+       public function __construct( $html, $title ) {
+               parent::__construct( $html );
+
+               $this->title = $title;
+               $this->flattenRedLinks();
+       }
+
+       /**
+        * Creates and returns a MobileFormatter
+        *
+        * @param MobileContext $context
+        * @param string $html
+        *
+        * @return MobileFormatter
+        */
+       public static function newFromContext( $context, $html ) {
+               wfProfileIn( __METHOD__ );
+
+               $title = $context->getTitle();
+               $isMainPage = $title->isMainPage();
+               $isFilePage = $title->inNamespace( NS_FILE );
+
+               $html = self::wrapHTML( $html );
+               if ( $context->getContentFormat() === 'WML' ) {
+                       $wmlContext = new WmlContext( $context );
+                       $formatter = new MobileFormatterWML( $html, $title, 
$wmlContext );
+               } else {
+                       $formatter = new MobileFormatterHTML( $html, $title );
+                       $formatter->enableExpandableSections( !$isMainPage );
+               }
+
+               if ( $context->isBetaGroupMember() ) {
+                       $formatter->disableBackToTop();
+               }
+               if ( !$context->isAlphaGroupMember() ) {
+                       $formatter->setIsMainPage( $isMainPage );
+               }
+               if ( $context->getContentTransformations() && !$isFilePage ) {
+                       $formatter->removeImages( $context->imagesDisabled() );
+               }
+
+               wfProfileOut( __METHOD__ );
+               return $formatter;
+       }
+
+       /**
+        * @return string: Output format
+        */
+       public abstract function getFormat();
+
+       /**
+        * @todo: kill with fire when there will be minimum of pre-1.1 app 
users remaining
+        * @param bool $flag
+        */
+       public function enableExpandableSections( $flag = true ) {
+               $this->expandableSections = $flag;
+       }
+
+       /**
+        * @todo: kill with fire when dynamic sections in production
+        */
+       public function disableBackToTop() {
+               $this->backToTopLink = false;
+       }
+
+       public function setIsMainPage( $value = true ) {
+               $this->mainPage = $value;
+       }
+
+       /**
+        * Removes content inappropriate for mobile devices
+        * @param bool $removeDefaults: Whether default settings at 
self::$defaultItemsToRemove should be used
+        */
+       public function filterContent( $removeDefaults = true ) {
+               global $wgMFRemovableClasses;
+
+               if ( $removeDefaults ) {
+                       $this->remove( $this->getDefaultItemsToRemove() );
+                       $this->remove( $wgMFRemovableClasses );
+               }
+               parent::filterContent();
+       }
+
+       /**
+        * Performs final transformations to mobile format and returns 
resulting HTML/WML
+        *
+        * @param DOMElement|string|null $element: ID of element to get HTML 
from or false to get it from the whole tree
+        * @return string: Processed HTML
+        */
+       public function getText( $element = null ) {
+               wfProfileIn( __METHOD__ );
+               if ( $this->mainPage ) {
+                       $element = $this->parseMainPage( $this->getDoc() );
+               }
+               $html = parent::getText( $element );
+               wfProfileOut( __METHOD__ );
+               return $html;
+       }
+
+       /**
+        * Getter for $this->defaultItemsToRemove
+        * @return array
+        */
+       public function getDefaultItemsToRemove() {
+               return $this->defaultItemsToRemove;
+       }
+
+       /**
+        * Setter for $this->defaultItemsToRemove
+        * @param array $defaultItemsToRemove: Indexed array of HTML elements 
to be stripped during mobile formatting
+        * @throws MWException
+        * @return array
+        */
+       public function setDefaultItemsToRemove( $defaultItemsToRemove ) {
+               if ( !is_array( $defaultItemsToRemove ) ) {
+                       throw new MWException( __METHOD__ . '(): 
defaultItemsToRemove must be an array.' );
+               }
+               $this->defaultItemsToRemove = $defaultItemsToRemove;
+       }
+
+       /**
+        * Callback for headingTransform()
+        * @param array $matches
+        * @return string
+        */
+       protected abstract function headingTransformCallback( $matches );
+
+       /**
+        * generates a back top link for a given section number
+        * @param int $headingNumber: The number corresponding to the section 
heading
+        * @return string
+        */
+       protected function backToTopLink( $headingNumber ) {
+               return Html::rawElement( 'a',
+                               array( 'id' => 'anchor_' . $headingNumber,
+                                       'href' => '#section_' . $headingNumber,
+                                       'class' => 'section_anchors' ),
+                               '&#8593;' . $this->msg( 
'mobile-frontend-back-to-top-of-section' ) );
+       }
+
+       /**
+        * Prepares headings in WML mode, makes sections expandable in HTML mode
+        * @param string $s
+        * @return string
+        */
+       protected function headingTransform( $s ) {
+               wfProfileIn( __METHOD__ );
+
+               // Closures are a PHP 5.3 feature.
+               // MediaWiki currently requires PHP 5.2.3 or higher.
+               // So, using old style for now.
+               $s = '<div id="content_0" class="content_block openSection">'
+                       . preg_replace_callback(
+                               '%<h2(.*)<span class="mw-headline" 
[^>]*>(.+)</span>[\s\r\n]*</h2>%sU',
+                               array( $this, 'headingTransformCallback' ),
+                               $s
+                       );
+
+               // if we had any, make sure to close the whole thing!
+               if ( $this->headings > 0 ) {
+                       if ( $this->backToTopLink ) {
+                               $bt = $this->backToTopLink( intval( 
$this->headings ) );
+                       } else {
+                               $bt = '';
+                       }
+                       $s .= '</div>' // <div class="content_block">
+                               . $bt
+                               . "\n</div>"; // <div class="section">
+               }
+               wfProfileOut( __METHOD__ );
+               return $s;
+       }
+
+       /**
+        * Returns interface message text
+        * @param string $key: Message key
+        * @return string Wiki text
+        */
+       protected function msg( $key ) {
+               return wfMessage( $key )->text();
+       }
+
+       /**
+        * Performs transformations specific to main page
+        * @param DOMDocument $mainPage: Tree to process
+        * @return DOMElement
+        */
+       protected function parseMainPage( DOMDocument $mainPage ) {
+               wfProfileIn( __METHOD__ );
+
+               // FIXME: Move to ZeroRatedMobileAccess extension
+               $zeroLandingPage = $mainPage->getElementById( 
'zero-landing-page' );
+               $featuredArticle = $mainPage->getElementById( 'mp-tfa' );
+               $newsItems = $mainPage->getElementById( 'mp-itn' );
+
+               $xpath = new DOMXpath( $mainPage );
+               $elements = $xpath->query( '//*[starts-with(@id, "mf-")]' );
+
+               $commonAttributes = array( 'mp-tfa', 'mp-itn' );
+
+               $content = $mainPage->createElement( 'div' );
+               $content->setAttribute( 'id', 'mainpage' );
+
+               // FIXME: Move to ZeroRatedMobileAccess extension
+               if ( $zeroLandingPage ) {
+                       $content->appendChild( $zeroLandingPage );
+               }
+
+               if ( $featuredArticle ) {
+                       $h2FeaturedArticle = $mainPage->createElement( 'h2', 
$this->msg( 'mobile-frontend-featured-article' ) );
+                       $content->appendChild( $h2FeaturedArticle );
+                       $content->appendChild( $featuredArticle );
+               }
+
+               if ( $newsItems ) {
+                       $h2NewsItems = $mainPage->createElement( 'h2', 
$this->msg( 'mobile-frontend-news-items' ) );
+                       $content->appendChild( $h2NewsItems );
+                       $content->appendChild( $newsItems );
+               }
+
+               /** @var $element DOMElement */
+               foreach ( $elements as $element ) {
+                       if ( $element->hasAttribute( 'id' ) ) {
+                               $id = $element->getAttribute( 'id' );
+                               if ( !in_array( $id, $commonAttributes ) ) {
+                                       $sectionTitle = $element->hasAttribute( 
'title' ) ? $element->getAttribute( 'title' ) : '';
+                                       if( $sectionTitle !== '' ) {
+                                               $element->removeAttribute( 
'title' );
+                                               $h2UnknownMobileSection = 
$mainPage->createElement( 'h2', $sectionTitle );
+                                               $content->appendChild( 
$h2UnknownMobileSection );
+                                       }
+                                       $br = $mainPage->createElement( 'br' );
+                                       $br->setAttribute( 'clear', 'all' );
+                                       $content->appendChild( $element );
+                                       $content->appendChild( $br );
+                               }
+                       }
+               }
+
+               wfProfileOut( __METHOD__ );
+               return $content;
+       }
+}
diff --git a/includes/formatters/MobileFormatterHTML.php 
b/includes/formatters/MobileFormatterHTML.php
new file mode 100644
index 0000000..5f6b671
--- /dev/null
+++ b/includes/formatters/MobileFormatterHTML.php
@@ -0,0 +1,74 @@
+<?php
+
+class MobileFormatterHTML extends MobileFormatter {
+       /**
+        * Constructor
+        *
+        * @param string $html: Text to process
+        * @param Title $title: Title to which $html belongs
+        */
+       public function __construct( $html, $title ) {
+               parent::__construct( $html, $title );
+       }
+
+       public function getFormat() {
+               return 'HTML';
+       }
+
+       protected function onHtmlReady( $html ) {
+               wfProfileIn( __METHOD__ );
+               if ( $this->expandableSections && strlen( $html ) > 4000 ) {
+                       $html = $this->headingTransform( $html );
+               }
+               wfProfileOut( __METHOD__ );
+               return $html;
+       }
+
+       protected function headingTransformCallback( $matches ) {
+               wfProfileIn( __METHOD__ );
+               if ( isset( $matches[0] ) ) {
+                       preg_match( '/id="([^"]*)"/', $matches[0], 
$headlineMatches );
+               }
+
+               $headlineId = ( isset( $headlineMatches[1] ) ) ? 
$headlineMatches[1] : '';
+
+               $this->headings++;
+               // Back to top link
+               if ( $this->backToTopLink ) {
+                       $backToTop = $this->backToTopLink( intval( 
$this->headings - 1 ) );
+               } else {
+                       $backToTop = '';
+               }
+
+               // generate the HTML we are going to inject
+               if ( $this->headings === 1 ) {
+                       $base = '</div>'; // close up content_0 section
+               } else {
+                       $base = '';
+               }
+               $base .= Html::openElement( 'div', array( 'class' => 'section' 
) );
+               $base .= Html::openElement( 'h2',
+                       array( 'class' => 'section_heading', 'id' => 'section_' 
. $this->headings )
+               );
+               $base .=
+                       Html::rawElement( 'span',
+                               array( 'id' => $headlineId ),
+                               $matches[2]
+                       )
+                       . Html::closeElement( 'h2' )
+                       . Html::openElement( 'div',
+                               array( 'class' => 'content_block', 'id' => 
'content_' . $this->headings )
+                       );
+
+               if ( $this->headings > 1 ) {
+                       // Close it up here
+                       $base = '</div>' // <div class="content_block">
+                               . $backToTop
+                               . "</div>" // <div class="section">
+                               . $base;
+               }
+
+               wfProfileOut( __METHOD__ );
+               return $base;
+       }
+}
diff --git a/includes/formatters/MobileFormatterWML.php 
b/includes/formatters/MobileFormatterWML.php
new file mode 100644
index 0000000..13f4da8
--- /dev/null
+++ b/includes/formatters/MobileFormatterWML.php
@@ -0,0 +1,110 @@
+<?php
+
+class MobileFormatterWML extends MobileFormatter {
+       const WML_SECTION_SEPARATOR = 
'***************************************************************************';
+
+       /**
+        * @var WmlContext
+        */
+       protected $wmlContext;
+
+       /**
+        * Constructor
+        *
+        * @param string $html: Text to process
+        * @param Title $title: Title to which $html belongs
+        * @param WmlContext $wmlContext: Context for creation of WML cards, 
can be omitted if $format == 'HTML'
+        * @throws MWException
+        */
+       public function __construct( $html, $title, WmlContext $wmlContext = 
null ) {
+               parent::__construct( $html, $title );
+
+               if ( !$wmlContext ) {
+                       throw new MWException( __METHOD__ . '(): WML context 
not set' );
+               }
+               $this->wmlContext = $wmlContext;
+       }
+
+       public function getFormat() {
+               return 'WML';
+       }
+
+       protected function onHtmlReady( $html ) {
+               wfProfileIn( __METHOD__ );
+               $html = $this->headingTransform( $html );
+               // Content removal for WML rendering
+               $this->flatten( array( 'span', 'div', 'sup', 'h[1-6]', 'sup', 
'sub' ) );
+               // Content wrapping
+               $html = $this->createWMLCard( $html );
+               wfProfileOut( __METHOD__ );
+               return $html;
+       }
+
+       /**
+        * Creates a WML card from input
+        * @param string $s: Raw WML
+        * @return string: WML card
+        */
+       private function createWMLCard( $s ) {
+               wfProfileIn( __METHOD__ );
+               $segments = explode( self::WML_SECTION_SEPARATOR, $s );
+               $card = '';
+               $idx = 0;
+               $requestedSegment = htmlspecialchars( 
$this->wmlContext->getRequestedSegment() );
+               $title = htmlspecialchars( $this->title->getText() );
+               $segmentText = $this->wmlContext->getOnlyThisSegment()
+                       ? str_replace( self::WML_SECTION_SEPARATOR, '', $s )
+                       : $segments[$requestedSegment];
+
+               $card .= "<card id='s{$idx}' 
title='{$title}'><p>{$segmentText}</p>";
+               $idx = intval( $requestedSegment ) + 1;
+               $segmentsCount = $this->wmlContext->getOnlyThisSegment()
+                       ? $idx + 1 // @todo: when using from API we don't have 
the total section count
+                       : count( $segments );
+               $card .= "<p>" . $idx . "/" . $segmentsCount . "</p>";
+
+               $useFormatParam = ( $this->wmlContext->getUseFormat() )
+                       ? '&amp;useformat=' . $this->wmlContext->getUseFormat()
+                       : '';
+
+               // Title::getLocalUrl doesn't work at this point since PHP 
5.1.x, all objects have their destructors called
+               // before the output buffer callback function executes.
+               // Thus, globalized objects will not be available as expected 
in the function.
+               // This is stated to be intended behavior, as per the 
following: [http://bugs.php.net/bug.php?id=40104]
+               $defaultQuery = wfCgiToArray( preg_replace( '/^.*?(\?|$)/', '', 
$this->wmlContext->getCurrentUrl() ) );
+               unset( $defaultQuery['seg'] );
+               unset( $defaultQuery['useformat'] );
+
+               $qs = wfArrayToCgi( $defaultQuery );
+               $delimiter = ( !empty( $qs ) ) ? '?' : '';
+               $basePageParts = wfParseUrl( $this->wmlContext->getCurrentUrl() 
);
+               $basePage = $basePageParts['scheme'] . 
$basePageParts['delimiter'] . $basePageParts['host'] . $basePageParts['path'] . 
$delimiter . $qs;
+               $appendDelimiter = ( $delimiter === '?' ) ? '&amp;' : '?';
+
+               if ( $idx < $segmentsCount ) {
+                       $card .= "<p><a 
href=\"{$basePage}{$appendDelimiter}seg={$idx}{$useFormatParam}\">"
+                               . $this->msg( 'mobile-frontend-wml-continue' ) 
. "</a></p>";
+               }
+
+               if ( $idx > 1 ) {
+                       $back_idx = $requestedSegment - 1;
+                       $card .= "<p><a 
href=\"{$basePage}{$appendDelimiter}seg={$back_idx}{$useFormatParam}\">"
+                               . $this->msg( 'mobile-frontend-wml-back' ) . 
"</a></p>";
+               }
+
+               $card .= '</card>';
+               wfProfileOut( __METHOD__ );
+               return $card;
+       }
+
+       protected function headingTransformCallback( $matches ) {
+               wfProfileIn( __METHOD__ );
+               $this->headings++;
+
+               $base = self::WML_SECTION_SEPARATOR .
+                       "<h2 class='section_heading' 
id='section_{$this->headings}'>{$matches[2]}</h2>";
+
+               wfProfileOut( __METHOD__ );
+               return $base;
+       }
+}
diff --git a/tests/MobileFormatterTest.php b/tests/MobileFormatterTest.php
index 78037d7..3048846 100644
--- a/tests/MobileFormatterTest.php
+++ b/tests/MobileFormatterTest.php
@@ -10,7 +10,7 @@
        public function testHtmlTransform( $input, $expected, $callback = false 
) {
                $t = Title::newFromText( 'Mobile' );
                $input = str_replace( "\r", '', $input ); // "yay" to Windows!
-               $mf = new MobileFormatter( MobileFormatter::wrapHTML( $input ), 
$t, 'HTML' );
+               $mf = new MobileFormatterHTML( MobileFormatter::wrapHTML( 
$input ), $t );
                if ( $callback ) {
                        $callback( $mf );
                }

-- 
To view, visit https://gerrit.wikimedia.org/r/68434
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I155335305197909e51dfc7744da3fc9ba0dcb13c
Gerrit-PatchSet: 3
Gerrit-Project: mediawiki/extensions/MobileFrontend
Gerrit-Branch: master
Gerrit-Owner: MaxSem <[email protected]>
Gerrit-Reviewer: MaxSem <[email protected]>
Gerrit-Reviewer: awjrichards <[email protected]>
Gerrit-Reviewer: jenkins-bot

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to