From 36956a93f08532befb84e76e0fbff2287dc9aa6c Mon Sep 17 00:00:00 2001 From: Alexander Skvortsov <38059171+askvortsov1@users.noreply.github.com> Date: Thu, 4 Feb 2021 10:56:10 -0500 Subject: [PATCH] Deprecate GetModelIsPrivate, replace with extender (#2587) --- src/Database/DatabaseServiceProvider.php | 31 +++++ src/Discussion/Discussion.php | 7 - src/Event/GetModelIsPrivate.php | 2 + src/Extend/ModelPrivate.php | 82 ++++++++++++ src/Post/Post.php | 7 - .../extenders/ModelPrivateTest.php | 121 ++++++++++++++++++ 6 files changed, 236 insertions(+), 14 deletions(-) create mode 100644 src/Extend/ModelPrivate.php create mode 100644 tests/integration/extenders/ModelPrivateTest.php diff --git a/src/Database/DatabaseServiceProvider.php b/src/Database/DatabaseServiceProvider.php index 8b8946baac3..e79b9bfbc34 100644 --- a/src/Database/DatabaseServiceProvider.php +++ b/src/Database/DatabaseServiceProvider.php @@ -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 @@ 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 => [] + ]; + }); } /** @@ -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; + }); + } } } diff --git a/src/Discussion/Discussion.php b/src/Discussion/Discussion.php index 5d2ecb6ed13..db96639eff5 100644 --- a/src/Discussion/Discussion.php +++ b/src/Discussion/Discussion.php @@ -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; @@ -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; - }); } /** diff --git a/src/Event/GetModelIsPrivate.php b/src/Event/GetModelIsPrivate.php index ffb572b9c1d..5dec691c494 100644 --- a/src/Event/GetModelIsPrivate.php +++ b/src/Event/GetModelIsPrivate.php @@ -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 diff --git a/src/Extend/ModelPrivate.php b/src/Extend/ModelPrivate.php new file mode 100644 index 00000000000..197e77e1332 --- /dev/null +++ b/src/Extend/ModelPrivate.php @@ -0,0 +1,82 @@ +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; + }); + } +} diff --git a/src/Post/Post.php b/src/Post/Post.php index fc9ef843bf2..4b294a55ff5 100644 --- a/src/Post/Post.php +++ b/src/Post/Post.php @@ -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; @@ -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)); diff --git a/tests/integration/extenders/ModelPrivateTest.php b/tests/integration/extenders/ModelPrivateTest.php new file mode 100644 index 00000000000..a2983e9d915 --- /dev/null +++ b/tests/integration/extenders/ModelPrivateTest.php @@ -0,0 +1,121 @@ +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'; + } +}