Dpatrick has submitted this change and it was merged.

Change subject: Send notifications about app management events
......................................................................


Send notifications about app management events

Send an Echo notification to all OAuth admins when a new app is
proposed or an existing one updated, and a notification to the app
owner when an app is approved, rejected, disabled or reenabled.

Bug: T62528
Bug: T61772
Change-Id: Iee6e0074d648236b01ae40da704a27cfb94d4748
Depends-On: Idbaf2102410f82343ad38ff51ccb7ab70b15e654
---
M OAuth.config.php
M OAuth.setup.php
M backend/MWOAuthConsumer.php
M backend/MWOAuthUtils.php
M backend/schema/mysql/OAuth.sql
M control/MWOAuthConsumerSubmitControl.php
A frontend/EchoOAuthStageChangePresentationModel.php
M frontend/MWOAuthUI.hooks.php
M frontend/MWOAuthUI.setup.php
A frontend/assets/echo-icon.png
M i18n/en.json
M i18n/qqq.json
12 files changed, 336 insertions(+), 70 deletions(-)

Approvals:
  Dpatrick: Verified; Looks good to me, approved



diff --git a/OAuth.config.php b/OAuth.config.php
index 3ff16fc..7347ece 100644
--- a/OAuth.config.php
+++ b/OAuth.config.php
@@ -56,6 +56,11 @@
 
 $wgGroupPermissions['user']['mwoauthmanagemygrants'] = true;
 
+$wgDefaultUserOptions['echo-subscriptions-web-oauth-owner'] = true;
+$wgDefaultUserOptions['echo-subscriptions-email-oauth-owner'] = true;
+$wgDefaultUserOptions['echo-subscriptions-web-oauth-admin'] = true;
+$wgDefaultUserOptions['echo-subscriptions-email-oauth-admin'] = true;
+
 /** @var bool Require HTTPs for user transactions that might send out secret 
tokens */
 $wgMWOAuthSecureTokenTransfer = true; // RfC compliance
 
@@ -80,5 +85,10 @@
  */
 $wgOAuthSecretKey = $wgSecretKey;
 
+/**
+ * @var string[] User groups to notify about new consumers that need to be 
reviewed.
+ */
+$wgOAuthGroupsToNotify = [];
+
 # End of configuration variables.
 # ########
diff --git a/OAuth.setup.php b/OAuth.setup.php
index 2443c32..b718b21 100644
--- a/OAuth.setup.php
+++ b/OAuth.setup.php
@@ -77,6 +77,10 @@
                $classes['MediaWiki\Extensions\OAuth\MWOAuthServer'] = 
"$backendDir/MWOAuthServer.php"; // "MWOAuth1Protocol"?
                
$classes['MediaWiki\Extensions\OAuth\MWOAuthSignatureMethod_RSA_SHA1'] = 
"$backendDir/MWOAuthSignatureMethod.php";
 
+               # Echo
+               
$classes['MediaWiki\Extensions\OAuth\EchoOAuthStageChangePresentationModel'] =
+                       
"$frontendDir/EchoOAuthStageChangePresentationModel.php";
+
                # Library
                $classes['MediaWiki\Extensions\OAuth\OAuthException'] = 
"$libDir/OAuth.php";
                $classes['MediaWiki\Extensions\OAuth\OAuthConsumer'] = 
"$libDir/OAuth.php";
diff --git a/backend/MWOAuthConsumer.php b/backend/MWOAuthConsumer.php
index 9bbd21d..2289888 100644
--- a/backend/MWOAuthConsumer.php
+++ b/backend/MWOAuthConsumer.php
@@ -38,7 +38,8 @@
        protected $consumerKey;
        /** @var string Name of connected application */
        protected $name;
-       /** @var integer Publisher user ID (on central wiki) */
+       /** @var integer Publisher's central user ID. $wgMWOAuthSharedUserIDs 
defines which central ID
+        *    provider to use. */
        protected $userId;
        /** @var string Version used for handshake breaking changes */
        protected $version;
diff --git a/backend/MWOAuthUtils.php b/backend/MWOAuthUtils.php
index 7df04fb..23a54a5 100644
--- a/backend/MWOAuthUtils.php
+++ b/backend/MWOAuthUtils.php
@@ -2,8 +2,9 @@
 
 namespace MediaWiki\Extensions\OAuth;
 
