EBernhardson has uploaded a new change for review. https://gerrit.wikimedia.org/r/91135
Change subject: [WIP] Moderate a post or topic ...................................................................... [WIP] Moderate a post or topic Adds actions to moderate topics and attempts to fix prior issues with application of delete and censor. Still needs some work in regards to historical posts. Change-Id: I10cae63c2491c9f467968d6f8d604ea7f743ea96 --- M Flow.i18n.php M includes/Block/Topic.php M includes/Model/AbstractRevision.php M includes/Model/PostRevision.php M includes/Templating.php M includes/View/PostActionMenu.php M modules/discussion/styles/topic.less A templates/bak M templates/topic.html.php 9 files changed, 556 insertions(+), 124 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Flow refs/changes/35/91135/1 diff --git a/Flow.i18n.php b/Flow.i18n.php index c68f915..6059643 100644 --- a/Flow.i18n.php +++ b/Flow.i18n.php @@ -27,6 +27,9 @@ 'flow-post-censored-by' => '{{GENDER:$1|Censored}} by $1 $2', 'flow-post-actions' => 'actions', 'flow-topic-actions' => 'actions', + 'flow-topic-hidden-by' => 'This topic was hidden by $1', + 'flow-topic-deleted-by' => 'This topic was deleted by $1', + 'flow-topic-censored-by' => 'This topic was suppressed by $1', 'flow-cancel' => 'Cancel', 'flow-newtopic-title-placeholder' => 'Message subject', @@ -59,6 +62,10 @@ 'flow-topic-action-watchlist' => 'Watchlist', 'flow-topic-action-edit-title' => 'Edit title', 'flow-topic-action-history' => 'Topic history', + 'flow-topic-action-hide-topic' => 'Hide topic', + 'flow-topic-action-delete-topic' => 'Delete topic', + 'flow-topic-action-censor-topic' => 'Suppress topic', + 'flow-topic-action-restore-topic' => 'Restore topic', 'flow-error-http' => 'An error occurred while contacting the server. Your post was not saved.', // Needs real copy 'flow-error-other' => 'An unexpected error occurred. Your post was not saved.', @@ -197,6 +204,12 @@ {{Identical|Action}}', 'flow-topic-actions' => 'Used as link text. {{Identical|Action}}', + 'flow-topic-hidden-by' => 'Used as replacement topic title when hidden. Parameters: +* $1 - username of the user that hid the topic', + 'flow-topic-deleted-by' => 'Used as replacement topic title when deleted. Parameters: +* $1 - username of the user that deleted the topic', + 'flow-topic-censored-by' => 'Used as replacement topic title when suppressed. Parameters: +* $1 - username of the user that suppressed the topic', 'flow-cancel' => 'Used as action link text. {{Identical|Cancel}}', 'flow-newtopic-title-placeholder' => 'Used as placeholder for the "Subject/Title for topic" textarea.', @@ -252,6 +265,10 @@ 'flow-topic-action-edit-title' => 'Used as title for the link which is used to edit the title.', 'flow-topic-action-history' => 'Used as text for the link which is used to view topic-history. {{Identical|Topic history}}', + 'flow-topic-action-hide-topic' => 'Used as a link in a dropdown menu to hide a topic.', + 'flow-topic-action-delete-topic' => 'Used as a link in a dropdown menu to delete a topic.', + 'flow-topic-action-censor-topic' => 'Used as a link in a dropdown menu to suppress a topic.', + 'flow-topic-action-restore-topic' => 'Used as a link in a dropdown menu to clear existing moderation.', 'flow-error-http' => 'Used as error message on HTTP error.', 'flow-error-other' => 'Used as generic error message.', 'flow-error-external' => 'Uses as error message. Parameters: diff --git a/includes/Block/Topic.php b/includes/Block/Topic.php index 6cec6dc..516b8dc 100644 --- a/includes/Block/Topic.php +++ b/includes/Block/Topic.php @@ -20,6 +20,7 @@ protected $topicTitle; protected $rootLoader; protected $newRevision; + protected $relatedRevisions = array(); protected $notification; protected $requestedPost; @@ -28,10 +29,12 @@ protected $supportedActions = array( // Standard editing 'edit-post', 'reply', - // Moderation + // Topic Moderation + 'hide-topic', 'delete-topic', 'censor-topic', 'restore-topic', + // Post Moderation 'hide-post', 'delete-post', 'censor-post', 'restore-post', // Other stuff - 'hide-topic', 'edit-title', + 'edit-title', ); public function __construct( Workflow $workflow, ManagerGroup $storage, NotificationController $notificationController, $root ) { @@ -58,8 +61,19 @@ break; case 'hide-topic': - // this should be a workflow level action, not implemented per-block - $this->validateHideTopic(); + $this->validateModerateTopic( AbstractRevision::MODERATED_HIDDEN ); + break; + + case 'delete-topic': + $this->validateModerateTopic( AbstractRevision::MODERATED_DELETED ); + break; + + case 'censor-topic': + $this->validateModerateTopic( AbstractRevision::MODERATED_CENSORED ); + break; + + case 'restore-topic': + $this->validateRestorePost(); break; case 'hide-post': @@ -139,9 +153,27 @@ } } - protected function validateHideTopic() { - if ( !$this->workflow->lock( $this->user ) ) { - $this->errors['hide-topic'] = wfMessage( 'flow-error-hide-failure' ); + protected function validateModerateTopic( $moderationState ) { + $topic = $this->loadTopicTitle(); + $this->newRevision = $topic->moderate( $this->user, $moderationState ); + // The application of moderation to historical revisions is pushed out of the + // model because the moderation is in AbstractRevision, but not all revisions + // (ex: header) will want moderation to apply to historical revisions. + if ( $this->newRevision && $this->newRevision->needsModerateHistorical() ) { + $this->relatedRevisions = $this->loadHistorical( $topic ); + foreach ( $this->relatedRevisions as $revision ) { + $revision->moderate( $this->user, $moderationState, null, /* $createRevision = */ false ); + } + } elseif ( !$this->newRevision ) { + $this->errors['moderate'] = wfMessage( 'flow-error-not-allowed' ); + } + } + + protected function validateRestoreTopic() { + $topic = $this->loadTopicTitle(); + $this->newRevision = $topic->restore( $this->user ); + if ( !$this->newRevision ) { + $this->errors['restore-topic'] = wfMessage( 'flow-error-not-allowed' ); } } @@ -213,6 +245,10 @@ switch( $this->action ) { case 'reply': + case 'hide-topic': + case 'delete-topic': + case 'censor-topic': + case 'restore-topic': case 'hide-post': case 'delete-post': case 'censor-post': @@ -224,6 +260,10 @@ } $this->storage->put( $this->newRevision ); $this->storage->put( $this->workflow ); + // These are moderated historical revisions of $this->newRevision + foreach ( $this->relatedRevisions as $revision ) { + $this->storage->put( $revision ); + } $self = $this; $newRevision = $this->newRevision; $rootPost = $this->loadRootPost(); @@ -557,6 +597,33 @@ return $this->requestedPost[$postId] ?: null; } + protected function loadHistorical( PostRevision $post ) { + if ( $post->isFirstRevision() ) { + throw new \Exception( 'first?' ); + return array(); + } + + $found = $this->storage->find( + 'PostRevision', + array( 'tree_rev_descendant_id' => $post->getPostId() ), + // TODO: should we really be reverting moderation state for more than + // 50 revisions? + array( 'limit' => 50 ) + ); + if ( !$found ) { + throw new \Exception( 'should have found revisions' ); + } + // We need to filter out $post + $revId = $post->getRevisionId(); + foreach ( $found as $idx => $revision ) { + if ( $revId->equals( $revision->getRevisionId() ) ) { + unset( $found[$idx] ); + break; + } + } + return $found; + } + // Somehow the template has to know which post the errors go with public function getRepliedTo() { return isset( $this->submitted['replyTo'] ) ? $this->submitted['replyTo'] : null; diff --git a/includes/Model/AbstractRevision.php b/includes/Model/AbstractRevision.php index 89776a5..c7ef83f 100644 --- a/includes/Model/AbstractRevision.php +++ b/includes/Model/AbstractRevision.php @@ -22,6 +22,8 @@ 'perm' => null, // i18n key to replace content with when state is active(unused with perm === null ) 'content' => null, + // This is the bit of text rendered instead of the content when isTopicTitle returns true + 'topic' => null, // This is the bit of text rendered instead of the post creator 'usertext' => null, // Whether or not to create a new revision when setting this state @@ -35,6 +37,8 @@ // i18n key to replace content with when state is active // NOTE: special case self::getHiddenContent still retrieves content in this case only 'content' => 'flow-post-hidden-by', + // This is the bit of text rendered instead of the content when isTopicTitle returns true + 'topic' => 'flow-topic-hidden-by', // This is the bit of text rendered instead of the post creator 'usertext' => 'flow-rev-message-hid-post', // Whether or not to create a new revision when setting this state @@ -47,6 +51,8 @@ 'perm' => 'flow-delete', // i18n key to replace content with when state is active 'content' => 'flow-post-deleted-by', + // This is the bit of text rendered instead of the content when isTopicTitle returns true + 'topic' => 'flow-topic-deleted-by', // This is the bit of text rendered instead of the post creator 'usertext' => 'flow-rev-message-deleted-post', // Whether or not to create a new revision when setting this state @@ -59,6 +65,8 @@ 'perm' => 'flow-censor', // i18n key to replace content with when state is active 'content' => 'flow-post-censored-by', + // This is the bit of text rendered instead of the content when isTopicTitle returns true + 'topic' => 'flow-topic-censored-by', // This is the bit of text rendered instead of the post creator 'usertext' => 'flow-rev-message-censored-post', // Whether or not to create a new revision when setting this state @@ -198,7 +206,11 @@ return $keys[max( $aPos, $bPos )]; } - public function moderate( User $user, $state, $changeType = null ) { + /** + * $createRevision = false should only be used to apply a moderation action + * to historical revisions, not general moderation. + */ + public function moderate( User $user, $state, $changeType = null, $createRevision = true ) { if ( !isset( self::$perms[$state] ) ) { wfDebugLog( __CLASS__, __FUNCTION__ . ': Provided moderation state does not exist : ' . $state ); return null; @@ -208,11 +220,14 @@ if ( !$this->isAllowed( $user, $mostRestrictive ) ) { return null; } - // Censoring is special, other moderation types just create - // a new revision but censoring adjusts the existing revision. - // Yes this mucks with the history just being a revision list. - if ( self::$perms[$state]['new-revision'] ) { + + if ( $createRevision ) { $obj = $this->newNullRevision( $user ); + if ( $changeType === null && isset( self::$perms[$state]['change-type'] ) ) { + $obj->changeType = self::$perms[$state]['change-type']; + } else { + $obj->changeType = $changeType; + } } else { $obj = $this; } @@ -227,12 +242,17 @@ $obj->moderatedByUserText = $user->getName(); $obj->moderationTimestamp = wfTimestampNow(); } - if ( $changeType === null && isset( self::$perms[$state]['change-type'] ) ) { - $obj->changeType = self::$perms[$state]['change-type']; - } else { - $obj->changeType = $changeType; - } + return $obj; + } + + public function needsModerateHistorical() { + $state = $this->moderationState; + if ( !isset( self::$perms[$state]['new-revision'] ) ) { + wfWarn( __CLASS__, __FUNCTION__ . ": Moderation state does not exist : $state" ); + return false; + } + return self::$perms[$state]['new-revision']; } public function restore( User $user ) { @@ -277,17 +297,21 @@ if ( $this->isAllowed( $user ) ) { return $this->getConvertedContent( $format ); } else { - $moderatedAt = new MWTimestamp( $this->moderationTimestamp ); - - // Messages: flow-post-hidden-by, flow-post-deleted-by, flow-post-censored-by - return wfMessage( - self::$perms[$this->moderationState]['content'], - $this->moderatedByUserText, - $moderatedAt->getHumanTimestamp() - ); + return $this->getModeratedContent(); } } + protected function getModeratedContent() { + $moderatedAt = new MWTimestamp( $this->moderationTimestamp ); + + // Messages: flow-post-hidden-by, flow-post-deleted-by, flow-post-censored-by + return wfMessage( + self::$perms[$this->moderationState]['content'], + $this->moderatedByUserText, + $moderatedAt->getHumanTimestamp() + ); + } + public function getContentRaw() { if ( $this->decompressedContent === null ) { $this->decompressedContent = \Revision::decompressRevisionText( $this->content, $this->flags ); diff --git a/includes/Model/PostRevision.php b/includes/Model/PostRevision.php index 9f878c5..0ceec7a 100644 --- a/includes/Model/PostRevision.php +++ b/includes/Model/PostRevision.php @@ -88,7 +88,7 @@ /** * Get the user ID of the user who created this post. - * Checks permissions and returns false + * Checks permissions and returns false * * @param $user User The user to check permissions for. * @return int|bool The user ID, or false @@ -389,4 +389,15 @@ } return $user->getId() == $this->getCreatorId() || $user->isAllowed( 'flow-edit-post' ); } + + protected function getModeratedContent() { + if ( $this->isTopicTitle() ) { + return wfMessage( + self::$perms[$this->moderationState]['topic'], + $this->moderatedByUserText + ); + } else { + return parent::getModeratedContent(); + } + } } diff --git a/includes/Templating.php b/includes/Templating.php index 250404c..76c1b5e 100644 --- a/includes/Templating.php +++ b/includes/Templating.php @@ -94,15 +94,7 @@ array( 'block' => $block, 'post' => $post, - // An ideal world may pull this from the container, but for now this is fine. This templating - // class has too many responsibilities to keep receiving all required objects in the constructor. - 'postActionMenu' => new PostActionMenu( - $this->urlGenerator, - $wgUser, - $block, - $post, - $wgUser->getEditToken( $wgFlowTokenSalt ) - ), + 'postActionMenu' => $this->createActionMenu( $post, $block ), ), $return ); @@ -113,7 +105,22 @@ 'block' => $block, 'topic' => $block->getWorkflow(), 'root' => $root, + 'postActionMenu' => $this->createActionMenu( $root, $block ), ), $return ); + } + + // An ideal world may pull this from the container, but for now this is fine. This templating + // class has too many responsibilities to keep receiving all required objects in the constructor. + protected function createActionMenu( PostRevision $post, Block $block ) { + global $wgUser, $wgFlowTokenSalt; + + return new PostActionMenu( + $this->urlGenerator, + $wgUser, + $block, + $post, + $wgUser->getEditToken( $wgFlowTokenSalt ) + ); } public function getPagingLink( $block, $direction, $offset, $limit ) { @@ -220,7 +227,7 @@ * Gets a Flow-formatted plaintext human-readable identifier for a user. * Usually the user's name, but it can also return "an anonymous user", * or information about an item's moderation state. - * + * * @param User $user The User object to get a description for. * @param AbstractRevision $rev An AbstractRevision object to retrieve moderation state from. * @param bool $showIPs Whether or not to show IP addresses for anonymous users diff --git a/includes/View/PostActionMenu.php b/includes/View/PostActionMenu.php index 43d1ea9..9386f65 100644 --- a/includes/View/PostActionMenu.php +++ b/includes/View/PostActionMenu.php @@ -32,6 +32,37 @@ */ protected function getActionDetails( $action ) { $actions = array( + // Not sure about mixing topic's and post's, although they are handled + // the same currently. + 'hide-topic' => array( + 'method' => 'POST', + 'permissions' => array( + PostRevision::MODERATED_NONE => 'flow-hide', + PostRevision::MODERATED_HIDDEN => 'flow-hide', + ), + ), + 'delete-topic' => array( + 'method' => 'POST', + 'permissions' => array( + PostRevision::MODERATED_NONE => 'flow-delete', + PostRevision::MODERATED_HIDDEN => 'flow-delete', + ), + ), + 'censor-topic' => array( + 'method' => 'POST', + 'permissions' => array( + PostRevision::MODERATED_NONE => 'flow-censor', + PostRevision::MODERATED_HIDDEN => 'flow-censor', + ), + ), + 'restore-topic' => array( + 'method' => 'POST', + 'permissions' => array( + PostRevision::MODERATED_HIDDEN => 'flow-hide', + PostRevision::MODERATED_DELETED => array( 'flow-delete', 'flow-censor' ), + PostRevision::MODERATED_CENSORED => 'flow-censor', + ), + ), 'hide-post' => array( 'method' => 'POST', 'permissions' => array( diff --git a/modules/discussion/styles/topic.less b/modules/discussion/styles/topic.less index f307318..6fe85a5 100644 --- a/modules/discussion/styles/topic.less +++ b/modules/discussion/styles/topic.less @@ -4,6 +4,38 @@ .flow-topic-container { padding-bottom: 54px; + &.flow-topic-moderated { + > .flow-post-container { + display: none + } + .flow-topic-posts-meta { + display: none + } + } + + .flow-topic-moderated-hide, + .flow-topic-moderated-deleted, + .flow-topic-moderated-suppressed { + padding-left: 22px !important; + + background-position: left; + background-size: 14px auto; + background-repeat: no-repeat; + } + + .flow-topic-moderated-hide { + .background-image-svg('../../base/images/moderate_menu_hidden_normal.svg', '../../base/images/moderate_menu_hidden_normal.png'); + } + + .flow-topic-moderated-deleted { + .background-image-svg('../../base/images/moderated_normal.svg', '../../base/images/moderated_normal.png'); + } + + .flow-topic-moderated-censored { + .background-image-svg('../../base/images/suppressed_normal.svg', '../../base/images/suppressed_normal.png'); + } + + .flow-titlebar { position: relative; @@ -22,6 +54,16 @@ &:hover, &.mw-ui-hover { background: @topic-titlebar-background-color; + + .flow-topic-moderated-censored { + .background-image-svg('../../base/images/suppressed_hover.svg', '../../base/images/suppressed_hover.png'); + } + .flow-topic-moderated-deleted { + .background-image-svg('../../base/images/moderated_hover.svg', '../../base/images/moderated_hover.png'); + } + .flow-topic-moderated-hide { + .background-image-svg('../../base/images/moderate_menu_hidden_hover.svg', '../../base/images/moderate_menu_hidden_hover.png'); + } } .flow-topic-title { diff --git a/templates/bak b/templates/bak new file mode 100644 index 0000000..8a8089a --- /dev/null +++ b/templates/bak @@ -0,0 +1,198 @@ +<?php + +// treat title like unparsed (wiki)text +$title = $root->getContent( $user, 'wikitext' ); + +// pre-register recursive callbacks; will then be fetched all at once when the +// first one's result is requested +$indexDescendantCount = $root->registerDescendantCount(); +$indexParticipants = $root->registerParticipants(); + +echo Html::openElement( 'div', array( + 'class' => 'flow-topic-container flow-topic-full' . $root->isModerated() ? ' flow-topic-moderated' : '', + 'id' => 'flow-topic-' . $topic->getId()->getHex(), + 'data-topic-id' => $topic->getId()->getHex(), + 'data-title' => $root->isModerated() ? '' : $title, +) ); +?> +<div class="flow-element-container"> + <div class="flow-titlebar mw-ui-button"> + <?php + echo Html::rawElement( + 'a', + array( + 'href' => $this->generateUrl( $root->getPostId(), 'edit-title' ), + 'class' => 'flow-edit-topic-link flow-icon flow-icon-top-aligned', + ), + wfMessage( 'flow-topic-action-edit-title' ) + ); + ?> + + <div class="flow-topic-title"> + <?php if ( $root->isModerated() ): ?> + <h2 class='flow-topic-moderated flow-topic-moderated-<?php echo $root->getModerationState() ?>'> + <?php // passing null as user (unprivileged) gets the hidden/deleted/suppressed text + echo htmlspecialchars( $root->getContent( null ) ) ?> + </h2> + <?php else: ?> + <h2 class="flow-realtitle"> + <?php echo htmlspecialchars( $title ); ?> + </h2> + <?php endif ?> + </div> + + <div class="flow-actions"> + <a class="flow-actions-link" href="#"><?php echo wfMessage( 'flow-topic-actions' )->escaped(); ?></a> + <div class="flow-actions-flyout"> + <ul> + <?php if ( $postActionMenu->isAllowed( 'hide-topic' ) ) { + echo '<li class="flow-action-hide">', $postActionMenu->getButton( + 'hide-topic', + wfMessage( 'flow-topic-action-hide-topic' )->plain(), + 'mw-ui-button' + ), '</li>'; + } ?> + <?php if ( $postActionMenu->isAllowed( 'delete-topic' ) ) { + echo '<li class="flow-action-delete">', $postActionMenu->getButton( + 'delete-topic', + wfMessage( 'flow-topic-action-delete-topic' )->plain(), + 'mw-ui-button' + ), '</li>'; + } ?> + <?php if ( $postActionMenu->isAllowed( 'censor-topic' ) ) { + echo '<li class="flow-action-censor">', $postActionMenu->getButton( + 'censor-topic', + wfMessage( 'flow-topic-action-censor-topic' )->plain(), + 'mw-ui-button' + ), '</li>'; + } ?> + <?php if ( $postActionMenu->isAllowed( 'restore-topic' ) ) { + echo '<li class="flow-action-restore">', $postActionMenu->getButton( + 'restore-topic', + wfMessage( 'flow-topic-action-restore-topic' )->plain(), + 'mw-ui-button' + ), '</li>'; + } ?> + <li class="flow-action-close"> + <a href="#" class="mw-ui-button">@todo: Close topic</a> + </li> + </ul> + </div> + </div> + + <p class="flow-datestamp"> + <?php + // timestamp html + $content = ' + <span class="flow-agotime" style="display: inline">'. $topic->getLastModifiedObj()->getHumanTimestamp() .'</span> + <span class="flow-utctime" style="display: none">'. $topic->getLastModifiedObj()->getTimestamp( TS_RFC2822 ) .'</span>'; + + // build history button with timestamp html as content + echo Html::rawElement( 'a', + array( + 'class' => 'flow-action-history-link', + 'href' => $this->generateUrl( $root->getPostId(), 'topic-history' ), + ), + $content + ); + ?> + </p> + + <?php if ( $root->isAllowed( $user ) ): + echo Html::element( + 'a', + array( + 'class' => 'flow-icon-permalink flow-icon flow-icon-top-aligned', + 'title' => wfMessage( 'flow-topic-action-view' )->text(), + 'href' => $this->generateUrl( $topic ), + ), + wfMessage( 'flow-topic-action-view' )->text() + ); + ?> + + <ul class="flow-topic-posts-meta"> + <li class="flow-topic-participants"> + <?php echo $this->printParticipants( $root, $indexParticipants ); ?> + </li> + <li class="flow-topic-comments"> + <a href="#" class="flow-reply-link" data-topic-id="<?php echo $topic->getId()->getHex() ?>"> + <?php + // get total number of posts in topic + $comments = $root->getRecursiveResult( $indexDescendantCount ); + echo wfMessage( 'flow-topic-comments', $comments )->text(); + ?> + </a> + </li> + </ul> + + <?php + // @todo: afaik, there's no watchlist functionality yet; this blurb is just to position it correctly already + + $watchlistActive = false; // @todo: true if already watchlisted, false if not + echo Html::element( + 'a', + array( + 'class' => 'flow-icon-watchlist flow-icon flow-icon-bottom-aligned' + . ( $watchlistActive ? ' flow-icon-watchlist-active' : '' ), + 'title' => wfMessage( 'flow-topic-action-watchlist' )->text(), + 'href' => '#', + 'onclick' => "alert( '@todo: Not yet implemented!' ); return false;" + ), + wfMessage( 'flow-topic-action-watchlist' )->text() + ); + ?> + <?php endif; /* !$root->isModerated() */ ?> + </div> +</div> +<?php if ( $root->isAllowed( $user ) ): + foreach( $root->getChildren() as $child ) { + echo $this->renderPost( $child, $block, $root ); + } + + // Topic reply box + echo Html::openElement( 'div', array( + 'class' => 'flow-topic-reply-container flow-post-container flow-element-container', + 'data-post-id' => $root->getRevisionId()->getHex(), + 'id' => 'flow-topic-reply-' . $topic->getId()->getHex() + ) ); + ?> + <span class="flow-creator"> + <span class="flow-creator-simple" style="display: inline"> + <?php echo $this->getUserText( $user ); ?> + </span> + <span class="flow-creator-full" style="display: none"> + <?php echo $this->userToolLinks( $user->getId(), $user->getName() ); ?> + </span> + </span> + <?php + echo Html::openElement( 'form', array( + 'method' => 'POST', + 'action' => $this->generateUrl( $block->getWorkflow(), 'reply' ), + 'class' => 'flow-topic-reply-form', + ) ), + Html::element( 'input', array( + 'type' => 'hidden', + 'name' => $block->getName() . '[replyTo]', + 'value' => $topic->getId()->getHex(), + ) ), + Html::element( 'input', array( + 'type' => 'hidden', + 'name' => 'wpEditToken', + 'value' => $editToken, + ) ), + Html::textarea( $block->getName() . '[topic-reply-content]', '', array( + 'placeholder' => wfMessage( 'flow-reply-topic-placeholder', $user->getName(), $title )->text(), + 'class' => 'flow-input mw-ui-input flow-topic-reply-content', + ) ), + Html::openElement( 'div', array( 'class' => 'flow-post-form-controls' ) ), + Html::element( 'input', array( + 'type' => 'submit', + 'value' => wfMessage( 'flow-reply-submit', $this->getUserText( $root->getCreator( $user ), $root ) )->text(), + 'class' => 'mw-ui-button mw-ui-constructive flow-topic-reply-submit', + ) ), + Html::closeElement( 'div' ), + Html::closeElement( 'form' ), + Html::closeElement( 'div' ); + ?> + </div> +<?php endif /* !$root->isModerated() */ ?> diff --git a/templates/topic.html.php b/templates/topic.html.php index 0adade3..67bbc84 100644 --- a/templates/topic.html.php +++ b/templates/topic.html.php @@ -9,10 +9,10 @@ $indexParticipants = $root->registerParticipants(); echo Html::openElement( 'div', array( - 'class' => 'flow-topic-container flow-topic-full', + 'class' => 'flow-topic-container flow-topic-full' . ( $root->isModerated() ? ' flow-topic-moderated' : '' ), 'id' => 'flow-topic-' . $topic->getId()->getHex(), 'data-topic-id' => $topic->getId()->getHex(), - 'data-title' => $title, + 'data-title' => $root->isModerated() ? '' : $title, ) ); ?> <div class="flow-element-container"> @@ -29,17 +29,50 @@ ?> <div class="flow-topic-title"> - <h2 class="flow-realtitle"> - <?php echo htmlspecialchars( $title ); ?> - </h2> + <?php if ( $root->isModerated() ): ?> + <h2 class='flow-topic-moderated flow-topic-moderated-<?php echo $root->getModerationState() ?>'> + <?php // passing null as user (unprivileged) gets the hidden/deleted/suppressed text + echo htmlspecialchars( $root->getContent( null ) ) ?> + </h2> + <?php else: ?> + <h2 class="flow-realtitle"> + <?php echo htmlspecialchars( $title ); ?> + </h2> + <?php endif ?> </div> + <div class="flow-actions"> <a class="flow-actions-link" href="#"><?php echo wfMessage( 'flow-topic-actions' )->escaped(); ?></a> <div class="flow-actions-flyout"> <ul> - <li class="flow-action-hide"> - <a href="#" class="mw-ui-button mw-ui-destructive">@todo: Hide topic</a> - </li> + <?php if ( $postActionMenu->isAllowed( 'hide-topic' ) ) { + echo '<li class="flow-action-hide">', $postActionMenu->getButton( + 'hide-topic', + wfMessage( 'flow-topic-action-hide-topic' )->plain(), + 'mw-ui-button' + ), '</li>'; + } ?> + <?php if ( $postActionMenu->isAllowed( 'delete-topic' ) ) { + echo '<li class="flow-action-delete">', $postActionMenu->getButton( + 'delete-topic', + wfMessage( 'flow-topic-action-delete-topic' )->plain(), + 'mw-ui-button' + ), '</li>'; + } ?> + <?php if ( $postActionMenu->isAllowed( 'censor-topic' ) ) { + echo '<li class="flow-action-censor">', $postActionMenu->getButton( + 'censor-topic', + wfMessage( 'flow-topic-action-censor-topic' )->plain(), + 'mw-ui-button' + ), '</li>'; + } ?> + <?php if ( $postActionMenu->isAllowed( 'restore-topic' ) ) { + echo '<li class="flow-action-restore">', $postActionMenu->getButton( + 'restore-topic', + wfMessage( 'flow-topic-action-restore-topic' )->plain(), + 'mw-ui-button' + ), '</li>'; + } ?> <li class="flow-action-close"> <a href="#" class="mw-ui-button">@todo: Close topic</a> </li> @@ -65,7 +98,7 @@ ?> </p> - <?php + <?php if ( $root->isAllowed( $user ) ): echo Html::element( 'a', array( @@ -75,89 +108,91 @@ ), wfMessage( 'flow-topic-action-view' )->text() ); - ?> + ?> - <ul class="flow-topic-posts-meta"> - <li class="flow-topic-participants"> - <?php echo $this->printParticipants( $root, $indexParticipants ); ?> - </li> - <li class="flow-topic-comments"> - <a href="#" class="flow-reply-link" data-topic-id="<?php echo $topic->getId()->getHex() ?>"> - <?php - // get total number of posts in topic - $comments = $root->getRecursiveResult( $indexDescendantCount ); - echo wfMessage( 'flow-topic-comments', $comments )->text(); - ?> - </a> - </li> - </ul> + <ul class="flow-topic-posts-meta"> + <li class="flow-topic-participants"> + <?php echo $this->printParticipants( $root, $indexParticipants ); ?> + </li> + <li class="flow-topic-comments"> + <a href="#" class="flow-reply-link" data-topic-id="<?php echo $topic->getId()->getHex() ?>"> + <?php + // get total number of posts in topic + $comments = $root->getRecursiveResult( $indexDescendantCount ); + echo wfMessage( 'flow-topic-comments', $comments )->text(); + ?> + </a> + </li> + </ul> - <?php - // @todo: afaik, there's no watchlist functionality yet; this blurb is just to position it correctly already + <?php + // @todo: afaik, there's no watchlist functionality yet; this blurb is just to position it correctly already - $watchlistActive = false; // @todo: true if already watchlisted, false if not - echo Html::element( - 'a', - array( - 'class' => 'flow-icon-watchlist flow-icon flow-icon-bottom-aligned' - . ( $watchlistActive ? ' flow-icon-watchlist-active' : '' ), - 'title' => wfMessage( 'flow-topic-action-watchlist' )->text(), - 'href' => '#', - 'onclick' => "alert( '@todo: Not yet implemented!' ); return false;" - ), - wfMessage( 'flow-topic-action-watchlist' )->text() - ); - ?> + $watchlistActive = false; // @todo: true if already watchlisted, false if not + echo Html::element( + 'a', + array( + 'class' => 'flow-icon-watchlist flow-icon flow-icon-bottom-aligned' + . ( $watchlistActive ? ' flow-icon-watchlist-active' : '' ), + 'title' => wfMessage( 'flow-topic-action-watchlist' )->text(), + 'href' => '#', + 'onclick' => "alert( '@todo: Not yet implemented!' ); return false;" + ), + wfMessage( 'flow-topic-action-watchlist' )->text() + ); + ?> + <?php endif; /* !$root->isModerated() */ ?> </div> </div> -<?php -foreach( $root->getChildren() as $child ) { - echo $this->renderPost( $child, $block, $root ); -} +<?php if ( $root->isAllowed( $user ) ): + foreach( $root->getChildren() as $child ) { + echo $this->renderPost( $child, $block, $root ); + } -// Topic reply box -echo Html::openElement( 'div', array( - 'class' => 'flow-topic-reply-container flow-post-container flow-element-container', - 'data-post-id' => $root->getRevisionId()->getHex(), - 'id' => 'flow-topic-reply-' . $topic->getId()->getHex() -) ); -?> - <span class="flow-creator"> - <span class="flow-creator-simple" style="display: inline"> - <?php echo $this->getUserText( $user ); ?> + // Topic reply box + echo Html::openElement( 'div', array( + 'class' => 'flow-topic-reply-container flow-post-container flow-element-container', + 'data-post-id' => $root->getRevisionId()->getHex(), + 'id' => 'flow-topic-reply-' . $topic->getId()->getHex() + ) ); + ?> + <span class="flow-creator"> + <span class="flow-creator-simple" style="display: inline"> + <?php echo $this->getUserText( $user ); ?> + </span> + <span class="flow-creator-full" style="display: none"> + <?php echo $this->userToolLinks( $user->getId(), $user->getName() ); ?> + </span> </span> - <span class="flow-creator-full" style="display: none"> - <?php echo $this->userToolLinks( $user->getId(), $user->getName() ); ?> - </span> - </span> -<?php - echo Html::openElement( 'form', array( - 'method' => 'POST', - 'action' => $this->generateUrl( $block->getWorkflow(), 'reply' ), - 'class' => 'flow-topic-reply-form', - ) ), - Html::element( 'input', array( - 'type' => 'hidden', - 'name' => $block->getName() . '[replyTo]', - 'value' => $topic->getId()->getHex(), - ) ), - Html::element( 'input', array( - 'type' => 'hidden', - 'name' => 'wpEditToken', - 'value' => $editToken, - ) ), - Html::textarea( $block->getName() . '[topic-reply-content]', '', array( - 'placeholder' => wfMessage( 'flow-reply-topic-placeholder', $user->getName(), $title )->text(), - 'class' => 'flow-input mw-ui-input flow-topic-reply-content', - ) ), - Html::openElement( 'div', array( 'class' => 'flow-post-form-controls' ) ), - Html::element( 'input', array( - 'type' => 'submit', - 'value' => wfMessage( 'flow-reply-submit', $this->getUserText( $root->getCreator( $user ), $root ) )->text(), - 'class' => 'mw-ui-button mw-ui-constructive flow-topic-reply-submit', - ) ), - Html::closeElement( 'div' ), - Html::closeElement( 'form' ), - Html::closeElement( 'div' ); -?> -</div> + <?php + echo Html::openElement( 'form', array( + 'method' => 'POST', + 'action' => $this->generateUrl( $block->getWorkflow(), 'reply' ), + 'class' => 'flow-topic-reply-form', + ) ), + Html::element( 'input', array( + 'type' => 'hidden', + 'name' => $block->getName() . '[replyTo]', + 'value' => $topic->getId()->getHex(), + ) ), + Html::element( 'input', array( + 'type' => 'hidden', + 'name' => 'wpEditToken', + 'value' => $editToken, + ) ), + Html::textarea( $block->getName() . '[topic-reply-content]', '', array( + 'placeholder' => wfMessage( 'flow-reply-topic-placeholder', $user->getName(), $title )->text(), + 'class' => 'flow-input mw-ui-input flow-topic-reply-content', + ) ), + Html::openElement( 'div', array( 'class' => 'flow-post-form-controls' ) ), + Html::element( 'input', array( + 'type' => 'submit', + 'value' => wfMessage( 'flow-reply-submit', $this->getUserText( $root->getCreator( $user ), $root ) )->text(), + 'class' => 'mw-ui-button mw-ui-constructive flow-topic-reply-submit', + ) ), + Html::closeElement( 'div' ), + Html::closeElement( 'form' ), + Html::closeElement( 'div' ); + ?> + </div> +<?php endif /* !$root->isModerated() */ ?> -- To view, visit https://gerrit.wikimedia.org/r/91135 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I10cae63c2491c9f467968d6f8d604ea7f743ea96 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/Flow Gerrit-Branch: master Gerrit-Owner: EBernhardson <ebernhard...@wikimedia.org> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits