Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
0.00% |
0 / 14 |
CRAP | |
0.00% |
0 / 346 |
AddMediaInfo | |
0.00% |
0 / 1 |
|
0.00% |
0 / 14 |
14762 | |
0.00% |
0 / 346 |
handleSize | |
0.00% |
0 / 1 |
420 | |
0.00% |
0 / 27 |
|||
parseTimeString | |
0.00% |
0 / 1 |
56 | |
0.00% |
0 / 15 |
|||
parseFrag | |
0.00% |
0 / 1 |
56 | |
0.00% |
0 / 14 |
|||
addSources | |
0.00% |
0 / 1 |
56 | |
0.00% |
0 / 25 |
|||
addTracks | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 17 |
|||
getPath | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 6 |
|||
handleAudio | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 14 |
|||
handleVideo | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 15 |
|||
handleImage | |
0.00% |
0 / 1 |
72 | |
0.00% |
0 / 25 |
|||
makeErr | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 4 |
|||
addErrors | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 8 |
|||
copyOverAttribute | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 6 |
|||
replaceAnchor | |
0.00% |
0 / 1 |
156 | |
0.00% |
0 / 37 |
|||
run | |
0.00% |
0 / 1 |
1482 | |
0.00% |
0 / 133 |
<?php | |
declare( strict_types = 1 ); | |
namespace Wikimedia\Parsoid\Wt2Html\PP\Processors; | |
use stdClass; | |
use Wikimedia\Assert\Assert; | |
use Wikimedia\Parsoid\Config\Env; | |
use Wikimedia\Parsoid\Core\Sanitizer; | |
use Wikimedia\Parsoid\DOM\DocumentFragment; | |
use Wikimedia\Parsoid\DOM\Element; | |
use Wikimedia\Parsoid\DOM\Node; | |
use Wikimedia\Parsoid\Html2Wt\WTSUtils; | |
use Wikimedia\Parsoid\Utils\ContentUtils; | |
use Wikimedia\Parsoid\Utils\DOMCompat; | |
use Wikimedia\Parsoid\Utils\DOMDataUtils; | |
use Wikimedia\Parsoid\Utils\DOMUtils; | |
use Wikimedia\Parsoid\Utils\Title; | |
use Wikimedia\Parsoid\Utils\WTUtils; | |
use Wikimedia\Parsoid\Wikitext\Consts; | |
use Wikimedia\Parsoid\Wt2Html\PegTokenizer; | |
use Wikimedia\Parsoid\Wt2Html\Wt2HtmlDOMProcessor; | |
class AddMediaInfo implements Wt2HtmlDOMProcessor { | |
/** | |
* Extract the dimensions for media. | |
* | |
* @param Env $env | |
* @param array $attrs | |
* @param array $info | |
* @phan-param array{size:array{height?:int,width?:int},format:string} $attrs | |
* @return array | |
*/ | |
private static function handleSize( Env $env, array $attrs, array $info ): array { | |
$height = $info['height']; | |
$width = $info['width']; | |
Assert::invariant( | |
is_numeric( $height ) && $height !== NAN, | |
'Expected $height as a valid number' | |
); | |
Assert::invariant( | |
is_numeric( $width ) && $width !== NAN, | |
'Expected $width as a valid number' | |
); | |
if ( !empty( $info['thumburl'] ) && !empty( $info['thumbheight'] ) ) { | |
$height = $info['thumbheight']; | |
} | |
if ( !empty( $info['thumburl'] ) && !empty( $info['thumbwidth'] ) ) { | |
$width = $info['thumbwidth']; | |
} | |
// Audio files don't have dimensions, so we fallback to these arbitrary | |
// defaults, and the "mw-default-audio-height" class is added. | |
if ( $info['mediatype'] === 'AUDIO' ) { | |
$height = /* height || */32; // Arguably, audio should respect a defined height | |
$width = $width ?: $env->getSiteConfig()->widthOption(); | |
} | |
// Handle client-side upscaling (including 'border') | |
$mustRender = $info['mustRender'] ?? $info['mediatype'] !== 'BITMAP'; | |
// Calculate the scaling ratio from the user-specified width and height | |
$ratio = null; | |
if ( !empty( $attrs['dims']['height'] ) && !empty( $info['height'] ) ) { | |
$ratio = $attrs['dims']['height'] / $info['height']; | |
} | |
if ( !empty( $attrs['dims']['width'] ) && !empty( $info['width'] ) ) { | |
$r = $attrs['dims']['width'] / $info['width']; | |
$ratio = ( $ratio === null || $r < $ratio ) ? $r : $ratio; | |
} | |
// If the user requested upscaling, then this is denied in the thumbnail | |
// and frameless format, except for files with mustRender. | |
if ( | |
$ratio !== null && $ratio > 1 && !$mustRender && | |
( $attrs['format'] === 'Thumb' || $attrs['format'] === 'Frameless' ) | |
) { | |
// Upscaling denied | |
$height = $info['height']; | |
$width = $info['width']; | |
} | |
return [ 'height' => $height, 'width' => $width ]; | |
} | |
/** | |
* This is a port of TMH's parseTimeString() | |
* | |
* @param string $timeString | |
* @param int|float|null $length | |
* @return int|float|null | |
*/ | |
private static function parseTimeString( | |
string $timeString, $length = null | |
) { | |
$parts = explode( ':', $timeString ); | |
$time = 0; | |
$countParts = count( $parts ); | |
if ( $countParts > 3 ) { | |
return null; | |
} | |
for ( $i = 0; $i < $countParts; $i++ ) { | |
if ( !is_numeric( $parts[$i] ) ) { | |
return null; | |
} | |
$time += floatval( $parts[$i] ) * pow( 60, $countParts - 1 - $i ); | |
} | |
if ( $time < 0 ) { | |
$time = 0; | |
} elseif ( $length !== null ) { | |
if ( $time > $length ) { | |
$time = $length - 1; | |
} | |
} | |
return $time; | |
} | |
/** | |
* Handle media fragments | |
* https://www.w3.org/TR/media-frags/ | |
* | |
* @param array $info | |
* @param stdClass $dataMw | |
* @return string | |
*/ | |
private static function parseFrag( array $info, stdClass $dataMw ): string { | |
$frag = ''; | |
$starttime = WTSUtils::getAttrFromDataMw( $dataMw, 'starttime', true ); | |
$endtime = WTSUtils::getAttrFromDataMw( $dataMw, 'endtime', true ); | |
if ( $starttime || $endtime ) { | |
$frag .= '#t='; | |
if ( $starttime ) { | |
$time = self::parseTimeString( $starttime[1]->txt, $info['duration'] ?? null ); | |
if ( $time !== null ) { | |
$frag .= $time; | |
} | |
} | |
if ( $endtime ) { | |
$time = self::parseTimeString( $endtime[1]->txt, $info['duration'] ?? null ); | |
if ( $time !== null ) { | |
$frag .= ',' . $time; | |
} | |
} | |
} | |
return $frag; | |
} | |
/** | |
* @param Element $elt | |
* @param array $info | |
* @param stdClass $dataMw | |
* @param bool $hasDimension | |
*/ | |
private static function addSources( | |
Element $elt, array $info, stdClass $dataMw, bool $hasDimension | |
): void { | |
$doc = $elt->ownerDocument; | |
$frag = self::parseFrag( $info, $dataMw ); | |
$dataFromTMH = true; | |
if ( is_array( $info['thumbdata']['derivatives'] ?? null ) ) { | |
// BatchAPI's `getAPIData` | |
$derivatives = $info['thumbdata']['derivatives']; | |
} elseif ( is_array( $info['derivatives'] ?? null ) ) { | |
// "videoinfo" prop | |
$derivatives = $info['derivatives']; | |
} else { | |
$derivatives = [ | |
[ | |
'src' => $info['url'], | |
'type' => $info['mime'], | |
'width' => (string)$info['width'], | |
'height' => (string)$info['height'], | |
], | |
]; | |
$dataFromTMH = false; | |
} | |
foreach ( $derivatives as $o ) { | |
$source = $doc->createElement( 'source' ); | |
$source->setAttribute( 'src', $o['src'] . $frag ); | |
$source->setAttribute( 'type', $o['type'] ); | |
$fromFile = isset( $o['transcodekey'] ) ? '' : '-file'; | |
if ( $hasDimension ) { | |
$source->setAttribute( 'data' . $fromFile . '-width', (string)$o['width'] ); | |
$source->setAttribute( 'data' . $fromFile . '-height', (string)$o['height'] ); | |
} | |
if ( $dataFromTMH ) { | |
$source->setAttribute( 'data-title', $o['title'] ); | |
$source->setAttribute( 'data-shorttitle', $o['shorttitle'] ); | |
} | |
$elt->appendChild( $source ); | |
} | |
} | |
/** | |
* @param Element $elt | |
* @param array $info | |
*/ | |
private static function addTracks( Element $elt, array $info ): void { | |
$doc = $elt->ownerDocument; | |
if ( is_array( $info['thumbdata']['timedtext'] ?? null ) ) { | |
// BatchAPI's `getAPIData` | |
$timedtext = $info['thumbdata']['timedtext']; | |
} elseif ( is_array( $info['timedtext'] ?? null ) ) { | |
// "videoinfo" prop | |
$timedtext = $info['timedtext']; | |
} else { | |
$timedtext = []; | |
} | |
foreach ( $timedtext as $o ) { | |
$track = $doc->createElement( 'track' ); | |
$track->setAttribute( 'kind', $o['kind'] ?? '' ); | |
$track->setAttribute( 'type', $o['type'] ?? '' ); | |
$track->setAttribute( 'src', $o['src'] ?? '' ); | |
$track->setAttribute( 'srclang', $o['srclang'] ?? '' ); | |
$track->setAttribute( 'label', $o['label'] ?? '' ); | |
$track->setAttribute( 'data-mwtitle', $o['title'] ?? '' ); | |
$track->setAttribute( 'data-dir', $o['dir'] ?? '' ); | |
$elt->appendChild( $track ); | |
} | |
} | |
/** | |
* Abstract way to get the path for an image given an info object. | |
* | |
* @param array $info | |
* @return string | |
*/ | |
private static function getPath( array $info ) { | |
$path = ''; | |
if ( !empty( $info['thumburl'] ) ) { | |
$path = $info['thumburl']; | |
} elseif ( !empty( $info['url'] ) ) { | |
$path = $info['url']; | |
} | |
return $path; | |
} | |
/** | |
* @param Env $env | |
* @param Element $span | |
* @param array $attrs | |
* @param array $info | |
* @param stdClass $dataMw | |
* @param Element $container | |
* @param string|null $captionText Unused, but matches the signature of handlers | |
* @return Element | |
*/ | |
private static function handleAudio( | |
Env $env, Element $span, array $attrs, array $info, stdClass $dataMw, | |
Element $container, ?string $captionText | |
): Element { | |
$doc = $span->ownerDocument; | |
$audio = $doc->createElement( 'audio' ); | |
$audio->setAttribute( 'controls', '' ); | |
$audio->setAttribute( 'preload', 'none' ); | |
$size = self::handleSize( $env, $attrs, $info ); | |
DOMDataUtils::addNormalizedAttribute( $audio, 'height', (string)$size['height'], null, true ); | |
DOMDataUtils::addNormalizedAttribute( $audio, 'width', (string)$size['width'], null, true ); | |
// Hardcoded until defined heights are respected. | |
// See `AddMediaInfo.handleSize` | |
DOMCompat::getClassList( $container )->add( 'mw-default-audio-height' ); | |
self::copyOverAttribute( $audio, $span, 'resource' ); | |
if ( $span->hasAttribute( 'lang' ) ) { | |
self::copyOverAttribute( $audio, $span, 'lang' ); | |
} | |
self::addSources( $audio, $info, $dataMw, false ); | |
self::addTracks( $audio, $info ); | |
return $audio; | |
} | |
/** | |
* @param Env $env | |
* @param Element $span | |
* @param array $attrs | |
* @param array $info | |
* @param stdClass $dataMw | |
* @param Element $container | |
* @param string|null $captionText Unused, but matches the signature of handlers | |
* @return Element | |
*/ | |
private static function handleVideo( | |
Env $env, Element $span, array $attrs, array $info, stdClass $dataMw, | |
Element $container, ?string $captionText | |
): Element { | |
$doc = $span->ownerDocument; | |
$video = $doc->createElement( 'video' ); | |
if ( !empty( $info['thumburl'] ) ) { | |
$video->setAttribute( 'poster', self::getPath( $info ) ); | |
} | |
$video->setAttribute( 'controls', '' ); | |
$video->setAttribute( 'preload', 'none' ); | |
$size = self::handleSize( $env, $attrs, $info ); | |
DOMDataUtils::addNormalizedAttribute( $video, 'height', (string)$size['height'], null, true ); | |
DOMDataUtils::addNormalizedAttribute( $video, 'width', (string)$size['width'], null, true ); | |
self::copyOverAttribute( $video, $span, 'resource' ); | |
if ( $span->hasAttribute( 'lang' ) ) { | |
self::copyOverAttribute( $video, $span, 'lang' ); | |
} | |
self::addSources( $video, $info, $dataMw, true ); | |
self::addTracks( $video, $info ); | |
return $video; | |
} | |
/** | |
* Set up the actual image structure, attributes, etc. | |
* | |
* @param Env $env | |
* @param Element $span | |
* @param array $attrs | |
* @param array $info | |
* @param stdClass $dataMw | |
* @param Element $container | |
* @param string|null $captionText | |
* @return Element | |
*/ | |
private static function handleImage( | |
Env $env, Element $span, array $attrs, array $info, stdClass $dataMw, | |
Element $container, ?string $captionText | |
): Element { | |
$doc = $span->ownerDocument; | |
$img = $doc->createElement( 'img' ); | |
$attr = WTSUtils::getAttrFromDataMw( $dataMw, 'alt', false ); | |
if ( $attr !== null ) { | |
$img->setAttribute( 'alt', $attr[1]->txt ); | |
} elseif ( $captionText ) { | |
$img->setAttribute( 'alt', $captionText ); | |
} | |
self::copyOverAttribute( $img, $span, 'resource' ); | |
$img->setAttribute( 'src', self::getPath( $info ) ); | |
$img->setAttribute( 'decoding', 'async' ); | |
if ( $span->hasAttribute( 'lang' ) ) { | |
self::copyOverAttribute( $img, $span, 'lang' ); | |
} | |
// Add (read-only) information about original file size (T64881) | |
$img->setAttribute( 'data-file-width', (string)$info['width'] ); | |
$img->setAttribute( 'data-file-height', (string)$info['height'] ); | |
$img->setAttribute( 'data-file-type', strtolower( $info['mediatype'] ?? '' ) ); | |
$size = self::handleSize( $env, $attrs, $info ); | |
DOMDataUtils::addNormalizedAttribute( $img, 'height', (string)$size['height'], null, true ); | |
DOMDataUtils::addNormalizedAttribute( $img, 'width', (string)$size['width'], null, true ); | |
// Handle "responsive" images, i.e. srcset | |
if ( !empty( $info['responsiveUrls'] ) ) { | |
$candidates = []; | |
foreach ( $info['responsiveUrls'] as $density => $url ) { | |
$candidates[] = $url . ' ' . $density . 'x'; | |
} | |
if ( $candidates ) { | |
$img->setAttribute( 'srcset', implode( ', ', $candidates ) ); | |
} | |
} | |
return $img; | |
} | |
/** | |
* @param string $key | |
* @param string $message | |
* @param ?array $params | |
* @return array | |
*/ | |
private static function makeErr( | |
string $key, string $message, ?array $params = null | |
): array { | |
$e = [ 'key' => $key, 'message' => $message ]; | |
// Additional error info for clients that could fix the error. | |
if ( $params !== null ) { | |
$e['params'] = $params; | |
} | |
return $e; | |
} | |
/** | |
* @param Element $container | |
* @param array $errs | |
* @param stdClass $dataMw | |
*/ | |
private static function addErrors( Element $container, array $errs, stdClass $dataMw ): void { | |
if ( !DOMUtils::hasTypeOf( $container, 'mw:Error' ) ) { | |
$typeOf = $container->getAttribute( 'typeof' ) ?? ''; | |
$typeOf = 'mw:Error' . ( $typeOf ? ' ' . $typeOf : '' ); | |
$container->setAttribute( 'typeof', $typeOf ); | |
} | |
if ( is_array( $dataMw->errors ?? null ) ) { | |
$errs = array_merge( $dataMw->errors, $errs ); | |
} | |
$dataMw->errors = $errs; | |
} | |
/** | |
* @param Element $elt | |
* @param Element $span | |
* @param string $attribute | |
*/ | |
private static function copyOverAttribute( | |
Element $elt, Element $span, string $attribute | |
): void { | |
DOMDataUtils::addNormalizedAttribute( | |
$elt, | |
$attribute, | |
$span->getAttribute( $attribute ) ?? '', | |
WTSUtils::getAttributeShadowInfo( $span, $attribute )['value'] | |
); | |
} | |
/** | |
* @param Env $env | |
* @param PegTokenizer $urlParser | |
* @param Element $container | |
* @param Element $oldAnchor | |
* @param array $attrs | |
* @param stdClass $dataMw | |
* @param bool $isImage | |
* @param string|null $captionText | |
* @param int $page | |
* @param string $lang | |
* @return Element | |
*/ | |
private static function replaceAnchor( | |
Env $env, PegTokenizer $urlParser, Element $container, | |
Element $oldAnchor, array $attrs, stdClass $dataMw, bool $isImage, | |
?string $captionText, int $page, string $lang | |
): Element { | |
$doc = $oldAnchor->ownerDocument; | |
$attr = WTSUtils::getAttrFromDataMw( $dataMw, 'link', true ); | |
if ( $isImage ) { | |
$anchor = $doc->createElement( 'a' ); | |
$addDescriptionLink = static function ( Title $title ) use ( $env, $anchor, $page, $lang ) { | |
$href = $env->makeLink( $title ); | |
$qs = []; | |
if ( $page > 0 ) { | |
$qs['page'] = $page; | |
} | |
if ( $lang ) { | |
$qs['lang'] = $lang; | |
} | |
if ( $qs ) { | |
$href .= '?' . http_build_query( $qs ); | |
} | |
$anchor->setAttribute( 'href', $href ); | |
$anchor->setAttribute( 'class', 'mw-file-description' ); | |
}; | |
if ( $attr !== null ) { | |
$discard = true; | |
$val = $attr[1]->txt; | |
if ( $val === '' ) { | |
// No href if link= was specified | |
$anchor = $doc->createElement( 'span' ); | |
} elseif ( $urlParser->tokenizeURL( $val ) !== false ) { | |
// An external link! | |
$href = Sanitizer::cleanUrl( $env->getSiteConfig(), $val, 'external' ); | |
$anchor->setAttribute( 'href', $href ); | |
} else { | |
$link = $env->makeTitleFromText( $val, null, true ); | |
if ( $link !== null ) { | |
$anchor->setAttribute( 'href', $env->makeLink( $link ) ); | |
$anchor->setAttribute( 'title', $link->getPrefixedText() ); | |
} else { | |
// Treat same as if link weren't present | |
$addDescriptionLink( $attrs['title'] ); | |
// but preserve for roundtripping | |
$discard = false; | |
} | |
} | |
if ( $discard ) { | |
WTSUtils::getAttrFromDataMw( $dataMw, 'link', /* keep */false ); | |
} | |
} else { | |
$addDescriptionLink( $attrs['title'] ); | |
} | |
} else { | |
$anchor = $doc->createElement( 'span' ); | |
} | |
if ( $captionText ) { | |
$anchor->setAttribute( 'title', $captionText ); | |
} | |
$oldAnchor->parentNode->replaceChild( $anchor, $oldAnchor ); | |
return $anchor; | |
} | |
/** | |
* @inheritDoc | |
*/ | |
public function run( | |
Env $env, Node $root, array $options = [], bool $atTopLevel = false | |
): void { | |
'@phan-var Element|DocumentFragment $root'; // @var Element|DocumentFragment $root | |
$urlParser = new PegTokenizer( $env ); | |
$validContainers = []; | |
$files = []; | |
$containers = DOMCompat::querySelectorAll( $root, '[typeof*="mw:File"]' ); | |
foreach ( $containers as $container ) { | |
// DOMFragmentWrappers assume the element name of their outermost | |
// content so, depending how the above query is written, we're | |
// protecting against getting a figure of the wrong type. However, | |
// since we're currently using typeof, it shouldn't be a problem. | |
// Also note that info for the media nested in the fragment has | |
// already been added in their respective pipeline. | |
Assert::invariant( | |
!WTUtils::isDOMFragmentWrapper( $container ), | |
'Media info for fragment was already added' | |
); | |
// We expect this structure to be predictable based on how it's | |
// emitted in the TT/WikiLinkHandler but treebuilding may have | |
// messed that up for us. | |
$anchor = $container; | |
do { | |
// An active formatting element may have been reopened inside | |
// the wrapper if a content model violation was encountered | |
// during treebuiling. Try to be a little lenient about that | |
// instead of bailing out | |
$anchor = $anchor->firstChild; | |
$anchorNodeName = DOMCompat::nodeName( $anchor ); | |
} while ( | |
$anchor instanceof Element && $anchorNodeName !== 'a' && | |
isset( Consts::$HTML['FormattingTags'][$anchorNodeName] ) | |
); | |
if ( !( $anchor instanceof Element && $anchorNodeName === 'a' ) ) { | |
$env->log( 'error', 'Unexpected structure when adding media info.' ); | |
continue; | |
} | |
$span = $anchor->firstChild; | |
if ( !( $span instanceof Element && DOMCompat::nodeName( $span ) === 'span' ) ) { | |
$env->log( 'error', 'Unexpected structure when adding media info.' ); | |
continue; | |
} | |
$dataMw = DOMDataUtils::getDataMw( $container ); | |
$dims = [ | |
'width' => (int)$span->getAttribute( 'data-width' ) ?: null, | |
'height' => (int)$span->getAttribute( 'data-height' ) ?: null, | |
]; | |
$page = WTSUtils::getAttrFromDataMw( $dataMw, 'page', true ); | |
if ( $page ) { | |
$dims['page'] = $page[1]->txt; | |
} | |
if ( $span->hasAttribute( 'lang' ) ) { | |
$dims['lang'] = $span->getAttribute( 'lang' ); | |
} | |
// "starttime" should be used if "thumbtime" isn't present, | |
// but only for rendering. | |
// "starttime" should be used if "thumbtime" isn't present, | |
// but only for rendering. | |
$thumbtime = WTSUtils::getAttrFromDataMw( $dataMw, 'thumbtime', true ); | |
$starttime = WTSUtils::getAttrFromDataMw( $dataMw, 'starttime', true ); | |
if ( $thumbtime || $starttime ) { | |
$seek = isset( $thumbtime[1] ) | |
? $thumbtime[1]->txt | |
: ( isset( $starttime[1] ) ? $starttime[1]->txt : '' ); | |
$seek = self::parseTimeString( $seek ); | |
if ( $seek !== null ) { | |
$dims['seek'] = $seek; | |
} | |
} | |
$attrs = [ | |
'dims' => $dims, | |
'format' => WTUtils::getMediaFormat( $container ), | |
'title' => $env->makeTitleFromText( $span->textContent ), | |
]; | |
$file = [ $attrs['title']->getKey(), $dims ]; | |
$infoKey = md5( json_encode( $file ) ); | |
$files[$infoKey] = $file; | |
$manualKey = null; | |
$manualthumb = WTSUtils::getAttrFromDataMw( $dataMw, 'manualthumb', true ); | |
if ( $manualthumb !== null ) { | |
$val = $manualthumb[1]->txt; | |
$title = $env->makeTitleFromText( $val, $attrs['title']->getNamespace(), true ); | |
if ( $title === null ) { | |
$errs = [ | |
self::makeErr( | |
'apierror-invalidtitle', | |
'Invalid thumbnail title.', | |
[ 'name' => $val ] | |
) | |
]; | |
self::addErrors( $container, $errs, $dataMw ); | |
continue; | |
} else { | |
$file = [ $title->getKey(), $dims ]; | |
$manualKey = md5( json_encode( $file ) ); | |
$files[$manualKey] = $file; | |
} | |
} | |
$validContainers[] = [ | |
'container' => $container, | |
'attrs' => $attrs, | |
// Pass the anchor because we did some work to find it above | |
'anchor' => $anchor, | |
'infoKey' => $infoKey, | |
'manualKey' => $manualKey, | |
]; | |
} | |
if ( !$validContainers ) { | |
return; | |
} | |
$start = microtime( true ); | |
$infos = $env->getDataAccess()->getFileInfo( | |
$env->getPageConfig(), | |
array_values( $files ) | |
); | |
if ( $env->profiling() ) { | |
$profile = $env->getCurrentProfile(); | |
$profile->bumpMWTime( "Media", 1000 * ( microtime( true ) - $start ), "api" ); | |
$profile->bumpCount( "Media" ); | |
} | |
$files = array_combine( | |
array_keys( $files ), | |
$infos | |
); | |
foreach ( $validContainers as $c ) { | |
$container = $c['container']; | |
$anchor = $c['anchor']; | |
$span = $anchor->firstChild; | |
$attrs = $c['attrs']; | |
$dataMw = DOMDataUtils::getDataMw( $container ); | |
$errs = []; | |
$info = $files[$c['infoKey']]; | |
if ( !$info ) { | |
$errs[] = self::makeErr( 'apierror-filedoesnotexist', 'This image does not exist.' ); | |
} elseif ( isset( $info['thumberror'] ) ) { | |
$errs[] = self::makeErr( 'apierror-unknownerror', $info['thumberror'] ); | |
} | |
// FIXME: Should we fallback to $info if there are errors with $manualinfo? | |
// What does the legacy parser do? | |
if ( $c['manualKey'] !== null ) { | |
$manualinfo = $files[$c['manualKey']]; | |
if ( !$manualinfo ) { | |
$errs[] = self::makeErr( 'apierror-filedoesnotexist', 'This image does not exist.' ); | |
} elseif ( isset( $manualinfo['thumberror'] ) ) { | |
$errs[] = self::makeErr( 'apierror-unknownerror', $manualinfo['thumberror'] ); | |
} else { | |
$info = $manualinfo; | |
} | |
} | |
if ( $info['badFile'] ?? false ) { | |
$errs[] = self::makeErr( 'apierror-badfile', 'This image is on the bad file list.' ); | |
} | |
// Add mw:Error to the RDFa type. | |
if ( $errs ) { | |
self::addErrors( $container, $errs, $dataMw ); | |
continue; | |
} | |
// Info relates to the thumb, not necessarily the file. | |
// The distinction matters for manualthumb, in which case only | |
// the "resource" copied over from the span relates to the file. | |
'@phan-var array $info'; // @var array $info | |
switch ( $info['mediatype'] ) { | |
case 'AUDIO': | |
$handler = 'handleAudio'; | |
$isImage = false; | |
break; | |
case 'VIDEO': | |
$handler = 'handleVideo'; | |
$isImage = false; | |
break; | |
default: | |
$handler = 'handleImage'; | |
$isImage = true; | |
break; | |
} | |
if ( WTUtils::hasVisibleCaption( $container ) ) { | |
$captionText = null; | |
} else { | |
if ( WTUtils::isInlineMedia( $container ) ) { | |
$caption = ContentUtils::createAndLoadDocumentFragment( | |
$container->ownerDocument, $dataMw->caption ?? '' | |
); | |
} else { | |
$caption = DOMCompat::querySelector( $container, 'figcaption' ); | |
// If the caption had tokens, it was placed in a DOMFragment | |
// and we haven't unpacked yet | |
if ( | |
$caption->firstChild && | |
DOMUtils::hasTypeOf( $caption->firstChild, 'mw:DOMFragment' ) | |
) { | |
$id = DOMDataUtils::getDataParsoid( $caption->firstChild )->html; | |
$caption = $env->getDOMFragment( $id ); | |
} | |
} | |
$captionText = trim( WTUtils::textContentFromCaption( $caption ) ); | |
// The sanitizer isn't going to do anything with a string value | |
// for alt/title and since we're going to use dom element setters, | |
// quote escaping should be fine. Note that if santization does | |
// happen here, it should also be done to $altFromCaption so that | |
// string comparison matches, where necessary. | |
// | |
// $sanitizedArgs = Sanitizer::sanitizeTagAttrs( $env->getSiteConfig(), 'img', null, [ | |
// new KV( 'alt', $captionText ) // Could be a 'title' too | |
// ] ); | |
// $captionText = $sanitizedArgs['alt'][0]; | |
} | |
$elt = self::$handler( $env, $span, $attrs, $info, $dataMw, $container, $captionText ); | |
$anchor = self::replaceAnchor( | |
$env, $urlParser, $container, $anchor, $attrs, $dataMw, $isImage, $captionText, | |
(int)( $attrs['dims']['page'] ?? 0 ), | |
$attrs['dims']['lang'] ?? '' | |
); | |
$anchor->appendChild( $elt ); | |
if ( isset( $dataMw->attribs ) && count( $dataMw->attribs ) === 0 ) { | |
unset( $dataMw->attribs ); | |
} | |
} | |
} | |
} |