...
 
Commits (2)
  • Brian Hatchet's avatar
    WIP firehose api · 4066f2d0
    Brian Hatchet authored
    New firehose admin endpoints for getting an unsorted list of activites from minds_badger and an admin endpoint that takes an activity guid and will mark it as moderated.
    
    New managers and repositories for talking to elastic search
    Redis lock system with some super basic hash keying. These records shouldn't get very big, so we might want to KISS for now. Else, there are distributed locking libraries we should look into
    
    It stores a key in moderation:entity_id:user_id format where the entity is the activity being moderated and user is the admin who "checked it out". These are set to a TTL of 15 minutes for now.
    When we query elastic search, we look for the opposite set (any locks that DON'T belong to the user) and for any activity that has not already been moderated.
    Requires adding two new fields to badger (in postman)
    4066f2d0
  • Mark Harding's avatar
    Merge branch 'admin_firehose' into 'master' · ba45f02b
    Mark Harding authored
    Firehose api
    
    See merge request !159
    ba45f02b
<?php
namespace Minds\Controllers\api\v2\admin;
use Minds\Api\Exportable;
use Minds\Api\Factory;
use Minds\Interfaces;
use Minds\Core\Di\Di;
use Minds\Core\Session;
use Minds\Entities\Activity;
class firehose implements Interfaces\Api, Interfaces\ApiAdminPam
{
/**
* Gets a list of entities sorted for admin approval.
*
* @param array $pages
*
* @throws \Exception
*/
public function get($pages)
{
$algorithm = $pages[0] ?? null;
if (!$algorithm) {
return Factory::response([
'status' => 'error',
'message' => 'Invalid algorithm'
]);
}
$type = '';
switch ($pages[1]) {
case 'activities':
$type = 'activity';
break;
case 'channels':
$type = 'user';
break;
case 'images':
$type = 'object:image';
break;
case 'videos':
$type = 'object:video';
break;
case 'groups':
$type = 'group';
break;
case 'blogs':
$type = 'object:blog';
break;
}
$period = $_GET['period'] ?? '12h';
if ($algorithm === 'hot') {
$period = '12h';
} elseif ($algorithm === 'latest') {
$period = '1y';
}
$hashtag = null;
if (isset($_GET['hashtag'])) {
$hashtag = strtolower($_GET['hashtag']);
}
$all = false;
if (!$hashtag && isset($_GET['all']) && $_GET['all']) {
$all = true;
}
$opts = [
'limit' => 12,
'offset' => 0,
'type' => $type,
'algorithm' => $algorithm,
'period' => $period,
'sync' => false,
'single_owner_threshold' => 0,
'nsfw' => [],
'moderation_user' => Session::getLoggedinUser(),
'exclude_moderated' => true
];
if ($hashtag) {
$opts['hashtags'] = [$hashtag];
$opts['filter_hashtags'] = true;
} elseif (isset($_GET['hashtags']) && $_GET['hashtags']) {
$opts['hashtags'] = explode(',', $_GET['hashtags']);
$opts['filter_hashtags'] = true;
} elseif (!$all) {
/** @var Core\Hashtags\User\Manager $hashtagsManager */
$hashtagsManager = Di::_()->get('Hashtags\User\Manager');
$hashtagsManager->setUser(Session::getLoggedInUser());
$result = $hashtagsManager->get([
'limit' => 50,
'trending' => false,
'defaults' => false,
]);
$opts['hashtags'] = array_column($result ?: [], 'value');
$opts['filter_hashtags'] = false;
}
try {
/** @var Core\Feeds\Firehose\Manager $manager */
$manager = Di::_()->get('Feeds\Firehose\Manager');
$activities = $manager->getList($opts);
} catch (\Exception $e) {
error_log($e);
return Factory::response(['status' => 'error', 'message' => $e->getMessage()]);
}
return Factory::response([
'status' => 'success',
'entities' => Exportable::_($activities)
]);
}
public function post($pages)
{
if (!is_numeric($pages[0])) {
header('X-Minds-Exception: entity guid required');
http_response_code(400);
return Factory::response(['status' => 'error', 'message' => 'entity guid required']);
}
/** @var EntitiesBuilder $entitiesBuilder */
$entitiesBuilder = Di::_()->get('EntitiesBuilder');
$entity = $entitiesBuilder->single($pages[0]);
$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);
return Factory::response([]);
}
public function put($pages)
{
return Factory::response([]);
}
public function delete($pages)
{
return Factory::response([]);
}
}
......@@ -12,6 +12,7 @@ use Minds\Entities;
use Minds\Entities\Activity;
use Minds\Interfaces;
use Minds\Core\Reports;
use Minds\Core\Reports\Jury\Decision;
class report implements Interfaces\Api
{
......@@ -80,6 +81,20 @@ class report implements Interfaces\Api
]);
}
// Auto accept admin reports
if ($user->isAdmin()) {
$decision = new Decision();
$decision->setAppeal(null)
->setAction('uphold')
->setUphold(true)
->setReport($report)
->setTimestamp(time())
->setJurorGuid($user->getGuid())
->setJurorHash($user->getPhoneNumberHash());
$juryManager = Di::_()->get('Moderation\Jury\Manager');
$juryManager->cast($decision);
}
return Factory::response([]);
}
......
......@@ -99,6 +99,21 @@ class DataProvider extends Provider
$this->di->bind('PubSub\Redis', function ($di) {
return new PubSub\Redis\Client();
}, ['useFactory'=>true]);
/**
* Redis
*/
$this->di->bind('Redis', function ($di) {
$master = $di->get('Config')->redis['master'];
$client = new Redis\Client();
$client->connect($master);
return $client;
}, ['useFactory'=>true]);
$this->di->bind('Redis\Slave', function ($di) {
$slave = $di->get('Config')->redis['slave'];
$client = new Redis\Client();
$client->connect($slave);
return $client;
}, ['useFactory'=>true]);
/**
* Prepared statements
*/
......
......@@ -38,6 +38,22 @@ class Client
return $this->redis->delete(...$args);
}
public function sAdd(...$args)
{
return $this->redis->sAdd(...$args);
}
public function sMembers(...$args)
{
return $this->redis->sMembers(...$args);
}
public function sRem(...$args)
{
return $this->redis->sRem(...$args);
}
public function __call($function, $arguments)
{
return $this->redis->$function(...$arguments);
......
<?php
/**
* Redis cacher
* Redis cacher.
*
* @author Mark Harding
*/
namespace Minds\Core\Data\cache;
use Minds\Core\Di\Di;
......@@ -10,8 +12,8 @@ use Redis as RedisServer;
class Redis extends abstractCacher
{
private $master = "127.0.0.1";
private $slave = "127.0.0.1";
private $master = '127.0.0.1';
private $slave = '127.0.0.1';
private $redisMaster;
private $redisSlave;
......@@ -20,8 +22,8 @@ class Redis extends abstractCacher
public function __construct($config = null)
{
$this->config = Di::_()->get('Config');
$this->master = $this->config->redis['master'];
$this->slave = $this->config->redis['slave'];
$this->master = $this->config->redis['master'];
$this->slave = $this->config->redis['slave'];
}
private function getMaster()
......@@ -30,6 +32,7 @@ class Redis extends abstractCacher
$this->redisMaster = new RedisServer();
$this->redisMaster->connect($this->master);
}
return $this->redisMaster;
}
......@@ -39,6 +42,7 @@ class Redis extends abstractCacher
$this->redisSlave = new RedisServer();
$this->redisSlave->connect($this->slave);
}
return $this->redisSlave;
}
......@@ -54,32 +58,47 @@ class Redis extends abstractCacher
$value = json_decode($value, true);
if (is_numeric($value)) {
$this->local[$key] = (int) $value;
return (int) $value;
}
$this->local[$key] = $value;
return $value;
}
} catch (\Exception $e) {
//error_log("could not read redis $this->slave");
//error_log($e->getMessage());
}
return false;
}
public function set($key, $value, $ttl = 0)
{
//error_log("still setting $key with value $value for $ttl seconds");
try {
$redis = $this->getMaster();
if ($ttl) {
$redis->set($key, json_encode($value), array('ex'=>$ttl));
} else {
$redis->set($key, json_encode($value));
}
} catch (\Exception $e) {
//error_log("could not write ($key) to redis $this->master");
try {
$redis = $this->getMaster();
if ($ttl) {
$redis->set($key, json_encode($value), array('ex' => $ttl));
} else {
$redis->set($key, json_encode($value));
}
} catch (\Exception $e) {
//error_log("could not write ($key) to redis $this->master");
//error_log($e->getMessage());
}
}
/** Iterate over redis keys that return a cursor
* iterator is passed by reference so you can paginate and get returned values
*/
public function scan(&$iterator, $pattern = null) {
try {
$redis = $this->getSlave();
return $redis->scan($iterator, $pattern);
} catch (\Exception $e) {
error_log($e->getMessage());
}
}
public function destroy($key)
......
......@@ -13,10 +13,17 @@ use Minds\Core\Session;
class Manager
{
/** @var User $user */
private $user;
/** @var Config $config */
private $config;
public function __construct($config = null)
{
$this->config = $config ?: Di::_()->get('Config');
}
/**
* Set the user
* @param User $user
......@@ -35,7 +42,7 @@ class Manager
*/
public function has($feature)
{
$features = Di::_()->get('Config')->get('features') ?: [];
$features = $this->config->get('features') ?: [];
if (!isset($features[$feature])) {
error_log("[Features\Manager] Feature '{$feature}' is not declared. Assuming true.");
......@@ -56,6 +63,6 @@ class Manager
*/
public function export()
{
return Di::_()->get('Config')->get('features') ?: [];
return $this->config->get('features') ?: [];
}
}
......@@ -4,18 +4,24 @@ namespace Minds\Core\Feeds;
use Minds\Core\Di\Provider;
class FeedsProvider extends Provider {
public function register() {
$this->di->bind('Feeds\Suggested\Repository', function($di) {
return new Suggested\Repository();
class FeedsProvider extends Provider
{
public function register()
{
$this->di->bind('Feeds\Suggested\Repository', function ($di) {
return new Suggested\Repository();
});
$this->di->bind('Feeds\Suggested\Manager', function($di) {
$this->di->bind('Feeds\Suggested\Manager', function ($di) {
return new Suggested\Manager();
});
$this->di->bind('Feeds\Top\Manager', function($di) {
$this->di->bind('Feeds\Top\Manager', function ($di) {
return new Top\Manager();
});
$this->di->bind('Feeds\Firehose\Manager', function ($di) {
return new Firehose\Manager();
});
}
}
<?php
namespace Minds\Core\Feeds\Firehose;
use Minds\Entities\User;
use Minds\Entities\Entity;
use Minds\Core\Entities\Actions\Save;
use Minds\Core\Di\Di;
use Minds\Core\Feeds\Top\Manager as TopFeedsManager;
class Manager
{
/** @var topFeedsManager */
protected $topFeedsManager;
/** @var ModerationCache */
protected $moderationCache;
public function __construct(
TopFeedsManager $topFeedsManager = null,
ModerationCache $moderationCache = null
) {
$this->topFeedsManager = $topFeedsManager ?: Di::_()->get('Feeds\Top\Manager');
$this->moderationCache = $moderationCache ?: new ModerationCache();
}
/**
* 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
*
* @return array entities that don't contain moderator_guids
*/
public function getList(array $opts = [])
{
$opts = array_merge([
'moderation_user' => null,
'exclude_moderated' => true,
'moderation_reservations' => null,
], $opts);
if ($opts['moderation_user']) {
$opts['moderation_reservations'] = $this->moderationCache->getKeysLockedByOtherUsers($opts['moderation_user']);
}
$response = $this->topFeedsManager->getList($opts);
if ($opts['moderation_user']) {
foreach ($response->toArray() as $entity) {
$this->moderationCache->store($entity->guid, $opts['moderation_user']);
}
}
return $response->filter(function ($entity) {
return $entity->get('moderator_guid') === null;
});
}
/**
* Marks an entity as moderated.
*
* @param Entity $entity the entity to mark as moderated
* @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
*/
public function save(
Entity $entity,
User $moderator,
int $reasonCode = null,
int $subreasonCode = null,
int $time = null)
{
if (!$time) {
$time = time();
}
$entity->setModeratorGuid($moderator->getGUID());
$entity->setTimeModerated($time);
$action = (new Save())
->setEntity($entity)
->save();
}
}
<?php
namespace Minds\Core\Feeds\Firehose;
use Minds\Core\Di\Di;
use Minds\Core\Data\Redis\Client as Redis;
use Minds\Core\Config;
use Minds\Entities\User;
class ModerationCache
{
const MODERATION_CACHE_TTL = 60 * 60; // 1 hour
const MODERATION_PREFIX = 'moderation_leases';
/** @var Redis $redis */
private $redis;
/** @var Config $config */
private $config;
public function __construct(
Redis $redis = null,
Config $config = null
) {
$this->config = $config ?: Di::_()->get('Config');
$this->redis = $redis ?: Di::_()->get('Redis');
}
/**
* Store a lease in a redis set undet the moderation prefix
*
* @param string $entityGUID
* @param User $user
* @param int $ttl
*/
public function store(string $entityGUID, User $user, int $time = null, int $ttl = ModerationCache::MODERATION_CACHE_TTL)
{
$time = $time ?: time();
try {
$expire = $time + $ttl;
$lease = implode(':', [
(string) $entityGUID,
$user->getGuid(),
$expire,
]);
$this->redis->sAdd(ModerationCache::MODERATION_PREFIX, $lease);
} catch (\Exception $e) {
error_log($e->getMessage());
}
}
/**
* Return leases that others have.
*
* @param User $user
*
* @return array
*/
public function getKeysLockedByOtherUsers(User $user, int $time = null)
{
$time = $time ?: time();
$locks = [];
try {
foreach ($this->redis->sMembers(ModerationCache::MODERATION_PREFIX) as $lease) {
list($entityGuid, $userGuid, $expire) = explode(':', $lease);
if ((int) $expire < $time) { //Should have expired, cleanup
$this->redis->sRem(ModerationCache::MODERATION_PREFIX, $lease);
}
if ($userGuid != $user->getGuid()) {
$locks[] = (int) $entityGuid;
}
}
} catch (Exception $e) {
error_log($e->getMessage());
}
return $locks;
}
}
......@@ -50,6 +50,8 @@ class Repository
'query' => null,
'nsfw' => null,
'from_timestamp' => null,
'exclude_moderated' => false,
'moderation_reservations' => null
], $opts);
if (!$opts['type']) {
......@@ -71,6 +73,7 @@ class Repository
'@timestamp',
'time_created',
'access_id',
'moderator_guid',
$this->getSourceField($opts['type']),
]),
'query' => [
......@@ -278,6 +281,21 @@ class Repository
}
}
// firehose options
if ($opts['exclude_moderated']) {
$body['query']['function_score']['query']['bool']['must_not'][] = ['exists' => ['field' => 'moderator_guid']];
}
if ($opts['moderation_reservations']) {
$body['query']['function_score']['query']['bool']['must_not'][] = [
'terms' => [
'guid' => $opts['moderation_reservations'],
],
];
}
//
$esQuery = $algorithm->getQuery();
......
......@@ -188,6 +188,13 @@ class EntityMapping implements MappingInterface
$map['nsfw'] = array_unique($this->entity->getNsfw());
if (isset($this->entity->moderator_guid) && !empty($this->entity->moderator_guid)) {
$map['moderator_guid'] = $this->entity->moderator_guid;
}
if (isset($this->entity->time_moderated) && $this->entity->time_moderated) {
$map['@moderated'] = $this->entity->time_created * 1000;
}
//
return $map;
......
......@@ -192,6 +192,7 @@ class AutoReporter
$decision
->setAppeal(null)
->setAction('uphold')
->setUphold(true)
->setReport($report)
->setTimestamp($time)
->setJurorGuid($stewardUser->getGuid())
......
......@@ -15,10 +15,7 @@ class ManagerSpec extends ObjectBehavior
function let(Config $config)
{
Di::_()->bind('Config', function ($di) use ($config) {
return $config->getWrappedObject();
});
$this->beConstructedWith($config);
$this->config = $config;
}
......@@ -47,60 +44,19 @@ class ManagerSpec extends ObjectBehavior
function it_should_check_if_a_user_is_active_for_an_admin_and_return_true(User $user)
{
$user = new User();
$user->guid = '1234';
$user->admin = true;
//remove ip whitelist check
$_SERVER['HTTP_X_FORWARDED_FOR'] = '10.56.0.1';
$this->setUser($user);
$this->config->get('development_mode')
$user->isAdmin()
->shouldBeCalled()
->willReturn(false);
$this->config->get('features')
->shouldBeCalled()
->willReturn(['plus' => true, 'wire' => 'admin']);
$this->config->get('last_tos_update')
->shouldBeCalled()
->willReturn(123456);
$this->config->get('admin_ip_whitelist')
->shouldBeCalled()
->willReturn([ '10.56.0.1' ]);
$this->has('wire')->shouldReturn(true);
}
function it_should_check_if_a_user_is_active_for_an_admin_and_return_true_development_node(User $user)
{
$user = new User();
$user->guid = '1234';
$user->admin = true;
->willReturn(true);
//remove ip whitelist check
$_SERVER['HTTP_X_FORWARDED_FOR'] = '10.56.0.1';
$this->setUser($user);
$this->config->get('development_mode')
->shouldBeCalled()
->willReturn(true);
$this->config->get('features')
->shouldBeCalled()
->willReturn(['plus' => true, 'wire' => 'admin']);
$this->config->get('last_tos_update')
->shouldBeCalled()
->willReturn(123456);
$this->config->get('admin_ip_whitelist')
->shouldNotBeCalled();
$this->has('wire')->shouldReturn(true);
}
......
<?php
namespace Spec\Minds\Core\Feeds\Firehose;
use PhpSpec\ObjectBehavior;
use Minds\Entities\User;
use Minds\Core\Feeds\Firehose\Manager;
use Minds\Common\Repository\Response;
use Minds\Entities\Activity;
use Minds\Entities\Entity;
use Minds\Core\Feeds\Top\Manager as TopFeedsManager;
use Minds\Core\Feeds\Firehose\ModerationCache;
class ManagerSpec extends ObjectBehavior
{
/** @var User */
protected $user;
/** @var TopFeedsManager */
protected $topFeedsManager;
/** @var ModerationCache */
protected $moderationCache;
protected $guids = [
'968599624820461570', '966142563226488850', '966145446911152135',
'966146759803801618', '968594045251096596', '966031787253829640',
'966032235331325967', '966030585254383635', '966020677003907088',
'966042003450105868',
];
public function let(
User $user,
TopFeedsManager $topFeedsManager,
ModerationCache $moderationCache)
{
$this->user = $user;
$this->topFeedsManager = $topFeedsManager;
$this->moderationCache = $moderationCache;
$this->user->getGUID()->willReturn('123');
$this->beConstructedWith(
$this->topFeedsManager,
$this->moderationCache
);
}
public function it_is_initializable()
{
$this->shouldHaveType(Manager::class);
}
public function it_should_return_results()
{
$activities = $this->getMockActivities();
/** @var Response $response */
$response = new Response($activities);
$this->topFeedsManager->getList([
'moderation_user' => $this->user,
'exclude_moderated' => true,
'moderation_reservations' => null,
])
->shouldBeCalled()
->willReturn($response);
$this->moderationCache->getKeysLockedByOtherUsers($this->user)
->shouldBeCalled();
$this->moderationCache->store('123', $this->user)
->shouldBeCalled();
$this->moderationCache->store('456', $this->user)
->shouldBeCalled();
$this->getList([
'moderation_user' => $this->user,
])->shouldBeLike($response);
}
public function it_should_return_results_without_a_user()
{
$activities = $this->getMockActivities();
/** @var Response $reponse */
$response = new Response($activities);
$activities = $this->getMockActivities($activities);
$this->topFeedsManager->getList([
'moderation_user' => null,
'exclude_moderated' => true,
'moderation_reservations' => null,
])
->shouldBeCalled()
->willReturn($response);
$this->getList()->shouldBeLike($response);
}
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();
$activity->setModeratorGuid('123')->shouldBeCalled();
$activity->setTimeModerated($time)->shouldBeCalled();
$activity->save()->shouldBeCalled();
$this->save($activity, $this->user, null, null, $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();
$activity->setModeratorGuid('123')->shouldBeCalled();
$activity->setTimeModerated($time)->shouldBeCalled();
$activity->save()->shouldBeCalled();
$this->save($activity, $this->user, 1, 1, $time);
}
private function getMockActivities(bool $moderated = false)
{
$entities = [];
$entity = new Activity();
$entity->guid = 123;
$entities[] = $entity;
$entity = new Activity();
$entity->guid = 456;
$entities[] = $entity;
return $entities;
}
}
<?php
namespace Spec\Minds\Core\Feeds\Firehose;
use Minds\Core\Feeds\Firehose\ModerationCache;
use PhpSpec\ObjectBehavior;
use Minds\Core\Data\Redis\Client as Redis;
use Minds\Core\Config;
use Minds\Entities\User;
class ModerationCacheSpec extends ObjectBehavior
{
protected $redis;
protected $config;
public function let(Redis $redis, Config $config)
{
$this->beConstructedWith($redis, $config);
$this->redis = $redis;
$this->config = $config;
}
public function it_is_initializable()
{
$this->shouldHaveType(ModerationCache::class);
}
public function it_should_store_a_key()
{
$time = time();
$expire = $time + ModerationCache::MODERATION_CACHE_TTL;
$user = (new User())
->set('guid', 456);
$this->redis->sAdd(ModerationCache::MODERATION_PREFIX, "123:456:{$expire}")->shouldBeCalled();
$this->store('123', $user, $time);
}
public function it_should_get_keys_locked_by_others()
{
$user = (new User())
->set('guid', 456);
$this->redis->sMembers(ModerationCache::MODERATION_PREFIX)
->shouldBeCalled()
->willReturn(['123:456:0', '789:012:0']);
$this->redis->sRem(ModerationCache::MODERATION_PREFIX, '123:456:0')->shouldBeCalled();
$this->redis->sRem(ModerationCache::MODERATION_PREFIX, '789:012:0')->shouldBeCalled();
$this->getKeysLockedByOtherUsers($user)->shouldEqual([789]);
}
}
......@@ -18,7 +18,8 @@ class ActivityMappingSpec extends ObjectBehavior
)
{
$now = time();
$activity->get('moderator_guid')->willReturn(null);
$activity->get('time_moderated')->willReturn(null);
$activity->get('rating')->willReturn(1);
$activity->get('interactions')->willReturn(42);
$activity->get('guid')->willReturn(5000);
......@@ -42,6 +43,69 @@ class ActivityMappingSpec extends ObjectBehavior
$activity->isPayWall()->willReturn(false);
$activity->getMature()->willReturn(false);
$this
->setEntity($activity)
->map([
'passedValue' => 'PHPSpec',
'guid' => '4999-will-disappear'
])
->shouldReturn([
'passedValue' => 'PHPSpec',
'guid' => '5000',
'interactions' => 42,
'type' => 'activity',
'subtype' => '',
'time_created' => $now,
'access_id' => '2',
'owner_guid' => '1000',
'container_guid' => '1000',
'mature' => false,
'message' => 'PHPSpec Message #test #hashtag',
'name' => 'PHPSpec Name',
'title' => 'PHPSpec Title',
'blurb' => 'PHPSpec Blurb',
'description' => 'PHPSpec Description',
'paywall' => false,
'rating' => 1,
'custom_type' => 'video',
'entity_guid' => '8000',
'@timestamp' => $now * 1000,
'taxonomy' => 'activity',
'public' => true,
'tags' => [ 'test', 'hashtag' ],
'nsfw' => [ 1 ]
]);
}
function it_should_map_an_activity_with_a_moderator(
Activity $activity
)
{
$now = time();
$activity->get('moderator_guid')->willReturn('123');
$activity->get('time_moderated')->willReturn(123);
$activity->get('rating')->willReturn(1);
$activity->get('interactions')->willReturn(42);
$activity->get('guid')->willReturn(5000);
$activity->get('type')->willReturn('activity');
$activity->get('subtype')->willReturn('');
$activity->get('time_created')->willReturn($now);
$activity->get('access_id')->willReturn(2);
$activity->get('owner_guid')->willReturn(1000);
$activity->get('container_guid')->willReturn(1000);
$activity->get('mature')->willReturn(false);
$activity->get('message')->willReturn('PHPSpec Message #test #hashtag');
$activity->get('name')->willReturn('PHPSpec Name');
$activity->get('title')->willReturn('PHPSpec Title');
$activity->get('blurb')->willReturn('PHPSpec Blurb');
$activity->get('description')->willReturn('PHPSpec Description');
$activity->get('paywall')->willReturn(false);
$activity->get('custom_type')->willReturn('video');
$activity->get('entity_guid')->willReturn(8000);
$activity->getNsfw()->willReturn([ 1 ]);
$activity->isPayWall()->willReturn(false);
$activity->getMature()->willReturn(false);
$this
->setEntity($activity)
->map([
......@@ -73,6 +137,8 @@ class ActivityMappingSpec extends ObjectBehavior
'public' => true,
'tags' => [ 'test', 'hashtag' ],
'nsfw' => [ 1 ],
'moderator_guid' => '123',
'@moderated' => $now * 1000
]);
}
}
......@@ -107,6 +107,8 @@ class EntityMappingSpec extends ObjectBehavior
$entity->get('paywall')->willReturn(false);
$entity->get('tags')->willReturn(['test', 'hashtag']);
$entity->get('rating')->willReturn(1);
$entity->get('moderator_guid')->willReturn('123');
$entity->get('time_moderated')->willReturn($now);
$entity->getNsfw()->willReturn([ 1 ]);
$this
......@@ -138,6 +140,8 @@ class EntityMappingSpec extends ObjectBehavior
'public' => true,
'tags' => [ 'test', 'hashtag' ],
'nsfw' => [ 1 ],
'moderator_guid' => '123',
'@moderated' => $now * 1000
]);
}
......
......@@ -39,6 +39,8 @@ class ObjectImageMappingSpec extends ObjectBehavior
$image->getFlag('mature')->willReturn(false);
$image->getFlag('paywall')->willReturn(false);
$image->get('moderator_guid')->willReturn('123');
$image->get('time_moderated')->willReturn($now);
$image->getNsfw()->willReturn([ 1 ]);
$this
......@@ -71,6 +73,8 @@ class ObjectImageMappingSpec extends ObjectBehavior
'public' => true,
'tags' => [ 'test', 'hashtag' ],
'nsfw' => [ 1 ],
'moderator_guid' => '123',
'@moderated' => $now * 1000
]);
}
}
......@@ -39,6 +39,8 @@ class ObjectVideoMappingSpec extends ObjectBehavior
$video->getFlag('mature')->willReturn(false);
$video->getFlag('paywall')->willReturn(false);
$video->get('moderator_guid')->willReturn('123');
$video->get('time_moderated')->willReturn($now);
$video->getNsfw()->willReturn([ 1 ]);
$this
......@@ -71,6 +73,8 @@ class ObjectVideoMappingSpec extends ObjectBehavior
'public' => true,
'tags' => [ 'test', 'hashtag' ],
'nsfw' => [ 1 ],
'moderator_guid' => '123',
'@moderated' => $now * 1000
]);
}
}
......@@ -37,6 +37,8 @@ class UserMappingSpec extends ObjectBehavior
$user->get('username')->willReturn('phpspec');
$user->get('briefdescription')->willReturn('PHPSpec Brief Description #invalidhashtag');
$user->get('rating')->willReturn(1);
$user->get('moderator_guid')->willReturn('123');
$user->get('time_moderated')->willReturn($now);
$user->isBanned()->willReturn(false);
$user->isMature()->willReturn(false);
......@@ -76,7 +78,9 @@ class UserMappingSpec extends ObjectBehavior
'public' => true,
'tags' => [ 'spaceiscool' ],
'nsfw' => [ 1 ],
'group_membership' => [ 2000 ],
'moderator_guid' => '123',
'@moderated' => $now * 1000,
'group_membership' => [ 2000 ]
]);
}
......@@ -110,7 +114,8 @@ class UserMappingSpec extends ObjectBehavior
$user->isMature()->willReturn(true);
$user->getMatureContent()->willReturn(false);
$user->getGroupMembership()->willReturn([ 2000 ]);
$user->get('moderator_guid')->willReturn(null);
$user->get('time_moderated')->willReturn(null);
$this
->setEntity($user)
->shouldThrow('Minds\Exceptions\BannedException')
......
......@@ -76,6 +76,7 @@ class AutoReporterSpec extends ObjectBehavior
->setAppeal(null)
->setAction('uphold')
->setReport($report)
->setUphold(true)
->setTimestamp(1)
->setJurorGuid($this->stewardUser->guid)
->setJurorHash(null);
......
......@@ -68,7 +68,10 @@ abstract class ElggEntity extends ElggData implements
$this->attributes['tags'] = null;
$this->attributes['nsfw'] = [];
$this->attributes['nsfw_lock'] = [];
$this->attributes['nsfw'] = [];
$this->attributes['nsfw_lock'] = [];
$this->attributes['moderator_guid'] = null;
$this->attributes['time_moderated'] = null;
}
/**
......@@ -1628,4 +1631,21 @@ abstract class ElggEntity extends ElggData implements
return "urn:entity:{$this->getGuid()}";
}
/**
* Marks the user as moderated by a user
* @param int $moderatorGuid the moderator
*/
public function setModeratorGuid($moderatorGuid)
{
$this->moderator_guid = $moderatorGuid;
}
/**
* Marks the time as when an entity was moderated
* @param int $timeModerated unix timestamp when the entity was moderated
*/
public function setTimeModerated($timeModerated)
{
$this->time_moderated = $timeModerated;
}
}