Dan Brown

Merge branch 'master' into release

Showing 85 changed files with 1646 additions and 477 deletions
1 dist: trusty 1 dist: trusty
2 -sudo: required 2 +sudo: false
3 language: php 3 language: php
4 php: 4 php:
5 - 7.0 5 - 7.0
...@@ -8,15 +8,11 @@ cache: ...@@ -8,15 +8,11 @@ cache:
8 directories: 8 directories:
9 - $HOME/.composer/cache 9 - $HOME/.composer/cache
10 10
11 -addons:
12 - apt:
13 - packages:
14 - - mysql-server-5.6
15 - - mysql-client-core-5.6
16 - - mysql-client-5.6
17 -
18 before_script: 11 before_script:
19 - mysql -u root -e 'create database `bookstack-test`;' 12 - mysql -u root -e 'create database `bookstack-test`;'
13 + - mysql -u root -e "CREATE USER 'bookstack-test'@'localhost' IDENTIFIED BY 'bookstack-test';"
14 + - mysql -u root -e "GRANT ALL ON \`bookstack-test\`.* TO 'bookstack-test'@'localhost';"
15 + - mysql -u root -e "FLUSH PRIVILEGES;"
20 - phpenv config-rm xdebug.ini 16 - phpenv config-rm xdebug.ini
21 - composer dump-autoload --no-interaction 17 - composer dump-autoload --no-interaction
22 - composer install --prefer-dist --no-interaction 18 - composer install --prefer-dist --no-interaction
...@@ -25,5 +21,8 @@ before_script: ...@@ -25,5 +21,8 @@ before_script:
25 - php artisan migrate --force -n --database=mysql_testing 21 - php artisan migrate --force -n --database=mysql_testing
26 - php artisan db:seed --force -n --class=DummyContentSeeder --database=mysql_testing 22 - php artisan db:seed --force -n --class=DummyContentSeeder --database=mysql_testing
27 23
24 +after_failure:
25 + - cat storage/logs/laravel.log
26 +
28 script: 27 script:
29 - phpunit 28 - phpunit
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -2,24 +2,37 @@ ...@@ -2,24 +2,37 @@
2 2
3 namespace BookStack\Console\Commands; 3 namespace BookStack\Console\Commands;
4 4
5 +use BookStack\Activity;
5 use Illuminate\Console\Command; 6 use Illuminate\Console\Command;
6 -use Illuminate\Foundation\Inspiring;
7 7
8 -class Inspire extends Command 8 +class ClearActivity extends Command
9 { 9 {
10 /** 10 /**
11 * The name and signature of the console command. 11 * The name and signature of the console command.
12 * 12 *
13 * @var string 13 * @var string
14 */ 14 */
15 - protected $signature = 'inspire'; 15 + protected $signature = 'bookstack:clear-activity';
16 16
17 /** 17 /**
18 * The console command description. 18 * The console command description.
19 * 19 *
20 * @var string 20 * @var string
21 */ 21 */
22 - protected $description = 'Display an inspiring quote'; 22 + protected $description = 'Clear user activity from the system';
23 +
24 + protected $activity;
25 +
26 + /**
27 + * Create a new command instance.
28 + *
29 + * @param Activity $activity
30 + */
31 + public function __construct(Activity $activity)
32 + {
33 + $this->activity = $activity;
34 + parent::__construct();
35 + }
23 36
24 /** 37 /**
25 * Execute the console command. 38 * Execute the console command.
...@@ -28,6 +41,7 @@ class Inspire extends Command ...@@ -28,6 +41,7 @@ class Inspire extends Command
28 */ 41 */
29 public function handle() 42 public function handle()
30 { 43 {
31 - $this->comment(PHP_EOL.Inspiring::quote().PHP_EOL); 44 + $this->activity->newQuery()->truncate();
45 + $this->comment('System activity cleared');
32 } 46 }
33 } 47 }
......
1 +<?php
2 +
3 +namespace BookStack\Console\Commands;
4 +
5 +use BookStack\PageRevision;
6 +use Illuminate\Console\Command;
7 +
8 +class ClearRevisions extends Command
9 +{
10 + /**
11 + * The name and signature of the console command.
12 + *
13 + * @var string
14 + */
15 + protected $signature = 'bookstack:clear-revisions
16 + {--a|all : Include active update drafts in deletion}
17 + ';
18 +
19 + /**
20 + * The console command description.
21 + *
22 + * @var string
23 + */
24 + protected $description = 'Clear page revisions';
25 +
26 + protected $pageRevision;
27 +
28 + /**
29 + * Create a new command instance.
30 + *
31 + * @param PageRevision $pageRevision
32 + */
33 + public function __construct(PageRevision $pageRevision)
34 + {
35 + $this->pageRevision = $pageRevision;
36 + parent::__construct();
37 + }
38 +
39 + /**
40 + * Execute the console command.
41 + *
42 + * @return mixed
43 + */
44 + public function handle()
45 + {
46 + $deleteTypes = $this->option('all') ? ['version', 'update_draft'] : ['version'];
47 + $this->pageRevision->newQuery()->whereIn('type', $deleteTypes)->delete();
48 + $this->comment('Revisions deleted');
49 + }
50 +}
...@@ -4,21 +4,21 @@ namespace BookStack\Console\Commands; ...@@ -4,21 +4,21 @@ namespace BookStack\Console\Commands;
4 4
5 use Illuminate\Console\Command; 5 use Illuminate\Console\Command;
6 6
7 -class ResetViews extends Command 7 +class ClearViews extends Command
8 { 8 {
9 /** 9 /**
10 * The name and signature of the console command. 10 * The name and signature of the console command.
11 * 11 *
12 * @var string 12 * @var string
13 */ 13 */
14 - protected $signature = 'views:reset'; 14 + protected $signature = 'bookstack:clear-views';
15 15
16 /** 16 /**
17 * The console command description. 17 * The console command description.
18 * 18 *
19 * @var string 19 * @var string
20 */ 20 */
21 - protected $description = 'Reset all view-counts for all entities.'; 21 + protected $description = 'Clear all view-counts for all entities.';
22 22
23 /** 23 /**
24 * Create a new command instance. 24 * Create a new command instance.
...@@ -37,5 +37,6 @@ class ResetViews extends Command ...@@ -37,5 +37,6 @@ class ResetViews extends Command
37 public function handle() 37 public function handle()
38 { 38 {
39 \Views::resetAll(); 39 \Views::resetAll();
40 + $this->comment('Views cleared');
40 } 41 }
41 } 42 }
......
...@@ -12,7 +12,7 @@ class RegeneratePermissions extends Command ...@@ -12,7 +12,7 @@ class RegeneratePermissions extends Command
12 * 12 *
13 * @var string 13 * @var string
14 */ 14 */
15 - protected $signature = 'permissions:regen'; 15 + protected $signature = 'bookstack:regenerate-permissions';
16 16
17 /** 17 /**
18 * The console command description. 18 * The console command description.
...@@ -47,5 +47,6 @@ class RegeneratePermissions extends Command ...@@ -47,5 +47,6 @@ class RegeneratePermissions extends Command
47 public function handle() 47 public function handle()
48 { 48 {
49 $this->permissionService->buildJointPermissions(); 49 $this->permissionService->buildJointPermissions();
50 + $this->comment('Permissions regenerated');
50 } 51 }
51 } 52 }
......
...@@ -13,8 +13,9 @@ class Kernel extends ConsoleKernel ...@@ -13,8 +13,9 @@ class Kernel extends ConsoleKernel
13 * @var array 13 * @var array
14 */ 14 */
15 protected $commands = [ 15 protected $commands = [
16 - \BookStack\Console\Commands\Inspire::class, 16 + \BookStack\Console\Commands\ClearViews::class,
17 - \BookStack\Console\Commands\ResetViews::class, 17 + \BookStack\Console\Commands\ClearActivity::class,
18 + \BookStack\Console\Commands\ClearRevisions::class,
18 \BookStack\Console\Commands\RegeneratePermissions::class, 19 \BookStack\Console\Commands\RegeneratePermissions::class,
19 ]; 20 ];
20 21
...@@ -26,7 +27,6 @@ class Kernel extends ConsoleKernel ...@@ -26,7 +27,6 @@ class Kernel extends ConsoleKernel
26 */ 27 */
27 protected function schedule(Schedule $schedule) 28 protected function schedule(Schedule $schedule)
28 { 29 {
29 - $schedule->command('inspire') 30 + //
30 - ->hourly();
31 } 31 }
32 } 32 }
......
...@@ -3,9 +3,9 @@ ...@@ -3,9 +3,9 @@
3 namespace BookStack\Exceptions; 3 namespace BookStack\Exceptions;
4 4
5 use Exception; 5 use Exception;
6 -use Illuminate\Contracts\Validation\ValidationException; 6 +use Illuminate\Auth\AuthenticationException;
7 +use Illuminate\Validation\ValidationException;
7 use Illuminate\Database\Eloquent\ModelNotFoundException; 8 use Illuminate\Database\Eloquent\ModelNotFoundException;
8 -use PhpSpec\Exception\Example\ErrorException;
9 use Symfony\Component\HttpKernel\Exception\HttpException; 9 use Symfony\Component\HttpKernel\Exception\HttpException;
10 use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; 10 use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
11 use Illuminate\Auth\Access\AuthorizationException; 11 use Illuminate\Auth\Access\AuthorizationException;
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
3 use Activity; 3 use Activity;
4 use BookStack\Repos\EntityRepo; 4 use BookStack\Repos\EntityRepo;
5 use BookStack\Repos\UserRepo; 5 use BookStack\Repos\UserRepo;
6 +use BookStack\Services\ExportService;
6 use Illuminate\Http\Request; 7 use Illuminate\Http\Request;
7 use Illuminate\Http\Response; 8 use Illuminate\Http\Response;
8 use Views; 9 use Views;
...@@ -12,16 +13,19 @@ class BookController extends Controller ...@@ -12,16 +13,19 @@ class BookController extends Controller
12 13
13 protected $entityRepo; 14 protected $entityRepo;
14 protected $userRepo; 15 protected $userRepo;
16 + protected $exportService;
15 17
16 /** 18 /**
17 * BookController constructor. 19 * BookController constructor.
18 * @param EntityRepo $entityRepo 20 * @param EntityRepo $entityRepo
19 * @param UserRepo $userRepo 21 * @param UserRepo $userRepo
22 + * @param ExportService $exportService
20 */ 23 */
21 - public function __construct(EntityRepo $entityRepo, UserRepo $userRepo) 24 + public function __construct(EntityRepo $entityRepo, UserRepo $userRepo, ExportService $exportService)
22 { 25 {
23 $this->entityRepo = $entityRepo; 26 $this->entityRepo = $entityRepo;
24 $this->userRepo = $userRepo; 27 $this->userRepo = $userRepo;
28 + $this->exportService = $exportService;
25 parent::__construct(); 29 parent::__construct();
26 } 30 }
27 31
...@@ -258,4 +262,49 @@ class BookController extends Controller ...@@ -258,4 +262,49 @@ class BookController extends Controller
258 session()->flash('success', trans('entities.books_permissions_updated')); 262 session()->flash('success', trans('entities.books_permissions_updated'));
259 return redirect($book->getUrl()); 263 return redirect($book->getUrl());
260 } 264 }
265 +
266 + /**
267 + * Export a book as a PDF file.
268 + * @param string $bookSlug
269 + * @return mixed
270 + */
271 + public function exportPdf($bookSlug)
272 + {
273 + $book = $this->entityRepo->getBySlug('book', $bookSlug);
274 + $pdfContent = $this->exportService->bookToPdf($book);
275 + return response()->make($pdfContent, 200, [
276 + 'Content-Type' => 'application/octet-stream',
277 + 'Content-Disposition' => 'attachment; filename="' . $bookSlug . '.pdf'
278 + ]);
279 + }
280 +
281 + /**
282 + * Export a book as a contained HTML file.
283 + * @param string $bookSlug
284 + * @return mixed
285 + */
286 + public function exportHtml($bookSlug)
287 + {
288 + $book = $this->entityRepo->getBySlug('book', $bookSlug);
289 + $htmlContent = $this->exportService->bookToContainedHtml($book);
290 + return response()->make($htmlContent, 200, [
291 + 'Content-Type' => 'application/octet-stream',
292 + 'Content-Disposition' => 'attachment; filename="' . $bookSlug . '.html'
293 + ]);
294 + }
295 +
296 + /**
297 + * Export a book as a plain text file.
298 + * @param $bookSlug
299 + * @return mixed
300 + */
301 + public function exportPlainText($bookSlug)
302 + {
303 + $book = $this->entityRepo->getBySlug('book', $bookSlug);
304 + $htmlContent = $this->exportService->bookToPlainText($book);
305 + return response()->make($htmlContent, 200, [
306 + 'Content-Type' => 'application/octet-stream',
307 + 'Content-Disposition' => 'attachment; filename="' . $bookSlug . '.txt'
308 + ]);
309 + }
261 } 310 }
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
3 use Activity; 3 use Activity;
4 use BookStack\Repos\EntityRepo; 4 use BookStack\Repos\EntityRepo;
5 use BookStack\Repos\UserRepo; 5 use BookStack\Repos\UserRepo;
6 +use BookStack\Services\ExportService;
6 use Illuminate\Http\Request; 7 use Illuminate\Http\Request;
7 use Illuminate\Http\Response; 8 use Illuminate\Http\Response;
8 use Views; 9 use Views;
...@@ -12,16 +13,19 @@ class ChapterController extends Controller ...@@ -12,16 +13,19 @@ class ChapterController extends Controller
12 13
13 protected $userRepo; 14 protected $userRepo;
14 protected $entityRepo; 15 protected $entityRepo;
16 + protected $exportService;
15 17
16 /** 18 /**
17 * ChapterController constructor. 19 * ChapterController constructor.
18 * @param EntityRepo $entityRepo 20 * @param EntityRepo $entityRepo
19 * @param UserRepo $userRepo 21 * @param UserRepo $userRepo
22 + * @param ExportService $exportService
20 */ 23 */
21 - public function __construct(EntityRepo $entityRepo, UserRepo $userRepo) 24 + public function __construct(EntityRepo $entityRepo, UserRepo $userRepo, ExportService $exportService)
22 { 25 {
23 $this->entityRepo = $entityRepo; 26 $this->entityRepo = $entityRepo;
24 $this->userRepo = $userRepo; 27 $this->userRepo = $userRepo;
28 + $this->exportService = $exportService;
25 parent::__construct(); 29 parent::__construct();
26 } 30 }
27 31
...@@ -236,4 +240,52 @@ class ChapterController extends Controller ...@@ -236,4 +240,52 @@ class ChapterController extends Controller
236 session()->flash('success', trans('entities.chapters_permissions_success')); 240 session()->flash('success', trans('entities.chapters_permissions_success'));
237 return redirect($chapter->getUrl()); 241 return redirect($chapter->getUrl());
238 } 242 }
243 +
244 + /**
245 + * Exports a chapter to pdf .
246 + * @param string $bookSlug
247 + * @param string $chapterSlug
248 + * @return \Illuminate\Http\Response
249 + */
250 + public function exportPdf($bookSlug, $chapterSlug)
251 + {
252 + $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
253 + $pdfContent = $this->exportService->chapterToPdf($chapter);
254 + return response()->make($pdfContent, 200, [
255 + 'Content-Type' => 'application/octet-stream',
256 + 'Content-Disposition' => 'attachment; filename="' . $chapterSlug . '.pdf'
257 + ]);
258 + }
259 +
260 + /**
261 + * Export a chapter to a self-contained HTML file.
262 + * @param string $bookSlug
263 + * @param string $chapterSlug
264 + * @return \Illuminate\Http\Response
265 + */
266 + public function exportHtml($bookSlug, $chapterSlug)
267 + {
268 + $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
269 + $containedHtml = $this->exportService->chapterToContainedHtml($chapter);
270 + return response()->make($containedHtml, 200, [
271 + 'Content-Type' => 'application/octet-stream',
272 + 'Content-Disposition' => 'attachment; filename="' . $chapterSlug . '.html'
273 + ]);
274 + }
275 +
276 + /**
277 + * Export a chapter to a simple plaintext .txt file.
278 + * @param string $bookSlug
279 + * @param string $chapterSlug
280 + * @return \Illuminate\Http\Response
281 + */
282 + public function exportPlainText($bookSlug, $chapterSlug)
283 + {
284 + $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
285 + $containedHtml = $this->exportService->chapterToPlainText($chapter);
286 + return response()->make($containedHtml, 200, [
287 + 'Content-Type' => 'application/octet-stream',
288 + 'Content-Disposition' => 'attachment; filename="' . $chapterSlug . '.txt'
289 + ]);
290 + }
239 } 291 }
......
...@@ -4,7 +4,7 @@ namespace BookStack\Http\Controllers; ...@@ -4,7 +4,7 @@ namespace BookStack\Http\Controllers;
4 4
5 use BookStack\Ownable; 5 use BookStack\Ownable;
6 use Illuminate\Foundation\Bus\DispatchesJobs; 6 use Illuminate\Foundation\Bus\DispatchesJobs;
7 -use Illuminate\Http\Exception\HttpResponseException; 7 +use Illuminate\Http\Exceptions\HttpResponseException;
8 use Illuminate\Http\Request; 8 use Illuminate\Http\Request;
9 use Illuminate\Routing\Controller as BaseController; 9 use Illuminate\Routing\Controller as BaseController;
10 use Illuminate\Foundation\Validation\ValidatesRequests; 10 use Illuminate\Foundation\Validation\ValidatesRequests;
......
...@@ -369,10 +369,13 @@ class PageController extends Controller ...@@ -369,10 +369,13 @@ class PageController extends Controller
369 public function showRevision($bookSlug, $pageSlug, $revisionId) 369 public function showRevision($bookSlug, $pageSlug, $revisionId)
370 { 370 {
371 $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug); 371 $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
372 - $revision = $this->entityRepo->getById('page_revision', $revisionId, false); 372 + $revision = $page->revisions()->where('id', '=', $revisionId)->first();
373 + if ($revision === null) {
374 + abort(404);
375 + }
373 376
374 $page->fill($revision->toArray()); 377 $page->fill($revision->toArray());
375 - $this->setPageTitle(trans('entities.pages_revision_named', ['pageName'=>$page->getShortName()])); 378 + $this->setPageTitle(trans('entities.pages_revision_named', ['pageName' => $page->getShortName()]));
376 379
377 return view('pages/revision', [ 380 return view('pages/revision', [
378 'page' => $page, 381 'page' => $page,
...@@ -390,7 +393,10 @@ class PageController extends Controller ...@@ -390,7 +393,10 @@ class PageController extends Controller
390 public function showRevisionChanges($bookSlug, $pageSlug, $revisionId) 393 public function showRevisionChanges($bookSlug, $pageSlug, $revisionId)
391 { 394 {
392 $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug); 395 $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
393 - $revision = $this->entityRepo->getById('page_revision', $revisionId); 396 + $revision = $page->revisions()->where('id', '=', $revisionId)->first();
397 + if ($revision === null) {
398 + abort(404);
399 + }
394 400
395 $prev = $revision->getPrevious(); 401 $prev = $revision->getPrevious();
396 $prevContent = ($prev === null) ? '' : $prev->html; 402 $prevContent = ($prev === null) ? '' : $prev->html;
...@@ -423,7 +429,7 @@ class PageController extends Controller ...@@ -423,7 +429,7 @@ class PageController extends Controller
423 } 429 }
424 430
425 /** 431 /**
426 - * Exports a page to pdf format using barryvdh/laravel-dompdf wrapper. 432 + * Exports a page to a PDF.
427 * https://github.com/barryvdh/laravel-dompdf 433 * https://github.com/barryvdh/laravel-dompdf
428 * @param string $bookSlug 434 * @param string $bookSlug
429 * @param string $pageSlug 435 * @param string $pageSlug
...@@ -433,7 +439,6 @@ class PageController extends Controller ...@@ -433,7 +439,6 @@ class PageController extends Controller
433 { 439 {
434 $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug); 440 $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
435 $pdfContent = $this->exportService->pageToPdf($page); 441 $pdfContent = $this->exportService->pageToPdf($page);
436 -// return $pdfContent;
437 return response()->make($pdfContent, 200, [ 442 return response()->make($pdfContent, 200, [
438 'Content-Type' => 'application/octet-stream', 443 'Content-Type' => 'application/octet-stream',
439 'Content-Disposition' => 'attachment; filename="' . $pageSlug . '.pdf' 444 'Content-Disposition' => 'attachment; filename="' . $pageSlug . '.pdf'
......
1 <?php namespace BookStack\Providers; 1 <?php namespace BookStack\Providers;
2 2
3 +use BookStack\Services\SettingService;
4 +use BookStack\Setting;
3 use Illuminate\Support\ServiceProvider; 5 use Illuminate\Support\ServiceProvider;
4 use Validator; 6 use Validator;
5 7
...@@ -17,6 +19,10 @@ class AppServiceProvider extends ServiceProvider ...@@ -17,6 +19,10 @@ class AppServiceProvider extends ServiceProvider
17 $imageMimes = ['image/png', 'image/bmp', 'image/gif', 'image/jpeg', 'image/jpg', 'image/tiff', 'image/webp']; 19 $imageMimes = ['image/png', 'image/bmp', 'image/gif', 'image/jpeg', 'image/jpg', 'image/tiff', 'image/webp'];
18 return in_array($value->getMimeType(), $imageMimes); 20 return in_array($value->getMimeType(), $imageMimes);
19 }); 21 });
22 +
23 + \Blade::directive('icon', function($expression) {
24 + return "<?php echo icon($expression); ?>";
25 + });
20 } 26 }
21 27
22 /** 28 /**
...@@ -26,6 +32,8 @@ class AppServiceProvider extends ServiceProvider ...@@ -26,6 +32,8 @@ class AppServiceProvider extends ServiceProvider
26 */ 32 */
27 public function register() 33 public function register()
28 { 34 {
29 - // 35 + $this->app->singleton(SettingService::class, function($app) {
36 + return new SettingService($app->make(Setting::class), $app->make('Illuminate\Contracts\Cache\Repository'));
37 + });
30 } 38 }
31 } 39 }
......
...@@ -4,6 +4,7 @@ namespace BookStack\Providers; ...@@ -4,6 +4,7 @@ namespace BookStack\Providers;
4 4
5 use Illuminate\Contracts\Events\Dispatcher as DispatcherContract; 5 use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
6 use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; 6 use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
7 +use SocialiteProviders\Manager\SocialiteWasCalled;
7 8
8 class EventServiceProvider extends ServiceProvider 9 class EventServiceProvider extends ServiceProvider
9 { 10 {
...@@ -13,8 +14,8 @@ class EventServiceProvider extends ServiceProvider ...@@ -13,8 +14,8 @@ class EventServiceProvider extends ServiceProvider
13 * @var array 14 * @var array
14 */ 15 */
15 protected $listen = [ 16 protected $listen = [
16 - 'BookStack\Events\SomeEvent' => [ 17 + SocialiteWasCalled::class => [
17 - 'BookStack\Listeners\EventListener', 18 + 'SocialiteProviders\Slack\SlackExtendSocialite@handle',
18 ], 19 ],
19 ]; 20 ];
20 21
......
1 -<?php namespace BookStack\Providers;
2 -
3 -
4 -use Illuminate\Support\ServiceProvider;
5 -
6 -class SocialiteServiceProvider extends ServiceProvider
7 -{
8 - /**
9 - * Indicates if loading of the provider is deferred.
10 - *
11 - * @var bool
12 - */
13 - protected $defer = true;
14 -
15 - /**
16 - * Register the service provider.
17 - *
18 - * @return void
19 - */
20 - public function register()
21 - {
22 - $this->app->bindShared('Laravel\Socialite\Contracts\Factory', function ($app) {
23 - return new SocialiteManager($app);
24 - });
25 - }
26 -
27 - /**
28 - * Get the services provided by the provider.
29 - *
30 - * @return array
31 - */
32 - public function provides()
33 - {
34 - return ['Laravel\Socialite\Contracts\Factory'];
35 - }
36 -}
...\ No newline at end of file ...\ No newline at end of file
...@@ -86,8 +86,7 @@ class EntityRepo ...@@ -86,8 +86,7 @@ class EntityRepo
86 $this->entities = [ 86 $this->entities = [
87 'page' => $this->page, 87 'page' => $this->page,
88 'chapter' => $this->chapter, 88 'chapter' => $this->chapter,
89 - 'book' => $this->book, 89 + 'book' => $this->book
90 - 'page_revision' => $this->pageRevision
91 ]; 90 ];
92 $this->viewService = $viewService; 91 $this->viewService = $viewService;
93 $this->permissionService = $permissionService; 92 $this->permissionService = $permissionService;
...@@ -314,11 +313,12 @@ class EntityRepo ...@@ -314,11 +313,12 @@ class EntityRepo
314 * Loads the book slug onto child elements to prevent access database access for getting the slug. 313 * Loads the book slug onto child elements to prevent access database access for getting the slug.
315 * @param Book $book 314 * @param Book $book
316 * @param bool $filterDrafts 315 * @param bool $filterDrafts
316 + * @param bool $renderPages
317 * @return mixed 317 * @return mixed
318 */ 318 */
319 - public function getBookChildren(Book $book, $filterDrafts = false) 319 + public function getBookChildren(Book $book, $filterDrafts = false, $renderPages = false)
320 { 320 {
321 - $q = $this->permissionService->bookChildrenQuery($book->id, $filterDrafts)->get(); 321 + $q = $this->permissionService->bookChildrenQuery($book->id, $filterDrafts, $renderPages)->get();
322 $entities = []; 322 $entities = [];
323 $parents = []; 323 $parents = [];
324 $tree = []; 324 $tree = [];
...@@ -326,6 +326,10 @@ class EntityRepo ...@@ -326,6 +326,10 @@ class EntityRepo
326 foreach ($q as $index => $rawEntity) { 326 foreach ($q as $index => $rawEntity) {
327 if ($rawEntity->entity_type === 'BookStack\\Page') { 327 if ($rawEntity->entity_type === 'BookStack\\Page') {
328 $entities[$index] = $this->page->newFromBuilder($rawEntity); 328 $entities[$index] = $this->page->newFromBuilder($rawEntity);
329 + if ($renderPages) {
330 + $entities[$index]->html = $rawEntity->description;
331 + $entities[$index]->html = $this->renderPage($entities[$index]);
332 + };
329 } else if ($rawEntity->entity_type === 'BookStack\\Chapter') { 333 } else if ($rawEntity->entity_type === 'BookStack\\Chapter') {
330 $entities[$index] = $this->chapter->newFromBuilder($rawEntity); 334 $entities[$index] = $this->chapter->newFromBuilder($rawEntity);
331 $key = $entities[$index]->entity_type . ':' . $entities[$index]->id; 335 $key = $entities[$index]->entity_type . ':' . $entities[$index]->id;
......
1 <?php namespace BookStack\Services; 1 <?php namespace BookStack\Services;
2 2
3 +use BookStack\Book;
4 +use BookStack\Chapter;
3 use BookStack\Page; 5 use BookStack\Page;
4 use BookStack\Repos\EntityRepo; 6 use BookStack\Repos\EntityRepo;
5 7
...@@ -25,25 +27,105 @@ class ExportService ...@@ -25,25 +27,105 @@ class ExportService
25 */ 27 */
26 public function pageToContainedHtml(Page $page) 28 public function pageToContainedHtml(Page $page)
27 { 29 {
28 - $cssContent = file_get_contents(public_path('/css/export-styles.css')); 30 + $pageHtml = view('pages/export', [
29 - $pageHtml = view('pages/export', ['page' => $page, 'pageContent' => $this->entityRepo->renderPage($page), 'css' => $cssContent])->render(); 31 + 'page' => $page,
32 + 'pageContent' => $this->entityRepo->renderPage($page)
33 + ])->render();
30 return $this->containHtml($pageHtml); 34 return $this->containHtml($pageHtml);
31 } 35 }
32 36
33 /** 37 /**
34 - * Convert a page to a pdf file. 38 + * Convert a chapter to a self-contained HTML file.
39 + * @param Chapter $chapter
40 + * @return mixed|string
41 + */
42 + public function chapterToContainedHtml(Chapter $chapter)
43 + {
44 + $pages = $this->entityRepo->getChapterChildren($chapter);
45 + $pages->each(function($page) {
46 + $page->html = $this->entityRepo->renderPage($page);
47 + });
48 + $html = view('chapters/export', [
49 + 'chapter' => $chapter,
50 + 'pages' => $pages
51 + ])->render();
52 + return $this->containHtml($html);
53 + }
54 +
55 + /**
56 + * Convert a book to a self-contained HTML file.
57 + * @param Book $book
58 + * @return mixed|string
59 + */
60 + public function bookToContainedHtml(Book $book)
61 + {
62 + $bookTree = $this->entityRepo->getBookChildren($book, true, true);
63 + $html = view('books/export', [
64 + 'book' => $book,
65 + 'bookChildren' => $bookTree
66 + ])->render();
67 + return $this->containHtml($html);
68 + }
69 +
70 + /**
71 + * Convert a page to a PDF file.
35 * @param Page $page 72 * @param Page $page
36 * @return mixed|string 73 * @return mixed|string
37 */ 74 */
38 public function pageToPdf(Page $page) 75 public function pageToPdf(Page $page)
39 { 76 {
40 - $cssContent = file_get_contents(public_path('/css/export-styles.css')); 77 + $html = view('pages/pdf', [
41 - $pageHtml = view('pages/pdf', ['page' => $page, 'pageContent' => $this->entityRepo->renderPage($page), 'css' => $cssContent])->render(); 78 + 'page' => $page,
42 -// return $pageHtml; 79 + 'pageContent' => $this->entityRepo->renderPage($page)
80 + ])->render();
81 + return $this->htmlToPdf($html);
82 + }
83 +
84 + /**
85 + * Convert a chapter to a PDF file.
86 + * @param Chapter $chapter
87 + * @return mixed|string
88 + */
89 + public function chapterToPdf(Chapter $chapter)
90 + {
91 + $pages = $this->entityRepo->getChapterChildren($chapter);
92 + $pages->each(function($page) {
93 + $page->html = $this->entityRepo->renderPage($page);
94 + });
95 + $html = view('chapters/export', [
96 + 'chapter' => $chapter,
97 + 'pages' => $pages
98 + ])->render();
99 + return $this->htmlToPdf($html);
100 + }
101 +
102 + /**
103 + * Convert a book to a PDF file
104 + * @param Book $book
105 + * @return string
106 + */
107 + public function bookToPdf(Book $book)
108 + {
109 + $bookTree = $this->entityRepo->getBookChildren($book, true, true);
110 + $html = view('books/export', [
111 + 'book' => $book,
112 + 'bookChildren' => $bookTree
113 + ])->render();
114 + return $this->htmlToPdf($html);
115 + }
116 +
117 + /**
118 + * Convert normal webpage HTML to a PDF.
119 + * @param $html
120 + * @return string
121 + */
122 + protected function htmlToPdf($html)
123 + {
124 + $containedHtml = $this->containHtml($html);
43 $useWKHTML = config('snappy.pdf.binary') !== false; 125 $useWKHTML = config('snappy.pdf.binary') !== false;
44 - $containedHtml = $this->containHtml($pageHtml);
45 if ($useWKHTML) { 126 if ($useWKHTML) {
46 $pdf = \SnappyPDF::loadHTML($containedHtml); 127 $pdf = \SnappyPDF::loadHTML($containedHtml);
128 + $pdf->setOption('print-media-type', true);
47 } else { 129 } else {
48 $pdf = \PDF::loadHTML($containedHtml); 130 $pdf = \PDF::loadHTML($containedHtml);
49 } 131 }
...@@ -123,6 +205,40 @@ class ExportService ...@@ -123,6 +205,40 @@ class ExportService
123 return $text; 205 return $text;
124 } 206 }
125 207
208 + /**
209 + * Convert a chapter into a plain text string.
210 + * @param Chapter $chapter
211 + * @return string
212 + */
213 + public function chapterToPlainText(Chapter $chapter)
214 + {
215 + $text = $chapter->name . "\n\n";
216 + $text .= $chapter->description . "\n\n";
217 + foreach ($chapter->pages as $page) {
218 + $text .= $this->pageToPlainText($page);
219 + }
220 + return $text;
221 + }
222 +
223 + /**
224 + * Convert a book into a plain text string.
225 + * @param Book $book
226 + * @return string
227 + */
228 + public function bookToPlainText(Book $book)
229 + {
230 + $bookTree = $this->entityRepo->getBookChildren($book, true, true);
231 + $text = $book->name . "\n\n";
232 + foreach ($bookTree as $bookChild) {
233 + if ($bookChild->isA('chapter')) {
234 + $text .= $this->chapterToPlainText($bookChild);
235 + } else {
236 + $text .= $this->pageToPlainText($bookChild);
237 + }
238 + }
239 + return $text;
240 + }
241 +
126 } 242 }
127 243
128 244
......
...@@ -41,7 +41,8 @@ class LdapService ...@@ -41,7 +41,8 @@ class LdapService
41 // Find user 41 // Find user
42 $userFilter = $this->buildFilter($this->config['user_filter'], ['user' => $userName]); 42 $userFilter = $this->buildFilter($this->config['user_filter'], ['user' => $userName]);
43 $baseDn = $this->config['base_dn']; 43 $baseDn = $this->config['base_dn'];
44 - $users = $this->ldap->searchAndGetEntries($ldapConnection, $baseDn, $userFilter, ['cn', 'uid', 'dn', 'mail']); 44 + $emailAttr = $this->config['email_attribute'];
45 + $users = $this->ldap->searchAndGetEntries($ldapConnection, $baseDn, $userFilter, ['cn', 'uid', 'dn', $emailAttr]);
45 if ($users['count'] === 0) return null; 46 if ($users['count'] === 0) return null;
46 47
47 $user = $users[0]; 48 $user = $users[0];
...@@ -49,7 +50,7 @@ class LdapService ...@@ -49,7 +50,7 @@ class LdapService
49 'uid' => (isset($user['uid'])) ? $user['uid'][0] : $user['dn'], 50 'uid' => (isset($user['uid'])) ? $user['uid'][0] : $user['dn'],
50 'name' => $user['cn'][0], 51 'name' => $user['cn'][0],
51 'dn' => $user['dn'], 52 'dn' => $user['dn'],
52 - 'email' => (isset($user['mail'])) ? $user['mail'][0] : null 53 + 'email' => (isset($user[$emailAttr])) ? (is_array($user[$emailAttr]) ? $user[$emailAttr][0] : $user[$emailAttr]) : null
53 ]; 54 ];
54 } 55 }
55 56
......
...@@ -475,10 +475,12 @@ class PermissionService ...@@ -475,10 +475,12 @@ class PermissionService
475 * Get the children of a book in an efficient single query, Filtered by the permission system. 475 * Get the children of a book in an efficient single query, Filtered by the permission system.
476 * @param integer $book_id 476 * @param integer $book_id
477 * @param bool $filterDrafts 477 * @param bool $filterDrafts
478 + * @param bool $fetchPageContent
478 * @return \Illuminate\Database\Query\Builder 479 * @return \Illuminate\Database\Query\Builder
479 */ 480 */
480 - public function bookChildrenQuery($book_id, $filterDrafts = false) { 481 + public function bookChildrenQuery($book_id, $filterDrafts = false, $fetchPageContent = false) {
481 - $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) { 482 + $pageContentSelect = $fetchPageContent ? 'html' : "''";
483 + $pageSelect = $this->db->table('pages')->selectRaw("'BookStack\\\\Page' as entity_type, id, slug, name, text, {$pageContentSelect} as description, book_id, priority, chapter_id, draft")->where('book_id', '=', $book_id)->where(function($query) use ($filterDrafts) {
482 $query->where('draft', '=', 0); 484 $query->where('draft', '=', 0);
483 if (!$filterDrafts) { 485 if (!$filterDrafts) {
484 $query->orWhere(function($query) { 486 $query->orWhere(function($query) {
......
...@@ -16,6 +16,7 @@ class SettingService ...@@ -16,6 +16,7 @@ class SettingService
16 16
17 protected $setting; 17 protected $setting;
18 protected $cache; 18 protected $cache;
19 + protected $localCache = [];
19 20
20 protected $cachePrefix = 'setting-'; 21 protected $cachePrefix = 'setting-';
21 22
...@@ -40,8 +41,12 @@ class SettingService ...@@ -40,8 +41,12 @@ class SettingService
40 public function get($key, $default = false) 41 public function get($key, $default = false)
41 { 42 {
42 if ($default === false) $default = config('setting-defaults.' . $key, false); 43 if ($default === false) $default = config('setting-defaults.' . $key, false);
44 + if (isset($this->localCache[$key])) return $this->localCache[$key];
45 +
43 $value = $this->getValueFromStore($key, $default); 46 $value = $this->getValueFromStore($key, $default);
44 - return $this->formatValue($value, $default); 47 + $formatted = $this->formatValue($value, $default);
48 + $this->localCache[$key] = $formatted;
49 + return $formatted;
45 } 50 }
46 51
47 /** 52 /**
...@@ -71,9 +76,8 @@ class SettingService ...@@ -71,9 +76,8 @@ class SettingService
71 76
72 // Check the cache 77 // Check the cache
73 $cacheKey = $this->cachePrefix . $key; 78 $cacheKey = $this->cachePrefix . $key;
74 - if ($this->cache->has($cacheKey)) { 79 + $cacheVal = $this->cache->get($cacheKey, null);
75 - return $this->cache->get($cacheKey); 80 + if ($cacheVal !== null) return $cacheVal;
76 - }
77 81
78 // Check the database 82 // Check the database
79 $settingObject = $this->getSettingObjectByKey($key); 83 $settingObject = $this->getSettingObjectByKey($key);
......
...@@ -14,7 +14,7 @@ class SocialAuthService ...@@ -14,7 +14,7 @@ class SocialAuthService
14 protected $socialite; 14 protected $socialite;
15 protected $socialAccount; 15 protected $socialAccount;
16 16
17 - protected $validSocialDrivers = ['google', 'github']; 17 + protected $validSocialDrivers = ['google', 'github', 'facebook', 'slack', 'twitter'];
18 18
19 /** 19 /**
20 * SocialAuthService constructor. 20 * SocialAuthService constructor.
...@@ -181,15 +181,25 @@ class SocialAuthService ...@@ -181,15 +181,25 @@ class SocialAuthService
181 public function getActiveDrivers() 181 public function getActiveDrivers()
182 { 182 {
183 $activeDrivers = []; 183 $activeDrivers = [];
184 - foreach ($this->validSocialDrivers as $driverName) { 184 + foreach ($this->validSocialDrivers as $driverKey) {
185 - if ($this->checkDriverConfigured($driverName)) { 185 + if ($this->checkDriverConfigured($driverKey)) {
186 - $activeDrivers[$driverName] = true; 186 + $activeDrivers[$driverKey] = $this->getDriverName($driverKey);
187 } 187 }
188 } 188 }
189 return $activeDrivers; 189 return $activeDrivers;
190 } 190 }
191 191
192 /** 192 /**
193 + * Get the presentational name for a driver.
194 + * @param $driver
195 + * @return mixed
196 + */
197 + public function getDriverName($driver)
198 + {
199 + return config('services.' . strtolower($driver) . '.name');
200 + }
201 +
202 + /**
193 * @param string $socialDriver 203 * @param string $socialDriver
194 * @param \Laravel\Socialite\Contracts\User $socialUser 204 * @param \Laravel\Socialite\Contracts\User $socialUser
195 * @return SocialAccount 205 * @return SocialAccount
...@@ -211,7 +221,6 @@ class SocialAuthService ...@@ -211,7 +221,6 @@ class SocialAuthService
211 */ 221 */
212 public function detachSocialAccount($socialDriver) 222 public function detachSocialAccount($socialDriver)
213 { 223 {
214 - session();
215 user()->socialAccounts()->where('driver', '=', $socialDriver)->delete(); 224 user()->socialAccounts()->where('driver', '=', $socialDriver)->delete();
216 session()->flash('success', trans('settings.users_social_disconnected', ['socialAccount' => title_case($socialDriver)])); 225 session()->flash('success', trans('settings.users_social_disconnected', ['socialAccount' => title_case($socialDriver)]));
217 return redirect(user()->getEditUrl()); 226 return redirect(user()->getEditUrl());
......
...@@ -73,7 +73,7 @@ function userCan($permission, Ownable $ownable = null) ...@@ -73,7 +73,7 @@ function userCan($permission, Ownable $ownable = null)
73 */ 73 */
74 function setting($key = null, $default = false) 74 function setting($key = null, $default = false)
75 { 75 {
76 - $settingService = app(\BookStack\Services\SettingService::class); 76 + $settingService = resolve(\BookStack\Services\SettingService::class);
77 if (is_null($key)) return $settingService; 77 if (is_null($key)) return $settingService;
78 return $settingService->get($key, $default); 78 return $settingService->get($key, $default);
79 } 79 }
...@@ -126,6 +126,16 @@ function redirect($to = null, $status = 302, $headers = [], $secure = null) ...@@ -126,6 +126,16 @@ function redirect($to = null, $status = 302, $headers = [], $secure = null)
126 return app('redirect')->to($to, $status, $headers, $secure); 126 return app('redirect')->to($to, $status, $headers, $secure);
127 } 127 }
128 128
129 +function icon($name, $attrs = []) {
130 + $iconPath = resource_path('assets/icons/' . $name . '.svg');
131 + $attrString = ' ';
132 + foreach ($attrs as $attrName => $attr) {
133 + $attrString .= $attrName . '="' . $attr . '" ';
134 + }
135 + $fileContents = file_get_contents($iconPath);
136 + return str_replace('<svg', '<svg' . $attrString, $fileContents);
137 +}
138 +
129 /** 139 /**
130 * Generate a url with multiple parameters for sorting purposes. 140 * Generate a url with multiple parameters for sorting purposes.
131 * Works out the logic to set the correct sorting direction 141 * Works out the logic to set the correct sorting direction
......
...@@ -6,17 +6,19 @@ ...@@ -6,17 +6,19 @@
6 "type": "project", 6 "type": "project",
7 "require": { 7 "require": {
8 "php": ">=5.6.4", 8 "php": ">=5.6.4",
9 - "laravel/framework": "^5.3.4", 9 + "laravel/framework": "5.4.*",
10 "ext-tidy": "*", 10 "ext-tidy": "*",
11 "intervention/image": "^2.3", 11 "intervention/image": "^2.3",
12 - "laravel/socialite": "^2.0", 12 + "laravel/socialite": "^3.0",
13 - "barryvdh/laravel-ide-helper": "^2.1", 13 + "barryvdh/laravel-ide-helper": "^2.2.3",
14 - "barryvdh/laravel-debugbar": "^2.2.3", 14 + "barryvdh/laravel-debugbar": "^2.3.2",
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.8",
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 + "barryvdh/laravel-snappy": "^0.3.1",
20 + "laravel/browser-kit-testing": "^1.0",
21 + "socialiteproviders/slack": "^3.0"
20 }, 22 },
21 "require-dev": { 23 "require-dev": {
22 "fzaninotto/faker": "~1.4", 24 "fzaninotto/faker": "~1.4",
...@@ -34,9 +36,9 @@ ...@@ -34,9 +36,9 @@
34 } 36 }
35 }, 37 },
36 "autoload-dev": { 38 "autoload-dev": {
37 - "classmap": [ 39 + "psr-4": {
38 - "tests/TestCase.php" 40 + "Tests\\": "tests/"
39 - ] 41 + }
40 }, 42 },
41 "scripts": { 43 "scripts": {
42 "post-root-package-install": [ 44 "post-root-package-install": [
......
This diff could not be displayed because it is too large.
...@@ -139,7 +139,7 @@ return [ ...@@ -139,7 +139,7 @@ return [
139 Illuminate\Validation\ValidationServiceProvider::class, 139 Illuminate\Validation\ValidationServiceProvider::class,
140 Illuminate\View\ViewServiceProvider::class, 140 Illuminate\View\ViewServiceProvider::class,
141 Illuminate\Notifications\NotificationServiceProvider::class, 141 Illuminate\Notifications\NotificationServiceProvider::class,
142 - Laravel\Socialite\SocialiteServiceProvider::class, 142 + SocialiteProviders\Manager\ServiceProvider::class,
143 143
144 /** 144 /**
145 * Third Party 145 * Third Party
......
...@@ -82,7 +82,7 @@ return [ ...@@ -82,7 +82,7 @@ return [
82 82
83 'mysql_testing' => [ 83 'mysql_testing' => [
84 'driver' => 'mysql', 84 'driver' => 'mysql',
85 - 'host' => 'localhost', 85 + 'host' => '127.0.0.1',
86 'database' => 'bookstack-test', 86 'database' => 'bookstack-test',
87 'username' => env('MYSQL_USER', 'bookstack-test'), 87 'username' => env('MYSQL_USER', 'bookstack-test'),
88 'password' => env('MYSQL_PASSWORD', 'bookstack-test'), 88 'password' => env('MYSQL_PASSWORD', 'bookstack-test'),
......
1 <?php 1 <?php
2 2
3 -return array( 3 +return [
4 4
5 /* 5 /*
6 |-------------------------------------------------------------------------- 6 |--------------------------------------------------------------------------
...@@ -13,7 +13,7 @@ return array( ...@@ -13,7 +13,7 @@ return array(
13 */ 13 */
14 'show_warnings' => false, // Throw an Exception on warnings from dompdf 14 'show_warnings' => false, // Throw an Exception on warnings from dompdf
15 'orientation' => 'portrait', 15 'orientation' => 'portrait',
16 - 'defines' => array( 16 + 'defines' => [
17 /** 17 /**
18 * The location of the DOMPDF font directory 18 * The location of the DOMPDF font directory
19 * 19 *
...@@ -143,7 +143,7 @@ return array( ...@@ -143,7 +143,7 @@ return array(
143 * the desired content might be different (e.g. screen or projection view of html file). 143 * the desired content might be different (e.g. screen or projection view of html file).
144 * Therefore allow specification of content here. 144 * Therefore allow specification of content here.
145 */ 145 */
146 - "DOMPDF_DEFAULT_MEDIA_TYPE" => "screen", 146 + "DOMPDF_DEFAULT_MEDIA_TYPE" => "print",
147 147
148 /** 148 /**
149 * The default paper size. 149 * The default paper size.
...@@ -260,7 +260,7 @@ return array( ...@@ -260,7 +260,7 @@ return array(
260 "DOMPDF_ENABLE_HTML5PARSER" => true, 260 "DOMPDF_ENABLE_HTML5PARSER" => true,
261 261
262 262
263 - ), 263 + ],
264 264
265 265
266 -); 266 +];
......
...@@ -41,12 +41,35 @@ return [ ...@@ -41,12 +41,35 @@ return [
41 'client_id' => env('GITHUB_APP_ID', false), 41 'client_id' => env('GITHUB_APP_ID', false),
42 'client_secret' => env('GITHUB_APP_SECRET', false), 42 'client_secret' => env('GITHUB_APP_SECRET', false),
43 'redirect' => env('APP_URL') . '/login/service/github/callback', 43 'redirect' => env('APP_URL') . '/login/service/github/callback',
44 + 'name' => 'GitHub',
44 ], 45 ],
45 46
46 'google' => [ 47 'google' => [
47 'client_id' => env('GOOGLE_APP_ID', false), 48 'client_id' => env('GOOGLE_APP_ID', false),
48 'client_secret' => env('GOOGLE_APP_SECRET', false), 49 'client_secret' => env('GOOGLE_APP_SECRET', false),
49 'redirect' => env('APP_URL') . '/login/service/google/callback', 50 'redirect' => env('APP_URL') . '/login/service/google/callback',
51 + 'name' => 'Google',
52 + ],
53 +
54 + 'slack' => [
55 + 'client_id' => env('SLACK_APP_ID', false),
56 + 'client_secret' => env('SLACK_APP_SECRET', false),
57 + 'redirect' => env('APP_URL') . '/login/service/slack/callback',
58 + 'name' => 'Slack',
59 + ],
60 +
61 + 'facebook' => [
62 + 'client_id' => env('FACEBOOK_APP_ID', false),
63 + 'client_secret' => env('FACEBOOK_APP_SECRET', false),
64 + 'redirect' => env('APP_URL') . '/login/service/facebook/callback',
65 + 'name' => 'Facebook',
66 + ],
67 +
68 + 'twitter' => [
69 + 'client_id' => env('TWITTER_APP_ID', false),
70 + 'client_secret' => env('TWITTER_APP_SECRET', false),
71 + 'redirect' => env('APP_URL') . '/login/service/twitter/callback',
72 + 'name' => 'Twitter',
50 ], 73 ],
51 74
52 'ldap' => [ 75 'ldap' => [
...@@ -55,7 +78,8 @@ return [ ...@@ -55,7 +78,8 @@ return [
55 'pass' => env('LDAP_PASS', false), 78 'pass' => env('LDAP_PASS', false),
56 'base_dn' => env('LDAP_BASE_DN', false), 79 'base_dn' => env('LDAP_BASE_DN', false),
57 'user_filter' => env('LDAP_USER_FILTER', '(&(uid=${user}))'), 80 'user_filter' => env('LDAP_USER_FILTER', '(&(uid=${user}))'),
58 - 'version' => env('LDAP_VERSION', false) 81 + 'version' => env('LDAP_VERSION', false),
82 + 'email_attribute' => env('LDAP_EMAIL_ATTRIBUTE', 'mail'),
59 ] 83 ]
60 84
61 ]; 85 ];
......
...@@ -11,14 +11,14 @@ class DummyContentSeeder extends Seeder ...@@ -11,14 +11,14 @@ class DummyContentSeeder extends Seeder
11 */ 11 */
12 public function run() 12 public function run()
13 { 13 {
14 - $user = factory(BookStack\User::class, 1)->create(); 14 + $user = factory(\BookStack\User::class)->create();
15 $role = \BookStack\Role::getRole('editor'); 15 $role = \BookStack\Role::getRole('editor');
16 $user->attachRole($role); 16 $user->attachRole($role);
17 17
18 18
19 - $books = factory(BookStack\Book::class, 20)->create(['created_by' => $user->id, 'updated_by' => $user->id]) 19 + $books = factory(\BookStack\Book::class, 20)->create(['created_by' => $user->id, 'updated_by' => $user->id])
20 ->each(function($book) use ($user) { 20 ->each(function($book) use ($user) {
21 - $chapters = factory(BookStack\Chapter::class, 5)->create(['created_by' => $user->id, 'updated_by' => $user->id]) 21 + $chapters = factory(\BookStack\Chapter::class, 5)->create(['created_by' => $user->id, 'updated_by' => $user->id])
22 ->each(function($chapter) use ($user, $book){ 22 ->each(function($chapter) use ($user, $book){
23 $pages = factory(\BookStack\Page::class, 5)->make(['created_by' => $user->id, 'updated_by' => $user->id, 'book_id' => $book->id]); 23 $pages = factory(\BookStack\Page::class, 5)->make(['created_by' => $user->id, 'updated_by' => $user->id, 'book_id' => $book->id]);
24 $chapter->pages()->saveMany($pages); 24 $chapter->pages()->saveMany($pages);
......
1 # BookStack 1 # BookStack
2 2
3 -[![GitHub release](https://img.shields.io/github/release/ssddanbrown/BookStack.svg?maxAge=2592000)](https://github.com/ssddanbrown/BookStack/releases/latest) 3 +[![GitHub release](https://img.shields.io/github/release/BookStackApp/BookStack.svg?maxAge=2592000)](https://github.com/BookStackApp/BookStack/releases/latest)
4 -[![license](https://img.shields.io/github/license/ssddanbrown/BookStack.svg?maxAge=2592000)](https://github.com/ssddanbrown/BookStack/blob/master/LICENSE) 4 +[![license](https://img.shields.io/github/license/BookStackApp/BookStack.svg?maxAge=2592000)](https://github.com/BookStackApp/BookStack/blob/master/LICENSE)
5 [![Build Status](https://travis-ci.org/BookStackApp/BookStack.svg)](https://travis-ci.org/BookStackApp/BookStack) 5 [![Build Status](https://travis-ci.org/BookStackApp/BookStack.svg)](https://travis-ci.org/BookStackApp/BookStack)
6 6
7 A platform for storing and organising information and documentation. General information and documentation for BookStack can be found at https://www.bookstackapp.com/. 7 A platform for storing and organising information and documentation. General information and documentation for BookStack can be found at https://www.bookstackapp.com/.
...@@ -46,6 +46,12 @@ As part of BookStack v0.14 support for translations has been built in. All text ...@@ -46,6 +46,12 @@ As part of BookStack v0.14 support for translations has been built in. All text
46 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. 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 48
49 +## Contributing
50 +
51 +Feel free to create issues to request new features or to report bugs and problems. Just please follow the template given when creating the issue.
52 +
53 +Pull requests are very welcome. If the scope of your pull request is very large it may be best to open the pull request early or create an issue for it to discuss how it will fit in to the project and plan out the merge.
54 +
49 ## Website, Docs & Blog 55 ## Website, Docs & Blog
50 56
51 The website project docs & Blog can be found in the [BookStackApp/website](https://github.com/BookStackApp/website) repo. 57 The website project docs & Blog can be found in the [BookStackApp/website](https://github.com/BookStackApp/website) repo.
......
1 +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 266.893 266.895"><path fill="#3C5A99" d="M248.082 262.307c7.854 0 14.223-6.37 14.223-14.225V18.812c0-7.857-6.368-14.224-14.223-14.224H18.812c-7.857 0-14.224 6.367-14.224 14.224v229.27c0 7.855 6.366 14.225 14.224 14.225h229.27z"/><path fill="#FFF" d="M182.41 262.307v-99.803h33.498l5.016-38.895H182.41V98.775c0-11.26 3.126-18.935 19.274-18.935l20.596-.01V45.047c-3.562-.474-15.788-1.533-30.012-1.533-29.695 0-50.025 18.126-50.025 51.413v28.684h-33.585v38.894h33.585v99.803h40.166z"/></svg>
...\ No newline at end of file ...\ No newline at end of file
1 +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path fill="#333333" fill-rule="evenodd" d="M31.9.693c-17.672 0-32 14.327-32 32 0 14.14 9.17 26.132 21.886 30.365 1.6.293 2.184-.695 2.184-1.544 0-.758-.028-2.77-.043-5.44-8.9 1.932-10.78-4.292-10.78-4.292-1.455-3.695-3.553-4.68-3.553-4.68-2.905-1.985.22-1.946.22-1.946 3.212.228 4.9 3.3 4.9 3.3 2.856 4.888 7.492 3.476 9.315 2.66.29-2.07 1.11-3.48 2.03-4.28-7.11-.807-14.58-3.554-14.58-15.816 0-3.493 1.243-6.35 3.29-8.586-.33-.81-1.428-4.063.313-8.47 0 0 2.687-.86 8.8 3.28 2.552-.708 5.29-1.063 8.01-1.075 2.718.01 5.457.36 8.01 1.07 6.11-4.14 8.793-3.28 8.793-3.28 1.747 4.403.65 7.66.32 8.47 2.05 2.233 3.29 5.09 3.29 8.582 0 12.293-7.483 15-14.61 15.79 1.15.99 2.17 2.94 2.17 5.926 0 4.277-.04 7.73-.04 8.777 0 .857.578 1.853 2.2 1.54 12.71-4.235 21.87-16.22 21.87-30.355 0-17.674-14.326-32-32-32"/></svg>
...\ No newline at end of file ...\ No newline at end of file
1 +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><g fill="none" fill-rule="evenodd"><path fill="#4285f4" d="M62.735 32.712c0-2.27-.204-4.45-.582-6.545H32.015v12.378h17.222c-.742 4-2.997 7.39-6.386 9.658v8.03h10.344c6.05-5.57 9.542-13.775 9.542-23.52z"/><path fill="#34a853" d="M32.015 63.985c8.64 0 15.883-2.865 21.178-7.753l-10.342-8.03c-2.863 1.92-6.53 3.056-10.834 3.056-8.335 0-15.39-5.63-17.906-13.193H3.417v8.29c5.266 10.46 16.088 17.63 28.597 17.63z"/><path fill="#fbbc05" d="M14.11 38.065c-.64-1.92-1.004-3.97-1.004-6.08s.363-4.16 1.003-6.08v-8.29H3.416C1.25 21.935.015 26.82.015 31.985c0 5.163 1.236 10.05 3.403 14.37l10.69-8.29z"/><path fill="#ea4335" d="M32.015 12.712c4.698 0 8.916 1.615 12.233 4.786l9.178-9.178C47.884 3.156 40.64-.015 32.016-.015c-12.51 0-23.332 7.17-28.598 17.63l10.69 8.29c2.518-7.563 9.572-13.193 17.907-13.193z"/><path d="M.015-.015h64v64h-64v-64z"/></g></svg>
...\ No newline at end of file ...\ No newline at end of file
1 +<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" viewBox="0 0 64 64"><style id="style3">.st0{fill:#ECB32D;} .st1{fill:#63C1A0;} .st2{fill:#E01A59;} .st3{fill:#331433;} .st4{fill:#D62027;} .st5{fill:#89D3DF;} .st6{fill:#258B74;} .st7{fill:#819C3C;}</style><g id="g5"><g id="g7"><path id="path9" fill="#ecb32d" d="M41.478 3.945C40.48.95 37.28-.677 34.288.27c-2.992.997-4.62 4.2-3.674 7.195l14.748 45.383c.997 2.784 4.042 4.36 6.928 3.52 3.044-.893 4.88-4.098 3.884-7.04 0-.104-14.696-45.383-14.696-45.383z" class="st0"/><path id="path11" fill="#63c1a0" d="M18.648 11.352c-.997-2.994-4.2-4.623-7.19-3.677-2.992.998-4.62 4.202-3.674 7.196l14.748 45.39c.997 2.784 4.04 4.36 6.928 3.52 3.044-.894 4.88-4.098 3.883-7.04 0-.105-14.695-45.383-14.695-45.383z" class="st1"/><path id="path13" fill="#e01a59" d="M60.058 41.502c2.99-.998 4.618-4.202 3.674-7.196-.997-2.994-4.2-4.622-7.19-3.677L11.14 45.44c-2.78.998-4.356 4.045-3.516 6.934.892 3.046 4.094 4.885 7.033 3.887.104 0 45.398-14.76 45.398-14.76z" class="st2"/><path id="path15" fill="#331433" d="M20.59 54.372c2.94-.946 6.77-2.207 10.864-3.52-.945-2.94-2.204-6.776-3.516-10.873l-10.865 3.514L20.59 54.37z" class="st3"/><path id="path17" fill="#d62027" d="M43.473 46.913c4.094-1.313 7.925-2.574 10.864-3.52-.945-2.94-2.204-6.776-3.516-10.873l-10.86 3.52 3.518 10.873z" class="st4"/><path id="path19" fill="#89d3df" d="M52.605 18.653c2.992-.998 4.62-4.202 3.674-7.196-1-2.994-4.2-4.623-7.19-3.677L3.74 22.54c-2.78.998-4.356 4.045-3.516 6.934.892 3.046 4.094 4.885 7.033 3.887.104 0 45.345-14.703 45.345-14.703z" class="st5"/><path id="path21" fill="#258b74" d="M13.19 31.47c2.94-.946 6.77-2.206 10.864-3.52-1.312-4.097-2.572-7.93-3.517-10.873l-10.864 3.52L13.19 31.47z" class="st6"/><path id="path23" fill="#819c3c" d="M36.02 24.063c4.094-1.313 7.925-2.573 10.864-3.52-1.312-4.096-2.57-7.93-3.516-10.872l-10.864 3.52 3.516 10.877z" class="st7"/></g></g></svg>
...\ No newline at end of file ...\ No newline at end of file
1 +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path fill="#00aced" d="M64 12.145c-2.355 1.045-4.886 1.75-7.54 2.068 2.71-1.625 4.79-4.198 5.772-7.265-2.538 1.505-5.347 2.598-8.338 3.187-2.395-2.552-5.808-4.147-9.584-4.147-7.252 0-13.13 5.88-13.13 13.13 0 1.03.115 2.032.34 2.993-10.914-.543-20.59-5.77-27.065-13.714-1.13 1.94-1.777 4.195-1.777 6.6 0 4.556 2.317 8.575 5.84 10.93-2.15-.068-4.176-.66-5.946-1.642v.166c0 6.36 4.525 11.667 10.53 12.874-1.1.3-2.26.46-3.458.46-.846 0-1.67-.08-2.47-.234 1.67 5.215 6.52 9.012 12.265 9.117-4.498 3.522-10.16 5.62-16.31 5.62-1.06 0-2.107-.06-3.13-.183C5.81 55.827 12.71 58 20.124 58c24.15 0 37.358-20.008 37.358-37.36 0-.568-.013-1.134-.038-1.698 2.566-1.85 4.792-4.163 6.552-6.797"/></svg>
...\ No newline at end of file ...\ No newline at end of file
...@@ -214,6 +214,19 @@ export default function (ngApp, events) { ...@@ -214,6 +214,19 @@ export default function (ngApp, events) {
214 } 214 }
215 }]); 215 }]);
216 216
217 + let renderer = new markdown.Renderer();
218 + // Custom markdown checkbox list item
219 + // Attribution: https://github.com/chjj/marked/issues/107#issuecomment-44542001
220 + renderer.listitem = function(text) {
221 + if (/^\s*\[[x ]\]\s*/.test(text)) {
222 + text = text
223 + .replace(/^\s*\[ \]\s*/, '<input type="checkbox"/>')
224 + .replace(/^\s*\[x\]\s*/, '<input type="checkbox" checked/>');
225 + return `<li class="checkbox-item">${text}</li>`;
226 + }
227 + return `<li>${text}</li>`;
228 + };
229 +
217 /** 230 /**
218 * Markdown input 231 * Markdown input
219 * Handles the logic for just the editor input field. 232 * Handles the logic for just the editor input field.
...@@ -231,13 +244,13 @@ export default function (ngApp, events) { ...@@ -231,13 +244,13 @@ export default function (ngApp, events) {
231 element = element.find('textarea').first(); 244 element = element.find('textarea').first();
232 let content = element.val(); 245 let content = element.val();
233 scope.mdModel = content; 246 scope.mdModel = content;
234 - scope.mdChange(markdown(content)); 247 + scope.mdChange(markdown(content, {renderer: renderer}));
235 248
236 element.on('change input', (event) => { 249 element.on('change input', (event) => {
237 content = element.val(); 250 content = element.val();
238 $timeout(() => { 251 $timeout(() => {
239 scope.mdModel = content; 252 scope.mdModel = content;
240 - scope.mdChange(markdown(content)); 253 + scope.mdChange(markdown(content, {renderer: renderer}));
241 }); 254 });
242 }); 255 });
243 256
......
...@@ -135,11 +135,19 @@ ...@@ -135,11 +135,19 @@
135 border-left: 3px solid #BBB; 135 border-left: 3px solid #BBB;
136 background-color: #EEE; 136 background-color: #EEE;
137 padding: $-s; 137 padding: $-s;
138 + padding-left: $-xl;
138 display: block; 139 display: block;
140 + position: relative;
139 &:before { 141 &:before {
140 font-family: 'Material-Design-Iconic-Font'; 142 font-family: 'Material-Design-Iconic-Font';
141 - padding-right: $-s; 143 + left: $-xs + 4px;
144 + top: 50%;
145 + margin-top: -9px;
146 + //top: $-xs + 5px;
142 display: inline-block; 147 display: inline-block;
148 + position: absolute;
149 + font-size: 1.222em;
150 + line-height: 1;
143 } 151 }
144 &.success { 152 &.success {
145 border-left-color: $positive; 153 border-left-color: $positive;
......
...@@ -54,6 +54,9 @@ $button-border-radius: 2px; ...@@ -54,6 +54,9 @@ $button-border-radius: 2px;
54 &.muted { 54 &.muted {
55 @include generate-button-colors(#EEE, #888); 55 @include generate-button-colors(#EEE, #888);
56 } 56 }
57 + &.muted-light {
58 + @include generate-button-colors(#666, #e4e4e4);
59 + }
57 } 60 }
58 61
59 .text-button { 62 .text-button {
...@@ -92,6 +95,9 @@ $button-border-radius: 2px; ...@@ -92,6 +95,9 @@ $button-border-radius: 2px;
92 width: 100%; 95 width: 100%;
93 text-align: center; 96 text-align: center;
94 display: block; 97 display: block;
98 + &.text-left {
99 + text-align: left;
100 + }
95 } 101 }
96 102
97 .button.icon { 103 .button.icon {
...@@ -100,6 +106,19 @@ $button-border-radius: 2px; ...@@ -100,6 +106,19 @@ $button-border-radius: 2px;
100 } 106 }
101 } 107 }
102 108
109 +.button.svg {
110 + svg {
111 + display: inline-block;
112 + position: absolute;
113 + left: $-m;
114 + top: $-s - 2px;
115 + width: 24px;
116 + }
117 + padding: $-s $-m;
118 + padding-bottom: $-s - 2px;
119 + padding-left: $-m*2 + 24px;
120 +}
121 +
103 .button[disabled] { 122 .button[disabled] {
104 background-color: #BBB; 123 background-color: #BBB;
105 cursor: default; 124 cursor: default;
......
...@@ -55,20 +55,6 @@ div[class^="col-"] img { ...@@ -55,20 +55,6 @@ div[class^="col-"] img {
55 } 55 }
56 } 56 }
57 57
58 -.center-box {
59 - margin: $-xl auto 0 auto;
60 - padding: $-m $-xxl $-xl*2 $-xxl;
61 - max-width: 346px;
62 - display: inline-block;
63 - text-align: left;
64 - vertical-align: top;
65 - &.login {
66 - background-color: #EEE;
67 - box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.1);
68 - border: 1px solid #DDD;
69 - }
70 -}
71 -
72 .row { 58 .row {
73 margin-left: -$-m; 59 margin-left: -$-m;
74 margin-right: -$-m; 60 margin-right: -$-m;
......
...@@ -16,7 +16,7 @@ h2 { ...@@ -16,7 +16,7 @@ h2 {
16 } 16 }
17 h3 { 17 h3 {
18 font-size: 2.333em; 18 font-size: 2.333em;
19 - line-height: 1.571428572em; 19 + line-height: 1.221428572em;
20 margin-top: 0.78571429em; 20 margin-top: 0.78571429em;
21 margin-bottom: 0.43137255em; 21 margin-bottom: 0.43137255em;
22 } 22 }
...@@ -71,6 +71,13 @@ a, .link { ...@@ -71,6 +71,13 @@ a, .link {
71 padding-right: 0; 71 padding-right: 0;
72 padding-left: $-s; 72 padding-left: $-s;
73 } 73 }
74 + &.icon {
75 + display: inline-block;
76 + }
77 + svg {
78 + position: relative;
79 + display: inline-block;
80 + }
74 } 81 }
75 82
76 /* 83 /*
...@@ -84,7 +91,6 @@ p, ul, ol, pre, table, blockquote { ...@@ -84,7 +91,6 @@ p, ul, ol, pre, table, blockquote {
84 hr { 91 hr {
85 border: 0; 92 border: 0;
86 height: 1px; 93 height: 1px;
87 - border: 0;
88 background: #EAEAEA; 94 background: #EAEAEA;
89 margin-bottom: $-l; 95 margin-bottom: $-l;
90 &.faded { 96 &.faded {
...@@ -275,6 +281,14 @@ ol { ...@@ -275,6 +281,14 @@ ol {
275 overflow: hidden; 281 overflow: hidden;
276 } 282 }
277 283
284 +li.checkbox-item {
285 + list-style: none;
286 + margin-left: - ($-m * 1.3);
287 + input[type="checkbox"] {
288 + margin-right: $-xs;
289 + }
290 +}
291 +
278 /* 292 /*
279 * Generic text styling classes 293 * Generic text styling classes
280 */ 294 */
......
1 -@import "reset"; 1 +//@import "reset";
2 @import "variables"; 2 @import "variables";
3 @import "mixins"; 3 @import "mixins";
4 @import "html"; 4 @import "html";
......
...@@ -251,10 +251,24 @@ $btt-size: 40px; ...@@ -251,10 +251,24 @@ $btt-size: 40px;
251 } 251 }
252 } 252 }
253 253
254 - 254 +.center-box {
255 - 255 + margin: $-xl auto 0 auto;
256 - 256 + padding: $-m $-xxl $-xl $-xxl;
257 - 257 + width: 420px;
258 + max-width: 100%;
259 + display: inline-block;
260 + text-align: left;
261 + vertical-align: top;
262 + //border: 1px solid #DDD;
263 + input {
264 + width: 100%;
265 + }
266 + &.login {
267 + background-color: #EEE;
268 + box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.1);
269 + border: 1px solid #DDD;
270 + }
271 +}
258 272
259 273
260 274
......
...@@ -14,7 +14,50 @@ return [ ...@@ -14,7 +14,50 @@ return [
14 'throttle' => 'Zu viele Anmeldeversuche. Bitte versuchen sie es in :seconds Sekunden erneut.', 14 'throttle' => 'Zu viele Anmeldeversuche. Bitte versuchen sie es in :seconds Sekunden erneut.',
15 15
16 /** 16 /**
17 - * Email Confirmation Text 17 + * Login & Register
18 + */
19 + 'sign_up' => 'Registrieren',
20 + 'log_in' => 'Anmelden',
21 + 'logout' => 'Abmelden',
22 +
23 + 'name' => 'Name',
24 + 'username' => 'Benutzername',
25 + 'email' => 'E-Mail',
26 + 'password' => 'Passwort',
27 + 'password_confirm' => 'Passwort best&auml;tigen',
28 + 'password_hint' => 'Mindestlänge: 5 Zeichen',
29 + 'forgot_password' => 'Passwort vergessen?',
30 + 'remember_me' => 'Angemeldet bleiben',
31 + 'ldap_email_hint' => 'Bitte geben Sie eine E-Mail-Adresse ein, um diese mit dem Account zu nutzen.',
32 + 'create_account' => 'Account anlegen',
33 + 'social_login' => 'Social Login',
34 + 'social_registration' => 'Social Registrierung',
35 + 'social_registration_text' => 'Mit einem dieser Möglichkeiten registrieren oder anmelden.',
36 +
37 +
38 + 'register_thanks' => 'Vielen Dank für Ihre Registrierung!',
39 + 'register_confirm' => 'Bitte prüfen Sie Ihren E-Mail Eingang und klicken auf den Verifizieren-Button, um :appName nutzen zu können.',
40 + 'registrations_disabled' => 'Die Registrierung ist momentan nicht möglich',
41 + 'registration_email_domain_invalid' => 'Diese E-Mail-Domain ist für die Benutzer der Applikation nicht freigeschaltet.',
42 + 'register_success' => 'Vielen Dank für Ihre Registrierung! Die Daten sind gespeichert und Sie sind angemeldet.',
43 +
44 +
45 + /**
46 + * Password Reset
47 + */
48 + 'reset_password' => 'Passwort vergessen',
49 + 'reset_password_send_instructions' => 'Bitte geben Sie unten Ihre E-Mail-Adresse ein und Sie erhalten eine E-Mail, um Ihr Passwort zurück zu setzen.',
50 + 'reset_password_send_button' => 'Passwort zurücksetzen',
51 + 'reset_password_sent_success' => 'Eine E-Mail mit den Instruktionen, um Ihr Passwort zurückzusetzen wurde an :email gesendet.',
52 + 'reset_password_success' => 'Ihr Passwort wurde erfolgreich zurück gesetzt.',
53 +
54 + 'email_reset_subject' => 'Passwort zurücksetzen für :appName',
55 + 'email_reset_text' => 'Sie erhalten diese E-Mail, weil eine Passwort-Rücksetzung für Ihren Account beantragt wurde.',
56 + 'email_reset_not_requested' => 'Wenn Sie die Passwort-Rücksetzung nicht ausgelöst haben, ist kein weiteres Handeln notwendig.',
57 +
58 +
59 + /**
60 + * Email Confirmation
18 */ 61 */
19 'email_confirm_subject' => 'Best&auml;tigen sie ihre E-Mail Adresse bei :appName', 62 'email_confirm_subject' => 'Best&auml;tigen sie ihre E-Mail Adresse bei :appName',
20 'email_confirm_greeting' => 'Danke, dass sie :appName beigetreten sind!', 63 'email_confirm_greeting' => 'Danke, dass sie :appName beigetreten sind!',
...@@ -23,4 +66,10 @@ return [ ...@@ -23,4 +66,10 @@ return [
23 'email_confirm_send_error' => 'Best&auml;tigungs-E-Mail ben&ouml;tigt, aber das System konnte die E-Mail nicht versenden. Kontaktieren sie den Administrator, um sicherzustellen, dass das Sytsem korrekt eingerichtet ist.', 66 'email_confirm_send_error' => 'Best&auml;tigungs-E-Mail ben&ouml;tigt, aber das System konnte die E-Mail nicht versenden. Kontaktieren sie den Administrator, um sicherzustellen, dass das Sytsem korrekt eingerichtet ist.',
24 'email_confirm_success' => 'Ihre E-Mail Adresse wurde best&auml;tigt!', 67 'email_confirm_success' => 'Ihre E-Mail Adresse wurde best&auml;tigt!',
25 'email_confirm_resent' => 'Best&auml;tigungs-E-Mail wurde erneut versendet, bitte &uuml;berpr&uuml;fen sie ihren Posteingang.', 68 'email_confirm_resent' => 'Best&auml;tigungs-E-Mail wurde erneut versendet, bitte &uuml;berpr&uuml;fen sie ihren Posteingang.',
69 +
70 + 'email_not_confirmed' => 'E-Mail-Adresse ist nicht bestätigt',
71 + 'email_not_confirmed_text' => 'Ihre E-Mail-Adresse ist bisher nicht bestätigt.',
72 + 'email_not_confirmed_click_link' => 'Bitte klicken Sie auf den Link in der E-Mail, die Sie nach der Registrierung erhalten haben.',
73 + 'email_not_confirmed_resend' => 'Wenn Sie die E-Mail nicht erhalten haben, können Sie die Nachricht erneut anfordern. Füllen Sie hierzu bitte das folgende Formular aus:',
74 + 'email_not_confirmed_resend_button' => 'Bestätigungs E-Mail erneut senden',
26 ]; 75 ];
......
1 +<?php
2 +return [
3 +
4 + /**
5 + * Buttons
6 + */
7 + 'cancel' => 'Abbrechen',
8 + 'confirm' => 'Bestätigen',
9 + 'back' => 'Zurück',
10 + 'save' => 'Speichern',
11 + 'continue' => 'Weiter',
12 + 'select' => 'Auswählen',
13 +
14 + /**
15 + * Form Labels
16 + */
17 + 'name' => 'Name',
18 + 'description' => 'Beschreibung',
19 + 'role' => 'Rolle',
20 +
21 + /**
22 + * Actions
23 + */
24 + 'actions' => 'Aktionen',
25 + 'view' => 'Anzeigen',
26 + 'create' => 'Anlegen',
27 + 'update' => 'Aktualisieren',
28 + 'edit' => 'Bearbeiten',
29 + 'sort' => 'Sortieren',
30 + 'move' => 'Verschieben',
31 + 'delete' => 'L&ouml;schen',
32 + 'search' => 'Suchen',
33 + 'search_clear' => 'Suche l&ouml;schen',
34 + 'reset' => 'Zurücksetzen',
35 + 'remove' => 'Entfernen',
36 +
37 +
38 + /**
39 + * Misc
40 + */
41 + 'deleted_user' => 'Gel&ouml;schte Benutzer',
42 + 'no_activity' => 'Keine Aktivit&auml;ten zum Anzeigen',
43 + 'no_items' => 'Keine Eintr&auml;ge gefunden.',
44 + 'back_to_top' => 'nach oben',
45 + 'toggle_details' => 'Details zeigen/verstecken',
46 +
47 + /**
48 + * Header
49 + */
50 + 'view_profile' => 'Profil ansehen',
51 + 'edit_profile' => 'Profil bearbeiten',
52 +
53 + /**
54 + * Email Content
55 + */
56 + 'email_action_help' => 'Sollte es beim Anklicken des ":actionText" Buttons Probleme geben, kopieren Sie folgende URL und fügen diese in Ihrem Webbrowser ein:',
57 + 'email_rights' => 'Alle Rechte vorbehalten',
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' => 'Bild auswählen',
8 + 'image_all' => 'Alle',
9 + 'image_all_title' => 'Alle Bilder anzeigen',
10 + 'image_book_title' => 'Zeige alle Bilder, die in dieses Buch hochgeladen wurden',
11 + 'image_page_title' => 'Zeige alle Bilder, die auf diese Seite hochgeladen wurden',
12 + 'image_search_hint' => 'Nach Bildnamen suchen',
13 + 'image_uploaded' => 'Hochgeladen am :uploadedDate',
14 + 'image_load_more' => 'Mehr',
15 + 'image_image_name' => 'Bildname',
16 + 'image_delete_confirm' => 'Dieses Bild wird auf den folgenden Seiten benutzt. Bitte klicken Sie erneut auf löschen, wenn Sie dieses Bild tatsächlich entfernen möchten.',
17 + 'image_select_image' => 'Bild auswählen',
18 + 'image_dropzone' => 'Ziehen Sie Bilder hier hinein oder klicken Sie hier, um ein Bild auszuwählen',
19 + 'images_deleted' => 'Bilder gelöscht',
20 + 'image_preview' => 'Bildvorschau',
21 + 'image_upload_success' => 'Bild erfolgreich hochgeladen',
22 + 'image_update_success' => 'Bilddetails erfolgreich aktualisiert',
23 + 'image_delete_success' => 'Bild erfolgreich gelöscht'
24 +];
...\ No newline at end of file ...\ No newline at end of file
...@@ -8,5 +8,63 @@ return [ ...@@ -8,5 +8,63 @@ return [
8 8
9 // Pages 9 // Pages
10 'permission' => 'Sie haben keine Berechtigung auf diese Seite zuzugreifen.', 10 'permission' => 'Sie haben keine Berechtigung auf diese Seite zuzugreifen.',
11 - 'permissionJson' => 'Sie haben keine Berechtigung die angeforderte Aktion auszuf&uuml;hren.' 11 + 'permissionJson' => 'Sie haben keine Berechtigung die angeforderte Aktion auszuf&uuml;hren.',
12 +
13 + // Auth
14 + 'error_user_exists_different_creds' => 'Ein Benutzer mit der E-Mail-Adresse :email ist bereits mit anderen Anmeldedaten angelegt.',
15 + 'email_already_confirmed' => 'Die E-Mail-Adresse ist bereits best&auml;tigt. Bitte melden Sie sich an.',
16 + 'email_confirmation_invalid' => 'Der Best&auml;tigungs-Token ist nicht g&uuml;ltig oder wurde bereits verwendet. Bitte registrieren Sie sich erneut.',
17 + 'email_confirmation_expired' => 'Der Best&auml;tigungs-Token ist abgelaufen. Es wurde eine neue Best&auml;tigungs-E-Mail gesendet.',
18 + 'ldap_fail_anonymous' => 'Anonymer LDAP Zugriff ist fehlgeschlafgen',
19 + 'ldap_fail_authed' => 'LDAP Zugriff mit DN & Passwort ist fehlgeschlagen',
20 + 'ldap_extension_not_installed' => 'LDAP PHP Erweiterung ist nicht installiert.',
21 + 'ldap_cannot_connect' => 'Die Verbindung zu LDAP-Server ist fehlgeschlagen. Beim initialen Verbindungsaufbau trat ein Fehler auf.',
22 + 'social_no_action_defined' => 'Es ist keine Aktion definiert',
23 + 'social_account_in_use' => 'Dieses :socialAccount Konto wird bereits verwendet. Bitte melden Sie sich mit dem :socialAccount Konto an.',
24 + 'social_account_email_in_use' => 'Die E-Mail-Adresse :email ist bereits registriert. Wenn Sie bereits registriert sind, k&ouml;nnen Sie Ihr :socialAccount Konto in Ihren Profil-Einstellungen verkn&uuml;pfen.',
25 + 'social_account_existing' => 'Dieses :socialAccount Konto ist bereits mit Ihrem Profil verkn&uuml;pft.',
26 + 'social_account_already_used_existing' => 'Dieses :socialAccount Konto wird bereits durch einen anderen Benutzer verwendet.',
27 + 'social_account_not_used' => 'Dieses :socialAccount Konto ist bisher keinem Benutzer zugeordnet. Bitte verkn&uuml;pfen Sie deses in Ihrem Profil-Einstellungen.',
28 + 'social_account_register_instructions' => 'Wenn Sie bisher keinen Social-Media Konto besitzen k&ouml;nnen Sie ein solches Konto mit der :socialAccount Option anlegen.',
29 + 'social_driver_not_found' => 'Social-Media Konto Treiber nicht gefunden',
30 + 'social_driver_not_configured' => 'Ihr :socialAccount Konto ist nicht korrekt konfiguriert.',
31 +
32 + // System
33 + 'path_not_writable' => 'Die Datei kann nicht in den angegebenen Pfad :filePath hochgeladen werden. Stellen Sie sicher, dass dieser Ordner auf dem Server beschreibbar ist.',
34 + 'cannot_get_image_from_url' => 'Bild konnte nicht von der URL :url geladen werden.',
35 + 'cannot_create_thumbs' => 'Der Server kann keine Vorschau-Bilder erzeugen. Bitte pr&uuml;fen Sie, ob Sie die GD PHP Erweiterung installiert haben.',
36 + 'server_upload_limit' => 'Der Server verbietet das Hochladen von Dateien mit dieser Dateigr&ouml;&szlig;e. Bitte versuchen Sie es mit einer kleineren Datei.',
37 + 'image_upload_error' => 'Beim Hochladen des Bildes trat ein Fehler auf.',
38 +
39 + // Attachments
40 + 'attachment_page_mismatch' => 'Die Seite stimmt nach dem Hochladen des Anhangs nicht &uuml;berein.',
41 +
42 + // Pages
43 + 'page_draft_autosave_fail' => 'Fehler beim Speichern des Entwurfs. Stellen Sie sicher, dass Sie mit dem Internet verbunden sind, bevor Sie den Entwurf dieser Seite speichern.',
44 +
45 + // Entities
46 + 'entity_not_found' => 'Eintrag nicht gefunden',
47 + 'book_not_found' => 'Buch nicht gefunden',
48 + 'page_not_found' => 'Seite nicht gefunden',
49 + 'chapter_not_found' => 'Kapitel nicht gefunden',
50 + 'selected_book_not_found' => 'Das gew&auml;hlte Buch wurde nicht gefunden.',
51 + 'selected_book_chapter_not_found' => 'Das gew&auml;hlte Buch oder Kapitel wurde nicht gefunden.',
52 + 'guests_cannot_save_drafts' => 'G&auml;ste k&ouml;nnen keine Entw&uuml;rfe speichern',
53 +
54 + // Users
55 + 'users_cannot_delete_only_admin' => 'Sie k&ouml;nnen den einzigen Administrator nicht l&ouml;schen.',
56 + 'users_cannot_delete_guest' => 'Sie k&ouml;nnen den Gast-Benutzer nicht l&ouml;schen',
57 +
58 + // Roles
59 + 'role_cannot_be_edited' => 'Diese Rolle kann nicht bearbeitet werden.',
60 + 'role_system_cannot_be_deleted' => 'Dies ist eine Systemrolle und kann nicht gel&ouml;scht werden',
61 + 'role_registration_default_cannot_delete' => 'Diese Rolle kann nicht gel&ouml;scht werden solange sie als Standardrolle f&uuml;r neue Registrierungen gesetzt ist',
62 +
63 + // Error pages
64 + '404_page_not_found' => 'Seite nicht gefunden',
65 + 'sorry_page_not_found' => 'Entschuldigung. Die Seite, die Sie angefordert haben wurde nicht gefunden.',
66 + 'return_home' => 'Zur&uuml;ck zur Startseite',
67 + 'error_occurred' => 'Es ist ein Fehler aufgetreten',
68 + 'app_down' => ':appName befindet sich aktuell im Wartungsmodus.',
69 + 'back_soon' => 'Wir werden so schnell wie m&ouml;glich wieder online sein.',
12 ]; 70 ];
......
...@@ -16,7 +16,7 @@ return [ ...@@ -16,7 +16,7 @@ return [
16 'password' => 'Pass&ouml;rter m&uuml;ssen mindestens sechs Zeichen enthalten und die Wiederholung muss identisch sein.', 16 'password' => 'Pass&ouml;rter m&uuml;ssen mindestens sechs Zeichen enthalten und die Wiederholung muss identisch sein.',
17 'user' => "Wir k&ouml;nnen keinen Benutzer mit dieser E-Mail Adresse finden.", 17 'user' => "Wir k&ouml;nnen keinen Benutzer mit dieser E-Mail Adresse finden.",
18 'token' => 'Dieser Passwort-Reset-Token ist ung&uuml;ltig.', 18 'token' => 'Dieser Passwort-Reset-Token ist ung&uuml;ltig.',
19 - 'sent' => 'Wir haben ihnen eine E-Mail mit einem Link zum Zurücksetzen des Passworts zugesendet!', 19 + 'sent' => 'Wir haben Ihnen eine E-Mail mit einem Link zum Zur&uuml;cksetzen des Passworts zugesendet!',
20 'reset' => 'Ihr Passwort wurde zur&uuml;ckgesetzt!', 20 'reset' => 'Ihr Passwort wurde zur&uuml;ckgesetzt!',
21 21
22 ]; 22 ];
......
...@@ -10,14 +10,19 @@ return [ ...@@ -10,14 +10,19 @@ return [
10 10
11 'settings' => 'Einstellungen', 11 'settings' => 'Einstellungen',
12 'settings_save' => 'Einstellungen speichern', 12 'settings_save' => 'Einstellungen speichern',
13 + 'settings_save_success' => 'Einstellungen gespeichert',
14 +
15 + /**
16 + * App settings
17 + */
13 18
14 'app_settings' => 'Anwendungseinstellungen', 19 'app_settings' => 'Anwendungseinstellungen',
15 'app_name' => 'Anwendungsname', 20 'app_name' => 'Anwendungsname',
16 - 'app_name_desc' => 'Dieser Name wird im Header und E-Mails angezeigt.', 21 + 'app_name_desc' => 'Dieser Name wird im Header und in E-Mails angezeigt.',
17 'app_name_header' => 'Anwendungsname im Header anzeigen?', 22 'app_name_header' => 'Anwendungsname im Header anzeigen?',
18 'app_public_viewing' => '&Ouml;ffentliche Ansicht erlauben?', 23 'app_public_viewing' => '&Ouml;ffentliche Ansicht erlauben?',
19 'app_secure_images' => 'Erh&ouml;hte Sicherheit f&uuml;r Bilduploads aktivieren?', 24 '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.', 25 + '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 Zugriff zu verhindern.',
21 'app_editor' => 'Seiteneditor', 26 '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.', 27 'app_editor_desc' => 'W&auml;hlen sie den Editor aus, der von allen Benutzern genutzt werden soll, um Seiten zu editieren.',
23 'app_custom_html' => 'Benutzerdefinierter HTML <head> Inhalt', 28 'app_custom_html' => 'Benutzerdefinierter HTML <head> Inhalt',
...@@ -25,15 +30,82 @@ return [ ...@@ -25,15 +30,82 @@ return [
25 'app_logo' => 'Anwendungslogo', 30 'app_logo' => 'Anwendungslogo',
26 'app_logo_desc' => 'Dieses Bild sollte 43px hoch sein. <br>Gr&ouml;&szlig;ere Bilder werden verkleinert.', 31 'app_logo_desc' => 'Dieses Bild sollte 43px hoch sein. <br>Gr&ouml;&szlig;ere Bilder werden verkleinert.',
27 'app_primary_color' => 'Prim&auml;re Anwendungsfarbe', 32 'app_primary_color' => 'Prim&auml;re Anwendungsfarbe',
28 - 'app_primary_color_desc' => 'Dies sollte ein HEX Wert sein. <br>Leer lassen des Feldes setzt auf die Standard-Anwendungsfarbe zur&uuml;ck.', 33 + 'app_primary_color_desc' => 'Dies sollte ein HEX Wert sein. <br>Wenn Sie nicht eingeben, wird die Anwendung auf die Standardfarbe zur&uuml;ckgesetzt.',
34 +
35 + /**
36 + * Registration settings
37 + */
29 38
30 'reg_settings' => 'Registrierungseinstellungen', 39 'reg_settings' => 'Registrierungseinstellungen',
31 'reg_allow' => 'Registrierung erlauben?', 40 'reg_allow' => 'Registrierung erlauben?',
32 'reg_default_role' => 'Standard-Benutzerrolle nach Registrierung', 41 'reg_default_role' => 'Standard-Benutzerrolle nach Registrierung',
33 'reg_confirm_email' => 'Best&auml;tigung per E-Mail erforderlich?', 42 'reg_confirm_email' => 'Best&auml;tigung per E-Mail erforderlich?',
34 - 'reg_confirm_email_desc' => 'Falls die Einschr&auml;nkung f&uumlr; Domains genutzt wird, ist die Best&auml;tigung per E-Mail zwingend erforderlich und der untenstehende Wert wird ignoriert.', 43 + 'reg_confirm_email_desc' => 'Falls die Einschr&auml;nkung f&uuml;r Domains genutzt wird, ist die Best&auml;tigung per E-Mail zwingend erforderlich und der untenstehende Wert wird ignoriert.',
35 'reg_confirm_restrict_domain' => 'Registrierung auf bestimmte Domains einschr&auml;nken', 44 'reg_confirm_restrict_domain' => 'Registrierung auf bestimmte Domains einschr&auml;nken',
36 'reg_confirm_restrict_domain_desc' => 'F&uuml;gen sie eine, durch Komma getrennte, Liste von E-Mail Domains hinzu, auf die die Registrierung eingeschr&auml;nkt werden soll. Benutzern wird eine E-Mail gesendet, um ihre E-Mail Adresse zu best&auml;tigen, bevor sie diese Anwendung nutzen k&ouml;nnen. <br> Hinweis: Benutzer k&ouml;nnen ihre E-Mail Adresse nach erfolgreicher Registrierung &auml;ndern.', 45 'reg_confirm_restrict_domain_desc' => 'F&uuml;gen sie eine, durch Komma getrennte, Liste von E-Mail Domains hinzu, auf die die Registrierung eingeschr&auml;nkt werden soll. Benutzern wird eine E-Mail gesendet, um ihre E-Mail Adresse zu best&auml;tigen, bevor sie diese Anwendung nutzen k&ouml;nnen. <br> Hinweis: Benutzer k&ouml;nnen ihre E-Mail Adresse nach erfolgreicher Registrierung &auml;ndern.',
37 'reg_confirm_restrict_domain_placeholder' => 'Keine Einschr&auml;nkung gesetzt', 46 'reg_confirm_restrict_domain_placeholder' => 'Keine Einschr&auml;nkung gesetzt',
38 47
48 + /**
49 + * Role settings
50 + */
51 +
52 + 'roles' => 'Rollen',
53 + 'role_user_roles' => 'Benutzer-Rollen',
54 + 'role_create' => 'Neue Rolle anlegen',
55 + 'role_create_success' => 'Rolle erfolgreich angelegt',
56 + 'role_delete' => 'Rolle l&ouml;schen',
57 + 'role_delete_confirm' => 'Sie m&ouml;chten die Rolle \':roleName\' l&ouml;schen.',
58 + 'role_delete_users_assigned' => 'Diese Rolle ist :userCount Benutzern zugeordnet. Sie k&ouml;nnen unten eine neue Rolle ausw&auml;hlen, die Sie diesen Benutzern zuordnen m&ouml;chten.',
59 + 'role_delete_no_migration' => "Den Benutzern keine andere Rolle zuordnen",
60 + 'role_delete_sure' => 'Sind Sie sicher, dass Sie diese Rolle l&ouml;schen m&ouml;chten?',
61 + 'role_delete_success' => 'Rolle erfolgreich gel&ouml;scht',
62 + 'role_edit' => 'Rolle bearbeiten',
63 + 'role_details' => 'Rollen-Details',
64 + 'role_name' => 'Rollenname',
65 + 'role_desc' => 'Kurzbeschreibung der Rolle',
66 + 'role_system' => 'System-Berechtigungen',
67 + 'role_manage_users' => 'Benutzer verwalten',
68 + 'role_manage_roles' => 'Rollen & Rollen-Berechtigungen verwalten',
69 + 'role_manage_entity_permissions' => 'Alle Buch-, Kapitel und Seiten-Berechtigungen verwalten',
70 + 'role_manage_own_entity_permissions' => 'Nur Berechtigungen eigener B&uuml;cher, Kapitel und Seiten verwalten',
71 + 'role_manage_settings' => 'Globaleinstellungen verwalrten',
72 + 'role_asset' => 'Berechtigungen',
73 + 'role_asset_desc' => 'Diese Berechtigungen gelten f&uuml;r den Standard-Zugriff innerhalb des Systems. Berechtigungen f&uuml;r B&uuml;cher, Kapitel und Seiten &uuml;berschreiben diese Berechtigungenen.',
74 + 'role_all' => 'Alle',
75 + 'role_own' => 'Eigene',
76 + 'role_controlled_by_asset' => 'Controlled by the asset they are uploaded to',
77 + 'role_save' => 'Rolle speichern',
78 + 'role_update_success' => 'Rolle erfolgreich gespeichert',
79 + 'role_users' => 'Dieser Rolle zugeordnete Benutzer',
80 + 'role_users_none' => 'Bisher sind dieser Rolle keiner Benutzer zugeordnet,',
81 +
82 + /**
83 + * Users
84 + */
85 +
86 + 'users' => 'Benutzer',
87 + 'user_profile' => 'Benutzerprofil',
88 + 'users_add_new' => 'Benutzer hinzuf&uuml;gen',
89 + 'users_search' => 'Benutzer suchen',
90 + 'users_role' => 'Benutzerrollen',
91 + 'users_external_auth_id' => 'Externe Authentifizierungs-ID',
92 + 'users_password_warning' => 'F&uuml;llen Sie die folgenden Felder nur aus, wenn Sie Ihr Passwort &auml;ndern m&ouml;chten:',
93 + 'users_system_public' => 'Dieser Benutzer repr&auml;sentiert alle Gast-Benutzer, die diese Seite betrachten. Er kann nicht zum Anmelden benutzt werden, sondern wird automatisch zugeordnet.',
94 + 'users_delete' => 'Benutzer l&ouml;schen',
95 + 'users_delete_named' => 'Benutzer :userName l&ouml;schen',
96 + 'users_delete_warning' => 'Sie m&ouml;chten den Benutzer \':userName\' g&auml;nzlich aus dem System l&ouml;schen.',
97 + 'users_delete_confirm' => 'Sind Sie sicher, dass Sie diesen Benutzer l&ouml;schen m&ouml;chten?',
98 + 'users_delete_success' => 'Benutzer erfolgreich gel&ouml;scht.',
99 + 'users_edit' => 'Benutzer bearbeiten',
100 + 'users_edit_profile' => 'Profil bearbeiten',
101 + 'users_edit_success' => 'Benutzer erfolgreich aktualisisert',
102 + 'users_avatar' => 'Benutzer-Bild',
103 + 'users_avatar_desc' => 'Dieses Bild sollte einen Durchmesser von ca. 256px haben.',
104 + 'users_preferred_language' => 'Bevorzugte Sprache',
105 + 'users_social_accounts' => 'Social-Media Konten',
106 + 'users_social_accounts_info' => 'Hier k&ouml;nnen Sie andere Social-Media Konten f&uuml;r eine schnellere und einfachere Anmeldung verkn&uuml;pfen. Wenn Sie ein Social-Media Konto hier l&ouml;sen, bleibt der Zugriff erhalteb. Entfernen Sie in diesem Falle die Berechtigung in Ihren Profil-Einstellungen des verkn&uuml;pften Social-Media Kontos.',
107 + 'users_social_connect' => 'Social-Media Konto verkn&uuml;pfen',
108 + 'users_social_disconnect' => 'Social-Media Kontoverkn&uuml;pfung l&ouml;sen',
109 + 'users_social_connected' => ':socialAccount Konto wurde erfolgreich mit dem Profil verkn&uuml;pft.',
110 + 'users_social_disconnected' => ':socialAccount Konto wurde erfolgreich vom Profil gel&ouml;st.',
39 ]; 111 ];
......
...@@ -18,6 +18,8 @@ return [ ...@@ -18,6 +18,8 @@ return [
18 */ 18 */
19 'sign_up' => 'Sign up', 19 'sign_up' => 'Sign up',
20 'log_in' => 'Log in', 20 'log_in' => 'Log in',
21 + 'log_in_with' => 'Login with :socialDriver',
22 + 'sign_up_with' => 'Sign up with :socialDriver',
21 'logout' => 'Logout', 23 'logout' => 'Logout',
22 24
23 'name' => 'Name', 25 'name' => 'Name',
......
...@@ -32,13 +32,12 @@ ...@@ -32,13 +32,12 @@
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">{{ trans('auth.social_login') }}</h3> 35 + @foreach($socialDrivers as $driver => $name)
36 - @if(isset($socialDrivers['google'])) 36 + <a id="social-login-{{$driver}}" class="button block muted-light svg text-left" href="{{ baseUrl("/login/service/" . $driver) }}">
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> 37 + @icon($driver)
38 - @endif 38 + {{ trans('auth.log_in_with', ['socialDriver' => $name]) }}
39 - @if(isset($socialDrivers['github'])) 39 + </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> 40 + @endforeach
41 - @endif
42 @endif 41 @endif
43 </div> 42 </div>
44 </div> 43 </div>
......
...@@ -35,14 +35,12 @@ ...@@ -35,14 +35,12 @@
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">{{ trans('auth.social_registration') }}</h3> 38 + @foreach($socialDrivers as $driver => $name)
39 - <p class="text-small">{{ trans('auth.social_registration_text') }}</p> 39 + <a id="social-register-{{$driver}}" class="button block muted-light svg text-left" href="{{ baseUrl("/register/service/" . $driver) }}">
40 - @if(isset($socialDrivers['google'])) 40 + @icon($driver)
41 - <a href="{{ baseUrl("/register/service/google") }}" style="color: #DC4E41;"><i class="zmdi zmdi-google-plus-box zmdi-hc-4x"></i></a> 41 + {{ trans('auth.sign_up_with', ['socialDriver' => $name]) }}
42 - @endif 42 + </a>
43 - @if(isset($socialDrivers['github'])) 43 + @endforeach
44 - <a href="{{ baseUrl("/register/service/github") }}" style="color:#444;"><i class="zmdi zmdi-github zmdi-hc-4x"></i></a>
45 - @endif
46 @endif 44 @endif
47 </div> 45 </div>
48 </div> 46 </div>
......
...@@ -23,9 +23,10 @@ ...@@ -23,9 +23,10 @@
23 23
24 @include('partials/custom-styles') 24 @include('partials/custom-styles')
25 25
26 + @if(setting('app-custom-head') && \Route::currentRouteName() !== 'settings')
26 <!-- Custom user content --> 27 <!-- Custom user content -->
27 - @if(setting('app-custom-head'))
28 {!! setting('app-custom-head') !!} 28 {!! setting('app-custom-head') !!}
29 + <!-- End custom user content -->
29 @endif 30 @endif
30 </head> 31 </head>
31 <body class="@yield('body-class')" ng-app="bookStack"> 32 <body class="@yield('body-class')" ng-app="bookStack">
......
1 +<!doctype html>
2 +<html lang="en">
3 +<head>
4 + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
5 + <title>{{ $book->name }}</title>
6 +
7 + <style>
8 + @if (!app()->environment('testing'))
9 + {!! file_get_contents(public_path('/css/export-styles.css')) !!}
10 + @endif
11 + .page-break {
12 + page-break-after: always;
13 + }
14 + .chapter-hint {
15 + color: #888;
16 + margin-top: 32px;
17 + }
18 + .chapter-hint + h1 {
19 + margin-top: 0;
20 + }
21 + ul.contents ul li {
22 + list-style: circle;
23 + }
24 + @media screen {
25 + .page-break {
26 + border-top: 1px solid #DDD;
27 + }
28 + }
29 + </style>
30 + @yield('head')
31 +</head>
32 +<body>
33 +<div class="container">
34 + <div class="row">
35 + <div class="col-md-8 col-md-offset-2">
36 + <div class="page-content">
37 +
38 + <h1 style="font-size: 4.8em">{{$book->name}}</h1>
39 +
40 + <p>{{ $book->description }}</p>
41 +
42 + @if(count($bookChildren) > 0)
43 + <ul class="contents">
44 + @foreach($bookChildren as $bookChild)
45 + <li><a href="#{{$bookChild->getType()}}-{{$bookChild->id}}">{{ $bookChild->name }}</a></li>
46 + @if($bookChild->isA('chapter') && count($bookChild->pages) > 0)
47 + <ul>
48 + @foreach($bookChild->pages as $page)
49 + <li><a href="#page-{{$page->id}}">{{ $page->name }}</a></li>
50 + @endforeach
51 + </ul>
52 + @endif
53 + @endforeach
54 + </ul>
55 + @endif
56 +
57 + @foreach($bookChildren as $bookChild)
58 + <div class="page-break"></div>
59 + <h1 id="{{$bookChild->getType()}}-{{$bookChild->id}}">{{ $bookChild->name }}</h1>
60 + @if($bookChild->isA('chapter'))
61 + <p>{{ $bookChild->description }}</p>
62 + @if(count($bookChild->pages) > 0)
63 + @foreach($bookChild->pages as $page)
64 + <div class="page-break"></div>
65 + <div class="chapter-hint">{{$bookChild->name}}</div>
66 + <h1 id="page-{{$page->id}}">{{ $page->name }}</h1>
67 + {!! $page->html !!}
68 + @endforeach
69 + @endif
70 + @else
71 + {!! $bookChild->html !!}
72 + @endif
73 + @endforeach
74 +
75 + </div>
76 + </div>
77 + </div>
78 +</div>
79 +</body>
80 +</html>
...@@ -5,11 +5,19 @@ ...@@ -5,11 +5,19 @@
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-6 faded"> 8 + <div class="col-sm-6 faded">
9 @include('books._breadcrumbs', ['book' => $book]) 9 @include('books._breadcrumbs', ['book' => $book])
10 </div> 10 </div>
11 - <div class="col-md-6"> 11 + <div class="col-sm-6">
12 <div class="action-buttons faded"> 12 <div class="action-buttons faded">
13 + <span dropdown class="dropdown-container">
14 + <div dropdown-toggle class="text-button text-primary"><i class="zmdi zmdi-open-in-new"></i>{{ trans('entities.pages_export') }}</div>
15 + <ul class="wide">
16 + <li><a href="{{ $book->getUrl('/export/html') }}" target="_blank">{{ trans('entities.pages_export_html') }} <span class="text-muted float right">.html</span></a></li>
17 + <li><a href="{{ $book->getUrl('/export/pdf') }}" target="_blank">{{ trans('entities.pages_export_pdf') }} <span class="text-muted float right">.pdf</span></a></li>
18 + <li><a href="{{ $book->getUrl('/export/plaintext') }}" target="_blank">{{ trans('entities.pages_export_text') }} <span class="text-muted float right">.txt</span></a></li>
19 + </ul>
20 + </span>
13 @if(userCan('page-create', $book)) 21 @if(userCan('page-create', $book))
14 <a href="{{ $book->getUrl('/page/create') }}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>{{ trans('entities.pages_new') }}</a> 22 <a href="{{ $book->getUrl('/page/create') }}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>{{ trans('entities.pages_new') }}</a>
15 @endif 23 @endif
......
1 +<!doctype html>
2 +<html lang="en">
3 +<head>
4 + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
5 + <title>{{ $chapter->name }}</title>
6 +
7 + <style>
8 + @if (!app()->environment('testing'))
9 + {!! file_get_contents(public_path('/css/export-styles.css')) !!}
10 + @endif
11 + .page-break {
12 + page-break-after: always;
13 + }
14 + ul.contents ul li {
15 + list-style: circle;
16 + }
17 + @media screen {
18 + .page-break {
19 + border-top: 1px solid #DDD;
20 + }
21 + }
22 + </style>
23 + @yield('head')
24 +</head>
25 +<body>
26 +<div class="container">
27 + <div class="row">
28 + <div class="col-md-8 col-md-offset-2">
29 + <div class="page-content">
30 +
31 + <h1 style="font-size: 4.8em">{{$chapter->name}}</h1>
32 +
33 + <p>{{ $chapter->description }}</p>
34 +
35 + @if(count($pages) > 0)
36 + <ul class="contents">
37 + @foreach($pages as $page)
38 + <li><a href="#page-{{$page->id}}">{{ $page->name }}</a></li>
39 + @endforeach
40 + </ul>
41 + @endif
42 +
43 + @foreach($pages as $page)
44 + <div class="page-break"></div>
45 + <h1 id="page-{{$page->id}}">{{ $page->name }}</h1>
46 + {!! $page->html !!}
47 + @endforeach
48 +
49 + </div>
50 + </div>
51 + </div>
52 +</div>
53 +</body>
54 +</html>
...@@ -10,6 +10,14 @@ ...@@ -10,6 +10,14 @@
10 </div> 10 </div>
11 <div class="col-sm-4 faded"> 11 <div class="col-sm-4 faded">
12 <div class="action-buttons"> 12 <div class="action-buttons">
13 + <span dropdown class="dropdown-container">
14 + <div dropdown-toggle class="text-button text-primary"><i class="zmdi zmdi-open-in-new"></i>{{ trans('entities.pages_export') }}</div>
15 + <ul class="wide">
16 + <li><a href="{{ $chapter->getUrl('/export/html') }}" target="_blank">{{ trans('entities.pages_export_html') }} <span class="text-muted float right">.html</span></a></li>
17 + <li><a href="{{ $chapter->getUrl('/export/pdf') }}" target="_blank">{{ trans('entities.pages_export_pdf') }} <span class="text-muted float right">.pdf</span></a></li>
18 + <li><a href="{{ $chapter->getUrl('/export/plaintext') }}" target="_blank">{{ trans('entities.pages_export_text') }} <span class="text-muted float right">.txt</span></a></li>
19 + </ul>
20 + </span>
13 @if(userCan('page-create', $chapter)) 21 @if(userCan('page-create', $chapter))
14 <a href="{{ $chapter->getUrl('/create-page') }}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>{{ trans('entities.pages_new') }}</a> 22 <a href="{{ $chapter->getUrl('/create-page') }}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>{{ trans('entities.pages_new') }}</a>
15 @endif 23 @endif
......
...@@ -5,7 +5,9 @@ ...@@ -5,7 +5,9 @@
5 <title>{{ $page->name }}</title> 5 <title>{{ $page->name }}</title>
6 6
7 <style> 7 <style>
8 - {!! $css !!} 8 + @if (!app()->environment('testing'))
9 + {!! file_get_contents(public_path('/css/export-styles.css')) !!}
10 + @endif
9 </style> 11 </style>
10 @yield('head') 12 @yield('head')
11 </head> 13 </head>
......
1 <div ng-non-bindable> 1 <div ng-non-bindable>
2 2
3 - <h1 id="bkmrk-page-title" class="float left">{{$page->name}}</h1> 3 + <h1 id="bkmrk-page-title">{{$page->name}}</h1>
4 4
5 <div style="clear:left;"></div> 5 <div style="clear:left;"></div>
6 6
......
...@@ -30,11 +30,5 @@ ...@@ -30,11 +30,5 @@
30 clear: both; 30 clear: both;
31 display: block; 31 display: block;
32 } 32 }
33 -
34 - .tag-display {
35 - min-width: 0;
36 - max-width: none;
37 - display: none;
38 - }
39 </style> 33 </style>
40 @stop 34 @stop
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
30 <header id="header"> 30 <header id="header">
31 <div class="container"> 31 <div class="container">
32 <div class="row"> 32 <div class="row">
33 - <div class="col-md-6"> 33 + <div class="col-sm-6">
34 34
35 <a href="{{ baseUrl('/') }}" class="logo"> 35 <a href="{{ baseUrl('/') }}" class="logo">
36 @if(setting('app-logo', '') !== 'none') 36 @if(setting('app-logo', '') !== 'none')
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
41 @endif 41 @endif
42 </a> 42 </a>
43 </div> 43 </div>
44 - <div class="col-md-6"> 44 + <div class="col-sm-6">
45 <div class="float right"> 45 <div class="float right">
46 <div class="links text-center"> 46 <div class="links text-center">
47 @yield('header-buttons') 47 @yield('header-buttons')
......
...@@ -59,30 +59,18 @@ ...@@ -59,30 +59,18 @@
59 <h3>{{ trans('settings.users_social_accounts') }}</h3> 59 <h3>{{ trans('settings.users_social_accounts') }}</h3>
60 <p class="text-muted">{{ trans('settings.users_social_accounts_info') }}</p> 60 <p class="text-muted">{{ trans('settings.users_social_accounts_info') }}</p>
61 <div class="row"> 61 <div class="row">
62 - @if(isset($activeSocialDrivers['google'])) 62 + @foreach($activeSocialDrivers as $driver => $enabled)
63 <div class="col-md-3 text-center"> 63 <div class="col-md-3 text-center">
64 - <div><i class="zmdi zmdi-google-plus-box zmdi-hc-4x" style="color: #DC4E41;"></i></div> 64 + <div>@icon($driver, ['width' => 56])</div>
65 <div> 65 <div>
66 - @if($user->hasSocialAccount('google')) 66 + @if($user->hasSocialAccount($driver))
67 - <a href="{{ baseUrl("/login/service/google/detach") }}" class="button neg">{{ trans('settings.users_social_disconnect') }}</a> 67 + <a href="{{ baseUrl("/login/service/{$driver}/detach") }}" class="button neg">{{ trans('settings.users_social_disconnect') }}</a>
68 @else 68 @else
69 - <a href="{{ baseUrl("/login/service/google") }}" class="button pos">{{ trans('settings.users_social_connect') }}</a> 69 + <a href="{{ baseUrl("/login/service/{$driver}") }}" class="button pos">{{ trans('settings.users_social_connect') }}</a>
70 @endif 70 @endif
71 </div> 71 </div>
72 </div> 72 </div>
73 - @endif 73 + @endforeach
74 - @if(isset($activeSocialDrivers['github']))
75 - <div class="col-md-3 text-center">
76 - <div><i class="zmdi zmdi-github zmdi-hc-4x" style="color: #444;"></i></div>
77 - <div>
78 - @if($user->hasSocialAccount('github'))
79 - <a href="{{ baseUrl("/login/service/github/detach") }}" class="button neg">{{ trans('settings.users_social_disconnect') }}</a>
80 - @else
81 - <a href="{{ baseUrl("/login/service/github") }}" class="button pos">{{ trans('settings.users_social_connect') }}</a>
82 - @endif
83 - </div>
84 - </div>
85 - @endif
86 </div> 74 </div>
87 @endif 75 @endif
88 76
......
...@@ -26,6 +26,9 @@ Route::group(['middleware' => 'auth'], function () { ...@@ -26,6 +26,9 @@ Route::group(['middleware' => 'auth'], function () {
26 Route::get('/{slug}/delete', 'BookController@showDelete'); 26 Route::get('/{slug}/delete', 'BookController@showDelete');
27 Route::get('/{bookSlug}/sort', 'BookController@sort'); 27 Route::get('/{bookSlug}/sort', 'BookController@sort');
28 Route::put('/{bookSlug}/sort', 'BookController@saveSort'); 28 Route::put('/{bookSlug}/sort', 'BookController@saveSort');
29 + Route::get('/{bookSlug}/export/html', 'BookController@exportHtml');
30 + Route::get('/{bookSlug}/export/pdf', 'BookController@exportPdf');
31 + Route::get('/{bookSlug}/export/plaintext', 'BookController@exportPlainText');
29 32
30 // Pages 33 // Pages
31 Route::get('/{bookSlug}/page/create', 'PageController@create'); 34 Route::get('/{bookSlug}/page/create', 'PageController@create');
...@@ -64,6 +67,9 @@ Route::group(['middleware' => 'auth'], function () { ...@@ -64,6 +67,9 @@ Route::group(['middleware' => 'auth'], function () {
64 Route::put('/{bookSlug}/chapter/{chapterSlug}/move', 'ChapterController@move'); 67 Route::put('/{bookSlug}/chapter/{chapterSlug}/move', 'ChapterController@move');
65 Route::get('/{bookSlug}/chapter/{chapterSlug}/edit', 'ChapterController@edit'); 68 Route::get('/{bookSlug}/chapter/{chapterSlug}/edit', 'ChapterController@edit');
66 Route::get('/{bookSlug}/chapter/{chapterSlug}/permissions', 'ChapterController@showRestrict'); 69 Route::get('/{bookSlug}/chapter/{chapterSlug}/permissions', 'ChapterController@showRestrict');
70 + Route::get('/{bookSlug}/chapter/{chapterSlug}/export/pdf', 'ChapterController@exportPdf');
71 + Route::get('/{bookSlug}/chapter/{chapterSlug}/export/html', 'ChapterController@exportHtml');
72 + Route::get('/{bookSlug}/chapter/{chapterSlug}/export/plaintext', 'ChapterController@exportPlainText');
67 Route::put('/{bookSlug}/chapter/{chapterSlug}/permissions', 'ChapterController@restrict'); 73 Route::put('/{bookSlug}/chapter/{chapterSlug}/permissions', 'ChapterController@restrict');
68 Route::get('/{bookSlug}/chapter/{chapterSlug}/delete', 'ChapterController@showDelete'); 74 Route::get('/{bookSlug}/chapter/{chapterSlug}/delete', 'ChapterController@showDelete');
69 Route::delete('/{bookSlug}/chapter/{chapterSlug}', 'ChapterController@destroy'); 75 Route::delete('/{bookSlug}/chapter/{chapterSlug}', 'ChapterController@destroy');
...@@ -129,7 +135,7 @@ Route::group(['middleware' => 'auth'], function () { ...@@ -129,7 +135,7 @@ Route::group(['middleware' => 'auth'], function () {
129 135
130 // Settings 136 // Settings
131 Route::group(['prefix' => 'settings'], function() { 137 Route::group(['prefix' => 'settings'], function() {
132 - Route::get('/', 'SettingController@index'); 138 + Route::get('/', 'SettingController@index')->name('settings');
133 Route::post('/', 'SettingController@update'); 139 Route::post('/', 'SettingController@update');
134 140
135 // Users 141 // Users
......
1 -<?php 1 +<?php namespace Tests;
2 2
3 -use Illuminate\Foundation\Testing\WithoutMiddleware;
4 -use Illuminate\Foundation\Testing\DatabaseMigrations;
5 -use Illuminate\Foundation\Testing\DatabaseTransactions;
6 3
7 -class ActivityTrackingTest extends TestCase 4 +class ActivityTrackingTest extends BrowserKitTest
8 { 5 {
9 6
10 public function test_recently_viewed_books() 7 public function test_recently_viewed_books()
......
1 -<?php 1 +<?php namespace Tests;
2 2
3 -class AttachmentTest extends TestCase 3 +class AttachmentTest extends BrowserKitTest
4 { 4 {
5 /** 5 /**
6 * Get a test file that can be uploaded 6 * Get a test file that can be uploaded
...@@ -75,7 +75,6 @@ class AttachmentTest extends TestCase ...@@ -75,7 +75,6 @@ class AttachmentTest extends TestCase
75 { 75 {
76 $page = \BookStack\Page::first(); 76 $page = \BookStack\Page::first();
77 $this->asAdmin(); 77 $this->asAdmin();
78 - $admin = $this->getAdmin();
79 $fileName = 'upload_test_file.txt'; 78 $fileName = 'upload_test_file.txt';
80 79
81 $this->uploadFile($fileName, $page->id); 80 $this->uploadFile($fileName, $page->id);
......
1 -<?php 1 +<?php namespace Tests;
2 2
3 use BookStack\Notifications\ConfirmEmail; 3 use BookStack\Notifications\ConfirmEmail;
4 use Illuminate\Support\Facades\Notification; 4 use Illuminate\Support\Facades\Notification;
5 5
6 -class AuthTest extends TestCase 6 +class AuthTest extends BrowserKitTest
7 { 7 {
8 8
9 public function test_auth_working() 9 public function test_auth_working()
...@@ -88,7 +88,7 @@ class AuthTest extends TestCase ...@@ -88,7 +88,7 @@ class AuthTest extends TestCase
88 ->press('Resend Confirmation Email'); 88 ->press('Resend Confirmation Email');
89 89
90 // Get confirmation and confirm notification matches 90 // Get confirmation and confirm notification matches
91 - $emailConfirmation = DB::table('email_confirmations')->where('user_id', '=', $dbUser->id)->first(); 91 + $emailConfirmation = \DB::table('email_confirmations')->where('user_id', '=', $dbUser->id)->first();
92 Notification::assertSentTo($dbUser, ConfirmEmail::class, function($notification, $channels) use ($emailConfirmation) { 92 Notification::assertSentTo($dbUser, ConfirmEmail::class, function($notification, $channels) use ($emailConfirmation) {
93 return $notification->token === $emailConfirmation->token; 93 return $notification->token === $emailConfirmation->token;
94 }); 94 });
...@@ -177,7 +177,7 @@ class AuthTest extends TestCase ...@@ -177,7 +177,7 @@ class AuthTest extends TestCase
177 ->seePageIs('/settings/users'); 177 ->seePageIs('/settings/users');
178 178
179 $userPassword = \BookStack\User::find($user->id)->password; 179 $userPassword = \BookStack\User::find($user->id)->password;
180 - $this->assertTrue(Hash::check('newpassword', $userPassword)); 180 + $this->assertTrue(\Hash::check('newpassword', $userPassword));
181 } 181 }
182 182
183 public function test_user_deletion() 183 public function test_user_deletion()
...@@ -220,6 +220,9 @@ class AuthTest extends TestCase ...@@ -220,6 +220,9 @@ class AuthTest extends TestCase
220 220
221 public function test_reset_password_flow() 221 public function test_reset_password_flow()
222 { 222 {
223 +
224 + Notification::fake();
225 +
223 $this->visit('/login')->click('Forgot Password?') 226 $this->visit('/login')->click('Forgot Password?')
224 ->seePageIs('/password/email') 227 ->seePageIs('/password/email')
225 ->type('admin@admin.com', 'email') 228 ->type('admin@admin.com', 'email')
...@@ -230,8 +233,12 @@ class AuthTest extends TestCase ...@@ -230,8 +233,12 @@ class AuthTest extends TestCase
230 'email' => 'admin@admin.com' 233 'email' => 'admin@admin.com'
231 ]); 234 ]);
232 235
233 - $reset = DB::table('password_resets')->where('email', '=', 'admin@admin.com')->first(); 236 + $user = \BookStack\User::where('email', '=', 'admin@admin.com')->first();
234 - $this->visit('/password/reset/' . $reset->token) 237 +
238 + Notification::assertSentTo($user, \BookStack\Notifications\ResetPassword::class);
239 + $n = Notification::sent($user, \BookStack\Notifications\ResetPassword::class);
240 +
241 + $this->visit('/password/reset/' . $n->first()->token)
235 ->see('Reset Password') 242 ->see('Reset Password')
236 ->submitForm('Reset Password', [ 243 ->submitForm('Reset Password', [
237 'email' => 'admin@admin.com', 244 'email' => 'admin@admin.com',
......
1 -<?php 1 +<?php namespace Tests;
2 -
3 -use BookStack\Services\LdapService;
4 use BookStack\User; 2 use BookStack\User;
5 3
6 -class LdapTest extends \TestCase 4 +class LdapTest extends BrowserKitTest
7 { 5 {
8 6
9 protected $mockLdap; 7 protected $mockLdap;
...@@ -14,7 +12,7 @@ class LdapTest extends \TestCase ...@@ -14,7 +12,7 @@ class LdapTest extends \TestCase
14 { 12 {
15 parent::setUp(); 13 parent::setUp();
16 app('config')->set(['auth.method' => 'ldap', 'services.ldap.base_dn' => 'dc=ldap,dc=local', 'auth.providers.users.driver' => 'ldap']); 14 app('config')->set(['auth.method' => 'ldap', 'services.ldap.base_dn' => 'dc=ldap,dc=local', 'auth.providers.users.driver' => 'ldap']);
17 - $this->mockLdap = Mockery::mock(BookStack\Services\Ldap::class); 15 + $this->mockLdap = \Mockery::mock(\BookStack\Services\Ldap::class);
18 $this->app['BookStack\Services\Ldap'] = $this->mockLdap; 16 $this->app['BookStack\Services\Ldap'] = $this->mockLdap;
19 $this->mockUser = factory(User::class)->make(); 17 $this->mockUser = factory(User::class)->make();
20 } 18 }
...@@ -24,7 +22,7 @@ class LdapTest extends \TestCase ...@@ -24,7 +22,7 @@ class LdapTest extends \TestCase
24 $this->mockLdap->shouldReceive('connect')->once()->andReturn($this->resourceId); 22 $this->mockLdap->shouldReceive('connect')->once()->andReturn($this->resourceId);
25 $this->mockLdap->shouldReceive('setVersion')->once(); 23 $this->mockLdap->shouldReceive('setVersion')->once();
26 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(4) 24 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(4)
27 - ->with($this->resourceId, config('services.ldap.base_dn'), Mockery::type('string'), Mockery::type('array')) 25 + ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
28 ->andReturn(['count' => 1, 0 => [ 26 ->andReturn(['count' => 1, 0 => [
29 'uid' => [$this->mockUser->name], 27 'uid' => [$this->mockUser->name],
30 'cn' => [$this->mockUser->name], 28 'cn' => [$this->mockUser->name],
...@@ -52,7 +50,7 @@ class LdapTest extends \TestCase ...@@ -52,7 +50,7 @@ class LdapTest extends \TestCase
52 $this->mockLdap->shouldReceive('setVersion')->once(); 50 $this->mockLdap->shouldReceive('setVersion')->once();
53 $ldapDn = 'cn=test-user,dc=test' . config('services.ldap.base_dn'); 51 $ldapDn = 'cn=test-user,dc=test' . config('services.ldap.base_dn');
54 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(2) 52 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
55 - ->with($this->resourceId, config('services.ldap.base_dn'), Mockery::type('string'), Mockery::type('array')) 53 + ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
56 ->andReturn(['count' => 1, 0 => [ 54 ->andReturn(['count' => 1, 0 => [
57 'cn' => [$this->mockUser->name], 55 'cn' => [$this->mockUser->name],
58 'dn' => $ldapDn, 56 'dn' => $ldapDn,
...@@ -75,7 +73,7 @@ class LdapTest extends \TestCase ...@@ -75,7 +73,7 @@ class LdapTest extends \TestCase
75 $this->mockLdap->shouldReceive('connect')->once()->andReturn($this->resourceId); 73 $this->mockLdap->shouldReceive('connect')->once()->andReturn($this->resourceId);
76 $this->mockLdap->shouldReceive('setVersion')->once(); 74 $this->mockLdap->shouldReceive('setVersion')->once();
77 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(2) 75 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
78 - ->with($this->resourceId, config('services.ldap.base_dn'), Mockery::type('string'), Mockery::type('array')) 76 + ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
79 ->andReturn(['count' => 1, 0 => [ 77 ->andReturn(['count' => 1, 0 => [
80 'uid' => [$this->mockUser->name], 78 'uid' => [$this->mockUser->name],
81 'cn' => [$this->mockUser->name], 79 'cn' => [$this->mockUser->name],
......
1 -<?php 1 +<?php namespace Tests;
2 2
3 -class SocialAuthTest extends TestCase 3 +class SocialAuthTest extends BrowserKitTest
4 { 4 {
5 5
6 public function test_social_registration() 6 public function test_social_registration()
...@@ -11,10 +11,10 @@ class SocialAuthTest extends TestCase ...@@ -11,10 +11,10 @@ class SocialAuthTest extends TestCase
11 $this->setSettings(['registration-enabled' => 'true']); 11 $this->setSettings(['registration-enabled' => 'true']);
12 config(['GOOGLE_APP_ID' => 'abc123', 'GOOGLE_APP_SECRET' => '123abc', 'APP_URL' => 'http://localhost']); 12 config(['GOOGLE_APP_ID' => 'abc123', 'GOOGLE_APP_SECRET' => '123abc', 'APP_URL' => 'http://localhost']);
13 13
14 - $mockSocialite = Mockery::mock('Laravel\Socialite\Contracts\Factory'); 14 + $mockSocialite = \Mockery::mock('Laravel\Socialite\Contracts\Factory');
15 $this->app['Laravel\Socialite\Contracts\Factory'] = $mockSocialite; 15 $this->app['Laravel\Socialite\Contracts\Factory'] = $mockSocialite;
16 - $mockSocialDriver = Mockery::mock('Laravel\Socialite\Contracts\Provider'); 16 + $mockSocialDriver = \Mockery::mock('Laravel\Socialite\Contracts\Provider');
17 - $mockSocialUser = Mockery::mock('\Laravel\Socialite\Contracts\User'); 17 + $mockSocialUser = \Mockery::mock('\Laravel\Socialite\Contracts\User');
18 18
19 $mockSocialite->shouldReceive('driver')->twice()->with('google')->andReturn($mockSocialDriver); 19 $mockSocialite->shouldReceive('driver')->twice()->with('google')->andReturn($mockSocialDriver);
20 $mockSocialDriver->shouldReceive('redirect')->once()->andReturn(redirect('/')); 20 $mockSocialDriver->shouldReceive('redirect')->once()->andReturn(redirect('/'));
...@@ -34,18 +34,16 @@ class SocialAuthTest extends TestCase ...@@ -34,18 +34,16 @@ class SocialAuthTest extends TestCase
34 34
35 public function test_social_login() 35 public function test_social_login()
36 { 36 {
37 - $user = factory(\BookStack\User::class)->make();
38 -
39 config([ 37 config([
40 'GOOGLE_APP_ID' => 'abc123', 'GOOGLE_APP_SECRET' => '123abc', 38 'GOOGLE_APP_ID' => 'abc123', 'GOOGLE_APP_SECRET' => '123abc',
41 'GITHUB_APP_ID' => 'abc123', 'GITHUB_APP_SECRET' => '123abc', 39 'GITHUB_APP_ID' => 'abc123', 'GITHUB_APP_SECRET' => '123abc',
42 'APP_URL' => 'http://localhost' 40 'APP_URL' => 'http://localhost'
43 ]); 41 ]);
44 42
45 - $mockSocialite = Mockery::mock('Laravel\Socialite\Contracts\Factory'); 43 + $mockSocialite = \Mockery::mock('Laravel\Socialite\Contracts\Factory');
46 $this->app['Laravel\Socialite\Contracts\Factory'] = $mockSocialite; 44 $this->app['Laravel\Socialite\Contracts\Factory'] = $mockSocialite;
47 - $mockSocialDriver = Mockery::mock('Laravel\Socialite\Contracts\Provider'); 45 + $mockSocialDriver = \Mockery::mock('Laravel\Socialite\Contracts\Provider');
48 - $mockSocialUser = Mockery::mock('\Laravel\Socialite\Contracts\User'); 46 + $mockSocialUser = \Mockery::mock('\Laravel\Socialite\Contracts\User');
49 47
50 $mockSocialUser->shouldReceive('getId')->twice()->andReturn('logintest123'); 48 $mockSocialUser->shouldReceive('getId')->twice()->andReturn('logintest123');
51 49
...@@ -68,7 +66,7 @@ class SocialAuthTest extends TestCase ...@@ -68,7 +66,7 @@ class SocialAuthTest extends TestCase
68 ->seePageIs('/login'); 66 ->seePageIs('/login');
69 67
70 // Test social callback with matching social account 68 // Test social callback with matching social account
71 - DB::table('social_accounts')->insert([ 69 + \DB::table('social_accounts')->insert([
72 'user_id' => $this->getAdmin()->id, 70 'user_id' => $this->getAdmin()->id,
73 'driver' => 'github', 71 'driver' => 'github',
74 'driver_id' => 'logintest123' 72 'driver_id' => 'logintest123'
......
1 +<?php namespace Tests;
2 +
3 +use BookStack\Role;
4 +use Illuminate\Contracts\Console\Kernel;
5 +use Illuminate\Foundation\Testing\DatabaseTransactions;
6 +use Laravel\BrowserKitTesting\TestCase;
7 +use Symfony\Component\DomCrawler\Crawler;
8 +
9 +abstract class BrowserKitTest extends TestCase
10 +{
11 +
12 + use DatabaseTransactions;
13 +
14 + /**
15 + * The base URL to use while testing the application.
16 + *
17 + * @var string
18 + */
19 + protected $baseUrl = 'http://localhost';
20 +
21 + // Local user instances
22 + private $admin;
23 + private $editor;
24 +
25 + /**
26 + * Creates the application.
27 + *
28 + * @return \Illuminate\Foundation\Application
29 + */
30 + public function createApplication()
31 + {
32 + $app = require __DIR__.'/../bootstrap/app.php';
33 +
34 + $app->make(Kernel::class)->bootstrap();
35 +
36 + return $app;
37 + }
38 +
39 + /**
40 + * Set the current user context to be an admin.
41 + * @return $this
42 + */
43 + public function asAdmin()
44 + {
45 + return $this->actingAs($this->getAdmin());
46 + }
47 +
48 + /**
49 + * Get the current admin user.
50 + * @return mixed
51 + */
52 + public function getAdmin() {
53 + if($this->admin === null) {
54 + $adminRole = Role::getSystemRole('admin');
55 + $this->admin = $adminRole->users->first();
56 + }
57 + return $this->admin;
58 + }
59 +
60 + /**
61 + * Set the current editor context to be an editor.
62 + * @return $this
63 + */
64 + public function asEditor()
65 + {
66 + if ($this->editor === null) {
67 + $this->editor = $this->getEditor();
68 + }
69 + return $this->actingAs($this->editor);
70 + }
71 +
72 + /**
73 + * Get a user that's not a system user such as the guest user.
74 + */
75 + public function getNormalUser()
76 + {
77 + return \BookStack\User::where('system_name', '=', null)->get()->last();
78 + }
79 +
80 + /**
81 + * Quickly sets an array of settings.
82 + * @param $settingsArray
83 + */
84 + protected function setSettings($settingsArray)
85 + {
86 + $settings = app('BookStack\Services\SettingService');
87 + foreach ($settingsArray as $key => $value) {
88 + $settings->put($key, $value);
89 + }
90 + }
91 +
92 + /**
93 + * Create a group of entities that belong to a specific user.
94 + * @param $creatorUser
95 + * @param $updaterUser
96 + * @return array
97 + */
98 + protected function createEntityChainBelongingToUser($creatorUser, $updaterUser = false)
99 + {
100 + if ($updaterUser === false) $updaterUser = $creatorUser;
101 + $book = factory(\BookStack\Book::class)->create(['created_by' => $creatorUser->id, 'updated_by' => $updaterUser->id]);
102 + $chapter = factory(\BookStack\Chapter::class)->create(['created_by' => $creatorUser->id, 'updated_by' => $updaterUser->id]);
103 + $page = factory(\BookStack\Page::class)->create(['created_by' => $creatorUser->id, 'updated_by' => $updaterUser->id, 'book_id' => $book->id]);
104 + $book->chapters()->saveMany([$chapter]);
105 + $chapter->pages()->saveMany([$page]);
106 + $restrictionService = $this->app[\BookStack\Services\PermissionService::class];
107 + $restrictionService->buildJointPermissionsForEntity($book);
108 + return [
109 + 'book' => $book,
110 + 'chapter' => $chapter,
111 + 'page' => $page
112 + ];
113 + }
114 +
115 + /**
116 + * Quick way to create a new user
117 + * @param array $attributes
118 + * @return mixed
119 + */
120 + protected function getEditor($attributes = [])
121 + {
122 + $user = factory(\BookStack\User::class)->create($attributes);
123 + $role = Role::getRole('editor');
124 + $user->attachRole($role);;
125 + return $user;
126 + }
127 +
128 + /**
129 + * Quick way to create a new user without any permissions
130 + * @param array $attributes
131 + * @return mixed
132 + */
133 + protected function getNewBlankUser($attributes = [])
134 + {
135 + $user = factory(\BookStack\User::class)->create($attributes);
136 + return $user;
137 + }
138 +
139 + /**
140 + * Assert that a given string is seen inside an element.
141 + *
142 + * @param bool|string|null $element
143 + * @param integer $position
144 + * @param string $text
145 + * @param bool $negate
146 + * @return $this
147 + */
148 + protected function seeInNthElement($element, $position, $text, $negate = false)
149 + {
150 + $method = $negate ? 'assertNotRegExp' : 'assertRegExp';
151 +
152 + $rawPattern = preg_quote($text, '/');
153 +
154 + $escapedPattern = preg_quote(e($text), '/');
155 +
156 + $content = $this->crawler->filter($element)->eq($position)->html();
157 +
158 + $pattern = $rawPattern == $escapedPattern
159 + ? $rawPattern : "({$rawPattern}|{$escapedPattern})";
160 +
161 + $this->$method("/$pattern/i", $content);
162 +
163 + return $this;
164 + }
165 +
166 + /**
167 + * Assert that the current page matches a given URI.
168 + *
169 + * @param string $uri
170 + * @return $this
171 + */
172 + protected function seePageUrlIs($uri)
173 + {
174 + $this->assertEquals(
175 + $uri, $this->currentUri, "Did not land on expected page [{$uri}].\n"
176 + );
177 +
178 + return $this;
179 + }
180 +
181 + /**
182 + * Do a forced visit that does not error out on exception.
183 + * @param string $uri
184 + * @param array $parameters
185 + * @param array $cookies
186 + * @param array $files
187 + * @return $this
188 + */
189 + protected function forceVisit($uri, $parameters = [], $cookies = [], $files = [])
190 + {
191 + $method = 'GET';
192 + $uri = $this->prepareUrlForRequest($uri);
193 + $this->call($method, $uri, $parameters, $cookies, $files);
194 + $this->clearInputs()->followRedirects();
195 + $this->currentUri = $this->app->make('request')->fullUrl();
196 + $this->crawler = new Crawler($this->response->getContent(), $uri);
197 + return $this;
198 + }
199 +
200 + /**
201 + * Click the text within the selected element.
202 + * @param $parentElement
203 + * @param $linkText
204 + * @return $this
205 + */
206 + protected function clickInElement($parentElement, $linkText)
207 + {
208 + $elem = $this->crawler->filter($parentElement);
209 + $link = $elem->selectLink($linkText);
210 + $this->visit($link->link()->getUri());
211 + return $this;
212 + }
213 +
214 + /**
215 + * Check if the page contains the given element.
216 + * @param string $selector
217 + */
218 + protected function pageHasElement($selector)
219 + {
220 + $elements = $this->crawler->filter($selector);
221 + $this->assertTrue(count($elements) > 0, "The page does not contain an element matching " . $selector);
222 + return $this;
223 + }
224 +
225 + /**
226 + * Check if the page contains the given element.
227 + * @param string $selector
228 + */
229 + protected function pageNotHasElement($selector)
230 + {
231 + $elements = $this->crawler->filter($selector);
232 + $this->assertFalse(count($elements) > 0, "The page contains " . count($elements) . " elements matching " . $selector);
233 + return $this;
234 + }
235 +}
1 +<?php namespace Tests;
2 +
3 +use BookStack\JointPermission;
4 +use BookStack\Page;
5 +use BookStack\Repos\EntityRepo;
6 +
7 +class CommandsTest extends TestCase
8 +{
9 +
10 + public function test_clear_views_command()
11 + {
12 + $this->asEditor();
13 + $page = Page::first();
14 +
15 + $this->get($page->getUrl());
16 +
17 + $this->assertDatabaseHas('views', [
18 + 'user_id' => $this->getEditor()->id,
19 + 'viewable_id' => $page->id,
20 + 'views' => 1
21 + ]);
22 +
23 + $exitCode = \Artisan::call('bookstack:clear-views');
24 + $this->assertTrue($exitCode === 0, 'Command executed successfully');
25 +
26 + $this->assertDatabaseMissing('views', [
27 + 'user_id' => $this->getEditor()->id
28 + ]);
29 + }
30 +
31 + public function test_clear_activity_command()
32 + {
33 + $this->asEditor();
34 + $page = Page::first();
35 + \Activity::add($page, 'page_update', $page->book->id);
36 +
37 + $this->assertDatabaseHas('activities', [
38 + 'key' => 'page_update',
39 + 'entity_id' => $page->id,
40 + 'user_id' => $this->getEditor()->id
41 + ]);
42 +
43 + $exitCode = \Artisan::call('bookstack:clear-activity');
44 + $this->assertTrue($exitCode === 0, 'Command executed successfully');
45 +
46 +
47 + $this->assertDatabaseMissing('activities', [
48 + 'key' => 'page_update'
49 + ]);
50 + }
51 +
52 + public function test_clear_revisions_command()
53 + {
54 + $this->asEditor();
55 + $entityRepo = $this->app[EntityRepo::class];
56 + $page = Page::first();
57 + $entityRepo->updatePage($page, $page->book_id, ['name' => 'updated page', 'html' => '<p>new content</p>', 'summary' => 'page revision testing']);
58 + $entityRepo->updatePageDraft($page, ['name' => 'updated page', 'html' => '<p>new content in draft</p>', 'summary' => 'page revision testing']);
59 +
60 + $this->assertDatabaseHas('page_revisions', [
61 + 'page_id' => $page->id,
62 + 'type' => 'version'
63 + ]);
64 + $this->assertDatabaseHas('page_revisions', [
65 + 'page_id' => $page->id,
66 + 'type' => 'update_draft'
67 + ]);
68 +
69 + $exitCode = \Artisan::call('bookstack:clear-revisions');
70 + $this->assertTrue($exitCode === 0, 'Command executed successfully');
71 +
72 + $this->assertDatabaseMissing('page_revisions', [
73 + 'page_id' => $page->id,
74 + 'type' => 'version'
75 + ]);
76 + $this->assertDatabaseHas('page_revisions', [
77 + 'page_id' => $page->id,
78 + 'type' => 'update_draft'
79 + ]);
80 +
81 + $exitCode = \Artisan::call('bookstack:clear-revisions', ['--all' => true]);
82 + $this->assertTrue($exitCode === 0, 'Command executed successfully');
83 +
84 + $this->assertDatabaseMissing('page_revisions', [
85 + 'page_id' => $page->id,
86 + 'type' => 'update_draft'
87 + ]);
88 + }
89 +
90 + public function test_regen_permissions_command()
91 + {
92 + JointPermission::query()->truncate();
93 + $page = Page::first();
94 +
95 + $this->assertDatabaseMissing('joint_permissions', ['entity_id' => $page->id]);
96 +
97 + $exitCode = \Artisan::call('bookstack:regenerate-permissions');
98 + $this->assertTrue($exitCode === 0, 'Command executed successfully');
99 +
100 + $this->assertDatabaseHas('joint_permissions', ['entity_id' => $page->id]);
101 + }
102 +}
1 +<?php namespace Tests;
2 +
3 +use Illuminate\Contracts\Console\Kernel;
4 +
5 +trait CreatesApplication
6 +{
7 + /**
8 + * Creates the application.
9 + *
10 + * @return \Illuminate\Foundation\Application
11 + */
12 + public function createApplication()
13 + {
14 + $app = require __DIR__.'/../bootstrap/app.php';
15 + $app->make(Kernel::class)->bootstrap();
16 + return $app;
17 + }
18 +}
...\ No newline at end of file ...\ No newline at end of file
1 -<?php 1 +<?php namespace Tests;
2 2
3 -use Illuminate\Support\Facades\DB; 3 +class EntitySearchTest extends BrowserKitTest
4 -
5 -class EntitySearchTest extends TestCase
6 { 4 {
7 5
8 public function test_page_search() 6 public function test_page_search()
......
1 -<?php 1 +<?php namespace Tests;
2 2
3 -use Illuminate\Support\Facades\DB; 3 +class EntityTest extends BrowserKitTest
4 -
5 -class EntityTest extends TestCase
6 { 4 {
7 5
8 public function test_entity_creation() 6 public function test_entity_creation()
......
1 +<?php namespace Tests;
2 +
3 +
4 +use BookStack\Chapter;
5 +use BookStack\Page;
6 +
7 +class ExportTest extends TestCase
8 +{
9 +
10 + public function test_page_text_export()
11 + {
12 + $page = Page::first();
13 + $this->asEditor();
14 +
15 + $resp = $this->get($page->getUrl('/export/plaintext'));
16 + $resp->assertStatus(200);
17 + $resp->assertSee($page->name);
18 + $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.txt');
19 + }
20 +
21 + public function test_page_pdf_export()
22 + {
23 + $page = Page::first();
24 + $this->asEditor();
25 +
26 + $resp = $this->get($page->getUrl('/export/pdf'));
27 + $resp->assertStatus(200);
28 + $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.pdf');
29 + }
30 +
31 + public function test_page_html_export()
32 + {
33 + $page = Page::first();
34 + $this->asEditor();
35 +
36 + $resp = $this->get($page->getUrl('/export/html'));
37 + $resp->assertStatus(200);
38 + $resp->assertSee($page->name);
39 + $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.html');
40 + }
41 +
42 + public function test_book_text_export()
43 + {
44 + $page = Page::first();
45 + $book = $page->book;
46 + $this->asEditor();
47 +
48 + $resp = $this->get($book->getUrl('/export/plaintext'));
49 + $resp->assertStatus(200);
50 + $resp->assertSee($book->name);
51 + $resp->assertSee($page->name);
52 + $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $book->slug . '.txt');
53 + }
54 +
55 + public function test_book_pdf_export()
56 + {
57 + $page = Page::first();
58 + $book = $page->book;
59 + $this->asEditor();
60 +
61 + $resp = $this->get($book->getUrl('/export/pdf'));
62 + $resp->assertStatus(200);
63 + $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $book->slug . '.pdf');
64 + }
65 +
66 + public function test_book_html_export()
67 + {
68 + $page = Page::first();
69 + $book = $page->book;
70 + $this->asEditor();
71 +
72 + $resp = $this->get($book->getUrl('/export/html'));
73 + $resp->assertStatus(200);
74 + $resp->assertSee($book->name);
75 + $resp->assertSee($page->name);
76 + $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $book->slug . '.html');
77 + }
78 +
79 + public function test_chapter_text_export()
80 + {
81 + $chapter = Chapter::first();
82 + $page = $chapter->pages[0];
83 + $this->asEditor();
84 +
85 + $resp = $this->get($chapter->getUrl('/export/plaintext'));
86 + $resp->assertStatus(200);
87 + $resp->assertSee($chapter->name);
88 + $resp->assertSee($page->name);
89 + $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.txt');
90 + }
91 +
92 + public function test_chapter_pdf_export()
93 + {
94 + $chapter = Chapter::first();
95 + $this->asEditor();
96 +
97 + $resp = $this->get($chapter->getUrl('/export/pdf'));
98 + $resp->assertStatus(200);
99 + $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.pdf');
100 + }
101 +
102 + public function test_chapter_html_export()
103 + {
104 + $chapter = Chapter::first();
105 + $page = $chapter->pages[0];
106 + $this->asEditor();
107 +
108 + $resp = $this->get($chapter->getUrl('/export/html'));
109 + $resp->assertStatus(200);
110 + $resp->assertSee($chapter->name);
111 + $resp->assertSee($page->name);
112 + $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.html');
113 + }
114 +
115 +}
...\ No newline at end of file ...\ No newline at end of file
1 -<?php 1 +<?php namespace Tests;
2 2
3 - 3 +class MarkdownTest extends BrowserKitTest
4 -class MarkdownTest extends TestCase
5 { 4 {
6 protected $page; 5 protected $page;
7 6
......
1 -<?php 1 +<?php namespace Tests;
2 +
3 +use BookStack\Page;
4 +use BookStack\Repos\EntityRepo;
2 5
3 class PageContentTest extends TestCase 6 class PageContentTest extends TestCase
4 { 7 {
5 8
6 public function test_page_includes() 9 public function test_page_includes()
7 { 10 {
8 - $page = \BookStack\Page::first(); 11 + $page = Page::first();
9 - $secondPage = \BookStack\Page::all()->get(2); 12 + $secondPage = Page::all()->get(2);
10 13
11 $secondPage->html = "<p id='section1'>Hello, This is a test</p><p id='section2'>This is a second block of content</p>"; 14 $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(); 15 $secondPage->save();
13 16
14 - $this->asAdmin()->visit($page->getUrl()) 17 + $this->asEditor();
15 - ->dontSee('Hello, This is a test'); 18 +
19 + $pageContent = $this->get($page->getUrl());
20 + $pageContent->assertDontSee('Hello, This is a test');
16 21
17 $originalHtml = $page->html; 22 $originalHtml = $page->html;
18 $page->html .= "{{@{$secondPage->id}}}"; 23 $page->html .= "{{@{$secondPage->id}}}";
19 $page->save(); 24 $page->save();
20 25
21 - $this->asAdmin()->visit($page->getUrl()) 26 + $pageContent = $this->get($page->getUrl());
22 - ->see('Hello, This is a test') 27 + $pageContent->assertSee('Hello, This is a test');
23 - ->see('This is a second block of content'); 28 + $pageContent->assertSee('This is a second block of content');
24 29
25 $page->html = $originalHtml . " Well {{@{$secondPage->id}#section2}}"; 30 $page->html = $originalHtml . " Well {{@{$secondPage->id}#section2}}";
26 $page->save(); 31 $page->save();
27 32
28 - $this->asAdmin()->visit($page->getUrl()) 33 + $pageContent = $this->get($page->getUrl());
29 - ->dontSee('Hello, This is a test') 34 + $pageContent->assertDontSee('Hello, This is a test');
30 - ->see('Well This is a second block of content'); 35 + $pageContent->assertSee('Well This is a second block of content');
36 + }
37 +
38 + public function test_page_revision_views_viewable()
39 + {
40 + $this->asEditor();
41 +
42 + $entityRepo = $this->app[EntityRepo::class];
43 + $page = Page::first();
44 + $entityRepo->updatePage($page, $page->book_id, ['name' => 'updated page', 'html' => '<p>new content</p>', 'summary' => 'page revision testing']);
45 + $pageRevision = $page->revisions->last();
46 +
47 + $revisionView = $this->get($page->getUrl() . '/revisions/' . $pageRevision->id);
48 + $revisionView->assertStatus(200);
49 + $revisionView->assertSee('new content');
50 +
51 + $revisionView = $this->get($page->getUrl() . '/revisions/' . $pageRevision->id . '/changes');
52 + $revisionView->assertStatus(200);
53 + $revisionView->assertSee('new content');
31 } 54 }
32 55
33 } 56 }
......
1 -<?php 1 +<?php namespace Tests;
2 2
3 3
4 -class PageDraftTest extends TestCase 4 +class PageDraftTest extends BrowserKitTest
5 { 5 {
6 protected $page; 6 protected $page;
7 protected $entityRepo; 7 protected $entityRepo;
......
1 -<?php 1 +<?php namespace Tests;
2 +
3 +use BookStack\Book;
4 +use BookStack\Page;
5 +use BookStack\Repos\EntityRepo;
2 6
3 class SortTest extends TestCase 7 class SortTest extends TestCase
4 { 8 {
...@@ -13,13 +17,14 @@ class SortTest extends TestCase ...@@ -13,13 +17,14 @@ class SortTest extends TestCase
13 public function test_drafts_do_not_show_up() 17 public function test_drafts_do_not_show_up()
14 { 18 {
15 $this->asAdmin(); 19 $this->asAdmin();
16 - $entityRepo = app('\BookStack\Repos\EntityRepo'); 20 + $entityRepo = app(EntityRepo::class);
17 $draft = $entityRepo->getDraftPage($this->book); 21 $draft = $entityRepo->getDraftPage($this->book);
18 22
19 - $this->visit($this->book->getUrl()) 23 + $resp = $this->get($this->book->getUrl());
20 - ->see($draft->name) 24 + $resp->assertSee($draft->name);
21 - ->visit($this->book->getUrl() . '/sort') 25 +
22 - ->dontSee($draft->name); 26 + $resp = $this->get($this->book->getUrl() . '/sort');
27 + $resp->assertDontSee($draft->name);
23 } 28 }
24 29
25 public function test_page_move() 30 public function test_page_move()
...@@ -27,17 +32,21 @@ class SortTest extends TestCase ...@@ -27,17 +32,21 @@ class SortTest extends TestCase
27 $page = \BookStack\Page::first(); 32 $page = \BookStack\Page::first();
28 $currentBook = $page->book; 33 $currentBook = $page->book;
29 $newBook = \BookStack\Book::where('id', '!=', $currentBook->id)->first(); 34 $newBook = \BookStack\Book::where('id', '!=', $currentBook->id)->first();
30 - $this->asAdmin()->visit($page->getUrl() . '/move')
31 - ->see('Move Page')
32 - ->type('book:' . $newBook->id, 'entity_selection')->press('Move Page');
33 35
36 + $resp = $this->asAdmin()->get($page->getUrl() . '/move');
37 + $resp->assertSee('Move Page');
38 +
39 + $movePageResp = $this->put($page->getUrl() . '/move', [
40 + 'entity_selection' => 'book:' . $newBook->id
41 + ]);
34 $page = \BookStack\Page::find($page->id); 42 $page = \BookStack\Page::find($page->id);
35 - $this->seePageIs($page->getUrl()); 43 +
44 + $movePageResp->assertRedirect($page->getUrl());
36 $this->assertTrue($page->book->id == $newBook->id, 'Page book is now the new book'); 45 $this->assertTrue($page->book->id == $newBook->id, 'Page book is now the new book');
37 46
38 - $this->visit($newBook->getUrl()) 47 + $newBookResp = $this->get($newBook->getUrl());
39 - ->seeInNthElement('.activity-list-item', 0, 'moved page') 48 + $newBookResp->assertSee('moved page');
40 - ->seeInNthElement('.activity-list-item', 0, $page->name); 49 + $newBookResp->assertSee($page->name);
41 } 50 }
42 51
43 public function test_chapter_move() 52 public function test_chapter_move()
...@@ -47,22 +56,68 @@ class SortTest extends TestCase ...@@ -47,22 +56,68 @@ class SortTest extends TestCase
47 $pageToCheck = $chapter->pages->first(); 56 $pageToCheck = $chapter->pages->first();
48 $newBook = \BookStack\Book::where('id', '!=', $currentBook->id)->first(); 57 $newBook = \BookStack\Book::where('id', '!=', $currentBook->id)->first();
49 58
50 - $this->asAdmin()->visit($chapter->getUrl() . '/move') 59 + $chapterMoveResp = $this->asAdmin()->get($chapter->getUrl() . '/move');
51 - ->see('Move Chapter') 60 + $chapterMoveResp->assertSee('Move Chapter');
52 - ->type('book:' . $newBook->id, 'entity_selection')->press('Move Chapter'); 61 +
62 + $moveChapterResp = $this->put($chapter->getUrl() . '/move', [
63 + 'entity_selection' => 'book:' . $newBook->id
64 + ]);
53 65
54 $chapter = \BookStack\Chapter::find($chapter->id); 66 $chapter = \BookStack\Chapter::find($chapter->id);
55 - $this->seePageIs($chapter->getUrl()); 67 + $moveChapterResp->assertRedirect($chapter->getUrl());
56 $this->assertTrue($chapter->book->id === $newBook->id, 'Chapter Book is now the new book'); 68 $this->assertTrue($chapter->book->id === $newBook->id, 'Chapter Book is now the new book');
57 69
58 - $this->visit($newBook->getUrl()) 70 + $newBookResp = $this->get($newBook->getUrl());
59 - ->seeInNthElement('.activity-list-item', 0, 'moved chapter') 71 + $newBookResp->assertSee('moved chapter');
60 - ->seeInNthElement('.activity-list-item', 0, $chapter->name); 72 + $newBookResp->assertSee($chapter->name);
61 73
62 $pageToCheck = \BookStack\Page::find($pageToCheck->id); 74 $pageToCheck = \BookStack\Page::find($pageToCheck->id);
63 $this->assertTrue($pageToCheck->book_id === $newBook->id, 'Chapter child page\'s book id has changed to the new book'); 75 $this->assertTrue($pageToCheck->book_id === $newBook->id, 'Chapter child page\'s book id has changed to the new book');
64 - $this->visit($pageToCheck->getUrl()) 76 + $pageCheckResp = $this->get($pageToCheck->getUrl());
65 - ->see($newBook->name); 77 + $pageCheckResp->assertSee($newBook->name);
78 + }
79 +
80 + public function test_book_sort()
81 + {
82 + $oldBook = Book::query()->first();
83 + $chapterToMove = $this->newChapter(['name' => 'chapter to move'], $oldBook);
84 + $newBook = $this->newBook(['name' => 'New sort book']);
85 + $pagesToMove = Page::query()->take(5)->get();
86 +
87 + // Create request data
88 + $reqData = [
89 + [
90 + 'id' => $chapterToMove->id,
91 + 'sort' => 0,
92 + 'parentChapter' => false,
93 + 'type' => 'chapter',
94 + 'book' => $newBook->id
95 + ]
96 + ];
97 + foreach ($pagesToMove as $index => $page) {
98 + $reqData[] = [
99 + 'id' => $page->id,
100 + 'sort' => $index,
101 + 'parentChapter' => $index === count($pagesToMove) - 1 ? $chapterToMove->id : false,
102 + 'type' => 'page',
103 + 'book' => $newBook->id
104 + ];
105 + }
106 +
107 + $sortResp = $this->asAdmin()->put($newBook->getUrl() . '/sort', ['sort-tree' => json_encode($reqData)]);
108 + $sortResp->assertRedirect($newBook->getUrl());
109 + $sortResp->assertStatus(302);
110 + $this->assertDatabaseHas('chapters', [
111 + 'id' => $chapterToMove->id,
112 + 'book_id' => $newBook->id,
113 + 'priority' => 0
114 + ]);
115 + $this->assertTrue($newBook->chapters()->count() === 1);
116 + $this->assertTrue($newBook->chapters()->first()->pages()->count() === 1);
117 +
118 + $checkPage = $pagesToMove[1];
119 + $checkResp = $this->get(Page::find($checkPage->id)->getUrl());
120 + $checkResp->assertSee($newBook->name);
66 } 121 }
67 122
68 } 123 }
...\ No newline at end of file ...\ No newline at end of file
......
1 -<?php namespace Entity; 1 +<?php namespace Tests;
2 2
3 use BookStack\Tag; 3 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 TagTest extends \TestCase 7 +class TagTest extends BrowserKitTest
8 { 8 {
9 9
10 protected $defaultTagCount = 20; 10 protected $defaultTagCount = 20;
......
1 -<?php 1 +<?php namespace Tests;
2 2
3 -class ImageTest extends TestCase 3 +class ImageTest extends BrowserKitTest
4 { 4 {
5 5
6 /** 6 /**
......
1 -<?php 1 +<?php namespace Tests;
2 2
3 -class RestrictionsTest extends TestCase 3 +class RestrictionsTest extends BrowserKitTest
4 { 4 {
5 protected $user; 5 protected $user;
6 protected $viewer; 6 protected $viewer;
......
1 -<?php 1 +<?php namespace Tests;
2 2
3 -class RolesTest extends TestCase 3 +class RolesTest extends BrowserKitTest
4 { 4 {
5 protected $user; 5 protected $user;
6 6
......
1 -<?php 1 +<?php namespace Tests;
2 2
3 -class PublicActionTest extends TestCase 3 +class PublicActionTest extends BrowserKitTest
4 { 4 {
5 5
6 public function test_app_not_public() 6 public function test_app_not_public()
...@@ -84,7 +84,7 @@ class PublicActionTest extends TestCase ...@@ -84,7 +84,7 @@ class PublicActionTest extends TestCase
84 { 84 {
85 $page = \BookStack\Page::first(); 85 $page = \BookStack\Page::first();
86 $this->asAdmin()->visit($page->getUrl()); 86 $this->asAdmin()->visit($page->getUrl());
87 - Auth::logout(); 87 + \Auth::logout();
88 view()->share('pageTitle', ''); 88 view()->share('pageTitle', '');
89 $this->forceVisit('/cats/dogs/hippos'); 89 $this->forceVisit('/cats/dogs/hippos');
90 $this->dontSee($page->name); 90 $this->dontSee($page->name);
......
1 -<?php 1 +<?php namespace Tests;
2 2
3 +use BookStack\Book;
4 +use BookStack\Chapter;
5 +use BookStack\Repos\EntityRepo;
6 +use BookStack\Role;
3 use Illuminate\Foundation\Testing\DatabaseTransactions; 7 use Illuminate\Foundation\Testing\DatabaseTransactions;
4 -use Symfony\Component\DomCrawler\Crawler; 8 +use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
5 9
6 -class TestCase extends Illuminate\Foundation\Testing\TestCase 10 +abstract class TestCase extends BaseTestCase
7 { 11 {
8 - 12 + use CreatesApplication;
9 use DatabaseTransactions; 13 use DatabaseTransactions;
10 14
11 - /** 15 + protected $admin;
12 - * The base URL to use while testing the application. 16 + protected $editor;
13 - *
14 - * @var string
15 - */
16 - protected $baseUrl = 'http://localhost';
17 -
18 - // Local user instances
19 - private $admin;
20 - private $editor;
21 -
22 - /**
23 - * Creates the application.
24 - *
25 - * @return \Illuminate\Foundation\Application
26 - */
27 - public function createApplication()
28 - {
29 - $app = require __DIR__.'/../bootstrap/app.php';
30 -
31 - $app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
32 -
33 - return $app;
34 - }
35 17
36 /** 18 /**
37 * Set the current user context to be an admin. 19 * Set the current user context to be an admin.
...@@ -48,187 +30,50 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase ...@@ -48,187 +30,50 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase
48 */ 30 */
49 public function getAdmin() { 31 public function getAdmin() {
50 if($this->admin === null) { 32 if($this->admin === null) {
51 - $adminRole = \BookStack\Role::getRole('admin'); 33 + $adminRole = Role::getSystemRole('admin');
52 $this->admin = $adminRole->users->first(); 34 $this->admin = $adminRole->users->first();
53 } 35 }
54 return $this->admin; 36 return $this->admin;
55 } 37 }
56 38
57 /** 39 /**
58 - * Set the current editor context to be an editor. 40 + * Set the current user context to be an editor.
59 * @return $this 41 * @return $this
60 */ 42 */
61 public function asEditor() 43 public function asEditor()
62 { 44 {
63 - if ($this->editor === null) { 45 + return $this->actingAs($this->getEditor());
64 - $this->editor = $this->getEditor();
65 - }
66 - return $this->actingAs($this->editor);
67 - }
68 -
69 - /**
70 - * Get a user that's not a system user such as the guest user.
71 - */
72 - public function getNormalUser()
73 - {
74 - return \BookStack\User::where('system_name', '=', null)->get()->last();
75 } 46 }
76 47
77 - /**
78 - * Quickly sets an array of settings.
79 - * @param $settingsArray
80 - */
81 - protected function setSettings($settingsArray)
82 - {
83 - $settings = app('BookStack\Services\SettingService');
84 - foreach ($settingsArray as $key => $value) {
85 - $settings->put($key, $value);
86 - }
87 - }
88 -
89 - /**
90 - * Create a group of entities that belong to a specific user.
91 - * @param $creatorUser
92 - * @param $updaterUser
93 - * @return array
94 - */
95 - protected function createEntityChainBelongingToUser($creatorUser, $updaterUser = false)
96 - {
97 - if ($updaterUser === false) $updaterUser = $creatorUser;
98 - $book = factory(BookStack\Book::class)->create(['created_by' => $creatorUser->id, 'updated_by' => $updaterUser->id]);
99 - $chapter = factory(BookStack\Chapter::class)->create(['created_by' => $creatorUser->id, 'updated_by' => $updaterUser->id]);
100 - $page = factory(BookStack\Page::class)->create(['created_by' => $creatorUser->id, 'updated_by' => $updaterUser->id, 'book_id' => $book->id]);
101 - $book->chapters()->saveMany([$chapter]);
102 - $chapter->pages()->saveMany([$page]);
103 - $restrictionService = $this->app[\BookStack\Services\PermissionService::class];
104 - $restrictionService->buildJointPermissionsForEntity($book);
105 - return [
106 - 'book' => $book,
107 - 'chapter' => $chapter,
108 - 'page' => $page
109 - ];
110 - }
111 -
112 - /**
113 - * Quick way to create a new user
114 - * @param array $attributes
115 - * @return mixed
116 - */
117 - protected function getEditor($attributes = [])
118 - {
119 - $user = factory(\BookStack\User::class)->create($attributes);
120 - $role = \BookStack\Role::getRole('editor');
121 - $user->attachRole($role);;
122 - return $user;
123 - }
124 48
125 /** 49 /**
126 - * Quick way to create a new user without any permissions 50 + * Get a editor user.
127 - * @param array $attributes
128 * @return mixed 51 * @return mixed
129 */ 52 */
130 - protected function getNewBlankUser($attributes = []) 53 + public function getEditor() {
131 - { 54 + if($this->editor === null) {
132 - $user = factory(\BookStack\User::class)->create($attributes); 55 + $editorRole = Role::getRole('editor');
133 - return $user; 56 + $this->editor = $editorRole->users->first();
134 } 57 }
135 - 58 + return $this->editor;
136 - /**
137 - * Assert that a given string is seen inside an element.
138 - *
139 - * @param bool|string|null $element
140 - * @param integer $position
141 - * @param string $text
142 - * @param bool $negate
143 - * @return $this
144 - */
145 - protected function seeInNthElement($element, $position, $text, $negate = false)
146 - {
147 - $method = $negate ? 'assertNotRegExp' : 'assertRegExp';
148 -
149 - $rawPattern = preg_quote($text, '/');
150 -
151 - $escapedPattern = preg_quote(e($text), '/');
152 -
153 - $content = $this->crawler->filter($element)->eq($position)->html();
154 -
155 - $pattern = $rawPattern == $escapedPattern
156 - ? $rawPattern : "({$rawPattern}|{$escapedPattern})";
157 -
158 - $this->$method("/$pattern/i", $content);
159 -
160 - return $this;
161 } 59 }
162 60
163 /** 61 /**
164 - * Assert that the current page matches a given URI. 62 + * Create and return a new book.
165 - * 63 + * @param array $input
166 - * @param string $uri 64 + * @return Book
167 - * @return $this
168 */ 65 */
169 - protected function seePageUrlIs($uri) 66 + public function newBook($input = ['name' => 'test book', 'description' => 'My new test book']) {
170 - { 67 + return $this->app[EntityRepo::class]->createFromInput('book', $input, false);
171 - $this->assertEquals(
172 - $uri, $this->currentUri, "Did not land on expected page [{$uri}].\n"
173 - );
174 -
175 - return $this;
176 } 68 }
177 69
178 /** 70 /**
179 - * Do a forced visit that does not error out on exception. 71 + * Create and return a new test chapter
180 - * @param string $uri 72 + * @param array $input
181 - * @param array $parameters 73 + * @param Book $book
182 - * @param array $cookies 74 + * @return Chapter
183 - * @param array $files
184 - * @return $this
185 */ 75 */
186 - protected function forceVisit($uri, $parameters = [], $cookies = [], $files = []) 76 + public function newChapter($input = ['name' => 'test chapter', 'description' => 'My new test chapter'], Book $book) {
187 - { 77 + return $this->app[EntityRepo::class]->createFromInput('chapter', $input, $book);
188 - $method = 'GET';
189 - $uri = $this->prepareUrlForRequest($uri);
190 - $this->call($method, $uri, $parameters, $cookies, $files);
191 - $this->clearInputs()->followRedirects();
192 - $this->currentUri = $this->app->make('request')->fullUrl();
193 - $this->crawler = new Crawler($this->response->getContent(), $uri);
194 - return $this;
195 - }
196 -
197 - /**
198 - * Click the text within the selected element.
199 - * @param $parentElement
200 - * @param $linkText
201 - * @return $this
202 - */
203 - protected function clickInElement($parentElement, $linkText)
204 - {
205 - $elem = $this->crawler->filter($parentElement);
206 - $link = $elem->selectLink($linkText);
207 - $this->visit($link->link()->getUri());
208 - return $this;
209 - }
210 -
211 - /**
212 - * Check if the page contains the given element.
213 - * @param string $selector
214 - * @return bool
215 - */
216 - protected function pageHasElement($selector)
217 - {
218 - $elements = $this->crawler->filter($selector);
219 - $this->assertTrue(count($elements) > 0, "The page does not contain an element matching " . $selector);
220 - return $this;
221 - }
222 -
223 - /**
224 - * Check if the page contains the given element.
225 - * @param string $selector
226 - * @return bool
227 - */
228 - protected function pageNotHasElement($selector)
229 - {
230 - $elements = $this->crawler->filter($selector);
231 - $this->assertFalse(count($elements) > 0, "The page contains " . count($elements) . " elements matching " . $selector);
232 - return $this;
233 } 78 }
234 } 79 }
...\ No newline at end of file ...\ No newline at end of file
......
1 -<?php 1 +<?php namespace Tests;
2 2
3 -class UserProfileTest extends TestCase 3 +class UserProfileTest extends BrowserKitTest
4 { 4 {
5 protected $user; 5 protected $user;
6 6
...@@ -55,8 +55,8 @@ class UserProfileTest extends TestCase ...@@ -55,8 +55,8 @@ class UserProfileTest extends TestCase
55 $newUser = $this->getEditor(); 55 $newUser = $this->getEditor();
56 $this->actingAs($newUser); 56 $this->actingAs($newUser);
57 $entities = $this->createEntityChainBelongingToUser($newUser, $newUser); 57 $entities = $this->createEntityChainBelongingToUser($newUser, $newUser);
58 - Activity::add($entities['book'], 'book_update', $entities['book']->id); 58 + \Activity::add($entities['book'], 'book_update', $entities['book']->id);
59 - Activity::add($entities['page'], 'page_create', $entities['book']->id); 59 + \Activity::add($entities['page'], 'page_create', $entities['book']->id);
60 60
61 $this->asAdmin()->visit('/user/' . $newUser->id) 61 $this->asAdmin()->visit('/user/' . $newUser->id)
62 ->seeInElement('#recent-activity', 'updated book') 62 ->seeInElement('#recent-activity', 'updated book')
...@@ -69,8 +69,8 @@ class UserProfileTest extends TestCase ...@@ -69,8 +69,8 @@ class UserProfileTest extends TestCase
69 $newUser = $this->getEditor(); 69 $newUser = $this->getEditor();
70 $this->actingAs($newUser); 70 $this->actingAs($newUser);
71 $entities = $this->createEntityChainBelongingToUser($newUser, $newUser); 71 $entities = $this->createEntityChainBelongingToUser($newUser, $newUser);
72 - Activity::add($entities['book'], 'book_update', $entities['book']->id); 72 + \Activity::add($entities['book'], 'book_update', $entities['book']->id);
73 - Activity::add($entities['page'], 'page_create', $entities['book']->id); 73 + \Activity::add($entities['page'], 'page_create', $entities['book']->id);
74 74
75 $this->asAdmin()->visit('/')->clickInElement('#recent-activity', $newUser->name) 75 $this->asAdmin()->visit('/')->clickInElement('#recent-activity', $newUser->name)
76 ->seePageIs('/user/' . $newUser->id) 76 ->seePageIs('/user/' . $newUser->id)
......
1 -v0.14.3
...\ No newline at end of file ...\ No newline at end of file
1 +v0.15.0
...\ No newline at end of file ...\ No newline at end of file
......