Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
78.32% covered (warning)
78.32%
242 / 309
35.29% covered (danger)
35.29%
6 / 17
CRAP
0.00% covered (danger)
0.00%
0 / 1
IntroMessageBuilder
78.32% covered (warning)
78.32%
242 / 309
35.29% covered (danger)
35.29%
6 / 17
178.28
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
1
 getLogExtract
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getIntroMessages
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
2
 addCodeEditingIntro
96.43% covered (success)
96.43%
54 / 56
0.00% covered (danger)
0.00%
0 / 1
18
 addSharedRepoHint
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
6
 addUserWarnings
92.86% covered (success)
92.86%
26 / 28
0.00% covered (danger)
0.00%
0 / 1
10.04
 addEditIntro
82.35% covered (warning)
82.35%
28 / 34
0.00% covered (danger)
0.00%
0 / 1
10.55
 addRecreateWarning
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
2
 addTalkPageText
50.00% covered (danger)
50.00%
1 / 2
0.00% covered (danger)
0.00%
0 / 1
2.50
 addEditNotices
85.71% covered (warning)
85.71%
12 / 14
0.00% covered (danger)
0.00%
0 / 1
5.07
 addOldRevisionWarning
50.00% covered (danger)
50.00%
1 / 2
0.00% covered (danger)
0.00%
0 / 1
4.12
 addReadOnlyWarning
20.00% covered (danger)
20.00%
1 / 5
0.00% covered (danger)
0.00%
0 / 1
4.05
 addAnonEditWarning
78.26% covered (warning)
78.26%
18 / 23
0.00% covered (danger)
0.00%
0 / 1
5.26
 isWrongCaseUserConfigPage
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
4.02
 addUserConfigPageInfo
53.85% covered (warning)
53.85%
14 / 26
0.00% covered (danger)
0.00%
0 / 1
19.83
 addPageProtectionWarningHeaders
