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' ),
- '↑' . $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() )
- ? '&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 === '?' ) ? '&' : '?';
-
- 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' ),
+ '↑' . $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() )
+ ? '&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 === '?' ) ? '&' : '?';
+
+ 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