Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 154
0.00% covered (danger)
0.00%
0 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
PFHooks
0.00% covered (danger)
0.00%
0 / 154
0.00% covered (danger)
0.00%
0 / 14
1640
0.00% covered (danger)
0.00%
0 / 1
 registerExtension
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 initialize
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 registerModules
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 registerNamespaces
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 registerFunctions
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
2
 setGlobalJSVariables
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
6
 registerPageSchemasClass
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 addToAdminLinks
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
12
 addToCargoTablesColumns
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 addToCargoTablesLinks
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
110
 addToCargoTablesRow
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 disableTinyMCE
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 showFormPreview
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
20
 setPostEditCookie
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/**
3 * Static functions called by various outside hooks, as well as by
4 * extension.json.
5 *
6 * @author Yaron Koren
7 * @file
8 * @ingroup PF
9 */
10
11use MediaWiki\MediaWikiServices;
12use MediaWiki\ResourceLoader\ResourceLoader;
13
14class PFHooks {
15
16    /**
17     * Used for caching by addToCargoTablesLinks().
18     */
19    private static $mMultiPageEditPage = null;
20
21    public static function registerExtension() {
22        if ( defined( 'PF_VERSION' ) ) {
23            // Do not load Page Forms more than once.
24            return 1;
25        }
26
27        define( 'PF_VERSION', '5.7.2' );
28
29        $GLOBALS['wgPageFormsIP'] = dirname( __DIR__ ) . '/../';
30
31        /**
32         * This is a delayed init that makes sure that MediaWiki is set
33         * up properly before we add our stuff.
34         */
35
36        if ( defined( 'SMW_VERSION' ) || ExtensionRegistry::getInstance()->isLoaded( 'SemanticMediaWiki' ) ) {
37            $GLOBALS['wgSpecialPages']['CreateProperty'] = 'PFCreateProperty';
38            $GLOBALS['wgAutoloadClasses']['PFCreateProperty'] = __DIR__ . '/../specials/PF_CreateProperty.php';
39            $GLOBALS['smwgEnabledSpecialPage'][] = 'RunQuery';
40        }
41
42        // Allow for popup windows for file upload
43        $GLOBALS['wgEditPageFrameOptions'] = 'SAMEORIGIN';
44    }
45
46    public static function initialize() {
47        $GLOBALS['wgPageFormsScriptPath'] = $GLOBALS['wgExtensionAssetsPath'] . '/PageForms';
48
49        // This global variable is needed so that other
50        // extensions can hook into it to add their own
51        // input types.
52        // @phan-suppress-next-line PhanUndeclaredFunctionInCallable
53        $GLOBALS['wgPageFormsFormPrinter'] = new StubObject( 'wgPageFormsFormPrinter', 'PFFormPrinter' );
54    }
55
56    /**
57     * Called by ResourceLoaderRegisterModules hook.
58     *
59     * @see https://www.mediawiki.org/wiki/Manual:Hooks/ResourceLoaderRegisterModules
60     *
61     * @param ResourceLoader $resourceLoader The ResourceLoader object
62     */
63    public static function registerModules( ResourceLoader $resourceLoader ) {
64        // These used to use a value of __DIR__ for 'localBasePath',
65        // but apparently in some installations that had a value of
66        // /PageForms/libs and in others just /PageForms, so we'll set
67        // the value here instead.
68        $pageFormsDir = __DIR__ . '/..';
69
70        $mapsModuleAttrs = [
71            'localBasePath' => $pageFormsDir,
72            'remoteExtPath' => 'PageForms',
73            'dependencies' => [ 'oojs-ui.styles.icons-location' ]
74        ];
75
76        if ( ExtensionRegistry::getInstance()->isLoaded( 'OpenLayers' ) ) {
77            $mapsModuleAttrs['scripts'] = '/libs/PF_maps.offline.js';
78            $mapsModuleAttrs['dependencies'][] = 'ext.openlayers.main';
79        } else {
80            $mapsModuleAttrs['scripts'] = '/libs/PF_maps.js';
81        }
82
83        $resourceLoader->register( [ 'ext.pageforms.maps' => $mapsModuleAttrs ] );
84    }
85
86    /**
87     * Register the namespaces for Page Forms.
88     * @see https://www.mediawiki.org/wiki/Manual:Hooks/CanonicalNamespaces
89     *
90     * @since 2.4.1
91     *
92     * @param array &$list
93     */
94    public static function registerNamespaces( array &$list ) {
95        global $wgNamespacesWithSubpages;
96
97        if ( !defined( 'PF_NS_FORM' ) ) {
98            define( 'PF_NS_FORM', 106 );
99            define( 'PF_NS_FORM_TALK', 107 );
100        }
101
102        $list[PF_NS_FORM] = 'Form';
103        $list[PF_NS_FORM_TALK] = 'Form_talk';
104
105        // Support subpages only for talk pages by default
106        $wgNamespacesWithSubpages[PF_NS_FORM_TALK] = true;
107    }
108
109    static function registerFunctions( Parser $parser ) {
110        $parser->setFunctionHook( 'default_form', [ 'PFDefaultForm', 'run' ] );
111        $parser->setFunctionHook( 'forminput', [ 'PFFormInputParserFunction', 'run' ] );
112        $parser->setFunctionHook( 'formlink', [ 'PFFormLink', 'run' ] );
113        $parser->setFunctionHook( 'formredlink', [ 'PFFormRedLink', 'run' ] );
114        $parser->setFunctionHook( 'queryformlink', [ 'PFQueryFormLink', 'run' ] );
115        $parser->setFunctionHook( 'arraymap', [ 'PFArrayMap', 'run' ], Parser::SFH_OBJECT_ARGS );
116        $parser->setFunctionHook( 'arraymaptemplate', [ 'PFArrayMapTemplate', 'run' ], Parser::SFH_OBJECT_ARGS );
117
118        $parser->setFunctionHook( 'autoedit', [ 'PFAutoEdit', 'run' ] );
119        $parser->setFunctionHook( 'autoedit_rating', [ 'PFAutoEditRating', 'run' ] );
120        $parser->setFunctionHook( 'template_params', [ 'PFTemplateParams', 'run' ] );
121        $parser->setFunctionHook( 'template_display', [ 'PFTemplateDisplay', 'run' ], Parser::SFH_OBJECT_ARGS );
122    }
123
124    static function setGlobalJSVariables( &$vars ) {
125        global $wgPageFormsTargetName;
126        global $wgPageFormsAutocompleteValues, $wgPageFormsAutocompleteOnAllChars;
127        global $wgPageFormsFieldProperties, $wgPageFormsCargoFields, $wgPageFormsDependentFields;
128        global $wgPageFormsGridValues, $wgPageFormsGridParams;
129        global $wgPageFormsCalendarValues, $wgPageFormsCalendarParams, $wgPageFormsCalendarHTML;
130        global $wgPageFormsContLangYes, $wgPageFormsContLangNo, $wgPageFormsContLangMonths;
131        global $wgPageFormsHeightForMinimizingInstances;
132        global $wgPageFormsShowOnSelect, $wgPageFormsScriptPath;
133        global $edgValues, $wgPageFormsEDSettings;
134        global $wgAmericanDates;
135
136        $vars['wgPageFormsTargetName'] = $wgPageFormsTargetName;
137        $vars['wgPageFormsAutocompleteValues'] = $wgPageFormsAutocompleteValues;
138        $vars['wgPageFormsAutocompleteOnAllChars'] = $wgPageFormsAutocompleteOnAllChars;
139        $vars['wgPageFormsFieldProperties'] = $wgPageFormsFieldProperties;
140        $vars['wgPageFormsCargoFields'] = $wgPageFormsCargoFields;
141        $vars['wgPageFormsDependentFields'] = $wgPageFormsDependentFields;
142        $vars['wgPageFormsCalendarValues'] = $wgPageFormsCalendarValues;
143        $vars['wgPageFormsCalendarParams'] = $wgPageFormsCalendarParams;
144        $vars['wgPageFormsCalendarHTML'] = $wgPageFormsCalendarHTML;
145        $vars['wgPageFormsGridValues'] = $wgPageFormsGridValues;
146        $vars['wgPageFormsGridParams'] = $wgPageFormsGridParams;
147        $vars['wgPageFormsContLangYes'] = $wgPageFormsContLangYes;
148        $vars['wgPageFormsContLangNo'] = $wgPageFormsContLangNo;
149        $vars['wgPageFormsContLangMonths'] = $wgPageFormsContLangMonths;
150        $vars['wgPageFormsHeightForMinimizingInstances'] = $wgPageFormsHeightForMinimizingInstances;
151        $vars['wgPageFormsShowOnSelect'] = $wgPageFormsShowOnSelect;
152        $vars['wgPageFormsScriptPath'] = $wgPageFormsScriptPath;
153        if ( method_exists( 'EDParserFunctions', 'getAllValues' ) ) {
154            // External Data 2.3+
155            $vars['edgValues'] = EDParserFunctions::getAllValues();
156        } else {
157            $vars['edgValues'] = $edgValues;
158        }
159        $vars['wgPageFormsEDSettings'] = $wgPageFormsEDSettings;
160        $vars['wgAmericanDates'] = $wgAmericanDates;
161    }
162
163    public static function registerPageSchemasClass() {
164        global $wgPageSchemasHandlerClasses;
165        $wgPageSchemasHandlerClasses[] = 'PFPageSchemas';
166        return true;
167    }
168
169    public static function addToAdminLinks( &$admin_links_tree ) {
170        $data_structure_label = wfMessage( 'pf-adminlinks-datastructure' )->escaped();
171        $data_structure_section = $admin_links_tree->getSection( $data_structure_label );
172        if ( $data_structure_section === null ) {
173            $data_structure_section = new ALSection( wfMessage( 'pf-adminlinks-datastructure' )->escaped() );
174        }
175
176        $pf_row = new ALRow( 'pageforms' );
177        $pf_row->addItem( ALItem::newFromSpecialPage( 'Categories' ) );
178        $data_structure_section->addRow( $pf_row );
179        $pf_admin_row = new ALRow( 'pageforms_admin' );
180        $data_structure_section->addRow( $pf_admin_row );
181
182        $admin_links_tree->addSection( $data_structure_section, wfMessage( 'adminlinks_browsesearch' )->escaped() );
183
184        $pf_row->addItem( ALItem::newFromSpecialPage( 'Templates' ), 'Properties' );
185        $pf_row->addItem( ALItem::newFromSpecialPage( 'Forms' ), 'SemanticStatistics' );
186        $pf_row->addItem( ALItem::newFromSpecialPage( 'MultiPageEdit' ) );
187        $pf_admin_row->addItem( ALItem::newFromSpecialPage( 'CreateClass' ), 'SMWAdmin' );
188        if ( class_exists( 'PFCreateProperty' ) ) {
189            $pf_admin_row->addItem( ALItem::newFromSpecialPage( 'CreateProperty' ), 'SMWAdmin' );
190        }
191        $pf_admin_row->addItem( ALItem::newFromSpecialPage( 'CreateTemplate' ), 'SMWAdmin' );
192        $pf_admin_row->addItem( ALItem::newFromSpecialPage( 'CreateForm' ), 'SMWAdmin' );
193        $pf_admin_row->addItem( ALItem::newFromSpecialPage( 'CreateCategory' ), 'SMWAdmin' );
194    }
195
196    public static function addToCargoTablesColumns( $cargoTablesPage, &$allowedActions ) {
197        if ( !$cargoTablesPage->getUser()->isAllowed( 'multipageedit' ) ) {
198            return;
199        }
200
201        $cargoTablesPage->getOutput()->addModuleStyles( [ 'oojs-ui.styles.icons-editing-core' ] );
202
203        $editColumn = [ 'edit' => [ 'ooui-icon' => 'edit', 'ooui-title' => 'edit' ] ];
204        $indexOfDrilldown = array_search( 'drilldown', array_keys( $allowedActions ) );
205        $pos = $indexOfDrilldown === false ? count( $allowedActions ) : $indexOfDrilldown + 1;
206        $allowedActions = array_merge( array_slice( $allowedActions, 0, $pos ), $editColumn, array_slice( $allowedActions, $pos ) );
207    }
208
209    /**
210     * Called by the CargoTablesActionLinks hook.
211     *
212     * Adds an "Edit" link to Special:CargoTables, pointing to Special:MultiPageEdit.
213     *
214     * @param array &$actionLinks Action links
215     * @param string $tableName Cargo table name
216     * @param bool $isReplacementTable Whether this table is a replacement table
217     * @param bool $hasReplacementTable Whether this table has a replacement table
218     * @param int[][] $templatesThatDeclareTables
219     * @param string[] $templatesThatAttachToTables
220     * @param User|null $user The current user
221     *
222     * @since 4.4
223     */
224    public static function addToCargoTablesLinks( &$actionLinks, $tableName, $isReplacementTable, $hasReplacementTable, $templatesThatDeclareTables, $templatesThatAttachToTables, $user = null ) {
225        // If it has a "replacement table", it's read-only and can't
226        // be edited (though the replacement table can).
227        if ( $hasReplacementTable ) {
228            return;
229        }
230
231        // Check permissions.
232        if ( $user == null ) {
233            // For Cargo versions < 3.1.
234            $user = RequestContext::getMain()->getUser();
235        }
236
237        if ( !$user->isAllowed( 'multipageedit' ) ) {
238            return;
239        }
240        // Only put in an "Edit" link if there's exactly one template
241        // for this Cargo table, and one form for that template.
242        if ( !array_key_exists( $tableName, $templatesThatDeclareTables ) ) {
243            return;
244        }
245        if ( array_key_exists( $tableName, $templatesThatAttachToTables ) ) {
246            return;
247        }
248        $templateIDs = $templatesThatDeclareTables[$tableName];
249        if ( count( $templateIDs ) > 1 ) {
250            return;
251        }
252
253        $templateTitle = Title::newFromID( $templateIDs[0] );
254        $templateName = $templateTitle->getText();
255        if ( self::$mMultiPageEditPage == null ) {
256            self::$mMultiPageEditPage = new PFMultiPageEdit();
257            self::$mMultiPageEditPage->setTemplateList();
258        }
259        $formName = self::$mMultiPageEditPage->getFormForTemplate( $templateName );
260        if ( $formName == null ) {
261            return;
262        }
263
264        $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
265        $sp = PFUtils::getSpecialPage( 'MultiPageEdit' );
266        $editMsg = wfMessage( 'edit' )->text();
267        $linkParams = [ 'template' => $templateName, 'form' => $formName ];
268        $text = $linkRenderer->makeKnownLink( $sp->getPageTitle(), $editMsg, [], $linkParams );
269
270        $indexOfDrilldown = array_search( 'drilldown', array_keys( $actionLinks ) );
271        $pos = $indexOfDrilldown === false ? count( $actionLinks ) : $indexOfDrilldown + 1;
272        $actionLinks = array_merge( array_slice( $actionLinks, 0, $pos ), [ 'edit' => $text ], array_slice( $actionLinks, $pos ) );
273    }
274
275    /**
276     * Called by the CargoTablesSetActionLinks hook.
277     *
278     * Adds an "Edit" link to Special:CargoTables, pointing to Special:MultiPageEdit.
279     *
280     * @param SpecialPage $cargoTablesPage
281     * @param array &$actionLinks Action links
282     * @param string $tableName Cargo table name
283     * @param bool $isReplacementTable Whether this table iss a replacement table
284     * @param bool $hasReplacementTable Whether this table has a replacement table
285     * @param int[][] $templatesThatDeclareTables
286     * @param string[] $templatesThatAttachToTables
287     * @param string[] $actionList
288     * @param User|null $user The current user
289     *
290     * @since 4.8.1
291     */
292    public static function addToCargoTablesRow( $cargoTablesPage, &$actionLinks, $tableName, $isReplacementTable, $hasReplacementTable, $templatesThatDeclareTables, $templatesThatAttachToTables, $actionList, $user = null ) {
293        $cargoTablesPage->getOutput()->addModuleStyles( [ 'oojs-ui.styles.icons-editing-core' ] );
294
295        // For the sake of simplicity, this function basically just
296        // wraps around the previous hook function, for Cargo <= 2.4.
297        // That's why there's this awkward behavior of parsing links
298        // to get their URL. Hopefully this won't cause problems.
299        self::addToCargoTablesLinks( $actionLinks, $tableName, $isReplacementTable, $hasReplacementTable, $templatesThatDeclareTables, $templatesThatAttachToTables, $user );
300
301        if ( array_key_exists( 'edit', $actionLinks ) ) {
302            preg_match( '/href="(.*?)"/', $actionLinks['edit'], $matches );
303            $mpeURL = html_entity_decode( $matches[1] );
304            $actionLinks['edit'] = $cargoTablesPage->getActionButton( 'edit', $mpeURL );
305        }
306    }
307
308    /**
309     * Disable TinyMCE if this is a form definition page, or a form-editable page.
310     *
311     * @param Title $title The page Title object
312     * @return bool Whether or not to disable TinyMCE
313     */
314    public static function disableTinyMCE( $title ) {
315        if ( $title->getNamespace() == PF_NS_FORM ) {
316            return false;
317        }
318
319        $defaultForms = PFFormLinker::getDefaultFormsForPage( $title );
320        if ( count( $defaultForms ) > 0 ) {
321            return false;
322        }
323
324        return true;
325    }
326
327    public static function showFormPreview( EditPage $editpage, WebRequest $request ) {
328        global $wgOut, $wgPageFormsFormPrinter;
329
330        wfDebug( __METHOD__ . ": enter.\n" );
331
332        // Exit if we're not in preview mode.
333        if ( !$editpage->preview ) {
334            return;
335        }
336        // Exit if we aren't in the "Form" namespace.
337        if ( $editpage->getArticle()->getTitle()->getNamespace() != PF_NS_FORM ) {
338            return;
339        }
340
341        // Needed in case there are any OOUI-based input types in the form.
342        $wgOut->enableOOUI();
343
344        $previewNote = $wgOut->parseAsInterface( wfMessage( 'pf-preview-note' )->text() );
345        // The "pfForm" ID is there so the form JS will be activated.
346        $editpage->previewTextAfterContent .= Html::element( 'h2', null, wfMessage( 'pf-preview-header' )->text() ) . "\n" .
347            '<div id="pfForm" class="previewnote" style="font-weight: bold">' . $previewNote . "</div>\n<hr />\n";
348
349        $form_definition = StringUtils::delimiterReplace( '<noinclude>', '</noinclude>', '', $editpage->textbox1 );
350        list( $form_text, $data_text, $form_page_title, $generated_page_name ) =
351            $wgPageFormsFormPrinter->formHTML( $form_definition, null, false, null, null, "Page Forms form preview dummy title", null );
352
353        $parserOutput = PFUtils::getParser()->getOutput();
354        if ( method_exists( $wgOut, 'addParserOutputMetadata' ) ) {
355            $wgOut->addParserOutputMetadata( $parserOutput );
356        } else {
357            $wgOut->addParserOutputNoText( $parserOutput );
358        }
359
360        PFUtils::addFormRLModules();
361        $editpage->previewTextAfterContent .=
362            '<div style="margin-top: 15px">' . $form_text . "</div>";
363    }
364
365    /**
366     * Called by the PageSaveComplete hook.
367     *
368     * Set a cookie after the page save so that a "Your edit was saved"
369     * popup will appear after form-based saves, just as it does after
370     * standard saves. This code will be called after all saves, which
371     * means that it will lead to redundant cookie-setting after normal
372     * saves. However, there doesn't appear to be a way to to set the
373     * cookie correctly only after form-based saves, unfortunately.
374     *
375     * @param WikiPage $wikiPage
376     * @param MediaWiki\User\UserIdentity $user
377     * @param string $summary
378     * @param int $flags
379     * @param MediaWiki\Revision\RevisionRecord $revisionRecord
380     * @param MediaWiki\Storage\EditResult $editResult
381     */
382    public static function setPostEditCookie( WikiPage $wikiPage, MediaWiki\User\UserIdentity $user, string $summary, int $flags,
383        MediaWiki\Revision\RevisionRecord $revisionRecord, MediaWiki\Storage\EditResult $editResult
384    ) {
385        // Have this take effect only if the save came from a form -
386        // we need to use a global variable to determine that.
387        global $wgPageFormsFormPrinter;
388        if ( !property_exists( $wgPageFormsFormPrinter, 'mInputTypeHooks' ) ) {
389            return;
390        }
391
392        // Code based loosely on EditPage::setPostEditCookie().
393        $postEditKey = EditPage::POST_EDIT_COOKIE_KEY_PREFIX . $revisionRecord->getID();
394        $response = RequestContext::getMain()->getRequest()->response();
395        $response->setCookie( $postEditKey, 'saved', time() + EditPage::POST_EDIT_COOKIE_DURATION );
396    }
397
398}