MediaWiki master
IntroMessageBuilder.php
Go to the documentation of this file.
1<?php
2
3namespace MediaWiki\EditPage;
4
5use LogicException;
34
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
112 private function getLogExtract( $types = [], $page = '', $user = '', $param = [] ): string {
113 $outString = '';
114 LogEventsList::showLogExtract( $outString, $types, $page, $user, $param );
115 return $outString;
116 }
117
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
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
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 ),
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
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}
const CONTENT_MODEL_VUE
Definition Defines.php:241
const NS_USER
Definition Defines.php:53
const CONTENT_MODEL_CSS
Definition Defines.php:237
const NS_FILE
Definition Defines.php:57
const PROTO_CURRENT
Definition Defines.php:222
const NS_MEDIAWIKI
Definition Defines.php:59
const CONTENT_MODEL_JSON
Definition Defines.php:239
const NS_USER_TALK
Definition Defines.php:54
const CONTENT_MODEL_JAVASCRIPT
Definition Defines.php:236
wfEscapeWikiText( $input)
Escapes the given text so that it may be output using addWikiText() without any linking,...
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:68
Provides the intro messages (edit notices and others) to be displayed before an edit form.
getIntroMessages(int $frames, array $skip, MessageLocalizer $localizer, ProperPageIdentity $page, ?RevisionRecord $revRecord, Authority $performer, ?string $editIntro, ?string $returnToQuery, bool $preview, ?string $section=null)
Return intro messages to be shown before an edit form.
__construct(Config $config, LinkRenderer $linkRenderer, PermissionManager $permManager, UserNameUtils $userNameUtils, TempUserCreator $tempUserCreator, UserFactory $userFactory, RestrictionStore $restrictionStore, DatabaseBlockStore $blockStore, ReadOnlyMode $readOnlyMode, SpecialPageFactory $specialPageFactory, RepoGroup $repoGroup, NamespaceInfo $namespaceInfo, SkinFactory $skinFactory, IConnectionProvider $dbProvider, UrlUtils $urlUtils)
Encapsulates a list of edit form intro messages (as HTML) with their identifiers.
Prioritized list of file repositories.
Definition RepoGroup.php:30
This class is a collection of static functions that serve two purposes:
Definition Html.php:43
Variant of the Message class.
Class that generates HTML for internal links.
A class containing constants representing the names of configuration variables.
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
Page revision base class.
Factory class to create Skin objects.
The base class for all skins.
Definition Skin.php:52
Factory for handling the special page list and generating SpecialPage objects.
Parent class for all special pages.
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
Represents a title within MediaWiki.
Definition Title.php:69
Service for temporary user creation.
Create User objects.
UserNameUtils service.
A service to expand, parse, and otherwise manipulate URLs.
Definition UrlUtils.php:16
Determine whether a site is currently in read-only mode.
Interface for configuration instances.
Definition Config.php:18
Interface for localizing messages in MediaWiki.
Interface for objects (potentially) representing a page that can be viewable and linked to on a wiki.
Interface for a page that is (or could be, or used to be) an editable wiki page.
This interface represents the authority associated with the current execution context,...
Definition Authority.php:23
Shared interface for rigor levels when dealing with User methods.
Provide primary and replica IDatabase connections.
$source
msg( $key,... $params)