Compare commits

...

147 Commits

Author SHA1 Message Date
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
3cd08382e9 Fixed export style paths 2018-03-30 15:31:39 +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
4f788384f0 Updated icons with height
Fixes issues within IE
2018-03-25 15:52:48 +01:00
Dan Brown
23f90ed6b4 Ensured uploaded system images remain public
Also added tests to cover local_secure image storage.

Fixes #725
2018-03-25 12:41:52 +01:00
Dan Brown
f1586be516 Removed invalid bracket from view 2018-03-25 11:35:58 +01:00
Dan Brown
1a9f676416 Updated create routes to prevent slug clashes
Fixes #758
2018-03-25 11:34:42 +01:00
Dan Brown
df1a3a0715 Properly escaped search results
Prevents vue-like syntax in results causing errors.
Related to #748
2018-03-25 11:06:21 +01:00
Dan Brown
1e015af3c9 Fixed incorrect search logic in last commit
Incorrect cross-entity pagination could lead to hidden entities.
2018-03-24 19:05:56 +00:00
Dan Brown
f101c1a622 Made search more efficient and tweaked weighting
Added per-entity weighting changes.
Now Books score higher than chapters which score higher than pages.

Reduced queries required on search by only searching once but at a
higher count to see if there's another page.
2018-03-24 18:46:31 +00:00
Dan Brown
3df7d828eb Fixed failing tests
Fixed syntax error in french translations.
Removed 'required' on image validation which was breaking tests
2018-03-24 15:25:13 +00:00
Dan Brown
5ad9c5d319 Merge branch 'bug/gif-image-740' of git://github.com/Abijeet/BookStack
Also removed console.logs in dropzone.js
2018-03-24 14:54:50 +00:00
Dan Brown
9fead9890b Merge branch 'Abijeet-bug/image-upload' 2018-03-24 14:45:10 +00:00
Dan Brown
746684ec8c Merge branch 'bug/image-upload' of git://github.com/Abijeet/BookStack into Abijeet-bug/image-upload 2018-03-24 14:39:57 +00:00
Dan Brown
2ede273ef3 Merge pull request #753 from Alwaysin/master
Update french language
2018-03-24 14:36:12 +00:00
Dan Brown
6882bd3c62 Merge pull request #752 from Alwaysin/patch-1
Update entities.php for french language
2018-03-24 14:35:04 +00:00
Dan Brown
1061946858 Merge pull request #761 from msaus/hotfix/japanese_translation
update japanese translation
2018-03-24 14:32:34 +00:00
Soseki Masao
696ef3ff33 fix entities.php 2018-03-23 18:20:44 +09:00
Soseki Masao
2d1567ea30 update japanese translation 2018-03-23 17:35:21 +09:00
Abijeet
2cfcbe0a3c Fixes an issue with handling of large image files.
Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-03-19 02:09:01 +05:30
Abijeet
bf8dddd99c Not resizing gif images.
See - https://github.com/Intervention/image/issues/176

