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