From e27f4937ff8c3d7f6eb0ccd9d98e1b2aac4ef390 Mon Sep 17 00:00:00 2001 From: Siddharth VP Date: Thu, 8 Jun 2023 01:48:51 +0530 Subject: [PATCH] Add API module for sending notifications Allows users to send notifications to themselves (T306211). For sending notifications to others, a new permission is created (echo-create), assigned only to bots by default. For now, only one user can be notified in one API request. If the email flag is set in the API params, the notification is also sent as an email, provided the user hasn't disabled email notifications for the "api-triggered" category. This feature is behind a feature flag. Set $wgEchoEnableApiEvents = true to use. Adapted from If0267a38be7d454e3d284d30f93c93a828288dd7. Co-authored-by: TheresNoTime Bug: T58362 Bug: T306211 Change-Id: I94642bff5dcb075cb9db862206d59c19edad9fd1 --- extension.json | 53 +++++- i18n/api/en.json | 9 ++ i18n/api/qqq.json | 9 ++ i18n/en.json | 8 + i18n/qqq.json | 8 + includes/Api/ApiEchoCreateEvent.php | 152 ++++++++++++++++++ .../EchoManualPresentationModel.php | 51 ++++++ includes/Hooks.php | 11 +- includes/Notifier.php | 4 + modules/icons/robot.svg | 7 + tests/phpunit/Api/ApiEchoCreateEventTest.php | 83 ++++++++++ 11 files changed, 393 insertions(+), 2 deletions(-) create mode 100644 includes/Api/ApiEchoCreateEvent.php create mode 100644 includes/Formatters/EchoManualPresentationModel.php create mode 100644 modules/icons/robot.svg create mode 100644 tests/phpunit/Api/ApiEchoCreateEventTest.php diff --git a/extension.json b/extension.json index 2dbd1dc81..f9f2ca3cd 100644 --- a/extension.json +++ b/extension.json @@ -35,6 +35,12 @@ "APIModules": { "echomarkread": "MediaWiki\\Extension\\Notifications\\Api\\ApiEchoMarkRead", "echomarkseen": "MediaWiki\\Extension\\Notifications\\Api\\ApiEchoMarkSeen", + "echocreateevent": { + "class": "MediaWiki\\Extension\\Notifications\\Api\\ApiEchoCreateEvent", + "services": [ + "UserNameUtils" + ] + }, "echoarticlereminder": "MediaWiki\\Extension\\Notifications\\Api\\ApiEchoArticleReminder", "echomute": { "class": "MediaWiki\\Extension\\Notifications\\Api\\ApiEchoMute", @@ -100,11 +106,15 @@ "NotificationsMarkRead": "MediaWiki\\Extension\\Notifications\\Special\\SpecialNotificationsMarkRead" }, "AvailableRights": [ - "manage-all-push-subscriptions" + "manage-all-push-subscriptions", + "echo-create" ], "GroupPermissions": { "push-subscription-manager": { "manage-all-push-subscriptions": true + }, + "bot": { + "echo-create": true } }, "MessagesDirs": { @@ -689,6 +699,10 @@ "minor-watchlist": { "priority": 6, "tooltip": "echo-pref-tooltip-minor-watchlist" + }, + "api-triggered": { + "priority": 9, + "tooltip": "echo-pref-tooltip-api-triggered" } }, "merge_strategy": "array_plus_2d" @@ -760,6 +774,9 @@ }, "article-reminder": { "path": "Echo/modules/icons/global-progressive.svg" + }, + "robot": { + "path": "Echo/modules/icons/robot.svg" } }, "merge_strategy": "array_plus_2d" @@ -1013,6 +1030,36 @@ "group": "positive", "presentation-model": "MediaWiki\\Extension\\Notifications\\Formatters\\EchoArticleReminderPresentationModel", "section": "message" + }, + "api-alert": { + "user-locators": [ + [ + "EchoUserLocator::locateFromEventExtra", + [ + "recipients" + ] + ] + ], + "canNotifyAgent": true, + "category": "api-triggered", + "group": "neutral", + "section": "alert", + "presentation-model": "MediaWiki\\Extension\\Notifications\\Formatters\\EchoManualPresentationModel" + }, + "api-notice": { + "user-locators": [ + [ + "EchoUserLocator::locateFromEventExtra", + [ + "recipients" + ] + ] + ], + "canNotifyAgent": true, + "category": "api-triggered", + "group": "neutral", + "section": "message", + "presentation-model": "MediaWiki\\Extension\\Notifications\\Formatters\\EchoManualPresentationModel" } }, "merge_strategy": "array_plus_2d" @@ -1040,6 +1087,10 @@ "value": true, "description": "Whether to send email notifications each time a watched page is edited (if false) or only the first time the page is changed before being visited again by the user (if true)" }, + "EchoEnableApiEvents": { + "value": false, + "description": "Whether to enable the API for creating custom Echo events" + }, "EchoEnablePush": { "value": false, "description": "Whether to enable push notifications" diff --git a/i18n/api/en.json b/i18n/api/en.json index 74edba24d..7f660dcfb 100644 --- a/i18n/api/en.json +++ b/i18n/api/en.json @@ -9,6 +9,15 @@ "Stephane Bisson" ] }, + "apihelp-echocreateevent-description": "Manually trigger a notification to a user", + "apihelp-echocreateevent-summary": "Manually trigger a notification to a user", + "apihelp-echocreateevent-example": "Send a notification", + "apihelp-echocreateevent-param-user": "User to send the notification to", + "apihelp-echocreateevent-param-header": "Header content of the notification", + "apihelp-echocreateevent-param-content": "Body content of the notification", + "apihelp-echocreateevent-param-page": "Page to link to in the notification", + "apihelp-echocreateevent-param-section": "Section where notification would be delivered", + "apihelp-echocreateevent-param-email": "Whether to send an email as well", "apihelp-echomarkread-summary": "Mark notifications as read for the current user.", "apihelp-echomarkread-param-list": "A list of notification IDs to mark as read.", "apihelp-echomarkread-param-unreadlist": "A list of notification IDs to mark as unread.", diff --git a/i18n/api/qqq.json b/i18n/api/qqq.json index 5f10b16b2..12c0366c1 100644 --- a/i18n/api/qqq.json +++ b/i18n/api/qqq.json @@ -11,6 +11,15 @@ "Umherirrender" ] }, + "apihelp-echocreateevent-description": "{{doc-apihelp-description|echocreateevent}}", + "apihelp-echocreateevent-summary": "{{doc-apihelp-summary|echocreateevent}}", + "apihelp-echocreateevent-example": "{{doc-apihelp-example|echocreateevent}}", + "apihelp-echocreateevent-param-user": "{{doc-apihelp-param|echocreateevent}}", + "apihelp-echocreateevent-param-header": "{{doc-apihelp-param|echocreateevent}}", + "apihelp-echocreateevent-param-content": "{{doc-apihelp-param|echocreateevent}}", + "apihelp-echocreateevent-param-page": "{{doc-apihelp-param|echocreateevent}}", + "apihelp-echocreateevent-param-section": "{{doc-apihelp-param|echocreateevent}}", + "apihelp-echocreateevent-param-email": "{{doc-apihelp-param|echocreateevent}}", "apihelp-echomarkread-summary": "{{doc-apihelp-summary|echomarkread}}", "apihelp-echomarkread-param-list": "{{doc-apihelp-param|echomarkread|list}}", "apihelp-echomarkread-param-unreadlist": "{{doc-apihelp-param|echomarkread|unreadlist}}", diff --git a/i18n/en.json b/i18n/en.json index 426e70e0b..49a048846 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -83,6 +83,7 @@ "echo-category-title-thank-you-edit": "Edit {{PLURAL:$1|milestone|milestones}}", "echo-category-title-watchlist": "Edit to watched page", "echo-category-title-minor-watchlist": "Minor edit to watched page", + "echo-category-title-api-triggered": "API triggered notifications", "echo-pref-tooltip-edit-user-talk": "Notify me when someone edits my user talk page.", "echo-pref-tooltip-edit-user-page": "Notify me when someone edits my user page.", "echo-pref-tooltip-article-linked": "Notify me when someone links to a page I created from another page.", @@ -96,6 +97,7 @@ "echo-pref-tooltip-thank-you-edit": "Notify me when I reach my 1st, 10th, 100th... edit.", "echo-pref-tooltip-watchlist": "Notify me when someone makes a (non-minor) edit to a page on my watchlist.", "echo-pref-tooltip-minor-watchlist": "Notify me when someone makes a minor edit to a page on my watchlist.", + "echo-pref-tooltip-api-triggered": "Send me notifications triggered by bots or gadgets via the API.", "notifications": "Notifications", "tooltip-pt-notifications-alert": "{{GENDER:|Your}} alerts", "tooltip-pt-notifications-notice": "{{GENDER:|Your}} notices", @@ -202,6 +204,8 @@ "notification-header-watchlist-multiuser-deleted": "$1, a page on {{GENDER:$2|your}} watchlist, was deleted $3 {{PLURAL:$3|time|times}}.", "notification-header-watchlist-multiuser-moved": "$1, a page on {{GENDER:$2|your}} watchlist, was moved $3 {{PLURAL:$3|time|times}}.", "notification-header-watchlist-multiuser-restored": "$1, a page on {{GENDER:$2|your}} watchlist, was restored $3 {{PLURAL:$3|time|times}}.", + "notification-header-api-triggered": "$1", + "notification-tooltip-api-triggered": "This notification was sent to you by $1", "notification-body-watchlist-once": "There will be no other email notifications in case of further activity unless {{GENDER:$1|you visit}} this page while logged in.", "notification-welcome-link": "", "notification-welcome-linktext": "Welcome", @@ -216,10 +220,12 @@ "notification-link-thank-you-edit": "{{GENDER:$1|Your}} edit", "notification-link-text-view-edit": "View edit", "notification-link-article-reminder": "View page", + "notification-link-api-triggered": "View page", "notification-header-reverted": "Your {{PLURAL:$4|edit on $3 was|edits on $3 were}} {{GENDER:$2|reverted}}.", "notification-body-reverted": "$1", "notification-header-emailuser": "$1 {{GENDER:$2|sent}} you an email.", "notification-body-emailuser": "$1", + "notification-body-api-triggered": "$1", "notification-edit-user-page-email-subject": "$1 {{GENDER:$2|edited}} {{GENDER:$3|your}} user page on {{SITENAME}}", "notification-edit-talk-page-email-subject2": "$1 {{GENDER:$2|left}} {{GENDER:$3|you}} a message on {{SITENAME}}", "notification-page-linked-email-subject": "A page {{GENDER:$3|you}} created was linked on {{SITENAME}}", @@ -268,6 +274,8 @@ "echo-foreign-wiki-lang": "$1 - $2", "echo-badge-count": "{{PLURAL:$1|$1|100={{formatnum:99}}+}}", "echo-blacklist": "", + "right-echo-create": "Send notifications to others", + "action-echo-create": "send notifications to others", "right-manage-all-push-subscriptions": "Manage all push subscriptions", "action-manage-all-push-subscriptions": "manage all push subscriptions", "group-push-subscription-manager": "Push subscription managers", diff --git a/i18n/qqq.json b/i18n/qqq.json index 1914bbaad..363d33dfc 100644 --- a/i18n/qqq.json +++ b/i18n/qqq.json @@ -87,6 +87,7 @@ "echo-category-title-thank-you-edit": "'''Note that this doesn't mean \"to edit milestones\", but \"milestones of editing\".'''\n\n{{doc-echo-category-title|tooltip=Echo-pref-tooltip-thank-you-edit}}", "echo-category-title-watchlist": "{{doc-echo-category-title|tooltip=Echo-pref-tooltip-watchlist}}", "echo-category-title-minor-watchlist": "{{doc-echo-category-title|tooltip=Echo-pref-tooltip-minor-watchlist}}", + "echo-category-title-api-triggered": "{{doc-echo-category-title|tooltip=Echo-pref-tooltip-api-triggered}}", "echo-pref-tooltip-edit-user-talk": "{{doc-echo-pref-tooltip|title=Echo-category-title-edit-user-talk}}", "echo-pref-tooltip-edit-user-page": "{{doc-echo-pref-tooltip|title=Echo-category-title-edit-user-page}}", "echo-pref-tooltip-article-linked": "{{doc-echo-pref-tooltip|title=Echo-category-title-article-linked}}", @@ -100,6 +101,7 @@ "echo-pref-tooltip-thank-you-edit": "{{doc-echo-pref-tooltip|title=Echo-category-title-thank-you-edit}}", "echo-pref-tooltip-watchlist": "{{doc-echo-pref-tooltip|title=Echo-category-title-watchlist}}", "echo-pref-tooltip-minor-watchlist": "{{doc-echo-pref-tooltip|title=Echo-category-title-minor-watchlist}}", + "echo-pref-tooltip-api-triggered": "{{doc-echo-pref-tooltip|title=Echo-category-title-api-triggered}}", "notifications": "{{doc-special|Notifications}}\n{{Identical|Notification}}", "tooltip-pt-notifications-alert": "This is used for the title (mouseover text) of the alert notifications user tool.", "tooltip-pt-notifications-notice": "This is used for the title (mouseover text) of the notice notifications user tool.", @@ -206,6 +208,8 @@ "notification-header-watchlist-multiuser-deleted": "Text of a notification when multiple different users delete pages on your watchlist.\n* $1 - name of the page that was deleted (with namespace) \n* $2 - name of the user viewing the notification, can be used for GENDER \n* $3 - Number of times the page has been deleted. (Always greater than one)", "notification-header-watchlist-multiuser-moved": "Text of a notification when multiple different users move pages on your watchlist.\n* $1 - name of the page that was moved (with namespace) \n* $2 - name of the user viewing the notification, can be used for GENDER \n* $3 - Number of times the page has been moved. (Always greater than one)", "notification-header-watchlist-multiuser-restored": "Text of a notification when multiple different users restore pages on your watchlist.\n* $1 - name of the page that was restored (with namespace) \n* $2 - name of the user viewing the notification, can be used for GENDER \n* $3 - Number of times the page has been restored. (Always greater than one)", + "notification-header-api-triggered": "Header text for a notification triggered via API.\n* $1 - header specified in API request\n* $2 - triggering user or bot", + "notification-tooltip-api-triggered": "Tooltip text for a notification triggered via API.\n* $1 - triggering user or bot", "notification-body-watchlist-once": "Text added to email notifications of watchlist changes to specify that no further emails will be sent for that page until it is visited. \n* $1 - user's name for use in GENDER", "notification-welcome-link": "{{notranslate}}", "notification-welcome-linktext": "Link text for link to the wiki's welcome or introduction page.\n{{Identical|Welcome}}", @@ -220,10 +224,12 @@ "notification-link-thank-you-edit": "Label for the link to the user's edit which triggered a threshold congratulations message.\nParameters:\n* $1 - the username for gender purposes", "notification-link-text-view-edit": "Label for button that links to a \"diff\" view showing an edit made to a page. This is an alternative to the wording in {{msg-mw|notification-link-text-view-changes}}, which serves essentially the same function.", "notification-link-article-reminder": "Label for the link to the article the user requested to be reminded about", + "notification-link-api-triggered": "Label for the link to the article included in the API notification", "notification-header-reverted": "Notification header of a user's edit has being reverted by other user.\nParameters:\n* $1 - the formatted username of the person who reverted the edit, using the undo or rollback features (not suitable for GENDER).\n* $2 - the username for GENDER\n* $3 - the page that was reverted, formatted\n* $4 - the number of edits that were reverted\n\nWhen the notified user clicks on the notification message, this appoint to a diff page.\n{{Related|Notification-reverted}}", "notification-body-reverted": "{{notranslate}}", "notification-header-emailuser": "Flyout-specific format for displaying notifications of user has sent an email to another user.\n\nParameters:\n* $1 - the formatted username of the person who sent the email.\n* $2 - the username for GENDER.", "notification-body-emailuser": "{{notranslate}}", + "notification-body-api-triggered": "{{notranslate}}", "notification-edit-user-page-email-subject": "Email subject. Parameters:\n* $1 - the formatted username of the person who edited (not suitable for GENDER).\n* $2 - the username for GENDER.\n* $3 - username of the current user, can be used for GENDER.", "notification-edit-talk-page-email-subject2": "Email subject. Parameters:\n* $1 - the formatted username of the person who edited (not suitable for GENDER).\n* $2 - the username for GENDER.\n* $3 - username of the current user, can be used for GENDER.", "notification-page-linked-email-subject": "E-mail subject Parameters:\n* $1 - the formatted username of the person who edited (not suitable for GENDER).\n* $2 - the username for GENDER.\n* $3 - username of the current user, can be used for GENDER.", @@ -272,6 +278,8 @@ "echo-foreign-wiki-lang": "{{optional}}\nTitle of the wiki for which you're seeing foreign notifications:\n\nParameters:\n* $1 - the name of the site (e.g. 'Wikipedia', 'Wikiversity', ...)\n* $2 - the language of the website (e.g. 'English', 'Deutsch', ...)", "echo-badge-count": "{{optional}}\nUsed only for the two Echo badges shown on the personal toolbar.\nParameters:\n* $1 - Formatted notification count. However, for 100 or greater, 100 will be passed.", "echo-blacklist": "{{ignored}} Site-wide list of accounts that cannot trigger notifications. Can be overridden by users at [[Special:MyPage/Echo-whitelist]].", + "right-echo-create": "{{doc-right|echo-create}}", + "action-echo-create": "{{doc-action|echo-create}}", "right-manage-all-push-subscriptions": "{{doc-right|manage-all-push-subscriptions}}", "action-manage-all-push-subscriptions": "{{doc-action|manage-all-push-subscriptions}}", "group-push-subscription-manager": "{{doc-group|push-subscription-manager}}", diff --git a/includes/Api/ApiEchoCreateEvent.php b/includes/Api/ApiEchoCreateEvent.php new file mode 100644 index 000000000..f5f6a0732 --- /dev/null +++ b/includes/Api/ApiEchoCreateEvent.php @@ -0,0 +1,152 @@ +userNameUtils = $userNameUtils; + } + + /** + * @see ApiBase::execute() + * @return void + */ + public function execute() { + if ( !$this->getConfig()->get( 'EchoEnableApiEvents' ) ) { + $this->dieWithError( [ 'apierror-moduledisabled', $this->getModuleName() ] ); + } + + // Only for logged in users + $user = $this->getUser(); + if ( !$user->isNamed() ) { + $this->dieWithError( 'apierror-mustbeloggedin-generic', 'login-required' ); + } + + $params = $this->extractRequestParams(); + + // Default to self if unspecified + /** @var UserIdentity $userToNotify */ + $userToNotify = $params['user'] ?? $user; + + if ( $userToNotify->getName() !== $user->getName() ) { + $this->checkUserRightsAny( 'echo-create' ); + } + + if ( !$userToNotify->isRegistered() || $this->userNameUtils->isTemp( $userToNotify->getName() ) ) { + $this->dieWithError( [ 'nosuchusershort', $userToNotify->getName() ] ); + } + + $event = Event::create( [ + // type is one of api-notice, api-alert + 'type' => 'api-' . $params['section'], + 'agent' => $user, + 'title' => $params['page'] ? Title::newFromLinkTarget( $params['page'] ) : null, + 'extra' => [ + 'recipients' => [ $userToNotify->getId() ], + 'header' => $params['header'], + 'content' => $params['content'], + // Send email only if specified + 'noemail' => !$params['email'], + ] + ] ); + + // Return a success message + $this->getResult()->addValue( + null, + $this->getModuleName(), + [ + 'result' => 'success' + ] + ); + } + + /** + * @see ApiBase::needsToken() + * @return bool + */ + public function mustBePosted() { + return true; + } + + public function isWriteMode() { + return true; + } + + public function needsToken() { + return 'csrf'; + } + + /** + * @see ApiBase::getAllowedParams() + * @return array + */ + public function getAllowedParams() { + return [ + 'user' => [ + ParamValidator::PARAM_TYPE => 'user', + UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'id' ], + UserDef::PARAM_RETURN_OBJECT => true, + ], + 'header' => [ + ParamValidator::PARAM_REQUIRED => true, + ParamValidator::PARAM_TYPE => 'string', + StringDef::PARAM_MAX_BYTES => 160, + ], + 'content' => [ + ParamValidator::PARAM_REQUIRED => true, + ParamValidator::PARAM_TYPE => 'string', + StringDef::PARAM_MAX_BYTES => 5000, + ], + 'page' => [ + ParamValidator::PARAM_TYPE => 'title', + TitleDef::PARAM_RETURN_OBJECT => true, + ], + 'section' => [ + ParamValidator::PARAM_REQUIRED => true, + ParamValidator::PARAM_TYPE => [ 'alert', 'notice' ], + ParamValidator::PARAM_DEFAULT => 'notice', + ], + 'email' => [ + ParamValidator::PARAM_TYPE => 'boolean', + ParamValidator::PARAM_DEFAULT => false, + ], + ]; + } + + /** + * @see ApiBase::getExamplesMessages() + * @return string[] + */ + protected function getExamplesMessages() { + return [ + 'action=echocreateevent&header=Hi&content=From_API' => 'apihelp-echocreateevent-example', + ]; + } + + /** + * @see ApiBase::getHelpUrls() + * @return string + */ + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/Special:MyLanguage/Echo_(Notifications)/API'; + } +} diff --git a/includes/Formatters/EchoManualPresentationModel.php b/includes/Formatters/EchoManualPresentationModel.php new file mode 100644 index 000000000..639274e2b --- /dev/null +++ b/includes/Formatters/EchoManualPresentationModel.php @@ -0,0 +1,51 @@ +msg( 'notification-header-api-triggered' ) + ->plaintextParams( $this->event->getExtraParam( 'header' ), $this->event->getAgent()->getName() ); + } + + /** @inheritDoc */ + public function getSubjectMessage() { + return $this->getHeaderMessage(); + } + + /** @inheritDoc */ + public function getPrimaryLink() { + if ( !$this->event->getTitle() ) { + return false; + } + return [ + 'url' => $this->event->getTitle()->getLocalURL(), + 'label' => $this->msg( 'notification-link-api-triggered' )->text(), + ]; + } + + /** @inheritDoc */ + public function getBodyMessage() { + $content = $this->event->getExtraParam( 'content' ); + + // Content here passed through plaintextParams for sanitization + return $content ? $this->msg( 'notification-body-api-triggered' )->plaintextParams( $content ) : false; + } + + /** @inheritDoc */ + public function getSecondaryLinks() { + $agentLink = $this->getAgentLink(); + $agentLink['tooltip'] = $this->msg( 'notification-tooltip-api-triggered', + $this->event->getAgent()->getName() ); + return [ + $agentLink + ]; + } +} diff --git a/includes/Hooks.php b/includes/Hooks.php index 6f33fe844..7e081495b 100644 --- a/includes/Hooks.php +++ b/includes/Hooks.php @@ -204,6 +204,10 @@ class Hooks implements 'minor-watchlist' => [ 'web' => false, ], + 'api-triggered' => [ + // emails are sent only if sender also sets the API option, which is disabled by default + 'email' => true, + ], ]; $echoPushEnabled = $this->config->get( ConfigNames::EnablePush ); @@ -241,7 +245,7 @@ class Hooks implements global $wgEchoNotifications, $wgEchoNotificationCategories, $wgEchoNotificationIcons, $wgEchoMentionStatusNotifications, $wgAllowArticleReminderNotification, $wgAPIModules, $wgEchoWatchlistNotifications, $wgEchoSeenTimeCacheType, $wgMainStash, $wgEnableEmail, - $wgEnableUserEmail; + $wgEnableUserEmail, $wgEchoEnableApiEvents; // allow extensions to define their own event ( new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )->onBeforeCreateEchoEvent( @@ -270,6 +274,11 @@ class Hooks implements unset( $wgEchoNotificationCategories['emailuser'] ); } + // Only allow API-triggered notifications when enabled + if ( !$wgEchoEnableApiEvents ) { + unset( $wgEchoNotificationCategories['api-triggered'] ); + } + // Default $wgEchoSeenTimeCacheType to $wgMainStash if ( $wgEchoSeenTimeCacheType === null ) { $wgEchoSeenTimeCacheType = $wgMainStash; diff --git a/includes/Notifier.php b/includes/Notifier.php index 3be5170bf..a955705d9 100644 --- a/includes/Notifier.php +++ b/includes/Notifier.php @@ -80,6 +80,10 @@ class Notifier { return false; } } + } elseif ( $event->getExtraParam( 'noemail' ) ) { + // Could be set for API triggered notifications were email is not + // requested in API request params + return false; } $hookRunner = new HookRunner( $services->getHookContainer() ); diff --git a/modules/icons/robot.svg b/modules/icons/robot.svg new file mode 100644 index 000000000..cc195976b --- /dev/null +++ b/modules/icons/robot.svg @@ -0,0 +1,7 @@ + + + + robot + + + diff --git a/tests/phpunit/Api/ApiEchoCreateEventTest.php b/tests/phpunit/Api/ApiEchoCreateEventTest.php new file mode 100644 index 000000000..ebcb8512f --- /dev/null +++ b/tests/phpunit/Api/ApiEchoCreateEventTest.php @@ -0,0 +1,83 @@ +overrideConfigValue( 'EchoEnableApiEvents', true ); + } + + public function testNotifySelf() { + $result = $this->doApiRequestWithToken( [ + 'action' => 'echocreateevent', + 'user' => $this->getTestUser()->getUser()->getName(), + 'header' => 'notification header', + 'content' => 'notification content', + ], null, $this->getTestUser()->getUser() ); + + $this->assertEquals( 'success', $result[0]['echocreateevent']['result'] ); + + $user = NotifUser::newFromUser( $this->getTestUser()->getUser() ); + $this->assertSame( 1, $user->getNotificationCount() ); + + $mapper = new NotificationMapper(); + $notifs = $mapper->fetchByUser( $this->getTestUser()->getUser(), 5, null, [ 'api-notice' ] ); + $this->assertCount( 1, $notifs ); + $notif = array_values( $notifs )[0]; + $this->assertSame( 'notification header', $notif->getEvent()->getExtraParam( 'header' ) ); + $this->assertSame( 'notification content', $notif->getEvent()->getExtraParam( 'content' ) ); + $this->assertTrue( $notif->getEvent()->getExtraParam( 'noemail' ) ); + } + + public function testAlertWithEmail() { + $result = $this->doApiRequestWithToken( [ + 'action' => 'echocreateevent', + 'user' => $this->getTestUser()->getUser()->getName(), + 'header' => 'notification header', + 'content' => 'notification content', + 'email' => true, + 'section' => 'alert' + ], null, $this->getTestUser()->getUser() ); + $this->assertEquals( 'success', $result[0]['echocreateevent']['result'] ); + + $mapper = new NotificationMapper(); + $notifs = $mapper->fetchByUser( $this->getTestUser()->getUser(), 5, null, [ 'api-alert' ] ); + $this->assertFalse( array_values( $notifs )[0]->getEvent()->getExtraParam( 'noemail' ) ); + } + + public function testNotifyOthers() { + $this->setGroupPermissions( 'sysop', 'echo-create', true ); + + $result = $this->doApiRequestWithToken( [ + 'action' => 'echocreateevent', + 'user' => $this->getTestUser()->getUser()->getName(), + 'header' => 'notification header', + 'content' => 'notification content', + ], null, $this->getTestSysop()->getUser() ); + + $this->assertEquals( 'success', $result[0]['echocreateevent']['result'] ); + + $user = NotifUser::newFromUser( $this->getTestUser()->getUser() ); + $this->assertSame( 1, $user->getNotificationCount() ); + } + + public function testNotifyOthersWithoutPermission() { + try { + $this->doApiRequestWithToken( [ + 'action' => 'echocreateevent', + 'user' => $this->getTestUser()->getUser()->getName(), + 'header' => 'notification header', + 'content' => 'notification content', + ], null, $this->getTestSysop()->getUser() ); + } catch ( ApiUsageException $ex ) { + $this->assertApiErrorCode( 'permissiondenied', $ex ); + } + } +}