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