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