Wctaiwan has uploaded a new change for review. https://gerrit.wikimedia.org/r/115557
Change subject: Flow Thanks ...................................................................... Flow Thanks Add support for thanking other users for comments on Flow boards. bug: 61930 Change-Id: Id37a14e3c75c63419fc34e0b7c2e21f74b3fa875 Co-authored-by: Bencmq <[email protected]> --- A ApiFlowThank.php A FlowThanksFormatter.php M Thanks.hooks.php M Thanks.i18n.php M Thanks.php M modules/ext.thanks.thank.js 6 files changed, 410 insertions(+), 1 deletion(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Thanks refs/changes/57/115557/1 diff --git a/ApiFlowThank.php b/ApiFlowThank.php new file mode 100644 index 0000000..5b83642 --- /dev/null +++ b/ApiFlowThank.php @@ -0,0 +1,184 @@ +<?php +/** + * API module to send Flow thanks notifications + * + * @ingroup API + * @ingroup Extensions + */ +class ApiFlowThank extends ApiBase { + public function execute() { + $this->dieIfFlowNotInstalled(); + $this->dieIfEchoNotInstalled(); + + $user = $this->getUser(); + $this->dieOnBadUser( $user ); + + $params = $this->extractRequestParams(); + $postId = $params['post-id']; + + if ( $this->userAlreadySentThanksForPost( $user, $postId ) ) { + $this->markResultSuccess(); + return; + } + + $data = $this->getFlowData( $postId ); + + $recipient = $this->getRecipientFromPost( $data['post'] ); + $this->dieOnBadRecipient( $user, $recipient ); + + $rootPost = $data['root']; + $workflowId = $rootPost->getPostId(); + $topicTitleText = $rootPost->getContent(); + $pageTitle = $this->getPageTitleFromRootPost( $rootPost ); + + $this->sendThanks( + $user, + $recipient, + $postId, + $workflowId, + $topicTitleText, + $pageTitle + ); + } + + private function dieIfFlowNotInstalled() { + if ( !class_exists( 'FlowHooks' ) ) { + $this->dieUsage( 'Flow is not installed on this wiki', 'flownotinstalled' ); + } + } + + private function dieIfEchoNotInstalled() { + if ( !class_exists( 'EchoNotifier' ) ) { + $this->dieUsage( 'Echo is not installed on this wiki', 'echonotinstalled' ); + } + } + + private function dieOnBadUser( User $user ) { + if ( $user->isAnon() ) { + $this->dieUsage( 'Anonymous users cannot send thanks', 'notloggedin' ); + } elseif ( $user->pingLimiter( 'thanks-notification' ) ) { + $this->dieUsageMsg( array( 'actionthrottledtext' ) ); + } elseif ( $user->isBlocked() ) { + $this->dieUsageMsg( array( 'blockedtext' ) ); + } + } + + private function userAlreadySentThanksForPost( User $user, $postId ) { + return $user->getRequest()->getSessionData( "thanks-thanked-{$postId}" ); + } + + private function markResultSuccess() { + $this->getResult()->addValue( null, 'result', array( + 'success' => 1, + ) ); + } + + private function getFlowData( $postId ) { + $rootPostLoader = new Flow\Data\RootPostLoader( Flow\Container::get( 'storage' ), Flow\Container::get( 'repository.tree' ) ); + $data = $rootPostLoader->getWithRoot( $postId ); + if ( $data['post'] === null ) { + $this->dieUsage( 'Supplied post-id is invalid', 'invalidpostid' ); + } + return $data; + } + + private function getRecipientFromPost( $post ) { + $uid = $post->getCreatorId(); + $recipient = User::newFromId( $uid ); + if ( !$recipient->loadFromId() ) { + $this->dieUsage( 'Recipient is invalid', 'invalidrecipient' ); + } + return $recipient; + } + + private function dieOnBadRecipient( User $agent, User $recipient ) { + global $wgThanksSendToBots; + + if ( $agent->getId() === $recipient->getId() ) { + $this->dieUsage( 'You cannot thank yourself', 'invalidrecipient' ); + } elseif ( !$wgThanksSendToBots && in_array( 'bot', $recipient->getGroups() ) ) { + $this->dieUsage( 'Bots cannot be thanked', 'invalidrecipient' ); + } + } + + private function getPageTitleFromRootPost( $rootPost ) { + $workflow = Flow\Container::get( 'storage' )->get( 'Workflow', $rootPost->getPostId() ); + return $workflow->getArticleTitle(); + } + + private function sendThanks( User $user, User $recipient, $postId, $workflowId, $topicTitleText, Title $pageTitle ) { + global $wgThanksLogging; + + // Create the notification via Echo extension + EchoEvent::create( array( + 'type' => 'flow-thank', + 'title' => $pageTitle, + 'extra' => array( + 'post-id' => $postId, + 'workflow' => $workflowId, + 'thanked-user-id' => $recipient->getId(), + 'topic-title' => $topicTitleText, + ), + 'agent' => $user, + ) ); + + // Mark the thank in session to prevent duplicates (Bug 46690) + $user->getRequest()->setSessionData( "thanks-thanked-{$postId}", true ); + // Set success message + $this->markResultSuccess(); + // Log it if we're supposed to log it + if ( $wgThanksLogging ) { + $logEntry = new ManualLogEntry( 'thanks', 'thank' ); + $logEntry->setPerformer( $user ); + $target = $recipient->getUserPage(); + $logEntry->setTarget( $target ); + $logId = $logEntry->insert(); + $logEntry->publish( $logId, 'udp' ); + } + } + + public function getDescription() { + return array( + 'This API is for sending thank you notifications for Flow comments.', + ); + } + + public function getParamDescription() { + return array( + 'post-id' => 'The UUID of the post to thank for', + 'token' => 'An edit token (to prevent CSRF abuse)', + ); + } + + public function getAllowedParams() { + return array( + 'post-id' => array( + ApiBase::PARAM_TYPE => 'string', + ApiBase::PARAM_REQUIRED => true, + ), + 'token' => array( + ApiBase::PARAM_TYPE => 'string', + ApiBase::PARAM_REQUIRED => true, + ), + ); + } + + public function needsToken() { + return true; + } + + // Writes to the Echo database and sometimes log tables. + public function isWriteMode() { + return true; + } + + public function getTokenSalt() { + return ''; + } + + public function getHelpUrls() { + return array( + 'https://www.mediawiki.org/wiki/Extension:Thanks#API_Documentation', + ); + } +} diff --git a/FlowThanksFormatter.php b/FlowThanksFormatter.php new file mode 100644 index 0000000..17b607b --- /dev/null +++ b/FlowThanksFormatter.php @@ -0,0 +1,56 @@ +<?php + +class EchoFlowThanksFormatter extends EchoBasicFormatter { + + /** + * @param $event EchoEvent + * @param $param + * @param $message Message + * @param $user User + */ + protected function processParam( $event, $param, $message, $user ) { + if ( $param === 'postlink' ) { + $eventData = $event->getExtra(); + $this->setTitleLink( + $event, + $message, + array( + 'class' => 'mw-echo-diff', + 'linkText' => wfMessage( 'notification-flow-thanks-post-link' )->text(), + 'param' => array( + 'workflow' => $eventData['workflow'], + ), + 'fragment' => "flow-post-{$eventData['post-id']}", + ) + ); + } elseif ( $param === 'topictitle' ) { + $eventData = $event->getExtra(); + $message->params( $eventData['topic-title'] ); + } else { + parent::processParam( $event, $param, $message, $user ); + } + } + + /** + * Overriding implementation in EchoBasicFormatter to support Flow posts + * + * @param EchoEvent $event + * @param User $user The user receiving the notification + * @param String $destination The destination type for the link, e.g. 'agent' + * @return Array including target and query parameters + */ + protected function getLinkParams( $event, $user, $destination ) { + $target = null; + $query = array(); + + if ( $destination === 'post' ) { + $eventData = $event->getExtra(); + $target = $event->getTitle(); + $target->setFragment( '#flow-post-' . $eventData['post-id'] ); + $query['workflow'] = $eventData['workflow']; + return array( $target, $query ); + } else { + return parent::getLinkParams( $event, $user, $destination ); + } + } +} diff --git a/Thanks.hooks.php b/Thanks.hooks.php index 4180c03..3df3d54 100644 --- a/Thanks.hooks.php +++ b/Thanks.hooks.php @@ -148,6 +148,22 @@ 'icon' => 'thanks', ); + $notifications['flow-thank'] = array( + 'primary-link' => array ( 'message' => 'notification-link-text-view-post', 'destination' => 'post' ), + 'category' => 'edit-thank', + 'group' => 'positive', + 'formatter-class' => 'EchoFlowThanksFormatter', + 'title-message' => 'notification-flow-thanks', + 'title-params' => array( 'agent', 'postlink', 'topictitle', 'title' ), + 'flyout-message' => 'notification-flow-thanks-flyout', + 'flyout-params' => array( 'agent', 'topictitle', 'title' ), + 'email-subject-message' => 'notification-flow-thanks-email-subject', + 'email-subject-params' => array( 'agent' ), + 'email-body-batch-message' => 'notification-flow-thanks-email-batch-body', + 'email-body-batch-params' => array( 'agent', 'topictitle', 'title' ), + 'icon' => 'thanks', + ); + $icons['thanks'] = array( 'path' => 'Thanks/ThankYou.png', ); @@ -164,6 +180,7 @@ public static function onEchoGetDefaultNotifiedUsers( $event, &$users ) { switch ( $event->getType() ) { case 'edit-thank': + case 'flow-thank': $extra = $event->getExtra(); if ( !$extra || !isset( $extra['thanked-user-id'] ) ) { break; @@ -253,4 +270,100 @@ $types[] = 'thanks'; return true; } + + /** + * Handler for FlowRegisterModules + * @param $context + * @return bool + */ + public static function onFlowRegisterModules( $context ) { + $context->addModules( array( 'ext.thanks' ) ); + return true; + } + + /** + * Handler for FlowAddInteractionLinks + * Inserts 'thank' link into flow post interface + * @param $rev Flow PostRevision object where the Thank link belongs to + * @param $links array of interaction links to be added to interface + * @param $classes associative array for 'active' and 'inactive' link CSS classes + * @return bool + */ + public static function onFlowAddPostInteractionLinks( $rev, &$links, $classes ) { + global $wgUser, $wgThanksSendToBots; + // Make sure Echo is turned on. + // Exclude anonymous users. + // Don't let users thank themselves. + // Exclude users who are blocked. + if ( class_exists( 'EchoNotifier' ) + && !$wgUser->isAnon() + && $rev->getCreatorId() != $wgUser->getId() + && !$wgUser->isBlocked() + // TODO: suppression level + ) { + $recipient = User::newFromId( $rev->getCreatorId() ); + $recipientAllowed = true; + // If bots are not allowed, exclude them as recipients + if ( !$wgThanksSendToBots ) { + $recipientAllowed = !in_array( 'bot', $recipient->getGroups() ); + } + if ( $recipientAllowed && !$recipient->isAnon() ) { + $links[] = self::generateFlowThankElement( $rev, $recipient, $classes ); + } + } + return true; + } + + /** + * Helper for self::insertThankLink + * Creates either a thank link or thanked span based on users session + * @param $rev Flow PostRevision object where the Thank link belongs to + * @param $recipient User the user who receives thanks notification + * @return string HTML segment for the links + */ + protected static function generateFlowThankElement( $rev, $recipient, $classes ) { + global $wgUser; + if ( empty( $classes[ 'active' ] ) ) { + $cssActiveClass = 'mw-thanks-flow-thank-link'; + } else { + $cssActiveClass = 'mw-thanks-flow-thank-link ' . $classes[ 'active' ]; + } + + if ( empty ( $classes[ 'inactive' ] ) ) { + $cssInactiveClass = 'mw-thanks-flow-thanked'; + } else { + $cssInactiveClass = 'mw-thanks-flow-thanked ' . $classes[ 'inactive' ]; + } + + // User has already thanked for revision + if ( $wgUser->getRequest()->getSessionData( "thanks-thanked-{$rev->getPostId()}" ) ) { + return Html::element( + 'span', + array( 'class' => $cssInactiveClass ), + wfMessage( 'thanks-button-thanked', $wgUser )->parse() + ); + } + + // Add 'thank' link + $tooltip = wfMessage( 'thanks-thank-tooltip' ) + ->params( $wgUser->getName(), $recipient->getName() ) + ->text(); + + return Html::element( + 'a', + array( + 'class' => $cssActiveClass, + 'href' => '#', // TODO: No-javascript fallback + 'title' => $tooltip, + 'data-post-id' => $rev->getPostId() + ), + wfMessage( 'thanks-button-thank', $wgUser )->parse() + ) + . Html::element( + 'span', + array( 'class' => $cssInactiveClass, + 'style' => 'display:none;' ), // TODO: is this acceptable without using jQuery.hide + wfMessage( 'thanks-button-thanked', $wgUser )->parse() + ); + } } diff --git a/Thanks.i18n.php b/Thanks.i18n.php index dba7e07..10be3b9 100644 --- a/Thanks.i18n.php +++ b/Thanks.i18n.php @@ -39,6 +39,14 @@ 'log-description-thanks' => 'Below is a list of users thanked by other users.', 'logentry-thanks-thank' => '$1 {{GENDER:$2|thanked}} {{GENDER:$4|$3}}', 'log-show-hide-thanks' => '$1 thanks log', + + //Flow Thanks + 'notification-link-text-view-post' => 'View post', + 'notification-flow-thanks' => '[[User:$1|$1]] {{GENDER:$1|thanked}} you for $2 in "$3" on [[:$4]].', + 'notification-flow-thanks-post-link' => 'your comment', + 'notification-flow-thanks-flyout' => '[[User:$1|$1]] {{GENDER:$1|thanked}} you for your comment in "$2" on $3.', + 'notification-flow-thanks-email-subject' => '$1 {{GENDER:$1|thanked}} you for your comment on {{SITENAME}}', + 'notification-flow-thanks-email-batch-body' => '$1 {{GENDER:$1|thanked}} you for your comment in "$2" on $3.', ); /** Message documentation (Message documentation) diff --git a/Thanks.php b/Thanks.php index 4d703e9..110b5b7 100644 --- a/Thanks.php +++ b/Thanks.php @@ -43,7 +43,9 @@ // Register files $wgAutoloadClasses['ThanksHooks'] = $dir . '/Thanks.hooks.php'; $wgAutoloadClasses['EchoThanksFormatter'] = $dir . '/ThanksFormatter.php'; +$wgAutoloadClasses['EchoFlowThanksFormatter'] = $dir . '/FlowThanksFormatter.php'; $wgAutoloadClasses['ApiThank'] = $dir . '/ApiThank.php'; +$wgAutoloadClasses['ApiFlowThank'] = $dir . '/ApiFlowThank.php'; $wgAutoloadClasses['ThanksLogFormatter'] = $dir . '/ThanksLogFormatter.php'; $wgAutoloadClasses['SpecialThanks'] = $dir . '/SpecialThanks.php'; $wgExtensionMessagesFiles['Thanks'] = $dir . '/Thanks.i18n.php'; @@ -52,6 +54,7 @@ // Register APIs $wgAPIModules['thank'] = 'ApiThank'; +$wgAPIModules['flow-thank'] = 'ApiFlowThank'; // Register special page $wgSpecialPages['Thanks'] = 'SpecialThanks'; @@ -68,6 +71,9 @@ $wgHooks['UnitTestsList'][] = 'ThanksHooks::registerUnitTests'; $wgHooks['GetLogTypesOnUser'][] = 'ThanksHooks::onGetLogTypesOnUser'; +$wgHooks['FlowAddPostInteractionLinks'][] = 'ThanksHooks::onFlowAddPostInteractionLinks'; +$wgHooks['FlowRegisterModules'][] = 'ThanksHooks::onFlowRegisterModules'; + // Register modules $wgResourceModules['ext.thanks'] = array( 'scripts' => array( @@ -75,6 +81,7 @@ ), 'messages' => array( 'thanks-thanked', + 'thanks-button-thanked', 'thanks-error-undefined', 'thanks-error-invalidrevision', 'thanks-error-ratelimited', diff --git a/modules/ext.thanks.thank.js b/modules/ext.thanks.thank.js index 6526823..cb89e37 100644 --- a/modules/ext.thanks.thank.js +++ b/modules/ext.thanks.thank.js @@ -67,7 +67,7 @@ } ); $dialog.dialog( 'open' ); }; - + var sendThanks = function( $thankLink ) { var source; if ( mw.config.get( 'wgAction' ) === 'history' ) { @@ -119,4 +119,45 @@ } } ); + $( 'a.mw-thanks-flow-thank-link' ).click( function( e ) { + var $thankLink = $( this ); + e.preventDefault(); + if ( !$thankLink.hasClass( 'mw-thanks-flow-thanked' ) ) { + sendFlowThanks( $thankLink ); + } + } ); + + var sendFlowThanks = function( $thankLink ) { + var source; + if ( mw.config.get( 'wgAction' ) === 'history' ) { + source = 'history'; + } else { + source = 'diff'; + } + ( new mw.Api ).get( { + 'action' : 'flow-thank', + 'post-id' : $thankLink.attr( 'data-post-id' ), + 'token' : mw.user.tokens.values.editToken + } ) + .done( function( data ) { + $thankLink.next().show(); + $thankLink.remove(); + + thanked.push( $thankLink ); + } ) + .fail( function( errorCode, details ) { + // TODO: use something besides alert for the error messages + switch( errorCode ) { + case 'invalidrevision': + alert( mw.msg( 'thanks-error-invalidrevision' ) ); + break; + case 'ratelimited': + alert( mw.msg( 'thanks-error-ratelimited', mw.user ) ); + break; + default: + alert( mw.msg( 'thanks-error-undefined' ) ); + } + } ); + }; + } )( jQuery, mediaWiki ); -- To view, visit https://gerrit.wikimedia.org/r/115557 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Id37a14e3c75c63419fc34e0b7c2e21f74b3fa875 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/Thanks Gerrit-Branch: master Gerrit-Owner: Wctaiwan <[email protected]> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ MediaWiki-commits mailing list [email protected] https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits
