Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.05% covered (success)
94.05%
79 / 84
80.00% covered (warning)
80.00%
4 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
PFFormLinker
94.05% covered (success)
94.05%
79 / 84
80.00% covered (warning)
80.00%
4 / 5
28.17
0.00% covered (danger)
0.00%
0 / 1
 getDefaultForm
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 createPageWithForm
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
3
 setBrokenLink
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
6
 getDefaultFormsForPage
82.76% covered (warning)
82.76%
24 / 29
0.00% covered (danger)
0.00%
0 / 1
13.87
 getDefaultFormForNamespace
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2
3use MediaWiki\Linker\LinkRenderer;
4use MediaWiki\MediaWikiServices;
5use MediaWiki\Title\Title;
6
7/**
8 * Gets the form(s) used to edit a page, both for existing pages and for
9 * not-yet-created, red-linked pages.
10 *
11 * @author Yaron Koren
12 * @file
13 * @ingroup PF
14 */
15
16class PFFormLinker {
17
18    private static $formPerNamespace = [];
19
20    static function getDefaultForm( ?Title $title ): ?string {
21        // The title passed in can be null in at least one
22        // situation: if the "namespace page" is being checked, and
23        // the project namespace alias contains any non-ASCII
24        // characters. There may be other cases too.
25        // If that happens, just exit.
26        if ( $title === null ) {
27            return null;
28        }
29
30        $props = MediaWikiServices::getInstance()->getPageProps()
31            ->getProperties( $title, [ 'PFDefaultForm', 'SFDefaultForm' ] );
32        $pageID = $title->getArticleID();
33
34        // Keep backward compatibility with the page property name for Semantic Forms.
35        return $props[$pageID]['PFDefaultForm'] ?? $props[$pageID]['SFDefaultForm'] ?? null;
36    }
37
38    public static function createPageWithForm( $title, $formName, $inQueryArr ) {
39        /** @var PFFormPrinter $wgPageFormsFormPrinter */
40        global $wgPageFormsFormPrinter, $wgOut;
41
42        $wgOut->enableOOUI();
43
44        $formTitle = Title::makeTitleSafe( PF_NS_FORM, $formName );
45        $formDefinition = PFUtils::getPageText( $formTitle );
46        $preloadContent = null;
47
48        // Allow outside code to set/change the preloaded text.
49        MediaWikiServices::getInstance()->getHookContainer()->run( 'PageForms::EditFormPreloadText', [ &$preloadContent, $title, $formTitle ] );
50
51        [ $formText, $pageText, $formPageTitle, $generatedPageName ] =
52            $wgPageFormsFormPrinter->formHTML(
53                $formDefinition, false, false, null, $preloadContent,
54                'Some very long page name that will hopefully never get created ABCDEF123',
55                null, PFFormPrinter::CONTEXT_AUTOCREATE, $inQueryArr
56            );
57        $params = [];
58
59        // Get user "responsible" for all auto-generated
60        // pages from red links.
61        $userID = 1;
62        global $wgPageFormsAutoCreateUser;
63        if ( $wgPageFormsAutoCreateUser !== null ) {
64            $user = MediaWikiServices::getInstance()->getUserFactory()->newFromName( $wgPageFormsAutoCreateUser );
65            if ( $user !== null ) {
66                $userID = $user->getId();
67            }
68        }
69        $params['user_id'] = $userID;
70        $params['page_text'] = $pageText;
71        $job = new PFCreatePageJob( $title, $params );
72        MediaWikiServices::getInstance()->getJobQueueGroup()->push( $job );
73    }
74
75    /**
76     * Called by the HtmlPageLinkRendererEnd hook.
77     * The $target argument is listed in the documentation as being of type
78     * LinkTarget, but in practice it seems to sometimes be of type Title
79     * and sometimes of type TitleValue. So we just leave out a type
80     * declaration for that argument in the header.
81     *
82     * @param LinkRenderer $linkRenderer
83     * @param Title $target
84     * @param bool $isKnown
85     * @param string &$text
86     * @param array &$attribs
87     * @param bool &$ret
88     * @return true
89     */
90    static function setBrokenLink( LinkRenderer $linkRenderer, $target, $isKnown, &$text, &$attribs, &$ret ) {
91        global $wgContentNamespaces;
92        global $wgPageFormsLinkAllRedLinksToForms;
93
94        // If it's not a broken (red) link, exit.
95        if ( $isKnown ) {
96            return true;
97        }
98
99        $namespace = $target->getNamespace();
100
101        // Quick check.
102        if ( $namespace == NS_SPECIAL ) {
103            return true;
104        }
105
106        if ( self::getDefaultFormForNamespace( $namespace ) !== null ) {
107            $title = Title::newFromLinkTarget( $target );
108            $attribs['href'] = $title->getLinkURL( [ 'action' => 'formedit', 'redlink' => '1' ] );
109            return true;
110        }
111
112        // If there's no default form, keep going only if we're
113        // modifying "all" red links, and this is a link to a
114        // content namespace.
115        if ( !$wgPageFormsLinkAllRedLinksToForms ||
116        !in_array( $namespace, $wgContentNamespaces ) ) {
117            return true;
118        }
119
120        // We're still here - change the link.
121        // The class of $target can be either Title or
122        // TitleValue.
123        $title = Title::newFromLinkTarget( $target );
124        $attribs['href'] = $title->getLinkURL( [ 'action' => 'formedit', 'redlink' => '1' ] );
125
126        return true;
127    }
128
129    /**
130     * Get the form(s) used to edit this page - either:
131     * - the default form(s) for the page itself, if there are any; or
132     * - the default form(s) for a category that this article belongs to,
133     * if there are any; or
134     * - the default form(s) for the article's namespace, if there are any.
135     * @param Title $title
136     * @return array
137     */
138    static function getDefaultFormsForPage( $title ) {
139        // See if the page itself has a default form (or forms), and
140        // return it/them if so.
141        // (Disregard category pages for this check.)
142        if ( $title->getNamespace() != NS_CATEGORY ) {
143            $default_form = self::getDefaultForm( $title );
144            if ( $default_form === '' ) {
145                // A call to "{{#default_form:}}" (i.e., no form
146                // specified) should cancel any inherited forms.
147                return [];
148            } elseif ( $default_form !== null ) {
149                return [ $default_form ];
150            }
151        }
152
153        // If this is not a category page, look for a default form
154        // for its parent category or categories.
155        $namespace = $title->getNamespace();
156        if ( NS_CATEGORY !== $namespace ) {
157            $default_forms = [];
158            $categories = PFValuesUtils::getCategoriesForPage( $title );
159            foreach ( $categories as $category ) {
160                if ( class_exists( 'PSSchema' ) ) {
161                    // Check the Page Schema, if one exists.
162                    $psSchema = new PSSchema( $category );
163                    if ( $psSchema->isPSDefined() ) {
164                        $formName = PFPageSchemas::getFormName( $psSchema );
165                        if ( $formName !== null ) {
166                            $default_forms[] = $formName;
167                        }
168                    }
169                }
170                $categoryPage = Title::makeTitleSafe( NS_CATEGORY, $category );
171                $defaultFormForCategory = self::getDefaultForm( $categoryPage );
172                if ( $defaultFormForCategory != '' ) {
173                    $default_forms[] = $defaultFormForCategory;
174                }
175            }
176            if ( count( $default_forms ) > 0 ) {
177                // It is possible for two categories to have the same default form, so purge any
178                // duplicates from the array to avoid a "more than one default form" warning.
179                return array_unique( $default_forms );
180            }
181        }
182
183        // All that's left is checking for the namespace. If this is
184        // a subpage, exit out - default forms for namespaces don't
185        // apply to subpages.
186        if ( $title->isSubpage() ) {
187            return [];
188        }
189
190        $default_form = self::getDefaultFormForNamespace( $namespace );
191        if ( $default_form != '' ) {
192            return [ $default_form ];
193        }
194
195        return [];
196    }
197
198    public static function getDefaultFormForNamespace( $namespace ) {
199        if ( array_key_exists( $namespace, self::$formPerNamespace ) ) {
200            return self::$formPerNamespace[$namespace];
201        }
202
203        if ( NS_MAIN === $namespace ) {
204            // If it's in the main (blank) namespace, check for the
205            // file named with the word for "Main" in this language.
206            $namespace_label = wfMessage( 'pf_blank_namespace' )->inContentLanguage()->text();
207        } else {
208            $namespace_labels = PFUtils::getContLang()->getNamespaces();
209            if ( !array_key_exists( $namespace, $namespace_labels ) ) {
210                // This can happen if it's a custom namespace that
211                // was not entirely correctly declared.
212                self::$formPerNamespace[$namespace] = null;
213                return null;
214            }
215            $namespace_label = $namespace_labels[$namespace];
216        }
217
218        $namespacePage = Title::makeTitleSafe( NS_PROJECT, $namespace_label );
219        $defaultForm = self::getDefaultForm( $namespacePage );
220        self::$formPerNamespace[$namespace] = $defaultForm;
221        return $defaultForm;
222    }
223}