EBernhardson (WMF) has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/80303


Change subject: First stab at content moderation
......................................................................

First stab at content moderation

Change-Id: I39241b68fd56d74ffe5a5051777dcb6e09750d9d
---
M Flow.i18n.php
M Flow.php
M flow.sql
M includes/Block/Topic.php
M includes/Data/ObjectManager.php
M includes/Data/RevisionStorage.php
M includes/Model/AbstractRevision.php
M includes/Model/PostRevision.php
M templates/post.html.php
9 files changed, 354 insertions(+), 168 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Flow 
refs/changes/03/80303/1

diff --git a/Flow.i18n.php b/Flow.i18n.php
index 4cd3713..ce378c3 100644
--- a/Flow.i18n.php
+++ b/Flow.i18n.php
@@ -15,7 +15,12 @@
        'flow-disclaimer' => "By clicking the \"Add message\" button, you agree 
to the Terms of Use,
 and you irrevocably agree to release your contribution under the CC-BY-SA 3.0 
License and the GFDL.
 You agree that a hyperlink or URL is sufficient attribution under the Creative 
Commons license.",
+       'flow-post-hidden' => '[post hidden]',
+       'flow-post-hidden-by' => 'Hidden by $1 $2',
        'flow-post-deleted' => '[post deleted]',
+       'flow-post-deleted-by' => 'Deleted by $1 $2',
+       'flow-post-oversighted' => '[post oversighted]',
+       'flow-post-oversighted-by' => 'Oversighted by $1 $2',
        'flow-post-actions' => 'actions',
        'flow-topic-actions' => 'actions',
        'flow-cancel' => 'Cancel',
@@ -33,7 +38,9 @@
 
        'flow-post-action-view' => 'Permalink',
        'flow-post-action-post-history' => 'Post history',
+       'flow-post-action-oversight-post' => 'Oversight post',
        'flow-post-action-delete-post' => 'Delete post',
+       'flow-post-action-hide-post' => 'Hide post',
        'flow-post-action-edit-post' => 'Edit post',
        'flow-post-action-edit' => 'Edit',
        'flow-post-action-restore-post' => 'Restore post',
@@ -52,6 +59,7 @@
        'flow-error-missing-replyto' => 'No replyTo parameter was supplied. 
This parameter is required for the "reply" action.',
        'flow-error-invalid-replyto' => 'replyTo parameter was invalid. The 
specified post could not be found.',
        'flow-error-delete-failure' => 'Deletion of this item failed.',
+       'flow-error-hide-failure' => 'Hiding this item failed.',
        'flow-error-missing-postId' => 'No postId parameter was supplied. This 
parameter is required to manipulate a post.',
        'flow-error-invalid-postId' => 'postId parameter was invalid. The 
specified post could not be found.',
        'flow-error-restore-failure' => 'Restoration of this item failed.',
@@ -67,6 +75,7 @@
 
        'flow-comment-restored' => 'Restored comment',
        'flow-comment-deleted' => 'Deleted comment',
+       'flow-comment-hidden' => 'Hidden comment',
 );
 
 /** Message documentation (Message documentation)
@@ -86,6 +95,7 @@
 See also:
 * {{msg-mw|Wikimedia-copyrightwarning}}',
        'flow-post-deleted' => 'Used as username/content if the post was 
deleted.',
+       'flow-post-hidden' => 'Used as username/content if the post was 
hidden.',
        'flow-post-actions' => 'Used as link text.
 {{Identical|Action}}',
        'flow-topic-actions' => 'Used as link text.
@@ -104,6 +114,7 @@
        'flow-post-action-view' => 'Used as text for the link which is used to 
view.
 {{Identical|Permalink}}',
        'flow-post-action-post-history' => 'Used as text for the link which is 
used to view post-history of the topic.',
+       'flow-post-action-hide-post' => 'Used as label for the Submit button.',
        'flow-post-action-delete-post' => 'Used as label for the Submit button.
 
 See also:
@@ -144,6 +155,9 @@
        'flow-error-delete-failure' => 'Used as error message.
 
 "this item" refers either "this topic" or "this post".',
+       'flow-error-hide-failure' => 'Used as error message.
+
+"this item" refers either "this topic" or "this post".',
        'flow-error-missing-postId' => 'Used as error message when 
deleting/restoring a post.
 
 "manipulate" refers either "delete" or "restore".',
@@ -161,6 +175,7 @@
 
 See also:
 * {{msg-mw|Flow-comment-deleted}}',
+       'flow-comment-hidden' => 'Used as comment when the comment has been 
hidden.',
        'flow-comment-deleted' => 'Used as comment when the comment has been 
deleted.
 
 See also:
diff --git a/Flow.php b/Flow.php
index 7ff4339..042ecfd 100755
--- a/Flow.php
+++ b/Flow.php
@@ -165,6 +165,12 @@
        ),
 );
 
+// User permissions
+
+$wgGroupPermissions['autoconfirmed']['flow-hide'] = true;
+$wgGroupPermissions['sysop']['flow-delete'] = true;
+$wgGroupPermissions['oversight']['flow-oversight'] = true;
+
 // Configuration
 
 // URL for more information about the Flow notification system
diff --git a/flow.sql b/flow.sql
index caad08b..b6ef1fd 100644
--- a/flow.sql
+++ b/flow.sql
@@ -112,6 +112,13 @@
        rev_content mediumblob not null,
        -- comment attached to revision's flag change
        rev_comment varchar(255) binary null,
+       -- current moderation state
+       rev_mod_state varchar(32) binary not null,
+       -- moderated by who?
+       rev_mod_user_id bigint unsigned,
+       rev_mod_user_text varchar(255) binary,
+       rev_mod_timestamp varchar(14) binary,
+
        PRIMARY KEY (rev_id)
 ) /*$wgDBTableOptions*/;
 
