Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
74.84% covered (warning)
74.84%
241 / 322
35.29% covered (danger)
35.29%
6 / 17
CRAP
0.00% covered (danger)
0.00%
0 / 1
IntroMessageBuilder
74.84% covered (warning)
74.84%
241 / 322
35.29% covered (danger)
35.29%
6 / 17
234.65
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
59.52% covered (warning)
59.52%
25 / 42
0.00% covered (danger)
0.00%
0 / 1
24.21
 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.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 LogicException;
7use MediaWiki\Block\Block;
8use MediaWiki\Block\DatabaseBlockStore;
9use MediaWiki\Config\Config;
10use MediaWiki\Html\Html;
11use MediaWiki\Language\RawMessage;
12use MediaWiki\Linker\LinkRenderer;
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\SpecialPage\SpecialPage;
20use MediaWiki\SpecialPage\SpecialPageFactory;
21use MediaWiki\Title\NamespaceInfo;
22use MediaWiki\Title\Title;
23use MediaWiki\User\TempUser\TempUserCreator;
24use MediaWiki\User\UserFactory;
25use MediaWiki\User\UserNameUtils;
26use MediaWiki\User\UserRigorOptions;
27use MediaWiki\Utils\UrlUtils;
28use MessageLocalizer;
29use RepoGroup;
30use Skin;
31use SkinFactory;
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 $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        $isCSS = $title->hasContentModel( CONTENT_MODEL_CSS );
220
221        if ( $namespace === NS_MEDIAWIKI ) {
222            $interfaceMsg = $localizer->msg( 'editinginterface' );
223            $interfaceMsgText = $interfaceMsg->parse();
224            # Show a warning if editing an interface message
225            $intro .= $interfaceMsgText ? Html::rawElement(
226                'div',
227                [ 'class' => 'mw-editinginterface' ],
228                $interfaceMsgText
229            ) : '';
230            # If this is a default message (but not css, json, or js),
231            # show a hint that it is translatable on translatewiki.net
232            if (
233                !$isCSS
234                && !$title->hasContentModel( CONTENT_MODEL_JSON )
235                && !$isJavaScript
236            ) {
237                $defaultMessageText = $title->getDefaultMessageText();
238                if ( $defaultMessageText !== false ) {
239                    $translateInterfaceText = $localizer->msg( 'translateinterface' )->parse();
240                    $intro .= $translateInterfaceText ? Html::rawElement(
241                        'div',
242                        [ 'class' => 'mw-translateinterface' ],
243                        $translateInterfaceText
244                    ) : '';
245                }
246            }
247        }
248
249        if ( $isUserJsConfig ) {
250            $userConfigDangerousMsg = $localizer->msg( 'userjsdangerous' )->parse();
251            $intro .= $userConfigDangerousMsg ? Html::rawElement(
252                'div',
253                [ 'class' => 'mw-userconfigdangerous' ],
254                $userConfigDangerousMsg
255            ) : '';
256        }
257
258        // If the wiki page contains JavaScript or CSS link add message specific to code.
259        if ( $isJavaScript || $isCSS ) {
260            $intro .= $codeMessageText;
261        }
262
263        $messages->addWithKey(
264            'code-editing-intro',
265            $intro,
266            // While semantically this is a warning, given the impact of editing these pages,
267            // it's best to deter users who don't understand what they are doing by
268            // acknowledging the danger here. This is a potentially destructive action
269            // so requires destructive coloring.
270            Html::errorBox( '$1' )
271        );
272    }
273
274    private function addSharedRepoHint(
275        IntroMessageList $messages,
276        MessageLocalizer $localizer,
277        ProperPageIdentity $page
278    ): void {
279        $namespace = $page->getNamespace();
280        if ( $namespace === NS_FILE ) {
281            # Show a hint to shared repo
282            $file = $this->repoGroup->findFile( $page );
283            if ( $file && !$file->isLocal() ) {
284                $descUrl = $file->getDescriptionUrl();
285                # there must be a description url to show a hint to shared repo
286                if ( $descUrl ) {
287                    if ( !$page->exists() ) {
288                        $messages->add(
289                            $localizer->msg(
290                                'sharedupload-desc-create',
291                                $file->getRepo()->getDisplayName(),
292                                $descUrl
293                            ),
294                            "<div class=\"mw-sharedupload-desc-create\">\n$1\n</div>"
295                        );
296                    } else {
297                        $messages->add(
298                            $localizer->msg(
299                                'sharedupload-desc-edit',
300                                $file->getRepo()->getDisplayName(),
301                                $descUrl
302                            ),
303                            "<div class=\"mw-sharedupload-desc-edit\">\n$1\n</div>"
304                        );
305                    }
306                }
307            }
308        }
309    }
310
311    private function addUserWarnings(
312        IntroMessageList $messages,
313        MessageLocalizer $localizer,
314        Title $title,
315        Authority $performer
316    ): void {
317        $namespace = $title->getNamespace();
318        # Show a warning message when someone creates/edits a user (talk) page but the user does not exist
319        # Show log extract when the user is currently blocked
320        if ( $namespace === NS_USER || $namespace === NS_USER_TALK ) {
321            $username = explode( '/', $title->getText(), 2 )[0];
322            // Allow IP users
323            $validation = UserRigorOptions::RIGOR_NONE;
324            $user = $this->userFactory->newFromName( $username, $validation );
325            $ip = $this->userNameUtils->isIP( $username );
326            $block = $this->blockStore->newFromTarget( $user, $user );
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            } elseif (
345                $block !== null &&
346                $block->getType() !== Block::TYPE_AUTO &&
347                (
348                    $block->isSitewide() ||
349                    $this->permManager->isBlockedFrom(
350                        // @phan-suppress-next-line PhanTypeMismatchArgumentNullable False positive
351                        $user,
352                        $title,
353                        true
354                    )
355                )
356            ) {
357                // Show log extract if the user is sitewide blocked or is partially
358                // blocked and not allowed to edit their user page or user talk page
359                $messages->addWithKey(
360                    'blocked-notice-logextract',
361                    $this->getLogExtract(
362                        'block',
363                        $this->namespaceInfo->getCanonicalName( NS_USER ) . ':' . $block->getTargetName(),
364                        '',
365                        [
366                            'lim' => 1,
367                            'showIfEmpty' => false,
368                            'msgKey' => [
369                                'blocked-notice-logextract',
370                                $user->getName() # Support GENDER in notice
371                            ],
372                        ]
373                    )
374                );
375            }
376        }
377    }
378
379    /**
380     * Try to add a custom edit intro, or use the standard one if this is not possible.
381     */
382    private function addEditIntro(
383        IntroMessageList $messages,
384        MessageLocalizer $localizer,
385        ProperPageIdentity $page,
386        Authority $performer,
387        ?string $editIntro,
388        ?string $section
389    ): void {
390        if ( ( $editIntro === null || $editIntro === '' ) && $section === 'new' ) {
391            // Custom edit intro for new sections
392            $editIntro = 'MediaWiki:addsection-editintro';
393        }
394        if ( $editIntro !== null && $editIntro !== '' ) {
395            $introTitle = Title::newFromText( $editIntro );
396
397            // (T334855) Use SpecialMyLanguage redirect so that nonexistent translated pages can
398            // fall back to the corresponding page in a suitable language
399            $introTitle = $this->getTargetTitleIfSpecialMyLanguage( $introTitle );
400
401            if ( $this->isPageExistingAndViewable( $introTitle, $performer ) ) {
402                $messages->addWithKey(
403                    'editintro',
404                    $localizer->msg( new RawMessage(
405                        // Added using template syntax, to take <noinclude>'s into account.
406                        '<div class="mw-editintro">{{:' . $introTitle->getFullText() . '}}</div>'
407                    ) )
408                        // Parse as content to enable language conversion (T353870)
409                        ->inContentLanguage()
410                        ->parse()
411                );
412                return;
413            }
414        }
415
416        if ( !$page->exists() ) {
417            $helpLink = $this->urlUtils->expand(
418                Skin::makeInternalOrExternalUrl(
419                    $localizer->msg( 'helppage' )->inContentLanguage()->text()
420                ),
421                PROTO_CURRENT
422            );
423            if ( $helpLink === null ) {
424                throw new LogicException( 'Help link was invalid, this should be impossible' );
425            }
426            if ( $performer->getUser()->isRegistered() ) {
427                $messages->add(
428                    $localizer->msg( 'newarticletext', $helpLink ),
429                    // Suppress the external link icon, consider the help url an internal one
430                    "<div class=\"mw-newarticletext plainlinks\">\n$1\n</div>"
431                );
432            } else {
433                $messages->add(
434                    $localizer->msg( 'newarticletextanon', $helpLink ),
435                    // Suppress the external link icon, consider the help url an internal one
436                    "<div class=\"mw-newarticletextanon plainlinks\">\n$1\n</div>"
437                );
438            }
439        }
440    }
441
442    private function addRecreateWarning(
443        IntroMessageList $messages,
444        MessageLocalizer $localizer,
445        ProperPageIdentity $page
446    ): void {
447        # Give a notice if the user is editing a deleted/moved page...
448        if ( !$page->exists() ) {
449            $dbr = $this->dbProvider->getReplicaDatabase();
450
451            $messages->addWithKey(
452                'recreate-moveddeleted-warn',
453                $this->getLogExtract( [ 'delete', 'move', 'merge' ], $page, '', [
454                    'lim' => 10,
455                    'conds' => [ $dbr->expr( 'log_action', '!=', 'revision' ) ],
456                    'showIfEmpty' => false,
457                    'msgKey' => [ 'recreate-moveddeleted-warn' ],
458                ] )
459            );
460        }
461    }
462
463    private function addTalkPageText(
464        IntroMessageList $messages,
465        MessageLocalizer $localizer,
466        Title $title
467    ): void {
468        if ( $title->isTalkPage() ) {
469            $messages->add( $localizer->msg( 'talkpagetext' ) );
470        }
471    }
472
473    private function addEditNotices(
474        IntroMessageList $messages,
475        MessageLocalizer $localizer,
476        Title $title,
477        ?RevisionRecord $revRecord
478    ): void {
479        $editNotices = $title->getEditNotices( $revRecord ? $revRecord->getId() : 0 );
480        if ( count( $editNotices ) ) {
481            foreach ( $editNotices as $key => $html ) {
482                $messages->addWithKey( $key, $html );
483            }
484        } else {
485            $msg = $localizer->msg( 'editnotice-notext' );
486            if ( !$msg->isDisabled() ) {
487                $messages->addWithKey(
488                    'editnotice-notext',
489                    Html::rawElement(
490                        'div',
491                        [ 'class' => 'mw-editnotice-notext' ],
492                        $msg->parseAsBlock()
493                    )
494                );
495            }
496        }
497    }
498
499    private function addOldRevisionWarning(
500        IntroMessageList $messages,
501        MessageLocalizer $localizer,
502        ?RevisionRecord $revRecord
503    ): void {
504        if ( $revRecord && !$revRecord->isCurrent() ) {
505            // This wrapper frame is not optional (T337071)
506            $messages->addWithKey( 'editingold', Html::warningBox( $localizer->msg( 'editingold' )->parse() ) );
507        }
508    }
509
510    private function addReadOnlyWarning(
511        IntroMessageList $messages,
512        MessageLocalizer $localizer
513    ): void {
514        if ( $this->readOnlyMode->isReadOnly() ) {
515            $messages->add(
516                $localizer->msg( 'readonlywarning', $this->readOnlyMode->getReason() ),
517                "<div id=\"mw-read-only-warning\">\n$1\n</div>"
518            );
519        }
520    }
521
522    private function addAnonEditWarning(
523        IntroMessageList $messages,
524        MessageLocalizer $localizer,
525        Title $title,
526        Authority $performer,
527        ?string $returnToQuery,
528        bool $preview
529    ): void {
530        if ( !$performer->getUser()->isRegistered() ) {
531            $tempUserCreateActive = $this->tempUserCreator->shouldAutoCreate( $performer, 'edit' );
532            if ( !$preview ) {
533                $messages->addWithKey(
534                    'anoneditwarning',
535                    $localizer->msg(
536                        $tempUserCreateActive ? 'autocreate-edit-warning' : 'anoneditwarning',
537                        // Log-in link
538                        SpecialPage::getTitleFor( 'Userlogin' )->getFullURL( [
539                            'returnto' => $title->getPrefixedDBkey(),
540                            'returntoquery' => $returnToQuery,
541                        ] ),
542                        // Sign-up link
543                        SpecialPage::getTitleFor( 'CreateAccount' )->getFullURL( [
544                            'returnto' => $title->getPrefixedDBkey(),
545                            'returntoquery' => $returnToQuery,
546                        ] )
547                    )->parse(),
548                    Html::warningBox( '$1', 'mw-anon-edit-warning' )
549                );
550            } else {
551                $messages->addWithKey(
552                    'anoneditwarning',
553                    $localizer->msg( $tempUserCreateActive ? 'autocreate-preview-warning' : 'anonpreviewwarning' )
554                        ->parse(),
555                    Html::warningBox( '$1', 'mw-anon-preview-warning' ) );
556            }
557        }
558    }
559
560    /**
561     * Checks whether the user entered a skin name in uppercase,
562     * e.g. "User:Example/Monobook.css" instead of "monobook.css"
563     */
564    private function isWrongCaseUserConfigPage( Title $title ): bool {
565        if ( $title->isUserCssConfigPage() || $title->isUserJsConfigPage() ) {
566            $name = $title->getSkinFromConfigSubpage();
567            $skins = array_merge(
568                array_keys( $this->skinFactory->getInstalledSkins() ),
569                [ 'common' ]
570            );
571            return !in_array( $name, $skins, true )
572                && in_array( strtolower( $name ), $skins, true );
573        } else {
574            return false;
575        }
576    }
577
578    private function addUserConfigPageInfo(
579        IntroMessageList $messages,
580        MessageLocalizer $localizer,
581        Title $title,
582        Authority $performer,
583        bool $preview
584    ): void {
585        if ( $title->isUserConfigPage() ) {
586            # Check the skin exists
587            if ( $this->isWrongCaseUserConfigPage( $title ) ) {
588                $messages->add(
589                    $localizer->msg( 'userinvalidconfigtitle', $title->getSkinFromConfigSubpage() ),
590                    Html::errorBox( '$1', '', 'mw-userinvalidconfigtitle' )
591                );
592            }
593            if ( $title->isSubpageOf( Title::makeTitle( NS_USER, $performer->getUser()->getName() ) ) ) {
594                $isUserCssConfig = $title->isUserCssConfigPage();
595                $isUserJsonConfig = $title->isUserJsonConfigPage();
596                $isUserJsConfig = $title->isUserJsConfigPage();
597
598                if ( !$preview ) {
599                    if ( $isUserCssConfig && $this->config->get( MainConfigNames::AllowUserCss ) ) {
600                        $messages->add(
601                            $localizer->msg( 'usercssyoucanpreview' ),
602                            "<div id='mw-usercssyoucanpreview'>\n$1\n</div>"
603                        );
604                    } elseif ( $isUserJsonConfig /* No comparable 'AllowUserJson' */ ) {
605                        $messages->add(
606                            $localizer->msg( 'userjsonyoucanpreview' ),
607                            "<div id='mw-userjsonyoucanpreview'>\n$1\n</div>"
608                        );
609                    } elseif ( $isUserJsConfig && $this->config->get( MainConfigNames::AllowUserJs ) ) {
610                        $messages->add(
611                            $localizer->msg( 'userjsyoucanpreview' ),
612                            "<div id='mw-userjsyoucanpreview'>\n$1\n</div>"
613                        );
614                    }
615                }
616            }
617        }
618    }
619
620    private function addPageProtectionWarningHeaders(
621        IntroMessageList $messages,
622        MessageLocalizer $localizer,
623        ProperPageIdentity $page
624    ): void {
625        if ( $this->restrictionStore->isProtected( $page, 'edit' ) &&
626            $this->permManager->getNamespaceRestrictionLevels(
627                $page->getNamespace()
628            ) !== [ '' ]
629        ) {
630            # Is the title semi-protected?
631            if ( $this->restrictionStore->isSemiProtected( $page ) ) {
632                $noticeMsg = 'semiprotectedpagewarning';
633            } else {
634                # Then it must be protected based on static groups (regular)
635                $noticeMsg = 'protectedpagewarning';
636            }
637            $messages->addWithKey(
638                $noticeMsg,
639                $this->getLogExtract( 'protect', $page, '', [ 'lim' => 1, 'msgKey' => [ $noticeMsg ] ] )
640            );
641        }
642        if ( $this->restrictionStore->isCascadeProtected( $page ) ) {
643            # Is this page under cascading protection from some source pages?
644            $cascadeSources = $this->restrictionStore->getCascadeProtectionSources( $page )[0];
645            $htmlList = '';
646            # Explain, and list the titles responsible
647            foreach ( $cascadeSources as $source ) {
648                $htmlList .= Html::rawElement( 'li', [], $this->linkRenderer->makeLink( $source ) );
649            }
650            $messages->addWithKey(
651                'cascadeprotectedwarning',
652                $localizer->msg( 'cascadeprotectedwarning', count( $cascadeSources ) )->parse() .
653                    ( $htmlList ? Html::rawElement( 'ul', [], $htmlList ) : '' ),
654                Html::warningBox( '$1', 'mw-cascadeprotectedwarning' )
655            );
656        }
657        if ( !$page->exists() && $this->restrictionStore->getRestrictions( $page, 'create' ) ) {
658            $messages->addWithKey(
659                'titleprotectedwarning',
660                $this->getLogExtract(
661                    'protect', $page,
662                    '',
663                    [
664                        'lim' => 1,
665                        'showIfEmpty' => false,
666                        'msgKey' => [ 'titleprotectedwarning' ],
667                        'wrap' => "<div class=\"mw-titleprotectedwarning\">\n$1</div>"
668                    ]
669                )
670            );
671        }
672    }
673
674    private function addHeaderCopyrightWarning(
675        IntroMessageList $messages,
676        MessageLocalizer $localizer
677    ): void {
678        $messages->add(
679            $localizer->msg( 'editpage-head-copy-warn' ),
680            "<div class='editpage-head-copywarn'>\n$1\n</div>"
681        );
682    }
683}