Fixes #223

Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-03-19 01:44:33 +05:30
Abijeet Patro
0335f58478 Merge branch 'master' into bug/image-upload 2018-03-18 23:44:33 +05:30
Abijeet
3a5c20c17e Removing the selected image and clearing the dropdzone on dialog close.
Towards #741

Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-03-18 23:38:37 +05:30
Dan Brown
380e2ff668 Updated outline button styles for svg icons 2018-03-18 15:35:04 +00:00
Dan Brown
c6844324d0 Use autodiscover for dev packages
Allows installation with `composer install --no-dev`
Fixed #742
2018-03-18 15:27:15 +00:00
Dan Brown
ecdeb545e0 Cleaned some form styling
Removed uppercasing of labels to make a little friendlier.
Extracted out toggleswitch JS into own component.
Improved basic code input for html-head-input area.
2018-03-18 15:13:46 +00:00
Dan Brown
2c8d7da885 Updated webpack SCSS extract to provide sourcemaps 2018-03-18 14:47:43 +00:00
Alwaysin
35c7e00203 Update entities.php 2018-03-18 14:46:56 +01:00
Abijeet
83d830fd7d Fixes the icons not being aligned properly in attached items section for the page.
Also formatting.

Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-03-18 18:58:04 +05:30
Alwaysin
accd936781 Update settings.php 2018-03-18 14:24:39 +01:00
Alwaysin
880987f15c Update components.php 2018-03-18 14:23:42 +01:00
Alwaysin
018084a951 Update common.php 2018-03-18 14:23:08 +01:00
Alwaysin
098b594104 Update auth.php 2018-03-18 14:21:11 +01:00
Alwaysin
bb7fab1dc0 Update activities.php 2018-03-18 14:20:20 +01:00
Abijeet
d859be3a12 Fixes a number of issues with the image uploader. Read below,
- Added a remove link to remove files that have an error.
- Error will appear below the progress bar.
- Hovering on dz-image or dz-details will display the error message. Otherwise error message was covering the remove link as well.
- Removed styling around the file size.
- Removed gradient effect in accordance with BookStack styling.
- Dropzone filenae will not overflow the container element. Also done for page attachments
- Added a 'uploaded' error message. this error was being thrown when the file size exceeded the server configured file size. (https://stackoverflow.com/a/42934387/903324)

Towards #741

Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-03-18 18:44:11 +05:30
Dan Brown
8b27ce3296 Fixed large content previews and improved mobile styles
Listing content previews no longer pre-wrap and instead simply break
correctly.
Updated titles to ensure they break on mobile devices.
Reduced spacing and font sizes on mobile to better adjust content to
screen size.

Fixes #739
2018-03-18 12:23:48 +00:00
Dan Brown
8828adfc9c Fixed up notification styling a little 2018-03-18 11:58:45 +00:00
Dan Brown
d44e0b7964 Prevented markdown editor pushing out toolbar 2018-03-18 11:46:08 +00:00
Dan Brown
0372efa89a Merge branch 'patch-1' of git://github.com/BackwardSpy/BookStack into BackwardSpy-patch-1 2018-03-18 11:40:38 +00:00
Dan Brown
d2eec4fbce Markdown editor image paste sets cursor correctly
Now sets cursor to alt text rather than end of placeholder image.
Fixed #751
2018-03-18 11:33:30 +00:00
Dan Brown
b42b07179f Updated exports to use DejaVu font
Provides potentially better language font coverage.
2018-03-17 17:12:01 +00:00
Dan Brown
1ad6fe1cbd Added togglable script escaping to page content
Configurable via 'ALLOW_CONTENT_SCRIPTS' env variable.
Fixes #575
2018-03-17 15:52:42 +00:00
Dan Brown
0a1546daea Moved jQuery to use NPM and fixed some asset refs 2018-03-17 15:20:15 +00:00
Dan Brown
b64940be82 Merge branch 'master' of github.com:BookStackApp/BookStack 2018-03-17 13:05:37 +00:00
Dan Brown
2ff2c0b257 Merge branch 'webpack-2018' 2018-03-17 13:05:25 +00:00
Dan Brown
ced4e58137 Finished off intitial conversion to webpack 2018-03-17 13:03:13 +00:00
Abijeet
f42d355fd7 Fixes issue with the validation message not being translated.
Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-03-13 09:10:23 +05:30
Dan Brown
28a5bd24b0 Merge pull request #743 from cipi1965/master
Update Italian translation
2018-03-12 19:33:21 +00:00
Matteo Piccina
85605ab570 Update Italian translation 2018-03-12 16:43:19 +01:00
Dan Brown
5e41933773 Merge branch 'master' of github.com:BookStackApp/BookStack 2018-03-11 16:35:14 +00:00
Dan Brown
f6f44c9de8 Reorganised dev-deps and updated moment 2018-03-11 16:34:58 +00:00
Dan Brown
e52bfc0c24 Attempted move to webpack again 2018-03-11 16:16:30 +00:00
Dan Brown
c44c42103c Merge pull request #711 from duncanbarnes/master
Added ability to configure email sender name
2018-03-10 17:45:16 +00:00
Dan Brown
1e98759722 Merge pull request #717 from owcz/patch-1
typo in readme.md
2018-03-10 17:43:10 +00:00
Dan Brown
bf4b95f929 Merge pull request #714 from pataar/patch-1
Add CACHE_PREFIX to the .env.example file
2018-03-10 17:42:39 +00:00
Dan Brown
a4072365e3 Merge pull request #718 from artur-trzesiok/master
add missing polish translations for comments
2018-03-05 20:26:26 +00:00
Dan Brown
2bd977b7d7 Merge pull request #709 from leomartinez/master
Updated 'Spanish Argentina' translation.
2018-03-05 20:22:26 +00:00
Pieter
496289ad94 Update .env.example 2018-03-05 13:10:59 +01:00
Artur Trzęsiok
98a3c815cf add missing polish translations for comments 2018-02-26 23:49:58 +01:00
Wolf
01e03f5a0f typo in readme.md 2018-02-26 09:10:43 -05:00
Chris Latham
1c8a8acb3d fix markdown editor resizing with long strings 2018-02-26 11:31:11 +00:00
Pieter
70cfb6624d Add CACHE_PREFIX to the .env.example file
We had some problems with multiple BookStack instances using the same caching server. Perhaps it's a good idea to have this available in the `.env.example` file.
2018-02-26 09:51:53 +01:00
Leonardo Martinez
fb48b025f3 Updated 'Spanish Argentina' translation. 2018-02-21 13:20:12 -03:00
Duncan Barnes
9a88b2cd0c Added ability to configure email sender name
Added env variable MAIL_FROM_NAME to allow the email sender name to be
customised. Also added MAIL_FROM to .env.example
2018-02-21 18:24:19 +09:00
Leonardo Martinez
395d02ef81 Updated 'Spanish Argentina' translation. 2018-02-19 10:15:24 -03:00
Dan Brown
67332a2f1b Merge pull request #704 from BookStackApp/svg_icons
Override-able SVG Icons
2018-02-17 19:51:59 +00:00
Dan Brown
81fa021083 Finished migrated from icon-font to SVG 2018-02-17 19:49:00 +00:00
Dan Brown
5ab39bfd5a Started migration to SVG icons 2018-02-17 13:30:52 +00:00
Dan Brown
dc1a16be4c Made it possible to override icons via custom theme 2018-02-17 12:36:24 +00: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
981d215155 Tweaked some comments 2018-02-11 18:18:16 +00:00
Dan Brown
2d43ab8a1b Fixed text overflow in various views
Fixes #669
2018-02-11 14:28:26 +00:00
Dan Brown
548dcd4db1 Fixed error when accessing non-authed attachment
Also updated attachment tests to use standard test-case.
Fixes #681
2018-02-11 12:37:02 +00:00
Dan Brown
2d41a4f064 Updated twitch SVG icon with vector SVG 2018-02-11 12:01:07 +00:00
Dan Brown
110f32a16d Merge branch 'master' of git://github.com/moutonnoireu/BookStack into moutonnoireu-master
Also updated composer deps
2018-02-11 11:44:09 +00:00
Dan Brown
bed7ba78d3 Updated grid view to use CSS grid and flexbox
Provides a cleaner height-matched design.
Closes #701
2018-02-11 11:36:51 +00:00
Dan Brown
2533db260d Merge branch 'master' of github.com:BookStackApp/BookStack 2018-02-04 18:14:41 +00:00
Dan Brown
87a45edde9 Merge branch 'pixwell-dev-support_for_gitlub_auth' 2018-02-04 18:14:16 +00:00
Dan Brown
9becc8055b Merge branch 'support_for_gitlub_auth' of git://github.com/pixwell-dev/BookStack into pixwell-dev-support_for_gitlub_auth 2018-02-04 17:51:30 +00:00
Dan Brown
d84f75c257 Merge pull request #695 from Yoginth/patch-2
Added Search Permission for not logged in Users
2018-02-04 17:48:25 +00:00
Dan Brown
1d49b65c2e Fixed code block wrapping on export
Now wraps instead of running off the page.

Fixed #676
2018-02-04 17:35:01 +00:00
Dan Brown
7c44f5462c Prevent image manager search from reloading page
Fixes #697
2018-02-04 17:18:55 +00:00
Dan Brown
b7e5cc6763 Merge pull request #696 from yuezhihan/master
Added simplified Chinese(zh-CN) language
2018-02-04 17:15:31 +00:00
Yue Zhihan
ab3231b550 Added 'zh_CN' to app.locales 2018-02-04 22:05:29 +08:00
Yue Zhihan
d65cd53c99 Added simplified Chinese(zh-CN) language 2018-02-04 21:42:19 +08:00
Dan Brown
46ea90c36e Merge pull request #692 from lommes/master
Corrected the keys for okta auth
2018-02-04 11:41:51 +00:00
Dan Brown
a45922616f Made default books view configurable in .env
Under 'APP_VIEWS_BOOKS' key.
Closes #675
2018-02-04 11:36:58 +00:00
Yoginth
ecf68b6365 Update all.blade.php 2018-02-03 20:15:36 +05:30
Jozef Balún
194bb0f042 add missing icon, fix name conventions 2018-02-01 18:26:19 +01:00
BlackSheep
addfb96002 reduced icon size 2018-02-01 09:55:37 +01:00
BlackSheep
6f7cfe7206 Update .env.example 2018-02-01 08:53:08 +01:00
BlackSheep
f51e0e9eb9 Update services.php 2018-02-01 08:51:35 +01:00
Timo Bartholomes
3cf2c6a027 Corrected the keys for okta auth 2018-01-31 21:11:17 +01:00
Jozef Balún
8b125be8f6 add missing lock file 2018-01-31 16:08:39 +01:00
Jozef Balún
44d8f39037 add support for gitlab authentification 2018-01-31 16:02:07 +01:00
BlackSheep
1651c807cb Update... 2018-01-30 09:59:56 +01:00
BlackSheep
5e2bf7c3e4 Add twitch socialite auth provider 2018-01-29 09:28:56 +01:00
Dan Brown
1d5440493c Set markdown editor preview width to 100%
Fixes #658
2018-01-28 18:14:02 +00:00
Dan Brown
59e809be16 Added command to add a new admin user
Closes #609
2018-01-28 18:09:26 +00:00
Dan Brown
ec050a5eef Fixed validation issue on register post
Added test to cover and also cleaned up RegisterController comments.

Fixes #670
2018-01-28 17:15:30 +00:00
Dan Brown
62342433f4 Set /app PHP code to PSR-2 standard
Also adde draw.io to attribution list.

Closes #649
2018-01-28 16:58:52 +00:00
Dan Brown
30b4f81fc6 Merge branch 'Abijeet-bug-650' 2018-01-28 14:20:35 +00:00
Dan Brown
bd711d69e4 Adapted code insert area and language select for mobile 2018-01-28 14:19:54 +00:00
Dan Brown
98d4bf4486 Merge branch 'bug-650' of git://github.com/Abijeet/BookStack into Abijeet-bug-650 2018-01-28 14:15:31 +00:00
Dan Brown
ead4b14d94 Updated user profile image delete to delete all uploads
Also moved test and made more comprehensive
2018-01-28 14:08:14 +00:00
Sampath Kumar
35e00ddb95 #630: Deleting user's profile pics on deleting of user account (#646)
* Issue-630: Fixed issue with deleting user profile pics when deleting a user.

* Issue #630: Deleting user's profile pics on deleting of user account

* Issue-630: Added test case for deleting user
2018-01-28 13:50:24 +00:00
Dan Brown
4eb5205070 Merge pull request #679 from marcusforsberg/master
Added Swedish translation
2018-01-28 13:40:01 +00:00
Dan Brown
1d1cc19596 Merge pull request #632 from BookStackApp/draw.io
draw.io integration
2018-01-28 13:39:14 +00:00
Dan Brown
faf7c55fdd Actually fixed the BaseURL this time 🤦 2018-01-28 13:33:50 +00:00
Dan Brown
ba6eb6727a Fixed test failing from missing baseURL
Also updated image upload test to delete before upload to prevent failed
tests breaking subsequent tests.
2018-01-28 13:27:41 +00:00
Dan Brown
88d09a2a3b Added drawing endpoint tests
Also refactored ImageTests away from BrowserKit
Also added image upload type validation.
2018-01-28 13:18:28 +00:00
marcusforsberg
daa11c3f13 Added Swedish locale to config 2018-01-26 20:27:28 +01:00
marcusforsberg
682bc9f896 Added Swedish translation 2018-01-26 20:16:35 +01:00
Dan Brown
9bbef3a3dd Added drawio abilities to markdown editor 2018-01-20 20:40:21 +00:00
Dan Brown
1411ee86b3 Extracted draw.io functionality to own file 2018-01-20 16:32:13 +00:00
Dan Brown
56264551e7 Added drawing icon and made drawio disablable 2018-01-20 15:00:54 +00:00
Dan Brown
0c383eee5b Merge branch 'master' into draw.io to fetch auth image changes 2018-01-20 14:06:44 +00:00
Dan Brown
f4bfbf91db Merge pull request #665 from BookStackApp/authed_images
Adds ability to secure images behind auth
2018-01-20 14:05:03 +00:00
Dan Brown
34782fbc91 Merge branch 'master' into draw.io 2018-01-20 14:01:56 +00:00
Dan Brown
1bfd77e7a1 Added drawing update ability 2018-01-20 14:01:35 +00:00
Dan Brown
5b075aa9bd Merge branch 'Abijeet-bug-638' 2018-01-13 16:45:28 +00:00
Dan Brown
281da59bae Refactored book sort using collections 2018-01-13 16:44:47 +00:00
Dan Brown
0afa417b0a Added ability to secure images behind auth
Still in testing.
Adds STORAGE_TYPE=local_secure option for setting images to be behind
auth. Stores images alongside attachments in /storage/uploads/images.
2018-01-13 11:11:23 +00:00
Abijeet
f2c62765ca Adds overflow:auto to popup content to allow it to scroll in lower res.
Fixes #650

Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-01-06 01:41:06 +05:30
Abijeet
a77756a2da Refactored the code to first check for the permissions before sorting the book.
Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-01-06 01:04:48 +05:30
Dan Brown
6988a6ff88 Added view override support
Relates to #652
2017-12-31 16:25:58 +00:00
Abijeet
e269cc7ea7 Adds test case for sorting permissions.
Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2017-12-31 20:17:08 +05:30
Abijeet
e13e71cbe0 Changed the sort view to only show books to which we have an update permission.
Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2017-12-31 16:44:46 +05:30
Abijeet
4a24d1c31b Checks the target and the source book before performing the sort.
Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2017-12-31 16:25:21 +05:30
Dan Brown
96b8c403a8 Fixed failing book view test
Also ensured setting system localcache is cleared correctly
2017-12-30 16:09:27 +00:00
Dan Brown
359b1b40a2 Fixed broken table/ol/ul page includes
Fixes #640
2017-12-30 15:50:33 +00:00
Dan Brown
920964a561 Enabled system-storage of drawings made via draw.io 2017-12-30 15:26:39 +00:00
Dan Brown
7bb336d1a8 Merge pull request #644 from Abijeet/bug-643
Adds font-size to ol to ensure that they are uniform.
2017-12-30 12:41:13 +00:00
Dan Brown
141bf22725 Updated book view change to PATCH + other amends
Moved toggle to right of header bar and added unique text and icon for
each view type.

Removed old profile setting to keep things clean.
2017-12-29 16:49:03 +00:00
Dan Brown
1aa4d0dc59 Merge branch 'feature-613' of git://github.com/Abijeet/BookStack into Abijeet-feature-613 2017-12-29 16:25:15 +00:00
Dan Brown
0c1b1cd435 Standardised admin role check 2017-12-29 16:14:20 +00:00
Dan Brown
3eb2246291 Merge branch 'feature-579' of git://github.com/Abijeet/BookStack into Abijeet-feature-579 2017-12-29 16:03:34 +00:00
Abijeet
937d2cd55c Adds font-size to ol to ensure that they are uniform.
Fixes #643

Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2017-12-28 22:32:24 +05:30
Dan Brown
afe781bc39 Enabled session in 404 responses
Fixes #634
2017-12-28 13:19:02 +00:00
Abijeet
d5a2529775 Adds test cases and fixes an issue with the permission checking.
Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2017-12-26 15:46:20 +05:30
Abijeet
0d4db603a4 Adds button to allow users to toggle the book view via the books list page.
Closes #613

Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2017-12-26 12:38:16 +05:30
Abijeet
7da8804753 Adds code to allow deletion of users via cmd line.
Fixes #579

Command:

php artisan bookstack:delete-users

Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2017-12-26 02:22:41 +05:30
Dan Brown
8b160b9fb4 Updated pull-request info to be clearer
Also updated dev version
2017-12-24 14:42:31 +00:00
Dan Brown
0dc1f0b07f Started draw.io integration 2017-12-24 14:28:35 +00:00
336 changed files with 20002 additions and 4527 deletions

View File

@@ -20,6 +20,8 @@ SESSION_DRIVER=file
#CACHE_DRIVER=memcached
#SESSION_DRIVER=memcached
QUEUE_DRIVER=sync
# A different prefix is useful when multiple BookStack instances use the same caching server
CACHE_PREFIX=bookstack
# Memcached settings
# If using a UNIX socket path for the host, set the port to 0
@@ -47,10 +49,15 @@ GITHUB_APP_SECRET=false
GOOGLE_APP_ID=false
GOOGLE_APP_SECRET=false
OKTA_BASE_URL=false
OKTA_KEY=false
OKTA_SECRET=false
OKTA_APP_ID=false
OKTA_APP_SECRET=false
TWITCH_APP_ID=false
TWITCH_APP_SECRET=false
GITLAB_APP_ID=false
GITLAB_APP_SECRET=false
GITLAB_BASE_URI=false
# External services such as Gravatar
# External services such as Gravatar and Draw.IO
DISABLE_EXTERNAL_SERVICES=false
# LDAP Settings
@@ -67,4 +74,6 @@ MAIL_HOST=localhost
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_ENCRYPTION=null
MAIL_FROM=null
MAIL_FROM_NAME=null

2
.gitignore vendored
View File

@@ -5,7 +5,7 @@ Homestead.yaml
.idea
npm-debug.log
yarn-error.log
/public/dist
/public/dist/*.map
/public/plugins
/public/css/*.map
/public/js/*.map

View File

@@ -16,7 +16,9 @@ class Activity extends Model
*/
public function entity()
{
if ($this->entity_type === '') $this->entity_type = null;
if ($this->entity_type === '') {
$this->entity_type = null;
}
return $this->morphTo('entity');
}
@@ -43,8 +45,8 @@ class Activity extends Model
* @param $activityB
* @return bool
*/
public function isSimilarTo($activityB) {
public function isSimilarTo($activityB)
{
return [$this->key, $this->entity_type, $this->entity_id] === [$activityB->key, $activityB->entity_type, $activityB->entity_id];
}
}

View File

@@ -1,6 +1,5 @@
<?php namespace BookStack;
class Attachment extends Ownable
{
protected $fillable = ['name', 'order'];
@@ -11,7 +10,9 @@ class Attachment extends Ownable
*/
public function getFileName()
{
if (str_contains($this->name, '.')) return $this->name;
if (str_contains($this->name, '.')) {
return $this->name;
}
return $this->name . '.' . $this->extension;
}
@@ -32,5 +33,4 @@ class Attachment extends Ownable
{
return baseUrl('/attachments/' . $this->id);
}
}

View File

@@ -2,6 +2,7 @@
class Book extends Entity
{
public $searchFactor = 2;
protected $fillable = ['name', 'description', 'image_id'];
@@ -27,7 +28,9 @@ class Book extends Entity
public function getBookCover($width = 440, $height = 250)
{
$default = baseUrl('/book_default_cover.png');
if (!$this->image_id) return $default;
if (!$this->image_id) {
return $default;
}
try {
$cover = $this->cover ? baseUrl($this->cover->getThumb($width, $height, false)) : $default;
@@ -91,5 +94,4 @@ class Book extends Entity
{
return "'BookStack\\\\Book' as entity_type, id, id as entity_id, slug, name, {$this->textField} as text,'' as html, '0' as book_id, '0' as priority, '0' as chapter_id, '0' as draft, created_by, updated_by, updated_at, created_at";
}
}

View File

@@ -1,8 +1,9 @@
<?php namespace BookStack;
class Chapter extends Entity
{
public $searchFactor = 1.3;
protected $fillable = ['name', 'description', 'priority', 'book_id'];
protected $with = ['book'];
@@ -59,5 +60,4 @@ class Chapter extends Entity
{
return "'BookStack\\\\Chapter' as entity_type, id, id as entity_id, slug, name, {$this->textField} as text, '' as html, book_id, priority, '0' as chapter_id, '0' as draft, created_by, updated_by, updated_at, created_at";
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace BookStack\Console\Commands;
use BookStack\Repos\UserRepo;
use Illuminate\Console\Command;
class CreateAdmin extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'bookstack:create-admin
{--email= : The email address for the new admin user}
{--name= : The name of the new admin user}
{--password= : The password to assign to the new admin user}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Add a new admin user to the system';
protected $userRepo;
/**
* Create a new command instance.
*
* @param UserRepo $userRepo
*/
public function __construct(UserRepo $userRepo)
{
$this->userRepo = $userRepo;
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
* @throws \BookStack\Exceptions\NotFoundException
*/
public function handle()
{
$email = trim($this->option('email'));
if (empty($email)) {
$email = $this->ask('Please specify an email address for the new admin user');
}
if (strlen($email) < 5 || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
return $this->error('Invalid email address provided');
}
if ($this->userRepo->getByEmail($email) !== null) {
return $this->error('A user with the provided email already exists!');
}
$name = trim($this->option('name'));
if (empty($name)) {
$name = $this->ask('Please specify an name for the new admin user');
}
if (strlen($name) < 2) {
return $this->error('Invalid name provided');
}
$password = trim($this->option('password'));
if (empty($password)) {
$password = $this->secret('Please specify a password for the new admin user');
}
if (strlen($password) < 5) {
return $this->error('Invalid password provided, Must be at least 5 characters');
}
$user = $this->userRepo->create(['email' => $email, 'name' => $name, 'password' => $password]);
$this->userRepo->attachSystemRole($user, 'admin');
$this->userRepo->downloadGravatarToUserAvatar($user);
$user->email_confirmed = true;
$user->save();
$this->info("Admin account with email \"{$user->email}\" successfully created!");
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace BookStack\Console\Commands;
use BookStack\User;
use BookStack\Repos\UserRepo;
use Illuminate\Console\Command;
class DeleteUsers extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'bookstack:delete-users';
protected $user;
protected $userRepo;
/**
* The console command description.
*
* @var string
*/
protected $description = 'Delete users that are not "admin" or system users.';
public function __construct(User $user, UserRepo $userRepo)
{
$this->user = $user;
$this->userRepo = $userRepo;
parent::__construct();
}
public function handle()
{
$confirm = $this->ask('This will delete all users from the system that are not "admin" or system users. Are you sure you want to continue? (Type "yes" to continue)');
$numDeleted = 0;
if (strtolower(trim($confirm)) === 'yes') {
$totalUsers = $this->user->count();
$users = $this->user->where('system_name', '=', null)->with('roles')->get();
foreach ($users as $user) {
if ($user->hasSystemRole('admin')) {
// don't delete users with "admin" role
continue;
}
$this->userRepo->destroy($user);
++$numDeleted;
}
$this->info("Deleted $numDeleted of $totalUsers total users.");
} else {
$this->info('Exiting...');
}
}
}

View File

@@ -1,13 +1,20 @@
<?php namespace BookStack;
use Illuminate\Database\Eloquent\Relations\MorphMany;
class Entity extends Ownable
{
/**
* @var string - Name of property where the main text content is found
*/
public $textField = 'description';
/**
* @var float - Multiplier for search indexing.
*/
public $searchFactor = 1.0;
/**
* Compares this entity to another given entity.
* Matches by comparing class and id.
@@ -28,7 +35,9 @@ class Entity extends Ownable
{
$matches = [get_class($this), $this->id] === [get_class($entity), $entity->id];
if ($matches) return true;
if ($matches) {
return true;
}
if (($entity->isA('chapter') || $entity->isA('page')) && $this->isA('book')) {
return $entity->book_id === $this->id;
@@ -159,7 +168,9 @@ class Entity extends Ownable
*/
public function getShortName($length = 25)
{
if (strlen($this->name) <= $length) return $this->name;
if (strlen($this->name) <= $length) {
return $this->name;
}
return substr($this->name, 0, $length - 3) . '...';
}
@@ -176,13 +187,19 @@ class Entity extends Ownable
* Return a generalised, common raw query that can be 'unioned' across entities.
* @return string
*/
public function entityRawQuery(){return '';}
public function entityRawQuery()
{
return '';
}
/**
* Get the url of this entity
* @param $path
* @return string
*/
public function getUrl($path){return '/';}
public function getUrl($path)
{
return '/';
}
}

View File

@@ -1,6 +1,5 @@
<?php namespace BookStack;
class EntityPermission extends Model
{

View File

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

View File

@@ -1,4 +1,6 @@
<?php namespace BookStack\Exceptions;
class ConfirmationEmailException extends NotifyException
{
class ConfirmationEmailException extends NotifyException {}
}

View File

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

View File

@@ -4,11 +4,14 @@ namespace BookStack\Exceptions;
use Exception;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Http\Request;
use Illuminate\Pipeline\Pipeline;
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
{
@@ -60,18 +63,44 @@ class Handler extends ExceptionHandler
return response()->view('errors/' . $code, ['message' => $message], $code);
}
// Handle 404 errors with a loaded session to enable showing user-specific information
if ($this->isExceptionType($e, NotFoundHttpException::class)) {
return $this->loadErrorMiddleware($request, function ($request) use ($e) {
$message = $e->getMessage() ?: trans('errors.404_page_not_found');
return response()->view('errors/404', ['message' => $message], 404);
});
}
return parent::render($request, $e);
}
/**
* Load the middleware required to show state/session-enabled error pages.
* @param Request $request
* @param $callback
* @return mixed
*/
protected function loadErrorMiddleware(Request $request, $callback)
{
$middleware = (\Route::getMiddlewareGroups()['web_errors']);
return (new Pipeline($this->container))
->send($request)
->through($middleware)
->then($callback);
}
/**
* Check the exception chain to compare against the original exception type.
* @param Exception $e
* @param $type
* @return bool
*/
protected function isExceptionType(Exception $e, $type) {
protected function isExceptionType(Exception $e, $type)
{
do {
if (is_a($e, $type)) return true;
if (is_a($e, $type)) {
return true;
}
} while ($e = $e->getPrevious());
return false;
}
@@ -81,7 +110,8 @@ class Handler extends ExceptionHandler
* @param Exception $e
* @return string
*/
protected function getOriginalMessage(Exception $e) {
protected function getOriginalMessage(Exception $e)
{
do {
$message = $e->getMessage();
} while ($e = $e->getPrevious());

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
<?php namespace BookStack\Exceptions;
class NotFoundException extends PrettyException {
class NotFoundException extends PrettyException
{
/**
* NotFoundException constructor.
@@ -11,4 +11,4 @@ class NotFoundException extends PrettyException {
{
parent::__construct($message, 404);
}
}
}

View File

@@ -1,6 +1,5 @@
<?php namespace BookStack\Exceptions;
class NotifyException extends \Exception
{
@@ -18,4 +17,4 @@ class NotifyException extends \Exception
$this->redirectLocation = $redirectLocation;
parent::__construct();
}
}
}

View File

@@ -1,6 +1,8 @@
<?php namespace BookStack\Exceptions;
use Exception;
class PermissionsException extends Exception {}
class PermissionsException extends Exception
{
}

View File

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

View File

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

View File

@@ -1,4 +1,6 @@
<?php namespace BookStack\Exceptions;
class SocialSignInException extends NotifyException
{
class SocialSignInException extends NotifyException {}
}

View File

@@ -1,4 +1,6 @@
<?php namespace BookStack\Exceptions;
class UserRegistrationException extends NotifyException
{
class UserRegistrationException extends NotifyException {}
}

View File

@@ -2,6 +2,7 @@
use BookStack\Exceptions\FileUploadException;
use BookStack\Attachment;
use BookStack\Exceptions\NotFoundException;
use BookStack\Repos\EntityRepo;
use BookStack\Services\AttachmentService;
use Illuminate\Http\Request;
@@ -182,11 +183,16 @@ class AttachmentController extends Controller
* Get an attachment from storage.
* @param $attachmentId
* @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Symfony\Component\HttpFoundation\Response
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
public function get($attachmentId)
{
$attachment = $this->attachment->findOrFail($attachmentId);
$page = $this->entityRepo->getById('page', $attachment->uploaded_to);
if ($page === null) {
throw new NotFoundException(trans('errors.attachment_not_found'));
}
$this->checkOwnablePermission('page-view', $page);
if ($attachment->external) {
@@ -204,6 +210,7 @@ class AttachmentController extends Controller
* Delete a specific attachment in the system.
* @param $attachmentId
* @return mixed
* @throws \Exception
*/
public function delete($attachmentId)
{

View File

@@ -64,5 +64,4 @@ class ForgotPasswordController extends Controller
['email' => trans($response)]
);
}
}
}

View File

@@ -70,7 +70,9 @@ class LoginController extends Controller
protected function authenticated(Request $request, Authenticatable $user)
{
// Explicitly log them out for now if they do no exist.
if (!$user->exists) auth()->logout($user);
if (!$user->exists) {
auth()->logout($user);
}
if (!$user->exists && $user->email === null && !$request->filled('email')) {
$request->flash();
@@ -83,7 +85,6 @@ class LoginController extends Controller
}
if (!$user->exists) {
// Check for users with same email already
$alreadyUser = $user->newQuery()->where('email', '=', $user->email)->count() > 0;
if ($alreadyUser) {
@@ -130,4 +131,4 @@ class LoginController extends Controller
session()->put('social-callback', 'login');
return $this->socialAuthService->startLogIn($socialDriver);
}
}
}

View File

@@ -91,6 +91,7 @@ class RegisterController extends Controller
/**
* Show the application registration form.
* @return Response
* @throws UserRegistrationException
*/
public function getRegister()
{
@@ -102,20 +103,13 @@ class RegisterController extends Controller
/**
* Handle a registration request for the application.
* @param Request|\Illuminate\Http\Request $request
* @return Response
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
* @throws UserRegistrationException
* @throws \Illuminate\Validation\ValidationException
*/
public function postRegister(Request $request)
{
$this->checkRegistrationAllowed();
$validator = $this->validator($request->all());
if ($validator->fails()) {
$this->throwValidationException(
$request, $validator
);
}
$this->validator($request->all())->validate();
$userData = $request->all();
return $this->registerUser($userData);
@@ -141,7 +135,6 @@ class RegisterController extends Controller
* @param bool|false|SocialAccount $socialAccount
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
* @throws UserRegistrationException
* @throws ConfirmationEmailException
*/
protected function registerUser(array $userData, $socialAccount = false)
{
@@ -239,6 +232,8 @@ class RegisterController extends Controller
* Redirect to the social site for authentication intended to register.
* @param $socialDriver
* @return mixed
* @throws UserRegistrationException
* @throws \BookStack\Exceptions\SocialDriverNotConfigured
*/
public function socialRegister($socialDriver)
{
@@ -272,8 +267,12 @@ class RegisterController extends Controller
}
$action = session()->pull('social-callback');
if ($action == 'login') return $this->socialAuthService->handleLoginCallback($socialDriver);
if ($action == 'register') return $this->socialRegisterCallback($socialDriver);
if ($action == 'login') {
return $this->socialAuthService->handleLoginCallback($socialDriver);
}
if ($action == 'register') {
return $this->socialRegisterCallback($socialDriver);
}
return redirect()->back();
}
@@ -291,7 +290,6 @@ class RegisterController extends Controller
* Register a new user after a registration callback.
* @param $socialDriver
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
* @throws ConfirmationEmailException
* @throws UserRegistrationException
* @throws \BookStack\Exceptions\SocialDriverNotConfigured
*/
@@ -308,5 +306,4 @@ class RegisterController extends Controller
];
return $this->registerUser($userData, $socialAccount);
}
}
}

View File

@@ -46,4 +46,4 @@ class ResetPasswordController extends Controller
return redirect($this->redirectPath())
->with('status', trans($response));
}
}
}

View File

@@ -36,17 +36,17 @@ class BookController extends Controller
*/
public function index()
{
$books = $this->entityRepo->getAllPaginated('book', 20);
$books = $this->entityRepo->getAllPaginated('book', 18);
$recents = $this->signedIn ? $this->entityRepo->getRecentlyViewed('book', 4, 0) : false;
$popular = $this->entityRepo->getPopular('book', 4, 0);
$new = $this->entityRepo->getRecentlyCreated('book', 4, 0);
$booksViewType = setting()->getUser($this->currentUser, 'books_view_type', 'list');
$booksViewType = setting()->getUser($this->currentUser, 'books_view_type', config('app.views.books', 'list'));
$this->setPageTitle(trans('entities.books'));
return view('books/index', [
'books' => $books,
'recents' => $recents,
'popular' => $popular,
'new' => $new,
'new' => $new,
'booksViewType' => $booksViewType
]);
}
@@ -109,7 +109,7 @@ class BookController extends Controller
{
$book = $this->entityRepo->getBySlug('book', $slug);
$this->checkOwnablePermission('book-update', $book);
$this->setPageTitle(trans('entities.books_edit_named',['bookName'=>$book->getShortName()]));
$this->setPageTitle(trans('entities.books_edit_named', ['bookName'=>$book->getShortName()]));
return view('books/edit', ['book' => $book, 'current' => $book]);
}
@@ -155,7 +155,7 @@ class BookController extends Controller
$book = $this->entityRepo->getBySlug('book', $bookSlug);
$this->checkOwnablePermission('book-update', $book);
$bookChildren = $this->entityRepo->getBookChildren($book, true);
$books = $this->entityRepo->getAll('book', false);
$books = $this->entityRepo->getAll('book', false, 'update');
$this->setPageTitle(trans('entities.books_sort_named', ['bookName'=>$book->getShortName()]));
return view('books/sort', ['book' => $book, 'current' => $book, 'books' => $books, 'bookChildren' => $bookChildren]);
}
@@ -190,42 +190,56 @@ class BookController extends Controller
}
// Sort pages and chapters
$sortedBooks = [];
$updatedModels = collect();
$sortMap = json_decode($request->get('sort-tree'));
$defaultBookId = $book->id;
$sortMap = collect(json_decode($request->get('sort-tree')));
$bookIdsInvolved = collect([$book->id]);
// Loop through contents of provided map and update entities accordingly
foreach ($sortMap as $bookChild) {
$priority = $bookChild->sort;
$id = intval($bookChild->id);
$isPage = $bookChild->type == 'page';
$bookId = $this->entityRepo->exists('book', $bookChild->book) ? intval($bookChild->book) : $defaultBookId;
$chapterId = ($isPage && $bookChild->parentChapter === false) ? 0 : intval($bookChild->parentChapter);
$model = $this->entityRepo->getById($isPage?'page':'chapter', $id);
// Load models into map
$sortMap->each(function ($mapItem) use ($bookIdsInvolved) {
$mapItem->type = ($mapItem->type === 'page' ? 'page' : 'chapter');
$mapItem->model = $this->entityRepo->getById($mapItem->type, $mapItem->id);
// Store source and target books
$bookIdsInvolved->push(intval($mapItem->model->book_id));
$bookIdsInvolved->push(intval($mapItem->book));
});
// Update models only if there's a change in parent chain or ordering.
if ($model->priority !== $priority || $model->book_id !== $bookId || ($isPage && $model->chapter_id !== $chapterId)) {
$this->entityRepo->changeBook($isPage?'page':'chapter', $bookId, $model);
$model->priority = $priority;
if ($isPage) $model->chapter_id = $chapterId;
// Get the books involved in the sort
$bookIdsInvolved = $bookIdsInvolved->unique()->toArray();
$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();
}
// Check permissions of involved books
$booksInvolved->each(function (Book $book) {
$this->checkOwnablePermission('book-update', $book);
});
// Perform the sort
$sortMap->each(function ($mapItem) {
$model = $mapItem->model;
$priorityChanged = intval($model->priority) !== intval($mapItem->sort);
$bookChanged = intval($model->book_id) !== intval($mapItem->book);
$chapterChanged = ($mapItem->type === 'page') && intval($model->chapter_id) !== $mapItem->parentChapter;
if ($bookChanged) {
$this->entityRepo->changeBook($mapItem->type, $mapItem->book, $model);
}
if ($chapterChanged) {
$model->chapter_id = intval($mapItem->parentChapter);
$model->save();
$updatedModels->push($model);
}
// Store involved books to be sorted later
if (!in_array($bookId, $sortedBooks)) {
$sortedBooks[] = $bookId;
if ($priorityChanged) {
$model->priority = intval($mapItem->sort);
$model->save();
}
}
});
// Add activity for books
foreach ($sortedBooks as $bookId) {
/** @var Book $updatedBook */
$updatedBook = $this->entityRepo->getById('book', $bookId);
$this->entityRepo->buildJointPermissionsForBook($updatedBook);
Activity::add($updatedBook, 'book_sort', $updatedBook->id);
}
// Rebuild permissions and add activity for involved books.
$booksInvolved->each(function (Book $book) {
$this->entityRepo->buildJointPermissionsForBook($book);
Activity::add($book, 'book_sort', $book->id);
});
return redirect($book->getUrl());
}

View File

@@ -159,7 +159,8 @@ class ChapterController extends Controller
* @return mixed
* @throws \BookStack\Exceptions\NotFoundException
*/
public function showMove($bookSlug, $chapterSlug) {
public function showMove($bookSlug, $chapterSlug)
{
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
$this->setPageTitle(trans('entities.chapters_move_named', ['chapterName' => $chapter->getShortName()]));
$this->checkOwnablePermission('chapter-update', $chapter);
@@ -177,7 +178,8 @@ class ChapterController extends Controller
* @return mixed
* @throws \BookStack\Exceptions\NotFoundException
*/
public function move($bookSlug, $chapterSlug, Request $request) {
public function move($bookSlug, $chapterSlug, Request $request)
{
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
$this->checkOwnablePermission('chapter-update', $chapter);

View File

@@ -51,7 +51,9 @@ abstract class Controller extends BaseController
*/
protected function preventAccessForDemoUsers()
{
if (config('app.env') === 'demo') $this->showPermissionError();
if (config('app.env') === 'demo') {
$this->showPermissionError();
}
}
/**
@@ -100,7 +102,9 @@ abstract class Controller extends BaseController
*/
protected function checkOwnablePermission($permission, Ownable $ownable)
{
if (userCan($permission, $ownable)) return true;
if (userCan($permission, $ownable)) {
return true;
}
return $this->showPermissionError();
}
@@ -113,7 +117,9 @@ abstract class Controller extends BaseController
protected function checkPermissionOr($permissionName, $callback)
{
$callbackResult = $callback();
if ($callbackResult === false) $this->checkPermission($permissionName);
if ($callbackResult === false) {
$this->checkPermission($permissionName);
}
return true;
}
@@ -145,5 +151,4 @@ abstract class Controller extends BaseController
->withInput($request->input())
->withErrors($errors, $this->errorBag());
}
}

View File

@@ -2,6 +2,7 @@
use Activity;
use BookStack\Repos\EntityRepo;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Views;
@@ -56,7 +57,8 @@ class HomeController extends Controller
* @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
* @throws \Exception
*/
public function getTranslations() {
public function getTranslations()
{
$locale = app()->getLocale();
$cacheKey = 'GLOBAL_TRANSLATIONS_' . $locale;
if (cache()->has($cacheKey) && config('app.env') !== 'development') {
@@ -87,6 +89,27 @@ class HomeController extends Controller
]);
}
/**
* Get an icon via image request.
* Can provide a 'color' parameter with hex value to color the icon.
* @param $iconName
* @param Request $request
* @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
*/
public function getIcon($iconName, Request $request)
{
$attrs = [];
if ($request->filled('color')) {
$attrs['fill'] = '#' . $request->get('color');
}
$icon = icon($iconName, $attrs);
return response($icon, 200, [
'Content-Type' => 'image/svg+xml',
'Cache-Control' => 'max-age=3600',
]);
}
/**
* Get custom head HTML, Used in ajax calls to show in editor.
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
@@ -95,5 +118,4 @@ class HomeController extends Controller
{
return view('partials/custom-head-content');
}
}

View File

@@ -1,6 +1,7 @@
<?php namespace BookStack\Http\Controllers;
use BookStack\Exceptions\ImageUploadException;
use BookStack\Exceptions\NotFoundException;
use BookStack\Repos\EntityRepo;
use BookStack\Repos\ImageRepo;
use Illuminate\Filesystem\Filesystem as File;
@@ -28,6 +29,21 @@ class ImageController extends Controller
parent::__construct();
}
/**
* Provide an image file from storage.
* @param string $path
* @return mixed
*/
public function showImage(string $path)
{
$path = storage_path('uploads/images/' . $path);
if (!file_exists($path)) {
abort(404);
}
return response()->file($path);
}
/**
* Get all images for a specific type, Paginated
* @param string $type
@@ -47,14 +63,14 @@ class ImageController extends Controller
* @param Request $request
* @return mixed
*/
public function searchByType($type, $page = 0, Request $request)
public function searchByType(Request $request, $type, $page = 0)
{
$this->validate($request, [
'term' => 'required|string'
]);
$searchTerm = $request->get('term');
$imgData = $this->imageRepo->searchPaginatedByType($type, $page, 24, $searchTerm);
$imgData = $this->imageRepo->searchPaginatedByType($type, $searchTerm, $page, 24);
return response()->json($imgData);
}
@@ -76,17 +92,19 @@ class ImageController extends Controller
* @param Request $request
* @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\Response
*/
public function getGalleryFiltered($filter, $page = 0, Request $request)
public function getGalleryFiltered(Request $request, $filter, $page = 0)
{
$this->validate($request, [
'page_id' => 'required|integer'
]);
$validFilters = collect(['page', 'book']);
if (!$validFilters->contains($filter)) return response('Invalid filter', 500);
if (!$validFilters->contains($filter)) {
return response('Invalid filter', 500);
}
$pageId = $request->get('page_id');
$imgData = $this->imageRepo->getGalleryFiltered($page, 24, strtolower($filter), $pageId);
$imgData = $this->imageRepo->getGalleryFiltered(strtolower($filter), $pageId, $page, 24);
return response()->json($imgData);
}
@@ -96,6 +114,7 @@ class ImageController extends Controller
* @param string $type
* @param Request $request
* @return \Illuminate\Http\JsonResponse
* @throws \Exception
*/
public function uploadByType($type, Request $request)
{
@@ -104,18 +123,90 @@ class ImageController extends Controller
'file' => 'is_image'
]);
if (!$this->imageRepo->isValidType($type)) {
return $this->jsonError(trans('errors.image_upload_type_error'));
}
$imageUpload = $request->file('file');
try {
$uploadedTo = $request->filled('uploaded_to') ? $request->get('uploaded_to') : 0;
$uploadedTo = $request->get('uploaded_to', 0);
$image = $this->imageRepo->saveNew($imageUpload, $type, $uploadedTo);
} catch (ImageUploadException $e) {
return response($e->getMessage(), 500);
}
return response()->json($image);
}
/**
* Upload a drawing to the system.
* @param Request $request
* @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\Response
*/
public function uploadDrawing(Request $request)
{
$this->validate($request, [
'image' => 'required|string',
'uploaded_to' => 'required|integer'
]);
$this->checkPermission('image-create-all');
$imageBase64Data = $request->get('image');
try {
$uploadedTo = $request->get('uploaded_to', 0);
$image = $this->imageRepo->saveDrawing($imageBase64Data, $uploadedTo);
} catch (ImageUploadException $e) {
return response($e->getMessage(), 500);
}
return response()->json($image);
}
/**
* Replace the data content of a drawing.
* @param string $id
* @param Request $request
* @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\Response
*/
public function replaceDrawing(string $id, Request $request)
{
$this->validate($request, [
'image' => 'required|string'
]);
$this->checkPermission('image-create-all');
$imageBase64Data = $request->get('image');
$image = $this->imageRepo->getById($id);
$this->checkOwnablePermission('image-update', $image);
try {
$image = $this->imageRepo->replaceDrawingContent($image, $imageBase64Data);
} catch (ImageUploadException $e) {
return response($e->getMessage(), 500);
}
return response()->json($image);
}
/**
* Get the content of an image based64 encoded.
* @param $id
* @return \Illuminate\Http\JsonResponse|mixed
*/
public function getBase64Image($id)
{
$image = $this->imageRepo->getById($id);
$imageData = $this->imageRepo->getImageData($image);
if ($imageData === null) {
return $this->jsonError("Image data could not be found");
}
return response()->json([
'content' => base64_encode($imageData)
]);
}
/**
* Generate a sized thumbnail for an image.
* @param $id
@@ -123,6 +214,8 @@ class ImageController extends Controller
* @param $height
* @param $crop
* @return \Illuminate\Http\JsonResponse
* @throws ImageUploadException
* @throws \Exception
*/
public function getThumbnail($id, $width, $height, $crop)
{
@@ -137,6 +230,8 @@ class ImageController extends Controller
* @param integer $imageId
* @param Request $request
* @return \Illuminate\Http\JsonResponse
* @throws ImageUploadException
* @throws \Exception
*/
public function update($imageId, Request $request)
{
@@ -173,6 +268,4 @@ class ImageController extends Controller
$this->imageRepo->destroyImage($image);
return response()->json(trans('components.images_deleted'));
}
}

View File

@@ -145,6 +145,7 @@ class PageController extends Controller
* @param string $bookSlug
* @param string $pageSlug
* @return Response
* @throws NotFoundException
*/
public function show($bookSlug, $pageSlug)
{
@@ -152,7 +153,9 @@ class PageController extends Controller
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
} catch (NotFoundException $e) {
$page = $this->entityRepo->getPageByOldSlug($pageSlug, $bookSlug);
if ($page === null) abort(404);
if ($page === null) {
throw $e;
}
return redirect($page->getUrl());
}
@@ -219,7 +222,9 @@ class PageController extends Controller
$warnings [] = $this->entityRepo->getUserPageDraftMessage($draft);
}
if (count($warnings) > 0) session()->flash('warning', implode("\n", $warnings));
if (count($warnings) > 0) {
session()->flash('warning', implode("\n", $warnings));
}
$draftsEnabled = $this->signedIn;
return view('pages/edit', [
@@ -603,5 +608,4 @@ class PageController extends Controller
session()->flash('success', trans('entities.pages_permissions_success'));
return redirect($page->getUrl());
}
}