diff --git a/includes/Block/Topic.php b/includes/Block/Topic.php
index e5c9d1b..8433452 100644
--- a/includes/Block/Topic.php
+++ b/includes/Block/Topic.php
@@ -4,6 +4,7 @@
 
 use Flow\Model\UUID;
 use Flow\Model\Workflow;
+use Flow\Model\AbstractRevision;
 use Flow\Model\PostRevision;
 use Flow\Data\ManagerGroup;
 use Flow\Data\RootPostLoader;
@@ -23,8 +24,12 @@
        // POST actions, GET do not need to be listed
        // unrecognized GET actions fallback to 'view'
        protected $supportedActions = array(
-               'edit-post', 'delete-post', 'restore-post',
-               'reply', 'delete-topic', 'edit-title',
+               // Standard editing
+               'edit-post', 'reply',
+               // Moderation
+               'hide-post', 'delete-post', 'oversight-post', 'restore-post',
+               // Other stuff
+               'hide-topic', 'edit-title',
        );
 
        public function __construct( Workflow $workflow, ManagerGroup $storage, 
$root ) {
@@ -50,13 +55,21 @@
                        $this->validateReply();
                        break;
 
-               case 'delete-topic':
+               case 'hide-topic':
                        // this should be a workflow level action, not 
implemented per-block
-                       $this->validateDeleteTopic();
+                       $this->validateHideTopic();
+                       break;
+
+               case 'hide-post':
+                       $this->validateModeratePost( 
AbstractRevision::MODERATED_HIDDEN );
                        break;
 
                case 'delete-post':
-                       $this->validateDeletePost();
+                       $this->validateModeratePost( 
AbstractRevision::MODERATED_DELETED );
+                       break;
+
+               case 'oversight-post':
+                       $this->validateModeratePost( 
AbstractRevision::MODERATED_OVERSIGHTED );
                        break;
 
                case 'restore-post':
@@ -112,33 +125,27 @@
                }
        }
 
