...
 
Commits (12)
......@@ -135,14 +135,8 @@ class firehose implements Interfaces\Api, Interfaces\ApiAdminPam
$moderator = Session::getLoggedinUser();
$manager = Di::_()->get('Feeds\Firehose\Manager');
if (isset($_POST['reason'])) {
$reasonCode = $_POST['reason'];
}
if (isset($_POST['subreason_code'])) {
$reasonCode = $_POST['subreason'];
}
$manager->save($entity, $moderator, $reasonCode, $subReasonCode);
$manager->save($entity, $moderator);
return Factory::response([]);
}
......
......@@ -89,6 +89,10 @@ use Minds\Traits\MagicAttributes;
* @method bool isEphemeral()
* @method Blog setHidden(bool $value)
* @method bool isHidden()
* @method Blog setModeratorGuid(int $moderatorGuid)
* @method int getModeratorGuid()
* @method Blog setTimeModerated(int $timeModerated)
* @method int getTimeModerated()
*/
class Blog extends RepositoryEntity
{
......@@ -222,8 +226,15 @@ class Blog extends RepositoryEntity
/** @var array */
protected $nsfw = [];
/** @var array */
protected $nsfwLock = [];
/** @var int */
protected $moderatorGuid;
/** @var int */
protected $timeModerated;
/**
* Blog constructor.
* @param null $eventsDispatcher
......@@ -631,4 +642,13 @@ class Blog extends RepositoryEntity
return $output;
}
/**
* Return the URN
* @return string
*/
public function getUrn()
{
return "urn:blog:{$this->getGuid()}";
}
}
......@@ -49,6 +49,8 @@ class Entity
'boostRejectionReason' => 'boost_rejection_reason',
'ownerObj' => 'ownerObj',
'nsfw' => 'nsfw',
'moderatorGuid' => 'moderator_guid',
'timeModerated' => 'time_moderated'
];
static $jsonEncodedFields = [
......
......@@ -4,6 +4,8 @@ namespace Minds\Core\Feeds\Firehose;
use Minds\Entities\User;
use Minds\Entities\Entity;
use Minds\Core\EntitiesBuilder;
use Minds\Core\Data\Call;
use Minds\Core\Entities\Actions\Save;
use Minds\Core\Di\Di;
use Minds\Core\Feeds\Top\Manager as TopFeedsManager;
......@@ -14,22 +16,34 @@ class Manager
protected $topFeedsManager;
/** @var ModerationCache */
protected $moderationCache;
/** @var EntitiesBuilder $entitiesBuilder */
protected $entitiesBuilder;
/** @var Call */
protected $db;
/** @var Save */
protected $save;
public function __construct(
TopFeedsManager $topFeedsManager = null,
ModerationCache $moderationCache = null
ModerationCache $moderationCache = null,
EntitiesBuilder $entitiesBuilder = null,
Call $db = null,
Save $save = null
) {
$this->topFeedsManager = $topFeedsManager ?: Di::_()->get('Feeds\Top\Manager');
$this->moderationCache = $moderationCache ?: new ModerationCache();
$this->entitiesBuilder = $entitiesBuilder ?: Di::_()->get('EntitiesBuilder');
$this->db = $db ?: new Call('entities_by_time');
$this->save = $save ?: new Save(); //Mockable, else instantiate a new one on save.
}
/**
* Gets the top feed and filters out any entities that have been moderated
* It caches entities for 1 hour in redis so moderators don't do double work.
*
* @param array $opts filtering options
* Pass in a moderation_user to cache the returned entities for that user
*
*
* @param array $opts filtering options
* Pass in a moderation_user to cache the returned entities for that user
*
* @return array entities that don't contain moderator_guids
*/
public function getList(array $opts = [])
......@@ -53,35 +67,54 @@ class Manager
}
return $response->filter(function ($entity) {
return $entity->get('moderator_guid') === null;
return (!$entity->getModeratorGuid());
});
}
/**
* Marks an entity as moderated.
*
* @param $entity the entity to mark as moderated, typeless because images do not inherit entity
* @param User $user the moderator
* @param int $reasonCode providing a reason code will cause it be reported
* @param int $subreaonCode report subreason
* @param int $time
* @param $entity the entity to mark as moderated, typeless because images do not inherit entity
* @param User $user the moderator
* @param int $time
*/
public function save(
$entity,
User $moderator,
int $reasonCode = null,
int $subreasonCode = null,
int $time = null)
{
if (!$time) {
$time = time();
}
//Save the entity
$this->saveEntity($entity, $moderator, $time);
if (method_exists($entity, 'getType')
&& $entity->getType() == 'activity'
&& $entity->get('entity_guid')
) {
$attachment = $this->entitiesBuilder->single($entity->get('entity_guid'));
$this->saveEntity($attachment, $moderator, $time);
}
//Moderate parents
foreach ($this->db->getRow('activity:entitylink:'.$entity->getGUID()) as $parentGuid => $ts) {
$activity = $this->entitiesBuilder->single($parentGuid);
$this->saveEntity($activity, $moderator, $time);
}
}
private function saveEntity(
$entity,
User $moderator,
int $time = null)
{
$entity->setModeratorGuid($moderator->getGUID());
$entity->setTimeModerated($time);
$action = (new Save())
->setEntity($entity)
->save();
$this->save
->setEntity($entity)
->save();
}
}
......@@ -75,7 +75,7 @@ class Events
->setToGuid($params['to'])
->setFromGuid($from_user->getGuid())
->setEntityGuid($entityGuid)
->setEntityUrn($entity->getUrn())
->setEntityUrn(method_exists($entity, 'getUrn') ? $entity->getUrn() : "urn:entity:$entityGuid")
->setType($params['notification_view'])
->setData($data);
......
......@@ -33,6 +33,8 @@ class EntityMapping implements MappingInterface
'nsfw' => [ 'type' => 'integer' ],
'paywall' => [ 'type' => 'boolean', '$exportField' => 'paywall' ],
'rating' => [ 'type' => 'integer', '$exportField' => 'rating' ],
'moderator_guid' => [ 'type' => 'text'],
'@moderated' => [ 'type' => 'date'],
];
/** @var mixed $entity */
......
......@@ -33,6 +33,8 @@ class ObjectBlogMapping extends EntityMapping implements MappingInterface
'license' => [ 'type' => 'text', '$exportGetter' => 'getLicense' ],
'rating' => [ 'type' => 'integer', '$exportField' => 'rating' ],
'nsfw' => [ 'type' => 'array', '$exportGetter' => 'getNsfw' ],
'moderator_guid' => [ 'type' => 'text' ],
'@moderated' => [ 'type' => 'date' ],
];
/** @var Blog $entity */
......@@ -158,8 +160,14 @@ class ObjectBlogMapping extends EntityMapping implements MappingInterface
$map['rating'] = (int) $this->entity->getRating();
//
if ($this->entity->getModeratorGuid()) {
$map['moderator_guid'] = $this->entity->getModeratorGuid();
}
if ($this->entity->getTimeModerated()) {
$map['@moderated'] = $this->entity->getTimeModerated() * 1000;
}
//
return $map;
}
......
......@@ -7,6 +7,15 @@ use Minds\Interfaces\Flaggable;
/**
* File Entity
* @todo Do not inherit from ElggFile
* @package Minds\Entities\File
* @method array getExportableValues()
* @method mixed|null getFlag(string $flag)
* @method File setFlag(string $flag, mixed $value)
* @method void save(bool $index)
* @method array getWireTotals()
* @method mixed getWireThreshold()
* @method File setWireThreshold(int $wire_threshold)
* @method int getModeratorGUID()
*/
class File extends \ElggFile implements Flaggable
{
......@@ -21,6 +30,8 @@ class File extends \ElggFile implements Flaggable
$this->attributes['flags'] = [];
$this->attributes['wire_threshold'] = 0;
$this->attributes['moderator_guid'] = null;
$this->attributes['time_moderated'] = null;
}
/**
......@@ -31,7 +42,9 @@ class File extends \ElggFile implements Flaggable
{
return array_merge(parent::getExportableValues(), [
'flags',
'wire_threshold'
'wire_threshold',
'moderator_guid',
'time_moderated'
]);
}
......@@ -141,4 +154,40 @@ class File extends \ElggFile implements Flaggable
$this->wire_threshold = $wire_threshold;
return $this;
}
/**
* Returns the user who moderated
* @return int moderator guid
*/
public function getModeratorGuid() {
return $this->moderator_guid;
}
/**
* Sets the user who moderated
* @param int $moderatorGuid
* @return File
*/
public function setModeratorGuid(int $moderatorGuid) {
$this->moderator_guid = $moderatorGuid;
return $this;
}
/**
* Returns when the file was moderated
* @return int time_moderated timestamp
*/
public function getTimeModerated() {
return $this->time_moderated;
}
/**
* Sets when the file was moderated
* @param int $timeModerated
* @return File
*/
public function setTimeModerated(int $timeModerated) {
$this->time_moderated = $timeModerated;
return $this;
}
}
......@@ -8,8 +8,13 @@ use Minds\Core\Feeds\Firehose\Manager;
use Minds\Common\Repository\Response;
use Minds\Entities\Activity;
use Minds\Entities\Entity;
use Minds\Core\Blogs\Blog;
use Minds\Entities\Image;
use Minds\Core\Feeds\Top\Manager as TopFeedsManager;
use Minds\Core\Feeds\Firehose\ModerationCache;
use Minds\Core\EntitiesBuilder;
use Minds\Core\Data\Call;
use Minds\Core\Entities\Actions\Save;
class ManagerSpec extends ObjectBehavior
{
......@@ -19,6 +24,12 @@ class ManagerSpec extends ObjectBehavior
protected $topFeedsManager;
/** @var ModerationCache */
protected $moderationCache;
/** @var EntitiesBuilder */
protected $entitiesBuilder;
/** @var Call */
protected $db;
/** @var Save */
protected $save;
protected $guids = [
'968599624820461570', '966142563226488850', '966145446911152135',
......@@ -30,16 +41,26 @@ class ManagerSpec extends ObjectBehavior
public function let(
User $user,
TopFeedsManager $topFeedsManager,
ModerationCache $moderationCache)
{
ModerationCache $moderationCache,
EntitiesBuilder $entitiesBuilder,
Call $db,
Save $save
) {
$this->user = $user;
$this->topFeedsManager = $topFeedsManager;
$this->moderationCache = $moderationCache;
$this->entitiesBuilder = $entitiesBuilder;
$this->db = $db;
$this->save = $save;
$this->user->getGUID()->willReturn(123);
$this->user->getGUID()->willReturn('123');
$this->beConstructedWith(
$this->topFeedsManager,
$this->moderationCache
$this->moderationCache,
$this->entitiesBuilder,
$this->db,
$this->save
);
}
......@@ -97,32 +118,79 @@ class ManagerSpec extends ObjectBehavior
public function it_should_save_moderated_activites(Entity $activity)
{
$time = time();
$activity->getNsfw()->shouldBeCalled()->willReturn([]);
$activity->getNsfwLock()->shouldBeCalled()->willReturn([]);
$activity->getOwnerEntity()->shouldBeCalled()->willReturn(null);
$activity->getContainerEntity()->shouldBeCalled()->willReturn(null);
$activity->setNsfw([])->shouldBeCalled();
$this->db->getRow('activity:entitylink:1')->shouldBeCalled()->willReturn([]);
$activity->getType()->shouldBeCalled()->willReturn('activity');
$activity->get('entity_guid')->shouldBeCalled()->willReturn(false);
$activity->getGUID()->shouldBeCalled()->willReturn(1);
$activity->setModeratorGuid('123')->shouldBeCalled();
$activity->setTimeModerated($time)->shouldBeCalled();
$activity->save()->shouldBeCalled();
$this->save($activity, $this->user, null, null, $time);
$this->save->setEntity($activity)->shouldBeCalled()->willReturn($this->save);
$this->save->save()->shouldBeCalled();
$this->save($activity, $this->user, $time);
}
public function it_should_save_reported_activites(Entity $activity)
{
$time = time();
$activity->getNsfw()->shouldBeCalled()->willReturn([]);
$activity->getNsfwLock()->shouldBeCalled()->willReturn([]);
$activity->getOwnerEntity()->shouldBeCalled()->willReturn(null);
$activity->getContainerEntity()->shouldBeCalled()->willReturn(null);
$activity->setNsfw([])->shouldBeCalled();
$this->db->getRow('activity:entitylink:1')->shouldBeCalled()->willReturn([]);
$activity->getType()->shouldBeCalled()->willReturn('activity');
$activity->get('entity_guid')->shouldBeCalled()->willReturn(false);
$activity->getGUID()->shouldBeCalled()->willReturn(1);
$activity->setTimeModerated($time)->shouldBeCalled();
$activity->setModeratorGuid('123')->shouldBeCalled();
$this->save->setEntity($activity)->shouldBeCalled()->willReturn($this->save);
$this->save->save()->shouldBeCalled();
$this->save($activity, $this->user, $time);
}
public function it_should_save_an_attachment(Entity $activity, Image $image)
{
$time = time();
$image->setModeratorGuid(123)->shouldBeCalled();
$image->setTimeModerated($time)->shouldBeCalled();
$this->db->getRow('activity:entitylink:1')->shouldBeCalled()->willReturn([]);
$this->entitiesBuilder->single(1)->shouldBeCalled()->willReturn($image);
$activity->getType()->shouldBeCalled()->willReturn('activity');
$activity->get('entity_guid')->shouldBeCalled()->willReturn(1);
$activity->getGUID()->shouldBeCalled()->willReturn(1);
$activity->setTimeModerated($time)->shouldBeCalled();
$activity->save()->shouldBeCalled();
$activity->setModeratorGuid(123)->shouldBeCalled();
$this->save->setEntity($activity)->shouldBeCalled()->willReturn($this->save);
$this->save->setEntity($image)->shouldBeCalled()->willReturn($this->save);
$this->save->save()->shouldBeCalled();
$this->save($activity, $this->user, $time);
}
public function it_should_save_a_blog(Blog $blog)
{
$time = time();
$this->db->getRow('activity:entitylink:1')->shouldBeCalled()->willReturn([]);
$blog->getType()->shouldBeCalled()->willReturn('object');
$blog->getGuid()->shouldBeCalled()->willReturn(1);
$blog->setTimeModerated($time)->shouldBeCalled();
$blog->setModeratorGuid('123')->shouldBeCalled();
$this->save->save()->shouldBeCalled();
$this->save->setEntity($blog)->shouldBeCalled()->willReturn($this->save);
$this->save($blog, $this->user, $time);
}
$this->save($activity, $this->user, 1, 1, $time);
public function it_should_save_a_linked_entity(Entity $activity, Entity $parent)
{
$time = time();
$parent->setTimeModerated($time)->shouldBeCalled();
$parent->setModeratorGuid('123')->shouldBeCalled();
$this->db->getRow('activity:entitylink:1')->shouldBeCalled()
->willReturn([2 => $parent]);
$this->entitiesBuilder->single(2)->shouldBeCalled()->willReturn($parent);
$activity->getType()->shouldBeCalled()->willReturn('activity');
$activity->get('entity_guid')->shouldBeCalled()->willReturn(false);
$activity->getGUID()->shouldBeCalled()->willReturn(1);
$activity->setTimeModerated($time)->shouldBeCalled();
$activity->setModeratorGuid('123')->shouldBeCalled();
$this->save->setEntity($activity)->shouldBeCalled()->willReturn($this->save);
$this->save->setEntity($parent)->shouldBeCalled()->willReturn($this->save);
$this->save->save()->shouldBeCalled();
$this->save($activity, $this->user, $time);
}
private function getMockActivities(bool $moderated = false)
......
......@@ -36,7 +36,8 @@ class ObjectBlogMappingSpec extends ObjectBehavior
$blog->getTags()->willReturn([ 'art' ]);
$blog->getRating()->willReturn(1);
$blog->getNsfw()->willReturn([ 1 ]);
$blog->getModeratorGuid()->willReturn('3');
$blog->getTimeModerated()->willReturn($now);
$this
->setEntity($blog)
->map([
......@@ -64,6 +65,8 @@ class ObjectBlogMappingSpec extends ObjectBehavior
'public' => true,
'tags' => [ 'art', 'test', 'hashtag' ],
'rating' => 1,
'moderator_guid' => '3',
'@moderated' => $now * 1000
]);
}
}
......@@ -15,6 +15,8 @@
* @property int $access_id Specifies the visibility level of this entity
* @property int $time_created A UNIX timestamp of when the entity was created (read-only, set on first save)
* @property int $time_updated A UNIX timestamp of when the entity was last updated (automatically updated on save)
* @property int $moderator_guid The GUID of the moderator
* @property int $moderated_at A UNIX timestamp of when the entity was moderated
* @property-read string $enabled
*/
abstract class ElggEntity extends ElggData implements
......@@ -1631,11 +1633,19 @@ abstract class ElggEntity extends ElggData implements
return "urn:entity:{$this->getGuid()}";
}
/** gets the guid of the moderator
* @return int
*/
public function getModeratorGuid() {
return $this->moderator_guid;
}
/**
* Marks the user as moderated by a user
* @param int $moderatorGuid the moderator
*/
public function setModeratorGuid($moderatorGuid)
public function setModeratorGuid(int $moderatorGuid)
{
$this->moderator_guid = $moderatorGuid;
}
......@@ -1644,8 +1654,16 @@ abstract class ElggEntity extends ElggData implements
* Marks the time as when an entity was moderated
* @param int $timeModerated unix timestamp when the entity was moderated
*/
public function setTimeModerated($timeModerated)
public function setTimeModerated(int $timeModerated)
{
$this->time_moderated = $timeModerated;
}
}
/**
* Gets the time moderated
* @return int
*/
public function getTimeModerated() {
return $this->time_moderated;
}
}