Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
3.28% covered (danger)
3.28%
4 / 122
33.33% covered (danger)
33.33%
1 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
PFFormLink
3.28% covered (danger)
3.28%
4 / 122
33.33% covered (danger)
33.33%
1 / 3
2312.07
0.00% covered (danger)
0.00%
0 / 1
 run
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 createFormLink
0.00% covered (danger)
0.00%
0 / 116
0.00% covered (danger)
0.00%
0 / 1
2352
 loadScriptsForPopupForm
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3use MediaWiki\Html\Html;
4use MediaWiki\MediaWikiServices;
5use MediaWiki\Parser\ParserOutputFlags;
6use MediaWiki\Title\Title;
7
8/**
9 * '#formlink' is called as:
10 *
11 * {{#formlink:form=|link text=|link type=|tooltip=|query string=|target=
12 * |popup|reload|...additional query string values...}}
13 *
14 * This function returns HTML representing a link to a form; given that
15 * no page name is entered by the user, the form must be one that
16 * creates an automatic page name, or else it will display an error
17 * message when the user clicks on the link.
18 *
19 * The first two arguments are mandatory:
20 * 'form' is the name of the PF form, and 'link text' is the text of the link.
21 * 'link type' is the type of the link: if set to 'button', the link will be
22 * a button; if set to 'post button', the link will be a button that uses the
23 * 'POST' method to send other values to the form; if set to anything else or
24 * not called, it will be a standard hyperlink.
25 * 'tooltip' sets a hovering tooltip text, if it's an actual link.
26 * 'query string' is the text to be added to the generated URL's query string
27 * (or, in the case of 'post button', to be sent as hidden inputs).
28 * 'target' is an optional value, setting the name of the page to be
29 * edited by the form.
30 * 'reload' is an optional parameter that can be used alongside either
31 * 'popup' or 'returnto'; it causes the page that the user ends up on after
32 * submitting the form to get reloaded with 'action=purge'.
33 *
34 * Example: to create a link to add data with a form called
35 * 'User' within a namespace also called 'User', and to have the form
36 * preload with the page called 'UserStub', you could call the following:
37 *
38 * {{#formlink:form=User|link text=Add a user
39 * |query string=namespace=User&preload=UserStub}}
40 */
41
42class PFFormLink {
43
44    public static function run( Parser $parser ) {
45        $params = func_get_args();
46        // We don't need the parser.
47        array_shift( $params );
48        $str = self::createFormLink( $parser, $params );
49        return [ $str, 'noparse' => true, 'isHTML' => true ];
50    }
51
52    protected static function createFormLink( Parser $parser, $params ) {
53        // Set defaults.
54        $inFormName = $inLinkStr = $inExistingPageLinkStr = $inLinkType =
55            $inTooltip = $inTargetName = '';
56        $hasPopup = $hasReturnTo = false;
57        $className = static::class;
58        if ( $className == 'PFQueryFormLink' ) {
59            $inLinkStr = wfMessage( 'runquery' )->text();
60        }
61        $inCreatePage = false;
62        $classStr = '';
63        $inQueryArr = [];
64        $targetWindow = '_self';
65
66        // Needed for the 'next' icon.
67        $parser->getOutput()->addModules( [ 'oojs-ui.styles.icons-movement' ] );
68
69        // assign params
70        // - support unlabelled params, for backwards compatibility
71        // - parse and sanitize all parameter values
72        foreach ( $params as $i => $param ) {
73            $elements = explode( '=', $param, 2 );
74
75            // set param_name and value
76            if ( count( $elements ) > 1 ) {
77                $param_name = trim( $elements[0] );
78
79                // parse (and sanitize) parameter values
80                $value = trim( $parser->recursiveTagParse( $elements[1] ) );
81            } else {
82                $param_name = null;
83
84                // parse (and sanitize) parameter values
85                $value = trim( $parser->recursiveTagParse( $param ) );
86            }
87
88            if ( $param_name == 'form' ) {
89                $inFormName = $value;
90            } elseif ( $param_name == 'link text' ) {
91                $inLinkStr = $value;
92            } elseif ( $param_name == 'existing page link text' ) {
93                $inExistingPageLinkStr = $value;
94            } elseif ( $param_name == 'link type' ) {
95                $inLinkType = $value;
96            } elseif ( $param_name == 'query string' ) {
97                $inQueryArr = PFAutoEdit::convertQueryString( $value, $inQueryArr );
98            } elseif ( $param_name == 'tooltip' ) {
99                $inTooltip = Sanitizer::decodeCharReferences( $value );
100            } elseif ( $param_name == 'target' ) {
101                $inTargetName = Sanitizer::decodeCharReferences( $value );
102            } elseif ( $param_name == null && $value == 'popup' ) {
103                self::loadScriptsForPopupForm( $parser );
104                $classStr = 'popupformlink';
105                $hasPopup = true;
106            } elseif ( $param_name == null && $value == 'reload' ) {
107                $classStr .= ' reload';
108                $inQueryArr['reload'] = '1';
109            } elseif ( $param_name == null && $value == 'new window' ) {
110                $targetWindow = '_blank';
111            } elseif ( $param_name == null && $value == 'create page' ) {
112                $inCreatePage = true;
113            } elseif ( $param_name !== null ) {
114                $value = urlencode( $value );
115                parse_str( "$param_name=$value", $arr );
116                $inQueryArr = PFUtils::arrayMergeRecursiveDistinct( $inQueryArr, $arr );
117                if ( $param_name == 'returnto' ) {
118                    $hasReturnTo = true;
119                }
120            }
121        }
122
123        if ( $hasPopup && $hasReturnTo ) {
124            return '<div class="error">Error: \'popup\' and \'returnto\' cannot be set in the same function.</div>';
125        }
126
127        // Not the most graceful way to do this, but it is the
128        // easiest - if this is the #formredlink function, just
129        // ignore whatever values were passed in for these params.
130        if ( $className == 'PFFormRedLink' ) {
131            $inLinkType = $inTooltip = null;
132        }
133
134        // If "red link only" was specified, and a target page was
135        // specified, and it exists, just link to the page.
136        if ( $inTargetName != '' ) {
137            // Call urldecode() on it, in case the target was
138            // set via {{PAGENAMEE}}, and the page name contains
139            // an apostrophe or other unusual character.
140            $targetTitle = Title::newFromText( urldecode( $inTargetName ) );
141            $targetPageExists = ( $targetTitle != '' && $targetTitle->exists() );
142        } else {
143            $targetPageExists = false;
144        }
145
146        if ( $className == 'PFFormRedLink' && $targetPageExists ) {
147            // Use PFUtils::makeLink so that labels containing HTML
148            // (resulting from parsed wiki markup) are handled safely
149            // and rendered correctly.
150            $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
151            return PFUtils::makeLink( $linkRenderer, $targetTitle, $inExistingPageLinkStr );
152        }
153
154        // The page doesn't exist, so if 'create page' was
155        // specified, create the page now.
156        if ( $className == 'PFFormRedLink' &&
157            $inCreatePage && $inTargetName != '' &&
158            // Skip if we're not looking at the current revision of this page.
159            $parser->getRevisionRecordObject()->isCurrent() ) {
160            $targetTitle = Title::newFromText( $inTargetName );
161            $parser->getOutput()->setOutputFlag( ParserOutputFlags::ENABLE_OOUI );
162            PFFormLinker::createPageWithForm( $targetTitle, $inFormName, $inQueryArr );
163        }
164
165        if ( $className == 'PFQueryFormLink' ) {
166            $formSpecialPage = PFUtils::getSpecialPage( 'RunQuery' );
167        } else {
168            $formSpecialPage = PFUtils::getSpecialPage( 'FormEdit' );
169        }
170        $formSpecialPageTitle = $formSpecialPage->getPageTitle();
171
172        if ( $inFormName == '' ) {
173            $query = [ 'target' => $inTargetName ];
174            $link_url = $formSpecialPageTitle->getLocalURL( $query );
175        } elseif ( strpos( $inFormName, '/' ) == true ) {
176            $query = [ 'form' => $inFormName, 'target' => $inTargetName ];
177            $link_url = $formSpecialPageTitle->getLocalURL( $query );
178        } else {
179            $link_url = $formSpecialPageTitle->getLocalURL() . "/$inFormName";
180            if ( !empty( $inTargetName ) ) {
181                $link_url .= "/$inTargetName";
182            }
183            $link_url = str_replace( ' ', '_', $link_url );
184        }
185        $hidden_inputs = "";
186        if ( !empty( $inQueryArr ) ) {
187            // Special handling for the buttons - query string
188            // has to be turned into hidden inputs.
189            if ( $inLinkType == 'button' || $inLinkType == 'post button' ) {
190                $query_components = explode( '&', http_build_query( $inQueryArr, '', '&' ) );
191
192                foreach ( $query_components as $query_component ) {
193                    $var_and_val = explode( '=', $query_component, 2 );
194                    if ( count( $var_and_val ) == 2 ) {
195                        $hidden_inputs .= Html::hidden( urldecode( $var_and_val[0] ), urldecode( $var_and_val[1] ) );
196                    }
197                }
198            } else {
199                $link_url .= ( strstr( $link_url, '?' ) ) ? '&' : '?';
200                $link_url .= str_replace( '+', '%20', http_build_query( $inQueryArr, '', '&' ) );
201            }
202        }
203        if ( $inLinkType == 'button' || $inLinkType == 'post button' ) {
204            // The 'oo-ui-layout' class is needed for at least the Timeless skin.
205            $classStr = 'oo-ui-layout ' . $classStr;
206
207            $parser->getOutput()->setEnableOOUI( true );
208            OutputPage::setupOOUI();
209            $buttonAttrs = [
210                'type' => 'submit',
211                'label' => $inLinkStr,
212                'title' => $inTooltip,
213                'flags' => 'progressive',
214                'icon' => 'next'
215            ];
216            $buttonHTML = new OOUI\ButtonInputWidget( $buttonAttrs );
217            $formAttrs = [
218                'action' => $link_url,
219                'method' => ( $inLinkType == 'button' ) ? 'get' : 'post',
220                'class' => $classStr,
221                'target' => $targetWindow
222            ];
223            $str = Html::rawElement( 'form', $formAttrs, $buttonHTML . $hidden_inputs );
224        } else {
225            // If a target page has been specified but it doesn't
226            // exist, make it a red link.
227            if ( !empty( $inTargetName ) ) {
228                if ( !$targetPageExists ) {
229                    $classStr .= " new";
230                }
231                // If no link string was specified, make it
232                // the name of the page.
233                if ( $inLinkStr == '' ) {
234                    $inLinkStr = $inTargetName;
235                }
236            }
237            $str = Html::rawElement( 'a', [ 'href' => $link_url, 'class' => $classStr, 'title' => $inTooltip, 'target' => $targetWindow ], $inLinkStr );
238        }
239
240        return $str;
241    }
242
243    public static function loadScriptsForPopupForm( Parser $parser ) {
244        $parser->getOutput()->addModules( [ 'ext.pageforms.popupformedit' ] );
245        return true;
246    }
247}