+use EchoEvent;
 use Hooks;
-use MWException;
+use User;
 
 /**
  * Static utility functions for OAuth
@@ -458,4 +459,55 @@
                return \MWGrants::grantsAreValid( $grants );
        }
 
+       /**
+        * Given an OAuth consumer stage change event, find out who needs to be 
notified.
+        * Will be used as an EchoAttributeManager::ATTR_LOCATORS callback.
+        * @param EchoEvent $event
+        * @return User[]
+        */
+       public static function locateUsersToNotify( EchoEvent $event ) {
+               $agent = $event->getAgent();
+               $owner = MWOAuthUtils::getLocalUserFromCentralId( 
$event->getExtraParam( 'owner-id' ) );
+
+               $users = [];
+               switch ( $event->getType() ) {
+                       case 'oauth-app-propose':
+                               // notify OAuth admins about new proposed apps
+                               $oauthAdmins = self::getOAuthAdmins();
+                               foreach ( $oauthAdmins as $admin ) {
+                                       if ( $admin->equals( $owner ) ) {
+                                               continue;
+                                       }
+                                       $users[$admin->getId()] = $admin;
+                               }
+                               break;
+                       case 'oauth-app-update':
+                       case 'oauth-app-approve':
+                       case 'oauth-app-reject':
+                       case 'oauth-app-disable':
+                       case 'oauth-app-reenable':
+                               // notify owner if someone else changed the 
status of the app
+                               if ( !$owner->equals( $agent ) ) {
+                                       $users[$owner->getId()] = $owner;
+                               }
+                               break;
+               }
+               return $users;
+       }
+
+       /**
+        * Return a list of all OAuth admins (or the first 5000 in the unlikely 
case that there is more
+        * than that).
+        * Should be called on the central OAuth wiki.
+        * @return User[]
+        */
+       protected static function getOAuthAdmins() {
+               global $wgOAuthGroupsToNotify;
+
+               if ( !$wgOAuthGroupsToNotify ) {
+                       return [];
+               }
+
+               return iterator_to_array( User::findUsersByGroup( 
$wgOAuthGroupsToNotify ) );
+       }
 }
diff --git a/backend/schema/mysql/OAuth.sql b/backend/schema/mysql/OAuth.sql
index 4edb72b..b0c99d0 100644
--- a/backend/schema/mysql/OAuth.sql
+++ b/backend/schema/mysql/OAuth.sql
@@ -13,7 +13,7 @@
     oarc_consumer_key varbinary(32) NOT NULL,
     -- Name of the application
     oarc_name varchar(128) binary NOT NULL,
-    -- Key to the user who proposed the application
+    -- (Central) user id of the user who proposed the application
     oarc_user_id integer unsigned NOT NULL,
     -- Version of the application
     oarc_version varbinary(32) NOT NULL,
diff --git a/control/MWOAuthConsumerSubmitControl.php 
b/control/MWOAuthConsumerSubmitControl.php
index 7b49fc4..957fed2 100644
--- a/control/MWOAuthConsumerSubmitControl.php
+++ b/control/MWOAuthConsumerSubmitControl.php
@@ -30,6 +30,13 @@
  * @TODO: improve error messages
  */
 class MWOAuthConsumerSubmitControl extends MWOAuthSubmitControl {
+       /**
+        * Names of the actions that can be performed on a consumer. These are 
the same as the
+        * options in getRequiredFields().
+        * @var array
+        */
+       public static $actions = array( 'propose', 'update', 'approve', 
'reject', 'disable', 'reenable' );
+
        /** @var \DBConnRef */
        protected $dbw;
 
@@ -241,18 +248,12 @@
                        );
                        $cmr->save( $dbw );
 
