Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
MediaStructure
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 9
462
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 isRedLink
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 hasResource
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getResource
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 hasAlt
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getAlt
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 hasMediaUrl
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 getMediaUrl
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 parse
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
90
1<?php
2declare( strict_types = 1 );
3
4namespace Wikimedia\Parsoid\Core;
5
6use Wikimedia\Parsoid\DOM\Element;
7use Wikimedia\Parsoid\DOM\Node;
8use Wikimedia\Parsoid\Utils\DOMCompat;
9use Wikimedia\Parsoid\Utils\DOMUtils;
10use Wikimedia\Parsoid\Utils\WTUtils;
11use Wikimedia\Parsoid\Wikitext\Consts;
12
13/**
14 * All media should have a fixed structure:
15 *
16 * ```
17 * <conatinerElt>
18 *  <linkElt><mediaElt /></linkElt>
19 *  <captionElt>...</captionElt>
20 * </containerElt>
21 * ```
22 *
23 * Pull out this fixed structure, being as generous as possible with
24 * possibly-broken HTML.
25 */
26class MediaStructure {
27
28    /**
29     * Node names: figure, span
30     *
31     * @var ?Element
32     */
33     public $containerElt;
34
35    /**
36     * Node names: a, span
37     *
38     * @var ?Element
39     */
40    public $linkElt;
41
42    /**
43     * Node names: img, audio, video, span
44     *
45     * @var ?Element
46     */
47    public $mediaElt;
48
49    /**
50     * Node names: figcaption
51     *
52     * @var ?Element
53     */
54    public $captionElt;
55
56    /**
57     * @param Element $mediaElt
58     * @param ?Element $linkElt
59     * @param ?Element $containerElt
60     */
61    public function __construct(
62        Element $mediaElt, ?Element $linkElt = null,
63        ?Element $containerElt = null
64    ) {
65        $this->mediaElt = $mediaElt;
66        $this->linkElt = $linkElt;
67        $this->containerElt = $containerElt;
68        if ( $containerElt && DOMCompat::nodeName( $containerElt ) === 'figure' ) {
69            // FIXME: Support last child, which is not the linkElt, as the caption?
70            $this->captionElt = DOMCompat::querySelector( $containerElt, 'figcaption' );
71        }
72    }
73
74    /**
75     * We were not able to fetch info for the title, so the media was
76     * considered missing and rendered as a span.
77     *
78     * @return bool
79     */
80    public function isRedLink(): bool {
81        return ( DOMCompat::nodeName( $this->mediaElt ) === 'span' );
82    }
83
84    /**
85     * @return bool
86     */
87    public function hasResource(): bool {
88        return $this->mediaElt->hasAttribute( 'resource' );
89    }
90
91    /**
92     * @return string
93     */
94    public function getResource(): string {
95        return $this->mediaElt->getAttribute( 'resource' ) ?? '';
96    }
97
98    /**
99     * @return bool
100     */
101    public function hasAlt(): bool {
102        return $this->mediaElt->hasAttribute( 'alt' );
103    }
104
105    /**
106     * @return string
107     */
108    public function getAlt(): string {
109        return $this->mediaElt->getAttribute( 'alt' ) ?? '';
110    }
111
112    /**
113     * @return bool
114     */
115    public function hasMediaUrl(): bool {
116        return $this->linkElt && $this->linkElt->hasAttribute( 'href' );
117    }
118
119    /**
120     * @return string
121     */
122    public function getMediaUrl(): string {
123        return $this->linkElt ? ( $this->linkElt->getAttribute( 'href' ) ?? '' ) : '';
124    }
125
126    /**
127     * @param Node $node
128     * @return ?MediaStructure
129     */
130    public static function parse( Node $node ): ?MediaStructure {
131        if ( !WTUtils::isGeneratedFigure( $node ) ) {
132            return null;
133        }
134        '@phan-var Element $node';  // @var Element $node
135        $linkElt = $node;
136        do {
137            // Try being lenient, maybe there was a content model violation when
138            // parsing and an active formatting element was reopened in the wrapper
139            $linkElt = DOMUtils::firstNonSepChild( $linkElt );
140        } while (
141            $linkElt instanceof Element && DOMCompat::nodeName( $linkElt ) !== 'a' &&
142            isset( Consts::$HTML['FormattingTags'][DOMCompat::nodeName( $linkElt )] )
143        );
144        if (
145            !( $linkElt instanceof Element &&
146                in_array( DOMCompat::nodeName( $linkElt ), [ 'a', 'span' ], true ) )
147        ) {
148            if ( $linkElt instanceof Element ) {
149                // Try being lenient, maybe this is the media element and we don't
150                // have a link elt.  See the test, "Image: from basic HTML (1)"
151                $mediaElt = $linkElt;
152                $linkElt = null;
153            } else {
154                return null;
155            }
156        } else {
157            $mediaElt = DOMUtils::firstNonSepChild( $linkElt );
158        }
159        if (
160            !( $mediaElt instanceof Element &&
161                in_array( DOMCompat::nodeName( $mediaElt ), [ 'audio', 'img', 'span', 'video' ], true ) )
162        ) {
163            return null;
164        }
165        return new MediaStructure( $mediaElt, $linkElt, $node );
166    }
167
168}