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