Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 131
0.00% covered (danger)
0.00%
0 / 1
CRAP
0.00% covered (danger)
0.00%
0 / 1
PFFormInputParserFunction
0.00% covered (danger)
0.00%
0 / 131
0.00% covered (danger)
0.00%
0 / 1
1332
0.00% covered (danger)
0.00%
0 / 1
 run
0.00% covered (danger)
0.00%
0 / 131
0.00% covered (danger)
0.00%
0 / 1
1332
1<?php
2/**
3 * '#forminput' is called as:
4 *
5 * {{#forminput:form=|size=|default value=|button text=|query string=
6 * |autocomplete on category=|autocomplete on namespace=
7 * |popup|reload|...additional query string values...}}
8 *
9 * This function returns HTML representing a form to let the user enter the
10 * name of a page to be added or edited using a Page Forms form. All
11 * arguments are optional. 'form' is the name of the PF form to be used;
12 * if it is left empty, a dropdown will appear, letting the user chose among
13 * all existing forms. 'size' represents the size of the text input (the
14 * default is the one set by OOUI), and 'default value' is the starting
15 * value of the input. 'button text' is the text that will appear on the
16 * "submit" button, and 'query string' is the set of values that you want
17 * passed in through the query string to the form. (Query string values can
18 * also be passed in directly as parameters.)
19 * Finally, you can can specify that the user will get autocompletion using
20 * the values from a category or namespace of your choice, using
21 * 'autocomplete on category' or 'autocomplete on namespace' (you can only
22 * use one). To autocomplete on all pages in the main (blank)
23 * namespace, specify "autocomplete on namespace=main".
24 * 'reload' is an optional parameter that can be used alongside either
25 * 'popup' or 'returnto'; it causes the page that the user ends up on after
26 * submitting the form to get reloaded with 'action=purge'.
27 *
28 * Example: to create an input to add or edit a page with a form called
29 * 'User' within a namespace also called 'User', and to have the form
30 * preload with the page called 'UserStub', you could call the following:
31 *
32 * {{#forminput:form=User|button text=Add or edit user
33 * |query string=namespace=User&preload=UserStub}}
34 */
35
36use MediaWiki\Html\Html;
37use MediaWiki\MediaWikiServices;
38
39class PFFormInputParserFunction {
40    /**
41     * static variable to guarantee that JavaScript for autocompletion
42     * only gets added to the page once.
43     */
44    private static $num_autocompletion_inputs = 0;
45
46    public static function run( Parser $parser ) {
47        global $wgCapitalLinks;
48
49        $params = func_get_args();
50        // We don't need the parser.
51        array_shift( $params );
52
53        $parser->getOutput()->addModules( [ 'ext.pageforms.forminput' ] );
54
55        // Set defaults.
56        $inFormName = $inValue = $inButtonStr = '';
57        $inQueryArr = [];
58        $inAutocompletionSource = '';
59        $inSize = '';
60        $classStr = "pfFormInput";
61        $inNamespaceSelector = null;
62        $inPlaceholder = null;
63        $inAutofocus = true;
64        $hasPopup = $hasReturnTo = false;
65
66        // Assign params.
67        foreach ( $params as $i => $param ) {
68            $elements = explode( '=', $param, 2 );
69
70            // Set param name and value.
71            if ( count( $elements ) > 1 ) {
72                $paramName = trim( $elements[0] );
73                // Parse (and sanitize) parameter values.
74                // We call recursivePreprocess() and not
75                // recursiveTagParse() so that URL values will
76                // not be turned into links.
77                $value = trim( $parser->recursivePreprocess( html_entity_decode( $elements[1], ENT_QUOTES ) ) );
78            } else {
79                $paramName = trim( $param );
80                $value = null;
81            }
82
83            switch ( $paramName ) {
84                case 'form':
85                    $inFormName = $value;
86                    break;
87                case 'size':
88                    $inSize = $value;
89                    break;
90                case 'default value':
91                    $inValue = $value;
92                    break;
93                case 'button text':
94                    $inButtonStr = $value;
95                    break;
96                case 'query string':
97                    $inQueryArr = PFAutoEdit::convertQueryString( $value, $inQueryArr );
98                    break;
99                case 'autocomplete on category':
100                    $inAutocompletionSource = $value;
101                    $autocompletionType = 'category';
102                    break;
103                case 'autocomplete on namespace':
104                    $inAutocompletionSource = $value;
105                    $autocompletionType = 'namespace';
106                    break;
107                case 'namespace selector':
108                    $inNamespaceSelector = explode( ',', $value );
109                    break;
110                case 'placeholder':
111                    $inPlaceholder = $value;
112                    break;
113                case 'popup':
114                    PFFormLink::loadScriptsForPopupForm( $parser );
115                    $classStr .= ' popupforminput';
116                    $hasPopup = true;
117                    break;
118                case 'reload':
119                    $classStr .= ' reload';
120                    $inQueryArr['reload'] = '1';
121                    break;
122                case 'no autofocus':
123                    $inAutofocus = false;
124                    break;
125                default:
126                    if ( $value !== null ) {
127                        $value = urlencode( $value );
128                        parse_str( "$paramName=$value", $arr );
129                        $inQueryArr = PFUtils::arrayMergeRecursiveDistinct( $inQueryArr, $arr );
130                        if ( $paramName === 'returnto' ) {
131                            $hasReturnTo = true;
132                        }
133                    }
134            }
135        }
136
137        if ( $hasPopup && $hasReturnTo ) {
138            return '<div class="error">Error: \'popup\' and \'returnto\' cannot be set in the same function.</div>';
139        }
140
141        $formInputAttrs = [
142            'class' => 'pfFormInputWrapper',
143            'data-size' => $inSize
144        ];
145
146        $formContents = '';
147
148        if ( $inValue != null ) {
149            $formInputAttrs['data-default-value'] = $inValue;
150        }
151
152        if ( $inNamespaceSelector !== null ) {
153            foreach ( $inNamespaceSelector as &$nsName ) {
154                $nsName = htmlspecialchars( trim( $nsName ) );
155            }
156            $possibleNamespacesStr = implode( '|', $inNamespaceSelector );
157            $formInputAttrs['data-possible-namespaces'] = $possibleNamespacesStr;
158        }
159
160        if ( $inPlaceholder != null ) {
161            $formInputAttrs['data-placeholder'] = $inPlaceholder;
162        }
163        if ( $inAutofocus ) {
164            $formInputAttrs['data-autofocus'] = true;
165        }
166        if ( !$wgCapitalLinks ) {
167            $formInputAttrs['data-autocapitalize'] = 'off';
168        }
169
170        // Now apply the necessary settings and JavaScript, depending
171        // on whether or not there's autocompletion (and whether the
172        // autocompletion is local or remote).
173        $input_num = 1;
174        if ( !empty( $inAutocompletionSource ) ) {
175            self::$num_autocompletion_inputs++;
176            $input_num = self::$num_autocompletion_inputs;
177            $inputID = 'input_' . $input_num;
178            $formInputAttrs['id'] = $inputID;
179            // This code formerly only used remote autocompletion
180            // when the number of autocompletion values was above
181            // a certain limit - as happens in regular forms -
182            // but local autocompletion didn't always work,
183            // apparently due to page caching.
184            $formInputAttrs['data-autocomplete-settings'] = $inAutocompletionSource;
185            $formInputAttrs['data-autocomplete-data-type'] = $autocompletionType;
186        }
187
188        // If the form start URL looks like "index.php?title=Special:FormStart"
189        // (i.e., it's in the default URL style), add in the title as a
190        // hidden value
191        $fs = PFUtils::getSpecialPage( 'FormStart' );
192        $fsURL = $fs->getPageTitle()->getLocalURL();
193        $pos = strpos( $fsURL, "title=" );
194        if ( $pos > -1 ) {
195            $formContents .= Html::hidden( "title", urldecode( substr( $fsURL, $pos + 6 ) ) );
196        }
197        $listOfForms = preg_split( '~(?<!\\\)' . preg_quote( ',', '~' ) . '~', $inFormName );
198        foreach ( $listOfForms as & $formName ) {
199            $formName = str_replace( "\,", ",", $formName );
200        }
201        unset( $formName );
202        if ( $inFormName == '' ) {
203            try {
204                $allForms = PFUtils::getAllForms();
205            } catch ( MWException $e ) {
206                return Html::element( 'div', [ 'class' => 'error' ], $e->getMessage() );
207            }
208            $formInputAttrs['data-possible-forms'] = implode( '|', $allForms );
209            $formInputAttrs['data-form-label'] = wfMessage( 'pf-formstart-formlabel' )->escaped();
210        } elseif ( count( $listOfForms ) == 1 ) {
211            $inFormName = str_replace( '\,', ',', $inFormName );
212            $formContents .= Html::hidden( "form", $inFormName );
213        } else {
214            $formInputAttrs['data-possible-forms'] = implode( '|', $listOfForms );
215            $formInputAttrs['data-form-label'] = wfMessage( 'pf-formstart-formlabel' )->escaped();
216        }
217
218        // Recreate the passed-in query string as a set of hidden
219        // variables.
220        if ( !empty( $inQueryArr ) ) {
221            // Query string has to be turned into hidden inputs.
222            $query_components = explode( '&', http_build_query( $inQueryArr, '', '&' ) );
223
224            foreach ( $query_components as $query_component ) {
225                $var_and_val = explode( '=', $query_component, 2 );
226                if ( count( $var_and_val ) == 2 ) {
227                    $formContents .= Html::hidden( urldecode( $var_and_val[0] ), urldecode( $var_and_val[1] ) );
228                }
229            }
230        }
231
232        $formInputAttrs['data-button-label'] = ( $inButtonStr != '' ) ? $inButtonStr : wfMessage( 'pf_formstart_createoredit' )->escaped();
233        $formContents .= Html::element( 'div', $formInputAttrs, null );
234
235        MediaWikiServices::getInstance()->getHookContainer()->run( 'PageForms::FormInputEnd', [ $params, &$formContents ] );
236
237        $str = "\t" . Html::rawElement( 'form', [
238                'name' => 'createbox',
239                'action' => $fsURL,
240                'method' => 'get',
241                'class' => $classStr
242            ], '<p>' . $formContents . '</p>'
243        ) . "\n";
244
245        return [ $str, 'noparse' => true, 'isHTML' => true ];
246    }
247}