Deprecate GetModelIsPrivate, replace with extender (#2587)

This commit is contained in:
Alexander Skvortsov 2021-02-04 10:56:10 -05:00 committed by GitHub
parent 17f15e36eb
commit 8366ec720e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 236 additions and 14 deletions

View File

@ -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;
@ -58,6 +61,15 @@ class DatabaseServiceProvider extends AbstractServiceProvider
$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 => []
];
});
}
/**
@ -67,5 +79,24 @@ class DatabaseServiceProvider extends AbstractServiceProvider
{
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;
});
}
}
}

View File

@ -17,7 +17,6 @@ use Flarum\Discussion\Event\Hidden;
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;
@ -109,12 +108,6 @@ class Discussion extends AbstractModel
Notification::whereSubject($discussion)->delete();
});
static::saving(function (self $discussion) {
$event = new GetModelIsPrivate($discussion);
$discussion->is_private = static::$dispatcher->until($event) === true;
});
}
/**

View File

@ -12,6 +12,8 @@ namespace Flarum\Event;
use Flarum\Database\AbstractModel;
/**
* @deprecated beta 16, remove beta 17.
*
* Determine whether or not a model should be marked as `is_private`.
*/
class GetModelIsPrivate

View File

@ -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;
});
}
}

View File

@ -12,7 +12,6 @@ namespace Flarum\Post;
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;
@ -96,12 +95,6 @@ class Post extends AbstractModel
$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));

View File

@ -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';
}
}