Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
82.76% covered (warning)
82.76%
24 / 29
57.14% covered (warning)
57.14%
4 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
TitleHelper
82.76% covered (warning)
82.76%
24 / 29
57.14% covered (warning)
57.14%
4 / 7
23.26
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
4
 makeTitle
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
5
 makeRedirectTitle
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
4
 isExternal
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 identifyInterwikiPrefix
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
3.33
 sanitizeSectionFragment
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getNamespaceText
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace CirrusSearch\Search;
4
5use CirrusSearch\InterwikiResolver;
6use CirrusSearch\Util;
7use MediaWiki\MediaWikiServices;
8use MediaWiki\Parser\Sanitizer;
9use MediaWiki\Title\Title;
10use MediaWiki\WikiMap\WikiMap;
11
12/**
13 * Utility class build MW Title from elastica Result/ResultSet classes
14 * This class can be used in all classes that need to build a Title
15 * by reading the elasticsearch output.
16 */
17class TitleHelper {
18    /**
19     * @var string
20     */
21    private $hostWikiID;
22
23    /**
24     * @var InterwikiResolver
25     */
26    private $interwikiResolver;
27
28    /**
29     * @var callable accepts a string and returns a string
30     */
31    private $linkSanitizer;
32
33    /**
34     * @param string|null $hostWikiID
35     * @param InterwikiResolver|null $interwikiResolver
36     * @param callable|null $linkSanitizer
37     */
38    public function __construct( $hostWikiID = null, ?InterwikiResolver $interwikiResolver = null, ?callable $linkSanitizer = null ) {
39        $this->hostWikiID = $hostWikiID ?: WikiMap::getCurrentWikiId();
40        $this->interwikiResolver = $interwikiResolver ?: MediaWikiServices::getInstance()->getService( InterwikiResolver::SERVICE );
41        $this->linkSanitizer = $linkSanitizer ?: [ Sanitizer::class, 'escapeIdForLink' ];
42    }
43
44    /**
45     * Create a title. When making interwiki titles we should be providing the
46     * namespace text as a portion of the text, rather than a namespace id,
47     * because namespace id's are not consistent across wiki's. This
48     * additionally prevents the local wiki from localizing the namespace text
49     * when it should be using the localized name of the remote wiki.
50     *
51     * @param \Elastica\Result $r int $namespace
52     * @return Title
53     */
54    public function makeTitle( \Elastica\Result $r ): Title {
55        $iwPrefix = $this->identifyInterwikiPrefix( $r );
56        $titleFactory = MediaWikiServices::getInstance()->getTitleFactory();
57        if ( $iwPrefix === null && $r->namespace !== null && $r->title !== null ) {
58            return $titleFactory->makeTitle( $r->namespace, $r->title );
59        }
60
61        $nsPrefix = $r->namespace_text ? $r->namespace_text . ':' : '';
62        return $titleFactory->makeTitle( 0, $nsPrefix . $r->title, '', $iwPrefix ?? '' );
63    }
64
65    /**
66     * Build a Title to a redirect, this always works for internal titles.
67     * For external titles we need to use the namespace_text which is only
68     * valid if the redirect namespace is equals to the target title namespace.
69     * If the namespaces do not match we return null.
70     *
71     * @param \Elastica\Result $r
72     * @param string $redirectText
73     * @param int $redirNamespace
74     * @return Title|null the Title to the Redirect or null if we can't build it
75     */
76    public function makeRedirectTitle( \Elastica\Result $r, $redirectText, $redirNamespace ) {
77        $iwPrefix = self::identifyInterwikiPrefix( $r );
78        if ( $iwPrefix === null ) {
79            return Title::makeTitle( $redirNamespace, $redirectText );
80        }
81        if ( $redirNamespace === $r->namespace ) {
82            $nsPrefix = $r->namespace_text ? $r->namespace_text . ':' : '';
83            return Title::makeTitle(
84                0,
85                $nsPrefix . $redirectText,
86                '',
87                $iwPrefix
88            );
89        } else {
90            // redir namespace does not match, we can't
91            // build this title.
92            // The caller should fallback to the target title.
93            return null;
94        }
95    }
96
97    /**
98     * @param \Elastica\Result $r
99     * @return bool true if this result refers to an external Title
100     */
101    public function isExternal( \Elastica\Result $r ) {
102        if ( isset( $r->wiki ) && $r->wiki !== $this->hostWikiID ) {
103            return true;
104        }
105        // no wiki is suspicious, should we log a warning?
106        return false;
107    }
108
109    /**
110     * @param \Elastica\Result $r
111     * @return string|null the interwiki prefix for this result or null or
112     * empty if local.
113     */
114    private function identifyInterwikiPrefix( $r ) {
115        if ( isset( $r->wiki ) && $r->wiki !== $this->hostWikiID ) {
116            return $this->interwikiResolver->getInterwikiPrefix( $r->wiki );
117        }
118        // no wiki is suspicious, should we log something?
119        return null;
120    }
121
122    /**
123     * @param string $id
124     * @return string
125     */
126    public function sanitizeSectionFragment( $id ) {
127        return ( $this->linkSanitizer )( $id );
128    }
129
130    /**
131     * @param Title $title
132     * @return string
133     */
134    public function getNamespaceText( Title $title ) {
135        return Util::getNamespaceText( $title );
136    }
137}