Dan Brown

Merge branch 'master' into release

Showing 50 changed files with 1012 additions and 477 deletions
1 +<?php namespace BookStack\Exceptions;
2 +
3 +
4 +class AuthException extends PrettyException {}
...\ No newline at end of file ...\ No newline at end of file
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
2 2
3 namespace BookStack\Http\Controllers\Auth; 3 namespace BookStack\Http\Controllers\Auth;
4 4
5 +use BookStack\Exceptions\AuthException;
6 +use BookStack\Exceptions\PrettyException;
5 use Illuminate\Contracts\Auth\Authenticatable; 7 use Illuminate\Contracts\Auth\Authenticatable;
6 use Illuminate\Http\Request; 8 use Illuminate\Http\Request;
7 use BookStack\Exceptions\SocialSignInException; 9 use BookStack\Exceptions\SocialSignInException;
...@@ -115,6 +117,7 @@ class AuthController extends Controller ...@@ -115,6 +117,7 @@ class AuthController extends Controller
115 * @param Request $request 117 * @param Request $request
116 * @param Authenticatable $user 118 * @param Authenticatable $user
117 * @return \Illuminate\Http\RedirectResponse 119 * @return \Illuminate\Http\RedirectResponse
120 + * @throws AuthException
118 */ 121 */
119 protected function authenticated(Request $request, Authenticatable $user) 122 protected function authenticated(Request $request, Authenticatable $user)
120 { 123 {
...@@ -132,6 +135,13 @@ class AuthController extends Controller ...@@ -132,6 +135,13 @@ class AuthController extends Controller
132 } 135 }
133 136
134 if (!$user->exists) { 137 if (!$user->exists) {
138 +
139 + // Check for users with same email already
140 + $alreadyUser = $user->newQuery()->where('email', '=', $user->email)->count() > 0;
141 + if ($alreadyUser) {
142 + throw new AuthException('A user with the email ' . $user->email . ' already exists but with different credentials.');
143 + }
144 +
135 $user->save(); 145 $user->save();
136 $this->userRepo->attachDefaultRole($user); 146 $this->userRepo->attachDefaultRole($user);
137 auth()->login($user); 147 auth()->login($user);
...@@ -184,14 +194,11 @@ class AuthController extends Controller ...@@ -184,14 +194,11 @@ class AuthController extends Controller
184 } 194 }
185 195
186 if (setting('registration-confirmation') || setting('registration-restrict')) { 196 if (setting('registration-confirmation') || setting('registration-restrict')) {
187 - $newUser->email_confirmed = false;
188 $newUser->save(); 197 $newUser->save();
189 $this->emailConfirmationService->sendConfirmation($newUser); 198 $this->emailConfirmationService->sendConfirmation($newUser);
190 return redirect('/register/confirm'); 199 return redirect('/register/confirm');
191 } 200 }
192 201
193 - $newUser->email_confirmed = true;
194 -
195 auth()->login($newUser); 202 auth()->login($newUser);
196 session()->flash('success', 'Thanks for signing up! You are now registered and signed in.'); 203 session()->flash('success', 'Thanks for signing up! You are now registered and signed in.');
197 return redirect($this->redirectPath()); 204 return redirect($this->redirectPath());
......
1 -<?php 1 +<?php namespace BookStack\Http\Controllers;
2 -
3 -namespace BookStack\Http\Controllers;
4 2
5 use Activity; 3 use Activity;
6 use BookStack\Repos\UserRepo; 4 use BookStack\Repos\UserRepo;
7 use Illuminate\Http\Request; 5 use Illuminate\Http\Request;
8 -
9 use Illuminate\Support\Facades\Auth; 6 use Illuminate\Support\Facades\Auth;
10 -use Illuminate\Support\Str;
11 use BookStack\Http\Requests; 7 use BookStack\Http\Requests;
12 use BookStack\Repos\BookRepo; 8 use BookStack\Repos\BookRepo;
13 use BookStack\Repos\ChapterRepo; 9 use BookStack\Repos\ChapterRepo;
...@@ -40,7 +36,6 @@ class BookController extends Controller ...@@ -40,7 +36,6 @@ class BookController extends Controller
40 36
41 /** 37 /**
42 * Display a listing of the book. 38 * Display a listing of the book.
43 - *
44 * @return Response 39 * @return Response
45 */ 40 */
46 public function index() 41 public function index()
...@@ -54,7 +49,6 @@ class BookController extends Controller ...@@ -54,7 +49,6 @@ class BookController extends Controller
54 49
55 /** 50 /**
56 * Show the form for creating a new book. 51 * Show the form for creating a new book.
57 - *
58 * @return Response 52 * @return Response
59 */ 53 */
60 public function create() 54 public function create()
...@@ -88,7 +82,6 @@ class BookController extends Controller ...@@ -88,7 +82,6 @@ class BookController extends Controller
88 82
89 /** 83 /**
90 * Display the specified book. 84 * Display the specified book.
91 - *
92 * @param $slug 85 * @param $slug
93 * @return Response 86 * @return Response
94 */ 87 */
...@@ -103,7 +96,6 @@ class BookController extends Controller ...@@ -103,7 +96,6 @@ class BookController extends Controller
103 96
104 /** 97 /**
105 * Show the form for editing the specified book. 98 * Show the form for editing the specified book.
106 - *
107 * @param $slug 99 * @param $slug
108 * @return Response 100 * @return Response
109 */ 101 */
...@@ -117,7 +109,6 @@ class BookController extends Controller ...@@ -117,7 +109,6 @@ class BookController extends Controller
117 109
118 /** 110 /**
119 * Update the specified book in storage. 111 * Update the specified book in storage.
120 - *
121 * @param Request $request 112 * @param Request $request
122 * @param $slug 113 * @param $slug
123 * @return Response 114 * @return Response
...@@ -267,7 +258,7 @@ class BookController extends Controller ...@@ -267,7 +258,7 @@ class BookController extends Controller
267 $book = $this->bookRepo->getBySlug($bookSlug); 258 $book = $this->bookRepo->getBySlug($bookSlug);
268 $this->checkOwnablePermission('restrictions-manage', $book); 259 $this->checkOwnablePermission('restrictions-manage', $book);
269 $this->bookRepo->updateRestrictionsFromRequest($request, $book); 260 $this->bookRepo->updateRestrictionsFromRequest($request, $book);
270 - session()->flash('success', 'Page Restrictions Updated'); 261 + session()->flash('success', 'Book Restrictions Updated');
271 return redirect($book->getUrl()); 262 return redirect($book->getUrl());
272 } 263 }
273 } 264 }
......
...@@ -187,7 +187,7 @@ class ChapterController extends Controller ...@@ -187,7 +187,7 @@ class ChapterController extends Controller
187 $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id); 187 $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
188 $this->checkOwnablePermission('restrictions-manage', $chapter); 188 $this->checkOwnablePermission('restrictions-manage', $chapter);
189 $this->chapterRepo->updateRestrictionsFromRequest($request, $chapter); 189 $this->chapterRepo->updateRestrictionsFromRequest($request, $chapter);
190 - session()->flash('success', 'Page Restrictions Updated'); 190 + session()->flash('success', 'Chapter Restrictions Updated');
191 return redirect($chapter->getUrl()); 191 return redirect($chapter->getUrl());
192 } 192 }
193 } 193 }
......
1 -<?php 1 +<?php namespace BookStack\Http\Controllers;
2 -
3 -namespace BookStack\Http\Controllers;
4 2
5 use BookStack\Exceptions\ImageUploadException; 3 use BookStack\Exceptions\ImageUploadException;
6 use BookStack\Repos\ImageRepo; 4 use BookStack\Repos\ImageRepo;
7 use Illuminate\Filesystem\Filesystem as File; 5 use Illuminate\Filesystem\Filesystem as File;
8 use Illuminate\Http\Request; 6 use Illuminate\Http\Request;
9 -use Illuminate\Support\Facades\Auth;
10 -use Intervention\Image\Facades\Image as ImageTool;
11 -use Illuminate\Support\Facades\DB;
12 use BookStack\Image; 7 use BookStack\Image;
13 use BookStack\Repos\PageRepo; 8 use BookStack\Repos\PageRepo;
14 9
...@@ -45,6 +40,24 @@ class ImageController extends Controller ...@@ -45,6 +40,24 @@ class ImageController extends Controller
45 } 40 }
46 41
47 /** 42 /**
43 + * Search through images within a particular type.
44 + * @param $type
45 + * @param int $page
46 + * @param Request $request
47 + * @return mixed
48 + */
49 + public function searchByType($type, $page = 0, Request $request)
50 + {
51 + $this->validate($request, [
52 + 'term' => 'required|string'
53 + ]);
54 +
55 + $searchTerm = $request->get('term');
56 + $imgData = $this->imageRepo->searchPaginatedByType($type, $page,24, $searchTerm);
57 + return response()->json($imgData);
58 + }
59 +
60 + /**
48 * Get all images for a user. 61 * Get all images for a user.
49 * @param int $page 62 * @param int $page
50 * @return \Illuminate\Http\JsonResponse 63 * @return \Illuminate\Http\JsonResponse
...@@ -56,6 +69,27 @@ class ImageController extends Controller ...@@ -56,6 +69,27 @@ class ImageController extends Controller
56 } 69 }
57 70
58 /** 71 /**
72 + * Get gallery images with a specific filter such as book or page
73 + * @param $filter
74 + * @param int $page
75 + * @param Request $request
76 + */
77 + public function getGalleryFiltered($filter, $page = 0, Request $request)
78 + {
79 + $this->validate($request, [
80 + 'page_id' => 'required|integer'
81 + ]);
82 +
83 + $validFilters = collect(['page', 'book']);
84 + if (!$validFilters->contains($filter)) return response('Invalid filter', 500);
85 +
86 + $pageId = $request->get('page_id');
87 + $imgData = $this->imageRepo->getGalleryFiltered($page, 24, strtolower($filter), $pageId);
88 +
89 + return response()->json($imgData);
90 + }
91 +
92 + /**
59 * Handles image uploads for use on pages. 93 * Handles image uploads for use on pages.
60 * @param string $type 94 * @param string $type
61 * @param Request $request 95 * @param Request $request
......
...@@ -4,6 +4,7 @@ use Activity; ...@@ -4,6 +4,7 @@ use Activity;
4 use BookStack\Exceptions\NotFoundException; 4 use BookStack\Exceptions\NotFoundException;
5 use BookStack\Repos\UserRepo; 5 use BookStack\Repos\UserRepo;
6 use BookStack\Services\ExportService; 6 use BookStack\Services\ExportService;
7 +use Carbon\Carbon;
7 use Illuminate\Http\Request; 8 use Illuminate\Http\Request;
8 use BookStack\Http\Requests; 9 use BookStack\Http\Requests;
9 use BookStack\Repos\BookRepo; 10 use BookStack\Repos\BookRepo;
...@@ -88,7 +89,6 @@ class PageController extends Controller ...@@ -88,7 +89,6 @@ class PageController extends Controller
88 89
89 $input = $request->all(); 90 $input = $request->all();
90 $book = $this->bookRepo->getBySlug($bookSlug); 91 $book = $this->bookRepo->getBySlug($bookSlug);
91 - $input['priority'] = $this->bookRepo->getNewPriority($book);
92 92
93 $draftPage = $this->pageRepo->getById($pageId, true); 93 $draftPage = $this->pageRepo->getById($pageId, true);
94 94
...@@ -96,6 +96,12 @@ class PageController extends Controller ...@@ -96,6 +96,12 @@ class PageController extends Controller
96 $parent = $chapterId !== 0 ? $this->chapterRepo->getById($chapterId) : $book; 96 $parent = $chapterId !== 0 ? $this->chapterRepo->getById($chapterId) : $book;
97 $this->checkOwnablePermission('page-create', $parent); 97 $this->checkOwnablePermission('page-create', $parent);
98 98
99 + if ($parent->isA('chapter')) {
100 + $input['priority'] = $this->chapterRepo->getNewPriority($parent);
101 + } else {
102 + $input['priority'] = $this->bookRepo->getNewPriority($parent);
103 + }
104 +
99 $page = $this->pageRepo->publishDraft($draftPage, $input); 105 $page = $this->pageRepo->publishDraft($draftPage, $input);
100 106
101 Activity::add($page, 'page_create', $book->id); 107 Activity::add($page, 'page_create', $book->id);
...@@ -164,6 +170,7 @@ class PageController extends Controller ...@@ -164,6 +170,7 @@ class PageController extends Controller
164 $draft = $this->pageRepo->getUserPageDraft($page, $this->currentUser->id); 170 $draft = $this->pageRepo->getUserPageDraft($page, $this->currentUser->id);
165 $page->name = $draft->name; 171 $page->name = $draft->name;
166 $page->html = $draft->html; 172 $page->html = $draft->html;
173 + $page->markdown = $draft->markdown;
167 $page->isDraft = true; 174 $page->isDraft = true;
168 $warnings [] = $this->pageRepo->getUserPageDraftMessage($draft); 175 $warnings [] = $this->pageRepo->getUserPageDraftMessage($draft);
169 } 176 }
...@@ -204,12 +211,18 @@ class PageController extends Controller ...@@ -204,12 +211,18 @@ class PageController extends Controller
204 $page = $this->pageRepo->getById($pageId, true); 211 $page = $this->pageRepo->getById($pageId, true);
205 $this->checkOwnablePermission('page-update', $page); 212 $this->checkOwnablePermission('page-update', $page);
206 if ($page->draft) { 213 if ($page->draft) {
207 - $draft = $this->pageRepo->updateDraftPage($page, $request->only(['name', 'html'])); 214 + $draft = $this->pageRepo->updateDraftPage($page, $request->only(['name', 'html', 'markdown']));
208 } else { 215 } else {
209 - $draft = $this->pageRepo->saveUpdateDraft($page, $request->only(['name', 'html'])); 216 + $draft = $this->pageRepo->saveUpdateDraft($page, $request->only(['name', 'html', 'markdown']));
210 } 217 }
211 - $updateTime = $draft->updated_at->format('H:i'); 218 +
212 - return response()->json(['status' => 'success', 'message' => 'Draft saved at ' . $updateTime]); 219 + $updateTime = $draft->updated_at->timestamp;
220 + $utcUpdateTimestamp = $updateTime + Carbon::createFromTimestamp(0)->offset;
221 + return response()->json([
222 + 'status' => 'success',
223 + 'message' => 'Draft saved at ',
224 + 'timestamp' => $utcUpdateTimestamp
225 + ]);
213 } 226 }
214 227
215 /** 228 /**
......
...@@ -11,14 +11,12 @@ class Authenticate ...@@ -11,14 +11,12 @@ class Authenticate
11 { 11 {
12 /** 12 /**
13 * The Guard implementation. 13 * The Guard implementation.
14 - *
15 * @var Guard 14 * @var Guard
16 */ 15 */
17 protected $auth; 16 protected $auth;
18 17
19 /** 18 /**
20 * Create a new filter instance. 19 * Create a new filter instance.
21 - *
22 * @param Guard $auth 20 * @param Guard $auth
23 */ 21 */
24 public function __construct(Guard $auth) 22 public function __construct(Guard $auth)
...@@ -28,14 +26,13 @@ class Authenticate ...@@ -28,14 +26,13 @@ class Authenticate
28 26
29 /** 27 /**
30 * Handle an incoming request. 28 * Handle an incoming request.
31 - *
32 * @param \Illuminate\Http\Request $request 29 * @param \Illuminate\Http\Request $request
33 * @param \Closure $next 30 * @param \Closure $next
34 * @return mixed 31 * @return mixed
35 */ 32 */
36 public function handle($request, Closure $next) 33 public function handle($request, Closure $next)
37 { 34 {
38 - if(auth()->check() && auth()->user()->email_confirmed == false) { 35 + if ($this->auth->check() && setting('registration-confirmation') && !$this->auth->user()->email_confirmed) {
39 return redirect()->guest('/register/confirm/awaiting'); 36 return redirect()->guest('/register/confirm/awaiting');
40 } 37 }
41 38
......
...@@ -75,6 +75,8 @@ Route::group(['middleware' => 'auth'], function () { ...@@ -75,6 +75,8 @@ Route::group(['middleware' => 'auth'], function () {
75 Route::post('/{type}/upload', 'ImageController@uploadByType'); 75 Route::post('/{type}/upload', 'ImageController@uploadByType');
76 Route::get('/{type}/all', 'ImageController@getAllByType'); 76 Route::get('/{type}/all', 'ImageController@getAllByType');
77 Route::get('/{type}/all/{page}', 'ImageController@getAllByType'); 77 Route::get('/{type}/all/{page}', 'ImageController@getAllByType');
78 + Route::get('/{type}/search/{page}', 'ImageController@searchByType');
79 + Route::get('/gallery/{filter}/{page}', 'ImageController@getGalleryFiltered');
78 Route::delete('/{imageId}', 'ImageController@destroy'); 80 Route::delete('/{imageId}', 'ImageController@destroy');
79 }); 81 });
80 82
......
...@@ -6,7 +6,7 @@ use Illuminate\Database\Eloquent\Model; ...@@ -6,7 +6,7 @@ use Illuminate\Database\Eloquent\Model;
6 6
7 class Page extends Entity 7 class Page extends Entity
8 { 8 {
9 - protected $fillable = ['name', 'html', 'priority']; 9 + protected $fillable = ['name', 'html', 'priority', 'markdown'];
10 10
11 protected $simpleAttributes = ['name', 'id', 'slug']; 11 protected $simpleAttributes = ['name', 'id', 'slug'];
12 12
......
...@@ -4,7 +4,7 @@ use Illuminate\Database\Eloquent\Model; ...@@ -4,7 +4,7 @@ use Illuminate\Database\Eloquent\Model;
4 4
5 class PageRevision extends Model 5 class PageRevision extends Model
6 { 6 {
7 - protected $fillable = ['name', 'html', 'text']; 7 + protected $fillable = ['name', 'html', 'text', 'markdown'];
8 8
9 /** 9 /**
10 * Get the user that created the page revision 10 * Get the user that created the page revision
......
...@@ -11,7 +11,7 @@ class Permission extends Model ...@@ -11,7 +11,7 @@ class Permission extends Model
11 */ 11 */
12 public function roles() 12 public function roles()
13 { 13 {
14 - return $this->belongsToMany('BookStack\Permissions'); 14 + return $this->belongsToMany('BookStack\Role');
15 } 15 }
16 16
17 /** 17 /**
......
...@@ -115,7 +115,7 @@ class LdapUserProvider implements UserProvider ...@@ -115,7 +115,7 @@ class LdapUserProvider implements UserProvider
115 $model->name = $userDetails['name']; 115 $model->name = $userDetails['name'];
116 $model->external_auth_id = $userDetails['uid']; 116 $model->external_auth_id = $userDetails['uid'];
117 $model->email = $userDetails['email']; 117 $model->email = $userDetails['email'];
118 - $model->email_confirmed = true; 118 + $model->email_confirmed = false;
119 return $model; 119 return $model;
120 } 120 }
121 121
......
...@@ -137,6 +137,18 @@ class ChapterRepo extends EntityRepo ...@@ -137,6 +137,18 @@ class ChapterRepo extends EntityRepo
137 } 137 }
138 138
139 /** 139 /**
140 + * Get a new priority value for a new page to be added
141 + * to the given chapter.
142 + * @param Chapter $chapter
143 + * @return int
144 + */
145 + public function getNewPriority(Chapter $chapter)
146 + {
147 + $lastPage = $chapter->pages->last();
148 + return $lastPage !== null ? $lastPage->priority + 1 : 0;
149 + }
150 +
151 + /**
140 * Get chapters by the given search term. 152 * Get chapters by the given search term.
141 * @param string $term 153 * @param string $term
142 * @param array $whereTerms 154 * @param array $whereTerms
......
...@@ -84,7 +84,7 @@ class EntityRepo ...@@ -84,7 +84,7 @@ class EntityRepo
84 if ($additionalQuery !== false && is_callable($additionalQuery)) { 84 if ($additionalQuery !== false && is_callable($additionalQuery)) {
85 $additionalQuery($query); 85 $additionalQuery($query);
86 } 86 }
87 - return $query->skip($page * $count)->take($count)->get(); 87 + return $query->with('book')->skip($page * $count)->take($count)->get();
88 } 88 }
89 89
90 /** 90 /**
...@@ -114,7 +114,7 @@ class EntityRepo ...@@ -114,7 +114,7 @@ class EntityRepo
114 { 114 {
115 return $this->restrictionService->enforcePageRestrictions($this->page) 115 return $this->restrictionService->enforcePageRestrictions($this->page)
116 ->where('draft', '=', false) 116 ->where('draft', '=', false)
117 - ->orderBy('updated_at', 'desc')->skip($page * $count)->take($count)->get(); 117 + ->orderBy('updated_at', 'desc')->with('book')->skip($page * $count)->take($count)->get();
118 } 118 }
119 119
120 /** 120 /**
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
2 2
3 3
4 use BookStack\Image; 4 use BookStack\Image;
5 +use BookStack\Page;
5 use BookStack\Services\ImageService; 6 use BookStack\Services\ImageService;
6 use BookStack\Services\RestrictionService; 7 use BookStack\Services\RestrictionService;
7 use Setting; 8 use Setting;
...@@ -13,18 +14,21 @@ class ImageRepo ...@@ -13,18 +14,21 @@ class ImageRepo
13 protected $image; 14 protected $image;
14 protected $imageService; 15 protected $imageService;
15 protected $restictionService; 16 protected $restictionService;
17 + protected $page;
16 18
17 /** 19 /**
18 * ImageRepo constructor. 20 * ImageRepo constructor.
19 * @param Image $image 21 * @param Image $image
20 * @param ImageService $imageService 22 * @param ImageService $imageService
21 * @param RestrictionService $restrictionService 23 * @param RestrictionService $restrictionService
24 + * @param Page $page
22 */ 25 */
23 - public function __construct(Image $image, ImageService $imageService, RestrictionService $restrictionService) 26 + public function __construct(Image $image, ImageService $imageService, RestrictionService $restrictionService, Page $page)
24 { 27 {
25 $this->image = $image; 28 $this->image = $image;
26 $this->imageService = $imageService; 29 $this->imageService = $imageService;
27 $this->restictionService = $restrictionService; 30 $this->restictionService = $restrictionService;
31 + $this->page = $page;
28 } 32 }
29 33
30 34
...@@ -39,22 +43,16 @@ class ImageRepo ...@@ -39,22 +43,16 @@ class ImageRepo
39 } 43 }
40 44
41 /** 45 /**
42 - * Gets a load images paginated, filtered by image type. 46 + * Execute a paginated query, returning in a standard format.
43 - * @param string $type 47 + * Also runs the query through the restriction system.
48 + * @param $query
44 * @param int $page 49 * @param int $page
45 * @param int $pageSize 50 * @param int $pageSize
46 - * @param bool|int $userFilter
47 * @return array 51 * @return array
48 */ 52 */
49 - public function getPaginatedByType($type, $page = 0, $pageSize = 24, $userFilter = false) 53 + private function returnPaginated($query, $page = 0, $pageSize = 24)
50 { 54 {
51 - $images = $this->image->where('type', '=', strtolower($type)); 55 + $images = $this->restictionService->filterRelatedPages($query, 'images', 'uploaded_to');
52 -
53 - if ($userFilter !== false) {
54 - $images = $images->where('created_by', '=', $userFilter);
55 - }
56 -
57 - $images = $this->restictionService->filterRelatedPages($images, 'images', 'uploaded_to');
58 $images = $images->orderBy('created_at', 'desc')->skip($pageSize * $page)->take($pageSize + 1)->get(); 56 $images = $images->orderBy('created_at', 'desc')->skip($pageSize * $page)->take($pageSize + 1)->get();
59 $hasMore = count($images) > $pageSize; 57 $hasMore = count($images) > $pageSize;
60 58
...@@ -70,6 +68,64 @@ class ImageRepo ...@@ -70,6 +68,64 @@ class ImageRepo
70 } 68 }
71 69
72 /** 70 /**
71 + * Gets a load images paginated, filtered by image type.
72 + * @param string $type
73 + * @param int $page
74 + * @param int $pageSize
75 + * @param bool|int $userFilter
76 + * @return array
77 + */
78 + public function getPaginatedByType($type, $page = 0, $pageSize = 24, $userFilter = false)
79 + {
80 + $images = $this->image->where('type', '=', strtolower($type));
81 +
82 + if ($userFilter !== false) {
83 + $images = $images->where('created_by', '=', $userFilter);
84 + }
85 +
86 + return $this->returnPaginated($images, $page, $pageSize);
87 + }
88 +
89 + /**
90 + * Search for images by query, of a particular type.
91 + * @param string $type
92 + * @param int $page
93 + * @param int $pageSize
94 + * @param string $searchTerm
95 + * @return array
96 + */
97 + public function searchPaginatedByType($type, $page = 0, $pageSize = 24, $searchTerm)
98 + {
99 + $images = $this->image->where('type', '=', strtolower($type))->where('name', 'LIKE', '%' . $searchTerm . '%');
100 + return $this->returnPaginated($images, $page, $pageSize);
101 + }
102 +
103 + /**
104 + * Get gallery images with a particular filter criteria such as
105 + * being within the current book or page.
106 + * @param int $pagination
107 + * @param int $pageSize
108 + * @param $filter
109 + * @param $pageId
110 + * @return array
111 + */
112 + public function getGalleryFiltered($pagination = 0, $pageSize = 24, $filter, $pageId)
113 + {
114 + $images = $this->image->where('type', '=', 'gallery');
115 +
116 + $page = $this->page->findOrFail($pageId);
117 +
118 + if ($filter === 'page') {
119 + $images = $images->where('uploaded_to', '=', $page->id);
120 + } elseif ($filter === 'book') {
121 + $validPageIds = $page->book->pages->pluck('id')->toArray();
122 + $images = $images->whereIn('uploaded_to', $validPageIds);
123 + }
124 +
125 + return $this->returnPaginated($images, $pagination, $pageSize);
126 + }
127 +
128 + /**
73 * Save a new image into storage and return the new image. 129 * Save a new image into storage and return the new image.
74 * @param UploadedFile $uploadFile 130 * @param UploadedFile $uploadFile
75 * @param string $type 131 * @param string $type
......
...@@ -312,6 +312,7 @@ class PageRepo extends EntityRepo ...@@ -312,6 +312,7 @@ class PageRepo extends EntityRepo
312 $page->fill($input); 312 $page->fill($input);
313 $page->html = $this->formatHtml($input['html']); 313 $page->html = $this->formatHtml($input['html']);
314 $page->text = strip_tags($page->html); 314 $page->text = strip_tags($page->html);
315 + if (setting('app-editor') !== 'markdown') $page->markdown = '';
315 $page->updated_by = $userId; 316 $page->updated_by = $userId;
316 $page->save(); 317 $page->save();
317 318
...@@ -348,6 +349,7 @@ class PageRepo extends EntityRepo ...@@ -348,6 +349,7 @@ class PageRepo extends EntityRepo
348 public function saveRevision(Page $page) 349 public function saveRevision(Page $page)
349 { 350 {
350 $revision = $this->pageRevision->fill($page->toArray()); 351 $revision = $this->pageRevision->fill($page->toArray());
352 + if (setting('app-editor') !== 'markdown') $revision->markdown = '';
351 $revision->page_id = $page->id; 353 $revision->page_id = $page->id;
352 $revision->slug = $page->slug; 354 $revision->slug = $page->slug;
353 $revision->book_slug = $page->book->slug; 355 $revision->book_slug = $page->book->slug;
...@@ -386,6 +388,8 @@ class PageRepo extends EntityRepo ...@@ -386,6 +388,8 @@ class PageRepo extends EntityRepo
386 } 388 }
387 389
388 $draft->fill($data); 390 $draft->fill($data);
391 + if (setting('app-editor') !== 'markdown') $draft->markdown = '';
392 +
389 $draft->save(); 393 $draft->save();
390 return $draft; 394 return $draft;
391 } 395 }
......
...@@ -106,7 +106,8 @@ class UserRepo ...@@ -106,7 +106,8 @@ class UserRepo
106 return $this->user->forceCreate([ 106 return $this->user->forceCreate([
107 'name' => $data['name'], 107 'name' => $data['name'],
108 'email' => $data['email'], 108 'email' => $data['email'],
109 - 'password' => bcrypt($data['password']) 109 + 'password' => bcrypt($data['password']),
110 + 'email_confirmed' => false
110 ]); 111 ]);
111 } 112 }
112 113
......
...@@ -44,6 +44,15 @@ class Role extends Model ...@@ -44,6 +44,15 @@ class Role extends Model
44 } 44 }
45 45
46 /** 46 /**
47 + * Detach a single permission from this role.
48 + * @param Permission $permission
49 + */
50 + public function detachPermission(Permission $permission)
51 + {
52 + $this->permissions()->detach($permission->id);
53 + }
54 +
55 + /**
47 * Get the role object for the specified role. 56 * Get the role object for the specified role.
48 * @param $roleName 57 * @param $roleName
49 * @return mixed 58 * @return mixed
......
...@@ -44,28 +44,39 @@ class SettingService ...@@ -44,28 +44,39 @@ class SettingService
44 44
45 /** 45 /**
46 * Gets a setting value from the cache or database. 46 * Gets a setting value from the cache or database.
47 + * Looks at the system defaults if not cached or in database.
47 * @param $key 48 * @param $key
48 * @param $default 49 * @param $default
49 * @return mixed 50 * @return mixed
50 */ 51 */
51 protected function getValueFromStore($key, $default) 52 protected function getValueFromStore($key, $default)
52 { 53 {
54 + // Check for an overriding value
53 $overrideValue = $this->getOverrideValue($key); 55 $overrideValue = $this->getOverrideValue($key);
54 if ($overrideValue !== null) return $overrideValue; 56 if ($overrideValue !== null) return $overrideValue;
55 57
58 + // Check the cache
56 $cacheKey = $this->cachePrefix . $key; 59 $cacheKey = $this->cachePrefix . $key;
57 if ($this->cache->has($cacheKey)) { 60 if ($this->cache->has($cacheKey)) {
58 return $this->cache->get($cacheKey); 61 return $this->cache->get($cacheKey);
59 } 62 }
60 63
64 + // Check the database
61 $settingObject = $this->getSettingObjectByKey($key); 65 $settingObject = $this->getSettingObjectByKey($key);
62 -
63 if ($settingObject !== null) { 66 if ($settingObject !== null) {
64 $value = $settingObject->value; 67 $value = $settingObject->value;
65 $this->cache->forever($cacheKey, $value); 68 $this->cache->forever($cacheKey, $value);
66 return $value; 69 return $value;
67 } 70 }
68 71
72 + // Check the defaults set in the app config.
73 + $configPrefix = 'setting-defaults.' . $key;
74 + if (config()->has($configPrefix)) {
75 + $value = config($configPrefix);
76 + $this->cache->forever($cacheKey, $value);
77 + return $value;
78 + }
79 +
69 return $default; 80 return $default;
70 } 81 }
71 82
......
1 <?php namespace BookStack\Services; 1 <?php namespace BookStack\Services;
2 2
3 -
4 use BookStack\Entity; 3 use BookStack\Entity;
5 use BookStack\View; 4 use BookStack\View;
6 5
...@@ -47,7 +46,6 @@ class ViewService ...@@ -47,7 +46,6 @@ class ViewService
47 return 1; 46 return 1;
48 } 47 }
49 48
50 -
51 /** 49 /**
52 * Get the entities with the most views. 50 * Get the entities with the most views.
53 * @param int $count 51 * @param int $count
...@@ -58,17 +56,13 @@ class ViewService ...@@ -58,17 +56,13 @@ class ViewService
58 { 56 {
59 $skipCount = $count * $page; 57 $skipCount = $count * $page;
60 $query = $this->restrictionService->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type') 58 $query = $this->restrictionService->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type')
61 - ->select('id', 'viewable_id', 'viewable_type', \DB::raw('SUM(views) as view_count')) 59 + ->select('*', 'viewable_id', 'viewable_type', \DB::raw('SUM(views) as view_count'))
62 ->groupBy('viewable_id', 'viewable_type') 60 ->groupBy('viewable_id', 'viewable_type')
63 ->orderBy('view_count', 'desc'); 61 ->orderBy('view_count', 'desc');
64 62
65 if ($filterModel) $query->where('viewable_type', '=', get_class($filterModel)); 63 if ($filterModel) $query->where('viewable_type', '=', get_class($filterModel));
66 64
67 - $views = $query->with('viewable')->skip($skipCount)->take($count)->get(); 65 + return $query->with('viewable')->skip($skipCount)->take($count)->get()->pluck('viewable');
68 - $viewedEntities = $views->map(function ($item) {
69 - return $item->viewable()->getResults();
70 - });
71 - return $viewedEntities;
72 } 66 }
73 67
74 /** 68 /**
...@@ -81,21 +75,18 @@ class ViewService ...@@ -81,21 +75,18 @@ class ViewService
81 public function getUserRecentlyViewed($count = 10, $page = 0, $filterModel = false) 75 public function getUserRecentlyViewed($count = 10, $page = 0, $filterModel = false)
82 { 76 {
83 if ($this->user === null) return collect(); 77 if ($this->user === null) return collect();
84 - $skipCount = $count * $page; 78 +
85 $query = $this->restrictionService 79 $query = $this->restrictionService
86 ->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type'); 80 ->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type');
87 81
88 if ($filterModel) $query = $query->where('viewable_type', '=', get_class($filterModel)); 82 if ($filterModel) $query = $query->where('viewable_type', '=', get_class($filterModel));
89 $query = $query->where('user_id', '=', auth()->user()->id); 83 $query = $query->where('user_id', '=', auth()->user()->id);
90 84
91 - $views = $query->with('viewable')->orderBy('updated_at', 'desc')->skip($skipCount)->take($count)->get(); 85 + $viewables = $query->with('viewable')->orderBy('updated_at', 'desc')
92 - $viewedEntities = $views->map(function ($item) { 86 + ->skip($count * $page)->take($count)->get()->pluck('viewable');
93 - return $item->viewable; 87 + return $viewables;
94 - });
95 - return $viewedEntities;
96 } 88 }
97 89
98 -
99 /** 90 /**
100 * Reset all view counts by deleting all views. 91 * Reset all view counts by deleting all views.
101 */ 92 */
...@@ -104,5 +95,4 @@ class ViewService ...@@ -104,5 +95,4 @@ class ViewService
104 $this->view->truncate(); 95 $this->view->truncate();
105 } 96 }
106 97
107 -
108 } 98 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -12,7 +12,8 @@ ...@@ -12,7 +12,8 @@
12 "barryvdh/laravel-ide-helper": "^2.1", 12 "barryvdh/laravel-ide-helper": "^2.1",
13 "barryvdh/laravel-debugbar": "^2.0", 13 "barryvdh/laravel-debugbar": "^2.0",
14 "league/flysystem-aws-s3-v3": "^1.0", 14 "league/flysystem-aws-s3-v3": "^1.0",
15 - "barryvdh/laravel-dompdf": "0.6.*" 15 + "barryvdh/laravel-dompdf": "0.6.*",
16 + "predis/predis": "^1.0"
16 }, 17 },
17 "require-dev": { 18 "require-dev": {
18 "fzaninotto/faker": "~1.4", 19 "fzaninotto/faker": "~1.4",
......
...@@ -4,21 +4,21 @@ ...@@ -4,21 +4,21 @@
4 "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 4 "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
5 "This file is @generated automatically" 5 "This file is @generated automatically"
6 ], 6 ],
7 - "hash": "523e654de96df9259fa5dfcb583d6e3e", 7 + "hash": "eb7c71e9ed116d3fd2a1d0af07f9f134",
8 - "content-hash": "74b5601c253aab71cf55e0885f31ae7f", 8 + "content-hash": "17d2d7fc5fed682f2a290d6588538035",
9 "packages": [ 9 "packages": [
10 { 10 {
11 "name": "aws/aws-sdk-php", 11 "name": "aws/aws-sdk-php",
12 - "version": "3.15.1", 12 + "version": "3.17.5",
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": "5e6078913293576de969703481994b77c380ca30" 16 + "reference": "1cef9b334729b3564c9aef15481a55561c54b53f"
17 }, 17 },
18 "dist": { 18 "dist": {
19 "type": "zip", 19 "type": "zip",
20 - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/5e6078913293576de969703481994b77c380ca30", 20 + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/1cef9b334729b3564c9aef15481a55561c54b53f",
21 - "reference": "5e6078913293576de969703481994b77c380ca30", 21 + "reference": "1cef9b334729b3564c9aef15481a55561c54b53f",
22 "shasum": "" 22 "shasum": ""
23 }, 23 },
24 "require": { 24 "require": {
...@@ -85,27 +85,27 @@ ...@@ -85,27 +85,27 @@
85 "s3", 85 "s3",
86 "sdk" 86 "sdk"
87 ], 87 ],
88 - "time": "2016-02-11 23:23:31" 88 + "time": "2016-04-07 22:44:13"
89 }, 89 },
90 { 90 {
91 "name": "barryvdh/laravel-debugbar", 91 "name": "barryvdh/laravel-debugbar",
92 - "version": "v2.1.1", 92 + "version": "v2.2.0",
93 "source": { 93 "source": {
94 "type": "git", 94 "type": "git",
95 "url": "https://github.com/barryvdh/laravel-debugbar.git", 95 "url": "https://github.com/barryvdh/laravel-debugbar.git",
96 - "reference": "974fd16e328ca851a081449100d9509af59cf0ff" 96 + "reference": "13b7058d2120c8d5af7f1ada21b7c44dd87b666a"
97 }, 97 },
98 "dist": { 98 "dist": {
99 "type": "zip", 99 "type": "zip",
100 - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/974fd16e328ca851a081449100d9509af59cf0ff", 100 + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/13b7058d2120c8d5af7f1ada21b7c44dd87b666a",
101 - "reference": "974fd16e328ca851a081449100d9509af59cf0ff", 101 + "reference": "13b7058d2120c8d5af7f1ada21b7c44dd87b666a",
102 "shasum": "" 102 "shasum": ""
103 }, 103 },
104 "require": { 104 "require": {
105 - "illuminate/support": "~5.0.17|5.1.*|5.2.*", 105 + "illuminate/support": "5.1.*|5.2.*",
106 "maximebf/debugbar": "~1.11.0", 106 "maximebf/debugbar": "~1.11.0",
107 - "php": ">=5.4.0", 107 + "php": ">=5.5.9",
108 - "symfony/finder": "~2.6|~3.0" 108 + "symfony/finder": "~2.7|~3.0"
109 }, 109 },
110 "type": "library", 110 "type": "library",
111 "extra": { 111 "extra": {
...@@ -139,7 +139,7 @@ ...@@ -139,7 +139,7 @@
139 "profiler", 139 "profiler",
140 "webprofiler" 140 "webprofiler"
141 ], 141 ],
142 - "time": "2015-12-22 06:22:38" 142 + "time": "2016-02-17 08:32:21"
143 }, 143 },
144 { 144 {
145 "name": "barryvdh/laravel-dompdf", 145 "name": "barryvdh/laravel-dompdf",
...@@ -191,16 +191,16 @@ ...@@ -191,16 +191,16 @@
191 }, 191 },
192 { 192 {
193 "name": "barryvdh/laravel-ide-helper", 193 "name": "barryvdh/laravel-ide-helper",
194 - "version": "v2.1.2", 194 + "version": "v2.1.4",
195 "source": { 195 "source": {
196 "type": "git", 196 "type": "git",
197 "url": "https://github.com/barryvdh/laravel-ide-helper.git", 197 "url": "https://github.com/barryvdh/laravel-ide-helper.git",
198 - "reference": "d82e8f191fb043a0f8cbf2de64fd3027bfa4f772" 198 + "reference": "f1ebd847aac9a4545325d35108cafc285fe1605f"
199 }, 199 },
200 "dist": { 200 "dist": {
201 "type": "zip", 201 "type": "zip",
202 - "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/d82e8f191fb043a0f8cbf2de64fd3027bfa4f772", 202 + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/f1ebd847aac9a4545325d35108cafc285fe1605f",
203 - "reference": "d82e8f191fb043a0f8cbf2de64fd3027bfa4f772", 203 + "reference": "f1ebd847aac9a4545325d35108cafc285fe1605f",
204 "shasum": "" 204 "shasum": ""
205 }, 205 },
206 "require": { 206 "require": {
...@@ -208,8 +208,8 @@ ...@@ -208,8 +208,8 @@
208 "illuminate/filesystem": "5.0.x|5.1.x|5.2.x", 208 "illuminate/filesystem": "5.0.x|5.1.x|5.2.x",
209 "illuminate/support": "5.0.x|5.1.x|5.2.x", 209 "illuminate/support": "5.0.x|5.1.x|5.2.x",
210 "php": ">=5.4.0", 210 "php": ">=5.4.0",
211 - "phpdocumentor/reflection-docblock": "2.0.4", 211 + "phpdocumentor/reflection-docblock": "^2.0.4",
212 - "symfony/class-loader": "~2.3" 212 + "symfony/class-loader": "~2.3|~3.0"
213 }, 213 },
214 "require-dev": { 214 "require-dev": {
215 "doctrine/dbal": "~2.3" 215 "doctrine/dbal": "~2.3"
...@@ -250,7 +250,7 @@ ...@@ -250,7 +250,7 @@
250 "phpstorm", 250 "phpstorm",
251 "sublime" 251 "sublime"
252 ], 252 ],
253 - "time": "2015-12-21 19:48:06" 253 + "time": "2016-03-03 08:45:00"
254 }, 254 },
255 { 255 {
256 "name": "classpreloader/classpreloader", 256 "name": "classpreloader/classpreloader",
...@@ -541,16 +541,16 @@ ...@@ -541,16 +541,16 @@
541 }, 541 },
542 { 542 {
543 "name": "guzzlehttp/guzzle", 543 "name": "guzzlehttp/guzzle",
544 - "version": "6.1.1", 544 + "version": "6.2.0",
545 "source": { 545 "source": {
546 "type": "git", 546 "type": "git",
547 "url": "https://github.com/guzzle/guzzle.git", 547 "url": "https://github.com/guzzle/guzzle.git",
548 - "reference": "c6851d6e48f63b69357cbfa55bca116448140e0c" 548 + "reference": "d094e337976dff9d8e2424e8485872194e768662"
549 }, 549 },
550 "dist": { 550 "dist": {
551 "type": "zip", 551 "type": "zip",
552 - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/c6851d6e48f63b69357cbfa55bca116448140e0c", 552 + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d094e337976dff9d8e2424e8485872194e768662",
553 - "reference": "c6851d6e48f63b69357cbfa55bca116448140e0c", 553 + "reference": "d094e337976dff9d8e2424e8485872194e768662",
554 "shasum": "" 554 "shasum": ""
555 }, 555 },
556 "require": { 556 "require": {
...@@ -566,7 +566,7 @@ ...@@ -566,7 +566,7 @@
566 "type": "library", 566 "type": "library",
567 "extra": { 567 "extra": {
568 "branch-alias": { 568 "branch-alias": {
569 - "dev-master": "6.1-dev" 569 + "dev-master": "6.2-dev"
570 } 570 }
571 }, 571 },
572 "autoload": { 572 "autoload": {
...@@ -599,20 +599,20 @@ ...@@ -599,20 +599,20 @@
599 "rest", 599 "rest",
600 "web service" 600 "web service"
601 ], 601 ],
602 - "time": "2015-11-23 00:47:50" 602 + "time": "2016-03-21 20:02:09"
603 }, 603 },
604 { 604 {
605 "name": "guzzlehttp/promises", 605 "name": "guzzlehttp/promises",
606 - "version": "1.0.3", 606 + "version": "1.1.0",
607 "source": { 607 "source": {
608 "type": "git", 608 "type": "git",
609 "url": "https://github.com/guzzle/promises.git", 609 "url": "https://github.com/guzzle/promises.git",
610 - "reference": "b1e1c0d55f8083c71eda2c28c12a228d708294ea" 610 + "reference": "bb9024c526b22f3fe6ae55a561fd70653d470aa8"
611 }, 611 },
612 "dist": { 612 "dist": {
613 "type": "zip", 613 "type": "zip",
614 - "url": "https://api.github.com/repos/guzzle/promises/zipball/b1e1c0d55f8083c71eda2c28c12a228d708294ea", 614 + "url": "https://api.github.com/repos/guzzle/promises/zipball/bb9024c526b22f3fe6ae55a561fd70653d470aa8",
615 - "reference": "b1e1c0d55f8083c71eda2c28c12a228d708294ea", 615 + "reference": "bb9024c526b22f3fe6ae55a561fd70653d470aa8",
616 "shasum": "" 616 "shasum": ""
617 }, 617 },
618 "require": { 618 "require": {
...@@ -650,20 +650,20 @@ ...@@ -650,20 +650,20 @@
650 "keywords": [ 650 "keywords": [
651 "promise" 651 "promise"
652 ], 652 ],
653 - "time": "2015-10-15 22:28:00" 653 + "time": "2016-03-08 01:15:46"
654 }, 654 },
655 { 655 {
656 "name": "guzzlehttp/psr7", 656 "name": "guzzlehttp/psr7",
657 - "version": "1.2.2", 657 + "version": "1.2.3",
658 "source": { 658 "source": {
659 "type": "git", 659 "type": "git",
660 "url": "https://github.com/guzzle/psr7.git", 660 "url": "https://github.com/guzzle/psr7.git",
661 - "reference": "f5d04bdd2881ac89abde1fb78cc234bce24327bb" 661 + "reference": "2e89629ff057ebb49492ba08e6995d3a6a80021b"
662 }, 662 },
663 "dist": { 663 "dist": {
664 "type": "zip", 664 "type": "zip",
665 - "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5d04bdd2881ac89abde1fb78cc234bce24327bb", 665 + "url": "https://api.github.com/repos/guzzle/psr7/zipball/2e89629ff057ebb49492ba08e6995d3a6a80021b",
666 - "reference": "f5d04bdd2881ac89abde1fb78cc234bce24327bb", 666 + "reference": "2e89629ff057ebb49492ba08e6995d3a6a80021b",
667 "shasum": "" 667 "shasum": ""
668 }, 668 },
669 "require": { 669 "require": {
...@@ -708,20 +708,20 @@ ...@@ -708,20 +708,20 @@
708 "stream", 708 "stream",
709 "uri" 709 "uri"
710 ], 710 ],
711 - "time": "2016-01-23 01:23:02" 711 + "time": "2016-02-18 21:54:00"
712 }, 712 },
713 { 713 {
714 "name": "intervention/image", 714 "name": "intervention/image",
715 - "version": "2.3.5", 715 + "version": "2.3.6",
716 "source": { 716 "source": {
717 "type": "git", 717 "type": "git",
718 "url": "https://github.com/Intervention/image.git", 718 "url": "https://github.com/Intervention/image.git",
719 - "reference": "9f29360b8ab94585cb9e80cf9045abd5b85feb89" 719 + "reference": "e368d262887dbb2fdfaf710880571ede51e9c0e6"
720 }, 720 },
721 "dist": { 721 "dist": {
722 "type": "zip", 722 "type": "zip",
723 - "url": "https://api.github.com/repos/Intervention/image/zipball/9f29360b8ab94585cb9e80cf9045abd5b85feb89", 723 + "url": "https://api.github.com/repos/Intervention/image/zipball/e368d262887dbb2fdfaf710880571ede51e9c0e6",
724 - "reference": "9f29360b8ab94585cb9e80cf9045abd5b85feb89", 724 + "reference": "e368d262887dbb2fdfaf710880571ede51e9c0e6",
725 "shasum": "" 725 "shasum": ""
726 }, 726 },
727 "require": { 727 "require": {
...@@ -770,7 +770,7 @@ ...@@ -770,7 +770,7 @@
770 "thumbnail", 770 "thumbnail",
771 "watermark" 771 "watermark"
772 ], 772 ],
773 - "time": "2016-01-02 19:15:13" 773 + "time": "2016-02-26 18:18:19"
774 }, 774 },
775 { 775 {
776 "name": "jakub-onderka/php-console-color", 776 "name": "jakub-onderka/php-console-color",
...@@ -919,16 +919,16 @@ ...@@ -919,16 +919,16 @@
919 }, 919 },
920 { 920 {
921 "name": "laravel/framework", 921 "name": "laravel/framework",
922 - "version": "v5.2.16", 922 + "version": "v5.2.29",
923 "source": { 923 "source": {
924 "type": "git", 924 "type": "git",
925 "url": "https://github.com/laravel/framework.git", 925 "url": "https://github.com/laravel/framework.git",
926 - "reference": "39e89553c124dce266da03ee3c0260bdd62f1848" 926 + "reference": "e3d644eb131f18c5f3d28ff7bc678bc797091f20"
927 }, 927 },
928 "dist": { 928 "dist": {
929 "type": "zip", 929 "type": "zip",
930 - "url": "https://api.github.com/repos/laravel/framework/zipball/39e89553c124dce266da03ee3c0260bdd62f1848", 930 + "url": "https://api.github.com/repos/laravel/framework/zipball/e3d644eb131f18c5f3d28ff7bc678bc797091f20",
931 - "reference": "39e89553c124dce266da03ee3c0260bdd62f1848", 931 + "reference": "e3d644eb131f18c5f3d28ff7bc678bc797091f20",
932 "shasum": "" 932 "shasum": ""
933 }, 933 },
934 "require": { 934 "require": {
...@@ -941,9 +941,9 @@ ...@@ -941,9 +941,9 @@
941 "monolog/monolog": "~1.11", 941 "monolog/monolog": "~1.11",
942 "mtdowling/cron-expression": "~1.0", 942 "mtdowling/cron-expression": "~1.0",
943 "nesbot/carbon": "~1.20", 943 "nesbot/carbon": "~1.20",
944 - "paragonie/random_compat": "~1.1", 944 + "paragonie/random_compat": "~1.4",
945 "php": ">=5.5.9", 945 "php": ">=5.5.9",
946 - "psy/psysh": "0.6.*", 946 + "psy/psysh": "0.7.*",
947 "swiftmailer/swiftmailer": "~5.1", 947 "swiftmailer/swiftmailer": "~5.1",
948 "symfony/console": "2.8.*|3.0.*", 948 "symfony/console": "2.8.*|3.0.*",
949 "symfony/debug": "2.8.*|3.0.*", 949 "symfony/debug": "2.8.*|3.0.*",
...@@ -1043,20 +1043,20 @@ ...@@ -1043,20 +1043,20 @@
1043 "framework", 1043 "framework",
1044 "laravel" 1044 "laravel"
1045 ], 1045 ],
1046 - "time": "2016-02-15 17:46:58" 1046 + "time": "2016-04-03 01:43:55"
1047 }, 1047 },
1048 { 1048 {
1049 "name": "laravel/socialite", 1049 "name": "laravel/socialite",
1050 - "version": "v2.0.14", 1050 + "version": "v2.0.15",
1051 "source": { 1051 "source": {
1052 "type": "git", 1052 "type": "git",
1053 "url": "https://github.com/laravel/socialite.git", 1053 "url": "https://github.com/laravel/socialite.git",
1054 - "reference": "b15f4be0ac739405120d74b837af423aa71502d9" 1054 + "reference": "edd00ab96933e3ef053533cce81e958fb26921af"
1055 }, 1055 },
1056 "dist": { 1056 "dist": {
1057 "type": "zip", 1057 "type": "zip",
1058 - "url": "https://api.github.com/repos/laravel/socialite/zipball/b15f4be0ac739405120d74b837af423aa71502d9", 1058 + "url": "https://api.github.com/repos/laravel/socialite/zipball/edd00ab96933e3ef053533cce81e958fb26921af",
1059 - "reference": "b15f4be0ac739405120d74b837af423aa71502d9", 1059 + "reference": "edd00ab96933e3ef053533cce81e958fb26921af",
1060 "shasum": "" 1060 "shasum": ""
1061 }, 1061 },
1062 "require": { 1062 "require": {
...@@ -1097,20 +1097,20 @@ ...@@ -1097,20 +1097,20 @@
1097 "laravel", 1097 "laravel",
1098 "oauth" 1098 "oauth"
1099 ], 1099 ],
1100 - "time": "2015-10-16 15:39:46" 1100 + "time": "2016-03-21 14:30:30"
1101 }, 1101 },
1102 { 1102 {
1103 "name": "league/flysystem", 1103 "name": "league/flysystem",
1104 - "version": "1.0.16", 1104 + "version": "1.0.20",
1105 "source": { 1105 "source": {
1106 "type": "git", 1106 "type": "git",
1107 "url": "https://github.com/thephpleague/flysystem.git", 1107 "url": "https://github.com/thephpleague/flysystem.git",
1108 - "reference": "183e1a610664baf6dcd6fceda415baf43cbdc031" 1108 + "reference": "e87a786e3ae12a25cf78a71bb07b4b384bfaa83a"
1109 }, 1109 },
1110 "dist": { 1110 "dist": {
1111 "type": "zip", 1111 "type": "zip",
1112 - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/183e1a610664baf6dcd6fceda415baf43cbdc031", 1112 + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/e87a786e3ae12a25cf78a71bb07b4b384bfaa83a",
1113 - "reference": "183e1a610664baf6dcd6fceda415baf43cbdc031", 1113 + "reference": "e87a786e3ae12a25cf78a71bb07b4b384bfaa83a",
1114 "shasum": "" 1114 "shasum": ""
1115 }, 1115 },
1116 "require": { 1116 "require": {
...@@ -1123,8 +1123,7 @@ ...@@ -1123,8 +1123,7 @@
1123 "ext-fileinfo": "*", 1123 "ext-fileinfo": "*",
1124 "mockery/mockery": "~0.9", 1124 "mockery/mockery": "~0.9",
1125 "phpspec/phpspec": "^2.2", 1125 "phpspec/phpspec": "^2.2",
1126 - "phpspec/prophecy-phpunit": "~1.0", 1126 + "phpunit/phpunit": "~4.8 || ~5.0"
1127 - "phpunit/phpunit": "~4.8"
1128 }, 1127 },
1129 "suggest": { 1128 "suggest": {
1130 "ext-fileinfo": "Required for MimeType", 1129 "ext-fileinfo": "Required for MimeType",
...@@ -1181,7 +1180,7 @@ ...@@ -1181,7 +1180,7 @@
1181 "sftp", 1180 "sftp",
1182 "storage" 1181 "storage"
1183 ], 1182 ],
1184 - "time": "2015-12-19 20:16:43" 1183 + "time": "2016-03-14 21:54:11"
1185 }, 1184 },
1186 { 1185 {
1187 "name": "league/flysystem-aws-s3-v3", 1186 "name": "league/flysystem-aws-s3-v3",
...@@ -1295,16 +1294,16 @@ ...@@ -1295,16 +1294,16 @@
1295 }, 1294 },
1296 { 1295 {
1297 "name": "maximebf/debugbar", 1296 "name": "maximebf/debugbar",
1298 - "version": "v1.11.0", 1297 + "version": "v1.11.1",
1299 "source": { 1298 "source": {
1300 "type": "git", 1299 "type": "git",
1301 "url": "https://github.com/maximebf/php-debugbar.git", 1300 "url": "https://github.com/maximebf/php-debugbar.git",
1302 - "reference": "07741d84d39d10f00551c94284cdefcc69703e77" 1301 + "reference": "d9302891c1f0a0ac5a4f66725163a00537c6359f"
1303 }, 1302 },
1304 "dist": { 1303 "dist": {
1305 "type": "zip", 1304 "type": "zip",
1306 - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/07741d84d39d10f00551c94284cdefcc69703e77", 1305 + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/d9302891c1f0a0ac5a4f66725163a00537c6359f",
1307 - "reference": "07741d84d39d10f00551c94284cdefcc69703e77", 1306 + "reference": "d9302891c1f0a0ac5a4f66725163a00537c6359f",
1308 "shasum": "" 1307 "shasum": ""
1309 }, 1308 },
1310 "require": { 1309 "require": {
...@@ -1352,20 +1351,20 @@ ...@@ -1352,20 +1351,20 @@
1352 "debug", 1351 "debug",
1353 "debugbar" 1352 "debugbar"
1354 ], 1353 ],
1355 - "time": "2015-12-10 09:50:24" 1354 + "time": "2016-01-22 12:22:23"
1356 }, 1355 },
1357 { 1356 {
1358 "name": "monolog/monolog", 1357 "name": "monolog/monolog",
1359 - "version": "1.17.2", 1358 + "version": "1.18.2",
1360 "source": { 1359 "source": {
1361 "type": "git", 1360 "type": "git",
1362 "url": "https://github.com/Seldaek/monolog.git", 1361 "url": "https://github.com/Seldaek/monolog.git",
1363 - "reference": "bee7f0dc9c3e0b69a6039697533dca1e845c8c24" 1362 + "reference": "064b38c16790249488e7a8b987acf1c9d7383c09"
1364 }, 1363 },
1365 "dist": { 1364 "dist": {
1366 "type": "zip", 1365 "type": "zip",
1367 - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/bee7f0dc9c3e0b69a6039697533dca1e845c8c24", 1366 + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/064b38c16790249488e7a8b987acf1c9d7383c09",
1368 - "reference": "bee7f0dc9c3e0b69a6039697533dca1e845c8c24", 1367 + "reference": "064b38c16790249488e7a8b987acf1c9d7383c09",
1369 "shasum": "" 1368 "shasum": ""
1370 }, 1369 },
1371 "require": { 1370 "require": {
...@@ -1380,13 +1379,13 @@ ...@@ -1380,13 +1379,13 @@
1380 "doctrine/couchdb": "~1.0@dev", 1379 "doctrine/couchdb": "~1.0@dev",
1381 "graylog2/gelf-php": "~1.0", 1380 "graylog2/gelf-php": "~1.0",
1382 "jakub-onderka/php-parallel-lint": "0.9", 1381 "jakub-onderka/php-parallel-lint": "0.9",
1382 + "php-amqplib/php-amqplib": "~2.4",
1383 "php-console/php-console": "^3.1.3", 1383 "php-console/php-console": "^3.1.3",
1384 "phpunit/phpunit": "~4.5", 1384 "phpunit/phpunit": "~4.5",
1385 "phpunit/phpunit-mock-objects": "2.3.0", 1385 "phpunit/phpunit-mock-objects": "2.3.0",
1386 "raven/raven": "^0.13", 1386 "raven/raven": "^0.13",
1387 "ruflin/elastica": ">=0.90 <3.0", 1387 "ruflin/elastica": ">=0.90 <3.0",
1388 - "swiftmailer/swiftmailer": "~5.3", 1388 + "swiftmailer/swiftmailer": "~5.3"
1389 - "videlalvaro/php-amqplib": "~2.4"
1390 }, 1389 },
1391 "suggest": { 1390 "suggest": {
1392 "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", 1391 "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
...@@ -1394,16 +1393,17 @@ ...@@ -1394,16 +1393,17 @@
1394 "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", 1393 "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
1395 "ext-mongo": "Allow sending log messages to a MongoDB server", 1394 "ext-mongo": "Allow sending log messages to a MongoDB server",
1396 "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", 1395 "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
1396 + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
1397 + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
1397 "php-console/php-console": "Allow sending log messages to Google Chrome", 1398 "php-console/php-console": "Allow sending log messages to Google Chrome",
1398 "raven/raven": "Allow sending log messages to a Sentry server", 1399 "raven/raven": "Allow sending log messages to a Sentry server",
1399 "rollbar/rollbar": "Allow sending log messages to Rollbar", 1400 "rollbar/rollbar": "Allow sending log messages to Rollbar",
1400 - "ruflin/elastica": "Allow sending log messages to an Elastic Search server", 1401 + "ruflin/elastica": "Allow sending log messages to an Elastic Search server"
1401 - "videlalvaro/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib"
1402 }, 1402 },
1403 "type": "library", 1403 "type": "library",
1404 "extra": { 1404 "extra": {
1405 "branch-alias": { 1405 "branch-alias": {
1406 - "dev-master": "1.16.x-dev" 1406 + "dev-master": "2.0.x-dev"
1407 } 1407 }
1408 }, 1408 },
1409 "autoload": { 1409 "autoload": {
...@@ -1429,7 +1429,7 @@ ...@@ -1429,7 +1429,7 @@
1429 "logging", 1429 "logging",
1430 "psr-3" 1430 "psr-3"
1431 ], 1431 ],
1432 - "time": "2015-10-14 12:51:02" 1432 + "time": "2016-04-02 13:12:58"
1433 }, 1433 },
1434 { 1434 {
1435 "name": "mtdowling/cron-expression", 1435 "name": "mtdowling/cron-expression",
...@@ -1579,16 +1579,16 @@ ...@@ -1579,16 +1579,16 @@
1579 }, 1579 },
1580 { 1580 {
1581 "name": "nikic/php-parser", 1581 "name": "nikic/php-parser",
1582 - "version": "v2.0.0", 1582 + "version": "v2.0.1",
1583 "source": { 1583 "source": {
1584 "type": "git", 1584 "type": "git",
1585 "url": "https://github.com/nikic/PHP-Parser.git", 1585 "url": "https://github.com/nikic/PHP-Parser.git",
1586 - "reference": "c542e5d86a9775abd1021618eb2430278bfc1e01" 1586 + "reference": "ce5be709d59b32dd8a88c80259028759991a4206"
1587 }, 1587 },
1588 "dist": { 1588 "dist": {
1589 "type": "zip", 1589 "type": "zip",
1590 - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/c542e5d86a9775abd1021618eb2430278bfc1e01", 1590 + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ce5be709d59b32dd8a88c80259028759991a4206",
1591 - "reference": "c542e5d86a9775abd1021618eb2430278bfc1e01", 1591 + "reference": "ce5be709d59b32dd8a88c80259028759991a4206",
1592 "shasum": "" 1592 "shasum": ""
1593 }, 1593 },
1594 "require": { 1594 "require": {
...@@ -1626,20 +1626,20 @@ ...@@ -1626,20 +1626,20 @@
1626 "parser", 1626 "parser",
1627 "php" 1627 "php"
1628 ], 1628 ],
1629 - "time": "2015-12-04 15:28:43" 1629 + "time": "2016-02-28 19:48:28"
1630 }, 1630 },
1631 { 1631 {
1632 "name": "paragonie/random_compat", 1632 "name": "paragonie/random_compat",
1633 - "version": "v1.2.0", 1633 + "version": "v1.4.1",
1634 "source": { 1634 "source": {
1635 "type": "git", 1635 "type": "git",
1636 "url": "https://github.com/paragonie/random_compat.git", 1636 "url": "https://github.com/paragonie/random_compat.git",
1637 - "reference": "b0e69d10852716b2ccbdff69c75c477637220790" 1637 + "reference": "c7e26a21ba357863de030f0b9e701c7d04593774"
1638 }, 1638 },
1639 "dist": { 1639 "dist": {
1640 "type": "zip", 1640 "type": "zip",
1641 - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/b0e69d10852716b2ccbdff69c75c477637220790", 1641 + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/c7e26a21ba357863de030f0b9e701c7d04593774",
1642 - "reference": "b0e69d10852716b2ccbdff69c75c477637220790", 1642 + "reference": "c7e26a21ba357863de030f0b9e701c7d04593774",
1643 "shasum": "" 1643 "shasum": ""
1644 }, 1644 },
1645 "require": { 1645 "require": {
...@@ -1674,7 +1674,7 @@ ...@@ -1674,7 +1674,7 @@
1674 "pseudorandom", 1674 "pseudorandom",
1675 "random" 1675 "random"
1676 ], 1676 ],
1677 - "time": "2016-02-06 03:52:05" 1677 + "time": "2016-03-18 20:34:03"
1678 }, 1678 },
1679 { 1679 {
1680 "name": "phenx/php-font-lib", 1680 "name": "phenx/php-font-lib",
...@@ -1760,6 +1760,56 @@ ...@@ -1760,6 +1760,56 @@
1760 "time": "2015-02-03 12:10:50" 1760 "time": "2015-02-03 12:10:50"
1761 }, 1761 },
1762 { 1762 {
1763 + "name": "predis/predis",
1764 + "version": "v1.0.3",
1765 + "source": {
1766 + "type": "git",
1767 + "url": "https://github.com/nrk/predis.git",
1768 + "reference": "84060b9034d756b4d79641667d7f9efe1aeb8e04"
1769 + },
1770 + "dist": {
1771 + "type": "zip",
1772 + "url": "https://api.github.com/repos/nrk/predis/zipball/84060b9034d756b4d79641667d7f9efe1aeb8e04",
1773 + "reference": "84060b9034d756b4d79641667d7f9efe1aeb8e04",
1774 + "shasum": ""
1775 + },
1776 + "require": {
1777 + "php": ">=5.3.2"
1778 + },
1779 + "require-dev": {
1780 + "phpunit/phpunit": "~4.0"
1781 + },
1782 + "suggest": {
1783 + "ext-curl": "Allows access to Webdis when paired with phpiredis",
1784 + "ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol"
1785 + },
1786 + "type": "library",
1787 + "autoload": {
1788 + "psr-4": {
1789 + "Predis\\": "src/"
1790 + }
1791 + },
1792 + "notification-url": "https://packagist.org/downloads/",
1793 + "license": [
1794 + "MIT"
1795 + ],
1796 + "authors": [
1797 + {
1798 + "name": "Daniele Alessandri",
1799 + "email": "suppakilla@gmail.com",
1800 + "homepage": "http://clorophilla.net"
1801 + }
1802 + ],
1803 + "description": "Flexible and feature-complete PHP client library for Redis",
1804 + "homepage": "http://github.com/nrk/predis",
1805 + "keywords": [
1806 + "nosql",
1807 + "predis",
1808 + "redis"
1809 + ],
1810 + "time": "2015-07-30 18:34:15"
1811 + },
1812 + {
1763 "name": "psr/http-message", 1813 "name": "psr/http-message",
1764 "version": "1.0", 1814 "version": "1.0",
1765 "source": { 1815 "source": {
...@@ -1848,16 +1898,16 @@ ...@@ -1848,16 +1898,16 @@
1848 }, 1898 },
1849 { 1899 {
1850 "name": "psy/psysh", 1900 "name": "psy/psysh",
1851 - "version": "v0.6.1", 1901 + "version": "v0.7.2",
1852 "source": { 1902 "source": {
1853 "type": "git", 1903 "type": "git",
1854 "url": "https://github.com/bobthecow/psysh.git", 1904 "url": "https://github.com/bobthecow/psysh.git",
1855 - "reference": "0f04df0b23663799a8941fae13cd8e6299bde3ed" 1905 + "reference": "e64e10b20f8d229cac76399e1f3edddb57a0f280"
1856 }, 1906 },
1857 "dist": { 1907 "dist": {
1858 "type": "zip", 1908 "type": "zip",
1859 - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/0f04df0b23663799a8941fae13cd8e6299bde3ed", 1909 + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/e64e10b20f8d229cac76399e1f3edddb57a0f280",
1860 - "reference": "0f04df0b23663799a8941fae13cd8e6299bde3ed", 1910 + "reference": "e64e10b20f8d229cac76399e1f3edddb57a0f280",
1861 "shasum": "" 1911 "shasum": ""
1862 }, 1912 },
1863 "require": { 1913 "require": {
...@@ -1886,7 +1936,7 @@ ...@@ -1886,7 +1936,7 @@
1886 "type": "library", 1936 "type": "library",
1887 "extra": { 1937 "extra": {
1888 "branch-alias": { 1938 "branch-alias": {
1889 - "dev-develop": "0.7.x-dev" 1939 + "dev-develop": "0.8.x-dev"
1890 } 1940 }
1891 }, 1941 },
1892 "autoload": { 1942 "autoload": {
...@@ -1916,7 +1966,7 @@ ...@@ -1916,7 +1966,7 @@
1916 "interactive", 1966 "interactive",
1917 "shell" 1967 "shell"
1918 ], 1968 ],
1919 - "time": "2015-11-12 16:18:56" 1969 + "time": "2016-03-09 05:03:14"
1920 }, 1970 },
1921 { 1971 {
1922 "name": "swiftmailer/swiftmailer", 1972 "name": "swiftmailer/swiftmailer",
...@@ -1973,28 +2023,32 @@ ...@@ -1973,28 +2023,32 @@
1973 }, 2023 },
1974 { 2024 {
1975 "name": "symfony/class-loader", 2025 "name": "symfony/class-loader",
1976 - "version": "v2.8.2", 2026 + "version": "v3.0.4",
1977 "source": { 2027 "source": {
1978 "type": "git", 2028 "type": "git",
1979 "url": "https://github.com/symfony/class-loader.git", 2029 "url": "https://github.com/symfony/class-loader.git",
1980 - "reference": "98e9089a428ed0e39423b67352c57ef5910a3269" 2030 + "reference": "cbb7e6a9c0213a0cffa5d9065ee8214ca4e83877"
1981 }, 2031 },
1982 "dist": { 2032 "dist": {
1983 "type": "zip", 2033 "type": "zip",
1984 - "url": "https://api.github.com/repos/symfony/class-loader/zipball/98e9089a428ed0e39423b67352c57ef5910a3269", 2034 + "url": "https://api.github.com/repos/symfony/class-loader/zipball/cbb7e6a9c0213a0cffa5d9065ee8214ca4e83877",
1985 - "reference": "98e9089a428ed0e39423b67352c57ef5910a3269", 2035 + "reference": "cbb7e6a9c0213a0cffa5d9065ee8214ca4e83877",
1986 "shasum": "" 2036 "shasum": ""
1987 }, 2037 },
1988 "require": { 2038 "require": {
1989 - "php": ">=5.3.9" 2039 + "php": ">=5.5.9"
1990 }, 2040 },
1991 "require-dev": { 2041 "require-dev": {
1992 - "symfony/finder": "~2.0,>=2.0.5|~3.0.0" 2042 + "symfony/finder": "~2.8|~3.0",
2043 + "symfony/polyfill-apcu": "~1.1"
2044 + },
2045 + "suggest": {
2046 + "symfony/polyfill-apcu": "For using ApcClassLoader on HHVM"
1993 }, 2047 },
1994 "type": "library", 2048 "type": "library",
1995 "extra": { 2049 "extra": {
1996 "branch-alias": { 2050 "branch-alias": {
1997 - "dev-master": "2.8-dev" 2051 + "dev-master": "3.0-dev"
1998 } 2052 }
1999 }, 2053 },
2000 "autoload": { 2054 "autoload": {
...@@ -2021,20 +2075,20 @@ ...@@ -2021,20 +2075,20 @@
2021 ], 2075 ],
2022 "description": "Symfony ClassLoader Component", 2076 "description": "Symfony ClassLoader Component",
2023 "homepage": "https://symfony.com", 2077 "homepage": "https://symfony.com",
2024 - "time": "2016-01-03 15:33:41" 2078 + "time": "2016-03-30 10:41:14"
2025 }, 2079 },
2026 { 2080 {
2027 "name": "symfony/console", 2081 "name": "symfony/console",
2028 - "version": "v3.0.2", 2082 + "version": "v3.0.4",
2029 "source": { 2083 "source": {
2030 "type": "git", 2084 "type": "git",
2031 "url": "https://github.com/symfony/console.git", 2085 "url": "https://github.com/symfony/console.git",
2032 - "reference": "5a02eaadaa285e2bb727eb6bbdfb8201fcd971b0" 2086 + "reference": "6b1175135bc2a74c08a28d89761272de8beed8cd"
2033 }, 2087 },
2034 "dist": { 2088 "dist": {
2035 "type": "zip", 2089 "type": "zip",
2036 - "url": "https://api.github.com/repos/symfony/console/zipball/5a02eaadaa285e2bb727eb6bbdfb8201fcd971b0", 2090 + "url": "https://api.github.com/repos/symfony/console/zipball/6b1175135bc2a74c08a28d89761272de8beed8cd",
2037 - "reference": "5a02eaadaa285e2bb727eb6bbdfb8201fcd971b0", 2091 + "reference": "6b1175135bc2a74c08a28d89761272de8beed8cd",
2038 "shasum": "" 2092 "shasum": ""
2039 }, 2093 },
2040 "require": { 2094 "require": {
...@@ -2081,20 +2135,20 @@ ...@@ -2081,20 +2135,20 @@
2081 ], 2135 ],
2082 "description": "Symfony Console Component", 2136 "description": "Symfony Console Component",
2083 "homepage": "https://symfony.com", 2137 "homepage": "https://symfony.com",
2084 - "time": "2016-02-02 13:44:19" 2138 + "time": "2016-03-16 17:00:50"
2085 }, 2139 },
2086 { 2140 {
2087 "name": "symfony/debug", 2141 "name": "symfony/debug",
2088 - "version": "v3.0.2", 2142 + "version": "v3.0.4",
2089 "source": { 2143 "source": {
2090 "type": "git", 2144 "type": "git",
2091 "url": "https://github.com/symfony/debug.git", 2145 "url": "https://github.com/symfony/debug.git",
2092 - "reference": "29606049ced1ec715475f88d1bbe587252a3476e" 2146 + "reference": "a06d10888a45afd97534506afb058ec38d9ba35b"
2093 }, 2147 },
2094 "dist": { 2148 "dist": {
2095 "type": "zip", 2149 "type": "zip",
2096 - "url": "https://api.github.com/repos/symfony/debug/zipball/29606049ced1ec715475f88d1bbe587252a3476e", 2150 + "url": "https://api.github.com/repos/symfony/debug/zipball/a06d10888a45afd97534506afb058ec38d9ba35b",
2097 - "reference": "29606049ced1ec715475f88d1bbe587252a3476e", 2151 + "reference": "a06d10888a45afd97534506afb058ec38d9ba35b",
2098 "shasum": "" 2152 "shasum": ""
2099 }, 2153 },
2100 "require": { 2154 "require": {
...@@ -2138,20 +2192,20 @@ ...@@ -2138,20 +2192,20 @@
2138 ], 2192 ],
2139 "description": "Symfony Debug Component", 2193 "description": "Symfony Debug Component",
2140 "homepage": "https://symfony.com", 2194 "homepage": "https://symfony.com",
2141 - "time": "2016-01-27 05:14:46" 2195 + "time": "2016-03-30 10:41:14"
2142 }, 2196 },
2143 { 2197 {
2144 "name": "symfony/event-dispatcher", 2198 "name": "symfony/event-dispatcher",
2145 - "version": "v3.0.2", 2199 + "version": "v3.0.4",
2146 "source": { 2200 "source": {
2147 "type": "git", 2201 "type": "git",
2148 "url": "https://github.com/symfony/event-dispatcher.git", 2202 "url": "https://github.com/symfony/event-dispatcher.git",
2149 - "reference": "4dd5df31a28c0f82b41cb1e1599b74b5dcdbdafa" 2203 + "reference": "9002dcf018d884d294b1ef20a6f968efc1128f39"
2150 }, 2204 },
2151 "dist": { 2205 "dist": {
2152 "type": "zip", 2206 "type": "zip",
2153 - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/4dd5df31a28c0f82b41cb1e1599b74b5dcdbdafa", 2207 + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9002dcf018d884d294b1ef20a6f968efc1128f39",
2154 - "reference": "4dd5df31a28c0f82b41cb1e1599b74b5dcdbdafa", 2208 + "reference": "9002dcf018d884d294b1ef20a6f968efc1128f39",
2155 "shasum": "" 2209 "shasum": ""
2156 }, 2210 },
2157 "require": { 2211 "require": {
...@@ -2198,20 +2252,20 @@ ...@@ -2198,20 +2252,20 @@
2198 ], 2252 ],
2199 "description": "Symfony EventDispatcher Component", 2253 "description": "Symfony EventDispatcher Component",
2200 "homepage": "https://symfony.com", 2254 "homepage": "https://symfony.com",
2201 - "time": "2016-01-27 05:14:46" 2255 + "time": "2016-03-10 10:34:12"
2202 }, 2256 },
2203 { 2257 {
2204 "name": "symfony/finder", 2258 "name": "symfony/finder",
2205 - "version": "v3.0.2", 2259 + "version": "v3.0.4",
2206 "source": { 2260 "source": {
2207 "type": "git", 2261 "type": "git",
2208 "url": "https://github.com/symfony/finder.git", 2262 "url": "https://github.com/symfony/finder.git",
2209 - "reference": "623bda0abd9aa29e529c8e9c08b3b84171914723" 2263 + "reference": "c54e407b35bc098916704e9fd090da21da4c4f52"
2210 }, 2264 },
2211 "dist": { 2265 "dist": {
2212 "type": "zip", 2266 "type": "zip",
2213 - "url": "https://api.github.com/repos/symfony/finder/zipball/623bda0abd9aa29e529c8e9c08b3b84171914723", 2267 + "url": "https://api.github.com/repos/symfony/finder/zipball/c54e407b35bc098916704e9fd090da21da4c4f52",
2214 - "reference": "623bda0abd9aa29e529c8e9c08b3b84171914723", 2268 + "reference": "c54e407b35bc098916704e9fd090da21da4c4f52",
2215 "shasum": "" 2269 "shasum": ""
2216 }, 2270 },
2217 "require": { 2271 "require": {
...@@ -2247,24 +2301,25 @@ ...@@ -2247,24 +2301,25 @@
2247 ], 2301 ],
2248 "description": "Symfony Finder Component", 2302 "description": "Symfony Finder Component",
2249 "homepage": "https://symfony.com", 2303 "homepage": "https://symfony.com",
2250 - "time": "2016-01-27 05:14:46" 2304 + "time": "2016-03-10 11:13:05"
2251 }, 2305 },
2252 { 2306 {
2253 "name": "symfony/http-foundation", 2307 "name": "symfony/http-foundation",
2254 - "version": "v3.0.2", 2308 + "version": "v3.0.4",
2255 "source": { 2309 "source": {
2256 "type": "git", 2310 "type": "git",
2257 "url": "https://github.com/symfony/http-foundation.git", 2311 "url": "https://github.com/symfony/http-foundation.git",
2258 - "reference": "9344a87ceedfc50354a39653e54257ee9aa6a77d" 2312 + "reference": "99f38445a874e7becb8afc4b4a79ee181cf6ec3f"
2259 }, 2313 },
2260 "dist": { 2314 "dist": {
2261 "type": "zip", 2315 "type": "zip",
2262 - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/9344a87ceedfc50354a39653e54257ee9aa6a77d", 2316 + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/99f38445a874e7becb8afc4b4a79ee181cf6ec3f",
2263 - "reference": "9344a87ceedfc50354a39653e54257ee9aa6a77d", 2317 + "reference": "99f38445a874e7becb8afc4b4a79ee181cf6ec3f",
2264 "shasum": "" 2318 "shasum": ""
2265 }, 2319 },
2266 "require": { 2320 "require": {
2267 - "php": ">=5.5.9" 2321 + "php": ">=5.5.9",
2322 + "symfony/polyfill-mbstring": "~1.1"
2268 }, 2323 },
2269 "require-dev": { 2324 "require-dev": {
2270 "symfony/expression-language": "~2.8|~3.0" 2325 "symfony/expression-language": "~2.8|~3.0"
...@@ -2299,20 +2354,20 @@ ...@@ -2299,20 +2354,20 @@
2299 ], 2354 ],
2300 "description": "Symfony HttpFoundation Component", 2355 "description": "Symfony HttpFoundation Component",
2301 "homepage": "https://symfony.com", 2356 "homepage": "https://symfony.com",
2302 - "time": "2016-02-02 13:44:19" 2357 + "time": "2016-03-27 14:50:32"
2303 }, 2358 },
2304 { 2359 {
2305 "name": "symfony/http-kernel", 2360 "name": "symfony/http-kernel",
2306 - "version": "v3.0.2", 2361 + "version": "v3.0.4",
2307 "source": { 2362 "source": {
2308 "type": "git", 2363 "type": "git",
2309 "url": "https://github.com/symfony/http-kernel.git", 2364 "url": "https://github.com/symfony/http-kernel.git",
2310 - "reference": "cec02604450481ac26710ca4249cc61b57b23942" 2365 + "reference": "579f828489659d7b3430f4bd9b67b4618b387dea"
2311 }, 2366 },
2312 "dist": { 2367 "dist": {
2313 "type": "zip", 2368 "type": "zip",
2314 - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/cec02604450481ac26710ca4249cc61b57b23942", 2369 + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/579f828489659d7b3430f4bd9b67b4618b387dea",
2315 - "reference": "cec02604450481ac26710ca4249cc61b57b23942", 2370 + "reference": "579f828489659d7b3430f4bd9b67b4618b387dea",
2316 "shasum": "" 2371 "shasum": ""
2317 }, 2372 },
2318 "require": { 2373 "require": {
...@@ -2381,11 +2436,11 @@ ...@@ -2381,11 +2436,11 @@
2381 ], 2436 ],
2382 "description": "Symfony HttpKernel Component", 2437 "description": "Symfony HttpKernel Component",
2383 "homepage": "https://symfony.com", 2438 "homepage": "https://symfony.com",
2384 - "time": "2016-02-03 12:38:44" 2439 + "time": "2016-03-25 01:41:20"
2385 }, 2440 },
2386 { 2441 {
2387 "name": "symfony/polyfill-mbstring", 2442 "name": "symfony/polyfill-mbstring",
2388 - "version": "v1.1.0", 2443 + "version": "v1.1.1",
2389 "source": { 2444 "source": {
2390 "type": "git", 2445 "type": "git",
2391 "url": "https://github.com/symfony/polyfill-mbstring.git", 2446 "url": "https://github.com/symfony/polyfill-mbstring.git",
...@@ -2444,7 +2499,7 @@ ...@@ -2444,7 +2499,7 @@
2444 }, 2499 },
2445 { 2500 {
2446 "name": "symfony/polyfill-php56", 2501 "name": "symfony/polyfill-php56",
2447 - "version": "v1.1.0", 2502 + "version": "v1.1.1",
2448 "source": { 2503 "source": {
2449 "type": "git", 2504 "type": "git",
2450 "url": "https://github.com/symfony/polyfill-php56.git", 2505 "url": "https://github.com/symfony/polyfill-php56.git",
...@@ -2500,7 +2555,7 @@ ...@@ -2500,7 +2555,7 @@
2500 }, 2555 },
2501 { 2556 {
2502 "name": "symfony/polyfill-util", 2557 "name": "symfony/polyfill-util",
2503 - "version": "v1.1.0", 2558 + "version": "v1.1.1",
2504 "source": { 2559 "source": {
2505 "type": "git", 2560 "type": "git",
2506 "url": "https://github.com/symfony/polyfill-util.git", 2561 "url": "https://github.com/symfony/polyfill-util.git",
...@@ -2552,16 +2607,16 @@ ...@@ -2552,16 +2607,16 @@
2552 }, 2607 },
2553 { 2608 {
2554 "name": "symfony/process", 2609 "name": "symfony/process",
2555 - "version": "v3.0.2", 2610 + "version": "v3.0.4",
2556 "source": { 2611 "source": {
2557 "type": "git", 2612 "type": "git",
2558 "url": "https://github.com/symfony/process.git", 2613 "url": "https://github.com/symfony/process.git",
2559 - "reference": "dfecef47506179db2501430e732adbf3793099c8" 2614 + "reference": "e6f1f98bbd355d209a992bfff45e7edfbd4a0776"
2560 }, 2615 },
2561 "dist": { 2616 "dist": {
2562 "type": "zip", 2617 "type": "zip",
2563 - "url": "https://api.github.com/repos/symfony/process/zipball/dfecef47506179db2501430e732adbf3793099c8", 2618 + "url": "https://api.github.com/repos/symfony/process/zipball/e6f1f98bbd355d209a992bfff45e7edfbd4a0776",
2564 - "reference": "dfecef47506179db2501430e732adbf3793099c8", 2619 + "reference": "e6f1f98bbd355d209a992bfff45e7edfbd4a0776",
2565 "shasum": "" 2620 "shasum": ""
2566 }, 2621 },
2567 "require": { 2622 "require": {
...@@ -2597,20 +2652,20 @@ ...@@ -2597,20 +2652,20 @@
2597 ], 2652 ],
2598 "description": "Symfony Process Component", 2653 "description": "Symfony Process Component",
2599 "homepage": "https://symfony.com", 2654 "homepage": "https://symfony.com",
2600 - "time": "2016-02-02 13:44:19" 2655 + "time": "2016-03-30 10:41:14"
2601 }, 2656 },
2602 { 2657 {
2603 "name": "symfony/routing", 2658 "name": "symfony/routing",
2604 - "version": "v3.0.2", 2659 + "version": "v3.0.4",
2605 "source": { 2660 "source": {
2606 "type": "git", 2661 "type": "git",
2607 "url": "https://github.com/symfony/routing.git", 2662 "url": "https://github.com/symfony/routing.git",
2608 - "reference": "4686baa55a835e1c1ede9b86ba02415c8c8d6166" 2663 + "reference": "d061b609f2d0769494c381ec92f5c5cc5e4a20aa"
2609 }, 2664 },
2610 "dist": { 2665 "dist": {
2611 "type": "zip", 2666 "type": "zip",
2612 - "url": "https://api.github.com/repos/symfony/routing/zipball/4686baa55a835e1c1ede9b86ba02415c8c8d6166", 2667 + "url": "https://api.github.com/repos/symfony/routing/zipball/d061b609f2d0769494c381ec92f5c5cc5e4a20aa",
2613 - "reference": "4686baa55a835e1c1ede9b86ba02415c8c8d6166", 2668 + "reference": "d061b609f2d0769494c381ec92f5c5cc5e4a20aa",
2614 "shasum": "" 2669 "shasum": ""
2615 }, 2670 },
2616 "require": { 2671 "require": {
...@@ -2633,6 +2688,7 @@ ...@@ -2633,6 +2688,7 @@
2633 "symfony/config": "For using the all-in-one router or any loader", 2688 "symfony/config": "For using the all-in-one router or any loader",
2634 "symfony/dependency-injection": "For loading routes from a service", 2689 "symfony/dependency-injection": "For loading routes from a service",
2635 "symfony/expression-language": "For using expression matching", 2690 "symfony/expression-language": "For using expression matching",
2691 + "symfony/http-foundation": "For using a Symfony Request object",
2636 "symfony/yaml": "For using the YAML loader" 2692 "symfony/yaml": "For using the YAML loader"
2637 }, 2693 },
2638 "type": "library", 2694 "type": "library",
...@@ -2671,20 +2727,20 @@ ...@@ -2671,20 +2727,20 @@
2671 "uri", 2727 "uri",
2672 "url" 2728 "url"
2673 ], 2729 ],
2674 - "time": "2016-01-27 05:14:46" 2730 + "time": "2016-03-23 13:23:25"
2675 }, 2731 },
2676 { 2732 {
2677 "name": "symfony/translation", 2733 "name": "symfony/translation",
2678 - "version": "v3.0.2", 2734 + "version": "v3.0.4",
2679 "source": { 2735 "source": {
2680 "type": "git", 2736 "type": "git",
2681 "url": "https://github.com/symfony/translation.git", 2737 "url": "https://github.com/symfony/translation.git",
2682 - "reference": "2de0b6f7ebe43cffd8a06996ebec6aab79ea9e91" 2738 + "reference": "f7a07af51ea067745a521dab1e3152044a2fb1f2"
2683 }, 2739 },
2684 "dist": { 2740 "dist": {
2685 "type": "zip", 2741 "type": "zip",
2686 - "url": "https://api.github.com/repos/symfony/translation/zipball/2de0b6f7ebe43cffd8a06996ebec6aab79ea9e91", 2742 + "url": "https://api.github.com/repos/symfony/translation/zipball/f7a07af51ea067745a521dab1e3152044a2fb1f2",
2687 - "reference": "2de0b6f7ebe43cffd8a06996ebec6aab79ea9e91", 2743 + "reference": "f7a07af51ea067745a521dab1e3152044a2fb1f2",
2688 "shasum": "" 2744 "shasum": ""
2689 }, 2745 },
2690 "require": { 2746 "require": {
...@@ -2735,20 +2791,20 @@ ...@@ -2735,20 +2791,20 @@
2735 ], 2791 ],
2736 "description": "Symfony Translation Component", 2792 "description": "Symfony Translation Component",
2737 "homepage": "https://symfony.com", 2793 "homepage": "https://symfony.com",
2738 - "time": "2016-02-02 13:44:19" 2794 + "time": "2016-03-25 01:41:20"
2739 }, 2795 },
2740 { 2796 {
2741 "name": "symfony/var-dumper", 2797 "name": "symfony/var-dumper",
2742 - "version": "v3.0.2", 2798 + "version": "v3.0.4",
2743 "source": { 2799 "source": {
2744 "type": "git", 2800 "type": "git",
2745 "url": "https://github.com/symfony/var-dumper.git", 2801 "url": "https://github.com/symfony/var-dumper.git",
2746 - "reference": "24bb94807eff00db49374c37ebf56a0304e8aef3" 2802 + "reference": "3841ed86527d18ee2c35fe4afb1b2fc60f8fae79"
2747 }, 2803 },
2748 "dist": { 2804 "dist": {
2749 "type": "zip", 2805 "type": "zip",
2750 - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/24bb94807eff00db49374c37ebf56a0304e8aef3", 2806 + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/3841ed86527d18ee2c35fe4afb1b2fc60f8fae79",
2751 - "reference": "24bb94807eff00db49374c37ebf56a0304e8aef3", 2807 + "reference": "3841ed86527d18ee2c35fe4afb1b2fc60f8fae79",
2752 "shasum": "" 2808 "shasum": ""
2753 }, 2809 },
2754 "require": { 2810 "require": {
...@@ -2798,7 +2854,7 @@ ...@@ -2798,7 +2854,7 @@
2798 "debug", 2854 "debug",
2799 "dump" 2855 "dump"
2800 ], 2856 ],
2801 - "time": "2016-01-07 13:38:51" 2857 + "time": "2016-03-10 10:34:12"
2802 }, 2858 },
2803 { 2859 {
2804 "name": "vlucas/phpdotenv", 2860 "name": "vlucas/phpdotenv",
...@@ -3105,16 +3161,16 @@ ...@@ -3105,16 +3161,16 @@
3105 }, 3161 },
3106 { 3162 {
3107 "name": "phpspec/phpspec", 3163 "name": "phpspec/phpspec",
3108 - "version": "2.4.1", 3164 + "version": "2.5.0",
3109 "source": { 3165 "source": {
3110 "type": "git", 3166 "type": "git",
3111 "url": "https://github.com/phpspec/phpspec.git", 3167 "url": "https://github.com/phpspec/phpspec.git",
3112 - "reference": "5528ce1e93a1efa090c9404aba3395c329b4e6ed" 3168 + "reference": "385ecb015e97c13818074f1517928b24d4a26067"
3113 }, 3169 },
3114 "dist": { 3170 "dist": {
3115 "type": "zip", 3171 "type": "zip",
3116 - "url": "https://api.github.com/repos/phpspec/phpspec/zipball/5528ce1e93a1efa090c9404aba3395c329b4e6ed", 3172 + "url": "https://api.github.com/repos/phpspec/phpspec/zipball/385ecb015e97c13818074f1517928b24d4a26067",
3117 - "reference": "5528ce1e93a1efa090c9404aba3395c329b4e6ed", 3173 + "reference": "385ecb015e97c13818074f1517928b24d4a26067",
3118 "shasum": "" 3174 "shasum": ""
3119 }, 3175 },
3120 "require": { 3176 "require": {
...@@ -3179,7 +3235,7 @@ ...@@ -3179,7 +3235,7 @@
3179 "testing", 3235 "testing",
3180 "tests" 3236 "tests"
3181 ], 3237 ],
3182 - "time": "2016-01-01 10:17:54" 3238 + "time": "2016-03-20 20:34:32"
3183 }, 3239 },
3184 { 3240 {
3185 "name": "phpspec/prophecy", 3241 "name": "phpspec/prophecy",
...@@ -3485,16 +3541,16 @@ ...@@ -3485,16 +3541,16 @@
3485 }, 3541 },
3486 { 3542 {
3487 "name": "phpunit/phpunit", 3543 "name": "phpunit/phpunit",
3488 - "version": "4.8.23", 3544 + "version": "4.8.24",
3489 "source": { 3545 "source": {
3490 "type": "git", 3546 "type": "git",
3491 "url": "https://github.com/sebastianbergmann/phpunit.git", 3547 "url": "https://github.com/sebastianbergmann/phpunit.git",
3492 - "reference": "6e351261f9cd33daf205a131a1ba61c6d33bd483" 3548 + "reference": "a1066c562c52900a142a0e2bbf0582994671385e"
3493 }, 3549 },
3494 "dist": { 3550 "dist": {
3495 "type": "zip", 3551 "type": "zip",
3496 - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6e351261f9cd33daf205a131a1ba61c6d33bd483", 3552 + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a1066c562c52900a142a0e2bbf0582994671385e",
3497 - "reference": "6e351261f9cd33daf205a131a1ba61c6d33bd483", 3553 + "reference": "a1066c562c52900a142a0e2bbf0582994671385e",
3498 "shasum": "" 3554 "shasum": ""
3499 }, 3555 },
3500 "require": { 3556 "require": {
...@@ -3553,7 +3609,7 @@ ...@@ -3553,7 +3609,7 @@
3553 "testing", 3609 "testing",
3554 "xunit" 3610 "xunit"
3555 ], 3611 ],
3556 - "time": "2016-02-11 14:56:33" 3612 + "time": "2016-03-14 06:16:08"
3557 }, 3613 },
3558 { 3614 {
3559 "name": "phpunit/phpunit-mock-objects", 3615 "name": "phpunit/phpunit-mock-objects",
...@@ -3729,16 +3785,16 @@ ...@@ -3729,16 +3785,16 @@
3729 }, 3785 },
3730 { 3786 {
3731 "name": "sebastian/environment", 3787 "name": "sebastian/environment",
3732 - "version": "1.3.3", 3788 + "version": "1.3.5",
3733 "source": { 3789 "source": {
3734 "type": "git", 3790 "type": "git",
3735 "url": "https://github.com/sebastianbergmann/environment.git", 3791 "url": "https://github.com/sebastianbergmann/environment.git",
3736 - "reference": "6e7133793a8e5a5714a551a8324337374be209df" 3792 + "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf"
3737 }, 3793 },
3738 "dist": { 3794 "dist": {
3739 "type": "zip", 3795 "type": "zip",
3740 - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6e7133793a8e5a5714a551a8324337374be209df", 3796 + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf",
3741 - "reference": "6e7133793a8e5a5714a551a8324337374be209df", 3797 + "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf",
3742 "shasum": "" 3798 "shasum": ""
3743 }, 3799 },
3744 "require": { 3800 "require": {
...@@ -3775,7 +3831,7 @@ ...@@ -3775,7 +3831,7 @@
3775 "environment", 3831 "environment",
3776 "hhvm" 3832 "hhvm"
3777 ], 3833 ],
3778 - "time": "2015-12-02 08:37:27" 3834 + "time": "2016-02-26 18:40:46"
3779 }, 3835 },
3780 { 3836 {
3781 "name": "sebastian/exporter", 3837 "name": "sebastian/exporter",
...@@ -3984,16 +4040,16 @@ ...@@ -3984,16 +4040,16 @@
3984 }, 4040 },
3985 { 4041 {
3986 "name": "symfony/css-selector", 4042 "name": "symfony/css-selector",
3987 - "version": "v3.0.2", 4043 + "version": "v3.0.4",
3988 "source": { 4044 "source": {
3989 "type": "git", 4045 "type": "git",
3990 "url": "https://github.com/symfony/css-selector.git", 4046 "url": "https://github.com/symfony/css-selector.git",
3991 - "reference": "6605602690578496091ac20ec7a5cbd160d4dff4" 4047 + "reference": "65e764f404685f2dc20c057e889b3ad04b2e2db0"
3992 }, 4048 },
3993 "dist": { 4049 "dist": {
3994 "type": "zip", 4050 "type": "zip",
3995 - "url": "https://api.github.com/repos/symfony/css-selector/zipball/6605602690578496091ac20ec7a5cbd160d4dff4", 4051 + "url": "https://api.github.com/repos/symfony/css-selector/zipball/65e764f404685f2dc20c057e889b3ad04b2e2db0",
3996 - "reference": "6605602690578496091ac20ec7a5cbd160d4dff4", 4052 + "reference": "65e764f404685f2dc20c057e889b3ad04b2e2db0",
3997 "shasum": "" 4053 "shasum": ""
3998 }, 4054 },
3999 "require": { 4055 "require": {
...@@ -4033,20 +4089,20 @@ ...@@ -4033,20 +4089,20 @@
4033 ], 4089 ],
4034 "description": "Symfony CssSelector Component", 4090 "description": "Symfony CssSelector Component",
4035 "homepage": "https://symfony.com", 4091 "homepage": "https://symfony.com",
4036 - "time": "2016-01-27 05:14:46" 4092 + "time": "2016-03-04 07:55:57"
4037 }, 4093 },
4038 { 4094 {
4039 "name": "symfony/dom-crawler", 4095 "name": "symfony/dom-crawler",
4040 - "version": "v3.0.2", 4096 + "version": "v3.0.4",
4041 "source": { 4097 "source": {
4042 "type": "git", 4098 "type": "git",
4043 "url": "https://github.com/symfony/dom-crawler.git", 4099 "url": "https://github.com/symfony/dom-crawler.git",
4044 - "reference": "b693a9650aa004576b593ff2e91ae749dc90123d" 4100 + "reference": "18a06d7a9af41718c20764a674a0ebba3bc40d1f"
4045 }, 4101 },
4046 "dist": { 4102 "dist": {
4047 "type": "zip", 4103 "type": "zip",
4048 - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/b693a9650aa004576b593ff2e91ae749dc90123d", 4104 + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/18a06d7a9af41718c20764a674a0ebba3bc40d1f",
4049 - "reference": "b693a9650aa004576b593ff2e91ae749dc90123d", 4105 + "reference": "18a06d7a9af41718c20764a674a0ebba3bc40d1f",
4050 "shasum": "" 4106 "shasum": ""
4051 }, 4107 },
4052 "require": { 4108 "require": {
...@@ -4089,20 +4145,20 @@ ...@@ -4089,20 +4145,20 @@
4089 ], 4145 ],
4090 "description": "Symfony DomCrawler Component", 4146 "description": "Symfony DomCrawler Component",
4091 "homepage": "https://symfony.com", 4147 "homepage": "https://symfony.com",
4092 - "time": "2016-01-25 09:56:57" 4148 + "time": "2016-03-23 13:23:25"
4093 }, 4149 },
4094 { 4150 {
4095 "name": "symfony/yaml", 4151 "name": "symfony/yaml",
4096 - "version": "v3.0.2", 4152 + "version": "v3.0.4",
4097 "source": { 4153 "source": {
4098 "type": "git", 4154 "type": "git",
4099 "url": "https://github.com/symfony/yaml.git", 4155 "url": "https://github.com/symfony/yaml.git",
4100 - "reference": "3cf0709d7fe936e97bee9e954382e449003f1d9a" 4156 + "reference": "0047c8366744a16de7516622c5b7355336afae96"
4101 }, 4157 },
4102 "dist": { 4158 "dist": {
4103 "type": "zip", 4159 "type": "zip",
4104 - "url": "https://api.github.com/repos/symfony/yaml/zipball/3cf0709d7fe936e97bee9e954382e449003f1d9a", 4160 + "url": "https://api.github.com/repos/symfony/yaml/zipball/0047c8366744a16de7516622c5b7355336afae96",
4105 - "reference": "3cf0709d7fe936e97bee9e954382e449003f1d9a", 4161 + "reference": "0047c8366744a16de7516622c5b7355336afae96",
4106 "shasum": "" 4162 "shasum": ""
4107 }, 4163 },
4108 "require": { 4164 "require": {
...@@ -4138,7 +4194,7 @@ ...@@ -4138,7 +4194,7 @@
4138 ], 4194 ],
4139 "description": "Symfony Yaml Component", 4195 "description": "Symfony Yaml Component",
4140 "homepage": "https://symfony.com", 4196 "homepage": "https://symfony.com",
4141 - "time": "2016-02-02 13:44:19" 4197 + "time": "2016-03-04 07:55:57"
4142 } 4198 }
4143 ], 4199 ],
4144 "aliases": [], 4200 "aliases": [],
......
...@@ -5,6 +5,8 @@ return [ ...@@ -5,6 +5,8 @@ return [
5 5
6 'env' => env('APP_ENV', 'production'), 6 'env' => env('APP_ENV', 'production'),
7 7
8 + 'editor' => env('APP_EDITOR', 'html'),
9 +
8 /* 10 /*
9 |-------------------------------------------------------------------------- 11 |--------------------------------------------------------------------------
10 | Application Debug Mode 12 | Application Debug Mode
......
...@@ -6,9 +6,8 @@ if (env('CACHE_DRIVER') === 'memcached') { ...@@ -6,9 +6,8 @@ if (env('CACHE_DRIVER') === 'memcached') {
6 $memcachedServers = explode(',', trim(env('MEMCACHED_SERVERS', '127.0.0.1:11211:100'), ',')); 6 $memcachedServers = explode(',', trim(env('MEMCACHED_SERVERS', '127.0.0.1:11211:100'), ','));
7 foreach ($memcachedServers as $index => $memcachedServer) { 7 foreach ($memcachedServers as $index => $memcachedServer) {
8 $memcachedServerDetails = explode(':', $memcachedServer); 8 $memcachedServerDetails = explode(':', $memcachedServer);
9 - $components = count($memcachedServerDetails); 9 + if (count($memcachedServerDetails) < 2) $memcachedServerDetails[] = '11211';
10 - if ($components < 2) $memcachedServerDetails[] = '11211'; 10 + if (count($memcachedServerDetails) < 3) $memcachedServerDetails[] = '100';
11 - if ($components < 3) $memcachedServerDetails[] = '100';
12 $memcachedServers[$index] = array_combine($memcachedServerKeys, $memcachedServerDetails); 11 $memcachedServers[$index] = array_combine($memcachedServerKeys, $memcachedServerDetails);
13 } 12 }
14 } 13 }
...@@ -83,6 +82,6 @@ return [ ...@@ -83,6 +82,6 @@ return [
83 | 82 |
84 */ 83 */
85 84
86 - 'prefix' => 'laravel', 85 + 'prefix' => env('CACHE_PREFIX', 'bookstack'),
87 86
88 ]; 87 ];
......
1 <?php 1 <?php
2 2
3 +// REDIS - Split out configuration into an array
4 +if (env('REDIS_SERVERS', false)) {
5 + $redisServerKeys = ['host', 'port', 'database'];
6 + $redisServers = explode(',', trim(env('REDIS_SERVERS', '127.0.0.1:6379:0'), ','));
7 + $redisConfig = [
8 + 'cluster' => env('REDIS_CLUSTER', false)
9 + ];
10 + foreach ($redisServers as $index => $redisServer) {
11 + $redisServerName = ($index === 0) ? 'default' : 'redis-server-' . $index;
12 + $redisServerDetails = explode(':', $redisServer);
13 + if (count($redisServerDetails) < 2) $redisServerDetails[] = '6379';
14 + if (count($redisServerDetails) < 3) $redisServerDetails[] = '0';
15 + $redisConfig[$redisServerName] = array_combine($redisServerKeys, $redisServerDetails);
16 + }
17 +}
18 +
3 return [ 19 return [
4 20
5 /* 21 /*
...@@ -123,16 +139,6 @@ return [ ...@@ -123,16 +139,6 @@ return [
123 | 139 |
124 */ 140 */
125 141
126 - 'redis' => [ 142 + 'redis' => $redisConfig,
127 -
128 - 'cluster' => false,
129 -
130 - 'default' => [
131 - 'host' => '127.0.0.1',
132 - 'port' => 6379,
133 - 'database' => 0,
134 - ],
135 -
136 - ],
137 143
138 ]; 144 ];
......
1 +<?php
2 +
3 +/**
4 + * The defaults for the system settings that are saved in the database.
5 + */
6 +return [
7 +
8 + 'app-editor' => 'wysiwyg'
9 +
10 +];
...\ No newline at end of file ...\ No newline at end of file
1 +<?php
2 +
3 +use Illuminate\Database\Schema\Blueprint;
4 +use Illuminate\Database\Migrations\Migration;
5 +
6 +class AddMarkdownSupport extends Migration
7 +{
8 + /**
9 + * Run the migrations.
10 + *
11 + * @return void
12 + */
13 + public function up()
14 + {
15 + Schema::table('pages', function (Blueprint $table) {
16 + $table->longText('markdown')->default('');
17 + });
18 +
19 + Schema::table('page_revisions', function (Blueprint $table) {
20 + $table->longText('markdown')->default('');
21 + });
22 + }
23 +
24 + /**
25 + * Reverse the migrations.
26 + *
27 + * @return void
28 + */
29 + public function down()
30 + {
31 + Schema::table('pages', function (Blueprint $table) {
32 + $table->dropColumn('markdown');
33 + });
34 +
35 + Schema::table('page_revisions', function (Blueprint $table) {
36 + $table->dropColumn('markdown');
37 + });
38 + }
39 +}
...@@ -12,6 +12,8 @@ ...@@ -12,6 +12,8 @@
12 "bootstrap-sass": "^3.0.0", 12 "bootstrap-sass": "^3.0.0",
13 "dropzone": "^4.0.1", 13 "dropzone": "^4.0.1",
14 "laravel-elixir": "^3.4.0", 14 "laravel-elixir": "^3.4.0",
15 + "marked": "^0.3.5",
16 + "moment": "^2.12.0",
15 "zeroclipboard": "^2.2.0" 17 "zeroclipboard": "^2.2.0"
16 } 18 }
17 } 19 }
......
No preview for this file type
1 # BookStack 1 # BookStack
2 2
3 -A platform to create documentation/wiki content. General information about BookStack can be found at https://www.bookstackapp.com/ 3 +A platform for storing and organising information and documentation. General information and documentation for BookStack can be found at https://www.bookstackapp.com/.
4 4
5 -1. [Requirements](#requirements) 5 +* [Installation Instructions](https://www.bookstackapp.com/docs/admin/installation)
6 -2. [Installation](#installation) 6 +* [Documentation](https://www.bookstackapp.com/docs)
7 - - [Server Rewrite Rules](#url-rewrite-rules) 7 +* [Demo Instance](https://demo.bookstackapp.com) *(Login username: `admin@example.com`. Password: `password`)*
8 -3. [Updating](#updating-bookstack) 8 +* [BookStack Blog](https://www.bookstackapp.com/blog)
9 -4. [Social Authentication](#social-authentication)
10 - - [Google](#google)
11 - - [GitHub](#github)
12 -5. [LDAP Authentication](#ldap-authentication)
13 -6. [Testing](#testing)
14 -7. [License](#license)
15 -8. [Attribution](#attribution)
16 -
17 -
18 -## Requirements
19 -
20 -BookStack has similar requirements to Laravel:
21 -
22 -* PHP >= 5.5.9, Will need to be usable from the command line.
23 -* PHP Extensions: `OpenSSL`, `PDO`, `MBstring`, `Tokenizer`, `GD`
24 -* MySQL >= 5.6
25 -* Git (Not strictly required but helps manage updates)
26 -* [Composer](https://getcomposer.org/)
27 -
28 -## Installation
29 -
30 -Ensure the above requirements are met before installing. Currently BookStack requires its own domain/subdomain and will not work in a site subdirectory.
31 -
32 -This project currently uses the `release` branch of this repository as a stable channel for providing updates.
33 -
34 -The installation is currently somewhat complicated and will be made simpler in future releases. Some PHP/Laravel experience will currently benefit.
35 -
36 -1. Clone the release branch of this repository into a folder.
37 -
38 -```
39 -git clone https://github.com/ssddanbrown/BookStack.git --branch release --single-branch
40 -```
41 -
42 -2. `cd` into the application folder and run `composer install`.
43 -3. Copy the `.env.example` file to `.env` and fill with your own database and mail details.
44 -4. Ensure the `storage`, `bootstrap/cache` & `public/uploads` folders are writable by the web server.
45 -5. In the application root, Run `php artisan key:generate` to generate a unique application key.
46 -6. If not using apache or if `.htaccess` files are disabled you will have to create some URL rewrite rules as shown below.
47 -7. Run `php artisan migrate` to update the database.
48 -8. Done! You can now login using the default admin details `admin@admin.com` with a password of `password`. It is recommended to change these details directly after first logging in.
49 -
50 -#### URL Rewrite rules
51 -
52 -**Apache**
53 -```
54 -Options +FollowSymLinks
55 -RewriteEngine On
56 -
57 -RewriteCond %{REQUEST_FILENAME} !-d
58 -RewriteCond %{REQUEST_FILENAME} !-f
59 -RewriteRule ^ index.php [L]
60 -```
61 -
62 -**Nginx**
63 -```
64 -location / {
65 - try_files $uri $uri/ /index.php?$query_string;
66 -}
67 -```
68 -## Updating BookStack
69 -
70 -To update BookStack you can run the following command in the root directory of the application:
71 -```
72 -git pull origin release && composer install && php artisan migrate
73 -```
74 -This command will update the repository that was created in the installation, install the PHP dependencies using `composer` then run the database migrations.
75 -
76 -## Social Authentication
77 -
78 -BookStack currently supports login via both Google and GitHub. Once enabled options for these services will show up in the login, registration and user profile pages. By default these services are disabled. To enable them you will have to create an application on the external services to obtain the require application id's and secrets. Here are instructions to do this for the current supported services:
79 -
80 -### Google
81 -
82 -1. Open the [Google Developers Console](https://console.developers.google.com/).
83 -2. Create a new project (May have to wait a short while for it to be created).
84 -3. Select 'Enable and manage APIs'.
85 -4. Enable the 'Google+ API'.
86 -5. In 'Credentials' choose the 'OAuth consent screen' tab and enter a product name ('BookStack' or your custom set name).
87 -6. Back in the 'Credentials' tab click 'New credentials' > 'OAuth client ID'.
88 -7. Choose an application type of 'Web application' and enter the following urls under 'Authorized redirect URIs', changing `https://example.com` to your own domain where BookStack is hosted:
89 - - `https://example.com/login/service/google/callback`
90 - - `https://example.com/register/service/google/callback`
91 -8. Click 'Create' and your app_id and secret will be displayed. Replace the false value on both the `GOOGLE_APP_ID` & `GOOGLE_APP_SECRET` variables in the '.env' file in the BookStack root directory with your own app_id and secret.
92 -9. Set the 'APP_URL' environment variable to be the same domain as you entered in step 7. So, in this example, it will be `https://example.com`.
93 -10. All done! Users should now be able to link to their social accounts in their account profile pages and also register/login using their Google accounts.
94 -
95 -### Github
96 -
97 -1. While logged in, open up your [GitHub developer applications](https://github.com/settings/developers).
98 -2. Click 'Register new application'.
99 -3. Enter an application name ('BookStack' or your custom set name), A link to your app instance under 'Homepage URL' and an 'Authorization callback URL' of the url that your BookStack instance is hosted on then click 'Register application'.
100 -4. A 'Client ID' and a 'Client Secret' value will be shown. Add these two values to the to the `GITHUB_APP_ID` and `GITHUB_APP_SECRET` variables, replacing the default false value, in the '.env' file found in the BookStack root folder.
101 -5. Set the 'APP_URL' environment variable to be the same domain as you entered in step 3.
102 -6. All done! Users should now be able to link to their social accounts in their account profile pages and also register/login using their Github account.
103 -
104 -## LDAP Authentication
105 -
106 -BookStack can be configured to allow LDAP based user login. While LDAP login is enabled you cannot log in with the standard user/password login and new user registration is disabled. BookStack will only use the LDAP server for getting user details and for authentication. Data on the LDAP server is not currently editable through BookStack.
107 -
108 -When a LDAP user logs into BookStack for the first time their BookStack profile will be created and they will be given the default role set under the 'Default user role after registration' option in the application settings.
109 -
110 -To set up LDAP-based authentication add or modify the following variables in your `.env` file:
111 -
112 -```
113 -# General auth
114 -AUTH_METHOD=ldap
115 -
116 -# The LDAP host, Adding a port is optional
117 -LDAP_SERVER=ldap://example.com:389
118 -
119 -# The base DN from where users will be searched within.
120 -LDAP_BASE_DN=ou=People,dc=example,dc=com
121 -
122 -# The full DN and password of the user used to search the server
123 -# Can both be left as false to bind anonymously
124 -LDAP_DN=false
125 -LDAP_PASS=false
126 -
127 -# A filter to use when searching for users
128 -# The user-provided user-name used to replace any occurrences of '${user}'
129 -LDAP_USER_FILTER=(&(uid=${user}))
130 -
131 -# Set the LDAP version to use when connecting to the server.
132 -LDAP_VERSION=false
133 -```
134 -
135 -You will also need to have the php-ldap extension installed on your system. It's recommended to change your `APP_DEBUG` variable to `true` while setting up LDAP to make any errors visible. Remember to change this back after LDAP is functioning.
136 -
137 -A user in BookStack will be linked to a LDAP user via a 'uid'. If a LDAP user uid changes it can be updated in BookStack by an admin by changing the 'External Authentication ID' field on the user's profile.
138 -
139 -You may find that you cannot log in with your initial Admin account after changing the `AUTH_METHOD` to `ldap`. To get around this set the `AUTH_METHOD` to `standard`, login with your admin account then change it back to `ldap`. You get then edit your profile and add your LDAP uid under the 'External Authentication ID' field. You will then be able to login in with that ID.
140 9
141 ## Development & Testing 10 ## Development & Testing
142 11
143 All development on BookStack is currently done on the master branch. When it's time for a release the master branch is merged into release with built & minified CSS & JS then tagged at it's version. Here are the current development requirements: 12 All development on BookStack is currently done on the master branch. When it's time for a release the master branch is merged into release with built & minified CSS & JS then tagged at it's version. Here are the current development requirements:
144 13
145 -* [Node.js](https://nodejs.org/en/) **Development Only** 14 +* [Node.js](https://nodejs.org/en/)
146 -* [Gulp](http://gulpjs.com/) **Development Only** 15 +* [Gulp](http://gulpjs.com/)
147 16
148 SASS is used to help the CSS development and the JavaScript is run through browserify/babel to allow for writing ES6 code. Both of these are done using gulp. 17 SASS is used to help the CSS development and the JavaScript is run through browserify/babel to allow for writing ES6 code. Both of these are done using gulp.
149 18
...@@ -176,3 +45,4 @@ These are the great projects used to help build BookStack: ...@@ -176,3 +45,4 @@ These are the great projects used to help build BookStack:
176 * [Dropzone.js](http://www.dropzonejs.com/) 45 * [Dropzone.js](http://www.dropzonejs.com/)
177 * [ZeroClipboard](http://zeroclipboard.org/) 46 * [ZeroClipboard](http://zeroclipboard.org/)
178 * [TinyColorPicker](http://www.dematte.at/tinyColorPicker/index.html) 47 * [TinyColorPicker](http://www.dematte.at/tinyColorPicker/index.html)
48 +* [Marked](https://github.com/chjj/marked)
......
1 "use strict"; 1 "use strict";
2 2
3 +var moment = require('moment');
4 +
3 module.exports = function (ngApp, events) { 5 module.exports = function (ngApp, events) {
4 6
5 ngApp.controller('ImageManagerController', ['$scope', '$attrs', '$http', '$timeout', 'imageManagerService', 7 ngApp.controller('ImageManagerController', ['$scope', '$attrs', '$http', '$timeout', 'imageManagerService',
...@@ -14,14 +16,22 @@ module.exports = function (ngApp, events) { ...@@ -14,14 +16,22 @@ module.exports = function (ngApp, events) {
14 $scope.imageUpdateSuccess = false; 16 $scope.imageUpdateSuccess = false;
15 $scope.imageDeleteSuccess = false; 17 $scope.imageDeleteSuccess = false;
16 $scope.uploadedTo = $attrs.uploadedTo; 18 $scope.uploadedTo = $attrs.uploadedTo;
19 + $scope.view = 'all';
20 +
21 + $scope.searching = false;
22 + $scope.searchTerm = '';
17 23
18 var page = 0; 24 var page = 0;
19 var previousClickTime = 0; 25 var previousClickTime = 0;
26 + var previousClickImage = 0;
20 var dataLoaded = false; 27 var dataLoaded = false;
21 var callback = false; 28 var callback = false;
22 29
30 + var preSearchImages = [];
31 + var preSearchHasMore = false;
32 +
23 /** 33 /**
24 - * Simple returns the appropriate upload url depending on the image type set. 34 + * Used by dropzone to get the endpoint to upload to.
25 * @returns {string} 35 * @returns {string}
26 */ 36 */
27 $scope.getUploadUrl = function () { 37 $scope.getUploadUrl = function () {
...@@ -29,6 +39,18 @@ module.exports = function (ngApp, events) { ...@@ -29,6 +39,18 @@ module.exports = function (ngApp, events) {
29 }; 39 };
30 40
31 /** 41 /**
42 + * Cancel the current search operation.
43 + */
44 + function cancelSearch() {
45 + $scope.searching = false;
46 + $scope.searchTerm = '';
47 + $scope.images = preSearchImages;
48 + $scope.hasMore = preSearchHasMore;
49 + }
50 + $scope.cancelSearch = cancelSearch;
51 +
52 +
53 + /**
32 * Runs on image upload, Adds an image to local list of images 54 * Runs on image upload, Adds an image to local list of images
33 * and shows a success message to the user. 55 * and shows a success message to the user.
34 * @param file 56 * @param file
...@@ -59,7 +81,7 @@ module.exports = function (ngApp, events) { ...@@ -59,7 +81,7 @@ module.exports = function (ngApp, events) {
59 var currentTime = Date.now(); 81 var currentTime = Date.now();
60 var timeDiff = currentTime - previousClickTime; 82 var timeDiff = currentTime - previousClickTime;
61 83
62 - if (timeDiff < dblClickTime) { 84 + if (timeDiff < dblClickTime && image.id === previousClickImage) {
63 // If double click 85 // If double click
64 callbackAndHide(image); 86 callbackAndHide(image);
65 } else { 87 } else {
...@@ -68,6 +90,7 @@ module.exports = function (ngApp, events) { ...@@ -68,6 +90,7 @@ module.exports = function (ngApp, events) {
68 $scope.dependantPages = false; 90 $scope.dependantPages = false;
69 } 91 }
70 previousClickTime = currentTime; 92 previousClickTime = currentTime;
93 + previousClickImage = image.id;
71 }; 94 };
72 95
73 /** 96 /**
...@@ -110,21 +133,70 @@ module.exports = function (ngApp, events) { ...@@ -110,21 +133,70 @@ module.exports = function (ngApp, events) {
110 $scope.showing = false; 133 $scope.showing = false;
111 }; 134 };
112 135
136 + var baseUrl = '/images/' + $scope.imageType + '/all/'
137 +
113 /** 138 /**
114 * Fetch the list image data from the server. 139 * Fetch the list image data from the server.
115 */ 140 */
116 function fetchData() { 141 function fetchData() {
117 - var url = '/images/' + $scope.imageType + '/all/' + page; 142 + var url = baseUrl + page + '?';
143 + var components = {};
144 + if ($scope.uploadedTo) components['page_id'] = $scope.uploadedTo;
145 + if ($scope.searching) components['term'] = $scope.searchTerm;
146 +
147 +
148 + var urlQueryString = Object.keys(components).map((key) => {
149 + return key + '=' + encodeURIComponent(components[key]);
150 + }).join('&');
151 + url += urlQueryString;
152 +
118 $http.get(url).then((response) => { 153 $http.get(url).then((response) => {
119 $scope.images = $scope.images.concat(response.data.images); 154 $scope.images = $scope.images.concat(response.data.images);
120 $scope.hasMore = response.data.hasMore; 155 $scope.hasMore = response.data.hasMore;
121 page++; 156 page++;
122 }); 157 });
123 } 158 }
124 -
125 $scope.fetchData = fetchData; 159 $scope.fetchData = fetchData;
126 160
127 /** 161 /**
162 + * Start a search operation
163 + * @param searchTerm
164 + */
165 + $scope.searchImages = function() {
166 +
167 + if ($scope.searchTerm === '') {
168 + cancelSearch();
169 + return;
170 + }
171 +
172 + if (!$scope.searching) {
173 + preSearchImages = $scope.images;
174 + preSearchHasMore = $scope.hasMore;
175 + }
176 +
177 + $scope.searching = true;
178 + $scope.images = [];
179 + $scope.hasMore = false;
180 + page = 0;
181 + baseUrl = '/images/' + $scope.imageType + '/search/';
182 + fetchData();
183 + };
184 +
185 + /**
186 + * Set the current image listing view.
187 + * @param viewName
188 + */
189 + $scope.setView = function(viewName) {
190 + cancelSearch();
191 + $scope.images = [];
192 + $scope.hasMore = false;
193 + page = 0;
194 + $scope.view = viewName;
195 + baseUrl = '/images/' + $scope.imageType + '/' + viewName + '/';
196 + fetchData();
197 + }
198 +
199 + /**
128 * Save the details of an image. 200 * Save the details of an image.
129 * @param event 201 * @param event
130 */ 202 */
...@@ -216,16 +288,20 @@ module.exports = function (ngApp, events) { ...@@ -216,16 +288,20 @@ module.exports = function (ngApp, events) {
216 }]); 288 }]);
217 289
218 290
219 - ngApp.controller('PageEditController', ['$scope', '$http', '$attrs', '$interval', '$timeout', function ($scope, $http, $attrs, $interval, $timeout) { 291 + ngApp.controller('PageEditController', ['$scope', '$http', '$attrs', '$interval', '$timeout', '$sce',
292 + function ($scope, $http, $attrs, $interval, $timeout, $sce) {
220 293
221 $scope.editorOptions = require('./pages/page-form'); 294 $scope.editorOptions = require('./pages/page-form');
222 - $scope.editorHtml = ''; 295 + $scope.editContent = '';
223 $scope.draftText = ''; 296 $scope.draftText = '';
224 var pageId = Number($attrs.pageId); 297 var pageId = Number($attrs.pageId);
225 var isEdit = pageId !== 0; 298 var isEdit = pageId !== 0;
226 var autosaveFrequency = 30; // AutoSave interval in seconds. 299 var autosaveFrequency = 30; // AutoSave interval in seconds.
300 + var isMarkdown = $attrs.editorType === 'markdown';
227 $scope.isUpdateDraft = Number($attrs.pageUpdateDraft) === 1; 301 $scope.isUpdateDraft = Number($attrs.pageUpdateDraft) === 1;
228 $scope.isNewPageDraft = Number($attrs.pageNewDraft) === 1; 302 $scope.isNewPageDraft = Number($attrs.pageNewDraft) === 1;
303 +
304 + // Set inital header draft text
229 if ($scope.isUpdateDraft || $scope.isNewPageDraft) { 305 if ($scope.isUpdateDraft || $scope.isNewPageDraft) {
230 $scope.draftText = 'Editing Draft' 306 $scope.draftText = 'Editing Draft'
231 } else { 307 } else {
...@@ -245,7 +321,18 @@ module.exports = function (ngApp, events) { ...@@ -245,7 +321,18 @@ module.exports = function (ngApp, events) {
245 }, 1000); 321 }, 1000);
246 } 322 }
247 323
248 - $scope.editorChange = function () {} 324 + // Actions specifically for the markdown editor
325 + if (isMarkdown) {
326 + $scope.displayContent = '';
327 + // Editor change event
328 + $scope.editorChange = function (content) {
329 + $scope.displayContent = $sce.trustAsHtml(content);
330 + }
331 + }
332 +
333 + if (!isMarkdown) {
334 + $scope.editorChange = function() {};
335 + }
249 336
250 /** 337 /**
251 * Start the AutoSave loop, Checks for content change 338 * Start the AutoSave loop, Checks for content change
...@@ -253,17 +340,18 @@ module.exports = function (ngApp, events) { ...@@ -253,17 +340,18 @@ module.exports = function (ngApp, events) {
253 */ 340 */
254 function startAutoSave() { 341 function startAutoSave() {
255 currentContent.title = $('#name').val(); 342 currentContent.title = $('#name').val();
256 - currentContent.html = $scope.editorHtml; 343 + currentContent.html = $scope.editContent;
257 344
258 autoSave = $interval(() => { 345 autoSave = $interval(() => {
259 var newTitle = $('#name').val(); 346 var newTitle = $('#name').val();
260 - var newHtml = $scope.editorHtml; 347 + var newHtml = $scope.editContent;
261 348
262 if (newTitle !== currentContent.title || newHtml !== currentContent.html) { 349 if (newTitle !== currentContent.title || newHtml !== currentContent.html) {
263 currentContent.html = newHtml; 350 currentContent.html = newHtml;
264 currentContent.title = newTitle; 351 currentContent.title = newTitle;
265 - saveDraft(newTitle, newHtml); 352 + saveDraft();
266 } 353 }
354 +
267 }, 1000 * autosaveFrequency); 355 }, 1000 * autosaveFrequency);
268 } 356 }
269 357
...@@ -272,20 +360,23 @@ module.exports = function (ngApp, events) { ...@@ -272,20 +360,23 @@ module.exports = function (ngApp, events) {
272 * @param title 360 * @param title
273 * @param html 361 * @param html
274 */ 362 */
275 - function saveDraft(title, html) { 363 + function saveDraft() {
276 - $http.put('/ajax/page/' + pageId + '/save-draft', { 364 + var data = {
277 - name: title, 365 + name: $('#name').val(),
278 - html: html 366 + html: isMarkdown ? $sce.getTrustedHtml($scope.displayContent) : $scope.editContent
279 - }).then((responseData) => { 367 + };
280 - $scope.draftText = responseData.data.message; 368 +
369 + if (isMarkdown) data.markdown = $scope.editContent;
370 +
371 + $http.put('/ajax/page/' + pageId + '/save-draft', data).then((responseData) => {
372 + var updateTime = moment.utc(moment.unix(responseData.data.timestamp)).toDate();
373 + $scope.draftText = responseData.data.message + moment(updateTime).format('HH:mm');
281 if (!$scope.isNewPageDraft) $scope.isUpdateDraft = true; 374 if (!$scope.isNewPageDraft) $scope.isUpdateDraft = true;
282 }); 375 });
283 } 376 }
284 377
285 $scope.forceDraftSave = function() { 378 $scope.forceDraftSave = function() {
286 - var newTitle = $('#name').val(); 379 + saveDraft();
287 - var newHtml = $scope.editorHtml;
288 - saveDraft(newTitle, newHtml);
289 }; 380 };
290 381
291 /** 382 /**
...@@ -298,6 +389,7 @@ module.exports = function (ngApp, events) { ...@@ -298,6 +389,7 @@ module.exports = function (ngApp, events) {
298 $scope.draftText = 'Editing Page'; 389 $scope.draftText = 'Editing Page';
299 $scope.isUpdateDraft = false; 390 $scope.isUpdateDraft = false;
300 $scope.$broadcast('html-update', responseData.data.html); 391 $scope.$broadcast('html-update', responseData.data.html);
392 + $scope.$broadcast('markdown-update', responseData.data.markdown || responseData.data.html);
301 $('#name').val(responseData.data.name); 393 $('#name').val(responseData.data.name);
302 $timeout(() => { 394 $timeout(() => {
303 startAutoSave(); 395 startAutoSave();
......
1 "use strict"; 1 "use strict";
2 var DropZone = require('dropzone'); 2 var DropZone = require('dropzone');
3 +var markdown = require('marked');
3 4
4 var toggleSwitchTemplate = require('./components/toggle-switch.html'); 5 var toggleSwitchTemplate = require('./components/toggle-switch.html');
5 var imagePickerTemplate = require('./components/image-picker.html'); 6 var imagePickerTemplate = require('./components/image-picker.html');
...@@ -200,7 +201,82 @@ module.exports = function (ngApp, events) { ...@@ -200,7 +201,82 @@ module.exports = function (ngApp, events) {
200 tinymce.init(scope.tinymce); 201 tinymce.init(scope.tinymce);
201 } 202 }
202 } 203 }
203 - }]) 204 + }]);
205 +
206 + ngApp.directive('markdownInput', ['$timeout', function($timeout) {
207 + return {
208 + restrict: 'A',
209 + scope: {
210 + mdModel: '=',
211 + mdChange: '='
212 + },
213 + link: function (scope, element, attrs) {
214 +
215 + // Set initial model content
216 + var content = element.val();
217 + scope.mdModel = content;
218 + scope.mdChange(markdown(content));
219 +
220 + element.on('change input', (e) => {
221 + content = element.val();
222 + $timeout(() => {
223 + scope.mdModel = content;
224 + scope.mdChange(markdown(content));
225 + });
226 + });
227 +
228 + scope.$on('markdown-update', (event, value) => {
229 + element.val(value);
230 + scope.mdModel= value;
231 + scope.mdChange(markdown(value));
232 + });
233 +
234 + }
235 + }
236 + }]);
237 +
238 + ngApp.directive('markdownEditor', ['$timeout', function($timeout) {
239 + return {
240 + restrict: 'A',
241 + link: function (scope, element, attrs) {
242 +
243 + // Elements
244 + var input = element.find('textarea[markdown-input]');
245 + var insertImage = element.find('button[data-action="insertImage"]');
246 +
247 + var currentCaretPos = 0;
248 +
249 + input.blur((event) => {
250 + currentCaretPos = input[0].selectionStart;
251 + });
204 252
253 + // Insert image shortcut
254 + input.keydown((event) => {
255 + if (event.which === 73 && event.ctrlKey && event.shiftKey) {
256 + event.preventDefault();
257 + var caretPos = input[0].selectionStart;
258 + var currentContent = input.val();
259 + var mdImageText = "![](http://)";
260 + input.val(currentContent.substring(0, caretPos) + mdImageText + currentContent.substring(caretPos));
261 + input.focus();
262 + input[0].selectionStart = caretPos + ("![](".length);
263 + input[0].selectionEnd = caretPos + ('![](http://'.length);
264 + }
265 + });
266 +
267 + // Insert image from image manager
268 + insertImage.click((event) => {
269 + window.ImageManager.showExternal((image) => {
270 + var caretPos = currentCaretPos;
271 + var currentContent = input.val();
272 + var mdImageText = "![" + image.name + "](" + image.url + ")";
273 + input.val(currentContent.substring(0, caretPos) + mdImageText + currentContent.substring(caretPos));
274 + input.change();
275 + });
276 + });
277 +
278 + }
279 + }
280 + }])
205 281
206 }; 282 };
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -94,3 +94,14 @@ ...@@ -94,3 +94,14 @@
94 font-weight: normal; 94 font-weight: normal;
95 font-style: normal; 95 font-style: normal;
96 } 96 }
97 +
98 +/* roboto-mono-regular - latin */
99 +// https://google-webfonts-helper.herokuapp.com
100 +@font-face {
101 + font-family: 'Roboto Mono';
102 + font-style: normal;
103 + font-weight: 400;
104 + src: local('Roboto Mono'), local('RobotoMono-Regular'),
105 + url('/fonts/roboto-mono-v4-latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+ */
106 + url('/fonts/roboto-mono-v4-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
107 +}
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -26,6 +26,59 @@ ...@@ -26,6 +26,59 @@
26 display: none; 26 display: none;
27 } 27 }
28 28
29 +#markdown-editor {
30 + position: relative;
31 + z-index: 5;
32 + textarea {
33 + font-family: 'Roboto Mono';
34 + font-style: normal;
35 + font-weight: 400;
36 + padding: $-xs $-m;
37 + color: #444;
38 + border-radius: 0;
39 + max-height: 100%;
40 + flex: 1;
41 + border: 0;
42 + width: 100%;
43 + &:focus {
44 + outline: 0;
45 + }
46 + }
47 + .markdown-display, .markdown-editor-wrap {
48 + flex: 1;
49 + position: relative;
50 + }
51 + .markdown-editor-wrap {
52 + display: flex;
53 + flex-direction: column;
54 + border: 1px solid #DDD;
55 + }
56 + .markdown-display {
57 + padding: 0 $-m 0;
58 + margin-left: -1px;
59 + overflow-y: scroll;
60 + .page-content {
61 + margin: 0 auto;
62 + }
63 + }
64 +}
65 +.editor-toolbar {
66 + width: 100%;
67 + padding: $-xs $-m;
68 + font-family: 'Roboto Mono';
69 + font-size: 11px;
70 + line-height: 1.6;
71 + border-bottom: 1px solid #DDD;
72 + background-color: #EEE;
73 + flex: none;
74 + &:after {
75 + content: '';
76 + display: block;
77 + clear: both;
78 + }
79 +}
80 +
81 +
29 label { 82 label {
30 display: block; 83 display: block;
31 line-height: 1.4em; 84 line-height: 1.4em;
...@@ -160,6 +213,10 @@ input:checked + .toggle-switch { ...@@ -160,6 +213,10 @@ input:checked + .toggle-switch {
160 width: 100%; 213 width: 100%;
161 } 214 }
162 215
216 +div[editor-type="markdown"] .title-input.page-title input[type="text"] {
217 + max-width: 100%;
218 +}
219 +
163 .search-box { 220 .search-box {
164 max-width: 100%; 221 max-width: 100%;
165 position: relative; 222 position: relative;
......
...@@ -189,12 +189,13 @@ form.search-box { ...@@ -189,12 +189,13 @@ form.search-box {
189 } 189 }
190 } 190 }
191 191
192 -.setting-nav { 192 +.nav-tabs {
193 text-align: center; 193 text-align: center;
194 - a { 194 + a, .tab-item {
195 padding: $-m; 195 padding: $-m;
196 display: inline-block; 196 display: inline-block;
197 color: #666; 197 color: #666;
198 + cursor: pointer;
198 &.selected { 199 &.selected {
199 border-bottom: 2px solid $primary; 200 border-bottom: 2px solid $primary;
200 } 201 }
......
...@@ -120,7 +120,6 @@ ...@@ -120,7 +120,6 @@
120 .image-manager-list { 120 .image-manager-list {
121 overflow-y: scroll; 121 overflow-y: scroll;
122 flex: 1; 122 flex: 1;
123 - border-top: 1px solid #ddd;
124 } 123 }
125 124
126 .image-manager-content { 125 .image-manager-content {
...@@ -128,6 +127,12 @@ ...@@ -128,6 +127,12 @@
128 flex-direction: column; 127 flex-direction: column;
129 height: 100%; 128 height: 100%;
130 flex: 1; 129 flex: 1;
130 + .container {
131 + width: 100%;
132 + }
133 + .full-tab {
134 + text-align: center;
135 + }
131 } 136 }
132 137
133 // Dropzone 138 // Dropzone
......
...@@ -25,3 +25,12 @@ table { ...@@ -25,3 +25,12 @@ table {
25 font-weight: 500; 25 font-weight: 500;
26 } 26 }
27 } 27 }
28 +
29 +table.list-table {
30 + margin: 0 -$-xs;
31 + td {
32 + border: 0;
33 + vertical-align: middle;
34 + padding: $-xs;
35 + }
36 +}
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -157,6 +157,12 @@ span.code { ...@@ -157,6 +157,12 @@ span.code {
157 @extend .code-base; 157 @extend .code-base;
158 padding: 1px $-xs; 158 padding: 1px $-xs;
159 } 159 }
160 +
161 +pre code {
162 + background-color: transparent;
163 + border: 0;
164 + font-size: 1em;
165 +}
160 /* 166 /*
161 * Text colors 167 * Text colors
162 */ 168 */
......
...@@ -177,3 +177,28 @@ $btt-size: 40px; ...@@ -177,3 +177,28 @@ $btt-size: 40px;
177 top: -5px; 177 top: -5px;
178 } 178 }
179 } 179 }
180 +
181 +.contained-search-box {
182 + display: flex;
183 + input, button {
184 + border-radius: 0;
185 + border: 1px solid #DDD;
186 + margin-left: -1px;
187 + }
188 + input {
189 + flex: 5;
190 + &:focus, &:active {
191 + outline: 0;
192 + }
193 + }
194 + button {
195 + width: 60px;
196 + }
197 + button i {
198 + padding: 0;
199 + }
200 + button.cancel.active {
201 + background-color: $negative;
202 + color: #EEE;
203 + }
204 +}
...\ No newline at end of file ...\ No newline at end of file
......
1 1
2 <label> 2 <label>
3 <input value="true" id="{{$name}}[{{$role->id}}][{{$action}}]" type="checkbox" name="{{$name}}[{{$role->id}}][{{$action}}]" 3 <input value="true" id="{{$name}}[{{$role->id}}][{{$action}}]" type="checkbox" name="{{$name}}[{{$role->id}}][{{$action}}]"
4 - @if(old($name .'.'.$role->id.'.'.$action) || (!old() && isset($model) && $model->hasRestriction($role->id, $action))) checked="checked" @endif 4 + @if(isset($model) && $model->hasRestriction($role->id, $action)) checked="checked" @endif>
5 - >
6 {{ $label }} 5 {{ $label }}
7 </label> 6 </label>
...\ No newline at end of file ...\ No newline at end of file
......
1 1
2 -<div class="page-editor flex-fill flex" ng-controller="PageEditController" page-id="{{ $model->id or 0 }}" page-new-draft="{{ $model->draft or 0 }}" page-update-draft="{{ $model->isDraft or 0 }}"> 2 +<div class="page-editor flex-fill flex" ng-controller="PageEditController" editor-type="{{ setting('app-editor') }}" page-id="{{ $model->id or 0 }}" page-new-draft="{{ $model->draft or 0 }}" page-update-draft="{{ $model->isDraft or 0 }}">
3 3
4 {{ csrf_field() }} 4 {{ csrf_field() }}
5 <div class="faded-small toolbar"> 5 <div class="faded-small toolbar">
...@@ -42,10 +42,45 @@ ...@@ -42,10 +42,45 @@
42 </div> 42 </div>
43 </div> 43 </div>
44 <div class="edit-area flex-fill flex"> 44 <div class="edit-area flex-fill flex">
45 - <textarea id="html-editor" tinymce="editorOptions" mce-change="editorChange" mce-model="editorHtml" name="html" rows="5" 45 + @if(setting('app-editor') === 'wysiwyg')
46 + <textarea id="html-editor" tinymce="editorOptions" mce-change="editorChange" mce-model="editContent" name="html" rows="5"
46 @if($errors->has('html')) class="neg" @endif>@if(isset($model) || old('html')){{htmlspecialchars( old('html') ? old('html') : $model->html)}}@endif</textarea> 47 @if($errors->has('html')) class="neg" @endif>@if(isset($model) || old('html')){{htmlspecialchars( old('html') ? old('html') : $model->html)}}@endif</textarea>
47 @if($errors->has('html')) 48 @if($errors->has('html'))
48 <div class="text-neg text-small">{{ $errors->first('html') }}</div> 49 <div class="text-neg text-small">{{ $errors->first('html') }}</div>
49 @endif 50 @endif
51 + @endif
52 +
53 + @if(setting('app-editor') === 'markdown')
54 + <div id="markdown-editor" markdown-editor class="flex-fill flex">
55 +
56 + <div class="markdown-editor-wrap">
57 + <div class="editor-toolbar">
58 + <span class="float left">Editor</span>
59 + <div class="float right buttons">
60 + <button class="text-button" type="button" data-action="insertImage"><i class="zmdi zmdi-image"></i>Insert Image</button>
61 + </div>
62 + </div>
63 + <textarea markdown-input md-change="editorChange" md-model="editContent" name="markdown" rows="5"
64 + @if($errors->has('markdown')) class="neg" @endif>@if(isset($model) || old('markdown')){{htmlspecialchars( old('markdown') ? old('markdown') : ($model->markdown === '' ? $model->html : $model->markdown))}}@endif</textarea>
65 + </div>
66 +
67 + <div class="markdown-editor-wrap">
68 + <div class="editor-toolbar">
69 + <div class="">Preview</div>
70 + </div>
71 + <div class="markdown-display">
72 + <div class="page-content" ng-bind-html="displayContent"></div>
73 + </div>
74 + </div>
75 +
76 + </div>
77 +
78 + <input type="hidden" name="html" ng-value="displayContent">
79 +
80 + @if($errors->has('markdown'))
81 + <div class="text-neg text-small">{{ $errors->first('markdown') }}</div>
82 + @endif
83 +
84 + @endif
50 </div> 85 </div>
51 </div> 86 </div>
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
12 .button-base:hover, .button:hover, input[type="button"]:hover, input[type="submit"]:hover, .button:focus { 12 .button-base:hover, .button:hover, input[type="button"]:hover, input[type="submit"]:hover, .button:focus {
13 background-color: {{ Setting::get('app-color') }}; 13 background-color: {{ Setting::get('app-color') }};
14 } 14 }
15 - .setting-nav a.selected { 15 + .nav-tabs a.selected, .nav-tabs .tab-item.selected {
16 border-bottom-color: {{ Setting::get('app-color') }}; 16 border-bottom-color: {{ Setting::get('app-color') }};
17 } 17 }
18 p.primary:hover, p .primary:hover, span.primary:hover, .text-primary:hover, a, a:hover, a:focus { 18 p.primary:hover, p .primary:hover, span.primary:hover, .text-primary:hover, a, a:hover, a:focus {
......
...@@ -3,6 +3,20 @@ ...@@ -3,6 +3,20 @@
3 <div class="image-manager-body" ng-click="$event.stopPropagation()"> 3 <div class="image-manager-body" ng-click="$event.stopPropagation()">
4 4
5 <div class="image-manager-content"> 5 <div class="image-manager-content">
6 + <div ng-if="imageType === 'gallery'" class="container">
7 + <div class="image-manager-header row faded-small nav-tabs">
8 + <div class="col-xs-4 tab-item" title="View all images" ng-class="{selected: (view=='all')}" ng-click="setView('all')"><i class="zmdi zmdi-collection-image"></i> All</div>
9 + <div class="col-xs-4 tab-item" title="View images uploaded to this book" ng-class="{selected: (view=='book')}" ng-click="setView('book')"><i class="zmdi zmdi-book text-book"></i> Book</div>
10 + <div class="col-xs-4 tab-item" title="View images uploaded to this page" ng-class="{selected: (view=='page')}" ng-click="setView('page')"><i class="zmdi zmdi-file-text text-page"></i> Page</div>
11 + </div>
12 + </div>
13 + <div ng-show="view === 'all'" >
14 + <form ng-submit="searchImages()" class="contained-search-box">
15 + <input type="text" placeholder="Search by image name" ng-model="searchTerm">
16 + <button ng-class="{active: searching}" title="Clear Search" type="button" ng-click="cancelSearch()" class="text-button cancel"><i class="zmdi zmdi-close-circle-o"></i></button>
17 + <button title="Search" class="text-button" type="submit"><i class="zmdi zmdi-search"></i></button>
18 + </form>
19 + </div>
6 <div class="image-manager-list"> 20 <div class="image-manager-list">
7 <div ng-repeat="image in images"> 21 <div ng-repeat="image in images">
8 <div class="image anim fadeIn" ng-style="{animationDelay: ($index > 26) ? '160ms' : ($index * 25) + 'ms'}" 22 <div class="image anim fadeIn" ng-style="{animationDelay: ($index > 26) ? '160ms' : ($index * 25) + 'ms'}"
......
...@@ -17,29 +17,37 @@ ...@@ -17,29 +17,37 @@
17 <div class="col-md-6"> 17 <div class="col-md-6">
18 <div class="form-group"> 18 <div class="form-group">
19 <label for="setting-app-name">Application name</label> 19 <label for="setting-app-name">Application name</label>
20 - <input type="text" value="{{ Setting::get('app-name', 'BookStack') }}" name="setting-app-name" id="setting-app-name"> 20 + <input type="text" value="{{ setting('app-name', 'BookStack') }}" name="setting-app-name" id="setting-app-name">
21 </div> 21 </div>
22 <div class="form-group"> 22 <div class="form-group">
23 <label>Allow public viewing?</label> 23 <label>Allow public viewing?</label>
24 - <toggle-switch name="setting-app-public" value="{{ Setting::get('app-public') }}"></toggle-switch> 24 + <toggle-switch name="setting-app-public" value="{{ setting('app-public') }}"></toggle-switch>
25 </div> 25 </div>
26 <div class="form-group"> 26 <div class="form-group">
27 <label>Enable higher security image uploads?</label> 27 <label>Enable higher security image uploads?</label>
28 <p class="small">For performance reasons, all images are public by default, This option adds a random, hard-to-guess characters in front of image names. Ensure directory indexes are not enabled to prevent easy access.</p> 28 <p class="small">For performance reasons, all images are public by default, This option adds a random, hard-to-guess characters in front of image names. Ensure directory indexes are not enabled to prevent easy access.</p>
29 - <toggle-switch name="setting-app-secure-images" value="{{ Setting::get('app-secure-images') }}"></toggle-switch> 29 + <toggle-switch name="setting-app-secure-images" value="{{ setting('app-secure-images') }}"></toggle-switch>
30 + </div>
31 + <div class="form-group">
32 + <label for="setting-app-editor">Page editor</label>
33 + <p class="small">Select which editor will be used by all users to edit pages.</p>
34 + <select name="setting-app-editor" id="setting-app-editor">
35 + <option @if(setting('app-editor') === 'wysiwyg') selected @endif value="wysiwyg">WYSIWYG</option>
36 + <option @if(setting('app-editor') === 'markdown') selected @endif value="markdown">Markdown</option>
37 + </select>
30 </div> 38 </div>
31 </div> 39 </div>
32 <div class="col-md-6"> 40 <div class="col-md-6">
33 <div class="form-group" id="logo-control"> 41 <div class="form-group" id="logo-control">
34 - <label for="setting-app-logo">Application Logo</label> 42 + <label for="setting-app-logo">Application logo</label>
35 <p class="small">This image should be 43px in height. <br>Large images will be scaled down.</p> 43 <p class="small">This image should be 43px in height. <br>Large images will be scaled down.</p>
36 - <image-picker resize-height="43" show-remove="true" resize-width="200" current-image="{{ Setting::get('app-logo', '') }}" default-image="/logo.png" name="setting-app-logo" image-class="logo-image"></image-picker> 44 + <image-picker resize-height="43" show-remove="true" resize-width="200" current-image="{{ setting('app-logo', '') }}" default-image="/logo.png" name="setting-app-logo" image-class="logo-image"></image-picker>
37 </div> 45 </div>
38 <div class="form-group" id="color-control"> 46 <div class="form-group" id="color-control">
39 - <label for="setting-app-color">Application Primary Color</label> 47 + <label for="setting-app-color">Application primary color</label>
40 <p class="small">This should be a hex value. <br> Leave empty to reset to the default color.</p> 48 <p class="small">This should be a hex value. <br> Leave empty to reset to the default color.</p>
41 - <input type="text" value="{{ Setting::get('app-color', '') }}" name="setting-app-color" id="setting-app-color" placeholder="#0288D1"> 49 + <input type="text" value="{{ setting('app-color', '') }}" name="setting-app-color" id="setting-app-color" placeholder="#0288D1">
42 - <input type="hidden" value="{{ Setting::get('app-color-light', '') }}" name="setting-app-color-light" id="setting-app-color-light" placeholder="rgba(21, 101, 192, 0.15)"> 50 + <input type="hidden" value="{{ setting('app-color-light', '') }}" name="setting-app-color-light" id="setting-app-color-light" placeholder="rgba(21, 101, 192, 0.15)">
43 </div> 51 </div>
44 </div> 52 </div>
45 </div> 53 </div>
...@@ -53,14 +61,14 @@ ...@@ -53,14 +61,14 @@
53 <div class="col-md-6"> 61 <div class="col-md-6">
54 <div class="form-group"> 62 <div class="form-group">
55 <label for="setting-registration-enabled">Allow registration?</label> 63 <label for="setting-registration-enabled">Allow registration?</label>
56 - <toggle-switch name="setting-registration-enabled" value="{{ Setting::get('registration-enabled') }}"></toggle-switch> 64 + <toggle-switch name="setting-registration-enabled" value="{{ setting('registration-enabled') }}"></toggle-switch>
57 </div> 65 </div>
58 <div class="form-group"> 66 <div class="form-group">
59 <label for="setting-registration-role">Default user role after registration</label> 67 <label for="setting-registration-role">Default user role after registration</label>
60 <select id="setting-registration-role" name="setting-registration-role" @if($errors->has('setting-registration-role')) class="neg" @endif> 68 <select id="setting-registration-role" name="setting-registration-role" @if($errors->has('setting-registration-role')) class="neg" @endif>
61 @foreach(\BookStack\Role::all() as $role) 69 @foreach(\BookStack\Role::all() as $role)
62 <option value="{{$role->id}}" 70 <option value="{{$role->id}}"
63 - @if(\Setting::get('registration-role', \BookStack\Role::first()->id) == $role->id) selected @endif 71 + @if(setting('registration-role', \BookStack\Role::first()->id) == $role->id) selected @endif
64 > 72 >
65 {{ $role->display_name }} 73 {{ $role->display_name }}
66 </option> 74 </option>
...@@ -70,7 +78,7 @@ ...@@ -70,7 +78,7 @@
70 <div class="form-group"> 78 <div class="form-group">
71 <label for="setting-registration-confirmation">Require email confirmation?</label> 79 <label for="setting-registration-confirmation">Require email confirmation?</label>
72 <p class="small">If domain restriction is used then email confirmation will be required and the below value will be ignored.</p> 80 <p class="small">If domain restriction is used then email confirmation will be required and the below value will be ignored.</p>
73 - <toggle-switch name="setting-registration-confirmation" value="{{ Setting::get('registration-confirmation') }}"></toggle-switch> 81 + <toggle-switch name="setting-registration-confirmation" value="{{ setting('registration-confirmation') }}"></toggle-switch>
74 </div> 82 </div>
75 </div> 83 </div>
76 <div class="col-md-6"> 84 <div class="col-md-6">
...@@ -78,7 +86,7 @@ ...@@ -78,7 +86,7 @@
78 <label for="setting-registration-restrict">Restrict registration to domain</label> 86 <label for="setting-registration-restrict">Restrict registration to domain</label>
79 <p class="small">Enter a comma separated list of email domains you would like to restrict registration to. Users will be sent an email to confirm their address before being allowed to interact with the application. 87 <p class="small">Enter a comma separated list of email domains you would like to restrict registration to. Users will be sent an email to confirm their address before being allowed to interact with the application.
80 <br> Note that users will be able to change their email addresses after successful registration.</p> 88 <br> Note that users will be able to change their email addresses after successful registration.</p>
81 - <input type="text" id="setting-registration-restrict" name="setting-registration-restrict" placeholder="No restriction set" value="{{ Setting::get('registration-restrict', '') }}"> 89 + <input type="text" id="setting-registration-restrict" name="setting-registration-restrict" placeholder="No restriction set" value="{{ setting('registration-restrict', '') }}">
82 </div> 90 </div>
83 </div> 91 </div>
84 </div> 92 </div>
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
2 <div class="faded-small toolbar"> 2 <div class="faded-small toolbar">
3 <div class="container"> 3 <div class="container">
4 <div class="row"> 4 <div class="row">
5 - <div class="col-md-12 setting-nav"> 5 + <div class="col-md-12 setting-nav nav-tabs">
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="/settings/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 <a href="/settings/roles" @if($selected == 'roles') class="selected text-button" @endif><i class="zmdi zmdi-lock-open"></i>Roles</a> 8 <a href="/settings/roles" @if($selected == 'roles') class="selected text-button" @endif><i class="zmdi zmdi-lock-open"></i>Roles</a>
......
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
2 2
3 <div class="row"> 3 <div class="row">
4 4
5 - <div class="col-md-6"> 5 + <div class="col-md-9">
6 + <div class="row">
7 + <div class="col-md-5">
6 <h3>Role Details</h3> 8 <h3>Role Details</h3>
7 <div class="form-group"> 9 <div class="form-group">
8 <label for="name">Role Name</label> 10 <label for="name">Role Name</label>
...@@ -13,36 +15,18 @@ ...@@ -13,36 +15,18 @@
13 @include('form/text', ['name' => 'description']) 15 @include('form/text', ['name' => 'description'])
14 </div> 16 </div>
15 <h3>System Permissions</h3> 17 <h3>System Permissions</h3>
16 - <div class="row"> 18 + <label>@include('settings/roles/checkbox', ['permission' => 'users-manage']) Manage users</label>
17 - <div class="col-md-6"> 19 + <label>@include('settings/roles/checkbox', ['permission' => 'user-roles-manage']) Manage roles & role permissions</label>
18 - <label> @include('settings/roles/checkbox', ['permission' => 'users-manage']) Manage users</label>
19 - </div>
20 - <div class="col-md-6">
21 - <label>@include('settings/roles/checkbox', ['permission' => 'user-roles-manage']) Manage user roles</label>
22 - </div>
23 - </div>
24 - <hr class="even">
25 - <div class="row">
26 - <div class="col-md-6">
27 <label>@include('settings/roles/checkbox', ['permission' => 'restrictions-manage-all']) Manage all Book, Chapter & Page permissions</label> 20 <label>@include('settings/roles/checkbox', ['permission' => 'restrictions-manage-all']) Manage all Book, Chapter & Page permissions</label>
28 - </div>
29 - <div class="col-md-6">
30 <label>@include('settings/roles/checkbox', ['permission' => 'restrictions-manage-own']) Manage permissions on own Book, Chapter & Pages</label> 21 <label>@include('settings/roles/checkbox', ['permission' => 'restrictions-manage-own']) Manage permissions on own Book, Chapter & Pages</label>
31 - </div>
32 - </div>
33 - <hr class="even">
34 - <div class="form-group">
35 <label>@include('settings/roles/checkbox', ['permission' => 'settings-manage']) Manage app settings</label> 22 <label>@include('settings/roles/checkbox', ['permission' => 'settings-manage']) Manage app settings</label>
36 </div> 23 </div>
37 - <hr class="even">
38 -
39 - </div>
40 24
41 <div class="col-md-6"> 25 <div class="col-md-6">
42 26
43 <h3>Asset Permissions</h3> 27 <h3>Asset Permissions</h3>
44 <p> 28 <p>
45 - These permissions control default access to the assets within the system. <br> 29 + These permissions control default access to the assets within the system.
46 Permissions on Books, Chapters and Pages will override these permissions. 30 Permissions on Books, Chapters and Pages will override these permissions.
47 </p> 31 </p>
48 <table class="table"> 32 <table class="table">
...@@ -110,8 +94,38 @@ ...@@ -110,8 +94,38 @@
110 </tr> 94 </tr>
111 </table> 95 </table>
112 </div> 96 </div>
97 + </div>
98 + <a href="/settings/roles" class="button muted">Cancel</a>
99 + <button type="submit" class="button pos">Save Role</button>
100 + </div>
101 + <div class="col-md-3">
102 + <h3>Users in this role</h3>
113 103
114 -</div> 104 + @if(isset($role) && count($role->users) > 0)
105 + <table class="list-table">
106 + @foreach($role->users as $user)
107 + <tr>
108 + <td style="line-height: 0;"><img class="avatar small" src="{{$user->getAvatar(40)}}" alt="{{$user->name}}"></td>
109 + <td>
110 + @if(userCan('users-manage') || $currentUser->id == $user->id)
111 + <a href="/settings/users/{{$user->id}}">
112 + @endif
113 + {{ $user->name }}
114 + @if(userCan('users-manage') || $currentUser->id == $user->id)
115 + </a>
116 + @endif
117 + </td>
118 + </tr>
119 + @endforeach
120 + </table>
121 + @else
122 + <p class="text-muted">
123 + No users currently in this role.
124 + </p>
125 + @endif
126 +
127 + </div>
115 128
116 -<a href="/settings/roles" class="button muted">Cancel</a>
117 -<button type="submit" class="button pos">Save Role</button>
...\ No newline at end of file ...\ No newline at end of file
129 +
130 +
131 +</div>
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -43,7 +43,7 @@ class LdapTest extends \TestCase ...@@ -43,7 +43,7 @@ class LdapTest extends \TestCase
43 ->press('Sign In') 43 ->press('Sign In')
44 ->seePageIs('/') 44 ->seePageIs('/')
45 ->see($this->mockUser->name) 45 ->see($this->mockUser->name)
46 - ->seeInDatabase('users', ['email' => $this->mockUser->email, 'email_confirmed' => 1, 'external_auth_id' => $this->mockUser->name]); 46 + ->seeInDatabase('users', ['email' => $this->mockUser->email, 'email_confirmed' => false, 'external_auth_id' => $this->mockUser->name]);
47 } 47 }
48 48
49 public function test_login_works_when_no_uid_provided_by_ldap_server() 49 public function test_login_works_when_no_uid_provided_by_ldap_server()
...@@ -67,7 +67,7 @@ class LdapTest extends \TestCase ...@@ -67,7 +67,7 @@ class LdapTest extends \TestCase
67 ->press('Sign In') 67 ->press('Sign In')
68 ->seePageIs('/') 68 ->seePageIs('/')
69 ->see($this->mockUser->name) 69 ->see($this->mockUser->name)
70 - ->seeInDatabase('users', ['email' => $this->mockUser->email, 'email_confirmed' => 1, 'external_auth_id' => $ldapDn]); 70 + ->seeInDatabase('users', ['email' => $this->mockUser->email, 'email_confirmed' => false, 'external_auth_id' => $ldapDn]);
71 } 71 }
72 72
73 public function test_initial_incorrect_details() 73 public function test_initial_incorrect_details()
......
1 +<?php
2 +
3 +
4 +class MarkdownTest extends TestCase
5 +{
6 + protected $page;
7 +
8 + public function setUp()
9 + {
10 + parent::setUp();
11 + $this->page = \BookStack\Page::first();
12 + }
13 +
14 + protected function setMarkdownEditor()
15 + {
16 + $this->setSettings(['app-editor' => 'markdown']);
17 + }
18 +
19 + public function test_default_editor_is_wysiwyg()
20 + {
21 + $this->assertEquals(setting('app-editor'), 'wysiwyg');
22 + $this->asAdmin()->visit($this->page->getUrl() . '/edit')
23 + ->pageHasElement('#html-editor');
24 + }
25 +
26 + public function test_markdown_setting_shows_markdown_editor()
27 + {
28 + $this->setMarkdownEditor();
29 + $this->asAdmin()->visit($this->page->getUrl() . '/edit')
30 + ->pageNotHasElement('#html-editor')
31 + ->pageHasElement('#markdown-editor');
32 + }
33 +
34 + public function test_markdown_content_given_to_editor()
35 + {
36 + $this->setMarkdownEditor();
37 + $mdContent = '# hello. This is a test';
38 + $this->page->markdown = $mdContent;
39 + $this->page->save();
40 + $this->asAdmin()->visit($this->page->getUrl() . '/edit')
41 + ->seeInField('markdown', $mdContent);
42 + }
43 +
44 + public function test_html_content_given_to_editor_if_no_markdown()
45 + {
46 + $this->setMarkdownEditor();
47 + $this->asAdmin()->visit($this->page->getUrl() . '/edit')
48 + ->seeInField('markdown', $this->page->html);
49 + }
50 +
51 +}
...\ No newline at end of file ...\ No newline at end of file
...@@ -171,11 +171,27 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase ...@@ -171,11 +171,27 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase
171 return $this; 171 return $this;
172 } 172 }
173 173
174 - protected function actingAsUsers($usersArray, $callback) 174 + /**
175 + * Check if the page contains the given element.
176 + * @param string $selector
177 + * @return bool
178 + */
179 + protected function pageHasElement($selector)
175 { 180 {
176 - foreach ($usersArray as $user) { 181 + $elements = $this->crawler->filter($selector);
177 - $this->actingAs($user); 182 + $this->assertTrue(count($elements) > 0, "The page does not contain an element matching " . $selector);
178 - $callback($user); 183 + return $this;
179 } 184 }
185 +
186 + /**
187 + * Check if the page contains the given element.
188 + * @param string $selector
189 + * @return bool
190 + */
191 + protected function pageNotHasElement($selector)
192 + {
193 + $elements = $this->crawler->filter($selector);
194 + $this->assertFalse(count($elements) > 0, "The page contains " . count($elements) . " elements matching " . $selector);
195 + return $this;
180 } 196 }
181 } 197 }
......