...
 
Commits (14)
......@@ -50,6 +50,16 @@ class Top extends Cli\Controller implements Interfaces\CliControllerInterface
return $this->syncBy('object', 'blog', $this->getOpt('period') ?? null, $this->getOpt('metric') ?? null);
}
public function sync_groups()
{
return $this->syncBy('group', null, $this->getOpt('period') ?? null, $this->getOpt('metric') ?? null);
}
public function sync_channels()
{
return $this->syncBy('user', null, $this->getOpt('period') ?? null, $this->getOpt('metric') ?? null);
}
protected function syncBy($type, $subtype, $period, $metric)
{
if (!$period) {
......
......@@ -15,6 +15,7 @@ use Minds\Helpers;
use Minds\Interfaces;
use Minds\Core\Events\Dispatcher;
use Minds\Api\Factory;
use Minds\Core\Entities\Actions\Save;
class media implements Interfaces\Api, Interfaces\ApiIgnorePam
{
......@@ -221,6 +222,7 @@ class media implements Interfaces\Api, Interfaces\ApiIgnorePam
private function _upload($clientType, array $data = [], array $media = [])
{
$user = Core\Session::getLoggedInUser();
$save = new Save();
// @note: Sometimes images are uploaded as videos. Polyfill:
$mimeIsImage = strpos($media['type'], 'image/') !== false;
......@@ -267,7 +269,10 @@ class media implements Interfaces\Api, Interfaces\ApiIgnorePam
$entity->setAssets($assets->upload($media, $data));
// Save initial entity
$success = $entity->save(true);
$success = $save
->setEntity($entity)
->save(true);
if (!$success) {
throw new \Exception('Error saving media entity');
......
......@@ -43,6 +43,7 @@ class notifications implements Interfaces\Api
{
Factory::isLoggedIn();
$response = [];
return;
if (!isset($pages[0])) {
$pages = ['list'];
......
......@@ -93,8 +93,8 @@ class fetch implements Interfaces\Api
foreach ($iterator as $boost) {
$feedSyncEntity = new Core\Feeds\FeedSyncEntity();
$feedSyncEntity
->setGuid($boost->getGuid())
->setOwnerGuid($boost->getOwnerGuid())
->setGuid((string) $boost->getGuid())
->setOwnerGuid((string) $boost->getOwnerGuid())
->setUrn(new Urn("urn:boost:{$boost->getType()}:{$boost->getGuid()}"));
$boosts[] = $feedSyncEntity;
......
......@@ -158,6 +158,7 @@ class feeds implements Interfaces\Api
'period' => $period,
'sync' => $sync,
'query' => $query ?? null,
'single_owner_threshold' => Core\Session::isAdmin() ? 0 : 36,
];
$nsfw = $_GET['nsfw'] ?? '';
......
......@@ -130,6 +130,7 @@ class container implements Interfaces\Api
'sync' => $sync,
'from_timestamp' => $fromTimestamp,
'query' => $query,
'single_owner_threshold' => 0,
];
if (isset($_GET['nsfw'])) {
......
......@@ -104,6 +104,7 @@ class subscribed implements Interfaces\Api
'from_timestamp' => $fromTimestamp,
'query' => $query ?? null,
'nsfw' => null,
'single_owner_threshold' => 0,
];
try {
......
......@@ -24,6 +24,7 @@ class appeals implements Interfaces\Api
$appeals = $appealManager->getList([
'hydrate' => true,
'showAppealed' => ($pages[0] ?? 'review') === 'pending',
'state' => $pages[0],
'owner_guid' => Core\Session::getLoggedInUser()->getGuid(),
]);
......
......@@ -19,7 +19,7 @@ class jury implements Interfaces\Api
$juryType = $pages[0] ?? 'appeal';
if ($juryType !== 'appeal' && !Core\Session::isAdmin()) {
exit;
//exit;
}
$juryManager = Di::_()->get('Moderation\Jury\Manager');
......@@ -40,7 +40,7 @@ class jury implements Interfaces\Api
{
$juryType = $pages[0] ?? null;
$entityGuid = $pages[1] ?? null;
$action = $_POST['decision'] ?? null;
$uphold = $_POST['uphold'] ?? null;
if (!$juryType) {
return Factory::response([
......@@ -56,14 +56,14 @@ class jury implements Interfaces\Api
]);
}
if (!$action) {
if (!isset($uphold)) {
return Factory::response([
'status' => 'error',
'message' => 'decision must be supplied in POST body',
'message' => 'uphold must be supplied in POST body',
]);
}
if (!Core\Session::getLoggedInUser()->getPhoneNumberHash()) {
if (!Core\Session::getLoggedInUser()->getPhoneNumberHash() && false) {
return Factory::response([
'status' => 'error',
'message' => 'juror must be in the rewards program',
......@@ -77,7 +77,7 @@ class jury implements Interfaces\Api
$decision = new Decision();
$decision
->setAppeal($juryType === 'appeal')
->setAction($action)
->setUphold($uphold)
->setReport($report)
->setTimestamp(round(microtime(true) * 1000))
->setJurorGuid(Core\Session::getLoggedInUser()->getGuid())
......
<?php
/**
* Api endpoint to get strikes
*/
namespace Minds\Controllers\api\v2\moderation;
use Minds\Api\Factory;
use Minds\Core;
use Minds\Entities;
use Minds\Entities\Activity;
use Minds\Interfaces;
use Minds\Core\Di\Di;
use Minds\Core\Reports\Appeals\Appeal;
class strikes implements Interfaces\Api
{
public function get($pages)
{
if ($_POST['offset']) {
return Factory::response([ ]);
}
$strikesManager = Di::_()->get('Moderation\Strikes\Manager');
$strikes = $strikesManager->getList([
'hydrate' => true,
'user' => Core\Session::getLoggedInUser(),
]);
return Factory::response([
'strikes' => Factory::exportable($strikes),
]);
}
public function post($pages)
{
return Factory::response([]);
}
public function put($pages)
{
return Factory::response([]);
}
public function delete($pages)
{
return Factory::response([]);
}
}
......@@ -34,7 +34,7 @@ class EntityGuidResolverDelegate implements ResolverDelegate
*/
public function shouldResolve(Urn $urn)
{
return $urn->getNid() === 'entity';
return $urn->getNid() === 'entity' || $urn->getNid() === 'activity';
}
/**
......@@ -90,6 +90,10 @@ class EntityGuidResolverDelegate implements ResolverDelegate
return null;
}
if ($entity->getUrn()) {
return $entity->getUrn();
}
if (method_exists($entity, '_magicAttributes') || method_exists($entity, 'getGuid')) {
return "urn:entity:{$entity->getGuid()}";
} elseif (isset($entity->guid)) {
......
......@@ -116,8 +116,8 @@ class Resolver
$sorted = [];
foreach ($this->urns as $urn) {
$sorted[] = $resolvedMap[$urn->getUrn()] ?? null;
foreach ($resolvedMap as $entity) {
$sorted[] = $entity ?? null;
}
// Filter out invalid entities
......@@ -141,4 +141,11 @@ class Resolver
return $sorted;
}
public function single($urn)
{
$this->urns = [ $urn ];
$entities = $this->fetch();
return $entities[0];
}
}
......@@ -87,6 +87,8 @@ class Manager
'from_timestamp' => null,
'query' => null,
'nsfw' => null,
'single_owner_threshold' => 36,
'filter_hashtags' => false,
], $opts);
if (isset($opts['query']) && $opts['query'] && in_array($opts['type'], ['user', 'group'])) {
......@@ -94,19 +96,32 @@ class Manager
$response = new Response($result);
return $response;
}
}
$feedSyncEntities = [];
$scores = [];
$owners = [];
$i = 0;
foreach ($this->repository->getList($opts) as $scoredGuid) {
if (!$scoredGuid->getGuid()) {
continue;
}
$ownerGuid = $scoredGuid->getOwnerGuid() ?: $scoredGuid->getGuid();
if (++$i < $opts['single_owner_threshold']
&& isset($owners[$ownerGuid])
&& !$opts['filter_hashtags']
&& !in_array($opts['type'], [ 'user', 'group' ])
) {
continue;
}
$owners[$ownerGuid] = true;
$feedSyncEntities[] = (new FeedSyncEntity())
->setGuid($scoredGuid->getGuid())
->setOwnerGuid($scoredGuid->getOwnerGuid())
->setGuid((string) $scoredGuid->getGuid())
->setOwnerGuid((string) $ownerGuid)
->setUrn(new Urn($scoredGuid->getGuid()))
->setTimestamp($scoredGuid->getTimestamp());
......@@ -169,8 +184,8 @@ class Manager
$response = $this->search->suggest('user', $opts['query'], $opts['limit']);
foreach ($response as $row) {
$feedSyncEntities[] = (new FeedSyncEntity())
->setGuid($row['guid'])
->setOwnerGuid($row['guid'])
->setGuid((string) $row['guid'])
->setOwnerGuid((string) $row['guid'])
->setUrn(new Urn($row['guid']))
->setTimestamp($row['time_created'] * 1000);
}
......
......@@ -94,7 +94,7 @@ class Repository
'sort' => [],
];
if ($opts['type'] === 'group') {
/*if ($opts['type'] === 'group' && false) {
if (!isset($body['query']['function_score']['query']['bool']['must_not'])) {
$body['query']['function_score']['query']['bool']['must_not'] = [];
}
......@@ -109,7 +109,7 @@ class Repository
'access_id' => '2',
],
];
}
}*/
//
......@@ -309,7 +309,7 @@ class Repository
$query = [
'index' => $this->index,
'type' => in_array($opts['type'], ['user', 'group']) ? 'activity' : $opts['type'],
'type' => $opts['type'],
'body' => $body,
'size' => $opts['limit'],
'from' => $opts['offset'],
......@@ -338,12 +338,12 @@ class Repository
private function getSourceField(string $type)
{
switch ($type) {
case 'user':
return 'owner_guid';
break;
case 'group':
return 'container_guid';
break;
//case 'user':
// return 'owner_guid';
// break;
//case 'group':
// return 'container_guid';
// break;
default:
return 'guid';
break;
......
......@@ -5,6 +5,7 @@ use Minds\Core;
use Minds\Core\Di\Di;
use Minds\Entities;
use Minds\Core\Events\Dispatcher;
use Minds\Core\Reports\Report;
class Actions
{
......
......@@ -11,6 +11,8 @@ use Minds\Core\Data\Cassandra\Prepared;
use Minds\Entities;
use Minds\Entities\DenormalizedEntity;
use Minds\Entities\NormalizedEntity;
use Minds\Core\Entities\Resolver as EntitiesResolver;
use Minds\Common\Urn;
class Manager
{
......@@ -21,17 +23,17 @@ class Manager
/** @var NotificationDelegate $notificationDelegate */
private $notificationDelegate;
/** @var EntitiesBuilder $entitesBuilder */
private $entitesBuilder;
/** @var EntitiesResolver $entitiesResolver */
private $entitiesResolver;
public function __construct(
$repository = null,
$entitesBuilder = null,
$entitiesResolver = null,
$notificationDelegate = null
)
{
$this->repository = $repository ?: new Repository;
$this->entitiesBuilder = $entitesBuilder ?: Di::_()->get('EntitiesBuilder');
$this->entitiesResolver = $entitiesResolver ?: new EntitiesResolver;
$this->notificationDelegate = $notificationDelegate ?: new Delegates\NotificationDelegate;
}
......@@ -51,7 +53,9 @@ class Manager
if ($opts['hydrate']) {
foreach ($response as $appeal) {
$report = $appeal->getReport();
$entity = $this->entitiesBuilder->single($report->getEntityGuid());
$entity = $this->entitiesResolver->single(
(new Urn())->setUrn($report->getEntityUrn())
);
$report->setEntity($entity);
$appeal->setReport($report);
}
......
......@@ -3,8 +3,10 @@ namespace Minds\Core\Reports\Appeals;
use Cassandra;
use Cassandra\Bigint;
use Cassandra\Tinyint;
use Cassandra\Decimal;
use Cassandra\Type;
use Cassandra\Type\Map;
use Cassandra\Map;
use Cassandra\Timestamp;
use Minds\Core;
use Minds\Core\Di\Di;
......@@ -27,7 +29,7 @@ class Repository
public function __construct($cql = null, $reportsRepository = null)
{
$this->cql = $cql ?: Di::_()->get('Database\Cassandra\Client');
$this->cql = $cql ?: Di::_()->get('Database\Cassandra\Cql');
$this->reportsRepository = $reportsRepository ?: new ReportsRepository;
}
......@@ -62,6 +64,36 @@ class Repository
foreach ($results as $row) {
$report = $this->reportsRepository->buildFromRow($row);
// TODO: make this on the query level
$skip = false;
switch ($opts['state']) {
case 'review':
if ($report->getState() != 'initial_jury_decided') {
$skip = true;
}
break;
case 'pending':
if ($report->getState() != 'appealed') {
$skip = true;
}
break;
case 'approved':
if ($report->getState() != 'appeal_jury_decided' && $report->isUpheld() === false) {
$skip = true;
}
break;
case 'rejected':
if ($report->getState() != 'appeal_jury_decided' && $report->isUpheld() === true) {
$skip = true;
}
break;
}
if ($skip) {
continue;
}
$appeal = new Appeal;
$appeal
->setTimestamp($report->getAppealTimestamp())
......@@ -80,22 +112,24 @@ class Repository
public function add(Appeal $appeal)
{
$statement = "UPDATE moderation_reports
SET appeal_note = ?
SET state = ?
SET state_changes += ?
SET appeal_note = ?,
state = ?,
state_changes += ?
WHERE entity_urn = ?
AND reason_code = ?
AND sub_reason_code = ?
AND timestamp = ?";
$stateChanges = new Map(Type::text(), Type::timestamp());
$stateChanges->set('appealed', new Timestamp($appeal->getTimestamp()));
$values = [
$appeal->getNote(),
'appealed',
(new Map(Type::text(), Type::bigint()))
->set('appealed', $appeal->getTimestamp()),
$stateChanges,
$appeal->getReport()->getEntityUrn(),
(float) $appeal->getReport()->getReasonCode(),
(float) $appeal->getReport()->getSubReasonCode(),
new Tinyint($appeal->getReport()->getReasonCode()),
new Decimal($appeal->getReport()->getSubReasonCode()),
new Timestamp($appeal->getReport()->getTimestamp()),
];
......
......@@ -12,6 +12,8 @@ use Minds\Entities;
use Minds\Entities\DenormalizedEntity;
use Minds\Entities\NormalizedEntity;
use Minds\Common\Repository\Response;
use Minds\Common\Urn;
use Minds\Core\Entities\Resolver as EntitiesResolver;
class Manager
{
......@@ -19,8 +21,8 @@ class Manager
/** @var Repository $repository */
private $repository;
/** @var EntitiesBuilder $entitiesBuilder */
private $entitiesBuilder;
/** @var EntitiesBuilder $entitiesResolver */
private $entitiesResolver;
/** @var VerdictManager $verdictManager */
private $verdictManager;
......@@ -33,12 +35,12 @@ class Manager
public function __construct(
$repository = null,
$entitiesBuilder = null,
$entitiesResolver = null,
$verdictManager = null
)
{
$this->repository = $repository ?: new Repository;
$this->entitiesBuilder = $entitiesBuilder ?: Di::_()->get('EntitiesBuilder');
$this->entitiesResolver = $entitiesResolver ?: new EntitiesResolver;
$this->verdictManager = $verdictManager ?: Di::_()->get('Moderation\Verdict\Manager');
}
......@@ -93,7 +95,9 @@ class Manager
if ($opts['hydrate']) {
foreach ($response as $report) {
$entity = $this->entitiesBuilder->single($report->getEntityGuid());
$entity = $this->entitiesResolver->single(
(new Urn())->setUrn($report->getEntityUrn())
);
$report->setEntity($entity);
}
}
......
......@@ -3,6 +3,13 @@ namespace Minds\Core\Reports\Jury;
use Cassandra;
use Cassandra\Type;
use Cassandra\Map;
use Cassandra\Set;
use Cassandra\Bigint;
use Cassandra\Tinyint;
use Cassandra\Decimal;
use Cassandra\Timestamp;
use Minds\Core;
use Minds\Core\Di\Di;
use Minds\Core\Data;
......@@ -25,7 +32,7 @@ class Repository
public function __construct($cql = null, $reportsRepository = null)
{
$this->cql = $cql ?: Di::_()->get('Database\Cassandra\Client');
$this->cql = $cql ?: Di::_()->get('Database\Cassandra\Cql');
$this->reportsRepository = $reportsRepository ?: new ReportsRepository;
}
......@@ -46,7 +53,7 @@ class Repository
], $opts);
if (!$opts['user']->getPhoneNumberHash()) {
return null;
//return null;
}
$statement = "SELECT * FROM moderation_reports_by_state
......@@ -64,7 +71,8 @@ class Repository
$response = new Response;
foreach ($result as $row) {
if (in_array($opts['user']->getPhoneNumberHash(),
if ($row['user_hashes']
&& in_array($opts['user']->getPhoneNumberHash(),
array_map(function ($hash) {
return $hash;
}, $row['user_hashes']->values())
......@@ -89,29 +97,35 @@ class Repository
public function add(Decision $decision)
{
$statement = "UPDATE moderation_reports
SET initial_jury += ?
SET user_hashes += ?
SET initial_jury += ?,
user_hashes += ?
WHERE entity_urn = ?
AND reason_code = ?
AND sub_reason_code = ?";
AND sub_reason_code = ?
AND timestamp = ?";
if ($decision->isAppeal()) {
$statement = "UPDATE moderation_reports
SET appeal_jury += ?
SET user_hashes += ?
SET appeal_jury += ?,
user_hashes += ?
WHERE entity_urn = ?
AND reason_code = ?
AND sub_reason_code = ?";
AND sub_reason_code = ?
AND timestamp = ?";
}
$map = new Map(Type::bigint(), Type::boolean());
$map->set(new Bigint($decision->getJurorGuid()), $decision->isUpheld());
$set = new Set(Type::text());
$set->add($decision->getJurorHash() ?? 'testing');
$params = [
(new Type\Map(Type::bigint(), Type::boolean()))
->set($decision->getJurorGuid(), $decision->isUpheld()),
(new Type\Set(Type::text()))
->set($decision->getJurorHash()),
$map,
$set,
$decision->getReport()->getEntityUrn(),
(float) $decision->getReport()->getReasonCode(),
(float) $decision->getReport()->getSubReasonCode(),
new Tinyint($decision->getReport()->getReasonCode()),
new Decimal($decision->getReport()->getSubReasonCode()),
new Timestamp($decision->getReport()->getTimestamp()),
];
$prepared = new Prepared();
......
......@@ -56,7 +56,7 @@ class Manager
}
/**
* Indempotent fucntion to return the latest report found
* Indempotent function to return the latest report found
* or supplied
* @param Report $report
* @return Report
......@@ -65,7 +65,18 @@ class Manager
{
$report->setState('reported')
->setTimestamp(round(microtime(true) * 1000));
return $report;
$reports = $this->getList([
'entity_urn' => $report->getEntityUrn(),
'reason_code' => $report->getReasonCode(),
'sub_reason_code' => $report->getSubReasonCode(),
]);
if (!$reports || !count($reports)) {
return $report;
}
return $reports[0];
}
}
......@@ -38,5 +38,8 @@ class Provider extends DiProvider
$this->di->bind('Moderation\Stats\Manager', function ($di) {
return new Stats\Manager;
}, [ 'useFactory'=> true ]);
$this->di->bind('Moderation\Strikes\Manager', function ($di) {
return new Strikes\Manager;
}, [ 'useFactory'=> true ]);
}
}
......@@ -19,6 +19,11 @@ use Minds\Traits\MagicAttributes;
* @method Report getAppealTimestamp: int
* @method Report getReasonCode(): int
* @method Report getSubReasonCode(): int
* @method Report setState(string $string)
* @method Report getState(): string
* @method Report setTimestamp(int $timestamp)
* @method Report setReasonCode(int $value)
* @method Report setSubReasonCode(int $value)
*/
class Report
{
......@@ -69,12 +74,22 @@ class Report
/** @var array $userHashes */
private $userHashes;
/** @var string $state */
private $state;
/** @var array $stateChanges */
private $stateChanges;
/**
* Return the state of the report from the state changes
*/
public function getState()
{
if (!$this->stateChanges) {
return 'reported';
}
$sortedStates = $this->stateChanges;
arsort($sortedStates);
return key($sortedStates);
}
/**
* Return the URN of this case
* @return string
......@@ -97,15 +112,16 @@ class Report
{
$export = [
'urn' => $this->getUrn(),
'entity_guid' => $this->entityGuid,
'entity_urn' => $this->entityUrn,
'entity' => $this->entity ? $this->entity->export() : null,
/*'reports' => $this->reports ? array_map(function($report){
return $report->export();
}, $this->reports) : [],*/
'is_appeal' => (bool) $this->isAppeal(),
'appeal_note' => $this->getAppealNote(),
'reason_code' => $this->getReasonCode(),
'sub_reason_code' => $this->getSubReasonCode(),
'reason_code' => (int) $this->getReasonCode(),
'sub_reason_code' => (int) $this->getSubReasonCode(),
'state' => $this->getState(),
];
return $export;
......
......@@ -2,26 +2,34 @@
namespace Minds\Core\Reports;
use Cassandra;
use Cassandra\Decimal;
use Cassandra\Timestamp;
use Cassandra\Tinyint;
use Minds\Core;
use Minds\Core\Di\Di;
use Minds\Core\Data;
use Minds\Core\Data\ElasticSearch\Prepared;
use Minds\Core\Data\Cassandra\Prepared;
use Minds\Entities;
use Minds\Entities\DenormalizedEntity;
use Minds\Entities\NormalizedEntity;
use Minds\Common\Repository\Response;
use Minds\Core\Reports\UserReports\UserReport;
use Minds\Core\Reports\ReportedEntity;
use Minds\Common\Urn;
class Repository
{
/** @var Data\ElasticSearch\Client $es */
protected $es;
/** @var Data\Cassandra\Client $cql */
protected $cql;
public function __construct($es = null)
/** @var Urn $urn */
protected $urn;
public function __construct($cql = null, $urn = null)
{
$this->es = $es ?: Di::_()->get('Database\ElasticSearch');
$this->cql = $cql ?: Di::_()->get('Database\Cassandra\Cql');
$this->urn = $urn ?: new Urn;
}
/**
......@@ -40,9 +48,10 @@ class Repository
'user' => null,
'must' => [],
'must_not' => [],
'timestamp' => null
], $opts);
$must = $opts['must'];
/*$must = $opts['must'];
$must_not = $opts['must_not'];
$body = [
......@@ -81,6 +90,46 @@ class Repository
->setInitialJuryDecidedTimestamp($row['_source']['@appeal_jury_decided_timestamp'] ?? null);
$response[] = $report;
}*/
$response = new Response;
$statement = "SELECT * FROM moderation_reports";
$where = [];
$values = [];
if ($opts['entity_urn']) {
$where[] = "entity_urn = ?";
$values[] = $opts['entity_urn'];
}
if ($opts['reason_code']) {
$where[] = "reason_code = ?";
$values[] = new Tinyint($opts['reason_code']);
}
if ($opts['sub_reason_code']) {
$where[] = "sub_reason_code = ?";
$values[] = new Decimal($opts['sub_reason_code']);
}
if ($opts['timestamp']) {
$where[] = "timestamp = ?";
$values[] = new Timestamp($opts['timestamp']);
}
if ($where) {
$statement .= " WHERE " . implode(' AND ', $where);
}
$prepared = new Prepared\Custom();
$prepared->query($statement, $values);
$rows = $this->cql->request($prepared);
foreach ($rows as $row) {
$response[] = $this->buildFromRow($row);
}
return $response;
......@@ -88,29 +137,31 @@ class Repository
/**
* Return a single report
* @param int $entity_guid
* @param string $urn
* @return ReportEntity
*/
public function get($entity_guid)
public function get($urn)
{
$prepared = new Prepared\Document();
$prepared->query([
'index' => 'minds-moderation',
'type' => 'reports',
'id' => $entity_guid,
]);
$parts = explode('-', $this->urn->setUrn($urn)->getNss());
$result = $this->es->request($prepared);
$entityUrn = substr($parts[0], 1, -1); // Remove the parenthases
$reasonCode = $parts[1];
$subReasonCode = $parts[2];
$timestamp = $parts[3];
$report = new Report();
$report
->setEntityGuid($result['_source']['entity_guid'])
->setReports($this->buildReports($result['_source']))
->setAppeal(isset($result['_source']['@initial_jury_decided_timestamp']))
->setInitialJuryDecisions($this->buildDecisions($result, 'initial'))
->setAppealJuryDecisions($this->buildDecisions($result, 'appeal'));
$response = $this->getList([
'entity_urn' => $entityUrn,
'reason_code' => $reasonCode,
'sub_reason_code' => $subReasonCode,
'timestamp' => $timestamp,
]);
return $report;
if (!$response[0]) {
return null;
}
return $response[0];
}
/**
......@@ -141,30 +192,31 @@ class Repository
$return = [];
$reports = $juryType === 'appeal'
? ($row['_source']['appeal_jury'] ?? [])
: ($row['_source']['initial_jury'] ?? []);
foreach ($reports as $row) {
if (!isset($row[0]['action'])) {
continue; // Something didn't save properly
}
if (isset($jurorGuids[$row[0]['juror_guid']])) { //TODO: change to juror_hash
foreach ($row as $jurorGuid => $uphold) {
if (isset($jurorGuids[$jurorGuid])) { //TODO: change to juror_hash
continue; // avoid duplicate reports
}
$jurorGuids[$row[0]['juror_guid']] = true;
$jurorGuids[$jurorGuids] = true;
$decision = new Jury\Decision();
$decision
->setTimestamp($row[0]['@timestamp'])
->setEntityGuid($row['_source']['entity_guid'])
->setJurorGuid($row[0]['juror_guid'])
->setAction($row[0]['action']);
->setJurorGuid($jurorGuid)
->setUphold($uphold);
$return[] = $decision;
}
return $return;
}
private function mapToAssoc($map)
{
$assoc = [];
foreach ($map as $k => $v) {
$assoc[(string) $k] = $v;
}
return $assoc;
}
/**
* Build from a row
* @param array $row
......@@ -174,28 +226,36 @@ class Repository
{
$report = new Report;
$report->setEntityUrn((string) $row['entity_urn'])
->setEntityOwnerGuid($row['entity_owner_guid']->value())
->setReasonCode($row['reason_code']->toFloat())
->setSubReasonCode($row['sub_reason_code']->toFloat())
->setEntityOwnerGuid(isset($row['entity_owner_guid']) ? $row['entity_owner_guid']->value() : null)
->setReasonCode($row['reason_code']->value())
->setSubReasonCode($row['sub_reason_code']->value())
->setTimestamp($row['timestamp']->time())
->setState((string) $row['state'])
//->setState((string) $row['state'])
->setUphold(isset($row['uphold']) ? (bool) $row['uphold'] : null)
->setStateChanges($row['state_changes']->values())
->setStateChanges(isset($row['state_changes']) ?
array_map(function ($timestamp) {
return $timestamp->microtime(true);
}, $this->mapToAssoc($row['state_changes']))
: null
)
->setAppeal(isset($row['appeal_note']) ? true : false)
->setAppealNote(isset($row['appeal_note']) ? (string) $row['appeal_note'] : '')
->setReports(
$this->buildReports($row['reports']->values())
)
->setInitialJuryDecisions(
isset($row['initial_jury']) ?
$this->buildDecisions($row['initial_jury']->values())
$this->buildDecisions($this->mapToAssoc($row['initial_jury']))
: null
)
->setAppealJuryDecision(
isset($row['appeal_jury']) ?
$this->buildDecisions($row['appeal_jury']->values())
$this->buildDecisions($this->mapToAssoc($row['appeal_jury']))
: null
)
->setUserHashes($row['user_hashes']->values());
->setUserHashes(isset($row['user_hashes']) ?
$row['user_hashes']->values() : null
);
return $report;
}
......
......@@ -6,6 +6,11 @@ namespace Minds\Core\Reports\Strikes;
class Manager
{
//const STRIKE_TIME_WINDOW = (60 * 60) * 24; // 24 hours
const STRIKE_TIME_WINDOW = 60;
const STRIKE_RETENTION_WINDOW = (60 * 60) * 24 * 90; // 90 days
/** @var Repository $repository */
private $repository;
......@@ -48,4 +53,21 @@ class Manager
return $this->repository->add($strike);
}
/**
* Return if a strike exists in the configured time window
* @param Strike $strike
* @return int
*/
public function countStrikesInTimeWindow($strike, $window)
{
$strikes = $this->repository->getList([
'user_guid' => $strike->getUserGuid(),
'reason_code' => $strike->getReasonCode(),
'sub_reason_code' => $strike->getSubReasonCode(),
'from' => time() - $window,
]);
return count($strikes);
}
}
\ No newline at end of file
......@@ -11,6 +11,8 @@ use Minds\Common\Urn;
use Minds\Common\Repository\Response;
use Cassandra\Timestamp;
use Cassandra\Bigint;
use Cassandra\Tinyint;
use Cassandra\Decimal;
class Repository
{
......@@ -22,7 +24,7 @@ class Repository
public function __construct($cql = null, $urn = null)
{
$this->cql = $cql ?: Di::_()->get('Database\Cassandra\Client');
$this->cql = $cql ?: Di::_()->get('Database\Cassandra\Cql');
$this->urn = $urn ?: new Urn();
}
......@@ -55,12 +57,12 @@ class Repository
if ($opts['reason_code']) {
$statement .= " AND reason_code = ?";
$values[] = (float) $opts['reason_code'];
$values[] = new Tinyint($opts['reason_code']);
}
if ($opts['sub_reason_code']) {
$statement .= " AND sub_reason_code = ?";
$values[] = (float) $opts['sub_reason_code'];
$values[] = new Decimal($opts['sub_reason_code']);
}
if ($opts['timestamp']) {
......@@ -78,6 +80,10 @@ class Repository
$values[] = new Timestamp($opts['to'] * 1000);
}
if (!$opts['reason_code'] && !$opts['sub_reason_code']) {
$statement .= " ALLOW FILTERING";
}
$prepared = new Prepared;
$prepared->query($statement, $values);
......@@ -90,8 +96,8 @@ class Repository
$strike
->setUserGuid($row['user_guid']->value())
->setTimestamp($row['timestamp']->time())
->setReasonCode($row['reason_code']->toFloat())
->setSubReasonCode($row['sub_reason_code']->toFloat())
->setReasonCode((int) $row['reason_code']->value())
->setSubReasonCode((int) $row['sub_reason_code']->value())
->setReportUrn($row['report_urn']);
$response[] = $strike;
}
......@@ -134,8 +140,8 @@ class Repository
$values = [
new Bigint($strike->getUserGuid()),
new Timestamp($strike->getTimestamp()),
(float) $strike->getReasonCode(),
(float) $strike->getSubReasonCode(),
new Tinyint($strike->getReasonCode()),
new Decimal($strike->getSubReasonCode()),
$strike->getReportUrn(),
];
......
......@@ -51,21 +51,25 @@ class Repository
public function add(UserReport $report)
{
$statement = "UPDATE moderation_reports
SET reports += ?";
SET reports += ?,
state = 'reported',
entity_owner_guid = ?";
$set = new Set(Type::bigint());
$set->add(new Bigint($report->getReporterGuid()));
$values = [
$set,
new Bigint($report->getReport()->getEntityOwnerGuid()),
];
if ($report->getReporterHash()) {
$statement .= ", user_hashes += ?";
$values[] = (new Set(Type::text()))
->add($report->getReporterHash());
$hashesSet = new Set(Type::text());
$hashesSet->add($report->getReporterHash());
$values[] = $hashesSet;
}
$statement .=" WHERE entity_urn = ?
$statement .= " WHERE entity_urn = ?
AND reason_code = ?
AND sub_reason_code = ?
AND timestamp = ?";
......
......@@ -8,6 +8,8 @@ use Minds\Core\Security\ACL;
use Minds\Core\Reports\Verdict\Verdict;
use Minds\Core\Di\Di;
use Minds\Common\Urn;
use Minds\Core\Reports\Report;
use Minds\Core\Reports\Strikes\Strike;
class ActionDelegate
{
......@@ -20,19 +22,28 @@ class ActionDelegate
/** @var Urn $urn */
private $urn;
/** @var StrikesManager $strikesManager */
private $strikesManager;
public function __construct(
$entitiesBuilder = null,
$actions = null,
$urn = null
$urn = null,
$strikesManager = null
)
{
$this->entitiesBuilder = $entitiesBuilder ?: Di::_()->get('EntitiesBuilder');
$this->actions = $actions ?: Di::_()->get('Reports\Actions');
$this->urn = $urn ?: new Urn;
$this->strikesManager = $strikesManager ?: Di::_()->get('Moderation\Strikes\Manager');
}
public function onAction(Verdict $verdict)
{
if ($verdict->isAppeal()) {
return; // Can not
}
$report = $verdict->getReport();
// Disable ACL
......@@ -45,32 +56,40 @@ class ActionDelegate
switch ($report->getReasonCode()) {
case 1: // Illegal (not appealable)
$this->actions->setDeletedFlag($entity, true);
// TODO: ban the owner of the post too
// Ban the owner of the post too
$this->applyBan($report);
break;
case 2: // NSFW
$nsfw = $report->getSubReasonCode();
$entity->setNsfw(array_merge([$nsfw], $entity->getNsfw()));
$entity->setNsfwLock(array_merge([$nsfw], $entity->getNsfwLock()));
$entity->save();
// Apply a strike to the owner
$this->applyStrike($report);
break;
case 3: // Incites violence
$this->actions->setDeletedFlag($entity, true);
// Ban the owner of the post
$this->applyBan($report);
break;
case 4: // Harrasment
$this->actions->setDeletedFlag($entity, true);
// Apply a strike to the owner
$this->applyStrike($report);
break;
case 5: // Personal and confidential information (not appelable)
$this->actions->setDeletedFlag($entity, true);
// Ban the owner of the post too
$this->applyBan($report);
break;
case 7: // Impersonates (channel level)
// Ban
$this->applyBan($report);
break;
case 8: // Spam
$this->actions->setDeletedFlag($entity, true);
// Apply a strike to the owner
$this->applyStrike($report);
break;
//case 12: // Incorrect use of hashtags
// De-index post
......@@ -79,18 +98,31 @@ class ActionDelegate
case 13: // Malware
$this->actions->setDeletedFlag($entity, true);
// Ban the owner
$this->applyBan($report);
break;
case 14: // Strikes
// Ban the user or make action
// Token manipulation => Ban
// Spam => Ban
// Harrasment => Ban
// NSFW => Lock
switch ($report->getSubReason()) {
case 4: // Harrasment
case 8: // Spam
case 16: // Token manipulation
$this->applyBan($report);
break;
case 2.1: // NSFW
case 2.2:
case 2.3:
case 2.4:
case 2.5:
case 2.6:
$this->applyNsfwLock($report);
break;
}
break;
case 16: // Token manipulation
// Strike
$this->applyStrike($report);
break;
}
......@@ -98,4 +130,68 @@ class ActionDelegate
ACL::$ignore = false;
}
/**
* Apply a strike to the user
* @param Report $report
* @return void
*/
private function applyStrike(Report $report)
{
$strike = new Strike;
$strike->setReport($report)
->setReportUrn($report->getUrn())
->setUserGuid($report->getEntityOwnerGuid())
->setReasonCode($report->getReasonCode())
->setSubReasonCode($report->getSubReasonCode())
->setTimestamp(round(microtime(true) * 1000));
$count = $this->strikesManager->countStrikesInTimeWindow($strike, $this->strikesManager::STRIKE_TIME_WINDOW);
if (!$count) {
$this->strikesManager->add($strike);
}
// If 3 or more strikes, ban or apply NSFW lock
if ($this->strikesManager->countStrikesInTimeWindow($strike, $this->strikesManager::STRIKE_RETENTION_WINDOW) >= 3) {
if ($report->getReasonCode() === 2) {
$this->applyNsfwLock($report);
} else {
$reasonCode = $report->getReasonCode();
$subReasonCode = $report->getSubReasonCode();
$report->setReasonCode(14) // Strike
->setSubReasonCode(implode('.', [ $reasonCode, $subReasonCode ]));
$this->applyBan($report);
}
}
}
/**
* Apply an NSFW lock to the user
* @param Report $report
*/
private function applyNsfwLock($report)
{
$user = $this->entitiesBuilder->single($report->getEntityOwnerGuid());
//list($reason, $subReason) = explode('.', $report->getSubReason());
$subReason = $report->getSubReasonCode();
$user->setNsfw(array_merge($user->getNsfw(), [ $subReason ]));
$user->setNsfwLock(array_merge($user->getNsfwLock(), [ $subReason ]));
$user->save();
}
/**
* Apply a ban to the channel
* @param Report $report
*/
private function applyBan($report)
{
$user = $this->entitiesBuilder->single($report->getEntityOwnerGuid());
$user->banned = 'yes';
$user->ban_reason = implode('.', [ $report->getReasonCode(), $report->getSubReasonCode() ]);
$user->save();
}
}
\ No newline at end of file
<?php
/**
* Reverse Action delegate for Verdicts
*/
namespace Minds\Core\Reports\Verdict\Delegates;
use Minds\Core\Security\ACL;
use Minds\Core\Reports\Verdict\Verdict;
use Minds\Core\Di\Di;
use Minds\Common\Urn;
use Minds\Core\Reports\Report;
use Minds\Core\Reports\Strikes\Strike;
class ReverseActionDelegate
{
/** @var EntitiesBuilder $entitiesBuilder */
private $entitiesBuilder;
/** @var Actions $actions */
private $actions;
/** @var Urn $urn */
private $urn;
/** @var StrikesManager $strikesManager */
private $strikesManager;
public function __construct(
$entitiesBuilder = null,
$actions = null,
$urn = null,
$strikesManager = null
)
{
$this->entitiesBuilder = $entitiesBuilder ?: Di::_()->get('EntitiesBuilder');
$this->actions = $actions ?: Di::_()->get('Reports\Actions');
$this->urn = $urn ?: new Urn;
$this->strikesManager = $strikesManager ?: Di::_()->get('Moderation\Strikes\Manager');
}
public function onReverse(Verdict $verdict)
{
if (!$verdict->isAppeal() && $verdict->isUpheld()) {
return; // Can not be reversed
}
$report = $verdict->getReport();
// Disable ACL
ACL::$ignore = true;
$entityUrn = $verdict->getReport()->getEntityUrn();
$entityGuid = $this->urn->setUrn($entityUrn)->getNss();
$entity = $this->entitiesBuilder->single($entityGuid);
switch ($report->getReasonCode()) {
case 1: // Illegal (not appealable)
$this->actions->setDeletedFlag($entity, false);
// Ban the owner of the post too
$this->unBan($report);
break;
case 2: // NSFW
$nsfw = $report->getSubReasonCode();
$entity->setNsfw(array_diff([$nsfw], $entity->getNsfw()));
$entity->setNsfwLock(array_diff([$nsfw], $entity->getNsfwLock()));
$entity->save();
// Apply a strike to the owner
$this->removeStrike($report);
break;
case 3: // Incites violence
$this->actions->setDeletedFlag($entity, false);
// Ban the owner of the post
$this->unBan($report);
break;
case 4: // Harrasment
$this->actions->setDeletedFlag($entity, false);
// Apply a strike to the owner
$this->removeStrike($report);
break;
case 5: // Personal and confidential information (not appelable)
$this->actions->setDeletedFlag($entity, false);
// Ban the owner of the post too
$this->unBan($report);
break;
case 7: // Impersonates (channel level)
// Ban
$this->unBan($report);
break;
case 8: // Spam
$this->actions->setDeletedFlag($entity, false);
// Apply a strike to the owner
$this->removeStrike($report);
break;
//case 12: // Incorrect use of hashtags
// De-index post
// Apply a strike to the owner
// break;
case 13: // Malware
$this->actions->setDeletedFlag($entity, false);
// Ban the owner
$this->unBan($report);
break;
case 14: // Strikes
break;
case 16: // Token manipulation
// Strike
$this->removeStrike($report);
break;
}
// Enable ACL again
ACL::$ignore = false;
}
/**
* Remove strike from user
* @param Report $report
* @return void
*/
private function removeStrike(Report $report)
{
$strike = new Strike;
$strike->setReport($report)
->setReportUrn($report->getUrn())
->setUserGuid($report->getEntityOwnerGuid())
->setReasonCode($report->getReasonCode())
->setSubReasonCode($report->getSubReasonCode())
->setTimestamp(round(microtime(true) * 1000));
$this->strikesManager->delete($strike);
// If 3 or more strikes, ban or apply NSFW lock
if ($this->strikesManager->countStrikesInTimeWindow($strike, $this->strikesManager::STRIKE_RETENTION_WINDOW) >= 3) {
if ($report->getReasonCode() === 2) {
$this->removeNsfwLock($report);
} else {
$this->unBan($report);
}
}
}
/**
* Remove an NSFW lock to the user
* @param Report $report
*/
private function removeNsfwLock($report)
{
$user = $this->entitiesBuilder->single($report->getEntityOwnerGuid());
//list($reason, $subReason) = explode('.', $report->getSubReason());
$subReason = $report->getSubReasonCode();
$user->setNsfw(array_diff($user->getNsfw(), [ $subReason ]));
$user->setNsfwLock(array_diff($user->getNsfwLock(), [ $subReason ]));
$user->save();
}
/**
* un ban to the channel
* @param Report $report
*/
private function unBan($report)
{
$user = $this->entitiesBuilder->single($report->getEntityOwnerGuid());
$user->banned = 'no';
$user->ban_reason = '';
$user->save();
}
}
\ No newline at end of file
......@@ -26,17 +26,22 @@ class Manager
/** @var Delegates\ActionDelegate $actionDelegate */
private $actionDelegate;
/** @var Delegates\ReverseActionDelegate $reverseActionDelegate */
private $reverseActionDelegate;
/** @var Delegates\NotificationDelegate $notificationDelegate */
private $notificationDelegate;
public function __construct(
$repository = null,
$actionDelegate = null,
$reverseActionDelegate = null,
$notificationDelegate = null
)
{
$this->repository = $repository ?: new Repository;
$this->actionDelegate = $actionDelegate ?: new Delegates\ActionDelegate;
$this->reverseActionDelegate = $reverseActionDelegate ?: new Delegates\ReverseActionDelegate;
$this->notificationDelegate = $notificationDelegate ?: new Delegates\NotificationDelegate;
}
......@@ -122,6 +127,9 @@ class Manager
// Make the action
$this->actionDelegate->onAction($verdict);
// Reverse the action (if appeal)
$this->reverseActionDelegate->onReverse($verdict);
// Send a notification to the reported user
$this->notificationDelegate->onAction($verdict);
......
......@@ -2,6 +2,13 @@
namespace Minds\Core\Reports\Verdict;
use Cassandra;
use Cassandra\Type;
use Cassandra\Map;
use Cassandra\Set;
use Cassandra\Bigint;
use Cassandra\Tinyint;
use Cassandra\Decimal;
use Cassandra\Timestamp;
use Minds\Core;
use Minds\Core\Di\Di;
......@@ -25,7 +32,7 @@ class Repository
public function __construct($cql = null, $reportsRepository = null)
{
$this->cql = $cql ?: Di::_()->get('Database\Cassandra\Client');
$this->cql = $cql ?: Di::_()->get('Database\Cassandra\Cql');
$this->reportsRepository = $reportsRepository ?: new ReportsRepository;
}
......@@ -57,16 +64,25 @@ class Repository
{
$statement = "UPDATE moderation_reports
SET state = ?
SET state = ?,
state_changes += ?
WHERE entity_urn = ?
AND reason_code = ?
AND sub_reason_code = ?";
AND sub_reason_code = ?
AND timestamp = ?";
$state = $verdict->isAppeal() ? 'appeal_jury_decided' : 'initial_jury_decided';
$stateChangesMap = new Map(Type::text(), Type::timestamp());
$stateChangesMap->set($state, new Timestamp(microtime(true)));
$values = [
$verdict->isAppeal() ? 'appeal_jury_decided' : 'initial_jury_decided',
$state,
$stateChangesMap,
$verdict->getReport()->getEntityUrn(),
$verdict->getReport()->getReasonCode(),
$verdict->getReport()->getSubReasonCode(),
new Tinyint($verdict->getReport()->getReasonCode()),
new Decimal($verdict->getReport()->getSubReasonCode()),
new Timestamp($verdict->getReport()->getTimestamp()),
];
$prepared = new Prepared;
......
......@@ -75,10 +75,13 @@ class Index
'index' => $this->esIndex,
'type' => $mapper->getType(),
'id' => $mapper->getId(),
'body' => $body
'body' => [
'doc' => $body,
'doc_as_upsert' => true,
],
];
$prepared = new Prepared\Index();
$prepared = new Prepared\Update();
$prepared->query($query);
$result = (bool) $this->client->request($prepared);
......
......@@ -33,9 +33,9 @@ class UserMapping extends EntityMapping implements MappingInterface
{
$map = parent::map($defaultValues);
if (isset($map['tags'])) {
unset($map['tags']);
}
//if (isset($map['tags'])) {
// unset($map['tags']);
//}
if ($this->entity->isBanned() == 'yes') {
throw new BannedException('User is banned');
......@@ -53,7 +53,7 @@ class UserMapping extends EntityMapping implements MappingInterface
$map['group_membership'] = [];
}
$map['tags'] = array_unique(array_merge($map['tags'], $this->entity->getTags()));
$map['tags'] = array_unique($this->entity->getTags());
return $map;
}
......@@ -95,7 +95,7 @@ class UserMapping extends EntityMapping implements MappingInterface
if (strlen($username) > 30) {
$map['weight'] = 1; //spammy username
}
return $map;
}
......
......@@ -30,7 +30,7 @@ class DownVotes extends Aggregate
]
];
if ($this->type && $this->type != 'group') {
if ($this->type && $this->type != 'group' && $this->type != 'user') {
$must[]['match'] = [
'entity_type' => $this->type
];
......@@ -47,7 +47,7 @@ class DownVotes extends Aggregate
if ($this->type == 'group') {
$field = 'entity_container_guid';
$this->multiplier = 4;
//$this->multiplier = 4;
$must[]['range'] = [
'entity_access_id' => [
'gte' => 3, //would be group
......@@ -56,6 +56,10 @@ class DownVotes extends Aggregate
];
}
if ($this->type == 'user') {
$field = 'entity_owner_guid';
}
//$must[]['match'] = [
// 'rating' => $this->rating
//];
......
<?php
/**
* Votes aggregates
*/
namespace Minds\Core\Trending\Aggregates;
use Minds\Core\Data\ElasticSearch;
class SuspiciousVotes extends Aggregate
{
protected $multiplier = 1;
public function get()
{
$filter = [
'term' => [
'action' => 'vote:up'
]
];
$must = [
[
'range' => [
'@timestamp' => [
'gte' => $this->from,
'lte' => $this->to
]
]
]
];
if ($this->type && $this->type != 'group') {
$must[]['match'] = [
'entity_type' => $this->type
];
}
if ($this->subtype) {
$must[]['match'] = [
'entity_subtype' => $this->subtype
];
}
$field = 'entity_guid';
//$cardinality_field = 'user_phone_number_hash';
$cardinality_field = 'ip_hash';
if ($this->type == 'group') {
$field = 'entity_container_guid';
$this->multiplier = 4;
$must[]['range'] = [
'entity_access_id' => [
'gte' => 3, //would be group
'lt' => null,
]
];
}
//$must[]['match'] = [
// 'rating' => $this->rating
//];
$query = [
'index' => 'minds-metrics-*',
'type' => 'action',
'size' => 0, //we want just the aggregates
'body' => [
'query' => [
'bool' => [
'filter' => $filter,
'must' => $must
]
],
'aggs' => [
'suspicious' => [
'significant_terms' => [
'field' => "$field.keyword",
'size' => $this->limit,
// 'order' => [ 'uniques' => 'DESC' ],
]
]
]
]
];
$prepared = new ElasticSearch\Prepared\Search();
$prepared->query($query);
$result = $this->client->request($prepared);
$entities = [];
foreach ($result['aggregations']['entities']['buckets'] as $entity) {
$entities[$entity['key']] = $entity['uniques']['value'] * $this->multiplier;
}
return $entities;
}
}
......@@ -33,7 +33,7 @@ class Votes extends Aggregate
]
];
if ($this->type && $this->type != 'group') {
if ($this->type && $this->type != 'group' && $this->type != 'user') {
$must[]['match'] = [
'entity_type' => $this->type
];
......@@ -56,10 +56,14 @@ class Votes extends Aggregate
'entity_access_id' => [
'gte' => 3, //would be group
'lt' => null,
]
],
];
}
if ($this->type === 'user') {
$field = 'entity_owner_guid';
}
//$must[]['match'] = [
// 'rating' => $this->rating
//];
......
......@@ -79,7 +79,7 @@ class ManagerSpec extends ObjectBehavior
$scoredGuid2->getOwnerGuid()
->shouldBeCalled()
->willReturn(1000);
->willReturn(1001);
$scoredGuid2->getTimestamp()
->shouldBeCalled()
......
......@@ -91,7 +91,7 @@ class RepositorySpec extends ObjectBehavior
$this->client->request(Argument::that(function ($query) {
$query = $query->build();
return $query['type'] === 'activity' && in_array('owner_guid', $query['body']['_source']);
return $query['type'] === 'user' && in_array('guid', $query['body']['_source']);
}))
->shouldBeCalled()
->willReturn([
......@@ -139,7 +139,7 @@ class RepositorySpec extends ObjectBehavior
$this->client->request(Argument::that(function ($query) {
$query = $query->build();
return $query['type'] === 'activity' && in_array('container_guid', $query['body']['_source']);
return $query['type'] === 'group' && in_array('guid', $query['body']['_source']);
}))
->shouldBeCalled()
->willReturn([
......
......@@ -7,26 +7,27 @@ use Minds\Core\Reports\Appeals\Repository;
use Minds\Core\Reports\Appeals\Appeal;
use Minds\Core\Reports\Appeals\Delegates;
use Minds\Core\Reports\Report;
use Minds\Core\EntitiesBuilder;
use Minds\Core\Entities\Resolver as EntitiesResolver;
use Minds\Entities\Entity;
use Minds\Common\Urn;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class ManagerSpec extends ObjectBehavior
{
private $repository;
private $entitiesBuilder;
private $entitiesResolver;
private $notificationDelegate;
function let(
Repository $repository,
EntitiesBuilder $entitiesBuilder,
EntitiesResolver $entitiesResolver,
Delegates\NotificationDelegate $notificationDelegate
)
{
$this->beConstructedWith($repository, $entitiesBuilder, $notificationDelegate);
$this->beConstructedWith($repository, $entitiesResolver, $notificationDelegate);
$this->repository = $repository;
$this->entitiesBuilder = $entitiesBuilder;
$this->entitiesResolver = $entitiesResolver;
$this->notificationDelegate = $notificationDelegate;
}
......@@ -45,27 +46,31 @@ class ManagerSpec extends ObjectBehavior
->willReturn([
(new Appeal)
->setReport((new Report)
->setEntityGuid(123)),
->setEntityUrn('urn:activity:123')),
(new Appeal)
->setReport((new Report)
->setEntityGuid(456)),
->setEntityUrn('urn:activity:456')),
]);
$this->entitiesBuilder->single(123)
$this->entitiesResolver->single(Argument::that(function ($urn) {
return $urn->getNss() == 123;
}))
->shouldBeCalled()
->willReturn((new Entity)->set('guid', 123));
$this->entitiesBuilder->single(456)
$this->entitiesResolver->single(Argument::that(function ($urn) {
return $urn->getNss() == 456;
}))
->shouldBeCalled()
->willReturn((new Entity)->set('guid', 456));
$response = $this->getList([ 'hydrate' => true ]);
$response->shouldHaveCount(2);
$response[0]->getReport()->getEntityGuid()
->shouldBe(123);
$response[0]->getReport()->getEntityUrn()
->shouldBe('urn:activity:123');
$response[0]->getReport()->getEntity()->getGuid()
->shouldBe(123);
$response[1]->getReport()->getEntityGuid()
->shouldBe(456);
$response[1]->getReport()->getEntityUrn()
->shouldBe('urn:activity:456');
$response[1]->getReport()->getEntity()->getGuid()
->shouldBe(456);
}
......
......@@ -52,12 +52,12 @@ class RepositorySpec extends ObjectBehavior
->set('reported', new Timestamp(1549451597000)),
'appeal_note' => null,
'reports' => (new Set(Type::bigint()))
->set(new Bigint(789)),
->add(new Bigint(789)),
'initial_jury' => (new Map(Type::bigint(), Type::boolean()))
->set(new Bigint(101112), new Boolean(true)),
'appeal_jury' => (new Map(Type::bigint(), Type::boolean())),
'user_hashes' => (new Set(Type::text()))
->set('hashFor789'),
->add('hashFor789'),
],
[
'entity_urn' => 'urn:activity:1234',
......@@ -70,12 +70,12 @@ class RepositorySpec extends ObjectBehavior
->set('reported', new Timestamp(1549451597000)),
'appeal_note' => null,
'reports' => (new Set(Type::bigint()))
->set(new Bigint(789)),
->add(new Bigint(789)),
'initial_jury' => (new Map(Type::bigint(), Type::boolean()))
->set(new Bigint(101112), new Boolean(true)),
'appeal_jury' => (new Map(Type::bigint(), Type::boolean())),
'user_hashes' => (new Set(Type::text()))
->set('hashFor789'),
->add('hashFor789'),
],
]);
......@@ -101,13 +101,13 @@ class RepositorySpec extends ObjectBehavior
->set('reported', new Timestamp(1549451597000)),
'appeal_note' => 'hello world',
'reports' => (new Set(Type::bigint()))
->set(new Bigint(789)),
->add(new Bigint(789)),
'initial_jury' => (new Map(Type::bigint(), Type::boolean()))
->set(new Bigint(101112), new Boolean(true)),
'appeal_jury' => (new Map(Type::bigint(), Type::boolean()))
->set(new Bigint(101113), new Boolean(true)),
'user_hashes' => (new Set(Type::text()))
->set('hashFor789'),
->add('hashFor789'),
],
]);
......@@ -131,8 +131,8 @@ class RepositorySpec extends ObjectBehavior
return $values[0] == 'Should not be reported because this is a test'
&& $values[1] == 'appealed'
&& $values[3] == 'urn:activity:123'
&& $values[4] == 2
&& $values[5] == 5;
&& $values[4]->value() == 2
&& $values[5]->value() == 5;
}))
->shouldBeCalled()
->willReturn(true);
......
......@@ -7,21 +7,21 @@ use Minds\Core\Reports\Verdict\Manager as VerdictManager;
use Minds\Core\Reports\Jury\Repository;
use Minds\Core\Reports\Jury\Decision;
use Minds\Core\Reports\Report;
use Minds\Core\EntitiesBuilder;
use Minds\Core\Entities\Resolver as EntitiesResolver;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class ManagerSpec extends ObjectBehavior
{
private $repository;
private $entitiesBuilder;
private $entitiesResolver;
private $verdictManager;
function let(Repository $repository, EntitiesBuilder $entitiesBuilder, VerdictManager $verdictManager)
function let(Repository $repository, EntitiesResolver $entitiesResolver, VerdictManager $verdictManager)
{
$this->beConstructedWith($repository, $entitiesBuilder, $verdictManager);
$this->beConstructedWith($repository, $entitiesResolver, $verdictManager);
$this->repository = $repository;
$this->entitiesBuilder = $entitiesBuilder;
$this->entitiesResolver = $entitiesResolver;
$this->verdictManager = $verdictManager;
}
......@@ -36,14 +36,18 @@ class ManagerSpec extends ObjectBehavior
->shouldBeCalled()
->willReturn([
(new Report)
->setEntityGuid(123),
->setEntityUrn('urn:activity:123'),
(new Report)
->setEntityGuid(456),
->setEntityUrn('urn:activity:456'),
]);
$this->entitiesBuilder->single(123)
$this->entitiesResolver->single(Argument::that(function ($urn) {
return $urn->getNss() == 123;
}))
->shouldBeCalled();
$this->entitiesBuilder->single(456)
$this->entitiesResolver->single(Argument::that(function ($urn) {
return $urn->getNss() == 456;
}))
->shouldBeCalled();
$response = $this->getUnmoderatedList([ 'hydrate' => true ]);
......
......@@ -49,7 +49,7 @@ class RepositorySpec extends ObjectBehavior
->willReturn([
[
'user_hashes' => (new Set(Type::text()))
->set('hash'),
->add('hash'),
'entity_urn' => 'urn:activity:123',
'entity_owner_guid' => new Bigint(456),
'reason_code' => new Float_(2),
......@@ -59,11 +59,11 @@ class RepositorySpec extends ObjectBehavior
'state_changes' => (new Map(Type::text(), Type::timestamp()))
->set('reported', time() * 1000),
'reports' => (new Set(Type::bigint()))
->set(789),
->add(789),
],
[
'user_hashes' => (new Set(Type::text()))
->set('hash'),
->add('hash'),
'entity_urn' => 'urn:activity:456',
'entity_owner_guid' => new Bigint(456),
'reason_code' => new Float_(2),
......@@ -73,7 +73,7 @@ class RepositorySpec extends ObjectBehavior
'state_changes' => (new Map(Type::text(), Type::timestamp()))
->set('reported', time() * 1000),
'reports' => (new Set(Type::bigint()))
->set(789),
->add(789),
],
]);
......@@ -116,8 +116,8 @@ class RepositorySpec extends ObjectBehavior
&& $values[0]->values()[456]->value() == true
&& $values[1]->values()[0]->value() === '0xqj1'
&& $values[2] === 'urn:activity:123'
&& (float) $values[3] === (float) 2
&& (float) $values[4] === (float) 5;
&& $values[3]->value() == 2
&& $values[4]->value() == 5;
}))
->shouldBeCalled()
->willReturn(true);
......@@ -158,8 +158,8 @@ class RepositorySpec extends ObjectBehavior
&& $values[0]->values()[456]->value() == true
&& $values[1]->values()[0]->value() === '0xqj1'
&& $values[2] === 'urn:activity:123'
&& (float) $values[3] === (float) 2
&& (float) $values[4] === (float) 5;
&& $values[3]->value() == 2
&& $values[4]->value() == 5;
}))
->shouldBeCalled()
->willReturn(true);
......
......@@ -8,7 +8,8 @@ use Minds\Core\Data\Cassandra\Client;
use Minds\Common\Urn;
use Cassandra\Bigint;
use Cassandra\Timestamp;
use Cassandra\Float_;
use Cassandra\Tinyint;
use Cassandra\Decimal;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
......@@ -38,8 +39,8 @@ class RepositorySpec extends ObjectBehavior
[
'user_guid' => new Bigint(123),
'timestamp' => new Timestamp(1557226524000),
'reason_code' => new Float_(2),
'sub_reason_code' => new Float_(5),
'reason_code' => new Tinyint(2),
'sub_reason_code' => new Decimal(5),
'report_urn' => 'urn:goes:here',
]
]);
......@@ -70,15 +71,15 @@ class RepositorySpec extends ObjectBehavior
[
'user_guid' => new Bigint(123),
'timestamp' => new Timestamp(1557226524000),
'reason_code' => new Float_(2),
'sub_reason_code' => new Float_(5),
'reason_code' => new Tinyint(2),
'sub_reason_code' => new Decimal(5),
'report_urn' => 'urn:goes:here',
],
[
'user_guid' => new Bigint(123),
'timestamp' => new Timestamp(1567226524000),
'reason_code' => new Float_(2),
'sub_reason_code' => new Float_(3),
'reason_code' => new Tinyint(2),
'sub_reason_code' => new Decimal(3),
'report_urn' => 'urn:goes:here',
]
]);
......@@ -115,8 +116,8 @@ class RepositorySpec extends ObjectBehavior
$values = $prepared->build()['values'];
return $values[0]->value() == 123
&& $values[1]->time() == 1549451597000
&& $values[2] == 2
&& $values[3] == 3
&& $values[2]->value() == 2
&& $values[3]->value() == 3
&& $values[4] == 'urn:report:(urn:activity:123)-2-3-1549451597000';
}))
->shouldBeCalled()
......
......@@ -7,6 +7,8 @@ use Minds\Core\Reports\UserReports\Repository;
use Minds\Core\Reports\UserReports\ElasticRepository;
use Minds\Core\Reports\UserReports\UserReport;
use Minds\Core\Reports\UserReports\Delegates;
use Minds\Core\Reports\Report;
use Minds\Core\Reports\Manager as ReportsManager;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
......@@ -15,17 +17,20 @@ class ManagerSpec extends ObjectBehavior
private $repository;
private $elasticRepository;
private $notificationDelegate;
private $reportsManager;
function let(
Repository $repository,
ElasticRepository $elasticRepository,
Delegates\NotificationDelegate $notificationDelegate
Delegates\NotificationDelegate $notificationDelegate,
ReportsManager $reportsManager
)
{
$this->beConstructedWith($repository, $elasticRepository, $notificationDelegate);
$this->beConstructedWith($repository, $elasticRepository, $notificationDelegate, $reportsManager);
$this->repository = $repository;
$this->elasticRepository = $elasticRepository;
$this->notificationDelegate = $notificationDelegate;
$this->reportsManager = $reportsManager;
}
function it_is_initializable()
......@@ -39,14 +44,15 @@ class ManagerSpec extends ObjectBehavior
->shouldBeCalled()
->willReturn(true);
$this->elasticRepository->add(Argument::type(UserReport::class))
$this->reportsManager->getLatestReport(Argument::type(Report::class))
->shouldBeCalled()
->willReturn(true);
->willReturn(new Report);
$this->notificationDelegate->onAction(Argument::type(UserReport::class))
->shouldBeCalled();
$userReport = new UserReport;
$userReport->setReport(new Report);
$this->add($userReport)
->shouldReturn(true);
}
......
......@@ -24,7 +24,7 @@ class RepositorySpec extends ObjectBehavior
$this->shouldHaveType(Repository::class);
}
function it_should_add_a_report(UserReport $report)
function it_should_add_a_report(UserReport $userReport)
{
$ts = (int) microtime(true);
$this->cql->request(Argument::that(function($prepared) use ($ts) {
......@@ -32,38 +32,33 @@ class RepositorySpec extends ObjectBehavior
$values = $query['values'];
return $values[0]->values()[0]->value() == 456
&& $values[1]->values()[0]->value() == 'hash'
&& $values[2] === 'urn:activity:123'
&& $values[3]->value() == 2
&& $values[4]->value() == 4
&& $values[5]->time() == $ts;
&& $values[2]->values()[0]->value() == 'hash'
&& $values[3] === 'urn:activity:123'
&& $values[4]->value() == 2
&& $values[5]->value() == 4
&& $values[6]->time() == $ts;
}))
->shouldBeCalled()
->willReturn(true);
$report->getReport()
$userReport->getReport()
->shouldBeCalled()
->willReturn((new Report)
->setEntityUrn("urn:activity:123")
->setTimestamp($ts));
->setTimestamp($ts)
->setReasonCode(2)
->setSubReasonCode(4)
);
$report->getReporterGuid()
$userReport->getReporterGuid()
->shouldBeCalled()
->willReturn(456);
$report->getReporterHash()
$userReport->getReporterHash()
->shouldBeCalled()
->willReturn('hash');
$report->getReasonCode()
->shouldBeCalled()
->willReturn(2);
$report->getSubReasonCode()
->shouldBeCalled()
->willReturn(4);
$this->add($report)
$this->add($userReport)
->shouldBe(true);
}
......
......@@ -7,6 +7,7 @@ use Minds\Core\EntitiesBuilder;
use Minds\Core\Reports\Verdict\Verdict;
use Minds\Core\Reports\Report;
use Minds\Core\Reports\Actions;
use Minds\Core\Reports\Strikes\Manager as StrikesManager;
use Minds\Entities\Entity;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
......@@ -15,15 +16,18 @@ class ActionDelegateSpec extends ObjectBehavior
{
private $entitiesBuilder;
private $actions;
private $strikesManager;
function let(
EntitiesBuilder $entitiesBuilder,
Actions $actions
Actions $actions,
StrikesManager $strikesManager
)
{
$this->beConstructedWith($entitiesBuilder, $actions);
$this->beConstructedWith($entitiesBuilder, $actions, null, $strikesManager);
$this->entitiesBuilder = $entitiesBuilder;
$this->actions = $actions;
$this->strikesManager = $strikesManager;
}
function it_is_initializable()
......@@ -37,6 +41,7 @@ class ActionDelegateSpec extends ObjectBehavior
$report->setEntityUrn('urn:activity:123')
->setReasonCode(2)
->setSubReasonCode(1);
$verdict = new Verdict;
$verdict->setReport($report)
->setUphold(true);
......@@ -51,16 +56,32 @@ class ActionDelegateSpec extends ObjectBehavior
$entity->setNsfw([ 1, 2 ])
->shouldBeCalled();
$entity->getNsfwLock()
->shouldBeCalled()
->willReturn([ ]);
$entity->setNsfwLock([ 1 ])
->shouldBeCalled();
$entity->save()
->shouldBeCalled();
$this->strikesManager->countStrikesInTimeWindow(Argument::any(), Argument::any())
->shouldBeCalled()
->willReturn(0);
$this->strikesManager->add(Argument::any())
->shouldBeCalled();
$this->onAction($verdict);
}
function it_should_removed_if_illegal(Entity $entity)
function it_should_removed_if_illegal(Entity $entity, Entity $user)
{
$report = new Report;
$report->setEntityUrn('urn:activity:123')
->setEntityOwnerGuid(456)
->setReasonCode(1)
->setSubReasonCode(1);
......@@ -72,6 +93,10 @@ class ActionDelegateSpec extends ObjectBehavior
->shouldBeCalled()
->willReturn($entity);
$this->entitiesBuilder->single(456)
->shouldBeCalled()
->willReturn($user);
$this->actions->setDeletedFlag(Argument::type(Entity::class), true)
->shouldBeCalled();
......
......@@ -16,17 +16,20 @@ class ManagerSpec extends ObjectBehavior
{
private $repository;
private $actionDelegate;
private $reverseDelegate;
private $notificationDelegate;
function let(
Repository $repository,
Delegates\ActionDelegate $actionDelegate,
Delegates\ReverseActionDelegate $reverseDelegate,
Delegates\NotificationDelegate $notificationDelegate
)
{
$this->beConstructedWith($repository, $actionDelegate, $notificationDelegate);
$this->beConstructedWith($repository, $actionDelegate, $reverseDelegate, $notificationDelegate);
$this->repository = $repository;
$this->actionDelegate = $actionDelegate;
$this->reverseDelegate = $reverseDelegate;
$this->notificationDelegate = $notificationDelegate;
}
......@@ -36,8 +39,17 @@ class ManagerSpec extends ObjectBehavior
$this->shouldHaveType(Manager::class);
}
function it_should_add_verdict_to_repository(Verdict $verdict)
function it_should_add_verdict_to_repository(Verdict $verdict, Report $report)
{
$verdict->isAppeal()
->willReturn(false);
$verdict->isUpheld()
->willReturn(false);
$verdict->getReport()
->willReturn($report);
$this->repository->add($verdict)
->shouldBeCalled()
->willReturn(true);
......
......@@ -4,6 +4,7 @@ namespace Spec\Minds\Core\Search;
use Minds\Core\Data\ElasticSearch\Client;
use Minds\Core\Data\ElasticSearch\Prepared\Index;
use Minds\Core\Data\ElasticSearch\Prepared\Update;
use Minds\Core\Di\Di;
use Minds\Core\Search\Mappings\Factory;
use Minds\Core\Search\Mappings\MappingInterface;
......@@ -66,7 +67,7 @@ class IndexSpec extends ObjectBehavior
->willReturn('1000');
$this->_client->request(Argument::that(function ($prepared) {
if (!($prepared instanceof Index)) {
if (!($prepared instanceof Update)) {
return false;
}
......@@ -77,9 +78,9 @@ class IndexSpec extends ObjectBehavior
$query['type'] == 'test' &&
$query['id'] == '1000' &&
isset($query['body']) &&
$query['body']['guid'] == '1000' &&
$query['body']['type'] == 'test' &&
isset($query['body']['suggest'])
$query['body']['doc']['guid'] == '1000' &&
$query['body']['doc']['type'] == 'test' &&
isset($query['body']['doc']['suggest'])
;
}))
->shouldBeCalled()
......
......@@ -43,6 +43,7 @@ class UserMappingSpec extends ObjectBehavior
$user->getMatureContent()->willReturn(false);
$user->getGroupMembership()->willReturn([ 2000 ]);
$user->getNsfw()->willReturn([ 1 ]);
$user->getTags()->willReturn([ 'spaceiscool' ]);
$this
->setEntity($user)
......@@ -73,6 +74,7 @@ class UserMappingSpec extends ObjectBehavior
'@timestamp' => $now * 1000,
'taxonomy' => 'user',
'public' => true,
'tags' => [ 'spaceiscool' ],
'nsfw' => [ 1 ],
'group_membership' => [ 2000 ],
]);
......
......@@ -211,15 +211,13 @@ class MockMap
public function set($key, $value)
{
$md5 = md5($key);
$hashTable[$md5] = $key;
$this->kv[$md5] = new Mock($value);
$this->kv[(string) $key] = new Mock($value);
return $this;
}
public function values()
{
return new MockCollectionValues($this->kv);
return ($this->kv);
}
}
......@@ -265,14 +263,14 @@ class MockCollectionValues implements ArrayAccess
class MockSet
{
private $valueType;
private $values;
private $values = [];
public function __construct($valueType)
{
$this->valueType = $valueType;
}
public function set($value)
public function add($value)
{
$this->values[] = new Mock($value);
return $this;
......@@ -296,8 +294,8 @@ if (!class_exists('Cassandra')) {
class_alias('Mock', 'Cassandra\Bigint');
class_alias('Mock', 'Cassandra\Float_');
class_alias('Mock', 'Cassandra\Tinyint');
class_alias('Mock', 'Cassandra\Set');
class_alias('Mock', 'Cassandra\Map');
class_alias('MockSet', 'Cassandra\Set');
class_alias('MockMap', 'Cassandra\Map');
class_alias('Mock', 'Cassandra\Uuid');
class_alias('Mock', 'Cassandra\Boolean');
class_alias('Mock', 'MongoDB\BSON\UTCDateTime');
......
......@@ -377,3 +377,7 @@ $CONFIG->set('tags', [
$CONFIG->set('steward_guid', '');
$CONFIG->set('steward_autoconfirm', false);
$CONFIG->set('development_mode', false);
$CONFIG->set('max_video_length', 900);
$CONFIG->set('max_video_length_plus', 1860);