mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Echo
synced 2024-11-25 00:05:29 +00:00
68f7a9a985
::doUserEditContent() is available since 1.36 as a replacement for ::doEditContent(), which has been deprecated. Extension already requires MediaWiki 1.37+, so the method is always available. Bug: T255507 Change-Id: Iee5de356dbccd453a3083e0a58859b4cd83a946b
573 lines
18 KiB
PHP
573 lines
18 KiB
PHP
<?php
|
|
// phpcs:disable Generic.Files.LineLength -- Long html test examples
|
|
// @phan-file-suppress PhanUndeclaredClassMethod, PhanUndeclaredClassConstant Other extensions used for testing purposes
|
|
|
|
use MediaWiki\MediaWikiServices;
|
|
use MediaWiki\Revision\RevisionRecord;
|
|
use MediaWiki\Revision\SlotRecord;
|
|
|
|
$IP = getenv( 'MW_INSTALL_PATH' );
|
|
if ( $IP === false ) {
|
|
$IP = __DIR__ . '/../../..';
|
|
}
|
|
require_once "$IP/maintenance/Maintenance.php";
|
|
|
|
/**
|
|
* A maintenance script that generates sample notifications for testing purposes.
|
|
*/
|
|
class GenerateSampleNotifications extends Maintenance {
|
|
|
|
/** @var string[] */
|
|
private $supportedNotificationTypes = [
|
|
'welcome',
|
|
'edit-user-talk',
|
|
'mention',
|
|
'page-linked',
|
|
'reverted',
|
|
'email',
|
|
'user-rights',
|
|
'cx',
|
|
'osm',
|
|
'edit-thanks',
|
|
'edu',
|
|
'page-connection',
|
|
];
|
|
|
|
/** @var int */
|
|
private $timestampCounter = 5;
|
|
|
|
public function __construct() {
|
|
parent::__construct();
|
|
$this->addDescription( "Generate sample notifications" );
|
|
|
|
$this->addOption(
|
|
'force',
|
|
'Bypass confirmation',
|
|
false, false, 'f' );
|
|
|
|
$allTypes = implode( ',', $this->supportedNotificationTypes );
|
|
$this->addOption(
|
|
'types',
|
|
"Comma-separated list of notification types to generate ($allTypes)",
|
|
false, true, 't' );
|
|
|
|
$this->addOption(
|
|
'user',
|
|
'Name of the user receiving the notifications',
|
|
true, true, 'u' );
|
|
|
|
$this->addOption(
|
|
'agent',
|
|
'Name of the user creating the notifications',
|
|
true, true, 'a' );
|
|
|
|
$this->addOption(
|
|
'other',
|
|
'Name of another user involved with the notifications',
|
|
true, true, 'o' );
|
|
|
|
$this->requireExtension( 'Echo' );
|
|
|
|
$this->addOption(
|
|
'timestamp',
|
|
'Add notification timestamps (Epoch time format). All notifications that are not ' .
|
|
'related directly to edits will be created with a timestamp starting 5 minutes ' .
|
|
'before the given timestamp, and increasing by 1 minute per notification.',
|
|
false, false, 'k' );
|
|
}
|
|
|
|
public function execute() {
|
|
$user = $this->getOptionUser( 'user' );
|
|
$agent = $this->getOptionUser( 'agent' );
|
|
$otherUser = $this->getOptionUser( 'other' );
|
|
$title = Title::newFromText( 'This is a pretty long page title lets see if it is going to be truncated' );
|
|
|
|
$types = $this->getOption( 'types' );
|
|
if ( $types ) {
|
|
$types = explode( ',', $types );
|
|
} else {
|
|
$types = $this->supportedNotificationTypes;
|
|
}
|
|
|
|
$this->confirm();
|
|
|
|
$this->output( "Started processing...\n" );
|
|
|
|
if ( $this->shouldGenerate( 'welcome', $types ) ) {
|
|
$this->generateWelcome( $user );
|
|
}
|
|
|
|
if ( $this->shouldGenerate( 'edit-user-talk', $types ) ) {
|
|
$this->generateEditUserTalk( $user, $agent );
|
|
}
|
|
|
|
if ( $this->shouldGenerate( 'mention', $types ) ) {
|
|
$this->generateMention( $user, $agent, $otherUser, $title );
|
|
}
|
|
|
|
if ( $this->shouldGenerate( 'page-linked', $types ) ) {
|
|
$this->generatePageLink( $user, $agent );
|
|
}
|
|
|
|
if ( $this->shouldGenerate( 'reverted', $types ) ) {
|
|
$this->generateReverted( $user, $agent );
|
|
}
|
|
|
|
if ( $this->shouldGenerate( 'email', $types ) ) {
|
|
$this->generateEmail( $user, $agent );
|
|
}
|
|
|
|
if ( $this->shouldGenerate( 'user-rights', $types ) ) {
|
|
$this->generateUserRights( $user, $agent );
|
|
}
|
|
|
|
if ( $this->shouldGenerate( 'cx', $types ) ) {
|
|
$this->generateContentTranslation( $user );
|
|
}
|
|
|
|
if ( $this->shouldGenerate( 'osm', $types ) ) {
|
|
$this->generateOpenStackManager( $user, $agent );
|
|
}
|
|
|
|
if ( $this->shouldGenerate( 'edit-thanks', $types ) ) {
|
|
$this->generateEditThanks( $user, $agent, $otherUser );
|
|
}
|
|
|
|
if ( $this->shouldGenerate( 'edu', $types ) ) {
|
|
$this->generateEducationProgram( $user, $agent );
|
|
}
|
|
|
|
if ( $this->shouldGenerate( 'page-connection', $types ) ) {
|
|
$this->generateWikibase( $user, $agent );
|
|
}
|
|
|
|
$this->output( "Completed \n" );
|
|
}
|
|
|
|
/**
|
|
* Get the set timestamp of the event
|
|
*
|
|
* @param bool $getEpoch Get the epoch value
|
|
* @return int Timestamp for the operation
|
|
*/
|
|
private function getTimestamp( $getEpoch = false ) {
|
|
$startTime = $this->getOption( 'timestamp' ) ?: time();
|
|
|
|
// Incrementally decrease X minutes from start time
|
|
$timestamp = strtotime( '-' . $this->timestampCounter++ . ' minute', $startTime );
|
|
|
|
return $getEpoch ? $timestamp : (int)wfTimestamp( TS_UNIX, $timestamp );
|
|
}
|
|
|
|
/**
|
|
* Add a timestamp string to the output, if a timestamp option was given,
|
|
* to note the time of the new generated event.
|
|
*
|
|
* @param string $output New output message with timestamp
|
|
* @return string
|
|
*/
|
|
private function addTimestampToOutput( $output ) {
|
|
if ( $this->getOption( 'timestamp' ) ) {
|
|
$output .= ' (Using timestamp: ' . date( 'Y-m-d H:i:s', $this->getTimestamp( true ) ) . ')';
|
|
}
|
|
return $output;
|
|
}
|
|
|
|
private function generateEditUserTalk( User $user, User $agent ) {
|
|
$this->output( "{$agent->getName()} is writing on {$user->getName()}'s user talk page\n" );
|
|
$editId = $this->generateRandomString();
|
|
$section = "== section $editId ==\n\nthis is the text $editId\n\n~~~~\n\n";
|
|
$this->addToUserTalk( $user, $agent, $section );
|
|
}
|
|
|
|
private function getOptionUser( $optionName ) {
|
|
$username = $this->getOption( $optionName );
|
|
$user = User::newFromName( $username );
|
|
if ( !$user->isRegistered() ) {
|
|
$this->error( "User $username does not seem to exist in this wiki", 1 );
|
|
}
|
|
return $user;
|
|
}
|
|
|
|
private function generateRandomString( $length = 10 ) {
|
|
return substr( str_shuffle( "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" ), 0, $length );
|
|
}
|
|
|
|
protected function confirm() {
|
|
if ( $this->getOption( 'force', false ) ) {
|
|
return;
|
|
}
|
|
$this->output( "=== WARNING ===\n" );
|
|
$this->output( "This script modifies the content of several pages,\n" );
|
|
$this->output( "including user's talk pages.\n" );
|
|
$this->output( "ONLY RUN ON TEST WIKIS\n" );
|
|
$this->output( "Enter 'yes' if you wish to continue or any other key to exit\n" );
|
|
$confirm = $this->readconsole();
|
|
if ( $confirm !== 'yes' ) {
|
|
$this->error( 'Safe decision', 1 );
|
|
}
|
|
}
|
|
|
|
private function addToUserTalk( User $user, User $agent, $contentText ) {
|
|
$this->addToPageContent( $user->getTalkPage(), $agent, $contentText );
|
|
}
|
|
|
|
private function addToPageContent( Title $title, User $agent, $contentText ) {
|
|
$page = WikiPage::factory( $title );
|
|
$previousContent = "";
|
|
$page->loadPageData( WikiPage::READ_LATEST );
|
|
$revision = $page->getRevisionRecord();
|
|
if ( $revision ) {
|
|
$content = $revision->getContent( SlotRecord::MAIN, RevisionRecord::FOR_PUBLIC );
|
|
if ( $content instanceof WikitextContent ) {
|
|
$previousContent = $content->getText();
|
|
}
|
|
}
|
|
$status = $page->doUserEditContent(
|
|
new WikitextContent( $contentText . $previousContent ),
|
|
$agent,
|
|
'generating sample notifications'
|
|
);
|
|
|
|
if ( !$status->isGood() ) {
|
|
$this->error( "Failed to edit {$title->getPrefixedText()}: {$status->getMessage()->text()}" );
|
|
}
|
|
|
|
return $status->getValue()['revision-record'];
|
|
}
|
|
|
|
private function generateMention( User $user, User $agent, User $otherUser, Title $title ) {
|
|
$mention = "== section {$this->generateRandomString()} ==\nHello [[User:{$user->getName()}]] \n~~~~\n";
|
|
|
|
// article talk
|
|
$this->output( "{$agent->getName()} is mentioning {$user->getName()} on {$title->getTalkPage()->getPrefixedText()}\n" );
|
|
$this->addToPageContent( $title->getTalkPage(), $agent, $mention );
|
|
|
|
// agent talk
|
|
$this->output( "{$agent->getName()} is mentioning {$user->getName()} on {$agent->getTalkPage()->getPrefixedText()}\n" );
|
|
$this->addToPageContent( $agent->getTalkPage(), $agent, $mention );
|
|
|
|
// user talk
|
|
$this->output( "{$agent->getName()} is mentioning {$user->getName()} on {$otherUser->getTalkPage()->getPrefixedText()}\n" );
|
|
$this->addToPageContent( $otherUser->getTalkPage(), $agent, $mention );
|
|
|
|
// any other page
|
|
$this->output( "{$agent->getName()} is mentioning {$user->getName()} on {$title->getPrefixedText()}\n" );
|
|
$this->addToPageContent( $title, $agent, $mention );
|
|
}
|
|
|
|
private function generatePageLink( User $user, User $agent ) {
|
|
$this->generateOnePageLink( $user, $agent );
|
|
$this->generateMultiplePageLinks( $user, $agent );
|
|
}
|
|
|
|
private function generateNewPageTitle() {
|
|
return Title::newFromText( $this->generateRandomString() );
|
|
}
|
|
|
|
private function generateReverted( User $user, User $agent ) {
|
|
$services = MediaWikiServices::getInstance();
|
|
$services->getUserGroupManager()->addUserToGroup( $agent, 'sysop' );
|
|
|
|
// revert (undo)
|
|
$moai = Title::newFromText( 'Moai' );
|
|
$page = WikiPage::factory( $moai );
|
|
$this->output( "{$agent->getName()} is reverting {$user->getName()}'s edit on {$moai->getPrefixedText()}\n" );
|
|
$this->addToPageContent( $moai, $agent, "\ncreating a good revision here\n" );
|
|
$this->addToPageContent( $moai, $user, "\nadding a line here\n" );
|
|
|
|
$undoRev = $page->getRevisionRecord();
|
|
$previous = $services->getRevisionLookup()
|
|
->getPreviousRevision( $undoRev );
|
|
|
|
$handler = $services->getContentHandlerFactory()
|
|
->getContentHandler(
|
|
$undoRev->getSlot( SlotRecord::MAIN, RevisionRecord::RAW )
|
|
->getModel()
|
|
);
|
|
$undoContent = $undoRev->getContent( SlotRecord::MAIN );
|
|
$previousContent = $previous->getContent( SlotRecord::MAIN );
|
|
|
|
$content = $handler->getUndoContent(
|
|
$undoContent,
|
|
$undoContent,
|
|
$previousContent,
|
|
true // undoIsLatest
|
|
);
|
|
|
|
$status = $page->doUserEditContent(
|
|
$content,
|
|
$agent,
|
|
'undo',
|
|
0, // $flags
|
|
false, // $originalRevId
|
|
[], // $tags
|
|
$undoRev->getId()
|
|
);
|
|
|
|
if ( !$status->isGood() ) {
|
|
$this->error( "Failed to undo {$moai->getPrefixedText()}: {$status->getMessage()->text()}" );
|
|
}
|
|
}
|
|
|
|
private function generateWelcome( User $user ) {
|
|
$this->output( "Welcoming {$user->getName()}\n" );
|
|
EchoEvent::create( [
|
|
'type' => 'welcome',
|
|
'agent' => $user,
|
|
'timestamp' => $this->getTimestamp(),
|
|
] );
|
|
}
|
|
|
|
private function generateEmail( User $user, User $agent ) {
|
|
$output = $this->addTimestampToOutput( "{$agent->getName()} is emailing {$user->getName()}" );
|
|
$this->output( "$output\n" );
|
|
EchoEvent::create( [
|
|
'type' => 'emailuser',
|
|
'extra' => [
|
|
'to-user-id' => $user->getId(),
|
|
'subject' => 'Long time no see',
|
|
],
|
|
'agent' => $agent,
|
|
'timestamp' => $this->getTimestamp(),
|
|
] );
|
|
}
|
|
|
|
private function generateUserRights( User $user, User $agent ) {
|
|
$output = $this->addTimestampToOutput( "{$agent->getName()} is changing {$user->getName()}'s rights" );
|
|
$this->output( "$output\n" );
|
|
$this->createUserRightsNotification( $user, $agent, [ 'OnlyAdd-1' ], null );
|
|
$this->createUserRightsNotification( $user, $agent, null, [ 'JustRemove-1', 'JustRemove-2' ] );
|
|
$this->createUserRightsNotification( $user, $agent, [ 'Add-1', 'Add-2' ], [ 'Remove-1', 'Remove-2' ] );
|
|
}
|
|
|
|
private function createUserRightsNotification( User $user, User $agent, $add, $remove ) {
|
|
EchoEvent::create(
|
|
[
|
|
'type' => 'user-rights',
|
|
'extra' => [
|
|
'user' => $user->getId(),
|
|
'add' => $add,
|
|
'remove' => $remove,
|
|
'reason' => 'This is the [[reason]] for changing your user rights.',
|
|
],
|
|
'agent' => $agent,
|
|
'timestamp' => $this->getTimestamp(),
|
|
]
|
|
);
|
|
}
|
|
|
|
private function generateContentTranslation( User $user ) {
|
|
if ( !ExtensionRegistry::getInstance()->isLoaded( 'ContentTranslation' ) ) {
|
|
return;
|
|
}
|
|
|
|
$this->output( "Generating CX notifications\n" );
|
|
foreach ( [ 'cx-first-translation', 'cx-tenth-translation', 'cx-hundredth-translation' ] as $eventType ) {
|
|
EchoEvent::create(
|
|
[
|
|
'type' => $eventType,
|
|
'extra' => [
|
|
'recipient' => $user->getId(),
|
|
],
|
|
'timestamp' => $this->getTimestamp(),
|
|
]
|
|
);
|
|
}
|
|
|
|
EchoEvent::create(
|
|
[
|
|
'type' => 'cx-suggestions-available',
|
|
'extra' => [
|
|
'recipient' => $user->getId(),
|
|
'lastTranslationTitle' => 'History of the People\'s Republic of China'
|
|
],
|
|
'timestamp' => $this->getTimestamp(),
|
|
]
|
|
);
|
|
}
|
|
|
|
private function generateOnePageLink( User $user, User $agent ) {
|
|
$pageBeingLinked = $this->generateNewPageTitle();
|
|
$this->addToPageContent( $pageBeingLinked, $user, "this is a new page" );
|
|
|
|
$pageLinking = $this->generateNewPageTitle();
|
|
$content = "checkout [[{$pageBeingLinked->getPrefixedText()}]]!";
|
|
$this->output( "{$agent->getName()} is linking to {$pageBeingLinked->getPrefixedText()} from {$pageLinking->getPrefixedText()}\n" );
|
|
$this->addToPageContent( $pageLinking, $agent, $content );
|
|
}
|
|
|
|
private function generateMultiplePageLinks( User $user, User $agent ) {
|
|
$pageBeingLinked = $this->generateNewPageTitle();
|
|
$this->addToPageContent( $pageBeingLinked, $user, "this is a new page" );
|
|
|
|
$content = "checkout [[{$pageBeingLinked->getPrefixedText()}]]!";
|
|
$this->output( "{$agent->getName()} is linking to {$pageBeingLinked->getPrefixedText()} from multiple pages\n" );
|
|
$this->addToPageContent( $this->generateNewPageTitle(), $agent, $content );
|
|
// @phan-suppress-next-line PhanPluginDuplicateAdjacentStatement
|
|
$this->addToPageContent( $this->generateNewPageTitle(), $agent, $content );
|
|
// @phan-suppress-next-line PhanPluginDuplicateAdjacentStatement
|
|
$this->addToPageContent( $this->generateNewPageTitle(), $agent, $content );
|
|
}
|
|
|
|
private function generateOpenStackManager( User $user, User $agent ) {
|
|
if ( !ExtensionRegistry::getInstance()->isLoaded( 'OpenStackManager' ) ) {
|
|
return;
|
|
}
|
|
|
|
$this->output( "Generating OpenStackManager notifications\n" );
|
|
|
|
foreach ( [ 'build-completed', 'reboot-completed', 'deleted' ] as $action ) {
|
|
EchoEvent::create( [
|
|
'type' => "osm-instance-$action",
|
|
'title' => Title::newFromText( "Moai" ),
|
|
'agent' => $user,
|
|
'extra' => [
|
|
'instanceName' => 'instance1',
|
|
'projectName' => 'TheProject',
|
|
'notifyAgent' => true,
|
|
],
|
|
'timestamp' => $this->getTimestamp(),
|
|
] );
|
|
}
|
|
|
|
EchoEvent::create( [
|
|
'type' => 'osm-projectmembers-add',
|
|
'title' => Title::newFromText( "Moai" ),
|
|
'agent' => $agent,
|
|
'extra' => [ 'userAdded' => $user->getId() ],
|
|
'timestamp' => $this->getTimestamp(),
|
|
] );
|
|
}
|
|
|
|
private function shouldGenerate( $type, array $types ) {
|
|
return array_search( $type, $types ) !== false;
|
|
}
|
|
|
|
private function generateEditThanks( User $user, User $agent, User $otherUser ) {
|
|
$this->generateOneEditThanks( $user, $agent );
|
|
$this->generateMultipleEditThanks( $user, $agent, $otherUser );
|
|
}
|
|
|
|
private function generateOneEditThanks( User $user, User $agent ) {
|
|
if ( !ExtensionRegistry::getInstance()->isLoaded( 'Thanks' ) ) {
|
|
return;
|
|
}
|
|
// make an edit, thank it once
|
|
$title = $this->generateNewPageTitle();
|
|
$revisionRecord = $this->addToPageContent( $title, $user, "an awesome edit! ~~~~" );
|
|
EchoEvent::create( [
|
|
'type' => 'edit-thank',
|
|
'title' => $title,
|
|
'extra' => [
|
|
'revid' => $revisionRecord->getId(),
|
|
'thanked-user-id' => $user->getId(),
|
|
'source' => 'generateSampleNotifications.php',
|
|
],
|
|
'agent' => $agent,
|
|
'timestamp' => $this->getTimestamp(),
|
|
] );
|
|
$output = $this->addTimestampToOutput( "{$agent->getName()} is thanking {$user->getName()} for edit {$revisionRecord->getId()} on {$title->getPrefixedText()}" );
|
|
$this->output( "$output\n" );
|
|
}
|
|
|
|
private function generateMultipleEditThanks( User $user, User $agent, User $otherUser ) {
|
|
if ( !ExtensionRegistry::getInstance()->isLoaded( 'Thanks' ) ) {
|
|
return;
|
|
}
|
|
// make an edit, thank it twice
|
|
$title = $this->generateNewPageTitle();
|
|
$revisionRecord = $this->addToPageContent( $title, $user, "an even better edit! ~~~~" );
|
|
EchoEvent::create( [
|
|
'type' => 'edit-thank',
|
|
'title' => $title,
|
|
'extra' => [
|
|
'revid' => $revisionRecord->getId(),
|
|
'thanked-user-id' => $user->getId(),
|
|
'source' => 'generateSampleNotifications.php',
|
|
],
|
|
'agent' => $agent,
|
|
'timestamp' => $this->getTimestamp(),
|
|
] );
|
|
EchoEvent::create( [
|
|
'type' => 'edit-thank',
|
|
'title' => $title,
|
|
'extra' => [
|
|
'revid' => $revisionRecord->getId(),
|
|
'thanked-user-id' => $user->getId(),
|
|
'source' => 'generateSampleNotifications.php',
|
|
],
|
|
'agent' => $otherUser,
|
|
'timestamp' => $this->getTimestamp(),
|
|
] );
|
|
$output = $this->addTimestampToOutput( "{$agent->getName()} and {$otherUser->getName()} are thanking {$user->getName()} for edit {$revisionRecord->getId()} on {$title->getPrefixedText()}" );
|
|
$this->output( "$output\n" );
|
|
}
|
|
|
|
private function generateEducationProgram( User $user, User $agent ) {
|
|
if ( !ExtensionRegistry::getInstance()->isLoaded( 'EducationProgram' ) ) {
|
|
$this->output( "Skipping EducationProgram. Extension not installed.\n" );
|
|
return;
|
|
}
|
|
|
|
$chem101 = Title::newFromText( 'School/Chemistry101' );
|
|
if ( !$chem101->exists() ) {
|
|
$this->addToPageContent( $chem101, $agent, "\nThis is the main page for the Chemistry 101 course\n" );
|
|
}
|
|
|
|
$notificationManager = EducationProgram\Extension::globalInstance()->getNotificationsManager();
|
|
|
|
$output = $this->addTimestampToOutput( "{$agent->getName()} is adding {$user->getName()} to {$chem101->getPrefixedText()} as instructor, student, campus volunteer and online volunteer" );
|
|
$this->output( "$output\n" );
|
|
|
|
$types = [
|
|
'ep-instructor-add-notification',
|
|
'ep-online-add-notification',
|
|
'ep-campus-add-notification',
|
|
'ep-student-add-notification',
|
|
];
|
|
foreach ( $types as $type ) {
|
|
$notificationManager->trigger(
|
|
$type,
|
|
[
|
|
'role-add-title' => $chem101,
|
|
'agent' => $agent,
|
|
'users' => [ $user->getId() ],
|
|
]
|
|
);
|
|
}
|
|
|
|
// NOTE: Not generating 'ep-course-talk-notification' for now
|
|
// as it requires a full setup to actually work (institution, course, instructors, students).
|
|
}
|
|
|
|
private function generateWikibase( User $user, User $agent ) {
|
|
if ( !ExtensionRegistry::getInstance()->isLoaded( 'WikibaseClient' ) ) {
|
|
$this->output( "Skipping Wikibase. Extension not installed.\n" );
|
|
return;
|
|
}
|
|
|
|
$title = $this->generateNewPageTitle();
|
|
$this->addToPageContent( $title, $user, "this is a new page" );
|
|
$helpPage = Title::newFromText( 'Project:Wikidata' );
|
|
$this->addToPageContent( $helpPage, $user, "this is the help page" );
|
|
|
|
$output = $this->addTimestampToOutput( "{$agent->getName()} is connecting {$user->getName()}'s page {$title->getPrefixedText()} to an item" );
|
|
$this->output( "$output\n" );
|
|
EchoEvent::create( [
|
|
'type' => EchoNotificationsHandlers::NOTIFICATION_TYPE,
|
|
'title' => $title,
|
|
'extra' => [
|
|
'url' => Title::newFromText( 'Item:Q1' )->getFullURL(),
|
|
'repoSiteName' => 'Wikidata',
|
|
'entity' => 'Q1',
|
|
],
|
|
'agent' => $agent,
|
|
'timestamp' => $this->getTimestamp(),
|
|
] );
|
|
}
|
|
}
|
|
|
|
$maintClass = GenerateSampleNotifications::class;
|
|
require_once RUN_MAINTENANCE_IF_MAIN;
|