Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 230
0.00% covered (danger)
0.00%
0 / 41
CRAP
0.00% covered (danger)
0.00%
0 / 1
PFTemplateInForm
0.00% covered (danger)
0.00%
0 / 230
0.00% covered (danger)
0.00%
0 / 41
17030
0.00% covered (danger)
0.00%
0 / 1
 create
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
12
 newFromFormTag
0.00% covered (danger)
0.00%
0 / 47
0.00% covered (danger)
0.00%
0 / 1
420
 getTemplateName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getHeight
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFields
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getEmbedInTemplate
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getEmbedInField
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getLabel
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getIntro
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getAddButtonText
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDisplay
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getEventTitleField
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getEventDateField
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getEventStartDateField
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getEventEndDateField
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPlaceholder
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDisplayedFieldsWhenMinimized
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 allowsMultiple
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 strictParsing
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMinInstancesAllowed
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMaxInstancesAllowed
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 createMarkup
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
42
 getFullTextInPage
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 pageCallsThisTemplate
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 hasValueFromPageForField
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getAndRemoveValueFromPageForField
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getValuesFromPage
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getInstanceNum
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getGridValues
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 incrementInstanceNum
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 allInstancesPrinted
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 addGridValue
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 addField
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 changeFieldValues
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 setFieldValuesFromSubmit
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
132
 getValuesFromSubmit
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 removeUnparsedText
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
56
 restoreUnparsedText
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 setFieldValuesFromPage
0.00% covered (danger)
0.00%
0 / 50
0.00% covered (danger)
0.00%
0 / 1
1056
 setPageRelatedInfo
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 checkIfAllInstancesPrinted
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
182
1<?php
2
3use MediaWiki\Request\WebRequest;
4
5/**
6 * Represents a template in a user-defined form.
7 * @author Yaron Koren
8 * @file
9 * @ingroup PF
10 */
11class PFTemplateInForm {
12    private $mTemplateName;
13    private $mLabel;
14    private $mIntro;
15    private $mAddButtonText;
16    private $mDisplay;
17    private $mEventTitleField;
18    private $mEventDateField;
19    private $mEventStartDateField;
20    private $mEventEndDateField;
21    private $mAllowMultiple;
22    private $mStrictParsing;
23    private $mMinAllowed;
24    private $mMaxAllowed;
25    private $mFields = [];
26    private $mEmbedInTemplate;
27    private $mEmbedInField;
28    private $mPlaceholder;
29    private $mHeight = '200px';
30    /**
31     * Conceptually, it would make more sense to define and store this
32     * parameter per-field, rather than per-template. However, it's a lot
33     * easier to handle it in just one place, rather than in every form
34     * input class. Also, this allows, in theory, to set the order of the
35     * fields being displayed - though that's not being done yet.
36     */
37    private $mDisplayedFieldsWhenMinimized;
38
39    /**
40     * These fields are for a specific usage of a form (or more
41     * specifically, a template in a form) to edit a particular page.
42     * Perhaps they should go in another class.
43     */
44    private $mSearchTemplateStr;
45    private $mPregMatchTemplateStr;
46    private $mFullTextInPage;
47    private $mValuesFromPage = [];
48    private $mValuesFromSubmit = [];
49    private $mNumInstancesFromSubmit = 0;
50    private $mPageCallsThisTemplate = false;
51    private $mInstanceNum = 0;
52    private $mAllInstancesPrinted = false;
53    private $mGridValues = [];
54
55    static function create( $name, $label = null, $allowMultiple = null, $maxAllowed = null, $formFields = null ) {
56        $tif = new PFTemplateInForm();
57        $tif->mTemplateName = str_replace( '_', ' ', $name );
58        if ( $formFields === null ) {
59            $template = PFTemplate::newFromName( $tif->mTemplateName );
60            $fields = $template->getTemplateFields();
61            foreach ( $fields as $field ) {
62                $tif->mFields[] = PFFormField::create( $field );
63            }
64        } else {
65            $tif->mFields = $formFields;
66        }
67        $tif->mLabel = $label;
68        $tif->mAllowMultiple = $allowMultiple;
69        $tif->mMaxAllowed = $maxAllowed;
70        return $tif;
71    }
72
73    public static function newFromFormTag( $tag_components ) {
74        global $wgPageFormsEmbeddedTemplates;
75
76        $parser = PFUtils::getParser();
77
78        $tif = new PFTemplateInForm();
79        $tif->mTemplateName = str_replace( '_', ' ', trim( PFFormPrinter::getParsedValue( $parser, $tag_components[1] ) ) );
80
81        $tif->mAddButtonText = wfMessage( 'pf_formedit_addanother' )->text();
82
83        if ( array_key_exists( $tif->mTemplateName, $wgPageFormsEmbeddedTemplates ) ) {
84            [ $tif->mEmbedInTemplate, $tif->mEmbedInField ] =
85                $wgPageFormsEmbeddedTemplates[$tif->mTemplateName];
86            $tif->mPlaceholder = PFFormPrinter::placeholderFormat( $tif->mEmbedInTemplate, $tif->mEmbedInField );
87        }
88
89        // Cycle through the other components.
90        for ( $i = 2; $i < count( $tag_components ); $i++ ) {
91            $component = $tag_components[$i];
92            if ( $component == 'multiple' ) {
93                $tif->mAllowMultiple = true;
94            } elseif ( $component == 'strict' ) {
95                $tif->mStrictParsing = true;
96            }
97            $sub_components = array_map( 'trim', explode( '=', $component, 2 ) );
98            if ( count( $sub_components ) == 2 ) {
99                if ( $sub_components[0] == 'label' ) {
100                    $tif->mLabel = PFFormPrinter::getParsedValue( $parser, $sub_components[1] );
101                } elseif ( $sub_components[0] == 'intro' ) {
102                    $tif->mIntro = $sub_components[1];
103                } elseif ( $sub_components[0] == 'minimum instances' ) {
104                    $tif->mMinAllowed = $sub_components[1];
105                } elseif ( $sub_components[0] == 'maximum instances' ) {
106                    $tif->mMaxAllowed = $sub_components[1];
107                } elseif ( $sub_components[0] == 'add button text' ) {
108                    $tif->mAddButtonText = PFFormPrinter::getParsedValue( $parser, $sub_components[1] );
109                } elseif ( $sub_components[0] == 'embed in field' ) {
110                    // Placeholder on form template level. Assume that the template form def
111                    // will have a multiple+placeholder parameters, and get the placeholder value.
112                    // We expect something like TemplateName[fieldName], and convert it to the
113                    // TemplateName___fieldName form used internally.
114                    preg_match( '/\s*(.*)\[(.*)\]\s*/', $sub_components[1], $matches );
115                    if ( count( $matches ) > 2 ) {
116                        $tif->mEmbedInTemplate = $matches[1];
117                        $tif->mEmbedInField = $matches[2];
118                        $tif->mPlaceholder = PFFormPrinter::placeholderFormat( $tif->mEmbedInTemplate, $tif->mEmbedInField );
119                    }
120                } elseif ( $sub_components[0] == 'display' ) {
121                    $tif->mDisplay = $sub_components[1];
122                } elseif ( $sub_components[0] == 'height' ) {
123                    $tif->mHeight = $sub_components[1];
124                } elseif ( $sub_components[0] == 'displayed fields when minimized' ) {
125                    $tif->mDisplayedFieldsWhenMinimized = $sub_components[1];
126                } elseif ( $sub_components[0] == 'event title field' ) {
127                    $tif->mEventTitleField = $sub_components[1];
128                } elseif ( $sub_components[0] == 'event date field' ) {
129                    $tif->mEventDateField = $sub_components[1];
130                } elseif ( $sub_components[0] == 'event start date field' ) {
131                    $tif->mEventStartDateField = $sub_components[1];
132                } elseif ( $sub_components[0] == 'event end date field' ) {
133                    $tif->mEventEndDateField = $sub_components[1];
134                }
135            }
136        }
137
138        return $tif;
139    }
140
141    function getTemplateName() {
142        return $this->mTemplateName;
143    }
144
145    function getHeight() {
146        return $this->mHeight;
147    }
148
149    function getFields() {
150        return $this->mFields;
151    }
152
153    function getEmbedInTemplate() {
154        return $this->mEmbedInTemplate;
155    }
156
157    function getEmbedInField() {
158        return $this->mEmbedInField;
159    }
160
161    function getLabel() {
162        return $this->mLabel;
163    }
164
165    function getIntro() {
166        return $this->mIntro;
167    }
168
169    function getAddButtonText() {
170        return $this->mAddButtonText;
171    }
172
173    function getDisplay() {
174        return $this->mDisplay;
175    }
176
177    function getEventTitleField() {
178        return $this->mEventTitleField;
179    }
180
181    function getEventDateField() {
182        return $this->mEventDateField;
183    }
184
185    function getEventStartDateField() {
186        return $this->mEventStartDateField;
187    }
188
189    function getEventEndDateField() {
190        return $this->mEventEndDateField;
191    }
192
193    function getPlaceholder() {
194        return $this->mPlaceholder;
195    }
196
197    function getDisplayedFieldsWhenMinimized() {
198        return $this->mDisplayedFieldsWhenMinimized;
199    }
200
201    function allowsMultiple() {
202        return $this->mAllowMultiple;
203    }
204
205    function strictParsing() {
206        return $this->mStrictParsing;
207    }
208
209    function getMinInstancesAllowed() {
210        return $this->mMinAllowed;
211    }
212
213    function getMaxInstancesAllowed() {
214        return $this->mMaxAllowed;
215    }
216
217    function createMarkup() {
218        $text = "{{{for template|" . $this->mTemplateName;
219        if ( $this->mAllowMultiple ) {
220            $text .= "|multiple";
221        }
222        if ( $this->mLabel != '' ) {
223            $text .= "|label=" . $this->mLabel;
224        }
225        $text .= "}}}\n";
226        // For now, HTML for templates differs for multiple-instance
227        // templates; this may change if handling of form definitions
228        // gets more sophisticated.
229        if ( !$this->mAllowMultiple ) {
230            $text .= "{| class=\"formtable\"\n";
231        }
232        foreach ( $this->mFields as $i => $field ) {
233            $is_last_field = ( $i == count( $this->mFields ) - 1 );
234            $text .= $field->createMarkup( $this->mAllowMultiple, $is_last_field );
235        }
236        if ( !$this->mAllowMultiple ) {
237            $text .= "|}\n";
238        }
239        $text .= "{{{end template}}}\n";
240        return $text;
241    }
242
243    /**
244     * This method, and all methods below it, are intended for an instance
245     * of a template in a specific form, and perhaps should be moved into
246     * another class.
247     *
248     * @return string
249     */
250    function getFullTextInPage() {
251        return $this->mFullTextInPage;
252    }
253
254    function pageCallsThisTemplate() {
255        return $this->mPageCallsThisTemplate;
256    }
257
258    function hasValueFromPageForField( $field_name ) {
259        return array_key_exists( $field_name, $this->mValuesFromPage );
260    }
261
262    function getAndRemoveValueFromPageForField( $field_name ) {
263        $value = $this->mValuesFromPage[$field_name];
264        unset( $this->mValuesFromPage[$field_name] );
265        return $value;
266    }
267
268    function getValuesFromPage() {
269        return $this->mValuesFromPage;
270    }
271
272    function getInstanceNum() {
273        return $this->mInstanceNum;
274    }
275
276    function getGridValues() {
277        return $this->mGridValues;
278    }
279
280    function incrementInstanceNum() {
281        $this->mInstanceNum++;
282    }
283
284    function allInstancesPrinted() {
285        return $this->mAllInstancesPrinted;
286    }
287
288    function addGridValue( $field_name, $cur_value ) {
289        if ( !array_key_exists( $this->mInstanceNum, $this->mGridValues ) ) {
290            $this->mGridValues[$this->mInstanceNum] = [];
291        }
292        $this->mGridValues[$this->mInstanceNum][$field_name] = $cur_value;
293    }
294
295    function addField( $form_field ) {
296        $this->mFields[] = $form_field;
297    }
298
299    /**
300     * This makes it possible for += and -= to modify values based on existing values.
301     *
302     * @param string $field_name
303     * @param string $new_value
304     * @param string|null $modifier
305     */
306    function changeFieldValues( $field_name, $new_value, $modifier = null ) {
307        $this->mValuesFromPage[$field_name] = $new_value;
308        if ( $modifier !== null && array_key_exists( $field_name . $modifier, $this->mValuesFromPage ) ) {
309            // clean up old values with + or - in them from the array
310            unset( $this->mValuesFromPage[$field_name . $modifier] );
311        }
312    }
313
314    function setFieldValuesFromSubmit( WebRequest $request ) {
315        // Reset values for every new instance, if this is a
316        // multiple-instance template.
317        if ( $this->mInstanceNum > 0 ) {
318            $this->mValuesFromSubmit = [];
319        }
320
321        $query_template_name = str_replace( ' ', '_', $this->mTemplateName );
322        // Also replace periods with underlines, since that's what
323        // POST does to strings anyway.
324        $query_template_name = str_replace( '.', '_', $query_template_name );
325        // ...and escape apostrophes.
326        //  (Or don't.)
327        // $query_template_name = str_replace( "'", "\'", $query_template_name );
328
329        $allValuesFromSubmit = $request->getArray( $query_template_name );
330        if ( $allValuesFromSubmit === null ) {
331            return;
332        }
333        // If this is a multiple-instance template, get the values for
334        // this instance of the template.
335        if ( $this->mAllowMultiple ) {
336            // If this data came from a spreadsheet, unescape some characters.
337            $spreadsheetTemplates = $request->getArray( 'spreadsheet_templates' );
338            if ( is_array( $spreadsheetTemplates ) && array_key_exists( $query_template_name, $spreadsheetTemplates ) ) {
339                foreach ( $allValuesFromSubmit as &$rowValues ) {
340                    foreach ( $rowValues as &$curValue ) {
341                        $curValue = str_replace( [ '&lt;', '&gt;' ], [ '<', '>' ], $curValue );
342                    }
343                }
344            }
345            $valuesFromSubmitKeys = [];
346            foreach ( array_keys( $allValuesFromSubmit ) as $key ) {
347                if ( $key != 'num' ) {
348                    $valuesFromSubmitKeys[] = $key;
349                }
350            }
351            $this->mNumInstancesFromSubmit = count( $valuesFromSubmitKeys );
352            if ( $this->mNumInstancesFromSubmit > $this->mInstanceNum ) {
353                $instanceKey = $valuesFromSubmitKeys[$this->mInstanceNum];
354                $this->mValuesFromSubmit = $allValuesFromSubmit[$instanceKey];
355            }
356        } else {
357            $this->mValuesFromSubmit = $allValuesFromSubmit;
358        }
359    }
360
361    function getValuesFromSubmit() {
362        return $this->mValuesFromSubmit;
363    }
364
365    /**
366     * Remove all the bits that should not be parsed - those
367     * contained in <pre> tags, etc. - and place them in an array,
368     * so that they can be added back in later. This will prevent
369     * the brackets, curly braces and pipes within those bits from
370     * interfering with the parsing we need to do.
371     *
372     * @param string $str
373     * @param string[] &$replacements
374     * @return string
375     */
376    static function removeUnparsedText( $str, &$replacements ) {
377        $startAndEndTags = [
378            [ '<pre', 'pre>' ],
379            [ '<syntaxhighlight', 'syntaxhighlight>' ],
380            [ '<source', 'source>' ],
381            [ '<ref', 'ref>' ],
382            [ '<nowiki', 'nowiki>' ]
383        ];
384        foreach ( $startAndEndTags as $tags ) {
385            [ $startTag, $endTag ] = $tags;
386
387            $startTagLoc = -1;
388            while ( ( $startTagLoc + strlen( $startTag ) < strlen( $str ) ) &&
389                ( ( $startTagLoc = strpos( $str, $startTag, $startTagLoc + strlen( $startTag ) ) ) !== false ) ) {
390                // Ignore "singleton" tags, like '<ref name="abc" />'.
391                $possibleSingletonTagEnd = strpos( $str, '/>', $startTagLoc );
392                if ( $possibleSingletonTagEnd !== false && $possibleSingletonTagEnd < strpos( $str, '>', $startTagLoc ) ) {
393                    continue;
394                }
395                $endTagLoc = strpos( $str, $endTag, $startTagLoc + strlen( $startTag ) );
396                // Also ignore unclosed tags.
397                if ( $endTagLoc === false ) {
398                    continue;
399                }
400                $fullTagTextLength = $endTagLoc + strlen( $endTag ) - $startTagLoc;
401                $replacements[] = substr( $str, $startTagLoc, $fullTagTextLength );
402                $replacementNum = count( $replacements ) - 1;
403                $str = substr_replace( $str, "\1" . $replacementNum . "\2", $startTagLoc, $fullTagTextLength );
404            }
405        }
406        return $str;
407    }
408
409    /**
410     * @param string $str
411     * @param string[] $replacements
412     * @return string
413     */
414    static function restoreUnparsedText( $str, $replacements ) {
415        foreach ( $replacements as $i => $fullTagText ) {
416            $str = str_replace( "\1" . $i . "\2", $fullTagText, $str );
417        }
418        return $str;
419    }
420
421    function setFieldValuesFromPage( $existing_page_content ) {
422        $unparsedTextReplacements = [];
423        $existing_page_content = self::removeUnparsedText( $existing_page_content, $unparsedTextReplacements );
424        $matches = [];
425        $search_pattern = '/{{' . $this->mPregMatchTemplateStr . '\s*[\|}]/i';
426        $content_str = str_replace( '_', ' ', $existing_page_content );
427        preg_match( $search_pattern, $content_str, $matches, PREG_OFFSET_CAPTURE );
428        // is this check necessary?
429        if ( array_key_exists( 0, $matches ) && array_key_exists( 1, $matches[0] ) ) {
430            $start_char = $matches[0][1];
431            $fields_start_char = $start_char + 2 + strlen( $this->mSearchTemplateStr );
432            // Skip ahead to the first real character.
433            while ( in_array( $existing_page_content[$fields_start_char], [ ' ', '\n' ] ) ) {
434                $fields_start_char++;
435            }
436            // If the next character is a pipe, skip that too.
437            if ( $existing_page_content[$fields_start_char] == '|' ) {
438                $fields_start_char++;
439            }
440            $this->mValuesFromPage = [ '0' => '' ];
441            // Cycle through template call, splitting it up by pipes ('|'),
442            // except when that pipe is part of a piped link.
443            $field = "";
444            $uncompleted_square_brackets = 0;
445            $uncompleted_curly_brackets = 2;
446            $template_ended = false;
447            for ( $i = $fields_start_char; !$template_ended && ( $i < strlen( $existing_page_content ) ); $i++ ) {
448                $c = $existing_page_content[$i];
449                if ( $i + 1 < strlen( $existing_page_content ) ) {
450                    $nextc = $existing_page_content[$i + 1];
451                } else {
452                    $nextc = null;
453                }
454                if ( $i > 0 ) {
455                    $prevc = $existing_page_content[$i - 1];
456                } else {
457                    $prevc = null;
458                }
459                if ( $c == '[' && ( $nextc == '[' || $prevc == '[' ) ) {
460                    $uncompleted_square_brackets++;
461                } elseif ( $c == ']' && ( $nextc == ']' || $prevc == ']' ) && $uncompleted_square_brackets > 0 ) {
462                    $uncompleted_square_brackets--;
463                } elseif ( $c == '{' && ( $nextc == '{' || $prevc == '{' ) ) {
464                    $uncompleted_curly_brackets++;
465                } elseif ( $c == '}' && ( $nextc == '}' || $prevc == '}' ) && $uncompleted_curly_brackets > 0 ) {
466                    $uncompleted_curly_brackets--;
467                }
468                // handle an end to a field and/or template declaration
469                $template_ended = ( $uncompleted_curly_brackets == 0 && $uncompleted_square_brackets == 0 );
470                $field_ended = ( $c == '|' && $uncompleted_square_brackets == 0 && $uncompleted_curly_brackets <= 2 );
471                if ( $template_ended || $field_ended ) {
472                    // If this was the last character in the template, remove
473                    // the closing curly brackets.
474                    if ( $template_ended ) {
475                        $field = substr( $field, 0, -1 );
476                    }
477                    $field = self::restoreUnparsedText( $field, $unparsedTextReplacements );
478                    // Either there's an equals sign near the beginning or not -
479                    // handling is similar in either way; if there's no equals
480                    // sign, the index of this field becomes the key.
481                    $sub_fields = explode( '=', $field, 2 );
482                    if ( count( $sub_fields ) > 1 ) {
483                        $this->mValuesFromPage[trim( $sub_fields[0] )] = trim( $sub_fields[1] );
484                    } else {
485                        $this->mValuesFromPage[] = trim( $sub_fields[0] );
486                    }
487                    $field = '';
488                } else {
489                    $field .= $c;
490                }
491            }
492
493            // If there are uncompleted opening brackets, the whole form will get messed up -
494            // throw an exception.
495            // (If there are too many *closing* brackets, some template stuff will end up in
496            // the "free text" field - which is bad, but it's harder for the code to detect
497            // the problem - though hopefully, easier for users.)
498            if ( $uncompleted_curly_brackets > 0 || $uncompleted_square_brackets > 0 ) {
499                throw new MWException( "PageFormsMismatchedBrackets" );
500            }
501
502            $fullText = substr( $existing_page_content, $start_char, $i - $start_char );
503            $this->mFullTextInPage = self::restoreUnparsedText( $fullText, $unparsedTextReplacements );
504        }
505    }
506
507    /**
508     * Set some vars based on the current contents of the page being
509     * edited - or at least vars that only need to be set if there's
510     * an existing page.
511     * @param string $existing_page_content
512     */
513    function setPageRelatedInfo( $existing_page_content ) {
514        // Replace underlines with spaces in template name, to allow for
515        // searching on either.
516        $this->mSearchTemplateStr = str_replace( '_', ' ', $this->mTemplateName );
517        $this->mPregMatchTemplateStr = str_replace(
518            [ '/', '(', ')', '^' ],
519            [ '\/', '\(', '\)', '\^' ],
520            $this->mSearchTemplateStr
521        );
522        // Strip content inside <pre>, <nowiki>, <syntaxhighlight> etc. before
523        // checking whether the page calls this template. Without this, a template
524        // inside a <pre> block would be falsely detected, causing checkIfAllInstancesPrinted()
525        // to never terminate the multiple-template loop.
526        $unparsedTextReplacements = [];
527        $contentToSearch = self::removeUnparsedText( $existing_page_content, $unparsedTextReplacements );
528        $this->mPageCallsThisTemplate = preg_match( '/{{' . $this->mPregMatchTemplateStr . '\s*[\|}]/i', str_replace( '_', ' ', $contentToSearch ) );
529    }
530
531    function checkIfAllInstancesPrinted( $form_submitted, $source_is_page, $is_autoedit ) {
532        // Find additional instances of this template in the page
533        // (if it's an existing page) or the query string (if it's a
534        // new page).
535        // If there's at least one, re-parse this section of the
536        // definition form for the subsequent template instance;
537        // if there's none, don't include fields at all.
538        // @TODO - There has to be a more efficient way to handle
539        // multiple instances of templates, one that doesn't involve
540        // re-parsing the same tags, but I don't know what it is.
541        // (Also add additional, blank instances if there's a minimum
542        // number required in this form, and we haven't reached it yet.)
543        if ( !$this->mAllowMultiple ) {
544            return;
545        }
546        if ( $form_submitted && $this->mInstanceNum < $this->mNumInstancesFromSubmit ) {
547            return;
548        }
549        if ( !$form_submitted && !$is_autoedit && $this->mInstanceNum < $this->mMinAllowed ) {
550            return;
551        }
552        if ( !$form_submitted && $source_is_page && $this->mPageCallsThisTemplate ) {
553            return;
554        }
555        if ( !$form_submitted && !$source_is_page && $this->mValuesFromSubmit != null ) {
556            return;
557        }
558        $this->mAllInstancesPrinted = true;
559    }
560
561}