View File

@@ -67,7 +67,9 @@ class PermissionController extends Controller
{
$this->checkPermission('user-roles-manage');
$role = $this->permissionsRepo->getRoleById($id);
if ($role->hidden) throw new PermissionsException(trans('errors.role_cannot_be_edited'));
if ($role->hidden) {
throw new PermissionsException(trans('errors.role_cannot_be_edited'));
}
return view('settings/roles/edit', ['role' => $role]);
}

View File

@@ -40,13 +40,12 @@ class SearchController extends Controller
$nextPageLink = baseUrl('/search?term=' . urlencode($searchTerm) . '&page=' . ($page+1));
$results = $this->searchService->searchEntities($searchTerm, 'all', $page, 20);
$hasNextPage = $this->searchService->searchEntities($searchTerm, 'all', $page+1, 20)['count'] > 0;
return view('search/all', [
'entities' => $results['results'],
'totalResults' => $results['total'],
'searchTerm' => $searchTerm,
'hasNextPage' => $hasNextPage,
'hasNextPage' => $results['has_more'],
'nextPageLink' => $nextPageLink
]);
}
@@ -104,7 +103,4 @@ class SearchController extends Controller
return view('search/entity-ajax-list', ['entities' => $entities]);
}
}

View File

@@ -33,7 +33,9 @@ class SettingController extends Controller
// Cycles through posted settings and update them
foreach ($request->all() as $name => $value) {
if (strpos($name, 'setting-') !== 0) continue;
if (strpos($name, 'setting-') !== 0) {
continue;
}
$key = str_replace('setting-', '', trim($name));
Setting::put($key, $value);
}
@@ -41,5 +43,4 @@ class SettingController extends Controller
session()->flash('success', trans('settings.settings_save_success'));
return redirect('/settings');
}
}

View File

@@ -54,5 +54,4 @@ class TagController extends Controller
$suggestions = $this->tagRepo->getValueSuggestions($searchTerm, $tagName);
return response()->json($suggestions);
}
}

View File

@@ -93,17 +93,7 @@ class UserController extends Controller
$user->roles()->sync($roles);
}
// Get avatar from gravatar and save
if (!config('services.disable_services')) {
try {
$avatar = \Images::saveUserGravatar($user);
$user->avatar()->associate($avatar);
$user->save();
} catch (Exception $e) {
\Log::error('Failed to save user gravatar image');
}
}
$this->userRepo->downloadGravatarToUserAvatar($user);
return redirect('/settings/users');
}
@@ -249,4 +239,27 @@ class UserController extends Controller
'assetCounts' => $assetCounts
]);
}
/**
* Update the user's preferred book-list display setting.
* @param $id
* @param Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function switchBookView($id, Request $request)
{
$this->checkPermissionOr('users-manage', function () use ($id) {
return $this->currentUser->id == $id;
});
$viewType = $request->get('book_view_type');
if (!in_array($viewType, ['grid', 'list'])) {
$viewType = 'list';
}
$user = $this->user->findOrFail($id);
setting()->putUser($user, 'books_view_type', $viewType);
return redirect()->back(302, [], "/settings/users/$id");
}
}

View File

@@ -33,6 +33,14 @@ class Kernel extends HttpKernel
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\BookStack\Http\Middleware\Localization::class
],
'web_errors' => [
\BookStack\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\BookStack\Http\Middleware\VerifyCsrfToken::class,
\BookStack\Http\Middleware\Localization::class
],
'api' => [
'throttle:60,1',
'bindings',

View File

@@ -19,7 +19,9 @@ class Localization
$locale = $defaultLang;
$availableLocales = config('app.locales');
foreach ($request->getLanguages() as $lang) {
if (!in_array($lang, $availableLocales)) continue;
if (!in_array($lang, $availableLocales)) {
continue;
}
$locale = $lang;
break;
}

View File

@@ -15,5 +15,4 @@ class Model extends EloquentModel
{
return parent::getAttributeFromArray($key);
}
}
}

View File

@@ -49,5 +49,4 @@ class ConfirmEmail extends Notification implements ShouldQueue
->line(trans('auth.email_confirm_text'))
->action(trans('auth.email_confirm_action'), baseUrl('/register/confirm/' . $this->token));
}
}

View File

@@ -1,6 +1,5 @@
<?php namespace BookStack;
abstract class Ownable extends Model
{
/**
@@ -29,5 +28,4 @@ abstract class Ownable extends Model
{
return strtolower(array_slice(explode('\\', static::class), -1, 1)[0]);
}
}
}

View File

@@ -1,6 +1,5 @@
<?php namespace BookStack;
class Page extends Entity
{
protected $fillable = ['name', 'html', 'priority', 'markdown'];
@@ -101,8 +100,8 @@ class Page extends Entity
* @return string
*/
public function entityRawQuery($withContent = false)
{ $htmlQuery = $withContent ? 'html' : "'' as html";
{
$htmlQuery = $withContent ? 'html' : "'' as html";
return "'BookStack\\\\Page' as entity_type, id, id as entity_id, slug, name, {$this->textField} as text, {$htmlQuery}, book_id, priority, chapter_id, draft, created_by, updated_by, updated_at, created_at";
}
}

View File

@@ -1,6 +1,5 @@
<?php namespace BookStack;
class PageRevision extends Model
{
protected $fillable = ['name', 'html', 'text', 'markdown', 'summary'];
@@ -31,7 +30,9 @@ class PageRevision extends Model
public function getUrl($path = null)
{
$url = $this->page->getUrl() . '/revisions/' . $this->id;
if ($path) return $url . '/' . trim($path, '/');
if ($path) {
return $url . '/' . trim($path, '/');
}
return $url;
}
@@ -58,5 +59,4 @@ class PageRevision extends Model
{
return $type === 'revision';
}
}

View File

