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