-                       $logEntry = new \ManualLogEntry(
-                               'mwoauthconsumer',
-                               $cmr->get( 'ownerOnly' ) ? 'create-owner-only' 
: 'propose'
-                       );
-                       $logEntry->setPerformer( $user );
-                       $logEntry->setTarget( $this->getLogTitle( $dbw, 
$cmr->get( 'userId' ) ) );
-                       $logEntry->setComment( $this->vals['description'] );
-                       $logEntry->setParameters( array( '4:consumer' => 
$cmr->get( 'consumerKey' ) ) );
-                       $logEntry->setRelations( array(
-                               'OAuthConsumer' => array( $cmr->get( 
'consumerKey' ) )
-                       ) );
-                       $logEntry->insert( $dbw );
+                       if ( $cmr->get( 'ownerOnly' ) ) {
+                               $this->makeLogEntry( $dbw, $cmr, 
'create-owner-only', $user, $this->vals['description'] );
+                       } else {
+                               $this->makeLogEntry( $dbw, $cmr, $action, 
$user, $this->vals['description'] );
+                               $this->notify( $cmr, $user, $action,  null );
+                       }
 
                        // If it's owner-only, automatically accept it for the 
user too.
                        $cmra = null;
@@ -302,15 +303,8 @@
 
                        // Log if something actually changed
                        if ( $cmr->save( $dbw ) ) {
-                               $logEntry = new \ManualLogEntry( 
'mwoauthconsumer', 'update' );
-                               $logEntry->setPerformer( $user );
-                               $logEntry->setTarget( $this->getLogTitle( $dbw, 
$cmr->get( 'userId' ) ) );
-                               $logEntry->setComment( $this->vals['reason'] );
-                               $logEntry->setParameters( array( '4:consumer' 
=> $cmr->get( 'consumerKey' ) ) );
-                               $logEntry->setRelations( array(
-                                       'OAuthConsumer' => array( $cmr->get( 
'consumerKey' ) )
-                               ) );
-                               $logEntry->insert( $dbw );
+                               $this->makeLogEntry( $dbw, $cmr, $action, 
$user, $this->vals['reason'] );
+                               $this->notify( $cmr, $user, $action,  
$this->vals['reason'] );
                        }
 
                        $cmra = null;
@@ -366,17 +360,8 @@
 
                        // Log if something actually changed
                        if ( $cmr->save( $dbw ) ) {
-                               $logEntry = new \ManualLogEntry( 
'mwoauthconsumer', 'approve' );
-                               $logEntry->setPerformer( $user );
-                               $logEntry->setTarget( $this->getLogTitle( $dbw, 
$cmr->get( 'userId' ) ) );
-                               $logEntry->setComment( $this->vals['reason'] );
-                               $logEntry->setParameters( array( '4:consumer' 
=> $cmr->get( 'consumerKey' ) ) );
-                               $logEntry->setRelations( array(
-                                       'OAuthConsumer' => array( $cmr->get( 
'consumerKey' ) )
-                               ) );
-                               $logEntry->insert( $dbw );
-
-                               // @TODO: email/notifications?
+                               $this->makeLogEntry( $dbw, $cmr, $action, 
$user, $this->vals['reason'] );
+                               $this->notify( $cmr, $user, $action,  
$this->vals['reason'] );
                        }
 
                        return $this->success( $cmr );
@@ -405,17 +390,8 @@
 
                        // Log if something actually changed
                        if ( $cmr->save( $dbw ) ) {
-                               $logEntry = new \ManualLogEntry( 
'mwoauthconsumer', 'reject' );
-                               $logEntry->setPerformer( $user );
-                               $logEntry->setTarget( $this->getLogTitle( $dbw, 
$cmr->get( 'userId' ) ) );
-                               $logEntry->setComment( $this->vals['reason'] );
-                               $logEntry->setParameters( array( '4:consumer' 
=> $cmr->get( 'consumerKey' ) ) );
-                               $logEntry->setRelations( array(
-                                       'OAuthConsumer' => array( $cmr->get( 
'consumerKey' ) )
-                               ) );
-                               $logEntry->insert( $dbw );
-
-                               // @TODO: email/notifications?
+                               $this->makeLogEntry( $dbw, $cmr, $action, 
$user, $this->vals['reason'] );
+                               $this->notify( $cmr, $user, $action,  
$this->vals['reason'] );
                        }
 
                        return $this->success( $cmr );
@@ -446,17 +422,8 @@
 
                        // Log if something actually changed
                        if ( $cmr->save( $dbw ) ) {
-                               $logEntry = new \ManualLogEntry( 
'mwoauthconsumer', 'disable' );
-                               $logEntry->setPerformer( $user );
-                               $logEntry->setTarget( $this->getLogTitle( $dbw, 
$cmr->get( 'userId' ) ) );
-                               $logEntry->setComment( $this->vals['reason'] );
-                               $logEntry->setParameters( array( '4:consumer' 
=> $cmr->get( 'consumerKey' ) ) );
-                               $logEntry->setRelations( array(
-                                       'OAuthConsumer' => array( $cmr->get( 
'consumerKey' ) )
-                               ) );
-                               $logEntry->insert( $dbw );
-
-                               // @TODO: email/notifications?
+                               $this->makeLogEntry( $dbw, $cmr, $action, 
$user, $this->vals['reason'] );
+                               $this->notify( $cmr, $user, $action,  
$this->vals['reason'] );
                        }
 
                        return $this->success( $cmr );
@@ -483,17 +450,8 @@
 
                        // Log if something actually changed
                        if ( $cmr->save( $dbw ) ) {
-                               $logEntry = new \ManualLogEntry( 
'mwoauthconsumer', 'reenable' );
-                               $logEntry->setPerformer( $user );
-                               $logEntry->setTarget( $this->getLogTitle( $dbw, 
$cmr->get( 'userId' ) ) );
-                               $logEntry->setComment( $this->vals['reason'] );
-                               $logEntry->setParameters( array( '4:consumer' 
=> $cmr->get( 'consumerKey' ) ) );
-                               $logEntry->setRelations( array(
-                                       'OAuthConsumer' => array( $cmr->get( 
'consumerKey' ) )
-                               ) );
-                               $logEntry->insert( $dbw );
-
-                               // @TODO: email/notifications?
+                               $this->makeLogEntry( $dbw, $cmr, $action, 
$user, $this->vals['reason'] );
+                               $this->notify( $cmr, $user, $action,  
$this->vals['reason'] );
                        }
 
                        return $this->success( $cmr );
@@ -509,4 +467,50 @@
                $name = MWOAuthUtils::getCentralUserNameFromId( $userId );
                return \Title::makeTitleSafe( NS_USER, $name );
        }
+
+       /**
+        * @param \DBConnRef $dbw
+        * @param MWOAuthConsumer $cmr
+        * @param string $action
+        * @param \User $performer
+        * @param string $comment
+        */
+       protected function makeLogEntry( $dbw, MWOAuthConsumer $cmr, $action, 
\User $performer, $comment ) {
+               $logEntry = new \ManualLogEntry( 'mwoauthconsumer', $action );
+               $logEntry->setPerformer( $performer );
+               $logEntry->setTarget( $this->getLogTitle( $dbw, $cmr->get( 
'userId' ) ) );
+               $logEntry->setComment( $comment );
+               $logEntry->setParameters( array( '4:consumer' => $cmr->get( 
'consumerKey' ) ) );
+               $logEntry->setRelations( array(
+                       'OAuthConsumer' => array( $cmr->get( 'consumerKey' ) )
+               ) );
+               $logEntry->insert( $dbw );
+       }
+
+       /**
+        * @param MWOAuthConsumer $cmr Consumer which was the subject of the 
action
+        * @param \User $user User who performed the action
+        * @param string $actionType Action type
+        * @param string $comment
+        * @throws \MWException
+        */
+       protected function notify( $cmr, $user, $actionType, $comment ) {
+               if ( !in_array( $actionType, self::$actions, true ) ) {
+                       throw new \MWException( "Invalid action type: 
$actionType" );
+               }
+               if ( !class_exists( '\EchoEvent' ) ) {
+                       return;
+               }
+
+               \EchoEvent::create( [
+                       'type' => 'oauth-app-' . $actionType,
+                       'agent' => $user,
+                       'extra' => [
+                               'action' => $actionType,
+                               'app-key' => $cmr->get( 'consumerKey' ),
+                               'owner-id' => $cmr->get( 'userId' ),
+                               'comment' => $comment,
+                       ],
+               ] );
+       }
 }
