Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 75 |
|
0.00% |
0 / 6 |
CRAP | |
0.00% |
0 / 1 |
SpecialViewObject | |
0.00% |
0 / 75 |
|
0.00% |
0 / 6 |
306 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getGroupName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getDescription | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 68 |
|
0.00% |
0 / 1 |
156 | |||
redirectToMain | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getRobotPolicy | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | /** |
4 | * WikiLambda Special:ViewObject page |
5 | * |
6 | * @file |
7 | * @ingroup Extensions |
8 | * @copyright 2020– Abstract Wikipedia team; see AUTHORS.txt |
9 | * @license MIT |
10 | */ |
11 | |
12 | namespace MediaWiki\Extension\WikiLambda\Special; |
13 | |
14 | use InvalidArgumentException; |
15 | use MediaWiki\Extension\WikiLambda\ZObjectContentHandler; |
16 | use MediaWiki\Extension\WikiLambda\ZObjectEditingPageTrait; |
17 | use MediaWiki\Extension\WikiLambda\ZObjectStore; |
18 | use MediaWiki\Extension\WikiLambda\ZObjectUtils; |
19 | use MediaWiki\Html\Html; |
20 | use MediaWiki\MediaWikiServices; |
21 | use MediaWiki\Output\OutputPage; |
22 | use MediaWiki\SpecialPage\SpecialPage; |
23 | use MediaWiki\Title\Title; |
24 | use ParserOptions; |
25 | use RuntimeException; |
26 | |
27 | class SpecialViewObject extends SpecialPage { |
28 | use ZObjectEditingPageTrait; |
29 | |
30 | private ZObjectStore $zObjectStore; |
31 | |
32 | /** |
33 | * @param ZObjectStore $zObjectStore |
34 | */ |
35 | public function __construct( ZObjectStore $zObjectStore ) { |
36 | parent::__construct( 'ViewObject', 'view', false ); |
37 | $this->zObjectStore = $zObjectStore; |
38 | } |
39 | |
40 | /** |
41 | * @inheritDoc |
42 | */ |
43 | protected function getGroupName() { |
44 | // Triggers use of message specialpages-group-wikilambda |
45 | return 'wikilambda'; |
46 | } |
47 | |
48 | /** |
49 | * @inheritDoc |
50 | */ |
51 | public function getDescription() { |
52 | return $this->msg( 'wikilambda-special-viewobject' ); |
53 | } |
54 | |
55 | /** |
56 | * @inheritDoc |
57 | */ |
58 | public function execute( $subPage ) { |
59 | // TODO (T362246): Dependency-inject |
60 | $services = MediaWikiServices::getInstance(); |
61 | |
62 | $outputPage = $this->getOutput(); |
63 | |
64 | // Make sure things don't think the page is wikitext, so that e.g. VisualEditor |
65 | // doesn't try to instantiate its tabs |
66 | $outputPage->getTitle()->setContentModel( CONTENT_MODEL_ZOBJECT ); |
67 | |
68 | // If there's no sub-page, just exit. |
69 | if ( !$subPage || !is_string( $subPage ) ) { |
70 | $this->redirectToMain( $outputPage ); |
71 | return; |
72 | } |
73 | |
74 | $subPageSplit = []; |
75 | if ( !preg_match( '/([^\/]+)\/(Z\d+)/', $subPage, $subPageSplit ) ) { |
76 | // Fallback to 'en' if request doesn't specify a language. |
77 | $targetLanguage = 'en'; |
78 | $targetPageName = $subPage; |
79 | } else { |
80 | $targetLanguage = $subPageSplit[1]; |
81 | $targetPageName = $subPageSplit[2]; |
82 | } |
83 | |
84 | // Allow the user to over-ride the content language if explicitly requested |
85 | $targetLanguage = $this->getRequest()->getRawVal( 'uselang' ) ?? $targetLanguage; |
86 | |
87 | $targetTitle = Title::newFromText( $targetPageName, NS_MAIN ); |
88 | |
89 | if ( |
90 | // If the given page isn't a Title |
91 | !( $targetTitle instanceof Title ) || !$targetTitle->exists() |
92 | // … or somehow it's not for a valid ZObject |
93 | || !ZObjectUtils::isValidId( $targetPageName ) |
94 | ) { |
95 | $this->redirectToMain( $outputPage ); |
96 | return; |
97 | } |
98 | |
99 | // (T345457) Tell OutputPage that our content is article-related, so we get Special:WhatLinksHere etc. |
100 | $outputPage->setArticleFlag( true ); |
101 | // Tell the skin what content specifically we're related to, so edit/history links etc. work. |
102 | $this->getSkin()->setRelevantTitle( $targetTitle ); |
103 | |
104 | // (T345453) Have the standard copyright stuff show up. |
105 | $this->getContext()->getOutput()->setCopyright( true ); |
106 | |
107 | $this->setHeaders(); |
108 | |
109 | try { |
110 | $targetLanguageObject = $services->getLanguageFactory()->getLanguage( $targetLanguage ); |
111 | } catch ( InvalidArgumentException $e ) { |
112 | // (T343006) Supplied language is invalid; probably a user-error, so just exit. |
113 | $this->redirectToMain( $outputPage ); |
114 | return; |
115 | } |
116 | |
117 | // Set the page language for our own purposes. |
118 | $this->getContext()->setLanguage( $targetLanguageObject ); |
119 | |
120 | $outputPage->addModules( [ 'ext.wikilambda.edit', 'mediawiki.special' ] ); |
121 | |
122 | $targetContent = $this->zObjectStore->fetchZObjectByTitle( $targetTitle ); |
123 | |
124 | if ( !$targetContent ) { |
125 | $this->redirectToMain( $outputPage ); |
126 | } |
127 | |
128 | // Request that we render the content in the given target language. |
129 | $parserOptions = ParserOptions::newFromUserAndLang( $this->getUser(), $targetLanguageObject ); |
130 | |
131 | $contentRenderer = $services->getContentRenderer(); |
132 | |
133 | $parserOutput = $contentRenderer->getParserOutput( |
134 | $targetContent, |
135 | $targetTitle, |
136 | null, |
137 | $parserOptions |
138 | ); |
139 | |
140 | // Add the header to the parserOutput |
141 | $header = ZObjectContentHandler::createZObjectViewHeader( $targetContent, $targetTitle, $targetLanguageObject ); |
142 | $outputPage->setPageTitle( $header ); |
143 | |
144 | $outputPage->addParserOutput( $parserOutput ); |
145 | |
146 | // Add all the see-other links to versions of this page in each of the known languages. |
147 | $languages = $this->zObjectStore->fetchAllZLanguageObjects(); |
148 | foreach ( $languages as $zid => $bcpcode ) { |
149 | if ( $bcpcode === $targetLanguage ) { |
150 | continue; |
151 | } |
152 | // Add each item individually to help phan understand the taint better, even though it's slower |
153 | $outputPage->addHeadItem( |
154 | 'link-alternate-language-' . strtolower( $bcpcode ), |
155 | Html::element( |
156 | 'link', |
157 | [ |
158 | 'rel' => 'alternate', |
159 | 'hreflang' => $bcpcode, |
160 | 'href' => "/view/$bcpcode/$targetPageName", |
161 | ] |
162 | ) |
163 | ); |
164 | } |
165 | |
166 | // (T355546) Over-ride the canonical URL to the /view/ form. |
167 | $urlUtils = $services->getUrlUtils(); |
168 | $viewURL = $urlUtils->expand( "/view/$targetLanguage/$targetPageName" ); |
169 | // $viewURL can be null 'if no valid URL can be constructed', which shouldn't ever happen. |
170 | if ( $viewURL === null ) { |
171 | throw new RuntimeException( 'No valid URL could be constructed for the canonical path' ); |
172 | } |
173 | $outputPage->setCanonicalUrl( $viewURL ); |
174 | |
175 | // TODO (T362241): Make this help page. |
176 | $this->addHelpLink( 'Help:Wikifunctions/Viewing Objects' ); |
177 | |
178 | $this->generateZObjectPayload( $outputPage, $this->getContext(), [ |
179 | 'createNewPage' => false, |
180 | 'zId' => $targetPageName, |
181 | 'viewmode' => true, |
182 | ] ); |
183 | } |
184 | |
185 | /** |
186 | * Redirect the user to the Main Page, as their request isn't valid / answerable. |
187 | * |
188 | * TODO (T343652): Actually tell the user why they ended up somewhere they might not want? |
189 | * |
190 | * @param OutputPage $outputPage |
191 | */ |
192 | private function redirectToMain( OutputPage $outputPage ) { |
193 | // We use inContentLanguage() to get it in English, rather than redirecting to non-existent pages |
194 | // like https://www.wikifunctions.org/wiki/Strona_g%C5%82%C3%B3wna if the user's language is pl. |
195 | $mainPageUrl = '/wiki/' . $outputPage->msg( 'Mainpage' )->inContentLanguage()->text(); |
196 | $outputPage->redirect( $mainPageUrl, 303 ); |
197 | } |
198 | |
199 | /** |
200 | * (T355441) Unlike regular Special pages, we actively want search engines to |
201 | * index our content and follow our links. |
202 | * |
203 | * @inheritDoc |
204 | */ |
205 | protected function getRobotPolicy() { |
206 | return 'index,follow'; |
207 | } |
208 | } |