16.22% covered (danger)
16.22%
6 / 37
0.00% covered (danger)
0.00%
0 / 1
68.81
 addHeaderCopyrightWarning
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace MediaWiki\EditPage;
4
5use LogicException;
6use MediaWiki\Block\DatabaseBlockStore;
7use MediaWiki\Config\Config;
8use MediaWiki\FileRepo\RepoGroup;
9use MediaWiki\Html\Html;
10use MediaWiki\Language\RawMessage;
11use MediaWiki\Linker\LinkRenderer;
12use MediaWiki\Logging\LogEventsList;
13use MediaWiki\MainConfigNames;
14use MediaWiki\Page\ProperPageIdentity;
15use MediaWiki\Permissions\Authority;
16use MediaWiki\Permissions\PermissionManager;
17use MediaWiki\Permissions\RestrictionStore;
18use MediaWiki\Revision\RevisionRecord;
19use MediaWiki\Skin\Skin;
20use MediaWiki\Skin\SkinFactory;
21use MediaWiki\SpecialPage\SpecialPage;
22use MediaWiki\SpecialPage\SpecialPageFactory;
23use MediaWiki\Title\NamespaceInfo;
24use MediaWiki\Title\Title;
25use MediaWiki\User\TempUser\TempUserCreator;
26use MediaWiki\User\UserFactory;
27use MediaWiki\User\UserNameUtils;
28use MediaWiki\User\UserRigorOptions;
29use MediaWiki\Utils\UrlUtils;
30use MessageLocalizer;
31use Wikimedia\Rdbms\IConnectionProvider;
32use Wikimedia\Rdbms\ReadOnlyMode;
33
34/**
35 * Provides the intro messages (edit notices and others) to be displayed before an edit form.
36 *
37 * Used by EditPage, and may be used by extensions providing alternative editors.
38 *
39 * @since 1.41
40 */
41class IntroMessageBuilder {
42
43    use ParametersHelper;
44
45    // Parameters for getIntroMessages()
46    public const MORE_FRAMES = 1;
47    public const LESS_FRAMES = 2;
48
49    private Config $config;
50    private LinkRenderer $linkRenderer;
51    private PermissionManager $permManager;
52    private UserNameUtils $userNameUtils;
53    private TempUserCreator $tempUserCreator;
54    private UserFactory $userFactory;
55    private RestrictionStore $restrictionStore;
56    private DatabaseBlockStore $blockStore;
57    private ReadOnlyMode $readOnlyMode;
58    private SpecialPageFactory $specialPageFactory;
59    private RepoGroup $repoGroup;
60    private NamespaceInfo $namespaceInfo;
61    private SkinFactory $skinFactory;
62    private IConnectionProvider $dbProvider;
63    private UrlUtils $urlUtils;
64
65    public function __construct(
66        Config $config,
67        LinkRenderer $linkRenderer,
68        PermissionManager $permManager,
69        UserNameUtils $userNameUtils,
70        TempUserCreator $tempUserCreator,
71        UserFactory $userFactory,
72        RestrictionStore $restrictionStore,
73        DatabaseBlockStore $blockStore,
74        ReadOnlyMode $readOnlyMode,
75        SpecialPageFactory $specialPageFactory,
76        RepoGroup $repoGroup,
77        NamespaceInfo $namespaceInfo,
78        SkinFactory $skinFactory,
79        IConnectionProvider $dbProvider,
80        UrlUtils $urlUtils
81    ) {
82        $this->config = $config;
83        $this->linkRenderer = $linkRenderer;
84        $this->permManager = $permManager;
85        $this->userNameUtils = $userNameUtils;
86        $this->tempUserCreator = $tempUserCreator;
87        $this->userFactory = $userFactory;
88        $this->restrictionStore = $restrictionStore;
89        $this->blockStore = $blockStore;
90        $this->readOnlyMode = $readOnlyMode;
91        $this->specialPageFactory = $specialPageFactory;
92        $this->repoGroup = $repoGroup;
93        $this->namespaceInfo = $namespaceInfo;
94        $this->skinFactory = $skinFactory;
95        $this->dbProvider = $dbProvider;
96        $this->urlUtils = $urlUtils;
97    }
98
99    /**
100     * Wrapper for LogEventsList::showLogExtract() that returns the string with the output.
101     *
102     * LogEventsList::showLogExtract() has some side effects affecting the global state (main request
103     * context), which should not be relied upon.
104     *
105     * @param string|array $types See LogEventsList::showLogExtract()
106     * @param string $page See LogEventsList::showLogExtract()
107     * @param string $user See LogEventsList::showLogExtract()
108     * @param array $param See LogEventsList::showLogExtract()
109     * @return string
110     */
111    private function getLogExtract( $types = [], $page = '', $user = '', $param = [] ): string {
112        $outString = '';
113        LogEventsList::showLogExtract( $outString, $types, $page, $user, $param );
114        return $outString;
115    }
116
117    /**
118     * Return intro messages to be shown before an edit form.
119     *
120     * The message identifiers used as array keys are stable. Callers of this method may recognize
121     * specific messages and omit them when displaying, if they're not applicable to some interface or
122     * if they provide the same information in an alternative way.
123     *
124     * Callers should load the 'mediawiki.interface.helpers.styles' ResourceLoader module, as some of
125     * the possible messages rely on those styles.
126     *
127     * @param int $frames Some intro messages come with optional wrapper frames.
128     *   Pass IntroMessageBuilder::MORE_FRAMES to include the frames whenever possible,
129     *   or IntroMessageBuilder::LESS_FRAMES to omit them whenever possible.
130     * @param string[] $skip Identifiers of messages not to generate
131     * @param MessageLocalizer $localizer
132     * @param ProperPageIdentity $page Page being viewed
133     * @param RevisionRecord|null $revRecord Revision being viewed, null if page doesn't exist
134     * @param Authority $performer
135     * @param string|null $editIntro
136     * @param string|null $returnToQuery
137     * @param bool $preview
138     * @param string|null $section
139     * @return array<string,string> Ordered map of identifiers to message HTML
140     */
141    public function getIntroMessages(
142        int $frames,
143        array $skip,
144        MessageLocalizer $localizer,
145        ProperPageIdentity $page,
146        ?RevisionRecord $revRecord,
147        Authority $performer,
148        ?string $editIntro,
149        ?string $returnToQuery,
150        bool $preview,
151        ?string $section = null
152    ): array {
153        $title = Title::newFromPageIdentity( $page );
154        $messages = new IntroMessageList( $frames, $skip );
155
156        $this->addOldRevisionWarning( $messages, $localizer, $revRecord );
157
158        if ( !$preview ) {
159            $this->addCodeEditingIntro( $messages, $localizer, $title, $performer );
160            $this->addSharedRepoHint( $messages, $localizer, $page );
161            $this->addUserWarnings( $messages, $localizer, $title, $performer );
162            $this->addEditIntro( $messages, $localizer, $page, $performer, $editIntro, $section );
163            $this->addRecreateWarning( $messages, $localizer, $page );
164        }
165
166        $this->addTalkPageText( $messages, $localizer, $title );
167        $this->addEditNotices( $messages, $localizer, $title, $revRecord );
168
169        $this->addReadOnlyWarning( $messages, $localizer );
170        $this->addAnonEditWarning( $messages, $localizer, $title, $performer, $returnToQuery, $preview );
171        $this->addUserConfigPageInfo( $messages, $localizer, $title, $performer, $preview );
172        $this->addPageProtectionWarningHeaders( $messages, $localizer, $page );
173        $this->addHeaderCopyrightWarning( $messages, $localizer );
174
175        return $messages->getList();
176    }
177
178    /**
179     * Adds introduction to code editing.
180     */
181    private function addCodeEditingIntro(
182        IntroMessageList $messages,
183        MessageLocalizer $localizer,
184        Title $title,
185        Authority $performer
186    ): void {
187        $isUserJsConfig = $title->isUserJsConfigPage();
188        $namespace = $title->getNamespace();
189        $intro = '';
190
191        if (
192            $title->isUserConfigPage() &&
193            $title->isSubpageOf( Title::makeTitle( NS_USER, $performer->getUser()->getName() ) )
194        ) {
195            $isUserCssConfig = $title->isUserCssConfigPage();
196            $isUserJsonConfig = $title->isUserJsonConfigPage();
197            $isUserJsConfig = $title->isUserJsConfigPage();
198
199            if ( $isUserCssConfig ) {
200                $warning = 'usercssispublic';
201            } elseif ( $isUserJsonConfig ) {
202                $warning = 'userjsonispublic';
203            } else {
204                $warning = 'userjsispublic';
205            }
206
207            $warningText = $localizer->msg( $warning )->parse();
208            $intro .= $warningText ? Html::rawElement(
209                'div',
210                [ 'class' => 'mw-userconfigpublic' ],
211                $warningText
212            ) : '';
213
214        }
215        $codeMsg = $localizer->msg( 'editpage-code-message' );
216        $codeMessageText = $codeMsg->isDisabled() ? '' : $codeMsg->parseAsBlock();
217        $isJavaScript = $title->hasContentModel( CONTENT_MODEL_JAVASCRIPT );
218        $isCSS = $title->hasContentModel( CONTENT_MODEL_CSS );
219
220        if ( $namespace === NS_MEDIAWIKI ) {
221            $interfaceMsg = $localizer->msg( 'editinginterface' );
222            $interfaceMsgText = $interfaceMsg->parse();
223            # Show a warning if editing an interface message
224            $intro .= $interfaceMsgText ? Html::rawElement(
225                'div',
226                [ 'class' => 'mw-editinginterface' ],
227                $interfaceMsgText
228            ) : '';
229            # If this is a default message (but not css, json, or js),
230            # show a hint that it is translatable on translatewiki.net
231            if (
232                !$isCSS
233                && !$title->hasContentModel( CONTENT_MODEL_JSON )
234                && !$isJavaScript
235            ) {
236                $defaultMessageText = $title->getDefaultMessageText();
237                if ( $defaultMessageText !== false ) {
238                    $translateInterfaceText = $localizer->msg( 'translateinterface' )->parse();
239                    $intro .= $translateInterfaceText ? Html::rawElement(
240                        'div',
241                        [ 'class' => 'mw-translateinterface' ],
242                        $translateInterfaceText
243                    ) : '';
244                }
245            }
246        }
247
248        if ( $isUserJsConfig ) {
249            $userConfigDangerousMsg = $localizer->msg( 'userjsdangerous' )->parse();
250            $intro .= $userConfigDangerousMsg ? Html::rawElement(
251                'div',
252                [ 'class' => 'mw-userconfigdangerous' ],
253                $userConfigDangerousMsg
254            ) : '';
255        }
256
257        // If the wiki page contains JavaScript or CSS link add message specific to code.
258        if ( $isJavaScript || $isCSS ) {
259            $intro .= $codeMessageText;
260        }
261
262        $messages->addWithKey(
263            'code-editing-intro',
264            $intro,
265            // While semantically this is a warning, given the impact of editing these pages,
266            // it's best to deter users who don't understand what they are doing by
267            // acknowledging the danger here. This is a potentially destructive action
268            // so requires destructive coloring.
269            Html::errorBox( '$1' )
270        );
271    }
272
273    private function addSharedRepoHint(
274        IntroMessageList $messages,
275        MessageLocalizer $localizer,
276        ProperPageIdentity $page
277    ): void {
278        $namespace = $page->getNamespace();
279        if ( $namespace === NS_FILE ) {
280            # Show a hint to shared repo
281            $file = $this->repoGroup->findFile( $page );
282            if ( $file && !$file->isLocal() ) {
283                $descUrl = $file->getDescriptionUrl();
284                # there must be a description url to show a hint to shared repo
285                if ( $descUrl ) {
286                    if ( !$page->exists() ) {
287                        $messages->add(
288                            $localizer->msg(
289                                'sharedupload-desc-create',
290                                $file->getRepo()->getDisplayName(),
291                                $descUrl
292                            ),
293                            "<div class=\"mw-sharedupload-desc-create\">\n$1\n</div>"
294                        );
295                    } else {
296                        $messages->add(
297                            $localizer->msg(
298                                'sharedupload-desc-edit',
299                                $file->getRepo()->getDisplayName(),
300                                $descUrl
301                            ),
302                            "<div class=\"mw-sharedupload-desc-edit\">\n$1\n</div>"
303                        );
304                    }
305                }
306            }
307        }
308    }
309
310    private function addUserWarnings(
311        IntroMessageList $messages,
312        MessageLocalizer $localizer,
313        Title $title,
314        Authority $performer
315    ): void {
316        $namespace = $title->getNamespace();
317        # Show a warning message when someone creates/edits a user (talk) page but the user does not exist
318        # Show log extract when the user is currently blocked
319        if ( $namespace === NS_USER || $namespace === NS_USER_TALK ) {
320            $username = explode( '/', $title->getText(), 2 )[0];
321            // Allow IP users
322            $validation = UserRigorOptions::RIGOR_NONE;
323            $user = $this->userFactory->newFromName( $username, $validation );
324            $ip = $this->userNameUtils->isIP( $username );
325
326            $userExists = ( $user && $user->isRegistered() );
327            if ( $userExists && $user->isHidden() && !$performer->isAllowed( 'hideuser' ) ) {
328                // If the user exists, but is hidden, and the viewer cannot see hidden
329                // users, pretend like they don't exist at all. See T120883
330                $userExists = false;
331            }
332
333            if ( !$userExists && !$ip ) {
334                $messages->addWithKey(
335                    'userpage-userdoesnotexist',
336                    // This wrapper frame, for whatever reason, is not optional
337                    Html::warningBox(
338                        $localizer->msg( 'userpage-userdoesnotexist', wfEscapeWikiText( $username ) )->parse(),
339                        'mw-userpage-userdoesnotexist'
340                    )
341                );
342                return;
343            }
344
345            $blockLogBox = LogEventsList::getBlockLogWarningBox(
346                $this->blockStore,
347                $this->namespaceInfo,
348                $localizer,
349                $this->linkRenderer,
350                $user,
351                $title
352            );
353            if ( $blockLogBox !== null ) {
354                $messages->addWithKey( 'blocked-notice-logextract', $blockLogBox );
355            }
356        }
357    }
358
359    /**
360     * Try to add a custom edit intro, or use the standard one if this is not possible.
361     */
362    private function addEditIntro(
363        IntroMessageList $messages,
364        MessageLocalizer $localizer,
365        ProperPageIdentity $page,
366        Authority $performer,
367        ?string $editIntro,
368        ?string $section
369    ): void {
370        if ( ( $editIntro === null || $editIntro === '' ) && $section === 'new' ) {
371            // Custom edit intro for new sections
372            $editIntro = 'MediaWiki:addsection-editintro';
373        }
374        if ( $editIntro !== null && $editIntro !== '' ) {
375            $introTitle = Title::newFromText( $editIntro );
376
377            // (T334855) Use SpecialMyLanguage redirect so that nonexistent translated pages can
378            // fall back to the corresponding page in a suitable language
379            $introTitle = $this->getTargetTitleIfSpecialMyLanguage( $introTitle );
380
381            if ( $this->isPageExistingAndViewable( $introTitle, $performer ) ) {
382                $messages->addWithKey(
383                    'editintro',
384                    $localizer->msg( new RawMessage(
385                        // Added using template syntax, to take <noinclude>'s into account.
386                        '<div class="mw-editintro">{{:' . $introTitle->getFullText() . '}}</div>'
387                    ) )
388                        // Parse as content to enable language conversion (T353870)
389                        ->inContentLanguage()
390                        ->parse()
391                );
392                return;
393            }
394        }
395
396        if ( !$page->exists() ) {
397            $helpLink = $this->urlUtils->expand(
398                Skin::makeInternalOrExternalUrl(
399                    $localizer->msg( 'helppage' )->inContentLanguage()->text()
400                ),
401                PROTO_CURRENT
402            );
403            if ( $helpLink === null ) {
404                throw new LogicException( 'Help link was invalid, this should be impossible' );
405            }
406            if ( $performer->getUser()->isRegistered() ) {
407                $messages->add(
408                    $localizer->msg( 'newarticletext', $helpLink ),
409                    // Suppress the external link icon, consider the help url an internal one
410                    "<div class=\"mw-newarticletext plainlinks\">\n$1\n</div>"
411                );
412            } else {
413                $messages->add(
414                    $localizer->msg( 'newarticletextanon', $helpLink ),
415                    // Suppress the external link icon, consider the help url an internal one
416                    "<div class=\"mw-newarticletextanon plainlinks\">\n$1\n</div>"
417                );
418            }
419        }
420    }
421
422    private function addRecreateWarning(
423        IntroMessageList $messages,
424        MessageLocalizer $localizer,
425        ProperPageIdentity $page
426    ): void {
427        # Give a notice if the user is editing a deleted/moved page...
428        if ( !$page->exists() ) {
429            $dbr = $this->dbProvider->getReplicaDatabase();
430
431            $messages->addWithKey(
432                'recreate-moveddeleted-warn',
433                $this->getLogExtract( [ 'delete', 'move', 'merge' ], $page, '', [
434                    'lim' => 10,
435                    'conds' => [ $dbr->expr( 'log_action', '!=', 'revision' ) ],
436                    'showIfEmpty' => false,
437                    'msgKey' => [ 'recreate-moveddeleted-warn' ],
438                ] )
439            );
440        }
441    }
442
443    private function addTalkPageText(
444        IntroMessageList $messages,
445        MessageLocalizer $localizer,
446        Title $title
447    ): void {
448        if ( $title->isTalkPage() ) {
449            $messages->add( $localizer->msg( 'talkpagetext' ) );
450        }
451    }
452
453    private function addEditNotices(
454        IntroMessageList $messages,
455        MessageLocalizer $localizer,
456        Title $title,
457        ?RevisionRecord $revRecord
458    ): void {
459        $editNotices = $title->getEditNotices( $revRecord ? $revRecord->getId() : 0 );
460        if ( count( $editNotices ) ) {
461            foreach ( $editNotices as $key => $html ) {
462                $messages->addWithKey( $key, $html );
463            }
464        } else {
465            $msg = $localizer->msg( 'editnotice-notext' );
466            if ( !$msg->isDisabled() ) {
467                $messages->addWithKey(
468                    'editnotice-notext',
469                    Html::rawElement(
470                        'div',
471                        [ 'class' => 'mw-editnotice-notext' ],
472                        $msg->parseAsBlock()
473                    )
474                );
475            }
476        }
477    }
478
479    private function addOldRevisionWarning(
480        IntroMessageList $messages,
481        MessageLocalizer $localizer,
482        ?RevisionRecord $revRecord
483    ): void {
484        if ( $revRecord && !$revRecord->isCurrent() ) {
485            // This wrapper frame is not optional (T337071)
486            $messages->addWithKey( 'editingold', Html::warningBox( $localizer->msg( 'editingold' )->parse() ) );
487        }
488    }
489
490    private function addReadOnlyWarning(
491        IntroMessageList $messages,
492        MessageLocalizer $localizer
493    ): void {
494        if ( $this->readOnlyMode->isReadOnly() ) {
495            $messages->add(
496                $localizer->msg( 'readonlywarning', $this->readOnlyMode->getReason() ),
497                "<div id=\"mw-read-only-warning\">\n$1\n</div>"
498            );
499        }
500    }
501
502    private function addAnonEditWarning(
503        IntroMessageList $messages,
504        MessageLocalizer $localizer,
505        Title $title,
506        Authority $performer,
507        ?string $returnToQuery,
508        bool $preview
509    ): void {
510        if ( !$performer->getUser()->isRegistered() ) {
511            $tempUserCreateActive = $this->tempUserCreator->shouldAutoCreate( $performer, 'edit' );
512            if ( !$preview ) {
513                $messages->addWithKey(
514                    'anoneditwarning',
515                    $localizer->msg(
516                        $tempUserCreateActive ? 'autocreate-edit-warning' : 'anoneditwarning',
517                        // Log-in link
518                        SpecialPage::getTitleFor( 'Userlogin' )->getFullURL( [
519                            'returnto' => $title->getPrefixedDBkey(),
520                            'returntoquery' => $returnToQuery,
521                        ] ),
522                        // Sign-up link
523                        SpecialPage::getTitleFor( 'CreateAccount' )->getFullURL( [
524                            'returnto' => $title->getPrefixedDBkey(),
525                            'returntoquery' => $returnToQuery,
526                        ] )
527                    )->parse(),
528                    Html::warningBox( '$1', 'mw-anon-edit-warning' )
529                );
530            } else {
531                $messages->addWithKey(
532                    'anoneditwarning',
533                    $localizer->msg( $tempUserCreateActive ? 'autocreate-preview-warning' : 'anonpreviewwarning' )
534                        ->parse(),
535                    Html::warningBox( '$1', 'mw-anon-preview-warning' ) );
536            }
537        }
538    }
539
540    /**
541     * Checks whether the user entered a skin name in uppercase,
542     * e.g. "User:Example/Monobook.css" instead of "monobook.css"
543     */
544    private function isWrongCaseUserConfigPage( Title $title ): bool {
545        if ( $title->isUserCssConfigPage() || $title->isUserJsConfigPage() ) {
546            $name = $title->getSkinFromConfigSubpage();
547            $skins = array_merge(
548                array_keys( $this->skinFactory->getInstalledSkins() ),
549                [ 'common' ]
550            );
551            return !in_array( $name, $skins, true )
552                && in_array( strtolower( $name ), $skins, true );
553        } else {
554            return false;
555        }
556    }
557
558    private function addUserConfigPageInfo(
559        IntroMessageList $messages,
560        MessageLocalizer $localizer,
561        Title $title,
562        Authority $performer,
563        bool $preview
564    ): void {
565        if ( $title->isUserConfigPage() ) {
566            # Check the skin exists
567            if ( $this->isWrongCaseUserConfigPage( $title ) ) {
568                $messages->add(
569                    $localizer->msg( 'userinvalidconfigtitle', $title->getSkinFromConfigSubpage() ),
570                    Html::errorBox( '$1', '', 'mw-userinvalidconfigtitle' )
571                );
572            }
573            if ( $title->isSubpageOf( Title::makeTitle( NS_USER, $performer->getUser()->getName() ) ) ) {
574                $isUserCssConfig = $title->isUserCssConfigPage();
575                $isUserJsonConfig = $title->isUserJsonConfigPage();
576                $isUserJsConfig = $title->isUserJsConfigPage();
577
578                if ( !$preview ) {
579                    if ( $isUserCssConfig && $this->config->get( MainConfigNames::AllowUserCss ) ) {
580                        $messages->add(
581                            $localizer->msg( 'usercssyoucanpreview' ),
582                            "<div id='mw-usercssyoucanpreview'>\n$1\n</div>"
583                        );
584                    } elseif ( $isUserJsonConfig /* No comparable 'AllowUserJson' */ ) {
585                        $messages->add(
586                            $localizer->msg( 'userjsonyoucanpreview' ),
587                            "<div id='mw-userjsonyoucanpreview'>\n$1\n</div>"
588                        );
589                    } elseif ( $isUserJsConfig && $this->config->get( MainConfigNames::AllowUserJs ) ) {
590                        $messages->add(
591                            $localizer->msg( 'userjsyoucanpreview' ),
592                            "<div id='mw-userjsyoucanpreview'>\n$1\n</div>"
593                        );
594                    }
595                }
596            }
597        }
598    }
599
600    private function addPageProtectionWarningHeaders(
601        IntroMessageList $messages,
602        MessageLocalizer $localizer,
603        ProperPageIdentity $page
604    ): void {
605        if ( $this->restrictionStore->isProtected( $page, 'edit' ) &&
606            $this->permManager->getNamespaceRestrictionLevels(
607                $page->getNamespace()
608            ) !== [ '' ]
609        ) {
610            # Is the title semi-protected?
611            if ( $this->restrictionStore->isSemiProtected( $page ) ) {
612                $noticeMsg = 'semiprotectedpagewarning';
613            } else {
614                # Then it must be protected based on static groups (regular)
615                $noticeMsg = 'protectedpagewarning';
616            }
617            $messages->addWithKey(
618                $noticeMsg,
619                $this->getLogExtract( 'protect', $page, '', [ 'lim' => 1, 'msgKey' => [ $noticeMsg ] ] )
620            );
621        }
622        if ( $this->restrictionStore->isCascadeProtected( $page ) ) {
623            # Is this page under cascading protection from some source pages?
624            $tlCascadeSources = $this->restrictionStore->getCascadeProtectionSources( $page )[2];
625            if ( $tlCascadeSources ) {
626                $htmlList = '';
627                # Explain, and list the titles responsible
628                foreach ( $tlCascadeSources as $source ) {
629                    $htmlList .= Html::rawElement( 'li', [], $this->linkRenderer->makeLink( $source ) );
630                }
631                $messages->addWithKey(
632                    'cascadeprotectedwarning',
633                    $localizer->msg( 'cascadeprotectedwarning', count( $tlCascadeSources ) )->parse() .
634                        ( $htmlList ? Html::rawElement( 'ul', [], $htmlList ) : '' ),
635                    Html::warningBox( '$1', 'mw-cascadeprotectedwarning' )
636                );
637            }
638        }
639        if ( !$page->exists() && $this->restrictionStore->getRestrictions( $page, 'create' ) ) {
640            $messages->addWithKey(
641                'titleprotectedwarning',
642                $this->getLogExtract(
643                    'protect', $page,
644                    '',
645                    [
646                        'lim' => 1,
647                        'showIfEmpty' => false,
648                        'msgKey' => [ 'titleprotectedwarning' ],
649                        'wrap' => "<div class=\"mw-titleprotectedwarning\">\n$1</div>"
650                    ]
651                )
652            );
653        }
654    }
655
656    private function addHeaderCopyrightWarning(
657        IntroMessageList $messages,
658        MessageLocalizer $localizer
659    ): void {
660        $messages->add(
661            $localizer->msg( 'editpage-head-copy-warn' ),
662            "<div class='editpage-head-copywarn'>\n$1\n</div>"
663        );
664    }
665}