Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
86.96% covered (warning)
86.96%
20 / 23
66.67% covered (warning)
66.67%
2 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
WikiLinkFixer
86.96% covered (warning)
86.96%
20 / 23
66.67% covered (warning)
66.67%
2 / 3
7.11
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getXPath
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 apply
85.71% covered (warning)
85.71%
18 / 21
0.00% covered (danger)
0.00%
0 / 1
5.07
1<?php
2
3namespace Flow\Parsoid\Fixer;
4
5use DOMElement;
6use DOMNode;
7use Flow\Conversion\Utils;
8use Flow\Parsoid\Fixer;
9use HtmlArmor;
10use MediaWiki\Cache\LinkBatch;
11use MediaWiki\MediaWikiServices;
12use MediaWiki\Title\Title;
13
14/**
15 * Parsoid ignores red links. With good reason: redlinks should only be
16 * applied when rendering the content, not when it's created.
17 *
18 * This class updates HTML content from Parsoid with anchors generated by
19 * LinkRenderer.  In addition to handling red links, this normalizes
20 * relative paths to start with a /, so the HTML renders correctly
21 * on any page.
22 */
23class WikiLinkFixer implements Fixer {
24    /**
25     * @var LinkBatch
26     */
27    protected $batch;
28
29    public function __construct( LinkBatch $batch ) {
30        $this->batch = $batch;
31    }
32
33    /**
34     * @return string
35     */
36    public function getXPath() {
37        return '//a[contains(concat(" ",normalize-space(@rel)," ")," mw:WikiLink ")]';
38    }
39
40    /**
41     * Parsoid ignores red links. With good reason: redlinks should only be
42     * applied when rendering the content, not when it's created.
43     *
44     * This method will parse a given content, fetch all of its links & let MW's
45     * LinkRenderer build the link HTML (which will take redlinks into account.)
46     * It will then substitute original link HTML for the one LinkRenderer generated.
47     *
48     * This replaces both existing and non-existent anchors because the relative links
49     * output by parsoid are not usable when output within a subpage.
50     *
51     * @param DOMNode $node
52     * @param Title $title Title to resolve relative links against
53     * @throws \Flow\Exception\WikitextException
54     */
55    public function apply( DOMNode $node, Title $title ) {
56        if ( !$node instanceof DOMElement ) {
57            return;
58        }
59
60        $href = $node->getAttribute( 'href' );
61        if ( $href === '' ) {
62            return;
63        }
64
65        $title = Utils::createRelativeTitle( rawurldecode( $href ), $title );
66        if ( $title === null ) {
67            return;
68        }
69
70        // gather existing link attributes
71        $attributes = [];
72        foreach ( $node->attributes as $attribute ) {
73            $attributes[$attribute->name] = $attribute->value;
74        }
75        // let MW build link HTML based on Parsoid data
76        $html = MediaWikiServices::getInstance()->getLinkRenderer()->makeLink(
77            $title,
78            new HtmlArmor( Utils::getInnerHtml( $node ) ),
79            $attributes
80        );
81        // create new DOM from this MW-built link
82        $replacementNode = Utils::createDOM( $html )
83            ->getElementsByTagName( 'a' )
84            ->item( 0 );
85        // import MW-built link node into content DOM
86        // @phan-suppress-next-line PhanTypeMismatchArgumentNullableInternal
87        $replacementNode = $node->ownerDocument->importNode( $replacementNode, true );
88        // replace Parsoid link with MW-built link
89        $node->parentNode->replaceChild( $replacementNode, $node );
90    }
91}