| Current Path : /var/www/html/mediawiki-1.43.1/extensions/Newsletter/includes/ |
| Current File : /var/www/html/mediawiki-1.43.1/extensions/Newsletter/includes/Hooks.php |
<?php
// phpcs:disable MediaWiki.NamingConventions.LowerCamelFunctionsName.FunctionName
namespace MediaWiki\Extension\Newsletter;
use Article;
use MediaWiki\Content\Content;
use MediaWiki\Content\Hook\ContentModelCanBeUsedOnHook;
use MediaWiki\Context\IContextSource;
use MediaWiki\Extension\Newsletter\Content\NewsletterContent;
use MediaWiki\Extension\Newsletter\Notifications\EchoNewsletterAnnouncePresentationModel;
use MediaWiki\Extension\Newsletter\Notifications\EchoNewsletterPublisherAddedPresentationModel;
use MediaWiki\Extension\Newsletter\Notifications\EchoNewsletterPublisherRemovedPresentationModel;
use MediaWiki\Extension\Newsletter\Notifications\EchoNewsletterSubscribedPresentationModel;
use MediaWiki\Extension\Newsletter\Notifications\EchoNewsletterUnsubscribedPresentationModel;
use MediaWiki\Extension\Newsletter\Notifications\EchoNewsletterUserLocator;
use MediaWiki\Extension\Notifications\UserLocator;
use MediaWiki\Hook\CustomEditorHook;
use MediaWiki\Hook\EditFilterMergedContentHook;
use MediaWiki\Hook\LoginFormValidErrorMessagesHook;
use MediaWiki\Hook\SkinTemplateNavigation__UniversalHook;
use MediaWiki\Hook\TitleMoveHook;
use MediaWiki\MediaWikiServices;
use MediaWiki\Page\Hook\ArticleDeleteHook;
use MediaWiki\Page\Hook\PageUndeleteHook;
use MediaWiki\Page\ProperPageIdentity;
use MediaWiki\Permissions\Authority;
use MediaWiki\Permissions\Hook\GetUserPermissionsErrorsHook;
use MediaWiki\Status\Status;
use MediaWiki\Title\Title;
use MediaWiki\User\User;
use PermissionsError;
use ReadOnlyError;
use RuntimeException;
use SkinTemplate;
use StatusValue;
use ThrottledError;
use WikiPage;
/**
* Class to add Hooks used by Newsletter.
*/
class Hooks implements
LoginFormValidErrorMessagesHook,
CustomEditorHook,
ArticleDeleteHook,
PageUndeleteHook,
TitleMoveHook,
ContentModelCanBeUsedOnHook,
EditFilterMergedContentHook,
SkinTemplateNavigation__UniversalHook,
GetUserPermissionsErrorsHook
{
/**
* Function to be called before EchoEvent
*
* @param array[] &$notifications Echo notifications
* @param array[] &$notificationCategories Echo notification categories
*/
public static function onBeforeCreateEchoEvent( &$notifications, &$notificationCategories ) {
$notificationCategories['newsletter'] = [
'priority' => 3,
'tooltip' => 'echo-pref-tooltip-newsletter',
];
$notifications['newsletter-announce'] = [
'category' => 'newsletter',
'section' => 'message',
'primary-link' => [
'message' => 'newsletter-notification-link-text-new-issue',
'destination' => 'new-issue'
],
'secondary-link' => [
'message' => 'newsletter-notification-link-text-view-newsletter',
'destination' => 'newsletter'
],
'user-locators' => [
[ [ EchoNewsletterUserLocator::class, 'locateNewsletterSubscribedUsers' ] ],
],
'canNotifyAgent' => true,
'presentation-model' => EchoNewsletterAnnouncePresentationModel::class,
'title-message' => 'newsletter-notification-title',
'title-params' => [ 'newsletter-name', 'title', 'agent', 'user' ],
'flyout-message' => 'newsletter-notification-flyout',
'flyout-params' => [ 'newsletter-name', 'agent', 'user' ],
'payload' => [ 'summary' ],
'email-subject-message' => 'newsletter-email-subject',
'email-subject-params' => [ 'newsletter-name' ],
'email-body-batch-message' => 'newsletter-email-batch-body',
'email-body-batch-params' => [ 'newsletter-name', 'agent', 'user' ],
];
$notifications['newsletter-newpublisher'] = [
'category' => 'newsletter',
'primary-link' => [
'message' => 'newsletter-notification-link-text-new-publisher',
'destination' => 'newsletter'
],
'user-locators' => [
[ [ UserLocator::class, 'locateFromEventExtra' ], [ 'new-publishers-id' ] ]
],
'presentation-model' => EchoNewsletterPublisherAddedPresentationModel::class,
'title-message' => 'newsletter-notification-new-publisher-title',
'title-params' => [ 'newsletter-name', 'agent' ],
'flyout-message' => 'newsletter-notification-new-publisher-flyout',
'flyout-params' => [ 'newsletter-name', 'agent' ],
];
$notifications['newsletter-delpublisher'] = [
'category' => 'newsletter',
'primary-link' => [
'message' => 'newsletter-notification-link-text-del-publisher',
'destination' => 'newsletter'
],
'user-locators' => [
[ [ UserLocator::class, 'locateFromEventExtra' ], [ 'del-publishers-id' ] ]
],
'presentation-model' => EchoNewsletterPublisherRemovedPresentationModel::class,
'title-message' => 'newsletter-notification-del-publisher-title',
'title-params' => [ 'newsletter-name', 'agent' ],
'flyout-message' => 'newsletter-notification-del-publisher-flyout',
'flyout-params' => [ 'newsletter-name', 'agent' ],
];
$notifications['newsletter-subscribed'] = [
'category' => 'newsletter',
'primary-link' => [
'message' => 'newsletter-notification-subscribed',
'destination' => 'newsletter'
],
'user-locators' => [
[ [ UserLocator::class, 'locateFromEventExtra' ], [ 'new-subscribers-id' ] ]
],
'presentation-model' => EchoNewsletterSubscribedPresentationModel::class,
'title-message' => 'newsletter-notification-subscribed',
'title-params' => [ 'newsletter-name' ],
];
$notifications['newsletter-unsubscribed'] = [
'category' => 'newsletter',
'primary-link' => [
'message' => 'newsletter-notification-unsubscribed',
'destination' => 'newsletter'
],
'user-locators' => [
[ [ UserLocator::class, 'locateFromEventExtra' ], [ 'removed-subscribers-id' ] ]
],
'presentation-model' => EchoNewsletterUnsubscribedPresentationModel::class,
'title-message' => 'newsletter-notification-unsubscribed',
'title-params' => [ 'newsletter-name' ],
];
}
/**
* Allows to add our own error message to LoginForm
*
* @param array &$messages
*/
public function onLoginFormValidErrorMessages( array &$messages ) {
// on Special:Newsletter/id/subscribe
$messages[] = 'newsletter-subscribe-loginrequired';
}
/**
* Tables that Extension:UserMerge needs to update
*
* @param array &$updateFields
*/
public static function onUserMergeAccountFields( array &$updateFields ) {
$updateFields[] = [ 'nl_publishers', 'nlp_publisher_id' ];
$updateFields[] = [ 'nl_subscriptions', 'nls_subscriber_id' ];
}
/**
* @param Article $article
* @param User $user
* @return bool
* @throws ReadOnlyError
*/
public function onCustomEditor( $article, $user ) {
if ( !$article->getTitle()->inNamespace( NS_NEWSLETTER ) ) {
return true;
}
$newsletter = Newsletter::newFromName( $article->getTitle()->getText() );
if ( $newsletter ) {
// A newsletter exists in that title, lets redirect to manage page
$editPage = new NewsletterEditPage( $article->getContext(), $newsletter );
$editPage->edit();
return false;
}
$editPage = new NewsletterEditPage( $article->getContext() );
$editPage->edit();
return false;
}
/**
* @param WikiPage $wikiPage
* @param User $user
* @param string &$reason
* @param string &$error
* @param Status &$status
* @param bool $suppress
* @throws PermissionsError
*/
public function onArticleDelete(
WikiPage $wikiPage,
User $user,
&$reason,
&$error,
Status &$status,
$suppress
) {
if ( !$wikiPage->getTitle()->inNamespace( NS_NEWSLETTER ) ) {
return;
}
$newsletter = Newsletter::newFromName( $wikiPage->getTitle()->getText() );
if ( $newsletter ) {
if ( !$newsletter->canDelete( $user ) ) {
throw new PermissionsError( 'newsletter-delete' );
}
NewsletterStore::getDefaultInstance()->deleteNewsletter( $newsletter );
}
}
/**
* @param ProperPageIdentity $page
* @param Authority $performer
* @param string $reason
* @param bool $unsuppress
* @param array $timestamps
* @param array $fileVersions
* @param StatusValue $status
* @return bool|void
*/
public function onPageUndelete(
ProperPageIdentity $page,
Authority $performer,
string $reason,
bool $unsuppress,
array $timestamps,
array $fileVersions,
StatusValue $status
) {
$title = MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $page )->getTitle();
if ( !$title->inNamespace( NS_NEWSLETTER ) ) {
return;
}
$newsletterName = $title->getText();
$newsletter = Newsletter::newFromName( $newsletterName, false );
if ( $newsletter ) {
if ( !$newsletter->canRestore( $performer ) ) {
$status->merge( User::newFatalPermissionDeniedStatus( 'newsletter-restore' ) );
return false;
}
$store = NewsletterStore::getDefaultInstance();
$rows = $store->newsletterExistsForMainPage( $newsletter->getPageId() );
foreach ( $rows as $row ) {
if ( (int)$row->nl_main_page_id === $newsletter->getPageId() && (int)$row->nl_active === 1 ) {
$status->fatal( 'newsletter-mainpage-in-use' );
return false;
}
}
$store->restoreNewsletter( $newsletter );
} elseif ( !$title->exists() ) {
// If the title exists, then there's no reason to block the undeletion
// whatever you are doing is probably a bad idea, but won't cause any inconsistencies
// since it will attach the disconnected revisions to the existing page
$status->fatal( 'newsletter-orphan-revisions' );
return false;
}
}
/**
* @param Title $title
* @param Title $newtitle
* @param User $user
* @param string $reason
* @param Status &$status
*/
public function onTitleMove(
Title $title,
Title $newtitle,
User $user,
$reason,
Status &$status
) {
if ( $newtitle->inNamespace( NS_NEWSLETTER ) ) {
$newsletter = Newsletter::newFromName( $title->getText() );
if ( $newsletter ) {
NewsletterStore::getDefaultInstance()->updateName( $newsletter->getId(), $newtitle->getText() );
} else {
throw new RuntimeException( 'Cannot find newsletter with name \"' . $title->getText() . '\"' );
}
}
}
/**
* Enforce the invariant that all pages in the Newsletter namespace
* correspond to an actual newsletter in the database by preventing
* any other content models from being used there.
* @param string $contentModel ID of the content model in question
* @param Title $title the Title in question.
* @param bool &$ok Output parameter, whether it is OK to use $contentModel on $title.
*/
public function onContentModelCanBeUsedOn( $contentModel, $title, &$ok ) {
if ( $title->inNamespace( NS_NEWSLETTER ) && $contentModel != 'NewsletterContent' ) {
$ok = false;
} elseif ( !$title->inNamespace( NS_NEWSLETTER ) && $contentModel == 'NewsletterContent' ) {
$ok = false;
}
}
/**
* @param IContextSource $context object implementing the IContextSource interface.
* @param Content $content content of the edit box, as a Content object.
* @param Status $status Status object to represent errors, etc.
* @param string $summary Edit summary for page
* @param User $user the User object representing the user who is performing the edit.
* @param bool $minoredit whether the edit was marked as minor by the user.
* @return bool
* @throws ThrottledError
*/
public function onEditFilterMergedContent(
IContextSource $context,
Content $content,
Status $status,
$summary,
User $user,
$minoredit
) {
if ( !$context->getTitle()->inNamespace( NS_NEWSLETTER ) ) {
return true;
}
if ( !$context->getTitle()->hasContentModel( 'NewsletterContent' ) ||
( !$content instanceof NewsletterContent )
) {
return true;
}
if ( $user->pingLimiter( 'newsletter' ) ) {
// Default user access level for creating a newsletter is quite low
// so add a throttle here to prevent abuse (eg. mass vandalism spree)
throw new ThrottledError;
}
$newsletter = Newsletter::newFromName( $context->getTitle()->getText() );
// Validate API Edit parameters
$formData = [
'Name' => $context->getTitle()->getText(),
'Description' => $content->getDescription(),
'MainPage' => $content->getMainPage(),
];
$validator = new NewsletterValidator( $formData );
$validation = $validator->validate( !$newsletter );
if ( !$validation->isGood() ) {
$status->merge( $validation );
// Invalid input was entered
return false;
}
$mainPageId = $content->getMainPage()->getArticleID();
$store = NewsletterStore::getDefaultInstance();
if ( !$newsletter || $newsletter->getPageId() !== $mainPageId ) {
$rows = $store->newsletterExistsForMainPage( $mainPageId );
foreach ( $rows as $row ) {
if ( (int)$row->nl_main_page_id === $mainPageId && (int)$row->nl_active === 1 ) {
$status->fatal( 'newsletter-mainpage-in-use' );
return false;
}
}
}
return true;
}
/**
* Hide the View Source tab in the Newsletter namespace for users who do not have any
* view permission ('newsletter-*')
*
* @param SkinTemplate $skinTemplate The skin template on which the UI is built.
* @param array &$links Navigation links.
*/
public function onSkinTemplateNavigation__Universal( $skinTemplate, &$links ): void {
if ( $skinTemplate->getTitle()->inNamespace( NS_NEWSLETTER ) ) {
unset( $links['views']['viewsource'] );
}
}
/**
* @param Title $title The title that permissions are being checked for
* @param User $user The User object representing the user who is attempting to perform the action
* @param string $action The action attempting to be performed
* @param string &$result Output parameter, set to a string to signify that the action isn't allowed
* @return bool
*/
public function onGetUserPermissionsErrors( $title, $user, $action, &$result ) {
if ( !$title->inNamespace( NS_NEWSLETTER ) ) {
return true;
}
if ( $action === 'edit' ) {
if ( $title->exists() ) {
$newsletter = Newsletter::newFromName( $title->getText() );
if ( !$newsletter->canManage( $user ) ) {
// This case can only trigger when using the API - the UI won't display an edit form at all
$result = "newsletter-api-error-nopermissions";
return false;
}
}
} elseif ( $action === 'create' ) {
$permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
if ( !$permissionManager->userHasRight( $user, 'newsletter-create' ) ) {
// This case can only trigger when using the API - the UI will display the standard
// "The action you have requested is limited to users in the group <groupnames>" error
$result = "newsletter-api-error-nocreate";
return false;
}
}
}
}