diff --git a/app/Entity.php b/app/Entity.php index 705444959..42323628a 100644 --- a/app/Entity.php +++ b/app/Entity.php @@ -98,7 +98,7 @@ abstract class Entity extends Model * @param string[] array $wheres * @return mixed */ - public static function fullTextSearch($fieldsToSearch, $terms, $wheres = []) + public static function fullTextSearchQuery($fieldsToSearch, $terms, $wheres = []) { $termString = ''; foreach ($terms as $term) { @@ -107,7 +107,7 @@ abstract class Entity extends Model $fields = implode(',', $fieldsToSearch); $termStringEscaped = \DB::connection()->getPdo()->quote($termString); $search = static::addSelect(\DB::raw('*, MATCH(name) AGAINST('.$termStringEscaped.' IN BOOLEAN MODE) AS title_relevance')); - $search = $search->whereRaw('MATCH(' . $fields . ') AGAINST(? IN BOOLEAN MODE)', [$termStringEscaped]); + $search = $search->whereRaw('MATCH(' . $fields . ') AGAINST(? IN BOOLEAN MODE)', [$termString]); // Add additional where terms foreach ($wheres as $whereTerm) { @@ -115,10 +115,13 @@ abstract class Entity extends Model } // Load in relations - if (!static::isA('book')) $search = $search->with('book'); - if (static::isA('page')) $search = $search->with('chapter'); + if (static::isA('page')) { + $search = $search->with('book', 'chapter', 'createdBy', 'updatedBy'); + } else if (static::isA('chapter')) { + $search = $search->with('book'); + } - return $search->orderBy('title_relevance', 'desc')->get(); + return $search->orderBy('title_relevance', 'desc'); } /** diff --git a/app/Http/Controllers/BookController.php b/app/Http/Controllers/BookController.php index a4365d605..d577a85b1 100644 --- a/app/Http/Controllers/BookController.php +++ b/app/Http/Controllers/BookController.php @@ -157,7 +157,7 @@ class BookController extends Controller $this->checkPermission('book-update'); $book = $this->bookRepo->getBySlug($bookSlug); $bookChildren = $this->bookRepo->getChildren($book); - $books = $this->bookRepo->getAll(); + $books = $this->bookRepo->getAll(false); $this->setPageTitle('Sort Book ' . $book->getShortName()); return view('books/sort', ['book' => $book, 'current' => $book, 'books' => $books, 'bookChildren' => $bookChildren]); } diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 8fd1c1009..e20c89e06 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -3,25 +3,21 @@ namespace BookStack\Http\Controllers; use Activity; -use Illuminate\Http\Request; - +use BookStack\Repos\EntityRepo; use BookStack\Http\Requests; -use BookStack\Repos\BookRepo; use Views; class HomeController extends Controller { - - protected $activityService; - protected $bookRepo; + protected $entityRepo; /** * HomeController constructor. - * @param BookRepo $bookRepo + * @param EntityRepo $entityRepo */ - public function __construct(BookRepo $bookRepo) + public function __construct(EntityRepo $entityRepo) { - $this->bookRepo = $bookRepo; + $this->entityRepo = $entityRepo; parent::__construct(); } @@ -33,9 +29,16 @@ class HomeController extends Controller */ public function index() { - $activity = Activity::latest(); - $recents = $this->signedIn ? Views::getUserRecentlyViewed(10, 0) : $this->bookRepo->getLatest(10); - return view('home', ['activity' => $activity, 'recents' => $recents]); + $activity = Activity::latest(10); + $recents = $this->signedIn ? Views::getUserRecentlyViewed(12, 0) : $this->entityRepo->getRecentlyCreatedBooks(10); + $recentlyCreatedPages = $this->entityRepo->getRecentlyCreatedPages(5); + $recentlyUpdatedPages = $this->entityRepo->getRecentlyUpdatedPages(5); + return view('home', [ + 'activity' => $activity, + 'recents' => $recents, + 'recentlyCreatedPages' => $recentlyCreatedPages, + 'recentlyUpdatedPages' => $recentlyUpdatedPages + ]); } } diff --git a/app/Http/Controllers/PageController.php b/app/Http/Controllers/PageController.php index b4ab9682b..e78ae13e4 100644 --- a/app/Http/Controllers/PageController.php +++ b/app/Http/Controllers/PageController.php @@ -11,6 +11,7 @@ use BookStack\Http\Requests; use BookStack\Repos\BookRepo; use BookStack\Repos\ChapterRepo; use BookStack\Repos\PageRepo; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Views; class PageController extends Controller @@ -81,6 +82,8 @@ class PageController extends Controller /** * Display the specified page. + * If the page is not found via the slug the + * revisions are searched for a match. * * @param $bookSlug * @param $pageSlug @@ -89,7 +92,15 @@ class PageController extends Controller public function show($bookSlug, $pageSlug) { $book = $this->bookRepo->getBySlug($bookSlug); - $page = $this->pageRepo->getBySlug($pageSlug, $book->id); + + try { + $page = $this->pageRepo->getBySlug($pageSlug, $book->id); + } catch (NotFoundHttpException $e) { + $page = $this->pageRepo->findPageUsingOldSlug($pageSlug, $bookSlug); + if ($page === null) abort(404); + return redirect($page->getUrl()); + } + $sidebarTree = $this->bookRepo->getChildren($book); Views::add($page); $this->setPageTitle($page->getShortName()); @@ -278,4 +289,30 @@ class PageController extends Controller ]); } + /** + * Show a listing of recently created pages + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function showRecentlyCreated() + { + $pages = $this->pageRepo->getRecentlyCreatedPaginated(20); + return view('pages/detailed-listing', [ + 'title' => 'Recently Created Pages', + 'pages' => $pages + ]); + } + + /** + * Show a listing of recently created pages + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function showRecentlyUpdated() + { + $pages = $this->pageRepo->getRecentlyUpdatedPaginated(20); + return view('pages/detailed-listing', [ + 'title' => 'Recently Updated Pages', + 'pages' => $pages + ]); + } + } diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index 035de9fe6..e198dc767 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -42,11 +42,77 @@ class SearchController extends Controller return redirect()->back(); } $searchTerm = $request->get('term'); - $pages = $this->pageRepo->getBySearch($searchTerm); - $books = $this->bookRepo->getBySearch($searchTerm); - $chapters = $this->chapterRepo->getBySearch($searchTerm); + $paginationAppends = $request->only('term'); + $pages = $this->pageRepo->getBySearch($searchTerm, [], 20, $paginationAppends); + $books = $this->bookRepo->getBySearch($searchTerm, 10, $paginationAppends); + $chapters = $this->chapterRepo->getBySearch($searchTerm, [], 10, $paginationAppends); $this->setPageTitle('Search For ' . $searchTerm); - return view('search/all', ['pages' => $pages, 'books' => $books, 'chapters' => $chapters, 'searchTerm' => $searchTerm]); + return view('search/all', [ + 'pages' => $pages, + 'books' => $books, + 'chapters' => $chapters, + 'searchTerm' => $searchTerm + ]); + } + + /** + * Search only the pages in the system. + * @param Request $request + * @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View + */ + public function searchPages(Request $request) + { + if (!$request->has('term')) return redirect()->back(); + + $searchTerm = $request->get('term'); + $paginationAppends = $request->only('term'); + $pages = $this->pageRepo->getBySearch($searchTerm, [], 20, $paginationAppends); + $this->setPageTitle('Page Search For ' . $searchTerm); + return view('search/entity-search-list', [ + 'entities' => $pages, + 'title' => 'Page Search Results', + 'searchTerm' => $searchTerm + ]); + } + + /** + * Search only the chapters in the system. + * @param Request $request + * @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View + */ + public function searchChapters(Request $request) + { + if (!$request->has('term')) return redirect()->back(); + + $searchTerm = $request->get('term'); + $paginationAppends = $request->only('term'); + $chapters = $this->chapterRepo->getBySearch($searchTerm, [], 20, $paginationAppends); + $this->setPageTitle('Chapter Search For ' . $searchTerm); + return view('search/entity-search-list', [ + 'entities' => $chapters, + 'title' => 'Chapter Search Results', + 'searchTerm' => $searchTerm + ]); + } + + /** + * Search only the books in the system. + * @param Request $request + * @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View + */ + public function searchBooks(Request $request) + { + if (!$request->has('term')) return redirect()->back(); + + $searchTerm = $request->get('term'); + $paginationAppends = $request->only('term'); + $books = $this->bookRepo->getBySearch($searchTerm, 20, $paginationAppends); + $this->setPageTitle('Book Search For ' . $searchTerm); + return view('search/entity-search-list', [ + 'entities' => $books, + 'title' => 'Book Search Results', + 'searchTerm' => $searchTerm + ]); } /** diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index bf25eafb2..55ca5be19 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -2,6 +2,7 @@ namespace BookStack\Http\Controllers; +use BookStack\Activity; use Illuminate\Http\Request; use Illuminate\Http\Response; @@ -92,10 +93,9 @@ class UserController extends Controller $user->save(); } - return redirect('/users'); + return redirect('/settings/users'); } - /** * Show the form for editing the specified user. * @param int $id @@ -159,7 +159,7 @@ class UserController extends Controller } $user->save(); - return redirect('/users'); + return redirect('/settings/users'); } /** @@ -197,6 +197,25 @@ class UserController extends Controller } $this->userRepo->destroy($user); - return redirect('/users'); + return redirect('/settings/users'); + } + + /** + * Show the user profile page + * @param $id + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function showProfilePage($id) + { + $user = $this->userRepo->getById($id); + $userActivity = $this->userRepo->getActivity($user); + $recentlyCreated = $this->userRepo->getRecentlyCreated($user, 5, 0); + $assetCounts = $this->userRepo->getAssetCounts($user); + return view('users/profile', [ + 'user' => $user, + 'activity' => $userActivity, + 'recentlyCreated' => $recentlyCreated, + 'assetCounts' => $assetCounts + ]); } } diff --git a/app/Http/routes.php b/app/Http/routes.php index 6ac322122..36cf2a19f 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -3,6 +3,11 @@ // Authenticated routes... Route::group(['middleware' => 'auth'], function () { + Route::group(['prefix' => 'pages'], function() { + Route::get('/recently-created', 'PageController@showRecentlyCreated'); + Route::get('/recently-updated', 'PageController@showRecentlyUpdated'); + }); + Route::group(['prefix' => 'books'], function () { // Books @@ -47,14 +52,8 @@ Route::group(['middleware' => 'auth'], function () { }); - // Users - Route::get('/users', 'UserController@index'); - Route::get('/users/create', 'UserController@create'); - Route::get('/users/{id}/delete', 'UserController@delete'); - Route::post('/users/create', 'UserController@store'); - Route::get('/users/{id}', 'UserController@edit'); - Route::put('/users/{id}', 'UserController@update'); - Route::delete('/users/{id}', 'UserController@destroy'); + // User Profile routes + Route::get('/user/{userId}', 'UserController@showProfilePage'); // Image routes Route::group(['prefix' => 'images'], function() { @@ -75,6 +74,9 @@ Route::group(['middleware' => 'auth'], function () { // Search Route::get('/search/all', 'SearchController@searchAll'); + Route::get('/search/pages', 'SearchController@searchPages'); + Route::get('/search/books', 'SearchController@searchBooks'); + Route::get('/search/chapters', 'SearchController@searchChapters'); Route::get('/search/book/{bookId}', 'SearchController@searchBook'); // Other Pages @@ -82,8 +84,18 @@ Route::group(['middleware' => 'auth'], function () { Route::get('/home', 'HomeController@index'); // Settings - Route::get('/settings', 'SettingController@index'); - Route::post('/settings', 'SettingController@update'); + Route::group(['prefix' => 'settings'], function() { + Route::get('/', 'SettingController@index'); + Route::post('/', 'SettingController@update'); + // Users + Route::get('/users', 'UserController@index'); + Route::get('/users/create', 'UserController@create'); + Route::get('/users/{id}/delete', 'UserController@delete'); + Route::post('/users/create', 'UserController@store'); + Route::get('/users/{id}', 'UserController@edit'); + Route::put('/users/{id}', 'UserController@update'); + Route::delete('/users/{id}', 'UserController@destroy'); + }); }); diff --git a/app/Page.php b/app/Page.php index bd5f3bafe..53724ec20 100644 --- a/app/Page.php +++ b/app/Page.php @@ -45,7 +45,8 @@ class Page extends Entity public function getExcerpt($length = 100) { - return strlen($this->text) > $length ? substr($this->text, 0, $length-3) . '...' : $this->text; + $text = strlen($this->text) > $length ? substr($this->text, 0, $length-3) . '...' : $this->text; + return mb_convert_encoding($text, 'UTF-8'); } } diff --git a/app/Repos/BookRepo.php b/app/Repos/BookRepo.php index a57050ce2..d8a24c099 100644 --- a/app/Repos/BookRepo.php +++ b/app/Repos/BookRepo.php @@ -14,8 +14,8 @@ class BookRepo /** * BookRepo constructor. - * @param Book $book - * @param PageRepo $pageRepo + * @param Book $book + * @param PageRepo $pageRepo * @param ChapterRepo $chapterRepo */ public function __construct(Book $book, PageRepo $pageRepo, ChapterRepo $chapterRepo) @@ -42,7 +42,9 @@ class BookRepo */ public function getAll($count = 10) { - return $this->book->orderBy('name', 'asc')->take($count)->get(); + $bookQuery = $this->book->orderBy('name', 'asc'); + if (!$count) return $bookQuery->get(); + return $bookQuery->take($count)->get(); } /** @@ -159,7 +161,7 @@ class BookRepo } /** - * @param string $slug + * @param string $slug * @param bool|false $currentId * @return bool */ @@ -175,7 +177,7 @@ class BookRepo /** * Provides a suitable slug for the given book name. * Ensures the returned slug is unique in the system. - * @param string $name + * @param string $name * @param bool|false $currentId * @return string */ @@ -218,12 +220,15 @@ class BookRepo /** * Get books by search term. * @param $term + * @param int $count + * @param array $paginationAppends * @return mixed */ - public function getBySearch($term) + public function getBySearch($term, $count = 20, $paginationAppends = []) { $terms = explode(' ', $term); - $books = $this->book->fullTextSearch(['name', 'description'], $terms); + $books = $this->book->fullTextSearchQuery(['name', 'description'], $terms) + ->paginate($count)->appends($paginationAppends); $words = join('|', explode(' ', preg_quote(trim($term), '/'))); foreach ($books as $book) { //highlight diff --git a/app/Repos/ChapterRepo.php b/app/Repos/ChapterRepo.php index 3824e6982..bba6cbe7a 100644 --- a/app/Repos/ChapterRepo.php +++ b/app/Repos/ChapterRepo.php @@ -125,12 +125,15 @@ class ChapterRepo * Get chapters by the given search term. * @param $term * @param array $whereTerms + * @param int $count + * @param array $paginationAppends * @return mixed */ - public function getBySearch($term, $whereTerms = []) + public function getBySearch($term, $whereTerms = [], $count = 20, $paginationAppends = []) { $terms = explode(' ', $term); - $chapters = $this->chapter->fullTextSearch(['name', 'description'], $terms, $whereTerms); + $chapters = $this->chapter->fullTextSearchQuery(['name', 'description'], $terms, $whereTerms) + ->paginate($count)->appends($paginationAppends); $words = join('|', explode(' ', preg_quote(trim($term), '/'))); foreach ($chapters as $chapter) { //highlight diff --git a/app/Repos/EntityRepo.php b/app/Repos/EntityRepo.php new file mode 100644 index 000000000..28942d94a --- /dev/null +++ b/app/Repos/EntityRepo.php @@ -0,0 +1,71 @@ +book = $book; + $this->chapter = $chapter; + $this->page = $page; + } + + /** + * Get the latest books added to the system. + * @param $count + * @param $page + */ + public function getRecentlyCreatedBooks($count = 20, $page = 0) + { + return $this->book->orderBy('created_at', 'desc')->skip($page*$count)->take($count)->get(); + } + + /** + * Get the most recently updated books. + * @param $count + * @param int $page + * @return mixed + */ + public function getRecentlyUpdatedBooks($count = 20, $page = 0) + { + return $this->book->orderBy('updated_at', 'desc')->skip($page*$count)->take($count)->get(); + } + + /** + * Get the latest pages added to the system. + * @param $count + * @param $page + */ + public function getRecentlyCreatedPages($count = 20, $page = 0) + { + return $this->page->orderBy('created_at', 'desc')->skip($page*$count)->take($count)->get(); + } + + /** + * Get the most recently updated pages. + * @param $count + * @param int $page + * @return mixed + */ + public function getRecentlyUpdatedPages($count = 20, $page = 0) + { + return $this->page->orderBy('updated_at', 'desc')->skip($page*$count)->take($count)->get(); + } + + +} \ No newline at end of file diff --git a/app/Repos/PageRepo.php b/app/Repos/PageRepo.php index 05052432e..f028a1fcc 100644 --- a/app/Repos/PageRepo.php +++ b/app/Repos/PageRepo.php @@ -10,6 +10,7 @@ use Illuminate\Support\Facades\Log; use Illuminate\Support\Str; use BookStack\Page; use BookStack\PageRevision; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; class PageRepo { @@ -65,11 +66,28 @@ class PageRepo public function getBySlug($slug, $bookId) { $page = $this->page->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first(); - if ($page === null) abort(404); + if ($page === null) throw new NotFoundHttpException('Page not found'); return $page; } /** + * Search through page revisions and retrieve + * the last page in the current book that + * has a slug equal to the one given. + * @param $pageSlug + * @param $bookSlug + * @return null | Page + */ + public function findPageUsingOldSlug($pageSlug, $bookSlug) + { + $revision = $this->pageRevision->where('slug', '=', $pageSlug) + ->where('book_slug', '=', $bookSlug)->orderBy('created_at', 'desc') + ->with('page')->first(); + return $revision !== null ? $revision->page : null; + } + + /** + * Get a new Page instance from the given input. * @param $input * @return Page */ @@ -125,21 +143,20 @@ class PageRepo if($htmlText == '') return $htmlText; libxml_use_internal_errors(true); $doc = new \DOMDocument(); - $doc->loadHTML($htmlText); + $doc->loadHTML(mb_convert_encoding($htmlText, 'HTML-ENTITIES', 'UTF-8')); $container = $doc->documentElement; $body = $container->childNodes->item(0); $childNodes = $body->childNodes; // Ensure no duplicate ids are used - $lastId = false; $idArray = []; foreach ($childNodes as $index => $childNode) { /** @var \DOMElement $childNode */ if (get_class($childNode) !== 'DOMElement') continue; - // Overwrite id if not a bookstack custom id + // Overwrite id if not a BookStack custom id if ($childNode->hasAttribute('id')) { $id = $childNode->getAttribute('id'); if (strpos($id, 'bkmrk') === 0 && array_search($id, $idArray) === false) { @@ -149,13 +166,18 @@ class PageRepo } // Create an unique id for the element - do { - $id = 'bkmrk-' . substr(uniqid(), -5); - } while ($id == $lastId); - $lastId = $id; + // Uses the content as a basis to ensure output is the same every time + // the same content is passed through. + $contentId = 'bkmrk-' . substr(strtolower(preg_replace('/\s+/', '-', trim($childNode->nodeValue))), 0, 20); + $newId = urlencode($contentId); + $loopIndex = 0; + while (in_array($newId, $idArray)) { + $newId = urlencode($contentId . '-' . $loopIndex); + $loopIndex++; + } - $childNode->setAttribute('id', $id); - $idArray[] = $id; + $childNode->setAttribute('id', $newId); + $idArray[] = $newId; } // Generate inner html as a string @@ -171,14 +193,17 @@ class PageRepo /** * Gets pages by a search term. * Highlights page content for showing in results. - * @param string $term + * @param string $term * @param array $whereTerms + * @param int $count + * @param array $paginationAppends * @return mixed */ - public function getBySearch($term, $whereTerms = []) + public function getBySearch($term, $whereTerms = [], $count = 20, $paginationAppends = []) { $terms = explode(' ', $term); - $pages = $this->page->fullTextSearch(['name', 'text'], $terms, $whereTerms); + $pages = $this->page->fullTextSearchQuery(['name', 'text'], $terms, $whereTerms) + ->paginate($count)->appends($paginationAppends); // Add highlights to page text. $words = join('|', explode(' ', preg_quote(trim($term), '/'))); @@ -238,9 +263,13 @@ class PageRepo $this->saveRevision($page); } + // Prevent slug being updated if no name change + if ($page->name !== $input['name']) { + $page->slug = $this->findSuitableSlug($input['name'], $book_id, $page->id); + } + // Update with new details $page->fill($input); - $page->slug = $this->findSuitableSlug($page->name, $book_id, $page->id); $page->html = $this->formatHtml($input['html']); $page->text = strip_tags($page->html); $page->updated_by = auth()->user()->id; @@ -276,6 +305,8 @@ class PageRepo { $revision = $this->pageRevision->fill($page->toArray()); $revision->page_id = $page->id; + $revision->slug = $page->slug; + $revision->book_slug = $page->book->slug; $revision->created_by = auth()->user()->id; $revision->created_at = $page->updated_at; $revision->save(); @@ -358,5 +389,22 @@ class PageRepo $page->delete(); } + /** + * Get the latest pages added to the system. + * @param $count + */ + public function getRecentlyCreatedPaginated($count = 20) + { + return $this->page->orderBy('created_at', 'desc')->paginate($count); + } -} \ No newline at end of file + /** + * Get the latest pages added to the system. + * @param $count + */ + public function getRecentlyUpdatedPaginated($count = 20) + { + return $this->page->orderBy('updated_at', 'desc')->paginate($count); + } + +} diff --git a/app/Repos/UserRepo.php b/app/Repos/UserRepo.php index d02ffe929..48541a51a 100644 --- a/app/Repos/UserRepo.php +++ b/app/Repos/UserRepo.php @@ -1,6 +1,5 @@ user = $user; $this->role = $role; + $this->entityRepo = $entityRepo; } /** @@ -112,4 +115,49 @@ class UserRepo $user->socialAccounts()->delete(); $user->delete(); } + + /** + * Get the latest activity for a user. + * @param User $user + * @param int $count + * @param int $page + * @return array + */ + public function getActivity(User $user, $count = 20, $page = 0) + { + return \Activity::userActivity($user, $count, $page); + } + + /** + * Get the recently created content for this given user. + * @param User $user + * @param int $count + * @return mixed + */ + public function getRecentlyCreated(User $user, $count = 20) + { + return [ + 'pages' => $this->entityRepo->page->where('created_by', '=', $user->id)->orderBy('created_at', 'desc') + ->take($count)->get(), + 'chapters' => $this->entityRepo->chapter->where('created_by', '=', $user->id)->orderBy('created_at', 'desc') + ->take($count)->get(), + 'books' => $this->entityRepo->book->where('created_by', '=', $user->id)->orderBy('created_at', 'desc') + ->take($count)->get() + ]; + } + + /** + * Get asset created counts for the give user. + * @param User $user + * @return array + */ + public function getAssetCounts(User $user) + { + return [ + 'pages' => $this->entityRepo->page->where('created_by', '=', $user->id)->count(), + 'chapters' => $this->entityRepo->chapter->where('created_by', '=', $user->id)->count(), + 'books' => $this->entityRepo->book->where('created_by', '=', $user->id)->count(), + ]; + } + } \ No newline at end of file diff --git a/app/Services/ActivityService.php b/app/Services/ActivityService.php index 2ef5f9cfe..a065ae01f 100644 --- a/app/Services/ActivityService.php +++ b/app/Services/ActivityService.php @@ -29,18 +29,19 @@ class ActivityService */ public function add(Entity $entity, $activityKey, $bookId = 0, $extra = false) { - $this->activity->user_id = $this->user->id; - $this->activity->book_id = $bookId; - $this->activity->key = strtolower($activityKey); + $activity = $this->activity->newInstance(); + $activity->user_id = $this->user->id; + $activity->book_id = $bookId; + $activity->key = strtolower($activityKey); if ($extra !== false) { - $this->activity->extra = $extra; + $activity->extra = $extra; } - $entity->activity()->save($this->activity); + $entity->activity()->save($activity); $this->setNotification($activityKey); } /** - * Adds a activity history with a message & without binding to a entitiy. + * Adds a activity history with a message & without binding to a entity. * @param $activityKey * @param int $bookId * @param bool|false $extra @@ -91,14 +92,14 @@ class ActivityService } /** - * Gets the latest activity for an entitiy, Filtering out similar + * Gets the latest activity for an entity, Filtering out similar * items to prevent a message activity list. * @param Entity $entity * @param int $count * @param int $page * @return array */ - function entityActivity($entity, $count = 20, $page = 0) + public function entityActivity($entity, $count = 20, $page = 0) { $activity = $entity->hasMany('BookStack\Activity')->orderBy('created_at', 'desc') ->skip($count * $page)->take($count)->get(); @@ -107,15 +108,30 @@ class ActivityService } /** - * Filters out similar activity. - * @param Activity[] $activity + * Get latest activity for a user, Filtering out similar + * items. + * @param $user + * @param int $count + * @param int $page * @return array */ - protected function filterSimilar($activity) + public function userActivity($user, $count = 20, $page = 0) + { + $activity = $this->activity->where('user_id', '=', $user->id) + ->orderBy('created_at', 'desc')->skip($count * $page)->take($count)->get(); + return $this->filterSimilar($activity); + } + + /** + * Filters out similar activity. + * @param Activity[] $activities + * @return array + */ + protected function filterSimilar($activities) { $newActivity = []; $previousItem = false; - foreach ($activity as $activityItem) { + foreach ($activities as $activityItem) { if ($previousItem === false) { $previousItem = $activityItem; $newActivity[] = $activityItem; diff --git a/app/User.php b/app/User.php index 4a5914afd..c55102078 100644 --- a/app/User.php +++ b/app/User.php @@ -164,6 +164,6 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon */ public function getEditUrl() { - return '/users/' . $this->id; + return '/settings/users/' . $this->id; } } diff --git a/composer.lock b/composer.lock index ad399b973..9951362c1 100644 --- a/composer.lock +++ b/composer.lock @@ -9,16 +9,16 @@ "packages": [ { "name": "aws/aws-sdk-php", - "version": "3.14.2", + "version": "3.15.1", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "2970cb63e7b7b37dd8c07a4fa4e4e18a110ed4e2" + "reference": "5e6078913293576de969703481994b77c380ca30" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/2970cb63e7b7b37dd8c07a4fa4e4e18a110ed4e2", - "reference": "2970cb63e7b7b37dd8c07a4fa4e4e18a110ed4e2", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/5e6078913293576de969703481994b77c380ca30", + "reference": "5e6078913293576de969703481994b77c380ca30", "shasum": "" }, "require": { @@ -40,7 +40,8 @@ "ext-simplexml": "*", "ext-spl": "*", "nette/neon": "^2.3", - "phpunit/phpunit": "~4.0|~5.0" + "phpunit/phpunit": "~4.0|~5.0", + "psr/cache": "^1.0" }, "suggest": { "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", @@ -84,7 +85,7 @@ "s3", "sdk" ], - "time": "2016-01-28 21:33:18" + "time": "2016-02-11 23:23:31" }, { "name": "barryvdh/laravel-debugbar", @@ -918,16 +919,16 @@ }, { "name": "laravel/framework", - "version": "v5.2.12", + "version": "v5.2.16", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "6b6255ad7bfbdb721b8d00b09d52b146c5d363d7" + "reference": "39e89553c124dce266da03ee3c0260bdd62f1848" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/6b6255ad7bfbdb721b8d00b09d52b146c5d363d7", - "reference": "6b6255ad7bfbdb721b8d00b09d52b146c5d363d7", + "url": "https://api.github.com/repos/laravel/framework/zipball/39e89553c124dce266da03ee3c0260bdd62f1848", + "reference": "39e89553c124dce266da03ee3c0260bdd62f1848", "shasum": "" }, "require": { @@ -1042,7 +1043,7 @@ "framework", "laravel" ], - "time": "2016-01-26 04:15:37" + "time": "2016-02-15 17:46:58" }, { "name": "laravel/socialite", @@ -1629,16 +1630,16 @@ }, { "name": "paragonie/random_compat", - "version": "1.1.6", + "version": "v1.2.0", "source": { "type": "git", "url": "https://github.com/paragonie/random_compat.git", - "reference": "e6f80ab77885151908d0ec743689ca700886e8b0" + "reference": "b0e69d10852716b2ccbdff69c75c477637220790" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/e6f80ab77885151908d0ec743689ca700886e8b0", - "reference": "e6f80ab77885151908d0ec743689ca700886e8b0", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/b0e69d10852716b2ccbdff69c75c477637220790", + "reference": "b0e69d10852716b2ccbdff69c75c477637220790", "shasum": "" }, "require": { @@ -1673,7 +1674,7 @@ "pseudorandom", "random" ], - "time": "2016-01-29 16:19:52" + "time": "2016-02-06 03:52:05" }, { "name": "phenx/php-font-lib", @@ -2024,16 +2025,16 @@ }, { "name": "symfony/console", - "version": "v3.0.1", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "ebcdc507829df915f4ca23067bd59ee4ef61f6c3" + "reference": "5a02eaadaa285e2bb727eb6bbdfb8201fcd971b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/ebcdc507829df915f4ca23067bd59ee4ef61f6c3", - "reference": "ebcdc507829df915f4ca23067bd59ee4ef61f6c3", + "url": "https://api.github.com/repos/symfony/console/zipball/5a02eaadaa285e2bb727eb6bbdfb8201fcd971b0", + "reference": "5a02eaadaa285e2bb727eb6bbdfb8201fcd971b0", "shasum": "" }, "require": { @@ -2080,20 +2081,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2015-12-22 10:39:06" + "time": "2016-02-02 13:44:19" }, { "name": "symfony/debug", - "version": "v3.0.1", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "73612266ac709769effdbfc0762e5b07cfd2ac2a" + "reference": "29606049ced1ec715475f88d1bbe587252a3476e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/73612266ac709769effdbfc0762e5b07cfd2ac2a", - "reference": "73612266ac709769effdbfc0762e5b07cfd2ac2a", + "url": "https://api.github.com/repos/symfony/debug/zipball/29606049ced1ec715475f88d1bbe587252a3476e", + "reference": "29606049ced1ec715475f88d1bbe587252a3476e", "shasum": "" }, "require": { @@ -2137,20 +2138,20 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2015-12-26 13:39:53" + "time": "2016-01-27 05:14:46" }, { "name": "symfony/event-dispatcher", - "version": "v3.0.1", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "d36355e026905fa5229e1ed7b4e9eda2e67adfcf" + "reference": "4dd5df31a28c0f82b41cb1e1599b74b5dcdbdafa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d36355e026905fa5229e1ed7b4e9eda2e67adfcf", - "reference": "d36355e026905fa5229e1ed7b4e9eda2e67adfcf", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/4dd5df31a28c0f82b41cb1e1599b74b5dcdbdafa", + "reference": "4dd5df31a28c0f82b41cb1e1599b74b5dcdbdafa", "shasum": "" }, "require": { @@ -2197,20 +2198,20 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2015-10-30 23:35:59" + "time": "2016-01-27 05:14:46" }, { "name": "symfony/finder", - "version": "v3.0.1", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "8617895eb798b6bdb338321ce19453dc113e5675" + "reference": "623bda0abd9aa29e529c8e9c08b3b84171914723" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/8617895eb798b6bdb338321ce19453dc113e5675", - "reference": "8617895eb798b6bdb338321ce19453dc113e5675", + "url": "https://api.github.com/repos/symfony/finder/zipball/623bda0abd9aa29e529c8e9c08b3b84171914723", + "reference": "623bda0abd9aa29e529c8e9c08b3b84171914723", "shasum": "" }, "require": { @@ -2246,20 +2247,20 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2015-12-05 11:13:14" + "time": "2016-01-27 05:14:46" }, { "name": "symfony/http-foundation", - "version": "v3.0.1", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "939c8c28a5b1e4ab7317bc30c1f9aa881c4b06b5" + "reference": "9344a87ceedfc50354a39653e54257ee9aa6a77d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/939c8c28a5b1e4ab7317bc30c1f9aa881c4b06b5", - "reference": "939c8c28a5b1e4ab7317bc30c1f9aa881c4b06b5", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/9344a87ceedfc50354a39653e54257ee9aa6a77d", + "reference": "9344a87ceedfc50354a39653e54257ee9aa6a77d", "shasum": "" }, "require": { @@ -2298,20 +2299,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2015-12-18 15:43:53" + "time": "2016-02-02 13:44:19" }, { "name": "symfony/http-kernel", - "version": "v3.0.1", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "f7933e9f19e26e7baba7ec04735b466fedd3a6db" + "reference": "cec02604450481ac26710ca4249cc61b57b23942" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/f7933e9f19e26e7baba7ec04735b466fedd3a6db", - "reference": "f7933e9f19e26e7baba7ec04735b466fedd3a6db", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/cec02604450481ac26710ca4249cc61b57b23942", + "reference": "cec02604450481ac26710ca4249cc61b57b23942", "shasum": "" }, "require": { @@ -2380,7 +2381,7 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2015-12-26 16:46:13" + "time": "2016-02-03 12:38:44" }, { "name": "symfony/polyfill-mbstring", @@ -2551,16 +2552,16 @@ }, { "name": "symfony/process", - "version": "v3.0.1", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "f4794f1d00f0746621be3020ffbd8c5e0b217ee3" + "reference": "dfecef47506179db2501430e732adbf3793099c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/f4794f1d00f0746621be3020ffbd8c5e0b217ee3", - "reference": "f4794f1d00f0746621be3020ffbd8c5e0b217ee3", + "url": "https://api.github.com/repos/symfony/process/zipball/dfecef47506179db2501430e732adbf3793099c8", + "reference": "dfecef47506179db2501430e732adbf3793099c8", "shasum": "" }, "require": { @@ -2596,20 +2597,20 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2015-12-23 11:04:02" + "time": "2016-02-02 13:44:19" }, { "name": "symfony/routing", - "version": "v3.0.1", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "3b1bac52f42cb0f54df1a2dbabd55a1d214e2a59" + "reference": "4686baa55a835e1c1ede9b86ba02415c8c8d6166" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/3b1bac52f42cb0f54df1a2dbabd55a1d214e2a59", - "reference": "3b1bac52f42cb0f54df1a2dbabd55a1d214e2a59", + "url": "https://api.github.com/repos/symfony/routing/zipball/4686baa55a835e1c1ede9b86ba02415c8c8d6166", + "reference": "4686baa55a835e1c1ede9b86ba02415c8c8d6166", "shasum": "" }, "require": { @@ -2670,20 +2671,20 @@ "uri", "url" ], - "time": "2015-12-23 08:00:11" + "time": "2016-01-27 05:14:46" }, { "name": "symfony/translation", - "version": "v3.0.1", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "dff0867826a7068d673801b7522f8e2634016ef9" + "reference": "2de0b6f7ebe43cffd8a06996ebec6aab79ea9e91" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/dff0867826a7068d673801b7522f8e2634016ef9", - "reference": "dff0867826a7068d673801b7522f8e2634016ef9", + "url": "https://api.github.com/repos/symfony/translation/zipball/2de0b6f7ebe43cffd8a06996ebec6aab79ea9e91", + "reference": "2de0b6f7ebe43cffd8a06996ebec6aab79ea9e91", "shasum": "" }, "require": { @@ -2734,20 +2735,20 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2015-12-05 17:45:07" + "time": "2016-02-02 13:44:19" }, { "name": "symfony/var-dumper", - "version": "v3.0.1", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "87db8700deb12ba2b65e858f656a1f885530bcb0" + "reference": "24bb94807eff00db49374c37ebf56a0304e8aef3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/87db8700deb12ba2b65e858f656a1f885530bcb0", - "reference": "87db8700deb12ba2b65e858f656a1f885530bcb0", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/24bb94807eff00db49374c37ebf56a0304e8aef3", + "reference": "24bb94807eff00db49374c37ebf56a0304e8aef3", "shasum": "" }, "require": { @@ -2797,7 +2798,7 @@ "debug", "dump" ], - "time": "2015-12-05 11:13:14" + "time": "2016-01-07 13:38:51" }, { "name": "vlucas/phpdotenv", @@ -3182,22 +3183,24 @@ }, { "name": "phpspec/prophecy", - "version": "v1.5.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7" + "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4745ded9307786b730d7a60df5cb5a6c43cf95f7", - "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/3c91bdf81797d725b14cb62906f9a4ce44235972", + "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", "phpdocumentor/reflection-docblock": "~2.0", - "sebastian/comparator": "~1.1" + "sebastian/comparator": "~1.1", + "sebastian/recursion-context": "~1.0" }, "require-dev": { "phpspec/phpspec": "~2.0" @@ -3205,7 +3208,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4.x-dev" + "dev-master": "1.5.x-dev" } }, "autoload": { @@ -3238,7 +3241,7 @@ "spy", "stub" ], - "time": "2015-08-13 10:07:40" + "time": "2016-02-15 07:46:21" }, { "name": "phpunit/php-code-coverage", @@ -3482,16 +3485,16 @@ }, { "name": "phpunit/phpunit", - "version": "4.8.21", + "version": "4.8.23", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "ea76b17bced0500a28098626b84eda12dbcf119c" + "reference": "6e351261f9cd33daf205a131a1ba61c6d33bd483" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ea76b17bced0500a28098626b84eda12dbcf119c", - "reference": "ea76b17bced0500a28098626b84eda12dbcf119c", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6e351261f9cd33daf205a131a1ba61c6d33bd483", + "reference": "6e351261f9cd33daf205a131a1ba61c6d33bd483", "shasum": "" }, "require": { @@ -3550,7 +3553,7 @@ "testing", "xunit" ], - "time": "2015-12-12 07:45:58" + "time": "2016-02-11 14:56:33" }, { "name": "phpunit/phpunit-mock-objects", @@ -3981,16 +3984,16 @@ }, { "name": "symfony/css-selector", - "version": "v3.0.1", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "4613311fd46e146f506403ce2f8a0c71d402d2a3" + "reference": "6605602690578496091ac20ec7a5cbd160d4dff4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/4613311fd46e146f506403ce2f8a0c71d402d2a3", - "reference": "4613311fd46e146f506403ce2f8a0c71d402d2a3", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/6605602690578496091ac20ec7a5cbd160d4dff4", + "reference": "6605602690578496091ac20ec7a5cbd160d4dff4", "shasum": "" }, "require": { @@ -4030,20 +4033,20 @@ ], "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", - "time": "2015-12-05 17:45:07" + "time": "2016-01-27 05:14:46" }, { "name": "symfony/dom-crawler", - "version": "v3.0.1", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "7c622b0c9fb8bdb146d6dfa86c5f91dcbfdbc11d" + "reference": "b693a9650aa004576b593ff2e91ae749dc90123d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/7c622b0c9fb8bdb146d6dfa86c5f91dcbfdbc11d", - "reference": "7c622b0c9fb8bdb146d6dfa86c5f91dcbfdbc11d", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/b693a9650aa004576b593ff2e91ae749dc90123d", + "reference": "b693a9650aa004576b593ff2e91ae749dc90123d", "shasum": "" }, "require": { @@ -4086,20 +4089,20 @@ ], "description": "Symfony DomCrawler Component", "homepage": "https://symfony.com", - "time": "2015-12-26 13:42:31" + "time": "2016-01-25 09:56:57" }, { "name": "symfony/yaml", - "version": "v3.0.1", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "3df409958a646dad2bc5046c3fb671ee24a1a691" + "reference": "3cf0709d7fe936e97bee9e954382e449003f1d9a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/3df409958a646dad2bc5046c3fb671ee24a1a691", - "reference": "3df409958a646dad2bc5046c3fb671ee24a1a691", + "url": "https://api.github.com/repos/symfony/yaml/zipball/3cf0709d7fe936e97bee9e954382e449003f1d9a", + "reference": "3cf0709d7fe936e97bee9e954382e449003f1d9a", "shasum": "" }, "require": { @@ -4135,7 +4138,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2015-12-26 13:39:53" + "time": "2016-02-02 13:44:19" } ], "aliases": [], diff --git a/database/migrations/2014_10_12_000000_create_users_table.php b/database/migrations/2014_10_12_000000_create_users_table.php index aa6cfee42..5e060006e 100644 --- a/database/migrations/2014_10_12_000000_create_users_table.php +++ b/database/migrations/2014_10_12_000000_create_users_table.php @@ -18,7 +18,7 @@ class CreateUsersTable extends Migration $table->string('email')->unique(); $table->string('password', 60); $table->rememberToken(); - $table->timestamps(); + $table->nullableTimestamps(); }); \BookStack\User::forceCreate([ diff --git a/database/migrations/2015_07_12_114933_create_books_table.php b/database/migrations/2015_07_12_114933_create_books_table.php index 1f8fae685..51fb55c48 100644 --- a/database/migrations/2015_07_12_114933_create_books_table.php +++ b/database/migrations/2015_07_12_114933_create_books_table.php @@ -17,7 +17,7 @@ class CreateBooksTable extends Migration $table->string('name'); $table->string('slug')->indexed(); $table->text('description'); - $table->timestamps(); + $table->nullableTimestamps(); }); } diff --git a/database/migrations/2015_07_12_190027_create_pages_table.php b/database/migrations/2015_07_12_190027_create_pages_table.php index b7b1dc130..b3b2b9244 100644 --- a/database/migrations/2015_07_12_190027_create_pages_table.php +++ b/database/migrations/2015_07_12_190027_create_pages_table.php @@ -21,7 +21,7 @@ class CreatePagesTable extends Migration $table->longText('html'); $table->longText('text'); $table->integer('priority'); - $table->timestamps(); + $table->nullableTimestamps(); }); } diff --git a/database/migrations/2015_07_13_172121_create_images_table.php b/database/migrations/2015_07_13_172121_create_images_table.php index ecd074a9b..61beaa7c3 100644 --- a/database/migrations/2015_07_13_172121_create_images_table.php +++ b/database/migrations/2015_07_13_172121_create_images_table.php @@ -16,7 +16,7 @@ class CreateImagesTable extends Migration $table->increments('id'); $table->string('name'); $table->string('url'); - $table->timestamps(); + $table->nullableTimestamps(); }); } diff --git a/database/migrations/2015_07_27_172342_create_chapters_table.php b/database/migrations/2015_07_27_172342_create_chapters_table.php index 5467e63f2..7974759f2 100644 --- a/database/migrations/2015_07_27_172342_create_chapters_table.php +++ b/database/migrations/2015_07_27_172342_create_chapters_table.php @@ -19,7 +19,7 @@ class CreateChaptersTable extends Migration $table->text('name'); $table->text('description'); $table->integer('priority'); - $table->timestamps(); + $table->nullableTimestamps(); }); } diff --git a/database/migrations/2015_08_09_093534_create_page_revisions_table.php b/database/migrations/2015_08_09_093534_create_page_revisions_table.php index b55097f0b..9c0693309 100644 --- a/database/migrations/2015_08_09_093534_create_page_revisions_table.php +++ b/database/migrations/2015_08_09_093534_create_page_revisions_table.php @@ -19,7 +19,7 @@ class CreatePageRevisionsTable extends Migration $table->longText('html'); $table->longText('text'); $table->integer('created_by'); - $table->timestamps(); + $table->nullableTimestamps(); }); } diff --git a/database/migrations/2015_08_16_142133_create_activities_table.php b/database/migrations/2015_08_16_142133_create_activities_table.php index a0e177ddb..f8d9064f2 100644 --- a/database/migrations/2015_08_16_142133_create_activities_table.php +++ b/database/migrations/2015_08_16_142133_create_activities_table.php @@ -20,7 +20,7 @@ class CreateActivitiesTable extends Migration $table->integer('user_id'); $table->integer('entity_id'); $table->string('entity_type'); - $table->timestamps(); + $table->nullableTimestamps(); }); } diff --git a/database/migrations/2015_08_29_105422_add_roles_and_permissions.php b/database/migrations/2015_08_29_105422_add_roles_and_permissions.php index 7247a0a38..4389dc32e 100644 --- a/database/migrations/2015_08_29_105422_add_roles_and_permissions.php +++ b/database/migrations/2015_08_29_105422_add_roles_and_permissions.php @@ -28,7 +28,7 @@ class AddRolesAndPermissions extends Migration $table->string('name')->unique(); $table->string('display_name')->nullable(); $table->string('description')->nullable(); - $table->timestamps(); + $table->nullableTimestamps(); }); // Create table for associating roles to users (Many-to-Many) @@ -50,7 +50,7 @@ class AddRolesAndPermissions extends Migration $table->string('name')->unique(); $table->string('display_name')->nullable(); $table->string('description')->nullable(); - $table->timestamps(); + $table->nullableTimestamps(); }); // Create table for associating permissions to roles (Many-to-Many) diff --git a/database/migrations/2015_08_30_125859_create_settings_table.php b/database/migrations/2015_08_30_125859_create_settings_table.php index 8437668f7..2cef3e6e7 100644 --- a/database/migrations/2015_08_30_125859_create_settings_table.php +++ b/database/migrations/2015_08_30_125859_create_settings_table.php @@ -15,7 +15,7 @@ class CreateSettingsTable extends Migration Schema::create('settings', function (Blueprint $table) { $table->string('setting_key')->primary()->indexed(); $table->text('value'); - $table->timestamps(); + $table->nullableTimestamps(); }); } diff --git a/database/migrations/2015_09_04_165821_create_social_accounts_table.php b/database/migrations/2015_09_04_165821_create_social_accounts_table.php index 3933e728f..700d7f90f 100644 --- a/database/migrations/2015_09_04_165821_create_social_accounts_table.php +++ b/database/migrations/2015_09_04_165821_create_social_accounts_table.php @@ -18,7 +18,7 @@ class CreateSocialAccountsTable extends Migration $table->string('driver')->index(); $table->string('driver_id'); $table->string('avatar'); - $table->timestamps(); + $table->nullableTimestamps(); }); } diff --git a/database/migrations/2015_09_05_164707_add_email_confirmation_table.php b/database/migrations/2015_09_05_164707_add_email_confirmation_table.php index 2f20cf0cf..105bda49e 100644 --- a/database/migrations/2015_09_05_164707_add_email_confirmation_table.php +++ b/database/migrations/2015_09_05_164707_add_email_confirmation_table.php @@ -20,7 +20,7 @@ class AddEmailConfirmationTable extends Migration $table->increments('id'); $table->integer('user_id')->index(); $table->string('token')->index(); - $table->timestamps(); + $table->nullableTimestamps(); }); } diff --git a/database/migrations/2015_11_21_145609_create_views_table.php b/database/migrations/2015_11_21_145609_create_views_table.php index 2baef7317..90c350864 100644 --- a/database/migrations/2015_11_21_145609_create_views_table.php +++ b/database/migrations/2015_11_21_145609_create_views_table.php @@ -18,7 +18,7 @@ class CreateViewsTable extends Migration $table->integer('viewable_id'); $table->string('viewable_type'); $table->integer('views'); - $table->timestamps(); + $table->nullableTimestamps(); }); } diff --git a/database/migrations/2016_02_25_184030_add_slug_to_revisions.php b/database/migrations/2016_02_25_184030_add_slug_to_revisions.php new file mode 100644 index 000000000..0be6c7940 --- /dev/null +++ b/database/migrations/2016_02_25_184030_add_slug_to_revisions.php @@ -0,0 +1,35 @@ +string('slug'); + $table->index('slug'); + $table->string('book_slug'); + $table->index('book_slug'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('page_revisions', function (Blueprint $table) { + $table->dropColumn('slug'); + $table->dropColumn('book_slug'); + }); + } +} diff --git a/phpunit.xml b/phpunit.xml index 48c0dde22..762fc2da7 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -25,8 +25,13 @@ - + + + + + + diff --git a/resources/assets/js/global.js b/resources/assets/js/global.js index a61299d21..90b03e856 100644 --- a/resources/assets/js/global.js +++ b/resources/assets/js/global.js @@ -106,6 +106,12 @@ $(function () { } }); + // Common jQuery actions + $('[data-action="expand-entity-list-details"]').click(function() { + $('.entity-list.compact').find('p').slideToggle(240); + }); + + }); diff --git a/resources/assets/sass/_header.scss b/resources/assets/sass/_header.scss index 2f06532c0..1edfc0037 100644 --- a/resources/assets/sass/_header.scss +++ b/resources/assets/sass/_header.scss @@ -139,54 +139,6 @@ form.search-box { height: 43px; } -.dropdown-container { - display: inline-block; - vertical-align: top; - position: relative; -} - -.dropdown-container ul { - display: none; - position: absolute; - z-index: 999; - top: 0; - list-style: none; - right: 0; - margin: $-m 0; - background-color: #FFFFFF; - box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.1); - border-radius: 1px; - border: 1px solid #EEE; - min-width: 180px; - padding: $-xs 0; - color: #555; - text-align: left !important; - &.wide { - min-width: 220px; - } - .text-muted { - color: #999; - } - a { - display: block; - padding: $-xs $-m; - color: #555; - &:hover { - text-decoration: none; - background-color: #EEE; - } - i { - margin-right: $-m; - padding-right: 0; - display: inline; - width: 22px; - } - } - li.border-bottom { - border-bottom: 1px solid #DDD; - } -} - .breadcrumbs span.sep { color: #aaa; padding: 0 $-xs; diff --git a/resources/assets/sass/_lists.scss b/resources/assets/sass/_lists.scss index da34c8f61..f0bd3b1ea 100644 --- a/resources/assets/sass/_lists.scss +++ b/resources/assets/sass/_lists.scss @@ -283,4 +283,87 @@ ul.pagination { a { color: $primary; } -} \ No newline at end of file +} + +.entity-list { + >div { + padding: $-m 0; + } + h3 { + margin: 0; + } + p { + margin: $-xs 0 0 0; + } + hr { + margin: 0; + } + .text-small.text-muted { + color: #AAA; + font-size: 0.75em; + margin-top: $-xs; + } +} +.entity-list.compact { + font-size: 0.6em; + h3, a { + line-height: 1.2; + } + p { + display: none; + font-size: $fs-m * 0.8; + padding-top: $-xs; + margin: 0; + } + hr { + margin: 0; + } +} + +.dropdown-container { + display: inline-block; + vertical-align: top; + position: relative; +} + +.dropdown-container ul { + display: none; + position: absolute; + z-index: 999; + top: 0; + list-style: none; + right: 0; + margin: $-m 0; + background-color: #FFFFFF; + box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.1); + border-radius: 1px; + border: 1px solid #EEE; + min-width: 180px; + padding: $-xs 0; + color: #555; + text-align: left !important; + &.wide { + min-width: 220px; + } + .text-muted { + color: #999; + } + a { + display: block; + padding: $-xs $-m; + color: #555; + &:hover { + text-decoration: none; + background-color: #EEE; + } + i { + margin-right: $-m; + padding-right: 0; + display: inline; + width: 22px; + } + } + li.border-bottom { + border-bottom: 1px solid #DDD; + } +} diff --git a/resources/assets/sass/_pages.scss b/resources/assets/sass/_pages.scss index 74fac4dfc..e1feccb64 100644 --- a/resources/assets/sass/_pages.scss +++ b/resources/assets/sass/_pages.scss @@ -100,7 +100,7 @@ background-color: #FFF; border: 1px solid #DDD; color: #666; - width: 180px; + width: 172px; z-index: 40; } input, button { diff --git a/resources/assets/sass/_tables.scss b/resources/assets/sass/_tables.scss index 031131158..999d65f8d 100644 --- a/resources/assets/sass/_tables.scss +++ b/resources/assets/sass/_tables.scss @@ -20,4 +20,8 @@ table.table { table { max-width: 100%; + thead { + background-color: #F8F8F8; + font-weight: 500; + } } \ No newline at end of file diff --git a/resources/assets/sass/_text.scss b/resources/assets/sass/_text.scss index 499896631..86150e94b 100644 --- a/resources/assets/sass/_text.scss +++ b/resources/assets/sass/_text.scss @@ -115,7 +115,8 @@ pre { box-shadow: 0 1px 2px 0px rgba(10, 10, 10, 0.06); border: 1px solid rgba(221, 221, 221, 0.66); background-color: #fdf6e3; - padding: 0.5em; + padding: $-s; + overflow-x: scroll; } blockquote { @@ -251,6 +252,18 @@ ol { text-align: right; } +.text-bigger { + font-size: 1.1em; +} + +.text-large { + font-size: 1.6666em; +} + +.no-color { + color: inherit; +} + /** * Grouping */ diff --git a/resources/assets/sass/styles.scss b/resources/assets/sass/styles.scss index 6b1c1400d..4e6823fc0 100644 --- a/resources/assets/sass/styles.scss +++ b/resources/assets/sass/styles.scss @@ -47,6 +47,13 @@ body.dragging, body.dragging * { width: 80px; height: 80px; } + &.huge { + width: 120px; + height: 120px; + } + &.square { + border-radius: 3px; + } } // System wide notifications diff --git a/resources/views/base.blade.php b/resources/views/base.blade.php index 2dfa7ddfd..0df49485e 100644 --- a/resources/views/base.blade.php +++ b/resources/views/base.blade.php @@ -58,10 +58,13 @@ diff --git a/resources/views/chapters/list-item.blade.php b/resources/views/chapters/list-item.blade.php index 91f5ce688..beaf1cac2 100644 --- a/resources/views/chapters/list-item.blade.php +++ b/resources/views/chapters/list-item.blade.php @@ -10,7 +10,7 @@

{{ $chapter->getExcerpt() }}

@endif - @if(count($chapter->pages) > 0 && !isset($hidePages)) + @if(!isset($hidePages) && count($chapter->pages) > 0)

{{ count($chapter->pages) }} Pages

@foreach($chapter->pages as $page) diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php index ccae9c0ea..8aaae1e1b 100644 --- a/resources/views/home.blade.php +++ b/resources/views/home.blade.php @@ -2,20 +2,44 @@ @section('content') +
+
+
+
+ +
+
+
+ +
+
+
+
+
+
-
+
@if($signedIn) -

My Recently Viewed

+

My Recently Viewed

@else -

Recent Books

+

Recent Books

@endif - @include('partials/entity-list', ['entities' => $recents]) + @include('partials/entity-list', ['entities' => $recents, 'style' => 'compact'])
-
-
 
+
+

Recently Created Pages

+ @include('partials/entity-list', ['entities' => $recentlyCreatedPages, 'style' => 'compact']) + +

Recently Updated Pages

+ @include('partials/entity-list', ['entities' => $recentlyCreatedPages, 'style' => 'compact']) +
+ +

Recent Activity

@include('partials/activity-list', ['activity' => $activity])
diff --git a/resources/views/pages/detailed-listing.blade.php b/resources/views/pages/detailed-listing.blade.php new file mode 100644 index 000000000..85b77fae1 --- /dev/null +++ b/resources/views/pages/detailed-listing.blade.php @@ -0,0 +1,18 @@ +@extends('base') + +@section('content') + +
+
+ +
+

{{ $title }}

+ @include('partials/entity-list', ['entities' => $pages, 'style' => 'detailed']) + {!! $pages->links() !!} +
+ +
+ +
+
+@stop \ No newline at end of file diff --git a/resources/views/pages/list-item.blade.php b/resources/views/pages/list-item.blade.php index 37fe9e66a..00664ed6f 100644 --- a/resources/views/pages/list-item.blade.php +++ b/resources/views/pages/list-item.blade.php @@ -3,18 +3,29 @@ {{ $page->name }} - @if(isset($showMeta) && $showMeta) -
- {{ $page->book->name }} - @if($page->chapter) - {{ $page->chapter->name }} - @endif -
- @endif - @if(isset($page->searchSnippet))

{!! $page->searchSnippet !!}

@else

{{ $page->getExcerpt() }}

@endif + + @if(isset($style) && $style === 'detailed') +
+
+ Created {{$page->created_at->diffForHumans()}} @if($page->createdBy)by {{$page->createdBy->name}}@endif
+ Last updated {{ $page->updated_at->diffForHumans() }} @if($page->updatedBy)by {{$page->updatedBy->name}} @endif +
+
+ {{ $page->book->getShortName(30) }} +
+ @if($page->chapter) + {{ $page->chapter->getShortName(30) }} + @else + Page is not in a chapter + @endif +
+
+ @endif + +
\ No newline at end of file diff --git a/resources/views/partials/activity-item.blade.php b/resources/views/partials/activity-item.blade.php index 1471bdb50..f94fad5e1 100644 --- a/resources/views/partials/activity-item.blade.php +++ b/resources/views/partials/activity-item.blade.php @@ -9,7 +9,7 @@
@if($activity->user) - {{$activity->user->name}} + {{$activity->user->name}} @else A deleted user @endif diff --git a/resources/views/partials/entity-list.blade.php b/resources/views/partials/entity-list.blade.php index a357a70fa..a52e5f013 100644 --- a/resources/views/partials/entity-list.blade.php +++ b/resources/views/partials/entity-list.blade.php @@ -1,21 +1,23 @@ -@if(count($entities) > 0) - @foreach($entities as $index => $entity) - @if($entity->isA('page')) - @include('pages/list-item', ['page' => $entity]) - @elseif($entity->isA('book')) - @include('books/list-item', ['book' => $entity]) - @elseif($entity->isA('chapter')) - @include('chapters/list-item', ['chapter' => $entity, 'hidePages' => true]) - @endif +
+ @if(count($entities) > 0) + @foreach($entities as $index => $entity) + @if($entity->isA('page')) + @include('pages/list-item', ['page' => $entity]) + @elseif($entity->isA('book')) + @include('books/list-item', ['book' => $entity]) + @elseif($entity->isA('chapter')) + @include('chapters/list-item', ['chapter' => $entity, 'hidePages' => true]) + @endif - @if($index !== count($entities) - 1) -
- @endif + @if($index !== count($entities) - 1) +
+ @endif - @endforeach -@else -

- No items available -

-@endif \ No newline at end of file + @endforeach + @else +

+ No items available +

+ @endif +
\ No newline at end of file diff --git a/resources/views/search/all.blade.php b/resources/views/search/all.blade.php index e0920dacc..f2385a5d3 100644 --- a/resources/views/search/all.blade.php +++ b/resources/views/search/all.blade.php @@ -6,41 +6,36 @@

Search Results    {{$searchTerm}}

+

+ View all matched pages + + @if(count($chapters) > 0) +      + View all matched chapters + @endif + + @if(count($books) > 0) +      + View all matched books + @endif +

-

Matching Pages

-
- @if(count($pages) > 0) - @foreach($pages as $page) - @include('pages/list-item', ['page' => $page, 'showMeta' => true]) -
- @endforeach - @else -

No pages matched this search

- @endif -
+

Matching Pages

+ @include('partials/entity-list', ['entities' => $pages, 'style' => 'detailed'])
@if(count($books) > 0) -

Matching Books

-
- @foreach($books as $book) - @include('books/list-item', ['book' => $book]) -
- @endforeach -
+

Matching Books

+ @include('partials/entity-list', ['entities' => $books]) @endif @if(count($chapters) > 0) -

Matching Chapters

-
- @foreach($chapters as $chapter) - @include('chapters/list-item', ['chapter' => $chapter, 'hidePages' => true]) - @endforeach -
+

Matching Chapters

+ @include('partials/entity-list', ['entities' => $chapters]) @endif
diff --git a/resources/views/search/entity-search-list.blade.php b/resources/views/search/entity-search-list.blade.php new file mode 100644 index 000000000..5ca6492ef --- /dev/null +++ b/resources/views/search/entity-search-list.blade.php @@ -0,0 +1,18 @@ +@extends('base') + +@section('content') + +
+
+ +
+

{{ $title }} {{$searchTerm}}

+ @include('partials.entity-list', ['entities' => $entities, 'style' => 'detailed']) + {!! $entities->links() !!} +
+ +
+ +
+
+@stop \ No newline at end of file diff --git a/resources/views/settings/navbar.blade.php b/resources/views/settings/navbar.blade.php index 49239b73c..3afe59a8e 100644 --- a/resources/views/settings/navbar.blade.php +++ b/resources/views/settings/navbar.blade.php @@ -4,7 +4,7 @@
diff --git a/resources/views/users/create.blade.php b/resources/views/users/create.blade.php index e6398b867..716742ba8 100644 --- a/resources/views/users/create.blade.php +++ b/resources/views/users/create.blade.php @@ -6,7 +6,7 @@

Create User

-
+ {!! csrf_field() !!} @include('users.forms.' . $authMethod)
diff --git a/resources/views/users/delete.blade.php b/resources/views/users/delete.blade.php index 0221dee9a..282ae242b 100644 --- a/resources/views/users/delete.blade.php +++ b/resources/views/users/delete.blade.php @@ -7,7 +7,7 @@

This will fully delete this user with the name '{{$user->name}}' from the system.

Are you sure you want to delete this user?

-
+ {!! csrf_field() !!} Cancel diff --git a/resources/views/users/edit.blade.php b/resources/views/users/edit.blade.php index d1cabe290..6d18c12f0 100644 --- a/resources/views/users/edit.blade.php +++ b/resources/views/users/edit.blade.php @@ -9,7 +9,7 @@
@@ -19,7 +19,7 @@
- +

Edit {{ $user->id === $currentUser->id ? 'Profile' : 'User' }}

diff --git a/resources/views/users/forms/ldap.blade.php b/resources/views/users/forms/ldap.blade.php index 3897dfd9a..4a6673dc2 100644 --- a/resources/views/users/forms/ldap.blade.php +++ b/resources/views/users/forms/ldap.blade.php @@ -25,6 +25,6 @@ @endif
- Cancel + Cancel
\ No newline at end of file diff --git a/resources/views/users/forms/standard.blade.php b/resources/views/users/forms/standard.blade.php index 7960a7ed5..c20e2955d 100644 --- a/resources/views/users/forms/standard.blade.php +++ b/resources/views/users/forms/standard.blade.php @@ -34,7 +34,7 @@
- Cancel + Cancel
diff --git a/resources/views/users/index.blade.php b/resources/views/users/index.blade.php index 24d68b97b..6e5d10c5f 100644 --- a/resources/views/users/index.blade.php +++ b/resources/views/users/index.blade.php @@ -10,7 +10,7 @@

Users

@if($currentUser->can('user-create'))

- Add new user + Add new user

@endif @@ -25,7 +25,7 @@
{{$user->name}} @if($currentUser->can('user-update') || $currentUser->id == $user->id) - + @endif {{ $user->name }} @if($currentUser->can('user-update') || $currentUser->id == $user->id) @@ -34,7 +34,7 @@ @if($currentUser->can('user-update') || $currentUser->id == $user->id) - + @endif {{ $user->email }} @if($currentUser->can('user-update') || $currentUser->id == $user->id) diff --git a/resources/views/users/profile.blade.php b/resources/views/users/profile.blade.php new file mode 100644 index 000000000..10189f9aa --- /dev/null +++ b/resources/views/users/profile.blade.php @@ -0,0 +1,77 @@ +@extends('base') + +@section('content') + +
+
+
+ +
+ +
+
+
+
+ {{ $user->name }} +
+
+

{{ $user->name }}

+

+ User for {{ $user->created_at->diffForHumans(null, true) }} +

+
+
+
+
+
Created Content
+
+ {{ $assetCounts['books'] }} {{ str_plural('Book', $assetCounts['books']) }} +
+
+ {{ $assetCounts['chapters'] }} {{ str_plural('Chapter', $assetCounts['chapters']) }} +
+
+ {{ $assetCounts['pages'] }} {{ str_plural('Page', $assetCounts['pages']) }} +
+
+
+ + +
+ +

Recently Created Pages

+ @if (count($recentlyCreated['pages']) > 0) + @include('partials/entity-list', ['entities' => $recentlyCreated['pages']]) + @else +

{{ $user->name }} has not created any pages

+ @endif + +
+ +

Recently Created Chapters

+ @if (count($recentlyCreated['chapters']) > 0) + @include('partials/entity-list', ['entities' => $recentlyCreated['chapters']]) + @else +

{{ $user->name }} has not created any chapters

+ @endif + +
+ +

Recently Created Books

+ @if (count($recentlyCreated['books']) > 0) + @include('partials/entity-list', ['entities' => $recentlyCreated['books']]) + @else +

{{ $user->name }} has not created any books

+ @endif +
+ +
+

Recent Activity

+ @include('partials/activity-list', ['activity' => $activity]) +
+ +
+
+ + +@stop \ No newline at end of file diff --git a/storage/fonts/.gitignore b/storage/fonts/.gitignore old mode 100644 new mode 100755 diff --git a/tests/Auth/AuthTest.php b/tests/Auth/AuthTest.php index 022dc14fb..694022666 100644 --- a/tests/Auth/AuthTest.php +++ b/tests/Auth/AuthTest.php @@ -129,7 +129,7 @@ class AuthTest extends TestCase $user = factory(\BookStack\User::class)->make(); $this->asAdmin() - ->visit('/users') + ->visit('/settings/users') ->click('Add new user') ->type($user->name, '#name') ->type($user->email, '#email') @@ -138,7 +138,7 @@ class AuthTest extends TestCase ->type($user->password, '#password-confirm') ->press('Save') ->seeInDatabase('users', $user->toArray()) - ->seePageIs('/users') + ->seePageIs('/settings/users') ->see($user->name); } @@ -147,13 +147,13 @@ class AuthTest extends TestCase $user = \BookStack\User::all()->last(); $password = $user->password; $this->asAdmin() - ->visit('/users') + ->visit('/settings/users') ->click($user->name) - ->seePageIs('/users/' . $user->id) + ->seePageIs('/settings/users/' . $user->id) ->see($user->email) ->type('Barry Scott', '#name') ->press('Save') - ->seePageIs('/users') + ->seePageIs('/settings/users') ->seeInDatabase('users', ['id' => $user->id, 'name' => 'Barry Scott', 'password' => $password]) ->notSeeInDatabase('users', ['name' => $user->name]); } @@ -161,7 +161,7 @@ class AuthTest extends TestCase public function test_user_password_update() { $user = \BookStack\User::all()->last(); - $userProfilePage = '/users/' . $user->id; + $userProfilePage = '/settings/users/' . $user->id; $this->asAdmin() ->visit($userProfilePage) ->type('newpassword', '#password') @@ -172,7 +172,7 @@ class AuthTest extends TestCase ->type('newpassword', '#password') ->type('newpassword', '#password-confirm') ->press('Save') - ->seePageIs('/users'); + ->seePageIs('/settings/users'); $userPassword = \BookStack\User::find($user->id)->password; $this->assertTrue(Hash::check('newpassword', $userPassword)); @@ -184,11 +184,11 @@ class AuthTest extends TestCase $user = $this->getNewUser($userDetails->toArray()); $this->asAdmin() - ->visit('/users/' . $user->id) + ->visit('/settings/users/' . $user->id) ->click('Delete User') ->see($user->name) ->press('Confirm') - ->seePageIs('/users') + ->seePageIs('/settings/users') ->notSeeInDatabase('users', ['name' => $user->name]); } @@ -199,10 +199,10 @@ class AuthTest extends TestCase $this->assertEquals(1, $adminRole->users()->count()); $user = $adminRole->users->first(); - $this->asAdmin()->visit('/users/' . $user->id) + $this->asAdmin()->visit('/settings/users/' . $user->id) ->click('Delete User') ->press('Confirm') - ->seePageIs('/users/' . $user->id) + ->seePageIs('/settings/users/' . $user->id) ->see('You cannot delete the only admin'); } diff --git a/tests/Auth/LdapTest.php b/tests/Auth/LdapTest.php index d80b8d50d..13036e5dc 100644 --- a/tests/Auth/LdapTest.php +++ b/tests/Auth/LdapTest.php @@ -94,7 +94,7 @@ class LdapTest extends \TestCase public function test_create_user_form() { - $this->asAdmin()->visit('/users/create') + $this->asAdmin()->visit('/settings/users/create') ->dontSee('Password') ->type($this->mockUser->name, '#name') ->type($this->mockUser->email, '#email') @@ -102,19 +102,19 @@ class LdapTest extends \TestCase ->see('The external auth id field is required.') ->type($this->mockUser->name, '#external_auth_id') ->press('Save') - ->seePageIs('/users') + ->seePageIs('/settings/users') ->seeInDatabase('users', ['email' => $this->mockUser->email, 'external_auth_id' => $this->mockUser->name, 'email_confirmed' => true]); } public function test_user_edit_form() { $editUser = User::all()->last(); - $this->asAdmin()->visit('/users/' . $editUser->id) + $this->asAdmin()->visit('/settings/users/' . $editUser->id) ->see('Edit User') ->dontSee('Password') ->type('test_auth_id', '#external_auth_id') ->press('Save') - ->seePageIs('/users') + ->seePageIs('/settings/users') ->seeInDatabase('users', ['email' => $editUser->email, 'external_auth_id' => 'test_auth_id']); } @@ -127,7 +127,7 @@ class LdapTest extends \TestCase public function test_non_admins_cannot_change_auth_id() { $testUser = User::all()->last(); - $this->actingAs($testUser)->visit('/users/' . $testUser->id) + $this->actingAs($testUser)->visit('/settings/users/' . $testUser->id) ->dontSee('External Authentication'); } diff --git a/tests/EntitySearchTest.php b/tests/EntitySearchTest.php new file mode 100644 index 000000000..6b313e7b8 --- /dev/null +++ b/tests/EntitySearchTest.php @@ -0,0 +1,85 @@ +first(); + $page = $book->pages->first(); + + $this->asAdmin() + ->visit('/') + ->type($page->name, 'term') + ->press('header-search-box-button') + ->see('Search Results') + ->see($page->name) + ->click($page->name) + ->seePageIs($page->getUrl()); + } + + public function test_invalid_page_search() + { + $this->asAdmin() + ->visit('/') + ->type('

test

', 'term') + ->press('header-search-box-button') + ->see('Search Results') + ->seeStatusCode(200); + } + + public function test_empty_search_redirects_back() + { + $this->asAdmin() + ->visit('/') + ->visit('/search/all') + ->seePageIs('/'); + } + + public function test_book_search() + { + $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 test_empty_book_search_redirects_back() + { + $book = \BookStack\Book::all()->first(); + $this->asAdmin() + ->visit('/books') + ->visit('/search/book/' . $book->id . '?term=') + ->seePageIs('/books'); + } + + + public function test_pages_search_listing() + { + $page = \BookStack\Page::all()->last(); + $this->asAdmin()->visit('/search/pages?term=' . $page->name) + ->see('Page Search Results')->see('.entity-list', $page->name); + } + + public function test_chapters_search_listing() + { + $chapter = \BookStack\Chapter::all()->last(); + $this->asAdmin()->visit('/search/chapters?term=' . $chapter->name) + ->see('Chapter Search Results')->seeInElement('.entity-list', $chapter->name); + } + + public function test_books_search_listing() + { + $book = \BookStack\Book::all()->last(); + $this->asAdmin()->visit('/search/books?term=' . $book->name) + ->see('Book Search Results')->see('.entity-list', $book->name); + } +} diff --git a/tests/EntityTest.php b/tests/EntityTest.php index 5bfedb535..2936fc047 100644 --- a/tests/EntityTest.php +++ b/tests/EntityTest.php @@ -155,63 +155,6 @@ class EntityTest extends TestCase return $book; } - public function test_page_search() - { - $book = \BookStack\Book::all()->first(); - $page = $book->pages->first(); - - $this->asAdmin() - ->visit('/') - ->type($page->name, 'term') - ->press('header-search-box-button') - ->see('Search Results') - ->see($page->name) - ->click($page->name) - ->seePageIs($page->getUrl()); - } - - public function test_invalid_page_search() - { - $this->asAdmin() - ->visit('/') - ->type('

test

', 'term') - ->press('header-search-box-button') - ->see('Search Results') - ->seeStatusCode(200); - } - - public function test_empty_search_redirects_back() - { - $this->asAdmin() - ->visit('/') - ->visit('/search/all') - ->seePageIs('/'); - } - - public function test_book_search() - { - $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 test_empty_book_search_redirects_back() - { - $book = \BookStack\Book::all()->first(); - $this->asAdmin() - ->visit('/books') - ->visit('/search/book/' . $book->id . '?term=') - ->seePageIs('/books'); - } - - public function test_entities_viewable_after_creator_deletion() { // Create required assets and revisions @@ -250,5 +193,36 @@ class EntityTest extends TestCase ->click('Revisions')->seeStatusCode(200); } + public function test_recently_created_pages_view() + { + $user = $this->getNewUser(); + $content = $this->createEntityChainBelongingToUser($user); + + $this->asAdmin()->visit('/pages/recently-created') + ->seeInNthElement('.entity-list .page', 0, $content['page']->name); + } + + public function test_recently_updated_pages_view() + { + $user = $this->getNewUser(); + $content = $this->createEntityChainBelongingToUser($user); + + $this->asAdmin()->visit('/pages/recently-updated') + ->seeInNthElement('.entity-list .page', 0, $content['page']->name); + } + + public function test_old_page_slugs_redirect_to_new_pages() + { + $page = \BookStack\Page::all()->first(); + $pageUrl = $page->getUrl(); + $newPageUrl = '/books/' . $page->book->slug . '/page/super-test-page'; + $this->asAdmin()->visit($pageUrl) + ->clickInElement('#content', 'Edit') + ->type('super test page', '#name') + ->press('Save Page') + ->seePageIs($newPageUrl) + ->visit($pageUrl) + ->seePageIs($newPageUrl); + } } diff --git a/tests/TestCase.php b/tests/TestCase.php index 24685321f..4b8578a43 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -109,4 +109,18 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase return $this; } + + /** + * Click the text within the selected element. + * @param $parentElement + * @param $linkText + * @return $this + */ + protected function clickInElement($parentElement, $linkText) + { + $elem = $this->crawler->filter($parentElement); + $link = $elem->selectLink($linkText); + $this->visit($link->link()->getUri()); + return $this; + } } diff --git a/tests/UserProfileTest.php b/tests/UserProfileTest.php new file mode 100644 index 000000000..170e7eed1 --- /dev/null +++ b/tests/UserProfileTest.php @@ -0,0 +1,80 @@ +user = \BookStack\User::all()->last(); + } + + public function test_profile_page_shows_name() + { + $this->asAdmin() + ->visit('/user/' . $this->user->id) + ->see($this->user->name); + } + + public function test_profile_page_shows_recent_entities() + { + $content = $this->createEntityChainBelongingToUser($this->user, $this->user); + + $this->asAdmin() + ->visit('/user/' . $this->user->id) + // Check the recently created page is shown + ->see($content['page']->name) + // Check the recently created chapter is shown + ->see($content['chapter']->name) + // Check the recently created book is shown + ->see($content['book']->name); + } + + public function test_profile_page_shows_created_content_counts() + { + $newUser = $this->getNewUser(); + + $this->asAdmin()->visit('/user/' . $newUser->id) + ->see($newUser->name) + ->seeInElement('#content-counts', '0 Books') + ->seeInElement('#content-counts', '0 Chapters') + ->seeInElement('#content-counts', '0 Pages'); + + $this->createEntityChainBelongingToUser($newUser, $newUser); + + $this->asAdmin()->visit('/user/' . $newUser->id) + ->see($newUser->name) + ->seeInElement('#content-counts', '1 Book') + ->seeInElement('#content-counts', '1 Chapter') + ->seeInElement('#content-counts', '1 Page'); + } + + public function test_profile_page_shows_recent_activity() + { + $newUser = $this->getNewUser(); + $this->actingAs($newUser); + $entities = $this->createEntityChainBelongingToUser($newUser, $newUser); + Activity::add($entities['book'], 'book_update', $entities['book']->id); + Activity::add($entities['page'], 'page_create', $entities['book']->id); + + $this->asAdmin()->visit('/user/' . $newUser->id) + ->seeInElement('#recent-activity', 'updated book') + ->seeInElement('#recent-activity', 'created page') + ->seeInElement('#recent-activity', $entities['page']->name); + } + + public function test_clicking_user_name_in_activity_leads_to_profile_page() + { + $newUser = $this->getNewUser(); + $this->actingAs($newUser); + $entities = $this->createEntityChainBelongingToUser($newUser, $newUser); + Activity::add($entities['book'], 'book_update', $entities['book']->id); + Activity::add($entities['page'], 'page_create', $entities['book']->id); + + $this->asAdmin()->visit('/')->clickInElement('#recent-activity', $newUser->name) + ->seePageIs('/user/' . $newUser->id) + ->see($newUser->name); + } + +}