Dan Brown

Merge branch 'master' into release

Showing 61 changed files with 1120 additions and 379 deletions
...@@ -98,7 +98,7 @@ abstract class Entity extends Model ...@@ -98,7 +98,7 @@ abstract class Entity extends Model
98 * @param string[] array $wheres 98 * @param string[] array $wheres
99 * @return mixed 99 * @return mixed
100 */ 100 */
101 - public static function fullTextSearch($fieldsToSearch, $terms, $wheres = []) 101 + public static function fullTextSearchQuery($fieldsToSearch, $terms, $wheres = [])
102 { 102 {
103 $termString = ''; 103 $termString = '';
104 foreach ($terms as $term) { 104 foreach ($terms as $term) {
...@@ -107,7 +107,7 @@ abstract class Entity extends Model ...@@ -107,7 +107,7 @@ abstract class Entity extends Model
107 $fields = implode(',', $fieldsToSearch); 107 $fields = implode(',', $fieldsToSearch);
108 $termStringEscaped = \DB::connection()->getPdo()->quote($termString); 108 $termStringEscaped = \DB::connection()->getPdo()->quote($termString);
109 $search = static::addSelect(\DB::raw('*, MATCH(name) AGAINST('.$termStringEscaped.' IN BOOLEAN MODE) AS title_relevance')); 109 $search = static::addSelect(\DB::raw('*, MATCH(name) AGAINST('.$termStringEscaped.' IN BOOLEAN MODE) AS title_relevance'));
110 - $search = $search->whereRaw('MATCH(' . $fields . ') AGAINST(? IN BOOLEAN MODE)', [$termStringEscaped]); 110 + $search = $search->whereRaw('MATCH(' . $fields . ') AGAINST(? IN BOOLEAN MODE)', [$termString]);
111 111
112 // Add additional where terms 112 // Add additional where terms
113 foreach ($wheres as $whereTerm) { 113 foreach ($wheres as $whereTerm) {
...@@ -115,10 +115,13 @@ abstract class Entity extends Model ...@@ -115,10 +115,13 @@ abstract class Entity extends Model
115 } 115 }
116 116
117 // Load in relations 117 // Load in relations
118 - if (!static::isA('book')) $search = $search->with('book'); 118 + if (static::isA('page')) {
119 - if (static::isA('page')) $search = $search->with('chapter'); 119 + $search = $search->with('book', 'chapter', 'createdBy', 'updatedBy');
120 + } else if (static::isA('chapter')) {
121 + $search = $search->with('book');
122 + }
120 123
121 - return $search->orderBy('title_relevance', 'desc')->get(); 124 + return $search->orderBy('title_relevance', 'desc');
122 } 125 }
123 126
124 /** 127 /**
......
...@@ -157,7 +157,7 @@ class BookController extends Controller ...@@ -157,7 +157,7 @@ class BookController extends Controller
157 $this->checkPermission('book-update'); 157 $this->checkPermission('book-update');
158 $book = $this->bookRepo->getBySlug($bookSlug); 158 $book = $this->bookRepo->getBySlug($bookSlug);
159 $bookChildren = $this->bookRepo->getChildren($book); 159 $bookChildren = $this->bookRepo->getChildren($book);
160 - $books = $this->bookRepo->getAll(); 160 + $books = $this->bookRepo->getAll(false);
161 $this->setPageTitle('Sort Book ' . $book->getShortName()); 161 $this->setPageTitle('Sort Book ' . $book->getShortName());
162 return view('books/sort', ['book' => $book, 'current' => $book, 'books' => $books, 'bookChildren' => $bookChildren]); 162 return view('books/sort', ['book' => $book, 'current' => $book, 'books' => $books, 'bookChildren' => $bookChildren]);
163 } 163 }
......
...@@ -3,25 +3,21 @@ ...@@ -3,25 +3,21 @@
3 namespace BookStack\Http\Controllers; 3 namespace BookStack\Http\Controllers;
4 4
5 use Activity; 5 use Activity;
6 -use Illuminate\Http\Request; 6 +use BookStack\Repos\EntityRepo;
7 -
8 use BookStack\Http\Requests; 7 use BookStack\Http\Requests;
9 -use BookStack\Repos\BookRepo;
10 use Views; 8 use Views;
11 9
12 class HomeController extends Controller 10 class HomeController extends Controller
13 { 11 {
14 - 12 + protected $entityRepo;
15 - protected $activityService;
16 - protected $bookRepo;
17 13
18 /** 14 /**
19 * HomeController constructor. 15 * HomeController constructor.
20 - * @param BookRepo $bookRepo 16 + * @param EntityRepo $entityRepo
21 */ 17 */
22 - public function __construct(BookRepo $bookRepo) 18 + public function __construct(EntityRepo $entityRepo)
23 { 19 {
24 - $this->bookRepo = $bookRepo; 20 + $this->entityRepo = $entityRepo;
25 parent::__construct(); 21 parent::__construct();
26 } 22 }
27 23
...@@ -33,9 +29,16 @@ class HomeController extends Controller ...@@ -33,9 +29,16 @@ class HomeController extends Controller
33 */ 29 */
34 public function index() 30 public function index()
35 { 31 {
36 - $activity = Activity::latest(); 32 + $activity = Activity::latest(10);
37 - $recents = $this->signedIn ? Views::getUserRecentlyViewed(10, 0) : $this->bookRepo->getLatest(10); 33 + $recents = $this->signedIn ? Views::getUserRecentlyViewed(12, 0) : $this->entityRepo->getRecentlyCreatedBooks(10);
38 - return view('home', ['activity' => $activity, 'recents' => $recents]); 34 + $recentlyCreatedPages = $this->entityRepo->getRecentlyCreatedPages(5);
35 + $recentlyUpdatedPages = $this->entityRepo->getRecentlyUpdatedPages(5);
36 + return view('home', [
37 + 'activity' => $activity,
38 + 'recents' => $recents,
39 + 'recentlyCreatedPages' => $recentlyCreatedPages,
40 + 'recentlyUpdatedPages' => $recentlyUpdatedPages
41 + ]);
39 } 42 }
40 43
41 } 44 }
......
...@@ -11,6 +11,7 @@ use BookStack\Http\Requests; ...@@ -11,6 +11,7 @@ use BookStack\Http\Requests;
11 use BookStack\Repos\BookRepo; 11 use BookStack\Repos\BookRepo;
12 use BookStack\Repos\ChapterRepo; 12 use BookStack\Repos\ChapterRepo;
13 use BookStack\Repos\PageRepo; 13 use BookStack\Repos\PageRepo;
14 +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
14 use Views; 15 use Views;
15 16
16 class PageController extends Controller 17 class PageController extends Controller
...@@ -81,6 +82,8 @@ class PageController extends Controller ...@@ -81,6 +82,8 @@ class PageController extends Controller
81 82
82 /** 83 /**
83 * Display the specified page. 84 * Display the specified page.
85 + * If the page is not found via the slug the
86 + * revisions are searched for a match.
84 * 87 *
85 * @param $bookSlug 88 * @param $bookSlug
86 * @param $pageSlug 89 * @param $pageSlug
...@@ -89,7 +92,15 @@ class PageController extends Controller ...@@ -89,7 +92,15 @@ class PageController extends Controller
89 public function show($bookSlug, $pageSlug) 92 public function show($bookSlug, $pageSlug)
90 { 93 {
91 $book = $this->bookRepo->getBySlug($bookSlug); 94 $book = $this->bookRepo->getBySlug($bookSlug);
92 - $page = $this->pageRepo->getBySlug($pageSlug, $book->id); 95 +
96 + try {
97 + $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
98 + } catch (NotFoundHttpException $e) {
99 + $page = $this->pageRepo->findPageUsingOldSlug($pageSlug, $bookSlug);
100 + if ($page === null) abort(404);
101 + return redirect($page->getUrl());
102 + }
103 +
93 $sidebarTree = $this->bookRepo->getChildren($book); 104 $sidebarTree = $this->bookRepo->getChildren($book);
94 Views::add($page); 105 Views::add($page);
95 $this->setPageTitle($page->getShortName()); 106 $this->setPageTitle($page->getShortName());
...@@ -278,4 +289,30 @@ class PageController extends Controller ...@@ -278,4 +289,30 @@ class PageController extends Controller
278 ]); 289 ]);
279 } 290 }
280 291
292 + /**
293 + * Show a listing of recently created pages
294 + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
295 + */
296 + public function showRecentlyCreated()
297 + {
298 + $pages = $this->pageRepo->getRecentlyCreatedPaginated(20);
299 + return view('pages/detailed-listing', [
300 + 'title' => 'Recently Created Pages',
301 + 'pages' => $pages
302 + ]);
303 + }
304 +
305 + /**
306 + * Show a listing of recently created pages
307 + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
308 + */
309 + public function showRecentlyUpdated()
310 + {
311 + $pages = $this->pageRepo->getRecentlyUpdatedPaginated(20);
312 + return view('pages/detailed-listing', [
313 + 'title' => 'Recently Updated Pages',
314 + 'pages' => $pages
315 + ]);
316 + }
317 +
281 } 318 }
......
...@@ -42,11 +42,77 @@ class SearchController extends Controller ...@@ -42,11 +42,77 @@ class SearchController extends Controller
42 return redirect()->back(); 42 return redirect()->back();
43 } 43 }
44 $searchTerm = $request->get('term'); 44 $searchTerm = $request->get('term');
45 - $pages = $this->pageRepo->getBySearch($searchTerm); 45 + $paginationAppends = $request->only('term');
46 - $books = $this->bookRepo->getBySearch($searchTerm); 46 + $pages = $this->pageRepo->getBySearch($searchTerm, [], 20, $paginationAppends);
47 - $chapters = $this->chapterRepo->getBySearch($searchTerm); 47 + $books = $this->bookRepo->getBySearch($searchTerm, 10, $paginationAppends);
48 + $chapters = $this->chapterRepo->getBySearch($searchTerm, [], 10, $paginationAppends);
48 $this->setPageTitle('Search For ' . $searchTerm); 49 $this->setPageTitle('Search For ' . $searchTerm);
49 - return view('search/all', ['pages' => $pages, 'books' => $books, 'chapters' => $chapters, 'searchTerm' => $searchTerm]); 50 + return view('search/all', [
51 + 'pages' => $pages,
52 + 'books' => $books,
53 + 'chapters' => $chapters,
54 + 'searchTerm' => $searchTerm
55 + ]);
56 + }
57 +
58 + /**
59 + * Search only the pages in the system.
60 + * @param Request $request
61 + * @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View
62 + */
63 + public function searchPages(Request $request)
64 + {
65 + if (!$request->has('term')) return redirect()->back();
66 +
67 + $searchTerm = $request->get('term');
68 + $paginationAppends = $request->only('term');
69 + $pages = $this->pageRepo->getBySearch($searchTerm, [], 20, $paginationAppends);
70 + $this->setPageTitle('Page Search For ' . $searchTerm);
71 + return view('search/entity-search-list', [
72 + 'entities' => $pages,
73 + 'title' => 'Page Search Results',
74 + 'searchTerm' => $searchTerm
75 + ]);
76 + }
77 +
78 + /**
79 + * Search only the chapters in the system.
80 + * @param Request $request
81 + * @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View
82 + */
83 + public function searchChapters(Request $request)
84 + {
85 + if (!$request->has('term')) return redirect()->back();
86 +
87 + $searchTerm = $request->get('term');
88 + $paginationAppends = $request->only('term');
89 + $chapters = $this->chapterRepo->getBySearch($searchTerm, [], 20, $paginationAppends);
90 + $this->setPageTitle('Chapter Search For ' . $searchTerm);
91 + return view('search/entity-search-list', [
92 + 'entities' => $chapters,
93 + 'title' => 'Chapter Search Results',
94 + 'searchTerm' => $searchTerm
95 + ]);
96 + }
97 +
98 + /**
99 + * Search only the books in the system.
100 + * @param Request $request
101 + * @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View
102 + */
103 + public function searchBooks(Request $request)
104 + {
105 + if (!$request->has('term')) return redirect()->back();
106 +
107 + $searchTerm = $request->get('term');
108 + $paginationAppends = $request->only('term');
109 + $books = $this->bookRepo->getBySearch($searchTerm, 20, $paginationAppends);
110 + $this->setPageTitle('Book Search For ' . $searchTerm);
111 + return view('search/entity-search-list', [
112 + 'entities' => $books,
113 + 'title' => 'Book Search Results',
114 + 'searchTerm' => $searchTerm
115 + ]);
50 } 116 }
51 117
52 /** 118 /**
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
2 2
3 namespace BookStack\Http\Controllers; 3 namespace BookStack\Http\Controllers;
4 4
5 +use BookStack\Activity;
5 use Illuminate\Http\Request; 6 use Illuminate\Http\Request;
6 7
7 use Illuminate\Http\Response; 8 use Illuminate\Http\Response;
...@@ -92,10 +93,9 @@ class UserController extends Controller ...@@ -92,10 +93,9 @@ class UserController extends Controller
92 $user->save(); 93 $user->save();
93 } 94 }
94 95
95 - return redirect('/users'); 96 + return redirect('/settings/users');
96 } 97 }
97 98
98 -
99 /** 99 /**
100 * Show the form for editing the specified user. 100 * Show the form for editing the specified user.
101 * @param int $id 101 * @param int $id
...@@ -159,7 +159,7 @@ class UserController extends Controller ...@@ -159,7 +159,7 @@ class UserController extends Controller
159 } 159 }
160 160
161 $user->save(); 161 $user->save();
162 - return redirect('/users'); 162 + return redirect('/settings/users');
163 } 163 }
164 164
165 /** 165 /**
...@@ -197,6 +197,25 @@ class UserController extends Controller ...@@ -197,6 +197,25 @@ class UserController extends Controller
197 } 197 }
198 $this->userRepo->destroy($user); 198 $this->userRepo->destroy($user);
199 199
200 - return redirect('/users'); 200 + return redirect('/settings/users');
201 + }
202 +
203 + /**
204 + * Show the user profile page
205 + * @param $id
206 + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
207 + */
208 + public function showProfilePage($id)
209 + {
210 + $user = $this->userRepo->getById($id);
211 + $userActivity = $this->userRepo->getActivity($user);
212 + $recentlyCreated = $this->userRepo->getRecentlyCreated($user, 5, 0);
213 + $assetCounts = $this->userRepo->getAssetCounts($user);
214 + return view('users/profile', [
215 + 'user' => $user,
216 + 'activity' => $userActivity,
217 + 'recentlyCreated' => $recentlyCreated,
218 + 'assetCounts' => $assetCounts
219 + ]);
201 } 220 }
202 } 221 }
......
...@@ -3,6 +3,11 @@ ...@@ -3,6 +3,11 @@
3 // Authenticated routes... 3 // Authenticated routes...
4 Route::group(['middleware' => 'auth'], function () { 4 Route::group(['middleware' => 'auth'], function () {
5 5
6 + Route::group(['prefix' => 'pages'], function() {
7 + Route::get('/recently-created', 'PageController@showRecentlyCreated');
8 + Route::get('/recently-updated', 'PageController@showRecentlyUpdated');
9 + });
10 +
6 Route::group(['prefix' => 'books'], function () { 11 Route::group(['prefix' => 'books'], function () {
7 12
8 // Books 13 // Books
...@@ -47,14 +52,8 @@ Route::group(['middleware' => 'auth'], function () { ...@@ -47,14 +52,8 @@ Route::group(['middleware' => 'auth'], function () {
47 52
48 }); 53 });
49 54
50 - // Users 55 + // User Profile routes
51 - Route::get('/users', 'UserController@index'); 56 + Route::get('/user/{userId}', 'UserController@showProfilePage');
52 - Route::get('/users/create', 'UserController@create');
53 - Route::get('/users/{id}/delete', 'UserController@delete');
54 - Route::post('/users/create', 'UserController@store');
55 - Route::get('/users/{id}', 'UserController@edit');
56 - Route::put('/users/{id}', 'UserController@update');
57 - Route::delete('/users/{id}', 'UserController@destroy');
58 57
59 // Image routes 58 // Image routes
60 Route::group(['prefix' => 'images'], function() { 59 Route::group(['prefix' => 'images'], function() {
...@@ -75,6 +74,9 @@ Route::group(['middleware' => 'auth'], function () { ...@@ -75,6 +74,9 @@ Route::group(['middleware' => 'auth'], function () {
75 74
76 // Search 75 // Search
77 Route::get('/search/all', 'SearchController@searchAll'); 76 Route::get('/search/all', 'SearchController@searchAll');
77 + Route::get('/search/pages', 'SearchController@searchPages');
78 + Route::get('/search/books', 'SearchController@searchBooks');
79 + Route::get('/search/chapters', 'SearchController@searchChapters');
78 Route::get('/search/book/{bookId}', 'SearchController@searchBook'); 80 Route::get('/search/book/{bookId}', 'SearchController@searchBook');
79 81
80 // Other Pages 82 // Other Pages
...@@ -82,8 +84,18 @@ Route::group(['middleware' => 'auth'], function () { ...@@ -82,8 +84,18 @@ Route::group(['middleware' => 'auth'], function () {
82 Route::get('/home', 'HomeController@index'); 84 Route::get('/home', 'HomeController@index');
83 85
84 // Settings 86 // Settings
85 - Route::get('/settings', 'SettingController@index'); 87 + Route::group(['prefix' => 'settings'], function() {
86 - Route::post('/settings', 'SettingController@update'); 88 + Route::get('/', 'SettingController@index');
89 + Route::post('/', 'SettingController@update');
90 + // Users
91 + Route::get('/users', 'UserController@index');
92 + Route::get('/users/create', 'UserController@create');
93 + Route::get('/users/{id}/delete', 'UserController@delete');
94 + Route::post('/users/create', 'UserController@store');
95 + Route::get('/users/{id}', 'UserController@edit');
96 + Route::put('/users/{id}', 'UserController@update');
97 + Route::delete('/users/{id}', 'UserController@destroy');
98 + });
87 99
88 }); 100 });
89 101
......
...@@ -45,7 +45,8 @@ class Page extends Entity ...@@ -45,7 +45,8 @@ class Page extends Entity
45 45
46 public function getExcerpt($length = 100) 46 public function getExcerpt($length = 100)
47 { 47 {
48 - return strlen($this->text) > $length ? substr($this->text, 0, $length-3) . '...' : $this->text; 48 + $text = strlen($this->text) > $length ? substr($this->text, 0, $length-3) . '...' : $this->text;
49 + return mb_convert_encoding($text, 'UTF-8');
49 } 50 }
50 51
51 } 52 }
......
...@@ -14,8 +14,8 @@ class BookRepo ...@@ -14,8 +14,8 @@ class BookRepo
14 14
15 /** 15 /**
16 * BookRepo constructor. 16 * BookRepo constructor.
17 - * @param Book $book 17 + * @param Book $book
18 - * @param PageRepo $pageRepo 18 + * @param PageRepo $pageRepo
19 * @param ChapterRepo $chapterRepo 19 * @param ChapterRepo $chapterRepo
20 */ 20 */
21 public function __construct(Book $book, PageRepo $pageRepo, ChapterRepo $chapterRepo) 21 public function __construct(Book $book, PageRepo $pageRepo, ChapterRepo $chapterRepo)
...@@ -42,7 +42,9 @@ class BookRepo ...@@ -42,7 +42,9 @@ class BookRepo
42 */ 42 */
43 public function getAll($count = 10) 43 public function getAll($count = 10)
44 { 44 {
45 - return $this->book->orderBy('name', 'asc')->take($count)->get(); 45 + $bookQuery = $this->book->orderBy('name', 'asc');
46 + if (!$count) return $bookQuery->get();
47 + return $bookQuery->take($count)->get();
46 } 48 }
47 49
48 /** 50 /**
...@@ -159,7 +161,7 @@ class BookRepo ...@@ -159,7 +161,7 @@ class BookRepo
159 } 161 }
160 162
161 /** 163 /**
162 - * @param string $slug 164 + * @param string $slug
163 * @param bool|false $currentId 165 * @param bool|false $currentId
164 * @return bool 166 * @return bool
165 */ 167 */
...@@ -175,7 +177,7 @@ class BookRepo ...@@ -175,7 +177,7 @@ class BookRepo
175 /** 177 /**
176 * Provides a suitable slug for the given book name. 178 * Provides a suitable slug for the given book name.
177 * Ensures the returned slug is unique in the system. 179 * Ensures the returned slug is unique in the system.
178 - * @param string $name 180 + * @param string $name
179 * @param bool|false $currentId 181 * @param bool|false $currentId
180 * @return string 182 * @return string
181 */ 183 */
...@@ -218,12 +220,15 @@ class BookRepo ...@@ -218,12 +220,15 @@ class BookRepo
218 /** 220 /**
219 * Get books by search term. 221 * Get books by search term.
220 * @param $term 222 * @param $term
223 + * @param int $count
224 + * @param array $paginationAppends
221 * @return mixed 225 * @return mixed
222 */ 226 */
223 - public function getBySearch($term) 227 + public function getBySearch($term, $count = 20, $paginationAppends = [])
224 { 228 {
225 $terms = explode(' ', $term); 229 $terms = explode(' ', $term);
226 - $books = $this->book->fullTextSearch(['name', 'description'], $terms); 230 + $books = $this->book->fullTextSearchQuery(['name', 'description'], $terms)
231 + ->paginate($count)->appends($paginationAppends);
227 $words = join('|', explode(' ', preg_quote(trim($term), '/'))); 232 $words = join('|', explode(' ', preg_quote(trim($term), '/')));
228 foreach ($books as $book) { 233 foreach ($books as $book) {
229 //highlight 234 //highlight
......
...@@ -125,12 +125,15 @@ class ChapterRepo ...@@ -125,12 +125,15 @@ class ChapterRepo
125 * Get chapters by the given search term. 125 * Get chapters by the given search term.
126 * @param $term 126 * @param $term
127 * @param array $whereTerms 127 * @param array $whereTerms
128 + * @param int $count
129 + * @param array $paginationAppends
128 * @return mixed 130 * @return mixed
129 */ 131 */
130 - public function getBySearch($term, $whereTerms = []) 132 + public function getBySearch($term, $whereTerms = [], $count = 20, $paginationAppends = [])
131 { 133 {
132 $terms = explode(' ', $term); 134 $terms = explode(' ', $term);
133 - $chapters = $this->chapter->fullTextSearch(['name', 'description'], $terms, $whereTerms); 135 + $chapters = $this->chapter->fullTextSearchQuery(['name', 'description'], $terms, $whereTerms)
136 + ->paginate($count)->appends($paginationAppends);
134 $words = join('|', explode(' ', preg_quote(trim($term), '/'))); 137 $words = join('|', explode(' ', preg_quote(trim($term), '/')));
135 foreach ($chapters as $chapter) { 138 foreach ($chapters as $chapter) {
136 //highlight 139 //highlight
......
1 +<?php namespace BookStack\Repos;
2 +
3 +
4 +use BookStack\Book;
5 +use BookStack\Chapter;
6 +use BookStack\Page;
7 +
8 +class EntityRepo
9 +{
10 +
11 + public $book;
12 + public $chapter;
13 + public $page;
14 +
15 + /**
16 + * EntityService constructor.
17 + * @param $book
18 + * @param $chapter
19 + * @param $page
20 + */
21 + public function __construct(Book $book, Chapter $chapter, Page $page)
22 + {
23 + $this->book = $book;
24 + $this->chapter = $chapter;
25 + $this->page = $page;
26 + }
27 +
28 + /**
29 + * Get the latest books added to the system.
30 + * @param $count
31 + * @param $page
32 + */
33 + public function getRecentlyCreatedBooks($count = 20, $page = 0)
34 + {
35 + return $this->book->orderBy('created_at', 'desc')->skip($page*$count)->take($count)->get();
36 + }
37 +
38 + /**
39 + * Get the most recently updated books.
40 + * @param $count
41 + * @param int $page
42 + * @return mixed
43 + */
44 + public function getRecentlyUpdatedBooks($count = 20, $page = 0)
45 + {
46 + return $this->book->orderBy('updated_at', 'desc')->skip($page*$count)->take($count)->get();
47 + }
48 +
49 + /**
50 + * Get the latest pages added to the system.
51 + * @param $count
52 + * @param $page
53 + */
54 + public function getRecentlyCreatedPages($count = 20, $page = 0)
55 + {
56 + return $this->page->orderBy('created_at', 'desc')->skip($page*$count)->take($count)->get();
57 + }
58 +
59 + /**
60 + * Get the most recently updated pages.
61 + * @param $count
62 + * @param int $page
63 + * @return mixed
64 + */
65 + public function getRecentlyUpdatedPages($count = 20, $page = 0)
66 + {
67 + return $this->page->orderBy('updated_at', 'desc')->skip($page*$count)->take($count)->get();
68 + }
69 +
70 +
71 +}
...\ No newline at end of file ...\ No newline at end of file
...@@ -10,6 +10,7 @@ use Illuminate\Support\Facades\Log; ...@@ -10,6 +10,7 @@ use Illuminate\Support\Facades\Log;
10 use Illuminate\Support\Str; 10 use Illuminate\Support\Str;
11 use BookStack\Page; 11 use BookStack\Page;
12 use BookStack\PageRevision; 12 use BookStack\PageRevision;
13 +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
13 14
14 class PageRepo 15 class PageRepo
15 { 16 {
...@@ -65,11 +66,28 @@ class PageRepo ...@@ -65,11 +66,28 @@ class PageRepo
65 public function getBySlug($slug, $bookId) 66 public function getBySlug($slug, $bookId)
66 { 67 {
67 $page = $this->page->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first(); 68 $page = $this->page->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first();
68 - if ($page === null) abort(404); 69 + if ($page === null) throw new NotFoundHttpException('Page not found');
69 return $page; 70 return $page;
70 } 71 }
71 72
72 /** 73 /**
74 + * Search through page revisions and retrieve
75 + * the last page in the current book that
76 + * has a slug equal to the one given.
77 + * @param $pageSlug
78 + * @param $bookSlug
79 + * @return null | Page
80 + */
81 + public function findPageUsingOldSlug($pageSlug, $bookSlug)
82 + {
83 + $revision = $this->pageRevision->where('slug', '=', $pageSlug)
84 + ->where('book_slug', '=', $bookSlug)->orderBy('created_at', 'desc')
85 + ->with('page')->first();
86 + return $revision !== null ? $revision->page : null;
87 + }
88 +
89 + /**
90 + * Get a new Page instance from the given input.
73 * @param $input 91 * @param $input
74 * @return Page 92 * @return Page
75 */ 93 */
...@@ -125,21 +143,20 @@ class PageRepo ...@@ -125,21 +143,20 @@ class PageRepo
125 if($htmlText == '') return $htmlText; 143 if($htmlText == '') return $htmlText;
126 libxml_use_internal_errors(true); 144 libxml_use_internal_errors(true);
127 $doc = new \DOMDocument(); 145 $doc = new \DOMDocument();
128 - $doc->loadHTML($htmlText); 146 + $doc->loadHTML(mb_convert_encoding($htmlText, 'HTML-ENTITIES', 'UTF-8'));
129 147
130 $container = $doc->documentElement; 148 $container = $doc->documentElement;
131 $body = $container->childNodes->item(0); 149 $body = $container->childNodes->item(0);
132 $childNodes = $body->childNodes; 150 $childNodes = $body->childNodes;
133 151
134 // Ensure no duplicate ids are used 152 // Ensure no duplicate ids are used
135 - $lastId = false;
136 $idArray = []; 153 $idArray = [];
137 154
138 foreach ($childNodes as $index => $childNode) { 155 foreach ($childNodes as $index => $childNode) {
139 /** @var \DOMElement $childNode */ 156 /** @var \DOMElement $childNode */
140 if (get_class($childNode) !== 'DOMElement') continue; 157 if (get_class($childNode) !== 'DOMElement') continue;
141 158
142 - // Overwrite id if not a bookstack custom id 159 + // Overwrite id if not a BookStack custom id
143 if ($childNode->hasAttribute('id')) { 160 if ($childNode->hasAttribute('id')) {
144 $id = $childNode->getAttribute('id'); 161 $id = $childNode->getAttribute('id');
145 if (strpos($id, 'bkmrk') === 0 && array_search($id, $idArray) === false) { 162 if (strpos($id, 'bkmrk') === 0 && array_search($id, $idArray) === false) {
...@@ -149,13 +166,18 @@ class PageRepo ...@@ -149,13 +166,18 @@ class PageRepo
149 } 166 }
150 167
151 // Create an unique id for the element 168 // Create an unique id for the element
152 - do { 169 + // Uses the content as a basis to ensure output is the same every time
153 - $id = 'bkmrk-' . substr(uniqid(), -5); 170 + // the same content is passed through.
154 - } while ($id == $lastId); 171 + $contentId = 'bkmrk-' . substr(strtolower(preg_replace('/\s+/', '-', trim($childNode->nodeValue))), 0, 20);
155 - $lastId = $id; 172 + $newId = urlencode($contentId);
173 + $loopIndex = 0;
174 + while (in_array($newId, $idArray)) {
175 + $newId = urlencode($contentId . '-' . $loopIndex);
176 + $loopIndex++;
177 + }
156 178
157 - $childNode->setAttribute('id', $id); 179 + $childNode->setAttribute('id', $newId);
158 - $idArray[] = $id; 180 + $idArray[] = $newId;
159 } 181 }
160 182
161 // Generate inner html as a string 183 // Generate inner html as a string
...@@ -171,14 +193,17 @@ class PageRepo ...@@ -171,14 +193,17 @@ class PageRepo
171 /** 193 /**
172 * Gets pages by a search term. 194 * Gets pages by a search term.
173 * Highlights page content for showing in results. 195 * Highlights page content for showing in results.
174 - * @param string $term 196 + * @param string $term
175 * @param array $whereTerms 197 * @param array $whereTerms
198 + * @param int $count
199 + * @param array $paginationAppends
176 * @return mixed 200 * @return mixed
177 */ 201 */
178 - public function getBySearch($term, $whereTerms = []) 202 + public function getBySearch($term, $whereTerms = [], $count = 20, $paginationAppends = [])
179 { 203 {
180 $terms = explode(' ', $term); 204 $terms = explode(' ', $term);
181 - $pages = $this->page->fullTextSearch(['name', 'text'], $terms, $whereTerms); 205 + $pages = $this->page->fullTextSearchQuery(['name', 'text'], $terms, $whereTerms)
206 + ->paginate($count)->appends($paginationAppends);
182 207
183 // Add highlights to page text. 208 // Add highlights to page text.
184 $words = join('|', explode(' ', preg_quote(trim($term), '/'))); 209 $words = join('|', explode(' ', preg_quote(trim($term), '/')));
...@@ -238,9 +263,13 @@ class PageRepo ...@@ -238,9 +263,13 @@ class PageRepo
238 $this->saveRevision($page); 263 $this->saveRevision($page);
239 } 264 }
240 265
266 + // Prevent slug being updated if no name change
267 + if ($page->name !== $input['name']) {
268 + $page->slug = $this->findSuitableSlug($input['name'], $book_id, $page->id);
269 + }
270 +
241 // Update with new details 271 // Update with new details
242 $page->fill($input); 272 $page->fill($input);
243 - $page->slug = $this->findSuitableSlug($page->name, $book_id, $page->id);
244 $page->html = $this->formatHtml($input['html']); 273 $page->html = $this->formatHtml($input['html']);
245 $page->text = strip_tags($page->html); 274 $page->text = strip_tags($page->html);
246 $page->updated_by = auth()->user()->id; 275 $page->updated_by = auth()->user()->id;
...@@ -276,6 +305,8 @@ class PageRepo ...@@ -276,6 +305,8 @@ class PageRepo
276 { 305 {
277 $revision = $this->pageRevision->fill($page->toArray()); 306 $revision = $this->pageRevision->fill($page->toArray());
278 $revision->page_id = $page->id; 307 $revision->page_id = $page->id;
308 + $revision->slug = $page->slug;
309 + $revision->book_slug = $page->book->slug;
279 $revision->created_by = auth()->user()->id; 310 $revision->created_by = auth()->user()->id;
280 $revision->created_at = $page->updated_at; 311 $revision->created_at = $page->updated_at;
281 $revision->save(); 312 $revision->save();
...@@ -358,5 +389,22 @@ class PageRepo ...@@ -358,5 +389,22 @@ class PageRepo
358 $page->delete(); 389 $page->delete();
359 } 390 }
360 391
392 + /**
393 + * Get the latest pages added to the system.
394 + * @param $count
395 + */
396 + public function getRecentlyCreatedPaginated($count = 20)
397 + {
398 + return $this->page->orderBy('created_at', 'desc')->paginate($count);
399 + }
400 +
401 + /**
402 + * Get the latest pages added to the system.
403 + * @param $count
404 + */
405 + public function getRecentlyUpdatedPaginated($count = 20)
406 + {
407 + return $this->page->orderBy('updated_at', 'desc')->paginate($count);
408 + }
361 409
362 -}
...\ No newline at end of file ...\ No newline at end of file
410 +}
......
1 <?php namespace BookStack\Repos; 1 <?php namespace BookStack\Repos;
2 2
3 -
4 use BookStack\Role; 3 use BookStack\Role;
5 use BookStack\User; 4 use BookStack\User;
6 use Setting; 5 use Setting;
...@@ -10,15 +9,19 @@ class UserRepo ...@@ -10,15 +9,19 @@ class UserRepo
10 9
11 protected $user; 10 protected $user;
12 protected $role; 11 protected $role;
12 + protected $entityRepo;
13 13
14 /** 14 /**
15 * UserRepo constructor. 15 * UserRepo constructor.
16 - * @param $user 16 + * @param User $user
17 + * @param Role $role
18 + * @param EntityRepo $entityRepo
17 */ 19 */
18 - public function __construct(User $user, Role $role) 20 + public function __construct(User $user, Role $role, EntityRepo $entityRepo)
19 { 21 {
20 $this->user = $user; 22 $this->user = $user;
21 $this->role = $role; 23 $this->role = $role;
24 + $this->entityRepo = $entityRepo;
22 } 25 }
23 26
24 /** 27 /**
...@@ -112,4 +115,49 @@ class UserRepo ...@@ -112,4 +115,49 @@ class UserRepo
112 $user->socialAccounts()->delete(); 115 $user->socialAccounts()->delete();
113 $user->delete(); 116 $user->delete();
114 } 117 }
118 +
119 + /**
120 + * Get the latest activity for a user.
121 + * @param User $user
122 + * @param int $count
123 + * @param int $page
124 + * @return array
125 + */
126 + public function getActivity(User $user, $count = 20, $page = 0)
127 + {
128 + return \Activity::userActivity($user, $count, $page);
129 + }
130 +
131 + /**
132 + * Get the recently created content for this given user.
133 + * @param User $user
134 + * @param int $count
135 + * @return mixed
136 + */
137 + public function getRecentlyCreated(User $user, $count = 20)
138 + {
139 + return [
140 + 'pages' => $this->entityRepo->page->where('created_by', '=', $user->id)->orderBy('created_at', 'desc')
141 + ->take($count)->get(),
142 + 'chapters' => $this->entityRepo->chapter->where('created_by', '=', $user->id)->orderBy('created_at', 'desc')
143 + ->take($count)->get(),
144 + 'books' => $this->entityRepo->book->where('created_by', '=', $user->id)->orderBy('created_at', 'desc')
145 + ->take($count)->get()
146 + ];
147 + }
148 +
149 + /**
150 + * Get asset created counts for the give user.
151 + * @param User $user
152 + * @return array
153 + */
154 + public function getAssetCounts(User $user)
155 + {
156 + return [
157 + 'pages' => $this->entityRepo->page->where('created_by', '=', $user->id)->count(),
158 + 'chapters' => $this->entityRepo->chapter->where('created_by', '=', $user->id)->count(),
159 + 'books' => $this->entityRepo->book->where('created_by', '=', $user->id)->count(),
160 + ];
161 + }
162 +
115 } 163 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -29,18 +29,19 @@ class ActivityService ...@@ -29,18 +29,19 @@ class ActivityService
29 */ 29 */
30 public function add(Entity $entity, $activityKey, $bookId = 0, $extra = false) 30 public function add(Entity $entity, $activityKey, $bookId = 0, $extra = false)
31 { 31 {
32 - $this->activity->user_id = $this->user->id; 32 + $activity = $this->activity->newInstance();
33 - $this->activity->book_id = $bookId; 33 + $activity->user_id = $this->user->id;
34 - $this->activity->key = strtolower($activityKey); 34 + $activity->book_id = $bookId;
35 + $activity->key = strtolower($activityKey);
35 if ($extra !== false) { 36 if ($extra !== false) {
36 - $this->activity->extra = $extra; 37 + $activity->extra = $extra;
37 } 38 }
38 - $entity->activity()->save($this->activity); 39 + $entity->activity()->save($activity);
39 $this->setNotification($activityKey); 40 $this->setNotification($activityKey);
40 } 41 }
41 42
42 /** 43 /**
43 - * Adds a activity history with a message & without binding to a entitiy. 44 + * Adds a activity history with a message & without binding to a entity.
44 * @param $activityKey 45 * @param $activityKey
45 * @param int $bookId 46 * @param int $bookId
46 * @param bool|false $extra 47 * @param bool|false $extra
...@@ -91,14 +92,14 @@ class ActivityService ...@@ -91,14 +92,14 @@ class ActivityService
91 } 92 }
92 93
93 /** 94 /**
94 - * Gets the latest activity for an entitiy, Filtering out similar 95 + * Gets the latest activity for an entity, Filtering out similar
95 * items to prevent a message activity list. 96 * items to prevent a message activity list.
96 * @param Entity $entity 97 * @param Entity $entity
97 * @param int $count 98 * @param int $count
98 * @param int $page 99 * @param int $page
99 * @return array 100 * @return array
100 */ 101 */
101 - function entityActivity($entity, $count = 20, $page = 0) 102 + public function entityActivity($entity, $count = 20, $page = 0)
102 { 103 {
103 $activity = $entity->hasMany('BookStack\Activity')->orderBy('created_at', 'desc') 104 $activity = $entity->hasMany('BookStack\Activity')->orderBy('created_at', 'desc')
104 ->skip($count * $page)->take($count)->get(); 105 ->skip($count * $page)->take($count)->get();
...@@ -107,15 +108,30 @@ class ActivityService ...@@ -107,15 +108,30 @@ class ActivityService
107 } 108 }
108 109
109 /** 110 /**
111 + * Get latest activity for a user, Filtering out similar
112 + * items.
113 + * @param $user
114 + * @param int $count
115 + * @param int $page
116 + * @return array
117 + */
118 + public function userActivity($user, $count = 20, $page = 0)
119 + {
120 + $activity = $this->activity->where('user_id', '=', $user->id)
121 + ->orderBy('created_at', 'desc')->skip($count * $page)->take($count)->get();
122 + return $this->filterSimilar($activity);
123 + }
124 +
125 + /**
110 * Filters out similar activity. 126 * Filters out similar activity.
111 - * @param Activity[] $activity 127 + * @param Activity[] $activities
112 * @return array 128 * @return array
113 */ 129 */
114 - protected function filterSimilar($activity) 130 + protected function filterSimilar($activities)
115 { 131 {
116 $newActivity = []; 132 $newActivity = [];
117 $previousItem = false; 133 $previousItem = false;
118 - foreach ($activity as $activityItem) { 134 + foreach ($activities as $activityItem) {
119 if ($previousItem === false) { 135 if ($previousItem === false) {
120 $previousItem = $activityItem; 136 $previousItem = $activityItem;
121 $newActivity[] = $activityItem; 137 $newActivity[] = $activityItem;
......
...@@ -164,6 +164,6 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon ...@@ -164,6 +164,6 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
164 */ 164 */
165 public function getEditUrl() 165 public function getEditUrl()
166 { 166 {
167 - return '/users/' . $this->id; 167 + return '/settings/users/' . $this->id;
168 } 168 }
169 } 169 }
......
...@@ -9,16 +9,16 @@ ...@@ -9,16 +9,16 @@
9 "packages": [ 9 "packages": [
10 { 10 {
11 "name": "aws/aws-sdk-php", 11 "name": "aws/aws-sdk-php",
12 - "version": "3.14.2", 12 + "version": "3.15.1",
13 "source": { 13 "source": {
14 "type": "git", 14 "type": "git",
15 "url": "https://github.com/aws/aws-sdk-php.git", 15 "url": "https://github.com/aws/aws-sdk-php.git",
16 - "reference": "2970cb63e7b7b37dd8c07a4fa4e4e18a110ed4e2" 16 + "reference": "5e6078913293576de969703481994b77c380ca30"
17 }, 17 },
18 "dist": { 18 "dist": {
19 "type": "zip", 19 "type": "zip",
20 - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/2970cb63e7b7b37dd8c07a4fa4e4e18a110ed4e2", 20 + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/5e6078913293576de969703481994b77c380ca30",
21 - "reference": "2970cb63e7b7b37dd8c07a4fa4e4e18a110ed4e2", 21 + "reference": "5e6078913293576de969703481994b77c380ca30",
22 "shasum": "" 22 "shasum": ""
23 }, 23 },
24 "require": { 24 "require": {
...@@ -40,7 +40,8 @@ ...@@ -40,7 +40,8 @@
40 "ext-simplexml": "*", 40 "ext-simplexml": "*",
41 "ext-spl": "*", 41 "ext-spl": "*",
42 "nette/neon": "^2.3", 42 "nette/neon": "^2.3",
43 - "phpunit/phpunit": "~4.0|~5.0" 43 + "phpunit/phpunit": "~4.0|~5.0",
44 + "psr/cache": "^1.0"
44 }, 45 },
45 "suggest": { 46 "suggest": {
46 "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", 47 "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications",
...@@ -84,7 +85,7 @@ ...@@ -84,7 +85,7 @@
84 "s3", 85 "s3",
85 "sdk" 86 "sdk"
86 ], 87 ],
87 - "time": "2016-01-28 21:33:18" 88 + "time": "2016-02-11 23:23:31"
88 }, 89 },
89 { 90 {
90 "name": "barryvdh/laravel-debugbar", 91 "name": "barryvdh/laravel-debugbar",
...@@ -918,16 +919,16 @@ ...@@ -918,16 +919,16 @@
918 }, 919 },
919 { 920 {
920 "name": "laravel/framework", 921 "name": "laravel/framework",
921 - "version": "v5.2.12", 922 + "version": "v5.2.16",
922 "source": { 923 "source": {
923 "type": "git", 924 "type": "git",
924 "url": "https://github.com/laravel/framework.git", 925 "url": "https://github.com/laravel/framework.git",
925 - "reference": "6b6255ad7bfbdb721b8d00b09d52b146c5d363d7" 926 + "reference": "39e89553c124dce266da03ee3c0260bdd62f1848"
926 }, 927 },
927 "dist": { 928 "dist": {
928 "type": "zip", 929 "type": "zip",
929 - "url": "https://api.github.com/repos/laravel/framework/zipball/6b6255ad7bfbdb721b8d00b09d52b146c5d363d7", 930 + "url": "https://api.github.com/repos/laravel/framework/zipball/39e89553c124dce266da03ee3c0260bdd62f1848",
930 - "reference": "6b6255ad7bfbdb721b8d00b09d52b146c5d363d7", 931 + "reference": "39e89553c124dce266da03ee3c0260bdd62f1848",
931 "shasum": "" 932 "shasum": ""
932 }, 933 },
933 "require": { 934 "require": {
...@@ -1042,7 +1043,7 @@ ...@@ -1042,7 +1043,7 @@
1042 "framework", 1043 "framework",
1043 "laravel" 1044 "laravel"
1044 ], 1045 ],
1045 - "time": "2016-01-26 04:15:37" 1046 + "time": "2016-02-15 17:46:58"
1046 }, 1047 },
1047 { 1048 {
1048 "name": "laravel/socialite", 1049 "name": "laravel/socialite",
...@@ -1629,16 +1630,16 @@ ...@@ -1629,16 +1630,16 @@
1629 }, 1630 },
1630 { 1631 {
1631 "name": "paragonie/random_compat", 1632 "name": "paragonie/random_compat",
1632 - "version": "1.1.6", 1633 + "version": "v1.2.0",
1633 "source": { 1634 "source": {
1634 "type": "git", 1635 "type": "git",
1635 "url": "https://github.com/paragonie/random_compat.git", 1636 "url": "https://github.com/paragonie/random_compat.git",
1636 - "reference": "e6f80ab77885151908d0ec743689ca700886e8b0" 1637 + "reference": "b0e69d10852716b2ccbdff69c75c477637220790"
1637 }, 1638 },
1638 "dist": { 1639 "dist": {
1639 "type": "zip", 1640 "type": "zip",
1640 - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/e6f80ab77885151908d0ec743689ca700886e8b0", 1641 + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/b0e69d10852716b2ccbdff69c75c477637220790",
1641 - "reference": "e6f80ab77885151908d0ec743689ca700886e8b0", 1642 + "reference": "b0e69d10852716b2ccbdff69c75c477637220790",
1642 "shasum": "" 1643 "shasum": ""
1643 }, 1644 },
1644 "require": { 1645 "require": {
...@@ -1673,7 +1674,7 @@ ...@@ -1673,7 +1674,7 @@
1673 "pseudorandom", 1674 "pseudorandom",
1674 "random" 1675 "random"
1675 ], 1676 ],
1676 - "time": "2016-01-29 16:19:52" 1677 + "time": "2016-02-06 03:52:05"
1677 }, 1678 },
1678 { 1679 {
1679 "name": "phenx/php-font-lib", 1680 "name": "phenx/php-font-lib",
...@@ -2024,16 +2025,16 @@ ...@@ -2024,16 +2025,16 @@
2024 }, 2025 },
2025 { 2026 {
2026 "name": "symfony/console", 2027 "name": "symfony/console",
2027 - "version": "v3.0.1", 2028 + "version": "v3.0.2",
2028 "source": { 2029 "source": {
2029 "type": "git", 2030 "type": "git",
2030 "url": "https://github.com/symfony/console.git", 2031 "url": "https://github.com/symfony/console.git",
2031 - "reference": "ebcdc507829df915f4ca23067bd59ee4ef61f6c3" 2032 + "reference": "5a02eaadaa285e2bb727eb6bbdfb8201fcd971b0"
2032 }, 2033 },
2033 "dist": { 2034 "dist": {
2034 "type": "zip", 2035 "type": "zip",
2035 - "url": "https://api.github.com/repos/symfony/console/zipball/ebcdc507829df915f4ca23067bd59ee4ef61f6c3", 2036 + "url": "https://api.github.com/repos/symfony/console/zipball/5a02eaadaa285e2bb727eb6bbdfb8201fcd971b0",
2036 - "reference": "ebcdc507829df915f4ca23067bd59ee4ef61f6c3", 2037 + "reference": "5a02eaadaa285e2bb727eb6bbdfb8201fcd971b0",
2037 "shasum": "" 2038 "shasum": ""
2038 }, 2039 },
2039 "require": { 2040 "require": {
...@@ -2080,20 +2081,20 @@ ...@@ -2080,20 +2081,20 @@
2080 ], 2081 ],
2081 "description": "Symfony Console Component", 2082 "description": "Symfony Console Component",
2082 "homepage": "https://symfony.com", 2083 "homepage": "https://symfony.com",
2083 - "time": "2015-12-22 10:39:06" 2084 + "time": "2016-02-02 13:44:19"
2084 }, 2085 },
2085 { 2086 {
2086 "name": "symfony/debug", 2087 "name": "symfony/debug",
2087 - "version": "v3.0.1", 2088 + "version": "v3.0.2",
2088 "source": { 2089 "source": {
2089 "type": "git", 2090 "type": "git",
2090 "url": "https://github.com/symfony/debug.git", 2091 "url": "https://github.com/symfony/debug.git",
2091 - "reference": "73612266ac709769effdbfc0762e5b07cfd2ac2a" 2092 + "reference": "29606049ced1ec715475f88d1bbe587252a3476e"
2092 }, 2093 },
2093 "dist": { 2094 "dist": {
2094 "type": "zip", 2095 "type": "zip",
2095 - "url": "https://api.github.com/repos/symfony/debug/zipball/73612266ac709769effdbfc0762e5b07cfd2ac2a", 2096 + "url": "https://api.github.com/repos/symfony/debug/zipball/29606049ced1ec715475f88d1bbe587252a3476e",
2096 - "reference": "73612266ac709769effdbfc0762e5b07cfd2ac2a", 2097 + "reference": "29606049ced1ec715475f88d1bbe587252a3476e",
2097 "shasum": "" 2098 "shasum": ""
2098 }, 2099 },
2099 "require": { 2100 "require": {
...@@ -2137,20 +2138,20 @@ ...@@ -2137,20 +2138,20 @@
2137 ], 2138 ],
2138 "description": "Symfony Debug Component", 2139 "description": "Symfony Debug Component",
2139 "homepage": "https://symfony.com", 2140 "homepage": "https://symfony.com",
2140 - "time": "2015-12-26 13:39:53" 2141 + "time": "2016-01-27 05:14:46"
2141 }, 2142 },
2142 { 2143 {
2143 "name": "symfony/event-dispatcher", 2144 "name": "symfony/event-dispatcher",
2144 - "version": "v3.0.1", 2145 + "version": "v3.0.2",
2145 "source": { 2146 "source": {
2146 "type": "git", 2147 "type": "git",
2147 "url": "https://github.com/symfony/event-dispatcher.git", 2148 "url": "https://github.com/symfony/event-dispatcher.git",
2148 - "reference": "d36355e026905fa5229e1ed7b4e9eda2e67adfcf" 2149 + "reference": "4dd5df31a28c0f82b41cb1e1599b74b5dcdbdafa"
2149 }, 2150 },
2150 "dist": { 2151 "dist": {
2151 "type": "zip", 2152 "type": "zip",
2152 - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d36355e026905fa5229e1ed7b4e9eda2e67adfcf", 2153 + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/4dd5df31a28c0f82b41cb1e1599b74b5dcdbdafa",
2153 - "reference": "d36355e026905fa5229e1ed7b4e9eda2e67adfcf", 2154 + "reference": "4dd5df31a28c0f82b41cb1e1599b74b5dcdbdafa",
2154 "shasum": "" 2155 "shasum": ""
2155 }, 2156 },
2156 "require": { 2157 "require": {
...@@ -2197,20 +2198,20 @@ ...@@ -2197,20 +2198,20 @@
2197 ], 2198 ],
2198 "description": "Symfony EventDispatcher Component", 2199 "description": "Symfony EventDispatcher Component",
2199 "homepage": "https://symfony.com", 2200 "homepage": "https://symfony.com",
2200 - "time": "2015-10-30 23:35:59" 2201 + "time": "2016-01-27 05:14:46"
2201 }, 2202 },
2202 { 2203 {
2203 "name": "symfony/finder", 2204 "name": "symfony/finder",
2204 - "version": "v3.0.1", 2205 + "version": "v3.0.2",
2205 "source": { 2206 "source": {
2206 "type": "git", 2207 "type": "git",
2207 "url": "https://github.com/symfony/finder.git", 2208 "url": "https://github.com/symfony/finder.git",
2208 - "reference": "8617895eb798b6bdb338321ce19453dc113e5675" 2209 + "reference": "623bda0abd9aa29e529c8e9c08b3b84171914723"
2209 }, 2210 },
2210 "dist": { 2211 "dist": {
2211 "type": "zip", 2212 "type": "zip",
2212 - "url": "https://api.github.com/repos/symfony/finder/zipball/8617895eb798b6bdb338321ce19453dc113e5675", 2213 + "url": "https://api.github.com/repos/symfony/finder/zipball/623bda0abd9aa29e529c8e9c08b3b84171914723",
2213 - "reference": "8617895eb798b6bdb338321ce19453dc113e5675", 2214 + "reference": "623bda0abd9aa29e529c8e9c08b3b84171914723",
2214 "shasum": "" 2215 "shasum": ""
2215 }, 2216 },
2216 "require": { 2217 "require": {
...@@ -2246,20 +2247,20 @@ ...@@ -2246,20 +2247,20 @@
2246 ], 2247 ],
2247 "description": "Symfony Finder Component", 2248 "description": "Symfony Finder Component",
2248 "homepage": "https://symfony.com", 2249 "homepage": "https://symfony.com",
2249 - "time": "2015-12-05 11:13:14" 2250 + "time": "2016-01-27 05:14:46"
2250 }, 2251 },
2251 { 2252 {
2252 "name": "symfony/http-foundation", 2253 "name": "symfony/http-foundation",
2253 - "version": "v3.0.1", 2254 + "version": "v3.0.2",
2254 "source": { 2255 "source": {
2255 "type": "git", 2256 "type": "git",
2256 "url": "https://github.com/symfony/http-foundation.git", 2257 "url": "https://github.com/symfony/http-foundation.git",
2257 - "reference": "939c8c28a5b1e4ab7317bc30c1f9aa881c4b06b5" 2258 + "reference": "9344a87ceedfc50354a39653e54257ee9aa6a77d"
2258 }, 2259 },
2259 "dist": { 2260 "dist": {
2260 "type": "zip", 2261 "type": "zip",
2261 - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/939c8c28a5b1e4ab7317bc30c1f9aa881c4b06b5", 2262 + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/9344a87ceedfc50354a39653e54257ee9aa6a77d",
2262 - "reference": "939c8c28a5b1e4ab7317bc30c1f9aa881c4b06b5", 2263 + "reference": "9344a87ceedfc50354a39653e54257ee9aa6a77d",
2263 "shasum": "" 2264 "shasum": ""
2264 }, 2265 },
2265 "require": { 2266 "require": {
...@@ -2298,20 +2299,20 @@ ...@@ -2298,20 +2299,20 @@
2298 ], 2299 ],
2299 "description": "Symfony HttpFoundation Component", 2300 "description": "Symfony HttpFoundation Component",
2300 "homepage": "https://symfony.com", 2301 "homepage": "https://symfony.com",
2301 - "time": "2015-12-18 15:43:53" 2302 + "time": "2016-02-02 13:44:19"
2302 }, 2303 },
2303 { 2304 {
2304 "name": "symfony/http-kernel", 2305 "name": "symfony/http-kernel",
2305 - "version": "v3.0.1", 2306 + "version": "v3.0.2",
2306 "source": { 2307 "source": {
2307 "type": "git", 2308 "type": "git",
2308 "url": "https://github.com/symfony/http-kernel.git", 2309 "url": "https://github.com/symfony/http-kernel.git",
2309 - "reference": "f7933e9f19e26e7baba7ec04735b466fedd3a6db" 2310 + "reference": "cec02604450481ac26710ca4249cc61b57b23942"
2310 }, 2311 },
2311 "dist": { 2312 "dist": {
2312 "type": "zip", 2313 "type": "zip",
2313 - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/f7933e9f19e26e7baba7ec04735b466fedd3a6db", 2314 + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/cec02604450481ac26710ca4249cc61b57b23942",
2314 - "reference": "f7933e9f19e26e7baba7ec04735b466fedd3a6db", 2315 + "reference": "cec02604450481ac26710ca4249cc61b57b23942",
2315 "shasum": "" 2316 "shasum": ""
2316 }, 2317 },
2317 "require": { 2318 "require": {
...@@ -2380,7 +2381,7 @@ ...@@ -2380,7 +2381,7 @@
2380 ], 2381 ],
2381 "description": "Symfony HttpKernel Component", 2382 "description": "Symfony HttpKernel Component",
2382 "homepage": "https://symfony.com", 2383 "homepage": "https://symfony.com",
2383 - "time": "2015-12-26 16:46:13" 2384 + "time": "2016-02-03 12:38:44"
2384 }, 2385 },
2385 { 2386 {
2386 "name": "symfony/polyfill-mbstring", 2387 "name": "symfony/polyfill-mbstring",
...@@ -2551,16 +2552,16 @@ ...@@ -2551,16 +2552,16 @@
2551 }, 2552 },
2552 { 2553 {
2553 "name": "symfony/process", 2554 "name": "symfony/process",
2554 - "version": "v3.0.1", 2555 + "version": "v3.0.2",
2555 "source": { 2556 "source": {
2556 "type": "git", 2557 "type": "git",
2557 "url": "https://github.com/symfony/process.git", 2558 "url": "https://github.com/symfony/process.git",
2558 - "reference": "f4794f1d00f0746621be3020ffbd8c5e0b217ee3" 2559 + "reference": "dfecef47506179db2501430e732adbf3793099c8"
2559 }, 2560 },
2560 "dist": { 2561 "dist": {
2561 "type": "zip", 2562 "type": "zip",
2562 - "url": "https://api.github.com/repos/symfony/process/zipball/f4794f1d00f0746621be3020ffbd8c5e0b217ee3", 2563 + "url": "https://api.github.com/repos/symfony/process/zipball/dfecef47506179db2501430e732adbf3793099c8",
2563 - "reference": "f4794f1d00f0746621be3020ffbd8c5e0b217ee3", 2564 + "reference": "dfecef47506179db2501430e732adbf3793099c8",
2564 "shasum": "" 2565 "shasum": ""
2565 }, 2566 },
2566 "require": { 2567 "require": {
...@@ -2596,20 +2597,20 @@ ...@@ -2596,20 +2597,20 @@
2596 ], 2597 ],
2597 "description": "Symfony Process Component", 2598 "description": "Symfony Process Component",
2598 "homepage": "https://symfony.com", 2599 "homepage": "https://symfony.com",
2599 - "time": "2015-12-23 11:04:02" 2600 + "time": "2016-02-02 13:44:19"
2600 }, 2601 },
2601 { 2602 {
2602 "name": "symfony/routing", 2603 "name": "symfony/routing",
2603 - "version": "v3.0.1", 2604 + "version": "v3.0.2",
2604 "source": { 2605 "source": {
2605 "type": "git", 2606 "type": "git",
2606 "url": "https://github.com/symfony/routing.git", 2607 "url": "https://github.com/symfony/routing.git",
2607 - "reference": "3b1bac52f42cb0f54df1a2dbabd55a1d214e2a59" 2608 + "reference": "4686baa55a835e1c1ede9b86ba02415c8c8d6166"
2608 }, 2609 },
2609 "dist": { 2610 "dist": {
2610 "type": "zip", 2611 "type": "zip",
2611 - "url": "https://api.github.com/repos/symfony/routing/zipball/3b1bac52f42cb0f54df1a2dbabd55a1d214e2a59", 2612 + "url": "https://api.github.com/repos/symfony/routing/zipball/4686baa55a835e1c1ede9b86ba02415c8c8d6166",
2612 - "reference": "3b1bac52f42cb0f54df1a2dbabd55a1d214e2a59", 2613 + "reference": "4686baa55a835e1c1ede9b86ba02415c8c8d6166",
2613 "shasum": "" 2614 "shasum": ""
2614 }, 2615 },
2615 "require": { 2616 "require": {
...@@ -2670,20 +2671,20 @@ ...@@ -2670,20 +2671,20 @@
2670 "uri", 2671 "uri",
2671 "url" 2672 "url"
2672 ], 2673 ],
2673 - "time": "2015-12-23 08:00:11" 2674 + "time": "2016-01-27 05:14:46"
2674 }, 2675 },
2675 { 2676 {
2676 "name": "symfony/translation", 2677 "name": "symfony/translation",
2677 - "version": "v3.0.1", 2678 + "version": "v3.0.2",
2678 "source": { 2679 "source": {
2679 "type": "git", 2680 "type": "git",
2680 "url": "https://github.com/symfony/translation.git", 2681 "url": "https://github.com/symfony/translation.git",
2681 - "reference": "dff0867826a7068d673801b7522f8e2634016ef9" 2682 + "reference": "2de0b6f7ebe43cffd8a06996ebec6aab79ea9e91"
2682 }, 2683 },
2683 "dist": { 2684 "dist": {
2684 "type": "zip", 2685 "type": "zip",
2685 - "url": "https://api.github.com/repos/symfony/translation/zipball/dff0867826a7068d673801b7522f8e2634016ef9", 2686 + "url": "https://api.github.com/repos/symfony/translation/zipball/2de0b6f7ebe43cffd8a06996ebec6aab79ea9e91",
2686 - "reference": "dff0867826a7068d673801b7522f8e2634016ef9", 2687 + "reference": "2de0b6f7ebe43cffd8a06996ebec6aab79ea9e91",
2687 "shasum": "" 2688 "shasum": ""
2688 }, 2689 },
2689 "require": { 2690 "require": {
...@@ -2734,20 +2735,20 @@ ...@@ -2734,20 +2735,20 @@
2734 ], 2735 ],
2735 "description": "Symfony Translation Component", 2736 "description": "Symfony Translation Component",
2736 "homepage": "https://symfony.com", 2737 "homepage": "https://symfony.com",
2737 - "time": "2015-12-05 17:45:07" 2738 + "time": "2016-02-02 13:44:19"
2738 }, 2739 },
2739 { 2740 {
2740 "name": "symfony/var-dumper", 2741 "name": "symfony/var-dumper",
2741 - "version": "v3.0.1", 2742 + "version": "v3.0.2",
2742 "source": { 2743 "source": {
2743 "type": "git", 2744 "type": "git",
2744 "url": "https://github.com/symfony/var-dumper.git", 2745 "url": "https://github.com/symfony/var-dumper.git",
2745 - "reference": "87db8700deb12ba2b65e858f656a1f885530bcb0" 2746 + "reference": "24bb94807eff00db49374c37ebf56a0304e8aef3"
2746 }, 2747 },
2747 "dist": { 2748 "dist": {
2748 "type": "zip", 2749 "type": "zip",
2749 - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/87db8700deb12ba2b65e858f656a1f885530bcb0", 2750 + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/24bb94807eff00db49374c37ebf56a0304e8aef3",
2750 - "reference": "87db8700deb12ba2b65e858f656a1f885530bcb0", 2751 + "reference": "24bb94807eff00db49374c37ebf56a0304e8aef3",
2751 "shasum": "" 2752 "shasum": ""
2752 }, 2753 },
2753 "require": { 2754 "require": {
...@@ -2797,7 +2798,7 @@ ...@@ -2797,7 +2798,7 @@
2797 "debug", 2798 "debug",
2798 "dump" 2799 "dump"
2799 ], 2800 ],
2800 - "time": "2015-12-05 11:13:14" 2801 + "time": "2016-01-07 13:38:51"
2801 }, 2802 },
2802 { 2803 {
2803 "name": "vlucas/phpdotenv", 2804 "name": "vlucas/phpdotenv",
...@@ -3182,22 +3183,24 @@ ...@@ -3182,22 +3183,24 @@
3182 }, 3183 },
3183 { 3184 {
3184 "name": "phpspec/prophecy", 3185 "name": "phpspec/prophecy",
3185 - "version": "v1.5.0", 3186 + "version": "v1.6.0",
3186 "source": { 3187 "source": {
3187 "type": "git", 3188 "type": "git",
3188 "url": "https://github.com/phpspec/prophecy.git", 3189 "url": "https://github.com/phpspec/prophecy.git",
3189 - "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7" 3190 + "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972"
3190 }, 3191 },
3191 "dist": { 3192 "dist": {
3192 "type": "zip", 3193 "type": "zip",
3193 - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4745ded9307786b730d7a60df5cb5a6c43cf95f7", 3194 + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/3c91bdf81797d725b14cb62906f9a4ce44235972",
3194 - "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7", 3195 + "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972",
3195 "shasum": "" 3196 "shasum": ""
3196 }, 3197 },
3197 "require": { 3198 "require": {
3198 "doctrine/instantiator": "^1.0.2", 3199 "doctrine/instantiator": "^1.0.2",
3200 + "php": "^5.3|^7.0",
3199 "phpdocumentor/reflection-docblock": "~2.0", 3201 "phpdocumentor/reflection-docblock": "~2.0",
3200 - "sebastian/comparator": "~1.1" 3202 + "sebastian/comparator": "~1.1",
3203 + "sebastian/recursion-context": "~1.0"
3201 }, 3204 },
3202 "require-dev": { 3205 "require-dev": {
3203 "phpspec/phpspec": "~2.0" 3206 "phpspec/phpspec": "~2.0"
...@@ -3205,7 +3208,7 @@ ...@@ -3205,7 +3208,7 @@
3205 "type": "library", 3208 "type": "library",
3206 "extra": { 3209 "extra": {
3207 "branch-alias": { 3210 "branch-alias": {
3208 - "dev-master": "1.4.x-dev" 3211 + "dev-master": "1.5.x-dev"
3209 } 3212 }
3210 }, 3213 },
3211 "autoload": { 3214 "autoload": {
...@@ -3238,7 +3241,7 @@ ...@@ -3238,7 +3241,7 @@
3238 "spy", 3241 "spy",
3239 "stub" 3242 "stub"
3240 ], 3243 ],
3241 - "time": "2015-08-13 10:07:40" 3244 + "time": "2016-02-15 07:46:21"
3242 }, 3245 },
3243 { 3246 {
3244 "name": "phpunit/php-code-coverage", 3247 "name": "phpunit/php-code-coverage",
...@@ -3482,16 +3485,16 @@ ...@@ -3482,16 +3485,16 @@
3482 }, 3485 },
3483 { 3486 {
3484 "name": "phpunit/phpunit", 3487 "name": "phpunit/phpunit",
3485 - "version": "4.8.21", 3488 + "version": "4.8.23",
3486 "source": { 3489 "source": {
3487 "type": "git", 3490 "type": "git",
3488 "url": "https://github.com/sebastianbergmann/phpunit.git", 3491 "url": "https://github.com/sebastianbergmann/phpunit.git",
3489 - "reference": "ea76b17bced0500a28098626b84eda12dbcf119c" 3492 + "reference": "6e351261f9cd33daf205a131a1ba61c6d33bd483"
3490 }, 3493 },
3491 "dist": { 3494 "dist": {
3492 "type": "zip", 3495 "type": "zip",
3493 - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ea76b17bced0500a28098626b84eda12dbcf119c", 3496 + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6e351261f9cd33daf205a131a1ba61c6d33bd483",
3494 - "reference": "ea76b17bced0500a28098626b84eda12dbcf119c", 3497 + "reference": "6e351261f9cd33daf205a131a1ba61c6d33bd483",
3495 "shasum": "" 3498 "shasum": ""
3496 }, 3499 },
3497 "require": { 3500 "require": {
...@@ -3550,7 +3553,7 @@ ...@@ -3550,7 +3553,7 @@
3550 "testing", 3553 "testing",
3551 "xunit" 3554 "xunit"
3552 ], 3555 ],
3553 - "time": "2015-12-12 07:45:58" 3556 + "time": "2016-02-11 14:56:33"
3554 }, 3557 },
3555 { 3558 {
3556 "name": "phpunit/phpunit-mock-objects", 3559 "name": "phpunit/phpunit-mock-objects",
...@@ -3981,16 +3984,16 @@ ...@@ -3981,16 +3984,16 @@
3981 }, 3984 },
3982 { 3985 {
3983 "name": "symfony/css-selector", 3986 "name": "symfony/css-selector",
3984 - "version": "v3.0.1", 3987 + "version": "v3.0.2",
3985 "source": { 3988 "source": {
3986 "type": "git", 3989 "type": "git",
3987 "url": "https://github.com/symfony/css-selector.git", 3990 "url": "https://github.com/symfony/css-selector.git",
3988 - "reference": "4613311fd46e146f506403ce2f8a0c71d402d2a3" 3991 + "reference": "6605602690578496091ac20ec7a5cbd160d4dff4"
3989 }, 3992 },
3990 "dist": { 3993 "dist": {
3991 "type": "zip", 3994 "type": "zip",
3992 - "url": "https://api.github.com/repos/symfony/css-selector/zipball/4613311fd46e146f506403ce2f8a0c71d402d2a3", 3995 + "url": "https://api.github.com/repos/symfony/css-selector/zipball/6605602690578496091ac20ec7a5cbd160d4dff4",
3993 - "reference": "4613311fd46e146f506403ce2f8a0c71d402d2a3", 3996 + "reference": "6605602690578496091ac20ec7a5cbd160d4dff4",
3994 "shasum": "" 3997 "shasum": ""
3995 }, 3998 },
3996 "require": { 3999 "require": {
...@@ -4030,20 +4033,20 @@ ...@@ -4030,20 +4033,20 @@
4030 ], 4033 ],
4031 "description": "Symfony CssSelector Component", 4034 "description": "Symfony CssSelector Component",
4032 "homepage": "https://symfony.com", 4035 "homepage": "https://symfony.com",
4033 - "time": "2015-12-05 17:45:07" 4036 + "time": "2016-01-27 05:14:46"
4034 }, 4037 },
4035 { 4038 {
4036 "name": "symfony/dom-crawler", 4039 "name": "symfony/dom-crawler",
4037 - "version": "v3.0.1", 4040 + "version": "v3.0.2",
4038 "source": { 4041 "source": {
4039 "type": "git", 4042 "type": "git",
4040 "url": "https://github.com/symfony/dom-crawler.git", 4043 "url": "https://github.com/symfony/dom-crawler.git",
4041 - "reference": "7c622b0c9fb8bdb146d6dfa86c5f91dcbfdbc11d" 4044 + "reference": "b693a9650aa004576b593ff2e91ae749dc90123d"
4042 }, 4045 },
4043 "dist": { 4046 "dist": {
4044 "type": "zip", 4047 "type": "zip",
4045 - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/7c622b0c9fb8bdb146d6dfa86c5f91dcbfdbc11d", 4048 + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/b693a9650aa004576b593ff2e91ae749dc90123d",
4046 - "reference": "7c622b0c9fb8bdb146d6dfa86c5f91dcbfdbc11d", 4049 + "reference": "b693a9650aa004576b593ff2e91ae749dc90123d",
4047 "shasum": "" 4050 "shasum": ""
4048 }, 4051 },
4049 "require": { 4052 "require": {
...@@ -4086,20 +4089,20 @@ ...@@ -4086,20 +4089,20 @@
4086 ], 4089 ],
4087 "description": "Symfony DomCrawler Component", 4090 "description": "Symfony DomCrawler Component",
4088 "homepage": "https://symfony.com", 4091 "homepage": "https://symfony.com",
4089 - "time": "2015-12-26 13:42:31" 4092 + "time": "2016-01-25 09:56:57"
4090 }, 4093 },
4091 { 4094 {
4092 "name": "symfony/yaml", 4095 "name": "symfony/yaml",
4093 - "version": "v3.0.1", 4096 + "version": "v3.0.2",
4094 "source": { 4097 "source": {
4095 "type": "git", 4098 "type": "git",
4096 "url": "https://github.com/symfony/yaml.git", 4099 "url": "https://github.com/symfony/yaml.git",
4097 - "reference": "3df409958a646dad2bc5046c3fb671ee24a1a691" 4100 + "reference": "3cf0709d7fe936e97bee9e954382e449003f1d9a"
4098 }, 4101 },
4099 "dist": { 4102 "dist": {
4100 "type": "zip", 4103 "type": "zip",
4101 - "url": "https://api.github.com/repos/symfony/yaml/zipball/3df409958a646dad2bc5046c3fb671ee24a1a691", 4104 + "url": "https://api.github.com/repos/symfony/yaml/zipball/3cf0709d7fe936e97bee9e954382e449003f1d9a",
4102 - "reference": "3df409958a646dad2bc5046c3fb671ee24a1a691", 4105 + "reference": "3cf0709d7fe936e97bee9e954382e449003f1d9a",
4103 "shasum": "" 4106 "shasum": ""
4104 }, 4107 },
4105 "require": { 4108 "require": {
...@@ -4135,7 +4138,7 @@ ...@@ -4135,7 +4138,7 @@
4135 ], 4138 ],
4136 "description": "Symfony Yaml Component", 4139 "description": "Symfony Yaml Component",
4137 "homepage": "https://symfony.com", 4140 "homepage": "https://symfony.com",
4138 - "time": "2015-12-26 13:39:53" 4141 + "time": "2016-02-02 13:44:19"
4139 } 4142 }
4140 ], 4143 ],
4141 "aliases": [], 4144 "aliases": [],
......
...@@ -18,7 +18,7 @@ class CreateUsersTable extends Migration ...@@ -18,7 +18,7 @@ class CreateUsersTable extends Migration
18 $table->string('email')->unique(); 18 $table->string('email')->unique();
19 $table->string('password', 60); 19 $table->string('password', 60);
20 $table->rememberToken(); 20 $table->rememberToken();
21 - $table->timestamps(); 21 + $table->nullableTimestamps();
22 }); 22 });
23 23
24 \BookStack\User::forceCreate([ 24 \BookStack\User::forceCreate([
......
...@@ -17,7 +17,7 @@ class CreateBooksTable extends Migration ...@@ -17,7 +17,7 @@ class CreateBooksTable extends Migration
17 $table->string('name'); 17 $table->string('name');
18 $table->string('slug')->indexed(); 18 $table->string('slug')->indexed();
19 $table->text('description'); 19 $table->text('description');
20 - $table->timestamps(); 20 + $table->nullableTimestamps();
21 }); 21 });
22 } 22 }
23 23
......
...@@ -21,7 +21,7 @@ class CreatePagesTable extends Migration ...@@ -21,7 +21,7 @@ class CreatePagesTable extends Migration
21 $table->longText('html'); 21 $table->longText('html');
22 $table->longText('text'); 22 $table->longText('text');
23 $table->integer('priority'); 23 $table->integer('priority');
24 - $table->timestamps(); 24 + $table->nullableTimestamps();
25 }); 25 });
26 } 26 }
27 27
......
...@@ -16,7 +16,7 @@ class CreateImagesTable extends Migration ...@@ -16,7 +16,7 @@ class CreateImagesTable extends Migration
16 $table->increments('id'); 16 $table->increments('id');
17 $table->string('name'); 17 $table->string('name');
18 $table->string('url'); 18 $table->string('url');
19 - $table->timestamps(); 19 + $table->nullableTimestamps();
20 }); 20 });
21 } 21 }
22 22
......
...@@ -19,7 +19,7 @@ class CreateChaptersTable extends Migration ...@@ -19,7 +19,7 @@ class CreateChaptersTable extends Migration
19 $table->text('name'); 19 $table->text('name');
20 $table->text('description'); 20 $table->text('description');
21 $table->integer('priority'); 21 $table->integer('priority');
22 - $table->timestamps(); 22 + $table->nullableTimestamps();
23 }); 23 });
24 } 24 }
25 25
......
...@@ -19,7 +19,7 @@ class CreatePageRevisionsTable extends Migration ...@@ -19,7 +19,7 @@ class CreatePageRevisionsTable extends Migration
19 $table->longText('html'); 19 $table->longText('html');
20 $table->longText('text'); 20 $table->longText('text');
21 $table->integer('created_by'); 21 $table->integer('created_by');
22 - $table->timestamps(); 22 + $table->nullableTimestamps();
23 }); 23 });
24 } 24 }
25 25
......
...@@ -20,7 +20,7 @@ class CreateActivitiesTable extends Migration ...@@ -20,7 +20,7 @@ class CreateActivitiesTable extends Migration
20 $table->integer('user_id'); 20 $table->integer('user_id');
21 $table->integer('entity_id'); 21 $table->integer('entity_id');
22 $table->string('entity_type'); 22 $table->string('entity_type');
23 - $table->timestamps(); 23 + $table->nullableTimestamps();
24 }); 24 });
25 } 25 }
26 26
......
...@@ -28,7 +28,7 @@ class AddRolesAndPermissions extends Migration ...@@ -28,7 +28,7 @@ class AddRolesAndPermissions extends Migration
28 $table->string('name')->unique(); 28 $table->string('name')->unique();
29 $table->string('display_name')->nullable(); 29 $table->string('display_name')->nullable();
30 $table->string('description')->nullable(); 30 $table->string('description')->nullable();
31 - $table->timestamps(); 31 + $table->nullableTimestamps();
32 }); 32 });
33 33
34 // Create table for associating roles to users (Many-to-Many) 34 // Create table for associating roles to users (Many-to-Many)
...@@ -50,7 +50,7 @@ class AddRolesAndPermissions extends Migration ...@@ -50,7 +50,7 @@ class AddRolesAndPermissions extends Migration
50 $table->string('name')->unique(); 50 $table->string('name')->unique();
51 $table->string('display_name')->nullable(); 51 $table->string('display_name')->nullable();
52 $table->string('description')->nullable(); 52 $table->string('description')->nullable();
53 - $table->timestamps(); 53 + $table->nullableTimestamps();
54 }); 54 });
55 55
56 // Create table for associating permissions to roles (Many-to-Many) 56 // Create table for associating permissions to roles (Many-to-Many)
......
...@@ -15,7 +15,7 @@ class CreateSettingsTable extends Migration ...@@ -15,7 +15,7 @@ class CreateSettingsTable extends Migration
15 Schema::create('settings', function (Blueprint $table) { 15 Schema::create('settings', function (Blueprint $table) {
16 $table->string('setting_key')->primary()->indexed(); 16 $table->string('setting_key')->primary()->indexed();
17 $table->text('value'); 17 $table->text('value');
18 - $table->timestamps(); 18 + $table->nullableTimestamps();
19 }); 19 });
20 } 20 }
21 21
......
...@@ -18,7 +18,7 @@ class CreateSocialAccountsTable extends Migration ...@@ -18,7 +18,7 @@ class CreateSocialAccountsTable extends Migration
18 $table->string('driver')->index(); 18 $table->string('driver')->index();
19 $table->string('driver_id'); 19 $table->string('driver_id');
20 $table->string('avatar'); 20 $table->string('avatar');
21 - $table->timestamps(); 21 + $table->nullableTimestamps();
22 }); 22 });
23 } 23 }
24 24
......
...@@ -20,7 +20,7 @@ class AddEmailConfirmationTable extends Migration ...@@ -20,7 +20,7 @@ class AddEmailConfirmationTable extends Migration
20 $table->increments('id'); 20 $table->increments('id');
21 $table->integer('user_id')->index(); 21 $table->integer('user_id')->index();
22 $table->string('token')->index(); 22 $table->string('token')->index();
23 - $table->timestamps(); 23 + $table->nullableTimestamps();
24 }); 24 });
25 } 25 }
26 26
......
...@@ -18,7 +18,7 @@ class CreateViewsTable extends Migration ...@@ -18,7 +18,7 @@ class CreateViewsTable extends Migration
18 $table->integer('viewable_id'); 18 $table->integer('viewable_id');
19 $table->string('viewable_type'); 19 $table->string('viewable_type');
20 $table->integer('views'); 20 $table->integer('views');
21 - $table->timestamps(); 21 + $table->nullableTimestamps();
22 }); 22 });
23 } 23 }
24 24
......
1 +<?php
2 +
3 +use Illuminate\Database\Schema\Blueprint;
4 +use Illuminate\Database\Migrations\Migration;
5 +
6 +class AddSlugToRevisions extends Migration
7 +{
8 + /**
9 + * Run the migrations.
10 + *
11 + * @return void
12 + */
13 + public function up()
14 + {
15 + Schema::table('page_revisions', function (Blueprint $table) {
16 + $table->string('slug');
17 + $table->index('slug');
18 + $table->string('book_slug');
19 + $table->index('book_slug');
20 + });
21 + }
22 +
23 + /**
24 + * Reverse the migrations.
25 + *
26 + * @return void
27 + */
28 + public function down()
29 + {
30 + Schema::table('page_revisions', function (Blueprint $table) {
31 + $table->dropColumn('slug');
32 + $table->dropColumn('book_slug');
33 + });
34 + }
35 +}
...@@ -25,8 +25,13 @@ ...@@ -25,8 +25,13 @@
25 <env name="SESSION_DRIVER" value="array"/> 25 <env name="SESSION_DRIVER" value="array"/>
26 <env name="QUEUE_DRIVER" value="sync"/> 26 <env name="QUEUE_DRIVER" value="sync"/>
27 <env name="DB_CONNECTION" value="mysql_testing"/> 27 <env name="DB_CONNECTION" value="mysql_testing"/>
28 - <env name="MAIL_PRETEND" value="true"/> 28 + <env name="MAIL_DRIVER" value="log"/>
29 <env name="AUTH_METHOD" value="standard"/> 29 <env name="AUTH_METHOD" value="standard"/>
30 <env name="DISABLE_EXTERNAL_SERVICES" value="false"/> 30 <env name="DISABLE_EXTERNAL_SERVICES" value="false"/>
31 + <env name="LDAP_VERSION" value="3"/>
32 + <env name="GITHUB_APP_ID" value="aaaaaaaaaaaaaa"/>
33 + <env name="GITHUB_APP_SECRET" value="aaaaaaaaaaaaaa"/>
34 + <env name="GOOGLE_APP_ID" value="aaaaaaaaaaaaaa"/>
35 + <env name="GOOGLE_APP_SECRET" value="aaaaaaaaaaaaaa"/>
31 </php> 36 </php>
32 </phpunit> 37 </phpunit>
......
...@@ -106,6 +106,12 @@ $(function () { ...@@ -106,6 +106,12 @@ $(function () {
106 } 106 }
107 }); 107 });
108 108
109 + // Common jQuery actions
110 + $('[data-action="expand-entity-list-details"]').click(function() {
111 + $('.entity-list.compact').find('p').slideToggle(240);
112 + });
113 +
114 +
109 }); 115 });
110 116
111 117
......
...@@ -139,54 +139,6 @@ form.search-box { ...@@ -139,54 +139,6 @@ form.search-box {
139 height: 43px; 139 height: 43px;
140 } 140 }
141 141
142 -.dropdown-container {
143 - display: inline-block;
144 - vertical-align: top;
145 - position: relative;
146 -}
147 -
148 -.dropdown-container ul {
149 - display: none;
150 - position: absolute;
151 - z-index: 999;
152 - top: 0;
153 - list-style: none;
154 - right: 0;
155 - margin: $-m 0;
156 - background-color: #FFFFFF;
157 - box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.1);
158 - border-radius: 1px;
159 - border: 1px solid #EEE;
160 - min-width: 180px;
161 - padding: $-xs 0;
162 - color: #555;
163 - text-align: left !important;
164 - &.wide {
165 - min-width: 220px;
166 - }
167 - .text-muted {
168 - color: #999;
169 - }
170 - a {
171 - display: block;
172 - padding: $-xs $-m;
173 - color: #555;
174 - &:hover {
175 - text-decoration: none;
176 - background-color: #EEE;
177 - }
178 - i {
179 - margin-right: $-m;
180 - padding-right: 0;
181 - display: inline;
182 - width: 22px;
183 - }
184 - }
185 - li.border-bottom {
186 - border-bottom: 1px solid #DDD;
187 - }
188 -}
189 -
190 .breadcrumbs span.sep { 142 .breadcrumbs span.sep {
191 color: #aaa; 143 color: #aaa;
192 padding: 0 $-xs; 144 padding: 0 $-xs;
......
...@@ -283,4 +283,87 @@ ul.pagination { ...@@ -283,4 +283,87 @@ ul.pagination {
283 a { 283 a {
284 color: $primary; 284 color: $primary;
285 } 285 }
286 -}
...\ No newline at end of file ...\ No newline at end of file
286 +}
287 +
288 +.entity-list {
289 + >div {
290 + padding: $-m 0;
291 + }
292 + h3 {
293 + margin: 0;
294 + }
295 + p {
296 + margin: $-xs 0 0 0;
297 + }
298 + hr {
299 + margin: 0;
300 + }
301 + .text-small.text-muted {
302 + color: #AAA;
303 + font-size: 0.75em;
304 + margin-top: $-xs;
305 + }
306 +}
307 +.entity-list.compact {
308 + font-size: 0.6em;
309 + h3, a {
310 + line-height: 1.2;
311 + }
312 + p {
313 + display: none;
314 + font-size: $fs-m * 0.8;
315 + padding-top: $-xs;
316 + margin: 0;
317 + }
318 + hr {
319 + margin: 0;
320 + }
321 +}
322 +
323 +.dropdown-container {
324 + display: inline-block;
325 + vertical-align: top;
326 + position: relative;
327 +}
328 +
329 +.dropdown-container ul {
330 + display: none;
331 + position: absolute;
332 + z-index: 999;
333 + top: 0;
334 + list-style: none;
335 + right: 0;
336 + margin: $-m 0;
337 + background-color: #FFFFFF;
338 + box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.1);
339 + border-radius: 1px;
340 + border: 1px solid #EEE;
341 + min-width: 180px;
342 + padding: $-xs 0;
343 + color: #555;
344 + text-align: left !important;
345 + &.wide {
346 + min-width: 220px;
347 + }
348 + .text-muted {
349 + color: #999;
350 + }
351 + a {
352 + display: block;
353 + padding: $-xs $-m;
354 + color: #555;
355 + &:hover {
356 + text-decoration: none;
357 + background-color: #EEE;
358 + }
359 + i {
360 + margin-right: $-m;
361 + padding-right: 0;
362 + display: inline;
363 + width: 22px;
364 + }
365 + }
366 + li.border-bottom {
367 + border-bottom: 1px solid #DDD;
368 + }
369 +}
......
...@@ -100,7 +100,7 @@ ...@@ -100,7 +100,7 @@
100 background-color: #FFF; 100 background-color: #FFF;
101 border: 1px solid #DDD; 101 border: 1px solid #DDD;
102 color: #666; 102 color: #666;
103 - width: 180px; 103 + width: 172px;
104 z-index: 40; 104 z-index: 40;
105 } 105 }
106 input, button { 106 input, button {
......
...@@ -20,4 +20,8 @@ table.table { ...@@ -20,4 +20,8 @@ table.table {
20 20
21 table { 21 table {
22 max-width: 100%; 22 max-width: 100%;
23 + thead {
24 + background-color: #F8F8F8;
25 + font-weight: 500;
26 + }
23 } 27 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -115,7 +115,8 @@ pre { ...@@ -115,7 +115,8 @@ pre {
115 box-shadow: 0 1px 2px 0px rgba(10, 10, 10, 0.06); 115 box-shadow: 0 1px 2px 0px rgba(10, 10, 10, 0.06);
116 border: 1px solid rgba(221, 221, 221, 0.66); 116 border: 1px solid rgba(221, 221, 221, 0.66);
117 background-color: #fdf6e3; 117 background-color: #fdf6e3;
118 - padding: 0.5em; 118 + padding: $-s;
119 + overflow-x: scroll;
119 } 120 }
120 121
121 blockquote { 122 blockquote {
...@@ -251,6 +252,18 @@ ol { ...@@ -251,6 +252,18 @@ ol {
251 text-align: right; 252 text-align: right;
252 } 253 }
253 254
255 +.text-bigger {
256 + font-size: 1.1em;
257 +}
258 +
259 +.text-large {
260 + font-size: 1.6666em;
261 +}
262 +
263 +.no-color {
264 + color: inherit;
265 +}
266 +
254 /** 267 /**
255 * Grouping 268 * Grouping
256 */ 269 */
......
...@@ -47,6 +47,13 @@ body.dragging, body.dragging * { ...@@ -47,6 +47,13 @@ body.dragging, body.dragging * {
47 width: 80px; 47 width: 80px;
48 height: 80px; 48 height: 80px;
49 } 49 }
50 + &.huge {
51 + width: 120px;
52 + height: 120px;
53 + }
54 + &.square {
55 + border-radius: 3px;
56 + }
50 } 57 }
51 58
52 // System wide notifications 59 // System wide notifications
......
...@@ -58,10 +58,13 @@ ...@@ -58,10 +58,13 @@
58 </span> 58 </span>
59 <ul> 59 <ul>
60 <li> 60 <li>
61 - <a href="/users/{{$currentUser->id}}" class="text-primary"><i class="zmdi zmdi-edit zmdi-hc-lg"></i>Edit Profile</a> 61 + <a href="/user/{{$currentUser->id}}" class="text-primary"><i class="zmdi zmdi-account zmdi-hc-fw zmdi-hc-lg"></i>View Profile</a>
62 </li> 62 </li>
63 <li> 63 <li>
64 - <a href="/logout" class="text-neg"><i class="zmdi zmdi-run zmdi-hc-lg"></i>Logout</a> 64 + <a href="/settings/users/{{$currentUser->id}}" class="text-primary"><i class="zmdi zmdi-edit zmdi-hc-fw zmdi-hc-lg"></i>Edit Profile</a>
65 + </li>
66 + <li>
67 + <a href="/logout" class="text-neg"><i class="zmdi zmdi-run zmdi-hc-fw zmdi-hc-lg"></i>Logout</a>
65 </li> 68 </li>
66 </ul> 69 </ul>
67 </div> 70 </div>
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
10 <p class="text-muted">{{ $chapter->getExcerpt() }}</p> 10 <p class="text-muted">{{ $chapter->getExcerpt() }}</p>
11 @endif 11 @endif
12 12
13 - @if(count($chapter->pages) > 0 && !isset($hidePages)) 13 + @if(!isset($hidePages) && count($chapter->pages) > 0)
14 <p class="text-muted chapter-toggle"><i class="zmdi zmdi-caret-right"></i> <i class="zmdi zmdi-file-text"></i> <span>{{ count($chapter->pages) }} Pages</span></p> 14 <p class="text-muted chapter-toggle"><i class="zmdi zmdi-caret-right"></i> <i class="zmdi zmdi-file-text"></i> <span>{{ count($chapter->pages) }} Pages</span></p>
15 <div class="inset-list"> 15 <div class="inset-list">
16 @foreach($chapter->pages as $page) 16 @foreach($chapter->pages as $page)
......
...@@ -2,20 +2,44 @@ ...@@ -2,20 +2,44 @@
2 2
3 @section('content') 3 @section('content')
4 4
5 + <div class="faded-small toolbar">
6 + <div class="container">
7 + <div class="row">
8 + <div class="col-sm-4 faded">
9 + <div class="action-buttons text-left">
10 + <a data-action="expand-entity-list-details" class="text-primary text-button"><i class="zmdi zmdi-wrap-text"></i>Toggle Details</a>
11 + </div>
12 + </div>
13 + <div class="col-sm-8 faded">
14 + <div class="action-buttons">
15 +
16 + </div>
17 + </div>
18 + </div>
19 + </div>
20 + </div>
21 +
5 <div class="container" ng-non-bindable> 22 <div class="container" ng-non-bindable>
6 <div class="row"> 23 <div class="row">
7 24
8 - <div class="col-md-7"> 25 + <div class="col-sm-4">
9 @if($signedIn) 26 @if($signedIn)
10 - <h2>My Recently Viewed</h2> 27 + <h3>My Recently Viewed</h3>
11 @else 28 @else
12 - <h2>Recent Books</h2> 29 + <h3>Recent Books</h3>
13 @endif 30 @endif
14 - @include('partials/entity-list', ['entities' => $recents]) 31 + @include('partials/entity-list', ['entities' => $recents, 'style' => 'compact'])
32 + </div>
33 +
34 + <div class="col-sm-4">
35 + <h3><a class="no-color" href="/pages/recently-created">Recently Created Pages</a></h3>
36 + @include('partials/entity-list', ['entities' => $recentlyCreatedPages, 'style' => 'compact'])
37 +
38 + <h3><a class="no-color" href="/pages/recently-updated">Recently Updated Pages</a></h3>
39 + @include('partials/entity-list', ['entities' => $recentlyCreatedPages, 'style' => 'compact'])
15 </div> 40 </div>
16 41
17 - <div class="col-md-4 col-md-offset-1"> 42 + <div class="col-sm-4" id="recent-activity">
18 - <div class="margin-top large">&nbsp;</div>
19 <h3>Recent Activity</h3> 43 <h3>Recent Activity</h3>
20 @include('partials/activity-list', ['activity' => $activity]) 44 @include('partials/activity-list', ['activity' => $activity])
21 </div> 45 </div>
......
1 +@extends('base')
2 +
3 +@section('content')
4 +
5 + <div class="container">
6 + <div class="row">
7 +
8 + <div class="col-sm-7">
9 + <h1>{{ $title }}</h1>
10 + @include('partials/entity-list', ['entities' => $pages, 'style' => 'detailed'])
11 + {!! $pages->links() !!}
12 + </div>
13 +
14 + <div class="col-sm-4 col-sm-offset-1"></div>
15 +
16 + </div>
17 + </div>
18 +@stop
...\ No newline at end of file ...\ No newline at end of file
...@@ -3,18 +3,29 @@ ...@@ -3,18 +3,29 @@
3 <a href="{{ $page->getUrl() }}" class="text-page"><i class="zmdi zmdi-file-text"></i>{{ $page->name }}</a> 3 <a href="{{ $page->getUrl() }}" class="text-page"><i class="zmdi zmdi-file-text"></i>{{ $page->name }}</a>
4 </h3> 4 </h3>
5 5
6 - @if(isset($showMeta) && $showMeta)
7 - <div class="meta">
8 - <span class="text-book"><i class="zmdi zmdi-book"></i> {{ $page->book->name }}</span>
9 - @if($page->chapter)
10 - <span class="text-chapter"><i class="zmdi zmdi-collection-bookmark"></i> {{ $page->chapter->name }}</span>
11 - @endif
12 - </div>
13 - @endif
14 -
15 @if(isset($page->searchSnippet)) 6 @if(isset($page->searchSnippet))
16 <p class="text-muted">{!! $page->searchSnippet !!}</p> 7 <p class="text-muted">{!! $page->searchSnippet !!}</p>
17 @else 8 @else
18 <p class="text-muted">{{ $page->getExcerpt() }}</p> 9 <p class="text-muted">{{ $page->getExcerpt() }}</p>
19 @endif 10 @endif
11 +
12 + @if(isset($style) && $style === 'detailed')
13 + <div class="row meta text-muted text-small">
14 + <div class="col-md-4">
15 + Created {{$page->created_at->diffForHumans()}} @if($page->createdBy)by {{$page->createdBy->name}}@endif <br>
16 + Last updated {{ $page->updated_at->diffForHumans() }} @if($page->updatedBy)by {{$page->updatedBy->name}} @endif
17 + </div>
18 + <div class="col-md-8">
19 + <a class="text-book" href="{{ $page->book->getUrl() }}"><i class="zmdi zmdi-book"></i>{{ $page->book->getShortName(30) }}</a>
20 + <br>
21 + @if($page->chapter)
22 + <a class="text-chapter" href="{{ $page->chapter->getUrl() }}"><i class="zmdi zmdi-collection-bookmark"></i>{{ $page->chapter->getShortName(30) }}</a>
23 + @else
24 + <i class="zmdi zmdi-collection-bookmark"></i> Page is not in a chapter
25 + @endif
26 + </div>
27 + </div>
28 + @endif
29 +
30 +
20 </div> 31 </div>
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
9 9
10 <div class="right" ng-non-bindable> 10 <div class="right" ng-non-bindable>
11 @if($activity->user) 11 @if($activity->user)
12 - {{$activity->user->name}} 12 + <a href="/user/{{ $activity->user->id }}">{{$activity->user->name}}</a>
13 @else 13 @else
14 A deleted user 14 A deleted user
15 @endif 15 @endif
......
1 1
2 -@if(count($entities) > 0) 2 +<div class="entity-list @if(isset($style)){{ $style }}@endif" ng-non-bindable>
3 - @foreach($entities as $index => $entity) 3 + @if(count($entities) > 0)
4 - @if($entity->isA('page')) 4 + @foreach($entities as $index => $entity)
5 - @include('pages/list-item', ['page' => $entity]) 5 + @if($entity->isA('page'))
6 - @elseif($entity->isA('book')) 6 + @include('pages/list-item', ['page' => $entity])
7 - @include('books/list-item', ['book' => $entity]) 7 + @elseif($entity->isA('book'))
8 - @elseif($entity->isA('chapter')) 8 + @include('books/list-item', ['book' => $entity])
9 - @include('chapters/list-item', ['chapter' => $entity, 'hidePages' => true]) 9 + @elseif($entity->isA('chapter'))
10 - @endif 10 + @include('chapters/list-item', ['chapter' => $entity, 'hidePages' => true])
11 + @endif
11 12
12 - @if($index !== count($entities) - 1) 13 + @if($index !== count($entities) - 1)
13 - <hr> 14 + <hr>
14 - @endif 15 + @endif
15 16
16 - @endforeach
17 -@else
18 - <p class="text-muted">
19 - No items available
20 - </p>
21 -@endif
...\ No newline at end of file ...\ No newline at end of file
17 + @endforeach
18 + @else
19 + <p class="text-muted">
20 + No items available
21 + </p>
22 + @endif
23 +</div>
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -6,41 +6,36 @@ ...@@ -6,41 +6,36 @@
6 6
7 <h1>Search Results&nbsp;&nbsp;&nbsp; <span class="text-muted">{{$searchTerm}}</span></h1> 7 <h1>Search Results&nbsp;&nbsp;&nbsp; <span class="text-muted">{{$searchTerm}}</span></h1>
8 8
9 + <p>
10 + <a href="/search/pages?term={{$searchTerm}}" class="text-page"><i class="zmdi zmdi-file-text"></i>View all matched pages</a>
11 +
12 + @if(count($chapters) > 0)
13 + &nbsp; &nbsp;&nbsp;
14 + <a href="/search/chapters?term={{$searchTerm}}" class="text-chapter"><i class="zmdi zmdi-collection-bookmark"></i>View all matched chapters</a>
15 + @endif
16 +
17 + @if(count($books) > 0)
18 + &nbsp; &nbsp;&nbsp;
19 + <a href="/search/books?term={{$searchTerm}}" class="text-book"><i class="zmdi zmdi-book"></i>View all matched books</a>
20 + @endif
21 + </p>
9 <div class="row"> 22 <div class="row">
10 23
11 <div class="col-md-6"> 24 <div class="col-md-6">
12 - <h3>Matching Pages</h3> 25 + <h3><a href="/search/pages?term={{$searchTerm}}" class="no-color">Matching Pages</a></h3>
13 - <div class="page-list"> 26 + @include('partials/entity-list', ['entities' => $pages, 'style' => 'detailed'])
14 - @if(count($pages) > 0)
15 - @foreach($pages as $page)
16 - @include('pages/list-item', ['page' => $page, 'showMeta' => true])
17 - <hr>
18 - @endforeach
19 - @else
20 - <p class="text-muted">No pages matched this search</p>
21 - @endif
22 - </div>
23 </div> 27 </div>
24 28
25 <div class="col-md-5 col-md-offset-1"> 29 <div class="col-md-5 col-md-offset-1">
26 30
27 @if(count($books) > 0) 31 @if(count($books) > 0)
28 - <h3>Matching Books</h3> 32 + <h3><a href="/search/books?term={{$searchTerm}}" class="no-color">Matching Books</a></h3>
29 - <div class="page-list"> 33 + @include('partials/entity-list', ['entities' => $books])
30 - @foreach($books as $book)
31 - @include('books/list-item', ['book' => $book])
32 - <hr>
33 - @endforeach
34 - </div>
35 @endif 34 @endif
36 35
37 @if(count($chapters) > 0) 36 @if(count($chapters) > 0)
38 - <h3>Matching Chapters</h3> 37 + <h3><a href="/search/chapters?term={{$searchTerm}}" class="no-color">Matching Chapters</a></h3>
39 - <div class="page-list"> 38 + @include('partials/entity-list', ['entities' => $chapters])
40 - @foreach($chapters as $chapter)
41 - @include('chapters/list-item', ['chapter' => $chapter, 'hidePages' => true])
42 - @endforeach
43 - </div>
44 @endif 39 @endif
45 40
46 </div> 41 </div>
......
1 +@extends('base')
2 +
3 +@section('content')
4 +
5 + <div class="container">
6 + <div class="row">
7 +
8 + <div class="col-sm-7">
9 + <h1>{{ $title }} <small>{{$searchTerm}}</small></h1>
10 + @include('partials.entity-list', ['entities' => $entities, 'style' => 'detailed'])
11 + {!! $entities->links() !!}
12 + </div>
13 +
14 + <div class="col-sm-4 col-sm-offset-1"></div>
15 +
16 + </div>
17 + </div>
18 +@stop
...\ No newline at end of file ...\ No newline at end of file
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
4 <div class="row"> 4 <div class="row">
5 <div class="col-md-12 setting-nav"> 5 <div class="col-md-12 setting-nav">
6 <a href="/settings" @if($selected == 'settings') class="selected text-button" @endif><i class="zmdi zmdi-settings"></i>Settings</a> 6 <a href="/settings" @if($selected == 'settings') class="selected text-button" @endif><i class="zmdi zmdi-settings"></i>Settings</a>
7 - <a href="/users" @if($selected == 'users') class="selected text-button" @endif><i class="zmdi zmdi-accounts"></i>Users</a> 7 + <a href="/settings/users" @if($selected == 'users') class="selected text-button" @endif><i class="zmdi zmdi-accounts"></i>Users</a>
8 </div> 8 </div>
9 </div> 9 </div>
10 </div> 10 </div>
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
6 <div class="container small" ng-non-bindable> 6 <div class="container small" ng-non-bindable>
7 <h1>Create User</h1> 7 <h1>Create User</h1>
8 8
9 - <form action="/users/create" method="post"> 9 + <form action="/settings/users/create" method="post">
10 {!! csrf_field() !!} 10 {!! csrf_field() !!}
11 @include('users.forms.' . $authMethod) 11 @include('users.forms.' . $authMethod)
12 </form> 12 </form>
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
7 <p>This will fully delete this user with the name '<span class="text-neg">{{$user->name}}</span>' from the system.</p> 7 <p>This will fully delete this user with the name '<span class="text-neg">{{$user->name}}</span>' from the system.</p>
8 <p class="text-neg">Are you sure you want to delete this user?</p> 8 <p class="text-neg">Are you sure you want to delete this user?</p>
9 9
10 - <form action="/users/{{$user->id}}" method="POST"> 10 + <form action="/settings/users/{{$user->id}}" method="POST">
11 {!! csrf_field() !!} 11 {!! csrf_field() !!}
12 <input type="hidden" name="_method" value="DELETE"> 12 <input type="hidden" name="_method" value="DELETE">
13 <a href="/users/{{$user->id}}" class="button muted">Cancel</a> 13 <a href="/users/{{$user->id}}" class="button muted">Cancel</a>
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
9 <div class="col-sm-6"></div> 9 <div class="col-sm-6"></div>
10 <div class="col-sm-6 faded"> 10 <div class="col-sm-6 faded">
11 <div class="action-buttons"> 11 <div class="action-buttons">
12 - <a href="/users/{{$user->id}}/delete" class="text-neg text-button"><i class="zmdi zmdi-delete"></i>Delete User</a> 12 + <a href="/settings/users/{{$user->id}}/delete" class="text-neg text-button"><i class="zmdi zmdi-delete"></i>Delete User</a>
13 </div> 13 </div>
14 </div> 14 </div>
15 </div> 15 </div>
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
19 19
20 20
21 <div class="container small"> 21 <div class="container small">
22 - <form action="/users/{{$user->id}}" method="post"> 22 + <form action="/settings/users/{{$user->id}}" method="post">
23 <div class="row"> 23 <div class="row">
24 <div class="col-md-6" ng-non-bindable> 24 <div class="col-md-6" ng-non-bindable>
25 <h1>Edit {{ $user->id === $currentUser->id ? 'Profile' : 'User' }}</h1> 25 <h1>Edit {{ $user->id === $currentUser->id ? 'Profile' : 'User' }}</h1>
......
...@@ -25,6 +25,6 @@ ...@@ -25,6 +25,6 @@
25 @endif 25 @endif
26 26
27 <div class="form-group"> 27 <div class="form-group">
28 - <a href="/users" class="button muted">Cancel</a> 28 + <a href="/settings/users" class="button muted">Cancel</a>
29 <button class="button pos" type="submit">Save</button> 29 <button class="button pos" type="submit">Save</button>
30 </div> 30 </div>
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
34 </div> 34 </div>
35 35
36 <div class="form-group"> 36 <div class="form-group">
37 - <a href="/users" class="button muted">Cancel</a> 37 + <a href="/settings/users" class="button muted">Cancel</a>
38 <button class="button pos" type="submit">Save</button> 38 <button class="button pos" type="submit">Save</button>
39 </div> 39 </div>
40 40
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
10 <h1>Users</h1> 10 <h1>Users</h1>
11 @if($currentUser->can('user-create')) 11 @if($currentUser->can('user-create'))
12 <p> 12 <p>
13 - <a href="/users/create" class="text-pos"><i class="zmdi zmdi-account-add"></i>Add new user</a> 13 + <a href="/settings/users/create" class="text-pos"><i class="zmdi zmdi-account-add"></i>Add new user</a>
14 </p> 14 </p>
15 @endif 15 @endif
16 <table class="table"> 16 <table class="table">
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
25 <td style="line-height: 0;"><img class="avatar med" src="{{$user->getAvatar(40)}}" alt="{{$user->name}}"></td> 25 <td style="line-height: 0;"><img class="avatar med" src="{{$user->getAvatar(40)}}" alt="{{$user->name}}"></td>
26 <td> 26 <td>
27 @if($currentUser->can('user-update') || $currentUser->id == $user->id) 27 @if($currentUser->can('user-update') || $currentUser->id == $user->id)
28 - <a href="/users/{{$user->id}}"> 28 + <a href="/settings/users/{{$user->id}}">
29 @endif 29 @endif
30 {{ $user->name }} 30 {{ $user->name }}
31 @if($currentUser->can('user-update') || $currentUser->id == $user->id) 31 @if($currentUser->can('user-update') || $currentUser->id == $user->id)
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
34 </td> 34 </td>
35 <td> 35 <td>
36 @if($currentUser->can('user-update') || $currentUser->id == $user->id) 36 @if($currentUser->can('user-update') || $currentUser->id == $user->id)
37 - <a href="/users/{{$user->id}}"> 37 + <a href="/settings/users/{{$user->id}}">
38 @endif 38 @endif
39 {{ $user->email }} 39 {{ $user->email }}
40 @if($currentUser->can('user-update') || $currentUser->id == $user->id) 40 @if($currentUser->can('user-update') || $currentUser->id == $user->id)
......
1 +@extends('base')
2 +
3 +@section('content')
4 +
5 + <div class="container" ng-non-bindable>
6 + <div class="row">
7 + <div class="col-sm-7">
8 +
9 + <div class="padded-top large"></div>
10 +
11 + <div class="row">
12 + <div class="col-md-7">
13 + <div class="clearfix">
14 + <div class="padded-right float left">
15 + <img class="avatar square huge" src="{{$user->getAvatar(120)}}" alt="{{ $user->name }}">
16 + </div>
17 + <div>
18 + <h3 style="margin-top: 0;">{{ $user->name }}</h3>
19 + <p class="text-muted">
20 + User for {{ $user->created_at->diffForHumans(null, true) }}
21 + </p>
22 + </div>
23 + </div>
24 + </div>
25 + <div class="col-md-5 text-bigger" id="content-counts">
26 + <div class="text-muted">Created Content</div>
27 + <div class="text-book">
28 + <i class="zmdi zmdi-book zmdi-hc-fw"></i> {{ $assetCounts['books'] }} {{ str_plural('Book', $assetCounts['books']) }}
29 + </div>
30 + <div class="text-chapter">
31 + <i class="zmdi zmdi-collection-bookmark zmdi-hc-fw"></i> {{ $assetCounts['chapters'] }} {{ str_plural('Chapter', $assetCounts['chapters']) }}
32 + </div>
33 + <div class="text-page">
34 + <i class="zmdi zmdi-file-text zmdi-hc-fw"></i> {{ $assetCounts['pages'] }} {{ str_plural('Page', $assetCounts['pages']) }}
35 + </div>
36 + </div>
37 + </div>
38 +
39 +
40 + <hr class="even">
41 +
42 + <h3>Recently Created Pages</h3>
43 + @if (count($recentlyCreated['pages']) > 0)
44 + @include('partials/entity-list', ['entities' => $recentlyCreated['pages']])
45 + @else
46 + <p class="text-muted">{{ $user->name }} has not created any pages</p>
47 + @endif
48 +
49 + <hr class="even">
50 +
51 + <h3>Recently Created Chapters</h3>
52 + @if (count($recentlyCreated['chapters']) > 0)
53 + @include('partials/entity-list', ['entities' => $recentlyCreated['chapters']])
54 + @else
55 + <p class="text-muted">{{ $user->name }} has not created any chapters</p>
56 + @endif
57 +
58 + <hr class="even">
59 +
60 + <h3>Recently Created Books</h3>
61 + @if (count($recentlyCreated['books']) > 0)
62 + @include('partials/entity-list', ['entities' => $recentlyCreated['books']])
63 + @else
64 + <p class="text-muted">{{ $user->name }} has not created any books</p>
65 + @endif
66 + </div>
67 +
68 + <div class="col-sm-4 col-sm-offset-1" id="recent-activity">
69 + <h3>Recent Activity</h3>
70 + @include('partials/activity-list', ['activity' => $activity])
71 + </div>
72 +
73 + </div>
74 + </div>
75 +
76 +
77 +@stop
...\ No newline at end of file ...\ No newline at end of file
File mode changed
...@@ -129,7 +129,7 @@ class AuthTest extends TestCase ...@@ -129,7 +129,7 @@ class AuthTest extends TestCase
129 $user = factory(\BookStack\User::class)->make(); 129 $user = factory(\BookStack\User::class)->make();
130 130
131 $this->asAdmin() 131 $this->asAdmin()
132 - ->visit('/users') 132 + ->visit('/settings/users')
133 ->click('Add new user') 133 ->click('Add new user')
134 ->type($user->name, '#name') 134 ->type($user->name, '#name')
135 ->type($user->email, '#email') 135 ->type($user->email, '#email')
...@@ -138,7 +138,7 @@ class AuthTest extends TestCase ...@@ -138,7 +138,7 @@ class AuthTest extends TestCase
138 ->type($user->password, '#password-confirm') 138 ->type($user->password, '#password-confirm')
139 ->press('Save') 139 ->press('Save')
140 ->seeInDatabase('users', $user->toArray()) 140 ->seeInDatabase('users', $user->toArray())
141 - ->seePageIs('/users') 141 + ->seePageIs('/settings/users')
142 ->see($user->name); 142 ->see($user->name);
143 } 143 }
144 144
...@@ -147,13 +147,13 @@ class AuthTest extends TestCase ...@@ -147,13 +147,13 @@ class AuthTest extends TestCase
147 $user = \BookStack\User::all()->last(); 147 $user = \BookStack\User::all()->last();
148 $password = $user->password; 148 $password = $user->password;
149 $this->asAdmin() 149 $this->asAdmin()
150 - ->visit('/users') 150 + ->visit('/settings/users')
151 ->click($user->name) 151 ->click($user->name)
152 - ->seePageIs('/users/' . $user->id) 152 + ->seePageIs('/settings/users/' . $user->id)
153 ->see($user->email) 153 ->see($user->email)
154 ->type('Barry Scott', '#name') 154 ->type('Barry Scott', '#name')
155 ->press('Save') 155 ->press('Save')
156 - ->seePageIs('/users') 156 + ->seePageIs('/settings/users')
157 ->seeInDatabase('users', ['id' => $user->id, 'name' => 'Barry Scott', 'password' => $password]) 157 ->seeInDatabase('users', ['id' => $user->id, 'name' => 'Barry Scott', 'password' => $password])
158 ->notSeeInDatabase('users', ['name' => $user->name]); 158 ->notSeeInDatabase('users', ['name' => $user->name]);
159 } 159 }
...@@ -161,7 +161,7 @@ class AuthTest extends TestCase ...@@ -161,7 +161,7 @@ class AuthTest extends TestCase
161 public function test_user_password_update() 161 public function test_user_password_update()
162 { 162 {
163 $user = \BookStack\User::all()->last(); 163 $user = \BookStack\User::all()->last();
164 - $userProfilePage = '/users/' . $user->id; 164 + $userProfilePage = '/settings/users/' . $user->id;
165 $this->asAdmin() 165 $this->asAdmin()
166 ->visit($userProfilePage) 166 ->visit($userProfilePage)
167 ->type('newpassword', '#password') 167 ->type('newpassword', '#password')
...@@ -172,7 +172,7 @@ class AuthTest extends TestCase ...@@ -172,7 +172,7 @@ class AuthTest extends TestCase
172 ->type('newpassword', '#password') 172 ->type('newpassword', '#password')
173 ->type('newpassword', '#password-confirm') 173 ->type('newpassword', '#password-confirm')
174 ->press('Save') 174 ->press('Save')
175 - ->seePageIs('/users'); 175 + ->seePageIs('/settings/users');
176 176
177 $userPassword = \BookStack\User::find($user->id)->password; 177 $userPassword = \BookStack\User::find($user->id)->password;
178 $this->assertTrue(Hash::check('newpassword', $userPassword)); 178 $this->assertTrue(Hash::check('newpassword', $userPassword));
...@@ -184,11 +184,11 @@ class AuthTest extends TestCase ...@@ -184,11 +184,11 @@ class AuthTest extends TestCase
184 $user = $this->getNewUser($userDetails->toArray()); 184 $user = $this->getNewUser($userDetails->toArray());
185 185
186 $this->asAdmin() 186 $this->asAdmin()
187 - ->visit('/users/' . $user->id) 187 + ->visit('/settings/users/' . $user->id)
188 ->click('Delete User') 188 ->click('Delete User')
189 ->see($user->name) 189 ->see($user->name)
190 ->press('Confirm') 190 ->press('Confirm')
191 - ->seePageIs('/users') 191 + ->seePageIs('/settings/users')
192 ->notSeeInDatabase('users', ['name' => $user->name]); 192 ->notSeeInDatabase('users', ['name' => $user->name]);
193 } 193 }
194 194
...@@ -199,10 +199,10 @@ class AuthTest extends TestCase ...@@ -199,10 +199,10 @@ class AuthTest extends TestCase
199 $this->assertEquals(1, $adminRole->users()->count()); 199 $this->assertEquals(1, $adminRole->users()->count());
200 $user = $adminRole->users->first(); 200 $user = $adminRole->users->first();
201 201
202 - $this->asAdmin()->visit('/users/' . $user->id) 202 + $this->asAdmin()->visit('/settings/users/' . $user->id)
203 ->click('Delete User') 203 ->click('Delete User')
204 ->press('Confirm') 204 ->press('Confirm')
205 - ->seePageIs('/users/' . $user->id) 205 + ->seePageIs('/settings/users/' . $user->id)
206 ->see('You cannot delete the only admin'); 206 ->see('You cannot delete the only admin');
207 } 207 }
208 208
......
...@@ -94,7 +94,7 @@ class LdapTest extends \TestCase ...@@ -94,7 +94,7 @@ class LdapTest extends \TestCase
94 94
95 public function test_create_user_form() 95 public function test_create_user_form()
96 { 96 {
97 - $this->asAdmin()->visit('/users/create') 97 + $this->asAdmin()->visit('/settings/users/create')
98 ->dontSee('Password') 98 ->dontSee('Password')
99 ->type($this->mockUser->name, '#name') 99 ->type($this->mockUser->name, '#name')
100 ->type($this->mockUser->email, '#email') 100 ->type($this->mockUser->email, '#email')
...@@ -102,19 +102,19 @@ class LdapTest extends \TestCase ...@@ -102,19 +102,19 @@ class LdapTest extends \TestCase
102 ->see('The external auth id field is required.') 102 ->see('The external auth id field is required.')
103 ->type($this->mockUser->name, '#external_auth_id') 103 ->type($this->mockUser->name, '#external_auth_id')
104 ->press('Save') 104 ->press('Save')
105 - ->seePageIs('/users') 105 + ->seePageIs('/settings/users')
106 ->seeInDatabase('users', ['email' => $this->mockUser->email, 'external_auth_id' => $this->mockUser->name, 'email_confirmed' => true]); 106 ->seeInDatabase('users', ['email' => $this->mockUser->email, 'external_auth_id' => $this->mockUser->name, 'email_confirmed' => true]);
107 } 107 }
108 108
109 public function test_user_edit_form() 109 public function test_user_edit_form()
110 { 110 {
111 $editUser = User::all()->last(); 111 $editUser = User::all()->last();
112 - $this->asAdmin()->visit('/users/' . $editUser->id) 112 + $this->asAdmin()->visit('/settings/users/' . $editUser->id)
113 ->see('Edit User') 113 ->see('Edit User')
114 ->dontSee('Password') 114 ->dontSee('Password')
115 ->type('test_auth_id', '#external_auth_id') 115 ->type('test_auth_id', '#external_auth_id')
116 ->press('Save') 116 ->press('Save')
117 - ->seePageIs('/users') 117 + ->seePageIs('/settings/users')
118 ->seeInDatabase('users', ['email' => $editUser->email, 'external_auth_id' => 'test_auth_id']); 118 ->seeInDatabase('users', ['email' => $editUser->email, 'external_auth_id' => 'test_auth_id']);
119 } 119 }
120 120
...@@ -127,7 +127,7 @@ class LdapTest extends \TestCase ...@@ -127,7 +127,7 @@ class LdapTest extends \TestCase
127 public function test_non_admins_cannot_change_auth_id() 127 public function test_non_admins_cannot_change_auth_id()
128 { 128 {
129 $testUser = User::all()->last(); 129 $testUser = User::all()->last();
130 - $this->actingAs($testUser)->visit('/users/' . $testUser->id) 130 + $this->actingAs($testUser)->visit('/settings/users/' . $testUser->id)
131 ->dontSee('External Authentication'); 131 ->dontSee('External Authentication');
132 } 132 }
133 133
......
1 +<?php
2 +
3 +use Illuminate\Support\Facades\DB;
4 +
5 +class EntitySearchTest extends TestCase
6 +{
7 +
8 + public function test_page_search()
9 + {
10 + $book = \BookStack\Book::all()->first();
11 + $page = $book->pages->first();
12 +
13 + $this->asAdmin()
14 + ->visit('/')
15 + ->type($page->name, 'term')
16 + ->press('header-search-box-button')
17 + ->see('Search Results')
18 + ->see($page->name)
19 + ->click($page->name)
20 + ->seePageIs($page->getUrl());
21 + }
22 +
23 + public function test_invalid_page_search()
24 + {
25 + $this->asAdmin()
26 + ->visit('/')
27 + ->type('<p>test</p>', 'term')
28 + ->press('header-search-box-button')
29 + ->see('Search Results')
30 + ->seeStatusCode(200);
31 + }
32 +
33 + public function test_empty_search_redirects_back()
34 + {
35 + $this->asAdmin()
36 + ->visit('/')
37 + ->visit('/search/all')
38 + ->seePageIs('/');
39 + }
40 +
41 + public function test_book_search()
42 + {
43 + $book = \BookStack\Book::all()->first();
44 + $page = $book->pages->last();
45 + $chapter = $book->chapters->last();
46 +
47 + $this->asAdmin()
48 + ->visit('/search/book/' . $book->id . '?term=' . urlencode($page->name))
49 + ->see($page->name)
50 +
51 + ->visit('/search/book/' . $book->id . '?term=' . urlencode($chapter->name))
52 + ->see($chapter->name);
53 + }
54 +
55 + public function test_empty_book_search_redirects_back()
56 + {
57 + $book = \BookStack\Book::all()->first();
58 + $this->asAdmin()
59 + ->visit('/books')
60 + ->visit('/search/book/' . $book->id . '?term=')
61 + ->seePageIs('/books');
62 + }
63 +
64 +
65 + public function test_pages_search_listing()
66 + {
67 + $page = \BookStack\Page::all()->last();
68 + $this->asAdmin()->visit('/search/pages?term=' . $page->name)
69 + ->see('Page Search Results')->see('.entity-list', $page->name);
70 + }
71 +
72 + public function test_chapters_search_listing()
73 + {
74 + $chapter = \BookStack\Chapter::all()->last();
75 + $this->asAdmin()->visit('/search/chapters?term=' . $chapter->name)
76 + ->see('Chapter Search Results')->seeInElement('.entity-list', $chapter->name);
77 + }
78 +
79 + public function test_books_search_listing()
80 + {
81 + $book = \BookStack\Book::all()->last();
82 + $this->asAdmin()->visit('/search/books?term=' . $book->name)
83 + ->see('Book Search Results')->see('.entity-list', $book->name);
84 + }
85 +}
...@@ -155,63 +155,6 @@ class EntityTest extends TestCase ...@@ -155,63 +155,6 @@ class EntityTest extends TestCase
155 return $book; 155 return $book;
156 } 156 }
157 157
158 - public function test_page_search()
159 - {
160 - $book = \BookStack\Book::all()->first();
161 - $page = $book->pages->first();
162 -
163 - $this->asAdmin()
164 - ->visit('/')
165 - ->type($page->name, 'term')
166 - ->press('header-search-box-button')
167 - ->see('Search Results')
168 - ->see($page->name)
169 - ->click($page->name)
170 - ->seePageIs($page->getUrl());
171 - }
172 -
173 - public function test_invalid_page_search()
174 - {
175 - $this->asAdmin()
176 - ->visit('/')
177 - ->type('<p>test</p>', 'term')
178 - ->press('header-search-box-button')
179 - ->see('Search Results')
180 - ->seeStatusCode(200);
181 - }
182 -
183 - public function test_empty_search_redirects_back()
184 - {
185 - $this->asAdmin()
186 - ->visit('/')
187 - ->visit('/search/all')
188 - ->seePageIs('/');
189 - }
190 -
191 - public function test_book_search()
192 - {
193 - $book = \BookStack\Book::all()->first();
194 - $page = $book->pages->last();
195 - $chapter = $book->chapters->last();
196 -
197 - $this->asAdmin()
198 - ->visit('/search/book/' . $book->id . '?term=' . urlencode($page->name))
199 - ->see($page->name)
200 -
201 - ->visit('/search/book/' . $book->id . '?term=' . urlencode($chapter->name))
202 - ->see($chapter->name);
203 - }
204 -
205 - public function test_empty_book_search_redirects_back()
206 - {
207 - $book = \BookStack\Book::all()->first();
208 - $this->asAdmin()
209 - ->visit('/books')
210 - ->visit('/search/book/' . $book->id . '?term=')
211 - ->seePageIs('/books');
212 - }
213 -
214 -
215 public function test_entities_viewable_after_creator_deletion() 158 public function test_entities_viewable_after_creator_deletion()
216 { 159 {
217 // Create required assets and revisions 160 // Create required assets and revisions
...@@ -250,5 +193,36 @@ class EntityTest extends TestCase ...@@ -250,5 +193,36 @@ class EntityTest extends TestCase
250 ->click('Revisions')->seeStatusCode(200); 193 ->click('Revisions')->seeStatusCode(200);
251 } 194 }
252 195
196 + public function test_recently_created_pages_view()
197 + {
198 + $user = $this->getNewUser();
199 + $content = $this->createEntityChainBelongingToUser($user);
200 +
201 + $this->asAdmin()->visit('/pages/recently-created')
202 + ->seeInNthElement('.entity-list .page', 0, $content['page']->name);
203 + }
204 +
205 + public function test_recently_updated_pages_view()
206 + {
207 + $user = $this->getNewUser();
208 + $content = $this->createEntityChainBelongingToUser($user);
209 +
210 + $this->asAdmin()->visit('/pages/recently-updated')
211 + ->seeInNthElement('.entity-list .page', 0, $content['page']->name);
212 + }
213 +
214 + public function test_old_page_slugs_redirect_to_new_pages()
215 + {
216 + $page = \BookStack\Page::all()->first();
217 + $pageUrl = $page->getUrl();
218 + $newPageUrl = '/books/' . $page->book->slug . '/page/super-test-page';
219 + $this->asAdmin()->visit($pageUrl)
220 + ->clickInElement('#content', 'Edit')
221 + ->type('super test page', '#name')
222 + ->press('Save Page')
223 + ->seePageIs($newPageUrl)
224 + ->visit($pageUrl)
225 + ->seePageIs($newPageUrl);
226 + }
253 227
254 } 228 }
......
...@@ -109,4 +109,18 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase ...@@ -109,4 +109,18 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase
109 109
110 return $this; 110 return $this;
111 } 111 }
112 +
113 + /**
114 + * Click the text within the selected element.
115 + * @param $parentElement
116 + * @param $linkText
117 + * @return $this
118 + */
119 + protected function clickInElement($parentElement, $linkText)
120 + {
121 + $elem = $this->crawler->filter($parentElement);
122 + $link = $elem->selectLink($linkText);
123 + $this->visit($link->link()->getUri());
124 + return $this;
125 + }
112 } 126 }
......
1 +<?php
2 +
3 +class UserProfileTest extends TestCase
4 +{
5 + protected $user;
6 +
7 + public function setUp()
8 + {
9 + parent::setUp();
10 + $this->user = \BookStack\User::all()->last();
11 + }
12 +
13 + public function test_profile_page_shows_name()
14 + {
15 + $this->asAdmin()
16 + ->visit('/user/' . $this->user->id)
17 + ->see($this->user->name);
18 + }
19 +
20 + public function test_profile_page_shows_recent_entities()
21 + {
22 + $content = $this->createEntityChainBelongingToUser($this->user, $this->user);
23 +
24 + $this->asAdmin()
25 + ->visit('/user/' . $this->user->id)
26 + // Check the recently created page is shown
27 + ->see($content['page']->name)
28 + // Check the recently created chapter is shown
29 + ->see($content['chapter']->name)
30 + // Check the recently created book is shown
31 + ->see($content['book']->name);
32 + }
33 +
34 + public function test_profile_page_shows_created_content_counts()
35 + {
36 + $newUser = $this->getNewUser();
37 +
38 + $this->asAdmin()->visit('/user/' . $newUser->id)
39 + ->see($newUser->name)
40 + ->seeInElement('#content-counts', '0 Books')
41 + ->seeInElement('#content-counts', '0 Chapters')
42 + ->seeInElement('#content-counts', '0 Pages');
43 +
44 + $this->createEntityChainBelongingToUser($newUser, $newUser);
45 +
46 + $this->asAdmin()->visit('/user/' . $newUser->id)
47 + ->see($newUser->name)
48 + ->seeInElement('#content-counts', '1 Book')
49 + ->seeInElement('#content-counts', '1 Chapter')
50 + ->seeInElement('#content-counts', '1 Page');
51 + }
52 +
53 + public function test_profile_page_shows_recent_activity()
54 + {
55 + $newUser = $this->getNewUser();
56 + $this->actingAs($newUser);
57 + $entities = $this->createEntityChainBelongingToUser($newUser, $newUser);
58 + Activity::add($entities['book'], 'book_update', $entities['book']->id);
59 + Activity::add($entities['page'], 'page_create', $entities['book']->id);
60 +
61 + $this->asAdmin()->visit('/user/' . $newUser->id)
62 + ->seeInElement('#recent-activity', 'updated book')
63 + ->seeInElement('#recent-activity', 'created page')
64 + ->seeInElement('#recent-activity', $entities['page']->name);
65 + }
66 +
67 + public function test_clicking_user_name_in_activity_leads_to_profile_page()
68 + {
69 + $newUser = $this->getNewUser();
70 + $this->actingAs($newUser);
71 + $entities = $this->createEntityChainBelongingToUser($newUser, $newUser);
72 + Activity::add($entities['book'], 'book_update', $entities['book']->id);
73 + Activity::add($entities['page'], 'page_create', $entities['book']->id);
74 +
75 + $this->asAdmin()->visit('/')->clickInElement('#recent-activity', $newUser->name)
76 + ->seePageIs('/user/' . $newUser->id)
77 + ->see($newUser->name);
78 + }
79 +
80 +}