diff --git a/frontend/EchoOAuthStageChangePresentationModel.php 
b/frontend/EchoOAuthStageChangePresentationModel.php
new file mode 100644
index 0000000..3137202
--- /dev/null
+++ b/frontend/EchoOAuthStageChangePresentationModel.php
@@ -0,0 +1,120 @@
+<?php
+
+namespace MediaWiki\Extensions\OAuth;
+
+use EchoAttributeManager;
+use EchoEventPresentationModel;
+use MediaWiki\Logger\LoggerFactory;
+use MWException;
+use User;
+use SpecialPage;
+
+class EchoOAuthStageChangePresentationModel extends EchoEventPresentationModel 
{
+       /** @var User[] OAuth admins who should be notified about additiions to 
the review queue */
+       protected static $oauthAdmins;
+
+       /** @var MWOAuthConsumer|false */
+       protected $consumer;
+
+       /** @var User|false The owner of the OAuth consumer */
+       protected $owner;
+
+       /**
+        * Helper function for $wgEchoNotifications
+        * @param string $action One of the actions from 
MWOAuthConsumerSubmitControl::$actions
+        * @return array
+        */
+       public static function getDefinition( $action ) {
+               if ( $action === 'propose' ) {
+                       // notify admins
+                       $category = 'oauth-admin';
+               } else {
+                       // notify owner
+                       $category = 'oauth-owner';
+               }
+
+               return [
+                       EchoAttributeManager::ATTR_LOCATORS => [ 
MWOAuthUtils::class . '::locateUsersToNotify' ],
+                       'category' => $category,
+                       'presentation-model' => self::class,
+                       'icon' => 'oauth',
+               ];
+       }
+
+       public function getHeaderMessage() {
+               $action = $this->event->getExtraParam( 'action' );
+               return $this->msg( "notification-oauth-app-$action-title",
+                       $this->event->getAgent(), $this->getConsumerName(), 
$this->getOwner() );
+       }
+
+       public function getSubjectMessage() {
+               $action = $this->event->getExtraParam( 'action' );
+               return $this->msg( "notification-oauth-app-$action-subject",
+                       $this->event->getAgent(), $this->getConsumerName(), 
$this->getOwner() );
+       }
+
+       public function getBodyMessage() {
+               $comment = $this->event->getExtraParam( 'comment' );
+               return $comment ? $this->msg( 'notification-oauth-app-body', 
$comment ) : false;
+       }
+
+       public function getIconType() {
+               return 'oauth';
+       }
+
+       public function getPrimaryLink() {
+               $consumerKey = $this->event->getExtraParam( 'app-key' );
+               $action = $this->event->getExtraParam( 'action' );
+
+               if ( $action === 'propose' ) {
+                       // show management interface
+                       $page = SpecialPage::getSafeTitleFor( 
'OAuthManageConsumers', $consumerKey );
+               } else {
+                       // show public view
+                       $page = SpecialPage::getSafeTitleFor( 
'OAuthListConsumers', "view/$consumerKey" );
+               }
+               if ( $page === null ) {
+                       throw new MWException( "Invalid app ID: $consumerKey" );
+               }
+
+               return [
+                       'url' => $page->getLocalURL(),
+                       'label' => $this->msg( 
"notification-oauth-app-$action-primary-link" )->text(),
+               ];
+       }
+
+       public function getSecondaryLinks() {
+               return [ $this->getAgentLink() ];
+       }
+
+       /**
+        * @return MWOAuthConsumer|false
+        */
+       protected function getConsumer() {
+               if ( $this->consumer === null ) {
+                       $dbr = MWOAuthUtils::getCentralDB( DB_SLAVE );
+                       $this->consumer =
+                               MWOAuthConsumer::newFromKey( $dbr, 
$this->event->getExtraParam( 'app-key' ) );
+               }
+               return $this->consumer;
+       }
+
+       /**
+        * @return User|false
+        */
+       protected function getOwner() {
+               if ( $this->owner === null ) {
+                       $this->owner = MWOAuthUtils::getLocalUserFromCentralId(
+                               $this->event->getExtraParam( 'owner-id' ) );
+               }
+               return $this->owner;
+       }
+
+       /**
+        * @return string
+        */
+       protected function getConsumerName() {
+               $consumer = $this->getConsumer();
+               return $consumer ? $consumer->get( 'name' ) : false;
+       }
+}
diff --git a/frontend/MWOAuthUI.hooks.php b/frontend/MWOAuthUI.hooks.php
index 268ab9b..d71d04d 100644
--- a/frontend/MWOAuthUI.hooks.php
+++ b/frontend/MWOAuthUI.hooks.php
@@ -173,4 +173,32 @@
                }
                return true;
        }
+
+       /**
+        * @param array $notifications
+        * @param array $notificationCategories
+        * @param array $icons
+        */
+       public static function onBeforeCreateEchoEvent( &$notifications, 
&$notificationCategories, &$icons ) {
+               global $wgOAuthGroupsToNotify;
+
+               $notificationCategories['oauth-owner'] = array(
+                       'tooltip' => 'echo-pref-tooltip-oauth-owner',
+               );
+
+               $notificationCategories['oauth-admin'] = array(
+                       'tooltip' => 'echo-pref-tooltip-oauth-admin',
+                       'usergroups' => $wgOAuthGroupsToNotify,
+               );
+
+               foreach ( MWOAuthConsumerSubmitControl::$actions as $eventName 
) {
+                       // oauth-app-propose and oauth-app-update notifies 
admins of the app.
+                       // oauth-app-approve, oauth-app-reject, 
oauth-app-disable and oauth-app-reenable
+                       // notify owner of the change.
+                       $notifications["oauth-app-$eventName"] =
+                               
EchoOAuthStageChangePresentationModel::getDefinition( $eventName );
+               }
+
+               $icons['oauth'] = array( 'path' => 
'OAuth/frontend/assets/echo-icon.png' );
+       }
 }
diff --git a/frontend/MWOAuthUI.setup.php b/frontend/MWOAuthUI.setup.php
index cf5a414..e4e00cb 100644
--- a/frontend/MWOAuthUI.setup.php
+++ b/frontend/MWOAuthUI.setup.php
@@ -22,6 +22,7 @@
                $wgHooks['MessagesPreLoad'][] = 
'MediaWiki\Extensions\OAuth\MWOAuthUIHooks::onMessagesPreLoad';
                $wgHooks['SpecialPageAfterExecute'][] = 
'MediaWiki\Extensions\OAuth\MWOAuthUIHooks::onSpecialPageAfterExecute';
                $wgHooks['SpecialPageBeforeFormDisplay'][] = 
'MediaWiki\Extensions\OAuth\MWOAuthUIHooks::onSpecialPageBeforeFormDisplay';
+               $wgHooks['BeforeCreateEchoEvent'][] = 
'MediaWiki\Extensions\OAuth\MWOAuthUIHooks::onBeforeCreateEchoEvent';
 
                $wgResourceModules['ext.MWOAuth.BasicStyles'] = array(
                        'position'              => 'top',
diff --git a/frontend/assets/echo-icon.png b/frontend/assets/echo-icon.png
new file mode 100644
index 0000000..675f789
--- /dev/null
+++ b/frontend/assets/echo-icon.png
Binary files differ
diff --git a/i18n/en.json b/i18n/en.json
index 7f0a75e..d6a0704 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -247,5 +247,28 @@
        "action-mwoauthupdateownconsumer": "update OAuth consumers you control",
        "action-mwoauthviewsuppressed": "view suppressed OAuth consumers",
        "mwoauth-tag-reserved": "Tags beginning with <code>OAuth CID:</code> 
are reserved for use by OAuth.",
-       "mwoauth-botpasswords-note": "<strong>Note:</strong> <span 
class=\"plainlinks\">[$1 OAuth]</span> is more secure than bot passwords and 
should be preferred whenever the bot supports it."
+       "mwoauth-botpasswords-note": "<strong>Note:</strong> <span 
class=\"plainlinks\">[$1 OAuth]</span> is more secure than bot passwords and 
should be preferred whenever the bot supports it.",
+       "echo-category-title-oauth-owner": "OAuth development",
+       "echo-pref-tooltip-oauth-owner": "Notify me about events related to 
OAuth applications I have created",
+       "echo-category-title-oauth-admin": "OAuth admin",
+       "echo-pref-tooltip-oauth-admin": "Notify me about events related to 
reviewing OAuth applications",
+       "notification-oauth-app-propose-title": "$1 {{GENDER:$1|proposed}} a 
new OAuth app: $2",
+       "notification-oauth-app-update-title": "$1 {{GENDER:$2|updated}} the 
OAuth app $2",
+       "notification-oauth-app-approve-title": "$1 {{GENDER:$2|approved}} 
{{GENDER:$3|your}} OAuth app ($2)",
+       "notification-oauth-app-reject-title": "$1 {{GENDER:$2|rejected}} 
{{GENDER:$3|your}} OAuth app ($2)",
+       "notification-oauth-app-disable-title": "$1 {{GENDER:$2|disabled}} 
{{GENDER:$3|your}} OAuth app ($2)",
+       "notification-oauth-app-reenable-title": "$1 {{GENDER:$2|reenabled}} 
{{GENDER:$3|your}} OAuth app ($2)",
+       "notification-oauth-app-propose-subject": "$1 {{GENDER:$1|proposed}} a 
new OAuth app on {{SITENAME}}",
+       "notification-oauth-app-update-subject": "$1 {{GENDER:$2|updated}} an 
OAuth app on {{SITENAME}}",
+       "notification-oauth-app-approve-subject": "$1 {{GENDER:$2|approved}} 
{{GENDER:$3|your}} OAuth app on {{SITENAME}}",
+       "notification-oauth-app-reject-subject": "$1 {{GENDER:$2|rejected}} 
{{GENDER:$3|your}} OAuth app on {{SITENAME}}",
+       "notification-oauth-app-disable-subject": "$1 {{GENDER:$2|disabled}} 
{{GENDER:$3|your}} OAuth app on {{SITENAME}}",
+       "notification-oauth-app-reenable-subject": "$1 {{GENDER:$2|reenabled}} 
{{GENDER:$3|your}} OAuth app on {{SITENAME}}",
+       "notification-oauth-app-propose-primary-link": "Review app",
+       "notification-oauth-app-update-primary-link": "Review app",
+       "notification-oauth-app-approve-primary-link": "View app",
+       "notification-oauth-app-reject-primary-link": "View app",
+       "notification-oauth-app-disable-primary-link": "View app",
+       "notification-oauth-app-reenable-primary-link": "View app",
+       "notification-oauth-app-body": "Reason: $1"
 }
