Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
86.08% covered (warning)
86.08%
68 / 79
80.00% covered (warning)
80.00%
4 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
PreloadedContentBuilder
86.08% covered (warning)
86.08%
68 / 79
80.00% covered (warning)
80.00%
4 / 5
26.69
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 getPreloadedContent
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
7
 getDefaultContent
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 getPreloadedContentFromParams
73.17% covered (warning)
73.17%
30 / 41
0.00% covered (danger)
0.00%
0 / 1
16.26
 transform
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace MediaWiki\EditPage;
4
5use MediaWiki\Content\Content;
6use MediaWiki\Content\IContentHandlerFactory;
7use MediaWiki\Content\Transform\ContentTransformer;
8use MediaWiki\HookContainer\HookContainer;
9use MediaWiki\HookContainer\HookRunner;
10use MediaWiki\Page\PageReference;
11use MediaWiki\Page\ProperPageIdentity;
12use MediaWiki\Page\RedirectLookup;
13use MediaWiki\Page\WikiPageFactory;
14use MediaWiki\Parser\ParserOptions;
15use MediaWiki\Permissions\Authority;
16use MediaWiki\Revision\RevisionRecord;
17use MediaWiki\SpecialPage\SpecialPageFactory;
18use MediaWiki\Title\Title;
19use Wikimedia\Assert\Assert;
20
21/**
22 * Provides the initial content of the edit box displayed in an edit form
23 * when creating a new page or a new section.
24 *
25 * Used by EditPage, and may be used by extensions providing alternative editors.
26 *
27 * @since 1.41
28 */
29class PreloadedContentBuilder {
30
31    use ParametersHelper;
32
33    private IContentHandlerFactory $contentHandlerFactory;
34    private WikiPageFactory $wikiPageFactory;
35    private RedirectLookup $redirectLookup;
36    private SpecialPageFactory $specialPageFactory;
37    private ContentTransformer $contentTransformer;
38    private HookRunner $hookRunner;
39
40    public function __construct(
41        IContentHandlerFactory $contentHandlerFactory,
42        WikiPageFactory $wikiPageFactory,
43        RedirectLookup $redirectLookup,
44        SpecialPageFactory $specialPageFactory,
45        ContentTransformer $contentTransformer,
46        HookContainer $hookContainer
47    ) {
48        // Services
49        $this->contentHandlerFactory = $contentHandlerFactory;
50        $this->wikiPageFactory = $wikiPageFactory;
51        $this->redirectLookup = $redirectLookup;
52        $this->specialPageFactory = $specialPageFactory;
53        $this->contentTransformer = $contentTransformer;
54        $this->hookRunner = new HookRunner( $hookContainer );
55    }
56
57    /**
58     * Get the initial content of the edit box displayed in an edit form
59     * when creating a new page or a new section.
60     *
61     * @param ProperPageIdentity $page
62     * @param Authority $performer
63     * @param string|null $preload
64     * @param string[] $preloadParams
65     * @param string|null $section
66     * @return Content
67     */
68    public function getPreloadedContent(
69        ProperPageIdentity $page,
70        Authority $performer,
71        ?string $preload,
72        array $preloadParams,
73        ?string $section
74    ): Content {
75        Assert::parameterElementType( 'string', $preloadParams, '$preloadParams' );
76
77        $content = null;
78        if ( $section !== 'new' ) {
79            $content = $this->getDefaultContent( $page );
80        }
81        if ( $content === null ) {
82            if ( ( $preload === null || $preload === '' ) && $section === 'new' ) {
83                // Custom preload text for new sections
84                $preload = 'MediaWiki:addsection-preload';
85            }
86            $content = $this->getPreloadedContentFromParams( $page, $performer, $preload, $preloadParams );
87        }
88        $title = Title::newFromPageIdentity( $page );
89        if ( !$title->getArticleID() ) {
90            $contentModel = $title->getContentModel();
91            $contentHandler = $this->contentHandlerFactory->getContentHandler( $contentModel );
92            $contentFormat = $contentHandler->getDefaultFormat();
93            $text = $contentHandler->serializeContent( $content, $contentFormat );
94            $this->hookRunner->onEditFormPreloadText( $text, $title );
95            $content = $contentHandler->unserializeContent( $text, $contentFormat );
96        }
97        return $content;
98    }
99
100    /**
101     * Get the content that is displayed when viewing a page that does not exist.
102     * Users should be discouraged from saving the page with identical content to this.
103     *
104     * Some code may depend on the fact that this is only non-null for the 'MediaWiki:' namespace.
105     * Beware.
106     *
107     * @param ProperPageIdentity $page
108     * @return Content|null
109     */
110    public function getDefaultContent( ProperPageIdentity $page ): ?Content {
111        $title = Title::newFromPageIdentity( $page );
112        $contentModel = $title->getContentModel();
113        $contentHandler = $this->contentHandlerFactory->getContentHandler( $contentModel );
114        $contentFormat = $contentHandler->getDefaultFormat();
115        if ( $title->getNamespace() === NS_MEDIAWIKI ) {
116            // If this is a system message, get the default text.
117            $text = $title->getDefaultMessageText();
118            if ( $text !== false ) {
119                return $contentHandler->unserializeContent( $text, $contentFormat );
120            }
121        }
122        return null;
123    }
124
125    /**
126     * Get the contents to be preloaded into the box by loading the given page.
127     *
128     * @param ProperPageIdentity $contextPage
129     * @param Authority $performer
130     * @param string|null $preload Representing the title to preload from.
131     * @param string[] $preloadParams Parameters to use (interface-message style) in the preloaded text
132     * @return Content
133     */
134    private function getPreloadedContentFromParams(
135        ProperPageIdentity $contextPage,
136        Authority $performer,
137        ?string $preload,
138        array $preloadParams
139    ): Content {
140        $contextTitle = Title::newFromPageIdentity( $contextPage );
141        $contentModel = $contextTitle->getContentModel();
142        $handler = $this->contentHandlerFactory->getContentHandler( $contentModel );
143
144        // T297725: Don't trick users into making edits to e.g. .js subpages
145        if ( !$handler->supportsPreloadContent() || $preload === null || $preload === '' ) {
146            return $handler->makeEmptyContent();
147        }
148
149        $title = Title::newFromText( $preload );
150
151        if ( $title && $title->getNamespace() == NS_MEDIAWIKI ) {
152            // When the preload source is in NS_MEDIAWIKI, get the content via wfMessage, to
153            // enable preloading from i18n messages. The message framework can work with normal
154            // pages in NS_MEDIAWIKI, so this does not restrict preloading only to i18n messages.
155            $msg = wfMessage( $title->getText() );
156
157            if ( $msg->isDisabled() ) {
158                // Message is disabled and should not be used for preloading
159                return $handler->makeEmptyContent();
160            }
161
162            return $this->transform(
163                $handler->unserializeContent( $msg
164                    ->page( $title )
165                    ->params( $preloadParams )
166                    ->inContentLanguage()
167                    ->plain()
168                ),
169                $title
170            );
171        }
172
173        // (T299544) Use SpecialMyLanguage redirect so that nonexistent translated pages can
174        // fall back to the corresponding page in a suitable language
175        $title = $this->getTargetTitleIfSpecialMyLanguage( $title );
176
177        # Check for existence to avoid getting MediaWiki:Noarticletext
178        if ( !$this->isPageExistingAndViewable( $title, $performer ) ) {
179            // TODO: somehow show a warning to the user!
180            return $handler->makeEmptyContent();
181        }
182
183        $page = $this->wikiPageFactory->newFromTitle( $title );
184        if ( $page->isRedirect() ) {
185            $redirTarget = $this->redirectLookup->getRedirectTarget( $title );
186            $redirTarget = Title::castFromLinkTarget( $redirTarget );
187            # Same as before
188            if ( !$this->isPageExistingAndViewable( $redirTarget, $performer ) ) {
189                // TODO: somehow show a warning to the user!
190                return $handler->makeEmptyContent();
191            }
192            $page = $this->wikiPageFactory->newFromTitle( $redirTarget );
193        }
194
195        $content = $page->getContent( RevisionRecord::RAW );
196
197        if ( !$content ) {
198            // TODO: somehow show a warning to the user!
199            return $handler->makeEmptyContent();
200        }
201
202        if ( $content->getModel() !== $handler->getModelID() ) {
203            $converted = $content->convert( $handler->getModelID() );
204
205            if ( !$converted ) {
206                // TODO: somehow show a warning to the user!
207                wfDebug( "Attempt to preload incompatible content: " .
208                    "can't convert " . $content->getModel() .
209                    " to " . $handler->getModelID() );
210
211                return $handler->makeEmptyContent();
212            }
213
214            $content = $converted;
215        }
216        return $this->transform( $content, $title, $preloadParams );
217    }
218
219    private function transform(
220        Content $content,
221        PageReference $title,
222        array $preloadParams = []
223    ): Content {
224        return $this->contentTransformer->preloadTransform(
225            $content,
226            $title,
227            // The preload transformations don't depend on the user anyway
228            ParserOptions::newFromAnon(),
229            $preloadParams
230        );
231    }
232}