Skip to content

Commit

Permalink
Deprecate GetModelIsPrivate, replace with extender (#2587)
Browse files Browse the repository at this point in the history
  • Loading branch information
askvortsov1 authored and KyrneDev committed Feb 20, 2021
1 parent 7aa221f commit 36956a9
Show file tree
Hide file tree
Showing 6 changed files with 236 additions and 14 deletions.
31 changes: 31 additions & 0 deletions src/Database/DatabaseServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@

namespace Flarum\Database;

use Flarum\Discussion\Discussion;
use Flarum\Event\GetModelIsPrivate;
use Flarum\Foundation\AbstractServiceProvider;
use Flarum\Post\Post;
use Illuminate\Database\Capsule\Manager;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Database\ConnectionResolverInterface;
Expand Down Expand Up @@ -58,6 +61,15 @@ public function register()
$this->app->singleton(MigrationRepositoryInterface::class, function ($app) {
return new DatabaseMigrationRepository($app['flarum.db'], 'migrations');
});

$this->app->singleton('flarum.database.model_private_checkers', function () {
// Discussion and Post are explicitly listed here to trigger the deprecated
// event-based model privacy system. They should be removed in beta 17.
return [
Discussion::class => [],
Post::class => []
];
});
}

/**
Expand All @@ -67,5 +79,24 @@ public function boot()
{
AbstractModel::setConnectionResolver($this->app->make(ConnectionResolverInterface::class));
AbstractModel::setEventDispatcher($this->app->make('events'));

foreach ($this->app->make('flarum.database.model_private_checkers') as $modelClass => $checkers) {
$modelClass::saving(function ($instance) use ($checkers) {
foreach ($checkers as $checker) {
if ($checker($instance) === true) {
$instance->is_private = true;

return;
}
}

$instance->is_private = false;

// @deprecated BC layer, remove beta 17
$event = new GetModelIsPrivate($instance);

$instance->is_private = $this->app->make('events')->until($event) === true;
});
}
}
}
7 changes: 0 additions & 7 deletions src/Discussion/Discussion.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
use Flarum\Discussion\Event\Renamed;
use Flarum\Discussion\Event\Restored;
use Flarum\Discussion\Event\Started;
use Flarum\Event\GetModelIsPrivate;
use Flarum\Foundation\EventGeneratorTrait;
use Flarum\Notification\Notification;
use Flarum\Post\MergeableInterface;
Expand Down Expand Up @@ -109,12 +108,6 @@ public static function boot()

Notification::whereSubject($discussion)->delete();
});

static::saving(function (self $discussion) {
$event = new GetModelIsPrivate($discussion);

$discussion->is_private = static::$dispatcher->until($event) === true;
});
}

/**
Expand Down
2 changes: 2 additions & 0 deletions src/Event/GetModelIsPrivate.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
use Flarum\Database\AbstractModel;

/**
* @deprecated beta 16, remove beta 17.
*
* Determine whether or not a model should be marked as `is_private`.
*/
class GetModelIsPrivate
Expand Down
82 changes: 82 additions & 0 deletions src/Extend/ModelPrivate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/

namespace Flarum\Extend;

use Flarum\Extension\Extension;
use Flarum\Foundation\ContainerUtil;
use Illuminate\Contracts\Container\Container;

