Compare commits

...

1116 Commits

Author SHA1 Message Date
Dan Brown
02dfe11ce6 Increment version for release v0.23.2 2018-08-19 15:33:23 +01:00
Dan Brown
83d06beb70 Merge branch 'master' into release 2018-08-19 15:33:10 +01:00
Dan Brown
d2a9b312e9 Fixed LDAP group sync using wrong user filter
LDAP group sync was trying to find users based on the external_auth_id
which is not garunteed to match the username entered so somtimes
the search for a user would fail.

This passes the username to the group sync.
Picked up by @yoyokko in #959.
2018-08-19 15:24:42 +01:00
Dan Brown
a8cfc059c8 Updated version for release v0.23.1 2018-08-12 14:22:53 +01:00
Dan Brown
1614b2bab0 Merge branch 'master' into release 2018-08-12 14:22:17 +01:00
Dan Brown
01260d95f3 Merge pull request #957 from moucho/master
Updated Spanish translation
2018-08-12 14:20:53 +01:00
Dan Brown
d69ba6b47a Updated composer dependancies 2018-08-12 13:42:17 +01:00
Dan Brown
098128aafb Added test to cover new language autodetect config option 2018-08-12 13:34:14 +01:00
Dan Brown
92c9837157 Fixed incorrect type error in LDAP group sync
Should fix #951
2018-08-12 13:28:40 +01:00
Marcos
18e5f86ffa Updated Spanish translation 2018-08-12 14:14:56 +02:00
Dan Brown
c860645a5a Tweaked bug report template to request hosting method 2018-08-12 13:12:47 +01:00
Dan Brown
fcb93dc7c8 Added option to disable public lang autodetect
Also cleaned up localization middleware a little.
Closes #944
2018-08-12 13:10:55 +01:00
Dan Brown
fcdb39e428 Merge pull request #942 from marcusforsberg/master
Updated Swedish translation
2018-08-12 12:51:00 +01:00
Dan Brown
1b3e1863f4 Merge pull request #948 from houbaron/fix/Chinese_translation
Fix/Chinese translation
2018-08-12 12:37:35 +01:00
Dan Brown
fbc2175789 Merge pull request #952 from leomartinez/master
Updated 'Spanish Argentina' translation.
2018-08-12 12:36:28 +01:00
Leonardo Martinez
8099c431bb Updated 'Spanish Argentina' translation. 2018-08-06 10:46:53 -03:00
Baron Hou
efbfe0f7af Update Traditional Chinese 2018-08-05 17:07:13 +08:00
Baron Hou
66402b474c Update Simplified Chinese 2018-08-05 17:05:41 +08:00
marcusforsberg
f47f0e05d6 Updated Swedish translation 2018-07-30 09:35:34 +03:00
Dan Brown
4bdec0d214 Updated version and assets for release v0.23 2018-07-29 20:28:49 +01:00
Dan Brown
6a7d7e7c2b Merge branch 'master' into release 2018-07-29 20:26:00 +01:00
Dan Brown
c83a51f7e2 Merge pull request #904 from lommes/903-socialite-discord
add everything needed to use discord as social login provider
2018-07-29 16:18:10 +01:00
Dan Brown
b922c8029e Merge pull request #933 from nicobubulle/master
French translation update
2018-07-29 16:04:39 +01:00
Dan Brown
653761e67d Merge pull request #925 from alex2702/fix/835
Fixed German translations for notifications
2018-07-29 16:03:29 +01:00
Dan Brown
d59ff132ab Delete ISSUE_TEMPLATE.md 2018-07-29 15:55:13 +01:00
Dan Brown
e6e740b2a1 Update issue templates 2018-07-29 15:54:53 +01:00
Dan Brown
af6f4e6c8c Updated pagination to use theme colour 2018-07-29 15:44:10 +01:00
Dan Brown
69a0f8d502 Prevented error notification being visible on load
Fixes #897

Also made design a little more compact
2018-07-29 15:34:54 +01:00
Dan Brown
6d35fb5237 Updated packages via npm audit 2018-07-28 15:03:29 +01:00
nicobubulle
79d0f707e6 French translation update 2018-07-22 18:20:09 +02:00
alex2702
369dc02e78 Fixed German translations for notifications 2018-07-15 21:26:55 +02:00
Dan Brown
9d2e65b73d Merge branch 'brennanmurphy-master' 2018-07-15 19:36:28 +01:00
Dan Brown
f421d83627 Added ability to set custom ldap group -> role mapping
Added input in role form to allow matching against custom names.
Changed default mapping to use role display name instead of the hidden
DB name.
2018-07-15 19:34:42 +01:00
Dan Brown
be2ca9d4bb Refactored out the LDAP repo 2018-07-15 18:21:45 +01:00
Dan Brown
17bca662a7 Added tests to cover ldap group mapping
Also updated .env.example formatting.
Updated how LdapRepo uses Ldap so can be mocked by testing.
2018-07-15 17:57:25 +01:00
Dan Brown
1776204870 Merge branch 'master' of git://github.com/brennanmurphy/BookStack into brennanmurphy-master 2018-07-14 14:17:55 +01:00
Dan Brown
985e214d94 Merge branch 'master' of github.com:BookStackApp/BookStack 2018-07-14 14:14:37 +01:00
Dan Brown
2bcc159fd6 Allowed creating pages in visible chapters in invisible books
Fixes permissions with test to cover in the event a page is created,
with permission, in a chapter but the user does not have permission to
see the parent book.

Fixes #912
2018-07-14 14:12:29 +01:00
Dan Brown
fb7c12438d Merge pull request #918 from DeehSlash/fix/pt_br_locale
Adds missing pt_BR strings
2018-07-14 10:31:18 +01:00
Dan Brown
b2cd363539 Added browserlist, Tweaked md scrollToText ot use ES6 2018-07-14 10:20:49 +01:00
Dan Brown
f668bee88b Merge branch 'master' into feature/edit-link-headers 2018-07-14 09:36:14 +01:00
André Luiz da Silva
642f2760cc Improves and adds missing pt_BR strings 2018-07-10 15:10:21 -03:00
Brennan Murphy
37aa8b05f8 Update files to PSR-2 standards 2018-07-02 17:27:43 +00:00
Brennan Murphy
d640cc1eee LDAP groups sync to Bookstack roles.
Closes #75
2018-07-02 17:09:39 +00:00
Abijeet Patro
c2d6e98985 Merge pull request #907 from BookStackApp/fix/date-image-manager
Changes the way the date is displayed in image-manager.
2018-07-02 00:34:30 +05:30
Dan Brown
84b4fe6176 Merge pull request #886 from leomartinez/master
Updated 'Spanish Argentina' translation.
2018-07-01 16:21:38 +01:00
Dan Brown
decdf5714b Merge pull request #865 from moucho/master
New strings from 0.22 release for Spanish translation
2018-07-01 16:20:47 +01:00
Dan Brown
9da600caf9 Merge pull request #906 from BookStackApp/bug/revision-wrap
Fixes issue with code not wrapping on revision page.
2018-07-01 16:18:49 +01:00
Dan Brown
45aee2a1c1 Merge pull request #874 from BookStackApp/fix/gototext
Fixes undefined error when clicking on link under page navigation.
2018-07-01 16:13:10 +01:00
Abijeet
f5df5ac7d5 Changes the way the date is displayed in image-manager.
Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-06-30 11:04:12 +05:30
Abijeet
fb29f4119d Fixes issue with code not wrapping on revision page.
Closes #888

Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-06-30 09:49:55 +05:30
Timo B
93795b6eda add everything needed to use discord as social login provider 2018-06-28 09:01:36 +02:00
Leonardo Martinez
f7b808a9e6 Merge remote-tracking branch 'upstream/master' 2018-06-26 09:37:20 -03:00
Abijeet Patro
448068e318 Merge pull request #892 from BookStackApp/fix/884
Fixes issue with having to click the delete icon for attachment twice.
2018-06-17 18:29:32 +05:30
Abijeet
7d81a95156 Fixes issue with having to click the delete icon for attachment twice.
Fixes #884

This is happening because -

Due to the limitations of modern JavaScript (and the abandonment of Object.observe), Vue cannot detect property addition or deletion. Since Vue performs the getter/setter conversion process during instance initialization, a property must be present in the data object in order for Vue to convert it and make it reactive.

Source: https://vuejs.org/v2/guide/reactivity.html

Also added padding to the icons in the attachment section.

Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-06-17 14:21:31 +05:30
Leonardo Martinez
a9bf2ed398 Updated 'Spanish Argentina' translation. 2018-06-13 10:12:36 -03:00
Abijeet
771f781e7f Fixes a corner case with exclamation in the ID.
Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-06-10 17:29:30 +05:30
Abijeet
78be8535f7 Removed previous code that is now unneeded
Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-06-10 17:19:03 +05:30
Abijeet
6c4c1ccb58 Changed the way we were displaying the edit icon.
Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-06-10 17:06:23 +05:30
Abijeet
562225a77b Added code to set the cursor at end of line while scrolling.
Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-06-10 17:04:54 +05:30
Abijeet
b936e1f403 Added code to handle scroll for markdown.
Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-06-10 13:11:10 +05:30
Dan Brown
b3cc3130f0 Added copy button to codemirror-rendered code blocks
Closes #858
2018-06-09 10:41:01 +01:00
Abijeet
0363fc4ea1 Fixes undefined error when clicking on page navigation links.
Fixes #873

Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-06-03 14:24:55 +05:30
Abijeet
134a96fa32 Adds edit icon to each header in the page.
Towards #618

Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-06-03 13:47:07 +05:30
Marcos
56f444a8a7 New strings 2018-05-30 01:41:25 +02:00
Dan Brown
30d4674657 Updated assets for release v0.22 2018-05-28 14:19:14 +01:00
Dan Brown
9f961f95f8 Merge branch 'master' into release 2018-05-28 14:19:04 +01:00
Dan Brown
86f43c8a65 Fixed incorrect tag from removing ng tags 2018-05-28 11:06:11 +01:00
Dan Brown
d886c6a32e Removed old ng tags, Fixed header spacing
Also prevent pointer error on custom home page
2018-05-28 10:33:38 +01:00
Dan Brown
f399e60910 Made header link spacing a little more even 2018-05-27 20:32:06 +01:00
Dan Brown
173eaf1c98 Made comments section more subtle
Also removed spacing from within details above active restrictions
2018-05-27 20:20:13 +01:00
Dan Brown
64eabaf882 Fixed search icon overalapping input in header
Fixes #859
2018-05-27 19:51:32 +01:00
Dan Brown
6b84a76af1 Merge branch 'drawing_updates' 2018-05-27 19:42:25 +01:00
Dan Brown
2bd6ba9895 Added maintenance view with image-cleanup 2018-05-27 19:40:07 +01:00
Dan Brown
1df0bcaf85 Made image cleanup safer
Also fixed drawing update in markdown editor.
Added shortcut for MD editor to view drawing manager.
2018-05-27 14:33:50 +01:00
Dan Brown
c31e6a03ce Added command to clean-up old images, Unfinished 2018-05-20 18:16:01 +01:00
Dan Brown
61c9324229 Removed old image versions test 2018-05-20 17:12:44 +01:00
Dan Brown
8c4c8cd95b Updated secure-images option to not effect image name
Instead only the image path is altered.
Also fixed image manger mode not changing on button press.
2018-05-20 16:47:53 +01:00
Dan Brown
0c9c1e4c6b Reverted work on revisions
Improved linkage of drawings and image manager.
Updated image updates to create new versions.
2018-05-20 16:41:14 +01:00
Dan Brown
9ec114641c Merge pull request #846 from moucho/master
Updated Spanish translation
2018-05-20 15:20:33 +01:00
Dan Brown
295c7918a4 Merge pull request #851 from vriic/master
Update german translation
2018-05-20 12:06:44 +01:00
Dan Brown
3ac34b5849 Merge pull request #802 from marcusforsberg/master
Updated Swedish translation
2018-05-20 12:05:11 +01:00
Dan Brown
6e7adcc095 Embedded SVG icons in css/js files
Allows removal of hacky /icon endpoint solution.
Fixes PDF exports with WKHTML and allows the icon to show in HTML
exports.

Fixes #796
2018-05-20 11:55:23 +01:00
Dan Brown
a1ecdcacba Fixed attachment error handling, Allowed all link types
Related to #812
2018-05-20 11:06:10 +01:00
Dan Brown
019b8196ad Merge branch 'feature/615' 2018-05-20 10:13:34 +01:00
Dan Brown
63f96c1c6f Reorganised home and robots views
Extracted home view sidebar into own view.
Moved home and robot views into 'common' folder so that we only have
layouts in the top-level views folder.
2018-05-20 10:11:56 +01:00
Dan Brown
8df9dab80a Merge branch 'master' into feature/615 2018-05-20 09:51:45 +01:00
Dan Brown
93147f4340 Prevented back-to-top showing on flexbox-body pages
Fixes #824
2018-05-20 09:48:11 +01:00
Dan Brown
77727e7e50 Update session config to match laravel
Includes option to set secure cookies via env.
Closes #817
2018-05-20 09:38:27 +01:00
Dan Brown
9f4c64a676 Codemirror mode now correct for c-like langs
Fixes #849
2018-05-20 09:32:15 +01:00
Nikolai Nikolajevic
e0ebae19aa Update: Übersetzung 2018-05-20 03:00:55 +02:00
Dan Brown
6cdb943916 Started work on revisions in image manager 2018-05-19 18:44:40 +01:00
Dan Brown
d3d8ddbe52 Improved 404 handling and fixed editor error
404 handling now not a hack-around and uses Laravel 'fallback' routes
instead. Prevents errors with the session when you have mulitple errors
on a page where a post/put/delete is made.
2018-05-19 17:01:33 +01:00
Marcos
57c312ec3f Updated Spanish translation 2018-05-18 03:10:49 +02:00
Dan Brown
13ad0031d6 Drawings now generate revisions, not replace
Updated drawing update test to accomodate.
Image deletion system now takes revisions into account.
2018-05-13 17:41:35 +01:00
Dan Brown
d5b922aa50 Started work on drawing revisions
Improved sidebar and selection styling of image manager.
Allowed image manager imageType to be changed on open.
Created models for image revisions.
2018-05-13 12:07:38 +01:00
Abijeet
28823c4fae Changed the location of the "view-toggle" to be under the books views.
Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-05-12 18:26:35 +05:30
Abijeet
b6bb078e0a removed some added CSS as it was causing unintended sideffects.
Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-05-12 17:28:10 +05:30
Abijeet
8254c3be8d Added the book view toggle option on the homepage.
Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-05-12 14:16:05 +05:30
Abijeet
47cb99a2d6 Added test cases.
Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-05-12 13:07:28 +05:30
Abijeet
86b2ddbd28 Implemented displaying of the books list on home page. 2018-05-10 09:05:18 +05:30
Abijeet
2e4863edb1 Added an option to set books as the default homepage.
Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-05-09 08:26:49 +05:30
Dan Brown
b0d027a4a9 Repaired other uses of entity-meta view 2018-04-30 15:12:10 +01:00
Dan Brown
0c3c8fc9c3 Updated npm dependancies 2018-04-30 14:54:54 +01:00
Dan Brown
624c568008 Revamped tag styling 2018-04-30 14:35:15 +01:00
Dan Brown
58a0a59d7e Cleaned details sidebar box and merged with permissions 2018-04-30 13:53:04 +01:00
Dan Brown
3d0d7f8be2 Updated version for next block of development 2018-04-30 13:52:22 +01:00
Dan Brown
bab99a26ec Updated assets and version for v0.21 release 2018-04-22 20:21:22 +01:00
Dan Brown
9a7fecd269 Merge branch 'master' into release 2018-04-22 20:19:02 +01:00
Dan Brown
eb5069ca66 Attempted to fix failing time-based test 2018-04-22 20:06:46 +01:00
Dan Brown
0306253c45 Merge branch 'master' of github.com:BookStackApp/BookStack 2018-04-22 12:26:21 +01:00
Dan Brown
71b6f09128 Applied phpcs findings 2018-04-22 12:25:32 +01:00
Dan Brown
67e0c3d2a5 Improved export base64 encoding of images
Now will use set storage mechanism to find image files.
Fixes #786

Added test to cover
2018-04-22 12:23:43 +01:00
Dan Brown
6aeb1387aa Fixed licence badge 2018-04-22 11:26:11 +01:00
Dan Brown
fa83e6bda4 Merge pull request #806 from leomartinez/master
Updated 'Spanish Argentina' translation.
2018-04-21 13:46:07 +01:00
Leonardo Martinez
ae89e05a25 Updated 'Spanish Argentina' translation. 2018-04-17 09:45:57 -03:00
Dan Brown
a50153d221 Slimmed down testing DB sized and improved permission caching 2018-04-14 22:17:47 +01:00
Dan Brown
cdb1c7ef88 Added destination permission checking to entity move 2018-04-14 18:47:13 +01:00
Dan Brown
0f7b0ad45a Added ability to copy a page
In 'More' menu alongside move.
Allows you to move if you have permission to create within the new
target parent.
Closes #673
2018-04-14 18:00:16 +01:00
marcusforsberg
6c5304a3de Updated Swedish translation 2018-04-14 18:09:09 +02:00
Dan Brown
d34b91f2c9 Updated move card width and made sidebar order more consistent 2018-04-14 16:23:16 +01:00
Dan Brown
dfadaa28f6 Updated reset-password flow design
Fixes #800
2018-04-14 16:16:29 +01:00
Dan Brown
fae564ff32 Merge pull request #798 from abno85/loc-de_DE
Update German localization
2018-04-14 16:02:48 +01:00
Dan Brown
502b22a0f2 Merge pull request #783 from moucho/master
Completely overhaul of the Spanish translation,  added missing strings
2018-04-14 16:01:25 +01:00
Dan Brown
f9feeef5c9 Merge pull request #780 from jasoncheng7115/master
Add Language zh_TW
2018-04-14 16:00:03 +01:00
Dan Brown
a6674a5a5e Merge pull request #767 from msaus/update_japanese_translation
update japanese translation
2018-04-14 15:58:35 +01:00
Dan Brown
fb18576259 Merge pull request #768 from BookStackApp/feature/tinymce-insert-video
Adds the media plugin to TinyMCE to allow insertion of videos.
2018-04-14 15:57:08 +01:00
Abijeet
7238a01f89 Moved the code to the wysiwyg-editor file.
Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-04-14 18:41:35 +05:30
Abijeet
93f92e9e16 Updated the TinyMCE to version 4.7.9.
Added some code to remove the box-shadow around the TinyMCE toolbar.

Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-04-14 18:30:34 +05:30
Abijeet
d92efd4edc Adds the media plugin to TinyMCE to allow insertion of videos.
Fixes #266

Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-04-14 18:30:28 +05:30
abno85
448f7d091b Add comment_add
Missed 'comment_add' the last time
2018-04-13 08:44:54 +02:00
abno85
11470b85f9 Update German localization
Added a few missing strings and (hopefully) fixed my borked first commit.
2018-04-12 13:48:07 +02:00
Marcos
9c07619099 orthography 2018-04-05 03:57:35 +02:00
Marcos
60a224f7a1 Missing comma 2018-04-05 02:58:32 +02:00
Marcos
e392e1fd8b Completely overhaul of the Spanish translation, added missing strings 2018-04-03 15:58:04 +02:00
Jason Cheng
64d5763d08 Add zh_TW Locales.
Add zh_TW Locales.
2018-04-02 16:09:23 +08:00
Jason Cheng
007059273e Add translate.
Add translate.
2018-04-02 15:54:06 +08:00
Jason Cheng
106432ee4e Added Language zh_TW
Added Language zh_TW
2018-04-02 15:03:07 +08:00
Dan Brown
0ade9b5b9b Refactored moment.js out of app
Reduces bundle size by 25%
2018-04-01 14:10:44 +01:00
Dan Brown
736d7118b0 Refactored js file structure to be standard throughout app
Still work to be done but a good start in standardisation.
2018-04-01 13:21:11 +01:00
Dan Brown
b612cf9e4c Refactored out page-display system 2018-04-01 12:46:27 +01:00
Dan Brown
1a72208d27 Added configurable robots.txt file.
Deleted old static file.
Default output depends on app-public setting.
Otherwise can be overidden in `.env` file via `ALLOW_ROBOTS`
Otherwise view file can be customized.

Fixes #779
2018-03-31 12:41:40 +01:00
Dan Brown
7f437c2e3c Fixed issue where cover images don't save on older books
Ensured an existing ID is always provided to image-picker.js.
Fixes #773
2018-03-31 11:21:22 +01:00
Dan Brown
cfdf5b93d9 Merge branch 'v0.20' to gain export fix 2018-03-30 15:45:34 +01:00
Dan Brown
a8dc0d449b Updated the version because i'm such a plonker
And forgot to do this last release.
I wonder if there's a simple commit hook that could prevent the same two
versions twice in a row?
2018-03-30 15:41:46 +01:00
Dan Brown
a0381f76bf Merge branch 'v0.20' into release 2018-03-30 15:33:23 +01:00
Dan Brown
3cd08382e9 Fixed export style paths 2018-03-30 15:31:39 +01:00
Dan Brown
58a6b2df7d Merge branch 'master' of github.com:BookStackApp/BookStack 2018-03-30 14:10:36 +01:00
Dan Brown
582158f70e Added tags to chapters and books
Closes #121
2018-03-30 14:09:51 +01:00
msaus
03ee3d21ba Merge branch 'master' into update_japanese_translation 2018-03-28 11:58:14 +09:00
Abijeet Patro
b99229a5c3 Merge pull request #769 from BookStackApp/psr-2-fixes
PSR2 fixes after running `./vendor/bin/phpcbf`
2018-03-28 01:09:09 +05:30
Abijeet
2fc513984d PSR2 fixes after running ./vendor/bin/phpcbf
Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-03-28 01:07:01 +05:30
Soseki Masao
34499658d5 update japanese translation 2018-03-27 16:18:38 +09:00
Dan Brown
a8f18c0102 Updated COC with criticism point 2018-03-25 18:16:53 +01:00
Dan Brown
c85cace48b Updated readme, license and added code of conduct 2018-03-25 18:12:49 +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
Dan Brown
22df25a480 Updated assets and version for v0.19.0 2017-12-10 18:21:07 +00:00
Dan Brown
8b30c7f02e Merge branch 'master' into release 2017-12-10 18:19:20 +00:00
Dan Brown
03eb63ec77 Made it possible to pre-fill login via url
Allows email to be passed to email field.
Also allows password only if in demo mode (Due to security concerns).
2017-12-10 13:56:25 +00:00
Dan Brown
3ed5426315 Moved book cover image input into collapsible section
Prevent extra friction when creating a new book and makes it easier to
skip if grid view is not in use
2017-12-10 13:46:50 +00:00
Dan Brown
90bf13c1ab Updated okta config keys, made SVG fully vector
Also added some additional error handling to login.
2017-12-09 13:32:45 +00:00
Dan Brown
d17eb0f54c Merge branch 'master' of git://github.com/lommes/BookStack into lommes-master 2017-12-09 12:48:08 +00:00
Dan Brown
ac7e3977de Fixed WYSIWYG fullscreen mode on firefox
Prevented overlapping sidebar and collapsed content.
Fixes #605
2017-12-09 12:38:30 +00:00
Dan Brown
d7edc389a6 Enabled custom HTML head content to work within editors
Closes #562
2017-12-08 11:52:43 +00:00
Dan Brown
56d5af1336 Made it possible to configure proxies via env
In reference to #146
2017-12-07 19:46:47 +00:00
Dan Brown
06cf175b08 Prevented page navigation highlighting erroring
This was when no page nav was on the page
2017-12-07 19:27:54 +00:00
Dan Brown
b65abd25e0 Made small var name and formatting tweaks 2017-12-07 19:19:25 +00:00
Dan Brown
a5e49f642b Merge branch 'disable-comments' of git://github.com/Abijeet/BookStack into Abijeet-disable-comments 2017-12-07 19:15:26 +00:00
Dan Brown
91444e83fd Cleaned up some page-show JS 2017-12-07 19:10:31 +00:00
Dan Brown
6063ac4a11 Merge branch 'master' of git://github.com/Abijeet/BookStack 2017-12-07 18:47:07 +00:00
Dan Brown
02fd1c48ed Added meta+enter shortcut for page save
Closes #604
2017-12-07 18:44:20 +00:00
Dan Brown
6ee35f55cc Refactored image picker to js component
Also adjusted default cover image size
2017-12-06 17:32:29 +00:00
Dan Brown
261e57fc4e Converted books view setting to user setting
Also cleaned up/moved new CSS and removed redundant new book methods.
2017-12-06 16:34:26 +00:00
Dan Brown
bc1302a8d8 Merge branch 'BookStackApp-master' of git://github.com/OsmosysSoftware/BookStack into OsmosysSoftware-BookStackApp-master 2017-12-06 15:52:54 +00:00
Dan Brown
eeb2b8cbe5 Prevented finding of check script in lang tests 2017-12-06 11:17:34 +00:00
Dan Brown
b167ae795e Added script to check translation files
Closes #373
2017-12-04 20:25:04 +00:00
Dan Brown
6ebe8bf619 Fixed conflicting PDF facade namespace and corrected php version
Updated composer to have the correct config to install dependancies that
work for 7.0
2017-12-04 17:59:53 +00:00
Timo Bartholomes
009af9736e Add socialite authentication for okta 2017-11-26 16:41:29 +01:00
Dan Brown
7668a999a2 Fixed heavy init breakages made in last commit 2017-11-19 18:31:24 +00:00
Dan Brown
ed88c623d6 Made some further laravel 5.5 cleanup
Removed old bootstrap files that are not needed and
amended composer to laravel upgrade guide
2017-11-19 17:59:12 +00:00
Dan Brown
873b1099f8 Updated to laravel 5.5
Closes #590
2017-11-19 15:56:19 +00:00
Abijeet
6a54733f2b Adding testcases for comments disable / enable setting. 2017-11-16 23:32:36 +05:30
Abijeet
7a5bd23909 Added language translation for the new settings icons. 2017-11-16 00:22:22 +05:30
Abijeet
6bb7b5465f Added code in the settings to disable comments. Based on that hiding the comments section on the page display. 2017-11-16 00:05:24 +05:30
Abijeet Patro
0b967d84ad Merge pull request #2 from BookStackApp/master
Getting the latest code.
2017-11-15 08:05:20 +05:30
Abijeet
2261308415 Removed invalid comments, and formatted the code. 2017-11-15 00:04:35 +05:30
Abijeet
7b5edb4d62 Merge branch 'master' of https://github.com/Abijeet/BookStack 2017-11-15 00:01:48 +05:30
Abijeet
8378f06889 Highlights all headings currently visible. Also fixes extra scrollbar appearing in Firefox. 2017-11-14 23:54:25 +05:30
Abijeet Patro
10dc851697 Merge pull request #1 from BookStackApp/master
Getting the latest changes.
2017-11-13 23:22:45 +05:30
Dan Brown
757cdddc7c Updated version and JS for release v0.18.5 2017-11-11 18:33:04 +00:00
Dan Brown
65579214e2 Allowed custom session expiry time
Closes #570
2017-11-11 18:30:55 +00:00
Dan Brown
d89440d198 Fixed required email confirmation with domain restriction
Added test to cover scenario.

Closes #573
2017-11-11 18:09:48 +00:00
Dan Brown
08e58bab79 Fixed vue component error 2017-11-11 17:10:15 +00:00
Dan Brown
d29b177c84 Merge pull request #563 from 10bass/master
Fix ajax tag suggestion for subdir installs
2017-11-11 17:03:36 +00:00
Dan Brown
151d72e42c Merge pull request #558 from lbguilherme/ptbr
Update pt_BR translations
2017-11-11 16:30:16 +00:00
Dan Brown
711ba258f1 Prevented mulitple hypens incorrectly in slug
Added test to check slug format.
Fixes #589
2017-11-11 16:27:29 +00:00
Dan Brown
df4d4f30f1 Added package-lock file for npm version locking 2017-11-11 16:19:24 +00:00
Dan Brown
f094837709 Added test to cover multi-byte slugs
Also removed check for 'mb_' functions since mbstring is a dependancy
2017-11-11 16:15:08 +00:00
Dan Brown
e27cbb9dce Merge branch 'wowkaster-patch-1' 2017-11-11 16:07:23 +00:00
Abijeet
bdba25b6f2 Refactored all functionality into one function. Changed margin-top. 2017-11-05 20:23:16 +05:30
Vladimir
6b2581de63 Russian slug and Multibyte String 2017-11-03 14:00:07 +02:00
Abijeet
1031c61d0c Fixes #466. Adds support for header highlighting using intersection observer. 2017-11-02 01:14:06 +05:30
10bass
46fc0e5026 Fix ajax tag suggestion for subdir installs
tag suggest URLs were hardcoded to /ajax in blade template. Wrapped them in baseUrl()
2017-10-16 18:24:47 -04:00
Guilherme Bernal
332f678ed0 Update pt_BR translations 2017-10-15 16:15:53 -03:00
Dan Brown
df95e99680 Updated assets and version for release v0.18.4 2017-10-15 19:28:29 +01:00
Dan Brown
5a6d544db7 Merge branch 'master' into release 2017-10-15 19:27:50 +01:00
Dan Brown
0d5d77d8ab Updated search test to fit with new tokenization 2017-10-15 19:24:06 +01:00
Dan Brown
db51cee2d8 Prevented custom homepage being deleted
Fixes #546
2017-10-15 19:14:46 +01:00
Dan Brown
a988438946 Expanded list of indexing split chars
Expands on #531
2017-10-15 19:14:31 +01:00
Dan Brown
3bf7cac030 Prevented flexbox contains overflowing page
Fixes #552
2017-10-15 18:34:37 +01:00
Dan Brown
79c3a07e9a Fixed include syntax erroring within vue
Fixes #553
2017-10-15 18:20:13 +01:00
Dan Brown
16117d329c Merge branch 'master' into release, Updated version 2017-10-06 21:05:45 +01:00
Dan Brown
9758872baf Updated image fetching in exporting
Added domain check to see if possibly local even when whole url found.
Changed image fetch from file_get_contents to curl for external
resources.

Hopeful solution to #392
2017-10-06 20:49:25 +01:00
Dan Brown
e90da18ada Updated assets and version for v0.18.2 release 2017-10-01 18:12:59 +01:00
Dan Brown
a08d80e1cc Merge branch 'master' into release 2017-10-01 18:12:07 +01:00
Dan Brown
b711bc6816 Prevented 'Discard draft' option showing after saving a draft page 2017-10-01 18:11:24 +01:00
Dan Brown
247e6dba85 Fixed some design issues around cards
Reverted drop shadow change.
Fixed header line-height when linked.
Fixed overflowing paragraph text. Fixes #533.
2017-10-01 17:59:51 +01:00
Dan Brown
2b3d6e4e4a Updated search-regen command description 2017-10-01 17:51:59 +01:00
Dan Brown
6b1980c4f3 Merge branch 'master' of github.com:BookStackApp/BookStack 2017-10-01 13:19:41 +01:00
Dan Brown
9ba29770e1 Added azureAD social auth option
Closes #509
2017-10-01 13:19:17 +01:00
Dan Brown
3d375fae55 Merge pull request #529 from cipi1965/master
Updated italian translation
2017-10-01 11:44:45 +01:00
Dan Brown
c99a50de2c Merge pull request #528 from turbotankist/master
russian lang fixes
2017-10-01 11:43:31 +01:00
Dan Brown
1a32b25b5e Merge pull request #523 from sanderdw/master
Update dutch translations
2017-10-01 11:33:22 +01:00
Dan Brown
481aa5b5b0 Added 'last_commented' sort option to search
Closes #440
2017-10-01 11:24:33 +01:00
Dan Brown
c943eb4d0d Removed empty string null middleware as was causing issues 2017-09-30 14:44:52 +01:00
Dan Brown
aca6de49b0 Added missing middleware to trim input 2017-09-30 14:31:27 +01:00
Dan Brown
5fd04fa470 Updated search indexer to split words better
Will now split up words based on more chars than just spaces.
Not takes into account newlines, tabs, periods & commas.

Fixed #531
2017-09-30 14:14:23 +01:00
Dan Brown
87339e4cd0 Added missing codemirror theme class
Fixes #535
2017-09-30 13:48:38 +01:00
Dan Brown
a9eb058dad Updated issue template 2017-09-30 13:41:06 +01:00
Dan Brown
61fad6a665 Finished migration of last angular code 2017-09-30 13:27:08 +01:00
Matteo Piccina
fa4bee2d98 Updated italian translation 2017-09-27 10:44:08 +02:00
alexey
ce63260fa6 russian lang fixes 2017-09-27 11:17:56 +03:00
Dan Brown
a3557d5bb2 Tweaked shadows on cards 2017-09-24 18:47:34 +01:00
Dan Brown
9ca22976c3 Migrated editor toolbox, No more directives! 2017-09-24 18:30:21 +01:00
Dan Brown
9e2934fe17 Migrated editor inputs to non-angular JS 2017-09-23 12:24:06 +01:00
sanderdw
2259263214 Update entities.php 2017-09-23 00:52:08 +02:00
sanderdw
762cf5f183 Update components.php 2017-09-23 00:47:02 +02:00
sanderdw
07175f2b3e Update dutch translations 2017-09-23 00:28:25 +02:00
Dan Brown
6258175922 Updated assets and version for v0.18.1 release 2017-09-20 21:36:17 +01:00
Dan Brown
15736777a0 Merge branch 'master' into release 2017-09-20 21:35:33 +01:00
Dan Brown
0c4ddf16a5 Moved details card above book nav 2017-09-20 21:32:19 +01:00
Dan Brown
74a5e3113e Fixed page includes erroring on save
Closes #514
2017-09-20 21:03:40 +01:00
Dan Brown
df0a982433 Merge branch 'master' of github.com:BookStackApp/BookStack 2017-09-20 20:27:50 +01:00
Dan Brown
212f924ffa Refactored WYSIWYG editor image upload code
Added sketchy timeout to fix images being pasted at end of page.
Fixes #489
2017-09-20 20:27:00 +01:00
Dan Brown
09936566dd Upgrade tinymce version 2017-09-20 20:26:34 +01:00
Dan Brown
9469c04eab Merge pull request #517 from leomartinez/master
Added 'Spanish Argentina' translation
2017-09-20 20:17:39 +01:00
Leonardo Martinez
941bb73a68 Added missing colon 2017-09-19 14:25:44 -03:00
Leonardo Martinez
8e652f5d8f Added 'Spanish Argentina' translation. 2017-09-19 13:43:17 -03:00
Leonardo Martinez
eec1b21928 Added 'Spanish Argentina' translation. 2017-09-19 13:42:39 -03:00
Dan Brown
1baeb7bec9 Merge pull request #510 from sanderdw/patch-1
Update entities.php
2017-09-17 11:09:12 +01:00
Dan Brown
61475ca0a3 Merge pull request #506 from turbotankist/master
Russian lang added
2017-09-14 20:44:19 +01:00
sanderdw
244c5a3ebb Update entities.php 2017-09-14 21:43:47 +02:00
Dan Brown
39e7ac1c15 Updated social login to redirect to intended page.
Closes #508.
2017-09-14 20:20:47 +01:00
alexey
ab7f5def04 Russian lang added 2017-09-12 16:42:04 +03:00
Dan Brown
75915e8a94 Updated assets for release v0.18 2017-09-10 17:07:57 +01:00
Dan Brown
9bde0ae4ea Merge branch 'master' into release 2017-09-10 17:05:05 +01:00
Dan Brown
cd7e727f8c Modified IT comment translations as per recent changes 2017-09-10 16:43:05 +01:00
Dan Brown
2329a5cedf Merge branch 'master' of github.com:BookStackApp/BookStack 2017-09-10 16:36:47 +01:00
Dan Brown
d1a4ff9308 Merge pull request #501 from cipi1965/master
Added Italian language
2017-09-10 16:29:44 +01:00
Dan Brown
9a3bc27ef4 Fixed bullet styles and added code highlight on comments 2017-09-10 16:14:04 +01:00
Matteo Piccina
f8315fb9c4 Added Italian language 2017-09-10 16:55:23 +02:00
Dan Brown
bb62dee5a2 Merge pull request #500 from timoschwarzer/translation_update_de
Update german translation
2017-09-10 14:08:23 +01:00
Timo Schwarzer
8acc188e16 Update german translation 2017-09-10 15:01:25 +02:00
Dan Brown
874386ceab Used trans_choice on profile view
Closes #417
2017-09-10 13:43:08 +01:00
Dan Brown
9dfbea8bf9 Restored seeder and fixed scroll on firefox 2017-09-10 13:29:48 +01:00
Dan Brown
c1627a1468 Fixed sidebar scroll on mobile 2017-09-10 13:19:47 +01:00
Dan Brown
576a59a693 Fixed line-height difference in tinymce 2017-09-10 13:08:12 +01:00
Dan Brown
fec6c65b78 Fixed quick save shortcut in wysiwyg editor
Fixes #467
2017-09-10 13:01:48 +01:00
Dan Brown
f8c046d182 Fixed markdown callout tags and cursor pos
Closes #470
2017-09-09 19:41:11 +01:00
Dan Brown
9ca2184f09 Attempt to fix travis switching phpunit version 2017-09-09 19:17:00 +01:00
Dan Brown
fd449582bd Removed comments from seeder since they are not used by tests 2017-09-09 18:48:47 +01:00
Dan Brown
621142a46e Removed outdated translations and updated tests 2017-09-09 18:41:59 +01:00
Dan Brown
0275d2ad58 Added loading icons, Added comment activity 2017-09-09 17:06:30 +01:00
Dan Brown
41f56e659d Added comment reply and delete confirmation.
Also fixed local_id bug
Added component helpers
Added global scroll & Highlight helpers
2017-09-09 15:56:24 +01:00
Bharadwaja G
5034f21394 Added migration file. 2017-09-05 19:53:29 +05:30
Bharadwaja G
e02fcbe983 Added Book cover image description in all languages. 2017-09-05 12:46:31 +05:30
Bharadwaja G
1c88d21abf Fixed books cover image ratio. 2017-09-04 20:50:24 +05:30
Bharadwaja G
c1a1bc0135 Books grid view 2017-09-04 20:27:52 +05:30
Dan Brown
fea5630ea4 Made some changes to the comment system
Changed to be rendered server side along with page content.
Changed deletion to fully delete comments from the database.
Added 'local_id' to comments for referencing.
Updated reply system to be non-nested (Incomplete)
Made database comment format entity-agnostic to be more future proof.
Updated designs of comment sections.
2017-09-03 16:37:51 +01:00
Dan Brown
e3f2bde26d Merge branch 'Abijeet-master' to convert comment system to vue 2017-09-02 17:40:40 +01:00
Dan Brown
756ee0b172 Merge branch 'master' of git://github.com/Abijeet/BookStack into Abijeet-master 2017-09-02 17:36:58 +01:00
Dan Brown
c81b63b56f Fixed broken page content includes 2017-09-02 16:06:03 +01:00
Dan Brown
70ee28ee13 Fixed long attachment names breaking outer boxes
Closes #460
2017-09-02 15:21:05 +01:00
Dan Brown
1c9ecc3edd Reformatted sortable toolbox components 2017-09-02 15:06:52 +01:00
Dan Brown
9bd5d6a422 Merge pull request #483 from msaus/japanese_lang_update
Japanese lang update
2017-09-02 14:39:14 +01:00
Dan Brown
1e41ccbc7a Added ability to override codemirror theme
Also cleaned codemirror file while there.
In referece to #455
2017-09-02 13:34:37 +01:00
Bharadwaja G
6200948eec Merge branch 'master' of git://github.com/BookStackApp/BookStack into BookStackApp-master
Conflicts:
	app/Http/Controllers/BookController.php
	resources/lang/en/common.php
	resources/views/books/create.blade.php
	resources/views/books/form.blade.php
	resources/views/books/index.blade.php
	resources/views/users/edit.blade.php
	tests/Entity/EntityTest.php
2017-08-29 12:19:00 +05:30
Dan Brown
0a402e3c63 Made custom home ignore permissions and added tests
Closes #126 and #372
2017-08-28 13:55:39 +01:00
Dan Brown
55759bd22a Added ability to set a page to view on the homepage.
Relates to #372 and #126
2017-08-28 13:38:32 +01:00
Dan Brown
d8e1f52ddd Made new sidebar layout responsive 2017-08-27 15:16:51 +01:00
Dan Brown
baea92b206 Migrated entity selector out of angular 2017-08-27 14:31:34 +01:00
Dan Brown
cfc05c1b22 Improved primary color control in settings 2017-08-27 12:59:56 +01:00
Dan Brown
ebf78d49a8 Merge pull request #480 from BookStackApp/design_update_2017
Design update 2017
2017-08-26 17:22:30 +01:00
Dan Brown
4cb4c9e568 Updated remaining views to 2017 design update.
Also fixed issue with duplicate confirmation email.
2017-08-26 17:17:04 +01:00
Dan Brown
36f524a354 Updated page view styles to align with 2017 update 2017-08-26 15:41:33 +01:00
Dan Brown
b60d2190ac Updated error views for redesign 2017-08-26 14:53:23 +01:00
Dan Brown
2f8b8c580d Resolved current failing tests 2017-08-26 14:41:46 +01:00
Dan Brown
5187d3fa78 Updated chapter views with new design 2017-08-26 14:36:48 +01:00
Dan Brown
9c07741972 Updated readme with project definition 2017-08-26 13:49:56 +01:00
Dan Brown
8fcbe44d3e Updated styles for auth and books views.
Also added sourcemaps to gulp sass build
2017-08-26 13:24:55 +01:00
Bharadwaja G
7f902e41c7 Resolved conflicts 2017-08-24 12:21:43 +05:30
soseki
3966fb1df6 update Japanese language for code editor 2017-08-24 15:30:07 +09:00
soseki
830614fb19 add Japanese language for code editor 2017-08-24 15:24:43 +09:00
Abijeet
76ae5c7398 Removes some unused code. 2017-08-23 01:04:36 +05:30
Abijeet
769935f99e Reverting database.php and app.php 2017-08-23 00:52:50 +05:30
Abijeet
6920d6eef1 Fixes the comment check for linked comment. 2017-08-22 01:39:09 +05:30
Abijeet
b5cd3bff3c Added functionality to highlight a comment. 2017-08-22 01:31:11 +05:30
Abijeet
ac07cb41b6 Fixed formatting and added error messages. 2017-08-21 02:21:31 +05:30
Abijeet
e8fa58f201 Removed code from the directives. 2017-08-21 00:38:59 +05:30
Abijeet
1f6994b62c Added code for permissions, removed unnecessary code. 2017-08-20 21:26:44 +05:30
Abijeet
47d82a1ac2 Merge branch 'master' of https://github.com/BookStackApp/BookStack
Conflicts:
	resources/assets/js/vues/vues.js
2017-08-20 20:35:56 +05:30
Abijeet
703d579561 Refactored Angular code to instead use VueJS, left with permissions, testing and load testing. 2017-08-20 20:21:32 +05:30
Abijeet
ed375bfaf7 Refactored Angular code to instead use VueJS, left with permissions, testing and load testing. 2017-08-20 20:21:27 +05:30
Dan Brown
3da8c01c1f Rolled out new design further 2017-08-20 13:57:25 +01:00
Dan Brown
295f520f21 Started design update 2017-08-19 18:32:24 +01:00
Dan Brown
fba7ae923d Darkened a few cm code styles for improved legibility 2017-08-19 15:51:51 +01:00
Dan Brown
28a9bd514f Updated header design 2017-08-19 15:33:22 +01:00
Dan Brown
666c86b108 Removed included fonts, Set to use system fonts.
All font definitions moved into _text.scss.
Needs override documentation to complete.
Relates to #423.
2017-08-19 14:33:55 +01:00
Dan Brown
194293664f Removed v-show delayed content display 2017-08-19 14:09:03 +01:00
Dan Brown
039ee5d06c Aligned entity dash name and fixed chapter toggle on dash 2017-08-19 14:04:38 +01:00
Dan Brown
afc66b3c3d Migrated attachment manager to vue 2017-08-19 13:55:56 +01:00
Dan Brown
a04b31866d Cleaned social callback 2017-08-17 19:44:35 +01:00
Dan Brown
db3dde98ef Merge pull request #474 from timoschwarzer/translation_update_de
Update and fix German translation
2017-08-17 18:51:35 +01:00
Timo Schwarzer
0d6a6b5b63 Update/fix german translation 2017-08-16 22:31:07 +02:00
Abijeet Patro
4df3267521 Merge pull request #13 from BookStackApp/master
Getting the latest changes.
2017-08-14 23:09:26 +05:30
Dan Brown
d3e4a1a6f9 Converted tag autosuggestion to vue component 2017-08-13 13:25:30 +01:00
Dan Brown
b023699f1b Converted tag manager to be fully vue based 2017-08-13 11:50:40 +01:00
Dan Brown
f338dbe3f8 Started vueifying tag system 2017-08-10 20:11:25 +01:00
Dan Brown
ab07f7df6c Converted image manager into vue component 2017-08-09 21:33:00 +01:00
Dan Brown
a59d73de7b Fixed bug causing image manager popup not to show 2017-08-07 19:32:31 +01:00
Dan Brown
1ac7618bb1 Updated clipboard lib reference and version used 2017-08-06 21:21:20 +01:00
Dan Brown
2a069880cd Converted jQuery bits into raw JS components 2017-08-06 21:08:03 +01:00
Dan Brown
5e5928a8a6 Added vanilla JS component system 2017-08-06 18:01:49 +01:00
Dan Brown
d6e87420c3 Merged comment migrations and incremented dev version 2017-08-01 20:05:49 +01:00
Dan Brown
79cfd39fde Merge branch 'Abijeet-master' 2017-08-01 20:04:33 +01:00
Dan Brown
e9831a7507 Merge branch 'master' of git://github.com/Abijeet/BookStack into Abijeet-master 2017-08-01 19:24:33 +01:00
Dan Brown
0c802d1f86 Updated assets and version for release v0.17.4 2017-07-28 13:04:21 +01:00
Dan Brown
b7a96c6466 Merge branch 'master' into release 2017-07-28 13:03:36 +01:00
Dan Brown
9126da6299 Updated dev command details
Closes #453
2017-07-28 11:32:42 +01:00
Dan Brown
fea139d8e7 Merge branch 'master' of github.com:BookStackApp/BookStack 2017-07-27 19:09:44 +01:00
Dan Brown
ac7a8a8e1e Expanded the available editor shortcuts in both editors
Adds formatting on ctrl+nums for everything on formats dropdown.
Closes #85.
2017-07-27 19:07:58 +01:00
Dan Brown
bbaa2f4cda Merge pull request #435 from JachuPL/polish-localization
Polish translation
2017-07-27 16:48:19 +01:00
Dan Brown
9d61eecd81 Merge branch 'Cyber-Duck-master' 2017-07-27 16:29:09 +01:00
Dan Brown
21247e10d0 Reverted travis changes and added html escaping 2017-07-27 16:28:23 +01:00
Dan Brown
c1fc06ae34 Merge branch 'master' of git://github.com/Cyber-Duck/BookStack into Cyber-Duck-master 2017-07-27 16:20:38 +01:00
Dan Brown
a0eb3d1079 Merge pull request #446 from Joorem/french-spelling
French spelling
2017-07-27 16:18:57 +01:00
Dan Brown
164aea3a3a Merge pull request #448 from 10bass/subdir-search-fix
Update search.js
2017-07-27 16:17:57 +01:00
Dan Brown
ec83f83017 Added breadcrumbs to pages in entity select
Fixes #391
2017-07-27 16:10:58 +01:00
Dan Brown
5cd08ab2f5 Fixed custom plugin when developing 2017-07-27 15:43:17 +01:00
Dan Brown
072f6b103e Vastly sped up gulp watch and added livereload 2017-07-27 15:14:53 +01:00
10bass
b4dcde252b Update search.js
Trying to apply an exact match or tag would previously redirect to /search, regardless of the installation path.
2017-07-24 20:06:15 -04:00
Jérôme Le Gal
0b2c3c1aa7 settings.php: add missing french translation 2017-07-22 23:45:09 +02:00
Jérôme Le Gal
0dc9d0bed7 errors.php: add missing french translation 2017-07-22 23:45:09 +02:00
Jérôme Le Gal
a2a2e37797 settings.php: fix some spelling issues in french translation 2017-07-22 23:45:09 +02:00
Jérôme Le Gal
3d9819f97c passwords.php: fix some spelling issues in french translation 2017-07-22 23:45:09 +02:00
Jérôme Le Gal
b2b4a24d7c errors.php: fix some spelling issues in french translation 2017-07-22 23:45:08 +02:00
Jérôme Le Gal
7557d6d619 entities.php: fix some spelling issues in french translation 2017-07-22 23:45:08 +02:00
Jérôme Le Gal
57eeb9b0a3 common.php: fix some spelling issues in french translation 2017-07-22 23:45:08 +02:00
Jérôme Le Gal
813c7d5902 auth.php: fix some spelling issues in french translation 2017-07-22 23:45:08 +02:00
Dan Brown
4b645a82c7 Updated version for release 2017-07-22 17:27:01 +01:00
Dan Brown
d599b77b6f Merge branch 'master' into release 2017-07-22 17:26:44 +01:00
Dan Brown
f200b4183d Defined LDAP constant for testing without LDAP installed 2017-07-22 17:22:31 +01:00
Dan Brown
33642c20ec Fixed faulty text rendering calls and LDAP tests 2017-07-22 17:10:52 +01:00
Dan Brown
26e93dc8c1 Updated assets and version for release v0.17.2 2017-07-22 16:49:07 +01:00
Dan Brown
a4c9a8491b Merge branch 'master' into release 2017-07-22 16:46:57 +01:00
Dan Brown
2704962277 Updated utfmb4 upgrade command 2017-07-22 16:19:17 +01:00
Dan Brown
6bcd89acf7 Moved utf8mb4 migration to command instead of migration
To prevent errors upon migration.
Command generates out the SQL syntax to make the change instead
so the upgrade can be done manually.

In reference to #425
2017-07-22 15:54:17 +01:00
Dan Brown
433cb9b3b2 Improved breadcrumb responsiveness
Closes #426
2017-07-22 15:20:36 +01:00
Dan Brown
7f43372dd4 Fixed broken code block rendering when using DOMPDF
Fixes #427
2017-07-22 14:34:17 +01:00
Dan Brown
b12e2ceada Added included content into page's text view
Allows rendered content to be shown in listings and used in searches.
Also prevented angular tags in content being parsed in listings.

Fixes #442
2017-07-22 14:21:56 +01:00
Dan Brown
bc067e9ad4 Updated dropdowns to hide after option click
Fixes #429
2017-07-22 14:03:06 +01:00
Clément Blanco
245294fbc5 Trying to make the tests green. 2017-07-17 14:42:08 +01:00
Clément Blanco
f38bc75ab4 Trying to make the tests green. 2017-07-17 14:21:41 +01:00
Clément Blanco
3407900abb Trying to make the tests green. 2017-07-17 14:18:03 +01:00
Clément Blanco
684c20c4ea Trying to make the tests green. 2017-07-17 14:09:21 +01:00
Clément Blanco
6ef522df7e Trying to make the tests green. 2017-07-17 14:05:41 +01:00
Clément Blanco
3b771f2976 Trying to make the tests green. 2017-07-17 14:03:31 +01:00
Clément Blanco
afc56c12fe Trying to make the tests green. 2017-07-17 14:01:10 +01:00
Clément Blanco
5eeed03dcd Trying to make the tests green. 2017-07-17 13:53:02 +01:00
Clément Blanco
0d98b4ce5e Trying to make the tests green. 2017-07-17 13:37:15 +01:00
Clément Blanco
ae2ec43a82 Avoid having to wait until all tests are processed to exit upon error/failure. 2017-07-17 13:35:38 +01:00
Clément Blanco
265ed34ffd Update travis.yml to try and solve the test issue around LDAP. 2017-07-17 13:34:19 +01:00
Clément Blanco
711dcb4a48 Update travis.yml to try and solve the test issue around LDAP. 2017-07-17 13:29:29 +01:00
Nilesh Deepak
3079a9f4de Reverted required changes. 2017-07-15 19:07:32 +05:30
Nilesh Deepak
a7d2cfdee2 Resolving test cases 2017-07-15 19:03:02 +05:30
Nilesh Deepak
a149e87ca7 Resolving test cases 2017-07-15 19:00:23 +05:30
Nilesh Deepak
854fd52a27 Resolving test cases 2017-07-15 18:57:09 +05:30
Nilesh Deepak
3d808ac75f Test for cover image 2017-07-15 18:39:13 +05:30
Nilesh Deepak
39b924f158 Merge branch 'master' of https://github.com/OsmosysSoftware/BookStack 2017-07-15 18:37:55 +05:30
Nilesh Deepak
a488ef6b00 Test for cover image. 2017-07-15 18:36:49 +05:30
abijeetp
6d66c38c12 Fixes issues with the test case, now creating a user with the required profile setting. 2017-07-15 18:00:39 +05:30
Nilesh Deepak
922964ecf2 Changes grid container size 2017-07-15 17:50:09 +05:30
Nilesh Deepak
0c70416b5c Test books display options. 2017-07-15 16:33:52 +05:30
Nilesh Deepak
770f30c3a8 Test books display options. 2017-07-15 16:29:42 +05:30
Nilesh Deepak
b4044e6c3a Resolves heading issues in grid view 2017-07-15 16:22:29 +05:30
Nilesh Deepak
9872767f20 Test for cover image upload 2017-07-15 16:19:35 +05:30
Nilesh Deepak
dd4d2f4696 Resolves book heading issues in grid view. 2017-07-15 16:15:45 +05:30
Nilesh Deepak
e5dc0e6bb8 Merge branch 'master' of https://github.com/OsmosysSoftware/BookStack 2017-07-15 16:13:48 +05:30
Nilesh Deepak
85fbe820c4 Adding getHeadingExcerpt to get heading. 2017-07-15 16:11:10 +05:30
abijeetp
832f8eaa94 Fixes the test case related to UserProfileTest. 2017-07-15 15:50:42 +05:30
Abijeet
3435dcc91e Merge pull request #10 from OsmosysSoftware/test-issue-181
Tests for issue 181
2017-07-15 14:29:38 +05:30
Nilesh Deepak
1ed74b8598 Test for grid and list layout selection. 2017-07-15 13:19:49 +05:30
Nilesh Deepak
fd36978c13 Test for layout selection. 2017-07-15 12:26:57 +05:30
Nilesh Deepak
1278a0b818 Test for layout selection. 2017-07-15 11:40:51 +05:30
Nilesh Deepak
6a6516ddd5 Test for layout selection. 2017-07-15 11:31:43 +05:30
Clément Blanco
67bc7007aa Support new lines for book/chapter descriptions
Avoid ignoring new lines when renderring the book/chapter descriptions on their respective detailed views.
2017-07-14 16:05:46 +01:00
Nilesh Deepak
1fe8f13503 Cover image test case 2017-07-14 18:36:50 +05:30
Nilesh Deepak
8f3adcda5d Cover image test case 2017-07-14 18:02:45 +05:30
JachuPL
9e0c931573 Polish translation 2017-07-13 16:00:42 +02:00
Abijeet
21a8df78ee Merge pull request #9 from OsmosysSoftware/feature-181
Feature 181
2017-07-13 15:50:43 +05:30
Nilesh Deepak
7f8351e044 Removed avatar class from form.blade.php 2017-07-13 15:20:53 +05:30
Nilesh Deepak
afc1ecafe9 4. Changed the border color of the gallery item to #ccc 2017-07-13 12:27:14 +05:30
Nilesh Deepak
ab6ff5fda2 3. New default.png 2017-07-13 12:26:01 +05:30
Nilesh Deepak
b0ba1a43a9 2. Added classed col-xs-6 col-sm-4 col-md-4 col-lg-3 in grid-item.blade.php
5. Added <div class="row"> in index.blade.php
2017-07-13 12:24:47 +05:30
Nilesh Deepak
e919cab3d1 1. Thumbnail size when creating or editing book. 2017-07-13 12:22:43 +05:30
Abijeet
f37509062e Merge pull request #8 from OsmosysSoftware/feature-181
Issue 181
2017-07-12 18:41:35 +05:30
Nilesh Deepak
24ee78ccd8 Update. 2017-07-12 18:04:06 +05:30
Nilesh Deepak
d37b398e79 Updates styles. 2017-07-12 13:52:21 +05:30
Nilesh Deepak
7a724f9134 Updated modifications. 2017-07-12 13:44:37 +05:30
Abijeet
f3b2e0fb91 Merge pull request #7 from OsmosysSoftware/revert-3-revert-1-issue-181
Revert "Revert "Bookstack grid view.""
2017-07-12 11:41:01 +05:30
Abijeet
844976c85b Revert "Revert "Bookstack grid view."" 2017-07-12 11:40:50 +05:30
Abijeet
f0d914abbf Merge pull request #5 from BookStackApp/master
Getting latest changes
2017-07-12 11:33:58 +05:30
Dan Brown
70ee636d87 Updated css and version for release 2017-07-10 20:52:32 +01:00
Dan Brown
b35f6dbb03 Merge branch 'master' into release 2017-07-10 20:51:25 +01:00
Dan Brown
2ea7e10923 Set ldap to not follow referrals by default
Added LDAP_FOLLOW_REFERRALS .env option to override.
Fixes #317
2017-07-10 19:43:49 +01:00
Dan Brown
e7f8188ee5 Prevented textarea styles interfering with codemirror
Closes #424
2017-07-10 19:29:35 +01:00
Abijeet
0ed3023b42 Merge pull request #3 from OsmosysSoftware/revert-1-issue-181
Revert "Bookstack grid view."
2017-07-07 17:28:47 +05:30
Abijeet
3fd61a3600 Revert "Bookstack grid view." 2017-07-07 17:28:34 +05:30
Nilesh Deepak
a663fc8aa8 Merge pull request #1 from OsmosysSoftware/issue-181
Bookstack grid view issue 181.
2017-07-07 17:08:19 +05:30
Nilesh Deepak
d84315fff8 Indentation correction. 2017-07-07 17:06:08 +05:30
Nilesh Deepak
144a6e469d Updated cover image upload and delete function. 2017-07-07 16:29:38 +05:30
Nilesh Deepak
c5f11e4516 Fixed pagination on change of display type. 2017-07-06 10:05:11 +05:30
Nilesh Deepak
16a09e8ff6 Deletion of image file on book deletion. 2017-07-06 10:03:40 +05:30
Nilesh Deepak
f51db4b9f6 Resolved responsiveness issues 2017-07-05 19:58:52 +05:30
Nilesh Deepak
6ad24a6bee Changed public getImageURL function to private. 2017-07-05 18:32:38 +05:30
Nilesh Deepak
5b736c3b36 Updated views to support different languages. 2017-07-05 16:12:29 +05:30
Nilesh Deepak
cc553cc93d Added labels for 'Thumbnail toggle' and 'Cover image' in different languages. 2017-07-05 16:11:15 +05:30
Nilesh Deepak
e88a06291e Updated toggle thumbnails function. 2017-07-05 16:09:20 +05:30
Nilesh Deepak
6eccb3d5b9 Adding new migration. 2017-07-05 16:08:04 +05:30
Nilesh Deepak
026de8c5ca Thumbnail toggle function. 2017-07-05 12:48:41 +05:30
Nilesh Deepak
e10d4b91cf styles.scss 2017-07-05 12:36:26 +05:30
Nilesh Deepak
d089eaf754 Changes in User edit profile page. 2017-07-05 12:32:39 +05:30
Nilesh Deepak
bb2d85965f Removed duplicated styles. 2017-07-05 12:29:16 +05:30
Nilesh Deepak
d99fd1fd65 Applied required changes 2017-07-05 12:26:02 +05:30
Nilesh Deepak
947c58f227 Applied required changes in BookStack. 2017-07-05 12:09:01 +05:30
Nilesh Deepak
bce5fdd5cd Merge branch 'master' into issue-181 2017-07-04 15:16:46 +05:30
Nilesh Deepak
fdf139edb2 Changing column size for responsiveness 2017-07-04 15:04:57 +05:30
Dan Brown
67d9e24d8f Merge branch 'master' into release
Also updated assets, Version number
2017-07-02 22:52:26 +01:00
Dan Brown
314d98abc3 Removed logs, Updated version, Fixed inconsistent subheader 2017-07-02 20:33:32 +01:00
Dan Brown
b2dfc069c5 Updated readme attribution 2017-07-02 19:38:28 +01:00
Dan Brown
69c50b9b48 Migrated markdown editor actions to new cm editor 2017-07-02 19:35:13 +01:00
Dan Brown
f101e4f010 Fixed quoting db/table names in encoding migration.
Also fixed incorrect if statement in db config.
2017-07-02 17:34:32 +01:00
Dan Brown
005f0eb4fc Updated default encoding and added conversion migration.
Also updated how DB port is defined so that the DB_PORT
env var can be used or it can be take from the host name.

Fixes #405
2017-07-02 17:30:12 +01:00
Dan Brown
7293ad7b24 Merge branch 'S64-japanese-translation' 2017-07-02 16:13:28 +01:00
Dan Brown
c5b58d8fd2 Merge branch 'japanese-translation' of git://github.com/S64/BookStack into S64-japanese-translation 2017-07-02 16:09:12 +01:00
Dan Brown
4db2c274e2 Prevent empty-state actions visible without permission.
Fixes #411
2017-07-02 15:59:40 +01:00
Dan Brown
cbff801aec Added test to cover f99c8ff.
Closes #409
2017-07-02 15:40:42 +01:00
Dan Brown
d5d83da766 Added diff and sh code types
Verified changes to ensure fixes #296.
2017-07-01 16:10:52 +01:00
Dan Brown
de6d8a811c Added quick lang-selection options to code editor 2017-07-01 15:50:28 +01:00
Dan Brown
a94844b6b7 Fixed code block selection and drag/drop issues 2017-07-01 15:38:11 +01:00
Dan Brown
968e7b8b72 Finished off main functionality of custom tinymce code editor 2017-07-01 13:23:46 +01:00
Shuma Yoshioka
5f461c9796 Add ja locale to config 2017-07-01 16:40:06 +09:00
Shuma Yoshioka
b2b8706d05 Translate errors.php 2017-07-01 16:38:43 +09:00
Shuma Yoshioka
2d345fe454 Translate entities.php 2017-07-01 16:38:20 +09:00
Shuma Yoshioka
7bec70d429 Translate common.php 2017-07-01 16:37:07 +09:00
Shuma Yoshioka
60710e7fb4 Translate auth.php 2017-07-01 16:33:54 +09:00
Shuma Yoshioka
f10d3a8564 Translate validation.php 2017-07-01 16:32:36 +09:00
Shuma Yoshioka
b020520366 Translate passwords.php 2017-07-01 16:32:02 +09:00
Shuma Yoshioka
52010e5820 Translate pagination.php 2017-07-01 16:31:32 +09:00
Nilesh Deepak
af72f0d490 Bookstack grid view. 2017-06-29 18:54:04 +05:30
Nilesh Deepak
8924618d12 test 2017-06-28 18:56:17 +05:30
Nilesh Deepak
6557fbb666 commit 2017-06-28 18:51:32 +05:30
Shuma Yoshioka
75eca03b05 Translate settings.php 2017-06-24 20:49:24 +09:00
Shuma Yoshioka
b8ebefd803 Translate components.php 2017-06-24 20:48:34 +09:00
Shuma Yoshioka
168c66815e add ja to settings 2017-06-24 20:46:38 +09:00
Shuma Yoshioka
26e703a1cd translate activities.php 2017-06-19 00:02:09 +09:00
Dan Brown
c8be214ee0 Started tinymce code editor components 2017-06-17 15:07:55 +01:00
Dan Brown
2060cdb931 Added code highlighting syntax modes 2017-06-17 12:41:18 +01:00
Abijeet
574ee820a9 #47 - Fixes the issues with the test case. 2017-06-13 02:37:50 +05:30
Abijeet
7d02f77e67 #47 - Added more test cases to test the APIs and permission for comments. 2017-06-13 02:31:17 +05:30
Abijeet
fd50efb503 #47 - Putting the comments right under the page. 2017-06-11 11:41:33 +05:30
Abijeet
9dbd7fa618 #47 - Adding comments to the dummy content seeder. 2017-06-11 11:40:37 +05:30
Abijeet
8dab31b87a Merge branch 'master' of https://github.com/Abijeet/BookStack 2017-06-10 22:56:07 +05:30
Abijeet
e155c52256 #47 - Fixes a few issues with the code. 2017-06-10 22:55:36 +05:30
Abijeet Patro
c76e7c706c adding a comment on top. 2017-06-10 19:47:45 +05:30
Abijeet
552943c033 #47 - Undos changes in config files. 2017-06-10 19:46:00 +05:30
Abijeet
4efe3b41da #47 - Added translations for other language files using Google translate. 2017-06-10 15:21:28 +05:30
Abijeet
218376a41c #47 - Fetching values from language files. 2017-06-08 01:30:43 +05:30
Abijeet
e647ec22b1 #47 - Adds direct linking to comments. 2017-06-08 01:14:53 +05:30
Abijeet
38fe756725 #47 - Fixes a couple of issues found during testing - delete not updating the UI, delete none not working properly. 2017-06-07 23:45:29 +05:30
Abijeet
652a67ad65 Removes some unncessary code. 2017-06-06 23:20:40 +05:30
Abijeet
5bd9da6054 #47 - Adds various translations in English, and a few code improvements. 2017-06-06 01:46:59 +05:30
Abijeet
7c6fe8c4e2 #47 - Changes the location of the reply and edit comment box. 2017-06-05 00:20:37 +05:30
Abijeet
689d1eb082 #47 - Adds a cancel button for edit and reply button. 2017-06-04 20:43:56 +05:30
Abijeet
06d75e1804 #47 - Updates the total comments when a comment is added. 2017-06-04 20:12:01 +05:30
Dan Brown
0528e98d9c Merge branch 'v0.16' 2017-06-04 15:39:55 +01:00
Dan Brown
3903fda6ca Incremented version 2017-06-04 15:38:49 +01:00
Dan Brown
441e46ebaa Merge branch 'v0.16' into release 2017-06-04 15:38:29 +01:00
Dan Brown
f99c8ff99a Fixed role permission removal bug 2017-06-04 15:37:10 +01:00
Abijeet
9558f84b97 #47 - Adds functionality to delete a comment. Also reduces the number of watchers. 2017-06-04 18:52:44 +05:30
Abijeet
2fd421b115 #47 - Adds comment level permissions to the front-end. 2017-06-04 11:17:14 +05:30
Abijeet
6ff440e677 Merge branch 'BookStackApp-master' 2017-06-04 10:20:25 +05:30
Abijeet
0bda5554dd Getting the latest changes 2017-06-04 10:20:01 +05:30
Abijeet
860d4d4be5 #47 - Changes the way we are handling fetching of data for the comment section. 2017-05-30 09:02:47 +05:30
Dan Brown
88f93f76dd Updated the markdown editor to use codemirror as editor
Improved scroll sync system to be smarter
2017-05-28 16:02:46 +01:00
Dan Brown
e5fc6bf5fa Moved from highlight.js to codemirror 2017-05-28 13:16:21 +01:00
Dan Brown
8990a9817f Updated tinymce to latest version 2017-05-28 11:57:58 +01:00
Abijeet
9a97995f18 #47 Displays the time for comments and border bottom for sub comments. 2017-05-25 08:04:19 +05:30
Abijeet
1a1e71cd60 #47 Adds two attributes updated and created to display time to user. 2017-05-25 08:03:27 +05:30
Abijeet
34802ff8a6 #47 Inserts null for updated_at when the user is creating a comment. 2017-05-25 08:02:49 +05:30
Abijeet
0ff5aad9c0 #47 Hides the reply button based if comments are 2 levels deep. 2017-05-24 07:02:11 +05:30
Abijeet
03e5d61798 #47 Implements the reply and edit functionality for comments. 2017-05-16 00:40:14 +05:30
Abijeet Patro
4f231d1bf0 Merge pull request #11 from BookStackApp/master
Fixed chapter check for non-mysqlnd instances
2017-05-15 22:25:33 +05:30
Dan Brown
1f4260f359 Updated version for release v0.16.2 2017-05-07 19:35:51 +01:00
Dan Brown
dc0bf8ad4e Merge branch 'master' into release 2017-05-07 19:35:34 +01:00
Dan Brown
75981c2412 Fixed chapter check for non-mysqlnd instances
Fixes #383
2017-05-07 19:34:56 +01:00
Abijeet
8b82753218 #47 - Gets rid of simplemde 2017-05-03 02:42:04 +05:30
Abijeet Patro
3368fe42d8 Merge pull request #10 from BookStackApp/master
Latest changes
2017-05-03 01:41:08 +05:30
Dan Brown
102e326e6a Updated JS and version for release v0.16.1 2017-04-30 19:51:23 +01:00
Dan Brown
2b25bf6f3b Merge branch 'master' into release 2017-04-30 19:50:29 +01:00
Dan Brown
f8ae4c335e Made single entity updates more efficent 2017-04-30 19:44:59 +01:00
Dan Brown
5570e858e5 Made more efficiency improvements to permission system 2017-04-30 11:38:58 +01:00
Dan Brown
1859a4d356 Refactored permission system components
Split joint permission creation into chunks

Fixes #374
2017-04-29 22:01:43 +01:00
Dan Brown
ad4642c2c4 Enabled translation when not logged in
Reads from the Accept-Language HTTP header.
Also fixed some encoding for ES translations.

Fixes #375
2017-04-29 16:47:41 +01:00
Dan Brown
92108d710d Re-enabled html in markdown editor
Fixes #378
2017-04-29 16:10:38 +01:00
Abijeet
c3ea0d333e #47 - Adds functionality to display child comments. Also has some code towards the reply functionality. 2017-04-27 02:35:29 +05:30
Dan Brown
f93280696d Updated assets for release v0.16 2017-04-23 20:42:28 +01:00
Dan Brown
1787391b07 Merge branch 'master' into release 2017-04-23 20:41:45 +01:00
Dan Brown
44347ee353 Fixed search system id clash 2017-04-23 20:27:49 +01:00
Dan Brown
9e704fcae4 Updated testing database connection issue 2017-04-23 17:51:01 +01:00
Dan Brown
fdd816b17d Merge pull request #362 from DaneEveritt/patch-1
Queue confirmation emails.
2017-04-23 17:15:06 +01:00
Dan Brown
82e2c523e6 Fixed chapter breadcrumbs and testing issues 2017-04-22 14:08:12 +01:00
Dan Brown
a323b0d49c Allowed child entity permissions to override parent permissions
Updated elements of a page display and sidebar render to allow
child permissions to work even when parent entitites have permission
set. This allows a page with a 'view' permission to be viewable even
when the parent book or chapter is not viewable.

Fixes #366
2017-04-22 13:39:34 +01:00
Dan Brown
4c985aac7e Added page revision counting
Adds stored revision counts to pages and the revisions themselves.
Closes #321
2017-04-20 20:58:54 +01:00
Dan Brown
87e18b8068 Merge pull request #357 from diegoseso/master
Spanish translation completed
2017-04-19 06:40:00 +01:00
Diego Jose Sosa Diaz
607a2c91fc Fixing encoding of files affecting accents. 2017-04-18 23:41:19 +02:00
Abijeet
d447355a61 Adding the view templates and styles. 2017-04-19 01:24:33 +05:30
Abijeet
8e2437498f Merge branch 'master' of https://github.com/Abijeet/BookStack 2017-04-19 01:23:27 +05:30
Abijeet Patro
9de85283cd Merge pull request #9 from BookStackApp/master
Get the latest changes.
2017-04-19 01:23:21 +05:30
Abijeet
b3d4c199ae Merge branch 'master' of https://github.com/Abijeet/BookStack
Conflicts:
	.gitignore
2017-04-19 01:21:45 +05:30
Dan Brown
fde970ba59 Switched out markdown render
Fixes #304.
Fixes #359.
2017-04-17 12:21:10 +01:00
Dan Brown
ec7be1b08b Merge pull request #363 from solidnerd/add-env-for-logging
Add APP_LOGGING
2017-04-17 11:46:32 +01:00
solidnerd
746a760a23 Add APP_LOGGING
This will add an variable for logging types to make it easier to define outside via .env.

Signed-off-by: solidnerd <niclas@mietz.io>
2017-04-17 09:55:11 +02:00
Dan Brown
1a09d88891 Added fade effect to page content highlighting
Closes #314
2017-04-16 16:46:55 +01:00
Dan Brown
46c01ecba2 Merge pull request #358 from jendrol/master
Add Slovak translation
2017-04-16 15:06:32 +01:00
Dan Brown
544ece03a5 Merge pull request #360 from Abijeet/spellcheck-fix
Fixes #354, Adds the spellchecker option
2017-04-16 15:02:20 +01:00
Dan Brown
5fee7c4db1 Merge pull request #340 from BookStackApp/search_system
Implementation of new search system
2017-04-16 11:01:00 +01:00
Dan Brown
8ed9f75d57 Fixed model extending mis-use 2017-04-16 10:54:23 +01:00
Dan Brown
a15b179676 Updated testcases for new search system.
Finishes implementation of new search system.
Closes #271
Closes #344
Fixes #285
Fixes #269
Closes #64
2017-04-16 10:47:44 +01:00
Dan Brown
73844b9eeb Enabled type search filter in book search 2017-04-15 19:31:11 +01:00
Dan Brown
dcde599709 Added chapter search
Migrated book search to vue-based system.
Updated old tag seached.
Made chapter page layout widths same as book page.
Closes #344
2017-04-15 19:16:07 +01:00
Dan Brown
0e0945ef84 Finished off UI for search system 2017-04-15 15:04:30 +01:00
Dan Brown
ad125327c0 Migrated to custom gulp setup and conintue search interface 2017-04-14 18:47:33 +01:00
Dane Everitt
dfaf20dd83 Actually include the Queueable namespace... 2017-04-13 20:09:38 -04:00
Dane Everitt
786262db3b Queue confirmation emails.
Implements Laravel's queue abilities into the email notification job. Should not affect existing installations that are not using queues as the environment file defaults to `sync`.
2017-04-13 20:03:03 -04:00
Abijeet
29a4110d8f Fixes #354, Adds the spellchecker option
Uses the browser_spellchecker option documented here - https://www.tinymce.com/docs/configure/spelling/#browser_spellcheck
2017-04-13 23:57:57 +05:30
Vlado Jendroľ
9b639f715f Add Slovak translation 2017-04-11 23:20:52 +02:00
Dan Brown
46f3d78c8a Fixed entity type filter bug in new search system 2017-04-09 21:12:13 +01:00
Dan Brown
1338ae2fc3 Started search interface, Added in vue and moved fonts 2017-04-09 20:59:57 +01:00
Dan Brown
37813a223a Improved DB prefix support and removed old search method 2017-04-09 14:44:56 +01:00
Diego Jose Sosa Diaz
b488e969bb Reaching 100 % Spanish translation 2017-04-06 23:12:05 +02:00
Diego Jose Sosa Diaz
1377296ef4 Translating to spanish entities errors and settings 2017-04-06 09:35:24 +02:00
Dan Brown
01cb22af37 Added tag searches and advanced filters to new search 2017-03-27 18:05:34 +01:00
Dan Brown
331305333d Added search term parsing and exact term matches 2017-03-27 11:57:33 +01:00
Dan Brown
0651eae7ec Improve efficiency of single entity search indexing 2017-03-26 19:34:53 +01:00
Dan Brown
1552417598 Developed basic search queries.
Updated search & permission regen commands with ability to specify
database.
2017-03-26 19:24:57 +01:00
Abijeet Patro
4e71a5a47b Merge pull request #8 from BookStackApp/master
Getting the latest changes.
2017-03-25 23:56:05 +05:30
Dan Brown
a74a8ee483 Updated version for v0.15.3 2017-03-23 22:22:16 +00:00
Dan Brown
7fa5405cb7 Merge branch 'master' into release 2017-03-23 22:21:04 +00:00
Dan Brown
cc0ce7c630 Fixed bug preventing page revision restore
Added regression tests to cover.
Fixes #341
2017-03-23 22:19:14 +00:00
Dan Brown
070d4aeb6c Started implementation of new search system 2017-03-19 12:48:44 +00:00
Dan Brown
668ce26269 Fixed back button behaviour on books edit
As reported in #339
2017-03-19 08:32:04 +00:00
Dan Brown
6725ddcc41 Updated version for release v0.15.2 2017-03-05 15:50:52 +00:00
Dan Brown
bce941db3f Merge branch 'master' into release 2017-03-05 15:49:47 +00:00
Dan Brown
4499ae84bb Made fixes to es languge files and users page
Fixed PHP formatting error in ES lang file and added tests to cover.
Made user edit page more responsive on smaller devices.
Fixed 'cancel' button on profile screen when the user does not have
permission to manage users.
2017-03-05 15:34:54 +00:00
Dan Brown
d4e790d3cf Added lang tests and update export text keys 2017-03-05 15:10:06 +00:00
Dan Brown
9b35aa42a2 Fixed spanish encoding, Added new lang to settings 2017-03-05 14:43:43 +00:00
Dan Brown
7163997367 Merge pull request #334 from diegoseso/master
First spanish translation effort
2017-03-05 14:18:55 +00:00
Dan Brown
2385a6c29b Merge pull request #325 from arietimmerman/dutchbranch
Dutch Language Files
2017-03-05 14:15:47 +00:00
Dan Brown
36173eb47d Removed extension from translation script link
Also fixed bug causing EN translation backup to not be passed
to javascript translation system.

Closes #328
2017-03-05 14:10:55 +00:00
Diego Jose Sosa Diaz
f7645824d9 First spanish translation effort 2017-03-03 00:21:33 +01:00
Dan Brown
6d926048ec Updated to version v0.15.1 2017-02-27 16:59:10 +00:00
Dan Brown
5335c973b4 Merge branch 'master' into release 2017-02-27 16:58:20 +00:00
Dan Brown
bcafa73faf Set composer to clean bootstrap/cache before an update 2017-02-27 16:55:40 +00:00
Dan Brown
15c3e5c96e Updated assets for release v0.15 2017-02-27 14:58:02 +00:00
Dan Brown
a5d5904969 Merge branch 'master' into release 2017-02-27 14:57:38 +00:00
Dan Brown
e3eefba745 Fixed export testing and updated travis settings 2017-02-26 21:39:15 +00:00
Dan Brown
a90f564980 Made LDAP email attribute configurable via .env
Closes #306
2017-02-26 14:51:49 +00:00
Dan Brown
253132afdf Added chapter export options
Closes #177
2017-02-26 14:25:02 +00:00
Dan Brown
eded8abded Added book export and created export tests to cover
In reference to #177
2017-02-26 13:26:51 +00:00
Dan Brown
0abed1afe5 Added clear activity/revision commands. Cleaned commands.
Added testing to cover each command.
Removed example laravel inspire command.
Standardised command names to be behind 'bookstack' naming.
In reference to #320.
2017-02-26 09:16:24 +00:00
Dan Brown
22077d4181 Updated DOMPDF to latest version 2017-02-25 14:59:56 +00:00
Dan Brown
b0e849f413 Added checkbox sytax parsing to markdown lists
Closes #319
2017-02-25 13:16:26 +00:00
Dan Brown
af3c0e43a5 Prevented custom HTML being inserted on settings page
Gives option for fixing if badly formatted HTML is inserted.
Closes #310
2017-02-25 12:41:32 +00:00
Dan Brown
387047f262 Fixed inaccessible revisions, added regression tests
Fixes #309
2017-02-25 12:29:01 +00:00
Dan Brown
4a2a539c08 Merge pull request #295 from ReeseSebastian/master
Updated and improved german translation
2017-02-23 19:19:21 +00:00
Arie Timmerman
4214fcd2fa Updated Dutch language files 2017-02-11 11:58:45 +01:00
Arie Timmerman
b2b64fb853 Started with Dutch translation 2017-02-10 22:10:41 +01:00
Dan Brown
a6128a1df1 Merge bugfixes from branch 'v0.14' 2017-02-05 21:24:15 +00:00
Dan Brown
598758b991 Updated version for v0.14.3 2017-02-05 21:23:27 +00:00
Dan Brown
9926e23bc8 Merge branch 'v0.14' into release 2017-02-05 21:21:54 +00:00
Dan Brown
6638ee47d3 Fixed entities wrongly visible on 404
Also ensured header state as expected on 404.
In reference to BookStackApp/website#9
2017-02-05 21:19:29 +00:00
Dan Brown
65899a3e91 Prevented settings being overfetched from db/cache 2017-02-05 18:57:57 +00:00
Dan Brown
86625a7642 Neatened up social login/register buttons 2017-02-05 15:28:53 +00:00
Dan Brown
ee495450cc Improved multi-line callout rendering
Closes #300
2017-02-05 14:47:26 +00:00
Dan Brown
d369d315a7 Fixed non-browserkit testcase and seeder issues 2017-02-05 14:37:50 +00:00
Dan Brown
7c9937e924 Converted sort tests to non browserkit testing
Added testing to cover book sort endpoint.
Closes #283
2017-02-05 14:20:59 +00:00
Abijeet
410e967eb1 Merge branch 'master' of https://github.com/Abijeet/BookStack 2017-02-05 16:46:32 +05:30
Abijeet Patro
388f2f40dc Merge pull request #7 from BookStackApp/master
Getting the latest.
2017-02-05 16:46:03 +05:30
Dan Brown
33a2999a57 Namespaced tests to align with new laravel default 2017-02-04 11:58:42 +00:00
Dan Brown
076693efc9 Added facebook, slack & twitter sign in options.
Also added icon svg blade helper.
Closes #125. Starts #213.
Requires documentation.
2017-02-04 11:01:49 +00:00
Sebastian Reese
54d1fcde5b Merge branch 'master' of https://github.com/BookStackApp/BookStack 2017-02-02 00:43:54 +01:00
Sebastian Reese
1c656d6556 Updated and improved german translation 2017-02-02 00:43:24 +01:00
Dan Brown
2431ce9f86 Merge branch 'v0.14' 2017-02-01 22:28:38 +00:00
Dan Brown
5d3264bc63 Updated assets for release v0.14.2 2017-02-01 22:27:04 +00:00
Dan Brown
d71f819f95 Merge branch 'v0.14' into release 2017-02-01 22:22:38 +00:00
Dan Brown
80f844139c Fixed missing subscript styling
Closes #284
2017-02-01 22:20:44 +00:00
Dan Brown
9eecaea31a Attempt to fix bookchildren and user getThumb
Hopefully Fixes #292 and #294 and #287
2017-02-01 22:16:32 +00:00
Dan Brown
3ccfa0e7fc Fixed readme badge links & added contributing block 2017-01-30 19:31:24 +00:00
Abijeet
148350009c #47 Adds comment permission to each role. 2017-01-29 14:25:20 +05:30
Abijeet
70991fc1e5 Merge branch 'master' of https://github.com/Abijeet/BookStack 2017-01-29 09:35:46 +05:30
Abijeet Patro
e5c4e0ac86 Merge pull request #6 from BookStackApp/master
Getting the latest
2017-01-29 09:35:21 +05:30
Dan Brown
6669998c10 Upgraded to Laravel 5.4 2017-01-25 19:35:40 +00:00
Dan Brown
ee13509760 Updated version number 2017-01-23 22:28:31 +00:00
Dan Brown
82d7bb1f32 Merge branch 'master' into release 2017-01-23 22:28:02 +00:00
Dan Brown
492e2f173e Fixed error causing permissions to be deleted on book sort
Closes #282
2017-01-23 22:27:11 +00:00
Dan Brown
cdfda508d8 Updated assets for release v0.14 2017-01-22 12:36:10 +00:00
Dan Brown
da941e584f Merge branch 'master' into release ready for v0.14 2017-01-22 12:31:27 +00:00
Dan Brown
380f0f2042 Prevented a missing avatar from crashing the application 2017-01-22 12:19:50 +00:00
Dan Brown
33d4844f17 Fixed role 'manage own permissions' permission 2017-01-22 12:16:02 +00:00
Dan Brown
989de47f22 Fixed hidden book children for admins on upgrade 2017-01-22 12:02:30 +00:00
Dan Brown
8f19231ed5 Added options to use database cache & sessions 2017-01-21 16:39:50 +00:00
Dan Brown
5c60f27a7d Merge branch 'page_transclusion'
Closes #123
2017-01-21 16:17:33 +00:00
Dan Brown
2d4034f3b7 Added transclusion tests and fixed other tests 2017-01-21 16:16:27 +00:00
Dan Brown
56d58ad8e5 Updated pointer to be able to show includes
Also fixed pointer copying on flash-blocked browsers
2017-01-21 14:58:03 +00:00
Dan Brown
a4f6bc63f0 Updated page include logic to use blade-style tags
It will also snippets of a page if and id is provided in a tag
2017-01-21 13:53:00 +00:00
Dan Brown
26da81a3b0 Added pt_BR to language dropdown and renamed folder 2017-01-18 20:04:29 +00:00
Dan Brown
ec9410b510 Merge pull request #279 from NakaharaL/master
Brazilian Portuguese Localization
2017-01-18 19:48:53 +00:00
NakaharaL
a0035e4de2 Brazilian Portuguese Localization
Translated files for messages from English to Brazilian Portuguese
2017-01-18 08:29:18 -03:00
Dan Brown
e4e3b25c22 Started page transclusion system 2017-01-16 21:24:48 +00:00
Dan Brown
d8c5f72258 Updated issue template and added TinyMCE autolinking
Closes #267
2017-01-16 19:33:29 +00:00
Dan Brown
d67ad47b2c Merge pull request #274 from GeneralMediaCH/feature/translations
French translations
2017-01-16 19:23:30 +00:00
Dan Brown
b08e49b59d Updated CI to not use github token 2017-01-16 19:14:58 +00:00
sirgix
d3dc73ca06 remove trailing spaces 2017-01-16 15:34:34 +01:00
sirgix
d5ea15e6dd fix typo 2017-01-16 14:51:45 +01:00
sirgix
5d45286646 french translation 2017-01-16 14:51:33 +01:00
Dan Brown
dabf149411 Added user setting system and added user-lang option
Supports #115
2017-01-15 16:27:24 +00:00
Dan Brown
ee5ded6e1e Updated permission bookChildrenQuery to use QueryBuilder 2017-01-15 15:00:29 +00:00
Dan Brown
598b07b53d Updated LDAP to allow protocol to be specified
As per details by fredericmohr in #236
2017-01-14 17:55:09 +00:00
Dan Brown
e211f31370 Merge branch 'patch-2' of git://github.com/fredericmohr/BookStack into fredericmohr-patch-2 2017-01-14 17:30:49 +00:00
Dan Brown
0bcf608e0b Fixed page navigation with special chars in id
Also made so it smooth-scrolls and uses app theme color.
Fixes #254
2017-01-14 16:36:12 +00:00
Dan Brown
969ad8911c Updated page nav to hide when empty 2017-01-14 15:34:52 +00:00
Abijeet
397db04428 Added comments controller, model, repo, and the database schema. Modified existing Page model to associate with comments. 2017-01-13 21:45:48 +05:30
Dan Brown
581c382f65 Fixed image delete permission issue
Also fixed missing translations and wrote tests to cover issue.
Fixes #258
2017-01-08 19:19:30 +00:00
Dan Brown
f7f86ff821 Merge branch 'master' of github.com:BookStackApp/BookStack 2017-01-08 18:43:33 +00:00
Dan Brown
212cd710aa Fixed default empty app settings effecting blank app color
Fixes #265
2017-01-08 18:42:46 +00:00
Dan Brown
33c44d3c0f Merge pull request #263 from Abijeet/typo-fix
Typo fix
2017-01-08 13:55:44 +00:00
Dan Brown
0faa130cfd Fixed offset code blocks when editing in markdown.
Fixes #264
2017-01-08 13:31:53 +00:00
Abijeet Patro
af76580b98 Fixes typo causing the message not to be displayed 2017-01-03 22:18:13 +05:30
Abijeet Patro
b526d172d6 Merge pull request #5 from BookStackApp/master
Getting the latest from Bookstack to push the typo-fix
2017-01-03 22:16:39 +05:30
Abijeet Patro
cd6572b61a Merge pull request #3 from BookStackApp/master
Getting the latest
2017-01-03 07:54:28 +05:30
Dan Brown
f2917fc462 Added tests to cover social login actions
Closes #244
2017-01-02 14:56:58 +00:00
Dan Brown
7c8c4c2a05 Normalised page nav header inset when only small headers are used 2017-01-02 12:13:03 +00:00
Dan Brown
3595ac2551 Merge pull request #262 from BookStackApp/entity_repo_refactor
Entity repo refactor
2017-01-02 11:12:44 +00:00
Dan Brown
8453191dfb Finished refactor of entity repos
Removed entity-specific repos and standardised
the majority of repo calls to be applicable to
all entity types
2017-01-02 11:07:27 +00:00
Dan Brown
65796cfc7b Rewrote book children query 2017-01-01 21:21:11 +00:00
Dan Brown
bab27462ab Fixed issue where default user was over-fetched 2017-01-01 17:33:06 +00:00
Dan Brown
241278226f Refactored search and slug repo components 2017-01-01 16:57:47 +00:00
Dan Brown
7f9de2c8ab Started refactor to merge entity repos 2017-01-01 16:05:44 +00:00
Dan Brown
f91f33c236 Updated readme attribution and npm scripts 2017-01-01 12:51:23 +00:00
Dan Brown
3f0ef57d31 Added wkhtmltopdf support and done some style tweaks
Closes #248
2017-01-01 12:20:30 +00:00
Dan Brown
0eb90cb3b6 Fixed carbon locale setting 2016-12-31 14:38:04 +00:00
Dan Brown
9fe158b78a Merge pull request #255 from olexus/master
Add Carbon localization support
2016-12-31 14:36:01 +00:00
Dan Brown
b14222dabd Merge pull request #234 from BookStackApp/translations
Setup for translations
2016-12-31 14:33:25 +00:00
Dan Brown
a24f3d7d47 Merge branch 'master' into translations 2016-12-31 14:32:52 +00:00
Dan Brown
c9700e38e2 Created solution for JS translations
Also tidied up existing components and JS
2016-12-31 14:27:40 +00:00
Dan Brown
05316c90ba converted image picker to blade-based component
Also updated some other JS translations
2016-12-24 15:21:19 +00:00
Dan Brown
242dc21876 Converted toggle switch into a blade/jquery template 2016-12-22 19:41:32 +00:00
Dan Brown
08c4b9ac7c Standardised JS vars and imports/exports 2016-12-19 19:16:31 +00:00
olexus
f30f4579e9 Add Carbon localization support 2016-12-19 11:05:06 +03:00
Dan Brown
573357a08c Extracted text from logic files 2016-12-04 16:51:39 +00:00
Dan Brown
0775cd09a1 Extracted text for remaining views 2016-12-04 14:08:04 +00:00
Dan Brown
96075dee7b Extracted text from page views & standardised breadcrumbs 2016-12-03 18:35:40 +00:00
Dan Brown
066adf3cea Moved text for errors and form views.
Updated 404 page with additional links
2016-12-03 13:31:54 +00:00
Abijeet
581881d0ca Merging gitignore. 2016-11-29 00:24:15 +05:30
Abijeet Patro
d2efc2f47f Merge pull request #2 from BookStackApp/master
Getting the latest
2016-11-29 00:23:30 +05:30
Dan Brown
65874d7b96 Updated assets for release v0.13.1 2016-11-27 19:42:33 +00:00
Dan Brown
ac9b8f405c Merge fixes from master for release v0.13.1 2016-11-27 19:41:12 +00:00
Dan Brown
286f9b0c7d Moved page tags to sidebar
Fixed #238
2016-11-27 19:37:57 +00:00
Dan Brown
c403d05755 Fixed social login routes
Fixes #239
2016-11-27 19:11:15 +00:00
Dan Brown
57dc53ceff Extracted text from book & chapter views 2016-11-17 13:33:07 +00:00
Frederic Mohr
340d3f833b Update Ldap.php
This is a very crude workaround, a better solution is explained in the comments I added.
2016-11-15 11:10:12 +01:00
Dan Brown
694a9459c1 Moved text from auth views into lang files 2016-11-13 16:34:28 +00:00
Dan Brown
8d1419a12e Update assets and version for release v0.13 2016-11-13 12:29:52 +00:00
Dan Brown
04f7a7d301 Merge branch 'master' into release 2016-11-13 12:26:56 +00:00
Dan Brown
0fb1fc87c8 Enabled utf8 slugs
Prevents slug change when using only non-ascii chars
Allows use of more localised urls.

Closes #233
2016-11-12 17:16:52 +00:00
Dan Brown
d3c7aada89 Fixed attachments on draft pages 2016-11-12 14:21:54 +00:00
Dan Brown
e639600ba5 Renamed files to attachments 2016-11-12 14:12:26 +00:00
Dan Brown
d439fb459b Made language configurable via .env file 2016-11-12 13:30:00 +00:00
Dan Brown
9d8a176182 Merge branch 'localization-de' of git://github.com/robertlandes/BookStack into robertlandes-localization-de 2016-11-12 13:27:37 +00:00
Dan Brown
600055bc73 Fixed tag searches and added tag search regression test
Fixes #222
2016-11-12 13:21:16 +00:00
Robert Landes
e691b401c8 Added initial translation into German (formal)
This translation is based on the current english language files. It is a
transaltion into German (formal) language.  A language selector on the
Settings page would be a nice addition.
2016-11-12 14:16:07 +01:00
Dan Brown
672b15d36c Fixed attachment base-url usage and non-existant images
Images now self-delete if the original file does not exist.
Prevents simply getting non-fixable errors.

Also cleaned some JS.
2016-11-12 12:41:34 +00:00
Dan Brown
ac80723058 Merge fixes from branch 'v0.12' 2016-11-12 11:40:54 +00:00
Dan Brown
ab468bac3c Updated build and versioning system
Added versioning file instead of using git tags
(Step towards removing git as a dependancy in the future)

Updated gulpfile to fit with verisioning system and cleaned
up node dependancies.

Fixes #108
2016-10-30 17:44:00 +00:00
Dan Brown
c10d2a1493 Updated assets for release v0.12.2 2016-10-30 13:19:19 +00:00
Dan Brown
97bbf79ffd Merge branch 'v0.12' into release 2016-10-30 13:18:23 +00:00
Dan Brown
2af0021c2b Fixed image tests after amends to url system 2016-10-30 12:58:01 +00:00
Dan Brown
0f2eaccb39 Added quick test to cover hypen breakage 2016-10-30 12:15:11 +00:00
Dan Brown
b251671e3f Amended search to not break on non-alpha-num chars
And also fixed exact term matches that contain non-alpha-num chars
Fixes #212
2016-10-30 12:08:22 +00:00
Dan Brown
c4eed37d8e Added custom head content into public pages
Closes #211
2016-10-30 11:46:23 +00:00
Dan Brown
8b43b91057 Improved password reset flow with notifications.
Also added links to sign-in/register.
Fixed links in emails sent out.

Fixes #210 and #218.
2016-10-30 11:36:57 +00:00
Dan Brown
91fe7f0bee Fixed PDF export table width
Closes #203

Signed-off-by: Dan Brown <ssddanbrown@googlemail.com>
2016-10-30 10:41:18 +00:00
Dan Brown
5cfb7b8de4 Altered 'ol' element padding to not clip numbering
Allows usage to 3-digits now
Closes #204
2016-10-30 10:23:49 +00:00
Dan Brown
6329a1842a Fixed issue with callouts overflowing page tags
Closes #179
2016-10-30 10:19:45 +00:00
Dan Brown
a6c6c6e300 Merge pull request #205 from ssddanbrown/attachments
Implementation of File Attachments
2016-10-23 18:01:20 +01:00
Dan Brown
30458405ce Page Attachments - Improved UI, Now initially complete
Closes #62
2016-10-23 17:55:48 +01:00
Dan Brown
91220239e5 Added in attachment tests 2016-10-23 15:25:04 +01:00
Dan Brown
7ee695d74a File upload deletion complete & added extension handling
Also fixed issue with file editing on JS side
2016-10-23 13:36:45 +01:00
Dan Brown
867fc8be64 Added basic attachment editing functionality 2016-10-11 20:39:11 +01:00
Dan Brown
89509b487a Added attachment creation from link/name 2016-10-10 21:13:18 +01:00
Dan Brown
ac0b29fb6d Added view, deletion and permissions for files 2016-10-10 20:30:27 +01:00
Dan Brown
673c74ddfc Started work on attachments
Created base models and started user-facing controls.
2016-10-09 18:58:22 +01:00
Dan Brown
3b7d223b0c Updated and added tests for new default user system
Closes #138
2016-09-29 17:07:58 +01:00
Dan Brown
b662670efc Prevented guest users creating draft pages. 2016-09-29 15:56:57 +01:00
Dan Brown
771626b6ec Started work on making the public role/user configurable
Create a new 'public' guest user and made the public
role visible on role setting screens.
2016-09-29 12:43:46 +01:00
Dan Brown
f15cc5bdfa Separated revision preview and diff & fixed chosen diff html
Closes #8
2016-09-29 10:10:46 +01:00
Dan Brown
fff5bbcee4 Merge branch 'diff' of git://github.com/younes0/BookStack into younes0-diff 2016-09-29 09:32:40 +01:00
Dan Brown
42d8e9e5bd Improved numeric term search capabilities
Prevented a quoted term also being added to fuzzy searches
and also added check to see if the term is numeric to check if
an exact match is required.

Closes #200
2016-09-29 09:13:15 +01:00
Abijeet Patro
4cc73657a1 Merge pull request #1 from ssddanbrown/master
Getting the latest of BookStack.
2016-09-25 13:29:22 +05:30
Dan Brown
da10c50945 Added app name header display setting + extracted setting text
Closes #194
2016-09-22 18:53:22 +01:00
Dan Brown
24523cf31d Added page autosave request failure notification
Closes #192
2016-09-18 15:10:27 +01:00
Dan Brown
1d681e53e4 Added page navigation and tweaked header styles
Changed header selection in editor to be more descriptive and
to provide a wider range of styles.

Closes #68
2016-09-18 14:49:36 +01:00
Dan Brown
e0235fda8b Made registration gravatar/email requests fail gracefully
* Extracted any email confirmation text into langs.
* Added new notification on confirmation email send fail.

Closes #187
2016-09-17 21:33:55 +01:00
Dan Brown
9dc9724e15 Laravel 5.3 upgrade (#189)
* Started move to laravel 5.3

* Started updating login & registration flows for laravel 5.3 update

* Updated app emails to notification system

* Fixed registations bugs and removed email confirmation model

* Fixed large portion of laravel post-upgrade issues

* Fixed and tested LDAP process
2016-09-17 18:22:04 +01:00
Dan Brown
f7b01ae53d Updated assets for release v0.12.1 2016-09-06 20:50:15 +01:00
Dan Brown
d704e1dbba Merge branch 'master' into release 2016-09-06 20:49:15 +01:00
Dan Brown
393f6047f2 Updated table styling to ideally by more predictable
Set styles to be fixed and not cut text.
2016-09-06 20:47:34 +01:00
Dan Brown
d94fc5b694 Fixed table cell p tag spacing and de-duped editor shortcuts
Also changed shortcuts to be better compatible with OSX by using the
command key.
2016-09-06 19:50:47 +01:00
Dan Brown
f06770d91d Updated TinyMCE version from 4.3.7 to 4.4.3 2016-09-06 19:29:09 +01:00
Dan Brown
ef2ff5e093 Updated assets for release v0.12 2016-09-05 19:49:42 +01:00
Dan Brown
7caed3b0db Merge branch 'master' into release 2016-09-05 19:35:21 +01:00
Dan Brown
11960d9d3a Cleaned page form JS & spaced tag box
As per #174
2016-09-05 19:33:14 +01:00
Dan Brown
bbd8fff021 Fixed bad image base-urls and forced tinyMCE to use absolute
Also ensured image file existance is checked during base64 conversion
during exports.
Closes #171.
2016-09-03 19:24:58 +01:00
Dan Brown
ec17bd8608 Improved Exception handling, Removed npm requirement for testing 2016-09-03 12:08:58 +01:00
Dan Brown
0dbb8babee Merge branch 'page_link_selector' 2016-09-03 10:40:27 +01:00
Dan Brown
b14e9fc619 Fixed some cross browser flexbox popup issues
Set min height for poor IE & safari flexbox support.
Fixes #105.
2016-09-03 10:32:14 +01:00
Dan Brown
63c6d3478d Added image paste and drop to markdown editor
Only currently tested in chrome.
Closes #128
2016-09-02 21:18:48 +01:00
Dan Brown
781f0e7887 Added draft save indicator and fixed notification positions 2016-09-02 19:26:12 +01:00
Dan Brown
23e014cb25 Added link selector to markdown editor 2016-09-02 18:54:26 +01:00
Dan Brown
5b64358ef1 Added link selector interface to WYSIWYG editor 2016-09-01 20:36:22 +01:00
Dan Brown
56df64063d Updated popup design and started integrating link selector
Extracted design and base styles from image manager into generic popup
classes that can be used by other app components such as the new
popup Book/Chapter/Page link selector
2016-08-30 20:05:59 +01:00
Dan Brown
3f81eba13b Updated travis testing to work with new helper configuration (#175)
* Updated travis to call phpunit globally rather then booting application first
2016-08-27 11:27:23 +01:00
Dan Brown
7973412c29 Improved sort efficiency by a factor of 10
Fixes #145
2016-08-26 20:20:58 +01:00
Dan Brown
f83de5f834 Fixed single word quoted search terms
Fixes #170
2016-08-25 17:17:26 +01:00
Dan Brown
f2ceba978a Removed animation from page content.
Prevents issues with browsers that do not support
animaton-fill-direction.
Fixes #173.
2016-08-25 16:47:25 +01:00
Dan Brown
96c074bb56 Merge fixes from branch 'v0.11' 2016-08-21 15:02:37 +01:00
Dan Brown
45641d0754 Updated assets for release v0.11.2 2016-08-21 14:56:29 +01:00
Dan Brown
4b1d08ba99 Merge branch 'v0.11' into release 2016-08-21 14:55:11 +01:00
Dan Brown
f8a299caee Fixed login 'intended' redirect for custom urls.
Also changed social account detach wording.
2016-08-21 14:49:40 +01:00
Chris
437dce7756 Applied baseUrl to login redirect 2016-08-21 13:48:56 +01:00
Dan Brown
f8ad820281 Merge pull request #166 from poppahorse/patch-1
Applied baseUrl to login redirect
2016-08-21 13:44:36 +01:00
Dan Brown
757f16a3b5 Improved page tag box structure and fixed footer in PDF export.
Fixes #162.
2016-08-21 13:35:22 +01:00
Chris
632ecc668f Applied baseUrl to login redirect 2016-08-15 15:07:45 +01:00
Dan Brown
92d393537c Merge branch 'v0.11' 2016-08-14 13:09:44 +01:00
Dan Brown
160fa99ba4 Updated assets for release v0.11.1 2016-08-14 12:40:55 +01:00
Dan Brown
d2a5ab49ed Merge branch 'v0.11' into release 2016-08-14 12:37:48 +01:00
Dan Brown
43d9d2eba7 Updated all application urls to allow path prefix.
Allows BookStack to be installed at a non-root location on a domain.
Closes #40.
2016-08-14 12:29:35 +01:00
Dan Brown
baa260a03d Started work on subdirectory support 2016-08-13 17:56:25 +01:00
Dan Brown
b157a9927a Fixed tag section and editor safari rendering.
Fixes #152.
2016-08-13 14:54:23 +01:00
Dan Brown
b246a67e8a Fixed double brace issues in both editors.
Double braces were being parsed by angular in both the WYSIWYG and markdown editor.
Fixes #150 and fixes #155.
2016-08-13 14:18:31 +01:00
Dan Brown
2d958e88bf Fixed entities created with blank slugs.
Fixes #156.
2016-08-13 13:53:04 +01:00
Dan Brown
07c7d5af17 Updated elixr and fixed table th element borders
Fixes #164.
2016-08-13 10:02:54 +01:00
Dan Brown
42976ca48c Fixed revision-based redirect on new pages 2016-07-26 18:16:40 +01:00
Dan Brown
d05e85efa9 Fixed back-to-top button on firefox. Fixes #153. 2016-07-26 18:03:10 +01:00
Dan Brown
547e117760 Changed issue template to use md extension 2016-07-26 17:46:09 +01:00
Nick Walke
50a5d3c546 Added issue template 2016-07-21 10:04:06 -05:00
Dan Brown
3fd82200cc Added WYSIWYG editor shortcuts 2016-07-12 21:11:48 +01:00
Dan Brown
7215392784 Changed when revisions are saved and update changelog input
Revisions are now saved when te page content is originally saved whereas before they were saved on the next update to the page.
2016-07-10 12:12:52 +01:00
Younès EL BIACHE
c279c6e2af replace GPL diff lib with MIT lib
replace gpl lib with mit lib
2016-07-10 12:01:05 +02:00
Dan Brown
8a9a8dfae5 Merge branch 'summary' of git://github.com/younes0/BookStack into younes0-summary 2016-07-10 10:42:47 +01:00
Dan Brown
c44314def3 Added check for s3 bucket name to choose shortest url 2016-07-10 10:28:05 +01:00
Dan Brown
8b899a9cf0 Merge branch 'patch-1' of git://github.com/younes0/BookStack into younes0-patch-1 2016-07-10 10:15:38 +01:00
Dan Brown
0ebdfa4825 Merge pull request #144 from younes0/patch-2
set uploaded images visibility public (relevant for S3 storage)
2016-07-10 10:14:19 +01:00
Younes El Biache
32a06f119b set uploaded files public visibliity (relevant for S3 storage) 2016-07-09 15:26:53 +02:00
Younes El Biache
ec30864ce5 shorter amazon S3 url 2016-07-09 14:33:37 +02:00
Younès EL BIACHE
6bc72e157a edit summary 2016-07-07 20:53:43 +02:00
Younès EL BIACHE
9537e2ae95 html diff in revision view 2016-07-07 19:54:40 +02:00
Dan Brown
c6404d8917 Updated assets for release v0.11 2016-07-03 10:56:16 +01:00
Dan Brown
7113807f12 Merge branch 'master' into release 2016-07-03 10:52:04 +01:00
Dan Brown
10418323ef Prevented shift-tab from selecting a tag 2016-07-03 10:47:20 +01:00
Dan Brown
a11d5245ec Cut down homepage queries a little 2016-07-03 10:42:13 +01:00
Dan Brown
565033e0d4 Fixed bug which hid entities and fixed new chapter priority 2016-07-03 10:31:20 +01:00
Dan Brown
c25ef18900 Made list default messages a little nicer 2016-07-03 10:12:12 +01:00
Dan Brown
b0a63ba0cc Tightened and cleaned some list styles 2016-07-03 09:58:45 +01:00
Dan Brown
7b6c88f17c Fixed error on image deletion
Also Added tests to cover image upload and deletion.
Fixes #136.
2016-07-01 20:13:30 +01:00
Dan Brown
361ba8b244 Made stick book navigation recalc on window resize 2016-06-25 15:46:31 +01:00
Dan Brown
9baa96d41c Added chapter move actions. Closes #86 2016-06-25 15:31:38 +01:00
Dan Brown
e584b4926f Travis CI updates (#130)
* Updated travis config to use mysql

* Added cache for composer
2016-06-19 19:30:43 +01:00
Dan Brown
bcd9c2044e Added WYSIWYG editor callouts 2016-06-19 19:02:53 +01:00
Dan Brown
d582e76fed Fixed theme color elements not showing on new instance
Also cleaned notification session access
2016-06-12 13:37:15 +01:00
Dan Brown
991dd8a558 Merged branch add_page_move into master
References #86
2016-06-12 12:49:08 +01:00
Dan Brown
bc49784797 Added tests to cover page_move features 2016-06-12 12:48:06 +01:00
Dan Brown
7f99903fdb Finished off page move functionality 2016-06-12 12:14:14 +01:00
Dan Brown
97d011ac8e Started work on page move view and entity selector 2016-06-11 21:04:18 +01:00
Dan Brown
61596a8e21 Prevent page redirect on draft saving
Only an issue when using the non 'mysqlnd' extension.
Fixes #120
2016-06-10 19:45:53 +01:00
Dan Brown
44e337cef6 Merged branch rovox-master into master 2016-06-04 16:33:23 +01:00
Dan Brown
1bec3eaa1e Added checks to use MyISAM if MySQL 5.5 is found 2016-06-04 16:32:57 +01:00
Dan Brown
5942d796b5 Merge branch 'master' of git://github.com/rovox/BookStack into rovox-master 2016-06-04 15:43:08 +01:00
Dan Brown
eec9c05518 Added tag autosuggestion when no input provided
Shows the most popular tag names/values.
As requested on #121
2016-06-04 15:37:28 +01:00
Dan Brown
246d1621f5 Limited tag value autosuggestions based on tag name
As requested on #121
2016-06-04 14:54:31 +01:00
Dan Brown
6c1e06bf86 Updated License file 2016-06-03 18:52:49 +01:00
Dan Brown
3c1e165134 Add in LICENSE file 2016-06-03 18:51:47 +01:00
Dan Brown
80d1c594cc Added licence and release badges 2016-06-03 18:50:20 +01:00
Dan Brown
947db95d16 Fixed error with similar activity filtering 2016-05-28 14:02:48 +01:00
Dan Brown
5b9362ab0b Added (Ctrl+s) draft force save 2016-05-28 13:51:07 +01:00
Dan Brown
f602b088ac Added basic markdown scroll syncing 2016-05-28 13:24:07 +01:00
robert
4acf0c4ee0 Making sure MyISAM is set for the tables that need it for new installtions that are using mariadb. 2016-05-25 23:52:43 +02:00
Dan Brown
be711215e8 Updated assets for release v0.10 2016-05-22 15:12:47 +01:00
Dan Brown
7e3b404240 Merge branch 'master' into release for version v0.10 2016-05-22 15:11:50 +01:00
Dan Brown
9d3f329bc9 Fixed missing column drop on migration rollback 2016-05-22 14:56:26 +01:00
Dan Brown
bd00a03e7b Added support for custom content in HTML head 2016-05-22 11:01:21 +01:00
Dan Brown
be517de7dc Added pagination, sorting & searching to users list
As requested on #113
2016-05-22 10:44:31 +01:00
Dan Brown
23ab1f0c81 Standardised delete aesthetics regarding roles and users
Also added user delete success message.
Fixes #114
2016-05-22 09:23:41 +01:00
Dan Brown
49621e7b15 Fixed flexbox overflow scrolling issues in firefox
Fixes #111
2016-05-22 09:08:37 +01:00
Dan Brown
1ab6f017c9 Made user names clickable at the bottom of assets
Fixes #117
2016-05-22 08:48:40 +01:00
Dan Brown
0b364fd72f Merge pull request #110 from ssddanbrown/page_attributes
Attribute System. Closes #48.
2016-05-15 20:24:57 +01:00
Dan Brown
e80ae76856 Added auto-suggestions to tag names and values 2016-05-15 20:12:53 +01:00
Dan Brown
eebad3e2a0 Fixed tags not being saved on new pages 2016-05-15 14:17:25 +01:00
Dan Brown
db2af47286 Revert travis CI changes 2016-05-15 13:59:16 +01:00
Dan Brown
7ad28aeab4 Updated travis file to hopefully fix CI errors 2016-05-15 13:53:42 +01:00
Dan Brown
8d80e7311c Added tag searching to search interfaces 2016-05-15 13:41:18 +01:00
Dan Brown
7932069535 Added tags to page display and simplified editing flow
Tags now save with the page content data
2016-05-15 10:39:17 +01:00
Dan Brown
78564ec61d Cleaned up tag edit interface 2016-05-14 20:02:00 +01:00
Dan Brown
b80184cd93 Renamed attribute to tags & continued interface
Also fixed page create route broken in last commit
2016-05-13 21:20:21 +01:00
Dan Brown
1fa079b466 Started the page attributes interface 2016-05-12 23:12:05 +01:00
Dan Brown
fcfb9470c9 Added further attribute endpoints and added tests 2016-05-07 14:29:43 +01:00
Dan Brown
c99653f0f2 Fixed bad refactor in the permission service 2016-05-06 20:44:07 +01:00
Dan Brown
5080b4996e Started base work on attribute system 2016-05-06 20:33:08 +01:00
Dan Brown
1903422113 Merge branch 'v0.9' 2016-05-03 21:30:55 +01:00
Dan Brown
e86901ca20 Updated assets for release v0.9.3 2016-05-03 21:13:02 +01:00
Dan Brown
bdfa61c8b2 Merge branch 'v0.9' into release 2016-05-03 21:11:01 +01:00
Dan Brown
4e8722661f Added bookstack version in settings
Gets the app version via git. Closes #99
2016-05-03 21:10:05 +01:00
Dan Brown
6f9d2939e7 Updated travisCI to help fix node error 2016-05-02 13:14:35 +01:00
Dan Brown
3234510d31 Merged ci_testing into master 2016-05-02 13:01:46 +01:00
Dan Brown
a40af08018 Changed mariadb version 2016-05-02 12:50:17 +01:00
Dan Brown
223a6b546c Added dependancy caching and travis badge 2016-05-02 12:37:58 +01:00
Dan Brown
0d5da2d9d4 Added travis CI file 2016-05-02 12:26:09 +01:00
Dan Brown
37337b8b1d Added codeship CI badge 2016-05-02 12:02:23 +01:00
Dan Brown
ffcc058927 Merge pull request #106 from ssddanbrown/ci_testing
Ci testing
2016-05-02 11:58:12 +01:00
Dan Brown
3a1cda5802 Updated ldap so extension not required in testing 2016-05-02 11:38:07 +01:00
Dan Brown
5c1015d6fc Updated social testing compatibility 2016-05-02 11:26:47 +01:00
Dan Brown
3385ec3f78 Updated database config to be codeship compatible 2016-05-02 10:35:42 +01:00
Dan Brown
1b46c19849 Merge pull request #103 from ssddanbrown/add_role_view_permissions
Overhauled permission system and stripped migrations of most app code to reduce future breakages. Closes #101.
2016-05-01 22:47:22 +01:00
Dan Brown
75a4fc905b Major permission naming refactor and database migration cleanup 2016-05-01 22:33:56 +01:00
Dan Brown
05666efda9 Added hidden public role to fit with new permissions system 2016-05-01 19:36:53 +01:00
Dan Brown
59367b3417 Improved permission regen performance by factor of 4
Worked around slower eloquent access to speed up permission generation.
2016-04-30 17:16:06 +01:00
Dan Brown
9a31b83b2a Worked around create permission quirks 2016-04-26 21:48:17 +01:00
Dan Brown
a81a56706e Rolled out new permissions system throughout application 2016-04-24 16:54:20 +01:00
Dan Brown
9dc1b35e82 Made settings ui a little less jumpy 2016-04-24 10:28:47 +01:00
Dan Brown
2dc1180a41 Added custom, More consistent hr insertion
Since the previous hr plugin caused large empty p tags
2016-04-23 19:15:49 +01:00
Dan Brown
ada7c83e96 Continued with database work for permissions overhaul
Added to the entity_permissions table with further required fields and indexes.
Wrote the code for checking permissions.
2016-04-23 18:14:26 +01:00
Dan Brown
ea287ebf86 Started creation of intermediate permission table 2016-04-20 21:37:57 +01:00
Dan Brown
043cdeafb3 Merge branch 'master' into add_role_view_permissions 2016-04-20 20:22:30 +01:00
Dan Brown
8b36ca95a3 Improved font quality and cyrillic support
Fixes dodgy font rendering on windows
2016-04-20 19:11:33 +01:00
Dan Brown
2cc36787f5 Updated assets for release 0.9.2 2016-04-15 19:57:02 +01:00
Dan Brown
448ac61b48 Merge branch 'master' into release 2016-04-15 19:52:59 +01:00
Dan Brown
8933179017 Prevented drafts from showing up in a book sort
Added tests to cover regresssion.
In reference to #100.
2016-04-15 19:51:27 +01:00
Dan Brown
792e786880 Added lists plugin to TinyMCE, Fixed strange nested list behaviour
Fixes #97
2016-04-14 22:34:33 +01:00
Dan Brown
753f6394f7 Merge branch 'master' into release 2016-04-12 20:09:14 +01:00
Dan Brown
0a8030306e Fixed redis config not being set if not using redis 2016-04-12 20:08:49 +01:00
Dan Brown
b1faf65934 Updated assets for release 0.9.0 2016-04-09 15:49:02 +01:00
Dan Brown
09f478bd74 Merge branch 'master' into release 2016-04-09 15:47:14 +01:00
Dan Brown
d6bad01130 Fixed draft time display, Cleaned up some code
Cleaned up some comment spacing in book controller and refactored some of the view service functions.
2016-04-09 14:26:42 +01:00
Dan Brown
a33deed26b Added user listing to role edit screen 2016-04-09 13:57:38 +01:00
Dan Brown
2e7345f4f0 Localised draft save time display
In reference to #83.
2016-04-09 13:36:32 +01:00
Dan Brown
6e03078de3 Started work towards adding role view permissions
Work halted as re-write required.
In reference to #92
2016-04-09 12:40:07 +01:00
Dan Brown
1a7de4c2d6 Fixed and cleaned some permisison/role based components 2016-04-09 12:37:58 +01:00
Dan Brown
66ba773367 Updated composer dependancies 2016-04-09 10:56:10 +01:00
Dan Brown
afc3583be8 Made new pages in chapters have a better inital priority 2016-04-07 19:03:00 +01:00
Dan Brown
cbff2c6035 Added uploaded to book/page filters & search in image manager
Also refactored tab styles which affected the settings area.

Closes #41
2016-04-03 14:59:54 +01:00
Dan Brown
8e614ecb6e Updated tests to match recent email confirmation changes 2016-04-03 12:34:10 +01:00
Dan Brown
d099885fd1 Fixed some label casing and fixed incorrect notifications
Fixed the book & chapter permission update notification stating the 'page permissions' have been updated.
2016-04-03 12:19:44 +01:00
Dan Brown
2bb8c3d914 Made email confirmations work with LDAP auth
The email_confirmed user field now actually indicates if an email is confirmed rather than defaulting to true if not checked.
 This ensures toggleing the 'Require email confirmation' setting actually makes all currently unconfirmed users confirm thier emails.
2016-04-03 12:16:54 +01:00
Dan Brown
4caa61fe96 Added a friendlier error for LDAP new user mismatches 2016-04-03 11:16:49 +01:00
Dan Brown
c5960f9b6a Added Redis cache/session support 2016-04-03 11:00:14 +01:00
Dan Brown
412eed19c3 Removed old input checks on entity permission checkboxes
Old input check potentialy causing issues (#89) and is not needed on the pages which it shows.
2016-04-03 10:23:16 +01:00
Dan Brown
e9b596d3bc Merge bugfixes from branch 'v0.8' 2016-03-30 21:49:25 +01:00
Dan Brown
a0497feddd Updated assets for release 0.8.2 2016-03-30 21:44:30 +01:00
Dan Brown
789693bde9 Merge branch 'v0.8' into release 2016-03-30 21:32:46 +01:00
Dan Brown
8b109bac13 Trimmed long names in header
Fixes #87
2016-03-30 21:28:38 +01:00
Dan Brown
097d9c9f3c Updated entity restrictions to allow permissions, Not just restrict
Also changed wording from 'Restrictions' to 'Permissions' to keep things more familiar and to better reflect what they do.

Referenced in issue #89.
2016-03-30 20:15:44 +01:00
Dan Brown
e7d8a041a8 Merge pull request #84 from ssddanbrown/markdown_editor
Initial implementation of a markdown editor. Closes #57.
2016-03-29 20:18:11 +01:00
Dan Brown
dc2978824e Added basic system tests for markdown editor, Added extra test helpers
Added test helpers for checking if an element exists / does not exist on a page.
Also fixed markdown editor bugs found while creating tests.
2016-03-29 20:13:23 +01:00
Dan Brown
e1994ef2cf Added editor control in admin settings & Fixed some markdown editor bugs
Also updated the setting system with a more sane approach to handling default values. (Now done via the setting-defaults config file)
2016-03-29 19:26:13 +01:00
Dan Brown
efb49019d4 Integrated the markdown editor with the image manager 2016-03-29 18:25:54 +01:00
Dan Brown
ef874712bb Cut readme down and added useful links
Remove a lot of the instructions/config info since much of it is now on the BookStack docs site.
2016-03-25 15:17:04 +00:00
Dan Brown
26965fa08f Added a markdown editor 2016-03-25 14:41:15 +00:00
Dan Brown
1fe933e4ea Merge branch 'master' into release 2016-03-13 15:38:06 +00:00
Dan Brown
491f73e0cd Fixed bug causing permission error on save and fixed non-gallery image save 2016-03-13 15:37:46 +00:00
Dan Brown
724b4b5a70 Updated assets for release 0.8.0 2016-03-13 15:15:14 +00:00
Dan Brown
1778a56146 Merge branch 'master' into release 2016-03-13 15:13:23 +00:00
Dan Brown
4656c12f6d Fixed bug causing editing notification to always show
Updated tests to prevent happening again
2016-03-13 14:33:43 +00:00
Dan Brown
a06321675a Forced browser scrollbar, Added custom theme compatible logo
Also removed link to search all pages if no pages mached search on global search page.
Closes #74.
2016-03-13 14:00:24 +00:00
Dan Brown
dbe11c1360 Attached images to pages and added restriction filtering
Closes #79
2016-03-13 13:30:47 +00:00
Dan Brown
75ecf1c44d Fixed inset chapter list colors
The colors were being overridden by the custom theme color.
Styles added to force color to page and/or page draft.
2016-03-13 12:43:42 +00:00
Dan Brown
5283919d24 Added new page drafts and started image entity attaching
Closes #80.
2016-03-13 12:04:08 +00:00
Dan Brown
ced8c8e497 Merged branch autosaving_drafts into master 2016-03-12 16:31:37 +00:00
Dan Brown
bf7852ce85 Organised test files & added page update draft tests
Also cleaned styling for new autosave ui parts.
Closes #36.
2016-03-12 16:31:02 +00:00
Dan Brown
30214fde74 Added UI components of page autosaving 2016-03-12 15:52:19 +00:00
Dan Brown
e9c213f803 Fixed firefox list editing bug and made button text size more consistent
Fixes #77
2016-03-09 23:23:28 +00:00
Dan Brown
9f11e045a5 Updated tinyMCE library from 4.3.2 to 4.3.7 2016-03-09 23:20:47 +00:00
Dan Brown
93ebdf724b Changed direct attributes to prevent conflicts 2016-03-09 22:54:18 +00:00
Dan Brown
59ce228c2e Moved page editing to angular controller and started work on update drafts 2016-03-09 22:32:07 +00:00
Dan Brown
744865fcb2 Updated assets for release 0.7.6 2016-03-06 13:28:44 +00:00
Dan Brown
7f8c8b448d Merged branch master into release 2016-03-06 13:26:29 +00:00
Dan Brown
1d6137f7e2 Added restrictions to user profile lists 2016-03-06 13:17:46 +00:00
Dan Brown
66c56e9d02 Added settings helper and formatted code in some files 2016-03-06 12:55:08 +00:00
Dan Brown
e744d4c82c Changed color picker library and moved color logic to front end
Since the old library was GPLv3 i changed the color picker to tiny-color-picker which is MIT.
Also extracted the styles to a shared view and move color calculation logic to javascript side.
2016-03-06 10:52:10 +00:00
Dan Brown
0774ecc89c Merge branch 'master' into nwalke-update_site_color 2016-03-06 09:08:20 +00:00
Dan Brown
5e7a4c7fb5 Fixed incorrect recents pages on homescreen
Fixed the bug causing the recently updated pages to be exaclty the same as the recently create pages.
Also added in tests to prevent regression.
2016-03-05 22:54:53 +00:00
Dan Brown
76eaf64f94 Fixed errors that occured when merging & refactored entity repositories
Also deleted the git '.orig' files that got added in last merge.
2016-03-05 19:00:26 +00:00
Dan Brown
80865b30a5 Merge branch 'custom_role_system'
Conflicts:
	app/Repos/BookRepo.php
	app/Repos/ChapterRepo.php
	app/Repos/PageRepo.php
2016-03-05 18:21:44 +00:00
Dan Brown
8e6248f57f Added restriction tests and fixed any bugs in the process
Also updated many styles within areas affected by the new permission and roles system.
2016-03-05 18:09:21 +00:00
Dan Brown
268db6b1d0 Added a whole load of permission & role tests 2016-03-05 12:09:09 +00:00
Dan Brown
479dd80a8c Made memcached config allow mulitple servers 2016-03-05 09:47:24 +00:00
Dan Brown
069431db72 Merge branch 'add_memcached' of git://github.com/nwalke/BookStack into nwalke-add_memcached 2016-03-05 09:05:27 +00:00
Dan Brown
bc2b310638 Updated fulltext search with custom escaped query
Changed pdo quoted query to use custom escaping and added like searches when quoted terms are used.
2016-03-04 20:49:35 +00:00
Nick Walke
33bf20cfc8 Found the source of the issue, not sure how to fix 2016-03-04 02:33:57 -06:00
Nick Walke
e3bdc391cd Closes #55. Allows you to set the primary color.
This certainly should not be seen as the final implementation of this.
I imagine that requests like this will happen for many other colors in
the system, it might be good at some point to allow them to upload their
own stylesheet to be used.
2016-03-03 12:01:14 -06:00
Nick Walke
5681f4dd69 Closes #70.
Added the ability to search by multi-word terms using double quotes.
2016-03-02 21:38:23 -06:00
Nick Walke
38d822e04c Closes #69. Implemented and tested memcached. 2016-03-02 20:44:00 -06:00
Dan Brown
8e274a5a84 Refactored some permission controls and increased testing for roles system 2016-03-02 22:35:01 +00:00
Dan Brown
985d2f1c2c Tied entity restriction system into userCan checks 2016-02-29 20:31:21 +00:00
Dan Brown
7f5872372d Added in restriction queries for most lists 2016-02-28 19:03:13 +00:00
Dan Brown
201f788806 Implemented database structure and inital interfaces for entity restrictions 2016-02-28 10:49:41 +00:00
Dan Brown
a14b5c33fd Added missing permission checkboxes and improved image AJAX permission responses 2016-02-27 20:52:46 +00:00
Dan Brown
473261be35 Finished initial implementation of custom role system 2016-02-27 19:24:42 +00:00
Dan Brown
a54be85185 Started work on exposing the role system as editable 2016-02-26 23:44:02 +00:00
Dan Brown
a67c53826d Updated assets for release 0.7.5 2016-02-25 21:24:09 +00:00
Dan Brown
14b131e850 Merge branch 'master' into release 2016-02-25 21:23:06 +00:00
Dan Brown
54e3122540 Added smarter page finding so changing the page name does not break old urls
Added page & book slug history to revisions so they can be looked up if a page is not found.
2016-02-25 20:01:59 +00:00
Dan Brown
d339ab1125 Updated phpunit testing mail settings due to laravel 5.2 changes 2016-02-22 21:28:53 +00:00
Dan Brown
3ab09ef708 Fixed issue with the book sort not showing all books in sidebar 2016-02-22 21:28:20 +00:00
Dan Brown
c86a122d80 Added table header row styling, made code blocks horizontally scrollbable fixed link popup on firefox 2016-02-22 20:18:08 +00:00
Dan Brown
3a58e37838 Updated phpunit environment variables with some required defaults 2016-02-22 19:39:51 +00:00
Dan Brown
6bd49bcd4b Fixed page listing excerpts not showing due to encoding issues 2016-02-21 13:15:46 +00:00
Dan Brown
61577cf6bf Added entity-specific search results pages. Cleaned & Fixed search results bugs
Added search result pages for pages, chapters and books.
Limited the results on the global search as it just listed out an infinate amount.
Fixed styling on new detailed page listings and also removed the 'bars' from the side to create  a cleaner view.
Fixed bad sql fulltext query format that may have thrown off searches.
Reduced the number of database queries down a thousand or so.
2016-02-21 12:53:58 +00:00
Dan Brown
b4dec2a99c Made page anchor hashes more relevant to the page content
This will help when adding support for new kinds of page content such as markdown as we won't be able to reference the same ID's as before thus they would break on every save.
2016-02-21 11:29:46 +00:00
Dan Brown
fe0b122aca Merge pull request #63 from AkibaWolf/patch-1
Fixes encoding issues the page HTML is formatted on save
2016-02-20 19:24:32 +00:00
Dan Brown
8eb2960950 Added recently created & updated page listings
Closes #46.
2016-02-20 18:51:01 +00:00
AkibaWolf
c2369a740d Update PageRepo.php
Fix encoding problem.

By default DOMDocument::loadHTML treats a string as being encoded with ISO-8859-1. This causes a problem with saving cyrillic pages' text that becomes completely unreadable (like Проверка instead of normal symbols).
2016-02-20 21:31:21 +05:00
Dan Brown
bab6fd1f2f Added recent pages to home view and made the home content more compact 2016-02-20 12:37:06 +00:00
Dan Brown
86fbc9a936 Added tests for profile pages 2016-02-18 19:32:07 +00:00
Dan Brown
4d9726dbdd Added content to user profile pages 2016-02-17 22:11:48 +00:00
Dan Brown
4442a2e6d1 Started work on user profile pages 2016-02-16 21:25:11 +00:00
Dan Brown
293be7093c Merged origin/master into master 2016-02-11 22:36:49 +00:00
Dan Brown
9b55a52b85 Updated assets for release 0.7.4 2016-02-11 22:35:01 +00:00
Dan Brown
db1d10e80f Merge branch 'master' into release 2016-02-11 22:29:29 +00:00
Dan Brown
354912a1df Made book-navigation sidebar on pages sticky 2016-02-11 22:23:19 +00:00
Nick Walke
eacff3a9f0 Fixes #45 2016-02-11 14:02:17 -06:00
Dan Brown
990acbb9ac Merge pull request #59 from nwalke/master
Updated email on users list to be a link
2016-02-11 18:53:12 +00:00
Nick Walke
17d4533e45 Fixes #58 2016-02-11 01:18:01 -06:00
Dan Brown
d6c00a85ad Fixed incorrect notification when deleting a page 2016-02-10 12:48:29 +00:00
Dan Brown
1be576966f Updated assets for release 0.7.3 2016-02-08 20:47:33 +00:00
Dan Brown
b97e792c5f Merge branch 'master' into release 2016-02-08 20:45:48 +00:00
Dan Brown
e0279f93f9 Added a back-to-top button on all pages
The new back-to-top button will show after scrolling a short distance down a long page.
Closes #44.
2016-02-08 20:42:41 +00:00
Dan Brown
9b83c57316 Fixed some design issues and improved page export styling
Fixed alignment on export options dropdown.
Fixed bullet list items sitting too close next to floated content. Fixes #34.
Fixed text overlaying images in PDF exports (Floats removed for now). Fixes #53.
Fixed spaced table cells on html & PDF exports.
2016-02-08 20:41:40 +00:00
Dan Brown
5d73d17c74 Fixed bug preventing LDAP users updating thier profile
Made email not required when a profile is updated. LDAP users without admin privileges could did not have an email field to submit therefore could previously not update thier profile.
2016-02-08 20:35:23 +00:00
Dan Brown
d32460070f Made ldap auth use the 'dn' if a 'uid' is not present.
Fixes #56
2016-02-08 19:45:01 +00:00
Dan Brown
105500e506 Tweaked page form header and added public uploads folder into repo 2016-02-07 10:21:09 +00:00
Dan Brown
8296782149 Updated image controller styling and added preview option
The notification system was also updated so it can be used from JavaScript events such as image manager uploads.

Closes #25
2016-02-07 10:17:38 +00:00
Dan Brown
8e8d582bc6 Updated app requirements & Added some friendlier errors 2016-02-03 20:55:37 +00:00
760 changed files with 59652 additions and 13343 deletions

2
.browserslistrc Normal file
View File

@@ -0,0 +1,2 @@
>0.25%
not op_mini all

View File

@@ -3,6 +3,10 @@ APP_ENV=production
APP_DEBUG=false
APP_KEY=SomeRandomString
# The below url has to be set if using social auth options
# or if you are not using BookStack at the root path of your domain.
# APP_URL=http://bookstack.dev
# Database details
DB_HOST=localhost
DB_DATABASE=database_database
@@ -12,7 +16,18 @@ DB_PASSWORD=database_user_password
# Cache and session
CACHE_DRIVER=file
SESSION_DRIVER=file
# If using Memcached, comment the above and uncomment these
#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
# This follows the following format: HOST:PORT:WEIGHT
# For multiple servers separate with a comma
MEMCACHED_SERVERS=127.0.0.1:11211:100
# Storage
STORAGE_TYPE=local
@@ -33,10 +48,18 @@ GITHUB_APP_ID=false
GITHUB_APP_SECRET=false
GOOGLE_APP_ID=false
GOOGLE_APP_SECRET=false
# URL used for social login redirects, NO TRAILING SLASH
APP_URL=http://bookstack.dev
OKTA_BASE_URL=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
DISCORD_APP_ID=false
DISCORD_APP_SECRET=false
# External services such as Gravatar
# External services such as Gravatar and Draw.IO
DISABLE_EXTERNAL_SERVICES=false
# LDAP Settings
@@ -46,6 +69,13 @@ LDAP_DN=false
LDAP_PASS=false
LDAP_USER_FILTER=false
LDAP_VERSION=false
# Do you want to sync LDAP groups to BookStack roles for a user
LDAP_USER_TO_GROUPS=false
# What is the LDAP attribute for group memberships
LDAP_GROUP_ATTRIBUTE="memberOf"
# Would you like to remove users from roles on BookStack if they do not match on LDAP
# If false, the ldap groups-roles sync will only add users to roles
LDAP_REMOVE_FROM_GROUPS=false
# Mail settings
MAIL_DRIVER=smtp
@@ -54,3 +84,5 @@ MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM=null
MAIL_FROM_NAME=null

84
.github/CODE_OF_CONDUCT.md vendored Normal file
View File

@@ -0,0 +1,84 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
education, socio-economic status, nationality, personal appearance, race,
religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
### Project Maintainer Standards
Project maintainers should generally follow these additional standards:
* Avoid using a negative or harsh tone in communication, Even if the other party
is being negative themselves.
* When providing criticism, try to make it constructive to lead the other person
down the correct path.
* Keep the [project definition](https://github.com/BookStackApp/BookStack#project-definition)
in mind when deciding what's in scope of the Project.
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior. In addition, Project
maintainers are responsible for following the standards themselves.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at the email address shown on [the profile here](https://github.com/ssddanbrown). All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org

29
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,29 @@
---
name: Bug report
about: Create a report to help us improve
---
**Describe the bug**
A clear and concise description of what the bug is.
**Steps To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Your Configuration (please complete the following information):**
- Exact BookStack Version (Found in settings):
- PHP Version:
- Hosting Method (Nginx/Apache/Docker):
**Additional context**
Add any other context about the problem here.

View File

@@ -0,0 +1,14 @@
---
name: Feature request
about: Suggest an idea for this project
---
**Describe the feature you'd like**
A clear description of the feature you'd like implemented in BookStack.
**Describe the benefits this feature would bring to BookStack users**
Explain the measurable benefits this feature would achieve.
**Additional context**
Add any other context or screenshots about the feature request here.

16
.gitignore vendored
View File

@@ -2,13 +2,23 @@
/node_modules
Homestead.yaml
.env
/public/dist
.idea
npm-debug.log
yarn-error.log
/public/dist/*.map
/public/plugins
/public/css/*.map
/public/js/*.map
/public/uploads
/public/bower
/public/build/
/storage/images
_ide_helper.php
/storage/debugbar
/storage/debugbar
.phpstorm.meta.php
yarn.lock
/bin
nbproject
.buildpath
.project
.settings/
webpack-stats.json

28
.travis.yml Normal file
View File

@@ -0,0 +1,28 @@
dist: trusty
sudo: false
language: php
php:
- 7.0.20
- 7.1.9
cache:
directories:
- $HOME/.composer/cache
before_script:
- mysql -u root -e 'create database `bookstack-test`;'
- mysql -u root -e "CREATE USER 'bookstack-test'@'localhost' IDENTIFIED BY 'bookstack-test';"
- mysql -u root -e "GRANT ALL ON \`bookstack-test\`.* TO 'bookstack-test'@'localhost';"
- mysql -u root -e "FLUSH PRIVILEGES;"
- phpenv config-rm xdebug.ini
- composer install --prefer-dist --no-interaction
- php artisan clear-compiled -n
- php artisan optimize -n
- php artisan migrate --force -n --database=mysql_testing
- php artisan db:seed --force -n --class=DummyContentSeeder --database=mysql_testing
after_failure:
- cat storage/logs/laravel.log
script:
- phpunit

22
LICENSE Normal file
View File

@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2018 Dan Brown and the BookStack Project contributors
https://github.com/BookStackApp/BookStack/graphs/contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -2,8 +2,6 @@
namespace BookStack;
use Illuminate\Database\Eloquent\Model;
/**
* @property string key
* @property \User user
@@ -15,15 +13,13 @@ class Activity extends Model
/**
* Get the entity for this activity.
* @return bool
*/
public function entity()
{
if ($this->entity_id) {
return $this->morphTo('entity')->first();
} else {
return false;
if ($this->entity_type === '') {
$this->entity_type = null;
}
return $this->morphTo('entity');
}
/**
@@ -32,7 +28,7 @@ class Activity extends Model
*/
public function user()
{
return $this->belongsTo('BookStack\User');
return $this->belongsTo(User::class);
}
/**
@@ -49,8 +45,8 @@ class Activity extends Model
* @param $activityB
* @return bool
*/
public function isSimilarTo($activityB) {
return [$this->key, $this->entitiy_type, $this->entitiy_id] === [$activityB->key, $activityB->entitiy_type, $activityB->entitiy_id];
public function isSimilarTo($activityB)
{
return [$this->key, $this->entity_type, $this->entity_id] === [$activityB->key, $activityB->entity_type, $activityB->entity_id];
}
}

39
app/Attachment.php Normal file
View File

@@ -0,0 +1,39 @@
<?php namespace BookStack;
class Attachment extends Ownable
{
protected $fillable = ['name', 'order'];
/**
* Get the downloadable file name for this upload.
* @return mixed|string
*/
public function getFileName()
{
if (str_contains($this->name, '.')) {
return $this->name;
}
return $this->name . '.' . $this->extension;
}
/**
* Get the page this file was uploaded to.
* @return Page
*/
public function page()
{
return $this->belongsTo(Page::class, 'uploaded_to');
}
/**
* Get the url of this file.
* @return string
*/
public function getUrl()
{
if ($this->external && strpos($this->path, 'http') !== 0) {
return $this->path;
}
return baseUrl('/attachments/' . $this->id);
}
}

View File

@@ -1,35 +1,97 @@
<?php
namespace BookStack;
<?php namespace BookStack;
class Book extends Entity
{
public $searchFactor = 2;
protected $fillable = ['name', 'description'];
protected $fillable = ['name', 'description', 'image_id'];
public function getUrl()
/**
* Get the url for this book.
* @param string|bool $path
* @return string
*/
public function getUrl($path = false)
{
return '/books/' . $this->slug;
if ($path !== false) {
return baseUrl('/books/' . urlencode($this->slug) . '/' . trim($path, '/'));
}
return baseUrl('/books/' . urlencode($this->slug));
}
/**
* Returns book cover image, if book cover not exists return default cover image.
* @param int $width - Width of the image
* @param int $height - Height of the image
* @return string
*/
public function getBookCover($width = 440, $height = 250)
{
$default = baseUrl('/book_default_cover.png');
if (!$this->image_id) {
return $default;
}
try {
$cover = $this->cover ? baseUrl($this->cover->getThumb($width, $height, false)) : $default;
} catch (\Exception $err) {
$cover = $default;
}
return $cover;
}
/**
* Get the cover image of the book
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function cover()
{
return $this->belongsTo(Image::class, 'image_id');
}
/*
* Get the edit url for this book.
* @return string
*/
public function getEditUrl()
{
return $this->getUrl() . '/edit';
}
/**
* Get all pages within this book.
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function pages()
{
return $this->hasMany('BookStack\Page');
return $this->hasMany(Page::class);
}
/**
* Get all chapters within this book.
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function chapters()
{
return $this->hasMany('BookStack\Chapter');
return $this->hasMany(Chapter::class);
}
/**
* Get an excerpt of this book's description to the specified length or less.
* @param int $length
* @return string
*/
public function getExcerpt($length = 100)
{
return strlen($this->description) > $length ? substr($this->description, 0, $length-3) . '...' : $this->description;
$description = $this->description;
return strlen($description) > $length ? substr($description, 0, $length-3) . '...' : $description;
}
/**
* Return a generalised, common raw query that can be 'unioned' across entities.
* @return string
*/
public function entityRawQuery()
{
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,29 +1,61 @@
<?php namespace BookStack;
class Chapter extends Entity
{
public $searchFactor = 1.3;
protected $fillable = ['name', 'description', 'priority', 'book_id'];
/**
* Get the book this chapter is within.
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function book()
{
return $this->belongsTo('BookStack\Book');
return $this->belongsTo(Book::class);
}
public function pages()
/**
* Get the pages that this chapter contains.
* @param string $dir
* @return mixed
*/
public function pages($dir = 'ASC')
{
return $this->hasMany('BookStack\Page')->orderBy('priority', 'ASC');
return $this->hasMany(Page::class)->orderBy('priority', $dir);
}
public function getUrl()
/**
* Get the url of this chapter.
* @param string|bool $path
* @return string
*/
public function getUrl($path = false)
{
$bookSlug = $this->getAttribute('bookSlug') ? $this->getAttribute('bookSlug') : $this->book->slug;
return '/books/' . $bookSlug. '/chapter/' . $this->slug;
if ($path !== false) {
return baseUrl('/books/' . urlencode($bookSlug) . '/chapter/' . urlencode($this->slug) . '/' . trim($path, '/'));
}
return baseUrl('/books/' . urlencode($bookSlug) . '/chapter/' . urlencode($this->slug));
}
/**
* Get an excerpt of this chapter's description to the specified length or less.
* @param int $length
* @return string
*/
public function getExcerpt($length = 100)
{
return strlen($this->description) > $length ? substr($this->description, 0, $length-3) . '...' : $this->description;
$description = $this->description;
return strlen($description) > $length ? substr($description, 0, $length-3) . '...' : $description;
}
/**
* Return a generalised, common raw query that can be 'unioned' across entities.
* @return string
*/
public function entityRawQuery()
{
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";
}
}

43
app/Comment.php Normal file
View File

@@ -0,0 +1,43 @@
<?php namespace BookStack;
class Comment extends Ownable
{
protected $fillable = ['text', 'html', 'parent_id'];
protected $appends = ['created', 'updated'];
/**
* Get the entity that this comment belongs to
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
public function entity()
{
return $this->morphTo('entity');
}
/**
* Check if a comment has been updated since creation.
* @return bool
*/
public function isUpdated()
{
return $this->updated_at->timestamp > $this->created_at->timestamp;
}
/**
* Get created date as a relative diff.
* @return mixed
*/
public function getCreatedAttribute()
{
return $this->created_at->diffForHumans();
}
/**
* Get updated date as a relative diff.
* @return mixed
*/
public function getUpdatedAttribute()
{
return $this->updated_at->diffForHumans();
}
}

View File

@@ -0,0 +1,83 @@
<?php
namespace BookStack\Console\Commands;
use BookStack\Services\ImageService;
use Illuminate\Console\Command;
use Symfony\Component\Console\Output\OutputInterface;
class CleanupImages extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'bookstack:cleanup-images
{--a|all : Include images that are used in page revisions}
{--f|force : Actually run the deletions}
';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Cleanup images and drawings';
protected $imageService;
/**
* Create a new command instance.
* @param ImageService $imageService
*/
public function __construct(ImageService $imageService)
{
$this->imageService = $imageService;
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$checkRevisions = $this->option('all') ? false : true;
$dryRun = $this->option('force') ? false : true;
if (!$dryRun) {
$proceed = $this->confirm("This operation is destructive and is not guaranteed to be fully accurate.\nEnsure you have a backup of your images.\nAre you sure you want to proceed?");
if (!$proceed) {
return;
}
}
$deleted = $this->imageService->deleteUnusedImages($checkRevisions, $dryRun);
$deleteCount = count($deleted);
if ($dryRun) {
$this->comment('Dry run, No images have been deleted');
$this->comment($deleteCount . ' images found that would have been deleted');
$this->showDeletedImages($deleted);
$this->comment('Run with -f or --force to perform deletions');
return;
}
$this->showDeletedImages($deleted);
$this->comment($deleteCount . ' images deleted');
}
protected function showDeletedImages($paths)
{
if ($this->getOutput()->getVerbosity() <= OutputInterface::VERBOSITY_NORMAL) return;
if (count($paths) > 0) {
$this->line('Images to delete:');
}
foreach ($paths as $path) {
$this->line($path);
}
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace BookStack\Console\Commands;
use BookStack\Activity;
use Illuminate\Console\Command;
class ClearActivity extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'bookstack:clear-activity';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Clear user activity from the system';
protected $activity;
/**
* Create a new command instance.
*
* @param Activity $activity
*/
public function __construct(Activity $activity)
{
$this->activity = $activity;
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$this->activity->newQuery()->truncate();
$this->comment('System activity cleared');
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace BookStack\Console\Commands;
use BookStack\PageRevision;
use Illuminate\Console\Command;
class ClearRevisions extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'bookstack:clear-revisions
{--a|all : Include active update drafts in deletion}
';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Clear page revisions';
protected $pageRevision;
/**
* Create a new command instance.
*
* @param PageRevision $pageRevision
*/
public function __construct(PageRevision $pageRevision)
{
$this->pageRevision = $pageRevision;
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$deleteTypes = $this->option('all') ? ['version', 'update_draft'] : ['version'];
$this->pageRevision->newQuery()->whereIn('type', $deleteTypes)->delete();
$this->comment('Revisions deleted');
}
}

View File

@@ -4,21 +4,21 @@ namespace BookStack\Console\Commands;
use Illuminate\Console\Command;
class ResetViews extends Command
class ClearViews extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'views:reset';
protected $signature = 'bookstack:clear-views';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Reset all view-counts for all entities.';
protected $description = 'Clear all view-counts for all entities.';
/**
* Create a new command instance.
@@ -37,5 +37,6 @@ class ResetViews extends Command
public function handle()
{
\Views::resetAll();
$this->comment('Views cleared');
}
}

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,33 +0,0 @@
<?php
namespace BookStack\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Foundation\Inspiring;
class Inspire extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'inspire';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Display an inspiring quote';
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$this->comment(PHP_EOL.Inspiring::quote().PHP_EOL);
}
}

View File

@@ -0,0 +1,60 @@
<?php
namespace BookStack\Console\Commands;
use BookStack\Services\PermissionService;
use Illuminate\Console\Command;
class RegeneratePermissions extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'bookstack:regenerate-permissions {--database= : The database connection to use.}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Regenerate all system permissions';
/**
* The service to handle the permission system.
*
* @var PermissionService
*/
protected $permissionService;
/**
* Create a new command instance.
*
* @param PermissionService $permissionService
*/
public function __construct(PermissionService $permissionService)
{
$this->permissionService = $permissionService;
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$connection = \DB::getDefaultConnection();
if ($this->option('database') !== null) {
\DB::setDefaultConnection($this->option('database'));
$this->permissionService->setConnection(\DB::connection($this->option('database')));
}
$this->permissionService->buildJointPermissions();
\DB::setDefaultConnection($connection);
$this->comment('Permissions regenerated');
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace BookStack\Console\Commands;
use BookStack\Services\SearchService;
use Illuminate\Console\Command;
class RegenerateSearch extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'bookstack:regenerate-search {--database= : The database connection to use.}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Re-index all content for searching';
protected $searchService;
/**
* Create a new command instance.
*
* @param SearchService $searchService
*/
public function __construct(SearchService $searchService)
{
parent::__construct();
$this->searchService = $searchService;
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$connection = \DB::getDefaultConnection();
if ($this->option('database') !== null) {
\DB::setDefaultConnection($this->option('database'));
$this->searchService->setConnection(\DB::connection($this->option('database')));
}
$this->searchService->indexAllEntities();
\DB::setDefaultConnection($connection);
$this->comment('Search index regenerated');
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace BookStack\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
class UpgradeDatabaseEncoding extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'bookstack:db-utf8mb4 {--database= : The database connection to use.}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Generate SQL commands to upgrade the database to UTF8mb4';
/**
* Create a new command instance.
*
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$connection = DB::getDefaultConnection();
if ($this->option('database') !== null) {
DB::setDefaultConnection($this->option('database'));
}
$database = DB::getDatabaseName();
$tables = DB::select('SHOW TABLES');
$this->line('ALTER DATABASE `'.$database.'` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
$this->line('USE `'.$database.'`;');
$key = 'Tables_in_' . $database;
foreach ($tables as $table) {
$tableName = $table->$key;
$this->line('ALTER TABLE `'.$tableName.'` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
}
DB::setDefaultConnection($connection);
}
}

View File

@@ -1,6 +1,4 @@
<?php
namespace BookStack\Console;
<?php namespace BookStack\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@@ -13,8 +11,7 @@ class Kernel extends ConsoleKernel
* @var array
*/
protected $commands = [
\BookStack\Console\Commands\Inspire::class,
\BookStack\Console\Commands\ResetViews::class,
//
];
/**
@@ -25,7 +22,16 @@ class Kernel extends ConsoleKernel
*/
protected function schedule(Schedule $schedule)
{
$schedule->command('inspire')
->hourly();
//
}
/**
* Register the commands for the application.
*
* @return void
*/
protected function commands()
{
$this->load(__DIR__.'/Commands');
}
}

View File

@@ -1,15 +0,0 @@
<?php
namespace BookStack;
use Illuminate\Database\Eloquent\Model;
class EmailConfirmation extends Model
{
protected $fillable = ['user_id', 'token'];
public function user()
{
return $this->belongsTo('BookStack\User');
}
}

View File

@@ -1,13 +1,19 @@
<?php
<?php namespace BookStack;
namespace BookStack;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\Model;
abstract class Entity extends Model
class Entity extends Ownable
{
use 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.
@@ -29,7 +35,9 @@ abstract class Entity extends Model
{
$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;
@@ -48,16 +56,73 @@ abstract class Entity extends Model
*/
public function activity()
{
return $this->morphMany('BookStack\Activity', 'entity')->orderBy('created_at', 'desc');
return $this->morphMany(Activity::class, 'entity')->orderBy('created_at', 'desc');
}
/**
* Get View objects for this entity.
* @return mixed
*/
public function views()
{
return $this->morphMany('BookStack\View', 'viewable');
return $this->morphMany(View::class, 'viewable');
}
/**
* Get the Tag models that have been user assigned to this entity.
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
*/
public function tags()
{
return $this->morphMany(Tag::class, 'entity')->orderBy('order', 'asc');
}
/**
* Get the comments for an entity
* @param bool $orderByCreated
* @return MorphMany
*/
public function comments($orderByCreated = true)
{
$query = $this->morphMany(Comment::class, 'entity');
return $orderByCreated ? $query->orderBy('created_at', 'asc') : $query;
}
/**
* Get the related search terms.
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
*/
public function searchTerms()
{
return $this->morphMany(SearchTerm::class, 'entity');
}
/**
* Get this entities restrictions.
*/
public function permissions()
{
return $this->morphMany(EntityPermission::class, 'restrictable');
}
/**
* Check if this entity has a specific restriction set against it.
* @param $role_id
* @param $action
* @return bool
*/
public function hasRestriction($role_id, $action)
{
return $this->permissions()->where('role_id', '=', $role_id)
->where('action', '=', $action)->count() > 0;
}
/**
* Get the entity jointPermissions this is connected to.
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
*/
public function jointPermissions()
{
return $this->morphMany(JointPermission::class, 'entity');
}
/**
@@ -68,63 +133,72 @@ abstract class Entity extends Model
*/
public static function isA($type)
{
return static::getClassName() === strtolower($type);
return static::getType() === strtolower($type);
}
/**
* Gets the class name.
* @return string
* Get entity type.
* @return mixed
*/
public static function getClassName()
public static function getType()
{
return strtolower(array_slice(explode('\\', static::class), -1, 1)[0]);
return strtolower(static::getClassName());
}
/**
*Gets a limited-length version of the entities name.
* Get an instance of an entity of the given type.
* @param $type
* @return Entity
*/
public static function getEntityInstance($type)
{
$types = ['Page', 'Book', 'Chapter'];
$className = str_replace([' ', '-', '_'], '', ucwords($type));
if (!in_array($className, $types)) {
return null;
}
return app('BookStack\\' . $className);
}
/**
* Gets a limited-length version of the entities name.
* @param int $length
* @return string
*/
public function getShortName($length = 25)
{
if(strlen($this->name) <= $length) return $this->name;
return substr($this->name, 0, $length-3) . '...';
if (strlen($this->name) <= $length) {
return $this->name;
}
return substr($this->name, 0, $length - 3) . '...';
}
/**
* Perform a full-text search on this entity.
* @param string[] $fieldsToSearch
* @param string[] $terms
* @param string[] array $wheres
* Get the body text of this entity.
* @return mixed
*/
public static function fullTextSearch($fieldsToSearch, $terms, $wheres = [])
public function getText()
{
$termString = '';
foreach ($terms as $term) {
$termString .= htmlentities($term) . '* ';
}
$fields = implode(',', $fieldsToSearch);
$termStringEscaped = \DB::connection()->getPdo()->quote($termString);
$search = static::addSelect(\DB::raw('*, MATCH(name) AGAINST('.$termStringEscaped.' IN BOOLEAN MODE) AS title_relevance'));
$search = $search->whereRaw('MATCH(' . $fields . ') AGAINST(? IN BOOLEAN MODE)', [$termStringEscaped]);
// Add additional where terms
foreach ($wheres as $whereTerm) {
$search->where($whereTerm[0], $whereTerm[1], $whereTerm[2]);
}
// Load in relations
if (!static::isA('book')) $search = $search->with('book');
if (static::isA('page')) $search = $search->with('chapter');
return $search->orderBy('title_relevance', 'desc')->get();
return $this->{$this->textField};
}
/**
* Get the url for this item.
* Return a generalised, common raw query that can be 'unioned' across entities.
* @return string
*/
abstract public function getUrl();
public function entityRawQuery()
{
return '';
}
/**
* Get the url of this entity
* @param $path
* @return string
*/
public function getUrl($path = '/')
{
return $path;
}
}

17
app/EntityPermission.php Normal file
View File

@@ -0,0 +1,17 @@
<?php namespace BookStack;
class EntityPermission extends Model
{
protected $fillable = ['role_id', 'action'];
public $timestamps = false;
/**
* Get all this restriction's attached entity.
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
public function restrictable()
{
return $this->morphTo('restrictable');
}
}

View File

@@ -1,8 +0,0 @@
<?php
namespace BookStack\Events;
abstract class Event
{
//
}

View File

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

View File

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

View File

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

View File

@@ -3,11 +3,13 @@
namespace BookStack\Exceptions;
use Exception;
use Illuminate\Contracts\Validation\ValidationException;
use Illuminate\Auth\AuthenticationException;
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
{
@@ -25,10 +27,11 @@ class Handler extends ExceptionHandler
/**
* Report or log an exception.
*
* This is a great spot to send exceptions to Sentry, Bugsnag, etc.
*
* @param \Exception $e
* @return mixed
* @throws Exception
*/
public function report(Exception $e)
{
@@ -38,17 +41,89 @@ class Handler extends ExceptionHandler
/**
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param \Exception $e
* @param \Illuminate\Http\Request $request
* @param \Exception $e
* @return \Illuminate\Http\Response
*/
public function render($request, Exception $e)
{
if($e instanceof NotifyException) {
\Session::flash('error', $e->message);
return response()->redirectTo($e->redirectLocation);
// Handle notify exceptions which will redirect to the
// specified location then show a notification message.
if ($this->isExceptionType($e, NotifyException::class)) {
session()->flash('error', $this->getOriginalMessage($e));
return redirect($e->redirectLocation);
}
// Handle pretty exceptions which will show a friendly application-fitting page
// Which will include the basic message to point the user roughly to the cause.
if ($this->isExceptionType($e, PrettyException::class) && !config('app.debug')) {
$message = $this->getOriginalMessage($e);
$code = ($e->getCode() === 0) ? 500 : $e->getCode();
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 \Route::respondWithRoute('fallback');
}
return parent::render($request, $e);
}
/**
* Check the exception chain to compare against the original exception type.
* @param Exception $e
* @param $type
* @return bool
*/
protected function isExceptionType(Exception $e, $type)
{
do {
if (is_a($e, $type)) {
return true;
}
} while ($e = $e->getPrevious());
return false;
}
/**
* Get original exception message.
* @param Exception $e
* @return string
*/
protected function getOriginalMessage(Exception $e)
{
do {
$message = $e->getMessage();
} while ($e = $e->getPrevious());
return $message;
}
/**
* Convert an authentication exception into an unauthenticated response.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Auth\AuthenticationException $exception
* @return \Illuminate\Http\Response
*/
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->expectsJson()) {
return response()->json(['error' => 'Unauthenticated.'], 401);
}
return redirect()->guest('login');
}
/**
* Convert a validation exception into a JSON response.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Validation\ValidationException $exception
* @return \Illuminate\Http\JsonResponse
*/
protected function invalidJson($request, ValidationException $exception)
{
return response()->json($exception->errors(), $exception->status);
}
}

View File

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

View File

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

View File

@@ -0,0 +1,14 @@
<?php namespace BookStack\Exceptions;
class NotFoundException extends PrettyException
{
/**
* NotFoundException constructor.
* @param string $message
*/
public function __construct($message = 'Item not found')
{
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

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,223 @@
<?php namespace BookStack\Http\Controllers;
use BookStack\Exceptions\FileUploadException;
use BookStack\Attachment;
use BookStack\Exceptions\NotFoundException;
use BookStack\Repos\EntityRepo;
use BookStack\Services\AttachmentService;
use Illuminate\Http\Request;
class AttachmentController extends Controller
{
protected $attachmentService;
protected $attachment;
protected $entityRepo;
/**
* AttachmentController constructor.
* @param AttachmentService $attachmentService
* @param Attachment $attachment
* @param EntityRepo $entityRepo
*/
public function __construct(AttachmentService $attachmentService, Attachment $attachment, EntityRepo $entityRepo)
{
$this->attachmentService = $attachmentService;
$this->attachment = $attachment;
$this->entityRepo = $entityRepo;
parent::__construct();
}
/**
* Endpoint at which attachments are uploaded to.
* @param Request $request
* @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\Response
*/
public function upload(Request $request)
{
$this->validate($request, [
'uploaded_to' => 'required|integer|exists:pages,id',
'file' => 'required|file'
]);
$pageId = $request->get('uploaded_to');
$page = $this->entityRepo->getById('page', $pageId, true);
$this->checkPermission('attachment-create-all');
$this->checkOwnablePermission('page-update', $page);
$uploadedFile = $request->file('file');
try {
$attachment = $this->attachmentService->saveNewUpload($uploadedFile, $pageId);
} catch (FileUploadException $e) {
return response($e->getMessage(), 500);
}
return response()->json($attachment);
}
/**
* Update an uploaded attachment.
* @param int $attachmentId
* @param Request $request
* @return mixed
*/
public function uploadUpdate($attachmentId, Request $request)
{
$this->validate($request, [
'uploaded_to' => 'required|integer|exists:pages,id',
'file' => 'required|file'
]);
$pageId = $request->get('uploaded_to');
$page = $this->entityRepo->getById('page', $pageId, true);
$attachment = $this->attachment->findOrFail($attachmentId);
$this->checkOwnablePermission('page-update', $page);
$this->checkOwnablePermission('attachment-create', $attachment);
if (intval($pageId) !== intval($attachment->uploaded_to)) {
return $this->jsonError(trans('errors.attachment_page_mismatch'));
}
$uploadedFile = $request->file('file');
try {
$attachment = $this->attachmentService->saveUpdatedUpload($uploadedFile, $attachment);
} catch (FileUploadException $e) {
return response($e->getMessage(), 500);
}
return response()->json($attachment);
}
/**
* Update the details of an existing file.
* @param $attachmentId
* @param Request $request
* @return Attachment|mixed
*/
public function update($attachmentId, Request $request)
{
$this->validate($request, [
'uploaded_to' => 'required|integer|exists:pages,id',
'name' => 'required|string|min:1|max:255',
'link' => 'string|min:1|max:255'
]);
$pageId = $request->get('uploaded_to');
$page = $this->entityRepo->getById('page', $pageId, true);
$attachment = $this->attachment->findOrFail($attachmentId);
$this->checkOwnablePermission('page-update', $page);
$this->checkOwnablePermission('attachment-create', $attachment);
if (intval($pageId) !== intval($attachment->uploaded_to)) {
return $this->jsonError(trans('errors.attachment_page_mismatch'));
}
$attachment = $this->attachmentService->updateFile($attachment, $request->all());
return response()->json($attachment);
}
/**
* Attach a link to a page.
* @param Request $request
* @return mixed
*/
public function attachLink(Request $request)
{
$this->validate($request, [
'uploaded_to' => 'required|integer|exists:pages,id',
'name' => 'required|string|min:1|max:255',
'link' => 'required|string|min:1|max:255'
]);
$pageId = $request->get('uploaded_to');
$page = $this->entityRepo->getById('page', $pageId, true);
$this->checkPermission('attachment-create-all');
$this->checkOwnablePermission('page-update', $page);
$attachmentName = $request->get('name');
$link = $request->get('link');
$attachment = $this->attachmentService->saveNewFromLink($attachmentName, $link, $pageId);
return response()->json($attachment);
}
/**
* Get the attachments for a specific page.
* @param $pageId
* @return mixed
*/
public function listForPage($pageId)
{
$page = $this->entityRepo->getById('page', $pageId, true);
$this->checkOwnablePermission('page-view', $page);
return response()->json($page->attachments);
}
/**
* Update the attachment sorting.
* @param $pageId
* @param Request $request
* @return mixed
*/
public function sortForPage($pageId, Request $request)
{
$this->validate($request, [
'files' => 'required|array',
'files.*.id' => 'required|integer',
]);
$page = $this->entityRepo->getById('page', $pageId);
$this->checkOwnablePermission('page-update', $page);
$attachments = $request->get('files');
$this->attachmentService->updateFileOrderWithinPage($attachments, $pageId);
return response()->json(['message' => trans('entities.attachments_order_updated')]);
}
/**
* 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
* @throws NotFoundException
*/
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) {
return redirect($attachment->path);
}
$attachmentContents = $this->attachmentService->getAttachmentFromStorage($attachment);
return response($attachmentContents, 200, [
'Content-Type' => 'application/octet-stream',
'Content-Disposition' => 'attachment; filename="'. $attachment->getFileName() .'"'
]);
}
/**
* Delete a specific attachment in the system.
* @param $attachmentId
* @return mixed
* @throws \Exception
*/
public function delete($attachmentId)
{
$attachment = $this->attachment->findOrFail($attachmentId);
$this->checkOwnablePermission('attachment-delete', $attachment);
$this->attachmentService->deleteFile($attachment);
return response()->json(['message' => trans('entities.attachments_deleted')]);
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace BookStack\Http\Controllers\Auth;
use BookStack\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Http\Request;
use Password;
class ForgotPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset emails and
| includes a trait which assists in sending these notifications from
| your application to your users. Feel free to explore this trait.
|
*/
use SendsPasswordResetEmails;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
parent::__construct();
}
/**
* Send a reset link to the given user.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function sendResetLinkEmail(Request $request)
{
$this->validate($request, ['email' => 'required|email']);
// We will send the password reset link to this user. Once we have attempted
// to send the link, we will examine the response then see the message we
// need to show to the user. Finally, we'll send out a proper response.
$response = $this->broker()->sendResetLink(
$request->only('email')
);
if ($response === Password::RESET_LINK_SENT) {
$message = trans('auth.reset_password_sent_success', ['email' => $request->get('email')]);
session()->flash('success', $message);
return back()->with('status', trans($response));
}
// If an error was returned by the password broker, we will get this message
// translated so we can notify a user of the problem. We'll redirect back
// to where the users came from so they can attempt this process again.
return back()->withErrors(
['email' => trans($response)]
);
}
}

View File

@@ -0,0 +1,145 @@
<?php
namespace BookStack\Http\Controllers\Auth;
use BookStack\Exceptions\AuthException;
use BookStack\Http\Controllers\Controller;
use BookStack\Repos\UserRepo;
use BookStack\Services\LdapService;
use BookStack\Services\SocialAuthService;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
use AuthenticatesUsers;
/**
* Where to redirect users after login.
*
* @var string
*/
protected $redirectTo = '/';
protected $redirectPath = '/';
protected $redirectAfterLogout = '/login';
protected $socialAuthService;
protected $ldapService;
protected $userRepo;
/**
* Create a new controller instance.
*
* @param SocialAuthService $socialAuthService
* @param LdapService $ldapService
* @param UserRepo $userRepo
*/
public function __construct(SocialAuthService $socialAuthService, LdapService $ldapService, UserRepo $userRepo)
{
$this->middleware('guest', ['only' => ['getLogin', 'postLogin']]);
$this->socialAuthService = $socialAuthService;
$this->ldapService = $ldapService;
$this->userRepo = $userRepo;
$this->redirectPath = baseUrl('/');
$this->redirectAfterLogout = baseUrl('/login');
parent::__construct();
}
public function username()
{
return config('auth.method') === 'standard' ? 'email' : 'username';
}
/**
* Overrides the action when a user is authenticated.
* If the user authenticated but does not exist in the user table we create them.
* @param Request $request
* @param Authenticatable $user
* @return \Illuminate\Http\RedirectResponse
* @throws AuthException
* @throws \BookStack\Exceptions\LdapException
*/
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 && $user->email === null && !$request->filled('email')) {
$request->flash();
session()->flash('request-email', true);
return redirect('/login');
}
if (!$user->exists && $user->email === null && $request->filled('email')) {
$user->email = $request->get('email');
}
if (!$user->exists) {
// Check for users with same email already
$alreadyUser = $user->newQuery()->where('email', '=', $user->email)->count() > 0;
if ($alreadyUser) {
throw new AuthException(trans('errors.error_user_exists_different_creds', ['email' => $user->email]));
}
$user->save();
$this->userRepo->attachDefaultRole($user);
auth()->login($user);
}
// Sync LDAP groups if required
if ($this->ldapService->shouldSyncGroups()) {
$this->ldapService->syncGroups($user, $request->get($this->username()));
}
$path = session()->pull('url.intended', '/');
$path = baseUrl($path, true);
return redirect($path);
}
/**
* Show the application login form.
* @param Request $request
* @return \Illuminate\Http\Response
*/
public function getLogin(Request $request)
{
$socialDrivers = $this->socialAuthService->getActiveDrivers();
$authMethod = config('auth.method');
if ($request->has('email')) {
session()->flashInput([
'email' => $request->get('email'),
'password' => (config('app.env') === 'demo') ? $request->get('password', '') : ''
]);
}
return view('auth/login', ['socialDrivers' => $socialDrivers, 'authMethod' => $authMethod]);
}
/**
* Redirect to the relevant social site.
* @param $socialDriver
* @return \Symfony\Component\HttpFoundation\RedirectResponse
* @throws \BookStack\Exceptions\SocialDriverNotConfigured
*/
public function getSocialLogin($socialDriver)
{
session()->put('social-callback', 'login');
return $this->socialAuthService->startLogIn($socialDriver);
}
}

View File

@@ -2,83 +2,96 @@
namespace BookStack\Http\Controllers\Auth;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Http\Request;
use BookStack\Exceptions\ConfirmationEmailException;
use BookStack\Exceptions\SocialSignInException;
use BookStack\Exceptions\UserRegistrationException;
use BookStack\Repos\UserRepo;
use BookStack\Services\EmailConfirmationService;
use BookStack\Services\SocialAuthService;
use BookStack\SocialAccount;
use BookStack\User;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Validator;
use BookStack\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
use Illuminate\Foundation\Auth\RegistersUsers;
class AuthController extends Controller
class RegisterController extends Controller
{
/*
|--------------------------------------------------------------------------
| Registration & Login Controller
| Register Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users, as well as the
| authentication of existing users. By default, this controller uses
| a simple trait to add these behaviors. Why don't you explore it?
| This controller handles the registration of new users as well as their
| validation and creation. By default this controller uses a trait to
| provide this functionality without requiring any additional code.
|
*/
use AuthenticatesAndRegistersUsers, ThrottlesLogins;
protected $redirectPath = '/';
protected $redirectAfterLogout = '/login';
protected $username = 'email';
use RegistersUsers;
protected $socialAuthService;
protected $emailConfirmationService;
protected $userRepo;
/**
* Create a new authentication controller instance.
* @param SocialAuthService $socialAuthService
* Where to redirect users after login / registration.
*
* @var string
*/
protected $redirectTo = '/';
protected $redirectPath = '/';
/**
* Create a new controller instance.
*
* @param SocialAuthService $socialAuthService
* @param EmailConfirmationService $emailConfirmationService
* @param UserRepo $userRepo
* @param UserRepo $userRepo
*/
public function __construct(SocialAuthService $socialAuthService, EmailConfirmationService $emailConfirmationService, UserRepo $userRepo)
{
$this->middleware('guest', ['only' => ['getLogin', 'postLogin', 'getRegister', 'postRegister']]);
$this->middleware('guest')->only(['getRegister', 'postRegister', 'socialRegister']);
$this->socialAuthService = $socialAuthService;
$this->emailConfirmationService = $emailConfirmationService;
$this->userRepo = $userRepo;
$this->username = config('auth.method') === 'standard' ? 'email' : 'username';
$this->redirectTo = baseUrl('/');
$this->redirectPath = baseUrl('/');
parent::__construct();
}
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'name' => 'required|max:255',
'email' => 'required|email|max:255|unique:users',
'name' => 'required|max:255',
'email' => 'required|email|max:255|unique:users',
'password' => 'required|min:6',
]);
}
/**
* Check whether or not registrations are allowed in the app settings.
* @throws UserRegistrationException
*/
protected function checkRegistrationAllowed()
{
if (!\Setting::get('registration-enabled')) {
throw new UserRegistrationException('Registrations are currently disabled.', '/login');
if (!setting('registration-enabled')) {
throw new UserRegistrationException(trans('auth.registrations_disabled'), '/login');
}
}
/**
* Show the application registration form.
* @return \Illuminate\Http\Response
* @return Response
* @throws UserRegistrationException
*/
public function getRegister()
{
@@ -89,92 +102,47 @@ class AuthController extends Controller
/**
* Handle a registration request for the application.
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
* @param Request|\Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
* @throws UserRegistrationException
*/
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);
}
/**
* Overrides the action when a user is authenticated.
* If the user authenticated but does not exist in the user table we create them.
* @param Request $request
* @param Authenticatable $user
* @return \Illuminate\Http\RedirectResponse
* Create a new user instance after a valid registration.
* @param array $data
* @return User
*/
protected function authenticated(Request $request, Authenticatable $user)
protected function create(array $data)
{
// Explicitly log them out for now if they do no exist.
if (!$user->exists) auth()->logout($user);
if (!$user->exists && $user->email === null && !$request->has('email')) {
$request->flash();
session()->flash('request-email', true);
return redirect('/login');
}
if (!$user->exists && $user->email === null && $request->has('email')) {
$user->email = $request->get('email');
}
if (!$user->exists) {
$user->save();
$this->userRepo->attachDefaultRole($user);
auth()->login($user);
}
return redirect()->intended($this->redirectPath());
}
/**
* Register a new user after a registration callback.
* @param $socialDriver
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
* @throws UserRegistrationException
*/
protected function socialRegisterCallback($socialDriver)
{
$socialUser = $this->socialAuthService->handleRegistrationCallback($socialDriver);
$socialAccount = $this->socialAuthService->fillSocialAccount($socialDriver, $socialUser);
// Create an array of the user data to create a new user instance
$userData = [
'name' => $socialUser->getName(),
'email' => $socialUser->getEmail(),
'password' => str_random(30)
];
return $this->registerUser($userData, $socialAccount);
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
}
/**
* The registrations flow for all users.
* @param array $userData
* @param array $userData
* @param bool|false|SocialAccount $socialAccount
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
* @throws UserRegistrationException
* @throws \BookStack\Exceptions\ConfirmationEmailException
*/
protected function registerUser(array $userData, $socialAccount = false)
{
if (\Setting::get('registration-restrict')) {
$restrictedEmailDomains = explode(',', str_replace(' ', '', \Setting::get('registration-restrict')));
if (setting('registration-restrict')) {
$restrictedEmailDomains = explode(',', str_replace(' ', '', setting('registration-restrict')));
$userEmailDomain = $domain = substr(strrchr($userData['email'], "@"), 1);
if (!in_array($userEmailDomain, $restrictedEmailDomains)) {
throw new UserRegistrationException('That email domain does not have access to this application', '/register');
throw new UserRegistrationException(trans('auth.registration_email_domain_invalid'), '/register');
}
}
@@ -183,17 +151,20 @@ class AuthController extends Controller
$newUser->socialAccounts()->save($socialAccount);
}
if (\Setting::get('registration-confirmation') || \Setting::get('registration-restrict')) {
$newUser->email_confirmed = false;
if (setting('registration-confirmation') || setting('registration-restrict')) {
$newUser->save();
$this->emailConfirmationService->sendConfirmation($newUser);
try {
$this->emailConfirmationService->sendConfirmation($newUser);
} catch (Exception $e) {
session()->flash('error', trans('auth.email_confirm_send_error'));
}
return redirect('/register/confirm');
}
$newUser->email_confirmed = true;
auth()->login($newUser);
session()->flash('success', 'Thanks for signing up! You are now registered and signed in.');
session()->flash('success', trans('auth.register_success'));
return redirect($this->redirectPath());
}
@@ -206,18 +177,6 @@ class AuthController extends Controller
return view('auth/register-confirm');
}
/**
* View the confirmation email as a standard web page.
* @param $token
* @return \Illuminate\View\View
* @throws UserRegistrationException
*/
public function viewConfirmEmail($token)
{
$confirmation = $this->emailConfirmationService->getEmailConfirmationFromToken($token);
return view('emails/email-confirmation', ['token' => $confirmation->token]);
}
/**
* Confirms an email via a token and logs the user into the system.
* @param $token
@@ -230,8 +189,8 @@ class AuthController extends Controller
$user = $confirmation->user;
$user->email_confirmed = true;
$user->save();
auth()->login($confirmation->user);
session()->flash('success', 'Your email has been confirmed!');
auth()->login($user);
session()->flash('success', trans('auth.email_confirm_success'));
$this->emailConfirmationService->deleteConfirmationsByUser($user);
return redirect($this->redirectPath);
}
@@ -257,37 +216,24 @@ class AuthController extends Controller
'email' => 'required|email|exists:users,email'
]);
$user = $this->userRepo->getByEmail($request->get('email'));
$this->emailConfirmationService->sendConfirmation($user);
session()->flash('success', 'Confirmation email resent, Please check your inbox.');
try {
$this->emailConfirmationService->sendConfirmation($user);
} catch (Exception $e) {
session()->flash('error', trans('auth.email_confirm_send_error'));
return redirect('/register/confirm');
}
session()->flash('success', trans('auth.email_confirm_resent'));
return redirect('/register/confirm');
}
/**
* Show the application login form.
* @return \Illuminate\Http\Response
*/
public function getLogin()
{
$socialDrivers = $this->socialAuthService->getActiveDrivers();
$authMethod = config('auth.method');
return view('auth/login', ['socialDrivers' => $socialDrivers, 'authMethod' => $authMethod]);
}
/**
* Redirect to the relevant social site.
* @param $socialDriver
* @return \Symfony\Component\HttpFoundation\RedirectResponse
*/
public function getSocialLogin($socialDriver)
{
session()->put('social-callback', 'login');
return $this->socialAuthService->startLogIn($socialDriver);
}
/**
* Redirect to the social site for authentication intended to register.
* @param $socialDriver
* @return mixed
* @throws UserRegistrationException
* @throws \BookStack\Exceptions\SocialDriverNotConfigured
*/
public function socialRegister($socialDriver)
{
@@ -299,20 +245,33 @@ class AuthController extends Controller
/**
* The callback for social login services.
* @param $socialDriver
* @param Request $request
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
* @throws SocialSignInException
* @throws UserRegistrationException
* @throws \BookStack\Exceptions\SocialDriverNotConfigured
* @throws ConfirmationEmailException
*/
public function socialCallback($socialDriver)
public function socialCallback($socialDriver, Request $request)
{
if (session()->has('social-callback')) {
$action = session()->pull('social-callback');
if ($action == 'login') {
return $this->socialAuthService->handleLoginCallback($socialDriver);
} elseif ($action == 'register') {
return $this->socialRegisterCallback($socialDriver);
}
} else {
throw new SocialSignInException('No action defined', '/login');
if (!session()->has('social-callback')) {
throw new SocialSignInException(trans('errors.social_no_action_defined'), '/login');
}
// Check request for error information
if ($request->has('error') && $request->has('error_description')) {
throw new SocialSignInException(trans('errors.social_login_bad_response', [
'socialAccount' => $socialDriver,
'error' => $request->get('error_description'),
]), '/login');
}
$action = session()->pull('social-callback');
if ($action == 'login') {
return $this->socialAuthService->handleLoginCallback($socialDriver);
}
if ($action == 'register') {
return $this->socialRegisterCallback($socialDriver);
}
return redirect()->back();
}
@@ -327,4 +286,24 @@ class AuthController extends Controller
return $this->socialAuthService->detachSocialAccount($socialDriver);
}
/**
* Register a new user after a registration callback.
* @param $socialDriver
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
* @throws UserRegistrationException
* @throws \BookStack\Exceptions\SocialDriverNotConfigured
*/
protected function socialRegisterCallback($socialDriver)
{
$socialUser = $this->socialAuthService->handleRegistrationCallback($socialDriver);
$socialAccount = $this->socialAuthService->fillSocialAccount($socialDriver, $socialUser);
// Create an array of the user data to create a new user instance
$userData = [
'name' => $socialUser->getName(),
'email' => $socialUser->getEmail(),
'password' => str_random(30)
];
return $this->registerUser($userData, $socialAccount);
}
}

View File

@@ -5,7 +5,7 @@ namespace BookStack\Http\Controllers\Auth;
use BookStack\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ResetsPasswords;
class PasswordController extends Controller
class ResetPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
@@ -23,10 +23,27 @@ class PasswordController extends Controller
protected $redirectTo = '/';
/**
* Create a new password controller instance.
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
parent::__construct();
}
/**
* Get the response for a successful password reset.
*
* @param string $response
* @return \Illuminate\Http\Response
*/
protected function sendResetResponse($response)
{
$message = trans('auth.reset_password_success');
session()->flash('success', $message);
return redirect($this->redirectPath())
->with('status', trans($response));
}
}

View File

@@ -1,62 +1,64 @@
<?php
namespace BookStack\Http\Controllers;
<?php namespace BookStack\Http\Controllers;
use Activity;
use BookStack\Book;
use BookStack\Repos\EntityRepo;
use BookStack\Repos\UserRepo;
use BookStack\Services\ExportService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use BookStack\Http\Requests;
use BookStack\Repos\BookRepo;
use BookStack\Repos\ChapterRepo;
use BookStack\Repos\PageRepo;
use Illuminate\Http\Response;
use Views;
class BookController extends Controller
{
protected $bookRepo;
protected $pageRepo;
protected $chapterRepo;
protected $entityRepo;
protected $userRepo;
protected $exportService;
/**
* BookController constructor.
* @param BookRepo $bookRepo
* @param PageRepo $pageRepo
* @param ChapterRepo $chapterRepo
* @param EntityRepo $entityRepo
* @param UserRepo $userRepo
* @param ExportService $exportService
*/
public function __construct(BookRepo $bookRepo, PageRepo $pageRepo, ChapterRepo $chapterRepo)
public function __construct(EntityRepo $entityRepo, UserRepo $userRepo, ExportService $exportService)
{
$this->bookRepo = $bookRepo;
$this->pageRepo = $pageRepo;
$this->chapterRepo = $chapterRepo;
$this->entityRepo = $entityRepo;
$this->userRepo = $userRepo;
$this->exportService = $exportService;
parent::__construct();
}
/**
* Display a listing of the book.
*
* @return Response
*/
public function index()
{
$books = $this->bookRepo->getAllPaginated(10);
$recents = $this->signedIn ? $this->bookRepo->getRecentlyViewed(4, 0) : false;
$popular = $this->bookRepo->getPopular(4, 0);
$this->setPageTitle('Books');
return view('books/index', ['books' => $books, 'recents' => $recents, 'popular' => $popular]);
$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', config('app.views.books', 'list'));
$this->setPageTitle(trans('entities.books'));
return view('books/index', [
'books' => $books,
'recents' => $recents,
'popular' => $popular,
'new' => $new,
'booksViewType' => $booksViewType
]);
}
/**
* Show the form for creating a new book.
*
* @return Response
*/
public function create()
{
$this->checkPermission('book-create');
$this->setPageTitle('Create New Book');
$this->checkPermission('book-create-all');
$this->setPageTitle(trans('entities.books_create'));
return view('books/create');
}
@@ -68,70 +70,66 @@ class BookController extends Controller
*/
public function store(Request $request)
{
$this->checkPermission('book-create');
$this->checkPermission('book-create-all');
$this->validate($request, [
'name' => 'required|string|max:255',
'name' => 'required|string|max:255',
'description' => 'string|max:1000'
]);
$book = $this->bookRepo->newFromInput($request->all());
$book->slug = $this->bookRepo->findSuitableSlug($book->name);
$book->created_by = Auth::user()->id;
$book->updated_by = Auth::user()->id;
$book->save();
$book = $this->entityRepo->createFromInput('book', $request->all());
Activity::add($book, 'book_create', $book->id);
return redirect($book->getUrl());
}
/**
* Display the specified book.
*
* @param $slug
* @return Response
*/
public function show($slug)
{
$book = $this->bookRepo->getBySlug($slug);
$bookChildren = $this->bookRepo->getChildren($book);
$book = $this->entityRepo->getBySlug('book', $slug);
$this->checkOwnablePermission('book-view', $book);
$bookChildren = $this->entityRepo->getBookChildren($book);
Views::add($book);
$this->setPageTitle($book->getShortName());
return view('books/show', ['book' => $book, 'current' => $book, 'bookChildren' => $bookChildren]);
return view('books/show', [
'book' => $book,
'current' => $book,
'bookChildren' => $bookChildren,
'activity' => Activity::entityActivity($book, 20, 0)
]);
}
/**
* Show the form for editing the specified book.
*
* @param $slug
* @return Response
*/
public function edit($slug)
{
$this->checkPermission('book-update');
$book = $this->bookRepo->getBySlug($slug);
$this->setPageTitle('Edit Book ' . $book->getShortName());
$book = $this->entityRepo->getBySlug('book', $slug);
$this->checkOwnablePermission('book-update', $book);
$this->setPageTitle(trans('entities.books_edit_named', ['bookName'=>$book->getShortName()]));
return view('books/edit', ['book' => $book, 'current' => $book]);
}
/**
* Update the specified book in storage.
*
* @param Request $request
* @param $slug
* @return Response
*/
public function update(Request $request, $slug)
{
$this->checkPermission('book-update');
$book = $this->bookRepo->getBySlug($slug);
$book = $this->entityRepo->getBySlug('book', $slug);
$this->checkOwnablePermission('book-update', $book);
$this->validate($request, [
'name' => 'required|string|max:255',
'name' => 'required|string|max:255',
'description' => 'string|max:1000'
]);
$book->fill($request->all());
$book->slug = $this->bookRepo->findSuitableSlug($book->name, $book->id);
$book->updated_by = Auth::user()->id;
$book->save();
Activity::add($book, 'book_update', $book->id);
return redirect($book->getUrl());
$book = $this->entityRepo->updateFromInput('book', $book, $request->all());
Activity::add($book, 'book_update', $book->id);
return redirect($book->getUrl());
}
/**
@@ -141,9 +139,9 @@ class BookController extends Controller
*/
public function showDelete($bookSlug)
{
$this->checkPermission('book-delete');
$book = $this->bookRepo->getBySlug($bookSlug);
$this->setPageTitle('Delete Book ' . $book->getShortName());
$book = $this->entityRepo->getBySlug('book', $bookSlug);
$this->checkOwnablePermission('book-delete', $book);
$this->setPageTitle(trans('entities.books_delete_named', ['bookName'=>$book->getShortName()]));
return view('books/delete', ['book' => $book, 'current' => $book]);
}
@@ -154,11 +152,11 @@ class BookController extends Controller
*/
public function sort($bookSlug)
{
$this->checkPermission('book-update');
$book = $this->bookRepo->getBySlug($bookSlug);
$bookChildren = $this->bookRepo->getChildren($book);
$books = $this->bookRepo->getAll();
$this->setPageTitle('Sort Book ' . $book->getShortName());
$book = $this->entityRepo->getBySlug('book', $bookSlug);
$this->checkOwnablePermission('book-update', $book);
$bookChildren = $this->entityRepo->getBookChildren($book, true);
$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]);
}
@@ -170,70 +168,170 @@ class BookController extends Controller
*/
public function getSortItem($bookSlug)
{
$book = $this->bookRepo->getBySlug($bookSlug);
$bookChildren = $this->bookRepo->getChildren($book);
$book = $this->entityRepo->getBySlug('book', $bookSlug);
$bookChildren = $this->entityRepo->getBookChildren($book);
return view('books/sort-box', ['book' => $book, 'bookChildren' => $bookChildren]);
}
/**
* Saves an array of sort mapping to pages and chapters.
*
* @param string $bookSlug
* @param Request $request
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function saveSort($bookSlug, Request $request)
{
$this->checkPermission('book-update');
$book = $this->bookRepo->getBySlug($bookSlug);
$book = $this->entityRepo->getBySlug('book', $bookSlug);
$this->checkOwnablePermission('book-update', $book);
// Return if no map sent
if (!$request->has('sort-tree')) {
if (!$request->filled('sort-tree')) {
return redirect($book->getUrl());
}
$sortedBooks = [];
// Sort pages and chapters
$sortMap = json_decode($request->get('sort-tree'));
$defaultBookId = $book->id;
foreach ($sortMap as $index => $bookChild) {
$id = $bookChild->id;
$isPage = $bookChild->type == 'page';
$bookId = $this->bookRepo->exists($bookChild->book) ? $bookChild->book : $defaultBookId;
$model = $isPage ? $this->pageRepo->getById($id) : $this->chapterRepo->getById($id);
$isPage ? $this->pageRepo->changeBook($bookId, $model) : $this->chapterRepo->changeBook($bookId, $model);
$model->priority = $index;
if ($isPage) {
$model->chapter_id = ($bookChild->parentChapter === false) ? 0 : $bookChild->parentChapter;
}
$model->save();
if (!in_array($bookId, $sortedBooks)) {
$sortedBooks[] = $bookId;
}
}
$sortMap = collect(json_decode($request->get('sort-tree')));
$bookIdsInvolved = collect([$book->id]);
// Add activity for books
foreach ($sortedBooks as $bookId) {
$updatedBook = $this->bookRepo->getById($bookId);
Activity::add($updatedBook, 'book_sort', $updatedBook->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));
});
// 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();
}
if ($priorityChanged) {
$model->priority = intval($mapItem->sort);
$model->save();
}
});
// 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());
}
/**
* Remove the specified book from storage.
*
* @param $bookSlug
* @return Response
*/
public function destroy($bookSlug)
{
$this->checkPermission('book-delete');
$book = $this->bookRepo->getBySlug($bookSlug);
$book = $this->entityRepo->getBySlug('book', $bookSlug);
$this->checkOwnablePermission('book-delete', $book);
Activity::addMessage('book_delete', 0, $book->name);
Activity::removeEntity($book);
$this->bookRepo->destroyBySlug($bookSlug);
$this->entityRepo->destroyBook($book);
return redirect('/books');
}
/**
* Show the Restrictions view.
* @param $bookSlug
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function showRestrict($bookSlug)
{
$book = $this->entityRepo->getBySlug('book', $bookSlug);
$this->checkOwnablePermission('restrictions-manage', $book);
$roles = $this->userRepo->getRestrictableRoles();
return view('books/restrictions', [
'book' => $book,
'roles' => $roles
]);
}
/**
* Set the restrictions for this book.
* @param $bookSlug
* @param $bookSlug
* @param Request $request
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function restrict($bookSlug, Request $request)
{
$book = $this->entityRepo->getBySlug('book', $bookSlug);
$this->checkOwnablePermission('restrictions-manage', $book);
$this->entityRepo->updateEntityPermissionsFromRequest($request, $book);
session()->flash('success', trans('entities.books_permissions_updated'));
return redirect($book->getUrl());
}
/**
* Export a book as a PDF file.
* @param string $bookSlug
* @return mixed
*/
public function exportPdf($bookSlug)
{
$book = $this->entityRepo->getBySlug('book', $bookSlug);
$pdfContent = $this->exportService->bookToPdf($book);
return response()->make($pdfContent, 200, [
'Content-Type' => 'application/octet-stream',
'Content-Disposition' => 'attachment; filename="' . $bookSlug . '.pdf'
]);
}
/**
* Export a book as a contained HTML file.
* @param string $bookSlug
* @return mixed
*/
public function exportHtml($bookSlug)
{
$book = $this->entityRepo->getBySlug('book', $bookSlug);
$htmlContent = $this->exportService->bookToContainedHtml($book);
return response()->make($htmlContent, 200, [
'Content-Type' => 'application/octet-stream',
'Content-Disposition' => 'attachment; filename="' . $bookSlug . '.html'
]);
}
/**
* Export a book as a plain text file.
* @param $bookSlug
* @return mixed
*/
public function exportPlainText($bookSlug)
{
$book = $this->entityRepo->getBySlug('book', $bookSlug);
$htmlContent = $this->exportService->bookToPlainText($book);
return response()->make($htmlContent, 200, [
'Content-Type' => 'application/octet-stream',
'Content-Disposition' => 'attachment; filename="' . $bookSlug . '.txt'
]);
}
}

View File

@@ -1,36 +1,34 @@
<?php
namespace BookStack\Http\Controllers;
<?php namespace BookStack\Http\Controllers;
use Activity;
use BookStack\Repos\EntityRepo;
use BookStack\Repos\UserRepo;
use BookStack\Services\ExportService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use BookStack\Http\Requests;
use BookStack\Http\Controllers\Controller;
use BookStack\Repos\BookRepo;
use BookStack\Repos\ChapterRepo;
use Illuminate\Http\Response;
use Views;
class ChapterController extends Controller
{
protected $bookRepo;
protected $chapterRepo;
protected $userRepo;
protected $entityRepo;
protected $exportService;
/**
* ChapterController constructor.
* @param $bookRepo
* @param $chapterRepo
* @param EntityRepo $entityRepo
* @param UserRepo $userRepo
* @param ExportService $exportService
*/
public function __construct(BookRepo $bookRepo, ChapterRepo $chapterRepo)
public function __construct(EntityRepo $entityRepo, UserRepo $userRepo, ExportService $exportService)
{
$this->bookRepo = $bookRepo;
$this->chapterRepo = $chapterRepo;
$this->entityRepo = $entityRepo;
$this->userRepo = $userRepo;
$this->exportService = $exportService;
parent::__construct();
}
/**
* Show the form for creating a new chapter.
* @param $bookSlug
@@ -38,9 +36,9 @@ class ChapterController extends Controller
*/
public function create($bookSlug)
{
$this->checkPermission('chapter-create');
$book = $this->bookRepo->getBySlug($bookSlug);
$this->setPageTitle('Create New Chapter');
$book = $this->entityRepo->getBySlug('book', $bookSlug);
$this->checkOwnablePermission('chapter-create', $book);
$this->setPageTitle(trans('entities.chapters_create'));
return view('chapters/create', ['book' => $book, 'current' => $book]);
}
@@ -52,18 +50,16 @@ class ChapterController extends Controller
*/
public function store($bookSlug, Request $request)
{
$this->checkPermission('chapter-create');
$this->validate($request, [
'name' => 'required|string|max:255'
]);
$book = $this->bookRepo->getBySlug($bookSlug);
$chapter = $this->chapterRepo->newFromInput($request->all());
$chapter->slug = $this->chapterRepo->findSuitableSlug($chapter->name, $book->id);
$chapter->priority = $this->bookRepo->getNewPriority($book);
$chapter->created_by = auth()->user()->id;
$chapter->updated_by = auth()->user()->id;
$book->chapters()->save($chapter);
$book = $this->entityRepo->getBySlug('book', $bookSlug);
$this->checkOwnablePermission('chapter-create', $book);
$input = $request->all();
$input['priority'] = $this->entityRepo->getNewBookPriority($book);
$chapter = $this->entityRepo->createFromInput('chapter', $input, $book);
Activity::add($chapter, 'chapter_create', $book->id);
return redirect($chapter->getUrl());
}
@@ -76,12 +72,19 @@ class ChapterController extends Controller
*/
public function show($bookSlug, $chapterSlug)
{
$book = $this->bookRepo->getBySlug($bookSlug);
$chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
$sidebarTree = $this->bookRepo->getChildren($book);
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
$this->checkOwnablePermission('chapter-view', $chapter);
$sidebarTree = $this->entityRepo->getBookChildren($chapter->book);
Views::add($chapter);
$this->setPageTitle($chapter->getShortName());
return view('chapters/show', ['book' => $book, 'chapter' => $chapter, 'current' => $chapter, 'sidebarTree' => $sidebarTree]);
$pages = $this->entityRepo->getChapterChildren($chapter);
return view('chapters/show', [
'book' => $chapter->book,
'chapter' => $chapter,
'current' => $chapter,
'sidebarTree' => $sidebarTree,
'pages' => $pages
]);
}
/**
@@ -92,11 +95,10 @@ class ChapterController extends Controller
*/
public function edit($bookSlug, $chapterSlug)
{
$this->checkPermission('chapter-update');
$book = $this->bookRepo->getBySlug($bookSlug);
$chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
$this->setPageTitle('Edit Chapter' . $chapter->getShortName());
return view('chapters/edit', ['book' => $book, 'chapter' => $chapter, 'current' => $chapter]);
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
$this->checkOwnablePermission('chapter-update', $chapter);
$this->setPageTitle(trans('entities.chapters_edit_named', ['chapterName' => $chapter->getShortName()]));
return view('chapters/edit', ['book' => $chapter->book, 'chapter' => $chapter, 'current' => $chapter]);
}
/**
@@ -105,17 +107,15 @@ class ChapterController extends Controller
* @param $bookSlug
* @param $chapterSlug
* @return Response
* @throws \BookStack\Exceptions\NotFoundException
*/
public function update(Request $request, $bookSlug, $chapterSlug)
{
$this->checkPermission('chapter-update');
$book = $this->bookRepo->getBySlug($bookSlug);
$chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
$chapter->fill($request->all());
$chapter->slug = $this->chapterRepo->findSuitableSlug($chapter->name, $book->id, $chapter->id);
$chapter->updated_by = auth()->user()->id;
$chapter->save();
Activity::add($chapter, 'chapter_update', $book->id);
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
$this->checkOwnablePermission('chapter-update', $chapter);
$this->entityRepo->updateFromInput('chapter', $chapter, $request->all());
Activity::add($chapter, 'chapter_update', $chapter->book->id);
return redirect($chapter->getUrl());
}
@@ -127,11 +127,10 @@ class ChapterController extends Controller
*/
public function showDelete($bookSlug, $chapterSlug)
{
$this->checkPermission('chapter-delete');
$book = $this->bookRepo->getBySlug($bookSlug);
$chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
$this->setPageTitle('Delete Chapter' . $chapter->getShortName());
return view('chapters/delete', ['book' => $book, 'chapter' => $chapter, 'current' => $chapter]);
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
$this->checkOwnablePermission('chapter-delete', $chapter);
$this->setPageTitle(trans('entities.chapters_delete_named', ['chapterName' => $chapter->getShortName()]));
return view('chapters/delete', ['book' => $chapter->book, 'chapter' => $chapter, 'current' => $chapter]);
}
/**
@@ -142,11 +141,150 @@ class ChapterController extends Controller
*/
public function destroy($bookSlug, $chapterSlug)
{
$this->checkPermission('chapter-delete');
$book = $this->bookRepo->getBySlug($bookSlug);
$chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
$book = $chapter->book;
$this->checkOwnablePermission('chapter-delete', $chapter);
Activity::addMessage('chapter_delete', $book->id, $chapter->name);
$this->chapterRepo->destroy($chapter);
$this->entityRepo->destroyChapter($chapter);
return redirect($book->getUrl());
}
/**
* Show the page for moving a chapter.
* @param $bookSlug
* @param $chapterSlug
* @return mixed
* @throws \BookStack\Exceptions\NotFoundException
*/
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);
return view('chapters/move', [
'chapter' => $chapter,
'book' => $chapter->book
]);
}
/**
* Perform the move action for a chapter.
* @param $bookSlug
* @param $chapterSlug
* @param Request $request
* @return mixed
* @throws \BookStack\Exceptions\NotFoundException
*/
public function move($bookSlug, $chapterSlug, Request $request)
{
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
$this->checkOwnablePermission('chapter-update', $chapter);
$entitySelection = $request->get('entity_selection', null);
if ($entitySelection === null || $entitySelection === '') {
return redirect($chapter->getUrl());
}
$stringExploded = explode(':', $entitySelection);
$entityType = $stringExploded[0];
$entityId = intval($stringExploded[1]);
$parent = false;
if ($entityType == 'book') {
$parent = $this->entityRepo->getById('book', $entityId);
}
if ($parent === false || $parent === null) {
session()->flash('error', trans('errors.selected_book_not_found'));
return redirect()->back();
}
$this->entityRepo->changeBook('chapter', $parent->id, $chapter, true);
Activity::add($chapter, 'chapter_move', $chapter->book->id);
session()->flash('success', trans('entities.chapter_move_success', ['bookName' => $parent->name]));
return redirect($chapter->getUrl());
}
/**
* Show the Restrictions view.
* @param $bookSlug
* @param $chapterSlug
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function showRestrict($bookSlug, $chapterSlug)
{
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
$this->checkOwnablePermission('restrictions-manage', $chapter);
$roles = $this->userRepo->getRestrictableRoles();
return view('chapters/restrictions', [
'chapter' => $chapter,
'roles' => $roles
]);
}
/**
* Set the restrictions for this chapter.
* @param $bookSlug
* @param $chapterSlug
* @param Request $request
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function restrict($bookSlug, $chapterSlug, Request $request)
{
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
$this->checkOwnablePermission('restrictions-manage', $chapter);
$this->entityRepo->updateEntityPermissionsFromRequest($request, $chapter);
session()->flash('success', trans('entities.chapters_permissions_success'));
return redirect($chapter->getUrl());
}
/**
* Exports a chapter to pdf .
* @param string $bookSlug
* @param string $chapterSlug
* @return \Illuminate\Http\Response
*/
public function exportPdf($bookSlug, $chapterSlug)
{
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
$pdfContent = $this->exportService->chapterToPdf($chapter);
return response()->make($pdfContent, 200, [
'Content-Type' => 'application/octet-stream',
'Content-Disposition' => 'attachment; filename="' . $chapterSlug . '.pdf'
]);
}
/**
* Export a chapter to a self-contained HTML file.
* @param string $bookSlug
* @param string $chapterSlug
* @return \Illuminate\Http\Response
*/
public function exportHtml($bookSlug, $chapterSlug)
{
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
$containedHtml = $this->exportService->chapterToContainedHtml($chapter);
return response()->make($containedHtml, 200, [
'Content-Type' => 'application/octet-stream',
'Content-Disposition' => 'attachment; filename="' . $chapterSlug . '.html'
]);
}
/**
* Export a chapter to a simple plaintext .txt file.
* @param string $bookSlug
* @param string $chapterSlug
* @return \Illuminate\Http\Response
*/
public function exportPlainText($bookSlug, $chapterSlug)
{
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
$containedHtml = $this->exportService->chapterToPlainText($chapter);
return response()->make($containedHtml, 200, [
'Content-Type' => 'application/octet-stream',
'Content-Disposition' => 'attachment; filename="' . $chapterSlug . '.txt'
]);
}
}

View File

@@ -0,0 +1,93 @@
<?php namespace BookStack\Http\Controllers;
use Activity;
use BookStack\Repos\CommentRepo;
use BookStack\Repos\EntityRepo;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Request;
class CommentController extends Controller
{
protected $entityRepo;
protected $commentRepo;
/**
* CommentController constructor.
* @param EntityRepo $entityRepo
* @param CommentRepo $commentRepo
*/
public function __construct(EntityRepo $entityRepo, CommentRepo $commentRepo)
{
$this->entityRepo = $entityRepo;
$this->commentRepo = $commentRepo;
parent::__construct();
}
/**
* Save a new comment for a Page
* @param Request $request
* @param integer $pageId
* @param null|integer $commentId
* @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\Response
*/
public function savePageComment(Request $request, $pageId, $commentId = null)
{
$this->validate($request, [
'text' => 'required|string',
'html' => 'required|string',
]);
try {
$page = $this->entityRepo->getById('page', $pageId, true);
} catch (ModelNotFoundException $e) {
return response('Not found', 404);
}
$this->checkOwnablePermission('page-view', $page);
// Prevent adding comments to draft pages
if ($page->draft) {
return $this->jsonError(trans('errors.cannot_add_comment_to_draft'), 400);
}
// Create a new comment.
$this->checkPermission('comment-create-all');
$comment = $this->commentRepo->create($page, $request->only(['html', 'text', 'parent_id']));
Activity::add($page, 'commented_on', $page->book->id);
return view('comments/comment', ['comment' => $comment]);
}
/**
* Update an existing comment.
* @param Request $request
* @param integer $commentId
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function update(Request $request, $commentId)
{
$this->validate($request, [
'text' => 'required|string',
'html' => 'required|string',
]);
$comment = $this->commentRepo->getById($commentId);
$this->checkOwnablePermission('page-view', $comment->entity);
$this->checkOwnablePermission('comment-update', $comment);
$comment = $this->commentRepo->update($comment, $request->only(['html', 'text']));
return view('comments/comment', ['comment' => $comment]);
}
/**
* Delete a comment from the system.
* @param integer $id
* @return \Illuminate\Http\JsonResponse
*/
public function destroy($id)
{
$comment = $this->commentRepo->getById($id);
$this->checkOwnablePermission('comment-delete', $comment);
$this->commentRepo->delete($comment);
return response()->json(['message' => trans('entities.comment_deleted')]);
}
}

View File

@@ -2,13 +2,12 @@
namespace BookStack\Http\Controllers;
use HttpRequestException;
use BookStack\Ownable;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Http\Exception\HttpResponseException;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;
use BookStack\User;
abstract class Controller extends BaseController
@@ -29,17 +28,21 @@ abstract class Controller extends BaseController
*/
public function __construct()
{
// Get a user instance for the current user
$user = auth()->user();
if (!$user) $user = User::getDefault();
$this->middleware(function ($request, $next) {
// Share variables with views
view()->share('signedIn', auth()->check());
view()->share('currentUser', $user);
// Get a user instance for the current user
$user = user();
// Share variables with controllers
$this->currentUser = $user;
$this->signedIn = auth()->check();
// Share variables with controllers
$this->currentUser = $user;
$this->signedIn = auth()->check();
// Share variables with views
view()->share('signedIn', $this->signedIn);
view()->share('currentUser', $user);
return $next($request);
});
}
/**
@@ -48,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();
}
}
/**
@@ -61,32 +66,48 @@ abstract class Controller extends BaseController
}
/**
* On a permission error redirect to home and display
* On a permission error redirect to home and display.
* the error as a notification.
*/
protected function showPermissionError()
{
Session::flash('error', trans('errors.permission'));
throw new HttpResponseException(
redirect('/')
);
if (request()->wantsJson()) {
$response = response()->json(['error' => trans('errors.permissionJson')], 403);
} else {
$response = redirect('/');
session()->flash('error', trans('errors.permission'));
}
throw new HttpResponseException($response);
}
/**
* Checks for a permission.
*
* @param $permissionName
* @param string $permissionName
* @return bool|\Illuminate\Http\RedirectResponse
*/
protected function checkPermission($permissionName)
{
if (!$this->currentUser || !$this->currentUser->can($permissionName)) {
if (!user() || !user()->can($permissionName)) {
$this->showPermissionError();
}
return true;
}
/**
* Check the current user's permissions against an ownable item.
* @param $permission
* @param Ownable $ownable
* @return bool
*/
protected function checkOwnablePermission($permission, Ownable $ownable)
{
if (userCan($permission, $ownable)) {
return true;
}
return $this->showPermissionError();
}
/**
* Check if a user has a permission or bypass if the callback is true.
* @param $permissionName
@@ -96,8 +117,38 @@ 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;
}
/**
* Send back a json error message.
* @param string $messageText
* @param int $statusCode
* @return mixed
*/
protected function jsonError($messageText = "", $statusCode = 500)
{
return response()->json(['message' => $messageText], $statusCode);
}
/**
* Create the response for when a request fails validation.
*
* @param \Illuminate\Http\Request $request
* @param array $errors
* @return \Symfony\Component\HttpFoundation\Response
*/
protected function buildFailedValidationResponse(Request $request, array $errors)
{
if ($request->expectsJson()) {
return response()->json(['validation' => $errors], 422);
}
return redirect()->to($this->getRedirectUrl())
->withInput($request->input())
->withErrors($errors, $this->errorBag());
}
}

View File

@@ -1,41 +1,143 @@
<?php
namespace BookStack\Http\Controllers;
<?php namespace BookStack\Http\Controllers;
use Activity;
use BookStack\Repos\EntityRepo;
use Illuminate\Http\Request;
use BookStack\Http\Requests;
use BookStack\Repos\BookRepo;
use Illuminate\Http\Response;
use Views;
class HomeController extends Controller
{
protected $activityService;
protected $bookRepo;
protected $entityRepo;
/**
* HomeController constructor.
* @param BookRepo $bookRepo
* @param EntityRepo $entityRepo
*/
public function __construct(BookRepo $bookRepo)
public function __construct(EntityRepo $entityRepo)
{
$this->bookRepo = $bookRepo;
$this->entityRepo = $entityRepo;
parent::__construct();
}
/**
* Display the homepage.
*
* @return Response
*/
public function index()
{
$activity = Activity::latest();
$recents = $this->signedIn ? Views::getUserRecentlyViewed(10, 0) : $this->bookRepo->getLatest(10);
return view('home', ['activity' => $activity, 'recents' => $recents]);
$activity = Activity::latest(10);
$draftPages = $this->signedIn ? $this->entityRepo->getUserDraftPages(6) : [];
$recentFactor = count($draftPages) > 0 ? 0.5 : 1;
$recents = $this->signedIn ? Views::getUserRecentlyViewed(12*$recentFactor, 0) : $this->entityRepo->getRecentlyCreated('book', 12*$recentFactor);
$recentlyUpdatedPages = $this->entityRepo->getRecentlyUpdated('page', 12);
$customHomepage = false;
$books = false;
$booksViewType = false;
// Check book homepage
$bookHomepageSetting = setting('app-book-homepage');
if ($bookHomepageSetting) {
$books = $this->entityRepo->getAllPaginated('book', 18);
$booksViewType = setting()->getUser($this->currentUser, 'books_view_type', config('app.views.books', 'list'));
} else {
// Check custom homepage
$homepageSetting = setting('app-homepage');
if ($homepageSetting) {
$id = intval(explode(':', $homepageSetting)[0]);
$customHomepage = $this->entityRepo->getById('page', $id, false, true);
$this->entityRepo->renderPage($customHomepage, true);
}
}
$view = 'home';
if ($bookHomepageSetting) {
$view = 'home-book';
} else if ($customHomepage) {
$view = 'home-custom';
}
return view('common/' . $view, [
'activity' => $activity,
'recents' => $recents,
'recentlyUpdatedPages' => $recentlyUpdatedPages,
'draftPages' => $draftPages,
'customHomepage' => $customHomepage,
'books' => $books,
'booksViewType' => $booksViewType
]);
}
/**
* Get a js representation of the current translations
* @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
* @throws \Exception
*/
public function getTranslations()
{
$locale = app()->getLocale();
$cacheKey = 'GLOBAL_TRANSLATIONS_' . $locale;
if (cache()->has($cacheKey) && config('app.env') !== 'development') {
$resp = cache($cacheKey);
} else {
$translations = [
// Get only translations which might be used in JS
'common' => trans('common'),
'components' => trans('components'),
'entities' => trans('entities'),
'errors' => trans('errors')
];
if ($locale !== 'en') {
$enTrans = [
'common' => trans('common', [], 'en'),
'components' => trans('components', [], 'en'),
'entities' => trans('entities', [], 'en'),
'errors' => trans('errors', [], 'en')
];
$translations = array_replace_recursive($enTrans, $translations);
}
$resp = 'window.translations = ' . json_encode($translations);
cache()->put($cacheKey, $resp, 120);
}
return response($resp, 200, [
'Content-Type' => 'application/javascript'
]);
}
/**
* Get custom head HTML, Used in ajax calls to show in editor.
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function customHeadContent()
{
return view('partials/custom-head-content');
}
/**
* Show the view for /robots.txt
* @return $this
*/
public function getRobots()
{
$sitePublic = setting('app-public', false);
$allowRobots = config('app.allow_robots');
if ($allowRobots === null) {
$allowRobots = $sitePublic;
}
return response()
->view('common/robots', ['allowRobots' => $allowRobots])
->header('Content-Type', 'text/plain');
}
/**
* Show the route for 404 responses.
*/
public function getNotFound()
{
return response()->view('errors/404', [], 404);
}
}

View File

@@ -1,14 +1,11 @@
<?php
namespace BookStack\Http\Controllers;
<?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;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Intervention\Image\Facades\Image as ImageTool;
use Illuminate\Support\Facades\DB;
use BookStack\Image;
use BookStack\Repos\PageRepo;
@@ -20,8 +17,8 @@ class ImageController extends Controller
/**
* ImageController constructor.
* @param Image $image
* @param File $file
* @param Image $image
* @param File $file
* @param ImageRepo $imageRepo
*/
public function __construct(Image $image, File $file, ImageRepo $imageRepo)
@@ -32,9 +29,24 @@ 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
* @param int $page
* @return \Illuminate\Http\JsonResponse
*/
@@ -44,6 +56,24 @@ class ImageController extends Controller
return response()->json($imgData);
}
/**
* Search through images within a particular type.
* @param $type
* @param int $page
* @param Request $request
* @return mixed
*/
public function searchByType(Request $request, $type, $page = 0)
{
$this->validate($request, [
'term' => 'required|string'
]);
$searchTerm = $request->get('term');
$imgData = $this->imageRepo->searchPaginatedByType($type, $searchTerm, $page, 24);
return response()->json($imgData);
}
/**
* Get all images for a user.
* @param int $page
@@ -55,24 +85,78 @@ class ImageController extends Controller
return response()->json($imgData);
}
/**
* Get gallery images with a specific filter such as book or page
* @param $filter
* @param int $page
* @param Request $request
* @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\Response
*/
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);
}
$pageId = $request->get('page_id');
$imgData = $this->imageRepo->getGalleryFiltered(strtolower($filter), $pageId, $page, 24);
return response()->json($imgData);
}
/**
* Handles image uploads for use on pages.
* @param string $type
* @param string $type
* @param Request $request
* @return \Illuminate\Http\JsonResponse
* @throws \Exception
*/
public function uploadByType($type, Request $request)
{
$this->checkPermission('image-create');
$this->checkPermission('image-create-all');
$this->validate($request, [
'file' => 'image|mimes:jpeg,gif,png'
'file' => 'is_image'
]);
if (!$this->imageRepo->isValidType($type)) {
return $this->jsonError(trans('errors.image_upload_type_error'));
}
$imageUpload = $request->file('file');
try {
$image = $this->imageRepo->saveNew($imageUpload, $type);
$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);
}
@@ -80,6 +164,23 @@ class ImageController extends Controller
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
@@ -87,10 +188,12 @@ class ImageController extends Controller
* @param $height
* @param $crop
* @return \Illuminate\Http\JsonResponse
* @throws ImageUploadException
* @throws \Exception
*/
public function getThumbnail($id, $width, $height, $crop)
{
$this->checkPermission('image-create');
$this->checkPermission('image-create-all');
$image = $this->imageRepo->getById($id);
$thumbnailUrl = $this->imageRepo->getThumbnail($image, $width, $height, $crop == 'false');
return response()->json(['url' => $thumbnailUrl]);
@@ -98,46 +201,48 @@ class ImageController extends Controller
/**
* Update image details
* @param $imageId
* @param integer $imageId
* @param Request $request
* @return \Illuminate\Http\JsonResponse
* @throws ImageUploadException
* @throws \Exception
*/
public function update($imageId, Request $request)
{
$this->checkPermission('image-update');
$this->validate($request, [
'name' => 'required|min:2|string'
]);
$image = $this->imageRepo->getById($imageId);
$this->checkOwnablePermission('image-update', $image);
$image = $this->imageRepo->updateImageDetails($image, $request->all());
return response()->json($image);
}
/**
* Show the usage of an image on pages.
* @param EntityRepo $entityRepo
* @param $id
* @return \Illuminate\Http\JsonResponse
*/
public function usage(EntityRepo $entityRepo, $id)
{
$image = $this->imageRepo->getById($id);
$pageSearch = $entityRepo->searchForImage($image->url);
return response()->json($pageSearch);
}
/**
* Deletes an image and all thumbnail/image files
* @param PageRepo $pageRepo
* @param Request $request
* @param int $id
* @param int $id
* @return \Illuminate\Http\JsonResponse
* @throws \Exception
*/
public function destroy(PageRepo $pageRepo, Request $request, $id)
public function destroy($id)
{
$this->checkPermission('image-delete');
$image = $this->imageRepo->getById($id);
// Check if this image is used on any pages
$isForced = ($request->has('force') && ($request->get('force') === 'true') || $request->get('force') === true);
if (!$isForced) {
$pageSearch = $pageRepo->searchForImage($image->url);
if ($pageSearch !== false) {
return response()->json($pageSearch, 400);
}
}
$this->checkOwnablePermission('image-delete', $image);
$this->imageRepo->destroyImage($image);
return response()->json('Image Deleted');
return response()->json(trans('components.images_deleted'));
}
}

View File

@@ -1,79 +1,149 @@
<?php
namespace BookStack\Http\Controllers;
<?php namespace BookStack\Http\Controllers;
use Activity;
use BookStack\Exceptions\NotFoundException;
use BookStack\Repos\EntityRepo;
use BookStack\Repos\UserRepo;
use BookStack\Services\ExportService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use BookStack\Http\Requests;
use BookStack\Repos\BookRepo;
use BookStack\Repos\ChapterRepo;
use BookStack\Repos\PageRepo;
use Illuminate\Http\Response;
use Views;
use GatherContent\Htmldiff\Htmldiff;
class PageController extends Controller
{
protected $pageRepo;
protected $bookRepo;
protected $chapterRepo;
protected $entityRepo;
protected $exportService;
protected $userRepo;
/**
* PageController constructor.
* @param PageRepo $pageRepo
* @param BookRepo $bookRepo
* @param ChapterRepo $chapterRepo
* @param EntityRepo $entityRepo
* @param ExportService $exportService
* @param UserRepo $userRepo
*/
public function __construct(PageRepo $pageRepo, BookRepo $bookRepo, ChapterRepo $chapterRepo, ExportService $exportService)
public function __construct(EntityRepo $entityRepo, ExportService $exportService, UserRepo $userRepo)
{
$this->pageRepo = $pageRepo;
$this->bookRepo = $bookRepo;
$this->chapterRepo = $chapterRepo;
$this->entityRepo = $entityRepo;
$this->exportService = $exportService;
$this->userRepo = $userRepo;
parent::__construct();
}
/**
* Show the form for creating a new page.
*
* @param $bookSlug
* @param bool $chapterSlug
* @param string $bookSlug
* @param string $chapterSlug
* @return Response
* @internal param bool $pageSlug
* @throws NotFoundException
*/
public function create($bookSlug, $chapterSlug = false)
public function create($bookSlug, $chapterSlug = null)
{
$this->checkPermission('page-create');
$book = $this->bookRepo->getBySlug($bookSlug);
$chapter = $chapterSlug ? $this->chapterRepo->getBySlug($chapterSlug, $book->id) : false;
$this->setPageTitle('Create New Page');
return view('pages/create', ['book' => $book, 'chapter' => $chapter]);
if ($chapterSlug !== null) {
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
$book = $chapter->book;
} else {
$chapter = null;
$book = $this->entityRepo->getBySlug('book', $bookSlug);
}
$parent = $chapter ? $chapter : $book;
$this->checkOwnablePermission('page-create', $parent);
// Redirect to draft edit screen if signed in
if ($this->signedIn) {
$draft = $this->entityRepo->getDraftPage($book, $chapter);
return redirect($draft->getUrl());
}
// Otherwise show the edit view if they're a guest
$this->setPageTitle(trans('entities.pages_new'));
return view('pages/guest-create', ['parent' => $parent]);
}
/**
* Store a newly created page in storage.
*
* Create a new page as a guest user.
* @param Request $request
* @param string $bookSlug
* @param string|null $chapterSlug
* @return mixed
* @throws NotFoundException
*/
public function createAsGuest(Request $request, $bookSlug, $chapterSlug = null)
{
$this->validate($request, [
'name' => 'required|string|max:255'
]);
if ($chapterSlug !== null) {
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
$book = $chapter->book;
} else {
$chapter = null;
$book = $this->entityRepo->getBySlug('book', $bookSlug);
}
$parent = $chapter ? $chapter : $book;
$this->checkOwnablePermission('page-create', $parent);
$page = $this->entityRepo->getDraftPage($book, $chapter);
$this->entityRepo->publishPageDraft($page, [
'name' => $request->get('name'),
'html' => ''
]);
return redirect($page->getUrl('/edit'));
}
/**
* Show form to continue editing a draft page.
* @param string $bookSlug
* @param int $pageId
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function editDraft($bookSlug, $pageId)
{
$draft = $this->entityRepo->getById('page', $pageId, true);
$this->checkOwnablePermission('page-create', $draft->parent);
$this->setPageTitle(trans('entities.pages_edit_draft'));
$draftsEnabled = $this->signedIn;
return view('pages/edit', [
'page' => $draft,
'book' => $draft->book,
'isDraft' => true,
'draftsEnabled' => $draftsEnabled
]);
}
/**
* Store a new page by changing a draft into a page.
* @param Request $request
* @param $bookSlug
* @param string $bookSlug
* @param int $pageId
* @return Response
*/
public function store(Request $request, $bookSlug)
public function store(Request $request, $bookSlug, $pageId)
{
$this->checkPermission('page-create');
$this->validate($request, [
'name' => 'required|string|max:255'
'name' => 'required|string|max:255'
]);
$input = $request->all();
$book = $this->bookRepo->getBySlug($bookSlug);
$chapterId = ($request->has('chapter') && $this->chapterRepo->idExists($request->get('chapter'))) ? $request->get('chapter') : null;
$input['priority'] = $this->bookRepo->getNewPriority($book);
$draftPage = $this->entityRepo->getById('page', $pageId, true);
$book = $draftPage->book;
$page = $this->pageRepo->saveNew($input, $book, $chapterId);
$parent = $draftPage->parent;
$this->checkOwnablePermission('page-create', $parent);
if ($parent->isA('chapter')) {
$input['priority'] = $this->entityRepo->getNewChapterPriority($parent);
} else {
$input['priority'] = $this->entityRepo->getNewBookPriority($parent);
}
$page = $this->entityRepo->publishPageDraft($draftPage, $input);
Activity::add($page, 'page_create', $book->id);
return redirect($page->getUrl());
@@ -81,201 +151,530 @@ class PageController extends Controller
/**
* Display the specified page.
*
* @param $bookSlug
* @param $pageSlug
* If the page is not found via the slug the revisions are searched for a match.
* @param string $bookSlug
* @param string $pageSlug
* @return Response
* @throws NotFoundException
*/
public function show($bookSlug, $pageSlug)
{
$book = $this->bookRepo->getBySlug($bookSlug);
$page = $this->pageRepo->getBySlug($pageSlug, $book->id);
$sidebarTree = $this->bookRepo->getChildren($book);
try {
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
} catch (NotFoundException $e) {
$page = $this->entityRepo->getPageByOldSlug($pageSlug, $bookSlug);
if ($page === null) {
throw $e;
}
return redirect($page->getUrl());
}
$this->checkOwnablePermission('page-view', $page);
$page->html = $this->entityRepo->renderPage($page);
$sidebarTree = $this->entityRepo->getBookChildren($page->book);
$pageNav = $this->entityRepo->getPageNav($page->html);
// check if the comment's are enabled
$commentsEnabled = !setting('app-disable-comments');
if ($commentsEnabled) {
$page->load(['comments.createdBy']);
}
Views::add($page);
$this->setPageTitle($page->getShortName());
return view('pages/show', ['page' => $page, 'book' => $book, 'current' => $page, 'sidebarTree' => $sidebarTree]);
return view('pages/show', [
'page' => $page,'book' => $page->book,
'current' => $page,
'sidebarTree' => $sidebarTree,
'commentsEnabled' => $commentsEnabled,
'pageNav' => $pageNav
]);
}
/**
* Get page from an ajax request.
* @param int $pageId
* @return \Illuminate\Http\JsonResponse
*/
public function getPageAjax($pageId)
{
$page = $this->entityRepo->getById('page', $pageId);
return response()->json($page);
}
/**
* Show the form for editing the specified page.
*
* @param $bookSlug
* @param $pageSlug
* @param string $bookSlug
* @param string $pageSlug
* @return Response
*/
public function edit($bookSlug, $pageSlug)
{
$this->checkPermission('page-update');
$book = $this->bookRepo->getBySlug($bookSlug);
$page = $this->pageRepo->getBySlug($pageSlug, $book->id);
$this->setPageTitle('Editing Page ' . $page->getShortName());
return view('pages/edit', ['page' => $page, 'book' => $book, 'current' => $page]);
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$this->checkOwnablePermission('page-update', $page);
$this->setPageTitle(trans('entities.pages_editing_named', ['pageName'=>$page->getShortName()]));
$page->isDraft = false;
// Check for active editing
$warnings = [];
if ($this->entityRepo->isPageEditingActive($page, 60)) {
$warnings[] = $this->entityRepo->getPageEditingActiveMessage($page, 60);
}
// Check for a current draft version for this user
if ($this->entityRepo->hasUserGotPageDraft($page, $this->currentUser->id)) {
$draft = $this->entityRepo->getUserPageDraft($page, $this->currentUser->id);
$page->name = $draft->name;
$page->html = $draft->html;
$page->markdown = $draft->markdown;
$page->isDraft = true;
$warnings [] = $this->entityRepo->getUserPageDraftMessage($draft);
}
if (count($warnings) > 0) {
session()->flash('warning', implode("\n", $warnings));
}
$draftsEnabled = $this->signedIn;
return view('pages/edit', [
'page' => $page,
'book' => $page->book,
'current' => $page,
'draftsEnabled' => $draftsEnabled
]);
}
/**
* Update the specified page in storage.
*
* @param Request $request
* @param $bookSlug
* @param $pageSlug
* @param string $bookSlug
* @param string $pageSlug
* @return Response
*/
public function update(Request $request, $bookSlug, $pageSlug)
{
$this->checkPermission('page-update');
$this->validate($request, [
'name' => 'required|string|max:255'
'name' => 'required|string|max:255'
]);
$book = $this->bookRepo->getBySlug($bookSlug);
$page = $this->pageRepo->getBySlug($pageSlug, $book->id);
$this->pageRepo->updatePage($page, $book->id, $request->all());
Activity::add($page, 'page_update', $book->id);
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$this->checkOwnablePermission('page-update', $page);
$this->entityRepo->updatePage($page, $page->book->id, $request->all());
Activity::add($page, 'page_update', $page->book->id);
return redirect($page->getUrl());
}
/**
* Save a draft update as a revision.
* @param Request $request
* @param int $pageId
* @return \Illuminate\Http\JsonResponse
*/
public function saveDraft(Request $request, $pageId)
{
$page = $this->entityRepo->getById('page', $pageId, true);
$this->checkOwnablePermission('page-update', $page);
if (!$this->signedIn) {
return response()->json([
'status' => 'error',
'message' => trans('errors.guests_cannot_save_drafts'),
], 500);
}
$draft = $this->entityRepo->updatePageDraft($page, $request->only(['name', 'html', 'markdown']));
$updateTime = $draft->updated_at->timestamp;
return response()->json([
'status' => 'success',
'message' => trans('entities.pages_edit_draft_save_at'),
'timestamp' => $updateTime
]);
}
/**
* Redirect from a special link url which
* uses the page id rather than the name.
* @param $pageId
* @param int $pageId
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function redirectFromLink($pageId)
{
$page = $this->pageRepo->getById($pageId);
$page = $this->entityRepo->getById('page', $pageId);
return redirect($page->getUrl());
}
/**
* Show the deletion page for the specified page.
* @param $bookSlug
* @param $pageSlug
* @param string $bookSlug
* @param string $pageSlug
* @return \Illuminate\View\View
*/
public function showDelete($bookSlug, $pageSlug)
{
$this->checkPermission('page-delete');
$book = $this->bookRepo->getBySlug($bookSlug);
$page = $this->pageRepo->getBySlug($pageSlug, $book->id);
$this->setPageTitle('Delete Page ' . $page->getShortName());
return view('pages/delete', ['book' => $book, 'page' => $page, 'current' => $page]);
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$this->checkOwnablePermission('page-delete', $page);
$this->setPageTitle(trans('entities.pages_delete_named', ['pageName'=>$page->getShortName()]));
return view('pages/delete', ['book' => $page->book, 'page' => $page, 'current' => $page]);
}
/**
* Show the deletion page for the specified page.
* @param string $bookSlug
* @param int $pageId
* @return \Illuminate\View\View
* @throws NotFoundException
*/
public function showDeleteDraft($bookSlug, $pageId)
{
$page = $this->entityRepo->getById('page', $pageId, true);
$this->checkOwnablePermission('page-update', $page);
$this->setPageTitle(trans('entities.pages_delete_draft_named', ['pageName'=>$page->getShortName()]));
return view('pages/delete', ['book' => $page->book, 'page' => $page, 'current' => $page]);
}
/**
* Remove the specified page from storage.
*
* @param $bookSlug
* @param $pageSlug
* @param string $bookSlug
* @param string $pageSlug
* @return Response
* @internal param int $id
*/
public function destroy($bookSlug, $pageSlug)
{
$this->checkPermission('page-delete');
$book = $this->bookRepo->getBySlug($bookSlug);
$page = $this->pageRepo->getBySlug($pageSlug, $book->id);
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$book = $page->book;
$this->checkOwnablePermission('page-delete', $page);
$this->entityRepo->destroyPage($page);
Activity::addMessage('page_delete', $book->id, $page->name);
$this->pageRepo->destroy($page);
session()->flash('success', trans('entities.pages_delete_success'));
return redirect($book->getUrl());
}
/**
* Remove the specified draft page from storage.
* @param string $bookSlug
* @param int $pageId
* @return Response
* @throws NotFoundException
*/
public function destroyDraft($bookSlug, $pageId)
{
$page = $this->entityRepo->getById('page', $pageId, true);
$book = $page->book;
$this->checkOwnablePermission('page-update', $page);
session()->flash('success', trans('entities.pages_delete_draft_success'));
$this->entityRepo->destroyPage($page);
return redirect($book->getUrl());
}
/**
* Shows the last revisions for this page.
* @param $bookSlug
* @param $pageSlug
* @param string $bookSlug
* @param string $pageSlug
* @return \Illuminate\View\View
*/
public function showRevisions($bookSlug, $pageSlug)
{
$book = $this->bookRepo->getBySlug($bookSlug);
$page = $this->pageRepo->getBySlug($pageSlug, $book->id);
$this->setPageTitle('Revisions For ' . $page->getShortName());
return view('pages/revisions', ['page' => $page, 'book' => $book, 'current' => $page]);
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$this->setPageTitle(trans('entities.pages_revisions_named', ['pageName'=>$page->getShortName()]));
return view('pages/revisions', ['page' => $page, 'book' => $page->book, 'current' => $page]);
}
/**
* Shows a preview of a single revision
* @param $bookSlug
* @param $pageSlug
* @param $revisionId
* @param string $bookSlug
* @param string $pageSlug
* @param int $revisionId
* @return \Illuminate\View\View
*/
public function showRevision($bookSlug, $pageSlug, $revisionId)
{
$book = $this->bookRepo->getBySlug($bookSlug);
$page = $this->pageRepo->getBySlug($pageSlug, $book->id);
$revision = $this->pageRepo->getRevisionById($revisionId);
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$revision = $page->revisions()->where('id', '=', $revisionId)->first();
if ($revision === null) {
abort(404);
}
$page->fill($revision->toArray());
$this->setPageTitle('Page Revision For ' . $page->getShortName());
return view('pages/revision', ['page' => $page, 'book' => $book]);
$this->setPageTitle(trans('entities.pages_revision_named', ['pageName' => $page->getShortName()]));
return view('pages/revision', [
'page' => $page,
'book' => $page->book,
'revision' => $revision
]);
}
/**
* Shows the changes of a single revision
* @param string $bookSlug
* @param string $pageSlug
* @param int $revisionId
* @return \Illuminate\View\View
*/
public function showRevisionChanges($bookSlug, $pageSlug, $revisionId)
{
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$revision = $page->revisions()->where('id', '=', $revisionId)->first();
if ($revision === null) {
abort(404);
}
$prev = $revision->getPrevious();
$prevContent = ($prev === null) ? '' : $prev->html;
$diff = (new Htmldiff)->diff($prevContent, $revision->html);
$page->fill($revision->toArray());
$this->setPageTitle(trans('entities.pages_revision_named', ['pageName'=>$page->getShortName()]));
return view('pages/revision', [
'page' => $page,
'book' => $page->book,
'diff' => $diff,
'revision' => $revision
]);
}
/**
* Restores a page using the content of the specified revision.
* @param $bookSlug
* @param $pageSlug
* @param $revisionId
* @param string $bookSlug
* @param string $pageSlug
* @param int $revisionId
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function restoreRevision($bookSlug, $pageSlug, $revisionId)
{
$this->checkPermission('page-update');
$book = $this->bookRepo->getBySlug($bookSlug);
$page = $this->pageRepo->getBySlug($pageSlug, $book->id);
$page = $this->pageRepo->restoreRevision($page, $book, $revisionId);
Activity::add($page, 'page_restore', $book->id);
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$this->checkOwnablePermission('page-update', $page);
$page = $this->entityRepo->restorePageRevision($page, $page->book, $revisionId);
Activity::add($page, 'page_restore', $page->book->id);
return redirect($page->getUrl());
}
/**
* Exports a page to pdf format using barryvdh/laravel-dompdf wrapper.
* Exports a page to a PDF.
* https://github.com/barryvdh/laravel-dompdf
* @param $bookSlug
* @param $pageSlug
* @param string $bookSlug
* @param string $pageSlug
* @return \Illuminate\Http\Response
*/
public function exportPdf($bookSlug, $pageSlug)
{
$book = $this->bookRepo->getBySlug($bookSlug);
$page = $this->pageRepo->getBySlug($pageSlug, $book->id);
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$page->html = $this->entityRepo->renderPage($page);
$pdfContent = $this->exportService->pageToPdf($page);
return response()->make($pdfContent, 200, [
'Content-Type' => 'application/octet-stream',
'Content-Disposition' => 'attachment; filename="'.$pageSlug.'.pdf'
'Content-Type' => 'application/octet-stream',
'Content-Disposition' => 'attachment; filename="' . $pageSlug . '.pdf'
]);
}
/**
* Export a page to a self-contained HTML file.
* @param $bookSlug
* @param $pageSlug
* @param string $bookSlug
* @param string $pageSlug
* @return \Illuminate\Http\Response
*/
public function exportHtml($bookSlug, $pageSlug)
{
$book = $this->bookRepo->getBySlug($bookSlug);
$page = $this->pageRepo->getBySlug($pageSlug, $book->id);
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$page->html = $this->entityRepo->renderPage($page);
$containedHtml = $this->exportService->pageToContainedHtml($page);
return response()->make($containedHtml, 200, [
'Content-Type' => 'application/octet-stream',
'Content-Disposition' => 'attachment; filename="'.$pageSlug.'.html'
'Content-Type' => 'application/octet-stream',
'Content-Disposition' => 'attachment; filename="' . $pageSlug . '.html'
]);
}
/**
* Export a page to a simple plaintext .txt file.
* @param $bookSlug
* @param $pageSlug
* @param string $bookSlug
* @param string $pageSlug
* @return \Illuminate\Http\Response
*/
public function exportPlainText($bookSlug, $pageSlug)
{
$book = $this->bookRepo->getBySlug($bookSlug);
$page = $this->pageRepo->getBySlug($pageSlug, $book->id);
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$containedHtml = $this->exportService->pageToPlainText($page);
return response()->make($containedHtml, 200, [
'Content-Type' => 'application/octet-stream',
'Content-Disposition' => 'attachment; filename="'.$pageSlug.'.txt'
'Content-Type' => 'application/octet-stream',
'Content-Disposition' => 'attachment; filename="' . $pageSlug . '.txt'
]);
}
/**
* Show a listing of recently created pages
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function showRecentlyCreated()
{
$pages = $this->entityRepo->getRecentlyCreatedPaginated('page', 20)->setPath(baseUrl('/pages/recently-created'));
return view('pages/detailed-listing', [
'title' => trans('entities.recently_created_pages'),
'pages' => $pages
]);
}
/**
* Show a listing of recently created pages
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function showRecentlyUpdated()
{
$pages = $this->entityRepo->getRecentlyUpdatedPaginated('page', 20)->setPath(baseUrl('/pages/recently-updated'));
return view('pages/detailed-listing', [
'title' => trans('entities.recently_updated_pages'),
'pages' => $pages
]);
}
/**
* Show the Restrictions view.
* @param string $bookSlug
* @param string $pageSlug
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function showRestrict($bookSlug, $pageSlug)
{
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$this->checkOwnablePermission('restrictions-manage', $page);
$roles = $this->userRepo->getRestrictableRoles();
return view('pages/restrictions', [
'page' => $page,
'roles' => $roles
]);
}
/**
* Show the view to choose a new parent to move a page into.
* @param string $bookSlug
* @param string $pageSlug
* @return mixed
* @throws NotFoundException
*/
public function showMove($bookSlug, $pageSlug)
{
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$this->checkOwnablePermission('page-update', $page);
return view('pages/move', [
'book' => $page->book,
'page' => $page
]);
}
/**
* Does the action of moving the location of a page
* @param string $bookSlug
* @param string $pageSlug
* @param Request $request
* @return mixed
* @throws NotFoundException
*/
public function move($bookSlug, $pageSlug, Request $request)
{
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$this->checkOwnablePermission('page-update', $page);
$entitySelection = $request->get('entity_selection', null);
if ($entitySelection === null || $entitySelection === '') {
return redirect($page->getUrl());
}
$stringExploded = explode(':', $entitySelection);
$entityType = $stringExploded[0];
$entityId = intval($stringExploded[1]);
try {
$parent = $this->entityRepo->getById($entityType, $entityId);
} catch (\Exception $e) {
session()->flash(trans('entities.selected_book_chapter_not_found'));
return redirect()->back();
}
$this->checkOwnablePermission('page-create', $parent);
$this->entityRepo->changePageParent($page, $parent);
Activity::add($page, 'page_move', $page->book->id);
session()->flash('success', trans('entities.pages_move_success', ['parentName' => $parent->name]));
return redirect($page->getUrl());
}
/**
* Show the view to copy a page.
* @param string $bookSlug
* @param string $pageSlug
* @return mixed
* @throws NotFoundException
*/
public function showCopy($bookSlug, $pageSlug)
{
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$this->checkOwnablePermission('page-update', $page);
session()->flashInput(['name' => $page->name]);
return view('pages/copy', [
'book' => $page->book,
'page' => $page
]);
}
/**
* Create a copy of a page within the requested target destination.
* @param string $bookSlug
* @param string $pageSlug
* @param Request $request
* @return mixed
* @throws NotFoundException
*/
public function copy($bookSlug, $pageSlug, Request $request)
{
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$this->checkOwnablePermission('page-update', $page);
$entitySelection = $request->get('entity_selection', null);
if ($entitySelection === null || $entitySelection === '') {
$parent = $page->chapter ? $page->chapter : $page->book;
} else {
$stringExploded = explode(':', $entitySelection);
$entityType = $stringExploded[0];
$entityId = intval($stringExploded[1]);
try {
$parent = $this->entityRepo->getById($entityType, $entityId);
} catch (\Exception $e) {
session()->flash(trans('entities.selected_book_chapter_not_found'));
return redirect()->back();
}
}
$this->checkOwnablePermission('page-create', $parent);
$pageCopy = $this->entityRepo->copyPage($page, $parent, $request->get('name', ''));
Activity::add($pageCopy, 'page_create', $pageCopy->book->id);
session()->flash('success', trans('entities.pages_copy_success'));
return redirect($pageCopy->getUrl());
}
/**
* Set the permissions for this page.
* @param string $bookSlug
* @param string $pageSlug
* @param Request $request
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
* @throws NotFoundException
*/
public function restrict($bookSlug, $pageSlug, Request $request)
{
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$this->checkOwnablePermission('restrictions-manage', $page);
$this->entityRepo->updateEntityPermissionsFromRequest($request, $page);
session()->flash('success', trans('entities.pages_permissions_success'));
return redirect($page->getUrl());
}
}

View File

@@ -0,0 +1,133 @@
<?php namespace BookStack\Http\Controllers;
use BookStack\Exceptions\PermissionsException;
use BookStack\Repos\PermissionsRepo;
use Illuminate\Http\Request;
class PermissionController extends Controller
{
protected $permissionsRepo;
/**
* PermissionController constructor.
* @param PermissionsRepo $permissionsRepo
*/
public function __construct(PermissionsRepo $permissionsRepo)
{
$this->permissionsRepo = $permissionsRepo;
parent::__construct();
}
/**
* Show a listing of the roles in the system.
*/
public function listRoles()
{
$this->checkPermission('user-roles-manage');
$roles = $this->permissionsRepo->getAllRoles();
return view('settings/roles/index', ['roles' => $roles]);
}
/**
* Show the form to create a new role
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function createRole()
{
$this->checkPermission('user-roles-manage');
return view('settings/roles/create');
}
/**
* Store a new role in the system.
* @param Request $request
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function storeRole(Request $request)
{
$this->checkPermission('user-roles-manage');
$this->validate($request, [
'display_name' => 'required|min:3|max:200',
'description' => 'max:250'
]);
$this->permissionsRepo->saveNewRole($request->all());
session()->flash('success', trans('settings.role_create_success'));
return redirect('/settings/roles');
}
/**
* Show the form for editing a user role.
* @param $id
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
* @throws PermissionsException
*/
public function editRole($id)
{
$this->checkPermission('user-roles-manage');
$role = $this->permissionsRepo->getRoleById($id);
if ($role->hidden) {
throw new PermissionsException(trans('errors.role_cannot_be_edited'));
}
return view('settings/roles/edit', ['role' => $role]);
}
/**
* Updates a user role.
* @param $id
* @param Request $request
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
* @throws PermissionsException
*/
public function updateRole($id, Request $request)
{
$this->checkPermission('user-roles-manage');
$this->validate($request, [
'display_name' => 'required|min:3|max:200',
'description' => 'max:250'
]);
$this->permissionsRepo->updateRole($id, $request->all());
session()->flash('success', trans('settings.role_update_success'));
return redirect('/settings/roles');
}
/**
* Show the view to delete a role.
* Offers the chance to migrate users.
* @param $id
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function showDeleteRole($id)
{
$this->checkPermission('user-roles-manage');
$role = $this->permissionsRepo->getRoleById($id);
$roles = $this->permissionsRepo->getAllRolesExcept($role);
$blankRole = $role->newInstance(['display_name' => trans('settings.role_delete_no_migration')]);
$roles->prepend($blankRole);
return view('settings/roles/delete', ['role' => $role, 'roles' => $roles]);
}
/**
* Delete a role from the system,
* Migrate from a previous role if set.
* @param $id
* @param Request $request
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function deleteRole($id, Request $request)
{
$this->checkPermission('user-roles-manage');
try {
$this->permissionsRepo->deleteRole($id, $request->get('migrate_role_id'));
} catch (PermissionsException $e) {
session()->flash('error', $e->getMessage());
return redirect()->back();
}
session()->flash('success', trans('settings.role_delete_success'));
return redirect('/settings/roles');
}
}

View File

@@ -1,32 +1,27 @@
<?php
namespace BookStack\Http\Controllers;
<?php namespace BookStack\Http\Controllers;
use BookStack\Repos\EntityRepo;
use BookStack\Services\SearchService;
use BookStack\Services\ViewService;
use Illuminate\Http\Request;
use BookStack\Http\Requests;
use BookStack\Http\Controllers\Controller;
use BookStack\Repos\BookRepo;
use BookStack\Repos\ChapterRepo;
use BookStack\Repos\PageRepo;
class SearchController extends Controller
{
protected $pageRepo;
protected $bookRepo;
protected $chapterRepo;
protected $entityRepo;
protected $viewService;
protected $searchService;
/**
* SearchController constructor.
* @param $pageRepo
* @param $bookRepo
* @param $chapterRepo
* @param EntityRepo $entityRepo
* @param ViewService $viewService
* @param SearchService $searchService
*/
public function __construct(PageRepo $pageRepo, BookRepo $bookRepo, ChapterRepo $chapterRepo)
public function __construct(EntityRepo $entityRepo, ViewService $viewService, SearchService $searchService)
{
$this->pageRepo = $pageRepo;
$this->bookRepo = $bookRepo;
$this->chapterRepo = $chapterRepo;
$this->entityRepo = $entityRepo;
$this->viewService = $viewService;
$this->searchService = $searchService;
parent::__construct();
}
@@ -36,19 +31,26 @@ class SearchController extends Controller
* @return \Illuminate\View\View
* @internal param string $searchTerm
*/
public function searchAll(Request $request)
public function search(Request $request)
{
if (!$request->has('term')) {
return redirect()->back();
}
$searchTerm = $request->get('term');
$pages = $this->pageRepo->getBySearch($searchTerm);
$books = $this->bookRepo->getBySearch($searchTerm);
$chapters = $this->chapterRepo->getBySearch($searchTerm);
$this->setPageTitle('Search For ' . $searchTerm);
return view('search/all', ['pages' => $pages, 'books' => $books, 'chapters' => $chapters, 'searchTerm' => $searchTerm]);
$this->setPageTitle(trans('entities.search_for_term', ['term' => $searchTerm]));
$page = intval($request->get('page', '0')) ?: 1;
$nextPageLink = baseUrl('/search?term=' . urlencode($searchTerm) . '&page=' . ($page+1));
$results = $this->searchService->searchEntities($searchTerm, 'all', $page, 20);
return view('search/all', [
'entities' => $results['results'],
'totalResults' => $results['total'],
'searchTerm' => $searchTerm,
'hasNextPage' => $results['has_more'],
'nextPageLink' => $nextPageLink
]);
}
/**
* Searches all entities within a book.
* @param Request $request
@@ -58,14 +60,48 @@ class SearchController extends Controller
*/
public function searchBook(Request $request, $bookId)
{
if (!$request->has('term')) {
return redirect()->back();
}
$searchTerm = $request->get('term');
$searchWhereTerms = [['book_id', '=', $bookId]];
$pages = $this->pageRepo->getBySearch($searchTerm, $searchWhereTerms);
$chapters = $this->chapterRepo->getBySearch($searchTerm, $searchWhereTerms);
return view('search/book', ['pages' => $pages, 'chapters' => $chapters, 'searchTerm' => $searchTerm]);
$term = $request->get('term', '');
$results = $this->searchService->searchBook($bookId, $term);
return view('partials/entity-list', ['entities' => $results]);
}
/**
* Searches all entities within a chapter.
* @param Request $request
* @param integer $chapterId
* @return \Illuminate\View\View
* @internal param string $searchTerm
*/
public function searchChapter(Request $request, $chapterId)
{
$term = $request->get('term', '');
$results = $this->searchService->searchChapter($chapterId, $term);
return view('partials/entity-list', ['entities' => $results]);
}
/**
* Search for a list of entities and return a partial HTML response of matching entities.
* Returns the most popular entities if no search is provided.
* @param Request $request
* @return mixed
*/
public function searchEntitiesAjax(Request $request)
{
$entityTypes = $request->filled('types') ? collect(explode(',', $request->get('types'))) : collect(['page', 'chapter', 'book']);
$searchTerm = $request->get('term', false);
$permission = $request->get('permission', 'view');
// Search for entities otherwise show most popular
if ($searchTerm !== false) {
$searchTerm .= ' {type:'. implode('|', $entityTypes->toArray()) .'}';
$entities = $this->searchService->searchEntities($searchTerm, 'all', 1, 20, $permission)['results'];
} else {
$entityNames = $entityTypes->map(function ($type) {
return 'BookStack\\' . ucfirst($type);
})->toArray();
$entities = $this->viewService->getPopular(20, 0, $entityNames, $permission);
}
return view('search/entity-ajax-list', ['entities' => $entities]);
}
}

View File

@@ -1,48 +1,91 @@
<?php
namespace BookStack\Http\Controllers;
<?php namespace BookStack\Http\Controllers;
use BookStack\Services\ImageService;
use Illuminate\Http\Request;
use BookStack\Http\Requests;
use BookStack\Http\Controllers\Controller;
use Illuminate\Http\Response;
use Setting;
class SettingController extends Controller
{
/**
* Display a listing of the settings.
*
* @return Response
*/
public function index()
{
$this->checkPermission('settings-update');
$this->setPageTitle('Settings');
return view('settings/index');
}
$this->checkPermission('settings-manage');
$this->setPageTitle(trans('settings.settings'));
// Get application version
$version = trim(file_get_contents(base_path('version')));
return view('settings/index', ['version' => $version]);
}
/**
* Update the specified settings in storage.
*
* @param Request $request
* @param Request $request
* @return Response
*/
public function update(Request $request)
{
$this->preventAccessForDemoUsers();
$this->checkPermission('settings-update');
$this->checkPermission('settings-manage');
// Cycles through posted settings and update them
foreach($request->all() as $name => $value) {
if(strpos($name, 'setting-') !== 0) continue;
foreach ($request->all() as $name => $value) {
if (strpos($name, 'setting-') !== 0) {
continue;
}
$key = str_replace('setting-', '', trim($name));
Setting::put($key, $value);
}
session()->flash('success', 'Settings Saved');
session()->flash('success', trans('settings.settings_save_success'));
return redirect('/settings');
}
/**
* Show the page for application maintenance.
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function showMaintenance()
{
$this->checkPermission('settings-manage');
$this->setPageTitle(trans('settings.maint'));
// Get application version
$version = trim(file_get_contents(base_path('version')));
return view('settings/maintenance', ['version' => $version]);
}
/**
* Action to clean-up images in the system.
* @param Request $request
* @param ImageService $imageService
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function cleanupImages(Request $request, ImageService $imageService)
{
$this->checkPermission('settings-manage');
$checkRevisions = !($request->get('ignore_revisions', 'false') === 'true');
$dryRun = !($request->has('confirm'));
$imagesToDelete = $imageService->deleteUnusedImages($checkRevisions, $dryRun);
$deleteCount = count($imagesToDelete);
if ($deleteCount === 0) {
session()->flash('warning', trans('settings.maint_image_cleanup_nothing_found'));
return redirect('/settings/maintenance')->withInput();
}
if ($dryRun) {
session()->flash('cleanup-images-warning', trans('settings.maint_image_cleanup_warning', ['count' => $deleteCount]));
} else {
session()->flash('success', trans('settings.maint_image_cleanup_success', ['count' => $deleteCount]));
}
return redirect('/settings/maintenance#image-cleanup')->withInput();
}
}

View File

@@ -0,0 +1,57 @@
<?php namespace BookStack\Http\Controllers;
use BookStack\Repos\TagRepo;
use Illuminate\Http\Request;
class TagController extends Controller
{
protected $tagRepo;
/**
* TagController constructor.
* @param $tagRepo
*/
public function __construct(TagRepo $tagRepo)
{
$this->tagRepo = $tagRepo;
parent::__construct();
}
/**
* Get all the Tags for a particular entity
* @param $entityType
* @param $entityId
* @return \Illuminate\Http\JsonResponse
*/
public function getForEntity($entityType, $entityId)
{
$tags = $this->tagRepo->getForEntity($entityType, $entityId);
return response()->json($tags);
}
/**
* Get tag name suggestions from a given search term.
* @param Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function getNameSuggestions(Request $request)
{
$searchTerm = $request->get('search', false);
$suggestions = $this->tagRepo->getNameSuggestions($searchTerm);
return response()->json($suggestions);
}
/**
* Get tag value suggestions from a given search term.
* @param Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function getValueSuggestions(Request $request)
{
$searchTerm = $request->get('search', false);
$tagName = $request->get('name', false);
$suggestions = $this->tagRepo->getValueSuggestions($searchTerm, $tagName);
return response()->json($suggestions);
}
}

View File

@@ -1,11 +1,8 @@
<?php
namespace BookStack\Http\Controllers;
<?php namespace BookStack\Http\Controllers;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use BookStack\Http\Requests;
use BookStack\Repos\UserRepo;
use BookStack\Services\SocialAuthService;
use BookStack\User;
@@ -30,13 +27,21 @@ class UserController extends Controller
/**
* Display a listing of the users.
* @param Request $request
* @return Response
*/
public function index()
public function index(Request $request)
{
$users = $this->user->all();
$this->setPageTitle('Users');
return view('users/index', ['users' => $users]);
$this->checkPermission('users-manage');
$listDetails = [
'order' => $request->get('order', 'asc'),
'search' => $request->get('search', ''),
'sort' => $request->get('sort', 'name'),
];
$users = $this->userRepo->getAllUsersPaginatedAndSorted(20, $listDetails);
$this->setPageTitle(trans('settings.users'));
$users->appends($listDetails);
return view('users/index', ['users' => $users, 'listDetails' => $listDetails]);
}
/**
@@ -45,9 +50,10 @@ class UserController extends Controller
*/
public function create()
{
$this->checkPermission('user-create');
$this->checkPermission('users-manage');
$authMethod = config('auth.method');
return view('users/create', ['authMethod' => $authMethod]);
$roles = $this->userRepo->getAllRoles();
return view('users/create', ['authMethod' => $authMethod, 'roles' => $roles]);
}
/**
@@ -57,11 +63,10 @@ class UserController extends Controller
*/
public function store(Request $request)
{
$this->checkPermission('user-create');
$this->checkPermission('users-manage');
$validationRules = [
'name' => 'required',
'email' => 'required|email|unique:users,email',
'role' => 'required|exists:roles,id'
'email' => 'required|email|unique:users,email'
];
$authMethod = config('auth.method');
@@ -73,7 +78,6 @@ class UserController extends Controller
}
$this->validate($request, $validationRules);
$user = $this->user->fill($request->all());
if ($authMethod === 'standard') {
@@ -83,18 +87,16 @@ class UserController extends Controller
}
$user->save();
$user->attachRoleId($request->get('role'));
// Get avatar from gravatar and save
if (!config('services.disable_services')) {
$avatar = \Images::saveUserGravatar($user);
$user->avatar()->associate($avatar);
$user->save();
if ($request->filled('roles')) {
$roles = $request->get('roles');
$user->roles()->sync($roles);
}
return redirect('/users');
}
$this->userRepo->downloadGravatarToUserAvatar($user);
return redirect('/settings/users');
}
/**
* Show the form for editing the specified user.
@@ -104,16 +106,18 @@ class UserController extends Controller
*/
public function edit($id, SocialAuthService $socialAuthService)
{
$this->checkPermissionOr('user-update', function () use ($id) {
$this->checkPermissionOr('users-manage', function () use ($id) {
return $this->currentUser->id == $id;
});
$authMethod = config('auth.method');
$user = $this->user->findOrFail($id);
$authMethod = ($user->system_name) ? 'system' : config('auth.method');
$activeSocialDrivers = $socialAuthService->getActiveDrivers();
$this->setPageTitle('User Profile');
return view('users/edit', ['user' => $user, 'activeSocialDrivers' => $activeSocialDrivers, 'authMethod' => $authMethod]);
$this->setPageTitle(trans('settings.user_profile'));
$roles = $this->userRepo->getAllRoles();
return view('users/edit', ['user' => $user, 'activeSocialDrivers' => $activeSocialDrivers, 'authMethod' => $authMethod, 'roles' => $roles]);
}
/**
@@ -125,56 +129,65 @@ class UserController extends Controller
public function update(Request $request, $id)
{
$this->preventAccessForDemoUsers();
$this->checkPermissionOr('user-update', function () use ($id) {
$this->checkPermissionOr('users-manage', function () use ($id) {
return $this->currentUser->id == $id;
});
$this->validate($request, [
'name' => 'required',
'email' => 'required|email|unique:users,email,' . $id,
'name' => 'min:2',
'email' => 'min:2|email|unique:users,email,' . $id,
'password' => 'min:5|required_with:password_confirm',
'password-confirm' => 'same:password|required_with:password',
'role' => 'exists:roles,id'
], [
'password-confirm.required_with' => 'Password confirmation required'
'setting' => 'array'
]);
$user = $this->user->findOrFail($id);
$user->fill($request->all());
// Role updates
if ($this->currentUser->can('user-update') && $request->has('role')) {
$user->attachRoleId($request->get('role'));
if (userCan('users-manage') && $request->filled('roles')) {
$roles = $request->get('roles');
$user->roles()->sync($roles);
}
// Password updates
if ($request->has('password') && $request->get('password') != '') {
if ($request->filled('password')) {
$password = $request->get('password');
$user->password = bcrypt($password);
}
// External auth id updates
if ($this->currentUser->can('user-update') && $request->has('external_auth_id')) {
if ($this->currentUser->can('users-manage') && $request->filled('external_auth_id')) {
$user->external_auth_id = $request->get('external_auth_id');
}
// Save an user-specific settings
if ($request->filled('setting')) {
foreach ($request->get('setting') as $key => $value) {
setting()->putUser($user, $key, $value);
}
}
$user->save();
return redirect('/users');
session()->flash('success', trans('settings.users_edit_success'));
$redirectUrl = userCan('users-manage') ? '/settings/users' : '/settings/users/' . $user->id;
return redirect($redirectUrl);
}
/**
* Show the user delete page.
* @param $id
* @param int $id
* @return \Illuminate\View\View
*/
public function delete($id)
{
$this->checkPermissionOr('user-delete', function () use ($id) {
$this->checkPermissionOr('users-manage', function () use ($id) {
return $this->currentUser->id == $id;
});
$user = $this->user->findOrFail($id);
$this->setPageTitle('Delete User ' . $user->name);
$this->setPageTitle(trans('settings.users_delete_named', ['userName' => $user->name]));
return view('users/delete', ['user' => $user]);
}
@@ -186,17 +199,67 @@ class UserController extends Controller
public function destroy($id)
{
$this->preventAccessForDemoUsers();
$this->checkPermissionOr('user-delete', function () use ($id) {
$this->checkPermissionOr('users-manage', function () use ($id) {
return $this->currentUser->id == $id;
});
$user = $this->userRepo->getById($id);
if ($this->userRepo->isOnlyAdmin($user)) {
session()->flash('error', 'You cannot delete the only admin');
session()->flash('error', trans('errors.users_cannot_delete_only_admin'));
return redirect($user->getEditUrl());
}
$this->userRepo->destroy($user);
return redirect('/users');
if ($user->system_name === 'public') {
session()->flash('error', trans('errors.users_cannot_delete_guest'));
return redirect($user->getEditUrl());
}
$this->userRepo->destroy($user);
session()->flash('success', trans('settings.users_delete_success'));
return redirect('/settings/users');
}
/**
* Show the user profile page
* @param $id
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function showProfilePage($id)
{
$user = $this->userRepo->getById($id);
$userActivity = $this->userRepo->getActivity($user);
$recentlyCreated = $this->userRepo->getRecentlyCreated($user, 5, 0);
$assetCounts = $this->userRepo->getAssetCounts($user);
return view('users/profile', [
'user' => $user,
'activity' => $userActivity,
'recentlyCreated' => $recentlyCreated,
'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

@@ -1,6 +1,4 @@
<?php
namespace BookStack\Http;
<?php namespace BookStack\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
@@ -9,15 +7,36 @@ class Kernel extends HttpKernel
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* @var array
*/
protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\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,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\BookStack\Http\Middleware\TrimStrings::class,
\BookStack\Http\Middleware\TrustProxies::class,
];
/**
* The application's route middleware groups.
*
* @var array
*/
protected $middlewareGroups = [
'web' => [
\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,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\BookStack\Http\Middleware\Localization::class
],
'api' => [
'throttle:60,1',
'bindings',
],
];
/**
@@ -28,7 +47,9 @@ class Kernel extends HttpKernel
protected $routeMiddleware = [
'auth' => \BookStack\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \BookStack\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'perm' => \BookStack\Http\Middleware\PermissionMiddleware::class
];
}

View File

@@ -4,21 +4,17 @@ namespace BookStack\Http\Middleware;
use Closure;
use Illuminate\Contracts\Auth\Guard;
use BookStack\Exceptions\UserRegistrationException;
use Setting;
class Authenticate
{
/**
* The Guard implementation.
*
* @var Guard
*/
protected $auth;
/**
* Create a new filter instance.
*
* @param Guard $auth
*/
public function __construct(Guard $auth)
@@ -28,22 +24,24 @@ class Authenticate
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if(auth()->check() && auth()->user()->email_confirmed == false) {
return redirect()->guest('/register/confirm/awaiting');
if ($this->auth->check()) {
$requireConfirmation = (setting('registration-confirmation') || setting('registration-restrict'));
if ($requireConfirmation && !$this->auth->user()->email_confirmed) {
return redirect('/register/confirm/awaiting');
}
}
if ($this->auth->guest() && !Setting::get('app-public')) {
if ($this->auth->guest() && !setting('app-public')) {
if ($request->ajax()) {
return response('Unauthorized.', 401);
} else {
return redirect()->guest('/login');
return redirect()->guest(baseUrl('/login'));
}
}

View File

@@ -2,9 +2,9 @@
namespace BookStack\Http\Middleware;
use Illuminate\Cookie\Middleware\EncryptCookies as BaseEncrypter;
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
class EncryptCookies extends BaseEncrypter
class EncryptCookies extends Middleware
{
/**
* The names of the cookies that should not be encrypted.

View File

@@ -0,0 +1,48 @@
<?php namespace BookStack\Http\Middleware;
use Carbon\Carbon;
use Closure;
use Illuminate\Http\Request;
class Localization
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$defaultLang = config('app.locale');
if (user()->isDefault() && config('app.auto_detect_locale')) {
$locale = $this->autoDetectLocale($request, $defaultLang);
} else {
$locale = setting()->getUser(user(), 'language', $defaultLang);
}
app()->setLocale($locale);
Carbon::setLocale($locale);
return $next($request);
}
/**
* Autodetect the visitors locale by matching locales in their headers
* against the locales supported by BookStack.
* @param Request $request
* @param string $default
* @return string
*/
protected function autoDetectLocale(Request $request, string $default)
{
$availableLocales = config('app.locales');
foreach ($request->getLanguages() as $lang) {
if (in_array($lang, $availableLocales)) {
return $lang;
}
}
return $default;
}
}

View File

@@ -1,6 +1,4 @@
<?php
namespace BookStack\Http\Middleware;
<?php namespace BookStack\Http\Middleware;
use Closure;
use Illuminate\Contracts\Auth\Guard;
@@ -34,7 +32,8 @@ class RedirectIfAuthenticated
*/
public function handle($request, Closure $next)
{
if ($this->auth->check()) {
$requireConfirmation = setting('registration-confirmation');
if ($this->auth->check() && (!$requireConfirmation || ($requireConfirmation && $this->auth->user()->email_confirmed))) {
return redirect('/');
}

View File

@@ -0,0 +1,19 @@
<?php
namespace BookStack\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
class TrimStrings extends Middleware
{
/**
* The names of the attributes that should not be trimmed.
*
* @var array
*/
protected $except = [
'password',
'password_confirmation',
'password-confirm',
];
}

View File

@@ -0,0 +1,47 @@
<?php
namespace BookStack\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Fideloper\Proxy\TrustProxies as Middleware;
class TrustProxies extends Middleware
{
/**
* The trusted proxies for this application.
*
* @var array
*/
protected $proxies;
/**
* The current proxy header mappings.
*
* @var array
*/
protected $headers = [
Request::HEADER_FORWARDED => 'FORWARDED',
Request::HEADER_X_FORWARDED_FOR => 'X_FORWARDED_FOR',
Request::HEADER_X_FORWARDED_HOST => 'X_FORWARDED_HOST',
Request::HEADER_X_FORWARDED_PORT => 'X_FORWARDED_PORT',
Request::HEADER_X_FORWARDED_PROTO => 'X_FORWARDED_PROTO',
];
/**
* Handle the request, Set the correct user-configured proxy information.
* @param Request $request
* @param Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$setProxies = config('app.proxies');
if ($setProxies !== '**' && $setProxies !== '*' && $setProxies !== '') {
$setProxies = explode(',', $setProxies);
}
$this->proxies = $setProxies;
return parent::handle($request, $next);
}
}

View File

@@ -2,9 +2,9 @@
namespace BookStack\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends BaseVerifier
class VerifyCsrfToken extends Middleware
{
/**
* The URIs that should be excluded from CSRF verification.

View File

@@ -1,113 +0,0 @@
<?php
// Authenticated routes...
Route::group(['middleware' => 'auth'], function () {
Route::group(['prefix' => 'books'], function () {
// Books
Route::get('/', 'BookController@index');
Route::get('/create', 'BookController@create');
Route::post('/', 'BookController@store');
Route::get('/{slug}/edit', 'BookController@edit');
Route::put('/{slug}', 'BookController@update');
Route::delete('/{id}', 'BookController@destroy');
Route::get('/{slug}/sort-item', 'BookController@getSortItem');
Route::get('/{slug}', 'BookController@show');
Route::get('/{slug}/delete', 'BookController@showDelete');
Route::get('/{bookSlug}/sort', 'BookController@sort');
Route::put('/{bookSlug}/sort', 'BookController@saveSort');
// Pages
Route::get('/{bookSlug}/page/create', 'PageController@create');
Route::post('/{bookSlug}/page', 'PageController@store');
Route::get('/{bookSlug}/page/{pageSlug}', 'PageController@show');
Route::get('/{bookSlug}/page/{pageSlug}/export/pdf', 'PageController@exportPdf');
Route::get('/{bookSlug}/page/{pageSlug}/export/html', 'PageController@exportHtml');
Route::get('/{bookSlug}/page/{pageSlug}/export/plaintext', 'PageController@exportPlainText');
Route::get('/{bookSlug}/page/{pageSlug}/edit', 'PageController@edit');
Route::get('/{bookSlug}/page/{pageSlug}/delete', 'PageController@showDelete');
Route::put('/{bookSlug}/page/{pageSlug}', 'PageController@update');
Route::delete('/{bookSlug}/page/{pageSlug}', 'PageController@destroy');
// Revisions
Route::get('/{bookSlug}/page/{pageSlug}/revisions', 'PageController@showRevisions');
Route::get('/{bookSlug}/page/{pageSlug}/revisions/{revId}', 'PageController@showRevision');
Route::get('/{bookSlug}/page/{pageSlug}/revisions/{revId}/restore', 'PageController@restoreRevision');
// Chapters
Route::get('/{bookSlug}/chapter/{chapterSlug}/create-page', 'PageController@create');
Route::get('/{bookSlug}/chapter/create', 'ChapterController@create');
Route::post('/{bookSlug}/chapter/create', 'ChapterController@store');
Route::get('/{bookSlug}/chapter/{chapterSlug}', 'ChapterController@show');
Route::put('/{bookSlug}/chapter/{chapterSlug}', 'ChapterController@update');
Route::get('/{bookSlug}/chapter/{chapterSlug}/edit', 'ChapterController@edit');
Route::get('/{bookSlug}/chapter/{chapterSlug}/delete', 'ChapterController@showDelete');
Route::delete('/{bookSlug}/chapter/{chapterSlug}', 'ChapterController@destroy');
});
// Users
Route::get('/users', 'UserController@index');
Route::get('/users/create', 'UserController@create');
Route::get('/users/{id}/delete', 'UserController@delete');
Route::post('/users/create', 'UserController@store');
Route::get('/users/{id}', 'UserController@edit');
Route::put('/users/{id}', 'UserController@update');
Route::delete('/users/{id}', 'UserController@destroy');
// Image routes
Route::group(['prefix' => 'images'], function() {
// Get for user images
Route::get('/user/all', 'ImageController@getAllForUserType');
Route::get('/user/all/{page}', 'ImageController@getAllForUserType');
// Standard get, update and deletion for all types
Route::get('/thumb/{id}/{width}/{height}/{crop}', 'ImageController@getThumbnail');
Route::put('/update/{imageId}', 'ImageController@update');
Route::post('/{type}/upload', 'ImageController@uploadByType');
Route::get('/{type}/all', 'ImageController@getAllByType');
Route::get('/{type}/all/{page}', 'ImageController@getAllByType');
Route::delete('/{imageId}', 'ImageController@destroy');
});
// Links
Route::get('/link/{id}', 'PageController@redirectFromLink');
// Search
Route::get('/search/all', 'SearchController@searchAll');
Route::get('/search/book/{bookId}', 'SearchController@searchBook');
// Other Pages
Route::get('/', 'HomeController@index');
Route::get('/home', 'HomeController@index');
// Settings
Route::get('/settings', 'SettingController@index');
Route::post('/settings', 'SettingController@update');
});
// Login using social authentication
Route::get('/login/service/{socialDriver}', 'Auth\AuthController@getSocialLogin');
Route::get('/login/service/{socialDriver}/callback', 'Auth\AuthController@socialCallback');
Route::get('/login/service/{socialDriver}/detach', 'Auth\AuthController@detachSocialAccount');
// Login/Logout routes
Route::get('/login', 'Auth\AuthController@getLogin');
Route::post('/login', 'Auth\AuthController@postLogin');
Route::get('/logout', 'Auth\AuthController@getLogout');
Route::get('/register', 'Auth\AuthController@getRegister');
Route::get('/register/confirm', 'Auth\AuthController@getRegisterConfirmation');
Route::get('/register/confirm/awaiting', 'Auth\AuthController@showAwaitingConfirmation');
Route::post('/register/confirm/resend', 'Auth\AuthController@resendConfirmation');
Route::get('/register/confirm/{token}', 'Auth\AuthController@confirmEmail');
Route::get('/register/confirm/{token}/email', 'Auth\AuthController@viewConfirmEmail');
Route::get('/register/service/{socialDriver}', 'Auth\AuthController@socialRegister');
Route::post('/register', 'Auth\AuthController@postRegister');
// Password reset link request routes...
Route::get('/password/email', 'Auth\PasswordController@getEmail');
Route::post('/password/email', 'Auth\PasswordController@postEmail');
// Password reset routes...
Route::get('/password/reset/{token}', 'Auth\PasswordController@getReset');
Route::post('/password/reset', 'Auth\PasswordController@postReset');

View File

@@ -1,26 +1,23 @@
<?php
<?php namespace BookStack;
namespace BookStack;
use Illuminate\Database\Eloquent\Model;
use Images;
class Image extends Model
class Image extends Ownable
{
use Ownable;
protected $fillable = ['name'];
/**
* Get a thumbnail for this image.
* @param int $width
* @param int $height
* @param int $width
* @param int $height
* @param bool|false $keepRatio
* @return string
* @throws \Exception
*/
public function getThumb($width, $height, $keepRatio = false)
{
return Images::getThumbnail($this, $width, $height, $keepRatio);
}
}

View File

@@ -1,21 +0,0 @@
<?php
namespace BookStack\Jobs;
use Illuminate\Bus\Queueable;
abstract class Job
{
/*
|--------------------------------------------------------------------------
| Queueable Jobs
|--------------------------------------------------------------------------
|
| This job base class provides a central location to place any logic that
| is shared across all of your jobs. The trait included with the class
| provides access to the "queueOn" and "delay" queue helper methods.
|
*/
use Queueable;
}

24
app/JointPermission.php Normal file
View File

@@ -0,0 +1,24 @@
<?php namespace BookStack;
class JointPermission extends Model
{
public $timestamps = false;
/**
* Get the role that this points to.
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function role()
{
return $this->belongsTo(Role::class);
}
/**
* Get the entity this points to.
* @return \Illuminate\Database\Eloquent\Relations\MorphOne
*/
public function entity()
{
return $this->morphOne(Entity::class, 'entity');
}
}

View File

18
app/Model.php Normal file
View File

@@ -0,0 +1,18 @@
<?php namespace BookStack;
use Illuminate\Database\Eloquent\Model as EloquentModel;
class Model extends EloquentModel
{
/**
* Provides public access to get the raw attribute value from the model.
* Used in areas where no mutations are required but performance is critical.
* @param $key
* @return mixed
*/
public function getRawAttribute($key)
{
return parent::getAttributeFromArray($key);
}
}

View File

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

View File

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

View File

@@ -1,7 +1,6 @@
<?php namespace BookStack;
trait Ownable
abstract class Ownable extends Model
{
/**
* Relation for the user that created this entity.
@@ -9,7 +8,7 @@ trait Ownable
*/
public function createdBy()
{
return $this->belongsTo('BookStack\User', 'created_by');
return $this->belongsTo(User::class, 'created_by');
}
/**
@@ -18,6 +17,15 @@ trait Ownable
*/
public function updatedBy()
{
return $this->belongsTo('BookStack\User', 'updated_by');
return $this->belongsTo(User::class, 'updated_by');
}
}
/**
* Gets the class name.
* @return string
*/
public static function getClassName()
{
return strtolower(array_slice(explode('\\', static::class), -1, 1)[0]);
}
}

View File

@@ -1,15 +1,17 @@
<?php
namespace BookStack;
use Illuminate\Database\Eloquent\Model;
<?php namespace BookStack;
class Page extends Entity
{
protected $fillable = ['name', 'html', 'priority'];
protected $fillable = ['name', 'html', 'priority', 'markdown'];
protected $simpleAttributes = ['name', 'id', 'slug'];
public $textField = 'text';
/**
* Converts this page into a simplified array.
* @return mixed
*/
public function toSimpleArray()
{
$array = array_intersect_key($this->toArray(), array_flip($this->simpleAttributes));
@@ -17,35 +19,97 @@ class Page extends Entity
return $array;
}
/**
* Get the book this page sits in.
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function book()
{
return $this->belongsTo('BookStack\Book');
return $this->belongsTo(Book::class);
}
/**
* Get the parent item
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function parent()
{
return $this->chapter_id ? $this->chapter() : $this->book();
}
/**
* Get the chapter that this page is in, If applicable.
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function chapter()
{
return $this->belongsTo('BookStack\Chapter');
return $this->belongsTo(Chapter::class);
}
/**
* Check if this page has a chapter.
* @return bool
*/
public function hasChapter()
{
return $this->chapter()->count() > 0;
}
/**
* Get the associated page revisions, ordered by created date.
* @return mixed
*/
public function revisions()
{
return $this->hasMany('BookStack\PageRevision')->orderBy('created_at', 'desc');
return $this->hasMany(PageRevision::class)->where('type', '=', 'version')->orderBy('created_at', 'desc');
}
public function getUrl()
/**
* Get the attachments assigned to this page.
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function attachments()
{
return $this->hasMany(Attachment::class, 'uploaded_to')->orderBy('order', 'asc');
}
/**
* Get the url for this page.
* @param string|bool $path
* @return string
*/
public function getUrl($path = false)
{
$bookSlug = $this->getAttribute('bookSlug') ? $this->getAttribute('bookSlug') : $this->book->slug;
return '/books/' . $bookSlug . '/page/' . $this->slug;
$midText = $this->draft ? '/draft/' : '/page/';
$idComponent = $this->draft ? $this->id : urlencode($this->slug);
if ($path !== false) {
return baseUrl('/books/' . urlencode($bookSlug) . $midText . $idComponent . '/' . trim($path, '/'));
}
return baseUrl('/books/' . urlencode($bookSlug) . $midText . $idComponent);
}
/**
* Get an excerpt of this page's content to the specified length.
* @param int $length
* @return mixed
*/
public function getExcerpt($length = 100)
{
return strlen($this->text) > $length ? substr($this->text, 0, $length-3) . '...' : $this->text;
$text = strlen($this->text) > $length ? substr($this->text, 0, $length-3) . '...' : $this->text;
return mb_convert_encoding($text, 'UTF-8');
}
/**
* Return a generalised, common raw query that can be 'unioned' across entities.
* @param bool $withContent
* @return string
*/
public function entityRawQuery($withContent = false)
{
$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,26 +1,62 @@
<?php
namespace BookStack;
use Illuminate\Database\Eloquent\Model;
<?php namespace BookStack;
class PageRevision extends Model
{
protected $fillable = ['name', 'html', 'text'];
protected $fillable = ['name', 'html', 'text', 'markdown', 'summary'];
/**
* Get the user that created the page revision
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function createdBy()
{
return $this->belongsTo('BookStack\User', 'created_by');
return $this->belongsTo(User::class, 'created_by');
}
/**
* Get the page this revision originates from.
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function page()
{
return $this->belongsTo('BookStack\Page');
return $this->belongsTo(Page::class);
}
public function getUrl()
/**
* Get the url for this revision.
* @param null|string $path
* @return string
*/
public function getUrl($path = null)
{
return $this->page->getUrl() . '/revisions/' . $this->id;
$url = $this->page->getUrl() . '/revisions/' . $this->id;
if ($path) {
return $url . '/' . trim($path, '/');
}
return $url;
}
/**
* Get the previous revision for the same page if existing
* @return \BookStack\PageRevision|null
*/
public function getPrevious()
{
if ($id = static::where('page_id', '=', $this->page_id)->where('id', '<', $this->id)->max('id')) {
return static::find($id);
}
return null;
}
/**
* Allows checking of the exact class, Used to check entity type.
* Included here to align with entities in similar use cases.
* (Yup, Bit of an awkward hack)
* @param $type
* @return bool
*/
public static function isA($type)
{
return $type === 'revision';
}
}

View File

@@ -1,16 +0,0 @@
<?php
namespace BookStack;
use Illuminate\Database\Eloquent\Model;
class Permission extends Model
{
/**
* The roles that belong to the permission.
*/
public function roles()
{
return $this->belongsToMany('BookStack\Permissions');
}
}

View File

@@ -1,10 +1,9 @@
<?php
<?php namespace BookStack\Providers;
namespace BookStack\Providers;
use Illuminate\Support\Facades\Auth;
use BookStack\Services\SettingService;
use BookStack\Setting;
use Illuminate\Support\ServiceProvider;
use BookStack\User;
use Validator;
class AppServiceProvider extends ServiceProvider
{
@@ -15,7 +14,18 @@ class AppServiceProvider extends ServiceProvider
*/
public function boot()
{
//
// Custom validation methods
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) {
return "<?php echo icon($expression); ?>";
});
// Allow longer string lengths after upgrade to utf8mb4
\Schema::defaultStringLength(191);
}
/**
@@ -25,6 +35,8 @@ class AppServiceProvider extends ServiceProvider
*/
public function register()
{
//
$this->app->singleton(SettingService::class, function ($app) {
return new SettingService($app->make(Setting::class), $app->make('Illuminate\Contracts\Cache\Repository'));
});
}
}

View File

@@ -3,6 +3,7 @@
namespace BookStack\Providers;
use Auth;
use BookStack\Services\LdapService;
use Illuminate\Support\ServiceProvider;
class AuthServiceProvider extends ServiceProvider
@@ -24,8 +25,8 @@ class AuthServiceProvider extends ServiceProvider
*/
public function register()
{
Auth::provider('ldap', function($app, array $config) {
return new LdapUserProvider($config['model'], $app['BookStack\Services\LdapService']);
Auth::provider('ldap', function ($app, array $config) {
return new LdapUserProvider($config['model'], $app[LdapService::class]);
});
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace BookStack\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Broadcast;
class BroadcastServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
// Broadcast::routes();
//
// /*
// * Authenticate the user's personal channel...
// */
// Broadcast::channel('BookStack.User.*', function ($user, $userId) {
// return (int) $user->id === (int) $userId;
// });
}
}

View File

@@ -2,11 +2,19 @@
namespace BookStack\Providers;
use BookStack\Activity;
use BookStack\Image;
use BookStack\Services\ImageService;
use BookStack\Services\PermissionService;
use BookStack\Services\ViewService;
use BookStack\Setting;
use BookStack\View;
use Illuminate\Contracts\Cache\Repository;
use Illuminate\Contracts\Filesystem\Factory;
use Illuminate\Support\ServiceProvider;
use BookStack\Services\ActivityService;
use BookStack\Services\SettingService;
use Intervention\Image\ImageManager;
class CustomFacadeProvider extends ServiceProvider
{
@@ -27,25 +35,33 @@ class CustomFacadeProvider extends ServiceProvider
*/
public function register()
{
$this->app->bind('activity', function() {
return new ActivityService($this->app->make('BookStack\Activity'));
});
$this->app->bind('views', function() {
return new ViewService($this->app->make('BookStack\View'));
});
$this->app->bind('setting', function() {
return new SettingService(
$this->app->make('BookStack\Setting'),
$this->app->make('Illuminate\Contracts\Cache\Repository')
$this->app->bind('activity', function () {
return new ActivityService(
$this->app->make(Activity::class),
$this->app->make(PermissionService::class)
);
});
$this->app->bind('images', function() {
$this->app->bind('views', function () {
return new ViewService(
$this->app->make(View::class),
$this->app->make(PermissionService::class)
);
});
$this->app->bind('setting', function () {
return new SettingService(
$this->app->make(Setting::class),
$this->app->make(Repository::class)
);
});
$this->app->bind('images', function () {
return new ImageService(
$this->app->make('Intervention\Image\ImageManager'),
$this->app->make('Illuminate\Contracts\Filesystem\Factory'),
$this->app->make('Illuminate\Contracts\Cache\Repository')
$this->app->make(Image::class),
$this->app->make(ImageManager::class),
$this->app->make(Factory::class),
$this->app->make(Repository::class)
);
});
}

View File

@@ -4,6 +4,7 @@ namespace BookStack\Providers;
use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use SocialiteProviders\Manager\SocialiteWasCalled;
class EventServiceProvider extends ServiceProvider
{
@@ -13,21 +14,23 @@ class EventServiceProvider extends ServiceProvider
* @var array
*/
protected $listen = [
'BookStack\Events\SomeEvent' => [
'BookStack\Listeners\EventListener',
SocialiteWasCalled::class => [
'SocialiteProviders\Slack\SlackExtendSocialite@handle',
'SocialiteProviders\Azure\AzureExtendSocialite@handle',
'SocialiteProviders\Okta\OktaExtendSocialite@handle',
'SocialiteProviders\GitLab\GitLabExtendSocialite@handle',
'SocialiteProviders\Twitch\TwitchExtendSocialite@handle',
'SocialiteProviders\Discord\DiscordExtendSocialite@handle',
],
];
/**
* Register any other events for your application.
*
* @param \Illuminate\Contracts\Events\Dispatcher $events
* @return void
*/
public function boot(DispatcherContract $events)
public function boot()
{
parent::boot($events);
//
parent::boot();
}
}

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,12 +111,14 @@ 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'];
$model->email = $userDetails['email'];
$model->email_confirmed = true;
$model->email_confirmed = false;
return $model;
}

View File

@@ -0,0 +1,34 @@
<?php namespace BookStack\Providers;
use Illuminate\Pagination\PaginationServiceProvider as IlluminatePaginationServiceProvider;
use Illuminate\Pagination\Paginator;
class PaginationServiceProvider extends IlluminatePaginationServiceProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
Paginator::viewFactoryResolver(function () {
return $this->app['view'];
});
Paginator::currentPathResolver(function () {
return baseUrl($this->app['request']->path());
});
Paginator::currentPageResolver(function ($pageName = 'page') {
$page = $this->app['request']->input($pageName);
if (filter_var($page, FILTER_VALIDATE_INT) !== false && (int) $page >= 1) {
return $page;
}
return 1;
});
}
}

View File

@@ -4,6 +4,7 @@ namespace BookStack\Providers;
use Illuminate\Routing\Router;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Route;
class RouteServiceProvider extends ServiceProvider
{
@@ -19,26 +20,54 @@ class RouteServiceProvider extends ServiceProvider
/**
* Define your route model bindings, pattern filters, etc.
*
* @param \Illuminate\Routing\Router $router
* @return void
*/
public function boot(Router $router)
public function boot()
{
//
parent::boot($router);
parent::boot();
}
/**
* Define the routes for the application.
*
* @param \Illuminate\Routing\Router $router
* @return void
*/
public function map(Router $router)
public function map()
{
$router->group(['namespace' => $this->namespace], function ($router) {
require app_path('Http/routes.php');
$this->mapWebRoutes();
// $this->mapApiRoutes();
}
/**
* Define the "web" routes for the application.
*
* These routes all receive session state, CSRF protection, etc.
*
* @return void
*/
protected function mapWebRoutes()
{
Route::group([
'middleware' => 'web',
'namespace' => $this->namespace,
], function ($router) {
require base_path('routes/web.php');
});
}
/**
* Define the "api" routes for the application.
*
* These routes are typically stateless.
*
* @return void
*/
protected function mapApiRoutes()
{
Route::group([
'middleware' => 'api',
'namespace' => $this->namespace,
'prefix' => 'api',
], function ($router) {
require base_path('routes/api.php');
});
}
}

View File

@@ -1,36 +0,0 @@
<?php namespace BookStack\Providers;
use Illuminate\Support\ServiceProvider;
class SocialiteServiceProvider extends ServiceProvider
{
/**
* Indicates if loading of the provider is deferred.
*
* @var bool
*/
protected $defer = true;
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app->bindShared('Laravel\Socialite\Contracts\Factory', function ($app) {
return new SocialiteManager($app);
});
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return ['Laravel\Socialite\Contracts\Factory'];
}
}

View File

@@ -1,236 +0,0 @@
<?php namespace BookStack\Repos;
use Activity;
use Illuminate\Support\Str;
use BookStack\Book;
use Views;
class BookRepo
{
protected $book;
protected $pageRepo;
protected $chapterRepo;
/**
* BookRepo constructor.
* @param Book $book
* @param PageRepo $pageRepo
* @param ChapterRepo $chapterRepo
*/
public function __construct(Book $book, PageRepo $pageRepo, ChapterRepo $chapterRepo)
{
$this->book = $book;
$this->pageRepo = $pageRepo;
$this->chapterRepo = $chapterRepo;
}
/**
* Get the book that has the given id.
* @param $id
* @return mixed
*/
public function getById($id)
{
return $this->book->findOrFail($id);
}
/**
* Get all books, Limited by count.
* @param int $count
* @return mixed
*/
public function getAll($count = 10)
{
return $this->book->orderBy('name', 'asc')->take($count)->get();
}
/**
* Get all books paginated.
* @param int $count
* @return mixed
*/
public function getAllPaginated($count = 10)
{
return $this->book->orderBy('name', 'asc')->paginate($count);
}
/**
* Get the latest books.
* @param int $count
* @return mixed
*/
public function getLatest($count = 10)
{
return $this->book->orderBy('created_at', 'desc')->take($count)->get();
}
/**
* Gets the most recently viewed for a user.
* @param int $count
* @param int $page
* @return mixed
*/
public function getRecentlyViewed($count = 10, $page = 0)
{
return Views::getUserRecentlyViewed($count, $page, $this->book);
}
/**
* Gets the most viewed books.
* @param int $count
* @param int $page
* @return mixed
*/
public function getPopular($count = 10, $page = 0)
{
return Views::getPopular($count, $page, $this->book);
}
/**
* Get a book by slug
* @param $slug
* @return mixed
*/
public function getBySlug($slug)
{
$book = $this->book->where('slug', '=', $slug)->first();
if ($book === null) abort(404);
return $book;
}
/**
* Checks if a book exists.
* @param $id
* @return bool
*/
public function exists($id)
{
return $this->book->where('id', '=', $id)->exists();
}
/**
* Get a new book instance from request input.
* @param $input
* @return Book
*/
public function newFromInput($input)
{
return $this->book->fill($input);
}
/**
* Count the amount of books that have a specific slug.
* @param $slug
* @return mixed
*/
public function countBySlug($slug)
{
return $this->book->where('slug', '=', $slug)->count();
}
/**
* Destroy a book identified by the given slug.
* @param $bookSlug
*/
public function destroyBySlug($bookSlug)
{
$book = $this->getBySlug($bookSlug);
foreach ($book->pages as $page) {
$this->pageRepo->destroy($page);
}
foreach ($book->chapters as $chapter) {
$this->chapterRepo->destroy($chapter);
}
$book->views()->delete();
$book->delete();
}
/**
* Get the next child element priority.
* @param Book $book
* @return int
*/
public function getNewPriority($book)
{
$lastElem = $this->getChildren($book)->pop();
return $lastElem ? $lastElem->priority + 1 : 0;
}
/**
* @param string $slug
* @param bool|false $currentId
* @return bool
*/
public function doesSlugExist($slug, $currentId = false)
{
$query = $this->book->where('slug', '=', $slug);
if ($currentId) {
$query = $query->where('id', '!=', $currentId);
}
return $query->count() > 0;
}
/**
* Provides a suitable slug for the given book name.
* Ensures the returned slug is unique in the system.
* @param string $name
* @param bool|false $currentId
* @return string
*/
public function findSuitableSlug($name, $currentId = false)
{
$originalSlug = Str::slug($name);
$slug = $originalSlug;
$count = 2;
while ($this->doesSlugExist($slug, $currentId)) {
$slug = $originalSlug . '-' . $count;
$count++;
}
return $slug;
}
/**
* Get all child objects of a book.
* Returns a sorted collection of Pages and Chapters.
* Loads the bookslug onto child elements to prevent access database access for getting the slug.
* @param Book $book
* @return mixed
*/
public function getChildren(Book $book)
{
$pages = $book->pages()->where('chapter_id', '=', 0)->get();
$chapters = $book->chapters()->with('pages')->get();
$children = $pages->merge($chapters);
$bookSlug = $book->slug;
$children->each(function ($child) use ($bookSlug) {
$child->setAttribute('bookSlug', $bookSlug);
if ($child->isA('chapter')) {
$child->pages->each(function ($page) use ($bookSlug) {
$page->setAttribute('bookSlug', $bookSlug);
});
}
});
return $children->sortBy('priority');
}
/**
* Get books by search term.
* @param $term
* @return mixed
*/
public function getBySearch($term)
{
$terms = explode(' ', $term);
$books = $this->book->fullTextSearch(['name', 'description'], $terms);
$words = join('|', explode(' ', preg_quote(trim($term), '/')));
foreach ($books as $book) {
//highlight
$result = preg_replace('#' . $words . '#iu', "<span class=\"highlight\">\$0</span>", $book->getExcerpt(100));
$book->searchSnippet = $result;
}
return $books;
}
}

View File

@@ -1,161 +0,0 @@
<?php namespace BookStack\Repos;
use Activity;
use Illuminate\Support\Str;
use BookStack\Chapter;
class ChapterRepo
{
protected $chapter;
/**
* ChapterRepo constructor.
* @param $chapter
*/
public function __construct(Chapter $chapter)
{
$this->chapter = $chapter;
}
/**
* Check if an id exists.
* @param $id
* @return bool
*/
public function idExists($id)
{
return $this->chapter->where('id', '=', $id)->count() > 0;
}
/**
* Get a chapter by a specific id.
* @param $id
* @return mixed
*/
public function getById($id)
{
return $this->chapter->findOrFail($id);
}
/**
* Get all chapters.
* @return \Illuminate\Database\Eloquent\Collection|static[]
*/
public function getAll()
{
return $this->chapter->all();
}
/**
* Get a chapter that has the given slug within the given book.
* @param $slug
* @param $bookId
* @return mixed
*/
public function getBySlug($slug, $bookId)
{
$chapter = $this->chapter->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first();
if ($chapter === null) abort(404);
return $chapter;
}
/**
* Create a new chapter from request input.
* @param $input
* @return $this
*/
public function newFromInput($input)
{
return $this->chapter->fill($input);
}
/**
* Destroy a chapter and its relations by providing its slug.
* @param Chapter $chapter
*/
public function destroy(Chapter $chapter)
{
if (count($chapter->pages) > 0) {
foreach ($chapter->pages as $page) {
$page->chapter_id = 0;
$page->save();
}
}
Activity::removeEntity($chapter);
$chapter->views()->delete();
$chapter->delete();
}
/**
* Check if a chapter's slug exists.
* @param $slug
* @param $bookId
* @param bool|false $currentId
* @return bool
*/
public function doesSlugExist($slug, $bookId, $currentId = false)
{
$query = $this->chapter->where('slug', '=', $slug)->where('book_id', '=', $bookId);
if ($currentId) {
$query = $query->where('id', '!=', $currentId);
}
return $query->count() > 0;
}
/**
* Finds a suitable slug for the provided name.
* Checks database to prevent duplicate slugs.
* @param $name
* @param $bookId
* @param bool|false $currentId
* @return string
*/
public function findSuitableSlug($name, $bookId, $currentId = false)
{
$slug = Str::slug($name);
while ($this->doesSlugExist($slug, $bookId, $currentId)) {
$slug .= '-' . substr(md5(rand(1, 500)), 0, 3);
}
return $slug;
}
/**
* Get chapters by the given search term.
* @param $term
* @param array $whereTerms
* @return mixed
*/
public function getBySearch($term, $whereTerms = [])
{
$terms = explode(' ', $term);
$chapters = $this->chapter->fullTextSearch(['name', 'description'], $terms, $whereTerms);
$words = join('|', explode(' ', preg_quote(trim($term), '/')));
foreach ($chapters as $chapter) {
//highlight
$result = preg_replace('#' . $words . '#iu', "<span class=\"highlight\">\$0</span>", $chapter->getExcerpt(100));
$chapter->searchSnippet = $result;
}
return $chapters;
}
/**
* Changes the book relation of this chapter.
* @param $bookId
* @param Chapter $chapter
* @return Chapter
*/
public function changeBook($bookId, Chapter $chapter)
{
$chapter->book_id = $bookId;
foreach ($chapter->activity as $activity) {
$activity->book_id = $bookId;
$activity->save();
}
$chapter->slug = $this->findSuitableSlug($chapter->name, $bookId, $chapter->id);
$chapter->save();
return $chapter;
}
}

90
app/Repos/CommentRepo.php Normal file
View File

@@ -0,0 +1,90 @@
<?php namespace BookStack\Repos;
use BookStack\Comment;
use BookStack\Entity;
/**
* Class CommentRepo
* @package BookStack\Repos
*/
class CommentRepo
{
/**
* @var Comment $comment
*/
protected $comment;
/**
* CommentRepo constructor.
* @param Comment $comment
*/
public function __construct(Comment $comment)
{
$this->comment = $comment;
}
/**
* Get a comment by ID.
* @param $id
* @return Comment|\Illuminate\Database\Eloquent\Model
*/
public function getById($id)
{
return $this->comment->newQuery()->findOrFail($id);
}
/**
* Create a new comment on an entity.
* @param Entity $entity
* @param array $data
* @return Comment
*/
public function create(Entity $entity, $data = [])
{
$userId = user()->id;
$comment = $this->comment->newInstance($data);
$comment->created_by = $userId;
$comment->updated_by = $userId;
$comment->local_id = $this->getNextLocalId($entity);
$entity->comments()->save($comment);
return $comment;
}
/**
* Update an existing comment.
* @param Comment $comment
* @param array $input
* @return mixed
*/
public function update($comment, $input)
{
$comment->updated_by = user()->id;
$comment->update($input);
return $comment;
}
/**
* Delete a comment from the system.
* @param Comment $comment
* @return mixed
*/
public function delete($comment)
{
return $comment->delete();
}
/**
* Get the next local ID relative to the linked entity.
* @param Entity $entity
* @return int
*/
protected function getNextLocalId(Entity $entity)
{
$comments = $entity->comments(false)->orderBy('local_id', 'desc')->first();
if ($comments === null) {
return 1;
}
return $comments->local_id + 1;
}
}

1226
app/Repos/EntityRepo.php Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,9 @@
<?php namespace BookStack\Repos;
use BookStack\Image;
use BookStack\Page;
use BookStack\Services\ImageService;
use Setting;
use BookStack\Services\PermissionService;
use Symfony\Component\HttpFoundation\File\UploadedFile;
class ImageRepo
@@ -11,16 +11,22 @@ class ImageRepo
protected $image;
protected $imageService;
protected $restrictionService;
protected $page;
/**
* ImageRepo constructor.
* @param Image $image
* @param Image $image
* @param ImageService $imageService
* @param PermissionService $permissionService
* @param Page $page
*/
public function __construct(Image $image, ImageService $imageService)
public function __construct(Image $image, ImageService $imageService, PermissionService $permissionService, Page $page)
{
$this->image = $image;
$this->imageService = $imageService;
$this->restrictionService = $permissionService;
$this->page = $page;
}
@@ -34,23 +40,17 @@ class ImageRepo
return $this->image->findOrFail($id);
}
/**
* Gets a load images paginated, filtered by image type.
* @param string $type
* @param int $page
* @param int $pageSize
* @param bool|int $userFilter
* Execute a paginated query, returning in a standard format.
* Also runs the query through the restriction system.
* @param $query
* @param int $page
* @param int $pageSize
* @return array
*/
public function getPaginatedByType($type, $page = 0, $pageSize = 24, $userFilter = false)
private function returnPaginated($query, $page = 0, $pageSize = 24)
{
$images = $this->image->where('type', '=', strtolower($type));
if ($userFilter !== false) {
$images = $images->where('created_by', '=', $userFilter);
}
$images = $this->restrictionService->filterRelatedPages($query, 'images', 'uploaded_to');
$images = $images->orderBy('created_at', 'desc')->skip($pageSize * $page)->take($pageSize + 1)->get();
$hasMore = count($images) > $pageSize;
@@ -65,24 +65,102 @@ class ImageRepo
];
}
/**
* Gets a load images paginated, filtered by image type.
* @param string $type
* @param int $page
* @param int $pageSize
* @param bool|int $userFilter
* @return array
*/
public function getPaginatedByType($type, $page = 0, $pageSize = 24, $userFilter = false)
{
$images = $this->image->where('type', '=', strtolower($type));
if ($userFilter !== false) {
$images = $images->where('created_by', '=', $userFilter);
}
return $this->returnPaginated($images, $page, $pageSize);
}
/**
* Search for images by query, of a particular type.
* @param string $type
* @param int $page
* @param int $pageSize
* @param string $searchTerm
* @return array
*/
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);
}
/**
* Get gallery images with a particular filter criteria such as
* being within the current book or page.
* @param $filter
* @param $pageId
* @param int $pageNum
* @param int $pageSize
* @return array
*/
public function getGalleryFiltered($filter, $pageId, $pageNum = 0, $pageSize = 24)
{
$images = $this->image->where('type', '=', 'gallery');
$page = $this->page->findOrFail($pageId);
if ($filter === 'page') {
$images = $images->where('uploaded_to', '=', $page->id);
} elseif ($filter === 'book') {
$validPageIds = $page->book->pages->pluck('id')->toArray();
$images = $images->whereIn('uploaded_to', $validPageIds);
}
return $this->returnPaginated($images, $pageNum, $pageSize);
}
/**
* Save a new image into storage and return the new image.
* @param UploadedFile $uploadFile
* @param string $type
* @param string $type
* @param int $uploadedTo
* @return Image
* @throws \BookStack\Exceptions\ImageUploadException
* @throws \Exception
*/
public function saveNew(UploadedFile $uploadFile, $type)
public function saveNew(UploadedFile $uploadFile, $type, $uploadedTo = 0)
{
$image = $this->imageService->saveNewFromUpload($uploadFile, $type);
$image = $this->imageService->saveNewFromUpload($uploadFile, $type, $uploadedTo);
$this->loadThumbs($image);
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;
}
/**
* 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)
{
@@ -94,13 +172,14 @@ class ImageRepo
/**
* Destroys an Image object along with its files and thumbnails.
* Destroys an Image object along with its revisions, files and thumbnails.
* @param Image $image
* @return bool
* @throws \Exception
*/
public function destroyImage(Image $image)
{
$this->imageService->destroyImage($image);
$this->imageService->destroy($image);
return true;
}
@@ -108,8 +187,10 @@ class ImageRepo
/**
* Load thumbnails onto an image object.
* @param Image $image
* @throws \BookStack\Exceptions\ImageUploadException
* @throws \Exception
*/
private function loadThumbs(Image $image)
protected function loadThumbs(Image $image)
{
$image->thumbs = [
'gallery' => $this->getThumbnail($image, 150, 150),
@@ -121,17 +202,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
* @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)
{
return $this->imageService->getThumbnail($image, $width, $height, $keepRatio);
try {
return $this->imageService->getThumbnail($image, $width, $height, $keepRatio);
} 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 = ['gallery', 'cover', 'system', 'user'];
return in_array($type, $validTypes);
}
}

View File

@@ -1,362 +0,0 @@
<?php namespace BookStack\Repos;
use Activity;
use BookStack\Book;
use BookStack\Chapter;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use BookStack\Page;
use BookStack\PageRevision;
class PageRepo
{
protected $page;
protected $pageRevision;
/**
* PageRepo constructor.
* @param Page $page
* @param PageRevision $pageRevision
*/
public function __construct(Page $page, PageRevision $pageRevision)
{
$this->page = $page;
$this->pageRevision = $pageRevision;
}
/**
* Check if a page id exists.
* @param $id
* @return bool
*/
public function idExists($id)
{
return $this->page->where('page_id', '=', $id)->count() > 0;
}
/**
* Get a page via a specific ID.
* @param $id
* @return mixed
*/
public function getById($id)
{
return $this->page->findOrFail($id);
}
/**
* Get all pages.
* @return \Illuminate\Database\Eloquent\Collection|static[]
*/
public function getAll()
{
return $this->page->all();
}
/**
* Get a page identified by the given slug.
* @param $slug
* @param $bookId
* @return mixed
*/
public function getBySlug($slug, $bookId)
{
$page = $this->page->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first();
if ($page === null) abort(404);
return $page;
}
/**
* @param $input
* @return Page
*/
public function newFromInput($input)
{
$page = $this->page->fill($input);
return $page;
}
/**
* Count the pages with a particular slug within a book.
* @param $slug
* @param $bookId
* @return mixed
*/
public function countBySlug($slug, $bookId)
{
return $this->page->where('slug', '=', $slug)->where('book_id', '=', $bookId)->count();
}
/**
* Save a new page into the system.
* Input validation must be done beforehand.
* @param array $input
* @param Book $book
* @param int $chapterId
* @return Page
*/
public function saveNew(array $input, Book $book, $chapterId = null)
{
$page = $this->newFromInput($input);
$page->slug = $this->findSuitableSlug($page->name, $book->id);
if ($chapterId) $page->chapter_id = $chapterId;
$page->html = $this->formatHtml($input['html']);
$page->text = strip_tags($page->html);
$page->created_by = auth()->user()->id;
$page->updated_by = auth()->user()->id;
$book->pages()->save($page);
return $page;
}
/**
* Formats a page's html to be tagged correctly
* within the system.
* @param string $htmlText
* @return string
*/
protected function formatHtml($htmlText)
{
if($htmlText == '') return $htmlText;
libxml_use_internal_errors(true);
$doc = new \DOMDocument();
$doc->loadHTML($htmlText);
$container = $doc->documentElement;
$body = $container->childNodes->item(0);
$childNodes = $body->childNodes;
// Ensure no duplicate ids are used
$lastId = false;
$idArray = [];
foreach ($childNodes as $index => $childNode) {
/** @var \DOMElement $childNode */
if (get_class($childNode) !== 'DOMElement') continue;
// Overwrite id if not a bookstack custom id
if ($childNode->hasAttribute('id')) {
$id = $childNode->getAttribute('id');
if (strpos($id, 'bkmrk') === 0 && array_search($id, $idArray) === false) {
$idArray[] = $id;
continue;
};
}
// Create an unique id for the element
do {
$id = 'bkmrk-' . substr(uniqid(), -5);
} while ($id == $lastId);
$lastId = $id;
$childNode->setAttribute('id', $id);
$idArray[] = $id;
}
// Generate inner html as a string
$html = '';
foreach ($childNodes as $childNode) {
$html .= $doc->saveHTML($childNode);
}
return $html;
}
/**
* Gets pages by a search term.
* Highlights page content for showing in results.
* @param string $term
* @param array $whereTerms
* @return mixed
*/
public function getBySearch($term, $whereTerms = [])
{
$terms = explode(' ', $term);
$pages = $this->page->fullTextSearch(['name', 'text'], $terms, $whereTerms);
// Add highlights to page text.
$words = join('|', explode(' ', preg_quote(trim($term), '/')));
//lookahead/behind assertions ensures cut between words
$s = '\s\x00-/:-@\[-`{-~'; //character set for start/end of words
foreach ($pages as $page) {
preg_match_all('#(?<=[' . $s . ']).{1,30}((' . $words . ').{1,30})+(?=[' . $s . '])#uis', $page->text, $matches, PREG_SET_ORDER);
//delimiter between occurrences
$results = [];
foreach ($matches as $line) {
$results[] = htmlspecialchars($line[0], 0, 'UTF-8');
}
$matchLimit = 6;
if (count($results) > $matchLimit) {
$results = array_slice($results, 0, $matchLimit);
}
$result = join('... ', $results);
//highlight
$result = preg_replace('#' . $words . '#iu', "<span class=\"highlight\">\$0</span>", $result);
if (strlen($result) < 5) {
$result = $page->getExcerpt(80);
}
$page->searchSnippet = $result;
}
return $pages;
}
/**
* Search for image usage.
* @param $imageString
* @return mixed
*/
public function searchForImage($imageString)
{
$pages = $this->page->where('html', 'like', '%' . $imageString . '%')->get();
foreach ($pages as $page) {
$page->url = $page->getUrl();
$page->html = '';
$page->text = '';
}
return count($pages) > 0 ? $pages : false;
}
/**
* Updates a page with any fillable data and saves it into the database.
* @param Page $page
* @param int $book_id
* @param string $input
* @return Page
*/
public function updatePage(Page $page, $book_id, $input)
{
// Save a revision before updating
if ($page->html !== $input['html'] || $page->name !== $input['name']) {
$this->saveRevision($page);
}
// Update with new details
$page->fill($input);
$page->slug = $this->findSuitableSlug($page->name, $book_id, $page->id);
$page->html = $this->formatHtml($input['html']);
$page->text = strip_tags($page->html);
$page->updated_by = auth()->user()->id;
$page->save();
return $page;
}
/**
* Restores a revision's content back into a page.
* @param Page $page
* @param Book $book
* @param int $revisionId
* @return Page
*/
public function restoreRevision(Page $page, Book $book, $revisionId)
{
$this->saveRevision($page);
$revision = $this->getRevisionById($revisionId);
$page->fill($revision->toArray());
$page->slug = $this->findSuitableSlug($page->name, $book->id, $page->id);
$page->text = strip_tags($page->html);
$page->updated_by = auth()->user()->id;
$page->save();
return $page;
}
/**
* Saves a page revision into the system.
* @param Page $page
* @return $this
*/
public function saveRevision(Page $page)
{
$revision = $this->pageRevision->fill($page->toArray());
$revision->page_id = $page->id;
$revision->created_by = auth()->user()->id;
$revision->created_at = $page->updated_at;
$revision->save();
// Clear old revisions
if ($this->pageRevision->where('page_id', '=', $page->id)->count() > 50) {
$this->pageRevision->where('page_id', '=', $page->id)
->orderBy('created_at', 'desc')->skip(50)->take(5)->delete();
}
return $revision;
}
/**
* Gets a single revision via it's id.
* @param $id
* @return mixed
*/
public function getRevisionById($id)
{
return $this->pageRevision->findOrFail($id);
}
/**
* Checks if a slug exists within a book already.
* @param $slug
* @param $bookId
* @param bool|false $currentId
* @return bool
*/
public function doesSlugExist($slug, $bookId, $currentId = false)
{
$query = $this->page->where('slug', '=', $slug)->where('book_id', '=', $bookId);
if ($currentId) $query = $query->where('id', '!=', $currentId);
return $query->count() > 0;
}
/**
* Changes the related book for the specified page.
* Changes the book id of any relations to the page that store the book id.
* @param int $bookId
* @param Page $page
* @return Page
*/
public function changeBook($bookId, Page $page)
{
$page->book_id = $bookId;
foreach ($page->activity as $activity) {
$activity->book_id = $bookId;
$activity->save();
}
$page->slug = $this->findSuitableSlug($page->name, $bookId, $page->id);
$page->save();
return $page;
}
/**
* Gets a suitable slug for the resource
* @param $name
* @param $bookId
* @param bool|false $currentId
* @return string
*/
public function findSuitableSlug($name, $bookId, $currentId = false)
{
$slug = Str::slug($name);
while ($this->doesSlugExist($slug, $bookId, $currentId)) {
$slug .= '-' . substr(md5(rand(1, 500)), 0, 3);
}
return $slug;
}
/**
* Destroy a given page along with its dependencies.
* @param $page
*/
public function destroy($page)
{
Activity::removeEntity($page);
$page->views()->delete();
$page->revisions()->delete();
$page->delete();
}
}

View File

@@ -0,0 +1,151 @@
<?php namespace BookStack\Repos;
use BookStack\Exceptions\PermissionsException;
use BookStack\RolePermission;
use BookStack\Role;
use BookStack\Services\PermissionService;
use Setting;
class PermissionsRepo
{
protected $permission;
protected $role;
protected $permissionService;
protected $systemRoles = ['admin', 'public'];
/**
* PermissionsRepo constructor.
* @param RolePermission $permission
* @param Role $role
* @param PermissionService $permissionService
*/
public function __construct(RolePermission $permission, Role $role, PermissionService $permissionService)
{
$this->permission = $permission;
$this->role = $role;
$this->permissionService = $permissionService;
}
/**
* Get all the user roles from the system.
* @return \Illuminate\Database\Eloquent\Collection|static[]
*/
public function getAllRoles()
{
return $this->role->all();
}
/**
* Get all the roles except for the provided one.
* @param Role $role
* @return mixed
*/
public function getAllRolesExcept(Role $role)
{
return $this->role->where('id', '!=', $role->id)->get();
}
/**
* Get a role via its ID.
* @param $id
* @return mixed
*/
public function getRoleById($id)
{
return $this->role->findOrFail($id);
}
/**
* Save a new role into the system.
* @param array $roleData
* @return Role
*/
public function saveNewRole($roleData)
{
$role = $this->role->newInstance($roleData);
$role->name = str_replace(' ', '-', strtolower($roleData['display_name']));
// Prevent duplicate names
while ($this->role->where('name', '=', $role->name)->count() > 0) {
$role->name .= strtolower(str_random(2));
}
$role->save();
$permissions = isset($roleData['permissions']) ? array_keys($roleData['permissions']) : [];
$this->assignRolePermissions($role, $permissions);
$this->permissionService->buildJointPermissionForRole($role);
return $role;
}
/**
* Updates an existing role.
* Ensure Admin role always has all permissions.
* @param $roleId
* @param $roleData
* @throws PermissionsException
*/
public function updateRole($roleId, $roleData)
{
$role = $this->role->findOrFail($roleId);
$permissions = isset($roleData['permissions']) ? array_keys($roleData['permissions']) : [];
$this->assignRolePermissions($role, $permissions);
if ($role->system_name === 'admin') {
$permissions = $this->permission->all()->pluck('id')->toArray();
$role->permissions()->sync($permissions);
}
$role->fill($roleData);
$role->save();
$this->permissionService->buildJointPermissionForRole($role);
}
/**
* Assign an list of permission names to an role.
* @param Role $role
* @param array $permissionNameArray
*/
public function assignRolePermissions(Role $role, $permissionNameArray = [])
{
$permissions = [];
$permissionNameArray = array_values($permissionNameArray);
if ($permissionNameArray && count($permissionNameArray) > 0) {
$permissions = $this->permission->whereIn('name', $permissionNameArray)->pluck('id')->toArray();
}
$role->permissions()->sync($permissions);
}
/**
* Delete a role from the system.
* Check it's not an admin role or set as default before deleting.
* If an migration Role ID is specified the users assign to the current role
* will be added to the role of the specified id.
* @param $roleId
* @param $migrateRoleId
* @throws PermissionsException
*/
public function deleteRole($roleId, $migrateRoleId)
{
$role = $this->role->findOrFail($roleId);
// Prevent deleting admin role or default registration role.
if ($role->system_name && in_array($role->system_name, $this->systemRoles)) {
throw new PermissionsException(trans('errors.role_system_cannot_be_deleted'));
} else if ($role->id == setting('registration-role')) {
throw new PermissionsException(trans('errors.role_registration_default_cannot_delete'));
}
if ($migrateRoleId) {
$newRole = $this->role->find($migrateRoleId);
if ($newRole) {
$users = $role->users->pluck('id')->toArray();
$newRole->users()->sync($users);
}
}
$this->permissionService->deleteJointPermissionsForRole($role);
$role->delete();
}
}

141
app/Repos/TagRepo.php Normal file
View File

@@ -0,0 +1,141 @@
<?php namespace BookStack\Repos;
use BookStack\Tag;
use BookStack\Entity;
use BookStack\Services\PermissionService;
/**
* Class TagRepo
* @package BookStack\Repos
*/
class TagRepo
{
protected $tag;
protected $entity;
protected $permissionService;
/**
* TagRepo constructor.
* @param Tag $attr
* @param Entity $ent
* @param PermissionService $ps
*/
public function __construct(Tag $attr, Entity $ent, PermissionService $ps)
{
$this->tag = $attr;
$this->entity = $ent;
$this->permissionService = $ps;
}
/**
* Get an entity instance of its particular type.
* @param $entityType
* @param $entityId
* @param string $action
* @return \Illuminate\Database\Eloquent\Model|null|static
*/
public function getEntity($entityType, $entityId, $action = 'view')
{
$entityInstance = $this->entity->getEntityInstance($entityType);
$searchQuery = $entityInstance->where('id', '=', $entityId)->with('tags');
$searchQuery = $this->permissionService->enforceEntityRestrictions($entityType, $searchQuery, $action);
return $searchQuery->first();
}
/**
* Get all tags for a particular entity.
* @param string $entityType
* @param int $entityId
* @return mixed
*/
public function getForEntity($entityType, $entityId)
{
$entity = $this->getEntity($entityType, $entityId);
if ($entity === null) {
return collect();
}
return $entity->tags;
}
/**
* Get tag name suggestions from scanning existing tag names.
* If no search term is given the 50 most popular tag names are provided.
* @param $searchTerm
* @return array
*/
public function getNameSuggestions($searchTerm = false)
{
$query = $this->tag->select('*', \DB::raw('count(*) as count'))->groupBy('name');
if ($searchTerm) {
$query = $query->where('name', 'LIKE', $searchTerm . '%')->orderBy('name', 'desc');
} else {
$query = $query->orderBy('count', 'desc')->take(50);
}
$query = $this->permissionService->filterRestrictedEntityRelations($query, 'tags', 'entity_id', 'entity_type');
return $query->get(['name'])->pluck('name');
}
/**
* Get tag value suggestions from scanning existing tag values.
* If no search is given the 50 most popular values are provided.
* Passing a tagName will only find values for a tags with a particular name.
* @param $searchTerm
* @param $tagName
* @return array
*/
public function getValueSuggestions($searchTerm = false, $tagName = false)
{
$query = $this->tag->select('*', \DB::raw('count(*) as count'))->groupBy('value');
if ($searchTerm) {
$query = $query->where('value', 'LIKE', $searchTerm . '%')->orderBy('value', 'desc');
} else {
$query = $query->orderBy('count', 'desc')->take(50);
}
if ($tagName !== false) {
$query = $query->where('name', '=', $tagName);
}
$query = $this->permissionService->filterRestrictedEntityRelations($query, 'tags', 'entity_id', 'entity_type');
return $query->get(['value'])->pluck('value');
}
/**
* Save an array of tags to an entity
* @param Entity $entity
* @param array $tags
* @return array|\Illuminate\Database\Eloquent\Collection
*/
public function saveTagsToEntity(Entity $entity, $tags = [])
{
$entity->tags()->delete();
$newTags = [];
foreach ($tags as $tag) {
if (trim($tag['name']) === '') {
continue;
}
$newTags[] = $this->newInstanceFromInput($tag);
}
return $entity->tags()->saveMany($newTags);
}
/**
* Create a new Tag instance from user input.
* @param $input
* @return Tag
*/
protected function newInstanceFromInput($input)
{
$name = trim($input['name']);
$value = isset($input['value']) ? trim($input['value']) : '';
// Any other modification or cleanup required can go here
$values = ['name' => $name, 'value' => $value];
return $this->tag->newInstance($values);
}
}

View File

@@ -1,24 +1,31 @@
<?php namespace BookStack\Repos;
use Activity;
use BookStack\Exceptions\NotFoundException;
use BookStack\Image;
use BookStack\Role;
use BookStack\User;
use Setting;
use Exception;
use Images;
class UserRepo
{
protected $user;
protected $role;
protected $entityRepo;
/**
* UserRepo constructor.
* @param $user
* @param User $user
* @param Role $role
* @param EntityRepo $entityRepo
*/
public function __construct(User $user, Role $role)
public function __construct(User $user, Role $role, EntityRepo $entityRepo)
{
$this->user = $user;
$this->role = $role;
$this->entityRepo = $entityRepo;
}
/**
@@ -39,6 +46,36 @@ class UserRepo
return $this->user->findOrFail($id);
}
/**
* Get all the users with their permissions.
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function getAllUsers()
{
return $this->user->with('roles', 'avatar')->orderBy('name', 'asc')->get();
}
/**
* Get all the users with their permissions in a paginated format.
* @param int $count
* @param $sortData
* @return \Illuminate\Database\Eloquent\Builder|static
*/
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('name', 'like', $term)
->orWhere('email', 'like', $term);
});
}
return $query->paginate($count);
}
/**
* Creates a new user and attaches a role to them.
* @param array $data
@@ -50,11 +87,7 @@ class UserRepo
$this->attachDefaultRole($user);
// Get avatar from gravatar and save
if (!config('services.disable_services')) {
$avatar = \Images::saveUserGravatar($user);
$user->avatar()->associate($avatar);
$user->save();
}
$this->downloadGravatarToUserAvatar($user);
return $user;
}
@@ -65,11 +98,28 @@ class UserRepo
*/
public function attachDefaultRole($user)
{
$roleId = Setting::get('registration-role');
if ($roleId === false) $roleId = $this->role->getDefault()->id;
$roleId = setting('registration-role');
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
@@ -77,15 +127,14 @@ class UserRepo
*/
public function isOnlyAdmin(User $user)
{
if ($user->role->name != 'admin') {
if (!$user->hasSystemRole('admin')) {
return false;
}
$adminRole = $this->role->where('name', '=', 'admin')->first();
if (count($adminRole->users) > 1) {
$adminRole = $this->role->getSystemRole('admin');
if ($adminRole->users->count() > 1) {
return false;
}
return true;
}
@@ -99,17 +148,115 @@ class UserRepo
return $this->user->forceCreate([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password'])
'password' => bcrypt($data['password']),
'email_confirmed' => false
]);
}
/**
* 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::destroy($image);
}
}
}
/**
* Get the latest activity for a user.
* @param User $user
* @param int $count
* @param int $page
* @return array
*/
public function getActivity(User $user, $count = 20, $page = 0)
{
return Activity::userActivity($user, $count, $page);
}
/**
* Get the recently created content for this given user.
* @param User $user
* @param int $count
* @return mixed
*/
public function getRecentlyCreated(User $user, $count = 20)
{
return [
'pages' => $this->entityRepo->getRecentlyCreated('page', $count, 0, function ($query) use ($user) {
$query->where('created_by', '=', $user->id);
}),
'chapters' => $this->entityRepo->getRecentlyCreated('chapter', $count, 0, function ($query) use ($user) {
$query->where('created_by', '=', $user->id);
}),
'books' => $this->entityRepo->getRecentlyCreated('book', $count, 0, function ($query) use ($user) {
$query->where('created_by', '=', $user->id);
})
];
}
/**
* Get asset created counts for the give user.
* @param User $user
* @return array
*/
public function getAssetCounts(User $user)
{
return [
'pages' => $this->entityRepo->page->where('created_by', '=', $user->id)->count(),
'chapters' => $this->entityRepo->chapter->where('created_by', '=', $user->id)->count(),
'books' => $this->entityRepo->book->where('created_by', '=', $user->id)->count(),
];
}
/**
* Get the roles in the system that are assignable to a user.
* @return mixed
*/
public function getAllRoles()
{
return $this->role->all();
}
/**
* Get all the roles which can be given restricted access to
* other entities in the system.
* @return mixed
*/
public function getRestrictableRoles()
{
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,58 +1,95 @@
<?php
namespace BookStack;
use Illuminate\Database\Eloquent\Model;
<?php namespace BookStack;
class Role extends Model
{
/**
* Sets the default role name for newly registered users.
* @var string
*/
protected static $default = 'viewer';
protected $fillable = ['display_name', 'description', 'external_auth_id'];
/**
* The roles that belong to the role.
*/
public function users()
{
return $this->belongsToMany('BookStack\User');
return $this->belongsToMany(User::class);
}
/**
* The permissions that belong to the role.
* Get all related JointPermissions.
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function jointPermissions()
{
return $this->hasMany(JointPermission::class);
}
/**
* The RolePermissions that belong to the role.
*/
public function permissions()
{
return $this->belongsToMany('BookStack\Permission');
return $this->belongsToMany(RolePermission::class, 'permission_role', 'role_id', 'permission_id');
}
/**
* Check if this role has a permission.
* @param $permissionName
* @return bool
*/
public function hasPermission($permissionName)
{
$permissions = $this->getRelationValue('permissions');
foreach ($permissions as $permission) {
if ($permission->getRawAttribute('name') === $permissionName) {
return true;
}
}
return false;
}
/**
* Add a permission to this role.
* @param Permission $permission
* @param RolePermission $permission
*/
public function attachPermission(Permission $permission)
public function attachPermission(RolePermission $permission)
{
$this->permissions()->attach($permission->id);
}
/**
* Get an instance of the default role.
* @return Role
* Detach a single permission from this role.
* @param RolePermission $permission
*/
public static function getDefault()
public function detachPermission(RolePermission $permission)
{
return static::getRole(static::$default);
$this->permissions()->detach($permission->id);
}
/**
* Get the role object for the specified role.
* @param $roleName
* @return mixed
* @return Role
*/
public static function getRole($roleName)
{
return static::where('name', '=', $roleName)->first();
}
/**
* Get the role object for the specified system role.
* @param $roleName
* @return Role
*/
public static function getSystemRole($roleName)
{
return static::where('system_name', '=', $roleName)->first();
}
/**
* Get all visible roles
* @return mixed
*/
public static function visible()
{
return static::where('hidden', '=', false)->orderBy('name')->get();
}
}

22
app/RolePermission.php Normal file
View File

@@ -0,0 +1,22 @@
<?php namespace BookStack;
class RolePermission extends Model
{
/**
* The roles that belong to the permission.
*/
public function roles()
{
return $this->belongsToMany(Role::class, 'permission_role', 'permission_id', 'role_id');
}
/**
* Get the permission object by name.
* @param $name
* @return mixed
*/
public static function getByName($name)
{
return static::where('name', '=', $name)->first();
}
}

17
app/SearchTerm.php Normal file
View File

@@ -0,0 +1,17 @@
<?php namespace BookStack;
class SearchTerm extends Model
{
protected $fillable = ['term', 'entity_id', 'entity_type', 'score'];
public $timestamps = false;
/**
* Get the entity that this term belongs to
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
public function entity()
{
return $this->morphTo('entity');
}
}

View File

@@ -1,6 +1,5 @@
<?php namespace BookStack\Services;
use Illuminate\Support\Facades\Auth;
use BookStack\Activity;
use BookStack\Entity;
use Session;
@@ -9,40 +8,44 @@ class ActivityService
{
protected $activity;
protected $user;
protected $permissionService;
/**
* ActivityService constructor.
* @param $activity
* @param Activity $activity
* @param PermissionService $permissionService
*/
public function __construct(Activity $activity)
public function __construct(Activity $activity, PermissionService $permissionService)
{
$this->activity = $activity;
$this->user = auth()->user();
$this->permissionService = $permissionService;
$this->user = user();
}
/**
* Add activity data to database.
* @param Entity $entity
* @param $activityKey
* @param int $bookId
* @param bool $extra
* @param int $bookId
* @param bool $extra
*/
public function add(Entity $entity, $activityKey, $bookId = 0, $extra = false)
{
$this->activity->user_id = $this->user->id;
$this->activity->book_id = $bookId;
$this->activity->key = strtolower($activityKey);
$activity = $this->activity->newInstance();
$activity->user_id = $this->user->id;
$activity->book_id = $bookId;
$activity->key = strtolower($activityKey);
if ($extra !== false) {
$this->activity->extra = $extra;
$activity->extra = $extra;
}
$entity->activity()->save($this->activity);
$entity->activity()->save($activity);
$this->setNotification($activityKey);
}
/**
* Adds a activity history with a message & without binding to a entitiy.
* Adds a activity history with a message & without binding to a entity.
* @param $activityKey
* @param int $bookId
* @param int $bookId
* @param bool|false $extra
*/
public function addMessage($activityKey, $bookId = 0, $extra = false)
@@ -85,37 +88,63 @@ class ActivityService
*/
public function latest($count = 20, $page = 0)
{
$activityList = $this->activity->orderBy('created_at', 'desc')
->skip($count * $page)->take($count)->get();
$activityList = $this->permissionService
->filterRestrictedEntityRelations($this->activity, 'activities', 'entity_id', 'entity_type')
->orderBy('created_at', 'desc')->with('user', 'entity')->skip($count * $page)->take($count)->get();
return $this->filterSimilar($activityList);
}
/**
* Gets the latest activity for an entitiy, Filtering out similar
* Gets the latest activity for an entity, Filtering out similar
* items to prevent a message activity list.
* @param Entity $entity
* @param int $count
* @param int $page
* @param int $count
* @param int $page
* @return array
*/
function entityActivity($entity, $count = 20, $page = 0)
public function entityActivity($entity, $count = 20, $page = 0)
{
$activity = $entity->hasMany('BookStack\Activity')->orderBy('created_at', 'desc')
->skip($count * $page)->take($count)->get();
if ($entity->isA('book')) {
$query = $this->activity->where('book_id', '=', $entity->id);
} else {
$query = $this->activity->where('entity_type', '=', get_class($entity))
->where('entity_id', '=', $entity->id);
}
$activity = $this->permissionService
->filterRestrictedEntityRelations($query, 'activities', 'entity_id', 'entity_type')
->orderBy('created_at', 'desc')->with(['entity', 'user.avatar'])->skip($count * $page)->take($count)->get();
return $this->filterSimilar($activity);
}
/**
* Filters out similar activity.
* @param Activity[] $activity
* Get latest activity for a user, Filtering out similar
* items.
* @param $user
* @param int $count
* @param int $page
* @return array
*/
protected function filterSimilar($activity)
public function userActivity($user, $count = 20, $page = 0)
{
$activityList = $this->permissionService
->filterRestrictedEntityRelations($this->activity, 'activities', 'entity_id', 'entity_type')
->orderBy('created_at', 'desc')->where('user_id', '=', $user->id)->skip($count * $page)->take($count)->get();
return $this->filterSimilar($activityList);
}
/**
* Filters out similar activity.
* @param Activity[] $activities
* @return array
*/
protected function filterSimilar($activities)
{
$newActivity = [];
$previousItem = false;
foreach ($activity as $activityItem) {
foreach ($activities as $activityItem) {
if ($previousItem === false) {
$previousItem = $activityItem;
$newActivity[] = $activityItem;
@@ -141,5 +170,4 @@ class ActivityService
Session::flash('success', $message);
}
}
}
}

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