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    /**
30     * @param LinkBatch $batch
31     */
32    public function __construct( LinkBatch $batch ) {
33        $this->batch = $batch;
34    }
35
36    /**
37     * @return string
38     */
39    public function getXPath() {
40        return '//a[contains(concat(" ",normalize-space(@rel)," ")," mw:WikiLink ")]';
41    }
42
43    /**
44     * Parsoid ignores red links. With good reason: redlinks should only be
45     * applied when rendering the content, not when it's created.
46     *
47     * This method will parse a given content, fetch all of its links & let MW's
48     * LinkRenderer build the link HTML (which will take redlinks into account.)
49     * It will then substitute original link HTML for the one LinkRenderer generated.
50     *
51     * This replaces both existing and non-existent anchors because the relative links
52     * output by parsoid are not usable when output within a subpage.
53     *
54     * @param DOMNode $node
55     * @param Title $title Title to resolve relative links against
56     * @throws \Flow\Exception\WikitextException
57     */
58    public function apply( DOMNode $node, Title $title ) {
59        if ( !$node instanceof DOMElement ) {
60            return;
61        }
62
63        $href = $node->getAttribute( 'href' );
64        if ( $href === '' ) {
65            return;
66        }
67
68        $title = Utils::createRelativeTitle( rawurldecode( $href ), $title );
69        if ( $title === null ) {
70            return;
71        }
72
73        // gather existing link attributes
74        $attributes = [];
75        foreach ( $node->attributes as $attribute ) {
76            $attributes[$attribute->name] = $attribute->value;
77        }
78        // let MW build link HTML based on Parsoid data
79        $html = MediaWikiServices::getInstance()->getLinkRenderer()->makeLink(
80            $title,
81            new HtmlArmor( Utils::getInnerHtml( $node ) ),
82            $attributes
83        );
84        // create new DOM from this MW-built link
85        $replacementNode = Utils::createDOM( $html )
86            ->getElementsByTagName( 'a' )
87            ->item( 0 );
88        // import MW-built link node into content DOM
89        // @phan-suppress-next-line PhanTypeMismatchArgumentNullableInternal
90        $replacementNode = $node->ownerDocument->importNode( $replacementNode, true );
91        // replace Parsoid link with MW-built link
92        $node->parentNode->replaceChild( $replacementNode, $node );
93    }
94}