...
 
Commits (21)
<?php
namespace Minds\Common;
class Access
{
const UNLISTED = 0;
const LOGGED_IN = 1;
const PUBLIC = 2;
const UNKNOWN = 99;
const ACCESS_STRINGS = [
0 => 'Unlisted',
1 => 'LoggedIn',
3 => 'Public'
];
public static function idToString(int $id) : string
{
return self::ACCESS_STRINGS[$id] ?? 'Unknown';
}
}
......@@ -10,10 +10,11 @@ namespace Minds\Controllers\api\v1;
use Minds\Api\Exportable;
use Minds\Api\Factory;
use Minds\Common\Access;
use Minds\Core;
use Minds\Entities\Activity;
use Minds\Helpers;
use Minds\Interfaces;
use Minds\Core\Blogs\Delegates\CreateActivity;
class blog implements Interfaces\Api
{
......@@ -93,7 +94,7 @@ class blog implements Interfaces\Api
$export = [];
foreach ($blogs as $blog) {
if ($blog->getOwnerGuid() != Core\Session::getLoggedInUserGuid() && $blog->getAccessId() != 2) {
if ($blog->getOwnerGuid() != Core\Session::getLoggedInUserGuid() && $blog->getAccessId() != Access::PUBLIC) {
continue;
}
$export[] = $blog;
......@@ -157,13 +158,16 @@ class blog implements Interfaces\Api
$header = new Core\Blogs\Header();
$response = [];
$alreadyPublished = false;
$oldAccessId = Access::UNKNOWN;
$editing = isset($pages[0]) && (is_numeric($pages[0]) || Core\Luid::isValid($pages[0]));
if ($editing) {
$blog = $manager->get($pages[0]);
$originallyPublished = $blog->isPublished();
$alreadyPublished = $blog->isPublished();
$oldAccessId = $alreadyPublished ? $blog->getAccessId() : $blog->getDraftAccessId();
} else {
$blog = new Core\Blogs\Blog();
$blog
......@@ -218,7 +222,8 @@ class blog implements Interfaces\Api
}
if (isset($_POST['published'])) {
$blog->setPublished(!!$_POST['published']);
$published = is_string($_POST['published']) ? json_decode($_POST['published']) : $_POST['published'];
$blog->setPublished($published);
}
if (isset($_POST['monetized'])) {
......@@ -237,9 +242,8 @@ class blog implements Interfaces\Api
}
}
//draft
if (!$_POST['published'] || $_POST['published'] === 'false') {
$blog->setAccessId(0);
if (!$blog->isPublished()) {
$blog->setAccessId(Access::UNLISTED);
$blog->setDraftAccessId($_POST['access_id']);
} elseif ($blog->getTimePublished() == '') {
$blog->setTimePublished(time());
......@@ -282,7 +286,6 @@ class blog implements Interfaces\Api
}
}
if (isset($_POST['mature']) && $_POST['mature']) {
$user = Core\Session::getLoggedInUser();
......@@ -331,21 +334,10 @@ class blog implements Interfaces\Api
}
if ($saved) {
$createActivity = new Core\Blogs\Delegates\CreateActivity();
if (
!$editing &&
$blog->isPublished() &&
$blog->getAccessId() == 2
) {
$createActivity->save($blog);
} elseif (
$editing &&
!$originallyPublished &&
$blog->isPublished() &&
$blog->getAccessId() == 2
) {
$createActivity->save($blog);
if ($blog->isPublished() && $blog->getAccessId() == Access::PUBLIC) {
if (!$editing || ($editing && !$alreadyPublished) || ($editing && $oldAccessId == Access::UNLISTED)) {
(new CreateActivity())->save($blog);
}
}
$response['guid'] = (string) $blog->getGuid();
......
......@@ -157,6 +157,13 @@ class comments implements Interfaces\Api
]);
}
if (!$entity->getAllowComments()) {
return Factory::response([
'status' => 'error',
'message' => 'Comments are disabled for this post'
]);
}
if (!$_POST['comment'] && !$_POST['attachment_guid']) {
return Factory::response([
'status' => 'error',
......
......@@ -77,7 +77,8 @@ class phone implements Interfaces\Api
return Factory::response(['status' => 'success', 'message' => 'voip phones not allowed']);
}
$sms->send($phone, $code);
$message = 'From Minds.com: Your code is '. $code;
$sms->send($phone, $message);
return Factory::response(['status' => 'success', 'secret' => $secret]);
}
......
......@@ -43,6 +43,8 @@ class settings implements Interfaces\Api
$response['channel']['email'] = $user->getEmail();
$response['channel']['boost_rating'] = $user->getBoostRating();
$response['channel']['disabled_emails'] = $user->disabled_emails;
$response['channel']['toaster_notifications'] = $user->getToasterNotifications();
$sessionsManager = Di::_()->get('Sessions\Manager');
$sessionsManager->setUser($user);
......@@ -140,6 +142,10 @@ class settings implements Interfaces\Api
$user->setLanguage($_POST['language']);
}
if (isset($_POST['toaster_notifications'])) {
$user->setToasterNotifications((bool) $_POST['toaster_notifications']);
}
$response = [];
if (!$user->save()) {
$response['status'] = 'error';
......
......@@ -59,7 +59,7 @@ class twofactor implements Interfaces\Api
switch ($pages[0]) {
case "setup":
$secret = $twofactor->createSecret();
$secret = $twofactor->createSecret();
/** @var Core\SMS\SMSServiceInterface $sms */
$sms = Core\Di\Di::_()->get('SMS');
......@@ -67,7 +67,9 @@ class twofactor implements Interfaces\Api
if (!$sms->verify($_POST['tel'])) {
return Factory::response(['status' => 'error', 'message' => 'voip phones are not supported']);
}
if ($sms->send($_POST['tel'], $twofactor->getCode($secret))) {
$message = 'From Minds.com: Your code is '. $twofactor->getCode($secret);
if ($sms->send($_POST['tel'], $message)) {
$response['secret'] = $secret;
} else {
$response['status'] = "error";
......
......@@ -94,6 +94,7 @@ class feed implements Interfaces\Api
$feedSyncEntity
->setGuid((string) $boost->getGuid())
->setOwnerGuid((string) $boost->getOwnerGuid())
->setTimestamp($boost->getCreatedTimestamp())
->setUrn(new Urn("urn:boost:{$boost->getType()}:{$boost->getGuid()}"));
$boosts[] = $feedSyncEntity;
......@@ -101,7 +102,17 @@ class feed implements Interfaces\Api
// $boosts = iterator_to_array($iterator, false);
$next = $iterator->getOffset();
$cacher->set(Core\Session::getLoggedinUser()->guid . ':boost-offset-rotator', $next);
if (isset($boosts[2])) { // Always offset to 3rd in list
$next = $boosts[2]->getTimestamp();
}
$ttl = 1800; // 30 minutes
if (($next / 1000) < strtotime('48 hours ago')) {
$ttl = 300; // 5 minutes;
}
$cacher->set(Core\Session::getLoggedinUser()->guid . ':boost-offset-rotator', $next, $ttl);
break;
case 'content':
......
......@@ -121,7 +121,7 @@ class feeds implements Interfaces\Api
$asActivities = (bool) ($_GET['as_activities'] ?? true);
$query = $_GET['query'] ?? null;
$query = isset($_GET['query']) ? urldecode($_GET['query']) : null;
$container_guid = $_GET['container_guid'] ?? null;
$custom_type = isset($_GET['custom_type']) && $_GET['custom_type'] ? [$_GET['custom_type']] : null;
......
<?php
namespace Minds\Controllers\api\v2\permissions;
use Minds\Api\Factory;
use Minds\Core\Di\Di;
use Minds\Interfaces;
use Minds\Core\Entities\Actions\Save;
use Minds\Core\Session;
use Minds\Core\Permissions\Permissions;
class comments implements Interfaces\Api
{
public function get($pages)
{
Factory::isLoggedIn();
if (!isset($pages[0])) {
return Factory::response([
'status' => 'error',
'message' => 'Entity guid must be provided',
]);
}
}
public function post($pages)
{
Factory::isLoggedIn();
$owner = Session::getLoggedInUser();
if (!isset($pages[0])) {
return Factory::response([
'status' => 'error',
'message' => 'Entity guid must be provided',
]);
}
if (!isset($_POST['allowed'])) {
return Factory::response([
'status' => 'error',
'message' => '(bool) allowed must be provided',
]);
}
$allowed = (bool) $_POST['allowed'];
/** @var EntitiesBuilder $entitiesBuilder */
$entitiesBuilder = Di::_()->get('EntitiesBuilder');
$entity = $entitiesBuilder->single($pages[0]);
if (!$entity->canEdit($owner)) {
return Factory::response([
'status' => 'error',
'message' => 'Only owner can change the comments permissions',
]);
}
/** @var PermissionsManager */
$manager = Di::_()->get('Permissions\Manager');
$permissions = new Permissions();
$permissions->setAllowComments($allowed);
$manager->save($entity, $permissions);
return Factory::response([
'status' => 'success',
'allowed' => $allowed,
]);
}
public function put($pages)
{
// TODO: Implement put() method.
}
public function delete($pages)
{
// TODO: Implement delete() method.
}
}
......@@ -25,7 +25,7 @@ class referrals implements Interfaces\Api
$response = [];
$referrer_guid = isset($pages[0]) ? $pages[0] : Core\Session::getLoggedInUser()->guid;
$limit = isset($_GET['limit']) ? $_GET['limit'] : 24;
$limit = isset($_GET['limit']) ? $_GET['limit'] : 12;
$offset = isset($_GET['offset']) ? $_GET['offset'] : "";
$manager = Di::_()->get('Referrals\Manager');
......@@ -47,31 +47,14 @@ class referrals implements Interfaces\Api
$response['referrals'] = Factory::exportable(array_values($referrals->toArray()));
$response['load-next'] = (string) $referrals->getPagingToken();
// $tempProspect = (object) [
// "guid" => "988145006634078224",
// "verified" => true,
// "username" => "oldprospector",
// "name" => "oldprospector",
// "icontime" => "1560987887"
// ];
// $tempRef = (object) [
// 'referrer_guid' => '987892327202689039',
// 'state' => 'complete',
// 'register_timestamp' => "1560857128000",
// 'join_timestamp' => "1560867128000",
// 'prospect' => $tempProspect
// ];
// array_push($response['referrals'], $tempRef);
return Factory::response($response);
}
// Not implemented
// Note: New referrals are added when prospect registers for Minds (in `Core/Events/Hooks/Register.php`)
public function post($pages)
{
}
// Notify a prospect to urge them to join the rewards program
......@@ -100,13 +83,13 @@ class referrals implements Interfaces\Api
->setProspectGuid($prospect_guid)
->setPingTimestamp(time());
$manager = Di::_()->get('Referrals\Manager');
$manager = Di::_()->get('Referrals\Manager');
if(!$manager->ping($referral)){
return Factory::response([
'status' => 'error',
'done' => false,
]);
}
}
return Factory::response([
'status' => 'success',
......
......@@ -93,6 +93,8 @@ use Minds\Traits\MagicAttributes;
* @method int getModeratorGuid()
* @method Blog setTimeModerated(int $timeModerated)
* @method int getTimeModerated()
* @method Blog setAllowComments(bool $allowComments)
* @method bool getAllowComments()
*/
class Blog extends RepositoryEntity
{
......@@ -235,6 +237,9 @@ class Blog extends RepositoryEntity
/** @var int */
protected $timeModerated;
/** @var bool */
protected $allowComments = true;
/**
* Blog constructor.
* @param null $eventsDispatcher
......@@ -573,6 +578,7 @@ class Blog extends RepositoryEntity
'tags',
'nsfw',
'nsfw_lock',
'allow_comments',
function ($export) {
return $this->_extendExport($export);
}
......@@ -599,6 +605,7 @@ class Blog extends RepositoryEntity
$output['tags'] = $this->getTags();
$output['nsfw'] = $this->getNsfw();
$output['nsfw_lock'] = $this->getNsfwLock();
$output['allow_comments'] = $this->getAllowComments();
$output['header_bg'] = $export['has_header_bg'];
if (!$this->isEphemeral()) {
......
......@@ -9,6 +9,7 @@
namespace Minds\Core\Blogs\Delegates;
use Minds\Core\Blogs\Blog;
use Minds\Core\Data\Call;
use Minds\Core\Entities\Actions\Save;
use Minds\Entities\Activity;
......@@ -17,22 +18,32 @@ class CreateActivity
/** @var Save */
protected $saveAction;
/** @var Call */
protected $db;
/**
* CreateActivity constructor.
* @param null $saveAction
*/
public function __construct($saveAction = null)
public function __construct($saveAction = null, Call $db=null)
{
$this->saveAction = $saveAction ?: new Save();
$this->db = $db ?? new Call('entities_by_time');
}
/**
* Creates a new activity for a blog
* @param Blog $blog
* @throws \Minds\Exceptions\StopEventException
* @return bool
*/
public function save(Blog $blog)
public function save(Blog $blog) : bool
{
$activities = $this->db->getRow("activity:entitylink:{$blog->getGuid()}");
if (!empty($activities)) {
return false;
}
$owner = $blog->getOwnerEntity();
$activity = (new Activity())
......@@ -53,5 +64,7 @@ class CreateActivity
$this->saveAction
->setEntity($activity)
->save();
return true;
}
}
......@@ -50,7 +50,8 @@ class Entity
'ownerObj' => 'ownerObj',
'nsfw' => 'nsfw',
'moderatorGuid' => 'moderator_guid',
'timeModerated' => 'time_moderated'
'timeModerated' => 'time_moderated',
'allowComments' => 'allow_comments',
];
static $jsonEncodedFields = [
......@@ -70,6 +71,7 @@ class Entity
'header_bg',
'monetized',
'paywall',
'allow_comments'
];
/**
......
......@@ -8,6 +8,7 @@
namespace Minds\Core\Comments;
use Minds\Common\Urn;
use Minds\Core\Di\Di;
use Minds\Core\EntitiesBuilder;
use Minds\Core\Luid;
......@@ -69,7 +70,7 @@ class Manager
$this->threadNotifications = $threadNotifications ?: new Delegates\ThreadNotifications();
$this->createEventDispatcher = $createEventDispatcher ?: new Delegates\CreateEventDispatcher();
$this->countCache = $countCache ?: new Delegates\CountCache();
$this->entitiesBuilder = $entitiesBuilder ?: Di::_()->get('EntitiesBuilder');
$this->entitiesBuilder = $entitiesBuilder ?: Di::_()->get('EntitiesBuilder');
$this->spam = $spam ?: Di::_()->get('Security\Spam');
}
......@@ -95,7 +96,7 @@ class Manager
}
$response = $this->repository->getList($opts);
if ($opts['is_focused'] === true && $opts['offset']) {
$count = count($response);
$diff = $opts['limit'] - $count;
......@@ -229,8 +230,8 @@ class Manager
public function delete(Comment $comment, $opts = [])
{
$opts = array_merge([
'force' => false,
], $opts);
'force' => false,
], $opts);
if (!$this->acl->write($comment) && !$opts['force']) {
return false; //TODO throw exception
......@@ -271,6 +272,34 @@ class Manager
return null;
}
/**
* @param string|Urn $urn
* @return Comment|null
* @throws \Exception
*/
public function getByUrn($urn)
{
if (is_string($urn)) {
$urn = new Urn($urn);
}
$components = explode(':', $urn->getNss());
if (count($components) !== 5) {
error_log("[CommentsManager]: Invalid Comment URN (${$components})");
return null;
}
$entityGuid = $components[0];
$parentPath = "{$components[1]}:{$components[2]}:{$components[3]}";
$guid = $components[4];
if ($this->legacyRepository->isLegacy($entityGuid)) {
return $this->legacyRepository->getByGuid($guid);
}
return $this->repository->get($entityGuid, $parentPath, $guid);
}
/**
* Counts comments on an entity
* @param int $entity_guid
......
......@@ -12,6 +12,11 @@ use Minds\Core\Di\Di;
use Minds\Core\Events\Dispatcher;
use Minds\Helpers\MagicAttributes;
/**
* Save Action
* @method Save setEntity($entity)
* @method bool save(...$args)
*/
class Save
{
/** @var Dispatcher */
......
......@@ -16,7 +16,7 @@ use Minds\Entities\Boost\BoostEntityInterface;
class BoostGuidResolverDelegate implements ResolverDelegate
{
/**
* @var Manager
* @var Manager
*/
protected $manager;
......
<?php
/**
* @author: Marcelo
*/
namespace Minds\Core\Entities\Delegates;
use Minds\Common\Urn;
use Minds\Core\Boost\Repository;
use Minds\Core\Comments\Comment;
use Minds\Core\Comments\Manager;
use Minds\Core\Di\Di;
use Minds\Core\EntitiesBuilder;
use Minds\Entities\Boost\BoostEntityInterface;
class CommentGuidResolverDelegate implements ResolverDelegate
{
/**
* @var Manager
*/
protected $manager;
/**
* CommentGuidResolverDelegate constructor.
* @param Manager $manager
*/
public function __construct($manager = null)
{
$this->manager = $manager ?: new Manager();
}
/**
* @param Urn $urn
* @return boolean
*/
public function shouldResolve(Urn $urn)
{
return $urn->getNid() === 'comment';
}
/**
* @param array $urns
* @param array $opts
* @return mixed
*/
public function resolve(array $urns, array $opts = [])
{
$entities = [];
foreach ($urns as $urn) {
/** @var Comment $comment */
$comment = $this->manager->getByUrn($urn);
$entities[] = $comment;
}
return $entities;
}
/**
* @param $urn
* @param Comment $entity
* @return mixed
*/
public function map($urn, $entity)
{
return $entity;
}
/**
* @param Comment $entity
* @return string|null
*/
public function asUrn($entity)
{
if (!$entity) {
return null;
}
return $entity->getUrn();
}
}
\ No newline at end of file
......@@ -79,6 +79,7 @@ class EntityGuidResolverDelegate implements ResolverDelegate
}
/**
* @param $urn
* @param mixed $entity
* @return mixed
*/
......
......@@ -8,12 +8,12 @@
namespace Minds\Core\Entities;
use Minds\Common\Urn;
use Minds\Core\Entities\Delegates\EntityGuidResolverDelegate;
use Minds\Core\Entities\Delegates\BoostGuidResolverDelegate;
use Minds\Core\Entities\Delegates\CommentGuidResolverDelegate;
use Minds\Core\Entities\Delegates\EntityGuidResolverDelegate;
use Minds\Core\Entities\Delegates\ResolverDelegate;
use Minds\Core\Security\ACL;
use Minds\Entities\User;
use Minds\Helpers\Flags;
class Resolver
{
......@@ -42,6 +42,7 @@ class Resolver
$this->resolverDelegates = $resolverDelegates ?: [
EntityGuidResolverDelegate::class => new EntityGuidResolverDelegate(),
BoostGuidResolverDelegate::class => new BoostGuidResolverDelegate(),
CommentGuidResolverDelegate::class => new CommentGuidResolverDelegate(),
];
$this->acl = $acl ?: ACL::_();
......@@ -122,13 +123,13 @@ class Resolver
// Filter out invalid entities
$sorted = array_filter($sorted, function($entity) { return (bool) $entity; });
$sorted = array_filter($sorted, function ($entity) { return (bool) $entity; });
// Filter out forbidden entities
$sorted = array_filter($sorted, function($entity) {
$sorted = array_filter($sorted, function ($entity) {
return $this->acl->read($entity, $this->user);
//&& !Flags::shouldFail($entity);
//&& !Flags::shouldFail($entity);
});
//
......@@ -138,7 +139,7 @@ class Resolver
public function single($urn)
{
$this->urns = [ $urn ];
$this->urns = [$urn];
$entities = $this->fetch();
return $entities[0];
}
......
......@@ -45,9 +45,9 @@ class Manager
$features = $this->config->get('features') ?: [];
if (!isset($features[$feature])) {
error_log("[Features\Manager] Feature '{$feature}' is not declared. Assuming true.");
error_log("[Features\Manager] Feature '{$feature}' is not declared. Assuming false.");
return true;
return false;
}
if ($features[$feature] === 'admin' && $this->user->isAdmin()) {
......
......@@ -19,6 +19,7 @@ class Minds extends base
Experiments\Module::class,
Helpdesk\Module::class,
Onboarding\Module::class,
Permissions\Module::class,
Subscriptions\Module::class,
SendWyre\Module::class,
Suggestions\Module::class,
......
<?php
namespace Minds\Core\Permissions;
use Minds\Core\Di\Di;
use Minds\Core\EntitiesBuilder;
use Minds\Core\Data\Call;
use Minds\Core\Entities\Actions\Save;
use Minds\Core\Permissions\Permissions;
class Manager
{
/** @var EntitiesBuilder $entitiesBuilder */
protected $entitiesBuilder;
/** @var Call */
protected $db;
/** @var Save */
protected $save;
public function __construct(
EntitiesBuilder $entitiesBuilder = null,
Call $db = null,
Save $save = null)
{
$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.
}
/**
* Save permissions for an entity and propegate it to linked objects
* @param mixed $entity a minds entity that implements the save function
* @param Permissions $permissions the flag to apply to the entity
*/
public function save($entity, Permissions $permissions)
{
$entity->setAllowComments($permissions->getAllowComments());
$this->save
->setEntity($entity)
->save();
if (method_exists($entity, 'getType')
&& $entity->getType() == 'activity'
&& $entity->get('entity_guid')
) {
$attachment = $this->entitiesBuilder->single($entity->get('entity_guid'));
$attachment->setAllowComments($permissions->getAllowComments());
$this->save
->setEntity($attachment)
->save();
}
foreach ($this->db->getRow('activity:entitylink:'.$entity->getGUID()) as $parentGuid => $ts) {
$activity = $this->entitiesBuilder->single($parentGuid);
$activity->setAllowComments($permissions->getAllowComments());
$this->save
->setEntity($activity)
->save();
}
}
}
<?php
/**
* Permissions module.
*/
namespace Minds\Core\Permissions;
use Minds\Interfaces\ModuleInterface;
class Module implements ModuleInterface
{
public function onInit()
{
$provider = new Provider();
$provider->register();
}
}
<?php
namespace Minds\Core\Permissions;
use Minds\Traits\MagicAttributes;
/**
* Class Permissions
* @method Permissions setAllowComments(bool $allowComments)
* @method bool getAllowComments();
*/
class Permissions {
use MagicAttributes;
/** @var bool AllowComments */
private $allowComments = true;
}
<?php
namespace Minds\Core\Permissions;
use Minds\Core\Di\Provider as DiProvider;
class Provider extends DiProvider
{
public function register()
{
$this->di->bind('Permissions\Manager', function ($di) {
return new Manager();
});
}
}
......@@ -44,15 +44,15 @@ class Manager
$response = $this->repository->getList($opts);
if ($opts['hydrate']) {
foreach ($response as $referral) {
if ($opts['hydrate']) {
foreach ($response as $referral) {
$prospect = $this->entitiesBuilder->single($referral->getProspectGuid());
$referral->setProspect($prospect);
}
}
return $response;
}
/**
* Create referral for pending prospect who registered for Minds
* @param Referral $referral
......@@ -61,7 +61,7 @@ class Manager
public function add($referral)
{
$this->repository->add($referral);
// Send a notification to the referrer
$this->notificationDelegate->notifyReferrer($referral);
......@@ -75,12 +75,12 @@ class Manager
*/
public function update($referral)
{
// Update the join timestamp
$this->repository->update($referral);
// Update join_timestamp
$this->repository->update($referral);
// Send a notification to the referrer
$this->notificationDelegate->notifyReferrer($referral);
return true;
}
......@@ -91,12 +91,12 @@ class Manager
*/
public function ping($referral)
{
// Update the ping timestamp
// Update ping_timestamp
$this->repository->ping($referral);
// Send a ping notification to the prospect
$this->notificationDelegate->notifyProspect($referral);
return true;
}
}
......@@ -65,12 +65,12 @@ class Referral
public function getPingable()
{
// determines duration a referrer must wait before re-pinging
// Duration referrer must wait before re-pinging
$waitTime= 1000*60*60*24*7; // 7 days
$now = time();
$now = time() * 1000;
$elapsedTime = $now - $this->pingTimestamp;
// referrer can't send another ping notification until a week has gone by
if ($this->pingTimestamp && $elapsedTime < $waitTime ) {
return false;
}
......
......@@ -2,12 +2,15 @@
/**
* Notification delegate for Verdicts
*/
namespace Minds\Core\Reports\Verdict\Delegates;
use Minds\Common\Urn;
use Minds\Core\Di\Di;
use Minds\Core\Reports\Verdict\Verdict;
use Minds\Core\Entities\Resolver;
use Minds\Core\EntitiesBuilder;
use Minds\Core\Events\EventsDispatcher;
use Minds\Common\Urn;
use Minds\Core\Reports\Verdict\Verdict;
class NotificationDelegate
{
......@@ -18,25 +21,36 @@ class NotificationDelegate
protected $entitiesBuilder;
/** @var Urn $urn */
protected $urn;
public function __construct($dispatcher = null, $entitiesBuilder = null, $urn = null)
/** @var Resolver */
protected $entitiesResolver;
public function __construct($dispatcher = null, $entitiesBuilder = null, $urn = null, $entitiesResolver = null)
{
$this->dispatcher = $dispatcher ?: Di::_()->get('EventsDispatcher');
$this->entitiesBuilder = $entitiesBuilder ?: Di::_()->get('EntitiesBuilder');
$this->urn = $urn ?: new Urn;
$this->entitiesResolver = $entitiesResolver ?: new Resolver();
}
/**
* Actioned notification
* @param Verdict $verdict
* @return void
* @throws \Exception
*/
public function onAction(Verdict $verdict)
{
$entityUrn = $verdict->getReport()->getEntityUrn();
$entityGuid = $this->urn->setUrn($entityUrn)->getNss();
$entity = $this->entitiesBuilder->single($entityGuid);
$entity = $this->entitiesResolver->single($this->urn->setUrn($entityUrn));
if (!$entity) {
$entityGuid = $this->urn->setUrn($entityUrn)->getNss();
$entity = $this->entitiesBuilder->single($entityGuid);
}
if ($verdict->isUpheld()) {
$readableAction = 'removed';
......@@ -58,13 +72,13 @@ class NotificationDelegate
} else {
$readableAction .= '. You can appeal this decision';
}
$this->dispatcher->trigger('notification', 'all', [
'to' => [$entity->getOwnerGuid()],
'entity' => $entity,
'from' => 100000000000000519,
'notification_view' => 'report_actioned',
'params' => [ 'action' => $readableAction ],
'params' => ['action' => $readableAction],
]);
}
......
......@@ -6,8 +6,9 @@ namespace Minds\Core\Rewards\Delegates;
use Minds\Core\Di\Di;
use Minds\Entities\User;
use Minds\Core\Referrals\Referral;
class ReferralDelegate
class ReferralDelegate
{
/** @var Manager $manager */
......@@ -29,9 +30,9 @@ class ReferralDelegate
$referral->setReferrerGuid((string) $user->referrer)
->setProspectGuid($user->guid)
->setJoinTimestamp(time());
$this->manager->update($referral);
}
}
\ No newline at end of file
......@@ -157,31 +157,20 @@ class Join
->setAction('joined')
->push();
//////////////////////////////////////////////
// TODOOJM confirm that this should still be removed
$this->testnetBalance->setUser($this->user);
$testnetBalanceVal = BigNumber::_($this->testnetBalance->get());
if ($testnetBalanceVal->lt(0)) {
return false; //balance negative
}
// Receive one free token automatically when you join rewards
$transactions = Di::_()->get('Blockchain\Wallets\OffChain\Transactions');
$transactions
->setUser($this->user)
->setType('joined')
->setAmount((string) $testnetBalanceVal); // replace 1 token (one with 80 zeros) 1 * 10 to the power of 18
//////////////////////////////////////////////
->setAmount(pow(10,18)); // TODO: v important - verify this is correct
$transaction = $transactions->create();
}
// validate referral and give referral prospect a token
// Validate referral and give both prospect and referrer +50 contribution score
if ($this->user->referrer && $this->user->guid != $this->user->referrer) {
$this->validator->setHash($hash);
$this->validator->setHash($hash);
if ($this->validator->validate()) {
$event = new Core\Analytics\Metrics\Event();
......@@ -194,27 +183,7 @@ class Join
->setAction('referral')
->push();
// OJMQ: instead of giving the prospect a token, isn't it better to give them +50 score too?
// TODO: get the prospect's hash and push an equivalent event for them??
// OR make a new Event.action type 'referred'? (OR alter the existing referral event)
// OJM: maybe something like this
// $event->setUserGuid((string) $this->user->referrer)
// ->setUserPhoneNumberHash($hash)
// ->setEntityGuid((string) $this->user->guid)
// ->push();
// OJM: this is what would be used if the prospect would get a token instead of +50
// // give the referral prospect a token
// $transactions = Di::_()->get('Blockchain\Wallets\OffChain\Transactions');
// $transactions
// ->setUser($this->user)
// ->setType('joined')
// ->setAmount((string) $testnetBalanceVal);
// // replace $testnetBalanceVal with 1 token (one with 80 zeros) 1 * 10 to the power of 18
// TODO: give prospect +50 contribution score as well
$this->referralDelegate->onReferral($this->user);
}
......
......@@ -186,7 +186,7 @@ class EntityMapping implements MappingInterface
$map['tags'] = [];
}
$map['tags'] = array_unique(array_merge($map['tags'], array_map('strtolower', $tags)));
$map['tags'] = array_values(array_unique(array_merge($map['tags'], array_map('strtolower', $tags))));
$map['nsfw'] = array_unique($this->entity->getNsfw());
......
......@@ -33,7 +33,7 @@ class GroupMapping extends EntityMapping implements MappingInterface
$map['membership'] = (int) $this->entity->getMembership();
$map['public'] = $map['membership'] == ACCESS_PUBLIC;
$map['tags'] = array_unique(array_merge($map['tags'], $this->entity->getTags()));
$map['tags'] = array_values(array_unique(array_merge($map['tags'], $this->entity->getTags())));
$map['rating'] = $this->entity->getRating();
return $map;
......
......@@ -53,7 +53,7 @@ class UserMapping extends EntityMapping implements MappingInterface
$map['group_membership'] = [];
}
$map['tags'] = array_unique($this->entity->getTags());
$map['tags'] = array_values(array_unique($this->entity->getTags()));
return $map;
}
......
......@@ -43,7 +43,7 @@ class User extends \ElggUser
$this->attributes['briefdescription'] = '';
$this->attributes['rating'] = 1;
$this->attributes['p2p_media_enabled'] = 0;
$this->attributes['is_mature'] = 0;
$this->attributes['is_mature'] = 0;
$this->attributes['mature_lock'] = 0;
$this->attributes['opted_in_hashtags'] = 0;
$this->attributes['last_accepted_tos'] = Core\Config::_()->get('last_tos_update');
......@@ -52,6 +52,7 @@ class User extends \ElggUser
$this->attributes['last_avatar_upload'] = 0;
$this->attributes['canary'] = 0;
$this->attributes['onchain_booster'] = null;
$this->attributes['toaster_notifications'] = 1;
parent::initializeAttributes();
}
......@@ -739,6 +740,7 @@ class User extends \ElggUser
$export['is_admin'] = $this->attributes['admin'] == 'yes';
$export['theme'] = $this->getTheme();
$export['onchain_booster'] = $this->getOnchainBooster();
$export['toaster_notifications'] = $this->getToasterNotifications();
if (is_string($export['social_profiles'])) {
$export['social_profiles'] = json_decode($export['social_profiles']);
......@@ -987,7 +989,8 @@ class User extends \ElggUser
'last_avatar_upload',
'canary',
'theme',
'onchain_booster'
'onchain_booster',
'toaster_notifications'
));
}
......@@ -1075,4 +1078,21 @@ class User extends \ElggUser
$this->onchain_booster = (int) $time;
return $this;
}
/**
* Returns toaster notifications state.
* @return boolean true if toaster notifications is enabled.
*/
public function getToasterNotifications()
{
return (bool) $this->toaster_notifications;
}
/**
* Set on/off toaster notifications
*/
public function setToasterNotifications($enabled = true)
{
$this->toaster_notifications = $enabled ? 1 : 0;
}
}
<?php
namespace Spec\Minds\Common;
use Minds\Common\Access;
use PhpSpec\ObjectBehavior;
class AccessSpec extends ObjectBehavior
{
public function it_should_return_string_for_an_access_id()
{
$this::idToString(Access::UNLISTED)->shouldBe('Unlisted');
}
public function it_should_return_unknown_for_invalid_access_id()
{
$this::idToString(666)->shouldBe('Unknown');
}
}
......@@ -239,4 +239,10 @@ class BlogSpec extends ObjectBehavior
$export = $this->export()->getWrappedObject();
expect($export['published'])->toBe(false);
}
function it_should_allow_comments() {
$this->getAllowComments()->shouldBe(true);
$this->setAllowComments(false);
$this->getAllowComments()->shouldBe(false);
}
}
......@@ -3,6 +3,7 @@
namespace Spec\Minds\Core\Blogs\Delegates;
use Minds\Core\Blogs\Blog;
use Minds\Core\Data\Call;
use Minds\Core\Entities\Actions\Save;
use Minds\Entities\Activity;
use Minds\Entities\User;
......@@ -14,12 +15,16 @@ class CreateActivitySpec extends ObjectBehavior
/** @var Save */
protected $saveAction;
/** @var Call */
protected $db;
function let(
Save $saveAction
Save $saveAction,
Call $db
) {
$this->beConstructedWith($saveAction);
$this->beConstructedWith($saveAction, $db);
$this->saveAction = $saveAction;
$this->db = $db;
}
function it_is_initializable()
......@@ -27,7 +32,7 @@ class CreateActivitySpec extends ObjectBehavior
$this->shouldHaveType('Minds\Core\Blogs\Delegates\CreateActivity');
}
function it_should_save(
function it_should_save_when_no_activity(
Blog $blog,
User $user
)
......@@ -64,6 +69,10 @@ class CreateActivitySpec extends ObjectBehavior
->shouldBeCalled()
->willReturn(false);
$blog->getGuid()
->shouldBeCalled()
->willReturn(9999);
$user->export()
->shouldBeCalled()
->willReturn([]);
......@@ -72,6 +81,10 @@ class CreateActivitySpec extends ObjectBehavior
->shouldBeCalled()
->willReturn(1000);
$this->db->getRow("activity:entitylink:9999")
->shouldBeCalled()
->willReturn([]);
$this->saveAction->setEntity(Argument::type(Activity::class))
->shouldBeCalled()
->willReturn($this->saveAction);
......@@ -82,7 +95,23 @@ class CreateActivitySpec extends ObjectBehavior
$this
->save($blog)
->shouldNotThrow();
->shouldReturn(true);
}
function it_should_not_save_when_previous_activity(
Blog $blog
)
{
$blog->getGuid()
->shouldBeCalled()
->willReturn(9999);
$this->db->getRow("activity:entitylink:9999")
->shouldBeCalled()
->willReturn(['activity1']);
$this
->save($blog)
->shouldReturn(false);
}
}
......@@ -24,13 +24,13 @@ class ManagerSpec extends ObjectBehavior
$this->shouldHaveType(Manager::class);
}
function it_should_check_if_a_feature_exists_unsuccessfully_and_assume_its_active()
function it_should_check_if_a_feature_exists_unsuccessfully_and_assume_its_inactive()
{
$this->config->get('features')
->shouldBeCalled()
->willReturn(['plus' => true, 'wire' => false]);
$this->has('boost')->shouldReturn(true);
$this->has('boost')->shouldReturn(false);
}
function it_should_check_if_a_feature_exists_and_return_its_deactivated()
......
<?php
namespace Spec\Minds\Core\Permissions;
use Minds\Core\Permissions\Manager;
use Minds\Core\Permissions\Permissions;
use Minds\Entities\User;
use PhpSpec\ObjectBehavior;
use Minds\Core\EntitiesBuilder;
use Minds\Core\Data\Call;
use Minds\Core\Entities\Actions\Save;
use Minds\Entities\Entity;
use Minds\Entities\Image;
class ManagerSpec extends ObjectBehavior
{
/** @var User */
protected $user;
/** @var EntitiesBuilder */
protected $entitiesBuilder;
/** @var Call */
protected $db;
/** @var Save */
protected $save;
public function let(
EntitiesBuilder $entitiesBuilder,
Call $db,
Save $save
) {
$this->entitiesBuilder = $entitiesBuilder;
$this->db = $db;
$this->save = $save;
$this->beConstructedWith($this->entitiesBuilder, $this->db, $this->save);
}
public function it_is_initializable()
{
$this->shouldHaveType(Manager::class);
}
public function it_should_save_entity_permissions(Entity $entity, Image $image)
{
$permissions = new Permissions();
$entity->setAllowComments(true)->shouldBeCalled();
$entity->get('entity_guid')->shouldBeCalled()->willReturn(false);
$entity->getGUID()->shouldBeCalled()->willReturn(1);
$entity->getType()->shouldBeCalled()->willReturn('activity');
$this->db->getRow('activity:entitylink:1')->shouldBeCalled()->willReturn([]);
$this->save->setEntity($entity)->shouldBeCalled()->willReturn($this->save);
$this->save->save()->shouldBeCalled();
$this->save($entity, $permissions);
}
public function it_should_save_attachment_permissions(Entity $entity, Image $image)
{
$permissions = new Permissions();
$image->setAllowComments(true)->shouldBeCalled();
$this->entitiesBuilder->single(1)->shouldBeCalled()->willReturn($image);
$entity->setAllowComments(true)->shouldBeCalled();
$entity->getGUID()->shouldBeCalled()->willReturn(1);
$entity->getType()->shouldBeCalled()->willReturn('activity');
$entity->get('entity_guid')->shouldBeCalled()->willReturn(1);
$this->db->getRow('activity:entitylink:1')->shouldBeCalled()->willReturn([]);
$this->save->setEntity($entity)->shouldBeCalled()->willReturn($this->save);
$this->save->setEntity($image)->shouldBeCalled()->willReturn($this->save);
$this->save->save()->shouldBeCalled();
$this->save($entity, $permissions);
}
public function it_should_save_linked_entity_permissions(Entity $entity, Entity $parent)
{
$permissions = new Permissions();
$parent->setAllowComments(true)->shouldBeCalled();
$this->db->getRow('activity:entitylink:1')->shouldBeCalled()
->willReturn([2 => $parent]);
$this->entitiesBuilder->single(1)->shouldBeCalled()->willReturn($parent);
$entity->setAllowComments(true)->shouldBeCalled();
$entity->getGUID()->shouldBeCalled()->willReturn(1);
$entity->getType()->shouldBeCalled()->willReturn('activity');
$entity->get('entity_guid')->shouldBeCalled()->willReturn(1);
$this->db->getRow('activity:entitylink:1')->shouldBeCalled()->willReturn([]);
$this->save->setEntity($entity)->shouldBeCalled()->willReturn($this->save);
$this->save->setEntity($parent)->shouldBeCalled()->willReturn($this->save);
$this->save->save()->shouldBeCalled();
$this->save($entity, $permissions);
}
}
......@@ -2,11 +2,13 @@
namespace Spec\Minds\Core\Reports\Verdict\Delegates;
use Minds\Common\Urn;
use Minds\Core\Entities\Resolver;
use Minds\Core\EntitiesBuilder;
use Minds\Core\Events\EventsDispatcher;
use Minds\Core\Reports\Report;
use Minds\Core\Reports\Verdict\Delegates\NotificationDelegate;
use Minds\Core\Reports\Verdict\Verdict;
use Minds\Core\Reports\Report;
use Minds\Core\Events\EventsDispatcher;
use Minds\Core\EntitiesBuilder;
use Minds\Entities\Entity;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
......@@ -19,11 +21,19 @@ class NotificationDelegateSpec extends ObjectBehavior
/** @var EntitiesBuilder $entitiesBuilder */
private $entitiesBuilder;
function let(EventsDispatcher $dispatcher, EntitiesBuilder $entitiesBuilder)
/** @var Urn */
private $urn;
/** @var Resolver */
private $entitiesResolver;
function let(EventsDispatcher $dispatcher, EntitiesBuilder $entitiesBuilder, Urn $urn, Resolver $entitiesResolver)
{
$this->beConstructedWith($dispatcher, $entitiesBuilder);
$this->beConstructedWith($dispatcher, $entitiesBuilder, $urn, $entitiesResolver);
$this->dispatcher = $dispatcher;
$this->entitiesBuilder = $entitiesBuilder;
$this->urn = $urn;
$this->entitiesResolver = $entitiesResolver;
}
function it_is_initializable()
......@@ -37,6 +47,18 @@ class NotificationDelegateSpec extends ObjectBehavior
->shouldBeCalled()
->willReturn('urn:activity:123');
$this->urn->setUrn('urn:activity:123')
->shouldBeCalled()
->willReturn($this->urn);
$this->entitiesResolver->single($this->urn)
->shouldBeCalled()
->willReturn(null);
$this->urn->getNss()
->shouldBeCalled()
->willReturn('123');
$report->getReasonCode()
->shouldBeCalled()
->willReturn(2);
......@@ -54,7 +76,43 @@ class NotificationDelegateSpec extends ObjectBehavior
$this->entitiesBuilder->single(123)
->willReturn($entity);
$this->dispatcher->trigger('notification', 'all', Argument::that(function($opts) {
$this->dispatcher->trigger('notification', 'all', Argument::that(function ($opts) {
return $opts['params']['action'] === 'marked as nsfw. You can appeal this decision';
}))
->shouldBeCalled();
$this->onAction($verdict);
}
function it_should_send_a_marked_as_nsfw_notification_but_resolving_urn_with_entitiesResolver(Verdict $verdict, Report $report, Entity $entity)
{
$report->getEntityUrn()
->shouldBeCalled()
->willReturn('urn:activity:123');
$this->urn->setUrn('urn:activity:123')
->shouldBeCalled()
->willReturn($this->urn);
$this->entitiesResolver->single($this->urn)
->shouldBeCalled()
->willReturn($entity);
$report->getReasonCode()
->shouldBeCalled()
->willReturn(2);
$report->isAppeal()
->shouldBeCalled()
->willReturn(false);
$verdict->getReport()
->willReturn($report);
$verdict->isUpheld()
->willReturn(true);
$this->dispatcher->trigger('notification', 'all', Argument::that(function ($opts) {
return $opts['params']['action'] === 'marked as nsfw. You can appeal this decision';
}))
->shouldBeCalled();
......@@ -68,6 +126,18 @@ class NotificationDelegateSpec extends ObjectBehavior
->shouldBeCalled()
->willReturn('urn:activity:123');
$this->urn->setUrn('urn:activity:123')
->shouldBeCalled()
->willReturn($this->urn);
$this->entitiesResolver->single($this->urn)
->shouldBeCalled()
->willReturn(null);
$this->urn->getNss()
->shouldBeCalled()
->willReturn('123');
$report->getReasonCode()
->shouldBeCalled()
->willReturn(4);
......@@ -85,7 +155,7 @@ class NotificationDelegateSpec extends ObjectBehavior
$this->entitiesBuilder->single(123)
->willReturn($entity);
$this->dispatcher->trigger('notification', 'all', Argument::that(function($opts) {
$this->dispatcher->trigger('notification', 'all', Argument::that(function ($opts) {
return $opts['params']['action'] === 'removed. You can appeal this decision';
}))
->shouldBeCalled();
......@@ -99,6 +169,18 @@ class NotificationDelegateSpec extends ObjectBehavior
->shouldBeCalled()
->willReturn('urn:activity:123');
$this->urn->setUrn('urn:activity:123')
->shouldBeCalled()
->willReturn($this->urn);
$this->entitiesResolver->single($this->urn)
->shouldBeCalled()
->willReturn(null);
$this->urn->getNss()
->shouldBeCalled()
->willReturn('123');
$report->isAppeal()
->shouldBeCalled()
->willReturn(true);
......@@ -112,7 +194,7 @@ class NotificationDelegateSpec extends ObjectBehavior
$this->entitiesBuilder->single(123)
->willReturn($entity);
$this->dispatcher->trigger('notification', 'all', Argument::that(function($opts) {
$this->dispatcher->trigger('notification', 'all', Argument::that(function ($opts) {
return $opts['params']['action'] === 'restored by the community appeal jury';
}))
->shouldBeCalled();
......@@ -126,6 +208,18 @@ class NotificationDelegateSpec extends ObjectBehavior
->shouldBeCalled()
->willReturn('urn:activity:123');
$this->urn->setUrn('urn:activity:123')
->shouldBeCalled()
->willReturn($this->urn);
$this->entitiesResolver->single($this->urn)
->shouldBeCalled()
->willReturn(null);
$this->urn->getNss()
->shouldBeCalled()
->willReturn('123');
$report->isAppeal()
->shouldBeCalled()
->willReturn(false);
......
......@@ -31,4 +31,10 @@ class ActivitySpec extends ObjectBehavior
'tokens' => 10
]);
}
public function it_allows_comments() {
$this->getAllowComments()->shouldBe(true);
$this->setAllowComments(false);
$this->getAllowComments()->shouldBe(false);
}
}
......@@ -17,6 +17,7 @@
* @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 bool $allow_comments A boolean value that turns off comments for an entity
* @property-read string $enabled
*/
abstract class ElggEntity extends ElggData implements
......@@ -74,6 +75,7 @@ abstract class ElggEntity extends ElggData implements
$this->attributes['nsfw_lock'] = [];
$this->attributes['moderator_guid'] = null;
$this->attributes['time_moderated'] = null;
$this->attributes['allow_comments'] = true;
}
/**
......@@ -1381,7 +1383,8 @@ abstract class ElggEntity extends ElggData implements
'access_id',
'tags',
'nsfw',
'nsfw_lock'
'nsfw_lock',
'allow_comments'
);
}
......@@ -1651,20 +1654,36 @@ abstract class ElggEntity extends ElggData implements
$this->moderator_guid = $moderatorGuid;
}
/**
* Marks the time as when an entity was moderated
* @param int $timeModerated unix timestamp when the entity was moderated
*/
/**
* Marks the time as when an entity was moderated
* @param int $timeModerated unix timestamp when the entity was moderated
*/
public function setTimeModerated(int $timeModerated)
{
$this->time_moderated = $timeModerated;
}
}
/**
* Gets the time moderated
* @return int
*/
/**
* Gets the time moderated
* @return int
*/
public function getTimeModerated() {
return $this->time_moderated;
return $this->time_moderated;
}
/**
* Sets the flag for allowing comments on an entity
* @param bool $allowComments
*/
public function setAllowComments(bool $allowComments) {
$this->allow_comments = $allowComments;
return $this;
}
/**
* Gets the flag for allowing comments on an entity
*/
public function getAllowComments() {
return $this->allow_comments;
}
}
......@@ -470,6 +470,7 @@ $CONFIG->set('features', [
'top-feeds' => true,
'cassandra-notifications' => true,
'dark-mode' => true,
'allow-comments-toggle' => false
]);
$CONFIG->set('email', [
......@@ -552,3 +553,13 @@ $CONFIG->cinemr_url = 'https://cinemr.s3.amazonaws.com/cinemr_dev/';
$CONFIG->mongodb_servers = ['minds_mongo_1'];
$CONFIG->set('last_tos_update', 1);
$CONFIG->set('gitlab', [
'project_id' => [
'mobile' => '10171280', // project id mobile
'front' => '10152778', // project id front
],
'private_key' => '{{private-key}}'
]);