/**
* Some models, in particular Discussion and Post, are intended to
* support a "private" mode, wherein they aren't visible unless some
* criteria is met. This can be used to implement anything from
* private discussions to post approvals.
*
* When a model is saved, any "privacy checkers" registered for it will
* be run. If any privacy checkers return `true`, the `is_private` field
* of that model instance will be set to `true`. Otherwise, it will be set to
* `false`. Accordingly, this is only available for models with an `is_private`
* field.
*
* In Flarum core, the Discussion and Post models come with private support.
* Core also contains visibility scopers that hide instances of these models
* with `is_private = true` from queries. Extensions can register custom scopers
* for these classes with the `viewPrivate` ability to grant access to view some
* private instances under some conditions.
*/
class ModelPrivate implements ExtenderInterface
{
private $modelClass;
private $checkers = [];

/**
* @param string $modelClass The ::class attribute of the model you are applying scopers to.
* This model must have a `is_private` field.
*/
public function __construct(string $modelClass)
{
$this->modelClass = $modelClass;
}

/**
* Add a model privacy checker.
*
* @param callable|string $callback
*
* The callback can be a closure or invokable class, and should accept:
* - \Flarum\User\User $actor
* - \Illuminate\Database\Eloquent\Builder $query
*
* It should return `true` if the model instance should be made private.
*
* @return self
*/
public function checker($callback)
{
$this->checkers[] = $callback;

return $this;
}

public function extend(Container $container, Extension $extension = null)
{
if (! class_exists($this->modelClass)) {
return;
}

$container->extend('flarum.database.model_private_checkers', function ($originalCheckers) use ($container) {
foreach ($this->checkers as $checker) {
$originalCheckers[$this->modelClass][] = ContainerUtil::wrapCallback($checker, $container);
}

return $originalCheckers;
});
}
}
7 changes: 0 additions & 7 deletions src/Post/Post.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
use Flarum\Database\AbstractModel;
use Flarum\Database\ScopeVisibilityTrait;
use Flarum\Discussion\Discussion;
use Flarum\Event\GetModelIsPrivate;
use Flarum\Foundation\EventGeneratorTrait;
use Flarum\Notification\Notification;
use Flarum\Post\Event\Deleted;
Expand Down Expand Up @@ -96,12 +95,6 @@ public static function boot()
$post->discussion->save();
});

static::saving(function (self $post) {
$event = new GetModelIsPrivate($post);

$post->is_private = static::$dispatcher->until($event) === true;
});

static::deleted(function (self $post) {
$post->raise(new Deleted($post));

Expand Down
121 changes: 121 additions & 0 deletions tests/integration/extenders/ModelPrivateTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php

/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/

namespace Flarum\Tests\integration\extenders;

use Flarum\Discussion\Discussion;
use Flarum\Extend;
use Flarum\Tests\integration\RetrievesAuthorizedUsers;
use Flarum\Tests\integration\TestCase;
use Flarum\User\User;

class ModelPrivateTest extends TestCase
{
use RetrievesAuthorizedUsers;

/**
* @test
*/
public function discussion_isnt_saved_as_private_by_default()
{
$this->app();

$user = User::find(1);

$discussion = Discussion::start('Some Discussion', $user);
$discussion->save();

$this->assertFalse($discussion->is_private);
}

/**
* @test
*/
public function discussion_is_saved_as_private_if_privacy_checker_added()
{
$this->extend(
(new Extend\ModelPrivate(Discussion::class))
->checker(function ($discussion) {
return $discussion->title === 'Private Discussion';
})
);

$this->app();

$user = User::find(1);

$privateDiscussion = Discussion::start('Private Discussion', $user);
$publicDiscussion = Discussion::start('Public Discussion', $user);
$privateDiscussion->save();
$publicDiscussion->save();

$this->assertTrue($privateDiscussion->is_private);
$this->assertFalse($publicDiscussion->is_private);
}

/**
* @test
*/
public function discussion_is_saved_as_private_if_privacy_checker_added_via_invokable_class()
{
$this->extend(
(new Extend\ModelPrivate(Discussion::class))
->checker(CustomPrivateChecker::class)
);

$this->app();

$user = User::find(1);

$privateDiscussion = Discussion::start('Private Discussion', $user);
$publicDiscussion = Discussion::start('Public Discussion', $user);
$privateDiscussion->save();
$publicDiscussion->save();

$this->assertTrue($privateDiscussion->is_private);
$this->assertFalse($publicDiscussion->is_private);
}

/**
* @test
*/
public function private_checkers_that_return_false_dont_matter()
{
$this->extend(
(new Extend\ModelPrivate(Discussion::class))
->checker(function ($discussion) {
return false;
})
->checker(CustomPrivateChecker::class)
->checker(function ($discussion) {
return false;
})
);

$this->app();

$user = User::find(1);

$privateDiscussion = Discussion::start('Private Discussion', $user);
$publicDiscussion = Discussion::start('Public Discussion', $user);
$privateDiscussion->save();
$publicDiscussion->save();

$this->assertTrue($privateDiscussion->is_private);
$this->assertFalse($publicDiscussion->is_private);
}
}

class CustomPrivateChecker
{
public function __invoke($discussion)
{
return $discussion->title === 'Private Discussion';
}
}

0 comments on commit 36956a9

Please sign in to comment.