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