...
 
Commits (2)
  • Brian Hatchet's avatar
    (feat) disabling comments · 31e1042b
    Brian Hatchet authored
    Backend for turning off comments
    Extended the base entities to track whether or not comments are allowed
    Setup a Permissions/Manager to centralize applying of permissions (much more to come, this is just setting up the flow with comments feature)
    Setup a new permissions end point that validates and applies the permissions
    Extended the comments endpoint to enforce comments permissions
    31e1042b
  • Mark Harding's avatar
    Merge branch 'feat/disable-comments-526' into 'master' · e33eedaf
    Mark Harding authored
    (feat) Disabling comments 526
    
    See merge request !255
    e33eedaf
......@@ -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',
......
<?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.
}
}
......@@ -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()) {
......
......@@ -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'
];
/**
......
......@@ -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 */
......
......@@ -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();
});
}
}
......@@ -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);
}
}
......@@ -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);
}
}
......@@ -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', [
......