Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 356 |
|
0.00% |
0 / 26 |
CRAP | |
0.00% |
0 / 1 |
PFFormUtils | |
0.00% |
0 / 356 |
|
0.00% |
0 / 26 |
9702 | |
0.00% |
0 / 1 |
unhandledFieldsHTML | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
30 | |||
summaryInputHTML | |
0.00% |
0 / 24 |
|
0.00% |
0 / 1 |
20 | |||
minorEditInputHTML | |
0.00% |
0 / 29 |
|
0.00% |
0 / 1 |
42 | |||
watchInputHTML | |
0.00% |
0 / 45 |
|
0.00% |
0 / 1 |
240 | |||
buttonHTML | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
12 | |||
saveButtonHTML | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
12 | |||
saveAndContinueButtonHTML | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
12 | |||
showPreviewButtonHTML | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
12 | |||
showChangesButtonHTML | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
12 | |||
cancelLinkHTML | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
30 | |||
runQueryButtonHTML | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
6 | |||
formBottom | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
12 | |||
getPreloadedText | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
20 | |||
queryFormBottom | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getMonthNames | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
2 | |||
setGlobalVarsForSpreadsheet | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
6 | |||
getFormDefinition | |
0.00% |
0 / 41 |
|
0.00% |
0 / 1 |
72 | |||
getFormDefinitionFromCache | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
12 | |||
cacheFormDefinition | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 | |||
purgeCache | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
20 | |||
purgeCacheOnSave | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
getFormCache | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
getCacheKey | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
headerHTML | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
getChangedIndex | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
42 | |||
setShowOnSelect | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
20 |
1 | <?php |
2 | |
3 | use MediaWiki\MediaWikiServices; |
4 | use MediaWiki\Revision\RenderedRevision; |
5 | use OOUI\ButtonInputWidget; |
6 | |
7 | /** |
8 | * Utilities for the display and retrieval of forms. |
9 | * |
10 | * @author Yaron Koren |
11 | * @author Jeffrey Stuckman |
12 | * @author Harold Solbrig |
13 | * @author Eugene Mednikov |
14 | * @file |
15 | * @ingroup PF |
16 | */ |
17 | |
18 | class PFFormUtils { |
19 | |
20 | /** |
21 | * Add a hidden input for each field in the template call that's |
22 | * not handled by the form itself |
23 | * @param PFTemplateInForm|null $template_in_form |
24 | * @return string |
25 | */ |
26 | static function unhandledFieldsHTML( $template_in_form ) { |
27 | // This shouldn't happen, but sometimes this value is null. |
28 | // @TODO - fix the code that calls this function so the |
29 | // value is never null. |
30 | if ( $template_in_form === null ) { |
31 | return ''; |
32 | } |
33 | |
34 | // HTML element names shouldn't contain spaces |
35 | $templateName = str_replace( ' ', '_', $template_in_form->getTemplateName() ); |
36 | $text = ""; |
37 | foreach ( $template_in_form->getValuesFromPage() as $key => $value ) { |
38 | if ( $key !== null && !is_numeric( $key ) ) { |
39 | $key = urlencode( $key ); |
40 | $text .= Html::hidden( '_unhandled_' . $templateName . '_' . $key, $value ); |
41 | } |
42 | } |
43 | return $text; |
44 | } |
45 | |
46 | static function summaryInputHTML( $is_disabled, $label = null, $attr = [], $value = '' ) { |
47 | global $wgPageFormsTabIndex; |
48 | |
49 | if ( $label == null ) { |
50 | $label = wfMessage( 'summary' )->text(); |
51 | } |
52 | |
53 | $wgPageFormsTabIndex++; |
54 | $attr += [ |
55 | 'tabIndex' => $wgPageFormsTabIndex, |
56 | 'value' => $value, |
57 | 'name' => 'wpSummary', |
58 | 'id' => 'wpSummary', |
59 | 'maxlength' => CommentStore::COMMENT_CHARACTER_LIMIT, |
60 | 'title' => wfMessage( 'tooltip-summary' )->text(), |
61 | 'accessKey' => wfMessage( 'accesskey-summary' )->text() |
62 | ]; |
63 | if ( $is_disabled ) { |
64 | $attr['disabled'] = true; |
65 | } |
66 | if ( array_key_exists( 'class', $attr ) ) { |
67 | $attr['classes'] = [ $attr['class'] ]; |
68 | } |
69 | |
70 | $text = new OOUI\FieldLayout( |
71 | new OOUI\TextInputWidget( $attr ), |
72 | [ |
73 | 'align' => 'top', |
74 | 'label' => $label |
75 | ] |
76 | ); |
77 | |
78 | return $text; |
79 | } |
80 | |
81 | static function minorEditInputHTML( $form_submitted, $is_disabled, $is_checked, $label = null, $attrs = [] ) { |
82 | global $wgPageFormsTabIndex; |
83 | |
84 | $wgPageFormsTabIndex++; |
85 | if ( !$form_submitted ) { |
86 | $user = RequestContext::getMain()->getUser(); |
87 | $is_checked = MediaWikiServices::getInstance()->getUserOptionsLookup() |
88 | ->getOption( $user, 'minordefault' ); |
89 | } |
90 | |
91 | if ( $label == null ) { |
92 | $label = wfMessage( 'minoredit' )->parse(); |
93 | } |
94 | |
95 | $attrs += [ |
96 | 'id' => 'wpMinoredit', |
97 | 'name' => 'wpMinoredit', |
98 | 'accessKey' => wfMessage( 'accesskey-minoredit' )->text(), |
99 | 'tabIndex' => $wgPageFormsTabIndex, |
100 | ]; |
101 | if ( $is_checked ) { |
102 | $attrs['selected'] = true; |
103 | } |
104 | if ( $is_disabled ) { |
105 | $attrs['disabled'] = true; |
106 | } |
107 | // @phan-suppress-next-line PhanImpossibleTypeComparison |
108 | if ( array_key_exists( 'class', $attrs ) ) { |
109 | $attrs['classes'] = [ $attrs['class'] ]; |
110 | } |
111 | |
112 | // We can't use OOUI\FieldLayout here, because it will make the display too wide. |
113 | $labelWidget = new OOUI\LabelWidget( [ |
114 | 'label' => new OOUI\HtmlSnippet( $label ) |
115 | ] ); |
116 | $text = Html::rawElement( |
117 | 'label', |
118 | [ 'title' => wfMessage( 'tooltip-minoredit' )->parse() ], |
119 | new OOUI\CheckboxInputWidget( $attrs ) . $labelWidget |
120 | ); |
121 | $text = Html::rawElement( 'div', [ 'style' => 'display: inline-block; padding: 12px 16px 12px 0;' ], $text ); |
122 | |
123 | return $text; |
124 | } |
125 | |
126 | static function watchInputHTML( $form_submitted, $is_disabled, $is_checked = false, $label = null, $attrs = [] ) { |
127 | global $wgPageFormsTabIndex, $wgTitle; |
128 | |
129 | $wgPageFormsTabIndex++; |
130 | // figure out if the checkbox should be checked - |
131 | // this code borrowed from /includes/EditPage.php |
132 | if ( !$form_submitted ) { |
133 | $user = RequestContext::getMain()->getUser(); |
134 | $services = MediaWikiServices::getInstance(); |
135 | $userOptionsLookup = $services->getUserOptionsLookup(); |
136 | if ( method_exists( \MediaWiki\Watchlist\WatchlistManager::class, 'isWatched' ) ) { |
137 | // MediaWiki 1.37+ |
138 | $watchlistManager = $services->getWatchlistManager(); |
139 | if ( $userOptionsLookup->getOption( $user, 'watchdefault' ) ) { |
140 | # Watch all edits |
141 | $is_checked = true; |
142 | } elseif ( $userOptionsLookup->getOption( $user, 'watchcreations' ) && |
143 | !$wgTitle->exists() ) { |
144 | # Watch creations |
145 | $is_checked = true; |
146 | } elseif ( $watchlistManager->isWatched( $user, $wgTitle ) ) { |
147 | # Already watched |
148 | $is_checked = true; |
149 | } |
150 | } else { |
151 | if ( $userOptionsLookup->getOption( $user, 'watchdefault' ) ) { |
152 | # Watch all edits |
153 | $is_checked = true; |
154 | } elseif ( $userOptionsLookup->getOption( $user, 'watchcreations' ) && |
155 | !$wgTitle->exists() ) { |
156 | # Watch creations |
157 | $is_checked = true; |
158 | } elseif ( $user->isWatched( $wgTitle ) ) { |
159 | # Already watched |
160 | $is_checked = true; |
161 | } |
162 | } |
163 | } |
164 | if ( $label == null ) { |
165 | $label = wfMessage( 'watchthis' )->parse(); |
166 | } |
167 | $attrs += [ |
168 | 'id' => 'wpWatchthis', |
169 | 'name' => 'wpWatchthis', |
170 | 'accessKey' => wfMessage( 'accesskey-watch' )->text(), |
171 | 'tabIndex' => $wgPageFormsTabIndex, |
172 | ]; |
173 | if ( $is_checked ) { |
174 | $attrs['selected'] = true; |
175 | } |
176 | if ( $is_disabled ) { |
177 | $attrs['disabled'] = true; |
178 | } |
179 | // @phan-suppress-next-line PhanImpossibleTypeComparison |
180 | if ( array_key_exists( 'class', $attrs ) ) { |
181 | $attrs['classes'] = [ $attrs['class'] ]; |
182 | } |
183 | |
184 | // We can't use OOUI\FieldLayout here, because it will make the display too wide. |
185 | $labelWidget = new OOUI\LabelWidget( [ |
186 | 'label' => new OOUI\HtmlSnippet( $label ) |
187 | ] ); |
188 | $text = Html::rawElement( |
189 | 'label', |
190 | [ 'title' => wfMessage( 'tooltip-watch' )->parse() ], |
191 | new OOUI\CheckboxInputWidget( $attrs ) . $labelWidget |
192 | ); |
193 | $text = Html::rawElement( 'div', [ 'style' => 'display: inline-block; padding: 12px 16px 12px 0;' ], $text ); |
194 | |
195 | return $text; |
196 | } |
197 | |
198 | /** |
199 | * Helper function to display a simple button |
200 | * @param string $name |
201 | * @param string $value |
202 | * @param string $type |
203 | * @param array $attrs |
204 | * @return ButtonInputWidget |
205 | */ |
206 | private static function buttonHTML( $name, $value, $type, $attrs ) { |
207 | $attrs += [ |
208 | 'type' => $type, |
209 | 'name' => $name, |
210 | 'label' => $value |
211 | ]; |
212 | $button = new ButtonInputWidget( $attrs ); |
213 | // Special handling for 'class'. |
214 | if ( isset( $attrs['class'] ) ) { |
215 | // Make sure it's an array. |
216 | if ( is_string( $attrs['class'] ) ) { |
217 | $attrs['class'] = [ $attrs['class'] ]; |
218 | } |
219 | $button->addClasses( $attrs['class'] ); |
220 | } |
221 | return $button; |
222 | } |
223 | |
224 | static function saveButtonHTML( $is_disabled, $label = null, $attr = [] ) { |
225 | global $wgPageFormsTabIndex; |
226 | |
227 | $wgPageFormsTabIndex++; |
228 | if ( $label == null ) { |
229 | $label = wfMessage( 'savearticle' )->text(); |
230 | } |
231 | $temp = $attr + [ |
232 | 'id' => 'wpSave', |
233 | 'tabIndex' => $wgPageFormsTabIndex, |
234 | 'accessKey' => wfMessage( 'accesskey-save' )->text(), |
235 | 'title' => wfMessage( 'tooltip-save' )->text(), |
236 | 'flags' => [ 'primary', 'progressive' ] |
237 | ]; |
238 | if ( $is_disabled ) { |
239 | $temp['disabled'] = true; |
240 | } |
241 | return self::buttonHTML( 'wpSave', $label, 'submit', $temp ); |
242 | } |
243 | |
244 | static function saveAndContinueButtonHTML( $is_disabled, $label = null, $attr = [] ) { |
245 | global $wgPageFormsTabIndex; |
246 | |
247 | $wgPageFormsTabIndex++; |
248 | |
249 | if ( $label == null ) { |
250 | $label = wfMessage( 'pf_formedit_saveandcontinueediting' )->text(); |
251 | } |
252 | |
253 | $temp = $attr + [ |
254 | 'id' => 'wpSaveAndContinue', |
255 | 'tabIndex' => $wgPageFormsTabIndex, |
256 | 'disabled' => true, |
257 | 'accessKey' => wfMessage( 'pf_formedit_accesskey_saveandcontinueediting' )->text(), |
258 | 'title' => wfMessage( 'pf_formedit_tooltip_saveandcontinueediting' )->text(), |
259 | ]; |
260 | |
261 | if ( $is_disabled ) { |
262 | $temp['class'] = 'pf-save_and_continue disabled'; |
263 | } else { |
264 | $temp['class'] = 'pf-save_and_continue'; |
265 | } |
266 | |
267 | return self::buttonHTML( 'wpSaveAndContinue', $label, 'button', $temp ); |
268 | } |
269 | |
270 | static function showPreviewButtonHTML( $is_disabled, $label = null, $attr = [] ) { |
271 | global $wgPageFormsTabIndex; |
272 | |
273 | $wgPageFormsTabIndex++; |
274 | if ( $label == null ) { |
275 | $label = wfMessage( 'showpreview' )->text(); |
276 | } |
277 | $temp = $attr + [ |
278 | 'id' => 'wpPreview', |
279 | 'tabIndex' => $wgPageFormsTabIndex, |
280 | 'accessKey' => wfMessage( 'accesskey-preview' )->text(), |
281 | 'title' => wfMessage( 'tooltip-preview' )->text(), |
282 | ]; |
283 | if ( $is_disabled ) { |
284 | $temp['disabled'] = true; |
285 | } |
286 | return self::buttonHTML( 'wpPreview', $label, 'submit', $temp ); |
287 | } |
288 | |
289 | static function showChangesButtonHTML( $is_disabled, $label = null, $attr = [] ) { |
290 | global $wgPageFormsTabIndex; |
291 | |
292 | $wgPageFormsTabIndex++; |
293 | if ( $label == null ) { |
294 | $label = wfMessage( 'showdiff' )->text(); |
295 | } |
296 | $temp = $attr + [ |
297 | 'id' => 'wpDiff', |
298 | 'tabIndex' => $wgPageFormsTabIndex, |
299 | 'accessKey' => wfMessage( 'accesskey-diff' )->text(), |
300 | 'title' => wfMessage( 'tooltip-diff' )->text(), |
301 | ]; |
302 | if ( $is_disabled ) { |
303 | $temp['disabled'] = true; |
304 | } |
305 | return self::buttonHTML( 'wpDiff', $label, 'submit', $temp ); |
306 | } |
307 | |
308 | static function cancelLinkHTML( $is_disabled, $label = null, $attr = [] ) { |
309 | global $wgTitle; |
310 | |
311 | if ( $label == null ) { |
312 | $label = wfMessage( 'cancel' )->parse(); |
313 | } |
314 | $attr['classes'] = []; |
315 | if ( $wgTitle == null || $wgTitle->isSpecial( 'FormEdit' ) ) { |
316 | $attr['classes'][] = 'pfSendBack'; |
317 | } else { |
318 | $attr['href'] = $wgTitle->getFullURL(); |
319 | } |
320 | $attr['framed'] = false; |
321 | $attr['label'] = $label; |
322 | $attr['flags'] = [ 'destructive' ]; |
323 | if ( array_key_exists( 'class', $attr ) ) { |
324 | $attr['classes'][] = $attr['class']; |
325 | } |
326 | |
327 | return "\t\t" . new OOUI\ButtonWidget( $attr ) . "\n"; |
328 | } |
329 | |
330 | static function runQueryButtonHTML( $is_disabled = false, $label = null, $attr = [] ) { |
331 | // is_disabled is currently ignored |
332 | global $wgPageFormsTabIndex; |
333 | |
334 | $wgPageFormsTabIndex++; |
335 | if ( $label == null ) { |
336 | $label = wfMessage( 'runquery' )->text(); |
337 | } |
338 | $buttonHTML = self::buttonHTML( 'wpRunQuery', $label, 'submit', |
339 | $attr + [ |
340 | 'id' => 'wpRunQuery', |
341 | 'tabIndex' => $wgPageFormsTabIndex, |
342 | 'title' => $label, |
343 | 'flags' => [ 'primary', 'progressive' ], |
344 | 'icon' => 'search' |
345 | ] ); |
346 | return new OOUI\FieldLayout( $buttonHTML ); |
347 | } |
348 | |
349 | /** |
350 | * Much of this function is based on MediaWiki's EditPage::showEditForm(). |
351 | * @param bool $form_submitted |
352 | * @param bool $is_disabled |
353 | * @return string |
354 | */ |
355 | static function formBottom( $form_submitted, $is_disabled ) { |
356 | $text = <<<END |
357 | <br /> |
358 | <div class='editOptions'> |
359 | |
360 | END; |
361 | $req = RequestContext::getMain()->getRequest(); |
362 | $summary = $req->getVal( 'wpSummary' ); |
363 | $text .= self::summaryInputHTML( $is_disabled, null, [], $summary ); |
364 | $user = RequestContext::getMain()->getUser(); |
365 | if ( $user->isAllowed( 'minoredit' ) ) { |
366 | $text .= self::minorEditInputHTML( $form_submitted, $is_disabled, false ); |
367 | } |
368 | |
369 | $userIsRegistered = $user->isRegistered(); |
370 | if ( $userIsRegistered ) { |
371 | $text .= self::watchInputHTML( $form_submitted, $is_disabled ); |
372 | } |
373 | |
374 | $text .= <<<END |
375 | <br /> |
376 | <div class='editButtons'> |
377 | |
378 | END; |
379 | $text .= self::saveButtonHTML( $is_disabled ); |
380 | $text .= self::showPreviewButtonHTML( $is_disabled ); |
381 | $text .= self::showChangesButtonHTML( $is_disabled ); |
382 | $text .= self::cancelLinkHTML( $is_disabled ); |
383 | $text .= <<<END |
384 | </div><!-- editButtons --> |
385 | </div><!-- editOptions --> |
386 | |
387 | END; |
388 | return $text; |
389 | } |
390 | |
391 | /** |
392 | * Loosely based on MediaWiki's EditPage::getPreloadedContent(). |
393 | * |
394 | * @param string $preload |
395 | * @return string |
396 | */ |
397 | static function getPreloadedText( $preload ) { |
398 | if ( $preload === '' ) { |
399 | return ''; |
400 | } |
401 | |
402 | $preloadTitle = Title::newFromText( $preload ); |
403 | if ( !isset( $preloadTitle ) ) { |
404 | return ''; |
405 | } |
406 | |
407 | $permissionManager = MediaWikiServices::getInstance()->getPermissionManager(); |
408 | $user = RequestContext::getMain()->getUser(); |
409 | if ( !$permissionManager->userCan( 'read', $user, $preloadTitle ) ) { |
410 | return ''; |
411 | } |
412 | |
413 | $text = PFUtils::getPageText( $preloadTitle ); |
414 | // Remove <noinclude> sections and <includeonly> tags from text |
415 | $text = StringUtils::delimiterReplace( '<noinclude>', '</noinclude>', '', $text ); |
416 | $text = strtr( $text, [ '<includeonly>' => '', '</includeonly>' => '' ] ); |
417 | return $text; |
418 | } |
419 | |
420 | /** |
421 | * Used by 'RunQuery' page |
422 | * @return string |
423 | */ |
424 | static function queryFormBottom() { |
425 | return self::runQueryButtonHTML( false ); |
426 | } |
427 | |
428 | static function getMonthNames() { |
429 | return [ |
430 | wfMessage( 'january' )->inContentLanguage()->text(), |
431 | wfMessage( 'february' )->inContentLanguage()->text(), |
432 | wfMessage( 'march' )->inContentLanguage()->text(), |
433 | wfMessage( 'april' )->inContentLanguage()->text(), |
434 | // Needed to avoid using 3-letter abbreviation |
435 | wfMessage( 'may_long' )->inContentLanguage()->text(), |
436 | wfMessage( 'june' )->inContentLanguage()->text(), |
437 | wfMessage( 'july' )->inContentLanguage()->text(), |
438 | wfMessage( 'august' )->inContentLanguage()->text(), |
439 | wfMessage( 'september' )->inContentLanguage()->text(), |
440 | wfMessage( 'october' )->inContentLanguage()->text(), |
441 | wfMessage( 'november' )->inContentLanguage()->text(), |
442 | wfMessage( 'december' )->inContentLanguage()->text() |
443 | ]; |
444 | } |
445 | |
446 | public static function setGlobalVarsForSpreadsheet() { |
447 | global $wgPageFormsContLangYes, $wgPageFormsContLangNo, $wgPageFormsContLangMonths; |
448 | |
449 | // JS variables that hold boolean and date values in the wiki's |
450 | // (as opposed to the user's) language. |
451 | $wgPageFormsContLangYes = wfMessage( 'htmlform-yes' )->inContentLanguage()->text(); |
452 | $wgPageFormsContLangNo = wfMessage( 'htmlform-no' )->inContentLanguage()->text(); |
453 | $monthMessages = [ |
454 | "january", "february", "march", "april", "may_long", "june", |
455 | "july", "august", "september", "october", "november", "december" |
456 | ]; |
457 | $wgPageFormsContLangMonths = [ '' ]; |
458 | foreach ( $monthMessages as $monthMsg ) { |
459 | $wgPageFormsContLangMonths[] = wfMessage( $monthMsg )->inContentLanguage()->text(); |
460 | } |
461 | } |
462 | |
463 | /** |
464 | * Parse the form definition and return it |
465 | * @param Parser $parser |
466 | * @param string|null $form_def |
467 | * @param string|null $form_id |
468 | * @return string |
469 | */ |
470 | public static function getFormDefinition( Parser $parser, $form_def = null, $form_id = null ) { |
471 | if ( $form_id !== null ) { |
472 | $cachedDef = self::getFormDefinitionFromCache( $form_id, $parser ); |
473 | |
474 | if ( $cachedDef !== null ) { |
475 | return $cachedDef; |
476 | } |
477 | } |
478 | |
479 | if ( $form_def !== null ) { |
480 | // Do nothing. |
481 | } elseif ( $form_id !== null ) { |
482 | $form_title = Title::newFromID( $form_id ); |
483 | $form_def = PFUtils::getPageText( $form_title ); |
484 | } else { |
485 | // No text, no ID -> no form definition. |
486 | return ''; |
487 | } |
488 | |
489 | // Remove <noinclude> sections and <includeonly> tags from form definition |
490 | $form_def = StringUtils::delimiterReplace( '<noinclude>', '</noinclude>', '', $form_def ); |
491 | $form_def = strtr( $form_def, [ '<includeonly>' => '', '</includeonly>' => '' ] ); |
492 | |
493 | // We need to replace all PF tags in the form definition by strip items. But we can not just use |
494 | // the Parser strip state because the Parser would during parsing replace all strip items and then |
495 | // mangle them into HTML code. So we have to use our own. Which means we also can not just use |
496 | // Parser::insertStripItem() (see below). |
497 | // Also include a quotation mark, to help avoid security leaks. |
498 | $rnd = wfRandomString( 16 ) . '"' . wfRandomString( 15 ); |
499 | |
500 | // This regexp will find any PF triple braced tags (including correct handling of contained braces), i.e. |
501 | // {{{field|foo|default={{Bar}}}}} is not a problem. When used with preg_match and friends, $matches[0] will |
502 | // contain the whole PF tag, $matches[1] will contain the tag without the enclosing triple braces. |
503 | $regexp = '#\{\{\{((?>[^\{\}]+)|(\{((?>[^\{\}]+)|(?-2))*\}))*\}\}\}#'; |
504 | // Needed to restore highlighting in vi - <? |
505 | |
506 | $items = []; |
507 | |
508 | // replace all PF tags by strip markers |
509 | $form_def = preg_replace_callback( |
510 | $regexp, |
511 | |
512 | // This is essentially a copy of Parser::insertStripItem(). |
513 | static function ( array $matches ) use ( &$items, $rnd ) { |
514 | $markerIndex = count( $items ); |
515 | $items[] = $matches[0]; |
516 | return "$rnd-item-$markerIndex-$rnd"; |
517 | }, |
518 | |
519 | $form_def |
520 | ); |
521 | |
522 | // Parse wiki-text. |
523 | // @phan-suppress-next-line PhanRedundantCondition for BC with old MW |
524 | $title = is_object( $parser->getTitle() ) ? $parser->getTitle() : $form_title; |
525 | // We need to pass "false" in to the parse() $clearState param so that |
526 | // embedding Special:RunQuery will work. |
527 | $output = $parser->parse( $form_def, $title, $parser->getOptions(), true, false ); |
528 | $form_def = $output->getText(); |
529 | $form_def = preg_replace_callback( |
530 | "/{$rnd}-item-(\d+)-{$rnd}/", |
531 | static function ( array $matches ) use ( $items ) { |
532 | $markerIndex = (int)$matches[1]; |
533 | return $items[$markerIndex]; |
534 | }, |
535 | $form_def |
536 | ); |
537 | |
538 | if ( $output->getCacheTime() == -1 ) { |
539 | $form_article = Article::newFromID( $form_id ); |
540 | self::purgeCache( $form_article ); |
541 | wfDebug( "Caching disabled for form definition $form_id\n" ); |
542 | } elseif ( $form_id !== null ) { |
543 | self::cacheFormDefinition( $form_id, $form_def, $parser ); |
544 | } |
545 | |
546 | return $form_def; |
547 | } |
548 | |
549 | /** |
550 | * Get a form definition from cache |
551 | * @param string $form_id |
552 | * @param Parser $parser |
553 | * @return string|null |
554 | */ |
555 | protected static function getFormDefinitionFromCache( $form_id, Parser $parser ) { |
556 | global $wgPageFormsCacheFormDefinitions; |
557 | |
558 | // use cache if allowed |
559 | if ( !$wgPageFormsCacheFormDefinitions ) { |
560 | return null; |
561 | } |
562 | |
563 | $cache = self::getFormCache(); |
564 | |
565 | // create a cache key consisting of owner name, article id and user options |
566 | $cacheKeyForForm = self::getCacheKey( $form_id, $parser ); |
567 | |
568 | $cached_def = $cache->get( $cacheKeyForForm ); |
569 | |
570 | // Cache hit? |
571 | if ( is_string( $cached_def ) ) { |
572 | wfDebug( "Cache hit: Got form definition $cacheKeyForForm from cache\n" ); |
573 | |
574 | return $cached_def; |
575 | } |
576 | |
577 | wfDebug( "Cache miss: Form definition $cacheKeyForForm not found in cache\n" ); |
578 | |
579 | return null; |
580 | } |
581 | |
582 | /** |
583 | * Store a form definition in cache |
584 | * @param string $form_id |
585 | * @param string $form_def |
586 | * @param Parser $parser |
587 | */ |
588 | protected static function cacheFormDefinition( $form_id, $form_def, Parser $parser ) { |
589 | global $wgPageFormsCacheFormDefinitions; |
590 | |
591 | // Store in cache if requested |
592 | if ( !$wgPageFormsCacheFormDefinitions ) { |
593 | return; |
594 | } |
595 | |
596 | $cache = self::getFormCache(); |
597 | $cacheKeyForForm = self::getCacheKey( $form_id, $parser ); |
598 | $cacheKeyForList = self::getCacheKey( $form_id ); |
599 | |
600 | // Update list of form definitions |
601 | $listOfFormKeys = $cache->get( $cacheKeyForList ); |
602 | // The list of values is used by self::purge, keys are ignored. |
603 | // This way we automatically override duplicates. |
604 | $listOfFormKeys[$cacheKeyForForm] = $cacheKeyForForm; |
605 | |
606 | // We cache indefinitely ignoring $wgParserCacheExpireTime. |
607 | // The reasoning is that there really is not point in expiring |
608 | // rarely changed forms automatically (after one day per |
609 | // default). Instead the cache is purged on storing/purging a |
610 | // form definition. |
611 | |
612 | // Store form definition with current user options |
613 | $cache->set( $cacheKeyForForm, $form_def ); |
614 | |
615 | // Store updated list of form definitions |
616 | $cache->set( $cacheKeyForList, $listOfFormKeys ); |
617 | wfDebug( "Cached form definition $cacheKeyForForm\n" ); |
618 | } |
619 | |
620 | /** |
621 | * Deletes the form definition associated with the given wiki page |
622 | * from the main cache. |
623 | * |
624 | * Hooks: ArticlePurge |
625 | * |
626 | * @param WikiPage $wikipage |
627 | * @return bool |
628 | */ |
629 | public static function purgeCache( WikiPage $wikipage ) { |
630 | if ( !$wikipage->getTitle()->inNamespace( PF_NS_FORM ) ) { |
631 | return true; |
632 | } |
633 | |
634 | $cache = self::getFormCache(); |
635 | $cacheKeyForList = self::getCacheKey( $wikipage->getId() ); |
636 | |
637 | // get references to stored datasets |
638 | $listOfFormKeys = $cache->get( $cacheKeyForList ); |
639 | |
640 | if ( !is_array( $listOfFormKeys ) ) { |
641 | return true; |
642 | } |
643 | |
644 | // delete stored datasets |
645 | foreach ( $listOfFormKeys as $key ) { |
646 | $cache->delete( $key ); |
647 | wfDebug( "Deleted cached form definition $key.\n" ); |
648 | } |
649 | |
650 | // delete references to datasets |
651 | $cache->delete( $cacheKeyForList ); |
652 | wfDebug( "Deleted cached form definition references $cacheKeyForList.\n" ); |
653 | |
654 | return true; |
655 | } |
656 | |
657 | /** |
658 | * Deletes the form definition associated with the given wiki page |
659 | * from the main cache. |
660 | * |
661 | * Hook: MultiContentSave |
662 | * |
663 | * @param RenderedRevision $renderedRevision |
664 | * @return bool |
665 | */ |
666 | public static function purgeCacheOnSave( RenderedRevision $renderedRevision ) { |
667 | $articleID = $renderedRevision->getRevision()->getPageId(); |
668 | $wikiPage = MediaWikiServices::getInstance()->getWikiPageFactory()->newFromID( $articleID ); |
669 | if ( $wikiPage == null ) { |
670 | // @TODO - should this ever happen? |
671 | return true; |
672 | } |
673 | return self::purgeCache( $wikiPage ); |
674 | } |
675 | |
676 | /** |
677 | * Get the cache object used by the form cache |
678 | * @return BagOStuff |
679 | */ |
680 | public static function getFormCache() { |
681 | global $wgPageFormsFormCacheType, $wgParserCacheType; |
682 | $ret = ObjectCache::getInstance( ( $wgPageFormsFormCacheType !== null ) ? $wgPageFormsFormCacheType : $wgParserCacheType ); |
683 | return $ret; |
684 | } |
685 | |
686 | /** |
687 | * Get a cache key. |
688 | * |
689 | * @param string $formId |
690 | * @param Parser|null $parser Provide parser to get unique cache key |
691 | * @return string |
692 | */ |
693 | public static function getCacheKey( $formId, $parser = null ) { |
694 | $cache = self::getFormCache(); |
695 | |
696 | return ( $parser === null ) |
697 | ? $cache->makeKey( 'ext.PageForms.formdefinition', $formId ) |
698 | : $cache->makeKey( |
699 | 'ext.PageForms.formdefinition', |
700 | $formId, |
701 | $parser->getOptions()->optionsHash( ParserOptions::allCacheVaryingOptions() ) |
702 | ); |
703 | } |
704 | |
705 | /** |
706 | * Get section header HTML |
707 | * @param string $header_name |
708 | * @param int $header_level |
709 | * @return string |
710 | */ |
711 | static function headerHTML( $header_name, $header_level = 2 ) { |
712 | global $wgPageFormsTabIndex; |
713 | |
714 | $wgPageFormsTabIndex++; |
715 | $text = ""; |
716 | |
717 | if ( !is_numeric( $header_level ) ) { |
718 | // The default header level is set to 2 |
719 | $header_level = 2; |
720 | } |
721 | |
722 | $header_level = min( $header_level, 6 ); |
723 | $elementName = 'h' . $header_level; |
724 | $text = Html::rawElement( $elementName, [], $header_name ); |
725 | return $text; |
726 | } |
727 | |
728 | /** |
729 | * Get the changed index if a new template or section was |
730 | * inserted before the end, or one was deleted in the form |
731 | * @param int $i |
732 | * @param int|null $new_item_loc |
733 | * @param int|null $deleted_item_loc |
734 | * @return int |
735 | */ |
736 | static function getChangedIndex( $i, $new_item_loc, $deleted_item_loc ) { |
737 | $old_i = $i; |
738 | if ( $new_item_loc != null ) { |
739 | if ( $i > $new_item_loc ) { |
740 | $old_i = $i - 1; |
741 | } elseif ( $i == $new_item_loc ) { |
742 | // it's the new template; it shouldn't |
743 | // get any query-string data |
744 | $old_i = -1; |
745 | } |
746 | } elseif ( $deleted_item_loc != null ) { |
747 | if ( $i >= $deleted_item_loc ) { |
748 | $old_i = $i + 1; |
749 | } |
750 | } |
751 | return $old_i; |
752 | } |
753 | |
754 | static function setShowOnSelect( $showOnSelectVals, $inputID, $isCheckbox = false ) { |
755 | global $wgPageFormsShowOnSelect; |
756 | |
757 | foreach ( $showOnSelectVals as $divID => $options ) { |
758 | // A checkbox will just have div ID(s). |
759 | $data = $isCheckbox ? $divID : [ $options, $divID ]; |
760 | if ( array_key_exists( $inputID, $wgPageFormsShowOnSelect ) ) { |
761 | $wgPageFormsShowOnSelect[$inputID][] = $data; |
762 | } else { |
763 | $wgPageFormsShowOnSelect[$inputID] = [ $data ]; |
764 | } |
765 | } |
766 | } |
767 | } |