@@ -15,12 +15,12 @@ class AppServiceProvider extends ServiceProvider
public function boot()
{
// Custom validation methods
Validator::extend('is_image', function($attribute, $value, $parameters, $validator) {
Validator::extend('is_image', function ($attribute, $value, $parameters, $validator) {
$imageMimes = ['image/png', 'image/bmp', 'image/gif', 'image/jpeg', 'image/jpg', 'image/tiff', 'image/webp'];
return in_array($value->getMimeType(), $imageMimes);
});
\Blade::directive('icon', function($expression) {
\Blade::directive('icon', function ($expression) {
return "<?php echo icon($expression); ?>";
});
@@ -35,7 +35,7 @@ class AppServiceProvider extends ServiceProvider
*/
public function register()
{
$this->app->singleton(SettingService::class, function($app) {
$this->app->singleton(SettingService::class, function ($app) {
return new SettingService($app->make(Setting::class), $app->make('Illuminate\Contracts\Cache\Repository'));
});
}

View File

@@ -25,7 +25,7 @@ class AuthServiceProvider extends ServiceProvider
*/
public function register()
{
Auth::provider('ldap', function($app, array $config) {
Auth::provider('ldap', function ($app, array $config) {
return new LdapUserProvider($config['model'], $app[LdapService::class]);
});
}

View File

@@ -34,28 +34,28 @@ class CustomFacadeProvider extends ServiceProvider
*/
public function register()
{
$this->app->bind('activity', function() {
$this->app->bind('activity', function () {
return new ActivityService(
$this->app->make(Activity::class),
$this->app->make(PermissionService::class)
);
});
$this->app->bind('views', function() {
$this->app->bind('views', function () {
return new ViewService(
$this->app->make(View::class),
$this->app->make(PermissionService::class)
);
});
$this->app->bind('setting', function() {
$this->app->bind('setting', function () {
return new SettingService(
$this->app->make(Setting::class),
$this->app->make(Repository::class)
);
});
$this->app->bind('images', function() {
$this->app->bind('images', function () {
return new ImageService(
$this->app->make(ImageManager::class),
$this->app->make(Factory::class),

View File

@@ -18,6 +18,8 @@ class EventServiceProvider extends ServiceProvider
'SocialiteProviders\Slack\SlackExtendSocialite@handle',
'SocialiteProviders\Azure\AzureExtendSocialite@handle',
'SocialiteProviders\Okta\OktaExtendSocialite@handle',
'SocialiteProviders\GitLab\GitLabExtendSocialite@handle',
'SocialiteProviders\Twitch\TwitchExtendSocialite@handle',
],
];

View File

@@ -2,7 +2,6 @@
namespace BookStack\Providers;
use BookStack\Role;
use BookStack\Services\LdapService;
use BookStack\User;
@@ -102,7 +101,9 @@ class LdapUserProvider implements UserProvider
{
// Get user via LDAP
$userDetails = $this->ldapService->getUserDetails($credentials['username']);
if ($userDetails === null) return null;
if ($userDetails === null) {
return null;
}
// Search current user base by looking up a uid
$model = $this->createModel();
@@ -110,7 +111,9 @@ class LdapUserProvider implements UserProvider
->where('external_auth_id', $userDetails['uid'])
->first();
if ($currentUser !== null) return $currentUser;
if ($currentUser !== null) {
return $currentUser;
}
$model->name = $userDetails['name'];
$model->external_auth_id = $userDetails['uid'];

View File

@@ -1,6 +1,5 @@
<?php namespace BookStack\Providers;
use Illuminate\Pagination\PaginationServiceProvider as IlluminatePaginationServiceProvider;
use Illuminate\Pagination\Paginator;
@@ -32,4 +31,4 @@ class PaginationServiceProvider extends IlluminatePaginationServiceProvider
return 1;
});
}
}
}

View File

@@ -7,7 +7,8 @@ use BookStack\Entity;
* Class CommentRepo
* @package BookStack\Repos
*/
class CommentRepo {
class CommentRepo
{
/**
* @var Comment $comment
@@ -39,7 +40,7 @@ class CommentRepo {
* @param array $data
* @return Comment
*/
public function create (Entity $entity, $data = [])
public function create(Entity $entity, $data = [])
{
$userId = user()->id;
$comment = $this->comment->newInstance($data);
@@ -81,7 +82,9 @@ class CommentRepo {
protected function getNextLocalId(Entity $entity)
{
$comments = $entity->comments(false)->orderBy('local_id', 'desc')->first();
if ($comments === null) return 1;
if ($comments === null) {
return 1;
}
return $comments->local_id + 1;
}
}
}

View File

@@ -77,11 +77,15 @@ class EntityRepo
* @param SearchService $searchService
*/
public function __construct(
Book $book, Chapter $chapter, Page $page, PageRevision $pageRevision,
ViewService $viewService, PermissionService $permissionService,
TagRepo $tagRepo, SearchService $searchService
)
{
Book $book,
Chapter $chapter,
Page $page,
PageRevision $pageRevision,
ViewService $viewService,
PermissionService $permissionService,
TagRepo $tagRepo,
SearchService $searchService
) {
$this->book = $book;
$this->chapter = $chapter;
$this->page = $page;
@@ -113,9 +117,9 @@ class EntityRepo
* @param bool $allowDrafts
* @return \Illuminate\Database\Query\Builder
*/
protected function entityQuery($type, $allowDrafts = false)
protected function entityQuery($type, $allowDrafts = false, $permission = 'view')
{
$q = $this->permissionService->enforceEntityRestrictions($type, $this->getEntity($type), 'view');
$q = $this->permissionService->enforceEntityRestrictions($type, $this->getEntity($type), $permission);
if (strtolower($type) === 'page' && !$allowDrafts) {
$q = $q->where('draft', '=', false);
}
@@ -163,14 +167,16 @@ class EntityRepo
$q = $this->entityQuery($type)->where('slug', '=', $slug);
if (strtolower($type) === 'chapter' || strtolower($type) === 'page') {
$q = $q->where('book_id', '=', function($query) use ($bookSlug) {
$q = $q->where('book_id', '=', function ($query) use ($bookSlug) {
$query->select('id')
->from($this->book->getTable())
->where('slug', '=', $bookSlug)->limit(1);
});
}
$entity = $q->first();
if ($entity === null) throw new NotFoundException(trans('errors.' . strtolower($type) . '_not_found'));
if ($entity === null) {
throw new NotFoundException(trans('errors.' . strtolower($type) . '_not_found'));
}
return $entity;
}
@@ -196,15 +202,18 @@ class EntityRepo
}
/**
* Get all entities of a type limited by count unless count if false.
* Get all entities of a type with the given permission, limited by count unless count is false.
* @param string $type
* @param integer|bool $count
* @param string $permission
* @return Collection
*/
public function getAll($type, $count = 20)
public function getAll($type, $count = 20, $permission = 'view')
{
$q = $this->entityQuery($type)->orderBy('name', 'asc');
if ($count !== false) $q = $q->take($count);
$q = $this->entityQuery($type, false, $permission)->orderBy('name', 'asc');
if ($count !== false) {
$q = $q->take($count);
}
return $q->get();
}
@@ -231,7 +240,9 @@ class EntityRepo
{
$query = $this->permissionService->enforceEntityRestrictions($type, $this->getEntity($type))
->orderBy('created_at', 'desc');
if (strtolower($type) === 'page') $query = $query->where('draft', '=', false);
if (strtolower($type) === 'page') {
$query = $query->where('draft', '=', false);
}
if ($additionalQuery !== false && is_callable($additionalQuery)) {
$additionalQuery($query);
}
@@ -250,7 +261,9 @@ class EntityRepo
{
$query = $this->permissionService->enforceEntityRestrictions($type, $this->getEntity($type))
->orderBy('updated_at', 'desc');
if (strtolower($type) === 'page') $query = $query->where('draft', '=', false);
if (strtolower($type) === 'page') {
$query = $query->where('draft', '=', false);
}
if ($additionalQuery !== false && is_callable($additionalQuery)) {
$additionalQuery($query);
}
@@ -347,12 +360,16 @@ class EntityRepo
$parents[$key] = $entities[$index];
$parents[$key]->setAttribute('pages', collect());
}
if ($entities[$index]->chapter_id === 0 || $entities[$index]->chapter_id === '0') $tree[] = $entities[$index];
if ($entities[$index]->chapter_id === 0 || $entities[$index]->chapter_id === '0') {
$tree[] = $entities[$index];
}
$entities[$index]->book = $book;
}
foreach ($entities as $entity) {
if ($entity->chapter_id === 0 || $entity->chapter_id === '0') continue;
if ($entity->chapter_id === 0 || $entity->chapter_id === '0') {
continue;
}
$parentKey = 'BookStack\\Chapter:' . $entity->chapter_id;
if (!isset($parents[$parentKey])) {
$tree[] = $entity;
@@ -431,7 +448,9 @@ class EntityRepo
if (strtolower($type) === 'page' || strtolower($type) === 'chapter') {
$query = $query->where('book_id', '=', $bookId);
}
if ($currentId) $query = $query->where('id', '!=', $currentId);
if ($currentId) {
$query = $query->where('id', '!=', $currentId);
}
return $query->count() > 0;
}
@@ -558,7 +577,9 @@ class EntityRepo
$slug = preg_replace('/[\+\/\\\?\@\}\{\.\,\=\[\]\#\&\!\*\'\;\:\$\%]/', '', mb_strtolower($name));
$slug = preg_replace('/\s{2,}/', ' ', $slug);
$slug = str_replace(' ', '-', $slug);
if ($slug === "") $slug = substr(md5(rand(1, 500)), 0, 5);
if ($slug === "") {
$slug = substr(md5(rand(1, 500)), 0, 5);
}
return $slug;
}
@@ -599,7 +620,9 @@ class EntityRepo
public function savePageRevision(Page $page, $summary = null)
{
$revision = $this->pageRevision->newInstance($page->toArray());
if (setting('app-editor') !== 'markdown') $revision->markdown = '';
if (setting('app-editor') !== 'markdown') {
$revision->markdown = '';
}
$revision->page_id = $page->id;
$revision->slug = $page->slug;
$revision->book_slug = $page->book->slug;
@@ -627,7 +650,9 @@ class EntityRepo
*/
protected function formatHtml($htmlText)
{
if ($htmlText == '') return $htmlText;
if ($htmlText == '') {
return $htmlText;
}
libxml_use_internal_errors(true);
$doc = new DOMDocument();
$doc->loadHTML(mb_convert_encoding($htmlText, 'HTML-ENTITIES', 'UTF-8'));
@@ -641,7 +666,9 @@ class EntityRepo
foreach ($childNodes as $index => $childNode) {
/** @var \DOMElement $childNode */
if (get_class($childNode) !== 'DOMElement') continue;
if (get_class($childNode) !== 'DOMElement') {
continue;
}
// Overwrite id if not a BookStack custom id
if ($childNode->hasAttribute('id')) {
@@ -686,14 +713,23 @@ class EntityRepo
public function renderPage(Page $page, $ignorePermissions = false)
{
$content = $page->html;
if (!config('app.allow_content_scripts')) {
$content = $this->escapeScripts($content);
}
$matches = [];
preg_match_all("/{{@\s?([0-9].*?)}}/", $content, $matches);
if (count($matches[0]) === 0) return $content;
if (count($matches[0]) === 0) {
return $content;
}
$topLevelTags = ['table', 'ul', 'ol'];
foreach ($matches[1] as $index => $includeId) {
$splitInclude = explode('#', $includeId, 2);
$pageId = intval($splitInclude[0]);
if (is_nan($pageId)) continue;
if (is_nan($pageId)) {
continue;
}
$matchedPage = $this->getById('page', $pageId, false, $ignorePermissions);
if ($matchedPage === null) {
@@ -714,8 +750,13 @@ class EntityRepo
continue;
}
$innerContent = '';
foreach ($matchingElem->childNodes as $childNode) {
$innerContent .= $doc->saveHTML($childNode);
$isTopLevel = in_array(strtolower($matchingElem->nodeName), $topLevelTags);
if ($isTopLevel) {
$innerContent .= $doc->saveHTML($matchingElem);
} else {
foreach ($matchingElem->childNodes as $childNode) {
$innerContent .= $doc->saveHTML($childNode);
}
}
$content = str_replace($matches[0][$index], trim($innerContent), $content);
}
@@ -723,6 +764,24 @@ class EntityRepo
return $content;
}
/**
* Escape script tags within HTML content.
* @param string $html
* @return mixed
*/
protected function escapeScripts(string $html)
{
$scriptSearchRegex = '/<script.*?>.*?<\/script>/ms';
$matches = [];
preg_match_all($scriptSearchRegex, $html, $matches);
if (count($matches) === 0) return $html;
foreach ($matches[0] as $match) {
$html = str_replace($match, htmlentities($match), $html);
}
return $html;
}
/**
* Get the plain text version of a page's content.
* @param Page $page
@@ -748,7 +807,9 @@ class EntityRepo
$page->updated_by = user()->id;
$page->draft = true;
if ($chapter) $page->chapter_id = $chapter->id;
if ($chapter) {
$page->chapter_id = $chapter->id;
}
$book->pages()->save($page);
$page = $this->page->find($page->id);
@@ -779,14 +840,18 @@ class EntityRepo
*/
public function getPageNav($pageContent)
{
if ($pageContent == '') return [];
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 [];
if (is_null($headers)) {
return [];
}
$tree = collect([]);
foreach ($headers as $header) {
@@ -802,7 +867,7 @@ class EntityRepo
// 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) {
$tree = $tree->map(function ($header) use ($minLevel) {
$header['level'] -= ($minLevel - 2);
return $header;
});
@@ -838,7 +903,9 @@ class EntityRepo
$page->fill($input);
$page->html = $this->formatHtml($input['html']);
$page->text = $this->pageToPlainText($page);
if (setting('app-editor') !== 'markdown') $page->markdown = '';
if (setting('app-editor') !== 'markdown') {
$page->markdown = '';
}
$page->updated_by = $userId;
$page->revision_count++;
$page->save();
@@ -900,7 +967,9 @@ class EntityRepo
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;
if ($draft->page->updated_at->timestamp <= $draft->updated_at->timestamp) {
return $message;
}
return $message . "\n" . trans('entities.pages_draft_edited_notification');
}
@@ -996,7 +1065,9 @@ class EntityRepo
}
$draft->fill($data);
if (setting('app-editor') !== 'markdown') $draft->markdown = '';
if (setting('app-editor') !== 'markdown') {
$draft->markdown = '';
}
$draft->save();
return $draft;
@@ -1103,17 +1174,4 @@ class EntityRepo
$page->delete();
}
}

View File

@@ -1,12 +1,9 @@
<?php namespace BookStack\Repos;
use BookStack\Image;
use BookStack\Page;
use BookStack\Services\ImageService;
use BookStack\Services\PermissionService;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Setting;
use Symfony\Component\HttpFoundation\File\UploadedFile;
class ImageRepo
@@ -95,7 +92,7 @@ class ImageRepo
* @param string $searchTerm
* @return array
*/
public function searchPaginatedByType($type, $page = 0, $pageSize = 24, $searchTerm)
public function searchPaginatedByType($type, $searchTerm, $page = 0, $pageSize = 24)
{
$images = $this->image->where('type', '=', strtolower($type))->where('name', 'LIKE', '%' . $searchTerm . '%');
return $this->returnPaginated($images, $page, $pageSize);
@@ -104,13 +101,13 @@ class ImageRepo
/**
* Get gallery images with a particular filter criteria such as
* being within the current book or page.
* @param int $pagination
* @param int $pageSize
* @param $filter
* @param $pageId
* @param int $pageNum
* @param int $pageSize
* @return array
*/
public function getGalleryFiltered($pagination = 0, $pageSize = 24, $filter, $pageId)
public function getGalleryFiltered($filter, $pageId, $pageNum = 0, $pageSize = 24)
{
$images = $this->image->where('type', '=', 'gallery');
@@ -123,7 +120,7 @@ class ImageRepo
$images = $images->whereIn('uploaded_to', $validPageIds);
}
return $this->returnPaginated($images, $pagination, $pageSize);
return $this->returnPaginated($images, $pageNum, $pageSize);
}
/**
@@ -132,6 +129,8 @@ class ImageRepo
* @param string $type
* @param int $uploadedTo
* @return Image
* @throws \BookStack\Exceptions\ImageUploadException
* @throws \Exception
*/
public function saveNew(UploadedFile $uploadFile, $type, $uploadedTo = 0)
{
@@ -140,11 +139,39 @@ class ImageRepo
return $image;
}
/**
* Save a drawing the the database;
* @param string $base64Uri
* @param int $uploadedTo
* @return Image
* @throws \BookStack\Exceptions\ImageUploadException
*/
public function saveDrawing(string $base64Uri, int $uploadedTo)
{
$name = 'Drawing-' . user()->getShortName(40) . '-' . strval(time()) . '.png';
$image = $this->imageService->saveNewFromBase64Uri($base64Uri, $name, 'drawio', $uploadedTo);
return $image;
}
/**
* Replace the image content of a drawing.
* @param Image $image
* @param string $base64Uri
* @return Image
* @throws \BookStack\Exceptions\ImageUploadException
*/
public function replaceDrawingContent(Image $image, string $base64Uri)
{
return $this->imageService->replaceImageDataFromBase64Uri($image, $base64Uri);
}
/**
* Update the details of an image via an array of properties.
* @param Image $image
* @param array $updateDetails
* @return Image
* @throws \BookStack\Exceptions\ImageUploadException
* @throws \Exception
*/
public function updateImageDetails(Image $image, $updateDetails)
{
@@ -170,6 +197,8 @@ class ImageRepo
/**
* Load thumbnails onto an image object.
* @param Image $image
* @throws \BookStack\Exceptions\ImageUploadException
* @throws \Exception
*/
private function loadThumbs(Image $image)
{
@@ -183,22 +212,45 @@ class ImageRepo
* Get the thumbnail for an image.
* If $keepRatio is true only the width will be used.
* Checks the cache then storage to avoid creating / accessing the filesystem on every check.
*
* @param Image $image
* @param int $width
* @param int $height
* @param bool $keepRatio
* @return string
* @throws \BookStack\Exceptions\ImageUploadException
* @throws \Exception
*/
public function getThumbnail(Image $image, $width = 220, $height = 220, $keepRatio = false)
{
try {
return $this->imageService->getThumbnail($image, $width, $height, $keepRatio);
} catch (FileNotFoundException $exception) {
$image->delete();
return [];
} catch (\Exception $exception) {
return null;
}
}
/**
* Get the raw image data from an Image.
* @param Image $image
* @return null|string
*/
public function getImageData(Image $image)
{
try {
return $this->imageService->getImageData($image);
} catch (\Exception $exception) {
return null;
}
}
}
/**
* Check if the provided image type is valid.
* @param $type
* @return bool
*/
public function isValidType($type)
{
$validTypes = ['drawing', 'gallery', 'cover', 'system', 'user'];
return in_array($type, $validTypes);
}
}

View File

@@ -1,6 +1,5 @@
<?php namespace BookStack\Repos;
use BookStack\Exceptions\PermissionsException;
use BookStack\RolePermission;
use BookStack\Role;
@@ -149,5 +148,4 @@ class PermissionsRepo
$this->permissionService->deleteJointPermissionsForRole($role);
$role->delete();
}
}
}

View File

@@ -52,7 +52,9 @@ class TagRepo
public function getForEntity($entityType, $entityId)
{
$entity = $this->getEntity($entityType, $entityId);
if ($entity === null) return collect();
if ($entity === null) {
return collect();
}
return $entity->tags;
}
@@ -95,7 +97,9 @@ class TagRepo
$query = $query->orderBy('count', 'desc')->take(50);
}
if ($tagName !== false) $query = $query->where('name', '=', $tagName);
if ($tagName !== false) {
$query = $query->where('name', '=', $tagName);
}
$query = $this->permissionService->filterRestrictedEntityRelations($query, 'tags', 'entity_id', 'entity_type');
return $query->get(['value'])->pluck('value');
@@ -112,7 +116,9 @@ class TagRepo
$entity->tags()->delete();
$newTags = [];
foreach ($tags as $tag) {
if (trim($tag['name']) === '') continue;
if (trim($tag['name']) === '') {
continue;
}
$newTags[] = $this->newInstanceFromInput($tag);
}
@@ -132,5 +138,4 @@ class TagRepo
$values = ['name' => $name, 'value' => $value];
return $this->tag->newInstance($values);
}
}
}

View File

@@ -1,8 +1,12 @@
<?php namespace BookStack\Repos;
use Activity;
use BookStack\Exceptions\NotFoundException;
use BookStack\Image;
use BookStack\Role;
use BookStack\User;
use Exception;
use Images;
class UserRepo
{
@@ -57,13 +61,13 @@ class UserRepo
* @param $sortData
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function getAllUsersPaginatedAndSorted($count = 20, $sortData)
public function getAllUsersPaginatedAndSorted($count, $sortData)
{
$query = $this->user->with('roles', 'avatar')->orderBy($sortData['sort'], $sortData['order']);
if ($sortData['search']) {
$term = '%' . $sortData['search'] . '%';
$query->where(function($query) use ($term) {
$query->where(function ($query) use ($term) {
$query->where('name', 'like', $term)
->orWhere('email', 'like', $term);
});
@@ -83,16 +87,7 @@ class UserRepo
$this->attachDefaultRole($user);
// Get avatar from gravatar and save
if (!config('services.disable_services')) {
try {
$avatar = \Images::saveUserGravatar($user);
$user->avatar()->associate($avatar);
$user->save();
} catch (Exception $e) {
$user->save();
\Log::error('Failed to save user gravatar image');
}
}
$this->downloadGravatarToUserAvatar($user);
return $user;
}
@@ -104,10 +99,27 @@ class UserRepo
public function attachDefaultRole($user)
{
$roleId = setting('registration-role');
if ($roleId === false) $roleId = $this->role->first()->id;
if ($roleId === false) {
$roleId = $this->role->first()->id;
}
$user->attachRoleId($roleId);
}
/**
* Assign a user to a system-level role.
* @param User $user
* @param $systemRoleName
* @throws NotFoundException
*/
public function attachSystemRole(User $user, $systemRoleName)
{
$role = $this->role->newQuery()->where('system_name', '=', $systemRoleName)->first();
if ($role === null) {
throw new NotFoundException("Role '{$systemRoleName}' not found");
}
$user->attachRole($role);
}
/**
* Checks if the give user is the only admin.
* @param User $user
@@ -115,10 +127,14 @@ class UserRepo
*/
public function isOnlyAdmin(User $user)
{
if (!$user->roles->pluck('name')->contains('admin')) return false;
if (!$user->hasSystemRole('admin')) {
return false;
}
$adminRole = $this->role->getRole('admin');
if ($adminRole->users->count() > 1) return false;
$adminRole = $this->role->getSystemRole('admin');
if ($adminRole->users->count() > 1) {
return false;
}
return true;
}
@@ -140,11 +156,18 @@ class UserRepo
/**
* Remove the given user from storage, Delete all related content.
* @param User $user
* @throws Exception
*/
public function destroy(User $user)
{
$user->socialAccounts()->delete();
$user->delete();
// Delete user profile images
$profileImages = $images = Image::where('type', '=', 'user')->where('created_by', '=', $user->id)->get();
foreach ($profileImages as $image) {
Images::destroyImage($image);
}
}
/**
@@ -156,7 +179,7 @@ class UserRepo
*/
public function getActivity(User $user, $count = 20, $page = 0)
{
return \Activity::userActivity($user, $count, $page);
return Activity::userActivity($user, $count, $page);
}
/**
@@ -213,4 +236,27 @@ class UserRepo
return $this->role->where('system_name', '!=', 'admin')->get();
}
}
/**
* 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 downloadGravatarToUserAvatar(User $user)
{
// Get avatar from gravatar and save
if (!config('services.gravatar')) {
return false;
}
try {
$avatar = Images::saveUserGravatar($user);
$user->avatar()->associate($avatar);
$user->save();
return true;
} catch (Exception $e) {
\Log::error('Failed to save user gravatar image');
return false;
}
}
}

View File

@@ -1,6 +1,5 @@
<?php namespace BookStack;
class Role extends Model
{
@@ -40,7 +39,9 @@ class Role extends Model
{
$permissions = $this->getRelationValue('permissions');
foreach ($permissions as $permission) {
if ($permission->getRawAttribute('name') === $permissionName) return true;
if ($permission->getRawAttribute('name') === $permissionName) {
return true;
}
}
return false;
}
@@ -91,5 +92,4 @@ class Role extends Model
{
return static::where('hidden', '=', false)->orderBy('name')->get();
}
}

View File

@@ -1,6 +1,5 @@
<?php namespace BookStack;
class RolePermission extends Model
{
/**
@@ -8,7 +7,7 @@ class RolePermission extends Model
*/
public function roles()
{
return $this->belongsToMany(Role::class, 'permission_role','permission_id', 'role_id');
return $this->belongsToMany(Role::class, 'permission_role', 'permission_id', 'role_id');
}
/**

View File

@@ -14,5 +14,4 @@ class SearchTerm extends Model
{
return $this->morphTo('entity');
}
}

View File

@@ -170,5 +170,4 @@ class ActivityService
Session::flash('success', $message);
}
}
}
}

View File

@@ -8,15 +8,31 @@ use Symfony\Component\HttpFoundation\File\UploadedFile;
class AttachmentService extends UploadService
{
/**
* Get the storage that will be used for storing files.
* @return \Illuminate\Contracts\Filesystem\Filesystem
*/
protected function getStorage()
{
$storageType = config('filesystems.default');
// Override default location if set to local public to ensure not visible.
if ($storageType === 'local') {
$storageType = 'local_secure';
}
return $this->fileSystem->disk($storageType);
}
/**
* Get an attachment from storage.
* @param Attachment $attachment
* @return string
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
public function getAttachmentFromStorage(Attachment $attachment)
{
$attachmentPath = $this->getStorageBasePath() . $attachment->path;
return $this->getStorage()->get($attachmentPath);
return $this->getStorage()->get($attachment->path);
}
/**
@@ -92,16 +108,6 @@ class AttachmentService extends UploadService
]);
}
/**
* Get the file storage base path, amended for storage type.
* This allows us to keep a generic path in the database.
* @return string
*/
private function getStorageBasePath()
{
return $this->isLocal() ? 'storage/' : '';
}
/**
* Updates the file ordering for a listing of attached files.
* @param array $attachmentList
@@ -138,6 +144,7 @@ class AttachmentService extends UploadService
/**
* Delete a File from the database and storage.
* @param Attachment $attachment
* @throws Exception
*/
public function deleteFile(Attachment $attachment)
{
@@ -157,11 +164,10 @@ class AttachmentService extends UploadService
*/
protected function deleteFileInStorage(Attachment $attachment)
{
$storedFilePath = $this->getStorageBasePath() . $attachment->path;
$storage = $this->getStorage();
$dirPath = dirname($storedFilePath);
$dirPath = dirname($attachment->path);
$storage->delete($storedFilePath);
$storage->delete($attachment->path);
if (count($storage->allFiles($dirPath)) === 0) {
$storage->deleteDirectory($dirPath);
}
@@ -179,23 +185,20 @@ class AttachmentService extends UploadService
$attachmentData = file_get_contents($uploadedFile->getRealPath());
$storage = $this->getStorage();
$attachmentBasePath = 'uploads/files/' . Date('Y-m-M') . '/';
$storageBasePath = $this->getStorageBasePath() . $attachmentBasePath;
$basePath = 'uploads/files/' . Date('Y-m-M') . '/';
$uploadFileName = $attachmentName;
while ($storage->exists($storageBasePath . $uploadFileName)) {
while ($storage->exists($basePath . $uploadFileName)) {
$uploadFileName = str_random(3) . $uploadFileName;
}
$attachmentPath = $attachmentBasePath . $uploadFileName;
$attachmentStoragePath = $this->getStorageBasePath() . $attachmentPath;
$attachmentPath = $basePath . $uploadFileName;
try {
$storage->put($attachmentStoragePath, $attachmentData);
$storage->put($attachmentPath, $attachmentData);
} catch (Exception $e) {
throw new FileUploadException(trans('errors.path_not_writable', ['filePath' => $attachmentStoragePath]));
throw new FileUploadException(trans('errors.path_not_writable', ['filePath' => $attachmentPath]));
}
return $attachmentPath;
}
}
}

View File

@@ -108,6 +108,4 @@ class EmailConfirmationService
}
return $token;
}
}
}

View File

@@ -42,7 +42,7 @@ class ExportService
public function chapterToContainedHtml(Chapter $chapter)
{
$pages = $this->entityRepo->getChapterChildren($chapter);
$pages->each(function($page) {
$pages->each(function ($page) {
$page->html = $this->entityRepo->renderPage($page);
});
$html = view('chapters/export', [
@@ -89,7 +89,7 @@ class ExportService
public function chapterToPdf(Chapter $chapter)
{
$pages = $this->entityRepo->getChapterChildren($chapter);
$pages->each(function($page) {
$pages->each(function ($page) {
$page->html = $this->entityRepo->renderPage($page);
});
$html = view('chapters/export', [
@@ -163,7 +163,9 @@ class ExportService
$pathString = public_path(trim($relString, '/'));
}
if ($isLocal && !file_exists($pathString)) continue;
if ($isLocal && !file_exists($pathString)) {
continue;
}
try {
if ($isLocal) {
$imageContent = file_get_contents($pathString);
@@ -173,7 +175,9 @@ class ExportService
$imageContent = curl_exec($ch);
$err = curl_error($ch);
curl_close($ch);
if ($err) throw new \Exception("Image fetch failed, Received error: " . $err);
if ($err) {
throw new \Exception("Image fetch failed, Received error: " . $err);
}
}
$imageEncoded = 'data:image/' . pathinfo($pathString, PATHINFO_EXTENSION) . ';base64,' . base64_encode($imageContent);
$newImageString = str_replace($srcString, $imageEncoded, $oldImgString);
@@ -257,17 +261,4 @@ class ExportService
}
return $text;
}
}

View File

@@ -1,6 +1,5 @@
<?php namespace BookStack\Services\Facades;
use Illuminate\Support\Facades\Facade;
class Activity extends Facade
@@ -10,5 +9,8 @@ class Activity extends Facade
*
* @return string
*/
protected static function getFacadeAccessor() { return 'activity'; }
}
protected static function getFacadeAccessor()
{
return 'activity';
}
}

View File

@@ -1,6 +1,5 @@
<?php namespace BookStack\Services\Facades;
use Illuminate\Support\Facades\Facade;
class Images extends Facade
@@ -10,5 +9,8 @@ class Images extends Facade
*
* @return string
*/
protected static function getFacadeAccessor() { return 'images'; }
}
protected static function getFacadeAccessor()
{
return 'images';
}
}

View File

@@ -1,6 +1,5 @@
<?php namespace BookStack\Services\Facades;
use Illuminate\Support\Facades\Facade;
class Setting extends Facade
@@ -10,5 +9,8 @@ class Setting extends Facade
*
* @return string
*/
protected static function getFacadeAccessor() { return 'setting'; }
}
protected static function getFacadeAccessor()
{
return 'setting';
}
}

View File

@@ -9,5 +9,8 @@ class Views extends Facade
*
* @return string
*/
protected static function getFacadeAccessor() { return 'views'; }
}
protected static function getFacadeAccessor()
{
return 'views';
}
}

View File

@@ -31,6 +31,23 @@ class ImageService extends UploadService
parent::__construct($fileSystem);
}
/**
* Get the storage that will be used for storing images.
* @param string $type
* @return \Illuminate\Contracts\Filesystem\Filesystem
*/
protected function getStorage($type = '')
{
$storageType = config('filesystems.default');
// Override default location if set to local public to ensure not visible.
if ($type === 'system' && $storageType === 'local_secure') {
$storageType = 'local';
}
return $this->fileSystem->disk($storageType);
}
/**
* Saves a new image from an upload.
* @param UploadedFile $uploadedFile
@@ -46,6 +63,49 @@ class ImageService extends UploadService
return $this->saveNew($imageName, $imageData, $type, $uploadedTo);
}
/**
* Save a new image from a uri-encoded base64 string of data.
* @param string $base64Uri
* @param string $name
* @param string $type
* @param int $uploadedTo
* @return Image
* @throws ImageUploadException
*/
public function saveNewFromBase64Uri(string $base64Uri, string $name, string $type, $uploadedTo = 0)
{
$splitData = explode(';base64,', $base64Uri);
if (count($splitData) < 2) {
throw new ImageUploadException("Invalid base64 image data provided");
}
$data = base64_decode($splitData[1]);
return $this->saveNew($name, $data, $type, $uploadedTo);
}
/**
* Replace the data for an image via a Base64 encoded string.
* @param Image $image
* @param string $base64Uri
* @return Image
* @throws ImageUploadException
*/
public function replaceImageDataFromBase64Uri(Image $image, string $base64Uri)
{
$splitData = explode(';base64,', $base64Uri);
if (count($splitData) < 2) {
throw new ImageUploadException("Invalid base64 image data provided");
}
$data = base64_decode($splitData[1]);
$storage = $this->getStorage();
try {
$storage->put($image->path, $data);
} catch (Exception $e) {
throw new ImageUploadException(trans('errors.path_not_writable', ['filePath' => $image->path]));
}
return $image;
}
/**
* Gets an image from url and saves it to the database.
@@ -59,7 +119,9 @@ class ImageService extends UploadService
{
$imageName = $imageName ? $imageName : basename($url);
$imageData = file_get_contents($url);
if($imageData === false) throw new \Exception(trans('errors.cannot_get_image_from_url', ['url' => $url]));
if ($imageData === false) {
throw new \Exception(trans('errors.cannot_get_image_from_url', ['url' => $url]));
}
return $this->saveNew($imageName, $imageData, $type);
}
@@ -74,16 +136,16 @@ class ImageService extends UploadService
*/
private function saveNew($imageName, $imageData, $type, $uploadedTo = 0)
{
$storage = $this->getStorage();
$storage = $this->getStorage($type);
$secureUploads = setting('app-secure-images');
$imageName = str_replace(' ', '-', $imageName);
if ($secureUploads) $imageName = str_random(16) . '-' . $imageName;
if ($secureUploads) {
$imageName = str_random(16) . '-' . $imageName;
}
$imagePath = '/uploads/images/' . $type . '/' . Date('Y-m-M') . '/';
if ($this->isLocal()) $imagePath = '/public' . $imagePath;
while ($storage->exists($imagePath . $imageName)) {
$imageName = str_random(3) . $imageName;
}
@@ -96,8 +158,6 @@ class ImageService extends UploadService
throw new ImageUploadException(trans('errors.path_not_writable', ['filePath' => $fullPath]));
}
if ($this->isLocal()) $fullPath = str_replace_first('/public', '', $fullPath);
$imageDetails = [
'name' => $imageName,
'path' => $fullPath,
@@ -112,8 +172,8 @@ class ImageService extends UploadService
$imageDetails['updated_by'] = $userId;
}
$image = Image::forceCreate($imageDetails);
$image = (new Image());
$image->forceFill($imageDetails)->save();
return $image;
}
@@ -124,14 +184,22 @@ class ImageService extends UploadService
*/
protected function getPath(Image $image)
{
return ($this->isLocal()) ? ('public/' . $image->path) : $image->path;
return $image->path;
}
/**
* Checks if the image is a gif. Returns true if it is, else false.
* @param Image $image
* @return boolean
*/
protected function isGif(Image $image) {
return strtolower(pathinfo($this->getPath($image), PATHINFO_EXTENSION)) === 'gif';
}
/**
* Get the thumbnail for an image.
* If $keepRatio is true only the width will be used.
* Checks the cache then storage to avoid creating / accessing the filesystem on every check.
*
* @param Image $image
* @param int $width
* @param int $height
@@ -142,6 +210,10 @@ class ImageService extends UploadService
*/
public function getThumbnail(Image $image, $width = 220, $height = 220, $keepRatio = false)
{
if ($keepRatio && $this->isGif($image)) {
return $this->getPublicUrl($this->getPath($image));
}
$thumbDirName = '/' . ($keepRatio ? 'scaled-' : 'thumbs-') . $width . '-' . $height . '/';
$imagePath = $this->getPath($image);
$thumbFilePath = dirname($imagePath) . $thumbDirName . basename($imagePath);
@@ -150,8 +222,7 @@ class ImageService extends UploadService
return $this->getPublicUrl($thumbFilePath);
}
$storage = $this->getStorage();
$storage = $this->getStorage($image->type);
if ($storage->exists($thumbFilePath)) {
return $this->getPublicUrl($thumbFilePath);
}
@@ -161,9 +232,8 @@ class ImageService extends UploadService
} catch (Exception $e) {
if ($e instanceof \ErrorException || $e instanceof NotSupportedException) {
throw new ImageUploadException(trans('errors.cannot_create_thumbs'));
} else {
throw $e;
}
throw $e;
}
if ($keepRatio) {
@@ -183,10 +253,24 @@ class ImageService extends UploadService
return $this->getPublicUrl($thumbFilePath);
}
/**
* Get the raw data content from an image.
* @param Image $image
* @return string
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
public function getImageData(Image $image)
{
$imagePath = $this->getPath($image);
$storage = $this->getStorage();
return $storage->get($imagePath);
}
/**
* Destroys an Image object along with its files and thumbnails.
* @param Image $image
* @return bool
* @throws Exception
*/
public function destroyImage(Image $image)
{
@@ -205,9 +289,13 @@ class ImageService extends UploadService
// Cleanup of empty folders
foreach ($storage->directories($imageFolder) as $directory) {
if ($this->isFolderEmpty($directory)) $storage->deleteDirectory($directory);
if ($this->isFolderEmpty($directory)) {
$storage->deleteDirectory($directory);
}
}
if ($this->isFolderEmpty($imageFolder)) {
$storage->deleteDirectory($imageFolder);
}
if ($this->isFolderEmpty($imageFolder)) $storage->deleteDirectory($imageFolder);
$image->delete();
return true;
@@ -216,8 +304,9 @@ class ImageService extends UploadService
/**
* Save a gravatar image and set a the profile image for a user.
* @param User $user
* @param int $size
* @param int $size
* @return mixed
* @throws Exception
*/
public function saveUserGravatar(User $user, $size = 500)
{
@@ -252,14 +341,10 @@ class ImageService extends UploadService
$storageUrl = 'https://s3-' . $storageDetails['region'] . '.amazonaws.com/' . $storageDetails['bucket'];
}
}
$this->storageUrl = $storageUrl;
}
if ($this->isLocal()) $filePath = str_replace_first('public/', '', $filePath);
return ($this->storageUrl == false ? rtrim(baseUrl(''), '/') : rtrim($this->storageUrl, '/')) . $filePath;
$basePath = ($this->storageUrl == false) ? baseUrl('/') : $this->storageUrl;
return rtrim($basePath, '/') . $filePath;
}
}

View File

@@ -1,6 +1,5 @@
<?php namespace BookStack\Services;
/**
* Class Ldap
* An object-orientated thin abstraction wrapper for common PHP LDAP functions.
@@ -93,5 +92,4 @@ class Ldap
{
return ldap_bind($ldapConnection, $bindRdn, $bindPassword);
}
}

View File

@@ -1,6 +1,5 @@
<?php namespace BookStack\Services;
use BookStack\Exceptions\LdapException;
use Illuminate\Contracts\Auth\Authenticatable;
@@ -45,7 +44,9 @@ class LdapService
$followReferrals = $this->config['follow_referrals'] ? 1 : 0;
$this->ldap->setOption($ldapConnection, LDAP_OPT_REFERRALS, $followReferrals);
$users = $this->ldap->searchAndGetEntries($ldapConnection, $baseDn, $userFilter, ['cn', 'uid', 'dn', $emailAttr]);
if ($users['count'] === 0) return null;
if ($users['count'] === 0) {
return null;
}
$user = $users[0];
return [
@@ -66,8 +67,12 @@ class LdapService
public function validateUserCredentials(Authenticatable $user, $username, $password)
{
$ldapUser = $this->getUserDetails($username);
if ($ldapUser === null) return false;
if ($ldapUser['uid'] !== $user->external_auth_id) return false;
if ($ldapUser === null) {
return false;
}
if ($ldapUser['uid'] !== $user->external_auth_id) {
return false;
}
$ldapConnection = $this->getConnection();
try {
@@ -97,7 +102,9 @@ class LdapService
$ldapBind = $this->ldap->bind($connection, $ldapDn, $ldapPass);
}
if (!$ldapBind) throw new LdapException(($isAnonymous ? trans('errors.ldap_fail_anonymous') : trans('errors.ldap_fail_authed')));
if (!$ldapBind) {
throw new LdapException(($isAnonymous ? trans('errors.ldap_fail_anonymous') : trans('errors.ldap_fail_authed')));
}
}
/**
@@ -108,7 +115,9 @@ class LdapService
*/
protected function getConnection()
{
if ($this->ldapConnection !== null) return $this->ldapConnection;
if ($this->ldapConnection !== null) {
return $this->ldapConnection;
}
// Check LDAP extension in installed
if (!function_exists('ldap_connect') && config('app.env') !== 'testing') {
@@ -118,7 +127,9 @@ class LdapService
// Get port from server string and protocol if specified.
$ldapServer = explode(':', $this->config['server']);
$hasProtocol = preg_match('/^ldaps{0,1}\:\/\//', $this->config['server']) === 1;
if (!$hasProtocol) array_unshift($ldapServer, '');
if (!$hasProtocol) {
array_unshift($ldapServer, '');
}
$hostName = $ldapServer[0] . ($hasProtocol?':':'') . $ldapServer[1];
$defaultPort = $ldapServer[0] === 'ldaps' ? 636 : 389;
$ldapConnection = $this->ldap->connect($hostName, count($ldapServer) > 2 ? intval($ldapServer[2]) : $defaultPort);
@@ -151,5 +162,4 @@ class LdapService
}
return strtr($filterString, $newAttrs);
}
}
}

View File

@@ -88,7 +88,9 @@ class PermissionService
}
$book = $this->book->find($bookId);
if ($book === null) $book = false;
if ($book === null) {
$book = false;
}
if (isset($this->entityCache['books'])) {
$this->entityCache['books']->put($bookId, $book);
}
@@ -108,7 +110,9 @@ class PermissionService
}
$chapter = $this->chapter->find($chapterId);
if ($chapter === null) $chapter = false;
if ($chapter === null) {
$chapter = false;
}
if (isset($this->entityCache['chapters'])) {
$this->entityCache['chapters']->put($chapterId, $chapter);
}
@@ -122,7 +126,9 @@ class PermissionService
*/
protected function getRoles()
{
if ($this->userRoles !== false) return $this->userRoles;
if ($this->userRoles !== false) {
return $this->userRoles;
}
$roles = [];
@@ -161,9 +167,9 @@ class PermissionService
*/
protected function bookFetchQuery()
{
return $this->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) {
}, 'pages' => function ($query) {
$query->select(['id', 'restricted', 'created_by', 'book_id', 'chapter_id']);
}]);
}
@@ -174,7 +180,8 @@ class PermissionService
* @param array $roles
* @param bool $deleteOld
*/
protected function buildJointPermissionsForBooks($books, $roles, $deleteOld = false) {
protected function buildJointPermissionsForBooks($books, $roles, $deleteOld = false)
{
$entities = clone $books;
/** @var Book $book */
@@ -187,7 +194,9 @@ class PermissionService
}
}
if ($deleteOld) $this->deleteManyJointPermissionsForEntities($entities->all());
if ($deleteOld) {
$this->deleteManyJointPermissionsForEntities($entities->all());
}
$this->createManyJointPermissions($entities, $roles);
}
@@ -261,7 +270,7 @@ class PermissionService
*/
protected function deleteManyJointPermissionsForRoles($roles)
{
$roleIds = array_map(function($role) {
$roleIds = array_map(function ($role) {
return $role->id;
}, $roles);
$this->jointPermission->newQuery()->whereIn('role_id', $roleIds)->delete();
@@ -282,21 +291,22 @@ class PermissionService
*/
protected function deleteManyJointPermissionsForEntities($entities)
{
if (count($entities) === 0) return;
if (count($entities) === 0) {
return;
}
$this->db->transaction(function() use ($entities) {
$this->db->transaction(function () use ($entities) {
foreach (array_chunk($entities, 1000) as $entityChunk) {
$query = $this->db->table('joint_permissions');
foreach ($entityChunk as $entity) {
$query->orWhere(function(QueryBuilder $query) use ($entity) {
$query->orWhere(function (QueryBuilder $query) use ($entity) {
$query->where('entity_id', '=', $entity->id)
->where('entity_type', '=', $entity->getMorphClass());
});
}
$query->delete();
}
});
}
@@ -315,7 +325,7 @@ class PermissionService
$permissionFetch = $this->entityPermission->newQuery();
foreach ($entities as $entity) {
$entityRestrictedMap[$entity->getMorphClass() . ':' . $entity->id] = boolval($entity->getRawAttribute('restricted'));
$permissionFetch->orWhere(function($query) use ($entity) {
$permissionFetch->orWhere(function ($query) use ($entity) {
$query->where('restrictable_id', '=', $entity->id)->where('restrictable_type', '=', $entity->getMorphClass());
});
}
@@ -346,7 +356,7 @@ class PermissionService
}
}
$this->db->transaction(function() use ($jointPermissions) {
$this->db->transaction(function () use ($jointPermissions) {
foreach (array_chunk($jointPermissions, 1000) as $jointPermissionChunk) {
$this->db->table('joint_permissions')->insert($jointPermissionChunk);
}
@@ -362,8 +372,12 @@ class PermissionService
protected function getActions(Entity $entity)
{
$baseActions = ['view', 'update', 'delete'];
if ($entity->isA('chapter') || $entity->isA('book')) $baseActions[] = 'page-create';
if ($entity->isA('book')) $baseActions[] = 'chapter-create';
if ($entity->isA('chapter') || $entity->isA('book')) {
$baseActions[] = 'page-create';
}
if ($entity->isA('book')) {
$baseActions[] = 'chapter-create';
}
return $baseActions;
}
@@ -412,7 +426,10 @@ class PermissionService
}
}
return $this->createJointPermissionDataArray($entity, $role, $action,
return $this->createJointPermissionDataArray(
$entity,
$role,
$action,
($hasExplicitAccessToParents || ($roleHasPermission && $hasPermissiveAccessToParents)),
($hasExplicitAccessToParents || ($roleHasPermissionOwn && $hasPermissiveAccessToParents))
);
@@ -426,7 +443,8 @@ class PermissionService
* @param $action
* @return bool
*/
protected function mapHasActiveRestriction($entityMap, Entity $entity, Role $role, $action) {
protected function mapHasActiveRestriction($entityMap, Entity $entity, Role $role, $action)
{
$key = $entity->getMorphClass() . ':' . $entity->getRawAttribute('id') . ':' . $role->getRawAttribute('id') . ':' . $action;
return isset($entityMap[$key]) ? $entityMap[$key] : false;
}
@@ -545,11 +563,12 @@ class PermissionService
* @param bool $fetchPageContent
* @return QueryBuilder
*/
public function bookChildrenQuery($book_id, $filterDrafts = false, $fetchPageContent = false) {
$pageSelect = $this->db->table('pages')->selectRaw($this->page->entityRawQuery($fetchPageContent))->where('book_id', '=', $book_id)->where(function($query) use ($filterDrafts) {
public function bookChildrenQuery($book_id, $filterDrafts = false, $fetchPageContent = false)
{
$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) {
$query->orWhere(function ($query) {
$query->where('draft', '=', 1)->where('created_by', '=', $this->currentUser()->id);
});
}
@@ -562,8 +581,8 @@ class PermissionService
$whereQuery = $this->db->table('joint_permissions as jp')->selectRaw('COUNT(*)')
->whereRaw('jp.entity_id=U.id')->whereRaw('jp.entity_type=U.entity_type')
->where('jp.action', '=', 'view')->whereIn('jp.role_id', $this->getRoles())
->where(function($query) {
$query->where('jp.has_permission', '=', 1)->orWhere(function($query) {
->where(function ($query) {
$query->where('jp.has_permission', '=', 1)->orWhere(function ($query) {
$query->where('jp.has_permission_own', '=', 1)->where('jp.created_by', '=', $this->currentUser()->id);
});
});
@@ -715,5 +734,4 @@ class PermissionService
$this->userRoles = false;
$this->isAdminUser = null;
}
}
}

View File

@@ -64,7 +64,7 @@ class SearchService
* @param string $searchString
* @param string $entityType
* @param int $page
* @param int $count
* @param int $count - Count of each entity to search, Total returned could can be larger and not guaranteed.
* @return array[int, Collection];
*/
public function searchEntities($searchString, $entityType = 'all', $page = 1, $count = 20)
@@ -72,7 +72,6 @@ class SearchService
$terms = $this->parseSearchString($searchString);
$entityTypes = array_keys($this->entities);
$entityTypesToSearch = $entityTypes;
$results = collect();
if ($entityType !== 'all') {
$entityTypesToSearch = $entityType;
@@ -80,18 +79,27 @@ class SearchService
$entityTypesToSearch = explode('|', $terms['filters']['type']);
}
$results = collect();
$total = 0;
$hasMore = false;
foreach ($entityTypesToSearch as $entityType) {
if (!in_array($entityType, $entityTypes)) continue;
if (!in_array($entityType, $entityTypes)) {
continue;
}
$search = $this->searchEntityTable($terms, $entityType, $page, $count);
$total += $this->searchEntityTable($terms, $entityType, $page, $count, true);
$entityTotal = $this->searchEntityTable($terms, $entityType, $page, $count, true);
if ($entityTotal > $page * $count) {
$hasMore = true;
}
$total += $entityTotal;
$results = $results->merge($search);
}
return [
'total' => $total,
'count' => count($results),
'has_more' => $hasMore,
'results' => $results->sortByDesc('score')->values()
];
}
@@ -111,7 +119,9 @@ class SearchService
$results = collect();
foreach ($entityTypesToSearch as $entityType) {
if (!in_array($entityType, $entityTypes)) continue;
if (!in_array($entityType, $entityTypes)) {
continue;
}
$search = $this->buildEntitySearchQuery($terms, $entityType)->where('book_id', '=', $bookId)->take(20)->get();
$results = $results->merge($search);
}
@@ -143,7 +153,9 @@ class SearchService
public function searchEntityTable($terms, $entityType = 'page', $page = 1, $count = 20, $getCount = false)
{
$query = $this->buildEntitySearchQuery($terms, $entityType);
if ($getCount) return $query->count();
if ($getCount) {
return $query->count();
}
$query = $query->skip(($page-1) * $count)->take($count);
return $query->get();
@@ -164,12 +176,12 @@ class SearchService
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', '=', 'BookStack\\' . ucfirst($entityType));
$subQuery->where(function(Builder $query) use ($terms) {
$subQuery->where(function (Builder $query) use ($terms) {
foreach ($terms['search'] as $inputTerm) {
$query->orWhere('term', 'like', $inputTerm .'%');
}
})->groupBy('entity_type', 'entity_id');
$entitySelect->join(\DB::raw('(' . $subQuery->toSql() . ') as s'), function(JoinClause $join) {
$entitySelect->join(\DB::raw('(' . $subQuery->toSql() . ') as s'), function (JoinClause $join) {
$join->on('id', '=', 'entity_id');
})->selectRaw($entity->getTable().'.*, s.score')->orderBy('score', 'desc');
$entitySelect->mergeBindings($subQuery);
@@ -177,7 +189,7 @@ class SearchService
// Handle exact term matching
if (count($terms['exact']) > 0) {
$entitySelect->where(function(\Illuminate\Database\Eloquent\Builder $query) use ($terms, $entity) {
$entitySelect->where(function (\Illuminate\Database\Eloquent\Builder $query) use ($terms, $entity) {
foreach ($terms['exact'] as $inputTerm) {
$query->where(function (\Illuminate\Database\Eloquent\Builder $query) use ($inputTerm, $entity) {
$query->where('name', 'like', '%'.$inputTerm .'%')
@@ -195,7 +207,9 @@ class SearchService
// Handle filters
foreach ($terms['filters'] as $filterTerm => $filterValue) {
$functionName = camel_case('filter_' . $filterTerm);
if (method_exists($this, $functionName)) $this->$functionName($entitySelect, $entity, $filterValue);
if (method_exists($this, $functionName)) {
$this->$functionName($entitySelect, $entity, $filterValue);
}
}
return $this->permissionService->enforceEntityRestrictions($entityType, $entitySelect, 'view');
@@ -234,7 +248,9 @@ class SearchService
// Parse standard terms
foreach (explode(' ', trim($searchString)) as $searchTerm) {
if ($searchTerm !== '') $terms['search'][] = $searchTerm;
if ($searchTerm !== '') {
$terms['search'][] = $searchTerm;
}
}
// Split filter values out
@@ -267,15 +283,18 @@ class SearchService
* @param string $tagTerm
* @return mixed
*/
protected function applyTagSearch(\Illuminate\Database\Eloquent\Builder $query, $tagTerm) {
protected function applyTagSearch(\Illuminate\Database\Eloquent\Builder $query, $tagTerm)
{
preg_match("/^(.*?)((".$this->getRegexEscapedOperators().")(.*?))?$/", $tagTerm, $tagSplit);
$query->whereHas('tags', function(\Illuminate\Database\Eloquent\Builder $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] : '';
$validOperator = in_array($tagOperator, $this->queryOperators);
if (!empty($tagOperator) && !empty($tagValue) && $validOperator) {
if (!empty($tagName)) $query->where('name', '=', $tagName);
if (!empty($tagName)) {
$query->where('name', '=', $tagName);
}
if (is_numeric($tagValue) && $tagOperator !== 'like') {
// We have to do a raw sql query for this since otherwise PDO will quote the value and MySQL will
// search the value as a string which prevents being able to do number-based operations
@@ -309,8 +328,8 @@ class SearchService
public function indexEntity(Entity $entity)
{
$this->deleteEntityTerms($entity);
$nameTerms = $this->generateTermArrayFromText($entity->name, 5);
$bodyTerms = $this->generateTermArrayFromText($entity->getText(), 1);
$nameTerms = $this->generateTermArrayFromText($entity->name, 5 * $entity->searchFactor);
$bodyTerms = $this->generateTermArrayFromText($entity->getText(), 1 * $entity->searchFactor);
$terms = array_merge($nameTerms, $bodyTerms);
foreach ($terms as $index => $term) {
$terms[$index]['entity_type'] = $entity->getMorphClass();
@@ -323,11 +342,12 @@ class SearchService
* Index multiple Entities at once
* @param Entity[] $entities
*/
protected function indexEntities($entities) {
protected function indexEntities($entities)
{
$terms = [];
foreach ($entities as $entity) {
$nameTerms = $this->generateTermArrayFromText($entity->name, 5);
$bodyTerms = $this->generateTermArrayFromText($entity->getText(), 1);
$nameTerms = $this->generateTermArrayFromText($entity->name, 5 * $entity->searchFactor);
$bodyTerms = $this->generateTermArrayFromText($entity->getText(), 1 * $entity->searchFactor);
foreach (array_merge($nameTerms, $bodyTerms) as $term) {
$term['entity_id'] = $entity->id;
$term['entity_type'] = $entity->getMorphClass();
@@ -386,7 +406,9 @@ class SearchService
$token = strtok($text, $splitChars);
while ($token !== false) {
if (!isset($tokenMap[$token])) $tokenMap[$token] = 0;
if (!isset($tokenMap[$token])) {
$tokenMap[$token] = 0;
}
$tokenMap[$token]++;
$token = strtok($splitChars);
}
@@ -410,43 +432,63 @@ class SearchService
protected function filterUpdatedAfter(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
{
try { $date = date_create($input);
} catch (\Exception $e) {return;}
try {
$date = date_create($input);
} catch (\Exception $e) {
return;
}
$query->where('updated_at', '>=', $date);
}
protected function filterUpdatedBefore(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
{
try { $date = date_create($input);
} catch (\Exception $e) {return;}
try {
$date = date_create($input);
} catch (\Exception $e) {
return;
}
$query->where('updated_at', '<', $date);
}
protected function filterCreatedAfter(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
{
try { $date = date_create($input);
} catch (\Exception $e) {return;}
try {
$date = date_create($input);
} catch (\Exception $e) {
return;
}
$query->where('created_at', '>=', $date);
}
protected function filterCreatedBefore(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
{
try { $date = date_create($input);
} catch (\Exception $e) {return;}
try {
$date = date_create($input);
} catch (\Exception $e) {
return;
}
$query->where('created_at', '<', $date);
}
protected function filterCreatedBy(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
{
if (!is_numeric($input) && $input !== 'me') return;
if ($input === 'me') $input = user()->id;
if (!is_numeric($input) && $input !== 'me') {
return;
}
if ($input === 'me') {
$input = user()->id;
}
$query->where('created_by', '=', $input);
}
protected function filterUpdatedBy(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
{
if (!is_numeric($input) && $input !== 'me') return;
if ($input === 'me') $input = user()->id;
if (!is_numeric($input) && $input !== 'me') {
return;
}
if ($input === 'me') {
$input = user()->id;
}
$query->where('updated_by', '=', $input);
}
@@ -455,7 +497,10 @@ class SearchService
$query->where('name', 'like', '%' .$input. '%');
}
protected function filterInTitle(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input) {$this->filterInName($query, $model, $input);}
protected function filterInTitle(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
{
$this->filterInName($query, $model, $input);
}
protected function filterInBody(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
{
@@ -469,14 +514,14 @@ class SearchService
protected function filterViewedByMe(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
{
$query->whereHas('views', function($query) {
$query->whereHas('views', function ($query) {
$query->where('user_id', '=', user()->id);
});
}
protected function filterNotViewedByMe(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
{
$query->whereDoesntHave('views', function($query) {
$query->whereDoesntHave('views', function ($query) {
$query->where('user_id', '=', user()->id);
});
}
@@ -484,7 +529,9 @@ class SearchService
protected function filterSortBy(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
{
$functionName = camel_case('sort_by_' . $input);
if (method_exists($this, $functionName)) $this->$functionName($query, $model);
if (method_exists($this, $functionName)) {
$this->$functionName($query, $model);
}
}
@@ -500,4 +547,4 @@ class SearchService
$query->join($commentQuery, $model->getTable() . '.id', '=', 'comments.entity_id')->orderBy('last_commented', 'desc');
}
}
}

View File

@@ -40,8 +40,12 @@ class SettingService
*/
public function get($key, $default = false)
{
if ($default === false) $default = config('setting-defaults.' . $key, false);
if (isset($this->localCache[$key])) return $this->localCache[$key];
if ($default === false) {
$default = config('setting-defaults.' . $key, false);
}
if (isset($this->localCache[$key])) {
return $this->localCache[$key];
}
$value = $this->getValueFromStore($key, $default);
$formatted = $this->formatValue($value, $default);
@@ -72,12 +76,16 @@ class SettingService
{
// Check for an overriding value
$overrideValue = $this->getOverrideValue($key);
if ($overrideValue !== null) return $overrideValue;
if ($overrideValue !== null) {
return $overrideValue;
}
// Check the cache
$cacheKey = $this->cachePrefix . $key;
$cacheVal = $this->cache->get($cacheKey, null);
if ($cacheVal !== null) return $cacheVal;
if ($cacheVal !== null) {
return $cacheVal;
}
// Check the database
$settingObject = $this->getSettingObjectByKey($key);
@@ -98,6 +106,9 @@ class SettingService
{
$cacheKey = $this->cachePrefix . $key;
$this->cache->forget($cacheKey);
if (isset($this->localCache[$key])) {
unset($this->localCache[$key]);
}
}
/**
@@ -109,11 +120,17 @@ class SettingService
protected function formatValue($value, $default)
{
// Change string booleans to actual booleans
if ($value === 'true') $value = true;
if ($value === 'false') $value = false;
if ($value === 'true') {
$value = true;
}
if ($value === 'false') {
$value = false;
}
// Set to default if empty
if ($value === '') $value = $default;
if ($value === '') {
$value = $default;
}
return $value;
}
@@ -222,8 +239,9 @@ class SettingService
*/
protected function getOverrideValue($key)
{
if ($key === 'registration-enabled' && config('auth.method') === 'ldap') return false;
if ($key === 'registration-enabled' && config('auth.method') === 'ldap') {
return false;
}
return null;
}
}
}

View File

@@ -16,7 +16,7 @@ class SocialAuthService
protected $socialite;
protected $socialAccount;
protected $validSocialDrivers = ['google', 'github', 'facebook', 'slack', 'twitter', 'azure', 'okta'];
protected $validSocialDrivers = ['google', 'github', 'facebook', 'slack', 'twitter', 'azure', 'okta', 'gitlab', 'twitch'];
/**
* SocialAuthService constructor.
@@ -150,8 +150,12 @@ class SocialAuthService
{
$driver = trim(strtolower($socialDriver));
if (!in_array($driver, $this->validSocialDrivers)) abort(404, trans('errors.social_driver_not_found'));
if (!$this->checkDriverConfigured($driver)) throw new SocialDriverNotConfigured(trans('errors.social_driver_not_configured', ['socialAccount' => title_case($socialDriver)]));
if (!in_array($driver, $this->validSocialDrivers)) {
abort(404, trans('errors.social_driver_not_found'));
}
if (!$this->checkDriverConfigured($driver)) {
throw new SocialDriverNotConfigured(trans('errors.social_driver_not_configured', ['socialAccount' => title_case($socialDriver)]));
}
return $driver;
}
@@ -220,5 +224,4 @@ class SocialAuthService
session()->flash('success', trans('settings.users_social_disconnected', ['socialAccount' => title_case($socialDriver)]));
return redirect(user()->getEditUrl());
}
}
}

View File

@@ -11,11 +11,6 @@ class UploadService
*/
protected $fileSystem;
/**
* @var FileSystemInstance
*/
protected $storageInstance;
/**
* FileService constructor.
@@ -32,15 +27,10 @@ class UploadService
*/
protected function getStorage()
{
if ($this->storageInstance !== null) return $this->storageInstance;
$storageType = config('filesystems.default');
$this->storageInstance = $this->fileSystem->disk($storageType);
return $this->storageInstance;
return $this->fileSystem->disk($storageType);
}
/**
* Check whether or not a folder is empty.
* @param $path
@@ -52,13 +42,4 @@ class UploadService
$folders = $this->getStorage()->directories($path);
return (count($files) === 0 && count($folders) === 0);
}
/**
* Check if using a local filesystem.
* @return bool
*/
protected function isLocal()
{
return strtolower(config('filesystems.default')) === 'local';
}
}
}

View File

@@ -27,7 +27,9 @@ class ViewService
public function add(Entity $entity)
{
$user = user();
if ($user === null || $user->isDefault()) return 0;
if ($user === null || $user->isDefault()) {
return 0;
}
$view = $entity->views()->where('user_id', '=', $user->id)->first();
// Add view if model exists
if ($view) {
@@ -77,12 +79,16 @@ class ViewService
public function getUserRecentlyViewed($count = 10, $page = 0, $filterModel = false)
{
$user = user();
if ($user === null || $user->isDefault()) return collect();
if ($user === null || $user->isDefault()) {
return collect();
}
$query = $this->permissionService
->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type');
if ($filterModel) $query = $query->where('viewable_type', '=', get_class($filterModel));
if ($filterModel) {
$query = $query->where('viewable_type', '=', get_class($filterModel));
}
$query = $query->where('user_id', '=', $user->id);
$viewables = $query->with('viewable')->orderBy('updated_at', 'desc')
@@ -97,5 +103,4 @@ class ViewService
{
$this->view->truncate();
}
}
}

View File

@@ -1,6 +1,5 @@
<?php namespace BookStack;
class SocialAccount extends Model
{

View File

@@ -16,4 +16,4 @@ class Tag extends Model
{
return $this->morphTo('entity');
}
}
}

View File

@@ -60,7 +60,9 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
*/
public function roles()
{
if ($this->id === 0) return ;
if ($this->id === 0) {
return ;
}
return $this->belongsToMany(Role::class);
}
@@ -81,7 +83,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
*/
public function hasSystemRole($role)
{
return $this->roles->pluck('system_name')->contains('admin');
return $this->roles->pluck('system_name')->contains($role);
}
/**
@@ -91,9 +93,11 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
*/
public function permissions($cache = true)
{
if(isset($this->permissions) && $cache) return $this->permissions;
if (isset($this->permissions) && $cache) {
return $this->permissions;
}
$this->load('roles.permissions');
$permissions = $this->roles->map(function($role) {
$permissions = $this->roles->map(function ($role) {
return $role->permissions;
})->flatten()->unique();
$this->permissions = $permissions;
@@ -107,7 +111,9 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
*/
public function can($permissionName)
{
if ($this->email === 'guest') return false;
if ($this->email === 'guest') {
return false;
}
return $this->permissions()->pluck('name')->contains($permissionName);
}
@@ -162,7 +168,9 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
{
$default = baseUrl('/user_avatar.png');
$imageId = $this->image_id;
if ($imageId === 0 || $imageId === '0' || $imageId === null) return $default;
if ($imageId === 0 || $imageId === '0' || $imageId === null) {
return $default;
}
try {
$avatar = $this->avatar ? baseUrl($this->avatar->getThumb($size, $size, false)) : $default;
@@ -206,10 +214,14 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
*/
public function getShortName($chars = 8)
{
if (strlen($this->name) <= $chars) return $this->name;
if (strlen($this->name) <= $chars) {
return $this->name;
}
$splitName = explode(' ', $this->name);
if (strlen($splitName[0]) <= $chars) return $splitName[0];
if (strlen($splitName[0]) <= $chars) {
return $splitName[0];
}
return '';
}

View File

@@ -74,7 +74,9 @@ function userCan($permission, Ownable $ownable = null)
function setting($key = null, $default = false)
{
$settingService = resolve(\BookStack\Services\SettingService::class);
if (is_null($key)) return $settingService;
if (is_null($key)) {
return $settingService;
}
return $settingService->get($key, $default);
}
@@ -87,7 +89,9 @@ function setting($key = null, $default = false)
function baseUrl($path, $forceAppDomain = false)
{
$isFullUrl = strpos($path, 'http') === 0;
if ($isFullUrl && !$forceAppDomain) return $path;
if ($isFullUrl && !$forceAppDomain) {
return $path;
}
$path = trim($path, '/');
// Remove non-specified domain if forced and we have a domain
@@ -126,12 +130,50 @@ function redirect($to = null, $status = 302, $headers = [], $secure = null)
return app('redirect')->to($to, $status, $headers, $secure);
}
function icon($name, $attrs = []) {
$iconPath = resource_path('assets/icons/' . $name . '.svg');
/**
* Get a path to a theme resource.
* @param string $path
* @return string|boolean
*/
function theme_path($path = '')
{
$theme = config('view.theme');
if (!$theme) {
return false;
}
return base_path('themes/' . $theme .($path ? DIRECTORY_SEPARATOR.$path : $path));
}
/**
* Get fetch an SVG icon as a string.
* Checks for icons defined within a custom theme before defaulting back
* to the 'resources/assets/icons' folder.
*
* Returns an empty string if icon file not found.
* @param $name
* @param array $attrs
* @return mixed
*/
function icon($name, $attrs = [])
{
$attrs = array_merge([
'class' => 'svg-icon',
'data-icon' => $name
], $attrs);
$attrString = ' ';
foreach ($attrs as $attrName => $attr) {
$attrString .= $attrName . '="' . $attr . '" ';
}
$iconPath = resource_path('assets/icons/' . $name . '.svg');
$themeIconPath = theme_path('icons/' . $name . '.svg');
if ($themeIconPath && file_exists($themeIconPath)) {
$iconPath = $themeIconPath;
} else if (!file_exists($iconPath)) {
return '';
}
$fileContents = file_get_contents($iconPath);
return str_replace('<svg', '<svg' . $attrString, $fileContents);
}
@@ -159,11 +201,15 @@ function sortUrl($path, $data, $overrideData = [])
foreach ($queryData as $name => $value) {
$trimmedVal = trim($value);
if ($trimmedVal === '') continue;
if ($trimmedVal === '') {
continue;
}
$queryStringSections[] = urlencode($name) . '=' . urlencode($trimmedVal);
}
if (count($queryStringSections) === 0) return $path;
if (count($queryStringSections) === 0) {
return $path;
}
return baseUrl($path . '?' . implode('&', $queryStringSections));
}
}

View File

@@ -18,7 +18,9 @@
"barryvdh/laravel-snappy": "^0.4.0",
"socialiteproviders/slack": "^3.0",
"socialiteproviders/microsoft-azure": "^3.0",
"socialiteproviders/okta": "^1.0"
"socialiteproviders/okta": "^1.0",
"socialiteproviders/gitlab": "^3.0",
"socialiteproviders/twitch": "^3.0"
},
"require-dev": {
"filp/whoops": "~2.0",
@@ -29,7 +31,8 @@
"symfony/dom-crawler": "3.1.*",
"laravel/browser-kit-testing": "^2.0",
"barryvdh/laravel-ide-helper": "^2.4.1",
"barryvdh/laravel-debugbar": "^3.1.0"
"barryvdh/laravel-debugbar": "^3.1.0",
"squizlabs/php_codesniffer": "^3.2"
},
"autoload": {
"classmap": [

379
composer.lock generated
View File

@@ -4,20 +4,20 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"content-hash": "7d60f09393b99551e9ffdb6622ed7ade",
"content-hash": "ed85d10e69b1071020178cb400a80e48",
"packages": [
{
"name": "aws/aws-sdk-php",
"version": "3.45.3",
"version": "3.52.6",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
"reference": "d0abb0b1194fa64973b135191f56df991bc5787c"
"reference": "c9af7657eddc0267cc7ac4f969c10d5c18459992"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/d0abb0b1194fa64973b135191f56df991bc5787c",
"reference": "d0abb0b1194fa64973b135191f56df991bc5787c",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/c9af7657eddc0267cc7ac4f969c10d5c18459992",
"reference": "c9af7657eddc0267cc7ac4f969c10d5c18459992",
"shasum": ""
},
"require": {
@@ -84,25 +84,25 @@
"s3",
"sdk"
],
"time": "2017-12-08T21:36:50+00:00"
"time": "2018-02-09T22:53:37+00:00"
},
{
"name": "barryvdh/laravel-dompdf",
"version": "v0.8.1",
"version": "v0.8.2",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-dompdf.git",
"reference": "3b2235e589616331d68482d61b7763789a2600fe"
"reference": "7dcdecfa125c174d0abe723603633dc2756ea3af"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/3b2235e589616331d68482d61b7763789a2600fe",
"reference": "3b2235e589616331d68482d61b7763789a2600fe",
"url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/7dcdecfa125c174d0abe723603633dc2756ea3af",
"reference": "7dcdecfa125c174d0abe723603633dc2756ea3af",
"shasum": ""
},
"require": {
"dompdf/dompdf": "^0.8",
"illuminate/support": "5.1.x|5.2.x|5.3.x|5.4.x|5.5.x",
"illuminate/support": "5.1.x|5.2.x|5.3.x|5.4.x|5.5.x|5.6.x",
"php": ">=5.5.9"
},
"type": "library",
@@ -140,32 +140,32 @@
"laravel",
"pdf"
],
"time": "2017-07-29T19:01:17+00:00"
"time": "2018-02-07T17:43:25+00:00"
},
{
"name": "barryvdh/laravel-snappy",
"version": "v0.4.0",
"version": "v0.4.1",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-snappy.git",
"reference": "f08c7e5b4ddea585bfcd48ab4f40f920e58dd1cf"
"reference": "5f6e7f3ba15c867d1b8e2885d454110270616ebe"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-snappy/zipball/f08c7e5b4ddea585bfcd48ab4f40f920e58dd1cf",
"reference": "f08c7e5b4ddea585bfcd48ab4f40f920e58dd1cf",
"url": "https://api.github.com/repos/barryvdh/laravel-snappy/zipball/5f6e7f3ba15c867d1b8e2885d454110270616ebe",
"reference": "5f6e7f3ba15c867d1b8e2885d454110270616ebe",
"shasum": ""
},
"require": {
"illuminate/filesystem": "5.0.x|5.1.x|5.2.x|5.3.x|5.4.x|5.5.x",
"illuminate/support": "5.0.x|5.1.x|5.2.x|5.3.x|5.4.x|5.5.x",
"knplabs/knp-snappy": "*",
"illuminate/filesystem": "5.0.x|5.1.x|5.2.x|5.3.x|5.4.x|5.5.x|5.6.x",
"illuminate/support": "5.0.x|5.1.x|5.2.x|5.3.x|5.4.x|5.5.x|5.6.x",
"knplabs/knp-snappy": "^1",
"php": ">=5.4.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "0.3-dev"
"dev-master": "0.4-dev"
},
"laravel": {
"providers": [
@@ -201,7 +201,7 @@
"wkhtmltoimage",
"wkhtmltopdf"
],
"time": "2017-08-14T06:48:50+00:00"
"time": "2018-02-08T15:58:26+00:00"
},
{
"name": "cogpowered/finediff",
@@ -898,16 +898,16 @@
},
{
"name": "knplabs/knp-snappy",
"version": "v1.0.3",
"version": "v1.0.4",
"source": {
"type": "git",
"url": "https://github.com/KnpLabs/snappy.git",
"reference": "68590ef3aa94425b1c0019cc28ce471729f51fcb"
"reference": "144c4ecd1ccaeda936bf832b93079efc490e6850"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/KnpLabs/snappy/zipball/68590ef3aa94425b1c0019cc28ce471729f51fcb",
"reference": "68590ef3aa94425b1c0019cc28ce471729f51fcb",
"url": "https://api.github.com/repos/KnpLabs/snappy/zipball/144c4ecd1ccaeda936bf832b93079efc490e6850",
"reference": "144c4ecd1ccaeda936bf832b93079efc490e6850",
"shasum": ""
},
"require": {
@@ -960,20 +960,20 @@
"thumbnail",
"wkhtmltopdf"
],
"time": "2017-12-03T23:18:18+00:00"
"time": "2018-01-22T19:40:51+00:00"
},
{
"name": "laravel/framework",
"version": "v5.5.24",
"version": "v5.5.34",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "06135405bb1f736dac5e9529ed1541fc446c9c0f"
"reference": "1de7c0aec13eadbdddc2d1ba4019b064b2c6b966"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/06135405bb1f736dac5e9529ed1541fc446c9c0f",
"reference": "06135405bb1f736dac5e9529ed1541fc446c9c0f",
"url": "https://api.github.com/repos/laravel/framework/zipball/1de7c0aec13eadbdddc2d1ba4019b064b2c6b966",
"reference": "1de7c0aec13eadbdddc2d1ba4019b064b2c6b966",
"shasum": ""
},
"require": {
@@ -1053,6 +1053,7 @@
"guzzlehttp/guzzle": "Required to use the Mailgun and Mandrill mail drivers and the ping methods on schedules (~6.0).",
"laravel/tinker": "Required to use the tinker console command (~1.0).",
"league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (~1.0).",
"league/flysystem-cached-adapter": "Required to use Flysystem caching (~1.0).",
"league/flysystem-rackspace": "Required to use the Flysystem Rackspace driver (~1.0).",
"nexmo/client": "Required to use the Nexmo transport (~1.0).",
"pda/pheanstalk": "Required to use the beanstalk queue driver (~3.0).",
@@ -1093,7 +1094,7 @@
"framework",
"laravel"
],
"time": "2017-12-07T01:28:21+00:00"
"time": "2018-02-06T15:36:55+00:00"
},
{
"name": "laravel/socialite",
@@ -1159,16 +1160,16 @@
},
{
"name": "league/flysystem",
"version": "1.0.41",
"version": "1.0.42",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem.git",
"reference": "f400aa98912c561ba625ea4065031b7a41e5a155"
"reference": "09eabc54e199950041aef258a85847676496fe8e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/f400aa98912c561ba625ea4065031b7a41e5a155",
"reference": "f400aa98912c561ba625ea4065031b7a41e5a155",
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/09eabc54e199950041aef258a85847676496fe8e",
"reference": "09eabc54e199950041aef258a85847676496fe8e",
"shasum": ""
},
"require": {
@@ -1179,12 +1180,13 @@
},
"require-dev": {
"ext-fileinfo": "*",
"mockery/mockery": "~0.9",
"phpspec/phpspec": "^2.2",
"phpunit/phpunit": "~4.8"
"phpspec/phpspec": "^3.4",
"phpunit/phpunit": "^5.7"
},
"suggest": {
"ext-fileinfo": "Required for MimeType",
"ext-ftp": "Allows you to use FTP server storage",
"ext-openssl": "Allows you to use FTPS server storage",
"league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2",
"league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3",
"league/flysystem-azure": "Allows you to use Windows Azure Blob storage",
@@ -1238,7 +1240,7 @@
"sftp",
"storage"
],
"time": "2017-08-06T17:41:04+00:00"
"time": "2018-01-27T16:03:56+00:00"
},
{
"name": "league/flysystem-aws-s3-v3",
@@ -1951,16 +1953,16 @@
},
{
"name": "ramsey/uuid",
"version": "3.7.1",
"version": "3.7.3",
"source": {
"type": "git",
"url": "https://github.com/ramsey/uuid.git",
"reference": "45cffe822057a09e05f7bd09ec5fb88eeecd2334"
"reference": "44abcdad877d9a46685a3a4d221e3b2c4b87cb76"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ramsey/uuid/zipball/45cffe822057a09e05f7bd09ec5fb88eeecd2334",
"reference": "45cffe822057a09e05f7bd09ec5fb88eeecd2334",
"url": "https://api.github.com/repos/ramsey/uuid/zipball/44abcdad877d9a46685a3a4d221e3b2c4b87cb76",
"reference": "44abcdad877d9a46685a3a4d221e3b2c4b87cb76",
"shasum": ""
},
"require": {
@@ -1971,17 +1973,15 @@
"rhumsaa/uuid": "self.version"
},
"require-dev": {
"apigen/apigen": "^4.1",
"codeception/aspect-mock": "^1.0 | ^2.0",
"codeception/aspect-mock": "^1.0 | ~2.0.0",
"doctrine/annotations": "~1.2.0",
"goaop/framework": "1.0.0-alpha.2 | ^1.0 | ^2.1",
"ircmaxell/random-lib": "^1.1",
"jakub-onderka/php-parallel-lint": "^0.9.0",
"mockery/mockery": "^0.9.4",
"mockery/mockery": "^0.9.9",
"moontoast/math": "^1.1",
"php-mock/php-mock-phpunit": "^0.3|^1.1",
"phpunit/phpunit": "^4.7|>=5.0 <5.4",
"satooshi/php-coveralls": "^0.6.1",
"phpunit/phpunit": "^4.7|^5.0",
"squizlabs/php_codesniffer": "^2.3"
},
"suggest": {
@@ -2029,7 +2029,7 @@
"identifier",
"uuid"
],
"time": "2017-09-22T20:46:04+00:00"
"time": "2018-01-20T00:28:24+00:00"
},
{
"name": "sabberworm/php-css-parser",
@@ -2076,17 +2076,54 @@
"time": "2016-07-19T19:14:21+00:00"
},
{
"name": "socialiteproviders/manager",
"version": "v3.3.0",
"name": "socialiteproviders/gitlab",
"version": "v3.0.1",
"source": {
"type": "git",
"url": "https://github.com/SocialiteProviders/Manager.git",
"reference": "ac108bce073135a55dfebf28ceaf1459669348e8"
"url": "https://github.com/SocialiteProviders/GitLab.git",
"reference": "c96dc004563a3caf157608fe9aa9e45c79065d00"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/ac108bce073135a55dfebf28ceaf1459669348e8",
"reference": "ac108bce073135a55dfebf28ceaf1459669348e8",
"url": "https://api.github.com/repos/SocialiteProviders/GitLab/zipball/c96dc004563a3caf157608fe9aa9e45c79065d00",
"reference": "c96dc004563a3caf157608fe9aa9e45c79065d00",
"shasum": ""
},
"require": {
"php": "^5.6 || ^7.0",
"socialiteproviders/manager": "~3.0"
},
"type": "library",
"autoload": {
"psr-4": {
"SocialiteProviders\\GitLab\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Christoffer Martinsen",
"email": "christoffermartinsen@gmail.com"
}
],
"description": "GitLab OAuth2 Provider for Laravel Socialite",
"time": "2017-01-31T05:06:13+00:00"
},
{
"name": "socialiteproviders/manager",
"version": "v3.3.1",
"source": {
"type": "git",
"url": "https://github.com/SocialiteProviders/Manager.git",
"reference": "1de3f3d874392da6f1a4c0bf30d843e9cd903ea7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/1de3f3d874392da6f1a4c0bf30d843e9cd903ea7",
"reference": "1de3f3d874392da6f1a4c0bf30d843e9cd903ea7",
"shasum": ""
},
"require": {
@@ -2122,7 +2159,7 @@
}
],
"description": "Easily add new or override built-in providers in Laravel Socialite.",
"time": "2017-09-21T07:21:55+00:00"
"time": "2017-11-20T08:42:57+00:00"
},
{
"name": "socialiteproviders/microsoft-azure",
@@ -2235,6 +2272,43 @@
"description": "Slack OAuth2 Provider for Laravel Socialite",
"time": "2017-04-10T05:10:48+00:00"
},
{
"name": "socialiteproviders/twitch",
"version": "v3.0.0",
"source": {
"type": "git",
"url": "https://github.com/SocialiteProviders/Twitch.git",
"reference": "a7ad148c0b42d0c607d8a034b6e47faf5fc85e93"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/SocialiteProviders/Twitch/zipball/a7ad148c0b42d0c607d8a034b6e47faf5fc85e93",
"reference": "a7ad148c0b42d0c607d8a034b6e47faf5fc85e93",
"shasum": ""
},
"require": {
"php": "^5.6 || ^7.0",
"socialiteproviders/manager": "~3.0"
},
"type": "library",
"autoload": {
"psr-4": {
"SocialiteProviders\\Twitch\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Brian Faust",
"email": "hello@brianfaust.de"
}
],
"description": "Twitch OAuth2 Provider for Laravel Socialite",
"time": "2017-01-25T09:48:29+00:00"
},
{
"name": "swiftmailer/swiftmailer",
"version": "v6.0.2",
@@ -2721,16 +2795,16 @@
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.6.0",
"version": "v1.7.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296"
"reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296",
"reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/78be803ce01e55d3491c1397cf1c64beb9c1b63b",
"reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b",
"shasum": ""
},
"require": {
@@ -2742,7 +2816,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.6-dev"
"dev-master": "1.7-dev"
}
},
"autoload": {
@@ -2776,7 +2850,7 @@
"portable",
"shim"
],
"time": "2017-10-11T12:05:26+00:00"
"time": "2018-01-30T19:27:44+00:00"
},
{
"name": "symfony/process",
@@ -3040,29 +3114,29 @@
},
{
"name": "tijsverkoyen/css-to-inline-styles",
"version": "2.2.0",
"version": "2.2.1",
"source": {
"type": "git",
"url": "https://github.com/tijsverkoyen/CssToInlineStyles.git",
"reference": "ab03919dfd85a74ae0372f8baf9f3c7d5c03b04b"
"reference": "0ed4a2ea4e0902dac0489e6436ebcd5bbcae9757"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/ab03919dfd85a74ae0372f8baf9f3c7d5c03b04b",
"reference": "ab03919dfd85a74ae0372f8baf9f3c7d5c03b04b",
"url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/0ed4a2ea4e0902dac0489e6436ebcd5bbcae9757",
"reference": "0ed4a2ea4e0902dac0489e6436ebcd5bbcae9757",
"shasum": ""
},
"require": {
"php": "^5.5 || ^7",
"symfony/css-selector": "^2.7|~3.0"
"php": "^5.5 || ^7.0",
"symfony/css-selector": "^2.7 || ^3.0 || ^4.0"
},
"require-dev": {
"phpunit/phpunit": "~4.8|5.1.*"
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
"dev-master": "2.2.x-dev"
}
},
"autoload": {
@@ -3083,7 +3157,7 @@
],
"description": "CssToInlineStyles is a class that enables you to convert HTML-pages/files into HTML-pages/files with inline styles. This is very useful when you're sending emails.",
"homepage": "https://github.com/tijsverkoyen/CssToInlineStyles",
"time": "2016-09-20T12:50:39+00:00"
"time": "2017-11-27T11:13:29+00:00"
},
{
"name": "vlucas/phpdotenv",
@@ -3139,26 +3213,26 @@
"packages-dev": [
{
"name": "barryvdh/laravel-debugbar",
"version": "v3.1.0",
"version": "v3.1.1",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-debugbar.git",
"reference": "01a859752094e00aa8548832312366753272f8af"
"reference": "f0018d359a2ad6968ad11b283283a925e017f3c9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/01a859752094e00aa8548832312366753272f8af",
"reference": "01a859752094e00aa8548832312366753272f8af",
"url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/f0018d359a2ad6968ad11b283283a925e017f3c9",
"reference": "f0018d359a2ad6968ad11b283283a925e017f3c9",
"shasum": ""
},
"require": {
"illuminate/routing": "5.5.x",
"illuminate/session": "5.5.x",
"illuminate/support": "5.5.x",
"maximebf/debugbar": "~1.14.0",
"illuminate/routing": "5.5.x|5.6.x",
"illuminate/session": "5.5.x|5.6.x",
"illuminate/support": "5.5.x|5.6.x",
"maximebf/debugbar": "~1.15.0",
"php": ">=7.0",
"symfony/debug": "^3",
"symfony/finder": "^3"
"symfony/debug": "^3|^4",
"symfony/finder": "^3|^4"
},
"require-dev": {
"illuminate/framework": "5.5.x"
@@ -3166,7 +3240,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
"dev-master": "3.2-dev"
},
"laravel": {
"providers": [
@@ -3203,34 +3277,34 @@
"profiler",
"webprofiler"
],
"time": "2017-09-18T13:32:46+00:00"
"time": "2018-02-07T08:29:09+00:00"
},
{
"name": "barryvdh/laravel-ide-helper",
"version": "v2.4.1",
"version": "v2.4.3",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-ide-helper.git",
"reference": "2b1273c45e2f8df7a625563e2283a17c14f02ae8"
"reference": "5c304db44fba8e9c4aa0c09739e59f7be7736fdd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/2b1273c45e2f8df7a625563e2283a17c14f02ae8",
"reference": "2b1273c45e2f8df7a625563e2283a17c14f02ae8",
"url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/5c304db44fba8e9c4aa0c09739e59f7be7736fdd",
"reference": "5c304db44fba8e9c4aa0c09739e59f7be7736fdd",
"shasum": ""
},
"require": {
"barryvdh/reflection-docblock": "^2.0.4",
"illuminate/console": "^5.0,<5.6",
"illuminate/filesystem": "^5.0,<5.6",
"illuminate/support": "^5.0,<5.6",
"illuminate/console": "^5.0,<5.7",
"illuminate/filesystem": "^5.0,<5.7",
"illuminate/support": "^5.0,<5.7",
"php": ">=5.4.0",
"symfony/class-loader": "^2.3|^3.0"
},
"require-dev": {
"doctrine/dbal": "~2.3",
"illuminate/config": "^5.0,<5.6",
"illuminate/view": "^5.0,<5.6",
"illuminate/config": "^5.0,<5.7",
"illuminate/view": "^5.0,<5.7",
"phpunit/phpunit": "4.*",
"scrutinizer/ocular": "~1.1",
"squizlabs/php_codesniffer": "~2.3"
@@ -3276,7 +3350,7 @@
"phpstorm",
"sublime"
],
"time": "2017-07-16T00:24:12+00:00"
"time": "2018-02-08T07:56:07+00:00"
},
{
"name": "barryvdh/reflection-docblock",
@@ -3590,22 +3664,22 @@
},
{
"name": "maximebf/debugbar",
"version": "v1.14.1",
"version": "v1.15.0",
"source": {
"type": "git",
"url": "https://github.com/maximebf/php-debugbar.git",
"reference": "64251a392344e3d22f3d21c3b7c531ba96eb01d2"
"reference": "30e7d60937ee5f1320975ca9bc7bcdd44d500f07"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/64251a392344e3d22f3d21c3b7c531ba96eb01d2",
"reference": "64251a392344e3d22f3d21c3b7c531ba96eb01d2",
"url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/30e7d60937ee5f1320975ca9bc7bcdd44d500f07",
"reference": "30e7d60937ee5f1320975ca9bc7bcdd44d500f07",
"shasum": ""
},
"require": {
"php": ">=5.3.0",
"psr/log": "^1.0",
"symfony/var-dumper": "^2.6|^3.0"
"symfony/var-dumper": "^2.6|^3.0|^4.0"
},
"require-dev": {
"phpunit/phpunit": "^4.0|^5.0"
@@ -3647,7 +3721,7 @@
"debug",
"debugbar"
],
"time": "2017-09-13T12:19:36+00:00"
"time": "2017-12-15T11:13:46+00:00"
},
{
"name": "mockery/mockery",
@@ -3917,16 +3991,16 @@
},
{
"name": "phpdocumentor/reflection-docblock",
"version": "4.2.0",
"version": "4.3.0",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
"reference": "66465776cfc249844bde6d117abff1d22e06c2da"
"reference": "94fd0001232e47129dd3504189fa1c7225010d08"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/66465776cfc249844bde6d117abff1d22e06c2da",
"reference": "66465776cfc249844bde6d117abff1d22e06c2da",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08",
"reference": "94fd0001232e47129dd3504189fa1c7225010d08",
"shasum": ""
},
"require": {
@@ -3964,7 +4038,7 @@
}
],
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
"time": "2017-11-27T17:38:31+00:00"
"time": "2017-11-30T07:14:17+00:00"
},
{
"name": "phpdocumentor/type-resolver",
@@ -4327,16 +4401,16 @@
},
{
"name": "phpunit/phpunit",
"version": "6.5.3",
"version": "6.5.6",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "882e886cc928a0abd3c61282b2a64026237d14a4"
"reference": "3330ef26ade05359d006041316ed0fa9e8e3cefe"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/882e886cc928a0abd3c61282b2a64026237d14a4",
"reference": "882e886cc928a0abd3c61282b2a64026237d14a4",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3330ef26ade05359d006041316ed0fa9e8e3cefe",
"reference": "3330ef26ade05359d006041316ed0fa9e8e3cefe",
"shasum": ""
},
"require": {
@@ -4354,7 +4428,7 @@
"phpunit/php-file-iterator": "^1.4.3",
"phpunit/php-text-template": "^1.2.1",
"phpunit/php-timer": "^1.0.9",
"phpunit/phpunit-mock-objects": "^5.0.4",
"phpunit/phpunit-mock-objects": "^5.0.5",
"sebastian/comparator": "^2.1",
"sebastian/diff": "^2.0",
"sebastian/environment": "^3.1",
@@ -4407,27 +4481,27 @@
"testing",
"xunit"
],
"time": "2017-12-06T09:42:03+00:00"
"time": "2018-02-01T05:57:37+00:00"
},
{
"name": "phpunit/phpunit-mock-objects",
"version": "5.0.4",
"version": "5.0.6",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
"reference": "16b50f4167e5e85e81ca8a3dd105d0a5fd32009a"
"reference": "33fd41a76e746b8fa96d00b49a23dadfa8334cdf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/16b50f4167e5e85e81ca8a3dd105d0a5fd32009a",
"reference": "16b50f4167e5e85e81ca8a3dd105d0a5fd32009a",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/33fd41a76e746b8fa96d00b49a23dadfa8334cdf",
"reference": "33fd41a76e746b8fa96d00b49a23dadfa8334cdf",
"shasum": ""
},
"require": {
"doctrine/instantiator": "^1.0.5",
"php": "^7.0",
"phpunit/php-text-template": "^1.2.1",
"sebastian/exporter": "^3.0"
"sebastian/exporter": "^3.1"
},
"conflict": {
"phpunit/phpunit": "<6.0"
@@ -4466,7 +4540,7 @@
"mock",
"xunit"
],
"time": "2017-12-02T05:31:19+00:00"
"time": "2018-01-06T05:45:45+00:00"
},
{
"name": "sebastian/code-unit-reverse-lookup",
@@ -4515,21 +4589,21 @@
},
{
"name": "sebastian/comparator",
"version": "2.1.0",
"version": "2.1.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
"reference": "1174d9018191e93cb9d719edec01257fc05f8158"
"reference": "34369daee48eafb2651bea869b4b15d75ccc35f9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1174d9018191e93cb9d719edec01257fc05f8158",
"reference": "1174d9018191e93cb9d719edec01257fc05f8158",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9",
"reference": "34369daee48eafb2651bea869b4b15d75ccc35f9",
"shasum": ""
},
"require": {
"php": "^7.0",
"sebastian/diff": "^2.0",
"sebastian/diff": "^2.0 || ^3.0",
"sebastian/exporter": "^3.1"
},
"require-dev": {
@@ -4575,7 +4649,7 @@
"compare",
"equality"
],
"time": "2017-11-03T07:16:52+00:00"
"time": "2018-02-01T13:46:46+00:00"
},
{
"name": "sebastian/diff",
@@ -5027,6 +5101,57 @@
"homepage": "https://github.com/sebastianbergmann/version",
"time": "2016-10-03T07:35:21+00:00"
},
{
"name": "squizlabs/php_codesniffer",
"version": "3.2.2",
"source": {
"type": "git",
"url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
"reference": "d7c00c3000ac0ce79c96fcbfef86b49a71158cd1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/d7c00c3000ac0ce79c96fcbfef86b49a71158cd1",
"reference": "d7c00c3000ac0ce79c96fcbfef86b49a71158cd1",
"shasum": ""
},
"require": {
"ext-simplexml": "*",
"ext-tokenizer": "*",
"ext-xmlwriter": "*",
"php": ">=5.4.0"
},
"require-dev": {
"phpunit/phpunit": "^4.0 || ^5.0 || ^6.0"
},
"bin": [
"bin/phpcs",
"bin/phpcbf"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.x-dev"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Greg Sherwood",
"role": "lead"
}
],
"description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
"homepage": "http://www.squizlabs.com/php-codesniffer",
"keywords": [
"phpcs",
"standards"
],
"time": "2017-12-19T21:44:46+00:00"
},
{
"name": "symfony/class-loader",
"version": "v3.3.6",
@@ -5181,16 +5306,16 @@
},
{
"name": "webmozart/assert",
"version": "1.2.0",
"version": "1.3.0",
"source": {
"type": "git",
"url": "https://github.com/webmozart/assert.git",
"reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f"
"reference": "0df1908962e7a3071564e857d86874dad1ef204a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/webmozart/assert/zipball/2db61e59ff05fe5126d152bd0655c9ea113e550f",
"reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f",
"url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a",
"reference": "0df1908962e7a3071564e857d86874dad1ef204a",
"shasum": ""
},
"require": {
@@ -5227,7 +5352,7 @@
"check",
"validate"
],
"time": "2016-11-23T20:04:58+00:00"
"time": "2018-01-29T19:49:41+00:00"
}
],
"aliases": [],

View File

@@ -2,10 +2,13 @@
return [
'env' => env('APP_ENV', 'production'),
'editor' => env('APP_EDITOR', 'html'),
'views' => [
'books' => env('APP_VIEWS_BOOKS', 'list')
],
'allow_content_scripts' => env('ALLOW_CONTENT_SCRIPTS', false),
/*
|--------------------------------------------------------------------------
@@ -58,7 +61,7 @@ return [
*/
'locale' => env('APP_LANG', 'en'),
'locales' => ['en', 'de', 'es', 'es_AR', 'fr', 'nl', 'pt_BR', 'sk', 'ja', 'pl', 'it', 'ru'],
'locales' => ['en', 'de', 'es', 'es_AR', 'fr', 'nl', 'pt_BR', 'sk', 'sv', 'ja', 'pl', 'it', 'ru', 'zh_CN'],
/*
|--------------------------------------------------------------------------
@@ -147,8 +150,6 @@ return [
*/
Intervention\Image\ImageServiceProvider::class,
Barryvdh\DomPDF\ServiceProvider::class,
Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class,
Barryvdh\Debugbar\ServiceProvider::class,
Barryvdh\Snappy\ServiceProvider::class,
@@ -163,7 +164,6 @@ return [
BookStack\Providers\EventServiceProvider::class,
BookStack\Providers\RouteServiceProvider::class,
BookStack\Providers\CustomFacadeProvider::class,
],
/*
@@ -221,7 +221,6 @@ return [
'ImageTool' => Intervention\Image\Facades\Image::class,
'DomPDF' => Barryvdh\DomPDF\Facade::class,
'SnappyPDF' => Barryvdh\Snappy\Facades\SnappyPdf::class,
'Debugbar' => Barryvdh\Debugbar\Facade::class,
/**
* Custom

View File

@@ -56,7 +56,12 @@ return [
'local' => [
'driver' => 'local',
'root' => base_path(),
'root' => public_path(),
],
'local_secure' => [
'driver' => 'local',
'root' => storage_path(),
],
'ftp' => [

View File

@@ -54,7 +54,7 @@ return [
|
*/
'from' => ['address' => env('MAIL_FROM', 'mail@bookstackapp.com'), 'name' => 'BookStack'],
'from' => ['address' => env('MAIL_FROM', 'mail@bookstackapp.com'), 'name' => env('MAIL_FROM_NAME','BookStack')],
/*
|--------------------------------------------------------------------------

View File

@@ -13,7 +13,13 @@ return [
| to have a conventional place to find your various credentials.
|
*/
// Single option to disable non-auth external services such as Gravatar and Draw.io
'disable_services' => env('DISABLE_EXTERNAL_SERVICES', false),
'gravatar' => env('GRAVATAR', !env('DISABLE_EXTERNAL_SERVICES', false)),
'drawio' => env('DRAWIO', !env('DISABLE_EXTERNAL_SERVICES', false)),
'callback_url' => env('APP_URL', false),
'mailgun' => [
@@ -86,7 +92,22 @@ return [
'redirect' => env('APP_URL') . '/login/service/okta/callback',
'base_url' => env('OKTA_BASE_URL'),
'name' => 'Okta',
],
],
'gitlab' => [
'client_id' => env('GITLAB_APP_ID'),
'client_secret' => env('GITLAB_APP_SECRET'),
'redirect' => env('APP_URL') . '/login/service/gitlab/callback',
'instance_uri' => env('GITLAB_BASE_URI'), // Needed only for self hosted instances
'name' => 'GitLab',
],
'twitch' => [
'client_id' => env('TWITCH_APP_ID'),
'client_secret' => env('TWITCH_APP_SECRET'),
'redirect' => env('APP_URL') . '/login/service/twitch/callback',
'name' => 'Twitch',
],
'ldap' => [
'server' => env('LDAP_SERVER', false),

View File

@@ -1,7 +1,25 @@
<?php
$viewPaths = [realpath(base_path('resources/views'))];
if ($theme = env('APP_THEME', false)) {
array_unshift($viewPaths, base_path('themes/' . $theme));
}
return [
/*
|--------------------------------------------------------------------------
| App theme
|--------------------------------------------------------------------------
|
| This option defines the theme to use for the application. When a theme
| is set there mush be a `themes/<theme_name>` folder to hold the
| custom theme overrides.
|
*/
'theme' => env('APP_THEME', false),
/*
|--------------------------------------------------------------------------
| View Storage Paths
@@ -13,9 +31,7 @@ return [
|
*/
'paths' => [
realpath(base_path('resources/views')),
],
'paths' => $viewPaths,
/*
|--------------------------------------------------------------------------

View File

@@ -1,83 +0,0 @@
'use strict';
const argv = require('yargs').argv;
const gulp = require('gulp'),
plumber = require('gulp-plumber');
const autoprefixer = require('gulp-autoprefixer');
const minifycss = require('gulp-clean-css');
const sass = require('gulp-sass');
const sourcemaps = require('gulp-sourcemaps');
const browserify = require("browserify");
const source = require('vinyl-source-stream');
const buffer = require('vinyl-buffer');
const babelify = require("babelify");
const watchify = require("watchify");
const envify = require("envify");
const uglify = require('gulp-uglify');
const gutil = require("gulp-util");
const liveReload = require('gulp-livereload');
if (argv.production) process.env.NODE_ENV = 'production';
let isProduction = argv.production || process.env.NODE_ENV === 'production';
gulp.task('styles', () => {
let chain = gulp.src(['resources/assets/sass/**/*.scss'])
.pipe(sourcemaps.init())
.pipe(plumber({
errorHandler: function (error) {
console.log(error.message);
this.emit('end');
}}))
.pipe(sass())
.pipe(autoprefixer('last 2 versions'));
if (isProduction) chain = chain.pipe(minifycss());
chain = chain.pipe(sourcemaps.write());
return chain.pipe(gulp.dest('public/css/')).pipe(liveReload());
});
function scriptTask(watch = false) {
let props = {
basedir: 'resources/assets/js',
debug: true,
entries: ['global.js'],
fast: !isProduction,
cache: {},
packageCache: {},
};
let bundler = watch ? watchify(browserify(props), { poll: true }) : browserify(props);
if (isProduction) {
bundler.transform(envify, {global: true}).transform(babelify, {presets: ['es2015']});
}
function rebundle() {
let stream = bundler.bundle();
stream = stream.pipe(source('common.js'));
if (isProduction) stream = stream.pipe(buffer()).pipe(uglify());
return stream.pipe(gulp.dest('public/js/')).pipe(liveReload());
}
bundler.on('update', function() {
rebundle();
gutil.log('Rebundling assets...');
});
bundler.on('log', gutil.log);
return rebundle();
}
gulp.task('scripts', () => {scriptTask(false)});
gulp.task('scripts-watch', () => {scriptTask(true)});
gulp.task('default', ['styles', 'scripts-watch'], () => {
liveReload.listen();
gulp.watch("resources/assets/sass/**/*.scss", ['styles']);
});
gulp.task('build', ['styles', 'scripts']);

10556
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,41 +1,40 @@
{
"private": true,
"scripts": {
"build": "gulp build",
"production": "gulp build --production",
"dev": "gulp",
"watch": "gulp",
"build": "webpack",
"production": "NODE_ENV=production webpack && rm -f ./public/dist/*styles.js",
"dev": "npm-run-all --parallel watch livereload",
"watch": "webpack --watch",
"livereload": "livereload ./public/dist/",
"permissions": "chown -R $USER:$USER bootstrap/cache storage public/uploads"
},
"devDependencies": {
"babelify": "^7.3.0",
"browserify": "^14.3.0",
"envify": "^4.0.0",
"gulp": "3.9.1",
"gulp-autoprefixer": "3.1.1",
"gulp-clean-css": "^3.0.4",
"gulp-livereload": "^3.8.1",
"gulp-minify-css": "1.2.4",
"gulp-plumber": "1.1.0",
"gulp-sass": "3.1.0",
"gulp-uglify": "2.1.2",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0",
"watchify": "^3.9.0",
"yargs": "^7.1.0"
"@babel/core": "^7.0.0-beta.40",
"@babel/preset-env": "^7.0.0-beta.40",
"autoprefixer": "^8.1.0",
"babel-loader": "^8.0.0-beta.0",
"babel-polyfill": "^6.26.0",
"css-loader": "^0.28.10",
"extract-text-webpack-plugin": "^4.0.0-beta.0",
"livereload": "^0.7.0",
"node-sass": "^4.7.2",
"npm-run-all": "^4.1.2",
"postcss-loader": "^2.1.1",
"sass-loader": "^6.0.7",
"style-loader": "^0.20.3",
"uglifyjs-webpack-plugin": "^1.2.3",
"webpack": "^4.1.1",
"webpack-cli": "^2.0.11"
},
"dependencies": {
"axios": "^0.16.1",
"babel-polyfill": "^6.23.0",
"babel-preset-es2015": "^6.24.1",
"clipboard": "^1.7.1",
"axios": "^0.18.0",
"clipboard": "^2.0.0",
"codemirror": "^5.26.0",
"dropzone": "^4.0.1",
"gulp-sourcemaps": "^2.6.1",
"gulp-util": "^3.0.8",
"dropzone": "^5.4.0",
"jquery": "^3.3.1",
"markdown-it": "^8.3.1",
"markdown-it-task-lists": "^2.0.0",
"moment": "^2.12.0",
"moment": "^2.21.0",
"vue": "^2.2.6",
"vuedraggable": "^2.14.1"
},

8
phpcs.xml Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<ruleset name="PHP_CodeSniffer">
<description>The coding standard for BookStack.</description>
<file>app</file>
<exclude-pattern>*/migrations/*</exclude-pattern>
<arg value="np"/>
<rule ref="PSR2"/>
</ruleset>

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +0,0 @@
header{display:none}body{font-size:12px}.faded-small{display:none}.page-content{margin:0 auto}.print-hidden{display:none}.print-full-width{width:100%;float:none;display:block}h2{font-size:2em;line-height:1;margin-top:.6em;margin-bottom:.3em}
/*# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInByaW50LXN0eWxlcy5zY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUVBLE9BQ0UsUUFBQSxLQUdGLEtBQ0UsVUFBQSxLQUdGLGFBQ0UsUUFBQSxLQUdGLGNBQ0UsT0FBQSxFQUFBLEtBR0YsY0FDRSxRQUFBLEtBR0Ysa0JBQ0UsTUFBQSxLQUNBLE1BQUEsS0FDQSxRQUFBLE1BR0YsR0FDRSxVQUFBLElBQ0EsWUFBQSxFQUNBLFdBQUEsS0FDQSxjQUFBIiwiZmlsZSI6InByaW50LXN0eWxlcy5jc3MiLCJzb3VyY2VzQ29udGVudCI6WyJAaW1wb3J0IFwidmFyaWFibGVzXCI7XG5cbmhlYWRlciB7XG4gIGRpc3BsYXk6IG5vbmU7XG59XG5cbmJvZHkge1xuICBmb250LXNpemU6IDEycHg7XG59XG5cbi5mYWRlZC1zbWFsbCB7XG4gIGRpc3BsYXk6IG5vbmU7XG59XG5cbi5wYWdlLWNvbnRlbnQge1xuICBtYXJnaW46IDAgYXV0bztcbn1cblxuLnByaW50LWhpZGRlbiB7XG4gIGRpc3BsYXk6IG5vbmU7XG59XG5cbi5wcmludC1mdWxsLXdpZHRoIHtcbiAgd2lkdGg6IDEwMCU7XG4gIGZsb2F0OiBub25lO1xuICBkaXNwbGF5OiBibG9jaztcbn1cblxuaDIge1xuICBmb250LXNpemU6IDJlbTtcbiAgbGluZS1oZWlnaHQ6IDE7XG4gIG1hcmdpbi10b3A6IDAuNmVtO1xuICBtYXJnaW4tYm90dG9tOiAwLjNlbTtcbn0iXX0= */

File diff suppressed because one or more lines are too long

1
public/dist/app.js vendored Normal file

File diff suppressed because one or more lines are too long

2429
public/dist/export-styles.css vendored Normal file

File diff suppressed because it is too large Load Diff

25
public/dist/print-styles.css vendored Normal file
View File

@@ -0,0 +1,25 @@
header {
display: none; }
body {
font-size: 12px; }
.faded-small {
display: none; }
.page-content {
margin: 0 auto; }
.print-hidden {
display: none; }
.print-full-width {
width: 100%;
float: none;
display: block; }
h2 {
font-size: 2em;
line-height: 1;
margin-top: 0.6em;
margin-bottom: 0.3em; }

4128
public/dist/styles.css vendored Normal file

File diff suppressed because it is too large Load Diff

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