DAT-3621 Unified tests for hero image and regular caption

* Disable sanitization on Oasis
* Handle previously unsupported case of nested tag ( <b><a></a></b> )
* Unify test formatting
* Change list handling (yes, I am not proud of that)
This commit is contained in:
Sebastian Marzjan 2016-01-09 16:01:13 +01:00
parent e68ce57c7a
commit 168bf09348
7 changed files with 166 additions and 26 deletions

View file

@ -173,7 +173,6 @@ class PortableInfoboxRenderService extends WikiaService {
for ( $i = 0; $i < count($data); $i++ ) { for ( $i = 0; $i < count($data); $i++ ) {
$data[$i][ 'context' ] = self::MEDIA_CONTEXT_INFOBOX; $data[$i][ 'context' ] = self::MEDIA_CONTEXT_INFOBOX;
$data[$i] = $helper->extendImageData( $data[$i] ); $data[$i] = $helper->extendImageData( $data[$i] );
$data[$i] = SanitizerBuilder::createFromType( $type )->sanitize( $data[$i] );
if ( !!$data[$i] ) { if ( !!$data[$i] ) {
$images[] = $data[$i]; $images[] = $data[$i];
@ -198,6 +197,9 @@ class PortableInfoboxRenderService extends WikiaService {
$templateName = $type; $templateName = $type;
} }
/**
* Currently, based on business decision, sanitization happens ONLY on Mercury
*/
if ( $helper->isWikiaMobile() ) { if ( $helper->isWikiaMobile() ) {
$data = SanitizerBuilder::createFromType( $type )->sanitize( $data ); $data = SanitizerBuilder::createFromType( $type )->sanitize( $data );
} }

View file

@ -1,6 +1,11 @@
<?php <?php
class NodeHeroImageSanitizer extends NodeSanitizer { class NodeHeroImageSanitizer extends NodeSanitizer {
protected $allowedTags = [ 'a' ];
protected $selectorsWrappingTextToPad = [ 'li' ];
protected $selectorsWrappingAllowedFeatures = [ 'sup[@class="reference"]' ];
protected $selectorsForFullRemoval = [ 'script', 'span[@itemprop="duration"]' ];
/** /**
* @param $data * @param $data
* @return mixed * @return mixed
@ -9,6 +14,9 @@ class NodeHeroImageSanitizer extends NodeSanitizer {
if ( !empty( $data[ 'title' ][ 'value' ] ) ) { if ( !empty( $data[ 'title' ][ 'value' ] ) ) {
$data[ 'title' ][ 'value' ] = $this->sanitizeElementData( $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; return $data;
} }

View file

@ -2,6 +2,7 @@
class NodeImageSanitizer extends NodeSanitizer { class NodeImageSanitizer extends NodeSanitizer {
protected $allowedTags = [ 'a' ]; protected $allowedTags = [ 'a' ];
protected $selectorsWrappingTextToPad = [ 'li' ];
protected $selectorsWrappingAllowedFeatures = [ 'sup[@class="reference"]' ]; protected $selectorsWrappingAllowedFeatures = [ 'sup[@class="reference"]' ];
protected $selectorsForFullRemoval = [ 'script', 'span[@itemprop="duration"]' ]; protected $selectorsForFullRemoval = [ 'script', 'span[@itemprop="duration"]' ];

View file

@ -9,9 +9,15 @@ abstract class NodeSanitizer implements NodeTypeSanitizerInterface {
* Sanitizer configuration * Sanitizer configuration
* Can be overridden by child classes * Can be overridden by child classes
*/ */
// these are selectors for explicitly allowed tags, like 'a'
protected $allowedTags = [ ]; protected $allowedTags = [ ];
// these are valid 'internal' node names (per libxml convention, i.e. a, #text, etc)
protected $validNodeNames = [ '#text' ]; protected $validNodeNames = [ '#text' ];
// these are selectors that describe nodes containing text that should be padded with whitespace
protected $selectorsWrappingTextToPad = [ ];
// these are selectors that describe root nodes of known features that should not be sanitized
protected $selectorsWrappingAllowedFeatures = [ ]; protected $selectorsWrappingAllowedFeatures = [ ];
// these are selectors that describe nodes for full removal
protected $selectorsForFullRemoval = [ ]; protected $selectorsForFullRemoval = [ ];
/** /**
@ -20,8 +26,8 @@ abstract class NodeSanitizer implements NodeTypeSanitizerInterface {
* @param $elementText * @param $elementText
* @return string * @return string
*/ */
protected function sanitizeElementData( $elementText ) { protected function sanitizeElementData( $elementText ) {
$dom = new \DOMDocument( ); $dom = new \DOMDocument();
$dom->loadHTML( $this->prepareValidXML( $elementText ) ); $dom->loadHTML( $this->prepareValidXML( $elementText ) );
$elementTextAfterTrim = trim( $this->cleanUpDOM( $dom ) ); $elementTextAfterTrim = trim( $this->cleanUpDOM( $dom ) );
@ -65,6 +71,7 @@ abstract class NodeSanitizer implements NodeTypeSanitizerInterface {
protected function cleanUpDOM( $dom ) { protected function cleanUpDOM( $dom ) {
$xpath = new \DOMXPath( $dom ); $xpath = new \DOMXPath( $dom );
$this->removeNodesBySelector( $xpath, $this->selectorsForFullRemoval ); $this->removeNodesBySelector( $xpath, $this->selectorsForFullRemoval );
$nodes = $this->extractNeededNodes( $xpath ); $nodes = $this->extractNeededNodes( $xpath );
return $this->normalizeWhitespace( $this->generateHTML( $nodes, $dom ) ); return $this->normalizeWhitespace( $this->generateHTML( $nodes, $dom ) );
@ -78,15 +85,22 @@ abstract class NodeSanitizer implements NodeTypeSanitizerInterface {
* @return string * @return string
*/ */
protected function generateHTML( $nodes, $dom ) { protected function generateHTML( $nodes, $dom ) {
$result = []; $result = [ ];
foreach ( $nodes as $node ) { foreach ( $nodes as $node ) {
/* $outputHtml = $rawHtml = $dom->saveHTML( $node );
* store the result; As the input text is already escaped, we make sure that if ( $node->nodeName === '#text' ) {
* our output will be escaped too // As the input text is already escaped, we make sure that our output will be escaped too
*/ $outputHtml = htmlspecialchars( $rawHtml, ENT_QUOTES );
$result[] = ( $node->nodeName === '#text' ) ? htmlspecialchars( $dom->saveHTML( $node ), ENT_QUOTES ) : $dom->saveHTML( $node ); }
if ( $node->parentNode && in_array( $node->parentNode->nodeName, $this->selectorsWrappingTextToPad ) ) {
$outputHtml = sprintf( ' %s ', $rawHtml );
}
$result[] = $outputHtml;
} }
return implode( ' ', $result ); return implode( '', $result );
} }
/** /**
@ -105,7 +119,16 @@ abstract class NodeSanitizer implements NodeTypeSanitizerInterface {
* @return string * @return string
*/ */
protected function getAllNodesXPath() { protected function getAllNodesXPath() {
return sprintf('//%s/* | //%s//text()', $this->rootNodeTag, $this->rootNodeTag); $xpathExpressions = [ ];
foreach ( $this->selectorsWrappingAllowedFeatures as $selector ) {
$xpathExpressions [] = sprintf( '//%s//%s', $this->rootNodeTag, $selector );
}
foreach ( $this->allowedTags as $selector ) {
$xpathExpressions [] = sprintf( '//%s//%s', $this->rootNodeTag, $selector );
}
$xpathExpressions [] = sprintf( '//%s//text()', $this->rootNodeTag );
return implode( ' | ', $xpathExpressions );
} }
/** /**
@ -135,7 +158,7 @@ abstract class NodeSanitizer implements NodeTypeSanitizerInterface {
* @param $xpath DOMXPath * @param $xpath DOMXPath
* @param $selectorsToRemove array * @param $selectorsToRemove array
*/ */
protected function removeNodesBySelector( $xpath, $selectorsToRemove = [] ) { protected function removeNodesBySelector( $xpath, $selectorsToRemove = [ ] ) {
foreach ( $selectorsToRemove as $selector ) { foreach ( $selectorsToRemove as $selector ) {
$nodesToRemove = $xpath->query( sprintf( '//%s//%s', $this->rootNodeTag, $selector ) ); $nodesToRemove = $xpath->query( sprintf( '//%s//%s', $this->rootNodeTag, $selector ) );
foreach ( $nodesToRemove as $node ) { foreach ( $nodesToRemove as $node ) {

View file

@ -572,7 +572,7 @@ class PortableInfoboxRenderServiceTest extends WikiaBaseTest {
'output' => '<aside class="portable-infobox pi-background"> 'output' => '<aside class="portable-infobox pi-background">
<div class="pi-item pi-hero"> <div class="pi-item pi-hero">
<hgroup class="pi-hero-title-wrapper pi-item-spacing"> <hgroup class="pi-hero-title-wrapper pi-item-spacing">
<h2 class="pi-hero-title">Test Title</h2> <h2 class="pi-hero-title">Test <a href="example.com">Title</a></h2>
</hgroup> </hgroup>
<img <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"}]\'/> 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"}]\'/>

View file

@ -25,13 +25,118 @@ class NodeHeroImageSanitizerTest extends WikiaBaseTest {
function testSanitizeDataProvider() { function testSanitizeDataProvider() {
return [ return [
[ [
['title' => ['value' => 'Test Title'] ], [ 'title' => [ 'value' => 'Test Title' ] ],
['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 <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' => '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 class="duration" itemprop="duration">01:00</span><span class="play-circle"></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 class="duration" itemprop="duration">01:00</span><span class="play-circle"></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)' ] ]
]
]; ];
} }
} }

View file

@ -45,18 +45,11 @@ class NodeImageSanitizerTest extends WikiaBaseTest {
[ 'caption' => '' ] [ 'caption' => '' ]
], ],
[ [
[ 'caption' => '<ul><li>1 [ 'caption' => '<ul><li>1</li><li>2</li><li>3</li></ul>' ],
</li><li>2
</li><li>3
</li></ul>' ],
[ 'caption' => '1 2 3' ] [ 'caption' => '1 2 3' ]
], ],
[ [
[ 'caption' => '<ol><li>1 [ 'caption' => '<ol><li>1</li><li>2<ol><li>2.1</li></ol></li></ol>' ],
</li><li>2
<ol><li>2.1
</li></ol>
</li></ol>' ],
[ 'caption' => '1 2 2.1' ] [ 'caption' => '1 2 2.1' ]
], ],
[ [
@ -66,6 +59,14 @@ class NodeImageSanitizerTest extends WikiaBaseTest {
[ [
[ 'caption' => 'Música de' ], [ 'caption' => 'Música de' ],
[ 'caption' => 'Música de' ] [ 'caption' => 'Música de' ]
],
[
[ '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>.' ],
[ 'caption' => 'A Kuruma in <a href="/wiki/Grand_Theft_Auto_Online" title="Grand Theft Auto Online">Grand Theft Auto Online</a>.' ]
],
[
[ '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)' ],
[ '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)' ]
] ]
]; ];
} }