Merge branch 'master' into release
This commit is contained in:
commit
a5d5904969
15
.travis.yml
15
.travis.yml
|
@ -1,5 +1,5 @@
|
|||
dist: trusty
|
||||
sudo: required
|
||||
sudo: false
|
||||
language: php
|
||||
php:
|
||||
- 7.0
|
||||
|
@ -8,15 +8,11 @@ cache:
|
|||
directories:
|
||||
- $HOME/.composer/cache
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- mysql-server-5.6
|
||||
- mysql-client-core-5.6
|
||||
- mysql-client-5.6
|
||||
|
||||
before_script:
|
||||
- mysql -u root -e 'create database `bookstack-test`;'
|
||||
- mysql -u root -e "CREATE USER 'bookstack-test'@'localhost' IDENTIFIED BY 'bookstack-test';"
|
||||
- mysql -u root -e "GRANT ALL ON \`bookstack-test\`.* TO 'bookstack-test'@'localhost';"
|
||||
- mysql -u root -e "FLUSH PRIVILEGES;"
|
||||
- phpenv config-rm xdebug.ini
|
||||
- composer dump-autoload --no-interaction
|
||||
- composer install --prefer-dist --no-interaction
|
||||
|
@ -25,5 +21,8 @@ before_script:
|
|||
- php artisan migrate --force -n --database=mysql_testing
|
||||
- php artisan db:seed --force -n --class=DummyContentSeeder --database=mysql_testing
|
||||
|
||||
after_failure:
|
||||
- cat storage/logs/laravel.log
|
||||
|
||||
script:
|
||||
- phpunit
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
namespace BookStack\Console\Commands;
|
||||
|
||||
use BookStack\Activity;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ClearActivity extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'bookstack:clear-activity';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Clear user activity from the system';
|
||||
|
||||
protected $activity;
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @param Activity $activity
|
||||
*/
|
||||
public function __construct(Activity $activity)
|
||||
{
|
||||
$this->activity = $activity;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->activity->newQuery()->truncate();
|
||||
$this->comment('System activity cleared');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
namespace BookStack\Console\Commands;
|
||||
|
||||
use BookStack\PageRevision;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ClearRevisions extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'bookstack:clear-revisions
|
||||
{--a|all : Include active update drafts in deletion}
|
||||
';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Clear page revisions';
|
||||
|
||||
protected $pageRevision;
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @param PageRevision $pageRevision
|
||||
*/
|
||||
public function __construct(PageRevision $pageRevision)
|
||||
{
|
||||
$this->pageRevision = $pageRevision;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$deleteTypes = $this->option('all') ? ['version', 'update_draft'] : ['version'];
|
||||
$this->pageRevision->newQuery()->whereIn('type', $deleteTypes)->delete();
|
||||
$this->comment('Revisions deleted');
|
||||
}
|
||||
}
|
|
@ -4,21 +4,21 @@ namespace BookStack\Console\Commands;
|
|||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ResetViews extends Command
|
||||
class ClearViews extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'views:reset';
|
||||
protected $signature = 'bookstack:clear-views';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Reset all view-counts for all entities.';
|
||||
protected $description = 'Clear all view-counts for all entities.';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
|
@ -37,5 +37,6 @@ class ResetViews extends Command
|
|||
public function handle()
|
||||
{
|
||||
\Views::resetAll();
|
||||
$this->comment('Views cleared');
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace BookStack\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Foundation\Inspiring;
|
||||
|
||||
class Inspire extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'inspire';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Display an inspiring quote';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->comment(PHP_EOL.Inspiring::quote().PHP_EOL);
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ class RegeneratePermissions extends Command
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'permissions:regen';
|
||||
protected $signature = 'bookstack:regenerate-permissions';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
|
@ -47,5 +47,6 @@ class RegeneratePermissions extends Command
|
|||
public function handle()
|
||||
{
|
||||
$this->permissionService->buildJointPermissions();
|
||||
$this->comment('Permissions regenerated');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,8 +13,9 @@ class Kernel extends ConsoleKernel
|
|||
* @var array
|
||||
*/
|
||||
protected $commands = [
|
||||
\BookStack\Console\Commands\Inspire::class,
|
||||
\BookStack\Console\Commands\ResetViews::class,
|
||||
\BookStack\Console\Commands\ClearViews::class,
|
||||
\BookStack\Console\Commands\ClearActivity::class,
|
||||
\BookStack\Console\Commands\ClearRevisions::class,
|
||||
\BookStack\Console\Commands\RegeneratePermissions::class,
|
||||
];
|
||||
|
||||
|
@ -26,7 +27,6 @@ class Kernel extends ConsoleKernel
|
|||
*/
|
||||
protected function schedule(Schedule $schedule)
|
||||
{
|
||||
$schedule->command('inspire')
|
||||
->hourly();
|
||||
//
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
namespace BookStack\Exceptions;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Validation\ValidationException;
|
||||
use Illuminate\Auth\AuthenticationException;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use PhpSpec\Exception\Example\ErrorException;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
use Activity;
|
||||
use BookStack\Repos\EntityRepo;
|
||||
use BookStack\Repos\UserRepo;
|
||||
use BookStack\Services\ExportService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Views;
|
||||
|
@ -12,16 +13,19 @@ class BookController extends Controller
|
|||
|
||||
protected $entityRepo;
|
||||
protected $userRepo;
|
||||
protected $exportService;
|
||||
|
||||
/**
|
||||
* BookController constructor.
|
||||
* @param EntityRepo $entityRepo
|
||||
* @param UserRepo $userRepo
|
||||
* @param ExportService $exportService
|
||||
*/
|
||||
public function __construct(EntityRepo $entityRepo, UserRepo $userRepo)
|
||||
public function __construct(EntityRepo $entityRepo, UserRepo $userRepo, ExportService $exportService)
|
||||
{
|
||||
$this->entityRepo = $entityRepo;
|
||||
$this->userRepo = $userRepo;
|
||||
$this->exportService = $exportService;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
|
@ -258,4 +262,49 @@ class BookController extends Controller
|
|||
session()->flash('success', trans('entities.books_permissions_updated'));
|
||||
return redirect($book->getUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* Export a book as a PDF file.
|
||||
* @param string $bookSlug
|
||||
* @return mixed
|
||||
*/
|
||||
public function exportPdf($bookSlug)
|
||||
{
|
||||
$book = $this->entityRepo->getBySlug('book', $bookSlug);
|
||||
$pdfContent = $this->exportService->bookToPdf($book);
|
||||
return response()->make($pdfContent, 200, [
|
||||
'Content-Type' => 'application/octet-stream',
|
||||
'Content-Disposition' => 'attachment; filename="' . $bookSlug . '.pdf'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export a book as a contained HTML file.
|
||||
* @param string $bookSlug
|
||||
* @return mixed
|
||||
*/
|
||||
public function exportHtml($bookSlug)
|
||||
{
|
||||
$book = $this->entityRepo->getBySlug('book', $bookSlug);
|
||||
$htmlContent = $this->exportService->bookToContainedHtml($book);
|
||||
return response()->make($htmlContent, 200, [
|
||||
'Content-Type' => 'application/octet-stream',
|
||||
'Content-Disposition' => 'attachment; filename="' . $bookSlug . '.html'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export a book as a plain text file.
|
||||
* @param $bookSlug
|
||||
* @return mixed
|
||||
*/
|
||||
public function exportPlainText($bookSlug)
|
||||
{
|
||||
$book = $this->entityRepo->getBySlug('book', $bookSlug);
|
||||
$htmlContent = $this->exportService->bookToPlainText($book);
|
||||
return response()->make($htmlContent, 200, [
|
||||
'Content-Type' => 'application/octet-stream',
|
||||
'Content-Disposition' => 'attachment; filename="' . $bookSlug . '.txt'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
use Activity;
|
||||
use BookStack\Repos\EntityRepo;
|
||||
use BookStack\Repos\UserRepo;
|
||||
use BookStack\Services\ExportService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Views;
|
||||
|
@ -12,16 +13,19 @@ class ChapterController extends Controller
|
|||
|
||||
protected $userRepo;
|
||||
protected $entityRepo;
|
||||
protected $exportService;
|
||||
|
||||
/**
|
||||
* ChapterController constructor.
|
||||
* @param EntityRepo $entityRepo
|
||||
* @param UserRepo $userRepo
|
||||
* @param ExportService $exportService
|
||||
*/
|
||||
public function __construct(EntityRepo $entityRepo, UserRepo $userRepo)
|
||||
public function __construct(EntityRepo $entityRepo, UserRepo $userRepo, ExportService $exportService)
|
||||
{
|
||||
$this->entityRepo = $entityRepo;
|
||||
$this->userRepo = $userRepo;
|
||||
$this->exportService = $exportService;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
|
@ -236,4 +240,52 @@ class ChapterController extends Controller
|
|||
session()->flash('success', trans('entities.chapters_permissions_success'));
|
||||
return redirect($chapter->getUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports a chapter to pdf .
|
||||
* @param string $bookSlug
|
||||
* @param string $chapterSlug
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function exportPdf($bookSlug, $chapterSlug)
|
||||
{
|
||||
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
|
||||
$pdfContent = $this->exportService->chapterToPdf($chapter);
|
||||
return response()->make($pdfContent, 200, [
|
||||
'Content-Type' => 'application/octet-stream',
|
||||
'Content-Disposition' => 'attachment; filename="' . $chapterSlug . '.pdf'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export a chapter to a self-contained HTML file.
|
||||
* @param string $bookSlug
|
||||
* @param string $chapterSlug
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function exportHtml($bookSlug, $chapterSlug)
|
||||
{
|
||||
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
|
||||
$containedHtml = $this->exportService->chapterToContainedHtml($chapter);
|
||||
return response()->make($containedHtml, 200, [
|
||||
'Content-Type' => 'application/octet-stream',
|
||||
'Content-Disposition' => 'attachment; filename="' . $chapterSlug . '.html'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export a chapter to a simple plaintext .txt file.
|
||||
* @param string $bookSlug
|
||||
* @param string $chapterSlug
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function exportPlainText($bookSlug, $chapterSlug)
|
||||
{
|
||||
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
|
||||
$containedHtml = $this->exportService->chapterToPlainText($chapter);
|
||||
return response()->make($containedHtml, 200, [
|
||||
'Content-Type' => 'application/octet-stream',
|
||||
'Content-Disposition' => 'attachment; filename="' . $chapterSlug . '.txt'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ namespace BookStack\Http\Controllers;
|
|||
|
||||
use BookStack\Ownable;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Illuminate\Http\Exception\HttpResponseException;
|
||||
use Illuminate\Http\Exceptions\HttpResponseException;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
|
|
|
@ -369,10 +369,13 @@ class PageController extends Controller
|
|||
public function showRevision($bookSlug, $pageSlug, $revisionId)
|
||||
{
|
||||
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
|
||||
$revision = $this->entityRepo->getById('page_revision', $revisionId, false);
|
||||
$revision = $page->revisions()->where('id', '=', $revisionId)->first();
|
||||
if ($revision === null) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
$page->fill($revision->toArray());
|
||||
$this->setPageTitle(trans('entities.pages_revision_named', ['pageName'=>$page->getShortName()]));
|
||||
$this->setPageTitle(trans('entities.pages_revision_named', ['pageName' => $page->getShortName()]));
|
||||
|
||||
return view('pages/revision', [
|
||||
'page' => $page,
|
||||
|
@ -390,7 +393,10 @@ class PageController extends Controller
|
|||
public function showRevisionChanges($bookSlug, $pageSlug, $revisionId)
|
||||
{
|
||||
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
|
||||
$revision = $this->entityRepo->getById('page_revision', $revisionId);
|
||||
$revision = $page->revisions()->where('id', '=', $revisionId)->first();
|
||||
if ($revision === null) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
$prev = $revision->getPrevious();
|
||||
$prevContent = ($prev === null) ? '' : $prev->html;
|
||||
|
@ -423,7 +429,7 @@ class PageController extends Controller
|
|||
}
|
||||
|
||||
/**
|
||||
* Exports a page to pdf format using barryvdh/laravel-dompdf wrapper.
|
||||
* Exports a page to a PDF.
|
||||
* https://github.com/barryvdh/laravel-dompdf
|
||||
* @param string $bookSlug
|
||||
* @param string $pageSlug
|
||||
|
@ -433,7 +439,6 @@ class PageController extends Controller
|
|||
{
|
||||
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
|
||||
$pdfContent = $this->exportService->pageToPdf($page);
|
||||
// return $pdfContent;
|
||||
return response()->make($pdfContent, 200, [
|
||||
'Content-Type' => 'application/octet-stream',
|
||||
'Content-Disposition' => 'attachment; filename="' . $pageSlug . '.pdf'
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
<?php namespace BookStack\Providers;
|
||||
|
||||
use BookStack\Services\SettingService;
|
||||
use BookStack\Setting;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Validator;
|
||||
|
||||
|
@ -17,6 +19,10 @@ class AppServiceProvider extends ServiceProvider
|
|||
$imageMimes = ['image/png', 'image/bmp', 'image/gif', 'image/jpeg', 'image/jpg', 'image/tiff', 'image/webp'];
|
||||
return in_array($value->getMimeType(), $imageMimes);
|
||||
});
|
||||
|
||||
\Blade::directive('icon', function($expression) {
|
||||
return "<?php echo icon($expression); ?>";
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -26,6 +32,8 @@ class AppServiceProvider extends ServiceProvider
|
|||
*/
|
||||
public function register()
|
||||
{
|
||||
//
|
||||
$this->app->singleton(SettingService::class, function($app) {
|
||||
return new SettingService($app->make(Setting::class), $app->make('Illuminate\Contracts\Cache\Repository'));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace BookStack\Providers;
|
|||
|
||||
use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
|
||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||
use SocialiteProviders\Manager\SocialiteWasCalled;
|
||||
|
||||
class EventServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
@ -13,8 +14,8 @@ class EventServiceProvider extends ServiceProvider
|
|||
* @var array
|
||||
*/
|
||||
protected $listen = [
|
||||
'BookStack\Events\SomeEvent' => [
|
||||
'BookStack\Listeners\EventListener',
|
||||
SocialiteWasCalled::class => [
|
||||
'SocialiteProviders\Slack\SlackExtendSocialite@handle',
|
||||
],
|
||||
];
|
||||
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
<?php namespace BookStack\Providers;
|
||||
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class SocialiteServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Indicates if loading of the provider is deferred.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $defer = true;
|
||||
|
||||
/**
|
||||
* Register the service provider.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$this->app->bindShared('Laravel\Socialite\Contracts\Factory', function ($app) {
|
||||
return new SocialiteManager($app);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the services provided by the provider.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function provides()
|
||||
{
|
||||
return ['Laravel\Socialite\Contracts\Factory'];
|
||||
}
|
||||
}
|
|
@ -86,8 +86,7 @@ class EntityRepo
|
|||
$this->entities = [
|
||||
'page' => $this->page,
|
||||
'chapter' => $this->chapter,
|
||||
'book' => $this->book,
|
||||
'page_revision' => $this->pageRevision
|
||||
'book' => $this->book
|
||||
];
|
||||
$this->viewService = $viewService;
|
||||
$this->permissionService = $permissionService;
|
||||
|
@ -314,11 +313,12 @@ class EntityRepo
|
|||
* Loads the book slug onto child elements to prevent access database access for getting the slug.
|
||||
* @param Book $book
|
||||
* @param bool $filterDrafts
|
||||
* @param bool $renderPages
|
||||
* @return mixed
|
||||
*/
|
||||
public function getBookChildren(Book $book, $filterDrafts = false)
|
||||
public function getBookChildren(Book $book, $filterDrafts = false, $renderPages = false)
|
||||
{
|
||||
$q = $this->permissionService->bookChildrenQuery($book->id, $filterDrafts)->get();
|
||||
$q = $this->permissionService->bookChildrenQuery($book->id, $filterDrafts, $renderPages)->get();
|
||||
$entities = [];
|
||||
$parents = [];
|
||||
$tree = [];
|
||||
|
@ -326,6 +326,10 @@ class EntityRepo
|
|||
foreach ($q as $index => $rawEntity) {
|
||||
if ($rawEntity->entity_type === 'BookStack\\Page') {
|
||||
$entities[$index] = $this->page->newFromBuilder($rawEntity);
|
||||
if ($renderPages) {
|
||||
$entities[$index]->html = $rawEntity->description;
|
||||
$entities[$index]->html = $this->renderPage($entities[$index]);
|
||||
};
|
||||
} else if ($rawEntity->entity_type === 'BookStack\\Chapter') {
|
||||
$entities[$index] = $this->chapter->newFromBuilder($rawEntity);
|
||||
$key = $entities[$index]->entity_type . ':' . $entities[$index]->id;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
<?php namespace BookStack\Services;
|
||||
|
||||
use BookStack\Book;
|
||||
use BookStack\Chapter;
|
||||
use BookStack\Page;
|
||||
use BookStack\Repos\EntityRepo;
|
||||
|
||||
|
@ -25,25 +27,105 @@ class ExportService
|
|||
*/
|
||||
public function pageToContainedHtml(Page $page)
|
||||
{
|
||||
$cssContent = file_get_contents(public_path('/css/export-styles.css'));
|
||||
$pageHtml = view('pages/export', ['page' => $page, 'pageContent' => $this->entityRepo->renderPage($page), 'css' => $cssContent])->render();
|
||||
$pageHtml = view('pages/export', [
|
||||
'page' => $page,
|
||||
'pageContent' => $this->entityRepo->renderPage($page)
|
||||
])->render();
|
||||
return $this->containHtml($pageHtml);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a page to a pdf file.
|
||||
* Convert a chapter to a self-contained HTML file.
|
||||
* @param Chapter $chapter
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function chapterToContainedHtml(Chapter $chapter)
|
||||
{
|
||||
$pages = $this->entityRepo->getChapterChildren($chapter);
|
||||
$pages->each(function($page) {
|
||||
$page->html = $this->entityRepo->renderPage($page);
|
||||
});
|
||||
$html = view('chapters/export', [
|
||||
'chapter' => $chapter,
|
||||
'pages' => $pages
|
||||
])->render();
|
||||
return $this->containHtml($html);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a book to a self-contained HTML file.
|
||||
* @param Book $book
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function bookToContainedHtml(Book $book)
|
||||
{
|
||||
$bookTree = $this->entityRepo->getBookChildren($book, true, true);
|
||||
$html = view('books/export', [
|
||||
'book' => $book,
|
||||
'bookChildren' => $bookTree
|
||||
])->render();
|
||||
return $this->containHtml($html);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a page to a PDF file.
|
||||
* @param Page $page
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function pageToPdf(Page $page)
|
||||
{
|
||||
$cssContent = file_get_contents(public_path('/css/export-styles.css'));
|
||||
$pageHtml = view('pages/pdf', ['page' => $page, 'pageContent' => $this->entityRepo->renderPage($page), 'css' => $cssContent])->render();
|
||||
// return $pageHtml;
|
||||
$html = view('pages/pdf', [
|
||||
'page' => $page,
|
||||
'pageContent' => $this->entityRepo->renderPage($page)
|
||||
])->render();
|
||||
return $this->htmlToPdf($html);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a chapter to a PDF file.
|
||||
* @param Chapter $chapter
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function chapterToPdf(Chapter $chapter)
|
||||
{
|
||||
$pages = $this->entityRepo->getChapterChildren($chapter);
|
||||
$pages->each(function($page) {
|
||||
$page->html = $this->entityRepo->renderPage($page);
|
||||
});
|
||||
$html = view('chapters/export', [
|
||||
'chapter' => $chapter,
|
||||
'pages' => $pages
|
||||
])->render();
|
||||
return $this->htmlToPdf($html);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a book to a PDF file
|
||||
* @param Book $book
|
||||
* @return string
|
||||
*/
|
||||
public function bookToPdf(Book $book)
|
||||
{
|
||||
$bookTree = $this->entityRepo->getBookChildren($book, true, true);
|
||||
$html = view('books/export', [
|
||||
'book' => $book,
|
||||
'bookChildren' => $bookTree
|
||||
])->render();
|
||||
return $this->htmlToPdf($html);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert normal webpage HTML to a PDF.
|
||||
* @param $html
|
||||
* @return string
|
||||
*/
|
||||
protected function htmlToPdf($html)
|
||||
{
|
||||
$containedHtml = $this->containHtml($html);
|
||||
$useWKHTML = config('snappy.pdf.binary') !== false;
|
||||
$containedHtml = $this->containHtml($pageHtml);
|
||||
if ($useWKHTML) {
|
||||
$pdf = \SnappyPDF::loadHTML($containedHtml);
|
||||
$pdf->setOption('print-media-type', true);
|
||||
} else {
|
||||
$pdf = \PDF::loadHTML($containedHtml);
|
||||
}
|
||||
|
@ -123,6 +205,40 @@ class ExportService
|
|||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a chapter into a plain text string.
|
||||
* @param Chapter $chapter
|
||||
* @return string
|
||||
*/
|
||||
public function chapterToPlainText(Chapter $chapter)
|
||||
{
|
||||
$text = $chapter->name . "\n\n";
|
||||
$text .= $chapter->description . "\n\n";
|
||||
foreach ($chapter->pages as $page) {
|
||||
$text .= $this->pageToPlainText($page);
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a book into a plain text string.
|
||||
* @param Book $book
|
||||
* @return string
|
||||
*/
|
||||
public function bookToPlainText(Book $book)
|
||||
{
|
||||
$bookTree = $this->entityRepo->getBookChildren($book, true, true);
|
||||
$text = $book->name . "\n\n";
|
||||
foreach ($bookTree as $bookChild) {
|
||||
if ($bookChild->isA('chapter')) {
|
||||
$text .= $this->chapterToPlainText($bookChild);
|
||||
} else {
|
||||
$text .= $this->pageToPlainText($bookChild);
|
||||
}
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -41,7 +41,8 @@ class LdapService
|
|||
// Find user
|
||||
$userFilter = $this->buildFilter($this->config['user_filter'], ['user' => $userName]);
|
||||
$baseDn = $this->config['base_dn'];
|
||||
$users = $this->ldap->searchAndGetEntries($ldapConnection, $baseDn, $userFilter, ['cn', 'uid', 'dn', 'mail']);
|
||||
$emailAttr = $this->config['email_attribute'];
|
||||
$users = $this->ldap->searchAndGetEntries($ldapConnection, $baseDn, $userFilter, ['cn', 'uid', 'dn', $emailAttr]);
|
||||
if ($users['count'] === 0) return null;
|
||||
|
||||
$user = $users[0];
|
||||
|
@ -49,7 +50,7 @@ class LdapService
|
|||
'uid' => (isset($user['uid'])) ? $user['uid'][0] : $user['dn'],
|
||||
'name' => $user['cn'][0],
|
||||
'dn' => $user['dn'],
|
||||
'email' => (isset($user['mail'])) ? $user['mail'][0] : null
|
||||
'email' => (isset($user[$emailAttr])) ? (is_array($user[$emailAttr]) ? $user[$emailAttr][0] : $user[$emailAttr]) : null
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -474,11 +474,13 @@ class PermissionService
|
|||
/**
|
||||
* Get the children of a book in an efficient single query, Filtered by the permission system.
|
||||
* @param integer $book_id
|
||||
* @param bool $filterDrafts
|
||||
* @param bool $filterDrafts
|
||||
* @param bool $fetchPageContent
|
||||
* @return \Illuminate\Database\Query\Builder
|
||||
*/
|
||||
public function bookChildrenQuery($book_id, $filterDrafts = false) {
|
||||
$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) {
|
||||
public function bookChildrenQuery($book_id, $filterDrafts = false, $fetchPageContent = false) {
|
||||
$pageContentSelect = $fetchPageContent ? 'html' : "''";
|
||||
$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) {
|
||||
$query->where('draft', '=', 0);
|
||||
if (!$filterDrafts) {
|
||||
$query->orWhere(function($query) {
|
||||
|
|
|
@ -16,6 +16,7 @@ class SettingService
|
|||
|
||||
protected $setting;
|
||||
protected $cache;
|
||||
protected $localCache = [];
|
||||
|
||||
protected $cachePrefix = 'setting-';
|
||||
|
||||
|
@ -40,8 +41,12 @@ class SettingService
|
|||
public function get($key, $default = false)
|
||||
{
|
||||
if ($default === false) $default = config('setting-defaults.' . $key, false);
|
||||
if (isset($this->localCache[$key])) return $this->localCache[$key];
|
||||
|
||||
$value = $this->getValueFromStore($key, $default);
|
||||
return $this->formatValue($value, $default);
|
||||
$formatted = $this->formatValue($value, $default);
|
||||
$this->localCache[$key] = $formatted;
|
||||
return $formatted;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -71,9 +76,8 @@ class SettingService
|
|||
|
||||
// Check the cache
|
||||
$cacheKey = $this->cachePrefix . $key;
|
||||
if ($this->cache->has($cacheKey)) {
|
||||
return $this->cache->get($cacheKey);
|
||||
}
|
||||
$cacheVal = $this->cache->get($cacheKey, null);
|
||||
if ($cacheVal !== null) return $cacheVal;
|
||||
|
||||
// Check the database
|
||||
$settingObject = $this->getSettingObjectByKey($key);
|
||||
|
|
|
@ -14,7 +14,7 @@ class SocialAuthService
|
|||
protected $socialite;
|
||||
protected $socialAccount;
|
||||
|
||||
protected $validSocialDrivers = ['google', 'github'];
|
||||
protected $validSocialDrivers = ['google', 'github', 'facebook', 'slack', 'twitter'];
|
||||
|
||||
/**
|
||||
* SocialAuthService constructor.
|
||||
|
@ -181,14 +181,24 @@ class SocialAuthService
|
|||
public function getActiveDrivers()
|
||||
{
|
||||
$activeDrivers = [];
|
||||
foreach ($this->validSocialDrivers as $driverName) {
|
||||
if ($this->checkDriverConfigured($driverName)) {
|
||||
$activeDrivers[$driverName] = true;
|
||||
foreach ($this->validSocialDrivers as $driverKey) {
|
||||
if ($this->checkDriverConfigured($driverKey)) {
|
||||
$activeDrivers[$driverKey] = $this->getDriverName($driverKey);
|
||||
}
|
||||
}
|
||||
return $activeDrivers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the presentational name for a driver.
|
||||
* @param $driver
|
||||
* @return mixed
|
||||
*/
|
||||
public function getDriverName($driver)
|
||||
{
|
||||
return config('services.' . strtolower($driver) . '.name');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $socialDriver
|
||||
* @param \Laravel\Socialite\Contracts\User $socialUser
|
||||
|
@ -211,7 +221,6 @@ class SocialAuthService
|
|||
*/
|
||||
public function detachSocialAccount($socialDriver)
|
||||
{
|
||||
session();
|
||||
user()->socialAccounts()->where('driver', '=', $socialDriver)->delete();
|
||||
session()->flash('success', trans('settings.users_social_disconnected', ['socialAccount' => title_case($socialDriver)]));
|
||||
return redirect(user()->getEditUrl());
|
||||
|
|
|
@ -73,7 +73,7 @@ function userCan($permission, Ownable $ownable = null)
|
|||
*/
|
||||
function setting($key = null, $default = false)
|
||||
{
|
||||
$settingService = app(\BookStack\Services\SettingService::class);
|
||||
$settingService = resolve(\BookStack\Services\SettingService::class);
|
||||
if (is_null($key)) return $settingService;
|
||||
return $settingService->get($key, $default);
|
||||
}
|
||||
|
@ -126,6 +126,16 @@ function redirect($to = null, $status = 302, $headers = [], $secure = null)
|
|||
return app('redirect')->to($to, $status, $headers, $secure);
|
||||
}
|
||||
|
||||
function icon($name, $attrs = []) {
|
||||
$iconPath = resource_path('assets/icons/' . $name . '.svg');
|
||||
$attrString = ' ';
|
||||
foreach ($attrs as $attrName => $attr) {
|
||||
$attrString .= $attrName . '="' . $attr . '" ';
|
||||
}
|
||||
$fileContents = file_get_contents($iconPath);
|
||||
return str_replace('<svg', '<svg' . $attrString, $fileContents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a url with multiple parameters for sorting purposes.
|
||||
* Works out the logic to set the correct sorting direction
|
||||
|
|
|
@ -6,17 +6,19 @@
|
|||
"type": "project",
|
||||
"require": {
|
||||
"php": ">=5.6.4",
|
||||
"laravel/framework": "^5.3.4",
|
||||
"laravel/framework": "5.4.*",
|
||||
"ext-tidy": "*",
|
||||
"intervention/image": "^2.3",
|
||||
"laravel/socialite": "^2.0",
|
||||
"barryvdh/laravel-ide-helper": "^2.1",
|
||||
"barryvdh/laravel-debugbar": "^2.2.3",
|
||||
"laravel/socialite": "^3.0",
|
||||
"barryvdh/laravel-ide-helper": "^2.2.3",
|
||||
"barryvdh/laravel-debugbar": "^2.3.2",
|
||||
"league/flysystem-aws-s3-v3": "^1.0",
|
||||
"barryvdh/laravel-dompdf": "^0.7",
|
||||
"barryvdh/laravel-dompdf": "^0.8",
|
||||
"predis/predis": "^1.1",
|
||||
"gathercontent/htmldiff": "^0.2.1",
|
||||
"barryvdh/laravel-snappy": "^0.3.1"
|
||||
"barryvdh/laravel-snappy": "^0.3.1",
|
||||
"laravel/browser-kit-testing": "^1.0",
|
||||
"socialiteproviders/slack": "^3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"fzaninotto/faker": "~1.4",
|
||||
|
@ -34,9 +36,9 @@
|
|||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"classmap": [
|
||||
"tests/TestCase.php"
|
||||
]
|
||||
"psr-4": {
|
||||
"Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"post-root-package-install": [
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -139,7 +139,7 @@ return [
|
|||
Illuminate\Validation\ValidationServiceProvider::class,
|
||||
Illuminate\View\ViewServiceProvider::class,
|
||||
Illuminate\Notifications\NotificationServiceProvider::class,
|
||||
Laravel\Socialite\SocialiteServiceProvider::class,
|
||||
SocialiteProviders\Manager\ServiceProvider::class,
|
||||
|
||||
/**
|
||||
* Third Party
|
||||
|
|
|
@ -82,7 +82,7 @@ return [
|
|||
|
||||
'mysql_testing' => [
|
||||
'driver' => 'mysql',
|
||||
'host' => 'localhost',
|
||||
'host' => '127.0.0.1',
|
||||
'database' => 'bookstack-test',
|
||||
'username' => env('MYSQL_USER', 'bookstack-test'),
|
||||
'password' => env('MYSQL_PASSWORD', 'bookstack-test'),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
return array(
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
@ -13,7 +13,7 @@ return array(
|
|||
*/
|
||||
'show_warnings' => false, // Throw an Exception on warnings from dompdf
|
||||
'orientation' => 'portrait',
|
||||
'defines' => array(
|
||||
'defines' => [
|
||||
/**
|
||||
* The location of the DOMPDF font directory
|
||||
*
|
||||
|
@ -143,7 +143,7 @@ return array(
|
|||
* the desired content might be different (e.g. screen or projection view of html file).
|
||||
* Therefore allow specification of content here.
|
||||
*/
|
||||
"DOMPDF_DEFAULT_MEDIA_TYPE" => "screen",
|
||||
"DOMPDF_DEFAULT_MEDIA_TYPE" => "print",
|
||||
|
||||
/**
|
||||
* The default paper size.
|
||||
|
@ -260,7 +260,7 @@ return array(
|
|||
"DOMPDF_ENABLE_HTML5PARSER" => true,
|
||||
|
||||
|
||||
),
|
||||
],
|
||||
|
||||
|
||||
);
|
||||
];
|
||||
|
|
|
@ -41,12 +41,35 @@ return [
|
|||
'client_id' => env('GITHUB_APP_ID', false),
|
||||
'client_secret' => env('GITHUB_APP_SECRET', false),
|
||||
'redirect' => env('APP_URL') . '/login/service/github/callback',
|
||||
'name' => 'GitHub',
|
||||
],
|
||||
|
||||
'google' => [
|
||||
'client_id' => env('GOOGLE_APP_ID', false),
|
||||
'client_secret' => env('GOOGLE_APP_SECRET', false),
|
||||
'redirect' => env('APP_URL') . '/login/service/google/callback',
|
||||
'name' => 'Google',
|
||||
],
|
||||
|
||||
'slack' => [
|
||||
'client_id' => env('SLACK_APP_ID', false),
|
||||
'client_secret' => env('SLACK_APP_SECRET', false),
|
||||
'redirect' => env('APP_URL') . '/login/service/slack/callback',
|
||||
'name' => 'Slack',
|
||||
],
|
||||
|
||||
'facebook' => [
|
||||
'client_id' => env('FACEBOOK_APP_ID', false),
|
||||
'client_secret' => env('FACEBOOK_APP_SECRET', false),
|
||||
'redirect' => env('APP_URL') . '/login/service/facebook/callback',
|
||||
'name' => 'Facebook',
|
||||
],
|
||||
|
||||
'twitter' => [
|
||||
'client_id' => env('TWITTER_APP_ID', false),
|
||||
'client_secret' => env('TWITTER_APP_SECRET', false),
|
||||
'redirect' => env('APP_URL') . '/login/service/twitter/callback',
|
||||
'name' => 'Twitter',
|
||||
],
|
||||
|
||||
'ldap' => [
|
||||
|
@ -55,7 +78,8 @@ return [
|
|||
'pass' => env('LDAP_PASS', false),
|
||||
'base_dn' => env('LDAP_BASE_DN', false),
|
||||
'user_filter' => env('LDAP_USER_FILTER', '(&(uid=${user}))'),
|
||||
'version' => env('LDAP_VERSION', false)
|
||||
'version' => env('LDAP_VERSION', false),
|
||||
'email_attribute' => env('LDAP_EMAIL_ATTRIBUTE', 'mail'),
|
||||
]
|
||||
|
||||
];
|
||||
|
|
|
@ -11,14 +11,14 @@ class DummyContentSeeder extends Seeder
|
|||
*/
|
||||
public function run()
|
||||
{
|
||||
$user = factory(BookStack\User::class, 1)->create();
|
||||
$user = factory(\BookStack\User::class)->create();
|
||||
$role = \BookStack\Role::getRole('editor');
|
||||
$user->attachRole($role);
|
||||
|
||||
|
||||
$books = factory(BookStack\Book::class, 20)->create(['created_by' => $user->id, 'updated_by' => $user->id])
|
||||
$books = factory(\BookStack\Book::class, 20)->create(['created_by' => $user->id, 'updated_by' => $user->id])
|
||||
->each(function($book) use ($user) {
|
||||
$chapters = factory(BookStack\Chapter::class, 5)->create(['created_by' => $user->id, 'updated_by' => $user->id])
|
||||
$chapters = factory(\BookStack\Chapter::class, 5)->create(['created_by' => $user->id, 'updated_by' => $user->id])
|
||||
->each(function($chapter) use ($user, $book){
|
||||
$pages = factory(\BookStack\Page::class, 5)->make(['created_by' => $user->id, 'updated_by' => $user->id, 'book_id' => $book->id]);
|
||||
$chapter->pages()->saveMany($pages);
|
||||
|
|
10
readme.md
10
readme.md
|
@ -1,7 +1,7 @@
|
|||
# BookStack
|
||||
|
||||
[](https://github.com/ssddanbrown/BookStack/releases/latest)
|
||||
[](https://github.com/ssddanbrown/BookStack/blob/master/LICENSE)
|
||||
[](https://github.com/BookStackApp/BookStack/releases/latest)
|
||||
[](https://github.com/BookStackApp/BookStack/blob/master/LICENSE)
|
||||
[](https://travis-ci.org/BookStackApp/BookStack)
|
||||
|
||||
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
|
|||
|
||||
Some strings have colon-prefixed variables in such as `:userName`. Leave these values as they are as they will be replaced at run-time.
|
||||
|
||||
## Contributing
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
## Website, Docs & Blog
|
||||
|
||||
The website project docs & Blog can be found in the [BookStackApp/website](https://github.com/BookStackApp/website) repo.
|
||||
|
|
|
@ -0,0 +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>
|
After Width: | Height: | Size: 541 B |
|
@ -0,0 +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>
|
After Width: | Height: | Size: 871 B |
|
@ -0,0 +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>
|
After Width: | Height: | Size: 906 B |
|
@ -0,0 +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>
|
After Width: | Height: | Size: 1.9 KiB |
|
@ -0,0 +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>
|
After Width: | Height: | Size: 746 B |
|
@ -214,6 +214,19 @@ export default function (ngApp, events) {
|
|||
}
|
||||
}]);
|
||||
|
||||
let renderer = new markdown.Renderer();
|
||||
// Custom markdown checkbox list item
|
||||
// Attribution: https://github.com/chjj/marked/issues/107#issuecomment-44542001
|
||||
renderer.listitem = function(text) {
|
||||
if (/^\s*\[[x ]\]\s*/.test(text)) {
|
||||
text = text
|
||||
.replace(/^\s*\[ \]\s*/, '<input type="checkbox"/>')
|
||||
.replace(/^\s*\[x\]\s*/, '<input type="checkbox" checked/>');
|
||||
return `<li class="checkbox-item">${text}</li>`;
|
||||
}
|
||||
return `<li>${text}</li>`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Markdown input
|
||||
* Handles the logic for just the editor input field.
|
||||
|
@ -231,13 +244,13 @@ export default function (ngApp, events) {
|
|||
element = element.find('textarea').first();
|
||||
let content = element.val();
|
||||
scope.mdModel = content;
|
||||
scope.mdChange(markdown(content));
|
||||
scope.mdChange(markdown(content, {renderer: renderer}));
|
||||
|
||||
element.on('change input', (event) => {
|
||||
content = element.val();
|
||||
$timeout(() => {
|
||||
scope.mdModel = content;
|
||||
scope.mdChange(markdown(content));
|
||||
scope.mdChange(markdown(content, {renderer: renderer}));
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -135,11 +135,19 @@
|
|||
border-left: 3px solid #BBB;
|
||||
background-color: #EEE;
|
||||
padding: $-s;
|
||||
padding-left: $-xl;
|
||||
display: block;
|
||||
position: relative;
|
||||
&:before {
|
||||
font-family: 'Material-Design-Iconic-Font';
|
||||
padding-right: $-s;
|
||||
left: $-xs + 4px;
|
||||
top: 50%;
|
||||
margin-top: -9px;
|
||||
//top: $-xs + 5px;
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
font-size: 1.222em;
|
||||
line-height: 1;
|
||||
}
|
||||
&.success {
|
||||
border-left-color: $positive;
|
||||
|
|
|
@ -54,6 +54,9 @@ $button-border-radius: 2px;
|
|||
&.muted {
|
||||
@include generate-button-colors(#EEE, #888);
|
||||
}
|
||||
&.muted-light {
|
||||
@include generate-button-colors(#666, #e4e4e4);
|
||||
}
|
||||
}
|
||||
|
||||
.text-button {
|
||||
|
@ -92,6 +95,9 @@ $button-border-radius: 2px;
|
|||
width: 100%;
|
||||
text-align: center;
|
||||
display: block;
|
||||
&.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.button.icon {
|
||||
|
@ -100,6 +106,19 @@ $button-border-radius: 2px;
|
|||
}
|
||||
}
|
||||
|
||||
.button.svg {
|
||||
svg {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
left: $-m;
|
||||
top: $-s - 2px;
|
||||
width: 24px;
|
||||
}
|
||||
padding: $-s $-m;
|
||||
padding-bottom: $-s - 2px;
|
||||
padding-left: $-m*2 + 24px;
|
||||
}
|
||||
|
||||
.button[disabled] {
|
||||
background-color: #BBB;
|
||||
cursor: default;
|
||||
|
|
|
@ -55,20 +55,6 @@ div[class^="col-"] img {
|
|||
}
|
||||
}
|
||||
|
||||
.center-box {
|
||||
margin: $-xl auto 0 auto;
|
||||
padding: $-m $-xxl $-xl*2 $-xxl;
|
||||
max-width: 346px;
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
&.login {
|
||||
background-color: #EEE;
|
||||
box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid #DDD;
|
||||
}
|
||||
}
|
||||
|
||||
.row {
|
||||
margin-left: -$-m;
|
||||
margin-right: -$-m;
|
||||
|
|
|
@ -16,7 +16,7 @@ h2 {
|
|||
}
|
||||
h3 {
|
||||
font-size: 2.333em;
|
||||
line-height: 1.571428572em;
|
||||
line-height: 1.221428572em;
|
||||
margin-top: 0.78571429em;
|
||||
margin-bottom: 0.43137255em;
|
||||
}
|
||||
|
@ -71,6 +71,13 @@ a, .link {
|
|||
padding-right: 0;
|
||||
padding-left: $-s;
|
||||
}
|
||||
&.icon {
|
||||
display: inline-block;
|
||||
}
|
||||
svg {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -84,7 +91,6 @@ p, ul, ol, pre, table, blockquote {
|
|||
hr {
|
||||
border: 0;
|
||||
height: 1px;
|
||||
border: 0;
|
||||
background: #EAEAEA;
|
||||
margin-bottom: $-l;
|
||||
&.faded {
|
||||
|
@ -275,6 +281,14 @@ ol {
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
li.checkbox-item {
|
||||
list-style: none;
|
||||
margin-left: - ($-m * 1.3);
|
||||
input[type="checkbox"] {
|
||||
margin-right: $-xs;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Generic text styling classes
|
||||
*/
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@import "reset";
|
||||
//@import "reset";
|
||||
@import "variables";
|
||||
@import "mixins";
|
||||
@import "html";
|
||||
|
|
|
@ -251,10 +251,24 @@ $btt-size: 40px;
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.center-box {
|
||||
margin: $-xl auto 0 auto;
|
||||
padding: $-m $-xxl $-xl $-xxl;
|
||||
width: 420px;
|
||||
max-width: 100%;
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
//border: 1px solid #DDD;
|
||||
input {
|
||||
width: 100%;
|
||||
}
|
||||
&.login {
|
||||
background-color: #EEE;
|
||||
box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid #DDD;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -14,7 +14,50 @@ return [
|
|||
'throttle' => 'Zu viele Anmeldeversuche. Bitte versuchen sie es in :seconds Sekunden erneut.',
|
||||
|
||||
/**
|
||||
* Email Confirmation Text
|
||||
* Login & Register
|
||||
*/
|
||||
'sign_up' => 'Registrieren',
|
||||
'log_in' => 'Anmelden',
|
||||
'logout' => 'Abmelden',
|
||||
|
||||
'name' => 'Name',
|
||||
'username' => 'Benutzername',
|
||||
'email' => 'E-Mail',
|
||||
'password' => 'Passwort',
|
||||
'password_confirm' => 'Passwort bestätigen',
|
||||
'password_hint' => 'Mindestlänge: 5 Zeichen',
|
||||
'forgot_password' => 'Passwort vergessen?',
|
||||
'remember_me' => 'Angemeldet bleiben',
|
||||
'ldap_email_hint' => 'Bitte geben Sie eine E-Mail-Adresse ein, um diese mit dem Account zu nutzen.',
|
||||
'create_account' => 'Account anlegen',
|
||||
'social_login' => 'Social Login',
|
||||
'social_registration' => 'Social Registrierung',
|
||||
'social_registration_text' => 'Mit einem dieser Möglichkeiten registrieren oder anmelden.',
|
||||
|
||||
|
||||
'register_thanks' => 'Vielen Dank für Ihre Registrierung!',
|
||||
'register_confirm' => 'Bitte prüfen Sie Ihren E-Mail Eingang und klicken auf den Verifizieren-Button, um :appName nutzen zu können.',
|
||||
'registrations_disabled' => 'Die Registrierung ist momentan nicht möglich',
|
||||
'registration_email_domain_invalid' => 'Diese E-Mail-Domain ist für die Benutzer der Applikation nicht freigeschaltet.',
|
||||
'register_success' => 'Vielen Dank für Ihre Registrierung! Die Daten sind gespeichert und Sie sind angemeldet.',
|
||||
|
||||
|
||||
/**
|
||||
* Password Reset
|
||||
*/
|
||||
'reset_password' => 'Passwort vergessen',
|
||||
'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.',
|
||||
'reset_password_send_button' => 'Passwort zurücksetzen',
|
||||
'reset_password_sent_success' => 'Eine E-Mail mit den Instruktionen, um Ihr Passwort zurückzusetzen wurde an :email gesendet.',
|
||||
'reset_password_success' => 'Ihr Passwort wurde erfolgreich zurück gesetzt.',
|
||||
|
||||
'email_reset_subject' => 'Passwort zurücksetzen für :appName',
|
||||
'email_reset_text' => 'Sie erhalten diese E-Mail, weil eine Passwort-Rücksetzung für Ihren Account beantragt wurde.',
|
||||
'email_reset_not_requested' => 'Wenn Sie die Passwort-Rücksetzung nicht ausgelöst haben, ist kein weiteres Handeln notwendig.',
|
||||
|
||||
|
||||
/**
|
||||
* Email Confirmation
|
||||
*/
|
||||
'email_confirm_subject' => 'Bestätigen sie ihre E-Mail Adresse bei :appName',
|
||||
'email_confirm_greeting' => 'Danke, dass sie :appName beigetreten sind!',
|
||||
|
@ -23,4 +66,10 @@ return [
|
|||
'email_confirm_send_error' => 'Bestätigungs-E-Mail benötigt, aber das System konnte die E-Mail nicht versenden. Kontaktieren sie den Administrator, um sicherzustellen, dass das Sytsem korrekt eingerichtet ist.',
|
||||
'email_confirm_success' => 'Ihre E-Mail Adresse wurde bestätigt!',
|
||||
'email_confirm_resent' => 'Bestätigungs-E-Mail wurde erneut versendet, bitte überprüfen sie ihren Posteingang.',
|
||||
|
||||
'email_not_confirmed' => 'E-Mail-Adresse ist nicht bestätigt',
|
||||
'email_not_confirmed_text' => 'Ihre E-Mail-Adresse ist bisher nicht bestätigt.',
|
||||
'email_not_confirmed_click_link' => 'Bitte klicken Sie auf den Link in der E-Mail, die Sie nach der Registrierung erhalten haben.',
|
||||
'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:',
|
||||
'email_not_confirmed_resend_button' => 'Bestätigungs E-Mail erneut senden',
|
||||
];
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
return [
|
||||
|
||||
/**
|
||||
* Buttons
|
||||
*/
|
||||
'cancel' => 'Abbrechen',
|
||||
'confirm' => 'Bestätigen',
|
||||
'back' => 'Zurück',
|
||||
'save' => 'Speichern',
|
||||
'continue' => 'Weiter',
|
||||
'select' => 'Auswählen',
|
||||
|
||||
/**
|
||||
* Form Labels
|
||||
*/
|
||||
'name' => 'Name',
|
||||
'description' => 'Beschreibung',
|
||||
'role' => 'Rolle',
|
||||
|
||||
/**
|
||||
* Actions
|
||||
*/
|
||||
'actions' => 'Aktionen',
|
||||
'view' => 'Anzeigen',
|
||||
'create' => 'Anlegen',
|
||||
'update' => 'Aktualisieren',
|
||||
'edit' => 'Bearbeiten',
|
||||
'sort' => 'Sortieren',
|
||||
'move' => 'Verschieben',
|
||||
'delete' => 'Löschen',
|
||||
'search' => 'Suchen',
|
||||
'search_clear' => 'Suche löschen',
|
||||
'reset' => 'Zurücksetzen',
|
||||
'remove' => 'Entfernen',
|
||||
|
||||
|
||||
/**
|
||||
* Misc
|
||||
*/
|
||||
'deleted_user' => 'Gelöschte Benutzer',
|
||||
'no_activity' => 'Keine Aktivitäten zum Anzeigen',
|
||||
'no_items' => 'Keine Einträge gefunden.',
|
||||
'back_to_top' => 'nach oben',
|
||||
'toggle_details' => 'Details zeigen/verstecken',
|
||||
|
||||
/**
|
||||
* Header
|
||||
*/
|
||||
'view_profile' => 'Profil ansehen',
|
||||
'edit_profile' => 'Profil bearbeiten',
|
||||
|
||||
/**
|
||||
* Email Content
|
||||
*/
|
||||
'email_action_help' => 'Sollte es beim Anklicken des ":actionText" Buttons Probleme geben, kopieren Sie folgende URL und fügen diese in Ihrem Webbrowser ein:',
|
||||
'email_rights' => 'Alle Rechte vorbehalten',
|
||||
];
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
return [
|
||||
|
||||
/**
|
||||
* Image Manager
|
||||
*/
|
||||
'image_select' => 'Bild auswählen',
|
||||
'image_all' => 'Alle',
|
||||
'image_all_title' => 'Alle Bilder anzeigen',
|
||||
'image_book_title' => 'Zeige alle Bilder, die in dieses Buch hochgeladen wurden',
|
||||
'image_page_title' => 'Zeige alle Bilder, die auf diese Seite hochgeladen wurden',
|
||||
'image_search_hint' => 'Nach Bildnamen suchen',
|
||||
'image_uploaded' => 'Hochgeladen am :uploadedDate',
|
||||
'image_load_more' => 'Mehr',
|
||||
'image_image_name' => 'Bildname',
|
||||
'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.',
|
||||
'image_select_image' => 'Bild auswählen',
|
||||
'image_dropzone' => 'Ziehen Sie Bilder hier hinein oder klicken Sie hier, um ein Bild auszuwählen',
|
||||
'images_deleted' => 'Bilder gelöscht',
|
||||
'image_preview' => 'Bildvorschau',
|
||||
'image_upload_success' => 'Bild erfolgreich hochgeladen',
|
||||
'image_update_success' => 'Bilddetails erfolgreich aktualisiert',
|
||||
'image_delete_success' => 'Bild erfolgreich gelöscht'
|
||||
];
|
|
@ -0,0 +1,225 @@
|
|||
<?php
|
||||
return [
|
||||
|
||||
/**
|
||||
* Shared
|
||||
*/
|
||||
'recently_created' => 'Kürzlich angelegt',
|
||||
'recently_created_pages' => 'Kürzlich angelegte Seiten',
|
||||
'recently_updated_pages' => 'Kürzlich aktualisierte Seiten',
|
||||
'recently_created_chapters' => 'Kürzlich angelegte Kapitel',
|
||||
'recently_created_books' => 'Kürzlich angelegte Bücher',
|
||||
'recently_update' => 'Kürzlich aktualisiert',
|
||||
'recently_viewed' => 'Kürzlich angesehen',
|
||||
'recent_activity' => 'Kürzliche Aktivität',
|
||||
'create_now' => 'Jetzt anlegen',
|
||||
'revisions' => 'Revisionen',
|
||||
'meta_created' => 'Angelegt am :timeLength',
|
||||
'meta_created_name' => 'Angelegt am :timeLength durch :user',
|
||||
'meta_updated' => 'Aktualisiert am :timeLength',
|
||||
'meta_updated_name' => 'Aktualisiert am :timeLength durch :user',
|
||||
'x_pages' => ':count Seiten',
|
||||
'entity_select' => 'Eintrag auswählen',
|
||||
'images' => 'Bilder',
|
||||
'my_recent_drafts' => 'Meine kürzlichen Entwürfe',
|
||||
'my_recently_viewed' => 'Kürzlich von mir angesehen',
|
||||
'no_pages_viewed' => 'Sie haben bisher keine Seiten angesehen.',
|
||||
'no_pages_recently_created' => 'Sie haben bisher keine Seiten angelegt.',
|
||||
'no_pages_recently_updated' => 'Sie haben bisher keine Seiten aktualisiert.',
|
||||
|
||||
/**
|
||||
* Permissions and restrictions
|
||||
*/
|
||||
'permissions' => 'Berechtigungen',
|
||||
'permissions_intro' => 'Wenn individuelle Berechtigungen aktiviert werden, überschreiben diese Einstellungen durch Rollen zugewiesene Berechtigungen.',
|
||||
'permissions_enable' => 'Individuelle Berechtigungen aktivieren',
|
||||
'permissions_save' => 'Berechtigungen speichern',
|
||||
|
||||
/**
|
||||
* Search
|
||||
*/
|
||||
'search_results' => 'Suchergebnisse',
|
||||
'search_results_page' => 'Seiten-Suchergebnisse',
|
||||
'search_results_chapter' => 'Kapitel-Suchergebnisse',
|
||||
'search_results_book' => 'Buch-Suchergebnisse',
|
||||
'search_clear' => 'Suche zurücksetzen',
|
||||
'search_view_pages' => 'Zeige alle passenden Seiten',
|
||||
'search_view_chapters' => 'Zeige alle passenden Kapitel',
|
||||
'search_view_books' => 'Zeige alle passenden Bücher',
|
||||
'search_no_pages' => 'Es wurden keine passenden Suchergebnisse gefunden',
|
||||
'search_for_term' => 'Suche nach :term',
|
||||
'search_page_for_term' => 'Suche nach :term in Seiten',
|
||||
'search_chapter_for_term' => 'Suche nach :term in Kapiteln',
|
||||
'search_book_for_term' => 'Suche nach :term in Büchern',
|
||||
|
||||
/**
|
||||
* Books
|
||||
*/
|
||||
'book' => 'Buch',
|
||||
'books' => 'Bücher',
|
||||
'books_empty' => 'Es wurden keine Bücher angelegt',
|
||||
'books_popular' => 'Populäre Bücher',
|
||||
'books_recent' => 'Kürzlich genutzte Bücher',
|
||||
'books_popular_empty' => 'Die populärsten Bücher werden hier angezeigt.',
|
||||
'books_create' => 'Neues Buch anlegen',
|
||||
'books_delete' => 'Buch löschen',
|
||||
'books_delete_named' => 'Buch :bookName löschen',
|
||||
'books_delete_explain' => 'Sie möchten das Buch \':bookName\' löschen und alle Seiten und Kapitel entfernen.',
|
||||
'books_delete_confirmation' => 'Sind Sie sicher, dass Sie dieses Buch löschen möchten?',
|
||||
'books_edit' => 'Buch bearbeiten',
|
||||
'books_edit_named' => 'Buch :bookName bearbeiten',
|
||||
'books_form_book_name' => 'Buchname',
|
||||
'books_save' => 'Buch speichern',
|
||||
'books_permissions' => 'Buch-Berechtigungen',
|
||||
'books_permissions_updated' => 'Buch-Berechtigungen aktualisiert',
|
||||
'books_empty_contents' => 'Es sind noch keine Seiten oder Kapitel für dieses Buch angelegt.',
|
||||
'books_empty_create_page' => 'Neue Seite anlegen',
|
||||
'books_empty_or' => 'oder',
|
||||
'books_empty_sort_current_book' => 'Aktuelles Buch sortieren',
|
||||
'books_empty_add_chapter' => 'Neues Kapitel hinzufügen',
|
||||
'books_permissions_active' => 'Buch-Berechtigungen aktiv',
|
||||
'books_search_this' => 'Dieses Buch durchsuchen',
|
||||
'books_navigation' => 'Buch-Navigation',
|
||||
'books_sort' => 'Buchinhalte sortieren',
|
||||
'books_sort_named' => 'Buch :bookName sortieren',
|
||||
'books_sort_show_other' => 'Andere Bücher zeigen',
|
||||
'books_sort_save' => 'Neue Reihenfolge speichern',
|
||||
|
||||
/**
|
||||
* Chapters
|
||||
*/
|
||||
'chapter' => 'Kapitel',
|
||||
'chapters' => 'Kapitel',
|
||||
'chapters_popular' => 'Populäre Kapitel',
|
||||
'chapters_new' => 'Neues Kapitel',
|
||||
'chapters_create' => 'Neues Kapitel anlegen',
|
||||
'chapters_delete' => 'Kapitel entfernen',
|
||||
'chapters_delete_named' => 'Kapitel :chapterName entfernen',
|
||||
'chapters_delete_explain' => 'Sie möchten das Kapitel \':chapterName\' löschen und alle Seiten dem direkten Eltern-Buch hinzugefügen.',
|
||||
'chapters_delete_confirm' => 'Sind Sie sicher, dass Sie dieses Kapitel löschen möchten?',
|
||||
'chapters_edit' => 'Kapitel bearbeiten',
|
||||
'chapters_edit_named' => 'Kapitel :chapterName bearbeiten',
|
||||
'chapters_save' => 'Kapitel speichern',
|
||||
'chapters_move' => 'Kapitel verschieben',
|
||||
'chapters_move_named' => 'Kapitel :chapterName verschieben',
|
||||
'chapter_move_success' => 'Kapitel in das Buch :bookName verschoben.',
|
||||
'chapters_permissions' => 'Kapitel-Berechtigungen',
|
||||
'chapters_empty' => 'Aktuell sind keine Kapitel in diesem Buch angelegt.',
|
||||
'chapters_permissions_active' => 'Kapitel-Berechtigungen aktiv',
|
||||
'chapters_permissions_success' => 'Kapitel-Berechtigungenen aktualisisert',
|
||||
|
||||
/**
|
||||
* Pages
|
||||
*/
|
||||
'page' => 'Seite',
|
||||
'pages' => 'Seiten',
|
||||
'pages_popular' => 'Populäre Seiten',
|
||||
'pages_new' => 'Neue Seite',
|
||||
'pages_attachments' => 'Anhänge',
|
||||
'pages_navigation' => 'Seitennavigation',
|
||||
'pages_delete' => 'Seite löschen',
|
||||
'pages_delete_named' => 'Seite :pageName löschen',
|
||||
'pages_delete_draft_named' => 'Seitenentwurf von :pageName löschen',
|
||||
'pages_delete_draft' => 'Seitenentwurf löschen',
|
||||
'pages_delete_success' => 'Seite gelöscht',
|
||||
'pages_delete_draft_success' => 'Seitenentwurf gelöscht',
|
||||
'pages_delete_confirm' => 'Sind Sie sicher, dass Sie diese Seite löschen möchen?',
|
||||
'pages_delete_draft_confirm' => 'Sind Sie sicher, dass Sie diesen Seitenentwurf löschen möchten?',
|
||||
'pages_editing_named' => 'Seite :pageName bearbeiten',
|
||||
'pages_edit_toggle_header' => 'Toggle header',
|
||||
'pages_edit_save_draft' => 'Entwurf speichern',
|
||||
'pages_edit_draft' => 'Seitenentwurf bearbeiten',
|
||||
'pages_editing_draft' => 'Seitenentwurf bearbeiten',
|
||||
'pages_editing_page' => 'Seite bearbeiten',
|
||||
'pages_edit_draft_save_at' => 'Entwurf gespeichert um ',
|
||||
'pages_edit_delete_draft' => 'Entwurf löschen',
|
||||
'pages_edit_discard_draft' => 'Entwurf verwerfen',
|
||||
'pages_edit_set_changelog' => 'Veränderungshinweis setzen',
|
||||
'pages_edit_enter_changelog_desc' => 'Bitte geben Sie eine kurze Zusammenfassung Ihrer Änderungen ein',
|
||||
'pages_edit_enter_changelog' => 'Veränderungshinweis eingeben',
|
||||
'pages_save' => 'Seite speichern',
|
||||
'pages_title' => 'Seitentitel',
|
||||
'pages_name' => 'Seitenname',
|
||||
'pages_md_editor' => 'Redakteur',
|
||||
'pages_md_preview' => 'Vorschau',
|
||||
'pages_md_insert_image' => 'Bild einfügen',
|
||||
'pages_md_insert_link' => 'Link zu einem Objekt einfügen',
|
||||
'pages_not_in_chapter' => 'Seite ist in keinem Kapitel',
|
||||
'pages_move' => 'Seite verschieben',
|
||||
'pages_move_success' => 'Seite nach ":parentName" verschoben',
|
||||
'pages_permissions' => 'Seiten Berechtigungen',
|
||||
'pages_permissions_success' => 'Seiten Berechtigungen aktualisiert',
|
||||
'pages_revisions' => 'Seitenversionen',
|
||||
'pages_revisions_named' => 'Seitenversionen von :pageName',
|
||||
'pages_revision_named' => 'Seitenversion von :pageName',
|
||||
'pages_revisions_created_by' => 'Angelegt von',
|
||||
'pages_revisions_date' => 'Versionsdatum',
|
||||
'pages_revisions_changelog' => 'Veränderungshinweise',
|
||||
'pages_revisions_changes' => 'Veränderungen',
|
||||
'pages_revisions_current' => 'Aktuelle Version',
|
||||
'pages_revisions_preview' => 'Vorschau',
|
||||
'pages_revisions_restore' => 'Zurück sichern',
|
||||
'pages_revisions_none' => 'Diese Seite hat keine älteren Versionen.',
|
||||
'pages_export' => 'Exportieren',
|
||||
'pages_export_html' => 'HTML-Datei',
|
||||
'pages_export_pdf' => 'PDF-Datei',
|
||||
'pages_export_text' => 'Text-Datei',
|
||||
'pages_copy_link' => 'Link kopieren',
|
||||
'pages_permissions_active' => 'Seiten-Berechtigungen aktiv',
|
||||
'pages_initial_revision' => 'Erste Veröffentlichung',
|
||||
'pages_initial_name' => 'Neue Seite',
|
||||
'pages_editing_draft_notification' => 'Sie bearbeiten momenten einen Entwurf, der zuletzt um :timeDiff gespeichert wurde.',
|
||||
'pages_draft_edited_notification' => 'Diese Seite wurde seit diesem Zeitpunkt verändert. Wir empfehlen Ihnen, diesen Entwurf zu verwerfen.',
|
||||
'pages_draft_edit_active' => [
|
||||
'start_a' => ':count Benutzer haben die Bearbeitung dieser Seite begonnen.',
|
||||
'start_b' => ':userName hat die Bearbeitung dieser Seite begonnen.',
|
||||
'time_a' => 'seit die Seiten zuletzt aktualisiert wurden.',
|
||||
'time_b' => 'in den letzten :minCount Minuten',
|
||||
'message' => ':start :time. Achten Sie darauf keine Aktualisierungen von anderen Benutzern zu überschreiben!',
|
||||
],
|
||||
'pages_draft_discarded' => 'Entwurf verworfen. Der aktuelle Seiteninhalt wurde geladen.',
|
||||
|
||||
/**
|
||||
* Editor sidebar
|
||||
*/
|
||||
'page_tags' => 'Seiten-Schlagwörter',
|
||||
'tag' => 'Schlagwort',
|
||||
'tags' => 'Schlagworte',
|
||||
'tag_value' => 'Schlagwortinhalt (Optional)',
|
||||
'tags_explain' => "Fügen Sie Schlagworte hinzu, um Ihren Inhalt zu kategorisieren. \n Sie können einen erklärenden Inhalt hinzufügen, um eine genauere Unterteilung vorzunehmen.",
|
||||
'tags_add' => 'Weiteres Schlagwort hinzufügen',
|
||||
'attachments' => 'Anhänge',
|
||||
'attachments_explain' => 'Sie können auf Ihrer Seite Dateien hochladen oder Links anfügen. Diese werden in der seitlich angezeigt.',
|
||||
'attachments_explain_instant_save' => 'Änderungen werden direkt gespeichert.',
|
||||
'attachments_items' => 'Angefügte Elemente',
|
||||
'attachments_upload' => 'Datei hochladen',
|
||||
'attachments_link' => 'Link anfügen',
|
||||
'attachments_set_link' => 'Link setzen',
|
||||
'attachments_delete_confirm' => 'Klicken Sie erneut auf löschen, um diesen Anhang zu entfernen.',
|
||||
'attachments_dropzone' => 'Ziehen Sie Dateien hier hinein oder klicken Sie hier, um eine Datei auszuwählen',
|
||||
'attachments_no_files' => 'Es wurden bisher keine Dateien hochgeladen.',
|
||||
'attachments_explain_link' => 'Wenn Sie keine Datei hochladen möchten, können Sie stattdessen einen Link anfügen. Dieser Link kann auf eine andere Seite oder zu einer Datei in der Cloud weisen.',
|
||||
'attachments_link_name' => 'Link-Name',
|
||||
'attachment_link' => 'Link zum Anhang',
|
||||
'attachments_link_url' => 'Link zu einer Datei',
|
||||
'attachments_link_url_hint' => 'URL einer Seite oder Datei',
|
||||
'attach' => 'anfügen',
|
||||
'attachments_edit_file' => 'Datei bearbeiten',
|
||||
'attachments_edit_file_name' => 'Dateiname',
|
||||
'attachments_edit_drop_upload' => 'Ziehen Sie Dateien hier hinein, um diese hochzuladen und zu überschreiben',
|
||||
'attachments_order_updated' => 'Reihenfolge der Anhänge aktualisiert',
|
||||
'attachments_updated_success' => 'Anhang-Details aktualisiert',
|
||||
'attachments_deleted' => 'Anhang gelöscht',
|
||||
'attachments_file_uploaded' => 'Datei erfolgrecich hochgeladen',
|
||||
'attachments_file_updated' => 'Datei erfolgreich aktualisisert',
|
||||
'attachments_link_attached' => 'Link erfolgreich der Seite hinzugefügt',
|
||||
|
||||
/**
|
||||
* Profile View
|
||||
*/
|
||||
'profile_user_for_x' => 'Benutzer seit :time',
|
||||
'profile_created_content' => 'Angelegte Inhalte',
|
||||
'profile_not_created_pages' => ':userName hat bisher keine Seiten angelegt.',
|
||||
'profile_not_created_chapters' => ':userName hat bisher keine Kapitel angelegt.',
|
||||
'profile_not_created_books' => ':userName hat bisher keine Bücher angelegt.',
|
||||
];
|
|
@ -8,5 +8,63 @@ return [
|
|||
|
||||
// Pages
|
||||
'permission' => 'Sie haben keine Berechtigung auf diese Seite zuzugreifen.',
|
||||
'permissionJson' => 'Sie haben keine Berechtigung die angeforderte Aktion auszuführen.'
|
||||
'permissionJson' => 'Sie haben keine Berechtigung die angeforderte Aktion auszuführen.',
|
||||
|
||||
// Auth
|
||||
'error_user_exists_different_creds' => 'Ein Benutzer mit der E-Mail-Adresse :email ist bereits mit anderen Anmeldedaten angelegt.',
|
||||
'email_already_confirmed' => 'Die E-Mail-Adresse ist bereits bestätigt. Bitte melden Sie sich an.',
|
||||
'email_confirmation_invalid' => 'Der Bestätigungs-Token ist nicht gültig oder wurde bereits verwendet. Bitte registrieren Sie sich erneut.',
|
||||
'email_confirmation_expired' => 'Der Bestätigungs-Token ist abgelaufen. Es wurde eine neue Bestätigungs-E-Mail gesendet.',
|
||||
'ldap_fail_anonymous' => 'Anonymer LDAP Zugriff ist fehlgeschlafgen',
|
||||
'ldap_fail_authed' => 'LDAP Zugriff mit DN & Passwort ist fehlgeschlagen',
|
||||
'ldap_extension_not_installed' => 'LDAP PHP Erweiterung ist nicht installiert.',
|
||||
'ldap_cannot_connect' => 'Die Verbindung zu LDAP-Server ist fehlgeschlagen. Beim initialen Verbindungsaufbau trat ein Fehler auf.',
|
||||
'social_no_action_defined' => 'Es ist keine Aktion definiert',
|
||||
'social_account_in_use' => 'Dieses :socialAccount Konto wird bereits verwendet. Bitte melden Sie sich mit dem :socialAccount Konto an.',
|
||||
'social_account_email_in_use' => 'Die E-Mail-Adresse :email ist bereits registriert. Wenn Sie bereits registriert sind, können Sie Ihr :socialAccount Konto in Ihren Profil-Einstellungen verknüpfen.',
|
||||
'social_account_existing' => 'Dieses :socialAccount Konto ist bereits mit Ihrem Profil verknüpft.',
|
||||
'social_account_already_used_existing' => 'Dieses :socialAccount Konto wird bereits durch einen anderen Benutzer verwendet.',
|
||||
'social_account_not_used' => 'Dieses :socialAccount Konto ist bisher keinem Benutzer zugeordnet. Bitte verknüpfen Sie deses in Ihrem Profil-Einstellungen.',
|
||||
'social_account_register_instructions' => 'Wenn Sie bisher keinen Social-Media Konto besitzen können Sie ein solches Konto mit der :socialAccount Option anlegen.',
|
||||
'social_driver_not_found' => 'Social-Media Konto Treiber nicht gefunden',
|
||||
'social_driver_not_configured' => 'Ihr :socialAccount Konto ist nicht korrekt konfiguriert.',
|
||||
|
||||
// System
|
||||
'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.',
|
||||
'cannot_get_image_from_url' => 'Bild konnte nicht von der URL :url geladen werden.',
|
||||
'cannot_create_thumbs' => 'Der Server kann keine Vorschau-Bilder erzeugen. Bitte prüfen Sie, ob Sie die GD PHP Erweiterung installiert haben.',
|
||||
'server_upload_limit' => 'Der Server verbietet das Hochladen von Dateien mit dieser Dateigröße. Bitte versuchen Sie es mit einer kleineren Datei.',
|
||||
'image_upload_error' => 'Beim Hochladen des Bildes trat ein Fehler auf.',
|
||||
|
||||
// Attachments
|
||||
'attachment_page_mismatch' => 'Die Seite stimmt nach dem Hochladen des Anhangs nicht überein.',
|
||||
|
||||
// Pages
|
||||
'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.',
|
||||
|
||||
// Entities
|
||||
'entity_not_found' => 'Eintrag nicht gefunden',
|
||||
'book_not_found' => 'Buch nicht gefunden',
|
||||
'page_not_found' => 'Seite nicht gefunden',
|
||||
'chapter_not_found' => 'Kapitel nicht gefunden',
|
||||
'selected_book_not_found' => 'Das gewählte Buch wurde nicht gefunden.',
|
||||
'selected_book_chapter_not_found' => 'Das gewählte Buch oder Kapitel wurde nicht gefunden.',
|
||||
'guests_cannot_save_drafts' => 'Gäste können keine Entwürfe speichern',
|
||||
|
||||
// Users
|
||||
'users_cannot_delete_only_admin' => 'Sie können den einzigen Administrator nicht löschen.',
|
||||
'users_cannot_delete_guest' => 'Sie können den Gast-Benutzer nicht löschen',
|
||||
|
||||
// Roles
|
||||
'role_cannot_be_edited' => 'Diese Rolle kann nicht bearbeitet werden.',
|
||||
'role_system_cannot_be_deleted' => 'Dies ist eine Systemrolle und kann nicht gelöscht werden',
|
||||
'role_registration_default_cannot_delete' => 'Diese Rolle kann nicht gelöscht werden solange sie als Standardrolle für neue Registrierungen gesetzt ist',
|
||||
|
||||
// Error pages
|
||||
'404_page_not_found' => 'Seite nicht gefunden',
|
||||
'sorry_page_not_found' => 'Entschuldigung. Die Seite, die Sie angefordert haben wurde nicht gefunden.',
|
||||
'return_home' => 'Zurück zur Startseite',
|
||||
'error_occurred' => 'Es ist ein Fehler aufgetreten',
|
||||
'app_down' => ':appName befindet sich aktuell im Wartungsmodus.',
|
||||
'back_soon' => 'Wir werden so schnell wie möglich wieder online sein.',
|
||||
];
|
||||
|
|
|
@ -16,7 +16,7 @@ return [
|
|||
'password' => 'Passörter müssen mindestens sechs Zeichen enthalten und die Wiederholung muss identisch sein.',
|
||||
'user' => "Wir können keinen Benutzer mit dieser E-Mail Adresse finden.",
|
||||
'token' => 'Dieser Passwort-Reset-Token ist ungültig.',
|
||||
'sent' => 'Wir haben ihnen eine E-Mail mit einem Link zum Zurücksetzen des Passworts zugesendet!',
|
||||
'sent' => 'Wir haben Ihnen eine E-Mail mit einem Link zum Zurücksetzen des Passworts zugesendet!',
|
||||
'reset' => 'Ihr Passwort wurde zurückgesetzt!',
|
||||
|
||||
];
|
||||
|
|
|
@ -10,14 +10,19 @@ return [
|
|||
|
||||
'settings' => 'Einstellungen',
|
||||
'settings_save' => 'Einstellungen speichern',
|
||||
'settings_save_success' => 'Einstellungen gespeichert',
|
||||
|
||||
/**
|
||||
* App settings
|
||||
*/
|
||||
|
||||
'app_settings' => 'Anwendungseinstellungen',
|
||||
'app_name' => 'Anwendungsname',
|
||||
'app_name_desc' => 'Dieser Name wird im Header und E-Mails angezeigt.',
|
||||
'app_name_desc' => 'Dieser Name wird im Header und in E-Mails angezeigt.',
|
||||
'app_name_header' => 'Anwendungsname im Header anzeigen?',
|
||||
'app_public_viewing' => 'Öffentliche Ansicht erlauben?',
|
||||
'app_secure_images' => 'Erhöhte Sicherheit für Bilduploads aktivieren?',
|
||||
'app_secure_images_desc' => 'Aus Leistungsgründen sind alle Bilder öffentlich sichtbar. Diese Option fügt zufällige, schwer zu eratene, Zeichenketten vor die Bild-URLs hinzu. Stellen sie sicher, dass Verzeichnindexes deaktiviert sind, um einen einfachen Zugrif zu verhindern.',
|
||||
'app_secure_images_desc' => 'Aus Leistungsgründen sind alle Bilder öffentlich sichtbar. Diese Option fügt zufällige, schwer zu eratene, Zeichenketten vor die Bild-URLs hinzu. Stellen sie sicher, dass Verzeichnindexes deaktiviert sind, um einen einfachen Zugriff zu verhindern.',
|
||||
'app_editor' => 'Seiteneditor',
|
||||
'app_editor_desc' => 'Wählen sie den Editor aus, der von allen Benutzern genutzt werden soll, um Seiten zu editieren.',
|
||||
'app_custom_html' => 'Benutzerdefinierter HTML <head> Inhalt',
|
||||
|
@ -25,15 +30,82 @@ return [
|
|||
'app_logo' => 'Anwendungslogo',
|
||||
'app_logo_desc' => 'Dieses Bild sollte 43px hoch sein. <br>Größere Bilder werden verkleinert.',
|
||||
'app_primary_color' => 'Primäre Anwendungsfarbe',
|
||||
'app_primary_color_desc' => 'Dies sollte ein HEX Wert sein. <br>Leer lassen des Feldes setzt auf die Standard-Anwendungsfarbe zurück.',
|
||||
'app_primary_color_desc' => 'Dies sollte ein HEX Wert sein. <br>Wenn Sie nicht eingeben, wird die Anwendung auf die Standardfarbe zurückgesetzt.',
|
||||
|
||||
/**
|
||||
* Registration settings
|
||||
*/
|
||||
|
||||
'reg_settings' => 'Registrierungseinstellungen',
|
||||
'reg_allow' => 'Registrierung erlauben?',
|
||||
'reg_default_role' => 'Standard-Benutzerrolle nach Registrierung',
|
||||
'reg_confirm_email' => 'Bestätigung per E-Mail erforderlich?',
|
||||
'reg_confirm_email_desc' => 'Falls die Einschränkung für; Domains genutzt wird, ist die Bestätigung per E-Mail zwingend erforderlich und der untenstehende Wert wird ignoriert.',
|
||||
'reg_confirm_email_desc' => 'Falls die Einschränkung für Domains genutzt wird, ist die Bestätigung per E-Mail zwingend erforderlich und der untenstehende Wert wird ignoriert.',
|
||||
'reg_confirm_restrict_domain' => 'Registrierung auf bestimmte Domains einschränken',
|
||||
'reg_confirm_restrict_domain_desc' => 'Fügen sie eine, durch Komma getrennte, Liste von E-Mail Domains hinzu, auf die die Registrierung eingeschränkt werden soll. Benutzern wird eine E-Mail gesendet, um ihre E-Mail Adresse zu bestätigen, bevor sie diese Anwendung nutzen können. <br> Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung ändern.',
|
||||
'reg_confirm_restrict_domain_placeholder' => 'Keine Einschränkung gesetzt',
|
||||
|
||||
/**
|
||||
* Role settings
|
||||
*/
|
||||
|
||||
'roles' => 'Rollen',
|
||||
'role_user_roles' => 'Benutzer-Rollen',
|
||||
'role_create' => 'Neue Rolle anlegen',
|
||||
'role_create_success' => 'Rolle erfolgreich angelegt',
|
||||
'role_delete' => 'Rolle löschen',
|
||||
'role_delete_confirm' => 'Sie möchten die Rolle \':roleName\' löschen.',
|
||||
'role_delete_users_assigned' => 'Diese Rolle ist :userCount Benutzern zugeordnet. Sie können unten eine neue Rolle auswählen, die Sie diesen Benutzern zuordnen möchten.',
|
||||
'role_delete_no_migration' => "Den Benutzern keine andere Rolle zuordnen",
|
||||
'role_delete_sure' => 'Sind Sie sicher, dass Sie diese Rolle löschen möchten?',
|
||||
'role_delete_success' => 'Rolle erfolgreich gelöscht',
|
||||
'role_edit' => 'Rolle bearbeiten',
|
||||
'role_details' => 'Rollen-Details',
|
||||
'role_name' => 'Rollenname',
|
||||
'role_desc' => 'Kurzbeschreibung der Rolle',
|
||||
'role_system' => 'System-Berechtigungen',
|
||||
'role_manage_users' => 'Benutzer verwalten',
|
||||
'role_manage_roles' => 'Rollen & Rollen-Berechtigungen verwalten',
|
||||
'role_manage_entity_permissions' => 'Alle Buch-, Kapitel und Seiten-Berechtigungen verwalten',
|
||||
'role_manage_own_entity_permissions' => 'Nur Berechtigungen eigener Bücher, Kapitel und Seiten verwalten',
|
||||
'role_manage_settings' => 'Globaleinstellungen verwalrten',
|
||||
'role_asset' => 'Berechtigungen',
|
||||
'role_asset_desc' => 'Diese Berechtigungen gelten für den Standard-Zugriff innerhalb des Systems. Berechtigungen für Bücher, Kapitel und Seiten überschreiben diese Berechtigungenen.',
|
||||
'role_all' => 'Alle',
|
||||
'role_own' => 'Eigene',
|
||||
'role_controlled_by_asset' => 'Controlled by the asset they are uploaded to',
|
||||
'role_save' => 'Rolle speichern',
|
||||
'role_update_success' => 'Rolle erfolgreich gespeichert',
|
||||
'role_users' => 'Dieser Rolle zugeordnete Benutzer',
|
||||
'role_users_none' => 'Bisher sind dieser Rolle keiner Benutzer zugeordnet,',
|
||||
|
||||
/**
|
||||
* Users
|
||||
*/
|
||||
|
||||
'users' => 'Benutzer',
|
||||
'user_profile' => 'Benutzerprofil',
|
||||
'users_add_new' => 'Benutzer hinzufügen',
|
||||
'users_search' => 'Benutzer suchen',
|
||||
'users_role' => 'Benutzerrollen',
|
||||
'users_external_auth_id' => 'Externe Authentifizierungs-ID',
|
||||
'users_password_warning' => 'Füllen Sie die folgenden Felder nur aus, wenn Sie Ihr Passwort ändern möchten:',
|
||||
'users_system_public' => 'Dieser Benutzer repräsentiert alle Gast-Benutzer, die diese Seite betrachten. Er kann nicht zum Anmelden benutzt werden, sondern wird automatisch zugeordnet.',
|
||||
'users_delete' => 'Benutzer löschen',
|
||||
'users_delete_named' => 'Benutzer :userName löschen',
|
||||
'users_delete_warning' => 'Sie möchten den Benutzer \':userName\' gänzlich aus dem System löschen.',
|
||||
'users_delete_confirm' => 'Sind Sie sicher, dass Sie diesen Benutzer löschen möchten?',
|
||||
'users_delete_success' => 'Benutzer erfolgreich gelöscht.',
|
||||
'users_edit' => 'Benutzer bearbeiten',
|
||||
'users_edit_profile' => 'Profil bearbeiten',
|
||||
'users_edit_success' => 'Benutzer erfolgreich aktualisisert',
|
||||
'users_avatar' => 'Benutzer-Bild',
|
||||
'users_avatar_desc' => 'Dieses Bild sollte einen Durchmesser von ca. 256px haben.',
|
||||
'users_preferred_language' => 'Bevorzugte Sprache',
|
||||
'users_social_accounts' => 'Social-Media Konten',
|
||||
'users_social_accounts_info' => 'Hier können Sie andere Social-Media Konten für eine schnellere und einfachere Anmeldung verknüpfen. Wenn Sie ein Social-Media Konto hier lösen, bleibt der Zugriff erhalteb. Entfernen Sie in diesem Falle die Berechtigung in Ihren Profil-Einstellungen des verknüpften Social-Media Kontos.',
|
||||
'users_social_connect' => 'Social-Media Konto verknüpfen',
|
||||
'users_social_disconnect' => 'Social-Media Kontoverknüpfung lösen',
|
||||
'users_social_connected' => ':socialAccount Konto wurde erfolgreich mit dem Profil verknüpft.',
|
||||
'users_social_disconnected' => ':socialAccount Konto wurde erfolgreich vom Profil gelöst.',
|
||||
];
|
||||
|
|
|
@ -18,6 +18,8 @@ return [
|
|||
*/
|
||||
'sign_up' => 'Sign up',
|
||||
'log_in' => 'Log in',
|
||||
'log_in_with' => 'Login with :socialDriver',
|
||||
'sign_up_with' => 'Sign up with :socialDriver',
|
||||
'logout' => 'Logout',
|
||||
|
||||
'name' => 'Name',
|
||||
|
|
|
@ -32,13 +32,12 @@
|
|||
|
||||
@if(count($socialDrivers) > 0)
|
||||
<hr class="margin-top">
|
||||
<h3 class="text-muted">{{ trans('auth.social_login') }}</h3>
|
||||
@if(isset($socialDrivers['google']))
|
||||
<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>
|
||||
@endif
|
||||
@if(isset($socialDrivers['github']))
|
||||
<a id="social-login-github" href="{{ baseUrl("/login/service/github") }}" style="color:#444;"><i class="zmdi zmdi-github zmdi-hc-4x"></i></a>
|
||||
@endif
|
||||
@foreach($socialDrivers as $driver => $name)
|
||||
<a id="social-login-{{$driver}}" class="button block muted-light svg text-left" href="{{ baseUrl("/login/service/" . $driver) }}">
|
||||
@icon($driver)
|
||||
{{ trans('auth.log_in_with', ['socialDriver' => $name]) }}
|
||||
</a>
|
||||
@endforeach
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -35,14 +35,12 @@
|
|||
|
||||
@if(count($socialDrivers) > 0)
|
||||
<hr class="margin-top">
|
||||
<h3 class="text-muted">{{ trans('auth.social_registration') }}</h3>
|
||||
<p class="text-small">{{ trans('auth.social_registration_text') }}</p>
|
||||
@if(isset($socialDrivers['google']))
|
||||
<a href="{{ baseUrl("/register/service/google") }}" style="color: #DC4E41;"><i class="zmdi zmdi-google-plus-box zmdi-hc-4x"></i></a>
|
||||
@endif
|
||||
@if(isset($socialDrivers['github']))
|
||||
<a href="{{ baseUrl("/register/service/github") }}" style="color:#444;"><i class="zmdi zmdi-github zmdi-hc-4x"></i></a>
|
||||
@endif
|
||||
@foreach($socialDrivers as $driver => $name)
|
||||
<a id="social-register-{{$driver}}" class="button block muted-light svg text-left" href="{{ baseUrl("/register/service/" . $driver) }}">
|
||||
@icon($driver)
|
||||
{{ trans('auth.sign_up_with', ['socialDriver' => $name]) }}
|
||||
</a>
|
||||
@endforeach
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -23,9 +23,10 @@
|
|||
|
||||
@include('partials/custom-styles')
|
||||
|
||||
<!-- Custom user content -->
|
||||
@if(setting('app-custom-head'))
|
||||
@if(setting('app-custom-head') && \Route::currentRouteName() !== 'settings')
|
||||
<!-- Custom user content -->
|
||||
{!! setting('app-custom-head') !!}
|
||||
<!-- End custom user content -->
|
||||
@endif
|
||||
</head>
|
||||
<body class="@yield('body-class')" ng-app="bookStack">
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
<title>{{ $book->name }}</title>
|
||||
|
||||
<style>
|
||||
@if (!app()->environment('testing'))
|
||||
{!! file_get_contents(public_path('/css/export-styles.css')) !!}
|
||||
@endif
|
||||
.page-break {
|
||||
page-break-after: always;
|
||||
}
|
||||
.chapter-hint {
|
||||
color: #888;
|
||||
margin-top: 32px;
|
||||
}
|
||||
.chapter-hint + h1 {
|
||||
margin-top: 0;
|
||||
}
|
||||
ul.contents ul li {
|
||||
list-style: circle;
|
||||
}
|
||||
@media screen {
|
||||
.page-break {
|
||||
border-top: 1px solid #DDD;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@yield('head')
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-8 col-md-offset-2">
|
||||
<div class="page-content">
|
||||
|
||||
<h1 style="font-size: 4.8em">{{$book->name}}</h1>
|
||||
|
||||
<p>{{ $book->description }}</p>
|
||||
|
||||
@if(count($bookChildren) > 0)
|
||||
<ul class="contents">
|
||||
@foreach($bookChildren as $bookChild)
|
||||
<li><a href="#{{$bookChild->getType()}}-{{$bookChild->id}}">{{ $bookChild->name }}</a></li>
|
||||
@if($bookChild->isA('chapter') && count($bookChild->pages) > 0)
|
||||
<ul>
|
||||
@foreach($bookChild->pages as $page)
|
||||
<li><a href="#page-{{$page->id}}">{{ $page->name }}</a></li>
|
||||
@endforeach
|
||||
</ul>
|
||||
@endif
|
||||
@endforeach
|
||||
</ul>
|
||||
@endif
|
||||
|
||||
@foreach($bookChildren as $bookChild)
|
||||
<div class="page-break"></div>
|
||||
<h1 id="{{$bookChild->getType()}}-{{$bookChild->id}}">{{ $bookChild->name }}</h1>
|
||||
@if($bookChild->isA('chapter'))
|
||||
<p>{{ $bookChild->description }}</p>
|
||||
@if(count($bookChild->pages) > 0)
|
||||
@foreach($bookChild->pages as $page)
|
||||
<div class="page-break"></div>
|
||||
<div class="chapter-hint">{{$bookChild->name}}</div>
|
||||
<h1 id="page-{{$page->id}}">{{ $page->name }}</h1>
|
||||
{!! $page->html !!}
|
||||
@endforeach
|
||||
@endif
|
||||
@else
|
||||
{!! $bookChild->html !!}
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -5,11 +5,19 @@
|
|||
<div class="faded-small toolbar">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6 faded">
|
||||
<div class="col-sm-6 faded">
|
||||
@include('books._breadcrumbs', ['book' => $book])
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="col-sm-6">
|
||||
<div class="action-buttons faded">
|
||||
<span dropdown class="dropdown-container">
|
||||
<div dropdown-toggle class="text-button text-primary"><i class="zmdi zmdi-open-in-new"></i>{{ trans('entities.pages_export') }}</div>
|
||||
<ul class="wide">
|
||||
<li><a href="{{ $book->getUrl('/export/html') }}" target="_blank">{{ trans('entities.pages_export_html') }} <span class="text-muted float right">.html</span></a></li>
|
||||
<li><a href="{{ $book->getUrl('/export/pdf') }}" target="_blank">{{ trans('entities.pages_export_pdf') }} <span class="text-muted float right">.pdf</span></a></li>
|
||||
<li><a href="{{ $book->getUrl('/export/plaintext') }}" target="_blank">{{ trans('entities.pages_export_text') }} <span class="text-muted float right">.txt</span></a></li>
|
||||
</ul>
|
||||
</span>
|
||||
@if(userCan('page-create', $book))
|
||||
<a href="{{ $book->getUrl('/page/create') }}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>{{ trans('entities.pages_new') }}</a>
|
||||
@endif
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
<title>{{ $chapter->name }}</title>
|
||||
|
||||
<style>
|
||||
@if (!app()->environment('testing'))
|
||||
{!! file_get_contents(public_path('/css/export-styles.css')) !!}
|
||||
@endif
|
||||
.page-break {
|
||||
page-break-after: always;
|
||||
}
|
||||
ul.contents ul li {
|
||||
list-style: circle;
|
||||
}
|
||||
@media screen {
|
||||
.page-break {
|
||||
border-top: 1px solid #DDD;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@yield('head')
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-8 col-md-offset-2">
|
||||
<div class="page-content">
|
||||
|
||||
<h1 style="font-size: 4.8em">{{$chapter->name}}</h1>
|
||||
|
||||
<p>{{ $chapter->description }}</p>
|
||||
|
||||
@if(count($pages) > 0)
|
||||
<ul class="contents">
|
||||
@foreach($pages as $page)
|
||||
<li><a href="#page-{{$page->id}}">{{ $page->name }}</a></li>
|
||||
@endforeach
|
||||
</ul>
|
||||
@endif
|
||||
|
||||
@foreach($pages as $page)
|
||||
<div class="page-break"></div>
|
||||
<h1 id="page-{{$page->id}}">{{ $page->name }}</h1>
|
||||
{!! $page->html !!}
|
||||
@endforeach
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -10,6 +10,14 @@
|
|||
</div>
|
||||
<div class="col-sm-4 faded">
|
||||
<div class="action-buttons">
|
||||
<span dropdown class="dropdown-container">
|
||||
<div dropdown-toggle class="text-button text-primary"><i class="zmdi zmdi-open-in-new"></i>{{ trans('entities.pages_export') }}</div>
|
||||
<ul class="wide">
|
||||
<li><a href="{{ $chapter->getUrl('/export/html') }}" target="_blank">{{ trans('entities.pages_export_html') }} <span class="text-muted float right">.html</span></a></li>
|
||||
<li><a href="{{ $chapter->getUrl('/export/pdf') }}" target="_blank">{{ trans('entities.pages_export_pdf') }} <span class="text-muted float right">.pdf</span></a></li>
|
||||
<li><a href="{{ $chapter->getUrl('/export/plaintext') }}" target="_blank">{{ trans('entities.pages_export_text') }} <span class="text-muted float right">.txt</span></a></li>
|
||||
</ul>
|
||||
</span>
|
||||
@if(userCan('page-create', $chapter))
|
||||
<a href="{{ $chapter->getUrl('/create-page') }}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>{{ trans('entities.pages_new') }}</a>
|
||||
@endif
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
<title>{{ $page->name }}</title>
|
||||
|
||||
<style>
|
||||
{!! $css !!}
|
||||
@if (!app()->environment('testing'))
|
||||
{!! file_get_contents(public_path('/css/export-styles.css')) !!}
|
||||
@endif
|
||||
</style>
|
||||
@yield('head')
|
||||
</head>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div ng-non-bindable>
|
||||
|
||||
<h1 id="bkmrk-page-title" class="float left">{{$page->name}}</h1>
|
||||
<h1 id="bkmrk-page-title">{{$page->name}}</h1>
|
||||
|
||||
<div style="clear:left;"></div>
|
||||
|
||||
|
|
|
@ -30,11 +30,5 @@
|
|||
clear: both;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tag-display {
|
||||
min-width: 0;
|
||||
max-width: none;
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
@stop
|
|
@ -30,7 +30,7 @@
|
|||
<header id="header">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="col-sm-6">
|
||||
|
||||
<a href="{{ baseUrl('/') }}" class="logo">
|
||||
@if(setting('app-logo', '') !== 'none')
|
||||
|
@ -41,7 +41,7 @@
|
|||
@endif
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="col-sm-6">
|
||||
<div class="float right">
|
||||
<div class="links text-center">
|
||||
@yield('header-buttons')
|
||||
|
|
|
@ -59,30 +59,18 @@
|
|||
<h3>{{ trans('settings.users_social_accounts') }}</h3>
|
||||
<p class="text-muted">{{ trans('settings.users_social_accounts_info') }}</p>
|
||||
<div class="row">
|
||||
@if(isset($activeSocialDrivers['google']))
|
||||
@foreach($activeSocialDrivers as $driver => $enabled)
|
||||
<div class="col-md-3 text-center">
|
||||
<div><i class="zmdi zmdi-google-plus-box zmdi-hc-4x" style="color: #DC4E41;"></i></div>
|
||||
<div>@icon($driver, ['width' => 56])</div>
|
||||
<div>
|
||||
@if($user->hasSocialAccount('google'))
|
||||
<a href="{{ baseUrl("/login/service/google/detach") }}" class="button neg">{{ trans('settings.users_social_disconnect') }}</a>
|
||||
@if($user->hasSocialAccount($driver))
|
||||
<a href="{{ baseUrl("/login/service/{$driver}/detach") }}" class="button neg">{{ trans('settings.users_social_disconnect') }}</a>
|
||||
@else
|
||||
<a href="{{ baseUrl("/login/service/google") }}" class="button pos">{{ trans('settings.users_social_connect') }}</a>
|
||||
<a href="{{ baseUrl("/login/service/{$driver}") }}" class="button pos">{{ trans('settings.users_social_connect') }}</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@if(isset($activeSocialDrivers['github']))
|
||||
<div class="col-md-3 text-center">
|
||||
<div><i class="zmdi zmdi-github zmdi-hc-4x" style="color: #444;"></i></div>
|
||||
<div>
|
||||
@if($user->hasSocialAccount('github'))
|
||||
<a href="{{ baseUrl("/login/service/github/detach") }}" class="button neg">{{ trans('settings.users_social_disconnect') }}</a>
|
||||
@else
|
||||
<a href="{{ baseUrl("/login/service/github") }}" class="button pos">{{ trans('settings.users_social_connect') }}</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
|
||||
|
|
|
@ -26,6 +26,9 @@ Route::group(['middleware' => 'auth'], function () {
|
|||
Route::get('/{slug}/delete', 'BookController@showDelete');
|
||||
Route::get('/{bookSlug}/sort', 'BookController@sort');
|
||||
Route::put('/{bookSlug}/sort', 'BookController@saveSort');
|
||||
Route::get('/{bookSlug}/export/html', 'BookController@exportHtml');
|
||||
Route::get('/{bookSlug}/export/pdf', 'BookController@exportPdf');
|
||||
Route::get('/{bookSlug}/export/plaintext', 'BookController@exportPlainText');
|
||||
|
||||
// Pages
|
||||
Route::get('/{bookSlug}/page/create', 'PageController@create');
|
||||
|
@ -64,6 +67,9 @@ Route::group(['middleware' => 'auth'], function () {
|
|||
Route::put('/{bookSlug}/chapter/{chapterSlug}/move', 'ChapterController@move');
|
||||
Route::get('/{bookSlug}/chapter/{chapterSlug}/edit', 'ChapterController@edit');
|
||||
Route::get('/{bookSlug}/chapter/{chapterSlug}/permissions', 'ChapterController@showRestrict');
|
||||
Route::get('/{bookSlug}/chapter/{chapterSlug}/export/pdf', 'ChapterController@exportPdf');
|
||||
Route::get('/{bookSlug}/chapter/{chapterSlug}/export/html', 'ChapterController@exportHtml');
|
||||
Route::get('/{bookSlug}/chapter/{chapterSlug}/export/plaintext', 'ChapterController@exportPlainText');
|
||||
Route::put('/{bookSlug}/chapter/{chapterSlug}/permissions', 'ChapterController@restrict');
|
||||
Route::get('/{bookSlug}/chapter/{chapterSlug}/delete', 'ChapterController@showDelete');
|
||||
Route::delete('/{bookSlug}/chapter/{chapterSlug}', 'ChapterController@destroy');
|
||||
|
@ -129,7 +135,7 @@ Route::group(['middleware' => 'auth'], function () {
|
|||
|
||||
// Settings
|
||||
Route::group(['prefix' => 'settings'], function() {
|
||||
Route::get('/', 'SettingController@index');
|
||||
Route::get('/', 'SettingController@index')->name('settings');
|
||||
Route::post('/', 'SettingController@update');
|
||||
|
||||
// Users
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
|
||||
use Illuminate\Foundation\Testing\WithoutMiddleware;
|
||||
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
|
||||
class ActivityTrackingTest extends TestCase
|
||||
class ActivityTrackingTest extends BrowserKitTest
|
||||
{
|
||||
|
||||
public function test_recently_viewed_books()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
|
||||
class AttachmentTest extends TestCase
|
||||
class AttachmentTest extends BrowserKitTest
|
||||
{
|
||||
/**
|
||||
* Get a test file that can be uploaded
|
||||
|
@ -75,7 +75,6 @@ class AttachmentTest extends TestCase
|
|||
{
|
||||
$page = \BookStack\Page::first();
|
||||
$this->asAdmin();
|
||||
$admin = $this->getAdmin();
|
||||
$fileName = 'upload_test_file.txt';
|
||||
|
||||
$this->uploadFile($fileName, $page->id);
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
|
||||
use BookStack\Notifications\ConfirmEmail;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
|
||||
class AuthTest extends TestCase
|
||||
class AuthTest extends BrowserKitTest
|
||||
{
|
||||
|
||||
public function test_auth_working()
|
||||
|
@ -88,7 +88,7 @@ class AuthTest extends TestCase
|
|||
->press('Resend Confirmation Email');
|
||||
|
||||
// Get confirmation and confirm notification matches
|
||||
$emailConfirmation = DB::table('email_confirmations')->where('user_id', '=', $dbUser->id)->first();
|
||||
$emailConfirmation = \DB::table('email_confirmations')->where('user_id', '=', $dbUser->id)->first();
|
||||
Notification::assertSentTo($dbUser, ConfirmEmail::class, function($notification, $channels) use ($emailConfirmation) {
|
||||
return $notification->token === $emailConfirmation->token;
|
||||
});
|
||||
|
@ -177,7 +177,7 @@ class AuthTest extends TestCase
|
|||
->seePageIs('/settings/users');
|
||||
|
||||
$userPassword = \BookStack\User::find($user->id)->password;
|
||||
$this->assertTrue(Hash::check('newpassword', $userPassword));
|
||||
$this->assertTrue(\Hash::check('newpassword', $userPassword));
|
||||
}
|
||||
|
||||
public function test_user_deletion()
|
||||
|
@ -220,6 +220,9 @@ class AuthTest extends TestCase
|
|||
|
||||
public function test_reset_password_flow()
|
||||
{
|
||||
|
||||
Notification::fake();
|
||||
|
||||
$this->visit('/login')->click('Forgot Password?')
|
||||
->seePageIs('/password/email')
|
||||
->type('admin@admin.com', 'email')
|
||||
|
@ -230,8 +233,12 @@ class AuthTest extends TestCase
|
|||
'email' => 'admin@admin.com'
|
||||
]);
|
||||
|
||||
$reset = DB::table('password_resets')->where('email', '=', 'admin@admin.com')->first();
|
||||
$this->visit('/password/reset/' . $reset->token)
|
||||
$user = \BookStack\User::where('email', '=', 'admin@admin.com')->first();
|
||||
|
||||
Notification::assertSentTo($user, \BookStack\Notifications\ResetPassword::class);
|
||||
$n = Notification::sent($user, \BookStack\Notifications\ResetPassword::class);
|
||||
|
||||
$this->visit('/password/reset/' . $n->first()->token)
|
||||
->see('Reset Password')
|
||||
->submitForm('Reset Password', [
|
||||
'email' => 'admin@admin.com',
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
<?php
|
||||
|
||||
use BookStack\Services\LdapService;
|
||||
<?php namespace Tests;
|
||||
use BookStack\User;
|
||||
|
||||
class LdapTest extends \TestCase
|
||||
class LdapTest extends BrowserKitTest
|
||||
{
|
||||
|
||||
protected $mockLdap;
|
||||
|
@ -14,7 +12,7 @@ class LdapTest extends \TestCase
|
|||
{
|
||||
parent::setUp();
|
||||
app('config')->set(['auth.method' => 'ldap', 'services.ldap.base_dn' => 'dc=ldap,dc=local', 'auth.providers.users.driver' => 'ldap']);
|
||||
$this->mockLdap = Mockery::mock(BookStack\Services\Ldap::class);
|
||||
$this->mockLdap = \Mockery::mock(\BookStack\Services\Ldap::class);
|
||||
$this->app['BookStack\Services\Ldap'] = $this->mockLdap;
|
||||
$this->mockUser = factory(User::class)->make();
|
||||
}
|
||||
|
@ -24,7 +22,7 @@ class LdapTest extends \TestCase
|
|||
$this->mockLdap->shouldReceive('connect')->once()->andReturn($this->resourceId);
|
||||
$this->mockLdap->shouldReceive('setVersion')->once();
|
||||
$this->mockLdap->shouldReceive('searchAndGetEntries')->times(4)
|
||||
->with($this->resourceId, config('services.ldap.base_dn'), Mockery::type('string'), Mockery::type('array'))
|
||||
->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
|
||||
->andReturn(['count' => 1, 0 => [
|
||||
'uid' => [$this->mockUser->name],
|
||||
'cn' => [$this->mockUser->name],
|
||||
|
@ -52,7 +50,7 @@ class LdapTest extends \TestCase
|
|||
$this->mockLdap->shouldReceive('setVersion')->once();
|
||||
$ldapDn = 'cn=test-user,dc=test' . config('services.ldap.base_dn');
|
||||
$this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
|
||||
->with($this->resourceId, config('services.ldap.base_dn'), Mockery::type('string'), Mockery::type('array'))
|
||||
->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
|
||||
->andReturn(['count' => 1, 0 => [
|
||||
'cn' => [$this->mockUser->name],
|
||||
'dn' => $ldapDn,
|
||||
|
@ -75,7 +73,7 @@ class LdapTest extends \TestCase
|
|||
$this->mockLdap->shouldReceive('connect')->once()->andReturn($this->resourceId);
|
||||
$this->mockLdap->shouldReceive('setVersion')->once();
|
||||
$this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
|
||||
->with($this->resourceId, config('services.ldap.base_dn'), Mockery::type('string'), Mockery::type('array'))
|
||||
->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
|
||||
->andReturn(['count' => 1, 0 => [
|
||||
'uid' => [$this->mockUser->name],
|
||||
'cn' => [$this->mockUser->name],
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
|
||||
class SocialAuthTest extends TestCase
|
||||
class SocialAuthTest extends BrowserKitTest
|
||||
{
|
||||
|
||||
public function test_social_registration()
|
||||
|
@ -11,10 +11,10 @@ class SocialAuthTest extends TestCase
|
|||
$this->setSettings(['registration-enabled' => 'true']);
|
||||
config(['GOOGLE_APP_ID' => 'abc123', 'GOOGLE_APP_SECRET' => '123abc', 'APP_URL' => 'http://localhost']);
|
||||
|
||||
$mockSocialite = Mockery::mock('Laravel\Socialite\Contracts\Factory');
|
||||
$mockSocialite = \Mockery::mock('Laravel\Socialite\Contracts\Factory');
|
||||
$this->app['Laravel\Socialite\Contracts\Factory'] = $mockSocialite;
|
||||
$mockSocialDriver = Mockery::mock('Laravel\Socialite\Contracts\Provider');
|
||||
$mockSocialUser = Mockery::mock('\Laravel\Socialite\Contracts\User');
|
||||
$mockSocialDriver = \Mockery::mock('Laravel\Socialite\Contracts\Provider');
|
||||
$mockSocialUser = \Mockery::mock('\Laravel\Socialite\Contracts\User');
|
||||
|
||||
$mockSocialite->shouldReceive('driver')->twice()->with('google')->andReturn($mockSocialDriver);
|
||||
$mockSocialDriver->shouldReceive('redirect')->once()->andReturn(redirect('/'));
|
||||
|
@ -34,18 +34,16 @@ class SocialAuthTest extends TestCase
|
|||
|
||||
public function test_social_login()
|
||||
{
|
||||
$user = factory(\BookStack\User::class)->make();
|
||||
|
||||
config([
|
||||
'GOOGLE_APP_ID' => 'abc123', 'GOOGLE_APP_SECRET' => '123abc',
|
||||
'GITHUB_APP_ID' => 'abc123', 'GITHUB_APP_SECRET' => '123abc',
|
||||
'APP_URL' => 'http://localhost'
|
||||
]);
|
||||
|
||||
$mockSocialite = Mockery::mock('Laravel\Socialite\Contracts\Factory');
|
||||
$mockSocialite = \Mockery::mock('Laravel\Socialite\Contracts\Factory');
|
||||
$this->app['Laravel\Socialite\Contracts\Factory'] = $mockSocialite;
|
||||
$mockSocialDriver = Mockery::mock('Laravel\Socialite\Contracts\Provider');
|
||||
$mockSocialUser = Mockery::mock('\Laravel\Socialite\Contracts\User');
|
||||
$mockSocialDriver = \Mockery::mock('Laravel\Socialite\Contracts\Provider');
|
||||
$mockSocialUser = \Mockery::mock('\Laravel\Socialite\Contracts\User');
|
||||
|
||||
$mockSocialUser->shouldReceive('getId')->twice()->andReturn('logintest123');
|
||||
|
||||
|
@ -68,7 +66,7 @@ class SocialAuthTest extends TestCase
|
|||
->seePageIs('/login');
|
||||
|
||||
// Test social callback with matching social account
|
||||
DB::table('social_accounts')->insert([
|
||||
\DB::table('social_accounts')->insert([
|
||||
'user_id' => $this->getAdmin()->id,
|
||||
'driver' => 'github',
|
||||
'driver_id' => 'logintest123'
|
||||
|
|
|
@ -0,0 +1,235 @@
|
|||
<?php namespace Tests;
|
||||
|
||||
use BookStack\Role;
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Laravel\BrowserKitTesting\TestCase;
|
||||
use Symfony\Component\DomCrawler\Crawler;
|
||||
|
||||
abstract class BrowserKitTest extends TestCase
|
||||
{
|
||||
|
||||
use DatabaseTransactions;
|
||||
|
||||
/**
|
||||
* The base URL to use while testing the application.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $baseUrl = 'http://localhost';
|
||||
|
||||
// Local user instances
|
||||
private $admin;
|
||||
private $editor;
|
||||
|
||||
/**
|
||||
* Creates the application.
|
||||
*
|
||||
* @return \Illuminate\Foundation\Application
|
||||
*/
|
||||
public function createApplication()
|
||||
{
|
||||
$app = require __DIR__.'/../bootstrap/app.php';
|
||||
|
||||
$app->make(Kernel::class)->bootstrap();
|
||||
|
||||
return $app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current user context to be an admin.
|
||||
* @return $this
|
||||
*/
|
||||
public function asAdmin()
|
||||
{
|
||||
return $this->actingAs($this->getAdmin());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current admin user.
|
||||
* @return mixed
|
||||
*/
|
||||
public function getAdmin() {
|
||||
if($this->admin === null) {
|
||||
$adminRole = Role::getSystemRole('admin');
|
||||
$this->admin = $adminRole->users->first();
|
||||
}
|
||||
return $this->admin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current editor context to be an editor.
|
||||
* @return $this
|
||||
*/
|
||||
public function asEditor()
|
||||
{
|
||||
if ($this->editor === null) {
|
||||
$this->editor = $this->getEditor();
|
||||
}
|
||||
return $this->actingAs($this->editor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a user that's not a system user such as the guest user.
|
||||
*/
|
||||
public function getNormalUser()
|
||||
{
|
||||
return \BookStack\User::where('system_name', '=', null)->get()->last();
|
||||
}
|
||||
|
||||
/**
|
||||
* Quickly sets an array of settings.
|
||||
* @param $settingsArray
|
||||
*/
|
||||
protected function setSettings($settingsArray)
|
||||
{
|
||||
$settings = app('BookStack\Services\SettingService');
|
||||
foreach ($settingsArray as $key => $value) {
|
||||
$settings->put($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a group of entities that belong to a specific user.
|
||||
* @param $creatorUser
|
||||
* @param $updaterUser
|
||||
* @return array
|
||||
*/
|
||||
protected function createEntityChainBelongingToUser($creatorUser, $updaterUser = false)
|
||||
{
|
||||
if ($updaterUser === false) $updaterUser = $creatorUser;
|
||||
$book = factory(\BookStack\Book::class)->create(['created_by' => $creatorUser->id, 'updated_by' => $updaterUser->id]);
|
||||
$chapter = factory(\BookStack\Chapter::class)->create(['created_by' => $creatorUser->id, 'updated_by' => $updaterUser->id]);
|
||||
$page = factory(\BookStack\Page::class)->create(['created_by' => $creatorUser->id, 'updated_by' => $updaterUser->id, 'book_id' => $book->id]);
|
||||
$book->chapters()->saveMany([$chapter]);
|
||||
$chapter->pages()->saveMany([$page]);
|
||||
$restrictionService = $this->app[\BookStack\Services\PermissionService::class];
|
||||
$restrictionService->buildJointPermissionsForEntity($book);
|
||||
return [
|
||||
'book' => $book,
|
||||
'chapter' => $chapter,
|
||||
'page' => $page
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Quick way to create a new user
|
||||
* @param array $attributes
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getEditor($attributes = [])
|
||||
{
|
||||
$user = factory(\BookStack\User::class)->create($attributes);
|
||||
$role = Role::getRole('editor');
|
||||
$user->attachRole($role);;
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Quick way to create a new user without any permissions
|
||||
* @param array $attributes
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getNewBlankUser($attributes = [])
|
||||
{
|
||||
$user = factory(\BookStack\User::class)->create($attributes);
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that a given string is seen inside an element.
|
||||
*
|
||||
* @param bool|string|null $element
|
||||
* @param integer $position
|
||||
* @param string $text
|
||||
* @param bool $negate
|
||||
* @return $this
|
||||
*/
|
||||
protected function seeInNthElement($element, $position, $text, $negate = false)
|
||||
{
|
||||
$method = $negate ? 'assertNotRegExp' : 'assertRegExp';
|
||||
|
||||
$rawPattern = preg_quote($text, '/');
|
||||
|
||||
$escapedPattern = preg_quote(e($text), '/');
|
||||
|
||||
$content = $this->crawler->filter($element)->eq($position)->html();
|
||||
|
||||
$pattern = $rawPattern == $escapedPattern
|
||||
? $rawPattern : "({$rawPattern}|{$escapedPattern})";
|
||||
|
||||
$this->$method("/$pattern/i", $content);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the current page matches a given URI.
|
||||
*
|
||||
* @param string $uri
|
||||
* @return $this
|
||||
*/
|
||||
protected function seePageUrlIs($uri)
|
||||
{
|
||||
$this->assertEquals(
|
||||
$uri, $this->currentUri, "Did not land on expected page [{$uri}].\n"
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do a forced visit that does not error out on exception.
|
||||
* @param string $uri
|
||||
* @param array $parameters
|
||||
* @param array $cookies
|
||||
* @param array $files
|
||||
* @return $this
|
||||
*/
|
||||
protected function forceVisit($uri, $parameters = [], $cookies = [], $files = [])
|
||||
{
|
||||
$method = 'GET';
|
||||
$uri = $this->prepareUrlForRequest($uri);
|
||||
$this->call($method, $uri, $parameters, $cookies, $files);
|
||||
$this->clearInputs()->followRedirects();
|
||||
$this->currentUri = $this->app->make('request')->fullUrl();
|
||||
$this->crawler = new Crawler($this->response->getContent(), $uri);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Click the text within the selected element.
|
||||
* @param $parentElement
|
||||
* @param $linkText
|
||||
* @return $this
|
||||
*/
|
||||
protected function clickInElement($parentElement, $linkText)
|
||||
{
|
||||
$elem = $this->crawler->filter($parentElement);
|
||||
$link = $elem->selectLink($linkText);
|
||||
$this->visit($link->link()->getUri());
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the page contains the given element.
|
||||
* @param string $selector
|
||||
*/
|
||||
protected function pageHasElement($selector)
|
||||
{
|
||||
$elements = $this->crawler->filter($selector);
|
||||
$this->assertTrue(count($elements) > 0, "The page does not contain an element matching " . $selector);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the page contains the given element.
|
||||
* @param string $selector
|
||||
*/
|
||||
protected function pageNotHasElement($selector)
|
||||
{
|
||||
$elements = $this->crawler->filter($selector);
|
||||
$this->assertFalse(count($elements) > 0, "The page contains " . count($elements) . " elements matching " . $selector);
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
<?php namespace Tests;
|
||||
|
||||
use BookStack\JointPermission;
|
||||
use BookStack\Page;
|
||||
use BookStack\Repos\EntityRepo;
|
||||
|
||||
class CommandsTest extends TestCase
|
||||
{
|
||||
|
||||
public function test_clear_views_command()
|
||||
{
|
||||
$this->asEditor();
|
||||
$page = Page::first();
|
||||
|
||||
$this->get($page->getUrl());
|
||||
|
||||
$this->assertDatabaseHas('views', [
|
||||
'user_id' => $this->getEditor()->id,
|
||||
'viewable_id' => $page->id,
|
||||
'views' => 1
|
||||
]);
|
||||
|
||||
$exitCode = \Artisan::call('bookstack:clear-views');
|
||||
$this->assertTrue($exitCode === 0, 'Command executed successfully');
|
||||
|
||||
$this->assertDatabaseMissing('views', [
|
||||
'user_id' => $this->getEditor()->id
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_clear_activity_command()
|
||||
{
|
||||
$this->asEditor();
|
||||
$page = Page::first();
|
||||
\Activity::add($page, 'page_update', $page->book->id);
|
||||
|
||||
$this->assertDatabaseHas('activities', [
|
||||
'key' => 'page_update',
|
||||
'entity_id' => $page->id,
|
||||
'user_id' => $this->getEditor()->id
|
||||
]);
|
||||
|
||||
$exitCode = \Artisan::call('bookstack:clear-activity');
|
||||
$this->assertTrue($exitCode === 0, 'Command executed successfully');
|
||||
|
||||
|
||||
$this->assertDatabaseMissing('activities', [
|
||||
'key' => 'page_update'
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_clear_revisions_command()
|
||||
{
|
||||
$this->asEditor();
|
||||
$entityRepo = $this->app[EntityRepo::class];
|
||||
$page = Page::first();
|
||||
$entityRepo->updatePage($page, $page->book_id, ['name' => 'updated page', 'html' => '<p>new content</p>', 'summary' => 'page revision testing']);
|
||||
$entityRepo->updatePageDraft($page, ['name' => 'updated page', 'html' => '<p>new content in draft</p>', 'summary' => 'page revision testing']);
|
||||
|
||||
$this->assertDatabaseHas('page_revisions', [
|
||||
'page_id' => $page->id,
|
||||
'type' => 'version'
|
||||
]);
|
||||
$this->assertDatabaseHas('page_revisions', [
|
||||
'page_id' => $page->id,
|
||||
'type' => 'update_draft'
|
||||
]);
|
||||
|
||||
$exitCode = \Artisan::call('bookstack:clear-revisions');
|
||||
$this->assertTrue($exitCode === 0, 'Command executed successfully');
|
||||
|
||||
$this->assertDatabaseMissing('page_revisions', [
|
||||
'page_id' => $page->id,
|
||||
'type' => 'version'
|
||||
]);
|
||||
$this->assertDatabaseHas('page_revisions', [
|
||||
'page_id' => $page->id,
|
||||
'type' => 'update_draft'
|
||||
]);
|
||||
|
||||
$exitCode = \Artisan::call('bookstack:clear-revisions', ['--all' => true]);
|
||||
$this->assertTrue($exitCode === 0, 'Command executed successfully');
|
||||
|
||||
$this->assertDatabaseMissing('page_revisions', [
|
||||
'page_id' => $page->id,
|
||||
'type' => 'update_draft'
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_regen_permissions_command()
|
||||
{
|
||||
JointPermission::query()->truncate();
|
||||
$page = Page::first();
|
||||
|
||||
$this->assertDatabaseMissing('joint_permissions', ['entity_id' => $page->id]);
|
||||
|
||||
$exitCode = \Artisan::call('bookstack:regenerate-permissions');
|
||||
$this->assertTrue($exitCode === 0, 'Command executed successfully');
|
||||
|
||||
$this->assertDatabaseHas('joint_permissions', ['entity_id' => $page->id]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php namespace Tests;
|
||||
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
|
||||
trait CreatesApplication
|
||||
{
|
||||
/**
|
||||
* Creates the application.
|
||||
*
|
||||
* @return \Illuminate\Foundation\Application
|
||||
*/
|
||||
public function createApplication()
|
||||
{
|
||||
$app = require __DIR__.'/../bootstrap/app.php';
|
||||
$app->make(Kernel::class)->bootstrap();
|
||||
return $app;
|
||||
}
|
||||
}
|
|
@ -1,8 +1,6 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class EntitySearchTest extends TestCase
|
||||
class EntitySearchTest extends BrowserKitTest
|
||||
{
|
||||
|
||||
public function test_page_search()
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class EntityTest extends TestCase
|
||||
class EntityTest extends BrowserKitTest
|
||||
{
|
||||
|
||||
public function test_entity_creation()
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
<?php namespace Tests;
|
||||
|
||||
|
||||
use BookStack\Chapter;
|
||||
use BookStack\Page;
|
||||
|
||||
class ExportTest extends TestCase
|
||||
{
|
||||
|
||||
public function test_page_text_export()
|
||||
{
|
||||
$page = Page::first();
|
||||
$this->asEditor();
|
||||
|
||||
$resp = $this->get($page->getUrl('/export/plaintext'));
|
||||
$resp->assertStatus(200);
|
||||
$resp->assertSee($page->name);
|
||||
$resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.txt');
|
||||
}
|
||||
|
||||
public function test_page_pdf_export()
|
||||
{
|
||||
$page = Page::first();
|
||||
$this->asEditor();
|
||||
|
||||
$resp = $this->get($page->getUrl('/export/pdf'));
|
||||
$resp->assertStatus(200);
|
||||
$resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.pdf');
|
||||
}
|
||||
|
||||
public function test_page_html_export()
|
||||
{
|
||||
$page = Page::first();
|
||||
$this->asEditor();
|
||||
|
||||
$resp = $this->get($page->getUrl('/export/html'));
|
||||
$resp->assertStatus(200);
|
||||
$resp->assertSee($page->name);
|
||||
$resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.html');
|
||||
}
|
||||
|
||||
public function test_book_text_export()
|
||||
{
|
||||
$page = Page::first();
|
||||
$book = $page->book;
|
||||
$this->asEditor();
|
||||
|
||||
$resp = $this->get($book->getUrl('/export/plaintext'));
|
||||
$resp->assertStatus(200);
|
||||
$resp->assertSee($book->name);
|
||||
$resp->assertSee($page->name);
|
||||
$resp->assertHeader('Content-Disposition', 'attachment; filename="' . $book->slug . '.txt');
|
||||
}
|
||||
|
||||
public function test_book_pdf_export()
|
||||
{
|
||||
$page = Page::first();
|
||||
$book = $page->book;
|
||||
$this->asEditor();
|
||||
|
||||
$resp = $this->get($book->getUrl('/export/pdf'));
|
||||
$resp->assertStatus(200);
|
||||
$resp->assertHeader('Content-Disposition', 'attachment; filename="' . $book->slug . '.pdf');
|
||||
}
|
||||
|
||||
public function test_book_html_export()
|
||||
{
|
||||
$page = Page::first();
|
||||
$book = $page->book;
|
||||
$this->asEditor();
|
||||
|
||||
$resp = $this->get($book->getUrl('/export/html'));
|
||||
$resp->assertStatus(200);
|
||||
$resp->assertSee($book->name);
|
||||
$resp->assertSee($page->name);
|
||||
$resp->assertHeader('Content-Disposition', 'attachment; filename="' . $book->slug . '.html');
|
||||
}
|
||||
|
||||
public function test_chapter_text_export()
|
||||
{
|
||||
$chapter = Chapter::first();
|
||||
$page = $chapter->pages[0];
|
||||
$this->asEditor();
|
||||
|
||||
$resp = $this->get($chapter->getUrl('/export/plaintext'));
|
||||
$resp->assertStatus(200);
|
||||
$resp->assertSee($chapter->name);
|
||||
$resp->assertSee($page->name);
|
||||
$resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.txt');
|
||||
}
|
||||
|
||||
public function test_chapter_pdf_export()
|
||||
{
|
||||
$chapter = Chapter::first();
|
||||
$this->asEditor();
|
||||
|
||||
$resp = $this->get($chapter->getUrl('/export/pdf'));
|
||||
$resp->assertStatus(200);
|
||||
$resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.pdf');
|
||||
}
|
||||
|
||||
public function test_chapter_html_export()
|
||||
{
|
||||
$chapter = Chapter::first();
|
||||
$page = $chapter->pages[0];
|
||||
$this->asEditor();
|
||||
|
||||
$resp = $this->get($chapter->getUrl('/export/html'));
|
||||
$resp->assertStatus(200);
|
||||
$resp->assertSee($chapter->name);
|
||||
$resp->assertSee($page->name);
|
||||
$resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.html');
|
||||
}
|
||||
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
|
||||
|
||||
class MarkdownTest extends TestCase
|
||||
class MarkdownTest extends BrowserKitTest
|
||||
{
|
||||
protected $page;
|
||||
|
||||
|
|
|
@ -1,33 +1,56 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
|
||||
use BookStack\Page;
|
||||
use BookStack\Repos\EntityRepo;
|
||||
|
||||
class PageContentTest extends TestCase
|
||||
{
|
||||
|
||||
public function test_page_includes()
|
||||
{
|
||||
$page = \BookStack\Page::first();
|
||||
$secondPage = \BookStack\Page::all()->get(2);
|
||||
$page = Page::first();
|
||||
$secondPage = Page::all()->get(2);
|
||||
|
||||
$secondPage->html = "<p id='section1'>Hello, This is a test</p><p id='section2'>This is a second block of content</p>";
|
||||
$secondPage->save();
|
||||
|
||||
$this->asAdmin()->visit($page->getUrl())
|
||||
->dontSee('Hello, This is a test');
|
||||
$this->asEditor();
|
||||
|
||||
$pageContent = $this->get($page->getUrl());
|
||||
$pageContent->assertDontSee('Hello, This is a test');
|
||||
|
||||
$originalHtml = $page->html;
|
||||
$page->html .= "{{@{$secondPage->id}}}";
|
||||
$page->save();
|
||||
|
||||
$this->asAdmin()->visit($page->getUrl())
|
||||
->see('Hello, This is a test')
|
||||
->see('This is a second block of content');
|
||||
$pageContent = $this->get($page->getUrl());
|
||||
$pageContent->assertSee('Hello, This is a test');
|
||||
$pageContent->assertSee('This is a second block of content');
|
||||
|
||||
$page->html = $originalHtml . " Well {{@{$secondPage->id}#section2}}";
|
||||
$page->save();
|
||||
|
||||
$this->asAdmin()->visit($page->getUrl())
|
||||
->dontSee('Hello, This is a test')
|
||||
->see('Well This is a second block of content');
|
||||
$pageContent = $this->get($page->getUrl());
|
||||
$pageContent->assertDontSee('Hello, This is a test');
|
||||
$pageContent->assertSee('Well This is a second block of content');
|
||||
}
|
||||
|
||||
public function test_page_revision_views_viewable()
|
||||
{
|
||||
$this->asEditor();
|
||||
|
||||
$entityRepo = $this->app[EntityRepo::class];
|
||||
$page = Page::first();
|
||||
$entityRepo->updatePage($page, $page->book_id, ['name' => 'updated page', 'html' => '<p>new content</p>', 'summary' => 'page revision testing']);
|
||||
$pageRevision = $page->revisions->last();
|
||||
|
||||
$revisionView = $this->get($page->getUrl() . '/revisions/' . $pageRevision->id);
|
||||
$revisionView->assertStatus(200);
|
||||
$revisionView->assertSee('new content');
|
||||
|
||||
$revisionView = $this->get($page->getUrl() . '/revisions/' . $pageRevision->id . '/changes');
|
||||
$revisionView->assertStatus(200);
|
||||
$revisionView->assertSee('new content');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
|
||||
|
||||
class PageDraftTest extends TestCase
|
||||
class PageDraftTest extends BrowserKitTest
|
||||
{
|
||||
protected $page;
|
||||
protected $entityRepo;
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
|
||||
use BookStack\Book;
|
||||
use BookStack\Page;
|
||||
use BookStack\Repos\EntityRepo;
|
||||
|
||||
class SortTest extends TestCase
|
||||
{
|
||||
|
@ -13,13 +17,14 @@ class SortTest extends TestCase
|
|||
public function test_drafts_do_not_show_up()
|
||||
{
|
||||
$this->asAdmin();
|
||||
$entityRepo = app('\BookStack\Repos\EntityRepo');
|
||||
$entityRepo = app(EntityRepo::class);
|
||||
$draft = $entityRepo->getDraftPage($this->book);
|
||||
|
||||
$this->visit($this->book->getUrl())
|
||||
->see($draft->name)
|
||||
->visit($this->book->getUrl() . '/sort')
|
||||
->dontSee($draft->name);
|
||||
$resp = $this->get($this->book->getUrl());
|
||||
$resp->assertSee($draft->name);
|
||||
|
||||
$resp = $this->get($this->book->getUrl() . '/sort');
|
||||
$resp->assertDontSee($draft->name);
|
||||
}
|
||||
|
||||
public function test_page_move()
|
||||
|
@ -27,17 +32,21 @@ class SortTest extends TestCase
|
|||
$page = \BookStack\Page::first();
|
||||
$currentBook = $page->book;
|
||||
$newBook = \BookStack\Book::where('id', '!=', $currentBook->id)->first();
|
||||
$this->asAdmin()->visit($page->getUrl() . '/move')
|
||||
->see('Move Page')
|
||||
->type('book:' . $newBook->id, 'entity_selection')->press('Move Page');
|
||||
|
||||
$resp = $this->asAdmin()->get($page->getUrl() . '/move');
|
||||
$resp->assertSee('Move Page');
|
||||
|
||||
$movePageResp = $this->put($page->getUrl() . '/move', [
|
||||
'entity_selection' => 'book:' . $newBook->id
|
||||
]);
|
||||
$page = \BookStack\Page::find($page->id);
|
||||
$this->seePageIs($page->getUrl());
|
||||
|
||||
$movePageResp->assertRedirect($page->getUrl());
|
||||
$this->assertTrue($page->book->id == $newBook->id, 'Page book is now the new book');
|
||||
|
||||
$this->visit($newBook->getUrl())
|
||||
->seeInNthElement('.activity-list-item', 0, 'moved page')
|
||||
->seeInNthElement('.activity-list-item', 0, $page->name);
|
||||
$newBookResp = $this->get($newBook->getUrl());
|
||||
$newBookResp->assertSee('moved page');
|
||||
$newBookResp->assertSee($page->name);
|
||||
}
|
||||
|
||||
public function test_chapter_move()
|
||||
|
@ -47,22 +56,68 @@ class SortTest extends TestCase
|
|||
$pageToCheck = $chapter->pages->first();
|
||||
$newBook = \BookStack\Book::where('id', '!=', $currentBook->id)->first();
|
||||
|
||||
$this->asAdmin()->visit($chapter->getUrl() . '/move')
|
||||
->see('Move Chapter')
|
||||
->type('book:' . $newBook->id, 'entity_selection')->press('Move Chapter');
|
||||
$chapterMoveResp = $this->asAdmin()->get($chapter->getUrl() . '/move');
|
||||
$chapterMoveResp->assertSee('Move Chapter');
|
||||
|
||||
$moveChapterResp = $this->put($chapter->getUrl() . '/move', [
|
||||
'entity_selection' => 'book:' . $newBook->id
|
||||
]);
|
||||
|
||||
$chapter = \BookStack\Chapter::find($chapter->id);
|
||||
$this->seePageIs($chapter->getUrl());
|
||||
$moveChapterResp->assertRedirect($chapter->getUrl());
|
||||
$this->assertTrue($chapter->book->id === $newBook->id, 'Chapter Book is now the new book');
|
||||
|
||||
$this->visit($newBook->getUrl())
|
||||
->seeInNthElement('.activity-list-item', 0, 'moved chapter')
|
||||
->seeInNthElement('.activity-list-item', 0, $chapter->name);
|
||||
$newBookResp = $this->get($newBook->getUrl());
|
||||
$newBookResp->assertSee('moved chapter');
|
||||
$newBookResp->assertSee($chapter->name);
|
||||
|
||||
$pageToCheck = \BookStack\Page::find($pageToCheck->id);
|
||||
$this->assertTrue($pageToCheck->book_id === $newBook->id, 'Chapter child page\'s book id has changed to the new book');
|
||||
$this->visit($pageToCheck->getUrl())
|
||||
->see($newBook->name);
|
||||
$pageCheckResp = $this->get($pageToCheck->getUrl());
|
||||
$pageCheckResp->assertSee($newBook->name);
|
||||
}
|
||||
|
||||
public function test_book_sort()
|
||||
{
|
||||
$oldBook = Book::query()->first();
|
||||
$chapterToMove = $this->newChapter(['name' => 'chapter to move'], $oldBook);
|
||||
$newBook = $this->newBook(['name' => 'New sort book']);
|
||||
$pagesToMove = Page::query()->take(5)->get();
|
||||
|
||||
// Create request data
|
||||
$reqData = [
|
||||
[
|
||||
'id' => $chapterToMove->id,
|
||||
'sort' => 0,
|
||||
'parentChapter' => false,
|
||||
'type' => 'chapter',
|
||||
'book' => $newBook->id
|
||||
]
|
||||
];
|
||||
foreach ($pagesToMove as $index => $page) {
|
||||
$reqData[] = [
|
||||
'id' => $page->id,
|
||||
'sort' => $index,
|
||||
'parentChapter' => $index === count($pagesToMove) - 1 ? $chapterToMove->id : false,
|
||||
'type' => 'page',
|
||||
'book' => $newBook->id
|
||||
];
|
||||
}
|
||||
|
||||
$sortResp = $this->asAdmin()->put($newBook->getUrl() . '/sort', ['sort-tree' => json_encode($reqData)]);
|
||||
$sortResp->assertRedirect($newBook->getUrl());
|
||||
$sortResp->assertStatus(302);
|
||||
$this->assertDatabaseHas('chapters', [
|
||||
'id' => $chapterToMove->id,
|
||||
'book_id' => $newBook->id,
|
||||
'priority' => 0
|
||||
]);
|
||||
$this->assertTrue($newBook->chapters()->count() === 1);
|
||||
$this->assertTrue($newBook->chapters()->first()->pages()->count() === 1);
|
||||
|
||||
$checkPage = $pagesToMove[1];
|
||||
$checkResp = $this->get(Page::find($checkPage->id)->getUrl());
|
||||
$checkResp->assertSee($newBook->name);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
<?php namespace Entity;
|
||||
<?php namespace Tests;
|
||||
|
||||
use BookStack\Tag;
|
||||
use BookStack\Page;
|
||||
use BookStack\Services\PermissionService;
|
||||
|
||||
class TagTest extends \TestCase
|
||||
class TagTest extends BrowserKitTest
|
||||
{
|
||||
|
||||
protected $defaultTagCount = 20;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
|
||||
class ImageTest extends TestCase
|
||||
class ImageTest extends BrowserKitTest
|
||||
{
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
|
||||
class RestrictionsTest extends TestCase
|
||||
class RestrictionsTest extends BrowserKitTest
|
||||
{
|
||||
protected $user;
|
||||
protected $viewer;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
|
||||
class RolesTest extends TestCase
|
||||
class RolesTest extends BrowserKitTest
|
||||
{
|
||||
protected $user;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
|
||||
class PublicActionTest extends TestCase
|
||||
class PublicActionTest extends BrowserKitTest
|
||||
{
|
||||
|
||||
public function test_app_not_public()
|
||||
|
@ -84,7 +84,7 @@ class PublicActionTest extends TestCase
|
|||
{
|
||||
$page = \BookStack\Page::first();
|
||||
$this->asAdmin()->visit($page->getUrl());
|
||||
Auth::logout();
|
||||
\Auth::logout();
|
||||
view()->share('pageTitle', '');
|
||||
$this->forceVisit('/cats/dogs/hippos');
|
||||
$this->dontSee($page->name);
|
||||
|
|
|
@ -1,37 +1,19 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
|
||||
use BookStack\Book;
|
||||
use BookStack\Chapter;
|
||||
use BookStack\Repos\EntityRepo;
|
||||
use BookStack\Role;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Symfony\Component\DomCrawler\Crawler;
|
||||
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
|
||||
|
||||
class TestCase extends Illuminate\Foundation\Testing\TestCase
|
||||
abstract class TestCase extends BaseTestCase
|
||||
{
|
||||
|
||||
use CreatesApplication;
|
||||
use DatabaseTransactions;
|
||||
|
||||
/**
|
||||
* The base URL to use while testing the application.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $baseUrl = 'http://localhost';
|
||||
|
||||
// Local user instances
|
||||
private $admin;
|
||||
private $editor;
|
||||
|
||||
/**
|
||||
* Creates the application.
|
||||
*
|
||||
* @return \Illuminate\Foundation\Application
|
||||
*/
|
||||
public function createApplication()
|
||||
{
|
||||
$app = require __DIR__.'/../bootstrap/app.php';
|
||||
|
||||
$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
|
||||
|
||||
return $app;
|
||||
}
|
||||
protected $admin;
|
||||
protected $editor;
|
||||
|
||||
/**
|
||||
* Set the current user context to be an admin.
|
||||
|
@ -48,187 +30,50 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase
|
|||
*/
|
||||
public function getAdmin() {
|
||||
if($this->admin === null) {
|
||||
$adminRole = \BookStack\Role::getRole('admin');
|
||||
$adminRole = Role::getSystemRole('admin');
|
||||
$this->admin = $adminRole->users->first();
|
||||
}
|
||||
return $this->admin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current editor context to be an editor.
|
||||
* Set the current user context to be an editor.
|
||||
* @return $this
|
||||
*/
|
||||
public function asEditor()
|
||||
{
|
||||
if ($this->editor === null) {
|
||||
$this->editor = $this->getEditor();
|
||||
}
|
||||
return $this->actingAs($this->editor);
|
||||
return $this->actingAs($this->getEditor());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a user that's not a system user such as the guest user.
|
||||
*/
|
||||
public function getNormalUser()
|
||||
{
|
||||
return \BookStack\User::where('system_name', '=', null)->get()->last();
|
||||
}
|
||||
|
||||
/**
|
||||
* Quickly sets an array of settings.
|
||||
* @param $settingsArray
|
||||
*/
|
||||
protected function setSettings($settingsArray)
|
||||
{
|
||||
$settings = app('BookStack\Services\SettingService');
|
||||
foreach ($settingsArray as $key => $value) {
|
||||
$settings->put($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a group of entities that belong to a specific user.
|
||||
* @param $creatorUser
|
||||
* @param $updaterUser
|
||||
* @return array
|
||||
*/
|
||||
protected function createEntityChainBelongingToUser($creatorUser, $updaterUser = false)
|
||||
{
|
||||
if ($updaterUser === false) $updaterUser = $creatorUser;
|
||||
$book = factory(BookStack\Book::class)->create(['created_by' => $creatorUser->id, 'updated_by' => $updaterUser->id]);
|
||||
$chapter = factory(BookStack\Chapter::class)->create(['created_by' => $creatorUser->id, 'updated_by' => $updaterUser->id]);
|
||||
$page = factory(BookStack\Page::class)->create(['created_by' => $creatorUser->id, 'updated_by' => $updaterUser->id, 'book_id' => $book->id]);
|
||||
$book->chapters()->saveMany([$chapter]);
|
||||
$chapter->pages()->saveMany([$page]);
|
||||
$restrictionService = $this->app[\BookStack\Services\PermissionService::class];
|
||||
$restrictionService->buildJointPermissionsForEntity($book);
|
||||
return [
|
||||
'book' => $book,
|
||||
'chapter' => $chapter,
|
||||
'page' => $page
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Quick way to create a new user
|
||||
* @param array $attributes
|
||||
* Get a editor user.
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getEditor($attributes = [])
|
||||
{
|
||||
$user = factory(\BookStack\User::class)->create($attributes);
|
||||
$role = \BookStack\Role::getRole('editor');
|
||||
$user->attachRole($role);;
|
||||
return $user;
|
||||
public function getEditor() {
|
||||
if($this->editor === null) {
|
||||
$editorRole = Role::getRole('editor');
|
||||
$this->editor = $editorRole->users->first();
|
||||
}
|
||||
return $this->editor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Quick way to create a new user without any permissions
|
||||
* @param array $attributes
|
||||
* @return mixed
|
||||
* Create and return a new book.
|
||||
* @param array $input
|
||||
* @return Book
|
||||
*/
|
||||
protected function getNewBlankUser($attributes = [])
|
||||
{
|
||||
$user = factory(\BookStack\User::class)->create($attributes);
|
||||
return $user;
|
||||
public function newBook($input = ['name' => 'test book', 'description' => 'My new test book']) {
|
||||
return $this->app[EntityRepo::class]->createFromInput('book', $input, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that a given string is seen inside an element.
|
||||
*
|
||||
* @param bool|string|null $element
|
||||
* @param integer $position
|
||||
* @param string $text
|
||||
* @param bool $negate
|
||||
* @return $this
|
||||
* Create and return a new test chapter
|
||||
* @param array $input
|
||||
* @param Book $book
|
||||
* @return Chapter
|
||||
*/
|
||||
protected function seeInNthElement($element, $position, $text, $negate = false)
|
||||
{
|
||||
$method = $negate ? 'assertNotRegExp' : 'assertRegExp';
|
||||
|
||||
$rawPattern = preg_quote($text, '/');
|
||||
|
||||
$escapedPattern = preg_quote(e($text), '/');
|
||||
|
||||
$content = $this->crawler->filter($element)->eq($position)->html();
|
||||
|
||||
$pattern = $rawPattern == $escapedPattern
|
||||
? $rawPattern : "({$rawPattern}|{$escapedPattern})";
|
||||
|
||||
$this->$method("/$pattern/i", $content);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the current page matches a given URI.
|
||||
*
|
||||
* @param string $uri
|
||||
* @return $this
|
||||
*/
|
||||
protected function seePageUrlIs($uri)
|
||||
{
|
||||
$this->assertEquals(
|
||||
$uri, $this->currentUri, "Did not land on expected page [{$uri}].\n"
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do a forced visit that does not error out on exception.
|
||||
* @param string $uri
|
||||
* @param array $parameters
|
||||
* @param array $cookies
|
||||
* @param array $files
|
||||
* @return $this
|
||||
*/
|
||||
protected function forceVisit($uri, $parameters = [], $cookies = [], $files = [])
|
||||
{
|
||||
$method = 'GET';
|
||||
$uri = $this->prepareUrlForRequest($uri);
|
||||
$this->call($method, $uri, $parameters, $cookies, $files);
|
||||
$this->clearInputs()->followRedirects();
|
||||
$this->currentUri = $this->app->make('request')->fullUrl();
|
||||
$this->crawler = new Crawler($this->response->getContent(), $uri);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Click the text within the selected element.
|
||||
* @param $parentElement
|
||||
* @param $linkText
|
||||
* @return $this
|
||||
*/
|
||||
protected function clickInElement($parentElement, $linkText)
|
||||
{
|
||||
$elem = $this->crawler->filter($parentElement);
|
||||
$link = $elem->selectLink($linkText);
|
||||
$this->visit($link->link()->getUri());
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the page contains the given element.
|
||||
* @param string $selector
|
||||
* @return bool
|
||||
*/
|
||||
protected function pageHasElement($selector)
|
||||
{
|
||||
$elements = $this->crawler->filter($selector);
|
||||
$this->assertTrue(count($elements) > 0, "The page does not contain an element matching " . $selector);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the page contains the given element.
|
||||
* @param string $selector
|
||||
* @return bool
|
||||
*/
|
||||
protected function pageNotHasElement($selector)
|
||||
{
|
||||
$elements = $this->crawler->filter($selector);
|
||||
$this->assertFalse(count($elements) > 0, "The page contains " . count($elements) . " elements matching " . $selector);
|
||||
return $this;
|
||||
public function newChapter($input = ['name' => 'test chapter', 'description' => 'My new test chapter'], Book $book) {
|
||||
return $this->app[EntityRepo::class]->createFromInput('chapter', $input, $book);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
|
||||
class UserProfileTest extends TestCase
|
||||
class UserProfileTest extends BrowserKitTest
|
||||
{
|
||||
protected $user;
|
||||
|
||||
|
@ -55,8 +55,8 @@ class UserProfileTest extends TestCase
|
|||
$newUser = $this->getEditor();
|
||||
$this->actingAs($newUser);
|
||||
$entities = $this->createEntityChainBelongingToUser($newUser, $newUser);
|
||||
Activity::add($entities['book'], 'book_update', $entities['book']->id);
|
||||
Activity::add($entities['page'], 'page_create', $entities['book']->id);
|
||||
\Activity::add($entities['book'], 'book_update', $entities['book']->id);
|
||||
\Activity::add($entities['page'], 'page_create', $entities['book']->id);
|
||||
|
||||
$this->asAdmin()->visit('/user/' . $newUser->id)
|
||||
->seeInElement('#recent-activity', 'updated book')
|
||||
|
@ -69,8 +69,8 @@ class UserProfileTest extends TestCase
|
|||
$newUser = $this->getEditor();
|
||||
$this->actingAs($newUser);
|
||||
$entities = $this->createEntityChainBelongingToUser($newUser, $newUser);
|
||||
Activity::add($entities['book'], 'book_update', $entities['book']->id);
|
||||
Activity::add($entities['page'], 'page_create', $entities['book']->id);
|
||||
\Activity::add($entities['book'], 'book_update', $entities['book']->id);
|
||||
\Activity::add($entities['page'], 'page_create', $entities['book']->id);
|
||||
|
||||
$this->asAdmin()->visit('/')->clickInElement('#recent-activity', $newUser->name)
|
||||
->seePageIs('/user/' . $newUser->id)
|
||||
|
|
Loading…
Reference in New Issue