diff --git a/i18n/qqq.json b/i18n/qqq.json
index 49f909a..fc35d71 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -255,5 +255,28 @@
        "action-mwoauthupdateownconsumer": 
"{{Doc-action|mwoauthupdateownconsumer}}",
        "action-mwoauthviewsuppressed": "{{Doc-action|mwoauthviewsuppressed}}",
        "mwoauth-tag-reserved": "Error message displayed on [[Special:Tags]] 
when a user attempts to manually create a change tag reserved by OAuth.",
-       "mwoauth-botpasswords-note": "Added to the top of 
[[Special:BotPasswords]] to point out that OAuth is a superior alternative. 
Parameters:\n* $1 - URL for [[Special:OAuthConsumerRegistration]], which might 
be on another wiki"
+       "mwoauth-botpasswords-note": "Added to the top of 
[[Special:BotPasswords]] to point out that OAuth is a superior alternative. 
Parameters:\n* $1 - URL for [[Special:OAuthConsumerRegistration]], which might 
be on another wiki",
+       "echo-category-title-oauth-owner": "{{doc-echo-category-title}}",
+       "echo-pref-tooltip-oauth-owner": "{{doc-echo-pref-tooltip}}",
+       "echo-category-title-oauth-admin": "{{doc-echo-category-title}}",
+       "echo-pref-tooltip-oauth-admin": "{{doc-echo-pref-tooltip}}",
+       "notification-oauth-app-propose-title": "Text of the notification sent 
to OAuth admins when a new app is proposed.\n* $1: app owner\n* $2: name of the 
app",
+       "notification-oauth-app-update-title": "Text of the notification sent 
to OAuth admins when the app is updated.\n* $1: app owner\n* $2: name of the 
app",
+       "notification-oauth-app-approve-title": "Text of the notification sent 
to OAuth app owner when the app is approved.\n* $1: approving admin\n* $2: name 
of the app\n* $3: app owner",
+       "notification-oauth-app-reject-title": "Text of the notification sent 
to OAuth app owner when the app is rejected.\n* $1: rejecting admin\n* $2: name 
of the app\n* $3: app owner",
+       "notification-oauth-app-disable-title": "Text of the notification sent 
to OAuth app owner when the app is disabled.\n* $1: disabling admin\n* $2: name 
of the app\n* $3: app owner",
+       "notification-oauth-app-reenable-title": "Text of the notification sent 
to OAuth app owner when the app is reenabled.\n* $1: reenabling admin\n* $2: 
name of the app\n* $3: app owner",
+       "notification-oauth-app-propose-subject": "Email subject of the 
notification sent to OAuth admins when a new app is proposed.\n* $1: app 
owner\n* $2: name of the app\n\nSee also: 
{{msg-mw|notification-oauth-app-propose-email-batch-body}}",
+       "notification-oauth-app-update-subject": "Email subject of the 
notification sent to OAuth admins when the app is updated.\n* $1: app owner\n* 
$2: name of the app\n\nSee also: 
{{msg-mw|notification-oauth-app-update-email-batch-body}}",
+       "notification-oauth-app-approve-subject": "Email subject of the 
notification sent to OAuth app owner when the app is approved.\n* $1: approving 
admin\n* $2: name of the app\n* $3: app owner\n\nSee also: 
{{msg-mw|notification-oauth-app-approve-email-batch-body}}",
+       "notification-oauth-app-reject-subject": "Email subject of the 
notification sent to OAuth app owner when the app is rejected.\n* $1: rejecting 
admin\n* $2: name of the app\n* $3: app owner\n\nSee also: 
{{msg-mw|notification-oauth-app-reject-email-batch-body}}",
+       "notification-oauth-app-disable-subject": "Email subject of the 
notification sent to OAuth app owner when the app is disabled.\n* $1: disabling 
admin\n* $2: name of the app\n* $3: app owner\n\nSee also: 
{{msg-mw|notification-oauth-app-disable-email-batch-body}}",
+       "notification-oauth-app-reenable-subject": "Email subject of the 
notification sent to OAuth app owner when the app is reenabled.\n* $1: 
reenabling admin\n* $2: name of the app\n* $3: app owner\n\nSee also: 
{{msg-mw|notification-oauth-app-reenable-email-batch-body}}",
+       "notification-oauth-app-propose-primary-link": "Text of the link which 
appears at the end of the email notification 
{{msg-mw|notification-oauth-app-propose-email-batch-body}}. It links to the app 
management page for OAuth admins.",
+       "notification-oauth-app-update-primary-link": "Text of the link which 
appears at the end of the email notification 
{{msg-mw|notification-oauth-app-update-email-batch-body}}. It links to the app 
management page for OAuth admins.",
+       "notification-oauth-app-approve-primary-link": "Text of the link which 
appears at the end of the email notification 
{{msg-mw|notification-oauth-app-approve-email-batch-body}}. It links to the app 
update page for app owners.",
+       "notification-oauth-app-reject-primary-link": "Text of the link which 
appears at the end of the email notification 
{{msg-mw|notification-oauth-app-reject-email-batch-body}}. It links to the app 
update page for app owners.",
+       "notification-oauth-app-disable-primary-link": "Text of the link which 
appears at the end of the email notification 
{{msg-mw|notification-oauth-app-disable-email-batch-body}}. It links to the app 
update page for app owners.",
+       "notification-oauth-app-reenable-primary-link": "Text of the link which 
appears at the end of the email notification 
{{msg-mw|notification-oauth-app-reenable-email-batch-body}}. It links to the 
app update page for app owners.",
+       "notification-oauth-app-body": "Text of the body of app stage change 
notifications. $1 is the reason given by the user who made the change."
 }

-- 
To view, visit https://gerrit.wikimedia.org/r/231989
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: Iee6e0074d648236b01ae40da704a27cfb94d4748
Gerrit-PatchSet: 11
Gerrit-Project: mediawiki/extensions/OAuth
Gerrit-Branch: master
Gerrit-Owner: Gergő Tisza <gti...@wikimedia.org>
Gerrit-Reviewer: Anomie <bjor...@wikimedia.org>
Gerrit-Reviewer: Brian Wolff <bawolff...@gmail.com>
Gerrit-Reviewer: Dpatrick <dpatr...@wikimedia.org>
Gerrit-Reviewer: Gergő Tisza <gti...@wikimedia.org>
Gerrit-Reviewer: Legoktm <legoktm.wikipe...@gmail.com>
Gerrit-Reviewer: Siebrand <siebr...@kitano.nl>
Gerrit-Reviewer: Springle <sprin...@wikimedia.org>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to