Merge branch 'master' into gallery_support

This commit is contained in:
Luqgreg 2018-08-03 13:37:55 +02:00
commit 34a11ba554
25 changed files with 310 additions and 1767 deletions

View file

@ -7,6 +7,12 @@ class PortableInfoboxHooks {
return true;
}
public static function onBeforePageDisplayMobile( OutputPage $out, Skin $skin ) {
$out->addModules( 'ext.PortableInfobox.mobile' );
return true;
}
public static function onImageServingCollectImages( &$imageNamesArray, $articleTitle ) {
if ( $articleTitle ) {
@ -118,13 +124,4 @@ class PortableInfoboxHooks {
return true;
}
public static function onArticleAsJsonBeforeEncode( &$text ) {
$tagController = PortableInfoboxParserTagController::getInstance();
$tagController->moveFirstMarkerToTop( $text );
$text = $tagController->replaceMarkers( $text );
return true;
}
}

View file

@ -9,7 +9,7 @@ use \Wikia\PortableInfobox\Parser\Nodes\UnimplementedNodeException;
class PortableInfoboxParserTagController extends WikiaObject {
const PARSER_TAG_NAME = 'infobox';
const PARSER_TAG_VERSION = 2;
const DEFAULT_THEME_NAME = 'wikia';
const DEFAULT_THEME_NAME = 'default';
const DEFAULT_LAYOUT_NAME = 'default';
const INFOBOX_THEME_PREFIX = 'pi-theme-';
const INFOBOX_LAYOUT_PREFIX = 'pi-layout-';
@ -54,11 +54,7 @@ class PortableInfoboxParserTagController extends WikiaObject {
* @return string
*/
public static function replaceInfoboxMarkers( Parser $parser, &$text ) {
global $wgArticleAsJson;
// The replacements for ArticleAsJson are handled in PortableInfoboxHooks::onArticleAsJsonBeforeEncode
if ( !$wgArticleAsJson ) {
$text = static::getInstance()->replaceMarkers( $text );
}
$text = static::getInstance()->replaceMarkers( $text );
return true;
}
@ -95,8 +91,7 @@ class PortableInfoboxParserTagController extends WikiaObject {
$accentColor = $this->getColor( self::ACCENT_COLOR, $params, $frame );
$accentColorText = $this->getColor( self::ACCENT_COLOR_TEXT, $params, $frame );
$renderService = \F::app()->checkSkin( 'wikiamobile' ) ?
new PortableInfoboxMobileRenderService() : new PortableInfoboxRenderService();
$renderService = new PortableInfoboxRenderService();
return $renderService->renderInfobox( $data, implode( ' ', $themeList ), $layout, $accentColor, $accentColorText );
}

View file

@ -12,6 +12,7 @@
"descriptionmsg": "portable-infobox-desc",
"version": "0.1",
"type": "parserhook",
"license-name": "GPL-3.0-or-later",
"MessagesDirs": {
"PortableInfobox": "i18n"
},
@ -19,11 +20,16 @@
"ext.PortableInfobox": {
"scripts": "js/PortableInfobox.js",
"styles": "styles/PortableInfobox.less"
},
"ext.PortableInfobox.mobile": {
"scripts": "js/PortableInfobox.js",
"styles": "styles/PortableInfobox.less",
"targets": "mobile"
}
},
"ResourceFileModulePaths": {
"localBasePath": "",
"remoteExtPath": "extensions/PortableInfobox"
"remoteExtPath": "PortableInfobox"
},
"AutoloadClasses": {
"F": "includes/nirvana/WikiaApp.class.php",
@ -31,7 +37,6 @@
"WikiaObject": "includes/nirvana/WikiaObject.class.php",
"WikiaRegistry": "includes/nirvana/WikiaRegistry.class.php",
"WikiaGlobalRegistry": "includes/nirvana/WikiaGlobalRegistry.class.php",
"Wikia\\Template\\Engine": "includes/mustache/Engine.class.php",
"Wikia\\Template\\MustacheEngine": "includes/mustache/MustacheEngine.class.php",
"MustacheService": "includes/mustache/MustacheService.class.php",
"HtmlHelper": "includes/filehelpers/HtmlHelper.class.php",
@ -40,7 +45,6 @@
"PortableInfoboxDataService": "services/PortableInfoboxDataService.class.php",
"PortableInfoboxQueryService": "services/PortableInfoboxQueryService.class.php",
"PortableInfoboxRenderService": "services/PortableInfoboxRenderService.class.php",
"PortableInfoboxMobileRenderService": "services/PortableInfoboxMobileRenderService.class.php",
"PortableInfoboxErrorRenderService": "services/PortableInfoboxErrorRenderService.class.php",
"Wikia\\PortableInfobox\\Parser\\ExternalParser": "services/Parser/ExternalParser.php",
"Wikia\\PortableInfobox\\Parser\\SimpleParser": "services/Parser/SimpleParser.php",
@ -68,7 +72,6 @@
"PassThroughSanitizer": "services/Sanitizers/PassThroughSanitizer.php",
"NodeTypeSanitizerInterface": "services/Sanitizers/NodeTypeSanitizerInterface.php",
"NodeDataSanitizer": "services/Sanitizers/NodeDataSanitizer.php",
"NodeHeroImageSanitizer": "services/Sanitizers/NodeHeroImageSanitizer.php",
"NodeHorizontalGroupSanitizer": "services/Sanitizers/NodeHorizontalGroupSanitizer.php",
"NodeImageSanitizer": "services/Sanitizers/NodeImageSanitizer.php",
"NodeTitleSanitizer": "services/Sanitizers/NodeTitleSanitizer.php",
@ -83,6 +86,7 @@
"ParserFirstCallInit": "PortableInfoboxParserTagController::parserTagInit",
"AfterParserParseImageGallery": "PortableInfoboxHooks::onAfterParserParseImageGallery",
"BeforePageDisplay": "PortableInfoboxHooks::onBeforePageDisplay",
"BeforePageDisplayMobile": "PortableInfoboxHooks::onBeforePageDisplayMobile",
"ParserAfterTidy": "PortableInfoboxParserTagController::replaceInfoboxMarkers",
"ImageServing::buildAndGetIndex": "PortableInfoboxHooks::onImageServingCollectImages",
"wgQueryPages": "PortableInfoboxHooks::onWgQueryPages",
@ -90,8 +94,7 @@
"ArticlePurge": "PortableInfoboxHooks::onArticlePurge",
"ArticleSave": "PortableInfoboxHooks::onArticleSave",
"BacklinksPurge": "PortableInfoboxHooks::onBacklinksPurge",
"ArticleInsertComplete": "PortableInfoboxHooks::onArticleInsertComplete",
"ArticleAsJsonBeforeEncode": "PortableInfoboxHooks::onArticleAsJsonBeforeEncode"
"ArticleInsertComplete": "PortableInfoboxHooks::onArticleInsertComplete"
},
"SpecialPages": {
"AllInfoboxes": "AllinfoboxesQueryPage"

View file

@ -1,246 +0,0 @@
<?php
namespace Wikia\Template;
/**
* Base class for Wikia Template System.
*
* Simple usage
* ------------
* Rendering a single template requiring a few values:
*
* (new Wikia\Template\PHPEngine)
* ->setValues( [
* 'name' => 'John',
* 'nick' => 'GIJoe',
* 'email' => 'GIJoe@anotherplace.com'
* ] )
* ->render( dirname(__FILE__) . '/templates/hello.php' )
*
* Intermediate usage
* ------------------
* Using a single instance for rendering multiple templates
* in the same folder, e.g. from different methods in a class:
*
* class A {
* private $tplEngine;
*
* function __construct(){
* //pay the instantiation fee only once
* $this->tplEngine = (new Wikia\Template\PHPEngine)
* ->setPrefix( dirname(__FILE__) . '/templates' );
* }
*
* function foo(){
* //renders templates/foo.php
* return $this->tplEngine
* //optional, removes all the values set by another call
* ->clearValues()
* ->render( 'foo' );
* }
*
* function bar(){
* //renders templates/bar.php
* return $this->tplEngine
* //replaces the whole set of values, safe for reuse
* ->setValues( ['one' => 1, 'two' => 2] );
* ->render( 'bar' );
* }
* }
*
* Advanced usage
* --------------
* Using a single instance for rendering multiple templates
* in the same folder with shared values:
*
* $family = [
* ['name' => 'Fred' , 'birthdate' => '01/01/10000 BC'],
* ['name' => 'Wilma' , 'birthdate' => '05/02/9994 BC' ],
* ['name' => 'Pebbles', 'birthdate' => '10/7/9963 BC' ]
* ];
*
* //render the list of the Flinstones family members in Bedrock
* $tplEngine = (new Wikia\Template\MustacheEngine)
* ->setPrefix( dirname(__FILE__) . '/templates' )
* //shared values across render calls
* ->setValues( [
* 'familyName' => 'Flinstone',
* 'city' => 'Bedrock'
* ] );
*
* //use the shared values for the header of the list
*
* //templates/header.mustache:
* //List of {{familyName}} inhabitants of {{city}}
* $output = $tplEngine->render( 'header.mustache' );
*
* foreach ( $family as $member ) {
* //templates/item.mustache:
* //* {{name}} {{familyName}}, born in {{birthdate}}
*
* //add/update the values keeping those set previously
* $output .= $tplEngine->updateValues(
* ['name' => $member['name], 'birthdate' => $member['birthdate']]
* )->render( 'item.mustache' ) . "\n";
* }
*
* @package Wikia\Template
* @author Federico "Lox" Lucignano
*/
abstract class Engine {
protected $values = [];
protected $prefix = '';
/**
* Renders the template as a string.
*
* @param string $template The name of the template, will be combined with
* the value passed to Engine::setPrefix().
* In case of FileSystem-based engines it should be the filename
* either alone (Engine::setPrefix()) or the full path, both need to
* include the file's extension.
*
* @return string The rendered template
*/
public abstract function render( $template );
/**
* Checks if a template exists.
*
* @param string $template The name of the template, will be combined with
* the value passed to Engine::setPrefix().
* In case of FileSystem-based engines it should be the filename
* either alone (Engine::setPrefix()) or the full path, both need to
* include the file's extension.
*
* @return bool Wether the template was found or not
*/
public abstract function exists( $template );
/**
* Sets the base path for this instance.
*
* @param string $prefix The prefix to append to the template
* name passed as a parameter to Engine::render(), e.g. the path
* to a folder containing template files for filesystem-based
* engines.
*
* @return Engine The current instance
*/
public function setPrefix ( $prefix ) {
$this->prefix = (string) $prefix;
return $this;
}
/**
* Returns the base path set for this instance.
*
* @return string|null
*/
public function getPrefix() {
return $this->prefix;
}
/**
* Sets multiple values to be passed to a template at once.
*
* @param array $values The values to be passed to a template
* in the form of an associative array, i.e. [IDENTIFIER => VALUE]
*
* @return Engine The current instance
*
* @see Engine::setVal() if you need to set only one value
*/
public function setData( Array $values ) {
$this->values = $values;
return $this;
}
/**
* Add/overwrites multiple values in the collection of the current instance
* to be passed to a template.
*
* @param array $values The values to be added/updated in the form of an
* associative array, i.e. [IDENTIFIER => VALUE]
*
* @return Engine The current instance
*
* * @see Engine::setVal() if you need to add/overwrite only one value
*/
public function updateData( Array $values ) {
wfProfileIn( __METHOD__ );
$this->values = array_merge( $this->values, $values );
wfProfileOut( __METHOD__ );
return $this;
}
/**
* Empties the collection of values stored in an instance
*
* @return Engine The current instance
*
* @see Engine::clearVal() if you need to clear only one value
*/
public function clearData(){
$this->values = [];
return $this;
}
/**
* Returns the values set for this instance to be passed to a template.
*
* @return array The values set for this instance, null if the collection
* wasn't set
*
* @see Engine::getVal() if you need to get only one value
*/
public function getData() {
return $this->values;
}
/**
* Sets/add a value in the collection to be passed to a template.
*
* @param string $name The name of the value
* @param mixed $value The real value
*
* @return Engine The current instance
*
* @see Engine::setData() if you need to set multiple values instead
* of calling this method multiple times
*/
public function setVal( $name, $value ) {
$this->values[$name] = $value;
return $this;
}
/**
* Removed a value from he collection to be passed to a template.
*
* @param string $name The name of the value
*
* @return Engine The current instance
*
* @see Engine::clearData() if you need to clear the whole collection
* instead of calling this method multiple times
*/
public function clearVal( $name ){
unset( $this->values[$name] );
return $this;
}
/**
* Returns a value in the collection to be passed to a template.
*
* @param string $name The name of the value
*
* @return mixed|null The current instance
*
* @see Engine::setData() if you need to get multiple values instead
* of calling this method multiple times
*/
public function getVal( $name ) {
return isset($this->values[$name]) ? $this->values[$name]: null;
}
}

View file

@ -6,13 +6,22 @@ namespace Wikia\Template;
*
* @package Wikia\Template
* @author Federico "Lox" Lucignano <federico@wikia-inc.com>
*
* @see Engine for documentation and usage
*/
class MustacheEngine extends Engine {
class MustacheEngine {
protected $values = [];
protected $prefix = '';
/**
* @see Engine::exists() for documentation and usage
* Checks if a template exists.
*
* @param string $template The name of the template, will be combined with
* the value passed to MustacheEngine::setPrefix().
* In case of FileSystem-based engines it should be the filename
* either alone (MustacheEngine::setPrefix()) or the full path, both need to
* include the file's extension.
*
* @return bool Wether the template was found or not
*/
public function exists( $template ) {
//wfProfileIn( __METHOD__ );
@ -24,7 +33,15 @@ class MustacheEngine extends Engine {
}
/**
* @see Engine::render() for documentation and usage
* Renders the template as a string.
*
* @param string $template The name of the template, will be combined with
* the value passed to MustacheEngine::setPrefix().
* In case of FileSystem-based engines it should be the filename
* either alone (MustacheEngine::setPrefix()) or the full path, both need to
* include the file's extension.
*
* @return string The rendered template
*/
public function render( $template ) {
//wfProfileIn( __METHOD__ );
@ -38,4 +55,132 @@ class MustacheEngine extends Engine {
//wfProfileOut( __METHOD__ );
return $contents;
}
/**
* Sets the base path for this instance.
*
* @param string $prefix The prefix to append to the template
* name passed as a parameter to MustacheEngine::render(), e.g. the path
* to a folder containing template files for filesystem-based
* engines.
*
* @return MustacheEngine The current instance
*/
public function setPrefix ( $prefix ) {
$this->prefix = (string) $prefix;
return $this;
}
/**
* Returns the base path set for this instance.
*
* @return string|null
*/
public function getPrefix() {
return $this->prefix;
}
/**
* Sets multiple values to be passed to a template at once.
*
* @param array $values The values to be passed to a template
* in the form of an associative array, i.e. [IDENTIFIER => VALUE]
*
* @return MustacheEngine The current instance
*
* @see MustacheEngine::setVal() if you need to set only one value
*/
public function setData( Array $values ) {
$this->values = $values;
return $this;
}
/**
* Add/overwrites multiple values in the collection of the current instance
* to be passed to a template.
*
* @param array $values The values to be added/updated in the form of an
* associative array, i.e. [IDENTIFIER => VALUE]
*
* @return MustacheEngine The current instance
*
* * @see MustacheEngine::setVal() if you need to add/overwrite only one value
*/
public function updateData( Array $values ) {
wfProfileIn( __METHOD__ );
$this->values = array_merge( $this->values, $values );
wfProfileOut( __METHOD__ );
return $this;
}
/**
* Empties the collection of values stored in an instance
*
* @return MustacheEngine The current instance
*
* @see MustacheEngine::clearVal() if you need to clear only one value
*/
public function clearData(){
$this->values = [];
return $this;
}
/**
* Returns the values set for this instance to be passed to a template.
*
* @return array The values set for this instance, null if the collection
* wasn't set
*
* @see MustacheEngine::getVal() if you need to get only one value
*/
public function getData() {
return $this->values;
}
/**
* Sets/add a value in the collection to be passed to a template.
*
* @param string $name The name of the value
* @param mixed $value The real value
*
* @return MustacheEngine The current instance
*
* @see MustacheEngine::setData() if you need to set multiple values instead
* of calling this method multiple times
*/
public function setVal( $name, $value ) {
$this->values[$name] = $value;
return $this;
}
/**
* Removed a value from he collection to be passed to a template.
*
* @param string $name The name of the value
*
* @return MustacheEngine The current instance
*
* @see MustacheEngine::clearData() if you need to clear the whole collection
* instead of calling this method multiple times
*/
public function clearVal( $name ){
unset( $this->values[$name] );
return $this;
}
/**
* Returns a value in the collection to be passed to a template.
*
* @param string $name The name of the value
*
* @return mixed|null The current instance
*
* @see MustacheEngine::setData() if you need to get multiple values instead
* of calling this method multiple times
*/
public function getVal( $name ) {
return isset($this->values[$name]) ? $this->values[$name]: null;
}
};

View file

@ -6,7 +6,7 @@
* in order to supply Mustache engine (either in PHP or JS) with all necessary
* dependencies.
*
* @see https://github.com/jbboehr/php-mustache
* @see https://github.com/wikimedia/mediawiki-vendor/tree/master/zordius/lightncandy
*
* @author Władysław Bodzek <wladek@wikia-inc.com>
*/

View file

@ -13,18 +13,12 @@ class PortableInfoboxMustacheEngine {
'title' => 'PortableInfoboxItemTitle.mustache',
'header' => 'PortableInfoboxItemHeader.mustache',
'image' => 'PortableInfoboxItemImage.mustache',
'image-mobile' => 'PortableInfoboxItemImageMobile.mustache',
'image-mobile-wikiamobile' => 'PortableInfoboxItemImageMobileWikiaMobile.mustache',
'data' => 'PortableInfoboxItemData.mustache',
'group' => 'PortableInfoboxItemGroup.mustache',
'smart-group' => 'PortableInfoboxItemSmartGroup.mustache',
'horizontal-group-content' => 'PortableInfoboxHorizontalGroupContent.mustache',
'navigation' => 'PortableInfoboxItemNavigation.mustache',
'hero-mobile' => 'PortableInfoboxItemHeroMobile.mustache',
'hero-mobile-wikiamobile' => 'PortableInfoboxItemHeroMobileWikiaMobile.mustache',
'image-collection' => 'PortableInfoboxItemImageCollection.mustache',
'image-collection-mobile' => 'PortableInfoboxItemImageCollectionMobile.mustache',
'image-collection-mobile-wikiamobile' => 'PortableInfoboxItemImageCollectionMobileWikiaMobile.mustache'
'image-collection' => 'PortableInfoboxItemImageCollection.mustache'
];
protected $templateEngine;

View file

@ -32,20 +32,13 @@ class NodeImage extends Node {
}
public static function getTabberData( $html ) {
global $wgArticleAsJson;
$data = array();
$doc = HtmlHelper::createDOMDocumentFromText( $html );
$sxml = simplexml_import_dom( $doc );
$divs = $sxml->xpath( '//div[@class=\'tabbertab\']' );
foreach ( $divs as $div ) {
if ( $wgArticleAsJson ) {
if ( preg_match( '/data-ref="([^"]+)"/', $div->asXML(), $out ) ) {
$data[] = array( 'label' => (string) $div['title'], 'title' => \ArticleAsJson::$media[$out[1]]['title'] );
}
} else {
if ( preg_match( '/data-(video|image)-key="([^"]+)"/', $div->asXML(), $out ) ) {
$data[] = array( 'label' => (string) $div['title'], 'title' => $out[2] );
}
if ( preg_match( '/data-(video|image)-key="([^"]+)"/', $div->asXML(), $out ) ) {
$data[] = array( 'label' => (string) $div['title'], 'title' => $out[2] );
}
}
return $data;

View file

@ -1,174 +0,0 @@
<?php
class PortableInfoboxMobileRenderService extends PortableInfoboxRenderService {
const MEDIA_CONTEXT_INFOBOX_HERO_IMAGE = 'infobox-hero-image';
const MEDIA_CONTEXT_INFOBOX = 'infobox';
const MOBILE_THUMBNAIL_WIDTH = 360;
/**
* renders infobox
*
* @param array $infoboxdata
*
* @param $theme
* @param $layout
* @param $accentColor
* @param $accentColorText
* @return string - infobox HTML
*/
public function renderInfobox( array $infoboxdata, $theme, $layout, $accentColor, $accentColorText ) {
$items = $this->splitOfHeroData( $infoboxdata, [ 'hero' => [ ], 'infobox' => [ ] ] );
if ( !empty( $items['hero'] ) ) {
$infoboxHtmlContent = $this->renderInfoboxHero( $items['hero'] ) . $this->renderChildren( $items['infobox'] );
} else {
$infoboxHtmlContent = $this->renderChildren( $infoboxdata );
}
if ( !empty( $infoboxHtmlContent ) ) {
$output = $this->renderItem( 'wrapper', [ 'content' => $infoboxHtmlContent, 'isMercury' => $this->isMercury() ] );
} else {
$output = '';
}
// Since portable infoboxes are rendered within ParserAfterTidy Hook, we can not be sure if this method will be
// invoked before or after infobox is rendered. If this method does not process PI html, markers related to
// template type parsing may be included in the result html and this may break our mobile application.
// See XW-4827
TemplateTypesParser::onParserAfterTidy( null, $output );
\Wikia\PortableInfobox\Helpers\PortableInfoboxDataBag::getInstance()->setFirstInfoboxAlredyRendered( true );
return $output;
}
private function splitOfHeroData( $items, $result ) {
foreach ( $items as $item ) {
$data = $item['data'];
$type = $item['type'];
if ( $this->isValidHeroDataItem( $item, $result['hero'] ) ) {
$result['hero'][$type] = $data;
} elseif ( $type === 'group' ) {
// go deeper to find nested hero data items
$groupResult = $this->splitOfHeroData( $data['value'], [ 'hero' => $result['hero'], 'infobox' => [ ] ] );
// make sure other elements structure stays the same
$result['hero'] = $groupResult['hero'];
$item['data']['value'] = $groupResult['infobox'];
$result['infobox'][] = $item;
} else {
$result['infobox'][] = $item;
}
}
return $result;
}
protected function renderImage( $data ) {
$images = [ ];
$count = count( $data );
$helper = $this->getImageHelper();
for ( $i = 0; $i < $count; $i++ ) {
$data[$i]['context'] = self::MEDIA_CONTEXT_INFOBOX;
$data[$i] = $helper->extendImageData( $data[$i], self::MOBILE_THUMBNAIL_WIDTH );
if ( !!$data[$i] ) {
$images[] = $data[$i];
}
}
if ( count( $images ) === 0 ) {
return '';
}
// use different template for wikiamobile
if ( !$this->isMercury() ) {
// always display only the first image on WikiaMobile
$data = $images[0];
$templateName = 'image-mobile-wikiamobile';
} else {
if ( count( $images ) === 1 ) {
$data = $images[0];
$templateName = 'image-mobile';
} else {
// more than one image means image collection
$data = $helper->extendImageCollectionData( $images );
$templateName = 'image-collection-mobile';
}
}
$data = SanitizerBuilder::createFromType( 'image' )->sanitize( $data );
return parent::render( $templateName, $data );
}
protected function renderTitle( $data ) {
return $this->render( 'title', $data );
}
protected function renderHeader( $data ) {
return $this->render( 'header', $data );
}
protected function render( $type, array $data ) {
$data = SanitizerBuilder::createFromType( $type )->sanitize( $data );
return parent::render( $type, $data );
}
/**
* renders infobox hero component
*
* @param array $data - infobox hero component data
*
* @return string
*/
private function renderInfoboxHero( $data ) {
$helper = $this->getImageHelper();
$template = '';
// In mobile-wiki SPA content of the first infobox's hero module has been moved to the article header.
$firstInfoboxAlredyRendered = \Wikia\PortableInfobox\Helpers\PortableInfoboxDataBag::getInstance()
->isFirstInfoboxAlredyRendered();
if ( isset( $data['image'] ) ) {
$image = $data['image'][0];
$image['context'] = self::MEDIA_CONTEXT_INFOBOX_HERO_IMAGE;
$image = $helper->extendImageData( $image, self::MOBILE_THUMBNAIL_WIDTH );
$data['image'] = $image;
if ( !$this->isMercury() ) {
return $this->renderItem( 'hero-mobile-wikiamobile', $data );
} elseif ( $firstInfoboxAlredyRendered ) {
return $this->renderItem( 'hero-mobile', $data );
}
} elseif ( !$this->isMercury() || $firstInfoboxAlredyRendered ) {
return $this->renderItem( 'title', $data['title'] );
}
return !empty( $template ) ? $this->renderItem( $template, $data['title'] ) : '';
}
/**
* checks if infobox data item is valid hero component data.
*
* @param array $item - infobox data item
* @param array $heroData - hero component data
*
* @return bool
*/
private function isValidHeroDataItem( $item, $heroData ) {
$type = $item['type'];
return ( $type === 'title' && !isset( $heroData['title'] ) ) ||
( $type === 'image' && !isset( $heroData['image'] ) &&
count( $item['data'] ) === 1 );
}
private function isMercury() {
global $wgArticleAsJson;
return !empty( $wgArticleAsJson );
}
}

View file

@ -1,24 +0,0 @@
<?php
class NodeHeroImageSanitizer extends NodeSanitizer {
protected $allowedTags = [ 'a' ];
protected $selectorsWrappingTextToPad = [ 'li' ];
protected $selectorsWrappingAllowedFeatures = [ 'sup[@class="reference"]' ];
protected $selectorsForFullRemoval =
[ 'script', 'span[@itemprop="duration"]', '*[contains(@data-component,"article-media")]'];
/**
* @param $data
* @return mixed
*/
public function sanitize( $data ) {
if ( !empty( $data[ 'title' ][ 'value' ] ) ) {
$data[ 'title' ][ 'value' ] = $this->sanitizeElementData( $data[ 'title' ][ 'value' ] );
}
if ( !empty( $data[ 'image' ][ 'caption' ] ) ) {
$data[ 'image' ]['caption'] = $this->sanitizeElementData( $data[ 'image' ]['caption'] );
}
return $data;
}
}

View file

@ -18,9 +18,6 @@ class SanitizerBuilder {
return new NodeTitleSanitizer();
case 'image':
return new NodeImageSanitizer();
case 'hero-mobile':
case 'hero-mobile-wikiamobile':
return new NodeHeroImageSanitizer();
default:
return new PassThroughSanitizer();
}

View file

@ -1,7 +1,7 @@
:root {
--pi-background: #e6f5f9;
--pi-secondary-background: #bfe6ef;
--pi-border-color: #bfe6ef;
--pi-background: #f8f9fa;
--pi-secondary-background: #eaecf0;
--pi-border-color: #a2a9b1;
--pi-item-spacing: 5px 10px;
--pi-margin: 15px;
--pi-width: 270px
@ -11,41 +11,53 @@
background: var(--pi-background);
clear: right;
float: right;
font-size: .85em;
margin: 0 0 var(--pi-margin) var(--pi-margin);
width: var(--pi-width);
.pi-border-color {
border-color: var(--pi-border-color);
}
.pi-secondary-background {
background-color: var(--pi-secondary-background);
}
.pi-secondary-font {
margin: 0;
padding: 0;
}
.pi-item-spacing {
padding: var(--pi-item-spacing);
}
.pi-secondary-font {
font-size: 12px;
font-weight: bold;
line-height: 18px;
margin-top: 0;
.pi-title {
border: 0;
margin: 0;
font-family: inherit;
font-size: 1.75em;
line-height: 1.5;
}
.pi-header {
border: 0;
margin: 0;
font-family: inherit;
font-size: 1.2em;
font-weight: bold;
line-height: 1.3;
}
.pi-title {
border: 0;
.pi-image {
margin: 0;
font-size: 18px;
line-height: 28px;
max-width: 100%;
text-align: center;
}
.pi-data-label {
margin-bottom: 0;
margin-top: 0;
padding-top: 0;
font-size: inherit;
line-height: inherit;
}
.pi-data-value {
>* {
>:not(ul), li {
margin: 0;
}
li {
margin: 0;
line-height: 19px;
}
ol, ul {
ol {
list-style-position: inside;
}
}
@ -54,15 +66,7 @@
}
}
.pi {
&-secondary-background {
background-color: var(--pi-secondary-background);
}
&-border-color {
border-color: var(--pi-border-color);
}
&-image {
text-align: center;
margin: 0;
&-thumbnail{
max-width: var(--pi-width);
height: auto;
@ -106,9 +110,8 @@
-ms-hyphens: auto;
hyphens: auto;
color: #808080;
font-size: 12px;
font-size: 1em;
font-style: italic;
text-align: left;
word-wrap: break-word;
}
&-data {
@ -140,19 +143,17 @@
word-wrap: break-word;
}
&-value {
-ms-flex: 1;
-webkit-flex: 1;
-moz-flex: 1;
flex: 1;
-webkit-hyphens: auto;
-moz-hyphens: auto;
-ms-hyphens: auto;
hyphens: auto;
font-size: 12px;
line-height: 18px;
padding-left: 0;
word-break: break-word;
&:not(:first-child) {
-ms-flex-preferred-size: 180px;
-webkit-flex-basis: 180px;
-moz-flex-basis: 180px;
flex-basis: 180px;
padding-left: 10px;
}
}
@ -172,6 +173,9 @@
.pi-header {
text-align: left;
}
thead + tbody .pi-data-value {
padding-top: 0;
}
.pi-horizontal-group-item {
vertical-align: top;
&:not(:first-child) {
@ -185,11 +189,7 @@
}
&-smart {
&-data {
&-label:not(:first-child) {
border-color: var(--pi-border-color);
border-left-style: solid;
border-left-width: 1px;
}
&-label:not(:first-child),
&-value:not(:first-child) {
border-color: var(--pi-border-color);
border-left-style: solid;
@ -205,24 +205,23 @@
&:last-child {
border-bottom: 0;
}
&-data {
&-label {
padding-bottom: 0;
box-sizing: border-box;
flex-basis: auto;
padding: 5px 10px;
}
&-value {
box-sizing: border-box;
flex-basis: auto;
padding: 5px 10px;
&-head {
display: flex;
& + .pi-smart-group-body .pi-data-value {
padding-top: 0;
}
}
&-body {
display: flex;
}
&-head {
display: flex;
.pi-smart-data-label {
padding-bottom: 0;
box-sizing: border-box;
flex-basis: auto;
}
.pi-smart-data-value {
box-sizing: border-box;
flex-basis: auto;
}
}
}
@ -230,55 +229,94 @@
.pi-data {
display: block;
}
.pi-data-value {
.pi-data-value:not(:only-child) {
padding-left: 10px;
}
}
&-collapse {
.pi-header {
&:first-child {
padding-right: 25px;
position: relative;
&::after {
border-color: currentColor;
border-style: solid;
border-width: 2px 2px 0 0;
content: '';
display: block;
height: 5px;
right: 10px;
position: absolute;
top: 50%;
width: 5px;
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
margin-top: -3px;
}
}
}
@media screen and (min-width: 720px) {
.pi-collapse .pi-header:first-child {
padding-right: 25px;
position: relative;
&::after {
border-color: currentColor;
border-style: solid;
border-width: 2px 2px 0 0;
content: '';
display: block;
height: 5px;
right: 10px;
position: absolute;
top: 50%;
width: 5px;
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
margin-top: -3px;
}
&-closed {
border-bottom: none;
.pi-header:first-child::after {
transform: rotate(135deg);
-webkit-transform: rotate(135deg);
-moz-transform: rotate(135deg);
-ms-transform: rotate(135deg);
margin-top: -5px;
}
> :nth-child(n+2) {
}
.pi-collapse-closed {
.pi-header:first-child::after {
transform: rotate(135deg);
-webkit-transform: rotate(135deg);
-moz-transform: rotate(135deg);
-ms-transform: rotate(135deg);
margin-top: -5px;
}
> :nth-child(n+2) {
display: none;
}
.pi-horizontal-group {
thead {
display: none;
}
.pi-horizontal-group {
thead {
display: none;
}
tbody {
display: none;
}
tbody {
display: none;
}
}
}
}
@media screen and (max-width: 720px) {
.portable-infobox {
float: none;
width: initial;
font-size: initial;
margin: 0;
.skin-minerva & {
margin: 0 -16px;
}
.pi-title {
text-align: center;
}
}
}
.skin-minerva {
.pi-caption {
margin: var(--pi-item-spacing);
}
.pi-horizontal-group {
display: table;
margin: 0;
width: 100% !important;
.pi-header {
display: table-caption;
}
}
.pi-navigation {
box-sizing: border-box;
width: 100%;
float: none;
}
}
/* PI errors */
p.pi-error-info {
background-color: #f33;
color: #fff;

View file

@ -1,10 +1,6 @@
{{!
This template is linked with of mercury/front/templates/main/components/infobox-builder-item-data.hbs
Changing this template requires modifying the other in mercury repo.
}}
<div class="pi-item pi-data pi-item-spacing pi-border-color{{#cssClasses}} {{{cssClasses}}}{{/cssClasses}}"{{#inlineStyles}} style="{{inlineStyles}}"{{/inlineStyles}}>
{{#label}}
<h3 class="pi-data-label pi-secondary-font">{{{label}}}</h3>
{{/label}}
<div class="pi-data-value pi-font">{{{value}}}</div>
</div>
</div>

View file

@ -1,45 +0,0 @@
<div class="pi-item pi-hero">
{{#title}}
<hgroup class="pi-hero-title-wrapper pi-item-spacing">
<h2 class="pi-hero-title">{{{value}}}</h2>
{{#image.caption}}
<h3 class="pi-hero-caption">{{{image.caption}}}</h3>
{{/image.caption}}
</hgroup>
{{/title}}
{{#image}}
<figure data-attrs="{{dataAttrs}}" data-file="{{fileName}}">
<a href="{{url}}">
<img class="article-media-placeholder lazyload"
src="data:image/svg+xml;charset=utf-8,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' viewBox%3D'0 0 {{originalWidth}} {{originalHeight}}'%2F%3E"
data-src="{{thumbnail}}"
data-srcset="{{thumbnail}}, {{thumbnail2x}} 2x"
data-sizes="auto"
alt="{{title}}"
{{#originalWidth}} width="{{originalWidth}}"{{/originalWidth}}
{{#originalHeight}} height="{{originalHeight}}"{{/originalHeight}}/>
<noscript>
<img src="{{url}}" alt="{{alt}}"{{#originalWidth}} width="{{originalWidth}}"{{/originalWidth}}{{#originalHeight}} height="{{originalHeight}}"{{/originalHeight}}/>
</noscript>
{{#isVideo}}
<div class="video-icon-container">
<svg class="wds-player-icon-play-medium" viewBox="0 0 180 180">
<g fill="none" fill-rule="evenodd">
<g opacity=".9" transform="rotate(90 75 90)">
<g fill="#000" filter="url(#a)">
<rect id="b" width="150" height="150" rx="75"></rect>
</g>
<g fill="#FFF">
<rect id="b" width="150" height="150" rx="75"></rect>
</g>
</g>
<path fill="#00D6D6" fill-rule="nonzero"
d="M80.87 58.006l34.32 25.523c3.052 2.27 3.722 6.633 1.496 9.746a6.91 6.91 0 0 1-1.497 1.527l-34.32 25.523c-3.053 2.27-7.33 1.586-9.558-1.527A7.07 7.07 0 0 1 70 114.69V63.643c0-3.854 3.063-6.977 6.84-6.977 1.45 0 2.86.47 4.03 1.34z"></path>
</g>
</svg>
</div>
{{/isVideo}}
</a>
</figure>
{{/image}}
</div>

View file

@ -1,16 +0,0 @@
<div class="pi-item pi-hero">
{{#title}}
<hgroup class="pi-hero-title-wrapper pi-item-spacing">
<h2 class="pi-hero-title">{{{value}}}</h2>
{{#image.caption}}
<h3 class="pi-hero-caption">{{{image.caption}}}</h3>
{{/image.caption}}
</hgroup>
{{/title}}
{{#image}}
<img src="data:image/gif;base64,R0lGODlhAQABAIABAAAAAP///yH5BAEAAAEALAAAAAABAAEAQAICTAEAOw%3D%3D"
data-src="{{thumbnail}}" class="pi-image-thumbnail lazy media article-media" alt="{{alt}}"
data-image-key="{{name}}" data-image-name="{{name}}" data-ref="{{ref}}"
data-params='[{"name":"{{name}}", "full":"{{url}}"}]'/>
{{/image}}
</div>

View file

@ -1,7 +1,3 @@
{{!
This template is linked with of mercury/front/templates/main/components/infobox-builder-item-image.hbs
Changing this template requires modifying the other in mercury repo.
}}
<figure class="pi-item pi-image">
<a href="{{url}}" class="image image-thumbnail{{#isVideo}} video video-thumbnail{{/isVideo}}"
title="{{alt}}">
@ -22,4 +18,4 @@
{{/isVideo}}
</a>
{{#caption}}<figcaption class="pi-item-spacing pi-caption">{{{caption}}}</figcaption>{{/caption}}
</figure>
</figure>

View file

@ -1,51 +0,0 @@
<div class="pi-image-collection gallery"
data-attrs="{{dataAttrs}}">
{{#images}}
<figure class="pi-item pi-image" data-ref="{{dataRef}}" data-file="{{fileName}}">
<a href="{{url}}">
<img class="article-media-placeholder lazyload"
src="data:image/svg+xml;charset=utf-8,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' viewBox%3D'0 0 {{width}} {{height}}'%2F%3E"
alt="{{title}}"
data-srcset="{{thumbnail}}, {{thumbnail2x}} 2x"
data-srcset=""
{{#width}} width="{{width}}"{{/width}}
{{#height}} height="{{height}}"{{/height}} />
<noscript>
<img src="{{url}}" alt="{{alt}}"{{#width}} width="{{width}}"{{/width}}{{#height}} height="{{height}}"{{/height}} />
</noscript>
{{#isVideo}}
<div class="video-icon-container">
<svg class="wds-player-icon-play-medium" viewBox="0 0 180 180">
<g fill="none" fill-rule="evenodd">
<g opacity=".9" transform="rotate(90 75 90)">
<g fill="#000" filter="url(#a)">
<rect id="b" width="150" height="150" rx="75"></rect>
</g>
<g fill="#FFF">
<rect id="b" width="150" height="150" rx="75"></rect>
</g>
</g>
<path fill="#00D6D6" fill-rule="nonzero"
d="M80.87 58.006l34.32 25.523c3.052 2.27 3.722 6.633 1.496 9.746a6.91 6.91 0 0 1-1.497 1.527l-34.32 25.523c-3.053 2.27-7.33 1.586-9.558-1.527A7.07 7.07 0 0 1 70 114.69V63.643c0-3.854 3.063-6.977 6.84-6.977 1.45 0 2.86.47 4.03 1.34z"></path>
</g>
</svg>
</div>
{{/isVideo}}
</a>
{{#caption}}<figcaption class="pi-hero-title-wrapper pi-item-spacing"><h3 class="pi-hero-caption">{{{caption}}}</h3></figcaption>{{/caption}}
<div class="image-collection-actions">
{{^isFirst}}
<button class="action-previous">
{{{menuControlIcon}}}
</button>
{{/isFirst}}
{{^isLast}}
<button class="action-next">
{{{menuControlIcon}}}
</button>
{{/isLast}}
</div>
</figure>
{{/images}}
</div>

View file

@ -1,6 +0,0 @@
<div class="pi-item pi-hero">
<img src="data:image/gif;base64,R0lGODlhAQABAIABAAAAAP///yH5BAEAAAEALAAAAAABAAEAQAICTAEAOw%3D%3D"
data-src="{{thumbnail}}" class="pi-image-thumbnail lazy media article-media" alt="{{alt}}"
data-image-key="{{name}}" data-image-name="{{name}}" data-ref="{{ref}}"
data-params='[{"name":"{{name}}", "full":"{{url}}"}]'/>
</div>

View file

@ -1,39 +0,0 @@
<div class="pi-item pi-image">
<figure class="article-media-thumbnail" data-attrs="{{dataAttrs}}" data-file="{{fileName}}">
<a href="{{url}}">
<img class="article-media-placeholder lazyload"
src="data:image/svg+xml;charset=utf-8,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' viewBox%3D'0 0 {{originalWidth}} {{originalHeight}}'%2F%3E"
data-src="{{thumbnail}}"
data-srcset="{{thumbnail}}, {{thumbnail2x}} 2x"
data-sizes="auto"
alt="{{title}}"
{{#originalWidth}} width="{{originalWidth}}"{{/originalWidth}}
{{#originalHeight}} height="{{originalHeight}}"{{/originalHeight}} />
<noscript>
<img src="{{url}}" alt="{{alt}}"{{#originalWidth}}
width="{{originalWidth}}"{{/originalWidth}}{{#originalHeight}} height="{{originalHeight}}"{{/originalHeight}} />
</noscript>
{{#caption}}
<figcaption>{{caption}}</figcaption>
{{/caption}}
{{#isVideo}}
<div class="video-icon-container">
<svg class="wds-player-icon-play-medium" viewBox="0 0 180 180">
<g fill="none" fill-rule="evenodd">
<g opacity=".9" transform="rotate(90 75 90)">
<g fill="#000" filter="url(#a)">
<rect id="b" width="150" height="150" rx="75"></rect>
</g>
<g fill="#FFF">
<rect id="b" width="150" height="150" rx="75"></rect>
</g>
</g>
<path fill="#00D6D6" fill-rule="nonzero"
d="M80.87 58.006l34.32 25.523c3.052 2.27 3.722 6.633 1.496 9.746a6.91 6.91 0 0 1-1.497 1.527l-34.32 25.523c-3.053 2.27-7.33 1.586-9.558-1.527A7.07 7.07 0 0 1 70 114.69V63.643c0-3.854 3.063-6.977 6.84-6.977 1.45 0 2.86.47 4.03 1.34z"></path>
</g>
</svg>
</div>
{{/isVideo}}
</a>
</figure>
</div>

View file

@ -1,6 +0,0 @@
<div class="pi-item pi-image">
<img src="data:image/gif;base64,R0lGODlhAQABAIABAAAAAP///yH5BAEAAAEALAAAAAABAAEAQAICTAEAOw%3D%3D"
data-src="{{thumbnail}}" class="pi-image-thumbnail lazy media article-media" alt="{{alt}}"
data-image-key="{{name}}" data-image-name="{{name}}" data-ref="{{ref}}"
data-params='[{"name":"{{name}}", "full":"{{url}}"}]'/>
</div>

View file

@ -2,13 +2,13 @@
{{#renderLabels}}
<section class="pi-smart-group-head">
{{#labels}}
<h3 class="pi-smart-data-label pi-data-label pi-secondary-font pi-item-spacing"{{#inlineStyles}} style="{{{inlineStyles}}}"{{/inlineStyles}}>{{{value}}}</h3>
<h3 class="pi-smart-data-label pi-data-label pi-secondary-font pi-item-spacing" style="{{{inlineStyles}}}">{{{value}}}</h3>
{{/labels}}
</section>
{{/renderLabels}}
<section class="pi-smart-group-body">
{{#values}}
<div class="pi-smart-data-value pi-data-value pi-font pi-item-spacing"{{#inlineStyles}} style="{{{inlineStyles}}}"{{/inlineStyles}}>{{{value}}}</div>
<div class="pi-smart-data-value pi-data-value pi-font pi-item-spacing" style="{{{inlineStyles}}}">{{{value}}}</div>
{{/values}}
</section>
</section>

View file

@ -1,5 +1 @@
{{!
This template is linked with of mercury/front/templates/main/components/infobox-builder-item-title.hbs
Changing this template requires modifying the other in mercury repo.
}}
<h2 class="pi-item pi-item-spacing pi-title"{{#inlineStyles}} style="{{inlineStyles}}"{{/inlineStyles}}>{{{value}}}</h2>
<h2 class="pi-item pi-item-spacing pi-title"{{#inlineStyles}} style="{{inlineStyles}}"{{/inlineStyles}}>{{{value}}}</h2>

View file

@ -1,5 +1 @@
{{!
This template is linked with of mercury/front/templates/main/components/infobox-builder-item-wrapper.hbs
Changing this template requires modifying the other in mercury repo.
}}
<aside class="portable-infobox pi-background{{#isMercury}} pi{{/isMercury}}{{#theme}} {{theme}}{{/theme}}{{#layout}} {{layout}}{{/layout}}">{{{content}}}</aside>
<aside class="portable-infobox pi-background{{#theme}} {{theme}}{{/theme}}{{#layout}} {{layout}}{{/layout}}">{{{content}}}</aside>

View file

@ -1,853 +0,0 @@
<?php
class PortableInfoboxMobileRenderServiceTest extends WikiaBaseTest {
protected function setUp() {
$this->setupFile = dirname( __FILE__ ) . '/../PortableInfobox.setup.php';
parent::setUp();
if ( !extension_loaded( 'mustache' ) ) {
$this->markTestSkipped( '"mustache" PHP extension needs to be loaded!' );
}
}
private function mockInfoboxImagesHelper( $input ) {
$extendImageData = isset( $input['extendImageData'] ) ? $input['extendImageData'] : null;
$mock = $this->getMockBuilder( 'Wikia\PortableInfobox\Helpers\PortableInfoboxImagesHelper' )
->setMethods( [ 'extendImageData', 'getFileWidth' ] )
->getMock();
$mock->expects( $this->any() )
->method( 'extendImageData' )
->will( $this->returnValue( $extendImageData ) );
$mock->expects( $this->any() )
->method( 'getFileWidth' )
->will( $this->returnValue( $extendImageData['width'] ) );
$this->mockClass( 'Wikia\PortableInfobox\Helpers\PortableInfoboxImagesHelper', $mock );
}
/**
* @param $html
* @return string
*/
private function normalizeHTML( $html ) {
if ( empty( $html ) ) {
return '';
}
$config = [
'indent' => true,
'output-xhtml' => false
];
$tidy = new tidy();
$tidy->parseString($html, $config);
$tidy->cleanRepair();
return preg_replace( '/>\s+/', '>', (string) $tidy );
}
/**
* @dataProvider renderInfoboxDataProvider
*/
public function testRenderInfobox( $input, $expectedOutput, $description, $mockParams ) {
$wrapper = new \Wikia\Util\GlobalStateWrapper( [ 'wgArticleAsJson' => $mockParams['isMercury'] ?? true ] );
$this->mockInfoboxImagesHelper( $mockParams );
$infoboxRenderService = new PortableInfoboxMobileRenderService();
$actualOutput = $wrapper->wrap( function () use ( $infoboxRenderService, $input ) {
return $infoboxRenderService->renderInfobox( $input, '', '', '', '' );
} );
$expectedHtml = $this->normalizeHTML( $expectedOutput );
$actualHtml = $this->normalizeHTML( $actualOutput );
$this->assertEquals( $expectedHtml, $actualHtml, $description );
}
public function renderInfoboxDataProvider() {
return [
[
'input' => [ ],
'output' => '',
'description' => 'Empty data should yield no infobox markup',
'mockParams' => [ ]
],
[
'input' => [
[
'type' => 'title',
'data' => [
'value' => 'Test Title'
]
]
],
'output' => '<aside class="portable-infobox pi-background pi">
<h2 class="pi-item pi-item-spacing pi-title">Test Title</h2>
</aside>',
'description' => 'Only title',
'mockParams' => [ ]
],
[
'input' => [
[
'type' => 'navigation',
'data' => [
'value' => 'navigation value',
]
]
],
'output' => '<aside class="portable-infobox pi-background pi">
<nav class="pi-navigation pi-item-spacing pi-secondary-background pi-secondary-font">navigation value</nav>
</aside>',
'description' => 'navigation only',
'mockParams' => [ ]
],
[
'input' => [
[
'type' => 'data',
'data' => [
'label' => 'test label',
'value' => 'test value'
]
]
],
'output' => '<aside class="portable-infobox pi-background pi">
<div class="pi-item pi-data pi-item-spacing pi-border-color">
<h3 class="pi-data-label pi-secondary-font">test label</h3>
<div class="pi-data-value pi-font">test value</div>
</div>
</aside>',
'description' => 'Only pair',
'mockParams' => [ ]
],
[
'input' => [
[
'type' => 'title',
'data' => [
'value' => 'Test Title'
]
],
[
'type' => 'image',
'data' => [
[
'alt' => 'image alt',
'url' => 'http://image.jpg',
'name' => 'image',
'key' => 'image',
'isVideo' => false
]
]
],
[
'type' => 'data',
'data' => [
'label' => 'test label',
'value' => 'test value'
]
]
],
'output' => '<aside class="portable-infobox pi-background pi">
<div class="pi-item pi-hero">
<hgroup class="pi-hero-title-wrapper pi-item-spacing">
<h2 class="pi-hero-title">Test Title</h2>
</hgroup>
<figure data-attrs="" data-file="">
<a href="http://image.jpg">
<img class="article-media-placeholder lazyload" src="data:image/svg+xml;charset=utf-8,%3Csvg xmlns%3D\'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg\' viewBox%3D\'0 0 400 200\'%2F%3E" data-src="http://image.jpg" data-srcset="http://image.jpg, http://image2x.jpg 2x" data-sizes="auto" alt="" width="400" height="200"/><noscript>
<img src="http://image.jpg" alt="image alt" width="400" height="200"/>
</noscript>
</a>
</figure>
</div>
<div class="pi-item pi-data pi-item-spacing pi-border-color">
<h3 class="pi-data-label pi-secondary-font">test label</h3>
<div class="pi-data-value pi-font">test value</div>
</div>
</aside>',
'description' => 'Simple infobox with title, image and key-value pair',
'mockParams' => [
'extendImageData' => [
'alt' => 'image alt',
'url' => 'http://image.jpg',
'name' => 'test1',
'key' => 'test1',
'ref' => 1,
'width' => '400',
'height' => '200',
'originalWidth' => '400',
'originalHeight' => '200',
'thumbnail' => 'http://image.jpg',
'thumbnail2x' => 'http://image2x.jpg',
'media-type' => 'image',
'isVideo' => false
]
]
],
[
'input' => [
[
'type' => 'title',
'data' => [
'value' => 'Test Title'
]
],
[
'type' => 'image',
'data' => [ ]
],
[
'type' => 'data',
'data' => [
'label' => 'test label',
'value' => 'test value'
]
]
],
'output' => '<aside class="portable-infobox pi-background pi">
<h2 class="pi-item pi-item-spacing pi-title">Test Title</h2>
<div class="pi-item pi-data pi-item-spacing pi-border-color">
<h3 class="pi-data-label pi-secondary-font">test label</h3>
<div class="pi-data-value pi-font">test value</div>
</div>
</aside>',
'description' => 'Simple infobox with title, INVALID image and key-value pair',
'mockParams' => [ ]
],
[
'input' => [
[
'type' => 'title',
'data' => [
'value' => 'Test Title'
]
],
[
'type' => 'data',
'data' => [
'label' => 'test label',
'value' => 'test value'
]
]
],
'output' => '<aside class="portable-infobox pi-background pi">
<h2 class="pi-item pi-item-spacing pi-title">Test Title</h2>
<div class="pi-item pi-data pi-item-spacing pi-border-color">
<h3 class="pi-data-label pi-secondary-font">test label</h3>
<div class="pi-data-value pi-font">test value</div>
</div>
</aside>',
'description' => 'Simple infobox with title, empty image and key-value pair',
'mockParams' => [ ]
],
[
'input' => [
[
'type' => 'title',
'data' => [
'value' => 'Test Title'
]
],
[
'type' => 'group',
'data' => [
'value' => [
[
'type' => 'header',
'data' => [
'value' => 'Test Header'
]
],
[
'type' => 'data',
'data' => [
'label' => 'test label',
'value' => 'test value'
]
],
[
'type' => 'data',
'data' => [
'label' => 'test label',
'value' => 'test value'
]
]
],
'layout' => 'default',
'collapse' => null,
'row-items' => null
]
]
],
'output' => '<aside class="portable-infobox pi-background pi">
<h2 class="pi-item pi-item-spacing pi-title">Test Title</h2>
<section class="pi-item pi-group pi-border-color">
<h2 class="pi-item pi-header pi-secondary-font pi-item-spacing pi-secondary-background">Test Header</h2>
<div class="pi-item pi-data pi-item-spacing pi-border-color">
<h3 class="pi-data-label pi-secondary-font">test label</h3>
<div class="pi-data-value pi-font">test value</div>
</div>
<div class="pi-item pi-data pi-item-spacing pi-border-color">
<h3 class="pi-data-label pi-secondary-font">test label</h3>
<div class="pi-data-value pi-font">test value</div>
</div>
</section>
</aside>',
'description' => 'Infobox with title, group with header and two key-value pairs',
'mockParams' => [ ]
],
[
'input' => [
[
'type' => 'group',
'data' => [
'value' => [
[
'type' => 'header',
'data' => [
'value' => 'Test header'
]
],
[
'type' => 'data',
'data' => [
'label' => 'test label',
'value' => 'test value'
]
],
[
'type' => 'data',
'data' => [
'label' => 'test label',
'value' => 'test value'
]
]
],
'layout' => 'horizontal',
'collapse' => null,
'row-items' => null
]
]
],
'output' => '<aside class="portable-infobox pi-background pi">
<section class="pi-item pi-group pi-border-color">
<table class="pi-horizontal-group">
<caption
class="pi-header pi-secondary-font pi-secondary-background pi-item-spacing">Test header</caption>
<thead>
<tr>
<th
class="pi-horizontal-group-item pi-data-label pi-secondary-font pi-border-color pi-item-spacing">test label</th>
<th
class="pi-horizontal-group-item pi-data-label pi-secondary-font pi-border-color pi-item-spacing">test label</th>
</tr>
</thead>
<tbody>
<tr>
<td
class="pi-horizontal-group-item pi-data-value pi-font pi-border-color pi-item-spacing">test value</td>
<td
class="pi-horizontal-group-item pi-data-value pi-font pi-border-color pi-item-spacing">test value</td>
</tr>
</tbody>
</table>
</section>
</aside>',
'description' => 'Infobox with horizontal group',
'mockParams' => [
'createHorizontalGroupData' => [
'header' => 'Test header',
'labels' => [ 'test label', 'test label' ],
'values' => [ 'test value', 'test value' ],
'renderLabels' => true
]
]
],
[
'input' => [
[
'type' => 'group',
'data' => [
'value' => [
[
'type' => 'data',
'data' => [
'label' => '',
'value' => 'test value'
]
],
[
'type' => 'data',
'data' => [
'label' => '',
'value' => 'test value'
]
]
],
'layout' => 'horizontal',
'collapse' => null,
'row-items' => null
]
]
],
'output' => '<aside class="portable-infobox pi-background pi">
<section class="pi-item pi-group pi-border-color">
<table class="pi-horizontal-group pi-horizontal-group-no-labels">
<tbody>
<tr>
<td
class="pi-horizontal-group-item pi-data-value pi-font pi-border-color pi-item-spacing">test value</td>
<td
class="pi-horizontal-group-item pi-data-value pi-font pi-border-color pi-item-spacing">test value</td>
</tr>
</tbody>
</table>
</section>
</aside>',
'description' => 'Infobox with horizontal group without header and labels',
'mockParams' => [
'createHorizontalGroupData' => [
'labels' => [ '', '' ],
'values' => [ 'test value', 'test value' ],
'renderLabels' => false
]
]
],
[
'input' => [
[
'type' => 'navigation',
'data' => [
'value' => '<p>Links</p>'
]
]
],
'output' => '<aside class="portable-infobox pi-background pi">
<nav class="pi-navigation pi-item-spacing pi-secondary-background pi-secondary-font">
<p>Links</p>
</nav>
</aside>',
'description' => 'Infobox with navigation',
'mockParams' => [ ]
],
[
'input' => [
[
'type' => 'image',
'data' => [
[
'alt' => 'image alt',
'url' => 'http://image.jpg',
'ref' => 1,
'name' => 'test1',
'key' => 'test1',
'isVideo' => false
]
]
]
],
'output' => '<aside class="portable-infobox pi-background">
<div class="pi-item pi-hero">
<img
src="data:image/gif;base64,R0lGODlhAQABAIABAAAAAP///yH5BAEAAAEALAAAAAABAAEAQAICTAEAOw%3D%3D" data-src="http://image.jpg" class="pi-image-thumbnail lazy media article-media" alt="image alt" data-image-key="test1" data-image-name="test1" data-ref="1" data-params=\'[{"name":"test1", "full":"http://image.jpg"}]\' />
</div>
</aside>',
'description' => 'WikiaMobile: Only image. Image is not small- should render hero.',
'mockParams' => [
'isMercury' => false,
'extendImageData' => [
'alt' => 'image alt',
'url' => 'http://image.jpg',
'name' => 'test1',
'key' => 'test1',
'ref' => 1,
'width' => '400',
'height' => '200',
'originalWidth' => '400',
'originalHeight' => '200',
'thumbnail' => 'http://image.jpg',
'thumbnail2x' => 'http://image2x.jpg',
'media-type' => 'image',
'isVideo' => false
]
]
],
[
'input' => [
[
'type' => 'title',
'data' => [
'value' => 'Test <img /><a href="example.com">Title</a>'
]
],
[
'type' => 'image',
'data' => [
[
'url' => 'http://image.jpg',
'name' => 'test1',
'key' => 'test1',
'ref' => 44,
'isVideo' => false
]
]
]
],
'output' => '<aside class="portable-infobox pi-background">
<div class="pi-item pi-hero">
<hgroup class="pi-hero-title-wrapper pi-item-spacing">
<h2 class="pi-hero-title">Test <a href="example.com">Title</a></h2>
</hgroup>
<img
src="data:image/gif;base64,R0lGODlhAQABAIABAAAAAP///yH5BAEAAAEALAAAAAABAAEAQAICTAEAOw%3D%3D" data-src="thumbnail.jpg" class="pi-image-thumbnail lazy media article-media" alt="" data-image-key="test1" data-image-name="test1" data-ref="44" data-params=\'[{"name":"test1", "full":"http://image.jpg"}]\'/>
</div>
</aside>',
'description' => 'WikiaMobile: Infobox with full hero module with title with HTML tags',
'mockParams' => [
'isMercury' => false,
'extendImageData' => [
'url' => 'http://image.jpg',
'name' => 'test1',
'key' => 'test1',
'ref' => 44,
'width' => '400',
'height' => '200',
'originalWidth' => '400',
'originalHeight' => '200',
'thumbnail' => 'thumbnail.jpg',
'thumbnail2x' => 'thumbnail2x.jpg',
'isVideo' => false,
'media-type' => 'image'
]
]
],
[
'input' => [
[
'type' => 'image',
'data' => [
[
'alt' => 'image alt',
'url' => 'http://image.jpg',
'ref' => 1,
'name' => 'test1',
'key' => 'test1',
'isVideo' => false
]
]
]
],
'output' => '<aside class="portable-infobox pi-background pi">
<div class="pi-item pi-hero">
<figure data-attrs="" data-file="">
<a href="http://image.jpg">
<img class="article-media-placeholder lazyload" src="data:image/svg+xml;charset=utf-8,%3Csvg xmlns%3D\'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg\' viewBox%3D\'0 0 400 200\'%2F%3E" data-src="http://image.jpg" data-srcset="http://image.jpg, http://image2x.jpg 2x" data-sizes="auto" alt="" width="400" height="200"/><noscript>
<img src="http://image.jpg" alt="image alt" width="400" height="200"/>
</noscript>
</a>
</figure>
</div>
</aside>',
'description' => 'Mercury: Only image. Image is not small- should render hero.',
'mockParams' => [
'extendImageData' => [
'alt' => 'image alt',
'url' => 'http://image.jpg',
'name' => 'test1',
'key' => 'test1',
'ref' => 1,
'width' => '400',
'height' => '200',
'originalWidth' => '400',
'originalHeight' => '200',
'thumbnail' => 'http://image.jpg',
'thumbnail2x' => 'http://image2x.jpg',
'media-type' => 'image',
'isVideo' => false,
'mercuryComponentAttrs' => json_encode( [
'itemContext' => 'portable-infobox',
'ref' => 1
] )
]
]
],
[
'input' => [
[
'type' => 'title',
'data' => [
'value' => 'Test <img /><a href="example.com">Title</a>'
]
],
[
'type' => 'image',
'data' => [
[
'url' => 'http://image.jpg',
'name' => 'test1',
'key' => 'test1',
'ref' => 44,
'isVideo' => false
]
]
]
],
'output' => '<aside class="portable-infobox pi-background pi">
<div class="pi-item pi-hero">
<hgroup class="pi-hero-title-wrapper pi-item-spacing">
<h2 class="pi-hero-title">Test <a href="example.com">Title</a></h2>
</hgroup>
<figure data-attrs="" data-file="">
<a href="http://image.jpg">
<img class="article-media-placeholder lazyload" src="data:image/svg+xml;charset=utf-8,%3Csvg xmlns%3D\'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg\' viewBox%3D\'0 0 400 200\'%2F%3E" data-src="thumbnail.jpg" data-srcset="thumbnail.jpg, thumbnail2x.jpg 2x" data-sizes="auto" alt="" width="400" height="200"/><noscript>
<img src="http://image.jpg" alt="" width="400" height="200"/>
</noscript>
</a>
</figure>
</div>
</aside>',
'description' => 'Mercury: Infobox with full hero module with title with HTML tags',
'mockParams' => [
'extendImageData' => [
'url' => 'http://image.jpg',
'name' => 'test1',
'key' => 'test1',
'ref' => 44,
'width' => '400',
'height' => '200',
'originalWidth' => '400',
'originalHeight' => '200',
'thumbnail' => 'thumbnail.jpg',
'thumbnail2x' => 'thumbnail2x.jpg',
'isVideo' => false,
'media-type' => 'image',
'mercuryComponentAttrs' => json_encode( [
'itemContext' => 'portable-infobox',
'ref' => 44
] )
]
]
],
[
'input' => [
[
'type' => 'data',
'data' => [
'label' => 'Test 1',
'value' => 'test value 1'
]
],
[
'type' => 'title',
'data' => [
'value' => 'Test <img /><a href="example.com">Title</a>'
]
],
[
'type' => 'group',
'data' => [
'value' => [
[
'type' => 'header',
'data' => [
'value' => 'Test Header'
]
],
[
'type' => 'data',
'data' => [
'label' => 'Test 2',
'value' => 'test value 2'
]
],
[
'type' => 'image',
'data' => [
[
'url' => 'http://image.jpg',
'name' => 'test1',
'key' => 'test1',
'ref' => 44,
'isVideo' => false
]
]
],
[
'type' => 'data',
'data' => [
'label' => 'Test 3',
'value' => 'test value 3'
]
]
],
'layout' => null,
'collapse' => null,
'row-items' => null
]
]
],
'output' => '<aside class="portable-infobox pi-background pi">
<div class="pi-item pi-hero">
<hgroup class="pi-hero-title-wrapper pi-item-spacing">
<h2 class="pi-hero-title">Test <a href="example.com">Title</a></h2>
</hgroup>
<figure data-attrs="" data-file="">
<a href="http://image.jpg">
<img class="article-media-placeholder lazyload" src="data:image/svg+xml;charset=utf-8,%3Csvg xmlns%3D\'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg\' viewBox%3D\'0 0 400 200\'%2F%3E" data-src="thumbnail.jpg" data-srcset="thumbnail.jpg, thumbnail2x.jpg 2x" data-sizes="auto" alt="" width="400" height="200"/><noscript>
<img src="http://image.jpg" alt="" width="400" height="200"/>
</noscript>
</a>
</figure>
</div>
<div class="pi-item pi-data pi-item-spacing pi-border-color">
<h3 class="pi-data-label pi-secondary-font">Test 1</h3>
<div class="pi-data-value pi-font">test value 1</div>
</div>
<section class="pi-item pi-group pi-border-color">
<h2 class="pi-item pi-header pi-secondary-font pi-item-spacing pi-secondary-background">Test Header</h2>
<div class="pi-item pi-data pi-item-spacing pi-border-color">
<h3 class="pi-data-label pi-secondary-font">Test 2</h3>
<div class="pi-data-value pi-font">test value 2</div>
</div>
<div class="pi-item pi-data pi-item-spacing pi-border-color">
<h3 class="pi-data-label pi-secondary-font">Test 3</h3>
<div class="pi-data-value pi-font">test value 3</div>
</div>
</section>
</aside>',
'description' => 'Mercury: Infobox with valid hero data partially nested in group',
'mockParams' => [
'extendImageData' => [
'url' => 'http://image.jpg',
'name' => 'test1',
'key' => 'test1',
'ref' => 44,
'width' => '400',
'height' => '200',
'originalWidth' => '400',
'originalHeight' => '200',
'thumbnail' => 'thumbnail.jpg',
'thumbnail2x' => 'thumbnail2x.jpg',
'isVideo' => false,
'media-type' => 'image',
'mercuryComponentAttrs' => json_encode( [
'itemContext' => 'portable-infobox',
'ref' => 44
] )
]
]
],
[
'input' => [
[
'type' => 'data',
'data' => [
'label' => 'Test 1',
'value' => 'test value 1'
]
],
[
'type' => 'title',
'data' => [
'value' => 'Test <img /><a href="example.com">Title</a>'
]
],
[
'type' => 'group',
'data' => [
'value' => [
[
'type' => 'header',
'data' => [
'value' => 'Test Header'
]
],
[
'type' => 'data',
'data' => [
'label' => 'Test 2',
'value' => 'test value 2'
]
],
[
'type' => 'image',
'data' => [
[
'url' => 'http://image.jpg',
'name' => 'test1',
'key' => 'test1',
'ref' => 44,
'isVideo' => false
]
]
],
[
'type' => 'data',
'data' => [
'label' => 'Test 3',
'value' => 'test value 3'
]
]
],
'layout' => null,
'collapse' => null,
'row-items' => null
]
]
],
'output' => '<aside class="portable-infobox pi-background pi">
<div class="pi-item pi-hero">
<hgroup class="pi-hero-title-wrapper pi-item-spacing">
<h2 class="pi-hero-title">Test <a href="example.com">Title</a></h2>
</hgroup>
<figure data-attrs="" data-file="">
<a href="http://image.jpg">
<img class="article-media-placeholder lazyload" src="data:image/svg+xml;charset=utf-8,%3Csvg xmlns%3D\'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg\' viewBox%3D\'0 0 400 200\'%2F%3E" data-src="thumbnail.jpg" data-srcset="thumbnail.jpg, thumbnail2x.jpg 2x" data-sizes="auto" alt="" width="400" height="200"/><noscript>
<img src="http://image.jpg" alt="" width="400" height="200"/>
</noscript>
</a>
</figure>
</div>
<div class="pi-item pi-data pi-item-spacing pi-border-color">
<h3 class="pi-data-label pi-secondary-font">Test 1</h3>
<div class="pi-data-value pi-font">test value 1</div>
</div>
<section class="pi-item pi-group pi-border-color">
<h2 class="pi-item pi-header pi-secondary-font pi-item-spacing pi-secondary-background">Test Header</h2>
<div class="pi-item pi-data pi-item-spacing pi-border-color">
<h3 class="pi-data-label pi-secondary-font">Test 2</h3>
<div class="pi-data-value pi-font">test value 2</div>
</div>
<div class="pi-item pi-data pi-item-spacing pi-border-color">
<h3 class="pi-data-label pi-secondary-font">Test 3</h3>
<div class="pi-data-value pi-font">test value 3</div>
</div>
</section>
</aside>',
'description' => 'Mercury: Infobox with invalid hero data partially nested in group',
'mockParams' => [
'extendImageData' => [
'url' => 'http://image.jpg',
'name' => 'test1',
'key' => 'test1',
'ref' => 44,
'width' => '200',
'height' => '200',
'originalWidth' => '400',
'originalHeight' => '200',
'thumbnail' => 'thumbnail.jpg',
'thumbnail2x' => 'thumbnail2x.jpg',
'isVideo' => false,
'media-type' => 'image',
'mercuryComponentAttrs' => json_encode( [
'itemContext' => 'portable-infobox',
'ref' => 44
] )
]
]
]
];
}
}

View file

@ -1,143 +0,0 @@
<?php
class NodeHeroImageSanitizerTest extends WikiaBaseTest {
/** @var NodeHeroImageSanitizer $sanitizer */
private $sanitizer;
protected function setUp() {
$this->setupFile = dirname( __FILE__ ) . '/../../PortableInfobox.setup.php';
$this->sanitizer = SanitizerBuilder::createFromType('hero-mobile');
parent::setUp();
}
/**
* @param $data
* @param $expected
* @dataProvider sanitizeDataProvider
*/
public function testSanitize( $data, $expected ) {
$this->assertEquals(
$expected,
$this->sanitizer->sanitize( $data )
);
}
public function sanitizeDataProvider() {
return [
[
[ 'title' => [ 'value' => 'Test Title' ] ],
[ 'title' => [ 'value' => 'Test Title' ] ]
],
[
[ 'title' => [ 'value' => 'Real world <a href="http://vignette-poz.wikia-dev.com/mediawiki116/images/b/b6/DBGT_Logo.svg/revision/latest?cb=20150601155347" class="image image-thumbnail" ><img src="http://vignette-poz.wikia-dev.com/mediawiki116/images/b/b6/DBGT_Logo.svg/revision/latest/scale-to-width-down/30?cb=20150601155347" alt="DBGT Logo" class="" data-image-key="DBGT_Logo.svg" data-image-name="DBGT Logo.svg" width="30" height="18" ></a>title example' ] ] ,
[ 'title' => [ 'value' => 'Real world title example' ] ]
],
[
[ 'title' => [ 'value' => 'Test <a>Title with</a> <span><small>small</small></span> tag, span tag and <img src="sfefes"/>tag' ] ],
[ 'title' => [ 'value' => 'Test <a>Title with</a> small tag, span tag and tag' ] ]
],
[
[ 'title' => [ 'value' => '<a href="http://vignette-poz.wikia-dev.com//images/9/95/All_Stats_%2B2.png/revision/latest?cb=20151222111955" class="image image-thumbnail"><img src="abc" alt="All Stats +2" class="thumbimage" /></a>' ] ],
[ 'title' => [ 'value' => '' ] ],
],
[
[ 'title' => [ 'value' => '<figure class="article-thumb tright show-info-icon" style="width: 335px"> <a href="http://mediawiki119.marzjan.wikia-dev.com/wiki/File:AMERICA%27S_TEST_KITCHEN_SEASON_9" class="video video-thumbnail image lightbox medium " itemprop=\'video\' itemscope itemtype="http://schema.org/VideoObject" ><img src="http://vignette-poz.wikia-dev.com//images/6/6e/AMERICA%27S_TEST_KITCHEN_SEASON_9/revision/latest/scale-to-width-down/335?cb=20130904003328" alt="AMERICA&#039;S TEST KITCHEN SEASON 9" class="thumbimage " data-video-key="AMERICA&#039;S_TEST_KITCHEN_SEASON_9" data-video-name="AMERICA&#039;S TEST KITCHEN SEASON 9" width="335" height="187" itemprop="thumbnail" ></span><meta itemprop="duration" content="PT01M00S"></a> <figcaption> <a href="/wiki/File:AMERICA%27S_TEST_KITCHEN_SEASON_9" class="sprite info-icon"></a> <p class="title">AMERICA&#039;S TEST KITCHEN SEASON 9</p> </figcaption> </figure>' ] ],
[ 'title' => [ 'value' => 'AMERICA&#039;S TEST KITCHEN SEASON 9' ] ]
],
[
[ 'title' => [ 'value' => '<sup id="cite_ref-0" class="reference"><a href="#cite_note-0">[1]</a></sup>' ] ],
[ 'title' => [ 'value' => '<sup id="cite_ref-0" class="reference"><a href="#cite_note-0">[1]</a></sup>' ] ]
],
[
[ 'title' => [ 'value' => '<script>JSSnippetsStack.push({dependencies:[{"url":"http://i3.marzjan.wikia-dev.com/__am/1451462348/group/-/wikia_photo_gallery_js","type":"js"},{"url":"http://i2.marzjan.wikia-dev.com/__am/1451462348/sass/background-dynamic%3D1%26background-image%3D%26background-image-height%3D1185%26background-image-width%3D1600%26color-body%3D%2523bacdd8%26color-body-middle%3D%2523bacdd8%26color-buttons%3D%2523006cb0%26color-header%3D%25233a5766%26color-links%3D%2523006cb0%26color-page%3D%2523ffffff%26oasisTypography%3D1%26page-opacity%3D100%26widthType%3D0/extensions/wikia/WikiaPhotoGallery/css/gallery.scss","type":"css"}],callback:function(json){WikiaPhotoGalleryView.init(json)},id:"WikiaPhotoGalleryView.init"})</script>' ] ],
[ 'title' => [ 'value' => '' ] ]
],
[
[ 'title' => [ 'value' => '<ul><li>1
</li><li>2
</li><li>3
</li></ul>' ] ],
[ 'title' => [ 'value' => '1 2 3' ] ]
],
[
[ 'title' => [ 'value' => '<ol><li>1
</li><li>2
<ol><li>2.1
</li></ol>
</li></ol>' ] ],
[ 'title' => [ 'value' => '1 2 2.1' ] ]
],
[
[ 'title' => [ 'value' => 'Próxima' ] ],
[ 'title' => [ 'value' => 'Próxima' ] ]
],
[
[ 'title' => [ 'value' => 'Música de' ] ],
[ 'title' => [ 'value' => 'Música de' ] ]
],
[
[ 'title' => [ 'value' => 'A <b>Kuruma</b> in <i><a href="/wiki/Grand_Theft_Auto_Online" title="Grand Theft Auto Online">Grand Theft Auto Online</a></i>.' ] ],
[ 'title' => [ 'value' => 'A Kuruma in <a href="/wiki/Grand_Theft_Auto_Online" title="Grand Theft Auto Online">Grand Theft Auto Online</a>.' ] ]
],
[
[ 'title' => [ 'value' => '<a href="/wiki/User:Idradm" class="new" title="User:Idradm (page does not exist)">Idradm</a> (<a href="/wiki/User_talk:Idradm" title="User talk:Idradm (page does not exist)">talk</a>) 15:34, January 4, 2016 (UTC)' ] ],
[ 'title' => [ 'value' => '<a href="/wiki/User:Idradm" class="new" title="User:Idradm (page does not exist)">Idradm</a> (<a href="/wiki/User_talk:Idradm" title="User talk:Idradm (page does not exist)">talk</a>) 15:34, January 4, 2016 (UTC)' ] ]
],
[
[ 'image' => [ 'caption' => 'Test Title' ] ],
[ 'image' => [ 'caption' => 'Test Title' ] ]
],
[
[ 'image' => [ 'caption' => 'Real world <a href="http://vignette-poz.wikia-dev.com/mediawiki116/images/b/b6/DBGT_Logo.svg/revision/latest?cb=20150601155347" class="image image-thumbnail" ><img src="http://vignette-poz.wikia-dev.com/mediawiki116/images/b/b6/DBGT_Logo.svg/revision/latest/scale-to-width-down/30?cb=20150601155347" alt="DBGT Logo" class="" data-image-key="DBGT_Logo.svg" data-image-name="DBGT Logo.svg" width="30" height="18" ></a>title example' ] ] ,
[ 'image' => [ 'caption' => 'Real world title example' ] ]
],
[
[ 'image' => [ 'caption' => 'Test <a>Title with</a> <span><small>small</small></span> tag, span tag and <img src="sfefes"/>tag' ] ],
[ 'image' => [ 'caption' => 'Test <a>Title with</a> small tag, span tag and tag' ] ]
],
[
[ 'image' => [ 'caption' => '<a href="http://vignette-poz.wikia-dev.com//images/9/95/All_Stats_%2B2.png/revision/latest?cb=20151222111955" class="image image-thumbnail"><img src="abc" alt="All Stats +2" class="thumbimage" /></a>' ] ],
[ 'image' => [ 'caption' => '' ] ],
],
[
[ 'image' => [ 'caption' => '<figure class="article-thumb tright show-info-icon" style="width: 335px"> <a href="http://mediawiki119.marzjan.wikia-dev.com/wiki/File:AMERICA%27S_TEST_KITCHEN_SEASON_9" class="video video-thumbnail image lightbox medium " itemprop=\'video\' itemscope itemtype="http://schema.org/VideoObject" ><img src="http://vignette-poz.wikia-dev.com//images/6/6e/AMERICA%27S_TEST_KITCHEN_SEASON_9/revision/latest/scale-to-width-down/335?cb=20130904003328" alt="AMERICA&#039;S TEST KITCHEN SEASON 9" class="thumbimage " data-video-key="AMERICA&#039;S_TEST_KITCHEN_SEASON_9" data-video-name="AMERICA&#039;S TEST KITCHEN SEASON 9" width="335" height="187" itemprop="thumbnail" ></span><meta itemprop="duration" content="PT01M00S"></a> <figcaption> <a href="/wiki/File:AMERICA%27S_TEST_KITCHEN_SEASON_9" class="sprite info-icon"></a> <p class="title">AMERICA&#039;S TEST KITCHEN SEASON 9</p> </figcaption> </figure>' ] ],
[ 'image' => [ 'caption' => 'AMERICA&#039;S TEST KITCHEN SEASON 9' ] ]
],
[
[ 'image' => [ 'caption' => '<sup id="cite_ref-0" class="reference"><a href="#cite_note-0">[1]</a></sup>' ] ],
[ 'image' => [ 'caption' => '<sup id="cite_ref-0" class="reference"><a href="#cite_note-0">[1]</a></sup>' ] ]
],
[
[ 'image' => [ 'caption' => '<script>JSSnippetsStack.push({dependencies:[{"url":"http://i3.marzjan.wikia-dev.com/__am/1451462348/group/-/wikia_photo_gallery_js","type":"js"},{"url":"http://i2.marzjan.wikia-dev.com/__am/1451462348/sass/background-dynamic%3D1%26background-image%3D%26background-image-height%3D1185%26background-image-width%3D1600%26color-body%3D%2523bacdd8%26color-body-middle%3D%2523bacdd8%26color-buttons%3D%2523006cb0%26color-header%3D%25233a5766%26color-links%3D%2523006cb0%26color-page%3D%2523ffffff%26oasisTypography%3D1%26page-opacity%3D100%26widthType%3D0/extensions/wikia/WikiaPhotoGallery/css/gallery.scss","type":"css"}],callback:function(json){WikiaPhotoGalleryView.init(json)},id:"WikiaPhotoGalleryView.init"})</script>' ] ],
[ 'image' => [ 'caption' => '' ] ]
],
[
[ 'image' => [ 'caption' => '<ul><li>1</li><li>2</li><li>3</li></ul>' ] ],
[ 'image' => [ 'caption' => '1 2 3' ] ]
],
[
[ 'image' => [ 'caption' => '<ol><li>1</li><li>2<ol><li>2.1</li></ol></li></ol>' ] ],
[ 'image' => [ 'caption' => '1 2 2.1' ] ]
],
[
[ 'image' => [ 'caption' => 'Próxima' ] ],
[ 'image' => [ 'caption' => 'Próxima' ] ]
],
[
[ 'image' => [ 'caption' => 'Música de' ] ],
[ 'image' => [ 'caption' => 'Música de' ] ]
],
[
[ 'image' => [ 'caption' => 'A <b>Kuruma</b> in <i><a href="/wiki/Grand_Theft_Auto_Online" title="Grand Theft Auto Online">Grand Theft Auto Online</a></i>.' ] ],
[ 'image' => [ 'caption' => 'A Kuruma in <a href="/wiki/Grand_Theft_Auto_Online" title="Grand Theft Auto Online">Grand Theft Auto Online</a>.' ] ]
],
[
[ 'image' => [ 'caption' => '<a href="/wiki/User:Idradm" class="new" title="User:Idradm (page does not exist)">Idradm</a> (<a href="/wiki/User_talk:Idradm" title="User talk:Idradm (page does not exist)">talk</a>) 15:34, January 4, 2016 (UTC)' ] ],
[ 'image' => [ 'caption' => '<a href="/wiki/User:Idradm" class="new" title="User:Idradm (page does not exist)">Idradm</a> (<a href="/wiki/User_talk:Idradm" title="User talk:Idradm (page does not exist)">talk</a>) 15:34, January 4, 2016 (UTC)' ] ]
]
];
}
}