Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 141
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
PFFormEditAction
0.00% covered (danger)
0.00%
0 / 141
0.00% covered (danger)
0.00%
0 / 9
1892
0.00% covered (danger)
0.00%
0 / 1
 getName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 show
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 displayTab
0.00% covered (danger)
0.00%
0 / 47
0.00% covered (danger)
0.00%
0 / 1
380
 displayFormChooser
0.00% covered (danger)
0.00%
0 / 44
0.00% covered (danger)
0.00%
0 / 1
132
 getNumPagesPerForm
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
6
 printLinksToFormArray
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 displayForm
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
12
 doesWrites
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3use MediaWiki\Html\Html;
4use MediaWiki\MediaWikiServices;
5
6/**
7 * Handles the formedit action.
8 *
9 * @author Yaron Koren
10 * @author Stephan Gambke
11 * @file
12 * @ingroup PF
13 */
14
15class PFFormEditAction extends Action {
16
17    /**
18     * Return the name of the action this object responds to
19     * @return string lowercase
20     */
21    public function getName() {
22        return 'formedit';
23    }
24
25    /**
26     * The main action entry point.  Do all output for display and send it to the context
27     * output. Do not use globals $wgOut, $wgRequest, etc, in implementations; use
28     * $this->getOutput(), etc.
29     * @throws ErrorPageError
30     * @return bool
31     */
32    public function show() {
33        return self::displayForm( $this, $this->getArticle() );
34    }
35
36    /**
37     * Execute the action in a silent fashion: do not display anything or release any errors.
38     * @return bool whether execution was successful
39     */
40    public function execute() {
41        return true;
42    }
43
44    /**
45     * Adds an "action" (i.e., a tab) to edit the current article with
46     * a form
47     * @param IContextSource $obj
48     * @param array &$links
49     */
50    static function displayTab( $obj, &$links ) {
51        $title = $obj->getTitle();
52        $user = $obj->getUser();
53
54        // Make sure that this is not a special page, and
55        // that the user is allowed to edit it
56        // - this function is almost never called on special pages,
57        // but before SMW is fully initialized, it's called on
58        // Special:SMWAdmin for some reason, which is why the
59        // special-page check is there.
60        if ( !isset( $title ) ||
61            ( $title->getNamespace() == NS_SPECIAL ) ) {
62            return;
63        }
64
65        $form_names = PFFormLinker::getDefaultFormsForPage( $title );
66        if ( count( $form_names ) == 0 ) {
67            return;
68        }
69
70        global $wgPageFormsRenameEditTabs, $wgPageFormsRenameMainEditTab;
71
72        $content_actions = &$links['views'];
73
74        $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
75        $user_can_edit = $permissionManager->userCan( 'edit', $user, $title, $permissionManager::RIGOR_QUICK );
76
77        // Create the form edit tab, and apply whatever changes are
78        // specified by the edit-tab global variables.
79        if ( $wgPageFormsRenameEditTabs ) {
80            $form_edit_tab_msg = $user_can_edit ? 'edit' : 'pf_viewform';
81            if ( array_key_exists( 'edit', $content_actions ) ) {
82                $msg = $user_can_edit ? 'pf_editsource' : 'viewsource';
83                $content_actions['edit']['text'] = wfMessage( $msg )->text();
84            }
85        } else {
86            if ( $user_can_edit ) {
87                $form_edit_tab_msg = $title->exists() ? 'formedit' : 'pf_formcreate';
88            } else {
89                $form_edit_tab_msg = 'pf_viewform';
90            }
91            // Check for renaming of main edit tab only if
92            // $wgPageFormsRenameEditTabs is off.
93            if ( $wgPageFormsRenameMainEditTab ) {
94                if ( array_key_exists( 'edit', $content_actions ) ) {
95                    $msg = $user_can_edit ? 'pf_editsource' : 'viewsource';
96                    $content_actions['edit']['text'] = wfMessage( $msg )->text();
97                }
98            }
99        }
100
101        $class_name = ( $obj->getRequest()->getVal( 'action' ) == 'formedit' ) ? 'selected' : '';
102        $form_edit_tab = [
103            'class' => $class_name,
104            'text' => wfMessage( $form_edit_tab_msg )->text(),
105            'href' => $title->getLocalURL( 'action=formedit' ),
106        ];
107
108        // For more recent MediaWiki versions, adding this icon adds
109        // this action to the MinervaNeue skin, while having no impact
110        // on the Vector 2022 skin; for earlier versions, it has no
111        // impact on MinervaNeue, while replacing the tab in Vector 2022
112        // with an icon (which is not good).
113        // @todo - find the ideal check for this addition, because it's
114        // probably not exactly this.
115        if ( version_compare( MW_VERSION, '1.44', '>=' ) ) {
116            $form_edit_tab['icon'] = 'edit';
117        }
118
119        // Find the location of the 'edit' tab, and add 'edit
120        // with form' right before it.
121        // This is a "key-safe" splice - it preserves both the keys
122        // and the values of the array, by editing them separately
123        // and then rebuilding the array. Based on the example at
124        // http://us2.php.net/manual/en/function.array-splice.php#31234
125        $tab_keys = array_keys( $content_actions );
126        $tab_values = array_values( $content_actions );
127        $edit_tab_location = array_search( 'edit', $tab_keys );
128
129        // If there's no 'edit' tab, look for the 'view source' tab
130        // instead.
131        if ( $edit_tab_location == null ) {
132            $edit_tab_location = array_search( 'viewsource', $tab_keys );
133        }
134
135        // This should rarely happen, but if there was no edit *or*
136        // view source tab, set the location index to -1, so the
137        // tab shows up near the end.
138        if ( $edit_tab_location == null ) {
139            $edit_tab_location = -1;
140        }
141        array_splice( $tab_keys, $edit_tab_location, 0, 'formedit' );
142        array_splice( $tab_values, $edit_tab_location, 0, [ $form_edit_tab ] );
143        $content_actions = [];
144        foreach ( $tab_keys as $i => $key ) {
145            $content_actions[$key] = $tab_values[$i];
146        }
147
148        if ( !$obj->getUser()->isAllowed( 'viewedittab' ) ) {
149            // The tab can have either of these two actions.
150            unset( $content_actions['edit'] );
151            unset( $content_actions['viewsource'] );
152        }
153    }
154
155    static function displayFormChooser( $output, $title ) {
156        global $wgPageFormsMainFormsMinimum;
157
158        $output->addModules( 'ext.pageforms.main.styles' );
159
160        $targetName = $title->getPrefixedText();
161        $output->setPageTitle( wfMessage( "creating", $targetName )->text() );
162
163        try {
164            $formNames = PFUtils::getAllForms();
165        } catch ( MWException $e ) {
166            $output->addHTML( Html::element( 'div', [ 'class' => 'error' ], $e->getMessage() ) );
167            return;
168        }
169
170        $output->addHTML( Html::element( 'p', [], wfMessage( 'pf-formedit-selectform' )->text() ) );
171        $pagesPerForm = self::getNumPagesPerForm();
172        $totalPages = 0;
173        foreach ( $pagesPerForm as $formName => $numPages ) {
174            $totalPages += $numPages;
175        }
176        // We define "popular forms" as those that are used to
177        // edit more than the specified amount of the wiki's
178        // form-editable pages. (Set by $wgPageFormsMainFormsMinimum,
179        // which by default is 1%.)
180        $popularForms = [];
181        foreach ( $pagesPerForm as $formName => $numPages ) {
182            if ( $numPages > $totalPages * $wgPageFormsMainFormsMinimum ) {
183                $popularForms[] = $formName;
184            }
185        }
186        $otherForms = [];
187        foreach ( $formNames as $i => $formName ) {
188            if ( !in_array( $formName, $popularForms ) ) {
189                $otherForms[] = $formName;
190            }
191        }
192
193        $fe = PFUtils::getSpecialPage( 'FormEdit' );
194
195        if ( count( $popularForms ) > 0 ) {
196            if ( count( $otherForms ) > 0 ) {
197                $output->addHTML( Html::element(
198                    'p',
199                    [],
200                    wfMessage( 'pf-formedit-mainforms' )->text()
201                ) );
202            }
203            $text = self::printLinksToFormArray( $popularForms, $targetName, $fe );
204            $output->addHTML( Html::rawElement( 'div', [ 'class' => 'infoMessage mainForms' ], $text ) );
205        }
206
207        if ( count( $otherForms ) > 0 ) {
208            if ( count( $popularForms ) > 0 ) {
209                $output->addHTML( Html::element(
210                    'p',
211                    [],
212                    wfMessage( 'pf-formedit-otherforms' )->text()
213                ) );
214            }
215            $text = self::printLinksToFormArray( $otherForms, $targetName, $fe );
216            $output->addHTML( Html::rawElement( 'div', [ 'class' => 'infoMessage otherForms' ], $text ) );
217        }
218
219        $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
220        $linkParams = [ 'action' => 'edit', 'redlink' => true ];
221        $noFormLink = $linkRenderer->makeKnownLink( $title, wfMessage( 'pf-formedit-donotuseform' )->escaped(), [], $linkParams );
222        $output->addHTML( Html::rawElement( 'p', [], $noFormLink ) );
223    }
224
225    /**
226     * Find the number of pages on the wiki that use each form, by getting
227     * all the categories that have a #default_form call pointing to a
228     * particular form, and adding up the number of pages in each such
229     * category.
230     * This approach doesn't count #default_form calls for namespaces or
231     * individual pages, but that doesn't seem like a big deal, because,
232     * when creating a page in a namespace that has a form, this interface
233     * probably won't get called anyway; and #default_form calls for
234     * individual pages are (hopefully) pretty rare.
235     * @return int[]
236     */
237    static function getNumPagesPerForm() {
238        $dbr = PFUtils::getReadDB();
239        $res = $dbr->select(
240            [ 'category', 'page', 'page_props' ],
241            [ 'pp_value', 'SUM(cat_pages) AS total_pages' ],
242            [
243                // Keep backward compatibility with
244                // the page property name for
245                // Semantic Forms.
246                'pp_propname' => [ 'PFDefaultForm', 'SFDefaultForm' ]
247            ],
248            __METHOD__,
249            [
250                'GROUP BY' => 'pp_value',
251                'ORDER BY' => 'total_pages DESC',
252                'LIMIT' => 100
253            ],
254            [
255                'page' => [ 'JOIN', 'cat_title = page_title' ],
256                'page_props' => [ 'JOIN', 'page_id = pp_page' ]
257            ]
258        );
259
260        $pagesPerForm = [];
261        while ( $row = $res->fetchRow() ) {
262            $formName = $row['pp_value'];
263            $pagesPerForm[$formName] = $row['total_pages'];
264        }
265        return $pagesPerForm;
266    }
267
268    static function printLinksToFormArray( $formNames, $targetName, $fe ) {
269        $text = '';
270        foreach ( $formNames as $i => $formName ) {
271            if ( $i > 0 ) {
272                $text .= ' <span class="pageforms-separator">&middot;</span> ';
273            }
274
275            // Special handling for forms whose name contains a slash.
276            if ( strpos( $formName, '/' ) !== false ) {
277                $url = $fe->getPageTitle()->getLocalURL( [ 'form' => $formName, 'target' => $targetName ] );
278            } else {
279                $url = $fe->getPageTitle( "$formName/$targetName" )->getLocalURL();
280            }
281            $text .= Html::element( 'a', [ 'href' => $url ], $formName );
282        }
283        return $text;
284    }
285
286    /**
287     * The function called if we're in index.php (as opposed to one of the
288     * special pages)
289     * @param Action $action
290     * @param Article $article
291     * @return bool
292     */
293    static function displayForm( $action, $article ) {
294        $output = $action->getOutput();
295        $title = $article->getTitle();
296        $form_names = PFFormLinker::getDefaultFormsForPage( $title );
297        if ( count( $form_names ) == 0 ) {
298            // If no form is set, display an interface to let the
299            // user choose out of all the forms defined on this wiki
300            // (or none at all).
301            self::displayFormChooser( $output, $title );
302            return true;
303        }
304
305        if ( count( $form_names ) > 1 ) {
306            $warning_text = Html::warningBox( wfMessage( 'pf_formedit_morethanoneform' )->text() );
307            $output->addWikiTextAsInterface( $warning_text );
308        }
309
310        $form_name = $form_names[0];
311        $page_name = $title->getPrefixedText();
312
313        $pfFormEdit = new PFFormEdit();
314        $pfFormEdit->printForm( $form_name, $page_name );
315
316        return false;
317    }
318
319    public function doesWrites() {
320        return true;
321    }
322}