Compare commits

...

1181 Commits

Author SHA1 Message Date
Dan Brown
04ecc128a2 Updated version and assets for release v0.29.2 2020-05-02 11:49:21 +01:00
Dan Brown
87d1d3423b Merge branch 'master' into release 2020-05-02 11:48:48 +01:00
Dan Brown
d3ec38bee3 Removed unused function in registration service 2020-05-02 01:07:30 +01:00
Dan Brown
413cac23ae Added command to regenerate comment content 2020-05-01 23:41:47 +01:00
Dan Brown
3c26e7b727 Updated comment md rendering to be server-side 2020-05-01 23:24:11 +01:00
Dan Brown
2a2d0aa15b Fixed incorrect color code causing yellow/orange code blocks 2020-04-29 18:28:26 +01:00
Dan Brown
4818192a2a Updated version and assets for release v0.29.1 2020-04-28 12:30:31 +01:00
Dan Brown
965dd97f54 Merge branch 'master' into release 2020-04-28 12:30:09 +01:00
Dan Brown
dfceab7cfa Updated translator attribution before release v0.29.1 2020-04-28 12:30:02 +01:00
Dan Brown
00c77e494b Updated ci with php7.4, update locale array 2020-04-28 12:28:19 +01:00
Dan Brown
ce8cea6a9f New Crowdin translations (#2071)
* New translations common.php (Korean)

* New translations settings.php (Korean)
2020-04-28 12:25:15 +01:00
Dan Brown
6f2a2c05bf New Crowdin translations (#2028)
* New translations settings.php (Chinese Simplified)

* New translations common.php (Spanish)

* New translations common.php (Spanish, Argentina)

* New translations common.php (Turkish)

* New translations common.php (French)

* New translations auth.php (Dutch)

* New translations common.php (Dutch)

* New translations entities.php (Dutch)

* New translations activities.php (Thai)

* New translations auth.php (Thai)

* New translations common.php (Thai)

* New translations components.php (Thai)

* New translations entities.php (Thai)

* New translations errors.php (Thai)

* New translations pagination.php (Thai)

* New translations passwords.php (Thai)

* New translations settings.php (Thai)

* New translations validation.php (Thai)
2020-04-28 10:19:42 +01:00
Dan Brown
898d0b5817 Added multi-select to book-sort interface
As discussed in #2064

Closes #2067
2020-04-27 16:53:27 +01:00
Dan Brown
4ef362143b Added auto-focus behaviour to page editor
- Will focus on title if the value of the field matches the default text
for the current user's language.
- Otherwise will focus on the editor body.
- Added and tested on both editors.

For #2036
2020-04-27 15:54:39 +01:00
Dan Brown
8ce38d2158 Fixed not shown existing-email warning on new ldap user
- Reduced the amount of different exceptions from LDAP attempt so they
can be handled more consistently.
- Added test to cover.
- Also cleaned up LDAP tests to reduce boilterplate mocks.

Fixes #2048
2020-04-26 12:13:00 +01:00
Dan Brown
468fec80de Updated WYSIWYG callout shortcut to handle child elems
- Will now search for a callout on/above the selected node rather than
only using the selected node.
- Issues previously where callout shortcut would not cycle if called
when child formatting was currently selected inside the callout.

For #2061
2020-04-26 09:26:41 +01:00
Dan Brown
2ec4ad1181 Tweaked ListingResponseBuilder to help avoid future issues
- Updated so none of the method mutate the query throughout the function
so that the query can be handled in a sane way, Since we were already
encountering issues due to internal method call order.
2020-04-25 22:15:59 +01:00
Dan Brown
a17b82bdde Fixed api query total not taking filters into account 2020-04-25 21:37:52 +01:00
Dan Brown
8fb1f7c361 Fixed floated content extending past page body
As shown in #2055
2020-04-25 19:59:23 +01:00
Dan Brown
c20110b6ae Fixed issue where callout and quotes overlap floated images
For #2055
2020-04-25 19:55:16 +01:00
Dan Brown
a880b1d5c5 Fixed selection not visible - dark theme codemirror
Fixes #2060
2020-04-25 19:19:41 +01:00
Dan Brown
07831df2d3 Updated user-create endpoint so saml and ldap is consistent. 2020-04-25 18:28:07 +01:00
Dan Brown
519283e643 Authenticated admins on all guards upon login
For #2031
2020-04-25 18:19:22 +01:00
Dan Brown
79a949836b Fixed incorrect API listing total when offset set
Fixes #2043
2020-04-25 16:38:11 +01:00
Dan Brown
195b74926c Updated version and assets for release v0.29.0 2020-04-13 16:10:23 +01:00
Dan Brown
2120db12b2 Merge branch 'master' into release 2020-04-13 16:10:11 +01:00
Dan Brown
0883b0533b Merge branch 'master' of github.com:BookStackApp/BookStack 2020-04-13 15:43:53 +01:00
Dan Brown
d030620846 Updated translator contributor file from crowdin 2020-04-13 15:43:20 +01:00
Dan Brown
687c4247ae New Crowdin translations (#2005)
* New translations settings.php (Portuguese)

* New translations settings.php (Portuguese, Brazilian)

* New translations settings.php (Russian)

* New translations settings.php (Korean)

* New translations settings.php (Persian)

* New translations settings.php (Polish)

* New translations settings.php (Swedish)

* New translations settings.php (Spanish, Argentina)

* New translations settings.php (Turkish)

* New translations settings.php (Slovak)

* New translations settings.php (Slovenian)

* New translations settings.php (Spanish)

* New translations settings.php (Czech)

* New translations settings.php (Danish)

* New translations settings.php (Dutch)

* New translations settings.php (Arabic)

* New translations settings.php (Chinese Simplified)

* New translations settings.php (Chinese Traditional)

* New translations settings.php (Italian)

* New translations settings.php (Hungarian)

* New translations settings.php (Japanese)

* New translations settings.php (French)

* New translations settings.php (German)

* New translations settings.php (Hebrew)

* New translations settings.php (German Informal)

* New translations settings.php (Vietnamese)

* New translations settings.php (Ukrainian)

* New translations activities.php (Turkish)

* New translations activities.php (Turkish)

* New translations auth.php (Turkish)

* New translations common.php (Turkish)

* New translations auth.php (Turkish)

* New translations components.php (Turkish)

* New translations common.php (Turkish)

* New translations components.php (Turkish)

* New translations entities.php (Turkish)

* New translations entities.php (Turkish)

* New translations errors.php (Turkish)

* New translations entities.php (Turkish)

* New translations passwords.php (Turkish)

* New translations settings.php (Turkish)

* New translations errors.php (Turkish)

* New translations settings.php (Turkish)

* New translations settings.php (Turkish)

* New translations validation.php (Turkish)

* New translations settings.php (Turkish)

* New translations validation.php (Turkish)

* New translations common.php (Turkish)

* New translations components.php (Turkish)

* New translations validation.php (Turkish)

* New translations components.php (Turkish)

* New translations entities.php (Turkish)

* New translations errors.php (Turkish)

* New translations settings.php (Turkish)

* New translations validation.php (Turkish)

* New translations passwords.php (Arabic)

* New translations auth.php (Arabic)

* New translations auth.php (Slovak)

* New translations passwords.php (Russian)

* New translations passwords.php (Slovak)

* New translations auth.php (Slovenian)

* New translations passwords.php (Slovenian)

* New translations auth.php (Spanish)

* New translations passwords.php (Portuguese, Brazilian)

* New translations passwords.php (Polish)

* New translations auth.php (Portuguese)

* New translations auth.php (Russian)

* New translations passwords.php (Portuguese)

* New translations auth.php (Portuguese, Brazilian)

* New translations auth.php (Ukrainian)

* New translations passwords.php (Ukrainian)

* New translations auth.php (Vietnamese)

* New translations passwords.php (Vietnamese)

* New translations auth.php (German Informal)

* New translations passwords.php (German Informal)

* New translations passwords.php (Turkish)

* New translations passwords.php (Spanish)

* New translations auth.php (Spanish, Argentina)

* New translations passwords.php (Spanish, Argentina)

* New translations auth.php (Swedish)

* New translations passwords.php (Swedish)

* New translations auth.php (Turkish)

* New translations components.php (Turkish)

* New translations entities.php (Turkish)

* New translations auth.php (Polish)

* New translations passwords.php (Danish)

* New translations auth.php (Dutch)

* New translations passwords.php (Dutch)

* New translations auth.php (Danish)

* New translations auth.php (French)

* New translations passwords.php (French)

* New translations auth.php (Chinese Simplified)

* New translations passwords.php (Chinese Simplified)

* New translations auth.php (Chinese Traditional)

* New translations passwords.php (Chinese Traditional)

* New translations auth.php (Czech)

* New translations passwords.php (Czech)

* New translations auth.php (German)

* New translations auth.php (Korean)

* New translations auth.php (Japanese)

* New translations passwords.php (Japanese)

* New translations passwords.php (Korean)

* New translations auth.php (Persian)

* New translations passwords.php (Persian)

* New translations passwords.php (Italian)

* New translations passwords.php (German)

* New translations auth.php (Hebrew)

* New translations passwords.php (Hebrew)

* New translations auth.php (Hungarian)

* New translations passwords.php (Hungarian)

* New translations auth.php (Italian)

* New translations entities.php (Turkish)

* New translations settings.php (Turkish)

* New translations validation.php (Turkish)

* New translations passwords.php (Turkish)

* New translations entities.php (Turkish)

* New translations errors.php (Turkish)

* New translations validation.php (Turkish)

* New translations auth.php (Turkish)

* New translations auth.php (Spanish)

* New translations passwords.php (Spanish)

* New translations settings.php (Spanish)

* New translations auth.php (Spanish, Argentina)

* New translations passwords.php (Spanish, Argentina)

* New translations entities.php (Turkish)

* New translations auth.php (French)

* New translations passwords.php (French)

* New translations settings.php (French)

* New translations common.php (Russian)

* New translations common.php (Slovak)

* New translations common.php (Slovenian)

* New translations common.php (Spanish)

* New translations common.php (Portuguese)

* New translations common.php (Polish)

* New translations common.php (Portuguese, Brazilian)

* New translations common.php (Ukrainian)

* New translations common.php (Vietnamese)

* New translations common.php (German Informal)

* New translations common.php (Spanish, Argentina)

* New translations common.php (Swedish)

* New translations common.php (Turkish)

* New translations common.php (Danish)

* New translations common.php (Dutch)

* New translations common.php (French)

* New translations common.php (Arabic)

* New translations common.php (Chinese Simplified)

* New translations common.php (Czech)

* New translations common.php (Chinese Traditional)

* New translations common.php (Japanese)

* New translations common.php (Italian)

* New translations common.php (Korean)

* New translations common.php (Persian)

* New translations common.php (German)

* New translations common.php (Hebrew)

* New translations common.php (Hungarian)

* New translations auth.php (Russian)

* New translations common.php (Russian)

* New translations passwords.php (Russian)

* New translations passwords.php (German)

* New translations settings.php (German)

* New translations auth.php (German)

* New translations common.php (German)

* New translations settings.php (German Informal)

* New translations passwords.php (German Informal)

* New translations common.php (German Informal)

* New translations auth.php (German Informal)
2020-04-13 15:31:35 +01:00
Dan Brown
3a70e9d49c Merge pull request #2023 from jzoy/master
fix Chinese translation error
2020-04-12 19:15:00 +01:00
Dan Brown
88dfb40c63 Some further dark-mode fixes, added toggle to homepage
- Homepage toggle especially useful for not-logged-in users since they
do not have a dropdown.
2020-04-12 19:06:34 +01:00
Dan Brown
b80b6ed942 Merge pull request #2022 from BookStackApp/dark-mode
Addition of a user-selectable dark-mode option
2020-04-11 20:52:19 +01:00
Dan Brown
50669e3f4a Added tests and translations for dark-mode components 2020-04-11 20:44:23 +01:00
Dan Brown
573c848d51 Added dark/light mode toggle to profile dropdown menu
- Also fixed some remaining areas which needed dark mode support.
2020-04-11 20:37:51 +01:00
Dan Brown
d4b0e4acad Removed throttling from web-end requests
Generally seems to cause issues when secure images are in use.
Was added during laravel upgrade but laravel does not use this directly
for its web middleware anyway.
2020-04-11 20:02:07 +01:00
Dan Brown
b0b28e7b5e Rolled dark mode out to the editors
- Updated editor, and other area, styles to look okay in dark mode.
- Used tinyMCE theme generator to create dark mode theme.
- Updated tinymce to latest 4x version.
2020-04-11 15:48:08 +01:00
jzoy
eb94500dca Update settings.php 2020-04-11 21:29:09 +08:00
jzoy
df8ea0b81d fix Chinese translation error 2020-04-11 21:26:13 +08:00
Dan Brown
067cb9c5b7 Merge branch 'master' into dark-mode 2020-04-11 14:22:41 +01:00
jzoy
7673a2bd6c Merge pull request #2 from BookStackApp/master
fetch upstream
2020-04-11 21:18:17 +08:00
Dan Brown
627720c5af Fixed incorrect []Activity -> array conversion 2020-04-10 22:49:52 +01:00
Dan Brown
1ba5a1274c Started work on supporting a dark-mode
- Most elements done, but still need to do editors, tables and final
pass.
- Toggled only by quick js check at the moment, checking via css media
query. Need to make into user-preference toggle.

For #1234
2020-04-10 22:38:29 +01:00
Dan Brown
d4df18098f Cleaned up the activity service
- Added test to ensure activity on entity delete works as expected.
2020-04-10 20:55:33 +01:00
Dan Brown
7b8fe5fbc6 Added book-export endpoints to the API 2020-04-10 16:05:17 +01:00
Dan Brown
29705a25ce Reviewed and added testing for BookShelf API implementation
- Tweaked how books are passed on update to prevent unassignment if
parameter is not provided.
- Added books to validation so they show in docs.
- Added request/response examples.
- Added tests to cover.
- Added child book info to shelf info.

Review of #1908
2020-04-10 15:19:18 +01:00
Dan Brown
da1cea06ca Merge branch 'master' of git://github.com/osmansorkar/BookStack into osmansorkar-master 2020-04-10 13:49:28 +01:00
Dan Brown
ba1be9d710 Updated password reset process not to indicate if email exists
- Intended to prevent enumeration to check if a user exists.
- Updated messages on both the reqest-reset and set-password elements.
- Also updated notification auto-hide to be dynamic based upon the
amount of words within the notification.
- Added tests to cover.

For #2016
2020-04-10 13:38:08 +01:00
Dan Brown
053cbbd5b6 Updated view-change endpoints to be clearer, separated books and shelf
- Separated books-list and shelf-show view types to be saved separately.

During review of #1755
2020-04-10 12:49:16 +01:00
Dan Brown
b8c16b15a9 Merge branch 'feature_change_view_in_shelves_show' of git://github.com/philjak/BookStack into philjak-feature_change_view_in_shelves_show 2020-04-10 12:21:56 +01:00
Dan Brown
47e645909e Reviewed #1688, Show parent shelves on books page
- Moved list to the left of the page to align with other navigational
items.
- Hid list of no shelves, to help hide shelf references if not in use.
- Tweaked test to ensure it wasn't finding shelf name in breadcrumb
rather than list being tested.
2020-04-09 17:29:22 +01:00
Dan Brown
898cedf536 Merge branch 'feature/#1598' of git://github.com/cw1998/BookStack into cw1998-feature/#1598 2020-04-09 17:18:37 +01:00
Dan Brown
e83d2eedbb Added "update-url" command to find/replace url in the database
- Also aligned format of command descriptions.

Targeted most common columns.
Have not done revisions for the sake of keeping that
content true to how it was originally stored but could
cause unexpected behaviour.

For #1225
2020-04-09 16:59:26 +01:00
Dan Brown
1962c81742 Updated WYSIWYG entity link selector to set link display text
- Sets as entity name if the input is currently empty.

For #2014
2020-04-09 15:28:44 +01:00
Dan Brown
642db1387e Updated wysiwyg code-block insert flow to be mouseless
- Can now save a code block with Ctrl+Enter.
- Codemirror will be in focus on popup show.
- TinyMCE will get back focus on code save.

For #1972
2020-04-05 21:55:31 +01:00
Dan Brown
02f7ffe53c Removed overflow hidden from all lists
- Was causing ol list numbers to be cut off.

Fixes #1978
2020-04-05 18:05:51 +01:00
Dan Brown
5f61620cc2 Added support for changing the draw.io instance URL
- Allowed DRAWIO env option to be passed as URL to point to instance.
- Updated tests to check URL gets passed to pages correctly.
- Update default URL to be the default theme.

For #826
2020-04-05 17:27:16 +01:00
Dan Brown
ea9e9565ef Removed bmp and tiff support from uploaded images.
Fixes #1990
2020-04-05 16:15:05 +01:00
Dan Brown
feab756b9f Merge pull request #2003 from BookStackApp/rtl_styles_update
Updated styles to use logical properties/values, for improved RTL support
2020-04-05 14:15:23 +01:00
Dan Brown
fb08194af1 New Crowdin translations (#2004)
* New translations errors.php (Russian)

* New translations settings.php (Portuguese, Brazilian)

* New translations auth.php (Russian)

* New translations entities.php (Russian)

* New translations settings.php (Russian)

* New translations settings.php (Slovak)

* New translations settings.php (Persian)

* New translations settings.php (Portuguese)

* New translations settings.php (Polish)

* New translations settings.php (Korean)

* New translations settings.php (Swedish)

* New translations validation.php (Swedish)

* New translations errors.php (Turkish)

* New translations settings.php (Turkish)

* New translations entities.php (Swedish)

* New translations settings.php (Ukrainian)

* New translations errors.php (Swedish)

* New translations entities.php (Slovenian)

* New translations errors.php (Slovenian)

* New translations pagination.php (Slovenian)

* New translations passwords.php (Slovenian)

* New translations settings.php (Slovenian)

* New translations validation.php (Slovenian)

* New translations settings.php (Spanish)

* New translations settings.php (Spanish, Argentina)

* New translations settings.php (Czech)

* New translations pagination.php (Danish)

* New translations settings.php (Danish)

* New translations settings.php (Dutch)

* New translations settings.php (Arabic)

* New translations settings.php (Chinese Simplified)

* New translations settings.php (Chinese Traditional)

* New translations settings.php (Hungarian)

* New translations errors.php (French)

* New translations errors.php (Italian)

* New translations settings.php (Italian)

* New translations settings.php (Japanese)

* New translations settings.php (French)

* New translations errors.php (German)

* New translations settings.php (German)

* New translations settings.php (Hebrew)

* New translations auth.php (Hebrew)

* New translations validation.php (Hebrew)

* New translations errors.php (Hebrew)

* New translations entities.php (Hebrew)

* New translations common.php (Hebrew)

* New translations settings.php (German Informal)

* New translations errors.php (German Informal)

* New translations settings.php (Vietnamese)
2020-04-05 14:12:23 +01:00
Dan Brown
f94fd44ff6 Updated styles to use logical properties/values
- Intended to improve RTL support in the interface.
- Also adds hebrew to language dropdown since that was missing.

Related to #1794
2020-04-05 13:07:19 +01:00
Dan Brown
f84bf8e883 Updated test files to be PSR-4 compliant
Closes #1924
2020-04-04 01:16:05 +01:00
Dan Brown
3500182c5f Updated drawing uploads to use user id in image name
- Instead of user name.
- Due to issues with advanced charts like emoji zero-width-joiners.
- Could also have security concerns on untrusted instances with certain
webserver config due to double extension possibilities.

Closes #1993
2020-04-04 00:48:32 +01:00
Dan Brown
ef416d3e86 Fixed editor JavaScript error in TemplateManager
- Caused when loading the editor with no templates in the system.
- Tried to init a search box that did not exist.
2020-04-04 00:09:58 +01:00
Dan Brown
c1fe068ffc Bumped npm packages up and ran audit-fix 2020-04-04 00:03:26 +01:00
Dan Brown
b0610d85da Updated socialite to fix deprecated GitHub auth method
- Also updated composer dependancies to cover symfony/http-foundation
security issue.

Fixes #1879
Related to #1989
2020-04-04 00:00:19 +01:00
Dan Brown
ed563fef28 Updated version and assets for release v0.28.3 2020-03-14 22:31:42 +00:00
Dan Brown
0d31a8e3f1 Merge branch 'master' into release 2020-03-14 22:31:11 +00:00
Dan Brown
64942268b8 Added Slovenian to available language options
Related to #1946
2020-03-14 22:24:27 +00:00
Dan Brown
b670ab61d6 Updated translations from crowdin
Squashed commit of the following:

commit 23861a31bb2398ca61655c584bd8c75ee9bccdad
Merge: d44acf4b 1c6287f2
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 14 22:18:50 2020 +0000

    Merge branch 'master' into l10n_master

commit d44acf4b64
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 14 19:39:46 2020 +0000

    New translations errors.php (Portuguese, Brazilian)

commit c1a4cc5d12
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 14 19:14:49 2020 +0000

    New translations errors.php (Spanish, Argentina)

commit fb3c5dcffc
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 14 19:14:48 2020 +0000

    New translations errors.php (Spanish)

commit f65c84635d
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 14 18:42:05 2020 +0000

    New translations errors.php (German Informal)

commit 99fba6932a
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 14 18:41:57 2020 +0000

    New translations errors.php (French)

commit bc4c9684b5
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 14 18:41:55 2020 +0000

    New translations errors.php (Portuguese, Brazilian)

commit 0afce79807
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 14 18:41:43 2020 +0000

    New translations errors.php (Russian)

commit f3daf77b95
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 14 18:41:41 2020 +0000

    New translations errors.php (Hungarian)

commit 63848278a5
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 14 18:41:40 2020 +0000

    New translations errors.php (Italian)

commit 53b5fce93c
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 14 18:41:38 2020 +0000

    New translations errors.php (Japanese)

commit 2638f7a663
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 14 18:41:37 2020 +0000

    New translations errors.php (Korean)

commit f19ffa468c
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 14 18:41:36 2020 +0000

    New translations errors.php (Persian)

commit 477ae0b845
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 14 18:41:34 2020 +0000

    New translations errors.php (Polish)

commit 56beebe12c
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 14 18:41:33 2020 +0000

    New translations errors.php (German)

commit 2b6540654a
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 14 18:41:32 2020 +0000

    New translations errors.php (Portuguese)

commit 80f7275011
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 14 18:41:30 2020 +0000

    New translations errors.php (Spanish)

commit 74c65f90ab
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 14 18:41:28 2020 +0000

    New translations errors.php (Spanish, Argentina)

commit 3bd9e4fb86
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 14 18:41:27 2020 +0000

    New translations errors.php (Swedish)

commit a698db00e3
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 14 18:41:26 2020 +0000

    New translations errors.php (Turkish)

commit f3cfc63b5c
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 14 18:41:25 2020 +0000

    New translations errors.php (Ukrainian)

commit 61eb76ac89
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 14 18:41:23 2020 +0000

    New translations errors.php (Vietnamese)

commit cfda94e2a8
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 14 18:41:22 2020 +0000

    New translations errors.php (Slovak)

commit f0659025a9
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 14 18:41:20 2020 +0000

    New translations errors.php (Dutch)

commit 27ac377ed6
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 14 18:41:19 2020 +0000

    New translations errors.php (Chinese Traditional)

commit 04bf21a325
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 14 18:41:18 2020 +0000

    New translations errors.php (Chinese Simplified)

commit 4dd5802979
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 14 18:41:16 2020 +0000

    New translations errors.php (Arabic)

commit 368cf2d248
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 14 18:41:11 2020 +0000

    New translations errors.php (Slovenian)

commit 52381df7be
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 14 18:41:08 2020 +0000

    New translations errors.php (Czech)

commit 7e8330510f
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 14 18:41:07 2020 +0000

    New translations errors.php (Danish)

commit 1bf07c6acf
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Wed Mar 11 22:57:41 2020 +0000

    New translations entities.php (Slovenian)

commit 4d2d120b57
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Wed Mar 11 22:27:35 2020 +0000

    New translations entities.php (Slovenian)

commit edfc88eb8e
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Wed Mar 11 21:55:51 2020 +0000

    New translations entities.php (Slovenian)

commit 840ed35d34
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Wed Mar 11 21:22:08 2020 +0000

    New translations entities.php (Slovenian)

commit aa130af285
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Wed Mar 11 14:02:50 2020 +0000

    New translations common.php (Czech)

commit 5007fee528
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Wed Mar 11 14:02:25 2020 +0000

    New translations entities.php (Slovenian)

commit 1ad8874e6b
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Wed Mar 11 13:30:21 2020 +0000

    New translations auth.php (Czech)

commit 56f17ff7d4
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Wed Mar 11 13:29:57 2020 +0000

    New translations entities.php (Slovenian)

commit 33789962cc
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Wed Mar 11 11:09:18 2020 +0000

    New translations components.php (Slovenian)

commit 1002cf9e3b
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Wed Mar 11 10:08:48 2020 +0000

    New translations components.php (Slovenian)

commit d5a6083ae8
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Wed Mar 11 09:37:23 2020 +0000

    New translations components.php (Slovenian)

commit 0d8df3a72e
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Wed Mar 11 09:07:01 2020 +0000

    New translations components.php (Slovenian)

commit 22337688f9
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Wed Mar 11 09:07:00 2020 +0000

    New translations common.php (Slovenian)

commit ff812694e2
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Wed Mar 11 08:20:44 2020 +0000

    New translations common.php (Slovenian)

commit 57b9e04927
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Wed Mar 11 06:17:11 2020 +0000

    New translations auth.php (Spanish)

commit 25343baac1
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Wed Mar 11 06:16:52 2020 +0000

    New translations activities.php (Spanish)

commit a5c72ee5f1
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Wed Mar 11 05:37:55 2020 +0000

    New translations activities.php (Spanish)

commit 6a6f28d095
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Tue Mar 10 15:49:14 2020 +0000

    New translations auth.php (Slovenian)

commit 33f30876e1
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Tue Mar 10 15:18:30 2020 +0000

    New translations auth.php (Slovenian)

commit 8bf8bc0fe5
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Tue Mar 10 14:48:46 2020 +0000

    New translations auth.php (Slovenian)

commit 303dc07704
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Tue Mar 10 14:48:44 2020 +0000

    New translations activities.php (Slovenian)

commit c219fb67aa
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Tue Mar 10 09:59:45 2020 +0000

    New translations activities.php (Slovenian)

commit f45b6c6c82
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Tue Mar 10 08:34:32 2020 +0000

    New translations activities.php (Slovenian)

commit 7d690eb13f
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Tue Mar 10 02:56:48 2020 +0000

    New translations entities.php (Russian)

commit 86aab0529e
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Tue Mar 10 02:56:43 2020 +0000

    New translations common.php (Russian)

commit 4fc1945543
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Mon Mar 9 20:37:26 2020 +0000

    New translations errors.php (Russian)

commit 09791b736d
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Mon Mar 9 20:07:52 2020 +0000

    New translations settings.php (Russian)

commit 9cb04bea4c
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Mon Mar 9 18:49:50 2020 +0000

    New translations validation.php (Slovenian)

commit 7964a5a2a0
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Mon Mar 9 18:49:34 2020 +0000

    New translations settings.php (Slovenian)

commit 361be13ff7
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Mon Mar 9 18:49:32 2020 +0000

    New translations passwords.php (Slovenian)

commit 9b6a7f0f64
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Mon Mar 9 18:49:31 2020 +0000

    New translations pagination.php (Slovenian)

commit a815adc24f
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Mon Mar 9 18:49:30 2020 +0000

    New translations errors.php (Slovenian)

commit ae6040af3a
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Mon Mar 9 18:49:28 2020 +0000

    New translations entities.php (Slovenian)

commit f1d0177dce
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Mon Mar 9 18:49:26 2020 +0000

    New translations components.php (Slovenian)

commit 16ba9f1fe1
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Mon Mar 9 18:49:24 2020 +0000

    New translations common.php (Slovenian)

commit caa47464ba
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Mon Mar 9 18:49:23 2020 +0000

    New translations auth.php (Slovenian)

commit 4b9a78aef8
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Mon Mar 9 18:49:21 2020 +0000

    New translations activities.php (Slovenian)

commit 6a55161fe9
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sun Mar 8 01:36:31 2020 +0000

    New translations common.php (French)

commit 94235393fa
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 7 18:49:01 2020 +0000

    New translations errors.php (Portuguese, Brazilian)

commit a0f75e7724
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 7 10:24:26 2020 +0000

    New translations settings.php (Russian)

commit 65437712a2
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 7 09:37:34 2020 +0000

    New translations auth.php (Russian)

commit f7f6a92dcf
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 7 00:01:07 2020 +0000

    New translations entities.php (Russian)

commit ac8819edd3
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Sat Mar 7 00:00:57 2020 +0000

    New translations settings.php (Russian)

commit f1290f2b87
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Fri Mar 6 23:23:44 2020 +0000

    New translations entities.php (Russian)

commit 8efb9bd571
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Fri Mar 6 22:45:53 2020 +0000

    New translations entities.php (Russian)

commit 4fd31df14e
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Fri Mar 6 22:45:44 2020 +0000

    New translations settings.php (Russian)

commit 262a2b3e4e
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Fri Mar 6 22:11:41 2020 +0000

    New translations entities.php (Russian)

commit 15c98a64b8
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Fri Mar 6 15:10:54 2020 +0000

    New translations errors.php (French)

commit 7a274db381
Author: Dan Brown <ssddanbrown@googlemail.com>
Date:   Fri Mar 6 15:10:43 2020 +0000

    New translations settings.php (French)
2020-03-14 22:20:01 +00:00
Dan Brown
1c6287f245 Updated translator list from Crowdin 2020-03-14 22:15:01 +00:00
Dan Brown
775ee1bde3 Merge branch 'patch-1' of git://github.com/MikeyMJCO/BookStack into MikeyMJCO-patch-1 2020-03-14 20:53:28 +00:00
Dan Brown
82278bd0f4 Updated readme
- Updated dev details to be current.
- Added warning for auto-fixing via phpcbf.
- Added warning that existing issue does not mean accepted pull request.
2020-03-14 18:56:49 +00:00
Dan Brown
8a4e75ef32 Added a "Start Database" step to github action flow
https://github.blog/changelog/2020-02-21-github-actions-breaking-change-ubuntu-virtual-environments-will-no-longer-start-the-mysql-service-automatically/
2020-03-14 18:44:02 +00:00
Dan Brown
7f6cbead33 Performed review of "public intended" functionality provided in #1817
- Updated logic to take url from referrer rather than pass as a query parameter.
- Added tests to cover functionality.
- Updated 404 page with login action button if not signed in.
- Updated 404 page with text to indicate permissions may be affecting visibility.

Related to #1817 and #1706
2020-03-14 18:29:31 +00:00
Dan Brown
a95588dc2e Merge branch 'feature/public-login-redirect' of git://github.com/Xiphoseer/BookStack into Xiphoseer-feature/public-login-redirect 2020-03-14 17:46:30 +00:00
Dan Brown
217954694c Updated npm dependancies 2020-03-14 17:38:39 +00:00
Dan Brown
17da30aa29 Updated default mail options 2020-03-14 17:32:11 +00:00
Dan Brown
aa12e48d73 Merge branch 'TBK-validation_fixes' 2020-03-14 12:46:01 +00:00
Dan Brown
200772da72 Merge branch 'validation_fixes' of git://github.com/TBK/BookStack into TBK-validation_fixes 2020-03-14 12:42:59 +00:00
dependabot[bot]
d7760b1b25 Bump acorn from 6.4.0 to 6.4.1
Bumps [acorn](https://github.com/acornjs/acorn) from 6.4.0 to 6.4.1.
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/6.4.0...6.4.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-03-14 11:31:01 +00:00
Dan Brown
a5f972043b Updated primary color action text to be consistent
- With other similar picker components on the page.

As reported in #1930
2020-03-11 21:51:43 +00:00
Dan Brown
ae1f237a1f Merge branch 'Statium-master' 2020-03-11 21:43:16 +00:00
Statium
8a681fd796 Update entities.php
More accurate translation, error correction.
2020-03-11 21:40:50 +00:00
Statium
fa9944610d Update common.php
More accurate translation, error correction.
2020-03-11 21:38:29 +00:00
Statium
bebda6ff1a Update auth.php
More accurate translation, error correction.
2020-03-11 21:38:29 +00:00
Statium
991f6da9f4 Update activities.php
More accurate translation, error correction.
2020-03-11 21:35:31 +00:00
Dan Brown
c6ff6db784 Merge branch 'Statium-patch-1' 2020-03-11 21:28:09 +00:00
Statium
b58110000d Code refactoring
Removed extra spaces displayed in the header of the login and registration link.
2020-03-11 21:27:22 +00:00
Statium
f87c3b2660 Update setting-entity-color-picker.blade.php
Reducing indentation to one look in the application settings.
2020-03-11 21:23:04 +00:00
Dan Brown
59aefe5371 Updated social auth to take name from email if empty
- Added tests to cover.

Fixes #1853
2020-03-10 19:09:22 +00:00
Dan Brown
ccf92331c2 Updated readme with extra discord and issue list links 2020-03-06 20:26:11 +00:00
Dan Brown
30db8af460 Merge branch 'master' of git://github.com/ch0wm3in/BookStack into ch0wm3in-master 2020-03-06 20:10:57 +00:00
Dan Brown
56be10f1cd Merge branch 'perl_syntax_highlight' of git://github.com/Iyeyasu/BookStack into Iyeyasu-perl_syntax_highlight 2020-03-06 19:54:15 +00:00
Dan Brown
4b2654598c Merge branch 'master' of git://github.com/JHenneberg/BookStack into JHenneberg-master 2020-03-06 19:49:16 +00:00
Dan Brown
dbf5a87adb Lang setting list changes via Crowdin
* New translations settings.php (Italian)

* New translations settings.php (Vietnamese)

* New translations settings.php (Ukrainian)

* New translations settings.php (Turkish)

* New translations settings.php (Swedish)

* New translations settings.php (Spanish, Argentina)

* New translations settings.php (Spanish)

* New translations settings.php (Slovak)

* New translations settings.php (Portuguese)

* New translations settings.php (Polish)

* New translations settings.php (Persian)

* New translations settings.php (Korean)

* New translations settings.php (Japanese)

* New translations settings.php (German)

* New translations settings.php (Russian)

* New translations settings.php (French)

* New translations settings.php (Dutch)

* New translations settings.php (Czech)

* New translations settings.php (Chinese Traditional)

* New translations settings.php (Chinese Simplified)

* New translations settings.php (Arabic)

* New translations settings.php (Danish)

* New translations settings.php (Portuguese, Brazilian)

* New translations settings.php (Hungarian)

* New translations settings.php (German Informal)
2020-03-04 22:43:30 +00:00
Dan Brown
b94b945fb0 Merge branch 'master' of git://github.com/Binternet/BookStack into Binternet-master 2020-03-04 22:22:08 +00:00
Dan Brown
34616ac195 Updated lanauge lists to match latest translations 2020-03-04 22:14:25 +00:00
Dan Brown
6303c788ed New Crowdin translations (#1868)
* New translations settings.php (Spanish)

* New translations errors.php (Japanese)

* New translations errors.php (Japanese)

* New translations components.php (Japanese)

* New translations settings.php (German)

* New translations settings.php (German)

* New translations auth.php (German Informal)

* New translations errors.php (German Informal)

* New translations settings.php (German Informal)

* New translations entities.php (German Informal)

* New translations activities.php (Persian)

* New translations auth.php (Persian)

* New translations common.php (Persian)

* New translations components.php (Persian)

* New translations entities.php (Persian)

* New translations errors.php (Persian)

* New translations pagination.php (Persian)

* New translations passwords.php (Persian)

* New translations settings.php (Persian)

* New translations validation.php (Persian)

* New translations settings.php (German Informal)

* New translations validation.php (German Informal)

* New translations settings.php (French)

* New translations errors.php (French)

* New translations settings.php (French)

* New translations settings.php (Hungarian)

* New translations activities.php (Portuguese)

* New translations auth.php (Vietnamese)

* New translations settings.php (Vietnamese)

* New translations passwords.php (Vietnamese)

* New translations pagination.php (Vietnamese)

* New translations errors.php (Vietnamese)

* New translations entities.php (Vietnamese)

* New translations components.php (Vietnamese)

* New translations common.php (Vietnamese)

* New translations activities.php (Vietnamese)

* New translations auth.php (Portuguese)

* New translations validation.php (Portuguese)

* New translations settings.php (Portuguese)

* New translations passwords.php (Portuguese)

* New translations pagination.php (Portuguese)

* New translations errors.php (Portuguese)

* New translations entities.php (Portuguese)

* New translations components.php (Portuguese)

* New translations common.php (Portuguese)

* New translations validation.php (Vietnamese)

* New translations components.php (Vietnamese)

* New translations auth.php (Vietnamese)

* New translations components.php (Vietnamese)

* New translations auth.php (Vietnamese)

* New translations activities.php (Vietnamese)

* New translations auth.php (Vietnamese)

* New translations entities.php (Vietnamese)

* New translations pagination.php (Vietnamese)

* New translations passwords.php (Vietnamese)

* New translations common.php (Vietnamese)

* New translations entities.php (Vietnamese)

* New translations common.php (Vietnamese)

* New translations settings.php (Vietnamese)

* New translations validation.php (Vietnamese)

* New translations validation.php (Vietnamese)

* New translations validation.php (Vietnamese)

* New translations errors.php (Vietnamese)

* New translations errors.php (Vietnamese)

* New translations errors.php (Vietnamese)

* New translations errors.php (Vietnamese)

* New translations errors.php (Vietnamese)

* New translations settings.php (Vietnamese)

* New translations settings.php (Vietnamese)

* New translations settings.php (Vietnamese)

* New translations settings.php (Vietnamese)

* New translations entities.php (Vietnamese)

* New translations settings.php (Vietnamese)

* New translations entities.php (Vietnamese)

* New translations settings.php (Vietnamese)

* New translations entities.php (Vietnamese)

* New translations entities.php (Vietnamese)

* New translations entities.php (Vietnamese)

* New translations errors.php (Chinese Simplified)

* New translations errors.php (French)

* New translations errors.php (German)

* New translations errors.php (Arabic)

* New translations errors.php (Czech)

* New translations errors.php (Danish)

* New translations errors.php (Dutch)

* New translations errors.php (Hungarian)

* New translations errors.php (Italian)

* New translations errors.php (Chinese Traditional)

* New translations errors.php (Swedish)

* New translations errors.php (Portuguese)

* New translations errors.php (Persian)

* New translations errors.php (German Informal)

* New translations errors.php (Ukrainian)

* New translations errors.php (Turkish)

* New translations errors.php (Korean)

* New translations errors.php (Spanish, Argentina)

* New translations errors.php (Spanish)

* New translations errors.php (Slovak)

* New translations errors.php (Russian)

* New translations errors.php (Polish)

* New translations errors.php (Japanese)

* New translations errors.php (Portuguese, Brazilian)

* New translations errors.php (Vietnamese)

* New translations errors.php (Spanish)

* New translations entities.php (Vietnamese)

* New translations entities.php (Vietnamese)

* New translations entities.php (Vietnamese)

* New translations entities.php (Vietnamese)

* New translations entities.php (Vietnamese)

* New translations entities.php (Vietnamese)

* New translations entities.php (Vietnamese)

* New translations entities.php (Vietnamese)

* New translations entities.php (Vietnamese)

* New translations errors.php (Vietnamese)

* New translations auth.php (Swedish)

* New translations common.php (Swedish)

* New translations entities.php (Swedish)

* New translations settings.php (Swedish)

* New translations errors.php (Chinese Simplified)

* New translations errors.php (Russian)

* New translations errors.php (Russian)

* New translations common.php (Russian)

* New translations settings.php (Russian)

* New translations settings.php (Russian)

* New translations errors.php (Hungarian)

* New translations settings.php (Hungarian)

* New translations settings.php (Russian)

* New translations errors.php (Russian)

* New translations settings.php (Russian)

* New translations activities.php (Russian)

* New translations auth.php (Russian)

* New translations components.php (Russian)

* New translations entities.php (Russian)

* New translations validation.php (Russian)

* New translations errors.php (Russian)

* New translations common.php (Russian)

* New translations entities.php (Russian)

* New translations errors.php (Russian)

* New translations settings.php (Russian)

* New translations settings.php (Portuguese, Brazilian)

* New translations auth.php (Russian)

* New translations components.php (Russian)

* New translations entities.php (Russian)

* New translations errors.php (Russian)

* New translations errors.php (Russian)

* New translations settings.php (Russian)

* New translations passwords.php (Russian)

* New translations auth.php (Danish)

* New translations auth.php (Danish)

* New translations common.php (Danish)

* New translations components.php (Danish)

* New translations entities.php (Danish)

* New translations entities.php (Danish)

* New translations entities.php (Danish)

* New translations errors.php (Danish)

* New translations errors.php (Russian)

* New translations settings.php (Russian)

* New translations settings.php (Russian)

* New translations validation.php (Russian)

* New translations errors.php (Danish)

* New translations errors.php (Danish)

* New translations settings.php (Danish)

* New translations settings.php (Danish)

* New translations settings.php (Danish)

* New translations validation.php (Danish)

* New translations validation.php (Danish)

* New translations settings.php (Danish)

* New translations settings.php (Danish)

* New translations auth.php (Russian)

* New translations settings.php (Russian)

* New translations errors.php (Russian)

* New translations settings.php (Russian)

* New translations validation.php (Russian)

* New translations settings.php (Russian)
2020-03-04 21:58:04 +00:00
TBK
57f587a78b Allow book, shelf, settings & profile form input validation to skip image 2020-03-04 00:17:53 +01:00
TBK
d3737d5a87 Remove redundant getImageValidationRules method 2020-03-04 00:17:49 +01:00
TBK
5cd56f63ff Change check to verify that request is present and contains a file 2020-03-04 00:17:45 +01:00
osmansorkar
1859c7917f added api functionality to handle book Shelves 2020-02-23 11:41:49 +06:00
Mikey O'Toole
65c4985710 Resolve issue #1911 (Additional margin/padding present in nested lists)
Optionally we could consider removing the rule at line 274. This doesn't handle multiple layers of nested lists and would be better covered by the generic rule which checks for an `ol` or `ul` nested inside a `li` which is how MarkDown renders nested lists as HTML.
2020-02-20 14:25:23 +00:00
osmansorkar
01adf39be8 added Handle Authorization Header on .htaccess to solve Authorization problem get from laravel orginal github ripo 2020-02-19 12:44:23 +06:00
jzoy
27191d1a2c Merge pull request #1 from BookStackApp/master
同步作者更新内容
2020-02-18 22:30:53 +08:00
Dan Brown
b8354b974b Updated version and assets for release v0.28.2 2020-02-15 22:36:08 +00:00
Dan Brown
034c1e289d Merge branch 'master' into release 2020-02-15 22:35:46 +00:00
Dan Brown
01b95d91ba Fixed side-effect in binary LDAP handling
- Was not stripping prefix when sending value to LDAP server in search.
- Updated test to cover.
2020-02-15 22:35:15 +00:00
Dan Brown
f31605a3de Updated version and assets for release v0.28.1 2020-02-15 22:08:06 +00:00
Dan Brown
e7cc75c74d Merge branch 'master' into release 2020-02-15 22:07:17 +00:00
Dan Brown
54a4c6e678 Fixed code-block drag+drop handling
- Added custom handling, Tracks if contenteditable blocks are being dragged. On drop the selection location will be roughly checked to put the block above or below the cursor block root element.
2020-02-15 21:37:41 +00:00
Dan Brown
29cc35a304 Added dump_user_details option to LDAP and added binary attribute decode option
Related to #1872
2020-02-15 20:31:23 +00:00
Dan Brown
6caedc7a37 Fixed issues preventing breadcrumb navigation menus from opening
- Added tests to cover endpoint

Fixes #1884
2020-02-15 19:09:33 +00:00
Dan Brown
5978d9a0d3 Updated cover image methods so image parameter is not optional but still nullable 2020-02-15 18:38:36 +00:00
Dan Brown
98ab3c1ffb Merge branch 'new_bookshelf_cover_fix' of git://github.com/TBK/BookStack into TBK-new_bookshelf_cover_fix 2020-02-15 18:34:45 +00:00
Dan Brown
ea3c3cde5a Added test to ensure shelf cover image gets set on create
Related to #1897
2020-02-15 18:34:02 +00:00
Dan Brown
e9d879bcc5 Made some updates to project readme and license 2020-02-15 15:47:17 +00:00
Dan Brown
ccd50fe918 Aligned export styles a little better and fixed potential DOMPDF css error
- Removed different PDF template used on pages.
- Updated export view files to have the intended format passed.
- Shared the export CSS amoung the export templates.

Should hopefully address #1886
2020-02-15 15:34:06 +00:00
Dan Brown
14363edb73 Fixed LDAP error thrown by not found user details
- Added testing to cover.

Related to #1876
2020-02-15 14:44:36 +00:00
Dan Brown
e8cfb4f2be Removed unintended extra lines in code blocks
Fixes #1877
2020-02-15 14:24:55 +00:00
Dan Brown
49386b42da Updated email test send to show error on failure
- Added test to cover
- Closes #1874
2020-02-15 14:13:15 +00:00
TBK
9533e0646e Fix for missing cover on create new shelf 2020-02-14 20:33:07 +01:00
ch0wm3in
c1fe81466f Fixed 'interaction_required' response for azure
Azure Conditional Access policy 2FA returns 'interaction_required' 400 response https://github.com/SocialiteProviders/Providers/issues/208
2020-02-12 15:03:55 +01:00
JHenneberg
0df0227ad4 Added support for Fortran language
sorted import alphabetically
2020-02-07 13:45:19 +01:00
Dan Brown
4b79d5e4e8 Updated version and assets for release v0.28.0 2020-02-03 22:44:45 +00:00
Dan Brown
34854915b3 Merge branch 'master' into release 2020-02-03 22:43:58 +00:00
Dan Brown
33ef1cd4fa Updated translators file 2020-02-03 22:25:17 +00:00
Dan Brown
d8072cbef2 New Crowdin translations (#1850)
* New translations settings.php (Korean)

* New translations settings.php (Polish)

* New translations settings.php (Ukrainian)

* New translations settings.php (Turkish)

* New translations settings.php (Swedish)

* New translations settings.php (Spanish, Argentina)

* New translations settings.php (Spanish)

* New translations errors.php (Spanish)

* New translations settings.php (Slovak)

* New translations settings.php (Russian)

* New translations settings.php (Portuguese, Brazilian)

* New translations settings.php (Japanese)

* New translations settings.php (Chinese Simplified)

* New translations settings.php (Italian)

* New translations settings.php (Hungarian)

* New translations settings.php (German)

* New translations settings.php (French)

* New translations settings.php (Dutch)

* New translations settings.php (Danish)

* New translations settings.php (Czech)

* New translations settings.php (Chinese Traditional)

* New translations settings.php (Arabic)

* New translations settings.php (German Informal)

* New translations common.php (Dutch)

* New translations settings.php (Spanish)

* New translations errors.php (Hungarian)

* New translations settings.php (Hungarian)

* New translations common.php (Hungarian)

* New translations errors.php (Hungarian)

* New translations settings.php (Hungarian)

* New translations validation.php (Hungarian)

* New translations errors.php (Portuguese, Brazilian)

* New translations errors.php (Chinese Simplified)

* New translations settings.php (Chinese Simplified)

* New translations settings.php (Chinese Simplified)

* New translations settings.php (Chinese Simplified)

* New translations auth.php (Chinese Traditional)

* New translations common.php (Chinese Traditional)

* New translations entities.php (Chinese Traditional)

* New translations errors.php (Chinese Traditional)

* New translations entities.php (Chinese Traditional)

* New translations settings.php (Chinese Traditional)

* New translations settings.php (Chinese Traditional)

* New translations settings.php (Chinese Traditional)

* New translations settings.php (Chinese Simplified)

* New translations settings.php (Chinese Traditional)

* New translations validation.php (Chinese Traditional)

* New translations validation.php (Chinese Traditional)

* New translations errors.php (German)

* New translations errors.php (German)

* New translations settings.php (German)

* New translations settings.php (German Informal)

* New translations errors.php (French)

* New translations settings.php (French)

* New translations errors.php (Portuguese, Brazilian)

* New translations settings.php (Portuguese, Brazilian)

* New translations settings.php (Portuguese, Brazilian)

* New translations settings.php (Portuguese, Brazilian)

* New translations settings.php (Hungarian)

* New translations settings.php (Hungarian)

* New translations validation.php (Hungarian)

* New translations errors.php (Chinese Simplified)

* New translations errors.php (Spanish)

* New translations errors.php (Korean)

* New translations settings.php (Korean)

* New translations errors.php (Polish)

* New translations settings.php (Polish)

* New translations errors.php (Russian)

* New translations settings.php (Russian)

* New translations errors.php (Slovak)

* New translations settings.php (Slovak)

* New translations settings.php (Spanish)

* New translations errors.php (Japanese)

* New translations errors.php (Spanish, Argentina)

* New translations settings.php (Spanish, Argentina)

* New translations errors.php (Swedish)

* New translations settings.php (Swedish)

* New translations errors.php (Turkish)

* New translations settings.php (Turkish)

* New translations errors.php (Ukrainian)

* New translations settings.php (Ukrainian)

* New translations settings.php (Japanese)

* New translations settings.php (Italian)

* New translations settings.php (Chinese Simplified)

* New translations settings.php (Portuguese, Brazilian)

* New translations errors.php (Chinese Traditional)

* New translations settings.php (Chinese Traditional)

* New translations errors.php (French)

* New translations settings.php (French)

* New translations errors.php (German)

* New translations settings.php (German)

* New translations settings.php (Hungarian)

* New translations errors.php (Portuguese, Brazilian)

* New translations settings.php (German Informal)

* New translations errors.php (Italian)

* New translations errors.php (Arabic)

* New translations settings.php (Arabic)

* New translations errors.php (Czech)

* New translations settings.php (Czech)

* New translations errors.php (Danish)

* New translations settings.php (Danish)

* New translations errors.php (Dutch)

* New translations settings.php (Dutch)

* New translations errors.php (Hungarian)

* New translations errors.php (German Informal)

* New translations settings.php (Spanish)

* New translations settings.php (French)

* New translations settings.php (Chinese Simplified)

* New translations settings.php (Japanese)

* New translations settings.php (Turkish)

* New translations settings.php (Swedish)

* New translations settings.php (Spanish, Argentina)

* New translations settings.php (Spanish)

* New translations settings.php (Slovak)

* New translations settings.php (Russian)

* New translations settings.php (Polish)

* New translations settings.php (Korean)

* New translations settings.php (Italian)

* New translations settings.php (Chinese Traditional)

* New translations settings.php (Dutch)

* New translations settings.php (Danish)

* New translations settings.php (Czech)

* New translations settings.php (Arabic)

* New translations settings.php (German Informal)

* New translations settings.php (Portuguese, Brazilian)

* New translations settings.php (Hungarian)

* New translations settings.php (German)

* New translations settings.php (French)

* New translations settings.php (Ukrainian)
2020-02-03 21:00:17 +00:00
Dan Brown
718a97537e Added app theme setting to complete env and fixed text error 2020-02-03 20:33:10 +00:00
Dan Brown
dea8343bc8 Made docs sidebar sticky, changed theme to default
- MDN theme appeared fairly bad for markdown use, and the geometric
background was a bit much. Swapped out to default theme.
- Rough-added stickiness to docs sidebar, will need more work once it
starts to expand possible screen height.
2020-02-02 21:59:51 +00:00
Dan Brown
5ce3b861a9 Improved styling of the 500 error page 2020-02-02 21:04:43 +00:00
Dan Brown
e15fcf5b50 Merge pull request #1866 from BookStackApp/auth_alignment
Auth service alignment
2020-02-02 18:06:15 +00:00
Dan Brown
9d77cca734 Cleaned setting section redirect path 2020-02-02 17:57:21 +00:00
Dan Brown
b4f2b73590 Updated settings-save action to return to the same section 2020-02-02 17:35:16 +00:00
Dan Brown
3991fbe726 Checked over and aligned registration option behavior across all auth options
- Added tests to cover
2020-02-02 17:31:00 +00:00
Dan Brown
e6c6de0848 Simplified guard names and rolled out guard route checks
- Included tests to cover for LDAP and SAML
- Updated wording for external auth id option.
- Updated 'assertPermissionError' test case to be usable in BrowserKitTests
2020-02-02 13:10:21 +00:00
Dan Brown
5d08ec3cef Fixed failing tests caused by auth changes 2020-02-02 12:00:41 +00:00
Dan Brown
e743cd3f60 Added files missed in previous commit 2020-02-02 10:59:03 +00:00
Dan Brown
3470a6a140 Aligned SAML2 system with LDAP implementation in terms of guards and UI 2020-02-01 16:11:56 +00:00
Dan Brown
7728931f15 Set more appropriate login validation and broken up LDAP guide a bit 2020-02-01 14:30:23 +00:00
Dan Brown
575b85021d Started alignment of auth services
- Removed LDAP specific logic from login controller, placed in Guard.
- Created safer base user provider for ldap login, to be used for SAML
soon.
- Moved LDAP auth work from user provider to guard.
2020-02-01 11:42:22 +00:00
Dan Brown
92690d1ae9 Moved socal auth routes to their own controller
Also cleaned some phpdocs and extracted register actions to their own
service.
2020-01-26 14:42:50 +00:00
Dan Brown
fb5df49fd4 Updated laravel version and moved flare to non-dev 2020-01-26 13:27:28 +00:00
Dan Brown
82a8db3739 Merge pull request #1845 from SoarinFerret/add-close-icon-to-notifications
Add close icon to notifications
2020-01-19 16:07:08 +00:00
D4rt
b059744fb5 Add Perl syntax higlighting to code editor 2020-01-19 07:41:18 +02:00
Dan Brown
5ff89a1abb Added danish to language arrays 2020-01-18 16:10:16 +00:00
Dan Brown
7a2404d5e0 New Crowdin translations (#1825)
* New translations common.php (Turkish)

* New translations errors.php (Turkish)

* New translations settings.php (Turkish)

* New translations common.php (Italian)

* New translations settings.php (Italian)

* New translations auth.php (Portuguese, Brazilian)

* New translations auth.php (Portuguese, Brazilian)

* New translations auth.php (Portuguese, Brazilian)

* New translations common.php (Portuguese, Brazilian)

* New translations validation.php (Portuguese, Brazilian)

* New translations activities.php (Portuguese, Brazilian)

* New translations auth.php (Portuguese, Brazilian)

* New translations common.php (Portuguese, Brazilian)

* New translations activities.php (Portuguese, Brazilian)

* New translations components.php (Portuguese, Brazilian)

* New translations entities.php (Portuguese, Brazilian)

* New translations entities.php (Portuguese, Brazilian)

* New translations activities.php (Portuguese, Brazilian)

* New translations activities.php (Portuguese, Brazilian)

* New translations activities.php (Portuguese, Brazilian)

* New translations common.php (Portuguese, Brazilian)

* New translations components.php (Portuguese, Brazilian)

* New translations passwords.php (Portuguese, Brazilian)

* New translations settings.php (Portuguese, Brazilian)

* New translations settings.php (Portuguese, Brazilian)

* New translations settings.php (Portuguese, Brazilian)

* New translations settings.php (Portuguese, Brazilian)

* New translations auth.php (Dutch)

* New translations auth.php (Dutch)

* New translations common.php (Dutch)

* New translations settings.php (Dutch)

* New translations common.php (Portuguese, Brazilian)

* New translations settings.php (Portuguese, Brazilian)

* New translations validation.php (Dutch)

* New translations settings.php (Portuguese, Brazilian)

* New translations components.php (Dutch)

* New translations errors.php (Dutch)

* New translations settings.php (Dutch)

* New translations validation.php (Dutch)

* New translations settings.php (Dutch)

* New translations validation.php (Dutch)

* New translations entities.php (Portuguese, Brazilian)

* New translations entities.php (Portuguese, Brazilian)

* New translations errors.php (Portuguese, Brazilian)

* New translations settings.php (Portuguese, Brazilian)

* New translations auth.php (Portuguese, Brazilian)

* New translations settings.php (Portuguese, Brazilian)

* New translations auth.php (Portuguese, Brazilian)

* New translations auth.php (Portuguese, Brazilian)

* New translations components.php (Portuguese, Brazilian)

* New translations settings.php (Portuguese, Brazilian)

* New translations errors.php (Portuguese, Brazilian)

* New translations entities.php (Portuguese, Brazilian)

* New translations errors.php (Portuguese, Brazilian)

* New translations entities.php (Portuguese, Brazilian)

* New translations entities.php (Portuguese, Brazilian)

* New translations validation.php (Portuguese, Brazilian)

* New translations validation.php (Portuguese, Brazilian)

* New translations validation.php (Portuguese, Brazilian)

* New translations errors.php (Danish)

* New translations errors.php (Danish)

* New translations activities.php (Danish)

* New translations common.php (Danish)

* New translations auth.php (Danish)

* New translations auth.php (Danish)

* New translations passwords.php (Danish)

* New translations common.php (Korean)

* New translations settings.php (Korean)

* New translations settings.php (Korean)

* New translations errors.php (Korean)

* New translations common.php (Chinese Simplified)

* New translations entities.php (Chinese Simplified)

* New translations errors.php (Chinese Simplified)

* New translations errors.php (Chinese Simplified)

* New translations settings.php (Chinese Simplified)

* New translations settings.php (Korean)

* New translations settings.php (Spanish)

* New translations settings.php (Polish)

* New translations errors.php (Portuguese, Brazilian)

* New translations settings.php (Portuguese, Brazilian)

* New translations errors.php (Russian)

* New translations settings.php (Russian)

* New translations errors.php (Slovak)

* New translations settings.php (Slovak)

* New translations errors.php (Spanish)

* New translations errors.php (Spanish, Argentina)

* New translations settings.php (Japanese)

* New translations settings.php (Spanish, Argentina)

* New translations errors.php (Swedish)

* New translations settings.php (Swedish)

* New translations errors.php (Turkish)

* New translations settings.php (Turkish)

* New translations errors.php (Ukrainian)

* New translations settings.php (Ukrainian)

* New translations errors.php (German Informal)

* New translations errors.php (Polish)

* New translations errors.php (Japanese)

* New translations errors.php (Korean)

* New translations errors.php (Danish)

* New translations errors.php (Chinese Simplified)

* New translations settings.php (Chinese Simplified)

* New translations errors.php (Arabic)

* New translations settings.php (Arabic)

* New translations errors.php (Chinese Traditional)

* New translations settings.php (Chinese Traditional)

* New translations errors.php (Czech)

* New translations settings.php (Czech)

* New translations settings.php (Danish)

* New translations settings.php (Italian)

* New translations errors.php (Dutch)

* New translations settings.php (Dutch)

* New translations errors.php (French)

* New translations settings.php (French)

* New translations errors.php (German)

* New translations settings.php (German)

* New translations errors.php (Hungarian)

* New translations settings.php (Hungarian)

* New translations errors.php (Italian)

* New translations settings.php (German Informal)
2020-01-18 16:03:27 +00:00
Dan Brown
0ba75713e1 Fixed github action workflow 2020-01-18 15:30:54 +00:00
Dan Brown
281200e212 Further updated github actions config
- Added composer caching based off github docs.
- Focused when actions run so they're not running unneccessarily.
2020-01-18 15:27:57 +00:00
Dan Brown
4ed23b0187 Added caching to github action workflow 2020-01-18 15:17:21 +00:00
Dan Brown
517687669c Merge pull request #1826 from BookStackApp/api_origins
Baseline API Implementation
2020-01-18 15:10:35 +00:00
Dan Brown
be554b9c79 Added configurable API throttling, Handled API errors standardly 2020-01-18 15:03:28 +00:00
Dan Brown
1350136ca3 Fixed bad test class name 2020-01-18 14:07:43 +00:00
Dan Brown
b9fb655b60 Added "Getting Started" API docs 2020-01-18 14:03:11 +00:00
Dan Brown
64455307b1 Added a few test to cover api docs pages 2020-01-18 10:04:13 +00:00
Dan Brown
8ead596067 Updated default codemirror theme
- To mdn-like theme, to have better default legibility and contrast
2020-01-18 09:55:02 +00:00
Dan Brown
8016f1121e Refined docs view, Added example requests 2020-01-18 09:48:30 +00:00
Dan Brown
45b5e631e2 Added a view for the API docs 2020-01-15 20:18:02 +00:00
SoarinFerret
4297d64e29 Add close icon to notifications 2020-01-14 13:50:29 -06:00
Dan Brown
bed2498667 Started work on generating API docs 2020-01-12 16:25:14 +00:00
Dan Brown
04a8614136 Filled out base Book API endpoints, added example responses 2020-01-12 14:45:54 +00:00
Dan Brown
a8595d8aaf Fixed test class names + add perm. check to api session auth 2020-01-01 17:01:36 +00:00
Dan Brown
a7a97a53f1 Added API listing filtering & cleaned ApiAuthenticate returns
API listing endpoint filter can be found via &filter[name]=my+book query
parameters. There are a range of operators that can be used such as
&filter[id:gte]=4
2020-01-01 16:33:47 +00:00
Dan Brown
55abf7be24 Added tests to cover API config and listing code 2019-12-30 20:48:23 +00:00
Dan Brown
3cacda6762 Added expiry checking to API token auth
- Added test to cover to ensure its checked going forward
2019-12-30 19:51:41 +00:00
Dan Brown
3d11cba223 Added testing coverage to API token auth 2019-12-30 19:42:46 +00:00
Dan Brown
6f1b88a6a6 Change email confirmation from own middle to trait
Email confirmation middleware caused more mess than good, As caused
priority issues and it depended on auth actions. Instead its now a trai
used on auth middlewares.

Also used 'EncryptCookies' middleware on API instead of custom
decryption in custom middleware since we'd need to do replicate all the
same actions anyway. Shouldn't have too much effect since it only
actions over cookies that exist, of which none should be there for most
API requests.

Also split out some large guard functions to be a little more readable
and appease codeclimate.
2019-12-30 15:49:20 +00:00
Dan Brown
349b4629be Extracted API auth into guard
Also implemented more elegant solution to allowing session auth for API
routes; A new 'StartSessionIfCookieExists' middleware, which wraps the
default 'StartSession' middleware will run for API routes which only
sets up the session if a session cookie is found on the request. Also
decrypts only the session cookie.

Also cleaned some TokenController codeclimate warnings.
2019-12-30 14:51:28 +00:00
Dan Brown
3de55ee645 Linked new API token system into middleware
Base logic in place but needs review and refactor to see if can better
fit into Laravel using 'Guard' system. Currently has issues due to
cookies in use from active session on API.
2019-12-30 02:16:07 +00:00
Lior Broshi
80a50f1ecb added rtl support for hebrew + added to localMap 2019-12-29 23:06:54 +02:00
Lior Broshi
23ad8024ec resolved conflict 2019-12-29 23:03:10 +02:00
Lior Broshi
da03e34c67 added he locale to configuration 2019-12-29 23:01:45 +02:00
Lior Broshi
5f333eebf0 validation 2019-12-29 22:53:42 +02:00
Dan Brown
2cfa37399c Fixed some empty-expiry conditions of token ui flows 2019-12-29 20:18:37 +00:00
Dan Brown
692fc46c7d Removed token 'client' text, avoid confusion w/ oAuth
- Instead have a token_id and a secret.
   - Displayed a 'Token ID' and 'Token Secret'.
2019-12-29 20:07:28 +00:00
Dan Brown
832fbd65af Added testing coverage to user API token interfaces 2019-12-29 19:46:46 +00:00
Dan Brown
dccb279c84 Built out interfaces & endpoints for API token managment 2019-12-29 17:03:52 +00:00
Dan Brown
d336ba6874 Started work on API token controls
- Added access-api permission.
- Started user profile UI work.
- Created database table and model for tokens.
- Fixed incorrect templates down migration :(
2019-12-29 13:02:26 +00:00
Dan Brown
04137e7c98 Started core API route work 2019-12-28 14:58:07 +00:00
Dan Brown
c055310507 Updated to latest laravel 6 version 2019-12-28 13:01:42 +00:00
Dan Brown
5c040bf2b7 Merge branch 'albergoniSivaf-master' 2019-12-27 17:15:45 +00:00
Dan Brown
cf743370a8 Updated code block lang order and added extra pascal option
- Fixed modal window sizing/positioning to be properly center and
responsive.

Related to #1730
2019-12-27 17:14:34 +00:00
Dan Brown
891dbfe085 Merge branch 'master' of git://github.com/albergoniSivaf/BookStack into albergoniSivaf-master 2019-12-27 17:03:10 +00:00
Dan Brown
6f9cad2106 Merge pull request #1793 from abublihi/master
Fix An Exception
2019-12-27 16:52:07 +00:00
Dan Brown
a433cde3b7 Merge pull request #1824 from BookStackApp/l10n_master
New Crowdin translations
2019-12-27 16:46:02 +00:00
Dan Brown
91081a0d72 New translations validation.php (German Informal) 2019-12-27 16:40:44 +00:00
Dan Brown
77c2b2ef64 New translations common.php (Slovak) 2019-12-27 16:40:43 +00:00
Dan Brown
426440d552 New translations errors.php (Slovak) 2019-12-27 16:40:40 +00:00
Dan Brown
a8ebcfaef6 New translations settings.php (Slovak) 2019-12-27 16:40:38 +00:00
Dan Brown
4db8c1279a New translations entities.php (Spanish) 2019-12-27 16:40:35 +00:00
Dan Brown
0ec4ca033c New translations errors.php (Spanish) 2019-12-27 16:40:33 +00:00
Dan Brown
0c446800a6 New translations settings.php (Spanish) 2019-12-27 16:40:31 +00:00
Dan Brown
2246b468bc New translations validation.php (Spanish) 2019-12-27 16:40:30 +00:00
Dan Brown
d7673c6a7b New translations common.php (Spanish) 2019-12-27 16:40:28 +00:00
Dan Brown
8269f99114 New translations common.php (Spanish, Argentina) 2019-12-27 16:40:27 +00:00
Dan Brown
9798fa8ba7 New translations settings.php (Russian) 2019-12-27 16:40:25 +00:00
Dan Brown
420baa2f65 New translations settings.php (Polish) 2019-12-27 16:40:24 +00:00
Dan Brown
f259bc2f15 New translations validation.php (Polish) 2019-12-27 16:40:22 +00:00
Dan Brown
312153befc New translations common.php (Portuguese, Brazilian) 2019-12-27 16:40:20 +00:00
Dan Brown
f85bf94c80 New translations errors.php (Portuguese, Brazilian) 2019-12-27 16:40:17 +00:00
Dan Brown
971c0b1b88 New translations activities.php (Russian) 2019-12-27 16:40:15 +00:00
Dan Brown
eff87696bc New translations auth.php (Russian) 2019-12-27 16:40:14 +00:00
Dan Brown
b44c27ba81 New translations common.php (Russian) 2019-12-27 16:40:13 +00:00
Dan Brown
f978ba3033 New translations entities.php (Russian) 2019-12-27 16:40:11 +00:00
Dan Brown
c163863b03 New translations passwords.php (Russian) 2019-12-27 16:40:08 +00:00
Dan Brown
ce70738c93 New translations settings.php (Portuguese, Brazilian) 2019-12-27 16:40:07 +00:00
Dan Brown
96f1fd534a New translations errors.php (Russian) 2019-12-27 16:40:05 +00:00
Dan Brown
5800bd82fb New translations errors.php (Spanish, Argentina) 2019-12-27 16:40:04 +00:00
Dan Brown
20ba63ce0e New translations validation.php (Turkish) 2019-12-27 16:40:03 +00:00
Dan Brown
026247be99 New translations activities.php (Ukrainian) 2019-12-27 16:40:01 +00:00
Dan Brown
303f4164f0 New translations auth.php (Ukrainian) 2019-12-27 16:40:00 +00:00
Dan Brown
3cb0742918 New translations common.php (Ukrainian) 2019-12-27 16:39:59 +00:00
Dan Brown
d7d634a3d7 New translations entities.php (Ukrainian) 2019-12-27 16:39:58 +00:00
Dan Brown
d957e72047 New translations errors.php (Ukrainian) 2019-12-27 16:39:56 +00:00
Dan Brown
6a0627e1d1 New translations passwords.php (Ukrainian) 2019-12-27 16:39:54 +00:00
Dan Brown
063a7af846 New translations settings.php (Turkish) 2019-12-27 16:39:53 +00:00
Dan Brown
dc5cc8c3ec New translations settings.php (Ukrainian) 2019-12-27 16:39:52 +00:00
Dan Brown
f39f2ca487 New translations auth.php (German Informal) 2019-12-27 16:39:50 +00:00
Dan Brown
b5d1b611b1 New translations common.php (German Informal) 2019-12-27 16:39:49 +00:00
Dan Brown
e9e6f11c2f New translations entities.php (German Informal) 2019-12-27 16:39:48 +00:00
Dan Brown
e634391fd7 New translations errors.php (German Informal) 2019-12-27 16:39:46 +00:00
Dan Brown
6ffb283014 New translations settings.php (German Informal) 2019-12-27 16:39:45 +00:00
Dan Brown
0105d4d5dd New translations validation.php (Ukrainian) 2019-12-27 16:39:42 +00:00
Dan Brown
63f9391f53 New translations errors.php (Turkish) 2019-12-27 16:39:40 +00:00
Dan Brown
51dd11410f New translations settings.php (Spanish, Argentina) 2019-12-27 16:39:38 +00:00
Dan Brown
475a1f4dff New translations common.php (Swedish) 2019-12-27 16:39:37 +00:00
Dan Brown
ed38be9672 New translations errors.php (Swedish) 2019-12-27 16:39:35 +00:00
Dan Brown
f55acc7cf1 New translations settings.php (Swedish) 2019-12-27 16:39:33 +00:00
Dan Brown
e6a1b2ea97 New translations auth.php (Turkish) 2019-12-27 16:39:30 +00:00
Dan Brown
8e4f331674 New translations common.php (Turkish) 2019-12-27 16:39:29 +00:00
Dan Brown
5ab6ad0526 New translations entities.php (Turkish) 2019-12-27 16:39:27 +00:00
Dan Brown
1568f953b6 New translations common.php (Dutch) 2019-12-27 16:39:25 +00:00
Dan Brown
68750b5da4 New translations settings.php (Czech) 2019-12-27 16:39:23 +00:00
Dan Brown
7cf2fd72ab New translations errors.php (Czech) 2019-12-27 16:39:21 +00:00
Dan Brown
c24905f606 New translations common.php (Czech) 2019-12-27 16:39:20 +00:00
Dan Brown
740543d32d New translations settings.php (Dutch) 2019-12-27 16:39:16 +00:00
Dan Brown
dd4feb4f3d New translations common.php (French) 2019-12-27 16:39:14 +00:00
Dan Brown
c58214c6fd New translations entities.php (French) 2019-12-27 16:39:12 +00:00
Dan Brown
3d187627d0 New translations errors.php (French) 2019-12-27 16:39:10 +00:00
Dan Brown
601126ba2e New translations errors.php (Dutch) 2019-12-27 16:39:07 +00:00
Dan Brown
003eb6ea26 New translations settings.php (Arabic) 2019-12-27 16:39:06 +00:00
Dan Brown
e4d847ab51 New translations settings.php (Chinese Traditional) 2019-12-27 16:39:05 +00:00
Dan Brown
82e344166e New translations settings.php (French) 2019-12-27 16:39:03 +00:00
Dan Brown
9e6f208dec New translations common.php (Arabic) 2019-12-27 16:39:02 +00:00
Dan Brown
5856998c60 New translations errors.php (Arabic) 2019-12-27 16:38:59 +00:00
Dan Brown
0d9be20af8 New translations auth.php (Chinese Simplified) 2019-12-27 16:38:57 +00:00
Dan Brown
f6816db615 New translations common.php (Chinese Simplified) 2019-12-27 16:38:56 +00:00
Dan Brown
faf455e78c New translations entities.php (Chinese Simplified) 2019-12-27 16:38:54 +00:00
Dan Brown
3534082c8c New translations settings.php (Chinese Simplified) 2019-12-27 16:38:52 +00:00
Dan Brown
8240295c86 New translations common.php (Chinese Traditional) 2019-12-27 16:38:50 +00:00
Dan Brown
bbb379919f New translations entities.php (Chinese Traditional) 2019-12-27 16:38:48 +00:00
Dan Brown
d1a9ddf3db New translations errors.php (Chinese Traditional) 2019-12-27 16:38:47 +00:00
Dan Brown
ee9c53325d New translations errors.php (Chinese Simplified) 2019-12-27 16:38:45 +00:00
Dan Brown
7c5488f318 New translations errors.php (Korean) 2019-12-27 16:38:44 +00:00
Dan Brown
f9969978a5 New translations auth.php (German) 2019-12-27 16:38:43 +00:00
Dan Brown
85c2e3bc8e New translations common.php (Japanese) 2019-12-27 16:38:41 +00:00
Dan Brown
f10808f6a0 New translations errors.php (Japanese) 2019-12-27 16:38:39 +00:00
Dan Brown
5d214b184d New translations settings.php (Japanese) 2019-12-27 16:38:38 +00:00
Dan Brown
f5a83eccb7 New translations auth.php (Korean) 2019-12-27 16:38:35 +00:00
Dan Brown
cf473edcbd New translations common.php (Korean) 2019-12-27 16:38:34 +00:00
Dan Brown
58b14d9e89 New translations entities.php (Korean) 2019-12-27 16:38:32 +00:00
Dan Brown
7cf60afe14 New translations passwords.php (Korean) 2019-12-27 16:38:30 +00:00
Dan Brown
78bd482931 New translations settings.php (Korean) 2019-12-27 16:38:29 +00:00
Dan Brown
83e946f146 New translations common.php (Polish) 2019-12-27 16:38:26 +00:00
Dan Brown
8d3220c98a New translations entities.php (Polish) 2019-12-27 16:38:25 +00:00
Dan Brown
9b0d1f1328 New translations errors.php (Polish) 2019-12-27 16:38:23 +00:00
Dan Brown
433d66e406 New translations activities.php (German) 2019-12-27 16:38:21 +00:00
Dan Brown
e2fec5cb4f New translations settings.php (Italian) 2019-12-27 16:38:20 +00:00
Dan Brown
702ad28a9a New translations common.php (German) 2019-12-27 16:38:18 +00:00
Dan Brown
8c224a57f0 New translations entities.php (German) 2019-12-27 16:38:17 +00:00
Dan Brown
a260279463 New translations errors.php (German) 2019-12-27 16:38:15 +00:00
Dan Brown
76f43b95d4 New translations settings.php (German) 2019-12-27 16:38:13 +00:00
Dan Brown
4b19ba9ed1 New translations validation.php (German) 2019-12-27 16:38:12 +00:00
Dan Brown
f1777280c3 New translations auth.php (Hungarian) 2019-12-27 16:38:10 +00:00
Dan Brown
4df112661f New translations common.php (Hungarian) 2019-12-27 16:38:09 +00:00
Dan Brown
33e17aed6e New translations entities.php (Hungarian) 2019-12-27 16:38:07 +00:00
Dan Brown
047b711fc4 New translations settings.php (Hungarian) 2019-12-27 16:38:04 +00:00
Dan Brown
15df902a32 New translations validation.php (Hungarian) 2019-12-27 16:38:02 +00:00
Dan Brown
5e2908c996 New translations common.php (Italian) 2019-12-27 16:38:01 +00:00
Dan Brown
6c9e162821 New translations entities.php (Italian) 2019-12-27 16:37:59 +00:00
Dan Brown
8235677b31 New translations errors.php (Italian) 2019-12-27 16:37:58 +00:00
Dan Brown
a4eb5c3268 New translations errors.php (Hungarian) 2019-12-27 16:37:56 +00:00
Dan Brown
4613dbfb8f Merge branch 'qianmengnet-master' 2019-12-27 16:29:06 +00:00
Dan Brown
8144887e4c Merge branch 'master' of git://github.com/qianmengnet/BookStack into qianmengnet-master 2019-12-27 16:28:56 +00:00
Dan Brown
d9f0d46d20 Merge pull request #1819 from johnroyer/translate
update translation
2019-12-27 16:22:59 +00:00
Dan Brown
611cb29073 Merge pull request #1804 from artskoczylas/master
Update of polish language
2019-12-27 16:21:23 +00:00
Dan Brown
84af59bbe6 Merge pull request #1791 from jzoy/master
Simplified Chinese Translation Update
2019-12-27 16:19:00 +00:00
Dan Brown
a3cf45cfb6 Merge pull request #1762 from dellamina/updateItalianTranslation
Update Italian translation
2019-12-27 16:18:04 +00:00
Dan Brown
dabe79a438 Merge pull request #1734 from ististudio/master
Update Korean translation
2019-12-27 16:16:55 +00:00
ezzra
a82d9fdba5 fix translate for "actions" 2019-12-27 15:47:03 +00:00
Lior Broshi
8794f62ad8 settings 2019-12-24 23:36:35 +02:00
Lior Broshi
290610b77e more heb 2019-12-23 22:04:15 +02:00
Dan Brown
865e8d4ec5 Improved markdown mobile editor experience
- Updated styles of codemirror area to be a bit more forefull in taking
up space.
- Added a fullscreen toggle as a backup option.

For #1675
2019-12-22 14:22:38 +00:00
Dan Brown
e06f9f7fe3 Removed setting override system due to confusing behaviour
- Was only used to disable registration when LDAP was enabled.
- Caused saved option not to show on settings page causing confusion.
- Extended setting logic where used to take ldap into account instead of
global override.
- Added warning on setting page to show registration enable setting is
not used while ldap is active.

For #1541
2019-12-22 13:19:17 +00:00
Dan Brown
32e7f0a2e6 Made display thumbnail generation use original data if smaller
Thumbnail generation would sometimes create a file larger than the
original, if the original was already well optimized, therefore making
the thumbnail counter-productive. This change compares the sizes of the
original and the generated thumbnail, and uses the smaller of the two if
the thumbnail does not change the aspect ratio of the image.

Fixes #1751
2019-12-22 12:44:49 +00:00
Lior Broshi
b65d4cda0e pagination, passwords and part of settings 2019-12-21 22:34:50 +02:00
Dan Brown
a83a7f34f4 Better standardised and fixes areas of image pasting
- Extracted logic to get images from paste/drop event into own file to
align usage in both events for both editors.
- Fixed non-ability to drag+drop into WYSIWYG editor.
- Updated check for table data to look for table specific rich-text
instead of just any text since some the old check was too general and
was preventing some legitimate image paste events.

Tested on Chrome and FireFox on Ubuntu.
Attempted to test on Safari via browserstack but environment was
unreliable and could not access folders to test drag/drop of files.

Relates to #1651 and #1697
2019-12-21 15:48:03 +00:00
Dan Brown
5491bd62a2 Fixed test failing due to redirect changes
- Also set APP_THEME param during testing to avoid local conflicts
2019-12-21 13:48:44 +00:00
Dan Brown
ceeb9d35a0 Updated JS dependancies 2019-12-21 13:30:41 +00:00
Zero
50ed56f65d update translation 2019-12-17 16:52:39 +08:00
Dan Brown
b93f8a4d46 Auto-expand collapsible sections if containing error
For #1693
2019-12-16 13:27:17 +00:00
Dan Brown
a9634b6b66 Set a default outline color and width
- Applied since the browser defaults caused outlines to appear very
large in some cases.
- Set default color to use app primary color, to help them blend into
the design a little.

For #1738
2019-12-16 13:15:11 +00:00
Dan Brown
f9fa6904b9 Made LDAP auth ID attribute configurable
- Allows the field that gets stored as the "External Authentication ID"
to be configurable. Defined as LDAP_ID_ATTRIBUTE=uid in .env.
- Added test to cover usage.
- Also now auto-lowercases when searching for attributes in LDAP
response since PHP always provides them as lower case.

Closes #592.
2019-12-16 12:40:21 +00:00
Dan Brown
017703ff1a Updated page delete to return to chapter if within one
- Added test to cover

Closes #1715
2019-12-16 11:54:53 +00:00
Dan Brown
f122bebae7 Prevented whitespace in codeblocks being trimmed
For #1771
2019-12-16 11:44:28 +00:00
Daniel Seiler
afa501e75b Recall previous route when manually clicking login 2019-12-14 08:41:22 +01:00
Dan Brown
02af69ddf2 Added command to copy shelf permissions
Has options to run for all or to specify a slug for a specific shelf.

Closes #1091
2019-12-11 21:22:03 +00:00
jzoy
7f0dbf350e Update entities.php 2019-12-11 20:03:32 +08:00
jzoy
b3889c380a Update entities.php 2019-12-11 19:24:18 +08:00
Dan Brown
cee4dccc55 Compacted entity color options in settings view
- Also extracted the view code into it's own blade template
- Made smaller color input styles
2019-12-07 21:23:15 +00:00
Dan Brown
615a050856 Merge branch 'settings-color-selector' of git://github.com/james-geiger/BookStack into james-geiger-settings-color-selector 2019-12-07 20:36:39 +00:00
Dan Brown
5a533fff8b Updated codeblock editor to work with fade animation
- Added fadeIn to animation JS service.
- Updated overlay to use anim service and to recieve a callback for
after-anim actions.
- Updated code editor popup to refresh Codemirror instance layout after
animation has completed.

Fixes #1672
2019-12-07 16:54:34 +00:00
Dan Brown
a6bbe46987 Updated code system to dynamically set php codemirror mode
- Codemirror mode mapping value can now be a function to dynamically set
mode depending on actual code content.
- Used above system to set php mode type, depending on if '<?php' tags
exist in content.

Closes #1557
2019-12-07 16:23:44 +00:00
Dan Brown
7af6fe4917 Standardised issue template name casing 2019-12-07 15:57:58 +00:00
Dan Brown
1746f2c249 Fixed issue template wording 2019-12-07 15:57:07 +00:00
Dan Brown
701d105b9e Updated details and attribution for translators 2019-12-07 15:54:25 +00:00
Artur Skoczylas
6bc6543402 consistent of book translation 2019-11-28 15:55:18 +01:00
Artur Skoczylas
000059f6cf Update polish language 2019-11-28 15:39:54 +01:00
Dan Brown
1bcb24a921 Added in the github translators that didn't save in the last comit 2019-11-22 22:41:00 +00:00
Dan Brown
abb5cc1852 Added a translator attriubtion file
Added list of users that have provided translations along with the
languages they have provided.

Github users, starting with an '@', were added via manually going
through past pull requests and inspecting the user and language.
2019-11-22 22:23:22 +00:00
qianmengnet
9bed57e40e Modify Chinese language pack
Modify Chinese language pack
2019-11-22 16:35:47 +08:00
abublihi
23a716a3ac Fix "Declaration of Middleware\TrustProxies::handle should be compatible with Fideloper\Proxy\TrustProxies::handle" 2019-11-20 14:00:20 +03:00
jzoy
f8279384a6 Simplified Chinese Translation Update 2019-11-19 18:22:23 +08:00
Dan Brown
4e09656c78 Merge pull request #1787 from BookStackApp/saml2_auth
SAML2 Authentication
2019-11-17 19:20:37 +00:00
Dan Brown
c33ef4b9b2 Added tests to cover saml and added controller middleware 2019-11-17 19:15:37 +00:00
Dan Brown
ebb3724892 Added onelogin attribution and tweaks after testing saml with onelogin 2019-11-17 17:00:42 +00:00
Dan Brown
6d899f3b17 Added icon for saml, added saml to register page, updated complete env 2019-11-17 16:07:06 +00:00
Dan Brown
aef6eb81e4 Added SAML singleLogoutService capabilities 2019-11-17 15:40:36 +00:00
Dan Brown
488325f459 Added the ability to auto-load config from metadata url 2019-11-17 14:44:26 +00:00
Dan Brown
3a17ba2cb9 Started using OneLogin SAML lib directly
- Aligned and formatted config options.
- Provided way to override onelogin lib options if required.
- Added endpoints in core bookstack routes.
- Provided way to debug details provided by idp and formatted by
bookstack.
- Started on test work
- Handled case of email address already in use.
2019-11-17 13:26:43 +00:00
Dan Brown
9bba84684f Appeased codeclimate by extracting out external_auth_id group matching 2019-11-16 15:24:09 +00:00
Dan Brown
8169c725d5 Started review of SAML implementation
- Updated PHPdoc of SAML service to use type hinting instead.
- Updated groups to only sync if enabled.
- Updated names of some config props.
- Removed a couple of unused config props.
- Added exception to handle no email on SAML response.
2019-11-16 14:42:51 +00:00
Dan Brown
bb1f43cbd8 Merge branch 'feature/saml' of git://github.com/Xiphoseer/BookStack into Xiphoseer-feature/saml 2019-11-16 12:42:45 +00:00
User
7c7098b601 Update some postpositional particles 2019-11-07 16:54:38 +09:00
User
ffd05f1584 Update some terms 2019-11-07 15:19:15 +09:00
Mattia Della Mina
b8a7ef15bd improve italian translation 2019-10-31 22:04:04 +01:00
jakob
6cd26e23a8 Allow toggling between grid and list view in shelf view (shelves.show) 2019-10-30 11:23:42 +01:00
Dan Brown
189a598d56 Merge branch 'master' of github.com:BookStackApp/BookStack 2019-10-29 22:34:12 +00:00
Dan Brown
90f9240be4 Merge branch 'philjak-feature_move_page_into_chapter' 2019-10-29 22:33:53 +00:00
Dan Brown
d64c358c4f Updated sort logic to handle chapter to book scenario
- Extended tests out to cover
2019-10-29 22:33:09 +00:00
Dan Brown
e108808a32 Merge branch 'feature_move_page_into_chapter' of git://github.com/philjak/BookStack into philjak-feature_move_page_into_chapter 2019-10-29 22:26:11 +00:00
Dan Brown
6a1b6a97f9 Added test for page move into chapter 2019-10-29 22:25:53 +00:00
jakob
bea983ab85 Download and assign avatar when creating LDAP user in database. Fixes issue #1161 2019-10-29 22:18:02 +00:00
jakob
7368ff3e6a No need to save page 2019-10-28 16:53:48 +01:00
jakob
4daeb9daa6 Check if parent is a chapter. If so, move into Book and assing page to chapter. 2019-10-28 15:33:28 +01:00
Dan Brown
b80aa2350f Merge branch 'philjak-feature_bugfix_save_book_cover' 2019-10-27 17:05:34 +00:00
Dan Brown
e26474f233 Merge branch 'feature_bugfix_save_book_cover' of git://github.com/philjak/BookStack into philjak-feature_bugfix_save_book_cover 2019-10-27 17:03:02 +00:00
Dan Brown
1b350d622d Merge branch 'cw1998-fix/#1662' 2019-10-27 16:57:00 +00:00
Dan Brown
4b9618cd21 Update book form so cancel URL is explicitly passed in
- Added to prevent future possibility of 'shelf' var being introduced in
scope and therefore causing a side-effect of redirect logic.
2019-10-27 16:55:05 +00:00
Dan Brown
28184c6bfc Merge branch 'fix/#1662' of git://github.com/cw1998/BookStack into cw1998-fix/#1662 2019-10-27 16:44:41 +00:00
Dan Brown
99ce3067c7 Added test to check custom theme lang items 2019-10-26 18:07:14 +01:00
Dan Brown
4763b899b6 Made it possible to override translations via theme system 2019-10-26 18:07:14 +01:00
Dan Brown
1366fc45ce Added tests to cover test email sends
- Also tweaked wording of 'E-mail' to 'Email' to remain consistent with
the rest of the app.

Related to #1696 and #1719
2019-10-23 20:25:51 +01:00
Dan Brown
a2370f7c9d Merge branch 'feature-send-test-email' of git://github.com/timoschwarzer/BookStack into timoschwarzer-feature-send-test-email 2019-10-23 19:53:51 +01:00
jakob
bc38fd3ac4 entity needs to be saved after image upload and associate 2019-10-22 11:18:08 +02:00
istist
5ae0e127df Update auth.php
Edit for  'Must be over 7 characters'.
2019-10-21 04:17:52 +09:00
User
2eb6f178c2 Banish .DS_Store 2019-10-21 03:51:55 +09:00
User
19ce6da0d3 Update Korean translation 2019-10-21 03:44:38 +09:00
Dan Brown
99ae592ae9 New Crowdin translations (#1732)
* New translations auth.php (Ukrainian)

* New translations common.php (Ukrainian)

* New translations entities.php (Ukrainian)

* New translations entities.php (Ukrainian)

* New translations settings.php (Ukrainian)

* New translations validation.php (Ukrainian)

* New translations settings.php (French)

* New translations settings.php (Russian)

* New translations settings.php (Spanish, Argentina)

* New translations settings.php (Portuguese, Brazilian)

* New translations settings.php (Chinese Traditional)

* New translations settings.php (Chinese Simplified)

* New translations settings.php (Ukrainian)

* New translations settings.php (Turkish)

* New translations settings.php (Swedish)

* New translations settings.php (Slovak)

* New translations settings.php (Polish)

* New translations settings.php (Spanish)

* New translations settings.php (Dutch)

* New translations settings.php (Korean)

* New translations settings.php (Japanese)

* New translations settings.php (Italian)

* New translations settings.php (Hungarian)

* New translations settings.php (German)

* New translations settings.php (Czech)

* New translations settings.php (Arabic)

* New translations settings.php (German Informal)
2019-10-19 00:13:00 +01:00
Dan Brown
f37131a5bf Removed old Translation Service + Provider
Was no longer needed due to only being there to perform
language extension for de_informal but now this is done by crowdin
instead so it's redundant. Same goes for checking and formatting
scripts.

Also removed comment advising deletion form settings.php language list
since this is now auto-copied to languages anyway.

Related to #1261
2019-10-19 00:04:49 +01:00
Dan Brown
f3a7d58816 Initial Crowdin Translation Integration Merge (#1731)
* New translations activities.php (French)

* New translations entities.php (Turkish)

* New translations entities.php (Swedish)

* New translations errors.php (Swedish)

* New translations pagination.php (Swedish)

* New translations passwords.php (Swedish)

* New translations settings.php (Swedish)

* New translations validation.php (Swedish)

* New translations auth.php (Turkish)

* New translations common.php (Turkish)

* New translations errors.php (Turkish)

* New translations common.php (Swedish)

* New translations settings.php (Turkish)

* New translations validation.php (Turkish)

* New translations activities.php (Ukrainian)

* New translations auth.php (Ukrainian)

* New translations common.php (Ukrainian)

* New translations components.php (Ukrainian)

* New translations entities.php (Ukrainian)

* New translations errors.php (Ukrainian)

* New translations components.php (Swedish)

* New translations auth.php (Swedish)

* New translations passwords.php (Ukrainian)

* New translations settings.php (Russian)

* New translations settings.php (Polish)

* New translations validation.php (Polish)

* New translations activities.php (Russian)

* New translations auth.php (Russian)

* New translations common.php (Russian)

* New translations components.php (Russian)

* New translations entities.php (Russian)

* New translations errors.php (Russian)

* New translations passwords.php (Russian)

* New translations validation.php (Russian)

* New translations activities.php (Swedish)

* New translations activities.php (Slovak)

* New translations auth.php (Slovak)

* New translations common.php (Slovak)

* New translations components.php (Slovak)

* New translations entities.php (Slovak)

* New translations errors.php (Slovak)

* New translations pagination.php (Slovak)

* New translations passwords.php (Slovak)

* New translations settings.php (Slovak)

* New translations validation.php (Slovak)

* New translations pagination.php (Ukrainian)

* New translations settings.php (Ukrainian)

* New translations pagination.php (Polish)

* New translations passwords.php (Spanish, Argentina)

* New translations settings.php (Portuguese, Brazilian)

* New translations validation.php (Portuguese, Brazilian)

* New translations activities.php (Spanish, Argentina)

* New translations auth.php (Spanish, Argentina)

* New translations common.php (Spanish, Argentina)

* New translations components.php (Spanish, Argentina)

* New translations entities.php (Spanish, Argentina)

* New translations errors.php (Spanish, Argentina)

* New translations pagination.php (Spanish, Argentina)

* New translations settings.php (Spanish, Argentina)

* New translations errors.php (Portuguese, Brazilian)

* New translations validation.php (Spanish, Argentina)

* New translations activities.php (German Informal)

* New translations auth.php (German Informal)

* New translations common.php (German Informal)

* New translations components.php (German Informal)

* New translations entities.php (German Informal)

* New translations errors.php (German Informal)

* New translations pagination.php (German Informal)

* New translations passwords.php (German Informal)

* New translations settings.php (German Informal)

* New translations entities.php (Portuguese, Brazilian)

* New translations validation.php (Ukrainian)

* New translations activities.php (Chinese Traditional)

* New translations activities.php (Chinese Simplified)

* New translations auth.php (Chinese Simplified)

* New translations common.php (Chinese Simplified)

* New translations components.php (Chinese Simplified)

* New translations entities.php (Chinese Simplified)

* New translations errors.php (Chinese Simplified)

* New translations pagination.php (Chinese Simplified)

* New translations passwords.php (Chinese Simplified)

* New translations settings.php (Chinese Simplified)

* New translations validation.php (Chinese Simplified)

* New translations auth.php (Chinese Traditional)

* New translations components.php (Portuguese, Brazilian)

* New translations common.php (Chinese Traditional)

* New translations components.php (Chinese Traditional)

* New translations entities.php (Chinese Traditional)

* New translations errors.php (Chinese Traditional)

* New translations pagination.php (Chinese Traditional)

* New translations passwords.php (Chinese Traditional)

* New translations settings.php (Chinese Traditional)

* New translations validation.php (Chinese Traditional)

* New translations activities.php (Portuguese, Brazilian)

* New translations auth.php (Portuguese, Brazilian)

* New translations common.php (Portuguese, Brazilian)

* New translations passwords.php (Polish)

* New translations errors.php (Polish)

* New translations auth.php (French)

* New translations validation.php (Czech)

* New translations validation.php (Arabic)

* New translations auth.php (Czech)

* New translations common.php (Czech)

* New translations entities.php (Czech)

* New translations errors.php (Czech)

* New translations pagination.php (Czech)

* New translations settings.php (Czech)

* New translations activities.php (German)

* New translations passwords.php (Arabic)

* New translations auth.php (German)

* New translations common.php (German)

* New translations components.php (German)

* New translations entities.php (German)

* New translations errors.php (German)

* New translations pagination.php (German)

* New translations passwords.php (German)

* New translations settings.php (German)

* New translations validation.php (German)

* New translations settings.php (Arabic)

* New translations pagination.php (Arabic)

* New translations common.php (Hungarian)

* New translations common.php (Spanish)

* New translations common.php (French)

* New translations components.php (French)

* New translations entities.php (French)

* New translations errors.php (French)

* New translations pagination.php (French)

* New translations passwords.php (French)

* New translations settings.php (French)

* New translations validation.php (French)

* New translations errors.php (Arabic)

* New translations settings.php (Spanish)

* New translations validation.php (Spanish)

* New translations activities.php (Arabic)

* New translations auth.php (Arabic)

* New translations common.php (Arabic)

* New translations components.php (Arabic)

* New translations entities.php (Arabic)

* New translations auth.php (Hungarian)

* New translations entities.php (Polish)

* New translations common.php (Dutch)

* New translations common.php (Korean)

* New translations components.php (Korean)

* New translations entities.php (Korean)

* New translations errors.php (Korean)

* New translations pagination.php (Korean)

* New translations passwords.php (Korean)

* New translations settings.php (Korean)

* New translations validation.php (Korean)

* New translations activities.php (Dutch)

* New translations auth.php (Dutch)

* New translations components.php (Dutch)

* New translations activities.php (Korean)

* New translations entities.php (Dutch)

* New translations errors.php (Dutch)

* New translations pagination.php (Dutch)

* New translations passwords.php (Dutch)

* New translations settings.php (Dutch)

* New translations validation.php (Dutch)

* New translations activities.php (Polish)

* New translations auth.php (Polish)

* New translations common.php (Polish)

* New translations components.php (Polish)

* New translations auth.php (Korean)

* New translations validation.php (Japanese)

* New translations entities.php (Hungarian)

* New translations errors.php (Italian)

* New translations errors.php (Hungarian)

* New translations settings.php (Hungarian)

* New translations validation.php (Hungarian)

* New translations activities.php (Italian)

* New translations auth.php (Italian)

* New translations common.php (Italian)

* New translations components.php (Italian)

* New translations entities.php (Italian)

* New translations pagination.php (Italian)

* New translations settings.php (Japanese)

* New translations passwords.php (Italian)

* New translations settings.php (Italian)

* New translations validation.php (Italian)

* New translations activities.php (Japanese)

* New translations auth.php (Japanese)

* New translations common.php (Japanese)

* New translations components.php (Japanese)

* New translations entities.php (Japanese)

* New translations errors.php (Japanese)

* New translations pagination.php (Japanese)

* New translations passwords.php (Japanese)

* New translations validation.php (German Informal)
2019-10-18 17:08:22 +01:00
Albergoni Andrea
48c44958f5 Added support for Pascal language 2019-10-18 16:34:38 +02:00
Dan Brown
f1d7699df5 Updated Korean to be correct country code 2019-10-18 14:27:41 +01:00
Dan Brown
37560a3f8c Fixed crowdin translation path 2019-10-18 02:09:12 +01:00
Dan Brown
5f3caf3643 Update Crowdin configuration file 2019-10-18 01:46:30 +01:00
Dan Brown
c8d90791ff Moved crownin config file 2019-10-18 01:45:25 +01:00
Dan Brown
c108c16419 Added crowdin config file 2019-10-18 01:40:50 +01:00
Dan Brown
b09ea76b8d Renamed properties input option as INI
- Also made INI be recognised as the codemirror "Properties" format.
2019-10-17 21:16:55 +01:00
Dan Brown
8b4bfa4d78 Merge branch 'master' of git://github.com/c0shea/BookStack into c0shea-master 2019-10-17 21:09:05 +01:00
James Geiger
e6fe299c4f added additional color settings into UI
Adds new options in the customization section of the settings to change the shelf, book, chapter, page, and draft colors.
2019-10-17 13:46:18 -05:00
James Geiger
ae19658b50 Placeholder for allowing colors to be changed in settings UI. 2019-10-17 09:44:20 -05:00
Dan Brown
d7557befe2 Copied release page link to normal settings page
- Also updated link to not leak referrer info
2019-10-17 15:06:55 +01:00
Dan Brown
5c7262673a Merge branch 'patch-1' of git://github.com/DeftNerd/BookStack into DeftNerd-patch-1 2019-10-17 14:58:20 +01:00
Dan Brown
ef762e851a Reverted changes to codemirror line wrapping 2019-10-17 14:41:39 +01:00
Dan Brown
e91ef54cc9 Merge branch 'fix-1575' of git://github.com/james-geiger/BookStack into james-geiger-fix-1575 2019-10-17 14:32:39 +01:00
Dan Brown
3959841dbc Added back in some tabindex that shouldn't have been removed 2019-10-17 14:21:13 +01:00
Dan Brown
e48d7d59cc Removed tabindexes where found to be not required 2019-10-17 14:19:35 +01:00
Dan Brown
5a887e31da Merge branch 'master' of git://github.com/almandin/BookStack into almandin-master 2019-10-17 14:09:07 +01:00
Dan Brown
df98deb59d Added Turkish to locale system 2019-10-17 14:01:19 +01:00
Dan Brown
ddb7f33868 Merge branch 'master' of git://github.com/oykenfurkan/BookStack into oykenfurkan-master 2019-10-17 13:56:53 +01:00
Dan Brown
076cf59a41 Merge pull request #1695 from qligier/master
French translation update
2019-10-17 13:46:33 +01:00
Dan Brown
4ab6a25893 Merge pull request #1681 from leomartinez/master
Updated 'Spanish Argentina' translation.
2019-10-17 13:44:28 +01:00
Dan Brown
f413fc528a Merge pull request #1646 from kostefun/patch-15
Update settings.php
2019-10-17 13:43:27 +01:00
Dan Brown
76bd0fdfa6 Added editor instance event hooks
As per #1721
2019-10-16 18:01:35 +01:00
Dan Brown
b24279cc12 Merge branch 'patching-v0.27' 2019-10-16 16:37:29 +01:00
Dan Brown
af6f34b529 Updated version and assets for release v0.27.5 2019-10-16 16:35:50 +01:00
Dan Brown
fb82a2b896 Merge branch 'patching-v0.27' into release 2019-10-16 16:35:10 +01:00
Timo Schwarzer
61a9139bf0 Add feature to send test e-mails 2019-10-16 08:24:33 +02:00
Dan Brown
d6456e961a Fixed issue causing text overlap in sort select box
Updated grid columns to be more adaptable to content, with a min-width
of the old value.
Fixes #1654
2019-10-07 21:06:15 +01:00
Dan Brown
d4c62265ca Made JS animation cleanup process more reliable
Fixes #1643
2019-10-07 20:57:25 +01:00
Dan Brown
b6c0baf44d Updated comment delete action to be a button
Fixes issue that causes code error when an anchor tag.

Closes #1650
2019-10-07 20:21:04 +01:00
Dan Brown
ccec991f6c Added zip/unzip to docker dev setup for composer to use 2019-10-05 13:21:38 +01:00
Dan Brown
6736779eff Merge pull request #1698 from 3mmarg97/master
Fixing composer install inside docker `app` container
2019-10-05 13:13:29 +01:00
Dan Brown
31f5786e01 Entity Repo & Controller Refactor (#1690)
* Started mass-refactoring of the current entity repos

* Rewrote book tree logic

- Now does two simple queries instead of one really complex one.
- Extracted logic into its own class.
- Remove model-level akward union field listing.
- Logic now more readable than being large separate query and
compilation functions.

* Extracted and split book sort logic

* Finished up Book controller/repo organisation

* Refactored bookshelves controllers and repo parts

* Fixed issues found via phpunit

* Refactored Chapter controller

* Updated Chapter export controller

* Started Page controller/repo refactor

* Refactored another chunk of PageController

* Completed initial pagecontroller refactor pass

* Fixed tests and continued reduction of old repos

* Removed old page remove and further reduced entity repo

* Removed old entity repo, split out page controller

* Ran phpcbf and split out some page content methods

* Tidied up some EntityProvider elements

* Fixed issued caused by viewservice change
2019-10-05 12:55:01 +01:00
Ammar Al-Khawaldeh
3f025f69cf Add git to the apt-get install packages. 2019-10-03 20:46:49 +03:00
Quentin Ligier
593933c84e French translation update 2019-10-01 20:34:54 +02:00
Christopher Wilkinson
4ad4dfa55a Show bookshelves that a book belongs to on a book view
Closes #1598
2019-09-27 00:45:22 +01:00
Christopher Wilkinson
2f94f078e3 Fix Book form (create) returning to the full books list on cancel
Fixes #1662
Added a small block of logic to determine the correct URL to attribute to the cancel button on a given page create form.
If adding a book from a bookshelf, return to the bookshelf. If editing a book, return to the book. In all other cases, return to the full books list.
2019-09-26 22:51:24 +01:00
Leonardo
0fa4ef072f Updated 'Spanish Argentina' translation. 2019-09-24 22:36:08 -03:00
Dan Brown
7cd956b24b Removed some unused parameters and fixed env test logic 2019-09-20 01:18:59 +01:00
Dan Brown
8b550991a4 Refactored some core entity actions
- Created BookChild class to share some page/chapter logic.
- Gave entities the power to generate their own permissions and slugs.
- Moved bits out of BaseController constructor since it was overly
sticky.
- Moved slug generation logic into its own class.
- Created a facade for permissions due to high use.
- Fixed failing test issues from last commits
2019-09-20 00:18:28 +01:00
Dan Brown
f7a5a0705b Moved shelf book append logic 2019-09-19 18:20:09 +01:00
Dan Brown
615b2de433 Simplified activity facade interface
Also cleaned up any other bits along the way.
2019-09-19 18:03:17 +01:00
Dan Brown
2a2cc858f0 Refactored notification showing and global view data 2019-09-19 15:12:10 +01:00
Connor O'Shea
fca69643db Normalize ini and properties values 2019-09-15 20:24:47 -04:00
Connor O'Shea
4ad43b1a1f Add support for properties 2019-09-15 20:22:26 -04:00
Connor O'Shea
228aa4740b Add support for properties (INI) 2019-09-15 20:20:11 -04:00
Dan Brown
60d0f96cd7 Extracted some methods into a BookRepo 2019-09-15 23:28:23 +01:00
Dan Brown
d28abf24d4 Split out export actions into own controllers 2019-09-15 22:33:27 +01:00
Dan Brown
3281925375 Standardised how request is injected into controller methods
Puts it in-line with how Laravel recommend.
2019-09-15 18:53:30 +01:00
Dan Brown
be08dc1588 Ran phpcbf and updated helpers typehinting 2019-09-15 18:29:51 +01:00
Dan Brown
b1566099a3 Added laravel stats package and enabled debugbar models 2019-09-15 18:07:00 +01:00
Dan Brown
e81f90d9bd Updated twitch provider 2019-09-15 17:50:08 +01:00
Dan Brown
8e212bdeab Ran NPM audit fix 2019-09-15 17:39:07 +01:00
Dan Brown
2ab5df75dd Updated version and removed travis 2019-09-14 14:23:16 +01:00
Dan Brown
ab91e245fb Merge branch 'master' of github.com:BookStackApp/BookStack 2019-09-14 14:21:35 +01:00
Dan Brown
1ee3e779e4 Merge branch 'laravel-upgrade' 2019-09-14 14:21:24 +01:00
Dan Brown
58a79fcb19 Removed old str_random functions from seeders 2019-09-14 14:17:55 +01:00
Dan Brown
cbf9d701af Updated to laravel 6 2019-09-14 14:12:39 +01:00
Dan Brown
140298bd96 Updated to Laravel 5.8 2019-09-13 23:58:40 +01:00
Furkan
84c36f6032 Added Turkish translations. 2019-09-13 15:45:38 +03:00
oykenfurkan
ec29c08beb Delete tr 2019-09-13 15:32:54 +03:00
oykenfurkan
9e8e5b2ca0 Create tr
Adding Turkish translations
2019-09-13 15:32:40 +03:00
Dan Brown
f311635de6 Add testing via GitHub actions (#1657)
To replace Travis CI. Travis to be removed after migration to Laravel 6.
2019-09-12 21:19:05 +01:00
kostefun
339d3849b8 Update settings.php
fix rus
2019-09-09 10:07:16 +07:00
Lior Broshi
974e52be5c errors.php 2019-09-08 23:08:49 +03:00
Lior Broshi
d84de86b40 entities.php 2019-09-07 22:46:30 +03:00
Dan Brown
5b464938b6 Updated version and assets for release v0.27.4 2019-09-07 13:30:08 +01:00
Dan Brown
81f954890d Merge branch 'patching-v0.27' into release 2019-09-07 13:29:53 +01:00
Dan Brown
23db81f2cc Merge pull request #1637 from kostefun/patch-14
Update validation.php
2019-09-07 13:22:12 +01:00
Dan Brown
a35c1bfb13 Merge pull request #1636 from kostefun/patch-13
Update passwords.php
2019-09-07 13:21:56 +01:00
Dan Brown
0b75b33044 Merge pull request #1635 from kostefun/patch-12
Update pagination.php
2019-09-07 13:21:37 +01:00
Dan Brown
b8617eb3d1 Merge pull request #1630 from kostefun/patch-11
Update errors.php
2019-09-07 13:21:11 +01:00
Dan Brown
1bf3666d6b Merge pull request #1629 from kostefun/patch-10
Update entities.php
2019-09-07 13:20:50 +01:00
Dan Brown
caaed849d5 Merge pull request #1628 from kostefun/patch-9
Update components.php
2019-09-07 13:20:10 +01:00
Dan Brown
e98986fc2d Merge pull request #1626 from kostefun/patch-7
Update auth.php
2019-09-07 13:19:39 +01:00
Dan Brown
c764208b25 Merge pull request #1624 from kostefun/patch-6
Update settings.php
2019-09-07 13:18:55 +01:00
Dan Brown
8fbe677975 Merge pull request #1627 from kostefun/patch-8
Update common.php
2019-09-07 13:18:01 +01:00
Dan Brown
d6e27af14a Merge pull request #1633 from moucho/master
Updated Spanish translation
2019-09-07 13:16:24 +01:00
Dan Brown
0b55e52e4e Merge pull request #1623 from leomartinez/master
Updated 'Spanish Argentina' translation.
2019-09-07 13:14:27 +01:00
Dan Brown
0345ece890 Fixed issue that caused quick-sort buttons to not be clickable
Fixes #1639
2019-09-07 13:08:55 +01:00
Dan Brown
58f5508b05 Added mysql service for travis 2019-09-07 00:14:19 +01:00
Dan Brown
de8a1a372d Updated travis-ci php versions 2019-09-07 00:10:03 +01:00
Dan Brown
6917ea088f Upgraded app to Laravel 5.7 2019-09-06 23:36:16 +01:00
Dan Brown
213e9d2941 Upgraded to Laravel 5.6 2019-09-06 22:14:39 +01:00
kostefun
739efdb0e2 Update entities.php
fix
2019-09-05 10:25:17 +07:00
kostefun
ac5784d0d0 Update entities.php 2019-09-05 10:20:22 +07:00
kostefun
e1aa212db8 Update validation.php
Fix & add Ru
2019-09-05 10:10:15 +07:00
kostefun
6758a42f80 Update passwords.php
Corresponds to the English version
2019-09-05 09:52:24 +07:00
kostefun
10199d684e Update pagination.php
Corresponds to the English version
2019-09-05 09:46:45 +07:00
moucho
21e5685816 Update Spanish translation 2019-09-04 15:40:25 +02:00
kostefun
746ad01ebb Update errors.php
update ru
2019-09-04 16:42:04 +07:00
kostefun
26b42b6414 Update entities.php
Update ru
2019-09-04 16:35:44 +07:00
kostefun
22a665a942 Update components.php
update ru
2019-09-04 16:07:10 +07:00
kostefun
7a5fc800b5 Update common.php
update ru
2019-09-04 16:05:20 +07:00
kostefun
3148b316cf Update auth.php
update ru
2019-09-04 15:57:29 +07:00
kostefun
1c2f43b3a6 Update settings.php 2019-09-04 15:41:08 +07:00
Leonardo
1bb1b568af Updated 'Spanish Argentina' translation. 2019-09-03 21:33:37 -03:00
Dan Brown
0e2bbcec62 Updated version and assets for release v0.27.3 2019-09-03 21:50:12 +01:00
Dan Brown
fdd339f525 Merge branch 'master' into release 2019-09-03 21:49:46 +01:00
Dan Brown
16d8a667b1 Fixed issue preventing FormData posting correctly
- Due to migration from Axios, Instances where we were sending FormData
were not considered and always converted to JSON which resulted in empty
JSON bodies.

Related to #1621
2019-09-03 21:46:46 +01:00
Leonardo
a1f89ad589 Merge remote-tracking branch 'upstream/master' 2019-09-02 17:04:11 -03:00
Dan Brown
8cf7d6a83d Updated version and assets for release v0.27.2 2019-09-01 12:12:23 +01:00
Dan Brown
58a5008718 Merge branch 'master' into release 2019-09-01 12:12:10 +01:00
Dan Brown
7a4425473b Fixed URL gen issue causing incorrect scheme to be used
For #1613
2019-09-01 12:07:51 +01:00
Dan Brown
c44a8df55d Updated version and assets for release v0.27.1 2019-09-01 11:13:50 +01:00
Dan Brown
ff1494c519 Merge branch 'master' into release 2019-09-01 11:13:18 +01:00
Dan Brown
f421a2e1d6 Updated pointer button styles so icon not hidden
Related to #1616
2019-09-01 11:06:19 +01:00
Dan Brown
79f1e87cd0 Updated export styles to remove grey bg 2019-09-01 10:55:00 +01:00
Dan Brown
e9d42a2e8c Fixed no md editor preview in FireFox 2019-09-01 10:51:52 +01:00
Dan Brown
712ea21efe Added mailhog to the docker-compose setup 2019-08-31 18:09:40 +01:00
Dan Brown
b8ce8fd852 Updated assets for release v0.27 2019-08-31 14:16:14 +01:00
Dan Brown
75e7454a5f Merge branch 'master' into release and set version 2019-08-31 14:15:18 +01:00
Dan Brown
36195c94df Merge branch 'timoschwarzer-docker-development-environment' 2019-08-26 22:01:19 +01:00
Dan Brown
21f09a5920 Added ldap and moved compose for docker-dev setup
- Also tweaked readme a little to fit this is with more recent changes.
2019-08-26 22:01:10 +01:00
Dan Brown
aea5319256 Merge branch 'docker-development-environment' of git://github.com/timoschwarzer/BookStack into timoschwarzer-docker-development-environment 2019-08-26 21:24:56 +01:00
Dan Brown
444a23a419 Merge pull request #1573 from miles75/lang-hu
Typo fix
2019-08-26 16:07:45 +01:00
Dan Brown
fde3867313 Made it possible to drag in template content 2019-08-26 15:34:03 +01:00
Dan Brown
5979f6667b Tweaked entity color palette for accessibility
Also converted entity colors to CSS variables for easier
instance customization.

Related to #1320
2019-08-26 14:38:50 +01:00
Dan Brown
64abe10dc4 Improved accessibility for many editor page components
Related to #1320
2019-08-26 12:47:04 +01:00
Dan Brown
7cc17934a8 Made MD editor display a sandboxed iframe
- Also added escaping of srcdoc elements in escape logic.

Related to #1531
2019-08-26 12:16:50 +01:00
Dan Brown
2dfe6c2d56 Fixed failing test and added more accessibility improvements
- Updated linked images to have obvious focus styles
- Added proper role to notifications
- Made dropdown list focus styles a bit nicer.
- Updated book list chapter child slide down to be keyboard activatable.

Related to #1320
2019-08-25 17:21:25 +01:00
Dan Brown
9fbef8cd1b Re-orged readme and added a11y info
- Also tweaked default theme color a tad to better fit in Level A
standard.
2019-08-25 16:19:56 +01:00
Dan Brown
cf5d51e7b8 Made another mass of accessibility improvements
- Set proper semantic tags for main parts of content.
- Removed focus-trap from tag manager/autosuggest.
- Set better accessibility labelling on tag manager.
- Updated collapsible sections to be keyboard navigatable.
- Improved input focus styling to better fit theme.
- Updated custom styled file picker to be accessible via keyboard.

Related to #1320
2019-08-25 15:44:51 +01:00
Dan Brown
ae93a6ed07 Converted primary color use to css variable
- Removed all existing SCSS usage of primary color.
- Cut down custom styles injection to just be css vars.
- Reduced button styles so default button is primary.
- Updated button styles to lighten/brighten on hover & active states even
when a custom color is set.
- Removed unused scss color vars.
- Updated default BookStack blue to achieve better accessibility.
2019-08-25 12:40:04 +01:00
Dan Brown
b792108bc1 Updated print css for recent redesign
Fixes #1472
2019-08-25 11:30:26 +01:00
Dan Brown
4bf77f67dd Set comment add box to show with correct permissions
- Also fixed const assignment issue in translations.js
2019-08-25 11:02:58 +01:00
Dan Brown
88e076a12a Removed console.log 2019-08-24 18:39:38 +01:00
Dan Brown
b27a5c7fb8 Made a mass of accessibility improvements
- Changed default focus styles
- Updated dropdowns with keyboard navigation
- Updated modals with esc exiting
- Added accessibility attirbutes where needed
- Made many more elements focusable
- Updated hover effects of many items to also apply when focused within

Related to #1320 and #1198
2019-08-24 18:29:02 +01:00
Dan Brown
1b33a0c5b9 Added labels and tweaked muted colors for accessibility
Home now passing automated checks in accessibility insights for web.
2019-08-18 19:17:43 +01:00
Dan Brown
78f8a51664 Merge branch 'kostasdizas-unicode' 2019-08-18 18:58:06 +01:00
Dan Brown
666213a4d4 Removed html dir tag for now, Updated lang format 2019-08-18 18:57:35 +01:00
Dan Brown
3acea12f1c Merge branch 'unicode' of git://github.com/kostasdizas/BookStack into kostasdizas-unicode 2019-08-18 18:51:20 +01:00
Dan Brown
eab0ca9648 Covered new invite system with testing
Closes #316
2019-08-18 13:55:28 +01:00
Dan Brown
42d8548960 Finished new user invite flow 2019-08-18 13:11:30 +01:00
Dan Brown
e5155a5dcb Refactored confirm actions to their own controller 2019-08-18 10:47:59 +01:00
Dan Brown
44330bdd24 Start user invite system 2019-08-17 15:52:33 +01:00
Lior Broshi
019085071a entities, not done yet 2019-08-15 23:24:45 +03:00
James Geiger
c14611d14b Fixed inline code overflowing off of page in issue #1575. 2019-08-14 23:45:48 -05:00
James Geiger
fccc112cc1 Merge pull request #1 from BookStackApp/master
merge aug. 11 commits
2019-08-14 06:10:43 +00:00
Virgile
3bcfe2a460 Adds autofocus on the email field of the standard login page. 2019-08-13 17:30:29 +02:00
Timo Schwarzer
c6b1f36412 Alter docker paths 2019-08-12 22:19:58 +02:00
Timo Schwarzer
b608bb8859 Remove additional database connections and seeders in docker env 2019-08-12 16:59:51 +02:00
Timo Schwarzer
9357620d55 Add docker development environment 2019-08-12 16:43:39 +02:00
Dan Brown
4de432b50d Removed console.log & added readme discord badge 2019-08-11 20:30:51 +01:00
Dan Brown
20c36d58a6 Merge pull request #1527 from BookStackApp/129-page-templates
Page Templates Implementation
2019-08-11 20:21:17 +01:00
Dan Brown
5fdab3b8af Updated template test to be more stable 2019-08-11 20:10:27 +01:00
Dan Brown
de3e9ab094 Added ability to use templates
- Added replace, append and prepend actions for template content into
both the WYSIWYG editor and markdown editor.
- Added further testing to cover.
2019-08-11 20:04:43 +01:00
Daniel Seiler
8e723f10dc Add error messages, fix LDAP error 2019-08-07 15:31:10 +02:00
Daniel Seiler
03dbe32f99 Refactor for codestyle 2019-08-07 12:07:21 +02:00
Daniel Seiler
bda0082461 Add login and automatic registration; Prepare Group sync 2019-08-06 23:42:46 +02:00
Dan Brown
421dd93ffd Merge branch 'v0.26' 2019-08-06 21:50:56 +01:00
Dan Brown
2558ea8931 Updated version for release v0.26.4 2019-08-06 21:42:09 +01:00
Dan Brown
ac0f47a4b2 Merge branch 'v0.26' into release 2019-08-06 21:41:06 +01:00
Dan Brown
f417675b1d Prevented normal users from changing own email
To address #1542

Updates to only allow email changes by users with the users-manage role
permission.
2019-08-06 21:29:42 +01:00
Dan Brown
2955f414dd Added iframe JS and data url escaping
Related to #1531
2019-08-06 21:08:24 +01:00
Dan Brown
4de719b325 Prevented potential apache image dir listing
Closes #1545
2019-08-06 20:35:27 +01:00
Daniel Seiler
3c41b15be6 Initial work on SAML integration 2019-08-05 20:06:39 +02:00
miles
518d9df961 Typo fix 2019-08-05 10:43:48 +02:00
Dan Brown
2ebbc6b658 Merge branch 'master' into 129-page-templates 2019-08-04 16:26:38 +01:00
Dan Brown
0b1ece664d Updated editor z-indexing to not hide app menu
Closes #1556
2019-08-04 16:21:16 +01:00
Dan Brown
83ef086470 Added missing locale option 2019-08-04 16:10:04 +01:00
Dan Brown
1b8a164412 Merge pull request #1561 from danielroehrig-mm/task/updateGermanTranslation
German translation extended
2019-08-04 16:08:48 +01:00
Dan Brown
84a387cf5b Merge pull request #1554 from miles75/lang-hu
Hungarian translation
2019-08-04 16:07:19 +01:00
Dan Brown
71ebb9df8b Removed unused config item
Left in by mistake during development
2019-08-04 14:41:08 +01:00
Dan Brown
0ac50c0e50 Updated phpunit instructions to composer phpunit
Closes #1555
2019-08-04 14:34:02 +01:00
Dan Brown
4b0c4e621a Replaced use of custom 'baseUrl' helper with 'url'
Also changed up how base URL setting was being done
by manipulating incoming request URLs instead of
altering then on generation.
2019-08-04 14:26:39 +01:00
Daniel Röhrig
fbf8378ae5 German translation extended 2019-07-31 13:44:28 +02:00
miles
d63157175b Hungarian translation 2019-07-27 14:03:01 +02:00
Lior Broshi
6c7e74f475 components 2019-07-22 22:57:00 +03:00
Lior Broshi
b153ffd019 common 2019-07-22 22:35:29 +03:00
Lior Broshi
9cfa391ab6 auth 2019-07-22 22:31:01 +03:00
Dan Brown
30da105812 Started refactor of URL system to better extend Laravel 2019-07-21 21:32:08 +01:00
Dan Brown
1e7df28238 Set export service to set correct svg image mimetype
For #1538
2019-07-17 22:37:19 +01:00
Dan Brown
629b7a674e Merge pull request #1534 from DeehSlash/pt_BR
Fix pt_BR translations
2019-07-15 20:11:03 +01:00
Dan Brown
ce1c70084e Merge pull request #1485 from lucaguindani/update-fr-lang
Updated the french lang files
2019-07-15 20:02:13 +01:00
André Luiz da Silva
94f610d040 Fix pt_BR translations 2019-07-11 19:30:00 -03:00
Dan Brown
8fcb0e6820 Merge branch 'v0.26' 2019-07-10 20:30:36 +01:00
Dan Brown
4f16129869 Updated version for release v0.26.3 2019-07-10 20:21:22 +01:00
Dan Brown
64a8037fdd Merge branch 'v0.26' into release 2019-07-10 20:19:54 +01:00
Dan Brown
c732970f6e Hardened page content script escaping
Increased range of tests to cover.

Fixes #1531
2019-07-10 20:17:22 +01:00
Lior Broshi
de140ab5a5 started Hebrew translation, not done yet 2019-07-09 22:40:35 +03:00
Dan Brown
94441832c5 Removed old translation endpoint tests 2019-07-07 13:54:17 +01:00
Dan Brown
71167426bb Started implementation of page template 2019-07-07 13:45:46 +01:00
Dan Brown
f7f7cd464c Removed jquery from dropzone
Also added fadeout to custom animation lib
2019-07-06 15:08:26 +01:00
Dan Brown
15c39c1976 Updated JS translations to be inserted from back-end
Removes old awkward JS translations endpoint.
New system still a little akward in code but not now in process.

Also extracted out page editors into their own files.

Closes #1258
2019-07-06 14:52:25 +01:00
Dan Brown
6fa093d9d0 Merge branch 'master' of github.com:BookStackApp/BookStack 2019-07-06 13:45:31 +01:00
Dan Brown
97fdfa6ebe Moved config dir into app dir
Closes #1506
2019-07-06 13:44:50 +01:00
Dan Brown
5c43a5a59a Merge pull request #1517 from bjubes/patch-1
Fix missing word in users_social_accounts_info string
2019-07-03 19:54:26 +01:00
Brian Jubelirer
e7508689de fix missing word 2019-07-02 09:14:42 -04:00
Dan Brown
5c70413784 Fixed incorrect testing vars and reset env vars in config test 2019-06-25 22:52:07 +01:00
Dan Brown
52b4c81aff Merge pull request #1505 from timoschwarzer/hide-permissions-table-unless-enabled
Hide permissions table unless custom permissions are enabled
2019-06-25 21:52:52 +01:00
Dan Brown
9a080da29f Added debugbar config
- Set debugbar support for APP_URL
- Set debugbar to not show by default in debug mode.

Closes #1508
2019-06-24 20:24:24 +01:00
Dan Brown
762d1d7595 Allowed different storage types for images and attachments
- Added new env and config vars to allow this.
- Also added tests for awkward config logic including fallback for new
env vars.

Closes #1302
2019-06-23 16:01:15 +01:00
Timo Schwarzer
6504a6f599 Hide permissions table unless custom permissions are enabled 2019-06-23 14:29:58 +02:00
Dan Brown
bf1371d04c Fixed firefox tri-layout grid issue and added tablet sticky sidebar
- Fixed issue with original left-sidebar content being placed halfway
down the page.
- Added sticky sidebar to mid-size tablet layout, only for original left
sidebar items.

Fixes #1434.
2019-06-16 12:46:23 +01:00
Dan Brown
f08668706f Updated page-nav to show more title content
Will now be truncated using CSS instead of being truncated on PHP side.
Closes #1206.
2019-06-16 12:08:07 +01:00
Dan Brown
7b506447c7 Updated WYSIWYG edtitor to be iOS scrollable
Fixes #1058
2019-06-16 11:55:01 +01:00
Dan Brown
fbb2b7ac6a Updated page nav header shift logic to be accurate
Added tests to cover.
Fixes #542
2019-06-16 11:32:38 +01:00
Dan Brown
56e31a5df7 Cleaned some page pointer layout/styles up 2019-06-16 11:17:15 +01:00
Kostas Dizas
86f56dd22b Added locale and text direction to html templates 2019-06-11 23:01:08 +01:00
Dan Brown
282c45f088 Updated roadmap & dev version, removed dupe locale mappings 2019-06-11 22:45:41 +01:00
Dan Brown
214c09c2b2 Changed translation key for last commit 2019-06-10 21:21:27 +01:00
Dan Brown
dda0200a94 Added note to custom HTML head input
To warn of being inactive while viewing the settings page.
Closes #1144
2019-06-10 19:54:22 +01:00
Dan Brown
53ba5b7e33 Removed jQuery and replaced axios with fetch 2019-06-08 00:02:51 +01:00
Dan Brown
b532ed0f86 Removed jquery usage from wysiwyg editor JS 2019-06-07 19:21:38 +01:00
Dan Brown
fdd34a74ed Removed jquery usage from page-display 2019-06-07 17:46:19 +01:00
Luca Guindani
239b3c8c2b Updated french translation files 2019-06-07 17:06:54 +02:00
Dan Brown
7634a84334 Converted existing custom slideup/down implementations 2019-06-07 16:00:34 +01:00
Dan Brown
2b929f5d95 Added custom slideUp/slideDown animations using plain JS 2019-06-07 15:51:01 +01:00
Dan Brown
ff841cff2e Removed "Toggle Header" option in page editor
Somewhat overlaps with the editor fullscreen button and is using jQuery
2019-06-06 14:14:32 +01:00
Dan Brown
9e397a57a9 Removed tiny color picker library 2019-06-06 14:05:06 +01:00
Dan Brown
d87eb277dd Replaced jquery sortable with sortablejs 2019-06-06 13:09:58 +01:00
Dan Brown
2eba8c611e Updated shelf-sort to use sortablejs 2019-06-06 11:49:51 +01:00
Dan Brown
f12a7540c9 Removed babel & auto-prefixer from build system
Closes #1468
2019-06-04 12:19:34 +01:00
Dan Brown
fe64248e86 Added keyboard navigation to breadcrumb dropdowns 2019-06-04 11:25:19 +01:00
Dan Brown
a9f983f156 Added focus and a11y attributes/functionality to custom checkboxes
Closes #1476
2019-06-04 10:47:09 +01:00
Dan Brown
7502ba1bc8 Updated version and assets for release v0.26.2 2019-05-27 13:48:20 +01:00
Dan Brown
33a04697ef Merge branch 'master' into release 2019-05-27 13:47:47 +01:00
Dan Brown
a602cdf401 Fixed some body card horizontal scroll and column collapse issues
As mentoined in #1441
2019-05-27 13:10:48 +01:00
Dan Brown
5aa741cb60 Prevented tri-layout sidebars being faded on mobile
As mentoined in #1441
2019-05-27 12:56:31 +01:00
Dan Brown
3ad1b42a74 Updated page delete to handle inactive custom homepage correctly
Fixes #1447
2019-05-27 12:40:19 +01:00
Dan Brown
2b7362fa94 Added highlighting to current book-tree item
Related to #1435
2019-05-25 16:52:44 +01:00
Dan Brown
35f35bcba5 Updated custom home views to use tri-layout
Closes #1423
2019-05-25 16:35:27 +01:00
Dan Brown
13c0386e84 Updated string functions to use mulitbyte versions where needed
Fixes #816
2019-05-25 16:15:19 +01:00
Dan Brown
78f5f44460 Updated page navigation click to show content tab on mobile
Fixes #1454
2019-05-25 15:37:49 +01:00
Dan Brown
35e6635379 Fixed chapter description not showing in book exports
Closes #1465
2019-05-25 15:21:02 +01:00
Leonardo Martinez
e42d90a5b6 Updated 'Spanish Argentina' translation. 2019-05-24 15:07:24 -03:00
Adam Brown
47a107ac5b Update maintenance.php
* Added a link to the Github releases page when someone clicks the current release version (to look for changelog information, or to see if there are new updates)
* Removed unnecessary BR tag by fixing the CSS class for the version display so it is properly aligned with the rest of the menu
2019-05-23 10:45:15 -04:00
Dan Brown
8ae35f645a Fixed faulty baseUrl rewrites
Fixes #1452
May help #1377
2019-05-19 16:25:05 +01:00
Dan Brown
5470a9e035 Merge branch 'kostefun-patch-1' 2019-05-19 15:43:11 +01:00
Dan Brown
dbfe63ccf6 Fixed missing comma in RU translation array 2019-05-19 15:42:46 +01:00
Dan Brown
114f10d5ca Merge branch 'patch-1' of git://github.com/kostefun/BookStack into kostefun-patch-1 2019-05-19 15:41:48 +01:00
Dan Brown
5226ddd959 Merge pull request #1446 from kostefun/patch-5
Update common.php
2019-05-19 15:41:21 +01:00
Dan Brown
60013f776a Merge branch 'master' into patch-5 2019-05-19 15:40:54 +01:00
Dan Brown
ac0a070fc8 Merge pull request #1445 from kostefun/patch-4
Update entities.php
2019-05-19 15:40:15 +01:00
Dan Brown
b05659d7a3 Merge pull request #1443 from kostefun/patch-3
Update common.php
2019-05-19 15:39:48 +01:00
Dan Brown
3af4648dc3 Merge pull request #1437 from NootoNooto/patch-2
Added Dutch translations for some new texts
2019-05-19 15:36:05 +01:00
Dan Brown
e1e1ea6099 Amended page save button layout to fix z-index issues
- Added a new mobile save button instead of trying to reposition the
original.
- Also recuced the point where the editor top toolbar will collapse to
become x-scrollable.

Fixes #1424
2019-05-19 15:30:58 +01:00
Dan Brown
0c3dc50cd9 Added mobile search bar on search page
Since the header one hides on mobile devices.
Fixes #1450
2019-05-19 15:06:52 +01:00
Dan Brown
0a0ceb382e Doubled image upload display thumb size
Related to #1108
2019-05-19 14:52:17 +01:00
Dan Brown
896f88174a Updated page navigation logic to ignore empty headers
Fixes #1429
2019-05-15 21:02:11 +01:00
Dan Brown
0ee9e5c4db Updated both editors to ignore image paste if text data apparent
Designed to ignore image data when copying from a spreadsheet.
Fixes #987
2019-05-15 20:23:09 +01:00
kostefun
112f73c91c Update settings.php
Ru locale fix
2019-05-14 17:50:23 +07:00
kostefun
b47dc046e0 Update common.php 2019-05-13 17:40:09 +07:00
kostefun
215d84d705 Update entities.php 2019-05-13 17:31:19 +07:00
kostefun
c459d86b58 Update common.php 2019-05-13 17:21:05 +07:00
kostefun
adf0d5fce2 Update settings.php 2019-05-13 17:09:50 +07:00
Nooto
cb355c8aad Modified Bookshelf texts 2019-05-08 23:57:44 +02:00
Nooto
d0e351b942 Added translations for Bookshelves 2019-05-08 23:51:34 +02:00
Nooto
e3d570e928 Update activities.php 2019-05-08 23:25:13 +02:00
Nooto
e00c170d85 Update common.php 2019-05-08 23:24:22 +02:00
Nooto
e430dad38c Added translations for View All, Copy, Reply, etc 2019-05-08 23:05:30 +02:00
Dan Brown
b70a5c0cdb Updated version and assets for release v0.26.1 2019-05-07 23:05:47 +01:00
Dan Brown
9443ae9f40 Merge branch 'master' into release 2019-05-07 23:05:10 +01:00
Dan Brown
d62d2384cb Updated guest settings system to format value as per non-guest
Fixes #1431
2019-05-07 22:56:48 +01:00
Dan Brown
a981dc41cb Merge pull request #1433 from Hambern/master
Updated the Swedish language files
2019-05-07 22:45:42 +01:00
Dan Brown
97ffbaa740 Fixed issue where books titles could be leaked via shelf home view
- Also added test to cover
Fixes #1425
2019-05-07 22:42:48 +01:00
vagrant
8051558d3a Updated the Swedish language files 2019-05-07 21:29:30 +00:00
Dan Brown
7ef059e254 Fixed some editor image/drawing upload endpoints
Fixes #1428
2019-05-07 22:23:44 +01:00
Dan Brown
4329fee2c9 Fixed 404 card header fonts
Fixes #1427
2019-05-07 22:10:54 +01:00
Dan Brown
b1cf5ab309 Standardised login tab order and evened card padding
Closes #1418
2019-05-07 22:07:50 +01:00
Dan Brown
b67d9f4036 Updated verison for 0.26 dev path 2019-05-07 21:55:41 +01:00
Dan Brown
224d9e7a7d Merge pull request #1420 from moucho/master
Spanish translation
2019-05-07 21:52:02 +01:00
Dan Brown
31419c3913 Merge pull request #1419 from Mant1kor/master
Ukrainian translation update
2019-05-07 21:51:15 +01:00
moucho
ba36d36597 Spanish 2019-05-07 01:28:00 +02:00
Mantikor
ac7d6b8737 Update validation.php 2019-05-06 23:11:23 +03:00
Mantikor
e52dab825a Update settings.php 2019-05-06 23:05:48 +03:00
Mantikor
33f51d5b78 Update passwords.php 2019-05-06 22:27:17 +03:00
Mantikor
ae7529376b Update pagination.php 2019-05-06 22:26:43 +03:00
Mantikor
aefd4d423c Update errors.php 2019-05-06 22:25:40 +03:00
Mantikor
bad44391d4 Update auth.php 2019-05-06 22:21:29 +03:00
Mantikor
1880633be3 Update common.php 2019-05-06 22:21:15 +03:00
Mantikor
8273f8b16e Update entities.php 2019-05-06 22:20:31 +03:00
Mantikor
0600f752eb Update components.php 2019-05-06 21:57:22 +03:00
Mantikor
342dc8948c Update common.php 2019-05-06 21:56:10 +03:00
Mantikor
3e24e44106 Update auth.php 2019-05-06 21:45:16 +03:00
Mantikor
404d11d0eb Update activities.php 2019-05-06 21:40:04 +03:00
Mantikor
07b889547d Merge pull request #1 from BookStackApp/master
merge changes
2019-05-06 21:33:09 +03:00
Dan Brown
220c2a4102 Updated version and assets for release v0.26.0 2019-05-06 18:58:56 +01:00
Dan Brown
e9914eb301 Merge branch 'master' into release 2019-05-06 18:57:58 +01:00
Dan Brown
e47a7e0b97 Merge pull request #1417 from Hambern/master
Updated the swedish lang files
2019-05-06 18:44:51 +01:00
vagrant
989309fdff Updated the swedish lang files 2019-05-06 17:19:51 +00:00
Dan Brown
6797c91eeb Updated dropdowns to close all others before opening 2019-05-06 17:59:17 +01:00
Dan Brown
b9ad3f9f65 Fixed intersection observer check on iOS 2019-05-06 16:08:08 +01:00
Dan Brown
7a8678e5f7 Tweaked colors for accessibility, applied fixes found during testing
- Fixed overriding h3 content header style.
- Updated notification styling to be less overwhelming.
- Increased floated image margin.
- Adjusted callout icon placement.
- Fixed tinymce fullscreen zindex issue.
2019-05-06 00:15:03 +01:00
Dan Brown
ba09dad1fe Fixed shelf activity display & updated book sort operation 2019-05-05 15:54:22 +01:00
Dan Brown
5910e00fb8 Made app core timezone configurable via env
Related to #1407
2019-05-05 15:09:04 +01:00
Dan Brown
3f83c548f8 Ran phpcbf 2019-05-05 14:54:37 +01:00
Dan Brown
adc866cb3d Added ability for dropdown menu to be bottom of dom body
- Used when a dropdown is within a scrollable section such as editor
toolbar on mobile.
- Also made mobile page save button more obvious by increasing size and
inverting color.
2019-05-05 14:43:26 +01:00
Dan Brown
ad542f0407 Prevented potential inline JS event usage
- Removes 'on*' attributes from elements.
- Also updated script logic to remove scripts instead of escaping.
- All JS injection removal now uses DomDocument + xpath parsing.
2019-05-05 13:53:37 +01:00
Dan Brown
15786e2630 Merge pull request #1410 from BookStackApp/image_management_rewrite
Image management rewrite
2019-05-04 18:16:58 +01:00
Dan Brown
8c190324ac Updated existing image tests to reflect changes
- Also added some new tests
2019-05-04 18:11:19 +01:00
Dan Brown
79f6dc00a3 Change image-selector to not use manager
- Now changes the images directly for user, system & cover.
- Extra permission checks added to edit & delete actions.
2019-05-04 15:50:29 +01:00
Dan Brown
cb832a2c10 Started diversion to not using image manager for cover/system/user 2019-04-27 14:55:23 +01:00
Dan Brown
a87ae16010 Started extraction of image controller to separate controllers 2019-04-27 14:18:00 +01:00
Dan Brown
aeb1fc4d49 Started rewriting back-end image managment 2019-04-21 15:52:29 +01:00
Dan Brown
6428f32483 Prevented invalid form inputs having incorrect padding 2019-04-21 14:11:49 +01:00
Dan Brown
884e20cc5e Updated TinyMCE to version 4.9.4 2019-04-21 13:49:46 +01:00
Dan Brown
fc761784a1 Merge branch 'cw1998-fix/registraion-form-validation' 2019-04-21 12:52:11 +01:00
Dan Brown
e0c229114f Updated register link text/placement on login card
- Also extracted "Already have account?" text to translation files.
2019-04-21 12:45:09 +01:00
Dan Brown
4e49d06182 Merge branch 'fix/registraion-form-validation' of git://github.com/cw1998/BookStack into cw1998-fix/registraion-form-validation 2019-04-21 12:24:39 +01:00
Dan Brown
2bb06463d5 Added deeper content id de-duplication
Closes #1393
2019-04-21 12:22:41 +01:00
Dan Brown
4d6df37963 Merge branch 'XVilka-patch-1' 2019-04-20 13:32:03 +01:00
Dan Brown
553d3ce861 Merge branch 'patch-1' of git://github.com/XVilka/BookStack into XVilka-patch-1 2019-04-20 13:30:50 +01:00
Dan Brown
0bc5ccba32 Add revision restore confirm and changed http method
Closes #1321
2019-04-20 13:25:16 +01:00
Dan Brown
ed330f246c Updated md drawing mngr shortcut to work on mac cmd key
Closes #1228
2019-04-20 13:12:35 +01:00
Dan Brown
6c66a8935a Added test to check page HTML id de-duplication
Relates to #1393
2019-04-20 13:01:56 +01:00
Dan Brown
c653618eab Merge branch 'cw1998-fix/dot-in-role-names' 2019-04-20 11:26:48 +01:00
Dan Brown
efc034bd8d Merge branch 'fix/dot-in-role-names' of git://github.com/cw1998/BookStack into cw1998-fix/dot-in-role-names 2019-04-16 23:08:23 +01:00
Dan Brown
c24764018a Updated ldap server option parsing to work with protocol and port
- Aligns with PHP behaviour where ports is ignore for full LDAP URI.
- Added tests to check format being passed to LDAP is as expected.
- May be related to #1220
- Related to #1386 and #1278
2019-04-16 22:47:53 +01:00
Christopher Wilkinson
c8cf6731e2 Add min length validation on name on register form & add sign up link 2019-04-16 12:18:51 +01:00
Dan Brown
d0db0f8e26 Reduced markup for books icon 2019-04-15 21:21:54 +01:00
Dan Brown
c380c10d54 Prevented bad duplicate IDs causing major exception
Related to #1393
2019-04-15 21:20:32 +01:00
Dan Brown
95d4149d5e Merge branch 'cw1998-feature/create-book-button-on-shelves' 2019-04-15 20:45:14 +01:00
Dan Brown
7f3f6e65b9 Aligned item creation wording and updated shelf-book-add logic 2019-04-15 20:45:04 +01:00
Christopher Wilkinson
29f17fd154 Replace dots with something else on user create and edit screens 2019-04-15 15:42:18 +01:00
Christopher Wilkinson
84419005e7 Update create new book button on shelves to 2019 design 2019-04-15 10:56:21 +01:00
Christopher Wilkinson
d3cd369247 Fix phpcs issues 2019-04-15 09:27:17 +01:00
Christopher Wilkinson
50a9c71de0 Add tests for creating a book and adding directly to a shelf 2019-04-15 09:27:17 +01:00
Christopher Wilkinson
faa3a8b842 Add button to add a book directly from a shelf view 2019-04-15 09:27:17 +01:00
Dan Brown
c836862d89 Tweaked header font size to fit redesign better 2019-04-14 14:20:53 +01:00
Dan Brown
03073dd9e4 Merge pull request #1153 from BookStackApp/2019-design
WIP: 2019 design
2019-04-14 13:54:36 +01:00
Dan Brown
ee58bea8b7 Updated user references to be app-default-supporting functions 2019-04-14 13:19:33 +01:00
Dan Brown
9406b4d4c9 Updated view toggle to store date
Also added test for user list order preferences
2019-04-14 13:01:51 +01:00
Dan Brown
01be72d5e2 Updated markdown editor for mobile
Also tweaked padding and responsivness on many common elements
2019-04-14 12:04:20 +01:00
Dan Brown
21e1123d12 Updated editor usability on mobile 2019-04-13 18:30:11 +01:00
Dan Brown
8d358e4894 Updated tri-layout on mobile to be tab based 2019-04-13 17:36:27 +01:00
Dan Brown
f797d2da20 Added column select-all to role permission table 2019-04-13 13:16:18 +01:00
Dan Brown
d3cc261320 Fixed "Add comment" layout when no comments exist 2019-04-13 12:58:57 +01:00
Dan Brown
cc24d478aa Organised entity action buttons a little more 2019-04-13 12:46:15 +01:00
Dan Brown
07adfb2ff1 Added select-all helpers to permission tables 2019-04-13 12:07:27 +01:00
Dan Brown
36481bb73f Updated guest page-create intermediate page 2019-04-13 11:30:19 +01:00
Dan Brown
4d5e47a2d2 Updated empty container item states 2019-04-13 11:24:41 +01:00
Dan Brown
d66fab8bee Updated and aligned entity dashboard elements 2019-04-13 11:09:17 +01:00
Dan Brown
2694bb8fab Updated the design of the comments section 2019-04-13 10:50:24 +01:00
Dan Brown
b12ae6d11b Added bookshelves to breadcrumbs
- Updated breadcrumb dropdown switchers and back-end sibling code to handle new breadcrumbs.
- Added breadcrumb view composer and EntityContext system to mangage
tracking if in the context of a bookshelf.
2019-04-07 18:28:11 +01:00
Dan Brown
221a483b40 Standardised view referencing to dot-notation 2019-04-07 12:00:09 +01:00
Dan Brown
4a127c29a5 Removed important color overrides to a tags 2019-04-07 11:36:50 +01:00
Dan Brown
8c21b5345d Cleaned up usage of some core scss files 2019-04-07 11:34:40 +01:00
Dan Brown
0a06e2bce3 Actioned some todo items, Cleaned old grid css 2019-04-07 09:57:48 +01:00
Dan Brown
d9cde4123d Fixed entity excerpt function signature misalignment 2019-04-06 18:47:27 +01:00
Dan Brown
7cda9b026e Updated tests to suit layout changes, Updated 404 page
- Also replaced 'or' usage in templates with null coalescing operator
2019-04-06 18:36:17 +01:00
Dan Brown
67ed4710b6 Cleaned up old toolbar usage 2019-04-06 17:43:44 +01:00
Dan Brown
745a0bb98d Updated custom homepage views 2019-04-06 17:31:59 +01:00
Dan Brown
aedff7dc6d Added book selector to books sort
Now more efficient rather than listing all books in the system.
2019-04-06 16:59:04 +01:00
Dan Brown
17969c0bbf Added shelves and search shortcuts to profile page 2019-04-06 16:21:20 +01:00
Dan Brown
666ced9c3b Fixed tri-layout overflow in some scenarios 2019-03-30 17:07:01 +00:00
Dan Brown
37bf7f11e4 Implemented new design in entity selector
- Also showed entity path in search.
- Cleaned popular entity fetch logic.
- Cleaned entity selector JS code a little
2019-03-30 16:54:15 +00:00
Dan Brown
bda8aa414b Fixed up edit views to use new layout
- Also updated chapter pages in books view to show detail
2019-03-30 15:49:14 +00:00
Dan Brown
60d175a9b9 Cleaned up sidebar book tree and moved details
- Also made top-spacing more consistent
2019-03-30 15:15:01 +00:00
Dan Brown
42e908c7f0 Cleaned up some existing tri-column views 2019-03-30 14:27:00 +00:00
Dan Brown
53a26a365c Merge branch 'master' into 2019-design 2019-03-30 13:17:29 +00:00
Dan Brown
4ee0fde0ac Updated readme with security info 2019-03-24 20:42:52 +00:00
Dan Brown
934512d09c Updated version and assets for release v0.25.5 2019-03-24 19:45:17 +00:00
Dan Brown
9102c90986 Merge branch 'master' into release 2019-03-24 19:45:00 +00:00
Dan Brown
9879a0d12c Added helper text for no_double_extension validation 2019-03-24 19:40:45 +00:00
Dan Brown
5d2e80bff3 Merge pull request #1348 from agvol/master
Russian translation
2019-03-24 19:14:53 +00:00
Dan Brown
83818234c8 Merge pull request #1347 from cima/czech-translation
Czech translation
2019-03-24 19:13:48 +00:00
Dan Brown
5c00187138 Merge pull request #1327 from leomartinez/master
Updated 'Spanish Argentina' translation.
2019-03-24 19:11:01 +00:00
Dan Brown
193e2ffebe Prevent dbl exts. on img upload, Randomized attachment upload names 2019-03-24 19:08:21 +00:00
agvol
b2a5d07787 Update russian lang 2019-03-23 10:22:54 +03:00
agvol
15cf9f8b32 Update russian lang 2019-03-23 10:18:44 +03:00
agvol
f9adfee47a Update russian lang 2019-03-23 10:04:50 +03:00
Martin Šimek
2e988277f0 Czech translation
+ Unit test repair
2019-03-23 00:35:42 +01:00
Martin Šimek
4e8e28c35f Czech translation
+ Typos
+ errors.php
2019-03-22 22:52:26 +01:00
Anton Kochkov
4cbfb2bf13 Enable syntax highlight for OCaml, Haskell, Rust 2019-03-22 16:42:02 +08:00
Dan Brown
c3e74219c4 Updated version and assets for release v0.25.4 2019-03-21 19:46:19 +00:00
Dan Brown
13c9d7bc2d Merge branch 'master' into release 2019-03-21 19:43:48 +00:00
Dan Brown
f5fe524e6c Added extension whitelist for image uploads
- A continuation of the security issues addressed in v0.25.3
2019-03-21 19:43:15 +00:00
Dan Brown
119b539586 Updated version and assets for release v0.25.3 2019-03-21 00:03:26 +00:00
Dan Brown
29a5c180f0 Merge branch 'master' into release 2019-03-21 00:02:33 +00:00
Dan Brown
37b91b6b0e Hardened image file validation by removing custom validation
- Added test to check PHP files cannot be uploaded as an image.
2019-03-20 23:59:55 +00:00
Martin Šimek
b3a4d8af2a Czech translation
+ Czech language (cs)
+ settings.php in english populated by new option
Note: validation php taken from  lavarel official translation (https://github.com/caouecs/Laravel-lang/blob/master/src/cs/validation.php)
2019-03-19 22:45:14 +01:00
Dan Brown
8b7bee7c67 Updated standard entity lists 2019-03-17 15:07:03 +00:00
Dan Brown
837d2bc582 Improved login, Fixed breadcrumbs & improved grid thumbnails 2019-03-16 16:00:41 +00:00
Leonardo Martinez
64cd499542 Updated 'Spanish Argentina' translation. 2019-03-12 11:46:02 -03:00
Dan Brown
5f2d226f09 Merge branch 'master' into 2019-design 2019-03-10 21:40:02 +00:00
Dan Brown
7906602291 Updated version and assets for release v0.25.2 2019-03-10 13:45:21 +00:00
Dan Brown
6dafe773ff Merge branch 'master' into release 2019-03-10 13:44:29 +00:00
Dan Brown
00703fa817 Merge branch 'dfanara-feature-ldap-attributes' 2019-03-10 10:55:36 +00:00
Dan Brown
44c537de1a Performed some LDAP service/test cleanup 2019-03-10 10:54:19 +00:00
Dan Brown
6bccf0e64a Merge branch 'feature-ldap-attributes' of git://github.com/dfanara/BookStack into dfanara-feature-ldap-attributes 2019-03-10 10:31:09 +00:00
Dan Brown
042a6f9760 Updated shelf menu item to show on custom permission
- Extended new 'userCanOnAny' helper to take a entity class for
filtering.

Closes #1201
2019-03-09 21:15:45 +00:00
Dan Brown
04287745e4 Merge branch 'mark-james-Copy-For-View-Only' 2019-03-09 16:52:47 +00:00
Dan Brown
5c9b528517 Abstracted userCanCreatePage helper to work for any permisison
- Added test to cover scenario where someone with create-own permission
would want to copy a viewable item into a container entity that they
own.
2019-03-09 16:50:22 +00:00
Dan Brown
6be2d3f28c Merge branch 'Copy-For-View-Only' of git://github.com/mark-james/BookStack into mark-james-Copy-For-View-Only 2019-03-09 16:12:12 +00:00
Daniel Fanara
6d20bdc1fb Preserve original display_name_attribute configuration values. 2019-03-09 01:13:30 -05:00
Daniel Fanara
502ea608bf Issue #1306 - Unit Tests for LdapService Changes 2019-03-09 01:08:49 -05:00
Daniel Fanara
55b07c7076 Issue #1306 - Specify display name attribute from LDAP 2019-03-08 23:55:11 -05:00
Dan Brown
33e999909f Merge branch 'fix-1186' 2019-03-08 22:57:57 +00:00
Dan Brown
6be95cd2ac Re-centered dropzone error arrow 2019-03-08 22:57:24 +00:00
Dan Brown
f467185e86 Merge branch 'master' into fix-1186 2019-03-08 22:47:31 +00:00
Dan Brown
646fd822c5 Updated redis config logic, Now takes a password
- Previous config did not use multiple servers in any way.
- Cluster will now be created automatically if multiple servers given.
- Removed REDIS_CLUSTER option.

Closes #1283
2019-03-08 22:42:48 +00:00
Dan Brown
d96baf2d4a Set 'uploaded_to' parameters for editor-pasted/dragged images
Allows image-listing permission system to work as intended.
Fixes #1287
2019-03-08 21:32:31 +00:00
Dan Brown
1c312906bc Added a configurable upload size limit
Closes #1293
2019-03-08 21:06:37 +00:00
Dan Brown
9126c87f2b Merge pull request #1272 from Xiphoseer/patch-1
Add german translations for shelves
2019-03-07 22:14:35 +00:00
Dan Brown
579d98a908 Merge pull request #1314 from maantje/patch-2
Update Dutch password_hint translation to correspond with validation
2019-03-07 21:53:08 +00:00
Dan Brown
98a4359198 Updated user language select to use correct default
- Updated localisation system to take note of system defaul locale
before replacing the current locale
Fixes #1316
2019-03-07 21:09:23 +00:00
Jamie Schouten
6f710225b5 Update Dutch password_hint translation to correspond with validation rule
At the moment the translation says ```Minimaal 5 tekens``` which means your password should be at least 5 characters long. But a 5 character long password is not allowed by the validator. 

I think this was a translation error from the English one where it says ```Must be over 5 characters```. To make the Dutch translation correct the Dutch translation should be changed to ```Minimaal 6 tekens```.

```
    /**
     * 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',
            'password' => 'required|min:6',
        ]);
    }
```
2019-03-06 17:10:15 +01:00
Dan Brown
b273b9d6d0 Improved alignment classes used by WYSIWYG editor
- Fixed table cells being floated, Fixes #1284.
- Made it possible to easily center linked images.
2019-03-02 09:08:01 +00:00
Dan Brown
e471d0c52a Added lua to code languages
Closes #1223
2019-03-02 08:52:14 +00:00
Dan Brown
0a431e3223 Merge pull request #1263 from christophert/add-powershellmarkup
Add Powershell Code Markup
2019-03-02 08:45:08 +00:00
Dan Brown
035a0d8efb Added experimental breadcrumb traversal 2019-02-24 15:57:35 +00:00
Dan Brown
e70423c73f Standardized breadcrumbs a little further with icons 2019-02-17 17:52:42 +00:00
Dan Brown
8445304fe9 Added book sort helper buttons 2019-02-17 11:44:02 +00:00
Dan Brown
f1e571a57c Made shelf listing more unique & efficient
- Now includes listing of all books within.
2019-02-16 17:13:01 +00:00
Dan Brown
e9be2b7174 Standardized setting casing 2019-02-16 15:39:23 +00:00
Dan Brown
352cbbd074 Updated design of page navigation box 2019-02-16 15:39:11 +00:00
Dan Brown
b00b319e83 Re-arranged some list items to flexbox instead of grid
Since flexbox is better supported on a wider range of elements
2019-02-16 15:05:18 +00:00
Dan Brown
a112c11df8 Re-ordered and updated main settings page 2019-02-16 14:17:35 +00:00
Xiphoseer
058cc2cbd6 Update entities.php 2019-02-12 12:30:43 +01:00
Xiphoseer
edd98c00e5 Update entities.php 2019-02-12 12:30:12 +01:00
Xiphoseer
19bb11a1c9 Update entities.php
Add informal german shelve localisations
2019-02-12 12:28:48 +01:00
Xiphoseer
9511d10ec8 Update entities.php
Add german shelve localizations
2019-02-12 12:24:01 +01:00
Dan Brown
3286f29a61 Merge branch 'master' into 2019-design 2019-02-09 14:58:38 +00:00
Christopher Tran
77d3bd31a6 add powershell code block link 2019-02-06 01:06:59 -05:00
Dan Brown
56004abdf4 Added high-level release and roadmap info to readme
Closes #1259
2019-02-06 00:09:39 +00:00
Dan Brown
df6f6e2d77 Merge branch 'master' of github.com:BookStackApp/BookStack 2019-02-04 19:57:43 +00:00
Dan Brown
ba1b3fc181 Made some readme tweaks 2019-02-04 19:57:21 +00:00
Dan Brown
67d4fa0c65 Updated npm packages and migrated webpack css plugin 2019-02-03 18:21:21 +00:00
Dan Brown
49deab3a02 Fixed page edit to have white background 2019-02-03 17:53:54 +00:00
Dan Brown
5325870271 Updated auth pages to new design, Removed public layout 2019-02-03 17:34:15 +00:00
Dan Brown
138f5d5c4f Updated user and shelf views to new design 2019-02-03 13:45:45 +00:00
Dan Brown
880d4f35da Started the migration of the setting views 2019-02-02 15:49:57 +00:00
Dan Brown
20988962fe Migrated a whole load more page/chapter/shelf views 2019-02-02 11:41:41 +00:00
Dan Brown
32603362a6 Updated a bunch of book views 2019-01-31 20:37:12 +00:00
abijeet
9dba9ca178 Fixes tooltip on the image manager.
Fixes #1186
2019-01-27 19:43:31 +05:30
Abijeet Patro
8a4a81629f Merge pull request #1237 from BookStackApp/phpcs-fixes
PHPCS related fixes.
2019-01-27 16:04:30 +05:30
abijeet
5ef0992d5b PHPCS related fixes. 2019-01-27 15:59:23 +05:30
Dan Brown
25bc28a1be Updated version and assets for release v0.25.1 2019-01-20 15:42:32 +00:00
Dan Brown
4c561c7fa0 Merge branch 'master' into release 2019-01-20 15:41:24 +00:00
Dan Brown
12be7d0086 Added extra s3 config parameters for use s3-like service compatibility
For #1192 and #1195
2019-01-20 15:23:49 +00:00
Dan Brown
ba0af9214e Updated socialite to work around google+ API shutdown
Fixes #1190
Will require docs update
2019-01-20 14:58:06 +00:00
Dan Brown
36424a24b5 Added ability for date format strings to be localized by back-end
Requires the locale to be installed on the system-side.
Closes #1214
2019-01-19 12:11:18 +00:00
Dan Brown
a70ee9664a Fixed firefox page print view and removed comments from prints
Closes #1211
2019-01-19 11:33:27 +00:00
Dan Brown
156c0a88e9 Updated sidebar to prevent rubber-banding with comments disabled
Fixes #1218
2019-01-19 11:10:46 +00:00
Dan Brown
0efed43389 Converted more views to new layout and made breadcrumbs more flexible 2019-01-13 15:54:55 +00:00
Dan Brown
163a57cf70 Merge branch 'master' into 2019-design 2019-01-13 14:10:27 +00:00
Dan Brown
95b3e78573 Updated version and assets for release v0.25.0 2019-01-12 22:48:53 +00:00
Dan Brown
63a345bc93 Merge branch 'master' into release 2019-01-12 22:47:07 +00:00
Dan Brown
a3ccde8698 Updated TinyMCE and fixed TinyMCE/Codemirror cursor jumping
For #1162
2019-01-12 19:23:18 +00:00
Dan Brown
9700b7ccea Merge pull request #1205 from BookStackApp/env-cleanup
Simplified example env and created full example copy
2019-01-06 16:05:51 +00:00
Dan Brown
54c428c375 Commented APP_URL by default to prevent upgrade path issues 2019-01-06 16:01:24 +00:00
Dan Brown
ebe5d643f3 Simplified example env and created full example copy 2019-01-06 15:46:16 +00:00
Dan Brown
e66ddbc17b Merge pull request #1197 from moucho/master
Spanish update
2019-01-06 14:37:11 +00:00
Dan Brown
0e0a17cc30 Prevented page text content includes
Avoids possible permission issues where included content shown in search or preview
where the user would not normally have permission to view the included content.

Closes #1178
2019-01-05 17:18:40 +00:00
Dan Brown
ffceb4092e Merge branch 'cw1998-fix/#1110' 2019-01-05 15:22:59 +00:00
Dan Brown
50e5527483 Added test to cover "users" header link in correct permission conditions 2019-01-05 15:22:47 +00:00
Dan Brown
f63fd4beca Merge branch 'fix/#1110' of git://github.com/cw1998/BookStack into cw1998-fix/#1110 2019-01-05 15:12:33 +00:00
Dan Brown
d682a0157f Merge branch 'qianmengnet-master' 2019-01-05 15:01:38 +00:00
Dan Brown
70ad707c3c Tweaked profile page anchor links and swapped register/login links
Also added test for login/register links on non-auth app view
Relates to #1146
2019-01-05 15:01:16 +00:00
Dan Brown
3062bf1876 Merge branch 'master' of git://github.com/qianmengnet/BookStack into qianmengnet-master 2019-01-05 14:47:47 +00:00
Dan Brown
a2087fe3ff Made delete permissions a requirement for move operations
Closes #1200
2019-01-05 14:39:40 +00:00
Mark James
19770d2792 Use joint_permissions to determine is a user has an available page or chapter to copy. 2019-01-02 16:55:28 +11:00
Mark James
99c6d70c51 Initial updates to allow for page copy when the user can read the page but can't update it. 2018-12-31 17:01:49 +11:00
mark-james
0830521e60 Merge pull request #1 from BookStackApp/master
Update From Bookstack
2018-12-31 09:00:19 +11:00
Dan Brown
2317bf2350 Added check for last admin on role change
Will show error message if last admin and admin role is removed.
Closes #1124
Also cleaned up user controller a little.
2018-12-30 16:11:58 +00:00
moucho
2c48f4f7e8 Format update 2018-12-24 12:41:42 +01:00
Dan Brown
456afdcd4c Updated configuration files
Added a notice to the top of each to explain they should not be normally modified.
Standardised comment format used for each item.
Better aligned some files with laravel 5.5 options.
2018-12-23 16:26:39 +00:00
Dan Brown
68017e2553 Added testing for avatar fetching systems & config
Abstracts imageservice http interaction.
Closes #1193
2018-12-23 15:34:38 +00:00
Dan Brown
866187830a Merge branch 'Vinrobot-custom-avatar-provider' 2018-12-22 19:29:35 +00:00
Dan Brown
b56fc21aaf Abstracted user avatar fetching away from gravatar
Still uses gravatar as a default.
Updated URL placeholders to follow LDAP format.
Potential breaking config change: `GRAVATAR=false` replaced by `AVATAR_URL=false`
Builds upon #1111
2018-12-22 19:29:19 +00:00
Dan Brown
d673bf61c2 Merge branch 'custom-avatar-provider' of git://github.com/Vinrobot/BookStack into Vinrobot-custom-avatar-provider 2018-12-22 18:18:14 +00:00
Dan Brown
18b10153e5 Updated composer with bumped php version and extra extensions 2018-12-22 16:49:09 +00:00
Dan Brown
7c8edf5673 Merge pull request #1096 from christophert/add-ldaptlsinsecure
Add option to disable LDAPS Certificate Validation
2018-12-22 16:38:50 +00:00
Dan Brown
f4ea5f1f55 Updated page exports to use absolute time format
For #1065
2018-12-22 16:35:04 +00:00
Dan Brown
f62843c861 Updated DZ upload timeout var name and error handling
For #1133 & #876
Concerns BookStackApp/website#31
2018-12-22 15:45:13 +00:00
Dan Brown
5fe630b8d2 Merge branch 'master' into dropzone-timeout 2018-12-22 15:08:54 +00:00
Dan Brown
d910defbfd Merge pull request #1183 from Mant1kor/master
Add Ukrainian translate
2018-12-22 15:00:56 +00:00
Dan Brown
153adb055c Merge pull request #1180 from vasiliev123/update-pl-language
Updates for PL language
2018-12-22 14:58:30 +00:00
Dan Brown
26ec1cc3dc Added proper escaping to LDAP filter operations
To cover #1163
2018-12-20 20:04:09 +00:00
Mantikor
d476e30df0 Added 'uk' locale 2018-12-18 10:03:10 +02:00
Mantikor
37ab97af8c Add files via upload 2018-12-18 10:01:50 +02:00
Mantikor
7fcd7a5d91 Rename resources/lang/activities.php to resources/lang/uk/activities.php 2018-12-18 10:01:18 +02:00
Mantikor
c67f76f776 Add files via upload 2018-12-18 10:00:45 +02:00
Mantikor
9a444b4a04 Update settings.php
added 'uk' language
2018-12-17 18:16:43 +02:00
Mantikor
106f32591d Update app.php
added 'uk' locale
2018-12-17 14:10:54 +02:00
Dan Brown
7f6929d716 Re-enabled plaintext view for email notifications
Updated mail notifications to set the HTML and plaintext views since before
no plaintext version was being created.

Closes #1182
2018-12-16 20:44:57 +00:00
Dan Brown
651ae2f3be Fixed failing language test after addition of formatter 2018-12-16 15:46:02 +00:00
Dan Brown
101a7b40b9 Updated codemirror SQL mode name
Now will highlight a lot more SQL syntax.
Closes #1181.
2018-12-16 15:38:49 +00:00
Dan Brown
1930ed4d6a Made some further fixes to the formatting script
Takes into account single and double quotes.
Ignores //! comments and the 'language_select' array.

Language files may need some cleaning up and may encounter some other bugs when running.
2018-12-16 14:04:04 +00:00
Dan Brown
2753629dbe Cleaned up script and formatted remaining EN files 2018-12-16 13:12:13 +00:00
Dan Brown
86a00a59d4 Created sketchy translation formatter script
Compares a translation file to a EN version to
place translations on matching line numbers and matches
up comments.
2018-12-14 21:23:05 +00:00
Jurij Vasiliev
d6dd96e7fc 1. Fixed translation for Copy and Reply 2018-12-13 13:58:08 +01:00
Jurij Vasiliev
1b1ddb6794 Major updates on polish language
1. Changed Book translation from księga => podręcznik (księga is very old word, and thus not fit to the app. Podręcznik is word for book used in school and fits much more to the documentation site)
2. Changed Entity transaltion from encja => obiekt (encja is word used in IT world, common people doesn't know what it is. Obiekt (object) fits better for no IT geeks and explains them more than word encja)
3. Added Shelf/Bookshelf transaltion. Now they are named Półka/Półki
4. Changed Draft translation from szkic => wersja robocza (in every system like wordpress/wiki etc. the word for draft is wersja robocza. Szkic is word for draft of an image)
5. Fixed typos
6. Fixed unfit plural words when they were not needed
2018-12-13 13:39:26 +01:00
Dan Brown
f65ff3a9a8 Merge branch 'ezzra-german_informal' 2018-12-12 20:47:03 +00:00
Dan Brown
323bff7d6d Extended translations system for arrays & extension
Extended the base Laravel translation system to
allow a locale to be based upon another.

Also adds functionality to take base & fallback locales into account when fetching
an array of translations.

Related to work done in #1159
2018-12-12 20:46:27 +00:00
Dan Brown
0e3d507ec2 Merge branch 'german_informal' of git://github.com/ezzra/BookStack into ezzra-german_informal 2018-12-12 19:02:16 +00:00
ezzra
f943f0d401 de_informal - remove comments from unused lines 2018-12-12 10:37:24 +01:00
Dan Brown
1b01d65965 Updated readme with phpunit version, removed old translations line 2018-12-11 23:26:43 +00:00
ezzra
a2acd063f3 add german informal language 2018-12-11 19:39:16 +01:00
Dan Brown
e9e3e8b6b1 Added npm install details
Closes #1174
2018-12-11 14:54:34 +00:00
Dan Brown
7f95b51b00 Rolled tri-layout to page edit and book-create 2018-12-09 16:51:31 +00:00
Dan Brown
ff0b9004bc Cleaned existing grid view up a little 2018-12-09 14:04:28 +00:00
Dan Brown
8fd8652bbf Added tri-layout desktop sticky-scroll 2018-12-09 13:42:35 +00:00
Dan Brown
e1474194db Added responsive functionality to tri-layout view. 2018-12-08 23:34:06 +00:00
Dan Brown
4c574c22a8 Implemented functionality to make books sort function
Also changed public user settings to be stored in session rather than DB.
Cleaned existing list view type logic.
2018-12-07 18:33:53 +00:00
Dan Brown
0b976d9f91 Added list sorting styles, Yet to add functionality 2018-12-01 21:28:21 +00:00
Dan Brown
2a882a43ff Updated books listing to three column layout design 2018-12-01 20:28:17 +00:00
Dan Brown
aabd4c0412 Started looking at the books listing design 2018-12-01 16:29:57 +00:00
Dan Brown
d39fc84301 Merge branch 'master' into 2019-design 2018-12-01 13:57:23 +00:00
Dan Brown
e093a172cb Updated assets and version for release v0.24.3 2018-11-27 21:52:20 +00:00
Dan Brown
4b01f8934b Merge branch 'master' into release 2018-11-27 21:51:32 +00:00
Dan Brown
75ca430fd4 Updated NPM dependancies 2018-11-27 21:50:29 +00:00
Dan Brown
4cf43f67d6 Merge pull request #1072 from CliffyPrime/german_update
Update german translation
2018-11-27 21:26:18 +00:00
Dan Brown
86899864dd Merge pull request #1117 from leomartinez/master
Updated 'Spanish Argentina' translation.
2018-11-27 21:24:01 +00:00
qianmengnet
3c796b1ae7 Add "register" to nav.
Add "register" to nav.You need to click "login" to find register, which is not convenient for people who are not familiar with the app.
2018-11-26 09:05:38 +08:00
qianmengnet
b7915cc7b0 Add anchor link to "Created Content" on the "View Profile"
Add 3 anchor link to "Created Content" on the "View Profile" page and click to jump to the page section
2018-11-26 08:47:49 +08:00
Abijeet
eac82c47a5 Fixes image deletion failing in subdirectory.
Fixes #1092

Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-11-25 00:46:04 +05:30
Abijeet
d3d3e2ad3e Added a default timeout of 60 seconds to dropzone.
Fixes #876
2018-11-18 00:22:08 +05:30
Christopher Wilkinson
54b36cd305 Show users link in top nav if user is signed in and only manages users 2018-11-13 13:43:20 +00:00
Leonardo Martinez
f8396d3632 Updated 'Spanish Argentina' translation. 2018-11-12 10:15:06 -03:00
Dan Brown
4df11701e7 Updated book-tree design and abstracted breadcrumb template 2018-11-11 13:11:36 +00:00
Dan Brown
4a872012c5 Merge branch 'master' into 2019-design 2018-11-11 11:44:35 +00:00
Dan Brown
bc116b45b5 Re-updated assets for release v0.24.2 2018-11-10 16:10:22 +00:00
Dan Brown
a059960b9e Merge branch 'master' into release 2018-11-10 16:09:14 +00:00
Dan Brown
302b53562d Fixed clipboard imports 2018-11-10 16:08:33 +00:00
Dan Brown
7770966fed Updated assets for release v0.24.2 2018-11-10 16:01:55 +00:00
Dan Brown
d7adcf6c69 Merge branch 'master' into release 2018-11-10 16:01:01 +00:00
Dan Brown
ebd4c3327d Merge branch 'thomasjsn-master' 2018-11-10 15:35:35 +00:00
Dan Brown
d0c166c207 Added linked images to markdown paste insert 2018-11-10 15:35:13 +00:00
Dan Brown
321b53c827 Merge branch 'master' of git://github.com/thomasjsn/BookStack into thomasjsn-master 2018-11-10 15:23:29 +00:00
Vinrobot
5e6c039b08 Added config to change Gravatar URL 2018-11-10 16:11:11 +01:00
Dan Brown
178b5af83a Added google select_account test
Also cleaned the function naming a little to be more descriptive of the
work they do.
2018-11-10 14:52:43 +00:00
Dan Brown
4be0c567cc Merge pull request #1063 from justein230/master
Add select account parameter for google authorization
2018-11-10 14:32:28 +00:00
Dan Brown
0724ae3640 Merge pull request #1098 from TheLastOperator/master
Update french translations
2018-11-10 13:58:56 +00:00
Dan Brown
62a475e464 Merge branch 'qianmengnet-master' 2018-11-10 13:55:48 +00:00
Dan Brown
cfc1a2f045 Removed settings that had been copied from en 2018-11-10 13:55:13 +00:00
Dan Brown
b012f27ae3 Merge branch 'master' of git://github.com/qianmengnet/BookStack into qianmengnet-master 2018-11-10 13:54:35 +00:00
Dan Brown
58bec7287f Merge pull request #1088 from kejjang/zh_TW_update
Update zh_TW translation
2018-11-10 13:52:23 +00:00
Dan Brown
9341ae4910 Merge pull request #1066 from limkukhyun/codev-kuk-master
completed the Korean translation. Please accept the pull request
2018-11-10 13:50:39 +00:00
Dan Brown
1328755f95 Merge pull request #1034 from DeehSlash/pt_br_translations
Adds missing pt_BR translation
2018-11-10 13:47:38 +00:00
Dan Brown
038b2418f7 Fixed baseURL helper when no app url is set
Also cleaned variable naming to be more obvious
2018-11-09 21:29:30 +00:00
Dan Brown
e3230f8f21 Standardised module loading system & fixed build system
Fixed broken build system in broken webpack version.
Also updates module system to standardise on ES6 import/exports,
Especially since babel has changed it's 'default' logic for the old
module system.
2018-11-09 21:17:35 +00:00
qianmengnet
fd37d95ffc Chinese translation update for v0.24.1
0.24.1
2018-11-08 08:10:04 +08:00
Justin Stein
3abde8bfe2 Added config entry for select account 2018-11-04 11:15:58 -08:00
Justin Stein
2ca8038df2 Removed return from documentation for function redirectToSocialProvider 2018-11-04 11:07:04 -08:00
Justin Stein
89de328439 Merge branch 'master' of https://github.com/BookStackApp/BookStack 2018-11-04 11:04:30 -08:00
Justin Stein
c37e73b626 Moved redirect functionality back to start register and log in functions 2018-11-04 10:48:55 -08:00
Justin Stein
0283ab11b5 Added function for redirect with parameters for Socialite 2018-11-04 10:40:06 -08:00
Dan Brown
5b36ddb12f Updated npm dependancies 2018-11-04 15:40:10 +00:00
Dan Brown
ffc1aa873e Merge branch 'v0.24-dev' 2018-11-04 15:36:40 +00:00
Dan Brown
19b7093438 Fixed redirect issue when custom app url in use
Fixes #956 & #1048
Also added tests to cover this url logic.
Also removed debugbar during tests to maybe improve test speed.
2018-11-04 15:18:27 +00:00
Dan Brown
7799ba5c79 Updated composer dependancies including laravel minor version
Updated larvel 5.5 to latest version to bring in latest fixes.
Fixes #1095
2018-11-04 14:53:13 +00:00
Dan Brown
c356612612 Added sidebar layout size tweaks 2018-11-04 14:41:52 +00:00
Florian PREVOST
1c89fcd20a Update french translasion 2018-10-30 15:13:17 +01:00
Christopher Tran
730cb78b45 switch spaces to tabs 2018-10-27 17:05:46 -04:00
Christopher Tran
8e7f703af7 fix how the option is set, change handle to NULL 2018-10-27 16:58:10 -04:00
Christopher Tran
6c14c09880 Add ability to disable LDAP certificate validation 2018-10-27 16:14:19 -04:00
Kej
773ab9d7ff Update zh_TW translation 2018-10-25 11:38:49 +08:00
Dan Brown
0e395b1e21 Started reworking of page-show design
- Updated core toolbar & breadcrumb design
2018-10-21 20:05:11 +01:00
CliffyPrime
6c7d87c836 Update german translation
Started working on updating / completing the german translation files, as some recent changes (shelves etc.) are not yet translated. Also changed some wordings to better fit into the flow and word order of the german language. Started with two small files, will do more soon...
2018-10-17 23:44:13 +02:00
Dan Brown
89be30ff0e Started on a design update
- Added base of new grid system.
- Added new margin/padding/visiblity helpers.
- Made header collapse to overflow menu on mobile.
2018-10-16 18:49:56 +01:00
codev-kuk-mac
e8ab4fd91f change config/app.php to add korean 2018-10-15 16:39:16 +09:00
codev-kuk-mac
5d1162fb64 translate complate 2018-10-15 16:36:11 +09:00
Justin Stein
216358c6e4 Added Google select account functionality to login 2018-10-13 15:14:06 -07:00
Justin Stein
57d99130ee Added environment variable for google select account option. 2018-10-13 14:50:58 -07:00
Justin Stein
79afec9737 Revert "Added else clause"
This reverts commit 77d7f764f1.
2018-10-13 14:31:29 -07:00
Thomas Jensen
90929baa52 Wrap images inserted with markdown editor with anchor tag to original file ref #1062 2018-10-13 21:43:35 +02:00
Dan Brown
85f330c79a Extracted many page-specific repo methods into page-specific repo 2018-10-13 11:27:55 +01:00
justein230
77d7f764f1 Added else clause 2018-10-12 22:50:02 -07:00
Justin Stein
a76599bd2a Add select account parameter for google authorization
Useful for choosing an account if a default account is outside the scope of a G Suite organization.
2018-10-12 11:52:13 -07:00
codev-kuk-mac
4afc67a962 settings - completed
entities - Some edits done
2018-10-12 17:22:29 +09:00
codev-kuk-mac
042d8b3274 translate entities, setting 2018-10-12 16:06:08 +09:00
codev-kuk-mac
1c0a196b9d Include Korean in settings 2018-10-12 16:05:28 +09:00
codev-kuk-mac
a1fda37896 done validation 2018-10-12 16:04:57 +09:00
codev-kuk-mac
43758a7d60 3개 번역 2018-10-12 15:13:27 +09:00
codev-kuk-mac
c7d3db9751 translate kr 50/100 2018-10-05 11:24:24 +09:00
codev-kuk-mac
fca7689e1a translate to kr 20/100 2018-10-05 10:24:37 +09:00
André Luiz da Silva
18bac4e673 Fixes "bookshelf" pt_BR translation in "activities" 2018-09-28 16:27:26 -03:00
André Luiz da Silva
ca2a9fbf1c Adds missing "settings" pt_BR translations 2018-09-28 16:26:01 -03:00
André Luiz da Silva
cbebe7c8de Adds missing "errors" pt_BR translations 2018-09-28 16:19:06 -03:00
André Luiz da Silva
0943221902 Adds missing "entities" pt_BR translations 2018-09-28 16:18:14 -03:00
André Luiz da Silva
17ed1b7faf Adds missing "common" pt_BR translations 2018-09-26 21:54:16 -03:00
André Luiz da Silva
36d18f28ee Adds missing "activities" pt_BR translations 2018-09-26 21:51:34 -03:00
Dan Brown
495d18814a Updated various classes to take EntityProvider instead of separate entities 2018-09-25 18:00:40 +01:00
Dan Brown
257a5a23ec Fleshed out entity provided and optimized imports 2018-09-25 16:58:03 +01:00
Dan Brown
919660678b Re-structured the app code to be feature based rather than code type based 2018-09-25 12:30:50 +01:00
Dan Brown
19751ed1cb Incremented dev version 2018-09-25 10:00:09 +01:00
Dan Brown
04a364dcc3 Incremented version for v0.24.1 2018-09-24 16:34:16 +01:00
Dan Brown
db83ac7eaa Merge branch 'master' into release 2018-09-24 16:32:30 +01:00
Dan Brown
818c02ed44 Added null role check to migrate path
Also added check for existing bookshelf role_permissions
in the event the user got that for.
Also related to #1027
2018-09-24 16:30:08 +01:00
Dan Brown
3ca9dddf61 Merge branch 'master' into release 2018-09-24 15:59:39 +01:00
Dan Brown
9abdab3991 Updated migration to convert MyISAM tables to InnoDB
New bookshelves_books tables requires foreign constraints which error on MyISAM.
For #1027
2018-09-24 15:58:40 +01:00
Dan Brown
bf74f53ca7 Updated assets for release and incremented version 2018-09-24 12:18:27 +01:00
Dan Brown
9d67efb4a4 Merge branch 'master' into release 2018-09-24 12:08:21 +01:00
Dan Brown
43122f86e8 Merge pull request #1026 from vriic/master
Update german translation
2018-09-24 10:35:22 +01:00
Nikolai Nikolajevic
935337862c Update german translation 2018-09-24 09:55:37 +02:00
Dan Brown
dd671524f8 Merge pull request #1025 from moucho/master
Spanish translation
2018-09-23 17:18:41 +01:00
Marcos
1cac3d43d3 Spanish translation 2018-09-23 17:31:41 +02:00
Dan Brown
9243c635f2 Made search test a little more consistent 2018-09-23 15:15:44 +01:00
Dan Brown
93f820d9da Updated TinyMCE config to end containers on empty blocks
Makes it easier to escape out of blockquote sections.
Fixes #961
2018-09-23 14:07:50 +01:00
Dan Brown
b62afcad1f Removed search indexing from migration path to prevent Bookshelf issue 2018-09-23 13:25:12 +01:00
Dan Brown
7b32aa163f Added Bookshelves to search system.
Also cleaned up and made search indexing system a little more efficient.
Closes #1023
2018-09-23 12:34:30 +01:00
Dan Brown
eebfd8904e Removed old fulltext indexes from migrations
Prevents forcing of MyISAM for some databases
Removed old code to add indexes and added checks for existing indexes before removal.
Should still allow upgrades, rollbacks to old bookstack versions may be funky but
should not be high use-case.
2018-09-23 00:30:48 +01:00
Dan Brown
0d84a0b976 Extracted search sidebar text to translation file
Closes #864
2018-09-22 23:40:04 +01:00
Dan Brown
88ac636c00 Made it possible to scroll past the end of the markdown editor
Closes #1020
2018-09-22 23:24:51 +01:00
Dan Brown
9dc26a8c52 Prevented TinyMCE clear-color option having scrollbar
Was covering the option if showing and prevented the option being pressed.
Fixes #999
2018-09-22 23:12:39 +01:00
Dan Brown
6543020fd2 Updated tinymce to v4.8.3 2018-09-22 22:58:06 +01:00
Dan Brown
be4f3d62cd Merge branch 'fix/ru-locale' of git://github.com/mullinsmikey/BookStack into mullinsmikey-fix/ru-locale 2018-09-22 22:29:03 +01:00
Dan Brown
da58c41ab6 Prevented attachDefaultRole from trying to re-attach if already existing
Fixes #1003
Added test to cover
2018-09-22 22:09:34 +01:00
Dan Brown
10d08e641c Merge pull request #1021 from moucho/master
Spanish translation
2018-09-22 21:43:19 +01:00
Dan Brown
3a39b9f440 Merge pull request #1022 from BookStackApp/revert-983-master
Revert "Update german translation"
2018-09-22 18:33:29 +01:00
Dan Brown
27f7aab375 Revert "Update german translation" 2018-09-22 18:33:15 +01:00
Dan Brown
337da0c467 Merge pull request #983 from vriic/master
Update german translation
2018-09-22 18:27:04 +01:00
Marcos
c4e4cebf14 Spanish translation 2018-09-22 18:36:52 +02:00
Dan Brown
3f58800ed1 Added ability to configure revision limit 2018-09-22 17:30:42 +01:00
Dan Brown
0931ff38e9 Tried to make chapter toggles a little smoother in FF 2018-09-22 16:36:35 +01:00
Dan Brown
07bc0612c0 Merge branch 'master' into fix/#960 2018-09-22 15:57:53 +01:00
Dan Brown
6dec485b45 Fixed sidebar rubber-banding when content is expanded
Hopefully for #905
Ideally layout needs to be re-thought
2018-09-22 15:52:09 +01:00
Dan Brown
a441faf65c Only show codeblock copy icon on hover
Fixes #980
2018-09-22 14:55:33 +01:00
Dan Brown
50ee1462ad Merge pull request #986 from DeehSlash/fix/pt_br_locale
Adds and fixes pt_BR strings
2018-09-22 14:40:23 +01:00
Dan Brown
1cb6ae39c8 Added base RTL support
For #939

- Adds way to check if current language is RTL via config system.
- Made TinyMCE default direction be based on current language text
direction.
- Fixed bullet points to be RTL compatible.
- Set page content body to have direction based on content.
2018-09-22 13:18:26 +01:00
Dan Brown
c667c6e235 Merge branch 'master' of git://github.com/kmoj86/BookStack into kmoj86-master 2018-09-22 12:23:17 +01:00
Dan Brown
e3e484e561 Added custom head content to exports
Closes #981

Also fixed incorrect download tests.
2018-09-22 11:53:40 +01:00
Dan Brown
73fa18a128 Made bookstack cookie name configurable
Closes #1018
2018-09-22 11:42:02 +01:00
Dan Brown
5c2e3f4e56 Extracted download response logic into controller method
Fixes incorrect 'Content-Disposition' header value.
Fixes #581
2018-09-22 11:34:09 +01:00
Dan Brown
c47b578599 Fixed formatting via phpcbf 2018-09-21 18:48:47 +01:00
Dan Brown
e60d11ee04 Altered social auto-reg to be configurable per service
- Added {$service}_AUTO_REGISTER and {$service}_AUTO_CONFIRM_EMAIL env
options for each social auth system.
- Auto-register will allow registration from login, even if registration
is disabled.
- Auto-confirm-email indicates trust and will mark new registrants as
'email_confirmed' and skip 'confirmation email' flow.
- Also added covering tests.
2018-09-21 18:05:06 +01:00
Dan Brown
7ad8314bd7 Merge branch 'feature/autoregistration_social_login' of git://github.com/ibrahimennafaa/BookStack into ibrahimennafaa-feature/autoregistration_social_login 2018-09-21 16:14:52 +01:00
Dan Brown
131fcae4c7 Merge pull request #947 from BookStackApp/bookshelves
Bookshelves
2018-09-21 15:29:52 +01:00
Dan Brown
c8d893fac7 Updated 404 test to not fail based on random long name 2018-09-21 15:24:29 +01:00
Dan Brown
b59e5942c8 Added testing coverage for Bookshelves
Created modified TestResponse so we can use DOM operations in new
Testcases as we move away from the BrowserKit tests.
2018-09-21 15:15:16 +01:00
Dan Brown
8ff969dd17 Updated so permission effect admins more
Asset permissions can now be configured for admins.
joint_permissions will now effect admins more often.
Made so shelves header link will hide if you have no bookshelves view
permission.
2018-09-20 19:48:08 +01:00
Dan Brown
6eead437d8 Added bookshelf permission control UI and copy-down ability 2018-09-20 19:16:11 +01:00
Dan Brown
0b6f83837b Removed joint_permission generation in older migration 2018-09-20 16:03:01 +01:00
Dan Brown
81eb642f75 Added bookshelves homepage options
- Updated homepage selection UI to be more scalable
- Cleaned homepage selection logic in code
- Added seed test data for bookshelves
- Added bookshelves to permission system
2018-09-20 15:27:30 +01:00
Dan Brown
47b08888ba Added bookshelf view, update, delete
- Enabled proper ordering of Books in a shelf.
- Improved related item destroy for all entities.
2018-09-16 19:34:09 +01:00
Abijeet Patro
32e34f10ff Merge pull request #1008 from BookStackApp/revision-deletion
#784 - Adds ability to remove particular revision.
2018-09-16 21:30:04 +05:30
Dan Brown
f455b317ec Added ability to click books in shelf-sort 2018-09-16 16:59:01 +01:00
Abijeet
08b967607f Changes as per code review, and fixes failing test cases.
Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-09-16 20:44:09 +05:30
Abijeet
90883bb22b Fixes issue wth the dropdown list upon double clicking.
Closes #960

Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-09-16 13:09:21 +05:30
Abijeet
0c8b6b7324 Final tweaks after code review and fixing failing test cases. 2018-09-16 01:12:36 +05:30
Abijeet
81d3bdc168 Removes the BadRequestException class added earlier.
Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-09-15 21:08:00 +05:30
Abijeet
54ca4487fa Adds tests and few fixes.
Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-09-15 21:05:51 +05:30
Abijeet
25da4d9a8b Added a success message on deletion of revision.
Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-09-15 16:08:20 +05:30
Abijeet
f0add69b61 Adding languages for the revision deletion. 2018-09-15 15:16:04 +05:30
Abijeet
714c7bbd3a Adds code to delete the revision.
Signed-off-by: Abijeet <abijeetpatro@gmail.com>
2018-09-15 15:15:42 +05:30
Michael Mullins
e0b479efef UTF-8 slugs & UI fixes 2018-09-11 22:42:25 +04:00
Michael Mullins
9d09c4c7b0 Ru locale fix
Fixed russian translations, added several missing strings
2018-09-10 22:28:47 +04:00
Dan Brown
b89411c108 Copied book content, Added create routes
Added view control
Added pivot table for books relation
Added control to assign books
2018-08-27 14:18:09 +01:00
André Luiz da Silva
c472b82ee3 Adds and fixes pt_BR strings 2018-08-24 16:03:04 -03:00
Nikolai Nikolajevic
f56b3560c4 Update german translation 2018-08-23 16:17:46 +02:00
Ibrahim Ennafaa
d2f5313f92 add missing @param in method comment 2018-08-21 12:44:42 -04:00
Ibrahim Ennafaa
572e75b783 Update UserRepo.php 2018-08-20 21:19:25 -04:00
Dan Brown
02dfe11ce6 Increment version for release v0.23.2 2018-08-19 15:33:23 +01:00
Dan Brown
83d06beb70 Merge branch 'master' into release 2018-08-19 15:33:10 +01:00
Dan Brown
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
Ibrahim Ennafaa
b224a2c8a0 attempt to fix unit test error for admin creation 2018-08-16 21:52:16 +00:00
Dan Brown
fe6dfcedf9 implement social auto registration feature 2018-08-16 21:26:54 +00: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
Dan Brown
c3986cedfc Added shelve icon, improved migration, added role permission
Icon is placeholder for now
Migration will now copy permissions from Books to apply to shelves.
Role view updated with visibility on shelve permission
2018-08-04 12:45:45 +01:00
Dan Brown
b5a2d3c1c4 Merge remote-tracking branch 'origin' into bookshelves 2018-08-04 11:35:01 +01:00
Khalid
a6862362c1 added Arabic to locales 2018-08-03 19:00:51 +03:00
Khalid
7d4ec0d633 added Arabic language to drop-down list. 2018-08-03 18:58:27 +03:00
Khalid
85544c04a9 translated 2018-08-03 18:51:18 +03:00
Khalid
0a8ff6ffad translated 2018-08-03 18:34:39 +03:00
Khalid
04274078c4 translated 2018-08-02 06:38:33 +03:00
Khalid
aac9fbf236 translated 2018-08-01 18:25:44 +03:00
Khalid
08e290f3ea translated 2018-07-30 12:58:12 +03:00
Khalid
86602854ac translated 2018-07-30 12:54:32 +03: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
Khalid
6eb63a1e03 translated 2018-07-28 14:13:12 +03:00
Khalid
8774f1a320 translated 2018-07-28 13:02:19 +03:00
Khalid
6ca8ccd330 translated 2018-07-28 12:47:35 +03:00
Khalid
df88ffa159 translated 2018-07-28 00:13:33 +03:00
Khalid
dcbb8ad960 translated 2018-07-24 21:40:49 +03:00
Khalid
0f2ffa9545 translated to Arabic 2018-07-24 21:36:12 +03:00
Khalid
6bae16f7e9 fully translated "common" and partially translated "entities" 2018-07-24 18:11:55 +03:00
kmoj86
f5ca7ab1c8 Update entities.php 2018-07-23 18:19:17 +03:00
Khalid
7dd11decb8 partial file translation 2018-07-23 13:08:07 +03: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
Dan Brown
4948b443b6 Started work on bookshelves 2018-06-24 13:38:19 +01: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
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
1050 changed files with 64240 additions and 35224 deletions

View File

@@ -1,11 +1,14 @@
# Environment
APP_ENV=production
APP_DEBUG=false
# Application key
# Used for encryption where needed.
# Run `php artisan key:generate` to generate a valid key.
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
# Application URL
# Remove the hash below and set a URL if using BookStack behind
# a proxy, if using a third-party authentication option.
# This must be the root URL that you want to host BookStack on.
# All URL's in BookStack will be generated using this value.
#APP_URL=https://example.com
# Database details
DB_HOST=localhost
@@ -13,67 +16,20 @@ DB_DATABASE=database_database
DB_USERNAME=database_username
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
# Amazon S3 Config
STORAGE_S3_KEY=false
STORAGE_S3_SECRET=false
STORAGE_S3_REGION=false
STORAGE_S3_BUCKET=false
# Storage URL
# Used to prefix image urls for when using custom domains/cdns
STORAGE_URL=false
# General auth
AUTH_METHOD=standard
# Social Authentication information. Defaults as off.
GITHUB_APP_ID=false
GITHUB_APP_SECRET=false
GOOGLE_APP_ID=false
GOOGLE_APP_SECRET=false
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
# External services such as Gravatar and Draw.IO
DISABLE_EXTERNAL_SERVICES=false
# LDAP Settings
LDAP_SERVER=false
LDAP_BASE_DN=false
LDAP_DN=false
LDAP_PASS=false
LDAP_USER_FILTER=false
LDAP_VERSION=false
# Mail settings
# Mail system to use
# Can be 'smtp' or 'sendmail'
MAIL_DRIVER=smtp
# Mail sender options
MAIL_FROM_NAME=BookStack
MAIL_FROM=bookstack@example.com
# SMTP mail options
MAIL_HOST=localhost
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM=null
MAIL_FROM_NAME=null
# A full list of options can be found in the '.env.example.complete' file.

273
.env.example.complete Normal file
View File

@@ -0,0 +1,273 @@
# Full list of environment variables that can be used with BookStack.
# Selectively copy these to your '.env' file as required.
# Each option is shown with it's default value.
# Do not copy this whole file to use as your '.env' file.
# Application environment
# Can be 'production', 'development', 'testing' or 'demo'
APP_ENV=production
# Enable debug mode
# Shows advanced debug information and errors.
# CAN EXPOSE OTHER VARIABLES, LEAVE DISABLED
APP_DEBUG=false
# Application key
# Used for encryption where needed.
# Run `php artisan key:generate` to generate a valid key.
APP_KEY=SomeRandomString
# Application URL
# This must be the root URL that you want to host BookStack on.
# All URL's in BookStack will be generated using this value.
APP_URL=https://example.com
# Application default language
# The default language choice to show.
# May be overridden by user-preference or visitor browser settings.
APP_LANG=en
# Auto-detect language for public visitors.
# Uses browser-sent headers to infer a language.
# APP_LANG will be used if such a header is not provided.
APP_AUTO_LANG_PUBLIC=true
# Application timezone
# Used where dates are displayed such as on exported content.
# Valid timezone values can be found here: https://www.php.net/manual/en/timezones.php
APP_TIMEZONE=UTC
# Application theme
# Used to specific a themes/<APP_THEME> folder where BookStack UI
# overrides can be made. Defaults to disabled.
APP_THEME=false
# Database details
# Host can contain a port (localhost:3306) or a separate DB_PORT option can be used.
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=database_database
DB_USERNAME=database_username
DB_PASSWORD=database_user_password
# Mail system to use
# Can be 'smtp', 'mail' or 'sendmail'
MAIL_DRIVER=smtp
# Mail sending options
MAIL_FROM=mail@bookstackapp.com
MAIL_FROM_NAME=BookStack
# SMTP mail options
MAIL_HOST=localhost
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
# Cache & Session driver to use
# Can be 'file', 'database', 'memcached' or 'redis'
CACHE_DRIVER=file
SESSION_DRIVER=file
# Session configuration
SESSION_LIFETIME=120
SESSION_COOKIE_NAME=bookstack_session
SESSION_SECURE_COOKIE=false
# Cache key prefix
# Can be used to prevent conflicts multiple BookStack instances use the same store.
CACHE_PREFIX=bookstack
# Memcached server configuration
# 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
# Redis server configuration
# This follows the following format: HOST:PORT:DATABASE
# or, if using a password: HOST:PORT:DATABASE:PASSWORD
# For multiple servers separate with a comma. These will be clustered.
REDIS_SERVERS=127.0.0.1:6379:0
# Queue driver to use
# Queue not really currently used but may be configurable in the future.
# Would advise not to change this for now.
QUEUE_CONNECTION=sync
# Storage system to use
# Can be 'local', 'local_secure' or 's3'
STORAGE_TYPE=local
# Image storage system to use
# Defaults to the value of STORAGE_TYPE if unset.
# Accepts the same values as STORAGE_TYPE.
STORAGE_IMAGE_TYPE=local
# Attachment storage system to use
# Defaults to the value of STORAGE_TYPE if unset.
# Accepts the same values as STORAGE_TYPE although 'local' will be forced to 'local_secure'.
STORAGE_ATTACHMENT_TYPE=local_secure
# Amazon S3 storage configuration
STORAGE_S3_KEY=your-s3-key
STORAGE_S3_SECRET=your-s3-secret
STORAGE_S3_BUCKET=s3-bucket-name
STORAGE_S3_REGION=s3-bucket-region
# S3 endpoint to use for storage calls
# Only set this if using a non-Amazon s3-compatible service such as Minio
STORAGE_S3_ENDPOINT=https://my-custom-s3-compatible.service.com:8001
# Storage URL prefix
# Used as a base for any generated image urls.
# An s3-format URL will be generated if not set.
STORAGE_URL=false
# Authentication method to use
# Can be 'standard', 'ldap' or 'saml2'
AUTH_METHOD=standard
# Social authentication configuration
# All disabled by default.
# Refer to https://www.bookstackapp.com/docs/admin/third-party-auth/
AZURE_APP_ID=false
AZURE_APP_SECRET=false
AZURE_TENANT=false
AZURE_AUTO_REGISTER=false
AZURE_AUTO_CONFIRM_EMAIL=false
DISCORD_APP_ID=false
DISCORD_APP_SECRET=false
DISCORD_AUTO_REGISTER=false
DISCORD_AUTO_CONFIRM_EMAIL=false
FACEBOOK_APP_ID=false
FACEBOOK_APP_SECRET=false
FACEBOOK_AUTO_REGISTER=false
FACEBOOK_AUTO_CONFIRM_EMAIL=false
GITHUB_APP_ID=false
GITHUB_APP_SECRET=false
GITHUB_AUTO_REGISTER=false
GITHUB_AUTO_CONFIRM_EMAIL=false
GITLAB_APP_ID=false
GITLAB_APP_SECRET=false
GITLAB_BASE_URI=false
GITLAB_AUTO_REGISTER=false
GITLAB_AUTO_CONFIRM_EMAIL=false
GOOGLE_APP_ID=false
GOOGLE_APP_SECRET=false
GOOGLE_SELECT_ACCOUNT=false
GOOGLE_AUTO_REGISTER=false
GOOGLE_AUTO_CONFIRM_EMAIL=false
OKTA_BASE_URL=false
OKTA_APP_ID=false
OKTA_APP_SECRET=false
OKTA_AUTO_REGISTER=false
OKTA_AUTO_CONFIRM_EMAIL=false
SLACK_APP_ID=false
SLACK_APP_SECRET=false
SLACK_AUTO_REGISTER=false
SLACK_AUTO_CONFIRM_EMAIL=false
TWITCH_APP_ID=false
TWITCH_APP_SECRET=false
TWITCH_AUTO_REGISTER=false
TWITCH_AUTO_CONFIRM_EMAIL=false
TWITTER_APP_ID=false
TWITTER_APP_SECRET=false
TWITTER_AUTO_REGISTER=false
TWITTER_AUTO_CONFIRM_EMAIL=false
# LDAP authentication configuration
# Refer to https://www.bookstackapp.com/docs/admin/ldap-auth/
LDAP_SERVER=false
LDAP_BASE_DN=false
LDAP_DN=false
LDAP_PASS=false
LDAP_USER_FILTER=false
LDAP_VERSION=false
LDAP_TLS_INSECURE=false
LDAP_ID_ATTRIBUTE=uid
LDAP_EMAIL_ATTRIBUTE=mail
LDAP_DISPLAY_NAME_ATTRIBUTE=cn
LDAP_FOLLOW_REFERRALS=true
LDAP_DUMP_USER_DETAILS=false
# LDAP group sync configuration
# Refer to https://www.bookstackapp.com/docs/admin/ldap-auth/
LDAP_USER_TO_GROUPS=false
LDAP_GROUP_ATTRIBUTE="memberOf"
LDAP_REMOVE_FROM_GROUPS=false
# SAML authentication configuration
# Refer to https://www.bookstackapp.com/docs/admin/saml2-auth/
SAML2_NAME=SSO
SAML2_EMAIL_ATTRIBUTE=email
SAML2_DISPLAY_NAME_ATTRIBUTES=username
SAML2_EXTERNAL_ID_ATTRIBUTE=null
SAML2_IDP_ENTITYID=null
SAML2_IDP_SSO=null
SAML2_IDP_SLO=null
SAML2_IDP_x509=null
SAML2_ONELOGIN_OVERRIDES=null
SAML2_DUMP_USER_DETAILS=false
SAML2_AUTOLOAD_METADATA=false
# SAML group sync configuration
# Refer to https://www.bookstackapp.com/docs/admin/saml2-auth/
SAML2_USER_TO_GROUPS=false
SAML2_GROUP_ATTRIBUTE=group
SAML2_REMOVE_FROM_GROUPS=false
# Disable default third-party services such as Gravatar and Draw.IO
# Service-specific options will override this option
DISABLE_EXTERNAL_SERVICES=false
# Use custom avatar service, Sets fetch URL
# Possible placeholders: ${hash} ${size} ${email}
# If set, Avatars will be fetched regardless of DISABLE_EXTERNAL_SERVICES option.
# Example: AVATAR_URL=https://seccdn.libravatar.org/avatar/${hash}?s=${size}&d=identicon
AVATAR_URL=
# Enable draw.io integration
# Can simply be true/false to enable/disable the integration.
# Alternatively, It can be URL to the draw.io instance you want to use.
# For URLs, The following URL parameters should be included: embed=1&proto=json&spin=1
DRAWIO=true
# Default item listing view
# Used for public visitors and user's without a preference
# Can be 'list' or 'grid'
APP_VIEWS_BOOKS=list
APP_VIEWS_BOOKSHELVES=grid
# Page revision limit
# Number of page revisions to keep in the system before deleting old revisions.
# If set to 'false' a limit will not be enforced.
REVISION_LIMIT=50
# Allow <script> tags in page content
# Note, if set to 'true' the page editor may still escape scripts.
ALLOW_CONTENT_SCRIPTS=false
# Indicate if robots/crawlers should crawl your instance.
# Can be 'true', 'false' or 'null'.
# The behaviour of the default 'null' option will depend on the 'app-public' admin setting.
# Contents of the robots.txt file can be overridden, making this option obsolete.
ALLOW_ROBOTS=null
# The default and maximum item-counts for listing API requests.
API_DEFAULT_ITEM_COUNT=100
API_MAX_ITEM_COUNT=500
# The number of API requests that can be made per minute by a single user.
API_REQUESTS_PER_MIN=180

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

View File

@@ -1,21 +0,0 @@
### For Feature Requests
Desired Feature:
### For Bug Reports
* BookStack Version *(Found in settings, Please don't put 'latest')*:
* PHP Version:
* MySQL Version:
##### Expected Behavior
##### Current Behavior
##### Steps to Reproduce

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.

View File

@@ -0,0 +1,13 @@
---
name: Language Request
about: Request a new language to be added to Crowdin for you to translate
---
### Language To Add
_Specify here the language you want to add._
----
_This issue template is to request a new language be added to our [Crowdin translation management project](https://crowdin.com/project/bookstack). Please don't use this template to request a new language that you are not prepared to provide translations for._

100
.github/translators.txt vendored Normal file
View File

@@ -0,0 +1,100 @@
Name :: Languages
@robertlandes :: German
@SergioMendolia :: French
@NakaharaL :: Portuguese, Brazilian
@ReeseSebastian :: German
@arietimmerman :: Dutch
@diegoseso :: Spanish
@S64 :: Japanese
@JachuPL :: Polish
@Joorem :: French
@timoschwarzer :: German
@sanderdw :: Dutch
@lbguilherme :: Portuguese, Brazilian
@marcusforsberg :: Swedish
@artur-trzesiok :: Polish
@Alwaysin :: French
@msaus :: Japanese
@moucho :: Spanish
@vriic :: German
@DeehSlash :: Portuguese, Brazilian
@alex2702 :: German
@nicobubulle :: French
@kmoj86 :: Arabic
@houbaron :: Chinese Traditional; Chinese Simplified
@mullinsmikey :: Russian
@limkukhyun :: Korean
@CliffyPrime :: German
@kejjang :: Chinese Traditional
@TheLastOperator :: French
@qianmengnet :: Simplified Chinese
@ezzra :: German; German Informal
@vasiliev123 :: Polish
@Mant1kor :: Ukrainian
@Xiphoseer :: German; German Informal
@maantje :: Dutch
@cima :: Czech
@agvol :: Russian
@Hambern :: Swedish
@NootoNooto :: Dutch
@kostefun :: Russian
@lucaguindani :: French
@miles75 :: Hungarian
@danielroehrig-mm :: German
@oykenfurkan :: Turkish
@qligier :: French
@johnroyer :: Traditional Chinese
@artskoczylas :: Polish
@dellamina :: Italian
@jzoy :: Simplified Chinese
@ististudio :: Korean
@leomartinez :: Spanish Argentina
cipi1965 :: Italian
Mykola Ronik (Mantikor) :: Ukrainian
furkanoyk :: Turkish
m0uch0 :: Spanish
Maxim Zalata (zlatin) :: Russian; Ukrainian
nutsflag :: French
Leonardo Mario Martinez (leonardo.m.martinez) :: Spanish, Argentina
Rodrigo Saczuk Niz (rodrigoniz) :: Portuguese, Brazilian
叫钦叔就好 (254351722) :: Chinese Traditional; Chinese Simplified
aekramer :: Dutch
JachuPL :: Polish
milesteg :: Hungarian
Beenbag :: German
Lett3rs :: Danish
Julian (julian.henneberg) :: German; German Informal
3GNWn :: Danish
dbguichu :: Chinese Simplified
Randy Kim (hyunjun) :: Korean
Francesco M. Taurino (ftaurino) :: Italian
DanielFrederiksen :: Danish
Finn Wessel (19finnwessel6) :: German Informal; German
Gustav Kånåhols (Kurbitz) :: Swedish
Vuong Trung Hieu (fpooon) :: Vietnamese
Emil Petersen (emoyly) :: Danish
mrjaboozy :: Slovenian
Statium :: Russian
Mikkel Struntze (MStruntze) :: Danish
kostefun :: Russian
Tuyen.NG (tuyendev) :: Vietnamese
Ghost_chu (dbguichu) :: Chinese Simplified
Ziipen :: Danish
Samuel Schwarz (Guiph7quan) :: Czech
Aleph (toishoki) :: Turkish
Julio Alberto García (Yllelder) :: Spanish
Rafael (raribeir) :: Portuguese, Brazilian
Hiroyuki Odake (dakesan) :: Japanese
Alex Lee (qianmengnet) :: Chinese Simplified
swinn37 :: French
Hasan Özbey (the-turk) :: Turkish
rcy :: Swedish
Ali Yasir Yılmaz (ayyilmaz) :: Turkish
scureza :: Italian
Biepa :: German Informal; German
syecu :: Chinese Simplified
Lap1t0r :: French
Thinkverse (thinkverse) :: Swedish
alef (toishoki) :: Turkish
Robbert Feunekes (Muukuro) :: Dutch
seohyeon.joo :: Korean

54
.github/workflows/phpunit.yml vendored Normal file
View File

@@ -0,0 +1,54 @@
name: phpunit
on:
push:
branches:
- master
- release
pull_request:
branches:
- '*'
- '*/*'
- '!l10n_master'
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
php: [7.2, 7.4]
steps:
- uses: actions/checkout@v1
- name: Get Composer Cache Directory
id: composer-cache
run: |
echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache composer packages
uses: actions/cache@v1
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ matrix.php }}
- name: Start Database
run: |
sudo /etc/init.d/mysql start
- name: Setup Database
run: |
mysql -uroot -proot -e 'CREATE DATABASE IF NOT EXISTS `bookstack-test`;'
mysql -uroot -proot -e "CREATE USER 'bookstack-test'@'localhost' IDENTIFIED BY 'bookstack-test';"
mysql -uroot -proot -e "GRANT ALL ON \`bookstack-test\`.* TO 'bookstack-test'@'localhost';"
mysql -uroot -proot -e 'FLUSH PRIVILEGES;'
- name: Install composer dependencies & Test
run: composer install --prefer-dist --no-interaction --ansi
- name: Migrate and seed the database
run: |
php${{ matrix.php }} artisan migrate --force -n --database=mysql_testing
php${{ matrix.php }} artisan db:seed --force -n --class=DummyContentSeeder --database=mysql_testing
- name: phpunit
run: php${{ matrix.php }} ./vendor/bin/phpunit

3
.gitignore vendored
View File

@@ -21,3 +21,6 @@ nbproject
.buildpath
.project
.settings/
webpack-stats.json
.phpunit.result.cache
.DS_Store

View File

@@ -1,28 +0,0 @@
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

View File

@@ -1,6 +1,7 @@
The MIT License (MIT)
Copyright (c) 2016 Dan Brown
Copyright (c) 2020 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

View File

@@ -1,12 +1,20 @@
<?php
namespace BookStack;
namespace BookStack\Actions;
use BookStack\Auth\User;
use BookStack\Entities\Entity;
use BookStack\Model;
/**
* @property string key
* @property \User user
* @property \Entity entity
* @property string extra
* @property string $key
* @property User $user
* @property Entity $entity
* @property string $extra
* @property string $entity_type
* @property int $entity_id
* @property int $user_id
* @property int $book_id
*/
class Activity extends Model
{
@@ -42,10 +50,8 @@ class Activity extends Model
/**
* Checks if another Activity matches the general information of another.
* @param $activityB
* @return bool
*/
public function isSimilarTo($activityB)
public function isSimilarTo(Activity $activityB): bool
{
return [$this->key, $this->entity_type, $this->entity_id] === [$activityB->key, $activityB->entity_type, $activityB->entity_id];
}

View File

@@ -0,0 +1,162 @@
<?php namespace BookStack\Actions;
use BookStack\Auth\Permissions\PermissionService;
use BookStack\Auth\User;
use BookStack\Entities\Entity;
use Illuminate\Support\Collection;
class ActivityService
{
protected $activity;
protected $user;
protected $permissionService;
/**
* ActivityService constructor.
*/
public function __construct(Activity $activity, PermissionService $permissionService)
{
$this->activity = $activity;
$this->permissionService = $permissionService;
$this->user = user();
}
/**
* Add activity data to database.
*/
public function add(Entity $entity, string $activityKey, ?int $bookId = null)
{
$activity = $this->newActivityForUser($activityKey, $bookId);
$entity->activity()->save($activity);
$this->setNotification($activityKey);
}
/**
* Adds a activity history with a message, without binding to a entity.
*/
public function addMessage(string $activityKey, string $message, ?int $bookId = null)
{
$this->newActivityForUser($activityKey, $bookId)->forceFill([
'extra' => $message
])->save();
$this->setNotification($activityKey);
}
/**
* Get a new activity instance for the current user.
*/
protected function newActivityForUser(string $key, ?int $bookId = null): Activity
{
return $this->activity->newInstance()->forceFill([
'key' => strtolower($key),
'user_id' => $this->user->id,
'book_id' => $bookId ?? 0,
]);
}
/**
* Removes the entity attachment from each of its activities
* and instead uses the 'extra' field with the entities name.
* Used when an entity is deleted.
*/
public function removeEntity(Entity $entity): Collection
{
$activities = $entity->activity()->get();
$entity->activity()->update([
'extra' => $entity->name,
'entity_id' => 0,
'entity_type' => '',
]);
return $activities;
}
/**
* Gets the latest activity.
*/
public function latest(int $count = 20, int $page = 0): array
{
$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 entity, Filtering out similar
* items to prevent a message activity list.
*/
public function entityActivity(Entity $entity, int $count = 20, int $page = 1): array
{
if ($entity->isA('book')) {
$query = $this->activity->newQuery()->where('book_id', '=', $entity->id);
} else {
$query = $this->activity->newQuery()->where('entity_type', '=', $entity->getMorphClass())
->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 - 1))
->take($count)
->get();
return $this->filterSimilar($activity);
}
/**
* Get latest activity for a user, Filtering out similar items.
*/
public function userActivity(User $user, int $count = 20, int $page = 0): array
{
$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(iterable $activities): array
{
$newActivity = [];
$previousItem = null;
foreach ($activities as $activityItem) {
if (!$previousItem || !$activityItem->isSimilarTo($previousItem)) {
$newActivity[] = $activityItem;
}
$previousItem = $activityItem;
}
return $newActivity;
}
/**
* Flashes a notification message to the session if an appropriate message is available.
*/
protected function setNotification(string $activityKey)
{
$notificationTextKey = 'activities.' . $activityKey . '_notification';
if (trans()->has($notificationTextKey)) {
$message = trans($notificationTextKey);
session()->flash('success', $message);
}
}
}

View File

@@ -1,8 +1,16 @@
<?php namespace BookStack;
<?php namespace BookStack\Actions;
use BookStack\Ownable;
/**
* @property string text
* @property string html
* @property int|null parent_id
* @property int local_id
*/
class Comment extends Ownable
{
protected $fillable = ['text', 'html', 'parent_id'];
protected $fillable = ['text', 'parent_id'];
protected $appends = ['created', 'updated'];
/**

View File

@@ -0,0 +1,92 @@
<?php namespace BookStack\Actions;
use BookStack\Entities\Entity;
use League\CommonMark\CommonMarkConverter;
/**
* Class CommentRepo
*/
class CommentRepo
{
/**
* @var Comment $comment
*/
protected $comment;
public function __construct(Comment $comment)
{
$this->comment = $comment;
}
/**
* Get a comment by ID.
*/
public function getById(int $id): Comment
{
return $this->comment->newQuery()->findOrFail($id);
}
/**
* Create a new comment on an entity.
*/
public function create(Entity $entity, string $text, ?int $parent_id): Comment
{
$userId = user()->id;
$comment = $this->comment->newInstance();
$comment->text = $text;
$comment->html = $this->commentToHtml($text);
$comment->created_by = $userId;
$comment->updated_by = $userId;
$comment->local_id = $this->getNextLocalId($entity);
$comment->parent_id = $parent_id;
$entity->comments()->save($comment);
return $comment;
}
/**
* Update an existing comment.
*/
public function update(Comment $comment, string $text): Comment
{
$comment->updated_by = user()->id;
$comment->text = $text;
$comment->html = $this->commentToHtml($text);
$comment->save();
return $comment;
}
/**
* Delete a comment from the system.
*/
public function delete(Comment $comment)
{
$comment->delete();
}
/**
* Convert the given comment markdown text to HTML.
*/
public function commentToHtml(string $commentText): string
{
$converter = new CommonMarkConverter([
'html_input' => 'strip',
'max_nesting_level' => 10,
'allow_unsafe_links' => false,
]);
return $converter->convertToHtml($commentText);
}
/**
* Get the next local ID relative to the linked entity.
*/
protected function getNextLocalId(Entity $entity): int
{
$comments = $entity->comments(false)->orderBy('local_id', 'desc')->first();
return ($comments->local_id ?? 0) + 1;
}
}

View File

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

View File

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

View File

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

View File

@@ -1,27 +1,34 @@
<?php namespace BookStack\Services;
<?php namespace BookStack\Actions;
use BookStack\Entity;
use BookStack\View;
use BookStack\Auth\Permissions\PermissionService;
use BookStack\Entities\Book;
use BookStack\Entities\Entity;
use BookStack\Entities\EntityProvider;
use DB;
use Illuminate\Support\Collection;
class ViewService
{
protected $view;
protected $permissionService;
protected $entityProvider;
/**
* ViewService constructor.
* @param View $view
* @param PermissionService $permissionService
* @param EntityProvider $entityProvider
*/
public function __construct(View $view, PermissionService $permissionService)
public function __construct(View $view, PermissionService $permissionService, EntityProvider $entityProvider)
{
$this->view = $view;
$this->permissionService = $permissionService;
$this->entityProvider = $entityProvider;
}
/**
* Add a view to the given entity.
* @param Entity $entity
* @param \BookStack\Entities\Entity $entity
* @return int
*/
public function add(Entity $entity)
@@ -38,7 +45,7 @@ class ViewService
}
// Otherwise create new view count
$entity->views()->save($this->view->create([
$entity->views()->save($this->view->newInstance([
'user_id' => $user->id,
'views' => 1
]));
@@ -50,20 +57,21 @@ class ViewService
* Get the entities with the most views.
* @param int $count
* @param int $page
* @param bool|false|array $filterModel
* @param string|array $filterModels
* @param string $action - used for permission checking
* @return Collection
*/
public function getPopular($count = 10, $page = 0, $filterModel = false)
public function getPopular(int $count = 10, int $page = 0, array $filterModels = null, string $action = 'view')
{
$skipCount = $count * $page;
$query = $this->permissionService->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type')
->select('*', 'viewable_id', 'viewable_type', \DB::raw('SUM(views) as view_count'))
$query = $this->permissionService
->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type', $action)
->select('*', 'viewable_id', 'viewable_type', DB::raw('SUM(views) as view_count'))
->groupBy('viewable_id', 'viewable_type')
->orderBy('view_count', 'desc');
if ($filterModel && is_array($filterModel)) {
$query->whereIn('viewable_type', $filterModel);
} else if ($filterModel) {
$query->where('viewable_type', '=', get_class($filterModel));
if ($filterModels) {
$query->whereIn('viewable_type', $this->entityProvider->getMorphClasses($filterModels));
}
return $query->with('viewable')->skip($skipCount)->take($count)->get()->pluck('viewable');
@@ -87,7 +95,7 @@ class ViewService
->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type');
if ($filterModel) {
$query = $query->where('viewable_type', '=', get_class($filterModel));
$query = $query->where('viewable_type', '=', $filterModel->getMorphClass());
}
$query = $query->where('user_id', '=', $user->id);

View File

@@ -0,0 +1,127 @@
<?php namespace BookStack\Api;
use BookStack\Http\Controllers\Api\ApiController;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str;
use ReflectionClass;
use ReflectionException;
use ReflectionMethod;
class ApiDocsGenerator
{
protected $reflectionClasses = [];
protected $controllerClasses = [];
/**
* Generate API documentation.
*/
public function generate(): Collection
{
$apiRoutes = $this->getFlatApiRoutes();
$apiRoutes = $this->loadDetailsFromControllers($apiRoutes);
$apiRoutes = $this->loadDetailsFromFiles($apiRoutes);
$apiRoutes = $apiRoutes->groupBy('base_model');
return $apiRoutes;
}
/**
* Load any API details stored in static files.
*/
protected function loadDetailsFromFiles(Collection $routes): Collection
{
return $routes->map(function (array $route) {
$exampleTypes = ['request', 'response'];
foreach ($exampleTypes as $exampleType) {
$exampleFile = base_path("dev/api/{$exampleType}s/{$route['name']}.json");
$exampleContent = file_exists($exampleFile) ? file_get_contents($exampleFile) : null;
$route["example_{$exampleType}"] = $exampleContent;
}
return $route;
});
}
/**
* Load any details we can fetch from the controller and its methods.
*/
protected function loadDetailsFromControllers(Collection $routes): Collection
{
return $routes->map(function (array $route) {
$method = $this->getReflectionMethod($route['controller'], $route['controller_method']);
$comment = $method->getDocComment();
$route['description'] = $comment ? $this->parseDescriptionFromMethodComment($comment) : null;
$route['body_params'] = $this->getBodyParamsFromClass($route['controller'], $route['controller_method']);
return $route;
});
}
/**
* Load body params and their rules by inspecting the given class and method name.
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
protected function getBodyParamsFromClass(string $className, string $methodName): ?array
{
/** @var ApiController $class */
$class = $this->controllerClasses[$className] ?? null;
if ($class === null) {
$class = app()->make($className);
$this->controllerClasses[$className] = $class;
}
$rules = $class->getValdationRules()[$methodName] ?? [];
foreach ($rules as $param => $ruleString) {
$rules[$param] = explode('|', $ruleString);
}
return count($rules) > 0 ? $rules : null;
}
/**
* Parse out the description text from a class method comment.
*/
protected function parseDescriptionFromMethodComment(string $comment)
{
$matches = [];
preg_match_all('/^\s*?\*\s((?![@\s]).*?)$/m', $comment, $matches);
return implode(' ', $matches[1] ?? []);
}
/**
* Get a reflection method from the given class name and method name.
* @throws ReflectionException
*/
protected function getReflectionMethod(string $className, string $methodName): ReflectionMethod
{
$class = $this->reflectionClasses[$className] ?? null;
if ($class === null) {
$class = new ReflectionClass($className);
$this->reflectionClasses[$className] = $class;
}
return $class->getMethod($methodName);
}
/**
* Get the system API routes, formatted into a flat collection.
*/
protected function getFlatApiRoutes(): Collection
{
return collect(Route::getRoutes()->getRoutes())->filter(function ($route) {
return strpos($route->uri, 'api/') === 0;
})->map(function ($route) {
[$controller, $controllerMethod] = explode('@', $route->action['uses']);
$baseModelName = explode('.', explode('/', $route->uri)[1])[0];
$shortName = $baseModelName . '-' . $controllerMethod;
return [
'name' => $shortName,
'uri' => $route->uri,
'method' => $route->methods[0],
'controller' => $controller,
'controller_method' => $controllerMethod,
'controller_method_kebab' => Str::kebab($controllerMethod),
'base_model' => $baseModelName,
];
});
}
}

31
app/Api/ApiToken.php Normal file
View File

@@ -0,0 +1,31 @@
<?php namespace BookStack\Api;
use BookStack\Auth\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Carbon;
class ApiToken extends Model
{
protected $fillable = ['name', 'expires_at'];
protected $casts = [
'expires_at' => 'date:Y-m-d'
];
/**
* Get the user that this token belongs to.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* Get the default expiry value for an API token.
* Set to 100 years from now.
*/
public static function defaultExpiry(): string
{
return Carbon::now()->addYears(100)->format('Y-m-d');
}
}

166
app/Api/ApiTokenGuard.php Normal file
View File

@@ -0,0 +1,166 @@
<?php
namespace BookStack\Api;
use BookStack\Exceptions\ApiAuthException;
use Illuminate\Auth\GuardHelpers;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Hash;
use Symfony\Component\HttpFoundation\Request;
class ApiTokenGuard implements Guard
{
use GuardHelpers;
/**
* The request instance.
*/
protected $request;
/**
* The last auth exception thrown in this request.
* @var ApiAuthException
*/
protected $lastAuthException;
/**
* ApiTokenGuard constructor.
*/
public function __construct(Request $request)
{
$this->request = $request;
}
/**
* @inheritDoc
*/
public function user()
{
// Return the user if we've already retrieved them.
// Effectively a request-instance cache for this method.
if (!is_null($this->user)) {
return $this->user;
}
$user = null;
try {
$user = $this->getAuthorisedUserFromRequest();
} catch (ApiAuthException $exception) {
$this->lastAuthException = $exception;
}
$this->user = $user;
return $user;
}
/**
* Determine if current user is authenticated. If not, throw an exception.
*
* @return \Illuminate\Contracts\Auth\Authenticatable
*
* @throws ApiAuthException
*/
public function authenticate()
{
if (! is_null($user = $this->user())) {
return $user;
}
if ($this->lastAuthException) {
throw $this->lastAuthException;
}
throw new ApiAuthException('Unauthorized');
}
/**
* Check the API token in the request and fetch a valid authorised user.
* @throws ApiAuthException
*/
protected function getAuthorisedUserFromRequest(): Authenticatable
{
$authToken = trim($this->request->headers->get('Authorization', ''));
$this->validateTokenHeaderValue($authToken);
[$id, $secret] = explode(':', str_replace('Token ', '', $authToken));
$token = ApiToken::query()
->where('token_id', '=', $id)
->with(['user'])->first();
$this->validateToken($token, $secret);
return $token->user;
}
/**
* Validate the format of the token header value string.
* @throws ApiAuthException
*/
protected function validateTokenHeaderValue(string $authToken): void
{
if (empty($authToken)) {
throw new ApiAuthException(trans('errors.api_no_authorization_found'));
}
if (strpos($authToken, ':') === false || strpos($authToken, 'Token ') !== 0) {
throw new ApiAuthException(trans('errors.api_bad_authorization_format'));
}
}
/**
* Validate the given secret against the given token and ensure the token
* currently has access to the instance API.
* @throws ApiAuthException
*/
protected function validateToken(?ApiToken $token, string $secret): void
{
if ($token === null) {
throw new ApiAuthException(trans('errors.api_user_token_not_found'));
}
if (!Hash::check($secret, $token->secret)) {
throw new ApiAuthException(trans('errors.api_incorrect_token_secret'));
}
$now = Carbon::now();
if ($token->expires_at <= $now) {
throw new ApiAuthException(trans('errors.api_user_token_expired'), 403);
}
if (!$token->user->can('access-api')) {
throw new ApiAuthException(trans('errors.api_user_no_api_permission'), 403);
}
}
/**
* @inheritDoc
*/
public function validate(array $credentials = [])
{
if (empty($credentials['id']) || empty($credentials['secret'])) {
return false;
}
$token = ApiToken::query()
->where('token_id', '=', $credentials['id'])
->with(['user'])->first();
if ($token === null) {
return false;
}
return Hash::check($credentials['secret'], $token->secret);
}
/**
* "Log out" the currently authenticated user.
*/
public function logout()
{
$this->user = null;
}
}

View File

@@ -0,0 +1,138 @@
<?php namespace BookStack\Api;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\Request;
class ListingResponseBuilder
{
protected $query;
protected $request;
protected $fields;
protected $filterOperators = [
'eq' => '=',
'ne' => '!=',
'gt' => '>',
'lt' => '<',
'gte' => '>=',
'lte' => '<=',
'like' => 'like'
];
/**
* ListingResponseBuilder constructor.
*/
public function __construct(Builder $query, Request $request, array $fields)
{
$this->query = $query;
$this->request = $request;
$this->fields = $fields;
}
/**
* Get the response from this builder.
*/
public function toResponse()
{
$filteredQuery = $this->filterQuery($this->query);
$total = $filteredQuery->count();
$data = $this->fetchData($filteredQuery);
return response()->json([
'data' => $data,
'total' => $total,
]);
}
/**
* Fetch the data to return in the response.
*/
protected function fetchData(Builder $query): Collection
{
$query = $this->countAndOffsetQuery($query);
$query = $this->sortQuery($query);
return $query->get($this->fields);
}
/**
* Apply any filtering operations found in the request.
*/
protected function filterQuery(Builder $query): Builder
{
$query = clone $query;
$requestFilters = $this->request->get('filter', []);
if (!is_array($requestFilters)) {
return $query;
}
$queryFilters = collect($requestFilters)->map(function ($value, $key) {
return $this->requestFilterToQueryFilter($key, $value);
})->filter(function ($value) {
return !is_null($value);
})->values()->toArray();
return $query->where($queryFilters);
}
/**
* Convert a request filter query key/value pair into a [field, op, value] where condition.
*/
protected function requestFilterToQueryFilter($fieldKey, $value): ?array
{
$splitKey = explode(':', $fieldKey);
$field = $splitKey[0];
$filterOperator = $splitKey[1] ?? 'eq';
if (!in_array($field, $this->fields)) {
return null;
}
if (!in_array($filterOperator, array_keys($this->filterOperators))) {
$filterOperator = 'eq';
}
$queryOperator = $this->filterOperators[$filterOperator];
return [$field, $queryOperator, $value];
}
/**
* Apply sorting operations to the query from given parameters
* otherwise falling back to the first given field, ascending.
*/
protected function sortQuery(Builder $query): Builder
{
$query = clone $query;
$defaultSortName = $this->fields[0];
$direction = 'asc';
$sort = $this->request->get('sort', '');
if (strpos($sort, '-') === 0) {
$direction = 'desc';
}
$sortName = ltrim($sort, '+- ');
if (!in_array($sortName, $this->fields)) {
$sortName = $defaultSortName;
}
return $query->orderBy($sortName, $direction);
}
/**
* Apply count and offset for paging, based on params from the request while falling
* back to system defined default, taking the max limit into account.
*/
protected function countAndOffsetQuery(Builder $query): Builder
{
$query = clone $query;
$offset = max(0, $this->request->get('offset', 0));
$maxCount = config('api.max_item_count');
$count = $this->request->get('count', config('api.default_item_count'));
$count = max(min($maxCount, $count), 1);
return $query->skip($offset)->take($count);
}
}

23
app/Application.php Normal file
View File

@@ -0,0 +1,23 @@
<?php
namespace BookStack;
class Application extends \Illuminate\Foundation\Application
{
/**
* Get the path to the application configuration files.
*
* @param string $path Optionally, a path to append to the config path
* @return string
*/
public function configPath($path = '')
{
return $this->basePath
. DIRECTORY_SEPARATOR
. 'app'
. DIRECTORY_SEPARATOR
. 'Config'
. ($path ? DIRECTORY_SEPARATOR.$path : $path);
}
}

View File

@@ -0,0 +1,39 @@
<?php namespace BookStack\Auth\Access;
use BookStack\Auth\User;
use BookStack\Exceptions\ConfirmationEmailException;
use BookStack\Notifications\ConfirmEmail;
class EmailConfirmationService extends UserTokenService
{
protected $tokenTable = 'email_confirmations';
protected $expiryTime = 24;
/**
* Create new confirmation for a user,
* Also removes any existing old ones.
* @param User $user
* @throws ConfirmationEmailException
*/
public function sendConfirmation(User $user)
{
if ($user->email_confirmed) {
throw new ConfirmationEmailException(trans('errors.email_already_confirmed'), '/login');
}
$this->deleteByUser($user);
$token = $this->createTokenForUser($user);
$user->notify(new ConfirmEmail($token));
}
/**
* Check if confirmation is required in this instance.
* @return bool
*/
public function confirmationRequired() : bool
{
return setting('registration-confirmation')
|| setting('registration-restrict');
}
}

View File

@@ -0,0 +1,81 @@
<?php namespace BookStack\Auth\Access;
use BookStack\Auth\Role;
use BookStack\Auth\User;
use Illuminate\Database\Eloquent\Builder;
class ExternalAuthService
{
/**
* Check a role against an array of group names to see if it matches.
* Checked against role 'external_auth_id' if set otherwise the name of the role.
*/
protected function roleMatchesGroupNames(Role $role, array $groupNames): bool
{
if ($role->external_auth_id) {
return $this->externalIdMatchesGroupNames($role->external_auth_id, $groupNames);
}
$roleName = str_replace(' ', '-', trim(strtolower($role->display_name)));
return in_array($roleName, $groupNames);
}
/**
* Check if the given external auth ID string matches one of the given group names.
*/
protected function externalIdMatchesGroupNames(string $externalId, array $groupNames): bool
{
$externalAuthIds = explode(',', strtolower($externalId));
foreach ($externalAuthIds as $externalAuthId) {
if (in_array(trim($externalAuthId), $groupNames)) {
return true;
}
}
return false;
}
/**
* Match an array of group names to BookStack system roles.
* Formats group names to be lower-case and hyphenated.
* @param array $groupNames
* @return \Illuminate\Support\Collection
*/
protected function matchGroupsToSystemsRoles(array $groupNames)
{
foreach ($groupNames as $i => $groupName) {
$groupNames[$i] = str_replace(' ', '-', trim(strtolower($groupName)));
}
$roles = Role::query()->where(function (Builder $query) use ($groupNames) {
$query->whereIn('name', $groupNames);
foreach ($groupNames as $groupName) {
$query->orWhere('external_auth_id', 'LIKE', '%' . $groupName . '%');
}
})->get();
$matchedRoles = $roles->filter(function (Role $role) use ($groupNames) {
return $this->roleMatchesGroupNames($role, $groupNames);
});
return $matchedRoles->pluck('id');
}
/**
* Sync the groups to the user roles for the current user
*/
public function syncWithGroups(User $user, array $userGroups): void
{
// Get the ids for the roles from the names
$groupsAsRoles = $this->matchGroupsToSystemsRoles($userGroups);
// Sync groups
if ($this->config['remove_from_groups']) {
$user->roles()->sync($groupsAsRoles);
$user->attachDefaultRole();
} else {
$user->roles()->syncWithoutDetaching($groupsAsRoles);
}
}
}

View File

@@ -1,14 +1,11 @@
<?php
namespace BookStack\Providers;
namespace BookStack\Auth\Access;
use BookStack\Role;
use BookStack\Services\LdapService;
use BookStack\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;
class LdapUserProvider implements UserProvider
class ExternalBaseUserProvider implements UserProvider
{
/**
@@ -18,21 +15,13 @@ class LdapUserProvider implements UserProvider
*/
protected $model;
/**
* @var LdapService
*/
protected $ldapService;
/**
* LdapUserProvider constructor.
* @param $model
* @param LdapService $ldapService
*/
public function __construct($model, LdapService $ldapService)
public function __construct(string $model)
{
$this->model = $model;
$this->ldapService = $ldapService;
}
/**
@@ -46,7 +35,6 @@ class LdapUserProvider implements UserProvider
return new $class;
}
/**
* Retrieve a user by their unique identifier.
*
@@ -67,12 +55,7 @@ class LdapUserProvider implements UserProvider
*/
public function retrieveByToken($identifier, $token)
{
$model = $this->createModel();
return $model->newQuery()
->where($model->getAuthIdentifierName(), $identifier)
->where($model->getRememberTokenName(), $token)
->first();
return null;
}
@@ -85,10 +68,7 @@ class LdapUserProvider implements UserProvider
*/
public function updateRememberToken(Authenticatable $user, $token)
{
if ($user->exists) {
$user->setRememberToken($token);
$user->save();
}
//
}
/**
@@ -99,27 +79,11 @@ class LdapUserProvider implements UserProvider
*/
public function retrieveByCredentials(array $credentials)
{
// Get user via LDAP
$userDetails = $this->ldapService->getUserDetails($credentials['username']);
if ($userDetails === null) {
return null;
}
// Search current user base by looking up a uid
$model = $this->createModel();
$currentUser = $model->newQuery()
->where('external_auth_id', $userDetails['uid'])
return $model->newQuery()
->where('external_auth_id', $credentials['external_auth_id'])
->first();
if ($currentUser !== null) {
return $currentUser;
}
$model->name = $userDetails['name'];
$model->external_auth_id = $userDetails['uid'];
$model->email = $userDetails['email'];
$model->email_confirmed = false;
return $model;
}
/**
@@ -131,6 +95,7 @@ class LdapUserProvider implements UserProvider
*/
public function validateCredentials(Authenticatable $user, array $credentials)
{
return $this->ldapService->validateUserCredentials($user, $credentials['username'], $credentials['password']);
// Should be done in the guard.
return false;
}
}

View File

@@ -0,0 +1,305 @@
<?php
namespace BookStack\Auth\Access\Guards;
use BookStack\Auth\Access\RegistrationService;
use Illuminate\Auth\GuardHelpers;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Session\Session;
/**
* Class BaseSessionGuard
* A base implementation of a session guard. Is a copy of the default Laravel
* guard with 'remember' functionality removed. Basic auth and event emission
* has also been removed to keep this simple. Designed to be extended by external
* Auth Guards.
*
* @package Illuminate\Auth
*/
class ExternalBaseSessionGuard implements StatefulGuard
{
use GuardHelpers;
/**
* The name of the Guard. Typically "session".
*
* Corresponds to guard name in authentication configuration.
*
* @var string
*/
protected $name;
/**
* The user we last attempted to retrieve.
*
* @var \Illuminate\Contracts\Auth\Authenticatable
*/
protected $lastAttempted;
/**
* The session used by the guard.
*
* @var \Illuminate\Contracts\Session\Session
*/
protected $session;
/**
* Indicates if the logout method has been called.
*
* @var bool
*/
protected $loggedOut = false;
/**
* Service to handle common registration actions.
*
* @var RegistrationService
*/
protected $registrationService;
/**
* Create a new authentication guard.
*
* @return void
*/
public function __construct(string $name, UserProvider $provider, Session $session, RegistrationService $registrationService)
{
$this->name = $name;
$this->session = $session;
$this->provider = $provider;
$this->registrationService = $registrationService;
}
/**
* Get the currently authenticated user.
*
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function user()
{
if ($this->loggedOut) {
return;
}
// If we've already retrieved the user for the current request we can just
// return it back immediately. We do not want to fetch the user data on
// every call to this method because that would be tremendously slow.
if (! is_null($this->user)) {
return $this->user;
}
$id = $this->session->get($this->getName());
// First we will try to load the user using the
// identifier in the session if one exists.
if (! is_null($id)) {
$this->user = $this->provider->retrieveById($id);
}
return $this->user;
}
/**
* Get the ID for the currently authenticated user.
*
* @return int|null
*/
public function id()
{
if ($this->loggedOut) {
return;
}
return $this->user()
? $this->user()->getAuthIdentifier()
: $this->session->get($this->getName());
}
/**
* Log a user into the application without sessions or cookies.
*
* @param array $credentials
* @return bool
*/
public function once(array $credentials = [])
{
if ($this->validate($credentials)) {
$this->setUser($this->lastAttempted);
return true;
}
return false;
}
/**
* Log the given user ID into the application without sessions or cookies.
*
* @param mixed $id
* @return \Illuminate\Contracts\Auth\Authenticatable|false
*/
public function onceUsingId($id)
{
if (! is_null($user = $this->provider->retrieveById($id))) {
$this->setUser($user);
return $user;
}
return false;
}
/**
* Validate a user's credentials.
*
* @param array $credentials
* @return bool
*/
public function validate(array $credentials = [])
{
return false;
}
/**
* Attempt to authenticate a user using the given credentials.
*
* @param array $credentials
* @param bool $remember
* @return bool
*/
public function attempt(array $credentials = [], $remember = false)
{
return false;
}
/**
* Log the given user ID into the application.
*
* @param mixed $id
* @param bool $remember
* @return \Illuminate\Contracts\Auth\Authenticatable|false
*/
public function loginUsingId($id, $remember = false)
{
if (! is_null($user = $this->provider->retrieveById($id))) {
$this->login($user, $remember);
return $user;
}
return false;
}
/**
* Log a user into the application.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param bool $remember
* @return void
*/
public function login(AuthenticatableContract $user, $remember = false)
{
$this->updateSession($user->getAuthIdentifier());
$this->setUser($user);
}
/**
* Update the session with the given ID.
*
* @param string $id
* @return void
*/
protected function updateSession($id)
{
$this->session->put($this->getName(), $id);
$this->session->migrate(true);
}
/**
* Log the user out of the application.
*
* @return void
*/
public function logout()
{
$this->clearUserDataFromStorage();
// Now we will clear the users out of memory so they are no longer available
// as the user is no longer considered as being signed into this
// application and should not be available here.
$this->user = null;
$this->loggedOut = true;
}
/**
* Remove the user data from the session and cookies.
*
* @return void
*/
protected function clearUserDataFromStorage()
{
$this->session->remove($this->getName());
}
/**
* Get the last user we attempted to authenticate.
*
* @return \Illuminate\Contracts\Auth\Authenticatable
*/
public function getLastAttempted()
{
return $this->lastAttempted;
}
/**
* Get a unique identifier for the auth session value.
*
* @return string
*/
public function getName()
{
return 'login_'.$this->name.'_'.sha1(static::class);
}
/**
* Determine if the user was authenticated via "remember me" cookie.
*
* @return bool
*/
public function viaRemember()
{
return false;
}
/**
* Return the currently cached user.
*
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function getUser()
{
return $this->user;
}
/**
* Set the current user.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return $this
*/
public function setUser(AuthenticatableContract $user)
{
$this->user = $user;
$this->loggedOut = false;
return $this;
}
}

View File

@@ -0,0 +1,123 @@
<?php
namespace BookStack\Auth\Access\Guards;
use BookStack\Auth\Access\LdapService;
use BookStack\Auth\Access\RegistrationService;
use BookStack\Auth\User;
use BookStack\Auth\UserRepo;
use BookStack\Exceptions\LdapException;
use BookStack\Exceptions\LoginAttemptException;
use BookStack\Exceptions\LoginAttemptEmailNeededException;
use BookStack\Exceptions\UserRegistrationException;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Session\Session;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
class LdapSessionGuard extends ExternalBaseSessionGuard
{
protected $ldapService;
/**
* LdapSessionGuard constructor.
*/
public function __construct($name,
UserProvider $provider,
Session $session,
LdapService $ldapService,
RegistrationService $registrationService
)
{
$this->ldapService = $ldapService;
parent::__construct($name, $provider, $session, $registrationService);
}
/**
* Validate a user's credentials.
*
* @param array $credentials
* @return bool
* @throws LdapException
*/
public function validate(array $credentials = [])
{
$userDetails = $this->ldapService->getUserDetails($credentials['username']);
if (isset($userDetails['uid'])) {
$this->lastAttempted = $this->provider->retrieveByCredentials([
'external_auth_id' => $userDetails['uid']
]);
}
return $this->ldapService->validateUserCredentials($userDetails, $credentials['password']);
}
/**
* Attempt to authenticate a user using the given credentials.
*
* @param array $credentials
* @param bool $remember
* @return bool
* @throws LoginAttemptException
* @throws LdapException
*/
public function attempt(array $credentials = [], $remember = false)
{
$username = $credentials['username'];
$userDetails = $this->ldapService->getUserDetails($username);
$user = null;
if (isset($userDetails['uid'])) {
$this->lastAttempted = $user = $this->provider->retrieveByCredentials([
'external_auth_id' => $userDetails['uid']
]);
}
if (!$this->ldapService->validateUserCredentials($userDetails, $credentials['password'])) {
return false;
}
if (is_null($user)) {
try {
$user = $this->createNewFromLdapAndCreds($userDetails, $credentials);
} catch (UserRegistrationException $exception) {
throw new LoginAttemptException($exception->message);
}
}
// Sync LDAP groups if required
if ($this->ldapService->shouldSyncGroups()) {
$this->ldapService->syncGroups($user, $username);
}
$this->login($user, $remember);
return true;
}
/**
* Create a new user from the given ldap credentials and login credentials
* @throws LoginAttemptEmailNeededException
* @throws LoginAttemptException
* @throws UserRegistrationException
*/
protected function createNewFromLdapAndCreds(array $ldapUserDetails, array $credentials): User
{
$email = trim($ldapUserDetails['email'] ?: ($credentials['email'] ?? ''));
if (empty($email)) {
throw new LoginAttemptEmailNeededException();
}
$details = [
'name' => $ldapUserDetails['name'],
'email' => $ldapUserDetails['email'] ?: $credentials['email'],
'external_auth_id' => $ldapUserDetails['uid'],
'password' => Str::random(32),
];
return $this->registrationService->registerUser($details, null, false);
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace BookStack\Auth\Access\Guards;
/**
* Saml2 Session Guard
*
* The saml2 login process is async in nature meaning it does not fit very well
* into the default laravel 'Guard' auth flow. Instead most of the logic is done
* via the Saml2 controller & Saml2Service. This class provides a safer, thin
* version of SessionGuard.
*
* @package BookStack\Auth\Access\Guards
*/
class Saml2SessionGuard extends ExternalBaseSessionGuard
{
/**
* Validate a user's credentials.
*
* @param array $credentials
* @return bool
*/
public function validate(array $credentials = [])
{
return false;
}
/**
* Attempt to authenticate a user using the given credentials.
*
* @param array $credentials
* @param bool $remember
* @return bool
*/
public function attempt(array $credentials = [], $remember = false)
{
return false;
}
}

View File

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

View File

@@ -0,0 +1,345 @@
<?php namespace BookStack\Auth\Access;
use BookStack\Auth\User;
use BookStack\Exceptions\JsonDebugException;
use BookStack\Exceptions\LdapException;
use ErrorException;
/**
* Class LdapService
* Handles any app-specific LDAP tasks.
*/
class LdapService extends ExternalAuthService
{
protected $ldap;
protected $ldapConnection;
protected $config;
protected $enabled;
/**
* LdapService constructor.
*/
public function __construct(Ldap $ldap)
{
$this->ldap = $ldap;
$this->config = config('services.ldap');
$this->enabled = config('auth.method') === 'ldap';
}
/**
* Check if groups should be synced.
* @return bool
*/
public function shouldSyncGroups()
{
return $this->enabled && $this->config['user_to_groups'] !== false;
}
/**
* Search for attributes for a specific user on the ldap.
* @throws LdapException
*/
private function getUserWithAttributes(string $userName, array $attributes): ?array
{
$ldapConnection = $this->getConnection();
$this->bindSystemUser($ldapConnection);
// Clean attributes
foreach ($attributes as $index => $attribute) {
if (strpos($attribute, 'BIN;') === 0) {
$attributes[$index] = substr($attribute, strlen('BIN;'));
}
}
// Find user
$userFilter = $this->buildFilter($this->config['user_filter'], ['user' => $userName]);
$baseDn = $this->config['base_dn'];
$followReferrals = $this->config['follow_referrals'] ? 1 : 0;
$this->ldap->setOption($ldapConnection, LDAP_OPT_REFERRALS, $followReferrals);
$users = $this->ldap->searchAndGetEntries($ldapConnection, $baseDn, $userFilter, $attributes);
if ($users['count'] === 0) {
return null;
}
return $users[0];
}
/**
* Get the details of a user from LDAP using the given username.
* User found via configurable user filter.
* @throws LdapException
*/
public function getUserDetails(string $userName): ?array
{
$idAttr = $this->config['id_attribute'];
$emailAttr = $this->config['email_attribute'];
$displayNameAttr = $this->config['display_name_attribute'];
$user = $this->getUserWithAttributes($userName, ['cn', 'dn', $idAttr, $emailAttr, $displayNameAttr]);
if ($user === null) {
return null;
}
$userCn = $this->getUserResponseProperty($user, 'cn', null);
$formatted = [
'uid' => $this->getUserResponseProperty($user, $idAttr, $user['dn']),
'name' => $this->getUserResponseProperty($user, $displayNameAttr, $userCn),
'dn' => $user['dn'],
'email' => $this->getUserResponseProperty($user, $emailAttr, null),
];
if ($this->config['dump_user_details']) {
throw new JsonDebugException([
'details_from_ldap' => $user,
'details_bookstack_parsed' => $formatted,
]);
}
return $formatted;
}
/**
* Get a property from an LDAP user response fetch.
* Handles properties potentially being part of an array.
* If the given key is prefixed with 'BIN;', that indicator will be stripped
* from the key and any fetched values will be converted from binary to hex.
*/
protected function getUserResponseProperty(array $userDetails, string $propertyKey, $defaultValue)
{
$isBinary = strpos($propertyKey, 'BIN;') === 0;
$propertyKey = strtolower($propertyKey);
$value = $defaultValue;
if ($isBinary) {
$propertyKey = substr($propertyKey, strlen('BIN;'));
}
if (isset($userDetails[$propertyKey])) {
$value = (is_array($userDetails[$propertyKey]) ? $userDetails[$propertyKey][0] : $userDetails[$propertyKey]);
if ($isBinary) {
$value = bin2hex($value);
}
}
return $value;
}
/**
* Check if the given credentials are valid for the given user.
* @throws LdapException
*/
public function validateUserCredentials(?array $ldapUserDetails, string $password): bool
{
if (is_null($ldapUserDetails)) {
return false;
}
$ldapConnection = $this->getConnection();
try {
$ldapBind = $this->ldap->bind($ldapConnection, $ldapUserDetails['dn'], $password);
} catch (ErrorException $e) {
$ldapBind = false;
}
return $ldapBind;
}
/**
* Bind the system user to the LDAP connection using the given credentials
* otherwise anonymous access is attempted.
* @param $connection
* @throws LdapException
*/
protected function bindSystemUser($connection)
{
$ldapDn = $this->config['dn'];
$ldapPass = $this->config['pass'];
$isAnonymous = ($ldapDn === false || $ldapPass === false);
if ($isAnonymous) {
$ldapBind = $this->ldap->bind($connection);
} else {
$ldapBind = $this->ldap->bind($connection, $ldapDn, $ldapPass);
}
if (!$ldapBind) {
throw new LdapException(($isAnonymous ? trans('errors.ldap_fail_anonymous') : trans('errors.ldap_fail_authed')));
}
}
/**
* Get the connection to the LDAP server.
* Creates a new connection if one does not exist.
* @return resource
* @throws LdapException
*/
protected function getConnection()
{
if ($this->ldapConnection !== null) {
return $this->ldapConnection;
}
// Check LDAP extension in installed
if (!function_exists('ldap_connect') && config('app.env') !== 'testing') {
throw new LdapException(trans('errors.ldap_extension_not_installed'));
}
// Check if TLS_INSECURE is set. The handle is set to NULL due to the nature of
// the LDAP_OPT_X_TLS_REQUIRE_CERT option. It can only be set globally and not per handle.
if ($this->config['tls_insecure']) {
$this->ldap->setOption(null, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_NEVER);
}
$serverDetails = $this->parseServerString($this->config['server']);
$ldapConnection = $this->ldap->connect($serverDetails['host'], $serverDetails['port']);
if ($ldapConnection === false) {
throw new LdapException(trans('errors.ldap_cannot_connect'));
}
// Set any required options
if ($this->config['version']) {
$this->ldap->setVersion($ldapConnection, $this->config['version']);
}
$this->ldapConnection = $ldapConnection;
return $this->ldapConnection;
}
/**
* Parse a LDAP server string and return the host and port for a connection.
* Is flexible to formats such as 'ldap.example.com:8069' or 'ldaps://ldap.example.com'.
*/
protected function parseServerString(string $serverString): array
{
$serverNameParts = explode(':', $serverString);
// If we have a protocol just return the full string since PHP will ignore a separate port.
if ($serverNameParts[0] === 'ldaps' || $serverNameParts[0] === 'ldap') {
return ['host' => $serverString, 'port' => 389];
}
// Otherwise, extract the port out
$hostName = $serverNameParts[0];
$ldapPort = (count($serverNameParts) > 1) ? intval($serverNameParts[1]) : 389;
return ['host' => $hostName, 'port' => $ldapPort];
}
/**
* Build a filter string by injecting common variables.
*/
protected function buildFilter(string $filterString, array $attrs): string
{
$newAttrs = [];
foreach ($attrs as $key => $attrText) {
$newKey = '${' . $key . '}';
$newAttrs[$newKey] = $this->ldap->escape($attrText);
}
return strtr($filterString, $newAttrs);
}
/**
* Get the groups a user is a part of on ldap.
* @throws LdapException
*/
public function getUserGroups(string $userName): array
{
$groupsAttr = $this->config['group_attribute'];
$user = $this->getUserWithAttributes($userName, [$groupsAttr]);
if ($user === null) {
return [];
}
$userGroups = $this->groupFilter($user);
$userGroups = $this->getGroupsRecursive($userGroups, []);
return $userGroups;
}
/**
* Get the parent groups of an array of groups.
* @throws LdapException
*/
private function getGroupsRecursive(array $groupsArray, array $checked): array
{
$groupsToAdd = [];
foreach ($groupsArray as $groupName) {
if (in_array($groupName, $checked)) {
continue;
}
$parentGroups = $this->getGroupGroups($groupName);
$groupsToAdd = array_merge($groupsToAdd, $parentGroups);
$checked[] = $groupName;
}
$groupsArray = array_unique(array_merge($groupsArray, $groupsToAdd), SORT_REGULAR);
if (empty($groupsToAdd)) {
return $groupsArray;
}
return $this->getGroupsRecursive($groupsArray, $checked);
}
/**
* Get the parent groups of a single group.
* @throws LdapException
*/
private function getGroupGroups(string $groupName): array
{
$ldapConnection = $this->getConnection();
$this->bindSystemUser($ldapConnection);
$followReferrals = $this->config['follow_referrals'] ? 1 : 0;
$this->ldap->setOption($ldapConnection, LDAP_OPT_REFERRALS, $followReferrals);
$baseDn = $this->config['base_dn'];
$groupsAttr = strtolower($this->config['group_attribute']);
$groupFilter = 'CN=' . $this->ldap->escape($groupName);
$groups = $this->ldap->searchAndGetEntries($ldapConnection, $baseDn, $groupFilter, [$groupsAttr]);
if ($groups['count'] === 0) {
return [];
}
return $this->groupFilter($groups[0]);
}
/**
* Filter out LDAP CN and DN language in a ldap search return.
* Gets the base CN (common name) of the string.
*/
protected function groupFilter(array $userGroupSearchResponse): array
{
$groupsAttr = strtolower($this->config['group_attribute']);
$ldapGroups = [];
$count = 0;
if (isset($userGroupSearchResponse[$groupsAttr]['count'])) {
$count = (int)$userGroupSearchResponse[$groupsAttr]['count'];
}
for ($i = 0; $i < $count; $i++) {
$dnComponents = $this->ldap->explodeDn($userGroupSearchResponse[$groupsAttr][$i], 1);
if (!in_array($dnComponents[0], $ldapGroups)) {
$ldapGroups[] = $dnComponents[0];
}
}
return $ldapGroups;
}
/**
* Sync the LDAP groups to the user roles for the current user.
* @throws LdapException
*/
public function syncGroups(User $user, string $username)
{
$userLdapGroups = $this->getUserGroups($username);
$this->syncWithGroups($user, $userLdapGroups);
}
}

View File

@@ -0,0 +1,109 @@
<?php namespace BookStack\Auth\Access;
use BookStack\Auth\SocialAccount;
use BookStack\Auth\User;
use BookStack\Auth\UserRepo;
use BookStack\Exceptions\UserRegistrationException;
use Exception;
class RegistrationService
{
protected $userRepo;
protected $emailConfirmationService;
/**
* RegistrationService constructor.
*/
public function __construct(UserRepo $userRepo, EmailConfirmationService $emailConfirmationService)
{
$this->userRepo = $userRepo;
$this->emailConfirmationService = $emailConfirmationService;
}
/**
* Check whether or not registrations are allowed in the app settings.
* @throws UserRegistrationException
*/
public function ensureRegistrationAllowed()
{
if (!$this->registrationAllowed()) {
throw new UserRegistrationException(trans('auth.registrations_disabled'), '/login');
}
}
/**
* Check if standard BookStack User registrations are currently allowed.
* Does not prevent external-auth based registration.
*/
protected function registrationAllowed(): bool
{
$authMethod = config('auth.method');
$authMethodsWithRegistration = ['standard'];
return in_array($authMethod, $authMethodsWithRegistration) && setting('registration-enabled');
}
/**
* The registrations flow for all users.
* @throws UserRegistrationException
*/
public function registerUser(array $userData, ?SocialAccount $socialAccount = null, bool $emailConfirmed = false): User
{
$userEmail = $userData['email'];
// Email restriction
$this->ensureEmailDomainAllowed($userEmail);
// Ensure user does not already exist
$alreadyUser = !is_null($this->userRepo->getByEmail($userEmail));
if ($alreadyUser) {
throw new UserRegistrationException(trans('errors.error_user_exists_different_creds', ['email' => $userEmail]));
}
// Create the user
$newUser = $this->userRepo->registerNew($userData, $emailConfirmed);
// Assign social account if given
if ($socialAccount) {
$newUser->socialAccounts()->save($socialAccount);
}
// Start email confirmation flow if required
if ($this->emailConfirmationService->confirmationRequired() && !$emailConfirmed) {
$newUser->save();
$message = '';
try {
$this->emailConfirmationService->sendConfirmation($newUser);
} catch (Exception $e) {
$message = trans('auth.email_confirm_send_error');
}
throw new UserRegistrationException($message, '/register/confirm');
}
return $newUser;
}
/**
* Ensure that the given email meets any active email domain registration restrictions.
* Throws if restrictions are active and the email does not match an allowed domain.
* @throws UserRegistrationException
*/
protected function ensureEmailDomainAllowed(string $userEmail): void
{
$registrationRestrict = setting('registration-restrict');
if (!$registrationRestrict) {
return;
}
$restrictedEmailDomains = explode(',', str_replace(' ', '', $registrationRestrict));
$userEmailDomain = $domain = mb_substr(mb_strrchr($userEmail, "@"), 1);
if (!in_array($userEmailDomain, $restrictedEmailDomains)) {
$redirect = $this->registrationAllowed() ? '/register' : '/login';
throw new UserRegistrationException(trans('auth.registration_email_domain_invalid'), $redirect);
}
}
}

View File

@@ -0,0 +1,378 @@
<?php namespace BookStack\Auth\Access;
use BookStack\Auth\User;
use BookStack\Exceptions\JsonDebugException;
use BookStack\Exceptions\SamlException;
use BookStack\Exceptions\UserRegistrationException;
use Exception;
use Illuminate\Support\Str;
use OneLogin\Saml2\Auth;
use OneLogin\Saml2\Error;
use OneLogin\Saml2\IdPMetadataParser;
use OneLogin\Saml2\ValidationError;
/**
* Class Saml2Service
* Handles any app-specific SAML tasks.
*/
class Saml2Service extends ExternalAuthService
{
protected $config;
protected $registrationService;
protected $user;
/**
* Saml2Service constructor.
*/
public function __construct(RegistrationService $registrationService, User $user)
{
$this->config = config('saml2');
$this->registrationService = $registrationService;
$this->user = $user;
}
/**
* Initiate a login flow.
* @throws Error
*/
public function login(): array
{
$toolKit = $this->getToolkit();
$returnRoute = url('/saml2/acs');
return [
'url' => $toolKit->login($returnRoute, [], false, false, true),
'id' => $toolKit->getLastRequestID(),
];
}
/**
* Initiate a logout flow.
* @throws Error
*/
public function logout(): array
{
$toolKit = $this->getToolkit();
$returnRoute = url('/');
try {
$url = $toolKit->logout($returnRoute, [], null, null, true);
$id = $toolKit->getLastRequestID();
} catch (Error $error) {
if ($error->getCode() !== Error::SAML_SINGLE_LOGOUT_NOT_SUPPORTED) {
throw $error;
}
$this->actionLogout();
$url = '/';
$id = null;
}
return ['url' => $url, 'id' => $id];
}
/**
* Process the ACS response from the idp and return the
* matching, or new if registration active, user matched to the idp.
* Returns null if not authenticated.
* @throws Error
* @throws SamlException
* @throws ValidationError
* @throws JsonDebugException
* @throws UserRegistrationException
*/
public function processAcsResponse(?string $requestId): ?User
{
$toolkit = $this->getToolkit();
$toolkit->processResponse($requestId);
$errors = $toolkit->getErrors();
if (!empty($errors)) {
throw new Error(
'Invalid ACS Response: '.implode(', ', $errors)
);
}
if (!$toolkit->isAuthenticated()) {
return null;
}
$attrs = $toolkit->getAttributes();
$id = $toolkit->getNameId();
return $this->processLoginCallback($id, $attrs);
}
/**
* Process a response for the single logout service.
* @throws Error
*/
public function processSlsResponse(?string $requestId): ?string
{
$toolkit = $this->getToolkit();
$redirect = $toolkit->processSLO(true, $requestId, false, null, true);
$errors = $toolkit->getErrors();
if (!empty($errors)) {
throw new Error(
'Invalid SLS Response: '.implode(', ', $errors)
);
}
$this->actionLogout();
return $redirect;
}
/**
* Do the required actions to log a user out.
*/
protected function actionLogout()
{
auth()->logout();
session()->invalidate();
}
/**
* Get the metadata for this service provider.
* @throws Error
*/
public function metadata(): string
{
$toolKit = $this->getToolkit();
$settings = $toolKit->getSettings();
$metadata = $settings->getSPMetadata();
$errors = $settings->validateMetadata($metadata);
if (!empty($errors)) {
throw new Error(
'Invalid SP metadata: '.implode(', ', $errors),
Error::METADATA_SP_INVALID
);
}
return $metadata;
}
/**
* Load the underlying Onelogin SAML2 toolkit.
* @throws Error
* @throws Exception
*/
protected function getToolkit(): Auth
{
$settings = $this->config['onelogin'];
$overrides = $this->config['onelogin_overrides'] ?? [];
if ($overrides && is_string($overrides)) {
$overrides = json_decode($overrides, true);
}
$metaDataSettings = [];
if ($this->config['autoload_from_metadata']) {
$metaDataSettings = IdPMetadataParser::parseRemoteXML($settings['idp']['entityId']);
}
$spSettings = $this->loadOneloginServiceProviderDetails();
$settings = array_replace_recursive($settings, $spSettings, $metaDataSettings, $overrides);
return new Auth($settings);
}
/**
* Load dynamic service provider options required by the onelogin toolkit.
*/
protected function loadOneloginServiceProviderDetails(): array
{
$spDetails = [
'entityId' => url('/saml2/metadata'),
'assertionConsumerService' => [
'url' => url('/saml2/acs'),
],
'singleLogoutService' => [
'url' => url('/saml2/sls')
],
];
return [
'baseurl' => url('/saml2'),
'sp' => $spDetails
];
}
/**
* Check if groups should be synced.
*/
protected function shouldSyncGroups(): bool
{
return $this->config['user_to_groups'] !== false;
}
/**
* Calculate the display name
*/
protected function getUserDisplayName(array $samlAttributes, string $defaultValue): string
{
$displayNameAttr = $this->config['display_name_attributes'];
$displayName = [];
foreach ($displayNameAttr as $dnAttr) {
$dnComponent = $this->getSamlResponseAttribute($samlAttributes, $dnAttr, null);
if ($dnComponent !== null) {
$displayName[] = $dnComponent;
}
}
if (count($displayName) == 0) {
$displayName = $defaultValue;
} else {
$displayName = implode(' ', $displayName);
}
return $displayName;
}
/**
* Get the value to use as the external id saved in BookStack
* used to link the user to an existing BookStack DB user.
*/
protected function getExternalId(array $samlAttributes, string $defaultValue)
{
$userNameAttr = $this->config['external_id_attribute'];
if ($userNameAttr === null) {
return $defaultValue;
}
return $this->getSamlResponseAttribute($samlAttributes, $userNameAttr, $defaultValue);
}
/**
* Extract the details of a user from a SAML response.
*/
protected function getUserDetails(string $samlID, $samlAttributes): array
{
$emailAttr = $this->config['email_attribute'];
$externalId = $this->getExternalId($samlAttributes, $samlID);
$defaultEmail = filter_var($samlID, FILTER_VALIDATE_EMAIL) ? $samlID : null;
$email = $this->getSamlResponseAttribute($samlAttributes, $emailAttr, $defaultEmail);
return [
'external_id' => $externalId,
'name' => $this->getUserDisplayName($samlAttributes, $externalId),
'email' => $email,
'saml_id' => $samlID,
];
}
/**
* Get the groups a user is a part of from the SAML response.
*/
public function getUserGroups(array $samlAttributes): array
{
$groupsAttr = $this->config['group_attribute'];
$userGroups = $samlAttributes[$groupsAttr] ?? null;
if (!is_array($userGroups)) {
$userGroups = [];
}
return $userGroups;
}
/**
* For an array of strings, return a default for an empty array,
* a string for an array with one element and the full array for
* more than one element.
*/
protected function simplifyValue(array $data, $defaultValue)
{
switch (count($data)) {
case 0:
$data = $defaultValue;
break;
case 1:
$data = $data[0];
break;
}
return $data;
}
/**
* Get a property from an SAML response.
* Handles properties potentially being an array.
*/
protected function getSamlResponseAttribute(array $samlAttributes, string $propertyKey, $defaultValue)
{
if (isset($samlAttributes[$propertyKey])) {
return $this->simplifyValue($samlAttributes[$propertyKey], $defaultValue);
}
return $defaultValue;
}
/**
* Get the user from the database for the specified details.
* @throws SamlException
* @throws UserRegistrationException
*/
protected function getOrRegisterUser(array $userDetails): ?User
{
$user = $this->user->newQuery()
->where('external_auth_id', '=', $userDetails['external_id'])
->first();
if (is_null($user)) {
$userData = [
'name' => $userDetails['name'],
'email' => $userDetails['email'],
'password' => Str::random(32),
'external_auth_id' => $userDetails['external_id'],
];
$user = $this->registrationService->registerUser($userData, null, false);
}
return $user;
}
/**
* Process the SAML response for a user. Login the user when
* they exist, optionally registering them automatically.
* @throws SamlException
* @throws JsonDebugException
* @throws UserRegistrationException
*/
public function processLoginCallback(string $samlID, array $samlAttributes): User
{
$userDetails = $this->getUserDetails($samlID, $samlAttributes);
$isLoggedIn = auth()->check();
if ($this->config['dump_user_details']) {
throw new JsonDebugException([
'id_from_idp' => $samlID,
'attrs_from_idp' => $samlAttributes,
'attrs_after_parsing' => $userDetails,
]);
}
if ($userDetails['email'] === null) {
throw new SamlException(trans('errors.saml_no_email_address'));
}
if ($isLoggedIn) {
throw new SamlException(trans('errors.saml_already_logged_in'), '/login');
}
$user = $this->getOrRegisterUser($userDetails);
if ($user === null) {
throw new SamlException(trans('errors.saml_user_not_registered', ['name' => $userDetails['external_id']]), '/login');
}
if ($this->shouldSyncGroups()) {
$groups = $this->getUserGroups($samlAttributes);
$this->syncWithGroups($user, $groups);
}
auth()->login($user);
return $user;
}
}

View File

@@ -1,13 +1,15 @@
<?php namespace BookStack\Services;
<?php namespace BookStack\Auth\Access;
use BookStack\Http\Requests\Request;
use GuzzleHttp\Exception\ClientException;
use Laravel\Socialite\Contracts\Factory as Socialite;
use BookStack\Auth\SocialAccount;
use BookStack\Auth\UserRepo;
use BookStack\Exceptions\SocialDriverNotConfigured;
use BookStack\Exceptions\SocialSignInException;
use BookStack\Exceptions\SocialSignInAccountNotUsed;
use BookStack\Exceptions\UserRegistrationException;
use BookStack\Repos\UserRepo;
use BookStack\SocialAccount;
use Illuminate\Support\Str;
use Laravel\Socialite\Contracts\Factory as Socialite;
use Laravel\Socialite\Contracts\Provider;
use Laravel\Socialite\Contracts\User as SocialUser;
use Symfony\Component\HttpFoundation\RedirectResponse;
class SocialAuthService
{
@@ -16,13 +18,10 @@ class SocialAuthService
protected $socialite;
protected $socialAccount;
protected $validSocialDrivers = ['google', 'github', 'facebook', 'slack', 'twitter', 'azure', 'okta', 'gitlab', 'twitch'];
protected $validSocialDrivers = ['google', 'github', 'facebook', 'slack', 'twitter', 'azure', 'okta', 'gitlab', 'twitch', 'discord'];
/**
* SocialAuthService constructor.
* @param UserRepo $userRepo
* @param Socialite $socialite
* @param SocialAccount $socialAccount
*/
public function __construct(UserRepo $userRepo, Socialite $socialite, SocialAccount $socialAccount)
{
@@ -34,42 +33,30 @@ class SocialAuthService
/**
* Start the social login path.
* @param string $socialDriver
* @return \Symfony\Component\HttpFoundation\RedirectResponse
* @throws SocialDriverNotConfigured
*/
public function startLogIn($socialDriver)
public function startLogIn(string $socialDriver): RedirectResponse
{
$driver = $this->validateDriver($socialDriver);
return $this->socialite->driver($driver)->redirect();
return $this->getSocialDriver($driver)->redirect();
}
/**
* Start the social registration process
* @param string $socialDriver
* @return \Symfony\Component\HttpFoundation\RedirectResponse
* @throws SocialDriverNotConfigured
*/
public function startRegister($socialDriver)
public function startRegister(string $socialDriver): RedirectResponse
{
$driver = $this->validateDriver($socialDriver);
return $this->socialite->driver($driver)->redirect();
return $this->getSocialDriver($driver)->redirect();
}
/**
* Handle the social registration process on callback.
* @param $socialDriver
* @return \Laravel\Socialite\Contracts\User
* @throws SocialDriverNotConfigured
* @throws UserRegistrationException
*/
public function handleRegistrationCallback($socialDriver)
public function handleRegistrationCallback(string $socialDriver, SocialUser $socialUser): SocialUser
{
$driver = $this->validateDriver($socialDriver);
// Get user details from social driver
$socialUser = $this->socialite->driver($driver)->user();
// Check social account has not already been used
if ($this->socialAccount->where('driver_id', '=', $socialUser->getId())->exists()) {
throw new UserRegistrationException(trans('errors.social_account_in_use', ['socialAccount'=>$socialDriver]), '/login');
@@ -77,30 +64,35 @@ class SocialAuthService
if ($this->userRepo->getByEmail($socialUser->getEmail())) {
$email = $socialUser->getEmail();
throw new UserRegistrationException(trans('errors.social_account_in_use', ['socialAccount'=>$socialDriver, 'email' => $email]), '/login');
throw new UserRegistrationException(trans('errors.error_user_exists_different_creds', ['email' => $email]), '/login');
}
return $socialUser;
}
/**
* Handle the login process on a oAuth callback.
* @param $socialDriver
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
* Get the social user details via the social driver.
* @throws SocialDriverNotConfigured
* @throws SocialSignInException
*/
public function handleLoginCallback($socialDriver)
public function getSocialUser(string $socialDriver): SocialUser
{
$driver = $this->validateDriver($socialDriver);
// Get user details from social driver
$socialUser = $this->socialite->driver($driver)->user();
return $this->socialite->driver($driver)->user();
}
/**
* Handle the login process on a oAuth callback.
* @throws SocialSignInAccountNotUsed
*/
public function handleLoginCallback(string $socialDriver, SocialUser $socialUser)
{
$socialId = $socialUser->getId();
// Get any attached social accounts or users
$socialAccount = $this->socialAccount->where('driver_id', '=', $socialId)->first();
$isLoggedIn = auth()->check();
$currentUser = user();
$titleCaseDriver = Str::title($socialDriver);
// When a user is not logged in and a matching SocialAccount exists,
// Simply log the user into the application.
@@ -114,47 +106,45 @@ class SocialAuthService
if ($isLoggedIn && $socialAccount === null) {
$this->fillSocialAccount($socialDriver, $socialUser);
$currentUser->socialAccounts()->save($this->socialAccount);
session()->flash('success', trans('settings.users_social_connected', ['socialAccount' => title_case($socialDriver)]));
session()->flash('success', trans('settings.users_social_connected', ['socialAccount' => $titleCaseDriver]));
return redirect($currentUser->getEditUrl());
}
// When a user is logged in and the social account exists and is already linked to the current user.
if ($isLoggedIn && $socialAccount !== null && $socialAccount->user->id === $currentUser->id) {
session()->flash('error', trans('errors.social_account_existing', ['socialAccount' => title_case($socialDriver)]));
session()->flash('error', trans('errors.social_account_existing', ['socialAccount' => $titleCaseDriver]));
return redirect($currentUser->getEditUrl());
}
// When a user is logged in, A social account exists but the users do not match.
if ($isLoggedIn && $socialAccount !== null && $socialAccount->user->id != $currentUser->id) {
session()->flash('error', trans('errors.social_account_already_used_existing', ['socialAccount' => title_case($socialDriver)]));
session()->flash('error', trans('errors.social_account_already_used_existing', ['socialAccount' => $titleCaseDriver]));
return redirect($currentUser->getEditUrl());
}
// Otherwise let the user know this social account is not used by anyone.
$message = trans('errors.social_account_not_used', ['socialAccount' => title_case($socialDriver)]);
if (setting('registration-enabled')) {
$message .= trans('errors.social_account_register_instructions', ['socialAccount' => title_case($socialDriver)]);
$message = trans('errors.social_account_not_used', ['socialAccount' => $titleCaseDriver]);
if (setting('registration-enabled') && config('auth.method') !== 'ldap' && config('auth.method') !== 'saml2') {
$message .= trans('errors.social_account_register_instructions', ['socialAccount' => $titleCaseDriver]);
}
throw new SocialSignInException($message, '/login');
throw new SocialSignInAccountNotUsed($message, '/login');
}
/**
* Ensure the social driver is correct and supported.
*
* @param $socialDriver
* @return string
* @throws SocialDriverNotConfigured
*/
private function validateDriver($socialDriver)
protected function validateDriver(string $socialDriver): string
{
$driver = trim(strtolower($socialDriver));
if (!in_array($driver, $this->validSocialDrivers)) {
abort(404, trans('errors.social_driver_not_found'));
}
if (!$this->checkDriverConfigured($driver)) {
throw new SocialDriverNotConfigured(trans('errors.social_driver_not_configured', ['socialAccount' => title_case($socialDriver)]));
throw new SocialDriverNotConfigured(trans('errors.social_driver_not_configured', ['socialAccount' => Str::title($socialDriver)]));
}
return $driver;
@@ -162,10 +152,8 @@ class SocialAuthService
/**
* Check a social driver has been configured correctly.
* @param $driver
* @return bool
*/
private function checkDriverConfigured($driver)
protected function checkDriverConfigured(string $driver): bool
{
$lowerName = strtolower($driver);
$configPrefix = 'services.' . $lowerName . '.';
@@ -175,35 +163,48 @@ class SocialAuthService
/**
* Gets the names of the active social drivers.
* @return array
*/
public function getActiveDrivers()
public function getActiveDrivers(): array
{
$activeDrivers = [];
foreach ($this->validSocialDrivers as $driverKey) {
if ($this->checkDriverConfigured($driverKey)) {
$activeDrivers[$driverKey] = $this->getDriverName($driverKey);
}
}
return $activeDrivers;
}
/**
* Get the presentational name for a driver.
* @param $driver
* @return mixed
*/
public function getDriverName($driver)
public function getDriverName(string $driver): string
{
return config('services.' . strtolower($driver) . '.name');
}
/**
* @param string $socialDriver
* @param \Laravel\Socialite\Contracts\User $socialUser
* @return SocialAccount
* Check if the current config for the given driver allows auto-registration.
*/
public function fillSocialAccount($socialDriver, $socialUser)
public function driverAutoRegisterEnabled(string $driver): bool
{
return config('services.' . strtolower($driver) . '.auto_register') === true;
}
/**
* Check if the current config for the given driver allow email address auto-confirmation.
*/
public function driverAutoConfirmEmailEnabled(string $driver): bool
{
return config('services.' . strtolower($driver) . '.auto_confirm') === true;
}
/**
* Fill and return a SocialAccount from the given driver name and SocialUser.
*/
public function fillSocialAccount(string $socialDriver, SocialUser $socialUser): SocialAccount
{
$this->socialAccount->fill([
'driver' => $socialDriver,
@@ -215,13 +216,27 @@ class SocialAuthService
/**
* Detach a social account from a user.
* @param $socialDriver
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function detachSocialAccount($socialDriver)
public function detachSocialAccount(string $socialDriver)
{
user()->socialAccounts()->where('driver', '=', $socialDriver)->delete();
session()->flash('success', trans('settings.users_social_disconnected', ['socialAccount' => title_case($socialDriver)]));
return redirect(user()->getEditUrl());
}
/**
* Provide redirect options per service for the Laravel Socialite driver
*/
public function getSocialDriver(string $driverName): Provider
{
$driver = $this->socialite->driver($driverName);
if ($driverName === 'google' && config('services.google.select_account')) {
$driver->with(['prompt' => 'select_account']);
}
if ($driverName === 'azure') {
$driver->with(['resource' => 'https://graph.windows.net']);
}
return $driver;
}
}

View File

@@ -0,0 +1,22 @@
<?php namespace BookStack\Auth\Access;
use BookStack\Auth\User;
use BookStack\Notifications\UserInvite;
class UserInviteService extends UserTokenService
{
protected $tokenTable = 'user_invites';
protected $expiryTime = 336; // Two weeks
/**
* Send an invitation to a user to sign into BookStack
* Removes existing invitation tokens.
* @param User $user
*/
public function sendInvitation(User $user)
{
$this->deleteByUser($user);
$token = $this->createTokenForUser($user);
$user->notify(new UserInvite($token));
}
}

View File

@@ -0,0 +1,134 @@
<?php namespace BookStack\Auth\Access;
use BookStack\Auth\User;
use BookStack\Exceptions\UserTokenExpiredException;
use BookStack\Exceptions\UserTokenNotFoundException;
use Carbon\Carbon;
use Illuminate\Database\Connection as Database;
use Illuminate\Support\Str;
use stdClass;
class UserTokenService
{
/**
* Name of table where user tokens are stored.
* @var string
*/
protected $tokenTable = 'user_tokens';
/**
* Token expiry time in hours.
* @var int
*/
protected $expiryTime = 24;
protected $db;
/**
* UserTokenService constructor.
* @param Database $db
*/
public function __construct(Database $db)
{
$this->db = $db;
}
/**
* Delete all email confirmations that belong to a user.
* @param User $user
* @return mixed
*/
public function deleteByUser(User $user)
{
return $this->db->table($this->tokenTable)
->where('user_id', '=', $user->id)
->delete();
}
/**
* Get the user id from a token, while check the token exists and has not expired.
* @param string $token
* @return int
* @throws UserTokenNotFoundException
* @throws UserTokenExpiredException
*/
public function checkTokenAndGetUserId(string $token) : int
{
$entry = $this->getEntryByToken($token);
if (is_null($entry)) {
throw new UserTokenNotFoundException('Token "' . $token . '" not found');
}
if ($this->entryExpired($entry)) {
throw new UserTokenExpiredException("Token of id {$entry->id} has expired.", $entry->user_id);
}
return $entry->user_id;
}
/**
* Creates a unique token within the email confirmation database.
* @return string
*/
protected function generateToken() : string
{
$token = Str::random(24);
while ($this->tokenExists($token)) {
$token = Str::random(25);
}
return $token;
}
/**
* Generate and store a token for the given user.
* @param User $user
* @return string
*/
protected function createTokenForUser(User $user) : string
{
$token = $this->generateToken();
$this->db->table($this->tokenTable)->insert([
'user_id' => $user->id,
'token' => $token,
'created_at' => Carbon::now(),
'updated_at' => Carbon::now()
]);
return $token;
}
/**
* Check if the given token exists.
* @param string $token
* @return bool
*/
protected function tokenExists(string $token) : bool
{
return $this->db->table($this->tokenTable)
->where('token', '=', $token)->exists();
}
/**
* Get a token entry for the given token.
* @param string $token
* @return object|null
*/
protected function getEntryByToken(string $token)
{
return $this->db->table($this->tokenTable)
->where('token', '=', $token)
->first();
}
/**
* Check if the given token entry has expired.
* @param stdClass $tokenEntry
* @return bool
*/
protected function entryExpired(stdClass $tokenEntry) : bool
{
return Carbon::now()->subHours($this->expiryTime)
->gt(new Carbon($tokenEntry->created_at));
}
}

View File

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

View File

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

View File

@@ -1,14 +1,14 @@
<?php namespace BookStack\Services;
<?php namespace BookStack\Auth\Permissions;
use BookStack\Book;
use BookStack\Chapter;
use BookStack\Entity;
use BookStack\EntityPermission;
use BookStack\JointPermission;
use BookStack\Auth\Permissions;
use BookStack\Auth\Role;
use BookStack\Entities\Book;
use BookStack\Entities\Bookshelf;
use BookStack\Entities\Chapter;
use BookStack\Entities\Entity;
use BookStack\Entities\EntityProvider;
use BookStack\Entities\Page;
use BookStack\Ownable;
use BookStack\Page;
use BookStack\Role;
use BookStack\User;
use Illuminate\Database\Connection;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\Builder as QueryBuilder;
@@ -22,38 +22,53 @@ class PermissionService
protected $userRoles = false;
protected $currentUserModel = false;
public $book;
public $chapter;
public $page;
/**
* @var Connection
*/
protected $db;
/**
* @var JointPermission
*/
protected $jointPermission;
/**
* @var Role
*/
protected $role;
/**
* @var EntityPermission
*/
protected $entityPermission;
/**
* @var EntityProvider
*/
protected $entityProvider;
protected $entityCache;
/**
* PermissionService constructor.
* @param JointPermission $jointPermission
* @param EntityPermission $entityPermission
* @param Connection $db
* @param Book $book
* @param Chapter $chapter
* @param Page $page
* @param Role $role
* @param Connection $db
* @param EntityProvider $entityProvider
*/
public function __construct(JointPermission $jointPermission, EntityPermission $entityPermission, Connection $db, Book $book, Chapter $chapter, Page $page, Role $role)
{
public function __construct(
JointPermission $jointPermission,
Permissions\EntityPermission $entityPermission,
Role $role,
Connection $db,
EntityProvider $entityProvider
) {
$this->db = $db;
$this->jointPermission = $jointPermission;
$this->entityPermission = $entityPermission;
$this->role = $role;
$this->book = $book;
$this->chapter = $chapter;
$this->page = $page;
// TODO - Update so admin still goes through filters
$this->entityProvider = $entityProvider;
}
/**
@@ -67,13 +82,19 @@ class PermissionService
/**
* Prepare the local entity cache and ensure it's empty
* @param \BookStack\Entities\Entity[] $entities
*/
protected function readyEntityCache()
protected function readyEntityCache($entities = [])
{
$this->entityCache = [
'books' => collect(),
'chapters' => collect()
];
$this->entityCache = [];
foreach ($entities as $entity) {
$type = $entity->getType();
if (!isset($this->entityCache[$type])) {
$this->entityCache[$type] = collect();
}
$this->entityCache[$type]->put($entity->id, $entity);
}
}
/**
@@ -83,17 +104,14 @@ class PermissionService
*/
protected function getBook($bookId)
{
if (isset($this->entityCache['books']) && $this->entityCache['books']->has($bookId)) {
return $this->entityCache['books']->get($bookId);
if (isset($this->entityCache['book']) && $this->entityCache['book']->has($bookId)) {
return $this->entityCache['book']->get($bookId);
}
$book = $this->book->find($bookId);
$book = $this->entityProvider->book->find($bookId);
if ($book === null) {
$book = false;
}
if (isset($this->entityCache['books'])) {
$this->entityCache['books']->put($bookId, $book);
}
return $book;
}
@@ -101,21 +119,18 @@ class PermissionService
/**
* Get a chapter via ID, Checks local cache
* @param $chapterId
* @return Book
* @return \BookStack\Entities\Book
*/
protected function getChapter($chapterId)
{
if (isset($this->entityCache['chapters']) && $this->entityCache['chapters']->has($chapterId)) {
return $this->entityCache['chapters']->get($chapterId);
if (isset($this->entityCache['chapter']) && $this->entityCache['chapter']->has($chapterId)) {
return $this->entityCache['chapter']->get($chapterId);
}
$chapter = $this->chapter->find($chapterId);
$chapter = $this->entityProvider->chapter->find($chapterId);
if ($chapter === null) {
$chapter = false;
}
if (isset($this->entityCache['chapters'])) {
$this->entityCache['chapters']->put($chapterId, $chapter);
}
return $chapter;
}
@@ -159,6 +174,12 @@ class PermissionService
$this->bookFetchQuery()->chunk(5, function ($books) use ($roles) {
$this->buildJointPermissionsForBooks($books, $roles);
});
// Chunk through all bookshelves
$this->entityProvider->bookshelf->newQuery()->select(['id', 'restricted', 'created_by'])
->chunk(50, function ($shelves) use ($roles) {
$this->buildJointPermissionsForShelves($shelves, $roles);
});
}
/**
@@ -167,11 +188,26 @@ class PermissionService
*/
protected function bookFetchQuery()
{
return $this->book->newQuery()->select(['id', 'restricted', 'created_by'])->with(['chapters' => function ($query) {
$query->select(['id', 'restricted', 'created_by', 'book_id']);
}, 'pages' => function ($query) {
$query->select(['id', 'restricted', 'created_by', 'book_id', 'chapter_id']);
}]);
return $this->entityProvider->book->newQuery()
->select(['id', 'restricted', 'created_by'])->with(['chapters' => function ($query) {
$query->select(['id', 'restricted', 'created_by', 'book_id']);
}, 'pages' => function ($query) {
$query->select(['id', 'restricted', 'created_by', 'book_id', 'chapter_id']);
}]);
}
/**
* @param Collection $shelves
* @param array $roles
* @param bool $deleteOld
* @throws \Throwable
*/
protected function buildJointPermissionsForShelves($shelves, $roles, $deleteOld = false)
{
if ($deleteOld) {
$this->deleteManyJointPermissionsForEntities($shelves->all());
}
$this->createManyJointPermissions($shelves, $roles);
}
/**
@@ -202,7 +238,8 @@ class PermissionService
/**
* Rebuild the entity jointPermissions for a particular entity.
* @param Entity $entity
* @param \BookStack\Entities\Entity $entity
* @throws \Throwable
*/
public function buildJointPermissionsForEntity(Entity $entity)
{
@@ -213,7 +250,9 @@ class PermissionService
return;
}
$entities[] = $entity->book;
if ($entity->book) {
$entities[] = $entity->book;
}
if ($entity->isA('page') && $entity->chapter_id) {
$entities[] = $entity->chapter;
@@ -225,13 +264,13 @@ class PermissionService
}
}
$this->deleteManyJointPermissionsForEntities($entities);
$this->buildJointPermissionsForEntities(collect($entities));
}
/**
* Rebuild the entity jointPermissions for a collection of entities.
* @param Collection $entities
* @throws \Throwable
*/
public function buildJointPermissionsForEntities(Collection $entities)
{
@@ -250,9 +289,15 @@ class PermissionService
$this->deleteManyJointPermissionsForRoles($roles);
// Chunk through all books
$this->bookFetchQuery()->chunk(5, function ($books) use ($roles) {
$this->bookFetchQuery()->chunk(20, function ($books) use ($roles) {
$this->buildJointPermissionsForBooks($books, $roles);
});
// Chunk through all bookshelves
$this->entityProvider->bookshelf->newQuery()->select(['id', 'restricted', 'created_by'])
->chunk(50, function ($shelves) use ($roles) {
$this->buildJointPermissionsForShelves($shelves, $roles);
});
}
/**
@@ -279,6 +324,7 @@ class PermissionService
/**
* Delete the entity jointPermissions for a particular entity.
* @param Entity $entity
* @throws \Throwable
*/
public function deleteJointPermissionsForEntity(Entity $entity)
{
@@ -287,7 +333,8 @@ class PermissionService
/**
* Delete all of the entity jointPermissions for a list of entities.
* @param Entity[] $entities
* @param \BookStack\Entities\Entity[] $entities
* @throws \Throwable
*/
protected function deleteManyJointPermissionsForEntities($entities)
{
@@ -314,10 +361,11 @@ class PermissionService
* Create & Save entity jointPermissions for many entities and jointPermissions.
* @param Collection $entities
* @param array $roles
* @throws \Throwable
*/
protected function createManyJointPermissions($entities, $roles)
{
$this->readyEntityCache();
$this->readyEntityCache($entities);
$jointPermissions = [];
// Fetch Entity Permissions and create a mapping of entity restricted statuses
@@ -342,7 +390,7 @@ class PermissionService
// Create a mapping of role permissions
$rolePermissionMap = [];
foreach ($roles as $role) {
foreach ($role->getRelationValue('permissions') as $permission) {
foreach ($role->permissions as $permission) {
$rolePermissionMap[$role->getRawAttribute('id') . ':' . $permission->getRawAttribute('name')] = true;
}
}
@@ -366,7 +414,7 @@ class PermissionService
/**
* Get the actions related to an entity.
* @param Entity $entity
* @param \BookStack\Entities\Entity $entity
* @return array
*/
protected function getActions(Entity $entity)
@@ -408,7 +456,7 @@ class PermissionService
return $this->createJointPermissionDataArray($entity, $role, $action, $hasAccess, $hasAccess);
}
if ($entity->isA('book')) {
if ($entity->isA('book') || $entity->isA('bookshelf')) {
return $this->createJointPermissionDataArray($entity, $role, $action, $roleHasPermission, $roleHasPermissionOwn);
}
@@ -452,7 +500,7 @@ class PermissionService
/**
* Create an array of data with the information of an entity jointPermissions.
* Used to build data for bulk insertion.
* @param Entity $entity
* @param \BookStack\Entities\Entity $entity
* @param Role $role
* @param $action
* @param $permissionAll
@@ -480,11 +528,6 @@ class PermissionService
*/
public function checkOwnableUserAccess(Ownable $ownable, $permission)
{
if ($this->isAdmin()) {
$this->clean();
return true;
}
$explodedPermission = explode('-', $permission);
$baseQuery = $ownable->where('id', '=', $ownable->id);
@@ -512,10 +555,43 @@ class PermissionService
return $q;
}
/**
* Checks if a user has the given permission for any items in the system.
* Can be passed an entity instance to filter on a specific type.
* @param string $permission
* @param string $entityClass
* @return bool
*/
public function checkUserHasPermissionOnAnything(string $permission, string $entityClass = null)
{
$userRoleIds = $this->currentUser()->roles()->select('id')->pluck('id')->toArray();
$userId = $this->currentUser()->id;
$permissionQuery = $this->db->table('joint_permissions')
->where('action', '=', $permission)
->whereIn('role_id', $userRoleIds)
->where(function ($query) use ($userId) {
$query->where('has_permission', '=', 1)
->orWhere(function ($query2) use ($userId) {
$query2->where('has_permission_own', '=', 1)
->where('created_by', '=', $userId);
});
});
if (!is_null($entityClass)) {
$entityInstance = app()->make($entityClass);
$permissionQuery = $permissionQuery->where('entity_type', '=', $entityInstance->getMorphClass());
}
$hasPermission = $permissionQuery->count() > 0;
$this->clean();
return $hasPermission;
}
/**
* Check if an entity has restrictions set on itself or its
* parent tree.
* @param Entity $entity
* @param \BookStack\Entities\Entity $entity
* @param $action
* @return bool|mixed
*/
@@ -557,47 +633,46 @@ class PermissionService
}
/**
* Get the children of a book in an efficient single query, Filtered by the permission system.
* @param integer $book_id
* @param bool $filterDrafts
* @param bool $fetchPageContent
* @return QueryBuilder
* Limited the given entity query so that the query will only
* return items that the user has permission for the given ability.
*/
public function bookChildrenQuery($book_id, $filterDrafts = false, $fetchPageContent = false)
public function restrictEntityQuery(Builder $query, string $ability = 'view'): Builder
{
$pageSelect = $this->db->table('pages')->selectRaw($this->page->entityRawQuery($fetchPageContent))->where('book_id', '=', $book_id)->where(function ($query) use ($filterDrafts) {
$query->where('draft', '=', 0);
if (!$filterDrafts) {
$query->orWhere(function ($query) {
$query->where('draft', '=', 1)->where('created_by', '=', $this->currentUser()->id);
});
}
});
$chapterSelect = $this->db->table('chapters')->selectRaw($this->chapter->entityRawQuery())->where('book_id', '=', $book_id);
$query = $this->db->query()->select('*')->from($this->db->raw("({$pageSelect->toSql()} UNION {$chapterSelect->toSql()}) AS U"))
->mergeBindings($pageSelect)->mergeBindings($chapterSelect);
if (!$this->isAdmin()) {
$whereQuery = $this->db->table('joint_permissions as jp')->selectRaw('COUNT(*)')
->whereRaw('jp.entity_id=U.id')->whereRaw('jp.entity_type=U.entity_type')
->where('jp.action', '=', 'view')->whereIn('jp.role_id', $this->getRoles())
->where(function ($query) {
$query->where('jp.has_permission', '=', 1)->orWhere(function ($query) {
$query->where('jp.has_permission_own', '=', 1)->where('jp.created_by', '=', $this->currentUser()->id);
});
});
$query->whereRaw("({$whereQuery->toSql()}) > 0")->mergeBindings($whereQuery);
}
$query->orderBy('draft', 'desc')->orderBy('priority', 'asc');
$this->clean();
return $query;
return $query->where(function (Builder $parentQuery) use ($ability) {
$parentQuery->whereHas('jointPermissions', function (Builder $permissionQuery) use ($ability) {
$permissionQuery->whereIn('role_id', $this->getRoles())
->where('action', '=', $ability)
->where(function (Builder $query) {
$query->where('has_permission', '=', true)
->orWhere(function (Builder $query) {
$query->where('has_permission_own', '=', true)
->where('created_by', '=', $this->currentUser()->id);
});
});
});
});
}
/**
* Extend the given page query to ensure draft items are not visible
* unless created by the given user.
*/
public function enforceDraftVisiblityOnQuery(Builder $query): Builder
{
return $query->where(function (Builder $query) {
$query->where('draft', '=', false)
->orWhere(function (Builder $query) {
$query->where('draft', '=', true)
->where('created_by', '=', $this->currentUser()->id);
});
});
}
/**
* Add restrictions for a generic entity
* @param string $entityType
* @param Builder|Entity $query
* @param Builder|\BookStack\Entities\Entity $query
* @param string $action
* @return Builder
*/
@@ -606,20 +681,14 @@ class PermissionService
if (strtolower($entityType) === 'page') {
// Prevent drafts being visible to others.
$query = $query->where(function ($query) {
$query->where('draft', '=', false);
if ($this->currentUser()) {
$query->orWhere(function ($query) {
$query->where('draft', '=', true)->where('created_by', '=', $this->currentUser()->id);
$query->where('draft', '=', false)
->orWhere(function ($query) {
$query->where('draft', '=', true)
->where('created_by', '=', $this->currentUser()->id);
});
}
});
}
if ($this->isAdmin()) {
$this->clean();
return $query;
}
$this->currentAction = $action;
return $this->entityRestrictionQuery($query);
}
@@ -630,16 +699,13 @@ class PermissionService
* @param string $tableName
* @param string $entityIdColumn
* @param string $entityTypeColumn
* @return mixed
* @param string $action
* @return QueryBuilder
*/
public function filterRestrictedEntityRelations($query, $tableName, $entityIdColumn, $entityTypeColumn)
public function filterRestrictedEntityRelations($query, $tableName, $entityIdColumn, $entityTypeColumn, $action = 'view')
{
if ($this->isAdmin()) {
$this->clean();
return $query;
}
$this->currentAction = 'view';
$this->currentAction = $action;
$tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn, 'entityTypeColumn' => $entityTypeColumn];
$q = $query->where(function ($query) use ($tableDetails) {
@@ -662,28 +728,27 @@ class PermissionService
}
/**
* Filters pages that are a direct relation to another item.
* Add conditions to a query to filter the selection to related entities
* where permissions are granted.
* @param $entityType
* @param $query
* @param $tableName
* @param $entityIdColumn
* @return mixed
*/
public function filterRelatedPages($query, $tableName, $entityIdColumn)
public function filterRelatedEntity($entityType, $query, $tableName, $entityIdColumn)
{
if ($this->isAdmin()) {
$this->clean();
return $query;
}
$this->currentAction = 'view';
$tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn];
$q = $query->where(function ($query) use ($tableDetails) {
$query->where(function ($query) use (&$tableDetails) {
$query->whereExists(function ($permissionQuery) use (&$tableDetails) {
$pageMorphClass = $this->entityProvider->get($entityType)->getMorphClass();
$q = $query->where(function ($query) use ($tableDetails, $pageMorphClass) {
$query->where(function ($query) use (&$tableDetails, $pageMorphClass) {
$query->whereExists(function ($permissionQuery) use (&$tableDetails, $pageMorphClass) {
$permissionQuery->select('id')->from('joint_permissions')
->whereRaw('joint_permissions.entity_id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
->where('entity_type', '=', 'Bookstack\\Page')
->where('entity_type', '=', $pageMorphClass)
->where('action', '=', $this->currentAction)
->whereIn('role_id', $this->getRoles())
->where(function ($query) {
@@ -695,26 +760,15 @@ class PermissionService
});
})->orWhere($tableDetails['entityIdColumn'], '=', 0);
});
$this->clean();
return $q;
}
/**
* Check if the current user is an admin.
* @return bool
*/
private function isAdmin()
{
if ($this->isAdminUser === null) {
$this->isAdminUser = ($this->currentUser()->id !== null) ? $this->currentUser()->hasSystemRole('admin') : false;
}
return $this->isAdminUser;
}
/**
* Get the current user
* @return User
* @return \BookStack\Auth\User
*/
private function currentUser()
{

View File

@@ -1,10 +1,9 @@
<?php namespace BookStack\Repos;
<?php namespace BookStack\Auth\Permissions;
use BookStack\Auth\Permissions;
use BookStack\Auth\Role;
use BookStack\Exceptions\PermissionsException;
use BookStack\RolePermission;
use BookStack\Role;
use BookStack\Services\PermissionService;
use Setting;
use Illuminate\Support\Str;
class PermissionsRepo
{
@@ -19,9 +18,9 @@ class PermissionsRepo
* PermissionsRepo constructor.
* @param RolePermission $permission
* @param Role $role
* @param PermissionService $permissionService
* @param \BookStack\Auth\Permissions\PermissionService $permissionService
*/
public function __construct(RolePermission $permission, Role $role, PermissionService $permissionService)
public function __construct(RolePermission $permission, Role $role, Permissions\PermissionService $permissionService)
{
$this->permission = $permission;
$this->role = $role;
@@ -68,7 +67,7 @@ class PermissionsRepo
$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->name .= strtolower(Str::random(2));
}
$role->save();
@@ -80,7 +79,7 @@ class PermissionsRepo
/**
* Updates an existing role.
* Ensure Admin role always has all permissions.
* Ensure Admin role always have core permissions.
* @param $roleId
* @param $roleData
* @throws PermissionsException
@@ -90,13 +89,18 @@ class PermissionsRepo
$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);
$permissions = array_merge($permissions, [
'users-manage',
'user-roles-manage',
'restrictions-manage-all',
'restrictions-manage-own',
'settings-manage',
]);
}
$this->assignRolePermissions($role, $permissions);
$role->fill($roleData);
$role->save();
$this->permissionService->buildJointPermissionForRole($role);
@@ -133,7 +137,7 @@ class PermissionsRepo
// 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')) {
} else if ($role->id === intval(setting('registration-role'))) {
throw new PermissionsException(trans('errors.role_registration_default_cannot_delete'));
}

View File

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

View File

@@ -1,16 +1,27 @@
<?php namespace BookStack;
<?php namespace BookStack\Auth;
use BookStack\Auth\Permissions\JointPermission;
use BookStack\Auth\Permissions\RolePermission;
use BookStack\Model;
/**
* Class Role
* @property string $display_name
* @property string $description
* @property string $external_auth_id
* @package BookStack\Auth
*/
class Role extends Model
{
protected $fillable = ['display_name', 'description'];
protected $fillable = ['display_name', 'description', 'external_auth_id'];
/**
* The roles that belong to the role.
*/
public function users()
{
return $this->belongsToMany(User::class);
return $this->belongsToMany(User::class)->orderBy('name', 'asc');
}
/**
@@ -61,7 +72,7 @@ class Role extends Model
*/
public function detachPermission(RolePermission $permission)
{
$this->permissions()->detach($permission->id);
$this->permissions()->detach([$permission->id]);
}
/**
@@ -71,7 +82,7 @@ class Role extends Model
*/
public static function getRole($roleName)
{
return static::where('name', '=', $roleName)->first();
return static::query()->where('name', '=', $roleName)->first();
}
/**
@@ -81,7 +92,7 @@ class Role extends Model
*/
public static function getSystemRole($roleName)
{
return static::where('system_name', '=', $roleName)->first();
return static::query()->where('system_name', '=', $roleName)->first();
}
/**
@@ -90,6 +101,15 @@ class Role extends Model
*/
public static function visible()
{
return static::where('hidden', '=', false)->orderBy('name')->get();
return static::query()->where('hidden', '=', false)->orderBy('name')->get();
}
/**
* Get the roles that can be restricted.
* @return \Illuminate\Database\Eloquent\Builder[]|\Illuminate\Database\Eloquent\Collection
*/
public static function restrictable()
{
return static::query()->where('system_name', '!=', 'admin')->get();
}
}

View File

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

View File

@@ -1,13 +1,32 @@
<?php namespace BookStack;
<?php namespace BookStack\Auth;
use BookStack\Api\ApiToken;
use BookStack\Model;
use BookStack\Notifications\ResetPassword;
use BookStack\Uploads\Image;
use Carbon\Carbon;
use Illuminate\Auth\Authenticatable;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Notifications\Notifiable;
/**
* Class User
* @package BookStack\Auth
* @property string $id
* @property string $name
* @property string $email
* @property string $password
* @property Carbon $created_at
* @property Carbon $updated_at
* @property bool $email_confirmed
* @property int $image_id
* @property string $external_auth_id
* @property string $system_name
*/
class User extends Model implements AuthenticatableContract, CanResetPasswordContract
{
use Authenticatable, CanResetPassword, Notifiable;
@@ -22,13 +41,16 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
* The attributes that are mass assignable.
* @var array
*/
protected $fillable = ['name', 'email', 'image_id'];
protected $fillable = ['name', 'email'];
/**
* The attributes excluded from the model's JSON form.
* @var array
*/
protected $hidden = ['password', 'remember_token'];
protected $hidden = [
'password', 'remember_token', 'system_name', 'email_confirmed', 'external_auth_id', 'email',
'created_at', 'updated_at',
];
/**
* This holds the user's permissions when loaded.
@@ -36,13 +58,24 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
*/
protected $permissions;
/**
* This holds the default user when loaded.
* @var null|User
*/
protected static $defaultUser = null;
/**
* Returns the default public user.
* @return User
*/
public static function getDefault()
{
return static::where('system_name', '=', 'public')->first();
if (!is_null(static::$defaultUser)) {
return static::$defaultUser;
}
static::$defaultUser = static::where('system_name', '=', 'public')->first();
return static::$defaultUser;
}
/**
@@ -86,6 +119,17 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
return $this->roles->pluck('system_name')->contains($role);
}
/**
* Attach the default system role to this user.
*/
public function attachDefaultRole(): void
{
$roleId = setting('registration-role');
if ($roleId && $this->roles()->where('id', '=', $roleId)->count() === 0) {
$this->roles()->attach($roleId);
}
}
/**
* Get all permissions belonging to a the current user.
* @param bool $cache
@@ -123,16 +167,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
*/
public function attachRole(Role $role)
{
$this->attachRoleId($role->id);
}
/**
* Attach a role id to this user.
* @param $id
*/
public function attachRoleId($id)
{
$this->roles()->attach($id);
$this->roles()->attach($role->id);
}
/**
@@ -166,14 +201,14 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
*/
public function getAvatar($size = 50)
{
$default = baseUrl('/user_avatar.png');
$default = url('/user_avatar.png');
$imageId = $this->image_id;
if ($imageId === 0 || $imageId === '0' || $imageId === null) {
return $default;
}
try {
$avatar = $this->avatar ? baseUrl($this->avatar->getThumb($size, $size, false)) : $default;
$avatar = $this->avatar ? url($this->avatar->getThumb($size, $size, false)) : $default;
} catch (\Exception $err) {
$avatar = $default;
}
@@ -190,21 +225,28 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
}
/**
* Get the url for editing this user.
* @return string
* Get the API tokens assigned to this user.
*/
public function getEditUrl()
public function apiTokens(): HasMany
{
return baseUrl('/settings/users/' . $this->id);
return $this->hasMany(ApiToken::class);
}
/**
* Get the url for editing this user.
*/
public function getEditUrl(string $path = ''): string
{
$uri = '/settings/users/' . $this->id . '/' . trim($path, '/');
return url(rtrim($uri, '/'));
}
/**
* Get the url that links to this user's profile.
* @return mixed
*/
public function getProfileUrl()
public function getProfileUrl(): string
{
return baseUrl('/user/' . $this->id);
return url('/user/' . $this->id);
}
/**
@@ -214,12 +256,12 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
*/
public function getShortName($chars = 8)
{
if (strlen($this->name) <= $chars) {
if (mb_strlen($this->name) <= $chars) {
return $this->name;
}
$splitName = explode(' ', $this->name);
if (strlen($splitName[0]) <= $chars) {
if (mb_strlen($splitName[0]) <= $chars) {
return $splitName[0];
}

View File

@@ -1,38 +1,37 @@
<?php namespace BookStack\Repos;
<?php namespace BookStack\Auth;
use Activity;
use BookStack\Entities\Book;
use BookStack\Entities\Bookshelf;
use BookStack\Entities\Chapter;
use BookStack\Entities\Page;
use BookStack\Exceptions\NotFoundException;
use BookStack\Image;
use BookStack\Role;
use BookStack\User;
use BookStack\Exceptions\UserUpdateException;
use BookStack\Uploads\Image;
use Exception;
use Illuminate\Database\Eloquent\Builder;
use Images;
use Log;
class UserRepo
{
protected $user;
protected $role;
protected $entityRepo;
/**
* UserRepo constructor.
* @param User $user
* @param Role $role
* @param EntityRepo $entityRepo
*/
public function __construct(User $user, Role $role, EntityRepo $entityRepo)
public function __construct(User $user, Role $role)
{
$this->user = $user;
$this->role = $role;
$this->entityRepo = $entityRepo;
}
/**
* @param string $email
* @return User|null
* Get a user by their email address.
*/
public function getByEmail($email)
public function getByEmail(string $email): ?User
{
return $this->user->where('email', '=', $email)->first();
}
@@ -43,12 +42,12 @@ class UserRepo
*/
public function getById($id)
{
return $this->user->findOrFail($id);
return $this->user->newQuery()->findOrFail($id);
}
/**
* Get all the users with their permissions.
* @return \Illuminate\Database\Eloquent\Builder|static
* @return Builder|static
*/
public function getAllUsers()
{
@@ -59,7 +58,7 @@ class UserRepo
* Get all the users with their permissions in a paginated format.
* @param int $count
* @param $sortData
* @return \Illuminate\Database\Eloquent\Builder|static
* @return Builder|static
*/
public function getAllUsersPaginatedAndSorted($count, $sortData)
{
@@ -76,35 +75,18 @@ class UserRepo
return $query->paginate($count);
}
/**
/**
* Creates a new user and attaches a role to them.
* @param array $data
* @return User
*/
public function registerNew(array $data)
public function registerNew(array $data, bool $emailConfirmed = false): User
{
$user = $this->create($data);
$this->attachDefaultRole($user);
// Get avatar from gravatar and save
$this->downloadGravatarToUserAvatar($user);
$user = $this->create($data, $emailConfirmed);
$user->attachDefaultRole();
$this->downloadAndAssignUserAvatar($user);
return $user;
}
/**
* Give a user the default role. Used when creating a new user.
* @param $user
*/
public function attachDefaultRole($user)
{
$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
@@ -139,17 +121,50 @@ class UserRepo
}
/**
* Create a new basic instance of user.
* @param array $data
* @return User
* Set the assigned user roles via an array of role IDs.
* @param User $user
* @param array $roles
* @throws UserUpdateException
*/
public function create(array $data)
public function setUserRoles(User $user, array $roles)
{
if ($this->demotingLastAdmin($user, $roles)) {
throw new UserUpdateException(trans('errors.role_cannot_remove_only_admin'), $user->getEditUrl());
}
$user->roles()->sync($roles);
}
/**
* Check if the given user is the last admin and their new roles no longer
* contains the admin role.
* @param User $user
* @param array $newRoles
* @return bool
*/
protected function demotingLastAdmin(User $user, array $newRoles) : bool
{
if ($this->isOnlyAdmin($user)) {
$adminRole = $this->role->getSystemRole('admin');
if (!in_array(strval($adminRole->id), $newRoles)) {
return true;
}
}
return false;
}
/**
* Create a new basic instance of user.
*/
public function create(array $data, bool $emailConfirmed = false): User
{
return $this->user->forceCreate([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
'email_confirmed' => false
'email_confirmed' => $emailConfirmed,
'external_auth_id' => $data['external_auth_id'] ?? '',
]);
}
@@ -161,12 +176,13 @@ class UserRepo
public function destroy(User $user)
{
$user->socialAccounts()->delete();
$user->apiTokens()->delete();
$user->delete();
// Delete user profile images
$profileImages = $images = Image::where('type', '=', 'user')->where('created_by', '=', $user->id)->get();
$profileImages = Image::where('type', '=', 'user')->where('uploaded_to', '=', $user->id)->get();
foreach ($profileImages as $image) {
Images::destroyImage($image);
Images::destroy($image);
}
}
@@ -184,36 +200,35 @@ class UserRepo
/**
* Get the recently created content for this given user.
* @param User $user
* @param int $count
* @return mixed
*/
public function getRecentlyCreated(User $user, $count = 20)
public function getRecentlyCreated(User $user, int $count = 20): array
{
$query = function (Builder $query) use ($user, $count) {
return $query->orderBy('created_at', 'desc')
->where('created_by', '=', $user->id)
->take($count)
->get();
};
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);
})
'pages' => $query(Page::visible()->where('draft', '=', false)),
'chapters' => $query(Chapter::visible()),
'books' => $query(Book::visible()),
'shelves' => $query(Bookshelf::visible()),
];
}
/**
* Get asset created counts for the give user.
* @param User $user
* @return array
*/
public function getAssetCounts(User $user)
public function getAssetCounts(User $user): array
{
$createdBy = ['created_by' => $user->id];
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(),
'pages' => Page::visible()->where($createdBy)->count(),
'chapters' => Chapter::visible()->where($createdBy)->count(),
'books' => Book::visible()->where($createdBy)->count(),
'shelves' => Bookshelf::visible()->where($createdBy)->count(),
];
}
@@ -223,39 +238,28 @@ class UserRepo
*/
public function getAllRoles()
{
return $this->role->all();
return $this->role->newQuery()->orderBy('name', 'asc')->get();
}
/**
* 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.
* Get an avatar image for a user and set it as their avatar.
* Returns early if avatars disabled or not set in config.
* @param User $user
* @return bool
*/
public function downloadGravatarToUserAvatar(User $user)
public function downloadAndAssignUserAvatar(User $user)
{
// Get avatar from gravatar and save
if (!config('services.gravatar')) {
if (!Images::avatarFetchEnabled()) {
return false;
}
try {
$avatar = Images::saveUserGravatar($user);
$avatar = Images::saveUserAvatar($user);
$user->avatar()->associate($avatar);
$user->save();
return true;
} catch (Exception $e) {
\Log::error('Failed to save user gravatar image');
Log::error('Failed to save user avatar image');
return false;
}
}

View File

@@ -1,97 +0,0 @@
<?php namespace BookStack;
class Book extends Entity
{
public $searchFactor = 2;
protected $fillable = ['name', 'description', 'image_id'];
/**
* Get the url for this book.
* @param string|bool $path
* @return string
*/
public function getUrl($path = false)
{
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(Page::class);
}
/**
* Get all chapters within this book.
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function chapters()
{
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)
{
$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,63 +0,0 @@
<?php namespace BookStack;
class Chapter extends Entity
{
public $searchFactor = 1.3;
protected $fillable = ['name', 'description', 'priority', 'book_id'];
protected $with = ['book'];
/**
* Get the book this chapter is within.
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function book()
{
return $this->belongsTo(Book::class);
}
/**
* Get the pages that this chapter contains.
* @param string $dir
* @return mixed
*/
public function pages($dir = 'ASC')
{
return $this->hasMany(Page::class)->orderBy('priority', $dir);
}
/**
* 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;
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)
{
$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";
}
}

23
app/Config/api.php Normal file
View File

@@ -0,0 +1,23 @@
<?php
/**
* API configuration options.
*
* Changes to these config files are not supported by BookStack and may break upon updates.
* Configuration should be altered via the `.env` file or environment variables.
* Do not edit this file unless you're happy to maintain any changes yourself.
*/
return [
// The default number of items that are returned in listing API requests.
// This count can often be overridden, up the the max option, per-request via request options.
'default_item_count' => env('API_DEFAULT_ITEM_COUNT', 100),
// The maximum number of items that can be returned in a listing API request.
'max_item_count' => env('API_MAX_ITEM_COUNT', 500),
// The number of API requests that can be made per minute by a single user.
'requests_per_minute' => env('API_REQUESTS_PER_MIN', 180)
];

View File

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

76
app/Config/auth.php Normal file
View File

@@ -0,0 +1,76 @@
<?php
/**
* Authentication configuration options.
*
* Changes to these config files are not supported by BookStack and may break upon updates.
* Configuration should be altered via the `.env` file or environment variables.
* Do not edit this file unless you're happy to maintain any changes yourself.
*/
return [
// Method of authentication to use
// Options: standard, ldap, saml2
'method' => env('AUTH_METHOD', 'standard'),
// Authentication Defaults
// This option controls the default authentication "guard" and password
// reset options for your application.
'defaults' => [
'guard' => env('AUTH_METHOD', 'standard'),
'passwords' => 'users',
],
// Authentication Guards
// All authentication drivers have a user provider. This defines how the
// users are actually retrieved out of your database or other storage
// mechanisms used by this application to persist your user's data.
// Supported drivers: "session", "api-token", "ldap-session"
'guards' => [
'standard' => [
'driver' => 'session',
'provider' => 'users',
],
'ldap' => [
'driver' => 'ldap-session',
'provider' => 'external',
],
'saml2' => [
'driver' => 'saml2-session',
'provider' => 'external',
],
'api' => [
'driver' => 'api-token',
],
],
// User Providers
// All authentication drivers have a user provider. This defines how the
// users are actually retrieved out of your database or other storage
// mechanisms used by this application to persist your user's data.
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => \BookStack\Auth\User::class,
],
'external' => [
'driver' => 'external-users',
'model' => \BookStack\Auth\User::class,
],
],
// Resetting Passwords
// The expire time is the number of minutes that the reset token should be
// considered valid. This security feature keeps tokens short-lived so
// they have less time to be guessed. You may change this as needed.
'passwords' => [
'users' => [
'provider' => 'users',
'email' => 'emails.password',
'table' => 'password_resets',
'expire' => 60,
],
],
];

View File

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

71
app/Config/cache.php Normal file
View File

@@ -0,0 +1,71 @@
<?php
/**
* Caching configuration options.
*
* Changes to these config files are not supported by BookStack and may break upon updates.
* Configuration should be altered via the `.env` file or environment variables.
* Do not edit this file unless you're happy to maintain any changes yourself.
*/
// MEMCACHED - Split out configuration into an array
if (env('CACHE_DRIVER') === 'memcached') {
$memcachedServerKeys = ['host', 'port', 'weight'];
$memcachedServers = explode(',', trim(env('MEMCACHED_SERVERS', '127.0.0.1:11211:100'), ','));
foreach ($memcachedServers as $index => $memcachedServer) {
$memcachedServerDetails = explode(':', $memcachedServer);
if (count($memcachedServerDetails) < 2) {
$memcachedServerDetails[] = '11211';
}
if (count($memcachedServerDetails) < 3) {
$memcachedServerDetails[] = '100';
}
$memcachedServers[$index] = array_combine($memcachedServerKeys, $memcachedServerDetails);
}
}
return [
// Default cache store to use
// Can be overridden at cache call-time
'default' => env('CACHE_DRIVER', 'file'),
// Available caches stores
'stores' => [
'apc' => [
'driver' => 'apc',
],
'array' => [
'driver' => 'array',
],
'database' => [
'driver' => 'database',
'table' => 'cache',
'connection' => null,
],
'file' => [
'driver' => 'file',
'path' => storage_path('framework/cache'),
],
'memcached' => [
'driver' => 'memcached',
'servers' => env('CACHE_DRIVER') === 'memcached' ? $memcachedServers : [],
],
'redis' => [
'driver' => 'redis',
'connection' => 'default',
],
],
// Cache key prefix
// Used to prevent collisions in shared cache systems.
'prefix' => env('CACHE_PREFIX', 'bookstack_cache'),
];

106
app/Config/database.php Normal file
View File

@@ -0,0 +1,106 @@
<?php
/**
* Database configuration options.
*
* Changes to these config files are not supported by BookStack and may break upon updates.
* Configuration should be altered via the `.env` file or environment variables.
* Do not edit this file unless you're happy to maintain any changes yourself.
*/
// REDIS
// Split out configuration into an array
if (env('REDIS_SERVERS', false)) {
$redisDefaults = ['host' => '127.0.0.1', 'port' => '6379', 'database' => '0', 'password' => null];
$redisServers = explode(',', trim(env('REDIS_SERVERS', '127.0.0.1:6379:0'), ','));
$redisConfig = ['client' => 'predis'];
$cluster = count($redisServers) > 1;
if ($cluster) {
$redisConfig['clusters'] = ['default' => []];
}
foreach ($redisServers as $index => $redisServer) {
$redisServerDetails = explode(':', $redisServer);
$serverConfig = [];
$configIndex = 0;
foreach ($redisDefaults as $configKey => $configDefault) {
$serverConfig[$configKey] = ($redisServerDetails[$configIndex] ?? $configDefault);
$configIndex++;
}
if ($cluster) {
$redisConfig['clusters']['default'][] = $serverConfig;
} else {
$redisConfig['default'] = $serverConfig;
}
}
}
// MYSQL
// Split out port from host if set
$mysql_host = env('DB_HOST', 'localhost');
$mysql_host_exploded = explode(':', $mysql_host);
$mysql_port = env('DB_PORT', 3306);
if (count($mysql_host_exploded) > 1) {
$mysql_host = $mysql_host_exploded[0];
$mysql_port = intval($mysql_host_exploded[1]);
}
return [
// Default database connection name.
// Options: mysql, mysql_testing
'default' => env('DB_CONNECTION', 'mysql'),
// Available database connections
// Many of those shown here are unsupported by BookStack.
'connections' => [
'mysql' => [
'driver' => 'mysql',
'url' => env('DATABASE_URL'),
'host' => $mysql_host,
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'port' => $mysql_port,
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => false,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
'mysql_testing' => [
'driver' => 'mysql',
'url' => env('TEST_DATABASE_URL'),
'host' => '127.0.0.1',
'database' => 'bookstack-test',
'username' => env('MYSQL_USER', 'bookstack-test'),
'password' => env('MYSQL_PASSWORD', 'bookstack-test'),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => false,
],
],
// Migration Repository Table
// This table keeps track of all the migrations that have already run for
// your application. Using this information, we can determine which of
// the migrations on disk haven't actually been run in the database.
'migrations' => 'migrations',
// Redis configuration to use if set
'redis' => env('REDIS_SERVERS', false) ? $redisConfig : [],
];

133
app/Config/debugbar.php Normal file
View File

@@ -0,0 +1,133 @@
<?php
/**
* Debugbar Configuration Options
*
* Changes to these config files are not supported by BookStack and may break upon updates.
* Configuration should be altered via the `.env` file or environment variables.
* Do not edit this file unless you're happy to maintain any changes yourself.
*/
return [
// Debugbar is enabled by default, when debug is set to true in app.php.
// You can override the value by setting enable to true or false instead of null.
//
// You can provide an array of URI's that must be ignored (eg. 'api/*')
'enabled' => env('DEBUGBAR_ENABLED', false),
'except' => [
'telescope*'
],
// DebugBar stores data for session/ajax requests.
// You can disable this, so the debugbar stores data in headers/session,
// but this can cause problems with large data collectors.
// By default, file storage (in the storage folder) is used. Redis and PDO
// can also be used. For PDO, run the package migrations first.
'storage' => [
'enabled' => true,
'driver' => 'file', // redis, file, pdo, custom
'path' => storage_path('debugbar'), // For file driver
'connection' => null, // Leave null for default connection (Redis/PDO)
'provider' => '' // Instance of StorageInterface for custom driver
],
// Vendor files are included by default, but can be set to false.
// This can also be set to 'js' or 'css', to only include javascript or css vendor files.
// Vendor files are for css: font-awesome (including fonts) and highlight.js (css files)
// and for js: jquery and and highlight.js
// So if you want syntax highlighting, set it to true.
// jQuery is set to not conflict with existing jQuery scripts.
'include_vendors' => true,
// The Debugbar can capture Ajax requests and display them. If you don't want this (ie. because of errors),
// you can use this option to disable sending the data through the headers.
// Optionally, you can also send ServerTiming headers on ajax requests for the Chrome DevTools.
'capture_ajax' => true,
'add_ajax_timing' => false,
// When enabled, the Debugbar shows deprecated warnings for Symfony components
// in the Messages tab.
'error_handler' => false,
// The Debugbar can emulate the Clockwork headers, so you can use the Chrome
// Extension, without the server-side code. It uses Debugbar collectors instead.
'clockwork' => false,
// Enable/disable DataCollectors
'collectors' => [
'phpinfo' => true, // Php version
'messages' => true, // Messages
'time' => true, // Time Datalogger
'memory' => true, // Memory usage
'exceptions' => true, // Exception displayer
'log' => true, // Logs from Monolog (merged in messages if enabled)
'db' => true, // Show database (PDO) queries and bindings
'views' => true, // Views with their data
'route' => true, // Current route information
'auth' => true, // Display Laravel authentication status
'gate' => true, // Display Laravel Gate checks
'session' => true, // Display session data
'symfony_request' => true, // Only one can be enabled..
'mail' => true, // Catch mail messages
'laravel' => false, // Laravel version and environment
'events' => false, // All events fired
'default_request' => false, // Regular or special Symfony request logger
'logs' => false, // Add the latest log messages
'files' => false, // Show the included files
'config' => false, // Display config settings
'cache' => false, // Display cache events
'models' => true, // Display models
],
// Configure some DataCollectors
'options' => [
'auth' => [
'show_name' => true, // Also show the users name/email in the debugbar
],
'db' => [
'with_params' => true, // Render SQL with the parameters substituted
'backtrace' => true, // Use a backtrace to find the origin of the query in your files.
'timeline' => false, // Add the queries to the timeline
'explain' => [ // Show EXPLAIN output on queries
'enabled' => false,
'types' => ['SELECT'], // ['SELECT', 'INSERT', 'UPDATE', 'DELETE']; for MySQL 5.6.3+
],
'hints' => true, // Show hints for common mistakes
],
'mail' => [
'full_log' => false
],
'views' => [
'data' => false, //Note: Can slow down the application, because the data can be quite large..
],
'route' => [
'label' => true // show complete route on bar
],
'logs' => [
'file' => null
],
'cache' => [
'values' => true // collect cache values
],
],
// Inject Debugbar into the response
// Usually, the debugbar is added just before </body>, by listening to the
// Response after the App is done. If you disable this, you have to add them
// in your template yourself. See http://phpdebugbar.com/docs/rendering.html
'inject' => true,
// DebugBar route prefix
// Sometimes you want to set route prefix to be used by DebugBar to load
// its resources from. Usually the need comes from misconfigured web server or
// from trying to overcome bugs like this: http://trac.nginx.org/nginx/ticket/97
'route_prefix' => '_debugbar',
// DebugBar route domain
// By default DebugBar route served from the same domain that request served.
// To override default domain, specify it as a non-empty value.
'route_domain' => env('APP_URL', '') === 'http://bookstack.dev' ? '' : env('APP_URL', ''),
];

View File

@@ -1,16 +1,16 @@
<?php
/**
* DOMPDF configuration options.
*
* Changes to these config files are not supported by BookStack and may break upon updates.
* Configuration should be altered via the `.env` file or environment variables.
* Do not edit this file unless you're happy to maintain any changes yourself.
*/
return [
/*
|--------------------------------------------------------------------------
| Settings
|--------------------------------------------------------------------------
|
| Set some default values. It is possible to add all defines that can be set
| in dompdf_config.inc.php. You can also override the entire config file.
|
*/
'show_warnings' => false, // Throw an Exception on warnings from dompdf
'orientation' => 'portrait',
'defines' => [
@@ -69,7 +69,7 @@ return [
* should be an absolute path.
* This is only checked on command line call by dompdf.php, but not by
* direct class use like:
* $dompdf = new DOMPDF(); $dompdf->load_html($htmldata); $dompdf->render(); $pdfdata = $dompdf->output();
* $dompdf = new DOMPDF(); $dompdf->load_html($htmldata); $dompdf->render(); $pdfdata = $dompdf->output();
*/
"DOMPDF_CHROOT" => realpath(base_path()),

View File

@@ -0,0 +1,74 @@
<?php
/**
* Filesystem configuration options.
*
* Changes to these config files are not supported by BookStack and may break upon updates.
* Configuration should be altered via the `.env` file or environment variables.
* Do not edit this file unless you're happy to maintain any changes yourself.
*/
return [
// Default Filesystem Disk
// Options: local, local_secure, s3
'default' => env('STORAGE_TYPE', 'local'),
// Filesystem to use specifically for image uploads.
'images' => env('STORAGE_IMAGE_TYPE', env('STORAGE_TYPE', 'local')),
// Filesystem to use specifically for file attachments.
'attachments' => env('STORAGE_ATTACHMENT_TYPE', env('STORAGE_TYPE', 'local')),
// Storage URL
// This is the url to where the storage is located for when using an external
// file storage service, such as s3, to store publicly accessible assets.
'url' => env('STORAGE_URL', false),
// Default Cloud Filesystem Disk
'cloud' => 's3',
// Available filesystem disks
// Only local, local_secure & s3 are supported by BookStack
'disks' => [
'local' => [
'driver' => 'local',
'root' => public_path(),
],
'local_secure' => [
'driver' => 'local',
'root' => storage_path(),
],
'ftp' => [
'driver' => 'ftp',
'host' => 'ftp.example.com',
'username' => 'your-username',
'password' => 'your-password',
],
's3' => [
'driver' => 's3',
'key' => env('STORAGE_S3_KEY', 'your-key'),
'secret' => env('STORAGE_S3_SECRET', 'your-secret'),
'region' => env('STORAGE_S3_REGION', 'your-region'),
'bucket' => env('STORAGE_S3_BUCKET', 'your-bucket'),
'endpoint' => env('STORAGE_S3_ENDPOINT', null),
'use_path_style_endpoint' => env('STORAGE_S3_ENDPOINT', null) !== null,
],
'rackspace' => [
'driver' => 'rackspace',
'username' => 'your-username',
'key' => 'your-key',
'container' => 'your-container',
'endpoint' => 'https://identity.api.rackspacecloud.com/v2.0/',
'region' => 'IAD',
'url_type' => 'publicURL',
],
],
];

37
app/Config/hashing.php Normal file
View File

@@ -0,0 +1,37 @@
<?php
/**
* Hashing configuration options.
*
* Changes to these config files are not supported by BookStack and may break upon updates.
* Configuration should be altered via the `.env` file or environment variables.
* Do not edit this file unless you're happy to maintain any changes yourself.
*/
return [
// Default Hash Driver
// This option controls the default hash driver that will be used to hash
// passwords for your application. By default, the bcrypt algorithm is used.
// Supported: "bcrypt", "argon", "argon2id"
'driver' => 'bcrypt',
// Bcrypt Options
// Here you may specify the configuration options that should be used when
// passwords are hashed using the Bcrypt algorithm. This will allow you
// to control the amount of time it takes to hash the given password.
'bcrypt' => [
'rounds' => env('BCRYPT_ROUNDS', 10),
],
// Argon Options
// Here you may specify the configuration options that should be used when
// passwords are hashed using the Argon algorithm. These will allow you
// to control the amount of time it takes to hash the given password.
'argon' => [
'memory' => 1024,
'threads' => 2,
'time' => 2,
],
];

82
app/Config/logging.php Normal file
View File

@@ -0,0 +1,82 @@
<?php
use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler;
/**
* Logging configuration options.
*
* Changes to these config files are not supported by BookStack and may break upon updates.
* Configuration should be altered via the `.env` file or environment variables.
* Do not edit this file unless you're happy to maintain any changes yourself.
*/
return [
// Default Log Channel
// This option defines the default log channel that gets used when writing
// messages to the logs. The name specified in this option should match
// one of the channels defined in the "channels" configuration array.
'default' => env('LOG_CHANNEL', 'single'),
// Log Channels
// Here you may configure the log channels for your application. Out of
// the box, Laravel uses the Monolog PHP logging library. This gives
// you a variety of powerful log handlers / formatters to utilize.
// Available Drivers: "single", "daily", "slack", "syslog",
// "errorlog", "monolog",
// "custom", "stack"
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['daily'],
'ignore_exceptions' => false,
],
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
'days' => 14,
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
'days' => 7,
],
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'Laravel Log',
'emoji' => ':boom:',
'level' => 'critical',
],
'stderr' => [
'driver' => 'monolog',
'handler' => StreamHandler::class,
'with' => [
'stream' => 'php://stderr',
],
],
'syslog' => [
'driver' => 'syslog',
'level' => 'debug',
],
'errorlog' => [
'driver' => 'errorlog',
'level' => 'debug',
],
'null' => [
'driver' => 'monolog',
'handler' => NullHandler::class,
],
],
];

55
app/Config/mail.php Normal file
View File

@@ -0,0 +1,55 @@
<?php
/**
* Mail configuration options.
*
* Changes to these config files are not supported by BookStack and may break upon updates.
* Configuration should be altered via the `.env` file or environment variables.
* Do not edit this file unless you're happy to maintain any changes yourself.
*/
return [
// Mail driver to use.
// Options: smtp, mail, sendmail, log
'driver' => env('MAIL_DRIVER', 'smtp'),
// SMTP host address
'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
// SMTP host port
'port' => env('MAIL_PORT', 587),
// Global "From" address & name
'from' => [
'address' => env('MAIL_FROM', 'mail@bookstackapp.com'),
'name' => env('MAIL_FROM_NAME', 'BookStack')
],
// Email encryption protocol
'encryption' => env('MAIL_ENCRYPTION', 'tls'),
// SMTP server username
'username' => env('MAIL_USERNAME'),
// SMTP server password
'password' => env('MAIL_PASSWORD'),
// Sendmail application path
'sendmail' => '/usr/sbin/sendmail -bs',
// Email markdown configuration
'markdown' => [
'theme' => 'default',
'paths' => [
resource_path('views/vendor/mail'),
],
],
// Log Channel
// If you are using the "log" driver, you may specify the logging channel
// if you prefer to keep mail messages separate from other log entries
// for simpler reading. Otherwise, the default channel will be used.
'log_channel' => env('MAIL_LOG_CHANNEL'),
];

47
app/Config/queue.php Normal file
View File

@@ -0,0 +1,47 @@
<?php
/**
* Queue configuration options.
*
* Changes to these config files are not supported by BookStack and may break upon updates.
* Configuration should be altered via the `.env` file or environment variables.
* Do not edit this file unless you're happy to maintain any changes yourself.
*/
return [
// Default driver to use for the queue
// Options: null, sync, redis
'default' => env('QUEUE_CONNECTION', 'sync'),
// Queue connection configuration
'connections' => [
'sync' => [
'driver' => 'sync',
],
'database' => [
'driver' => 'database',
'table' => 'jobs',
'queue' => 'default',
'retry_after' => 90,
],
'redis' => [
'driver' => 'redis',
'connection' => 'default',
'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => 90,
'block_for' => null,
],
],
// Failed queue job logging
'failed' => [
'database' => 'mysql', 'table' => 'failed_jobs',
],
];

144
app/Config/saml2.php Normal file
View File

@@ -0,0 +1,144 @@
<?php
return [
// Display name, shown to users, for SAML2 option
'name' => env('SAML2_NAME', 'SSO'),
// Dump user details after a login request for debugging purposes
'dump_user_details' => env('SAML2_DUMP_USER_DETAILS', false),
// Attribute, within a SAML response, to find the user's email address
'email_attribute' => env('SAML2_EMAIL_ATTRIBUTE', 'email'),
// Attribute, within a SAML response, to find the user's display name
'display_name_attributes' => explode('|', env('SAML2_DISPLAY_NAME_ATTRIBUTES', 'username')),
// Attribute, within a SAML response, to use to connect a BookStack user to the SAML user.
'external_id_attribute' => env('SAML2_EXTERNAL_ID_ATTRIBUTE', null),
// Group sync options
// Enable syncing, upon login, of SAML2 groups to BookStack groups
'user_to_groups' => env('SAML2_USER_TO_GROUPS', false),
// Attribute, within a SAML response, to find group names on
'group_attribute' => env('SAML2_GROUP_ATTRIBUTE', 'group'),
// When syncing groups, remove any groups that no longer match. Otherwise sync only adds new groups.
'remove_from_groups' => env('SAML2_REMOVE_FROM_GROUPS', false),
// Autoload IDP details from the metadata endpoint
'autoload_from_metadata' => env('SAML2_AUTOLOAD_METADATA', false),
// Overrides, in JSON format, to the configuration passed to underlying onelogin library.
'onelogin_overrides' => env('SAML2_ONELOGIN_OVERRIDES', null),
'onelogin' => [
// If 'strict' is True, then the PHP Toolkit will reject unsigned
// or unencrypted messages if it expects them signed or encrypted
// Also will reject the messages if not strictly follow the SAML
// standard: Destination, NameId, Conditions ... are validated too.
'strict' => true,
// Enable debug mode (to print errors)
'debug' => env('APP_DEBUG', false),
// Set a BaseURL to be used instead of try to guess
// the BaseURL of the view that process the SAML Message.
// Ex. http://sp.example.com/
// http://example.com/sp/
'baseurl' => null,
// Service Provider Data that we are deploying
'sp' => [
// Identifier of the SP entity (must be a URI)
'entityId' => '',
// Specifies info about where and how the <AuthnResponse> message MUST be
// returned to the requester, in this case our SP.
'assertionConsumerService' => [
// URL Location where the <Response> from the IdP will be returned
'url' => '',
// SAML protocol binding to be used when returning the <Response>
// message. Onelogin Toolkit supports for this endpoint the
// HTTP-POST binding only
'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
],
// Specifies info about where and how the <Logout Response> message MUST be
// returned to the requester, in this case our SP.
'singleLogoutService' => [
// URL Location where the <Response> from the IdP will be returned
'url' => '',
// SAML protocol binding to be used when returning the <Response>
// message. Onelogin Toolkit supports for this endpoint the
// HTTP-Redirect binding only
'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
],
// Specifies constraints on the name identifier to be used to
// represent the requested subject.
// Take a look on lib/Saml2/Constants.php to see the NameIdFormat supported
'NameIDFormat' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
// Usually x509cert and privateKey of the SP are provided by files placed at
// the certs folder. But we can also provide them with the following parameters
'x509cert' => '',
'privateKey' => '',
],
// Identity Provider Data that we want connect with our SP
'idp' => [
// Identifier of the IdP entity (must be a URI)
'entityId' => env('SAML2_IDP_ENTITYID', null),
// SSO endpoint info of the IdP. (Authentication Request protocol)
'singleSignOnService' => [
// URL Target of the IdP where the SP will send the Authentication Request Message
'url' => env('SAML2_IDP_SSO', null),
// SAML protocol binding to be used when returning the <Response>
// message. Onelogin Toolkit supports for this endpoint the
// HTTP-Redirect binding only
'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
],
// SLO endpoint info of the IdP.
'singleLogoutService' => [
// URL Location of the IdP where the SP will send the SLO Request
'url' => env('SAML2_IDP_SLO', null),
// URL location of the IdP where the SP will send the SLO Response (ResponseLocation)
// if not set, url for the SLO Request will be used
'responseUrl' => '',
// SAML protocol binding to be used when returning the <Response>
// message. Onelogin Toolkit supports for this endpoint the
// HTTP-Redirect binding only
'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
],
// Public x509 certificate of the IdP
'x509cert' => env('SAML2_IDP_x509', null),
/*
* Instead of use the whole x509cert you can use a fingerprint in
* order to validate the SAMLResponse, but we don't recommend to use
* that method on production since is exploitable by a collision
* attack.
* (openssl x509 -noout -fingerprint -in "idp.crt" to generate it,
* or add for example the -sha256 , -sha384 or -sha512 parameter)
*
* If a fingerprint is provided, then the certFingerprintAlgorithm is required in order to
* let the toolkit know which Algorithm was used. Possible values: sha1, sha256, sha384 or sha512
* 'sha1' is the default value.
*/
// 'certFingerprint' => '',
// 'certFingerprintAlgorithm' => 'sha1',
/* In some scenarios the IdP uses different certificates for
* signing/encryption, or is under key rollover phase and more
* than one certificate is published on IdP metadata.
* In order to handle that the toolkit offers that parameter.
* (when used, 'x509cert' and 'certFingerprint' values are
* ignored).
*/
// 'x509certMulti' => array(
// 'signing' => array(
// 0 => '<cert1-string>',
// ),
// 'encryption' => array(
// 0 => '<cert2-string>',
// )
// ),
],
],
];

View File

@@ -1,53 +1,34 @@
<?php
/**
* Third party service configuration options.
*
* Changes to these config files are not supported by BookStack and may break upon updates.
* Configuration should be altered via the `.env` file or environment variables.
* Do not edit this file unless you're happy to maintain any changes yourself.
*/
return [
/*
|--------------------------------------------------------------------------
| Third Party Services
|--------------------------------------------------------------------------
|
| This file is for storing the credentials for third party services such
| as Stripe, Mailgun, Mandrill, and others. This file provides a sane
| default location for this type of information, allowing packages
| to have a conventional place to find your various credentials.
|
*/
// Single option to disable non-auth external services such as Gravatar and Draw.io
'disable_services' => env('DISABLE_EXTERNAL_SERVICES', false),
'gravatar' => env('GRAVATAR', !env('DISABLE_EXTERNAL_SERVICES', false)),
// Draw.io integration active
'drawio' => env('DRAWIO', !env('DISABLE_EXTERNAL_SERVICES', false)),
// URL for fetching avatars
'avatar_url' => env('AVATAR_URL', ''),
// Callback URL for social authentication methods
'callback_url' => env('APP_URL', false),
'mailgun' => [
'domain' => '',
'secret' => '',
],
'mandrill' => [
'secret' => '',
],
'ses' => [
'key' => '',
'secret' => '',
'region' => 'us-east-1',
],
'stripe' => [
'model' => BookStack\User::class,
'key' => '',
'secret' => '',
],
'github' => [
'client_id' => env('GITHUB_APP_ID', false),
'client_secret' => env('GITHUB_APP_SECRET', false),
'redirect' => env('APP_URL') . '/login/service/github/callback',
'name' => 'GitHub',
'auto_register' => env('GITHUB_AUTO_REGISTER', false),
'auto_confirm' => env('GITHUB_AUTO_CONFIRM_EMAIL', false),
],
'google' => [
@@ -55,6 +36,9 @@ return [
'client_secret' => env('GOOGLE_APP_SECRET', false),
'redirect' => env('APP_URL') . '/login/service/google/callback',
'name' => 'Google',
'auto_register' => env('GOOGLE_AUTO_REGISTER', false),
'auto_confirm' => env('GOOGLE_AUTO_CONFIRM_EMAIL', false),
'select_account' => env('GOOGLE_SELECT_ACCOUNT', false),
],
'slack' => [
@@ -62,6 +46,8 @@ return [
'client_secret' => env('SLACK_APP_SECRET', false),
'redirect' => env('APP_URL') . '/login/service/slack/callback',
'name' => 'Slack',
'auto_register' => env('SLACK_AUTO_REGISTER', false),
'auto_confirm' => env('SLACK_AUTO_CONFIRM_EMAIL', false),
],
'facebook' => [
@@ -69,6 +55,8 @@ return [
'client_secret' => env('FACEBOOK_APP_SECRET', false),
'redirect' => env('APP_URL') . '/login/service/facebook/callback',
'name' => 'Facebook',
'auto_register' => env('FACEBOOK_AUTO_REGISTER', false),
'auto_confirm' => env('FACEBOOK_AUTO_CONFIRM_EMAIL', false),
],
'twitter' => [
@@ -76,6 +64,8 @@ return [
'client_secret' => env('TWITTER_APP_SECRET', false),
'redirect' => env('APP_URL') . '/login/service/twitter/callback',
'name' => 'Twitter',
'auto_register' => env('TWITTER_AUTO_REGISTER', false),
'auto_confirm' => env('TWITTER_AUTO_CONFIRM_EMAIL', false),
],
'azure' => [
@@ -84,14 +74,18 @@ return [
'tenant' => env('AZURE_TENANT', false),
'redirect' => env('APP_URL') . '/login/service/azure/callback',
'name' => 'Microsoft Azure',
'auto_register' => env('AZURE_AUTO_REGISTER', false),
'auto_confirm' => env('AZURE_AUTO_CONFIRM_EMAIL', false),
],
'okta' => [
'client_id' => env('OKTA_APP_ID'),
'client_secret' => env('OKTA_APP_SECRET'),
'redirect' => env('APP_URL') . '/login/service/okta/callback',
'base_url' => env('OKTA_BASE_URL'),
'redirect' => env('APP_URL') . '/login/service/okta/callback',
'base_url' => env('OKTA_BASE_URL'),
'name' => 'Okta',
'auto_register' => env('OKTA_AUTO_REGISTER', false),
'auto_confirm' => env('OKTA_AUTO_CONFIRM_EMAIL', false),
],
'gitlab' => [
@@ -100,6 +94,8 @@ return [
'redirect' => env('APP_URL') . '/login/service/gitlab/callback',
'instance_uri' => env('GITLAB_BASE_URI'), // Needed only for self hosted instances
'name' => 'GitLab',
'auto_register' => env('GITLAB_AUTO_REGISTER', false),
'auto_confirm' => env('GITLAB_AUTO_CONFIRM_EMAIL', false),
],
'twitch' => [
@@ -107,17 +103,35 @@ return [
'client_secret' => env('TWITCH_APP_SECRET'),
'redirect' => env('APP_URL') . '/login/service/twitch/callback',
'name' => 'Twitch',
'auto_register' => env('TWITCH_AUTO_REGISTER', false),
'auto_confirm' => env('TWITCH_AUTO_CONFIRM_EMAIL', false),
],
'discord' => [
'client_id' => env('DISCORD_APP_ID'),
'client_secret' => env('DISCORD_APP_SECRET'),
'redirect' => env('APP_URL') . '/login/service/discord/callback',
'name' => 'Discord',
'auto_register' => env('DISCORD_AUTO_REGISTER', false),
'auto_confirm' => env('DISCORD_AUTO_CONFIRM_EMAIL', false),
],
'ldap' => [
'server' => env('LDAP_SERVER', false),
'dump_user_details' => env('LDAP_DUMP_USER_DETAILS', false),
'dn' => env('LDAP_DN', false),
'pass' => env('LDAP_PASS', false),
'base_dn' => env('LDAP_BASE_DN', false),
'user_filter' => env('LDAP_USER_FILTER', '(&(uid=${user}))'),
'version' => env('LDAP_VERSION', false),
'id_attribute' => env('LDAP_ID_ATTRIBUTE', 'uid'),
'email_attribute' => env('LDAP_EMAIL_ATTRIBUTE', 'mail'),
'display_name_attribute' => env('LDAP_DISPLAY_NAME_ATTRIBUTE', 'cn'),
'follow_referrals' => env('LDAP_FOLLOW_REFERRALS', false),
]
'user_to_groups' => env('LDAP_USER_TO_GROUPS', false),
'group_attribute' => env('LDAP_GROUP_ATTRIBUTE', 'memberOf'),
'remove_from_groups' => env('LDAP_REMOVE_FROM_GROUPS', false),
'tls_insecure' => env('LDAP_TLS_INSECURE', false),
],
];

85
app/Config/session.php Normal file
View File

@@ -0,0 +1,85 @@
<?php
/**
* Session configuration options.
*
* Changes to these config files are not supported by BookStack and may break upon updates.
* Configuration should be altered via the `.env` file or environment variables.
* Do not edit this file unless you're happy to maintain any changes yourself.
*/
return [
// Default session driver
// Options: file, cookie, database, redis, memcached, array
'driver' => env('SESSION_DRIVER', 'file'),
// Session lifetime, in minutes
'lifetime' => env('SESSION_LIFETIME', 120),
// Expire session on browser close
'expire_on_close' => false,
// Encrypt session data
'encrypt' => false,
// Location to store session files
'files' => storage_path('framework/sessions'),
// Session Database Connection
// When using the "database" or "redis" session drivers, you can specify a
// connection that should be used to manage these sessions. This should
// correspond to a connection in your database configuration options.
'connection' => null,
// Session database table, if database driver is in use
'table' => 'sessions',
// Session Cache Store
// When using the "apc" or "memcached" session drivers, you may specify a
// cache store that should be used for these sessions. This value must
// correspond with one of the application's configured cache stores.
'store' => null,
// Session Sweeping Lottery
// Some session drivers must manually sweep their storage location to get
// rid of old sessions from storage. Here are the chances that it will
// happen on a given request. By default, the odds are 2 out of 100.
'lottery' => [2, 100],
// Session Cookie Name
// Here you may change the name of the cookie used to identify a session
// instance by ID. The name specified here will get used every time a
// new session cookie is created by the framework for every driver.
'cookie' => env('SESSION_COOKIE_NAME', 'bookstack_session'),
// Session Cookie Path
// The session cookie path determines the path for which the cookie will
// be regarded as available. Typically, this will be the root path of
// your application but you are free to change this when necessary.
'path' => '/',
// Session Cookie Domain
// Here you may change the domain of the cookie used to identify a session
// in your application. This will determine which domains the cookie is
// available to in your application. A sensible default has been set.
'domain' => env('SESSION_DOMAIN', null),
// HTTPS Only Cookies
// By setting this option to true, session cookies will only be sent back
// to the server if the browser has a HTTPS connection. This will keep
// the cookie from being sent to you if it can not be done securely.
'secure' => env('SESSION_SECURE_COOKIE', false),
// HTTP Access Only
// Setting this value to true will prevent JavaScript from accessing the
// value of the cookie and the cookie will only be accessible through the HTTP protocol.
'http_only' => true,
// Same-Site Cookies
// This option determines how your cookies behave when cross-site requests
// take place, and can be used to mitigate CSRF attacks. By default, we
// do not enable this as other CSRF protection services are in place.
// Options: lax, strict
'same_site' => null,
];

View File

@@ -0,0 +1,27 @@
<?php
/**
* Default system settings.
*
* Changes to these config files are not supported by BookStack and may break upon updates.
* Configuration should be altered via the `.env` file or environment variables.
* Do not edit this file unless you're happy to maintain any changes yourself.
*/
return [
'app-name' => 'BookStack',
'app-logo' => '',
'app-name-header' => true,
'app-editor' => 'wysiwyg',
'app-color' => '#206ea7',
'app-color-light' => 'rgba(32,110,167,0.15)',
'bookshelf-color' => '#a94747',
'book-color' => '#077b70',
'chapter-color' => '#af4d0d',
'page-color' => '#206ea7',
'page-draft-color' => '#7e50b1',
'app-custom-head' => false,
'registration-enabled' => false,
];

View File

@@ -1,11 +1,21 @@
<?php
/**
* SnappyPDF configuration options.
*
* Changes to these config files are not supported by BookStack and may break upon updates.
* Configuration should be altered via the `.env` file or environment variables.
* Do not edit this file unless you're happy to maintain any changes yourself.
*/
return [
'pdf' => [
'enabled' => true,
'binary' => file_exists(base_path('wkhtmltopdf')) ? base_path('wkhtmltopdf') : env('WKHTMLTOPDF', false),
'timeout' => false,
'options' => [],
'options' => [
'outline' => true
],
'env' => [],
],
'image' => [

37
app/Config/view.php Normal file
View File

@@ -0,0 +1,37 @@
<?php
/**
* View configuration options.
*
* Changes to these config files are not supported by BookStack and may break upon updates.
* Configuration should be altered via the `.env` file or environment variables.
* Do not edit this file unless you're happy to maintain any changes yourself.
*/
// Join up possible view locations
$viewPaths = [realpath(base_path('resources/views'))];
if ($theme = env('APP_THEME', false)) {
array_unshift($viewPaths, base_path('themes/' . $theme));
}
return [
// App theme
// This option defines the theme to use for the application. When a theme
// is set there must be a `themes/<theme_name>` folder to hold the
// custom theme overrides.
'theme' => env('APP_THEME', false),
// View Storage Paths
// Most templating systems load templates from disk. Here you may specify
// an array of paths that should be checked for your views. Of course
// the usual Laravel view path has already been registered for you.
'paths' => $viewPaths,
// Compiled View Path
// This option determines where all the compiled Blade templates will be
// stored for your application. Typically, this is within the storage
// directory. However, as usual, you are free to change this value.
'compiled' => realpath(storage_path('framework/views')),
];

View File

@@ -0,0 +1,85 @@
<?php
namespace BookStack\Console\Commands;
use BookStack\Uploads\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 \BookStack\Uploads\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

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

View File

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

View File

@@ -18,7 +18,7 @@ class ClearViews extends Command
*
* @var string
*/
protected $description = 'Clear all view-counts for all entities.';
protected $description = 'Clear all view-counts for all entities';
/**
* Create a new command instance.

View File

@@ -0,0 +1,88 @@
<?php
namespace BookStack\Console\Commands;
use BookStack\Entities\Bookshelf;
use BookStack\Entities\Repos\BookshelfRepo;
use Illuminate\Console\Command;
class CopyShelfPermissions extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'bookstack:copy-shelf-permissions
{--a|all : Perform for all shelves in the system}
{--s|slug= : The slug for a shelf to target}
';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Copy shelf permissions to all child books';
/**
* @var BookshelfRepo
*/
protected $bookshelfRepo;
/**
* Create a new command instance.
*
* @return void
*/
public function __construct(BookshelfRepo $repo)
{
$this->bookshelfRepo = $repo;
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$shelfSlug = $this->option('slug');
$cascadeAll = $this->option('all');
$shelves = null;
if (!$cascadeAll && !$shelfSlug) {
$this->error('Either a --slug or --all option must be provided.');
return;
}
if ($cascadeAll) {
$continue = $this->confirm(
'Permission settings for all shelves will be cascaded. '.
'Books assigned to multiple shelves will receive only the permissions of it\'s last processed shelf. '.
'Are you sure you want to proceed?'
);
if (!$continue && !$this->hasOption('no-interaction')) {
return;
}
$shelves = Bookshelf::query()->get(['id', 'restricted']);
}
if ($shelfSlug) {
$shelves = Bookshelf::query()->where('slug', '=', $shelfSlug)->get(['id', 'restricted']);
if ($shelves->count() === 0) {
$this->info('No shelves found with the given slug.');
}
}
foreach ($shelves as $shelf) {
$this->bookshelfRepo->copyDownPermissions($shelf, false);
$this->info('Copied permissions for shelf [' . $shelf->id . ']');
}
$this->info('Permissions copied for ' . $shelves->count() . ' shelves.');
}
}

View File

@@ -2,7 +2,7 @@
namespace BookStack\Console\Commands;
use BookStack\Repos\UserRepo;
use BookStack\Auth\UserRepo;
use Illuminate\Console\Command;
class CreateAdmin extends Command
@@ -49,7 +49,7 @@ class CreateAdmin extends Command
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)) {
if (mb_strlen($email) < 5 || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
return $this->error('Invalid email address provided');
}
@@ -61,7 +61,7 @@ class CreateAdmin extends Command
if (empty($name)) {
$name = $this->ask('Please specify an name for the new admin user');
}
if (strlen($name) < 2) {
if (mb_strlen($name) < 2) {
return $this->error('Invalid name provided');
}
@@ -69,14 +69,14 @@ class CreateAdmin extends Command
if (empty($password)) {
$password = $this->secret('Please specify a password for the new admin user');
}
if (strlen($password) < 5) {
if (mb_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);
$this->userRepo->downloadAndAssignUserAvatar($user);
$user->email_confirmed = true;
$user->save();

View File

@@ -2,8 +2,8 @@
namespace BookStack\Console\Commands;
use BookStack\User;
use BookStack\Repos\UserRepo;
use BookStack\Auth\User;
use BookStack\Auth\UserRepo;
use Illuminate\Console\Command;
class DeleteUsers extends Command
@@ -25,7 +25,7 @@ class DeleteUsers extends Command
*
* @var string
*/
protected $description = 'Delete users that are not "admin" or system users.';
protected $description = 'Delete users that are not "admin" or system users';
public function __construct(User $user, UserRepo $userRepo)
{

View File

@@ -0,0 +1,61 @@
<?php
namespace BookStack\Console\Commands;
use BookStack\Actions\Comment;
use BookStack\Actions\CommentRepo;
use Illuminate\Console\Command;
class RegenerateCommentContent extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'bookstack:regenerate-comment-content {--database= : The database connection to use.}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Regenerate the stored HTML of all comments';
/**
* @var CommentRepo
*/
protected $commentRepo;
/**
* Create a new command instance.
*/
public function __construct(CommentRepo $commentRepo)
{
$this->commentRepo = $commentRepo;
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$connection = \DB::getDefaultConnection();
if ($this->option('database') !== null) {
\DB::setDefaultConnection($this->option('database'));
}
Comment::query()->chunk(100, function ($comments) {
foreach ($comments as $comment) {
$comment->html = $this->commentRepo->commentToHtml($comment->text);
$comment->save();
}
});
\DB::setDefaultConnection($connection);
$this->comment('Comment HTML content has been regenerated');
}
}

View File

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

View File

@@ -2,7 +2,8 @@
namespace BookStack\Console\Commands;
use BookStack\Services\SearchService;
use BookStack\Entities\SearchService;
use DB;
use Illuminate\Console\Command;
class RegenerateSearch extends Command
@@ -41,14 +42,14 @@ class RegenerateSearch extends Command
*/
public function handle()
{
$connection = \DB::getDefaultConnection();
$connection = DB::getDefaultConnection();
if ($this->option('database') !== null) {
\DB::setDefaultConnection($this->option('database'));
$this->searchService->setConnection(\DB::connection($this->option('database')));
DB::setDefaultConnection($this->option('database'));
$this->searchService->setConnection(DB::connection($this->option('database')));
}
$this->searchService->indexAllEntities();
\DB::setDefaultConnection($connection);
DB::setDefaultConnection($connection);
$this->comment('Search index regenerated');
}
}

View File

@@ -0,0 +1,91 @@
<?php
namespace BookStack\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Database\Connection;
class UpdateUrl extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'bookstack:update-url
{oldUrl : URL to replace}
{newUrl : URL to use as the replacement}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Find and replace the given URLs in your BookStack database';
protected $db;
/**
* Create a new command instance.
*
* @return void
*/
public function __construct(Connection $db)
{
$this->db = $db;
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$oldUrl = str_replace("'", '', $this->argument('oldUrl'));
$newUrl = str_replace("'", '', $this->argument('newUrl'));
$urlPattern = '/https?:\/\/(.+)/';
if (!preg_match($urlPattern, $oldUrl) || !preg_match($urlPattern, $newUrl)) {
$this->error("The given urls are expected to be full urls starting with http:// or https://");
return 1;
}
if (!$this->checkUserOkayToProceed($oldUrl, $newUrl)) {
return 1;
}
$columnsToUpdateByTable = [
"attachments" => ["path"],
"pages" => ["html", "text", "markdown"],
"images" => ["url"],
"comments" => ["html", "text"],
];
foreach ($columnsToUpdateByTable as $table => $columns) {
foreach ($columns as $column) {
$changeCount = $this->db->table($table)->update([
$column => $this->db->raw("REPLACE({$column}, '{$oldUrl}', '{$newUrl}')")
]);
$this->info("Updated {$changeCount} rows in {$table}->{$column}");
}
}
$this->info("URL update procedure complete.");
return 0;
}
/**
* Warn the user of the dangers of this operation.
* Returns a boolean indicating if they've accepted the warnings.
*/
protected function checkUserOkayToProceed(string $oldUrl, string $newUrl): bool
{
$dangerWarning = "This will search for \"{$oldUrl}\" in your database and replace it with \"{$newUrl}\".\n";
$dangerWarning .= "Are you sure you want to proceed?";
$backupConfirmation = "This operation could cause issues if used incorrectly. Have you made a backup of your existing database?";
return $this->confirm($dangerWarning) && $this->confirm($backupConfirmation);
}
}

131
app/Entities/Book.php Normal file
View File

@@ -0,0 +1,131 @@
<?php namespace BookStack\Entities;
use BookStack\Uploads\Image;
use Exception;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Collection;
/**
* Class Book
* @property string $description
* @property int $image_id
* @property Image|null $cover
* @package BookStack\Entities
*/
class Book extends Entity implements HasCoverImage
{
public $searchFactor = 2;
protected $fillable = ['name', 'description'];
protected $hidden = ['restricted', 'pivot'];
/**
* Get the url for this book.
* @param string|bool $path
* @return string
*/
public function getUrl($path = false)
{
if ($path !== false) {
return url('/books/' . urlencode($this->slug) . '/' . trim($path, '/'));
}
return url('/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 = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
if (!$this->image_id) {
return $default;
}
try {
$cover = $this->cover ? url($this->cover->getThumb($width, $height, false)) : $default;
} catch (Exception $err) {
$cover = $default;
}
return $cover;
}
/**
* Get the cover image of the book
*/
public function cover(): BelongsTo
{
return $this->belongsTo(Image::class, 'image_id');
}
/**
* Get the type of the image model that is used when storing a cover image.
*/
public function coverImageTypeKey(): string
{
return 'cover_book';
}
/**
* Get all pages within this book.
* @return HasMany
*/
public function pages()
{
return $this->hasMany(Page::class);
}
/**
* Get the direct child pages of this book.
* @return HasMany
*/
public function directPages()
{
return $this->pages()->where('chapter_id', '=', '0');
}
/**
* Get all chapters within this book.
* @return HasMany
*/
public function chapters()
{
return $this->hasMany(Chapter::class);
}
/**
* Get the shelves this book is contained within.
* @return BelongsToMany
*/
public function shelves()
{
return $this->belongsToMany(Bookshelf::class, 'bookshelves_books', 'book_id', 'bookshelf_id');
}
/**
* Get the direct child items within this book.
* @return Collection
*/
public function getDirectChildren(): Collection
{
$pages = $this->directPages()->visible()->get();
$chapters = $this->chapters()->visible()->get();
return $pages->concat($chapters)->sortBy('priority')->sortByDesc('draft');
}
/**
* Get an excerpt of this book's description to the specified length or less.
* @param int $length
* @return string
*/
public function getExcerpt(int $length = 100)
{
$description = $this->description;
return mb_strlen($description) > $length ? mb_substr($description, 0, $length-3) . '...' : $description;
}
}

View File

@@ -0,0 +1,60 @@
<?php namespace BookStack\Entities;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* Class BookChild
* @property int $book_id
* @property int $priority
* @property Book $book
* @method Builder whereSlugs(string $bookSlug, string $childSlug)
*/
class BookChild extends Entity
{
/**
* Scope a query to find items where the the child has the given childSlug
* where its parent has the bookSlug.
*/
public function scopeWhereSlugs(Builder $query, string $bookSlug, string $childSlug)
{
return $query->with('book')
->whereHas('book', function (Builder $query) use ($bookSlug) {
$query->where('slug', '=', $bookSlug);
})
->where('slug', '=', $childSlug);
}
/**
* Get the book this page sits in.
* @return BelongsTo
*/
public function book(): BelongsTo
{
return $this->belongsTo(Book::class);
}
/**
* Change the book that this entity belongs to.
*/
public function changeBook(int $newBookId): Entity
{
$this->book_id = $newBookId;
$this->refreshSlug();
$this->save();
$this->refresh();
// Update related activity
$this->activity()->update(['book_id' => $newBookId]);
// Update all child pages if a chapter
if ($this instanceof Chapter) {
foreach ($this->pages as $page) {
$page->changeBook($newBookId);
}
}
return $this;
}
}

122
app/Entities/Bookshelf.php Normal file
View File

@@ -0,0 +1,122 @@
<?php namespace BookStack\Entities;
use BookStack\Uploads\Image;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Bookshelf extends Entity implements HasCoverImage
{
protected $table = 'bookshelves';
public $searchFactor = 3;
protected $fillable = ['name', 'description', 'image_id'];
protected $hidden = ['restricted'];
/**
* Get the books in this shelf.
* Should not be used directly since does not take into account permissions.
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function books()
{
return $this->belongsToMany(Book::class, 'bookshelves_books', 'bookshelf_id', 'book_id')
->withPivot('order')
->orderBy('order', 'asc');
}
/**
* Related books that are visible to the current user.
*/
public function visibleBooks(): BelongsToMany
{
return $this->books()->visible();
}
/**
* Get the url for this bookshelf.
* @param string|bool $path
* @return string
*/
public function getUrl($path = false)
{
if ($path !== false) {
return url('/shelves/' . urlencode($this->slug) . '/' . trim($path, '/'));
}
return url('/shelves/' . urlencode($this->slug));
}
/**
* Returns BookShelf cover image, if cover does 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)
{
// TODO - Make generic, focused on books right now, Perhaps set-up a better image
$default = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
if (!$this->image_id) {
return $default;
}
try {
$cover = $this->cover ? url($this->cover->getThumb($width, $height, false)) : $default;
} catch (\Exception $err) {
$cover = $default;
}
return $cover;
}
/**
* Get the cover image of the shelf
*/
public function cover(): BelongsTo
{
return $this->belongsTo(Image::class, 'image_id');
}
/**
* Get the type of the image model that is used when storing a cover image.
*/
public function coverImageTypeKey(): string
{
return 'cover_shelf';
}
/**
* Get an excerpt of this book's description to the specified length or less.
* @param int $length
* @return string
*/
public function getExcerpt(int $length = 100)
{
$description = $this->description;
return mb_strlen($description) > $length ? mb_substr($description, 0, $length-3) . '...' : $description;
}
/**
* Check if this shelf contains the given book.
* @param Book $book
* @return bool
*/
public function contains(Book $book): bool
{
return $this->books()->where('id', '=', $book->id)->count() > 0;
}
/**
* Add a book to the end of this shelf.
* @param Book $book
*/
public function appendBook(Book $book)
{
if ($this->contains($book)) {
return;
}
$maxOrder = $this->books()->max('order');
$this->books()->attach($book->id, ['order' => $maxOrder + 1]);
}
}

View File

@@ -0,0 +1,36 @@
<?php namespace BookStack\Entities;
use BookStack\Entities\Managers\EntityContext;
use Illuminate\View\View;
class BreadcrumbsViewComposer
{
protected $entityContextManager;
/**
* BreadcrumbsViewComposer constructor.
* @param EntityContext $entityContextManager
*/
public function __construct(EntityContext $entityContextManager)
{
$this->entityContextManager = $entityContextManager;
}
/**
* Modify data when the view is composed.
* @param View $view
*/
public function compose(View $view)
{
$crumbs = $view->getData()['crumbs'];
$firstCrumb = $crumbs[0] ?? null;
if ($firstCrumb instanceof Book) {
$shelf = $this->entityContextManager->getContextualShelfForBook($firstCrumb);
if ($shelf) {
array_unshift($crumbs, $shelf);
$view->with('crumbs', $crumbs);
}
}
}
}

73
app/Entities/Chapter.php Normal file
View File

@@ -0,0 +1,73 @@
<?php namespace BookStack\Entities;
use Illuminate\Support\Collection;
/**
* Class Chapter
* @property Collection<Page> $pages
* @package BookStack\Entities
*/
class Chapter extends BookChild
{
public $searchFactor = 1.3;
protected $fillable = ['name', 'description', 'priority', 'book_id'];
/**
* Get the pages that this chapter contains.
* @param string $dir
* @return mixed
*/
public function pages($dir = 'ASC')
{
return $this->hasMany(Page::class)->orderBy('priority', $dir);
}
/**
* 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;
$fullPath = '/books/' . urlencode($bookSlug) . '/chapter/' . urlencode($this->slug);
if ($path !== false) {
$fullPath .= '/' . trim($path, '/');
}
return url($fullPath);
}
/**
* Get an excerpt of this chapter's description to the specified length or less.
* @param int $length
* @return string
*/
public function getExcerpt(int $length = 100)
{
$description = $this->text ?? $this->description;
return mb_strlen($description) > $length ? mb_substr($description, 0, $length-3) . '...' : $description;
}
/**
* Check if this chapter has any child pages.
* @return bool
*/
public function hasChildren()
{
return count($this->pages) > 0;
}
/**
* Get the visible pages in this chapter.
*/
public function getVisiblePages(): Collection
{
return $this->pages()->visible()
->orderBy('draft', 'desc')
->orderBy('priority', 'asc')
->get();
}
}

View File

@@ -1,7 +1,39 @@
<?php namespace BookStack;
<?php namespace BookStack\Entities;
use BookStack\Actions\Activity;
use BookStack\Actions\Comment;
use BookStack\Actions\Tag;
use BookStack\Actions\View;
use BookStack\Auth\Permissions\EntityPermission;
use BookStack\Auth\Permissions\JointPermission;
use BookStack\Facades\Permissions;
use BookStack\Ownable;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Relations\MorphMany;
/**
* Class Entity
* The base class for book-like items such as pages, chapters & books.
* This is not a database model in itself but extended.
*
* @property int $id
* @property string $name
* @property string $slug
* @property Carbon $created_at
* @property Carbon $updated_at
* @property int $created_by
* @property int $updated_by
* @property boolean $restricted
* @property Collection $tags
* @method static Entity|Builder visible()
* @method static Entity|Builder hasPermission(string $permission)
* @method static Builder withLastView()
* @method static Builder withViewCount()
*
* @package BookStack\Entities
*/
class Entity extends Ownable
{
@@ -15,6 +47,48 @@ class Entity extends Ownable
*/
public $searchFactor = 1.0;
/**
* Get the entities that are visible to the current user.
*/
public function scopeVisible(Builder $query)
{
return $this->scopeHasPermission($query, 'view');
}
/**
* Scope the query to those entities that the current user has the given permission for.
*/
public function scopeHasPermission(Builder $query, string $permission)
{
return Permissions::restrictEntityQuery($query, $permission);
}
/**
* Query scope to get the last view from the current user.
*/
public function scopeWithLastView(Builder $query)
{
$viewedAtQuery = View::query()->select('updated_at')
->whereColumn('viewable_id', '=', $this->getTable() . '.id')
->where('viewable_type', '=', $this->getMorphClass())
->where('user_id', '=', user()->id)
->take(1);
return $query->addSelect(['last_viewed_at' => $viewedAtQuery]);
}
/**
* Query scope to get the total view count of the entities.
*/
public function scopeWithViewCount(Builder $query)
{
$viewCountQuery = View::query()->selectRaw('SUM(views) as view_count')
->whereColumn('viewable_id', '=', $this->getTable() . '.id')
->where('viewable_type', '=', $this->getMorphClass())->take(1);
$query->addSelect(['view_count' => $viewCountQuery]);
}
/**
* Compares this entity to another given entity.
* Matches by comparing class and id.
@@ -52,11 +126,12 @@ class Entity extends Ownable
/**
* Gets the activity objects for this entity.
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
* @return MorphMany
*/
public function activity()
{
return $this->morphMany(Activity::class, 'entity')->orderBy('created_at', 'desc');
return $this->morphMany(Activity::class, 'entity')
->orderBy('created_at', 'desc');
}
/**
@@ -69,7 +144,7 @@ class Entity extends Ownable
/**
* Get the Tag models that have been user assigned to this entity.
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
* @return MorphMany
*/
public function tags()
{
@@ -89,7 +164,7 @@ class Entity extends Ownable
/**
* Get the related search terms.
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
* @return MorphMany
*/
public function searchTerms()
{
@@ -118,7 +193,7 @@ class Entity extends Ownable
/**
* Get the entity jointPermissions this is connected to.
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
* @return MorphMany
*/
public function jointPermissions()
{
@@ -152,13 +227,13 @@ class Entity extends Ownable
*/
public static function getEntityInstance($type)
{
$types = ['Page', 'Book', 'Chapter'];
$types = ['Page', 'Book', 'Chapter', 'Bookshelf'];
$className = str_replace([' ', '-', '_'], '', ucwords($type));
if (!in_array($className, $types)) {
return null;
}
return app('BookStack\\' . $className);
return app('BookStack\\Entities\\' . $className);
}
/**
@@ -168,10 +243,10 @@ class Entity extends Ownable
*/
public function getShortName($length = 25)
{
if (strlen($this->name) <= $length) {
if (mb_strlen($this->name) <= $length) {
return $this->name;
}
return substr($this->name, 0, $length - 3) . '...';
return mb_substr($this->name, 0, $length - 3) . '...';
}
/**
@@ -184,12 +259,17 @@ class Entity extends Ownable
}
/**
* Return a generalised, common raw query that can be 'unioned' across entities.
* @return string
* Get an excerpt of this entity's descriptive content to the specified length.
* @param int $length
* @return mixed
*/
public function entityRawQuery()
public function getExcerpt(int $length = 100)
{
return '';
$text = $this->getText();
if (mb_strlen($text) > $length) {
$text = mb_substr($text, 0, $length-3) . '...';
}
return trim($text);
}
/**
@@ -197,9 +277,36 @@ class Entity extends Ownable
* @param $path
* @return string
*/
public function getUrl($path)
public function getUrl($path = '/')
{
return '/';
return $path;
}
/**
* Rebuild the permissions for this entity.
*/
public function rebuildPermissions()
{
/** @noinspection PhpUnhandledExceptionInspection */
Permissions::buildJointPermissionsForEntity($this);
}
/**
* Index the current entity for search
*/
public function indexForSearch()
{
$searchService = app()->make(SearchService::class);
$searchService->indexEntity($this);
}
/**
* Generate and set a new URL slug for this model.
*/
public function refreshSlug(): string
{
$generator = new SlugGenerator($this);
$this->slug = $generator->generate();
return $this->slug;
}
}

View File

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

View File

@@ -1,144 +1,145 @@
<?php namespace BookStack\Services;
<?php namespace BookStack\Entities;
use BookStack\Book;
use BookStack\Chapter;
use BookStack\Page;
use BookStack\Repos\EntityRepo;
use BookStack\Entities\Managers\BookContents;
use BookStack\Entities\Managers\PageContent;
use BookStack\Uploads\ImageService;
use DomPDF;
use Exception;
use SnappyPDF;
use Throwable;
class ExportService
{
protected $entityRepo;
protected $imageService;
/**
* ExportService constructor.
* @param $entityRepo
*/
public function __construct(EntityRepo $entityRepo)
public function __construct(ImageService $imageService)
{
$this->entityRepo = $entityRepo;
$this->imageService = $imageService;
}
/**
* Convert a page to a self-contained HTML file.
* Includes required CSS & image content. Images are base64 encoded into the HTML.
* @param Page $page
* @return mixed|string
* @throws Throwable
*/
public function pageToContainedHtml(Page $page)
{
$this->entityRepo->renderPage($page);
$pageHtml = view('pages/export', [
'page' => $page
$page->html = (new PageContent($page))->render();
$pageHtml = view('pages.export', [
'page' => $page,
'format' => 'html',
])->render();
return $this->containHtml($pageHtml);
}
/**
* Convert a chapter to a self-contained HTML file.
* @param Chapter $chapter
* @return mixed|string
* @throws Throwable
*/
public function chapterToContainedHtml(Chapter $chapter)
{
$pages = $this->entityRepo->getChapterChildren($chapter);
$pages = $chapter->getVisiblePages();
$pages->each(function ($page) {
$page->html = $this->entityRepo->renderPage($page);
$page->html = (new PageContent($page))->render();
});
$html = view('chapters/export', [
$html = view('chapters.export', [
'chapter' => $chapter,
'pages' => $pages
'pages' => $pages,
'format' => 'html',
])->render();
return $this->containHtml($html);
}
/**
* Convert a book to a self-contained HTML file.
* @param Book $book
* @return mixed|string
* @throws Throwable
*/
public function bookToContainedHtml(Book $book)
{
$bookTree = $this->entityRepo->getBookChildren($book, true, true);
$html = view('books/export', [
$bookTree = (new BookContents($book))->getTree(false, true);
$html = view('books.export', [
'book' => $book,
'bookChildren' => $bookTree
'bookChildren' => $bookTree,
'format' => 'html',
])->render();
return $this->containHtml($html);
}
/**
* Convert a page to a PDF file.
* @param Page $page
* @return mixed|string
* @throws Throwable
*/
public function pageToPdf(Page $page)
{
$this->entityRepo->renderPage($page);
$html = view('pages/pdf', [
'page' => $page
$page->html = (new PageContent($page))->render();
$html = view('pages.export', [
'page' => $page,
'format' => 'pdf',
])->render();
return $this->htmlToPdf($html);
}
/**
* Convert a chapter to a PDF file.
* @param Chapter $chapter
* @return mixed|string
* @throws Throwable
*/
public function chapterToPdf(Chapter $chapter)
{
$pages = $this->entityRepo->getChapterChildren($chapter);
$pages = $chapter->getVisiblePages();
$pages->each(function ($page) {
$page->html = $this->entityRepo->renderPage($page);
$page->html = (new PageContent($page))->render();
});
$html = view('chapters/export', [
$html = view('chapters.export', [
'chapter' => $chapter,
'pages' => $pages
'pages' => $pages,
'format' => 'pdf',
])->render();
return $this->htmlToPdf($html);
}
/**
* Convert a book to a PDF file
* @param Book $book
* @return string
* Convert a book to a PDF file.
* @throws Throwable
*/
public function bookToPdf(Book $book)
{
$bookTree = $this->entityRepo->getBookChildren($book, true, true);
$html = view('books/export', [
$bookTree = (new BookContents($book))->getTree(false, true);
$html = view('books.export', [
'book' => $book,
'bookChildren' => $bookTree
'bookChildren' => $bookTree,
'format' => 'pdf',
])->render();
return $this->htmlToPdf($html);
}
/**
* Convert normal webpage HTML to a PDF.
* @param $html
* @return string
* Convert normal web-page HTML to a PDF.
* @throws Exception
*/
protected function htmlToPdf($html)
protected function htmlToPdf(string $html): string
{
$containedHtml = $this->containHtml($html);
$useWKHTML = config('snappy.pdf.binary') !== false;
if ($useWKHTML) {
$pdf = \SnappyPDF::loadHTML($containedHtml);
$pdf = SnappyPDF::loadHTML($containedHtml);
$pdf->setOption('print-media-type', true);
} else {
$pdf = \DomPDF::loadHTML($containedHtml);
$pdf = DomPDF::loadHTML($containedHtml);
}
return $pdf->output();
}
/**
* Bundle of the contents of a html file to be self-contained.
* @param $htmlContent
* @return mixed|string
* @throws \Exception
* @throws Exception
*/
protected function containHtml($htmlContent)
protected function containHtml(string $htmlContent): string
{
$imageTagsOutput = [];
preg_match_all("/\<img.*src\=(\'|\")(.*?)(\'|\").*?\>/i", $htmlContent, $imageTagsOutput);
@@ -146,45 +147,14 @@ class ExportService
// Replace image src with base64 encoded image strings
if (isset($imageTagsOutput[0]) && count($imageTagsOutput[0]) > 0) {
foreach ($imageTagsOutput[0] as $index => $imgMatch) {
$oldImgString = $imgMatch;
$oldImgTagString = $imgMatch;
$srcString = $imageTagsOutput[2][$index];
$isLocal = strpos(trim($srcString), 'http') !== 0;
if ($isLocal) {
$pathString = public_path(trim($srcString, '/'));
} else {
$pathString = $srcString;
$imageEncoded = $this->imageService->imageUriToBase64($srcString);
if ($imageEncoded === null) {
$imageEncoded = $srcString;
}
// Attempt to find local files even if url not absolute
$base = baseUrl('/');
if (strpos($srcString, $base) === 0) {
$isLocal = true;
$relString = str_replace($base, '', $srcString);
$pathString = public_path(trim($relString, '/'));
}
if ($isLocal && !file_exists($pathString)) {
continue;
}
try {
if ($isLocal) {
$imageContent = file_get_contents($pathString);
} else {
$ch = curl_init();
curl_setopt_array($ch, [CURLOPT_URL => $pathString, CURLOPT_RETURNTRANSFER => 1, CURLOPT_CONNECTTIMEOUT => 5]);
$imageContent = curl_exec($ch);
$err = curl_error($ch);
curl_close($ch);
if ($err) {
throw new \Exception("Image fetch failed, Received error: " . $err);
}
}
$imageEncoded = 'data:image/' . pathinfo($pathString, PATHINFO_EXTENSION) . ';base64,' . base64_encode($imageContent);
$newImageString = str_replace($srcString, $imageEncoded, $oldImgString);
} catch (\ErrorException $e) {
$newImageString = '';
}
$htmlContent = str_replace($oldImgString, $newImageString, $htmlContent);
$newImgTagString = str_replace($srcString, $imageEncoded, $oldImgTagString);
$htmlContent = str_replace($oldImgTagString, $newImgTagString, $htmlContent);
}
}
@@ -211,12 +181,10 @@ class ExportService
/**
* Converts the page contents into simple plain text.
* This method filters any bad looking content to provide a nice final output.
* @param Page $page
* @return mixed
*/
public function pageToPlainText(Page $page)
public function pageToPlainText(Page $page): string
{
$html = $this->entityRepo->renderPage($page);
$html = (new PageContent($page))->render();
$text = strip_tags($html);
// Replace multiple spaces with single spaces
$text = preg_replace('/\ {2,}/', ' ', $text);
@@ -230,10 +198,8 @@ class ExportService
/**
* Convert a chapter into a plain text string.
* @param Chapter $chapter
* @return string
*/
public function chapterToPlainText(Chapter $chapter)
public function chapterToPlainText(Chapter $chapter): string
{
$text = $chapter->name . "\n\n";
$text .= $chapter->description . "\n\n";
@@ -245,12 +211,10 @@ class ExportService
/**
* Convert a book into a plain text string.
* @param Book $book
* @return string
*/
public function bookToPlainText(Book $book)
public function bookToPlainText(Book $book): string
{
$bookTree = $this->entityRepo->getBookChildren($book, true, true);
$bookTree = (new BookContents($book))->getTree(false, true);
$text = $book->name . "\n\n";
foreach ($bookTree as $bookChild) {
if ($bookChild->isA('chapter')) {

View File

@@ -0,0 +1,20 @@
<?php
namespace BookStack\Entities;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
interface HasCoverImage
{
/**
* Get the cover image for this item.
*/
public function cover(): BelongsTo;
/**
* Get the type of the image model that is used when storing a cover image.
*/
public function coverImageTypeKey(): string;
}

View File

@@ -0,0 +1,204 @@
<?php namespace BookStack\Entities\Managers;
use BookStack\Entities\Book;
use BookStack\Entities\BookChild;
use BookStack\Entities\Chapter;
use BookStack\Entities\Entity;
use BookStack\Entities\Page;
use BookStack\Exceptions\SortOperationException;
use Illuminate\Support\Collection;
class BookContents
{
/**
* @var Book
*/
protected $book;
/**
* BookContents constructor.
* @param $book
*/
public function __construct(Book $book)
{
$this->book = $book;
}
/**
* Get the current priority of the last item
* at the top-level of the book.
*/
public function getLastPriority(): int
{
$maxPage = Page::visible()->where('book_id', '=', $this->book->id)
->where('draft', '=', false)
->where('chapter_id', '=', 0)->max('priority');
$maxChapter = Chapter::visible()->where('book_id', '=', $this->book->id)
->max('priority');
return max($maxChapter, $maxPage, 1);
}
/**
* Get the contents as a sorted collection tree.
* TODO - Support $renderPages option
*/
public function getTree(bool $showDrafts = false, bool $renderPages = false): Collection
{
$pages = $this->getPages($showDrafts);
$chapters = Chapter::visible()->where('book_id', '=', $this->book->id)->get();
$all = collect()->concat($pages)->concat($chapters);
$chapterMap = $chapters->keyBy('id');
$lonePages = collect();
$pages->groupBy('chapter_id')->each(function ($pages, $chapter_id) use ($chapterMap, &$lonePages) {
$chapter = $chapterMap->get($chapter_id);
if ($chapter) {
$chapter->setAttribute('pages', collect($pages)->sortBy($this->bookChildSortFunc()));
} else {
$lonePages = $lonePages->concat($pages);
}
});
$all->each(function (Entity $entity) {
$entity->setRelation('book', $this->book);
});
return collect($chapters)->concat($lonePages)->sortBy($this->bookChildSortFunc());
}
/**
* Function for providing a sorting score for an entity in relation to the
* other items within the book.
*/
protected function bookChildSortFunc(): callable
{
return function (Entity $entity) {
if (isset($entity['draft']) && $entity['draft']) {
return -100;
}
return $entity['priority'] ?? 0;
};
}
/**
* Get the visible pages within this book.
*/
protected function getPages(bool $showDrafts = false): Collection
{
$query = Page::visible()->where('book_id', '=', $this->book->id);
if (!$showDrafts) {
$query->where('draft', '=', false);
}
return $query->get();
}
/**
* Sort the books content using the given map.
* The map is a single-dimension collection of objects in the following format:
* {
* +"id": "294" (ID of item)
* +"sort": 1 (Sort order index)
* +"parentChapter": false (ID of parent chapter, as string, or false)
* +"type": "page" (Entity type of item)
* +"book": "1" (Id of book to place item in)
* }
*
* Returns a list of books that were involved in the operation.
* @throws SortOperationException
*/
public function sortUsingMap(Collection $sortMap): Collection
{
// Load models into map
$this->loadModelsIntoSortMap($sortMap);
$booksInvolved = $this->getBooksInvolvedInSort($sortMap);
// Perform the sort
$sortMap->each(function ($mapItem) {
$this->applySortUpdates($mapItem);
});
// Update permissions and activity.
$booksInvolved->each(function (Book $book) {
$book->rebuildPermissions();
});
return $booksInvolved;
}
/**
* Using the given sort map item, detect changes for the related model
* and update it if required.
*/
protected function applySortUpdates(\stdClass $sortMapItem)
{
/** @var BookChild $model */
$model = $sortMapItem->model;
$priorityChanged = intval($model->priority) !== intval($sortMapItem->sort);
$bookChanged = intval($model->book_id) !== intval($sortMapItem->book);
$chapterChanged = ($sortMapItem->type === 'page') && intval($model->chapter_id) !== $sortMapItem->parentChapter;
if ($bookChanged) {
$model->changeBook($sortMapItem->book);
}
if ($chapterChanged) {
$model->chapter_id = intval($sortMapItem->parentChapter);
$model->save();
}
if ($priorityChanged) {
$model->priority = intval($sortMapItem->sort);
$model->save();
}
}
/**
* Load models from the database into the given sort map.
*/
protected function loadModelsIntoSortMap(Collection $sortMap): void
{
$keyMap = $sortMap->keyBy(function (\stdClass $sortMapItem) {
return $sortMapItem->type . ':' . $sortMapItem->id;
});
$pageIds = $sortMap->where('type', '=', 'page')->pluck('id');
$chapterIds = $sortMap->where('type', '=', 'chapter')->pluck('id');
$pages = Page::visible()->whereIn('id', $pageIds)->get();
$chapters = Chapter::visible()->whereIn('id', $chapterIds)->get();
foreach ($pages as $page) {
$sortItem = $keyMap->get('page:' . $page->id);
$sortItem->model = $page;
}
foreach ($chapters as $chapter) {
$sortItem = $keyMap->get('chapter:' . $chapter->id);
$sortItem->model = $chapter;
}
}
/**
* Get the books involved in a sort.
* The given sort map should have its models loaded first.
* @throws SortOperationException
*/
protected function getBooksInvolvedInSort(Collection $sortMap): Collection
{
$bookIdsInvolved = collect([$this->book->id]);
$bookIdsInvolved = $bookIdsInvolved->concat($sortMap->pluck('book'));
$bookIdsInvolved = $bookIdsInvolved->concat($sortMap->pluck('model.book_id'));
$bookIdsInvolved = $bookIdsInvolved->unique()->toArray();
$books = Book::hasPermission('update')->whereIn('id', $bookIdsInvolved)->get();
if (count($books) !== count($bookIdsInvolved)) {
throw new SortOperationException("Could not find all books requested in sort operation");
}
return $books;
}
}

View File

@@ -0,0 +1,54 @@
<?php namespace BookStack\Entities\Managers;
use BookStack\Entities\Book;
use BookStack\Entities\Bookshelf;
use Illuminate\Session\Store;
class EntityContext
{
protected $session;
protected $KEY_SHELF_CONTEXT_ID = 'context_bookshelf_id';
/**
* EntityContextManager constructor.
*/
public function __construct(Store $session)
{
$this->session = $session;
}
/**
* Get the current bookshelf context for the given book.
*/
public function getContextualShelfForBook(Book $book): ?Bookshelf
{
$contextBookshelfId = $this->session->get($this->KEY_SHELF_CONTEXT_ID, null);
if (!is_int($contextBookshelfId)) {
return null;
}
$shelf = Bookshelf::visible()->find($contextBookshelfId);
$shelfContainsBook = $shelf && $shelf->contains($book);
return $shelfContainsBook ? $shelf : null;
}
/**
* Store the current contextual shelf ID.
* @param int $shelfId
*/
public function setShelfContext(int $shelfId)
{
$this->session->put($this->KEY_SHELF_CONTEXT_ID, $shelfId);
}
/**
* Clear the session stored shelf context id.
*/
public function clearShelfContext()
{
$this->session->forget($this->KEY_SHELF_CONTEXT_ID);
}
}

View File

@@ -0,0 +1,304 @@
<?php namespace BookStack\Entities\Managers;
use BookStack\Entities\Page;
use DOMDocument;
use DOMElement;
use DOMNodeList;
use DOMXPath;
class PageContent
{
protected $page;
/**
* PageContent constructor.
*/
public function __construct(Page $page)
{
$this->page = $page;
}
/**
* Update the content of the page with new provided HTML.
*/
public function setNewHTML(string $html)
{
$this->page->html = $this->formatHtml($html);
$this->page->text = $this->toPlainText();
}
/**
* Formats a page's html to be tagged correctly within the system.
*/
protected function formatHtml(string $htmlText): string
{
if ($htmlText == '') {
return $htmlText;
}
libxml_use_internal_errors(true);
$doc = new DOMDocument();
$doc->loadHTML(mb_convert_encoding($htmlText, 'HTML-ENTITIES', 'UTF-8'));
$container = $doc->documentElement;
$body = $container->childNodes->item(0);
$childNodes = $body->childNodes;
// Set ids on top-level nodes
$idMap = [];
foreach ($childNodes as $index => $childNode) {
$this->setUniqueId($childNode, $idMap);
}
// Ensure no duplicate ids within child items
$xPath = new DOMXPath($doc);
$idElems = $xPath->query('//body//*//*[@id]');
foreach ($idElems as $domElem) {
$this->setUniqueId($domElem, $idMap);
}
// Generate inner html as a string
$html = '';
foreach ($childNodes as $childNode) {
$html .= $doc->saveHTML($childNode);
}
return $html;
}
/**
* Set a unique id on the given DOMElement.
* A map for existing ID's should be passed in to check for current existence.
* @param DOMElement $element
* @param array $idMap
*/
protected function setUniqueId($element, array &$idMap)
{
if (get_class($element) !== 'DOMElement') {
return;
}
// Overwrite id if not a BookStack custom id
$existingId = $element->getAttribute('id');
if (strpos($existingId, 'bkmrk') === 0 && !isset($idMap[$existingId])) {
$idMap[$existingId] = true;
return;
}
// Create an unique id for the element
// Uses the content as a basis to ensure output is the same every time
// the same content is passed through.
$contentId = 'bkmrk-' . mb_substr(strtolower(preg_replace('/\s+/', '-', trim($element->nodeValue))), 0, 20);
$newId = urlencode($contentId);
$loopIndex = 0;
while (isset($idMap[$newId])) {
$newId = urlencode($contentId . '-' . $loopIndex);
$loopIndex++;
}
$element->setAttribute('id', $newId);
$idMap[$newId] = true;
}
/**
* Get a plain-text visualisation of this page.
*/
protected function toPlainText(): string
{
$html = $this->render(true);
return strip_tags($html);
}
/**
* Render the page for viewing
*/
public function render(bool $blankIncludes = false) : string
{
$content = $this->page->html;
if (!config('app.allow_content_scripts')) {
$content = $this->escapeScripts($content);
}
if ($blankIncludes) {
$content = $this->blankPageIncludes($content);
} else {
$content = $this->parsePageIncludes($content);
}
return $content;
}
/**
* Parse the headers on the page to get a navigation menu
*/
public function getNavigation(string $htmlContent): array
{
if (empty($htmlContent)) {
return [];
}
libxml_use_internal_errors(true);
$doc = new DOMDocument();
$doc->loadHTML(mb_convert_encoding($htmlContent, 'HTML-ENTITIES', 'UTF-8'));
$xPath = new DOMXPath($doc);
$headers = $xPath->query("//h1|//h2|//h3|//h4|//h5|//h6");
return $headers ? $this->headerNodesToLevelList($headers) : [];
}
/**
* Convert a DOMNodeList into an array of readable header attributes
* with levels normalised to the lower header level.
*/
protected function headerNodesToLevelList(DOMNodeList $nodeList): array
{
$tree = collect($nodeList)->map(function ($header) {
$text = trim(str_replace("\xc2\xa0", '', $header->nodeValue));
$text = mb_substr($text, 0, 100);
return [
'nodeName' => strtolower($header->nodeName),
'level' => intval(str_replace('h', '', $header->nodeName)),
'link' => '#' . $header->getAttribute('id'),
'text' => $text,
];
})->filter(function ($header) {
return mb_strlen($header['text']) > 0;
});
// Shift headers if only smaller headers have been used
$levelChange = ($tree->pluck('level')->min() - 1);
$tree = $tree->map(function ($header) use ($levelChange) {
$header['level'] -= ($levelChange);
return $header;
});
return $tree->toArray();
}
/**
* Remove any page include tags within the given HTML.
*/
protected function blankPageIncludes(string $html) : string
{
return preg_replace("/{{@\s?([0-9].*?)}}/", '', $html);
}
/**
* Parse any include tags "{{@<page_id>#section}}" to be part of the page.
*/
protected function parsePageIncludes(string $html) : string
{
$matches = [];
preg_match_all("/{{@\s?([0-9].*?)}}/", $html, $matches);
foreach ($matches[1] as $index => $includeId) {
$fullMatch = $matches[0][$index];
$splitInclude = explode('#', $includeId, 2);
// Get page id from reference
$pageId = intval($splitInclude[0]);
if (is_nan($pageId)) {
continue;
}
// Find page and skip this if page not found
$matchedPage = Page::visible()->find($pageId);
if ($matchedPage === null) {
$html = str_replace($fullMatch, '', $html);
continue;
}
// If we only have page id, just insert all page html and continue.
if (count($splitInclude) === 1) {
$html = str_replace($fullMatch, $matchedPage->html, $html);
continue;
}
// Create and load HTML into a document
$innerContent = $this->fetchSectionOfPage($matchedPage, $splitInclude[1]);
$html = str_replace($fullMatch, trim($innerContent), $html);
}
return $html;
}
/**
* Fetch the content from a specific section of the given page.
*/
protected function fetchSectionOfPage(Page $page, string $sectionId): string
{
$topLevelTags = ['table', 'ul', 'ol'];
$doc = new DOMDocument();
libxml_use_internal_errors(true);
$doc->loadHTML(mb_convert_encoding('<body>'.$page->html.'</body>', 'HTML-ENTITIES', 'UTF-8'));
// Search included content for the id given and blank out if not exists.
$matchingElem = $doc->getElementById($sectionId);
if ($matchingElem === null) {
return '';
}
// Otherwise replace the content with the found content
// Checks if the top-level wrapper should be included by matching on tag types
$innerContent = '';
$isTopLevel = in_array(strtolower($matchingElem->nodeName), $topLevelTags);
if ($isTopLevel) {
$innerContent .= $doc->saveHTML($matchingElem);
} else {
foreach ($matchingElem->childNodes as $childNode) {
$innerContent .= $doc->saveHTML($childNode);
}
}
libxml_clear_errors();
return $innerContent;
}
/**
* Escape script tags within HTML content.
*/
protected function escapeScripts(string $html) : string
{
if (empty($html)) {
return $html;
}
libxml_use_internal_errors(true);
$doc = new DOMDocument();
$doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
$xPath = new DOMXPath($doc);
// Remove standard script tags
$scriptElems = $xPath->query('//script');
foreach ($scriptElems as $scriptElem) {
$scriptElem->parentNode->removeChild($scriptElem);
}
// Remove data or JavaScript iFrames
$badIframes = $xPath->query('//*[contains(@src, \'data:\')] | //*[contains(@src, \'javascript:\')] | //*[@srcdoc]');
foreach ($badIframes as $badIframe) {
$badIframe->parentNode->removeChild($badIframe);
}
// Remove 'on*' attributes
$onAttributes = $xPath->query('//@*[starts-with(name(), \'on\')]');
foreach ($onAttributes as $attr) {
/** @var \DOMAttr $attr*/
$attrName = $attr->nodeName;
$attr->parentNode->removeAttribute($attrName);
}
$html = '';
$topElems = $doc->documentElement->childNodes->item(0)->childNodes;
foreach ($topElems as $child) {
$html .= $doc->saveHTML($child);
}
return $html;
}
}

View File

@@ -0,0 +1,74 @@
<?php namespace BookStack\Entities\Managers;
use BookStack\Entities\Page;
use BookStack\Entities\PageRevision;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
class PageEditActivity
{
protected $page;
/**
* PageEditActivity constructor.
*/
public function __construct(Page $page)
{
$this->page = $page;
}
/**
* Check if there's active editing being performed on this page.
* @return bool
*/
public function hasActiveEditing(): bool
{
return $this->activePageEditingQuery(60)->count() > 0;
}
/**
* Get a notification message concerning the editing activity on the page.
*/
public function activeEditingMessage(): string
{
$pageDraftEdits = $this->activePageEditingQuery(60)->get();
$count = $pageDraftEdits->count();
$userMessage = $count > 1 ? trans('entities.pages_draft_edit_active.start_a', ['count' => $count]): trans('entities.pages_draft_edit_active.start_b', ['userName' => $pageDraftEdits->first()->createdBy->name]);
$timeMessage = trans('entities.pages_draft_edit_active.time_b', ['minCount'=> 60]);
return trans('entities.pages_draft_edit_active.message', ['start' => $userMessage, 'time' => $timeMessage]);
}
/**
* Get the message to show when the user will be editing one of their drafts.
* @param PageRevision $draft
* @return string
*/
public function getEditingActiveDraftMessage(PageRevision $draft): string
{
$message = trans('entities.pages_editing_draft_notification', ['timeDiff' => $draft->updated_at->diffForHumans()]);
if ($draft->page->updated_at->timestamp <= $draft->updated_at->timestamp) {
return $message;
}
return $message . "\n" . trans('entities.pages_draft_edited_notification');
}
/**
* A query to check for active update drafts on a particular page
* within the last given many minutes.
*/
protected function activePageEditingQuery(int $withinMinutes): Builder
{
$checkTime = Carbon::now()->subMinutes($withinMinutes);
$query = PageRevision::query()
->where('type', '=', 'update_draft')
->where('page_id', '=', $this->page->id)
->where('updated_at', '>', $this->page->updated_at)
->where('created_by', '!=', user()->id)
->where('updated_at', '>=', $checkTime)
->with('createdBy');
return $query;
}
}

View File

@@ -0,0 +1,109 @@
<?php namespace BookStack\Entities\Managers;
use BookStack\Entities\Book;
use BookStack\Entities\Bookshelf;
use BookStack\Entities\Chapter;
use BookStack\Entities\Entity;
use BookStack\Entities\HasCoverImage;
use BookStack\Entities\Page;
use BookStack\Exceptions\NotifyException;
use BookStack\Facades\Activity;
use BookStack\Uploads\AttachmentService;
use BookStack\Uploads\ImageService;
use Exception;
use Illuminate\Contracts\Container\BindingResolutionException;
class TrashCan
{
/**
* Remove a bookshelf from the system.
* @throws Exception
*/
public function destroyShelf(Bookshelf $shelf)
{
$this->destroyCommonRelations($shelf);
$shelf->delete();
}
/**
* Remove a book from the system.
* @throws NotifyException
* @throws BindingResolutionException
*/
public function destroyBook(Book $book)
{
foreach ($book->pages as $page) {
$this->destroyPage($page);
}
foreach ($book->chapters as $chapter) {
$this->destroyChapter($chapter);
}
$this->destroyCommonRelations($book);
$book->delete();
}
/**
* Remove a page from the system.
* @throws NotifyException
*/
public function destroyPage(Page $page)
{
// Check if set as custom homepage & remove setting if not used or throw error if active
$customHome = setting('app-homepage', '0:');
if (intval($page->id) === intval(explode(':', $customHome)[0])) {
if (setting('app-homepage-type') === 'page') {
throw new NotifyException(trans('errors.page_custom_home_deletion'), $page->getUrl());
}
setting()->remove('app-homepage');
}
$this->destroyCommonRelations($page);
// Delete Attached Files
$attachmentService = app(AttachmentService::class);
foreach ($page->attachments as $attachment) {
$attachmentService->deleteFile($attachment);
}
$page->delete();
}
/**
* Remove a chapter from the system.
* @throws Exception
*/
public function destroyChapter(Chapter $chapter)
{
if (count($chapter->pages) > 0) {
foreach ($chapter->pages as $page) {
$page->chapter_id = 0;
$page->save();
}
}
$this->destroyCommonRelations($chapter);
$chapter->delete();
}
/**
* Update entity relations to remove or update outstanding connections.
*/
protected function destroyCommonRelations(Entity $entity)
{
Activity::removeEntity($entity);
$entity->views()->delete();
$entity->permissions()->delete();
$entity->tags()->delete();
$entity->comments()->delete();
$entity->jointPermissions()->delete();
$entity->searchTerms()->delete();
if ($entity instanceof HasCoverImage && $entity->cover) {
$imageService = app()->make(ImageService::class);
$imageService->destroy($entity->cover);
}
}
}

View File

@@ -1,14 +1,41 @@
<?php namespace BookStack;
<?php namespace BookStack\Entities;
class Page extends Entity
use BookStack\Uploads\Attachment;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Permissions;
/**
* Class Page
* @property int $chapter_id
* @property string $html
* @property string $markdown
* @property string $text
* @property bool $template
* @property bool $draft
* @property int $revision_count
* @property Chapter $chapter
* @property Collection $attachments
*/
class Page extends BookChild
{
protected $fillable = ['name', 'html', 'priority', 'markdown'];
protected $simpleAttributes = ['name', 'id', 'slug'];
protected $with = ['book'];
public $textField = 'text';
/**
* Get the entities that are visible to the current user.
*/
public function scopeVisible(Builder $query)
{
$query = Permissions::enforceDraftVisiblityOnQuery($query);
return parent::scopeVisible($query);
}
/**
* Converts this page into a simplified array.
* @return mixed
@@ -21,17 +48,16 @@ class Page extends Entity
}
/**
* Get the book this page sits in.
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
* Get the parent item
*/
public function book()
public function parent(): Entity
{
return $this->belongsTo(Book::class);
return $this->chapter_id ? $this->chapter : $this->book;
}
/**
* Get the chapter that this page is in, If applicable.
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
* @return BelongsTo
*/
public function chapter()
{
@@ -53,12 +79,12 @@ class Page extends Entity
*/
public function revisions()
{
return $this->hasMany(PageRevision::class)->where('type', '=', 'version')->orderBy('created_at', 'desc');
return $this->hasMany(PageRevision::class)->where('type', '=', 'version')->orderBy('created_at', 'desc')->orderBy('id', 'desc');
}
/**
* Get the attachments assigned to this page.
* @return \Illuminate\Database\Eloquent\Relations\HasMany
* @return HasMany
*/
public function attachments()
{
@@ -76,32 +102,20 @@ class Page extends Entity
$midText = $this->draft ? '/draft/' : '/page/';
$idComponent = $this->draft ? $this->id : urlencode($this->slug);
$url = '/books/' . urlencode($bookSlug) . $midText . $idComponent;
if ($path !== false) {
return baseUrl('/books/' . urlencode($bookSlug) . $midText . $idComponent . '/' . trim($path, '/'));
$url .= '/' . trim($path, '/');
}
return baseUrl('/books/' . urlencode($bookSlug) . $midText . $idComponent);
return url($url);
}
/**
* Get an excerpt of this page's content to the specified length.
* @param int $length
* @return mixed
* Get the current revision for the page if existing
* @return PageRevision|null
*/
public function getExcerpt($length = 100)
public function getCurrentRevision()
{
$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";
return $this->revisions()->first();
}
}

View File

@@ -1,5 +1,22 @@
<?php namespace BookStack;
<?php namespace BookStack\Entities;
use BookStack\Auth\User;
use BookStack\Model;
use Carbon\Carbon;
/**
* Class PageRevision
* @property int $page_id
* @property string $slug
* @property string $book_slug
* @property int $created_by
* @property Carbon $created_at
* @property string $type
* @property string $summary
* @property string $markdown
* @property string $html
* @property int $revision_number
*/
class PageRevision extends Model
{
protected $fillable = ['name', 'html', 'text', 'markdown', 'summary'];
@@ -38,13 +55,18 @@ class PageRevision extends Model
/**
* Get the previous revision for the same page if existing
* @return \BookStack\PageRevision|null
* @return \BookStack\Entities\PageRevision|null
*/
public function getPrevious()
{
if ($id = static::where('page_id', '=', $this->page_id)->where('id', '<', $this->id)->max('id')) {
return static::find($id);
$id = static::newQuery()->where('page_id', '=', $this->page_id)
->where('id', '<', $this->id)
->max('id');
if ($id) {
return static::query()->find($id);
}
return null;
}

View File

@@ -0,0 +1,119 @@
<?php
namespace BookStack\Entities\Repos;
use BookStack\Actions\TagRepo;
use BookStack\Entities\Book;
use BookStack\Entities\Entity;
use BookStack\Entities\HasCoverImage;
use BookStack\Exceptions\ImageUploadException;
use BookStack\Uploads\ImageRepo;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Collection;
class BaseRepo
{
protected $tagRepo;
protected $imageRepo;
/**
* BaseRepo constructor.
* @param $tagRepo
*/
public function __construct(TagRepo $tagRepo, ImageRepo $imageRepo)
{
$this->tagRepo = $tagRepo;
$this->imageRepo = $imageRepo;
}
/**
* Create a new entity in the system
*/
public function create(Entity $entity, array $input)
{
$entity->fill($input);
$entity->forceFill([
'created_by' => user()->id,
'updated_by' => user()->id,
]);
$entity->refreshSlug();
$entity->save();
if (isset($input['tags'])) {
$this->tagRepo->saveTagsToEntity($entity, $input['tags']);
}
$entity->rebuildPermissions();
$entity->indexForSearch();
}
/**
* Update the given entity.
*/
public function update(Entity $entity, array $input)
{
$entity->fill($input);
$entity->updated_by = user()->id;
if ($entity->isDirty('name')) {
$entity->refreshSlug();
}
$entity->save();
if (isset($input['tags'])) {
$this->tagRepo->saveTagsToEntity($entity, $input['tags']);
}
$entity->rebuildPermissions();
$entity->indexForSearch();
}
/**
* Update the given items' cover image, or clear it.
* @throws ImageUploadException
* @throws \Exception
*/
public function updateCoverImage(HasCoverImage $entity, ?UploadedFile $coverImage, bool $removeImage = false)
{
if ($coverImage) {
$this->imageRepo->destroyImage($entity->cover);
$image = $this->imageRepo->saveNew($coverImage, 'cover_book', $entity->id, 512, 512, true);
$entity->cover()->associate($image);
$entity->save();
}
if ($removeImage) {
$this->imageRepo->destroyImage($entity->cover);
$entity->image_id = 0;
$entity->save();
}
}
/**
* Update the permissions of an entity.
*/
public function updatePermissions(Entity $entity, bool $restricted, Collection $permissions = null)
{
$entity->restricted = $restricted;
$entity->permissions()->delete();
if (!is_null($permissions)) {
$entityPermissionData = $permissions->flatMap(function ($restrictions, $roleId) {
return collect($restrictions)->keys()->map(function ($action) use ($roleId) {
return [
'role_id' => $roleId,
'action' => strtolower($action),
] ;
});
});
$entity->permissions()->createMany($entityPermissionData);
}
$entity->save();
$entity->rebuildPermissions();
}
}

View File

@@ -0,0 +1,134 @@
<?php namespace BookStack\Entities\Repos;
use BookStack\Actions\TagRepo;
use BookStack\Entities\Book;
use BookStack\Entities\Managers\TrashCan;
use BookStack\Exceptions\ImageUploadException;
use BookStack\Exceptions\NotFoundException;
use BookStack\Exceptions\NotifyException;
use BookStack\Uploads\ImageRepo;
use Exception;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Collection;
class BookRepo
{
protected $baseRepo;
protected $tagRepo;
protected $imageRepo;
/**
* BookRepo constructor.
* @param $tagRepo
*/
public function __construct(BaseRepo $baseRepo, TagRepo $tagRepo, ImageRepo $imageRepo)
{
$this->baseRepo = $baseRepo;
$this->tagRepo = $tagRepo;
$this->imageRepo = $imageRepo;
}
/**
* Get all books in a paginated format.
*/
public function getAllPaginated(int $count = 20, string $sort = 'name', string $order = 'asc'): LengthAwarePaginator
{
return Book::visible()->orderBy($sort, $order)->paginate($count);
}
/**
* Get the books that were most recently viewed by this user.
*/
public function getRecentlyViewed(int $count = 20): Collection
{
return Book::visible()->withLastView()
->having('last_viewed_at', '>', 0)
->orderBy('last_viewed_at', 'desc')
->take($count)->get();
}
/**
* Get the most popular books in the system.
*/
public function getPopular(int $count = 20): Collection
{
return Book::visible()->withViewCount()
->having('view_count', '>', 0)
->orderBy('view_count', 'desc')
->take($count)->get();
}
/**
* Get the most recently created books from the system.
*/
public function getRecentlyCreated(int $count = 20): Collection
{
return Book::visible()->orderBy('created_at', 'desc')
->take($count)->get();
}
/**
* Get a book by its slug.
*/
public function getBySlug(string $slug): Book
{
$book = Book::visible()->where('slug', '=', $slug)->first();
if ($book === null) {
throw new NotFoundException(trans('errors.book_not_found'));
}
return $book;
}
/**
* Create a new book in the system
*/
public function create(array $input): Book
{
$book = new Book();
$this->baseRepo->create($book, $input);
return $book;
}
/**
* Update the given book.
*/
public function update(Book $book, array $input): Book
{
$this->baseRepo->update($book, $input);
return $book;
}
/**
* Update the given book's cover image, or clear it.
* @throws ImageUploadException
* @throws Exception
*/
public function updateCoverImage(Book $book, ?UploadedFile $coverImage, bool $removeImage = false)
{
$this->baseRepo->updateCoverImage($book, $coverImage, $removeImage);
}
/**
* Update the permissions of a book.
*/
public function updatePermissions(Book $book, bool $restricted, Collection $permissions = null)
{
$this->baseRepo->updatePermissions($book, $restricted, $permissions);
}
/**
* Remove a book from the system.
* @throws NotifyException
* @throws BindingResolutionException
*/
public function destroy(Book $book)
{
$trashCan = new TrashCan();
$trashCan->destroyBook($book);
}
}

View File

@@ -0,0 +1,177 @@
<?php namespace BookStack\Entities\Repos;
use BookStack\Entities\Book;
use BookStack\Entities\Bookshelf;
use BookStack\Entities\Managers\TrashCan;
use BookStack\Exceptions\ImageUploadException;
use BookStack\Exceptions\NotFoundException;
use Exception;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Collection;
class BookshelfRepo
{
protected $baseRepo;
/**
* BookshelfRepo constructor.
* @param $baseRepo
*/
public function __construct(BaseRepo $baseRepo)
{
$this->baseRepo = $baseRepo;
}
/**
* Get all bookshelves in a paginated format.
*/
public function getAllPaginated(int $count = 20, string $sort = 'name', string $order = 'asc'): LengthAwarePaginator
{
return Bookshelf::visible()->with('visibleBooks')
->orderBy($sort, $order)->paginate($count);
}
/**
* Get the bookshelves that were most recently viewed by this user.
*/
public function getRecentlyViewed(int $count = 20): Collection
{
return Bookshelf::visible()->withLastView()
->having('last_viewed_at', '>', 0)
->orderBy('last_viewed_at', 'desc')
->take($count)->get();
}
/**
* Get the most popular bookshelves in the system.
*/
public function getPopular(int $count = 20): Collection
{
return Bookshelf::visible()->withViewCount()
->having('view_count', '>', 0)
->orderBy('view_count', 'desc')
->take($count)->get();
}
/**
* Get the most recently created bookshelves from the system.
*/
public function getRecentlyCreated(int $count = 20): Collection
{
return Bookshelf::visible()->orderBy('created_at', 'desc')
->take($count)->get();
}
/**
* Get a shelf by its slug.
*/
public function getBySlug(string $slug): Bookshelf
{
$shelf = Bookshelf::visible()->where('slug', '=', $slug)->first();
if ($shelf === null) {
throw new NotFoundException(trans('errors.bookshelf_not_found'));
}
return $shelf;
}
/**
* Create a new shelf in the system.
*/
public function create(array $input, array $bookIds): Bookshelf
{
$shelf = new Bookshelf();
$this->baseRepo->create($shelf, $input);
$this->updateBooks($shelf, $bookIds);
return $shelf;
}
/**
* Create a new shelf in the system.
*/
public function update(Bookshelf $shelf, array $input, ?array $bookIds): Bookshelf
{
$this->baseRepo->update($shelf, $input);
if (!is_null($bookIds)) {
$this->updateBooks($shelf, $bookIds);
}
return $shelf;
}
/**
* Update which books are assigned to this shelf by
* syncing the given book ids.
* Function ensures the books are visible to the current user and existing.
*/
protected function updateBooks(Bookshelf $shelf, array $bookIds)
{
$numericIDs = collect($bookIds)->map(function ($id) {
return intval($id);
});
$syncData = Book::visible()
->whereIn('id', $bookIds)
->get(['id'])->pluck('id')->mapWithKeys(function ($bookId) use ($numericIDs) {
return [$bookId => ['order' => $numericIDs->search($bookId)]];
});
$shelf->books()->sync($syncData);
}
/**
* Update the given shelf cover image, or clear it.
* @throws ImageUploadException
* @throws Exception
*/
public function updateCoverImage(Bookshelf $shelf, ?UploadedFile $coverImage, bool $removeImage = false)
{
$this->baseRepo->updateCoverImage($shelf, $coverImage, $removeImage);
}
/**
* Update the permissions of a bookshelf.
*/
public function updatePermissions(Bookshelf $shelf, bool $restricted, Collection $permissions = null)
{
$this->baseRepo->updatePermissions($shelf, $restricted, $permissions);
}
/**
* Copy down the permissions of the given shelf to all child books.
*/
public function copyDownPermissions(Bookshelf $shelf, $checkUserPermissions = true): int
{
$shelfPermissions = $shelf->permissions()->get(['role_id', 'action'])->toArray();
$shelfBooks = $shelf->books()->get(['id', 'restricted']);
$updatedBookCount = 0;
/** @var Book $book */
foreach ($shelfBooks as $book) {
if ($checkUserPermissions && !userCan('restrictions-manage', $book)) {
continue;
}
$book->permissions()->delete();
$book->restricted = $shelf->restricted;
$book->permissions()->createMany($shelfPermissions);
$book->save();
$book->rebuildPermissions();
$updatedBookCount++;
}
return $updatedBookCount;
}
/**
* Remove a bookshelf from the system.
* @throws Exception
*/
public function destroy(Bookshelf $shelf)
{
$trashCan = new TrashCan();
$trashCan->destroyShelf($shelf);
}
}

View File

@@ -0,0 +1,108 @@
<?php namespace BookStack\Entities\Repos;
use BookStack\Entities\Book;
use BookStack\Entities\Chapter;
use BookStack\Entities\Managers\BookContents;
use BookStack\Entities\Managers\TrashCan;
use BookStack\Exceptions\MoveOperationException;
use BookStack\Exceptions\NotFoundException;
use BookStack\Exceptions\NotifyException;
use Exception;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
class ChapterRepo
{
protected $baseRepo;
/**
* ChapterRepo constructor.
* @param $baseRepo
*/
public function __construct(BaseRepo $baseRepo)
{
$this->baseRepo = $baseRepo;
}
/**
* Get a chapter via the slug.
* @throws NotFoundException
*/
public function getBySlug(string $bookSlug, string $chapterSlug): Chapter
{
$chapter = Chapter::visible()->whereSlugs($bookSlug, $chapterSlug)->first();
if ($chapter === null) {
throw new NotFoundException(trans('errors.chapter_not_found'));
}
return $chapter;
}
/**
* Create a new chapter in the system.
*/
public function create(array $input, Book $parentBook): Chapter
{
$chapter = new Chapter();
$chapter->book_id = $parentBook->id;
$chapter->priority = (new BookContents($parentBook))->getLastPriority() + 1;
$this->baseRepo->create($chapter, $input);
return $chapter;
}
/**
* Update the given chapter.
*/
public function update(Chapter $chapter, array $input): Chapter
{
$this->baseRepo->update($chapter, $input);
return $chapter;
}
/**
* Update the permissions of a chapter.
*/
public function updatePermissions(Chapter $chapter, bool $restricted, Collection $permissions = null)
{
$this->baseRepo->updatePermissions($chapter, $restricted, $permissions);
}
/**
* Remove a chapter from the system.
* @throws Exception
*/
public function destroy(Chapter $chapter)
{
$trashCan = new TrashCan();
$trashCan->destroyChapter($chapter);
}
/**
* Move the given chapter into a new parent book.
* The $parentIdentifier must be a string of the following format:
* 'book:<id>' (book:5)
* @throws MoveOperationException
*/
public function move(Chapter $chapter, string $parentIdentifier): Book
{
$stringExploded = explode(':', $parentIdentifier);
$entityType = $stringExploded[0];
$entityId = intval($stringExploded[1]);
if ($entityType !== 'book') {
throw new MoveOperationException('Chapters can only be moved into books');
}
$parent = Book::visible()->where('id', '=', $entityId)->first();
if ($parent === null) {
throw new MoveOperationException('Book to move chapter into not found');
}
$chapter->changeBook($parent->id);
$chapter->rebuildPermissions();
return $parent;
}
}

View File

@@ -0,0 +1,461 @@
<?php namespace BookStack\Entities\Repos;
use BookStack\Entities\Book;
use BookStack\Entities\Chapter;
use BookStack\Entities\Entity;
use BookStack\Entities\Managers\BookContents;
use BookStack\Entities\Managers\PageContent;
use BookStack\Entities\Managers\TrashCan;
use BookStack\Entities\Page;
use BookStack\Entities\PageRevision;
use BookStack\Exceptions\MoveOperationException;
use BookStack\Exceptions\NotFoundException;
use BookStack\Exceptions\NotifyException;
use BookStack\Exceptions\PermissionsException;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
class PageRepo
{
protected $baseRepo;
/**
* PageRepo constructor.
*/
public function __construct(BaseRepo $baseRepo)
{
$this->baseRepo = $baseRepo;
}
/**
* Get a page by ID.
* @throws NotFoundException
*/
public function getById(int $id): Page
{
$page = Page::visible()->with(['book'])->find($id);
if (!$page) {
throw new NotFoundException(trans('errors.page_not_found'));
}
return $page;
}
/**
* Get a page its book and own slug.
* @throws NotFoundException
*/
public function getBySlug(string $bookSlug, string $pageSlug): Page
{
$page = Page::visible()->whereSlugs($bookSlug, $pageSlug)->first();
if (!$page) {
throw new NotFoundException(trans('errors.page_not_found'));
}
return $page;
}
/**
* Get a page by its old slug but checking the revisions table
* for the last revision that matched the given page and book slug.
*/
public function getByOldSlug(string $bookSlug, string $pageSlug): ?Page
{
$revision = PageRevision::query()
->whereHas('page', function (Builder $query) {
$query->visible();
})
->where('slug', '=', $pageSlug)
->where('type', '=', 'version')
->where('book_slug', '=', $bookSlug)
->orderBy('created_at', 'desc')
->with('page')
->first();
return $revision ? $revision->page : null;
}
/**
* Get pages that have been marked as a template.
*/
public function getTemplates(int $count = 10, int $page = 1, string $search = ''): LengthAwarePaginator
{
$query = Page::visible()
->where('template', '=', true)
->orderBy('name', 'asc')
->skip(($page - 1) * $count)
->take($count);
if ($search) {
$query->where('name', 'like', '%' . $search . '%');
}
$paginator = $query->paginate($count, ['*'], 'page', $page);
$paginator->withPath('/templates');
return $paginator;
}
/**
* Get a parent item via slugs.
*/
public function getParentFromSlugs(string $bookSlug, string $chapterSlug = null): Entity
{
if ($chapterSlug !== null) {
return $chapter = Chapter::visible()->whereSlugs($bookSlug, $chapterSlug)->firstOrFail();
}
return Book::visible()->where('slug', '=', $bookSlug)->firstOrFail();
}
/**
* Get the draft copy of the given page for the current user.
*/
public function getUserDraft(Page $page): ?PageRevision
{
$revision = $this->getUserDraftQuery($page)->first();
return $revision;
}
/**
* Get a new draft page belonging to the given parent entity.
*/
public function getNewDraftPage(Entity $parent)
{
$page = (new Page())->forceFill([
'name' => trans('entities.pages_initial_name'),
'created_by' => user()->id,
'updated_by' => user()->id,
'draft' => true,
]);
if ($parent instanceof Chapter) {
$page->chapter_id = $parent->id;
$page->book_id = $parent->book_id;
} else {
$page->book_id = $parent->id;
}
$page->save();
$page->refresh()->rebuildPermissions();
return $page;
}
/**
* Publish a draft page to make it a live, non-draft page.
*/
public function publishDraft(Page $draft, array $input): Page
{
$this->baseRepo->update($draft, $input);
if (isset($input['template']) && userCan('templates-manage')) {
$draft->template = ($input['template'] === 'true');
}
$pageContent = new PageContent($draft);
$pageContent->setNewHTML($input['html']);
$draft->draft = false;
$draft->revision_count = 1;
$draft->priority = $this->getNewPriority($draft);
$draft->refreshSlug();
$draft->save();
$this->savePageRevision($draft, trans('entities.pages_initial_revision'));
$draft->indexForSearch();
return $draft->refresh();
}
/**
* Update a page in the system.
*/
public function update(Page $page, array $input): Page
{
// Hold the old details to compare later
$oldHtml = $page->html;
$oldName = $page->name;
if (isset($input['template']) && userCan('templates-manage')) {
$page->template = ($input['template'] === 'true');
}
$this->baseRepo->update($page, $input);
// Update with new details
$page->fill($input);
$pageContent = new PageContent($page);
$pageContent->setNewHTML($input['html']);
$page->revision_count++;
if (setting('app-editor') !== 'markdown') {
$page->markdown = '';
}
$page->save();
// Remove all update drafts for this user & page.
$this->getUserDraftQuery($page)->delete();
// Save a revision after updating
$summary = $input['summary'] ?? null;
if ($oldHtml !== $input['html'] || $oldName !== $input['name'] || $summary !== null) {
$this->savePageRevision($page, $summary);
}
return $page;
}
/**
* Saves a page revision into the system.
*/
protected function savePageRevision(Page $page, string $summary = null)
{
$revision = new PageRevision($page->toArray());
if (setting('app-editor') !== 'markdown') {
$revision->markdown = '';
}
$revision->page_id = $page->id;
$revision->slug = $page->slug;
$revision->book_slug = $page->book->slug;
$revision->created_by = user()->id;
$revision->created_at = $page->updated_at;
$revision->type = 'version';
$revision->summary = $summary;
$revision->revision_number = $page->revision_count;
$revision->save();
$this->deleteOldRevisions($page);
return $revision;
}
/**
* Save a page update draft.
*/
public function updatePageDraft(Page $page, array $input)
{
// If the page itself is a draft simply update that
if ($page->draft) {
$page->fill($input);
if (isset($input['html'])) {
$content = new PageContent($page);
$content->setNewHTML($input['html']);
}
$page->save();
return $page;
}
// Otherwise save the data to a revision
$draft = $this->getPageRevisionToUpdate($page);
$draft->fill($input);
if (setting('app-editor') !== 'markdown') {
$draft->markdown = '';
}
$draft->save();
return $draft;
}
/**
* Destroy a page from the system.
* @throws NotifyException
*/
public function destroy(Page $page)
{
$trashCan = new TrashCan();
$trashCan->destroyPage($page);
}
/**
* Restores a revision's content back into a page.
*/
public function restoreRevision(Page $page, int $revisionId): Page
{
$page->revision_count++;
$this->savePageRevision($page);
$revision = $page->revisions()->where('id', '=', $revisionId)->first();
$page->fill($revision->toArray());
$content = new PageContent($page);
$content->setNewHTML($page->html);
$page->updated_by = user()->id;
$page->refreshSlug();
$page->save();
$page->indexForSearch();
return $page;
}
/**
* Move the given page into a new parent book or chapter.
* The $parentIdentifier must be a string of the following format:
* 'book:<id>' (book:5)
* @throws MoveOperationException
* @throws PermissionsException
*/
public function move(Page $page, string $parentIdentifier): Book
{
$parent = $this->findParentByIdentifier($parentIdentifier);
if ($parent === null) {
throw new MoveOperationException('Book or chapter to move page into not found');
}
if (!userCan('page-create', $parent)) {
throw new PermissionsException('User does not have permission to create a page within the new parent');
}
$page->chapter_id = ($parent instanceof Chapter) ? $parent->id : null;
$page->changeBook($parent instanceof Book ? $parent->id : $parent->book->id);
$page->rebuildPermissions();
return ($parent instanceof Book ? $parent : $parent->book);
}
/**
* Copy an existing page in the system.
* Optionally providing a new parent via string identifier and a new name.
* @throws MoveOperationException
* @throws PermissionsException
*/
public function copy(Page $page, string $parentIdentifier = null, string $newName = null): Page
{
$parent = $parentIdentifier ? $this->findParentByIdentifier($parentIdentifier) : $page->parent();
if ($parent === null) {
throw new MoveOperationException('Book or chapter to move page into not found');
}
if (!userCan('page-create', $parent)) {
throw new PermissionsException('User does not have permission to create a page within the new parent');
}
$copyPage = $this->getNewDraftPage($parent);
$pageData = $page->getAttributes();
// Update name
if (!empty($newName)) {
$pageData['name'] = $newName;
}
// Copy tags from previous page if set
if ($page->tags) {
$pageData['tags'] = [];
foreach ($page->tags as $tag) {
$pageData['tags'][] = ['name' => $tag->name, 'value' => $tag->value];
}
}
return $this->publishDraft($copyPage, $pageData);
}
/**
* Find a page parent entity via a identifier string in the format:
* {type}:{id}
* Example: (book:5)
* @throws MoveOperationException
*/
protected function findParentByIdentifier(string $identifier): ?Entity
{
$stringExploded = explode(':', $identifier);
$entityType = $stringExploded[0];
$entityId = intval($stringExploded[1]);
if ($entityType !== 'book' && $entityType !== 'chapter') {
throw new MoveOperationException('Pages can only be in books or chapters');
}
$parentClass = $entityType === 'book' ? Book::class : Chapter::class;
return $parentClass::visible()->where('id', '=', $entityId)->first();
}
/**
* Update the permissions of a page.
*/
public function updatePermissions(Page $page, bool $restricted, Collection $permissions = null)
{
$this->baseRepo->updatePermissions($page, $restricted, $permissions);
}
/**
* Change the page's parent to the given entity.
*/
protected function changeParent(Page $page, Entity $parent)
{
$book = ($parent instanceof Book) ? $parent : $parent->book;
$page->chapter_id = ($parent instanceof Chapter) ? $parent->id : 0;
$page->save();
if ($page->book->id !== $book->id) {
$page->changeBook($book->id);
}
$page->load('book');
$book->rebuildPermissions();
}
/**
* Get a page revision to update for the given page.
* Checks for an existing revisions before providing a fresh one.
*/
protected function getPageRevisionToUpdate(Page $page): PageRevision
{
$drafts = $this->getUserDraftQuery($page)->get();
if ($drafts->count() > 0) {
return $drafts->first();
}
$draft = new PageRevision();
$draft->page_id = $page->id;
$draft->slug = $page->slug;
$draft->book_slug = $page->book->slug;
$draft->created_by = user()->id;
$draft->type = 'update_draft';
return $draft;
}
/**
* Delete old revisions, for the given page, from the system.
*/
protected function deleteOldRevisions(Page $page)
{
$revisionLimit = config('app.revision_limit');
if ($revisionLimit === false) {
return;
}
$revisionsToDelete = PageRevision::query()
->where('page_id', '=', $page->id)
->orderBy('created_at', 'desc')
->skip(intval($revisionLimit))
->take(10)
->get(['id']);
if ($revisionsToDelete->count() > 0) {
PageRevision::query()->whereIn('id', $revisionsToDelete->pluck('id'))->delete();
}
}
/**
* Get a new priority for a page
*/
protected function getNewPriority(Page $page): int
{
if ($page->parent() instanceof Chapter) {
$lastPage = $page->parent()->pages('desc')->first();
return $lastPage ? $lastPage->priority + 1 : 0;
}
return (new BookContents($page->book))->getLastPriority() + 1;
}
/**
* Get the query to find the user's draft copies of the given page.
*/
protected function getUserDraftQuery(Page $page)
{
return PageRevision::query()->where('created_by', '=', user()->id)
->where('type', 'update_draft')
->where('page_id', '=', $page->id)
->orderBy('created_at', 'desc');
}
}

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