Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
1.17% |
10 / 857 |
|
2.44% |
1 / 41 |
CRAP | |
0.00% |
0 / 1 |
SpecialCollection | |
1.17% |
10 / 857 |
|
2.44% |
1 / 41 |
52643.66 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
doesWrites | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getDescription | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 180 |
|
0.00% |
0 / 1 |
3192 | |||
processSuggestCommand | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
56 | |||
processSaveCollectionCommand | |
0.00% |
0 / 31 |
|
0.00% |
0 / 1 |
156 | |||
renderBookCreatorPage | |
0.00% |
0 / 46 |
|
0.00% |
0 / 1 |
30 | |||
renderStopBookCreatorPage | |
0.00% |
0 / 33 |
|
0.00% |
0 / 1 |
2 | |||
getBookPagePrefixes | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
12 | |||
renderSpecialPage | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
6 | |||
setTitles | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
setSettings | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
sortByTitle | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
sortItems | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
42 | |||
addChapter | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
12 | |||
renameChapter | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
addArticleFromName | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
addArticle | |
0.00% |
0 / 30 |
|
0.00% |
0 / 1 |
30 | |||
removeArticleFromName | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
removeArticle | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
addCategoryFromName | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
addCategory | |
0.00% |
0 / 27 |
|
0.00% |
0 / 1 |
56 | |||
limitExceeded | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
removeItem | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
12 | |||
moveItem | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
12 | |||
moveItemInCollection | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
5 | |||
setSorting | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
20 | |||
parseCollectionLine | |
0.00% |
0 / 57 |
|
0.00% |
0 / 1 |
342 | |||
loadCollection | |
0.00% |
0 / 24 |
|
0.00% |
0 / 1 |
42 | |||
saveCollection | |
0.00% |
0 / 48 |
|
0.00% |
0 / 1 |
342 | |||
renderCollection | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
20 | |||
forceRenderCollection | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
20 | |||
renderRenderingPage | |
0.00% |
0 / 79 |
|
0.00% |
0 / 1 |
156 | |||
download | |
0.00% |
0 / 45 |
|
0.00% |
0 / 1 |
56 | |||
makeCollection | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
20 | |||
applySettings | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
72 | |||
postZip | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
20 | |||
renderSaveOverwritePage | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 | |||
renderLoadOverwritePage | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
handleResult | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 | |||
getGroupName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * Collection Extension for MediaWiki |
4 | * |
5 | * Copyright (C) PediaPress GmbH |
6 | * |
7 | * This program is free software; you can redistribute it and/or modify |
8 | * it under the terms of the GNU General Public License as published by |
9 | * the Free Software Foundation; either version 2 of the License, or |
10 | * (at your option) any later version. |
11 | * |
12 | * This program is distributed in the hope that it will be useful, |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | * GNU General Public License for more details. |
16 | * |
17 | * You should have received a copy of the GNU General Public License along |
18 | * with this program; if not, write to the Free Software Foundation, Inc., |
19 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
20 | * http://www.gnu.org/copyleft/gpl.html |
21 | */ |
22 | |
23 | namespace MediaWiki\Extension\Collection\Specials; |
24 | |
25 | use ApiMain; |
26 | use MediaWiki\Extension\Collection\MessageBoxHelper; |
27 | use MediaWiki\Extension\Collection\Rendering\CollectionAPIResult; |
28 | use MediaWiki\Extension\Collection\Rendering\CollectionRenderingAPI; |
29 | use MediaWiki\Extension\Collection\Session as CollectionSession; |
30 | use MediaWiki\Extension\Collection\Suggest; |
31 | use MediaWiki\Extension\Collection\Templates\CollectionFailedTemplate; |
32 | use MediaWiki\Extension\Collection\Templates\CollectionFinishedTemplate; |
33 | use MediaWiki\Extension\Collection\Templates\CollectionLoadOverwriteTemplate; |
34 | use MediaWiki\Extension\Collection\Templates\CollectionPageTemplate; |
35 | use MediaWiki\Extension\Collection\Templates\CollectionRenderingTemplate; |
36 | use MediaWiki\Extension\Collection\Templates\CollectionSaveOverwriteTemplate; |
37 | use MediaWiki\Html\Html; |
38 | use MediaWiki\MediaWikiServices; |
39 | use MediaWiki\Request\DerivativeRequest; |
40 | use MediaWiki\Request\WebRequest; |
41 | use MediaWiki\SpecialPage\SpecialPage; |
42 | use MediaWiki\Title\Title; |
43 | use OOUI\ButtonGroupWidget; |
44 | use OOUI\ButtonInputWidget; |
45 | use OOUI\ButtonWidget; |
46 | use OOUI\FormLayout; |
47 | use RequestContext; |
48 | use SkinTemplate; |
49 | use UnexpectedValueException; |
50 | |
51 | class SpecialCollection extends SpecialPage { |
52 | |
53 | /** @var resource */ |
54 | private $tempfile; |
55 | |
56 | /** @var false|array[] */ |
57 | private $mPODPartners; |
58 | |
59 | /** |
60 | * @param false|array[] $PODPartners |
61 | */ |
62 | public function __construct( $PODPartners = false ) { |
63 | parent::__construct( "Book" ); |
64 | global $wgCollectionPODPartners; |
65 | if ( $PODPartners ) { |
66 | $this->mPODPartners = $PODPartners; |
67 | } else { |
68 | $this->mPODPartners = $wgCollectionPODPartners; |
69 | } |
70 | } |
71 | |
72 | public function doesWrites() { |
73 | return true; |
74 | } |
75 | |
76 | /** |
77 | * @return string |
78 | */ |
79 | public function getDescription() { |
80 | return $this->msg( 'coll-collection' ); |
81 | } |
82 | |
83 | /** |
84 | * @param null|string $par |
85 | */ |
86 | public function execute( $par ) { |
87 | global $wgCollectionMaxArticles; |
88 | |
89 | $out = $this->getOutput(); |
90 | $request = $this->getRequest(); |
91 | |
92 | // support previous URLs (e.g. used in templates) which used the "$par" part |
93 | // (i.e. subpages of the Special page) |
94 | if ( $par ) { |
95 | if ( $request->wasPosted() ) { // don't redirect POST reqs |
96 | // TODO |
97 | } |
98 | $out->redirect( wfAppendQuery( |
99 | SkinTemplate::makeSpecialUrl( 'Book' ), |
100 | $request->appendQueryArray( [ 'bookcmd' => rtrim( $par, '/' ) ] ) |
101 | ) ); |
102 | return; |
103 | } |
104 | |
105 | switch ( $request->getVal( 'bookcmd', '' ) ) { |
106 | case 'book_creator': |
107 | $this->renderBookCreatorPage( $request->getVal( 'referer', '' ), $par ); |
108 | return; |
109 | |
110 | case 'start_book_creator': |
111 | $title = Title::newFromText( $request->getVal( 'referer', '' ) ); |
112 | if ( $title === null ) { |
113 | $title = Title::newMainPage(); |
114 | } |
115 | if ( $request->getVal( 'confirm' ) ) { |
116 | CollectionSession::enable(); |
117 | } |
118 | $out->redirect( $title->getFullURL() ); |
119 | return; |
120 | |
121 | case 'stop_book_creator': |
122 | $title = Title::newFromText( $request->getVal( 'referer', '' ) ); |
123 | if ( $title === null || $title->equals( $this->getPageTitle( $par ) ) ) { |
124 | $title = Title::newMainPage(); |
125 | } |
126 | if ( $request->getVal( 'confirm' ) ) { |
127 | CollectionSession::disable(); |
128 | } elseif ( !$request->getVal( 'continue' ) ) { |
129 | $this->renderStopBookCreatorPage( $title ); |
130 | return; |
131 | } |
132 | $out->redirect( $title->getFullURL() ); |
133 | return; |
134 | |
135 | case 'add_article': |
136 | if ( CollectionSession::countArticles() >= $wgCollectionMaxArticles ) { |
137 | self::limitExceeded(); |
138 | return; |
139 | } |
140 | $oldid = $request->getInt( 'oldid', 0 ); |
141 | $title = Title::newFromText( $request->getVal( 'arttitle', '' ) ); |
142 | if ( !$title ) { |
143 | return; |
144 | } |
145 | if ( self::addArticle( $title, $oldid ) ) { |
146 | if ( $oldid == 0 ) { |
147 | $redirectURL = $title->getFullURL(); |
148 | } else { |
149 | $redirectURL = $title->getFullURL( 'oldid=' . $oldid ); |
150 | } |
151 | $out->redirect( $redirectURL ); |
152 | } else { |
153 | $out->showErrorPage( |
154 | 'coll-couldnotaddarticle_title', |
155 | 'coll-couldnotaddarticle_msg' |
156 | ); |
157 | } |
158 | return; |
159 | |
160 | case 'remove_article': |
161 | $oldid = $request->getInt( 'oldid', 0 ); |
162 | $title = Title::newFromText( $request->getVal( 'arttitle', '' ) ); |
163 | if ( !$title ) { |
164 | return; |
165 | } |
166 | if ( self::removeArticle( $title, $oldid ) ) { |
167 | if ( $oldid == 0 ) { |
168 | $redirectURL = $title->getFullURL(); |
169 | } else { |
170 | $redirectURL = $title->getFullURL( 'oldid=' . $oldid ); |
171 | } |
172 | $out->redirect( $redirectURL ); |
173 | } else { |
174 | $out->showErrorPage( |
175 | 'coll-couldnotremovearticle_title', |
176 | 'coll-couldnotremovearticle_msg' |
177 | ); |
178 | } |
179 | return; |
180 | |
181 | case 'clear_collection': |
182 | CollectionSession::clearCollection(); |
183 | $redirect = $request->getVal( 'return_to', '' ); |
184 | $redirectURL = SkinTemplate::makeSpecialUrl( 'Book' ); |
185 | if ( $redirect !== '' ) { |
186 | $title = Title::newFromText( $redirect ); |
187 | if ( $title ) { |
188 | $redirectURL = $title->getFullURL(); |
189 | } |
190 | } |
191 | $out->redirect( $redirectURL ); |
192 | return; |
193 | |
194 | case 'set_titles': |
195 | self::setTitles( |
196 | $request->getText( 'collectionTitle', '' ), |
197 | $request->getText( 'collectionSubtitle', '' ) |
198 | ); |
199 | $out->redirect( SkinTemplate::makeSpecialUrl( 'Book' ) ); |
200 | return; |
201 | |
202 | case 'sort_items': |
203 | self::sortItems(); |
204 | $out->redirect( SkinTemplate::makeSpecialUrl( 'Book' ) ); |
205 | return; |
206 | |
207 | case 'add_category': |
208 | $title = Title::makeTitleSafe( NS_CATEGORY, $request->getVal( 'cattitle', '' ) ); |
209 | if ( !$title ) { |
210 | return; |
211 | } elseif ( self::addCategory( $title ) ) { |
212 | self::limitExceeded(); |
213 | return; |
214 | } else { |
215 | $out->redirect( $request->getVal( 'return_to', $title->getFullURL() ) ); |
216 | } |
217 | return; |
218 | |
219 | case 'remove_item': |
220 | self::removeItem( $request->getInt( 'index', 0 ) ); |
221 | $out->redirect( SkinTemplate::makeSpecialUrl( 'Book' ) ); |
222 | return; |
223 | |
224 | case 'move_item': |
225 | self::moveItem( $request->getInt( 'index', 0 ), $request->getInt( 'delta', 0 ) ); |
226 | $out->redirect( SkinTemplate::makeSpecialUrl( 'Book' ) ); |
227 | return; |
228 | |
229 | case 'load_collection': |
230 | $title = Title::newFromText( $request->getVal( 'colltitle', '' ) ); |
231 | if ( !$title ) { |
232 | return; |
233 | } |
234 | if ( $request->getVal( 'cancel' ) ) { |
235 | $out->redirect( $title->getFullURL() ); |
236 | return; |
237 | } |
238 | if ( !CollectionSession::countArticles() |
239 | || $request->getVal( 'overwrite' ) |
240 | || $request->getVal( 'append' ) |
241 | ) { |
242 | $collection = $this->loadCollection( $title, $request->getBool( 'append' ) ); |
243 | if ( $collection ) { |
244 | CollectionSession::startSession(); |
245 | CollectionSession::setCollection( $collection ); |
246 | CollectionSession::enable(); |
247 | $out->redirect( SkinTemplate::makeSpecialUrl( 'Book' ) ); |
248 | } |
249 | return; |
250 | } |
251 | $this->renderLoadOverwritePage( $title ); |
252 | return; |
253 | |
254 | case 'order_collection': |
255 | $title = Title::newFromText( $request->getVal( 'colltitle', '' ) ); |
256 | if ( !$title ) { |
257 | return; |
258 | } |
259 | $collection = $this->loadCollection( $title ); |
260 | if ( $collection ) { |
261 | $partner = $request->getVal( 'partner', key( $this->mPODPartners ) ); |
262 | $this->postZip( $collection, $partner ); |
263 | } |
264 | return; |
265 | |
266 | case 'save_collection': |
267 | $this->processSaveCollectionCommand(); |
268 | return; |
269 | |
270 | case 'render': |
271 | $this->renderCollection( |
272 | CollectionSession::getCollection(), |
273 | SpecialPage::getTitleFor( 'Book' ), |
274 | $request->getVal( 'writer', '' ) |
275 | ); |
276 | return; |
277 | |
278 | case 'forcerender': |
279 | $this->forceRenderCollection(); |
280 | return; |
281 | |
282 | case 'rendering': |
283 | $this->renderRenderingPage(); |
284 | return; |
285 | |
286 | case 'download': |
287 | $this->download(); |
288 | return; |
289 | |
290 | case 'render_article': |
291 | $title = Title::newFromText( $request->getVal( 'arttitle', '' ) ); |
292 | if ( !$title ) { |
293 | return; |
294 | } |
295 | $oldid = $request->getInt( 'oldid', 0 ); |
296 | $collection = $this->makeCollection( $title, $oldid ); |
297 | if ( $collection ) { |
298 | $this->applySettings( $collection, $request ); |
299 | $this->renderCollection( $collection, $title, $request->getVal( 'writer', 'rl' ) ); |
300 | } |
301 | return; |
302 | |
303 | case 'render_collection': |
304 | $title = Title::newFromText( $request->getVal( 'colltitle', '' ) ); |
305 | if ( !$title ) { |
306 | return; |
307 | } |
308 | $collection = $this->loadCollection( $title ); |
309 | if ( $collection ) { |
310 | $this->applySettings( $collection, $request ); |
311 | $this->renderCollection( $collection, $title, $request->getVal( 'writer', 'rl' ) ); |
312 | } |
313 | return; |
314 | |
315 | case 'post_zip': |
316 | $partner = $request->getVal( 'partner', 'pediapress' ); |
317 | $this->postZip( CollectionSession::getCollection(), $partner ); |
318 | return; |
319 | |
320 | case 'suggest': |
321 | $this->processSuggestCommand(); |
322 | return; |
323 | |
324 | case '': |
325 | $this->renderSpecialPage(); |
326 | return; |
327 | |
328 | default: |
329 | $out->showErrorPage( 'coll-unknown_subpage_title', 'coll-unknown_subpage_text' ); |
330 | } |
331 | } |
332 | |
333 | /** |
334 | * Processes the suggest command |
335 | */ |
336 | private function processSuggestCommand() { |
337 | $request = $this->getRequest(); |
338 | |
339 | $add = $request->getVal( 'add' ); |
340 | $ban = $request->getVal( 'ban' ); |
341 | $remove = $request->getVal( 'remove' ); |
342 | $addselected = $request->getVal( 'addselected' ); |
343 | |
344 | if ( $request->getVal( 'resetbans' ) ) { |
345 | Suggest::run( 'resetbans' ); |
346 | } elseif ( $add !== null ) { |
347 | Suggest::run( 'add', $add ); |
348 | } elseif ( $ban !== null ) { |
349 | Suggest::run( 'ban', $ban ); |
350 | } elseif ( $remove !== null ) { |
351 | Suggest::run( 'remove', $remove ); |
352 | } elseif ( $addselected !== null ) { |
353 | $articleList = $request->getArray( 'articleList' ); |
354 | if ( $articleList !== null ) { |
355 | Suggest::run( 'addAll', $articleList ); |
356 | } else { |
357 | Suggest::run(); |
358 | } |
359 | } else { |
360 | Suggest::run(); |
361 | } |
362 | } |
363 | |
364 | /** |
365 | * Processes the save book command |
366 | */ |
367 | private function processSaveCollectionCommand() { |
368 | $out = $this->getOutput(); |
369 | $request = $this->getRequest(); |
370 | $user = $this->getUser(); |
371 | |
372 | if ( $request->getVal( 'abort' ) ) { |
373 | $out->redirect( SkinTemplate::makeSpecialUrl( 'Book' ) ); |
374 | return; |
375 | } |
376 | if ( !$user->matchEditToken( $request->getVal( 'token' ) ) ) { |
377 | return; |
378 | } |
379 | |
380 | $colltype = $request->getVal( 'colltype' ); |
381 | $prefixes = $this->getBookPagePrefixes(); |
382 | $title = null; |
383 | if ( $colltype == 'personal' ) { |
384 | $collname = $request->getVal( 'pcollname', '' ); |
385 | if ( !$user->isAllowed( 'collectionsaveasuserpage' ) || $collname === '' ) { |
386 | return; |
387 | } |
388 | $title = Title::newFromText( $prefixes['user-prefix'] . $collname ); |
389 | } elseif ( $colltype == 'community' ) { |
390 | $collname = $request->getVal( 'ccollname', '' ); |
391 | if ( !$user->isAllowed( 'collectionsaveascommunitypage' ) || $collname === '' ) { |
392 | return; |
393 | } |
394 | $title = Title::newFromText( $prefixes['community-prefix'] . $collname ); |
395 | } |
396 | if ( !$title || !$colltype ) { |
397 | return; |
398 | } |
399 | |
400 | if ( $this->saveCollection( $title, $request->getBool( 'overwrite' ) ) ) { |
401 | $out->redirect( $title->getFullURL() ); |
402 | } else { |
403 | $this->renderSaveOverwritePage( |
404 | $colltype, |
405 | $title, |
406 | $request->getVal( 'pcollname' ), |
407 | $request->getVal( 'ccollname' ) |
408 | ); |
409 | } |
410 | } |
411 | |
412 | /** |
413 | * @param string $referer |
414 | * @param string $par |
415 | */ |
416 | private function renderBookCreatorPage( $referer, $par ) { |
417 | $out = $this->getOutput(); |
418 | $out->enableOOUI(); |
419 | |
420 | $this->setHeaders(); |
421 | $out->setPageTitleMsg( $this->msg( 'coll-book_creator' ) ); |
422 | |
423 | MessageBoxHelper::addModuleStyles( $out ); |
424 | $out->addHTML( MessageBoxHelper::renderWarningBoxes() ); |
425 | $out->addWikiMsg( 'coll-book_creator_intro' ); |
426 | |
427 | $out->addModules( 'ext.collection.checkLoadFromLocalStorage' ); |
428 | |
429 | $title = Title::newFromText( $referer ); |
430 | if ( $title === null || $title->equals( $this->getPageTitle( $par ) ) ) { |
431 | $title = Title::newMainPage(); |
432 | } |
433 | |
434 | $form = new FormLayout( [ |
435 | 'method' => 'POST', |
436 | 'action' => SkinTemplate::makeSpecialUrl( |
437 | 'Book', |
438 | [ |
439 | 'bookcmd' => 'start_book_creator', |
440 | 'referer' => $referer, |
441 | ] |
442 | ), |
443 | ] ); |
444 | $form->appendContent( new ButtonGroupWidget( [ |
445 | 'items' => [ |
446 | new ButtonInputWidget( [ |
447 | 'type' => 'submit', |
448 | 'name' => 'confirm', |
449 | 'value' => 'yes', |
450 | 'flags' => [ 'primary', 'progressive' ], |
451 | 'label' => $this->msg( 'coll-start_book_creator' )->text(), |
452 | ] ), |
453 | new ButtonWidget( [ |
454 | 'href' => $title->getLinkURL(), |
455 | 'title' => $title->getPrefixedText(), |
456 | 'label' => $this->msg( 'coll-cancel' )->text(), |
457 | 'noFollow' => true, |
458 | ] ), |
459 | ], |
460 | ] ) ); |
461 | |
462 | $out->addHTML( $form ); |
463 | |
464 | $title_string = $this->msg( 'coll-book_creator_text_article' )->inContentLanguage()->text(); |
465 | $t = Title::newFromText( $title_string ); |
466 | if ( $t !== null ) { |
467 | if ( $t->exists() ) { |
468 | $out->addWikiTextAsInterface( '{{:' . $title_string . '}}' ); |
469 | return; |
470 | } |
471 | } |
472 | $out->addWikiMsg( 'coll-book_creator_help' ); |
473 | } |
474 | |
475 | /** |
476 | * @param string $referer |
477 | */ |
478 | private function renderStopBookCreatorPage( $referer ) { |
479 | $out = $this->getOutput(); |
480 | $out->enableOOUI(); |
481 | |
482 | $this->setHeaders(); |
483 | $out->setPageTitleMsg( $this->msg( 'coll-book_creator_disable' ) ); |
484 | $out->addWikiMsg( 'coll-book_creator_disable_text' ); |
485 | |
486 | $form = new FormLayout( [ |
487 | 'method' => 'POST', |
488 | 'action' => SkinTemplate::makeSpecialUrl( |
489 | 'Book', |
490 | [ |
491 | 'bookcmd' => 'stop_book_creator', |
492 | 'referer' => $referer, |
493 | ] |
494 | ), |
495 | ] ); |
496 | $form->appendContent( new ButtonGroupWidget( [ |
497 | 'items' => [ |
498 | new ButtonInputWidget( [ |
499 | 'type' => 'submit', |
500 | 'name' => 'continue', |
501 | 'value' => 'yes', |
502 | 'label' => $this->msg( 'coll-book_creator_continue' )->text(), |
503 | ] ), |
504 | new ButtonInputWidget( [ |
505 | 'type' => 'submit', |
506 | 'name' => 'confirm', |
507 | 'value' => 'yes', |
508 | 'label' => $this->msg( 'coll-book_creator_disable' )->text(), |
509 | 'flags' => [ 'primary', 'destructive' ], |
510 | ] ), |
511 | ], |
512 | ] ) ); |
513 | |
514 | $out->addHTML( $form ); |
515 | } |
516 | |
517 | /** |
518 | * @return array |
519 | */ |
520 | private function getBookPagePrefixes() { |
521 | $result = []; |
522 | $user = $this->getUser(); |
523 | $communityCollectionNamespace = $this->getConfig()->get( 'CommunityCollectionNamespace' ); |
524 | |
525 | $t = $this->msg( 'coll-user_book_prefix', $user->getName() )->inContentLanguage(); |
526 | if ( $t->isDisabled() ) { |
527 | $userPageTitle = $user->getUserPage()->getPrefixedText(); |
528 | $result['user-prefix'] = $userPageTitle . '/' |
529 | . $this->msg( 'coll-collections' )->inContentLanguage()->text() . '/'; |
530 | } else { |
531 | $result['user-prefix'] = $t->text(); |
532 | } |
533 | |
534 | $comBookPrefix = $this->msg( 'coll-community_book_prefix' )->inContentLanguage(); |
535 | if ( $comBookPrefix->isDisabled() ) { |
536 | $title = Title::makeTitle( |
537 | $communityCollectionNamespace, |
538 | $this->msg( 'coll-collections' )->inContentLanguage()->text() |
539 | ); |
540 | $result['community-prefix'] = $title->getPrefixedText() . '/'; |
541 | } else { |
542 | $result['community-prefix'] = $comBookPrefix->text(); |
543 | } |
544 | return $result; |
545 | } |
546 | |
547 | private function renderSpecialPage() { |
548 | global $wgCollectionFormats, $wgCollectionRendererSettings, |
549 | $wgCollectionDisableDownloadSection; |
550 | |
551 | if ( !CollectionSession::hasSession() ) { |
552 | CollectionSession::startSession(); |
553 | } |
554 | |
555 | $out = $this->getOutput(); |
556 | MessageBoxHelper::addModuleStyles( $out ); |
557 | |
558 | $this->setHeaders(); |
559 | $this->addHelpLink( 'Special:MyLanguage/Extension:Collection/Help' ); |
560 | $out->setPageTitleMsg( $this->msg( 'coll-manage_your_book' ) ); |
561 | $out->addModules( 'ext.collection' ); |
562 | $out->addModuleStyles( [ 'mediawiki.hlist', 'ext.collection.bookcreator.styles' ] ); |
563 | $out->addJsConfigVars( [ |
564 | 'wgCollectionDisableDownloadSection' => $wgCollectionDisableDownloadSection |
565 | ] ); |
566 | |
567 | $template = new CollectionPageTemplate(); |
568 | $template->set( 'context', $this->getContext() ); |
569 | $template->set( 'collection', CollectionSession::getCollection() ); |
570 | $template->set( 'podpartners', $this->mPODPartners ); |
571 | $template->set( 'settings', $wgCollectionRendererSettings ); |
572 | $template->set( 'formats', $wgCollectionFormats ); |
573 | $prefixes = $this->getBookPagePrefixes(); |
574 | $template->set( 'user-book-prefix', $prefixes['user-prefix'] ); |
575 | $template->set( 'community-book-prefix', $prefixes['community-prefix'] ); |
576 | $out->addTemplate( $template ); |
577 | } |
578 | |
579 | /** |
580 | * @param string $title |
581 | * @param string $subtitle |
582 | */ |
583 | public static function setTitles( $title, $subtitle ) { |
584 | $collection = CollectionSession::getCollection(); |
585 | $collection['title'] = $title; |
586 | $collection['subtitle'] = $subtitle; |
587 | CollectionSession::setCollection( $collection ); |
588 | } |
589 | |
590 | /** |
591 | * @param array $settings |
592 | */ |
593 | public static function setSettings( array $settings ) { |
594 | $collection = CollectionSession::getCollection(); |
595 | if ( !isset( $collection['settings'] ) ) { |
596 | $collection['settings'] = []; |
597 | } |
598 | $collection['settings'] = $settings + $collection['settings']; |
599 | CollectionSession::setCollection( $collection ); |
600 | } |
601 | |
602 | /** |
603 | * @param array &$items |
604 | */ |
605 | private static function sortByTitle( array &$items ) { |
606 | usort( $items, static function ( $a, $b ) { |
607 | return strcasecmp( $a['title'], $b['title'] ); |
608 | } ); |
609 | } |
610 | |
611 | public static function sortItems() { |
612 | $collection = CollectionSession::getCollection(); |
613 | if ( !isset( $collection['items'] ) || !is_array( $collection['items'] ) ) { |
614 | $collection['items'] = []; |
615 | CollectionSession::setCollection( $collection ); |
616 | return; |
617 | } |
618 | |
619 | $articles = []; |
620 | $new_items = []; |
621 | foreach ( $collection['items'] as $item ) { |
622 | '@phan-var array $item'; |
623 | if ( $item['type'] == 'chapter' ) { |
624 | self::sortByTitle( $articles ); |
625 | $new_items = array_merge( $new_items, $articles, [ $item ] ); |
626 | $articles = []; |
627 | } elseif ( $item['type'] == 'article' ) { |
628 | $articles[] = $item; |
629 | } |
630 | } |
631 | self::sortByTitle( $articles ); |
632 | $collection['items'] = array_merge( $new_items, $articles ); |
633 | CollectionSession::setCollection( $collection ); |
634 | } |
635 | |
636 | /** |
637 | * @param string $name |
638 | */ |
639 | public static function addChapter( $name ) { |
640 | $collection = CollectionSession::getCollection(); |
641 | if ( !isset( $collection['items'] ) || !is_array( $collection['items'] ) ) { |
642 | $collection['items'] = []; |
643 | } |
644 | array_push( $collection['items'], [ |
645 | 'type' => 'chapter', |
646 | 'title' => $name, |
647 | ] ); |
648 | CollectionSession::setCollection( $collection ); |
649 | } |
650 | |
651 | /** |
652 | * @param int $index |
653 | * @param string $name |
654 | */ |
655 | public static function renameChapter( $index, $name ) { |
656 | if ( !is_int( $index ) ) { |
657 | return; |
658 | } |
659 | $collection = CollectionSession::getCollection(); |
660 | |
661 | // T293261: Make sure the index exist in the array |
662 | if ( !array_key_exists( $index, $collection['items'] ) || |
663 | $collection['items'][$index]['type'] !== 'chapter' |
664 | ) { |
665 | return; |
666 | } |
667 | |
668 | $collection['items'][$index]['title'] = $name; |
669 | CollectionSession::setCollection( $collection ); |
670 | } |
671 | |
672 | /** |
673 | * @param int $namespace |
674 | * @param string $name |
675 | * @param int $oldid |
676 | * @return bool |
677 | */ |
678 | public static function addArticleFromName( $namespace, $name, $oldid = 0 ) { |
679 | $title = Title::makeTitleSafe( $namespace, $name ); |
680 | if ( !$title ) { |
681 | return false; |
682 | } |
683 | return self::addArticle( $title, $oldid ); |
684 | } |
685 | |
686 | /** |
687 | * @param Title $title |
688 | * @param int $oldid |
689 | * @return bool |
690 | */ |
691 | private static function addArticle( $title, $oldid = 0 ) { |
692 | $latest = $title->getLatestRevID(); |
693 | |
694 | $currentVersion = 0; |
695 | if ( $oldid == 0 ) { |
696 | $currentVersion = 1; |
697 | $oldid = $latest; |
698 | } |
699 | |
700 | $prefixedText = $title->getPrefixedText(); |
701 | |
702 | $index = CollectionSession::findArticle( $prefixedText, $oldid ); |
703 | if ( $index != -1 ) { |
704 | return false; |
705 | } |
706 | |
707 | if ( !CollectionSession::hasSession() ) { |
708 | CollectionSession::startSession(); |
709 | } |
710 | $collection = CollectionSession::getCollection(); |
711 | $revision = MediaWikiServices::getInstance() |
712 | ->getRevisionLookup() |
713 | ->getRevisionByTitle( $title, $oldid ); |
714 | if ( !$revision ) { |
715 | return false; |
716 | } |
717 | |
718 | $item = [ |
719 | 'type' => 'article', |
720 | 'content_type' => 'text/x-wiki', |
721 | 'title' => $prefixedText, |
722 | 'revision' => strval( $oldid ), |
723 | 'latest' => strval( $latest ), |
724 | 'timestamp' => wfTimestamp( TS_UNIX, $revision->getTimestamp() ), |
725 | 'url' => $title->getCanonicalURL(), |
726 | 'currentVersion' => $currentVersion, |
727 | ]; |
728 | |
729 | $collection['items'][] = $item; |
730 | CollectionSession::setCollection( $collection ); |
731 | return true; |
732 | } |
733 | |
734 | /** |
735 | * @param int $namespace |
736 | * @param string $name |
737 | * @param int $oldid |
738 | * @return bool |
739 | */ |
740 | public static function removeArticleFromName( $namespace, $name, $oldid = 0 ) { |
741 | $title = Title::makeTitleSafe( $namespace, $name ); |
742 | return self::removeArticle( $title, $oldid ); |
743 | } |
744 | |
745 | /** |
746 | * @param Title $title |
747 | * @param int $oldid |
748 | * @return bool |
749 | */ |
750 | private static function removeArticle( $title, $oldid = 0 ) { |
751 | if ( !CollectionSession::hasSession() || !$title ) { |
752 | return false; |
753 | } |
754 | $collection = CollectionSession::getCollection(); |
755 | $index = CollectionSession::findArticle( $title->getPrefixedText(), $oldid ); |
756 | if ( $index != -1 ) { |
757 | array_splice( $collection['items'], $index, 1 ); |
758 | } |
759 | CollectionSession::setCollection( $collection ); |
760 | return true; |
761 | } |
762 | |
763 | /** |
764 | * @param string $name |
765 | * @return bool |
766 | */ |
767 | public static function addCategoryFromName( $name ) { |
768 | $title = Title::makeTitleSafe( NS_CATEGORY, $name ); |
769 | return self::addCategory( $title ); |
770 | } |
771 | |
772 | /** |
773 | * @param Title $title |
774 | * @return bool |
775 | */ |
776 | private static function addCategory( $title ) { |
777 | global $wgCollectionMaxArticles, $wgCollectionArticleNamespaces; |
778 | |
779 | $limit = $wgCollectionMaxArticles - CollectionSession::countArticles(); |
780 | if ( $limit <= 0 || !$title ) { |
781 | self::limitExceeded(); |
782 | return false; |
783 | } |
784 | $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase(); |
785 | $tables = [ 'page', 'categorylinks' ]; |
786 | $fields = [ 'page_namespace', 'page_title' ]; |
787 | $options = [ |
788 | 'ORDER BY' => 'cl_type, cl_sortkey', |
789 | 'LIMIT' => $limit + 1, |
790 | ]; |
791 | $where = [ |
792 | 'cl_from=page_id', |
793 | 'cl_to' => $title->getDBkey(), |
794 | ]; |
795 | $res = $dbr->select( $tables, $fields, $where, __METHOD__, $options ); |
796 | |
797 | $count = 0; |
798 | $limitExceeded = false; |
799 | foreach ( $res as $row ) { |
800 | if ( ++$count > $limit ) { |
801 | $limitExceeded = true; |
802 | break; |
803 | } |
804 | if ( in_array( $row->page_namespace, $wgCollectionArticleNamespaces ) ) { |
805 | $articleTitle = Title::makeTitle( $row->page_namespace, $row->page_title ); |
806 | if ( CollectionSession::findArticle( $articleTitle->getPrefixedText() ) == -1 ) { |
807 | self::addArticle( $articleTitle ); |
808 | } |
809 | } |
810 | } |
811 | return $limitExceeded; |
812 | } |
813 | |
814 | private static function limitExceeded() { |
815 | $out = RequestContext::getMain()->getOutput(); |
816 | $out->showErrorPage( 'coll-limit_exceeded_title', 'coll-limit_exceeded_text' ); |
817 | } |
818 | |
819 | /** |
820 | * @param int $index |
821 | * @return bool |
822 | */ |
823 | public static function removeItem( $index ) { |
824 | if ( !is_int( $index ) ) { |
825 | return false; |
826 | } |
827 | if ( !CollectionSession::hasSession() ) { |
828 | return false; |
829 | } |
830 | $collection = CollectionSession::getCollection(); |
831 | array_splice( $collection['items'], $index, 1 ); |
832 | CollectionSession::setCollection( $collection ); |
833 | return true; |
834 | } |
835 | |
836 | /** |
837 | * @param int $index |
838 | * @param int $delta |
839 | * @return bool |
840 | */ |
841 | private static function moveItem( $index, $delta ) { |
842 | if ( !CollectionSession::hasSession() ) { |
843 | return false; |
844 | } |
845 | $collection = CollectionSession::getCollection(); |
846 | $collection = self::moveItemInCollection( $collection, $index, $delta ); |
847 | if ( $collection === false ) { |
848 | return false; |
849 | } else { |
850 | CollectionSession::setCollection( $collection ); |
851 | return true; |
852 | } |
853 | } |
854 | |
855 | /** |
856 | * @param array $collection |
857 | * @param int $index |
858 | * @param int $delta |
859 | * @return array|false |
860 | */ |
861 | public static function moveItemInCollection( array $collection, $index, $delta ) { |
862 | $swapIndex = $index + $delta; |
863 | if ( !$collection || !isset( $collection['items'] ) ) { |
864 | return false; |
865 | } |
866 | $items = $collection['items']; |
867 | if ( isset( $items[$swapIndex] ) && isset( $items[$index] ) ) { |
868 | $saved = $items[$swapIndex]; |
869 | $collection['items'][$swapIndex] = $items[$index]; |
870 | $collection['items'][$index] = $saved; |
871 | return $collection; |
872 | } else { |
873 | return false; |
874 | } |
875 | } |
876 | |
877 | /** |
878 | * @param array<int,int> $items Mapping new to old positions, missing positions will be deleted |
879 | */ |
880 | public static function setSorting( array $items ) { |
881 | if ( !CollectionSession::hasSession() ) { |
882 | return; |
883 | } |
884 | $collection = CollectionSession::getCollection(); |
885 | $old_items = $collection['items']; |
886 | $new_items = []; |
887 | foreach ( $items as $new_index => $old_index ) { |
888 | // Fail-safe when the "setsorting" API is hit multiple times, but an old item is already |
889 | // deleted |
890 | if ( isset( $old_items[$old_index] ) ) { |
891 | $new_items[$new_index] = $old_items[$old_index]; |
892 | } |
893 | } |
894 | $collection['items'] = $new_items; |
895 | CollectionSession::setCollection( $collection ); |
896 | } |
897 | |
898 | /** |
899 | * @param array &$collection |
900 | * @param string $line |
901 | * @param bool $append |
902 | * @return array|null |
903 | */ |
904 | private function parseCollectionLine( &$collection, $line, $append ) { |
905 | $line = trim( $line ); |
906 | if ( !$append && preg_match( '/^===\s*(.*?)\s*===$/', $line, $match ) ) { |
907 | $collection['subtitle'] = $match[ 1 ]; |
908 | } elseif ( !$append && preg_match( '/^==\s*(.*?)\s*==$/', $line, $match ) ) { |
909 | $collection['title'] = $match[ 1 ]; |
910 | } elseif ( |
911 | !$append && |
912 | preg_match( '/^\s*\|\s*setting-([a-zA-Z0-9_-]+)\s*=\s*([^|]*)\s*$/', $line, $match ) |
913 | ) { |
914 | $collection['settings'][$match[ 1 ]] = $match[ 2 ]; |
915 | } elseif ( substr( $line, 0, 1 ) == ';' ) { // chapter |
916 | return [ |
917 | 'type' => 'chapter', |
918 | 'title' => trim( substr( $line, 1 ) ), |
919 | ]; |
920 | } elseif ( substr( $line, 0, 1 ) == ':' ) { // article |
921 | $articleTitle = trim( substr( $line, 1 ) ); |
922 | if ( preg_match( '/^\[\[:?(.*?)(\|(.*?))?\]\]$/', $articleTitle, $match ) ) { |
923 | $articleTitle = $match[1]; |
924 | if ( isset( $match[3] ) ) { |
925 | $displayTitle = $match[3]; |
926 | } else { |
927 | $displayTitle = null; |
928 | } |
929 | $oldid = 0; |
930 | $currentVersion = 1; |
931 | } elseif ( |
932 | preg_match( '/^\[\{\{fullurl:(.*?)\|oldid=(.*?)\}\}\s+(.*?)\]$/', $articleTitle, $match ) |
933 | ) { |
934 | $articleTitle = $match[1]; |
935 | if ( isset( $match[3] ) ) { |
936 | $displayTitle = $match[3]; |
937 | } else { |
938 | $displayTitle = null; |
939 | } |
940 | $oldid = (int)$match[2]; |
941 | $currentVersion = 0; |
942 | } else { |
943 | return null; |
944 | } |
945 | |
946 | $articleTitle = Title::newFromText( $articleTitle ); |
947 | if ( !$articleTitle ) { |
948 | return null; |
949 | } |
950 | |
951 | if ( !$articleTitle->exists() ) { |
952 | return null; |
953 | } |
954 | |
955 | $revision = MediaWikiServices::getInstance() |
956 | ->getRevisionLookup() |
957 | ->getRevisionByTitle( $articleTitle, $oldid ); |
958 | if ( !$revision ) { |
959 | return null; |
960 | } |
961 | $latest = $articleTitle->getLatestRevID(); |
962 | |
963 | if ( !$oldid ) { |
964 | $oldid = $latest; |
965 | } |
966 | |
967 | $d = [ |
968 | 'type' => 'article', |
969 | 'content_type' => 'text/x-wiki', |
970 | 'title' => $articleTitle->getPrefixedText(), |
971 | 'latest' => $latest, |
972 | 'revision' => $oldid, |
973 | 'timestamp' => wfTimestamp( TS_UNIX, $revision->getTimestamp() ), |
974 | 'url' => $articleTitle->getCanonicalURL(), |
975 | 'currentVersion' => $currentVersion, |
976 | ]; |
977 | if ( $displayTitle ) { |
978 | $d['displaytitle'] = $displayTitle; |
979 | } |
980 | return $d; |
981 | } |
982 | return null; |
983 | } |
984 | |
985 | /** |
986 | * @param Title $title |
987 | * @param bool $append |
988 | * @return array|false |
989 | */ |
990 | private function loadCollection( Title $title, $append = false ) { |
991 | $out = $this->getOutput(); |
992 | |
993 | if ( !$title->exists() ) { |
994 | $out->showErrorPage( 'coll-notfound_title', 'coll-notfound_text' ); |
995 | return false; |
996 | } |
997 | |
998 | if ( !$append || !CollectionSession::hasSession() ) { |
999 | $collection = [ |
1000 | 'title' => '', |
1001 | 'subtitle' => '', |
1002 | 'settings' => [], |
1003 | ]; |
1004 | $items = []; |
1005 | } else { |
1006 | $collection = CollectionSession::getCollection(); |
1007 | $items = $collection['items']; |
1008 | } |
1009 | |
1010 | $page = MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $title ); |
1011 | $lines = preg_split( |
1012 | '/[\r\n]+/', |
1013 | $page->getContent()->getNativeData() |
1014 | ); |
1015 | |
1016 | foreach ( $lines as $line ) { |
1017 | $item = $this->parseCollectionLine( $collection, $line, $append ); |
1018 | if ( $item !== null ) { |
1019 | $items[] = $item; |
1020 | } |
1021 | } |
1022 | $collection['items'] = $items; |
1023 | return $collection; |
1024 | } |
1025 | |
1026 | /** |
1027 | * @param Title $title |
1028 | * @param bool $forceOverwrite |
1029 | * @return bool |
1030 | */ |
1031 | private function saveCollection( Title $title, $forceOverwrite = false ) { |
1032 | if ( $title->exists() && !$forceOverwrite ) { |
1033 | return false; |
1034 | } |
1035 | |
1036 | $collection = CollectionSession::getCollection(); |
1037 | $articleText = "{{" . $this->msg( 'coll-savedbook_template' )->inContentLanguage()->text(); |
1038 | if ( !empty( $collection['settings'] ) ) { |
1039 | $articleText .= "\n"; |
1040 | foreach ( $collection['settings'] as $key => $value ) { |
1041 | $articleText .= " | setting-$key = $value\n"; |
1042 | } |
1043 | } |
1044 | $articleText .= "}}\n\n"; |
1045 | if ( $collection['title'] ) { |
1046 | $articleText .= '== ' . $collection['title'] . " ==\n"; |
1047 | } |
1048 | if ( $collection['subtitle'] ) { |
1049 | $articleText .= '=== ' . $collection['subtitle'] . " ===\n"; |
1050 | } |
1051 | if ( !empty( $collection['items'] ) ) { |
1052 | foreach ( $collection['items'] as $item ) { |
1053 | if ( $item['type'] == 'chapter' ) { |
1054 | $articleText .= ';' . $item['title'] . "\n"; |
1055 | } elseif ( $item['type'] == 'article' ) { |
1056 | if ( $item['currentVersion'] == 1 ) { |
1057 | $articleText .= ":[[" . $item['title']; |
1058 | if ( isset( $item['displaytitle'] ) && $item['displaytitle'] ) { |
1059 | $articleText .= "|" . $item['displaytitle']; |
1060 | } |
1061 | $articleText .= "]]\n"; |
1062 | } else { |
1063 | $articleText .= ":[{{fullurl:" . $item['title']; |
1064 | $articleText .= "|oldid=" . $item['revision'] . "}} "; |
1065 | if ( isset( $item['displaytitle'] ) && $item['displaytitle'] ) { |
1066 | $articleText .= $item['displaytitle']; |
1067 | } else { |
1068 | $articleText .= $item['title']; |
1069 | } |
1070 | $articleText .= "]\n"; |
1071 | } |
1072 | } |
1073 | // $articleText .= $item['revision'] . "/" . $item['latest']."\n"; |
1074 | } |
1075 | } |
1076 | $t = $this->msg( 'coll-bookscategory' )->inContentLanguage(); |
1077 | if ( !$t->isDisabled() ) { |
1078 | $catTitle = Title::makeTitle( NS_CATEGORY, $t->text() ); |
1079 | if ( $catTitle !== null ) { |
1080 | $articleText .= "\n[[" . $catTitle->getPrefixedText() . |
1081 | "|" . wfEscapeWikiText( $title->getSubpageText() ) . "]]\n"; |
1082 | } |
1083 | } |
1084 | |
1085 | $req = new DerivativeRequest( |
1086 | $this->getRequest(), |
1087 | [ |
1088 | 'action' => 'edit', |
1089 | 'title' => $title->getPrefixedText(), |
1090 | 'text' => $articleText, |
1091 | 'token' => $this->getUser()->getEditToken(), |
1092 | ], |
1093 | true |
1094 | ); |
1095 | $api = new ApiMain( $req, true ); |
1096 | $api->execute(); |
1097 | return true; |
1098 | } |
1099 | |
1100 | /** |
1101 | * Take an array of arrays, each containing information about one item to be |
1102 | * assembled and exported, and appropriately feed the backend chosen ($writer). |
1103 | * @param array $collection following the collection/Metabook dictionary formats |
1104 | * https://www.mediawiki.org/wiki/Offline_content_generator/metabook.json |
1105 | * https://mwlib.readthedocs.org/en/latest/internals.html#article |
1106 | * @param Title $referrer Used only to provide a returnto parameter. |
1107 | * @param string $writer A writer registered in the appropriate configuration. |
1108 | */ |
1109 | private function renderCollection( array $collection, Title $referrer, $writer ) { |
1110 | if ( !$writer ) { |
1111 | $writer = 'rl'; |
1112 | } |
1113 | |
1114 | $api = CollectionRenderingAPI::instance( $writer ); |
1115 | $response = $api->render( $collection ); |
1116 | |
1117 | if ( !$this->handleResult( $response ) ) { |
1118 | return; |
1119 | } |
1120 | |
1121 | $query = 'bookcmd=rendering' |
1122 | . '&return_to=' . urlencode( $referrer->getPrefixedText() ) |
1123 | . '&collection_id=' . urlencode( $response->get( 'collection_id' ) ) |
1124 | . '&writer=' . urlencode( $writer ); |
1125 | if ( $response->get( 'is_cached' ) ) { |
1126 | $query .= '&is_cached=1'; |
1127 | } |
1128 | $redirect = SkinTemplate::makeSpecialUrl( 'Book', $query ); |
1129 | $this->getOutput()->redirect( $redirect ); |
1130 | } |
1131 | |
1132 | private function forceRenderCollection() { |
1133 | $request = $this->getRequest(); |
1134 | |
1135 | $collectionID = $request->getVal( 'collection_id', '' ); |
1136 | $writer = $request->getVal( 'writer', 'rl' ); |
1137 | |
1138 | $api = CollectionRenderingAPI::instance( $writer ); |
1139 | $response = $api->forceRender( $collectionID ); |
1140 | |
1141 | if ( !$response || $response->isError() ) { |
1142 | return; |
1143 | } |
1144 | |
1145 | $query = 'bookcmd=rendering' |
1146 | . '&return_to=' . urlencode( $request->getVal( 'return_to', '' ) ) |
1147 | . '&collection_id=' . urlencode( $response->get( 'collection_id' ) ) |
1148 | . '&writer=' . urlencode( $response->get( 'writer' ) ); |
1149 | if ( $response->get( 'is_cached' ) ) { |
1150 | $query .= '&is_cached=1'; |
1151 | } |
1152 | $this->getOutput()->redirect( SkinTemplate::makeSpecialUrl( 'Book', $query ) ); |
1153 | } |
1154 | |
1155 | private function renderRenderingPage() { |
1156 | $this->setHeaders(); |
1157 | $request = $this->getRequest(); |
1158 | $out = $this->getOutput(); |
1159 | $stats = MediaWikiServices::getInstance()->getStatsdDataFactory(); |
1160 | |
1161 | $collectionId = $request->getVal( 'collection_id' ); |
1162 | $writer = $request->getVal( 'writer' ); |
1163 | $return_to = $request->getVal( 'return_to', '' ); |
1164 | |
1165 | $result = CollectionRenderingAPI::instance( $writer )->getRenderStatus( $collectionId ); |
1166 | if ( !$this->handleResult( $result ) ) { |
1167 | return; // FIXME? |
1168 | } |
1169 | |
1170 | $query = 'collection_id=' . urlencode( $collectionId ) |
1171 | . '&writer=' . urlencode( $writer ) |
1172 | . '&return_to=' . urlencode( $return_to ); |
1173 | |
1174 | switch ( $result->get( 'state' ) ) { |
1175 | case 'pending': |
1176 | case 'progress': |
1177 | $out->addHeadItem( |
1178 | 'refresh-nojs', |
1179 | '<noscript><meta http-equiv="refresh" content="2" /></noscript>' |
1180 | ); |
1181 | $out->addInlineScript( 'var collection_id = ' . Html::encodeJsVar( urlencode( $collectionId ) ) . ';' ); |
1182 | $out->addInlineScript( 'var writer = ' . Html::encodeJsVar( urlencode( $writer ) ) . ';' ); |
1183 | $out->addInlineScript( 'var collection_rendering = true;' ); |
1184 | $out->addModules( 'ext.collection' ); |
1185 | $out->setPageTitleMsg( $this->msg( 'coll-rendering_title' ) ); |
1186 | |
1187 | $statusText = $result->get( 'status', 'status' ); |
1188 | if ( $statusText ) { |
1189 | if ( $result->get( 'status', 'article' ) ) { |
1190 | $statusText .= ' ' . $this->msg( |
1191 | 'coll-rendering_article', |
1192 | $result->get( 'status', 'article' ) |
1193 | )->text(); |
1194 | } elseif ( $result->get( 'status', 'page' ) ) { |
1195 | $statusText .= ' '; |
1196 | $statusText .= $this->msg( 'coll-rendering_page' ) |
1197 | ->numParams( $result->get( 'status', 'page' ) )->text(); |
1198 | } |
1199 | $status = $this->msg( 'coll-rendering_status', $statusText )->text(); |
1200 | } else { |
1201 | $status = ''; |
1202 | } |
1203 | |
1204 | $template = new CollectionRenderingTemplate(); |
1205 | $template->set( 'status', $status ); |
1206 | $progress = $result->get( 'status', 'progress' ); |
1207 | if ( !$progress ) { |
1208 | $progress = 0.00; |
1209 | } |
1210 | $template->set( 'progress', $progress ); |
1211 | $out->addTemplate( $template ); |
1212 | $stats->increment( 'collection.renderingpage.pending' ); |
1213 | break; |
1214 | |
1215 | case 'finished': |
1216 | $out->setPageTitleMsg( $this->msg( 'coll-rendering_finished_title' ) ); |
1217 | |
1218 | $template = new CollectionFinishedTemplate(); |
1219 | $template->set( |
1220 | 'download_url', |
1221 | wfExpandUrl( |
1222 | SkinTemplate::makeSpecialUrl( 'Book', 'bookcmd=download&' . $query ), |
1223 | PROTO_CURRENT |
1224 | ) |
1225 | ); |
1226 | $template->set( 'is_cached', $request->getVal( 'is_cached' ) ); |
1227 | $template->set( 'writer', $request->getVal( 'writer' ) ); |
1228 | $template->set( 'query', $query ); |
1229 | $template->set( 'return_to', $return_to ); |
1230 | $out->addTemplate( $template ); |
1231 | $stats->increment( 'collection.renderingpage.finished' ); |
1232 | break; |
1233 | |
1234 | case 'failed': |
1235 | $out->setPageTitleMsg( $this->msg( 'coll-rendering_failed_title' ) ); |
1236 | $statusText = $result->get( 'status', 'status' ); |
1237 | if ( $statusText ) { |
1238 | $status = $this->msg( 'coll-rendering_failed_status', $statusText )->text(); |
1239 | } else { |
1240 | $status = ''; |
1241 | } |
1242 | |
1243 | $template = new CollectionFailedTemplate(); |
1244 | $template->set( 'status', $status ); |
1245 | $template->set( 'query', $query ); |
1246 | $template->set( 'return_to', $return_to ); |
1247 | $out->addTemplate( $template ); |
1248 | $stats->increment( 'collection.renderingpage.failed' ); |
1249 | break; |
1250 | |
1251 | default: |
1252 | $stats->increment( 'collection.renderingpage.unknown' ); |
1253 | throw new UnexpectedValueException( __METHOD__ . "(): unknown state '{$result->get( 'state' )}'" ); |
1254 | } |
1255 | } |
1256 | |
1257 | private function download() { |
1258 | global $wgCollectionContentTypeToFilename; |
1259 | |
1260 | $request = $this->getRequest(); |
1261 | $collectionId = $request->getVal( 'collection_id' ); |
1262 | $writer = $request->getVal( 'writer' ); |
1263 | $api = CollectionRenderingAPI::instance( $writer ); |
1264 | |
1265 | $this->tempfile = tmpfile(); |
1266 | $r = $api->getRenderStatus( $collectionId ); |
1267 | |
1268 | $info = false; |
1269 | $url = $r->get( 'url' ); |
1270 | if ( $url ) { |
1271 | $req = MediaWikiServices::getInstance()->getHttpRequestFactory()->create( $url, [], __METHOD__ ); |
1272 | $req->setCallback( function ( $fh, $content ) { |
1273 | return fwrite( $this->tempfile, $content ); |
1274 | } ); |
1275 | if ( $req->execute()->isOK() ) { |
1276 | $info = true; |
1277 | } |
1278 | $content_type = $r->get( 'content_type' ); |
1279 | $content_length = $r->get( 'content_length' ); |
1280 | $content_disposition = $r->get( 'content_disposition' ); |
1281 | } else { |
1282 | $info = $api->download( $collectionId ); |
1283 | $content_type = $info->get( 'content_type' ); |
1284 | $content_length = $info->get( 'download_content_length' ); |
1285 | $content_disposition = null; |
1286 | if ( $info->isError() ) { |
1287 | $info = false; |
1288 | } |
1289 | } |
1290 | if ( !$info ) { |
1291 | $this->getOutput()->showErrorPage( |
1292 | 'coll-download_notfound_title', |
1293 | 'coll-download_notfound_text' |
1294 | ); |
1295 | return; |
1296 | } |
1297 | wfResetOutputBuffers(); |
1298 | header( 'Content-Type: ' . $content_type ); |
1299 | header( 'Content-Length: ' . $content_length ); |
1300 | if ( $content_disposition ) { |
1301 | header( 'Content-Disposition: ' . $content_disposition ); |
1302 | } else { |
1303 | $mimeType = explode( ';', $content_type )[0]; |
1304 | if ( isset( $wgCollectionContentTypeToFilename[$mimeType] ) ) { |
1305 | header( |
1306 | 'Content-Disposition: ' . |
1307 | 'inline; filename=' . |
1308 | $wgCollectionContentTypeToFilename[$mimeType] |
1309 | ); |
1310 | } |
1311 | } |
1312 | fseek( $this->tempfile, 0 ); |
1313 | fpassthru( $this->tempfile ); |
1314 | $this->getOutput()->disable(); |
1315 | } |
1316 | |
1317 | /** |
1318 | * Render a single page: fetch page name and revision information, then |
1319 | * assemble and feed to renderCollection() a single-item $collection. |
1320 | * @param Title $title Full page name aka prefixed title. |
1321 | * @param int $oldid |
1322 | * @return array|null |
1323 | */ |
1324 | private function makeCollection( $title, $oldid ) { |
1325 | if ( $title === null ) { |
1326 | $this->getOutput()->showErrorPage( 'coll-notitle_title', 'coll-notitle_msg' ); |
1327 | return null; |
1328 | } |
1329 | $article = [ |
1330 | 'type' => 'article', |
1331 | 'content_type' => 'text/x-wiki', |
1332 | 'title' => $title->getPrefixedText() |
1333 | ]; |
1334 | if ( $oldid ) { |
1335 | $article['revision'] = (string)$oldid; |
1336 | } |
1337 | |
1338 | $revision = MediaWikiServices::getInstance() |
1339 | ->getRevisionLookup() |
1340 | ->getRevisionByTitle( $title, $oldid ); |
1341 | if ( $revision ) { |
1342 | $article['timestamp'] = wfTimestamp( TS_UNIX, $revision->getTimestamp() ); |
1343 | } |
1344 | return [ 'items' => [ $article ] ]; |
1345 | } |
1346 | |
1347 | /** |
1348 | * Apply query string parameters to the given collection. |
1349 | * Use defaults specified in $wgCollectionRendererSettings. |
1350 | * @param array &$collection |
1351 | * @param WebRequest &$request |
1352 | */ |
1353 | private function applySettings( &$collection, &$request ) { |
1354 | global $wgCollectionRendererSettings; |
1355 | if ( !isset( $collection['settings'] ) ) { |
1356 | $collection['settings'] = []; |
1357 | } |
1358 | foreach ( $wgCollectionRendererSettings as $key => $desc ) { |
1359 | if ( $desc['type'] != 'select' ) { |
1360 | continue; |
1361 | } |
1362 | $val = $request->getVal( $key ); |
1363 | if ( !isset( $collections['settings'][$key] ) ) { |
1364 | $collection['settings'][$key] = $desc['default']; |
1365 | } |
1366 | if ( $val !== null ) { |
1367 | foreach ( $desc['options'] as $ignore => $valid ) { |
1368 | if ( $val == $valid ) { |
1369 | $collection['settings'][$key] = $valid; |
1370 | } |
1371 | } |
1372 | } |
1373 | } |
1374 | } |
1375 | |
1376 | /** |
1377 | * @param array $collection |
1378 | * @param string $partner |
1379 | */ |
1380 | private function postZip( array $collection, $partner ) { |
1381 | $out = $this->getOutput(); |
1382 | if ( !isset( $this->mPODPartners[$partner] ) ) { |
1383 | $out->showErrorPage( 'coll-invalid_podpartner_title', 'coll-invalid_podpartner_msg' ); |
1384 | return; |
1385 | } |
1386 | |
1387 | $api = CollectionRenderingAPI::instance(); |
1388 | $result = $api->postZip( $collection, $this->mPODPartners[$partner]['posturl'] ); |
1389 | if ( !$this->handleResult( $result ) ) { |
1390 | return; |
1391 | } |
1392 | if ( $result->get( 'redirect_url' ) ) { |
1393 | $out->redirect( $result->get( 'redirect_url' ) ); |
1394 | } |
1395 | } |
1396 | |
1397 | /** |
1398 | * @param string $colltype |
1399 | * @param string $title |
1400 | * @param string $pcollname |
1401 | * @param string $ccollname |
1402 | */ |
1403 | private function renderSaveOverwritePage( $colltype, $title, $pcollname, $ccollname ) { |
1404 | $this->setHeaders(); |
1405 | $out = $this->getOutput(); |
1406 | $out->setPageTitleMsg( $this->msg( 'coll-save_collection' ) ); |
1407 | |
1408 | $template = new CollectionSaveOverwriteTemplate(); |
1409 | $template->set( 'title', $title ); |
1410 | $template->set( 'pcollname', $pcollname ); |
1411 | $template->set( 'ccollname', $ccollname ); |
1412 | $template->set( 'colltype', $colltype ); |
1413 | $template->set( 'skin', $out->getSkin() ); |
1414 | $this->getOutput()->addTemplate( $template ); |
1415 | } |
1416 | |
1417 | /** |
1418 | * @param string $title |
1419 | */ |
1420 | private function renderLoadOverwritePage( $title ) { |
1421 | $this->setHeaders(); |
1422 | $this->getOutput()->setPageTitleMsg( $this->msg( 'coll-load_collection' ) ); |
1423 | |
1424 | $template = new CollectionLoadOverwriteTemplate(); |
1425 | $template->set( 'output', $this->getOutput() ); |
1426 | $template->set( 'title', $title ); |
1427 | $this->getOutput()->addTemplate( $template ); |
1428 | } |
1429 | |
1430 | /** |
1431 | * @param CollectionAPIResult $result |
1432 | * |
1433 | * @return bool Whether the result had errors |
1434 | */ |
1435 | private function handleResult( CollectionAPIResult $result ) { |
1436 | if ( !$result->isError() ) { |
1437 | return true; |
1438 | } |
1439 | |
1440 | $output = $this->getOutput(); |
1441 | MessageBoxHelper::addModuleStyles( $output ); |
1442 | $output->prepareErrorPage(); |
1443 | $output->setPageTitleMsg( $output->msg( 'coll-request_failed_title' ) ); |
1444 | $output->addHTML( MessageBoxHelper::renderWarningBoxes() ); |
1445 | $output->addWikiMsgArray( 'coll-request_failed_msg', [] ); |
1446 | $output->returnToMain(); |
1447 | |
1448 | return false; |
1449 | } |
1450 | |
1451 | protected function getGroupName() { |
1452 | return 'pagetools'; |
1453 | } |
1454 | } |