Compare commits

..

123 Commits

Author SHA1 Message Date
Dan Brown
04a364dcc3 Incremented version for v0.24.1 2018-09-24 16:34:16 +01:00
Dan Brown
db83ac7eaa Merge branch 'master' into release 2018-09-24 16:32:30 +01:00
Dan Brown
3ca9dddf61 Merge branch 'master' into release 2018-09-24 15:59:39 +01:00
Dan Brown
bf74f53ca7 Updated assets for release and incremented version 2018-09-24 12:18:27 +01:00
Dan Brown
9d67efb4a4 Merge branch 'master' into release 2018-09-24 12:08:21 +01:00
Dan Brown
3a39b9f440 Merge pull request #1022 from BookStackApp/revert-983-master
Revert "Update german translation"
2018-09-22 18:33:29 +01:00
Dan Brown
27f7aab375 Revert "Update german translation" 2018-09-22 18:33:15 +01:00
Dan Brown
337da0c467 Merge pull request #983 from vriic/master
Update german translation
2018-09-22 18:27:04 +01:00
Nikolai Nikolajevic
f56b3560c4 Update german translation 2018-08-23 16:17:46 +02:00
Dan Brown
02dfe11ce6 Increment version for release v0.23.2 2018-08-19 15:33:23 +01:00
Dan Brown
83d06beb70 Merge branch 'master' into release 2018-08-19 15:33:10 +01:00
Dan Brown
a8cfc059c8 Updated version for release v0.23.1 2018-08-12 14:22:53 +01:00
Dan Brown
1614b2bab0 Merge branch 'master' into release 2018-08-12 14:22:17 +01:00
Dan Brown
4bdec0d214 Updated version and assets for release v0.23 2018-07-29 20:28:49 +01:00
Dan Brown
6a7d7e7c2b Merge branch 'master' into release 2018-07-29 20:26:00 +01:00
Dan Brown
30d4674657 Updated assets for release v0.22 2018-05-28 14:19:14 +01:00
Dan Brown
9f961f95f8 Merge branch 'master' into release 2018-05-28 14:19:04 +01:00
Dan Brown
bab99a26ec Updated assets and version for v0.21 release 2018-04-22 20:21:22 +01:00
Dan Brown
9a7fecd269 Merge branch 'master' into release 2018-04-22 20:19:02 +01:00
Dan Brown
a8dc0d449b Updated the version because i'm such a plonker
And forgot to do this last release.
I wonder if there's a simple commit hook that could prevent the same two
versions twice in a row?
2018-03-30 15:41:46 +01:00
Dan Brown
a0381f76bf Merge branch 'v0.20' into release 2018-03-30 15:33:23 +01:00
Dan Brown
6102f66daa Updated assets for release v0.20.1 2018-03-25 16:58:14 +01:00
Dan Brown
c6134d162d Merge branch 'master' into release 2018-03-25 16:54:48 +01:00
Dan Brown
2046f9b9de Updated assets for release v0.20.0 2018-02-11 18:20:17 +00:00
Dan Brown
ac3ba594a4 Merge branch 'master' into release and updated version 2018-02-11 18:19:38 +00:00
Dan Brown
22df25a480 Updated assets and version for v0.19.0 2017-12-10 18:21:07 +00:00
Dan Brown
8b30c7f02e Merge branch 'master' into release 2017-12-10 18:19:20 +00:00
Dan Brown
757cdddc7c Updated version and JS for release v0.18.5 2017-11-11 18:33:04 +00:00
Dan Brown
df95e99680 Updated assets and version for release v0.18.4 2017-10-15 19:28:29 +01:00
Dan Brown
5a6d544db7 Merge branch 'master' into release 2017-10-15 19:27:50 +01:00
Dan Brown
16117d329c Merge branch 'master' into release, Updated version 2017-10-06 21:05:45 +01:00
Dan Brown
e90da18ada Updated assets and version for v0.18.2 release 2017-10-01 18:12:59 +01:00
Dan Brown
a08d80e1cc Merge branch 'master' into release 2017-10-01 18:12:07 +01:00
Dan Brown
6258175922 Updated assets and version for v0.18.1 release 2017-09-20 21:36:17 +01:00
Dan Brown
15736777a0 Merge branch 'master' into release 2017-09-20 21:35:33 +01:00
Dan Brown
75915e8a94 Updated assets for release v0.18 2017-09-10 17:07:57 +01:00
Dan Brown
9bde0ae4ea Merge branch 'master' into release 2017-09-10 17:05:05 +01:00
Dan Brown
0c802d1f86 Updated assets and version for release v0.17.4 2017-07-28 13:04:21 +01:00
Dan Brown
b7a96c6466 Merge branch 'master' into release 2017-07-28 13:03:36 +01:00
Dan Brown
4b645a82c7 Updated version for release 2017-07-22 17:27:01 +01:00
Dan Brown
d599b77b6f Merge branch 'master' into release 2017-07-22 17:26:44 +01:00
Dan Brown
26e93dc8c1 Updated assets and version for release v0.17.2 2017-07-22 16:49:07 +01:00
Dan Brown
a4c9a8491b Merge branch 'master' into release 2017-07-22 16:46:57 +01:00
Dan Brown
70ee636d87 Updated css and version for release 2017-07-10 20:52:32 +01:00
Dan Brown
b35f6dbb03 Merge branch 'master' into release 2017-07-10 20:51:25 +01:00
Dan Brown
67d9e24d8f Merge branch 'master' into release
Also updated assets, Version number
2017-07-02 22:52:26 +01:00
Dan Brown
3903fda6ca Incremented version 2017-06-04 15:38:49 +01:00
Dan Brown
441e46ebaa Merge branch 'v0.16' into release 2017-06-04 15:38:29 +01:00
Dan Brown
1f4260f359 Updated version for release v0.16.2 2017-05-07 19:35:51 +01:00
Dan Brown
dc0bf8ad4e Merge branch 'master' into release 2017-05-07 19:35:34 +01:00
Dan Brown
102e326e6a Updated JS and version for release v0.16.1 2017-04-30 19:51:23 +01:00
Dan Brown
2b25bf6f3b Merge branch 'master' into release 2017-04-30 19:50:29 +01:00
Dan Brown
f93280696d Updated assets for release v0.16 2017-04-23 20:42:28 +01:00
Dan Brown
1787391b07 Merge branch 'master' into release 2017-04-23 20:41:45 +01:00
Dan Brown
a74a8ee483 Updated version for v0.15.3 2017-03-23 22:22:16 +00:00
Dan Brown
7fa5405cb7 Merge branch 'master' into release 2017-03-23 22:21:04 +00:00
Dan Brown
6725ddcc41 Updated version for release v0.15.2 2017-03-05 15:50:52 +00:00
Dan Brown
bce941db3f Merge branch 'master' into release 2017-03-05 15:49:47 +00:00
Dan Brown
6d926048ec Updated to version v0.15.1 2017-02-27 16:59:10 +00:00
Dan Brown
5335c973b4 Merge branch 'master' into release 2017-02-27 16:58:20 +00:00
Dan Brown
15c3e5c96e Updated assets for release v0.15 2017-02-27 14:58:02 +00:00
Dan Brown
a5d5904969 Merge branch 'master' into release 2017-02-27 14:57:38 +00:00
Dan Brown
598758b991 Updated version for v0.14.3 2017-02-05 21:23:27 +00:00
Dan Brown
9926e23bc8 Merge branch 'v0.14' into release 2017-02-05 21:21:54 +00:00
Dan Brown
5d3264bc63 Updated assets for release v0.14.2 2017-02-01 22:27:04 +00:00
Dan Brown
d71f819f95 Merge branch 'v0.14' into release 2017-02-01 22:22:38 +00:00
Dan Brown
ee13509760 Updated version number 2017-01-23 22:28:31 +00:00
Dan Brown
82d7bb1f32 Merge branch 'master' into release 2017-01-23 22:28:02 +00:00
Dan Brown
cdfda508d8 Updated assets for release v0.14 2017-01-22 12:36:10 +00:00
Dan Brown
da941e584f Merge branch 'master' into release ready for v0.14 2017-01-22 12:31:27 +00:00
Dan Brown
65874d7b96 Updated assets for release v0.13.1 2016-11-27 19:42:33 +00:00
Dan Brown
ac9b8f405c Merge fixes from master for release v0.13.1 2016-11-27 19:41:12 +00:00
Dan Brown
8d1419a12e Update assets and version for release v0.13 2016-11-13 12:29:52 +00:00
Dan Brown
04f7a7d301 Merge branch 'master' into release 2016-11-13 12:26:56 +00:00
Dan Brown
c10d2a1493 Updated assets for release v0.12.2 2016-10-30 13:19:19 +00:00
Dan Brown
97bbf79ffd Merge branch 'v0.12' into release 2016-10-30 13:18:23 +00:00
Dan Brown
f7b01ae53d Updated assets for release v0.12.1 2016-09-06 20:50:15 +01:00
Dan Brown
d704e1dbba Merge branch 'master' into release 2016-09-06 20:49:15 +01:00
Dan Brown
ef2ff5e093 Updated assets for release v0.12 2016-09-05 19:49:42 +01:00
Dan Brown
7caed3b0db Merge branch 'master' into release 2016-09-05 19:35:21 +01:00
Dan Brown
45641d0754 Updated assets for release v0.11.2 2016-08-21 14:56:29 +01:00
Dan Brown
4b1d08ba99 Merge branch 'v0.11' into release 2016-08-21 14:55:11 +01:00
Dan Brown
160fa99ba4 Updated assets for release v0.11.1 2016-08-14 12:40:55 +01:00
Dan Brown
d2a5ab49ed Merge branch 'v0.11' into release 2016-08-14 12:37:48 +01:00
Dan Brown
c6404d8917 Updated assets for release v0.11 2016-07-03 10:56:16 +01:00
Dan Brown
7113807f12 Merge branch 'master' into release 2016-07-03 10:52:04 +01:00
Dan Brown
be711215e8 Updated assets for release v0.10 2016-05-22 15:12:47 +01:00
Dan Brown
7e3b404240 Merge branch 'master' into release for version v0.10 2016-05-22 15:11:50 +01:00
Dan Brown
e86901ca20 Updated assets for release v0.9.3 2016-05-03 21:13:02 +01:00
Dan Brown
bdfa61c8b2 Merge branch 'v0.9' into release 2016-05-03 21:11:01 +01:00
Dan Brown
2cc36787f5 Updated assets for release 0.9.2 2016-04-15 19:57:02 +01:00
Dan Brown
448ac61b48 Merge branch 'master' into release 2016-04-15 19:52:59 +01:00
Dan Brown
753f6394f7 Merge branch 'master' into release 2016-04-12 20:09:14 +01:00
Dan Brown
b1faf65934 Updated assets for release 0.9.0 2016-04-09 15:49:02 +01:00
Dan Brown
09f478bd74 Merge branch 'master' into release 2016-04-09 15:47:14 +01:00
Dan Brown
a0497feddd Updated assets for release 0.8.2 2016-03-30 21:44:30 +01:00
Dan Brown
789693bde9 Merge branch 'v0.8' into release 2016-03-30 21:32:46 +01:00
Dan Brown
1fe933e4ea Merge branch 'master' into release 2016-03-13 15:38:06 +00:00
Dan Brown
724b4b5a70 Updated assets for release 0.8.0 2016-03-13 15:15:14 +00:00
Dan Brown
1778a56146 Merge branch 'master' into release 2016-03-13 15:13:23 +00:00
Dan Brown
744865fcb2 Updated assets for release 0.7.6 2016-03-06 13:28:44 +00:00
Dan Brown
7f8c8b448d Merged branch master into release 2016-03-06 13:26:29 +00:00
Dan Brown
a67c53826d Updated assets for release 0.7.5 2016-02-25 21:24:09 +00:00
Dan Brown
14b131e850 Merge branch 'master' into release 2016-02-25 21:23:06 +00:00
Dan Brown
9b55a52b85 Updated assets for release 0.7.4 2016-02-11 22:35:01 +00:00
Dan Brown
db1d10e80f Merge branch 'master' into release 2016-02-11 22:29:29 +00:00
Dan Brown
1be576966f Updated assets for release 0.7.3 2016-02-08 20:47:33 +00:00
Dan Brown
b97e792c5f Merge branch 'master' into release 2016-02-08 20:45:48 +00:00
Dan Brown
8dec674cc3 Merge branch 'master' into release 2016-02-02 07:35:20 +00:00
Dan Brown
f784c03746 Merge branch 'master' into release 2016-02-01 18:31:04 +00:00
Dan Brown
148e172fe8 Updated assets for release 0.7 2016-01-31 18:03:55 +00:00
Dan Brown
56ae86646f Merge branch 'master' into release 2016-01-31 18:01:25 +00:00
Dan Brown
1d2b6fdfa2 Add updated assets 2016-01-02 14:50:59 +00:00
Dan Brown
4fc75beed4 Merge branch 'master' into release 2016-01-02 14:49:05 +00:00
Dan Brown
3b3bc0c4bf Updated compiled assets 2015-12-31 17:26:22 +00:00
Dan Brown
910faab88e Merge branch 'master' into release 2015-12-31 17:22:03 +00:00
Dan Brown
f184d763ad Added build folder to release 2015-12-16 17:53:53 +00:00
Dan Brown
a91d42634d Merge branch 'master' into release 2015-12-16 17:29:34 +00:00
Dan Brown
f517ef3616 Added new asset structure 2015-12-16 17:27:53 +00:00
Dan Brown
e99507ddcf Merge branch 'master' into release 2015-12-16 17:21:21 +00:00
Dan Brown
d2cacf1945 Release update 2015-12-01 21:30:21 +00:00
Dan Brown
448ac1405b Merge branch 'master' into release 2015-12-01 21:15:08 +00:00
Dan Brown
6ad21ce885 Added built assets for release 2015-11-30 21:59:34 +00:00
279 changed files with 14802 additions and 10103 deletions

View File

@@ -48,7 +48,6 @@ GITHUB_APP_ID=false
GITHUB_APP_SECRET=false
GOOGLE_APP_ID=false
GOOGLE_APP_SECRET=false
GOOGLE_SELECT_ACCOUNT=false
OKTA_BASE_URL=false
OKTA_APP_ID=false
OKTA_APP_SECRET=false
@@ -60,13 +59,8 @@ GITLAB_BASE_URI=false
DISCORD_APP_ID=false
DISCORD_APP_SECRET=false
# Disable default services such as Gravatar and Draw.IO
# External services such as Gravatar and Draw.IO
DISABLE_EXTERNAL_SERVICES=false
# Use custom avatar service, Sets fetch URL
# Possible placeholders: ${hash} ${size} ${email}
# If set, Avatars will be fetched regardless of DISABLE_EXTERNAL_SERVICES option.
# AVATAR_URL=https://seccdn.libravatar.org/avatar/${hash}?s=${size}&d=identicon
# LDAP Settings
LDAP_SERVER=false
@@ -82,8 +76,6 @@ LDAP_GROUP_ATTRIBUTE="memberOf"
# Would you like to remove users from roles on BookStack if they do not match on LDAP
# If false, the ldap groups-roles sync will only add users to roles
LDAP_REMOVE_FROM_GROUPS=false
# Set this option to disable LDAPS Certificate Verification
LDAP_TLS_INSECURE=false
# Mail settings
MAIL_DRIVER=smtp

6
.gitignore vendored
View File

