Dan Brown

Merge branch 'master' into release ready for v0.14

Showing 199 changed files with 5035 additions and 2758 deletions
1 ### For Feature Requests 1 ### For Feature Requests
2 +
2 Desired Feature: 3 Desired Feature:
3 4
4 ### For Bug Reports 5 ### For Bug Reports
5 -PHP Version:
6 6
7 -MySQL Version: 7 +* BookStack Version:
8 +* PHP Version:
9 +* MySQL Version:
8 10
9 -Expected Behavior: 11 +##### Expected Behavior
10 12
11 -Actual Behavior: 13 +##### Actual Behavior
......
...@@ -13,3 +13,4 @@ _ide_helper.php ...@@ -13,3 +13,4 @@ _ide_helper.php
13 /storage/debugbar 13 /storage/debugbar
14 .phpstorm.meta.php 14 .phpstorm.meta.php
15 yarn.lock 15 yarn.lock
16 +/bin
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -17,9 +17,7 @@ addons: ...@@ -17,9 +17,7 @@ addons:
17 17
18 before_script: 18 before_script:
19 - mysql -u root -e 'create database `bookstack-test`;' 19 - mysql -u root -e 'create database `bookstack-test`;'
20 - - composer config -g github-oauth.github.com $GITHUB_ACCESS_TOKEN
21 - phpenv config-rm xdebug.ini 20 - phpenv config-rm xdebug.ini
22 - - composer self-update
23 - composer dump-autoload --no-interaction 21 - composer dump-autoload --no-interaction
24 - composer install --prefer-dist --no-interaction 22 - composer install --prefer-dist --no-interaction
25 - php artisan clear-compiled -n 23 - php artisan clear-compiled -n
......
...@@ -5,6 +5,8 @@ class Chapter extends Entity ...@@ -5,6 +5,8 @@ class Chapter extends Entity
5 { 5 {
6 protected $fillable = ['name', 'description', 'priority', 'book_id']; 6 protected $fillable = ['name', 'description', 'priority', 'book_id'];
7 7
8 + protected $with = ['book'];
9 +
8 /** 10 /**
9 * Get the book this chapter is within. 11 * Get the book this chapter is within.
10 * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 12 * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
...@@ -16,11 +18,12 @@ class Chapter extends Entity ...@@ -16,11 +18,12 @@ class Chapter extends Entity
16 18
17 /** 19 /**
18 * Get the pages that this chapter contains. 20 * Get the pages that this chapter contains.
21 + * @param string $dir
19 * @return mixed 22 * @return mixed
20 */ 23 */
21 - public function pages() 24 + public function pages($dir = 'ASC')
22 { 25 {
23 - return $this->hasMany(Page::class)->orderBy('priority', 'ASC'); 26 + return $this->hasMany(Page::class)->orderBy('priority', $dir);
24 } 27 }
25 28
26 /** 29 /**
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
4 class Entity extends Ownable 4 class Entity extends Ownable
5 { 5 {
6 6
7 + protected $fieldsToSearch = ['name', 'description'];
8 +
7 /** 9 /**
8 * Compares this entity to another given entity. 10 * Compares this entity to another given entity.
9 * Matches by comparing class and id. 11 * Matches by comparing class and id.
...@@ -157,7 +159,7 @@ class Entity extends Ownable ...@@ -157,7 +159,7 @@ class Entity extends Ownable
157 * @param string[] array $wheres 159 * @param string[] array $wheres
158 * @return mixed 160 * @return mixed
159 */ 161 */
160 - public function fullTextSearchQuery($fieldsToSearch, $terms, $wheres = []) 162 + public function fullTextSearchQuery($terms, $wheres = [])
161 { 163 {
162 $exactTerms = []; 164 $exactTerms = [];
163 $fuzzyTerms = []; 165 $fuzzyTerms = [];
...@@ -181,16 +183,16 @@ class Entity extends Ownable ...@@ -181,16 +183,16 @@ class Entity extends Ownable
181 // Perform fulltext search if relevant terms exist. 183 // Perform fulltext search if relevant terms exist.
182 if ($isFuzzy) { 184 if ($isFuzzy) {
183 $termString = implode(' ', $fuzzyTerms); 185 $termString = implode(' ', $fuzzyTerms);
184 - $fields = implode(',', $fieldsToSearch); 186 + $fields = implode(',', $this->fieldsToSearch);
185 $search = $search->selectRaw('*, MATCH(name) AGAINST(? IN BOOLEAN MODE) AS title_relevance', [$termString]); 187 $search = $search->selectRaw('*, MATCH(name) AGAINST(? IN BOOLEAN MODE) AS title_relevance', [$termString]);
186 $search = $search->whereRaw('MATCH(' . $fields . ') AGAINST(? IN BOOLEAN MODE)', [$termString]); 188 $search = $search->whereRaw('MATCH(' . $fields . ') AGAINST(? IN BOOLEAN MODE)', [$termString]);
187 } 189 }
188 190
189 // Ensure at least one exact term matches if in search 191 // Ensure at least one exact term matches if in search
190 if (count($exactTerms) > 0) { 192 if (count($exactTerms) > 0) {
191 - $search = $search->where(function ($query) use ($exactTerms, $fieldsToSearch) { 193 + $search = $search->where(function ($query) use ($exactTerms) {
192 foreach ($exactTerms as $exactTerm) { 194 foreach ($exactTerms as $exactTerm) {
193 - foreach ($fieldsToSearch as $field) { 195 + foreach ($this->fieldsToSearch as $field) {
194 $query->orWhere($field, 'like', $exactTerm); 196 $query->orWhere($field, 'like', $exactTerm);
195 } 197 }
196 } 198 }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
2 2
3 use BookStack\Exceptions\FileUploadException; 3 use BookStack\Exceptions\FileUploadException;
4 use BookStack\Attachment; 4 use BookStack\Attachment;
5 -use BookStack\Repos\PageRepo; 5 +use BookStack\Repos\EntityRepo;
6 use BookStack\Services\AttachmentService; 6 use BookStack\Services\AttachmentService;
7 use Illuminate\Http\Request; 7 use Illuminate\Http\Request;
8 8
...@@ -10,19 +10,19 @@ class AttachmentController extends Controller ...@@ -10,19 +10,19 @@ class AttachmentController extends Controller
10 { 10 {
11 protected $attachmentService; 11 protected $attachmentService;
12 protected $attachment; 12 protected $attachment;
13 - protected $pageRepo; 13 + protected $entityRepo;
14 14
15 /** 15 /**
16 * AttachmentController constructor. 16 * AttachmentController constructor.
17 * @param AttachmentService $attachmentService 17 * @param AttachmentService $attachmentService
18 * @param Attachment $attachment 18 * @param Attachment $attachment
19 - * @param PageRepo $pageRepo 19 + * @param EntityRepo $entityRepo
20 */ 20 */
21 - public function __construct(AttachmentService $attachmentService, Attachment $attachment, PageRepo $pageRepo) 21 + public function __construct(AttachmentService $attachmentService, Attachment $attachment, EntityRepo $entityRepo)
22 { 22 {
23 $this->attachmentService = $attachmentService; 23 $this->attachmentService = $attachmentService;
24 $this->attachment = $attachment; 24 $this->attachment = $attachment;
25 - $this->pageRepo = $pageRepo; 25 + $this->entityRepo = $entityRepo;
26 parent::__construct(); 26 parent::__construct();
27 } 27 }
28 28
...@@ -40,7 +40,7 @@ class AttachmentController extends Controller ...@@ -40,7 +40,7 @@ class AttachmentController extends Controller
40 ]); 40 ]);
41 41
42 $pageId = $request->get('uploaded_to'); 42 $pageId = $request->get('uploaded_to');
43 - $page = $this->pageRepo->getById($pageId, true); 43 + $page = $this->entityRepo->getById('page', $pageId, true);
44 44
45 $this->checkPermission('attachment-create-all'); 45 $this->checkPermission('attachment-create-all');
46 $this->checkOwnablePermission('page-update', $page); 46 $this->checkOwnablePermission('page-update', $page);
...@@ -70,14 +70,14 @@ class AttachmentController extends Controller ...@@ -70,14 +70,14 @@ class AttachmentController extends Controller
70 ]); 70 ]);
71 71
72 $pageId = $request->get('uploaded_to'); 72 $pageId = $request->get('uploaded_to');
73 - $page = $this->pageRepo->getById($pageId, true); 73 + $page = $this->entityRepo->getById('page', $pageId, true);
74 $attachment = $this->attachment->findOrFail($attachmentId); 74 $attachment = $this->attachment->findOrFail($attachmentId);
75 75
76 $this->checkOwnablePermission('page-update', $page); 76 $this->checkOwnablePermission('page-update', $page);
77 $this->checkOwnablePermission('attachment-create', $attachment); 77 $this->checkOwnablePermission('attachment-create', $attachment);
78 78
79 if (intval($pageId) !== intval($attachment->uploaded_to)) { 79 if (intval($pageId) !== intval($attachment->uploaded_to)) {
80 - return $this->jsonError('Page mismatch during attached file update'); 80 + return $this->jsonError(trans('errors.attachment_page_mismatch'));
81 } 81 }
82 82
83 $uploadedFile = $request->file('file'); 83 $uploadedFile = $request->file('file');
...@@ -106,18 +106,18 @@ class AttachmentController extends Controller ...@@ -106,18 +106,18 @@ class AttachmentController extends Controller
106 ]); 106 ]);
107 107
108 $pageId = $request->get('uploaded_to'); 108 $pageId = $request->get('uploaded_to');
109 - $page = $this->pageRepo->getById($pageId, true); 109 + $page = $this->entityRepo->getById('page', $pageId, true);
110 $attachment = $this->attachment->findOrFail($attachmentId); 110 $attachment = $this->attachment->findOrFail($attachmentId);
111 111
112 $this->checkOwnablePermission('page-update', $page); 112 $this->checkOwnablePermission('page-update', $page);
113 $this->checkOwnablePermission('attachment-create', $attachment); 113 $this->checkOwnablePermission('attachment-create', $attachment);
114 114
115 if (intval($pageId) !== intval($attachment->uploaded_to)) { 115 if (intval($pageId) !== intval($attachment->uploaded_to)) {
116 - return $this->jsonError('Page mismatch during attachment update'); 116 + return $this->jsonError(trans('errors.attachment_page_mismatch'));
117 } 117 }
118 118
119 $attachment = $this->attachmentService->updateFile($attachment, $request->all()); 119 $attachment = $this->attachmentService->updateFile($attachment, $request->all());
120 - return $attachment; 120 + return response()->json($attachment);
121 } 121 }
122 122
123 /** 123 /**
...@@ -134,7 +134,7 @@ class AttachmentController extends Controller ...@@ -134,7 +134,7 @@ class AttachmentController extends Controller
134 ]); 134 ]);
135 135
136 $pageId = $request->get('uploaded_to'); 136 $pageId = $request->get('uploaded_to');
137 - $page = $this->pageRepo->getById($pageId, true); 137 + $page = $this->entityRepo->getById('page', $pageId, true);
138 138
139 $this->checkPermission('attachment-create-all'); 139 $this->checkPermission('attachment-create-all');
140 $this->checkOwnablePermission('page-update', $page); 140 $this->checkOwnablePermission('page-update', $page);
...@@ -153,7 +153,7 @@ class AttachmentController extends Controller ...@@ -153,7 +153,7 @@ class AttachmentController extends Controller
153 */ 153 */
154 public function listForPage($pageId) 154 public function listForPage($pageId)
155 { 155 {
156 - $page = $this->pageRepo->getById($pageId, true); 156 + $page = $this->entityRepo->getById('page', $pageId, true);
157 $this->checkOwnablePermission('page-view', $page); 157 $this->checkOwnablePermission('page-view', $page);
158 return response()->json($page->attachments); 158 return response()->json($page->attachments);
159 } 159 }
...@@ -170,12 +170,12 @@ class AttachmentController extends Controller ...@@ -170,12 +170,12 @@ class AttachmentController extends Controller
170 'files' => 'required|array', 170 'files' => 'required|array',
171 'files.*.id' => 'required|integer', 171 'files.*.id' => 'required|integer',
172 ]); 172 ]);
173 - $page = $this->pageRepo->getById($pageId); 173 + $page = $this->entityRepo->getById('page', $pageId);
174 $this->checkOwnablePermission('page-update', $page); 174 $this->checkOwnablePermission('page-update', $page);
175 175
176 $attachments = $request->get('files'); 176 $attachments = $request->get('files');
177 $this->attachmentService->updateFileOrderWithinPage($attachments, $pageId); 177 $this->attachmentService->updateFileOrderWithinPage($attachments, $pageId);
178 - return response()->json(['message' => 'Attachment order updated']); 178 + return response()->json(['message' => trans('entities.attachments_order_updated')]);
179 } 179 }
180 180
181 /** 181 /**
...@@ -186,7 +186,7 @@ class AttachmentController extends Controller ...@@ -186,7 +186,7 @@ class AttachmentController extends Controller
186 public function get($attachmentId) 186 public function get($attachmentId)
187 { 187 {
188 $attachment = $this->attachment->findOrFail($attachmentId); 188 $attachment = $this->attachment->findOrFail($attachmentId);
189 - $page = $this->pageRepo->getById($attachment->uploaded_to); 189 + $page = $this->entityRepo->getById('page', $attachment->uploaded_to);
190 $this->checkOwnablePermission('page-view', $page); 190 $this->checkOwnablePermission('page-view', $page);
191 191
192 if ($attachment->external) { 192 if ($attachment->external) {
...@@ -210,6 +210,6 @@ class AttachmentController extends Controller ...@@ -210,6 +210,6 @@ class AttachmentController extends Controller
210 $attachment = $this->attachment->findOrFail($attachmentId); 210 $attachment = $this->attachment->findOrFail($attachmentId);
211 $this->checkOwnablePermission('attachment-delete', $attachment); 211 $this->checkOwnablePermission('attachment-delete', $attachment);
212 $this->attachmentService->deleteFile($attachment); 212 $this->attachmentService->deleteFile($attachment);
213 - return response()->json(['message' => 'Attachment deleted']); 213 + return response()->json(['message' => trans('entities.attachments_deleted')]);
214 } 214 }
215 } 215 }
......
...@@ -52,7 +52,7 @@ class ForgotPasswordController extends Controller ...@@ -52,7 +52,7 @@ class ForgotPasswordController extends Controller
52 ); 52 );
53 53
54 if ($response === Password::RESET_LINK_SENT) { 54 if ($response === Password::RESET_LINK_SENT) {
55 - $message = 'A password reset link has been sent to ' . $request->get('email') . '.'; 55 + $message = trans('auth.reset_password_sent_success', ['email' => $request->get('email')]);
56 session()->flash('success', $message); 56 session()->flash('success', $message);
57 return back()->with('status', trans($response)); 57 return back()->with('status', trans($response));
58 } 58 }
......
...@@ -87,7 +87,7 @@ class LoginController extends Controller ...@@ -87,7 +87,7 @@ class LoginController extends Controller
87 // Check for users with same email already 87 // Check for users with same email already
88 $alreadyUser = $user->newQuery()->where('email', '=', $user->email)->count() > 0; 88 $alreadyUser = $user->newQuery()->where('email', '=', $user->email)->count() > 0;
89 if ($alreadyUser) { 89 if ($alreadyUser) {
90 - throw new AuthException('A user with the email ' . $user->email . ' already exists but with different credentials.'); 90 + throw new AuthException(trans('errors.error_user_exists_different_creds', ['email' => $user->email]));
91 } 91 }
92 92
93 $user->save(); 93 $user->save();
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
3 namespace BookStack\Http\Controllers\Auth; 3 namespace BookStack\Http\Controllers\Auth;
4 4
5 use BookStack\Exceptions\ConfirmationEmailException; 5 use BookStack\Exceptions\ConfirmationEmailException;
6 +use BookStack\Exceptions\SocialSignInException;
6 use BookStack\Exceptions\UserRegistrationException; 7 use BookStack\Exceptions\UserRegistrationException;
7 use BookStack\Repos\UserRepo; 8 use BookStack\Repos\UserRepo;
8 use BookStack\Services\EmailConfirmationService; 9 use BookStack\Services\EmailConfirmationService;
...@@ -82,7 +83,7 @@ class RegisterController extends Controller ...@@ -82,7 +83,7 @@ class RegisterController extends Controller
82 protected function checkRegistrationAllowed() 83 protected function checkRegistrationAllowed()
83 { 84 {
84 if (!setting('registration-enabled')) { 85 if (!setting('registration-enabled')) {
85 - throw new UserRegistrationException('Registrations are currently disabled.', '/login'); 86 + throw new UserRegistrationException(trans('auth.registrations_disabled'), '/login');
86 } 87 }
87 } 88 }
88 89
...@@ -147,7 +148,7 @@ class RegisterController extends Controller ...@@ -147,7 +148,7 @@ class RegisterController extends Controller
147 $restrictedEmailDomains = explode(',', str_replace(' ', '', setting('registration-restrict'))); 148 $restrictedEmailDomains = explode(',', str_replace(' ', '', setting('registration-restrict')));
148 $userEmailDomain = $domain = substr(strrchr($userData['email'], "@"), 1); 149 $userEmailDomain = $domain = substr(strrchr($userData['email'], "@"), 1);
149 if (!in_array($userEmailDomain, $restrictedEmailDomains)) { 150 if (!in_array($userEmailDomain, $restrictedEmailDomains)) {
150 - throw new UserRegistrationException('That email domain does not have access to this application', '/register'); 151 + throw new UserRegistrationException(trans('auth.registration_email_domain_invalid'), '/register');
151 } 152 }
152 } 153 }
153 154
...@@ -169,7 +170,7 @@ class RegisterController extends Controller ...@@ -169,7 +170,7 @@ class RegisterController extends Controller
169 } 170 }
170 171
171 auth()->login($newUser); 172 auth()->login($newUser);
172 - session()->flash('success', 'Thanks for signing up! You are now registered and signed in.'); 173 + session()->flash('success', trans('auth.register_success'));
173 return redirect($this->redirectPath()); 174 return redirect($this->redirectPath());
174 } 175 }
175 176
...@@ -262,7 +263,7 @@ class RegisterController extends Controller ...@@ -262,7 +263,7 @@ class RegisterController extends Controller
262 return $this->socialRegisterCallback($socialDriver); 263 return $this->socialRegisterCallback($socialDriver);
263 } 264 }
264 } else { 265 } else {
265 - throw new SocialSignInException('No action defined', '/login'); 266 + throw new SocialSignInException(trans('errors.social_no_action_defined'), '/login');
266 } 267 }
267 return redirect()->back(); 268 return redirect()->back();
268 } 269 }
......
...@@ -41,7 +41,7 @@ class ResetPasswordController extends Controller ...@@ -41,7 +41,7 @@ class ResetPasswordController extends Controller
41 */ 41 */
42 protected function sendResetResponse($response) 42 protected function sendResetResponse($response)
43 { 43 {
44 - $message = 'Your password has been successfully reset.'; 44 + $message = trans('auth.reset_password_success');
45 session()->flash('success', $message); 45 session()->flash('success', $message);
46 return redirect($this->redirectPath()) 46 return redirect($this->redirectPath())
47 ->with('status', trans($response)); 47 ->with('status', trans($response));
......
1 <?php namespace BookStack\Http\Controllers; 1 <?php namespace BookStack\Http\Controllers;
2 2
3 use Activity; 3 use Activity;
4 +use BookStack\Repos\EntityRepo;
4 use BookStack\Repos\UserRepo; 5 use BookStack\Repos\UserRepo;
5 use Illuminate\Http\Request; 6 use Illuminate\Http\Request;
6 -use BookStack\Http\Requests; 7 +use Illuminate\Http\Response;
7 -use BookStack\Repos\BookRepo;
8 -use BookStack\Repos\ChapterRepo;
9 -use BookStack\Repos\PageRepo;
10 use Views; 8 use Views;
11 9
12 class BookController extends Controller 10 class BookController extends Controller
13 { 11 {
14 12
15 - protected $bookRepo; 13 + protected $entityRepo;
16 - protected $pageRepo;
17 - protected $chapterRepo;
18 protected $userRepo; 14 protected $userRepo;
19 15
20 /** 16 /**
21 * BookController constructor. 17 * BookController constructor.
22 - * @param BookRepo $bookRepo 18 + * @param EntityRepo $entityRepo
23 - * @param PageRepo $pageRepo
24 - * @param ChapterRepo $chapterRepo
25 * @param UserRepo $userRepo 19 * @param UserRepo $userRepo
26 */ 20 */
27 - public function __construct(BookRepo $bookRepo, PageRepo $pageRepo, ChapterRepo $chapterRepo, UserRepo $userRepo) 21 + public function __construct(EntityRepo $entityRepo, UserRepo $userRepo)
28 { 22 {
29 - $this->bookRepo = $bookRepo; 23 + $this->entityRepo = $entityRepo;
30 - $this->pageRepo = $pageRepo;
31 - $this->chapterRepo = $chapterRepo;
32 $this->userRepo = $userRepo; 24 $this->userRepo = $userRepo;
33 parent::__construct(); 25 parent::__construct();
34 } 26 }
...@@ -39,9 +31,9 @@ class BookController extends Controller ...@@ -39,9 +31,9 @@ class BookController extends Controller
39 */ 31 */
40 public function index() 32 public function index()
41 { 33 {
42 - $books = $this->bookRepo->getAllPaginated(10); 34 + $books = $this->entityRepo->getAllPaginated('book', 10);
43 - $recents = $this->signedIn ? $this->bookRepo->getRecentlyViewed(4, 0) : false; 35 + $recents = $this->signedIn ? $this->entityRepo->getRecentlyViewed('book', 4, 0) : false;
44 - $popular = $this->bookRepo->getPopular(4, 0); 36 + $popular = $this->entityRepo->getPopular('book', 4, 0);
45 $this->setPageTitle('Books'); 37 $this->setPageTitle('Books');
46 return view('books/index', ['books' => $books, 'recents' => $recents, 'popular' => $popular]); 38 return view('books/index', ['books' => $books, 'recents' => $recents, 'popular' => $popular]);
47 } 39 }
...@@ -53,7 +45,7 @@ class BookController extends Controller ...@@ -53,7 +45,7 @@ class BookController extends Controller
53 public function create() 45 public function create()
54 { 46 {
55 $this->checkPermission('book-create-all'); 47 $this->checkPermission('book-create-all');
56 - $this->setPageTitle('Create New Book'); 48 + $this->setPageTitle(trans('entities.books_create'));
57 return view('books/create'); 49 return view('books/create');
58 } 50 }
59 51
...@@ -70,7 +62,7 @@ class BookController extends Controller ...@@ -70,7 +62,7 @@ class BookController extends Controller
70 'name' => 'required|string|max:255', 62 'name' => 'required|string|max:255',
71 'description' => 'string|max:1000' 63 'description' => 'string|max:1000'
72 ]); 64 ]);
73 - $book = $this->bookRepo->createFromInput($request->all()); 65 + $book = $this->entityRepo->createFromInput('book', $request->all());
74 Activity::add($book, 'book_create', $book->id); 66 Activity::add($book, 'book_create', $book->id);
75 return redirect($book->getUrl()); 67 return redirect($book->getUrl());
76 } 68 }
...@@ -82,9 +74,9 @@ class BookController extends Controller ...@@ -82,9 +74,9 @@ class BookController extends Controller
82 */ 74 */
83 public function show($slug) 75 public function show($slug)
84 { 76 {
85 - $book = $this->bookRepo->getBySlug($slug); 77 + $book = $this->entityRepo->getBySlug('book', $slug);
86 $this->checkOwnablePermission('book-view', $book); 78 $this->checkOwnablePermission('book-view', $book);
87 - $bookChildren = $this->bookRepo->getChildren($book); 79 + $bookChildren = $this->entityRepo->getBookChildren($book);
88 Views::add($book); 80 Views::add($book);
89 $this->setPageTitle($book->getShortName()); 81 $this->setPageTitle($book->getShortName());
90 return view('books/show', ['book' => $book, 'current' => $book, 'bookChildren' => $bookChildren]); 82 return view('books/show', ['book' => $book, 'current' => $book, 'bookChildren' => $bookChildren]);
...@@ -97,9 +89,9 @@ class BookController extends Controller ...@@ -97,9 +89,9 @@ class BookController extends Controller
97 */ 89 */
98 public function edit($slug) 90 public function edit($slug)
99 { 91 {
100 - $book = $this->bookRepo->getBySlug($slug); 92 + $book = $this->entityRepo->getBySlug('book', $slug);
101 $this->checkOwnablePermission('book-update', $book); 93 $this->checkOwnablePermission('book-update', $book);
102 - $this->setPageTitle('Edit Book ' . $book->getShortName()); 94 + $this->setPageTitle(trans('entities.books_edit_named',['bookName'=>$book->getShortName()]));
103 return view('books/edit', ['book' => $book, 'current' => $book]); 95 return view('books/edit', ['book' => $book, 'current' => $book]);
104 } 96 }
105 97
...@@ -111,13 +103,13 @@ class BookController extends Controller ...@@ -111,13 +103,13 @@ class BookController extends Controller
111 */ 103 */
112 public function update(Request $request, $slug) 104 public function update(Request $request, $slug)
113 { 105 {
114 - $book = $this->bookRepo->getBySlug($slug); 106 + $book = $this->entityRepo->getBySlug('book', $slug);
115 $this->checkOwnablePermission('book-update', $book); 107 $this->checkOwnablePermission('book-update', $book);
116 $this->validate($request, [ 108 $this->validate($request, [
117 'name' => 'required|string|max:255', 109 'name' => 'required|string|max:255',
118 'description' => 'string|max:1000' 110 'description' => 'string|max:1000'
119 ]); 111 ]);
120 - $book = $this->bookRepo->updateFromInput($book, $request->all()); 112 + $book = $this->entityRepo->updateFromInput('book', $book, $request->all());
121 Activity::add($book, 'book_update', $book->id); 113 Activity::add($book, 'book_update', $book->id);
122 return redirect($book->getUrl()); 114 return redirect($book->getUrl());
123 } 115 }
...@@ -129,9 +121,9 @@ class BookController extends Controller ...@@ -129,9 +121,9 @@ class BookController extends Controller
129 */ 121 */
130 public function showDelete($bookSlug) 122 public function showDelete($bookSlug)
131 { 123 {
132 - $book = $this->bookRepo->getBySlug($bookSlug); 124 + $book = $this->entityRepo->getBySlug('book', $bookSlug);
133 $this->checkOwnablePermission('book-delete', $book); 125 $this->checkOwnablePermission('book-delete', $book);
134 - $this->setPageTitle('Delete Book ' . $book->getShortName()); 126 + $this->setPageTitle(trans('entities.books_delete_named', ['bookName'=>$book->getShortName()]));
135 return view('books/delete', ['book' => $book, 'current' => $book]); 127 return view('books/delete', ['book' => $book, 'current' => $book]);
136 } 128 }
137 129
...@@ -142,11 +134,11 @@ class BookController extends Controller ...@@ -142,11 +134,11 @@ class BookController extends Controller
142 */ 134 */
143 public function sort($bookSlug) 135 public function sort($bookSlug)
144 { 136 {
145 - $book = $this->bookRepo->getBySlug($bookSlug); 137 + $book = $this->entityRepo->getBySlug('book', $bookSlug);
146 $this->checkOwnablePermission('book-update', $book); 138 $this->checkOwnablePermission('book-update', $book);
147 - $bookChildren = $this->bookRepo->getChildren($book, true); 139 + $bookChildren = $this->entityRepo->getBookChildren($book, true);
148 - $books = $this->bookRepo->getAll(false); 140 + $books = $this->entityRepo->getAll('book', false);
149 - $this->setPageTitle('Sort Book ' . $book->getShortName()); 141 + $this->setPageTitle(trans('entities.books_sort_named', ['bookName'=>$book->getShortName()]));
150 return view('books/sort', ['book' => $book, 'current' => $book, 'books' => $books, 'bookChildren' => $bookChildren]); 142 return view('books/sort', ['book' => $book, 'current' => $book, 'books' => $books, 'bookChildren' => $bookChildren]);
151 } 143 }
152 144
...@@ -158,8 +150,8 @@ class BookController extends Controller ...@@ -158,8 +150,8 @@ class BookController extends Controller
158 */ 150 */
159 public function getSortItem($bookSlug) 151 public function getSortItem($bookSlug)
160 { 152 {
161 - $book = $this->bookRepo->getBySlug($bookSlug); 153 + $book = $this->entityRepo->getBySlug('book', $bookSlug);
162 - $bookChildren = $this->bookRepo->getChildren($book); 154 + $bookChildren = $this->entityRepo->getBookChildren($book);
163 return view('books/sort-box', ['book' => $book, 'bookChildren' => $bookChildren]); 155 return view('books/sort-box', ['book' => $book, 'bookChildren' => $bookChildren]);
164 } 156 }
165 157
...@@ -171,7 +163,7 @@ class BookController extends Controller ...@@ -171,7 +163,7 @@ class BookController extends Controller
171 */ 163 */
172 public function saveSort($bookSlug, Request $request) 164 public function saveSort($bookSlug, Request $request)
173 { 165 {
174 - $book = $this->bookRepo->getBySlug($bookSlug); 166 + $book = $this->entityRepo->getBySlug('book', $bookSlug);
175 $this->checkOwnablePermission('book-update', $book); 167 $this->checkOwnablePermission('book-update', $book);
176 168
177 // Return if no map sent 169 // Return if no map sent
...@@ -190,13 +182,13 @@ class BookController extends Controller ...@@ -190,13 +182,13 @@ class BookController extends Controller
190 $priority = $bookChild->sort; 182 $priority = $bookChild->sort;
191 $id = intval($bookChild->id); 183 $id = intval($bookChild->id);
192 $isPage = $bookChild->type == 'page'; 184 $isPage = $bookChild->type == 'page';
193 - $bookId = $this->bookRepo->exists($bookChild->book) ? intval($bookChild->book) : $defaultBookId; 185 + $bookId = $this->entityRepo->exists('book', $bookChild->book) ? intval($bookChild->book) : $defaultBookId;
194 $chapterId = ($isPage && $bookChild->parentChapter === false) ? 0 : intval($bookChild->parentChapter); 186 $chapterId = ($isPage && $bookChild->parentChapter === false) ? 0 : intval($bookChild->parentChapter);
195 - $model = $isPage ? $this->pageRepo->getById($id) : $this->chapterRepo->getById($id); 187 + $model = $this->entityRepo->getById($isPage?'page':'chapter', $id);
196 188
197 // Update models only if there's a change in parent chain or ordering. 189 // Update models only if there's a change in parent chain or ordering.
198 if ($model->priority !== $priority || $model->book_id !== $bookId || ($isPage && $model->chapter_id !== $chapterId)) { 190 if ($model->priority !== $priority || $model->book_id !== $bookId || ($isPage && $model->chapter_id !== $chapterId)) {
199 - $isPage ? $this->pageRepo->changeBook($bookId, $model) : $this->chapterRepo->changeBook($bookId, $model); 191 + $this->entityRepo->changeBook($isPage?'page':'chapter', $bookId, $model);
200 $model->priority = $priority; 192 $model->priority = $priority;
201 if ($isPage) $model->chapter_id = $chapterId; 193 if ($isPage) $model->chapter_id = $chapterId;
202 $model->save(); 194 $model->save();
...@@ -211,12 +203,12 @@ class BookController extends Controller ...@@ -211,12 +203,12 @@ class BookController extends Controller
211 203
212 // Add activity for books 204 // Add activity for books
213 foreach ($sortedBooks as $bookId) { 205 foreach ($sortedBooks as $bookId) {
214 - $updatedBook = $this->bookRepo->getById($bookId); 206 + $updatedBook = $this->entityRepo->getById('book', $bookId);
215 Activity::add($updatedBook, 'book_sort', $updatedBook->id); 207 Activity::add($updatedBook, 'book_sort', $updatedBook->id);
216 } 208 }
217 209
218 // Update permissions on changed models 210 // Update permissions on changed models
219 - $this->bookRepo->buildJointPermissions($updatedModels); 211 + $this->entityRepo->buildJointPermissions($updatedModels);
220 212
221 return redirect($book->getUrl()); 213 return redirect($book->getUrl());
222 } 214 }
...@@ -228,11 +220,10 @@ class BookController extends Controller ...@@ -228,11 +220,10 @@ class BookController extends Controller
228 */ 220 */
229 public function destroy($bookSlug) 221 public function destroy($bookSlug)
230 { 222 {
231 - $book = $this->bookRepo->getBySlug($bookSlug); 223 + $book = $this->entityRepo->getBySlug('book', $bookSlug);
232 $this->checkOwnablePermission('book-delete', $book); 224 $this->checkOwnablePermission('book-delete', $book);
233 Activity::addMessage('book_delete', 0, $book->name); 225 Activity::addMessage('book_delete', 0, $book->name);
234 - Activity::removeEntity($book); 226 + $this->entityRepo->destroyBook($book);
235 - $this->bookRepo->destroy($book);
236 return redirect('/books'); 227 return redirect('/books');
237 } 228 }
238 229
...@@ -243,7 +234,7 @@ class BookController extends Controller ...@@ -243,7 +234,7 @@ class BookController extends Controller
243 */ 234 */
244 public function showRestrict($bookSlug) 235 public function showRestrict($bookSlug)
245 { 236 {
246 - $book = $this->bookRepo->getBySlug($bookSlug); 237 + $book = $this->entityRepo->getBySlug('book', $bookSlug);
247 $this->checkOwnablePermission('restrictions-manage', $book); 238 $this->checkOwnablePermission('restrictions-manage', $book);
248 $roles = $this->userRepo->getRestrictableRoles(); 239 $roles = $this->userRepo->getRestrictableRoles();
249 return view('books/restrictions', [ 240 return view('books/restrictions', [
...@@ -261,10 +252,10 @@ class BookController extends Controller ...@@ -261,10 +252,10 @@ class BookController extends Controller
261 */ 252 */
262 public function restrict($bookSlug, Request $request) 253 public function restrict($bookSlug, Request $request)
263 { 254 {
264 - $book = $this->bookRepo->getBySlug($bookSlug); 255 + $book = $this->entityRepo->getBySlug('book', $bookSlug);
265 $this->checkOwnablePermission('restrictions-manage', $book); 256 $this->checkOwnablePermission('restrictions-manage', $book);
266 - $this->bookRepo->updateEntityPermissionsFromRequest($request, $book); 257 + $this->entityRepo->updateEntityPermissionsFromRequest($request, $book);
267 - session()->flash('success', 'Book Restrictions Updated'); 258 + session()->flash('success', trans('entities.books_permissions_updated'));
268 return redirect($book->getUrl()); 259 return redirect($book->getUrl());
269 } 260 }
270 } 261 }
......
1 <?php namespace BookStack\Http\Controllers; 1 <?php namespace BookStack\Http\Controllers;
2 2
3 use Activity; 3 use Activity;
4 +use BookStack\Repos\EntityRepo;
4 use BookStack\Repos\UserRepo; 5 use BookStack\Repos\UserRepo;
5 use Illuminate\Http\Request; 6 use Illuminate\Http\Request;
6 -use BookStack\Http\Requests; 7 +use Illuminate\Http\Response;
7 -use BookStack\Repos\BookRepo;
8 -use BookStack\Repos\ChapterRepo;
9 use Views; 8 use Views;
10 9
11 class ChapterController extends Controller 10 class ChapterController extends Controller
12 { 11 {
13 12
14 - protected $bookRepo;
15 - protected $chapterRepo;
16 protected $userRepo; 13 protected $userRepo;
14 + protected $entityRepo;
17 15
18 /** 16 /**
19 * ChapterController constructor. 17 * ChapterController constructor.
20 - * @param BookRepo $bookRepo 18 + * @param EntityRepo $entityRepo
21 - * @param ChapterRepo $chapterRepo
22 * @param UserRepo $userRepo 19 * @param UserRepo $userRepo
23 */ 20 */
24 - public function __construct(BookRepo $bookRepo, ChapterRepo $chapterRepo, UserRepo $userRepo) 21 + public function __construct(EntityRepo $entityRepo, UserRepo $userRepo)
25 { 22 {
26 - $this->bookRepo = $bookRepo; 23 + $this->entityRepo = $entityRepo;
27 - $this->chapterRepo = $chapterRepo;
28 $this->userRepo = $userRepo; 24 $this->userRepo = $userRepo;
29 parent::__construct(); 25 parent::__construct();
30 } 26 }
...@@ -36,9 +32,9 @@ class ChapterController extends Controller ...@@ -36,9 +32,9 @@ class ChapterController extends Controller
36 */ 32 */
37 public function create($bookSlug) 33 public function create($bookSlug)
38 { 34 {
39 - $book = $this->bookRepo->getBySlug($bookSlug); 35 + $book = $this->entityRepo->getBySlug('book', $bookSlug);
40 $this->checkOwnablePermission('chapter-create', $book); 36 $this->checkOwnablePermission('chapter-create', $book);
41 - $this->setPageTitle('Create New Chapter'); 37 + $this->setPageTitle(trans('entities.chapters_create'));
42 return view('chapters/create', ['book' => $book, 'current' => $book]); 38 return view('chapters/create', ['book' => $book, 'current' => $book]);
43 } 39 }
44 40
...@@ -54,12 +50,12 @@ class ChapterController extends Controller ...@@ -54,12 +50,12 @@ class ChapterController extends Controller
54 'name' => 'required|string|max:255' 50 'name' => 'required|string|max:255'
55 ]); 51 ]);
56 52
57 - $book = $this->bookRepo->getBySlug($bookSlug); 53 + $book = $this->entityRepo->getBySlug('book', $bookSlug);
58 $this->checkOwnablePermission('chapter-create', $book); 54 $this->checkOwnablePermission('chapter-create', $book);
59 55
60 $input = $request->all(); 56 $input = $request->all();
61 - $input['priority'] = $this->bookRepo->getNewPriority($book); 57 + $input['priority'] = $this->entityRepo->getNewBookPriority($book);
62 - $chapter = $this->chapterRepo->createFromInput($input, $book); 58 + $chapter = $this->entityRepo->createFromInput('chapter', $input, $book);
63 Activity::add($chapter, 'chapter_create', $book->id); 59 Activity::add($chapter, 'chapter_create', $book->id);
64 return redirect($chapter->getUrl()); 60 return redirect($chapter->getUrl());
65 } 61 }
...@@ -72,15 +68,14 @@ class ChapterController extends Controller ...@@ -72,15 +68,14 @@ class ChapterController extends Controller
72 */ 68 */
73 public function show($bookSlug, $chapterSlug) 69 public function show($bookSlug, $chapterSlug)
74 { 70 {
75 - $book = $this->bookRepo->getBySlug($bookSlug); 71 + $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
76 - $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
77 $this->checkOwnablePermission('chapter-view', $chapter); 72 $this->checkOwnablePermission('chapter-view', $chapter);
78 - $sidebarTree = $this->bookRepo->getChildren($book); 73 + $sidebarTree = $this->entityRepo->getBookChildren($chapter->book);
79 Views::add($chapter); 74 Views::add($chapter);
80 $this->setPageTitle($chapter->getShortName()); 75 $this->setPageTitle($chapter->getShortName());
81 - $pages = $this->chapterRepo->getChildren($chapter); 76 + $pages = $this->entityRepo->getChapterChildren($chapter);
82 return view('chapters/show', [ 77 return view('chapters/show', [
83 - 'book' => $book, 78 + 'book' => $chapter->book,
84 'chapter' => $chapter, 79 'chapter' => $chapter,
85 'current' => $chapter, 80 'current' => $chapter,
86 'sidebarTree' => $sidebarTree, 81 'sidebarTree' => $sidebarTree,
...@@ -96,11 +91,10 @@ class ChapterController extends Controller ...@@ -96,11 +91,10 @@ class ChapterController extends Controller
96 */ 91 */
97 public function edit($bookSlug, $chapterSlug) 92 public function edit($bookSlug, $chapterSlug)
98 { 93 {
99 - $book = $this->bookRepo->getBySlug($bookSlug); 94 + $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
100 - $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
101 $this->checkOwnablePermission('chapter-update', $chapter); 95 $this->checkOwnablePermission('chapter-update', $chapter);
102 - $this->setPageTitle('Edit Chapter' . $chapter->getShortName()); 96 + $this->setPageTitle(trans('entities.chapters_edit_named', ['chapterName' => $chapter->getShortName()]));
103 - return view('chapters/edit', ['book' => $book, 'chapter' => $chapter, 'current' => $chapter]); 97 + return view('chapters/edit', ['book' => $chapter->book, 'chapter' => $chapter, 'current' => $chapter]);
104 } 98 }
105 99
106 /** 100 /**
...@@ -112,16 +106,15 @@ class ChapterController extends Controller ...@@ -112,16 +106,15 @@ class ChapterController extends Controller
112 */ 106 */
113 public function update(Request $request, $bookSlug, $chapterSlug) 107 public function update(Request $request, $bookSlug, $chapterSlug)
114 { 108 {
115 - $book = $this->bookRepo->getBySlug($bookSlug); 109 + $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
116 - $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
117 $this->checkOwnablePermission('chapter-update', $chapter); 110 $this->checkOwnablePermission('chapter-update', $chapter);
118 if ($chapter->name !== $request->get('name')) { 111 if ($chapter->name !== $request->get('name')) {
119 - $chapter->slug = $this->chapterRepo->findSuitableSlug($request->get('name'), $book->id, $chapter->id); 112 + $chapter->slug = $this->entityRepo->findSuitableSlug('chapter', $request->get('name'), $chapter->id, $chapter->book->id);
120 } 113 }
121 $chapter->fill($request->all()); 114 $chapter->fill($request->all());
122 $chapter->updated_by = user()->id; 115 $chapter->updated_by = user()->id;
123 $chapter->save(); 116 $chapter->save();
124 - Activity::add($chapter, 'chapter_update', $book->id); 117 + Activity::add($chapter, 'chapter_update', $chapter->book->id);
125 return redirect($chapter->getUrl()); 118 return redirect($chapter->getUrl());
126 } 119 }
127 120
...@@ -133,11 +126,10 @@ class ChapterController extends Controller ...@@ -133,11 +126,10 @@ class ChapterController extends Controller
133 */ 126 */
134 public function showDelete($bookSlug, $chapterSlug) 127 public function showDelete($bookSlug, $chapterSlug)
135 { 128 {
136 - $book = $this->bookRepo->getBySlug($bookSlug); 129 + $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
137 - $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
138 $this->checkOwnablePermission('chapter-delete', $chapter); 130 $this->checkOwnablePermission('chapter-delete', $chapter);
139 - $this->setPageTitle('Delete Chapter' . $chapter->getShortName()); 131 + $this->setPageTitle(trans('entities.chapters_delete_named', ['chapterName' => $chapter->getShortName()]));
140 - return view('chapters/delete', ['book' => $book, 'chapter' => $chapter, 'current' => $chapter]); 132 + return view('chapters/delete', ['book' => $chapter->book, 'chapter' => $chapter, 'current' => $chapter]);
141 } 133 }
142 134
143 /** 135 /**
...@@ -148,11 +140,11 @@ class ChapterController extends Controller ...@@ -148,11 +140,11 @@ class ChapterController extends Controller
148 */ 140 */
149 public function destroy($bookSlug, $chapterSlug) 141 public function destroy($bookSlug, $chapterSlug)
150 { 142 {
151 - $book = $this->bookRepo->getBySlug($bookSlug); 143 + $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
152 - $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id); 144 + $book = $chapter->book;
153 $this->checkOwnablePermission('chapter-delete', $chapter); 145 $this->checkOwnablePermission('chapter-delete', $chapter);
154 Activity::addMessage('chapter_delete', $book->id, $chapter->name); 146 Activity::addMessage('chapter_delete', $book->id, $chapter->name);
155 - $this->chapterRepo->destroy($chapter); 147 + $this->entityRepo->destroyChapter($chapter);
156 return redirect($book->getUrl()); 148 return redirect($book->getUrl());
157 } 149 }
158 150
...@@ -164,12 +156,12 @@ class ChapterController extends Controller ...@@ -164,12 +156,12 @@ class ChapterController extends Controller
164 * @throws \BookStack\Exceptions\NotFoundException 156 * @throws \BookStack\Exceptions\NotFoundException
165 */ 157 */
166 public function showMove($bookSlug, $chapterSlug) { 158 public function showMove($bookSlug, $chapterSlug) {
167 - $book = $this->bookRepo->getBySlug($bookSlug); 159 + $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
168 - $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id); 160 + $this->setPageTitle(trans('entities.chapters_move_named', ['chapterName' => $chapter->getShortName()]));
169 $this->checkOwnablePermission('chapter-update', $chapter); 161 $this->checkOwnablePermission('chapter-update', $chapter);
170 return view('chapters/move', [ 162 return view('chapters/move', [
171 'chapter' => $chapter, 163 'chapter' => $chapter,
172 - 'book' => $book 164 + 'book' => $chapter->book
173 ]); 165 ]);
174 } 166 }
175 167
...@@ -182,8 +174,7 @@ class ChapterController extends Controller ...@@ -182,8 +174,7 @@ class ChapterController extends Controller
182 * @throws \BookStack\Exceptions\NotFoundException 174 * @throws \BookStack\Exceptions\NotFoundException
183 */ 175 */
184 public function move($bookSlug, $chapterSlug, Request $request) { 176 public function move($bookSlug, $chapterSlug, Request $request) {
185 - $book = $this->bookRepo->getBySlug($bookSlug); 177 + $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
186 - $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
187 $this->checkOwnablePermission('chapter-update', $chapter); 178 $this->checkOwnablePermission('chapter-update', $chapter);
188 179
189 $entitySelection = $request->get('entity_selection', null); 180 $entitySelection = $request->get('entity_selection', null);
...@@ -198,17 +189,17 @@ class ChapterController extends Controller ...@@ -198,17 +189,17 @@ class ChapterController extends Controller
198 $parent = false; 189 $parent = false;
199 190
200 if ($entityType == 'book') { 191 if ($entityType == 'book') {
201 - $parent = $this->bookRepo->getById($entityId); 192 + $parent = $this->entityRepo->getById('book', $entityId);
202 } 193 }
203 194
204 if ($parent === false || $parent === null) { 195 if ($parent === false || $parent === null) {
205 - session()->flash('The selected Book was not found'); 196 + session()->flash('error', trans('errors.selected_book_not_found'));
206 return redirect()->back(); 197 return redirect()->back();
207 } 198 }
208 199
209 - $this->chapterRepo->changeBook($parent->id, $chapter, true); 200 + $this->entityRepo->changeBook('chapter', $parent->id, $chapter, true);
210 Activity::add($chapter, 'chapter_move', $chapter->book->id); 201 Activity::add($chapter, 'chapter_move', $chapter->book->id);
211 - session()->flash('success', sprintf('Chapter moved to "%s"', $parent->name)); 202 + session()->flash('success', trans('entities.chapter_move_success', ['bookName' => $parent->name]));
212 203
213 return redirect($chapter->getUrl()); 204 return redirect($chapter->getUrl());
214 } 205 }
...@@ -221,8 +212,7 @@ class ChapterController extends Controller ...@@ -221,8 +212,7 @@ class ChapterController extends Controller
221 */ 212 */
222 public function showRestrict($bookSlug, $chapterSlug) 213 public function showRestrict($bookSlug, $chapterSlug)
223 { 214 {
224 - $book = $this->bookRepo->getBySlug($bookSlug); 215 + $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
225 - $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
226 $this->checkOwnablePermission('restrictions-manage', $chapter); 216 $this->checkOwnablePermission('restrictions-manage', $chapter);
227 $roles = $this->userRepo->getRestrictableRoles(); 217 $roles = $this->userRepo->getRestrictableRoles();
228 return view('chapters/restrictions', [ 218 return view('chapters/restrictions', [
...@@ -240,11 +230,10 @@ class ChapterController extends Controller ...@@ -240,11 +230,10 @@ class ChapterController extends Controller
240 */ 230 */
241 public function restrict($bookSlug, $chapterSlug, Request $request) 231 public function restrict($bookSlug, $chapterSlug, Request $request)
242 { 232 {
243 - $book = $this->bookRepo->getBySlug($bookSlug); 233 + $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
244 - $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
245 $this->checkOwnablePermission('restrictions-manage', $chapter); 234 $this->checkOwnablePermission('restrictions-manage', $chapter);
246 - $this->chapterRepo->updateEntityPermissionsFromRequest($request, $chapter); 235 + $this->entityRepo->updateEntityPermissionsFromRequest($request, $chapter);
247 - session()->flash('success', 'Chapter Restrictions Updated'); 236 + session()->flash('success', trans('entities.chapters_permissions_success'));
248 return redirect($chapter->getUrl()); 237 return redirect($chapter->getUrl());
249 } 238 }
250 } 239 }
......
...@@ -5,6 +5,7 @@ namespace BookStack\Http\Controllers; ...@@ -5,6 +5,7 @@ namespace BookStack\Http\Controllers;
5 use Activity; 5 use Activity;
6 use BookStack\Repos\EntityRepo; 6 use BookStack\Repos\EntityRepo;
7 use BookStack\Http\Requests; 7 use BookStack\Http\Requests;
8 +use Illuminate\Http\Response;
8 use Views; 9 use Views;
9 10
10 class HomeController extends Controller 11 class HomeController extends Controller
...@@ -31,9 +32,9 @@ class HomeController extends Controller ...@@ -31,9 +32,9 @@ class HomeController extends Controller
31 $activity = Activity::latest(10); 32 $activity = Activity::latest(10);
32 $draftPages = $this->signedIn ? $this->entityRepo->getUserDraftPages(6) : []; 33 $draftPages = $this->signedIn ? $this->entityRepo->getUserDraftPages(6) : [];
33 $recentFactor = count($draftPages) > 0 ? 0.5 : 1; 34 $recentFactor = count($draftPages) > 0 ? 0.5 : 1;
34 - $recents = $this->signedIn ? Views::getUserRecentlyViewed(12*$recentFactor, 0) : $this->entityRepo->getRecentlyCreatedBooks(10*$recentFactor); 35 + $recents = $this->signedIn ? Views::getUserRecentlyViewed(12*$recentFactor, 0) : $this->entityRepo->getRecentlyCreated('book', 10*$recentFactor);
35 - $recentlyCreatedPages = $this->entityRepo->getRecentlyCreatedPages(5); 36 + $recentlyCreatedPages = $this->entityRepo->getRecentlyCreated('page', 5);
36 - $recentlyUpdatedPages = $this->entityRepo->getRecentlyUpdatedPages(5); 37 + $recentlyUpdatedPages = $this->entityRepo->getRecentlyUpdated('page', 5);
37 return view('home', [ 38 return view('home', [
38 'activity' => $activity, 39 'activity' => $activity,
39 'recents' => $recents, 40 'recents' => $recents,
...@@ -43,4 +44,39 @@ class HomeController extends Controller ...@@ -43,4 +44,39 @@ class HomeController extends Controller
43 ]); 44 ]);
44 } 45 }
45 46
47 + /**
48 + * Get a js representation of the current translations
49 + * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
50 + */
51 + public function getTranslations() {
52 + $locale = trans()->getLocale();
53 + $cacheKey = 'GLOBAL_TRANSLATIONS_' . $locale;
54 + if (cache()->has($cacheKey) && config('app.env') !== 'development') {
55 + $resp = cache($cacheKey);
56 + } else {
57 + $translations = [
58 + // Get only translations which might be used in JS
59 + 'common' => trans('common'),
60 + 'components' => trans('components'),
61 + 'entities' => trans('entities'),
62 + 'errors' => trans('errors')
63 + ];
64 + if ($locale !== 'en') {
65 + $enTrans = [
66 + 'common' => trans('common', [], null, 'en'),
67 + 'components' => trans('components', [], null, 'en'),
68 + 'entities' => trans('entities', [], null, 'en'),
69 + 'errors' => trans('errors', [], null, 'en')
70 + ];
71 + $translations = array_replace_recursive($enTrans, $translations);
72 + }
73 + $resp = 'window.translations = ' . json_encode($translations);
74 + cache()->put($cacheKey, $resp, 120);
75 + }
76 +
77 + return response($resp, 200, [
78 + 'Content-Type' => 'application/javascript'
79 + ]);
80 + }
81 +
46 } 82 }
......
1 <?php namespace BookStack\Http\Controllers; 1 <?php namespace BookStack\Http\Controllers;
2 2
3 use BookStack\Exceptions\ImageUploadException; 3 use BookStack\Exceptions\ImageUploadException;
4 +use BookStack\Repos\EntityRepo;
4 use BookStack\Repos\ImageRepo; 5 use BookStack\Repos\ImageRepo;
5 use Illuminate\Filesystem\Filesystem as File; 6 use Illuminate\Filesystem\Filesystem as File;
6 use Illuminate\Http\Request; 7 use Illuminate\Http\Request;
...@@ -73,6 +74,7 @@ class ImageController extends Controller ...@@ -73,6 +74,7 @@ class ImageController extends Controller
73 * @param $filter 74 * @param $filter
74 * @param int $page 75 * @param int $page
75 * @param Request $request 76 * @param Request $request
77 + * @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\Response
76 */ 78 */
77 public function getGalleryFiltered($filter, $page = 0, Request $request) 79 public function getGalleryFiltered($filter, $page = 0, Request $request)
78 { 80 {
...@@ -149,12 +151,12 @@ class ImageController extends Controller ...@@ -149,12 +151,12 @@ class ImageController extends Controller
149 151
150 /** 152 /**
151 * Deletes an image and all thumbnail/image files 153 * Deletes an image and all thumbnail/image files
152 - * @param PageRepo $pageRepo 154 + * @param EntityRepo $entityRepo
153 * @param Request $request 155 * @param Request $request
154 * @param int $id 156 * @param int $id
155 * @return \Illuminate\Http\JsonResponse 157 * @return \Illuminate\Http\JsonResponse
156 */ 158 */
157 - public function destroy(PageRepo $pageRepo, Request $request, $id) 159 + public function destroy(EntityRepo $entityRepo, Request $request, $id)
158 { 160 {
159 $image = $this->imageRepo->getById($id); 161 $image = $this->imageRepo->getById($id);
160 $this->checkOwnablePermission('image-delete', $image); 162 $this->checkOwnablePermission('image-delete', $image);
...@@ -162,14 +164,14 @@ class ImageController extends Controller ...@@ -162,14 +164,14 @@ class ImageController extends Controller
162 // Check if this image is used on any pages 164 // Check if this image is used on any pages
163 $isForced = ($request->has('force') && ($request->get('force') === 'true') || $request->get('force') === true); 165 $isForced = ($request->has('force') && ($request->get('force') === 'true') || $request->get('force') === true);
164 if (!$isForced) { 166 if (!$isForced) {
165 - $pageSearch = $pageRepo->searchForImage($image->url); 167 + $pageSearch = $entityRepo->searchForImage($image->url);
166 if ($pageSearch !== false) { 168 if ($pageSearch !== false) {
167 return response()->json($pageSearch, 400); 169 return response()->json($pageSearch, 400);
168 } 170 }
169 } 171 }
170 172
171 $this->imageRepo->destroyImage($image); 173 $this->imageRepo->destroyImage($image);
172 - return response()->json('Image Deleted'); 174 + return response()->json(trans('components.images_deleted'));
173 } 175 }
174 176
175 177
......
...@@ -2,40 +2,31 @@ ...@@ -2,40 +2,31 @@
2 2
3 use Activity; 3 use Activity;
4 use BookStack\Exceptions\NotFoundException; 4 use BookStack\Exceptions\NotFoundException;
5 +use BookStack\Repos\EntityRepo;
5 use BookStack\Repos\UserRepo; 6 use BookStack\Repos\UserRepo;
6 use BookStack\Services\ExportService; 7 use BookStack\Services\ExportService;
7 use Carbon\Carbon; 8 use Carbon\Carbon;
8 use Illuminate\Http\Request; 9 use Illuminate\Http\Request;
9 -use BookStack\Http\Requests; 10 +use Illuminate\Http\Response;
10 -use BookStack\Repos\BookRepo;
11 -use BookStack\Repos\ChapterRepo;
12 -use BookStack\Repos\PageRepo;
13 -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
14 use Views; 11 use Views;
15 use GatherContent\Htmldiff\Htmldiff; 12 use GatherContent\Htmldiff\Htmldiff;
16 13
17 class PageController extends Controller 14 class PageController extends Controller
18 { 15 {
19 16
20 - protected $pageRepo; 17 + protected $entityRepo;
21 - protected $bookRepo;
22 - protected $chapterRepo;
23 protected $exportService; 18 protected $exportService;
24 protected $userRepo; 19 protected $userRepo;
25 20
26 /** 21 /**
27 * PageController constructor. 22 * PageController constructor.
28 - * @param PageRepo $pageRepo 23 + * @param EntityRepo $entityRepo
29 - * @param BookRepo $bookRepo
30 - * @param ChapterRepo $chapterRepo
31 * @param ExportService $exportService 24 * @param ExportService $exportService
32 * @param UserRepo $userRepo 25 * @param UserRepo $userRepo
33 */ 26 */
34 - public function __construct(PageRepo $pageRepo, BookRepo $bookRepo, ChapterRepo $chapterRepo, ExportService $exportService, UserRepo $userRepo) 27 + public function __construct(EntityRepo $entityRepo, ExportService $exportService, UserRepo $userRepo)
35 { 28 {
36 - $this->pageRepo = $pageRepo; 29 + $this->entityRepo = $entityRepo;
37 - $this->bookRepo = $bookRepo;
38 - $this->chapterRepo = $chapterRepo;
39 $this->exportService = $exportService; 30 $this->exportService = $exportService;
40 $this->userRepo = $userRepo; 31 $this->userRepo = $userRepo;
41 parent::__construct(); 32 parent::__construct();
...@@ -50,19 +41,19 @@ class PageController extends Controller ...@@ -50,19 +41,19 @@ class PageController extends Controller
50 */ 41 */
51 public function create($bookSlug, $chapterSlug = null) 42 public function create($bookSlug, $chapterSlug = null)
52 { 43 {
53 - $book = $this->bookRepo->getBySlug($bookSlug); 44 + $book = $this->entityRepo->getBySlug('book', $bookSlug);
54 - $chapter = $chapterSlug ? $this->chapterRepo->getBySlug($chapterSlug, $book->id) : null; 45 + $chapter = $chapterSlug ? $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug) : null;
55 $parent = $chapter ? $chapter : $book; 46 $parent = $chapter ? $chapter : $book;
56 $this->checkOwnablePermission('page-create', $parent); 47 $this->checkOwnablePermission('page-create', $parent);
57 48
58 // Redirect to draft edit screen if signed in 49 // Redirect to draft edit screen if signed in
59 if ($this->signedIn) { 50 if ($this->signedIn) {
60 - $draft = $this->pageRepo->getDraftPage($book, $chapter); 51 + $draft = $this->entityRepo->getDraftPage($book, $chapter);
61 return redirect($draft->getUrl()); 52 return redirect($draft->getUrl());
62 } 53 }
63 54
64 // Otherwise show edit view 55 // Otherwise show edit view
65 - $this->setPageTitle('Create New Page'); 56 + $this->setPageTitle(trans('entities.pages_new'));
66 return view('pages/guest-create', ['parent' => $parent]); 57 return view('pages/guest-create', ['parent' => $parent]);
67 } 58 }
68 59
...@@ -80,13 +71,13 @@ class PageController extends Controller ...@@ -80,13 +71,13 @@ class PageController extends Controller
80 'name' => 'required|string|max:255' 71 'name' => 'required|string|max:255'
81 ]); 72 ]);
82 73
83 - $book = $this->bookRepo->getBySlug($bookSlug); 74 + $book = $this->entityRepo->getBySlug('book', $bookSlug);
84 - $chapter = $chapterSlug ? $this->chapterRepo->getBySlug($chapterSlug, $book->id) : null; 75 + $chapter = $chapterSlug ? $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug) : null;
85 $parent = $chapter ? $chapter : $book; 76 $parent = $chapter ? $chapter : $book;
86 $this->checkOwnablePermission('page-create', $parent); 77 $this->checkOwnablePermission('page-create', $parent);
87 78
88 - $page = $this->pageRepo->getDraftPage($book, $chapter); 79 + $page = $this->entityRepo->getDraftPage($book, $chapter);
89 - $this->pageRepo->publishDraft($page, [ 80 + $this->entityRepo->publishPageDraft($page, [
90 'name' => $request->get('name'), 81 'name' => $request->get('name'),
91 'html' => '' 82 'html' => ''
92 ]); 83 ]);
...@@ -101,15 +92,14 @@ class PageController extends Controller ...@@ -101,15 +92,14 @@ class PageController extends Controller
101 */ 92 */
102 public function editDraft($bookSlug, $pageId) 93 public function editDraft($bookSlug, $pageId)
103 { 94 {
104 - $book = $this->bookRepo->getBySlug($bookSlug); 95 + $draft = $this->entityRepo->getById('page', $pageId, true);
105 - $draft = $this->pageRepo->getById($pageId, true); 96 + $this->checkOwnablePermission('page-create', $draft->book);
106 - $this->checkOwnablePermission('page-create', $book); 97 + $this->setPageTitle(trans('entities.pages_edit_draft'));
107 - $this->setPageTitle('Edit Page Draft');
108 98
109 $draftsEnabled = $this->signedIn; 99 $draftsEnabled = $this->signedIn;
110 return view('pages/edit', [ 100 return view('pages/edit', [
111 'page' => $draft, 101 'page' => $draft,
112 - 'book' => $book, 102 + 'book' => $draft->book,
113 'isDraft' => true, 103 'isDraft' => true,
114 'draftsEnabled' => $draftsEnabled 104 'draftsEnabled' => $draftsEnabled
115 ]); 105 ]);
...@@ -119,6 +109,7 @@ class PageController extends Controller ...@@ -119,6 +109,7 @@ class PageController extends Controller
119 * Store a new page by changing a draft into a page. 109 * Store a new page by changing a draft into a page.
120 * @param Request $request 110 * @param Request $request
121 * @param string $bookSlug 111 * @param string $bookSlug
112 + * @param int $pageId
122 * @return Response 113 * @return Response
123 */ 114 */
124 public function store(Request $request, $bookSlug, $pageId) 115 public function store(Request $request, $bookSlug, $pageId)
...@@ -128,21 +119,21 @@ class PageController extends Controller ...@@ -128,21 +119,21 @@ class PageController extends Controller
128 ]); 119 ]);
129 120
130 $input = $request->all(); 121 $input = $request->all();
131 - $book = $this->bookRepo->getBySlug($bookSlug); 122 + $book = $this->entityRepo->getBySlug('book', $bookSlug);
132 123
133 - $draftPage = $this->pageRepo->getById($pageId, true); 124 + $draftPage = $this->entityRepo->getById('page', $pageId, true);
134 125
135 $chapterId = intval($draftPage->chapter_id); 126 $chapterId = intval($draftPage->chapter_id);
136 - $parent = $chapterId !== 0 ? $this->chapterRepo->getById($chapterId) : $book; 127 + $parent = $chapterId !== 0 ? $this->entityRepo->getById('chapter', $chapterId) : $book;
137 $this->checkOwnablePermission('page-create', $parent); 128 $this->checkOwnablePermission('page-create', $parent);
138 129
139 if ($parent->isA('chapter')) { 130 if ($parent->isA('chapter')) {
140 - $input['priority'] = $this->chapterRepo->getNewPriority($parent); 131 + $input['priority'] = $this->entityRepo->getNewChapterPriority($parent);
141 } else { 132 } else {
142 - $input['priority'] = $this->bookRepo->getNewPriority($parent); 133 + $input['priority'] = $this->entityRepo->getNewBookPriority($parent);
143 } 134 }
144 135
145 - $page = $this->pageRepo->publishDraft($draftPage, $input); 136 + $page = $this->entityRepo->publishPageDraft($draftPage, $input);
146 137
147 Activity::add($page, 'page_create', $book->id); 138 Activity::add($page, 'page_create', $book->id);
148 return redirect($page->getUrl()); 139 return redirect($page->getUrl());
...@@ -150,33 +141,33 @@ class PageController extends Controller ...@@ -150,33 +141,33 @@ class PageController extends Controller
150 141
151 /** 142 /**
152 * Display the specified page. 143 * Display the specified page.
153 - * If the page is not found via the slug the 144 + * If the page is not found via the slug the revisions are searched for a match.
154 - * revisions are searched for a match.
155 * @param string $bookSlug 145 * @param string $bookSlug
156 * @param string $pageSlug 146 * @param string $pageSlug
157 * @return Response 147 * @return Response
158 */ 148 */
159 public function show($bookSlug, $pageSlug) 149 public function show($bookSlug, $pageSlug)
160 { 150 {
161 - $book = $this->bookRepo->getBySlug($bookSlug);
162 -
163 try { 151 try {
164 - $page = $this->pageRepo->getBySlug($pageSlug, $book->id); 152 + $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
165 } catch (NotFoundException $e) { 153 } catch (NotFoundException $e) {
166 - $page = $this->pageRepo->findPageUsingOldSlug($pageSlug, $bookSlug); 154 + $page = $this->entityRepo->getPageByOldSlug($pageSlug, $bookSlug);
167 if ($page === null) abort(404); 155 if ($page === null) abort(404);
168 return redirect($page->getUrl()); 156 return redirect($page->getUrl());
169 } 157 }
170 158
171 $this->checkOwnablePermission('page-view', $page); 159 $this->checkOwnablePermission('page-view', $page);
172 160
173 - $sidebarTree = $this->bookRepo->getChildren($book); 161 + $pageContent = $this->entityRepo->renderPage($page);
174 - $pageNav = $this->pageRepo->getPageNav($page); 162 + $sidebarTree = $this->entityRepo->getBookChildren($page->book);
163 + $pageNav = $this->entityRepo->getPageNav($pageContent);
175 164
176 Views::add($page); 165 Views::add($page);
177 $this->setPageTitle($page->getShortName()); 166 $this->setPageTitle($page->getShortName());
178 - return view('pages/show', ['page' => $page, 'book' => $book, 167 + return view('pages/show', [
179 - 'current' => $page, 'sidebarTree' => $sidebarTree, 'pageNav' => $pageNav]); 168 + 'page' => $page,'book' => $page->book,
169 + 'current' => $page, 'sidebarTree' => $sidebarTree,
170 + 'pageNav' => $pageNav, 'pageContent' => $pageContent]);
180 } 171 }
181 172
182 /** 173 /**
...@@ -186,7 +177,7 @@ class PageController extends Controller ...@@ -186,7 +177,7 @@ class PageController extends Controller
186 */ 177 */
187 public function getPageAjax($pageId) 178 public function getPageAjax($pageId)
188 { 179 {
189 - $page = $this->pageRepo->getById($pageId); 180 + $page = $this->entityRepo->getById('page', $pageId);
190 return response()->json($page); 181 return response()->json($page);
191 } 182 }
192 183
...@@ -198,26 +189,25 @@ class PageController extends Controller ...@@ -198,26 +189,25 @@ class PageController extends Controller
198 */ 189 */
199 public function edit($bookSlug, $pageSlug) 190 public function edit($bookSlug, $pageSlug)
200 { 191 {
201 - $book = $this->bookRepo->getBySlug($bookSlug); 192 + $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
202 - $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
203 $this->checkOwnablePermission('page-update', $page); 193 $this->checkOwnablePermission('page-update', $page);
204 - $this->setPageTitle('Editing Page ' . $page->getShortName()); 194 + $this->setPageTitle(trans('entities.pages_editing_named', ['pageName'=>$page->getShortName()]));
205 $page->isDraft = false; 195 $page->isDraft = false;
206 196
207 // Check for active editing 197 // Check for active editing
208 $warnings = []; 198 $warnings = [];
209 - if ($this->pageRepo->isPageEditingActive($page, 60)) { 199 + if ($this->entityRepo->isPageEditingActive($page, 60)) {
210 - $warnings[] = $this->pageRepo->getPageEditingActiveMessage($page, 60); 200 + $warnings[] = $this->entityRepo->getPageEditingActiveMessage($page, 60);
211 } 201 }
212 202
213 // Check for a current draft version for this user 203 // Check for a current draft version for this user
214 - if ($this->pageRepo->hasUserGotPageDraft($page, $this->currentUser->id)) { 204 + if ($this->entityRepo->hasUserGotPageDraft($page, $this->currentUser->id)) {
215 - $draft = $this->pageRepo->getUserPageDraft($page, $this->currentUser->id); 205 + $draft = $this->entityRepo->getUserPageDraft($page, $this->currentUser->id);
216 $page->name = $draft->name; 206 $page->name = $draft->name;
217 $page->html = $draft->html; 207 $page->html = $draft->html;
218 $page->markdown = $draft->markdown; 208 $page->markdown = $draft->markdown;
219 $page->isDraft = true; 209 $page->isDraft = true;
220 - $warnings [] = $this->pageRepo->getUserPageDraftMessage($draft); 210 + $warnings [] = $this->entityRepo->getUserPageDraftMessage($draft);
221 } 211 }
222 212
223 if (count($warnings) > 0) session()->flash('warning', implode("\n", $warnings)); 213 if (count($warnings) > 0) session()->flash('warning', implode("\n", $warnings));
...@@ -225,7 +215,7 @@ class PageController extends Controller ...@@ -225,7 +215,7 @@ class PageController extends Controller
225 $draftsEnabled = $this->signedIn; 215 $draftsEnabled = $this->signedIn;
226 return view('pages/edit', [ 216 return view('pages/edit', [
227 'page' => $page, 217 'page' => $page,
228 - 'book' => $book, 218 + 'book' => $page->book,
229 'current' => $page, 219 'current' => $page,
230 'draftsEnabled' => $draftsEnabled 220 'draftsEnabled' => $draftsEnabled
231 ]); 221 ]);
...@@ -243,11 +233,10 @@ class PageController extends Controller ...@@ -243,11 +233,10 @@ class PageController extends Controller
243 $this->validate($request, [ 233 $this->validate($request, [
244 'name' => 'required|string|max:255' 234 'name' => 'required|string|max:255'
245 ]); 235 ]);
246 - $book = $this->bookRepo->getBySlug($bookSlug); 236 + $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
247 - $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
248 $this->checkOwnablePermission('page-update', $page); 237 $this->checkOwnablePermission('page-update', $page);
249 - $this->pageRepo->updatePage($page, $book->id, $request->all()); 238 + $this->entityRepo->updatePage($page, $page->book->id, $request->all());
250 - Activity::add($page, 'page_update', $book->id); 239 + Activity::add($page, 'page_update', $page->book->id);
251 return redirect($page->getUrl()); 240 return redirect($page->getUrl());
252 } 241 }
253 242
...@@ -259,27 +248,23 @@ class PageController extends Controller ...@@ -259,27 +248,23 @@ class PageController extends Controller
259 */ 248 */
260 public function saveDraft(Request $request, $pageId) 249 public function saveDraft(Request $request, $pageId)
261 { 250 {
262 - $page = $this->pageRepo->getById($pageId, true); 251 + $page = $this->entityRepo->getById('page', $pageId, true);
263 $this->checkOwnablePermission('page-update', $page); 252 $this->checkOwnablePermission('page-update', $page);
264 253
265 if (!$this->signedIn) { 254 if (!$this->signedIn) {
266 return response()->json([ 255 return response()->json([
267 'status' => 'error', 256 'status' => 'error',
268 - 'message' => 'Guests cannot save drafts', 257 + 'message' => trans('errors.guests_cannot_save_drafts'),
269 ], 500); 258 ], 500);
270 } 259 }
271 260
272 - if ($page->draft) { 261 + $draft = $this->entityRepo->updatePageDraft($page, $request->only(['name', 'html', 'markdown']));
273 - $draft = $this->pageRepo->updateDraftPage($page, $request->only(['name', 'html', 'markdown']));
274 - } else {
275 - $draft = $this->pageRepo->saveUpdateDraft($page, $request->only(['name', 'html', 'markdown']));
276 - }
277 262
278 $updateTime = $draft->updated_at->timestamp; 263 $updateTime = $draft->updated_at->timestamp;
279 $utcUpdateTimestamp = $updateTime + Carbon::createFromTimestamp(0)->offset; 264 $utcUpdateTimestamp = $updateTime + Carbon::createFromTimestamp(0)->offset;
280 return response()->json([ 265 return response()->json([
281 'status' => 'success', 266 'status' => 'success',
282 - 'message' => 'Draft saved at ', 267 + 'message' => trans('entities.pages_edit_draft_save_at'),
283 'timestamp' => $utcUpdateTimestamp 268 'timestamp' => $utcUpdateTimestamp
284 ]); 269 ]);
285 } 270 }
...@@ -292,7 +277,7 @@ class PageController extends Controller ...@@ -292,7 +277,7 @@ class PageController extends Controller
292 */ 277 */
293 public function redirectFromLink($pageId) 278 public function redirectFromLink($pageId)
294 { 279 {
295 - $page = $this->pageRepo->getById($pageId); 280 + $page = $this->entityRepo->getById('page', $pageId);
296 return redirect($page->getUrl()); 281 return redirect($page->getUrl());
297 } 282 }
298 283
...@@ -304,11 +289,10 @@ class PageController extends Controller ...@@ -304,11 +289,10 @@ class PageController extends Controller
304 */ 289 */
305 public function showDelete($bookSlug, $pageSlug) 290 public function showDelete($bookSlug, $pageSlug)
306 { 291 {
307 - $book = $this->bookRepo->getBySlug($bookSlug); 292 + $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
308 - $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
309 $this->checkOwnablePermission('page-delete', $page); 293 $this->checkOwnablePermission('page-delete', $page);
310 - $this->setPageTitle('Delete Page ' . $page->getShortName()); 294 + $this->setPageTitle(trans('entities.pages_delete_named', ['pageName'=>$page->getShortName()]));
311 - return view('pages/delete', ['book' => $book, 'page' => $page, 'current' => $page]); 295 + return view('pages/delete', ['book' => $page->book, 'page' => $page, 'current' => $page]);
312 } 296 }
313 297
314 298
...@@ -321,11 +305,10 @@ class PageController extends Controller ...@@ -321,11 +305,10 @@ class PageController extends Controller
321 */ 305 */
322 public function showDeleteDraft($bookSlug, $pageId) 306 public function showDeleteDraft($bookSlug, $pageId)
323 { 307 {
324 - $book = $this->bookRepo->getBySlug($bookSlug); 308 + $page = $this->entityRepo->getById('page', $pageId, true);
325 - $page = $this->pageRepo->getById($pageId, true);
326 $this->checkOwnablePermission('page-update', $page); 309 $this->checkOwnablePermission('page-update', $page);
327 - $this->setPageTitle('Delete Draft Page ' . $page->getShortName()); 310 + $this->setPageTitle(trans('entities.pages_delete_draft_named', ['pageName'=>$page->getShortName()]));
328 - return view('pages/delete', ['book' => $book, 'page' => $page, 'current' => $page]); 311 + return view('pages/delete', ['book' => $page->book, 'page' => $page, 'current' => $page]);
329 } 312 }
330 313
331 /** 314 /**
...@@ -337,12 +320,12 @@ class PageController extends Controller ...@@ -337,12 +320,12 @@ class PageController extends Controller
337 */ 320 */
338 public function destroy($bookSlug, $pageSlug) 321 public function destroy($bookSlug, $pageSlug)
339 { 322 {
340 - $book = $this->bookRepo->getBySlug($bookSlug); 323 + $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
341 - $page = $this->pageRepo->getBySlug($pageSlug, $book->id); 324 + $book = $page->book;
342 $this->checkOwnablePermission('page-delete', $page); 325 $this->checkOwnablePermission('page-delete', $page);
343 Activity::addMessage('page_delete', $book->id, $page->name); 326 Activity::addMessage('page_delete', $book->id, $page->name);
344 - session()->flash('success', 'Page deleted'); 327 + session()->flash('success', trans('entities.pages_delete_success'));
345 - $this->pageRepo->destroy($page); 328 + $this->entityRepo->destroyPage($page);
346 return redirect($book->getUrl()); 329 return redirect($book->getUrl());
347 } 330 }
348 331
...@@ -355,11 +338,11 @@ class PageController extends Controller ...@@ -355,11 +338,11 @@ class PageController extends Controller
355 */ 338 */
356 public function destroyDraft($bookSlug, $pageId) 339 public function destroyDraft($bookSlug, $pageId)
357 { 340 {
358 - $book = $this->bookRepo->getBySlug($bookSlug); 341 + $page = $this->entityRepo->getById('page', $pageId, true);
359 - $page = $this->pageRepo->getById($pageId, true); 342 + $book = $page->book;
360 $this->checkOwnablePermission('page-update', $page); 343 $this->checkOwnablePermission('page-update', $page);
361 - session()->flash('success', 'Draft deleted'); 344 + session()->flash('success', trans('entities.pages_delete_draft_success'));
362 - $this->pageRepo->destroy($page); 345 + $this->entityRepo->destroyPage($page);
363 return redirect($book->getUrl()); 346 return redirect($book->getUrl());
364 } 347 }
365 348
...@@ -371,10 +354,9 @@ class PageController extends Controller ...@@ -371,10 +354,9 @@ class PageController extends Controller
371 */ 354 */
372 public function showRevisions($bookSlug, $pageSlug) 355 public function showRevisions($bookSlug, $pageSlug)
373 { 356 {
374 - $book = $this->bookRepo->getBySlug($bookSlug); 357 + $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
375 - $page = $this->pageRepo->getBySlug($pageSlug, $book->id); 358 + $this->setPageTitle(trans('entities.pages_revisions_named', ['pageName'=>$page->getShortName()]));
376 - $this->setPageTitle('Revisions For ' . $page->getShortName()); 359 + return view('pages/revisions', ['page' => $page, 'book' => $page->book, 'current' => $page]);
377 - return view('pages/revisions', ['page' => $page, 'book' => $book, 'current' => $page]);
378 } 360 }
379 361
380 /** 362 /**
...@@ -386,16 +368,15 @@ class PageController extends Controller ...@@ -386,16 +368,15 @@ class PageController extends Controller
386 */ 368 */
387 public function showRevision($bookSlug, $pageSlug, $revisionId) 369 public function showRevision($bookSlug, $pageSlug, $revisionId)
388 { 370 {
389 - $book = $this->bookRepo->getBySlug($bookSlug); 371 + $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
390 - $page = $this->pageRepo->getBySlug($pageSlug, $book->id); 372 + $revision = $this->entityRepo->getById('page_revision', $revisionId, false);
391 - $revision = $this->pageRepo->getRevisionById($revisionId);
392 373
393 $page->fill($revision->toArray()); 374 $page->fill($revision->toArray());
394 - $this->setPageTitle('Page Revision For ' . $page->getShortName()); 375 + $this->setPageTitle(trans('entities.pages_revision_named', ['pageName'=>$page->getShortName()]));
395 376
396 return view('pages/revision', [ 377 return view('pages/revision', [
397 'page' => $page, 378 'page' => $page,
398 - 'book' => $book, 379 + 'book' => $page->book,
399 ]); 380 ]);
400 } 381 }
401 382
...@@ -408,20 +389,19 @@ class PageController extends Controller ...@@ -408,20 +389,19 @@ class PageController extends Controller
408 */ 389 */
409 public function showRevisionChanges($bookSlug, $pageSlug, $revisionId) 390 public function showRevisionChanges($bookSlug, $pageSlug, $revisionId)
410 { 391 {
411 - $book = $this->bookRepo->getBySlug($bookSlug); 392 + $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
412 - $page = $this->pageRepo->getBySlug($pageSlug, $book->id); 393 + $revision = $this->entityRepo->getById('page_revision', $revisionId);
413 - $revision = $this->pageRepo->getRevisionById($revisionId);
414 394
415 $prev = $revision->getPrevious(); 395 $prev = $revision->getPrevious();
416 $prevContent = ($prev === null) ? '' : $prev->html; 396 $prevContent = ($prev === null) ? '' : $prev->html;
417 $diff = (new Htmldiff)->diff($prevContent, $revision->html); 397 $diff = (new Htmldiff)->diff($prevContent, $revision->html);
418 398
419 $page->fill($revision->toArray()); 399 $page->fill($revision->toArray());
420 - $this->setPageTitle('Page Revision For ' . $page->getShortName()); 400 + $this->setPageTitle(trans('entities.pages_revision_named', ['pageName'=>$page->getShortName()]));
421 401
422 return view('pages/revision', [ 402 return view('pages/revision', [
423 'page' => $page, 403 'page' => $page,
424 - 'book' => $book, 404 + 'book' => $page->book,
425 'diff' => $diff, 405 'diff' => $diff,
426 ]); 406 ]);
427 } 407 }
...@@ -435,11 +415,10 @@ class PageController extends Controller ...@@ -435,11 +415,10 @@ class PageController extends Controller
435 */ 415 */
436 public function restoreRevision($bookSlug, $pageSlug, $revisionId) 416 public function restoreRevision($bookSlug, $pageSlug, $revisionId)
437 { 417 {
438 - $book = $this->bookRepo->getBySlug($bookSlug); 418 + $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
439 - $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
440 $this->checkOwnablePermission('page-update', $page); 419 $this->checkOwnablePermission('page-update', $page);
441 - $page = $this->pageRepo->restoreRevision($page, $book, $revisionId); 420 + $page = $this->entityRepo->restorePageRevision($page, $page->book, $revisionId);
442 - Activity::add($page, 'page_restore', $book->id); 421 + Activity::add($page, 'page_restore', $page->book->id);
443 return redirect($page->getUrl()); 422 return redirect($page->getUrl());
444 } 423 }
445 424
...@@ -452,9 +431,9 @@ class PageController extends Controller ...@@ -452,9 +431,9 @@ class PageController extends Controller
452 */ 431 */
453 public function exportPdf($bookSlug, $pageSlug) 432 public function exportPdf($bookSlug, $pageSlug)
454 { 433 {
455 - $book = $this->bookRepo->getBySlug($bookSlug); 434 + $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
456 - $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
457 $pdfContent = $this->exportService->pageToPdf($page); 435 $pdfContent = $this->exportService->pageToPdf($page);
436 +// return $pdfContent;
458 return response()->make($pdfContent, 200, [ 437 return response()->make($pdfContent, 200, [
459 'Content-Type' => 'application/octet-stream', 438 'Content-Type' => 'application/octet-stream',
460 'Content-Disposition' => 'attachment; filename="' . $pageSlug . '.pdf' 439 'Content-Disposition' => 'attachment; filename="' . $pageSlug . '.pdf'
...@@ -469,8 +448,7 @@ class PageController extends Controller ...@@ -469,8 +448,7 @@ class PageController extends Controller
469 */ 448 */
470 public function exportHtml($bookSlug, $pageSlug) 449 public function exportHtml($bookSlug, $pageSlug)
471 { 450 {
472 - $book = $this->bookRepo->getBySlug($bookSlug); 451 + $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
473 - $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
474 $containedHtml = $this->exportService->pageToContainedHtml($page); 452 $containedHtml = $this->exportService->pageToContainedHtml($page);
475 return response()->make($containedHtml, 200, [ 453 return response()->make($containedHtml, 200, [
476 'Content-Type' => 'application/octet-stream', 454 'Content-Type' => 'application/octet-stream',
...@@ -486,8 +464,7 @@ class PageController extends Controller ...@@ -486,8 +464,7 @@ class PageController extends Controller
486 */ 464 */
487 public function exportPlainText($bookSlug, $pageSlug) 465 public function exportPlainText($bookSlug, $pageSlug)
488 { 466 {
489 - $book = $this->bookRepo->getBySlug($bookSlug); 467 + $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
490 - $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
491 $containedHtml = $this->exportService->pageToPlainText($page); 468 $containedHtml = $this->exportService->pageToPlainText($page);
492 return response()->make($containedHtml, 200, [ 469 return response()->make($containedHtml, 200, [
493 'Content-Type' => 'application/octet-stream', 470 'Content-Type' => 'application/octet-stream',
...@@ -501,9 +478,9 @@ class PageController extends Controller ...@@ -501,9 +478,9 @@ class PageController extends Controller
501 */ 478 */
502 public function showRecentlyCreated() 479 public function showRecentlyCreated()
503 { 480 {
504 - $pages = $this->pageRepo->getRecentlyCreatedPaginated(20)->setPath(baseUrl('/pages/recently-created')); 481 + $pages = $this->entityRepo->getRecentlyCreatedPaginated('page', 20)->setPath(baseUrl('/pages/recently-created'));
505 return view('pages/detailed-listing', [ 482 return view('pages/detailed-listing', [
506 - 'title' => 'Recently Created Pages', 483 + 'title' => trans('entities.recently_created_pages'),
507 'pages' => $pages 484 'pages' => $pages
508 ]); 485 ]);
509 } 486 }
...@@ -514,9 +491,9 @@ class PageController extends Controller ...@@ -514,9 +491,9 @@ class PageController extends Controller
514 */ 491 */
515 public function showRecentlyUpdated() 492 public function showRecentlyUpdated()
516 { 493 {
517 - $pages = $this->pageRepo->getRecentlyUpdatedPaginated(20)->setPath(baseUrl('/pages/recently-updated')); 494 + $pages = $this->entityRepo->getRecentlyUpdatedPaginated('page', 20)->setPath(baseUrl('/pages/recently-updated'));
518 return view('pages/detailed-listing', [ 495 return view('pages/detailed-listing', [
519 - 'title' => 'Recently Updated Pages', 496 + 'title' => trans('entities.recently_updated_pages'),
520 'pages' => $pages 497 'pages' => $pages
521 ]); 498 ]);
522 } 499 }
...@@ -529,8 +506,7 @@ class PageController extends Controller ...@@ -529,8 +506,7 @@ class PageController extends Controller
529 */ 506 */
530 public function showRestrict($bookSlug, $pageSlug) 507 public function showRestrict($bookSlug, $pageSlug)
531 { 508 {
532 - $book = $this->bookRepo->getBySlug($bookSlug); 509 + $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
533 - $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
534 $this->checkOwnablePermission('restrictions-manage', $page); 510 $this->checkOwnablePermission('restrictions-manage', $page);
535 $roles = $this->userRepo->getRestrictableRoles(); 511 $roles = $this->userRepo->getRestrictableRoles();
536 return view('pages/restrictions', [ 512 return view('pages/restrictions', [
...@@ -548,11 +524,10 @@ class PageController extends Controller ...@@ -548,11 +524,10 @@ class PageController extends Controller
548 */ 524 */
549 public function showMove($bookSlug, $pageSlug) 525 public function showMove($bookSlug, $pageSlug)
550 { 526 {
551 - $book = $this->bookRepo->getBySlug($bookSlug); 527 + $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
552 - $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
553 $this->checkOwnablePermission('page-update', $page); 528 $this->checkOwnablePermission('page-update', $page);
554 return view('pages/move', [ 529 return view('pages/move', [
555 - 'book' => $book, 530 + 'book' => $page->book,
556 'page' => $page 531 'page' => $page
557 ]); 532 ]);
558 } 533 }
...@@ -567,8 +542,7 @@ class PageController extends Controller ...@@ -567,8 +542,7 @@ class PageController extends Controller
567 */ 542 */
568 public function move($bookSlug, $pageSlug, Request $request) 543 public function move($bookSlug, $pageSlug, Request $request)
569 { 544 {
570 - $book = $this->bookRepo->getBySlug($bookSlug); 545 + $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
571 - $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
572 $this->checkOwnablePermission('page-update', $page); 546 $this->checkOwnablePermission('page-update', $page);
573 547
574 $entitySelection = $request->get('entity_selection', null); 548 $entitySelection = $request->get('entity_selection', null);
...@@ -580,22 +554,17 @@ class PageController extends Controller ...@@ -580,22 +554,17 @@ class PageController extends Controller
580 $entityType = $stringExploded[0]; 554 $entityType = $stringExploded[0];
581 $entityId = intval($stringExploded[1]); 555 $entityId = intval($stringExploded[1]);
582 556
583 - $parent = false;
584 557
585 - if ($entityType == 'chapter') { 558 + try {
586 - $parent = $this->chapterRepo->getById($entityId); 559 + $parent = $this->entityRepo->getById($entityType, $entityId);
587 - } else if ($entityType == 'book') { 560 + } catch (\Exception $e) {
588 - $parent = $this->bookRepo->getById($entityId); 561 + session()->flash(trans('entities.selected_book_chapter_not_found'));
589 - }
590 -
591 - if ($parent === false || $parent === null) {
592 - session()->flash('The selected Book or Chapter was not found');
593 return redirect()->back(); 562 return redirect()->back();
594 } 563 }
595 564
596 - $this->pageRepo->changePageParent($page, $parent); 565 + $this->entityRepo->changePageParent($page, $parent);
597 Activity::add($page, 'page_move', $page->book->id); 566 Activity::add($page, 'page_move', $page->book->id);
598 - session()->flash('success', sprintf('Page moved to "%s"', $parent->name)); 567 + session()->flash('success', trans('entities.pages_move_success', ['parentName' => $parent->name]));
599 568
600 return redirect($page->getUrl()); 569 return redirect($page->getUrl());
601 } 570 }
...@@ -609,11 +578,10 @@ class PageController extends Controller ...@@ -609,11 +578,10 @@ class PageController extends Controller
609 */ 578 */
610 public function restrict($bookSlug, $pageSlug, Request $request) 579 public function restrict($bookSlug, $pageSlug, Request $request)
611 { 580 {
612 - $book = $this->bookRepo->getBySlug($bookSlug); 581 + $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
613 - $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
614 $this->checkOwnablePermission('restrictions-manage', $page); 582 $this->checkOwnablePermission('restrictions-manage', $page);
615 - $this->pageRepo->updateEntityPermissionsFromRequest($request, $page); 583 + $this->entityRepo->updateEntityPermissionsFromRequest($request, $page);
616 - session()->flash('success', 'Page Permissions Updated'); 584 + session()->flash('success', trans('entities.pages_permissions_success'));
617 return redirect($page->getUrl()); 585 return redirect($page->getUrl());
618 } 586 }
619 587
......
...@@ -2,9 +2,7 @@ ...@@ -2,9 +2,7 @@
2 2
3 use BookStack\Exceptions\PermissionsException; 3 use BookStack\Exceptions\PermissionsException;
4 use BookStack\Repos\PermissionsRepo; 4 use BookStack\Repos\PermissionsRepo;
5 -use BookStack\Services\PermissionService;
6 use Illuminate\Http\Request; 5 use Illuminate\Http\Request;
7 -use BookStack\Http\Requests;
8 6
9 class PermissionController extends Controller 7 class PermissionController extends Controller
10 { 8 {
...@@ -55,7 +53,7 @@ class PermissionController extends Controller ...@@ -55,7 +53,7 @@ class PermissionController extends Controller
55 ]); 53 ]);
56 54
57 $this->permissionsRepo->saveNewRole($request->all()); 55 $this->permissionsRepo->saveNewRole($request->all());
58 - session()->flash('success', 'Role successfully created'); 56 + session()->flash('success', trans('settings.role_create_success'));
59 return redirect('/settings/roles'); 57 return redirect('/settings/roles');
60 } 58 }
61 59
...@@ -69,7 +67,7 @@ class PermissionController extends Controller ...@@ -69,7 +67,7 @@ class PermissionController extends Controller
69 { 67 {
70 $this->checkPermission('user-roles-manage'); 68 $this->checkPermission('user-roles-manage');
71 $role = $this->permissionsRepo->getRoleById($id); 69 $role = $this->permissionsRepo->getRoleById($id);
72 - if ($role->hidden) throw new PermissionsException('This role cannot be edited'); 70 + if ($role->hidden) throw new PermissionsException(trans('errors.role_cannot_be_edited'));
73 return view('settings/roles/edit', ['role' => $role]); 71 return view('settings/roles/edit', ['role' => $role]);
74 } 72 }
75 73
...@@ -88,7 +86,7 @@ class PermissionController extends Controller ...@@ -88,7 +86,7 @@ class PermissionController extends Controller
88 ]); 86 ]);
89 87
90 $this->permissionsRepo->updateRole($id, $request->all()); 88 $this->permissionsRepo->updateRole($id, $request->all());
91 - session()->flash('success', 'Role successfully updated'); 89 + session()->flash('success', trans('settings.role_update_success'));
92 return redirect('/settings/roles'); 90 return redirect('/settings/roles');
93 } 91 }
94 92
...@@ -103,7 +101,7 @@ class PermissionController extends Controller ...@@ -103,7 +101,7 @@ class PermissionController extends Controller
103 $this->checkPermission('user-roles-manage'); 101 $this->checkPermission('user-roles-manage');
104 $role = $this->permissionsRepo->getRoleById($id); 102 $role = $this->permissionsRepo->getRoleById($id);
105 $roles = $this->permissionsRepo->getAllRolesExcept($role); 103 $roles = $this->permissionsRepo->getAllRolesExcept($role);
106 - $blankRole = $role->newInstance(['display_name' => 'Don\'t migrate users']); 104 + $blankRole = $role->newInstance(['display_name' => trans('settings.role_delete_no_migration')]);
107 $roles->prepend($blankRole); 105 $roles->prepend($blankRole);
108 return view('settings/roles/delete', ['role' => $role, 'roles' => $roles]); 106 return view('settings/roles/delete', ['role' => $role, 'roles' => $roles]);
109 } 107 }
...@@ -126,7 +124,7 @@ class PermissionController extends Controller ...@@ -126,7 +124,7 @@ class PermissionController extends Controller
126 return redirect()->back(); 124 return redirect()->back();
127 } 125 }
128 126
129 - session()->flash('success', 'Role successfully deleted'); 127 + session()->flash('success', trans('settings.role_delete_success'));
130 return redirect('/settings/roles'); 128 return redirect('/settings/roles');
131 } 129 }
132 } 130 }
......
1 -<?php 1 +<?php namespace BookStack\Http\Controllers;
2 -
3 -namespace BookStack\Http\Controllers;
4 2
3 +use BookStack\Repos\EntityRepo;
5 use BookStack\Services\ViewService; 4 use BookStack\Services\ViewService;
6 use Illuminate\Http\Request; 5 use Illuminate\Http\Request;
7 6
8 -use BookStack\Http\Requests;
9 -use BookStack\Repos\BookRepo;
10 -use BookStack\Repos\ChapterRepo;
11 -use BookStack\Repos\PageRepo;
12 -
13 class SearchController extends Controller 7 class SearchController extends Controller
14 { 8 {
15 - protected $pageRepo; 9 + protected $entityRepo;
16 - protected $bookRepo;
17 - protected $chapterRepo;
18 protected $viewService; 10 protected $viewService;
19 11
20 /** 12 /**
21 * SearchController constructor. 13 * SearchController constructor.
22 - * @param PageRepo $pageRepo 14 + * @param EntityRepo $entityRepo
23 - * @param BookRepo $bookRepo
24 - * @param ChapterRepo $chapterRepo
25 * @param ViewService $viewService 15 * @param ViewService $viewService
26 */ 16 */
27 - public function __construct(PageRepo $pageRepo, BookRepo $bookRepo, ChapterRepo $chapterRepo, ViewService $viewService) 17 + public function __construct(EntityRepo $entityRepo, ViewService $viewService)
28 { 18 {
29 - $this->pageRepo = $pageRepo; 19 + $this->entityRepo = $entityRepo;
30 - $this->bookRepo = $bookRepo;
31 - $this->chapterRepo = $chapterRepo;
32 $this->viewService = $viewService; 20 $this->viewService = $viewService;
33 parent::__construct(); 21 parent::__construct();
34 } 22 }
...@@ -46,10 +34,10 @@ class SearchController extends Controller ...@@ -46,10 +34,10 @@ class SearchController extends Controller
46 } 34 }
47 $searchTerm = $request->get('term'); 35 $searchTerm = $request->get('term');
48 $paginationAppends = $request->only('term'); 36 $paginationAppends = $request->only('term');
49 - $pages = $this->pageRepo->getBySearch($searchTerm, [], 20, $paginationAppends); 37 + $pages = $this->entityRepo->getBySearch('page', $searchTerm, [], 20, $paginationAppends);
50 - $books = $this->bookRepo->getBySearch($searchTerm, 10, $paginationAppends); 38 + $books = $this->entityRepo->getBySearch('book', $searchTerm, [], 10, $paginationAppends);
51 - $chapters = $this->chapterRepo->getBySearch($searchTerm, [], 10, $paginationAppends); 39 + $chapters = $this->entityRepo->getBySearch('chapter', $searchTerm, [], 10, $paginationAppends);
52 - $this->setPageTitle('Search For ' . $searchTerm); 40 + $this->setPageTitle(trans('entities.search_for_term', ['term' => $searchTerm]));
53 return view('search/all', [ 41 return view('search/all', [
54 'pages' => $pages, 42 'pages' => $pages,
55 'books' => $books, 43 'books' => $books,
...@@ -69,11 +57,11 @@ class SearchController extends Controller ...@@ -69,11 +57,11 @@ class SearchController extends Controller
69 57
70 $searchTerm = $request->get('term'); 58 $searchTerm = $request->get('term');
71 $paginationAppends = $request->only('term'); 59 $paginationAppends = $request->only('term');
72 - $pages = $this->pageRepo->getBySearch($searchTerm, [], 20, $paginationAppends); 60 + $pages = $this->entityRepo->getBySearch('page', $searchTerm, [], 20, $paginationAppends);
73 - $this->setPageTitle('Page Search For ' . $searchTerm); 61 + $this->setPageTitle(trans('entities.search_page_for_term', ['term' => $searchTerm]));
74 return view('search/entity-search-list', [ 62 return view('search/entity-search-list', [
75 'entities' => $pages, 63 'entities' => $pages,
76 - 'title' => 'Page Search Results', 64 + 'title' => trans('entities.search_results_page'),
77 'searchTerm' => $searchTerm 65 'searchTerm' => $searchTerm
78 ]); 66 ]);
79 } 67 }
...@@ -89,11 +77,11 @@ class SearchController extends Controller ...@@ -89,11 +77,11 @@ class SearchController extends Controller
89 77
90 $searchTerm = $request->get('term'); 78 $searchTerm = $request->get('term');
91 $paginationAppends = $request->only('term'); 79 $paginationAppends = $request->only('term');
92 - $chapters = $this->chapterRepo->getBySearch($searchTerm, [], 20, $paginationAppends); 80 + $chapters = $this->entityRepo->getBySearch('chapter', $searchTerm, [], 20, $paginationAppends);
93 - $this->setPageTitle('Chapter Search For ' . $searchTerm); 81 + $this->setPageTitle(trans('entities.search_chapter_for_term', ['term' => $searchTerm]));
94 return view('search/entity-search-list', [ 82 return view('search/entity-search-list', [
95 'entities' => $chapters, 83 'entities' => $chapters,
96 - 'title' => 'Chapter Search Results', 84 + 'title' => trans('entities.search_results_chapter'),
97 'searchTerm' => $searchTerm 85 'searchTerm' => $searchTerm
98 ]); 86 ]);
99 } 87 }
...@@ -109,11 +97,11 @@ class SearchController extends Controller ...@@ -109,11 +97,11 @@ class SearchController extends Controller
109 97
110 $searchTerm = $request->get('term'); 98 $searchTerm = $request->get('term');
111 $paginationAppends = $request->only('term'); 99 $paginationAppends = $request->only('term');
112 - $books = $this->bookRepo->getBySearch($searchTerm, 20, $paginationAppends); 100 + $books = $this->entityRepo->getBySearch('book', $searchTerm, [], 20, $paginationAppends);
113 - $this->setPageTitle('Book Search For ' . $searchTerm); 101 + $this->setPageTitle(trans('entities.search_book_for_term', ['term' => $searchTerm]));
114 return view('search/entity-search-list', [ 102 return view('search/entity-search-list', [
115 'entities' => $books, 103 'entities' => $books,
116 - 'title' => 'Book Search Results', 104 + 'title' => trans('entities.search_results_book'),
117 'searchTerm' => $searchTerm 105 'searchTerm' => $searchTerm
118 ]); 106 ]);
119 } 107 }
...@@ -132,8 +120,8 @@ class SearchController extends Controller ...@@ -132,8 +120,8 @@ class SearchController extends Controller
132 } 120 }
133 $searchTerm = $request->get('term'); 121 $searchTerm = $request->get('term');
134 $searchWhereTerms = [['book_id', '=', $bookId]]; 122 $searchWhereTerms = [['book_id', '=', $bookId]];
135 - $pages = $this->pageRepo->getBySearch($searchTerm, $searchWhereTerms); 123 + $pages = $this->entityRepo->getBySearch('page', $searchTerm, $searchWhereTerms);
136 - $chapters = $this->chapterRepo->getBySearch($searchTerm, $searchWhereTerms); 124 + $chapters = $this->entityRepo->getBySearch('chapter', $searchTerm, $searchWhereTerms);
137 return view('search/book', ['pages' => $pages, 'chapters' => $chapters, 'searchTerm' => $searchTerm]); 125 return view('search/book', ['pages' => $pages, 'chapters' => $chapters, 'searchTerm' => $searchTerm]);
138 } 126 }
139 127
...@@ -152,9 +140,11 @@ class SearchController extends Controller ...@@ -152,9 +140,11 @@ class SearchController extends Controller
152 140
153 // Search for entities otherwise show most popular 141 // Search for entities otherwise show most popular
154 if ($searchTerm !== false) { 142 if ($searchTerm !== false) {
155 - if ($entityTypes->contains('page')) $entities = $entities->merge($this->pageRepo->getBySearch($searchTerm)->items()); 143 + foreach (['page', 'chapter', 'book'] as $entityType) {
156 - if ($entityTypes->contains('chapter')) $entities = $entities->merge($this->chapterRepo->getBySearch($searchTerm)->items()); 144 + if ($entityTypes->contains($entityType)) {
157 - if ($entityTypes->contains('book')) $entities = $entities->merge($this->bookRepo->getBySearch($searchTerm)->items()); 145 + $entities = $entities->merge($this->entityRepo->getBySearch($entityType, $searchTerm)->items());
146 + }
147 + }
158 $entities = $entities->sortByDesc('title_relevance'); 148 $entities = $entities->sortByDesc('title_relevance');
159 } else { 149 } else {
160 $entityNames = $entityTypes->map(function ($type) { 150 $entityNames = $entityTypes->map(function ($type) {
......
1 <?php namespace BookStack\Http\Controllers; 1 <?php namespace BookStack\Http\Controllers;
2 2
3 use Illuminate\Http\Request; 3 use Illuminate\Http\Request;
4 - 4 +use Illuminate\Http\Response;
5 -use BookStack\Http\Requests;
6 use Setting; 5 use Setting;
7 6
8 class SettingController extends Controller 7 class SettingController extends Controller
...@@ -39,7 +38,7 @@ class SettingController extends Controller ...@@ -39,7 +38,7 @@ class SettingController extends Controller
39 Setting::put($key, $value); 38 Setting::put($key, $value);
40 } 39 }
41 40
42 - session()->flash('success', 'Settings Saved'); 41 + session()->flash('success', trans('settings.settings_save_success'));
43 return redirect('/settings'); 42 return redirect('/settings');
44 } 43 }
45 44
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
2 2
3 use BookStack\Repos\TagRepo; 3 use BookStack\Repos\TagRepo;
4 use Illuminate\Http\Request; 4 use Illuminate\Http\Request;
5 -use BookStack\Http\Requests;
6 5
7 class TagController extends Controller 6 class TagController extends Controller
8 { 7 {
...@@ -16,12 +15,14 @@ class TagController extends Controller ...@@ -16,12 +15,14 @@ class TagController extends Controller
16 public function __construct(TagRepo $tagRepo) 15 public function __construct(TagRepo $tagRepo)
17 { 16 {
18 $this->tagRepo = $tagRepo; 17 $this->tagRepo = $tagRepo;
18 + parent::__construct();
19 } 19 }
20 20
21 /** 21 /**
22 * Get all the Tags for a particular entity 22 * Get all the Tags for a particular entity
23 * @param $entityType 23 * @param $entityType
24 * @param $entityId 24 * @param $entityId
25 + * @return \Illuminate\Http\JsonResponse
25 */ 26 */
26 public function getForEntity($entityType, $entityId) 27 public function getForEntity($entityType, $entityId)
27 { 28 {
...@@ -30,28 +31,9 @@ class TagController extends Controller ...@@ -30,28 +31,9 @@ class TagController extends Controller
30 } 31 }
31 32
32 /** 33 /**
33 - * Update the tags for a particular entity.
34 - * @param $entityType
35 - * @param $entityId
36 - * @param Request $request
37 - * @return mixed
38 - */
39 - public function updateForEntity($entityType, $entityId, Request $request)
40 - {
41 - $entity = $this->tagRepo->getEntity($entityType, $entityId, 'update');
42 - if ($entity === null) return $this->jsonError("Entity not found", 404);
43 -
44 - $inputTags = $request->input('tags');
45 - $tags = $this->tagRepo->saveTagsToEntity($entity, $inputTags);
46 - return response()->json([
47 - 'tags' => $tags,
48 - 'message' => 'Tags successfully updated'
49 - ]);
50 - }
51 -
52 - /**
53 * Get tag name suggestions from a given search term. 34 * Get tag name suggestions from a given search term.
54 * @param Request $request 35 * @param Request $request
36 + * @return \Illuminate\Http\JsonResponse
55 */ 37 */
56 public function getNameSuggestions(Request $request) 38 public function getNameSuggestions(Request $request)
57 { 39 {
...@@ -63,6 +45,7 @@ class TagController extends Controller ...@@ -63,6 +45,7 @@ class TagController extends Controller
63 /** 45 /**
64 * Get tag value suggestions from a given search term. 46 * Get tag value suggestions from a given search term.
65 * @param Request $request 47 * @param Request $request
48 + * @return \Illuminate\Http\JsonResponse
66 */ 49 */
67 public function getValueSuggestions(Request $request) 50 public function getValueSuggestions(Request $request)
68 { 51 {
......
1 -<?php 1 +<?php namespace BookStack\Http\Controllers;
2 2
3 -namespace BookStack\Http\Controllers;
4 -
5 -use BookStack\Activity;
6 use Exception; 3 use Exception;
7 use Illuminate\Http\Request; 4 use Illuminate\Http\Request;
8 -
9 use Illuminate\Http\Response; 5 use Illuminate\Http\Response;
10 -use BookStack\Http\Requests;
11 use BookStack\Repos\UserRepo; 6 use BookStack\Repos\UserRepo;
12 use BookStack\Services\SocialAuthService; 7 use BookStack\Services\SocialAuthService;
13 use BookStack\User; 8 use BookStack\User;
...@@ -44,7 +39,7 @@ class UserController extends Controller ...@@ -44,7 +39,7 @@ class UserController extends Controller
44 'sort' => $request->has('sort') ? $request->get('sort') : 'name', 39 'sort' => $request->has('sort') ? $request->get('sort') : 'name',
45 ]; 40 ];
46 $users = $this->userRepo->getAllUsersPaginatedAndSorted(20, $listDetails); 41 $users = $this->userRepo->getAllUsersPaginatedAndSorted(20, $listDetails);
47 - $this->setPageTitle('Users'); 42 + $this->setPageTitle(trans('settings.users'));
48 $users->appends($listDetails); 43 $users->appends($listDetails);
49 return view('users/index', ['users' => $users, 'listDetails' => $listDetails]); 44 return view('users/index', ['users' => $users, 'listDetails' => $listDetails]);
50 } 45 }
...@@ -83,7 +78,6 @@ class UserController extends Controller ...@@ -83,7 +78,6 @@ class UserController extends Controller
83 } 78 }
84 $this->validate($request, $validationRules); 79 $this->validate($request, $validationRules);
85 80
86 -
87 $user = $this->user->fill($request->all()); 81 $user = $this->user->fill($request->all());
88 82
89 if ($authMethod === 'standard') { 83 if ($authMethod === 'standard') {
...@@ -131,7 +125,7 @@ class UserController extends Controller ...@@ -131,7 +125,7 @@ class UserController extends Controller
131 $authMethod = ($user->system_name) ? 'system' : config('auth.method'); 125 $authMethod = ($user->system_name) ? 'system' : config('auth.method');
132 126
133 $activeSocialDrivers = $socialAuthService->getActiveDrivers(); 127 $activeSocialDrivers = $socialAuthService->getActiveDrivers();
134 - $this->setPageTitle('User Profile'); 128 + $this->setPageTitle(trans('settings.user_profile'));
135 $roles = $this->userRepo->getAllRoles(); 129 $roles = $this->userRepo->getAllRoles();
136 return view('users/edit', ['user' => $user, 'activeSocialDrivers' => $activeSocialDrivers, 'authMethod' => $authMethod, 'roles' => $roles]); 130 return view('users/edit', ['user' => $user, 'activeSocialDrivers' => $activeSocialDrivers, 'authMethod' => $authMethod, 'roles' => $roles]);
137 } 131 }
...@@ -153,9 +147,8 @@ class UserController extends Controller ...@@ -153,9 +147,8 @@ class UserController extends Controller
153 'name' => 'min:2', 147 'name' => 'min:2',
154 'email' => 'min:2|email|unique:users,email,' . $id, 148 'email' => 'min:2|email|unique:users,email,' . $id,
155 'password' => 'min:5|required_with:password_confirm', 149 'password' => 'min:5|required_with:password_confirm',
156 - 'password-confirm' => 'same:password|required_with:password' 150 + 'password-confirm' => 'same:password|required_with:password',
157 - ], [ 151 + 'setting' => 'array'
158 - 'password-confirm.required_with' => 'Password confirmation required'
159 ]); 152 ]);
160 153
161 $user = $this->user->findOrFail($id); 154 $user = $this->user->findOrFail($id);
...@@ -178,8 +171,15 @@ class UserController extends Controller ...@@ -178,8 +171,15 @@ class UserController extends Controller
178 $user->external_auth_id = $request->get('external_auth_id'); 171 $user->external_auth_id = $request->get('external_auth_id');
179 } 172 }
180 173
174 + // Save an user-specific settings
175 + if ($request->has('setting')) {
176 + foreach ($request->get('setting') as $key => $value) {
177 + setting()->putUser($user, $key, $value);
178 + }
179 + }
180 +
181 $user->save(); 181 $user->save();
182 - session()->flash('success', 'User successfully updated'); 182 + session()->flash('success', trans('settings.users_edit_success'));
183 183
184 $redirectUrl = userCan('users-manage') ? '/settings/users' : '/settings/users/' . $user->id; 184 $redirectUrl = userCan('users-manage') ? '/settings/users' : '/settings/users/' . $user->id;
185 return redirect($redirectUrl); 185 return redirect($redirectUrl);
...@@ -197,7 +197,7 @@ class UserController extends Controller ...@@ -197,7 +197,7 @@ class UserController extends Controller
197 }); 197 });
198 198
199 $user = $this->user->findOrFail($id); 199 $user = $this->user->findOrFail($id);
200 - $this->setPageTitle('Delete User ' . $user->name); 200 + $this->setPageTitle(trans('settings.users_delete_named', ['userName' => $user->name]));
201 return view('users/delete', ['user' => $user]); 201 return view('users/delete', ['user' => $user]);
202 } 202 }
203 203
...@@ -216,17 +216,17 @@ class UserController extends Controller ...@@ -216,17 +216,17 @@ class UserController extends Controller
216 $user = $this->userRepo->getById($id); 216 $user = $this->userRepo->getById($id);
217 217
218 if ($this->userRepo->isOnlyAdmin($user)) { 218 if ($this->userRepo->isOnlyAdmin($user)) {
219 - session()->flash('error', 'You cannot delete the only admin'); 219 + session()->flash('error', trans('errors.users_cannot_delete_only_admin'));
220 return redirect($user->getEditUrl()); 220 return redirect($user->getEditUrl());
221 } 221 }
222 222
223 if ($user->system_name === 'public') { 223 if ($user->system_name === 'public') {
224 - session()->flash('error', 'You cannot delete the guest user'); 224 + session()->flash('error', trans('errors.users_cannot_delete_guest'));
225 return redirect($user->getEditUrl()); 225 return redirect($user->getEditUrl());
226 } 226 }
227 227
228 $this->userRepo->destroy($user); 228 $this->userRepo->destroy($user);
229 - session()->flash('success', 'User successfully removed'); 229 + session()->flash('success', trans('settings.users_delete_success'));
230 230
231 return redirect('/settings/users'); 231 return redirect('/settings/users');
232 } 232 }
......
1 -<?php 1 +<?php namespace BookStack\Http;
2 -
3 -namespace BookStack\Http;
4 2
5 use Illuminate\Foundation\Http\Kernel as HttpKernel; 3 use Illuminate\Foundation\Http\Kernel as HttpKernel;
6 4
...@@ -30,6 +28,7 @@ class Kernel extends HttpKernel ...@@ -30,6 +28,7 @@ class Kernel extends HttpKernel
30 \Illuminate\View\Middleware\ShareErrorsFromSession::class, 28 \Illuminate\View\Middleware\ShareErrorsFromSession::class,
31 \BookStack\Http\Middleware\VerifyCsrfToken::class, 29 \BookStack\Http\Middleware\VerifyCsrfToken::class,
32 \Illuminate\Routing\Middleware\SubstituteBindings::class, 30 \Illuminate\Routing\Middleware\SubstituteBindings::class,
31 + \BookStack\Http\Middleware\Localization::class
33 ], 32 ],
34 'api' => [ 33 'api' => [
35 'throttle:60,1', 34 'throttle:60,1',
......
...@@ -4,8 +4,6 @@ namespace BookStack\Http\Middleware; ...@@ -4,8 +4,6 @@ namespace BookStack\Http\Middleware;
4 4
5 use Closure; 5 use Closure;
6 use Illuminate\Contracts\Auth\Guard; 6 use Illuminate\Contracts\Auth\Guard;
7 -use BookStack\Exceptions\UserRegistrationException;
8 -use Setting;
9 7
10 class Authenticate 8 class Authenticate
11 { 9 {
......
1 +<?php namespace BookStack\Http\Middleware;
2 +
3 +use Carbon\Carbon;
4 +use Closure;
5 +
6 +class Localization
7 +{
8 + /**
9 + * Handle an incoming request.
10 + *
11 + * @param \Illuminate\Http\Request $request
12 + * @param \Closure $next
13 + * @return mixed
14 + */
15 + public function handle($request, Closure $next)
16 + {
17 + $defaultLang = config('app.locale');
18 + $locale = setting()->getUser(user(), 'language', $defaultLang);
19 + app()->setLocale($locale);
20 + Carbon::setLocale($locale);
21 + return $next($request);
22 + }
23 +}
1 -<?php 1 +<?php namespace BookStack\Http\Middleware;
2 -
3 -namespace BookStack\Http\Middleware;
4 2
5 use Closure; 3 use Closure;
6 use Illuminate\Contracts\Auth\Guard; 4 use Illuminate\Contracts\Auth\Guard;
......
...@@ -43,8 +43,9 @@ class ResetPassword extends Notification ...@@ -43,8 +43,9 @@ class ResetPassword extends Notification
43 public function toMail() 43 public function toMail()
44 { 44 {
45 return (new MailMessage) 45 return (new MailMessage)
46 - ->line('You are receiving this email because we received a password reset request for your account.') 46 + ->subject(trans('auth.email_reset_subject', ['appName' => setting('app-name')]))
47 - ->action('Reset Password', baseUrl('password/reset/' . $this->token)) 47 + ->line(trans('auth.email_reset_text'))
48 - ->line('If you did not request a password reset, no further action is required.'); 48 + ->action(trans('auth.reset_password'), baseUrl('password/reset/' . $this->token))
49 + ->line(trans('auth.email_reset_not_requested'));
49 } 50 }
50 } 51 }
......
...@@ -7,6 +7,10 @@ class Page extends Entity ...@@ -7,6 +7,10 @@ class Page extends Entity
7 7
8 protected $simpleAttributes = ['name', 'id', 'slug']; 8 protected $simpleAttributes = ['name', 'id', 'slug'];
9 9
10 + protected $with = ['book'];
11 +
12 + protected $fieldsToSearch = ['name', 'text'];
13 +
10 /** 14 /**
11 * Converts this page into a simplified array. 15 * Converts this page into a simplified array.
12 * @return mixed 16 * @return mixed
......
1 <?php namespace BookStack\Providers; 1 <?php namespace BookStack\Providers;
2 2
3 use Illuminate\Support\ServiceProvider; 3 use Illuminate\Support\ServiceProvider;
4 +use Validator;
4 5
5 class AppServiceProvider extends ServiceProvider 6 class AppServiceProvider extends ServiceProvider
6 { 7 {
...@@ -12,11 +13,10 @@ class AppServiceProvider extends ServiceProvider ...@@ -12,11 +13,10 @@ class AppServiceProvider extends ServiceProvider
12 public function boot() 13 public function boot()
13 { 14 {
14 // Custom validation methods 15 // Custom validation methods
15 - \Validator::extend('is_image', function($attribute, $value, $parameters, $validator) { 16 + Validator::extend('is_image', function($attribute, $value, $parameters, $validator) {
16 $imageMimes = ['image/png', 'image/bmp', 'image/gif', 'image/jpeg', 'image/jpg', 'image/tiff', 'image/webp']; 17 $imageMimes = ['image/png', 'image/bmp', 'image/gif', 'image/jpeg', 'image/jpg', 'image/tiff', 'image/webp'];
17 return in_array($value->getMimeType(), $imageMimes); 18 return in_array($value->getMimeType(), $imageMimes);
18 }); 19 });
19 -
20 } 20 }
21 21
22 /** 22 /**
......
1 -<?php namespace BookStack\Repos;
2 -
3 -use Alpha\B;
4 -use BookStack\Exceptions\NotFoundException;
5 -use Illuminate\Database\Eloquent\Collection;
6 -use Illuminate\Support\Str;
7 -use BookStack\Book;
8 -use Views;
9 -
10 -class BookRepo extends EntityRepo
11 -{
12 - protected $pageRepo;
13 - protected $chapterRepo;
14 -
15 - /**
16 - * BookRepo constructor.
17 - * @param PageRepo $pageRepo
18 - * @param ChapterRepo $chapterRepo
19 - */
20 - public function __construct(PageRepo $pageRepo, ChapterRepo $chapterRepo)
21 - {
22 - $this->pageRepo = $pageRepo;
23 - $this->chapterRepo = $chapterRepo;
24 - parent::__construct();
25 - }
26 -
27 - /**
28 - * Base query for getting books.
29 - * Takes into account any restrictions.
30 - * @return mixed
31 - */
32 - private function bookQuery()
33 - {
34 - return $this->permissionService->enforceBookRestrictions($this->book, 'view');
35 - }
36 -
37 - /**
38 - * Get the book that has the given id.
39 - * @param $id
40 - * @return mixed
41 - */
42 - public function getById($id)
43 - {
44 - return $this->bookQuery()->findOrFail($id);
45 - }
46 -
47 - /**
48 - * Get all books, Limited by count.
49 - * @param int $count
50 - * @return mixed
51 - */
52 - public function getAll($count = 10)
53 - {
54 - $bookQuery = $this->bookQuery()->orderBy('name', 'asc');
55 - if (!$count) return $bookQuery->get();
56 - return $bookQuery->take($count)->get();
57 - }
58 -
59 - /**
60 - * Get all books paginated.
61 - * @param int $count
62 - * @return mixed
63 - */
64 - public function getAllPaginated($count = 10)
65 - {
66 - return $this->bookQuery()
67 - ->orderBy('name', 'asc')->paginate($count);
68 - }
69 -
70 -
71 - /**
72 - * Get the latest books.
73 - * @param int $count
74 - * @return mixed
75 - */
76 - public function getLatest($count = 10)
77 - {
78 - return $this->bookQuery()->orderBy('created_at', 'desc')->take($count)->get();
79 - }
80 -
81 - /**
82 - * Gets the most recently viewed for a user.
83 - * @param int $count
84 - * @param int $page
85 - * @return mixed
86 - */
87 - public function getRecentlyViewed($count = 10, $page = 0)
88 - {
89 - return Views::getUserRecentlyViewed($count, $page, $this->book);
90 - }
91 -
92 - /**
93 - * Gets the most viewed books.
94 - * @param int $count
95 - * @param int $page
96 - * @return mixed
97 - */
98 - public function getPopular($count = 10, $page = 0)
99 - {
100 - return Views::getPopular($count, $page, $this->book);
101 - }
102 -
103 - /**
104 - * Get a book by slug
105 - * @param $slug
106 - * @return mixed
107 - * @throws NotFoundException
108 - */
109 - public function getBySlug($slug)
110 - {
111 - $book = $this->bookQuery()->where('slug', '=', $slug)->first();
112 - if ($book === null) throw new NotFoundException('Book not found');
113 - return $book;
114 - }
115 -
116 - /**
117 - * Checks if a book exists.
118 - * @param $id
119 - * @return bool
120 - */
121 - public function exists($id)
122 - {
123 - return $this->bookQuery()->where('id', '=', $id)->exists();
124 - }
125 -
126 - /**
127 - * Get a new book instance from request input.
128 - * @param array $input
129 - * @return Book
130 - */
131 - public function createFromInput($input)
132 - {
133 - $book = $this->book->newInstance($input);
134 - $book->slug = $this->findSuitableSlug($book->name);
135 - $book->created_by = user()->id;
136 - $book->updated_by = user()->id;
137 - $book->save();
138 - $this->permissionService->buildJointPermissionsForEntity($book);
139 - return $book;
140 - }
141 -
142 - /**
143 - * Update the given book from user input.
144 - * @param Book $book
145 - * @param $input
146 - * @return Book
147 - */
148 - public function updateFromInput(Book $book, $input)
149 - {
150 - if ($book->name !== $input['name']) {
151 - $book->slug = $this->findSuitableSlug($input['name'], $book->id);
152 - }
153 - $book->fill($input);
154 - $book->updated_by = user()->id;
155 - $book->save();
156 - $this->permissionService->buildJointPermissionsForEntity($book);
157 - return $book;
158 - }
159 -
160 - /**
161 - * Destroy the given book.
162 - * @param Book $book
163 - * @throws \Exception
164 - */
165 - public function destroy(Book $book)
166 - {
167 - foreach ($book->pages as $page) {
168 - $this->pageRepo->destroy($page);
169 - }
170 - foreach ($book->chapters as $chapter) {
171 - $this->chapterRepo->destroy($chapter);
172 - }
173 - $book->views()->delete();
174 - $book->permissions()->delete();
175 - $this->permissionService->deleteJointPermissionsForEntity($book);
176 - $book->delete();
177 - }
178 -
179 - /**
180 - * Get the next child element priority.
181 - * @param Book $book
182 - * @return int
183 - */
184 - public function getNewPriority($book)
185 - {
186 - $lastElem = $this->getChildren($book)->pop();
187 - return $lastElem ? $lastElem->priority + 1 : 0;
188 - }
189 -
190 - /**
191 - * @param string $slug
192 - * @param bool|false $currentId
193 - * @return bool
194 - */
195 - public function doesSlugExist($slug, $currentId = false)
196 - {
197 - $query = $this->book->where('slug', '=', $slug);
198 - if ($currentId) {
199 - $query = $query->where('id', '!=', $currentId);
200 - }
201 - return $query->count() > 0;
202 - }
203 -
204 - /**
205 - * Provides a suitable slug for the given book name.
206 - * Ensures the returned slug is unique in the system.
207 - * @param string $name
208 - * @param bool|false $currentId
209 - * @return string
210 - */
211 - public function findSuitableSlug($name, $currentId = false)
212 - {
213 - $slug = $this->nameToSlug($name);
214 - while ($this->doesSlugExist($slug, $currentId)) {
215 - $slug .= '-' . substr(md5(rand(1, 500)), 0, 3);
216 - }
217 - return $slug;
218 - }
219 -
220 - /**
221 - * Get all child objects of a book.
222 - * Returns a sorted collection of Pages and Chapters.
223 - * Loads the book slug onto child elements to prevent access database access for getting the slug.
224 - * @param Book $book
225 - * @param bool $filterDrafts
226 - * @return mixed
227 - */
228 - public function getChildren(Book $book, $filterDrafts = false)
229 - {
230 - $pageQuery = $book->pages()->where('chapter_id', '=', 0);
231 - $pageQuery = $this->permissionService->enforcePageRestrictions($pageQuery, 'view');
232 -
233 - if ($filterDrafts) {
234 - $pageQuery = $pageQuery->where('draft', '=', false);
235 - }
236 -
237 - $pages = $pageQuery->get();
238 -
239 - $chapterQuery = $book->chapters()->with(['pages' => function ($query) use ($filterDrafts) {
240 - $this->permissionService->enforcePageRestrictions($query, 'view');
241 - if ($filterDrafts) $query->where('draft', '=', false);
242 - }]);
243 - $chapterQuery = $this->permissionService->enforceChapterRestrictions($chapterQuery, 'view');
244 - $chapters = $chapterQuery->get();
245 - $children = $pages->values();
246 - foreach ($chapters as $chapter) {
247 - $children->push($chapter);
248 - }
249 - $bookSlug = $book->slug;
250 -
251 - $children->each(function ($child) use ($bookSlug) {
252 - $child->setAttribute('bookSlug', $bookSlug);
253 - if ($child->isA('chapter')) {
254 - $child->pages->each(function ($page) use ($bookSlug) {
255 - $page->setAttribute('bookSlug', $bookSlug);
256 - });
257 - $child->pages = $child->pages->sortBy(function ($child, $key) {
258 - $score = $child->priority;
259 - if ($child->draft) $score -= 100;
260 - return $score;
261 - });
262 - }
263 - });
264 -
265 - // Sort items with drafts first then by priority.
266 - return $children->sortBy(function ($child, $key) {
267 - $score = $child->priority;
268 - if ($child->isA('page') && $child->draft) $score -= 100;
269 - return $score;
270 - });
271 - }
272 -
273 - /**
274 - * Get books by search term.
275 - * @param $term
276 - * @param int $count
277 - * @param array $paginationAppends
278 - * @return mixed
279 - */
280 - public function getBySearch($term, $count = 20, $paginationAppends = [])
281 - {
282 - $terms = $this->prepareSearchTerms($term);
283 - $bookQuery = $this->permissionService->enforceBookRestrictions($this->book->fullTextSearchQuery(['name', 'description'], $terms));
284 - $bookQuery = $this->addAdvancedSearchQueries($bookQuery, $term);
285 - $books = $bookQuery->paginate($count)->appends($paginationAppends);
286 - $words = join('|', explode(' ', preg_quote(trim($term), '/')));
287 - foreach ($books as $book) {
288 - //highlight
289 - $result = preg_replace('#' . $words . '#iu', "<span class=\"highlight\">\$0</span>", $book->getExcerpt(100));
290 - $book->searchSnippet = $result;
291 - }
292 - return $books;
293 - }
294 -
295 -}
...\ No newline at end of file ...\ No newline at end of file
1 -<?php namespace BookStack\Repos;
2 -
3 -
4 -use Activity;
5 -use BookStack\Book;
6 -use BookStack\Exceptions\NotFoundException;
7 -use Illuminate\Support\Str;
8 -use BookStack\Chapter;
9 -
10 -class ChapterRepo extends EntityRepo
11 -{
12 - protected $pageRepo;
13 -
14 - /**
15 - * ChapterRepo constructor.
16 - * @param $pageRepo
17 - */
18 - public function __construct(PageRepo $pageRepo)
19 - {
20 - $this->pageRepo = $pageRepo;
21 - parent::__construct();
22 - }
23 -
24 - /**
25 - * Base query for getting chapters, Takes permissions into account.
26 - * @return mixed
27 - */
28 - private function chapterQuery()
29 - {
30 - return $this->permissionService->enforceChapterRestrictions($this->chapter, 'view');
31 - }
32 -
33 - /**
34 - * Check if an id exists.
35 - * @param $id
36 - * @return bool
37 - */
38 - public function idExists($id)
39 - {
40 - return $this->chapterQuery()->where('id', '=', $id)->count() > 0;
41 - }
42 -
43 - /**
44 - * Get a chapter by a specific id.
45 - * @param $id
46 - * @return mixed
47 - */
48 - public function getById($id)
49 - {
50 - return $this->chapterQuery()->findOrFail($id);
51 - }
52 -
53 - /**
54 - * Get all chapters.
55 - * @return \Illuminate\Database\Eloquent\Collection|static[]
56 - */
57 - public function getAll()
58 - {
59 - return $this->chapterQuery()->all();
60 - }
61 -
62 - /**
63 - * Get a chapter that has the given slug within the given book.
64 - * @param $slug
65 - * @param $bookId
66 - * @return mixed
67 - * @throws NotFoundException
68 - */
69 - public function getBySlug($slug, $bookId)
70 - {
71 - $chapter = $this->chapterQuery()->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first();
72 - if ($chapter === null) throw new NotFoundException('Chapter not found');
73 - return $chapter;
74 - }
75 -
76 - /**
77 - * Get the child items for a chapter
78 - * @param Chapter $chapter
79 - */
80 - public function getChildren(Chapter $chapter)
81 - {
82 - $pages = $this->permissionService->enforcePageRestrictions($chapter->pages())->get();
83 - // Sort items with drafts first then by priority.
84 - return $pages->sortBy(function ($child, $key) {
85 - $score = $child->priority;
86 - if ($child->draft) $score -= 100;
87 - return $score;
88 - });
89 - }
90 -
91 - /**
92 - * Create a new chapter from request input.
93 - * @param $input
94 - * @param Book $book
95 - * @return Chapter
96 - */
97 - public function createFromInput($input, Book $book)
98 - {
99 - $chapter = $this->chapter->newInstance($input);
100 - $chapter->slug = $this->findSuitableSlug($chapter->name, $book->id);
101 - $chapter->created_by = user()->id;
102 - $chapter->updated_by = user()->id;
103 - $chapter = $book->chapters()->save($chapter);
104 - $this->permissionService->buildJointPermissionsForEntity($chapter);
105 - return $chapter;
106 - }
107 -
108 - /**
109 - * Destroy a chapter and its relations by providing its slug.
110 - * @param Chapter $chapter
111 - */
112 - public function destroy(Chapter $chapter)
113 - {
114 - if (count($chapter->pages) > 0) {
115 - foreach ($chapter->pages as $page) {
116 - $page->chapter_id = 0;
117 - $page->save();
118 - }
119 - }
120 - Activity::removeEntity($chapter);
121 - $chapter->views()->delete();
122 - $chapter->permissions()->delete();
123 - $this->permissionService->deleteJointPermissionsForEntity($chapter);
124 - $chapter->delete();
125 - }
126 -
127 - /**
128 - * Check if a chapter's slug exists.
129 - * @param $slug
130 - * @param $bookId
131 - * @param bool|false $currentId
132 - * @return bool
133 - */
134 - public function doesSlugExist($slug, $bookId, $currentId = false)
135 - {
136 - $query = $this->chapter->where('slug', '=', $slug)->where('book_id', '=', $bookId);
137 - if ($currentId) {
138 - $query = $query->where('id', '!=', $currentId);
139 - }
140 - return $query->count() > 0;
141 - }
142 -
143 - /**
144 - * Finds a suitable slug for the provided name.
145 - * Checks database to prevent duplicate slugs.
146 - * @param $name
147 - * @param $bookId
148 - * @param bool|false $currentId
149 - * @return string
150 - */
151 - public function findSuitableSlug($name, $bookId, $currentId = false)
152 - {
153 - $slug = $this->nameToSlug($name);
154 - while ($this->doesSlugExist($slug, $bookId, $currentId)) {
155 - $slug .= '-' . substr(md5(rand(1, 500)), 0, 3);
156 - }
157 - return $slug;
158 - }
159 -
160 - /**
161 - * Get a new priority value for a new page to be added
162 - * to the given chapter.
163 - * @param Chapter $chapter
164 - * @return int
165 - */
166 - public function getNewPriority(Chapter $chapter)
167 - {
168 - $lastPage = $chapter->pages->last();
169 - return $lastPage !== null ? $lastPage->priority + 1 : 0;
170 - }
171 -
172 - /**
173 - * Get chapters by the given search term.
174 - * @param string $term
175 - * @param array $whereTerms
176 - * @param int $count
177 - * @param array $paginationAppends
178 - * @return mixed
179 - */
180 - public function getBySearch($term, $whereTerms = [], $count = 20, $paginationAppends = [])
181 - {
182 - $terms = $this->prepareSearchTerms($term);
183 - $chapterQuery = $this->permissionService->enforceChapterRestrictions($this->chapter->fullTextSearchQuery(['name', 'description'], $terms, $whereTerms));
184 - $chapterQuery = $this->addAdvancedSearchQueries($chapterQuery, $term);
185 - $chapters = $chapterQuery->paginate($count)->appends($paginationAppends);
186 - $words = join('|', explode(' ', preg_quote(trim($term), '/')));
187 - foreach ($chapters as $chapter) {
188 - //highlight
189 - $result = preg_replace('#' . $words . '#iu', "<span class=\"highlight\">\$0</span>", $chapter->getExcerpt(100));
190 - $chapter->searchSnippet = $result;
191 - }
192 - return $chapters;
193 - }
194 -
195 - /**
196 - * Changes the book relation of this chapter.
197 - * @param $bookId
198 - * @param Chapter $chapter
199 - * @param bool $rebuildPermissions
200 - * @return Chapter
201 - */
202 - public function changeBook($bookId, Chapter $chapter, $rebuildPermissions = false)
203 - {
204 - $chapter->book_id = $bookId;
205 - // Update related activity
206 - foreach ($chapter->activity as $activity) {
207 - $activity->book_id = $bookId;
208 - $activity->save();
209 - }
210 - $chapter->slug = $this->findSuitableSlug($chapter->name, $bookId, $chapter->id);
211 - $chapter->save();
212 - // Update all child pages
213 - foreach ($chapter->pages as $page) {
214 - $this->pageRepo->changeBook($bookId, $page);
215 - }
216 -
217 - // Update permissions if applicable
218 - if ($rebuildPermissions) {
219 - $chapter->load('book');
220 - $this->permissionService->buildJointPermissionsForEntity($chapter->book);
221 - }
222 -
223 - return $chapter;
224 - }
225 -
226 -}
...\ No newline at end of file ...\ No newline at end of file
...@@ -3,11 +3,16 @@ ...@@ -3,11 +3,16 @@
3 use BookStack\Book; 3 use BookStack\Book;
4 use BookStack\Chapter; 4 use BookStack\Chapter;
5 use BookStack\Entity; 5 use BookStack\Entity;
6 +use BookStack\Exceptions\NotFoundException;
6 use BookStack\Page; 7 use BookStack\Page;
8 +use BookStack\PageRevision;
9 +use BookStack\Services\AttachmentService;
7 use BookStack\Services\PermissionService; 10 use BookStack\Services\PermissionService;
8 -use BookStack\User; 11 +use BookStack\Services\ViewService;
12 +use Carbon\Carbon;
13 +use DOMDocument;
14 +use DOMXPath;
9 use Illuminate\Support\Collection; 15 use Illuminate\Support\Collection;
10 -use Illuminate\Support\Facades\Log;
11 16
12 class EntityRepo 17 class EntityRepo
13 { 18 {
...@@ -28,11 +33,32 @@ class EntityRepo ...@@ -28,11 +33,32 @@ class EntityRepo
28 public $page; 33 public $page;
29 34
30 /** 35 /**
36 + * @var PageRevision
37 + */
38 + protected $pageRevision;
39 +
40 + /**
41 + * Base entity instances keyed by type
42 + * @var []Entity
43 + */
44 + protected $entities;
45 +
46 + /**
31 * @var PermissionService 47 * @var PermissionService
32 */ 48 */
33 protected $permissionService; 49 protected $permissionService;
34 50
35 /** 51 /**
52 + * @var ViewService
53 + */
54 + protected $viewService;
55 +
56 + /**
57 + * @var TagRepo
58 + */
59 + protected $tagRepo;
60 +
61 + /**
36 * Acceptable operators to be used in a query 62 * Acceptable operators to be used in a query
37 * @var array 63 * @var array
38 */ 64 */
...@@ -40,72 +66,181 @@ class EntityRepo ...@@ -40,72 +66,181 @@ class EntityRepo
40 66
41 /** 67 /**
42 * EntityService constructor. 68 * EntityService constructor.
69 + * @param Book $book
70 + * @param Chapter $chapter
71 + * @param Page $page
72 + * @param PageRevision $pageRevision
73 + * @param ViewService $viewService
74 + * @param PermissionService $permissionService
75 + * @param TagRepo $tagRepo
43 */ 76 */
44 - public function __construct() 77 + public function __construct(
78 + Book $book, Chapter $chapter, Page $page, PageRevision $pageRevision,
79 + ViewService $viewService, PermissionService $permissionService, TagRepo $tagRepo
80 + )
45 { 81 {
46 - $this->book = app(Book::class); 82 + $this->book = $book;
47 - $this->chapter = app(Chapter::class); 83 + $this->chapter = $chapter;
48 - $this->page = app(Page::class); 84 + $this->page = $page;
49 - $this->permissionService = app(PermissionService::class); 85 + $this->pageRevision = $pageRevision;
86 + $this->entities = [
87 + 'page' => $this->page,
88 + 'chapter' => $this->chapter,
89 + 'book' => $this->book,
90 + 'page_revision' => $this->pageRevision
91 + ];
92 + $this->viewService = $viewService;
93 + $this->permissionService = $permissionService;
94 + $this->tagRepo = $tagRepo;
50 } 95 }
51 96
52 /** 97 /**
53 - * Get the latest books added to the system. 98 + * Get an entity instance via type.
54 - * @param int $count 99 + * @param $type
55 - * @param int $page 100 + * @return Entity
56 - * @param bool $additionalQuery
57 - * @return
58 */ 101 */
59 - public function getRecentlyCreatedBooks($count = 20, $page = 0, $additionalQuery = false) 102 + protected function getEntity($type)
60 { 103 {
61 - $query = $this->permissionService->enforceBookRestrictions($this->book) 104 + return $this->entities[strtolower($type)];
62 - ->orderBy('created_at', 'desc');
63 - if ($additionalQuery !== false && is_callable($additionalQuery)) {
64 - $additionalQuery($query);
65 } 105 }
66 - return $query->skip($page * $count)->take($count)->get(); 106 +
107 + /**
108 + * Base query for searching entities via permission system
109 + * @param string $type
110 + * @param bool $allowDrafts
111 + * @return \Illuminate\Database\Query\Builder
112 + */
113 + protected function entityQuery($type, $allowDrafts = false)
114 + {
115 + $q = $this->permissionService->enforceEntityRestrictions($type, $this->getEntity($type), 'view');
116 + if (strtolower($type) === 'page' && !$allowDrafts) {
117 + $q = $q->where('draft', '=', false);
118 + }
119 + return $q;
67 } 120 }
68 121
69 /** 122 /**
70 - * Get the most recently updated books. 123 + * Check if an entity with the given id exists.
71 - * @param $count 124 + * @param $type
72 - * @param int $page 125 + * @param $id
73 - * @return mixed 126 + * @return bool
127 + */
128 + public function exists($type, $id)
129 + {
130 + return $this->entityQuery($type)->where('id', '=', $id)->exists();
131 + }
132 +
133 + /**
134 + * Get an entity by ID
135 + * @param string $type
136 + * @param integer $id
137 + * @param bool $allowDrafts
138 + * @return Entity
139 + */
140 + public function getById($type, $id, $allowDrafts = false)
141 + {
142 + return $this->entityQuery($type, $allowDrafts)->find($id);
143 + }
144 +
145 + /**
146 + * Get an entity by its url slug.
147 + * @param string $type
148 + * @param string $slug
149 + * @param string|bool $bookSlug
150 + * @return Entity
151 + * @throws NotFoundException
152 + */
153 + public function getBySlug($type, $slug, $bookSlug = false)
154 + {
155 + $q = $this->entityQuery($type)->where('slug', '=', $slug);
156 +
157 + if (strtolower($type) === 'chapter' || strtolower($type) === 'page') {
158 + $q = $q->where('book_id', '=', function($query) use ($bookSlug) {
159 + $query->select('id')
160 + ->from($this->book->getTable())
161 + ->where('slug', '=', $bookSlug)->limit(1);
162 + });
163 + }
164 + $entity = $q->first();
165 + if ($entity === null) throw new NotFoundException(trans('errors.' . strtolower($type) . '_not_found'));
166 + return $entity;
167 + }
168 +
169 +
170 + /**
171 + * Search through page revisions and retrieve the last page in the
172 + * current book that has a slug equal to the one given.
173 + * @param string $pageSlug
174 + * @param string $bookSlug
175 + * @return null|Page
74 */ 176 */
75 - public function getRecentlyUpdatedBooks($count = 20, $page = 0) 177 + public function getPageByOldSlug($pageSlug, $bookSlug)
76 { 178 {
77 - return $this->permissionService->enforceBookRestrictions($this->book) 179 + $revision = $this->pageRevision->where('slug', '=', $pageSlug)
78 - ->orderBy('updated_at', 'desc')->skip($page * $count)->take($count)->get(); 180 + ->whereHas('page', function ($query) {
181 + $this->permissionService->enforceEntityRestrictions('page', $query);
182 + })
183 + ->where('type', '=', 'version')
184 + ->where('book_slug', '=', $bookSlug)
185 + ->orderBy('created_at', 'desc')
186 + ->with('page')->first();
187 + return $revision !== null ? $revision->page : null;
79 } 188 }
80 189
81 /** 190 /**
82 - * Get the latest pages added to the system. 191 + * Get all entities of a type limited by count unless count if false.
192 + * @param string $type
193 + * @param integer|bool $count
194 + * @return Collection
195 + */
196 + public function getAll($type, $count = 20)
197 + {
198 + $q = $this->entityQuery($type)->orderBy('name', 'asc');
199 + if ($count !== false) $q = $q->take($count);
200 + return $q->get();
201 + }
202 +
203 + /**
204 + * Get all entities in a paginated format
205 + * @param $type
206 + * @param int $count
207 + * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
208 + */
209 + public function getAllPaginated($type, $count = 10)
210 + {
211 + return $this->entityQuery($type)->orderBy('name', 'asc')->paginate($count);
212 + }
213 +
214 + /**
215 + * Get the most recently created entities of the given type.
216 + * @param string $type
83 * @param int $count 217 * @param int $count
84 * @param int $page 218 * @param int $page
85 - * @param bool $additionalQuery 219 + * @param bool|callable $additionalQuery
86 - * @return
87 */ 220 */
88 - public function getRecentlyCreatedPages($count = 20, $page = 0, $additionalQuery = false) 221 + public function getRecentlyCreated($type, $count = 20, $page = 0, $additionalQuery = false)
89 { 222 {
90 - $query = $this->permissionService->enforcePageRestrictions($this->page) 223 + $query = $this->permissionService->enforceEntityRestrictions($type, $this->getEntity($type))
91 - ->orderBy('created_at', 'desc')->where('draft', '=', false); 224 + ->orderBy('created_at', 'desc');
225 + if (strtolower($type) === 'page') $query = $query->where('draft', '=', false);
92 if ($additionalQuery !== false && is_callable($additionalQuery)) { 226 if ($additionalQuery !== false && is_callable($additionalQuery)) {
93 $additionalQuery($query); 227 $additionalQuery($query);
94 } 228 }
95 - return $query->with('book')->skip($page * $count)->take($count)->get(); 229 + return $query->skip($page * $count)->take($count)->get();
96 } 230 }
97 231
98 /** 232 /**
99 - * Get the latest chapters added to the system. 233 + * Get the most recently updated entities of the given type.
234 + * @param string $type
100 * @param int $count 235 * @param int $count
101 * @param int $page 236 * @param int $page
102 - * @param bool $additionalQuery 237 + * @param bool|callable $additionalQuery
103 - * @return
104 */ 238 */
105 - public function getRecentlyCreatedChapters($count = 20, $page = 0, $additionalQuery = false) 239 + public function getRecentlyUpdated($type, $count = 20, $page = 0, $additionalQuery = false)
106 { 240 {
107 - $query = $this->permissionService->enforceChapterRestrictions($this->chapter) 241 + $query = $this->permissionService->enforceEntityRestrictions($type, $this->getEntity($type))
108 - ->orderBy('created_at', 'desc'); 242 + ->orderBy('updated_at', 'desc');
243 + if (strtolower($type) === 'page') $query = $query->where('draft', '=', false);
109 if ($additionalQuery !== false && is_callable($additionalQuery)) { 244 if ($additionalQuery !== false && is_callable($additionalQuery)) {
110 $additionalQuery($query); 245 $additionalQuery($query);
111 } 246 }
...@@ -113,16 +248,51 @@ class EntityRepo ...@@ -113,16 +248,51 @@ class EntityRepo
113 } 248 }
114 249
115 /** 250 /**
116 - * Get the most recently updated pages. 251 + * Get the most recently viewed entities.
117 - * @param $count 252 + * @param string|bool $type
253 + * @param int $count
254 + * @param int $page
255 + * @return mixed
256 + */
257 + public function getRecentlyViewed($type, $count = 10, $page = 0)
258 + {
259 + $filter = is_bool($type) ? false : $this->getEntity($type);
260 + return $this->viewService->getUserRecentlyViewed($count, $page, $filter);
261 + }
262 +
263 + /**
264 + * Get the latest pages added to the system with pagination.
265 + * @param string $type
266 + * @param int $count
267 + * @return mixed
268 + */
269 + public function getRecentlyCreatedPaginated($type, $count = 20)
270 + {
271 + return $this->entityQuery($type)->orderBy('created_at', 'desc')->paginate($count);
272 + }
273 +
274 + /**
275 + * Get the latest pages added to the system with pagination.
276 + * @param string $type
277 + * @param int $count
278 + * @return mixed
279 + */
280 + public function getRecentlyUpdatedPaginated($type, $count = 20)
281 + {
282 + return $this->entityQuery($type)->orderBy('updated_at', 'desc')->paginate($count);
283 + }
284 +
285 + /**
286 + * Get the most popular entities base on all views.
287 + * @param string|bool $type
288 + * @param int $count
118 * @param int $page 289 * @param int $page
119 * @return mixed 290 * @return mixed
120 */ 291 */
121 - public function getRecentlyUpdatedPages($count = 20, $page = 0) 292 + public function getPopular($type, $count = 10, $page = 0)
122 { 293 {
123 - return $this->permissionService->enforcePageRestrictions($this->page) 294 + $filter = is_bool($type) ? false : $this->getEntity($type);
124 - ->where('draft', '=', false) 295 + return $this->viewService->getPopular($count, $page, $filter);
125 - ->orderBy('updated_at', 'desc')->with('book')->skip($page * $count)->take($count)->get();
126 } 296 }
127 297
128 /** 298 /**
...@@ -139,6 +309,163 @@ class EntityRepo ...@@ -139,6 +309,163 @@ class EntityRepo
139 } 309 }
140 310
141 /** 311 /**
312 + * Get all child objects of a book.
313 + * Returns a sorted collection of Pages and Chapters.
314 + * Loads the book slug onto child elements to prevent access database access for getting the slug.
315 + * @param Book $book
316 + * @param bool $filterDrafts
317 + * @return mixed
318 + */
319 + public function getBookChildren(Book $book, $filterDrafts = false)
320 + {
321 + $q = $this->permissionService->bookChildrenQuery($book->id, $filterDrafts)->get();
322 + $entities = [];
323 + $parents = [];
324 + $tree = [];
325 +
326 + foreach ($q as $index => $rawEntity) {
327 + if ($rawEntity->entity_type === 'BookStack\\Page') {
328 + $entities[$index] = $this->page->newFromBuilder($rawEntity);
329 + } else if ($rawEntity->entity_type === 'BookStack\\Chapter') {
330 + $entities[$index] = $this->chapter->newFromBuilder($rawEntity);
331 + $key = $entities[$index]->entity_type . ':' . $entities[$index]->id;
332 + $parents[$key] = $entities[$index];
333 + $parents[$key]->setAttribute('pages', collect());
334 + }
335 + if ($entities[$index]->chapter_id === 0) $tree[] = $entities[$index];
336 + $entities[$index]->book = $book;
337 + }
338 +
339 + foreach ($entities as $entity) {
340 + if ($entity->chapter_id === 0) continue;
341 + $parentKey = 'BookStack\\Chapter:' . $entity->chapter_id;
342 + $chapter = $parents[$parentKey];
343 + $chapter->pages->push($entity);
344 + }
345 +
346 + return collect($tree);
347 + }
348 +
349 + /**
350 + * Get the child items for a chapter sorted by priority but
351 + * with draft items floated to the top.
352 + * @param Chapter $chapter
353 + */
354 + public function getChapterChildren(Chapter $chapter)
355 + {
356 + return $this->permissionService->enforceEntityRestrictions('page', $chapter->pages())
357 + ->orderBy('draft', 'DESC')->orderBy('priority', 'ASC')->get();
358 + }
359 +
360 + /**
361 + * Search entities of a type via a given query.
362 + * @param string $type
363 + * @param string $term
364 + * @param array $whereTerms
365 + * @param int $count
366 + * @param array $paginationAppends
367 + * @return mixed
368 + */
369 + public function getBySearch($type, $term, $whereTerms = [], $count = 20, $paginationAppends = [])
370 + {
371 + $terms = $this->prepareSearchTerms($term);
372 + $q = $this->permissionService->enforceEntityRestrictions($type, $this->getEntity($type)->fullTextSearchQuery($terms, $whereTerms));
373 + $q = $this->addAdvancedSearchQueries($q, $term);
374 + $entities = $q->paginate($count)->appends($paginationAppends);
375 + $words = join('|', explode(' ', preg_quote(trim($term), '/')));
376 +
377 + // Highlight page content
378 + if ($type === 'page') {
379 + //lookahead/behind assertions ensures cut between words
380 + $s = '\s\x00-/:-@\[-`{-~'; //character set for start/end of words
381 +
382 + foreach ($entities as $page) {
383 + preg_match_all('#(?<=[' . $s . ']).{1,30}((' . $words . ').{1,30})+(?=[' . $s . '])#uis', $page->text, $matches, PREG_SET_ORDER);
384 + //delimiter between occurrences
385 + $results = [];
386 + foreach ($matches as $line) {
387 + $results[] = htmlspecialchars($line[0], 0, 'UTF-8');
388 + }
389 + $matchLimit = 6;
390 + if (count($results) > $matchLimit) $results = array_slice($results, 0, $matchLimit);
391 + $result = join('... ', $results);
392 +
393 + //highlight
394 + $result = preg_replace('#' . $words . '#iu', "<span class=\"highlight\">\$0</span>", $result);
395 + if (strlen($result) < 5) $result = $page->getExcerpt(80);
396 +
397 + $page->searchSnippet = $result;
398 + }
399 + return $entities;
400 + }
401 +
402 + // Highlight chapter/book content
403 + foreach ($entities as $entity) {
404 + //highlight
405 + $result = preg_replace('#' . $words . '#iu', "<span class=\"highlight\">\$0</span>", $entity->getExcerpt(100));
406 + $entity->searchSnippet = $result;
407 + }
408 + return $entities;
409 + }
410 +
411 + /**
412 + * Get the next sequential priority for a new child element in the given book.
413 + * @param Book $book
414 + * @return int
415 + */
416 + public function getNewBookPriority(Book $book)
417 + {
418 + $lastElem = $this->getBookChildren($book)->pop();
419 + return $lastElem ? $lastElem->priority + 1 : 0;
420 + }
421 +
422 + /**
423 + * Get a new priority for a new page to be added to the given chapter.
424 + * @param Chapter $chapter
425 + * @return int
426 + */
427 + public function getNewChapterPriority(Chapter $chapter)
428 + {
429 + $lastPage = $chapter->pages('DESC')->first();
430 + return $lastPage !== null ? $lastPage->priority + 1 : 0;
431 + }
432 +
433 + /**
434 + * Find a suitable slug for an entity.
435 + * @param string $type
436 + * @param string $name
437 + * @param bool|integer $currentId
438 + * @param bool|integer $bookId Only pass if type is not a book
439 + * @return string
440 + */
441 + public function findSuitableSlug($type, $name, $currentId = false, $bookId = false)
442 + {
443 + $slug = $this->nameToSlug($name);
444 + while ($this->slugExists($type, $slug, $currentId, $bookId)) {
445 + $slug .= '-' . substr(md5(rand(1, 500)), 0, 3);
446 + }
447 + return $slug;
448 + }
449 +
450 + /**
451 + * Check if a slug already exists in the database.
452 + * @param string $type
453 + * @param string $slug
454 + * @param bool|integer $currentId
455 + * @param bool|integer $bookId
456 + * @return bool
457 + */
458 + protected function slugExists($type, $slug, $currentId = false, $bookId = false)
459 + {
460 + $query = $this->getEntity($type)->where('slug', '=', $slug);
461 + if (strtolower($type) === 'page' || strtolower($type) === 'chapter') {
462 + $query = $query->where('book_id', '=', $bookId);
463 + }
464 + if ($currentId) $query = $query->where('id', '!=', $currentId);
465 + return $query->count() > 0;
466 + }
467 +
468 + /**
142 * Updates entity restrictions from a request 469 * Updates entity restrictions from a request
143 * @param $request 470 * @param $request
144 * @param Entity $entity 471 * @param Entity $entity
...@@ -261,6 +588,81 @@ class EntityRepo ...@@ -261,6 +588,81 @@ class EntityRepo
261 } 588 }
262 589
263 /** 590 /**
591 + * Create a new entity from request input.
592 + * Used for books and chapters.
593 + * @param string $type
594 + * @param array $input
595 + * @param bool|Book $book
596 + * @return Entity
597 + */
598 + public function createFromInput($type, $input = [], $book = false)
599 + {
600 + $isChapter = strtolower($type) === 'chapter';
601 + $entity = $this->getEntity($type)->newInstance($input);
602 + $entity->slug = $this->findSuitableSlug($type, $entity->name, false, $isChapter ? $book->id : false);
603 + $entity->created_by = user()->id;
604 + $entity->updated_by = user()->id;
605 + $isChapter ? $book->chapters()->save($entity) : $entity->save();
606 + $this->permissionService->buildJointPermissionsForEntity($entity);
607 + return $entity;
608 + }
609 +
610 + /**
611 + * Update entity details from request input.
612 + * Use for books and chapters
613 + * @param string $type
614 + * @param Entity $entityModel
615 + * @param array $input
616 + * @return Entity
617 + */
618 + public function updateFromInput($type, Entity $entityModel, $input = [])
619 + {
620 + if ($entityModel->name !== $input['name']) {
621 + $entityModel->slug = $this->findSuitableSlug($type, $input['name'], $entityModel->id);
622 + }
623 + $entityModel->fill($input);
624 + $entityModel->updated_by = user()->id;
625 + $entityModel->save();
626 + $this->permissionService->buildJointPermissionsForEntity($entityModel);
627 + return $entityModel;
628 + }
629 +
630 + /**
631 + * Change the book that an entity belongs to.
632 + * @param string $type
633 + * @param integer $newBookId
634 + * @param Entity $entity
635 + * @param bool $rebuildPermissions
636 + * @return Entity
637 + */
638 + public function changeBook($type, $newBookId, Entity $entity, $rebuildPermissions = false)
639 + {
640 + $entity->book_id = $newBookId;
641 + // Update related activity
642 + foreach ($entity->activity as $activity) {
643 + $activity->book_id = $newBookId;
644 + $activity->save();
645 + }
646 + $entity->slug = $this->findSuitableSlug($type, $entity->name, $entity->id, $newBookId);
647 + $entity->save();
648 +
649 + // Update all child pages if a chapter
650 + if (strtolower($type) === 'chapter') {
651 + foreach ($entity->pages as $page) {
652 + $this->changeBook('page', $newBookId, $page, false);
653 + }
654 + }
655 +
656 + // Update permissions if applicable
657 + if ($rebuildPermissions) {
658 + $entity->load('book');
659 + $this->permissionService->buildJointPermissionsForEntity($entity->book);
660 + }
661 +
662 + return $entity;
663 + }
664 +
665 + /**
264 * Alias method to update the book jointPermissions in the PermissionService. 666 * Alias method to update the book jointPermissions in the PermissionService.
265 * @param Collection $collection collection on entities 667 * @param Collection $collection collection on entities
266 */ 668 */
...@@ -282,6 +684,518 @@ class EntityRepo ...@@ -282,6 +684,518 @@ class EntityRepo
282 return $slug; 684 return $slug;
283 } 685 }
284 686
687 + /**
688 + * Publish a draft page to make it a normal page.
689 + * Sets the slug and updates the content.
690 + * @param Page $draftPage
691 + * @param array $input
692 + * @return Page
693 + */
694 + public function publishPageDraft(Page $draftPage, array $input)
695 + {
696 + $draftPage->fill($input);
697 +
698 + // Save page tags if present
699 + if (isset($input['tags'])) {
700 + $this->tagRepo->saveTagsToEntity($draftPage, $input['tags']);
701 + }
702 +
703 + $draftPage->slug = $this->findSuitableSlug('page', $draftPage->name, false, $draftPage->book->id);
704 + $draftPage->html = $this->formatHtml($input['html']);
705 + $draftPage->text = strip_tags($draftPage->html);
706 + $draftPage->draft = false;
707 +
708 + $draftPage->save();
709 + $this->savePageRevision($draftPage, trans('entities.pages_initial_revision'));
710 +
711 + return $draftPage;
712 + }
713 +
714 + /**
715 + * Saves a page revision into the system.
716 + * @param Page $page
717 + * @param null|string $summary
718 + * @return PageRevision
719 + */
720 + public function savePageRevision(Page $page, $summary = null)
721 + {
722 + $revision = $this->pageRevision->newInstance($page->toArray());
723 + if (setting('app-editor') !== 'markdown') $revision->markdown = '';
724 + $revision->page_id = $page->id;
725 + $revision->slug = $page->slug;
726 + $revision->book_slug = $page->book->slug;
727 + $revision->created_by = user()->id;
728 + $revision->created_at = $page->updated_at;
729 + $revision->type = 'version';
730 + $revision->summary = $summary;
731 + $revision->save();
732 +
733 + // Clear old revisions
734 + if ($this->pageRevision->where('page_id', '=', $page->id)->count() > 50) {
735 + $this->pageRevision->where('page_id', '=', $page->id)
736 + ->orderBy('created_at', 'desc')->skip(50)->take(5)->delete();
737 + }
738 +
739 + return $revision;
740 + }
741 +
742 + /**
743 + * Formats a page's html to be tagged correctly
744 + * within the system.
745 + * @param string $htmlText
746 + * @return string
747 + */
748 + protected function formatHtml($htmlText)
749 + {
750 + if ($htmlText == '') return $htmlText;
751 + libxml_use_internal_errors(true);
752 + $doc = new DOMDocument();
753 + $doc->loadHTML(mb_convert_encoding($htmlText, 'HTML-ENTITIES', 'UTF-8'));
754 +
755 + $container = $doc->documentElement;
756 + $body = $container->childNodes->item(0);
757 + $childNodes = $body->childNodes;
758 +
759 + // Ensure no duplicate ids are used
760 + $idArray = [];
761 +
762 + foreach ($childNodes as $index => $childNode) {
763 + /** @var \DOMElement $childNode */
764 + if (get_class($childNode) !== 'DOMElement') continue;
765 +
766 + // Overwrite id if not a BookStack custom id
767 + if ($childNode->hasAttribute('id')) {
768 + $id = $childNode->getAttribute('id');
769 + if (strpos($id, 'bkmrk') === 0 && array_search($id, $idArray) === false) {
770 + $idArray[] = $id;
771 + continue;
772 + };
773 + }
774 +
775 + // Create an unique id for the element
776 + // Uses the content as a basis to ensure output is the same every time
777 + // the same content is passed through.
778 + $contentId = 'bkmrk-' . substr(strtolower(preg_replace('/\s+/', '-', trim($childNode->nodeValue))), 0, 20);
779 + $newId = urlencode($contentId);
780 + $loopIndex = 0;
781 + while (in_array($newId, $idArray)) {
782 + $newId = urlencode($contentId . '-' . $loopIndex);
783 + $loopIndex++;
784 + }
785 +
786 + $childNode->setAttribute('id', $newId);
787 + $idArray[] = $newId;
788 + }
789 +
790 + // Generate inner html as a string
791 + $html = '';
792 + foreach ($childNodes as $childNode) {
793 + $html .= $doc->saveHTML($childNode);
794 + }
795 +
796 + return $html;
797 + }
798 +
799 +
800 + /**
801 + * Render the page for viewing, Parsing and performing features such as page transclusion.
802 + * @param Page $page
803 + * @return mixed|string
804 + */
805 + public function renderPage(Page $page)
806 + {
807 + $content = $page->html;
808 + $matches = [];
809 + preg_match_all("/{{@\s?([0-9].*?)}}/", $content, $matches);
810 + if (count($matches[0]) === 0) return $content;
811 +
812 + foreach ($matches[1] as $index => $includeId) {
813 + $splitInclude = explode('#', $includeId, 2);
814 + $pageId = intval($splitInclude[0]);
815 + if (is_nan($pageId)) continue;
816 +
817 + $page = $this->getById('page', $pageId);
818 + if ($page === null) {
819 + $content = str_replace($matches[0][$index], '', $content);
820 + continue;
821 + }
822 +
823 + if (count($splitInclude) === 1) {
824 + $content = str_replace($matches[0][$index], $page->html, $content);
825 + continue;
826 + }
827 +
828 + $doc = new DOMDocument();
829 + $doc->loadHTML(mb_convert_encoding('<body>'.$page->html.'</body>', 'HTML-ENTITIES', 'UTF-8'));
830 + $matchingElem = $doc->getElementById($splitInclude[1]);
831 + if ($matchingElem === null) {
832 + $content = str_replace($matches[0][$index], '', $content);
833 + continue;
834 + }
835 + $innerContent = '';
836 + foreach ($matchingElem->childNodes as $childNode) {
837 + $innerContent .= $doc->saveHTML($childNode);
838 + }
839 + $content = str_replace($matches[0][$index], trim($innerContent), $content);
840 + }
841 +
842 + return $content;
843 + }
844 +
845 + /**
846 + * Get a new draft page instance.
847 + * @param Book $book
848 + * @param Chapter|bool $chapter
849 + * @return Page
850 + */
851 + public function getDraftPage(Book $book, $chapter = false)
852 + {
853 + $page = $this->page->newInstance();
854 + $page->name = trans('entities.pages_initial_name');
855 + $page->created_by = user()->id;
856 + $page->updated_by = user()->id;
857 + $page->draft = true;
858 +
859 + if ($chapter) $page->chapter_id = $chapter->id;
860 +
861 + $book->pages()->save($page);
862 + $this->permissionService->buildJointPermissionsForEntity($page);
863 + return $page;
864 + }
865 +
866 + /**
867 + * Search for image usage within page content.
868 + * @param $imageString
869 + * @return mixed
870 + */
871 + public function searchForImage($imageString)
872 + {
873 + $pages = $this->entityQuery('page')->where('html', 'like', '%' . $imageString . '%')->get();
874 + foreach ($pages as $page) {
875 + $page->url = $page->getUrl();
876 + $page->html = '';
877 + $page->text = '';
878 + }
879 + return count($pages) > 0 ? $pages : false;
880 + }
881 +
882 + /**
883 + * Parse the headers on the page to get a navigation menu
884 + * @param String $pageContent
885 + * @return array
886 + */
887 + public function getPageNav($pageContent)
888 + {
889 + if ($pageContent == '') return [];
890 + libxml_use_internal_errors(true);
891 + $doc = new DOMDocument();
892 + $doc->loadHTML(mb_convert_encoding($pageContent, 'HTML-ENTITIES', 'UTF-8'));
893 + $xPath = new DOMXPath($doc);
894 + $headers = $xPath->query("//h1|//h2|//h3|//h4|//h5|//h6");
895 +
896 + if (is_null($headers)) return [];
897 +
898 + $tree = collect([]);
899 + foreach ($headers as $header) {
900 + $text = $header->nodeValue;
901 + $tree->push([
902 + 'nodeName' => strtolower($header->nodeName),
903 + 'level' => intval(str_replace('h', '', $header->nodeName)),
904 + 'link' => '#' . $header->getAttribute('id'),
905 + 'text' => strlen($text) > 30 ? substr($text, 0, 27) . '...' : $text
906 + ]);
907 + }
908 +
909 + // Normalise headers if only smaller headers have been used
910 + if (count($tree) > 0) {
911 + $minLevel = $tree->pluck('level')->min();
912 + $tree = $tree->map(function($header) use ($minLevel) {
913 + $header['level'] -= ($minLevel - 2);
914 + return $header;
915 + });
916 + }
917 + return $tree->toArray();
918 + }
919 +
920 + /**
921 + * Updates a page with any fillable data and saves it into the database.
922 + * @param Page $page
923 + * @param int $book_id
924 + * @param array $input
925 + * @return Page
926 + */
927 + public function updatePage(Page $page, $book_id, $input)
928 + {
929 + // Hold the old details to compare later
930 + $oldHtml = $page->html;
931 + $oldName = $page->name;
932 +
933 + // Prevent slug being updated if no name change
934 + if ($page->name !== $input['name']) {
935 + $page->slug = $this->findSuitableSlug('page', $input['name'], $page->id, $book_id);
936 + }
937 +
938 + // Save page tags if present
939 + if (isset($input['tags'])) {
940 + $this->tagRepo->saveTagsToEntity($page, $input['tags']);
941 + }
942 +
943 + // Update with new details
944 + $userId = user()->id;
945 + $page->fill($input);
946 + $page->html = $this->formatHtml($input['html']);
947 + $page->text = strip_tags($page->html);
948 + if (setting('app-editor') !== 'markdown') $page->markdown = '';
949 + $page->updated_by = $userId;
950 + $page->save();
951 +
952 + // Remove all update drafts for this user & page.
953 + $this->userUpdatePageDraftsQuery($page, $userId)->delete();
954 +
955 + // Save a revision after updating
956 + if ($oldHtml !== $input['html'] || $oldName !== $input['name'] || $input['summary'] !== null) {
957 + $this->savePageRevision($page, $input['summary']);
958 + }
959 +
960 + return $page;
961 + }
962 +
963 + /**
964 + * The base query for getting user update drafts.
965 + * @param Page $page
966 + * @param $userId
967 + * @return mixed
968 + */
969 + protected function userUpdatePageDraftsQuery(Page $page, $userId)
970 + {
971 + return $this->pageRevision->where('created_by', '=', $userId)
972 + ->where('type', 'update_draft')
973 + ->where('page_id', '=', $page->id)
974 + ->orderBy('created_at', 'desc');
975 + }
976 +
977 + /**
978 + * Checks whether a user has a draft version of a particular page or not.
979 + * @param Page $page
980 + * @param $userId
981 + * @return bool
982 + */
983 + public function hasUserGotPageDraft(Page $page, $userId)
984 + {
985 + return $this->userUpdatePageDraftsQuery($page, $userId)->count() > 0;
986 + }
987 +
988 + /**
989 + * Get the latest updated draft revision for a particular page and user.
990 + * @param Page $page
991 + * @param $userId
992 + * @return mixed
993 + */
994 + public function getUserPageDraft(Page $page, $userId)
995 + {
996 + return $this->userUpdatePageDraftsQuery($page, $userId)->first();
997 + }
998 +
999 + /**
1000 + * Get the notification message that informs the user that they are editing a draft page.
1001 + * @param PageRevision $draft
1002 + * @return string
1003 + */
1004 + public function getUserPageDraftMessage(PageRevision $draft)
1005 + {
1006 + $message = trans('entities.pages_editing_draft_notification', ['timeDiff' => $draft->updated_at->diffForHumans()]);
1007 + if ($draft->page->updated_at->timestamp <= $draft->updated_at->timestamp) return $message;
1008 + return $message . "\n" . trans('entities.pages_draft_edited_notification');
1009 + }
1010 +
1011 + /**
1012 + * Check if a page is being actively editing.
1013 + * Checks for edits since last page updated.
1014 + * Passing in a minuted range will check for edits
1015 + * within the last x minutes.
1016 + * @param Page $page
1017 + * @param null $minRange
1018 + * @return bool
1019 + */
1020 + public function isPageEditingActive(Page $page, $minRange = null)
1021 + {
1022 + $draftSearch = $this->activePageEditingQuery($page, $minRange);
1023 + return $draftSearch->count() > 0;
1024 + }
1025 +
1026 + /**
1027 + * A query to check for active update drafts on a particular page.
1028 + * @param Page $page
1029 + * @param null $minRange
1030 + * @return mixed
1031 + */
1032 + protected function activePageEditingQuery(Page $page, $minRange = null)
1033 + {
1034 + $query = $this->pageRevision->where('type', '=', 'update_draft')
1035 + ->where('page_id', '=', $page->id)
1036 + ->where('updated_at', '>', $page->updated_at)
1037 + ->where('created_by', '!=', user()->id)
1038 + ->with('createdBy');
1039 +
1040 + if ($minRange !== null) {
1041 + $query = $query->where('updated_at', '>=', Carbon::now()->subMinutes($minRange));
1042 + }
1043 +
1044 + return $query;
1045 + }
1046 +
1047 + /**
1048 + * Restores a revision's content back into a page.
1049 + * @param Page $page
1050 + * @param Book $book
1051 + * @param int $revisionId
1052 + * @return Page
1053 + */
1054 + public function restorePageRevision(Page $page, Book $book, $revisionId)
1055 + {
1056 + $this->savePageRevision($page);
1057 + $revision = $this->getById('page_revision', $revisionId);
1058 + $page->fill($revision->toArray());
1059 + $page->slug = $this->findSuitableSlug('page', $page->name, $page->id, $book->id);
1060 + $page->text = strip_tags($page->html);
1061 + $page->updated_by = user()->id;
1062 + $page->save();
1063 + return $page;
1064 + }
1065 +
1066 +
1067 + /**
1068 + * Save a page update draft.
1069 + * @param Page $page
1070 + * @param array $data
1071 + * @return PageRevision|Page
1072 + */
1073 + public function updatePageDraft(Page $page, $data = [])
1074 + {
1075 + // If the page itself is a draft simply update that
1076 + if ($page->draft) {
1077 + $page->fill($data);
1078 + if (isset($data['html'])) {
1079 + $page->text = strip_tags($data['html']);
1080 + }
1081 + $page->save();
1082 + return $page;
1083 + }
1084 +
1085 + // Otherwise save the data to a revision
1086 + $userId = user()->id;
1087 + $drafts = $this->userUpdatePageDraftsQuery($page, $userId)->get();
1088 +
1089 + if ($drafts->count() > 0) {
1090 + $draft = $drafts->first();
1091 + } else {
1092 + $draft = $this->pageRevision->newInstance();
1093 + $draft->page_id = $page->id;
1094 + $draft->slug = $page->slug;
1095 + $draft->book_slug = $page->book->slug;
1096 + $draft->created_by = $userId;
1097 + $draft->type = 'update_draft';
1098 + }
1099 +
1100 + $draft->fill($data);
1101 + if (setting('app-editor') !== 'markdown') $draft->markdown = '';
1102 +
1103 + $draft->save();
1104 + return $draft;
1105 + }
1106 +
1107 + /**
1108 + * Get a notification message concerning the editing activity on a particular page.
1109 + * @param Page $page
1110 + * @param null $minRange
1111 + * @return string
1112 + */
1113 + public function getPageEditingActiveMessage(Page $page, $minRange = null)
1114 + {
1115 + $pageDraftEdits = $this->activePageEditingQuery($page, $minRange)->get();
1116 +
1117 + $userMessage = $pageDraftEdits->count() > 1 ? trans('entities.pages_draft_edit_active.start_a', ['count' => $pageDraftEdits->count()]): trans('entities.pages_draft_edit_active.start_b', ['userName' => $pageDraftEdits->first()->createdBy->name]);
1118 + $timeMessage = $minRange === null ? trans('entities.pages_draft_edit_active.time_a') : trans('entities.pages_draft_edit_active.time_b', ['minCount'=>$minRange]);
1119 + return trans('entities.pages_draft_edit_active.message', ['start' => $userMessage, 'time' => $timeMessage]);
1120 + }
1121 +
1122 + /**
1123 + * Change the page's parent to the given entity.
1124 + * @param Page $page
1125 + * @param Entity $parent
1126 + */
1127 + public function changePageParent(Page $page, Entity $parent)
1128 + {
1129 + $book = $parent->isA('book') ? $parent : $parent->book;
1130 + $page->chapter_id = $parent->isA('chapter') ? $parent->id : 0;
1131 + $page->save();
1132 + if ($page->book->id !== $book->id) {
1133 + $page = $this->changeBook('page', $book->id, $page);
1134 + }
1135 + $page->load('book');
1136 + $this->permissionService->buildJointPermissionsForEntity($book);
1137 + }
1138 +
1139 + /**
1140 + * Destroy the provided book and all its child entities.
1141 + * @param Book $book
1142 + */
1143 + public function destroyBook(Book $book)
1144 + {
1145 + foreach ($book->pages as $page) {
1146 + $this->destroyPage($page);
1147 + }
1148 + foreach ($book->chapters as $chapter) {
1149 + $this->destroyChapter($chapter);
1150 + }
1151 + \Activity::removeEntity($book);
1152 + $book->views()->delete();
1153 + $book->permissions()->delete();
1154 + $this->permissionService->deleteJointPermissionsForEntity($book);
1155 + $book->delete();
1156 + }
1157 +
1158 + /**
1159 + * Destroy a chapter and its relations.
1160 + * @param Chapter $chapter
1161 + */
1162 + public function destroyChapter(Chapter $chapter)
1163 + {
1164 + if (count($chapter->pages) > 0) {
1165 + foreach ($chapter->pages as $page) {
1166 + $page->chapter_id = 0;
1167 + $page->save();
1168 + }
1169 + }
1170 + \Activity::removeEntity($chapter);
1171 + $chapter->views()->delete();
1172 + $chapter->permissions()->delete();
1173 + $this->permissionService->deleteJointPermissionsForEntity($chapter);
1174 + $chapter->delete();
1175 + }
1176 +
1177 + /**
1178 + * Destroy a given page along with its dependencies.
1179 + * @param Page $page
1180 + */
1181 + public function destroyPage(Page $page)
1182 + {
1183 + \Activity::removeEntity($page);
1184 + $page->views()->delete();
1185 + $page->tags()->delete();
1186 + $page->revisions()->delete();
1187 + $page->permissions()->delete();
1188 + $this->permissionService->deleteJointPermissionsForEntity($page);
1189 +
1190 + // Delete Attached Files
1191 + $attachmentService = app(AttachmentService::class);
1192 + foreach ($page->attachments as $attachment) {
1193 + $attachmentService->deleteFile($attachment);
1194 + }
1195 +
1196 + $page->delete();
1197 + }
1198 +
285 } 1199 }
286 1200
287 1201
......
1 -<?php namespace BookStack\Repos;
2 -
3 -use Activity;
4 -use BookStack\Book;
5 -use BookStack\Chapter;
6 -use BookStack\Entity;
7 -use BookStack\Exceptions\NotFoundException;
8 -use BookStack\Services\AttachmentService;
9 -use Carbon\Carbon;
10 -use DOMDocument;
11 -use DOMXPath;
12 -use Illuminate\Support\Str;
13 -use BookStack\Page;
14 -use BookStack\PageRevision;
15 -
16 -class PageRepo extends EntityRepo
17 -{
18 -
19 - protected $pageRevision;
20 - protected $tagRepo;
21 -
22 - /**
23 - * PageRepo constructor.
24 - * @param PageRevision $pageRevision
25 - * @param TagRepo $tagRepo
26 - */
27 - public function __construct(PageRevision $pageRevision, TagRepo $tagRepo)
28 - {
29 - $this->pageRevision = $pageRevision;
30 - $this->tagRepo = $tagRepo;
31 - parent::__construct();
32 - }
33 -
34 - /**
35 - * Base query for getting pages, Takes restrictions into account.
36 - * @param bool $allowDrafts
37 - * @return mixed
38 - */
39 - private function pageQuery($allowDrafts = false)
40 - {
41 - $query = $this->permissionService->enforcePageRestrictions($this->page, 'view');
42 - if (!$allowDrafts) {
43 - $query = $query->where('draft', '=', false);
44 - }
45 - return $query;
46 - }
47 -
48 - /**
49 - * Get a page via a specific ID.
50 - * @param $id
51 - * @param bool $allowDrafts
52 - * @return Page
53 - */
54 - public function getById($id, $allowDrafts = false)
55 - {
56 - return $this->pageQuery($allowDrafts)->findOrFail($id);
57 - }
58 -
59 - /**
60 - * Get a page identified by the given slug.
61 - * @param $slug
62 - * @param $bookId
63 - * @return Page
64 - * @throws NotFoundException
65 - */
66 - public function getBySlug($slug, $bookId)
67 - {
68 - $page = $this->pageQuery()->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first();
69 - if ($page === null) throw new NotFoundException('Page not found');
70 - return $page;
71 - }
72 -
73 - /**
74 - * Search through page revisions and retrieve
75 - * the last page in the current book that
76 - * has a slug equal to the one given.
77 - * @param $pageSlug
78 - * @param $bookSlug
79 - * @return null | Page
80 - */
81 - public function findPageUsingOldSlug($pageSlug, $bookSlug)
82 - {
83 - $revision = $this->pageRevision->where('slug', '=', $pageSlug)
84 - ->whereHas('page', function ($query) {
85 - $this->permissionService->enforcePageRestrictions($query);
86 - })
87 - ->where('type', '=', 'version')
88 - ->where('book_slug', '=', $bookSlug)->orderBy('created_at', 'desc')
89 - ->with('page')->first();
90 - return $revision !== null ? $revision->page : null;
91 - }
92 -
93 - /**
94 - * Get a new Page instance from the given input.
95 - * @param $input
96 - * @return Page
97 - */
98 - public function newFromInput($input)
99 - {
100 - $page = $this->page->fill($input);
101 - return $page;
102 - }
103 -
104 - /**
105 - * Count the pages with a particular slug within a book.
106 - * @param $slug
107 - * @param $bookId
108 - * @return mixed
109 - */
110 - public function countBySlug($slug, $bookId)
111 - {
112 - return $this->page->where('slug', '=', $slug)->where('book_id', '=', $bookId)->count();
113 - }
114 -
115 - /**
116 - * Publish a draft page to make it a normal page.
117 - * Sets the slug and updates the content.
118 - * @param Page $draftPage
119 - * @param array $input
120 - * @return Page
121 - */
122 - public function publishDraft(Page $draftPage, array $input)
123 - {
124 - $draftPage->fill($input);
125 -
126 - // Save page tags if present
127 - if (isset($input['tags'])) {
128 - $this->tagRepo->saveTagsToEntity($draftPage, $input['tags']);
129 - }
130 -
131 - $draftPage->slug = $this->findSuitableSlug($draftPage->name, $draftPage->book->id);
132 - $draftPage->html = $this->formatHtml($input['html']);
133 - $draftPage->text = strip_tags($draftPage->html);
134 - $draftPage->draft = false;
135 -
136 - $draftPage->save();
137 - $this->saveRevision($draftPage, 'Initial Publish');
138 -
139 - return $draftPage;
140 - }
141 -
142 - /**
143 - * Get a new draft page instance.
144 - * @param Book $book
145 - * @param Chapter|bool $chapter
146 - * @return static
147 - */
148 - public function getDraftPage(Book $book, $chapter = false)
149 - {
150 - $page = $this->page->newInstance();
151 - $page->name = 'New Page';
152 - $page->created_by = user()->id;
153 - $page->updated_by = user()->id;
154 - $page->draft = true;
155 -
156 - if ($chapter) $page->chapter_id = $chapter->id;
157 -
158 - $book->pages()->save($page);
159 - $this->permissionService->buildJointPermissionsForEntity($page);
160 - return $page;
161 - }
162 -
163 - /**
164 - * Parse te headers on the page to get a navigation menu
165 - * @param Page $page
166 - * @return array
167 - */
168 - public function getPageNav(Page $page)
169 - {
170 - if ($page->html == '') return null;
171 - libxml_use_internal_errors(true);
172 - $doc = new DOMDocument();
173 - $doc->loadHTML(mb_convert_encoding($page->html, 'HTML-ENTITIES', 'UTF-8'));
174 - $xPath = new DOMXPath($doc);
175 - $headers = $xPath->query("//h1|//h2|//h3|//h4|//h5|//h6");
176 -
177 - if (is_null($headers)) return null;
178 -
179 - $tree = [];
180 - foreach ($headers as $header) {
181 - $text = $header->nodeValue;
182 - $tree[] = [
183 - 'nodeName' => strtolower($header->nodeName),
184 - 'level' => intval(str_replace('h', '', $header->nodeName)),
185 - 'link' => '#' . $header->getAttribute('id'),
186 - 'text' => strlen($text) > 30 ? substr($text, 0, 27) . '...' : $text
187 - ];
188 - }
189 - return $tree;
190 - }
191 -
192 - /**
193 - * Formats a page's html to be tagged correctly
194 - * within the system.
195 - * @param string $htmlText
196 - * @return string
197 - */
198 - protected function formatHtml($htmlText)
199 - {
200 - if ($htmlText == '') return $htmlText;
201 - libxml_use_internal_errors(true);
202 - $doc = new DOMDocument();
203 - $doc->loadHTML(mb_convert_encoding($htmlText, 'HTML-ENTITIES', 'UTF-8'));
204 -
205 - $container = $doc->documentElement;
206 - $body = $container->childNodes->item(0);
207 - $childNodes = $body->childNodes;
208 -
209 - // Ensure no duplicate ids are used
210 - $idArray = [];
211 -
212 - foreach ($childNodes as $index => $childNode) {
213 - /** @var \DOMElement $childNode */
214 - if (get_class($childNode) !== 'DOMElement') continue;
215 -
216 - // Overwrite id if not a BookStack custom id
217 - if ($childNode->hasAttribute('id')) {
218 - $id = $childNode->getAttribute('id');
219 - if (strpos($id, 'bkmrk') === 0 && array_search($id, $idArray) === false) {
220 - $idArray[] = $id;
221 - continue;
222 - };
223 - }
224 -
225 - // Create an unique id for the element
226 - // Uses the content as a basis to ensure output is the same every time
227 - // the same content is passed through.
228 - $contentId = 'bkmrk-' . substr(strtolower(preg_replace('/\s+/', '-', trim($childNode->nodeValue))), 0, 20);
229 - $newId = urlencode($contentId);
230 - $loopIndex = 0;
231 - while (in_array($newId, $idArray)) {
232 - $newId = urlencode($contentId . '-' . $loopIndex);
233 - $loopIndex++;
234 - }
235 -
236 - $childNode->setAttribute('id', $newId);
237 - $idArray[] = $newId;
238 - }
239 -
240 - // Generate inner html as a string
241 - $html = '';
242 - foreach ($childNodes as $childNode) {
243 - $html .= $doc->saveHTML($childNode);
244 - }
245 -
246 - return $html;
247 - }
248 -
249 -
250 - /**
251 - * Gets pages by a search term.
252 - * Highlights page content for showing in results.
253 - * @param string $term
254 - * @param array $whereTerms
255 - * @param int $count
256 - * @param array $paginationAppends
257 - * @return mixed
258 - */
259 - public function getBySearch($term, $whereTerms = [], $count = 20, $paginationAppends = [])
260 - {
261 - $terms = $this->prepareSearchTerms($term);
262 - $pageQuery = $this->permissionService->enforcePageRestrictions($this->page->fullTextSearchQuery(['name', 'text'], $terms, $whereTerms));
263 - $pageQuery = $this->addAdvancedSearchQueries($pageQuery, $term);
264 - $pages = $pageQuery->paginate($count)->appends($paginationAppends);
265 -
266 - // Add highlights to page text.
267 - $words = join('|', explode(' ', preg_quote(trim($term), '/')));
268 - //lookahead/behind assertions ensures cut between words
269 - $s = '\s\x00-/:-@\[-`{-~'; //character set for start/end of words
270 -
271 - foreach ($pages as $page) {
272 - preg_match_all('#(?<=[' . $s . ']).{1,30}((' . $words . ').{1,30})+(?=[' . $s . '])#uis', $page->text, $matches, PREG_SET_ORDER);
273 - //delimiter between occurrences
274 - $results = [];
275 - foreach ($matches as $line) {
276 - $results[] = htmlspecialchars($line[0], 0, 'UTF-8');
277 - }
278 - $matchLimit = 6;
279 - if (count($results) > $matchLimit) {
280 - $results = array_slice($results, 0, $matchLimit);
281 - }
282 - $result = join('... ', $results);
283 -
284 - //highlight
285 - $result = preg_replace('#' . $words . '#iu', "<span class=\"highlight\">\$0</span>", $result);
286 - if (strlen($result) < 5) {
287 - $result = $page->getExcerpt(80);
288 - }
289 - $page->searchSnippet = $result;
290 - }
291 - return $pages;
292 - }
293 -
294 - /**
295 - * Search for image usage.
296 - * @param $imageString
297 - * @return mixed
298 - */
299 - public function searchForImage($imageString)
300 - {
301 - $pages = $this->pageQuery()->where('html', 'like', '%' . $imageString . '%')->get();
302 - foreach ($pages as $page) {
303 - $page->url = $page->getUrl();
304 - $page->html = '';
305 - $page->text = '';
306 - }
307 - return count($pages) > 0 ? $pages : false;
308 - }
309 -
310 - /**
311 - * Updates a page with any fillable data and saves it into the database.
312 - * @param Page $page
313 - * @param int $book_id
314 - * @param string $input
315 - * @return Page
316 - */
317 - public function updatePage(Page $page, $book_id, $input)
318 - {
319 - // Hold the old details to compare later
320 - $oldHtml = $page->html;
321 - $oldName = $page->name;
322 -
323 - // Prevent slug being updated if no name change
324 - if ($page->name !== $input['name']) {
325 - $page->slug = $this->findSuitableSlug($input['name'], $book_id, $page->id);
326 - }
327 -
328 - // Save page tags if present
329 - if (isset($input['tags'])) {
330 - $this->tagRepo->saveTagsToEntity($page, $input['tags']);
331 - }
332 -
333 - // Update with new details
334 - $userId = user()->id;
335 - $page->fill($input);
336 - $page->html = $this->formatHtml($input['html']);
337 - $page->text = strip_tags($page->html);
338 - if (setting('app-editor') !== 'markdown') $page->markdown = '';
339 - $page->updated_by = $userId;
340 - $page->save();
341 -
342 - // Remove all update drafts for this user & page.
343 - $this->userUpdateDraftsQuery($page, $userId)->delete();
344 -
345 - // Save a revision after updating
346 - if ($oldHtml !== $input['html'] || $oldName !== $input['name'] || $input['summary'] !== null) {
347 - $this->saveRevision($page, $input['summary']);
348 - }
349 -
350 - return $page;
351 - }
352 -
353 - /**
354 - * Restores a revision's content back into a page.
355 - * @param Page $page
356 - * @param Book $book
357 - * @param int $revisionId
358 - * @return Page
359 - */
360 - public function restoreRevision(Page $page, Book $book, $revisionId)
361 - {
362 - $this->saveRevision($page);
363 - $revision = $this->getRevisionById($revisionId);
364 - $page->fill($revision->toArray());
365 - $page->slug = $this->findSuitableSlug($page->name, $book->id, $page->id);
366 - $page->text = strip_tags($page->html);
367 - $page->updated_by = user()->id;
368 - $page->save();
369 - return $page;
370 - }
371 -
372 - /**
373 - * Saves a page revision into the system.
374 - * @param Page $page
375 - * @param null|string $summary
376 - * @return $this
377 - */
378 - public function saveRevision(Page $page, $summary = null)
379 - {
380 - $revision = $this->pageRevision->newInstance($page->toArray());
381 - if (setting('app-editor') !== 'markdown') $revision->markdown = '';
382 - $revision->page_id = $page->id;
383 - $revision->slug = $page->slug;
384 - $revision->book_slug = $page->book->slug;
385 - $revision->created_by = user()->id;
386 - $revision->created_at = $page->updated_at;
387 - $revision->type = 'version';
388 - $revision->summary = $summary;
389 - $revision->save();
390 -
391 - // Clear old revisions
392 - if ($this->pageRevision->where('page_id', '=', $page->id)->count() > 50) {
393 - $this->pageRevision->where('page_id', '=', $page->id)
394 - ->orderBy('created_at', 'desc')->skip(50)->take(5)->delete();
395 - }
396 -
397 - return $revision;
398 - }
399 -
400 - /**
401 - * Save a page update draft.
402 - * @param Page $page
403 - * @param array $data
404 - * @return PageRevision
405 - */
406 - public function saveUpdateDraft(Page $page, $data = [])
407 - {
408 - $userId = user()->id;
409 - $drafts = $this->userUpdateDraftsQuery($page, $userId)->get();
410 -
411 - if ($drafts->count() > 0) {
412 - $draft = $drafts->first();
413 - } else {
414 - $draft = $this->pageRevision->newInstance();
415 - $draft->page_id = $page->id;
416 - $draft->slug = $page->slug;
417 - $draft->book_slug = $page->book->slug;
418 - $draft->created_by = $userId;
419 - $draft->type = 'update_draft';
420 - }
421 -
422 - $draft->fill($data);
423 - if (setting('app-editor') !== 'markdown') $draft->markdown = '';
424 -
425 - $draft->save();
426 - return $draft;
427 - }
428 -
429 - /**
430 - * Update a draft page.
431 - * @param Page $page
432 - * @param array $data
433 - * @return Page
434 - */
435 - public function updateDraftPage(Page $page, $data = [])
436 - {
437 - $page->fill($data);
438 -
439 - if (isset($data['html'])) {
440 - $page->text = strip_tags($data['html']);
441 - }
442 -
443 - $page->save();
444 - return $page;
445 - }
446 -
447 - /**
448 - * The base query for getting user update drafts.
449 - * @param Page $page
450 - * @param $userId
451 - * @return mixed
452 - */
453 - private function userUpdateDraftsQuery(Page $page, $userId)
454 - {
455 - return $this->pageRevision->where('created_by', '=', $userId)
456 - ->where('type', 'update_draft')
457 - ->where('page_id', '=', $page->id)
458 - ->orderBy('created_at', 'desc');
459 - }
460 -
461 - /**
462 - * Checks whether a user has a draft version of a particular page or not.
463 - * @param Page $page
464 - * @param $userId
465 - * @return bool
466 - */
467 - public function hasUserGotPageDraft(Page $page, $userId)
468 - {
469 - return $this->userUpdateDraftsQuery($page, $userId)->count() > 0;
470 - }
471 -
472 - /**
473 - * Get the latest updated draft revision for a particular page and user.
474 - * @param Page $page
475 - * @param $userId
476 - * @return mixed
477 - */
478 - public function getUserPageDraft(Page $page, $userId)
479 - {
480 - return $this->userUpdateDraftsQuery($page, $userId)->first();
481 - }
482 -
483 - /**
484 - * Get the notification message that informs the user that they are editing a draft page.
485 - * @param PageRevision $draft
486 - * @return string
487 - */
488 - public function getUserPageDraftMessage(PageRevision $draft)
489 - {
490 - $message = 'You are currently editing a draft that was last saved ' . $draft->updated_at->diffForHumans() . '.';
491 - if ($draft->page->updated_at->timestamp > $draft->updated_at->timestamp) {
492 - $message .= "\n This page has been updated by since that time. It is recommended that you discard this draft.";
493 - }
494 - return $message;
495 - }
496 -
497 - /**
498 - * Check if a page is being actively editing.
499 - * Checks for edits since last page updated.
500 - * Passing in a minuted range will check for edits
501 - * within the last x minutes.
502 - * @param Page $page
503 - * @param null $minRange
504 - * @return bool
505 - */
506 - public function isPageEditingActive(Page $page, $minRange = null)
507 - {
508 - $draftSearch = $this->activePageEditingQuery($page, $minRange);
509 - return $draftSearch->count() > 0;
510 - }
511 -
512 - /**
513 - * Get a notification message concerning the editing activity on
514 - * a particular page.
515 - * @param Page $page
516 - * @param null $minRange
517 - * @return string
518 - */
519 - public function getPageEditingActiveMessage(Page $page, $minRange = null)
520 - {
521 - $pageDraftEdits = $this->activePageEditingQuery($page, $minRange)->get();
522 - $userMessage = $pageDraftEdits->count() > 1 ? $pageDraftEdits->count() . ' users have' : $pageDraftEdits->first()->createdBy->name . ' has';
523 - $timeMessage = $minRange === null ? 'since the page was last updated' : 'in the last ' . $minRange . ' minutes';
524 - $message = '%s started editing this page %s. Take care not to overwrite each other\'s updates!';
525 - return sprintf($message, $userMessage, $timeMessage);
526 - }
527 -
528 - /**
529 - * A query to check for active update drafts on a particular page.
530 - * @param Page $page
531 - * @param null $minRange
532 - * @return mixed
533 - */
534 - private function activePageEditingQuery(Page $page, $minRange = null)
535 - {
536 - $query = $this->pageRevision->where('type', '=', 'update_draft')
537 - ->where('page_id', '=', $page->id)
538 - ->where('updated_at', '>', $page->updated_at)
539 - ->where('created_by', '!=', user()->id)
540 - ->with('createdBy');
541 -
542 - if ($minRange !== null) {
543 - $query = $query->where('updated_at', '>=', Carbon::now()->subMinutes($minRange));
544 - }
545 -
546 - return $query;
547 - }
548 -
549 - /**
550 - * Gets a single revision via it's id.
551 - * @param $id
552 - * @return PageRevision
553 - */
554 - public function getRevisionById($id)
555 - {
556 - return $this->pageRevision->findOrFail($id);
557 - }
558 -
559 - /**
560 - * Checks if a slug exists within a book already.
561 - * @param $slug
562 - * @param $bookId
563 - * @param bool|false $currentId
564 - * @return bool
565 - */
566 - public function doesSlugExist($slug, $bookId, $currentId = false)
567 - {
568 - $query = $this->page->where('slug', '=', $slug)->where('book_id', '=', $bookId);
569 - if ($currentId) $query = $query->where('id', '!=', $currentId);
570 - return $query->count() > 0;
571 - }
572 -
573 - /**
574 - * Changes the related book for the specified page.
575 - * Changes the book id of any relations to the page that store the book id.
576 - * @param int $bookId
577 - * @param Page $page
578 - * @return Page
579 - */
580 - public function changeBook($bookId, Page $page)
581 - {
582 - $page->book_id = $bookId;
583 - foreach ($page->activity as $activity) {
584 - $activity->book_id = $bookId;
585 - $activity->save();
586 - }
587 - $page->slug = $this->findSuitableSlug($page->name, $bookId, $page->id);
588 - $page->save();
589 - return $page;
590 - }
591 -
592 -
593 - /**
594 - * Change the page's parent to the given entity.
595 - * @param Page $page
596 - * @param Entity $parent
597 - */
598 - public function changePageParent(Page $page, Entity $parent)
599 - {
600 - $book = $parent->isA('book') ? $parent : $parent->book;
601 - $page->chapter_id = $parent->isA('chapter') ? $parent->id : 0;
602 - $page->save();
603 - $page = $this->changeBook($book->id, $page);
604 - $page->load('book');
605 - $this->permissionService->buildJointPermissionsForEntity($book);
606 - }
607 -
608 - /**
609 - * Gets a suitable slug for the resource
610 - * @param string $name
611 - * @param int $bookId
612 - * @param bool|false $currentId
613 - * @return string
614 - */
615 - public function findSuitableSlug($name, $bookId, $currentId = false)
616 - {
617 - $slug = $this->nameToSlug($name);
618 - while ($this->doesSlugExist($slug, $bookId, $currentId)) {
619 - $slug .= '-' . substr(md5(rand(1, 500)), 0, 3);
620 - }
621 - return $slug;
622 - }
623 -
624 - /**
625 - * Destroy a given page along with its dependencies.
626 - * @param $page
627 - */
628 - public function destroy(Page $page)
629 - {
630 - Activity::removeEntity($page);
631 - $page->views()->delete();
632 - $page->tags()->delete();
633 - $page->revisions()->delete();
634 - $page->permissions()->delete();
635 - $this->permissionService->deleteJointPermissionsForEntity($page);
636 -
637 - // Delete AttachedFiles
638 - $attachmentService = app(AttachmentService::class);
639 - foreach ($page->attachments as $attachment) {
640 - $attachmentService->deleteFile($attachment);
641 - }
642 -
643 - $page->delete();
644 - }
645 -
646 - /**
647 - * Get the latest pages added to the system.
648 - * @param $count
649 - * @return mixed
650 - */
651 - public function getRecentlyCreatedPaginated($count = 20)
652 - {
653 - return $this->pageQuery()->orderBy('created_at', 'desc')->paginate($count);
654 - }
655 -
656 - /**
657 - * Get the latest pages added to the system.
658 - * @param $count
659 - * @return mixed
660 - */
661 - public function getRecentlyUpdatedPaginated($count = 20)
662 - {
663 - return $this->pageQuery()->orderBy('updated_at', 'desc')->paginate($count);
664 - }
665 -
666 -}
...@@ -93,7 +93,7 @@ class PermissionsRepo ...@@ -93,7 +93,7 @@ class PermissionsRepo
93 $permissions = isset($roleData['permissions']) ? array_keys($roleData['permissions']) : []; 93 $permissions = isset($roleData['permissions']) ? array_keys($roleData['permissions']) : [];
94 $this->assignRolePermissions($role, $permissions); 94 $this->assignRolePermissions($role, $permissions);
95 95
96 - if ($role->name === 'admin') { 96 + if ($role->system_name === 'admin') {
97 $permissions = $this->permission->all()->pluck('id')->toArray(); 97 $permissions = $this->permission->all()->pluck('id')->toArray();
98 $role->permissions()->sync($permissions); 98 $role->permissions()->sync($permissions);
99 } 99 }
...@@ -133,9 +133,9 @@ class PermissionsRepo ...@@ -133,9 +133,9 @@ class PermissionsRepo
133 133
134 // Prevent deleting admin role or default registration role. 134 // Prevent deleting admin role or default registration role.
135 if ($role->system_name && in_array($role->system_name, $this->systemRoles)) { 135 if ($role->system_name && in_array($role->system_name, $this->systemRoles)) {
136 - throw new PermissionsException('This role is a system role and cannot be deleted'); 136 + throw new PermissionsException(trans('errors.role_system_cannot_be_deleted'));
137 } else if ($role->id == setting('registration-role')) { 137 } else if ($role->id == setting('registration-role')) {
138 - throw new PermissionsException('This role cannot be deleted while set as the default registration role.'); 138 + throw new PermissionsException(trans('errors.role_registration_default_cannot_delete'));
139 } 139 }
140 140
141 if ($migrateRoleId) { 141 if ($migrateRoleId) {
......
...@@ -38,7 +38,7 @@ class TagRepo ...@@ -38,7 +38,7 @@ class TagRepo
38 { 38 {
39 $entityInstance = $this->entity->getEntityInstance($entityType); 39 $entityInstance = $this->entity->getEntityInstance($entityType);
40 $searchQuery = $entityInstance->where('id', '=', $entityId)->with('tags'); 40 $searchQuery = $entityInstance->where('id', '=', $entityId)->with('tags');
41 - $searchQuery = $this->permissionService->enforceEntityRestrictions($searchQuery, $action); 41 + $searchQuery = $this->permissionService->enforceEntityRestrictions($entityType, $searchQuery, $action);
42 return $searchQuery->first(); 42 return $searchQuery->first();
43 } 43 }
44 44
...@@ -121,7 +121,7 @@ class TagRepo ...@@ -121,7 +121,7 @@ class TagRepo
121 /** 121 /**
122 * Create a new Tag instance from user input. 122 * Create a new Tag instance from user input.
123 * @param $input 123 * @param $input
124 - * @return static 124 + * @return Tag
125 */ 125 */
126 protected function newInstanceFromInput($input) 126 protected function newInstanceFromInput($input)
127 { 127 {
......
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
3 use BookStack\Role; 3 use BookStack\Role;
4 use BookStack\User; 4 use BookStack\User;
5 use Exception; 5 use Exception;
6 -use Setting;
7 6
8 class UserRepo 7 class UserRepo
9 { 8 {
...@@ -169,13 +168,13 @@ class UserRepo ...@@ -169,13 +168,13 @@ class UserRepo
169 public function getRecentlyCreated(User $user, $count = 20) 168 public function getRecentlyCreated(User $user, $count = 20)
170 { 169 {
171 return [ 170 return [
172 - 'pages' => $this->entityRepo->getRecentlyCreatedPages($count, 0, function ($query) use ($user) { 171 + 'pages' => $this->entityRepo->getRecentlyCreated('page', $count, 0, function ($query) use ($user) {
173 $query->where('created_by', '=', $user->id); 172 $query->where('created_by', '=', $user->id);
174 }), 173 }),
175 - 'chapters' => $this->entityRepo->getRecentlyCreatedChapters($count, 0, function ($query) use ($user) { 174 + 'chapters' => $this->entityRepo->getRecentlyCreated('chapter', $count, 0, function ($query) use ($user) {
176 $query->where('created_by', '=', $user->id); 175 $query->where('created_by', '=', $user->id);
177 }), 176 }),
178 - 'books' => $this->entityRepo->getRecentlyCreatedBooks($count, 0, function ($query) use ($user) { 177 + 'books' => $this->entityRepo->getRecentlyCreated('book', $count, 0, function ($query) use ($user) {
179 $query->where('created_by', '=', $user->id); 178 $query->where('created_by', '=', $user->id);
180 }) 179 })
181 ]; 180 ];
......
...@@ -114,7 +114,7 @@ class ActivityService ...@@ -114,7 +114,7 @@ class ActivityService
114 114
115 $activity = $this->permissionService 115 $activity = $this->permissionService
116 ->filterRestrictedEntityRelations($query, 'activities', 'entity_id', 'entity_type') 116 ->filterRestrictedEntityRelations($query, 'activities', 'entity_id', 'entity_type')
117 - ->orderBy('created_at', 'desc')->skip($count * $page)->take($count)->get(); 117 + ->orderBy('created_at', 'desc')->with(['entity', 'user.avatar'])->skip($count * $page)->take($count)->get();
118 118
119 return $this->filterSimilar($activity); 119 return $this->filterSimilar($activity);
120 } 120 }
......
...@@ -193,7 +193,7 @@ class AttachmentService extends UploadService ...@@ -193,7 +193,7 @@ class AttachmentService extends UploadService
193 try { 193 try {
194 $storage->put($attachmentStoragePath, $attachmentData); 194 $storage->put($attachmentStoragePath, $attachmentData);
195 } catch (Exception $e) { 195 } catch (Exception $e) {
196 - throw new FileUploadException('File path ' . $attachmentStoragePath . ' could not be uploaded to. Ensure it is writable to the server.'); 196 + throw new FileUploadException(trans('errors.path_not_writable', ['filePath' => $attachmentStoragePath]));
197 } 197 }
198 return $attachmentPath; 198 return $attachmentPath;
199 } 199 }
......
...@@ -33,7 +33,7 @@ class EmailConfirmationService ...@@ -33,7 +33,7 @@ class EmailConfirmationService
33 public function sendConfirmation(User $user) 33 public function sendConfirmation(User $user)
34 { 34 {
35 if ($user->email_confirmed) { 35 if ($user->email_confirmed) {
36 - throw new ConfirmationEmailException('Email has already been confirmed, Try logging in.', '/login'); 36 + throw new ConfirmationEmailException(trans('errors.email_already_confirmed'), '/login');
37 } 37 }
38 38
39 $this->deleteConfirmationsByUser($user); 39 $this->deleteConfirmationsByUser($user);
...@@ -63,7 +63,7 @@ class EmailConfirmationService ...@@ -63,7 +63,7 @@ class EmailConfirmationService
63 * Gets an email confirmation by looking up the token, 63 * Gets an email confirmation by looking up the token,
64 * Ensures the token has not expired. 64 * Ensures the token has not expired.
65 * @param string $token 65 * @param string $token
66 - * @return EmailConfirmation 66 + * @return array|null|\stdClass
67 * @throws UserRegistrationException 67 * @throws UserRegistrationException
68 */ 68 */
69 public function getEmailConfirmationFromToken($token) 69 public function getEmailConfirmationFromToken($token)
...@@ -72,14 +72,14 @@ class EmailConfirmationService ...@@ -72,14 +72,14 @@ class EmailConfirmationService
72 72
73 // If not found show error 73 // If not found show error
74 if ($emailConfirmation === null) { 74 if ($emailConfirmation === null) {
75 - throw new UserRegistrationException('This confirmation token is not valid or has already been used, Please try registering again.', '/register'); 75 + throw new UserRegistrationException(trans('errors.email_confirmation_invalid'), '/register');
76 } 76 }
77 77
78 // If more than a day old 78 // If more than a day old
79 if (Carbon::now()->subDay()->gt(new Carbon($emailConfirmation->created_at))) { 79 if (Carbon::now()->subDay()->gt(new Carbon($emailConfirmation->created_at))) {
80 $user = $this->users->getById($emailConfirmation->user_id); 80 $user = $this->users->getById($emailConfirmation->user_id);
81 $this->sendConfirmation($user); 81 $this->sendConfirmation($user);
82 - throw new UserRegistrationException('The confirmation token has expired, A new confirmation email has been sent.', '/register/confirm'); 82 + throw new UserRegistrationException(trans('errors.email_confirmation_expired'), '/register/confirm');
83 } 83 }
84 84
85 $emailConfirmation->user = $this->users->getById($emailConfirmation->user_id); 85 $emailConfirmation->user = $this->users->getById($emailConfirmation->user_id);
......
1 <?php namespace BookStack\Services; 1 <?php namespace BookStack\Services;
2 2
3 -
4 use BookStack\Page; 3 use BookStack\Page;
4 +use BookStack\Repos\EntityRepo;
5 5
6 class ExportService 6 class ExportService
7 { 7 {
8 8
9 + protected $entityRepo;
10 +
11 + /**
12 + * ExportService constructor.
13 + * @param $entityRepo
14 + */
15 + public function __construct(EntityRepo $entityRepo)
16 + {
17 + $this->entityRepo = $entityRepo;
18 + }
19 +
9 /** 20 /**
10 * Convert a page to a self-contained HTML file. 21 * Convert a page to a self-contained HTML file.
11 * Includes required CSS & image content. Images are base64 encoded into the HTML. 22 * Includes required CSS & image content. Images are base64 encoded into the HTML.
...@@ -15,7 +26,7 @@ class ExportService ...@@ -15,7 +26,7 @@ class ExportService
15 public function pageToContainedHtml(Page $page) 26 public function pageToContainedHtml(Page $page)
16 { 27 {
17 $cssContent = file_get_contents(public_path('/css/export-styles.css')); 28 $cssContent = file_get_contents(public_path('/css/export-styles.css'));
18 - $pageHtml = view('pages/export', ['page' => $page, 'css' => $cssContent])->render(); 29 + $pageHtml = view('pages/export', ['page' => $page, 'pageContent' => $this->entityRepo->renderPage($page), 'css' => $cssContent])->render();
19 return $this->containHtml($pageHtml); 30 return $this->containHtml($pageHtml);
20 } 31 }
21 32
...@@ -27,9 +38,15 @@ class ExportService ...@@ -27,9 +38,15 @@ class ExportService
27 public function pageToPdf(Page $page) 38 public function pageToPdf(Page $page)
28 { 39 {
29 $cssContent = file_get_contents(public_path('/css/export-styles.css')); 40 $cssContent = file_get_contents(public_path('/css/export-styles.css'));
30 - $pageHtml = view('pages/pdf', ['page' => $page, 'css' => $cssContent])->render(); 41 + $pageHtml = view('pages/pdf', ['page' => $page, 'pageContent' => $this->entityRepo->renderPage($page), 'css' => $cssContent])->render();
42 +// return $pageHtml;
43 + $useWKHTML = config('snappy.pdf.binary') !== false;
31 $containedHtml = $this->containHtml($pageHtml); 44 $containedHtml = $this->containHtml($pageHtml);
45 + if ($useWKHTML) {
46 + $pdf = \SnappyPDF::loadHTML($containedHtml);
47 + } else {
32 $pdf = \PDF::loadHTML($containedHtml); 48 $pdf = \PDF::loadHTML($containedHtml);
49 + }
33 return $pdf->output(); 50 return $pdf->output();
34 } 51 }
35 52
...@@ -55,9 +72,13 @@ class ExportService ...@@ -55,9 +72,13 @@ class ExportService
55 $pathString = $srcString; 72 $pathString = $srcString;
56 } 73 }
57 if ($isLocal && !file_exists($pathString)) continue; 74 if ($isLocal && !file_exists($pathString)) continue;
75 + try {
58 $imageContent = file_get_contents($pathString); 76 $imageContent = file_get_contents($pathString);
59 $imageEncoded = 'data:image/' . pathinfo($pathString, PATHINFO_EXTENSION) . ';base64,' . base64_encode($imageContent); 77 $imageEncoded = 'data:image/' . pathinfo($pathString, PATHINFO_EXTENSION) . ';base64,' . base64_encode($imageContent);
60 $newImageString = str_replace($srcString, $imageEncoded, $oldImgString); 78 $newImageString = str_replace($srcString, $imageEncoded, $oldImgString);
79 + } catch (\ErrorException $e) {
80 + $newImageString = '';
81 + }
61 $htmlContent = str_replace($oldImgString, $newImageString, $htmlContent); 82 $htmlContent = str_replace($oldImgString, $newImageString, $htmlContent);
62 } 83 }
63 } 84 }
...@@ -84,14 +105,14 @@ class ExportService ...@@ -84,14 +105,14 @@ class ExportService
84 105
85 /** 106 /**
86 * Converts the page contents into simple plain text. 107 * Converts the page contents into simple plain text.
87 - * This method filters any bad looking content to 108 + * This method filters any bad looking content to provide a nice final output.
88 - * provide a nice final output.
89 * @param Page $page 109 * @param Page $page
90 * @return mixed 110 * @return mixed
91 */ 111 */
92 public function pageToPlainText(Page $page) 112 public function pageToPlainText(Page $page)
93 { 113 {
94 - $text = $page->text; 114 + $html = $this->entityRepo->renderPage($page);
115 + $text = strip_tags($html);
95 // Replace multiple spaces with single spaces 116 // Replace multiple spaces with single spaces
96 $text = preg_replace('/\ {2,}/', ' ', $text); 117 $text = preg_replace('/\ {2,}/', ' ', $text);
97 // Reduce multiple horrid whitespace characters. 118 // Reduce multiple horrid whitespace characters.
......
...@@ -59,7 +59,7 @@ class ImageService extends UploadService ...@@ -59,7 +59,7 @@ class ImageService extends UploadService
59 { 59 {
60 $imageName = $imageName ? $imageName : basename($url); 60 $imageName = $imageName ? $imageName : basename($url);
61 $imageData = file_get_contents($url); 61 $imageData = file_get_contents($url);
62 - if($imageData === false) throw new \Exception('Cannot get image from ' . $url); 62 + if($imageData === false) throw new \Exception(trans('errors.cannot_get_image_from_url', ['url' => $url]));
63 return $this->saveNew($imageName, $imageData, $type); 63 return $this->saveNew($imageName, $imageData, $type);
64 } 64 }
65 65
...@@ -93,7 +93,7 @@ class ImageService extends UploadService ...@@ -93,7 +93,7 @@ class ImageService extends UploadService
93 $storage->put($fullPath, $imageData); 93 $storage->put($fullPath, $imageData);
94 $storage->setVisibility($fullPath, 'public'); 94 $storage->setVisibility($fullPath, 'public');
95 } catch (Exception $e) { 95 } catch (Exception $e) {
96 - throw new ImageUploadException('Image Path ' . $fullPath . ' is not writable by the server.'); 96 + throw new ImageUploadException(trans('errors.path_not_writable', ['filePath' => $fullPath]));
97 } 97 }
98 98
99 if ($this->isLocal()) $fullPath = str_replace_first('/public', '', $fullPath); 99 if ($this->isLocal()) $fullPath = str_replace_first('/public', '', $fullPath);
...@@ -160,7 +160,7 @@ class ImageService extends UploadService ...@@ -160,7 +160,7 @@ class ImageService extends UploadService
160 $thumb = $this->imageTool->make($storage->get($imagePath)); 160 $thumb = $this->imageTool->make($storage->get($imagePath));
161 } catch (Exception $e) { 161 } catch (Exception $e) {
162 if ($e instanceof \ErrorException || $e instanceof NotSupportedException) { 162 if ($e instanceof \ErrorException || $e instanceof NotSupportedException) {
163 - throw new ImageUploadException('The server cannot create thumbnails. Please check you have the GD PHP extension installed.'); 163 + throw new ImageUploadException(trans('errors.cannot_create_thumbs'));
164 } else { 164 } else {
165 throw $e; 165 throw $e;
166 } 166 }
......
...@@ -94,7 +94,7 @@ class LdapService ...@@ -94,7 +94,7 @@ class LdapService
94 $ldapBind = $this->ldap->bind($connection, $ldapDn, $ldapPass); 94 $ldapBind = $this->ldap->bind($connection, $ldapDn, $ldapPass);
95 } 95 }
96 96
97 - if (!$ldapBind) throw new LdapException('LDAP access failed using ' . ($isAnonymous ? ' anonymous bind.' : ' given dn & pass details')); 97 + if (!$ldapBind) throw new LdapException(($isAnonymous ? trans('errors.ldap_fail_anonymous') : trans('errors.ldap_fail_authed')));
98 } 98 }
99 99
100 /** 100 /**
...@@ -109,15 +109,19 @@ class LdapService ...@@ -109,15 +109,19 @@ class LdapService
109 109
110 // Check LDAP extension in installed 110 // Check LDAP extension in installed
111 if (!function_exists('ldap_connect') && config('app.env') !== 'testing') { 111 if (!function_exists('ldap_connect') && config('app.env') !== 'testing') {
112 - throw new LdapException('LDAP PHP extension not installed'); 112 + throw new LdapException(trans('errors.ldap_extension_not_installed'));
113 } 113 }
114 114
115 - // Get port from server string if specified. 115 + // Get port from server string and protocol if specified.
116 $ldapServer = explode(':', $this->config['server']); 116 $ldapServer = explode(':', $this->config['server']);
117 - $ldapConnection = $this->ldap->connect($ldapServer[0], count($ldapServer) > 1 ? $ldapServer[1] : 389); 117 + $hasProtocol = preg_match('/^ldaps{0,1}\:\/\//', $this->config['server']) === 1;
118 + if (!$hasProtocol) array_unshift($ldapServer, '');
119 + $hostName = $ldapServer[0] . ($hasProtocol?':':'') . $ldapServer[1];
120 + $defaultPort = $ldapServer[0] === 'ldaps' ? 636 : 389;
121 + $ldapConnection = $this->ldap->connect($hostName, count($ldapServer) > 2 ? intval($ldapServer[2]) : $defaultPort);
118 122
119 if ($ldapConnection === false) { 123 if ($ldapConnection === false) {
120 - throw new LdapException('Cannot connect to ldap server, Initial connection failed'); 124 + throw new LdapException(trans('errors.ldap_cannot_connect'));
121 } 125 }
122 126
123 // Set any required options 127 // Set any required options
......
...@@ -8,8 +8,9 @@ use BookStack\Ownable; ...@@ -8,8 +8,9 @@ use BookStack\Ownable;
8 use BookStack\Page; 8 use BookStack\Page;
9 use BookStack\Role; 9 use BookStack\Role;
10 use BookStack\User; 10 use BookStack\User;
11 +use Illuminate\Database\Connection;
12 +use Illuminate\Database\Eloquent\Builder;
11 use Illuminate\Support\Collection; 13 use Illuminate\Support\Collection;
12 -use Illuminate\Support\Facades\Log;
13 14
14 class PermissionService 15 class PermissionService
15 { 16 {
...@@ -23,6 +24,8 @@ class PermissionService ...@@ -23,6 +24,8 @@ class PermissionService
23 public $chapter; 24 public $chapter;
24 public $page; 25 public $page;
25 26
27 + protected $db;
28 +
26 protected $jointPermission; 29 protected $jointPermission;
27 protected $role; 30 protected $role;
28 31
...@@ -31,18 +34,21 @@ class PermissionService ...@@ -31,18 +34,21 @@ class PermissionService
31 /** 34 /**
32 * PermissionService constructor. 35 * PermissionService constructor.
33 * @param JointPermission $jointPermission 36 * @param JointPermission $jointPermission
37 + * @param Connection $db
34 * @param Book $book 38 * @param Book $book
35 * @param Chapter $chapter 39 * @param Chapter $chapter
36 * @param Page $page 40 * @param Page $page
37 * @param Role $role 41 * @param Role $role
38 */ 42 */
39 - public function __construct(JointPermission $jointPermission, Book $book, Chapter $chapter, Page $page, Role $role) 43 + public function __construct(JointPermission $jointPermission, Connection $db, Book $book, Chapter $chapter, Page $page, Role $role)
40 { 44 {
45 + $this->db = $db;
41 $this->jointPermission = $jointPermission; 46 $this->jointPermission = $jointPermission;
42 $this->role = $role; 47 $this->role = $role;
43 $this->book = $book; 48 $this->book = $book;
44 $this->chapter = $chapter; 49 $this->chapter = $chapter;
45 $this->page = $page; 50 $this->page = $page;
51 + // TODO - Update so admin still goes through filters
46 } 52 }
47 53
48 /** 54 /**
...@@ -151,7 +157,7 @@ class PermissionService ...@@ -151,7 +157,7 @@ class PermissionService
151 */ 157 */
152 public function buildJointPermissionsForEntity(Entity $entity) 158 public function buildJointPermissionsForEntity(Entity $entity)
153 { 159 {
154 - $roles = $this->role->with('jointPermissions')->get(); 160 + $roles = $this->role->get();
155 $entities = collect([$entity]); 161 $entities = collect([$entity]);
156 162
157 if ($entity->isA('book')) { 163 if ($entity->isA('book')) {
...@@ -171,7 +177,7 @@ class PermissionService ...@@ -171,7 +177,7 @@ class PermissionService
171 */ 177 */
172 public function buildJointPermissionsForEntities(Collection $entities) 178 public function buildJointPermissionsForEntities(Collection $entities)
173 { 179 {
174 - $roles = $this->role->with('jointPermissions')->get(); 180 + $roles = $this->role->get();
175 $this->deleteManyJointPermissionsForEntities($entities); 181 $this->deleteManyJointPermissionsForEntities($entities);
176 $this->createManyJointPermissions($entities, $roles); 182 $this->createManyJointPermissions($entities, $roles);
177 } 183 }
...@@ -302,6 +308,10 @@ class PermissionService ...@@ -302,6 +308,10 @@ class PermissionService
302 $explodedAction = explode('-', $action); 308 $explodedAction = explode('-', $action);
303 $restrictionAction = end($explodedAction); 309 $restrictionAction = end($explodedAction);
304 310
311 + if ($role->system_name === 'admin') {
312 + return $this->createJointPermissionDataArray($entity, $role, $action, true, true);
313 + }
314 +
305 if ($entity->isA('book')) { 315 if ($entity->isA('book')) {
306 316
307 if (!$entity->restricted) { 317 if (!$entity->restricted) {
...@@ -395,7 +405,7 @@ class PermissionService ...@@ -395,7 +405,7 @@ class PermissionService
395 $action = end($explodedPermission); 405 $action = end($explodedPermission);
396 $this->currentAction = $action; 406 $this->currentAction = $action;
397 407
398 - $nonJointPermissions = ['restrictions']; 408 + $nonJointPermissions = ['restrictions', 'image', 'attachment'];
399 409
400 // Handle non entity specific jointPermissions 410 // Handle non entity specific jointPermissions
401 if (in_array($explodedPermission[0], $nonJointPermissions)) { 411 if (in_array($explodedPermission[0], $nonJointPermissions)) {
...@@ -411,7 +421,6 @@ class PermissionService ...@@ -411,7 +421,6 @@ class PermissionService
411 $this->currentAction = $permission; 421 $this->currentAction = $permission;
412 } 422 }
413 423
414 -
415 $q = $this->entityRestrictionQuery($baseQuery)->count() > 0; 424 $q = $this->entityRestrictionQuery($baseQuery)->count() > 0;
416 $this->clean(); 425 $this->clean();
417 return $q; 426 return $q;
...@@ -462,60 +471,67 @@ class PermissionService ...@@ -462,60 +471,67 @@ class PermissionService
462 } 471 }
463 472
464 /** 473 /**
465 - * Add restrictions for a page query 474 + * Get the children of a book in an efficient single query, Filtered by the permission system.
466 - * @param $query 475 + * @param integer $book_id
467 - * @param string $action 476 + * @param bool $filterDrafts
468 - * @return mixed 477 + * @return \Illuminate\Database\Query\Builder
469 */ 478 */
470 - public function enforcePageRestrictions($query, $action = 'view') 479 + public function bookChildrenQuery($book_id, $filterDrafts = false) {
471 - { 480 + $pageSelect = $this->db->table('pages')->selectRaw("'BookStack\\\\Page' as entity_type, id, slug, name, text, '' as description, book_id, priority, chapter_id, draft")->where('book_id', '=', $book_id)->where(function($query) use ($filterDrafts) {
472 - // Prevent drafts being visible to others. 481 + $query->where('draft', '=', 0);
473 - $query = $query->where(function ($query) { 482 + if (!$filterDrafts) {
474 - $query->where('draft', '=', false); 483 + $query->orWhere(function($query) {
475 - if ($this->currentUser()) { 484 + $query->where('draft', '=', 1)->where('created_by', '=', $this->currentUser()->id);
476 - $query->orWhere(function ($query) {
477 - $query->where('draft', '=', true)->where('created_by', '=', $this->currentUser()->id);
478 }); 485 });
479 } 486 }
480 }); 487 });
481 - 488 + $chapterSelect = $this->db->table('chapters')->selectRaw("'BookStack\\\\Chapter' as entity_type, id, slug, name, '' as text, description, book_id, priority, 0 as chapter_id, 0 as draft")->where('book_id', '=', $book_id);
482 - return $this->enforceEntityRestrictions($query, $action); 489 + $query = $this->db->query()->select('*')->from($this->db->raw("({$pageSelect->toSql()} UNION {$chapterSelect->toSql()}) AS U"))
490 + ->mergeBindings($pageSelect)->mergeBindings($chapterSelect);
491 +
492 + if (!$this->isAdmin()) {
493 + $whereQuery = $this->db->table('joint_permissions as jp')->selectRaw('COUNT(*)')
494 + ->whereRaw('jp.entity_id=U.id')->whereRaw('jp.entity_type=U.entity_type')
495 + ->where('jp.action', '=', 'view')->whereIn('jp.role_id', $this->getRoles())
496 + ->where(function($query) {
497 + $query->where('jp.has_permission', '=', 1)->orWhere(function($query) {
498 + $query->where('jp.has_permission_own', '=', 1)->where('jp.created_by', '=', $this->currentUser()->id);
499 + });
500 + });
501 + $query->whereRaw("({$whereQuery->toSql()}) > 0")->mergeBindings($whereQuery);
483 } 502 }
484 503
485 - /** 504 + $query->orderBy('draft', 'desc')->orderBy('priority', 'asc');
486 - * Add on permission restrictions to a chapter query. 505 + $this->clean();
487 - * @param $query 506 + return $query;
488 - * @param string $action
489 - * @return mixed
490 - */
491 - public function enforceChapterRestrictions($query, $action = 'view')
492 - {
493 - return $this->enforceEntityRestrictions($query, $action);
494 } 507 }
495 508
496 /** 509 /**
497 - * Add restrictions to a book query. 510 + * Add restrictions for a generic entity
498 - * @param $query 511 + * @param string $entityType
512 + * @param Builder|Entity $query
499 * @param string $action 513 * @param string $action
500 * @return mixed 514 * @return mixed
501 */ 515 */
502 - public function enforceBookRestrictions($query, $action = 'view') 516 + public function enforceEntityRestrictions($entityType, $query, $action = 'view')
503 { 517 {
504 - return $this->enforceEntityRestrictions($query, $action); 518 + if (strtolower($entityType) === 'page') {
519 + // Prevent drafts being visible to others.
520 + $query = $query->where(function ($query) {
521 + $query->where('draft', '=', false);
522 + if ($this->currentUser()) {
523 + $query->orWhere(function ($query) {
524 + $query->where('draft', '=', true)->where('created_by', '=', $this->currentUser()->id);
525 + });
526 + }
527 + });
505 } 528 }
506 529
507 - /**
508 - * Add restrictions for a generic entity
509 - * @param $query
510 - * @param string $action
511 - * @return mixed
512 - */
513 - public function enforceEntityRestrictions($query, $action = 'view')
514 - {
515 if ($this->isAdmin()) { 530 if ($this->isAdmin()) {
516 $this->clean(); 531 $this->clean();
517 return $query; 532 return $query;
518 } 533 }
534 +
519 $this->currentAction = $action; 535 $this->currentAction = $action;
520 return $this->entityRestrictionQuery($query); 536 return $this->entityRestrictionQuery($query);
521 } 537 }
...@@ -553,6 +569,7 @@ class PermissionService ...@@ -553,6 +569,7 @@ class PermissionService
553 }); 569 });
554 }); 570 });
555 }); 571 });
572 + $this->clean();
556 return $q; 573 return $q;
557 } 574 }
558 575
...@@ -601,7 +618,7 @@ class PermissionService ...@@ -601,7 +618,7 @@ class PermissionService
601 private function isAdmin() 618 private function isAdmin()
602 { 619 {
603 if ($this->isAdminUser === null) { 620 if ($this->isAdminUser === null) {
604 - $this->isAdminUser = ($this->currentUser()->id !== null) ? $this->currentUser()->hasRole('admin') : false; 621 + $this->isAdminUser = ($this->currentUser()->id !== null) ? $this->currentUser()->hasSystemRole('admin') : false;
605 } 622 }
606 623
607 return $this->isAdminUser; 624 return $this->isAdminUser;
......
1 <?php namespace BookStack\Services; 1 <?php namespace BookStack\Services;
2 2
3 use BookStack\Setting; 3 use BookStack\Setting;
4 +use BookStack\User;
4 use Illuminate\Contracts\Cache\Repository as Cache; 5 use Illuminate\Contracts\Cache\Repository as Cache;
5 6
6 /** 7 /**
...@@ -38,11 +39,24 @@ class SettingService ...@@ -38,11 +39,24 @@ class SettingService
38 */ 39 */
39 public function get($key, $default = false) 40 public function get($key, $default = false)
40 { 41 {
42 + if ($default === false) $default = config('setting-defaults.' . $key, false);
41 $value = $this->getValueFromStore($key, $default); 43 $value = $this->getValueFromStore($key, $default);
42 return $this->formatValue($value, $default); 44 return $this->formatValue($value, $default);
43 } 45 }
44 46
45 /** 47 /**
48 + * Get a user-specific setting from the database or cache.
49 + * @param User $user
50 + * @param $key
51 + * @param bool $default
52 + * @return bool|string
53 + */
54 + public function getUser($user, $key, $default = false)
55 + {
56 + return $this->get($this->userKey($user->id, $key), $default);
57 + }
58 +
59 + /**
46 * Gets a setting value from the cache or database. 60 * Gets a setting value from the cache or database.
47 * Looks at the system defaults if not cached or in database. 61 * Looks at the system defaults if not cached or in database.
48 * @param $key 62 * @param $key
...@@ -69,14 +83,6 @@ class SettingService ...@@ -69,14 +83,6 @@ class SettingService
69 return $value; 83 return $value;
70 } 84 }
71 85
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 -
80 return $default; 86 return $default;
81 } 87 }
82 88
...@@ -119,6 +125,16 @@ class SettingService ...@@ -119,6 +125,16 @@ class SettingService
119 } 125 }
120 126
121 /** 127 /**
128 + * Check if a user setting is in the database.
129 + * @param $key
130 + * @return bool
131 + */
132 + public function hasUser($key)
133 + {
134 + return $this->has($this->userKey($key));
135 + }
136 +
137 + /**
122 * Add a setting to the database. 138 * Add a setting to the database.
123 * @param $key 139 * @param $key
124 * @param $value 140 * @param $value
...@@ -136,6 +152,28 @@ class SettingService ...@@ -136,6 +152,28 @@ class SettingService
136 } 152 }
137 153
138 /** 154 /**
155 + * Put a user-specific setting into the database.
156 + * @param User $user
157 + * @param $key
158 + * @param $value
159 + * @return bool
160 + */
161 + public function putUser($user, $key, $value)
162 + {
163 + return $this->put($this->userKey($user->id, $key), $value);
164 + }
165 +
166 + /**
167 + * Convert a setting key into a user-specific key.
168 + * @param $key
169 + * @return string
170 + */
171 + protected function userKey($userId, $key = '')
172 + {
173 + return 'user:' . $userId . ':' . $key;
174 + }
175 +
176 + /**
139 * Removes a setting from the database. 177 * Removes a setting from the database.
140 * @param $key 178 * @param $key
141 * @return bool 179 * @return bool
...@@ -151,6 +189,16 @@ class SettingService ...@@ -151,6 +189,16 @@ class SettingService
151 } 189 }
152 190
153 /** 191 /**
192 + * Delete settings for a given user id.
193 + * @param $userId
194 + * @return mixed
195 + */
196 + public function deleteUserSettings($userId)
197 + {
198 + return $this->setting->where('setting_key', 'like', $this->userKey($userId) . '%')->delete();
199 + }
200 +
201 + /**
154 * Gets a setting model from the database for the given key. 202 * Gets a setting model from the database for the given key.
155 * @param $key 203 * @param $key
156 * @return mixed 204 * @return mixed
......
...@@ -70,12 +70,12 @@ class SocialAuthService ...@@ -70,12 +70,12 @@ class SocialAuthService
70 70
71 // Check social account has not already been used 71 // Check social account has not already been used
72 if ($this->socialAccount->where('driver_id', '=', $socialUser->getId())->exists()) { 72 if ($this->socialAccount->where('driver_id', '=', $socialUser->getId())->exists()) {
73 - throw new UserRegistrationException('This ' . $socialDriver . ' account is already in use, Try logging in via the ' . $socialDriver . ' option.', '/login'); 73 + throw new UserRegistrationException(trans('errors.social_account_in_use', ['socialAccount'=>$socialDriver]), '/login');
74 } 74 }
75 75
76 if ($this->userRepo->getByEmail($socialUser->getEmail())) { 76 if ($this->userRepo->getByEmail($socialUser->getEmail())) {
77 $email = $socialUser->getEmail(); 77 $email = $socialUser->getEmail();
78 - throw new UserRegistrationException('The email ' . $email . ' is already in use. If you already have an account you can connect your ' . $socialDriver . ' account from your profile settings.', '/login'); 78 + throw new UserRegistrationException(trans('errors.social_account_in_use', ['socialAccount'=>$socialDriver, 'email' => $email]), '/login');
79 } 79 }
80 80
81 return $socialUser; 81 return $socialUser;
...@@ -98,7 +98,6 @@ class SocialAuthService ...@@ -98,7 +98,6 @@ class SocialAuthService
98 98
99 // Get any attached social accounts or users 99 // Get any attached social accounts or users
100 $socialAccount = $this->socialAccount->where('driver_id', '=', $socialId)->first(); 100 $socialAccount = $this->socialAccount->where('driver_id', '=', $socialId)->first();
101 - $user = $this->userRepo->getByEmail($socialUser->getEmail());
102 $isLoggedIn = auth()->check(); 101 $isLoggedIn = auth()->check();
103 $currentUser = user(); 102 $currentUser = user();
104 103
...@@ -113,27 +112,26 @@ class SocialAuthService ...@@ -113,27 +112,26 @@ class SocialAuthService
113 if ($isLoggedIn && $socialAccount === null) { 112 if ($isLoggedIn && $socialAccount === null) {
114 $this->fillSocialAccount($socialDriver, $socialUser); 113 $this->fillSocialAccount($socialDriver, $socialUser);
115 $currentUser->socialAccounts()->save($this->socialAccount); 114 $currentUser->socialAccounts()->save($this->socialAccount);
116 - session()->flash('success', title_case($socialDriver) . ' account was successfully attached to your profile.'); 115 + session()->flash('success', trans('settings.users_social_connected', ['socialAccount' => title_case($socialDriver)]));
117 return redirect($currentUser->getEditUrl()); 116 return redirect($currentUser->getEditUrl());
118 } 117 }
119 118
120 // When a user is logged in and the social account exists and is already linked to the current user. 119 // When a user is logged in and the social account exists and is already linked to the current user.
121 if ($isLoggedIn && $socialAccount !== null && $socialAccount->user->id === $currentUser->id) { 120 if ($isLoggedIn && $socialAccount !== null && $socialAccount->user->id === $currentUser->id) {
122 - session()->flash('error', 'This ' . title_case($socialDriver) . ' account is already attached to your profile.'); 121 + session()->flash('error', trans('errors.social_account_existing', ['socialAccount' => title_case($socialDriver)]));
123 return redirect($currentUser->getEditUrl()); 122 return redirect($currentUser->getEditUrl());
124 } 123 }
125 124
126 // When a user is logged in, A social account exists but the users do not match. 125 // When a user is logged in, A social account exists but the users do not match.
127 - // Change the user that the social account is assigned to.
128 if ($isLoggedIn && $socialAccount !== null && $socialAccount->user->id != $currentUser->id) { 126 if ($isLoggedIn && $socialAccount !== null && $socialAccount->user->id != $currentUser->id) {
129 - session()->flash('success', 'This ' . title_case($socialDriver) . ' account is already used by another user.'); 127 + session()->flash('error', trans('errors.social_account_already_used_existing', ['socialAccount' => title_case($socialDriver)]));
130 return redirect($currentUser->getEditUrl()); 128 return redirect($currentUser->getEditUrl());
131 } 129 }
132 130
133 // Otherwise let the user know this social account is not used by anyone. 131 // Otherwise let the user know this social account is not used by anyone.
134 - $message = 'This ' . $socialDriver . ' account is not linked to any users. Please attach it in your profile settings'; 132 + $message = trans('errors.social_account_not_used', ['socialAccount' => title_case($socialDriver)]);
135 if (setting('registration-enabled')) { 133 if (setting('registration-enabled')) {
136 - $message .= ' or, If you do not yet have an account, You can register an account using the ' . $socialDriver . ' option'; 134 + $message .= trans('errors.social_account_register_instructions', ['socialAccount' => title_case($socialDriver)]);
137 } 135 }
138 136
139 throw new SocialSignInException($message . '.', '/login'); 137 throw new SocialSignInException($message . '.', '/login');
...@@ -157,8 +155,8 @@ class SocialAuthService ...@@ -157,8 +155,8 @@ class SocialAuthService
157 { 155 {
158 $driver = trim(strtolower($socialDriver)); 156 $driver = trim(strtolower($socialDriver));
159 157
160 - if (!in_array($driver, $this->validSocialDrivers)) abort(404, 'Social Driver Not Found'); 158 + if (!in_array($driver, $this->validSocialDrivers)) abort(404, trans('errors.social_driver_not_found'));
161 - if (!$this->checkDriverConfigured($driver)) throw new SocialDriverNotConfigured("Your {$driver} social settings are not configured correctly."); 159 + if (!$this->checkDriverConfigured($driver)) throw new SocialDriverNotConfigured(trans('errors.social_driver_not_configured', ['socialAccount' => title_case($socialDriver)]));
162 160
163 return $driver; 161 return $driver;
164 } 162 }
...@@ -215,7 +213,7 @@ class SocialAuthService ...@@ -215,7 +213,7 @@ class SocialAuthService
215 { 213 {
216 session(); 214 session();
217 user()->socialAccounts()->where('driver', '=', $socialDriver)->delete(); 215 user()->socialAccounts()->where('driver', '=', $socialDriver)->delete();
218 - session()->flash('success', title_case($socialDriver) . ' account successfully detached'); 216 + session()->flash('success', trans('settings.users_social_disconnected', ['socialAccount' => title_case($socialDriver)]));
219 return redirect(user()->getEditUrl()); 217 return redirect(user()->getEditUrl());
220 } 218 }
221 219
......
...@@ -5,9 +5,7 @@ use BookStack\View; ...@@ -5,9 +5,7 @@ use BookStack\View;
5 5
6 class ViewService 6 class ViewService
7 { 7 {
8 -
9 protected $view; 8 protected $view;
10 - protected $user;
11 protected $permissionService; 9 protected $permissionService;
12 10
13 /** 11 /**
...@@ -18,7 +16,6 @@ class ViewService ...@@ -18,7 +16,6 @@ class ViewService
18 public function __construct(View $view, PermissionService $permissionService) 16 public function __construct(View $view, PermissionService $permissionService)
19 { 17 {
20 $this->view = $view; 18 $this->view = $view;
21 - $this->user = user();
22 $this->permissionService = $permissionService; 19 $this->permissionService = $permissionService;
23 } 20 }
24 21
...@@ -29,8 +26,9 @@ class ViewService ...@@ -29,8 +26,9 @@ class ViewService
29 */ 26 */
30 public function add(Entity $entity) 27 public function add(Entity $entity)
31 { 28 {
32 - if ($this->user === null) return 0; 29 + $user = user();
33 - $view = $entity->views()->where('user_id', '=', $this->user->id)->first(); 30 + if ($user === null || $user->isDefault()) return 0;
31 + $view = $entity->views()->where('user_id', '=', $user->id)->first();
34 // Add view if model exists 32 // Add view if model exists
35 if ($view) { 33 if ($view) {
36 $view->increment('views'); 34 $view->increment('views');
...@@ -39,7 +37,7 @@ class ViewService ...@@ -39,7 +37,7 @@ class ViewService
39 37
40 // Otherwise create new view count 38 // Otherwise create new view count
41 $entity->views()->save($this->view->create([ 39 $entity->views()->save($this->view->create([
42 - 'user_id' => $this->user->id, 40 + 'user_id' => $user->id,
43 'views' => 1 41 'views' => 1
44 ])); 42 ]));
45 43
...@@ -78,13 +76,14 @@ class ViewService ...@@ -78,13 +76,14 @@ class ViewService
78 */ 76 */
79 public function getUserRecentlyViewed($count = 10, $page = 0, $filterModel = false) 77 public function getUserRecentlyViewed($count = 10, $page = 0, $filterModel = false)
80 { 78 {
81 - if ($this->user === null) return collect(); 79 + $user = user();
80 + if ($user === null || $user->isDefault()) return collect();
82 81
83 $query = $this->permissionService 82 $query = $this->permissionService
84 ->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type'); 83 ->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type');
85 84
86 if ($filterModel) $query = $query->where('viewable_type', '=', get_class($filterModel)); 85 if ($filterModel) $query = $query->where('viewable_type', '=', get_class($filterModel));
87 - $query = $query->where('user_id', '=', user()->id); 86 + $query = $query->where('user_id', '=', $user->id);
88 87
89 $viewables = $query->with('viewable')->orderBy('updated_at', 'desc') 88 $viewables = $query->with('viewable')->orderBy('updated_at', 'desc')
90 ->skip($count * $page)->take($count)->get()->pluck('viewable'); 89 ->skip($count * $page)->take($count)->get()->pluck('viewable');
......
...@@ -75,6 +75,16 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon ...@@ -75,6 +75,16 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
75 } 75 }
76 76
77 /** 77 /**
78 + * Check if the user has a role.
79 + * @param $role
80 + * @return mixed
81 + */
82 + public function hasSystemRole($role)
83 + {
84 + return $this->roles->pluck('system_name')->contains('admin');
85 + }
86 +
87 + /**
78 * Get all permissions belonging to a the current user. 88 * Get all permissions belonging to a the current user.
79 * @param bool $cache 89 * @param bool $cache
80 * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough 90 * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough
...@@ -150,8 +160,16 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon ...@@ -150,8 +160,16 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
150 */ 160 */
151 public function getAvatar($size = 50) 161 public function getAvatar($size = 50)
152 { 162 {
153 - if ($this->image_id === 0 || $this->image_id === '0' || $this->image_id === null) return baseUrl('/user_avatar.png'); 163 + $default = baseUrl('/user_avatar.png');
154 - return baseUrl($this->avatar->getThumb($size, $size, false)); 164 + $imageId = $this->image_id;
165 + if ($imageId === 0 || $imageId === '0' || $imageId === null) return $default;
166 +
167 + try {
168 + $avatar = baseUrl($this->avatar->getThumb($size, $size, false));
169 + } catch (\Exception $err) {
170 + $avatar = $default;
171 + }
172 + return $avatar;
155 } 173 }
156 174
157 /** 175 /**
......
...@@ -60,11 +60,12 @@ function userCan($permission, Ownable $ownable = null) ...@@ -60,11 +60,12 @@ function userCan($permission, Ownable $ownable = null)
60 * Helper to access system settings. 60 * Helper to access system settings.
61 * @param $key 61 * @param $key
62 * @param bool $default 62 * @param bool $default
63 - * @return mixed 63 + * @return bool|string|\BookStack\Services\SettingService
64 */ 64 */
65 -function setting($key, $default = false) 65 +function setting($key = null, $default = false)
66 { 66 {
67 $settingService = app(\BookStack\Services\SettingService::class); 67 $settingService = app(\BookStack\Services\SettingService::class);
68 + if (is_null($key)) return $settingService;
68 return $settingService->get($key, $default); 69 return $settingService->get($key, $default);
69 } 70 }
70 71
......
...@@ -15,7 +15,8 @@ ...@@ -15,7 +15,8 @@
15 "league/flysystem-aws-s3-v3": "^1.0", 15 "league/flysystem-aws-s3-v3": "^1.0",
16 "barryvdh/laravel-dompdf": "^0.7", 16 "barryvdh/laravel-dompdf": "^0.7",
17 "predis/predis": "^1.1", 17 "predis/predis": "^1.1",
18 - "gathercontent/htmldiff": "^0.2.1" 18 + "gathercontent/htmldiff": "^0.2.1",
19 + "barryvdh/laravel-snappy": "^0.3.1"
19 }, 20 },
20 "require-dev": { 21 "require-dev": {
21 "fzaninotto/faker": "~1.4", 22 "fzaninotto/faker": "~1.4",
......
...@@ -4,8 +4,8 @@ ...@@ -4,8 +4,8 @@
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": "3124d900cfe857392a94de479f3ff6d4", 7 + "hash": "2438a2f4a02adbea5f378f9e9408eb29",
8 - "content-hash": "a968767a73f77e66e865c276cf76eedf", 8 + "content-hash": "6add8bff71ecc86e0c90858590834a26",
9 "packages": [ 9 "packages": [
10 { 10 {
11 "name": "aws/aws-sdk-php", 11 "name": "aws/aws-sdk-php",
...@@ -256,6 +256,58 @@ ...@@ -256,6 +256,58 @@
256 "time": "2016-07-04 11:52:48" 256 "time": "2016-07-04 11:52:48"
257 }, 257 },
258 { 258 {
259 + "name": "barryvdh/laravel-snappy",
260 + "version": "v0.3.1",
261 + "source": {
262 + "type": "git",
263 + "url": "https://github.com/barryvdh/laravel-snappy.git",
264 + "reference": "509a4497be63d8ee7ff464a3daf00d9edde08e21"
265 + },
266 + "dist": {
267 + "type": "zip",
268 + "url": "https://api.github.com/repos/barryvdh/laravel-snappy/zipball/509a4497be63d8ee7ff464a3daf00d9edde08e21",
269 + "reference": "509a4497be63d8ee7ff464a3daf00d9edde08e21",
270 + "shasum": ""
271 + },
272 + "require": {
273 + "illuminate/filesystem": "5.0.x|5.1.x|5.2.x|5.3.x",
274 + "illuminate/support": "5.0.x|5.1.x|5.2.x|5.3.x",
275 + "knplabs/knp-snappy": "*",
276 + "php": ">=5.4.0"
277 + },
278 + "type": "library",
279 + "extra": {
280 + "branch-alias": {
281 + "dev-master": "0.3-dev"
282 + }
283 + },
284 + "autoload": {
285 + "psr-4": {
286 + "Barryvdh\\Snappy\\": "src/"
287 + }
288 + },
289 + "notification-url": "https://packagist.org/downloads/",
290 + "license": [
291 + "MIT"
292 + ],
293 + "authors": [
294 + {
295 + "name": "Barry vd. Heuvel",
296 + "email": "barryvdh@gmail.com"
297 + }
298 + ],
299 + "description": "Snappy PDF/Image for Laravel 4",
300 + "keywords": [
301 + "image",
302 + "laravel",
303 + "pdf",
304 + "snappy",
305 + "wkhtmltoimage",
306 + "wkhtmltopdf"
307 + ],
308 + "time": "2016-08-05 13:08:28"
309 + },
310 + {
259 "name": "barryvdh/reflection-docblock", 311 "name": "barryvdh/reflection-docblock",
260 "version": "v2.0.4", 312 "version": "v2.0.4",
261 "source": { 313 "source": {
...@@ -998,6 +1050,71 @@ ...@@ -998,6 +1050,71 @@
998 "time": "2015-12-05 17:17:57" 1050 "time": "2015-12-05 17:17:57"
999 }, 1051 },
1000 { 1052 {
1053 + "name": "knplabs/knp-snappy",
1054 + "version": "0.4.3",
1055 + "source": {
1056 + "type": "git",
1057 + "url": "https://github.com/KnpLabs/snappy.git",
1058 + "reference": "44f7a9b37d5686fd7db4c1e9569a802a5d16923f"
1059 + },
1060 + "dist": {
1061 + "type": "zip",
1062 + "url": "https://api.github.com/repos/KnpLabs/snappy/zipball/44f7a9b37d5686fd7db4c1e9569a802a5d16923f",
1063 + "reference": "44f7a9b37d5686fd7db4c1e9569a802a5d16923f",
1064 + "shasum": ""
1065 + },
1066 + "require": {
1067 + "php": ">=5.3.3",
1068 + "symfony/process": "~2.3|~3.0"
1069 + },
1070 + "require-dev": {
1071 + "phpunit/phpunit": "~4.7"
1072 + },
1073 + "suggest": {
1074 + "h4cc/wkhtmltoimage-amd64": "Provides wkhtmltoimage-amd64 binary for Linux-compatible machines, use version `~0.12` as dependency",
1075 + "h4cc/wkhtmltoimage-i386": "Provides wkhtmltoimage-i386 binary for Linux-compatible machines, use version `~0.12` as dependency",
1076 + "h4cc/wkhtmltopdf-amd64": "Provides wkhtmltopdf-amd64 binary for Linux-compatible machines, use version `~0.12` as dependency",
1077 + "h4cc/wkhtmltopdf-i386": "Provides wkhtmltopdf-i386 binary for Linux-compatible machines, use version `~0.12` as dependency",
1078 + "wemersonjanuario/wkhtmltopdf-windows": "Provides wkhtmltopdf executable for Windows, use version `~0.12` as dependency"
1079 + },
1080 + "type": "library",
1081 + "extra": {
1082 + "branch-alias": {
1083 + "dev-master": "0.5.x-dev"
1084 + }
1085 + },
1086 + "autoload": {
1087 + "psr-0": {
1088 + "Knp\\Snappy": "src/"
1089 + }
1090 + },
1091 + "notification-url": "https://packagist.org/downloads/",
1092 + "license": [
1093 + "MIT"
1094 + ],
1095 + "authors": [
1096 + {
1097 + "name": "KNPLabs Team",
1098 + "homepage": "http://knplabs.com"
1099 + },
1100 + {
1101 + "name": "Symfony Community",
1102 + "homepage": "http://github.com/KnpLabs/snappy/contributors"
1103 + }
1104 + ],
1105 + "description": "PHP5 library allowing thumbnail, snapshot or PDF generation from a url or a html page. Wrapper for wkhtmltopdf/wkhtmltoimage.",
1106 + "homepage": "http://github.com/KnpLabs/snappy",
1107 + "keywords": [
1108 + "knp",
1109 + "knplabs",
1110 + "pdf",
1111 + "snapshot",
1112 + "thumbnail",
1113 + "wkhtmltopdf"
1114 + ],
1115 + "time": "2015-11-17 13:16:27"
1116 + },
1117 + {
1001 "name": "laravel/framework", 1118 "name": "laravel/framework",
1002 "version": "v5.3.11", 1119 "version": "v5.3.11",
1003 "source": { 1120 "source": {
......
...@@ -148,6 +148,7 @@ return [ ...@@ -148,6 +148,7 @@ return [
148 Barryvdh\DomPDF\ServiceProvider::class, 148 Barryvdh\DomPDF\ServiceProvider::class,
149 Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class, 149 Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class,
150 Barryvdh\Debugbar\ServiceProvider::class, 150 Barryvdh\Debugbar\ServiceProvider::class,
151 + Barryvdh\Snappy\ServiceProvider::class,
151 152
152 153
153 /* 154 /*
...@@ -218,6 +219,7 @@ return [ ...@@ -218,6 +219,7 @@ return [
218 219
219 'ImageTool' => Intervention\Image\Facades\Image::class, 220 'ImageTool' => Intervention\Image\Facades\Image::class,
220 'PDF' => Barryvdh\DomPDF\Facade::class, 221 'PDF' => Barryvdh\DomPDF\Facade::class,
222 + 'SnappyPDF' => Barryvdh\Snappy\Facades\SnappyPdf::class,
221 'Debugbar' => Barryvdh\Debugbar\Facade::class, 223 'Debugbar' => Barryvdh\Debugbar\Facade::class,
222 224
223 /** 225 /**
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
6 return [ 6 return [
7 7
8 'app-name' => 'BookStack', 8 'app-name' => 'BookStack',
9 + 'app-logo' => '',
9 'app-name-header' => true, 10 'app-name-header' => true,
10 'app-editor' => 'wysiwyg', 11 'app-editor' => 'wysiwyg',
11 'app-color' => '#0288D1', 12 'app-color' => '#0288D1',
......
1 +<?php
2 +
3 +return [
4 + 'pdf' => [
5 + 'enabled' => true,
6 + 'binary' => file_exists(base_path('wkhtmltopdf')) ? base_path('wkhtmltopdf') : env('WKHTMLTOPDF', false),
7 + 'timeout' => false,
8 + 'options' => [],
9 + 'env' => [],
10 + ],
11 + 'image' => [
12 + 'enabled' => false,
13 + 'binary' => '/usr/local/bin/wkhtmltoimage',
14 + 'timeout' => false,
15 + 'options' => [],
16 + 'env' => [],
17 + ],
18 +];
...@@ -60,3 +60,13 @@ $factory->define(BookStack\Tag::class, function ($faker) { ...@@ -60,3 +60,13 @@ $factory->define(BookStack\Tag::class, function ($faker) {
60 'value' => $faker->sentence(3) 60 'value' => $faker->sentence(3)
61 ]; 61 ];
62 }); 62 });
63 +
64 +$factory->define(BookStack\Image::class, function ($faker) {
65 + return [
66 + 'name' => $faker->slug . '.jpg',
67 + 'url' => $faker->url,
68 + 'path' => $faker->url,
69 + 'type' => 'gallery',
70 + 'uploaded_to' => 0
71 + ];
72 +});
...\ No newline at end of file ...\ No newline at end of file
......
1 +<?php
2 +
3 +use Illuminate\Support\Facades\Schema;
4 +use Illuminate\Database\Schema\Blueprint;
5 +use Illuminate\Database\Migrations\Migration;
6 +
7 +class CreateCacheTable extends Migration
8 +{
9 + /**
10 + * Run the migrations.
11 + *
12 + * @return void
13 + */
14 + public function up()
15 + {
16 + Schema::create('cache', function (Blueprint $table) {
17 + $table->string('key')->unique();
18 + $table->text('value');
19 + $table->integer('expiration');
20 + });
21 + }
22 +
23 + /**
24 + * Reverse the migrations.
25 + *
26 + * @return void
27 + */
28 + public function down()
29 + {
30 + Schema::dropIfExists('cache');
31 + }
32 +}
1 +<?php
2 +
3 +use Illuminate\Support\Facades\Schema;
4 +use Illuminate\Database\Schema\Blueprint;
5 +use Illuminate\Database\Migrations\Migration;
6 +
7 +class CreateSessionsTable extends Migration
8 +{
9 + /**
10 + * Run the migrations.
11 + *
12 + * @return void
13 + */
14 + public function up()
15 + {
16 + Schema::create('sessions', function (Blueprint $table) {
17 + $table->string('id')->unique();
18 + $table->integer('user_id')->nullable();
19 + $table->string('ip_address', 45)->nullable();
20 + $table->text('user_agent')->nullable();
21 + $table->text('payload');
22 + $table->integer('last_activity');
23 + });
24 + }
25 +
26 + /**
27 + * Reverse the migrations.
28 + *
29 + * @return void
30 + */
31 + public function down()
32 + {
33 + Schema::dropIfExists('sessions');
34 + }
35 +}
1 { 1 {
2 "private": true, 2 "private": true,
3 "scripts": { 3 "scripts": {
4 - "prod": "gulp --production", 4 + "build": "gulp --production",
5 - "dev": "gulp watch" 5 + "dev": "gulp watch",
6 + "watch": "gulp watch"
6 }, 7 },
7 "devDependencies": { 8 "devDependencies": {
8 "angular": "^1.5.5", 9 "angular": "^1.5.5",
...@@ -15,7 +16,9 @@ ...@@ -15,7 +16,9 @@
15 "laravel-elixir": "^6.0.0-11", 16 "laravel-elixir": "^6.0.0-11",
16 "laravel-elixir-browserify-official": "^0.1.3", 17 "laravel-elixir-browserify-official": "^0.1.3",
17 "marked": "^0.3.5", 18 "marked": "^0.3.5",
18 - "moment": "^2.12.0", 19 + "moment": "^2.12.0"
19 - "zeroclipboard": "^2.2.0" 20 + },
21 + "dependencies": {
22 + "clipboard": "^1.5.16"
20 } 23 }
21 } 24 }
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
22 <php> 22 <php>
23 <env name="APP_ENV" value="testing"/> 23 <env name="APP_ENV" value="testing"/>
24 <env name="APP_DEBUG" value="false"/> 24 <env name="APP_DEBUG" value="false"/>
25 + <env name="APP_LANG" value="en"/>
25 <env name="CACHE_DRIVER" value="array"/> 26 <env name="CACHE_DRIVER" value="array"/>
26 <env name="SESSION_DRIVER" value="array"/> 27 <env name="SESSION_DRIVER" value="array"/>
27 <env name="QUEUE_DRIVER" value="sync"/> 28 <env name="QUEUE_DRIVER" value="sync"/>
......
No preview for this file type
...@@ -17,25 +17,42 @@ A platform for storing and organising information and documentation. General inf ...@@ -17,25 +17,42 @@ A platform for storing and organising information and documentation. General inf
17 17
18 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: 18 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:
19 19
20 -* [Node.js](https://nodejs.org/en/) 20 +* [Node.js](https://nodejs.org/en/) v6.9+
21 -* [Gulp](http://gulpjs.com/)
22 21
23 -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. 22 +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. To run the build task you can use the following commands:
23 +
24 +``` bash
25 +# Build and minify for production
26 +npm run-script build
27 +
28 +# Build for dev (With sourcemaps) and watch for changes
29 +npm run-script dev
30 +```
24 31
25 BookStack has many integration tests that use Laravel's built-in testing capabilities which makes use of PHPUnit. To use you will need PHPUnit installed and accessible via command line. There is a `mysql_testing` database defined within the app config which is what is used by PHPUnit. This database is set with the following database name, user name and password defined as `bookstack-test`. You will have to create that database and credentials before testing. 32 BookStack has many integration tests that use Laravel's built-in testing capabilities which makes use of PHPUnit. To use you will need PHPUnit installed and accessible via command line. There is a `mysql_testing` database defined within the app config which is what is used by PHPUnit. This database is set with the following database name, user name and password defined as `bookstack-test`. You will have to create that database and credentials before testing.
26 33
27 The testing database will also need migrating and seeding beforehand. This can be done with the following commands: 34 The testing database will also need migrating and seeding beforehand. This can be done with the following commands:
28 35
29 -``` 36 +``` bash
30 php artisan migrate --database=mysql_testing 37 php artisan migrate --database=mysql_testing
31 php artisan db:seed --class=DummyContentSeeder --database=mysql_testing 38 php artisan db:seed --class=DummyContentSeeder --database=mysql_testing
32 ``` 39 ```
33 40
34 Once done you can run `phpunit` in the application root directory to run all tests. 41 Once done you can run `phpunit` in the application root directory to run all tests.
35 42
43 +## Translations
44 +
45 +As part of BookStack v0.14 support for translations has been built in. All text strings can be found in the `resources/lang` folder where each language option has its own folder. To add a new language you should copy the `en` folder to an new folder (eg. `fr` for french) then go through and translate all text strings in those files, leaving the keys and file-names intact. If a language string is missing then the `en` translation will be used. To show the language option in the user preferences language drop-down you will need to add your language to the options found at the bottom of the `resources/lang/en/settings.php` file. A system-wide language can also be set in the `.env` file like so: `APP_LANG=en`.
46 +
47 + Some strings have colon-prefixed variables in such as `:userName`. Leave these values as they are as they will be replaced at run-time.
48 +
49 +## Website, Docs & Blog
50 +
51 +The website project docs & Blog can be found in the [BookStackApp/website](https://github.com/BookStackApp/website) repo.
52 +
36 ## License 53 ## License
37 54
38 -BookStack is provided under the MIT License. 55 +The BookStack source is provided under the MIT License.
39 56
40 ## Attribution 57 ## Attribution
41 58
...@@ -53,5 +70,11 @@ These are the great projects used to help build BookStack: ...@@ -53,5 +70,11 @@ These are the great projects used to help build BookStack:
53 * [TinyColorPicker](http://www.dematte.at/tinyColorPicker/index.html) 70 * [TinyColorPicker](http://www.dematte.at/tinyColorPicker/index.html)
54 * [Marked](https://github.com/chjj/marked) 71 * [Marked](https://github.com/chjj/marked)
55 * [Moment.js](http://momentjs.com/) 72 * [Moment.js](http://momentjs.com/)
73 +* [BarryVD](https://github.com/barryvdh)
74 + * [Debugbar](https://github.com/barryvdh/laravel-debugbar)
75 + * [Dompdf](https://github.com/barryvdh/laravel-dompdf)
76 + * [Snappy (WKHTML2PDF)](https://github.com/barryvdh/laravel-snappy)
77 + * [Laravel IDE helper](https://github.com/barryvdh/laravel-ide-helper)
78 +* [WKHTMLtoPDF](http://wkhtmltopdf.org/index.html)
56 79
57 Additionally, Thank you [BrowserStack](https://www.browserstack.com/) for supporting us and making cross-browser testing easy. 80 Additionally, Thank you [BrowserStack](https://www.browserstack.com/) for supporting us and making cross-browser testing easy.
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
2 2
3 import moment from 'moment'; 3 import moment from 'moment';
4 import 'moment/locale/en-gb'; 4 import 'moment/locale/en-gb';
5 +import editorOptions from "./pages/page-form";
6 +
5 moment.locale('en-gb'); 7 moment.locale('en-gb');
6 8
7 export default function (ngApp, events) { 9 export default function (ngApp, events) {
...@@ -23,14 +25,14 @@ export default function (ngApp, events) { ...@@ -23,14 +25,14 @@ export default function (ngApp, events) {
23 $scope.searching = false; 25 $scope.searching = false;
24 $scope.searchTerm = ''; 26 $scope.searchTerm = '';
25 27
26 - var page = 0; 28 + let page = 0;
27 - var previousClickTime = 0; 29 + let previousClickTime = 0;
28 - var previousClickImage = 0; 30 + let previousClickImage = 0;
29 - var dataLoaded = false; 31 + let dataLoaded = false;
30 - var callback = false; 32 + let callback = false;
31 33
32 - var preSearchImages = []; 34 + let preSearchImages = [];
33 - var preSearchHasMore = false; 35 + let preSearchHasMore = false;
34 36
35 /** 37 /**
36 * Used by dropzone to get the endpoint to upload to. 38 * Used by dropzone to get the endpoint to upload to.
...@@ -62,7 +64,7 @@ export default function (ngApp, events) { ...@@ -62,7 +64,7 @@ export default function (ngApp, events) {
62 $scope.$apply(() => { 64 $scope.$apply(() => {
63 $scope.images.unshift(data); 65 $scope.images.unshift(data);
64 }); 66 });
65 - events.emit('success', 'Image uploaded'); 67 + events.emit('success', trans('components.image_upload_success'));
66 }; 68 };
67 69
68 /** 70 /**
...@@ -79,9 +81,9 @@ export default function (ngApp, events) { ...@@ -79,9 +81,9 @@ export default function (ngApp, events) {
79 * @param image 81 * @param image
80 */ 82 */
81 $scope.imageSelect = function (image) { 83 $scope.imageSelect = function (image) {
82 - var dblClickTime = 300; 84 + let dblClickTime = 300;
83 - var currentTime = Date.now(); 85 + let currentTime = Date.now();
84 - var timeDiff = currentTime - previousClickTime; 86 + let timeDiff = currentTime - previousClickTime;
85 87
86 if (timeDiff < dblClickTime && image.id === previousClickImage) { 88 if (timeDiff < dblClickTime && image.id === previousClickImage) {
87 // If double click 89 // If double click
...@@ -137,22 +139,21 @@ export default function (ngApp, events) { ...@@ -137,22 +139,21 @@ export default function (ngApp, events) {
137 $('#image-manager').find('.overlay').fadeOut(240); 139 $('#image-manager').find('.overlay').fadeOut(240);
138 }; 140 };
139 141
140 - var baseUrl = window.baseUrl('/images/' + $scope.imageType + '/all/'); 142 + let baseUrl = window.baseUrl('/images/' + $scope.imageType + '/all/');
141 143
142 /** 144 /**
143 * Fetch the list image data from the server. 145 * Fetch the list image data from the server.
144 */ 146 */
145 function fetchData() { 147 function fetchData() {
146 - var url = baseUrl + page + '?'; 148 + let url = baseUrl + page + '?';
147 - var components = {}; 149 + let components = {};
148 if ($scope.uploadedTo) components['page_id'] = $scope.uploadedTo; 150 if ($scope.uploadedTo) components['page_id'] = $scope.uploadedTo;
149 if ($scope.searching) components['term'] = $scope.searchTerm; 151 if ($scope.searching) components['term'] = $scope.searchTerm;
150 152
151 153
152 - var urlQueryString = Object.keys(components).map((key) => { 154 + url += Object.keys(components).map((key) => {
153 return key + '=' + encodeURIComponent(components[key]); 155 return key + '=' + encodeURIComponent(components[key]);
154 }).join('&'); 156 }).join('&');
155 - url += urlQueryString;
156 157
157 $http.get(url).then((response) => { 158 $http.get(url).then((response) => {
158 $scope.images = $scope.images.concat(response.data.images); 159 $scope.images = $scope.images.concat(response.data.images);
...@@ -205,13 +206,13 @@ export default function (ngApp, events) { ...@@ -205,13 +206,13 @@ export default function (ngApp, events) {
205 */ 206 */
206 $scope.saveImageDetails = function (event) { 207 $scope.saveImageDetails = function (event) {
207 event.preventDefault(); 208 event.preventDefault();
208 - var url = window.baseUrl('/images/update/' + $scope.selectedImage.id); 209 + let url = window.baseUrl('/images/update/' + $scope.selectedImage.id);
209 $http.put(url, this.selectedImage).then(response => { 210 $http.put(url, this.selectedImage).then(response => {
210 - events.emit('success', 'Image details updated'); 211 + events.emit('success', trans('components.image_update_success'));
211 }, (response) => { 212 }, (response) => {
212 if (response.status === 422) { 213 if (response.status === 422) {
213 - var errors = response.data; 214 + let errors = response.data;
214 - var message = ''; 215 + let message = '';
215 Object.keys(errors).forEach((key) => { 216 Object.keys(errors).forEach((key) => {
216 message += errors[key].join('\n'); 217 message += errors[key].join('\n');
217 }); 218 });
...@@ -230,13 +231,13 @@ export default function (ngApp, events) { ...@@ -230,13 +231,13 @@ export default function (ngApp, events) {
230 */ 231 */
231 $scope.deleteImage = function (event) { 232 $scope.deleteImage = function (event) {
232 event.preventDefault(); 233 event.preventDefault();
233 - var force = $scope.dependantPages !== false; 234 + let force = $scope.dependantPages !== false;
234 - var url = window.baseUrl('/images/' + $scope.selectedImage.id); 235 + let url = window.baseUrl('/images/' + $scope.selectedImage.id);
235 if (force) url += '?force=true'; 236 if (force) url += '?force=true';
236 $http.delete(url).then((response) => { 237 $http.delete(url).then((response) => {
237 $scope.images.splice($scope.images.indexOf($scope.selectedImage), 1); 238 $scope.images.splice($scope.images.indexOf($scope.selectedImage), 1);
238 $scope.selectedImage = false; 239 $scope.selectedImage = false;
239 - events.emit('success', 'Image successfully deleted'); 240 + events.emit('success', trans('components.image_delete_success'));
240 }, (response) => { 241 }, (response) => {
241 // Pages failure 242 // Pages failure
242 if (response.status === 400) { 243 if (response.status === 400) {
...@@ -266,11 +267,11 @@ export default function (ngApp, events) { ...@@ -266,11 +267,11 @@ export default function (ngApp, events) {
266 267
267 $scope.searchBook = function (e) { 268 $scope.searchBook = function (e) {
268 e.preventDefault(); 269 e.preventDefault();
269 - var term = $scope.searchTerm; 270 + let term = $scope.searchTerm;
270 if (term.length == 0) return; 271 if (term.length == 0) return;
271 $scope.searching = true; 272 $scope.searching = true;
272 $scope.searchResults = ''; 273 $scope.searchResults = '';
273 - var searchUrl = window.baseUrl('/search/book/' + $attrs.bookId); 274 + let searchUrl = window.baseUrl('/search/book/' + $attrs.bookId);
274 searchUrl += '?term=' + encodeURIComponent(term); 275 searchUrl += '?term=' + encodeURIComponent(term);
275 $http.get(searchUrl).then((response) => { 276 $http.get(searchUrl).then((response) => {
276 $scope.searchResults = $sce.trustAsHtml(response.data); 277 $scope.searchResults = $sce.trustAsHtml(response.data);
...@@ -294,27 +295,27 @@ export default function (ngApp, events) { ...@@ -294,27 +295,27 @@ export default function (ngApp, events) {
294 ngApp.controller('PageEditController', ['$scope', '$http', '$attrs', '$interval', '$timeout', '$sce', 295 ngApp.controller('PageEditController', ['$scope', '$http', '$attrs', '$interval', '$timeout', '$sce',
295 function ($scope, $http, $attrs, $interval, $timeout, $sce) { 296 function ($scope, $http, $attrs, $interval, $timeout, $sce) {
296 297
297 - $scope.editorOptions = require('./pages/page-form'); 298 + $scope.editorOptions = editorOptions();
298 $scope.editContent = ''; 299 $scope.editContent = '';
299 $scope.draftText = ''; 300 $scope.draftText = '';
300 - var pageId = Number($attrs.pageId); 301 + let pageId = Number($attrs.pageId);
301 - var isEdit = pageId !== 0; 302 + let isEdit = pageId !== 0;
302 - var autosaveFrequency = 30; // AutoSave interval in seconds. 303 + let autosaveFrequency = 30; // AutoSave interval in seconds.
303 - var isMarkdown = $attrs.editorType === 'markdown'; 304 + let isMarkdown = $attrs.editorType === 'markdown';
304 $scope.draftsEnabled = $attrs.draftsEnabled === 'true'; 305 $scope.draftsEnabled = $attrs.draftsEnabled === 'true';
305 $scope.isUpdateDraft = Number($attrs.pageUpdateDraft) === 1; 306 $scope.isUpdateDraft = Number($attrs.pageUpdateDraft) === 1;
306 $scope.isNewPageDraft = Number($attrs.pageNewDraft) === 1; 307 $scope.isNewPageDraft = Number($attrs.pageNewDraft) === 1;
307 308
308 // Set initial header draft text 309 // Set initial header draft text
309 if ($scope.isUpdateDraft || $scope.isNewPageDraft) { 310 if ($scope.isUpdateDraft || $scope.isNewPageDraft) {
310 - $scope.draftText = 'Editing Draft' 311 + $scope.draftText = trans('entities.pages_editing_draft');
311 } else { 312 } else {
312 - $scope.draftText = 'Editing Page' 313 + $scope.draftText = trans('entities.pages_editing_page');
313 } 314 }
314 315
315 - var autoSave = false; 316 + let autoSave = false;
316 317
317 - var currentContent = { 318 + let currentContent = {
318 title: false, 319 title: false,
319 html: false 320 html: false
320 }; 321 };
...@@ -351,8 +352,8 @@ export default function (ngApp, events) { ...@@ -351,8 +352,8 @@ export default function (ngApp, events) {
351 autoSave = $interval(() => { 352 autoSave = $interval(() => {
352 // Return if manually saved recently to prevent bombarding the server 353 // Return if manually saved recently to prevent bombarding the server
353 if (Date.now() - lastSave < (1000*autosaveFrequency)/2) return; 354 if (Date.now() - lastSave < (1000*autosaveFrequency)/2) return;
354 - var newTitle = $('#name').val(); 355 + let newTitle = $('#name').val();
355 - var newHtml = $scope.editContent; 356 + let newHtml = $scope.editContent;
356 357
357 if (newTitle !== currentContent.title || newHtml !== currentContent.html) { 358 if (newTitle !== currentContent.title || newHtml !== currentContent.html) {
358 currentContent.html = newHtml; 359 currentContent.html = newHtml;
...@@ -369,7 +370,7 @@ export default function (ngApp, events) { ...@@ -369,7 +370,7 @@ export default function (ngApp, events) {
369 */ 370 */
370 function saveDraft() { 371 function saveDraft() {
371 if (!$scope.draftsEnabled) return; 372 if (!$scope.draftsEnabled) return;
372 - var data = { 373 + let data = {
373 name: $('#name').val(), 374 name: $('#name').val(),
374 html: isMarkdown ? $sce.getTrustedHtml($scope.displayContent) : $scope.editContent 375 html: isMarkdown ? $sce.getTrustedHtml($scope.displayContent) : $scope.editContent
375 }; 376 };
...@@ -379,14 +380,14 @@ export default function (ngApp, events) { ...@@ -379,14 +380,14 @@ export default function (ngApp, events) {
379 let url = window.baseUrl('/ajax/page/' + pageId + '/save-draft'); 380 let url = window.baseUrl('/ajax/page/' + pageId + '/save-draft');
380 $http.put(url, data).then(responseData => { 381 $http.put(url, data).then(responseData => {
381 draftErroring = false; 382 draftErroring = false;
382 - var updateTime = moment.utc(moment.unix(responseData.data.timestamp)).toDate(); 383 + let updateTime = moment.utc(moment.unix(responseData.data.timestamp)).toDate();
383 $scope.draftText = responseData.data.message + moment(updateTime).format('HH:mm'); 384 $scope.draftText = responseData.data.message + moment(updateTime).format('HH:mm');
384 if (!$scope.isNewPageDraft) $scope.isUpdateDraft = true; 385 if (!$scope.isNewPageDraft) $scope.isUpdateDraft = true;
385 showDraftSaveNotification(); 386 showDraftSaveNotification();
386 lastSave = Date.now(); 387 lastSave = Date.now();
387 }, errorRes => { 388 }, errorRes => {
388 if (draftErroring) return; 389 if (draftErroring) return;
389 - events.emit('error', 'Failed to save draft. Ensure you have internet connection before saving this page.') 390 + events.emit('error', trans('errors.page_draft_autosave_fail'));
390 draftErroring = true; 391 draftErroring = true;
391 }); 392 });
392 } 393 }
...@@ -419,7 +420,7 @@ export default function (ngApp, events) { ...@@ -419,7 +420,7 @@ export default function (ngApp, events) {
419 let url = window.baseUrl('/ajax/page/' + pageId); 420 let url = window.baseUrl('/ajax/page/' + pageId);
420 $http.get(url).then((responseData) => { 421 $http.get(url).then((responseData) => {
421 if (autoSave) $interval.cancel(autoSave); 422 if (autoSave) $interval.cancel(autoSave);
422 - $scope.draftText = 'Editing Page'; 423 + $scope.draftText = trans('entities.pages_editing_page');
423 $scope.isUpdateDraft = false; 424 $scope.isUpdateDraft = false;
424 $scope.$broadcast('html-update', responseData.data.html); 425 $scope.$broadcast('html-update', responseData.data.html);
425 $scope.$broadcast('markdown-update', responseData.data.markdown || responseData.data.html); 426 $scope.$broadcast('markdown-update', responseData.data.markdown || responseData.data.html);
...@@ -427,7 +428,7 @@ export default function (ngApp, events) { ...@@ -427,7 +428,7 @@ export default function (ngApp, events) {
427 $timeout(() => { 428 $timeout(() => {
428 startAutoSave(); 429 startAutoSave();
429 }, 1000); 430 }, 1000);
430 - events.emit('success', 'Draft discarded, The editor has been updated with the current page content'); 431 + events.emit('success', trans('entities.pages_draft_discarded'));
431 }); 432 });
432 }; 433 };
433 434
...@@ -506,20 +507,6 @@ export default function (ngApp, events) { ...@@ -506,20 +507,6 @@ export default function (ngApp, events) {
506 }; 507 };
507 508
508 /** 509 /**
509 - * Save the tags to the current page.
510 - */
511 - $scope.saveTags = function() {
512 - setTagOrder();
513 - let postData = {tags: $scope.tags};
514 - let url = window.baseUrl('/ajax/tags/update/page/' + pageId);
515 - $http.post(url, postData).then((responseData) => {
516 - $scope.tags = responseData.data.tags;
517 - addEmptyTag();
518 - events.emit('success', responseData.data.message);
519 - })
520 - };
521 -
522 - /**
523 * Remove a tag from the current list. 510 * Remove a tag from the current list.
524 * @param tag 511 * @param tag
525 */ 512 */
...@@ -588,7 +575,7 @@ export default function (ngApp, events) { ...@@ -588,7 +575,7 @@ export default function (ngApp, events) {
588 * Get files for the current page from the server. 575 * Get files for the current page from the server.
589 */ 576 */
590 function getFiles() { 577 function getFiles() {
591 - let url = window.baseUrl(`/attachments/get/page/${pageId}`) 578 + let url = window.baseUrl(`/attachments/get/page/${pageId}`);
592 $http.get(url).then(resp => { 579 $http.get(url).then(resp => {
593 $scope.files = resp.data; 580 $scope.files = resp.data;
594 currentOrder = resp.data.map(file => {return file.id}).join(':'); 581 currentOrder = resp.data.map(file => {return file.id}).join(':');
...@@ -606,7 +593,7 @@ export default function (ngApp, events) { ...@@ -606,7 +593,7 @@ export default function (ngApp, events) {
606 $scope.$apply(() => { 593 $scope.$apply(() => {
607 $scope.files.push(data); 594 $scope.files.push(data);
608 }); 595 });
609 - events.emit('success', 'File uploaded'); 596 + events.emit('success', trans('entities.attachments_file_uploaded'));
610 }; 597 };
611 598
612 /** 599 /**
...@@ -624,7 +611,7 @@ export default function (ngApp, events) { ...@@ -624,7 +611,7 @@ export default function (ngApp, events) {
624 data.link = ''; 611 data.link = '';
625 } 612 }
626 }); 613 });
627 - events.emit('success', 'File updated'); 614 + events.emit('success', trans('entities.attachments_file_updated'));
628 }; 615 };
629 616
630 /** 617 /**
...@@ -650,7 +637,7 @@ export default function (ngApp, events) { ...@@ -650,7 +637,7 @@ export default function (ngApp, events) {
650 file.uploaded_to = pageId; 637 file.uploaded_to = pageId;
651 $http.post(window.baseUrl('/attachments/link'), file).then(resp => { 638 $http.post(window.baseUrl('/attachments/link'), file).then(resp => {
652 $scope.files.push(resp.data); 639 $scope.files.push(resp.data);
653 - events.emit('success', 'Link attached'); 640 + events.emit('success', trans('entities.attachments_link_attached'));
654 $scope.file = getCleanFile(); 641 $scope.file = getCleanFile();
655 }, checkError('link')); 642 }, checkError('link'));
656 }; 643 };
...@@ -684,7 +671,7 @@ export default function (ngApp, events) { ...@@ -684,7 +671,7 @@ export default function (ngApp, events) {
684 $scope.editFile.link = ''; 671 $scope.editFile.link = '';
685 } 672 }
686 $scope.editFile = false; 673 $scope.editFile = false;
687 - events.emit('success', 'Attachment details updated'); 674 + events.emit('success', trans('entities.attachments_updated_success'));
688 }, checkError('edit')); 675 }, checkError('edit'));
689 }; 676 };
690 677
......
1 "use strict"; 1 "use strict";
2 -const DropZone = require('dropzone'); 2 +import DropZone from "dropzone";
3 -const markdown = require('marked'); 3 +import markdown from "marked";
4 4
5 -module.exports = function (ngApp, events) { 5 +export default function (ngApp, events) {
6 -
7 - /**
8 - * Toggle Switches
9 - * Has basic on/off functionality.
10 - * Use string values of 'true' & 'false' to dictate the current state.
11 - */
12 - ngApp.directive('toggleSwitch', function () {
13 - return {
14 - restrict: 'A',
15 - template: `
16 - <div class="toggle-switch" ng-click="switch()" ng-class="{'active': isActive}">
17 - <input type="hidden" ng-attr-name="{{name}}" ng-attr-value="{{value}}"/>
18 - <div class="switch-handle"></div>
19 - </div>
20 - `,
21 - scope: true,
22 - link: function (scope, element, attrs) {
23 - scope.name = attrs.name;
24 - scope.value = attrs.value;
25 - scope.isActive = scope.value == true && scope.value != 'false';
26 - scope.value = (scope.value == true && scope.value != 'false') ? 'true' : 'false';
27 -
28 - scope.switch = function () {
29 - scope.isActive = !scope.isActive;
30 - scope.value = scope.isActive ? 'true' : 'false';
31 - }
32 -
33 - }
34 - };
35 - });
36 6
37 /** 7 /**
38 * Common tab controls using simple jQuery functions. 8 * Common tab controls using simple jQuery functions.
...@@ -65,7 +35,7 @@ module.exports = function (ngApp, events) { ...@@ -65,7 +35,7 @@ module.exports = function (ngApp, events) {
65 }); 35 });
66 36
67 /** 37 /**
68 - * Sub form component to allow inner-form sections to act like thier own forms. 38 + * Sub form component to allow inner-form sections to act like their own forms.
69 */ 39 */
70 ngApp.directive('subForm', function() { 40 ngApp.directive('subForm', function() {
71 return { 41 return {
...@@ -80,96 +50,13 @@ module.exports = function (ngApp, events) { ...@@ -80,96 +50,13 @@ module.exports = function (ngApp, events) {
80 element.find('button[type="submit"]').click(submitEvent); 50 element.find('button[type="submit"]').click(submitEvent);
81 51
82 function submitEvent(e) { 52 function submitEvent(e) {
83 - e.preventDefault() 53 + e.preventDefault();
84 if (attrs.subForm) scope.$eval(attrs.subForm); 54 if (attrs.subForm) scope.$eval(attrs.subForm);
85 } 55 }
86 } 56 }
87 }; 57 };
88 }); 58 });
89 59
90 -
91 - /**
92 - * Image Picker
93 - * Is a simple front-end interface that connects to an ImageManager if present.
94 - */
95 - ngApp.directive('imagePicker', ['$http', 'imageManagerService', function ($http, imageManagerService) {
96 - return {
97 - restrict: 'E',
98 - template: `
99 - <div class="image-picker">
100 - <div>
101 - <img ng-if="image && image !== 'none'" ng-src="{{image}}" ng-class="{{imageClass}}" alt="Image Preview">
102 - <img ng-if="image === '' && defaultImage" ng-src="{{defaultImage}}" ng-class="{{imageClass}}" alt="Image Preview">
103 - </div>
104 - <button class="button" type="button" ng-click="showImageManager()">Select Image</button>
105 - <br>
106 -
107 - <button class="text-button" ng-click="reset()" type="button">Reset</button>
108 - <span ng-show="showRemove" class="sep">|</span>
109 - <button ng-show="showRemove" class="text-button neg" ng-click="remove()" type="button">Remove</button>
110 -
111 - <input type="hidden" ng-attr-name="{{name}}" ng-attr-id="{{name}}" ng-attr-value="{{value}}">
112 - </div>
113 - `,
114 - scope: {
115 - name: '@',
116 - resizeHeight: '@',
117 - resizeWidth: '@',
118 - resizeCrop: '@',
119 - showRemove: '=',
120 - currentImage: '@',
121 - currentId: '@',
122 - defaultImage: '@',
123 - imageClass: '@'
124 - },
125 - link: function (scope, element, attrs) {
126 - let usingIds = typeof scope.currentId !== 'undefined' || scope.currentId === 'false';
127 - scope.image = scope.currentImage;
128 - scope.value = scope.currentImage || '';
129 - if (usingIds) scope.value = scope.currentId;
130 -
131 - function setImage(imageModel, imageUrl) {
132 - scope.image = imageUrl;
133 - scope.value = usingIds ? imageModel.id : imageUrl;
134 - }
135 -
136 - scope.reset = function () {
137 - setImage({id: 0}, scope.defaultImage);
138 - };
139 -
140 - scope.remove = function () {
141 - scope.image = 'none';
142 - scope.value = 'none';
143 - };
144 -
145 - scope.showImageManager = function () {
146 - imageManagerService.show((image) => {
147 - scope.updateImageFromModel(image);
148 - });
149 - };
150 -
151 - scope.updateImageFromModel = function (model) {
152 - let isResized = scope.resizeWidth && scope.resizeHeight;
153 -
154 - if (!isResized) {
155 - scope.$apply(() => {
156 - setImage(model, model.url);
157 - });
158 - return;
159 - }
160 -
161 - let cropped = scope.resizeCrop ? 'true' : 'false';
162 - let requestString = '/images/thumb/' + model.id + '/' + scope.resizeWidth + '/' + scope.resizeHeight + '/' + cropped;
163 - requestString = window.baseUrl(requestString);
164 - $http.get(requestString).then((response) => {
165 - setImage(model, response.data.url);
166 - });
167 - };
168 -
169 - }
170 - };
171 - }]);
172 -
173 /** 60 /**
174 * DropZone 61 * DropZone
175 * Used for uploading images 62 * Used for uploading images
...@@ -179,25 +66,26 @@ module.exports = function (ngApp, events) { ...@@ -179,25 +66,26 @@ module.exports = function (ngApp, events) {
179 restrict: 'E', 66 restrict: 'E',
180 template: ` 67 template: `
181 <div class="dropzone-container"> 68 <div class="dropzone-container">
182 - <div class="dz-message">Drop files or click here to upload</div> 69 + <div class="dz-message">{{message}}</div>
183 </div> 70 </div>
184 `, 71 `,
185 scope: { 72 scope: {
186 uploadUrl: '@', 73 uploadUrl: '@',
187 eventSuccess: '=', 74 eventSuccess: '=',
188 eventError: '=', 75 eventError: '=',
189 - uploadedTo: '@' 76 + uploadedTo: '@',
190 }, 77 },
191 link: function (scope, element, attrs) { 78 link: function (scope, element, attrs) {
79 + scope.message = attrs.message;
192 if (attrs.placeholder) element[0].querySelector('.dz-message').textContent = attrs.placeholder; 80 if (attrs.placeholder) element[0].querySelector('.dz-message').textContent = attrs.placeholder;
193 - var dropZone = new DropZone(element[0].querySelector('.dropzone-container'), { 81 + let dropZone = new DropZone(element[0].querySelector('.dropzone-container'), {
194 url: scope.uploadUrl, 82 url: scope.uploadUrl,
195 init: function () { 83 init: function () {
196 - var dz = this; 84 + let dz = this;
197 dz.on('sending', function (file, xhr, data) { 85 dz.on('sending', function (file, xhr, data) {
198 - var token = window.document.querySelector('meta[name=token]').getAttribute('content'); 86 + let token = window.document.querySelector('meta[name=token]').getAttribute('content');
199 data.append('_token', token); 87 data.append('_token', token);
200 - var uploadedTo = typeof scope.uploadedTo === 'undefined' ? 0 : scope.uploadedTo; 88 + let uploadedTo = typeof scope.uploadedTo === 'undefined' ? 0 : scope.uploadedTo;
201 data.append('uploaded_to', uploadedTo); 89 data.append('uploaded_to', uploadedTo);
202 }); 90 });
203 if (typeof scope.eventSuccess !== 'undefined') dz.on('success', scope.eventSuccess); 91 if (typeof scope.eventSuccess !== 'undefined') dz.on('success', scope.eventSuccess);
...@@ -214,7 +102,7 @@ module.exports = function (ngApp, events) { ...@@ -214,7 +102,7 @@ module.exports = function (ngApp, events) {
214 $(file.previewElement).find('[data-dz-errormessage]').text(message); 102 $(file.previewElement).find('[data-dz-errormessage]').text(message);
215 } 103 }
216 104
217 - if (xhr.status === 413) setMessage('The server does not allow uploads of this size. Please try a smaller file.'); 105 + if (xhr.status === 413) setMessage(trans('errors.server_upload_limit'));
218 if (errorMessage.file) setMessage(errorMessage.file[0]); 106 if (errorMessage.file) setMessage(errorMessage.file[0]);
219 107
220 }); 108 });
...@@ -273,7 +161,7 @@ module.exports = function (ngApp, events) { ...@@ -273,7 +161,7 @@ module.exports = function (ngApp, events) {
273 161
274 function tinyMceSetup(editor) { 162 function tinyMceSetup(editor) {
275 editor.on('ExecCommand change NodeChange ObjectResized', (e) => { 163 editor.on('ExecCommand change NodeChange ObjectResized', (e) => {
276 - var content = editor.getContent(); 164 + let content = editor.getContent();
277 $timeout(() => { 165 $timeout(() => {
278 scope.mceModel = content; 166 scope.mceModel = content;
279 }); 167 });
...@@ -301,9 +189,9 @@ module.exports = function (ngApp, events) { ...@@ -301,9 +189,9 @@ module.exports = function (ngApp, events) {
301 // Custom tinyMCE plugins 189 // Custom tinyMCE plugins
302 tinymce.PluginManager.add('customhr', function (editor) { 190 tinymce.PluginManager.add('customhr', function (editor) {
303 editor.addCommand('InsertHorizontalRule', function () { 191 editor.addCommand('InsertHorizontalRule', function () {
304 - var hrElem = document.createElement('hr'); 192 + let hrElem = document.createElement('hr');
305 - var cNode = editor.selection.getNode(); 193 + let cNode = editor.selection.getNode();
306 - var parentNode = cNode.parentNode; 194 + let parentNode = cNode.parentNode;
307 parentNode.insertBefore(hrElem, cNode); 195 parentNode.insertBefore(hrElem, cNode);
308 }); 196 });
309 197
...@@ -373,15 +261,21 @@ module.exports = function (ngApp, events) { ...@@ -373,15 +261,21 @@ module.exports = function (ngApp, events) {
373 link: function (scope, element, attrs) { 261 link: function (scope, element, attrs) {
374 262
375 // Elements 263 // Elements
376 - const input = element.find('[markdown-input] textarea').first(); 264 + const $input = element.find('[markdown-input] textarea').first();
377 - const display = element.find('.markdown-display').first(); 265 + const $display = element.find('.markdown-display').first();
378 - const insertImage = element.find('button[data-action="insertImage"]'); 266 + const $insertImage = element.find('button[data-action="insertImage"]');
379 - const insertEntityLink = element.find('button[data-action="insertEntityLink"]') 267 + const $insertEntityLink = element.find('button[data-action="insertEntityLink"]');
268 +
269 + // Prevent markdown display link click redirect
270 + $display.on('click', 'a', function(event) {
271 + event.preventDefault();
272 + window.open(this.getAttribute('href'));
273 + });
380 274
381 let currentCaretPos = 0; 275 let currentCaretPos = 0;
382 276
383 - input.blur(event => { 277 + $input.blur(event => {
384 - currentCaretPos = input[0].selectionStart; 278 + currentCaretPos = $input[0].selectionStart;
385 }); 279 });
386 280
387 // Scroll sync 281 // Scroll sync
...@@ -391,10 +285,10 @@ module.exports = function (ngApp, events) { ...@@ -391,10 +285,10 @@ module.exports = function (ngApp, events) {
391 displayHeight; 285 displayHeight;
392 286
393 function setScrollHeights() { 287 function setScrollHeights() {
394 - inputScrollHeight = input[0].scrollHeight; 288 + inputScrollHeight = $input[0].scrollHeight;
395 - inputHeight = input.height(); 289 + inputHeight = $input.height();
396 - displayScrollHeight = display[0].scrollHeight; 290 + displayScrollHeight = $display[0].scrollHeight;
397 - displayHeight = display.height(); 291 + displayHeight = $display.height();
398 } 292 }
399 293
400 setTimeout(() => { 294 setTimeout(() => {
...@@ -403,29 +297,29 @@ module.exports = function (ngApp, events) { ...@@ -403,29 +297,29 @@ module.exports = function (ngApp, events) {
403 window.addEventListener('resize', setScrollHeights); 297 window.addEventListener('resize', setScrollHeights);
404 let scrollDebounceTime = 800; 298 let scrollDebounceTime = 800;
405 let lastScroll = 0; 299 let lastScroll = 0;
406 - input.on('scroll', event => { 300 + $input.on('scroll', event => {
407 let now = Date.now(); 301 let now = Date.now();
408 if (now - lastScroll > scrollDebounceTime) { 302 if (now - lastScroll > scrollDebounceTime) {
409 setScrollHeights() 303 setScrollHeights()
410 } 304 }
411 - let scrollPercent = (input.scrollTop() / (inputScrollHeight - inputHeight)); 305 + let scrollPercent = ($input.scrollTop() / (inputScrollHeight - inputHeight));
412 let displayScrollY = (displayScrollHeight - displayHeight) * scrollPercent; 306 let displayScrollY = (displayScrollHeight - displayHeight) * scrollPercent;
413 - display.scrollTop(displayScrollY); 307 + $display.scrollTop(displayScrollY);
414 lastScroll = now; 308 lastScroll = now;
415 }); 309 });
416 310
417 // Editor key-presses 311 // Editor key-presses
418 - input.keydown(event => { 312 + $input.keydown(event => {
419 // Insert image shortcut 313 // Insert image shortcut
420 if (event.which === 73 && event.ctrlKey && event.shiftKey) { 314 if (event.which === 73 && event.ctrlKey && event.shiftKey) {
421 event.preventDefault(); 315 event.preventDefault();
422 - let caretPos = input[0].selectionStart; 316 + let caretPos = $input[0].selectionStart;
423 - let currentContent = input.val(); 317 + let currentContent = $input.val();
424 const mdImageText = "![](http://)"; 318 const mdImageText = "![](http://)";
425 - input.val(currentContent.substring(0, caretPos) + mdImageText + currentContent.substring(caretPos)); 319 + $input.val(currentContent.substring(0, caretPos) + mdImageText + currentContent.substring(caretPos));
426 - input.focus(); 320 + $input.focus();
427 - input[0].selectionStart = caretPos + ("![](".length); 321 + $input[0].selectionStart = caretPos + ("![](".length);
428 - input[0].selectionEnd = caretPos + ('![](http://'.length); 322 + $input[0].selectionEnd = caretPos + ('![](http://'.length);
429 return; 323 return;
430 } 324 }
431 325
...@@ -440,48 +334,48 @@ module.exports = function (ngApp, events) { ...@@ -440,48 +334,48 @@ module.exports = function (ngApp, events) {
440 }); 334 });
441 335
442 // Insert image from image manager 336 // Insert image from image manager
443 - insertImage.click(event => { 337 + $insertImage.click(event => {
444 window.ImageManager.showExternal(image => { 338 window.ImageManager.showExternal(image => {
445 let caretPos = currentCaretPos; 339 let caretPos = currentCaretPos;
446 - let currentContent = input.val(); 340 + let currentContent = $input.val();
447 let mdImageText = "![" + image.name + "](" + image.thumbs.display + ")"; 341 let mdImageText = "![" + image.name + "](" + image.thumbs.display + ")";
448 - input.val(currentContent.substring(0, caretPos) + mdImageText + currentContent.substring(caretPos)); 342 + $input.val(currentContent.substring(0, caretPos) + mdImageText + currentContent.substring(caretPos));
449 - input.change(); 343 + $input.change();
450 }); 344 });
451 }); 345 });
452 346
453 function showLinkSelector() { 347 function showLinkSelector() {
454 window.showEntityLinkSelector((entity) => { 348 window.showEntityLinkSelector((entity) => {
455 let selectionStart = currentCaretPos; 349 let selectionStart = currentCaretPos;
456 - let selectionEnd = input[0].selectionEnd; 350 + let selectionEnd = $input[0].selectionEnd;
457 let textSelected = (selectionEnd !== selectionStart); 351 let textSelected = (selectionEnd !== selectionStart);
458 - let currentContent = input.val(); 352 + let currentContent = $input.val();
459 353
460 if (textSelected) { 354 if (textSelected) {
461 let selectedText = currentContent.substring(selectionStart, selectionEnd); 355 let selectedText = currentContent.substring(selectionStart, selectionEnd);
462 let linkText = `[${selectedText}](${entity.link})`; 356 let linkText = `[${selectedText}](${entity.link})`;
463 - input.val(currentContent.substring(0, selectionStart) + linkText + currentContent.substring(selectionEnd)); 357 + $input.val(currentContent.substring(0, selectionStart) + linkText + currentContent.substring(selectionEnd));
464 } else { 358 } else {
465 let linkText = ` [${entity.name}](${entity.link}) `; 359 let linkText = ` [${entity.name}](${entity.link}) `;
466 - input.val(currentContent.substring(0, selectionStart) + linkText + currentContent.substring(selectionStart)) 360 + $input.val(currentContent.substring(0, selectionStart) + linkText + currentContent.substring(selectionStart))
467 } 361 }
468 - input.change(); 362 + $input.change();
469 }); 363 });
470 } 364 }
471 - insertEntityLink.click(showLinkSelector); 365 + $insertEntityLink.click(showLinkSelector);
472 366
473 // Upload and insert image on paste 367 // Upload and insert image on paste
474 function editorPaste(e) { 368 function editorPaste(e) {
475 e = e.originalEvent; 369 e = e.originalEvent;
476 if (!e.clipboardData) return 370 if (!e.clipboardData) return
477 - var items = e.clipboardData.items; 371 + let items = e.clipboardData.items;
478 if (!items) return; 372 if (!items) return;
479 - for (var i = 0; i < items.length; i++) { 373 + for (let i = 0; i < items.length; i++) {
480 uploadImage(items[i].getAsFile()); 374 uploadImage(items[i].getAsFile());
481 } 375 }
482 } 376 }
483 377
484 - input.on('paste', editorPaste); 378 + $input.on('paste', editorPaste);
485 379
486 // Handle image drop, Uploads images to BookStack. 380 // Handle image drop, Uploads images to BookStack.
487 function handleImageDrop(event) { 381 function handleImageDrop(event) {
...@@ -493,17 +387,17 @@ module.exports = function (ngApp, events) { ...@@ -493,17 +387,17 @@ module.exports = function (ngApp, events) {
493 } 387 }
494 } 388 }
495 389
496 - input.on('drop', handleImageDrop); 390 + $input.on('drop', handleImageDrop);
497 391
498 // Handle image upload and add image into markdown content 392 // Handle image upload and add image into markdown content
499 function uploadImage(file) { 393 function uploadImage(file) {
500 if (file.type.indexOf('image') !== 0) return; 394 if (file.type.indexOf('image') !== 0) return;
501 - var formData = new FormData(); 395 + let formData = new FormData();
502 - var ext = 'png'; 396 + let ext = 'png';
503 - var xhr = new XMLHttpRequest(); 397 + let xhr = new XMLHttpRequest();
504 398
505 if (file.name) { 399 if (file.name) {
506 - var fileNameMatches = file.name.match(/\.(.+)$/); 400 + let fileNameMatches = file.name.match(/\.(.+)$/);
507 if (fileNameMatches) { 401 if (fileNameMatches) {
508 ext = fileNameMatches[1]; 402 ext = fileNameMatches[1];
509 } 403 }
...@@ -511,17 +405,17 @@ module.exports = function (ngApp, events) { ...@@ -511,17 +405,17 @@ module.exports = function (ngApp, events) {
511 405
512 // Insert image into markdown 406 // Insert image into markdown
513 let id = "image-" + Math.random().toString(16).slice(2); 407 let id = "image-" + Math.random().toString(16).slice(2);
514 - let selectStart = input[0].selectionStart; 408 + let selectStart = $input[0].selectionStart;
515 - let selectEnd = input[0].selectionEnd; 409 + let selectEnd = $input[0].selectionEnd;
516 - let content = input[0].value; 410 + let content = $input[0].value;
517 let selectText = content.substring(selectStart, selectEnd); 411 let selectText = content.substring(selectStart, selectEnd);
518 let placeholderImage = window.baseUrl(`/loading.gif#upload${id}`); 412 let placeholderImage = window.baseUrl(`/loading.gif#upload${id}`);
519 let innerContent = ((selectEnd > selectStart) ? `![${selectText}]` : '![]') + `(${placeholderImage})`; 413 let innerContent = ((selectEnd > selectStart) ? `![${selectText}]` : '![]') + `(${placeholderImage})`;
520 - input[0].value = content.substring(0, selectStart) + innerContent + content.substring(selectEnd); 414 + $input[0].value = content.substring(0, selectStart) + innerContent + content.substring(selectEnd);
521 415
522 - input.focus(); 416 + $input.focus();
523 - input[0].selectionStart = selectStart; 417 + $input[0].selectionStart = selectStart;
524 - input[0].selectionEnd = selectStart; 418 + $input[0].selectionEnd = selectStart;
525 419
526 let remoteFilename = "image-" + Date.now() + "." + ext; 420 let remoteFilename = "image-" + Date.now() + "." + ext;
527 formData.append('file', file, remoteFilename); 421 formData.append('file', file, remoteFilename);
...@@ -529,20 +423,20 @@ module.exports = function (ngApp, events) { ...@@ -529,20 +423,20 @@ module.exports = function (ngApp, events) {
529 423
530 xhr.open('POST', window.baseUrl('/images/gallery/upload')); 424 xhr.open('POST', window.baseUrl('/images/gallery/upload'));
531 xhr.onload = function () { 425 xhr.onload = function () {
532 - let selectStart = input[0].selectionStart; 426 + let selectStart = $input[0].selectionStart;
533 if (xhr.status === 200 || xhr.status === 201) { 427 if (xhr.status === 200 || xhr.status === 201) {
534 - var result = JSON.parse(xhr.responseText); 428 + let result = JSON.parse(xhr.responseText);
535 - input[0].value = input[0].value.replace(placeholderImage, result.thumbs.display); 429 + $input[0].value = $input[0].value.replace(placeholderImage, result.thumbs.display);
536 - input.change(); 430 + $input.change();
537 } else { 431 } else {
538 - console.log('An error occurred uploading the image'); 432 + console.log(trans('errors.image_upload_error'));
539 console.log(xhr.responseText); 433 console.log(xhr.responseText);
540 - input[0].value = input[0].value.replace(innerContent, ''); 434 + $input[0].value = $input[0].value.replace(innerContent, '');
541 - input.change(); 435 + $input.change();
542 } 436 }
543 - input.focus(); 437 + $input.focus();
544 - input[0].selectionStart = selectStart; 438 + $input[0].selectionStart = selectStart;
545 - input[0].selectionEnd = selectStart; 439 + $input[0].selectionEnd = selectStart;
546 }; 440 };
547 xhr.send(formData); 441 xhr.send(formData);
548 } 442 }
...@@ -680,8 +574,7 @@ module.exports = function (ngApp, events) { ...@@ -680,8 +574,7 @@ module.exports = function (ngApp, events) {
680 } 574 }
681 // Enter or tab key 575 // Enter or tab key
682 else if ((event.keyCode === 13 || event.keyCode === 9) && !event.shiftKey) { 576 else if ((event.keyCode === 13 || event.keyCode === 9) && !event.shiftKey) {
683 - let text = suggestionElems[active].textContent; 577 + currentInput[0].value = suggestionElems[active].textContent;
684 - currentInput[0].value = text;
685 currentInput.focus(); 578 currentInput.focus();
686 $suggestionBox.hide(); 579 $suggestionBox.hide();
687 isShowing = false; 580 isShowing = false;
...@@ -732,14 +625,13 @@ module.exports = function (ngApp, events) { ...@@ -732,14 +625,13 @@ module.exports = function (ngApp, events) {
732 // Build suggestions 625 // Build suggestions
733 $suggestionBox[0].innerHTML = ''; 626 $suggestionBox[0].innerHTML = '';
734 for (let i = 0; i < suggestions.length; i++) { 627 for (let i = 0; i < suggestions.length; i++) {
735 - var suggestion = document.createElement('li'); 628 + let suggestion = document.createElement('li');
736 suggestion.textContent = suggestions[i]; 629 suggestion.textContent = suggestions[i];
737 suggestion.onclick = suggestionClick; 630 suggestion.onclick = suggestionClick;
738 if (i === 0) { 631 if (i === 0) {
739 - suggestion.className = 'active' 632 + suggestion.className = 'active';
740 active = 0; 633 active = 0;
741 } 634 }
742 - ;
743 $suggestionBox[0].appendChild(suggestion); 635 $suggestionBox[0].appendChild(suggestion);
744 } 636 }
745 637
...@@ -748,12 +640,11 @@ module.exports = function (ngApp, events) { ...@@ -748,12 +640,11 @@ module.exports = function (ngApp, events) {
748 640
749 // Suggestion click event 641 // Suggestion click event
750 function suggestionClick(event) { 642 function suggestionClick(event) {
751 - let text = this.textContent; 643 + currentInput[0].value = this.textContent;
752 - currentInput[0].value = text;
753 currentInput.focus(); 644 currentInput.focus();
754 $suggestionBox.hide(); 645 $suggestionBox.hide();
755 isShowing = false; 646 isShowing = false;
756 - }; 647 + }
757 648
758 // Get suggestions & cache 649 // Get suggestions & cache
759 function getSuggestions(input, url) { 650 function getSuggestions(input, url) {
...@@ -779,7 +670,7 @@ module.exports = function (ngApp, events) { ...@@ -779,7 +670,7 @@ module.exports = function (ngApp, events) {
779 670
780 ngApp.directive('entityLinkSelector', [function($http) { 671 ngApp.directive('entityLinkSelector', [function($http) {
781 return { 672 return {
782 - restict: 'A', 673 + restrict: 'A',
783 link: function(scope, element, attrs) { 674 link: function(scope, element, attrs) {
784 675
785 const selectButton = element.find('.entity-link-selector-confirm'); 676 const selectButton = element.find('.entity-link-selector-confirm');
...@@ -843,7 +734,7 @@ module.exports = function (ngApp, events) { ...@@ -843,7 +734,7 @@ module.exports = function (ngApp, events) {
843 const input = element.find('[entity-selector-input]').first(); 734 const input = element.find('[entity-selector-input]').first();
844 735
845 // Detect double click events 736 // Detect double click events
846 - var lastClick = 0; 737 + let lastClick = 0;
847 function isDoubleClick() { 738 function isDoubleClick() {
848 let now = Date.now(); 739 let now = Date.now();
849 let answer = now - lastClick < 300; 740 let answer = now - lastClick < 300;
......
1 "use strict"; 1 "use strict";
2 2
3 // AngularJS - Create application and load components 3 // AngularJS - Create application and load components
4 -var angular = require('angular'); 4 +import angular from "angular";
5 -var ngResource = require('angular-resource'); 5 +import "angular-resource";
6 -var ngAnimate = require('angular-animate'); 6 +import "angular-animate";
7 -var ngSanitize = require('angular-sanitize'); 7 +import "angular-sanitize";
8 -require('angular-ui-sortable'); 8 +import "angular-ui-sortable";
9 9
10 // Url retrieval function 10 // Url retrieval function
11 window.baseUrl = function(path) { 11 window.baseUrl = function(path) {
...@@ -15,7 +15,13 @@ window.baseUrl = function(path) { ...@@ -15,7 +15,13 @@ window.baseUrl = function(path) {
15 return basePath + '/' + path; 15 return basePath + '/' + path;
16 }; 16 };
17 17
18 -var ngApp = angular.module('bookStack', ['ngResource', 'ngAnimate', 'ngSanitize', 'ui.sortable']); 18 +let ngApp = angular.module('bookStack', ['ngResource', 'ngAnimate', 'ngSanitize', 'ui.sortable']);
19 +
20 +// Translation setup
21 +// Creates a global function with name 'trans' to be used in the same way as Laravel's translation system
22 +import Translations from "./translations"
23 +let translator = new Translations(window.translations);
24 +window.trans = translator.get.bind(translator);
19 25
20 // Global Event System 26 // Global Event System
21 class EventManager { 27 class EventManager {
...@@ -25,9 +31,9 @@ class EventManager { ...@@ -25,9 +31,9 @@ class EventManager {
25 31
26 emit(eventName, eventData) { 32 emit(eventName, eventData) {
27 if (typeof this.listeners[eventName] === 'undefined') return this; 33 if (typeof this.listeners[eventName] === 'undefined') return this;
28 - var eventsToStart = this.listeners[eventName]; 34 + let eventsToStart = this.listeners[eventName];
29 for (let i = 0; i < eventsToStart.length; i++) { 35 for (let i = 0; i < eventsToStart.length; i++) {
30 - var event = eventsToStart[i]; 36 + let event = eventsToStart[i];
31 event(eventData); 37 event(eventData);
32 } 38 }
33 return this; 39 return this;
...@@ -55,10 +61,9 @@ Controllers(ngApp, window.Events); ...@@ -55,10 +61,9 @@ Controllers(ngApp, window.Events);
55 // Smooth scrolling 61 // Smooth scrolling
56 jQuery.fn.smoothScrollTo = function () { 62 jQuery.fn.smoothScrollTo = function () {
57 if (this.length === 0) return; 63 if (this.length === 0) return;
58 - let scrollElem = document.documentElement.scrollTop === 0 ? document.body : document.documentElement; 64 + $('html, body').animate({
59 - $(scrollElem).animate({
60 scrollTop: this.offset().top - 60 // Adjust to change final scroll position top margin 65 scrollTop: this.offset().top - 60 // Adjust to change final scroll position top margin
61 - }, 800); // Adjust to change animations speed (ms) 66 + }, 300); // Adjust to change animations speed (ms)
62 return this; 67 return this;
63 }; 68 };
64 69
...@@ -70,49 +75,47 @@ jQuery.expr[":"].contains = $.expr.createPseudo(function (arg) { ...@@ -70,49 +75,47 @@ jQuery.expr[":"].contains = $.expr.createPseudo(function (arg) {
70 }); 75 });
71 76
72 // Global jQuery Elements 77 // Global jQuery Elements
73 -$(function () { 78 +let notifications = $('.notification');
74 - 79 +let successNotification = notifications.filter('.pos');
75 - var notifications = $('.notification'); 80 +let errorNotification = notifications.filter('.neg');
76 - var successNotification = notifications.filter('.pos'); 81 +let warningNotification = notifications.filter('.warning');
77 - var errorNotification = notifications.filter('.neg'); 82 +// Notification Events
78 - var warningNotification = notifications.filter('.warning'); 83 +window.Events.listen('success', function (text) {
79 - // Notification Events
80 - window.Events.listen('success', function (text) {
81 successNotification.hide(); 84 successNotification.hide();
82 successNotification.find('span').text(text); 85 successNotification.find('span').text(text);
83 setTimeout(() => { 86 setTimeout(() => {
84 successNotification.show(); 87 successNotification.show();
85 }, 1); 88 }, 1);
86 - }); 89 +});
87 - window.Events.listen('warning', function (text) { 90 +window.Events.listen('warning', function (text) {
88 warningNotification.find('span').text(text); 91 warningNotification.find('span').text(text);
89 warningNotification.show(); 92 warningNotification.show();
90 - }); 93 +});
91 - window.Events.listen('error', function (text) { 94 +window.Events.listen('error', function (text) {
92 errorNotification.find('span').text(text); 95 errorNotification.find('span').text(text);
93 errorNotification.show(); 96 errorNotification.show();
94 - }); 97 +});
95 98
96 - // Notification hiding 99 +// Notification hiding
97 - notifications.click(function () { 100 +notifications.click(function () {
98 $(this).fadeOut(100); 101 $(this).fadeOut(100);
99 - }); 102 +});
100 103
101 - // Chapter page list toggles 104 +// Chapter page list toggles
102 - $('.chapter-toggle').click(function (e) { 105 +$('.chapter-toggle').click(function (e) {
103 e.preventDefault(); 106 e.preventDefault();
104 $(this).toggleClass('open'); 107 $(this).toggleClass('open');
105 $(this).closest('.chapter').find('.inset-list').slideToggle(180); 108 $(this).closest('.chapter').find('.inset-list').slideToggle(180);
106 - }); 109 +});
107 110
108 - // Back to top button 111 +// Back to top button
109 - $('#back-to-top').click(function() { 112 +$('#back-to-top').click(function() {
110 $('#header').smoothScrollTo(); 113 $('#header').smoothScrollTo();
111 - }); 114 +});
112 - var scrollTopShowing = false; 115 +let scrollTopShowing = false;
113 - var scrollTop = document.getElementById('back-to-top'); 116 +let scrollTop = document.getElementById('back-to-top');
114 - var scrollTopBreakpoint = 1200; 117 +let scrollTopBreakpoint = 1200;
115 - window.addEventListener('scroll', function() { 118 +window.addEventListener('scroll', function() {
116 let scrollTopPos = document.documentElement.scrollTop || document.body.scrollTop || 0; 119 let scrollTopPos = document.documentElement.scrollTop || document.body.scrollTop || 0;
117 if (!scrollTopShowing && scrollTopPos > scrollTopBreakpoint) { 120 if (!scrollTopShowing && scrollTopPos > scrollTopBreakpoint) {
118 scrollTop.style.display = 'block'; 121 scrollTop.style.display = 'block';
...@@ -127,36 +130,28 @@ $(function () { ...@@ -127,36 +130,28 @@ $(function () {
127 scrollTop.style.display = 'none'; 130 scrollTop.style.display = 'none';
128 }, 500); 131 }, 500);
129 } 132 }
130 - }); 133 +});
131 134
132 - // Common jQuery actions 135 +// Common jQuery actions
133 - $('[data-action="expand-entity-list-details"]').click(function() { 136 +$('[data-action="expand-entity-list-details"]').click(function() {
134 $('.entity-list.compact').find('p').not('.empty-text').slideToggle(240); 137 $('.entity-list.compact').find('p').not('.empty-text').slideToggle(240);
135 - }); 138 +});
136 139
137 - // Popup close 140 +// Popup close
138 - $('.popup-close').click(function() { 141 +$('.popup-close').click(function() {
139 $(this).closest('.overlay').fadeOut(240); 142 $(this).closest('.overlay').fadeOut(240);
140 - }); 143 +});
141 - $('.overlay').click(function(event) { 144 +$('.overlay').click(function(event) {
142 if (!$(event.target).hasClass('overlay')) return; 145 if (!$(event.target).hasClass('overlay')) return;
143 $(this).fadeOut(240); 146 $(this).fadeOut(240);
144 - }); 147 +});
145 -
146 - // Prevent markdown display link click redirect
147 - $('.markdown-display').on('click', 'a', function(event) {
148 - event.preventDefault();
149 - window.open($(this).attr('href'));
150 - });
151 148
152 - // Detect IE for css 149 +// Detect IE for css
153 - if(navigator.userAgent.indexOf('MSIE')!==-1 150 +if(navigator.userAgent.indexOf('MSIE')!==-1
154 || navigator.appVersion.indexOf('Trident/') > 0 151 || navigator.appVersion.indexOf('Trident/') > 0
155 || navigator.userAgent.indexOf('Safari') !== -1){ 152 || navigator.userAgent.indexOf('Safari') !== -1){
156 $('body').addClass('flexbox-support'); 153 $('body').addClass('flexbox-support');
157 - } 154 +}
158 -
159 -});
160 155
161 // Page specific items 156 // Page specific items
162 -require('./pages/page-show'); 157 +import "./pages/page-show";
......
...@@ -60,7 +60,8 @@ function registerEditorShortcuts(editor) { ...@@ -60,7 +60,8 @@ function registerEditorShortcuts(editor) {
60 editor.addShortcut('meta+shift+E', '', ['FormatBlock', false, 'code']); 60 editor.addShortcut('meta+shift+E', '', ['FormatBlock', false, 'code']);
61 } 61 }
62 62
63 -var mceOptions = module.exports = { 63 +export default function() {
64 + let settings = {
64 selector: '#html-editor', 65 selector: '#html-editor',
65 content_css: [ 66 content_css: [
66 window.baseUrl('/css/styles.css'), 67 window.baseUrl('/css/styles.css'),
...@@ -76,7 +77,7 @@ var mceOptions = module.exports = { ...@@ -76,7 +77,7 @@ var mceOptions = module.exports = {
76 extended_valid_elements: 'pre[*]', 77 extended_valid_elements: 'pre[*]',
77 automatic_uploads: false, 78 automatic_uploads: false,
78 valid_children: "-div[p|pre|h1|h2|h3|h4|h5|h6|blockquote]", 79 valid_children: "-div[p|pre|h1|h2|h3|h4|h5|h6|blockquote]",
79 - plugins: "image table textcolor paste link fullscreen imagetools code customhr autosave lists", 80 + plugins: "image table textcolor paste link autolink fullscreen imagetools code customhr autosave lists",
80 imagetools_toolbar: 'imageoptions', 81 imagetools_toolbar: 'imageoptions',
81 toolbar: "undo redo | styleselect | bold italic underline strikethrough superscript subscript | forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | table image-insert link hr | removeformat code fullscreen", 82 toolbar: "undo redo | styleselect | bold italic underline strikethrough superscript subscript | forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | table image-insert link hr | removeformat code fullscreen",
82 content_style: "body {padding-left: 15px !important; padding-right: 15px !important; margin:0!important; margin-left:auto!important;margin-right:auto!important;}", 83 content_style: "body {padding-left: 15px !important; padding-right: 15px !important; margin:0!important; margin-left:auto!important;margin-right:auto!important;}",
...@@ -147,21 +148,20 @@ var mceOptions = module.exports = { ...@@ -147,21 +148,20 @@ var mceOptions = module.exports = {
147 148
148 // Run additional setup actions 149 // Run additional setup actions
149 // Used by the angular side of things 150 // Used by the angular side of things
150 - for (let i = 0; i < mceOptions.extraSetups.length; i++) { 151 + for (let i = 0; i < settings.extraSetups.length; i++) {
151 - mceOptions.extraSetups[i](editor); 152 + settings.extraSetups[i](editor);
152 } 153 }
153 154
154 registerEditorShortcuts(editor); 155 registerEditorShortcuts(editor);
155 156
156 - (function () { 157 + let wrap;
157 - var wrap;
158 158
159 function hasTextContent(node) { 159 function hasTextContent(node) {
160 return node && !!( node.textContent || node.innerText ); 160 return node && !!( node.textContent || node.innerText );
161 } 161 }
162 162
163 editor.on('dragstart', function () { 163 editor.on('dragstart', function () {
164 - var node = editor.selection.getNode(); 164 + let node = editor.selection.getNode();
165 165
166 if (node.nodeName !== 'IMG') return; 166 if (node.nodeName !== 'IMG') return;
167 wrap = editor.dom.getParent(node, '.mceTemp'); 167 wrap = editor.dom.getParent(node, '.mceTemp');
...@@ -172,7 +172,7 @@ var mceOptions = module.exports = { ...@@ -172,7 +172,7 @@ var mceOptions = module.exports = {
172 }); 172 });
173 173
174 editor.on('drop', function (event) { 174 editor.on('drop', function (event) {
175 - var dom = editor.dom, 175 + let dom = editor.dom,
176 rng = tinymce.dom.RangeUtils.getCaretRangeFromPoint(event.clientX, event.clientY, editor.getDoc()); 176 rng = tinymce.dom.RangeUtils.getCaretRangeFromPoint(event.clientX, event.clientY, editor.getDoc());
177 177
178 // Don't allow anything to be dropped in a captioned image. 178 // Don't allow anything to be dropped in a captioned image.
...@@ -190,7 +190,6 @@ var mceOptions = module.exports = { ...@@ -190,7 +190,6 @@ var mceOptions = module.exports = {
190 190
191 wrap = null; 191 wrap = null;
192 }); 192 });
193 - })();
194 193
195 // Custom Image picker button 194 // Custom Image picker button
196 editor.addButton('image-insert', { 195 editor.addButton('image-insert', {
...@@ -212,4 +211,6 @@ var mceOptions = module.exports = { ...@@ -212,4 +211,6 @@ var mceOptions = module.exports = {
212 editorPaste(event, editor); 211 editorPaste(event, editor);
213 }); 212 });
214 } 213 }
215 -};
...\ No newline at end of file ...\ No newline at end of file
214 + };
215 + return settings;
216 +}
...\ No newline at end of file ...\ No newline at end of file
......
1 "use strict"; 1 "use strict";
2 // Configure ZeroClipboard 2 // Configure ZeroClipboard
3 -var zeroClipBoard = require('zeroclipboard'); 3 +import Clipboard from "clipboard";
4 -zeroClipBoard.config({
5 - swfPath: window.baseUrl('/ZeroClipboard.swf')
6 -});
7 4
8 -window.setupPageShow = module.exports = function (pageId) { 5 +export default window.setupPageShow = function (pageId) {
9 6
10 // Set up pointer 7 // Set up pointer
11 - var $pointer = $('#pointer').detach(); 8 + let $pointer = $('#pointer').detach();
12 - var $pointerInner = $pointer.children('div.pointer').first(); 9 + let pointerShowing = false;
13 - var isSelection = false; 10 + let $pointerInner = $pointer.children('div.pointer').first();
11 + let isSelection = false;
12 + let pointerModeLink = true;
13 + let pointerSectionId = '';
14 14
15 // Select all contents on input click 15 // Select all contents on input click
16 $pointer.on('click', 'input', function (e) { 16 $pointer.on('click', 'input', function (e) {
...@@ -18,35 +18,53 @@ window.setupPageShow = module.exports = function (pageId) { ...@@ -18,35 +18,53 @@ window.setupPageShow = module.exports = function (pageId) {
18 e.stopPropagation(); 18 e.stopPropagation();
19 }); 19 });
20 20
21 - // Set up copy-to-clipboard 21 + // Pointer mode toggle
22 - new zeroClipBoard($pointer.find('button').first()[0]); 22 + $pointer.on('click', 'span.icon', event => {
23 + let $icon = $(event.currentTarget);
24 + pointerModeLink = !pointerModeLink;
25 + $icon.html(pointerModeLink ? '<i class="zmdi zmdi-link"></i>' : '<i class="zmdi zmdi-square-down"></i>');
26 + updatePointerContent();
27 + });
28 +
29 + // Set up clipboard
30 + let clipboard = new Clipboard('#pointer button');
23 31
24 // Hide pointer when clicking away 32 // Hide pointer when clicking away
25 - $(document.body).find('*').on('click focus', function (e) { 33 + $(document.body).find('*').on('click focus', event => {
26 - if (!isSelection) { 34 + if (!pointerShowing || isSelection) return;
35 + let target = $(event.target);
36 + if (target.is('.zmdi') || $(event.target).closest('#pointer').length === 1) return;
37 +
27 $pointer.detach(); 38 $pointer.detach();
28 - } 39 + pointerShowing = false;
29 }); 40 });
30 41
42 + function updatePointerContent() {
43 + let inputText = pointerModeLink ? window.baseUrl(`/link/${pageId}#${pointerSectionId}`) : `{{@${pageId}#${pointerSectionId}}}`;
44 + if (pointerModeLink && inputText.indexOf('http') !== 0) inputText = window.location.protocol + "//" + window.location.host + inputText;
45 +
46 + $pointer.find('input').val(inputText);
47 + }
48 +
31 // Show pointer when selecting a single block of tagged content 49 // Show pointer when selecting a single block of tagged content
32 $('.page-content [id^="bkmrk"]').on('mouseup keyup', function (e) { 50 $('.page-content [id^="bkmrk"]').on('mouseup keyup', function (e) {
33 e.stopPropagation(); 51 e.stopPropagation();
34 - var selection = window.getSelection(); 52 + let selection = window.getSelection();
35 if (selection.toString().length === 0) return; 53 if (selection.toString().length === 0) return;
36 54
37 // Show pointer and set link 55 // Show pointer and set link
38 - var $elem = $(this); 56 + let $elem = $(this);
39 - let link = window.baseUrl('/link/' + pageId + '#' + $elem.attr('id')); 57 + pointerSectionId = $elem.attr('id');
40 - if (link.indexOf('http') !== 0) link = window.location.protocol + "//" + window.location.host + link; 58 + updatePointerContent();
41 - $pointer.find('input').val(link); 59 +
42 - $pointer.find('button').first().attr('data-clipboard-text', link);
43 $elem.before($pointer); 60 $elem.before($pointer);
44 $pointer.show(); 61 $pointer.show();
62 + pointerShowing = true;
45 63
46 // Set pointer to sit near mouse-up position 64 // Set pointer to sit near mouse-up position
47 - var pointerLeftOffset = (e.pageX - $elem.offset().left - ($pointerInner.width() / 2)); 65 + let pointerLeftOffset = (e.pageX - $elem.offset().left - ($pointerInner.width() / 2));
48 if (pointerLeftOffset < 0) pointerLeftOffset = 0; 66 if (pointerLeftOffset < 0) pointerLeftOffset = 0;
49 - var pointerLeftOffsetPercent = (pointerLeftOffset / $elem.width()) * 100; 67 + let pointerLeftOffsetPercent = (pointerLeftOffset / $elem.width()) * 100;
50 $pointerInner.css('left', pointerLeftOffsetPercent + '%'); 68 $pointerInner.css('left', pointerLeftOffsetPercent + '%');
51 69
52 isSelection = true; 70 isSelection = true;
...@@ -57,10 +75,12 @@ window.setupPageShow = module.exports = function (pageId) { ...@@ -57,10 +75,12 @@ window.setupPageShow = module.exports = function (pageId) {
57 75
58 // Go to, and highlight if necessary, the specified text. 76 // Go to, and highlight if necessary, the specified text.
59 function goToText(text) { 77 function goToText(text) {
60 - var idElem = $('.page-content #' + text).first(); 78 + let idElem = document.getElementById(text);
61 - if (idElem.length !== 0) { 79 + $('.page-content [data-highlighted]').attr('data-highlighted', '').css('background-color', '');
62 - idElem.smoothScrollTo(); 80 + if (idElem !== null) {
63 - idElem.css('background-color', 'rgba(244, 249, 54, 0.25)'); 81 + let $idElem = $(idElem);
82 + let color = $('#custom-styles').attr('data-color-light');
83 + $idElem.css('background-color', color).attr('data-highlighted', 'true').smoothScrollTo();
64 } else { 84 } else {
65 $('.page-content').find(':contains("' + text + '")').smoothScrollTo(); 85 $('.page-content').find(':contains("' + text + '")').smoothScrollTo();
66 } 86 }
...@@ -68,19 +88,24 @@ window.setupPageShow = module.exports = function (pageId) { ...@@ -68,19 +88,24 @@ window.setupPageShow = module.exports = function (pageId) {
68 88
69 // Check the hash on load 89 // Check the hash on load
70 if (window.location.hash) { 90 if (window.location.hash) {
71 - var text = window.location.hash.replace(/\%20/g, ' ').substr(1); 91 + let text = window.location.hash.replace(/\%20/g, ' ').substr(1);
72 goToText(text); 92 goToText(text);
73 } 93 }
74 94
95 + // Sidebar page nav click event
96 + $('.sidebar-page-nav').on('click', 'a', event => {
97 + goToText(event.target.getAttribute('href').substr(1));
98 + });
99 +
75 // Make the book-tree sidebar stick in view on scroll 100 // Make the book-tree sidebar stick in view on scroll
76 - var $window = $(window); 101 + let $window = $(window);
77 - var $bookTree = $(".book-tree"); 102 + let $bookTree = $(".book-tree");
78 - var $bookTreeParent = $bookTree.parent(); 103 + let $bookTreeParent = $bookTree.parent();
79 // Check the page is scrollable and the content is taller than the tree 104 // Check the page is scrollable and the content is taller than the tree
80 - var pageScrollable = ($(document).height() > $window.height()) && ($bookTree.height() < $('.page-content').height()); 105 + let pageScrollable = ($(document).height() > $window.height()) && ($bookTree.height() < $('.page-content').height());
81 // Get current tree's width and header height 106 // Get current tree's width and header height
82 - var headerHeight = $("#header").height() + $(".toolbar").height(); 107 + let headerHeight = $("#header").height() + $(".toolbar").height();
83 - var isFixed = $window.scrollTop() > headerHeight; 108 + let isFixed = $window.scrollTop() > headerHeight;
84 // Function to fix the tree as a sidebar 109 // Function to fix the tree as a sidebar
85 function stickTree() { 110 function stickTree() {
86 $bookTree.width($bookTreeParent.width() + 15); 111 $bookTree.width($bookTreeParent.width() + 15);
...@@ -95,7 +120,7 @@ window.setupPageShow = module.exports = function (pageId) { ...@@ -95,7 +120,7 @@ window.setupPageShow = module.exports = function (pageId) {
95 } 120 }
96 // Checks if the tree stickiness state should change 121 // Checks if the tree stickiness state should change
97 function checkTreeStickiness(skipCheck) { 122 function checkTreeStickiness(skipCheck) {
98 - var shouldBeFixed = $window.scrollTop() > headerHeight; 123 + let shouldBeFixed = $window.scrollTop() > headerHeight;
99 if (shouldBeFixed && (!isFixed || skipCheck)) { 124 if (shouldBeFixed && (!isFixed || skipCheck)) {
100 stickTree(); 125 stickTree();
101 } else if (!shouldBeFixed && (isFixed || skipCheck)) { 126 } else if (!shouldBeFixed && (isFixed || skipCheck)) {
......
1 +/**
2 + * Translation Manager
3 + * Handles the JavaScript side of translating strings
4 + * in a way which fits with Laravel.
5 + */
6 +class Translator {
7 +
8 + /**
9 + * Create an instance, Passing in the required translations
10 + * @param translations
11 + */
12 + constructor(translations) {
13 + this.store = translations;
14 + }
15 +
16 + /**
17 + * Get a translation, Same format as laravel's 'trans' helper
18 + * @param key
19 + * @param replacements
20 + * @returns {*}
21 + */
22 + get(key, replacements) {
23 + let splitKey = key.split('.');
24 + let value = splitKey.reduce((a, b) => {
25 + return a != undefined ? a[b] : a;
26 + }, this.store);
27 +
28 + if (value === undefined) {
29 + console.log(`Translation with key "${key}" does not exist`);
30 + value = key;
31 + }
32 +
33 + if (replacements === undefined) return value;
34 +
35 + let replaceMatches = value.match(/:([\S]+)/g);
36 + if (replaceMatches === null) return value;
37 + replaceMatches.forEach(match => {
38 + let key = match.substring(1);
39 + if (typeof replacements[key] === 'undefined') return;
40 + value = value.replace(match, replacements[key]);
41 + });
42 + return value;
43 + }
44 +
45 +}
46 +
47 +export default Translator
...@@ -136,9 +136,6 @@ ...@@ -136,9 +136,6 @@
136 background-color: #EEE; 136 background-color: #EEE;
137 padding: $-s; 137 padding: $-s;
138 display: block; 138 display: block;
139 - > * {
140 - display: inline-block;
141 - }
142 &:before { 139 &:before {
143 font-family: 'Material-Design-Iconic-Font'; 140 font-family: 'Material-Design-Iconic-Font';
144 padding-right: $-s; 141 padding-right: $-s;
......
...@@ -109,4 +109,3 @@ $button-border-radius: 2px; ...@@ -109,4 +109,3 @@ $button-border-radius: 2px;
109 box-shadow: none; 109 box-shadow: none;
110 } 110 }
111 } 111 }
...\ No newline at end of file ...\ No newline at end of file
112 -
......
...@@ -70,9 +70,6 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { ...@@ -70,9 +70,6 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
70 #entity-selector-wrap .popup-body .form-group { 70 #entity-selector-wrap .popup-body .form-group {
71 margin: 0; 71 margin: 0;
72 } 72 }
73 -//body.ie #entity-selector-wrap .popup-body .form-group {
74 -// min-height: 60vh;
75 -//}
76 73
77 .image-manager-body { 74 .image-manager-body {
78 min-height: 70vh; 75 min-height: 70vh;
...@@ -466,3 +463,7 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { ...@@ -466,3 +463,7 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
466 } 463 }
467 } 464 }
468 } 465 }
466 +
467 +.image-picker .none {
468 + display: none;
469 +}
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
33 position: relative; 33 position: relative;
34 z-index: 5; 34 z-index: 5;
35 textarea { 35 textarea {
36 - font-family: 'Roboto Mono'; 36 + font-family: 'Roboto Mono', monospace;
37 font-style: normal; 37 font-style: normal;
38 font-weight: 400; 38 font-weight: 400;
39 padding: $-xs $-m; 39 padding: $-xs $-m;
...@@ -55,6 +55,7 @@ ...@@ -55,6 +55,7 @@
55 display: flex; 55 display: flex;
56 flex-direction: column; 56 flex-direction: column;
57 border: 1px solid #DDD; 57 border: 1px solid #DDD;
58 + width: 50%;
58 } 59 }
59 .markdown-display { 60 .markdown-display {
60 padding: 0 $-m 0; 61 padding: 0 $-m 0;
...@@ -68,7 +69,7 @@ ...@@ -68,7 +69,7 @@
68 .editor-toolbar { 69 .editor-toolbar {
69 width: 100%; 70 width: 100%;
70 padding: $-xs $-m; 71 padding: $-xs $-m;
71 - font-family: 'Roboto Mono'; 72 + font-family: 'Roboto Mono', monospace;
72 font-size: 11px; 73 font-size: 11px;
73 line-height: 1.6; 74 line-height: 1.6;
74 border-bottom: 1px solid #DDD; 75 border-bottom: 1px solid #DDD;
...@@ -268,8 +269,3 @@ input.outline { ...@@ -268,8 +269,3 @@ input.outline {
268 .image-picker img { 269 .image-picker img {
269 background-color: #BBB; 270 background-color: #BBB;
270 } 271 }
...\ No newline at end of file ...\ No newline at end of file
271 -
272 -div[toggle-switch] {
273 - height: 18px;
274 - width: 150px;
275 -}
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -322,6 +322,9 @@ ul.pagination { ...@@ -322,6 +322,9 @@ ul.pagination {
322 font-size: 0.75em; 322 font-size: 0.75em;
323 margin-top: $-xs; 323 margin-top: $-xs;
324 } 324 }
325 + .text-muted p.text-muted {
326 + margin-top: 0;
327 + }
325 .page.draft .text-page { 328 .page.draft .text-page {
326 color: $color-page-draft; 329 color: $color-page-draft;
327 } 330 }
......
...@@ -138,6 +138,10 @@ ...@@ -138,6 +138,10 @@
138 font-size: 18px; 138 font-size: 18px;
139 padding-top: 4px; 139 padding-top: 4px;
140 } 140 }
141 + span.icon {
142 + cursor: pointer;
143 + user-select: none;
144 + }
141 .button { 145 .button {
142 line-height: 1; 146 line-height: 1;
143 margin: 0 0 0 -4px; 147 margin: 0 0 0 -4px;
......
...@@ -35,6 +35,12 @@ table.table { ...@@ -35,6 +35,12 @@ table.table {
35 tr:hover { 35 tr:hover {
36 background-color: #EEE; 36 background-color: #EEE;
37 } 37 }
38 + .text-right {
39 + text-align: right;
40 + }
41 + .text-center {
42 + text-align: center;
43 + }
38 } 44 }
39 45
40 table.no-style { 46 table.no-style {
......
...@@ -109,6 +109,9 @@ em, i, .italic { ...@@ -109,6 +109,9 @@ em, i, .italic {
109 small, p.small, span.small, .text-small { 109 small, p.small, span.small, .text-small {
110 font-size: 0.8em; 110 font-size: 0.8em;
111 color: lighten($text-dark, 20%); 111 color: lighten($text-dark, 20%);
112 + small, p.small, span.small, .text-small {
113 + font-size: 1em;
114 + }
112 } 115 }
113 116
114 sup, .superscript { 117 sup, .superscript {
...@@ -172,6 +175,7 @@ pre code { ...@@ -172,6 +175,7 @@ pre code {
172 background-color: transparent; 175 background-color: transparent;
173 border: 0; 176 border: 0;
174 font-size: 1em; 177 font-size: 1em;
178 + display: block;
175 } 179 }
176 /* 180 /*
177 * Text colors 181 * Text colors
......
1 -//@import "reset"; 1 +@import "reset";
2 @import "variables"; 2 @import "variables";
3 @import "mixins"; 3 @import "mixins";
4 @import "html"; 4 @import "html";
......
...@@ -16,7 +16,7 @@ return [ ...@@ -16,7 +16,7 @@ return [
16 'app_name_desc' => 'Dieser Name wird im Header und E-Mails angezeigt.', 16 'app_name_desc' => 'Dieser Name wird im Header und E-Mails angezeigt.',
17 'app_name_header' => 'Anwendungsname im Header anzeigen?', 17 'app_name_header' => 'Anwendungsname im Header anzeigen?',
18 'app_public_viewing' => '&Ouml;ffentliche Ansicht erlauben?', 18 'app_public_viewing' => '&Ouml;ffentliche Ansicht erlauben?',
19 - 'app_secure_images' => 'Erh&oml;hte Sicherheit f&uuml;r Bilduploads aktivieren?', 19 + 'app_secure_images' => 'Erh&ouml;hte Sicherheit f&uuml;r Bilduploads aktivieren?',
20 'app_secure_images_desc' => 'Aus Leistungsgr&uuml;nden sind alle Bilder &ouml;ffentlich sichtbar. Diese Option f&uuml;gt zuf&auml;llige, schwer zu eratene, Zeichenketten vor die Bild-URLs hinzu. Stellen sie sicher, dass Verzeichnindexes deaktiviert sind, um einen einfachen Zugrif zu verhindern.', 20 'app_secure_images_desc' => 'Aus Leistungsgr&uuml;nden sind alle Bilder &ouml;ffentlich sichtbar. Diese Option f&uuml;gt zuf&auml;llige, schwer zu eratene, Zeichenketten vor die Bild-URLs hinzu. Stellen sie sicher, dass Verzeichnindexes deaktiviert sind, um einen einfachen Zugrif zu verhindern.',
21 'app_editor' => 'Seiteneditor', 21 'app_editor' => 'Seiteneditor',
22 'app_editor_desc' => 'W&auml;hlen sie den Editor aus, der von allen Benutzern genutzt werden soll, um Seiten zu editieren.', 22 'app_editor_desc' => 'W&auml;hlen sie den Editor aus, der von allen Benutzern genutzt werden soll, um Seiten zu editieren.',
......
...@@ -14,7 +14,49 @@ return [ ...@@ -14,7 +14,49 @@ return [
14 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', 14 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
15 15
16 /** 16 /**
17 - * Email Confirmation Text 17 + * Login & Register
18 + */
19 + 'sign_up' => 'Sign up',
20 + 'log_in' => 'Log in',
21 + 'logout' => 'Logout',
22 +
23 + 'name' => 'Name',
24 + 'username' => 'Username',
25 + 'email' => 'Email',
26 + 'password' => 'Password',
27 + 'password_confirm' => 'Confirm Password',
28 + 'password_hint' => 'Must be over 5 characters',
29 + 'forgot_password' => 'Forgot Password?',
30 + 'remember_me' => 'Remember Me',
31 + 'ldap_email_hint' => 'Please enter an email to use for this account.',
32 + 'create_account' => 'Create Account',
33 + 'social_login' => 'Social Login',
34 + 'social_registration' => 'Social Registration',
35 + 'social_registration_text' => 'Register and sign in using another service.',
36 +
37 + 'register_thanks' => 'Thanks for registering!',
38 + 'register_confirm' => 'Please check your email and click the confirmation button to access :appName.',
39 + 'registrations_disabled' => 'Registrations are currently disabled',
40 + 'registration_email_domain_invalid' => 'That email domain does not have access to this application',
41 + 'register_success' => 'Thanks for signing up! You are now registered and signed in.',
42 +
43 +
44 + /**
45 + * Password Reset
46 + */
47 + 'reset_password' => 'Reset Password',
48 + 'reset_password_send_instructions' => 'Enter your email below and you will be sent an email with a password reset link.',
49 + 'reset_password_send_button' => 'Send Reset Link',
50 + 'reset_password_sent_success' => 'A password reset link has been sent to :email.',
51 + 'reset_password_success' => 'Your password has been successfully reset.',
52 +
53 + 'email_reset_subject' => 'Reset your :appName password',
54 + 'email_reset_text' => 'You are receiving this email because we received a password reset request for your account.',
55 + 'email_reset_not_requested' => 'If you did not request a password reset, no further action is required.',
56 +
57 +
58 + /**
59 + * Email Confirmation
18 */ 60 */
19 'email_confirm_subject' => 'Confirm your email on :appName', 61 'email_confirm_subject' => 'Confirm your email on :appName',
20 'email_confirm_greeting' => 'Thanks for joining :appName!', 62 'email_confirm_greeting' => 'Thanks for joining :appName!',
...@@ -23,4 +65,10 @@ return [ ...@@ -23,4 +65,10 @@ return [
23 'email_confirm_send_error' => 'Email confirmation required but the system could not send the email. Contact the admin to ensure email is set up correctly.', 65 'email_confirm_send_error' => 'Email confirmation required but the system could not send the email. Contact the admin to ensure email is set up correctly.',
24 'email_confirm_success' => 'Your email has been confirmed!', 66 'email_confirm_success' => 'Your email has been confirmed!',
25 'email_confirm_resent' => 'Confirmation email resent, Please check your inbox.', 67 'email_confirm_resent' => 'Confirmation email resent, Please check your inbox.',
68 +
69 + 'email_not_confirmed' => 'Email Address Not Confirmed',
70 + 'email_not_confirmed_text' => 'Your email address has not yet been confirmed.',
71 + 'email_not_confirmed_click_link' => 'Please click the link in the email that was sent shortly after you registered.',
72 + 'email_not_confirmed_resend' => 'If you cannot find the email you can re-send the confirmation email by submitting the form below.',
73 + 'email_not_confirmed_resend_button' => 'Resend Confirmation Email',
26 ]; 74 ];
...\ No newline at end of file ...\ No newline at end of file
......
1 +<?php
2 +return [
3 +
4 + /**
5 + * Buttons
6 + */
7 + 'cancel' => 'Cancel',
8 + 'confirm' => 'Confirm',
9 + 'back' => 'Back',
10 + 'save' => 'Save',
11 + 'continue' => 'Continue',
12 + 'select' => 'Select',
13 +
14 + /**
15 + * Form Labels
16 + */
17 + 'name' => 'Name',
18 + 'description' => 'Description',
19 + 'role' => 'Role',
20 +
21 + /**
22 + * Actions
23 + */
24 + 'actions' => 'Actions',
25 + 'view' => 'View',
26 + 'create' => 'Create',
27 + 'update' => 'Update',
28 + 'edit' => 'Edit',
29 + 'sort' => 'Sort',
30 + 'move' => 'Move',
31 + 'delete' => 'Delete',
32 + 'search' => 'Search',
33 + 'search_clear' => 'Clear Search',
34 + 'reset' => 'Reset',
35 + 'remove' => 'Remove',
36 +
37 +
38 + /**
39 + * Misc
40 + */
41 + 'deleted_user' => 'Deleted User',
42 + 'no_activity' => 'No activity to show',
43 + 'no_items' => 'No items available',
44 + 'back_to_top' => 'Back to top',
45 + 'toggle_details' => 'Toggle Details',
46 +
47 + /**
48 + * Header
49 + */
50 + 'view_profile' => 'View Profile',
51 + 'edit_profile' => 'Edit Profile',
52 +
53 + /**
54 + * Email Content
55 + */
56 + 'email_action_help' => 'If you’re having trouble clicking the ":actionText" button, copy and paste the URL below into your web browser:',
57 + 'email_rights' => 'All rights reserved',
58 +];
...\ No newline at end of file ...\ No newline at end of file
1 +<?php
2 +return [
3 +
4 + /**
5 + * Image Manager
6 + */
7 + 'image_select' => 'Image Select',
8 + 'image_all' => 'All',
9 + 'image_all_title' => 'View all images',
10 + 'image_book_title' => 'View images uploaded to this book',
11 + 'image_page_title' => 'View images uploaded to this page',
12 + 'image_search_hint' => 'Search by image name',
13 + 'image_uploaded' => 'Uploaded :uploadedDate',
14 + 'image_load_more' => 'Load More',
15 + 'image_image_name' => 'Image Name',
16 + 'image_delete_confirm' => 'This image is used in the pages below, Click delete again to confirm you want to delete this image.',
17 + 'image_select_image' => 'Select Image',
18 + 'image_dropzone' => 'Drop images or click here to upload',
19 + 'images_deleted' => 'Images Deleted',
20 + 'image_preview' => 'Image Preview',
21 + 'image_upload_success' => 'Image uploaded successfully',
22 + 'image_update_success' => 'Image details successfully updated',
23 + 'image_delete_success' => 'Image successfully deleted'
24 +];
...\ No newline at end of file ...\ No newline at end of file
1 +<?php
2 +return [
3 +
4 + /**
5 + * Shared
6 + */
7 + 'recently_created' => 'Recently Created',
8 + 'recently_created_pages' => 'Recently Created Pages',
9 + 'recently_updated_pages' => 'Recently Updated Pages',
10 + 'recently_created_chapters' => 'Recently Created Chapters',
11 + 'recently_created_books' => 'Recently Created Books',
12 + 'recently_update' => 'Recently Updated',
13 + 'recently_viewed' => 'Recently Viewed',
14 + 'recent_activity' => 'Recent Activity',
15 + 'create_now' => 'Create one now',
16 + 'revisions' => 'Revisions',
17 + 'meta_created' => 'Created :timeLength',
18 + 'meta_created_name' => 'Created :timeLength by :user',
19 + 'meta_updated' => 'Updated :timeLength',
20 + 'meta_updated_name' => 'Updated :timeLength by :user',
21 + 'x_pages' => ':count Pages',
22 + 'entity_select' => 'Entity Select',
23 + 'images' => 'Images',
24 + 'my_recent_drafts' => 'My Recent Drafts',
25 + 'my_recently_viewed' => 'My Recently Viewed',
26 + 'no_pages_viewed' => 'You have not viewed any pages',
27 + 'no_pages_recently_created' => 'No pages have been recently created',
28 + 'no_pages_recently_updated' => 'No pages have been recently updated',
29 +
30 + /**
31 + * Permissions and restrictions
32 + */
33 + 'permissions' => 'Permissions',
34 + 'permissions_intro' => 'Once enabled, These permissions will take priority over any set role permissions.',
35 + 'permissions_enable' => 'Enable Custom Permissions',
36 + 'permissions_save' => 'Save Permissions',
37 +
38 + /**
39 + * Search
40 + */
41 + 'search_results' => 'Search Results',
42 + 'search_results_page' => 'Page Search Results',
43 + 'search_results_chapter' => 'Chapter Search Results',
44 + 'search_results_book' => 'Book Search Results',
45 + 'search_clear' => 'Clear Search',
46 + 'search_view_pages' => 'View all matches pages',
47 + 'search_view_chapters' => 'View all matches chapters',
48 + 'search_view_books' => 'View all matches books',
49 + 'search_no_pages' => 'No pages matched this search',
50 + 'search_for_term' => 'Search for :term',
51 + 'search_page_for_term' => 'Page search for :term',
52 + 'search_chapter_for_term' => 'Chapter search for :term',
53 + 'search_book_for_term' => 'Books search for :term',
54 +
55 + /**
56 + * Books
57 + */
58 + 'book' => 'Book',
59 + 'books' => 'Books',
60 + 'books_empty' => 'No books have been created',
61 + 'books_popular' => 'Popular Books',
62 + 'books_recent' => 'Recent Books',
63 + 'books_popular_empty' => 'The most popular books will appear here.',
64 + 'books_create' => 'Create New Book',
65 + 'books_delete' => 'Delete Book',
66 + 'books_delete_named' => 'Delete Book :bookName',
67 + 'books_delete_explain' => 'This will delete the book with the name \':bookName\', All pages and chapters will be removed.',
68 + 'books_delete_confirmation' => 'Are you sure you want to delete this book?',
69 + 'books_edit' => 'Edit Book',
70 + 'books_edit_named' => 'Edit Book :bookName',
71 + 'books_form_book_name' => 'Book Name',
72 + 'books_save' => 'Save Book',
73 + 'books_permissions' => 'Book Permissions',
74 + 'books_permissions_updated' => 'Book Permissions Updated',
75 + 'books_empty_contents' => 'No pages or chapters have been created for this book.',
76 + 'books_empty_create_page' => 'Create a new page',
77 + 'books_empty_or' => 'or',
78 + 'books_empty_sort_current_book' => 'Sort the current book',
79 + 'books_empty_add_chapter' => 'Add a chapter',
80 + 'books_permissions_active' => 'Book Permissions Active',
81 + 'books_search_this' => 'Search this book',
82 + 'books_navigation' => 'Book Navigation',
83 + 'books_sort' => 'Sort Book Contents',
84 + 'books_sort_named' => 'Sort Book :bookName',
85 + 'books_sort_show_other' => 'Show Other Books',
86 + 'books_sort_save' => 'Save New Order',
87 +
88 + /**
89 + * Chapters
90 + */
91 + 'chapter' => 'Chapter',
92 + 'chapters' => 'Chapters',
93 + 'chapters_popular' => 'Popular Chapters',
94 + 'chapters_new' => 'New Chapter',
95 + 'chapters_create' => 'Create New Chapter',
96 + 'chapters_delete' => 'Delete Chapter',
97 + 'chapters_delete_named' => 'Delete Chapter :chapterName',
98 + 'chapters_delete_explain' => 'This will delete the chapter with the name \':chapterName\', All pages will be removed
99 + and added directly to the parent book.',
100 + 'chapters_delete_confirm' => 'Are you sure you want to delete this chapter?',
101 + 'chapters_edit' => 'Edit Chapter',
102 + 'chapters_edit_named' => 'Edit Chapter :chapterName',
103 + 'chapters_save' => 'Save Chapter',
104 + 'chapters_move' => 'Move Chapter',
105 + 'chapters_move_named' => 'Move Chapter :chapterName',
106 + 'chapter_move_success' => 'Chapter moved to :bookName',
107 + 'chapters_permissions' => 'Chapter Permissions',
108 + 'chapters_empty' => 'No pages are currently in this chapter.',
109 + 'chapters_permissions_active' => 'Chapter Permissions Active',
110 + 'chapters_permissions_success' => 'Chapter Permissions Updated',
111 +
112 + /**
113 + * Pages
114 + */
115 + 'page' => 'Page',
116 + 'pages' => 'Pages',
117 + 'pages_popular' => 'Popular Pages',
118 + 'pages_new' => 'New Page',
119 + 'pages_attachments' => 'Attachments',
120 + 'pages_navigation' => 'Page Navigation',
121 + 'pages_delete' => 'Delete Page',
122 + 'pages_delete_named' => 'Delete Page :pageName',
123 + 'pages_delete_draft_named' => 'Delete Draft Page :pageName',
124 + 'pages_delete_draft' => 'Delete Draft Page',
125 + 'pages_delete_success' => 'Page deleted',
126 + 'pages_delete_draft_success' => 'Draft page deleted',
127 + 'pages_delete_confirm' => 'Are you sure you want to delete this page?',
128 + 'pages_delete_draft_confirm' => 'Are you sure you want to delete this draft page?',
129 + 'pages_editing_named' => 'Editing Page :pageName',
130 + 'pages_edit_toggle_header' => 'Toggle header',
131 + 'pages_edit_save_draft' => 'Save Draft',
132 + 'pages_edit_draft' => 'Edit Page Draft',
133 + 'pages_editing_draft' => 'Editing Draft',
134 + 'pages_editing_page' => 'Editing Page',
135 + 'pages_edit_draft_save_at' => 'Draft saved at ',
136 + 'pages_edit_delete_draft' => 'Delete Draft',
137 + 'pages_edit_discard_draft' => 'Discard Draft',
138 + 'pages_edit_set_changelog' => 'Set Changelog',
139 + 'pages_edit_enter_changelog_desc' => 'Enter a brief description of the changes you\'ve made',
140 + 'pages_edit_enter_changelog' => 'Enter Changelog',
141 + 'pages_save' => 'Save Page',
142 + 'pages_title' => 'Page Title',
143 + 'pages_name' => 'Page Name',
144 + 'pages_md_editor' => 'Editor',
145 + 'pages_md_preview' => 'Preview',
146 + 'pages_md_insert_image' => 'Insert Image',
147 + 'pages_md_insert_link' => 'Insert Entity Link',
148 + 'pages_not_in_chapter' => 'Page is not in a chapter',
149 + 'pages_move' => 'Move Page',
150 + 'pages_move_success' => 'Page moved to ":parentName"',
151 + 'pages_permissions' => 'Page Permissions',
152 + 'pages_permissions_success' => 'Page permissions updated',
153 + 'pages_revisions' => 'Page Revisions',
154 + 'pages_revisions_named' => 'Page Revisions for :pageName',
155 + 'pages_revision_named' => 'Page Revision for :pageName',
156 + 'pages_revisions_created_by' => 'Created By',
157 + 'pages_revisions_date' => 'Revision Date',
158 + 'pages_revisions_changelog' => 'Changelog',
159 + 'pages_revisions_changes' => 'Changes',
160 + 'pages_revisions_current' => 'Current Version',
161 + 'pages_revisions_preview' => 'Preview',
162 + 'pages_revisions_restore' => 'Restore',
163 + 'pages_revisions_none' => 'This page has no revisions',
164 + 'pages_export' => 'Export',
165 + 'pages_export_html' => 'Contained Web File',
166 + 'pages_export_pdf' => 'PDF File',
167 + 'pages_export_text' => 'Plain Text File',
168 + 'pages_copy_link' => 'Copy Link',
169 + 'pages_permissions_active' => 'Page Permissions Active',
170 + 'pages_initial_revision' => 'Initial publish',
171 + 'pages_initial_name' => 'New Page',
172 + 'pages_editing_draft_notification' => 'You are currently editing a draft that was last saved :timeDiff.',
173 + 'pages_draft_edited_notification' => 'This page has been updated by since that time. It is recommended that you discard this draft.',
174 + 'pages_draft_edit_active' => [
175 + 'start_a' => ':count users have started editing this page',
176 + 'start_b' => ':userName has started editing this page',
177 + 'time_a' => 'since the pages was last updated',
178 + 'time_b' => 'in the last :minCount minutes',
179 + 'message' => ':start :time. Take care not to overwrite each other\'s updates!',
180 + ],
181 + 'pages_draft_discarded' => 'Draft discarded, The editor has been updated with the current page content',
182 +
183 + /**
184 + * Editor sidebar
185 + */
186 + 'page_tags' => 'Page Tags',
187 + 'tag' => 'Tag',
188 + 'tags' => '',
189 + 'tag_value' => 'Tag Value (Optional)',
190 + 'tags_explain' => "Add some tags to better categorise your content. \n You can assign a value to a tag for more in-depth organisation.",
191 + 'tags_add' => 'Add another tag',
192 + 'attachments' => 'Attachments',
193 + 'attachments_explain' => 'Upload some files or attach some link to display on your page. These are visible in the page sidebar.',
194 + 'attachments_explain_instant_save' => 'Changes here are saved instantly.',
195 + 'attachments_items' => 'Attached Items',
196 + 'attachments_upload' => 'Upload File',
197 + 'attachments_link' => 'Attach Link',
198 + 'attachments_set_link' => 'Set Link',
199 + 'attachments_delete_confirm' => 'Click delete again to confirm you want to delete this attachment.',
200 + 'attachments_dropzone' => 'Drop files or click here to attach a file',
201 + 'attachments_no_files' => 'No files have been uploaded',
202 + 'attachments_explain_link' => 'You can attach a link if you\'d prefer not to upload a file. This can be a link to another page or a link to a file in the cloud.',
203 + 'attachments_link_name' => 'Link Name',
204 + 'attachment_link' => 'Attachment link',
205 + 'attachments_link_url' => 'Link to file',
206 + 'attachments_link_url_hint' => 'Url of site or file',
207 + 'attach' => 'Attach',
208 + 'attachments_edit_file' => 'Edit File',
209 + 'attachments_edit_file_name' => 'File Name',
210 + 'attachments_edit_drop_upload' => 'Drop files or click here to upload and overwrite',
211 + 'attachments_order_updated' => 'Attachment order updated',
212 + 'attachments_updated_success' => 'Attachment details updated',
213 + 'attachments_deleted' => 'Attachment deleted',
214 + 'attachments_file_uploaded' => 'File successfully uploaded',
215 + 'attachments_file_updated' => 'File successfully updated',
216 + 'attachments_link_attached' => 'Link successfully attached to page',
217 +
218 + /**
219 + * Profile View
220 + */
221 + 'profile_user_for_x' => 'User for :time',
222 + 'profile_created_content' => 'Created Content',
223 + 'profile_not_created_pages' => ':userName has not created any pages',
224 + 'profile_not_created_chapters' => ':userName has not created any chapters',
225 + 'profile_not_created_books' => ':userName has not created any books',
226 +];
...\ No newline at end of file ...\ No newline at end of file
...@@ -6,7 +6,65 @@ return [ ...@@ -6,7 +6,65 @@ return [
6 * Error text strings. 6 * Error text strings.
7 */ 7 */
8 8
9 - // Pages 9 + // Permissions
10 'permission' => 'You do not have permission to access the requested page.', 10 'permission' => 'You do not have permission to access the requested page.',
11 - 'permissionJson' => 'You do not have permission to perform the requested action.' 11 + 'permissionJson' => 'You do not have permission to perform the requested action.',
12 +
13 + // Auth
14 + 'error_user_exists_different_creds' => 'A user with the email :email already exists but with different credentials.',
15 + 'email_already_confirmed' => 'Email has already been confirmed, Try logging in.',
16 + 'email_confirmation_invalid' => 'This confirmation token is not valid or has already been used, Please try registering again.',
17 + 'email_confirmation_expired' => 'The confirmation token has expired, A new confirmation email has been sent.',
18 + 'ldap_fail_anonymous' => 'LDAP access failed using anonymous bind',
19 + 'ldap_fail_authed' => 'LDAP access failed using given dn & password details',
20 + 'ldap_extension_not_installed' => 'LDAP PHP extension not installed',
21 + 'ldap_cannot_connect' => 'Cannot connect to ldap server, Initial connection failed',
22 + 'social_no_action_defined' => 'No action defined',
23 + 'social_account_in_use' => 'This :socialAccount account is already in use, Try logging in via the :socialAccount option.',
24 + 'social_account_email_in_use' => 'The email :email is already in use. If you already have an account you can connect your :socialAccount account from your profile settings.',
25 + 'social_account_existing' => 'This :socialAccount is already attached to your profile.',
26 + 'social_account_already_used_existing' => 'This :socialAccount account is already used by another user.',
27 + 'social_account_not_used' => 'This :socialAccount account is not linked to any users. Please attach it in your profile settings. ',
28 + 'social_account_register_instructions' => 'If you do not yet have an account, You can register an account using the :socialAccount option.',
29 + 'social_driver_not_found' => 'Social driver not found',
30 + 'social_driver_not_configured' => 'Your :socialAccount social settings are not configured correctly.',
31 +
32 + // System
33 + 'path_not_writable' => 'File path :filePath could not be uploaded to. Ensure it is writable to the server.',
34 + 'cannot_get_image_from_url' => 'Cannot get image from :url',
35 + 'cannot_create_thumbs' => 'The server cannot create thumbnails. Please check you have the GD PHP extension installed.',
36 + 'server_upload_limit' => 'The server does not allow uploads of this size. Please try a smaller file size.',
37 + 'image_upload_error' => 'An error occurred uploading the image',
38 +
39 + // Attachments
40 + 'attachment_page_mismatch' => 'Page mismatch during attachment update',
41 +
42 + // Pages
43 + 'page_draft_autosave_fail' => 'Failed to save draft. Ensure you have internet connection before saving this page',
44 +
45 + // Entities
46 + 'entity_not_found' => 'Entity not found',
47 + 'book_not_found' => 'Book not found',
48 + 'page_not_found' => 'Page not found',
49 + 'chapter_not_found' => 'Chapter not found',
50 + 'selected_book_not_found' => 'The selected book was not found',
51 + 'selected_book_chapter_not_found' => 'The selected Book or Chapter was not found',
52 + 'guests_cannot_save_drafts' => 'Guests cannot save drafts',
53 +
54 + // Users
55 + 'users_cannot_delete_only_admin' => 'You cannot delete the only admin',
56 + 'users_cannot_delete_guest' => 'You cannot delete the guest user',
57 +
58 + // Roles
59 + 'role_cannot_be_edited' => 'This role cannot be edited',
60 + 'role_system_cannot_be_deleted' => 'This role is a system role and cannot be deleted',
61 + 'role_registration_default_cannot_delete' => 'This role cannot be deleted while set as the default registration role',
62 +
63 + // Error pages
64 + '404_page_not_found' => 'Page Not Found',
65 + 'sorry_page_not_found' => 'Sorry, The page you were looking for could not be found.',
66 + 'return_home' => 'Return to home',
67 + 'error_occurred' => 'An Error Occurred',
68 + 'app_down' => ':appName is down right now',
69 + 'back_soon' => 'It will be back up soon.',
12 ]; 70 ];
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -10,6 +10,11 @@ return [ ...@@ -10,6 +10,11 @@ return [
10 10
11 'settings' => 'Settings', 11 'settings' => 'Settings',
12 'settings_save' => 'Save Settings', 12 'settings_save' => 'Save Settings',
13 + 'settings_save_success' => 'Settings saved',
14 +
15 + /**
16 + * App settings
17 + */
13 18
14 'app_settings' => 'App Settings', 19 'app_settings' => 'App Settings',
15 'app_name' => 'Application name', 20 'app_name' => 'Application name',
...@@ -27,6 +32,10 @@ return [ ...@@ -27,6 +32,10 @@ return [
27 'app_primary_color' => 'Application primary color', 32 'app_primary_color' => 'Application primary color',
28 'app_primary_color_desc' => 'This should be a hex value. <br>Leave empty to reset to the default color.', 33 'app_primary_color_desc' => 'This should be a hex value. <br>Leave empty to reset to the default color.',
29 34
35 + /**
36 + * Registration settings
37 + */
38 +
30 'reg_settings' => 'Registration Settings', 39 'reg_settings' => 'Registration Settings',
31 'reg_allow' => 'Allow registration?', 40 'reg_allow' => 'Allow registration?',
32 'reg_default_role' => 'Default user role after registration', 41 'reg_default_role' => 'Default user role after registration',
...@@ -36,4 +45,79 @@ return [ ...@@ -36,4 +45,79 @@ return [
36 'reg_confirm_restrict_domain_desc' => '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. <br> Note that users will be able to change their email addresses after successful registration.', 45 'reg_confirm_restrict_domain_desc' => '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. <br> Note that users will be able to change their email addresses after successful registration.',
37 'reg_confirm_restrict_domain_placeholder' => 'No restriction set', 46 'reg_confirm_restrict_domain_placeholder' => 'No restriction set',
38 47
48 + /**
49 + * Role settings
50 + */
51 +
52 + 'roles' => 'Roles',
53 + 'role_user_roles' => 'User Roles',
54 + 'role_create' => 'Create New Role',
55 + 'role_create_success' => 'Role successfully created',
56 + 'role_delete' => 'Delete Role',
57 + 'role_delete_confirm' => 'This will delete the role with the name \':roleName\'.',
58 + 'role_delete_users_assigned' => 'This role has :userCount users assigned to it. If you would like to migrate the users from this role select a new role below.',
59 + 'role_delete_no_migration' => "Don't migrate users",
60 + 'role_delete_sure' => 'Are you sure you want to delete this role?',
61 + 'role_delete_success' => 'Role successfully deleted',
62 + 'role_edit' => 'Edit Role',
63 + 'role_details' => 'Role Details',
64 + 'role_name' => 'Role Name',
65 + 'role_desc' => 'Short Description of Role',
66 + 'role_system' => 'System Permissions',
67 + 'role_manage_users' => 'Manage users',
68 + 'role_manage_roles' => 'Manage roles & role permissions',
69 + 'role_manage_entity_permissions' => 'Manage all book, chapter & page permissions',
70 + 'role_manage_own_entity_permissions' => 'Manage permissions on own book, chapter & pages',
71 + 'role_manage_settings' => 'Manage app settings',
72 + 'role_asset' => 'Asset Permissions',
73 + 'role_asset_desc' => 'These permissions control default access to the assets within the system. Permissions on Books, Chapters and Pages will override these permissions.',
74 + 'role_all' => 'All',
75 + 'role_own' => 'Own',
76 + 'role_controlled_by_asset' => 'Controlled by the asset they are uploaded to',
77 + 'role_save' => 'Save Role',
78 + 'role_update_success' => 'Role successfully updated',
79 + 'role_users' => 'Users in this role',
80 + 'role_users_none' => 'No users are currently assigned to this role',
81 +
82 + /**
83 + * Users
84 + */
85 +
86 + 'users' => 'Users',
87 + 'user_profile' => 'User Profile',
88 + 'users_add_new' => 'Add New User',
89 + 'users_search' => 'Search Users',
90 + 'users_role' => 'User Roles',
91 + 'users_external_auth_id' => 'External Authentication ID',
92 + 'users_password_warning' => 'Only fill the below if you would like to change your password:',
93 + 'users_system_public' => 'This user represents any guest users that visit your instance. It cannot be used to log in but is assigned automatically.',
94 + 'users_delete' => 'Delete User',
95 + 'users_delete_named' => 'Delete user :userName',
96 + 'users_delete_warning' => 'This will fully delete this user with the name \':userName\' from the system.',
97 + 'users_delete_confirm' => 'Are you sure you want to delete this user?',
98 + 'users_delete_success' => 'Users successfully removed',
99 + 'users_edit' => 'Edit User',
100 + 'users_edit_profile' => 'Edit Profile',
101 + 'users_edit_success' => 'User successfully updated',
102 + 'users_avatar' => 'User Avatar',
103 + 'users_avatar_desc' => 'This image should be approx 256px square.',
104 + 'users_preferred_language' => 'Preferred Language',
105 + 'users_social_accounts' => 'Social Accounts',
106 + 'users_social_accounts_info' => 'Here you can connect your other accounts for quicker and easier login. Disconnecting an account here does not previously authorized access. Revoke access from your profile settings on the connected social account.',
107 + 'users_social_connect' => 'Connect Account',
108 + 'users_social_disconnect' => 'Disconnect Account',
109 + 'users_social_connected' => ':socialAccount account was successfully attached to your profile.',
110 + 'users_social_disconnected' => ':socialAccount account was successfully disconnected from your profile.',
111 +
112 + // Since these labels are already localized this array does not need to be
113 + // translated in the language-specific files.
114 + // DELETE BELOW IF COPIED FROM EN
115 + ///////////////////////////////////
116 + 'language_select' => [
117 + 'en' => 'English',
118 + 'de' => 'Deutsch',
119 + 'fr' => 'Français',
120 + 'pt_BR' => 'Português do Brasil'
121 + ]
122 + ///////////////////////////////////
39 ]; 123 ];
......
...@@ -87,8 +87,8 @@ return [ ...@@ -87,8 +87,8 @@ return [
87 */ 87 */
88 88
89 'custom' => [ 89 'custom' => [
90 - 'attribute-name' => [ 90 + 'password-confirm' => [
91 - 'rule-name' => 'custom-message', 91 + 'required_with' => 'Password confirmation required',
92 ], 92 ],
93 ], 93 ],
94 94
......
1 +<?php
2 +
3 +return [
4 +
5 + /**
6 + * Activity text strings.
7 + * Is used for all the text within activity logs & notifications.
8 + */
9 +
10 + // Pages
11 + 'page_create' => 'a créé la page',
12 + 'page_create_notification' => 'Page créée avec succès',
13 + 'page_update' => 'a modifié la page',
14 + 'page_update_notification' => 'Page modifiée avec succès',
15 + 'page_delete' => 'a supprimé la page',
16 + 'page_delete_notification' => 'Page supprimée avec succès',
17 + 'page_restore' => 'a restauré la page',
18 + 'page_restore_notification' => 'Page réstaurée avec succès',
19 + 'page_move' => 'a déplacé la page',
20 +
21 + // Chapters
22 + 'chapter_create' => 'a créé le chapitre',
23 + 'chapter_create_notification' => 'Chapitre créé avec succès',
24 + 'chapter_update' => 'a modifié le chapitre',
25 + 'chapter_update_notification' => 'Chapitre modifié avec succès',
26 + 'chapter_delete' => 'a supprimé le chapitre',
27 + 'chapter_delete_notification' => 'Chapitre supprimé avec succès',
28 + 'chapter_move' => 'a déplacé le chapitre',
29 +
30 + // Books
31 + 'book_create' => 'a créé le livre',
32 + 'book_create_notification' => 'Livre créé avec succès',
33 + 'book_update' => 'a modifié le livre',
34 + 'book_update_notification' => 'Livre modifié avec succès',
35 + 'book_delete' => 'a supprimé le livre',
36 + 'book_delete_notification' => 'Livre supprimé avec succès',
37 + 'book_sort' => 'a réordonné le livre',
38 + 'book_sort_notification' => 'Livre réordonné avec succès',
39 +
40 +];
1 +<?php
2 +return [
3 + /*
4 + |--------------------------------------------------------------------------
5 + | Authentication Language Lines
6 + |--------------------------------------------------------------------------
7 + |
8 + | The following language lines are used during authentication for various
9 + | messages that we need to display to the user. You are free to modify
10 + | these language lines according to your application's requirements.
11 + |
12 + */
13 + 'failed' => 'Ces informations ne correspondent a aucun compte.',
14 + 'throttle' => "Trop d'essais, veuillez réessayer dans :seconds secondes.",
15 +
16 + /**
17 + * Login & Register
18 + */
19 + 'sign_up' => "S'inscrire",
20 + 'log_in' => 'Se connecter',
21 + 'logout' => 'Se déconnecter',
22 +
23 + 'name' => 'Nom',
24 + 'username' => "Nom d'utilisateur",
25 + 'email' => 'E-mail',
26 + 'password' => 'Mot de passe',
27 + 'password_confirm' => 'Confirmez le mot de passe',
28 + 'password_hint' => 'Doit faire plus de 5 caractères',
29 + 'forgot_password' => 'Mot de passe oublié?',
30 + 'remember_me' => 'Se souvenir de moi',
31 + 'ldap_email_hint' => "Merci d'entrer une adresse e-mail pour ce compte",
32 + 'create_account' => 'Créer un compte',
33 + 'social_login' => 'Social Login',
34 + 'social_registration' => 'Enregistrement Social',
35 + 'social_registration_text' => "S'inscrire et se connecter avec un réseau social",
36 +
37 + 'register_thanks' => 'Merci pour votre enregistrement',
38 + 'register_confirm' => 'Vérifiez vos e-mails et cliquer sur le lien de confirmation pour rejoindre :appName.',
39 + 'registrations_disabled' => "L'inscription est désactivée pour le moment",
40 + 'registration_email_domain_invalid' => 'Cette adresse e-mail ne peux pas adcéder à l\'application',
41 + 'register_success' => 'Merci pour votre inscription. Vous êtes maintenant inscrit(e) et connecté(e)',
42 +
43 +
44 + /**
45 + * Password Reset
46 + */
47 + 'reset_password' => 'Reset Password',
48 + 'reset_password_send_instructions' => 'Entrez votre adresse e-mail ci-dessous et un e-mail avec un lien de réinitialisation de mot de passe vous sera envoyé',
49 + 'reset_password_send_button' => 'Envoyer un lien de réinitialisation',
50 + 'reset_password_sent_success' => 'Un lien de réinitialisation a été envoyé à :email.',
51 + 'reset_password_success' => 'Votre mot de passe a été réinitialisé avec succès.',
52 +
53 + 'email_reset_subject' => 'Réinitialisez votre mot de passe pour :appName',
54 + 'email_reset_text' => 'Vous recevez cet e-mail parceque nous avons reçu une demande de réinitialisation pour votre compte',
55 + 'email_reset_not_requested' => 'Si vous n\'avez pas effectué cette demande, vous pouvez ignorer cet e-mail.',
56 +
57 +
58 + /**
59 + * Email Confirmation
60 + */
61 + 'email_confirm_subject' => 'Confirmez votre adresse e-mail pour :appName',
62 + 'email_confirm_greeting' => 'Merci d\'avoir rejoint :appName!',
63 + 'email_confirm_text' => 'Merci de confirmer en cliquant sur le lien ci-dessous:',
64 + 'email_confirm_action' => 'Confirmez votre adresse e-mail',
65 + 'email_confirm_send_error' => 'La confirmation par e-mail est requise mais le système n\'a pas pu envoyer l\'e-mail. Contactez l\'administrateur système.',
66 + 'email_confirm_success' => 'Votre adresse e-mail a été confirmée!',
67 + 'email_confirm_resent' => 'L\'e-mail de confirmation a été ré-envoyé. Vérifiez votre boîte de récéption.',
68 +
69 + 'email_not_confirmed' => 'Adresse e-mail non confirmée',
70 + 'email_not_confirmed_text' => 'Votre adresse e-mail n\'a pas été confirmée.',
71 + 'email_not_confirmed_click_link' => 'Merci de cliquer sur le lien dans l\'e-mail qui vous a été envoyé après l\'enregistrement.',
72 + 'email_not_confirmed_resend' => 'Si vous ne retrouvez plus l\'e-mail, vous pouvez renvoyer un e-mail de confirmation en utilisant le formulaire ci-dessous.',
73 + 'email_not_confirmed_resend_button' => 'Renvoyez l\'e-mail de confirmation',
74 +];
1 +<?php
2 +return [
3 +
4 + /**
5 + * Buttons
6 + */
7 + 'cancel' => 'Annuler',
8 + 'confirm' => 'Confirmer',
9 + 'back' => 'Retour',
10 + 'save' => 'Enregistrer',
11 + 'continue' => 'Continuer',
12 + 'select' => 'Selectionner',
13 +
14 + /**
15 + * Form Labels
16 + */
17 + 'name' => 'Nom',
18 + 'description' => 'Description',
19 + 'role' => 'Rôle',
20 +
21 + /**
22 + * Actions
23 + */
24 + 'actions' => 'Actions',
25 + 'view' => 'Voir',
26 + 'create' => 'Créer',
27 + 'update' => 'Modifier',
28 + 'edit' => 'Editer',
29 + 'sort' => 'Trier',
30 + 'move' => 'Déplacer',
31 + 'delete' => 'Supprimer',
32 + 'search' => 'Chercher',
33 + 'search_clear' => 'Réinitialiser la recherche',
34 + 'reset' => 'Réinitialiser',
35 + 'remove' => 'Enlever',
36 +
37 +
38 + /**
39 + * Misc
40 + */
41 + 'deleted_user' => 'Utilisateur supprimé',
42 + 'no_activity' => 'Aucune activité',
43 + 'no_items' => 'Aucun élément',
44 + 'back_to_top' => 'Retour en haut',
45 + 'toggle_details' => 'Afficher les détails',
46 +
47 + /**
48 + * Header
49 + */
50 + 'view_profile' => 'Voir le profil',
51 + 'edit_profile' => 'Modifier le profil',
52 +
53 + /**
54 + * Email Content
55 + */
56 + 'email_action_help' => 'Si vous rencontrez des problèmes pour cliquer le bouton ":actionText", copiez et collez l\'adresse ci-dessous dans votre navigateur:',
57 + 'email_rights' => 'Tous droits réservés',
58 +];
1 +<?php
2 +return [
3 +
4 + /**
5 + * Image Manager
6 + */
7 + 'image_select' => 'Selectionner une image',
8 + 'image_all' => 'Toutes',
9 + 'image_all_title' => 'Voir toutes les images',
10 + 'image_book_title' => 'Voir les images ajoutées à ce livre',
11 + 'image_page_title' => 'Voir les images ajoutées à cette page',
12 + 'image_search_hint' => 'Rechercher par nom d\'image',
13 + 'image_uploaded' => 'Ajoutée le :uploadedDate',
14 + 'image_load_more' => 'Charger plus',
15 + 'image_image_name' => 'Nom de l\'image',
16 + 'image_delete_confirm' => 'Cette image est utilisée dans les pages ci-dessous. Confirmez que vous souhaitez bien supprimer cette image.',
17 + 'image_select_image' => 'Selectionner l\'image',
18 + 'image_dropzone' => 'Glissez les images ici ou cliquez pour les ajouter',
19 + 'images_deleted' => 'Images supprimées',
20 + 'image_preview' => 'Prévisualiser l\'image',
21 + 'image_upload_success' => 'Image ajoutée avec succès',
22 + 'image_update_success' => 'Détails de l\'image mis à jour',
23 + 'image_delete_success' => 'Image supprimée avec succès'
24 +];
1 +<?php
2 +return [
3 +
4 + /**
5 + * Shared
6 + */
7 + 'recently_created' => 'Créé récemment',
8 + 'recently_created_pages' => 'Pages créées récemment',
9 + 'recently_updated_pages' => 'Pages mises à jour récemment',
10 + 'recently_created_chapters' => 'Chapitres créés récemment',
11 + 'recently_created_books' => 'Livres créés récemment',
12 + 'recently_update' => 'Mis à jour récemment',
13 + 'recently_viewed' => 'Vus récemment',
14 + 'recent_activity' => 'Activité récente',
15 + 'create_now' => 'En créer un récemment',
16 + 'revisions' => 'Révisions',
17 + 'meta_created' => 'Créé :timeLength',
18 + 'meta_created_name' => 'Créé :timeLength par :user',
19 + 'meta_updated' => 'Mis à jour :timeLength',
20 + 'meta_updated_name' => 'Mis à jour :timeLength par :user',
21 + 'x_pages' => ':count pages',
22 + 'entity_select' => 'Sélectionner l\'entité',
23 + 'images' => 'Images',
24 + 'my_recent_drafts' => 'Mes brouillons récents',
25 + 'my_recently_viewed' => 'Vus récemment',
26 + 'no_pages_viewed' => 'Vous n\'avez rien visité récemment',
27 + 'no_pages_recently_created' => 'Aucune page créée récemment',
28 + 'no_pages_recently_updated' => 'Aucune page mise à jour récemment',
29 +
30 + /**
31 + * Permissions and restrictions
32 + */
33 + 'permissions' => 'Permissions',
34 + 'permissions_intro' => 'Une fois activées ces permission prendont la priorité sur tous les sets de permissions pré-existants.',
35 + 'permissions_enable' => 'Activer les permissions personnalisées',
36 + 'permissions_save' => 'Enregistrer les permissions',
37 +
38 + /**
39 + * Search
40 + */
41 + 'search_results' => 'Résultats de recherche',
42 + 'search_results_page' => 'Résultats de recherche des pages',
43 + 'search_results_chapter' => 'Résultats de recherche des chapitres',
44 + 'search_results_book' => 'Résultats de recherche des livres',
45 + 'search_clear' => 'Réinitialiser la recherche',
46 + 'search_view_pages' => 'Voir toutes les pages correspondantes',
47 + 'search_view_chapters' => 'Voir tous les chapitres correspondants',
48 + 'search_view_books' => 'Voir tous les livres correspondants',
49 + 'search_no_pages' => 'Aucune page correspondant à cette recherche',
50 + 'search_for_term' => 'recherche pour :term',
51 + 'search_page_for_term' => 'Recherche de page pour :term',
52 + 'search_chapter_for_term' => 'Recherche de chapitre pour :term',
53 + 'search_book_for_term' => 'Recherche de livres pour :term',
54 +
55 + /**
56 + * Books
57 + */
58 + 'book' => 'Livre',
59 + 'books' => 'Livres',
60 + 'books_empty' => 'Aucun livre n\'a été créé',
61 + 'books_popular' => 'Livres populaires',
62 + 'books_recent' => 'Livres récents',
63 + 'books_popular_empty' => 'Les livres les plus populaires apparaîtront ici.',
64 + 'books_create' => 'Créer un nouveau livre',
65 + 'books_delete' => 'Supprimer un livre',
66 + 'books_delete_named' => 'Supprimer le livre :bookName',
67 + 'books_delete_explain' => 'Ceci va supprimer le livre nommé \':bookName\', Tous les chapitres et pages seront supprimés.',
68 + 'books_delete_confirmation' => 'Êtes-vous sûr(e) de vouloir supprimer ce livre?',
69 + 'books_edit' => 'Modifier le livre',
70 + 'books_edit_named' => 'Modifier le livre :bookName',
71 + 'books_form_book_name' => 'Nom du livre',
72 + 'books_save' => 'Enregistrer le livre',
73 + 'books_permissions' => 'Permissions du livre',
74 + 'books_permissions_updated' => 'Permissions du livre mises à jour',
75 + 'books_empty_contents' => 'Aucune page ou chapitre n\'a été ajouté à ce livre.',
76 + 'books_empty_create_page' => 'Créer une nouvelle page',
77 + 'books_empty_or' => 'ou',
78 + 'books_empty_sort_current_book' => 'Trier les pages du livre',
79 + 'books_empty_add_chapter' => 'Ajouter un chapitre',
80 + 'books_permissions_active' => 'Permissions personnalisées activées',
81 + 'books_search_this' => 'Chercher dans le livre',
82 + 'books_navigation' => 'Navigation dans le livre',
83 + 'books_sort' => 'Trier les contenus du livre',
84 + 'books_sort_named' => 'Trier le livre :bookName',
85 + 'books_sort_show_other' => 'Afficher d\'autres livres',
86 + 'books_sort_save' => 'Enregistrer l\'ordre',
87 +
88 + /**
89 + * Chapters
90 + */
91 + 'chapter' => 'Chapitre',
92 + 'chapters' => 'Chapitres',
93 + 'chapters_popular' => 'Chapitres populaires',
94 + 'chapters_new' => 'Nouveau chapitre',
95 + 'chapters_create' => 'Créer un nouveau chapitre',
96 + 'chapters_delete' => 'Supprimer le chapitre',
97 + 'chapters_delete_named' => 'Supprimer le chapitre :chapterName',
98 + 'chapters_delete_explain' => 'Ceci va supprimer le chapitre \':chapterName\', Toutes les pages seront déplacée dans le livre parent.',
99 + 'chapters_delete_confirm' => 'Etes-vous sûr(e) de vouloir supprimer ce chapitre?',
100 + 'chapters_edit' => 'Modifier le chapitre',
101 + 'chapters_edit_named' => 'Modifier le chapitre :chapterName',
102 + 'chapters_save' => 'Enregistrer le chapitre',
103 + 'chapters_move' => 'Déplace le chapitre',
104 + 'chapters_move_named' => 'Déplacer le chapitre :chapterName',
105 + 'chapter_move_success' => 'Chapitre déplacé dans :bookName',
106 + 'chapters_permissions' => 'Permissions du chapitre',
107 + 'chapters_empty' => 'Il n\'y a pas de pages dans ce chapitre actuellement.',
108 + 'chapters_permissions_active' => 'Permissions du chapitre activées',
109 + 'chapters_permissions_success' => 'Permissions du chapitres mises à jour',
110 +
111 + /**
112 + * Pages
113 + */
114 + 'page' => 'Page',
115 + 'pages' => 'Pages',
116 + 'pages_popular' => 'Pages populaires',
117 + 'pages_new' => 'Nouvelle page',
118 + 'pages_attachments' => 'Fichiers joints',
119 + 'pages_navigation' => 'Navigation des pages',
120 + 'pages_delete' => 'Supprimer la page',
121 + 'pages_delete_named' => 'Supprimer la page :pageName',
122 + 'pages_delete_draft_named' => 'supprimer le brouillon de la page :pageName',
123 + 'pages_delete_draft' => 'Supprimer le brouillon',
124 + 'pages_delete_success' => 'Page supprimée',
125 + 'pages_delete_draft_success' => 'Brouillon supprimé',
126 + 'pages_delete_confirm' => 'Êtes-vous sûr(e) de vouloir supprimer cette page?',
127 + 'pages_delete_draft_confirm' => 'Êtes-vous sûr(e) de vouloir supprimer ce brouillon?',
128 + 'pages_editing_named' => 'Modification de la page :pageName',
129 + 'pages_edit_toggle_header' => 'Afficher/cacher l\'en-tête',
130 + 'pages_edit_save_draft' => 'Enregistrer le brouillon',
131 + 'pages_edit_draft' => 'Modifier le brouillon',
132 + 'pages_editing_draft' => 'Modification du brouillon',
133 + 'pages_editing_page' => 'Modification de la page',
134 + 'pages_edit_draft_save_at' => 'Brouillon sauvé le ',
135 + 'pages_edit_delete_draft' => 'Supprimer le brouillon',
136 + 'pages_edit_discard_draft' => 'Ecarter le brouillon',
137 + 'pages_edit_set_changelog' => 'Remplir le journal des changements',
138 + 'pages_edit_enter_changelog_desc' => 'Entrez une brève description des changements effectués',
139 + 'pages_edit_enter_changelog' => 'Entrez dans le journal des changements',
140 + 'pages_save' => 'Enregistrez la page',
141 + 'pages_title' => 'Titre de la page',
142 + 'pages_name' => 'Nom de la page',
143 + 'pages_md_editor' => 'Editeur',
144 + 'pages_md_preview' => 'Prévisualisation',
145 + 'pages_md_insert_image' => 'Insérer une image',
146 + 'pages_md_insert_link' => 'Insérer un lien',
147 + 'pages_not_in_chapter' => 'La page n\'est pas dans un chanpitre',
148 + 'pages_move' => 'Déplacer la page',
149 + 'pages_move_success' => 'Page déplacée à ":parentName"',
150 + 'pages_permissions' => 'Permissions de la page',
151 + 'pages_permissions_success' => 'Permissions de la page mises à jour',
152 + 'pages_revisions' => 'Révisions de la page',
153 + 'pages_revisions_named' => 'Révisions pour :pageName',
154 + 'pages_revision_named' => 'Révision pour :pageName',
155 + 'pages_revisions_created_by' => 'Créé par',
156 + 'pages_revisions_date' => 'Date de révision',
157 + 'pages_revisions_changelog' => 'Journal des changements',
158 + 'pages_revisions_changes' => 'Changements',
159 + 'pages_revisions_current' => 'Version courante',
160 + 'pages_revisions_preview' => 'Prévisualisation',
161 + 'pages_revisions_restore' => 'Restaurer',
162 + 'pages_revisions_none' => 'Cette page n\'a aucune révision',
163 + 'pages_export' => 'Exporter',
164 + 'pages_export_html' => 'Fichiers web',
165 + 'pages_export_pdf' => 'Fichier PDF',
166 + 'pages_export_text' => 'Document texte',
167 + 'pages_copy_link' => 'Copier le lien',
168 + 'pages_permissions_active' => 'Permissions de page actives',
169 + 'pages_initial_revision' => 'Publication initiale',
170 + 'pages_initial_name' => 'Nouvelle page',
171 + 'pages_editing_draft_notification' => 'Vous éditez actuellement un brouillon qui a été sauvé :timeDiff.',
172 + 'pages_draft_edited_notification' => 'La page a été mise à jour depuis votre dernière visit. Vous devriez écarter ce brouillon.',
173 + 'pages_draft_edit_active' => [
174 + 'start_a' => ':count utilisateurs ont commencé a éditer cette page',
175 + 'start_b' => ':userName a commencé à éditer cette page',
176 + 'time_a' => 'depuis la dernière sauvegarde',
177 + 'time_b' => 'dans les :minCount dernières minutes',
178 + 'message' => ':start :time. Attention a ne pas écraser les mises à jour de quelqu\'un d\'autre!',
179 + ],
180 + 'pages_draft_discarded' => 'Brouuillon écarté, la page est dans sa version actuelle.',
181 +
182 + /**
183 + * Editor sidebar
184 + */
185 + 'page_tags' => 'Mots-clés de la page',
186 + 'tag' => 'Mot-clé',
187 + 'tags' => 'Mots-clé',
188 + 'tag_value' => 'Valeur du mot-clé (Optionnel)',
189 + 'tags_explain' => "Ajouter des mot-clés pour catégoriser votre contenu.",
190 + 'tags_add' => 'Ajouter un autre mot-clé',
191 + 'attachments' => 'Fichiers joints',
192 + 'attachments_explain' => 'Ajouter des fichiers ou des liens pour les afficher sur votre page. Ils seront affichés dans la barre latérale',
193 + 'attachments_explain_instant_save' => 'Ces changements sont enregistrés immédiatement.',
194 + 'attachments_items' => 'Fichiers joints',
195 + 'attachments_upload' => 'Uploader un fichier',
196 + 'attachments_link' => 'Attacher un lien',
197 + 'attachments_set_link' => 'Définir un lien',
198 + 'attachments_delete_confirm' => 'Cliquer une seconde fois sur supprimer pour valider la suppression.',
199 + 'attachments_dropzone' => 'Glissez des fichiers ou cliquez ici pour attacher des fichiers',
200 + 'attachments_no_files' => 'Aucun fichier ajouté',
201 + 'attachments_explain_link' => 'Vous pouvez attacher un lien si vous ne souhaitez pas uploader un fichier.',
202 + 'attachments_link_name' => 'Nom du lien',
203 + 'attachment_link' => 'Lien de l\'attachement',
204 + 'attachments_link_url' => 'Lien sur un fichier',
205 + 'attachments_link_url_hint' => 'URL du site ou du fichier',
206 + 'attach' => 'Attacher',
207 + 'attachments_edit_file' => 'Modifier le fichier',
208 + 'attachments_edit_file_name' => 'Nom du fichier',
209 + 'attachments_edit_drop_upload' => 'Glissez un fichier ou cliquer pour mettre à jour le fichier',
210 + 'attachments_order_updated' => 'Ordre des fichiers joints mis à jour',
211 + 'attachments_updated_success' => 'Détails des fichiers joints mis à jour',
212 + 'attachments_deleted' => 'Fichier joint supprimé',
213 + 'attachments_file_uploaded' => 'Fichier ajouté avec succès',
214 + 'attachments_file_updated' => 'Fichier mis à jour avec succès',
215 + 'attachments_link_attached' => 'Lien attaché à la page avec succès',
216 +
217 + /**
218 + * Profile View
219 + */
220 + 'profile_user_for_x' => 'Utilisateur depuis :time',
221 + 'profile_created_content' => 'Contenu créé',
222 + 'profile_not_created_pages' => ':userName n\'a pas créé de pages',
223 + 'profile_not_created_chapters' => ':userName n\'a pas créé de chapitres',
224 + 'profile_not_created_books' => ':userName n\'a pas créé de livres',
225 +];
1 +<?php
2 +
3 +return [
4 +
5 + /**
6 + * Error text strings.
7 + */
8 +
9 + // Permissions
10 + 'permission' => 'Vous n\'avez pas les droits pour accéder à cette page.',
11 + 'permissionJson' => 'Vous n\'avez pas les droits pour exécuter cette action.',
12 +
13 + // Auth
14 + 'error_user_exists_different_creds' => 'Un utilisateur avec l\'adresse :email existe déjà.',
15 + 'email_already_confirmed' => 'Cet e-mail a déjà été validé, vous pouvez vous connecter.',
16 + 'email_confirmation_invalid' => 'Cette confirmation est invalide. Veuillez essayer de vous inscrire à nouveau.',
17 + 'email_confirmation_expired' => 'Le jeton de confirmation est perimé. Un nouvel e-mail vous a été envoyé.',
18 + 'ldap_fail_anonymous' => 'L\'accès LDAP anonyme n\'a pas abouti',
19 + 'ldap_fail_authed' => 'L\'accès LDAP n\'a pas abouti avec cet utilisateur et ce mot de passe',
20 + 'ldap_extension_not_installed' => 'L\'extention LDAP PHP n\'est pas installée',
21 + 'ldap_cannot_connect' => 'Cannot connect to ldap server, Initial connection failed',
22 + 'social_no_action_defined' => 'No action defined',
23 + 'social_account_in_use' => 'Cet compte :socialAccount est déjà utilisé. Essayez de vous connecter via :socialAccount.',
24 + 'social_account_email_in_use' => 'L\'email :email Est déjà utilisé. Si vous avez déjà un compte :socialAccount, vous pouvez le joindre à votre profil existant.',
25 + 'social_account_existing' => 'Ce compte :socialAccount est déjà rattaché à votre profil.',
26 + 'social_account_already_used_existing' => 'Ce compte :socialAccount est déjà utilisé par un autre utilisateur.',
27 + 'social_account_not_used' => 'Ce compte :socialAccount n\'est lié à aucun utilisateur. ',
28 + 'social_account_register_instructions' => 'Si vous n\'avez pas encore de compte, vous pouvez le lier avec l\'option :socialAccount.',
29 + 'social_driver_not_found' => 'Social driver not found',
30 + 'social_driver_not_configured' => 'Your :socialAccount social settings are not configured correctly.',
31 +
32 + // System
33 + 'path_not_writable' => 'File path :filePath could not be uploaded to. Ensure it is writable to the server.',
34 + 'cannot_get_image_from_url' => 'Impossible de récupérer l\'image depuis :url',
35 + 'cannot_create_thumbs' => 'Le serveur ne peux pas créer de miniatures, vérifier que l\extensions GD PHP est installée.',
36 + 'server_upload_limit' => 'La taille du fichier est trop grande.',
37 + 'image_upload_error' => 'Une erreur est survenue pendant l\'envoi de l\'image',
38 +
39 + // Attachments
40 + 'attachment_page_mismatch' => 'Page mismatch during attachment update',
41 +
42 + // Pages
43 + 'page_draft_autosave_fail' => 'Le brouillon n\'a pas pu être sauvé. Vérifiez votre connexion internet',
44 +
45 + // Entities
46 + 'entity_not_found' => 'Entité non trouvée',
47 + 'book_not_found' => 'Livre non trouvé',
48 + 'page_not_found' => 'Page non trouvée',
49 + 'chapter_not_found' => 'Chapitre non trouvé',
50 + 'selected_book_not_found' => 'Ce livre n\'a pas été trouvé',
51 + 'selected_book_chapter_not_found' => 'Ce livre ou chapitre n\'a pas été trouvé',
52 + 'guests_cannot_save_drafts' => 'Les invités ne peuvent pas sauver de brouillons',
53 +
54 + // Users
55 + 'users_cannot_delete_only_admin' => 'Vous ne pouvez pas supprimer le dernier admin',
56 + 'users_cannot_delete_guest' => 'Vous ne pouvez pas supprimer l\'utilisateur invité',
57 +
58 + // Roles
59 + 'role_cannot_be_edited' => 'Ce rôle ne peut pas être modifié',
60 + 'role_system_cannot_be_deleted' => 'Ceci est un rôle du système et on ne peut pas le supprimer',
61 + 'role_registration_default_cannot_delete' => 'Ce rôle ne peut pas être supprimé tant qu\'il est le rôle par défaut',
62 +
63 + // Error pages
64 + '404_page_not_found' => 'Page non trouvée',
65 + 'sorry_page_not_found' => 'Désolé, cette page n\'a pas pu être trouvée.',
66 + 'return_home' => 'Retour à l\'accueil',
67 + 'error_occurred' => 'Une erreur est survenue',
68 + 'app_down' => ':appName n\'est pas en service pour le moment',
69 + 'back_soon' => 'Nous serons bientôt de retour.',
70 +];
1 +<?php
2 +
3 +return [
4 +
5 + /*
6 + |--------------------------------------------------------------------------
7 + | Pagination Language Lines
8 + |--------------------------------------------------------------------------
9 + |
10 + | The following language lines are used by the paginator library to build
11 + | the simple pagination links. You are free to change them to anything
12 + | you want to customize your views to better match your application.
13 + |
14 + */
15 +
16 + 'previous' => '&laquo; Précédent',
17 + 'next' => 'Suivant &raquo;',
18 +
19 +];
1 +<?php
2 +
3 +return [
4 +
5 + /*
6 + |--------------------------------------------------------------------------
7 + | Password Reminder Language Lines
8 + |--------------------------------------------------------------------------
9 + |
10 + | The following language lines are the default lines which match reasons
11 + | that are given by the password broker for a password update attempt
12 + | has failed, such as for an invalid token or invalid new password.
13 + |
14 + */
15 +
16 + 'password' => 'Les mots de passe doivent faire au moins 6 caractères et correspondre à la confirmation.',
17 + 'user' => "Nous n'avons pas trouvé d'utilisateur avec cette adresse.",
18 + 'token' => 'Le jeton de réinitialisation est invalide.',
19 + 'sent' => 'Nous vous avons envoyé un lien de réinitialisation de mot de passe!',
20 + 'reset' => 'Votre mot de passe a été réinitialisé!',
21 +
22 +];
1 +<?php
2 +
3 +return [
4 +
5 + /**
6 + * Settings text strings
7 + * Contains all text strings used in the general settings sections of BookStack
8 + * including users and roles.
9 + */
10 +
11 + 'settings' => 'Préférences',
12 + 'settings_save' => 'Enregistrer les préférences',
13 + 'settings_save_success' => 'Préférences enregistrées',
14 +
15 + /**
16 + * App settings
17 + */
18 +
19 + 'app_settings' => 'Préférences de l\'application',
20 + 'app_name' => 'Nom de l\'application',
21 + 'app_name_desc' => 'Ce nom est affiché dans l\'en-tête et les e-mails.',
22 + 'app_name_header' => 'Afficher le nom dans l\'en-tête?',
23 + 'app_public_viewing' => 'Accepter le visionnage public des pages?',
24 + 'app_secure_images' => 'Activer l\'ajout d\'image sécurisé?',
25 + 'app_secure_images_desc' => 'Pour des questions de performances, toutes les images sont publiques. Cette option ajoute une chaîne aléatoire difficile à deviner dans les URLs des images.',
26 + 'app_editor' => 'Editeur des pages',
27 + 'app_editor_desc' => 'Sélectionnez l\'éditeur qui sera utilisé pour modifier les pages.',
28 + 'app_custom_html' => 'HTML personnalisé dans l\'en-tête',
29 + 'app_custom_html_desc' => 'Le contenu inséré ici sera jouté en bas de la balise <head> de toutes les pages. Vous pouvez l\'utiliser pour ajouter du CSS personnalisé ou un tracker analytique.',
30 + 'app_logo' => 'Logo de l\'Application',
31 + 'app_logo_desc' => 'Cette image doit faire 43px de hauteur. <br>Les images plus larges seront réduites.',
32 + 'app_primary_color' => 'Couleur principale de l\'application',
33 + 'app_primary_color_desc' => 'This should be a hex value. <br>Leave empty to reset to the default color.',
34 +
35 + /**
36 + * Registration settings
37 + */
38 +
39 + 'reg_settings' => 'Préférence pour l\'inscription',
40 + 'reg_allow' => 'Accepter l\'inscription?',
41 + 'reg_default_role' => 'Rôle par défaut lors de l\'inscription',
42 + 'reg_confirm_email' => 'Obliger la confirmation par e-mail?',
43 + 'reg_confirm_email_desc' => 'Si la restriction de domaine est activée, la confirmation sera automatiquement obligatoire et cette valeur sera ignorée.',
44 + 'reg_confirm_restrict_domain' => 'Restreindre l\'inscription à un domaine',
45 + 'reg_confirm_restrict_domain_desc' => 'Entrez une liste de domaines acceptés lors de l\'inscription, séparés par une virgule. Les utilisateur recevront un e-mail de confirmation à cette adresse. <br> Les utilisateurs pourront changer leur adresse après inscription s\'ils le souhaitent.',
46 + 'reg_confirm_restrict_domain_placeholder' => 'Aucune restriction en place',
47 +
48 + /**
49 + * Role settings
50 + */
51 +
52 + 'roles' => 'Rôles',
53 + 'role_user_roles' => 'Rôles des utilisateurs',
54 + 'role_create' => 'Créer un nouveau rôle',
55 + 'role_create_success' => 'Rôle créé avec succès',
56 + 'role_delete' => 'Supprimer le rôle',
57 + 'role_delete_confirm' => 'Ceci va supprimer le rôle \':roleName\'.',
58 + 'role_delete_users_assigned' => 'Ce rôle a :userCount utilisateurs assignés. Vous pouvez choisir un rôle de remplacement pour ces utilisateurs.',
59 + 'role_delete_no_migration' => "Ne pas assigner de nouveau rôle",
60 + 'role_delete_sure' => 'Êtes vous sûr(e) de vouloir supprimer ce rôle?',
61 + 'role_delete_success' => 'Le rôle a été supprimé avec succès',
62 + 'role_edit' => 'Modifier le rôle',
63 + 'role_details' => 'Détails du rôle',
64 + 'role_name' => 'Nom du Rôle',
65 + 'role_desc' => 'Courte description du rôle',
66 + 'role_system' => 'Permissions système',
67 + 'role_manage_users' => 'Gérer les utilisateurs',
68 + 'role_manage_roles' => 'Gérer les rôles et permissions',
69 + 'role_manage_entity_permissions' => 'Gérer les permissions sur les livres, chapitres et pages',
70 + 'role_manage_own_entity_permissions' => 'Gérer les permissions de ses propres livres chapitres et pages',
71 + 'role_manage_settings' => 'Gérer les préférences de l\'application',
72 + 'role_asset' => 'Asset Permissions',
73 + 'role_asset_desc' => 'These permissions control default access to the assets within the system. Permissions on Books, Chapters and Pages will override these permissions.',
74 + 'role_all' => 'Tous',
75 + 'role_own' => 'Propres',
76 + 'role_controlled_by_asset' => 'Controlled by the asset they are uploaded to',
77 + 'role_save' => 'Enregistrer le rôle',
78 + 'role_update_success' => 'Rôle mis à jour avec succès',
79 + 'role_users' => 'Utilisateurs ayant ce rôle',
80 + 'role_users_none' => 'Aucun utilisateur avec ce rôle actuellement',
81 +
82 + /**
83 + * Users
84 + */
85 +
86 + 'users' => 'Utilisateurs',
87 + 'user_profile' => 'Profil d\'utilisateur',
88 + 'users_add_new' => 'Ajouter un nouvel utilisateur',
89 + 'users_search' => 'Chercher les utilisateurs',
90 + 'users_role' => 'Rôles des utilisateurs',
91 + 'users_external_auth_id' => 'Identifiant d\'authentification externe',
92 + 'users_password_warning' => 'Remplissez ce fomulaire uniquement si vous souhaitez changer de mot de passe:',
93 + 'users_system_public' => 'Cet utilisateur représente les invités visitant votre instance. Il est assigné automatiquement aux invités.',
94 + 'users_delete' => 'Supprimer un utilisateur',
95 + 'users_delete_named' => 'Supprimer l\'utilisateur :userName',
96 + 'users_delete_warning' => 'Ceci va supprimer \':userName\' du système.',
97 + 'users_delete_confirm' => 'Êtes-vous sûr(e) de vouloir supprimer cet utilisateur?',
98 + 'users_delete_success' => 'Utilisateurs supprimés avec succès',
99 + 'users_edit' => 'Modifier l\'utilisateur',
100 + 'users_edit_profile' => 'Modifier le profil',
101 + 'users_edit_success' => 'Utilisateur mis à jour avec succès',
102 + 'users_avatar' => 'Avatar de l\'utilisateur',
103 + 'users_avatar_desc' => 'Cette image doit être un carré d\'environ 256px.',
104 + 'users_preferred_language' => 'Langue préférée',
105 + 'users_social_accounts' => 'Comptes sociaux',
106 + 'users_social_accounts_info' => 'Vous pouvez connecter des réseaux sociaux à votre compte pour vous connecter plus rapidement. Déconnecter un compte n\'enlèvera pas les accès autorisés précédemment sur votre compte de réseau social.',
107 + 'users_social_connect' => 'Connecter le compte',
108 + 'users_social_disconnect' => 'Déconnecter le compte',
109 + 'users_social_connected' => 'Votre compte :socialAccount a élté ajouté avec succès.',
110 + 'users_social_disconnected' => 'Votre compte :socialAccount a été déconnecté avec succès',
111 +
112 +];
1 +<?php
2 +
3 +return [
4 +
5 + /*
6 + |--------------------------------------------------------------------------
7 + | Validation Language Lines
8 + |--------------------------------------------------------------------------
9 + |
10 + | The following language lines contain the default error messages used by
11 + | the validator class. Some of these rules have multiple versions such
12 + | as the size rules. Feel free to tweak each of these messages here.
13 + |
14 + */
15 +
16 + 'accepted' => ':attribute doit être accepté.',
17 + 'active_url' => ':attribute n\'est pas une URL valide.',
18 + 'after' => ':attribute doit être supérieur à :date.',
19 + 'alpha' => ':attribute ne doit contenir que des lettres.',
20 + 'alpha_dash' => ':attribute doit contenir uniquement des lettres, chiffres et traits d\'union.',
21 + 'alpha_num' => ':attribute doit contenir uniquement des chiffres et des lettres.',
22 + 'array' => ':attribute doit être un tableau.',
23 + 'before' => ':attribute doit être inférieur à :date.',
24 + 'between' => [
25 + 'numeric' => ':attribute doit être compris entre :min et :max.',
26 + 'file' => ':attribute doit être compris entre :min et :max kilobytes.',
27 + 'string' => ':attribute doit être compris entre :min et :max caractères.',
28 + 'array' => ':attribute doit être compris entre :min et :max éléments.',
29 + ],
30 + 'boolean' => ':attribute doit être vrai ou faux.',
31 + 'confirmed' => ':attribute la confirmation n\'est pas valide.',
32 + 'date' => ':attribute n\'est pas une date valide.',
33 + 'date_format' => ':attribute ne correspond pas au format :format.',
34 + 'different' => ':attribute et :other doivent être différents l\'un de l\'autre.',
35 + 'digits' => ':attribute doit être de longueur :digits.',
36 + 'digits_between' => ':attribute doit avoir une longueur entre :min et :max.',
37 + 'email' => ':attribute doit être une adresse e-mail valide.',
38 + 'filled' => ':attribute est un champ requis.',
39 + 'exists' => 'L\'attribut :attribute est invalide.',
40 + 'image' => ':attribute doit être une image.',
41 + 'in' => 'L\'attribut :attribute est invalide.',
42 + 'integer' => ':attribute doit être un chiffre entier.',
43 + 'ip' => ':attribute doit être une adresse IP valide.',
44 + 'max' => [
45 + 'numeric' => ':attribute ne doit pas excéder :max.',
46 + 'file' => ':attribute ne doit pas excéder :max kilobytes.',
47 + 'string' => ':attribute ne doit pas excéder :max caractères.',
48 + 'array' => ':attribute ne doit pas contenir plus de :max éléments.',
49 + ],
50 + 'mimes' => ':attribute doit être un fichier de type :values.',
51 + 'min' => [
52 + 'numeric' => ':attribute doit être au moins :min.',
53 + 'file' => ':attribute doit faire au moins :min kilobytes.',
54 + 'string' => ':attribute doit contenir au moins :min caractères.',
55 + 'array' => ':attribute doit contenir au moins :min éléments.',
56 + ],
57 + 'not_in' => 'L\'attribut sélectionné :attribute est invalide.',
58 + 'numeric' => ':attribute doit être un nombre.',
59 + 'regex' => ':attribute a un format invalide.',
60 + 'required' => ':attribute est un champ requis.',
61 + 'required_if' => ':attribute est requis si :other est :value.',
62 + 'required_with' => ':attribute est requis si :values est présent.',
63 + 'required_with_all' => ':attribute est requis si :values est présent.',
64 + 'required_without' => ':attribute est requis si:values n\'est pas présent.',
65 + 'required_without_all' => ':attribute est requis si aucun des valeurs :values n\'est présente.',
66 + 'same' => ':attribute et :other doivent être identiques.',
67 + 'size' => [
68 + 'numeric' => ':attribute doit avoir la taille :size.',
69 + 'file' => ':attribute doit peser :size kilobytes.',
70 + 'string' => ':attribute doit contenir :size caractères.',
71 + 'array' => ':attribute doit contenir :size éléments.',
72 + ],
73 + 'string' => ':attribute doit être une chaîne de caractères.',
74 + 'timezone' => ':attribute doit être une zone valide.',
75 + 'unique' => ':attribute est déjà utilisé.',
76 + 'url' => ':attribute a un format invalide.',
77 +
78 + /*
79 + |--------------------------------------------------------------------------
80 + | Custom Validation Language Lines
81 + |--------------------------------------------------------------------------
82 + |
83 + | Here you may specify custom validation messages for attributes using the
84 + | convention "attribute.rule" to name the lines. This makes it quick to
85 + | specify a specific custom language line for a given attribute rule.
86 + |
87 + */
88 +
89 + 'custom' => [
90 + 'password-confirm' => [
91 + 'required_with' => 'La confirmation du mot de passe est requise',
92 + ],
93 + ],
94 +
95 + /*
96 + |--------------------------------------------------------------------------
97 + | Custom Validation Attributes
98 + |--------------------------------------------------------------------------
99 + |
100 + | The following language lines are used to swap attribute place-holders
101 + | with something more reader friendly such as E-Mail Address instead
102 + | of "email". This simply helps us make messages a little cleaner.
103 + |
104 + */
105 +
106 + 'attributes' => [],
107 +
108 +];
1 +<?php
2 +
3 +return [
4 +
5 + /**
6 + * Activity text strings.
7 + * Is used for all the text within activity logs & notifications.
8 + */
9 +
10 + // Pages
11 + 'page_create' => 'página criada',
12 + 'page_create_notification' => 'Página criada com sucesso',
13 + 'page_update' => 'página atualizada',
14 + 'page_update_notification' => 'Página atualizada com sucesso',
15 + 'page_delete' => 'página excluída',
16 + 'page_delete_notification' => 'Página excluída com sucesso',
17 + 'page_restore' => 'página restaurada',
18 + 'page_restore_notification' => 'Página restaurada com sucesso',
19 + 'page_move' => 'página movida',
20 +
21 + // Chapters
22 + 'chapter_create' => 'capítulo criado',
23 + 'chapter_create_notification' => 'Capítulo criado com sucesso',
24 + 'chapter_update' => 'capítulo atualizado',
25 + 'chapter_update_notification' => 'capítulo atualizado com sucesso',
26 + 'chapter_delete' => 'capítulo excluído',
27 + 'chapter_delete_notification' => 'Capítulo excluído com sucesso',
28 + 'chapter_move' => 'capitulo movido',
29 +
30 + // Books
31 + 'book_create' => 'livro criado',
32 + 'book_create_notification' => 'Livro criado com sucesso',
33 + 'book_update' => 'livro atualizado',
34 + 'book_update_notification' => 'Livro atualizado com sucesso',
35 + 'book_delete' => 'livro excluído',
36 + 'book_delete_notification' => 'Livro excluído com sucesso',
37 + 'book_sort' => 'livro classificado',
38 + 'book_sort_notification' => 'Livro reclassificado com sucesso',
39 +
40 +];
1 +<?php
2 +return [
3 + /*
4 + |--------------------------------------------------------------------------
5 + | Authentication Language Lines
6 + |--------------------------------------------------------------------------
7 + |
8 + | The following language lines are used during authentication for various
9 + | messages that we need to display to the user. You are free to modify
10 + | these language lines according to your application's requirements.
11 + |
12 + */
13 + 'failed' => 'As credenciais fornecidas não puderam ser validadas em nossos registros..',
14 + 'throttle' => 'Muitas tentativas de login. Por favor, tente novamente em :seconds segundos.',
15 +
16 + /**
17 + * Login & Register
18 + */
19 + 'sign_up' => 'Registrar-se',
20 + 'log_in' => 'Entrar',
21 + 'logout' => 'Sair',
22 +
23 + 'name' => 'Nome',
24 + 'username' => 'Nome de Usuário',
25 + 'email' => 'E-mail',
26 + 'password' => 'Senha',
27 + 'password_confirm' => 'Confirmar Senha',
28 + 'password_hint' => 'Senha deverá ser maior que 5 caracteres',
29 + 'forgot_password' => 'Esqueceu a senha?',
30 + 'remember_me' => 'Lembrar de mim',
31 + 'ldap_email_hint' => 'Por favor, digite um e-mail para essa conta.',
32 + 'create_account' => 'Criar conta',
33 + 'social_login' => 'Login social',
34 + 'social_registration' => 'Registro social',
35 + 'social_registration_text' => 'Registre e entre usando outro serviço.',
36 +
37 + 'register_thanks' => 'Obrigado por efetuar o registro!',
38 + 'register_confirm' => 'Por favor, verifique seu e-mail e clique no botão de confirmação para acessar :appName.',
39 + 'registrations_disabled' => 'Registros estão temporariamente desabilitados',
40 + 'registration_email_domain_invalid' => 'O domínio de e-mail usado não tem acesso permitido a essa aplicação',
41 + 'register_success' => 'Obrigado por se registrar! Você agora encontra-se registrado e logado..',
42 +
43 +
44 + /**
45 + * Password Reset
46 + */
47 + 'reset_password' => 'Resetar senha',
48 + 'reset_password_send_instructions' => 'Digite seu e-mail abaixo e o sistema enviará uma mensagem com o link de reset de senha.',
49 + 'reset_password_send_button' => 'Enviar o link de reset de senha',
50 + 'reset_password_sent_success' => 'Um link de reset de senha foi enviado para :email.',
51 + 'reset_password_success' => 'Sua senha foi resetada com sucesso.',
52 +
53 + 'email_reset_subject' => 'Resetar a senha de :appName',
54 + 'email_reset_text' => 'Você recebeu esse e-mail pois recebemos uma solicitação de reset de senha para sua conta.',
55 + 'email_reset_not_requested' => 'Caso não tenha sido você a solicitar o reset de senha, ignore esse e-mail.',
56 +
57 +
58 + /**
59 + * Email Confirmation
60 + */
61 + 'email_confirm_subject' => 'Confirme seu e-mail para :appName',
62 + 'email_confirm_greeting' => 'Obrigado por se registrar em :appName!',
63 + 'email_confirm_text' => 'Por favor, confirme seu endereço de e-mail clicando no botão abaixo:',
64 + 'email_confirm_action' => 'Confirmar E-mail',
65 + 'email_confirm_send_error' => 'E-mail de confirmação é requerido, mas o sistema não pôde enviar a mensagem. Por favor, entre em contato com o admin para se certificar que o serviço de envio de e-mails está corretamente configurado.',
66 + 'email_confirm_success' => 'Seu e-mail foi confirmado!',
67 + 'email_confirm_resent' => 'E-mail de confirmação reenviado. Por favor, cheque sua caixa postal.',
68 +
69 + 'email_not_confirmed' => 'Endereço de e-mail não foi confirmado',
70 + 'email_not_confirmed_text' => 'Seu endereço de e-mail ainda não foi confirmado.',
71 + 'email_not_confirmed_click_link' => 'Por favor, clique no link no e-mail que foi enviado após o registro.',
72 + 'email_not_confirmed_resend' => 'Caso não encontre o e-mail você poderá reenviar a confirmação usando o formulário abaixo.',
73 + 'email_not_confirmed_resend_button' => 'Reenviar o e-mail de confirmação',
74 +];
...\ No newline at end of file ...\ No newline at end of file
1 +<?php
2 +return [
3 +
4 + /**
5 + * Buttons
6 + */
7 + 'cancel' => 'Cancelar',
8 + 'confirm' => 'Confirmar',
9 + 'back' => 'Voltar',
10 + 'save' => 'Salvar',
11 + 'continue' => 'Continuar',
12 + 'select' => 'Selecionar',
13 +
14 + /**
15 + * Form Labels
16 + */
17 + 'name' => 'Nome',
18 + 'description' => 'Descrição',
19 + 'role' => 'Regra',
20 +
21 + /**
22 + * Actions
23 + */
24 + 'actions' => 'Ações',
25 + 'view' => 'Visualizar',
26 + 'create' => 'Criar',
27 + 'update' => 'Atualizar',
28 + 'edit' => 'Editar',
29 + 'sort' => 'Ordenar',
30 + 'move' => 'Mover',
31 + 'delete' => 'Excluir',
32 + 'search' => 'Pesquisar',
33 + 'search_clear' => 'Limpar Pesquisa',
34 + 'reset' => 'Resetar',
35 + 'remove' => 'Remover',
36 +
37 +
38 + /**
39 + * Misc
40 + */
41 + 'deleted_user' => 'Usuário excluído',
42 + 'no_activity' => 'Nenhuma atividade a mostrar',
43 + 'no_items' => 'Nenhum item disponível',
44 + 'back_to_top' => 'Voltar ao topo',
45 + 'toggle_details' => 'Alternar Detalhes',
46 +
47 + /**
48 + * Header
49 + */
50 + 'view_profile' => 'Visualizar Perfil',
51 + 'edit_profile' => 'Editar Perfil',
52 +
53 + /**
54 + * Email Content
55 + */
56 + 'email_action_help' => 'Se você estiver tendo problemas ao clicar o botão ":actionText", copie e cole a URL abaixo no seu navegador:',
57 + 'email_rights' => 'Todos os direitos reservados',
58 +];
...\ No newline at end of file ...\ No newline at end of file
1 +<?php
2 +return [
3 +
4 + /**
5 + * Image Manager
6 + */
7 + 'image_select' => 'Selecionar imagem',
8 + 'image_all' => 'Todos',
9 + 'image_all_title' => 'Visualizar todas as imagens',
10 + 'image_book_title' => 'Visualizar imagens relacionadas a esse livro',
11 + 'image_page_title' => 'visualizar imagens relacionadas a essa página',
12 + 'image_search_hint' => 'Pesquisar imagem por nome',
13 + 'image_uploaded' => 'Carregado :uploadedDate',
14 + 'image_load_more' => 'Carregar Mais',
15 + 'image_image_name' => 'Nome da Imagem',
16 + 'image_delete_confirm' => 'Essa imagem é usada nas páginas abaixo. Clique em Excluir novamente para confirmar que você deseja mesmo eliminar a imagem.',
17 + 'image_select_image' => 'Selecionar Imagem',
18 + 'image_dropzone' => 'Arraste imagens ou clique aqui para fazer upload',
19 + 'images_deleted' => 'Imagens excluídas',
20 + 'image_preview' => 'Virtualização de Imagem',
21 + 'image_upload_success' => 'Upload de imagem efetuado com sucesso',
22 + 'image_update_success' => 'Upload de detalhes da imagem efetuado com sucesso',
23 + 'image_delete_success' => 'Imagem excluída com sucesso'
24 +];
...\ No newline at end of file ...\ No newline at end of file
1 +<?php
2 +return [
3 +
4 + /**
5 + * Shared
6 + */
7 + 'recently_created' => 'Recentemente criado',
8 + 'recently_created_pages' => 'Páginas recentemente criadas',
9 + 'recently_updated_pages' => 'Páginas recentemente atualizadas',
10 + 'recently_created_chapters' => 'Capítulos recentemente criados',
11 + 'recently_created_books' => 'Livros recentemente criados',
12 + 'recently_update' => 'Recentemente atualizado',
13 + 'recently_viewed' => 'Recentemente visualizado',
14 + 'recent_activity' => 'Atividade recente',
15 + 'create_now' => 'Criar um agora',
16 + 'revisions' => 'Revisões',
17 + 'meta_created' => 'Criado em :timeLength',
18 + 'meta_created_name' => 'Criado em :timeLength por :user',
19 + 'meta_updated' => 'Atualizado em :timeLength',
20 + 'meta_updated_name' => 'Atualizado em :timeLength por :user',
21 + 'x_pages' => ':count Páginas',
22 + 'entity_select' => 'Seleção de Entidade',
23 + 'images' => 'Imagens',
24 + 'my_recent_drafts' => 'Meus rascunhos recentes',
25 + 'my_recently_viewed' => 'Meus itens recentemente visto',
26 + 'no_pages_viewed' => 'Você não visualizou nenhuma página',
27 + 'no_pages_recently_created' => 'Nenhuma página recentemente criada',
28 + 'no_pages_recently_updated' => 'Nenhuma página recentemente atualizada',
29 +
30 + /**
31 + * Permissions and restrictions
32 + */
33 + 'permissions' => 'Permissões',
34 + 'permissions_intro' => 'Uma vez habilitado, as permissões terão prioridade sobre outro conjunto de permissões.',
35 + 'permissions_enable' => 'Habilitar Permissões Customizadas',
36 + 'permissions_save' => 'Salvar Permissões',
37 +
38 + /**
39 + * Search
40 + */
41 + 'search_results' => 'Resultado(s) da Pesquisa',
42 + 'search_results_page' => 'Resultado(s) de Pesquisa de Página',
43 + 'search_results_chapter' => 'Resultado(s) de Pesquisa de Capítulo',
44 + 'search_results_book' => 'Resultado(s) de Pesquisa de Livro',
45 + 'search_clear' => 'Limpar Pesquisa',
46 + 'search_view_pages' => 'Visualizar todas as páginas correspondentes',
47 + 'search_view_chapters' => 'Visualizar todos os capítulos correspondentes',
48 + 'search_view_books' => 'Visualizar todos os livros correspondentes',
49 + 'search_no_pages' => 'Nenhuma página corresponde à pesquisa',
50 + 'search_for_term' => 'Pesquisar por :term',
51 + 'search_page_for_term' => 'Pesquisar Página por :term',
52 + 'search_chapter_for_term' => 'Pesquisar Capítulo por :term',
53 + 'search_book_for_term' => 'Pesquisar Livros por :term',
54 +
55 + /**
56 + * Books
57 + */
58 + 'book' => 'Livro',
59 + 'books' => 'Livros',
60 + 'books_empty' => 'Nenhum livro foi criado',
61 + 'books_popular' => 'Livros populares',
62 + 'books_recent' => 'Livros recentes',
63 + 'books_popular_empty' => 'Os livros mais populares aparecerão aqui.',
64 + 'books_create' => 'Criar novo Livro',
65 + 'books_delete' => 'Excluir Livro',
66 + 'books_delete_named' => 'Excluir Livro :bookName',
67 + 'books_delete_explain' => 'A ação vai excluír o livro com o nome \':bookName\'. Todas as páginas e capítulos serão removidos.',
68 + 'books_delete_confirmation' => 'Você tem certeza que quer excluír o Livro?',
69 + 'books_edit' => 'Editar Livro',
70 + 'books_edit_named' => 'Editar Livro :bookName',
71 + 'books_form_book_name' => 'Nome do Livro',
72 + 'books_save' => 'Salvar Livro',
73 + 'books_permissions' => 'Permissões do Livro',
74 + 'books_permissions_updated' => 'Permissões do Livro Atualizadas',
75 + 'books_empty_contents' => 'Nenhuma página ou capítulo criado para esse livro.',
76 + 'books_empty_create_page' => 'Criar uma nova página',
77 + 'books_empty_or' => 'ou',
78 + 'books_empty_sort_current_book' => 'Ordenar o livro atual',
79 + 'books_empty_add_chapter' => 'Adicionar um capítulo',
80 + 'books_permissions_active' => 'Permissões do Livro ativadas',
81 + 'books_search_this' => 'Pesquisar esse livro',
82 + 'books_navigation' => 'Navegação do Livro',
83 + 'books_sort' => 'Ordenar conteúdos do Livro',
84 + 'books_sort_named' => 'Ordenar Livro :bookName',
85 + 'books_sort_show_other' => 'Mostrar outros livros',
86 + 'books_sort_save' => 'Salvar nova ordenação',
87 +
88 + /**
89 + * Chapters
90 + */
91 + 'chapter' => 'Capitulo',
92 + 'chapters' => 'Capítulos',
93 + 'chapters_popular' => 'Capítulos Populares',
94 + 'chapters_new' => 'Novo Capítulo',
95 + 'chapters_create' => 'Criar novo Capítulo',
96 + 'chapters_delete' => 'Excluír Capítulo',
97 + 'chapters_delete_named' => 'Excluir Capítulo :chapterName',
98 + 'chapters_delete_explain' => 'A ação vai excluír o capítulo de nome \':chapterName\'. Todas as páginas do capítulo serão removidas
99 + e adicionadas diretamente ao livro pai.',
100 + 'chapters_delete_confirm' => 'Tem certeza que deseja excluír o capitulo?',
101 + 'chapters_edit' => 'Editar Capítulo',
102 + 'chapters_edit_named' => 'Editar capitulo :chapterName',
103 + 'chapters_save' => 'Salvar Capítulo',
104 + 'chapters_move' => 'Mover Capítulo',
105 + 'chapters_move_named' => 'Mover Capítulo :chapterName',
106 + 'chapter_move_success' => 'Capítulo movido para :bookName',
107 + 'chapters_permissions' => 'Permissões do Capítulo',
108 + 'chapters_empty' => 'Nenhuma página existente nesse capítulo.',
109 + 'chapters_permissions_active' => 'Permissões de Capítulo ativadas',
110 + 'chapters_permissions_success' => 'Permissões de Capítulo atualizadas',
111 +
112 + /**
113 + * Pages
114 + */
115 + 'page' => 'Página',
116 + 'pages' => 'Páginas',
117 + 'pages_popular' => 'Páginas Popular',
118 + 'pages_new' => 'Nova Página',
119 + 'pages_attachments' => 'Anexos',
120 + 'pages_navigation' => 'Página de Navegação',
121 + 'pages_delete' => 'Excluír Página',
122 + 'pages_delete_named' => 'Excluír Página :pageName',
123 + 'pages_delete_draft_named' => 'Excluir rascunho de Página de nome :pageName',
124 + 'pages_delete_draft' => 'Excluir rascunho de Página',
125 + 'pages_delete_success' => 'Página excluída',
126 + 'pages_delete_draft_success' => 'Página de rascunho excluída',
127 + 'pages_delete_confirm' => 'Tem certeza que deseja excluir a página?',
128 + 'pages_delete_draft_confirm' => 'Tem certeza que deseja excluir o rascunho de página?',
129 + 'pages_editing_named' => 'Editando a Página :pageName',
130 + 'pages_edit_toggle_header' => 'Alternar cabeçalho',
131 + 'pages_edit_save_draft' => 'Salvar Rascunho',
132 + 'pages_edit_draft' => 'Editar rascunho de Página',
133 + 'pages_editing_draft' => 'Editando Rascunho',
134 + 'pages_editing_page' => 'Editando Página',
135 + 'pages_edit_draft_save_at' => 'Rascunho salvo em ',
136 + 'pages_edit_delete_draft' => 'Excluir rascunho',
137 + 'pages_edit_discard_draft' => 'Descartar rascunho',
138 + 'pages_edit_set_changelog' => 'Definir Changelog',
139 + 'pages_edit_enter_changelog_desc' => 'Digite uma breve descrição das mudanças efetuadas por você',
140 + 'pages_edit_enter_changelog' => 'Entrar no Changelog',
141 + 'pages_save' => 'Salvar Página',
142 + 'pages_title' => 'Título de Página',
143 + 'pages_name' => 'Nome da Página',
144 + 'pages_md_editor' => 'Editor',
145 + 'pages_md_preview' => 'Preview',
146 + 'pages_md_insert_image' => 'Inserir Imagem',
147 + 'pages_md_insert_link' => 'Inserir Link para Entidade',
148 + 'pages_not_in_chapter' => 'Página não está dentro de um Capítulo',
149 + 'pages_move' => 'Mover Página',
150 + 'pages_move_success' => 'Pagina movida para ":parentName"',
151 + 'pages_permissions' => 'Permissões de Página',
152 + 'pages_permissions_success' => 'Permissões de Página atualizadas',
153 + 'pages_revisions' => 'Revisões de Página',
154 + 'pages_revisions_named' => 'Revisões de Página para :pageName',
155 + 'pages_revision_named' => 'Revisão de Página para :pageName',
156 + 'pages_revisions_created_by' => 'Criado por',
157 + 'pages_revisions_date' => 'Data da Revisão',
158 + 'pages_revisions_changelog' => 'Changelog',
159 + 'pages_revisions_changes' => 'Mudanças',
160 + 'pages_revisions_current' => 'Versão atual',
161 + 'pages_revisions_preview' => 'Preview',
162 + 'pages_revisions_restore' => 'Restaurar',
163 + 'pages_revisions_none' => 'Essa página não tem revisões',
164 + 'pages_export' => 'Exportar',
165 + 'pages_export_html' => 'Arquivo Web Contained',
166 + 'pages_export_pdf' => 'Arquivo PDF',
167 + 'pages_export_text' => 'Arquivo Texto',
168 + 'pages_copy_link' => 'Copia Link',
169 + 'pages_permissions_active' => 'Permissões de Página Ativas',
170 + 'pages_initial_revision' => 'Publicação Inicial',
171 + 'pages_initial_name' => 'Nova Página',
172 + 'pages_editing_draft_notification' => 'Você está atualmente editando um rascunho que foi salvo da última vez em :timeDiff.',
173 + 'pages_draft_edited_notification' => 'Essa página foi atualizada desde então. É recomendado que você descarte esse rascunho.',
174 + 'pages_draft_edit_active' => [
175 + 'start_a' => ':count usuários que iniciaram edição dessa página',
176 + 'start_b' => ':userName iniciou a edição dessa página',
177 + 'time_a' => 'desde que a página foi atualizada pela última vez',
178 + 'time_b' => 'nos últimos :minCount minutos',
179 + 'message' => ':start :time. Tome cuidado para não sobrescrever atualizações de outras pessoas!',
180 + ],
181 + 'pages_draft_discarded' => 'Rascunho descartado. O editor foi atualizado com a página atualizada',
182 +
183 + /**
184 + * Editor sidebar
185 + */
186 + 'page_tags' => 'Tags de Página',
187 + 'tag' => 'Tag',
188 + 'tags' => '',
189 + 'tag_value' => 'Valor da Tag (Opcional)',
190 + 'tags_explain' => "Adicione algumas tags para melhor categorizar seu conteúdo. \n Você pode atrelar um valor para uma tag para uma organização mais consistente.",
191 + 'tags_add' => 'Adicionar outra tag',
192 + 'attachments' => 'Anexos',
193 + 'attachments_explain' => 'Faça o Upload de alguns arquivos ou anexo algum link para ser mostrado na sua página. Eles estarão visíveis na barra lateral à direita da página.',
194 + 'attachments_explain_instant_save' => 'Mudanças são salvas instantaneamente.',
195 + 'attachments_items' => 'Itens Anexados',
196 + 'attachments_upload' => 'Upload de arquivos',
197 + 'attachments_link' => 'Links Anexados',
198 + 'attachments_set_link' => 'Definir Link',
199 + 'attachments_delete_confirm' => 'Clique novamente em Excluir para confirmar a exclusão desse anexo.',
200 + 'attachments_dropzone' => 'Arraste arquivos para cá ou clique para anexar arquivos',
201 + 'attachments_no_files' => 'Nenhum arquivo foi enviado',
202 + 'attachments_explain_link' => 'Você pode anexar um link se preferir não fazer o upload do arquivo. O link poderá ser para uma outra página ou link para um arquivo na nuvem.',
203 + 'attachments_link_name' => 'Nome do Link',
204 + 'attachment_link' => 'Link para o Anexo',
205 + 'attachments_link_url' => 'Link para o Arquivo',
206 + 'attachments_link_url_hint' => 'URL do site ou arquivo',
207 + 'attach' => 'Anexar',
208 + 'attachments_edit_file' => 'Editar Arquivo',
209 + 'attachments_edit_file_name' => 'Nome do Arquivo',
210 + 'attachments_edit_drop_upload' => 'Arraste arquivos para cá ou clique para anexar arquivos e sobrescreve-lo',
211 + 'attachments_order_updated' => 'Ordem dos anexos atualizada',
212 + 'attachments_updated_success' => 'Detalhes dos anexos atualizados',
213 + 'attachments_deleted' => 'Anexo excluído',
214 + 'attachments_file_uploaded' => 'Upload de arquivo efetuado com sucesso',
215 + 'attachments_file_updated' => 'Arquivo atualizado com sucesso',
216 + 'attachments_link_attached' => 'Link anexado com sucesso à página',
217 +
218 + /**
219 + * Profile View
220 + */
221 + 'profile_user_for_x' => 'Usuário por :time',
222 + 'profile_created_content' => 'Conteúdo Criado',
223 + 'profile_not_created_pages' => ':userName não criou páginas',
224 + 'profile_not_created_chapters' => ':userName não criou capítulos',
225 + 'profile_not_created_books' => ':userName não criou livros',
226 +];
...\ No newline at end of file ...\ No newline at end of file
1 +<?php
2 +
3 +return [
4 +
5 + /**
6 + * Error text strings.
7 + */
8 +
9 + // Permissions
10 + 'permission' => 'Você não tem permissões para acessar a página requerida.',
11 + 'permissionJson' => 'Você não tem permissão para realizar a ação requerida.',
12 +
13 + // Auth
14 + 'error_user_exists_different_creds' => 'Um usuário com o e-mail :email já existe mas com credenciais diferentes.',
15 + 'email_already_confirmed' => 'E-mail já foi confirmado. Tente efetuar o login.',
16 + 'email_confirmation_invalid' => 'Esse token de confirmação não é válido ou já foi utilizado. Por favor, tente efetuar o registro novamente.',
17 + 'email_confirmation_expired' => 'O token de confirmação já expirou. Um novo e-mail foi enviado.',
18 + 'ldap_fail_anonymous' => 'O acesso LDAP falhou ao tentar usar o anonymous bind',
19 + 'ldap_fail_authed' => 'O acesso LDAPfalou ao tentar os detalhes do dn e senha fornecidos',
20 + 'ldap_extension_not_installed' => 'As extensões LDAP PHP não estão instaladas',
21 + 'ldap_cannot_connect' => 'Não foi possível conectar ao servidor LDAP. Conexão inicial falhou',
22 + 'social_no_action_defined' => 'Nenhuma ação definida',
23 + 'social_account_in_use' => 'Essa conta :socialAccount já está em uso. Por favor, tente se logar usando a opção :socialAccount',
24 + 'social_account_email_in_use' => 'O e-mail :email já está e muso. Se você já tem uma conta você poderá se conectar a conta :socialAccount a partir das configurações de seu perfil.',
25 + 'social_account_existing' => 'Essa conta :socialAccount já está atrelada a esse perfil.',
26 + 'social_account_already_used_existing' => 'Essa conta :socialAccount já está sendo usada por outro usuário.',
27 + 'social_account_not_used' => 'Essa conta :socialAccount não está atrelada a nenhum usuário. Por favor, faça o link da conta com suas configurações de perfil. ',
28 + 'social_account_register_instructions' => 'Se você não tem uma conta, você poderá fazer o registro usando a opção :socialAccount',
29 + 'social_driver_not_found' => 'Social driver não encontrado',
30 + 'social_driver_not_configured' => 'Seus parâmetros socials de :socialAccount não estão configurados corretamente.',
31 +
32 + // System
33 + 'path_not_writable' => 'O caminho de destino (:filePath) de upload de arquivo não possui permissão de escrita. Certifique-se que ele possui direitos de escrita no servidor.',
34 + 'cannot_get_image_from_url' => 'Não foi possivel capturar a imagem a partir de :url',
35 + 'cannot_create_thumbs' => 'O servidor não pôde criar as miniaturas de imagem. Por favor, verifique se a extensão GD PHP está instalada.',
36 + 'server_upload_limit' => 'O servidor não permite o upload de arquivos com esse tamanho. Por favor, tente fazer o upload de arquivos de menor tamanho.',
37 + 'image_upload_error' => 'Um erro aconteceu enquanto o servidor tentava efetuar o upload da imagem',
38 +
39 + // Attachments
40 + 'attachment_page_mismatch' => 'Erro de \'Page mismatch\' durante a atualização do anexo',
41 +
42 + // Pages
43 + 'page_draft_autosave_fail' => 'Falou ao tentar salvar o rascunho. Certifique-se que a conexão de internet está funcional antes de tentar salvar essa página',
44 +
45 + // Entities
46 + 'entity_not_found' => 'Entidade não encontrada',
47 + 'book_not_found' => 'Livro não encontrado',
48 + 'page_not_found' => 'Página não encontrada',
49 + 'chapter_not_found' => 'Capítulo não encontrado',
50 + 'selected_book_not_found' => 'O livro selecionado não foi encontrado',
51 + 'selected_book_chapter_not_found' => 'O Livro selecionado ou Capítulo não foi encontrado',
52 + 'guests_cannot_save_drafts' => 'Convidados não podem salvar rascunhos',
53 +
54 + // Users
55 + 'users_cannot_delete_only_admin' => 'Você não pode excluir o conteúdo, apenas o admin.',
56 + 'users_cannot_delete_guest' => 'Você não pode excluir o usuário convidado',
57 +
58 + // Roles
59 + 'role_cannot_be_edited' => 'Esse perfil não poed ser editado',
60 + 'role_system_cannot_be_deleted' => 'Esse perfil é um perfil de sistema e não pode ser excluído',
61 + 'role_registration_default_cannot_delete' => 'Esse perfil não poderá se excluído enquando estiver registrado como o perfil padrão',
62 +
63 + // Error pages
64 + '404_page_not_found' => 'Página não encontrada',
65 + 'sorry_page_not_found' => 'Desculpe, a página que você está procurando não pôde ser encontrada.',
66 + 'return_home' => 'Retornar à página principal',
67 + 'error_occurred' => 'Um erro ocorreu',
68 + 'app_down' => ':appName está fora do ar no momento',
69 + 'back_soon' => 'Voltaremos em seguida.',
70 +];
...\ No newline at end of file ...\ No newline at end of file
1 +<?php
2 +
3 +return [
4 +
5 + /*
6 + |--------------------------------------------------------------------------
7 + | Pagination Language Lines
8 + |--------------------------------------------------------------------------
9 + |
10 + | The following language lines are used by the paginator library to build
11 + | the simple pagination links. You are free to change them to anything
12 + | you want to customize your views to better match your application.
13 + |
14 + */
15 +
16 + 'previous' => '&laquo; Anterior',
17 + 'next' => 'Próximo &raquo;',
18 +
19 +];
1 +<?php
2 +
3 +return [
4 +
5 + /*
6 + |--------------------------------------------------------------------------
7 + | Password Reminder Language Lines
8 + |--------------------------------------------------------------------------
9 + |
10 + | The following language lines are the default lines which match reasons
11 + | that are given by the password broker for a password update attempt
12 + | has failed, such as for an invalid token or invalid new password.
13 + |
14 + */
15 +
16 + 'password' => 'Senhas devem ter ao menos 6 caraceres e combinar com os atributos mínimos para a senha.',
17 + 'user' => "Não pudemos encontrar um usuário com o e-mail fornecido.",
18 + 'token' => 'O token de reset de senha é inválido.',
19 + 'sent' => 'Enviamos para seu e-mail o link de reset de senha!',
20 + 'reset' => 'Sua senha foi resetada com sucesso!',
21 +
22 +];
1 +<?php
2 +
3 +return [
4 +
5 + /**
6 + * Settings text strings
7 + * Contains all text strings used in the general settings sections of BookStack
8 + * including users and roles.
9 + */
10 +
11 + 'settings' => 'Configurações',
12 + 'settings_save' => 'Salvar Configurações',
13 + 'settings_save_success' => 'Configurações Salvas',
14 +
15 + /**
16 + * App settings
17 + */
18 +
19 + 'app_settings' => 'Configurações do App',
20 + 'app_name' => 'Nome da Aplicação',
21 + 'app_name_desc' => 'Esse nome será mostrado no cabeçalho e em e-mails.',
22 + 'app_name_header' => 'Mostrar o nome da Aplicação no cabeçalho?',
23 + 'app_public_viewing' => 'Permitir visualização pública?',
24 + 'app_secure_images' => 'Permitir upload de imagens com maior segurança?',
25 + 'app_secure_images_desc' => 'Por questões de performance, todas as imagens são públicas. Essa opção adiciona uma string randômica na frente da imagem. Certifique-se de que os índices do diretórios permitem o acesso fácil.',
26 + 'app_editor' => 'Editor de Página',
27 + 'app_editor_desc' => 'Selecione qual editor a ser usado pelos usuários para editar páginas.',
28 + 'app_custom_html' => 'Conteúdo para tag HTML HEAD customizado',
29 + 'app_custom_html_desc' => 'Quaisquer conteúdos aqui inseridos serão inseridos no final da seção <head> do HTML de cada página. Essa é uma maneira útil de sobrescrever estilos e adicionar códigos de análise de site.',
30 + 'app_logo' => 'Logo da Aplicação',
31 + 'app_logo_desc' => 'A imagem deve ter 43px de altura. <br>Imagens mais largas devem ser reduzidas.',
32 + 'app_primary_color' => 'Cor primária da Aplicação',
33 + 'app_primary_color_desc' => 'Esse valor deverá ser Hexadecimal. <br>Deixe em branco para que o Bookstack assuma a cor padrão.',
34 +
35 + /**
36 + * Registration settings
37 + */
38 +
39 + 'reg_settings' => 'Parâmetros de Registro',
40 + 'reg_allow' => 'Permitir Registro?',
41 + 'reg_default_role' => 'Perfil padrão para usuários após o registro',
42 + 'reg_confirm_email' => 'Requerer confirmação por e-mail?',
43 + 'reg_confirm_email_desc' => 'Se restrições de domínio são usadas a confirmação por e-mail será requerida e o valor abaixo será ignorado.',
44 + 'reg_confirm_restrict_domain' => 'Restringir registro ao domínio',
45 + 'reg_confirm_restrict_domain_desc' => 'Entre com uma lista de domínios de e-mails separados por vírgula para os quais você deseja restringir os registros. Será enviado um e-mail de confirmação para o usuário validar o e-mail antes de ser permitido interação com a aplicação. <br> Note que os usuários serão capazes de alterar o e-mail cadastrado após o sucesso na confirmação do registro.',
46 + 'reg_confirm_restrict_domain_placeholder' => 'Nenhuma restrição configurada',
47 +
48 + /**
49 + * Role settings
50 + */
51 +
52 + 'roles' => 'Perfis',
53 + 'role_user_roles' => 'Perfis de Usuário',
54 + 'role_create' => 'Criar novo Perfil',
55 + 'role_create_success' => 'Perfil criado com sucesso',
56 + 'role_delete' => 'Excluir Perfil',
57 + 'role_delete_confirm' => 'A ação vai excluír o Perfil de nome \':roleName\'.',
58 + 'role_delete_users_assigned' => 'Esse Perfil tem :userCount usuários assinalados a ele. Se quiser migrar usuários desse Perfil para outro, selecione um novo Perfil.',
59 + 'role_delete_no_migration' => "Não migre os usuários",
60 + 'role_delete_sure' => 'Tem certeza que deseja excluir esse Perfil?',
61 + 'role_delete_success' => 'Perfil excluído com sucesso',
62 + 'role_edit' => 'Editar Perfil',
63 + 'role_details' => 'Detalhes do Perfil',
64 + 'role_name' => 'Nome do Perfil',
65 + 'role_desc' => 'Descrição Curta do Perfil',
66 + 'role_system' => 'Permissões do Sistema',
67 + 'role_manage_users' => 'Gerenciar Usuários',
68 + 'role_manage_roles' => 'Gerenciar Perfis & Permissões de Perfis',
69 + 'role_manage_entity_permissions' => 'Gerenciar todos os livros, capítulos e permissões de páginas',
70 + 'role_manage_own_entity_permissions' => 'Gerenciar permissões de seu próprio livro, capítulo e paginas',
71 + 'role_manage_settings' => 'Gerenciar configurações de app',
72 + 'role_asset' => 'Permissões de Ativos',
73 + 'role_asset_desc' => 'Essas permissões controlam o acesso padrão para os ativos dentro do sistema. Permissões em Livros, Capítulos e Páginas serão sobrescritas por essas permissões.',
74 + 'role_all' => 'Todos',
75 + 'role_own' => 'Próprio',
76 + 'role_controlled_by_asset' => 'Controlado pelos ativos que você fez upload',
77 + 'role_save' => 'Salvar Perfil',
78 + 'role_update_success' => 'Perfil atualizado com sucesso',
79 + 'role_users' => 'Usuários neste Perfil',
80 + 'role_users_none' => 'Nenhum usuário está atualmente atrelado a esse Perfil',
81 +
82 + /**
83 + * Users
84 + */
85 +
86 + 'users' => 'Usuários',
87 + 'user_profile' => 'Perfil de Usuário',
88 + 'users_add_new' => 'Adicionar Novo Usuário',
89 + 'users_search' => 'Pesquisar Usuários',
90 + 'users_role' => 'Perfis de Usuário',
91 + 'users_external_auth_id' => 'ID de Autenticação Externa',
92 + 'users_password_warning' => 'Preencha os dados abaixo caso queira modificar a sua senha:',
93 + 'users_system_public' => 'Esse usuário representa quaisquer convidados que visitam o aplicativo. Ele não pode ser usado para login.',
94 + 'users_delete' => 'Excluir Usuário',
95 + 'users_delete_named' => 'Excluir :userName',
96 + 'users_delete_warning' => 'A ação vai excluir completamente o usuário de nome \':userName\' do sistema.',
97 + 'users_delete_confirm' => 'Tem certeza que deseja excluir esse usuário?',
98 + 'users_delete_success' => 'Usuários excluídos com sucesso',
99 + 'users_edit' => 'Editar usuário',
100 + 'users_edit_profile' => 'Editar Perfil',
101 + 'users_edit_success' => 'Usuário atualizado com sucesso',
102 + 'users_avatar' => 'Imagem de Usuário',
103 + 'users_avatar_desc' => 'Essa imagem deve ser um quadrado com aproximadamente 256px de altura e largura.',
104 + 'users_social_accounts' => 'Contas Sociais',
105 + 'users_social_accounts_info' => 'Aqui você pode conectar outras contas para acesso mais rápido. Desconectar uma conta não retira a possibilidade de acesso usando-a. Para revogar o acesso ao perfil através da conta social, você deverá fazê-lo na sua conta social.',
106 + 'users_social_connect' => 'Contas conectadas',
107 + 'users_social_disconnect' => 'Desconectar Conta',
108 + 'users_social_connected' => 'Conta :socialAccount foi conectada com sucesso ao seu perfil.',
109 + 'users_social_disconnected' => 'Conta :socialAccount foi desconectada com sucesso de seu perfil.',
110 +];
111 +
112 +
113 +
114 +
115 +
116 +
117 +
118 +
119 +
120 +
121 +
122 +
123 +
124 +
125 +
126 +
127 +
128 +
129 +
130 +
131 +
132 +
133 +
134 +
135 +
136 +
137 +
138 +
139 +
140 +
1 +<?php
2 +
3 +return [
4 +
5 + /*
6 + |--------------------------------------------------------------------------
7 + | Validation Language Lines
8 + |--------------------------------------------------------------------------
9 + |
10 + | The following language lines contain the default error messages used by
11 + | the validator class. Some of these rules have multiple versions such
12 + | as the size rules. Feel free to tweak each of these messages here.
13 + |
14 + */
15 +
16 + 'accepted' => 'O :attribute deve ser aceito.',
17 + 'active_url' => 'O :attribute não é uma URL válida.',
18 + 'after' => 'O :attribute deve ser uma data posterior à data :date.',
19 + 'alpha' => 'O :attribute deve conter apenas letras.',
20 + 'alpha_dash' => 'O :attribute deve conter apenas letras, números e traços.',
21 + 'alpha_num' => 'O :attribute deve conter apenas letras e números.',
22 + 'array' => 'O :attribute deve ser uma array.',
23 + 'before' => 'O :attribute deve ser uma data anterior à data :date.',
24 + 'between' => [
25 + 'numeric' => 'O :attribute deve ter tamanho entre :min e :max.',
26 + 'file' => 'O :attribute deve ter entre :min e :max kilobytes.',
27 + 'string' => 'O :attribute deve ter entre :min e :max caracteres.',
28 + 'array' => 'O :attribute deve ter entre :min e :max itens.',
29 + ],
30 + 'boolean' => 'O campo :attribute deve ser verdadeiro ou falso.',
31 + 'confirmed' => 'O campo :attribute de confirmação não é igual.',
32 + 'date' => 'O campo :attribute não está em um formato de data válido.',
33 + 'date_format' => 'O campo :attribute não tem a formatação :format.',
34 + 'different' => 'O campo :attribute e o campo :other devem ser diferentes.',
35 + 'digits' => 'O campo :attribute deve ter :digits dígitos.',
36 + 'digits_between' => 'O campo :attribute deve ter entre :min e :max dígitos.',
37 + 'email' => 'O campo :attribute deve ser um e-mail válido.',
38 + 'filled' => 'O campo :attribute é requerido.',
39 + 'exists' => 'O atributo :attribute selecionado não é válido.',
40 + 'image' => 'O campo :attribute deve ser uma imagem.',
41 + 'in' => 'The selected :attribute is invalid.',
42 + 'integer' => 'O campo :attribute deve ser um número inteiro.',
43 + 'ip' => 'O campo :attribute deve ser um IP válido.',
44 + 'max' => [
45 + 'numeric' => 'O valor para o campo :attribute não deve ser maior que :max.',
46 + 'file' => 'O valor para o campo :attribute não deve ter tamanho maior que :max kilobytes.',
47 + 'string' => 'O valor para o campo :attribute não deve ter mais que :max caracteres.',
48 + 'array' => 'O valor para o campo :attribute não deve ter mais que :max itens.',
49 + ],
50 + 'mimes' => 'O :attribute deve ser do tipo type: :values.',
51 + 'min' => [
52 + 'numeric' => 'O valor para o campo :attribute não deve ser menor que :min.',
53 + 'file' => 'O valor para o campo :attribute não deve ter tamanho menor que :min kilobytes.',
54 + 'string' => 'O valor para o campo :attribute não deve ter menos que :min caracteres.',
55 + 'array' => 'O valor para o campo :attribute não deve ter menos que :min itens.',
56 + ],
57 + 'not_in' => 'O campo selecionado :attribute é inválido.',
58 + 'numeric' => 'O campo :attribute deve ser um número.',
59 + 'regex' => 'O formato do campo :attribute é inválido.',
60 + 'required' => 'O campo :attribute é requerido.',
61 + 'required_if' => 'O campo :attribute é requerido quando o campo :other tem valor :value.',
62 + 'required_with' => 'O campo :attribute é requerido quando os valores :values estiverem presentes.',
63 + 'required_with_all' => 'O campo :attribute é requerido quando os valores :values estiverem presentes.',
64 + 'required_without' => 'O campo :attribute é requerido quando os valores :values não estiverem presentes.',
65 + 'required_without_all' => 'O campo :attribute é requerido quando nenhum dos valores :values estiverem presentes.',
66 + 'same' => 'O campo :attribute e o campo :other devem ser iguais.',
67 + 'size' => [
68 + 'numeric' => 'O tamanho do campo :attribute deve ser :size.',
69 + 'file' => 'O tamanho do arquivo :attribute deve ser de :size kilobytes.',
70 + 'string' => 'O tamanho do campo :attribute deve ser de :size caracteres.',
71 + 'array' => 'O campo :attribute deve conter :size itens.',
72 + ],
73 + 'string' => 'O campo :attribute deve ser uma string.',
74 + 'timezone' => 'O campo :attribute deve conter uma timezone válida.',
75 + 'unique' => 'Já existe um campo/dado de nome :attribute.',
76 + 'url' => 'O formato da URL :attribute é inválido.',
77 +
78 + /*
79 + |--------------------------------------------------------------------------
80 + | Custom Validation Language Lines
81 + |--------------------------------------------------------------------------
82 + |
83 + | Here you may specify custom validation messages for attributes using the
84 + | convention "attribute.rule" to name the lines. This makes it quick to
85 + | specify a specific custom language line for a given attribute rule.
86 + |
87 + */
88 +
89 + 'custom' => [
90 + 'password-confirm' => [
91 + 'required_with' => 'Confirmação de senha requerida',
92 + ],
93 + ],
94 +
95 + /*
96 + |--------------------------------------------------------------------------
97 + | Custom Validation Attributes
98 + |--------------------------------------------------------------------------
99 + |
100 + | The following language lines are used to swap attribute place-holders
101 + | with something more reader friendly such as E-Mail Address instead
102 + | of "email". This simply helps us make messages a little cleaner.
103 + |
104 + */
105 +
106 + 'attributes' => [],
107 +
108 +];
1 <div class="form-group"> 1 <div class="form-group">
2 - <label for="username">Username</label> 2 + <label for="username">{{ trans('auth.username') }}</label>
3 @include('form/text', ['name' => 'username', 'tabindex' => 1]) 3 @include('form/text', ['name' => 'username', 'tabindex' => 1])
4 </div> 4 </div>
5 5
6 @if(session('request-email', false) === true) 6 @if(session('request-email', false) === true)
7 <div class="form-group"> 7 <div class="form-group">
8 - <label for="email">Email</label> 8 + <label for="email">{{ trans('auth.email') }}</label>
9 @include('form/text', ['name' => 'email', 'tabindex' => 1]) 9 @include('form/text', ['name' => 'email', 'tabindex' => 1])
10 <span class="text-neg"> 10 <span class="text-neg">
11 - Please enter an email to use for this account. 11 + {{ trans('auth.ldap_email_hint') }}
12 </span> 12 </span>
13 </div> 13 </div>
14 @endif 14 @endif
15 15
16 <div class="form-group"> 16 <div class="form-group">
17 - <label for="password">Password</label> 17 + <label for="password">{{ trans('auth.password') }}</label>
18 @include('form/password', ['name' => 'password', 'tabindex' => 2]) 18 @include('form/password', ['name' => 'password', 'tabindex' => 2])
19 </div> 19 </div>
...\ No newline at end of file ...\ No newline at end of file
......
1 <div class="form-group"> 1 <div class="form-group">
2 - <label for="email">Email</label> 2 + <label for="email">{{ trans('auth.email') }}</label>
3 @include('form/text', ['name' => 'email', 'tabindex' => 1]) 3 @include('form/text', ['name' => 'email', 'tabindex' => 1])
4 </div> 4 </div>
5 5
6 <div class="form-group"> 6 <div class="form-group">
7 - <label for="password">Password</label> 7 + <label for="password">{{ trans('auth.password') }}</label>
8 @include('form/password', ['name' => 'password', 'tabindex' => 2]) 8 @include('form/password', ['name' => 'password', 'tabindex' => 2])
9 - <span class="block small"><a href="{{ baseUrl('/password/email') }}">Forgot Password?</a></span> 9 + <span class="block small"><a href="{{ baseUrl('/password/email') }}">{{ trans('auth.forgot_password') }}</a></span>
10 </div> 10 </div>
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
2 2
3 @section('header-buttons') 3 @section('header-buttons')
4 @if(setting('registration-enabled', false)) 4 @if(setting('registration-enabled', false))
5 - <a href="{{ baseUrl("/register") }}"><i class="zmdi zmdi-account-add"></i>Sign up</a> 5 + <a href="{{ baseUrl("/register") }}"><i class="zmdi zmdi-account-add"></i>{{ trans('auth.sign_up') }}</a>
6 @endif 6 @endif
7 @stop 7 @stop
8 8
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
10 10
11 <div class="text-center"> 11 <div class="text-center">
12 <div class="center-box"> 12 <div class="center-box">
13 - <h1>Log In</h1> 13 + <h1>{{ title_case(trans('auth.log_in')) }}</h1>
14 14
15 <form action="{{ baseUrl("/login") }}" method="POST" id="login-form"> 15 <form action="{{ baseUrl("/login") }}" method="POST" id="login-form">
16 {!! csrf_field() !!} 16 {!! csrf_field() !!}
...@@ -19,25 +19,25 @@ ...@@ -19,25 +19,25 @@
19 @include('auth/forms/login/' . $authMethod) 19 @include('auth/forms/login/' . $authMethod)
20 20
21 <div class="form-group"> 21 <div class="form-group">
22 - <label for="remember" class="inline">Remember Me</label> 22 + <label for="remember" class="inline">{{ trans('auth.remember_me') }}</label>
23 <input type="checkbox" id="remember" name="remember" class="toggle-switch-checkbox"> 23 <input type="checkbox" id="remember" name="remember" class="toggle-switch-checkbox">
24 <label for="remember" class="toggle-switch"></label> 24 <label for="remember" class="toggle-switch"></label>
25 </div> 25 </div>
26 26
27 27
28 <div class="from-group"> 28 <div class="from-group">
29 - <button class="button block pos" tabindex="3"><i class="zmdi zmdi-sign-in"></i> Sign In</button> 29 + <button class="button block pos" tabindex="3"><i class="zmdi zmdi-sign-in"></i> {{ title_case(trans('auth.log_in')) }}</button>
30 </div> 30 </div>
31 </form> 31 </form>
32 32
33 @if(count($socialDrivers) > 0) 33 @if(count($socialDrivers) > 0)
34 <hr class="margin-top"> 34 <hr class="margin-top">
35 - <h3 class="text-muted">Social Login</h3> 35 + <h3 class="text-muted">{{ trans('auth.social_login') }}</h3>
36 @if(isset($socialDrivers['google'])) 36 @if(isset($socialDrivers['google']))
37 - <a href="{{ baseUrl("/login/service/google") }}" style="color: #DC4E41;"><i class="zmdi zmdi-google-plus-box zmdi-hc-4x"></i></a> 37 + <a id="social-login-google" href="{{ baseUrl("/login/service/google") }}" style="color: #DC4E41;"><i class="zmdi zmdi-google-plus-box zmdi-hc-4x"></i></a>
38 @endif 38 @endif
39 @if(isset($socialDrivers['github'])) 39 @if(isset($socialDrivers['github']))
40 - <a href="{{ baseUrl("/login/service/github") }}" style="color:#444;"><i class="zmdi zmdi-github zmdi-hc-4x"></i></a> 40 + <a id="social-login-github" href="{{ baseUrl("/login/service/github") }}" style="color:#444;"><i class="zmdi zmdi-github zmdi-hc-4x"></i></a>
41 @endif 41 @endif
42 @endif 42 @endif
43 </div> 43 </div>
......
1 @extends('public') 1 @extends('public')
2 2
3 @section('header-buttons') 3 @section('header-buttons')
4 - <a href="{{ baseUrl("/login") }}"><i class="zmdi zmdi-sign-in"></i>Sign in</a> 4 + <a href="{{ baseUrl("/login") }}"><i class="zmdi zmdi-sign-in"></i>{{ trans('auth.log_in') }}</a>
5 @if(setting('registration-enabled')) 5 @if(setting('registration-enabled'))
6 - <a href="{{ baseUrl("/register") }}"><i class="zmdi zmdi-account-add"></i>Sign up</a> 6 + <a href="{{ baseUrl("/register") }}"><i class="zmdi zmdi-account-add"></i>{{ trans('auth.sign_up') }}</a>
7 @endif 7 @endif
8 @stop 8 @stop
9 9
...@@ -12,20 +12,20 @@ ...@@ -12,20 +12,20 @@
12 12
13 <div class="text-center"> 13 <div class="text-center">
14 <div class="center-box text-left"> 14 <div class="center-box text-left">
15 - <h1>Reset Password</h1> 15 + <h1>{{ trans('auth.reset_password') }}</h1>
16 16
17 - <p class="muted small">Enter your email below and you will be sent an email with a password reset link.</p> 17 + <p class="muted small">{{ trans('auth.reset_password_send_instructions') }}</p>
18 18
19 <form action="{{ baseUrl("/password/email") }}" method="POST"> 19 <form action="{{ baseUrl("/password/email") }}" method="POST">
20 {!! csrf_field() !!} 20 {!! csrf_field() !!}
21 21
22 <div class="form-group"> 22 <div class="form-group">
23 - <label for="email">Email</label> 23 + <label for="email">{{ trans('auth.email') }}</label>
24 @include('form/text', ['name' => 'email']) 24 @include('form/text', ['name' => 'email'])
25 </div> 25 </div>
26 26
27 <div class="from-group"> 27 <div class="from-group">
28 - <button class="button block pos">Send Reset Link</button> 28 + <button class="button block pos">{{ trans('auth.reset_password_send_button') }}</button>
29 </div> 29 </div>
30 </form> 30 </form>
31 </div> 31 </div>
......
1 @extends('public') 1 @extends('public')
2 2
3 @section('header-buttons') 3 @section('header-buttons')
4 - <a href="{{ baseUrl("/login") }}"><i class="zmdi zmdi-sign-in"></i>Sign in</a> 4 + <a href="{{ baseUrl("/login") }}"><i class="zmdi zmdi-sign-in"></i>{{ trans('auth.log_in') }}</a>
5 @if(setting('registration-enabled')) 5 @if(setting('registration-enabled'))
6 - <a href="{{ baseUrl("/register") }}"><i class="zmdi zmdi-account-add"></i>Sign up</a> 6 + <a href="{{ baseUrl("/register") }}"><i class="zmdi zmdi-account-add"></i>{{ trans('auth.sign_up') }}</a>
7 @endif 7 @endif
8 @stop 8 @stop
9 9
...@@ -14,29 +14,29 @@ ...@@ -14,29 +14,29 @@
14 14
15 <div class="text-center"> 15 <div class="text-center">
16 <div class="center-box text-left"> 16 <div class="center-box text-left">
17 - <h1>Reset Password</h1> 17 + <h1>{{ trans('auth.reset_password') }}</h1>
18 18
19 <form action="{{ baseUrl("/password/reset") }}" method="POST"> 19 <form action="{{ baseUrl("/password/reset") }}" method="POST">
20 {!! csrf_field() !!} 20 {!! csrf_field() !!}
21 <input type="hidden" name="token" value="{{ $token }}"> 21 <input type="hidden" name="token" value="{{ $token }}">
22 22
23 <div class="form-group"> 23 <div class="form-group">
24 - <label for="email">Email</label> 24 + <label for="email">{{ trans('auth.email') }}</label>
25 @include('form/text', ['name' => 'email']) 25 @include('form/text', ['name' => 'email'])
26 </div> 26 </div>
27 27
28 <div class="form-group"> 28 <div class="form-group">
29 - <label for="password">Password</label> 29 + <label for="password">{{ trans('auth.password') }}</label>
30 @include('form/password', ['name' => 'password']) 30 @include('form/password', ['name' => 'password'])
31 </div> 31 </div>
32 32
33 <div class="form-group"> 33 <div class="form-group">
34 - <label for="password_confirmation">Confirm Password</label> 34 + <label for="password_confirmation">{{ trans('auth.password_confirm') }}</label>
35 @include('form/password', ['name' => 'password_confirmation']) 35 @include('form/password', ['name' => 'password_confirmation'])
36 </div> 36 </div>
37 37
38 <div class="from-group"> 38 <div class="from-group">
39 - <button class="button block pos">Reset Password</button> 39 + <button class="button block pos">{{ trans('auth.reset_password') }}</button>
40 </div> 40 </div>
41 </form> 41 </form>
42 </div> 42 </div>
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
2 2
3 @section('header-buttons') 3 @section('header-buttons')
4 @if(!$signedIn) 4 @if(!$signedIn)
5 - <a href="{{ baseUrl("/login") }}"><i class="zmdi zmdi-sign-in"></i>Sign in</a> 5 + <a href="{{ baseUrl("/login") }}"><i class="zmdi zmdi-sign-in"></i>{{ trans('auth.log_in') }}</a>
6 @endif 6 @endif
7 @stop 7 @stop
8 8
...@@ -10,10 +10,9 @@ ...@@ -10,10 +10,9 @@
10 10
11 <div class="text-center"> 11 <div class="text-center">
12 <div class="center-box"> 12 <div class="center-box">
13 - <h2>Thanks for registering!</h2> 13 + <h2>{{ trans('auth.register_thanks') }}</h2>
14 - <p>Please check your email and click the confirmation button to access {{ setting('app-name', 'BookStack') }}.</p> 14 + <p>{{ trans('auth.register_confirm', ['appName' => setting('app-name')]) }}</p>
15 </div> 15 </div>
16 </div> 16 </div>
17 17
18 -
19 @stop 18 @stop
......
1 @extends('public') 1 @extends('public')
2 2
3 @section('header-buttons') 3 @section('header-buttons')
4 - <a href="{{ baseUrl("/login") }}"><i class="zmdi zmdi-sign-in"></i>Sign in</a> 4 + <a href="{{ baseUrl("/login") }}"><i class="zmdi zmdi-sign-in"></i>{{ trans('auth.log_in') }}</a>
5 @stop 5 @stop
6 6
7 @section('content') 7 @section('content')
8 8
9 <div class="text-center"> 9 <div class="text-center">
10 <div class="center-box"> 10 <div class="center-box">
11 - <h1>Sign Up</h1> 11 + <h1>{{ title_case(trans('auth.sign_up')) }}</h1>
12 12
13 <form action="{{ baseUrl("/register") }}" method="POST"> 13 <form action="{{ baseUrl("/register") }}" method="POST">
14 {!! csrf_field() !!} 14 {!! csrf_field() !!}
15 15
16 <div class="form-group"> 16 <div class="form-group">
17 - <label for="email">Name</label> 17 + <label for="email">{{ trans('auth.name') }}</label>
18 @include('form/text', ['name' => 'name']) 18 @include('form/text', ['name' => 'name'])
19 </div> 19 </div>
20 20
21 <div class="form-group"> 21 <div class="form-group">
22 - <label for="email">Email</label> 22 + <label for="email">{{ trans('auth.email') }}</label>
23 @include('form/text', ['name' => 'email']) 23 @include('form/text', ['name' => 'email'])
24 </div> 24 </div>
25 25
26 <div class="form-group"> 26 <div class="form-group">
27 - <label for="password">Password</label> 27 + <label for="password">{{ trans('auth.password') }}</label>
28 - @include('form/password', ['name' => 'password', 'placeholder' => 'Must be over 5 characters']) 28 + @include('form/password', ['name' => 'password', 'placeholder' => trans('auth.password_hint')])
29 </div> 29 </div>
30 30
31 <div class="from-group"> 31 <div class="from-group">
32 - <button class="button block pos">Create Account</button> 32 + <button class="button block pos">{{ trans('auth.create_account') }}</button>
33 </div> 33 </div>
34 </form> 34 </form>
35 35
36 @if(count($socialDrivers) > 0) 36 @if(count($socialDrivers) > 0)
37 <hr class="margin-top"> 37 <hr class="margin-top">
38 - <h3 class="text-muted">Social Registration</h3> 38 + <h3 class="text-muted">{{ trans('auth.social_registration') }}</h3>
39 - <p class="text-small">Register and sign in using another service.</p> 39 + <p class="text-small">{{ trans('auth.social_registration_text') }}</p>
40 @if(isset($socialDrivers['google'])) 40 @if(isset($socialDrivers['google']))
41 <a href="{{ baseUrl("/register/service/google") }}" style="color: #DC4E41;"><i class="zmdi zmdi-google-plus-box zmdi-hc-4x"></i></a> 41 <a href="{{ baseUrl("/register/service/google") }}" style="color: #DC4E41;"><i class="zmdi zmdi-google-plus-box zmdi-hc-4x"></i></a>
42 @endif 42 @endif
......
...@@ -4,16 +4,16 @@ ...@@ -4,16 +4,16 @@
4 4
5 <div class="row"> 5 <div class="row">
6 <div class="col-md-6 col-md-offset-3"> 6 <div class="col-md-6 col-md-offset-3">
7 - <h2>Email Address not confirmed</h2> 7 + <h2>{{ trans('auth.email_not_confirmed') }}</h2>
8 - <p class="text-muted">Your email address has not yet been confirmed. <br> 8 + <p class="text-muted">{{ trans('auth.email_not_confirmed_text') }}<br>
9 - Please click the link in the email that was sent shortly after you registered. <br> 9 + {{ trans('auth.email_not_confirmed_click_link') }} <br>
10 - If you cannot find the email you can re-send the confirmation email by submitting the form below. 10 + {{ trans('auth.email_not_confirmed_resend') }}
11 </p> 11 </p>
12 <hr> 12 <hr>
13 <form action="{{ baseUrl("/register/confirm/resend") }}" method="POST"> 13 <form action="{{ baseUrl("/register/confirm/resend") }}" method="POST">
14 {!! csrf_field() !!} 14 {!! csrf_field() !!}
15 <div class="form-group"> 15 <div class="form-group">
16 - <label for="email">Email Address</label> 16 + <label for="email">{{ trans('auth.email') }}</label>
17 @if(auth()->check()) 17 @if(auth()->check())
18 @include('form/text', ['name' => 'email', 'model' => auth()->user()]) 18 @include('form/text', ['name' => 'email', 'model' => auth()->user()])
19 @else 19 @else
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
21 @endif 21 @endif
22 </div> 22 </div>
23 <div class="form-group"> 23 <div class="form-group">
24 - <button type="submit" class="button pos">Resend Confirmation Email</button> 24 + <button type="submit" class="button pos">{{ trans('auth.email_not_confirmed_resend_button') }}</button>
25 </div> 25 </div>
26 </form> 26 </form>
27 </div> 27 </div>
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
17 <!-- Scripts --> 17 <!-- Scripts -->
18 <script src="{{ baseUrl('/libs/jquery/jquery.min.js?version=2.1.4') }}"></script> 18 <script src="{{ baseUrl('/libs/jquery/jquery.min.js?version=2.1.4') }}"></script>
19 <script src="{{ baseUrl('/libs/jquery/jquery-ui.min.js?version=1.11.4') }}"></script> 19 <script src="{{ baseUrl('/libs/jquery/jquery-ui.min.js?version=1.11.4') }}"></script>
20 + <script src="{{ baseUrl('/translations.js') }}"></script>
20 21
21 @yield('head') 22 @yield('head')
22 23
...@@ -53,32 +54,16 @@ ...@@ -53,32 +54,16 @@
53 <div class="col-lg-4 col-sm-5"> 54 <div class="col-lg-4 col-sm-5">
54 <div class="float right"> 55 <div class="float right">
55 <div class="links text-center"> 56 <div class="links text-center">
56 - <a href="{{ baseUrl('/books') }}"><i class="zmdi zmdi-book"></i>Books</a> 57 + <a href="{{ baseUrl('/books') }}"><i class="zmdi zmdi-book"></i>{{ trans('entities.books') }}</a>
57 @if(isset($currentUser) && userCan('settings-manage')) 58 @if(isset($currentUser) && userCan('settings-manage'))
58 - <a href="{{ baseUrl('/settings') }}"><i class="zmdi zmdi-settings"></i>Settings</a> 59 + <a href="{{ baseUrl('/settings') }}"><i class="zmdi zmdi-settings"></i>{{ trans('settings.settings') }}</a>
59 @endif 60 @endif
60 @if(!isset($signedIn) || !$signedIn) 61 @if(!isset($signedIn) || !$signedIn)
61 - <a href="{{ baseUrl('/login') }}"><i class="zmdi zmdi-sign-in"></i>Sign In</a> 62 + <a href="{{ baseUrl('/login') }}"><i class="zmdi zmdi-sign-in"></i>{{ trans('auth.log_in') }}</a>
62 @endif 63 @endif
63 </div> 64 </div>
64 @if(isset($signedIn) && $signedIn) 65 @if(isset($signedIn) && $signedIn)
65 - <div class="dropdown-container" dropdown> 66 + @include('partials._header-dropdown', ['currentUser' => $currentUser])
66 - <span class="user-name" dropdown-toggle>
67 - <img class="avatar" src="{{$currentUser->getAvatar(30)}}" alt="{{ $currentUser->name }}">
68 - <span class="name" ng-non-bindable>{{ $currentUser->getShortName(9) }}</span> <i class="zmdi zmdi-caret-down"></i>
69 - </span>
70 - <ul>
71 - <li>
72 - <a href="{{ baseUrl("/user/{$currentUser->id}") }}" class="text-primary"><i class="zmdi zmdi-account zmdi-hc-fw zmdi-hc-lg"></i>View Profile</a>
73 - </li>
74 - <li>
75 - <a href="{{ baseUrl("/settings/users/{$currentUser->id}") }}" class="text-primary"><i class="zmdi zmdi-edit zmdi-hc-fw zmdi-hc-lg"></i>Edit Profile</a>
76 - </li>
77 - <li>
78 - <a href="{{ baseUrl('/logout') }}" class="text-neg"><i class="zmdi zmdi-run zmdi-hc-fw zmdi-hc-lg"></i>Logout</a>
79 - </li>
80 - </ul>
81 - </div>
82 @endif 67 @endif
83 68
84 </div> 69 </div>
...@@ -93,7 +78,7 @@ ...@@ -93,7 +78,7 @@
93 78
94 <div id="back-to-top"> 79 <div id="back-to-top">
95 <div class="inner"> 80 <div class="inner">
96 - <i class="zmdi zmdi-chevron-up"></i> <span>Back to top</span> 81 + <i class="zmdi zmdi-chevron-up"></i> <span>{{ trans('common.back_to_top') }}</span>
97 </div> 82 </div>
98 </div> 83 </div>
99 @yield('bottom') 84 @yield('bottom')
......
1 +<div class="breadcrumbs">
2 + <a href="{{$book->getUrl()}}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $book->getShortName() }}</a>
3 +</div>
...\ No newline at end of file ...\ No newline at end of file
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
3 @section('content') 3 @section('content')
4 4
5 <div class="container small" ng-non-bindable> 5 <div class="container small" ng-non-bindable>
6 - <h1>Create New Book</h1> 6 + <h1>{{ trans('entities.books_create') }}</h1>
7 <form action="{{ baseUrl("/books") }}" method="POST"> 7 <form action="{{ baseUrl("/books") }}" method="POST">
8 @include('books/form') 8 @include('books/form')
9 </form> 9 </form>
......
...@@ -2,16 +2,26 @@ ...@@ -2,16 +2,26 @@
2 2
3 @section('content') 3 @section('content')
4 4
5 + <div class="faded-small toolbar">
6 + <div class="container">
7 + <div class="row">
8 + <div class="col-sm-12 faded">
9 + @include('books._breadcrumbs', ['book' => $book])
10 + </div>
11 + </div>
12 + </div>
13 + </div>
14 +
5 <div class="container small" ng-non-bindable> 15 <div class="container small" ng-non-bindable>
6 - <h1>Delete Book</h1> 16 + <h1>{{ trans('entities.books_delete') }}</h1>
7 - <p>This will delete the book with the name '{{$book->name}}', All pages and chapters will be removed.</p> 17 + <p>{{ trans('entities.books_delete_explain', ['bookName' => $book->name]) }}</p>
8 - <p class="text-neg">Are you sure you want to delete this book?</p> 18 + <p class="text-neg">{{ trans('entities.books_delete_confirmation') }}</p>
9 19
10 <form action="{{$book->getUrl()}}" method="POST"> 20 <form action="{{$book->getUrl()}}" method="POST">
11 {!! csrf_field() !!} 21 {!! csrf_field() !!}
12 <input type="hidden" name="_method" value="DELETE"> 22 <input type="hidden" name="_method" value="DELETE">
13 - <a href="{{$book->getUrl()}}" class="button">Cancel</a> 23 + <a href="{{$book->getUrl()}}" class="button">{{ trans('common.cancel') }}</a>
14 - <button type="submit" class="button neg">Confirm</button> 24 + <button type="submit" class="button neg">{{ trans('common.confirm') }}</button>
15 </form> 25 </form>
16 </div> 26 </div>
17 27
......
...@@ -2,8 +2,18 @@ ...@@ -2,8 +2,18 @@
2 2
3 @section('content') 3 @section('content')
4 4
5 + <div class="faded-small toolbar">
6 + <div class="container">
7 + <div class="row">
8 + <div class="col-sm-12 faded">
9 + @include('books._breadcrumbs', ['book' => $book])
10 + </div>
11 + </div>
12 + </div>
13 + </div>
14 +
5 <div class="container small" ng-non-bindable> 15 <div class="container small" ng-non-bindable>
6 - <h1>Edit Book</h1> 16 + <h1>{{ trans('entities.books_edit') }}</h1>
7 <form action="{{ $book->getUrl() }}" method="POST"> 17 <form action="{{ $book->getUrl() }}" method="POST">
8 <input type="hidden" name="_method" value="PUT"> 18 <input type="hidden" name="_method" value="PUT">
9 @include('books/form', ['model' => $book]) 19 @include('books/form', ['model' => $book])
......
1 1
2 {{ csrf_field() }} 2 {{ csrf_field() }}
3 <div class="form-group title-input"> 3 <div class="form-group title-input">
4 - <label for="name">Book Name</label> 4 + <label for="name">{{ trans('common.name') }}</label>
5 @include('form/text', ['name' => 'name']) 5 @include('form/text', ['name' => 'name'])
6 </div> 6 </div>
7 7
8 <div class="form-group description-input"> 8 <div class="form-group description-input">
9 - <label for="description">Description</label> 9 + <label for="description">{{ trans('common.description') }}</label>
10 @include('form/textarea', ['name' => 'description']) 10 @include('form/textarea', ['name' => 'description'])
11 </div> 11 </div>
12 12
13 <div class="form-group"> 13 <div class="form-group">
14 - <a href="{{ back()->getTargetUrl() }}" class="button muted">Cancel</a> 14 + <a href="{{ back()->getTargetUrl() }}" class="button muted">{{ trans('common.cancel') }}</a>
15 - <button type="submit" class="button pos">Save Book</button> 15 + <button type="submit" class="button pos">{{ trans('entities.books_save') }}</button>
16 </div> 16 </div>
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
9 <div class="col-xs-11 faded"> 9 <div class="col-xs-11 faded">
10 <div class="action-buttons"> 10 <div class="action-buttons">
11 @if($currentUser->can('book-create-all')) 11 @if($currentUser->can('book-create-all'))
12 - <a href="{{ baseUrl("/books/create") }}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>Add new book</a> 12 + <a href="{{ baseUrl("/books/create") }}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>{{ trans('entities.books_create') }}</a>
13 @endif 13 @endif
14 </div> 14 </div>
15 </div> 15 </div>
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
21 <div class="container" ng-non-bindable> 21 <div class="container" ng-non-bindable>
22 <div class="row"> 22 <div class="row">
23 <div class="col-sm-7"> 23 <div class="col-sm-7">
24 - <h1>Books</h1> 24 + <h1>{{ trans('entities.books') }}</h1>
25 @if(count($books) > 0) 25 @if(count($books) > 0)
26 @foreach($books as $book) 26 @foreach($books as $book)
27 @include('books/list-item', ['book' => $book]) 27 @include('books/list-item', ['book' => $book])
...@@ -29,27 +29,27 @@ ...@@ -29,27 +29,27 @@
29 @endforeach 29 @endforeach
30 {!! $books->render() !!} 30 {!! $books->render() !!}
31 @else 31 @else
32 - <p class="text-muted">No books have been created.</p> 32 + <p class="text-muted">{{ trans('entities.books_empty') }}</p>
33 @if(userCan('books-create-all')) 33 @if(userCan('books-create-all'))
34 - <a href="{{ baseUrl("/books/create") }}" class="text-pos"><i class="zmdi zmdi-edit"></i>Create one now</a> 34 + <a href="{{ baseUrl("/books/create") }}" class="text-pos"><i class="zmdi zmdi-edit"></i>{{ trans('entities.create_one_now') }}</a>
35 @endif 35 @endif
36 @endif 36 @endif
37 </div> 37 </div>
38 <div class="col-sm-4 col-sm-offset-1"> 38 <div class="col-sm-4 col-sm-offset-1">
39 <div id="recents"> 39 <div id="recents">
40 @if($recents) 40 @if($recents)
41 - <div class="margin-top large">&nbsp;</div> 41 + <div class="margin-top">&nbsp;</div>
42 - <h3>Recently Viewed</h3> 42 + <h3>{{ trans('entities.recently_viewed') }}</h3>
43 @include('partials/entity-list', ['entities' => $recents]) 43 @include('partials/entity-list', ['entities' => $recents])
44 @endif 44 @endif
45 </div> 45 </div>
46 <div class="margin-top large">&nbsp;</div> 46 <div class="margin-top large">&nbsp;</div>
47 <div id="popular"> 47 <div id="popular">
48 - <h3>Popular Books</h3> 48 + <h3>{{ trans('entities.books_popular') }}</h3>
49 @if(count($popular) > 0) 49 @if(count($popular) > 0)
50 @include('partials/entity-list', ['entities' => $popular]) 50 @include('partials/entity-list', ['entities' => $popular])
51 @else 51 @else
52 - <p class="text-muted">The most popular books will appear here.</p> 52 + <p class="text-muted">{{ trans('entities.books_popular_empty') }}</p>
53 @endif 53 @endif
54 </div> 54 </div>
55 </div> 55 </div>
......
...@@ -6,9 +6,7 @@ ...@@ -6,9 +6,7 @@
6 <div class="container"> 6 <div class="container">
7 <div class="row"> 7 <div class="row">
8 <div class="col-sm-12 faded"> 8 <div class="col-sm-12 faded">
9 - <div class="breadcrumbs"> 9 + @include('books._breadcrumbs', ['book' => $book])
10 - <a href="{{$book->getUrl()}}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $book->getShortName() }}</a>
11 - </div>
12 </div> 10 </div>
13 </div> 11 </div>
14 </div> 12 </div>
...@@ -16,7 +14,7 @@ ...@@ -16,7 +14,7 @@
16 14
17 15
18 <div class="container" ng-non-bindable> 16 <div class="container" ng-non-bindable>
19 - <h1>Book Permissions</h1> 17 + <h1>{{ trans('entities.books_permissions') }}</h1>
20 @include('form/restriction-form', ['model' => $book]) 18 @include('form/restriction-form', ['model' => $book])
21 </div> 19 </div>
22 20
......
...@@ -5,29 +5,32 @@ ...@@ -5,29 +5,32 @@
5 <div class="faded-small toolbar"> 5 <div class="faded-small toolbar">
6 <div class="container"> 6 <div class="container">
7 <div class="row"> 7 <div class="row">
8 - <div class="col-md-12"> 8 + <div class="col-md-6 faded">
9 + @include('books._breadcrumbs', ['book' => $book])
10 + </div>
11 + <div class="col-md-6">
9 <div class="action-buttons faded"> 12 <div class="action-buttons faded">
10 @if(userCan('page-create', $book)) 13 @if(userCan('page-create', $book))
11 - <a href="{{ $book->getUrl('/page/create') }}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i> New Page</a> 14 + <a href="{{ $book->getUrl('/page/create') }}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>{{ trans('entities.pages_new') }}</a>
12 @endif 15 @endif
13 @if(userCan('chapter-create', $book)) 16 @if(userCan('chapter-create', $book))
14 - <a href="{{ $book->getUrl('/chapter/create') }}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i> New Chapter</a> 17 + <a href="{{ $book->getUrl('/chapter/create') }}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>{{ trans('entities.chapters_new') }}</a>
15 @endif 18 @endif
16 @if(userCan('book-update', $book)) 19 @if(userCan('book-update', $book))
17 - <a href="{{$book->getEditUrl()}}" class="text-primary text-button"><i class="zmdi zmdi-edit"></i>Edit</a> 20 + <a href="{{$book->getEditUrl()}}" class="text-primary text-button"><i class="zmdi zmdi-edit"></i>{{ trans('common.edit') }}</a>
18 @endif 21 @endif
19 @if(userCan('book-update', $book) || userCan('restrictions-manage', $book) || userCan('book-delete', $book)) 22 @if(userCan('book-update', $book) || userCan('restrictions-manage', $book) || userCan('book-delete', $book))
20 <div dropdown class="dropdown-container"> 23 <div dropdown class="dropdown-container">
21 <a dropdown-toggle class="text-primary text-button"><i class="zmdi zmdi-more-vert"></i></a> 24 <a dropdown-toggle class="text-primary text-button"><i class="zmdi zmdi-more-vert"></i></a>
22 <ul> 25 <ul>
23 @if(userCan('book-update', $book)) 26 @if(userCan('book-update', $book))
24 - <li><a href="{{ $book->getUrl('/sort') }}" class="text-primary"><i class="zmdi zmdi-sort"></i>Sort</a></li> 27 + <li><a href="{{ $book->getUrl('/sort') }}" class="text-primary"><i class="zmdi zmdi-sort"></i>{{ trans('common.sort') }}</a></li>
25 @endif 28 @endif
26 @if(userCan('restrictions-manage', $book)) 29 @if(userCan('restrictions-manage', $book))
27 - <li><a href="{{ $book->getUrl('/permissions') }}" class="text-primary"><i class="zmdi zmdi-lock-outline"></i>Permissions</a></li> 30 + <li><a href="{{ $book->getUrl('/permissions') }}" class="text-primary"><i class="zmdi zmdi-lock-outline"></i>{{ trans('entities.permissions') }}</a></li>
28 @endif 31 @endif
29 @if(userCan('book-delete', $book)) 32 @if(userCan('book-delete', $book))
30 - <li><a href="{{ $book->getUrl('/delete') }}" class="text-neg"><i class="zmdi zmdi-delete"></i>Delete</a></li> 33 + <li><a href="{{ $book->getUrl('/delete') }}" class="text-neg"><i class="zmdi zmdi-delete"></i>{{ trans('common.delete') }}</a></li>
31 @endif 34 @endif
32 </ul> 35 </ul>
33 </div> 36 </div>
...@@ -59,23 +62,19 @@ ...@@ -59,23 +62,19 @@
59 <hr> 62 <hr>
60 @endforeach 63 @endforeach
61 @else 64 @else
62 - <p class="text-muted">No pages or chapters have been created for this book.</p> 65 + <p class="text-muted">{{ trans('entities.books_empty_contents') }}</p>
63 <p> 66 <p>
64 - <a href="{{ $book->getUrl('/page/create') }}" class="text-page"><i class="zmdi zmdi-file-text"></i>Create a new page</a> 67 + <a href="{{ $book->getUrl('/page/create') }}" class="text-page"><i class="zmdi zmdi-file-text"></i>{{ trans('entities.books_empty_create_page') }}</a>
65 - &nbsp;&nbsp;<em class="text-muted">-or-</em>&nbsp;&nbsp;&nbsp; 68 + &nbsp;&nbsp;<em class="text-muted">-{{ trans('entities.books_empty_or') }}-</em>&nbsp;&nbsp;&nbsp;
66 - <a href="{{ $book->getUrl('/chapter/create') }}" class="text-chapter"><i class="zmdi zmdi-collection-bookmark"></i>Add a chapter</a> 69 + <a href="{{ $book->getUrl('/chapter/create') }}" class="text-chapter"><i class="zmdi zmdi-collection-bookmark"></i>{{ trans('entities.books_empty_add_chapter') }}</a>
67 </p> 70 </p>
68 <hr> 71 <hr>
69 @endif 72 @endif
70 - <p class="text-muted small"> 73 + @include('partials.entity-meta', ['entity' => $book])
71 - Created {{$book->created_at->diffForHumans()}} @if($book->createdBy) by <a href="{{ $book->createdBy->getProfileUrl() }}">{{$book->createdBy->name}}</a> @endif
72 - <br>
73 - Last Updated {{$book->updated_at->diffForHumans()}} @if($book->updatedBy) by <a href="{{ $book->updatedBy->getProfileUrl() }}">{{$book->updatedBy->name}}</a> @endif
74 - </p>
75 </div> 74 </div>
76 </div> 75 </div>
77 <div class="search-results" ng-cloak ng-show="searching"> 76 <div class="search-results" ng-cloak ng-show="searching">
78 - <h3 class="text-muted">Search Results <a ng-if="searching" ng-click="clearSearch()" class="text-small"><i class="zmdi zmdi-close"></i>Clear Search</a></h3> 77 + <h3 class="text-muted">{{ trans('entities.search_results') }} <a ng-if="searching" ng-click="clearSearch()" class="text-small"><i class="zmdi zmdi-close"></i>{{ trans('entities.search_clear') }}</a></h3>
79 <div ng-if="!searchResults"> 78 <div ng-if="!searchResults">
80 @include('partials/loading-icon') 79 @include('partials/loading-icon')
81 </div> 80 </div>
...@@ -90,21 +89,21 @@ ...@@ -90,21 +89,21 @@
90 @if($book->restricted) 89 @if($book->restricted)
91 <p class="text-muted"> 90 <p class="text-muted">
92 @if(userCan('restrictions-manage', $book)) 91 @if(userCan('restrictions-manage', $book))
93 - <a href="{{ $book->getUrl('/permissions') }}"><i class="zmdi zmdi-lock-outline"></i>Book Permissions Active</a> 92 + <a href="{{ $book->getUrl('/permissions') }}"><i class="zmdi zmdi-lock-outline"></i>{{ trans('entities.books_permissions_active') }}</a>
94 @else 93 @else
95 - <i class="zmdi zmdi-lock-outline"></i>Book Permissions Active 94 + <i class="zmdi zmdi-lock-outline"></i>{{ trans('entities.books_permissions_active') }}
96 @endif 95 @endif
97 </p> 96 </p>
98 @endif 97 @endif
99 <div class="search-box"> 98 <div class="search-box">
100 <form ng-submit="searchBook($event)"> 99 <form ng-submit="searchBook($event)">
101 - <input ng-model="searchTerm" ng-change="checkSearchForm()" type="text" name="term" placeholder="Search This Book"> 100 + <input ng-model="searchTerm" ng-change="checkSearchForm()" type="text" name="term" placeholder="{{ trans('entities.books_search_this') }}">
102 <button type="submit"><i class="zmdi zmdi-search"></i></button> 101 <button type="submit"><i class="zmdi zmdi-search"></i></button>
103 <button ng-if="searching" ng-click="clearSearch()" type="button"><i class="zmdi zmdi-close"></i></button> 102 <button ng-if="searching" ng-click="clearSearch()" type="button"><i class="zmdi zmdi-close"></i></button>
104 </form> 103 </form>
105 </div> 104 </div>
106 <div class="activity anim fadeIn"> 105 <div class="activity anim fadeIn">
107 - <h3>Recent Activity</h3> 106 + <h3>{{ trans('entities.recent_activity') }}</h3>
108 @include('partials/activity-list', ['activity' => Activity::entityActivity($book, 20, 0)]) 107 @include('partials/activity-list', ['activity' => Activity::entityActivity($book, 20, 0)])
109 </div> 108 </div>
110 </div> 109 </div>
......
...@@ -6,8 +6,18 @@ ...@@ -6,8 +6,18 @@
6 6
7 @section('content') 7 @section('content')
8 8
9 + <div class="faded-small toolbar">
10 + <div class="container">
11 + <div class="row">
12 + <div class="col-sm-12 faded">
13 + @include('books._breadcrumbs', ['book' => $book])
14 + </div>
15 + </div>
16 + </div>
17 + </div>
18 +
9 <div class="container" ng-non-bindable> 19 <div class="container" ng-non-bindable>
10 - <h1>Sorting Pages & Chapters<span class="subheader">For {{ $book->name }}</span></h1> 20 + <h1>{{ trans('entities.books_sort') }}</h1>
11 <div class="row"> 21 <div class="row">
12 <div class="col-md-8" id="sort-boxes"> 22 <div class="col-md-8" id="sort-boxes">
13 23
...@@ -17,7 +27,7 @@ ...@@ -17,7 +27,7 @@
17 27
18 @if(count($books) > 1) 28 @if(count($books) > 1)
19 <div class="col-md-4"> 29 <div class="col-md-4">
20 - <h3>Show Other Books</h3> 30 + <h3>{{ trans('entities.books_sort_show_other') }}</h3>
21 <div id="additional-books"> 31 <div id="additional-books">
22 @foreach($books as $otherBook) 32 @foreach($books as $otherBook)
23 @if($otherBook->id !== $book->id) 33 @if($otherBook->id !== $book->id)
...@@ -37,8 +47,8 @@ ...@@ -37,8 +47,8 @@
37 <input type="hidden" name="_method" value="PUT"> 47 <input type="hidden" name="_method" value="PUT">
38 <input type="hidden" id="sort-tree-input" name="sort-tree"> 48 <input type="hidden" id="sort-tree-input" name="sort-tree">
39 <div class="list"> 49 <div class="list">
40 - <a href="{{ $book->getUrl() }}" class="button muted">Cancel</a> 50 + <a href="{{ $book->getUrl() }}" class="button muted">{{ trans('common.cancel') }}</a>
41 - <button class="button pos" type="submit">Save Order</button> 51 + <button class="button pos" type="submit">{{ trans('entities.books_sort_save') }}</button>
42 </div> 52 </div>
43 </form> 53 </form>
44 54
......
1 +<div class="breadcrumbs">
2 + <a href="{{ $chapter->book->getUrl() }}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $chapter->book->getShortName() }}</a>
3 + <span class="sep">&raquo;</span>
4 + <a href="{{ $chapter->getUrl() }}" class="text-chapter text-button"><i class="zmdi zmdi-collection-bookmark"></i>{{$chapter->getShortName()}}</a>
5 +</div>
...\ No newline at end of file ...\ No newline at end of file
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
3 @section('content') 3 @section('content')
4 4
5 <div class="container small" ng-non-bindable> 5 <div class="container small" ng-non-bindable>
6 - <h1>Create New Chapter</h1> 6 + <h1>{{ trans('entities.chapters_create') }}</h1>
7 <form action="{{ $book->getUrl('/chapter/create') }}" method="POST"> 7 <form action="{{ $book->getUrl('/chapter/create') }}" method="POST">
8 @include('chapters/form') 8 @include('chapters/form')
9 </form> 9 </form>
......
...@@ -2,17 +2,26 @@ ...@@ -2,17 +2,26 @@
2 2
3 @section('content') 3 @section('content')
4 4
5 + <div class="faded-small toolbar">
6 + <div class="container">
7 + <div class="row">
8 + <div class="col-sm-12 faded">
9 + @include('chapters._breadcrumbs', ['chapter' => $chapter])
10 + </div>
11 + </div>
12 + </div>
13 + </div>
14 +
5 <div class="container small" ng-non-bindable> 15 <div class="container small" ng-non-bindable>
6 - <h1>Delete Chapter</h1> 16 + <h1>{{ trans('entities.chapters_delete') }}</h1>
7 - <p>This will delete the chapter with the name '{{$chapter->name}}', All pages will be removed 17 + <p>{{ trans('entities.chapters_delete_explain', ['chapterName' => $chapter->name]) }}</p>
8 - and added directly to the book.</p> 18 + <p class="text-neg">{{ trans('entities.chapters_delete_confirm') }}</p>
9 - <p class="text-neg">Are you sure you want to delete this chapter?</p>
10 19
11 <form action="{{ $chapter->getUrl() }}" method="POST"> 20 <form action="{{ $chapter->getUrl() }}" method="POST">
12 {!! csrf_field() !!} 21 {!! csrf_field() !!}
13 <input type="hidden" name="_method" value="DELETE"> 22 <input type="hidden" name="_method" value="DELETE">
14 - <a href="{{ $chapter->getUrl() }}" class="button primary">Cancel</a> 23 + <a href="{{ $chapter->getUrl() }}" class="button primary">{{ trans('common.cancel') }}</a>
15 - <button type="submit" class="button neg">Confirm</button> 24 + <button type="submit" class="button neg">{{ trans('common.confirm') }}</button>
16 </form> 25 </form>
17 </div> 26 </div>
18 27
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
3 @section('content') 3 @section('content')
4 4
5 <div class="container small" ng-non-bindable> 5 <div class="container small" ng-non-bindable>
6 - <h1>Edit Chapter</h1> 6 + <h1>{{ trans('entities.chapters_edit') }}</h1>
7 <form action="{{ $chapter->getUrl() }}" method="POST"> 7 <form action="{{ $chapter->getUrl() }}" method="POST">
8 <input type="hidden" name="_method" value="PUT"> 8 <input type="hidden" name="_method" value="PUT">
9 @include('chapters/form', ['model' => $chapter]) 9 @include('chapters/form', ['model' => $chapter])
......
...@@ -2,16 +2,16 @@ ...@@ -2,16 +2,16 @@
2 {!! csrf_field() !!} 2 {!! csrf_field() !!}
3 3
4 <div class="form-group title-input"> 4 <div class="form-group title-input">
5 - <label for="name">Chapter Name</label> 5 + <label for="name">{{ trans('common.name') }}</label>
6 @include('form/text', ['name' => 'name']) 6 @include('form/text', ['name' => 'name'])
7 </div> 7 </div>
8 8
9 <div class="form-group description-input"> 9 <div class="form-group description-input">
10 - <label for="description">Description</label> 10 + <label for="description">{{ trans('common.description') }}</label>
11 @include('form/textarea', ['name' => 'description']) 11 @include('form/textarea', ['name' => 'description'])
12 </div> 12 </div>
13 13
14 <div class="form-group"> 14 <div class="form-group">
15 - <a href="{{ back()->getTargetUrl() }}" class="button muted">Cancel</a> 15 + <a href="{{ back()->getTargetUrl() }}" class="button muted">{{ trans('common.cancel') }}</a>
16 - <button type="submit" class="button pos">Save Chapter</button> 16 + <button type="submit" class="button pos">{{ trans('entities.chapters_save') }}</button>
17 </div> 17 </div>
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
17 @endif 17 @endif
18 18
19 @if(!isset($hidePages) && count($chapter->pages) > 0) 19 @if(!isset($hidePages) && count($chapter->pages) > 0)
20 - <p class="text-muted chapter-toggle"><i class="zmdi zmdi-caret-right"></i> <i class="zmdi zmdi-file-text"></i> <span>{{ count($chapter->pages) }} Pages</span></p> 20 + <p class="text-muted chapter-toggle"><i class="zmdi zmdi-caret-right"></i> <i class="zmdi zmdi-file-text"></i> <span>{{ trans('entities.x_pages', ['count' => $chapter->pages->count()]) }}</span></p>
21 <div class="inset-list"> 21 <div class="inset-list">
22 @foreach($chapter->pages as $page) 22 @foreach($chapter->pages as $page)
23 <h5 class="@if($page->draft) draft @endif"><a href="{{ $page->getUrl() }}" class="text-page @if($page->draft) draft @endif"><i class="zmdi zmdi-file-text"></i>{{$page->name}}</a></h5> 23 <h5 class="@if($page->draft) draft @endif"><a href="{{ $page->getUrl() }}" class="text-page @if($page->draft) draft @endif"><i class="zmdi zmdi-file-text"></i>{{$page->name}}</a></h5>
......
...@@ -6,27 +6,23 @@ ...@@ -6,27 +6,23 @@
6 <div class="container"> 6 <div class="container">
7 <div class="row"> 7 <div class="row">
8 <div class="col-sm-12 faded"> 8 <div class="col-sm-12 faded">
9 - <div class="breadcrumbs"> 9 + @include('chapters._breadcrumbs', ['chapter' => $chapter])
10 - <a href="{{ $book->getUrl() }}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $book->getShortName() }}</a>
11 - <span class="sep">&raquo;</span>
12 - <a href="{{ $chapter->getUrl() }}" class="text-chapter text-button"><i class="zmdi zmdi-collection-bookmark"></i>{{ $chapter->getShortName() }}</a>
13 - </div>
14 </div> 10 </div>
15 </div> 11 </div>
16 </div> 12 </div>
17 </div> 13 </div>
18 14
19 <div class="container"> 15 <div class="container">
20 - <h1>Move Chapter <small class="subheader">{{$chapter->name}}</small></h1> 16 + <h1>{{ trans('entities.chapters_move') }}</h1>
21 17
22 <form action="{{ $chapter->getUrl('/move') }}" method="POST"> 18 <form action="{{ $chapter->getUrl('/move') }}" method="POST">
23 {!! csrf_field() !!} 19 {!! csrf_field() !!}
24 <input type="hidden" name="_method" value="PUT"> 20 <input type="hidden" name="_method" value="PUT">
25 21
26 - @include('partials/entity-selector', ['name' => 'entity_selection', 'selectorSize' => 'large', 'entityTypes' => 'book']) 22 + @include('components.entity-selector', ['name' => 'entity_selection', 'selectorSize' => 'large', 'entityTypes' => 'book'])
27 23
28 - <a href="{{ $chapter->getUrl() }}" class="button muted">Cancel</a> 24 + <a href="{{ $chapter->getUrl() }}" class="button muted">{{ trans('common.cancel') }}</a>
29 - <button type="submit" class="button pos">Move Chapter</button> 25 + <button type="submit" class="button pos">{{ trans('entities.chapters_move') }}</button>
30 </form> 26 </form>
31 </div> 27 </div>
32 28
......
...@@ -6,18 +6,14 @@ ...@@ -6,18 +6,14 @@
6 <div class="container"> 6 <div class="container">
7 <div class="row"> 7 <div class="row">
8 <div class="col-sm-12 faded"> 8 <div class="col-sm-12 faded">
9 - <div class="breadcrumbs"> 9 + @include('chapters._breadcrumbs', ['chapter' => $chapter])
10 - <a href="{{ $chapter->book->getUrl() }}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $chapter->book->getShortName() }}</a>
11 - <span class="sep">&raquo;</span>
12 - <a href="{{ $chapter->getUrl() }}" class="text-chapter text-button"><i class="zmdi zmdi-collection-bookmark"></i>{{$chapter->getShortName()}}</a>
13 - </div>
14 </div> 10 </div>
15 </div> 11 </div>
16 </div> 12 </div>
17 </div> 13 </div>
18 14
19 <div class="container" ng-non-bindable> 15 <div class="container" ng-non-bindable>
20 - <h1>Chapter Permissions</h1> 16 + <h1>{{ trans('entities.chapters_permissions') }}</h1>
21 @include('form/restriction-form', ['model' => $chapter]) 17 @include('form/restriction-form', ['model' => $chapter])
22 </div> 18 </div>
23 19
......
...@@ -6,30 +6,28 @@ ...@@ -6,30 +6,28 @@
6 <div class="container"> 6 <div class="container">
7 <div class="row"> 7 <div class="row">
8 <div class="col-sm-8 faded" ng-non-bindable> 8 <div class="col-sm-8 faded" ng-non-bindable>
9 - <div class="breadcrumbs"> 9 + @include('chapters._breadcrumbs', ['chapter' => $chapter])
10 - <a href="{{ $book->getUrl() }}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $book->getShortName() }}</a>
11 - </div>
12 </div> 10 </div>
13 <div class="col-sm-4 faded"> 11 <div class="col-sm-4 faded">
14 <div class="action-buttons"> 12 <div class="action-buttons">
15 @if(userCan('page-create', $chapter)) 13 @if(userCan('page-create', $chapter))
16 - <a href="{{ $chapter->getUrl('/create-page') }}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>New Page</a> 14 + <a href="{{ $chapter->getUrl('/create-page') }}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>{{ trans('entities.pages_new') }}</a>
17 @endif 15 @endif
18 @if(userCan('chapter-update', $chapter)) 16 @if(userCan('chapter-update', $chapter))
19 - <a href="{{ $chapter->getUrl('/edit') }}" class="text-primary text-button"><i class="zmdi zmdi-edit"></i>Edit</a> 17 + <a href="{{ $chapter->getUrl('/edit') }}" class="text-primary text-button"><i class="zmdi zmdi-edit"></i>{{ trans('common.edit') }}</a>
20 @endif 18 @endif
21 @if(userCan('chapter-update', $chapter) || userCan('restrictions-manage', $chapter) || userCan('chapter-delete', $chapter)) 19 @if(userCan('chapter-update', $chapter) || userCan('restrictions-manage', $chapter) || userCan('chapter-delete', $chapter))
22 <div dropdown class="dropdown-container"> 20 <div dropdown class="dropdown-container">
23 <a dropdown-toggle class="text-primary text-button"><i class="zmdi zmdi-more-vert"></i></a> 21 <a dropdown-toggle class="text-primary text-button"><i class="zmdi zmdi-more-vert"></i></a>
24 <ul> 22 <ul>
25 @if(userCan('chapter-update', $chapter)) 23 @if(userCan('chapter-update', $chapter))
26 - <li><a href="{{ $chapter->getUrl('/move') }}" class="text-primary"><i class="zmdi zmdi-folder"></i>Move</a></li> 24 + <li><a href="{{ $chapter->getUrl('/move') }}" class="text-primary"><i class="zmdi zmdi-folder"></i>{{ trans('common.move') }}</a></li>
27 @endif 25 @endif
28 @if(userCan('restrictions-manage', $chapter)) 26 @if(userCan('restrictions-manage', $chapter))
29 - <li><a href="{{ $chapter->getUrl('/permissions') }}" class="text-primary"><i class="zmdi zmdi-lock-outline"></i>Permissions</a></li> 27 + <li><a href="{{ $chapter->getUrl('/permissions') }}" class="text-primary"><i class="zmdi zmdi-lock-outline"></i>{{ trans('entities.permissions') }}</a></li>
30 @endif 28 @endif
31 @if(userCan('chapter-delete', $chapter)) 29 @if(userCan('chapter-delete', $chapter))
32 - <li><a href="{{ $chapter->getUrl('/delete') }}" class="text-neg"><i class="zmdi zmdi-delete"></i>Delete</a></li> 30 + <li><a href="{{ $chapter->getUrl('/delete') }}" class="text-neg"><i class="zmdi zmdi-delete"></i>{{ trans('common.delete') }}</a></li>
33 @endif 31 @endif
34 </ul> 32 </ul>
35 </div> 33 </div>
...@@ -57,26 +55,22 @@ ...@@ -57,26 +55,22 @@
57 </div> 55 </div>
58 @else 56 @else
59 <hr> 57 <hr>
60 - <p class="text-muted">No pages are currently in this chapter.</p> 58 + <p class="text-muted">{{ trans('entities.chapters_empty') }}</p>
61 <p> 59 <p>
62 @if(userCan('page-create', $chapter)) 60 @if(userCan('page-create', $chapter))
63 - <a href="{{ $chapter->getUrl('/create-page') }}" class="text-page"><i class="zmdi zmdi-file-text"></i>Create a new page</a> 61 + <a href="{{ $chapter->getUrl('/create-page') }}" class="text-page"><i class="zmdi zmdi-file-text"></i>{{ trans('entities.books_empty_create_page') }}</a>
64 @endif 62 @endif
65 @if(userCan('page-create', $chapter) && userCan('book-update', $book)) 63 @if(userCan('page-create', $chapter) && userCan('book-update', $book))
66 - &nbsp;&nbsp;<em class="text-muted">-or-</em>&nbsp;&nbsp;&nbsp; 64 + &nbsp;&nbsp;<em class="text-muted">-{{ trans('entities.books_empty_or') }}-</em>&nbsp;&nbsp;&nbsp;
67 @endif 65 @endif
68 @if(userCan('book-update', $book)) 66 @if(userCan('book-update', $book))
69 - <a href="{{ $book->getUrl('/sort') }}" class="text-book"><i class="zmdi zmdi-book"></i>Sort the current book</a> 67 + <a href="{{ $book->getUrl('/sort') }}" class="text-book"><i class="zmdi zmdi-book"></i>{{ trans('entities.books_empty_sort_current_book') }}</a>
70 @endif 68 @endif
71 </p> 69 </p>
72 <hr> 70 <hr>
73 @endif 71 @endif
74 72
75 - <p class="text-muted small"> 73 + @include('partials.entity-meta', ['entity' => $chapter])
76 - Created {{ $chapter->created_at->diffForHumans() }} @if($chapter->createdBy) by <a href="{{ $chapter->createdBy->getProfileUrl() }}">{{ $chapter->createdBy->name}}</a> @endif
77 - <br>
78 - Last Updated {{ $chapter->updated_at->diffForHumans() }} @if($chapter->updatedBy) by <a href="{{ $chapter->updatedBy->getProfileUrl() }}">{{ $chapter->updatedBy->name}}</a> @endif
79 - </p>
80 </div> 74 </div>
81 <div class="col-md-3 col-md-offset-1"> 75 <div class="col-md-3 col-md-offset-1">
82 <div class="margin-top large"></div> 76 <div class="margin-top large"></div>
...@@ -84,19 +78,20 @@ ...@@ -84,19 +78,20 @@
84 <div class="text-muted"> 78 <div class="text-muted">
85 79
86 @if($book->restricted) 80 @if($book->restricted)
81 + <p class="text-muted">
87 @if(userCan('restrictions-manage', $book)) 82 @if(userCan('restrictions-manage', $book))
88 - <a href="{{ $book->getUrl('/permissions') }}"><i class="zmdi zmdi-lock-outline"></i>Book Permissions Active</a> 83 + <a href="{{ $book->getUrl('/permissions') }}"><i class="zmdi zmdi-lock-outline"></i>{{ trans('entities.books_permissions_active') }}</a>
89 @else 84 @else
90 - <i class="zmdi zmdi-lock-outline"></i>Book Permissions Active 85 + <i class="zmdi zmdi-lock-outline"></i>{{ trans('entities.books_permissions_active') }}
91 @endif 86 @endif
92 - <br> 87 + </p>
93 @endif 88 @endif
94 89
95 @if($chapter->restricted) 90 @if($chapter->restricted)
96 @if(userCan('restrictions-manage', $chapter)) 91 @if(userCan('restrictions-manage', $chapter))
97 - <a href="{{ $chapter->getUrl('/permissions') }}"><i class="zmdi zmdi-lock-outline"></i>Chapter Permissions Active</a> 92 + <a href="{{ $chapter->getUrl('/permissions') }}"><i class="zmdi zmdi-lock-outline"></i>{{ trans('entities.chapters_permissions_active') }}</a>
98 @else 93 @else
99 - <i class="zmdi zmdi-lock-outline"></i>Chapter Permissions Active 94 + <i class="zmdi zmdi-lock-outline"></i>{{ trans('entities.chapters_permissions_active') }}
100 @endif 95 @endif
101 @endif 96 @endif
102 </div> 97 </div>
......
...@@ -2,12 +2,12 @@ ...@@ -2,12 +2,12 @@
2 <div class="overlay" entity-link-selector> 2 <div class="overlay" entity-link-selector>
3 <div class="popup-body small flex-child"> 3 <div class="popup-body small flex-child">
4 <div class="popup-header primary-background"> 4 <div class="popup-header primary-background">
5 - <div class="popup-title">Entity Select</div> 5 + <div class="popup-title">{{ trans('entities.entity_select') }}</div>
6 <button type="button" class="corner-button neg button popup-close">x</button> 6 <button type="button" class="corner-button neg button popup-close">x</button>
7 </div> 7 </div>
8 - @include('partials/entity-selector', ['name' => 'entity-selector']) 8 + @include('components.entity-selector', ['name' => 'entity-selector'])
9 <div class="popup-footer"> 9 <div class="popup-footer">
10 - <button type="button" disabled="true" class="button entity-link-selector-confirm pos corner-button">Select</button> 10 + <button type="button" disabled="true" class="button entity-link-selector-confirm pos corner-button">{{ trans('common.select') }}</button>
11 </div> 11 </div>
12 </div> 12 </div>
13 </div> 13 </div>
......
1 <div class="form-group"> 1 <div class="form-group">
2 <div entity-selector class="entity-selector {{$selectorSize or ''}}" entity-types="{{ $entityTypes or 'book,chapter,page' }}"> 2 <div entity-selector class="entity-selector {{$selectorSize or ''}}" entity-types="{{ $entityTypes or 'book,chapter,page' }}">
3 <input type="hidden" entity-selector-input name="{{$name}}" value=""> 3 <input type="hidden" entity-selector-input name="{{$name}}" value="">
4 - <input type="text" placeholder="Search" ng-model="search" ng-model-options="{debounce: 200}" ng-change="searchEntities()"> 4 + <input type="text" placeholder="{{ trans('common.search') }}" ng-model="search" ng-model-options="{debounce: 200}" ng-change="searchEntities()">
5 - <div class="text-center loading" ng-show="loading">@include('partials/loading-icon')</div> 5 + <div class="text-center loading" ng-show="loading">@include('partials.loading-icon')</div>
6 <div ng-show="!loading" ng-bind-html="entityResults"></div> 6 <div ng-show="!loading" ng-bind-html="entityResults"></div>
7 </div> 7 </div>
8 </div> 8 </div>
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
3 <div class="popup-body" ng-click="$event.stopPropagation()"> 3 <div class="popup-body" ng-click="$event.stopPropagation()">
4 4
5 <div class="popup-header primary-background"> 5 <div class="popup-header primary-background">
6 - <div class="popup-title">Image Select</div> 6 + <div class="popup-title">{{ trans('components.image_select') }}</div>
7 <button class="popup-close neg corner-button button">x</button> 7 <button class="popup-close neg corner-button button">x</button>
8 </div> 8 </div>
9 9
...@@ -12,16 +12,16 @@ ...@@ -12,16 +12,16 @@
12 <div class="image-manager-content"> 12 <div class="image-manager-content">
13 <div ng-if="imageType === 'gallery'" class="container"> 13 <div ng-if="imageType === 'gallery'" class="container">
14 <div class="image-manager-header row faded-small nav-tabs"> 14 <div class="image-manager-header row faded-small nav-tabs">
15 - <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> 15 + <div class="col-xs-4 tab-item" title="{{ trans('components.image_all_title') }}" ng-class="{selected: (view=='all')}" ng-click="setView('all')"><i class="zmdi zmdi-collection-image"></i> {{ trans('components.image_all') }}</div>
16 - <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> 16 + <div class="col-xs-4 tab-item" title="{{ trans('components.image_book_title') }}" ng-class="{selected: (view=='book')}" ng-click="setView('book')"><i class="zmdi zmdi-book text-book"></i> {{ trans('entities.book') }}</div>
17 - <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> 17 + <div class="col-xs-4 tab-item" title="{{ trans('components.image_page_title') }}" ng-class="{selected: (view=='page')}" ng-click="setView('page')"><i class="zmdi zmdi-file-text text-page"></i> {{ trans('entities.page') }}</div>
18 </div> 18 </div>
19 </div> 19 </div>
20 <div ng-show="view === 'all'" > 20 <div ng-show="view === 'all'" >
21 <form ng-submit="searchImages()" class="contained-search-box"> 21 <form ng-submit="searchImages()" class="contained-search-box">
22 - <input type="text" placeholder="Search by image name" ng-model="searchTerm"> 22 + <input type="text" placeholder="{{ trans('components.image_search_hint') }}" ng-model="searchTerm">
23 - <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> 23 + <button ng-class="{active: searching}" title="{{ trans('common.search_clear') }}" type="button" ng-click="cancelSearch()" class="text-button cancel"><i class="zmdi zmdi-close-circle-o"></i></button>
24 - <button title="Search" class="text-button" type="submit"><i class="zmdi zmdi-search"></i></button> 24 + <button title="{{ trans('common.search') }}" class="text-button" type="submit"><i class="zmdi zmdi-search"></i></button>
25 </form> 25 </form>
26 </div> 26 </div>
27 <div class="image-manager-list"> 27 <div class="image-manager-list">
...@@ -31,11 +31,11 @@ ...@@ -31,11 +31,11 @@
31 <img ng-src="@{{image.thumbs.gallery}}" ng-attr-alt="@{{image.title}}" ng-attr-title="@{{image.name}}"> 31 <img ng-src="@{{image.thumbs.gallery}}" ng-attr-alt="@{{image.title}}" ng-attr-title="@{{image.name}}">
32 <div class="image-meta"> 32 <div class="image-meta">
33 <span class="name" ng-bind="image.name"></span> 33 <span class="name" ng-bind="image.name"></span>
34 - <span class="date">Uploaded @{{ getDate(image.created_at) }}</span> 34 + <span class="date">{{ trans('components.image_uploaded', ['uploadedDate' => "{{ getDate(image.created_at) }" . "}"]) }}</span>
35 </div> 35 </div>
36 </div> 36 </div>
37 </div> 37 </div>
38 - <div class="load-more" ng-show="hasMore" ng-click="fetchData()">Load More</div> 38 + <div class="load-more" ng-show="hasMore" ng-click="fetchData()">{{ trans('components.image_load_more') }}</div>
39 </div> 39 </div>
40 </div> 40 </div>
41 41
...@@ -51,15 +51,14 @@ ...@@ -51,15 +51,14 @@
51 </a> 51 </a>
52 </div> 52 </div>
53 <div class="form-group"> 53 <div class="form-group">
54 - <label for="name">Image Name</label> 54 + <label for="name">{{ trans('components.image_image_name') }}</label>
55 <input type="text" id="name" name="name" ng-model="selectedImage.name"> 55 <input type="text" id="name" name="name" ng-model="selectedImage.name">
56 </div> 56 </div>
57 </form> 57 </form>
58 58
59 <div ng-show="dependantPages"> 59 <div ng-show="dependantPages">
60 <p class="text-neg text-small"> 60 <p class="text-neg text-small">
61 - This image is used in the pages below, Click delete again to confirm you want to delete 61 + {{ trans('components.image_delete_confirm') }}
62 - this image.
63 </p> 62 </p>
64 <ul class="text-neg"> 63 <ul class="text-neg">
65 <li ng-repeat="page in dependantPages"> 64 <li ng-repeat="page in dependantPages">
...@@ -73,13 +72,13 @@ ...@@ -73,13 +72,13 @@
73 <button class="button icon neg"><i class="zmdi zmdi-delete"></i></button> 72 <button class="button icon neg"><i class="zmdi zmdi-delete"></i></button>
74 </form> 73 </form>
75 <button class="button pos anim fadeIn float right" ng-show="selectedImage" ng-click="selectButtonClick()"> 74 <button class="button pos anim fadeIn float right" ng-show="selectedImage" ng-click="selectButtonClick()">
76 - <i class="zmdi zmdi-square-right"></i>Select Image 75 + <i class="zmdi zmdi-square-right"></i>{{ trans('components.image_select_image') }}
77 </button> 76 </button>
78 </div> 77 </div>
79 78
80 </div> 79 </div>
81 80
82 - <drop-zone upload-url="@{{getUploadUrl()}}" uploaded-to="@{{uploadedTo}}" event-success="uploadSuccess"></drop-zone> 81 + <drop-zone message="{{ trans('components.image_dropzone') }}" upload-url="@{{getUploadUrl()}}" uploaded-to="@{{uploadedTo}}" event-success="uploadSuccess"></drop-zone>
83 82
84 83
85 </div> 84 </div>
......
1 +<div class="image-picker" image-picker="{{$name}}" data-default-image="{{ $defaultImage }}" data-resize-height="{{ $resizeHeight }}" data-resize-width="{{ $resizeWidth }}" data-current-id="{{ $currentId or '' }}" data-resize-crop="{{ $resizeCrop or '' }}">
2 +
3 + <div>
4 + <img @if($currentImage && $currentImage !== 'none') src="{{$currentImage}}" @else src="{{$defaultImage}}" @endif class="{{$imageClass}} @if($currentImage=== 'none') none @endif" alt="{{ trans('components.image_preview') }}">
5 + </div>
6 +
7 + <button class="button" type="button" data-action="show-image-manager">{{ trans('components.image_select_image') }}</button>
8 + <br>
9 + <button class="text-button" data-action="reset-image" type="button">{{ trans('common.reset') }}</button>
10 +
11 + @if ($showRemove)
12 + <span class="sep">|</span>
13 + <button class="text-button neg" data-action="remove-image" type="button">{{ trans('common.remove') }}</button>
14 + @endif
15 +
16 + <input type="hidden" name="{{$name}}" id="{{$name}}" value="{{ isset($currentId) && ($currentId !== '' && $currentId !== false) ? $currentId : $currentImage}}">
17 +</div>
18 +
19 +<script>
20 + (function(){
21 +
22 + var picker = document.querySelector('[image-picker="{{$name}}"]');
23 + picker.addEventListener('click', function(event) {
24 + if (event.target.nodeName.toLowerCase() !== 'button') return;
25 + var button = event.target;
26 + var action = button.getAttribute('data-action');
27 + var resize = picker.getAttribute('data-resize-height') && picker.getAttribute('data-resize-width');
28 + var usingIds = picker.getAttribute('data-current-id') !== '';
29 + var resizeCrop = picker.getAttribute('data-resize-crop') !== '';
30 + var imageElem = picker.querySelector('img');
31 + var input = picker.querySelector('input');
32 +
33 + function setImage(image) {
34 + if (image === 'none') {
35 + imageElem.src = picker.getAttribute('data-default-image');
36 + imageElem.classList.add('none');
37 + input.value = 'none';
38 + return;
39 + }
40 + imageElem.src = image.url;
41 + input.value = usingIds ? image.id : image.url;
42 + imageElem.classList.remove('none');
43 + }
44 +
45 + if (action === 'show-image-manager') {
46 + window.ImageManager.showExternal((image) => {
47 + if (!resize) {
48 + setImage(image);
49 + return;
50 + }
51 + var requestString = '/images/thumb/' + image.id + '/' + picker.getAttribute('data-resize-width') + '/' + picker.getAttribute('data-resize-height') + '/' + (resizeCrop ? 'true' : 'false');
52 + $.get(window.baseUrl(requestString), resp => {
53 + image.url = resp.url;
54 + setImage(image);
55 + });
56 + });
57 + } else if (action === 'reset-image') {
58 + setImage({id: 0, url: picker.getAttribute('data-default-image')});
59 + } else if (action === 'remove-image') {
60 + setImage('none');
61 + }
62 +
63 + });
64 +
65 + })();
66 +</script>
...\ No newline at end of file ...\ No newline at end of file
1 +<div toggle-switch="{{$name}}" class="toggle-switch @if($value) active @endif">
2 + <input type="hidden" name="{{$name}}" value="{{$value?'true':'false'}}"/>
3 + <div class="switch-handle"></div>
4 +</div>
5 +<script>
6 + (function() {
7 + var toggle = document.querySelector('[toggle-switch="{{$name}}"]');
8 + var toggleInput = toggle.querySelector('input');
9 + toggle.onclick = function(event) {
10 + var checked = toggleInput.value !== 'true';
11 + toggleInput.value = checked ? 'true' : 'false';
12 + checked ? toggle.classList.add('active') : toggle.classList.remove('active');
13 + };
14 + })()
15 +</script>
...\ No newline at end of file ...\ No newline at end of file
...@@ -4,9 +4,28 @@ ...@@ -4,9 +4,28 @@
4 4
5 5
6 <div class="container"> 6 <div class="container">
7 - <h1 class="text-muted">{{ $message or 'Page Not Found' }}</h1> 7 +
8 - <p>Sorry, The page you were looking for could not be found.</p> 8 +
9 - <a href="{{ baseUrl('/') }}" class="button">Return To Home</a> 9 + <h1>{{ $message or trans('errors.404_page_not_found') }}</h1>
10 + <p>{{ trans('errors.sorry_page_not_found') }}</p>
11 + <p><a href="{{ baseUrl('/') }}" class="button">{{ trans('errors.return_home') }}</a></p>
12 +
13 + <hr>
14 +
15 + <div class="row">
16 + <div class="col-md-4">
17 + <h3 class="text-muted">{{ trans('entities.pages_popular') }}</h3>
18 + @include('partials.entity-list', ['entities' => Views::getPopular(10, 0, [\BookStack\Page::class]), 'style' => 'compact'])
19 + </div>
20 + <div class="col-md-4">
21 + <h3 class="text-muted">{{ trans('entities.books_popular') }}</h3>
22 + @include('partials.entity-list', ['entities' => Views::getPopular(10, 0, [\BookStack\Book::class]), 'style' => 'compact'])
23 + </div>
24 + <div class="col-md-4">
25 + <h3 class="text-muted">{{ trans('entities.chapters_popular') }}</h3>
26 + @include('partials.entity-list', ['entities' => Views::getPopular(10, 0, [\BookStack\Chapter::class]), 'style' => 'compact'])
27 + </div>
28 + </div>
10 </div> 29 </div>
11 30
12 @stop 31 @stop
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
3 @section('content') 3 @section('content')
4 4
5 <div class="container"> 5 <div class="container">
6 - <h1 class="text-muted">An Error Occurred</h1> 6 + <h1 class="text-muted">{{ trans('errors.error_occurred') }}</h1>
7 <p>{{ $message }}</p> 7 <p>{{ $message }}</p>
8 </div> 8 </div>
9 9
......
...@@ -3,8 +3,8 @@ ...@@ -3,8 +3,8 @@
3 @section('content') 3 @section('content')
4 4
5 <div class="container"> 5 <div class="container">
6 - <h1 class="text-muted">{{ setting('app-name') }} is down right now</h1> 6 + <h1 class="text-muted">{{ trans('errors.app_down', ['appName' => setting('app-name')]) }}</h1>
7 - <p>It will be back up soon.</p> 7 + <p>{{ trans('errors.back_soon') }}</p>
8 </div> 8 </div>
9 9
10 @stop 10 @stop
...\ No newline at end of file ...\ No newline at end of file
......
1 <form action="{{$url}}" method="POST" class="inline"> 1 <form action="{{$url}}" method="POST" class="inline">
2 {{ csrf_field() }} 2 {{ csrf_field() }}
3 <input type="hidden" name="_method" value="DELETE"> 3 <input type="hidden" name="_method" value="DELETE">
4 - <button type="submit" class="button neg">{{ isset($text) ? $text : 'Delete' }}</button> 4 + <button type="submit" class="button neg">{{ isset($text) ? $text : trans('common.delete') }}</button>
5 </form> 5 </form>
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -2,31 +2,31 @@ ...@@ -2,31 +2,31 @@
2 {!! csrf_field() !!} 2 {!! csrf_field() !!}
3 <input type="hidden" name="_method" value="PUT"> 3 <input type="hidden" name="_method" value="PUT">
4 4
5 - <p>Once enabled, These permissions will take priority over any set role permissions.</p> 5 + <p>{{ trans('entities.permissions_intro') }}</p>
6 6
7 <div class="form-group"> 7 <div class="form-group">
8 - @include('form/checkbox', ['name' => 'restricted', 'label' => 'Enable custom permissions']) 8 + @include('form/checkbox', ['name' => 'restricted', 'label' => trans('entities.permissions_enable')])
9 </div> 9 </div>
10 10
11 11
12 <table class="table"> 12 <table class="table">
13 <tr> 13 <tr>
14 - <th>Role</th> 14 + <th>{{ trans('common.role') }}</th>
15 - <th @if($model->isA('page')) colspan="3" @else colspan="4" @endif>Actions</th> 15 + <th @if($model->isA('page')) colspan="3" @else colspan="4" @endif>{{ trans('common.actions') }}</th>
16 </tr> 16 </tr>
17 @foreach($roles as $role) 17 @foreach($roles as $role)
18 <tr> 18 <tr>
19 <td>{{ $role->display_name }}</td> 19 <td>{{ $role->display_name }}</td>
20 - <td>@include('form/restriction-checkbox', ['name'=>'restrictions', 'label' => 'View', 'action' => 'view'])</td> 20 + <td>@include('form/restriction-checkbox', ['name'=>'restrictions', 'label' => trans('common.view'), 'action' => 'view'])</td>
21 @if(!$model->isA('page')) 21 @if(!$model->isA('page'))
22 - <td>@include('form/restriction-checkbox', ['name'=>'restrictions', 'label' => 'Create', 'action' => 'create'])</td> 22 + <td>@include('form/restriction-checkbox', ['name'=>'restrictions', 'label' => trans('common.create'), 'action' => 'create'])</td>
23 @endif 23 @endif
24 - <td>@include('form/restriction-checkbox', ['name'=>'restrictions', 'label' => 'Update', 'action' => 'update'])</td> 24 + <td>@include('form/restriction-checkbox', ['name'=>'restrictions', 'label' => trans('common.update'), 'action' => 'update'])</td>
25 - <td>@include('form/restriction-checkbox', ['name'=>'restrictions', 'label' => 'Delete', 'action' => 'delete'])</td> 25 + <td>@include('form/restriction-checkbox', ['name'=>'restrictions', 'label' => trans('common.delete'), 'action' => 'delete'])</td>
26 </tr> 26 </tr>
27 @endforeach 27 @endforeach
28 </table> 28 </table>
29 29
30 - <a href="{{ $model->getUrl() }}" class="button muted">Cancel</a> 30 + <a href="{{ $model->getUrl() }}" class="button muted">{{ trans('common.cancel') }}</a>
31 - <button type="submit" class="button pos">Save Permissions</button> 31 + <button type="submit" class="button pos">{{ trans('entities.permissions_save') }}</button>
32 </form> 32 </form>
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -5,14 +5,9 @@ ...@@ -5,14 +5,9 @@
5 <div class="faded-small toolbar"> 5 <div class="faded-small toolbar">
6 <div class="container"> 6 <div class="container">
7 <div class="row"> 7 <div class="row">
8 - <div class="col-sm-4 faded"> 8 + <div class="col-sm-6 faded">
9 <div class="action-buttons text-left"> 9 <div class="action-buttons text-left">
10 - <a data-action="expand-entity-list-details" class="text-primary text-button"><i class="zmdi zmdi-wrap-text"></i>Toggle Details</a> 10 + <a data-action="expand-entity-list-details" class="text-primary text-button"><i class="zmdi zmdi-wrap-text"></i>{{ trans('common.toggle_details') }}</a>
11 - </div>
12 - </div>
13 - <div class="col-sm-8 faded">
14 - <div class="action-buttons">
15 -
16 </div> 11 </div>
17 </div> 12 </div>
18 </div> 13 </div>
...@@ -25,44 +20,44 @@ ...@@ -25,44 +20,44 @@
25 <div class="col-sm-4"> 20 <div class="col-sm-4">
26 <div id="recent-drafts"> 21 <div id="recent-drafts">
27 @if(count($draftPages) > 0) 22 @if(count($draftPages) > 0)
28 - <h4>My Recent Drafts</h4> 23 + <h4>{{ trans('entities.my_recent_drafts') }}</h4>
29 @include('partials/entity-list', ['entities' => $draftPages, 'style' => 'compact']) 24 @include('partials/entity-list', ['entities' => $draftPages, 'style' => 'compact'])
30 @endif 25 @endif
31 </div> 26 </div>
32 @if($signedIn) 27 @if($signedIn)
33 - <h4>My Recently Viewed</h4> 28 + <h4>{{ trans('entities.my_recently_viewed') }}</h4>
34 @else 29 @else
35 - <h4>Recent Books</h4> 30 + <h4>{{ trans('entities.books_recent') }}</h4>
36 @endif 31 @endif
37 @include('partials/entity-list', [ 32 @include('partials/entity-list', [
38 'entities' => $recents, 33 'entities' => $recents,
39 'style' => 'compact', 34 'style' => 'compact',
40 - 'emptyText' => $signedIn ? 'You have not viewed any pages' : 'No books have been created' 35 + 'emptyText' => $signedIn ? trans('entities.no_pages_viewed') : trans('entities.books_empty')
41 ]) 36 ])
42 </div> 37 </div>
43 38
44 <div class="col-sm-4"> 39 <div class="col-sm-4">
45 - <h4><a class="no-color" href="{{ baseUrl("/pages/recently-created") }}">Recently Created Pages</a></h4> 40 + <h4><a class="no-color" href="{{ baseUrl("/pages/recently-created") }}">{{ trans('entities.recently_created_pages') }}</a></h4>
46 <div id="recently-created-pages"> 41 <div id="recently-created-pages">
47 @include('partials/entity-list', [ 42 @include('partials/entity-list', [
48 'entities' => $recentlyCreatedPages, 43 'entities' => $recentlyCreatedPages,
49 'style' => 'compact', 44 'style' => 'compact',
50 - 'emptyText' => 'No pages have been recently created' 45 + 'emptyText' => trans('entities.no_pages_recently_created')
51 ]) 46 ])
52 </div> 47 </div>
53 48
54 - <h4><a class="no-color" href="{{ baseUrl("/pages/recently-updated") }}">Recently Updated Pages</a></h4> 49 + <h4><a class="no-color" href="{{ baseUrl("/pages/recently-updated") }}">{{ trans('entities.recently_updated_pages') }}</a></h4>
55 <div id="recently-updated-pages"> 50 <div id="recently-updated-pages">
56 @include('partials/entity-list', [ 51 @include('partials/entity-list', [
57 'entities' => $recentlyUpdatedPages, 52 'entities' => $recentlyUpdatedPages,
58 'style' => 'compact', 53 'style' => 'compact',
59 - 'emptyText' => 'No pages have been recently updated' 54 + 'emptyText' => trans('entities.no_pages_recently_updated')
60 ]) 55 ])
61 </div> 56 </div>
62 </div> 57 </div>
63 58
64 <div class="col-sm-4" id="recent-activity"> 59 <div class="col-sm-4" id="recent-activity">
65 - <h4>Recent Activity</h4> 60 + <h4>{{ trans('entities.recent_activity') }}</h4>
66 @include('partials/activity-list', ['activity' => $activity]) 61 @include('partials/activity-list', ['activity' => $activity])
67 </div> 62 </div>
68 63
......
1 +<div class="breadcrumbs">
2 + <a href="{{ $page->book->getUrl() }}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $page->book->getShortName() }}</a>
3 + @if($page->hasChapter())
4 + <span class="sep">&raquo;</span>
5 + <a href="{{ $page->chapter->getUrl() }}" class="text-chapter text-button">
6 + <i class="zmdi zmdi-collection-bookmark"></i>
7 + {{ $page->chapter->getShortName() }}
8 + </a>
9 + @endif
10 + <span class="sep">&raquo;</span>
11 + <a href="{{ $page->getUrl() }}" class="text-page text-button"><i class="zmdi zmdi-file"></i>{{ $page->getShortName() }}</a>
12 +</div>
...\ No newline at end of file ...\ No newline at end of file
...@@ -2,15 +2,25 @@ ...@@ -2,15 +2,25 @@
2 2
3 @section('content') 3 @section('content')
4 4
5 + <div class="faded-small toolbar">
6 + <div class="container">
7 + <div class="row">
8 + <div class="col-sm-12 faded">
9 + @include('pages._breadcrumbs', ['page' => $page])
10 + </div>
11 + </div>
12 + </div>
13 + </div>
14 +
5 <div class="container small" ng-non-bindable> 15 <div class="container small" ng-non-bindable>
6 - <h1>Delete {{ $page->draft ? 'Draft' : '' }} Page</h1> 16 + <h1>{{ $page->draft ? trans('entities.pages_delete_draft') : trans('entities.pages_delete') }}</h1>
7 - <p class="text-neg">Are you sure you want to delete this {{ $page->draft ? 'draft' : '' }} page?</p> 17 + <p class="text-neg">{{ $page->draft ? trans('entities.pages_delete_draft_confirm'): trans('entities.pages_delete_confirm') }}</p>
8 18
9 <form action="{{ $page->getUrl() }}" method="POST"> 19 <form action="{{ $page->getUrl() }}" method="POST">
10 {!! csrf_field() !!} 20 {!! csrf_field() !!}
11 <input type="hidden" name="_method" value="DELETE"> 21 <input type="hidden" name="_method" value="DELETE">
12 - <a href="{{ $page->getUrl() }}" class="button primary">Cancel</a> 22 + <a href="{{ $page->getUrl() }}" class="button primary">{{ trans('common.cancel') }}</a>
13 - <button type="submit" class="button neg">Confirm</button> 23 + <button type="submit" class="button neg">{{ trans('common.confirm') }}</button>
14 </form> 24 </form>
15 </div> 25 </div>
16 26
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
20 20
21 </div> 21 </div>
22 22
23 - @include('partials/image-manager', ['imageType' => 'gallery', 'uploaded_to' => $page->id]) 23 + @include('components.image-manager', ['imageType' => 'gallery', 'uploaded_to' => $page->id])
24 - @include('partials/entity-selector-popup') 24 + @include('components.entity-selector-popup')
25 25
26 @stop 26 @stop
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -15,15 +15,11 @@ ...@@ -15,15 +15,11 @@
15 <div class="col-md-8 col-md-offset-2"> 15 <div class="col-md-8 col-md-offset-2">
16 <div class="page-content"> 16 <div class="page-content">
17 17
18 - @include('pages/page-display') 18 + @include('pages.page-display')
19 19
20 <hr> 20 <hr>
21 21
22 - <p class="text-muted small"> 22 + @include('partials.entity-meta', ['entity' => $page])
23 - Created {{$page->created_at->toDayDateTimeString()}} @if($page->createdBy) by {{$page->createdBy->name}} @endif
24 - <br>
25 - Last Updated {{$page->updated_at->toDayDateTimeString()}} @if($page->updatedBy) by {{$page->updatedBy->name}} @endif
26 - </p>
27 23
28 </div> 24 </div>
29 </div> 25 </div>
......
...@@ -3,22 +3,22 @@ ...@@ -3,22 +3,22 @@
3 3
4 <div class="tabs primary-background-light"> 4 <div class="tabs primary-background-light">
5 <span toolbox-toggle><i class="zmdi zmdi-caret-left-circle"></i></span> 5 <span toolbox-toggle><i class="zmdi zmdi-caret-left-circle"></i></span>
6 - <span toolbox-tab-button="tags" title="Page Tags" class="active"><i class="zmdi zmdi-tag"></i></span> 6 + <span toolbox-tab-button="tags" title="{{ trans('entities.page_tags') }}" class="active"><i class="zmdi zmdi-tag"></i></span>
7 @if(userCan('attachment-create-all')) 7 @if(userCan('attachment-create-all'))
8 - <span toolbox-tab-button="files" title="Attachments"><i class="zmdi zmdi-attachment"></i></span> 8 + <span toolbox-tab-button="files" title="{{ trans('entities.attachments') }}"><i class="zmdi zmdi-attachment"></i></span>
9 @endif 9 @endif
10 </div> 10 </div>
11 11
12 <div toolbox-tab-content="tags" ng-controller="PageTagController" page-id="{{ $page->id or 0 }}"> 12 <div toolbox-tab-content="tags" ng-controller="PageTagController" page-id="{{ $page->id or 0 }}">
13 - <h4>Page Tags</h4> 13 + <h4>{{ trans('entities.page_tags') }}</h4>
14 <div class="padded tags"> 14 <div class="padded tags">
15 - <p class="muted small">Add some tags to better categorise your content. <br> You can assign a value to a tag for more in-depth organisation.</p> 15 + <p class="muted small">{!! nl2br(e(trans('entities.tags_explain'))) !!}</p>
16 <table class="no-style" tag-autosuggestions style="width: 100%;"> 16 <table class="no-style" tag-autosuggestions style="width: 100%;">
17 <tbody ui-sortable="sortOptions" ng-model="tags" > 17 <tbody ui-sortable="sortOptions" ng-model="tags" >
18 <tr ng-repeat="tag in tags track by $index"> 18 <tr ng-repeat="tag in tags track by $index">
19 <td width="20" ><i class="handle zmdi zmdi-menu"></i></td> 19 <td width="20" ><i class="handle zmdi zmdi-menu"></i></td>
20 - <td><input autosuggest="{{ baseUrl('/ajax/tags/suggest/names') }}" autosuggest-type="name" class="outline" ng-attr-name="tags[@{{$index}}][name]" type="text" ng-model="tag.name" ng-change="tagChange(tag)" ng-blur="tagBlur(tag)" placeholder="Tag"></td> 20 + <td><input autosuggest="{{ baseUrl('/ajax/tags/suggest/names') }}" autosuggest-type="name" class="outline" ng-attr-name="tags[@{{$index}}][name]" type="text" ng-model="tag.name" ng-change="tagChange(tag)" ng-blur="tagBlur(tag)" placeholder="{{ trans('entities.tag') }}"></td>
21 - <td><input autosuggest="{{ baseUrl('/ajax/tags/suggest/values') }}" autosuggest-type="value" class="outline" ng-attr-name="tags[@{{$index}}][value]" type="text" ng-model="tag.value" ng-change="tagChange(tag)" ng-blur="tagBlur(tag)" placeholder="Tag Value (Optional)"></td> 21 + <td><input autosuggest="{{ baseUrl('/ajax/tags/suggest/values') }}" autosuggest-type="value" class="outline" ng-attr-name="tags[@{{$index}}][value]" type="text" ng-model="tag.value" ng-change="tagChange(tag)" ng-blur="tagBlur(tag)" placeholder="{{ trans('entities.tag_value') }}"></td>
22 <td width="10" ng-show="tags.length != 1" class="text-center text-neg" style="padding: 0;" ng-click="removeTag(tag)"><i class="zmdi zmdi-close"></i></td> 22 <td width="10" ng-show="tags.length != 1" class="text-center text-neg" style="padding: 0;" ng-click="removeTag(tag)"><i class="zmdi zmdi-close"></i></td>
23 </tr> 23 </tr>
24 </tbody> 24 </tbody>
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
28 <tr class="unsortable"> 28 <tr class="unsortable">
29 <td width="34"></td> 29 <td width="34"></td>
30 <td ng-click="addEmptyTag()"> 30 <td ng-click="addEmptyTag()">
31 - <button type="button" class="text-button">Add another tag</button> 31 + <button type="button" class="text-button">{{ trans('entities.tags_add') }}</button>
32 </td> 32 </td>
33 <td></td> 33 <td></td>
34 </tr> 34 </tr>
...@@ -39,17 +39,17 @@ ...@@ -39,17 +39,17 @@
39 39
40 @if(userCan('attachment-create-all')) 40 @if(userCan('attachment-create-all'))
41 <div toolbox-tab-content="files" ng-controller="PageAttachmentController" page-id="{{ $page->id or 0 }}"> 41 <div toolbox-tab-content="files" ng-controller="PageAttachmentController" page-id="{{ $page->id or 0 }}">
42 - <h4>Attachments</h4> 42 + <h4>{{ trans('entities.attachments') }}</h4>
43 <div class="padded files"> 43 <div class="padded files">
44 44
45 <div id="file-list" ng-show="!editFile"> 45 <div id="file-list" ng-show="!editFile">
46 - <p class="muted small">Upload some files or attach some link to display on your page. These are visible in the page sidebar. <span class="secondary">Changes here are saved instantly.</span></p> 46 + <p class="muted small">{{ trans('entities.attachments_explain') }} <span class="secondary">{{ trans('entities.attachments_explain_instant_save') }}</span></p>
47 47
48 <div tab-container> 48 <div tab-container>
49 <div class="nav-tabs"> 49 <div class="nav-tabs">
50 - <div tab-button="list" class="tab-item">Attached Items</div> 50 + <div tab-button="list" class="tab-item">{{ trans('entities.attachments_items') }}</div>
51 - <div tab-button="file" class="tab-item">Upload File</div> 51 + <div tab-button="file" class="tab-item">{{ trans('entities.attachments_upload') }}</div>
52 - <div tab-button="link" class="tab-item">Attach Link</div> 52 + <div tab-button="link" class="tab-item">{{ trans('entities.attachments_link') }}</div>
53 </div> 53 </div>
54 <div tab-content="list"> 54 <div tab-content="list">
55 <table class="file-table" style="width: 100%;"> 55 <table class="file-table" style="width: 100%;">
...@@ -59,9 +59,9 @@ ...@@ -59,9 +59,9 @@
59 <td> 59 <td>
60 <a ng-href="@{{getFileUrl(file)}}" target="_blank" ng-bind="file.name"></a> 60 <a ng-href="@{{getFileUrl(file)}}" target="_blank" ng-bind="file.name"></a>
61 <div ng-if="file.deleting"> 61 <div ng-if="file.deleting">
62 - <span class="neg small">Click delete again to confirm you want to delete this attachment.</span> 62 + <span class="neg small">{{ trans('entities.attachments_delete_confirm') }}</span>
63 <br> 63 <br>
64 - <span class="text-primary small" ng-click="file.deleting=false;">Cancel</span> 64 + <span class="text-primary small" ng-click="file.deleting=false;">{{ trans('common.cancel') }}</span>
65 </div> 65 </div>
66 </td> 66 </td>
67 <td width="10" ng-click="startEdit(file)" class="text-center text-primary" style="padding: 0;"><i class="zmdi zmdi-edit"></i></td> 67 <td width="10" ng-click="startEdit(file)" class="text-center text-primary" style="padding: 0;"><i class="zmdi zmdi-edit"></i></td>
...@@ -71,25 +71,25 @@ ...@@ -71,25 +71,25 @@
71 </tbody> 71 </tbody>
72 </table> 72 </table>
73 <p class="small muted" ng-if="files.length == 0"> 73 <p class="small muted" ng-if="files.length == 0">
74 - No files have been uploaded. 74 + {{ trans('entities.attachments_no_files') }}
75 </p> 75 </p>
76 </div> 76 </div>
77 <div tab-content="file"> 77 <div tab-content="file">
78 - <drop-zone upload-url="@{{getUploadUrl()}}" uploaded-to="@{{uploadedTo}}" event-success="uploadSuccess"></drop-zone> 78 + <drop-zone message="{{ trans('entities.attachments_dropzone') }}" upload-url="@{{getUploadUrl()}}" uploaded-to="@{{uploadedTo}}" event-success="uploadSuccess"></drop-zone>
79 </div> 79 </div>
80 <div tab-content="link" sub-form="attachLinkSubmit(file)"> 80 <div tab-content="link" sub-form="attachLinkSubmit(file)">
81 - <p class="muted small">You can attach a link if you'd prefer not to upload a file. This can be a link to another page or a link to a file in the cloud.</p> 81 + <p class="muted small">{{ trans('entities.attachments_explain_link') }}</p>
82 <div class="form-group"> 82 <div class="form-group">
83 - <label for="attachment-via-link">Link Name</label> 83 + <label for="attachment-via-link">{{ trans('entities.attachments_link_name') }}</label>
84 - <input type="text" placeholder="Link name" ng-model="file.name"> 84 + <input type="text" placeholder="{{ trans('entities.attachments_link_name') }}" ng-model="file.name">
85 <p class="small neg" ng-repeat="error in errors.link.name" ng-bind="error"></p> 85 <p class="small neg" ng-repeat="error in errors.link.name" ng-bind="error"></p>
86 </div> 86 </div>
87 <div class="form-group"> 87 <div class="form-group">
88 - <label for="attachment-via-link">Link to file</label> 88 + <label for="attachment-via-link">{{ trans('entities.attachments_link_url') }}</label>
89 - <input type="text" placeholder="Url of site or file" ng-model="file.link"> 89 + <input type="text" placeholder="{{ trans('entities.attachments_link_url_hint') }}" ng-model="file.link">
90 <p class="small neg" ng-repeat="error in errors.link.link" ng-bind="error"></p> 90 <p class="small neg" ng-repeat="error in errors.link.link" ng-bind="error"></p>
91 </div> 91 </div>
92 - <button type="submit" class="button pos">Attach</button> 92 + <button type="submit" class="button pos">{{ trans('entities.attach') }}</button>
93 93
94 </div> 94 </div>
95 </div> 95 </div>
...@@ -97,34 +97,34 @@ ...@@ -97,34 +97,34 @@
97 </div> 97 </div>
98 98
99 <div id="file-edit" ng-if="editFile" sub-form="updateFile(editFile)"> 99 <div id="file-edit" ng-if="editFile" sub-form="updateFile(editFile)">
100 - <h5>Edit File</h5> 100 + <h5>{{ trans('entities.attachments_edit_file') }}</h5>
101 101
102 <div class="form-group"> 102 <div class="form-group">
103 - <label for="attachment-name-edit">File Name</label> 103 + <label for="attachment-name-edit">{{ trans('entities.attachments_edit_file_name') }}</label>
104 - <input type="text" id="attachment-name-edit" placeholder="File name" ng-model="editFile.name"> 104 + <input type="text" id="attachment-name-edit" placeholder="{{ trans('entities.attachments_edit_file_name') }}" ng-model="editFile.name">
105 <p class="small neg" ng-repeat="error in errors.edit.name" ng-bind="error"></p> 105 <p class="small neg" ng-repeat="error in errors.edit.name" ng-bind="error"></p>
106 </div> 106 </div>
107 107
108 <div tab-container="@{{ editFile.external ? 'link' : 'file' }}"> 108 <div tab-container="@{{ editFile.external ? 'link' : 'file' }}">
109 <div class="nav-tabs"> 109 <div class="nav-tabs">
110 - <div tab-button="file" class="tab-item">Upload File</div> 110 + <div tab-button="file" class="tab-item">{{ trans('entities.attachments_upload') }}</div>
111 - <div tab-button="link" class="tab-item">Set Link</div> 111 + <div tab-button="link" class="tab-item">{{ trans('entities.attachments_set_link') }}</div>
112 </div> 112 </div>
113 <div tab-content="file"> 113 <div tab-content="file">
114 - <drop-zone upload-url="@{{getUploadUrl(editFile)}}" uploaded-to="@{{uploadedTo}}" placeholder="Drop files or click here to upload and overwrite" event-success="uploadSuccessUpdate"></drop-zone> 114 + <drop-zone upload-url="@{{getUploadUrl(editFile)}}" uploaded-to="@{{uploadedTo}}" placeholder="{{ trans('entities.attachments_edit_drop_upload') }}" event-success="uploadSuccessUpdate"></drop-zone>
115 <br> 115 <br>
116 </div> 116 </div>
117 <div tab-content="link"> 117 <div tab-content="link">
118 <div class="form-group"> 118 <div class="form-group">
119 - <label for="attachment-link-edit">Link to file</label> 119 + <label for="attachment-link-edit">{{ trans('entities.attachments_link_url') }}</label>
120 - <input type="text" id="attachment-link-edit" placeholder="Attachment link" ng-model="editFile.link"> 120 + <input type="text" id="attachment-link-edit" placeholder="{{ trans('entities.attachment_link') }}" ng-model="editFile.link">
121 <p class="small neg" ng-repeat="error in errors.edit.link" ng-bind="error"></p> 121 <p class="small neg" ng-repeat="error in errors.edit.link" ng-bind="error"></p>
122 </div> 122 </div>
123 </div> 123 </div>
124 </div> 124 </div>
125 125
126 - <button type="button" class="button" ng-click="cancelEdit()">Back</button> 126 + <button type="button" class="button" ng-click="cancelEdit()">{{ trans('common.back') }}</button>
127 - <button type="submit" class="button pos">Save</button> 127 + <button type="submit" class="button pos">{{ trans('common.save') }}</button>
128 </div> 128 </div>
129 129
130 </div> 130 </div>
......
...@@ -9,8 +9,8 @@ ...@@ -9,8 +9,8 @@
9 <div class="row"> 9 <div class="row">
10 <div class="col-sm-4 faded"> 10 <div class="col-sm-4 faded">
11 <div class="action-buttons text-left"> 11 <div class="action-buttons text-left">
12 - <a href="{{ back()->getTargetUrl() }}" class="text-button text-primary"><i class="zmdi zmdi-arrow-left"></i>Back</a> 12 + <a href="{{ back()->getTargetUrl() }}" class="text-button text-primary"><i class="zmdi zmdi-arrow-left"></i>{{ trans('common.back') }}</a>
13 - <a onclick="$('body>header').slideToggle();" class="text-button text-primary"><i class="zmdi zmdi-swap-vertical"></i>Toggle Header</a> 13 + <a onclick="$('body>header').slideToggle();" class="text-button text-primary"><i class="zmdi zmdi-swap-vertical"></i>{{ trans('entities.pages_edit_toggle_header') }}</a>
14 </div> 14 </div>
15 </div> 15 </div>
16 <div class="col-sm-4 faded text-center"> 16 <div class="col-sm-4 faded text-center">
...@@ -20,13 +20,13 @@ ...@@ -20,13 +20,13 @@
20 <i class="zmdi zmdi-check-circle text-pos draft-notification" ng-class="{visible: draftUpdated}"></i> 20 <i class="zmdi zmdi-check-circle text-pos draft-notification" ng-class="{visible: draftUpdated}"></i>
21 <ul> 21 <ul>
22 <li> 22 <li>
23 - <a ng-click="forceDraftSave()" class="text-pos"><i class="zmdi zmdi-save"></i>Save Draft</a> 23 + <a ng-click="forceDraftSave()" class="text-pos"><i class="zmdi zmdi-save"></i>{{ trans('entities.pages_edit_save_draft') }}</a>
24 </li> 24 </li>
25 <li ng-if="isNewPageDraft"> 25 <li ng-if="isNewPageDraft">
26 - <a href="{{ $model->getUrl('/delete') }}" class="text-neg"><i class="zmdi zmdi-delete"></i>Delete Draft</a> 26 + <a href="{{ $model->getUrl('/delete') }}" class="text-neg"><i class="zmdi zmdi-delete"></i>{{ trans('entities.pages_edit_delete_draft') }}</a>
27 </li> 27 </li>
28 <li> 28 <li>
29 - <a type="button" ng-if="isUpdateDraft" ng-click="discardDraft()" class="text-neg"><i class="zmdi zmdi-close-circle"></i>Discard Draft</a> 29 + <a type="button" ng-if="isUpdateDraft" ng-click="discardDraft()" class="text-neg"><i class="zmdi zmdi-close-circle"></i>{{ trans('entities.pages_edit_discard_draft') }}</a>
30 </li> 30 </li>
31 </ul> 31 </ul>
32 </div> 32 </div>
...@@ -34,16 +34,16 @@ ...@@ -34,16 +34,16 @@
34 <div class="col-sm-4 faded"> 34 <div class="col-sm-4 faded">
35 <div class="action-buttons" ng-cloak> 35 <div class="action-buttons" ng-cloak>
36 <div dropdown class="dropdown-container"> 36 <div dropdown class="dropdown-container">
37 - <a dropdown-toggle class="text-primary text-button"><i class="zmdi zmdi-edit"></i> @{{(changeSummary | limitTo:16) + (changeSummary.length>16?'...':'') || 'Set Changelog'}}</a> 37 + <a dropdown-toggle class="text-primary text-button"><i class="zmdi zmdi-edit"></i> <span ng-bind="(changeSummary | limitTo:16) + (changeSummary.length>16?'...':'') || '{{ trans('entities.pages_edit_set_changelog') }}'"></span></a>
38 <ul class="wide"> 38 <ul class="wide">
39 <li class="padded"> 39 <li class="padded">
40 - <p class="text-muted">Enter a brief description of the changes you've made</p> 40 + <p class="text-muted">{{ trans('entities.pages_edit_enter_changelog_desc') }}</p>
41 - <input name="summary" id="summary-input" type="text" placeholder="Enter Changelog" ng-model="changeSummary" /> 41 + <input name="summary" id="summary-input" type="text" placeholder="{{ trans('entities.pages_edit_enter_changelog') }}" ng-model="changeSummary" />
42 </li> 42 </li>
43 </ul> 43 </ul>
44 </div> 44 </div>
45 45
46 - <button type="submit" id="save-button" class="text-button text-pos"><i class="zmdi zmdi-floppy"></i>Save Page</button> 46 + <button type="submit" id="save-button" class="text-button text-pos"><i class="zmdi zmdi-floppy"></i>{{ trans('entities.pages_save') }}</button>
47 </div> 47 </div>
48 </div> 48 </div>
49 </div> 49 </div>
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
53 {{--Title input--}} 53 {{--Title input--}}
54 <div class="title-input page-title clearfix" ng-non-bindable> 54 <div class="title-input page-title clearfix" ng-non-bindable>
55 <div class="input"> 55 <div class="input">
56 - @include('form/text', ['name' => 'name', 'placeholder' => 'Page Title']) 56 + @include('form/text', ['name' => 'name', 'placeholder' => trans('entities.pages_title')])
57 </div> 57 </div>
58 </div> 58 </div>
59 59
...@@ -78,11 +78,11 @@ ...@@ -78,11 +78,11 @@
78 78
79 <div class="markdown-editor-wrap"> 79 <div class="markdown-editor-wrap">
80 <div class="editor-toolbar"> 80 <div class="editor-toolbar">
81 - <span class="float left">Editor</span> 81 + <span class="float left">{{ trans('entities.pages_md_editor') }}</span>
82 <div class="float right buttons"> 82 <div class="float right buttons">
83 - <button class="text-button" type="button" data-action="insertImage"><i class="zmdi zmdi-image"></i>Insert Image</button> 83 + <button class="text-button" type="button" data-action="insertImage"><i class="zmdi zmdi-image"></i>{{ trans('entities.pages_md_insert_image') }}</button>
84 &nbsp;|&nbsp; 84 &nbsp;|&nbsp;
85 - <button class="text-button" type="button" data-action="insertEntityLink"><i class="zmdi zmdi-link"></i>Insert Entity Link</button> 85 + <button class="text-button" type="button" data-action="insertEntityLink"><i class="zmdi zmdi-link"></i>{{ trans('entities.pages_md_insert_link') }}</button>
86 </div> 86 </div>
87 </div> 87 </div>
88 88
...@@ -95,7 +95,7 @@ ...@@ -95,7 +95,7 @@
95 95
96 <div class="markdown-editor-wrap"> 96 <div class="markdown-editor-wrap">
97 <div class="editor-toolbar"> 97 <div class="editor-toolbar">
98 - <div class="">Preview</div> 98 + <div class="">{{ trans('entities.pages_md_preview') }}</div>
99 </div> 99 </div>
100 <div class="markdown-display"> 100 <div class="markdown-display">
101 <div class="page-content" ng-bind-html="displayContent"></div> 101 <div class="page-content" ng-bind-html="displayContent"></div>
......
...@@ -3,19 +3,19 @@ ...@@ -3,19 +3,19 @@
3 @section('content') 3 @section('content')
4 4
5 <div class="container small" ng-non-bindable> 5 <div class="container small" ng-non-bindable>
6 - <h1>Create Page</h1> 6 + <h1>{{ trans('entities.pages_new') }}</h1>
7 <form action="{{ $parent->getUrl('/page/create/guest') }}" method="POST"> 7 <form action="{{ $parent->getUrl('/page/create/guest') }}" method="POST">
8 8
9 {!! csrf_field() !!} 9 {!! csrf_field() !!}
10 10
11 <div class="form-group title-input"> 11 <div class="form-group title-input">
12 - <label for="name">Page Name</label> 12 + <label for="name">{{ trans('entities.pages_name') }}</label>
13 @include('form/text', ['name' => 'name']) 13 @include('form/text', ['name' => 'name'])
14 </div> 14 </div>
15 15
16 <div class="form-group"> 16 <div class="form-group">
17 - <a href="{{ $parent->getUrl() }}" class="button muted">Cancel</a> 17 + <a href="{{ $parent->getUrl() }}" class="button muted">{{ trans('common.cancel') }}</a>
18 - <button type="submit" class="button pos">Continue</button> 18 + <button type="submit" class="button pos">{{ trans('common.continue') }}</button>
19 </div> 19 </div>
20 20
21 </form> 21 </form>
......
...@@ -12,8 +12,7 @@ ...@@ -12,8 +12,7 @@
12 @if(isset($style) && $style === 'detailed') 12 @if(isset($style) && $style === 'detailed')
13 <div class="row meta text-muted text-small"> 13 <div class="row meta text-muted text-small">
14 <div class="col-md-6"> 14 <div class="col-md-6">
15 - Created {{$page->created_at->diffForHumans()}} @if($page->createdBy)by {{$page->createdBy->name}}@endif <br> 15 + @include('partials.entity-meta', ['entity' => $page])
16 - Last updated {{ $page->updated_at->diffForHumans() }} @if($page->updatedBy)by {{$page->updatedBy->name}} @endif
17 </div> 16 </div>
18 <div class="col-md-6"> 17 <div class="col-md-6">
19 <a class="text-book" href="{{ $page->book->getUrl() }}"><i class="zmdi zmdi-book"></i>{{ $page->book->getShortName(30) }}</a> 18 <a class="text-book" href="{{ $page->book->getUrl() }}"><i class="zmdi zmdi-book"></i>{{ $page->book->getShortName(30) }}</a>
...@@ -21,7 +20,7 @@ ...@@ -21,7 +20,7 @@
21 @if($page->chapter) 20 @if($page->chapter)
22 <a class="text-chapter" href="{{ $page->chapter->getUrl() }}"><i class="zmdi zmdi-collection-bookmark"></i>{{ $page->chapter->getShortName(30) }}</a> 21 <a class="text-chapter" href="{{ $page->chapter->getUrl() }}"><i class="zmdi zmdi-collection-bookmark"></i>{{ $page->chapter->getShortName(30) }}</a>
23 @else 22 @else
24 - <i class="zmdi zmdi-collection-bookmark"></i> Page is not in a chapter 23 + <i class="zmdi zmdi-collection-bookmark"></i> {{ trans('entities.pages_not_in_chapter') }}
25 @endif 24 @endif
26 </div> 25 </div>
27 </div> 26 </div>
......
...@@ -6,34 +6,23 @@ ...@@ -6,34 +6,23 @@
6 <div class="container"> 6 <div class="container">
7 <div class="row"> 7 <div class="row">
8 <div class="col-sm-12 faded"> 8 <div class="col-sm-12 faded">
9 - <div class="breadcrumbs"> 9 + @include('pages._breadcrumbs', ['page' => $page])
10 - <a href="{{ $book->getUrl() }}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $book->getShortName() }}</a>
11 - @if($page->hasChapter())
12 - <span class="sep">&raquo;</span>
13 - <a href="{{ $page->chapter->getUrl() }}" class="text-chapter text-button">
14 - <i class="zmdi zmdi-collection-bookmark"></i>
15 - {{ $page->chapter->getShortName() }}
16 - </a>
17 - @endif
18 - <span class="sep">&raquo;</span>
19 - <a href="{{ $page->getUrl() }}" class="text-page text-button"><i class="zmdi zmdi-file-text"></i>{{ $page->getShortName() }}</a>
20 - </div>
21 </div> 10 </div>
22 </div> 11 </div>
23 </div> 12 </div>
24 </div> 13 </div>
25 14
26 <div class="container"> 15 <div class="container">
27 - <h1>Move Page <small class="subheader">{{$page->name}}</small></h1> 16 + <h1>{{ trans('entities.pages_move') }}</h1>
28 17
29 <form action="{{ $page->getUrl('/move') }}" method="POST"> 18 <form action="{{ $page->getUrl('/move') }}" method="POST">
30 {!! csrf_field() !!} 19 {!! csrf_field() !!}
31 <input type="hidden" name="_method" value="PUT"> 20 <input type="hidden" name="_method" value="PUT">
32 21
33 - @include('partials/entity-selector', ['name' => 'entity_selection', 'selectorSize' => 'large', 'entityTypes' => 'book,chapter']) 22 + @include('components.entity-selector', ['name' => 'entity_selection', 'selectorSize' => 'large', 'entityTypes' => 'book,chapter'])
34 23
35 - <a href="{{ $page->getUrl() }}" class="button muted">Cancel</a> 24 + <a href="{{ $page->getUrl() }}" class="button muted">{{ trans('common.cancel') }}</a>
36 - <button type="submit" class="button pos">Move Page</button> 25 + <button type="submit" class="button pos">{{ trans('entities.pages_move') }}</button>
37 </form> 26 </form>
38 </div> 27 </div>
39 28
......
...@@ -7,6 +7,6 @@ ...@@ -7,6 +7,6 @@
7 @if (isset($diff) && $diff) 7 @if (isset($diff) && $diff)
8 {!! $diff !!} 8 {!! $diff !!}
9 @else 9 @else
10 - {!! $page->html !!} 10 + {!! isset($pageContent) ? $pageContent : $page->html !!}
11 @endif 11 @endif
12 </div> 12 </div>
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -36,6 +36,5 @@ ...@@ -36,6 +36,5 @@
36 max-width: none; 36 max-width: none;
37 display: none; 37 display: none;
38 } 38 }
39 -
40 </style> 39 </style>
41 @stop 40 @stop
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -6,26 +6,15 @@ ...@@ -6,26 +6,15 @@
6 <div class="container"> 6 <div class="container">
7 <div class="row"> 7 <div class="row">
8 <div class="col-sm-12 faded"> 8 <div class="col-sm-12 faded">
9 - <div class="breadcrumbs"> 9 + @include('pages._breadcrumbs', ['page' => $page])
10 - <a href="{{ $page->book->getUrl() }}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $page->book->getShortName() }}</a>
11 - @if($page->hasChapter())
12 - <span class="sep">&raquo;</span>
13 - <a href="{{ $page->chapter->getUrl() }}" class="text-chapter text-button">
14 - <i class="zmdi zmdi-collection-bookmark"></i>
15 - {{ $page->chapter->getShortName() }}
16 - </a>
17 - @endif
18 - <span class="sep">&raquo;</span>
19 - <a href="{{ $page->getUrl() }}" class="text-page text-button"><i class="zmdi zmdi-file"></i>{{ $page->getShortName() }}</a>
20 - </div>
21 </div> 10 </div>
22 </div> 11 </div>
23 </div> 12 </div>
24 </div> 13 </div>
25 14
26 <div class="container" ng-non-bindable> 15 <div class="container" ng-non-bindable>
27 - <h1>Page Permissions</h1> 16 + <h1>{{ trans('entities.pages_permissions') }}</h1>
28 - @include('form/restriction-form', ['model' => $page]) 17 + @include('form.restriction-form', ['model' => $page])
29 </div> 18 </div>
30 19
31 @stop 20 @stop
......
...@@ -7,14 +7,12 @@ ...@@ -7,14 +7,12 @@
7 <div class="row"> 7 <div class="row">
8 <div class="col-md-9"> 8 <div class="col-md-9">
9 <div class="page-content anim fadeIn"> 9 <div class="page-content anim fadeIn">
10 - @include('pages/page-display') 10 + @include('pages.page-display')
11 </div> 11 </div>
12 </div> 12 </div>
13 </div> 13 </div>
14 </div> 14 </div>
15 15
16 16
17 - 17 + @include('partials.highlight')
18 - @include('partials/highlight')
19 -
20 @stop 18 @stop
......
...@@ -6,37 +6,24 @@ ...@@ -6,37 +6,24 @@
6 <div class="container"> 6 <div class="container">
7 <div class="row"> 7 <div class="row">
8 <div class="col-sm-12 faded"> 8 <div class="col-sm-12 faded">
9 - <div class="breadcrumbs"> 9 + @include('pages._breadcrumbs', ['page' => $page])
10 - <a href="{{ $page->book->getUrl() }}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $page->book->getShortName() }}</a>
11 - @if($page->hasChapter())
12 - <span class="sep">&raquo;</span>
13 - <a href="{{ $page->chapter->getUrl() }}" class="text-chapter text-button">
14 - <i class="zmdi zmdi-collection-bookmark"></i>
15 - {{ $page->chapter->getShortName() }}
16 - </a>
17 - @endif
18 - <span class="sep">&raquo;</span>
19 - <a href="{{ $page->getUrl() }}" class="text-page text-button"><i class="zmdi zmdi-file"></i>{{ $page->getShortName() }}</a>
20 - </div>
21 </div> 10 </div>
22 </div> 11 </div>
23 </div> 12 </div>
24 </div> 13 </div>
25 14
26 -
27 -
28 <div class="container" ng-non-bindable> 15 <div class="container" ng-non-bindable>
29 - <h1>Page Revisions <span class="subheader">For "{{ $page->name }}"</span></h1> 16 + <h1>{{ trans('entities.pages_revisions') }}</h1>
30 17
31 @if(count($page->revisions) > 0) 18 @if(count($page->revisions) > 0)
32 19
33 <table class="table"> 20 <table class="table">
34 <tr> 21 <tr>
35 - <th width="23%">Name</th> 22 + <th width="23%">{{ trans('entities.pages_name') }}</th>
36 - <th colspan="2" width="8%">Created By</th> 23 + <th colspan="2" width="8%">{{ trans('entities.pages_revisions_created_by') }}</th>
37 - <th width="15%">Revision Date</th> 24 + <th width="15%">{{ trans('entities.pages_revisions_date') }}</th>
38 - <th width="25%">Changelog</th> 25 + <th width="25%">{{ trans('entities.pages_revisions_changelog') }}</th>
39 - <th width="20%">Actions</th> 26 + <th width="20%">{{ trans('common.actions') }}</th>
40 </tr> 27 </tr>
41 @foreach($page->revisions as $index => $revision) 28 @foreach($page->revisions as $index => $revision)
42 <tr> 29 <tr>
...@@ -46,19 +33,19 @@ ...@@ -46,19 +33,19 @@
46 <img class="avatar" src="{{ $revision->createdBy->getAvatar(30) }}" alt="{{ $revision->createdBy->name }}"> 33 <img class="avatar" src="{{ $revision->createdBy->getAvatar(30) }}" alt="{{ $revision->createdBy->name }}">
47 @endif 34 @endif
48 </td> 35 </td>
49 - <td> @if($revision->createdBy) {{ $revision->createdBy->name }} @else Deleted User @endif</td> 36 + <td> @if($revision->createdBy) {{ $revision->createdBy->name }} @else {{ trans('common.deleted_user') }} @endif</td>
50 <td><small>{{ $revision->created_at->format('jS F, Y H:i:s') }} <br> ({{ $revision->created_at->diffForHumans() }})</small></td> 37 <td><small>{{ $revision->created_at->format('jS F, Y H:i:s') }} <br> ({{ $revision->created_at->diffForHumans() }})</small></td>
51 <td>{{ $revision->summary }}</td> 38 <td>{{ $revision->summary }}</td>
52 <td> 39 <td>
53 - <a href="{{ $revision->getUrl('changes') }}" target="_blank">Changes</a> 40 + <a href="{{ $revision->getUrl('changes') }}" target="_blank">{{ trans('entities.pages_revisions_changes') }}</a>
54 <span class="text-muted">&nbsp;|&nbsp;</span> 41 <span class="text-muted">&nbsp;|&nbsp;</span>
55 42
56 @if ($index === 0) 43 @if ($index === 0)
57 - <a target="_blank" href="{{ $page->getUrl() }}"><i>Current Version</i></a> 44 + <a target="_blank" href="{{ $page->getUrl() }}"><i>{{ trans('entities.pages_revisions_current') }}</i></a>
58 @else 45 @else
59 - <a href="{{ $revision->getUrl() }}" target="_blank">Preview</a> 46 + <a href="{{ $revision->getUrl() }}" target="_blank">{{ trans('entities.pages_revisions_preview') }}</a>
60 <span class="text-muted">&nbsp;|&nbsp;</span> 47 <span class="text-muted">&nbsp;|&nbsp;</span>
61 - <a href="{{ $revision->getUrl('restore') }}" target="_blank">Restore</a> 48 + <a href="{{ $revision->getUrl('restore') }}">{{ trans('entities.pages_revisions_restore') }}</a>
62 @endif 49 @endif
63 </td> 50 </td>
64 </tr> 51 </tr>
...@@ -66,7 +53,7 @@ ...@@ -66,7 +53,7 @@
66 </table> 53 </table>
67 54
68 @else 55 @else
69 - <p>This page has no revisions.</p> 56 + <p>{{ trans('entities.pages_revisions_none') }}</p>
70 @endif 57 @endif
71 58
72 </div> 59 </div>
......
...@@ -6,43 +6,34 @@ ...@@ -6,43 +6,34 @@
6 <div class="container"> 6 <div class="container">
7 <div class="row"> 7 <div class="row">
8 <div class="col-sm-6 faded"> 8 <div class="col-sm-6 faded">
9 - <div class="breadcrumbs"> 9 + @include('pages._breadcrumbs', ['page' => $page])
10 - <a href="{{ $book->getUrl() }}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $book->getShortName() }}</a>
11 - @if($page->hasChapter())
12 - <span class="sep">&raquo;</span>
13 - <a href="{{ $page->chapter->getUrl() }}" class="text-chapter text-button">
14 - <i class="zmdi zmdi-collection-bookmark"></i>
15 - {{ $page->chapter->getShortName() }}
16 - </a>
17 - @endif
18 - </div>
19 </div> 10 </div>
20 <div class="col-sm-6 faded"> 11 <div class="col-sm-6 faded">
21 <div class="action-buttons"> 12 <div class="action-buttons">
22 <span dropdown class="dropdown-container"> 13 <span dropdown class="dropdown-container">
23 - <div dropdown-toggle class="text-button text-primary"><i class="zmdi zmdi-open-in-new"></i>Export</div> 14 + <div dropdown-toggle class="text-button text-primary"><i class="zmdi zmdi-open-in-new"></i>{{ trans('entities.pages_export') }}</div>
24 <ul class="wide"> 15 <ul class="wide">
25 - <li><a href="{{ $page->getUrl('/export/html') }}" target="_blank">Contained Web File <span class="text-muted float right">.html</span></a></li> 16 + <li><a href="{{ $page->getUrl('/export/html') }}" target="_blank">{{ trans('entities.pages_export_html') }} <span class="text-muted float right">.html</span></a></li>
26 - <li><a href="{{ $page->getUrl('/export/pdf') }}" target="_blank">PDF File <span class="text-muted float right">.pdf</span></a></li> 17 + <li><a href="{{ $page->getUrl('/export/pdf') }}" target="_blank">{{ trans('entities.pages_export_pdf') }} <span class="text-muted float right">.pdf</span></a></li>
27 - <li><a href="{{ $page->getUrl('/export/plaintext') }}" target="_blank">Plain Text File <span class="text-muted float right">.txt</span></a></li> 18 + <li><a href="{{ $page->getUrl('/export/plaintext') }}" target="_blank">{{ trans('entities.pages_export_text') }} <span class="text-muted float right">.txt</span></a></li>
28 </ul> 19 </ul>
29 </span> 20 </span>
30 @if(userCan('page-update', $page)) 21 @if(userCan('page-update', $page))
31 - <a href="{{ $page->getUrl('/edit') }}" class="text-primary text-button" ><i class="zmdi zmdi-edit"></i>Edit</a> 22 + <a href="{{ $page->getUrl('/edit') }}" class="text-primary text-button" ><i class="zmdi zmdi-edit"></i>{{ trans('common.edit') }}</a>
32 @endif 23 @endif
33 @if(userCan('page-update', $page) || userCan('restrictions-manage', $page) || userCan('page-delete', $page)) 24 @if(userCan('page-update', $page) || userCan('restrictions-manage', $page) || userCan('page-delete', $page))
34 <div dropdown class="dropdown-container"> 25 <div dropdown class="dropdown-container">
35 <a dropdown-toggle class="text-primary text-button"><i class="zmdi zmdi-more-vert"></i></a> 26 <a dropdown-toggle class="text-primary text-button"><i class="zmdi zmdi-more-vert"></i></a>
36 <ul> 27 <ul>
37 @if(userCan('page-update', $page)) 28 @if(userCan('page-update', $page))
38 - <li><a href="{{ $page->getUrl('/move') }}" class="text-primary" ><i class="zmdi zmdi-folder"></i>Move</a></li> 29 + <li><a href="{{ $page->getUrl('/move') }}" class="text-primary" ><i class="zmdi zmdi-folder"></i>{{ trans('common.move') }}</a></li>
39 - <li><a href="{{ $page->getUrl('/revisions') }}" class="text-primary"><i class="zmdi zmdi-replay"></i>Revisions</a></li> 30 + <li><a href="{{ $page->getUrl('/revisions') }}" class="text-primary"><i class="zmdi zmdi-replay"></i>{{ trans('entities.revisions') }}</a></li>
40 @endif 31 @endif
41 @if(userCan('restrictions-manage', $page)) 32 @if(userCan('restrictions-manage', $page))
42 - <li><a href="{{ $page->getUrl('/permissions') }}" class="text-primary"><i class="zmdi zmdi-lock-outline"></i>Permissions</a></li> 33 + <li><a href="{{ $page->getUrl('/permissions') }}" class="text-primary"><i class="zmdi zmdi-lock-outline"></i>{{ trans('entities.permissions') }}</a></li>
43 @endif 34 @endif
44 @if(userCan('page-delete', $page)) 35 @if(userCan('page-delete', $page))
45 - <li><a href="{{ $page->getUrl('/delete') }}" class="text-neg"><i class="zmdi zmdi-delete"></i>Delete</a></li> 36 + <li><a href="{{ $page->getUrl('/delete') }}" class="text-neg"><i class="zmdi zmdi-delete"></i>{{ trans('common.delete') }}</a></li>
46 @endif 37 @endif
47 </ul> 38 </ul>
48 </div> 39 </div>
...@@ -62,9 +53,9 @@ ...@@ -62,9 +53,9 @@
62 53
63 <div class="pointer-container" id="pointer"> 54 <div class="pointer-container" id="pointer">
64 <div class="pointer anim"> 55 <div class="pointer anim">
65 - <i class="zmdi zmdi-link"></i> 56 + <span class="icon text-primary"><i class="zmdi zmdi-link"></i></span>
66 - <input readonly="readonly" type="text" placeholder="url"> 57 + <input readonly="readonly" type="text" id="pointer-url" placeholder="url">
67 - <button class="button icon" title="Copy Link" data-clipboard-text=""><i class="zmdi zmdi-copy"></i></button> 58 + <button class="button icon" data-clipboard-target="#pointer-url" type="button" title="{{ trans('entities.pages_copy_link') }}"><i class="zmdi zmdi-copy"></i></button>
68 </div> 59 </div>
69 </div> 60 </div>
70 61
...@@ -72,11 +63,7 @@ ...@@ -72,11 +63,7 @@
72 63
73 <hr> 64 <hr>
74 65
75 - <p class="text-muted small"> 66 + @include('partials.entity-meta', ['entity' => $page])
76 - Created {{ $page->created_at->diffForHumans() }} @if($page->createdBy) by <a href="{{ $page->createdBy->getProfileUrl() }}">{{$page->createdBy->name}}</a> @endif
77 - <br>
78 - Last Updated {{ $page->updated_at->diffForHumans() }} @if($page->updatedBy) by <a href="{{ $page->updatedBy->getProfileUrl() }}">{{$page->updatedBy->name}}</a> @endif
79 - </p>
80 67
81 </div> 68 </div>
82 </div> 69 </div>
...@@ -88,27 +75,27 @@ ...@@ -88,27 +75,27 @@
88 75
89 @if($book->restricted) 76 @if($book->restricted)
90 @if(userCan('restrictions-manage', $book)) 77 @if(userCan('restrictions-manage', $book))
91 - <a href="{{ $book->getUrl('/permissions') }}"><i class="zmdi zmdi-lock-outline"></i>Book Permissions Active</a> 78 + <a href="{{ $book->getUrl('/permissions') }}"><i class="zmdi zmdi-lock-outline"></i>{{ trans('entities.books_permissions_active') }}</a>
92 @else 79 @else
93 - <i class="zmdi zmdi-lock-outline"></i>Book Permissions Active 80 + <i class="zmdi zmdi-lock-outline"></i>{{ trans('entities.books_permissions_active') }}
94 @endif 81 @endif
95 <br> 82 <br>
96 @endif 83 @endif
97 84
98 @if($page->chapter && $page->chapter->restricted) 85 @if($page->chapter && $page->chapter->restricted)
99 @if(userCan('restrictions-manage', $page->chapter)) 86 @if(userCan('restrictions-manage', $page->chapter))
100 - <a href="{{ $page->chapter->getUrl('/permissions') }}"><i class="zmdi zmdi-lock-outline"></i>Chapter Permissions Active</a> 87 + <a href="{{ $page->chapter->getUrl('/permissions') }}"><i class="zmdi zmdi-lock-outline"></i>{{ trans('entities.chapters_permissions_active') }}</a>
101 @else 88 @else
102 - <i class="zmdi zmdi-lock-outline"></i>Chapter Permissions Active 89 + <i class="zmdi zmdi-lock-outline"></i>{{ trans('entities.chapters_permissions_active') }}
103 @endif 90 @endif
104 <br> 91 <br>
105 @endif 92 @endif
106 93
107 @if($page->restricted) 94 @if($page->restricted)
108 @if(userCan('restrictions-manage', $page)) 95 @if(userCan('restrictions-manage', $page))
109 - <a href="{{ $page->getUrl('/permissions') }}"><i class="zmdi zmdi-lock-outline"></i>Page Permissions Active</a> 96 + <a href="{{ $page->getUrl('/permissions') }}"><i class="zmdi zmdi-lock-outline"></i>{{ trans('entities.pages_permissions_active') }}</a>
110 @else 97 @else
111 - <i class="zmdi zmdi-lock-outline"></i>Page Permissions Active 98 + <i class="zmdi zmdi-lock-outline"></i>{{ trans('entities.pages_permissions_active') }}
112 @endif 99 @endif
113 <br> 100 <br>
114 @endif 101 @endif
......
...@@ -18,26 +18,26 @@ ...@@ -18,26 +18,26 @@
18 @endif 18 @endif
19 19
20 @if (isset($page) && $page->attachments->count() > 0) 20 @if (isset($page) && $page->attachments->count() > 0)
21 - <h6 class="text-muted">Attachments</h6> 21 + <h6 class="text-muted">{{ trans('entities.pages_attachments') }}</h6>
22 @foreach($page->attachments as $attachment) 22 @foreach($page->attachments as $attachment)
23 <div class="attachment"> 23 <div class="attachment">
24 - <a href="{{ $attachment->getUrl() }}" @if($attachment->external) target="_blank" @endif><i class="zmdi zmdi-{{ $attachment->external ? 'open-in-new' : 'file' }}"></i> {{ $attachment->name }}</a> 24 + <a href="{{ $attachment->getUrl() }}" @if($attachment->external) target="_blank" @endif><i class="zmdi zmdi-{{ $attachment->external ? 'open-in-new' : 'file' }}"></i>{{ $attachment->name }}</a>
25 </div> 25 </div>
26 @endforeach 26 @endforeach
27 @endif 27 @endif
28 28
29 - @if (isset($pageNav) && $pageNav) 29 + @if (isset($pageNav) && count($pageNav))
30 - <h6 class="text-muted">Page Navigation</h6> 30 + <h6 class="text-muted">{{ trans('entities.pages_navigation') }}</h6>
31 <div class="sidebar-page-nav menu"> 31 <div class="sidebar-page-nav menu">
32 @foreach($pageNav as $navItem) 32 @foreach($pageNav as $navItem)
33 - <li class="page-nav-item {{ $navItem['nodeName'] }}"> 33 + <li class="page-nav-item h{{ $navItem['level'] }}">
34 <a href="{{ $navItem['link'] }}">{{ $navItem['text'] }}</a> 34 <a href="{{ $navItem['link'] }}">{{ $navItem['text'] }}</a>
35 </li> 35 </li>
36 @endforeach 36 @endforeach
37 </div> 37 </div>
38 @endif 38 @endif
39 39
40 - <h6 class="text-muted">Book Navigation</h6> 40 + <h6 class="text-muted">{{ trans('entities.books_navigation') }}</h6>
41 <ul class="sidebar-page-list menu"> 41 <ul class="sidebar-page-list menu">
42 <li class="book-header"><a href="{{ $book->getUrl() }}" class="book {{ $current->matches($book)? 'selected' : '' }}"><i class="zmdi zmdi-book"></i>{{$book->name}}</a></li> 42 <li class="book-header"><a href="{{ $book->getUrl() }}" class="book {{ $current->matches($book)? 'selected' : '' }}"><i class="zmdi zmdi-book"></i>{{$book->name}}</a></li>
43 43
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
50 50
51 @if($bookChild->isA('chapter') && count($bookChild->pages) > 0) 51 @if($bookChild->isA('chapter') && count($bookChild->pages) > 0)
52 <p class="text-muted chapter-toggle @if($bookChild->matchesOrContains($current)) open @endif"> 52 <p class="text-muted chapter-toggle @if($bookChild->matchesOrContains($current)) open @endif">
53 - <i class="zmdi zmdi-caret-right"></i> <i class="zmdi zmdi-file-text"></i> <span>{{ count($bookChild->pages) }} Pages</span> 53 + <i class="zmdi zmdi-caret-right"></i> <i class="zmdi zmdi-file-text"></i> <span>{{ trans('entities.x_pages', ['count' => $bookChild->pages->count()]) }}</span>
54 </p> 54 </p>
55 <ul class="menu sub-menu inset-list @if($bookChild->matchesOrContains($current)) open @endif"> 55 <ul class="menu sub-menu inset-list @if($bookChild->matchesOrContains($current)) open @endif">
56 @foreach($bookChild->pages as $childPage) 56 @foreach($bookChild->pages as $childPage)
......
1 +<div class="dropdown-container" dropdown>
2 + <span class="user-name" dropdown-toggle>
3 + <img class="avatar" src="{{$currentUser->getAvatar(30)}}" alt="{{ $currentUser->name }}">
4 + <span class="name" ng-non-bindable>{{ $currentUser->getShortName(9) }}</span> <i class="zmdi zmdi-caret-down"></i>
5 + </span>
6 + <ul>
7 + <li>
8 + <a href="{{ baseUrl("/user/{$currentUser->id}") }}" class="text-primary"><i class="zmdi zmdi-account zmdi-hc-fw zmdi-hc-lg"></i>{{ trans('common.view_profile') }}</a>
9 + </li>
10 + <li>
11 + <a href="{{ baseUrl("/settings/users/{$currentUser->id}") }}" class="text-primary"><i class="zmdi zmdi-edit zmdi-hc-fw zmdi-hc-lg"></i>{{ trans('common.edit_profile') }}</a>
12 + </li>
13 + <li>
14 + <a href="{{ baseUrl('/logout') }}" class="text-neg"><i class="zmdi zmdi-run zmdi-hc-fw zmdi-hc-lg"></i>{{ trans('auth.logout') }}</a>
15 + </li>
16 + </ul>
17 +</div>
...\ No newline at end of file ...\ No newline at end of file
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
11 @if($activity->user) 11 @if($activity->user)
12 <a href="{{ $activity->user->getProfileUrl() }}">{{ $activity->user->name }}</a> 12 <a href="{{ $activity->user->getProfileUrl() }}">{{ $activity->user->name }}</a>
13 @else 13 @else
14 - A deleted user 14 + {{ trans('common.deleted_user') }}
15 @endif 15 @endif
16 16
17 {{ $activity->getText() }} 17 {{ $activity->getText() }}
......
...@@ -8,5 +8,5 @@ ...@@ -8,5 +8,5 @@
8 @endforeach 8 @endforeach
9 </div> 9 </div>
10 @else 10 @else
11 - <p class="text-muted">No activity to show</p> 11 + <p class="text-muted">{{ trans('common.no_activity') }}</p>
12 @endif 12 @endif
...\ No newline at end of file ...\ No newline at end of file
......
1 -<style> 1 +<style id="custom-styles" data-color="{{ setting('app-color') }}" data-color-light="{{ setting('app-color-light') }}">
2 header, #back-to-top, .primary-background { 2 header, #back-to-top, .primary-background {
3 background-color: {{ setting('app-color') }} !important; 3 background-color: {{ setting('app-color') }} !important;
4 } 4 }
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
17 @endforeach 17 @endforeach
18 @else 18 @else
19 <p class="text-muted empty-text"> 19 <p class="text-muted empty-text">
20 - {{ $emptyText or 'No items available' }} 20 + {{ $emptyText or trans('common.no_items') }}
21 </p> 21 </p>
22 @endif 22 @endif
23 </div> 23 </div>
...\ No newline at end of file ...\ No newline at end of file
......
1 +<p class="text-muted small">
2 + @if ($entity->createdBy)
3 + {!! trans('entities.meta_created_name', ['timeLength' => $entity->created_at->diffForHumans(), 'user' => "<a href='{$entity->createdBy->getProfileUrl()}'>".htmlentities($entity->createdBy->name). "</a>"]) !!}
4 + @else
5 + {{ trans('entities.meta_created', ['timeLength' => $entity->created_at->diffForHumans()]) }}
6 + @endif
7 + <br>
8 + @if ($entity->updatedBy)
9 + {!! trans('entities.meta_updated_name', ['timeLength' => $entity->updated_at->diffForHumans(), 'user' => "<a href='{$entity->updatedBy->getProfileUrl()}'>".htmlentities($entity->updatedBy->name). "</a>"]) !!}
10 + @else
11 + {{ trans('entities.meta_updated', ['timeLength' => $entity->updated_at->diffForHumans()]) }}
12 + @endif
13 +</p>
...\ No newline at end of file ...\ No newline at end of file
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
25 </head> 25 </head>
26 <body class="@yield('body-class')" ng-app="bookStack"> 26 <body class="@yield('body-class')" ng-app="bookStack">
27 27
28 -@include('partials/notifications') 28 +@include('partials.notifications')
29 29
30 <header id="header"> 30 <header id="header">
31 <div class="container"> 31 <div class="container">
...@@ -47,23 +47,7 @@ ...@@ -47,23 +47,7 @@
47 @yield('header-buttons') 47 @yield('header-buttons')
48 </div> 48 </div>
49 @if(isset($signedIn) && $signedIn) 49 @if(isset($signedIn) && $signedIn)
50 - <div class="dropdown-container" dropdown> 50 + @include('partials._header-dropdown', ['currentUser' => $currentUser])
51 - <span class="user-name" dropdown-toggle>
52 - <img class="avatar" src="{{$currentUser->getAvatar(30)}}" alt="{{ $currentUser->name }}">
53 - <span class="name" ng-non-bindable>{{ $currentUser->getShortName(9) }}</span> <i class="zmdi zmdi-caret-down"></i>
54 - </span>
55 - <ul>
56 - <li>
57 - <a href="{{ baseUrl("/user/{$currentUser->id}") }}" class="text-primary"><i class="zmdi zmdi-account zmdi-hc-fw zmdi-hc-lg"></i>View Profile</a>
58 - </li>
59 - <li>
60 - <a href="{{ baseUrl("/settings/users/{$currentUser->id}") }}" class="text-primary"><i class="zmdi zmdi-edit zmdi-hc-fw zmdi-hc-lg"></i>Edit Profile</a>
61 - </li>
62 - <li>
63 - <a href="{{ baseUrl('/logout') }}" class="text-neg"><i class="zmdi zmdi-run zmdi-hc-fw zmdi-hc-lg"></i>Logout</a>
64 - </li>
65 - </ul>
66 - </div>
67 @endif 51 @endif
68 </div> 52 </div>
69 </div> 53 </div>
......
...@@ -2,49 +2,55 @@ ...@@ -2,49 +2,55 @@
2 2
3 @section('content') 3 @section('content')
4 4
5 - <div class="container anim fadeIn" ng-non-bindable> 5 + <div class="faded-small toolbar">
6 + <div class="container">
7 + <div class="row">
8 + <div class="col-sm-12 faded">
9 + <div class="breadcrumbs">
10 + <a href="{{ baseUrl("/search/all?term={$searchTerm}") }}" class="text-button"><i class="zmdi zmdi-search"></i>{{ $searchTerm }}</a>
11 + </div>
12 + </div>
13 + </div>
14 + </div>
15 + </div>
6 16
7 - <h1>Search Results&nbsp;&nbsp;&nbsp; <span class="text-muted">{{ $searchTerm }}</span></h1>
8 17
9 - <p> 18 + <div class="container" ng-non-bindable>
10 19
20 + <h1>{{ trans('entities.search_results') }}</h1>
21 +
22 + <p>
11 @if(count($pages) > 0) 23 @if(count($pages) > 0)
12 - <a href="{{ baseUrl("/search/pages?term={$searchTerm}") }}" class="text-page"><i class="zmdi zmdi-file-text"></i>View all matched pages</a> 24 + <a href="{{ baseUrl("/search/pages?term={$searchTerm}") }}" class="text-page"><i class="zmdi zmdi-file-text"></i>{{ trans('entities.search_view_pages') }}</a>
13 @endif 25 @endif
14 26
15 -
16 @if(count($chapters) > 0) 27 @if(count($chapters) > 0)
17 &nbsp; &nbsp;&nbsp; 28 &nbsp; &nbsp;&nbsp;
18 - <a href="{{ baseUrl("/search/chapters?term={$searchTerm}") }}" class="text-chapter"><i class="zmdi zmdi-collection-bookmark"></i>View all matched chapters</a> 29 + <a href="{{ baseUrl("/search/chapters?term={$searchTerm}") }}" class="text-chapter"><i class="zmdi zmdi-collection-bookmark"></i>{{ trans('entities.search_view_chapters') }}</a>
19 @endif 30 @endif
20 31
21 @if(count($books) > 0) 32 @if(count($books) > 0)
22 &nbsp; &nbsp;&nbsp; 33 &nbsp; &nbsp;&nbsp;
23 - <a href="{{ baseUrl("/search/books?term={$searchTerm}") }}" class="text-book"><i class="zmdi zmdi-book"></i>View all matched books</a> 34 + <a href="{{ baseUrl("/search/books?term={$searchTerm}") }}" class="text-book"><i class="zmdi zmdi-book"></i>{{ trans('entities.search_view_books') }}</a>
24 @endif 35 @endif
25 </p> 36 </p>
26 - <div class="row">
27 37
38 + <div class="row">
28 <div class="col-md-6"> 39 <div class="col-md-6">
29 - <h3><a href="{{ baseUrl("/search/pages?term={$searchTerm}") }}" class="no-color">Matching Pages</a></h3> 40 + <h3><a href="{{ baseUrl("/search/pages?term={$searchTerm}") }}" class="no-color">{{ trans('entities.pages') }}</a></h3>
30 @include('partials/entity-list', ['entities' => $pages, 'style' => 'detailed']) 41 @include('partials/entity-list', ['entities' => $pages, 'style' => 'detailed'])
31 </div> 42 </div>
32 -
33 <div class="col-md-5 col-md-offset-1"> 43 <div class="col-md-5 col-md-offset-1">
34 -
35 @if(count($books) > 0) 44 @if(count($books) > 0)
36 - <h3><a href="{{ baseUrl("/search/books?term={$searchTerm}") }}" class="no-color">Matching Books</a></h3> 45 + <h3><a href="{{ baseUrl("/search/books?term={$searchTerm}") }}" class="no-color">{{ trans('entities.books') }}</a></h3>
37 @include('partials/entity-list', ['entities' => $books]) 46 @include('partials/entity-list', ['entities' => $books])
38 @endif 47 @endif
39 48
40 @if(count($chapters) > 0) 49 @if(count($chapters) > 0)
41 - <h3><a href="{{ baseUrl("/search/chapters?term={$searchTerm}") }}" class="no-color">Matching Chapters</a></h3> 50 + <h3><a href="{{ baseUrl("/search/chapters?term={$searchTerm}") }}" class="no-color">{{ trans('entities.chapters') }}</a></h3>
42 @include('partials/entity-list', ['entities' => $chapters]) 51 @include('partials/entity-list', ['entities' => $chapters])
43 @endif 52 @endif
44 -
45 </div> 53 </div>
46 -
47 -
48 </div> 54 </div>
49 55
50 56
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
7 </div> 7 </div>
8 @endforeach 8 @endforeach
9 @else 9 @else
10 - <p class="text-muted">No pages matched this search</p> 10 + <p class="text-muted">{{ trans('entities.search_no_pages') }}</p>
11 @endif 11 @endif
12 </div> 12 </div>
13 13
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
16 @endforeach 16 @endforeach
17 @else 17 @else
18 <p class="text-muted"> 18 <p class="text-muted">
19 - No items available 19 + {{ trans('common.no_items') }}
20 </p> 20 </p>
21 @endif 21 @endif
22 </div> 22 </div>
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -2,17 +2,27 @@ ...@@ -2,17 +2,27 @@
2 2
3 @section('content') 3 @section('content')
4 4
5 + <div class="faded-small toolbar">
6 + <div class="container">
7 + <div class="row">
8 + <div class="col-sm-12 faded">
9 + <div class="breadcrumbs">
10 + <a href="{{ baseUrl("/search/all?term={$searchTerm}") }}" class="text-button"><i class="zmdi zmdi-search"></i>{{ $searchTerm }}</a>
11 + </div>
12 + </div>
13 + </div>
14 + </div>
15 + </div>
16 +
5 <div class="container"> 17 <div class="container">
6 <div class="row"> 18 <div class="row">
7 19
8 <div class="col-sm-7"> 20 <div class="col-sm-7">
9 - <h1>{{ $title }} <small>{{ $searchTerm }}</small></h1> 21 + <h1>{{ $title }}</h1>
10 @include('partials.entity-list', ['entities' => $entities, 'style' => 'detailed']) 22 @include('partials.entity-list', ['entities' => $entities, 'style' => 'detailed'])
11 {!! $entities->links() !!} 23 {!! $entities->links() !!}
12 </div> 24 </div>
13 25
14 - <div class="col-sm-4 col-sm-offset-1"></div>
15 -
16 </div> 26 </div>
17 </div> 27 </div>
18 @stop 28 @stop
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -8,10 +8,10 @@ ...@@ -8,10 +8,10 @@
8 8
9 <h1>{{ trans('settings.settings') }}</h1> 9 <h1>{{ trans('settings.settings') }}</h1>
10 10
11 - <form action="{{ baseUrl("/settings") }}" method="POST" ng-cloak> 11 + <form action="{{ baseUrl("/settings") }}" method="POST">
12 {!! csrf_field() !!} 12 {!! csrf_field() !!}
13 13
14 - <h3>App Settings</h3> 14 + <h3>{{ trans('settings.app_settings') }}</h3>
15 15
16 <div class="row"> 16 <div class="row">
17 17
...@@ -23,16 +23,16 @@ ...@@ -23,16 +23,16 @@
23 </div> 23 </div>
24 <div class="form-group"> 24 <div class="form-group">
25 <label>{{ trans('settings.app_name_header') }}</label> 25 <label>{{ trans('settings.app_name_header') }}</label>
26 - <div toggle-switch name="setting-app-name-header" value="{{ setting('app-name-header') }}"></div> 26 + @include('components.toggle-switch', ['name' => 'setting-app-name-header', 'value' => setting('app-name-header')])
27 </div> 27 </div>
28 <div class="form-group"> 28 <div class="form-group">
29 <label for="setting-app-public">{{ trans('settings.app_public_viewing') }}</label> 29 <label for="setting-app-public">{{ trans('settings.app_public_viewing') }}</label>
30 - <div toggle-switch name="setting-app-public" value="{{ setting('app-public') }}"></div> 30 + @include('components.toggle-switch', ['name' => 'setting-app-public', 'value' => setting('app-public')])
31 </div> 31 </div>
32 <div class="form-group"> 32 <div class="form-group">
33 <label>{{ trans('settings.app_secure_images') }}</label> 33 <label>{{ trans('settings.app_secure_images') }}</label>
34 <p class="small">{{ trans('settings.app_secure_images_desc') }}</p> 34 <p class="small">{{ trans('settings.app_secure_images_desc') }}</p>
35 - <div toggle-switch name="setting-app-secure-images" value="{{ setting('app-secure-images') }}"></div> 35 + @include('components.toggle-switch', ['name' => 'setting-app-secure-images', 'value' => setting('app-secure-images')])
36 </div> 36 </div>
37 <div class="form-group"> 37 <div class="form-group">
38 <label for="setting-app-editor">{{ trans('settings.app_editor') }}</label> 38 <label for="setting-app-editor">{{ trans('settings.app_editor') }}</label>
...@@ -48,7 +48,18 @@ ...@@ -48,7 +48,18 @@
48 <div class="form-group" id="logo-control"> 48 <div class="form-group" id="logo-control">
49 <label for="setting-app-logo">{{ trans('settings.app_logo') }}</label> 49 <label for="setting-app-logo">{{ trans('settings.app_logo') }}</label>
50 <p class="small">{!! trans('settings.app_logo_desc') !!}</p> 50 <p class="small">{!! trans('settings.app_logo_desc') !!}</p>
51 - <image-picker resize-height="43" show-remove="true" resize-width="200" current-image="{{ setting('app-logo', '') }}" default-image="{{ baseUrl('/logo.png') }}" name="setting-app-logo" image-class="logo-image"></image-picker> 51 +
52 + @include('components.image-picker', [
53 + 'resizeHeight' => '43',
54 + 'resizeWidth' => '200',
55 + 'showRemove' => true,
56 + 'defaultImage' => baseUrl('/logo.png'),
57 + 'currentImage' => setting('app-logo'),
58 + 'name' => 'setting-app-logo',
59 + 'imageClass' => 'logo-image',
60 + 'currentId' => false
61 + ])
62 +
52 </div> 63 </div>
53 <div class="form-group" id="color-control"> 64 <div class="form-group" id="color-control">
54 <label for="setting-app-color">{{ trans('settings.app_primary_color') }}</label> 65 <label for="setting-app-color">{{ trans('settings.app_primary_color') }}</label>
...@@ -74,7 +85,7 @@ ...@@ -74,7 +85,7 @@
74 <div class="col-md-6"> 85 <div class="col-md-6">
75 <div class="form-group"> 86 <div class="form-group">
76 <label for="setting-registration-enabled">{{ trans('settings.reg_allow') }}</label> 87 <label for="setting-registration-enabled">{{ trans('settings.reg_allow') }}</label>
77 - <div toggle-switch name="setting-registration-enabled" value="{{ setting('registration-enabled') }}"></div> 88 + @include('components.toggle-switch', ['name' => 'setting-registration-enabled', 'value' => setting('registration-enabled')])
78 </div> 89 </div>
79 <div class="form-group"> 90 <div class="form-group">
80 <label for="setting-registration-role">{{ trans('settings.reg_default_role') }}</label> 91 <label for="setting-registration-role">{{ trans('settings.reg_default_role') }}</label>
...@@ -91,7 +102,7 @@ ...@@ -91,7 +102,7 @@
91 <div class="form-group"> 102 <div class="form-group">
92 <label for="setting-registration-confirmation">{{ trans('settings.reg_confirm_email') }}</label> 103 <label for="setting-registration-confirmation">{{ trans('settings.reg_confirm_email') }}</label>
93 <p class="small">{{ trans('settings.reg_confirm_email_desc') }}</p> 104 <p class="small">{{ trans('settings.reg_confirm_email_desc') }}</p>
94 - <div toggle-switch name="setting-registration-confirmation" value="{{ setting('registration-confirmation') }}"></div> 105 + @include('components.toggle-switch', ['name' => 'setting-registration-confirmation', 'value' => setting('registration-confirmation')])
95 </div> 106 </div>
96 </div> 107 </div>
97 <div class="col-md-6"> 108 <div class="col-md-6">
...@@ -115,7 +126,7 @@ ...@@ -115,7 +126,7 @@
115 126
116 </div> 127 </div>
117 128
118 -@include('partials/image-manager', ['imageType' => 'system']) 129 +@include('components.image-manager', ['imageType' => 'system'])
119 130
120 @stop 131 @stop
121 132
...@@ -132,10 +143,16 @@ ...@@ -132,10 +143,16 @@
132 var isEmpty = $.trim($elm.val()).length === 0; 143 var isEmpty = $.trim($elm.val()).length === 0;
133 if (!isEmpty) $elm.val(hexVal); 144 if (!isEmpty) $elm.val(hexVal);
134 $('#setting-app-color-light').val(isEmpty ? '' : rgbLightVal); 145 $('#setting-app-color-light').val(isEmpty ? '' : rgbLightVal);
135 - // Set page elements to provide preview 146 +
136 - $('#header, .image-picker .button').attr('style', 'background-color:'+ hexVal+'!important;'); 147 + var customStyles = document.getElementById('custom-styles');
137 - $('.faded-small').css('background-color', rgbLightVal); 148 + var oldColor = customStyles.getAttribute('data-color');
138 - $('.setting-nav a.selected').css('border-bottom-color', hexVal + '!important'); 149 + var oldColorLight = customStyles.getAttribute('data-color-light');
150 +
151 + customStyles.innerHTML = customStyles.innerHTML.split(oldColor).join(hexVal);
152 + customStyles.innerHTML = customStyles.innerHTML.split(oldColorLight).join(rgbLightVal);
153 +
154 + customStyles.setAttribute('data-color', hexVal);
155 + customStyles.setAttribute('data-color-light', rgbLightVal);
139 } 156 }
140 }); 157 });
141 </script> 158 </script>
......
...@@ -4,13 +4,13 @@ ...@@ -4,13 +4,13 @@
4 <div class="row"> 4 <div class="row">
5 <div class="col-md-12 setting-nav nav-tabs"> 5 <div class="col-md-12 setting-nav nav-tabs">
6 @if($currentUser->can('settings-manage')) 6 @if($currentUser->can('settings-manage'))
7 - <a href="{{ baseUrl('/settings') }}" @if($selected == 'settings') class="selected text-button" @endif><i class="zmdi zmdi-settings"></i>Settings</a> 7 + <a href="{{ baseUrl('/settings') }}" @if($selected == 'settings') class="selected text-button" @endif><i class="zmdi zmdi-settings"></i>{{ trans('settings.settings') }}</a>
8 @endif 8 @endif
9 @if($currentUser->can('users-manage')) 9 @if($currentUser->can('users-manage'))
10 - <a href="{{ baseUrl('/settings/users') }}" @if($selected == 'users') class="selected text-button" @endif><i class="zmdi zmdi-accounts"></i>Users</a> 10 + <a href="{{ baseUrl('/settings/users') }}" @if($selected == 'users') class="selected text-button" @endif><i class="zmdi zmdi-accounts"></i>{{ trans('settings.users') }}</a>
11 @endif 11 @endif
12 @if($currentUser->can('user-roles-manage')) 12 @if($currentUser->can('user-roles-manage'))
13 - <a href="{{ baseUrl('/settings/roles') }}" @if($selected == 'roles') class="selected text-button" @endif><i class="zmdi zmdi-lock-open"></i>Roles</a> 13 + <a href="{{ baseUrl('/settings/roles') }}" @if($selected == 'roles') class="selected text-button" @endif><i class="zmdi zmdi-lock-open"></i>{{ trans('settings.roles') }}</a>
14 @endif 14 @endif
15 </div> 15 </div>
16 </div> 16 </div>
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
5 @include('settings/navbar', ['selected' => 'roles']) 5 @include('settings/navbar', ['selected' => 'roles'])
6 6
7 <div class="container"> 7 <div class="container">
8 - <h1>Create New Role</h1> 8 + <h1>{{ trans('settings.role_create') }}</h1>
9 9
10 <form action="{{ baseUrl("/settings/roles/new") }}" method="POST"> 10 <form action="{{ baseUrl("/settings/roles/new") }}" method="POST">
11 @include('settings/roles/form') 11 @include('settings/roles/form')
......
...@@ -5,8 +5,8 @@ ...@@ -5,8 +5,8 @@
5 @include('settings/navbar', ['selected' => 'roles']) 5 @include('settings/navbar', ['selected' => 'roles'])
6 6
7 <div class="container small" ng-non-bindable> 7 <div class="container small" ng-non-bindable>
8 - <h1>Delete Role</h1> 8 + <h1>{{ trans('settings.role_delete') }}</h1>
9 - <p>This will delete the role with the name '{{ $role->display_name }}'.</p> 9 + <p>{{ trans('settings.role_delete_confirm', ['roleName' => $role->display_name]) }}</p>
10 10
11 <form action="{{ baseUrl("/settings/roles/delete/{$role->id}") }}" method="POST"> 11 <form action="{{ baseUrl("/settings/roles/delete/{$role->id}") }}" method="POST">
12 {!! csrf_field() !!} 12 {!! csrf_field() !!}
...@@ -14,14 +14,14 @@ ...@@ -14,14 +14,14 @@
14 14
15 @if($role->users->count() > 0) 15 @if($role->users->count() > 0)
16 <div class="form-group"> 16 <div class="form-group">
17 - <p>This role has {{$role->users->count()}} users assigned to it. If you would like to migrate the users from this role select a new role below.</p> 17 + <p>{{ trans('settings.role_delete_users_assigned', ['userCount' => $role->users->count()]) }}</p>
18 @include('form/role-select', ['options' => $roles, 'name' => 'migration_role_id']) 18 @include('form/role-select', ['options' => $roles, 'name' => 'migration_role_id'])
19 </div> 19 </div>
20 @endif 20 @endif
21 21
22 - <p class="text-neg">Are you sure you want to delete this role?</p> 22 + <p class="text-neg">{{ trans('settings.role_delete_sure') }}</p>
23 - <a href="{{ baseUrl("/settings/roles/{$role->id}") }}" class="button">Cancel</a> 23 + <a href="{{ baseUrl("/settings/roles/{$role->id}") }}" class="button muted">{{ trans('common.cancel') }}</a>
24 - <button type="submit" class="button neg">Confirm</button> 24 + <button type="submit" class="button neg">{{ trans('common.confirm') }}</button>
25 </form> 25 </form>
26 </div> 26 </div>
27 27
......
...@@ -7,11 +7,11 @@ ...@@ -7,11 +7,11 @@
7 <div class="container"> 7 <div class="container">
8 <div class="row"> 8 <div class="row">
9 <div class="col-sm-6"> 9 <div class="col-sm-6">
10 - <h1>Edit Role <small> {{ $role->display_name }}</small></h1> 10 + <h1>{{ trans('settings.role_edit') }}</h1>
11 </div> 11 </div>
12 <div class="col-sm-6"> 12 <div class="col-sm-6">
13 <p></p> 13 <p></p>
14 - <a href="{{ baseUrl("/settings/roles/delete/{$role->id}") }}" class="button neg float right">Delete Role</a> 14 + <a href="{{ baseUrl("/settings/roles/delete/{$role->id}") }}" class="button neg float right">{{ trans('settings.role_delete') }}</a>
15 </div> 15 </div>
16 </div> 16 </div>
17 17
......
...@@ -5,128 +5,126 @@ ...@@ -5,128 +5,126 @@
5 <div class="col-md-9"> 5 <div class="col-md-9">
6 <div class="row"> 6 <div class="row">
7 <div class="col-md-5"> 7 <div class="col-md-5">
8 - <h3>Role Details</h3> 8 + <h3>{{ trans('settings.role_details') }}</h3>
9 <div class="form-group"> 9 <div class="form-group">
10 - <label for="name">Role Name</label> 10 + <label for="name">{{ trans('settings.role_name') }}</label>
11 @include('form/text', ['name' => 'display_name']) 11 @include('form/text', ['name' => 'display_name'])
12 </div> 12 </div>
13 <div class="form-group"> 13 <div class="form-group">
14 - <label for="name">Short Role Description</label> 14 + <label for="name">{{ trans('settings.role_desc') }}</label>
15 @include('form/text', ['name' => 'description']) 15 @include('form/text', ['name' => 'description'])
16 </div> 16 </div>
17 - <h3>System Permissions</h3> 17 + <h3>{{ trans('settings.role_system') }}</h3>
18 - <label>@include('settings/roles/checkbox', ['permission' => 'users-manage']) Manage users</label> 18 + <label>@include('settings/roles/checkbox', ['permission' => 'users-manage']) {{ trans('settings.role_manage_users') }}</label>
19 - <label>@include('settings/roles/checkbox', ['permission' => 'user-roles-manage']) Manage roles & role permissions</label> 19 + <label>@include('settings/roles/checkbox', ['permission' => 'user-roles-manage']) {{ trans('settings.role_manage_roles') }}</label>
20 - <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']) {{ trans('settings.role_manage_entity_permissions') }}</label>
21 - <label>@include('settings/roles/checkbox', ['permission' => 'permissions']) Manage permissions on own Book, Chapter & Pages</label> 21 + <label>@include('settings/roles/checkbox', ['permission' => 'restrictions-manage-own']) {{ trans('settings.role_manage_own_entity_permissions') }}</label>
22 - <label>@include('settings/roles/checkbox', ['permission' => 'settings-manage']) Manage app settings</label> 22 + <label>@include('settings/roles/checkbox', ['permission' => 'settings-manage']) {{ trans('settings.role_manage_settings') }}</label>
23 </div> 23 </div>
24 24
25 <div class="col-md-6"> 25 <div class="col-md-6">
26 26
27 - <h3>Asset Permissions</h3> 27 + <h3>{{ trans('settings.role_asset') }}</h3>
28 - <p> 28 + <p>{{ trans('settings.role_asset_desc') }}</p>
29 - These permissions control default access to the assets within the system. 29 +
30 - Permissions on Books, Chapters and Pages will override these permissions.
31 - </p>
32 <table class="table"> 30 <table class="table">
33 <tr> 31 <tr>
34 <th width="20%"></th> 32 <th width="20%"></th>
35 - <th width="20%">Create</th> 33 + <th width="20%">{{ trans('common.create') }}</th>
36 - <th width="20%">View</th> 34 + <th width="20%">{{ trans('common.view') }}</th>
37 - <th width="20%">Edit</th> 35 + <th width="20%">{{ trans('common.edit') }}</th>
38 - <th width="20%">Delete</th> 36 + <th width="20%">{{ trans('common.delete') }}</th>
39 </tr> 37 </tr>
40 <tr> 38 <tr>
41 - <td>Books</td> 39 + <td>{{ trans('entities.books') }}</td>
42 <td> 40 <td>
43 - <label>@include('settings/roles/checkbox', ['permission' => 'book-create-all']) All</label> 41 + <label>@include('settings/roles/checkbox', ['permission' => 'book-create-all']) {{ trans('settings.role_all') }}</label>
44 </td> 42 </td>
45 <td> 43 <td>
46 - <label>@include('settings/roles/checkbox', ['permission' => 'book-view-own']) Own</label> 44 + <label>@include('settings/roles/checkbox', ['permission' => 'book-view-own']) {{ trans('settings.role_own') }}</label>
47 - <label>@include('settings/roles/checkbox', ['permission' => 'book-view-all']) All</label> 45 + <label>@include('settings/roles/checkbox', ['permission' => 'book-view-all']) {{ trans('settings.role_all') }}</label>
48 </td> 46 </td>
49 <td> 47 <td>
50 - <label>@include('settings/roles/checkbox', ['permission' => 'book-update-own']) Own</label> 48 + <label>@include('settings/roles/checkbox', ['permission' => 'book-update-own']) {{ trans('settings.role_own') }}</label>
51 - <label>@include('settings/roles/checkbox', ['permission' => 'book-update-all']) All</label> 49 + <label>@include('settings/roles/checkbox', ['permission' => 'book-update-all']) {{ trans('settings.role_all') }}</label>
52 </td> 50 </td>
53 <td> 51 <td>
54 - <label>@include('settings/roles/checkbox', ['permission' => 'book-delete-own']) Own</label> 52 + <label>@include('settings/roles/checkbox', ['permission' => 'book-delete-own']) {{ trans('settings.role_own') }}</label>
55 - <label>@include('settings/roles/checkbox', ['permission' => 'book-delete-all']) All</label> 53 + <label>@include('settings/roles/checkbox', ['permission' => 'book-delete-all']) {{ trans('settings.role_all') }}</label>
56 </td> 54 </td>
57 </tr> 55 </tr>
58 <tr> 56 <tr>
59 - <td>Chapters</td> 57 + <td>{{ trans('entities.chapters') }}</td>
60 <td> 58 <td>
61 - <label>@include('settings/roles/checkbox', ['permission' => 'chapter-create-own']) Own</label> 59 + <label>@include('settings/roles/checkbox', ['permission' => 'chapter-create-own']) {{ trans('settings.role_own') }}</label>
62 - <label>@include('settings/roles/checkbox', ['permission' => 'chapter-create-all']) All</label> 60 + <label>@include('settings/roles/checkbox', ['permission' => 'chapter-create-all']) {{ trans('settings.role_all') }}</label>
63 </td> 61 </td>
64 <td> 62 <td>
65 - <label>@include('settings/roles/checkbox', ['permission' => 'chapter-view-own']) Own</label> 63 + <label>@include('settings/roles/checkbox', ['permission' => 'chapter-view-own']) {{ trans('settings.role_own') }}</label>
66 - <label>@include('settings/roles/checkbox', ['permission' => 'chapter-view-all']) All</label> 64 + <label>@include('settings/roles/checkbox', ['permission' => 'chapter-view-all']) {{ trans('settings.role_all') }}</label>
67 </td> 65 </td>
68 <td> 66 <td>
69 - <label>@include('settings/roles/checkbox', ['permission' => 'chapter-update-own']) Own</label> 67 + <label>@include('settings/roles/checkbox', ['permission' => 'chapter-update-own']) {{ trans('settings.role_own') }}</label>
70 - <label>@include('settings/roles/checkbox', ['permission' => 'chapter-update-all']) All</label> 68 + <label>@include('settings/roles/checkbox', ['permission' => 'chapter-update-all']) {{ trans('settings.role_all') }}</label>
71 </td> 69 </td>
72 <td> 70 <td>
73 - <label>@include('settings/roles/checkbox', ['permission' => 'chapter-delete-own']) Own</label> 71 + <label>@include('settings/roles/checkbox', ['permission' => 'chapter-delete-own']) {{ trans('settings.role_own') }}</label>
74 - <label>@include('settings/roles/checkbox', ['permission' => 'chapter-delete-all']) All</label> 72 + <label>@include('settings/roles/checkbox', ['permission' => 'chapter-delete-all']) {{ trans('settings.role_all') }}</label>
75 </td> 73 </td>
76 </tr> 74 </tr>
77 <tr> 75 <tr>
78 - <td>Pages</td> 76 + <td>{{ trans('entities.pages') }}</td>
79 <td> 77 <td>
80 - <label>@include('settings/roles/checkbox', ['permission' => 'page-create-own']) Own</label> 78 + <label>@include('settings/roles/checkbox', ['permission' => 'page-create-own']) {{ trans('settings.role_own') }}</label>
81 - <label>@include('settings/roles/checkbox', ['permission' => 'page-create-all']) All</label> 79 + <label>@include('settings/roles/checkbox', ['permission' => 'page-create-all']) {{ trans('settings.role_all') }}</label>
82 </td> 80 </td>
83 <td> 81 <td>
84 - <label>@include('settings/roles/checkbox', ['permission' => 'page-view-own']) Own</label> 82 + <label>@include('settings/roles/checkbox', ['permission' => 'page-view-own']) {{ trans('settings.role_own') }}</label>
85 - <label>@include('settings/roles/checkbox', ['permission' => 'page-view-all']) All</label> 83 + <label>@include('settings/roles/checkbox', ['permission' => 'page-view-all']) {{ trans('settings.role_all') }}</label>
86 </td> 84 </td>
87 <td> 85 <td>
88 - <label>@include('settings/roles/checkbox', ['permission' => 'page-update-own']) Own</label> 86 + <label>@include('settings/roles/checkbox', ['permission' => 'page-update-own']) {{ trans('settings.role_own') }}</label>
89 - <label>@include('settings/roles/checkbox', ['permission' => 'page-update-all']) All</label> 87 + <label>@include('settings/roles/checkbox', ['permission' => 'page-update-all']) {{ trans('settings.role_all') }}</label>
90 </td> 88 </td>
91 <td> 89 <td>
92 - <label>@include('settings/roles/checkbox', ['permission' => 'page-delete-own']) Own</label> 90 + <label>@include('settings/roles/checkbox', ['permission' => 'page-delete-own']) {{ trans('settings.role_own') }}</label>
93 - <label>@include('settings/roles/checkbox', ['permission' => 'page-delete-all']) All</label> 91 + <label>@include('settings/roles/checkbox', ['permission' => 'page-delete-all']) {{ trans('settings.role_all') }}</label>
94 </td> 92 </td>
95 </tr> 93 </tr>
96 <tr> 94 <tr>
97 - <td>Images</td> 95 + <td>{{ trans('entities.images') }}</td>
98 <td>@include('settings/roles/checkbox', ['permission' => 'image-create-all'])</td> 96 <td>@include('settings/roles/checkbox', ['permission' => 'image-create-all'])</td>
99 - <td style="line-height:1.2;"><small class="faded">Controlled by the asset they are uploaded to</small></td> 97 + <td style="line-height:1.2;"><small class="faded">{{ trans('settings.role_controlled_by_asset') }}</small></td>
100 <td> 98 <td>
101 - <label>@include('settings/roles/checkbox', ['permission' => 'image-update-own']) Own</label> 99 + <label>@include('settings/roles/checkbox', ['permission' => 'image-update-own']) {{ trans('settings.role_own') }}</label>
102 - <label>@include('settings/roles/checkbox', ['permission' => 'image-update-all']) All</label> 100 + <label>@include('settings/roles/checkbox', ['permission' => 'image-update-all']) {{ trans('settings.role_all') }}</label>
103 </td> 101 </td>
104 <td> 102 <td>
105 - <label>@include('settings/roles/checkbox', ['permission' => 'image-delete-own']) Own</label> 103 + <label>@include('settings/roles/checkbox', ['permission' => 'image-delete-own']) {{ trans('settings.role_own') }}</label>
106 - <label>@include('settings/roles/checkbox', ['permission' => 'image-delete-all']) All</label> 104 + <label>@include('settings/roles/checkbox', ['permission' => 'image-delete-all']) {{ trans('settings.role_all') }}</label>
107 </td> 105 </td>
108 </tr> 106 </tr>
109 <tr> 107 <tr>
110 - <td>Attachments</td> 108 + <td>{{ trans('entities.attachments') }}</td>
111 <td>@include('settings/roles/checkbox', ['permission' => 'attachment-create-all'])</td> 109 <td>@include('settings/roles/checkbox', ['permission' => 'attachment-create-all'])</td>
112 - <td style="line-height:1.2;"><small class="faded">Controlled by the asset they are uploaded to</small></td> 110 + <td style="line-height:1.2;"><small class="faded">{{ trans('settings.role_controlled_by_asset') }}</small></td>
113 <td> 111 <td>
114 - <label>@include('settings/roles/checkbox', ['permission' => 'attachment-update-own']) Own</label> 112 + <label>@include('settings/roles/checkbox', ['permission' => 'attachment-update-own']) {{ trans('settings.role_own') }}</label>
115 - <label>@include('settings/roles/checkbox', ['permission' => 'attachment-update-all']) All</label> 113 + <label>@include('settings/roles/checkbox', ['permission' => 'attachment-update-all']) {{ trans('settings.role_all') }}</label>
116 </td> 114 </td>
117 <td> 115 <td>
118 - <label>@include('settings/roles/checkbox', ['permission' => 'attachment-delete-own']) Own</label> 116 + <label>@include('settings/roles/checkbox', ['permission' => 'attachment-delete-own']) {{ trans('settings.role_own') }}</label>
119 - <label>@include('settings/roles/checkbox', ['permission' => 'attachment-delete-all']) All</label> 117 + <label>@include('settings/roles/checkbox', ['permission' => 'attachment-delete-all']) {{ trans('settings.role_all') }}</label>
120 </td> 118 </td>
121 </tr> 119 </tr>
122 </table> 120 </table>
123 </div> 121 </div>
124 </div> 122 </div>
125 - <a href="{{ baseUrl("/settings/roles") }}" class="button muted">Cancel</a> 123 + <a href="{{ baseUrl("/settings/roles") }}" class="button muted">{{ trans('common.cancel') }}</a>
126 - <button type="submit" class="button pos">Save Role</button> 124 + <button type="submit" class="button pos">{{ trans('settings.role_save') }}</button>
127 </div> 125 </div>
128 <div class="col-md-3"> 126 <div class="col-md-3">
129 - <h3>Users in this role</h3> 127 + <h3>{{ trans('settings.role_users') }}</h3>
130 128
131 @if(isset($role) && count($role->users) > 0) 129 @if(isset($role) && count($role->users) > 0)
132 <table class="list-table"> 130 <table class="list-table">
...@@ -147,7 +145,7 @@ ...@@ -147,7 +145,7 @@
147 </table> 145 </table>
148 @else 146 @else
149 <p class="text-muted"> 147 <p class="text-muted">
150 - No users currently in this role. 148 + {{ trans('settings.role_users_none') }}
151 </p> 149 </p>
152 @endif 150 @endif
153 151
......
...@@ -8,25 +8,25 @@ ...@@ -8,25 +8,25 @@
8 8
9 <div class="row action-header"> 9 <div class="row action-header">
10 <div class="col-sm-8"> 10 <div class="col-sm-8">
11 - <h1>User Roles</h1> 11 + <h1>{{ trans('settings.role_user_roles') }}</h1>
12 </div> 12 </div>
13 <div class="col-sm-4"> 13 <div class="col-sm-4">
14 <p></p> 14 <p></p>
15 - <a href="{{ baseUrl("/settings/roles/new") }}" class="button float right pos"><i class="zmdi zmdi-lock-open"></i>Add new role</a> 15 + <a href="{{ baseUrl("/settings/roles/new") }}" class="button float right pos"><i class="zmdi zmdi-lock-open"></i>{{ trans('settings.role_create') }}</a>
16 </div> 16 </div>
17 </div> 17 </div>
18 18
19 <table class="table"> 19 <table class="table">
20 <tr> 20 <tr>
21 - <th>Role Name</th> 21 + <th>{{ trans('settings.role_name') }}</th>
22 <th></th> 22 <th></th>
23 - <th class="text-right">Users</th> 23 + <th class="text-center">{{ trans('settings.users') }}</th>
24 </tr> 24 </tr>
25 @foreach($roles as $role) 25 @foreach($roles as $role)
26 <tr> 26 <tr>
27 <td><a href="{{ baseUrl("/settings/roles/{$role->id}") }}">{{ $role->display_name }}</a></td> 27 <td><a href="{{ baseUrl("/settings/roles/{$role->id}") }}">{{ $role->display_name }}</a></td>
28 <td>{{ $role->description }}</td> 28 <td>{{ $role->description }}</td>
29 - <td class="text-right">{{ $role->users->count() }}</td> 29 + <td class="text-center">{{ $role->users->count() }}</td>
30 </tr> 30 </tr>
31 @endforeach 31 @endforeach
32 </table> 32 </table>
......
...@@ -3,8 +3,20 @@ ...@@ -3,8 +3,20 @@
3 3
4 @section('content') 4 @section('content')
5 5
6 + <div class="faded-small toolbar">
7 + <div class="container">
8 + <div class="row">
9 + <div class="col-sm-12 faded">
10 + <div class="breadcrumbs">
11 + <a href="{{ baseUrl('/settings/users') }}" class="text-button"><i class="zmdi zmdi-accounts"></i>{{ trans('settings.users') }}</a>
12 + </div>
13 + </div>
14 + </div>
15 + </div>
16 + </div>
17 +
6 <div class="container small" ng-non-bindable> 18 <div class="container small" ng-non-bindable>
7 - <h1>Create User</h1> 19 + <h1>{{ trans('settings.users_add_new') }}</h1>
8 20
9 <form action="{{ baseUrl("/settings/users/create") }}" method="post"> 21 <form action="{{ baseUrl("/settings/users/create") }}" method="post">
10 {!! csrf_field() !!} 22 {!! csrf_field() !!}
......
...@@ -2,16 +2,30 @@ ...@@ -2,16 +2,30 @@
2 2
3 @section('content') 3 @section('content')
4 4
5 + <div class="faded-small toolbar">
6 + <div class="container">
7 + <div class="row">
8 + <div class="col-sm-12 faded">
9 + <div class="breadcrumbs">
10 + <a href="{{ baseUrl("/settings/users") }}" class="text-button"><i class="zmdi zmdi-accounts"></i>Users</a>
11 + <span class="sep">&raquo;</span>
12 + <a href="{{ baseUrl("/settings/users/{$user->id}") }}" class="text-button"><i class="zmdi zmdi-account"></i>{{ $user->name }}</a>
13 + </div>
14 + </div>
15 + </div>
16 + </div>
17 + </div>
18 +
5 <div class="container small" ng-non-bindable> 19 <div class="container small" ng-non-bindable>
6 - <h1>Delete User</h1> 20 + <h1>{{ trans('settings.users_delete') }}</h1>
7 - <p>This will fully delete this user with the name '<span class="text-neg">{{ $user->name }}</span>' from the system.</p> 21 + <p>{{ trans('settings.users_delete_warning', ['userName' => $user->name]) }}</p>
8 - <p class="text-neg">Are you sure you want to delete this user?</p> 22 + <p class="text-neg">{{ trans('settings.users_delete_confirm') }}</p>
9 23
10 <form action="{{ baseUrl("/settings/users/{$user->id}") }}" method="POST"> 24 <form action="{{ baseUrl("/settings/users/{$user->id}") }}" method="POST">
11 {!! csrf_field() !!} 25 {!! csrf_field() !!}
12 <input type="hidden" name="_method" value="DELETE"> 26 <input type="hidden" name="_method" value="DELETE">
13 - <a href="{{ baseUrl("/settings/users/{$user->id}") }}" class="button muted">Cancel</a> 27 + <a href="{{ baseUrl("/settings/users/{$user->id}") }}" class="button muted">{{ trans('common.cancel') }}</a>
14 - <button type="submit" class="button neg">Confirm</button> 28 + <button type="submit" class="button neg">{{ trans('common.confirm') }}</button>
15 </form> 29 </form>
16 </div> 30 </div>
17 31
......
...@@ -5,18 +5,16 @@ ...@@ -5,18 +5,16 @@
5 5
6 @include('settings/navbar', ['selected' => 'users']) 6 @include('settings/navbar', ['selected' => 'users'])
7 7
8 -
9 -
10 <div class="container small"> 8 <div class="container small">
11 <form action="{{ baseUrl("/settings/users/{$user->id}") }}" method="post"> 9 <form action="{{ baseUrl("/settings/users/{$user->id}") }}" method="post">
12 <div class="row"> 10 <div class="row">
13 <div class="col-sm-8"> 11 <div class="col-sm-8">
14 - <h1>Edit {{ $user->id === $currentUser->id ? 'Profile' : 'User' }}</h1> 12 + <h1>{{ $user->id === $currentUser->id ? trans('settings.users_edit_profile') : trans('settings.users_edit') }}</h1>
15 </div> 13 </div>
16 <div class="col-sm-4"> 14 <div class="col-sm-4">
17 <p></p> 15 <p></p>
18 @if($authMethod !== 'system') 16 @if($authMethod !== 'system')
19 - <a href="{{ baseUrl("/settings/users/{$user->id}/delete") }}" class="neg button float right">Delete User</a> 17 + <a href="{{ baseUrl("/settings/users/{$user->id}/delete") }}" class="neg button float right">{{ trans('settings.users_delete') }}</a>
20 @endif 18 @endif
21 </div> 19 </div>
22 </div> 20 </div>
...@@ -29,9 +27,27 @@ ...@@ -29,9 +27,27 @@
29 </div> 27 </div>
30 <div class="col-md-6"> 28 <div class="col-md-6">
31 <div class="form-group" id="logo-control"> 29 <div class="form-group" id="logo-control">
32 - <label for="user-avatar">User Avatar</label> 30 + <label for="user-avatar">{{ trans('settings.users_avatar') }}</label>
33 - <p class="small">This image should be approx 256px square.</p> 31 + <p class="small">{{ trans('settings.users_avatar_desc') }}</p>
34 - <image-picker resize-height="512" resize-width="512" current-image="{{ $user->getAvatar(80) }}" current-id="{{ $user->image_id }}" default-image="{{ baseUrl("/user_avatar.png") }}" name="image_id" show-remove="false" image-class="['avatar' ,'large']"></image-picker> 32 +
33 + @include('components.image-picker', [
34 + 'resizeHeight' => '512',
35 + 'resizeWidth' => '512',
36 + 'showRemove' => false,
37 + 'defaultImage' => baseUrl('/user_avatar.png'),
38 + 'currentImage' => $user->getAvatar(80),
39 + 'currentId' => $user->image_id,
40 + 'name' => 'image_id',
41 + 'imageClass' => 'avatar large'
42 + ])
43 + </div>
44 + <div class="form-group">
45 + <label for="user-language">{{ trans('settings.users_preferred_language') }}</label>
46 + <select name="setting[language]" id="user-language">
47 + @foreach(trans('settings.language_select') as $lang => $label)
48 + <option @if(setting()->getUser($user, 'language') === $lang) selected @endif value="{{ $lang }}">{{ $label }}</option>
49 + @endforeach
50 + </select>
35 </div> 51 </div>
36 </div> 52 </div>
37 </div> 53 </div>
...@@ -40,20 +56,17 @@ ...@@ -40,20 +56,17 @@
40 <hr class="margin-top large"> 56 <hr class="margin-top large">
41 57
42 @if($currentUser->id === $user->id && count($activeSocialDrivers) > 0) 58 @if($currentUser->id === $user->id && count($activeSocialDrivers) > 0)
43 - <h3>Social Accounts</h3> 59 + <h3>{{ trans('settings.users_social_accounts') }}</h3>
44 - <p class="text-muted"> 60 + <p class="text-muted">{{ trans('settings.users_social_accounts_info') }}</p>
45 - Here you can connect your other accounts for quicker and easier login. <br>
46 - Disconnecting an account here does not previously authorized access. Revoke access from your profile settings on the connected social account.
47 - </p>
48 <div class="row"> 61 <div class="row">
49 @if(isset($activeSocialDrivers['google'])) 62 @if(isset($activeSocialDrivers['google']))
50 <div class="col-md-3 text-center"> 63 <div class="col-md-3 text-center">
51 <div><i class="zmdi zmdi-google-plus-box zmdi-hc-4x" style="color: #DC4E41;"></i></div> 64 <div><i class="zmdi zmdi-google-plus-box zmdi-hc-4x" style="color: #DC4E41;"></i></div>
52 <div> 65 <div>
53 @if($user->hasSocialAccount('google')) 66 @if($user->hasSocialAccount('google'))
54 - <a href="{{ baseUrl("/login/service/google/detach") }}" class="button neg">Disconnect Account</a> 67 + <a href="{{ baseUrl("/login/service/google/detach") }}" class="button neg">{{ trans('settings.users_social_disconnect') }}</a>
55 @else 68 @else
56 - <a href="{{ baseUrl("/login/service/google") }}" class="button pos">Attach Account</a> 69 + <a href="{{ baseUrl("/login/service/google") }}" class="button pos">{{ trans('settings.users_social_connect') }}</a>
57 @endif 70 @endif
58 </div> 71 </div>
59 </div> 72 </div>
...@@ -63,9 +76,9 @@ ...@@ -63,9 +76,9 @@
63 <div><i class="zmdi zmdi-github zmdi-hc-4x" style="color: #444;"></i></div> 76 <div><i class="zmdi zmdi-github zmdi-hc-4x" style="color: #444;"></i></div>
64 <div> 77 <div>
65 @if($user->hasSocialAccount('github')) 78 @if($user->hasSocialAccount('github'))
66 - <a href="{{ baseUrl("/login/service/github/detach") }}" class="button neg">Disconnect Account</a> 79 + <a href="{{ baseUrl("/login/service/github/detach") }}" class="button neg">{{ trans('settings.users_social_disconnect') }}</a>
67 @else 80 @else
68 - <a href="{{ baseUrl("/login/service/github") }}" class="button pos">Attach Account</a> 81 + <a href="{{ baseUrl("/login/service/github") }}" class="button pos">{{ trans('settings.users_social_connect') }}</a>
69 @endif 82 @endif
70 </div> 83 </div>
71 </div> 84 </div>
...@@ -77,5 +90,5 @@ ...@@ -77,5 +90,5 @@
77 </div> 90 </div>
78 91
79 <p class="margin-top large"><br></p> 92 <p class="margin-top large"><br></p>
80 - @include('partials/image-manager', ['imageType' => 'user']) 93 + @include('components.image-manager', ['imageType' => 'user'])
81 @stop 94 @stop
......
1 <div class="form-group"> 1 <div class="form-group">
2 - <label for="name">Name</label> 2 + <label for="name">{{ trans('auth.name') }}</label>
3 @include('form.text', ['name' => 'name']) 3 @include('form.text', ['name' => 'name'])
4 </div> 4 </div>
5 5
6 @if(userCan('users-manage')) 6 @if(userCan('users-manage'))
7 <div class="form-group"> 7 <div class="form-group">
8 - <label for="email">Email</label> 8 + <label for="email">{{ trans('auth.email') }}</label>
9 @include('form.text', ['name' => 'email']) 9 @include('form.text', ['name' => 'email'])
10 </div> 10 </div>
11 @endif 11 @endif
12 12
13 @if(userCan('users-manage')) 13 @if(userCan('users-manage'))
14 <div class="form-group"> 14 <div class="form-group">
15 - <label for="role">User Role</label> 15 + <label for="role">{{ trans('settings.users_role') }}</label>
16 @include('form/role-checkboxes', ['name' => 'roles', 'roles' => $roles]) 16 @include('form/role-checkboxes', ['name' => 'roles', 'roles' => $roles])
17 </div> 17 </div>
18 @endif 18 @endif
19 19
20 @if(userCan('users-manage')) 20 @if(userCan('users-manage'))
21 <div class="form-group"> 21 <div class="form-group">
22 - <label for="external_auth_id">External Authentication ID</label> 22 + <label for="external_auth_id">{{ trans('settings.users_external_auth_id') }}</label>
23 @include('form.text', ['name' => 'external_auth_id']) 23 @include('form.text', ['name' => 'external_auth_id'])
24 </div> 24 </div>
25 @endif 25 @endif
26 26
27 <div class="form-group"> 27 <div class="form-group">
28 - <a href="{{ baseUrl("/settings/users") }}" class="button muted">Cancel</a> 28 + <a href="{{ baseUrl("/settings/users") }}" class="button muted">{{ trans('common.cancel') }}</a>
29 - <button class="button pos" type="submit">Save</button> 29 + <button class="button pos" type="submit">{{ trans('common.save') }}</button>
30 </div> 30 </div>
...\ No newline at end of file ...\ No newline at end of file
......
1 <div class="form-group"> 1 <div class="form-group">
2 - <label for="name">Name</label> 2 + <label for="name">{{ trans('auth.name') }}</label>
3 @include('form.text', ['name' => 'name']) 3 @include('form.text', ['name' => 'name'])
4 </div> 4 </div>
5 5
6 <div class="form-group"> 6 <div class="form-group">
7 - <label for="email">Email</label> 7 + <label for="email">{{ trans('auth.email') }}</label>
8 @include('form.text', ['name' => 'email']) 8 @include('form.text', ['name' => 'email'])
9 </div> 9 </div>
10 10
11 @if(userCan('users-manage')) 11 @if(userCan('users-manage'))
12 <div class="form-group"> 12 <div class="form-group">
13 - <label for="role">User Role</label> 13 + <label for="role">{{ trans('settings.users_role') }}</label>
14 @include('form/role-checkboxes', ['name' => 'roles', 'roles' => $roles]) 14 @include('form/role-checkboxes', ['name' => 'roles', 'roles' => $roles])
15 </div> 15 </div>
16 @endif 16 @endif
...@@ -18,23 +18,23 @@ ...@@ -18,23 +18,23 @@
18 @if(isset($model)) 18 @if(isset($model))
19 <div class="form-group"> 19 <div class="form-group">
20 <span class="text-muted"> 20 <span class="text-muted">
21 - Only fill the below if you would like <br>to change your password: 21 + {{ trans('settings.users_password_warning') }}
22 </span> 22 </span>
23 </div> 23 </div>
24 @endif 24 @endif
25 25
26 <div class="form-group"> 26 <div class="form-group">
27 - <label for="password">Password</label> 27 + <label for="password">{{ trans('auth.password') }}</label>
28 @include('form.password', ['name' => 'password']) 28 @include('form.password', ['name' => 'password'])
29 </div> 29 </div>
30 30
31 <div class="form-group"> 31 <div class="form-group">
32 - <label for="password-confirm">Confirm Password</label> 32 + <label for="password-confirm">{{ trans('auth.password_confirm') }}</label>
33 @include('form.password', ['name' => 'password-confirm']) 33 @include('form.password', ['name' => 'password-confirm'])
34 </div> 34 </div>
35 35
36 <div class="form-group"> 36 <div class="form-group">
37 - <a href="{{ baseUrl("/settings/users") }}" class="button muted">Cancel</a> 37 + <a href="{{ baseUrl("/settings/users") }}" class="button muted">{{ trans('common.cancel') }}</a>
38 - <button class="button pos" type="submit">Save</button> 38 + <button class="button pos" type="submit">{{ trans('common.save') }}</button>
39 </div> 39 </div>
40 40
......
1 @if($user->system_name == 'public') 1 @if($user->system_name == 'public')
2 - <p>This user represents any guest users that visit your instance. It cannot be used for logins but is assigned&nbsp;automatically.</p> 2 + <p>{{ trans('settings.users_system_public') }}</p>
3 @endif 3 @endif
4 4
5 <div class="form-group"> 5 <div class="form-group">
6 - <label for="name">Name</label> 6 + <label for="name">{{ trans('auth.name') }}</label>
7 @include('form.text', ['name' => 'name']) 7 @include('form.text', ['name' => 'name'])
8 </div> 8 </div>
9 9
10 <div class="form-group"> 10 <div class="form-group">
11 - <label for="email">Email</label> 11 + <label for="email">{{ trans('auth.email') }}</label>
12 @include('form.text', ['name' => 'email']) 12 @include('form.text', ['name' => 'email'])
13 </div> 13 </div>
14 14
15 @if(userCan('users-manage')) 15 @if(userCan('users-manage'))
16 <div class="form-group"> 16 <div class="form-group">
17 - <label for="role">User Role</label> 17 + <label for="role">{{ trans('settings.users_role') }}</label>
18 @include('form/role-checkboxes', ['name' => 'roles', 'roles' => $roles]) 18 @include('form/role-checkboxes', ['name' => 'roles', 'roles' => $roles])
19 </div> 19 </div>
20 @endif 20 @endif
21 21
22 <div class="form-group"> 22 <div class="form-group">
23 - <a href="{{ baseUrl("/settings/users") }}" class="button muted">Cancel</a> 23 + <a href="{{ baseUrl("/settings/users") }}" class="button muted">{{ trans('common.cancel') }}</a>
24 - <button class="button pos" type="submit">Save</button> 24 + <button class="button pos" type="submit">{{ trans('common.save') }}</button>
25 </div> 25 </div>
26 +
......
...@@ -9,12 +9,12 @@ ...@@ -9,12 +9,12 @@
9 <div class="container small" ng-non-bindable> 9 <div class="container small" ng-non-bindable>
10 <div class="row action-header"> 10 <div class="row action-header">
11 <div class="col-sm-8"> 11 <div class="col-sm-8">
12 - <h1>Users</h1> 12 + <h1>{{ trans('settings.users') }}</h1>
13 </div> 13 </div>
14 <div class="col-sm-4"> 14 <div class="col-sm-4">
15 <p></p> 15 <p></p>
16 @if(userCan('users-manage')) 16 @if(userCan('users-manage'))
17 - <a href="{{ baseUrl("/settings/users/create") }}" class="pos button float right"><i class="zmdi zmdi-account-add"></i>Add new user</a> 17 + <a href="{{ baseUrl("/settings/users/create") }}" class="pos button float right"><i class="zmdi zmdi-account-add"></i>{{ trans('settings.users_add_new') }}</a>
18 @endif 18 @endif
19 </div> 19 </div>
20 </div> 20 </div>
...@@ -30,20 +30,17 @@ ...@@ -30,20 +30,17 @@
30 @foreach(collect($listDetails)->except('search') as $name => $val) 30 @foreach(collect($listDetails)->except('search') as $name => $val)
31 <input type="hidden" name="{{ $name }}" value="{{ $val }}"> 31 <input type="hidden" name="{{ $name }}" value="{{ $val }}">
32 @endforeach 32 @endforeach
33 - <input type="text" name="search" placeholder="Search Users" @if($listDetails['search']) value="{{$listDetails['search']}}" @endif> 33 + <input type="text" name="search" placeholder="{{ trans('settings.users_search') }}" @if($listDetails['search']) value="{{$listDetails['search']}}" @endif>
34 </form> 34 </form>
35 </div> 35 </div>
36 </div> 36 </div>
37 - <div class="text-center">
38 -
39 - </div>
40 37
41 <table class="table"> 38 <table class="table">
42 <tr> 39 <tr>
43 <th></th> 40 <th></th>
44 - <th><a href="{{ sortUrl('/settings/users', $listDetails, ['sort' => 'name']) }}">Name</a></th> 41 + <th><a href="{{ sortUrl('/settings/users', $listDetails, ['sort' => 'name']) }}">{{ trans('auth.name') }}</a></th>
45 - <th><a href="{{ sortUrl('/settings/users', $listDetails, ['sort' => 'email']) }}">Email</a></th> 42 + <th><a href="{{ sortUrl('/settings/users', $listDetails, ['sort' => 'email']) }}">{{ trans('auth.email') }}</a></th>
46 - <th>User Roles</th> 43 + <th>{{ trans('settings.role_user_roles') }}</th>
47 </tr> 44 </tr>
48 @foreach($users as $user) 45 @foreach($users as $user)
49 <tr> 46 <tr>
......
...@@ -17,21 +17,21 @@ ...@@ -17,21 +17,21 @@
17 <div> 17 <div>
18 <h3 style="margin-top: 0;">{{ $user->name }}</h3> 18 <h3 style="margin-top: 0;">{{ $user->name }}</h3>
19 <p class="text-muted"> 19 <p class="text-muted">
20 - User for {{ $user->created_at->diffForHumans(null, true) }} 20 + {{ trans('entities.profile_user_for_x', ['time' => $user->created_at->diffForHumans(null, true)]) }}
21 </p> 21 </p>
22 </div> 22 </div>
23 </div> 23 </div>
24 </div> 24 </div>
25 <div class="col-md-5 text-bigger" id="content-counts"> 25 <div class="col-md-5 text-bigger" id="content-counts">
26 - <div class="text-muted">Created Content</div> 26 + <div class="text-muted">{{ trans('entities.profile_created_content') }}</div>
27 <div class="text-book"> 27 <div class="text-book">
28 - <i class="zmdi zmdi-book zmdi-hc-fw"></i> {{ $assetCounts['books'] }} {{ str_plural('Book', $assetCounts['books']) }} 28 + <i class="zmdi zmdi-book zmdi-hc-fw"></i> {{ $assetCounts['books'] }} {{ str_plural(trans('entities.book'), $assetCounts['books']) }}
29 </div> 29 </div>
30 <div class="text-chapter"> 30 <div class="text-chapter">
31 - <i class="zmdi zmdi-collection-bookmark zmdi-hc-fw"></i> {{ $assetCounts['chapters'] }} {{ str_plural('Chapter', $assetCounts['chapters']) }} 31 + <i class="zmdi zmdi-collection-bookmark zmdi-hc-fw"></i> {{ $assetCounts['chapters'] }} {{ str_plural(trans('entities.chapter'), $assetCounts['chapters']) }}
32 </div> 32 </div>
33 <div class="text-page"> 33 <div class="text-page">
34 - <i class="zmdi zmdi-file-text zmdi-hc-fw"></i> {{ $assetCounts['pages'] }} {{ str_plural('Page', $assetCounts['pages']) }} 34 + <i class="zmdi zmdi-file-text zmdi-hc-fw"></i> {{ $assetCounts['pages'] }} {{ str_plural(trans('entities.page'), $assetCounts['pages']) }}
35 </div> 35 </div>
36 </div> 36 </div>
37 </div> 37 </div>
...@@ -39,34 +39,34 @@ ...@@ -39,34 +39,34 @@
39 39
40 <hr class="even"> 40 <hr class="even">
41 41
42 - <h3>Recently Created Pages</h3> 42 + <h3>{{ trans('entities.recently_created_pages') }}</h3>
43 @if (count($recentlyCreated['pages']) > 0) 43 @if (count($recentlyCreated['pages']) > 0)
44 @include('partials/entity-list', ['entities' => $recentlyCreated['pages']]) 44 @include('partials/entity-list', ['entities' => $recentlyCreated['pages']])
45 @else 45 @else
46 - <p class="text-muted">{{ $user->name }} has not created any pages</p> 46 + <p class="text-muted">{{ trans('entities.profile_not_created_pages', ['userName' => $user->name]) }}</p>
47 @endif 47 @endif
48 48
49 <hr class="even"> 49 <hr class="even">
50 50
51 - <h3>Recently Created Chapters</h3> 51 + <h3>{{ trans('entities.recently_created_chapters') }}</h3>
52 @if (count($recentlyCreated['chapters']) > 0) 52 @if (count($recentlyCreated['chapters']) > 0)
53 @include('partials/entity-list', ['entities' => $recentlyCreated['chapters']]) 53 @include('partials/entity-list', ['entities' => $recentlyCreated['chapters']])
54 @else 54 @else
55 - <p class="text-muted">{{ $user->name }} has not created any chapters</p> 55 + <p class="text-muted">{{ trans('entities.profile_not_created_chapters', ['userName' => $user->name]) }}</p>
56 @endif 56 @endif
57 57
58 <hr class="even"> 58 <hr class="even">
59 59
60 - <h3>Recently Created Books</h3> 60 + <h3>{{ trans('entities.recently_created_books') }}</h3>
61 @if (count($recentlyCreated['books']) > 0) 61 @if (count($recentlyCreated['books']) > 0)
62 @include('partials/entity-list', ['entities' => $recentlyCreated['books']]) 62 @include('partials/entity-list', ['entities' => $recentlyCreated['books']])
63 @else 63 @else
64 - <p class="text-muted">{{ $user->name }} has not created any books</p> 64 + <p class="text-muted">{{ trans('entities.profile_not_created_books', ['userName' => $user->name]) }}</p>
65 @endif 65 @endif
66 </div> 66 </div>
67 67
68 <div class="col-sm-4 col-sm-offset-1" id="recent-activity"> 68 <div class="col-sm-4 col-sm-offset-1" id="recent-activity">
69 - <h3>Recent Activity</h3> 69 + <h3>{{ trans('entities.recent_activity') }}</h3>
70 @include('partials/activity-list', ['activity' => $activity]) 70 @include('partials/activity-list', ['activity' => $activity])
71 </div> 71 </div>
72 72
......
...@@ -18,5 +18,5 @@ if (! empty($outroLines)) { ...@@ -18,5 +18,5 @@ if (! empty($outroLines)) {
18 echo implode("\n", $outroLines), "\n\n"; 18 echo implode("\n", $outroLines), "\n\n";
19 } 19 }
20 20
21 -echo 'Regards,', "\n"; 21 +echo "\n";
22 -echo config('app.name'), "\n"; 22 +echo setting('app-name'), "\n";
......
...@@ -159,8 +159,7 @@ $style = [ ...@@ -159,8 +159,7 @@ $style = [
159 <tr> 159 <tr>
160 <td style="{{ $fontFamily }}"> 160 <td style="{{ $fontFamily }}">
161 <p style="{{ $style['paragraph-sub'] }}"> 161 <p style="{{ $style['paragraph-sub'] }}">
162 - If you’re having trouble clicking the "{{ $actionText }}" button, 162 + {{ trans('common.email_action_help', ['actionText' => $actionText]) }}
163 - copy and paste the URL below into your web browser:
164 </p> 163 </p>
165 164
166 <p style="{{ $style['paragraph-sub'] }}"> 165 <p style="{{ $style['paragraph-sub'] }}">
...@@ -188,7 +187,7 @@ $style = [ ...@@ -188,7 +187,7 @@ $style = [
188 <p style="{{ $style['paragraph-sub'] }}"> 187 <p style="{{ $style['paragraph-sub'] }}">
189 &copy; {{ date('Y') }} 188 &copy; {{ date('Y') }}
190 <a style="{{ $style['anchor'] }}" href="{{ baseUrl('/') }}" target="_blank">{{ setting('app-name') }}</a>. 189 <a style="{{ $style['anchor'] }}" href="{{ baseUrl('/') }}" target="_blank">{{ setting('app-name') }}</a>.
191 - All rights reserved. 190 + {{ trans('common.email_rights') }}
192 </p> 191 </p>
193 </td> 192 </td>
194 </tr> 193 </tr>
......
1 <?php 1 <?php
2 2
3 +Route::get('/translations.js', 'HomeController@getTranslations');
4 +
3 // Authenticated routes... 5 // Authenticated routes...
4 Route::group(['middleware' => 'auth'], function () { 6 Route::group(['middleware' => 'auth'], function () {
5 7
...@@ -107,7 +109,6 @@ Route::group(['middleware' => 'auth'], function () { ...@@ -107,7 +109,6 @@ Route::group(['middleware' => 'auth'], function () {
107 Route::get('/get/{entityType}/{entityId}', 'TagController@getForEntity'); 109 Route::get('/get/{entityType}/{entityId}', 'TagController@getForEntity');
108 Route::get('/suggest/names', 'TagController@getNameSuggestions'); 110 Route::get('/suggest/names', 'TagController@getNameSuggestions');
109 Route::get('/suggest/values', 'TagController@getValueSuggestions'); 111 Route::get('/suggest/values', 'TagController@getValueSuggestions');
110 - Route::post('/update/{entityType}/{entityId}', 'TagController@updateForEntity');
111 }); 112 });
112 113
113 Route::get('/ajax/search/entities', 'SearchController@searchEntitiesAjax'); 114 Route::get('/ajax/search/entities', 'SearchController@searchEntitiesAjax');
......
...@@ -24,7 +24,7 @@ class AuthTest extends TestCase ...@@ -24,7 +24,7 @@ class AuthTest extends TestCase
24 $settings->put('app-public', 'true'); 24 $settings->put('app-public', 'true');
25 $this->visit('/') 25 $this->visit('/')
26 ->seePageIs('/') 26 ->seePageIs('/')
27 - ->see('Sign In'); 27 + ->see('Log In');
28 } 28 }
29 29
30 public function test_registration_showing() 30 public function test_registration_showing()
...@@ -132,7 +132,7 @@ class AuthTest extends TestCase ...@@ -132,7 +132,7 @@ class AuthTest extends TestCase
132 132
133 $this->asAdmin() 133 $this->asAdmin()
134 ->visit('/settings/users') 134 ->visit('/settings/users')
135 - ->click('Add new user') 135 + ->click('Add New User')
136 ->type($user->name, '#name') 136 ->type($user->name, '#name')
137 ->type($user->email, '#email') 137 ->type($user->email, '#email')
138 ->check('roles[admin]') 138 ->check('roles[admin]')
...@@ -245,7 +245,7 @@ class AuthTest extends TestCase ...@@ -245,7 +245,7 @@ class AuthTest extends TestCase
245 { 245 {
246 $this->setSettings(['registration-enabled' => 'true']); 246 $this->setSettings(['registration-enabled' => 'true']);
247 $this->visit('/password/email') 247 $this->visit('/password/email')
248 - ->seeLink('Sign in') 248 + ->seeLink('Log in')
249 ->seeLink('Sign up'); 249 ->seeLink('Sign up');
250 } 250 }
251 251
...@@ -260,6 +260,6 @@ class AuthTest extends TestCase ...@@ -260,6 +260,6 @@ class AuthTest extends TestCase
260 return $this->visit('/login') 260 return $this->visit('/login')
261 ->type($email, '#email') 261 ->type($email, '#email')
262 ->type($password, '#password') 262 ->type($password, '#password')
263 - ->press('Sign In'); 263 + ->press('Log In');
264 } 264 }
265 } 265 }
......
...@@ -36,11 +36,11 @@ class LdapTest extends \TestCase ...@@ -36,11 +36,11 @@ class LdapTest extends \TestCase
36 ->see('Username') 36 ->see('Username')
37 ->type($this->mockUser->name, '#username') 37 ->type($this->mockUser->name, '#username')
38 ->type($this->mockUser->password, '#password') 38 ->type($this->mockUser->password, '#password')
39 - ->press('Sign In') 39 + ->press('Log In')
40 ->seePageIs('/login')->see('Please enter an email to use for this account.'); 40 ->seePageIs('/login')->see('Please enter an email to use for this account.');
41 41
42 $this->type($this->mockUser->email, '#email') 42 $this->type($this->mockUser->email, '#email')
43 - ->press('Sign In') 43 + ->press('Log In')
44 ->seePageIs('/') 44 ->seePageIs('/')
45 ->see($this->mockUser->name) 45 ->see($this->mockUser->name)
46 ->seeInDatabase('users', ['email' => $this->mockUser->email, 'email_confirmed' => false, 'external_auth_id' => $this->mockUser->name]); 46 ->seeInDatabase('users', ['email' => $this->mockUser->email, 'email_confirmed' => false, 'external_auth_id' => $this->mockUser->name]);
...@@ -64,7 +64,7 @@ class LdapTest extends \TestCase ...@@ -64,7 +64,7 @@ class LdapTest extends \TestCase
64 ->see('Username') 64 ->see('Username')
65 ->type($this->mockUser->name, '#username') 65 ->type($this->mockUser->name, '#username')
66 ->type($this->mockUser->password, '#password') 66 ->type($this->mockUser->password, '#password')
67 - ->press('Sign In') 67 + ->press('Log In')
68 ->seePageIs('/') 68 ->seePageIs('/')
69 ->see($this->mockUser->name) 69 ->see($this->mockUser->name)
70 ->seeInDatabase('users', ['email' => $this->mockUser->email, 'email_confirmed' => false, 'external_auth_id' => $ldapDn]); 70 ->seeInDatabase('users', ['email' => $this->mockUser->email, 'email_confirmed' => false, 'external_auth_id' => $ldapDn]);
...@@ -87,7 +87,7 @@ class LdapTest extends \TestCase ...@@ -87,7 +87,7 @@ class LdapTest extends \TestCase
87 ->see('Username') 87 ->see('Username')
88 ->type($this->mockUser->name, '#username') 88 ->type($this->mockUser->name, '#username')
89 ->type($this->mockUser->password, '#password') 89 ->type($this->mockUser->password, '#password')
90 - ->press('Sign In') 90 + ->press('Log In')
91 ->seePageIs('/login')->see('These credentials do not match our records.') 91 ->seePageIs('/login')->see('These credentials do not match our records.')
92 ->dontSeeInDatabase('users', ['external_auth_id' => $this->mockUser->name]); 92 ->dontSeeInDatabase('users', ['external_auth_id' => $this->mockUser->name]);
93 } 93 }
......
...@@ -32,4 +32,48 @@ class SocialAuthTest extends TestCase ...@@ -32,4 +32,48 @@ class SocialAuthTest extends TestCase
32 $this->seeInDatabase('social_accounts', ['user_id' => $user->id]); 32 $this->seeInDatabase('social_accounts', ['user_id' => $user->id]);
33 } 33 }
34 34
35 + public function test_social_login()
36 + {
37 + $user = factory(\BookStack\User::class)->make();
38 +
39 + config([
40 + 'GOOGLE_APP_ID' => 'abc123', 'GOOGLE_APP_SECRET' => '123abc',
41 + 'GITHUB_APP_ID' => 'abc123', 'GITHUB_APP_SECRET' => '123abc',
42 + 'APP_URL' => 'http://localhost'
43 + ]);
44 +
45 + $mockSocialite = Mockery::mock('Laravel\Socialite\Contracts\Factory');
46 + $this->app['Laravel\Socialite\Contracts\Factory'] = $mockSocialite;
47 + $mockSocialDriver = Mockery::mock('Laravel\Socialite\Contracts\Provider');
48 + $mockSocialUser = Mockery::mock('\Laravel\Socialite\Contracts\User');
49 +
50 + $mockSocialUser->shouldReceive('getId')->twice()->andReturn('logintest123');
51 +
52 + $mockSocialDriver->shouldReceive('user')->twice()->andReturn($mockSocialUser);
53 + $mockSocialite->shouldReceive('driver')->twice()->with('google')->andReturn($mockSocialDriver);
54 + $mockSocialite->shouldReceive('driver')->twice()->with('github')->andReturn($mockSocialDriver);
55 + $mockSocialDriver->shouldReceive('redirect')->twice()->andReturn(redirect('/'));
56 +
57 + // Test login routes
58 + $this->visit('/login')->seeElement('#social-login-google')
59 + ->click('#social-login-google')
60 + ->seePageIs('/login');
61 +
62 + // Test social callback
63 + $this->visit('/login/service/google/callback')->seePageIs('/login')
64 + ->see(trans('errors.social_account_not_used', ['socialAccount' => 'Google']));
65 +
66 + $this->visit('/login')->seeElement('#social-login-github')
67 + ->click('#social-login-github')
68 + ->seePageIs('/login');
69 +
70 + // Test social callback with matching social account
71 + DB::table('social_accounts')->insert([
72 + 'user_id' => $this->getAdmin()->id,
73 + 'driver' => 'github',
74 + 'driver_id' => 'logintest123'
75 + ]);
76 + $this->visit('/login/service/github/callback')->seePageIs('/');
77 + }
78 +
35 } 79 }
......
...@@ -15,8 +15,8 @@ class EntitySearchTest extends TestCase ...@@ -15,8 +15,8 @@ class EntitySearchTest extends TestCase
15 ->type($page->name, 'term') 15 ->type($page->name, 'term')
16 ->press('header-search-box-button') 16 ->press('header-search-box-button')
17 ->see('Search Results') 17 ->see('Search Results')
18 - ->see($page->name) 18 + ->seeInElement('.entity-list', $page->name)
19 - ->click($page->name) 19 + ->clickInElement('.entity-list', $page->name)
20 ->seePageIs($page->getUrl()); 20 ->seePageIs($page->getUrl());
21 } 21 }
22 22
......
...@@ -136,7 +136,7 @@ class EntityTest extends TestCase ...@@ -136,7 +136,7 @@ class EntityTest extends TestCase
136 $this->asAdmin() 136 $this->asAdmin()
137 ->visit('/books') 137 ->visit('/books')
138 // Choose to create a book 138 // Choose to create a book
139 - ->click('Add new book') 139 + ->click('Create New Book')
140 ->seePageIs('/books/create') 140 ->seePageIs('/books/create')
141 // Fill out form & save 141 // Fill out form & save
142 ->type($book->name, '#name') 142 ->type($book->name, '#name')
...@@ -168,7 +168,7 @@ class EntityTest extends TestCase ...@@ -168,7 +168,7 @@ class EntityTest extends TestCase
168 $entities = $this->createEntityChainBelongingToUser($creator, $updater); 168 $entities = $this->createEntityChainBelongingToUser($creator, $updater);
169 $this->actingAs($creator); 169 $this->actingAs($creator);
170 app('BookStack\Repos\UserRepo')->destroy($creator); 170 app('BookStack\Repos\UserRepo')->destroy($creator);
171 - app('BookStack\Repos\PageRepo')->saveRevision($entities['page']); 171 + app('BookStack\Repos\EntityRepo')->savePageRevision($entities['page']);
172 172
173 $this->checkEntitiesViewable($entities); 173 $this->checkEntitiesViewable($entities);
174 } 174 }
...@@ -181,7 +181,7 @@ class EntityTest extends TestCase ...@@ -181,7 +181,7 @@ class EntityTest extends TestCase
181 $entities = $this->createEntityChainBelongingToUser($creator, $updater); 181 $entities = $this->createEntityChainBelongingToUser($creator, $updater);
182 $this->actingAs($updater); 182 $this->actingAs($updater);
183 app('BookStack\Repos\UserRepo')->destroy($updater); 183 app('BookStack\Repos\UserRepo')->destroy($updater);
184 - app('BookStack\Repos\PageRepo')->saveRevision($entities['page']); 184 + app('BookStack\Repos\EntityRepo')->savePageRevision($entities['page']);
185 185
186 $this->checkEntitiesViewable($entities); 186 $this->checkEntitiesViewable($entities);
187 } 187 }
......
1 +<?php
2 +
3 +class PageContentTest extends TestCase
4 +{
5 +
6 + public function test_page_includes()
7 + {
8 + $page = \BookStack\Page::first();
9 + $secondPage = \BookStack\Page::all()->get(2);
10 +
11 + $secondPage->html = "<p id='section1'>Hello, This is a test</p><p id='section2'>This is a second block of content</p>";
12 + $secondPage->save();
13 +
14 + $this->asAdmin()->visit($page->getUrl())
15 + ->dontSee('Hello, This is a test');
16 +
17 + $originalHtml = $page->html;
18 + $page->html .= "{{@{$secondPage->id}}}";
19 + $page->save();
20 +
21 + $this->asAdmin()->visit($page->getUrl())
22 + ->see('Hello, This is a test')
23 + ->see('This is a second block of content');
24 +
25 + $page->html = $originalHtml . " Well {{@{$secondPage->id}#section2}}";
26 + $page->save();
27 +
28 + $this->asAdmin()->visit($page->getUrl())
29 + ->dontSee('Hello, This is a test')
30 + ->see('Well This is a second block of content');
31 + }
32 +
33 +}
...@@ -4,13 +4,13 @@ ...@@ -4,13 +4,13 @@
4 class PageDraftTest extends TestCase 4 class PageDraftTest extends TestCase
5 { 5 {
6 protected $page; 6 protected $page;
7 - protected $pageRepo; 7 + protected $entityRepo;
8 8
9 public function setUp() 9 public function setUp()
10 { 10 {
11 parent::setUp(); 11 parent::setUp();
12 $this->page = \BookStack\Page::first(); 12 $this->page = \BookStack\Page::first();
13 - $this->pageRepo = app('\BookStack\Repos\PageRepo'); 13 + $this->entityRepo = app('\BookStack\Repos\EntityRepo');
14 } 14 }
15 15
16 public function test_draft_content_shows_if_available() 16 public function test_draft_content_shows_if_available()
...@@ -20,7 +20,7 @@ class PageDraftTest extends TestCase ...@@ -20,7 +20,7 @@ class PageDraftTest extends TestCase
20 ->dontSeeInField('html', $addedContent); 20 ->dontSeeInField('html', $addedContent);
21 21
22 $newContent = $this->page->html . $addedContent; 22 $newContent = $this->page->html . $addedContent;
23 - $this->pageRepo->saveUpdateDraft($this->page, ['html' => $newContent]); 23 + $this->entityRepo->updatePageDraft($this->page, ['html' => $newContent]);
24 $this->asAdmin()->visit($this->page->getUrl() . '/edit') 24 $this->asAdmin()->visit($this->page->getUrl() . '/edit')
25 ->seeInField('html', $newContent); 25 ->seeInField('html', $newContent);
26 } 26 }
...@@ -33,7 +33,7 @@ class PageDraftTest extends TestCase ...@@ -33,7 +33,7 @@ class PageDraftTest extends TestCase
33 33
34 $newContent = $this->page->html . $addedContent; 34 $newContent = $this->page->html . $addedContent;
35 $newUser = $this->getEditor(); 35 $newUser = $this->getEditor();
36 - $this->pageRepo->saveUpdateDraft($this->page, ['html' => $newContent]); 36 + $this->entityRepo->updatePageDraft($this->page, ['html' => $newContent]);
37 $this->actingAs($newUser)->visit($this->page->getUrl() . '/edit') 37 $this->actingAs($newUser)->visit($this->page->getUrl() . '/edit')
38 ->dontSeeInField('html', $newContent); 38 ->dontSeeInField('html', $newContent);
39 } 39 }
...@@ -41,7 +41,7 @@ class PageDraftTest extends TestCase ...@@ -41,7 +41,7 @@ class PageDraftTest extends TestCase
41 public function test_alert_message_shows_if_editing_draft() 41 public function test_alert_message_shows_if_editing_draft()
42 { 42 {
43 $this->asAdmin(); 43 $this->asAdmin();
44 - $this->pageRepo->saveUpdateDraft($this->page, ['html' => 'test content']); 44 + $this->entityRepo->updatePageDraft($this->page, ['html' => 'test content']);
45 $this->asAdmin()->visit($this->page->getUrl() . '/edit') 45 $this->asAdmin()->visit($this->page->getUrl() . '/edit')
46 ->see('You are currently editing a draft'); 46 ->see('You are currently editing a draft');
47 } 47 }
...@@ -55,7 +55,7 @@ class PageDraftTest extends TestCase ...@@ -55,7 +55,7 @@ class PageDraftTest extends TestCase
55 55
56 $newContent = $this->page->html . $addedContent; 56 $newContent = $this->page->html . $addedContent;
57 $newUser = $this->getEditor(); 57 $newUser = $this->getEditor();
58 - $this->pageRepo->saveUpdateDraft($this->page, ['html' => $newContent]); 58 + $this->entityRepo->updatePageDraft($this->page, ['html' => $newContent]);
59 59
60 $this->actingAs($newUser) 60 $this->actingAs($newUser)
61 ->visit($this->page->getUrl() . '/edit') 61 ->visit($this->page->getUrl() . '/edit')
......
...@@ -13,8 +13,8 @@ class SortTest extends TestCase ...@@ -13,8 +13,8 @@ class SortTest extends TestCase
13 public function test_drafts_do_not_show_up() 13 public function test_drafts_do_not_show_up()
14 { 14 {
15 $this->asAdmin(); 15 $this->asAdmin();
16 - $pageRepo = app('\BookStack\Repos\PageRepo'); 16 + $entityRepo = app('\BookStack\Repos\EntityRepo');
17 - $draft = $pageRepo->getDraftPage($this->book); 17 + $draft = $entityRepo->getDraftPage($this->book);
18 18
19 $this->visit($this->book->getUrl()) 19 $this->visit($this->book->getUrl())
20 ->see($draft->name) 20 ->see($draft->name)
...@@ -28,7 +28,7 @@ class SortTest extends TestCase ...@@ -28,7 +28,7 @@ class SortTest extends TestCase
28 $currentBook = $page->book; 28 $currentBook = $page->book;
29 $newBook = \BookStack\Book::where('id', '!=', $currentBook->id)->first(); 29 $newBook = \BookStack\Book::where('id', '!=', $currentBook->id)->first();
30 $this->asAdmin()->visit($page->getUrl() . '/move') 30 $this->asAdmin()->visit($page->getUrl() . '/move')
31 - ->see('Move Page')->see($page->name) 31 + ->see('Move Page')
32 ->type('book:' . $newBook->id, 'entity_selection')->press('Move Page'); 32 ->type('book:' . $newBook->id, 'entity_selection')->press('Move Page');
33 33
34 $page = \BookStack\Page::find($page->id); 34 $page = \BookStack\Page::find($page->id);
...@@ -48,7 +48,7 @@ class SortTest extends TestCase ...@@ -48,7 +48,7 @@ class SortTest extends TestCase
48 $newBook = \BookStack\Book::where('id', '!=', $currentBook->id)->first(); 48 $newBook = \BookStack\Book::where('id', '!=', $currentBook->id)->first();
49 49
50 $this->asAdmin()->visit($chapter->getUrl() . '/move') 50 $this->asAdmin()->visit($chapter->getUrl() . '/move')
51 - ->see('Move Chapter')->see($chapter->name) 51 + ->see('Move Chapter')
52 ->type('book:' . $newBook->id, 'entity_selection')->press('Move Chapter'); 52 ->type('book:' . $newBook->id, 'entity_selection')->press('Move Chapter');
53 53
54 $chapter = \BookStack\Chapter::find($chapter->id); 54 $chapter = \BookStack\Chapter::find($chapter->id);
......
...@@ -4,7 +4,7 @@ use BookStack\Tag; ...@@ -4,7 +4,7 @@ use BookStack\Tag;
4 use BookStack\Page; 4 use BookStack\Page;
5 use BookStack\Services\PermissionService; 5 use BookStack\Services\PermissionService;
6 6
7 -class TagTests extends \TestCase 7 +class TagTest extends \TestCase
8 { 8 {
9 9
10 protected $defaultTagCount = 20; 10 protected $defaultTagCount = 20;
...@@ -86,61 +86,16 @@ class TagTests extends \TestCase ...@@ -86,61 +86,16 @@ class TagTests extends \TestCase
86 $attrs = $attrs->merge(factory(Tag::class, 5)->make(['name' => 'color'])); 86 $attrs = $attrs->merge(factory(Tag::class, 5)->make(['name' => 'color']));
87 $page = $this->getPageWithTags($attrs); 87 $page = $this->getPageWithTags($attrs);
88 88
89 - $this->asAdmin()->get('/ajax/tags/suggest?search=co')->seeJsonEquals(['color', 'country']); 89 + $this->asAdmin()->get('/ajax/tags/suggest/names?search=co')->seeJsonEquals(['color', 'country']);
90 - $this->asEditor()->get('/ajax/tags/suggest?search=co')->seeJsonEquals(['color', 'country']); 90 + $this->asEditor()->get('/ajax/tags/suggest/names?search=co')->seeJsonEquals(['color', 'country']);
91 91
92 // Set restricted permission the page 92 // Set restricted permission the page
93 $page->restricted = true; 93 $page->restricted = true;
94 $page->save(); 94 $page->save();
95 $permissionService->buildJointPermissionsForEntity($page); 95 $permissionService->buildJointPermissionsForEntity($page);
96 96
97 - $this->asAdmin()->get('/ajax/tags/suggest?search=co')->seeJsonEquals(['color', 'country']); 97 + $this->asAdmin()->get('/ajax/tags/suggest/names?search=co')->seeJsonEquals(['color', 'country']);
98 - $this->asEditor()->get('/ajax/tags/suggest?search=co')->seeJsonEquals([]); 98 + $this->asEditor()->get('/ajax/tags/suggest/names?search=co')->seeJsonEquals([]);
99 - }
100 -
101 - public function test_entity_tag_updating()
102 - {
103 - $page = $this->getPageWithTags();
104 -
105 - $testJsonData = [
106 - ['name' => 'color', 'value' => 'red'],
107 - ['name' => 'color', 'value' => ' blue '],
108 - ['name' => 'city', 'value' => 'London '],
109 - ['name' => 'country', 'value' => ' England'],
110 - ];
111 - $testResponseJsonData = [
112 - ['name' => 'color', 'value' => 'red'],
113 - ['name' => 'color', 'value' => 'blue'],
114 - ['name' => 'city', 'value' => 'London'],
115 - ['name' => 'country', 'value' => 'England'],
116 - ];
117 -
118 - // Do update request
119 - $this->asAdmin()->json("POST", "/ajax/tags/update/page/" . $page->id, ['tags' => $testJsonData]);
120 - $updateData = json_decode($this->response->getContent());
121 - // Check data is correct
122 - $testDataCorrect = true;
123 - foreach ($updateData->tags as $data) {
124 - $testItem = ['name' => $data->name, 'value' => $data->value];
125 - if (!in_array($testItem, $testResponseJsonData)) $testDataCorrect = false;
126 - }
127 - $testMessage = "Expected data was not found in the response.\nExpected Data: %s\nRecieved Data: %s";
128 - $this->assertTrue($testDataCorrect, sprintf($testMessage, json_encode($testResponseJsonData), json_encode($updateData)));
129 - $this->assertTrue(isset($updateData->message), "No message returned in tag update response");
130 -
131 - // Do get request
132 - $this->asAdmin()->get("/ajax/tags/get/page/" . $page->id);
133 - $getResponseData = json_decode($this->response->getContent());
134 - // Check counts
135 - $this->assertTrue(count($getResponseData) === count($testJsonData), "The received tag count is incorrect");
136 - // Check data is correct
137 - $testDataCorrect = true;
138 - foreach ($getResponseData as $data) {
139 - $testItem = ['name' => $data->name, 'value' => $data->value];
140 - if (!in_array($testItem, $testResponseJsonData)) $testDataCorrect = false;
141 - }
142 - $testMessage = "Expected data was not found in the response.\nExpected Data: %s\nRecieved Data: %s";
143 - $this->assertTrue($testDataCorrect, sprintf($testMessage, json_encode($testResponseJsonData), json_encode($getResponseData)));
144 } 99 }
145 100
146 } 101 }
......
...@@ -90,7 +90,7 @@ class ImageTest extends TestCase ...@@ -90,7 +90,7 @@ class ImageTest extends TestCase
90 'type' => 'gallery' 90 'type' => 'gallery'
91 ]); 91 ]);
92 92
93 - $this->assertFalse(file_exists(public_path($relPath)), 'Uploaded image has been deleted'); 93 + $this->assertFalse(file_exists(public_path($relPath)), 'Uploaded image has not been deleted as expected');
94 } 94 }
95 95
96 } 96 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -65,9 +65,9 @@ class RestrictionsTest extends TestCase ...@@ -65,9 +65,9 @@ class RestrictionsTest extends TestCase
65 $this->forceVisit($bookUrl) 65 $this->forceVisit($bookUrl)
66 ->see('Book not found'); 66 ->see('Book not found');
67 $this->forceVisit($bookPage->getUrl()) 67 $this->forceVisit($bookPage->getUrl())
68 - ->see('Book not found'); 68 + ->see('Page not found');
69 $this->forceVisit($bookChapter->getUrl()) 69 $this->forceVisit($bookChapter->getUrl())
70 - ->see('Book not found'); 70 + ->see('Chapter not found');
71 71
72 $this->setEntityRestrictions($book, ['view']); 72 $this->setEntityRestrictions($book, ['view']);
73 73
......
...@@ -81,7 +81,7 @@ class RolesTest extends TestCase ...@@ -81,7 +81,7 @@ class RolesTest extends TestCase
81 $this->asAdmin()->visit('/settings') 81 $this->asAdmin()->visit('/settings')
82 ->click('Roles') 82 ->click('Roles')
83 ->seePageIs('/settings/roles') 83 ->seePageIs('/settings/roles')
84 - ->click('Add new role') 84 + ->click('Create New Role')
85 ->type('Test Role', 'display_name') 85 ->type('Test Role', 'display_name')
86 ->type('A little test description', 'description') 86 ->type('A little test description', 'description')
87 ->press('Save Role') 87 ->press('Save Role')
...@@ -211,7 +211,7 @@ class RolesTest extends TestCase ...@@ -211,7 +211,7 @@ class RolesTest extends TestCase
211 $this->checkAccessPermission('book-create-all', [ 211 $this->checkAccessPermission('book-create-all', [
212 '/books/create' 212 '/books/create'
213 ], [ 213 ], [
214 - '/books' => 'Add new book' 214 + '/books' => 'Create New Book'
215 ]); 215 ]);
216 216
217 $this->visit('/books/create') 217 $this->visit('/books/create')
...@@ -578,4 +578,44 @@ class RolesTest extends TestCase ...@@ -578,4 +578,44 @@ class RolesTest extends TestCase
578 ->see('Cannot be deleted'); 578 ->see('Cannot be deleted');
579 } 579 }
580 580
581 +
582 +
583 + public function test_image_delete_own_permission()
584 + {
585 + $this->giveUserPermissions($this->user, ['image-update-all']);
586 + $page = \BookStack\Page::first();
587 + $image = factory(\BookStack\Image::class)->create(['uploaded_to' => $page->id, 'created_by' => $this->user->id, 'updated_by' => $this->user->id]);
588 +
589 + $this->actingAs($this->user)->json('delete', '/images/' . $image->id)
590 + ->seeStatusCode(403);
591 +
592 + $this->giveUserPermissions($this->user, ['image-delete-own']);
593 +
594 + $this->actingAs($this->user)->json('delete', '/images/' . $image->id)
595 + ->seeStatusCode(200)
596 + ->dontSeeInDatabase('images', ['id' => $image->id]);
597 + }
598 +
599 + public function test_image_delete_all_permission()
600 + {
601 + $this->giveUserPermissions($this->user, ['image-update-all']);
602 + $admin = $this->getAdmin();
603 + $page = \BookStack\Page::first();
604 + $image = factory(\BookStack\Image::class)->create(['uploaded_to' => $page->id, 'created_by' => $admin->id, 'updated_by' => $admin->id]);
605 +
606 + $this->actingAs($this->user)->json('delete', '/images/' . $image->id)
607 + ->seeStatusCode(403);
608 +
609 + $this->giveUserPermissions($this->user, ['image-delete-own']);
610 +
611 + $this->actingAs($this->user)->json('delete', '/images/' . $image->id)
612 + ->seeStatusCode(403);
613 +
614 + $this->giveUserPermissions($this->user, ['image-delete-all']);
615 +
616 + $this->actingAs($this->user)->json('delete', '/images/' . $image->id)
617 + ->seeStatusCode(200)
618 + ->dontSeeInDatabase('images', ['id' => $image->id]);
619 + }
620 +
581 } 621 }
......
...@@ -64,7 +64,7 @@ class PublicActionTest extends TestCase ...@@ -64,7 +64,7 @@ class PublicActionTest extends TestCase
64 $this->visit($chapter->book->getUrl()); 64 $this->visit($chapter->book->getUrl());
65 $this->visit($chapter->getUrl()) 65 $this->visit($chapter->getUrl())
66 ->click('New Page') 66 ->click('New Page')
67 - ->see('Create Page') 67 + ->see('New Page')
68 ->seePageIs($chapter->getUrl('/create-page')); 68 ->seePageIs($chapter->getUrl('/create-page'));
69 69
70 $this->submitForm('Continue', [ 70 $this->submitForm('Continue', [
......
...@@ -60,7 +60,7 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase ...@@ -60,7 +60,7 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase
60 */ 60 */
61 public function asEditor() 61 public function asEditor()
62 { 62 {
63 - if($this->editor === null) { 63 + if ($this->editor === null) {
64 $this->editor = $this->getEditor(); 64 $this->editor = $this->getEditor();
65 } 65 }
66 return $this->actingAs($this->editor); 66 return $this->actingAs($this->editor);
......