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