-       protected function validateDeleteTopic() {
+       protected function validateHideTopic() {
                if ( !$this->workflow->lock( $this->user ) ) {
-                       $this->errors['delete-topic'] = wfMessage( 
'flow-error-delete-failure' );
+                       $this->errors['hide-topic'] = wfMessage( 
'flow-error-hide-failure' );
                }
        }
 
-       protected function validateDeletePost() {
+       protected function validateModeratePost( $moderationState ) {
                if ( empty( $this->submitted['postId'] ) ) {
-                       $this->errors['delete-post'] = wfMessage( 
'flow-error-missing-postId' );
+                       $this->errors['moderate-post'] = wfMessage( 
'flow-error-missing-postId' );
                        return;
                }
-               $found = $this->storage->find(
-                       'PostRevision',
-                       array( 'tree_rev_descendant_id' => UUID::create( 
$this->submitted['postId'] ) ),
-                       array( 'sort' => 'rev_id', 'order' => 'DESC', 'limit' 
=> 1 )
-               );
-               if ( !$found ) {
-                       $this->errors['delete-post'] = wfMessage( 
'flow-error-invalid-postId' );
+               $post = $this->loadRequestedPost( $this->submitted['postId'] );
+               if ( !$post ) {
+                       $this->errors['moderate-post'] = wfMessage( 
'flow-error-invalid-postId' );
                        return;
                }
-               // TODO: validate it has $this->workflow as its topic
-               $post = reset( $found );
 
-               // returns new revision to save
-               $this->newRevision = $post->addFlag( $this->user, 'deleted', 
'flow-comment-deleted' );
+               $this->newRevision = $post->moderate( $this->user, 
$moderationState );
                if ( !$this->newRevision ) {
-                       $this->errors['delete-post'] = wfMessage( 
'flow-error-delete-failure' );
+                       die( 'no allowed' );
+                       $this->errors['moderate'] = wfMessage( 
'flow-error-not-allowed' );
                }
        }
 
@@ -147,20 +154,15 @@
                        $this->errors['restore-post'] = wfMessage( 
'flow-error-missing-postId' );
                        return;
                }
-               $found = $this->storage->find(
-                       'PostRevision',
-                       array( 'tree_rev_descendant_id' => UUID::create( 
$this->submitted['postId'] ) ),
-                       array( 'sort' => 'rev_id', 'order' => 'DESC', 'limit' 
=> 1 )
-               );
-               if ( !$found ) {
+               $post = $this->loadRequestedPost( $this->submitted['postId'] );
+               if ( !$post ) {
                        $this->errors['restore-post'] = wfMessage( 
'flow-error-invalid-postId' );
                        return;
                }
-               $post = reset( $found );
 
-               $this->newRevision = $post->removeFlag( $this->user, 'deleted', 
'flow-comment-restored' );
+               $this->newRevision = $post->restore( $this->user );
                if ( !$this->newRevision ) {
-                       $this->errors['restore-post'] = wfMessage( 
'flow-error-restore-failure' );
+                       $this->errors['restore-post'] = wfMessage( 
'flow-error-not-allowed' );
                }
        }
 
@@ -189,7 +191,9 @@
        public function commit() {
                switch( $this->action ) {
                case 'reply':
+               case 'hide-post':
                case 'delete-post':
+               case 'oversight-post':
                case 'restore-post':
                case 'edit-title':
                case 'edit-post':
@@ -221,6 +225,7 @@
 
                        return $output;
                        break;
+
                case 'delete-topic':
                        $this->storage->put( $this->workflow );
 
@@ -291,6 +296,10 @@
                if ( !isset( $options['postId'] ) ) {
                        throw new \Exception( 'No postId provided' );
                }
+               $post = $this->loadRequestedPost( $options['postId'] );
+               if ( $post->isFlaggedAll( 'oversighted', 'deleted', 'hidden' ) 
) {
+                       throw new \Exception( 'Cannot edit restricted post.  
Restore first.' );
+               }
                return $templating->render( "flow:edit-post.html.php", array(
                        'block' => $this,
                        'topic' => $this->workflow,
@@ -353,7 +362,7 @@
 
                $output['post-id'] = $post->getPostId()->getHex();
 
-               if ( $post->isFlagged( 'deleted' ) ) {
+               if ( $post->isFlaggedAll( 'deleted' ) ) {
                        $output['post-deleted'] = 'post-deleted';
                } else {
                        $output['content'] = array( '*' => $post->getContent() 
);
diff --git a/includes/Data/ObjectManager.php b/includes/Data/ObjectManager.php
index b460160..7d04460 100644
--- a/includes/Data/ObjectManager.php
+++ b/includes/Data/ObjectManager.php
@@ -413,6 +413,22 @@
                }
        }
 
+       static public function calcUpdates( array $old, array $new ) {
+               $updates = array();
+               foreach ( array_keys( $new ) as $key ) {
+                       if ( !array_key_exists( $key, $old ) || $old[$key] !== 
$new[$key] ) {
+                               $updates[$key] = $new[$key];
+                       }
+                       unset( $old[$key] );
+               }
+               // These keys dont exist in $new
+               foreach ( array_keys( $old ) as $key ) {
+                       $updates[$key] = null;
+               }
+               return $updates;
+       }
+
+
        /**
         * Separate a set of keys from an array. Returns null if not
         * all keys are set.
@@ -506,7 +522,7 @@
                        $missing = array_diff( $this->primaryKey, array_keys( 
$old ) );
                        throw new PersistenceException( 'Row has null primary 
key: ' . implode( $missing ) );
                }
-               $updates = $this->calcUpdates( $old, $new );
+               $updates = ObjectManager::calcUpdates( $old, $new );
                if ( !$updates ) {
                        return true; // nothing to change, success
                }
@@ -517,22 +533,6 @@
                // we also want to check that $pk actually selected a row to 
update
                return $res && $dbw->affectedRows();
        }
-
-       protected function calcUpdates( array $old, array $new ) {
-               $updates = array();
-               foreach ( array_keys( $new ) as $key ) {
-                       if ( !array_key_exists( $key, $old ) || $old[$key] !== 
$new[$key] ) {
-                               $updates[$key] = $new[$key];
-                       }
-                       unset( $old[$key] );
-               }
-               // These keys dont exist in $new
-               foreach ( array_keys( $old ) as $key ) {
-                       $updates[$key] = null;
-               }
-               return $updates;
-       }
-
 
        /**
         * @return boolean success
diff --git a/includes/Data/RevisionStorage.php 
b/includes/Data/RevisionStorage.php
index 0233371..543d2ae 100644
--- a/includes/Data/RevisionStorage.php
+++ b/includes/Data/RevisionStorage.php
@@ -10,7 +10,9 @@
 use User;
 
 abstract class RevisionStorage implements WritableObjectStorage {
-       static protected $allowedUpdateColumns = array( 'rev_flags' );
+       static protected $allowedUpdateColumns = array(
+               'rev_mod_state', 'rev_mod_user_id', 'rev_mod_user_text', 
'rev_mod_timestamp',
+       );
        protected $dbFactory;
        protected $externalStores;
 
@@ -267,7 +269,8 @@
        // This is to *UPDATE* a revision.  It should hardly ever be used.
        // For the most part should insert a new revision.  This will only be 
called
        // for oversighting?
-       public function update( array $row, array $changeSet ) {
+       public function update( array $old, array $new ) {
+               $changeSet = ObjectManager::calcUpdates( $old, $new );
                $extra = array_diff( array_keys( $changeSet ), 
self::$allowedUpdateColumns );
                if ( $extra ) {
                        throw new \MWException( 'Update not allowed on: ' . 
implode( ', ', $extra ) );
@@ -279,14 +282,15 @@
                        $dbw = $this->dbFactory->getDB( DB_MASTER );
                        $res = $dbw->update(
                                'flow_revision',
-                               $updates,
-                               array( 'rev_id' => $row['rev_id'] ),
+                               $rev,
+                               array( 'rev_id' => $old['rev_id'] ),
                                __METHOD__
                        );
-                       if ( !( $res && $res->numRows() ) ) {
+                       if ( !( $res && $dbw->affectedRows() ) ) {
                                return false;
                        }
                }
+               // TODO: this probably wont work, it needs $row
                return $this->updateRelated( $rev, $related );
        }
 
@@ -456,12 +460,12 @@
        }
 
        public function onAfterInsert( $object, array $new ) {
-               $new['topic_root'] = $this->treeRepository->findRoot( 
UUID::create( $new['tree_rev_descendant_id'] ) );
+               $new['topic_root'] = $this->treeRepository->findRoot( 
UUID::create( $new['tree_rev_descendant_id'] ) )->getBinary();
                parent::onAfterInsert( $object, $new );
        }
 
        public function onAfterUpdate( $object, array $old, array $new ) {
-               $old['topic_root'] = $new['topic_root'] = 
$this->treeRepository->findRoot( UUID::create( $old['tree_rev_descendant_id'] ) 
);
+               $old['topic_root'] = $new['topic_root'] = 
$this->treeRepository->findRoot( UUID::create( $old['tree_rev_descendant_id'] ) 
)->getBinary();
                parent::onAfterUpdate( $object, $old, $new );
        }
 
diff --git a/includes/Model/AbstractRevision.php 
b/includes/Model/AbstractRevision.php
index 7b87cfc..f487c90 100644
--- a/includes/Model/AbstractRevision.php
+++ b/includes/Model/AbstractRevision.php
@@ -2,9 +2,39 @@
 
 namespace Flow\Model;
 
+use MWTimestamp;
 use User;
 
 abstract class AbstractRevision {
+       const MODERATED_NONE = '';
+       const MODERATED_HIDDEN = 'hide';
+       const MODERATED_DELETED = 'delete';
+
+       const MODERATED_OVERSIGHTED = 'oversight';
+
+       static private $perms = array(
+               self::MODERATED_NONE => array(
+                       'perm' => null,
+                       'usertext' => null,
+                       'content' => null
+               ),
+               self::MODERATED_HIDDEN => array(
+                       'perm' => 'flow-hide',
+                       'usertext' => 'flow-post-hidden',
+                       'content' => 'flow-post-hidden-by',
+               ),
+               self::MODERATED_DELETED => array(
+                       'perm' => 'flow-delete',
+                       'usertext' => 'flow-post-deleted',
+                       'content' => 'flow-post-deleted-by',
+               ),
+               self::MODERATED_OVERSIGHTED => array(
+                       'perm' => 'flow-oversight',
+                       'usertext' => 'flow-post-oversighted',
+                       'content' => 'flow-post-oversighted-by',
+               ),
+       );
+
        protected $revId;
        protected $userId;
        protected $userText;
@@ -22,6 +52,15 @@
        // This is decompressed on-demand from $this->content in 
self::getContent()
        protected $decompressedContent;
 
+       // moderation states for the revision.  This is technically 
denormalized data
+       // since it can be overwritten and does not provide a full history.
+       // The tricky part is updating moderation is a new revision for hide and
+       // delete, but adjusts an existing revision for full oversight.
+       protected $moderationState = self::MODERATED_NONE;
+       protected $moderationTimestamp;
+       protected $moderatedByUserId;
+       protected $moderatedByUserText;
+
        static public function fromStorageRow( array $row, $obj = null ) {
                if ( $obj === null ) {
                        $obj = new static;
@@ -33,11 +72,17 @@
                $obj->userText = $row['rev_user_text'];
                $obj->prevRevision = UUID::create( $row['rev_parent_id'] );
                $obj->comment = $row['rev_comment'];
-               $obj->flags = array_filter( explode( ',', $row['rev_flags'] ) );
+               $obj->flags = array_filter( explode( ',', $row['rev_flags'] ) );
                $obj->content = $row['rev_content'];
                // null if external store is not being used
                $obj->contentUrl = $row['rev_content_url'];
                $obj->decompressedContent = null;
+
+               $obj->moderationState = $row['rev_mod_state'];
+               $obj->moderatedByUserId = $row['rev_mod_user_id'];
+               $obj->moderatedByUserText = $row['rev_mod_user_text'];
+               $obj->moderationTimestamp = $row['rev_mod_timestamp'];
+
                return $obj;
        }
 
@@ -53,6 +98,11 @@
                        'rev_content' => $obj->content,
                        'rev_content_url' => $obj->contentUrl,
                        'rev_flags' => implode( ',', $obj->flags ),
+
+                       'rev_mod_state' => $obj->moderationState,
+                       'rev_mod_user_id' => $obj->moderatedByUserId,
+                       'rev_mod_user_text' => $obj->moderatedByUserText,
+                       'rev_mod_timestamp' => $obj->moderationTimestamp,
                );
        }
 
@@ -77,18 +127,91 @@
                return $obj;
        }
 
+       public function moderate( User $user, $state ) {
+               $mostRestricted = max( $state, $this->moderationState );
+               if ( !$this->isAllowed( $user, $mostRestricted ) ) {
+                       return null;
+               }
+               // Oversight is special,  other moderation types just create
+               // a new revision but oversighting adjusts the existing 
revision.
+               // Yes this mucks with the history just being a revision list.
+               if ( $state === self::MODERATED_OVERSIGHTED ) {
+                       $obj = $this;
+               } else {
+                       $obj = $this->newNullRevision( $user );
+               }
+
+               $obj->moderationState = $state;
+               if ( $state === self::MODERATED_NONE ) {
+                       $obj->moderatedByUserId = null;
+                       $obj->moderatedByUserText = null;
+                       $obj->moderationTimestamp = null;
+               } else {
+                       $obj->moderatedByUserId = $user->getId();
+                       $obj->moderatedByUserText = $user->getName();
+                       $obj->moderationTimestamp = wfTimestampNow();
+               }
+               return $obj;
+       }
+
+       public function restore( User $user ) {
+               return $this->moderate( $user, self::MODERATED_NONE );
+       }
+
        public function getRevisionId() {
                return $this->revId;
        }
 
-       public function getContent() {
+       // Is the user allowed to see this revision ?
+       protected function isAllowed( $user = null, $state = null ) {
+               if ( $state === null ) {
+                       $state = $this->moderationState;
+               }
+               if ( !isset( self::$perms[$state] ) ) {
+                       throw new \Exception( 'Unknown stored moderation state' 
);
+               }
+
+               $perm = self::$perms[$state]['perm'];
+               return $perm === null || ( $user && $user->isAllowed( $perm ) );
+       }
+
+       public function getContent( $user = null ) {
+               if ( $this->isAllowed( $user ) ) {
+                       return $this->getContentRaw();
+               } else {
+                       $moderatedAt = new MWTimestamp( 
$this->moderationTimestamp );
+
+                       return wfMessage(
+                               self::$perms[$this->moderationState]['content'],
+                               $this->moderatedByUserText,
+                               $moderatedAt->getHumanTimestamp( new 
MWTimestamp )
+                       );
+               }
+       }
+
+       public function getContentRaw() {
                if ( $this->decompressedContent === null ) {
                        $this->decompressedContent = 
\Revision::decompressRevisionText( $this->content, $this->flags );
                }
                return $this->decompressedContent;
        }
 
+       public function getUserText( $user = null ) {
+               if ( $this->isAllowed( $user ) ) {
+                       return $this->getUserTextRaw();
+               } else {
+                       return wfMessage( 
self::$perms[$this->moderationState]['usertext'] );
+               }
+       }
+
+       public function getUserTextRaw() {
+               return $this->userText;
+       }
+
        protected function setContent( $content ) {
+               if ( $this->moderationState !== self::MODERATED_NONE ) {
+                       throw new \Exception( 'Cannot change content of 
restricted revision' );
+               }
                if ( $content !== $this->getContent() ) {
                        $this->content = $this->decompressedContent = $content;
                        $this->contentUrl = null;
@@ -105,32 +228,34 @@
                return $this->comment;
        }
 
-       public function addFlag( User $user, $flag, $comment ) {
-               if ( $this->isFlagged( $flag ) ) {
-                       // already flagged
-                       return $this;
-               }
-               if ( false !== strpos( ',', $flag ) ) {
-                       throw new \MWException( 'Invalid flag name: contains 
comma' );
-               }
-               $updated = $this->newNullRevision( $user );
-               $updated->flags[] = $flag;
-               $updated->comment = $comment;
-               return $updated;
+       public function getModerationState() {
+               return $this->moderationState;
        }
 
-       public function removeFlag( User $user, $flag, $comment ) {
-               if ( !$this->isFlagged( $flag ) ) {
-                       return $this;
-               }
-               $updated = $this->newNullRevision( $user );
-               unset( $updated->flags[array_search( $flag, $updated->flags )] 
);
-               $updated->comment = $comment;
-               return $updated;
+       public function getModerationTimestamp() {
+               return $this->moderationTimestamp;
        }
 
-       public function isFlagged( $flag ) {
-               return false !== array_search( $flag, $this->flags );
+       /**
+        * @param string|array $flags
+        * @return boolean True when at least one flag in $flags is set
+        */
+       public function isFlaggedAny( $flags ) {
+               foreach ( (array) $flags as $flag ) {
+                       if ( false !== array_search( $flag, $this->flags ) ) {
+                               return true;
+                       }
+               }
+               return false;
+       }
+
+       public function isFlaggedAll( $flags ) {
+               foreach ( (array) $flags as $flag ) {
+                       if ( false === array_search( $flag, $this->flags ) ) {
+                               return false;
+                       }
+               }
+               return true;
        }
 }
 
diff --git a/includes/Model/PostRevision.php b/includes/Model/PostRevision.php
index e91da62..1d42315 100644
--- a/includes/Model/PostRevision.php
+++ b/includes/Model/PostRevision.php
@@ -83,10 +83,6 @@
                return $this->postId;
        }
 
-       public function getUserText() {
-               return $this->userText;
-       }
-
        public function isTopicTitle() {
                return $this->replyToId === null;
        }
diff --git a/templates/post.html.php b/templates/post.html.php
index 332e18e..e110546 100644
--- a/templates/post.html.php
+++ b/templates/post.html.php
@@ -48,47 +48,7 @@
        );
 };
 
-$renderPost = function( $post ) use( $self, $block ) {
-       echo $self->renderPost( $post, $block );
-};
-
-echo Html::openElement( 'div', array(
-       'class' => 'flow-post-container',
-       'data-post-id' => $post->getRevisionId()->getHex(),
-) );
-
-$class = 'flow-post';
-$actions = array();
-$replyForm = '';
-if ( $post->isFlagged( 'deleted' ) ) {
-       $class .= ' flow-post-deleted';
-}
-
-echo Html::openElement( 'div', array(
-       'class' => $class,
-       'data-post-id' => $post->getPostId()->getHex(),
-       'id' => 'flow-post-' . $post->getPostId()->getHex(),
-) );
-
-if ( $post->isFlagged( 'deleted' ) ) {
-       $content = wfMessage( 'flow-post-deleted' );
-       $user = wfMessage( 'flow-post-deleted' );
-
-       // TODO make conditional on rights
-       $actions['restore'] = $postAction( 'restore-post', array( 'postId' => 
$post->getPostId()->getHex() ), 'mw-ui-constructive' );
-
-       $actions['history'] = Html::element( 'a', array(
-               'href' => $self->generateUrl( $block->getWorkflowId(), 
'post-history', array(
-                       $block->getName() . '[postId]' => 
$post->getPostId()->getHex(),
-               ) ),
-       ), wfMessage( 'flow-post-action-history' )->plain() );
-} else {
-       $user = Html::element( 'span', null, $post->getUserText() );
-       $content = $post->getContent();
-       $actions['delete'] = $postAction( 'delete-post', array( 'postId' => 
$post->getPostId()->getHex() ), 'mw-ui-destructive' );
-       $actions['history'] = $getAction( 'post-history' );
-       $actions['permalink'] = $getAction( 'view' );
-       $actions['edit-post'] = $getAction( 'edit-post' );
+$createReplyForm = function() use( $self, $block, $post, $editToken ) {
        $replyForm = Html::openElement( 'form', array(
                        'method' => 'POST',
                        // root post id is same as topic workflow id
@@ -102,7 +62,8 @@
                        $replyForm .= $error->text() . '<br>'; // the pain ...
                }
        }
-       $replyForm .= Html::element( 'input', array(
+       return $replyForm .
+               Html::element( 'input', array(
                        'type' => 'hidden',
                        'name' => $block->getName() . '[replyTo]',
                        'value' => $post->getPostId()->getHex(),
@@ -125,53 +86,116 @@
                ), wfMessage( 'flow-disclaimer' )->parse() ) .
                Html::closeElement( 'div' ) .
                '</form>';
+};
+
+$class = 'flow-post';
+$content = $post->getContent();
+$userText = $post->getUserText();
+if ( !$userText instanceof \Message ) {
+       $userText = Html::element( 'span', null, $userText );
+}
+$actions = array();
+$replyForm = '';
+
+if ( $post->isFlaggedAny( 'oversighted', 'deleted', 'hidden' ) ) {
+       $class .= ' flow-post-deleted';
 }
 
-?>
-<div class="flow-post-title">
-       <div class="flow-post-authorline">
-<?php
-echo $user;
-?>
-               <span class="flow-datestamp">
-                       <span class="flow-agotime" style="display: 
inline">&lt;timestamp&gt;</span>
-                       <span class="flow-utctime" style="display: 
none">&lt;timestamp&gt;</span>
-               </span>
-       </div>
-</div>
+// Build the actions for the post
+switch( $post->getModerationState() ) {
+case $post::MODERATED_NONE:
+       if ( $user->isAllowed( 'flow-hide' ) ) {
+               $actions['hide'] = $postAction( 'hide-post', array( 'postId' => 
$post->getPostId()->getHex() ), 'mw-ui-destructive' );
+       }
+       if ( $user->isAllowed( 'flow-delete' ) ) {
+               $actions['delete'] = $postAction( 'delete-post', array( 
'postId' => $post->getPostId()->getHex() ), 'mw-ui-destructive' );
+       }
+       if ( $user->isAllowed( 'flow-oversight' ) ) {
+               $actions['oversight'] = $postAction( 'oversight-post', array( 
'postId' => $post->getPostId()->getHex() ), 'mw-ui-destructive' );
+       }
+       $actions['history'] = $getAction( 'post-history' );
+       $actions['edit-post'] = $getAction( 'edit-post' );
+       $replyForm = $createReplyForm();
+       break;
 
-<div class="flow-post-content">
-<?php
-echo $content
-?>
-</div>
-<div class="flow-post-controls">
-       <div class="flow-post-actions">
-               <a><?php echo wfMessage('flow-post-actions')->escaped(); ?></a>
-               <div class="flow-actionbox-pokey">&nbsp;</div>
-               <div class="flow-post-actionbox">
-                       <ul>
-<?php
-foreach( $actions as $key => $action ) {
-       echo '<li class="flow-action-'.$key.'">' . $action . "</li>\n";
+case $post::MODERATED_HIDDEN:
+       if ( $user->isAllowed( 'flow-hide' ) ) {
+               $actions['restore'] = $postAction( 'restore-post', array( 
'postId' => $post->getPostId()->getHex() ), 'mw-ui-constructive' );
+       }
+       if ( $user->isAllowed( 'flow-delete' ) ) {
+               $actions['delete'] = $postAction( 'delete-post', array( 
'postId' => $post->getPostId()->getHex() ), 'mw-ui-destructive' );
+       }
+       if ( $user->isAllowed( 'flow-oversight' ) ) {
+               $actions['oversight'] = $postAction( 'oversight-post', array( 
'postId' => $post->getPostId()->getHex() ), 'mw-ui-destructive' );
+       }
+       $actions['history'] = $getAction( 'post-history' );
+       break;
+
+case $post::MODERATED_DELETED:
+       if ( $user->isAllowedAny( 'flow-delete', 'flow-oversight' ) ) {
+               $actions['restore'] = $postAction( 'restore-post', array( 
'postId' => $post->getPostId()->getHex() ), 'mw-ui-constructive' );
+       }
+       $actions['history'] = $getAction( 'post-history' );
+       break;
+
+case $post::MODERATED_OVERSIGHTED:
+       if ( !$user->isAllowed( 'flow-oversight' ) ) {
+               // no children, no nothing
+               return;
+       }
+       $actions['restore'] = $postAction( 'restore-post', array( 'postId' => 
$post->getPostId()->getHex() ), 'mw-ui-constructive' );
+       $actions['history'] = $getAction( 'post-history' );
+       break;
 }
-?>
-                       </ul>
+
+// Default always-available actions
+$actions['permalink'] = $getAction( 'view' );
+
+// The actual output
+echo Html::openElement( 'div', array(
+       'class' => 'flow-post-container',
+       'data-post-id' => $post->getRevisionId()->getHex(),
+) );
+       echo Html::openElement( 'div', array(
+               'class' => $class,
+               'data-post-id' => $post->getPostId()->getHex(),
+               'id' => 'flow-post-' . $post->getPostId()->getHex(),
+       ) ); ?>
+               <div class="flow-post-title">
+                       <div class="flow-post-authorline">
+                               <?php echo $userText; ?>
+                               <span class="flow-datestamp">
+                                       <span class="flow-agotime" 
style="display: inline">&lt;timestamp&gt;</span>
+                                       <span class="flow-utctime" 
style="display: none">&lt;timestamp&gt;</span>
+                               </span>
+                       </div>
+               </div>
+
+               <div class="flow-post-content">
+                       <?php echo $content ?>
+               </div>
+               <div class="flow-post-controls">
+                       <div class="flow-post-actions">
+                               <a><?php echo wfMessage( 'flow-post-actions' 
)->escaped(); ?></a>
+                               <div class="flow-actionbox-pokey">&nbsp;</div>
+                               <div class="flow-post-actionbox">
+                                       <ul>
+                                               <?php
+                                               foreach( $actions as $key => 
$action ) {
+                                                       echo '<li 
class="flow-action-'.$key.'">' . $action . "</li>\n";
+                                               }
+                                               ?>
+                                       </ul>
+                               </div>
+                       </div>
                </div>
        </div>
+       <?php echo $replyForm; ?>
+       <div class='flow-post-replies'>
+               <?php
+               foreach( $post->getChildren() as $child ) {
+                       echo $this->renderPost( $child, $block );
+               }
+               ?>
+       </div>
 </div>
-<?php
-
-echo '</div>';
-
-echo $replyForm;
-
-echo Html::openElement( 'div', array(
-       'class' => 'flow-post-replies',
-) );
-foreach( $post->getChildren() as $child ) {
-       $renderPost( $child );
-}
-
-echo '</div>';
-echo '</div>';
\ No newline at end of file

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

Gerrit-MessageType: newchange
Gerrit-Change-Id: I39241b68fd56d74ffe5a5051777dcb6e09750d9d
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Flow
Gerrit-Branch: master
Gerrit-Owner: EBernhardson (WMF) <ebernhard...@wikimedia.org>

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

Reply via email to