Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 140
0.00% covered (danger)
0.00%
0 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
PFAutoEdit
0.00% covered (danger)
0.00%
0 / 140
0.00% covered (danger)
0.00%
0 / 2
1560
0.00% covered (danger)
0.00%
0 / 1
 run
0.00% covered (danger)
0.00%
0 / 134
0.00% covered (danger)
0.00%
0 / 1
1482
 convertQueryString
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3/**
4 * '#autoedit' is called as:
5 *
6 * {{#autoedit:form=|target=|link text=|link type=|tooltip=|query string=
7 * |minor|reload}}
8 *
9 * This function creates a link or button that, when clicked on,
10 * automatically modifies the specified page according to the values in the
11 * 'query string' variable.
12 *
13 * The parameters of #autoedit are called in the same format as those
14 * of #formlink. T The two additions are:
15 * 'minor' - sets this to be a "minor edit"
16 * 'reload' - causes the page to reload after the user clicks the button
17 * or link.
18 */
19
20use MediaWiki\MediaWikiServices;
21
22class PFAutoEdit {
23    public static function run( Parser $parser ) {
24        global $wgPageFormsAutoeditNamespaces;
25
26        $parser->getOutput()->addModules( [ 'ext.pageforms.autoedit' ] );
27        if ( method_exists( $parser->getOutput(), 'setPreventClickjacking' ) ) {
28            // MW 1.38+
29            $parser->getOutput()->setPreventClickjacking( true );
30        } else {
31            $parser->getOutput()->preventClickjacking( true );
32        }
33
34        // Set defaults.
35        $formcontent = '';
36        $linkString = null;
37        $linkType = 'span';
38        $summary = null;
39        $minorEdit = false;
40        $classString = 'autoedit-trigger';
41        $inTooltip = null;
42        $inQueryArr = [];
43        $editTime = null;
44        $latestRevId = null;
45        $confirmEdit = false;
46        $redirect = '';
47        $bringToPage = false;
48        $linkToPage = '#';
49
50        // Parse parameters.
51        $params = func_get_args();
52        // We don't need the parser.
53        array_shift( $params );
54
55        $wikiPageFactory = MediaWikiServices::getInstance()->getWikiPageFactory();
56
57        foreach ( $params as $param ) {
58            $elements = explode( '=', $param, 2 );
59
60            $key = trim( $elements[ 0 ] );
61            $value = ( count( $elements ) > 1 ) ? trim( $elements[ 1 ] ) : '';
62
63            switch ( $key ) {
64                case 'link text':
65                    $linkString = $parser->recursiveTagParse( $value );
66                    break;
67                case 'link type':
68                    $linkType = $parser->recursiveTagParse( $value );
69                    break;
70                case 'reload':
71                    $classString .= ' reload';
72                    break;
73                case 'summary':
74                    $summary = $parser->recursiveTagParse( $value );
75                    break;
76                case 'minor':
77                    $minorEdit = true;
78                    break;
79                case 'confirm':
80                    $confirmEdit = true;
81                    break;
82                case 'query string':
83                    $inQueryArr = self::convertQueryString( $value, $inQueryArr );
84                    break;
85
86                case 'ok text':
87                case 'error text':
88                    // do not parse ok text or error text yet. Will be parsed on api call
89                    $arr = [ $key => $value ];
90                    $inQueryArr = PFUtils::arrayMergeRecursiveDistinct( $inQueryArr, $arr );
91                    break;
92                case 'tooltip':
93                    $inTooltip = Sanitizer::decodeCharReferences( $value );
94                    break;
95
96                case 'target':
97                case 'title':
98                    $value = $parser->recursiveTagParse( $value );
99                    $arr = [ $key => $value ];
100                    $inQueryArr = PFUtils::arrayMergeRecursiveDistinct( $inQueryArr, $arr );
101
102                    $targetTitle = Title::newFromText( $value );
103
104                    if ( $targetTitle !== null ) {
105                        $allowedNamespaces = array_merge(
106                            $wgPageFormsAutoeditNamespaces,
107                            [ NS_CATEGORY ]
108                        );
109                        if ( !in_array( $targetTitle->getNamespace(), $allowedNamespaces ) ) {
110                            $errorMsg = wfMessage( 'pf-autoedit-invalidnamespace', $targetTitle->getNsText() )->parse();
111                            return Html::element( 'div', [ 'class' => 'error' ], $errorMsg );
112                        }
113                        $targetWikiPage = $wikiPageFactory->newFromTitle( $targetTitle );
114                        $targetWikiPage->clear();
115                        $editTime = $targetWikiPage->getTimestamp();
116                        $latestRevId = $targetWikiPage->getLatest();
117                    }
118                    break;
119                case 'redirect':
120                    $redirect = $value;
121                    break;
122                case 'bring to page':
123                    $bringToPage = true;
124                    break;
125
126                default:
127                    $value = $parser->recursiveTagParse( $value );
128                    $arr = [ $key => $value ];
129                    $inQueryArr = PFUtils::arrayMergeRecursiveDistinct( $inQueryArr, $arr );
130            }
131        }
132
133        if ( $redirect != '' && $bringToPage ) {
134            $errorMsg = wfMessage( 'pf_autoedit_notsettogether', 'redirect', 'bring to page' )->parse();
135            return Html::element( 'div', [ 'class' => 'error' ], $errorMsg );
136        }
137
138        if ( $redirect != '' ) {
139            $linkToPage = $redirect;
140        } elseif ( $bringToPage ) {
141            $linkToPage = $targetTitle->getFullURL();
142        }
143
144        // query string has to be turned into hidden inputs.
145        if ( !empty( $inQueryArr ) ) {
146            $query_components = explode( '&', http_build_query( $inQueryArr, '', '&' ) );
147
148            foreach ( $query_components as $query_component ) {
149                $var_and_val = explode( '=', $query_component, 2 );
150                if ( count( $var_and_val ) == 2 ) {
151                    $formcontent .= Html::hidden( urldecode( $var_and_val[0] ), urldecode( $var_and_val[1] ) );
152                }
153            }
154        }
155
156        if ( $linkString == null ) {
157            return null;
158        }
159
160        if ( $linkType == 'button' ) {
161            $attrs = [
162                'flags' => 'progressive',
163                'label' => $linkString,
164                'classes' => [ $classString ],
165                'href' => $linkToPage
166            ];
167            if ( $inTooltip != null ) {
168                $attrs['title'] = $inTooltip;
169            }
170            $parser->getOutput()->setEnableOOUI( true );
171            OutputPage::setupOOUI();
172            $linkElement = new OOUI\ButtonWidget( $attrs );
173        } elseif ( $linkType == 'link' ) {
174            $attrs = [ 'class' => $classString, 'href' => $linkToPage ];
175            if ( $inTooltip != null ) {
176                $attrs['title'] = $inTooltip;
177            }
178            $linkElement = Html::rawElement( 'a', $attrs, $linkString );
179        } else {
180            $linkElement = Html::rawElement( 'span', [ 'class' => $classString ], $linkString );
181        }
182
183        if ( $summary == null ) {
184            $summary = wfMessage( 'pf_autoedit_summary', "[[{$parser->getTitle()}]]" )->text();
185        }
186
187        $formcontent .= Html::hidden( 'wpSummary', $summary );
188
189        if ( $minorEdit ) {
190            $formcontent .= Html::hidden( 'wpMinoredit', true );
191        }
192
193        if ( $editTime !== null ) {
194            $formcontent .= Html::hidden( 'wpEdittime', $editTime );
195        }
196        if ( $latestRevId !== null ) {
197            $formcontent .= Html::hidden( 'editRevId', $latestRevId );
198        }
199
200        if ( $confirmEdit ) {
201            $formAttrs = [ 'class' => [ 'autoedit-data', 'confirm-edit' ] ];
202        } else {
203            $formAttrs = [ 'class' => 'autoedit-data' ];
204        }
205
206        $form = Html::rawElement( 'form', $formAttrs, $formcontent );
207
208        $output = Html::rawElement( 'div', [ 'class' => 'autoedit' ],
209                $linkElement .
210                Html::rawElement( 'span', [ 'class' => "autoedit-result" ], null ) .
211                $form
212        );
213
214        // Return output HTML.
215        return [ $output, 'noparse' => true, 'isHTML' => true ];
216    }
217
218    public static function convertQueryString( $queryString, $inQueryArr ) {
219        // Change HTML-encoded ampersands directly to URL-encoded
220        // ampersands, so that the string doesn't get split up on the '&'.
221        $queryString = str_replace( '&amp;', '%26', $queryString );
222        // "Decode" any other HTML tags.
223        $queryString = html_entity_decode( $queryString, ENT_QUOTES );
224        // next, replace  Foo[Bar] += Baz  with  Foo[Bar+] = Baz
225        // and do the same for -=
226        // This way, parse_str won't strip out the += and -=
227        $queryString = preg_replace( "/\[([^\]]+)\]\s*(\+|-)=/", "[$1$2]=", $queryString );
228        // Prevent "decoding" + into a space character
229        $queryString = str_replace( '+', '%2B', $queryString );
230
231        parse_str( $queryString, $arr );
232
233        return PFUtils::arrayMergeRecursiveDistinct( $inQueryArr, $arr );
234    }
235}