@@ -5,10 +5,10 @@ Homestead.yaml
.idea
npm-debug.log
yarn-error.log
/public/dist
/public/dist/*.map
/public/plugins
/public/css
/public/js
/public/css/*.map
/public/js/*.map
/public/bower
/public/build/
/storage/images

View File

@@ -1,9 +1,6 @@
<?php
namespace BookStack\Actions;
use BookStack\Auth\User;
use BookStack\Model;
namespace BookStack;
/**
* @property string key

View File

@@ -1,7 +1,4 @@
<?php namespace BookStack\Uploads;
use BookStack\Entities\Page;
use BookStack\Ownable;
<?php namespace BookStack;
class Attachment extends Ownable
{
@@ -21,7 +18,7 @@ class Attachment extends Ownable
/**
* Get the page this file was uploaded to.
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
* @return Page
*/
public function page()
{

View File

@@ -1,6 +1,4 @@
<?php namespace BookStack\Entities;
use BookStack\Uploads\Image;
<?php namespace BookStack;
class Book extends Entity
{
@@ -8,15 +6,6 @@ class Book extends Entity
protected $fillable = ['name', 'description', 'image_id'];
/**
* Get the morph class for this model.
* @return string
*/
public function getMorphClass()
{
return 'BookStack\\Book';
}
/**
* Get the url for this book.
* @param string|bool $path

View File

@@ -1,6 +1,4 @@
<?php namespace BookStack\Entities;
use BookStack\Uploads\Image;
<?php namespace BookStack;
class Bookshelf extends Entity
{
@@ -10,15 +8,6 @@ class Bookshelf extends Entity
protected $fillable = ['name', 'description', 'image_id'];
/**
* Get the morph class for this model.
* @return string
*/
public function getMorphClass()
{
return 'BookStack\\Bookshelf';
}
/**
* Get the books in this shelf.
* Should not be used directly since does not take into account permissions.

View File

@@ -1,4 +1,4 @@
<?php namespace BookStack\Entities;
<?php namespace BookStack;
class Chapter extends Entity
{
@@ -6,15 +6,6 @@ class Chapter extends Entity
protected $fillable = ['name', 'description', 'priority', 'book_id'];
/**
* Get the morph class for this model.
* @return string
*/
public function getMorphClass()
{
return 'BookStack\\Chapter';
}
/**
* Get the book this chapter is within.
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo

View File

@@ -1,6 +1,4 @@
<?php namespace BookStack\Actions;
use BookStack\Ownable;
<?php namespace BookStack;
class Comment extends Ownable
{

View File

@@ -2,7 +2,7 @@
namespace BookStack\Console\Commands;
use BookStack\Uploads\ImageService;
use BookStack\Services\ImageService;
use Illuminate\Console\Command;
use Symfony\Component\Console\Output\OutputInterface;
@@ -30,7 +30,7 @@ class CleanupImages extends Command
/**
* Create a new command instance.
* @param \BookStack\Uploads\ImageService $imageService
* @param ImageService $imageService
*/
public function __construct(ImageService $imageService)
{

View File

@@ -2,7 +2,7 @@
namespace BookStack\Console\Commands;
use BookStack\Actions\Activity;
use BookStack\Activity;
use Illuminate\Console\Command;
class ClearActivity extends Command

View File

@@ -2,7 +2,7 @@
namespace BookStack\Console\Commands;
use BookStack\Entities\PageRevision;
use BookStack\PageRevision;
use Illuminate\Console\Command;
class ClearRevisions extends Command

View File

@@ -2,7 +2,7 @@
namespace BookStack\Console\Commands;
use BookStack\Auth\UserRepo;
use BookStack\Repos\UserRepo;
use Illuminate\Console\Command;
class CreateAdmin extends Command
@@ -76,7 +76,7 @@ class CreateAdmin extends Command
$user = $this->userRepo->create(['email' => $email, 'name' => $name, 'password' => $password]);
$this->userRepo->attachSystemRole($user, 'admin');
$this->userRepo->downloadAndAssignUserAvatar($user);
$this->userRepo->downloadGravatarToUserAvatar($user);
$user->email_confirmed = true;
$user->save();

View File

@@ -2,8 +2,8 @@
namespace BookStack\Console\Commands;
use BookStack\Auth\User;
use BookStack\Auth\UserRepo;
use BookStack\User;
use BookStack\Repos\UserRepo;
use Illuminate\Console\Command;
class DeleteUsers extends Command

View File

@@ -2,7 +2,7 @@
namespace BookStack\Console\Commands;
use BookStack\Auth\Permissions\PermissionService;
use BookStack\Services\PermissionService;
use Illuminate\Console\Command;
class RegeneratePermissions extends Command
@@ -31,7 +31,7 @@ class RegeneratePermissions extends Command
/**
* Create a new command instance.
*
* @param \BookStack\Auth\\BookStack\Auth\Permissions\PermissionService $permissionService
* @param PermissionService $permissionService
*/
public function __construct(PermissionService $permissionService)
{

View File

@@ -2,7 +2,7 @@
namespace BookStack\Console\Commands;
use BookStack\Entities\SearchService;
use BookStack\Services\SearchService;
use Illuminate\Console\Command;
class RegenerateSearch extends Command
@@ -26,7 +26,7 @@ class RegenerateSearch extends Command
/**
* Create a new command instance.
*
* @param \BookStack\Entities\SearchService $searchService
* @param SearchService $searchService
*/
public function __construct(SearchService $searchService)
{

View File

@@ -1,89 +0,0 @@
<?php namespace BookStack\Entities;
/**
* Class EntityProvider
*
* Provides access to the core entity models.
* Wrapped up in this provider since they are often used together
* so this is a neater alternative to injecting all in individually.
*
* @package BookStack\Entities
*/
class EntityProvider
{
/**
* @var Bookshelf
*/
public $bookshelf;
/**
* @var Book
*/
public $book;
/**
* @var Chapter
*/
public $chapter;
/**
* @var Page
*/
public $page;
/**
* @var PageRevision
*/
public $pageRevision;
/**
* EntityProvider constructor.
* @param Bookshelf $bookshelf
* @param Book $book
* @param Chapter $chapter
* @param Page $page
* @param PageRevision $pageRevision
*/
public function __construct(
Bookshelf $bookshelf,
Book $book,
Chapter $chapter,
Page $page,
PageRevision $pageRevision
) {
$this->bookshelf = $bookshelf;
$this->book = $book;
$this->chapter = $chapter;
$this->page = $page;
$this->pageRevision = $pageRevision;
}
/**
* Fetch all core entity types as an associated array
* with their basic names as the keys.
* @return Entity[]
*/
public function all()
{
return [
'bookshelf' => $this->bookshelf,
'book' => $this->book,
'chapter' => $this->chapter,
'page' => $this->page,
];
}
/**
* Get an entity instance by it's basic name.
* @param string $type
* @return Entity
*/
public function get(string $type)
{
$type = strtolower($type);
return $this->all()[$type];
}
}

View File

@@ -1,508 +0,0 @@
<?php namespace BookStack\Entities\Repos;
use BookStack\Entities\Book;
use BookStack\Entities\Chapter;
use BookStack\Entities\Entity;
use BookStack\Entities\Page;
use BookStack\Entities\PageRevision;
use Carbon\Carbon;
use DOMDocument;
use DOMXPath;
class PageRepo extends EntityRepo
{
/**
* Get page by slug.
* @param string $pageSlug
* @param string $bookSlug
* @return Page
* @throws \BookStack\Exceptions\NotFoundException
*/
public function getPageBySlug(string $pageSlug, string $bookSlug)
{
return $this->getBySlug('page', $pageSlug, $bookSlug);
}
/**
* Search through page revisions and retrieve the last page in the
* current book that has a slug equal to the one given.
* @param string $pageSlug
* @param string $bookSlug
* @return null|Page
*/
public function getPageByOldSlug(string $pageSlug, string $bookSlug)
{
$revision = $this->entityProvider->pageRevision->where('slug', '=', $pageSlug)
->whereHas('page', function ($query) {
$this->permissionService->enforceEntityRestrictions('page', $query);
})
->where('type', '=', 'version')
->where('book_slug', '=', $bookSlug)
->orderBy('created_at', 'desc')
->with('page')->first();
return $revision !== null ? $revision->page : null;
}
/**
* Updates a page with any fillable data and saves it into the database.
* @param Page $page
* @param int $book_id
* @param array $input
* @return Page
* @throws \Exception
*/
public function updatePage(Page $page, int $book_id, array $input)
{
// Hold the old details to compare later
$oldHtml = $page->html;
$oldName = $page->name;
// Prevent slug being updated if no name change
if ($page->name !== $input['name']) {
$page->slug = $this->findSuitableSlug('page', $input['name'], $page->id, $book_id);
}
// Save page tags if present
if (isset($input['tags'])) {
$this->tagRepo->saveTagsToEntity($page, $input['tags']);
}
// Update with new details
$userId = user()->id;
$page->fill($input);
$page->html = $this->formatHtml($input['html']);
$page->text = $this->pageToPlainText($page);
if (setting('app-editor') !== 'markdown') {
$page->markdown = '';
}
$page->updated_by = $userId;
$page->revision_count++;
$page->save();
// Remove all update drafts for this user & page.
$this->userUpdatePageDraftsQuery($page, $userId)->delete();
// Save a revision after updating
if ($oldHtml !== $input['html'] || $oldName !== $input['name'] || $input['summary'] !== null) {
$this->savePageRevision($page, $input['summary']);
}
$this->searchService->indexEntity($page);
return $page;
}
/**
* Saves a page revision into the system.
* @param Page $page
* @param null|string $summary
* @return PageRevision
* @throws \Exception
*/
public function savePageRevision(Page $page, string $summary = null)
{
$revision = $this->entityProvider->pageRevision->newInstance($page->toArray());
if (setting('app-editor') !== 'markdown') {
$revision->markdown = '';
}
$revision->page_id = $page->id;
$revision->slug = $page->slug;
$revision->book_slug = $page->book->slug;
$revision->created_by = user()->id;
$revision->created_at = $page->updated_at;
$revision->type = 'version';
$revision->summary = $summary;
$revision->revision_number = $page->revision_count;
$revision->save();
$revisionLimit = config('app.revision_limit');
if ($revisionLimit !== false) {
$revisionsToDelete = $this->entityProvider->pageRevision->where('page_id', '=', $page->id)
->orderBy('created_at', 'desc')->skip(intval($revisionLimit))->take(10)->get(['id']);
if ($revisionsToDelete->count() > 0) {
$this->entityProvider->pageRevision->whereIn('id', $revisionsToDelete->pluck('id'))->delete();
}
}
return $revision;
}
/**
* Formats a page's html to be tagged correctly
* within the system.
* @param string $htmlText
* @return string
*/
protected function formatHtml(string $htmlText)
{
if ($htmlText == '') {
return $htmlText;
}
libxml_use_internal_errors(true);
$doc = new DOMDocument();
$doc->loadHTML(mb_convert_encoding($htmlText, 'HTML-ENTITIES', 'UTF-8'));
$container = $doc->documentElement;
$body = $container->childNodes->item(0);
$childNodes = $body->childNodes;
// Ensure no duplicate ids are used
$idArray = [];
foreach ($childNodes as $index => $childNode) {
/** @var \DOMElement $childNode */
if (get_class($childNode) !== 'DOMElement') {
continue;
}
// Overwrite id if not a BookStack custom id
if ($childNode->hasAttribute('id')) {
$id = $childNode->getAttribute('id');
if (strpos($id, 'bkmrk') === 0 && array_search($id, $idArray) === false) {
$idArray[] = $id;
continue;
};
}
// Create an unique id for the element
// Uses the content as a basis to ensure output is the same every time
// the same content is passed through.
$contentId = 'bkmrk-' . substr(strtolower(preg_replace('/\s+/', '-', trim($childNode->nodeValue))), 0, 20);
$newId = urlencode($contentId);
$loopIndex = 0;
while (in_array($newId, $idArray)) {
$newId = urlencode($contentId . '-' . $loopIndex);
$loopIndex++;
}
$childNode->setAttribute('id', $newId);
$idArray[] = $newId;
}
// Generate inner html as a string
$html = '';
foreach ($childNodes as $childNode) {
$html .= $doc->saveHTML($childNode);
}
return $html;
}
/**
* Get the plain text version of a page's content.
* @param \BookStack\Entities\Page $page
* @return string
*/
public function pageToPlainText(Page $page)
{
$html = $this->renderPage($page);
return strip_tags($html);
}
/**
* Get a new draft page instance.
* @param Book $book
* @param Chapter|null $chapter
* @return \BookStack\Entities\Page
* @throws \Throwable
*/
public function getDraftPage(Book $book, Chapter $chapter = null)
{
$page = $this->entityProvider->page->newInstance();
$page->name = trans('entities.pages_initial_name');
$page->created_by = user()->id;
$page->updated_by = user()->id;
$page->draft = true;
if ($chapter) {
$page->chapter_id = $chapter->id;
}
$book->pages()->save($page);
$page = $this->entityProvider->page->find($page->id);
$this->permissionService->buildJointPermissionsForEntity($page);
return $page;
}
/**
* Save a page update draft.
* @param Page $page
* @param array $data
* @return PageRevision|Page
*/
public function updatePageDraft(Page $page, array $data = [])
{
// If the page itself is a draft simply update that
if ($page->draft) {
$page->fill($data);
if (isset($data['html'])) {
$page->text = $this->pageToPlainText($page);
}
$page->save();
return $page;
}
// Otherwise save the data to a revision
$userId = user()->id;
$drafts = $this->userUpdatePageDraftsQuery($page, $userId)->get();
if ($drafts->count() > 0) {
$draft = $drafts->first();
} else {
$draft = $this->entityProvider->pageRevision->newInstance();
$draft->page_id = $page->id;
$draft->slug = $page->slug;
$draft->book_slug = $page->book->slug;
$draft->created_by = $userId;
$draft->type = 'update_draft';
}
$draft->fill($data);
if (setting('app-editor') !== 'markdown') {
$draft->markdown = '';
}
$draft->save();
return $draft;
}
/**
* Publish a draft page to make it a normal page.
* Sets the slug and updates the content.
* @param Page $draftPage
* @param array $input
* @return Page
* @throws \Exception
*/
public function publishPageDraft(Page $draftPage, array $input)
{
$draftPage->fill($input);
// Save page tags if present
if (isset($input['tags'])) {
$this->tagRepo->saveTagsToEntity($draftPage, $input['tags']);
}
$draftPage->slug = $this->findSuitableSlug('page', $draftPage->name, false, $draftPage->book->id);
$draftPage->html = $this->formatHtml($input['html']);
$draftPage->text = $this->pageToPlainText($draftPage);
$draftPage->draft = false;
$draftPage->revision_count = 1;
$draftPage->save();
$this->savePageRevision($draftPage, trans('entities.pages_initial_revision'));
$this->searchService->indexEntity($draftPage);
return $draftPage;
}
/**
* The base query for getting user update drafts.
* @param Page $page
* @param $userId
* @return mixed
*/
protected function userUpdatePageDraftsQuery(Page $page, int $userId)
{
return $this->entityProvider->pageRevision->where('created_by', '=', $userId)
->where('type', 'update_draft')
->where('page_id', '=', $page->id)
->orderBy('created_at', 'desc');
}
/**
* Get the latest updated draft revision for a particular page and user.
* @param Page $page
* @param $userId
* @return PageRevision|null
*/
public function getUserPageDraft(Page $page, int $userId)
{
return $this->userUpdatePageDraftsQuery($page, $userId)->first();
}
/**
* Get the notification message that informs the user that they are editing a draft page.
* @param PageRevision $draft
* @return string
*/
public function getUserPageDraftMessage(PageRevision $draft)
{
$message = trans('entities.pages_editing_draft_notification', ['timeDiff' => $draft->updated_at->diffForHumans()]);
if ($draft->page->updated_at->timestamp <= $draft->updated_at->timestamp) {
return $message;
}
return $message . "\n" . trans('entities.pages_draft_edited_notification');
}
/**
* A query to check for active update drafts on a particular page.
* @param Page $page
* @param int $minRange
* @return mixed
*/
protected function activePageEditingQuery(Page $page, int $minRange = null)
{
$query = $this->entityProvider->pageRevision->where('type', '=', 'update_draft')
->where('page_id', '=', $page->id)
->where('updated_at', '>', $page->updated_at)
->where('created_by', '!=', user()->id)
->with('createdBy');
if ($minRange !== null) {
$query = $query->where('updated_at', '>=', Carbon::now()->subMinutes($minRange));
}
return $query;
}
/**
* Check if a page is being actively editing.
* Checks for edits since last page updated.
* Passing in a minuted range will check for edits
* within the last x minutes.
* @param Page $page
* @param int $minRange
* @return bool
*/
public function isPageEditingActive(Page $page, int $minRange = null)
{
$draftSearch = $this->activePageEditingQuery($page, $minRange);
return $draftSearch->count() > 0;
}
/**
* Get a notification message concerning the editing activity on a particular page.
* @param Page $page
* @param int $minRange
* @return string
*/
public function getPageEditingActiveMessage(Page $page, int $minRange = null)
{
$pageDraftEdits = $this->activePageEditingQuery($page, $minRange)->get();
$userMessage = $pageDraftEdits->count() > 1 ? trans('entities.pages_draft_edit_active.start_a', ['count' => $pageDraftEdits->count()]): trans('entities.pages_draft_edit_active.start_b', ['userName' => $pageDraftEdits->first()->createdBy->name]);
$timeMessage = $minRange === null ? trans('entities.pages_draft_edit_active.time_a') : trans('entities.pages_draft_edit_active.time_b', ['minCount'=>$minRange]);
return trans('entities.pages_draft_edit_active.message', ['start' => $userMessage, 'time' => $timeMessage]);
}
/**
* Parse the headers on the page to get a navigation menu
* @param string $pageContent
* @return array
*/
public function getPageNav(string $pageContent)
{
if ($pageContent == '') {
return [];
}
libxml_use_internal_errors(true);
$doc = new DOMDocument();
$doc->loadHTML(mb_convert_encoding($pageContent, 'HTML-ENTITIES', 'UTF-8'));
$xPath = new DOMXPath($doc);
$headers = $xPath->query("//h1|//h2|//h3|//h4|//h5|//h6");
if (is_null($headers)) {
return [];
}
$tree = collect([]);
foreach ($headers as $header) {
$text = $header->nodeValue;
$tree->push([
'nodeName' => strtolower($header->nodeName),
'level' => intval(str_replace('h', '', $header->nodeName)),
'link' => '#' . $header->getAttribute('id'),
'text' => strlen($text) > 30 ? substr($text, 0, 27) . '...' : $text
]);
}
// Normalise headers if only smaller headers have been used
if (count($tree) > 0) {
$minLevel = $tree->pluck('level')->min();
$tree = $tree->map(function ($header) use ($minLevel) {
$header['level'] -= ($minLevel - 2);
return $header;
});
}
return $tree->toArray();
}
/**
* Restores a revision's content back into a page.
* @param Page $page
* @param Book $book
* @param int $revisionId
* @return Page
* @throws \Exception
*/
public function restorePageRevision(Page $page, Book $book, int $revisionId)
{
$page->revision_count++;
$this->savePageRevision($page);
$revision = $page->revisions()->where('id', '=', $revisionId)->first();
$page->fill($revision->toArray());
$page->slug = $this->findSuitableSlug('page', $page->name, $page->id, $book->id);
$page->text = $this->pageToPlainText($page);
$page->updated_by = user()->id;
$page->save();
$this->searchService->indexEntity($page);
return $page;
}
/**
* Change the page's parent to the given entity.
* @param Page $page
* @param Entity $parent
* @throws \Throwable
*/
public function changePageParent(Page $page, Entity $parent)
{
$book = $parent->isA('book') ? $parent : $parent->book;
$page->chapter_id = $parent->isA('chapter') ? $parent->id : 0;
$page->save();
if ($page->book->id !== $book->id) {
$page = $this->changeBook('page', $book->id, $page);
}
$page->load('book');
$this->permissionService->buildJointPermissionsForEntity($book);
}
/**
* Create a copy of a page in a new location with a new name.
* @param \BookStack\Entities\Page $page
* @param \BookStack\Entities\Entity $newParent
* @param string $newName
* @return \BookStack\Entities\Page
* @throws \Throwable
*/
public function copyPage(Page $page, Entity $newParent, string $newName = '')
{
$newBook = $newParent->isA('book') ? $newParent : $newParent->book;
$newChapter = $newParent->isA('chapter') ? $newParent : null;
$copyPage = $this->getDraftPage($newBook, $newChapter);
$pageData = $page->getAttributes();
// Update name
if (!empty($newName)) {
$pageData['name'] = $newName;
}
// Copy tags from previous page if set
if ($page->tags) {
$pageData['tags'] = [];
foreach ($page->tags as $tag) {
$pageData['tags'][] = ['name' => $tag->name, 'value' => $tag->value];
}
}
// Set priority
if ($newParent->isA('chapter')) {
$pageData['priority'] = $this->getNewChapterPriority($newParent);
} else {
$pageData['priority'] = $this->getNewBookPriority($newParent);
}
return $this->publishPageDraft($copyPage, $pageData);
}
}

View File

@@ -1,31 +1,7 @@
<?php namespace BookStack\Entities;
<?php namespace BookStack;
use BookStack\Actions\Activity;
use BookStack\Actions\Comment;
use BookStack\Actions\Tag;
use BookStack\Actions\View;
use BookStack\Auth\Permissions\EntityPermission;
use BookStack\Auth\Permissions\JointPermission;
use BookStack\Ownable;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Relations\MorphMany;
/**
* Class Entity
* The base class for book-like items such as pages, chapters & books.
* This is not a database model in itself but extended.
*
* @property integer $id
* @property string $name
* @property string $slug
* @property Carbon $created_at
* @property Carbon $updated_at
* @property int $created_by
* @property int $updated_by
* @property boolean $restricted
*
* @package BookStack\Entities
*/
class Entity extends Ownable
{
@@ -39,17 +15,6 @@ class Entity extends Ownable
*/
public $searchFactor = 1.0;
/**
* Get the morph class for this model.
* Set here since, due to folder changes, the namespace used
* in the database no longer matches the class namespace.
* @return string
*/
public function getMorphClass()
{
return 'BookStack\\Entity';
}
/**
* Compares this entity to another given entity.
* Matches by comparing class and id.
@@ -193,7 +158,7 @@ class Entity extends Ownable
return null;
}
return app('BookStack\\Entities\\' . $className);
return app('BookStack\\' . $className);
}
/**

View File

@@ -1,6 +1,4 @@
<?php namespace BookStack\Auth\Permissions;
use BookStack\Model;
<?php namespace BookStack;
class EntityPermission extends Model
{

View File

@@ -1,6 +0,0 @@
<?php namespace BookStack\Exceptions;
class ExportException extends PrettyException
{
}

View File

@@ -3,12 +3,12 @@
namespace BookStack\Exceptions;
use Exception;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Validation\ValidationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Auth\Access\AuthorizationException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class Handler extends ExceptionHandler

View File

@@ -1,5 +0,0 @@
<?php namespace BookStack\Exceptions;
use Exception;
class HttpFetchException extends Exception {}

View File

@@ -11,7 +11,7 @@ class NotifyException extends \Exception
* @param string $message
* @param string $redirectLocation
*/
public function __construct(string $message, string $redirectLocation = "/")
public function __construct($message, $redirectLocation)
{
$this->message = $message;
$this->redirectLocation = $redirectLocation;

View File

@@ -1,3 +0,0 @@
<?php namespace BookStack\Exceptions;
class UserUpdateException extends NotifyException {}

View File

@@ -1,10 +1,10 @@
<?php namespace BookStack\Http\Controllers;
use BookStack\Entities\Repos\EntityRepo;
use BookStack\Exceptions\FileUploadException;
use BookStack\Attachment;
use BookStack\Exceptions\NotFoundException;
use BookStack\Uploads\Attachment;
use BookStack\Uploads\AttachmentService;
use BookStack\Repos\EntityRepo;
use BookStack\Services\AttachmentService;
use Illuminate\Http\Request;
class AttachmentController extends Controller
@@ -15,7 +15,7 @@ class AttachmentController extends Controller
/**
* AttachmentController constructor.
* @param \BookStack\Uploads\AttachmentService $attachmentService
* @param AttachmentService $attachmentService
* @param Attachment $attachment
* @param EntityRepo $entityRepo
*/

View File

@@ -2,11 +2,11 @@
namespace BookStack\Http\Controllers\Auth;
use BookStack\Auth\Access\LdapService;
use BookStack\Auth\Access\SocialAuthService;
use BookStack\Auth\UserRepo;
use BookStack\Exceptions\AuthException;
use BookStack\Http\Controllers\Controller;
use BookStack\Repos\UserRepo;
use BookStack\Services\LdapService;
use BookStack\Services\SocialAuthService;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
@@ -43,9 +43,9 @@ class LoginController extends Controller
/**
* Create a new controller instance.
*
* @param \BookStack\Auth\\BookStack\Auth\Access\SocialAuthService $socialAuthService
* @param SocialAuthService $socialAuthService
* @param LdapService $ldapService
* @param \BookStack\Auth\UserRepo $userRepo
* @param UserRepo $userRepo
*/
public function __construct(SocialAuthService $socialAuthService, LdapService $ldapService, UserRepo $userRepo)
{

View File

@@ -2,19 +2,21 @@
namespace BookStack\Http\Controllers\Auth;
use BookStack\Auth\SocialAccount;
use BookStack\Auth\User;
use BookStack\Auth\UserRepo;
use BookStack\Exceptions\SocialSignInAccountNotUsed;
use BookStack\Exceptions\SocialSignInException;
use BookStack\Exceptions\UserRegistrationException;
use BookStack\Http\Controllers\Controller;
use BookStack\Repos\UserRepo;
use BookStack\Services\EmailConfirmationService;
use BookStack\Services\SocialAuthService;
use BookStack\SocialAccount;
use BookStack\User;
use Exception;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Laravel\Socialite\Contracts\User as SocialUser;
use Validator;
use BookStack\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\RegistersUsers;
use Laravel\Socialite\Contracts\User as SocialUser;
class RegisterController extends Controller
{
@@ -46,11 +48,11 @@ class RegisterController extends Controller
/**
* Create a new controller instance.
*
* @param \BookStack\Auth\Access\SocialAuthService $socialAuthService
* @param \BookStack\Auth\EmailConfirmationService $emailConfirmationService
* @param \BookStack\Auth\UserRepo $userRepo
* @param SocialAuthService $socialAuthService
* @param EmailConfirmationService $emailConfirmationService
* @param UserRepo $userRepo
*/
public function __construct(\BookStack\Auth\Access\SocialAuthService $socialAuthService, \BookStack\Auth\Access\EmailConfirmationService $emailConfirmationService, UserRepo $userRepo)
public function __construct(SocialAuthService $socialAuthService, EmailConfirmationService $emailConfirmationService, UserRepo $userRepo)
{
$this->middleware('guest')->only(['getRegister', 'postRegister', 'socialRegister']);
$this->socialAuthService = $socialAuthService;
@@ -117,7 +119,7 @@ class RegisterController extends Controller
/**
* Create a new user instance after a valid registration.
* @param array $data
* @return \BookStack\Auth\User
* @return User
*/
protected function create(array $data)
{

View File

@@ -1,10 +1,10 @@
<?php namespace BookStack\Http\Controllers;
use Activity;
use BookStack\Auth\UserRepo;
use BookStack\Entities\Book;
use BookStack\Entities\Repos\EntityRepo;
use BookStack\Entities\ExportService;
use BookStack\Book;
use BookStack\Repos\EntityRepo;
use BookStack\Repos\UserRepo;
use BookStack\Services\ExportService;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Views;
@@ -19,8 +19,8 @@ class BookController extends Controller
/**
* BookController constructor.
* @param EntityRepo $entityRepo
* @param \BookStack\Auth\UserRepo $userRepo
* @param \BookStack\Entities\ExportService $exportService
* @param UserRepo $userRepo
* @param ExportService $exportService
*/
public function __construct(EntityRepo $entityRepo, UserRepo $userRepo, ExportService $exportService)
{
@@ -204,7 +204,7 @@ class BookController extends Controller
// Get the books involved in the sort
$bookIdsInvolved = $bookIdsInvolved->unique()->toArray();
$booksInvolved = $this->entityRepo->getManyById('book', $bookIdsInvolved, false, true);
$booksInvolved = $this->entityRepo->book->newQuery()->whereIn('id', $bookIdsInvolved)->get();
// Throw permission error if invalid ids or inaccessible books given.
if (count($bookIdsInvolved) !== count($booksInvolved)) {
$this->showPermissionError();

View File

@@ -1,10 +1,11 @@
<?php namespace BookStack\Http\Controllers;
use Activity;
use BookStack\Auth\UserRepo;
use BookStack\Entities\Bookshelf;
use BookStack\Entities\Repos\EntityRepo;
use BookStack\Entities\ExportService;
use BookStack\Book;
use BookStack\Bookshelf;
use BookStack\Repos\EntityRepo;
use BookStack\Repos\UserRepo;
use BookStack\Services\ExportService;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Views;
@@ -18,9 +19,9 @@ class BookshelfController extends Controller
/**
* BookController constructor.
* @param \BookStack\Entities\Repos\EntityRepo $entityRepo
* @param EntityRepo $entityRepo
* @param UserRepo $userRepo
* @param \BookStack\Entities\ExportService $exportService
* @param ExportService $exportService
*/
public function __construct(EntityRepo $entityRepo, UserRepo $userRepo, ExportService $exportService)
{

View File

@@ -1,9 +1,9 @@
<?php namespace BookStack\Http\Controllers;
use Activity;
use BookStack\Auth\UserRepo;
use BookStack\Entities\Repos\EntityRepo;
use BookStack\Entities\ExportService;
use BookStack\Repos\EntityRepo;
use BookStack\Repos\UserRepo;
use BookStack\Services\ExportService;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Views;
@@ -19,7 +19,7 @@ class ChapterController extends Controller
* ChapterController constructor.
* @param EntityRepo $entityRepo
* @param UserRepo $userRepo
* @param \BookStack\Entities\ExportService $exportService
* @param ExportService $exportService
*/
public function __construct(EntityRepo $entityRepo, UserRepo $userRepo, ExportService $exportService)
{

View File

@@ -1,8 +1,8 @@
<?php namespace BookStack\Http\Controllers;
use Activity;
use BookStack\Actions\CommentRepo;
use BookStack\Entities\Repos\EntityRepo;
use BookStack\Repos\CommentRepo;
use BookStack\Repos\EntityRepo;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Request;
@@ -13,8 +13,8 @@ class CommentController extends Controller
/**
* CommentController constructor.
* @param \BookStack\Entities\Repos\EntityRepo $entityRepo
* @param \BookStack\Actions\CommentRepo $commentRepo
* @param EntityRepo $entityRepo
* @param CommentRepo $commentRepo
*/
public function __construct(EntityRepo $entityRepo, CommentRepo $commentRepo)
{

View File

@@ -2,13 +2,13 @@
namespace BookStack\Http\Controllers;
use BookStack\Auth\User;
use BookStack\Ownable;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use BookStack\User;
abstract class Controller extends BaseController
{

View File

@@ -1,7 +1,8 @@
<?php namespace BookStack\Http\Controllers;
use Activity;
use BookStack\Entities\Repos\EntityRepo;
use BookStack\Repos\EntityRepo;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Views;
@@ -79,7 +80,6 @@ class HomeController extends Controller
{
$locale = app()->getLocale();
$cacheKey = 'GLOBAL_TRANSLATIONS_' . $locale;
if (cache()->has($cacheKey) && config('app.env') !== 'development') {
$resp = cache($cacheKey);
} else {
@@ -90,6 +90,15 @@ class HomeController extends Controller
'entities' => trans('entities'),
'errors' => trans('errors')
];
if ($locale !== 'en') {
$enTrans = [
'common' => trans('common', [], 'en'),
'components' => trans('components', [], 'en'),
'entities' => trans('entities', [], 'en'),
'errors' => trans('errors', [], 'en')
];
$translations = array_replace_recursive($enTrans, $translations);
}
$resp = 'window.translations = ' . json_encode($translations);
cache()->put($cacheKey, $resp, 120);
}

View File

@@ -1,12 +1,13 @@
<?php namespace BookStack\Http\Controllers;
use BookStack\Entities\Repos\EntityRepo;
use BookStack\Exceptions\ImageUploadException;
use BookStack\Repos\PageRepo;
use BookStack\Uploads\Image;
use BookStack\Uploads\ImageRepo;
use BookStack\Exceptions\NotFoundException;
use BookStack\Repos\EntityRepo;
use BookStack\Repos\ImageRepo;
use Illuminate\Filesystem\Filesystem as File;
use Illuminate\Http\Request;
use BookStack\Image;
use BookStack\Repos\PageRepo;
class ImageController extends Controller
{
@@ -219,7 +220,7 @@ class ImageController extends Controller
/**
* Show the usage of an image on pages.
* @param \BookStack\Entities\Repos\EntityRepo $entityRepo
* @param EntityRepo $entityRepo
* @param $id
* @return \Illuminate\Http\JsonResponse
*/

View File

@@ -1,32 +1,31 @@
<?php namespace BookStack\Http\Controllers;
use Activity;
use BookStack\Auth\UserRepo;
use BookStack\Entities\Repos\EntityRepo;
use BookStack\Entities\ExportService;
use BookStack\Entities\Repos\PageRepo;
use BookStack\Exceptions\NotFoundException;
use GatherContent\Htmldiff\Htmldiff;
use BookStack\Repos\EntityRepo;
use BookStack\Repos\UserRepo;
use BookStack\Services\ExportService;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Views;
use GatherContent\Htmldiff\Htmldiff;
class PageController extends Controller
{
protected $pageRepo;
protected $entityRepo;
protected $exportService;
protected $userRepo;
/**
* PageController constructor.
* @param \BookStack\Entities\Repos\PageRepo $pageRepo
* @param \BookStack\Entities\ExportService $exportService
* @param EntityRepo $entityRepo
* @param ExportService $exportService
* @param UserRepo $userRepo
*/
public function __construct(PageRepo $pageRepo, ExportService $exportService, UserRepo $userRepo)
public function __construct(EntityRepo $entityRepo, ExportService $exportService, UserRepo $userRepo)
{
$this->pageRepo = $pageRepo;
$this->entityRepo = $entityRepo;
$this->exportService = $exportService;
$this->userRepo = $userRepo;
parent::__construct();
@@ -43,11 +42,11 @@ class PageController extends Controller
public function create($bookSlug, $chapterSlug = null)
{
if ($chapterSlug !== null) {
$chapter = $this->pageRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
$book = $chapter->book;
} else {
$chapter = null;
$book = $this->pageRepo->getBySlug('book', $bookSlug);
$book = $this->entityRepo->getBySlug('book', $bookSlug);
}
$parent = $chapter ? $chapter : $book;
@@ -55,7 +54,7 @@ class PageController extends Controller
// Redirect to draft edit screen if signed in
if ($this->signedIn) {
$draft = $this->pageRepo->getDraftPage($book, $chapter);
$draft = $this->entityRepo->getDraftPage($book, $chapter);
return redirect($draft->getUrl());
}
@@ -79,18 +78,18 @@ class PageController extends Controller
]);
if ($chapterSlug !== null) {
$chapter = $this->pageRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
$book = $chapter->book;
} else {
$chapter = null;
$book = $this->pageRepo->getBySlug('book', $bookSlug);
$book = $this->entityRepo->getBySlug('book', $bookSlug);
}
$parent = $chapter ? $chapter : $book;
$this->checkOwnablePermission('page-create', $parent);
$page = $this->pageRepo->getDraftPage($book, $chapter);
$this->pageRepo->publishPageDraft($page, [
$page = $this->entityRepo->getDraftPage($book, $chapter);
$this->entityRepo->publishPageDraft($page, [
'name' => $request->get('name'),
'html' => ''
]);
@@ -105,7 +104,7 @@ class PageController extends Controller
*/
public function editDraft($bookSlug, $pageId)
{
$draft = $this->pageRepo->getById('page', $pageId, true);
$draft = $this->entityRepo->getById('page', $pageId, true);
$this->checkOwnablePermission('page-create', $draft->parent);
$this->setPageTitle(trans('entities.pages_edit_draft'));
@@ -132,19 +131,19 @@ class PageController extends Controller
]);
$input = $request->all();
$draftPage = $this->pageRepo->getById('page', $pageId, true);
$draftPage = $this->entityRepo->getById('page', $pageId, true);
$book = $draftPage->book;
$parent = $draftPage->parent;
$this->checkOwnablePermission('page-create', $parent);
if ($parent->isA('chapter')) {
$input['priority'] = $this->pageRepo->getNewChapterPriority($parent);
$input['priority'] = $this->entityRepo->getNewChapterPriority($parent);
} else {
$input['priority'] = $this->pageRepo->getNewBookPriority($parent);
$input['priority'] = $this->entityRepo->getNewBookPriority($parent);
}
$page = $this->pageRepo->publishPageDraft($draftPage, $input);
$page = $this->entityRepo->publishPageDraft($draftPage, $input);
Activity::add($page, 'page_create', $book->id);
return redirect($page->getUrl());
@@ -161,9 +160,9 @@ class PageController extends Controller
public function show($bookSlug, $pageSlug)
{
try {
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
} catch (NotFoundException $e) {
$page = $this->pageRepo->getPageByOldSlug($pageSlug, $bookSlug);
$page = $this->entityRepo->getPageByOldSlug($pageSlug, $bookSlug);
if ($page === null) {
throw $e;
}
@@ -172,9 +171,9 @@ class PageController extends Controller
$this->checkOwnablePermission('page-view', $page);
$page->html = $this->pageRepo->renderPage($page);
$sidebarTree = $this->pageRepo->getBookChildren($page->book);
$pageNav = $this->pageRepo->getPageNav($page->html);
$page->html = $this->entityRepo->renderPage($page);
$sidebarTree = $this->entityRepo->getBookChildren($page->book);
$pageNav = $this->entityRepo->getPageNav($page->html);
// check if the comment's are enabled
$commentsEnabled = !setting('app-disable-comments');
@@ -200,7 +199,7 @@ class PageController extends Controller
*/
public function getPageAjax($pageId)
{
$page = $this->pageRepo->getById('page', $pageId);
$page = $this->entityRepo->getById('page', $pageId);
return response()->json($page);
}
@@ -209,29 +208,28 @@ class PageController extends Controller
* @param string $bookSlug
* @param string $pageSlug
* @return Response
* @throws NotFoundException
*/
public function edit($bookSlug, $pageSlug)
{
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$this->checkOwnablePermission('page-update', $page);
$this->setPageTitle(trans('entities.pages_editing_named', ['pageName'=>$page->getShortName()]));
$page->isDraft = false;
// Check for active editing
$warnings = [];
if ($this->pageRepo->isPageEditingActive($page, 60)) {
$warnings[] = $this->pageRepo->getPageEditingActiveMessage($page, 60);
if ($this->entityRepo->isPageEditingActive($page, 60)) {
$warnings[] = $this->entityRepo->getPageEditingActiveMessage($page, 60);
}
// Check for a current draft version for this user
$userPageDraft = $this->pageRepo->getUserPageDraft($page, $this->currentUser->id);
if ($userPageDraft !== null) {
$page->name = $userPageDraft->name;
$page->html = $userPageDraft->html;
$page->markdown = $userPageDraft->markdown;
if ($this->entityRepo->hasUserGotPageDraft($page, $this->currentUser->id)) {
$draft = $this->entityRepo->getUserPageDraft($page, $this->currentUser->id);
$page->name = $draft->name;
$page->html = $draft->html;
$page->markdown = $draft->markdown;
$page->isDraft = true;
$warnings [] = $this->pageRepo->getUserPageDraftMessage($userPageDraft);
$warnings [] = $this->entityRepo->getUserPageDraftMessage($draft);
}
if (count($warnings) > 0) {
@@ -259,9 +257,9 @@ class PageController extends Controller
$this->validate($request, [
'name' => 'required|string|max:255'
]);
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$this->checkOwnablePermission('page-update', $page);
$this->pageRepo->updatePage($page, $page->book->id, $request->all());
$this->entityRepo->updatePage($page, $page->book->id, $request->all());
Activity::add($page, 'page_update', $page->book->id);
return redirect($page->getUrl());
}
@@ -274,7 +272,7 @@ class PageController extends Controller
*/
public function saveDraft(Request $request, $pageId)
{
$page = $this->pageRepo->getById('page', $pageId, true);
$page = $this->entityRepo->getById('page', $pageId, true);
$this->checkOwnablePermission('page-update', $page);
if (!$this->signedIn) {
@@ -284,7 +282,7 @@ class PageController extends Controller
], 500);
}
$draft = $this->pageRepo->updatePageDraft($page, $request->only(['name', 'html', 'markdown']));
$draft = $this->entityRepo->updatePageDraft($page, $request->only(['name', 'html', 'markdown']));
$updateTime = $draft->updated_at->timestamp;
return response()->json([
@@ -302,7 +300,7 @@ class PageController extends Controller
*/
public function redirectFromLink($pageId)
{
$page = $this->pageRepo->getById('page', $pageId);
$page = $this->entityRepo->getById('page', $pageId);
return redirect($page->getUrl());
}
@@ -314,7 +312,7 @@ class PageController extends Controller
*/
public function showDelete($bookSlug, $pageSlug)
{
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$this->checkOwnablePermission('page-delete', $page);
$this->setPageTitle(trans('entities.pages_delete_named', ['pageName'=>$page->getShortName()]));
return view('pages/delete', ['book' => $page->book, 'page' => $page, 'current' => $page]);
@@ -330,7 +328,7 @@ class PageController extends Controller
*/
public function showDeleteDraft($bookSlug, $pageId)
{
$page = $this->pageRepo->getById('page', $pageId, true);
$page = $this->entityRepo->getById('page', $pageId, true);
$this->checkOwnablePermission('page-update', $page);
$this->setPageTitle(trans('entities.pages_delete_draft_named', ['pageName'=>$page->getShortName()]));
return view('pages/delete', ['book' => $page->book, 'page' => $page, 'current' => $page]);
@@ -345,10 +343,10 @@ class PageController extends Controller
*/
public function destroy($bookSlug, $pageSlug)
{
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$book = $page->book;
$this->checkOwnablePermission('page-delete', $page);
$this->pageRepo->destroyPage($page);
$this->entityRepo->destroyPage($page);
Activity::addMessage('page_delete', $book->id, $page->name);
session()->flash('success', trans('entities.pages_delete_success'));
@@ -364,11 +362,11 @@ class PageController extends Controller
*/
public function destroyDraft($bookSlug, $pageId)
{
$page = $this->pageRepo->getById('page', $pageId, true);
$page = $this->entityRepo->getById('page', $pageId, true);
$book = $page->book;
$this->checkOwnablePermission('page-update', $page);
session()->flash('success', trans('entities.pages_delete_draft_success'));
$this->pageRepo->destroyPage($page);
$this->entityRepo->destroyPage($page);
return redirect($book->getUrl());
}
@@ -380,7 +378,7 @@ class PageController extends Controller
*/
public function showRevisions($bookSlug, $pageSlug)
{
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$this->setPageTitle(trans('entities.pages_revisions_named', ['pageName'=>$page->getShortName()]));
return view('pages/revisions', ['page' => $page, 'book' => $page->book, 'current' => $page]);
}
@@ -394,7 +392,7 @@ class PageController extends Controller
*/
public function showRevision($bookSlug, $pageSlug, $revisionId)
{
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$revision = $page->revisions()->where('id', '=', $revisionId)->first();
if ($revision === null) {
abort(404);
@@ -419,7 +417,7 @@ class PageController extends Controller
*/
public function showRevisionChanges($bookSlug, $pageSlug, $revisionId)
{
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$revision = $page->revisions()->where('id', '=', $revisionId)->first();
if ($revision === null) {
abort(404);
@@ -449,9 +447,9 @@ class PageController extends Controller
*/
public function restoreRevision($bookSlug, $pageSlug, $revisionId)
{
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$this->checkOwnablePermission('page-update', $page);
$page = $this->pageRepo->restorePageRevision($page, $page->book, $revisionId);
$page = $this->entityRepo->restorePageRevision($page, $page->book, $revisionId);
Activity::add($page, 'page_restore', $page->book->id);
return redirect($page->getUrl());
}
@@ -468,7 +466,7 @@ class PageController extends Controller
*/
public function destroyRevision($bookSlug, $pageSlug, $revId)
{
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$this->checkOwnablePermission('page-delete', $page);
$revision = $page->revisions()->where('id', '=', $revId)->first();
@@ -495,15 +493,13 @@ class PageController extends Controller
* https://github.com/barryvdh/laravel-dompdf
* @param string $bookSlug
* @param string $pageSlug
* @param Request $request
* @return \Illuminate\Http\Response
*/
public function exportPdf($bookSlug, $pageSlug, Request $request)
public function exportPdf($bookSlug, $pageSlug)
{
$isTesting = $request->query('isTesting');
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
$page->html = $this->pageRepo->renderPage($page);
$pdfContent = $this->exportService->pageToPdf($page, !empty($isTesting));
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$page->html = $this->entityRepo->renderPage($page);
$pdfContent = $this->exportService->pageToPdf($page);
return $this->downloadResponse($pdfContent, $pageSlug . '.pdf');
}
@@ -515,8 +511,8 @@ class PageController extends Controller
*/
public function exportHtml($bookSlug, $pageSlug)
{
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
$page->html = $this->pageRepo->renderPage($page);
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$page->html = $this->entityRepo->renderPage($page);
$containedHtml = $this->exportService->pageToContainedHtml($page);
return $this->downloadResponse($containedHtml, $pageSlug . '.html');
}
@@ -529,7 +525,7 @@ class PageController extends Controller
*/
public function exportPlainText($bookSlug, $pageSlug)
{
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$pageText = $this->exportService->pageToPlainText($page);
return $this->downloadResponse($pageText, $pageSlug . '.txt');
}
@@ -540,7 +536,7 @@ class PageController extends Controller
*/
public function showRecentlyCreated()
{
$pages = $this->pageRepo->getRecentlyCreatedPaginated('page', 20)->setPath(baseUrl('/pages/recently-created'));
$pages = $this->entityRepo->getRecentlyCreatedPaginated('page', 20)->setPath(baseUrl('/pages/recently-created'));
return view('pages/detailed-listing', [
'title' => trans('entities.recently_created_pages'),
'pages' => $pages
@@ -553,7 +549,7 @@ class PageController extends Controller
*/
public function showRecentlyUpdated()
{
$pages = $this->pageRepo->getRecentlyUpdatedPaginated('page', 20)->setPath(baseUrl('/pages/recently-updated'));
$pages = $this->entityRepo->getRecentlyUpdatedPaginated('page', 20)->setPath(baseUrl('/pages/recently-updated'));
return view('pages/detailed-listing', [
'title' => trans('entities.recently_updated_pages'),
'pages' => $pages
@@ -568,7 +564,7 @@ class PageController extends Controller
*/
public function showRestrict($bookSlug, $pageSlug)
{
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$this->checkOwnablePermission('restrictions-manage', $page);
$roles = $this->userRepo->getRestrictableRoles();
return view('pages/restrictions', [
@@ -586,7 +582,7 @@ class PageController extends Controller
*/
public function showMove($bookSlug, $pageSlug)
{
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$this->checkOwnablePermission('page-update', $page);
return view('pages/move', [
'book' => $page->book,
@@ -604,7 +600,7 @@ class PageController extends Controller
*/
public function move($bookSlug, $pageSlug, Request $request)
{
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$this->checkOwnablePermission('page-update', $page);
$entitySelection = $request->get('entity_selection', null);
@@ -618,7 +614,7 @@ class PageController extends Controller
try {
$parent = $this->pageRepo->getById($entityType, $entityId);
$parent = $this->entityRepo->getById($entityType, $entityId);
} catch (\Exception $e) {
session()->flash(trans('entities.selected_book_chapter_not_found'));
return redirect()->back();
@@ -626,7 +622,7 @@ class PageController extends Controller
$this->checkOwnablePermission('page-create', $parent);
$this->pageRepo->changePageParent($page, $parent);
$this->entityRepo->changePageParent($page, $parent);
Activity::add($page, 'page_move', $page->book->id);
session()->flash('success', trans('entities.pages_move_success', ['parentName' => $parent->name]));
@@ -642,7 +638,7 @@ class PageController extends Controller
*/
public function showCopy($bookSlug, $pageSlug)
{
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$this->checkOwnablePermission('page-update', $page);
session()->flashInput(['name' => $page->name]);
return view('pages/copy', [
@@ -661,7 +657,7 @@ class PageController extends Controller
*/
public function copy($bookSlug, $pageSlug, Request $request)
{
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$this->checkOwnablePermission('page-update', $page);
$entitySelection = $request->get('entity_selection', null);
@@ -673,7 +669,7 @@ class PageController extends Controller
$entityId = intval($stringExploded[1]);
try {
$parent = $this->pageRepo->getById($entityType, $entityId);
$parent = $this->entityRepo->getById($entityType, $entityId);
} catch (\Exception $e) {
session()->flash(trans('entities.selected_book_chapter_not_found'));
return redirect()->back();
@@ -682,7 +678,7 @@ class PageController extends Controller
$this->checkOwnablePermission('page-create', $parent);
$pageCopy = $this->pageRepo->copyPage($page, $parent, $request->get('name', ''));
$pageCopy = $this->entityRepo->copyPage($page, $parent, $request->get('name', ''));
Activity::add($pageCopy, 'page_create', $pageCopy->book->id);
session()->flash('success', trans('entities.pages_copy_success'));
@@ -700,9 +696,9 @@ class PageController extends Controller
*/
public function restrict($bookSlug, $pageSlug, Request $request)
{
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$this->checkOwnablePermission('restrictions-manage', $page);
$this->pageRepo->updateEntityPermissionsFromRequest($request, $page);
$this->entityRepo->updateEntityPermissionsFromRequest($request, $page);
session()->flash('success', trans('entities.pages_permissions_success'));
return redirect($page->getUrl());
}

View File

@@ -1,7 +1,7 @@
<?php namespace BookStack\Http\Controllers;
use BookStack\Auth\Permissions\PermissionsRepo;
use BookStack\Exceptions\PermissionsException;
use BookStack\Repos\PermissionsRepo;
use Illuminate\Http\Request;
class PermissionController extends Controller
@@ -11,7 +11,7 @@ class PermissionController extends Controller
/**
* PermissionController constructor.
* @param \BookStack\Auth\Permissions\PermissionsRepo $permissionsRepo
* @param PermissionsRepo $permissionsRepo
*/
public function __construct(PermissionsRepo $permissionsRepo)
{

View File

@@ -1,8 +1,8 @@
<?php namespace BookStack\Http\Controllers;
use BookStack\Actions\ViewService;
use BookStack\Entities\Repos\EntityRepo;
use BookStack\Entities\SearchService;
use BookStack\Repos\EntityRepo;
use BookStack\Services\SearchService;
use BookStack\Services\ViewService;
use Illuminate\Http\Request;
class SearchController extends Controller
@@ -13,7 +13,7 @@ class SearchController extends Controller
/**
* SearchController constructor.
* @param \BookStack\Entities\Repos\EntityRepo $entityRepo
* @param EntityRepo $entityRepo
* @param ViewService $viewService
* @param SearchService $searchService
*/
@@ -97,7 +97,7 @@ class SearchController extends Controller
$entities = $this->searchService->searchEntities($searchTerm, 'all', 1, 20, $permission)['results'];
} else {
$entityNames = $entityTypes->map(function ($type) {
return 'BookStack\\' . ucfirst($type); // TODO - Extract this elsewhere, too specific and stringy
return 'BookStack\\' . ucfirst($type);
})->toArray();
$entities = $this->viewService->getPopular(20, 0, $entityNames, $permission);
}

View File

@@ -1,6 +1,6 @@
<?php namespace BookStack\Http\Controllers;
use BookStack\Uploads\ImageService;
use BookStack\Services\ImageService;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Setting;

View File

@@ -1,6 +1,6 @@
<?php namespace BookStack\Http\Controllers;
use BookStack\Actions\TagRepo;
use BookStack\Repos\TagRepo;
use Illuminate\Http\Request;
class TagController extends Controller

View File

@@ -1,11 +1,11 @@
<?php namespace BookStack\Http\Controllers;
use BookStack\Auth\Access\SocialAuthService;
use BookStack\Auth\User;
use BookStack\Auth\UserRepo;
use BookStack\Exceptions\UserUpdateException;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use BookStack\Repos\UserRepo;
use BookStack\Services\SocialAuthService;
use BookStack\User;
class UserController extends Controller
{
@@ -60,7 +60,6 @@ class UserController extends Controller
* Store a newly created user in storage.
* @param Request $request
* @return Response
* @throws UserUpdateException
*/
public function store(Request $request)
{
@@ -91,10 +90,10 @@ class UserController extends Controller
if ($request->filled('roles')) {
$roles = $request->get('roles');
$this->userRepo->setUserRoles($user, $roles);
$user->roles()->sync($roles);
}
$this->userRepo->downloadAndAssignUserAvatar($user);
$this->userRepo->downloadGravatarToUserAvatar($user);
return redirect('/settings/users');
}
@@ -102,7 +101,7 @@ class UserController extends Controller
/**
* Show the form for editing the specified user.
* @param int $id
* @param \BookStack\Auth\Access\SocialAuthService $socialAuthService
* @param SocialAuthService $socialAuthService
* @return Response
*/
public function edit($id, SocialAuthService $socialAuthService)
@@ -124,9 +123,8 @@ class UserController extends Controller
/**
* Update the specified user in storage.
* @param Request $request
* @param int $id
* @param int $id
* @return Response
* @throws UserUpdateException
*/
public function update(Request $request, $id)
{
@@ -143,13 +141,13 @@ class UserController extends Controller
'setting' => 'array'
]);
$user = $this->userRepo->getById($id);
$user = $this->user->findOrFail($id);
$user->fill($request->all());
// Role updates
if (userCan('users-manage') && $request->filled('roles')) {
$roles = $request->get('roles');
$this->userRepo->setUserRoles($user, $roles);
$user->roles()->sync($roles);
}
// Password updates
@@ -188,7 +186,7 @@ class UserController extends Controller
return $this->currentUser->id == $id;
});
$user = $this->userRepo->getById($id);
$user = $this->user->findOrFail($id);
$this->setPageTitle(trans('settings.users_delete_named', ['userName' => $user->name]));
return view('users/delete', ['user' => $user]);
}
@@ -197,7 +195,6 @@ class UserController extends Controller
* Remove the specified user from storage.
* @param int $id
* @return Response
* @throws \Exception
*/
public function destroy($id)
{
@@ -283,7 +280,7 @@ class UserController extends Controller
$viewType = 'list';
}
$user = $this->userRepo->getById($id);
$user = $this->user->findOrFail($id);
setting()->putUser($user, 'bookshelves_view_type', $viewType);
return redirect()->back(302, [], "/settings/users/$id");

View File

@@ -3,8 +3,8 @@
namespace BookStack\Http\Middleware;
use Closure;
use Fideloper\Proxy\TrustProxies as Middleware;
use Illuminate\Http\Request;
use Fideloper\Proxy\TrustProxies as Middleware;
class TrustProxies extends Middleware
{

View File

@@ -1,6 +1,5 @@
<?php namespace BookStack\Uploads;
<?php namespace BookStack;
use BookStack\Ownable;
use Images;
class Image extends Ownable

View File

@@ -1,8 +1,4 @@
<?php namespace BookStack\Auth\Permissions;
use BookStack\Auth\Role;
use BookStack\Entities\Entity;
use BookStack\Model;
<?php namespace BookStack;
class JointPermission extends Model
{

View File

@@ -1,7 +1,17 @@
<?php namespace BookStack\Notifications;
<?php
class ConfirmEmail extends MailNotification
namespace BookStack\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
class ConfirmEmail extends Notification implements ShouldQueue
{
use Queueable;
public $token;
/**
@@ -13,6 +23,17 @@ class ConfirmEmail extends MailNotification
$this->token = $token;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
@@ -22,10 +43,10 @@ class ConfirmEmail extends MailNotification
public function toMail($notifiable)
{
$appName = ['appName' => setting('app-name')];
return $this->newMailMessage()
->subject(trans('auth.email_confirm_subject', $appName))
->greeting(trans('auth.email_confirm_greeting', $appName))
->line(trans('auth.email_confirm_text'))
->action(trans('auth.email_confirm_action'), baseUrl('/register/confirm/' . $this->token));
return (new MailMessage)
->subject(trans('auth.email_confirm_subject', $appName))
->greeting(trans('auth.email_confirm_greeting', $appName))
->line(trans('auth.email_confirm_text'))
->action(trans('auth.email_confirm_action'), baseUrl('/register/confirm/' . $this->token));
}
}

View File

@@ -1,35 +0,0 @@
<?php namespace BookStack\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class MailNotification extends Notification implements ShouldQueue
{
use Queueable;
/**
* Get the notification's channels.
*
* @param mixed $notifiable
* @return array|string
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Create a new mail message.
* @return MailMessage
*/
protected function newMailMessage()
{
return (new MailMessage)->view([
'html' => 'vendor.notifications.email',
'text' => 'vendor.notifications.email-plain'
]);
}
}

View File

@@ -1,7 +1,11 @@
<?php namespace BookStack\Notifications;
<?php
namespace BookStack\Notifications;
class ResetPassword extends MailNotification
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\MailMessage;
class ResetPassword extends Notification
{
/**
* The password reset token.
@@ -20,6 +24,17 @@ class ResetPassword extends MailNotification
$this->token = $token;
}
/**
* Get the notification's channels.
*
* @param mixed $notifiable
* @return array|string
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Build the mail representation of the notification.
*
@@ -27,7 +42,7 @@ class ResetPassword extends MailNotification
*/
public function toMail()
{
return $this->newMailMessage()
return (new MailMessage)
->subject(trans('auth.email_reset_subject', ['appName' => setting('app-name')]))
->line(trans('auth.email_reset_text'))
->action(trans('auth.reset_password'), baseUrl('password/reset/' . $this->token))

View File

@@ -1,7 +1,5 @@
<?php namespace BookStack;
use BookStack\Auth\User;
abstract class Ownable extends Model
{
/**

View File

@@ -1,6 +1,4 @@
<?php namespace BookStack\Entities;
use BookStack\Uploads\Attachment;
<?php namespace BookStack;
class Page extends Entity
{
@@ -10,15 +8,6 @@ class Page extends Entity
public $textField = 'text';
/**
* Get the morph class for this model.
* @return string
*/
public function getMorphClass()
{
return 'BookStack\\Page';
}
/**
* Converts this page into a simplified array.
* @return mixed
@@ -126,7 +115,7 @@ class Page extends Entity
/**
* Get the current revision for the page if existing
* @return \BookStack\Entities\PageRevision|null
* @return \BookStack\PageRevision|null
*/
public function getCurrentRevision()
{

View File

@@ -1,7 +1,4 @@
<?php namespace BookStack\Entities;
use BookStack\Auth\User;
use BookStack\Model;
<?php namespace BookStack;
class PageRevision extends Model
{

View File

@@ -1,15 +1,8 @@
<?php namespace BookStack\Providers;
use Blade;
use BookStack\Entities\Book;
use BookStack\Entities\Bookshelf;
use BookStack\Entities\Chapter;
use BookStack\Entities\Page;
use BookStack\Settings\Setting;
use BookStack\Settings\SettingService;
use Illuminate\Database\Eloquent\Relations\Relation;
use BookStack\Services\SettingService;
use BookStack\Setting;
use Illuminate\Support\ServiceProvider;
use Schema;
use Validator;
class AppServiceProvider extends ServiceProvider
@@ -27,21 +20,12 @@ class AppServiceProvider extends ServiceProvider
return in_array($value->getMimeType(), $imageMimes);
});
// Custom blade view directives
Blade::directive('icon', function ($expression) {
\Blade::directive('icon', function ($expression) {
return "<?php echo icon($expression); ?>";
});
// Allow longer string lengths after upgrade to utf8mb4
Schema::defaultStringLength(191);
// Set morph-map due to namespace changes
Relation::morphMap([
'BookStack\\Bookshelf' => Bookshelf::class,
'BookStack\\Book' => Book::class,
'BookStack\\Chapter' => Chapter::class,
'BookStack\\Page' => Page::class,
]);
\Schema::defaultStringLength(191);
}
/**

View File

@@ -3,7 +3,7 @@
namespace BookStack\Providers;
use Auth;
use BookStack\Auth\Access\LdapService;
use BookStack\Services\LdapService;
use Illuminate\Support\ServiceProvider;
class AuthServiceProvider extends ServiceProvider

View File

@@ -3,6 +3,7 @@
namespace BookStack\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Broadcast;
class BroadcastServiceProvider extends ServiceProvider
{

View File

@@ -2,19 +2,18 @@
namespace BookStack\Providers;
use BookStack\Actions\Activity;
use BookStack\Actions\ActivityService;
use BookStack\Actions\View;
use BookStack\Actions\ViewService;
use BookStack\Auth\Permissions\PermissionService;
use BookStack\Settings\Setting;
use BookStack\Settings\SettingService;
use BookStack\Uploads\HttpFetcher;
use BookStack\Uploads\Image;
use BookStack\Uploads\ImageService;
use BookStack\Activity;
use BookStack\Image;
use BookStack\Services\ImageService;
use BookStack\Services\PermissionService;
use BookStack\Services\ViewService;
use BookStack\Setting;
use BookStack\View;
use Illuminate\Contracts\Cache\Repository;
use Illuminate\Contracts\Filesystem\Factory;
use Illuminate\Support\ServiceProvider;
use BookStack\Services\ActivityService;
use BookStack\Services\SettingService;
use Intervention\Image\ImageManager;
class CustomFacadeProvider extends ServiceProvider
@@ -62,8 +61,7 @@ class CustomFacadeProvider extends ServiceProvider
$this->app->make(Image::class),
$this->app->make(ImageManager::class),
$this->app->make(Factory::class),
$this->app->make(Repository::class),
$this->app->make(HttpFetcher::class)
$this->app->make(Repository::class)
);
});
}

View File

@@ -2,6 +2,7 @@
namespace BookStack\Providers;
use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use SocialiteProviders\Manager\SocialiteWasCalled;

View File

@@ -2,7 +2,9 @@
namespace BookStack\Providers;
use BookStack\Auth\Access\LdapService;
use BookStack\Role;
use BookStack\Services\LdapService;
use BookStack\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;
@@ -17,7 +19,7 @@ class LdapUserProvider implements UserProvider
protected $model;
/**
* @var \BookStack\Auth\LdapService
* @var LdapService
*/
protected $ldapService;
@@ -25,7 +27,7 @@ class LdapUserProvider implements UserProvider
/**
* LdapUserProvider constructor.
* @param $model
* @param \BookStack\Auth\LdapService $ldapService
* @param LdapService $ldapService
*/
public function __construct($model, LdapService $ldapService)
{

View File

@@ -2,6 +2,7 @@
namespace BookStack\Providers;
use Illuminate\Routing\Router;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Route;

View File

@@ -1,32 +0,0 @@
<?php namespace BookStack\Providers;
use BookStack\Translation\Translator;
class TranslationServiceProvider extends \Illuminate\Translation\TranslationServiceProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->registerLoader();
$this->app->singleton('translator', function ($app) {
$loader = $app['translation.loader'];
// When registering the translator component, we'll need to set the default
// locale as well as the fallback locale. So, we'll grab the application
// configuration so we can easily get both of these values from there.
$locale = $app['config']['app.locale'];
$trans = new Translator($loader, $locale);
$trans->setFallback($app['config']['app.fallback_locale']);
return $trans;
});
}
}

View File

@@ -1,6 +1,7 @@
<?php namespace BookStack\Actions;
<?php namespace BookStack\Repos;
use BookStack\Entities\Entity;
use BookStack\Comment;
use BookStack\Entity;
/**
* Class CommentRepo
@@ -10,13 +11,13 @@ class CommentRepo
{
/**
* @var \BookStack\Actions\Comment $comment
* @var Comment $comment
*/
protected $comment;
/**
* CommentRepo constructor.
* @param \BookStack\Actions\Comment $comment
* @param Comment $comment
*/
public function __construct(Comment $comment)
{
@@ -26,7 +27,7 @@ class CommentRepo
/**
* Get a comment by ID.
* @param $id
* @return \BookStack\Actions\Comment|\Illuminate\Database\Eloquent\Model
* @return Comment|\Illuminate\Database\Eloquent\Model
*/
public function getById($id)
{
@@ -35,9 +36,9 @@ class CommentRepo
/**
* Create a new comment on an entity.
* @param \BookStack\Entities\Entity $entity
* @param Entity $entity
* @param array $data
* @return \BookStack\Actions\Comment
* @return Comment
*/
public function create(Entity $entity, $data = [])
{
@@ -52,7 +53,7 @@ class CommentRepo
/**
* Update an existing comment.
* @param \BookStack\Actions\Comment $comment
* @param Comment $comment
* @param array $input
* @return mixed
*/
@@ -65,7 +66,7 @@ class CommentRepo
/**
* Delete a comment from the system.
* @param \BookStack\Actions\Comment $comment
* @param Comment $comment
* @return mixed
*/
public function delete($comment)
@@ -75,7 +76,7 @@ class CommentRepo
/**
* Get the next local ID relative to the linked entity.
* @param \BookStack\Entities\Entity $entity
* @param Entity $entity
* @return int
*/
protected function getNextLocalId(Entity $entity)

View File

@@ -1,30 +1,54 @@
<?php namespace BookStack\Entities\Repos;
<?php namespace BookStack\Repos;
use BookStack\Actions\TagRepo;
use BookStack\Actions\ViewService;
use BookStack\Auth\Permissions\PermissionService;
use BookStack\Auth\User;
use BookStack\Entities\Book;
use BookStack\Entities\Bookshelf;
use BookStack\Entities\Chapter;
use BookStack\Entities\Entity;
use BookStack\Entities\EntityProvider;
use BookStack\Entities\Page;
use BookStack\Entities\SearchService;
use BookStack\Book;
use BookStack\Bookshelf;
use BookStack\Chapter;
use BookStack\Entity;
use BookStack\Exceptions\NotFoundException;
use BookStack\Exceptions\NotifyException;
use BookStack\Uploads\AttachmentService;
use BookStack\Page;
use BookStack\PageRevision;
use BookStack\Services\AttachmentService;
use BookStack\Services\PermissionService;
use BookStack\Services\SearchService;
use BookStack\Services\ViewService;
use Carbon\Carbon;
use DOMDocument;
use Illuminate\Http\Request;
use DOMXPath;
use Illuminate\Support\Collection;
class EntityRepo
{
/**
* @var Bookshelf
*/
public $bookshelf;
/**
* @var EntityProvider
* @var Book $book
*/
protected $entityProvider;
public $book;
/**
* @var Chapter
*/
public $chapter;
/**
* @var Page
*/
public $page;
/**
* @var PageRevision
*/
protected $pageRevision;
/**
* Base entity instances keyed by type
* @var []Entity
*/
protected $entities;
/**
* @var PermissionService
@@ -48,36 +72,63 @@ class EntityRepo
/**
* EntityRepo constructor.
* @param EntityProvider $entityProvider
* @param Bookshelf $bookshelf
* @param Book $book
* @param Chapter $chapter
* @param Page $page
* @param PageRevision $pageRevision
* @param ViewService $viewService
* @param PermissionService $permissionService
* @param TagRepo $tagRepo
* @param SearchService $searchService
*/
public function __construct(
EntityProvider $entityProvider,
Bookshelf $bookshelf,
Book $book,
Chapter $chapter,
Page $page,
PageRevision $pageRevision,
ViewService $viewService,
PermissionService $permissionService,
TagRepo $tagRepo,
SearchService $searchService
) {
$this->entityProvider = $entityProvider;
$this->bookshelf = $bookshelf;
$this->book = $book;
$this->chapter = $chapter;
$this->page = $page;
$this->pageRevision = $pageRevision;
$this->entities = [
'bookshelf' => $this->bookshelf,
'page' => $this->page,
'chapter' => $this->chapter,
'book' => $this->book
];
$this->viewService = $viewService;
$this->permissionService = $permissionService;
$this->tagRepo = $tagRepo;
$this->searchService = $searchService;
}
/**
* Get an entity instance via type.
* @param $type
* @return Entity
*/
protected function getEntity($type)
{
return $this->entities[strtolower($type)];
}
/**
* Base query for searching entities via permission system
* @param string $type
* @param bool $allowDrafts
* @param string $permission
* @return \Illuminate\Database\Query\Builder
*/
protected function entityQuery($type, $allowDrafts = false, $permission = 'view')
{
$q = $this->permissionService->enforceEntityRestrictions($type, $this->entityProvider->get($type), $permission);
$q = $this->permissionService->enforceEntityRestrictions($type, $this->getEntity($type), $permission);
if (strtolower($type) === 'page' && !$allowDrafts) {
$q = $q->where('draft', '=', false);
}
@@ -101,35 +152,15 @@ class EntityRepo
* @param integer $id
* @param bool $allowDrafts
* @param bool $ignorePermissions
* @return \BookStack\Entities\Entity
* @return Entity
*/
public function getById($type, $id, $allowDrafts = false, $ignorePermissions = false)
{
$query = $this->entityQuery($type, $allowDrafts);
if ($ignorePermissions) {
$query = $this->entityProvider->get($type)->newQuery();
$entity = $this->getEntity($type);
return $entity->newQuery()->find($id);
}
return $query->find($id);
}
/**
* @param string $type
* @param []int $ids
* @param bool $allowDrafts
* @param bool $ignorePermissions
* @return \Illuminate\Database\Eloquent\Builder[]|\Illuminate\Database\Eloquent\Collection|Collection
*/
public function getManyById($type, $ids, $allowDrafts = false, $ignorePermissions = false)
{
$query = $this->entityQuery($type, $allowDrafts);
if ($ignorePermissions) {
$query = $this->entityProvider->get($type)->newQuery();
}
return $query->whereIn('id', $ids)->get();
return $this->entityQuery($type, $allowDrafts)->find($id);
}
/**
@@ -137,7 +168,7 @@ class EntityRepo
* @param string $type
* @param string $slug
* @param string|bool $bookSlug
* @return \BookStack\Entities\Entity
* @return Entity
* @throws NotFoundException
*/
public function getBySlug($type, $slug, $bookSlug = false)
@@ -147,7 +178,7 @@ class EntityRepo
if (strtolower($type) === 'chapter' || strtolower($type) === 'page') {
$q = $q->where('book_id', '=', function ($query) use ($bookSlug) {
$query->select('id')
->from($this->entityProvider->book->getTable())
->from($this->book->getTable())
->where('slug', '=', $bookSlug)->limit(1);
});
}
@@ -159,6 +190,26 @@ class EntityRepo
}
/**
* Search through page revisions and retrieve the last page in the
* current book that has a slug equal to the one given.
* @param string $pageSlug
* @param string $bookSlug
* @return null|Page
*/
public function getPageByOldSlug($pageSlug, $bookSlug)
{
$revision = $this->pageRevision->where('slug', '=', $pageSlug)
->whereHas('page', function ($query) {
$this->permissionService->enforceEntityRestrictions('page', $query);
})
->where('type', '=', 'version')
->where('book_slug', '=', $bookSlug)
->orderBy('created_at', 'desc')
->with('page')->first();
return $revision !== null ? $revision->page : null;
}
/**
* Get all entities of a type with the given permission, limited by count unless count is false.
* @param string $type
@@ -196,7 +247,7 @@ class EntityRepo
*/
public function getRecentlyCreated($type, $count = 20, $page = 0, $additionalQuery = false)
{
$query = $this->permissionService->enforceEntityRestrictions($type, $this->entityProvider->get($type))
$query = $this->permissionService->enforceEntityRestrictions($type, $this->getEntity($type))
->orderBy('created_at', 'desc');
if (strtolower($type) === 'page') {
$query = $query->where('draft', '=', false);
@@ -217,7 +268,7 @@ class EntityRepo
*/
public function getRecentlyUpdated($type, $count = 20, $page = 0, $additionalQuery = false)
{
$query = $this->permissionService->enforceEntityRestrictions($type, $this->entityProvider->get($type))
$query = $this->permissionService->enforceEntityRestrictions($type, $this->getEntity($type))
->orderBy('updated_at', 'desc');
if (strtolower($type) === 'page') {
$query = $query->where('draft', '=', false);
@@ -237,7 +288,7 @@ class EntityRepo
*/
public function getRecentlyViewed($type, $count = 10, $page = 0)
{
$filter = is_bool($type) ? false : $this->entityProvider->get($type);
$filter = is_bool($type) ? false : $this->getEntity($type);
return $this->viewService->getUserRecentlyViewed($count, $page, $filter);
}
@@ -272,7 +323,7 @@ class EntityRepo
*/
public function getPopular($type, $count = 10, $page = 0)
{
$filter = is_bool($type) ? false : $this->entityProvider->get($type);
$filter = is_bool($type) ? false : $this->getEntity($type);
return $this->viewService->getPopular($count, $page, $filter);
}
@@ -280,32 +331,19 @@ class EntityRepo
* Get draft pages owned by the current user.
* @param int $count
* @param int $page
* @return Collection
*/
public function getUserDraftPages($count = 20, $page = 0)
{
return $this->entityProvider->page->where('draft', '=', true)
return $this->page->where('draft', '=', true)
->where('created_by', '=', user()->id)
->orderBy('updated_at', 'desc')
->skip($count * $page)->take($count)->get();
}
/**
* Get the number of entities the given user has created.
* @param string $type
* @param User $user
* @return int
*/
public function getUserTotalCreated(string $type, User $user)
{
return $this->entityProvider->get($type)
->where('created_by', '=', $user->id)->count();
}
/**
* Get the child items for a chapter sorted by priority but
* with draft items floated to the top.
* @param \BookStack\Entities\Bookshelf $bookshelf
* @param Bookshelf $bookshelf
* @return \Illuminate\Database\Eloquent\Collection|static[]
*/
public function getBookshelfChildren(Bookshelf $bookshelf)
@@ -317,7 +355,7 @@ class EntityRepo
* Get all child objects of a book.
* Returns a sorted collection of Pages and Chapters.
* Loads the book slug onto child elements to prevent access database access for getting the slug.
* @param \BookStack\Entities\Book $book
* @param Book $book
* @param bool $filterDrafts
* @param bool $renderPages
* @return mixed
@@ -330,14 +368,14 @@ class EntityRepo
$tree = [];
foreach ($q as $index => $rawEntity) {
if ($rawEntity->entity_type === $this->entityProvider->page->getMorphClass()) {
$entities[$index] = $this->entityProvider->page->newFromBuilder($rawEntity);
if ($rawEntity->entity_type === 'BookStack\\Page') {
$entities[$index] = $this->page->newFromBuilder($rawEntity);
if ($renderPages) {
$entities[$index]->html = $rawEntity->html;
$entities[$index]->html = $this->renderPage($entities[$index]);
};
} else if ($rawEntity->entity_type === $this->entityProvider->chapter->getMorphClass()) {
$entities[$index] = $this->entityProvider->chapter->newFromBuilder($rawEntity);
} else if ($rawEntity->entity_type === 'BookStack\\Chapter') {
$entities[$index] = $this->chapter->newFromBuilder($rawEntity);
$key = $entities[$index]->entity_type . ':' . $entities[$index]->id;
$parents[$key] = $entities[$index];
$parents[$key]->setAttribute('pages', collect());
@@ -352,7 +390,7 @@ class EntityRepo
if ($entity->chapter_id === 0 || $entity->chapter_id === '0') {
continue;
}
$parentKey = $this->entityProvider->chapter->getMorphClass() . ':' . $entity->chapter_id;
$parentKey = 'BookStack\\Chapter:' . $entity->chapter_id;
if (!isset($parents[$parentKey])) {
$tree[] = $entity;
continue;
@@ -367,7 +405,7 @@ class EntityRepo
/**
* Get the child items for a chapter sorted by priority but
* with draft items floated to the top.
* @param \BookStack\Entities\Chapter $chapter
* @param Chapter $chapter
* @return \Illuminate\Database\Eloquent\Collection|static[]
*/
public function getChapterChildren(Chapter $chapter)
@@ -379,7 +417,7 @@ class EntityRepo
/**
* Get the next sequential priority for a new child element in the given book.
* @param \BookStack\Entities\Book $book
* @param Book $book
* @return int
*/
public function getNewBookPriority(Book $book)
@@ -390,7 +428,7 @@ class EntityRepo
/**
* Get a new priority for a new page to be added to the given chapter.
* @param \BookStack\Entities\Chapter $chapter
* @param Chapter $chapter
* @return int
*/
public function getNewChapterPriority(Chapter $chapter)
@@ -426,7 +464,7 @@ class EntityRepo
*/
protected function slugExists($type, $slug, $currentId = false, $bookId = false)
{
$query = $this->entityProvider->get($type)->where('slug', '=', $slug);
$query = $this->getEntity($type)->where('slug', '=', $slug);
if (strtolower($type) === 'page' || strtolower($type) === 'chapter') {
$query = $query->where('book_id', '=', $bookId);
}
@@ -438,11 +476,10 @@ class EntityRepo
/**
* Updates entity restrictions from a request
* @param Request $request
* @param \BookStack\Entities\Entity $entity
* @throws \Throwable
* @param $request
* @param Entity $entity
*/
public function updateEntityPermissionsFromRequest(Request $request, Entity $entity)
public function updateEntityPermissionsFromRequest($request, Entity $entity)
{
$entity->restricted = $request->get('restricted', '') === 'true';
$entity->permissions()->delete();
@@ -470,12 +507,12 @@ class EntityRepo
* @param string $type
* @param array $input
* @param bool|Book $book
* @return \BookStack\Entities\Entity
* @return Entity
*/
public function createFromInput($type, $input = [], $book = false)
{
$isChapter = strtolower($type) === 'chapter';
$entityModel = $this->entityProvider->get($type)->newInstance($input);
$entityModel = $this->getEntity($type)->newInstance($input);
$entityModel->slug = $this->findSuitableSlug($type, $entityModel->name, false, $isChapter ? $book->id : false);
$entityModel->created_by = user()->id;
$entityModel->updated_by = user()->id;
@@ -494,9 +531,9 @@ class EntityRepo
* Update entity details from request input.
* Used for books and chapters
* @param string $type
* @param \BookStack\Entities\Entity $entityModel
* @param Entity $entityModel
* @param array $input
* @return \BookStack\Entities\Entity
* @return Entity
*/
public function updateFromInput($type, Entity $entityModel, $input = [])
{
@@ -519,7 +556,7 @@ class EntityRepo
/**
* Sync the books assigned to a shelf from a comma-separated list
* of book IDs.
* @param \BookStack\Entities\Bookshelf $shelf
* @param Bookshelf $shelf
* @param string $books
*/
public function updateShelfBooks(Bookshelf $shelf, string $books)
@@ -544,7 +581,7 @@ class EntityRepo
* @param integer $newBookId
* @param Entity $entity
* @param bool $rebuildPermissions
* @return \BookStack\Entities\Entity
* @return Entity
*/
public function changeBook($type, $newBookId, Entity $entity, $rebuildPermissions = false)
{
@@ -598,6 +635,191 @@ class EntityRepo
return $slug;
}
/**
* Get a new draft page instance.
* @param Book $book
* @param Chapter|bool $chapter
* @return Page
*/
public function getDraftPage(Book $book, $chapter = false)
{
$page = $this->page->newInstance();
$page->name = trans('entities.pages_initial_name');
$page->created_by = user()->id;
$page->updated_by = user()->id;
$page->draft = true;
if ($chapter) {
$page->chapter_id = $chapter->id;
}
$book->pages()->save($page);
$page = $this->page->find($page->id);
$this->permissionService->buildJointPermissionsForEntity($page);
return $page;
}
/**
* Publish a draft page to make it a normal page.
* Sets the slug and updates the content.
* @param Page $draftPage
* @param array $input
* @return Page
*/
public function publishPageDraft(Page $draftPage, array $input)
{
$draftPage->fill($input);
// Save page tags if present
if (isset($input['tags'])) {
$this->tagRepo->saveTagsToEntity($draftPage, $input['tags']);
}
$draftPage->slug = $this->findSuitableSlug('page', $draftPage->name, false, $draftPage->book->id);
$draftPage->html = $this->formatHtml($input['html']);
$draftPage->text = $this->pageToPlainText($draftPage);
$draftPage->draft = false;
$draftPage->revision_count = 1;
$draftPage->save();
$this->savePageRevision($draftPage, trans('entities.pages_initial_revision'));
$this->searchService->indexEntity($draftPage);
return $draftPage;
}
/**
* Create a copy of a page in a new location with a new name.
* @param Page $page
* @param Entity $newParent
* @param string $newName
* @return Page
*/
public function copyPage(Page $page, Entity $newParent, $newName = '')
{
$newBook = $newParent->isA('book') ? $newParent : $newParent->book;
$newChapter = $newParent->isA('chapter') ? $newParent : null;
$copyPage = $this->getDraftPage($newBook, $newChapter);
$pageData = $page->getAttributes();
// Update name
if (!empty($newName)) {
$pageData['name'] = $newName;
}
// Copy tags from previous page if set
if ($page->tags) {
$pageData['tags'] = [];
foreach ($page->tags as $tag) {
$pageData['tags'][] = ['name' => $tag->name, 'value' => $tag->value];
}
}
// Set priority
if ($newParent->isA('chapter')) {
$pageData['priority'] = $this->getNewChapterPriority($newParent);
} else {
$pageData['priority'] = $this->getNewBookPriority($newParent);
}
return $this->publishPageDraft($copyPage, $pageData);
}
/**
* Saves a page revision into the system.
* @param Page $page
* @param null|string $summary
* @return PageRevision
*/
public function savePageRevision(Page $page, $summary = null)
{
$revision = $this->pageRevision->newInstance($page->toArray());
if (setting('app-editor') !== 'markdown') {
$revision->markdown = '';
}
$revision->page_id = $page->id;
$revision->slug = $page->slug;
$revision->book_slug = $page->book->slug;
$revision->created_by = user()->id;
$revision->created_at = $page->updated_at;
$revision->type = 'version';
$revision->summary = $summary;
$revision->revision_number = $page->revision_count;
$revision->save();
$revisionLimit = config('app.revision_limit');
if ($revisionLimit !== false) {
$revisionsToDelete = $this->pageRevision->where('page_id', '=', $page->id)
->orderBy('created_at', 'desc')->skip(intval($revisionLimit))->take(10)->get(['id']);
if ($revisionsToDelete->count() > 0) {
$this->pageRevision->whereIn('id', $revisionsToDelete->pluck('id'))->delete();
}
}
return $revision;
}
/**
* Formats a page's html to be tagged correctly
* within the system.
* @param string $htmlText
* @return string
*/
protected function formatHtml($htmlText)
{
if ($htmlText == '') {
return $htmlText;
}
libxml_use_internal_errors(true);
$doc = new DOMDocument();
$doc->loadHTML(mb_convert_encoding($htmlText, 'HTML-ENTITIES', 'UTF-8'));
$container = $doc->documentElement;
$body = $container->childNodes->item(0);
$childNodes = $body->childNodes;
// Ensure no duplicate ids are used
$idArray = [];
foreach ($childNodes as $index => $childNode) {
/** @var \DOMElement $childNode */
if (get_class($childNode) !== 'DOMElement') {
continue;
}
// Overwrite id if not a BookStack custom id
if ($childNode->hasAttribute('id')) {
$id = $childNode->getAttribute('id');
if (strpos($id, 'bkmrk') === 0 && array_search($id, $idArray) === false) {
$idArray[] = $id;
continue;
};
}
// Create an unique id for the element
// Uses the content as a basis to ensure output is the same every time
// the same content is passed through.
$contentId = 'bkmrk-' . substr(strtolower(preg_replace('/\s+/', '-', trim($childNode->nodeValue))), 0, 20);
$newId = urlencode($contentId);
$loopIndex = 0;
while (in_array($newId, $idArray)) {
$newId = urlencode($contentId . '-' . $loopIndex);
$loopIndex++;
}
$childNode->setAttribute('id', $newId);
$idArray[] = $newId;
}
// Generate inner html as a string
$html = '';
foreach ($childNodes as $childNode) {
$html .= $doc->saveHTML($childNode);
}
return $html;
}
/**
* Render the page for viewing, Parsing and performing features such as page transclusion.
* @param Page $page
@@ -678,6 +900,17 @@ class EntityRepo
return $html;
}
/**
* Get the plain text version of a page's content.
* @param Page $page
* @return string
*/
public function pageToPlainText(Page $page)
{
$html = $this->renderPage($page);
return strip_tags($html);
}
/**
* Search for image usage within page content.
* @param $imageString
@@ -694,9 +927,281 @@ class EntityRepo
return count($pages) > 0 ? $pages : false;
}
/**
* Parse the headers on the page to get a navigation menu
* @param String $pageContent
* @return array
*/
public function getPageNav($pageContent)
{
if ($pageContent == '') {
return [];
}
libxml_use_internal_errors(true);
$doc = new DOMDocument();
$doc->loadHTML(mb_convert_encoding($pageContent, 'HTML-ENTITIES', 'UTF-8'));
$xPath = new DOMXPath($doc);
$headers = $xPath->query("//h1|//h2|//h3|//h4|//h5|//h6");
if (is_null($headers)) {
return [];
}
$tree = collect([]);
foreach ($headers as $header) {
$text = $header->nodeValue;
$tree->push([
'nodeName' => strtolower($header->nodeName),
'level' => intval(str_replace('h', '', $header->nodeName)),
'link' => '#' . $header->getAttribute('id'),
'text' => strlen($text) > 30 ? substr($text, 0, 27) . '...' : $text
]);
}
// Normalise headers if only smaller headers have been used
if (count($tree) > 0) {
$minLevel = $tree->pluck('level')->min();
$tree = $tree->map(function ($header) use ($minLevel) {
$header['level'] -= ($minLevel - 2);
return $header;
});
}
return $tree->toArray();
}
/**
* Updates a page with any fillable data and saves it into the database.
* @param Page $page
* @param int $book_id
* @param array $input
* @return Page
*/
public function updatePage(Page $page, $book_id, $input)
{
// Hold the old details to compare later
$oldHtml = $page->html;
$oldName = $page->name;
// Prevent slug being updated if no name change
if ($page->name !== $input['name']) {
$page->slug = $this->findSuitableSlug('page', $input['name'], $page->id, $book_id);
}
// Save page tags if present
if (isset($input['tags'])) {
$this->tagRepo->saveTagsToEntity($page, $input['tags']);
}
// Update with new details
$userId = user()->id;
$page->fill($input);
$page->html = $this->formatHtml($input['html']);
$page->text = $this->pageToPlainText($page);
if (setting('app-editor') !== 'markdown') {
$page->markdown = '';
}
$page->updated_by = $userId;
$page->revision_count++;
$page->save();
// Remove all update drafts for this user & page.
$this->userUpdatePageDraftsQuery($page, $userId)->delete();
// Save a revision after updating
if ($oldHtml !== $input['html'] || $oldName !== $input['name'] || $input['summary'] !== null) {
$this->savePageRevision($page, $input['summary']);
}
$this->searchService->indexEntity($page);
return $page;
}
/**
* The base query for getting user update drafts.
* @param Page $page
* @param $userId
* @return mixed
*/
protected function userUpdatePageDraftsQuery(Page $page, $userId)
{
return $this->pageRevision->where('created_by', '=', $userId)
->where('type', 'update_draft')
->where('page_id', '=', $page->id)
->orderBy('created_at', 'desc');
}
/**
* Checks whether a user has a draft version of a particular page or not.
* @param Page $page
* @param $userId
* @return bool
*/
public function hasUserGotPageDraft(Page $page, $userId)
{
return $this->userUpdatePageDraftsQuery($page, $userId)->count() > 0;
}
/**
* Get the latest updated draft revision for a particular page and user.
* @param Page $page
* @param $userId
* @return mixed
*/
public function getUserPageDraft(Page $page, $userId)
{
return $this->userUpdatePageDraftsQuery($page, $userId)->first();
}
/**
* Get the notification message that informs the user that they are editing a draft page.
* @param PageRevision $draft
* @return string
*/
public function getUserPageDraftMessage(PageRevision $draft)
{
$message = trans('entities.pages_editing_draft_notification', ['timeDiff' => $draft->updated_at->diffForHumans()]);
if ($draft->page->updated_at->timestamp <= $draft->updated_at->timestamp) {
return $message;
}
return $message . "\n" . trans('entities.pages_draft_edited_notification');
}
/**
* Check if a page is being actively editing.
* Checks for edits since last page updated.
* Passing in a minuted range will check for edits
* within the last x minutes.
* @param Page $page
* @param null $minRange
* @return bool
*/
public function isPageEditingActive(Page $page, $minRange = null)
{
$draftSearch = $this->activePageEditingQuery($page, $minRange);
return $draftSearch->count() > 0;
}
/**
* A query to check for active update drafts on a particular page.
* @param Page $page
* @param null $minRange
* @return mixed
*/
protected function activePageEditingQuery(Page $page, $minRange = null)
{
$query = $this->pageRevision->where('type', '=', 'update_draft')
->where('page_id', '=', $page->id)
->where('updated_at', '>', $page->updated_at)
->where('created_by', '!=', user()->id)
->with('createdBy');
if ($minRange !== null) {
$query = $query->where('updated_at', '>=', Carbon::now()->subMinutes($minRange));
}
return $query;
}
/**
* Restores a revision's content back into a page.
* @param Page $page
* @param Book $book
* @param int $revisionId
* @return Page
*/
public function restorePageRevision(Page $page, Book $book, $revisionId)
{
$page->revision_count++;
$this->savePageRevision($page);
$revision = $page->revisions()->where('id', '=', $revisionId)->first();
$page->fill($revision->toArray());
$page->slug = $this->findSuitableSlug('page', $page->name, $page->id, $book->id);
$page->text = $this->pageToPlainText($page);
$page->updated_by = user()->id;
$page->save();
$this->searchService->indexEntity($page);
return $page;
}
/**
* Save a page update draft.
* @param Page $page
* @param array $data
* @return PageRevision|Page
*/
public function updatePageDraft(Page $page, $data = [])
{
// If the page itself is a draft simply update that
if ($page->draft) {
$page->fill($data);
if (isset($data['html'])) {
$page->text = $this->pageToPlainText($page);
}
$page->save();
return $page;
}
// Otherwise save the data to a revision
$userId = user()->id;
$drafts = $this->userUpdatePageDraftsQuery($page, $userId)->get();
if ($drafts->count() > 0) {
$draft = $drafts->first();
} else {
$draft = $this->pageRevision->newInstance();
$draft->page_id = $page->id;
$draft->slug = $page->slug;
$draft->book_slug = $page->book->slug;
$draft->created_by = $userId;
$draft->type = 'update_draft';
}
$draft->fill($data);
if (setting('app-editor') !== 'markdown') {
$draft->markdown = '';
}
$draft->save();
return $draft;
}
/**
* Get a notification message concerning the editing activity on a particular page.
* @param Page $page
* @param null $minRange
* @return string
*/
public function getPageEditingActiveMessage(Page $page, $minRange = null)
{
$pageDraftEdits = $this->activePageEditingQuery($page, $minRange)->get();
$userMessage = $pageDraftEdits->count() > 1 ? trans('entities.pages_draft_edit_active.start_a', ['count' => $pageDraftEdits->count()]): trans('entities.pages_draft_edit_active.start_b', ['userName' => $pageDraftEdits->first()->createdBy->name]);
$timeMessage = $minRange === null ? trans('entities.pages_draft_edit_active.time_a') : trans('entities.pages_draft_edit_active.time_b', ['minCount'=>$minRange]);
return trans('entities.pages_draft_edit_active.message', ['start' => $userMessage, 'time' => $timeMessage]);
}
/**
* Change the page's parent to the given entity.
* @param Page $page
* @param Entity $parent
*/
public function changePageParent(Page $page, Entity $parent)
{
$book = $parent->isA('book') ? $parent : $parent->book;
$page->chapter_id = $parent->isA('chapter') ? $parent->id : 0;
$page->save();
if ($page->book->id !== $book->id) {
$page = $this->changeBook('page', $book->id, $page);
}
$page->load('book');
$this->permissionService->buildJointPermissionsForEntity($book);
}
/**
* Destroy a bookshelf instance
* @param \BookStack\Entities\Bookshelf $shelf
* @param Bookshelf $shelf
* @throws \Throwable
*/
public function destroyBookshelf(Bookshelf $shelf)
@@ -707,7 +1212,7 @@ class EntityRepo
/**
* Destroy the provided book and all its child entities.
* @param \BookStack\Entities\Book $book
* @param Book $book
* @throws NotifyException
* @throws \Throwable
*/
@@ -725,7 +1230,7 @@ class EntityRepo
/**
* Destroy a chapter and its relations.
* @param \BookStack\Entities\Chapter $chapter
* @param Chapter $chapter
* @throws \Throwable
*/
public function destroyChapter(Chapter $chapter)
@@ -767,7 +1272,7 @@ class EntityRepo
/**
* Destroy or handle the common relations connected to an entity.
* @param \BookStack\Entities\Entity $entity
* @param Entity $entity
* @throws \Throwable
*/
protected function destroyEntityCommonRelations(Entity $entity)
@@ -784,7 +1289,7 @@ class EntityRepo
/**
* Copy the permissions of a bookshelf to all child books.
* Returns the number of books that had permissions updated.
* @param \BookStack\Entities\Bookshelf $bookshelf
* @param Bookshelf $bookshelf
* @return int
* @throws \Throwable
*/

View File

@@ -1,7 +1,9 @@
<?php namespace BookStack\Uploads;
<?php namespace BookStack\Repos;
use BookStack\Auth\Permissions\PermissionService;
use BookStack\Entities\Page;
use BookStack\Image;
use BookStack\Page;
use BookStack\Services\ImageService;
use BookStack\Services\PermissionService;
use Symfony\Component\HttpFoundation\File\UploadedFile;
class ImageRepo
@@ -16,8 +18,8 @@ class ImageRepo
* ImageRepo constructor.
* @param Image $image
* @param ImageService $imageService
* @param \BookStack\Auth\Permissions\PermissionService $permissionService
* @param \BookStack\Entities\Page $page
* @param PermissionService $permissionService
* @param Page $page
*/
public function __construct(Image $image, ImageService $imageService, PermissionService $permissionService, Page $page)
{

View File

@@ -1,8 +1,10 @@
<?php namespace BookStack\Auth\Permissions;
<?php namespace BookStack\Repos;
use BookStack\Auth\Permissions;
use BookStack\Auth\Role;
use BookStack\Exceptions\PermissionsException;
use BookStack\RolePermission;
use BookStack\Role;
use BookStack\Services\PermissionService;
use Setting;
class PermissionsRepo
{
@@ -17,9 +19,9 @@ class PermissionsRepo
* PermissionsRepo constructor.
* @param RolePermission $permission
* @param Role $role
* @param \BookStack\Auth\Permissions\PermissionService $permissionService
* @param PermissionService $permissionService
*/
public function __construct(RolePermission $permission, Role $role, Permissions\PermissionService $permissionService)
public function __construct(RolePermission $permission, Role $role, PermissionService $permissionService)
{
$this->permission = $permission;
$this->role = $role;

View File

@@ -1,7 +1,8 @@
<?php namespace BookStack\Actions;
<?php namespace BookStack\Repos;
use BookStack\Auth\Permissions\PermissionService;
use BookStack\Entities\Entity;
use BookStack\Tag;
use BookStack\Entity;
use BookStack\Services\PermissionService;
/**
* Class TagRepo
@@ -16,9 +17,9 @@ class TagRepo
/**
* TagRepo constructor.
* @param \BookStack\Actions\Tag $attr
* @param \BookStack\Entities\Entity $ent
* @param \BookStack\Auth\Permissions\PermissionService $ps
* @param Tag $attr
* @param Entity $ent
* @param PermissionService $ps
*/
public function __construct(Tag $attr, Entity $ent, PermissionService $ps)
{
@@ -106,7 +107,7 @@ class TagRepo
/**
* Save an array of tags to an entity
* @param \BookStack\Entities\Entity $entity
* @param Entity $entity
* @param array $tags
* @return array|\Illuminate\Database\Eloquent\Collection
*/
@@ -127,7 +128,7 @@ class TagRepo
/**
* Create a new Tag instance from user input.
* @param $input
* @return \BookStack\Actions\Tag
* @return Tag
*/
protected function newInstanceFromInput($input)
{

View File

@@ -1,10 +1,10 @@
<?php namespace BookStack\Auth;
<?php namespace BookStack\Repos;
use Activity;
use BookStack\Entities\Repos\EntityRepo;
use BookStack\Exceptions\NotFoundException;
use BookStack\Exceptions\UserUpdateException;
use BookStack\Uploads\Image;
use BookStack\Image;
use BookStack\Role;
use BookStack\User;
use Exception;
use Images;
@@ -43,7 +43,7 @@ class UserRepo
*/
public function getById($id)
{
return $this->user->newQuery()->findOrFail($id);
return $this->user->findOrFail($id);
}
/**
@@ -80,13 +80,15 @@ class UserRepo
* Creates a new user and attaches a role to them.
* @param array $data
* @param boolean $verifyEmail
* @return \BookStack\Auth\User
* @return User
*/
public function registerNew(array $data, $verifyEmail = false)
{
$user = $this->create($data, $verifyEmail);
$this->attachDefaultRole($user);
$this->downloadAndAssignUserAvatar($user);
// Get avatar from gravatar and save
$this->downloadGravatarToUserAvatar($user);
return $user;
}
@@ -120,7 +122,7 @@ class UserRepo
/**
* Checks if the give user is the only admin.
* @param \BookStack\Auth\User $user
* @param User $user
* @return bool
*/
public function isOnlyAdmin(User $user)
@@ -136,48 +138,15 @@ class UserRepo
return true;
}
/**
* Set the assigned user roles via an array of role IDs.
* @param User $user
* @param array $roles
* @throws UserUpdateException
*/
public function setUserRoles(User $user, array $roles)
{
if ($this->demotingLastAdmin($user, $roles)) {
throw new UserUpdateException(trans('errors.role_cannot_remove_only_admin'), $user->getEditUrl());
}
$user->roles()->sync($roles);
}
/**
* Check if the given user is the last admin and their new roles no longer
* contains the admin role.
* @param User $user
* @param array $newRoles
* @return bool
*/
protected function demotingLastAdmin(User $user, array $newRoles) : bool
{
if ($this->isOnlyAdmin($user)) {
$adminRole = $this->role->getSystemRole('admin');
if (!in_array(strval($adminRole->id), $newRoles)) {
return true;
}
}
return false;
}
/**
* Create a new basic instance of user.
* @param array $data
* @param boolean $verifyEmail
* @return \BookStack\Auth\User
* @return User
*/
public function create(array $data, $verifyEmail = false)
{
return $this->user->forceCreate([
'name' => $data['name'],
'email' => $data['email'],
@@ -188,7 +157,7 @@ class UserRepo
/**
* Remove the given user from storage, Delete all related content.
* @param \BookStack\Auth\User $user
* @param User $user
* @throws Exception
*/
public function destroy(User $user)
@@ -205,7 +174,7 @@ class UserRepo
/**
* Get the latest activity for a user.
* @param \BookStack\Auth\User $user
* @param User $user
* @param int $count
* @param int $page
* @return array
@@ -217,7 +186,7 @@ class UserRepo
/**
* Get the recently created content for this given user.
* @param \BookStack\Auth\User $user
* @param User $user
* @param int $count
* @return mixed
*/
@@ -238,15 +207,15 @@ class UserRepo
/**
* Get asset created counts for the give user.
* @param \BookStack\Auth\User $user
* @param User $user
* @return array
*/
public function getAssetCounts(User $user)
{
return [
'pages' => $this->entityRepo->getUserTotalCreated('page', $user),
'chapters' => $this->entityRepo->getUserTotalCreated('chapter', $user),
'books' => $this->entityRepo->getUserTotalCreated('book', $user),
'pages' => $this->entityRepo->page->where('created_by', '=', $user->id)->count(),
'chapters' => $this->entityRepo->chapter->where('created_by', '=', $user->id)->count(),
'books' => $this->entityRepo->book->where('created_by', '=', $user->id)->count(),
];
}
@@ -270,24 +239,25 @@ class UserRepo
}
/**
* Get an avatar image for a user and set it as their avatar.
* Returns early if avatars disabled or not set in config.
* Get a gravatar image for a user and set it as their avatar.
* Does not run if gravatar disabled in config.
* @param User $user
* @return bool
*/
public function downloadAndAssignUserAvatar(User $user)
public function downloadGravatarToUserAvatar(User $user)
{
if (!Images::avatarFetchEnabled()) {
// Get avatar from gravatar and save
if (!config('services.gravatar')) {
return false;
}
try {
$avatar = Images::saveUserAvatar($user);
$avatar = Images::saveUserGravatar($user);
$user->avatar()->associate($avatar);
$user->save();
return true;
} catch (Exception $e) {
\Log::error('Failed to save user avatar image');
\Log::error('Failed to save user gravatar image');
return false;
}
}

View File

@@ -1,7 +1,4 @@
<?php namespace BookStack\Auth;
use BookStack\Auth\Permissions\JointPermission;
use BookStack\Model;
<?php namespace BookStack;
class Role extends Model
{
@@ -30,7 +27,7 @@ class Role extends Model
*/
public function permissions()
{
return $this->belongsToMany(Permissions\RolePermission::class, 'permission_role', 'role_id', 'permission_id');
return $this->belongsToMany(RolePermission::class, 'permission_role', 'role_id', 'permission_id');
}
/**
@@ -51,18 +48,18 @@ class Role extends Model
/**
* Add a permission to this role.
* @param \BookStack\Auth\Permissions\RolePermission $permission
* @param RolePermission $permission
*/
public function attachPermission(Permissions\RolePermission $permission)
public function attachPermission(RolePermission $permission)
{
$this->permissions()->attach($permission->id);
}
/**
* Detach a single permission from this role.
* @param \BookStack\Auth\Permissions\RolePermission $permission
* @param RolePermission $permission
*/
public function detachPermission(Permissions\RolePermission $permission)
public function detachPermission(RolePermission $permission)
{
$this->permissions()->detach($permission->id);
}

View File

@@ -1,7 +1,4 @@
<?php namespace BookStack\Auth\Permissions;
use BookStack\Auth\Role;
use BookStack\Model;
<?php namespace BookStack;
class RolePermission extends Model
{

View File

@@ -1,6 +1,4 @@
<?php namespace BookStack\Entities;
use BookStack\Model;
<?php namespace BookStack;
class SearchTerm extends Model
{

View File

@@ -1,7 +1,7 @@
<?php namespace BookStack\Actions;
<?php namespace BookStack\Services;
use BookStack\Auth\Permissions\PermissionService;
use BookStack\Entities\Entity;
use BookStack\Activity;
use BookStack\Entity;
use Session;
class ActivityService
@@ -12,7 +12,7 @@ class ActivityService
/**
* ActivityService constructor.
* @param \BookStack\Actions\Activity $activity
* @param Activity $activity
* @param PermissionService $permissionService
*/
public function __construct(Activity $activity, PermissionService $permissionService)

View File

@@ -1,6 +1,7 @@
<?php namespace BookStack\Uploads;
<?php namespace BookStack\Services;
use BookStack\Exceptions\FileUploadException;
use BookStack\Attachment;
use Exception;
use Symfony\Component\HttpFoundation\File\UploadedFile;

View File

@@ -1,11 +1,11 @@
<?php namespace BookStack\Auth\Access;
<?php namespace BookStack\Services;
use BookStack\Auth\User;
use BookStack\Auth\UserRepo;
use BookStack\Notifications\ConfirmEmail;
use BookStack\Repos\UserRepo;
use Carbon\Carbon;
use BookStack\Exceptions\ConfirmationEmailException;
use BookStack\Exceptions\UserRegistrationException;
use BookStack\Notifications\ConfirmEmail;
use Carbon\Carbon;
use BookStack\User;
use Illuminate\Database\Connection as Database;
class EmailConfirmationService
@@ -16,7 +16,7 @@ class EmailConfirmationService
/**
* EmailConfirmationService constructor.
* @param Database $db
* @param \BookStack\Auth\UserRepo $users
* @param UserRepo $users
*/
public function __construct(Database $db, UserRepo $users)
{
@@ -27,7 +27,7 @@ class EmailConfirmationService
/**
* Create new confirmation for a user,
* Also removes any existing old ones.
* @param \BookStack\Auth\User $user
* @param User $user
* @throws ConfirmationEmailException
*/
public function sendConfirmation(User $user)
@@ -88,7 +88,7 @@ class EmailConfirmationService
/**
* Delete all email confirmations that belong to a user.
* @param \BookStack\Auth\User $user
* @param User $user
* @return mixed
*/
public function deleteConfirmationsByUser(User $user)

View File

@@ -1,23 +1,19 @@
<?php namespace BookStack\Entities;
<?php namespace BookStack\Services;
use BookStack\Entities\Repos\EntityRepo;
use BookStack\Uploads\ImageService;
use BookStack\Exceptions\ExportException;
use BookStack\Book;
use BookStack\Chapter;
use BookStack\Page;
use BookStack\Repos\EntityRepo;
class ExportService
{
protected $contentMatching = [
'video' => ["www.youtube.com", "player.vimeo.com", "www.dailymotion.com"],
'map' => ['maps.google.com']
];
protected $entityRepo;
protected $imageService;
/**
* ExportService constructor.
* @param EntityRepo $entityRepo
* @param ImageService $imageService
* @param $entityRepo
*/
public function __construct(EntityRepo $entityRepo, ImageService $imageService)
{
@@ -28,7 +24,7 @@ class ExportService
/**
* Convert a page to a self-contained HTML file.
* Includes required CSS & image content. Images are base64 encoded into the HTML.
* @param \BookStack\Entities\Page $page
* @param Page $page
* @return mixed|string
* @throws \Throwable
*/
@@ -43,7 +39,7 @@ class ExportService
/**
* Convert a chapter to a self-contained HTML file.
* @param \BookStack\Entities\Chapter $chapter
* @param Chapter $chapter
* @return mixed|string
* @throws \Throwable
*/
@@ -79,22 +75,21 @@ class ExportService
/**
* Convert a page to a PDF file.
* @param Page $page
* @param bool $isTesting
* @return mixed|string
* @throws \Throwable
*/
public function pageToPdf(Page $page, bool $isTesting = false)
public function pageToPdf(Page $page)
{
$this->entityRepo->renderPage($page);
$html = view('pages/pdf', [
'page' => $page
])->render();
return $this->htmlToPdf($html, $isTesting);
return $this->htmlToPdf($html);
}
/**
* Convert a chapter to a PDF file.
* @param \BookStack\Entities\Chapter $chapter
* @param Chapter $chapter
* @return mixed|string
* @throws \Throwable
*/
@@ -113,7 +108,7 @@ class ExportService
/**
* Convert a book to a PDF file
* @param \BookStack\Entities\Book $book
* @param Book $book
* @return string
* @throws \Throwable
*/
@@ -130,16 +125,12 @@ class ExportService
/**
* Convert normal webpage HTML to a PDF.
* @param $html
* @param $isTesting
* @return string
* @throws \Exception
*/
protected function htmlToPdf($html, $isTesting = false)
protected function htmlToPdf($html)
{
$containedHtml = $this->containHtml($html, true);
if ($isTesting) {
return $containedHtml;
}
$containedHtml = $this->containHtml($html);
$useWKHTML = config('snappy.pdf.binary') !== false;
if ($useWKHTML) {
$pdf = \SnappyPDF::loadHTML($containedHtml);
@@ -153,64 +144,46 @@ class ExportService
/**
* Bundle of the contents of a html file to be self-contained.
* @param $htmlContent
* @param bool $isPDF
* @return mixed|string
* @throws \BookStack\Exceptions\ExportException
* @throws \Exception
*/
protected function containHtml(string $htmlContent, bool $isPDF = false) : string
protected function containHtml($htmlContent)
{
$dom = $this->getDOM($htmlContent);
if ($dom === false) {
throw new ExportException(trans('errors.dom_parse_error'));
}
$imageTagsOutput = [];
preg_match_all("/\<img.*src\=(\'|\")(.*?)(\'|\").*?\>/i", $htmlContent, $imageTagsOutput);
// replace image src with base64 encoded image strings
$images = $dom->getElementsByTagName('img');
foreach ($images as $img) {
$base64String = $this->imageService->imageUriToBase64($img->getAttribute('src'));
if ($base64String !== null) {
$img->setAttribute('src', $base64String);
$dom->saveHTML($img);
// Replace image src with base64 encoded image strings
if (isset($imageTagsOutput[0]) && count($imageTagsOutput[0]) > 0) {
foreach ($imageTagsOutput[0] as $index => $imgMatch) {
$oldImgTagString = $imgMatch;
$srcString = $imageTagsOutput[2][$index];
$imageEncoded = $this->imageService->imageUriToBase64($srcString);
if ($imageEncoded === null) {
$imageEncoded = $srcString;
}
$newImgTagString = str_replace($srcString, $imageEncoded, $oldImgTagString);
$htmlContent = str_replace($oldImgTagString, $newImgTagString, $htmlContent);
}
}
// replace all relative hrefs.
$links = $dom->getElementsByTagName('a');
foreach ($links as $link) {
$href = $link->getAttribute('href');
if (strpos(trim($href), 'http') !== 0) {
$newHref = url($href);
$link->setAttribute('href', $newHref);
$dom->saveHTML($link);
$linksOutput = [];
preg_match_all("/\<a.*href\=(\'|\")(.*?)(\'|\").*?\>/i", $htmlContent, $linksOutput);
// Replace image src with base64 encoded image strings
if (isset($linksOutput[0]) && count($linksOutput[0]) > 0) {
foreach ($linksOutput[0] as $index => $linkMatch) {
$oldLinkString = $linkMatch;
$srcString = $linksOutput[2][$index];
if (strpos(trim($srcString), 'http') !== 0) {
$newSrcString = url($srcString);
$newLinkString = str_replace($srcString, $newSrcString, $oldLinkString);
$htmlContent = str_replace($oldLinkString, $newLinkString, $htmlContent);
}
}
}
// replace all src in video, audio and iframe tags
$xmlDoc = new \DOMXPath($dom);
$srcElements = $xmlDoc->query('//video | //audio | //iframe');
foreach ($srcElements as $element) {
$element = $this->fixRelativeSrc($element);
$dom->saveHTML($element);
if ($isPDF) {
$src = $element->getAttribute('src');
$label = $this->getContentLabel($src);
$div = $dom->createElement('div');
$textNode = $dom->createTextNode($label);
$anchor = $dom->createElement('a');
$anchor->setAttribute('href', $src);
$anchor->textContent = $src;
$div->appendChild($textNode);
$div->appendChild($anchor);
$element->parentNode->replaceChild($div, $element);
}
}
return $dom->saveHTML();
// Replace any relative links with system domain
return $htmlContent;
}
/**
@@ -218,43 +191,11 @@ class ExportService
* This method filters any bad looking content to provide a nice final output.
* @param Page $page
* @return mixed
* @throws \BookStack\Exceptions\ExportException
*/
public function pageToPlainText(Page $page)
{
$html = $this->entityRepo->renderPage($page);
$dom = $this->getDom($html);
if ($dom === false) {
throw new ExportException(trans('errors.dom_parse_error'));
}
// handle anchor tags.
$links = $dom->getElementsByTagName('a');
foreach ($links as $link) {
$href = $link->getAttribute('href');
if (strpos(trim($href), 'http') !== 0) {
$newHref = url($href);
$link->setAttribute('href', $newHref);
}
$link->textContent = trim($link->textContent . " ($href)");
$dom->saveHTML();
}
$xmlDoc = new \DOMXPath($dom);
$srcElements = $xmlDoc->query('//video | //audio | //iframe | //img');
foreach ($srcElements as $element) {
$element = $this->fixRelativeSrc($element);
$fixedSrc = $element->getAttribute('src');
$label = $this->getContentLabel($fixedSrc);
$finalLabel = "\n\n$label $fixedSrc\n\n";
$textNode = $dom->createTextNode($finalLabel);
$element->parentNode->replaceChild($textNode, $element);
}
$text = strip_tags($dom->saveHTML());
$text = strip_tags($html);
// Replace multiple spaces with single spaces
$text = preg_replace('/\ {2,}/', ' ', $text);
// Reduce multiple horrid whitespace characters.
@@ -267,7 +208,7 @@ class ExportService
/**
* Convert a chapter into a plain text string.
* @param \BookStack\Entities\Chapter $chapter
* @param Chapter $chapter
* @return string
*/
public function chapterToPlainText(Chapter $chapter)
@@ -298,37 +239,4 @@ class ExportService
}
return $text;
}
protected function getDom(string $htmlContent) : \DOMDocument
{
// See - https://stackoverflow.com/a/17559716/903324
$dom = new \DOMDocument();
libxml_use_internal_errors(true);
$dom->loadHTML($htmlContent);
libxml_clear_errors();
return $dom;
}
protected function fixRelativeSrc(\DOMElement $element): \DOMElement
{
$src = $element->getAttribute('src');
if (strpos(trim($src), 'http') !== 0) {
$newSrc = 'https:' . $src;
$element->setAttribute('src', $newSrc);
}
return $element;
}
protected function getContentLabel(string $src) : string
{
foreach ($this->contentMatching as $key => $possibleValues) {
foreach ($possibleValues as $value) {
if (strpos($src, $value)) {
return trans("entities.$key");
}
}
}
return trans('entities.embedded_content');
}
}

View File

@@ -1,4 +1,4 @@
<?php namespace BookStack\Facades;
<?php namespace BookStack\Services\Facades;
use Illuminate\Support\Facades\Facade;

View File

@@ -1,4 +1,4 @@
<?php namespace BookStack\Facades;
<?php namespace BookStack\Services\Facades;
use Illuminate\Support\Facades\Facade;

View File

@@ -1,4 +1,4 @@
<?php namespace BookStack\Facades;
<?php namespace BookStack\Services\Facades;
use Illuminate\Support\Facades\Facade;

View File

@@ -1,4 +1,4 @@
<?php namespace BookStack\Facades;
<?php namespace BookStack\Services\Facades;
use Illuminate\Support\Facades\Facade;

View File

@@ -1,14 +1,14 @@
<?php namespace BookStack\Uploads;
<?php namespace BookStack\Services;
use BookStack\Auth\User;
use BookStack\Exceptions\HttpFetchException;
use BookStack\Exceptions\ImageUploadException;
use BookStack\Image;
use BookStack\User;
use DB;
use Exception;
use Illuminate\Contracts\Cache\Repository as Cache;
use Illuminate\Contracts\Filesystem\Factory as FileSystem;
use Intervention\Image\Exception\NotSupportedException;
use Intervention\Image\ImageManager;
use Illuminate\Contracts\Filesystem\Factory as FileSystem;
use Illuminate\Contracts\Cache\Repository as Cache;
use Symfony\Component\HttpFoundation\File\UploadedFile;
class ImageService extends UploadService
@@ -18,7 +18,6 @@ class ImageService extends UploadService
protected $cache;
protected $storageUrl;
protected $image;
protected $http;
/**
* ImageService constructor.
@@ -26,14 +25,12 @@ class ImageService extends UploadService
* @param ImageManager $imageTool
* @param FileSystem $fileSystem
* @param Cache $cache
* @param HttpFetcher $http
*/
public function __construct(Image $image, ImageManager $imageTool, FileSystem $fileSystem, Cache $cache, HttpFetcher $http)
public function __construct(Image $image, ImageManager $imageTool, FileSystem $fileSystem, Cache $cache)
{
$this->image = $image;
$this->imageTool = $imageTool;
$this->cache = $cache;
$this->http = $http;
parent::__construct($fileSystem);
}
@@ -99,9 +96,8 @@ class ImageService extends UploadService
private function saveNewFromUrl($url, $type, $imageName = false)
{
$imageName = $imageName ? $imageName : basename($url);
try {
$imageData = $this->http->fetch($url);
} catch (HttpFetchException $exception) {
$imageData = file_get_contents($url);
if ($imageData === false) {
throw new \Exception(trans('errors.cannot_get_image_from_url', ['url' => $url]));
}
return $this->saveNew($imageName, $imageData, $type);
@@ -284,57 +280,24 @@ class ImageService extends UploadService
}
/**
* Save an avatar image from an external service.
* @param \BookStack\Auth\User $user
* Save a gravatar image and set a the profile image for a user.
* @param User $user
* @param int $size
* @return Image
* @return mixed
* @throws Exception
*/
public function saveUserAvatar(User $user, $size = 500)
public function saveUserGravatar(User $user, $size = 500)
{
$avatarUrl = $this->getAvatarUrl();
$email = strtolower(trim($user->email));
$replacements = [
'${hash}' => md5($email),
'${size}' => $size,
'${email}' => urlencode($email),
];
$userAvatarUrl = strtr($avatarUrl, $replacements);
$imageName = str_replace(' ', '-', $user->name . '-avatar.png');
$image = $this->saveNewFromUrl($userAvatarUrl, 'user', $imageName);
$emailHash = md5(strtolower(trim($user->email)));
$url = 'https://www.gravatar.com/avatar/' . $emailHash . '?s=' . $size . '&d=identicon';
$imageName = str_replace(' ', '-', $user->name . '-gravatar.png');
$image = $this->saveNewFromUrl($url, 'user', $imageName);
$image->created_by = $user->id;
$image->updated_by = $user->id;
$image->save();
return $image;
}
/**
* Check if fetching external avatars is enabled.
* @return bool
*/
public function avatarFetchEnabled()
{
$fetchUrl = $this->getAvatarUrl();
return is_string($fetchUrl) && strpos($fetchUrl, 'http') === 0;
}
/**
* Get the URL to fetch avatars from.
* @return string|mixed
*/
protected function getAvatarUrl()
{
$url = trim(config('services.avatar_url'));
if (empty($url) && !config('services.disable_services')) {
$url = 'https://www.gravatar.com/avatar/${hash}?s=${size}&d=identicon';
}
return $url;
}
/**
* Delete gallery and drawings that are not within HTML content of pages or page revisions.
@@ -403,7 +366,14 @@ class ImageService extends UploadService
}
} else {
try {
$imageData = $this->http->fetch($uri);
$ch = curl_init();
curl_setopt_array($ch, [CURLOPT_URL => $uri, CURLOPT_RETURNTRANSFER => 1, CURLOPT_CONNECTTIMEOUT => 5]);
$imageData = curl_exec($ch);
$err = curl_error($ch);
curl_close($ch);
if ($err) {
throw new \Exception("Image fetch failed, Received error: " . $err);
}
} catch (\Exception $e) {
}
}

View File

@@ -1,4 +1,4 @@
<?php namespace BookStack\Auth\Access;
<?php namespace BookStack\Services;
/**
* Class Ldap
@@ -92,27 +92,4 @@ class Ldap
{
return ldap_bind($ldapConnection, $bindRdn, $bindPassword);
}
/**
* Explode a LDAP dn string into an array of components.
* @param string $dn
* @param int $withAttrib
* @return array
*/
public function explodeDn(string $dn, int $withAttrib)
{
return ldap_explode_dn($dn, $withAttrib);
}
/**
* Escape a string for use in an LDAP filter.
* @param string $value
* @param string $ignore
* @param int $flags
* @return string
*/
public function escape(string $value, string $ignore = "", int $flags = 0)
{
return ldap_escape($value, $ignore, $flags);
}
}

View File

@@ -1,10 +1,9 @@
<?php namespace BookStack\Auth\Access;
<?php namespace BookStack\Services;
use BookStack\Auth\Access;
use BookStack\Auth\Role;
use BookStack\Auth\User;
use BookStack\Auth\UserRepo;
use BookStack\Exceptions\LdapException;
use BookStack\Repos\UserRepo;
use BookStack\Role;
use BookStack\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Builder;
@@ -25,9 +24,9 @@ class LdapService
/**
* LdapService constructor.
* @param Ldap $ldap
* @param \BookStack\Auth\UserRepo $userRepo
* @param UserRepo $userRepo
*/
public function __construct(Access\Ldap $ldap, UserRepo $userRepo)
public function __construct(Ldap $ldap, UserRepo $userRepo)
{
$this->ldap = $ldap;
$this->config = config('services.ldap');
@@ -107,7 +106,6 @@ class LdapService
if ($ldapUser === null) {
return false;
}
if ($ldapUser['uid'] !== $user->external_auth_id) {
return false;
}
@@ -170,16 +168,6 @@ class LdapService
}
$hostName = $ldapServer[0] . ($hasProtocol?':':'') . $ldapServer[1];
$defaultPort = $ldapServer[0] === 'ldaps' ? 636 : 389;
/*
* Check if TLS_INSECURE is set. The handle is set to NULL due to the nature of
* the LDAP_OPT_X_TLS_REQUIRE_CERT option. It can only be set globally and not
* per handle.
*/
if($this->config['tls_insecure']) {
$this->ldap->setOption(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_NEVER);
}
$ldapConnection = $this->ldap->connect($hostName, count($ldapServer) > 2 ? intval($ldapServer[2]) : $defaultPort);
if ($ldapConnection === false) {
@@ -206,7 +194,7 @@ class LdapService
$newAttrs = [];
foreach ($attrs as $key => $attrText) {
$newKey = '${' . $key . '}';
$newAttrs[$newKey] = $this->ldap->escape($attrText);
$newAttrs[$newKey] = $attrText;
}
return strtr($filterString, $newAttrs);
}
@@ -276,8 +264,7 @@ class LdapService
$baseDn = $this->config['base_dn'];
$groupsAttr = strtolower($this->config['group_attribute']);
$groupFilter = 'CN=' . $this->ldap->escape($groupName);
$groups = $this->ldap->searchAndGetEntries($ldapConnection, $baseDn, $groupFilter, [$groupsAttr]);
$groups = $this->ldap->searchAndGetEntries($ldapConnection, $baseDn, 'CN='.$groupName, [$groupsAttr]);
if ($groups['count'] === 0) {
return [];
}
@@ -289,32 +276,29 @@ class LdapService
/**
* Filter out LDAP CN and DN language in a ldap search return
* Gets the base CN (common name) of the string
* @param array $userGroupSearchResponse
* @param string $ldapSearchReturn
* @return array
*/
protected function groupFilter(array $userGroupSearchResponse)
protected function groupFilter($ldapSearchReturn)
{
$groupsAttr = strtolower($this->config['group_attribute']);
$ldapGroups = [];
$count = 0;
if (isset($userGroupSearchResponse[$groupsAttr]['count'])) {
$count = (int) $userGroupSearchResponse[$groupsAttr]['count'];
if (isset($ldapSearchReturn[$groupsAttr]['count'])) {
$count = (int) $ldapSearchReturn[$groupsAttr]['count'];
}
for ($i=0; $i<$count; $i++) {
$dnComponents = $this->ldap->explodeDn($userGroupSearchResponse[$groupsAttr][$i], 1);
$dnComponents = ldap_explode_dn($ldapSearchReturn[$groupsAttr][$i], 1);
if (!in_array($dnComponents[0], $ldapGroups)) {
$ldapGroups[] = $dnComponents[0];
}
}
return $ldapGroups;
}
/**
* Sync the LDAP groups to the user roles for the current user
* @param \BookStack\Auth\User $user
* @param \BookStack\User $user
* @param string $username
* @throws LdapException
*/
@@ -363,7 +347,7 @@ class LdapService
/**
* Check a role against an array of group names to see if it matches.
* Checked against role 'external_auth_id' if set otherwise the name of the role.
* @param \BookStack\Auth\Role $role
* @param Role $role
* @param array $groupNames
* @return bool
*/

View File

@@ -1,14 +1,15 @@
<?php namespace BookStack\Auth\Permissions;
<?php namespace BookStack\Services;
use BookStack\Auth\Permissions;
use BookStack\Auth\Role;
use BookStack\Entities\Book;
use BookStack\Entities\Bookshelf;
use BookStack\Entities\Chapter;
use BookStack\Entities\Entity;
use BookStack\Entities\EntityProvider;
use BookStack\Entities\Page;
use BookStack\Book;
use BookStack\Bookshelf;
use BookStack\Chapter;
use BookStack\Entity;
use BookStack\EntityPermission;
use BookStack\JointPermission;
use BookStack\Ownable;
use BookStack\Page;
use BookStack\Role;
use BookStack\User;
use Illuminate\Database\Connection;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\Builder as QueryBuilder;
@@ -22,31 +23,17 @@ class PermissionService
protected $userRoles = false;
protected $currentUserModel = false;
/**
* @var Connection
*/
public $book;
public $chapter;
public $page;
public $bookshelf;
protected $db;
/**
* @var JointPermission
*/
protected $jointPermission;
/**
* @var Role
*/
protected $role;
/**
* @var EntityPermission
*/
protected $entityPermission;
/**
* @var EntityProvider
*/
protected $entityProvider;
protected $entityCache;
/**
@@ -55,20 +42,29 @@ class PermissionService
* @param EntityPermission $entityPermission
* @param Role $role
* @param Connection $db
* @param EntityProvider $entityProvider
* @param Bookshelf $bookshelf
* @param Book $book
* @param Chapter $chapter
* @param Page $page
*/
public function __construct(
JointPermission $jointPermission,
Permissions\EntityPermission $entityPermission,
EntityPermission $entityPermission,
Role $role,
Connection $db,
EntityProvider $entityProvider
Bookshelf $bookshelf,
Book $book,
Chapter $chapter,
Page $page
) {
$this->db = $db;
$this->jointPermission = $jointPermission;
$this->entityPermission = $entityPermission;
$this->role = $role;
$this->entityProvider = $entityProvider;
$this->bookshelf = $bookshelf;
$this->book = $book;
$this->chapter = $chapter;
$this->page = $page;
}
/**
@@ -82,7 +78,7 @@ class PermissionService
/**
* Prepare the local entity cache and ensure it's empty
* @param \BookStack\Entities\Entity[] $entities
* @param Entity[] $entities
*/
protected function readyEntityCache($entities = [])
{
@@ -108,7 +104,7 @@ class PermissionService
return $this->entityCache['book']->get($bookId);
}
$book = $this->entityProvider->book->find($bookId);
$book = $this->book->find($bookId);
if ($book === null) {
$book = false;
}
@@ -119,7 +115,7 @@ class PermissionService
/**
* Get a chapter via ID, Checks local cache
* @param $chapterId
* @return \BookStack\Entities\Book
* @return Book
*/
protected function getChapter($chapterId)
{
@@ -127,7 +123,7 @@ class PermissionService
return $this->entityCache['chapter']->get($chapterId);
}
$chapter = $this->entityProvider->chapter->find($chapterId);
$chapter = $this->chapter->find($chapterId);
if ($chapter === null) {
$chapter = false;
}
@@ -176,7 +172,7 @@ class PermissionService
});
// Chunk through all bookshelves
$this->entityProvider->bookshelf->newQuery()->select(['id', 'restricted', 'created_by'])
$this->bookshelf->newQuery()->select(['id', 'restricted', 'created_by'])
->chunk(50, function ($shelves) use ($roles) {
$this->buildJointPermissionsForShelves($shelves, $roles);
});
@@ -188,8 +184,7 @@ class PermissionService
*/
protected function bookFetchQuery()
{
return $this->entityProvider->book->newQuery()
->select(['id', 'restricted', 'created_by'])->with(['chapters' => function ($query) {
return $this->book->newQuery()->select(['id', 'restricted', 'created_by'])->with(['chapters' => function ($query) {
$query->select(['id', 'restricted', 'created_by', 'book_id']);
}, 'pages' => function ($query) {
$query->select(['id', 'restricted', 'created_by', 'book_id', 'chapter_id']);
@@ -239,7 +234,7 @@ class PermissionService
/**
* Rebuild the entity jointPermissions for a particular entity.
* @param \BookStack\Entities\Entity $entity
* @param Entity $entity
* @throws \Throwable
*/
public function buildJointPermissionsForEntity(Entity $entity)
@@ -295,7 +290,7 @@ class PermissionService
});
// Chunk through all bookshelves
$this->entityProvider->bookshelf->newQuery()->select(['id', 'restricted', 'created_by'])
$this->bookshelf->newQuery()->select(['id', 'restricted', 'created_by'])
->chunk(50, function ($shelves) use ($roles) {
$this->buildJointPermissionsForShelves($shelves, $roles);
});
@@ -334,7 +329,7 @@ class PermissionService
/**
* Delete all of the entity jointPermissions for a list of entities.
* @param \BookStack\Entities\Entity[] $entities
* @param Entity[] $entities
* @throws \Throwable
*/
protected function deleteManyJointPermissionsForEntities($entities)
@@ -415,7 +410,7 @@ class PermissionService
/**
* Get the actions related to an entity.
* @param \BookStack\Entities\Entity $entity
* @param Entity $entity
* @return array
*/
protected function getActions(Entity $entity)
@@ -501,7 +496,7 @@ class PermissionService
/**
* Create an array of data with the information of an entity jointPermissions.
* Used to build data for bulk insertion.
* @param \BookStack\Entities\Entity $entity
* @param Entity $entity
* @param Role $role
* @param $action
* @param $permissionAll
@@ -559,7 +554,7 @@ class PermissionService
/**
* Check if an entity has restrictions set on itself or its
* parent tree.
* @param \BookStack\Entities\Entity $entity
* @param Entity $entity
* @param $action
* @return bool|mixed
*/
@@ -609,9 +604,7 @@ class PermissionService
*/
public function bookChildrenQuery($book_id, $filterDrafts = false, $fetchPageContent = false)
{
$entities = $this->entityProvider;
$pageSelect = $this->db->table('pages')->selectRaw($entities->page->entityRawQuery($fetchPageContent))
->where('book_id', '=', $book_id)->where(function ($query) use ($filterDrafts) {
$pageSelect = $this->db->table('pages')->selectRaw($this->page->entityRawQuery($fetchPageContent))->where('book_id', '=', $book_id)->where(function ($query) use ($filterDrafts) {
$query->where('draft', '=', 0);
if (!$filterDrafts) {
$query->orWhere(function ($query) {
@@ -619,7 +612,7 @@ class PermissionService
});
}
});
$chapterSelect = $this->db->table('chapters')->selectRaw($entities->chapter->entityRawQuery())->where('book_id', '=', $book_id);
$chapterSelect = $this->db->table('chapters')->selectRaw($this->chapter->entityRawQuery())->where('book_id', '=', $book_id);
$query = $this->db->query()->select('*')->from($this->db->raw("({$pageSelect->toSql()} UNION {$chapterSelect->toSql()}) AS U"))
->mergeBindings($pageSelect)->mergeBindings($chapterSelect);
@@ -642,7 +635,7 @@ class PermissionService
/**
* Add restrictions for a generic entity
* @param string $entityType
* @param Builder|\BookStack\Entities\Entity $query
* @param Builder|Entity $query
* @param string $action
* @return Builder
*/
@@ -710,13 +703,12 @@ class PermissionService
$this->currentAction = 'view';
$tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn];
$pageMorphClass = $this->entityProvider->page->getMorphClass();
$q = $query->where(function ($query) use ($tableDetails, $pageMorphClass) {
$query->where(function ($query) use (&$tableDetails, $pageMorphClass) {
$query->whereExists(function ($permissionQuery) use (&$tableDetails, $pageMorphClass) {
$q = $query->where(function ($query) use ($tableDetails) {
$query->where(function ($query) use (&$tableDetails) {
$query->whereExists(function ($permissionQuery) use (&$tableDetails) {
$permissionQuery->select('id')->from('joint_permissions')
->whereRaw('joint_permissions.entity_id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
->where('entity_type', '=', $pageMorphClass)
->where('entity_type', '=', 'Bookstack\\Page')
->where('action', '=', $this->currentAction)
->whereIn('role_id', $this->getRoles())
->where(function ($query) {
@@ -734,7 +726,7 @@ class PermissionService
/**
* Get the current user
* @return \BookStack\Auth\User
* @return User
*/
private function currentUser()
{

View File

@@ -1,34 +1,30 @@
<?php namespace BookStack\Entities;
<?php namespace BookStack\Services;
use BookStack\Auth\Permissions\PermissionService;
use BookStack\Book;
use BookStack\Bookshelf;
use BookStack\Chapter;
use BookStack\Entity;
use BookStack\Page;
use BookStack\SearchTerm;
use Illuminate\Database\Connection;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Support\Collection;
class SearchService
{
/**
* @var SearchTerm
*/
protected $searchTerm;
/**
* @var EntityProvider
*/
protected $entityProvider;
/**
* @var Connection
*/
protected $bookshelf;
protected $book;
protected $chapter;
protected $page;
protected $db;
/**
* @var PermissionService
*/
protected $permissionService;
/**
* @var Entity[]
*/
protected $entities;
/**
* Acceptable operators to be used in a query
@@ -39,15 +35,27 @@ class SearchService
/**
* SearchService constructor.
* @param SearchTerm $searchTerm
* @param EntityProvider $entityProvider
* @param Bookshelf $bookshelf
* @param Book $book
* @param Chapter $chapter
* @param Page $page
* @param Connection $db
* @param PermissionService $permissionService
*/
public function __construct(SearchTerm $searchTerm, EntityProvider $entityProvider, Connection $db, PermissionService $permissionService)
public function __construct(SearchTerm $searchTerm, Bookshelf $bookshelf, Book $book, Chapter $chapter, Page $page, Connection $db, PermissionService $permissionService)
{
$this->searchTerm = $searchTerm;
$this->entityProvider = $entityProvider;
$this->bookshelf = $bookshelf;
$this->book = $book;
$this->chapter = $chapter;
$this->page = $page;
$this->db = $db;
$this->entities = [
'bookshelf' => $this->bookshelf,
'page' => $this->page,
'chapter' => $this->chapter,
'book' => $this->book
];
$this->permissionService = $permissionService;
}
@@ -72,7 +80,7 @@ class SearchService
public function searchEntities($searchString, $entityType = 'all', $page = 1, $count = 20, $action = 'view')
{
$terms = $this->parseSearchString($searchString);
$entityTypes = array_keys($this->entityProvider->all());
$entityTypes = array_keys($this->entities);
$entityTypesToSearch = $entityTypes;
if ($entityType !== 'all') {
@@ -169,17 +177,17 @@ class SearchService
* @param array $terms
* @param string $entityType
* @param string $action
* @return EloquentBuilder
* @return \Illuminate\Database\Eloquent\Builder
*/
protected function buildEntitySearchQuery($terms, $entityType = 'page', $action = 'view')
{
$entity = $this->entityProvider->get($entityType);
$entity = $this->getEntity($entityType);
$entitySelect = $entity->newQuery();
// Handle normal search terms
if (count($terms['search']) > 0) {
$subQuery = $this->db->table('search_terms')->select('entity_id', 'entity_type', \DB::raw('SUM(score) as score'));
$subQuery->where('entity_type', '=', $entity->getMorphClass());
$subQuery->where('entity_type', '=', 'BookStack\\' . ucfirst($entityType));
$subQuery->where(function (Builder $query) use ($terms) {
foreach ($terms['search'] as $inputTerm) {
$query->orWhere('term', 'like', $inputTerm .'%');
@@ -193,9 +201,9 @@ class SearchService
// Handle exact term matching
if (count($terms['exact']) > 0) {
$entitySelect->where(function (EloquentBuilder $query) use ($terms, $entity) {
$entitySelect->where(function (\Illuminate\Database\Eloquent\Builder $query) use ($terms, $entity) {
foreach ($terms['exact'] as $inputTerm) {
$query->where(function (EloquentBuilder $query) use ($inputTerm, $entity) {
$query->where(function (\Illuminate\Database\Eloquent\Builder $query) use ($inputTerm, $entity) {
$query->where('name', 'like', '%'.$inputTerm .'%')
->orWhere($entity->textField, 'like', '%'.$inputTerm .'%');
});
@@ -283,14 +291,14 @@ class SearchService
/**
* Apply a tag search term onto a entity query.
* @param EloquentBuilder $query
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $tagTerm
* @return mixed
*/
protected function applyTagSearch(EloquentBuilder $query, $tagTerm)
protected function applyTagSearch(\Illuminate\Database\Eloquent\Builder $query, $tagTerm)
{
preg_match("/^(.*?)((".$this->getRegexEscapedOperators().")(.*?))?$/", $tagTerm, $tagSplit);
$query->whereHas('tags', function (EloquentBuilder $query) use ($tagSplit) {
$query->whereHas('tags', function (\Illuminate\Database\Eloquent\Builder $query) use ($tagSplit) {
$tagName = $tagSplit[1];
$tagOperator = count($tagSplit) > 2 ? $tagSplit[3] : '';
$tagValue = count($tagSplit) > 3 ? $tagSplit[4] : '';
@@ -315,6 +323,16 @@ class SearchService
return $query;
}
/**
* Get an entity instance via type.
* @param $type
* @return Entity
*/
protected function getEntity($type)
{
return $this->entities[strtolower($type)];
}
/**
* Index the given entity.
* @param Entity $entity
@@ -334,7 +352,7 @@ class SearchService
/**
* Index multiple Entities at once
* @param \BookStack\Entities\Entity[] $entities
* @param Entity[] $entities
*/
protected function indexEntities($entities)
{
@@ -362,7 +380,7 @@ class SearchService
{
$this->searchTerm->truncate();
foreach ($this->entityProvider->all() as $entityModel) {
foreach ($this->entities as $entityModel) {
$selectFields = ['id', 'name', $entityModel->textField];
$entityModel->newQuery()->select($selectFields)->chunk(1000, function ($entities) {
$this->indexEntities($entities);
@@ -416,7 +434,7 @@ class SearchService
* Custom entity search filters
*/
protected function filterUpdatedAfter(EloquentBuilder $query, Entity $model, $input)
protected function filterUpdatedAfter(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
{
try {
$date = date_create($input);
@@ -426,7 +444,7 @@ class SearchService
$query->where('updated_at', '>=', $date);
}
protected function filterUpdatedBefore(EloquentBuilder $query, Entity $model, $input)
protected function filterUpdatedBefore(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
{
try {
$date = date_create($input);
@@ -436,7 +454,7 @@ class SearchService
$query->where('updated_at', '<', $date);
}
protected function filterCreatedAfter(EloquentBuilder $query, Entity $model, $input)
protected function filterCreatedAfter(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
{
try {
$date = date_create($input);
@@ -446,7 +464,7 @@ class SearchService
$query->where('created_at', '>=', $date);
}
protected function filterCreatedBefore(EloquentBuilder $query, Entity $model, $input)
protected function filterCreatedBefore(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
{
try {
$date = date_create($input);
@@ -456,7 +474,7 @@ class SearchService
$query->where('created_at', '<', $date);
}
protected function filterCreatedBy(EloquentBuilder $query, Entity $model, $input)
protected function filterCreatedBy(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
{
if (!is_numeric($input) && $input !== 'me') {
return;
@@ -467,7 +485,7 @@ class SearchService
$query->where('created_by', '=', $input);
}
protected function filterUpdatedBy(EloquentBuilder $query, Entity $model, $input)
protected function filterUpdatedBy(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
{
if (!is_numeric($input) && $input !== 'me') {
return;
@@ -478,41 +496,41 @@ class SearchService
$query->where('updated_by', '=', $input);
}
protected function filterInName(EloquentBuilder $query, Entity $model, $input)
protected function filterInName(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
{
$query->where('name', 'like', '%' .$input. '%');
}
protected function filterInTitle(EloquentBuilder $query, Entity $model, $input)
protected function filterInTitle(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
{
$this->filterInName($query, $model, $input);
}
protected function filterInBody(EloquentBuilder $query, Entity $model, $input)
protected function filterInBody(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
{
$query->where($model->textField, 'like', '%' .$input. '%');
}
protected function filterIsRestricted(EloquentBuilder $query, Entity $model, $input)
protected function filterIsRestricted(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
{
$query->where('restricted', '=', true);
}
protected function filterViewedByMe(EloquentBuilder $query, Entity $model, $input)
protected function filterViewedByMe(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
{
$query->whereHas('views', function ($query) {
$query->where('user_id', '=', user()->id);
});
}
protected function filterNotViewedByMe(EloquentBuilder $query, Entity $model, $input)
protected function filterNotViewedByMe(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
{
$query->whereDoesntHave('views', function ($query) {
$query->where('user_id', '=', user()->id);
});
}
protected function filterSortBy(EloquentBuilder $query, Entity $model, $input)
protected function filterSortBy(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
{
$functionName = camel_case('sort_by_' . $input);
if (method_exists($this, $functionName)) {
@@ -525,7 +543,7 @@ class SearchService
* Sorting filter options
*/
protected function sortByLastCommented(EloquentBuilder $query, Entity $model)
protected function sortByLastCommented(\Illuminate\Database\Eloquent\Builder $query, Entity $model)
{
$commentsTable = $this->db->getTablePrefix() . 'comments';
$morphClass = str_replace('\\', '\\\\', $model->getMorphClass());

View File

@@ -1,5 +1,7 @@
<?php namespace BookStack\Settings;
<?php namespace BookStack\Services;
use BookStack\Setting;
use BookStack\User;
use Illuminate\Contracts\Cache\Repository as Cache;
/**
@@ -53,7 +55,7 @@ class SettingService
/**
* Get a user-specific setting from the database or cache.
* @param \BookStack\Auth\User $user
* @param User $user
* @param $key
* @param bool $default
* @return bool|string
@@ -172,7 +174,7 @@ class SettingService
/**
* Put a user-specific setting into the database.
* @param \BookStack\Auth\User $user
* @param User $user
* @param $key
* @param $value
* @return bool

View File

@@ -1,11 +1,11 @@
<?php namespace BookStack\Auth\Access;
<?php namespace BookStack\Services;
use BookStack\Auth\SocialAccount;
use BookStack\Auth\UserRepo;
use BookStack\Exceptions\SocialDriverNotConfigured;
use BookStack\Exceptions\SocialSignInAccountNotUsed;
use BookStack\Exceptions\UserRegistrationException;
use Laravel\Socialite\Contracts\Factory as Socialite;
use BookStack\Exceptions\SocialDriverNotConfigured;
use BookStack\Exceptions\UserRegistrationException;
use BookStack\Repos\UserRepo;
use BookStack\SocialAccount;
use Laravel\Socialite\Contracts\User as SocialUser;
class SocialAuthService
@@ -19,7 +19,7 @@ class SocialAuthService
/**
* SocialAuthService constructor.
* @param \BookStack\Auth\UserRepo $userRepo
* @param UserRepo $userRepo
* @param Socialite $socialite
* @param SocialAccount $socialAccount
*/
@@ -40,7 +40,7 @@ class SocialAuthService
public function startLogIn($socialDriver)
{
$driver = $this->validateDriver($socialDriver);
return $this->getSocialDriver($driver)->redirect();
return $this->socialite->driver($driver)->redirect();
}
/**
@@ -52,7 +52,7 @@ class SocialAuthService
public function startRegister($socialDriver)
{
$driver = $this->validateDriver($socialDriver);
return $this->getSocialDriver($driver)->redirect();
return $this->socialite->driver($driver)->redirect();
}
/**
@@ -247,20 +247,4 @@ class SocialAuthService
session()->flash('success', trans('settings.users_social_disconnected', ['socialAccount' => title_case($socialDriver)]));
return redirect(user()->getEditUrl());
}
/**
* Provide redirect options per service for the Laravel Socialite driver
* @param $driverName
* @return \Laravel\Socialite\Contracts\Provider
*/
public function getSocialDriver(string $driverName)
{
$driver = $this->socialite->driver($driverName);
if ($driverName === 'google' && config('services.google.select_account')) {
$driver->with(['prompt' => 'select_account']);
}
return $driver;
}
}

View File

@@ -1,9 +1,9 @@
<?php namespace BookStack\Uploads;
<?php namespace BookStack\Services;
use Illuminate\Contracts\Filesystem\Factory as FileSystem;
use Illuminate\Contracts\Filesystem\Filesystem as FileSystemInstance;
abstract class UploadService
class UploadService
{
/**

View File

@@ -1,7 +1,7 @@
<?php namespace BookStack\Actions;
<?php namespace BookStack\Services;
use BookStack\Auth\Permissions\PermissionService;
use BookStack\Entities\Entity;
use BookStack\Entity;
use BookStack\View;
class ViewService
{
@@ -10,8 +10,8 @@ class ViewService
/**
* ViewService constructor.
* @param \BookStack\Actions\View $view
* @param \BookStack\Auth\Permissions\PermissionService $permissionService
* @param View $view
* @param PermissionService $permissionService
*/
public function __construct(View $view, PermissionService $permissionService)
{
@@ -50,13 +50,12 @@ class ViewService
* Get the entities with the most views.
* @param int $count
* @param int $page
* @param Entity|false|array $filterModel
* @param bool|false|array $filterModel
* @param string $action - used for permission checking
* @return
*/
public function getPopular($count = 10, $page = 0, $filterModel = false, $action = 'view')
{
// TODO - Standardise input filter
$skipCount = $count * $page;
$query = $this->permissionService->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type', $action)
->select('*', 'viewable_id', 'viewable_type', \DB::raw('SUM(views) as view_count'))
@@ -66,7 +65,7 @@ class ViewService
if ($filterModel && is_array($filterModel)) {
$query->whereIn('viewable_type', $filterModel);
} else if ($filterModel) {
$query->where('viewable_type', '=', $filterModel->getMorphClass());
$query->where('viewable_type', '=', get_class($filterModel));
}
return $query->with('viewable')->skip($skipCount)->take($count)->get()->pluck('viewable');
@@ -90,7 +89,7 @@ class ViewService
->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type');
if ($filterModel) {
$query = $query->where('viewable_type', '=', $filterModel->getMorphClass());
$query = $query->where('viewable_type', '=', get_class($filterModel));
}
$query = $query->where('user_id', '=', $user->id);

View File

@@ -1,6 +1,4 @@
<?php namespace BookStack\Settings;
use BookStack\Model;
<?php namespace BookStack;
class Setting extends Model
{

View File

@@ -1,6 +1,4 @@
<?php namespace BookStack\Auth;
use BookStack\Model;
<?php namespace BookStack;
class SocialAccount extends Model
{

View File

@@ -1,6 +1,4 @@
<?php namespace BookStack\Actions;
use BookStack\Model;
<?php namespace BookStack;
/**
* Class Attribute

View File

@@ -1,74 +0,0 @@
<?php namespace BookStack\Translation;
class Translator extends \Illuminate\Translation\Translator
{
/**
* Mapping of locales to their base locales
* @var array
*/
protected $baseLocaleMap = [
'de_informal' => 'de',
];
/**
* Get the translation for a given key.
*
* @param string $key
* @param array $replace
* @param string $locale
* @return string|array|null
*/
public function trans($key, array $replace = [], $locale = null)
{
$translation = $this->get($key, $replace, $locale);
if (is_array($translation)) {
$translation = $this->mergeBackupTranslations($translation, $key, $locale);
}
return $translation;
}
/**
* Merge the fallback translations, and base translations if existing,
* into the provided core key => value array of translations content.
* @param array $translationArray
* @param string $key
* @param null $locale
* @return array
*/
protected function mergeBackupTranslations(array $translationArray, string $key, $locale = null)
{
$fallback = $this->get($key, [], $this->fallback);
$baseLocale = $this->getBaseLocale($locale ?? $this->locale);
$baseTranslations = $baseLocale ? $this->get($key, [], $baseLocale) : [];
return array_replace_recursive($fallback, $baseTranslations, $translationArray);
}
/**
* Get the array of locales to be checked.
*
* @param string|null $locale
* @return array
*/
protected function localeArray($locale)
{
$primaryLocale = $locale ?: $this->locale;
return array_filter([$primaryLocale, $this->getBaseLocale($primaryLocale), $this->fallback]);
}
/**
* Get the locale to extend for the given locale.
*
* @param string $locale
* @return string|null
*/
protected function getBaseLocale($locale)
{
return $this->baseLocaleMap[$locale] ?? null;
}
}

View File

@@ -1,34 +0,0 @@
<?php namespace BookStack\Uploads;
use BookStack\Exceptions\HttpFetchException;
class HttpFetcher
{
/**
* Fetch content from an external URI.
* @param string $uri
* @return bool|string
* @throws HttpFetchException
*/
public function fetch(string $uri)
{
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $uri,
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_CONNECTTIMEOUT => 5
]);
$data = curl_exec($ch);
$err = curl_error($ch);
curl_close($ch);
if ($err) {
throw new HttpFetchException($err);
}
return $data;
}
}

View File

@@ -1,8 +1,6 @@
<?php namespace BookStack\Auth;
<?php namespace BookStack;
use BookStack\Model;
use BookStack\Notifications\ResetPassword;
use BookStack\Uploads\Image;
use Illuminate\Auth\Authenticatable;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;

View File

@@ -1,6 +1,4 @@
<?php namespace BookStack\Actions;
use BookStack\Model;
<?php namespace BookStack;
class View extends Model
{

View File

@@ -30,11 +30,11 @@ function versioned_asset($file = '')
/**
* Helper method to get the current User.
* Defaults to public 'Guest' user if not logged in.
* @return \BookStack\Auth\User
* @return \BookStack\User
*/
function user()
{
return auth()->user() ?: \BookStack\Auth\User::getDefault();
return auth()->user() ?: \BookStack\User::getDefault();
}
/**
@@ -61,7 +61,7 @@ function userCan($permission, Ownable $ownable = null)
}
// Check permission on ownable item
$permissionService = app(\BookStack\Auth\Permissions\PermissionService::class);
$permissionService = app(\BookStack\Services\PermissionService::class);
return $permissionService->checkOwnableUserAccess($ownable, $permission);
}
@@ -69,11 +69,11 @@ function userCan($permission, Ownable $ownable = null)
* Helper to access system settings.
* @param $key
* @param bool $default
* @return bool|string|\BookStack\Settings\SettingService
* @return bool|string|\BookStack\Services\SettingService
*/
function setting($key = null, $default = false)
{
$settingService = resolve(\BookStack\Settings\SettingService::class);
$settingService = resolve(\BookStack\Services\SettingService::class);
if (is_null($key)) {
return $settingService;
}
@@ -92,15 +92,10 @@ function baseUrl($path, $forceAppDomain = false)
if ($isFullUrl && !$forceAppDomain) {
return $path;
}
$path = trim($path, '/');
$base = rtrim(config('app.url'), '/');
// Remove non-specified domain if forced and we have a domain
if ($isFullUrl && $forceAppDomain) {
if (!empty($base) && strpos($path, $base) === 0) {
$path = trim(substr($path, strlen($base) - 1));
}
$explodedPath = explode('/', $path);
$path = implode('/', array_splice($explodedPath, 3));
}
@@ -110,7 +105,7 @@ function baseUrl($path, $forceAppDomain = false)
return url($path);
}
return $base . '/' . $path;
return rtrim(config('app.url'), '/') . '/' . $path;
}
/**

View File

@@ -5,15 +5,10 @@
"license": "MIT",
"type": "project",
"require": {
"php": ">=7.0.5",
"ext-json": "*",
"php": ">=7.0.0",
"ext-tidy": "*",
"ext-dom": "*",
"ext-xml": "*",
"ext-mbstring": "*",
"ext-gd": "*",
"ext-curl": "*",
"laravel/framework": "~5.5.44",
"laravel/framework": "~5.5.42",
"fideloper/proxy": "~3.3",
"intervention/image": "^2.4",
"laravel/socialite": "^3.0",
@@ -87,7 +82,7 @@
"optimize-autoloader": true,
"preferred-install": "dist",
"platform": {
"php": "7.0.5"
"php": "7.0"
}
}
}

930
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,84 +1,175 @@
<?php
/**
* Global app configuration options.
*
* Changes to these config files are not supported by BookStack and may break upon updates.
* Configuration should be altered via the `.env` file or environment variables.
* Do not edit this file unless you're happy to maintain any changes yourself.
*/
return [
// The environment to run BookStack in.
// Options: production, development, demo, testing
'env' => env('APP_ENV', 'production'),
// Enter the application in debug mode.
// Shows much more verbose error messages. Has potential to show
// private configuration variables so should remain disabled in public.
'debug' => env('APP_DEBUG', false),
// Set the default view type for various lists. Can be overridden by user preferences.
// These will be used for public viewers and users that have not set a preference.
/**
* Set the default view type for various lists. Can be overridden by user preferences.
* This will be used for public viewers and users that have not set a preference.
*/
'views' => [
'books' => env('APP_VIEWS_BOOKS', 'list')
],
// The number of revisions to keep in the database.
// Once this limit is reached older revisions will be deleted.
// If set to false then a limit will not be enforced.
/**
* The number of revisions to keep in the database.
* Once this limit is reached older revisions will be deleted.
* If set to false then a limit will not be enforced.
*/
'revision_limit' => env('REVISION_LIMIT', 50),
// Allow <script> tags to entered within page content.
// <script> tags are escaped by default.
// Even when overridden the WYSIWYG editor may still escape script content.
/**
* Allow <script> tags to entered within page content.
* <script> tags are escaped by default.
* Even when overridden the WYSIWYG editor may still escape script content.
*/
'allow_content_scripts' => env('ALLOW_CONTENT_SCRIPTS', false),
// Override the default behaviour for allowing crawlers to crawl the instance.
// May be ignored if view has be overridden or modified.
// Defaults to null since, if not set, 'app-public' status used instead.
/**
* Override the default behaviour for allowing crawlers to crawl the instance.
* May be ignored if view has be overridden or modified.
* Defaults to null since, if not set, 'app-public' status used instead.
*/
'allow_robots' => env('ALLOW_ROBOTS', null),
// Application Base URL, Used by laravel in development commands
// and used by BookStack in URL generation.
/*
|--------------------------------------------------------------------------
| Application Debug Mode
|--------------------------------------------------------------------------
|
| When your application is in debug mode, detailed error messages with
| stack traces will be shown on every error that occurs within your
| application. If disabled, a simple generic error page is shown.
|
*/
'debug' => env('APP_DEBUG', false),
/*
|--------------------------------------------------------------------------
| Application URL
|--------------------------------------------------------------------------
|
| This URL is used by the console to properly generate URLs when using
| the Artisan command line tool. You should set this to the root of
| your application so that it is used when running Artisan tasks.
|
*/
'url' => env('APP_URL', '') === 'http://bookstack.dev' ? '' : env('APP_URL', ''),
// Application timezone for back-end date functions.
/*
|--------------------------------------------------------------------------
| Application Timezone
|--------------------------------------------------------------------------
|
| Here you may specify the default timezone for your application, which
| will be used by the PHP date and date-time functions. We have gone
| ahead and set this to a sensible default for you out of the box.
|
*/
'timezone' => 'UTC',
// Default locale to use
/*
|--------------------------------------------------------------------------
| Application Locale Configuration
|--------------------------------------------------------------------------
|
| The application locale determines the default locale that will be used
| by the translation service provider. You are free to set this value
| to any of the locales which will be supported by the application.
|
*/
'locale' => env('APP_LANG', 'en'),
'locales' => ['en', 'ar', 'de', 'es', 'es_AR', 'fr', 'nl', 'pt_BR', 'sk', 'sv', 'ja', 'pl', 'it', 'ru', 'zh_CN', 'zh_TW'],
// Locales available
'locales' => ['en', 'ar', 'de', 'de_informal', 'es', 'es_AR', 'fr', 'nl', 'pt_BR', 'sk', 'sv', 'kr', 'ja', 'pl', 'it', 'ru', 'uk', 'zh_CN', 'zh_TW'],
/*
|--------------------------------------------------------------------------
| Right-to-left text control
|--------------------------------------------------------------------------
|
| Right-to-left text control is set to false by default since English
| is the primary supported application but this may be dynamically
| altered by the applications localization system.
|
*/
// Application Fallback Locale
'fallback_locale' => 'en',
// Enable right-to-left text control.
'rtl' => false,
// Auto-detect the locale for public users
// For public users their locale can be guessed by headers sent by their
// browser. This is usually set by users in their browser settings.
// If not found the default app locale will be used.
/*
|--------------------------------------------------------------------------
| Auto-detect the locale for public users
|--------------------------------------------------------------------------
|
| For public users their locale can be guessed by headers sent by their
| browser. This is usually set by users in their browser settings.
| If not found the default app locale will be used.
|
*/
'auto_detect_locale' => env('APP_AUTO_LANG_PUBLIC', true),
// Encryption key
/*
|--------------------------------------------------------------------------
| Application Fallback Locale
|--------------------------------------------------------------------------
|
| The fallback locale determines the locale to use when the current one
| is not available. You may change the value to correspond to any of
| the language folders that are provided through your application.
|
*/
'fallback_locale' => 'en',
/*
|--------------------------------------------------------------------------
| Encryption Key
|--------------------------------------------------------------------------
|
| This key is used by the Illuminate encrypter service and should be set
| to a random, 32 character string, otherwise these encrypted strings
| will not be safe. Please do this before deploying an application!
|
*/
'key' => env('APP_KEY', 'AbAZchsay4uBTU33RubBzLKw203yqSqr'),
// Encryption cipher
'cipher' => 'AES-256-CBC',
// Logging configuration
// Options: single, daily, syslog, errorlog
/*
|--------------------------------------------------------------------------
| Logging Configuration
|--------------------------------------------------------------------------
|
| Here you may configure the log settings for your application. Out of
| the box, Laravel uses the Monolog PHP logging library. This gives
| you a variety of powerful log handlers / formatters to utilize.
|
| Available Settings: "single", "daily", "syslog", "errorlog"
|
*/
'log' => env('APP_LOGGING', 'single'),
// Application Services Provides
/*
|--------------------------------------------------------------------------
| Autoloaded Service Providers
|--------------------------------------------------------------------------
|
| The service providers listed here will be automatically loaded on the
| request to your application. Feel free to add your own services to
| this array to grant expanded functionality to your applications.
|
*/
'providers' => [
// Laravel Framework Service Providers...
/*
* Laravel Framework Service Providers...
*/
Illuminate\Auth\AuthServiceProvider::class,
Illuminate\Broadcasting\BroadcastServiceProvider::class,
Illuminate\Bus\BusServiceProvider::class,
@@ -96,22 +187,25 @@ return [
Illuminate\Redis\RedisServiceProvider::class,
Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
Illuminate\Session\SessionServiceProvider::class,
Illuminate\Translation\TranslationServiceProvider::class,
Illuminate\Validation\ValidationServiceProvider::class,
Illuminate\View\ViewServiceProvider::class,
Illuminate\Notifications\NotificationServiceProvider::class,
SocialiteProviders\Manager\ServiceProvider::class,
// Third party service providers
/**
* Third Party
*/
Intervention\Image\ImageServiceProvider::class,
Barryvdh\DomPDF\ServiceProvider::class,
Barryvdh\Snappy\ServiceProvider::class,
// BookStack replacement service providers (Extends Laravel)
/*
* Application Service Providers...
*/
BookStack\Providers\PaginationServiceProvider::class,
BookStack\Providers\TranslationServiceProvider::class,
// BookStack custom service providers
BookStack\Providers\AuthServiceProvider::class,
BookStack\Providers\AppServiceProvider::class,
BookStack\Providers\BroadcastServiceProvider::class,
@@ -131,10 +225,8 @@ return [
|
*/
// Class aliases, Registered on application start
'aliases' => [
// Laravel
'App' => Illuminate\Support\Facades\App::class,
'Artisan' => Illuminate\Support\Facades\Artisan::class,
'Auth' => Illuminate\Support\Facades\Auth::class,
@@ -170,20 +262,25 @@ return [
'View' => Illuminate\Support\Facades\View::class,
'Socialite' => Laravel\Socialite\Facades\Socialite::class,
// Third Party
/**
* Third Party
*/
'ImageTool' => Intervention\Image\Facades\Image::class,
'DomPDF' => Barryvdh\DomPDF\Facade::class,
'SnappyPDF' => Barryvdh\Snappy\Facades\SnappyPdf::class,
// Custom BookStack
'Activity' => BookStack\Facades\Activity::class,
'Setting' => BookStack\Facades\Setting::class,
'Views' => BookStack\Facades\Views::class,
'Images' => BookStack\Facades\Images::class,
/**
* Custom
*/
'Activity' => BookStack\Services\Facades\Activity::class,
'Setting' => BookStack\Services\Facades\Setting::class,
'Views' => BookStack\Services\Facades\Views::class,
'Images' => BookStack\Services\Facades\Images::class,
],
// Proxy configuration
'proxies' => env('APP_PROXIES', ''),
];

View File

@@ -1,32 +1,43 @@
<?php
/**
* Authentication configuration options.
*
* Changes to these config files are not supported by BookStack and may break upon updates.
* Configuration should be altered via the `.env` file or environment variables.
* Do not edit this file unless you're happy to maintain any changes yourself.
*/
return [
// Method of authentication to use
// Options: standard, ldap
'method' => env('AUTH_METHOD', 'standard'),
// Authentication Defaults
// This option controls the default authentication "guard" and password
// reset options for your application.
/*
|--------------------------------------------------------------------------
| Authentication Defaults
|--------------------------------------------------------------------------
|
| This option controls the default authentication "guard" and password
| reset options for your application. You may change these defaults
| as required, but they're a perfect start for most applications.
|
*/
'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],
// Authentication Guards
// All authentication drivers have a user provider. This defines how the
// users are actually retrieved out of your database or other storage
// mechanisms used by this application to persist your user's data.
// Supported: "session", "token"
/*
|--------------------------------------------------------------------------
| Authentication Guards
|--------------------------------------------------------------------------
|
| Next, you may define every authentication guard for your application.
| Of course, a great default configuration has been defined for you
| here which uses session storage and the Eloquent user provider.
|
| All authentication drivers have a user provider. This defines how the
| users are actually retrieved out of your database or other storage
| mechanisms used by this application to persist your user's data.
|
| Supported: "session", "token"
|
*/
'guards' => [
'web' => [
'driver' => 'session',
@@ -39,15 +50,27 @@ return [
],
],
// User Providers
// All authentication drivers have a user provider. This defines how the
// users are actually retrieved out of your database or other storage
// mechanisms used by this application to persist your user's data.
// Supported: database, eloquent, ldap
/*
|--------------------------------------------------------------------------
| User Providers
|--------------------------------------------------------------------------
|
| All authentication drivers have a user provider. This defines how the
| users are actually retrieved out of your database or other storage
| mechanisms used by this application to persist your user's data.
|
| If you have multiple user tables or models you may configure multiple
| sources which represent each model / table. These sources may then
| be assigned to any extra authentication guards you have defined.
|
| Supported: "database", "eloquent"
|
*/
'providers' => [
'users' => [
'driver' => env('AUTH_METHOD', 'standard') === 'standard' ? 'eloquent' : env('AUTH_METHOD'),
'model' => \BookStack\Auth\User::class,
'model' => BookStack\User::class,
],
// 'users' => [
@@ -56,10 +79,25 @@ return [
// ],
],
// Resetting Passwords
// The expire time is the number of minutes that the reset token should be
// considered valid. This security feature keeps tokens short-lived so
// they have less time to be guessed. You may change this as needed.
/*
|--------------------------------------------------------------------------
| Resetting Passwords
|--------------------------------------------------------------------------
|
| Here you may set the options for resetting passwords including the view
| that is your password reset e-mail. You may also set the name of the
| table that maintains all of the reset tokens for your application.
|
| You may specify multiple password reset configurations if you have more
| than one user table or model in the application and you want to have
| separate password reset settings based on the specific user types.
|
| The expire time is the number of minutes that the reset token should be
| considered valid. This security feature keeps tokens short-lived so
| they have less time to be guessed. You may change this as needed.
|
*/
'passwords' => [
'users' => [
'provider' => 'users',

View File

@@ -1,25 +1,31 @@
<?php
/**
* Broadcasting configuration options.
*
* Changes to these config files are not supported by BookStack and may break upon updates.
* Configuration should be altered via the `.env` file or environment variables.
* Do not edit this file unless you're happy to maintain any changes yourself.
*/
return [
// Default Broadcaster
// This option controls the default broadcaster that will be used by the
// framework when an event needs to be broadcast. This can be set to
// any of the connections defined in the "connections" array below.
/*
|--------------------------------------------------------------------------
| Default Broadcaster
|--------------------------------------------------------------------------
|
| This option controls the default broadcaster that will be used by the
| framework when an event needs to be broadcast. You may set this to
| any of the connections defined in the "connections" array below.
|
*/
'default' => env('BROADCAST_DRIVER', 'pusher'),
// Broadcast Connections
// Here you may define all of the broadcast connections that will be used
// to broadcast events to other systems or over websockets. Samples of
// each available type of connection are provided inside this array.
/*
|--------------------------------------------------------------------------
| Broadcast Connections
|--------------------------------------------------------------------------
|
| Here you may define all of the broadcast connections that will be used
| to broadcast events to other systems or over websockets. Samples of
| each available type of connection are provided inside this array.
|
*/
'connections' => [
'pusher' => [

View File

@@ -1,13 +1,5 @@
<?php
/**
* Caching configuration options.
*
* Changes to these config files are not supported by BookStack and may break upon updates.
* Configuration should be altered via the `.env` file or environment variables.
* Do not edit this file unless you're happy to maintain any changes yourself.
*/
// MEMCACHED - Split out configuration into an array
if (env('CACHE_DRIVER') === 'memcached') {
$memcachedServerKeys = ['host', 'port', 'weight'];
@@ -22,11 +14,30 @@ if (env('CACHE_DRIVER') === 'memcached') {
return [
// Default cache store to use
// Can be overridden at cache call-time
/*
|--------------------------------------------------------------------------
| Default Cache Store
|--------------------------------------------------------------------------
|
| This option controls the default cache connection that gets used while
| using this caching library. This connection is used when another is
| not explicitly specified when executing a given caching function.
|
*/
'default' => env('CACHE_DRIVER', 'file'),
// Available caches stores
/*
|--------------------------------------------------------------------------
| Cache Stores
|--------------------------------------------------------------------------
|
| Here you may define all of the cache "stores" for your application as
| well as their drivers. You may even define multiple stores for the
| same cache driver to group types of items stored in your caches.
|
*/
'stores' => [
'apc' => [
@@ -60,8 +71,17 @@ return [
],
// Cache key prefix
// Used to prevent collisions in shared cache systems.
/*
|--------------------------------------------------------------------------
| Cache Key Prefix
|--------------------------------------------------------------------------
|
| When utilizing a RAM based store such as APC or Memcached, there might
| be other applications utilizing the same cache. So, we'll specify a
| value to get prefixed to all our keys so we can avoid collisions.
|
*/
'prefix' => env('CACHE_PREFIX', 'bookstack'),
];

35
config/compile.php Normal file
View File

@@ -0,0 +1,35 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Additional Compiled Classes
|--------------------------------------------------------------------------
|
| Here you may specify additional classes to include in the compiled file
| generated by the `artisan optimize` command. These should be classes
| that are included on basically every request into the application.
|
*/
'files' => [
//
],
/*
|--------------------------------------------------------------------------
| Compiled File Providers
|--------------------------------------------------------------------------
|
| Here you may list service providers which define a "compiles" function
| that returns additional files that should be compiled, providing an
| easy way to get common files from any packages you are utilizing.
|
*/
'providers' => [
//
],
];

View File

@@ -1,13 +1,5 @@
<?php
/**
* Database configuration options.
*
* Changes to these config files are not supported by BookStack and may break upon updates.
* Configuration should be altered via the `.env` file or environment variables.
* Do not edit this file unless you're happy to maintain any changes yourself.
*/
// REDIS - Split out configuration into an array
if (env('REDIS_SERVERS', false)) {
$redisServerKeys = ['host', 'port', 'database'];
@@ -24,7 +16,6 @@ if (env('REDIS_SERVERS', false)) {
}
}
// MYSQL - Split out port from host if set
$mysql_host = env('DB_HOST', 'localhost');
$mysql_host_exploded = explode(':', $mysql_host);
$mysql_port = env('DB_PORT', 3306);
@@ -35,12 +26,48 @@ if (count($mysql_host_exploded) > 1) {
return [
// Default database connection name.
// Options: mysql, mysql_testing
/*
|--------------------------------------------------------------------------
| PDO Fetch Style
|--------------------------------------------------------------------------
|
| By default, database results will be returned as instances of the PHP
| stdClass object; however, you may desire to retrieve records in an
| array format for simplicity. Here you can tweak the fetch style.
|
*/
'fetch' => PDO::FETCH_CLASS,
/*
|--------------------------------------------------------------------------
| Default Database Connection Name
|--------------------------------------------------------------------------
|
| Here you may specify which of the database connections below you wish
| to use as your default connection for all database work. Of course
| you may use many connections at once using the Database library.
|
*/
'default' => env('DB_CONNECTION', 'mysql'),
// Available database connections
// Many of those shown here are unsupported by BookStack.
/*
|--------------------------------------------------------------------------
| Database Connections
|--------------------------------------------------------------------------
|
| Here are each of the database connections setup for your application.
| Of course, examples of configuring each database platform that is
| supported by Laravel is shown below to make development simple.
|
|
| All database work in Laravel is done through the PHP PDO facilities
| so make sure you have the driver for your particular database of
| choice installed on your machine before you begin development.
|
*/
'connections' => [
'sqlite' => [
@@ -55,13 +82,11 @@ return [
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'port' => $mysql_port,
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => false,
'engine' => null,
],
'mysql_testing' => [
@@ -99,13 +124,30 @@ return [
],
// Migration Repository Table
// This table keeps track of all the migrations that have already run for
// your application. Using this information, we can determine which of
// the migrations on disk haven't actually been run in the database.
/*
|--------------------------------------------------------------------------
| Migration Repository Table
|--------------------------------------------------------------------------
|
| This table keeps track of all the migrations that have already run for
| your application. Using this information, we can determine which of
| the migrations on disk haven't actually been run in the database.
|
*/
'migrations' => 'migrations',
// Redis configuration to use if set
/*
|--------------------------------------------------------------------------
| Redis Databases
|--------------------------------------------------------------------------
|
| Redis is an open source, fast, and advanced key-value store that also
| provides a richer set of commands than a typical key-value systems
| such as APC or Memcached. Laravel makes it easy to dig right in.
|
*/
'redis' => env('REDIS_SERVERS', false) ? $redisConfig : [],
];

View File

@@ -1,16 +1,16 @@
<?php
/**
* DOMPDF configuration options.
*
* Changes to these config files are not supported by BookStack and may break upon updates.
* Configuration should be altered via the `.env` file or environment variables.
* Do not edit this file unless you're happy to maintain any changes yourself.
*/
return [
/*
|--------------------------------------------------------------------------
| Settings
|--------------------------------------------------------------------------
|
| Set some default values. It is possible to add all defines that can be set
| in dompdf_config.inc.php. You can also override the entire config file.
|
*/
'show_warnings' => false, // Throw an Exception on warnings from dompdf
'orientation' => 'portrait',
'defines' => [

Some files were not shown because too many files have changed in this diff Show More