]> BookStack Code Mirror - bookstack/commitdiff
Added command to copy shelf permissions
authorDan Brown <redacted>
Wed, 11 Dec 2019 21:21:19 +0000 (21:21 +0000)
committerDan Brown <redacted>
Wed, 11 Dec 2019 21:22:03 +0000 (21:22 +0000)
Has options to run for all or to specify a slug for a specific shelf.

Closes #1091

app/Console/Commands/CopyShelfPermissions.php [new file with mode: 0644]
app/Entities/Repos/BookshelfRepo.php
tests/CommandsTest.php

diff --git a/app/Console/Commands/CopyShelfPermissions.php b/app/Console/Commands/CopyShelfPermissions.php
new file mode 100644 (file)
index 0000000..d9a1c1d
--- /dev/null
@@ -0,0 +1,88 @@
+<?php
+
+namespace BookStack\Console\Commands;
+
+use BookStack\Entities\Bookshelf;
+use BookStack\Entities\Repos\BookshelfRepo;
+use Illuminate\Console\Command;
+
+class CopyShelfPermissions extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'bookstack:copy-shelf-permissions
+                            {--a|all : Perform for all shelves in the system}
+                            {--s|slug= : The slug for a shelf to target}
+                            ';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Copy shelf permissions to all child books.';
+
+    /**
+     * @var BookshelfRepo
+     */
+    protected $bookshelfRepo;
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct(BookshelfRepo $repo)
+    {
+        $this->bookshelfRepo = $repo;
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+        $shelfSlug = $this->option('slug');
+        $cascadeAll = $this->option('all');
+        $shelves = null;
+
+        if (!$cascadeAll && !$shelfSlug) {
+            $this->error('Either a --slug or --all option must be provided.');
+            return;
+        }
+
+        if ($cascadeAll) {
+            $continue = $this->confirm(
+                'Permission settings for all shelves will be cascaded. '.
+                        'Books assigned to multiple shelves will receive only the permissions of it\'s last processed shelf. '.
+                        'Are you sure you want to proceed?'
+            );
+
+            if (!$continue && !$this->hasOption('no-interaction')) {
+                return;
+            }
+
+            $shelves = Bookshelf::query()->get(['id', 'restricted']);
+        }
+
+        if ($shelfSlug) {
+            $shelves = Bookshelf::query()->where('slug', '=', $shelfSlug)->get(['id', 'restricted']);
+            if ($shelves->count() === 0) {
+                $this->info('No shelves found with the given slug.');
+            }
+        }
+
+        foreach ($shelves as $shelf) {
+            $this->bookshelfRepo->copyDownPermissions($shelf, false);
+            $this->info('Copied permissions for shelf [' . $shelf->id . ']');
+        }
+
+        $this->info('Permissions copied for ' . $shelves->count() . ' shelves.');
+    }
+}
index ab4a518054eb89ac76c47855bd0d811f09792ea7..03b54f009a5ae6215656b39cdf2cb023db6513e3 100644 (file)
@@ -139,15 +139,15 @@ class BookshelfRepo
     /**
      * Copy down the permissions of the given shelf to all child books.
      */
-    public function copyDownPermissions(Bookshelf $shelf): int
+    public function copyDownPermissions(Bookshelf $shelf, $checkUserPermissions = true): int
     {
         $shelfPermissions = $shelf->permissions()->get(['role_id', 'action'])->toArray();
-        $shelfBooks = $shelf->books()->get();
+        $shelfBooks = $shelf->books()->get(['id', 'restricted']);
         $updatedBookCount = 0;
 
         /** @var Book $book */
         foreach ($shelfBooks as $book) {
-            if (!userCan('restrictions-manage', $book)) {
+            if ($checkUserPermissions && !userCan('restrictions-manage', $book)) {
                 continue;
             }
             $book->permissions()->delete();
index 4aef0ed2658861b2fe8a6f326c12ea131e3888ee..099af2939ddfd932aa1b5b9abb73544d3ce9adda 100644 (file)
@@ -1,6 +1,7 @@
 <?php namespace Tests;
 
 use BookStack\Auth\Permissions\JointPermission;
+use BookStack\Entities\Bookshelf;
 use BookStack\Entities\Page;
 use BookStack\Auth\User;
 use BookStack\Entities\Repos\PageRepo;
@@ -118,4 +119,51 @@ class CommandsTest extends TestCase
         $this->assertTrue(User::where('email', '=', '[email protected]')->first()->hasSystemRole('admin'), 'User has admin role as expected');
         $this->assertTrue(\Auth::attempt(['email' => '[email protected]', 'password' => 'testing-4']), 'Password stored as expected');
     }
+
+    public function test_copy_shelf_permissions_command_shows_error_when_no_required_option_given()
+    {
+        $this->artisan('bookstack:copy-shelf-permissions')
+            ->expectsOutput('Either a --slug or --all option must be provided.')
+            ->assertExitCode(0);
+    }
+
+    public function test_copy_shelf_permissions_command_using_slug()
+    {
+        $shelf = Bookshelf::first();
+        $child = $shelf->books()->first();
+        $editorRole = $this->getEditor()->roles()->first();
+        $this->assertFalse(boolval($child->restricted), "Child book should not be restricted by default");
+        $this->assertTrue($child->permissions()->count() === 0, "Child book should have no permissions by default");
+
+        $this->setEntityRestrictions($shelf, ['view', 'update'], [$editorRole]);
+        $this->artisan('bookstack:copy-shelf-permissions', [
+            '--slug' => $shelf->slug,
+        ]);
+        $child = $shelf->books()->first();
+
+        $this->assertTrue(boolval($child->restricted), "Child book should now be restricted");
+        $this->assertTrue($child->permissions()->count() === 2, "Child book should have copied permissions");
+        $this->assertDatabaseHas('entity_permissions', ['restrictable_id' => $child->id, 'action' => 'view', 'role_id' => $editorRole->id]);
+        $this->assertDatabaseHas('entity_permissions', ['restrictable_id' => $child->id, 'action' => 'update', 'role_id' => $editorRole->id]);
+    }
+
+    public function test_copy_shelf_permissions_command_using_all()
+    {
+        $shelf = Bookshelf::query()->first();
+        Bookshelf::query()->where('id', '!=', $shelf->id)->delete();
+        $child = $shelf->books()->first();
+        $editorRole = $this->getEditor()->roles()->first();
+        $this->assertFalse(boolval($child->restricted), "Child book should not be restricted by default");
+        $this->assertTrue($child->permissions()->count() === 0, "Child book should have no permissions by default");
+
+        $this->setEntityRestrictions($shelf, ['view', 'update'], [$editorRole]);
+        $this->artisan('bookstack:copy-shelf-permissions --all')
+            ->expectsQuestion('Permission settings for all shelves will be cascaded. Books assigned to multiple shelves will receive only the permissions of it\'s last processed shelf. Are you sure you want to proceed?', 'y');
+        $child = $shelf->books()->first();
+
+        $this->assertTrue(boolval($child->restricted), "Child book should now be restricted");
+        $this->assertTrue($child->permissions()->count() === 2, "Child book should have copied permissions");
+        $this->assertDatabaseHas('entity_permissions', ['restrictable_id' => $child->id, 'action' => 'view', 'role_id' => $editorRole->id]);
+        $this->assertDatabaseHas('entity_permissions', ['restrictable_id' => $child->id, 'action' => 'update', 'role_id' => $editorRole->id]);
+    }
 }