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"><timestamp></span>
- <span class="flow-utctime" style="display:
none"><timestamp></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"> </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"><timestamp></span>
+ <span class="flow-utctime"
style="display: none"><timestamp></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"> </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) <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits