Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 62 |
|
0.00% |
0 / 8 |
CRAP | |
0.00% |
0 / 1 |
Hooks | |
0.00% |
0 / 62 |
|
0.00% |
0 / 8 |
650 | |
0.00% |
0 / 1 |
onParserFirstCallInit | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
onFuncIsIn | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
onParserAfterTidy | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
12 | |||
completeImplicitIsIn | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
onOutputPageParserOutput | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
makeTrail | |
0.00% |
0 / 26 |
|
0.00% |
0 / 1 |
72 | |||
getParentRegion | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
getParserOutput | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\GeoCrumbs; |
4 | |
5 | use MediaWiki\Hook\ParserAfterTidyHook; |
6 | use MediaWiki\Hook\ParserFirstCallInitHook; |
7 | use MediaWiki\Html\Html; |
8 | use MediaWiki\MediaWikiServices; |
9 | use MediaWiki\Output\Hook\OutputPageParserOutputHook; |
10 | use MediaWiki\Output\OutputPage; |
11 | use MediaWiki\Parser\Parser; |
12 | use MediaWiki\Parser\ParserOutput; |
13 | use MediaWiki\Title\Title; |
14 | use MediaWiki\User\User; |
15 | |
16 | class Hooks implements |
17 | ParserFirstCallInitHook, |
18 | ParserAfterTidyHook, |
19 | OutputPageParserOutputHook |
20 | { |
21 | |
22 | /** |
23 | * @param Parser $parser |
24 | */ |
25 | public function onParserFirstCallInit( $parser ) { |
26 | $parser->setFunctionHook( 'isin', [ self::class, 'onFuncIsIn' ] ); |
27 | } |
28 | |
29 | /** |
30 | * @param Parser $parser |
31 | * @param string $article |
32 | * @return string |
33 | */ |
34 | public static function onFuncIsIn( Parser $parser, $article ) { |
35 | // Tribute to Evan! |
36 | $article = urldecode( $article ); |
37 | |
38 | $page = $parser->getPage(); |
39 | $title = Title::newFromText( $article, $page ? $page->getNamespace() : NS_MAIN ); |
40 | if ( $title ) { |
41 | $article = [ 'id' => $title->getArticleID() ]; |
42 | $parser->getOutput()->setExtensionData( 'GeoCrumbIsIn', $article ); |
43 | } |
44 | |
45 | return ''; |
46 | } |
47 | |
48 | /** |
49 | * Assumes that mRevisionId is only set for primary wiki text when a new revision is saved. |
50 | * We need this in order to save IsIn info appropriately. |
51 | * We could add this at onSkinTemplateOutputPageBeforeExec too, but then it won't be in |
52 | * ParserCache, available for other articles. |
53 | * |
54 | * @param Parser $parser |
55 | * @param string &$text |
56 | */ |
57 | public function onParserAfterTidy( $parser, &$text ) { |
58 | $page = $parser->getPage(); |
59 | if ( $page && MediaWikiServices::getInstance()->getNamespaceInfo()->isContent( $page->getNamespace() ) ) { |
60 | // @phan-suppress-next-line PhanTypeMismatchArgumentNullable The cast cannot return null here |
61 | self::completeImplicitIsIn( $parser->getOutput(), Title::castFromPageReference( $page ) ); |
62 | } |
63 | } |
64 | |
65 | /** |
66 | * Generates an IsIn from title for subpages. |
67 | * |
68 | * @param ParserOutput $parserOutput |
69 | * @param Title $title |
70 | */ |
71 | public static function completeImplicitIsIn( $parserOutput, Title $title ) { |
72 | // only do implicitly if none is defined through parser hook |
73 | $existing = $parserOutput->getExtensionData( 'GeoCrumbIsIn' ); |
74 | if ( $existing !== null ) { |
75 | return; |
76 | } |
77 | |
78 | // if we're dealing with a subpage, the parent should be in breadcrumb |
79 | $parent = $title->getBaseTitle(); |
80 | |
81 | if ( !$parent->equals( $title ) ) { |
82 | $article = [ 'id' => $parent->getArticleID() ]; |
83 | $parserOutput->setExtensionData( 'GeoCrumbIsIn', $article ); |
84 | } |
85 | } |
86 | |
87 | /** |
88 | * @param OutputPage $out |
89 | * @param ParserOutput $parserOutput |
90 | */ |
91 | public function onOutputPageParserOutput( $out, $parserOutput ): void { |
92 | $breadCrumbs = self::makeTrail( $out->getTitle(), $parserOutput, $out->getUser() ); |
93 | |
94 | if ( count( $breadCrumbs ) > 1 ) { |
95 | $breadCrumbs = Html::rawElement( 'span', [ 'class' => 'ext-geocrumbs-breadcrumbs' ], |
96 | implode( wfMessage( 'geocrumbs-delimiter' )->inContentLanguage()->escaped(), $breadCrumbs ) |
97 | ); |
98 | $out->addSubtitle( $breadCrumbs ); |
99 | } |
100 | } |
101 | |
102 | /** |
103 | * @param Title $title |
104 | * @param ParserOutput $parserOutput |
105 | * @param User $user |
106 | * @return array |
107 | */ |
108 | public static function makeTrail( Title $title, ParserOutput $parserOutput, User $user ): array { |
109 | if ( $title->getArticleID() <= 0 ) { |
110 | return []; |
111 | } |
112 | |
113 | $breadCrumbs = []; |
114 | $idStack = []; |
115 | $langConverter = MediaWikiServices::getInstance()->getLanguageConverterFactory() |
116 | ->getLanguageConverter( $title->getPageLanguage() ); |
117 | |
118 | for ( $i = 0; $title && $i < 20; $i++ ) { |
119 | $linkText = $langConverter->convert( $title->getSubpageText() ); |
120 | |
121 | // do not link the final breadcrumb |
122 | if ( $i === 0 ) { |
123 | $link = $linkText; |
124 | } else { |
125 | $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer(); |
126 | $link = $linkRenderer->makeLink( $title, $linkText ); |
127 | } |
128 | |
129 | // mark redirects with italics. |
130 | if ( $title->isRedirect() ) { |
131 | $link = Html::rawElement( 'i', [], $link ); |
132 | } |
133 | |
134 | // enclose the links with <bdi> tags. T318507 |
135 | $link = Html::rawElement( 'bdi', [], $link ); |
136 | |
137 | array_unshift( $breadCrumbs, $link ); |
138 | |
139 | // avoid cyclic trails |
140 | if ( in_array( $title->getArticleID(), $idStack ) ) { |
141 | $breadCrumbs[0] = Html::rawElement( 'strike', [], $breadCrumbs[0] ); |
142 | break; |
143 | } |
144 | $idStack[] = $title->getArticleID(); |
145 | |
146 | $parserOutput ??= self::getParserOutput( $title->getArticleID(), $user ); |
147 | if ( $parserOutput ) { |
148 | $title = self::getParentRegion( $parserOutput ); |
149 | // Reset so we can fetch parser output for the parent page |
150 | $parserOutput = null; |
151 | } else { |
152 | $title = null; |
153 | } |
154 | } |
155 | |
156 | return $breadCrumbs; |
157 | } |
158 | |
159 | /** |
160 | * @param ParserOutput $parserOutput |
161 | * @return Title|null |
162 | */ |
163 | public static function getParentRegion( ParserOutput $parserOutput ) { |
164 | $article = $parserOutput->getExtensionData( 'GeoCrumbIsIn' ); |
165 | if ( $article ) { |
166 | return Title::newFromID( $article['id'] ); |
167 | } |
168 | return null; |
169 | } |
170 | |
171 | /** |
172 | * @param int $pageId |
173 | * @param User $user |
174 | * @return bool|ParserOutput false if not found |
175 | */ |
176 | public static function getParserOutput( int $pageId, User $user ) { |
177 | if ( $pageId <= 0 ) { |
178 | return false; |
179 | } |
180 | |
181 | $page = MediaWikiServices::getInstance() |
182 | ->getWikiPageFactory() |
183 | ->newFromID( $pageId ); |
184 | if ( !$page ) { |
185 | return false; |
186 | } |
187 | return $page->getParserOutput( $page->makeParserOptions( $user ) ); |
188 | } |
189 | } |