Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
AutoURLLinkText
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 4
156
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 badSuffix
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 fromSelSerImpl
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
30
 escape
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2declare( strict_types = 1 );
3
4namespace Wikimedia\Parsoid\Html2Wt\ConstrainedText;
5
6use Wikimedia\Parsoid\Config\Env;
7use Wikimedia\Parsoid\DOM\Element;
8use Wikimedia\Parsoid\NodeData\DataParsoid;
9use Wikimedia\Parsoid\Utils\DOMCompat;
10
11/**
12 * An autolink to an external resource, like `http://example.com`.
13 */
14class AutoURLLinkText extends RegExpConstrainedText {
15
16    public function __construct( string $url, Element $node ) {
17        parent::__construct( [
18                'text' => $url,
19                'node' => $node,
20                // there's a \b boundary at start, and first char of url is a word char
21                'badPrefix' => /* RegExp */ '/\w$/uD',
22                'badSuffix' => self::badSuffix( $url )
23            ]
24        );
25    }
26
27    // This regexp comes from the legacy parser's EXT_LINK_URL_CLASS regexp.
28    private const EXT_LINK_URL_CLASS =
29        '^\[\]<>"\x00-\x20\x7F\x{00A0}\x{1680}\x{180E}\x{2000}-\x{200A}\x{202F}\x{205F}\x{3000}';
30    // This set of trailing punctuation comes from Parser.php::makeFreeExternalLink
31    private const TRAILING_PUNCT = ',;\\\.:!?';
32    private const NOT_LTGTNBSP = '(?!&(lt|gt|nbsp|#x0*(3[CcEe]|[Aa]0)|#0*(60|62|160));)';
33    private const NOT_QQ = "(?!'')";
34    // Trailing context for an autourl link
35    private const PAREN_AUTOURL_BAD_SUFFIX =
36        '/^' . self::NOT_LTGTNBSP . self::NOT_QQ .
37        '[' . self::TRAILING_PUNCT . ']*' .
38        '[' . self::EXT_LINK_URL_CLASS . self::TRAILING_PUNCT . ']/u';
39    // If the URL has an doesn't have an open paren in it, TRAILING PUNCT will
40    // include ')' as well.
41    private const NOPAREN_AUTOURL_BAD_SUFFIX =
42        '/^' . self::NOT_LTGTNBSP . self::NOT_QQ .
43        '[' . self::TRAILING_PUNCT . '\)]*' .
44        '[' . self::EXT_LINK_URL_CLASS . self::TRAILING_PUNCT . '\)]/u';
45
46    private static function badSuffix( string $url ): string {
47        return strpos( $url, '(' ) === false ?
48            self::NOPAREN_AUTOURL_BAD_SUFFIX :
49            self::PAREN_AUTOURL_BAD_SUFFIX;
50    }
51
52    protected static function fromSelSerImpl(
53        string $text, Element $node, DataParsoid $dataParsoid,
54        Env $env, array $opts
55    ): ?AutoURLLinkText {
56        $stx = $dataParsoid->stx ?? null;
57        $type = $dataParsoid->type ?? null;
58        if (
59            ( DOMCompat::nodeName( $node ) === 'a' && $stx === 'url' ) ||
60            ( DOMCompat::nodeName( $node ) === 'img' && $type === 'extlink' )
61        ) {
62            return new AutoURLLinkText( $text, $node );
63        }
64        return null;
65    }
66
67    /** @inheritDoc */
68    public function escape( State $state ): Result {
69        // Special case for entities which "leak off the end".
70        $r = parent::escape( $state );
71        // If the text ends with an incomplete entity, be careful of
72        // suffix text which could complete it.
73        if ( !$r->suffix &&
74            preg_match( '/&[#0-9a-zA-Z]*$/D', $r->text ) &&
75            preg_match( '/^[#0-9a-zA-Z]*;/', $state->rightContext )
76        ) {
77            $r->suffix = $this->suffix;
78        }
79        return $r;
80    }
81}