Merge branch 'master' into release
This commit is contained in:
		
						commit
						4fc75beed4
					
				| 
						 | 
				
			
			@ -31,11 +31,7 @@ abstract class Entity extends Model
 | 
			
		|||
 | 
			
		||||
        if ($matches) return true;
 | 
			
		||||
 | 
			
		||||
        if ($entity->isA('chapter') && $this->isA('book')) {
 | 
			
		||||
            return $entity->book_id === $this->id;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($entity->isA('page') && $this->isA('book')) {
 | 
			
		||||
        if (($entity->isA('chapter') || $entity->isA('page')) && $this->isA('book')) {
 | 
			
		||||
            return $entity->book_id === $this->id;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -64,15 +60,6 @@ abstract class Entity extends Model
 | 
			
		|||
        return $this->morphMany('BookStack\View', 'viewable');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get just the views for the current user.
 | 
			
		||||
     * @return mixed
 | 
			
		||||
     */
 | 
			
		||||
    public function userViews()
 | 
			
		||||
    {
 | 
			
		||||
        return $this->views()->where('user_id', '=', auth()->user()->id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Allows checking of the exact class, Used to check entity type.
 | 
			
		||||
     * Cleaner method for is_a.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -42,6 +42,15 @@ abstract class Controller extends BaseController
 | 
			
		|||
        $this->signedIn = auth()->check();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Stops the application and shows a permission error if
 | 
			
		||||
     * the application is in demo mode.
 | 
			
		||||
     */
 | 
			
		||||
    protected function preventAccessForDemoUsers()
 | 
			
		||||
    {
 | 
			
		||||
        if (env('APP_ENV', 'production') === 'demo') $this->showPermissionError();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds the page title into the view.
 | 
			
		||||
     * @param $title
 | 
			
		||||
| 
						 | 
				
			
			@ -51,6 +60,18 @@ abstract class Controller extends BaseController
 | 
			
		|||
        view()->share('pageTitle', $title);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * On a permission error redirect to home and display
 | 
			
		||||
     * the error as a notification.
 | 
			
		||||
     */
 | 
			
		||||
    protected function showPermissionError()
 | 
			
		||||
    {
 | 
			
		||||
        Session::flash('error', trans('errors.permission'));
 | 
			
		||||
        throw new HttpResponseException(
 | 
			
		||||
            redirect('/')
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks for a permission.
 | 
			
		||||
     *
 | 
			
		||||
| 
						 | 
				
			
			@ -60,15 +81,18 @@ abstract class Controller extends BaseController
 | 
			
		|||
    protected function checkPermission($permissionName)
 | 
			
		||||
    {
 | 
			
		||||
        if (!$this->currentUser || !$this->currentUser->can($permissionName)) {
 | 
			
		||||
            Session::flash('error', trans('errors.permission'));
 | 
			
		||||
            throw new HttpResponseException(
 | 
			
		||||
                redirect('/')
 | 
			
		||||
            );
 | 
			
		||||
            $this->showPermissionError();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if a user has a permission or bypass if the callback is true.
 | 
			
		||||
     * @param $permissionName
 | 
			
		||||
     * @param $callback
 | 
			
		||||
     * @return bool
 | 
			
		||||
     */
 | 
			
		||||
    protected function checkPermissionOr($permissionName, $callback)
 | 
			
		||||
    {
 | 
			
		||||
        $callbackResult = $callback();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -62,9 +62,9 @@ class SearchController extends Controller
 | 
			
		|||
            return redirect()->back();
 | 
			
		||||
        }
 | 
			
		||||
        $searchTerm = $request->get('term');
 | 
			
		||||
        $whereTerm = [['book_id', '=', $bookId]];
 | 
			
		||||
        $pages = $this->pageRepo->getBySearch($searchTerm, $whereTerm);
 | 
			
		||||
        $chapters = $this->chapterRepo->getBySearch($searchTerm, $whereTerm);
 | 
			
		||||
        $searchWhereTerms = [['book_id', '=', $bookId]];
 | 
			
		||||
        $pages = $this->pageRepo->getBySearch($searchTerm, $searchWhereTerms);
 | 
			
		||||
        $chapters = $this->chapterRepo->getBySearch($searchTerm, $searchWhereTerms);
 | 
			
		||||
        return view('search/book', ['pages' => $pages, 'chapters' => $chapters, 'searchTerm' => $searchTerm]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,13 +31,16 @@ class SettingController extends Controller
 | 
			
		|||
     */
 | 
			
		||||
    public function update(Request $request)
 | 
			
		||||
    {
 | 
			
		||||
        $this->preventAccessForDemoUsers();
 | 
			
		||||
        $this->checkPermission('settings-update');
 | 
			
		||||
 | 
			
		||||
        // Cycles through posted settings and update them
 | 
			
		||||
        foreach($request->all() as $name => $value) {
 | 
			
		||||
            if(strpos($name, 'setting-') !== 0) continue;
 | 
			
		||||
            $key = str_replace('setting-', '', trim($name));
 | 
			
		||||
            Setting::put($key, $value);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        session()->flash('success', 'Settings Saved');
 | 
			
		||||
        return redirect('/settings');
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -108,15 +108,19 @@ class UserController extends Controller
 | 
			
		|||
     */
 | 
			
		||||
    public function update(Request $request, $id)
 | 
			
		||||
    {
 | 
			
		||||
        $this->preventAccessForDemoUsers();
 | 
			
		||||
        $this->checkPermissionOr('user-update', function () use ($id) {
 | 
			
		||||
            return $this->currentUser->id == $id;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        $this->validate($request, [
 | 
			
		||||
            'name'             => 'required',
 | 
			
		||||
            'email'            => 'required|email|unique:users,email,' . $id,
 | 
			
		||||
            'password'         => 'min:5',
 | 
			
		||||
            'password-confirm' => 'same:password',
 | 
			
		||||
            'password'         => 'min:5|required_with:password_confirm',
 | 
			
		||||
            'password-confirm' => 'same:password|required_with:password',
 | 
			
		||||
            'role'             => 'exists:roles,id'
 | 
			
		||||
        ], [
 | 
			
		||||
            'password-confirm.required_with' => 'Password confirmation required'
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        $user = $this->user->findOrFail($id);
 | 
			
		||||
| 
						 | 
				
			
			@ -130,6 +134,7 @@ class UserController extends Controller
 | 
			
		|||
            $password = $request->get('password');
 | 
			
		||||
            $user->password = bcrypt($password);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $user->save();
 | 
			
		||||
        return redirect('/users');
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -144,6 +149,7 @@ class UserController extends Controller
 | 
			
		|||
        $this->checkPermissionOr('user-delete', function () use ($id) {
 | 
			
		||||
            return $this->currentUser->id == $id;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        $user = $this->user->findOrFail($id);
 | 
			
		||||
        $this->setPageTitle('Delete User ' . $user->name);
 | 
			
		||||
        return view('users/delete', ['user' => $user]);
 | 
			
		||||
| 
						 | 
				
			
			@ -156,6 +162,7 @@ class UserController extends Controller
 | 
			
		|||
     */
 | 
			
		||||
    public function destroy($id)
 | 
			
		||||
    {
 | 
			
		||||
        $this->preventAccessForDemoUsers();
 | 
			
		||||
        $this->checkPermissionOr('user-delete', function () use ($id) {
 | 
			
		||||
            return $this->currentUser->id == $id;
 | 
			
		||||
        });
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										12
									
								
								app/Role.php
								
								
								
								
							
							
						
						
									
										12
									
								
								app/Role.php
								
								
								
								
							| 
						 | 
				
			
			@ -43,6 +43,16 @@ class Role extends Model
 | 
			
		|||
     */
 | 
			
		||||
    public static function getDefault()
 | 
			
		||||
    {
 | 
			
		||||
        return static::where('name', '=', static::$default)->first();
 | 
			
		||||
        return static::getRole(static::$default);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the role object for the specified role.
 | 
			
		||||
     * @param $roleName
 | 
			
		||||
     * @return mixed
 | 
			
		||||
     */
 | 
			
		||||
    public static function getRole($roleName)
 | 
			
		||||
    {
 | 
			
		||||
        return static::where('name', '=', $roleName)->first();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -107,7 +107,7 @@ class ActivityService
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Filters out similar acitivity.
 | 
			
		||||
     * Filters out similar activity.
 | 
			
		||||
     * @param Activity[] $activity
 | 
			
		||||
     * @return array
 | 
			
		||||
     */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,7 +12,7 @@ class DummyContentSeeder extends Seeder
 | 
			
		|||
    public function run()
 | 
			
		||||
    {
 | 
			
		||||
        $user = factory(BookStack\User::class, 1)->create();
 | 
			
		||||
        $role = \BookStack\Role::where('name', '=', 'admin')->first();
 | 
			
		||||
        $role = \BookStack\Role::getDefault();
 | 
			
		||||
        $user->attachRole($role);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,6 +26,6 @@
 | 
			
		|||
        <env name="QUEUE_DRIVER" value="sync"/>
 | 
			
		||||
        <env name="DB_CONNECTION" value="mysql_testing"/>
 | 
			
		||||
        <env name="MAIL_PRETEND" value="true"/>
 | 
			
		||||
        <env name="DISABLE_EXTERNAL_SERVICES" value="true"/>
 | 
			
		||||
        <env name="DISABLE_EXTERNAL_SERVICES" value="false"/>
 | 
			
		||||
    </php>
 | 
			
		||||
</phpunit>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,2 @@
 | 
			
		|||
*
 | 
			
		||||
!.gitignore
 | 
			
		||||
| 
						 | 
				
			
			@ -82,7 +82,7 @@ BookStack is provided under the MIT License.
 | 
			
		|||
These are the great projects used to help build BookStack:
 | 
			
		||||
 | 
			
		||||
* [Laravel](http://laravel.com/)
 | 
			
		||||
* [VueJS](http://vuejs.org/)
 | 
			
		||||
* [AngularJS](https://angularjs.org/)
 | 
			
		||||
* [jQuery](https://jquery.com/)
 | 
			
		||||
* [TinyMCE](https://www.tinymce.com/)
 | 
			
		||||
* [highlight.js](https://highlightjs.org/)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -127,7 +127,7 @@ module.exports = function (ngApp) {
 | 
			
		|||
        }]);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ngApp.controller('BookShowController', ['$scope', '$http', '$attrs', function ($scope, $http, $attrs) {
 | 
			
		||||
    ngApp.controller('BookShowController', ['$scope', '$http', '$attrs', '$sce', function ($scope, $http, $attrs, $sce) {
 | 
			
		||||
        $scope.searching = false;
 | 
			
		||||
        $scope.searchTerm = '';
 | 
			
		||||
        $scope.searchResults = '';
 | 
			
		||||
| 
						 | 
				
			
			@ -141,7 +141,7 @@ module.exports = function (ngApp) {
 | 
			
		|||
            var searchUrl = '/search/book/' + $attrs.bookId;
 | 
			
		||||
            searchUrl += '?term=' + encodeURIComponent(term);
 | 
			
		||||
            $http.get(searchUrl).then((response) => {
 | 
			
		||||
                $scope.searchResults = response.data;
 | 
			
		||||
                $scope.searchResults = $sce.trustAsHtml(response.data);
 | 
			
		||||
            });
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -43,14 +43,14 @@
 | 
			
		|||
                    <div class="float right">
 | 
			
		||||
                        <div class="links text-center">
 | 
			
		||||
                            <a href="/books"><i class="zmdi zmdi-book"></i>Books</a>
 | 
			
		||||
                            @if($currentUser->can('settings-update'))
 | 
			
		||||
                            @if(isset($currentUser) && $currentUser->can('settings-update'))
 | 
			
		||||
                                <a href="/settings"><i class="zmdi zmdi-settings"></i>Settings</a>
 | 
			
		||||
                            @endif
 | 
			
		||||
                            @if(!$signedIn)
 | 
			
		||||
                            @if(!isset($signedIn) || !$signedIn)
 | 
			
		||||
                                <a href="/login"><i class="zmdi zmdi-sign-in"></i>Sign In</a>
 | 
			
		||||
                            @endif
 | 
			
		||||
                        </div>
 | 
			
		||||
                        @if($signedIn)
 | 
			
		||||
                        @if(isset($signedIn) && $signedIn)
 | 
			
		||||
                            <div class="dropdown-container" dropdown>
 | 
			
		||||
                                <span class="user-name" dropdown-toggle>
 | 
			
		||||
                                    <img class="avatar" src="{{$currentUser->getAvatar(30)}}" alt="{{ $currentUser->name }}">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,8 +4,9 @@
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
<div class="container">
 | 
			
		||||
    <h1>Page Not Found</h1>
 | 
			
		||||
    <p>The page you were looking for could not be found.</p>
 | 
			
		||||
    <h1 class="text-muted">Page Not Found</h1>
 | 
			
		||||
    <p>Sorry, The page you were looking for could not be found.</p>
 | 
			
		||||
    <a href="/" class="button">Return To Home</a>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
@stop
 | 
			
		||||
| 
						 | 
				
			
			@ -9,7 +9,7 @@
 | 
			
		|||
                <div class="col-md-6"></div>
 | 
			
		||||
                <div class="col-md-6 faded">
 | 
			
		||||
                    <div class="action-buttons">
 | 
			
		||||
                        <a href="/users/{{$user->id}}/delete" class="text-neg text-button"><i class="zmdi zmdi-delete"></i>Delete user</a>
 | 
			
		||||
                        <a href="/users/{{$user->id}}/delete" class="text-neg text-button"><i class="zmdi zmdi-delete"></i>Delete User</a>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -102,10 +102,10 @@ class AuthTest extends TestCase
 | 
			
		|||
            ->seeInDatabase('users', ['name' => $user->name, 'email' => $user->email, 'email_confirmed' => true]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testUserControl()
 | 
			
		||||
    public function testUserCreation()
 | 
			
		||||
    {
 | 
			
		||||
        $user = factory(\BookStack\User::class)->make();
 | 
			
		||||
        // Test creation
 | 
			
		||||
 | 
			
		||||
        $this->asAdmin()
 | 
			
		||||
            ->visit('/users')
 | 
			
		||||
            ->click('Add new user')
 | 
			
		||||
| 
						 | 
				
			
			@ -118,9 +118,12 @@ class AuthTest extends TestCase
 | 
			
		|||
            ->seeInDatabase('users', $user->toArray())
 | 
			
		||||
            ->seePageIs('/users')
 | 
			
		||||
            ->see($user->name);
 | 
			
		||||
        $user = $user->where('email', '=', $user->email)->first();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        // Test editing
 | 
			
		||||
    public function testUserUpdating()
 | 
			
		||||
    {
 | 
			
		||||
        $user = \BookStack\User::all()->last();
 | 
			
		||||
        $password = $user->password;
 | 
			
		||||
        $this->asAdmin()
 | 
			
		||||
            ->visit('/users')
 | 
			
		||||
            ->click($user->name)
 | 
			
		||||
| 
						 | 
				
			
			@ -129,20 +132,58 @@ class AuthTest extends TestCase
 | 
			
		|||
            ->type('Barry Scott', '#name')
 | 
			
		||||
            ->press('Save')
 | 
			
		||||
            ->seePageIs('/users')
 | 
			
		||||
            ->seeInDatabase('users', ['id' => $user->id, 'name' => 'Barry Scott'])
 | 
			
		||||
            ->seeInDatabase('users', ['id' => $user->id, 'name' => 'Barry Scott', 'password' => $password])
 | 
			
		||||
            ->notSeeInDatabase('users', ['name' => $user->name]);
 | 
			
		||||
        $user = $user->find($user->id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testUserPasswordUpdate()
 | 
			
		||||
    {
 | 
			
		||||
        $user = \BookStack\User::all()->last();
 | 
			
		||||
        $userProfilePage = '/users/' . $user->id;
 | 
			
		||||
        $this->asAdmin()
 | 
			
		||||
            ->visit($userProfilePage)
 | 
			
		||||
            ->type('newpassword', '#password')
 | 
			
		||||
            ->press('Save')
 | 
			
		||||
            ->seePageIs($userProfilePage)
 | 
			
		||||
            ->see('Password confirmation required')
 | 
			
		||||
 | 
			
		||||
            ->type('newpassword', '#password')
 | 
			
		||||
            ->type('newpassword', '#password-confirm')
 | 
			
		||||
            ->press('Save')
 | 
			
		||||
            ->seePageIs('/users');
 | 
			
		||||
 | 
			
		||||
            $userPassword = \BookStack\User::find($user->id)->password;
 | 
			
		||||
            $this->assertTrue(Hash::check('newpassword', $userPassword));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testUserDeletion()
 | 
			
		||||
    {
 | 
			
		||||
        $userDetails = factory(\BookStack\User::class)->make();
 | 
			
		||||
        $user = $this->getNewUser($userDetails->toArray());
 | 
			
		||||
 | 
			
		||||
        // Test Deletion
 | 
			
		||||
        $this->asAdmin()
 | 
			
		||||
            ->visit('/users/' . $user->id)
 | 
			
		||||
            ->click('Delete user')
 | 
			
		||||
            ->click('Delete User')
 | 
			
		||||
            ->see($user->name)
 | 
			
		||||
            ->press('Confirm')
 | 
			
		||||
            ->seePageIs('/users')
 | 
			
		||||
            ->notSeeInDatabase('users', ['name' => $user->name]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testUserCannotBeDeletedIfLastAdmin()
 | 
			
		||||
    {
 | 
			
		||||
        $adminRole = \BookStack\Role::getRole('admin');
 | 
			
		||||
        // Ensure we currently only have 1 admin user
 | 
			
		||||
        $this->assertEquals(1, $adminRole->users()->count());
 | 
			
		||||
        $user = $adminRole->users->first();
 | 
			
		||||
 | 
			
		||||
        $this->asAdmin()->visit('/users/' . $user->id)
 | 
			
		||||
            ->click('Delete User')
 | 
			
		||||
            ->press('Confirm')
 | 
			
		||||
            ->seePageIs('/users/' . $user->id)
 | 
			
		||||
            ->see('You cannot delete the only admin');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testLogout()
 | 
			
		||||
    {
 | 
			
		||||
        $this->asAdmin()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -180,6 +180,37 @@ class EntityTest extends TestCase
 | 
			
		|||
            ->seeStatusCode(200);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testEmptySearchRedirectsBack()
 | 
			
		||||
    {
 | 
			
		||||
        $this->asAdmin()
 | 
			
		||||
            ->visit('/')
 | 
			
		||||
            ->visit('/search/all')
 | 
			
		||||
            ->seePageIs('/');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testBookSearch()
 | 
			
		||||
    {
 | 
			
		||||
        $book = \BookStack\Book::all()->first();
 | 
			
		||||
        $page = $book->pages->last();
 | 
			
		||||
        $chapter = $book->chapters->last();
 | 
			
		||||
 | 
			
		||||
        $this->asAdmin()
 | 
			
		||||
            ->visit('/search/book/' . $book->id . '?term=' . urlencode($page->name))
 | 
			
		||||
            ->see($page->name)
 | 
			
		||||
 | 
			
		||||
            ->visit('/search/book/' . $book->id  . '?term=' . urlencode($chapter->name))
 | 
			
		||||
            ->see($chapter->name);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testEmptyBookSearchRedirectsBack()
 | 
			
		||||
    {
 | 
			
		||||
        $book = \BookStack\Book::all()->first();
 | 
			
		||||
        $this->asAdmin()
 | 
			
		||||
            ->visit('/books')
 | 
			
		||||
            ->visit('/search/book/' . $book->id . '?term=')
 | 
			
		||||
            ->seePageIs('/books');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public function testEntitiesViewableAfterCreatorDeletion()
 | 
			
		||||
    {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue