Compare commits

...

1039 Commits

Author SHA1 Message Date
Dan Brown
c9ed32e518 Updated version and assets for release v23.02.2 2023-03-25 12:27:32 +00:00
Dan Brown
6b4c3a0969 Merge branch 'v23.02-branch' into release 2023-03-25 12:27:05 +00:00
Dan Brown
0a0fdd7f3e Fixed delete role failing with no migrate role provided
For #4128
2023-03-25 12:21:22 +00:00
Dan Brown
3410cf21cb Updated php deps 2023-03-25 12:21:04 +00:00
Dan Brown
6e284d7a6c Fixed issue with user delete ownership not migrating
Caused by input not being part of the submitted form.
Updated test to ensure the input is within a form.
For #4124
2023-03-25 12:20:49 +00:00
Dan Brown
ea7914422c Updated php deps 2023-03-25 12:20:13 +00:00
Dan Brown
509cab3e28 Merged latest crowdin changes 2023-03-25 12:18:45 +00:00
Dan Brown
7b5111571c Removed bookstack wording instances in color setting options 2023-02-28 01:01:25 +00:00
Dan Brown
2dad92d1bd Updated version and assets for release v23.02.1 2023-02-27 19:26:13 +00:00
Dan Brown
c1fb7ab7dc Merge branch 'development' into release 2023-02-27 19:23:33 +00:00
Dan Brown
3464f5e961 Updated translations with latest Crowdin changes (#4066) 2023-02-27 19:19:03 +00:00
Dan Brown
7c27d26161 Fixed language locale setting issue
Attempted to access an array that had been filtered and therefore could
have holes within, including as position 0 which would then be
accessed.
Also added cs language to internal map

Related to #4068
2023-02-27 19:14:45 +00:00
Dan Brown
98315f3899 Updated version and assets for release v23.02 2023-02-26 11:03:49 +00:00
Dan Brown
8c82aaabd6 Merge branch 'development' into release 2023-02-26 11:02:56 +00:00
Dan Brown
c7e33d1981 Fixed caching issue when running tests 2023-02-26 10:50:14 +00:00
Dan Brown
ba21b54195 Updated translations with latest Crowdin changes (#4025) 2023-02-26 10:36:15 +00:00
Dan Brown
f35c42b0b8 Updated php deps and translaters in prep for v23.02 2023-02-25 17:35:21 +00:00
Dan Brown
b88b1bef2c Added updated_at index to pages table
This has a large impact on some areas where latest updated pages are
shown, such as the homepage for example.
2023-02-23 23:06:12 +00:00
Dan Brown
8abb41abbd Added caching to the loading of system roles
Admin system role was being loaded for each permission check performed.
This caches the fetching for the request lifetime.
2023-02-23 23:01:03 +00:00
Dan Brown
a031edec16 Fixed old deprecated encoding convert on HTML doc load 2023-02-23 22:59:26 +00:00
Dan Brown
2724b2867b Merge pull request #4062 from BookStackApp/settings_perf
Changed the way settings are loaded
2023-02-23 22:22:32 +00:00
Dan Brown
8bebea4cca Changed the way settings are loaded
This new method batch-loads them from the database, and removes the
cache-layer with the intention that a couple of batch fetches from the
DB is more efficient than hitting the cache each time.
2023-02-23 22:14:47 +00:00
Dan Brown
6545afacd6 Changed autosave handling for better editor performance
This changes how the editors interact with the parent page-editor
compontent, which handles auto-saving.
Instead of blasting the full editor content upon any change to that
parent compontent, the editors just alert of a change, without the
content. The parent compontent then requests the editor content from the
editor component when it needs that data for an autosave.

For #3981
2023-02-23 12:30:27 +00:00
Dan Brown
31495758a9 Made page-save HTML formatting much more efficient
Replaced the existing xpath-heavy system with a more manual traversal
approach. Fixes following slow areas of old system:
- Old system would repeat ID-setting action for elements (Headers could
  be processed up to three times).
- Old system had a few very open xpath queries for headers.
- Old system would update links on every ID change, which triggers it's
  own xpath query for links, leading to exponential scaling issues.

New system only does one xpath query for links when changes are needed.
Added test to cover.

For #3932
2023-02-22 14:32:40 +00:00
Dan Brown
c80396136f Increased attachment link limit from 192 to 2k
Added test to cover.
Did attempt a 64k limit, but values over 2k significantly increase
chance of other issues since this URL may be used in redirect headers.
Would rather catch issues in-app.

For #4044
2023-02-20 13:05:23 +00:00
Dan Brown
8da3e64039 Updated language files to remove literal "1" values
This is to encourge the ":count" values to be used instead of 1s in the
translated variants so that non-pluralised languages are hardcoded with
"1"s in their content, even when not used in a singular context.

For #4040
2023-02-20 12:05:52 +00:00
Dan Brown
c1167f8821 Merge pull request #4051 from BookStackApp/roles_api
User Roles API Endpoint
2023-02-19 16:11:30 +00:00
Dan Brown
4176b598ce Fixed unselectable checkbox role form options 2023-02-19 16:03:50 +00:00
Dan Brown
950c02e996 Added role API responses & requests
Also applied other slight tweaks and comment updates based upon manual
endpoint testing.
2023-02-19 15:58:29 +00:00
Dan Brown
9502f349a2 Updated test to have reliable check ordering 2023-02-18 19:01:38 +00:00
Dan Brown
3c3c2ae9b5 Set order to role permissions API response 2023-02-18 18:50:01 +00:00
Dan Brown
723f108bd9 Aded roles API controller methods
Altered & updated permissions repo, and existing connected
RoleController to suit.
Also extracts in-app success notifications to auto activity system.
Tweaked tests where required.
2023-02-18 18:36:34 +00:00
Dan Brown
55456a57d6 Added tests for not-yet-built role API endpoints 2023-02-18 13:51:18 +00:00
Dan Brown
fd45d280b4 Updated tinymce from 6.1.0 to 6.3.1 2023-02-17 21:16:42 +00:00
Dan Brown
524adce654 Merge pull request #4049 from BookStackApp/shelf_book_sort_updates
Shelf book sort improvements
2023-02-17 16:20:59 +00:00
Dan Brown
f799c9b260 Applied shelf book sort changes from testing
Added better labelling of sort lists for screen readers.
Fadded out sort-item action buttons until hovering for a cleaner look.
2023-02-17 16:18:24 +00:00
Dan Brown
9c26ccf43d Added shelf book item sort action functionality
Adds JS logic, and dropdown action list, for quick-sorting the book
shelf list in addition to handling the book item action buttons.
2023-02-17 15:53:24 +00:00
Dan Brown
71a09bcf6e Started accessible controls for shelf book sort
Added buttons and fit to design.
Added new icon variations to support.
Extracted book item to own view and setup for future auto sorts.
2023-02-17 15:05:28 +00:00
Dan Brown
af31a6fc1b Made sendmail command configurable
For #4001
Added simple test to cover config option.
2023-02-17 14:25:38 +00:00
Dan Brown
08b39500b3 Fixed gallery images not visible until draft publish
For #4028
2023-02-16 17:57:34 +00:00
Dan Brown
f9fcc9f3c7 Updated php deps 2023-02-16 17:27:09 +00:00
Dan Brown
0812184995 Added torutec as sponsor, updated license and version 2023-02-14 16:16:08 +00:00
Dan Brown
646f8f60c0 Merge pull request #4032 from BookStackApp/favicon
Generate favicon.ico file
2023-02-09 21:37:38 +00:00
Dan Brown
f333db8e4f Added control-upon-access of the default favicon.ico file 2023-02-09 21:16:27 +00:00
Dan Brown
da42fc7457 Added default favicon creation upon access. 2023-02-09 20:57:35 +00:00
Dan Brown
48f1934387 Updated favicon gen to use png-based ICO
From testing, worked on Firefox, Chrome, Gnome Web
2023-02-09 17:47:33 +00:00
Dan Brown
2845e0003e Got favicons better supported, can't get transparency right
Digging deeper, I don't think PHPGD supports 32bit bmp output which
complicates matters.
2023-02-09 15:14:41 +00:00
Dan Brown
1a189640f1 Integrated favicon handler with correct files & actions
Format does not look 100% correct though, won't show in Firefox/gimp.
2023-02-09 13:24:43 +00:00
Dan Brown
420f89af99 Built custom favicon.ico file creator
Followed wikipedia-defined ICO file format info, and used with
Intervention's good bmp support, to create a working proof-of-concept.
2023-02-08 23:06:42 +00:00
Dan Brown
da1a66abd3 Extracted test file handling to its own class
Closes #3995
2023-02-08 14:39:13 +00:00
Dan Brown
5d18e7df79 Removed deprecated syntax in old migration file 2023-02-08 13:20:00 +00:00
Dan Brown
ba25a3e1b7 Merge pull request #4021 from BookStackApp/laravel9
Upgrade framework to Laravel 9
2023-02-07 12:11:04 +00:00
Dan Brown
bc18dc7da6 Removed parallel testing, updated predis
Parallel testing paratest library caused issues due to a single version
not being compatibile across our php range. Removed for now as not
really worth the faff to get compatible.
2023-02-07 11:50:59 +00:00
Dan Brown
5e8ec56196 Fixed issues found from tests 2023-02-06 20:41:33 +00:00
Dan Brown
9ca088a4e2 Fixed static analysis issues 2023-02-06 20:00:44 +00:00
Dan Brown
008e7a4d25 Followed Laravel 9 update steps and file changes 2023-02-06 16:58:29 +00:00
Dan Brown
ce9b536b78 Updated version and assets for release v23.01.1 2023-02-02 12:29:26 +00:00
Dan Brown
d9c50e5bc1 Merge branch 'development' into release 2023-02-02 12:29:07 +00:00
Dan Brown
6e6f113336 Merge branch 'development' of github.com:BookStackApp/BookStack into development 2023-02-02 12:17:06 +00:00
Dan Brown
f7441e2abc Updated translations with latest Crowdin changes (#4008) 2023-02-02 12:16:56 +00:00
Dan Brown
28c168145f Added missing app icon image
Fixes #4006
2023-02-02 11:49:06 +00:00
Dan Brown
c2115cab59 Updated php depenencies 2023-02-02 11:44:25 +00:00
Dan Brown
bf075f7dd8 Updated version and assets for release v23.01 2023-01-31 11:59:51 +00:00
Dan Brown
a4fd673285 Merge branch 'development' into release 2023-01-31 11:59:28 +00:00
Dan Brown
813d140213 Merge branch 'development' of github.com:BookStackApp/BookStack into development 2023-01-31 11:39:21 +00:00
Dan Brown
3dc5942a85 Updated translation attribution before v23.01 release 2023-01-31 11:38:56 +00:00
Dan Brown
03e2a9b200 Updated translations with latest Crowdin changes (#3925) 2023-01-31 11:29:36 +00:00
Dan Brown
8367a94e90 Merge pull request #4002 from BookStackApp/color_upgrades
Better application color scheme control
2023-01-28 17:59:54 +00:00
Dan Brown
631546a68a Adjusted/improved some color setting wording 2023-01-28 17:57:43 +00:00
Dan Brown
7751022c66 Updated migration to carry across more colors, updated export
Updated export to use link color for link.
Export will now copy primary color to link color options for stable
upgrades.
2023-01-28 17:49:48 +00:00
Dan Brown
f42ff59b43 Added migration of color settings to dark mode 2023-01-28 17:31:43 +00:00
Dan Brown
104621841b Update JS to show live changes and set light color values 2023-01-28 17:11:15 +00:00
Dan Brown
c337439370 Rolled out use of seperate link color style 2023-01-28 16:06:11 +00:00
Dan Brown
65ebdb7234 Added usage and defaults for dark colors 2023-01-28 15:20:08 +00:00
Dan Brown
e708ce93ba Updated generic tab styles and js to force accessible usage
Added use of more accessible tags to create tabbed-interfaces then
updated css and JS to require use of those attributes rather than custom
techniques.

Updated relevant parts of app.
Some custom parts using their own tabs though, something to improve in
future.
2023-01-28 12:50:51 +00:00
Dan Brown
1f69965c1e Updated settings view to have dark-mode color options
Also added link color option, not yet used.
Cleaned up tabbed interface control design as part of this.
2023-01-28 11:50:46 +00:00
Dan Brown
d7723b33f3 Merge pull request #3999 from BookStackApp/sort_ui_improvements
Improve Book Sorting User Experience
2023-01-27 18:02:14 +00:00
Dan Brown
87e371ffde Added prevention of nested chapters on sort 2023-01-27 17:39:51 +00:00
Dan Brown
b649738718 Made book-sort changes based on screen reader testing
- Removed having sort items in tabbing order since they have no action.
- Updated "show other books" list to add upon single selection since it
  was not clear how these were added (double press) without then seeing
the add button, and even then the add button would be after the scroll
list.
2023-01-27 17:06:39 +00:00
Dan Brown
022cbb9c00 Finished off design and fixing of sort buttons 2023-01-27 16:25:06 +00:00
Dan Brown
40e112fc5b Extracted text & added dropdown for book sort move actions
Primarily styling and testing left to do.
2023-01-27 13:26:58 +00:00
Dan Brown
7cacbaadf0 Added functionality/logic for button-based sorting 2023-01-27 13:08:35 +00:00
Dan Brown
a3e7e754b9 Improves sortable ux
- Fixes multi-select functionality.
- Updated other books to be sticky.
- Added some general intro/desc text.
- Updated sort boxes to be collapsible.
- Cleaned up other books styling.
2023-01-27 11:16:17 +00:00
Dan Brown
03ad288aaa Updated user avatar reset to clear relation id in database
Added test to cover.
For #3977
2023-01-26 17:15:09 +00:00
Dan Brown
811be3a36a Added option to change the OIDC claim regarded as the ID
Defined via a OIDC_EXTERNAL_ID_CLAIM env option.
For #3914
2023-01-26 16:43:15 +00:00
Dan Brown
3202f96181 Tweak tag list to add new row on input instead of change
Prevented interferance with the user's action if they interacted with
something below the tags, since a new row would be added on blur and
hence shift down positions.

For #3931
2023-01-26 16:10:47 +00:00
Dan Brown
f6a6b11ec5 Added and addressed multi-role/own-role-perm/inheretance scenario
Found during manual testing.
Have checked against relation queries manually too.
2023-01-26 12:53:25 +00:00
Dan Brown
48df8725d8 Added better drawing load failure handling
Failure of loading drawings will now close the drawing view and show an
error message, hinting at file or permission issues, instead of leaving
the user facing a continuosly loading interface.

Adds test to cover.

This also updates errors from our HTTP service to be wrapped in a custom
error type for better identification and so the error is an actual
javascript error. Should be object compatible.

Related to #3955.
2023-01-26 12:18:33 +00:00
Dan Brown
25bdd71477 Add scheme and sql-variant code language options
For #3954 and #3942
2023-01-26 11:26:20 +00:00
Dan Brown
deda331745 Fixed global search preview click on safari
Safari needs an element to be focusable to be able to use :focus-within.
For #3926
2023-01-25 21:46:26 +00:00
Dan Brown
f6d3944b20 Merge pull request #3994 from BookStackApp/app_icon_setting
Added ability to control app icon (favicon) via settings
2023-01-25 16:50:48 +00:00
Dan Brown
a50b0ea1e5 Covered app icon setting with testing 2023-01-25 16:41:41 +00:00
Dan Brown
3c658e39ab Extracted app icon text, fixed issues
Tweaked sizes and meta tags based unpon ipad testing.
Fixed reduced sizes not being cleaned up.
2023-01-25 16:11:34 +00:00
Dan Brown
d8354255e7 Added practicali to sponsor list 2023-01-25 12:06:11 +00:00
Dan Brown
55b6a7842e Added ability to control app icon (favicon) via settings 2023-01-25 11:03:19 +00:00
Dan Brown
0f113ec41f Merge pull request #3986 from BookStackApp/permission_testing
Permission Testing & Alignment
2023-01-24 21:37:28 +00:00
Dan Brown
1fa5a31960 Fixed role entity permissions ignoring inheritance
Added additional scnenario tests to cover
2023-01-24 21:26:41 +00:00
Dan Brown
8be36455ab Addressed fallback override cases found during testing
Had misalignment between query and usercan, The nuance between fallback
and entity-role permissions was not taken into account by the query
system. Now added with new test cases to cover.
2023-01-24 20:42:20 +00:00
Dan Brown
d1bd6d0e39 Fixed incorrect field in down migration 2023-01-24 19:21:23 +00:00
Dan Brown
1660e72cc5 Migrated remaining relation permission usages
Now all tests are passing.
Some level of manual checks to do.
2023-01-24 19:04:32 +00:00
Dan Brown
2d1f1abce4 Implemented alternate approach to current joint_permissions
Is a tweak upon the existing approach, mainly to store and query role
permission access in a way that allows muli-level states that may
override eachother. These states are represented in the new PermissionStatus
class.

This also simplifies how own permissions are stored and queried, to be
part of a single column.
2023-01-24 14:55:34 +00:00
Dan Brown
7d74575eb8 Found a sql having-style approach to permissions
As a way to check aggregate queries for required changes to need to
analyse across combined permission values.
2023-01-24 13:44:38 +00:00
Dan Brown
91e613fe60 Shared entity permission logic across both query methods
The runtime userCan() and the JointPermissionBuilder now share much of
the same logic for handling entity permission resolution.
2023-01-23 15:09:03 +00:00
Dan Brown
f3f2a0c1d5 Updated userCan logic to meet expectations in tests
Updated with similar logic to that used in the user_permissions branch,
but all extracted to a seperate class for doing all fetch and collapse
work.
2023-01-23 12:40:11 +00:00
Dan Brown
1c2ae7bff6 Added gmp extension to test workflow
If was not already enabled by default, should enable faster testing
handling as it helps the phpseclib usage for OIDC tokens in test rocket
through.
2023-01-21 21:34:39 +00:00
Dan Brown
78ebcb6f38 Addressed a range of deprecation warnings
Closes #3969
2023-01-21 20:50:04 +00:00
Dan Brown
28dda39260 Updated PHP and JS depenencies 2023-01-21 19:09:19 +00:00
Dan Brown
e2a72d16aa Made adjustments to fit copied work into dev branch
Ported non-compatible elements, Now all tests passing apart from some
specific permission scenario tests which are probably correctly failing.
Updates some tests to better avoid messing environment state.
2023-01-21 13:03:47 +00:00
Dan Brown
c724bfe4d3 Copied over work from user_permissions branch
Only that relevant to the additional testing work.
2023-01-21 11:08:34 +00:00
Dan Brown
6070d804f8 Fixed incorrect pluralisation for de_informal
Updated language system to only use initial part of locale for
translation pluralisation to better match the hard-coded logic of the
built-in MessageSelector. Extends and overrides Laravel's default for
this system.

Added test to cover.
Related to #3976.
2023-01-16 16:56:41 +00:00
Dan Brown
e794c977bc Updated version and assets for release v22.11.1 2022-12-16 23:49:14 +00:00
Dan Brown
0b088ef1d3 Merge branch 'development' into release 2022-12-16 23:48:35 +00:00
Dan Brown
5393465ea7 Updated translator attribution before release v22.11.1 2022-12-16 23:48:04 +00:00
Dan Brown
f5df811b15 Removed old unused style definition 2022-12-16 23:21:24 +00:00
Dan Brown
a521f41838 Fixed lack of scroll in editor toolbox contents
For #2887
2022-12-16 23:16:51 +00:00
Dan Brown
0123d83fb2 Fixed not being able to remove all user roles
User roles would only be actioned if they existed in the form request,
hence removal of all roles would have no data to action upon.
This adds a placeholder 0-id role to ensure there is always role data to
send, even when no roles are selected. This field value is latter
filtered out.

Added test to cover.

Likely related to #3922.
2022-12-16 17:44:13 +00:00
Dan Brown
559e392f1b Merge branch 'development' of https://github.com/jhit/BookStack into jhit-development 2022-12-16 17:12:57 +00:00
Dan Brown
8468b632a1 Updated crowdin config with PR title and labels
Aligns to the title and labelling I already do manually.
2022-12-16 17:11:01 +00:00
Dan Brown
7053a8669f New Crowdin updates (#3881) 2022-12-16 17:06:52 +00:00
Dan Brown
2c0a7346b1 Prevent search focus change on left/right arrow press
For #3920
2022-12-16 17:03:48 +00:00
Dan Brown
bf6a6af683 Updated version and assets for release v22.11 2022-11-30 12:30:21 +00:00
Dan Brown
914790fd99 Merge branch 'development' into release 2022-11-30 12:29:52 +00:00
Dan Brown
69d702c783 Updated locale list to align with lang folders 2022-11-30 12:13:50 +00:00
Dan Brown
dd92cf9e96 Updated translator attribution before v22.11 release 2022-11-30 12:02:10 +00:00
Dan Brown
0cd0b44cdb New Crowdin updates (#3828) 2022-11-30 12:01:19 +00:00
Jürgen Hörmann
d505642336 Add popular PHP templating languages to code editor
Smarty and Twig are two very popular PHP templating engines and might be
useful to some Bookstack users too.
2022-11-29 14:53:41 +01:00
Dan Brown
31c28be57a Converted md settings to localstorage, added preview resize 2022-11-28 14:08:20 +00:00
Dan Brown
38db3a28ea Merge pull request #3878 from BookStackApp/dark_style_cleanup
Cleaned up dark mode styles inc. setting browser color scheme
2022-11-28 12:42:16 +00:00
Dan Brown
09fa2d2c9c Cleaned up dark mode styles inc. setting browser color scheme
Forces browser colorscheme based on BookStack color scheme, via
'color-scheme' css property.
Sets proper dark mode colors for some previously missed areas like
templates and attachment control buttons.
Also fixed search bar icon position for some search inputs.
2022-11-28 12:38:30 +00:00
Dan Brown
b786ed07be Merge pull request #3875 from BookStackApp/md_editor_updates
Markdown Editor Updates
2022-11-28 12:21:33 +00:00
Dan Brown
0527c4a1ea Added test to preference boolean endpoint 2022-11-28 12:17:22 +00:00
Dan Brown
ec3713bc74 Connected md editor settings to logic for functionality 2022-11-28 12:12:36 +00:00
Dan Brown
9fd5190c70 Added md editor ui dropdown options & their back-end storage
Still need to perform actual in-editor functionality for those controls.
2022-11-27 20:30:14 +00:00
Dan Brown
3995b01399 Tightened existing markdown editor styles 2022-11-27 19:52:10 +00:00
Dan Brown
3fdb88c7aa Added callout cycling in markdown editor via shortcut 2022-11-26 23:18:51 +00:00
Dan Brown
8e4bb32b77 Fixed md editor refactoring issues after manual test
Testing was a full manual feature test of each piece of supported logic
defined in the code.
2022-11-26 21:33:39 +00:00
Dan Brown
63d6272282 Refactored markdown editor logic
Split out the markdown editor logic into seperate components to provide
a more orgranised heirachy with feature-specific files.
2022-11-26 16:43:28 +00:00
Dan Brown
40a1377c0b Fixed tests to align with recent changes, Updated php deps 2022-11-23 12:08:55 +00:00
Dan Brown
e20c944350 Fixed OIDC handling when no JWKS 'use' prop exists
Now assume, based on OIDC discovery spec, that keys without 'use' are
'sig' keys. Should not affect existing use-cases since existance of such
keys would have throw exceptions in prev. versions of bookstack.

For #3869
2022-11-23 11:50:59 +00:00
Dan Brown
85b7b10c01 Merge branch 'development' of github.com:BookStackApp/BookStack into development 2022-11-23 00:13:02 +00:00
Dan Brown
35f73bb474 Updated global search component to new format 2022-11-23 00:12:41 +00:00
Dan Brown
ffc9c28ad5 Merge branch 'search_preview' into development 2022-11-23 00:10:21 +00:00
Dan Brown
fcff206853 Adjusted global search preview for dark mode 2022-11-23 00:05:24 +00:00
Dan Brown
0e528986ab Extracted keyboard nav. from dropdowns to share w/ search 2022-11-21 17:35:19 +00:00
Dan Brown
e7e83a4109 Added new endpoint for search suggestions 2022-11-21 10:35:53 +00:00
Dan Brown
891543ff0a Merge pull request #3852 from BookStackApp/php82
PHP8.2 Support
2022-11-20 22:21:52 +00:00
Dan Brown
c617190905 Added global search input debounce and loading indicator 2022-11-20 22:20:31 +00:00
Dan Brown
2c1f20969a Replaced JS logic with CSS focus-within logic 2022-11-20 21:53:53 +00:00
Dan Brown
851ab47f8a Fixed input styles in search preview mode, added animation
Also added JS handlers for hiding the suggestions
2022-11-20 21:50:59 +00:00
Dan Brown
bbf13e9242 Merge pull request #3853 from BookStackApp/component_refactor
Started refactor and alignment of JS component system
2022-11-16 16:05:57 +00:00
Dan Brown
05a24ea355 Updated js dev docs with latest component changes 2022-11-16 16:02:31 +00:00
Dan Brown
be736b3939 Replaced el.components mapping with component service weakmap
Old system was hard to track in terms of usage and it's application of
'components' properties directly to elements was shoddy.
This routes usage via the components service, with element-specific
component usage tracked via a local weakmap.
Updated existing found usages to use the new system.
2022-11-16 15:46:41 +00:00
Dan Brown
25c23a2e5f Removed use of image-manager/entity-selector window globals 2022-11-16 15:21:22 +00:00
Dan Brown
3b8ee3954e Finished updating remainder of JS components to new system 2022-11-16 13:06:08 +00:00
Dan Brown
db79167469 Updated a whole load more js components 2022-11-15 16:04:46 +00:00
Dan Brown
b37e84dc10 Updated another set of components 2022-11-15 12:44:57 +00:00
Dan Brown
4310d34135 Updated a batch of JS components 2022-11-15 11:24:31 +00:00
Dan Brown
09c6a3c240 Started refactor and alignment of component system
- Updates old components to newer format, removes legacy component
support.
- Makes component registration easier and less duplicated.
- Adds base component class to extend for better editor support.
- Aligns global window exposure usage and aligns with other service
  names.
2022-11-14 23:19:02 +00:00
Dan Brown
796f4090b5 Added php8.2 to GH action checks 2022-11-14 18:26:01 +00:00
Dan Brown
19a792bc12 Started on a live-preview on global search input 2022-11-14 10:24:14 +00:00
Dan Brown
a1b1f8138a Updated email confirmation flow so confirmation is done via POST
To avoid non-user GET requests (Such as those from email scanners)
auto-triggering the confirm submission. Made auto-submit the form via
JavaScript in this extra added step with user-link backup to keep
existing user flow experience.

Closes #3797
2022-11-12 15:11:59 +00:00
Dan Brown
0e627a6e05 Merge pull request #3848 from BookStackApp/auth_message_partials
Added login/register message partials for easier use via theme system
2022-11-12 09:03:59 +00:00
Dan Brown
d2cd33e226 Added login/register message partials for easier use via theme system
Related to #608
2022-11-12 09:02:33 +00:00
Dan Brown
2fa5c2581c Added swift support to code blocks and editor
Closes #3847
2022-11-12 08:44:25 +00:00
Dan Brown
d2260b234c Fixed app logo visibility with secure_restricted images
Includes test to cover.
For #3827
2022-11-10 14:15:59 +00:00
Dan Brown
832356d56e Added test to cover books perms. gen with deleted chapter
Closes #3796
2022-11-10 13:48:17 +00:00
Dan Brown
5fd1c07c9d Added dart support to code blocks/editing
For #3808
2022-11-10 13:38:56 +00:00
Dan Brown
4c75358abd Extracted hardcoded english text to language files
Closes #3822
2022-11-10 13:30:48 +00:00
Dan Brown
d520d6cab8 Merge pull request #3830 from BookStackApp/shortcuts
User interface shortcuts system
2022-11-10 10:32:56 +00:00
Dan Brown
737904fa63 Extracted shortcut text to language files 2022-11-10 10:25:28 +00:00
Dan Brown
a3fcc98d6e Aligned user preference endpoints in style and behaviour
Changes their endpoints and remove the user id from the URLs.
Simplifies list changes to share a single endpoint, which aligns it to
the behaviour of the existing sort preference endpoint.
Also added test to ensure user preferences are deleted on user delete.
2022-11-09 19:30:08 +00:00
Dan Brown
24a7e8500d Added tests to cover shortcut endpoints 2022-11-09 18:42:54 +00:00
Dan Brown
9067902267 Added shortcut input controls to make custom shortcuts work 2022-11-09 14:40:44 +00:00
Dan Brown
66c8809799 Started interface user shortcut form interface
Built controller actions and initual UI.
Still needs JS logic for shortcut input handling.
2022-11-08 21:17:45 +00:00
Dan Brown
1fc994177f Improved shortcut overlay with related action highlighting 2022-11-05 13:57:22 +00:00
Dan Brown
78b6450031 Distributed shortcut actions to common ui elements 2022-11-05 13:39:17 +00:00
Dan Brown
b4cb375a02 Started implementation of UI shortcuts system 2022-11-04 15:20:19 +00:00
Dan Brown
33e5c85503 Merge pull request #3821 from BookStackApp/list_reworks
Revision of item list views
2022-11-03 14:52:40 +00:00
Dan Brown
9e8240a736 Addressed additional unsupported array spread operation 2022-11-03 14:40:01 +00:00
Dan Brown
37afd35b6f Fixed use of array unpacking syntax
Since it was using keyed arrays, unpacking is only supported in php8.1+
2022-11-03 14:33:23 +00:00
Dan Brown
6364c541ea Fixed phpstan static usage warning, updated ci flows
CI flow updates to follow deprecation warnings
2022-11-03 14:14:22 +00:00
Dan Brown
8ec6b07690 Updated role permission table to responsive format 2022-11-03 13:28:07 +00:00
Dan Brown
7101ec09ed Updated search term lists to flex layouts 2022-11-03 12:49:05 +00:00
Dan Brown
2c5efddf6c Merge branch 'v22-10' into development 2022-11-02 15:22:53 +00:00
Dan Brown
edb0c6a9e8 Updated version and assets for release v22.10.2 2022-11-02 15:22:13 +00:00
Dan Brown
84049de696 Merge branch 'v22-10' into release 2022-11-02 15:19:33 +00:00
Dan Brown
a37bdffcd9 Updated translator attribution before release v22.10.2 2022-11-02 15:19:13 +00:00
Dan Brown
e95ab36f76 Merged and squashed l10n_development into v22-10 2022-11-02 15:17:54 +00:00
Dan Brown
f809bd3a62 Updated tests to align with recent list changes 2022-11-01 14:53:36 +00:00
Dan Brown
d4e71e431b Revised revision list to responsive layout 2022-10-31 21:26:31 +00:00
Dan Brown
de807f8538 Updated recycle bin list to new responsive layout 2022-10-31 16:45:32 +00:00
Dan Brown
80d2889217 Updated tags list to new responsive format 2022-10-31 11:40:28 +00:00
Dan Brown
9e8516c2df Tweaked list spacings a little to align paddings 2022-10-30 21:06:42 +00:00
Dan Brown
09f2bc28d2 Removed addition detail spacing in audit list 2022-10-30 20:29:21 +00:00
Dan Brown
be320c5501 Adjusted audit log row spacing a tad 2022-10-30 20:27:41 +00:00
Dan Brown
2bbf7b2194 Revised audit log list to new responsive format 2022-10-30 20:24:08 +00:00
Dan Brown
ab184c01d8 Updated API tokens list to new responsive format 2022-10-30 15:37:52 +00:00
Dan Brown
2c114e1a4a Split out user controller preference methods to new controller 2022-10-30 15:25:02 +00:00
Dan Brown
ec4cbbd004 Refactored common list handling operations to new class 2022-10-30 15:16:06 +00:00
Dan Brown
f75091a1c5 Revised webhooks list to new format
Also aligned query naming to start with model in use.
Also added created/updated sort options to roles.
2022-10-30 12:02:06 +00:00
Dan Brown
98b59a1024 Revised role index list to align with user list 2022-10-29 20:52:17 +01:00
Dan Brown
0ef06fd298 Extracted user list item to its own template 2022-10-29 15:25:28 +01:00
Dan Brown
986346a0e9 Redesigned users list to be responsive and aligned 2022-10-29 15:23:21 +01:00
Dan Brown
2a65331573 Worked towards phpstan level 2, 13 errors remain 2022-10-24 12:12:48 +01:00
Dan Brown
45d0860448 Updated npm package versions 2022-10-24 11:40:05 +01:00
Dan Brown
da0531e63b Updated version and assets for release v22.10.1 2022-10-21 21:52:32 +01:00
Dan Brown
421dc75f4e Merge branch 'development' into release 2022-10-21 21:52:16 +01:00
Dan Brown
ea6eacb400 Fixed chapter fetching during joint permission building
Somehow I accidentally deleted previous line 143 in this commit:
3839bf6bf1
which would then break permission generation for content related to, or
containing, chapters in the recycle bin.
Found via user report (subz) & debugging in discord.
2022-10-21 21:49:29 +01:00
Dan Brown
8ae91df038 Updated version and assets for release v22.10 2022-10-21 11:16:45 +01:00
Dan Brown
64b41dd626 Merge branch 'development' into release 2022-10-21 11:16:25 +01:00
Dan Brown
103649887f Updated translator attribution before release v22.10 2022-10-21 11:15:35 +01:00
Dan Brown
7b2fd515da Updated test to align with latest translation 2022-10-21 10:41:55 +01:00
Dan Brown
3f61bfc43c Fixed toggle controls on added content permission role rows 2022-10-21 10:13:11 +01:00
Dan Brown
905d339572 Added greek language option 2022-10-20 12:25:02 +01:00
Dan Brown
5d37a814fd New Crowdin updates (#3737) 2022-10-20 12:18:58 +01:00
Dan Brown
f9c0edbd0c Set fixed cell widths for users list table
To prevent certain cells squashing others.
Related to #3787.
2022-10-19 11:15:17 +01:00
Dan Brown
d084f225a0 Updated page pointer to use a fixed positioning system
Avoids interferance with elements that have their own overflow behaviour
such as table cells.
Related to #3774
2022-10-18 22:40:13 +01:00
Dan Brown
ff3fb2ebb9 Extracted page pointer to its own compontent 2022-10-18 22:02:34 +01:00
Dan Brown
725ff5a328 Updated php deps 2022-10-16 09:54:07 +01:00
Dan Brown
f0ac454be1 Prevented saml2 autodiscovery on metadata load
Fixes issue where metadata cannot be viewed if autload is active and
entityid url is not active.
For #2480
2022-10-16 09:50:08 +01:00
Dan Brown
0269f5122e Added wysiwyg code block edit tooltip
For easier editing access on mobile devices where previous doubleclick
does not work so well.
For #2815
2022-10-15 15:47:34 +01:00
Dan Brown
6adc642d2f Merge branch 'development' into bugfix/fix-being-unable-to-clear-filters 2022-10-15 15:12:55 +01:00
Dan Brown
22a91c955d Merge pull request #3760 from BookStackApp/item_permission_revamp
Refactor of item-level permission to be more intuitive
2022-10-14 17:34:51 +01:00
Dan Brown
6951aa3d39 Fixed permission row permission check 2022-10-14 16:03:06 +01:00
Dan Brown
bd412ddbf9 Updated test for perms. changes and fixed static issues 2022-10-12 12:12:36 +01:00
Dan Brown
7792da99ce Updated entity perms. changes for dark mode support 2022-10-12 11:27:24 +01:00
Dan Brown
98c6422fa6 Extracted entity perms. text to translation files 2022-10-11 15:52:56 +01:00
Dan Brown
25708542ff Refined design and text for entity permission changes 2022-10-11 15:41:21 +01:00
Dan Brown
0fae807713 Fixed and updated "Everyone Else" permissions handling
- Fixed inheriting control for new system.
- Tested copying shelf permissions to books.
- Added additional handling for inheriting scenario identification.
2022-10-10 17:22:38 +01:00
Dan Brown
0f68be608d Removed most usages of restricted entitiy property 2022-10-10 16:58:26 +01:00
Dan Brown
63056dbef4 Updated restricted usage on search and entity meta details
Also removed now unused view.
2022-10-10 16:22:51 +01:00
Dan Brown
803934d020 Added interface for adding/removing roles in entity perms. 2022-10-10 12:24:23 +01:00
Dan Brown
ffd6a1002e Centralised handling of permission form data to own class
Also updates show roles on permission view to just those with
permissions applied.
Fixes rounded borders for lone permission rows.
Moves "Everyone Else" handling from role to new class.
2022-10-09 17:14:11 +01:00
Dan Brown
bf591765c1 Reorgranised permission routes into their own controller
Also introduced helpers for getting entities by slugs since we do it in
so many places.
2022-10-09 16:36:03 +01:00
Dan Brown
06a7f1b54a Added migration to drop entity restricted field 2022-10-08 15:30:03 +01:00
Dan Brown
3839bf6bf1 Updated joint perms. gen. to use new entity permission format 2022-10-08 14:28:44 +01:00
Dan Brown
aee0e16194 Started code update for new entity permission format 2022-10-08 13:52:59 +01:00
Dan Brown
1d3dbd6f6e Migrated entity_permissions table to new flat format
Simplifies structure and limits content count, while allowing direct
mapping of new UI intent, where we may have entries with no permissions.
Not yet updated app logic to suit.

Tested via migrating and rolling-back, then comparing export data,
across a set of custom permission entries.
2022-10-07 15:07:09 +01:00
Dan Brown
1df9ec9647 Added proper entity permission removal on role deletion
Added test to cover.
2022-10-07 13:12:33 +01:00
Allan
d4143c3101 Only output hidden user filters when not set to 'me' 2022-10-06 19:25:47 +02:00
Dan Brown
a03245e427 Added user-interface for "Everyone Else" entity permission item
Nothing on back-end logic done to hook this new option up.
Addition of permissions for role_id=0 works out of the box, but active
"everyone else" permissions, with no priviliges, is currently not
working. Needs change of permission gen logic also.
2022-10-02 18:09:48 +01:00
Dan Brown
a090720241 Developed dev JS docs a bit further 2022-10-02 14:27:12 +01:00
Dan Brown
b8b0afa0df Cleaned up old permission JS code
Removed now unused JS entity-permissions compontent.
Updated existing permissions-table compontent to newer format.
Removed now unused translation string.
2022-10-02 13:57:32 +01:00
Dan Brown
f19bad8903 Started item permission design revamp 2022-10-02 13:17:28 +01:00
Dan Brown
953402f2eb Started playing with table icons
To make a little more accessible, Related to #3397
2022-09-30 18:37:37 +01:00
Dan Brown
8c945034b9 Merge pull request #3757 from BookStackApp/tests_entity_cleanup
Testing cleanup
2022-09-29 22:18:34 +01:00
Dan Brown
900e853b15 Quick run through of applying new test entity helper class 2022-09-29 22:11:16 +01:00
Dan Brown
b56f7355aa Migrated much test entity usage via find/replace 2022-09-29 17:31:38 +01:00
Dan Brown
068a8a068c Extracted entity testcase methods to own class
Also added some new fetch helper methods for future use.
2022-09-29 16:49:25 +01:00
Dan Brown
0e94fd44a8 Added contents to book-show endpoint
Created a generic list formatting helper class for this, to align with
logic used on the search results endpoint and for easier future re-use
in a standardised way.
Also updated some class property types.
Added test to cover new books-contents results.
Related to #3734
2022-09-29 15:08:18 +01:00
Dan Brown
ccbc68b560 Updated shelf book management to allow scroll on mobile
Updates book drag handling to be limited to the handle so scrolling can
be done on the items themselves.
Increased handling area and improved styling to support
2022-09-28 20:48:29 +01:00
Dan Brown
f79b7bc799 Added api format advisory regarding PUT/DELETE form data 2022-09-28 20:15:48 +01:00
Dan Brown
60171b3522 Updated book copy to copy shelf relations
Where permission to edit the shelf is allowed.
For #3699
2022-09-28 14:14:51 +01:00
Dan Brown
8f3430d386 Improved tag suggestion handling
- Aligned prefix-type filtering with back-end.
- Increased suggestion search cut-off from 3 to 4.
- Increased amount of suggestions shown.
- Ordered suggestions to be name asc, as you'd expect on search.
- Updated front-end filtering to use full search query, instead of
  truncated version, for further front-end filtering capability.

Related to #3720
2022-09-28 13:50:40 +01:00
Dan Brown
1ac1cf0c78 Applied permissions to revision action visibility
Related to #3723
2022-09-28 11:10:06 +01:00
Dan Brown
6dd89ba956 Split out some development-specific readme parts to own pages 2022-09-27 20:11:58 +01:00
Dan Brown
bf56254077 Merge branch 'auth_review' into development 2022-09-27 19:34:48 +01:00
Dan Brown
d933fe5dce Updated WYSIWYG config to allow styles on list elements 2022-09-27 19:05:03 +01:00
Dan Brown
391fb2cc62 Added MATLAB/Octave code highlighting support 2022-09-27 18:52:21 +01:00
Dan Brown
af11e7dd54 Merge branch 'development' of github.com:BookStackApp/BookStack into development 2022-09-27 18:45:08 +01:00
Dan Brown
af434d0216 Fixed custom code theme not showing in WYSIWYG
Fixes #3753
Was caused by not including added styles to the code block shadow root.
2022-09-27 18:44:06 +01:00
Dan Brown
931641ed2c Tweaked license and readme text
Updated license copyright line to better help it be detected as MIT by
automatic license systems (Such as GitHub license detection) while
removing contributors link which would not actually list all
contributors.
Also added year range back in to be more specific about active lifetime.
2022-09-27 12:23:16 +01:00
Dan Brown
b716fd2b8b Updated composer deps, incremented dev version 2022-09-27 02:56:49 +01:00
Dan Brown
a6a78d2ab5 Refactored app service providers
Removed old pagination provider as url handling now achieved in a better
way.
Removed unused broadcast service provider.
Moved view-based tweaks into specific provider.
Reorganised provider config list.
2022-09-27 02:48:05 +01:00
Dan Brown
67d7534d4f Merge pull request #3751 from BookStackApp/parallel_testing
Parallel Testing Support
2022-09-27 01:31:37 +01:00
Dan Brown
f21669c0c9 Cleaned testing service provider usage
Moved testing content out of AppServiceProvider, to a testing-specific
service provider. Updated docs and added composer commands to support
parallel testing.
Also reverted unintentional change to wysiwyg/config.js.
2022-09-27 01:27:51 +01:00
Dan Brown
e18033ec1a Added initial support for parallel testing 2022-09-26 21:25:32 +01:00
Dan Brown
5c5ea64228 Added login throttling test, updated reset-pw test method names 2022-09-22 17:29:38 +01:00
Dan Brown
90b4257889 Split out registration and pw-reset tests methods 2022-09-22 17:15:15 +01:00
Dan Brown
f4388d5e4a Removed usage of laravel/ui dependency
Brings app auth controller handling aligned within the app, rather than
having many overrides of the framwork packages causing confusion and
messiness over time.
2022-09-22 16:54:27 +01:00
Dan Brown
7165481075 Updated auth controllers with property types 2022-09-22 15:12:05 +01:00
Dan Brown
ebd6e4d3a2 Updated version and assets for release v22.09.1 2022-09-20 13:19:34 +01:00
Dan Brown
80374aea5c Merge branch 'development' into release 2022-09-20 13:19:03 +01:00
Dan Brown
aec772c5eb Updated translator attribution before release v22.09.1 2022-09-20 13:18:41 +01:00
Dan Brown
2e4d29e062 New Crowdin updates (#3710) 2022-09-20 13:16:15 +01:00
Dan Brown
dce6a82954 Added reason, if existing, into SAML acs error
Closes #3731
2022-09-20 12:52:44 +01:00
Dan Brown
050d69ea27 Added extra setlocale format to help windows support
Related to #3650
2022-09-20 12:00:14 +01:00
Dan Brown
0cc68b7665 Fixed language request link in readme 2022-09-19 17:24:21 +01:00
Dan Brown
75d6b56072 Merge pull request #3728 from BookStackApp/php_formatting
Addition of PHPCS for formatting
2022-09-18 15:04:07 +01:00
Dan Brown
ac27b5aebb Updated readme for phpcs usage, aligned gh action workflows 2022-09-18 14:50:25 +01:00
Dan Brown
ecbc7344fc Added php lint gh action, updated composer scripts 2022-09-18 01:56:45 +01:00
Dan Brown
8a749c6acf Added and ran PHPCS 2022-09-18 01:25:20 +01:00
Dan Brown
2ac9efae7d Updated version and assets for release v22.09 2022-09-08 12:41:09 +01:00
Dan Brown
a11d565ba4 Merge branch 'development' into release 2022-09-08 12:40:57 +01:00
Dan Brown
d0dc5e5c5d Added a little protection to migration query
Just to be sure the query is filtered as expected to only affect
shelf-based images.
2022-09-08 12:26:14 +01:00
Dan Brown
e4642257a6 New Crowdin updates (#3701) 2022-09-08 11:59:57 +01:00
Dan Brown
f7418d0600 Updated translator attribution 2022-09-08 11:58:55 +01:00
Dan Brown
98aed794cc Made a range of rtl fixes
Mostly around dropdowns and other items that had right/left specific
styling.
For #3702
2022-09-06 21:31:18 +01:00
Dan Brown
623ccd4cfa Removed old thai files, added romanian as lang option
Also applied styleci changes
2022-09-06 17:41:32 +01:00
Dan Brown
d8672944a5 Added image view access notice to role form
Added to clarify the role permission in scenarios where users may have
not read the docs site to understand image access control.

Related to #3688
2022-09-06 17:20:35 +01:00
Dan Brown
6955b2fd5a Widened svg content attribute xss filtering
Takes care of additional cases that can occur.
Closes #3705
2022-09-06 17:01:56 +01:00
Dan Brown
24f82749ff Updated OIDC group attr option name
To match the existing option name for display names.
Closes #3704
2022-09-06 16:33:17 +01:00
Dan Brown
b9941e8e61 Merge pull request #3698 from BookStackApp/include_theme_event
Added "page_include_parse" theme event
2022-09-05 16:51:01 +01:00
Dan Brown
7101ce3050 Added "page_include_parse" theme event
For custom control of include tag parsing.
2022-09-05 16:40:42 +01:00
Dan Brown
fbef0d06f2 Added permission visiblity control to image-delete button
Includes test to cover.
For #3697
2022-09-05 15:52:12 +01:00
Dan Brown
b698bb0e07 Wrapped wysiwyg drawing change in editor transaction
To make the content changes made a undoable transaction that is picked
up as a change.
From my testing, should address #3682
2022-09-05 15:06:47 +01:00
Dan Brown
2d7552aa09 Addressed setlocale issue caught by phpstan
setlocale could be called with no second param if the language given to
the modified function was empty.
2022-09-05 13:33:05 +01:00
Dan Brown
ee1e936660 Applied styleci changes, updated composer deps 2022-09-05 13:18:37 +01:00
Dan Brown
50214d5fe6 New Crowdin updates (#3643) 2022-09-05 13:17:10 +01:00
Dan Brown
2fe261e207 Updated page revisions link visibility
To match the actual visibilities of the revisions listing page and
options.
Related to #2946
2022-09-03 12:32:21 +01:00
Dan Brown
9158a66bff Updated & improved language locale handling
Extracted much of the language and locale work to a seperate, focused class.
Updated php set_locale usage to prioritise UTF8 usage.
Added locale options for windows.
Clarified what's a locale and a bookstack language string.

For #3590 and maybe #3650
2022-09-02 19:19:01 +01:00
Dan Brown
7f8b3eff5a Fixed failing tests due to shelf text changes, applied styleci changes 2022-09-02 14:47:44 +01:00
Dan Brown
5736919836 Merge pull request #3693 from BookStackApp/local_secure_restricted
Addition of a `local_secure_restricted` image storage option
2022-09-02 14:41:25 +01:00
Dan Brown
c76b5e2ec4 Fixed local_secure_restricted preventing attachment uploads
Due to option name change and therefore lack of handling.
Added test case to cover.
2022-09-02 14:40:17 +01:00
Dan Brown
092b6d6378 Added test and handling for local_secure_restricted in exports 2022-09-02 14:21:43 +01:00
Dan Brown
f88330202b Added test to cover secure restricted functionality 2022-09-02 14:03:23 +01:00
Dan Brown
f28ed0ef0b Fixed shelf covers being stored as 'cover_book'
Are now stored as 'cover_bookshelf' as expected.
Added a migrate to alter existing shelf cover image types.
2022-09-02 12:54:54 +01:00
Dan Brown
27ac122502 Started work on local_secure_restricted image option 2022-09-01 16:17:14 +01:00
Dan Brown
9da3130a12 Aligned bookshelf terminology to consistently be 'Shelf'
For #3553
EN only, other languages should be handled via CrowdIn
2022-09-01 14:55:35 +01:00
Dan Brown
1afc915aed Fixed missing nested list indent next to floated content
Fixes #3672
2022-09-01 13:11:59 +01:00
Dan Brown
34c63e1c30 Added test & update to prevent page creation w/ empty slug
Caused by changes to page repo in reference work,
This adds back in the slug generate although at a more central place.
Adds a test case to cover the problematic scenario.
2022-09-01 12:53:34 +01:00
Dan Brown
f092c97748 Fixed lack of url reference updating on book child move 2022-08-30 22:12:52 +01:00
Dan Brown
9153be963d Added book child reference handling on book url change
Closes #3683
2022-08-30 22:00:32 +01:00
Dan Brown
1cc7c649dc Applied StyleCi changes, updated php deps 2022-08-29 17:46:41 +01:00
Dan Brown
e537d0c4e8 Merge pull request #3656 from BookStackApp/x_linking
Link reference tracking & updating
2022-08-29 17:45:05 +01:00
Dan Brown
961e418cb7 Fixed phpstan wanring about usage of static 2022-08-29 17:39:50 +01:00
Dan Brown
6edf2c155d Added maintenance action to regenerate references 2022-08-29 17:30:26 +01:00
Dan Brown
401c156687 Merge pull request #3616 from BookStackApp/oidc_group_sync
Added OIDC group sync functionality
2022-08-25 11:17:18 +01:00
Dan Brown
760eff397f Updated API docs with better request format explanation
Explained the content-types accepted by BookStack.
Made it clear that 'Content-Type' is expected on requests.
Added example to shown how to achieve more complex formats using
non-json requests.
Also added link to api-scripts repo.

Related to #3666 and #3652
2022-08-23 17:05:42 +01:00
Dan Brown
d134639eca Doubled default revision limit
Due to potential increase of revision entries due to auto-changes.
2022-08-23 16:32:07 +01:00
Dan Brown
b86ee6d252 Rolled out reference link updating logic usage
Added test to cover updating of content on reference url change
2022-08-21 18:05:19 +01:00
Dan Brown
0dbf08453f Built out cross link replacer, not yet tested 2022-08-21 11:29:34 +01:00
Dan Brown
26ccb7b644 Started work on reference on-change-updates
Refactored out revision-specific actions within PageRepo for
organisition and re-use for cross-linking work.
2022-08-20 21:09:07 +01:00
Dan Brown
f634b4ea57 Added entity meta link to reference page
Not totally happy with implementation as is requires extra service to be
injected to core controllers, but does the job.
Included test to cover.
Updated some controller properties to be typed while there.
2022-08-20 12:07:38 +01:00
Dan Brown
d198332d3c Rolled out reference pages to all entities, added testing
Including testing to check permissions applied to listed references.
2022-08-19 22:40:44 +01:00
Dan Brown
d5465726e2 Added inbound references listing for pages 2022-08-19 13:14:43 +01:00
Dan Brown
bbe504c559 Added reference handling on page actions
Page update/create/restore/clone/delete.
Added a couple of tests to cover a couple of those.
2022-08-17 17:37:27 +01:00
Dan Brown
3290ab3ac9 Added regenerate-references command test
Also updated model resolvers to only fetch model ID, to prevent bringing
back way more data from database than desired.
2022-08-17 16:59:23 +01:00
Dan Brown
5d29d0cc7b Added reference storage system, and command to re-index
Also re-named/orgranized some files for this, to make them "References"
specific instead of a subset of "Util".
2022-08-17 14:40:14 +01:00
Dan Brown
344b3a3615 Added system to extract model references from HTML content
For the start of a managed cross-linking system.
2022-08-16 13:23:53 +01:00
Dan Brown
837fd74bf6 Refactored search-based code to its own folder
Also applied StyleCI changes
2022-08-16 11:28:05 +01:00
Dan Brown
2b06e86d53 Merge pull request #3653 from krsriq/patch-1
Fix typos
2022-08-15 22:31:49 +01:00
Daniel Schmelz
9041e25476 Fix typos 2022-08-15 22:41:44 +02:00
Dan Brown
1fdf854ea7 Updated version and assets for release v22.07.3 2022-08-11 15:17:06 +01:00
Dan Brown
e9c9792cb9 Merge branch 'development' into release 2022-08-11 15:16:34 +01:00
Dan Brown
d6235bcf92 Merge branch '3636-security-patch' into development 2022-08-11 15:15:19 +01:00
Dan Brown
6a3f4f5e79 Updated translator attribution pre v22.07.3 release 2022-08-11 13:17:18 +01:00
Dan Brown
7b100ef361 Merge branch 'persian_translate_22_08_10' into development 2022-08-11 13:15:15 +01:00
Dan Brown
443415ea0d New Crowdin updates (#3635) 2022-08-11 13:12:55 +01:00
Dan Brown
e02bd5e57e Added content security section to the api docs
Related to #3636
2022-08-11 10:49:45 +01:00
Dan Brown
5f7cd735ea Added content filtering of tags with javascript or data in values attr
Case would be blocked by CSP but adding for cases where CSP may not be
active when content taken externally.

For #3636
2022-08-11 10:28:32 +01:00
samad hassan allafi
89ff0d43bb Completion of Persian translation 2022-08-10 2022-08-10 22:55:31 +04:30
Dan Brown
375abca1ee Merge pull request #3632 from BookStackApp/ownable_permission_fix
Fixed failed permission checks due to non-loaded fields
2022-08-10 17:59:46 +01:00
Dan Brown
031c67ba58 Reduced the memory usage, db queries and cache hits loading revisions
Updated revision listing to only fetch required fields, massively
reducing memory usage by not loading content.
This also updates user avatar handling to effectively cache the avatar
url within request to avoid re-searching from cache, which may improve
performance of others areas of the application.
This also upates handling of the revisions list view to extract table
row to its own view to break things down a bit.

For #3633
2022-08-10 17:50:35 +01:00
Dan Brown
764489e30b Improved WYSWYG editor code block layout update
To help prevent against empty areas during inital empty-cache loads.
This delays the original layout update a little to give time for the
layout to render as expected.

For #3637
2022-08-10 13:51:54 +01:00
Dan Brown
16eedc8264 Fixed failed permission checks due to non-loaded fields
Added additional exceptions to prevent such cases in the future, so
that they are caught in dev ideally.
Added test case specifically for reported favourite scenario.
2022-08-10 08:06:48 +01:00
Dan Brown
5ae524c25a Updated version and assets for release v22.07.2 2022-08-09 13:55:52 +01:00
Dan Brown
0d7287fc8b Merge branch 'development' into release 2022-08-09 13:55:40 +01:00
Dan Brown
219da9da9b Updated translator attribution before release v22.07.2 2022-08-09 13:55:26 +01:00
Dan Brown
38ce54ea0c Merge pull request #3630 from BookStackApp/export_template_parts
Export template partials
2022-08-09 13:51:24 +01:00
Dan Brown
97ec560282 Added test to cover export body start/end partial usage 2022-08-09 13:49:42 +01:00
Dan Brown
06b5a83d8f Added convenience theme system partials for export layouts
To allow easier additions to start/end of body tag in export formats.
2022-08-09 13:46:52 +01:00
Dan Brown
45dc28ba2a Applied latest styleci changes 2022-08-09 13:26:45 +01:00
Dan Brown
6e0a7344fa Added revision activity types to system and audit log
Closes #3628
2022-08-09 13:25:18 +01:00
Dan Brown
7fa934e7f2 New Crowdin updates (#3625) 2022-08-09 13:00:39 +01:00
Dan Brown
a90446796a Fixed issue preventing selection of activity type in audit log
For #3623
2022-08-09 12:58:10 +01:00
Dan Brown
4209f27f1a Set a fairly sensible limit on user name validation
Also updated controller properties with types within modified files.
Related to #3614
2022-08-09 12:40:59 +01:00
Dan Brown
89ec9a5081 Sprinkled in some user language validation
For #3615
2022-08-04 17:24:04 +01:00
Dan Brown
b987bea37a Added OIDC group sync functionality
Is generally aligned with out SAML2 group sync functionality, but for
OIDC based upon feedback in #3004.
Neeeded the tangental addition of being able to define custom scopes on
the initial auth request as some systems use this to provide additional
id token claims such as groups.

Includes tests to cover.
Tested live using Okta.
2022-08-02 16:56:56 +01:00
Dan Brown
e77c96f6b7 Updated version and assets for release v22.07.1 2022-08-02 11:47:25 +01:00
Dan Brown
9b8a10dd3a Merge branch 'development' into release 2022-08-02 11:47:08 +01:00
Dan Brown
42f4c9afae New Crowdin updates (#3605) 2022-08-02 11:31:24 +01:00
Dan Brown
8d6071cb84 Updated cache busting for tinymce library import
Changes from a manual cache buster string to a app-version-based cache
buster, as per our other scripts and styles.

To address #3611
2022-08-02 11:17:02 +01:00
Dan Brown
49200ca5ce Updated version and assets for release v22.07 2022-07-28 14:53:15 +01:00
Dan Brown
34aa4dbf10 Merge branch 'development' into release 2022-07-28 14:53:01 +01:00
Dan Brown
a21d09fed7 New Crowdin updates (#3600) 2022-07-28 14:12:13 +01:00
Dan Brown
50bc2e49c1 Update translators.txt 2022-07-28 14:10:14 +01:00
Dan Brown
8776113210 Updated translator attribution pre 22.07 release 2022-07-28 14:01:27 +01:00
Dan Brown
397a36cfd0 Merge branch 'lang_de' into development 2022-07-27 11:20:08 +01:00
Dan Brown
ee24635e06 Merge pull request #3556 from GongMingCai/development
Fixed comment count update error
2022-07-27 11:18:05 +01:00
Dan Brown
7c8368cc63 Merge pull request #3545 from BookStackApp/l10n_development
New Crowdin updates
2022-07-27 11:15:45 +01:00
Dan Brown
f93e380d19 Merge branch 'development' of github.com:BookStackApp/BookStack into development 2022-07-27 11:08:03 +01:00
Dan Brown
0bb5654f80 Updated composer deps, applied StyleCI changes 2022-07-27 11:07:41 +01:00
Dan Brown
89324bf9cc Merge pull request #3599 from BookStackApp/editor_list_shortcuts
Add editor shortcuts for two main list types
2022-07-27 11:03:08 +01:00
Dan Brown
9abb207e4d Added list shortcuts to markdown editor
Added some logic for ordered lists to continue the numbering logic,
while keeping the number list format style the same [1. vs 1)]
2022-07-27 11:01:37 +01:00
Dan Brown
8aad8e4a24 New translations entities.php (Spanish) 2022-07-26 20:14:59 +01:00
Dan Brown
8681c5f613 Added ordered/unordered WYSIWYG list shortcuts
Related to #1269
2022-07-26 16:43:15 +01:00
Dan Brown
944ac2e6eb New translations entities.php (German Informal) 2022-07-26 13:13:26 +01:00
Dan Brown
75759fb735 New translations entities.php (Dutch) 2022-07-26 13:13:25 +01:00
Dan Brown
f47c9a53aa New translations entities.php (Lithuanian) 2022-07-26 13:13:24 +01:00
Dan Brown
480d591acf New translations entities.php (Korean) 2022-07-26 13:13:23 +01:00
Dan Brown
ae40ec10a4 New translations entities.php (Japanese) 2022-07-26 13:13:22 +01:00
Dan Brown
180927cdb9 New translations entities.php (Italian) 2022-07-26 13:13:21 +01:00
Dan Brown
f37e7186d9 New translations entities.php (Hungarian) 2022-07-26 13:13:20 +01:00
Dan Brown
bb7bd903ef New translations entities.php (Hebrew) 2022-07-26 13:13:19 +01:00
Dan Brown
6c767cd205 New translations entities.php (Polish) 2022-07-26 13:13:18 +01:00
Dan Brown
99aa093e2b New translations entities.php (Basque) 2022-07-26 13:13:17 +01:00
Dan Brown
42b576df55 New translations entities.php (Czech) 2022-07-26 13:13:16 +01:00
Dan Brown
3fbe304cfc New translations entities.php (Catalan) 2022-07-26 13:13:15 +01:00
Dan Brown
e0d4a43e1e New translations entities.php (Bulgarian) 2022-07-26 13:13:14 +01:00
Dan Brown
ab6a3144ec New translations entities.php (Arabic) 2022-07-26 13:13:13 +01:00
Dan Brown
7a2f54b890 New translations entities.php (Spanish) 2022-07-26 13:13:12 +01:00
Dan Brown
8f28bb9e3c New translations entities.php (French) 2022-07-26 13:13:11 +01:00
Dan Brown
cb12f76f46 New translations entities.php (German) 2022-07-26 13:13:10 +01:00
Dan Brown
3f1b376b2b New translations entities.php (Danish) 2022-07-26 13:13:09 +01:00
Dan Brown
147f038806 New translations entities.php (Ukrainian) 2022-07-26 13:13:08 +01:00
Dan Brown
812675dfc2 New translations entities.php (Russian) 2022-07-26 13:13:06 +01:00
Dan Brown
3b2fb67d78 New translations entities.php (Slovenian) 2022-07-26 13:13:05 +01:00
Dan Brown
ae91831ba6 New translations entities.php (Norwegian Bokmal) 2022-07-26 13:13:04 +01:00
Dan Brown
fcfafbdac5 New translations entities.php (Uzbek) 2022-07-26 13:13:02 +01:00
Dan Brown
705f81561a New translations entities.php (Bosnian) 2022-07-26 13:13:01 +01:00
Dan Brown
1c70684a99 New translations entities.php (Welsh) 2022-07-26 13:13:00 +01:00
Dan Brown
32e305ef4f New translations entities.php (Latvian) 2022-07-26 13:12:59 +01:00
Dan Brown
8c70a69fff New translations entities.php (Estonian) 2022-07-26 13:12:58 +01:00
Dan Brown
f0eb4df1e9 New translations entities.php (Croatian) 2022-07-26 13:12:57 +01:00
Dan Brown
852f4e61a5 New translations entities.php (Slovak) 2022-07-26 13:12:56 +01:00
Dan Brown
d68ee461e0 New translations entities.php (Spanish, Argentina) 2022-07-26 13:12:55 +01:00
Dan Brown
98ce7a0675 New translations entities.php (Indonesian) 2022-07-26 13:12:54 +01:00
Dan Brown
e6e1b9423d New translations entities.php (Portuguese, Brazilian) 2022-07-26 13:12:52 +01:00
Dan Brown
b3c93a2188 New translations entities.php (Vietnamese) 2022-07-26 13:12:51 +01:00
Dan Brown
59dbc0b9f1 New translations entities.php (Chinese Traditional) 2022-07-26 13:12:50 +01:00
Dan Brown
7a43b6d5b7 New translations entities.php (Chinese Simplified) 2022-07-26 13:12:49 +01:00
Dan Brown
fb8f92e835 New translations entities.php (Turkish) 2022-07-26 13:12:48 +01:00
Dan Brown
0d36e3fecf New translations entities.php (Swedish) 2022-07-26 13:12:47 +01:00
Dan Brown
b878ccc361 New translations entities.php (Persian) 2022-07-26 13:12:46 +01:00
Dan Brown
2bab892dce New translations entities.php (Portuguese) 2022-07-26 13:12:45 +01:00
Dan Brown
4fa73be80e Merge pull request #3598 from BookStackApp/chapter_sort_book_option
Added 'Sort Book' action to chapters
2022-07-26 12:37:18 +01:00
Dan Brown
bd14dc067b Added 'Sort Book' action to chapters
Related to #2335
2022-07-26 12:36:17 +01:00
Dan Brown
d4a119b2aa Fixed disabling of avatar urls, Removed id from gravatar image name
Included test to cover avatar url disabling.
Related to #1835
2022-07-26 12:10:19 +01:00
Dan Brown
2ec8a33927 Removed labels from WYSIWYG colors
To ease burden of translation.

Related to #3530
2022-07-26 11:07:40 +01:00
Dan Brown
fee3022ad8 Added tinymce de-focus toolbar hack for drawing editor load
The tinymce event system would not pick up the focus within the loaded
draw.io instance, after the drawing toolbar button was clicked, hence
the toolbar would hang around.

This adds a hack to dispatch a mousedown event on the body to get the
toolbar to hide.

For #3597
2022-07-25 19:56:01 +01:00
Dan Brown
050ae01f94 Merge pull request #3593 from BookStackApp/code-editor-favorites
Code-editor lang favorites system
2022-07-25 19:16:11 +01:00
Dan Brown
8e5f7c6425 Added language list favourites sorting, updated styles
- Also made code box be greedier with vertical space.
2022-07-25 19:13:25 +01:00
Dan Brown
7fdc7c68b9 Added test to cover code favourite pref. endpoint 2022-07-25 18:48:40 +01:00
Dan Brown
017c7659e5 New translations editor.php (Estonian) 2022-07-25 16:03:40 +01:00
Dan Brown
a08ea54615 New translations entities.php (Estonian) 2022-07-25 16:03:39 +01:00
Dan Brown
0df5ae0658 Added core code-lang-favourites JS, PHP & CSS logic
- Got the functionality now working to favourite items and store that
  status within the system for the user.
- Improved CSS display for usability.
2022-07-25 13:10:27 +01:00
Dan Brown
3fa43c804b New translations activities.php (Indonesian) 2022-07-25 00:23:51 +01:00
Dan Brown
ebc5a53410 Started code-editor lang favorites system
- Split bash from shell in language list
- Updated code-lang highlighting to be exact match only to prevent
  confusion scenarios (Java matching JavaScript, etc..)
- Added design for favorites
- Changed blade language list to be generated from array.
2022-07-24 21:15:43 +01:00
Dan Brown
62500a9bfa New translations editor.php (Dutch) 2022-07-24 19:51:07 +01:00
Dan Brown
a5153ff5af New translations entities.php (Dutch) 2022-07-24 19:51:06 +01:00
Dan Brown
3734b0a37e New translations editor.php (Spanish) 2022-07-24 15:39:46 +01:00
Dan Brown
4d72ac16a3 New translations entities.php (Spanish) 2022-07-24 15:39:46 +01:00
Dan Brown
29404f7e38 New translations entities.php (German Informal) 2022-07-24 12:39:49 +01:00
Dan Brown
a7252301c1 New translations entities.php (Dutch) 2022-07-24 12:39:48 +01:00
Dan Brown
0825dd17cf New translations entities.php (Lithuanian) 2022-07-24 12:39:47 +01:00
Dan Brown
9dd51c7cff New translations entities.php (Korean) 2022-07-24 12:39:46 +01:00
Dan Brown
854d2fe2dc New translations entities.php (Japanese) 2022-07-24 12:39:45 +01:00
Dan Brown
27848cea75 New translations entities.php (Italian) 2022-07-24 12:39:44 +01:00
Dan Brown
d1d999a98a New translations entities.php (Hungarian) 2022-07-24 12:39:43 +01:00
Dan Brown
5a6e171a7e New translations entities.php (Hebrew) 2022-07-24 12:39:42 +01:00
Dan Brown
90ffa46331 New translations entities.php (Polish) 2022-07-24 12:39:42 +01:00
Dan Brown
957214b84b New translations entities.php (Basque) 2022-07-24 12:39:41 +01:00
Dan Brown
a2bda11787 New translations entities.php (Czech) 2022-07-24 12:39:40 +01:00
Dan Brown
56204963e7 New translations entities.php (Catalan) 2022-07-24 12:39:39 +01:00
Dan Brown
ee579115b6 New translations entities.php (Bulgarian) 2022-07-24 12:39:38 +01:00
Dan Brown
d431141918 New translations entities.php (Arabic) 2022-07-24 12:39:37 +01:00
Dan Brown
4ff6b7fc51 New translations entities.php (Spanish) 2022-07-24 12:39:36 +01:00
Dan Brown
5d42f36a2b New translations entities.php (French) 2022-07-24 12:39:35 +01:00
Dan Brown
fb3491092e New translations entities.php (German) 2022-07-24 12:39:34 +01:00
Dan Brown
812c65fa3c New translations entities.php (Danish) 2022-07-24 12:39:33 +01:00
Dan Brown
9b48ee90f0 New translations entities.php (Ukrainian) 2022-07-24 12:39:32 +01:00
Dan Brown
6ad6bcaf82 New translations entities.php (Russian) 2022-07-24 12:39:31 +01:00
Dan Brown
f4ef85d587 New translations entities.php (Slovenian) 2022-07-24 12:39:30 +01:00
Dan Brown
1c57223a2d New translations entities.php (Norwegian Bokmal) 2022-07-24 12:39:29 +01:00
Dan Brown
a1a900035b New translations entities.php (Uzbek) 2022-07-24 12:39:28 +01:00
Dan Brown
bc8d4c40da New translations entities.php (Bosnian) 2022-07-24 12:39:28 +01:00
Dan Brown
cb91bd4933 New translations entities.php (Welsh) 2022-07-24 12:39:27 +01:00
Dan Brown
f336ce9119 New translations entities.php (Latvian) 2022-07-24 12:39:26 +01:00
Dan Brown
f9e040658d New translations entities.php (Estonian) 2022-07-24 12:39:25 +01:00
Dan Brown
d1b9d62e40 New translations entities.php (Croatian) 2022-07-24 12:39:24 +01:00
Dan Brown
7e70c14a16 New translations entities.php (Slovak) 2022-07-24 12:39:23 +01:00
Dan Brown
1b7a1e847e New translations entities.php (Spanish, Argentina) 2022-07-24 12:39:22 +01:00
Dan Brown
011178c302 New translations entities.php (Indonesian) 2022-07-24 12:39:21 +01:00
Dan Brown
e27b53fc6c New translations entities.php (Portuguese, Brazilian) 2022-07-24 12:39:20 +01:00
Dan Brown
260c9d528f New translations entities.php (Vietnamese) 2022-07-24 12:39:19 +01:00
Dan Brown
14b5c39e71 New translations entities.php (Chinese Traditional) 2022-07-24 12:39:18 +01:00
Dan Brown
4c1256f02a New translations entities.php (Chinese Simplified) 2022-07-24 12:39:16 +01:00
Dan Brown
a7f7200478 New translations entities.php (Turkish) 2022-07-24 12:39:15 +01:00
Dan Brown
f2088d3a56 New translations entities.php (Swedish) 2022-07-24 12:39:14 +01:00
Dan Brown
affd8df594 New translations entities.php (Persian) 2022-07-24 12:39:13 +01:00
Dan Brown
bc40601d7d New translations entities.php (Portuguese) 2022-07-24 12:39:12 +01:00
Dan Brown
da6169159d Merge pull request #3591 from BookStackApp/shelf_books_enhancements
Improved shelf book management interface
2022-07-24 12:28:01 +01:00
Dan Brown
b0adb74d62 Improved shelf book management interface
- Added ability to search books list (Local simple text match).
- Added handles, hover-states and cursor states for better user
  interaction and clearer use of drag & drop.
- Improved styles for dark mode.
- Converted shelf sort component to newer component format.
- Modernized shelf controller code a little.

Related to #3266
2022-07-24 12:23:25 +01:00
Dan Brown
f004cb69d7 New translations editor.php (French) 2022-07-23 19:12:14 +01:00
Dan Brown
975ba4f8d8 Added content-view body classes generated from tags
Included tests to cover.

Closes #3583
2022-07-23 18:29:04 +01:00
Dan Brown
468040edc4 New translations activities.php (Slovak) 2022-07-23 17:51:24 +01:00
Dan Brown
840a1ea011 Applied latest styleci changes 2022-07-23 15:11:06 +01:00
Dan Brown
72c8b138e1 Updated tests to use ssddanbrown/asserthtml package
Closes #3519
2022-07-23 15:10:18 +01:00
Dan Brown
cf73e5f2c6 Tweaked wording aroung the IP address precision option 2022-07-23 13:46:13 +01:00
Dan Brown
4e8995c3d0 Added ability to adjust stored IP address precision
Included tests to cover.

For #3560
2022-07-23 13:41:29 +01:00
Dan Brown
67d12cc1df Fixed failing license test 2022-07-23 12:08:55 +01:00
Dan Brown
7931ab1b91 New translations editor.php (German Informal) 2022-07-23 11:43:10 +01:00
Dan Brown
137beb4002 New translations editor.php (Polish) 2022-07-23 11:43:09 +01:00
Dan Brown
2354ce49ba New translations editor.php (Dutch) 2022-07-23 11:43:08 +01:00
Dan Brown
d0925e0e91 New translations editor.php (Lithuanian) 2022-07-23 11:43:07 +01:00
Dan Brown
69473d28f3 New translations editor.php (Korean) 2022-07-23 11:43:06 +01:00
Dan Brown
11cf9fd832 New translations editor.php (Japanese) 2022-07-23 11:43:06 +01:00
Dan Brown
c89865b574 New translations editor.php (Italian) 2022-07-23 11:43:05 +01:00
Dan Brown
8a1fb300fe New translations editor.php (Hungarian) 2022-07-23 11:43:04 +01:00
Dan Brown
8c4ed9e0b7 New translations editor.php (Portuguese) 2022-07-23 11:43:03 +01:00
Dan Brown
c4f3a71652 New translations editor.php (Hebrew) 2022-07-23 11:43:02 +01:00
Dan Brown
c5259d0195 New translations editor.php (German) 2022-07-23 11:43:01 +01:00
Dan Brown
3899b44622 New translations editor.php (Danish) 2022-07-23 11:43:00 +01:00
Dan Brown
58057855f8 New translations editor.php (Czech) 2022-07-23 11:42:59 +01:00
Dan Brown
4cdaa1ad99 New translations editor.php (Catalan) 2022-07-23 11:42:58 +01:00
Dan Brown
c737fa8a6b New translations editor.php (Bulgarian) 2022-07-23 11:42:58 +01:00
Dan Brown
c5a0b99d20 New translations editor.php (Arabic) 2022-07-23 11:42:57 +01:00
Dan Brown
554d706468 New translations editor.php (Spanish) 2022-07-23 11:42:56 +01:00
Dan Brown
4591583deb New translations editor.php (Basque) 2022-07-23 11:42:55 +01:00
Dan Brown
05f9f8f969 New translations editor.php (French) 2022-07-23 11:42:54 +01:00
Dan Brown
1f7f26bd29 New translations editor.php (Russian) 2022-07-23 11:42:53 +01:00
Dan Brown
1e028f51eb New translations editor.php (Slovenian) 2022-07-23 11:42:52 +01:00
Dan Brown
7389b33980 New translations editor.php (Norwegian Bokmal) 2022-07-23 11:42:52 +01:00
Dan Brown
70fccfd8d3 New translations editor.php (Uzbek) 2022-07-23 11:42:51 +01:00
Dan Brown
9ccee6707d New translations editor.php (Bosnian) 2022-07-23 11:42:50 +01:00
Dan Brown
2de804950b New translations editor.php (Welsh) 2022-07-23 11:42:49 +01:00
Dan Brown
e4e130a5da New translations editor.php (Latvian) 2022-07-23 11:42:48 +01:00
Dan Brown
3101d76726 New translations editor.php (Estonian) 2022-07-23 11:42:47 +01:00
Dan Brown
694da007b6 New translations editor.php (Croatian) 2022-07-23 11:42:46 +01:00
Dan Brown
ea2aa626a9 New translations editor.php (Slovak) 2022-07-23 11:42:45 +01:00
Dan Brown
9ab485093e New translations editor.php (Spanish, Argentina) 2022-07-23 11:42:44 +01:00
Dan Brown
93d9c77595 New translations editor.php (Indonesian) 2022-07-23 11:42:43 +01:00
Dan Brown
71e760e345 New translations editor.php (Portuguese, Brazilian) 2022-07-23 11:42:43 +01:00
Dan Brown
34d15230dc New translations editor.php (Vietnamese) 2022-07-23 11:42:42 +01:00
Dan Brown
af5517ad59 New translations editor.php (Chinese Traditional) 2022-07-23 11:42:41 +01:00
Dan Brown
002b093e82 New translations editor.php (Ukrainian) 2022-07-23 11:42:40 +01:00
Dan Brown
2e9000b18c New translations editor.php (Turkish) 2022-07-23 11:42:39 +01:00
Dan Brown
b24f5b7392 New translations editor.php (Swedish) 2022-07-23 11:42:38 +01:00
Dan Brown
3caf308f6b New translations editor.php (Persian) 2022-07-23 11:42:37 +01:00
Dan Brown
e01baac15d New translations editor.php (Chinese Simplified) 2022-07-23 11:42:36 +01:00
Dan Brown
f573e09004 Applied styleci changes, updated dev version & readme roadmap 2022-07-23 11:36:37 +01:00
Dan Brown
f4dd38ea94 Merge pull request #3580 from BookStackApp/tinymce6
TinyMCE6 update
2022-07-23 11:33:51 +01:00
Dan Brown
aad22384cb Enabled modern tinymce table features 2022-07-23 11:32:26 +01:00
Dan Brown
8176ca153a Fixed blue wyswiyg toolbar in dark mode 2022-07-23 11:22:34 +01:00
Dan Brown
f86bb27a83 Ensured wysiwyg details contents are wrapped in block elements
Fixes issue where inline-only content would disappear when unwrapping a
details block element.
2022-07-23 11:18:03 +01:00
Dan Brown
a9ee2e6889 Removed toolbar dialog background line 2022-07-23 10:43:47 +01:00
Dan Brown
d9f0c9eee8 New translations entities.php (French) 2022-07-20 08:32:06 +01:00
Dan Brown
7b508dac3d New translations editor.php (Chinese Simplified) 2022-07-18 18:51:48 +01:00
Dan Brown
3ca64da4a5 New translations entities.php (Italian) 2022-07-18 15:55:46 +01:00
Dan Brown
6a6f00058f Added back in image options context toolbar item 2022-07-18 13:37:50 +01:00
Dan Brown
cd929b2555 Made a bunch of tinymce 6 upgrade fixes
- Added workaround for new 'srcdoc' usage that's breaking content in
  Firefox, added new 'custom-changes.md' file to document for future.
- Updated old usages of 'new' when creating nodes.
- Tested and changed logic, where required, where 'editor.dom.select'
  has been used to replace the old '$' usages.
- Fixed bad boolean value being passed to 'setActive' in task list
  logic.
2022-07-18 13:18:46 +01:00
Dan Brown
400e584911 New translations activities.php (Norwegian Bokmal) 2022-07-18 10:54:22 +01:00
Dan Brown
9c90e798df New translations entities.php (Spanish) 2022-07-17 23:19:49 +01:00
Dan Brown
c519f707e8 Started upgrade to TinyMCE6, Untested
- Merged in latest TinyMCE code.
- Gone through tinymce upgrade guide, made required config changes.
- Altered license references.
2022-07-17 18:33:03 +01:00
Dan Brown
e024b03a61 New translations entities.php (Chinese Simplified) 2022-07-17 17:19:51 +01:00
Dan Brown
d9e9c1735a Merge pull request #3579 from BookStackApp/dompdf_and_php_deps
Updated DOMPDF, and other PHP dependancies
2022-07-17 14:40:19 +01:00
Dan Brown
56da25b07a Fixed failing tests from dompdf chanages 2022-07-17 14:32:09 +01:00
Dan Brown
24f4febcd5 Updated DOMPDF, and other PHP dependancies 2022-07-17 14:01:59 +01:00
Dan Brown
5f5b6ff0be Added "ACTIVITY_LOGGED" theme event
Closes #3572
2022-07-17 13:28:56 +01:00
Dan Brown
8f9923c7c1 Re-ordered theme events to be alphabetical 2022-07-17 13:08:44 +01:00
Dan Brown
7be7caacd5 New translations entities.php (German Informal) 2022-07-17 10:50:26 +01:00
Dan Brown
bcd06c1d56 New translations entities.php (Chinese Simplified) 2022-07-17 10:50:26 +01:00
Dan Brown
e01a0e61d9 New translations entities.php (Chinese Traditional) 2022-07-17 10:50:24 +01:00
Dan Brown
f0049e346b New translations entities.php (Portuguese, Brazilian) 2022-07-17 10:50:24 +01:00
Dan Brown
b7f84171c6 New translations entities.php (Indonesian) 2022-07-17 10:50:23 +01:00
Dan Brown
cb5b4392f4 New translations entities.php (Persian) 2022-07-17 10:50:22 +01:00
Dan Brown
4eb76699a9 New translations entities.php (Spanish, Argentina) 2022-07-17 10:50:21 +01:00
Dan Brown
a48a1d80ae New translations entities.php (Croatian) 2022-07-17 10:50:20 +01:00
Dan Brown
b3b8da0fe7 New translations entities.php (Vietnamese) 2022-07-17 10:50:19 +01:00
Dan Brown
a2440e20bc New translations entities.php (Latvian) 2022-07-17 10:50:18 +01:00
Dan Brown
f3f72fde6b New translations entities.php (Welsh) 2022-07-17 10:50:17 +01:00
Dan Brown
9023487d99 New translations entities.php (Bosnian) 2022-07-17 10:50:16 +01:00
Dan Brown
d77c0d3ddd New translations entities.php (Uzbek) 2022-07-17 10:50:15 +01:00
Dan Brown
cffa0a0cf3 New translations entities.php (Norwegian Bokmal) 2022-07-17 10:50:14 +01:00
Dan Brown
7b4e36eb38 New translations entities.php (Estonian) 2022-07-17 10:50:13 +01:00
Dan Brown
f3eb7c4208 New translations entities.php (Basque) 2022-07-17 10:50:12 +01:00
Dan Brown
2fcb0b6db2 New translations entities.php (Danish) 2022-07-17 10:50:11 +01:00
Dan Brown
ea0ac2a853 New translations entities.php (Czech) 2022-07-17 10:50:10 +01:00
Dan Brown
76049f0cdb New translations entities.php (Catalan) 2022-07-17 10:50:09 +01:00
Dan Brown
0143fe88d3 New translations entities.php (Bulgarian) 2022-07-17 10:50:08 +01:00
Dan Brown
0b89642610 New translations entities.php (Spanish) 2022-07-17 10:50:07 +01:00
Dan Brown
bc8d2d8209 New translations entities.php (French) 2022-07-17 10:50:06 +01:00
Dan Brown
f832a9545e New translations entities.php (German) 2022-07-17 10:50:05 +01:00
Dan Brown
aad2f54c15 New translations entities.php (Hebrew) 2022-07-17 10:50:04 +01:00
Dan Brown
0cd44a6e7d New translations entities.php (Arabic) 2022-07-17 10:50:03 +01:00
Dan Brown
79e386f457 New translations entities.php (Hungarian) 2022-07-17 10:50:02 +01:00
Dan Brown
387b6620e4 New translations entities.php (Russian) 2022-07-17 10:50:01 +01:00
Dan Brown
d76bdbc976 New translations entities.php (Japanese) 2022-07-17 10:50:00 +01:00
Dan Brown
89de7a60c6 New translations entities.php (Korean) 2022-07-17 10:49:59 +01:00
Dan Brown
42c6179350 New translations entities.php (Lithuanian) 2022-07-17 10:49:58 +01:00
Dan Brown
01c2c92710 New translations entities.php (Dutch) 2022-07-17 10:49:57 +01:00
Dan Brown
4b770ee2dc New translations entities.php (Polish) 2022-07-17 10:49:56 +01:00
Dan Brown
c47997bbb7 New translations entities.php (Ukrainian) 2022-07-17 10:49:55 +01:00
Dan Brown
9d78af2c1d New translations entities.php (Slovak) 2022-07-17 10:49:54 +01:00
Dan Brown
a8d933753e New translations entities.php (Slovenian) 2022-07-17 10:49:53 +01:00
Dan Brown
2853feb9c4 New translations entities.php (Swedish) 2022-07-17 10:49:52 +01:00
Dan Brown
86e7386db9 New translations entities.php (Turkish) 2022-07-17 10:49:52 +01:00
Dan Brown
ce9a788fb9 New translations entities.php (Italian) 2022-07-17 10:49:51 +01:00
Dan Brown
6b8083244d New translations entities.php (Portuguese) 2022-07-17 10:49:50 +01:00
Dan Brown
94bf7e2e0c Merge pull request #3569 from BookStackApp/permissions_v2
Permissions System Refactor
2022-07-17 10:36:33 +01:00
Dan Brown
9cf05944f6 Applied StyleCI changes 2022-07-17 10:32:16 +01:00
Dan Brown
e6e6d25974 Removed test web route, extracted text, added test 2022-07-17 10:18:24 +01:00
Dan Brown
8f90996cef Dropped use of non-view joint permissions 2022-07-16 21:50:42 +01:00
Dan Brown
2332401854 Fixed a couple of non-intended logical permission issues
Both caught in tests:
Fixed loss of permissions for admin users when entity restrictions were
active, since there are no entity-restrictions for the admin role but
we'd force generate them in joint permissions, which would be queried.
Fixed new role permission checks when permissions given with only the
action (eg. 'view'), since the type prefix would be required for role
permission checks. Was previously not needed as only the simpler form
was used in the jointpermissions after merge & calculation.
2022-07-16 20:55:32 +01:00
Dan Brown
afe1a04239 Aligned permission applicator method names
Also removed lesser used function, that was mostly a duplicate of an
existing function, and only used for search.
2022-07-16 19:54:25 +01:00
Dan Brown
f459a68535 Removed remaining dynamic action usages in joint permission queries 2022-07-16 19:28:04 +01:00
Dan Brown
1d875ccfb7 Continued removal of joint permission non-view queries
Cleaned up PermissionApplicator to remove old cache system which was
hardly ever actuall caching anything since it was reset after each
public method run.

Changed the scope of 'userCanOnAny' to just check entity permissions,
and added protections of action scope creep, in case a role permission
action was passed by mistake.
2022-07-16 13:17:08 +01:00
Dan Brown
2b4b7c68cc New translations entities.php (German) 2022-07-15 08:45:23 +01:00
Dan Brown
ad8d8dde2d New translations entities.php (German) 2022-07-15 07:39:07 +01:00
Dan Brown
23f9b4d217 New translations auth.php (German) 2022-07-15 07:39:06 +01:00
Dan Brown
bfbd0fc168 New translations activities.php (German) 2022-07-14 23:50:40 +01:00
Dan Brown
77b57c068f New translations activities.php (German) 2022-07-14 22:50:28 +01:00
Dan Brown
40d939394b Merge pull request #3573 from BookStackApp/m1_docker_support
M1/Apple Silicon dev docker compatibility
2022-07-14 11:03:25 +01:00
Dan Brown
7e04f70bf3 Tweaked docker dev container to work with m1 apple silicon
Tested on m1 macbook, needs testing on amd64
2022-07-14 01:34:57 +01:00
Dan Brown
4fb85a9a5c Started removal of non-view permission queries
Updated ajax search and entity selector usage to display and handle
items that the user does not have permission to interact with.
Started logic changes to not allow permission type to be passed around,
with views instead being the fixed sole permission.
2022-07-13 15:23:03 +01:00
Jan Koid
55dc86037f Fixed some typos and corrected grammar. 2022-07-12 23:05:44 +02:00
Dan Brown
2989852520 Added simple data model for faster permission generation 2022-07-12 21:13:02 +01:00
Dan Brown
4daac5a114 New translations auth.php (Croatian) 2022-07-12 21:07:49 +01:00
Dan Brown
82baab66cc New translations activities.php (Croatian) 2022-07-12 21:07:48 +01:00
Dan Brown
b0a4d3d059 Renamed and cleaned up existing permission service classes use 2022-07-12 20:15:41 +01:00
Dan Brown
943cb7810b New translations activities.php (Croatian) 2022-07-12 20:02:15 +01:00
Dan Brown
2d4f708c79 Extracted permission building out of permission service 2022-07-12 19:38:11 +01:00
Dan Brown
376640db25 New translations settings.php (Portuguese) 2022-07-12 13:03:52 +01:00
Dan Brown
9cfded1311 New translations common.php (Portuguese) 2022-07-12 13:03:51 +01:00
Dan Brown
dde2ea743f New translations auth.php (Portuguese) 2022-07-12 13:03:50 +01:00
Dan Brown
ddd45dde6b New translations entities.php (Portuguese) 2022-07-12 13:03:49 +01:00
Dan Brown
a99cbcfe12 New translations activities.php (Portuguese) 2022-07-12 13:03:47 +01:00
Dan Brown
c5e9dfa168 Optimized pre-joint-permission logic efficiency 2022-07-10 13:45:04 +01:00
mcgong
83d2a3c763 Fixed comment count update error 2022-07-06 17:30:46 +08:00
Dan Brown
f3d7d06536 New translations activities.php (Polish) 2022-07-05 19:10:04 +01:00
Dan Brown
bd1971c827 New translations editor.php (Ukrainian) 2022-07-05 14:30:15 +01:00
Dan Brown
61b3bc10a3 New translations common.php (Ukrainian) 2022-07-05 14:30:14 +01:00
Dan Brown
1a224e1719 New translations auth.php (Ukrainian) 2022-07-05 14:30:13 +01:00
Dan Brown
e89348b02a New translations entities.php (Ukrainian) 2022-07-05 14:30:12 +01:00
Dan Brown
451300606f New translations activities.php (Ukrainian) 2022-07-05 14:30:10 +01:00
Dan Brown
459659a680 New translations entities.php (Dutch) 2022-07-02 22:09:56 +01:00
Dan Brown
4487ea576f New translations common.php (Dutch) 2022-07-02 22:09:55 +01:00
Dan Brown
0c8bd581ae New translations auth.php (Dutch) 2022-07-02 21:05:42 +01:00
Dan Brown
f9f4a87e1b New translations entities.php (Dutch) 2022-07-02 21:05:41 +01:00
Dan Brown
f4fda8d80c New translations activities.php (Dutch) 2022-07-02 21:05:40 +01:00
Dan Brown
6d66682620 New translations entities.php (German) 2022-06-30 09:36:18 +01:00
Dan Brown
ab52f3367a New translations editor.php (Russian) 2022-06-29 15:26:03 +01:00
Dan Brown
5ee79d16c9 Updated version and assets for release v22.06.2 2022-06-28 11:57:37 +01:00
Dan Brown
a1ea4006e0 Merge branch 'development' into release 2022-06-28 11:57:24 +01:00
Dan Brown
a721405202 New Crowdin updates (#3540) 2022-06-28 11:56:07 +01:00
Dan Brown
d20aacb732 Merge branch '3535-group-sync-fix' into development 2022-06-28 11:47:22 +01:00
Dan Brown
65fa96e405 New Crowdin updates (#3531) 2022-06-27 14:29:10 +01:00
Dan Brown
736d6afb7d Aligned entity-selector-popup button and dblclick behaviour
Fixes #3534
2022-06-27 14:27:29 +01:00
Dan Brown
0bcd1795cb Auth group sync: Fixed unintential mapping behaviour change
Due to change in how casing was handled when used in the "External Auth
ID" role field.
Likely related to #3535.
Added test to cover.
2022-06-27 14:18:46 +01:00
Dan Brown
47887ec595 Added path example to visual theme system 2022-06-27 13:38:51 +01:00
Dan Brown
9078188939 Updated version and assets for release v22.06.1 2022-06-25 14:33:07 +01:00
Dan Brown
ed0aad1a7a Merge branch 'development' into release 2022-06-25 14:32:49 +01:00
Dan Brown
43749cd94e Merge branch 'development' of github.com:BookStackApp/BookStack into development 2022-06-25 14:27:46 +01:00
Dan Brown
107df6c28f Applied StyleCI changes 2022-06-25 14:27:32 +01:00
Dan Brown
c1d1ec5b89 New Crowdin updates (#3526) 2022-06-25 14:26:40 +01:00
Dan Brown
12c282597d Fixed non-translated category strings
For #3529
2022-06-25 14:24:38 +01:00
Dan Brown
c9d0e22132 Updated entity-selector-popup to reset on selection
Better links the core selector component to the popup version, with new
public methods for direct controlling.

For #3528
2022-06-25 14:13:17 +01:00
Dan Brown
0801955a26 Fixed grid layouts being pushed out by child content
Ran a quick app run-thorugh in FireFox & Chrome, No secondary affects
immediately noticed but possible this could cause changes elsewhere due
to wide-spread grid item child targeting.

For #3523
2022-06-25 13:55:57 +01:00
Dan Brown
3ed1ffdbeb Fixed issue blocking tags on book update
For #3527
2022-06-25 13:46:55 +01:00
Dan Brown
5c59cfb020 Updated version and assets for release v22.06 2022-06-24 11:50:56 +01:00
Dan Brown
3ca15ad68a Merge branch 'development' into release 2022-06-24 11:45:29 +01:00
Dan Brown
36f0a68f1b Added missing welsh locale to config 2022-06-24 11:42:38 +01:00
Dan Brown
ed981cbab1 New Crowdin updates (#3428) 2022-06-24 11:35:59 +01:00
Dan Brown
f69af8933c Updated translator list before v22.06 release 2022-06-24 11:30:15 +01:00
Dan Brown
46d71a181e Updated php deps and applied styleci changes 2022-06-22 12:49:58 +01:00
Dan Brown
8d8da31fdd Added base template convenience partials for theme system users
Included test to cover usage and paths.
Closes #894
2022-06-22 12:47:31 +01:00
Dan Brown
0d9b5a9d90 Merge branch 'login-auto-redirect' into development 2022-06-21 15:38:01 +01:00
Dan Brown
8b211ed461 Review and update of login auto initiation PR
For PR #3406

- Updated naming from 'redirect' to 'initate/initation'.
- Updated phpunit.xml and .env.example.complete files with the new
  option.
- Cleaned up controller logic a bit.
- Added content and design to the new initation view to not leave user
  on a blank view for a while.
- Added non-JS button to initiation view as fallback option for
  progression.
- Moved new test to it's own Test class and expanded with additional
  scenario tests for better functionality coverage.
2022-06-21 15:32:18 +01:00
Dan Brown
9dd69b04b8 Fixed code snippets being added as single line
TinyMCE was adding attributes to <br> elements within code blocks which
would then not be converted to newlines by our code regex match.
This changes the conversion to use dom querying instead.

Fixes #3507
2022-06-21 12:01:06 +01:00
Dan Brown
0c6f598d91 Fixed issue where text after line breaks not indexed
Linebreaks would previously essentially be removed during index and
hence joined to adjacent words, breaking prefix matching.
Added test to cover.
For #3508
2022-06-20 23:47:42 +01:00
Dan Brown
df94b73e29 Merge pull request #3512 from BookStackApp/code_manager_updates
WYSIWYG Code Editor Updates
2022-06-20 23:13:28 +01:00
Dan Brown
7d4b941abf Added code editor changes mobile design handling 2022-06-20 23:12:07 +01:00
Dan Brown
d181106df3 Adjusted code manager changes for dark mode 2022-06-20 23:06:54 +01:00
Dan Brown
75110813e6 Aligned other popup windows
Primary change was altering image-manager to use same footer bar style
as other windows.
2022-06-20 23:02:06 +01:00
Dan Brown
1e41546e51 Updated code editor language lists
To align and update supported languages.

Related to #3511 and #3494
2022-06-20 17:49:56 +01:00
Dan Brown
f39b565a1c Tweaked code editor sidebar side to be smaller 2022-06-20 17:16:28 +01:00
Dan Brown
77cd550fae Polished up code editor design 2022-06-20 17:11:34 +01:00
Dan Brown
96d9077479 Started design changes to the code-editor 2022-06-20 13:42:12 +01:00
Dan Brown
be1d691529 Merge pull request #3499 from BookStackApp/convert_hierachy
Chapter and Book Conversion Actions
2022-06-20 12:51:13 +01:00
Dan Brown
8cde362f6f Removed bad trailing comma in method 2022-06-19 18:45:48 +01:00
Dan Brown
388343aeb0 Fixed failing tests after conversion changes 2022-06-19 18:44:34 +01:00
Dan Brown
ba25dda031 Applied styleci changes for conversion work 2022-06-19 18:14:53 +01:00
Dan Brown
85f59b5275 Added tests for content conversion action permissions
- Updated 'removePermissionFromUser' test helper to work for
  entity-permissions that become part of the joint permissions system.
2022-06-19 18:12:36 +01:00
Dan Brown
65d4505079 Added tests and doc updates for shelf/book cover image API abilities 2022-06-19 17:26:23 +01:00
Dan Brown
663f81a2b1 Added tests to cover convert functionality
Also updated cloner class with typed properties.
2022-06-19 16:57:33 +01:00
Dan Brown
f145ffc930 Extracted conversion text to translation file 2022-06-19 16:23:18 +01:00
Dan Brown
19d7e26dda Merge pull request #3503 from andrii-bodnar/fix/crowdin-name
Fix Crowdin name in the language_request issue template
2022-06-16 12:07:40 +01:00
Andrii Bodnar
a13b9d8d14 Fix Crowdin name in the language_request issue template 2022-06-16 11:34:27 +03:00
Dan Brown
8c67011a1d Got book to shelf conversions working
- Also extracted shelf to book view elements to own partial.
- Fixed some existing logic including image param handling in update
  request and activity logging against correct element.
2022-06-15 15:05:08 +01:00
Dan Brown
8da856bac3 Got chapter conversion to books working
- Added required UI within edit view.
- Added required routes and controller actions.
2022-06-14 16:42:29 +01:00
Dan Brown
90ec40691a Added clone of entity permissions on chapter/book promotion 2022-06-14 15:55:44 +01:00
Dan Brown
d676e1e824 Started work on hierachy conversion actions
- Updates book/shelf cover image handling for easier cloning/handling.
- Adds core logic for promoting books/chapters up a level.
- Enables usage of book/shelf cover image via API.

Related to #1087
2022-06-13 17:20:21 +01:00
Dan Brown
0a05119aa5 Applied styleci changes, updated composer deps 2022-06-10 12:37:14 +01:00
Dan Brown
abc283fc64 Extracted download response logic to its own class
Cleans up base controller and groups up download & streaming logic for
potential future easier addition of range request support.
2022-06-08 23:50:42 +01:00
Dan Brown
e72ade727d Added audio mimes to our safe list for inline serving
Closes #3485
2022-06-08 22:30:55 +01:00
Dan Brown
c8b123bfac Updated composer deps, applied styleci changes 2022-06-08 18:00:30 +01:00
Dan Brown
88012449f3 Reorganised and split out export templates & styles
Moved export templates elements into their own folder for better
grouping of logical usage.
Within the base export template, added some body classes to allow easier
targeted customisation via custom head css.
Split content of export templates into smaller partials for easier
future customization.

Closes #3443
2022-06-08 17:56:59 +01:00
Dan Brown
e00d88f45d Updated markdown preview to update on diff-basis
Uses vdom system to diff and update the current markdown preview view
instead of requiring a full HTML replace change.
This should provide better performance, expecially where dynamically
loaded content such as iframes were in use.

Closes #3454
2022-06-07 16:07:28 +01:00
Dan Brown
3fe666f36a Updated image drop handling to respect original file name
Now uses the previously timestamp gen name as a backup to the original
name. Aligns with the image manager upload which uses the original name
where given.

Closes #3470
2022-06-07 14:59:00 +01:00
Dan Brown
3f271ebecb Removed image_id property from books & shelves api docs
This was either not provided or not provided for the last 18 months.
Likely not providing much value as-is so removing.

Closes #3474
2022-06-07 14:30:43 +01:00
Dan Brown
7c597a05f6 Added codeblock latex/stext support
For #3458
2022-05-30 18:41:40 +01:00
Dan Brown
16e023985d Prevented inadvertant logging during MFA flow
- Added StoppedAuthenticationException to dontReport list.
- Added test to cover.

Closes #3468
2022-05-30 18:31:08 +01:00
Dan Brown
43cbab2822 Merge branch 'development' of github.com:BookStackApp/BookStack into development 2022-05-30 17:01:46 +01:00
Dan Brown
1a3505c899 Updated JS deps 2022-05-30 17:01:32 +01:00
Dan Brown
2930025f51 Update dev version to track current release target 2022-05-30 16:58:01 +01:00
Dan Brown
39fcf3a68f Merge pull request #3416 from BookStackApp/group_sync_comma_escaping
Added ability to escape role "External Auth ID" commas
2022-05-30 16:55:32 +01:00
Dan Brown
6ce34fe6cc Merge pull request #3433 from BookStackApp/tiny_improvements
Bunch of tiny improvements
2022-05-30 16:51:59 +01:00
Dan Brown
3c3aed58aa Updated funding with kofi link 2022-05-30 16:49:24 +01:00
Dan Brown
73f36b279e Updated PHP deps 2022-05-30 16:46:48 +01:00
Dan Brown
2b817e7d24 Updated attachment links to have dropdown for open type
- Allows easier accessibility of inline attachments.
- Introduces a new split-icon-list-item thingy to support such cases
  where only part of the button is actually linked.
2022-05-19 17:38:04 +01:00
Dan Brown
cb10ad804f Made chapter toggle in book sidebar nav more consistent
- Now has a hover state to match other items.
- Now spans the full sidebar with like other items.
- Also updated chapter-toggle to a chapter-contents component, following
  the newer component system.
2022-05-18 14:06:40 +01:00
Dan Brown
eeccc2ef10 Readjusted book child item styles after other changes
Was extra space showing due to structure changes and flex gap.
2022-05-18 13:28:34 +01:00
Dan Brown
b030c1398b Tweaked chapter list item styles
- Improves animation smoothness
- Changed animation slideup/down animations to use max-height instead of height
  to better avoid jutter at the end.
- Cleaned spacing to match page items in books listing.
2022-05-18 13:18:21 +01:00
Dan Brown
4759fa1e1f Made the "Custom HTML Head Content" setting a highlighted code editor 2022-05-17 17:39:31 +01:00
Dan Brown
cb1c2db282 Aligned collapsed header dropdown item styles
Previously the desktop-visible items would style different when collapsed
into the expanded dropdown menu, compared to existing items.
2022-05-17 14:27:58 +01:00
Dan Brown
4866a3a198 Refined header bar styles
- Updated many items to be flexbox-based.
- Updated & aligned hover states across header bar items.
2022-05-17 14:16:43 +01:00
Dan Brown
340c9ec7a1 Fixed some inputs affected by height changes 2022-05-17 13:37:43 +01:00
Dan Brown
49498cfaf9 Fixed entity-specific tag counts listing
Was reporting wrong due to use of old polymorphic namespace references.
Test was not picking up as assertElementContains had wider scope than
expected, looking within the HTML of the element instead of the text
which you might expect. Updated test helper to look at text instead.
2022-05-16 14:05:21 +01:00
Dan Brown
3a4aa81115 Removed dialog debug script from default home
Accidentally left in from before.
Closes #3430
2022-05-16 13:36:42 +01:00
Dan Brown
d20c74babf Improved input size consistency
Specifically updates dropdown search and user-search implementation,
although does affect all inputs.
Decouples breadcrum and select-style dropdown search toggles.

Addresses #2678
2022-05-14 16:05:29 +01:00
Dan Brown
9fda0df798 Updated dropdown search boxe positions to align with other dropdowns 2022-05-14 14:19:54 +01:00
Dan Brown
6fa699a835 Fixed skip-to-content link shadow being slightly visible
Would cause a slight dark area in top left of view while hidden.
2022-05-14 13:59:10 +01:00
Dan Brown
78920d7d65 Updated tri-layout sidebars to not be cut-off by padding
Would cause effect where scroll area would be cut of by spacing which
looked a bit strange. This retains the same padding sizes but cuts the
content at the header or top of viewport.
2022-05-14 13:55:03 +01:00
Dan Brown
35a47a273b Added animation transition for breadcrumb dropdown load
Animates the height on breadcrumb dropdown menus to transition to the
loaded animations quicker. Includes a new animation helper for doing
similar tasks in future.
2022-05-14 13:32:25 +01:00
Dan Brown
89dfa43e73 Fixed loading animation delay
Loading animation would show in an unready state due to animation-delay
on components. Updated to a negative delay to ensure elements were in
correct positions right away upon show.
2022-05-14 13:31:24 +01:00
Dan Brown
2c74dfd1d4 Updated breadcrumb dropdown styles, improved keyboard nav
- Removed harsh theme color border between search and content.
- Prevented intermediate focus on list container to align arrow & tab
  behaviour, and to get to content quicker.
2022-05-14 13:11:48 +01:00
Dan Brown
e6864a9cff Improved card list design
- Removed border and rounded list item styles to make hover states have
  less edge detail and to align with other UI elements.
- In expanded-detail view, removed space used for entity description if
  there is not description content existing.
2022-05-14 12:54:23 +01:00
Dan Brown
60e319c4b4 Tidied up book navigation styles
- Removed background track line since it would darken entity item bars.
- Updated item spacing to be a bit tighter.
- Updated action hover styles to be a bit lighter, and visible on dark
  mode, to fit rest of system.
2022-05-13 18:34:47 +01:00
Dan Brown
24b31b624c Cleaned up entity details listing 2022-05-13 18:03:43 +01:00
Dan Brown
a0fe6147d8 Improved the display of dropdown menus
- Tweaked styling to add a little extra shadow and be more rounded to
  match other UI areas.
- Added slight horizontal inset when in right sidebar to prevent shadow
  being cut-off in most cases.
- Added logic to "drop upwards" if dropping down would take the menu
  offscreen.
2022-05-13 17:12:45 +01:00
Dan Brown
221d910ff2 Reduced excess margin in chapter contents lists 2022-05-12 17:27:57 +01:00
Dan Brown
bef2045df1 Embedded css sources for easier firefox dev work 2022-05-12 17:27:29 +01:00
Dan Brown
f021823287 Updated default value for secure session detection
Updated default value for APP_URL so that the startsWith call is not
passed null, since that causes deprecation notice in PHP8.1.
Would show when APP_URL was not set, adding extra confusiion.
2022-05-11 16:47:09 +01:00
Dan Brown
60014989f5 Updated version and assets for release v22.04.2 2022-05-09 16:10:16 +01:00
Dan Brown
57b10f195e Merge branch 'development' into release 2022-05-09 16:09:54 +01:00
Dan Brown
3a8a476906 Updated translators, applied styleCI change 2022-05-09 16:09:31 +01:00
Dan Brown
328bc88f02 Fixed LDAP_DUMP_* options when data contains binary
Dumping details that were binary, such as the jpegphoto data, would
cause the dump to fail on the encoding to JSON.
This change forces content to be UTF8 before dumping.
Updated existing test to cover.

Closes #3396
2022-05-09 15:57:50 +01:00
Dan Brown
2a99e23e6d Updated attachment download to check OB before cleaning it
Call to `ob_end_clean` would error if the environment did not use the
PHP `output_buffering` option. This adds an additional check and updates
the comment to be more specific to the exact scenario of the condition.
Tested with output_buffering=Off and output_buffering=4096

Closes #3415
2022-05-09 15:25:06 +01:00
Dan Brown
b855bbaaea New Crowdin updates (#3418) 2022-05-09 15:15:35 +01:00
Dan Brown
96436839f1 Added rate limit section to the API docs
Closes #3423
2022-05-09 15:12:29 +01:00
Dan Brown
b4f29a85ab Added Farsi language available
Closes #3426
2022-05-09 14:58:04 +01:00
Dan Brown
4a2a044f3d Updated PHP deps 2022-05-09 14:57:34 +01:00
Dan Brown
ca09ed916f Added support plans link to issue links 2022-05-05 15:48:27 +01:00
Dan Brown
dbefda055f Updated method of string interpolation
In prep for future PHP changes as per RFC
https://wiki.php.net/rfc/deprecate_dollar_brace_string_interpolation
2022-05-05 09:33:25 +01:00
Dan Brown
b1e95eb39f Updated version and assets for release v22.04.1 2022-05-04 21:26:58 +01:00
Dan Brown
b3da77b8f9 Merge branch 'development' into release 2022-05-04 21:26:31 +01:00
Dan Brown
93ef8c97b6 Applied styleci changes 2022-05-04 21:19:46 +01:00
Dan Brown
420b29f32f New Crowdin updates (#3402) 2022-05-04 21:18:47 +01:00
Dan Brown
d795af04df Added ability to escape role "External Auth ID" commas
- Using a backslash in this field before a comma.
- Could potentially (Although unlikely) be a breaking change.

For #3405
2022-05-04 21:03:13 +01:00
Dan Brown
d2ed98d20d Merge branch 'development' of github.com:BookStackApp/BookStack into development 2022-05-04 21:01:20 +01:00
Dan Brown
ebc69a8f2c Fixed double path slash URL issue in some cases
- Occurred on system request path usage (Primarily on guest login
  redirection) when a custom path was not in use.
- Added test to cover.

For #3404
2022-05-04 20:08:22 +01:00
Robert Meredith
d5ce6b680c Skip intermediate login page with single provider 2022-05-02 20:35:11 +10:00
Dan Brown
1a345b74bb Updated version and assets for release v22.04 2022-04-29 15:55:32 +01:00
Dan Brown
8ffc3a4abf Merge branch 'development' into release 2022-04-29 15:55:05 +01:00
Dan Brown
44013721f0 New Crowdin updates (#3401) 2022-04-29 15:53:06 +01:00
Dan Brown
16222de5fa Added uzbeck into local list
Not yet an actual added language yet due to low translation rate.
2022-04-29 15:52:11 +01:00
Dan Brown
ebfe946160 Updated translation attribution before v22.04 2022-04-29 15:43:30 +01:00
Dan Brown
5d2aad6a9e Merge pull request #3373 from evandroamaro/patch-1
Tiny header
2022-04-29 15:41:04 +01:00
Dan Brown
8fb016d1bf New Crowdin updates (#3384) 2022-04-29 15:40:38 +01:00
Dan Brown
c216a6a210 Applied stylci changes, updated composer deps 2022-04-29 15:38:06 +01:00
Dan Brown
26af9acc6c Improved iframe & summary handling in HTML to MD conversion 2022-04-29 14:58:28 +01:00
Dan Brown
c8a7acb6c7 Fixed drawing handling on HTML to Markdown conversion 2022-04-29 12:17:14 +01:00
Dan Brown
d3b39fbe50 Move html to markdown formatting tests to their own class 2022-04-29 11:50:34 +01:00
Dan Brown
ac7b2dd1bf Tweaked DRAW.IO params in complete .env file to show configure param 2022-04-27 17:52:35 +01:00
Dan Brown
f1a8ad4980 Applied latest StyleCI changes 2022-04-25 18:42:31 +01:00
Dan Brown
d5b7fff102 Merge branch 'recycle_bin_api_endpoints' into development 2022-04-25 18:32:55 +01:00
Dan Brown
0930e8519c Updated polymorphic database relation types to simpler version
- Means we can use these simpler types in API response, As desired in #3377.

Closes #3395
2022-04-25 18:31:37 +01:00
Dan Brown
ff8dadefee Reviewed recycle bin API PR and made changes
Made the following changes, many of these are just to align with
existing conventions.

- Updated urls to be hypenated, instead of underscored, to match other system endpoints.
- Updated URL parameter to be `deletionId` instead of `id`, and removed the ID-based comment on controller methods, so the required ID model is clear from the URL alone, since its not clear from the URL endpoint alone like existing endpoints. This follows the pattern used in the "web" routes.
- Added extra detail on some controller method comments, and copied permission comment to each method.
- Removed existing field visibility mechanisms to use simpler model-based visibility since we didn't need anything too special here (After some of my other changes).
- Allowed the "deletable" model to be shown in response to provide a little more detail on the main deleted item.
- Updated parent/child-count loading to be on the "deletable" model instead of additional properties which results in simpler controller logic and enforces the idea these are relations on the deletable, not the deletion itself. It also removes additional exposure of model namespacing.
- Updated (int) casts to intval, just since that's our most common conversion method in the codebase.
- Testing: Removed `actingAsAuthorizedUser` and used the admin user instead to prevent extra auth steps on each test.
- Testing: Cut logic/data-checks from tests if already covered by other tests.
- Testing: Added simple assertions for delete/restore response data.
- Examples: Updated list example to reflect changes.

Review of PR #3377
To be followed up with changes to polymorphic relations to hide
namespacing.
2022-04-25 17:54:59 +01:00
Dan Brown
2b0ae23da0 Updated composer deps, applied latest StyleCI changes 2022-04-24 18:22:40 +01:00
Dan Brown
63cb6015a8 Merge pull request #3364 from BookStackApp/app_url_requests
Updated custom request overrides to better match original intent
2022-04-24 14:52:38 +01:00
Dan Brown
5a7fb20116 Merge pull request #3387 from BookStackApp/editor_switching
Page editor switching
2022-04-24 14:03:03 +01:00
Dan Brown
829f808800 Merge pull request #3365 from BookStackApp/data_streaming
Add data streaming where beneficial to reduce memory usage
2022-04-24 13:59:47 +01:00
Dan Brown
0dfe5cb66b Merge pull request #3391 from BookStackApp/drawio_config_event
Made it possible to configure draw.io/diagrams.net integration
2022-04-24 13:58:59 +01:00
julesdevops
14bccae6bd do some cleanup and add doc 2022-04-24 10:49:29 +02:00
Dan Brown
b97c150ac8 Added additional testing for editor switching permissions 2022-04-23 23:34:15 +01:00
Dan Brown
0c5723d76e Switched to database-based tracking for page editor
- Works better to avoid bad assumptions when showing the editor based
  upon content type.
- Also updated some previous tests to cleaner format.
2022-04-23 23:20:46 +01:00
Dan Brown
bec61a56c0 Added listing of editor type to revisions
- Also tweaked some editor revision table styles and merged some
  sections to reduce space usage.
2022-04-23 15:03:58 +01:00
Dan Brown
1b46aa8756 Aded tests for core editor switching functionality 2022-04-23 14:22:04 +01:00
julesdevops
f14e6e8f2d Complete list endpoint and add some tests 2022-04-21 22:23:24 +02:00
Dan Brown
0003ce61cd Fixed failing test after drawio default url change 2022-04-20 23:42:47 +01:00
Dan Brown
d76bbb2954 Made it possible to configure draw.io/diagrams.net integration
Added new editor public event to hook into draw.io configuration step.
Required change of embed url to trigger the configure step.
2022-04-20 23:32:02 +01:00
Dan Brown
478067483f Linked up confirmation prompt to editor switching 2022-04-20 18:21:21 +01:00
Dan Brown
eff539f89b Added new confirm-dialog component, both view and logic 2022-04-20 14:58:37 +01:00
Dan Brown
214992650d Standardised dropdown list item styles, Extracted page editor toolbar
- Updated all dropdown list item actions into three specific styles:
  icon-item, text-item & label-item. Allows a stronger structure while
  prevents mixing of styles as we were getting for header dropdown in
  dark mode.
- Extracted out page editor top toolbar to its own view file & split
  editor switch options to different markdown options.
2022-04-20 14:03:47 +01:00
Dan Brown
492ffff0a4 Added core editor switching functionality 2022-04-18 17:39:28 +01:00
Dan Brown
956eb1308f Aligned page edit controller method data usage
Extracted page editor view data gathering to its own class for
alignment. Updated the data used in views as part of the process to use
view-specific variables instead of custom attributes added to models.
Also moved tinymce library loading so it's not loaded when not using the
wysiwyg editor.
2022-04-17 23:01:14 +01:00
Dan Brown
0cc215f8c3 Added editor type change button 2022-04-17 15:01:29 +01:00
Dan Brown
e8e38f1f7b Added an 'editor-change' role permission 2022-04-17 14:33:06 +01:00
Dan Brown
7dc80a9e14 Updated editor setting to reflect "Default editor" 2022-04-17 14:13:14 +01:00
Dan Brown
e49afdbd72 New Crowdin updates (#3358) 2022-04-14 16:14:05 +01:00
Dan Brown
56254bdb66 Added testing for our request method overrides 2022-04-13 13:02:42 +01:00
Dan Brown
25654b2322 Fixed base URL starting slash usage 2022-04-13 12:46:19 +01:00
Dan Brown
27339079f7 Extracted esbuild config to a build script
Allows us to use NodeJS code for file/directory locating to not be
shell/os specific, while also also reducing duplicated complexity within
packages.json file.

Related to #3323
2022-04-13 12:08:56 +01:00
julesdevops
55e52e45fb Start recycle bin API endpoints: list, restore, delete 2022-04-07 22:34:00 +02:00
evandroamaro
c979e6465e Tiny header
Had the same translation as the small header. Corrected the translation.
2022-04-05 10:53:52 +01:00
Dan Brown
c30a9d3564 Touched entity timestamps on entity tag update
Decided it's relevant to entity updated_at since tags are now indexed
alongside content.

- Also fixed tags not applied on shelf.
- Also enforced proper page API update validation.
- Adds tests to cover.

For #3319
Fixes #3370
2022-04-04 17:24:05 +01:00
Dan Brown
59d1fb2d10 Fixed tests from streaming changes
- Added testing check to buffer stop/clear on streaming output due to
  interference during tests.
- Made content-disposition header a little safer in download responses.
- Also aligned how we check for testing environment.
2022-04-03 16:22:31 +01:00
Dan Brown
08a8c0070e Added streaming support to API attachment read responses
Required some special handling due to the content being base64-encoded
within a JSON response.
2022-04-02 19:21:19 +01:00
Dan Brown
cb770c534d Added streamed uploads for attachments 2022-04-02 18:46:48 +01:00
Dan Brown
6749faa89a Fixed streamed outputs in more extreme scenarios
Fixes hitting memory limits where downloaded file sizes are much greater
than memory limit. Stopping and flushing output buffer seemed to stop
limits causing issues when fpassthru is used.
Tested with 24M memory limit and 734M file
2022-04-02 18:42:15 +01:00
Dan Brown
82e8b1577e Updated attachment download responses to stream from filesystem
This allows download of attachments that are larger than current memory
limits, since we're not loading the entire file into memory any more.

For inline file responses, we take a 1kb portion of the file to sniff
before to check mime before we proceed.
2022-04-02 18:07:43 +01:00
Dan Brown
4dce03c0d3 Updated custom request overrides to better match original intent
This updates the custom Request handler to provide only the scheme and
host on the `getSchemeAndHttpHost` call, instead of providing the whole
APP_URL value, while adding an override to the 'getBaseUrl' to use the
APP_URL content instead of the guessed/detected Symfony value.

Untested apart from simple local setup.

Related to #2765
2022-04-02 17:14:37 +01:00
Dan Brown
7233c1c7b2 Updated version and assets for release v22.03.1 2022-03-30 19:37:07 +01:00
Dan Brown
1309a01131 Merge branch 'development' into release 2022-03-30 19:36:45 +01:00
Dan Brown
affae2e3c4 New Crowdin updates (#3354) 2022-03-30 19:29:13 +01:00
Dan Brown
1a90b98b8f Updated composer dependancies 2022-03-30 19:22:47 +01:00
Dan Brown
da4308bb0f Fixed settings redirect issue and custom head display
- Fixed issue where redirect for `/settings` view would not be ran
  through base url generator so would not create a correct path in some
  cases. Now routed through controller with normal redirect.
- Fixed custom head content being active on settings pages due to route
  name changes, for when viewing settings, in last release.

Fixes #3356 and #3355
2022-03-30 19:15:24 +01:00
Dan Brown
0333185b6d Updated version and assets for release v22.03 2022-03-30 13:49:17 +01:00
Dan Brown
83f89f64e8 Merge branch 'development' into release 2022-03-30 13:49:05 +01:00
Dan Brown
135022136a New Crowdin updates (#3353) 2022-03-30 13:31:59 +01:00
Dan Brown
12f96bb1a4 Updated translation contributors, added Basque to language options 2022-03-30 13:12:17 +01:00
Dan Brown
678314a0c5 New Crowdin updates (#3320) 2022-03-30 13:00:27 +01:00
Dan Brown
0887c39694 Updated example env with LDAP group dump option 2022-03-29 11:49:02 +01:00
Dan Brown
078e8e7dc3 PHPStan and StyleCI fixes
- Updated PhpStan PHP version option to match project.
- Applied StyleCI changes.
- Updated static to self in WebhookFormatter, following static analysis
  guidance.
- Fixed mis-matched header tags.
2022-03-28 11:31:06 +01:00
Dan Brown
038015f852 Merge pull request #3349 from BookStackApp/settings_reorg
Reorganization of settings view
2022-03-28 11:22:21 +01:00
Dan Brown
7c12920dc8 Added 404 response for non-existing setting categories
- Added test to cover.
2022-03-28 11:16:20 +01:00
Dan Brown
895f656897 Split out settings view and made functional
- Split settings out to new views using a core shared layout.
- Extracted added language text to translation files.
- Updated settings routes to be dynamic to category.
- Added redirect for old primary settings route.
- Updated existing tests to cover settings route changes.
- Added tests to cover settings view.
- Improved contrast of settings links for dark mode.
2022-03-28 11:09:55 +01:00
Dan Brown
31dbf132b9 Started playing with new settings view layout 2022-03-26 21:36:05 +00:00
Dan Brown
b5281bc9ca Fixed tests, applied StyleCI changes 2022-03-26 20:38:03 +00:00
Dan Brown
3625f12abe Added extendable/scalable formatter for webhook data
Creates a new organsied formatting system for webhook data, with
interfaces for extending with custom model formatting rules.
Allows easy usage & extension of the default bookstack formatting
behaviour when customizing webhook events via theme system, and keeps
default data customizations organised.

This also makes the following webhook data changes:
- owned_by/created_by/updated_by user details are loaded for events with
  Entity details. (POTENTIALLY BREAKING CHANGE).
- current_revision details are loaded for page update/create events.

Added testing to cover added model formatting rules.

For #3279 and #3218
2022-03-26 16:53:02 +00:00
Dan Brown
55d61fceb2 Added manual image thumbnail exif orientation handling
Uses original image data to extract orientation exif to apply image
transformations before scaling and save. Manually done due to issues
with exif data loss during the existing Invervention image path.

For #1854
2022-03-26 12:32:08 +00:00
Dan Brown
2325a307a5 Applied latest styleCI changes 2022-03-25 11:14:27 +00:00
Dan Brown
d2b49084b0 Added pre-render sizes to wysiwyg code blocks
Sets sizes on WYSIWYG code block sections based on content lines
as an early pre-codemirror height prediction to avoid excessive
jumping in the editor.

For #3326
2022-03-25 11:13:04 +00:00
Dan Brown
8594f42584 Added LDAP group debugging env option
Closes #3345
2022-03-23 16:34:23 +00:00
Dan Brown
dd7463259a Added wysiwyg filter to handle <br> tags within code blocks
This filters out <br> elements within code blocks and replaces them with
newlines. The editor started using <br>'s more harshley after some
configuration changes upon upgrading tinymce, in which we standardised
on forced br tags to avoid empty elements.

For #3327
2022-03-23 15:11:14 +00:00
Dan Brown
d23b24b8db Added additional missing editor translations
- Also merged StyleCI fixes

As per #3342
2022-03-23 14:41:54 +00:00
Dan Brown
1c859e94e0 Fixed conctenation of direct book pages within markdown export
- Updated to ensure seperation with newlines.
- Added test to cover.

For #3341
2022-03-23 14:31:42 +00:00
Dan Brown
981807220c Applied StyleCI changes and updated dependancies 2022-03-23 12:02:01 +00:00
Dan Brown
a2231c3604 Merge pull request #3333 from BookStackApp/wysiwyg_tasklist
WYSIWYG tasklist support
2022-03-23 11:58:16 +00:00
Dan Brown
622adc5450 Updated justify translation for editor
Fixes #3342
2022-03-23 11:57:20 +00:00
Dan Brown
95e496d16f Added translation string for tasklist WYSIWYG action 2022-03-23 11:54:27 +00:00
Dan Brown
883e18f7c4 Updated tasklist style and functionality for cross-browser use
- Updated styles to better align checkboxes within page content.
- Updated functionality to use a cross-compatible property on checkbox
  click within the editor.
2022-03-23 11:51:19 +00:00
Dan Brown
c5aad29c72 Added tasklist support to markdown exporter 2022-03-22 14:56:51 +00:00
Dan Brown
ea62fe6004 Improved tasklist wysiwyg behaviour
- Updated buttons/actions to better handle nesting.
- Added hack for better usage with normal bullets
2022-03-22 14:03:20 +00:00
Dan Brown
5ae9ed1e22 Added functioning wysiwyg tasklist toolbar button
- Includes new icon.
- Includes menu button overrides of existing list styles to prevent
  incompatible mixing.
2022-03-20 13:30:48 +00:00
Dan Brown
b6be8a2bb9 Added WYSIWYG tasklist clicking ability 2022-03-20 11:59:46 +00:00
Dan Brown
65dd7ad1e9 Changed to a psuedo-style approach for tasklist in wysiwyg 2022-03-19 17:13:26 +00:00
Dan Brown
f991948c49 Started initial tasklist attempt, failed implementation 2022-03-19 16:04:33 +00:00
Dan Brown
ee6a2339b6 Applied latest styleCI changes 2022-03-09 14:30:36 +00:00
Dan Brown
fd26f54b99 Merge pull request #3298 from BookStackApp/wysiwyg_links
WYSIWYG editor link updates
2022-03-09 14:29:03 +00:00
Dan Brown
11a1a6fb16 Updated version and assets for release v22.02.3 2022-03-07 15:12:22 +00:00
Dan Brown
882c609296 Merge branch 'development' into release 2022-03-07 15:12:09 +00:00
Dan Brown
77ad819970 Updated translation attribution before v22.02.3 release 2022-03-07 15:06:44 +00:00
Dan Brown
2835e5be93 New Crowdin updates (#3312) 2022-03-07 15:06:21 +00:00
Dan Brown
856fca8289 Updated CSP with frame-src rules
- Configurable via 'ALLOWED_IFRAME_SOURCES' .env option.
- Also updated how CSP rules are set, with a single header being used
  instead of many.
- Also applied CSP rules to HTML export outputs.
- Updated tests to cover.

For #3314
2022-03-07 14:27:41 +00:00
Dan Brown
48d0095aa2 Added mysql-ssl-ca option to complete .env 2022-03-02 21:51:18 +00:00
Dan Brown
176a0dcd59 Updated version and assets for release v22.02.2 2022-03-01 22:45:41 +00:00
Dan Brown
94b0f70bfa Merge branch 'development' into release 2022-03-01 22:45:12 +00:00
Dan Brown
36d7ff77a9 New translations editor.php (Italian) (#3301) 2022-03-01 22:32:43 +00:00
Dan Brown
fb16ac326f Reduced dynamic fade in dark mode
For #3203
2022-03-01 22:29:31 +00:00
Dan Brown
5947f59a04 Updated strategy for empty newline sections
- For some reason, TinyMCE would handle empty paragraphs with a '&nbsp'
  by default but this would be removed when the paragraph had an
  attribute. This was fine in the old editor.
- This changes the approach to use '<br>' tags within elements
  for "spaced emptiness".
- For compatbility with any existing empty paragraphs, I updated the
  styles to show default height for empty paragraph sections.
- This also makes changes to help preserve encoded &nbsp; html tags
  since they were getting converted along the journey.

Related to #3302
2022-03-01 17:26:06 +00:00
Dan Brown
1843d80fb7 Added cache breaker to tinymce loading systems
Takes the version from BookStack app.js paths instead of tinyMCE version
since things external from TinyMCE could be loaded using this.
2022-03-01 13:41:53 +00:00
Dan Brown
6252b46395 Added a custom link context toolbar
- Allows for easy unlinking, link preview or link editing.
- Created custom one to limit actions available.
- Performed refactoring of non-plugin toolbar editor code to extact into
  its own file.

Related to #3276
2022-02-28 13:56:23 +00:00
Dan Brown
20ecaa5c5a Added ctrl+shift+k shortcut to WYSIWYG
Shows entity select dialog for more direct entity link insertion.
Aligns with shortcut from markdown editor.

For #3244
2022-02-28 13:34:32 +00:00
Dan Brown
08b2a77d41 Updated version and assets for release v22.02.1 2022-02-27 17:46:06 +00:00
Dan Brown
3e8e9a23cf Merge branch 'development' into release 2022-02-27 17:45:49 +00:00
Dan Brown
1253711c7d New translations editor.php (Chinese Simplified) (#3291) 2022-02-27 17:44:58 +00:00
Dan Brown
963d8f4693 Updated issue templates, readme and dev version
- Updated bug report template to capture browser.
- Updated readme roadmap.
- Bumped dev version.
2022-02-27 17:26:27 +00:00
Dan Brown
0de4d6d223 Improved WYSIWYG code block behaviour via range of fixes
- Fixed issues with new code blocks breaking or acting odd due to
  misnamed contenteditable attribute.
- Helped fix issue where code blocks may show in a strage blank state
  due to timing within shadow dom loading.
- Fixed some function timing issues where some functions required their
  async predecessor to have finished.

Tested rather heavily in firefox and brave.
Fixes #3292
2022-02-27 17:21:24 +00:00
Dan Brown
06f694bad2 Updated tinymce link query to break caches
Fixes #3293
2022-02-27 16:03:18 +00:00
Dan Brown
58b83b64c8 Updated version and assets for release v22.02 2022-02-26 12:01:44 +00:00
Dan Brown
dfe4cde6ee Merge branch 'development' into release 2022-02-26 12:00:46 +00:00
Dan Brown
41689a1e65 New Crowdin updates (#3259) 2022-02-26 11:46:33 +00:00
Dan Brown
2ae8026903 Updated translators for v22.02 release 2022-02-26 11:40:09 +00:00
Dan Brown
dcb36b27a0 Updated github issue templates
- Removed titles since they don't provide added benefit upon the labels
  and would often lead to being submitted with just the placeholder
  title.
- Feature request form
  - Added further context to benefits field for hopefully better
    responses that target the core goal.
  - Added a field to ask if feature can already be achieved, to
    gain an idea if the submitter has explored other options (if
    existing).
  - Added a field to ensure the submitter has search the issue list
    before submitting.
  - Added a field to ask existing BookStack usage time to understand
    potential evolution of usage and/or influence of other platforms.
2022-02-24 18:26:34 +00:00
Dan Brown
83082c32ef Applied latest StyleCI changes 2022-02-24 15:04:09 +00:00
Dan Brown
1e112f78d8 On WYSIWYG details unwrap, provided better restore of cursor
Also prevents the toolbar from sticking around after the details block
was removed.
2022-02-24 15:02:23 +00:00
Dan Brown
9283f28e31 Updated JS deps 2022-02-24 15:02:06 +00:00
Dan Brown
7f5fc9fbe3 Updated composer dependancies
Includes update to dompdf v1.2 which helps address image sizing in
tables and hence fix #3190
2022-02-24 14:30:55 +00:00
Dan Brown
ce566bea2a Updated OIDC error handling for better error reporting
Fixes issue where certain errors would not show to the user
due to extra navigation jumps which lost the error message
in the process.
This simplifies and aligns exceptions with more directly
handled exception usage at the controller level.

Fixes #3264
2022-02-24 14:16:09 +00:00
Dan Brown
63ce3c9add Updated incorrect feature request template description 2022-02-13 13:18:42 +00:00
Dan Brown
f0470afb4c Applied StyleCI changes, updated readme badges & roadmap 2022-02-13 13:16:43 +00:00
Dan Brown
f8e6172582 Updated github actions to ignore language branch
Old branch filters did not seem to work since they are supposed to
reference the target branch, not source branch.
Instead used if statement to prevent run on crowdin branch.
2022-02-13 13:03:41 +00:00
Dan Brown
7a8505f812 Made a pass to clean up UserRepo 2022-02-13 12:56:26 +00:00
Dan Brown
9806907d53 Merge pull request #3260 from BookStackApp/wysiwyg_details
WYSIWYG details/summary blocks
2022-02-09 19:33:53 +00:00
Dan Brown
2b3726702d Revamped workings of WYSIWYG code blocks
Code blocks in tinymce could sometimes end up exploded into the sub
elements of the codemirror display.
This changes the strategy to render codemirror within the shadow dom of
a custom element while preserving the normal pre/code DOM structure.

Still a little instability when moving/adding code blocks within details
blocks but much harder to break things now.
2022-02-09 19:24:27 +00:00
Dan Brown
2b46b00f29 Updated PDF export to open detail blocks 2022-02-09 11:33:23 +00:00
Dan Brown
536ad14276 WYSIWYG details: Improved usage reliability and dark mdoe styles 2022-02-09 11:25:22 +00:00
Dan Brown
a318775cfc Improved wysiwyg details/summary edit controls
- Added specific non-editable/editable filtering to make editing within
  box more reliable.
- Updated toolbar icons and controls.
2022-02-09 10:40:46 +00:00
Dan Brown
9e0b8a9fb6 Started support for WYSIWYG details/summary blocks 2022-02-08 23:08:00 +00:00
Dan Brown
7c692ec588 Changed editor bottom padding technique
- Ensures padding works across FF & Chrome, was only working on FF
  before.
- Fixes sketchy editor positioning focus on FF, since tinyMCE would
  add a hidden element to the bottom of the body which would remove/add
  our body padding causing unstable positioning.
2022-02-08 17:05:38 +00:00
Dan Brown
da0dc7292c Merged in editor translation strings 2022-02-08 15:57:19 +00:00
Dan Brown
045710ea08 Updated with latest styleci changes 2022-02-08 15:29:58 +00:00
Dan Brown
c6ad16dba6 Merge branch 'tinymce' into development 2022-02-08 15:28:56 +00:00
Dan Brown
4ea1f0c633 Merge crowdin changes from users API changes 2022-02-08 15:14:18 +00:00
Dan Brown
f5077c17f4 Merge pull request #3238 from BookStackApp/users_api
User Management API
2022-02-08 13:32:45 +00:00
Dan Brown
c73773930e Merge pull request #3245 from BookStackApp/php7.4
Updated minimum php version from 7.3 to 7.4
2022-02-08 13:31:15 +00:00
Dan Brown
1782618c64 New Crowdin updates (#3251)
* New translations activities.php (Hebrew)

* New translations auth.php (Hebrew)

* New translations common.php (Hebrew)

* New translations activities.php (Hebrew)

* New translations common.php (Hebrew)

* New translations entities.php (Hebrew)

* New translations errors.php (Hebrew)

* New translations validation.php (Spanish)
2022-02-08 13:29:16 +00:00
ististyle
a01bb92989 Update Korean translation (#3256)
* Update validation.php

* Update activities.php

* Update passwords.php

* Update common.php

* Update common.php

* Update auth.php

* Update components.php

* Add files via upload

* Update errors.php

* Update entities.php

* Update entities.php

* Update entities.php

* Update auth.php

* Update activities.php

* Update components.php

* Update components.php

* Update entities.php

* Update components.php

* Update entities.php

* Update errors.php

* Update settings.php

* Update settings.php

* Add files via upload

* Update errors.php
2022-02-08 13:29:01 +00:00
Dan Brown
a2bcf765a8 Split out codemirror JS to its own module
Added a cache-compatible module loading system/pattern to the codebase.
2022-02-08 11:10:01 +00:00
Dan Brown
130dc05517 Updated wysiwyg with dark mode patches
- To better fit in with default BookStack dark theme.
2022-02-08 10:09:17 +00:00
Dan Brown
572d8b3700 Removed unused scroll patch after testing
- Tested on android and ios
- Also checked on translations and removed todo.
2022-02-08 09:42:18 +00:00
Dan Brown
e0d9380055 Aligned some editor events, Changed wysiwyg custom styles loading
- Removed old 'editor-*-update' commands to instead use the aligned
  'editor::replace' command that we already have.
- Changed the way custom styles are loaded for the WYSIWYG editor so we
  don't need an API call but instead scape content from the parent page
  header using comments as identifiers. Added tests to ensure comments
  exist and align.
2022-02-08 01:01:37 +00:00
Dan Brown
15647a0409 Merged color and formats wysiwyg groups 2022-02-08 00:20:36 +00:00
Dan Brown
e88dbe4db3 Added license references to readme attribution 2022-02-08 00:18:29 +00:00
Dan Brown
84c501bcf4 Simplified wysiwyg toolbar with a overflow groups 2022-02-07 23:56:39 +00:00
Dan Brown
c8b6f622f4 Added help/about box to wysiwyg editor
- To display license info along with shortcuts.
- Extracted out plain layout from 503 error page.
- Added tests to ensure license references are as expected.
2022-02-07 23:19:04 +00:00
Dan Brown
ef211a76ae Made WYSIWYG editor translatable
- Created new translation file for editor view.
- Added simple logic to format for tinymce.
- Aligned some of the custom labels we were using.
2022-02-06 21:17:08 +00:00
Dan Brown
d11144d9e2 Updated version and assets for release v21.12.5 2022-02-06 15:49:23 +00:00
Dan Brown
f96b0ea5f3 Merge branch 'development' into release 2022-02-06 15:48:55 +00:00
Dan Brown
b4e29d2b7d New Crowdin updates (#3225) 2022-02-06 15:46:28 +00:00
Dan Brown
2732d8961f Merge branch 'fix-code-block-linefeed' into development 2022-02-06 15:19:52 +00:00
Dan Brown
b2f863e1f1 WYSIWG: Improved handling of cross-block code block creation
- Updated code content to get specific text selection instead of using
  node-based handling which could return the whole document when
  multiple top-level nodes were in selection.
- Simplified how code gets applied into the page to not be node based
  but use native editor methods to replace the selection. Allows
  creation from half-way through a block.

Tested on chrome+Firefox on Fedora 35.
Builds upon changes in #3246.
For #3200.
2022-02-06 15:19:18 +00:00
Dan Brown
1df7497c09 Added missing validation.file message
- Included test to cover
- Also applied StyleCI fixes

Closes #3248
2022-02-06 14:48:33 +00:00
Dan Brown
d29a2a647a Prevented PCRE limit issues in markdown base64 extraction
For #3249
2022-02-06 07:51:38 +00:00
Dan Brown
43f32f6d5a Added attachment API file size limit test
Created while testing for #3248, Was not something that's currently
failing within BookStack but will still add for coverage.
2022-02-06 05:05:17 +00:00
Dan Brown
921131f999 Modularised our tinymce config and plugins
- Split everything into specific plugin/concern files to make things
  more managable. Means original component file is now simple and much
  of the core config is focused in one place.
2022-02-05 23:15:58 +00:00
Dan Brown
0cde2704d0 Made further tweaks to align with current editor
- Ensured each of the core actions worked at a high level.
- Handled some TinyMCE API changes.
- Moved code block insert to its own button.
2022-02-05 21:20:20 +00:00
Dan Brown
db4093d523 Got TinyMCE 5 added in barely working state
- Some extensions & custom actions not working.
- Updated anything visual to not be breaking (Icons) and anything
  functional that prevented loading.
2022-02-05 16:57:42 +00:00
julesdevops
049d6ba5b2 fix(wysiwyg): preserves line feeds in code block mode 2022-02-05 10:28:44 +01:00
Dan Brown
e33b587b87 Updated minimum php version from 7.3 to 7.4
Closes #3152
2022-02-04 13:27:11 +00:00
Dan Brown
c8be6ee8a6 Addressed test failures from users API changes 2022-02-04 01:02:13 +00:00
Dan Brown
46e6e239dc Added user API examples 2022-02-04 00:44:56 +00:00
Dan Brown
eb653bda16 Added user-create API endpoint
- Required extracting logic into repo.
- Changed some existing creation paths to standardise behaviour.
- Added test to cover new endpoint.
- Added extra test for user delete to test migration.
- Changed how permission errors are thrown to ensure the right status
  code can be reported when handled in API.
2022-02-04 00:26:19 +00:00
Dan Brown
9e1c8ec82a Added user-update API endpoint
- Required changing the docs generator to handle more complex
  object-style rules. Bit of a hack for some types (password).
- Extracted core update logic to repo for sharing with API.
- Moved user update language string to align with activity/logging
  system.
- Added tests to cover.
2022-02-03 16:52:28 +00:00
Dan Brown
2cd7a48044 Added users-delete API endpoint
- Refactored some delete checks into repo.
- Added tests to cover.
- Moved some translations to align with activity/logging system.
2022-02-03 15:12:50 +00:00
Dan Brown
d089623aac Refactored existing user API work
- Updated routes to use new format.
- Changed how hidden fields are exposed to be more flexible to different
  use-cases.
- Updated properties available on read/list results.
- Started adding testing coverage.
- Removed old unused UserRepo 'getAllUsers' function.

Related to #2701, Progression of #2734
2022-02-03 12:33:26 +00:00
Dan Brown
8d7febe482 Merge branch 'api-endpoint-users' into users_api 2022-02-03 11:38:55 +00:00
Dan Brown
815f8d79ed Updated version and assets for release v21.12.4 2022-02-01 11:52:24 +00:00
Dan Brown
b62dab32e0 Merge branch 'development' into release 2022-02-01 11:51:48 +00:00
Dan Brown
9d15688a43 Applied latest styleci changes 2022-02-01 11:49:30 +00:00
Dan Brown
033b163675 New Crowdin updates (#3214)
* New translations auth.php (Spanish)

* New translations auth.php (Estonian)

* New translations entities.php (Estonian)

* New translations common.php (French)

* New translations common.php (Indonesian)

* New translations common.php (Turkish)

* New translations common.php (Ukrainian)

* New translations common.php (Chinese Simplified)

* New translations common.php (Chinese Traditional)

* New translations common.php (Vietnamese)

* New translations common.php (Portuguese, Brazilian)

* New translations common.php (Persian)

* New translations common.php (Slovenian)

* New translations common.php (Spanish, Argentina)

* New translations common.php (Croatian)

* New translations common.php (Estonian)

* New translations common.php (Latvian)

* New translations common.php (Bosnian)

* New translations common.php (Norwegian Bokmal)

* New translations common.php (Swedish)

* New translations common.php (Slovak)

* New translations common.php (Spanish)

* New translations common.php (Hebrew)

* New translations common.php (Arabic)

* New translations common.php (Bulgarian)

* New translations common.php (Catalan)

* New translations common.php (Czech)

* New translations common.php (Danish)

* New translations common.php (German)

* New translations common.php (Hungarian)

* New translations common.php (Russian)

* New translations common.php (Italian)

* New translations common.php (Japanese)

* New translations common.php (Korean)

* New translations common.php (Lithuanian)

* New translations common.php (Dutch)

* New translations common.php (Polish)

* New translations common.php (Portuguese)

* New translations common.php (German Informal)

* New translations common.php (Spanish)

* New translations common.php (Italian)

* New translations settings.php (Italian)

* New translations common.php (Spanish, Argentina)
2022-02-01 11:48:29 +00:00
Dan Brown
6eadf3efb3 Added language select to the user create form
- Updated user invite to take language from user.
- Added tests to cover.
- Added page/tab title to user create view.

For #2576 and #2408
2022-01-31 22:15:21 +00:00
Dan Brown
f83cc83877 Added external-auth-id option to create-admin command
- Added tests to cover.
- Refactored some existing testing.
- Requires password or external_auth_id to be provided. Defaults to
  password.
- Randomly sets password to 32 digit random chars if external_auth_id
  provided instead.

For #3222
2022-01-31 20:43:41 +00:00
Dan Brown
17215431ca Fixed default registration role display options
- This also allows an admin to choose not to have a default role.
- Also applied latest styleCI fixes.

For #3220
2022-01-31 14:16:56 +00:00
Dan Brown
90c543064b Merge branch 'development' of github.com:BookStackApp/BookStack into development 2022-01-30 17:41:16 +00:00
Dan Brown
a709fd04b5 Added option to configure PDF export paper size
For #995
2022-01-30 17:40:42 +00:00
StyleCI Bot
4a1d060eb9 Apply fixes from StyleCI 2022-01-30 16:44:51 +00:00
Dan Brown
e17cdab420 Updated default branch name references 2022-01-30 16:33:03 +00:00
Dan Brown
2d074caf72 Merge pull request #3210 from Julesdevops/simple-503-error-file
Massively simplify the 503 error view
2022-01-30 16:24:24 +00:00
julesdevops
99202b3bb8 fix(503): massively simplify the 503 error view
This view was relying on too much app logic, which could lead to errors
when rendering it.
2022-01-29 10:56:13 +01:00
Dan Brown
73eac83afe Fixed OIDC JWT key parsing in microsoft environments
Made existence of 'alg' optional when JWK array set so we instead infer
it as RSA256 if not existing.

Fixes #3206
2022-01-28 14:00:55 +00:00
Dan Brown
c11f795c1d Added cloudabove sponsor logo 2022-01-26 20:45:14 +00:00
Dan Brown
262f863981 Updated version and assets for release v21.12.3 2022-01-24 22:49:42 +00:00
Dan Brown
a4c94390a1 Merge branch 'master' into release 2022-01-24 22:49:31 +00:00
Dan Brown
7e6e1fca76 Fixed test broken by PdfGenerator changes 2022-01-24 22:24:41 +00:00
Dan Brown
aaa2205df1 Refreshed markdown cm instance layout on size change
Intended to fix positioning quirks caused by changing codemirror
instance size when you have lines that wrap and cause line height
changes. Often caused by editor toolbox expand/collapse.

This adds a debounced resize observer to refresh editor layout on size
change.
Also tweaks toolbox expand/collapse to more consistently set aria
attribute.

For #3186
2022-01-24 22:08:36 +00:00
Dan Brown
4aed3f8558 Patched gallery duplication on multi-image upload
Quick patch to clear the gallery display when getting the first page.
Duplication of the galler was occuring due to the mulitple upload events
loading the gallery mulitple times while only clearing the existing
gallery at the start of all refreshes.

A bit flashy in terms of user experience, as there will still be
mulitple load/clear events but fixes the duplication. Could be done more
elegently in future by communicating up image upload counts.

For #3160
2022-01-24 21:38:11 +00:00
Dan Brown
7b4086107c Added parent context to recently updated items
- Includes tests to cover
For #3183
2022-01-24 21:21:30 +00:00
Dan Brown
585bd0cc45 Updated translator attribution and StyleCI changes 2022-01-24 20:55:03 +00:00
Dan Brown
f18e2784be New Crowdin updates (#3158)
* New translations activities.php (Slovak)

* New translations activities.php (Slovak)

* New translations common.php (Russian)

* New translations settings.php (Russian)

* New translations common.php (Japanese)

* New translations settings.php (Japanese)

* New translations activities.php (Persian)

* New translations auth.php (Persian)

* New translations auth.php (Persian)

* New translations entities.php (Persian)

* New translations common.php (Persian)

* New translations auth.php (Persian)

* New translations entities.php (Persian)

* New translations settings.php (Persian)

* New translations entities.php (Persian)

* New translations validation.php (Persian)

* New translations settings.php (Persian)

* New translations settings.php (Spanish, Argentina)

* New translations common.php (Spanish, Argentina)

* New translations settings.php (Spanish, Argentina)

* New translations activities.php (Spanish, Argentina)

* New translations entities.php (Spanish, Argentina)

* New translations entities.php (Persian)

* New translations settings.php (Persian)

* New translations entities.php (Persian)

* New translations errors.php (Persian)

* New translations settings.php (French)

* New translations common.php (French)

* New translations settings.php (French)

* New translations entities.php (Persian)

* New translations settings.php (Persian)

* New translations entities.php (Persian)

* New translations errors.php (Persian)
2022-01-24 20:53:36 +00:00
Dan Brown
f88e6d1520 Updated HTMLDiff package to address multibtye issue
Addresses potential issue when using multibyte characters.
Couple of other packages seemed to have updates also since earlier.

For #3170
2022-01-24 20:27:14 +00:00
Dan Brown
872961ef7c Updated npm and php dependancies 2022-01-24 18:53:28 +00:00
Dan Brown
bbd8d63652 Merge pull request #3179 from Julesdevops/atomic-user-creation
When creating a user, do not persist the user on invitation sending failure
2022-01-24 18:48:00 +00:00
Dan Brown
af39ff15ac Merge branch 'show_more_informations_on_recently_updated_pages' 2022-01-24 18:23:47 +00:00
Dan Brown
aae3cd69d7 Added test to cover PR #3177 2022-01-24 18:23:16 +00:00
Dan Brown
2d3df955ae Merge branch 'master' of github.com:BookStackApp/BookStack 2022-01-24 17:26:17 +00:00
Dan Brown
8b5747eae2 Further adjusted linked image sizes on PDF export
Further fixes for #3120, Adds DOMPDF specific adjustments to prevent
full width linked images being cut-off as per last tweak.
This does not fix usage in smaller cases (tables) but tested on
master DOMPDF branch shows that will likely be fixed in next DOMPDF
upstream release.
DOMPDF fixes would break WKHTMLTOPDF presentation so system updated
to conditionally apply styles.
2022-01-24 17:24:00 +00:00
Dan Brown
6c699f7fab Merge pull request #3193 from Julesdevops/xdebug-docker-compose-setup
chore(dev): add xdebug support for docker setup
2022-01-24 16:11:29 +00:00
julesdevops
ac6eceb0e5 doc(dev): add xdebug informations 2022-01-23 14:26:01 +01:00
julesdevops
a2a2f3a4dd chore(dev): add xdebug support for docker setup 2022-01-22 17:43:29 +01:00
julesdevops
6db64763fe enh(recently updated): show updatedBy and updated_at 2022-01-19 21:49:45 +01:00
julesdevops
c9beacbfbf fix(User Creation): do not persist the user if invitation fails
- Wrap the user creation process in a transaction
- Add test
2022-01-19 20:46:38 +01:00
Dan Brown
53f3cca85d Updated version and assets for release v21.12.2 2022-01-10 18:23:44 +00:00
Dan Brown
ed08bbcecc Merge branch 'master' into release 2022-01-10 18:23:19 +00:00
Dan Brown
2aace16704 Updated translator attribution before release v21.12.2 2022-01-10 18:22:43 +00:00
Dan Brown
ade66dcf2f Applied latest styleci changes 2022-01-10 18:18:15 +00:00
Dan Brown
d3eaaf6457 New Crowdin updates (#3148)
* New translations common.php (Czech)

* New translations common.php (Chinese Simplified)

* New translations settings.php (Chinese Simplified)

* New translations errors.php (Japanese)

* New translations entities.php (Japanese)

* New translations common.php (Japanese)

* New translations settings.php (Japanese)

* New translations entities.php (Japanese)

* New translations settings.php (Japanese)

* New translations auth.php (Japanese)

* New translations common.php (Portuguese, Brazilian)

* New translations activities.php (Portuguese, Brazilian)

* New translations activities.php (Portuguese, Brazilian)
2022-01-10 18:17:28 +00:00
Dan Brown
941217d9fb Improved loading for images with failed thumbnails
- A placeholder is now shown in the gallery.
- The page editors will use the original image url if the display
  thumbnail is missing.

For #3142
2022-01-10 18:13:48 +00:00
Dan Brown
4239d4c54d Fixed error on webhooks for recycle bin operations
Updated the getUrl method on deletions to not require any passed
params to align with usage in webhooks.
Probably better to have a proper interface but would require a wider
change.

Fixes #3154
2022-01-10 17:47:49 +00:00
Dan Brown
8d91f4369b Improved custom homepage check on item deletion
Custom homepage usage will now be checked before any actioning
of deletion rather than potentially causing an exception acting
during the deletion.

Previously a deletion could still be created, within the recycle bin,
for the parent which may lead to the page being deleted anyway.

For #3150
2022-01-10 17:04:01 +00:00
Dan Brown
722aa04577 Merge pull request #3153 from AitorMatxi/patch-1
Update auth.php
2022-01-10 16:10:39 +00:00
Aitor Matxinea
2d0abc4164 Update auth.php
Fix misspelled word "As" to "Has".
2022-01-10 11:45:48 +01:00
Dan Brown
c3f7b39a0f Addressed phpstan cases 2022-01-07 13:04:49 +00:00
Dan Brown
de97ebf9b7 Updated version and assets for release v21.12.1 2022-01-06 12:20:37 +00:00
Dan Brown
f492a660a8 Merge branch 'master' into release 2022-01-06 12:20:26 +00:00
Dan Brown
ef11100863 Updated translator attribution before release v21.12.1 2022-01-06 12:20:13 +00:00
Dan Brown
1a26b47782 Applied latest styleCI changes 2022-01-06 12:18:11 +00:00
Dan Brown
cb0d674a71 Merge branch 'sort_changes'
Related to #3134
2022-01-06 12:03:15 +00:00
Dan Brown
4d094331cf New Crowdin updates (#3117)
* New translations auth.php (Bulgarian)

* New translations auth.php (Catalan)

* New translations auth.php (Czech)

* New translations auth.php (Danish)

* New translations auth.php (Hebrew)

* New translations auth.php (Swedish)

* New translations auth.php (Hungarian)

* New translations auth.php (Italian)

* New translations auth.php (Japanese)

* New translations auth.php (Korean)

* New translations auth.php (Lithuanian)

* New translations auth.php (Dutch)

* New translations auth.php (Polish)

* New translations auth.php (Russian)

* New translations auth.php (Slovak)

* New translations auth.php (Slovenian)

* New translations settings.php (Korean)

* New translations settings.php (Lithuanian)

* New translations settings.php (Portuguese, Brazilian)

* New translations settings.php (Norwegian Bokmal)

* New translations settings.php (Bosnian)

* New translations settings.php (Latvian)

* New translations settings.php (Estonian)

* New translations settings.php (Croatian)

* New translations settings.php (Spanish, Argentina)

* New translations settings.php (Persian)

* New translations settings.php (Indonesian)

* New translations settings.php (Vietnamese)

* New translations settings.php (Dutch)

* New translations settings.php (Chinese Traditional)

* New translations settings.php (Chinese Simplified)

* New translations settings.php (Turkish)

* New translations settings.php (Swedish)

* New translations settings.php (Slovenian)

* New translations settings.php (Slovak)

* New translations settings.php (Russian)

* New translations settings.php (Portuguese)

* New translations settings.php (Polish)

* New translations settings.php (German Informal)

* New translations settings.php (Spanish)

* New translations activities.php (Spanish)

* New translations auth.php (Spanish)

* New translations common.php (Spanish)

* New translations settings.php (Spanish)

* New translations auth.php (German)

* New translations passwords.php (German)

* New translations settings.php (German)

* New translations activities.php (German)

* New translations auth.php (German)

* New translations auth.php (German Informal)

* New translations common.php (German)

* New translations entities.php (German)

* New translations errors.php (German)

* New translations errors.php (German Informal)

* New translations settings.php (German)

* New translations settings.php (German Informal)

* New translations entities.php (Japanese)

* New translations entities.php (Vietnamese)

* New translations entities.php (Slovak)

* New translations entities.php (Slovenian)

* New translations entities.php (Swedish)

* New translations entities.php (Turkish)

* New translations entities.php (Chinese Simplified)

* New translations entities.php (Chinese Traditional)

* New translations entities.php (Portuguese, Brazilian)

* New translations entities.php (Polish)

* New translations entities.php (Indonesian)

* New translations entities.php (Persian)

* New translations entities.php (Croatian)

* New translations entities.php (Estonian)

* New translations entities.php (Latvian)

* New translations entities.php (Bosnian)

* New translations entities.php (Russian)

* New translations entities.php (Dutch)

* New translations entities.php (Portuguese)

* New translations entities.php (Bulgarian)

* New translations entities.php (Ukrainian)

* New translations entities.php (Spanish, Argentina)

* New translations entities.php (Norwegian Bokmal)

* New translations entities.php (French)

* New translations entities.php (Spanish)

* New translations entities.php (Arabic)

* New translations entities.php (Catalan)

* New translations entities.php (Lithuanian)

* New translations entities.php (Czech)

* New translations entities.php (Danish)

* New translations entities.php (German)

* New translations entities.php (Hebrew)

* New translations entities.php (Hungarian)

* New translations entities.php (Italian)

* New translations entities.php (Korean)

* New translations entities.php (German Informal)

* New translations entities.php (Spanish)

* New translations auth.php (Portuguese)

* New translations common.php (Portuguese)

* New translations errors.php (Portuguese)

* New translations settings.php (Portuguese)

* New translations activities.php (French)

* New translations activities.php (French)

* New translations auth.php (French)

* New translations common.php (French)

* New translations entities.php (French)

* New translations settings.php (French)

* New translations activities.php (Spanish, Argentina)

* New translations entities.php (Spanish, Argentina)

* New translations auth.php (Spanish, Argentina)

* New translations common.php (Spanish, Argentina)

* New translations activities.php (German Informal)

* New translations common.php (German Informal)

* New translations settings.php (Spanish, Argentina)

* New translations activities.php (Chinese Simplified)

* New translations activities.php (Chinese Simplified)

* New translations auth.php (Chinese Simplified)

* New translations common.php (Chinese Simplified)

* New translations entities.php (Chinese Simplified)

* New translations settings.php (Chinese Simplified)

* New translations activities.php (Estonian)

* New translations auth.php (Estonian)

* New translations common.php (Estonian)

* New translations entities.php (Chinese Simplified)

* New translations entities.php (Estonian)

* New translations settings.php (Chinese Simplified)

* New translations settings.php (Estonian)

* New translations settings.php (Estonian)

* New translations validation.php (Estonian)

* New translations auth.php (Italian)

* New translations common.php (Italian)

* New translations entities.php (Italian)

* New translations settings.php (Italian)

* New translations activities.php (Russian)

* New translations auth.php (Russian)

* New translations common.php (Russian)

* New translations activities.php (Russian)

* New translations entities.php (Russian)

* New translations settings.php (Russian)

* New translations activities.php (Japanese)

* New translations auth.php (Portuguese, Brazilian)

* New translations auth.php (Portuguese, Brazilian)

* New translations auth.php (Arabic)

* New translations activities.php (Czech)

* New translations auth.php (Czech)

* New translations activities.php (Czech)

* New translations auth.php (Czech)

* New translations common.php (Czech)

* New translations entities.php (Czech)

* New translations settings.php (Czech)

* New translations activities.php (Czech)

* New translations auth.php (Czech)

* New translations common.php (Czech)

* New translations entities.php (Czech)

* New translations settings.php (Czech)

* New translations auth.php (Czech)

* New translations entities.php (Czech)

* New translations settings.php (Czech)

* New translations auth.php (Czech)

* New translations auth.php (Czech)

* New translations activities.php (Latvian)

* New translations auth.php (Latvian)

* New translations common.php (Latvian)

* New translations entities.php (Latvian)

* New translations settings.php (Latvian)

* New translations activities.php (Latvian)

* New translations settings.php (Latvian)

* New translations activities.php (Italian)

* New translations entities.php (Italian)

* New translations activities.php (Italian)

* New translations settings.php (Italian)

* New translations common.php (Japanese)

* New translations settings.php (French)

* New translations common.php (Vietnamese)

* New translations common.php (Portuguese, Brazilian)

* New translations common.php (Indonesian)

* New translations common.php (Persian)

* New translations common.php (Croatian)

* New translations common.php (Estonian)

* New translations common.php (Latvian)

* New translations common.php (Bosnian)

* New translations common.php (German Informal)

* New translations settings.php (Spanish)

* New translations common.php (Chinese Simplified)

* New translations settings.php (Arabic)

* New translations settings.php (Bulgarian)

* New translations settings.php (Catalan)

* New translations settings.php (Czech)

* New translations settings.php (Danish)

* New translations settings.php (German)

* New translations settings.php (Hebrew)

* New translations settings.php (Hungarian)

* New translations settings.php (Italian)

* New translations settings.php (Japanese)

* New translations common.php (Chinese Traditional)

* New translations common.php (Turkish)

* New translations common.php (Portuguese)

* New translations common.php (Danish)

* New translations common.php (Ukrainian)

* New translations common.php (Spanish, Argentina)

* New translations common.php (Norwegian Bokmal)

* New translations settings.php (Ukrainian)

* New translations common.php (French)

* New translations common.php (Spanish)

* New translations common.php (Arabic)

* New translations common.php (Bulgarian)

* New translations common.php (Catalan)

* New translations common.php (Czech)

* New translations common.php (German)

* New translations common.php (Swedish)

* New translations common.php (Hebrew)

* New translations common.php (Hungarian)

* New translations common.php (Italian)

* New translations common.php (Korean)

* New translations common.php (Lithuanian)

* New translations common.php (Dutch)

* New translations common.php (Polish)

* New translations common.php (Russian)

* New translations common.php (Slovak)

* New translations common.php (Slovenian)

* New translations settings.php (Korean)

* New translations settings.php (Lithuanian)

* New translations settings.php (Portuguese, Brazilian)

* New translations settings.php (Norwegian Bokmal)

* New translations settings.php (Bosnian)

* New translations settings.php (Latvian)

* New translations settings.php (Estonian)

* New translations settings.php (Croatian)

* New translations settings.php (Spanish, Argentina)

* New translations settings.php (Persian)

* New translations settings.php (Indonesian)

* New translations settings.php (Vietnamese)

* New translations settings.php (Dutch)

* New translations settings.php (Chinese Traditional)

* New translations settings.php (Chinese Simplified)

* New translations settings.php (Turkish)

* New translations settings.php (Swedish)

* New translations settings.php (Slovenian)

* New translations settings.php (Slovak)

* New translations settings.php (Russian)

* New translations settings.php (Portuguese)

* New translations settings.php (Polish)

* New translations settings.php (German Informal)

* New translations common.php (Estonian)

* New translations entities.php (Estonian)

* New translations settings.php (Estonian)

* New translations common.php (Spanish)

* New translations settings.php (Spanish)

* New translations entities.php (French)

* New translations settings.php (French)

* New translations common.php (Chinese Simplified)

* New translations settings.php (Chinese Simplified)

* New translations common.php (Spanish, Argentina)

* New translations settings.php (Spanish, Argentina)
2022-01-06 12:02:49 +00:00
Dan Brown
2312d07bb5 Removed old book sort permission test
Permission handling now done via other means with more extensive
permissions testing in SortTest class.
2022-01-05 16:46:03 +00:00
Dan Brown
fbd388ba4c Aligned chapter move permissions with page move permissions 2022-01-05 16:18:19 +00:00
Dan Brown
d3ca23b195 Added additional permission checks and tests for book sorts
- Aligned permissions control with move operations to check
  delete/create permissions against old/new locations.
- Added tests to cover additional permissions scenarios.
2022-01-05 15:42:59 +00:00
Dan Brown
553954ad18 Altered sort permission checking and started tests
Previous implemenations were hard to read so changing to be more
logically simplistic. Still needs further coverage in tests and
review/alignment of permissions to use.
2022-01-05 14:39:21 +00:00
Dan Brown
d8c45f5746 Changed model loading and permission checking on book sort
Models are now loaded into their own map to then be used for sorting and
reporting back of changed books. Prevents akward logic ordering issues
of before where some bits of code assumed/hoped for loaded models on
abstract data structures.

New levels of permissions are now checked for items within the
sort operation. Needs testing to cover.
2022-01-04 21:09:34 +00:00
Dan Brown
edc7c12edf Refactored sort system a little
To standardise the handled data format a little better.
2022-01-04 17:31:57 +00:00
Dan Brown
a72bd75e3a Added page titles to many missing app areas
Many pages were missing their unique tab/page titles
so this change is just to distribute them back over
many common areas where they were missing.
2022-01-04 13:33:24 +00:00
Dan Brown
31f1dca8a8 Added detection and thumbnail bypass for apng images
Adds apng sniffing when generating thumbnails with retained ratios to
serve the original image files, as we do for GIF images, to prevent
the image being resized to a static version.

Is more tricky than GIF since apng file mimes and extensions
are the same as png, we have to detect part of the file header
to sniff the type. Means we have to sniff at a later stage
than GIF since we have to load the image file data.

Made some changes to the image thubmnail caching while doing
this work to fit in with this handling.

Added test to cover.
For #3136.
2022-01-04 13:10:35 +00:00
Dan Brown
819ec55b1b Fixed code block language parsing issue
Language parsing of code blocks could falter on pasted code blocks due
to the lanuage being parsed with a space which would throw an error when
used as a css class.
This adds more extensive language parsing to be safer.

Fixes #3133
2022-01-04 11:54:24 +00:00
Dan Brown
dba506a20e Added search autofocus on entity-selector-popup
Closes #3127
2022-01-04 11:30:44 +00:00
Dan Brown
d0de4fd8f9 Fixed failing webhook test cases 2022-01-03 19:51:13 +00:00
Dan Brown
00eedafbfd Added timeout and debugging statuses to webhooks
- Added a user-configurable timeout option to webhooks.
- Added webhook fields for last-call/error datetime, in addition to last
  error string, which are shown on  webhook edit view.

Related to #3122
2022-01-03 19:42:48 +00:00
Dan Brown
6e18620a0a Added webhook call http exception handling
Will now catch and log errors on events such as http timeouts.
For #3122
2022-01-03 18:37:56 +00:00
Dan Brown
fe54c7f27a Added webhook_call_before theme event hook 2022-01-03 18:22:03 +00:00
Dan Brown
65830b428c Fixed linked images being micro on pdf export
Was caused by max-width: 100% causing confusion when images were
inside an anchor. This change resets that property on PDF
exports allowing full width images to be shown as so
without affecting smaller sizes.

Fixes #3120
2022-01-01 18:18:37 +00:00
Dan Brown
b438e0187c Updated webhooks list to not squash events/status
Closes #3135
2022-01-01 17:43:33 +00:00
Dan Brown
8614775c14 Updated sponsors in readme 2021-12-30 16:43:28 +00:00
Dan Brown
09436836a5 Updated version and assets for release v21.12 2021-12-22 17:04:18 +00:00
Dan Brown
bb455d7788 Merge branch 'master' into release 2021-12-22 17:03:50 +00:00
Dan Brown
b0666e5d70 Updated translator contribution before v21.12 release 2021-12-22 16:30:48 +00:00
Dan Brown
fc109f7e1c Applied latest StyleCI changes 2021-12-20 17:40:27 +00:00
Dan Brown
21f2a7087c Merge pull request #3118 from BookStackApp/copy_stuff
Additional copy/clone abilities
2021-12-20 17:39:44 +00:00
Dan Brown
ff70509fca Added copy considerations
Show to the user when copying stuff to highlight important things such
as what's not copied or change in permissions.
2021-12-20 17:33:19 +00:00
Dan Brown
0288320700 Added ability to clone books 2021-12-19 19:20:31 +00:00
Dan Brown
20e093a7a1 Added ability to copy/clone chapters
Builds upon page clone work. Takes permissions into account to decide
if child pages should be copied.
2021-12-19 15:40:52 +00:00
Dan Brown
3f9527f166 Extracted page copy to new cloner class
Fundemental refactor for planned additional clone operations.
No behaviour change intended in this commit.
2021-12-19 12:56:27 +00:00
Dan Brown
da01913616 Added ability to copy a role
- Copies via loading in model on create view.
- Updated role views while editing to bring up to similar format as
  that used for more modern app areas.
- Added tests to cover.

Related to #1123
2021-12-19 12:27:14 +00:00
Dan Brown
67b6c07548 Updated failing tests, Applied StyleCI changes 2021-12-18 16:41:42 +00:00
Dan Brown
bb9cd9d610 Aligned password length requirements
Updated all password validation to use central password defaults
system while updating length requirements to now all match
at 8 characters minimum.

Some language text was technically correct (More than 7 characters)
but this has been updated for clarity and to prompt other translations
to be updated.

Closes #2237
2021-12-18 16:33:40 +00:00
Dan Brown
04f37e21e2 Applied latest StyleCI changes 2021-12-18 11:43:05 +00:00
Dan Brown
a3ead5062a Merge branch 'webhooks' 2021-12-18 11:40:08 +00:00
Dan Brown
24e29c523b Aligned notification capitalisation 2021-12-18 11:24:58 +00:00
Dan Brown
04d59763c3 Updated auditlog IP search test
To ensure the test covers filtering logic.
Related to #3081.
2021-12-18 11:05:41 +00:00
Dan Brown
5c04f25c86 Merge branch 'search-by-ip' of https://github.com/johnroyer/BookStack into johnroyer-search-by-ip 2021-12-18 10:58:07 +00:00
Dan Brown
767a82fb41 Reverted unrequired use of mb_ function 2021-12-18 10:43:43 +00:00
Dan Brown
5c5a3de7cb Merge branch 'fix/multibyte-safe-search' 2021-12-18 10:40:38 +00:00
Dan Brown
c6e3e85e82 Added test case for multibyte search highlighting
Related to #3113
2021-12-18 10:38:33 +00:00
Kristian Krastev
d0fd1b7f5c Make building of search results work for multi-byte encoded characters 2021-12-15 16:29:43 +02:00
Dan Brown
009212ab80 Updated version and assets for release v21.11.3 2021-12-15 14:08:37 +00:00
Dan Brown
ba9cb591c8 Merge branch 'master' into release 2021-12-15 14:08:17 +00:00
Dan Brown
632cb71af4 Updated translator attribution before release v21.11.3 2021-12-15 14:07:54 +00:00
Dan Brown
74ab99ec41 Updated php deps 2021-12-15 14:00:30 +00:00
Dan Brown
aa9dafec85 Altered mysql start command in workflows
Due to https://github.com/actions/virtual-environments/issues/4732
2021-12-15 13:56:21 +00:00
Dan Brown
73a37b3cd9 Applied latest StyleCI changes 2021-12-15 13:49:20 +00:00
Dan Brown
e43f679e62 Merge branch 'user_list_control' 2021-12-15 13:47:48 +00:00
Dan Brown
57fc1ba38f New Crowdin updates (#3093)
* New translations auth.php (Vietnamese)

* New translations entities.php (Norwegian Bokmal)

* New translations common.php (Norwegian Bokmal)

* New translations entities.php (Norwegian Bokmal)

* New translations auth.php (Spanish, Argentina)

* New translations entities.php (Spanish, Argentina)

* New translations errors.php (Spanish, Argentina)

* New translations auth.php (Ukrainian)

* New translations auth.php (Ukrainian)

* New translations common.php (Ukrainian)

* New translations entities.php (Ukrainian)

* New translations errors.php (Ukrainian)

* New translations settings.php (Ukrainian)

* New translations validation.php (Ukrainian)

* New translations entities.php (Japanese)

* New translations common.php (Japanese)

* New translations entities.php (Japanese)

* New translations auth.php (Portuguese)

* New translations auth.php (Portuguese)

* New translations common.php (Portuguese)

* New translations entities.php (Portuguese)
2021-12-15 13:46:49 +00:00
Dan Brown
e765e61854 Addressed user detail harvesting issue
Altered access & usage of the /search/users/select endpoint with the
following changes:
- Removed searching of email address to prevent email detail discovery
  via hunting via search queries.
- Required the user to be logged in and have permission to manage users
  or manage permissions on items in some way.
- Removed the user migration option on user delete unless they have
  permission to manage users.

For #3108
Reported in https://huntr.dev/bounties/135f2d7d-ab0b-4351-99b9-889efac46fca/
Reported by @haxatron
2021-12-14 18:47:22 +00:00
Dan Brown
d00ac3101d Allowed database queue usage where desired 2021-12-13 18:34:18 +00:00
Dan Brown
f27d0d5aeb Added testing to cover webhook calling
Migrated call logic to Laravel's HTTP client for easier testing
capabilities.
2021-12-12 19:01:50 +00:00
Dan Brown
8d8b45860a Updated REST API docs with links to webhooks & theme-systems 2021-12-12 18:14:22 +00:00
Dan Brown
3bf34b6a0d Added webhook format example to webhook management views 2021-12-12 18:02:08 +00:00
Dan Brown
dbd4281ae8 Added active toggle to webhooks
To allow easy temporary de-activation without deletion or other
workarounds. Updated tests to cover.
2021-12-12 17:39:06 +00:00
Dan Brown
917598f7c8 Added webhook call functionality 2021-12-11 22:29:33 +00:00
Dan Brown
9079700170 Refactored the activity service
- Renamed to "ActivityLogger" to be more focused in usage.
- Extracted out query elements to seperate "ActivityQueries" class.
- Removed old 'addForEntity' activity method to limit activity record
  points.
2021-12-11 17:29:33 +00:00
Dan Brown
f2cb3b94f9 Added missing migration down table drop 2021-12-10 14:58:14 +00:00
Dan Brown
6381041252 Added testing for webhook management interface 2021-12-10 14:54:58 +00:00
Zero
7d13666039 Add unit test for ip addess searching 2021-12-10 15:11:30 +08:00
Zero
e6e92618b1 Fix PHP CS 2021-12-10 14:58:05 +08:00
Zero
2342f0c1c7 Fix UI error of IP searching input box 2021-12-10 14:50:04 +08:00
Zero
ee1106630e Update translation setting in blade 2021-12-10 14:50:04 +08:00
Zero
93e80e5d4e Delete duplicated translation 2021-12-10 14:50:04 +08:00
Zero
72d19968dd Search IP by partial-equal 2021-12-10 14:50:04 +08:00
Zero
2fd7b1f0d5 Update index name to 'activities_ip_index' 2021-12-10 14:50:04 +08:00
Zero
a93254430c Add index for user IP address 2021-12-10 14:50:04 +08:00
Zero
e686b2cf3c Show current search IP 2021-12-10 14:50:04 +08:00
Zero
4e63554cc6 Add an hidden submit
This hidden submit makes auto submit while user press Enter on IP
input field.
2021-12-10 14:50:04 +08:00
Zero
882f195927 Add margin right for IP input box 2021-12-10 14:50:04 +08:00
Zero
a12e346439 Add filter of user IP 2021-12-10 14:50:04 +08:00
Zero
8dee3d3a83 Add label translation 2021-12-10 14:50:04 +08:00
Zero
0e25298db9 Fix label and input box error 2021-12-10 14:50:04 +08:00
Zero
9cac6fad73 Add IP address search field mock 2021-12-10 14:50:04 +08:00
Dan Brown
8716b1922b Completed webhook management interface
Got webhook CRUD actions in place within the interface.
Quick manual test pass done, Needs automated tests.
2021-12-08 17:35:58 +00:00
Dan Brown
4621d8bcc5 Initial controller/views for webhooks management 2021-12-08 14:29:42 +00:00
Dan Brown
a3a3055695 Started webhook implementation 2021-12-07 14:55:11 +00:00
Dan Brown
867cbe15ea Added link to OIDC docs in .env.example.complete 2021-12-07 13:45:43 +00:00
Dan Brown
b22dd3cb88 Added url and preview_html params to search API results
Allows easy direct linking and usage of the HTML preview content
we show in the UI when viewing search results.
Note: preview_html content is a rough representation only, it does not
match exactly what was matched in the database-search-operation which
finds the results.

For #3096 and #3080
2021-12-06 20:42:04 +00:00
Dan Brown
d00ac2f34e Updated version and assets for release v21.11.2 2021-11-30 14:30:19 +00:00
Dan Brown
bd4dc6d463 Merge branch 'master' into release 2021-11-30 14:29:53 +00:00
Dan Brown
e6c8ecba9c Merge branch 'master' of github.com:BookStackApp/BookStack 2021-11-30 14:25:27 +00:00
Dan Brown
9490457d04 Applied StyleCI changes 2021-11-30 14:25:09 +00:00
Dan Brown
3e97fdf827 New Crowdin updates (#3076)
* New translations entities.php (Chinese Simplified)

* New translations settings.php (Portuguese, Brazilian)

* New translations validation.php (Portuguese, Brazilian)

* New translations common.php (Chinese Simplified)

* New translations settings.php (Chinese Simplified)

* New translations auth.php (Turkish)
2021-11-30 14:24:35 +00:00
Dan Brown
3b3eb0f44f Updated API session auth to consider public access setting
For #3091
2021-11-30 13:55:56 +00:00
Dan Brown
b4fa82e329 Fixed related permissions query not considering drafts
Page-related items added on drafts could be visible in certain scenarios
since the applied permissions query filters would not consider
page draft visibility.
This commit alters queries on related items to apply such filtering.

Included test to cover API scenario.
Thanks to @haxatron for reporting.
2021-11-30 00:06:17 +00:00
Dan Brown
42703dd859 Tweaked pdf export iframe replacement to fix compatibility
Was using a method that wasn't a proper available part of the
DomElement API.
2021-11-28 21:01:35 +00:00
Dan Brown
2c21850da7 Added conversion of iframes to anchors on PDF export
- Replaced iframe elements with anchor elements wrapped in a paragraph.
- Extracted PDF generation action to seperate class for easier mocking
  within testing.
- Added test to cover.

For #3077
2021-11-25 15:12:32 +00:00
Dan Brown
709533c1fb Fixed up logical theme docs a tad
- Added link to video guide on YouTube.
- Formalised the customCommand docs parts I hastily added before.
2021-11-24 18:58:46 +00:00
Jascha Sticher
4cbd1a9eb5 Extend /users API endpoint
* add /users/{id} to get a single user
* add variable to print fields that are otherwise hidden (e.g. email)
2021-05-06 11:20:08 +02:00
Jascha Sticher
07626669da Test API Endpoint for users 2021-05-05 14:16:15 +02:00
1474 changed files with 61115 additions and 26996 deletions

View File

@@ -42,7 +42,7 @@ APP_TIMEZONE=UTC
# overrides can be made. Defaults to disabled.
APP_THEME=false
# Trusted Proxies
# Trusted proxies
# Used to indicate trust of systems that proxy to the application so
# certain header values (Such as "X-Forwarded-For") can be used from the
# incoming proxy request to provide origin detail.
@@ -58,6 +58,13 @@ DB_DATABASE=database_database
DB_USERNAME=database_username
DB_PASSWORD=database_user_password
# MySQL specific connection options
# Path to Certificate Authority (CA) certificate file for your MySQL instance.
# When this option is used host name identity verification will be performed
# which checks the hostname, used by the client, against names within the
# certificate itself (Common Name or Subject Alternative Name).
MYSQL_ATTR_SSL_CA="/path/to/ca.pem"
# Mail system to use
# Can be 'smtp' or 'sendmail'
MAIL_DRIVER=smtp
@@ -73,6 +80,9 @@ MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
# Command to use when email is sent via sendmail
MAIL_SENDMAIL_COMMAND="/usr/sbin/sendmail -bs"
# Cache & Session driver to use
# Can be 'file', 'database', 'memcached' or 'redis'
CACHE_DRIVER=file
@@ -100,8 +110,7 @@ MEMCACHED_SERVERS=127.0.0.1:11211:100
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.
# Can be 'sync', 'database' or 'redis'
QUEUE_CONNECTION=sync
# Storage system to use
@@ -134,9 +143,13 @@ STORAGE_S3_ENDPOINT=https://my-custom-s3-compatible.service.com:8001
STORAGE_URL=false
# Authentication method to use
# Can be 'standard', 'ldap' or 'saml2'
# Can be 'standard', 'ldap', 'saml2' or 'oidc'
AUTH_METHOD=standard
# Automatically initiate login via external auth system if it's the only auth method.
# Works with saml2 or oidc auth methods.
AUTH_AUTO_INITIATE=false
# Social authentication configuration
# All disabled by default.
# Refer to https://www.bookstackapp.com/docs/admin/third-party-auth/
@@ -217,6 +230,7 @@ LDAP_DUMP_USER_DETAILS=false
LDAP_USER_TO_GROUPS=false
LDAP_GROUP_ATTRIBUTE="memberOf"
LDAP_REMOVE_FROM_GROUPS=false
LDAP_DUMP_USER_GROUPS=false
# SAML authentication configuration
# Refer to https://www.bookstackapp.com/docs/admin/saml2-auth/
@@ -242,6 +256,7 @@ SAML2_GROUP_ATTRIBUTE=group
SAML2_REMOVE_FROM_GROUPS=false
# OpenID Connect authentication configuration
# Refer to https://www.bookstackapp.com/docs/admin/oidc-auth/
OIDC_NAME=SSO
OIDC_DISPLAY_NAME_CLAIMS=name
OIDC_CLIENT_ID=null
@@ -251,7 +266,12 @@ OIDC_ISSUER_DISCOVER=false
OIDC_PUBLIC_KEY=null
OIDC_AUTH_ENDPOINT=null
OIDC_TOKEN_ENDPOINT=null
OIDC_ADDITIONAL_SCOPES=null
OIDC_DUMP_USER_DETAILS=false
OIDC_USER_TO_GROUPS=false
OIDC_GROUPS_CLAIM=groups
OIDC_REMOVE_FROM_GROUPS=false
OIDC_EXTERNAL_ID_CLAIM=sub
# Disable default third-party services such as Gravatar and Draw.IO
# Service-specific options will override this option
@@ -266,7 +286,7 @@ AVATAR_URL=
# Enable diagrams.net integration
# Can simply be true/false to enable/disable the integration.
# Alternatively, It can be URL to the diagrams.net instance you want to use.
# For URLs, The following URL parameters should be included: embed=1&proto=json&spin=1
# For URLs, The following URL parameters should be included: embed=1&proto=json&spin=1&configure=1
DRAWIO=true
# Default item listing view
@@ -283,7 +303,7 @@ APP_DEFAULT_DARK_MODE=false
# 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
REVISION_LIMIT=100
# Recycle Bin Lifetime
# The number of days that content will remain in the recycle bin before
@@ -297,6 +317,11 @@ RECYCLE_BIN_LIFETIME=30
# Maximum file size, in megabytes, that can be uploaded to the system.
FILE_UPLOAD_SIZE_LIMIT=50
# Export Page Size
# Primarily used to determine page size of PDF exports.
# Can be 'a4' or 'letter'.
EXPORT_PAGE_SIZE=a4
# Allow <script> tags in page content
# Note, if set to 'true' the page editor may still escape scripts.
ALLOW_CONTENT_SCRIPTS=false
@@ -319,6 +344,13 @@ ALLOW_UNTRUSTED_SERVER_FETCHING=false
# Setting this option will also auto-adjust cookies to be SameSite=None.
ALLOWED_IFRAME_HOSTS=null
# A list of sources/hostnames that can be loaded within iframes within BookStack.
# Space separated if multiple. BookStack host domain is auto-inferred.
# Can be set to a lone "*" to allow all sources for iframe content (Not advised).
# Defaults to a set of common services.
# Current host and source for the "DRAWIO" setting will be auto-appended to the sources configured.
ALLOWED_IFRAME_SOURCES="https://*.draw.io https://*.youtube.com https://*.youtube-nocookie.com https://*.vimeo.com"
# The default and maximum item-counts for listing API requests.
API_DEFAULT_ITEM_COUNT=100
API_MAX_ITEM_COUNT=500
@@ -333,3 +365,11 @@ API_REQUESTS_PER_MIN=180
# user identifier (Username or email).
LOG_FAILED_LOGIN_MESSAGE=false
LOG_FAILED_LOGIN_CHANNEL=errorlog_plain_webserver
# Alter the precision of IP addresses stored by BookStack.
# Should be a number between 0 and 4, where 4 retains the full IP address
# and 0 completely hides the IP address. As an example, a value of 2 for the
# IP address '146.191.42.4' would result in '146.191.x.x' being logged.
# For the IPv6 address '2001:db8:85a3:8d3:1319:8a2e:370:7348' this would result as:
# '2001:db8:85a3:8d3:x:x:x:x'
IP_ADDRESS_PRECISION=4

1
.github/FUNDING.yml vendored
View File

@@ -1,3 +1,4 @@
# These are supported funding model platforms
github: [ssddanbrown]
ko_fi: ssddanbrown

View File

@@ -1,6 +1,5 @@
name: New API Endpoint or API Ability
description: Request a new endpoint or API feature be added
title: "[API Request]: "
labels: [":nut_and_bolt: API Request"]
body:
- type: textarea

View File

@@ -1,6 +1,5 @@
name: Bug Report
description: Create a report to help us improve or fix things
title: "[Bug Report]: "
labels: [":bug: Bug"]
body:
- type: textarea
@@ -36,6 +35,15 @@ body:
description: Provide any additional context and screenshots here to help us solve this issue
validations:
required: false
- type: input
id: browserdetails
attributes:
label: Browser Details
description: |
If this is an issue that occurs when using the BookStack interface, please provide details of the browser used which presents the reported issue.
placeholder: (eg. Firefox 97 (64-bit) on Windows 11)
validations:
required: false
- type: input
id: bsversion
attributes:

View File

@@ -1,9 +1,13 @@
blank_issues_enabled: false
contact_links:
- name: Discord chat support
- name: Discord Chat Support
url: https://discord.gg/ztkBqR2
about: Realtime support / chat with the community and the team.
about: Realtime support & chat with the BookStack community and the team.
- name: Debugging & Common Issues
url: https://www.bookstackapp.com/docs/admin/debugging/
about: Find details on how to debug issues and view common issues with thier resolutions.
about: Find details on how to debug issues and view common issues with their resolutions.
- name: Official Support Plans
url: https://www.bookstackapp.com/support/
about: View our official support plans that offer assured support for business.

View File

@@ -1,6 +1,5 @@
name: Feature Request
description: Request a new language to be added to CrowdIn for you to translate
title: "[Feature Request]: "
description: Request a new feature or idea to be added to BookStack
labels: [":hammer: Feature Request"]
body:
- type: textarea
@@ -13,8 +12,41 @@ body:
- type: textarea
id: benefits
attributes:
label: Describe the benefits this feature would bring to BookStack users
description: Explain the measurable benefits this feature would achieve for existing BookStack users
label: Describe the benefits this would bring to existing BookStack users
description: |
Explain the measurable benefits this feature would achieve for existing BookStack users.
These benefits should details outcomes in terms of what this request solves/achieves, and should not be specific to implementation.
This helps us understand the core desired goal so that a variety of potential implementations could be explored.
This field is important. Lack if input here may lead to early issue closure.
validations:
required: true
- type: textarea
id: already_achieved
attributes:
label: Can the goal of this request already be achieved via other means?
description: |
Yes/No. If yes, please describe how the requested approach fits in with the existing method.
validations:
required: true
- type: checkboxes
id: confirm-search
attributes:
label: Have you searched for an existing open/closed issue?
description: |
To help us keep these issues under control, please ensure you have first [searched our issue list](https://github.com/BookStackApp/BookStack/issues?q=is%3Aissue) for any existing issues that cover the fundemental benefit/goal of your request.
options:
- label: I have searched for existing issues and none cover my fundemental request
required: true
- type: dropdown
id: existing_usage
attributes:
label: How long have you been using BookStack?
options:
- Not using yet, just scoping
- 0 to 6 months
- 6 months to 1 year
- 1 to 5 years
- Over 5 years
validations:
required: true
- type: textarea

View File

@@ -1,6 +1,5 @@
name: Language Request
description: Request a new language to be added to CrowdIn for you to translate
title: "[Language Request]: "
description: Request a new language to be added to Crowdin for you to translate
labels: [":earth_africa: Translations"]
assignees:
- ssddanbrown
@@ -24,7 +23,7 @@ body:
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.
options:
- label: I confirm I'm offering to help translate for this new language via CrowdIn.
- label: I confirm I'm offering to help translate for this new language via Crowdin.
required: true
- type: markdown
attributes:

View File

@@ -1,6 +1,5 @@
name: Support Request
description: Request support for a specific problem you have not been able to solve yourself
title: "[Support Request]: "
labels: [":dog2: Support"]
body:
- type: checkboxes

View File

@@ -55,6 +55,8 @@ Name :: Languages
@Baptistou :: French
@arcoai :: Spanish
@Jokuna :: Korean
@smartshogu :: German; German Informal
@samadha56 :: Persian
cipi1965 :: Italian
Mykola Ronik (Mantikor) :: Ukrainian
furkanoyk :: Turkish
@@ -126,7 +128,7 @@ Zenahr Barzani (Zenahr) :: German; Japanese; Dutch; German Informal
tatsuya.info :: Japanese
fadiapp :: Arabic
Jakub Bouček (jakubboucek) :: Czech
Marco (cdrfun) :: German
Marco (cdrfun) :: German; German Informal
10935336 :: Chinese Simplified
孟繁阳 (FanyangMeng) :: Chinese Simplified
Andrej Močan (andrejm) :: Slovenian
@@ -136,7 +138,7 @@ Xiphoseer :: German
MerlinSVK (merlinsvk) :: Slovak
Kauê Sena (kaue.sena.ks) :: Portuguese, Brazilian
MatthieuParis :: French
Douradinho :: Portuguese, Brazilian
Douradinho :: Portuguese, Brazilian; Portuguese
Gaku Yaguchi (tama11) :: Japanese
johnroyer :: Chinese Traditional
jackaaa :: Chinese Traditional
@@ -158,14 +160,14 @@ HenrijsS :: Latvian
Pascal R-B (pborgner) :: German
Boris (Ginfred) :: Russian
Jonas Anker Rasmussen (jonasanker) :: Danish
Gerwin de Keijzer (gdekeijzer) :: Dutch; German; German Informal
Gerwin de Keijzer (gdekeijzer) :: Dutch; German Informal; German
kometchtech :: Japanese
Auri (Atalonica) :: Catalan
Francesco Franchina (ffranchina) :: Italian
Aimrane Kds (aimrane.kds) :: Arabic
whenwesober :: Indonesian
Rem (remkovdhoef) :: Dutch
syn7ax69 :: Bulgarian; Turkish
syn7ax69 :: Bulgarian; Turkish; German
Blaade :: French
Behzad HosseinPoor (behzad.hp) :: Persian
Ole Aldric (Swoy) :: Norwegian Bokmal
@@ -174,7 +176,7 @@ Alexander Predl (Harveyhase68) :: German
Rem (Rem9000) :: Dutch
Michał Stelmach (stelmach-web) :: Polish
arniom :: French
REMOVED_USER :: Turkish
REMOVED_USER :: ; French; Dutch; Turkish
林祖年 (contagion) :: Chinese Traditional
Siamak Guodarzi (siamakgoudarzi88) :: Persian
Lis Maestrelo (lismtrl) :: Portuguese, Brazilian
@@ -200,3 +202,112 @@ sulfo :: Danish
Raukze :: German
zygimantus :: Lithuanian
marinkaberg :: Russian
Vitaliy (gviabcua) :: Ukrainian
mannycarreiro :: Portuguese
Thiago Rafael Pereira de Carvalho (thiago.rafael) :: Portuguese, Brazilian
Ken Roger Bolgnes (kenbo124) :: Norwegian Bokmal
Nguyen Hung Phuong (hnwolf) :: Vietnamese
Umut ERGENE (umutergene67) :: Turkish
Tomáš Batelka (Vofy) :: Czech
Mundo Racional (ismael.mesquita) :: Portuguese, Brazilian
Zarik (3apuk) :: Russian
Ali Shaatani (a.shaatani) :: Arabic
ChacMaster :: Portuguese, Brazilian
Saeed (saeed205) :: Persian
Julesdevops :: French
peter cerny (posli.to.semka) :: Slovak
Pavel Karlin (pavelkarlin) :: Russian
SmokingCrop :: Dutch
Maciej Lebiest (Szwendacz) :: Polish
DiscordDigital :: German; German Informal
Gábor Marton (dodver) :: Hungarian
Jasell :: Swedish
Ghost_chu (ghostchu) :: Chinese Simplified
Ravid Shachar (ravidshachar) :: Hebrew
Helga Guchshenskaya (guchshenskaya) :: Russian
daniel chou (chou0214) :: Chinese Traditional
Manolis PATRIARCHE (m.patriarche) :: French
Mohammed Haboubi (haboubi92) :: Arabic
roncallyt :: Portuguese, Brazilian
goegol :: Dutch
msevgen :: Turkish
Khroners :: French
MASOUD HOSSEINY (masoudme) :: Persian
Thomerson Roncally (roncallyt) :: Portuguese, Brazilian
metaarch :: Bulgarian
Xabi (xabikip) :: Basque
pedromcsousa :: Portuguese
Nir Louk (looknear) :: Hebrew
Alex (qianmengnet) :: Chinese Simplified
stothew :: German
sgenc :: Turkish
Shukrullo (vodiylik) :: Uzbek
William W. (Nevnt) :: Chinese Traditional
eamaro :: Portuguese
Ypsilon-dev :: Arabic
Hieu Vuong Trung (vuongtrunghieu) :: Vietnamese
David Clubb (davidoclubb) :: Welsh
welles freire (wellesximenes) :: Portuguese, Brazilian
Magnus Jensen (MagnusHJensen) :: Danish
Hesley Magno (hesleymagno) :: Portuguese, Brazilian
Éric Gaspar (erga) :: French
Fr3shlama :: German
DSR :: Spanish, Argentina
Andrii Bodnar (andrii-bodnar) :: Ukrainian
Younes el Anjri (younesea28) :: Dutch
Guclu Ozturk (gucluoz) :: Turkish
Atmis :: French
redjack666 :: Chinese Traditional
Ashita007 :: Russian
lihaorr :: Chinese Simplified
Marcus Silber (marcus.silber82) :: German
PellNet :: Croatian
Winetradr :: German
Sebastian Klaus (sebklaus) :: German
Filip Antala (AntalaFilip) :: Slovak
mcgong (GongMingCai) :: Chinese Simplified; Chinese Traditional
Nanang Setia Budi (sefidananang) :: Indonesian
Андрей Павлов (andrei.pavlov) :: Russian
Alex Navarro (alex.n.navarro) :: Portuguese, Brazilian
Ji-Hyeon Gim (PotatoGim) :: Korean
Mihai Ochian (soulstorm19) :: Romanian
HeartCore :: German Informal; German
simon.pct :: French
okaeiz :: Persian
Naoto Ishikawa (na3shkw) :: Japanese
sdhadi :: Persian
DerLinkman (derlinkman) :: German; German Informal
TurnArabic :: Arabic
Martin Sebek (sebekmartin) :: Czech
Kuchinashi Hoshikawa (kuchinashi) :: Chinese Simplified
digilady :: Greek
Linus (LinusOP) :: Swedish
Felipe Cardoso (felipecardosoruff) :: Portuguese, Brazilian
RandomUser0815 :: German Informal; German
Ismael Mesquita (mesquitoliveira) :: Portuguese, Brazilian
구인회 (laskdjlaskdj12) :: Korean
LiZerui (CNLiZerui) :: Chinese Traditional
Fabrice Boyer (FabriceBoyer) :: French
mikael (bitcanon) :: Swedish
Matthias Mai (schnapsidee) :: German; German Informal
Ufuk Ayyıldız (ufukayyildiz) :: Turkish
Jan Mitrof (jan.kachlik) :: Czech
edwardsmirnov :: Russian
Mr_OSS117 :: French
shotu :: French
Cesar_Lopez_Aguillon :: Spanish
bdewoop :: German
dina davoudi (dina.davoudi) :: Persian
Angelos Chouvardas (achouvardas) :: Greek
rndrss :: Portuguese, Brazilian
rirac294 :: Russian
David Furman (thefourCraft) :: Hebrew
Pafzedog :: French
Yllelder :: Spanish
Adrian Ocneanu (aocneanu) :: Romanian
Eduardo Castanho (EduardoCastanho) :: Portuguese
VIET NAM VPS (vietnamvps) :: Vietnamese
m4tthi4s :: French
toras9000 :: Japanese
pathab :: German
MichelSchoon85 :: Dutch

34
.github/workflows/analyse-php.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: analyse-php
on: [push, pull_request]
jobs:
build:
if: ${{ github.ref != 'refs/heads/l10n_development' }}
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v1
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.1
extensions: gd, mbstring, json, curl, xml, mysql, ldap
- name: Get Composer Cache Directory
id: composer-cache
run: |
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer packages
uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-8.1
restore-keys: ${{ runner.os }}-composer-
- name: Install composer dependencies
run: composer install --prefer-dist --no-interaction --ansi
- name: Run static analysis check
run: composer check-static

19
.github/workflows/lint-php.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
name: lint-php
on: [push, pull_request]
jobs:
build:
if: ${{ github.ref != 'refs/heads/l10n_development' }}
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v1
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.1
tools: phpcs
- name: Run formatting check
run: composer lint

View File

@@ -1,41 +0,0 @@
name: phpstan
on:
push:
branches-ignore:
- l10n_master
pull_request:
branches-ignore:
- l10n_master
jobs:
build:
runs-on: ubuntu-20.04
strategy:
matrix:
php: ['7.3']
steps:
- uses: actions/checkout@v1
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: gd, mbstring, json, curl, xml, mysql, ldap
- 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: Install composer dependencies
run: composer install --prefer-dist --no-interaction --ansi
- name: Run PHPStan
run: php${{ matrix.php }} ./vendor/bin/phpstan analyse --memory-limit=2G

View File

@@ -1,19 +1,14 @@
name: test-migrations
on:
push:
branches-ignore:
- l10n_master
pull_request:
branches-ignore:
- l10n_master
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-20.04
if: ${{ github.ref != 'refs/heads/l10n_development' }}
runs-on: ubuntu-22.04
strategy:
matrix:
php: ['7.3', '7.4', '8.0', '8.1']
php: ['8.0', '8.1', '8.2']
steps:
- uses: actions/checkout@v1
@@ -26,17 +21,18 @@ jobs:
- name: Get Composer Cache Directory
id: composer-cache
run: |
echo "::set-output name=dir::$(composer config cache-files-dir)"
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer packages
uses: actions/cache@v1
uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ matrix.php }}
restore-keys: ${{ runner.os }}-composer-
- name: Start MySQL
run: |
sudo /etc/init.d/mysql start
sudo systemctl start mysql
- name: Create database & user
run: |

View File

@@ -1,19 +1,14 @@
name: phpunit
name: test-php
on:
push:
branches-ignore:
- l10n_master
pull_request:
branches-ignore:
- l10n_master
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-20.04
if: ${{ github.ref != 'refs/heads/l10n_development' }}
runs-on: ubuntu-22.04
strategy:
matrix:
php: ['7.3', '7.4', '8.0', '8.1']
php: ['8.0', '8.1', '8.2']
steps:
- uses: actions/checkout@v1
@@ -21,22 +16,23 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: gd, mbstring, json, curl, xml, mysql, ldap
extensions: gd, mbstring, json, curl, xml, mysql, ldap, gmp
- name: Get Composer Cache Directory
id: composer-cache
run: |
echo "::set-output name=dir::$(composer config cache-files-dir)"
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer packages
uses: actions/cache@v1
uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ matrix.php }}
restore-keys: ${{ runner.os }}-composer-
- name: Start Database
run: |
sudo /etc/init.d/mysql start
sudo systemctl start mysql
- name: Setup Database
run: |
@@ -53,5 +49,5 @@ jobs:
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
- name: Run PHP tests
run: php${{ matrix.php }} ./vendor/bin/phpunit

1
.gitignore vendored
View File

@@ -11,6 +11,7 @@ yarn-error.log
/public/js/*.map
/public/bower
/public/build/
/public/favicon.ico
/storage/images
_ide_helper.php
/storage/debugbar

View File

@@ -1,7 +1,6 @@
The MIT License (MIT)
Copyright (c) 2015-present, Dan Brown and the BookStack Project contributors
https://github.com/BookStackApp/BookStack/graphs/contributors
Copyright (c) 2015-2023, Dan Brown and the BookStack Project 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

@@ -2,10 +2,12 @@
namespace BookStack\Actions;
use BookStack\Auth\Permissions\JointPermission;
use BookStack\Auth\User;
use BookStack\Entities\Models\Entity;
use BookStack\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Support\Str;
@@ -40,6 +42,12 @@ class Activity extends Model
return $this->belongsTo(User::class);
}
public function jointPermissions(): HasMany
{
return $this->hasMany(JointPermission::class, 'entity_id', 'entity_id')
->whereColumn('activities.entity_type', '=', 'joint_permissions.entity_type');
}
/**
* Returns text from the language files, Looks up by using the activity key.
*/

View File

@@ -0,0 +1,109 @@
<?php
namespace BookStack\Actions;
use BookStack\Entities\Models\Entity;
use BookStack\Facades\Theme;
use BookStack\Interfaces\Loggable;
use BookStack\Theming\ThemeEvents;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Log;
class ActivityLogger
{
/**
* Add a generic activity event to the database.
*
* @param string|Loggable $detail
*/
public function add(string $type, $detail = '')
{
$detailToStore = ($detail instanceof Loggable) ? $detail->logDescriptor() : $detail;
$activity = $this->newActivityForUser($type);
$activity->detail = $detailToStore;
if ($detail instanceof Entity) {
$activity->entity_id = $detail->id;
$activity->entity_type = $detail->getMorphClass();
}
$activity->save();
$this->setNotification($type);
$this->dispatchWebhooks($type, $detail);
Theme::dispatch(ThemeEvents::ACTIVITY_LOGGED, $type, $detail);
}
/**
* Get a new activity instance for the current user.
*/
protected function newActivityForUser(string $type): Activity
{
return (new Activity())->forceFill([
'type' => strtolower($type),
'user_id' => user()->id,
'ip' => IpFormatter::fromCurrentRequest()->format(),
]);
}
/**
* 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)
{
$entity->activity()->update([
'detail' => $entity->name,
'entity_id' => null,
'entity_type' => null,
]);
}
/**
* Flashes a notification message to the session if an appropriate message is available.
*/
protected function setNotification(string $type): void
{
$notificationTextKey = 'activities.' . $type . '_notification';
if (trans()->has($notificationTextKey)) {
$message = trans($notificationTextKey);
session()->flash('success', $message);
}
}
/**
* @param string|Loggable $detail
*/
protected function dispatchWebhooks(string $type, $detail): void
{
$webhooks = Webhook::query()
->whereHas('trackedEvents', function (Builder $query) use ($type) {
$query->where('event', '=', $type)
->orWhere('event', '=', 'all');
})
->where('active', '=', true)
->get();
foreach ($webhooks as $webhook) {
dispatch(new DispatchWebhookJob($webhook, $type, $detail));
}
}
/**
* Log out a failed login attempt, Providing the given username
* as part of the message if the '%u' string is used.
*/
public function logFailedLogin(string $username)
{
$message = config('logging.failed_login.message');
if (!$message) {
return;
}
$message = str_replace('%u', $username, $message);
$channel = config('logging.failed_login.channel');
Log::channel($channel)->warning($message);
}
}

View File

@@ -0,0 +1,112 @@
<?php
namespace BookStack\Actions;
use BookStack\Auth\Permissions\PermissionApplicator;
use BookStack\Auth\User;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\Page;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\Relation;
class ActivityQueries
{
protected PermissionApplicator $permissions;
public function __construct(PermissionApplicator $permissions)
{
$this->permissions = $permissions;
}
/**
* Gets the latest activity.
*/
public function latest(int $count = 20, int $page = 0): array
{
$activityList = $this->permissions
->restrictEntityRelationQuery(Activity::query(), '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
{
/** @var array<string, int[]> $queryIds */
$queryIds = [$entity->getMorphClass() => [$entity->id]];
if ($entity instanceof Book) {
$queryIds[(new Chapter())->getMorphClass()] = $entity->chapters()->scopes('visible')->pluck('id');
}
if ($entity instanceof Book || $entity instanceof Chapter) {
$queryIds[(new Page())->getMorphClass()] = $entity->pages()->scopes('visible')->pluck('id');
}
$query = Activity::query();
$query->where(function (Builder $query) use ($queryIds) {
foreach ($queryIds as $morphClass => $idArr) {
$query->orWhere(function (Builder $innerQuery) use ($morphClass, $idArr) {
$innerQuery->where('entity_type', '=', $morphClass)
->whereIn('entity_id', $idArr);
});
}
});
$activity = $query->orderBy('created_at', 'desc')
->with(['entity' => function (Relation $query) {
$query->withTrashed();
}, 'user.avatar'])
->skip($count * ($page - 1))
->take($count)
->get();
return $this->filterSimilar($activity);
}
/**
* Get the latest activity for a user, Filtering out similar items.
*/
public function userActivity(User $user, int $count = 20, int $page = 0): array
{
$activityList = $this->permissions
->restrictEntityRelationQuery(Activity::query(), '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
*/
protected function filterSimilar(iterable $activities): array
{
$newActivity = [];
$previousItem = null;
foreach ($activities as $activityItem) {
if (!$previousItem || !$activityItem->isSimilarTo($previousItem)) {
$newActivity[] = $activityItem;
}
$previousItem = $activityItem;
}
return $newActivity;
}
}

View File

@@ -1,201 +0,0 @@
<?php
namespace BookStack\Actions;
use BookStack\Auth\Permissions\PermissionService;
use BookStack\Auth\User;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\Page;
use BookStack\Interfaces\Loggable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Support\Facades\Log;
class ActivityService
{
protected $activity;
protected $permissionService;
public function __construct(Activity $activity, PermissionService $permissionService)
{
$this->activity = $activity;
$this->permissionService = $permissionService;
}
/**
* Add activity data to database for an entity.
*/
public function addForEntity(Entity $entity, string $type)
{
$activity = $this->newActivityForUser($type);
$entity->activity()->save($activity);
$this->setNotification($type);
}
/**
* Add a generic activity event to the database.
*
* @param string|Loggable $detail
*/
public function add(string $type, $detail = '')
{
if ($detail instanceof Loggable) {
$detail = $detail->logDescriptor();
}
$activity = $this->newActivityForUser($type);
$activity->detail = $detail;
$activity->save();
$this->setNotification($type);
}
/**
* Get a new activity instance for the current user.
*/
protected function newActivityForUser(string $type): Activity
{
$ip = request()->ip() ?? '';
return $this->activity->newInstance()->forceFill([
'type' => strtolower($type),
'user_id' => user()->id,
'ip' => config('app.env') === 'demo' ? '127.0.0.1' : $ip,
]);
}
/**
* 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)
{
$entity->activity()->update([
'detail' => $entity->name,
'entity_id' => null,
'entity_type' => null,
]);
}
/**
* Gets the latest activity.
*/
public function latest(int $count = 20, int $page = 0): array
{
$activityList = $this->permissionService
->filterRestrictedEntityRelations($this->activity->newQuery(), '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
{
/** @var array<string, int[]> $queryIds */
$queryIds = [$entity->getMorphClass() => [$entity->id]];
if ($entity instanceof Book) {
$queryIds[(new Chapter())->getMorphClass()] = $entity->chapters()->scopes('visible')->pluck('id');
}
if ($entity instanceof Book || $entity instanceof Chapter) {
$queryIds[(new Page())->getMorphClass()] = $entity->pages()->scopes('visible')->pluck('id');
}
$query = $this->activity->newQuery();
$query->where(function (Builder $query) use ($queryIds) {
foreach ($queryIds as $morphClass => $idArr) {
$query->orWhere(function (Builder $innerQuery) use ($morphClass, $idArr) {
$innerQuery->where('entity_type', '=', $morphClass)
->whereIn('entity_id', $idArr);
});
}
});
$activity = $query->orderBy('created_at', 'desc')
->with(['entity' => function (Relation $query) {
$query->withTrashed();
}, '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->newQuery(), '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 $type)
{
$notificationTextKey = 'activities.' . $type . '_notification';
if (trans()->has($notificationTextKey)) {
$message = trans($notificationTextKey);
session()->flash('success', $message);
}
}
/**
* Log out a failed login attempt, Providing the given username
* as part of the message if the '%u' string is used.
*/
public function logFailedLogin(string $username)
{
$message = config('logging.failed_login.message');
if (!$message) {
return;
}
$message = str_replace('%u', $username, $message);
$channel = config('logging.failed_login.channel');
Log::channel($channel)->warning($message);
}
}

View File

@@ -16,17 +16,22 @@ class ActivityType
const CHAPTER_MOVE = 'chapter_move';
const BOOK_CREATE = 'book_create';
const BOOK_CREATE_FROM_CHAPTER = 'book_create_from_chapter';
const BOOK_UPDATE = 'book_update';
const BOOK_DELETE = 'book_delete';
const BOOK_SORT = 'book_sort';
const BOOKSHELF_CREATE = 'bookshelf_create';
const BOOKSHELF_CREATE_FROM_BOOK = 'bookshelf_create_from_book';
const BOOKSHELF_UPDATE = 'bookshelf_update';
const BOOKSHELF_DELETE = 'bookshelf_delete';
const COMMENTED_ON = 'commented_on';
const PERMISSIONS_UPDATE = 'permissions_update';
const REVISION_RESTORE = 'revision_restore';
const REVISION_DELETE = 'revision_delete';
const SETTINGS_UPDATE = 'settings_update';
const MAINTENANCE_ACTION_RUN = 'maintenance_action_run';
@@ -53,4 +58,16 @@ class ActivityType
const MFA_SETUP_METHOD = 'mfa_setup_method';
const MFA_REMOVE_METHOD = 'mfa_remove_method';
const WEBHOOK_CREATE = 'webhook_create';
const WEBHOOK_UPDATE = 'webhook_update';
const WEBHOOK_DELETE = 'webhook_delete';
/**
* Get all the possible values.
*/
public static function all(): array
{
return (new \ReflectionClass(static::class))->getConstants();
}
}

View File

@@ -45,7 +45,7 @@ class CommentRepo
$comment->parent_id = $parent_id;
$entity->comments()->save($comment);
ActivityService::addForEntity($entity, ActivityType::COMMENTED_ON);
ActivityService::add(ActivityType::COMMENTED_ON, $entity);
return $comment;
}

View File

@@ -0,0 +1,82 @@
<?php
namespace BookStack\Actions;
use BookStack\Auth\User;
use BookStack\Facades\Theme;
use BookStack\Interfaces\Loggable;
use BookStack\Theming\ThemeEvents;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
class DispatchWebhookJob implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
use SerializesModels;
protected Webhook $webhook;
protected string $event;
protected User $initiator;
protected int $initiatedTime;
/**
* @var string|Loggable
*/
protected $detail;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Webhook $webhook, string $event, $detail)
{
$this->webhook = $webhook;
$this->event = $event;
$this->detail = $detail;
$this->initiator = user();
$this->initiatedTime = time();
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$themeResponse = Theme::dispatch(ThemeEvents::WEBHOOK_CALL_BEFORE, $this->event, $this->webhook, $this->detail, $this->initiator, $this->initiatedTime);
$webhookData = $themeResponse ?? WebhookFormatter::getDefault($this->event, $this->webhook, $this->detail, $this->initiator, $this->initiatedTime)->format();
$lastError = null;
try {
$response = Http::asJson()
->withOptions(['allow_redirects' => ['strict' => true]])
->timeout($this->webhook->timeout)
->post($this->webhook->endpoint, $webhookData);
} catch (\Exception $exception) {
$lastError = $exception->getMessage();
Log::error("Webhook call to endpoint {$this->webhook->endpoint} failed with error \"{$lastError}\"");
}
if (isset($response) && $response->failed()) {
$lastError = "Response status from endpoint was {$response->status()}";
Log::error("Webhook call to endpoint {$this->webhook->endpoint} failed with status {$response->status()}");
}
$this->webhook->last_called_at = now();
if ($lastError) {
$this->webhook->last_errored_at = now();
$this->webhook->last_error = $lastError;
}
$this->webhook->save();
}
}

View File

@@ -2,7 +2,9 @@
namespace BookStack\Actions;
use BookStack\Auth\Permissions\JointPermission;
use BookStack\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphTo;
class Favourite extends Model
@@ -16,4 +18,10 @@ class Favourite extends Model
{
return $this->morphTo();
}
public function jointPermissions(): HasMany
{
return $this->hasMany(JointPermission::class, 'entity_id', 'favouritable_id')
->whereColumn('favourites.favouritable_type', '=', 'joint_permissions.entity_type');
}
}

View File

@@ -0,0 +1,81 @@
<?php
namespace BookStack\Actions;
class IpFormatter
{
protected string $ip;
protected int $precision;
public function __construct(string $ip, int $precision)
{
$this->ip = trim($ip);
$this->precision = max(0, min($precision, 4));
}
public function format(): string
{
if (empty($this->ip) || $this->precision === 4) {
return $this->ip;
}
return $this->isIpv6() ? $this->maskIpv6() : $this->maskIpv4();
}
protected function maskIpv4(): string
{
$exploded = $this->explodeAndExpandIp('.', 4);
$maskGroupCount = min(4 - $this->precision, count($exploded));
for ($i = 0; $i < $maskGroupCount; $i++) {
$exploded[3 - $i] = 'x';
}
return implode('.', $exploded);
}
protected function maskIpv6(): string
{
$exploded = $this->explodeAndExpandIp(':', 8);
$maskGroupCount = min(8 - ($this->precision * 2), count($exploded));
for ($i = 0; $i < $maskGroupCount; $i++) {
$exploded[7 - $i] = 'x';
}
return implode(':', $exploded);
}
protected function isIpv6(): bool
{
return strpos($this->ip, ':') !== false;
}
protected function explodeAndExpandIp(string $separator, int $targetLength): array
{
$exploded = explode($separator, $this->ip);
while (count($exploded) < $targetLength) {
$emptyIndex = array_search('', $exploded) ?: count($exploded) - 1;
array_splice($exploded, $emptyIndex, 0, '0');
}
$emptyIndex = array_search('', $exploded);
if ($emptyIndex !== false) {
$exploded[$emptyIndex] = '0';
}
return $exploded;
}
public static function fromCurrentRequest(): self
{
$ip = request()->ip() ?? '';
if (config('app.env') === 'demo') {
$ip = '127.0.0.1';
}
return new self($ip, config('app.ip_address_precision'));
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace BookStack\Actions\Queries;
use BookStack\Actions\Webhook;
use BookStack\Util\SimpleListOptions;
use Illuminate\Pagination\LengthAwarePaginator;
/**
* Get all the webhooks in the system in a paginated format.
*/
class WebhooksAllPaginatedAndSorted
{
public function run(int $count, SimpleListOptions $listOptions): LengthAwarePaginator
{
$query = Webhook::query()->select(['*'])
->withCount(['trackedEvents'])
->orderBy($listOptions->getSort(), $listOptions->getOrder());
if ($listOptions->getSearch()) {
$term = '%' . $listOptions->getSearch() . '%';
$query->where(function ($query) use ($term) {
$query->where('name', 'like', $term)
->orWhere('endpoint', 'like', $term);
});
}
return $query->paginate($count);
}
}

View File

@@ -2,8 +2,10 @@
namespace BookStack\Actions;
use BookStack\Auth\Permissions\JointPermission;
use BookStack\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphTo;
/**
@@ -27,6 +29,12 @@ class Tag extends Model
return $this->morphTo('entity');
}
public function jointPermissions(): HasMany
{
return $this->hasMany(JointPermission::class, 'entity_id', 'entity_id')
->whereColumn('tags.entity_type', '=', 'joint_permissions.entity_type');
}
/**
* Get a full URL to start a tag name search for this tag name.
*/

View File

@@ -0,0 +1,49 @@
<?php
namespace BookStack\Actions;
class TagClassGenerator
{
protected array $tags;
/**
* @param Tag[] $tags
*/
public function __construct(array $tags)
{
$this->tags = $tags;
}
/**
* @return string[]
*/
public function generate(): array
{
$classes = [];
foreach ($this->tags as $tag) {
$name = $this->normalizeTagClassString($tag->name);
$value = $this->normalizeTagClassString($tag->value);
$classes[] = 'tag-name-' . $name;
if ($value) {
$classes[] = 'tag-value-' . $value;
$classes[] = 'tag-pair-' . $name . '-' . $value;
}
}
return array_unique($classes);
}
public function generateAsString(): string
{
return implode(' ', $this->generate());
}
protected function normalizeTagClassString(string $value): string
{
$value = str_replace(' ', '', strtolower($value));
$value = str_replace('-', '', strtolower($value));
return $value;
}
}

View File

@@ -2,38 +2,44 @@
namespace BookStack\Actions;
use BookStack\Auth\Permissions\PermissionService;
use BookStack\Auth\Permissions\PermissionApplicator;
use BookStack\Entities\Models\Entity;
use BookStack\Util\SimpleListOptions;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
class TagRepo
{
protected $tag;
protected $permissionService;
protected PermissionApplicator $permissions;
public function __construct(PermissionService $ps)
public function __construct(PermissionApplicator $permissions)
{
$this->permissionService = $ps;
$this->permissions = $permissions;
}
/**
* Start a query against all tags in the system.
*/
public function queryWithTotals(string $searchTerm, string $nameFilter): Builder
public function queryWithTotals(SimpleListOptions $listOptions, string $nameFilter): Builder
{
$searchTerm = $listOptions->getSearch();
$sort = $listOptions->getSort();
if ($sort === 'name' && $nameFilter) {
$sort = 'value';
}
$query = Tag::query()
->select([
'name',
($searchTerm || $nameFilter) ? 'value' : DB::raw('COUNT(distinct value) as `values`'),
DB::raw('COUNT(id) as usages'),
DB::raw('SUM(IF(entity_type = \'BookStack\\\\Page\', 1, 0)) as page_count'),
DB::raw('SUM(IF(entity_type = \'BookStack\\\\Chapter\', 1, 0)) as chapter_count'),
DB::raw('SUM(IF(entity_type = \'BookStack\\\\Book\', 1, 0)) as book_count'),
DB::raw('SUM(IF(entity_type = \'BookStack\\\\BookShelf\', 1, 0)) as shelf_count'),
DB::raw('SUM(IF(entity_type = \'page\', 1, 0)) as page_count'),
DB::raw('SUM(IF(entity_type = \'chapter\', 1, 0)) as chapter_count'),
DB::raw('SUM(IF(entity_type = \'book\', 1, 0)) as book_count'),
DB::raw('SUM(IF(entity_type = \'bookshelf\', 1, 0)) as shelf_count'),
])
->orderBy($nameFilter ? 'value' : 'name');
->orderBy($sort, $listOptions->getOrder());
if ($nameFilter) {
$query->where('name', '=', $nameFilter);
@@ -51,28 +57,28 @@ class TagRepo
});
}
return $this->permissionService->filterRestrictedEntityRelations($query, 'tags', 'entity_id', 'entity_type');
return $this->permissions->restrictEntityRelationQuery($query, 'tags', 'entity_id', 'entity_type');
}
/**
* Get tag name suggestions from scanning existing tag names.
* If no search term is given the 50 most popular tag names are provided.
*/
public function getNameSuggestions(?string $searchTerm): Collection
public function getNameSuggestions(string $searchTerm): Collection
{
$query = Tag::query()
->select('*', DB::raw('count(*) as count'))
->groupBy('name');
if ($searchTerm) {
$query = $query->where('name', 'LIKE', $searchTerm . '%')->orderBy('name', 'desc');
$query = $query->where('name', 'LIKE', $searchTerm . '%')->orderBy('name', 'asc');
} else {
$query = $query->orderBy('count', 'desc')->take(50);
}
$query = $this->permissionService->filterRestrictedEntityRelations($query, 'tags', 'entity_id', 'entity_type');
$query = $this->permissions->restrictEntityRelationQuery($query, 'tags', 'entity_id', 'entity_type');
return $query->get(['name'])->pluck('name');
return $query->pluck('name');
}
/**
@@ -80,7 +86,7 @@ class TagRepo
* If no search is given the 50 most popular values are provided.
* Passing a tagName will only find values for a tags with a particular name.
*/
public function getValueSuggestions(?string $searchTerm, ?string $tagName): Collection
public function getValueSuggestions(string $searchTerm, string $tagName): Collection
{
$query = Tag::query()
->select('*', DB::raw('count(*) as count'))
@@ -96,9 +102,9 @@ class TagRepo
$query = $query->where('name', '=', $tagName);
}
$query = $this->permissionService->filterRestrictedEntityRelations($query, 'tags', 'entity_id', 'entity_type');
$query = $this->permissions->restrictEntityRelationQuery($query, 'tags', 'entity_id', 'entity_type');
return $query->get(['value'])->pluck('value');
return $query->pluck('value');
}
/**

View File

@@ -2,8 +2,10 @@
namespace BookStack\Actions;
use BookStack\Auth\Permissions\JointPermission;
use BookStack\Interfaces\Viewable;
use BookStack\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphTo;
/**
@@ -28,6 +30,12 @@ class View extends Model
return $this->morphTo();
}
public function jointPermissions(): HasMany
{
return $this->hasMany(JointPermission::class, 'entity_id', 'viewable_id')
->whereColumn('views.viewable_type', '=', 'joint_permissions.entity_type');
}
/**
* Increment the current user's view count for the given viewable model.
*/

85
app/Actions/Webhook.php Normal file
View File

@@ -0,0 +1,85 @@
<?php
namespace BookStack\Actions;
use BookStack\Interfaces\Loggable;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
/**
* @property int $id
* @property string $name
* @property string $endpoint
* @property Collection $trackedEvents
* @property bool $active
* @property int $timeout
* @property string $last_error
* @property Carbon $last_called_at
* @property Carbon $last_errored_at
*/
class Webhook extends Model implements Loggable
{
use HasFactory;
protected $fillable = ['name', 'endpoint', 'timeout'];
protected $casts = [
'last_called_at' => 'datetime',
'last_errored_at' => 'datetime',
];
/**
* Define the tracked event relation a webhook.
*/
public function trackedEvents(): HasMany
{
return $this->hasMany(WebhookTrackedEvent::class);
}
/**
* Update the tracked events for a webhook from the given list of event types.
*/
public function updateTrackedEvents(array $events): void
{
$this->trackedEvents()->delete();
$eventsToStore = array_intersect($events, array_values(ActivityType::all()));
if (in_array('all', $events)) {
$eventsToStore = ['all'];
}
$trackedEvents = [];
foreach ($eventsToStore as $event) {
$trackedEvents[] = new WebhookTrackedEvent(['event' => $event]);
}
$this->trackedEvents()->saveMany($trackedEvents);
}
/**
* Check if this webhook tracks the given event.
*/
public function tracksEvent(string $event): bool
{
return $this->trackedEvents->pluck('event')->contains($event);
}
/**
* Get a URL for this webhook within the settings interface.
*/
public function getUrl(string $path = ''): string
{
return url('/settings/webhooks/' . $this->id . '/' . ltrim($path, '/'));
}
/**
* Get the string descriptor for this item.
*/
public function logDescriptor(): string
{
return "({$this->id}) {$this->name}";
}
}

View File

@@ -0,0 +1,124 @@
<?php
namespace BookStack\Actions;
use BookStack\Auth\User;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\Page;
use BookStack\Interfaces\Loggable;
use BookStack\Model;
use Illuminate\Support\Carbon;
class WebhookFormatter
{
protected Webhook $webhook;
protected string $event;
protected User $initiator;
protected int $initiatedTime;
/**
* @var string|Loggable
*/
protected $detail;
/**
* @var array{condition: callable(string, Model):bool, format: callable(Model):void}[]
*/
protected $modelFormatters = [];
public function __construct(string $event, Webhook $webhook, $detail, User $initiator, int $initiatedTime)
{
$this->webhook = $webhook;
$this->event = $event;
$this->initiator = $initiator;
$this->initiatedTime = $initiatedTime;
$this->detail = is_object($detail) ? clone $detail : $detail;
}
public function format(): array
{
$data = [
'event' => $this->event,
'text' => $this->formatText(),
'triggered_at' => Carbon::createFromTimestampUTC($this->initiatedTime)->toISOString(),
'triggered_by' => $this->initiator->attributesToArray(),
'triggered_by_profile_url' => $this->initiator->getProfileUrl(),
'webhook_id' => $this->webhook->id,
'webhook_name' => $this->webhook->name,
];
if (method_exists($this->detail, 'getUrl')) {
$data['url'] = $this->detail->getUrl();
}
if ($this->detail instanceof Model) {
$data['related_item'] = $this->formatModel();
}
return $data;
}
/**
* @param callable(string, Model):bool $condition
* @param callable(Model):void $format
*/
public function addModelFormatter(callable $condition, callable $format): void
{
$this->modelFormatters[] = [
'condition' => $condition,
'format' => $format,
];
}
public function addDefaultModelFormatters(): void
{
// Load entity owner, creator, updater details
$this->addModelFormatter(
fn ($event, $model) => ($model instanceof Entity),
fn ($model) => $model->load(['ownedBy', 'createdBy', 'updatedBy'])
);
// Load revision detail for page update and create events
$this->addModelFormatter(
fn ($event, $model) => ($model instanceof Page && ($event === ActivityType::PAGE_CREATE || $event === ActivityType::PAGE_UPDATE)),
fn ($model) => $model->load('currentRevision')
);
}
protected function formatModel(): array
{
/** @var Model $model */
$model = $this->detail;
$model->unsetRelations();
foreach ($this->modelFormatters as $formatter) {
if ($formatter['condition']($this->event, $model)) {
$formatter['format']($model);
}
}
return $model->toArray();
}
protected function formatText(): string
{
$textParts = [
$this->initiator->name,
trans('activities.' . $this->event),
];
if ($this->detail instanceof Entity) {
$textParts[] = '"' . $this->detail->name . '"';
}
return implode(' ', $textParts);
}
public static function getDefault(string $event, Webhook $webhook, $detail, User $initiator, int $initiatedTime): self
{
$instance = new self($event, $webhook, $detail, $initiator, $initiatedTime);
$instance->addDefaultModelFormatters();
return $instance;
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace BookStack\Actions;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* @property int $id
* @property int $webhook_id
* @property string $event
*/
class WebhookTrackedEvent extends Model
{
use HasFactory;
protected $fillable = ['event'];
}

View File

@@ -3,11 +3,13 @@
namespace BookStack\Api;
use BookStack\Http\Controllers\Api\ApiController;
use Exception;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules\Password;
use ReflectionClass;
use ReflectionException;
use ReflectionMethod;
@@ -100,11 +102,37 @@ class ApiDocsGenerator
$this->controllerClasses[$className] = $class;
}
$rules = $class->getValdationRules()[$methodName] ?? [];
$rules = collect($class->getValidationRules()[$methodName] ?? [])->map(function ($validations) {
return array_map(function ($validation) {
return $this->getValidationAsString($validation);
}, $validations);
})->toArray();
return empty($rules) ? null : $rules;
}
/**
* Convert the given validation message to a readable string.
*/
protected function getValidationAsString($validation): string
{
if (is_string($validation)) {
return $validation;
}
if (is_object($validation) && method_exists($validation, '__toString')) {
return strval($validation);
}
if ($validation instanceof Password) {
return 'min:8';
}
$class = get_class($validation);
throw new Exception("Cannot provide string representation of rule for class: {$class}");
}
/**
* Parse out the description text from a class method comment.
*/

View File

@@ -0,0 +1,107 @@
<?php
namespace BookStack\Api;
use BookStack\Entities\Models\Entity;
class ApiEntityListFormatter
{
/**
* The list to be formatted.
* @var Entity[]
*/
protected $list = [];
/**
* The fields to show in the formatted data.
* Can be a plain string array item for a direct model field (If existing on model).
* If the key is a string, with a callable value, the return value of the callable
* will be used for the resultant value. A null return value will omit the property.
* @var array<string|int, string|callable>
*/
protected $fields = [
'id', 'name', 'slug', 'book_id', 'chapter_id',
'draft', 'template', 'created_at', 'updated_at',
];
public function __construct(array $list)
{
$this->list = $list;
// Default dynamic fields
$this->withField('url', fn(Entity $entity) => $entity->getUrl());
}
/**
* Add a field to be used in the formatter, with the property using the given
* name and value being the return type of the given callback.
*/
public function withField(string $property, callable $callback): self
{
$this->fields[$property] = $callback;
return $this;
}
/**
* Show the 'type' property in the response reflecting the entity type.
* EG: page, chapter, bookshelf, book
* To be included in results with non-pre-determined types.
*/
public function withType(): self
{
$this->withField('type', fn(Entity $entity) => $entity->getType());
return $this;
}
/**
* Include tags in the formatted data.
*/
public function withTags(): self
{
$this->withField('tags', fn(Entity $entity) => $entity->tags);
return $this;
}
/**
* Format the data and return an array of formatted content.
* @return array[]
*/
public function format(): array
{
$results = [];
foreach ($this->list as $item) {
$results[] = $this->formatSingle($item);
}
return $results;
}
/**
* Format a single entity item to a plain array.
*/
protected function formatSingle(Entity $entity): array
{
$result = [];
$values = (clone $entity)->toArray();
foreach ($this->fields as $field => $callback) {
if (is_string($callback)) {
$field = $callback;
if (!isset($values[$field])) {
continue;
}
$value = $values[$field];
} else {
$value = $callback($entity);
if (is_null($value)) {
continue;
}
}
$result[$field] = $value;
}
return $result;
}
}

View File

@@ -4,15 +4,29 @@ namespace BookStack\Api;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ListingResponseBuilder
{
protected $query;
protected $request;
protected $fields;
protected Builder $query;
protected Request $request;
protected $filterOperators = [
/**
* @var string[]
*/
protected array $fields;
/**
* @var array<callable>
*/
protected array $resultModifiers = [];
/**
* @var array<string, string>
*/
protected array $filterOperators = [
'eq' => '=',
'ne' => '!=',
'gt' => '>',
@@ -24,6 +38,7 @@ class ListingResponseBuilder
/**
* ListingResponseBuilder constructor.
* The given fields will be forced visible within the model results.
*/
public function __construct(Builder $query, Request $request, array $fields)
{
@@ -35,12 +50,16 @@ class ListingResponseBuilder
/**
* Get the response from this builder.
*/
public function toResponse()
public function toResponse(): JsonResponse
{
$filteredQuery = $this->filterQuery($this->query);
$total = $filteredQuery->count();
$data = $this->fetchData($filteredQuery);
$data = $this->fetchData($filteredQuery)->each(function ($model) {
foreach ($this->resultModifiers as $modifier) {
$modifier($model);
}
});
return response()->json([
'data' => $data,
@@ -49,7 +68,17 @@ class ListingResponseBuilder
}
/**
* Fetch the data to return in the response.
* Add a callback to modify each element of the results.
*
* @param (callable(Model): void) $modifier
*/
public function modifyResults(callable $modifier): void
{
$this->resultModifiers[] = $modifier;
}
/**
* Fetch the data to return within the response.
*/
protected function fetchData(Builder $query): Collection
{

View File

@@ -28,10 +28,8 @@ class GroupSyncService
*/
protected function externalIdMatchesGroupNames(string $externalId, array $groupNames): bool
{
$externalAuthIds = explode(',', strtolower($externalId));
foreach ($externalAuthIds as $externalAuthId) {
if (in_array(trim($externalAuthId), $groupNames)) {
foreach ($this->parseRoleExternalAuthId($externalId) as $externalAuthId) {
if (in_array($externalAuthId, $groupNames)) {
return true;
}
}
@@ -39,6 +37,18 @@ class GroupSyncService
return false;
}
protected function parseRoleExternalAuthId(string $externalId): array
{
$inputIds = preg_split('/(?<!\\\),/', strtolower($externalId));
$cleanIds = [];
foreach ($inputIds as $inputId) {
$cleanIds[] = str_replace('\,', ',', trim($inputId));
}
return $cleanIds;
}
/**
* Match an array of group names to BookStack system roles.
* Formats group names to be lower-case and hyphenated.

View File

@@ -5,6 +5,7 @@ namespace BookStack\Auth\Access\Guards;
use BookStack\Auth\Access\LdapService;
use BookStack\Auth\Access\RegistrationService;
use BookStack\Auth\User;
use BookStack\Exceptions\JsonDebugException;
use BookStack\Exceptions\LdapException;
use BookStack\Exceptions\LoginAttemptEmailNeededException;
use BookStack\Exceptions\LoginAttemptException;
@@ -15,7 +16,7 @@ use Illuminate\Support\Str;
class LdapSessionGuard extends ExternalBaseSessionGuard
{
protected $ldapService;
protected LdapService $ldapService;
/**
* LdapSessionGuard constructor.
@@ -59,8 +60,9 @@ class LdapSessionGuard extends ExternalBaseSessionGuard
* @param array $credentials
* @param bool $remember
*
* @throws LdapException*@throws \BookStack\Exceptions\JsonDebugException
* @throws LoginAttemptException
* @throws LdapException
* @throws JsonDebugException
*
* @return bool
*/
@@ -84,7 +86,7 @@ class LdapSessionGuard extends ExternalBaseSessionGuard
try {
$user = $this->createNewFromLdapAndCreds($userDetails, $credentials);
} catch (UserRegistrationException $exception) {
throw new LoginAttemptException($exception->message);
throw new LoginAttemptException($exception->getMessage());
}
}

View File

@@ -15,12 +15,17 @@ use Illuminate\Support\Facades\Log;
*/
class LdapService
{
protected $ldap;
protected $groupSyncService;
protected Ldap $ldap;
protected GroupSyncService $groupSyncService;
protected UserAvatars $userAvatars;
/**
* @var resource
*/
protected $ldapConnection;
protected $userAvatars;
protected $config;
protected $enabled;
protected array $config;
protected bool $enabled;
/**
* LdapService constructor.
@@ -100,7 +105,7 @@ class LdapService
'name' => $this->getUserResponseProperty($user, $displayNameAttr, $userCn),
'dn' => $user['dn'],
'email' => $this->getUserResponseProperty($user, $emailAttr, null),
'avatar'=> $thumbnailAttr ? $this->getUserResponseProperty($user, $thumbnailAttr, null) : null,
'avatar' => $thumbnailAttr ? $this->getUserResponseProperty($user, $thumbnailAttr, null) : null,
];
if ($this->config['dump_user_details']) {
@@ -274,6 +279,7 @@ class LdapService
* Get the groups a user is a part of on ldap.
*
* @throws LdapException
* @throws JsonDebugException
*/
public function getUserGroups(string $userName): array
{
@@ -285,8 +291,17 @@ class LdapService
}
$userGroups = $this->groupFilter($user);
$allGroups = $this->getGroupsRecursive($userGroups, []);
return $this->getGroupsRecursive($userGroups, []);
if ($this->config['dump_user_groups']) {
throw new JsonDebugException([
'details_from_ldap' => $user,
'parsed_direct_user_groups' => $userGroups,
'parsed_recursive_user_groups' => $allGroups,
]);
}
return $allGroups;
}
/**
@@ -369,6 +384,7 @@ class LdapService
* Sync the LDAP groups to the user roles for the current user.
*
* @throws LdapException
* @throws JsonDebugException
*/
public function syncGroups(User $user, string $username)
{

View File

@@ -5,6 +5,7 @@ namespace BookStack\Auth\Access;
use BookStack\Actions\ActivityType;
use BookStack\Auth\Access\Mfa\MfaSession;
use BookStack\Auth\User;
use BookStack\Exceptions\LoginAttemptException;
use BookStack\Exceptions\StoppedAuthenticationException;
use BookStack\Facades\Activity;
use BookStack\Facades\Theme;
@@ -149,6 +150,7 @@ class LoginService
* May interrupt the flow if extra authentication requirements are imposed.
*
* @throws StoppedAuthenticationException
* @throws LoginAttemptException
*/
public function attempt(array $credentials, string $method, bool $remember = false): bool
{

View File

@@ -0,0 +1,9 @@
<?php
namespace BookStack\Auth\Access\Oidc;
use Exception;
class OidcException extends Exception
{
}

View File

@@ -2,6 +2,8 @@
namespace BookStack\Auth\Access\Oidc;
class OidcIssuerDiscoveryException extends \Exception
use Exception;
class OidcIssuerDiscoveryException extends Exception
{
}

View File

@@ -60,15 +60,17 @@ class OidcJwtSigningKey
*/
protected function loadFromJwkArray(array $jwk)
{
if ($jwk['alg'] !== 'RS256') {
throw new OidcInvalidKeyException("Only RS256 keys are currently supported. Found key using {$jwk['alg']}");
// 'alg' is optional for a JWK, but we will still attempt to validate if
// it exists otherwise presume it will be compatible.
$alg = $jwk['alg'] ?? null;
if ($jwk['kty'] !== 'RSA' || !(is_null($alg) || $alg === 'RS256')) {
throw new OidcInvalidKeyException("Only RS256 keys are currently supported. Found key using {$alg}");
}
if (empty($jwk['use'])) {
throw new OidcInvalidKeyException('A "use" parameter on the provided key is expected');
}
if ($jwk['use'] !== 'sig') {
// 'use' is optional for a JWK but we assume 'sig' where no value exists since that's what
// the OIDC discovery spec infers since 'sig' MUST be set if encryption keys come into play.
$use = $jwk['use'] ?? 'sig';
if ($use !== 'sig') {
throw new OidcInvalidKeyException("Only signature keys are currently supported. Found key for use {$jwk['use']}");
}

View File

@@ -30,6 +30,11 @@ class OidcOAuthProvider extends AbstractProvider
*/
protected $tokenEndpoint;
/**
* Scopes to use for the OIDC authorization call.
*/
protected array $scopes = ['openid', 'profile', 'email'];
/**
* Returns the base URL for authorizing a client.
*/
@@ -54,6 +59,15 @@ class OidcOAuthProvider extends AbstractProvider
return '';
}
/**
* Add an additional scope to this provider upon the default.
*/
public function addScope(string $scope): void
{
$this->scopes[] = $scope;
$this->scopes = array_unique($this->scopes);
}
/**
* Returns the default scopes used by this provider.
*
@@ -62,7 +76,7 @@ class OidcOAuthProvider extends AbstractProvider
*/
protected function getDefaultScopes(): array
{
return ['openid', 'profile', 'email'];
return $this->scopes;
}
/**

View File

@@ -15,40 +15,17 @@ use Psr\Http\Client\ClientInterface;
*/
class OidcProviderSettings
{
/**
* @var string
*/
public $issuer;
/**
* @var string
*/
public $clientId;
/**
* @var string
*/
public $clientSecret;
/**
* @var string
*/
public $redirectUri;
/**
* @var string
*/
public $authorizationEndpoint;
/**
* @var string
*/
public $tokenEndpoint;
public string $issuer;
public string $clientId;
public string $clientSecret;
public ?string $redirectUri;
public ?string $authorizationEndpoint;
public ?string $tokenEndpoint;
/**
* @var string[]|array[]
*/
public $keys = [];
public ?array $keys = [];
public function __construct(array $settings)
{
@@ -164,7 +141,10 @@ class OidcProviderSettings
protected function filterKeys(array $keys): array
{
return array_filter($keys, function (array $key) {
return $key['kty'] === 'RSA' && $key['use'] === 'sig' && $key['alg'] === 'RS256';
$alg = $key['alg'] ?? 'RS256';
$use = $key['use'] ?? 'sig';
return $key['kty'] === 'RSA' && $use === 'sig' && $alg === 'RS256';
});
}

View File

@@ -2,22 +2,18 @@
namespace BookStack\Auth\Access\Oidc;
use function auth;
use BookStack\Auth\Access\GroupSyncService;
use BookStack\Auth\Access\LoginService;
use BookStack\Auth\Access\RegistrationService;
use BookStack\Auth\User;
use BookStack\Exceptions\JsonDebugException;
use BookStack\Exceptions\OpenIdConnectException;
use BookStack\Exceptions\StoppedAuthenticationException;
use BookStack\Exceptions\UserRegistrationException;
use function config;
use Exception;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Cache;
use League\OAuth2\Client\OptionProvider\HttpBasicAuthOptionProvider;
use Psr\Http\Client\ClientExceptionInterface;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use Psr\Http\Client\ClientInterface as HttpClient;
use function trans;
use function url;
/**
* Class OpenIdConnectService
@@ -25,30 +21,37 @@ use function url;
*/
class OidcService
{
protected $registrationService;
protected $loginService;
protected $httpClient;
protected RegistrationService $registrationService;
protected LoginService $loginService;
protected HttpClient $httpClient;
protected GroupSyncService $groupService;
/**
* OpenIdService constructor.
*/
public function __construct(RegistrationService $registrationService, LoginService $loginService, HttpClient $httpClient)
{
public function __construct(
RegistrationService $registrationService,
LoginService $loginService,
HttpClient $httpClient,
GroupSyncService $groupService
) {
$this->registrationService = $registrationService;
$this->loginService = $loginService;
$this->httpClient = $httpClient;
$this->groupService = $groupService;
}
/**
* Initiate an authorization flow.
*
* @throws OidcException
*
* @return array{url: string, state: string}
*/
public function login(): array
{
$settings = $this->getProviderSettings();
$provider = $this->getProvider($settings);
return [
'url' => $provider->getAuthorizationUrl(),
'state' => $provider->getState(),
@@ -57,14 +60,15 @@ class OidcService
/**
* Process the Authorization response from the authorization server and
* return the matching, or new if registration active, user matched to
* the authorization server.
* Returns null if not authenticated.
* return the matching, or new if registration active, user matched to the
* authorization server. Throws if the user cannot be auth if not authenticated.
*
* @throws Exception
* @throws ClientExceptionInterface
* @throws JsonDebugException
* @throws OidcException
* @throws StoppedAuthenticationException
* @throws IdentityProviderException
*/
public function processAuthorizeResponse(?string $authorizationCode): ?User
public function processAuthorizeResponse(?string $authorizationCode): User
{
$settings = $this->getProviderSettings();
$provider = $this->getProvider($settings);
@@ -78,8 +82,7 @@ class OidcService
}
/**
* @throws OidcIssuerDiscoveryException
* @throws ClientExceptionInterface
* @throws OidcException
*/
protected function getProviderSettings(): OidcProviderSettings
{
@@ -100,7 +103,11 @@ class OidcService
// Run discovery
if ($config['discover'] ?? false) {
$settings->discoverFromIssuer($this->httpClient, Cache::store(null), 15);
try {
$settings->discoverFromIssuer($this->httpClient, Cache::store(null), 15);
} catch (OidcIssuerDiscoveryException $exception) {
throw new OidcException('OIDC Discovery Error: ' . $exception->getMessage());
}
}
$settings->validate();
@@ -113,10 +120,31 @@ class OidcService
*/
protected function getProvider(OidcProviderSettings $settings): OidcOAuthProvider
{
return new OidcOAuthProvider($settings->arrayForProvider(), [
$provider = new OidcOAuthProvider($settings->arrayForProvider(), [
'httpClient' => $this->httpClient,
'optionProvider' => new HttpBasicAuthOptionProvider(),
]);
foreach ($this->getAdditionalScopes() as $scope) {
$provider->addScope($scope);
}
return $provider;
}
/**
* Get any user-defined addition/custom scopes to apply to the authentication request.
*
* @return string[]
*/
protected function getAdditionalScopes(): array
{
$scopeConfig = $this->config()['additional_scopes'] ?: '';
$scopeArr = explode(',', $scopeConfig);
$scopeArr = array_map(fn (string $scope) => trim($scope), $scopeArr);
return array_filter($scopeArr);
}
/**
@@ -141,19 +169,43 @@ class OidcService
return implode(' ', $displayName);
}
/**
* Extract the assigned groups from the id token.
*
* @return string[]
*/
protected function getUserGroups(OidcIdToken $token): array
{
$groupsAttr = $this->config()['groups_claim'];
if (empty($groupsAttr)) {
return [];
}
$groupsList = Arr::get($token->getAllClaims(), $groupsAttr);
if (!is_array($groupsList)) {
return [];
}
return array_values(array_filter($groupsList, function ($val) {
return is_string($val);
}));
}
/**
* Extract the details of a user from an ID token.
*
* @return array{name: string, email: string, external_id: string}
* @return array{name: string, email: string, external_id: string, groups: string[]}
*/
protected function getUserDetails(OidcIdToken $token): array
{
$id = $token->getClaim('sub');
$idClaim = $this->config()['external_id_claim'];
$id = $token->getClaim($idClaim);
return [
'external_id' => $id,
'email' => $token->getClaim('email'),
'name' => $this->getUserDisplayName($token, $id),
'groups' => $this->getUserGroups($token),
];
}
@@ -161,9 +213,8 @@ class OidcService
* Processes a received access token for a user. Login the user when
* they exist, optionally registering them automatically.
*
* @throws OpenIdConnectException
* @throws OidcException
* @throws JsonDebugException
* @throws UserRegistrationException
* @throws StoppedAuthenticationException
*/
protected function processAccessTokenCallback(OidcAccessToken $accessToken, OidcProviderSettings $settings): User
@@ -182,28 +233,34 @@ class OidcService
try {
$idToken->validate($settings->clientId);
} catch (OidcInvalidTokenException $exception) {
throw new OpenIdConnectException("ID token validate failed with error: {$exception->getMessage()}");
throw new OidcException("ID token validate failed with error: {$exception->getMessage()}");
}
$userDetails = $this->getUserDetails($idToken);
$isLoggedIn = auth()->check();
if (empty($userDetails['email'])) {
throw new OpenIdConnectException(trans('errors.oidc_no_email_address'));
throw new OidcException(trans('errors.oidc_no_email_address'));
}
if ($isLoggedIn) {
throw new OpenIdConnectException(trans('errors.oidc_already_logged_in'), '/login');
throw new OidcException(trans('errors.oidc_already_logged_in'));
}
$user = $this->registrationService->findOrRegister(
$userDetails['name'],
$userDetails['email'],
$userDetails['external_id']
);
try {
$user = $this->registrationService->findOrRegister(
$userDetails['name'],
$userDetails['email'],
$userDetails['external_id']
);
} catch (UserRegistrationException $exception) {
throw new OidcException($exception->getMessage());
}
if ($user === null) {
throw new OpenIdConnectException(trans('errors.oidc_user_not_registered', ['name' => $userDetails['external_id']]), '/login');
if ($this->shouldSyncGroups()) {
$groups = $userDetails['groups'];
$detachExisting = $this->config()['remove_from_groups'];
$this->groupService->syncUserWithFoundGroups($user, $groups, $detachExisting);
}
$this->loginService->login($user, 'oidc');
@@ -218,4 +275,12 @@ class OidcService
{
return config('oidc');
}
/**
* Check if groups should be synced.
*/
protected function shouldSyncGroups(): bool
{
return $this->config()['user_to_groups'] !== false;
}
}

View File

@@ -96,7 +96,8 @@ class RegistrationService
}
// Create the user
$newUser = $this->userRepo->registerNew($userData, $emailConfirmed);
$newUser = $this->userRepo->createWithoutActivity($userData, $emailConfirmed);
$newUser->attachDefaultRole();
// Assign social account if given
if ($socialAccount) {

View File

@@ -20,14 +20,11 @@ use OneLogin\Saml2\ValidationError;
*/
class Saml2Service
{
protected $config;
protected $registrationService;
protected $loginService;
protected $groupSyncService;
protected array $config;
protected RegistrationService $registrationService;
protected LoginService $loginService;
protected GroupSyncService $groupSyncService;
/**
* Saml2Service constructor.
*/
public function __construct(
RegistrationService $registrationService,
LoginService $loginService,
@@ -109,9 +106,10 @@ class Saml2Service
$errors = $toolkit->getErrors();
if (!empty($errors)) {
throw new Error(
'Invalid ACS Response: ' . implode(', ', $errors)
);
$reason = $toolkit->getLastErrorReason();
$message = 'Invalid ACS Response; Errors: ' . implode(', ', $errors);
$message .= $reason ? "; Reason: {$reason}" : '';
throw new Error($message);
}
if (!$toolkit->isAuthenticated()) {
@@ -168,7 +166,7 @@ class Saml2Service
*/
public function metadata(): string
{
$toolKit = $this->getToolkit();
$toolKit = $this->getToolkit(true);
$settings = $toolKit->getSettings();
$metadata = $settings->getSPMetadata();
$errors = $settings->validateMetadata($metadata);
@@ -189,7 +187,7 @@ class Saml2Service
* @throws Error
* @throws Exception
*/
protected function getToolkit(): Auth
protected function getToolkit(bool $spOnly = false): Auth
{
$settings = $this->config['onelogin'];
$overrides = $this->config['onelogin_overrides'] ?? [];
@@ -199,14 +197,14 @@ class Saml2Service
}
$metaDataSettings = [];
if ($this->config['autoload_from_metadata']) {
if (!$spOnly && $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);
return new Auth($settings, $spOnly);
}
/**

View File

@@ -2,20 +2,41 @@
namespace BookStack\Auth\Permissions;
use BookStack\Auth\Role;
use BookStack\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;
/**
* @property int $id
* @property int $role_id
* @property int $entity_id
* @property string $entity_type
* @property boolean $view
* @property boolean $create
* @property boolean $update
* @property boolean $delete
*/
class EntityPermission extends Model
{
protected $fillable = ['role_id', 'action'];
public const PERMISSIONS = ['view', 'create', 'update', 'delete'];
protected $fillable = ['role_id', 'view', 'create', 'update', 'delete'];
public $timestamps = false;
/**
* Get all this restriction's attached entity.
*
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
* Get this restriction's attached entity.
*/
public function restrictable()
public function restrictable(): MorphTo
{
return $this->morphTo('restrictable');
}
/**
* Get the role assigned to this entity permission.
*/
public function role(): BelongsTo
{
return $this->belongsTo(Role::class);
}
}

View File

@@ -0,0 +1,141 @@
<?php
namespace BookStack\Auth\Permissions;
use BookStack\Auth\Role;
use BookStack\Entities\Models\Entity;
use Illuminate\Database\Eloquent\Builder;
class EntityPermissionEvaluator
{
protected string $action;
public function __construct(string $action)
{
$this->action = $action;
}
public function evaluateEntityForUser(Entity $entity, array $userRoleIds): ?bool
{
if ($this->isUserSystemAdmin($userRoleIds)) {
return true;
}
$typeIdChain = $this->gatherEntityChainTypeIds(SimpleEntityData::fromEntity($entity));
$relevantPermissions = $this->getPermissionsMapByTypeId($typeIdChain, [...$userRoleIds, 0]);
$permitsByType = $this->collapseAndCategorisePermissions($typeIdChain, $relevantPermissions);
$status = $this->evaluatePermitsByType($permitsByType);
return is_null($status) ? null : $status === PermissionStatus::IMPLICIT_ALLOW || $status === PermissionStatus::EXPLICIT_ALLOW;
}
/**
* @param array<string, array<string, int>> $permitsByType
*/
protected function evaluatePermitsByType(array $permitsByType): ?int
{
// Return grant or reject from role-level if exists
if (count($permitsByType['role']) > 0) {
return max($permitsByType['role']) ? PermissionStatus::EXPLICIT_ALLOW : PermissionStatus::EXPLICIT_DENY;
}
// Return fallback permission if exists
if (count($permitsByType['fallback']) > 0) {
return $permitsByType['fallback'][0] ? PermissionStatus::IMPLICIT_ALLOW : PermissionStatus::IMPLICIT_DENY;
}
return null;
}
/**
* @param string[] $typeIdChain
* @param array<string, EntityPermission[]> $permissionMapByTypeId
* @return array<string, array<string, int>>
*/
protected function collapseAndCategorisePermissions(array $typeIdChain, array $permissionMapByTypeId): array
{
$permitsByType = ['fallback' => [], 'role' => []];
foreach ($typeIdChain as $typeId) {
$permissions = $permissionMapByTypeId[$typeId] ?? [];
foreach ($permissions as $permission) {
$roleId = $permission->role_id;
$type = $roleId === 0 ? 'fallback' : 'role';
if (!isset($permitsByType[$type][$roleId])) {
$permitsByType[$type][$roleId] = $permission->{$this->action};
}
}
if (isset($permitsByType['fallback'][0])) {
break;
}
}
return $permitsByType;
}
/**
* @param string[] $typeIdChain
* @return array<string, EntityPermission[]>
*/
protected function getPermissionsMapByTypeId(array $typeIdChain, array $filterRoleIds): array
{
$query = EntityPermission::query()->where(function (Builder $query) use ($typeIdChain) {
foreach ($typeIdChain as $typeId) {
$query->orWhere(function (Builder $query) use ($typeId) {
[$type, $id] = explode(':', $typeId);
$query->where('entity_type', '=', $type)
->where('entity_id', '=', $id);
});
}
});
if (!empty($filterRoleIds)) {
$query->where(function (Builder $query) use ($filterRoleIds) {
$query->whereIn('role_id', [...$filterRoleIds, 0]);
});
}
$relevantPermissions = $query->get(['entity_id', 'entity_type', 'role_id', $this->action])->all();
$map = [];
foreach ($relevantPermissions as $permission) {
$key = $permission->entity_type . ':' . $permission->entity_id;
if (!isset($map[$key])) {
$map[$key] = [];
}
$map[$key][] = $permission;
}
return $map;
}
/**
* @return string[]
*/
protected function gatherEntityChainTypeIds(SimpleEntityData $entity): array
{
// The array order here is very important due to the fact we walk up the chain
// elsewhere in the class. Earlier items in the chain have higher priority.
$chain = [$entity->type . ':' . $entity->id];
if ($entity->type === 'page' && $entity->chapter_id) {
$chain[] = 'chapter:' . $entity->chapter_id;
}
if ($entity->type === 'page' || $entity->type === 'chapter') {
$chain[] = 'book:' . $entity->book_id;
}
return $chain;
}
protected function isUserSystemAdmin($userRoleIds): bool
{
$adminRoleId = Role::getSystemRole('admin')->id;
return in_array($adminRoleId, $userRoleIds);
}
}

View File

@@ -0,0 +1,292 @@
<?php
namespace BookStack\Auth\Permissions;
use BookStack\Auth\Role;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\BookChild;
use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\Page;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Support\Facades\DB;
/**
* Joint permissions provide a pre-query "cached" table of view permissions for all core entity
* types for all roles in the system. This class generates out that table for different scenarios.
*/
class JointPermissionBuilder
{
/**
* Re-generate all entity permission from scratch.
*/
public function rebuildForAll()
{
JointPermission::query()->truncate();
// Get all roles (Should be the most limited dimension)
$roles = Role::query()->with('permissions')->get()->all();
// Chunk through all books
$this->bookFetchQuery()->chunk(5, function (EloquentCollection $books) use ($roles) {
$this->buildJointPermissionsForBooks($books, $roles);
});
// Chunk through all bookshelves
Bookshelf::query()->withTrashed()->select(['id', 'owned_by'])
->chunk(50, function (EloquentCollection $shelves) use ($roles) {
$this->createManyJointPermissions($shelves->all(), $roles);
});
}
/**
* Rebuild the entity jointPermissions for a particular entity.
*/
public function rebuildForEntity(Entity $entity)
{
$entities = [$entity];
if ($entity instanceof Book) {
$books = $this->bookFetchQuery()->where('id', '=', $entity->id)->get();
$this->buildJointPermissionsForBooks($books, Role::query()->with('permissions')->get()->all(), true);
return;
}
/** @var BookChild $entity */
if ($entity->book) {
$entities[] = $entity->book;
}
if ($entity instanceof Page && $entity->chapter_id) {
$entities[] = $entity->chapter;
}
if ($entity instanceof Chapter) {
foreach ($entity->pages as $page) {
$entities[] = $page;
}
}
$this->buildJointPermissionsForEntities($entities);
}
/**
* Build the entity jointPermissions for a particular role.
*/
public function rebuildForRole(Role $role)
{
$roles = [$role];
$role->jointPermissions()->delete();
$role->load('permissions');
// Chunk through all books
$this->bookFetchQuery()->chunk(20, function ($books) use ($roles) {
$this->buildJointPermissionsForBooks($books, $roles);
});
// Chunk through all bookshelves
Bookshelf::query()->select(['id', 'owned_by'])
->chunk(50, function ($shelves) use ($roles) {
$this->createManyJointPermissions($shelves->all(), $roles);
});
}
/**
* Get a query for fetching a book with its children.
*/
protected function bookFetchQuery(): Builder
{
return Book::query()->withTrashed()
->select(['id', 'owned_by'])->with([
'chapters' => function ($query) {
$query->withTrashed()->select(['id', 'owned_by', 'book_id']);
},
'pages' => function ($query) {
$query->withTrashed()->select(['id', 'owned_by', 'book_id', 'chapter_id']);
},
]);
}
/**
* Build joint permissions for the given book and role combinations.
*/
protected function buildJointPermissionsForBooks(EloquentCollection $books, array $roles, bool $deleteOld = false)
{
$entities = clone $books;
/** @var Book $book */
foreach ($books->all() as $book) {
foreach ($book->getRelation('chapters') as $chapter) {
$entities->push($chapter);
}
foreach ($book->getRelation('pages') as $page) {
$entities->push($page);
}
}
if ($deleteOld) {
$this->deleteManyJointPermissionsForEntities($entities->all());
}
$this->createManyJointPermissions($entities->all(), $roles);
}
/**
* Rebuild the entity jointPermissions for a collection of entities.
*/
protected function buildJointPermissionsForEntities(array $entities)
{
$roles = Role::query()->get()->values()->all();
$this->deleteManyJointPermissionsForEntities($entities);
$this->createManyJointPermissions($entities, $roles);
}
/**
* Delete all the entity jointPermissions for a list of entities.
*
* @param Entity[] $entities
*/
protected function deleteManyJointPermissionsForEntities(array $entities)
{
$simpleEntities = $this->entitiesToSimpleEntities($entities);
$idsByType = $this->entitiesToTypeIdMap($simpleEntities);
DB::transaction(function () use ($idsByType) {
foreach ($idsByType as $type => $ids) {
foreach (array_chunk($ids, 1000) as $idChunk) {
DB::table('joint_permissions')
->where('entity_type', '=', $type)
->whereIn('entity_id', $idChunk)
->delete();
}
}
});
}
/**
* @param Entity[] $entities
*
* @return SimpleEntityData[]
*/
protected function entitiesToSimpleEntities(array $entities): array
{
$simpleEntities = [];
foreach ($entities as $entity) {
$simple = SimpleEntityData::fromEntity($entity);
$simpleEntities[] = $simple;
}
return $simpleEntities;
}
/**
* Create & Save entity jointPermissions for many entities and roles.
*
* @param Entity[] $originalEntities
* @param Role[] $roles
*/
protected function createManyJointPermissions(array $originalEntities, array $roles)
{
$entities = $this->entitiesToSimpleEntities($originalEntities);
$jointPermissions = [];
// Fetch related entity permissions
$permissions = new MassEntityPermissionEvaluator($entities, 'view');
// Create a mapping of role permissions
$rolePermissionMap = [];
foreach ($roles as $role) {
foreach ($role->permissions as $permission) {
$rolePermissionMap[$role->getRawAttribute('id') . ':' . $permission->getRawAttribute('name')] = true;
}
}
// Create Joint Permission Data
foreach ($entities as $entity) {
foreach ($roles as $role) {
$jp = $this->createJointPermissionData(
$entity,
$role->getRawAttribute('id'),
$permissions,
$rolePermissionMap,
$role->system_name === 'admin'
);
$jointPermissions[] = $jp;
}
}
DB::transaction(function () use ($jointPermissions) {
foreach (array_chunk($jointPermissions, 1000) as $jointPermissionChunk) {
DB::table('joint_permissions')->insert($jointPermissionChunk);
}
});
}
/**
* From the given entity list, provide back a mapping of entity types to
* the ids of that given type. The type used is the DB morph class.
*
* @param SimpleEntityData[] $entities
*
* @return array<string, int[]>
*/
protected function entitiesToTypeIdMap(array $entities): array
{
$idsByType = [];
foreach ($entities as $entity) {
if (!isset($idsByType[$entity->type])) {
$idsByType[$entity->type] = [];
}
$idsByType[$entity->type][] = $entity->id;
}
return $idsByType;
}
/**
* Create entity permission data for an entity and role
* for a particular action.
*/
protected function createJointPermissionData(SimpleEntityData $entity, int $roleId, MassEntityPermissionEvaluator $permissionMap, array $rolePermissionMap, bool $isAdminRole): array
{
// Ensure system admin role retains permissions
if ($isAdminRole) {
return $this->createJointPermissionDataArray($entity, $roleId, PermissionStatus::EXPLICIT_ALLOW, true);
}
// Return evaluated entity permission status if it has an affect.
$entityPermissionStatus = $permissionMap->evaluateEntityForRole($entity, $roleId);
if ($entityPermissionStatus !== null) {
return $this->createJointPermissionDataArray($entity, $roleId, $entityPermissionStatus, false);
}
// Otherwise default to the role-level permissions
$permissionPrefix = $entity->type . '-view';
$roleHasPermission = isset($rolePermissionMap[$roleId . ':' . $permissionPrefix . '-all']);
$roleHasPermissionOwn = isset($rolePermissionMap[$roleId . ':' . $permissionPrefix . '-own']);
$status = $roleHasPermission ? PermissionStatus::IMPLICIT_ALLOW : PermissionStatus::IMPLICIT_DENY;
return $this->createJointPermissionDataArray($entity, $roleId, $status, $roleHasPermissionOwn);
}
/**
* Create an array of data with the information of an entity jointPermissions.
* Used to build data for bulk insertion.
*/
protected function createJointPermissionDataArray(SimpleEntityData $entity, int $roleId, int $permissionStatus, bool $hasPermissionOwn): array
{
$ownPermissionActive = ($hasPermissionOwn && $permissionStatus !== PermissionStatus::EXPLICIT_DENY && $entity->owned_by);
return [
'entity_id' => $entity->id,
'entity_type' => $entity->type,
'role_id' => $roleId,
'status' => $permissionStatus,
'owner_id' => $ownPermissionActive ? $entity->owned_by : null,
];
}
}

View File

@@ -0,0 +1,81 @@
<?php
namespace BookStack\Auth\Permissions;
class MassEntityPermissionEvaluator extends EntityPermissionEvaluator
{
/**
* @var SimpleEntityData[]
*/
protected array $entitiesInvolved;
protected array $permissionMapCache;
public function __construct(array $entitiesInvolved, string $action)
{
$this->entitiesInvolved = $entitiesInvolved;
parent::__construct($action);
}
public function evaluateEntityForRole(SimpleEntityData $entity, int $roleId): ?int
{
$typeIdChain = $this->gatherEntityChainTypeIds($entity);
$relevantPermissions = $this->getPermissionMapByTypeIdForChainAndRole($typeIdChain, $roleId);
$permitsByType = $this->collapseAndCategorisePermissions($typeIdChain, $relevantPermissions);
return $this->evaluatePermitsByType($permitsByType);
}
/**
* @param string[] $typeIdChain
* @return array<string, EntityPermission[]>
*/
protected function getPermissionMapByTypeIdForChainAndRole(array $typeIdChain, int $roleId): array
{
$allPermissions = $this->getPermissionMapByTypeIdAndRoleForAllInvolved();
$relevantPermissions = [];
// Filter down permissions to just those for current typeId
// and current roleID or fallback permissions.
foreach ($typeIdChain as $typeId) {
$relevantPermissions[$typeId] = [
...($allPermissions[$typeId][$roleId] ?? []),
...($allPermissions[$typeId][0] ?? [])
];
}
return $relevantPermissions;
}
/**
* @return array<string, array<int, EntityPermission[]>>
*/
protected function getPermissionMapByTypeIdAndRoleForAllInvolved(): array
{
if (isset($this->permissionMapCache)) {
return $this->permissionMapCache;
}
$entityTypeIdChain = [];
foreach ($this->entitiesInvolved as $entity) {
$entityTypeIdChain[] = $entity->type . ':' . $entity->id;
}
$permissionMap = $this->getPermissionsMapByTypeId($entityTypeIdChain, []);
// Manipulate permission map to also be keyed by roleId.
foreach ($permissionMap as $typeId => $permissions) {
$permissionMap[$typeId] = [];
foreach ($permissions as $permission) {
$roleId = $permission->getRawAttribute('role_id');
if (!isset($permissionMap[$typeId][$roleId])) {
$permissionMap[$typeId][$roleId] = [];
}
$permissionMap[$typeId][$roleId][] = $permission;
}
}
$this->permissionMapCache = $permissionMap;
return $this->permissionMapCache;
}
}

View File

@@ -0,0 +1,203 @@
<?php
namespace BookStack\Auth\Permissions;
use BookStack\Auth\Role;
use BookStack\Auth\User;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\Page;
use BookStack\Model;
use BookStack\Traits\HasCreatorAndUpdater;
use BookStack\Traits\HasOwner;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\Builder as QueryBuilder;
use InvalidArgumentException;
class PermissionApplicator
{
/**
* Checks if an entity has a restriction set upon it.
*
* @param HasCreatorAndUpdater|HasOwner $ownable
*/
public function checkOwnableUserAccess(Model $ownable, string $permission): bool
{
$explodedPermission = explode('-', $permission);
$action = $explodedPermission[1] ?? $explodedPermission[0];
$fullPermission = count($explodedPermission) > 1 ? $permission : $ownable->getMorphClass() . '-' . $permission;
$user = $this->currentUser();
$userRoleIds = $this->getCurrentUserRoleIds();
$allRolePermission = $user->can($fullPermission . '-all');
$ownRolePermission = $user->can($fullPermission . '-own');
$nonJointPermissions = ['restrictions', 'image', 'attachment', 'comment'];
$ownerField = ($ownable instanceof Entity) ? 'owned_by' : 'created_by';
$ownableFieldVal = $ownable->getAttribute($ownerField);
if (is_null($ownableFieldVal)) {
throw new InvalidArgumentException("{$ownerField} field used but has not been loaded");
}
$isOwner = $user->id === $ownableFieldVal;
$hasRolePermission = $allRolePermission || ($isOwner && $ownRolePermission);
// Handle non entity specific jointPermissions
if (in_array($explodedPermission[0], $nonJointPermissions)) {
return $hasRolePermission;
}
$hasApplicableEntityPermissions = $this->hasEntityPermission($ownable, $userRoleIds, $action);
return is_null($hasApplicableEntityPermissions) ? $hasRolePermission : $hasApplicableEntityPermissions;
}
/**
* Check if there are permissions that are applicable for the given entity item, action and roles.
* Returns null when no entity permissions are in force.
*/
protected function hasEntityPermission(Entity $entity, array $userRoleIds, string $action): ?bool
{
$this->ensureValidEntityAction($action);
return (new EntityPermissionEvaluator($action))->evaluateEntityForUser($entity, $userRoleIds);
}
/**
* 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.
*/
public function checkUserHasEntityPermissionOnAny(string $action, string $entityClass = ''): bool
{
$this->ensureValidEntityAction($action);
$permissionQuery = EntityPermission::query()
->where($action, '=', true)
->whereIn('role_id', $this->getCurrentUserRoleIds());
if (!empty($entityClass)) {
/** @var Entity $entityInstance */
$entityInstance = app()->make($entityClass);
$permissionQuery = $permissionQuery->where('entity_type', '=', $entityInstance->getMorphClass());
}
$hasPermission = $permissionQuery->count() > 0;
return $hasPermission;
}
/**
* Limit the given entity query so that the query will only
* return items that the user has view permission for.
*/
public function restrictEntityQuery(Builder $query): Builder
{
return $query->where(function (Builder $parentQuery) {
$parentQuery->whereHas('jointPermissions', function (Builder $permissionQuery) {
$permissionQuery->select(['entity_id', 'entity_type'])
->selectRaw('max(owner_id) as owner_id')
->selectRaw('max(status) as status')
->whereIn('role_id', $this->getCurrentUserRoleIds())
->groupBy(['entity_type', 'entity_id'])
->havingRaw('(status IN (1, 3) or (owner_id = ? and status != 2))', [$this->currentUser()->id]);
});
});
}
/**
* Extend the given page query to ensure draft items are not visible
* unless created by the given user.
*/
public function restrictDraftsOnPageQuery(Builder $query): Builder
{
return $query->where(function (Builder $query) {
$query->where('draft', '=', false)
->orWhere(function (Builder $query) {
$query->where('draft', '=', true)
->where('owned_by', '=', $this->currentUser()->id);
});
});
}
/**
* Filter items that have entities set as a polymorphic relation.
* For simplicity, this will not return results attached to draft pages.
* Draft pages should never really have related items though.
*/
public function restrictEntityRelationQuery(Builder $query, string $tableName, string $entityIdColumn, string $entityTypeColumn): Builder
{
$tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn, 'entityTypeColumn' => $entityTypeColumn];
$pageMorphClass = (new Page())->getMorphClass();
return $this->restrictEntityQuery($query)
->where(function ($query) use ($tableDetails, $pageMorphClass) {
/** @var Builder $query */
$query->where($tableDetails['entityTypeColumn'], '!=', $pageMorphClass)
->orWhereExists(function (QueryBuilder $query) use ($tableDetails, $pageMorphClass) {
$query->select('id')->from('pages')
->whereColumn('pages.id', '=', $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
->where($tableDetails['tableName'] . '.' . $tableDetails['entityTypeColumn'], '=', $pageMorphClass)
->where('pages.draft', '=', false);
});
});
}
/**
* Add conditions to a query for a model that's a relation of a page, so only the model results
* on visible pages are returned by the query.
* Is effectively the same as "restrictEntityRelationQuery" but takes into account page drafts
* while not expecting a polymorphic relation, Just a simpler one-page-to-many-relations set-up.
*/
public function restrictPageRelationQuery(Builder $query, string $tableName, string $pageIdColumn): Builder
{
$fullPageIdColumn = $tableName . '.' . $pageIdColumn;
return $this->restrictEntityQuery($query)
->where(function ($query) use ($fullPageIdColumn) {
/** @var Builder $query */
$query->whereExists(function (QueryBuilder $query) use ($fullPageIdColumn) {
$query->select('id')->from('pages')
->whereColumn('pages.id', '=', $fullPageIdColumn)
->where('pages.draft', '=', false);
})->orWhereExists(function (QueryBuilder $query) use ($fullPageIdColumn) {
$query->select('id')->from('pages')
->whereColumn('pages.id', '=', $fullPageIdColumn)
->where('pages.draft', '=', true)
->where('pages.created_by', '=', $this->currentUser()->id);
});
});
}
/**
* Get the current user.
*/
protected function currentUser(): User
{
return user();
}
/**
* Get the roles for the current logged-in user.
*
* @return int[]
*/
protected function getCurrentUserRoleIds(): array
{
if (auth()->guest()) {
return [Role::getSystemRole('public')->id];
}
return $this->currentUser()->roles->pluck('id')->values()->all();
}
/**
* Ensure the given action is a valid and expected entity action.
* Throws an exception if invalid otherwise does nothing.
* @throws InvalidArgumentException
*/
protected function ensureValidEntityAction(string $action): void
{
if (!in_array($action, EntityPermission::PERMISSIONS)) {
throw new InvalidArgumentException('Action should be a simple entity permission action, not a role permission');
}
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace BookStack\Auth\Permissions;
use BookStack\Auth\Role;
use BookStack\Entities\Models\Entity;
class PermissionFormData
{
protected Entity $entity;
public function __construct(Entity $entity)
{
$this->entity = $entity;
}
/**
* Get the permissions with assigned roles.
*/
public function permissionsWithRoles(): array
{
return $this->entity->permissions()
->with('role')
->where('role_id', '!=', 0)
->get()
->sortBy('role.display_name')
->all();
}
/**
* Get the roles that don't yet have specific permissions for the
* entity we're managing permissions for.
*/
public function rolesNotAssigned(): array
{
$assigned = $this->entity->permissions()->pluck('role_id');
return Role::query()
->where('system_name', '!=', 'admin')
->whereNotIn('id', $assigned)
->orderBy('display_name', 'asc')
->get()
->all();
}
/**
* Get the entity permission for the "Everyone Else" option.
*/
public function everyoneElseEntityPermission(): EntityPermission
{
/** @var ?EntityPermission $permission */
$permission = $this->entity->permissions()
->where('role_id', '=', 0)
->first();
return $permission ?? (new EntityPermission());
}
/**
* Get the "Everyone Else" role entry.
*/
public function everyoneElseRole(): Role
{
return (new Role())->forceFill([
'id' => 0,
'display_name' => trans('entities.permissions_role_everyone_else'),
'description' => trans('entities.permissions_role_everyone_else_desc'),
]);
}
}

View File

@@ -1,695 +0,0 @@
<?php
namespace BookStack\Auth\Permissions;
use BookStack\Auth\Role;
use BookStack\Auth\User;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\BookChild;
use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\Page;
use BookStack\Model;
use BookStack\Traits\HasCreatorAndUpdater;
use BookStack\Traits\HasOwner;
use Illuminate\Database\Connection;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Database\Query\Builder as QueryBuilder;
use Throwable;
class PermissionService
{
/**
* @var ?array
*/
protected $userRoles = null;
/**
* @var ?User
*/
protected $currentUserModel = null;
/**
* @var Connection
*/
protected $db;
/**
* @var array
*/
protected $entityCache;
/**
* PermissionService constructor.
*/
public function __construct(Connection $db)
{
$this->db = $db;
}
/**
* Set the database connection.
*/
public function setConnection(Connection $connection)
{
$this->db = $connection;
}
/**
* Prepare the local entity cache and ensure it's empty.
*
* @param Entity[] $entities
*/
protected function readyEntityCache(array $entities = [])
{
$this->entityCache = [];
foreach ($entities as $entity) {
$class = get_class($entity);
if (!isset($this->entityCache[$class])) {
$this->entityCache[$class] = collect();
}
$this->entityCache[$class]->put($entity->id, $entity);
}
}
/**
* Get a book via ID, Checks local cache.
*/
protected function getBook(int $bookId): ?Book
{
if (isset($this->entityCache[Book::class]) && $this->entityCache[Book::class]->has($bookId)) {
return $this->entityCache[Book::class]->get($bookId);
}
return Book::query()->withTrashed()->find($bookId);
}
/**
* Get a chapter via ID, Checks local cache.
*/
protected function getChapter(int $chapterId): ?Chapter
{
if (isset($this->entityCache[Chapter::class]) && $this->entityCache[Chapter::class]->has($chapterId)) {
return $this->entityCache[Chapter::class]->get($chapterId);
}
return Chapter::query()
->withTrashed()
->find($chapterId);
}
/**
* Get the roles for the current logged in user.
*/
protected function getCurrentUserRoles(): array
{
if (!is_null($this->userRoles)) {
return $this->userRoles;
}
if (auth()->guest()) {
$this->userRoles = [Role::getSystemRole('public')->id];
} else {
$this->userRoles = $this->currentUser()->roles->pluck('id')->values()->all();
}
return $this->userRoles;
}
/**
* Re-generate all entity permission from scratch.
*/
public function buildJointPermissions()
{
JointPermission::query()->truncate();
$this->readyEntityCache();
// Get all roles (Should be the most limited dimension)
$roles = Role::query()->with('permissions')->get()->all();
// Chunk through all books
$this->bookFetchQuery()->chunk(5, function (EloquentCollection $books) use ($roles) {
$this->buildJointPermissionsForBooks($books, $roles);
});
// Chunk through all bookshelves
Bookshelf::query()->withTrashed()->select(['id', 'restricted', 'owned_by'])
->chunk(50, function (EloquentCollection $shelves) use ($roles) {
$this->buildJointPermissionsForShelves($shelves, $roles);
});
}
/**
* Get a query for fetching a book with it's children.
*/
protected function bookFetchQuery(): Builder
{
return Book::query()->withTrashed()
->select(['id', 'restricted', 'owned_by'])->with([
'chapters' => function ($query) {
$query->withTrashed()->select(['id', 'restricted', 'owned_by', 'book_id']);
},
'pages' => function ($query) {
$query->withTrashed()->select(['id', 'restricted', 'owned_by', 'book_id', 'chapter_id']);
},
]);
}
/**
* Build joint permissions for the given shelf and role combinations.
*
* @throws Throwable
*/
protected function buildJointPermissionsForShelves(EloquentCollection $shelves, array $roles, bool $deleteOld = false)
{
if ($deleteOld) {
$this->deleteManyJointPermissionsForEntities($shelves->all());
}
$this->createManyJointPermissions($shelves->all(), $roles);
}
/**
* Build joint permissions for the given book and role combinations.
*
* @throws Throwable
*/
protected function buildJointPermissionsForBooks(EloquentCollection $books, array $roles, bool $deleteOld = false)
{
$entities = clone $books;
/** @var Book $book */
foreach ($books->all() as $book) {
foreach ($book->getRelation('chapters') as $chapter) {
$entities->push($chapter);
}
foreach ($book->getRelation('pages') as $page) {
$entities->push($page);
}
}
if ($deleteOld) {
$this->deleteManyJointPermissionsForEntities($entities->all());
}
$this->createManyJointPermissions($entities->all(), $roles);
}
/**
* Rebuild the entity jointPermissions for a particular entity.
*
* @throws Throwable
*/
public function buildJointPermissionsForEntity(Entity $entity)
{
$entities = [$entity];
if ($entity instanceof Book) {
$books = $this->bookFetchQuery()->where('id', '=', $entity->id)->get();
$this->buildJointPermissionsForBooks($books, Role::query()->get()->all(), true);
return;
}
/** @var BookChild $entity */
if ($entity->book) {
$entities[] = $entity->book;
}
if ($entity instanceof Page && $entity->chapter_id) {
$entities[] = $entity->chapter;
}
if ($entity instanceof Chapter) {
foreach ($entity->pages as $page) {
$entities[] = $page;
}
}
$this->buildJointPermissionsForEntities($entities);
}
/**
* Rebuild the entity jointPermissions for a collection of entities.
*
* @throws Throwable
*/
public function buildJointPermissionsForEntities(array $entities)
{
$roles = Role::query()->get()->values()->all();
$this->deleteManyJointPermissionsForEntities($entities);
$this->createManyJointPermissions($entities, $roles);
}
/**
* Build the entity jointPermissions for a particular role.
*/
public function buildJointPermissionForRole(Role $role)
{
$roles = [$role];
$this->deleteManyJointPermissionsForRoles($roles);
// Chunk through all books
$this->bookFetchQuery()->chunk(20, function ($books) use ($roles) {
$this->buildJointPermissionsForBooks($books, $roles);
});
// Chunk through all bookshelves
Bookshelf::query()->select(['id', 'restricted', 'owned_by'])
->chunk(50, function ($shelves) use ($roles) {
$this->buildJointPermissionsForShelves($shelves, $roles);
});
}
/**
* Delete the entity jointPermissions attached to a particular role.
*/
public function deleteJointPermissionsForRole(Role $role)
{
$this->deleteManyJointPermissionsForRoles([$role]);
}
/**
* Delete all of the entity jointPermissions for a list of entities.
*
* @param Role[] $roles
*/
protected function deleteManyJointPermissionsForRoles($roles)
{
$roleIds = array_map(function ($role) {
return $role->id;
}, $roles);
JointPermission::query()->whereIn('role_id', $roleIds)->delete();
}
/**
* Delete the entity jointPermissions for a particular entity.
*
* @param Entity $entity
*
* @throws Throwable
*/
public function deleteJointPermissionsForEntity(Entity $entity)
{
$this->deleteManyJointPermissionsForEntities([$entity]);
}
/**
* Delete all of the entity jointPermissions for a list of entities.
*
* @param Entity[] $entities
*
* @throws Throwable
*/
protected function deleteManyJointPermissionsForEntities(array $entities)
{
if (count($entities) === 0) {
return;
}
$this->db->transaction(function () use ($entities) {
foreach (array_chunk($entities, 1000) as $entityChunk) {
$query = $this->db->table('joint_permissions');
foreach ($entityChunk as $entity) {
$query->orWhere(function (QueryBuilder $query) use ($entity) {
$query->where('entity_id', '=', $entity->id)
->where('entity_type', '=', $entity->getMorphClass());
});
}
$query->delete();
}
});
}
/**
* Create & Save entity jointPermissions for many entities and roles.
*
* @param Entity[] $entities
* @param Role[] $roles
*
* @throws Throwable
*/
protected function createManyJointPermissions(array $entities, array $roles)
{
$this->readyEntityCache($entities);
$jointPermissions = [];
// Fetch Entity Permissions and create a mapping of entity restricted statuses
$entityRestrictedMap = [];
$permissionFetch = EntityPermission::query();
foreach ($entities as $entity) {
$entityRestrictedMap[$entity->getMorphClass() . ':' . $entity->id] = boolval($entity->getRawAttribute('restricted'));
$permissionFetch->orWhere(function ($query) use ($entity) {
$query->where('restrictable_id', '=', $entity->id)->where('restrictable_type', '=', $entity->getMorphClass());
});
}
$permissions = $permissionFetch->get();
// Create a mapping of explicit entity permissions
$permissionMap = [];
foreach ($permissions as $permission) {
$key = $permission->restrictable_type . ':' . $permission->restrictable_id . ':' . $permission->role_id . ':' . $permission->action;
$isRestricted = $entityRestrictedMap[$permission->restrictable_type . ':' . $permission->restrictable_id];
$permissionMap[$key] = $isRestricted;
}
// Create a mapping of role permissions
$rolePermissionMap = [];
foreach ($roles as $role) {
foreach ($role->permissions as $permission) {
$rolePermissionMap[$role->getRawAttribute('id') . ':' . $permission->getRawAttribute('name')] = true;
}
}
// Create Joint Permission Data
foreach ($entities as $entity) {
foreach ($roles as $role) {
foreach ($this->getActions($entity) as $action) {
$jointPermissions[] = $this->createJointPermissionData($entity, $role, $action, $permissionMap, $rolePermissionMap);
}
}
}
$this->db->transaction(function () use ($jointPermissions) {
foreach (array_chunk($jointPermissions, 1000) as $jointPermissionChunk) {
$this->db->table('joint_permissions')->insert($jointPermissionChunk);
}
});
}
/**
* Get the actions related to an entity.
*/
protected function getActions(Entity $entity): array
{
$baseActions = ['view', 'update', 'delete'];
if ($entity instanceof Chapter || $entity instanceof Book) {
$baseActions[] = 'page-create';
}
if ($entity instanceof Book) {
$baseActions[] = 'chapter-create';
}
return $baseActions;
}
/**
* Create entity permission data for an entity and role
* for a particular action.
*/
protected function createJointPermissionData(Entity $entity, Role $role, string $action, array $permissionMap, array $rolePermissionMap): array
{
$permissionPrefix = (strpos($action, '-') === false ? ($entity->getType() . '-') : '') . $action;
$roleHasPermission = isset($rolePermissionMap[$role->getRawAttribute('id') . ':' . $permissionPrefix . '-all']);
$roleHasPermissionOwn = isset($rolePermissionMap[$role->getRawAttribute('id') . ':' . $permissionPrefix . '-own']);
$explodedAction = explode('-', $action);
$restrictionAction = end($explodedAction);
if ($role->system_name === 'admin') {
return $this->createJointPermissionDataArray($entity, $role, $action, true, true);
}
if ($entity->restricted) {
$hasAccess = $this->mapHasActiveRestriction($permissionMap, $entity, $role, $restrictionAction);
return $this->createJointPermissionDataArray($entity, $role, $action, $hasAccess, $hasAccess);
}
if ($entity instanceof Book || $entity instanceof Bookshelf) {
return $this->createJointPermissionDataArray($entity, $role, $action, $roleHasPermission, $roleHasPermissionOwn);
}
// For chapters and pages, Check if explicit permissions are set on the Book.
$book = $this->getBook($entity->book_id);
$hasExplicitAccessToParents = $this->mapHasActiveRestriction($permissionMap, $book, $role, $restrictionAction);
$hasPermissiveAccessToParents = !$book->restricted;
// For pages with a chapter, Check if explicit permissions are set on the Chapter
if ($entity instanceof Page && intval($entity->chapter_id) !== 0) {
$chapter = $this->getChapter($entity->chapter_id);
$hasPermissiveAccessToParents = $hasPermissiveAccessToParents && !$chapter->restricted;
if ($chapter->restricted) {
$hasExplicitAccessToParents = $this->mapHasActiveRestriction($permissionMap, $chapter, $role, $restrictionAction);
}
}
return $this->createJointPermissionDataArray(
$entity,
$role,
$action,
($hasExplicitAccessToParents || ($roleHasPermission && $hasPermissiveAccessToParents)),
($hasExplicitAccessToParents || ($roleHasPermissionOwn && $hasPermissiveAccessToParents))
);
}
/**
* Check for an active restriction in an entity map.
*/
protected function mapHasActiveRestriction(array $entityMap, Entity $entity, Role $role, string $action): bool
{
$key = $entity->getMorphClass() . ':' . $entity->getRawAttribute('id') . ':' . $role->getRawAttribute('id') . ':' . $action;
return $entityMap[$key] ?? false;
}
/**
* Create an array of data with the information of an entity jointPermissions.
* Used to build data for bulk insertion.
*/
protected function createJointPermissionDataArray(Entity $entity, Role $role, string $action, bool $permissionAll, bool $permissionOwn): array
{
return [
'role_id' => $role->getRawAttribute('id'),
'entity_id' => $entity->getRawAttribute('id'),
'entity_type' => $entity->getMorphClass(),
'action' => $action,
'has_permission' => $permissionAll,
'has_permission_own' => $permissionOwn,
'owned_by' => $entity->getRawAttribute('owned_by'),
];
}
/**
* Checks if an entity has a restriction set upon it.
*
* @param HasCreatorAndUpdater|HasOwner $ownable
*/
public function checkOwnableUserAccess(Model $ownable, string $permission): bool
{
$explodedPermission = explode('-', $permission);
$baseQuery = $ownable->newQuery()->where('id', '=', $ownable->id);
$action = end($explodedPermission);
$user = $this->currentUser();
$nonJointPermissions = ['restrictions', 'image', 'attachment', 'comment'];
// Handle non entity specific jointPermissions
if (in_array($explodedPermission[0], $nonJointPermissions)) {
$allPermission = $user && $user->can($permission . '-all');
$ownPermission = $user && $user->can($permission . '-own');
$ownerField = ($ownable instanceof Entity) ? 'owned_by' : 'created_by';
$isOwner = $user && $user->id === $ownable->$ownerField;
return $allPermission || ($isOwner && $ownPermission);
}
// Handle abnormal create jointPermissions
if ($action === 'create') {
$action = $permission;
}
$hasAccess = $this->entityRestrictionQuery($baseQuery, $action)->count() > 0;
$this->clean();
return $hasAccess;
}
/**
* 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.
*/
public function checkUserHasPermissionOnAnything(string $permission, ?string $entityClass = null): bool
{
$userRoleIds = $this->currentUser()->roles()->select('id')->pluck('id')->toArray();
$userId = $this->currentUser()->id;
$permissionQuery = JointPermission::query()
->where('action', '=', $permission)
->whereIn('role_id', $userRoleIds)
->where(function (Builder $query) use ($userId) {
$this->addJointHasPermissionCheck($query, $userId);
});
if (!is_null($entityClass)) {
$entityInstance = app($entityClass);
$permissionQuery = $permissionQuery->where('entity_type', '=', $entityInstance->getMorphClass());
}
$hasPermission = $permissionQuery->count() > 0;
$this->clean();
return $hasPermission;
}
/**
* The general query filter to remove all entities
* that the current user does not have access to.
*/
protected function entityRestrictionQuery(Builder $query, string $action): Builder
{
$q = $query->where(function ($parentQuery) use ($action) {
$parentQuery->whereHas('jointPermissions', function ($permissionQuery) use ($action) {
$permissionQuery->whereIn('role_id', $this->getCurrentUserRoles())
->where('action', '=', $action)
->where(function (Builder $query) {
$this->addJointHasPermissionCheck($query, $this->currentUser()->id);
});
});
});
$this->clean();
return $q;
}
/**
* Limited the given entity query so that the query will only
* return items that the user has permission for the given ability.
*/
public function restrictEntityQuery(Builder $query, string $ability = 'view'): Builder
{
$this->clean();
return $query->where(function (Builder $parentQuery) use ($ability) {
$parentQuery->whereHas('jointPermissions', function (Builder $permissionQuery) use ($ability) {
$permissionQuery->whereIn('role_id', $this->getCurrentUserRoles())
->where('action', '=', $ability)
->where(function (Builder $query) {
$this->addJointHasPermissionCheck($query, $this->currentUser()->id);
});
});
});
}
/**
* Extend the given page query to ensure draft items are not visible
* unless created by the given user.
*/
public function enforceDraftVisibilityOnQuery(Builder $query): Builder
{
return $query->where(function (Builder $query) {
$query->where('draft', '=', false)
->orWhere(function (Builder $query) {
$query->where('draft', '=', true)
->where('owned_by', '=', $this->currentUser()->id);
});
});
}
/**
* Add restrictions for a generic entity.
*/
public function enforceEntityRestrictions(Entity $entity, Builder $query, string $action = 'view'): Builder
{
if ($entity instanceof Page) {
// Prevent drafts being visible to others.
$this->enforceDraftVisibilityOnQuery($query);
}
return $this->entityRestrictionQuery($query, $action);
}
/**
* Filter items that have entities set as a polymorphic relation.
*
* @param Builder|QueryBuilder $query
*/
public function filterRestrictedEntityRelations($query, string $tableName, string $entityIdColumn, string $entityTypeColumn, string $action = 'view')
{
$tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn, 'entityTypeColumn' => $entityTypeColumn];
$q = $query->where(function ($query) use ($tableDetails, $action) {
$query->whereExists(function ($permissionQuery) use (&$tableDetails, $action) {
/** @var Builder $permissionQuery */
$permissionQuery->select(['role_id'])->from('joint_permissions')
->whereColumn('joint_permissions.entity_id', '=', $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
->whereColumn('joint_permissions.entity_type', '=', $tableDetails['tableName'] . '.' . $tableDetails['entityTypeColumn'])
->where('action', '=', $action)
->whereIn('role_id', $this->getCurrentUserRoles())
->where(function (QueryBuilder $query) {
$this->addJointHasPermissionCheck($query, $this->currentUser()->id);
});
});
});
$this->clean();
return $q;
}
/**
* Add conditions to a query to filter the selection to related entities
* where view permissions are granted.
*/
public function filterRelatedEntity(string $entityClass, Builder $query, string $tableName, string $entityIdColumn): Builder
{
$tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn];
$morphClass = app($entityClass)->getMorphClass();
$q = $query->where(function ($query) use ($tableDetails, $morphClass) {
$query->where(function ($query) use (&$tableDetails, $morphClass) {
$query->whereExists(function ($permissionQuery) use (&$tableDetails, $morphClass) {
/** @var Builder $permissionQuery */
$permissionQuery->select('id')->from('joint_permissions')
->whereColumn('joint_permissions.entity_id', '=', $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
->where('entity_type', '=', $morphClass)
->where('action', '=', 'view')
->whereIn('role_id', $this->getCurrentUserRoles())
->where(function (QueryBuilder $query) {
$this->addJointHasPermissionCheck($query, $this->currentUser()->id);
});
});
})->orWhere($tableDetails['entityIdColumn'], '=', 0);
});
$this->clean();
return $q;
}
/**
* Add the query for checking the given user id has permission
* within the join_permissions table.
*
* @param QueryBuilder|Builder $query
*/
protected function addJointHasPermissionCheck($query, int $userIdToCheck)
{
$query->where('has_permission', '=', true)->orWhere(function ($query) use ($userIdToCheck) {
$query->where('has_permission_own', '=', true)
->where('owned_by', '=', $userIdToCheck);
});
}
/**
* Get the current user.
*/
private function currentUser(): User
{
if (is_null($this->currentUserModel)) {
$this->currentUserModel = user();
}
return $this->currentUserModel;
}
/**
* Clean the cached user elements.
*/
private function clean(): void
{
$this->currentUserModel = null;
$this->userRoles = null;
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace BookStack\Auth\Permissions;
class PermissionStatus
{
const IMPLICIT_DENY = 0;
const IMPLICIT_ALLOW = 1;
const EXPLICIT_DENY = 2;
const EXPLICIT_ALLOW = 3;
}

View File

@@ -11,20 +11,12 @@ use Illuminate\Database\Eloquent\Collection;
class PermissionsRepo
{
protected $permission;
protected $role;
protected $permissionService;
protected JointPermissionBuilder $permissionBuilder;
protected array $systemRoles = ['admin', 'public'];
protected $systemRoles = ['admin', 'public'];
/**
* PermissionsRepo constructor.
*/
public function __construct(RolePermission $permission, Role $role, PermissionService $permissionService)
public function __construct(JointPermissionBuilder $permissionBuilder)
{
$this->permission = $permission;
$this->role = $role;
$this->permissionService = $permissionService;
$this->permissionBuilder = $permissionBuilder;
}
/**
@@ -32,7 +24,7 @@ class PermissionsRepo
*/
public function getAllRoles(): Collection
{
return $this->role->all();
return Role::query()->get();
}
/**
@@ -40,15 +32,15 @@ class PermissionsRepo
*/
public function getAllRolesExcept(Role $role): Collection
{
return $this->role->where('id', '!=', $role->id)->get();
return Role::query()->where('id', '!=', $role->id)->get();
}
/**
* Get a role via its ID.
*/
public function getRoleById($id): Role
public function getRoleById(int $id): Role
{
return $this->role->newQuery()->findOrFail($id);
return Role::query()->findOrFail($id);
}
/**
@@ -56,13 +48,14 @@ class PermissionsRepo
*/
public function saveNewRole(array $roleData): Role
{
$role = $this->role->newInstance($roleData);
$role->mfa_enforced = ($roleData['mfa_enforced'] ?? 'false') === 'true';
$role = new Role($roleData);
$role->mfa_enforced = boolval($roleData['mfa_enforced'] ?? false);
$role->save();
$permissions = isset($roleData['permissions']) ? array_keys($roleData['permissions']) : [];
$permissions = $roleData['permissions'] ?? [];
$this->assignRolePermissions($role, $permissions);
$this->permissionService->buildJointPermissionForRole($role);
$this->permissionBuilder->rebuildForRole($role);
Activity::add(ActivityType::ROLE_CREATE, $role);
return $role;
@@ -70,43 +63,46 @@ class PermissionsRepo
/**
* Updates an existing role.
* Ensure Admin role always have core permissions.
* Ensures Admin system role always have core permissions.
*/
public function updateRole($roleId, array $roleData)
public function updateRole($roleId, array $roleData): Role
{
/** @var Role $role */
$role = $this->role->newQuery()->findOrFail($roleId);
$role = $this->getRoleById($roleId);
$permissions = isset($roleData['permissions']) ? array_keys($roleData['permissions']) : [];
if (isset($roleData['permissions'])) {
$this->assignRolePermissions($role, $roleData['permissions']);
}
$role->fill($roleData);
$role->save();
$this->permissionBuilder->rebuildForRole($role);
Activity::add(ActivityType::ROLE_UPDATE, $role);
return $role;
}
/**
* Assign a list of permission names to the given role.
*/
protected function assignRolePermissions(Role $role, array $permissionNameArray = []): void
{
$permissions = [];
$permissionNameArray = array_values($permissionNameArray);
// Ensure the admin system role retains vital system permissions
if ($role->system_name === 'admin') {
$permissions = array_merge($permissions, [
$permissionNameArray = array_unique(array_merge($permissionNameArray, [
'users-manage',
'user-roles-manage',
'restrictions-manage-all',
'restrictions-manage-own',
'settings-manage',
]);
]));
}
$this->assignRolePermissions($role, $permissions);
$role->fill($roleData);
$role->mfa_enforced = ($roleData['mfa_enforced'] ?? 'false') === 'true';
$role->save();
$this->permissionService->buildJointPermissionForRole($role);
Activity::add(ActivityType::ROLE_UPDATE, $role);
}
/**
* Assign an list of permission names to an role.
*/
protected function assignRolePermissions(Role $role, array $permissionNameArray = [])
{
$permissions = [];
$permissionNameArray = array_values($permissionNameArray);
if ($permissionNameArray) {
$permissions = $this->permission->newQuery()
if (!empty($permissionNameArray)) {
$permissions = RolePermission::query()
->whereIn('name', $permissionNameArray)
->pluck('id')
->toArray();
@@ -118,16 +114,15 @@ class PermissionsRepo
/**
* Delete a role from the system.
* Check it's not an admin role or set as default before deleting.
* If an migration Role ID is specified the users assign to the current role
* If a migration Role ID is specified the users assign to the current role
* will be added to the role of the specified id.
*
* @throws PermissionsException
* @throws Exception
*/
public function deleteRole($roleId, $migrateRoleId)
public function deleteRole(int $roleId, int $migrateRoleId = 0): void
{
/** @var Role $role */
$role = $this->role->newQuery()->findOrFail($roleId);
$role = $this->getRoleById($roleId);
// Prevent deleting admin role or default registration role.
if ($role->system_name && in_array($role->system_name, $this->systemRoles)) {
@@ -136,15 +131,16 @@ class PermissionsRepo
throw new PermissionsException(trans('errors.role_registration_default_cannot_delete'));
}
if ($migrateRoleId) {
$newRole = $this->role->newQuery()->find($migrateRoleId);
if ($migrateRoleId !== 0) {
$newRole = Role::query()->find($migrateRoleId);
if ($newRole) {
$users = $role->users()->pluck('id')->toArray();
$newRole->users()->sync($users);
}
}
$this->permissionService->deleteJointPermissionsForRole($role);
$role->entityPermissions()->delete();
$role->jointPermissions()->delete();
Activity::add(ActivityType::ROLE_DELETE, $role);
$role->delete();
}

View File

@@ -8,6 +8,8 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany;
/**
* @property int $id
* @property string $name
* @property string $display_name
*/
class RolePermission extends Model
{

View File

@@ -0,0 +1,28 @@
<?php
namespace BookStack\Auth\Permissions;
use BookStack\Entities\Models\Entity;
class SimpleEntityData
{
public int $id;
public string $type;
public int $owned_by;
public ?int $book_id;
public ?int $chapter_id;
public static function fromEntity(Entity $entity): self
{
$attrs = $entity->getAttributes();
$simple = new self();
$simple->id = $attrs['id'];
$simple->type = $entity->getMorphClass();
$simple->owned_by = $attrs['owned_by'] ?? 0;
$simple->book_id = $attrs['book_id'] ?? null;
$simple->chapter_id = $attrs['chapter_id'] ?? null;
return $simple;
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace BookStack\Auth\Queries;
use BookStack\Auth\Role;
use BookStack\Util\SimpleListOptions;
use Illuminate\Pagination\LengthAwarePaginator;
/**
* Get all the roles in the system in a paginated format.
*/
class RolesAllPaginatedAndSorted
{
public function run(int $count, SimpleListOptions $listOptions): LengthAwarePaginator
{
$sort = $listOptions->getSort();
if ($sort === 'created_at') {
$sort = 'users.created_at';
}
$query = Role::query()->select(['*'])
->withCount(['users', 'permissions'])
->orderBy($sort, $listOptions->getOrder());
if ($listOptions->getSearch()) {
$term = '%' . $listOptions->getSearch() . '%';
$query->where(function ($query) use ($term) {
$query->where('display_name', 'like', $term)
->orWhere('description', 'like', $term);
});
}
return $query->paginate($count);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace BookStack\Auth\Queries;
use BookStack\Auth\User;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Page;
/**
* Get asset created counts for the given user.
*/
class UserContentCounts
{
/**
* @return array{pages: int, chapters: int, books: int, shelves: int}
*/
public function run(User $user): array
{
$createdBy = ['created_by' => $user->id];
return [
'pages' => Page::visible()->where($createdBy)->count(),
'chapters' => Chapter::visible()->where($createdBy)->count(),
'books' => Book::visible()->where($createdBy)->count(),
'shelves' => Bookshelf::visible()->where($createdBy)->count(),
];
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace BookStack\Auth\Queries;
use BookStack\Auth\User;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Page;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
/**
* Get the recently created content for the provided user.
*/
class UserRecentlyCreatedContent
{
/**
* @return array{pages: Collection, chapters: Collection, books: Collection, shelves: Collection}
*/
public function run(User $user, int $count): array
{
$query = function (Builder $query) use ($user, $count) {
return $query->orderBy('created_at', 'desc')
->where('created_by', '=', $user->id)
->take($count)
->get();
};
return [
'pages' => $query(Page::visible()->where('draft', '=', false)),
'chapters' => $query(Chapter::visible()),
'books' => $query(Book::visible()),
'shelves' => $query(Bookshelf::visible()),
];
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace BookStack\Auth\Queries;
use BookStack\Auth\User;
use BookStack\Util\SimpleListOptions;
use Illuminate\Pagination\LengthAwarePaginator;
/**
* Get all the users with their permissions in a paginated format.
* Note: Due to the use of email search this should only be used when
* user is assumed to be trusted. (Admin users).
* Email search can be abused to extract email addresses.
*/
class UsersAllPaginatedAndSorted
{
public function run(int $count, SimpleListOptions $listOptions): LengthAwarePaginator
{
$sort = $listOptions->getSort();
if ($sort === 'created_at') {
$sort = 'users.created_at';
}
$query = User::query()->select(['*'])
->scopes(['withLastActivityAt'])
->with(['roles', 'avatar'])
->withCount('mfaValues')
->orderBy($sort, $listOptions->getOrder());
if ($listOptions->getSearch()) {
$term = '%' . $listOptions->getSearch() . '%';
$query->where(function ($query) use ($term) {
$query->where('name', 'like', $term)
->orWhere('email', 'like', $term);
});
}
return $query->paginate($count);
}
}

View File

@@ -2,6 +2,7 @@
namespace BookStack\Auth;
use BookStack\Auth\Permissions\EntityPermission;
use BookStack\Auth\Permissions\JointPermission;
use BookStack\Auth\Permissions\RolePermission;
use BookStack\Interfaces\Loggable;
@@ -26,7 +27,13 @@ class Role extends Model implements Loggable
{
use HasFactory;
protected $fillable = ['display_name', 'description', 'external_auth_id'];
protected $fillable = ['display_name', 'description', 'external_auth_id', 'mfa_enforced'];
protected $hidden = ['pivot'];
protected $casts = [
'mfa_enforced' => 'boolean',
];
/**
* The roles that belong to the role.
@@ -52,6 +59,14 @@ class Role extends Model implements Loggable
return $this->belongsToMany(RolePermission::class, 'permission_role', 'role_id', 'permission_id');
}
/**
* Get the entity permissions assigned to this role.
*/
public function entityPermissions(): HasMany
{
return $this->hasMany(EntityPermission::class);
}
/**
* Check if this role has a permission.
*/
@@ -96,26 +111,13 @@ class Role extends Model implements Loggable
*/
public static function getSystemRole(string $systemName): ?self
{
return static::query()->where('system_name', '=', $systemName)->first();
}
static $cache = [];
/**
* Get all visible roles.
*/
public static function visible(): Collection
{
return static::query()->where('hidden', '=', false)->orderBy('name')->get();
}
if (!isset($cache[$systemName])) {
$cache[$systemName] = static::query()->where('system_name', '=', $systemName)->first();
}
/**
* Get the roles that can be restricted.
*/
public static function restrictable(): Collection
{
return static::query()
->where('system_name', '!=', 'admin')
->orderBy('display_name', 'asc')
->get();
return $cache[$systemName];
}
/**

View File

@@ -72,22 +72,25 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
*/
protected $hidden = [
'password', 'remember_token', 'system_name', 'email_confirmed', 'external_auth_id', 'email',
'created_at', 'updated_at', 'image_id',
'created_at', 'updated_at', 'image_id', 'roles', 'avatar', 'user_id', 'pivot',
];
/**
* This holds the user's permissions when loaded.
*
* @var ?Collection
*/
protected $permissions;
protected ?Collection $permissions;
/**
* This holds the user's avatar URL when loaded to prevent re-calculating within the same request.
*/
protected string $avatarUrl = '';
/**
* This holds the default user when loaded.
*
* @var null|User
*/
protected static $defaultUser = null;
protected static ?User $defaultUser = null;
/**
* Returns the default public user.
@@ -146,7 +149,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
*/
public function attachDefaultRole(): void
{
$roleId = setting('registration-role');
$roleId = intval(setting('registration-role'));
if ($roleId && $this->roles()->where('id', '=', $roleId)->count() === 0) {
$this->roles()->attach($roleId);
}
@@ -165,7 +168,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
}
/**
* Get all permissions belonging to a the current user.
* Get all permissions belonging to the current user.
*/
protected function permissions(): Collection
{
@@ -197,6 +200,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
public function attachRole(Role $role)
{
$this->roles()->attach($role->id);
$this->unsetRelation('roles');
}
/**
@@ -235,12 +239,18 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
return $default;
}
if (!empty($this->avatarUrl)) {
return $this->avatarUrl;
}
try {
$avatar = $this->avatar ? url($this->avatar->getThumb($size, $size, false)) : $default;
} catch (Exception $err) {
$avatar = $default;
}
$this->avatarUrl = $avatar;
return $avatar;
}

View File

@@ -2,31 +2,30 @@
namespace BookStack\Auth;
use Activity;
use BookStack\Actions\ActivityType;
use BookStack\Auth\Access\UserInviteService;
use BookStack\Entities\EntityProvider;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Page;
use BookStack\Exceptions\NotFoundException;
use BookStack\Exceptions\NotifyException;
use BookStack\Exceptions\UserUpdateException;
use BookStack\Facades\Activity;
use BookStack\Uploads\UserAvatars;
use Exception;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
class UserRepo
{
protected $userAvatar;
protected UserAvatars $userAvatar;
protected UserInviteService $inviteService;
/**
* UserRepo constructor.
*/
public function __construct(UserAvatars $userAvatar)
public function __construct(UserAvatars $userAvatar, UserInviteService $inviteService)
{
$this->userAvatar = $userAvatar;
$this->inviteService = $inviteService;
}
/**
@@ -54,67 +53,167 @@ class UserRepo
}
/**
* Get all the users with their permissions.
* Create a new basic instance of user with the given pre-validated data.
*
* @param array{name: string, email: string, password: ?string, external_auth_id: ?string, language: ?string, roles: ?array} $data
*/
public function getAllUsers(): Collection
public function createWithoutActivity(array $data, bool $emailConfirmed = false): User
{
return User::query()->with('roles', 'avatar')->orderBy('name', 'asc')->get();
}
$user = new User();
$user->name = $data['name'];
$user->email = $data['email'];
$user->password = Hash::make(empty($data['password']) ? Str::random(32) : $data['password']);
$user->email_confirmed = $emailConfirmed;
$user->external_auth_id = $data['external_auth_id'] ?? '';
/**
* Get all the users with their permissions in a paginated format.
*/
public function getAllUsersPaginatedAndSorted(int $count, array $sortData): LengthAwarePaginator
{
$sort = $sortData['sort'];
$user->refreshSlug();
$user->save();
$query = User::query()->select(['*'])
->withLastActivityAt()
->with(['roles', 'avatar'])
->withCount('mfaValues')
->orderBy($sort, $sortData['order']);
if ($sortData['search']) {
$term = '%' . $sortData['search'] . '%';
$query->where(function ($query) use ($term) {
$query->where('name', 'like', $term)
->orWhere('email', 'like', $term);
});
if (!empty($data['language'])) {
setting()->putUser($user, 'language', $data['language']);
}
return $query->paginate($count);
}
if (isset($data['roles'])) {
$this->setUserRoles($user, $data['roles']);
}
/**
* Creates a new user and attaches a role to them.
*/
public function registerNew(array $data, bool $emailConfirmed = false): User
{
$user = $this->create($data, $emailConfirmed);
$user->attachDefaultRole();
$this->downloadAndAssignUserAvatar($user);
return $user;
}
/**
* Assign a user to a system-level role.
* As per "createWithoutActivity" but records a "create" activity.
*
* @throws NotFoundException
* @param array{name: string, email: string, password: ?string, external_auth_id: ?string, language: ?string, roles: ?array} $data
*/
public function attachSystemRole(User $user, string $systemRoleName)
public function create(array $data, bool $sendInvite = false): User
{
$role = Role::getSystemRole($systemRoleName);
if (is_null($role)) {
throw new NotFoundException("Role '{$systemRoleName}' not found");
$user = $this->createWithoutActivity($data, true);
if ($sendInvite) {
$this->inviteService->sendInvitation($user);
}
Activity::add(ActivityType::USER_CREATE, $user);
return $user;
}
/**
* Update the given user with the given data.
*
* @param array{name: ?string, email: ?string, external_auth_id: ?string, password: ?string, roles: ?array<int>, language: ?string} $data
*
* @throws UserUpdateException
*/
public function update(User $user, array $data, bool $manageUsersAllowed): User
{
if (!empty($data['name'])) {
$user->name = $data['name'];
$user->refreshSlug();
}
if (!empty($data['email']) && $manageUsersAllowed) {
$user->email = $data['email'];
}
if (!empty($data['external_auth_id']) && $manageUsersAllowed) {
$user->external_auth_id = $data['external_auth_id'];
}
if (isset($data['roles']) && $manageUsersAllowed) {
$this->setUserRoles($user, $data['roles']);
}
if (!empty($data['password'])) {
$user->password = Hash::make($data['password']);
}
if (!empty($data['language'])) {
setting()->putUser($user, 'language', $data['language']);
}
$user->save();
Activity::add(ActivityType::USER_UPDATE, $user);
return $user;
}
/**
* Remove the given user from storage, Delete all related content.
*
* @throws Exception
*/
public function destroy(User $user, ?int $newOwnerId = null)
{
$this->ensureDeletable($user);
$user->socialAccounts()->delete();
$user->apiTokens()->delete();
$user->favourites()->delete();
$user->mfaValues()->delete();
$user->delete();
// Delete user profile images
$this->userAvatar->destroyAllForUser($user);
// Delete related activities
setting()->deleteUserSettings($user->id);
if (!empty($newOwnerId)) {
$newOwner = User::query()->find($newOwnerId);
if (!is_null($newOwner)) {
$this->migrateOwnership($user, $newOwner);
}
}
Activity::add(ActivityType::USER_DELETE, $user);
}
/**
* @throws NotifyException
*/
protected function ensureDeletable(User $user): void
{
if ($this->isOnlyAdmin($user)) {
throw new NotifyException(trans('errors.users_cannot_delete_only_admin'), $user->getEditUrl());
}
if ($user->system_name === 'public') {
throw new NotifyException(trans('errors.users_cannot_delete_guest'), $user->getEditUrl());
}
}
/**
* Migrate ownership of items in the system from one user to another.
*/
protected function migrateOwnership(User $fromUser, User $toUser)
{
$entities = (new EntityProvider())->all();
foreach ($entities as $instance) {
$instance->newQuery()->where('owned_by', '=', $fromUser->id)
->update(['owned_by' => $toUser->id]);
}
}
/**
* Get an avatar image for a user and set it as their avatar.
* Returns early if avatars disabled or not set in config.
*/
protected function downloadAndAssignUserAvatar(User $user): void
{
try {
$this->userAvatar->fetchAndAssignToUser($user);
} catch (Exception $e) {
Log::error('Failed to save user avatar image');
}
$user->attachRole($role);
}
/**
* Checks if the give user is the only admin.
*/
public function isOnlyAdmin(User $user): bool
protected function isOnlyAdmin(User $user): bool
{
if (!$user->hasSystemRole('admin')) {
return false;
@@ -133,8 +232,10 @@ class UserRepo
*
* @throws UserUpdateException
*/
public function setUserRoles(User $user, array $roles)
protected function setUserRoles(User $user, array $roles)
{
$roles = array_filter(array_values($roles));
if ($this->demotingLastAdmin($user, $roles)) {
throw new UserUpdateException(trans('errors.role_cannot_remove_only_admin'), $user->getEditUrl());
}
@@ -157,125 +258,4 @@ class UserRepo
return false;
}
/**
* Create a new basic instance of user.
*/
public function create(array $data, bool $emailConfirmed = false): User
{
$details = [
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
'email_confirmed' => $emailConfirmed,
'external_auth_id' => $data['external_auth_id'] ?? '',
];
$user = new User();
$user->forceFill($details);
$user->refreshSlug();
$user->save();
return $user;
}
/**
* Remove the given user from storage, Delete all related content.
*
* @throws Exception
*/
public function destroy(User $user, ?int $newOwnerId = null)
{
$user->socialAccounts()->delete();
$user->apiTokens()->delete();
$user->favourites()->delete();
$user->mfaValues()->delete();
$user->delete();
// Delete user profile images
$this->userAvatar->destroyAllForUser($user);
if (!empty($newOwnerId)) {
$newOwner = User::query()->find($newOwnerId);
if (!is_null($newOwner)) {
$this->migrateOwnership($user, $newOwner);
}
}
}
/**
* Migrate ownership of items in the system from one user to another.
*/
protected function migrateOwnership(User $fromUser, User $toUser)
{
$entities = (new EntityProvider())->all();
foreach ($entities as $instance) {
$instance->newQuery()->where('owned_by', '=', $fromUser->id)
->update(['owned_by' => $toUser->id]);
}
}
/**
* Get the latest activity for a user.
*/
public function getActivity(User $user, int $count = 20, int $page = 0): array
{
return Activity::userActivity($user, $count, $page);
}
/**
* Get the recently created content for this given user.
*/
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' => $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.
*/
public function getAssetCounts(User $user): array
{
$createdBy = ['created_by' => $user->id];
return [
'pages' => Page::visible()->where($createdBy)->count(),
'chapters' => Chapter::visible()->where($createdBy)->count(),
'books' => Book::visible()->where($createdBy)->count(),
'shelves' => Bookshelf::visible()->where($createdBy)->count(),
];
}
/**
* Get the roles in the system that are assignable to a user.
*/
public function getAllRoles(): Collection
{
return Role::query()->orderBy('display_name', 'asc')->get();
}
/**
* Get an avatar image for a user and set it as their avatar.
* Returns early if avatars disabled or not set in config.
*/
public function downloadAndAssignUserAvatar(User $user): void
{
try {
$this->userAvatar->fetchAndAssignToUser($user);
} catch (Exception $e) {
Log::error('Failed to save user avatar image');
}
}
}

View File

@@ -8,6 +8,8 @@
* Do not edit this file unless you're happy to maintain any changes yourself.
*/
use Illuminate\Support\Facades\Facade;
return [
// The environment to run BookStack in.
@@ -22,7 +24,7 @@ return [
// 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),
'revision_limit' => env('REVISION_LIMIT', 100),
// The number of days that content will remain in the recycle bin before
// being considered for auto-removal. It is not a guarantee that content will
@@ -57,6 +59,17 @@ return [
// Space separated if multiple. BookStack host domain is auto-inferred.
'iframe_hosts' => env('ALLOWED_IFRAME_HOSTS', null),
// A list of sources/hostnames that can be loaded within iframes within BookStack.
// Space separated if multiple. BookStack host domain is auto-inferred.
// Can be set to a lone "*" to allow all sources for iframe content (Not advised).
// Defaults to a set of common services.
// Current host and source for the "DRAWIO" setting will be auto-appended to the sources configured.
'iframe_sources' => env('ALLOWED_IFRAME_SOURCES', 'https://*.draw.io https://*.youtube.com https://*.youtube-nocookie.com https://*.vimeo.com'),
// Alter the precision of IP addresses stored by BookStack.
// Integer value between 0 (IP hidden) to 4 (Full IP usage)
'ip_address_precision' => env('IP_ADDRESS_PRECISION', 4),
// Application timezone for back-end date functions.
'timezone' => env('APP_TIMEZONE', 'UTC'),
@@ -64,7 +77,7 @@ return [
'locale' => env('APP_LANG', 'en'),
// Locales available
'locales' => ['en', 'ar', 'bg', 'bs', 'ca', 'cs', 'da', 'de', 'de_informal', 'es', 'es_AR', 'et', 'fa', 'fr', 'he', 'hr', 'hu', 'id', 'it', 'ja', 'ko', 'lt', 'lv', 'nl', 'nb', 'pt', 'pt_BR', 'sk', 'sl', 'sv', 'pl', 'ru', 'th', 'tr', 'uk', 'vi', 'zh_CN', 'zh_TW'],
'locales' => ['en', 'ar', 'bg', 'bs', 'ca', 'cs', 'cy', 'da', 'de', 'de_informal', 'el', 'es', 'es_AR', 'et', 'eu', 'fa', 'fr', 'he', 'hr', 'hu', 'id', 'it', 'ja', 'ka', 'ko', 'lt', 'lv', 'nl', 'nb', 'pt', 'pt_BR', 'sk', 'sl', 'sv', 'pl', 'ro', 'ru', 'tr', 'uk', 'uz', 'vi', 'zh_CN', 'zh_TW'],
// Application Fallback Locale
'fallback_locale' => 'en',
@@ -87,7 +100,13 @@ return [
// Encryption cipher
'cipher' => 'AES-256-CBC',
// Application Services Provides
// Maintenance Mode Driver
'maintenance' => [
'driver' => 'file',
// 'store' => 'redis',
],
// Application Service Providers
'providers' => [
// Laravel Framework Service Providers...
@@ -103,6 +122,8 @@ return [
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
Illuminate\Hashing\HashServiceProvider::class,
Illuminate\Mail\MailServiceProvider::class,
Illuminate\Notifications\NotificationServiceProvider::class,
Illuminate\Pagination\PaginationServiceProvider::class,
Illuminate\Pipeline\PipelineServiceProvider::class,
Illuminate\Queue\QueueServiceProvider::class,
Illuminate\Redis\RedisServiceProvider::class,
@@ -110,94 +131,37 @@ return [
Illuminate\Session\SessionServiceProvider::class,
Illuminate\Validation\ValidationServiceProvider::class,
Illuminate\View\ViewServiceProvider::class,
Illuminate\Notifications\NotificationServiceProvider::class,
SocialiteProviders\Manager\ServiceProvider::class,
// Third party service providers
Intervention\Image\ImageServiceProvider::class,
Barryvdh\DomPDF\ServiceProvider::class,
Barryvdh\Snappy\ServiceProvider::class,
// BookStack replacement service providers (Extends Laravel)
BookStack\Providers\PaginationServiceProvider::class,
BookStack\Providers\TranslationServiceProvider::class,
Intervention\Image\ImageServiceProvider::class,
SocialiteProviders\Manager\ServiceProvider::class,
// BookStack custom service providers
BookStack\Providers\ThemeServiceProvider::class,
BookStack\Providers\AuthServiceProvider::class,
BookStack\Providers\AppServiceProvider::class,
BookStack\Providers\BroadcastServiceProvider::class,
BookStack\Providers\AuthServiceProvider::class,
BookStack\Providers\EventServiceProvider::class,
BookStack\Providers\RouteServiceProvider::class,
BookStack\Providers\CustomFacadeProvider::class,
BookStack\Providers\CustomValidationServiceProvider::class,
BookStack\Providers\TranslationServiceProvider::class,
BookStack\Providers\ValidationRuleServiceProvider::class,
BookStack\Providers\ViewTweaksServiceProvider::class,
],
/*
|--------------------------------------------------------------------------
| Class Aliases
|--------------------------------------------------------------------------
|
| This array of class aliases will be registered when this application
| is started. However, feel free to register as many as you wish as
| the aliases are "lazy" loaded so they don't hinder performance.
|
*/
// 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,
'Bus' => Illuminate\Support\Facades\Bus::class,
'Cache' => Illuminate\Support\Facades\Cache::class,
'Config' => Illuminate\Support\Facades\Config::class,
'Cookie' => Illuminate\Support\Facades\Cookie::class,
'Crypt' => Illuminate\Support\Facades\Crypt::class,
'Date' => Illuminate\Support\Facades\Date::class,
'DB' => Illuminate\Support\Facades\DB::class,
'Eloquent' => Illuminate\Database\Eloquent\Model::class,
'Event' => Illuminate\Support\Facades\Event::class,
'File' => Illuminate\Support\Facades\File::class,
'Gate' => Illuminate\Support\Facades\Gate::class,
'Hash' => Illuminate\Support\Facades\Hash::class,
'Http' => Illuminate\Support\Facades\Http::class,
'Lang' => Illuminate\Support\Facades\Lang::class,
'Log' => Illuminate\Support\Facades\Log::class,
'Mail' => Illuminate\Support\Facades\Mail::class,
'Notification' => Illuminate\Support\Facades\Notification::class,
'Password' => Illuminate\Support\Facades\Password::class,
'Queue' => Illuminate\Support\Facades\Queue::class,
'RateLimiter' => Illuminate\Support\Facades\RateLimiter::class,
'Redirect' => Illuminate\Support\Facades\Redirect::class,
// 'Redis' => Illuminate\Support\Facades\Redis::class,
'Request' => Illuminate\Support\Facades\Request::class,
'Response' => Illuminate\Support\Facades\Response::class,
'Route' => Illuminate\Support\Facades\Route::class,
'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,
// Class Aliases
// This array of class aliases to be registered on application start.
'aliases' => Facade::defaultAliases()->merge([
// Laravel Packages
'Socialite' => Laravel\Socialite\Facades\Socialite::class,
// Third Party
'ImageTool' => Intervention\Image\Facades\Image::class,
'DomPDF' => Barryvdh\DomPDF\Facade::class,
'SnappyPDF' => Barryvdh\Snappy\Facades\SnappyPdf::class,
// Custom BookStack
'Activity' => BookStack\Facades\Activity::class,
'Permissions' => BookStack\Facades\Permissions::class,
'Theme' => BookStack\Facades\Theme::class,
],
])->toArray(),
// Proxy configuration
'proxies' => env('APP_PROXIES', ''),

View File

@@ -13,6 +13,10 @@ return [
// Options: standard, ldap, saml2, oidc
'method' => env('AUTH_METHOD', 'standard'),
// Automatically initiate login via external auth system if it's the sole auth method.
// Works with saml2 or oidc auth methods.
'auto_initiate' => env('AUTH_AUTO_INITIATE', false),
// Authentication Defaults
// This option controls the default authentication "guard" and password
// reset options for your application.

View File

@@ -14,7 +14,7 @@ return [
// 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'),
'default' => 'null',
// Broadcast Connections
// Here you may define all of the broadcast connections that will be used
@@ -22,21 +22,7 @@ return [
// 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',
],
// Default options removed since we don't use broadcasting.
'log' => [
'driver' => 'log',

View File

@@ -87,6 +87,6 @@ return [
|
*/
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_cache'),
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_cache_'),
];

View File

@@ -8,11 +8,16 @@
* Do not edit this file unless you're happy to maintain any changes yourself.
*/
$dompdfPaperSizeMap = [
'a4' => 'a4',
'letter' => 'letter',
];
return [
'show_warnings' => false, // Throw an Exception on warnings from dompdf
'orientation' => 'portrait',
'defines' => [
'options' => [
/**
* The location of the DOMPDF font directory.
*
@@ -73,15 +78,25 @@ return [
'chroot' => realpath(public_path()),
/**
* Whether to use Unicode fonts or not.
* Protocol whitelist.
*
* When set to true the PDF backend must be set to "CPDF" and fonts must be
* loaded via load_font.php.
* Protocols and PHP wrappers allowed in URIs, and the validation rules
* that determine if a resouce may be loaded. Full support is not guaranteed
* for the protocols/wrappers specified
* by this array.
*
* When enabled, dompdf can support all Unicode glyphs. Any glyphs used in a
* document must be present in your fonts, however.
* @var array
*/
'unicode_enabled' => true,
'allowed_protocols' => [
'file://' => ['rules' => []],
'http://' => ['rules' => []],
'https://' => ['rules' => []],
],
/**
* @var string
*/
'log_output_file' => null,
/**
* Whether to enable font subsetting or not.
@@ -150,7 +165,16 @@ return [
*
* @see CPDF_Adapter::PAPER_SIZES for valid sizes ('letter', 'legal', 'A4', etc.)
*/
'default_paper_size' => 'a4',
'default_paper_size' => $dompdfPaperSizeMap[env('EXPORT_PAGE_SIZE', 'a4')] ?? 'a4',
/**
* The default paper orientation.
*
* The orientation of the page (portrait or landscape).
*
* @var string
*/
'default_paper_orientation' => 'portrait',
/**
* The default font family.
@@ -254,10 +278,13 @@ return [
'enable_css_float' => true,
/**
* Use the more-than-experimental HTML5 Lib parser.
* Use the HTML5 Lib parser.
*
* @deprecated This feature is now always on in dompdf 2.x
*
* @var bool
*/
'enable_html5parser' => true,
'enable_html5_parser' => true,
],
];

View File

@@ -33,17 +33,20 @@ return [
'driver' => 'local',
'root' => public_path(),
'visibility' => 'public',
'throw' => true,
],
'local_secure_attachments' => [
'driver' => 'local',
'root' => storage_path('uploads/files/'),
'throw' => true,
],
'local_secure_images' => [
'driver' => 'local',
'root' => storage_path('uploads/images/'),
'visibility' => 'public',
'throw' => true,
],
's3' => [
@@ -54,6 +57,7 @@ return [
'bucket' => env('STORAGE_S3_BUCKET', 'your-bucket'),
'endpoint' => env('STORAGE_S3_ENDPOINT', null),
'use_path_style_endpoint' => env('STORAGE_S3_ENDPOINT', null) !== null,
'throw' => true,
],
],

View File

@@ -21,6 +21,15 @@ return [
// one of the channels defined in the "channels" configuration array.
'default' => env('LOG_CHANNEL', 'single'),
// Deprecations Log Channel
// This option controls the log channel that should be used to log warnings
// regarding deprecated PHP and library features. This allows you to get
// your application ready for upcoming major versions of dependencies.
'deprecations' => [
'channel' => 'null',
'trace' => false,
],
// 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

View File

@@ -14,13 +14,7 @@ return [
// From Laravel 7+ this is MAIL_MAILER in laravel.
// Kept as MAIL_DRIVER in BookStack to prevent breaking change.
// Options: smtp, sendmail, log, array
'driver' => env('MAIL_DRIVER', 'smtp'),
// SMTP host address
'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
// SMTP host port
'port' => env('MAIL_PORT', 587),
'default' => env('MAIL_DRIVER', 'smtp'),
// Global "From" address & name
'from' => [
@@ -28,17 +22,42 @@ return [
'name' => env('MAIL_FROM_NAME', 'BookStack'),
],
// Email encryption protocol
'encryption' => env('MAIL_ENCRYPTION', 'tls'),
// Mailer Configurations
// Available mailing methods and their settings.
'mailers' => [
'smtp' => [
'transport' => 'smtp',
'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
'port' => env('MAIL_PORT', 587),
'encryption' => env('MAIL_ENCRYPTION', 'tls'),
'username' => env('MAIL_USERNAME'),
'password' => env('MAIL_PASSWORD'),
'timeout' => null,
'local_domain' => env('MAIL_EHLO_DOMAIN'),
],
// SMTP server username
'username' => env('MAIL_USERNAME'),
'sendmail' => [
'transport' => 'sendmail',
'path' => env('MAIL_SENDMAIL_COMMAND', '/usr/sbin/sendmail -bs'),
],
// SMTP server password
'password' => env('MAIL_PASSWORD'),
'log' => [
'transport' => 'log',
'channel' => env('MAIL_LOG_CHANNEL'),
],
// Sendmail application path
'sendmail' => '/usr/sbin/sendmail -bs',
'array' => [
'transport' => 'array',
],
'failover' => [
'transport' => 'failover',
'mailers' => [
'smtp',
'log',
],
],
],
// Email markdown configuration
'markdown' => [
@@ -47,11 +66,4 @@ return [
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'),
];

View File

@@ -8,9 +8,12 @@ return [
// Dump user details after a login request for debugging purposes
'dump_user_details' => env('OIDC_DUMP_USER_DETAILS', false),
// Attribute, within a OpenId token, to find the user's display name
// Claim, within an OpenId token, to find the user's display name
'display_name_claims' => explode('|', env('OIDC_DISPLAY_NAME_CLAIMS', 'name')),
// Claim, within an OpenID token, to use to connect a BookStack user to the OIDC user.
'external_id_claim' => env('OIDC_EXTERNAL_ID_CLAIM', 'sub'),
// OAuth2/OpenId client id, as configured in your Authorization server.
'client_id' => env('OIDC_CLIENT_ID', null),
@@ -32,4 +35,16 @@ return [
// OAuth2 endpoints.
'authorization_endpoint' => env('OIDC_AUTH_ENDPOINT', null),
'token_endpoint' => env('OIDC_TOKEN_ENDPOINT', null),
// Add extra scopes, upon those required, to the OIDC authentication request
// Multiple values can be provided comma seperated.
'additional_scopes' => env('OIDC_ADDITIONAL_SCOPES', null),
// Group sync options
// Enable syncing, upon login, of OIDC groups to BookStack roles
'user_to_groups' => env('OIDC_USER_TO_GROUPS', false),
// Attribute, within a OIDC ID token, to find group names within
'groups_claim' => env('OIDC_GROUPS_CLAIM', 'groups'),
// When syncing groups, remove any groups that no longer match. Otherwise sync only adds new groups.
'remove_from_groups' => env('OIDC_REMOVE_FROM_GROUPS', false),
];

View File

@@ -11,7 +11,7 @@
return [
// Default driver to use for the queue
// Options: null, sync, redis
// Options: sync, database, redis
'default' => env('QUEUE_CONNECTION', 'sync'),
// Queue connection configuration

View File

@@ -119,6 +119,7 @@ return [
'ldap' => [
'server' => env('LDAP_SERVER', false),
'dump_user_details' => env('LDAP_DUMP_USER_DETAILS', false),
'dump_user_groups' => env('LDAP_DUMP_USER_GROUPS', false),
'dn' => env('LDAP_DN', false),
'pass' => env('LDAP_PASS', false),
'base_dn' => env('LDAP_BASE_DN', false),

View File

@@ -72,7 +72,7 @@ return [
// 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', null)
?? Str::startsWith(env('APP_URL'), 'https:'),
?? Str::startsWith(env('APP_URL', ''), 'https:'),
// HTTP Access Only
// Setting this value to true will prevent JavaScript from accessing the

View File

@@ -16,16 +16,27 @@ return [
'app-editor' => 'wysiwyg',
'app-color' => '#206ea7',
'app-color-light' => 'rgba(32,110,167,0.15)',
'link-color' => '#206ea7',
'bookshelf-color' => '#a94747',
'book-color' => '#077b70',
'chapter-color' => '#af4d0d',
'page-color' => '#206ea7',
'page-draft-color' => '#7e50b1',
'app-color-dark' => '#195785',
'app-color-light-dark' => 'rgba(32,110,167,0.15)',
'link-color-dark' => '#429fe3',
'bookshelf-color-dark' => '#ff5454',
'book-color-dark' => '#389f60',
'chapter-color-dark' => '#ee7a2d',
'page-color-dark' => '#429fe3',
'page-draft-color-dark' => '#a66ce8',
'app-custom-head' => false,
'registration-enabled' => false,
// User-level default settings
'user' => [
'ui-shortcuts' => '{}',
'ui-shortcuts-enabled' => false,
'dark-mode-enabled' => env('APP_DEFAULT_DARK_MODE', false),
'bookshelves_view_type' => env('APP_VIEWS_BOOKSHELVES', 'grid'),
'bookshelf_view_type' => env('APP_VIEWS_BOOKSHELF', 'grid'),

View File

@@ -8,13 +8,19 @@
* Do not edit this file unless you're happy to maintain any changes yourself.
*/
$snappyPaperSizeMap = [
'a4' => 'A4',
'letter' => 'Letter',
];
return [
'pdf' => [
'enabled' => true,
'binary' => file_exists(base_path('wkhtmltopdf')) ? base_path('wkhtmltopdf') : env('WKHTMLTOPDF', false),
'timeout' => false,
'options' => [
'outline' => true,
'outline' => true,
'page-size' => $snappyPaperSizeMap[env('EXPORT_PAGE_SIZE', 'a4')] ?? 'A4',
],
'env' => [],
],

View File

@@ -3,7 +3,7 @@
namespace BookStack\Console\Commands;
use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Repos\BookshelfRepo;
use BookStack\Entities\Tools\PermissionsUpdater;
use Illuminate\Console\Command;
class CopyShelfPermissions extends Command
@@ -25,19 +25,16 @@ class CopyShelfPermissions extends Command
*/
protected $description = 'Copy shelf permissions to all child books';
/**
* @var BookshelfRepo
*/
protected $bookshelfRepo;
protected PermissionsUpdater $permissionsUpdater;
/**
* Create a new command instance.
*
* @return void
*/
public function __construct(BookshelfRepo $repo)
public function __construct(PermissionsUpdater $permissionsUpdater)
{
$this->bookshelfRepo = $repo;
$this->permissionsUpdater = $permissionsUpdater;
parent::__construct();
}
@@ -69,18 +66,18 @@ class CopyShelfPermissions extends Command
return;
}
$shelves = Bookshelf::query()->get(['id', 'restricted']);
$shelves = Bookshelf::query()->get(['id']);
}
if ($shelfSlug) {
$shelves = Bookshelf::query()->where('slug', '=', $shelfSlug)->get(['id', 'restricted']);
$shelves = Bookshelf::query()->where('slug', '=', $shelfSlug)->get(['id']);
if ($shelves->count() === 0) {
$this->info('No shelves found with the given slug.');
}
}
foreach ($shelves as $shelf) {
$this->bookshelfRepo->copyDownPermissions($shelf, false);
$this->permissionsUpdater->updateBookPermissionsFromShelf($shelf, false);
$this->info('Copied permissions for shelf [' . $shelf->id . ']');
}

View File

@@ -2,8 +2,14 @@
namespace BookStack\Console\Commands;
use BookStack\Auth\Role;
use BookStack\Auth\UserRepo;
use BookStack\Exceptions\NotFoundException;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules\Password;
use Illuminate\Validation\Rules\Unique;
use Symfony\Component\Console\Command\Command as SymfonyCommand;
class CreateAdmin extends Command
@@ -16,7 +22,8 @@ class CreateAdmin extends Command
protected $signature = 'bookstack:create-admin
{--email= : The email address for the new admin user}
{--name= : The name of the new admin user}
{--password= : The password to assign to the new admin user}';
{--password= : The password to assign to the new admin user}
{--external-auth-id= : The external authentication system id for the new admin user (SAML2/LDAP/OIDC)}';
/**
* The console command description.
@@ -39,51 +46,47 @@ class CreateAdmin extends Command
/**
* Execute the console command.
*
* @throws \BookStack\Exceptions\NotFoundException
* @throws NotFoundException
*
* @return mixed
*/
public function handle()
{
$email = trim($this->option('email'));
if (empty($email)) {
$email = $this->ask('Please specify an email address for the new admin user');
$details = $this->snakeCaseOptions();
if (empty($details['email'])) {
$details['email'] = $this->ask('Please specify an email address for the new admin user');
}
if (mb_strlen($email) < 5 || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
$this->error('Invalid email address provided');
if (empty($details['name'])) {
$details['name'] = $this->ask('Please specify a name for the new admin user');
}
if (empty($details['password'])) {
if (empty($details['external_auth_id'])) {
$details['password'] = $this->ask('Please specify a password for the new admin user (8 characters min)');
} else {
$details['password'] = Str::random(32);
}
}
$validator = Validator::make($details, [
'email' => ['required', 'email', 'min:5', new Unique('users', 'email')],
'name' => ['required', 'min:2'],
'password' => ['required_without:external_auth_id', Password::default()],
'external_auth_id' => ['required_without:password'],
]);
if ($validator->fails()) {
foreach ($validator->errors()->all() as $error) {
$this->error($error);
}
return SymfonyCommand::FAILURE;
}
if ($this->userRepo->getByEmail($email) !== null) {
$this->error('A user with the provided email already exists!');
return SymfonyCommand::FAILURE;
}
$name = trim($this->option('name'));
if (empty($name)) {
$name = $this->ask('Please specify an name for the new admin user');
}
if (mb_strlen($name) < 2) {
$this->error('Invalid name provided');
return SymfonyCommand::FAILURE;
}
$password = trim($this->option('password'));
if (empty($password)) {
$password = $this->secret('Please specify a password for the new admin user');
}
if (mb_strlen($password) < 5) {
$this->error('Invalid password provided, Must be at least 5 characters');
return SymfonyCommand::FAILURE;
}
$user = $this->userRepo->create(['email' => $email, 'name' => $name, 'password' => $password]);
$this->userRepo->attachSystemRole($user, 'admin');
$this->userRepo->downloadAndAssignUserAvatar($user);
$user = $this->userRepo->createWithoutActivity($validator->validated());
$user->attachRole(Role::getSystemRole('admin'));
$user->email_confirmed = true;
$user->save();
@@ -91,4 +94,14 @@ class CreateAdmin extends Command
return SymfonyCommand::SUCCESS;
}
protected function snakeCaseOptions(): array
{
$returnOpts = [];
foreach ($this->options() as $key => $value) {
$returnOpts[str_replace('-', '_', $key)] = $value;
}
return $returnOpts;
}
}

View File

@@ -15,8 +15,6 @@ class DeleteUsers extends Command
*/
protected $signature = 'bookstack:delete-users';
protected $user;
protected $userRepo;
/**
@@ -26,9 +24,8 @@ class DeleteUsers extends Command
*/
protected $description = 'Delete users that are not "admin" or system users';
public function __construct(User $user, UserRepo $userRepo)
public function __construct(UserRepo $userRepo)
{
$this->user = $user;
$this->userRepo = $userRepo;
parent::__construct();
}
@@ -38,8 +35,8 @@ class DeleteUsers extends Command
$confirm = $this->ask('This will delete all users from the system that are not "admin" or system users. Are you sure you want to continue? (Type "yes" to continue)');
$numDeleted = 0;
if (strtolower(trim($confirm)) === 'yes') {
$totalUsers = $this->user->count();
$users = $this->user->where('system_name', '=', null)->with('roles')->get();
$totalUsers = User::query()->count();
$users = User::query()->whereNull('system_name')->with('roles')->get();
foreach ($users as $user) {
if ($user->hasSystemRole('admin')) {
// don't delete users with "admin" role

View File

@@ -5,6 +5,7 @@ namespace BookStack\Console\Commands;
use BookStack\Actions\Comment;
use BookStack\Actions\CommentRepo;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
class RegenerateCommentContent extends Command
{
@@ -43,9 +44,9 @@ class RegenerateCommentContent extends Command
*/
public function handle()
{
$connection = \DB::getDefaultConnection();
$connection = DB::getDefaultConnection();
if ($this->option('database') !== null) {
\DB::setDefaultConnection($this->option('database'));
DB::setDefaultConnection($this->option('database'));
}
Comment::query()->chunk(100, function ($comments) {
@@ -55,7 +56,9 @@ class RegenerateCommentContent extends Command
}
});
\DB::setDefaultConnection($connection);
DB::setDefaultConnection($connection);
$this->comment('Comment HTML content has been regenerated');
return 0;
}
}

View File

@@ -2,8 +2,9 @@
namespace BookStack\Console\Commands;
use BookStack\Auth\Permissions\PermissionService;
use BookStack\Auth\Permissions\JointPermissionBuilder;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
class RegeneratePermissions extends Command
{
@@ -21,19 +22,14 @@ class RegeneratePermissions extends Command
*/
protected $description = 'Regenerate all system permissions';
/**
* The service to handle the permission system.
*
* @var PermissionService
*/
protected $permissionService;
protected JointPermissionBuilder $permissionBuilder;
/**
* Create a new command instance.
*/
public function __construct(PermissionService $permissionService)
public function __construct(JointPermissionBuilder $permissionBuilder)
{
$this->permissionService = $permissionService;
$this->permissionBuilder = $permissionBuilder;
parent::__construct();
}
@@ -44,15 +40,17 @@ class RegeneratePermissions extends Command
*/
public function handle()
{
$connection = \DB::getDefaultConnection();
if ($this->option('database') !== null) {
\DB::setDefaultConnection($this->option('database'));
$this->permissionService->setConnection(\DB::connection($this->option('database')));
$connection = DB::getDefaultConnection();
if ($this->option('database')) {
DB::setDefaultConnection($this->option('database'));
}
$this->permissionService->buildJointPermissions();
$this->permissionBuilder->rebuildForAll();
\DB::setDefaultConnection($connection);
DB::setDefaultConnection($connection);
$this->comment('Permissions regenerated');
return 0;
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace BookStack\Console\Commands;
use BookStack\References\ReferenceStore;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
class RegenerateReferences extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'bookstack:regenerate-references {--database= : The database connection to use.}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Regenerate all the cross-item model reference index';
protected ReferenceStore $references;
/**
* Create a new command instance.
*
* @return void
*/
public function __construct(ReferenceStore $references)
{
$this->references = $references;
parent::__construct();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$connection = DB::getDefaultConnection();
if ($this->option('database')) {
DB::setDefaultConnection($this->option('database'));
}
$this->references->updateForAllPages();
DB::setDefaultConnection($connection);
$this->comment('References have been regenerated');
return 0;
}
}

View File

@@ -3,7 +3,7 @@
namespace BookStack\Console\Commands;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Tools\SearchIndex;
use BookStack\Search\SearchIndex;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;

View File

@@ -19,6 +19,7 @@ use Illuminate\Support\Collection;
* @property \Illuminate\Database\Eloquent\Collection $chapters
* @property \Illuminate\Database\Eloquent\Collection $pages
* @property \Illuminate\Database\Eloquent\Collection $directPages
* @property \Illuminate\Database\Eloquent\Collection $shelves
*/
class Book extends Entity implements HasCoverImage
{
@@ -27,7 +28,7 @@ class Book extends Entity implements HasCoverImage
public $searchFactor = 1.2;
protected $fillable = ['name', 'description'];
protected $hidden = ['restricted', 'pivot', 'image_id', 'deleted_at'];
protected $hidden = ['pivot', 'image_id', 'deleted_at'];
/**
* Get the url for this book.
@@ -119,4 +120,13 @@ class Book extends Entity implements HasCoverImage
return $pages->concat($chapters)->sortBy('priority')->sortByDesc('draft');
}
/**
* Get a visible book by its slug.
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
public static function getBySlug(string $slug): self
{
return static::visible()->where('slug', '=', $slug)->firstOrFail();
}
}

View File

@@ -2,6 +2,7 @@
namespace BookStack\Entities\Models;
use BookStack\References\ReferenceUpdater;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
@@ -57,11 +58,16 @@ abstract class BookChild extends Entity
*/
public function changeBook(int $newBookId): Entity
{
$oldUrl = $this->getUrl();
$this->book_id = $newBookId;
$this->refreshSlug();
$this->save();
$this->refresh();
if ($oldUrl !== $this->getUrl()) {
app()->make(ReferenceUpdater::class)->updateEntityPageReferences($this, $oldUrl);
}
// Update all child pages if a chapter
if ($this instanceof Chapter) {
foreach ($this->pages()->withTrashed()->get() as $page) {

View File

@@ -17,7 +17,7 @@ class Bookshelf extends Entity implements HasCoverImage
protected $fillable = ['name', 'description', 'image_id'];
protected $hidden = ['restricted', 'image_id', 'deleted_at'];
protected $hidden = ['image_id', 'deleted_at'];
/**
* Get the books in this shelf.
@@ -86,15 +86,11 @@ class Bookshelf extends Entity implements HasCoverImage
*/
public function coverImageTypeKey(): string
{
return 'cover_shelf';
return 'cover_bookshelf';
}
/**
* Check if this shelf contains the given book.
*
* @param Book $book
*
* @return bool
*/
public function contains(Book $book): bool
{
@@ -103,8 +99,6 @@ class Bookshelf extends Entity implements HasCoverImage
/**
* Add a book to the end of this shelf.
*
* @param Book $book
*/
public function appendBook(Book $book)
{
@@ -115,4 +109,13 @@ class Bookshelf extends Entity implements HasCoverImage
$maxOrder = $this->books()->max('order');
$this->books()->attach($book->id, ['order' => $maxOrder + 1]);
}
/**
* Get a visible shelf by its slug.
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
public static function getBySlug(string $slug): self
{
return static::visible()->where('slug', '=', $slug)->firstOrFail();
}
}

View File

@@ -18,8 +18,8 @@ class Chapter extends BookChild
public $searchFactor = 1.2;
protected $fillable = ['name', 'description', 'priority', 'book_id'];
protected $hidden = ['restricted', 'pivot', 'deleted_at'];
protected $fillable = ['name', 'description', 'priority'];
protected $hidden = ['pivot', 'deleted_at'];
/**
* Get the pages that this chapter contains.
@@ -58,4 +58,13 @@ class Chapter extends BookChild
->orderBy('priority', 'asc')
->get();
}
/**
* Get a visible chapter by its book and page slugs.
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
public static function getBySlugs(string $bookSlug, string $chapterSlug): self
{
return static::visible()->whereSlugs($bookSlug, $chapterSlug)->firstOrFail();
}
}

View File

@@ -10,10 +10,16 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;
/**
* @property int $id
* @property int $deleted_by
* @property string $deletable_type
* @property int $deletable_id
* @property Deletable $deletable
*/
class Deletion extends Model implements Loggable
{
protected $hidden = [];
/**
* Get the related deletable record.
*/
@@ -59,7 +65,7 @@ class Deletion extends Model implements Loggable
/**
* Get a URL for this specific deletion.
*/
public function getUrl($path): string
public function getUrl(string $path = 'restore'): string
{
return url("/settings/recycle-bin/{$this->id}/" . ltrim($path, '/'));
}

View File

@@ -9,14 +9,18 @@ use BookStack\Actions\Tag;
use BookStack\Actions\View;
use BookStack\Auth\Permissions\EntityPermission;
use BookStack\Auth\Permissions\JointPermission;
use BookStack\Entities\Tools\SearchIndex;
use BookStack\Auth\Permissions\JointPermissionBuilder;
use BookStack\Auth\Permissions\PermissionApplicator;
use BookStack\Entities\Tools\SlugGenerator;
use BookStack\Facades\Permissions;
use BookStack\Interfaces\Deletable;
use BookStack\Interfaces\Favouritable;
use BookStack\Interfaces\Loggable;
use BookStack\Interfaces\Sluggable;
use BookStack\Interfaces\Viewable;
use BookStack\Model;
use BookStack\References\Reference;
use BookStack\Search\SearchIndex;
use BookStack\Search\SearchTerm;
use BookStack\Traits\HasCreatorAndUpdater;
use BookStack\Traits\HasOwner;
use Carbon\Carbon;
@@ -35,17 +39,16 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property string $slug
* @property Carbon $created_at
* @property Carbon $updated_at
* @property Carbon $deleted_at
* @property int $created_by
* @property int $updated_by
* @property bool $restricted
* @property Collection $tags
*
* @method static Entity|Builder visible()
* @method static Entity|Builder hasPermission(string $permission)
* @method static Builder withLastView()
* @method static Builder withViewCount()
*/
abstract class Entity extends Model implements Sluggable, Favouritable, Viewable, Deletable
abstract class Entity extends Model implements Sluggable, Favouritable, Viewable, Deletable, Loggable
{
use SoftDeletes;
use HasCreatorAndUpdater;
@@ -66,15 +69,7 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
*/
public function scopeVisible(Builder $query): Builder
{
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);
return app()->make(PermissionApplicator::class)->restrictEntityQuery($query);
}
/**
@@ -180,16 +175,15 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
*/
public function permissions(): MorphMany
{
return $this->morphMany(EntityPermission::class, 'restrictable');
return $this->morphMany(EntityPermission::class, 'entity');
}
/**
* Check if this entity has a specific restriction set against it.
*/
public function hasRestriction(int $role_id, string $action): bool
public function hasPermissions(): bool
{
return $this->permissions()->where('role_id', '=', $role_id)
->where('action', '=', $action)->count() > 0;
return $this->permissions()->count() > 0;
}
/**
@@ -208,6 +202,22 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
return $this->morphMany(Deletion::class, 'deletable');
}
/**
* Get the references pointing from this entity to other items.
*/
public function referencesFrom(): MorphMany
{
return $this->morphMany(Reference::class, 'from');
}
/**
* Get the references pointing to this entity from other items.
*/
public function referencesTo(): MorphMany
{
return $this->morphMany(Reference::class, 'to');
}
/**
* Check if this instance or class is a certain type of entity.
* Examples of $type are 'page', 'book', 'chapter'.
@@ -282,8 +292,7 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
*/
public function rebuildPermissions()
{
/** @noinspection PhpUnhandledExceptionInspection */
Permissions::buildJointPermissionsForEntity(clone $this);
app()->make(JointPermissionBuilder::class)->rebuildForEntity(clone $this);
}
/**
@@ -291,7 +300,7 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
*/
public function indexForSearch()
{
app(SearchIndex::class)->indexEntity(clone $this);
app()->make(SearchIndex::class)->indexEntity(clone $this);
}
/**
@@ -299,7 +308,7 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
*/
public function refreshSlug(): string
{
$this->slug = app(SlugGenerator::class)->generate($this);
$this->slug = app()->make(SlugGenerator::class)->generate($this);
return $this->slug;
}
@@ -321,4 +330,12 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
->where('user_id', '=', user()->id)
->exists();
}
/**
* {@inheritdoc}
*/
public function logDescriptor(): string
{
return "({$this->id}) {$this->name}";
}
}

View File

@@ -2,27 +2,31 @@
namespace BookStack\Entities\Models;
use BookStack\Auth\Permissions\PermissionApplicator;
use BookStack\Entities\Tools\PageContent;
use BookStack\Facades\Permissions;
use BookStack\Uploads\Attachment;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
/**
* 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
* @property int $chapter_id
* @property string $html
* @property string $markdown
* @property string $text
* @property bool $template
* @property bool $draft
* @property int $revision_count
* @property string $editor
* @property Chapter $chapter
* @property Collection $attachments
* @property Collection $revisions
* @property PageRevision $currentRevision
*/
class Page extends BookChild
{
@@ -35,7 +39,7 @@ class Page extends BookChild
public $textField = 'text';
protected $hidden = ['html', 'markdown', 'text', 'restricted', 'pivot', 'deleted_at'];
protected $hidden = ['html', 'markdown', 'text', 'pivot', 'deleted_at'];
protected $casts = [
'draft' => 'boolean',
@@ -47,7 +51,7 @@ class Page extends BookChild
*/
public function scopeVisible(Builder $query): Builder
{
$query = Permissions::enforceDraftVisibilityOnQuery($query);
$query = app()->make(PermissionApplicator::class)->restrictDraftsOnPageQuery($query);
return parent::scopeVisible($query);
}
@@ -82,6 +86,17 @@ class Page extends BookChild
->orderBy('id', 'desc');
}
/**
* Get the current revision for the page if existing.
*/
public function currentRevision(): HasOne
{
return $this->hasOne(PageRevision::class)
->where('type', '=', 'version')
->orderBy('created_at', 'desc')
->orderBy('id', 'desc');
}
/**
* Get all revision instances assigned to this page.
* Includes all types of revisions.
@@ -117,16 +132,6 @@ class Page extends BookChild
return url('/' . implode('/', $parts));
}
/**
* Get the current revision for the page if existing.
*
* @return PageRevision|null
*/
public function getCurrentRevision()
{
return $this->revisions()->first();
}
/**
* Get this page for JSON display.
*/
@@ -138,4 +143,13 @@ class Page extends BookChild
return $refreshed;
}
/**
* Get a visible page by its book and page slugs.
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
public static function getBySlugs(string $bookSlug, string $pageSlug): self
{
return static::visible()->whereSlugs($bookSlug, $pageSlug)->firstOrFail();
}
}

View File

@@ -3,6 +3,7 @@
namespace BookStack\Entities\Models;
use BookStack\Auth\User;
use BookStack\Interfaces\Loggable;
use BookStack\Model;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
@@ -10,7 +11,9 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* Class PageRevision.
*
* @property mixed $id
* @property int $page_id
* @property string $name
* @property string $slug
* @property string $book_slug
* @property int $created_by
@@ -20,13 +23,15 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
* @property string $summary
* @property string $markdown
* @property string $html
* @property string $text
* @property int $revision_number
* @property Page $page
* @property-read ?User $createdBy
*/
class PageRevision extends Model
class PageRevision extends Model implements Loggable
{
protected $fillable = ['name', 'html', 'text', 'markdown', 'summary'];
protected $fillable = ['name', 'text', 'summary'];
protected $hidden = ['html', 'markdown', 'text'];
/**
* Get the user that created the page revision.
@@ -46,19 +51,10 @@ class PageRevision extends Model
/**
* Get the url for this revision.
*
* @param null|string $path
*
* @return string
*/
public function getUrl($path = null)
public function getUrl(string $path = ''): string
{
$url = $this->page->getUrl() . '/revisions/' . $this->id;
if ($path) {
return $url . '/' . trim($path, '/');
}
return $url;
return $this->page->getUrl('/revisions/' . $this->id . '/' . ltrim($path, '/'));
}
/**
@@ -88,4 +84,9 @@ class PageRevision extends Model
{
return $type === 'revision';
}
public function logDescriptor(): string
{
return "Revision #{$this->revision_number} (ID: {$this->id}) for page ID {$this->page_id}";
}
}

View File

@@ -2,14 +2,14 @@
namespace BookStack\Entities\Queries;
use BookStack\Auth\Permissions\PermissionService;
use BookStack\Auth\Permissions\PermissionApplicator;
use BookStack\Entities\EntityProvider;
abstract class EntityQuery
{
protected function permissionService(): PermissionService
protected function permissionService(): PermissionApplicator
{
return app()->make(PermissionService::class);
return app()->make(PermissionApplicator::class);
}
protected function entityProvider(): EntityProvider

View File

@@ -7,10 +7,10 @@ use Illuminate\Support\Facades\DB;
class Popular extends EntityQuery
{
public function run(int $count, int $page, array $filterModels = null, string $action = 'view')
public function run(int $count, int $page, array $filterModels = null)
{
$query = $this->permissionService()
->filterRestrictedEntityRelations(View::query(), 'views', 'viewable_id', 'viewable_type', $action)
->restrictEntityRelationQuery(View::query(), 'views', 'viewable_id', 'viewable_type')
->select('*', 'viewable_id', 'viewable_type', DB::raw('SUM(views) as view_count'))
->groupBy('viewable_id', 'viewable_type')
->orderBy('view_count', 'desc');

View File

@@ -14,12 +14,11 @@ class RecentlyViewed extends EntityQuery
return collect();
}
$query = $this->permissionService()->filterRestrictedEntityRelations(
$query = $this->permissionService()->restrictEntityRelationQuery(
View::query(),
'views',
'viewable_id',
'viewable_type',
'view'
'viewable_type'
)
->orderBy('views.updated_at', 'desc')
->where('user_id', '=', user()->id);

View File

@@ -15,7 +15,7 @@ class TopFavourites extends EntityQuery
}
$query = $this->permissionService()
->filterRestrictedEntityRelations(Favourite::query(), 'favourites', 'favouritable_id', 'favouritable_type', 'view')
->restrictEntityRelationQuery(Favourite::query(), 'favourites', 'favouritable_id', 'favouritable_type')
->select('favourites.*')
->leftJoin('views', function (JoinClause $join) {
$join->on('favourites.favouritable_id', '=', 'views.viewable_id');

View File

@@ -6,18 +6,21 @@ use BookStack\Actions\TagRepo;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\HasCoverImage;
use BookStack\Exceptions\ImageUploadException;
use BookStack\References\ReferenceUpdater;
use BookStack\Uploads\ImageRepo;
use Illuminate\Http\UploadedFile;
class BaseRepo
{
protected $tagRepo;
protected $imageRepo;
protected TagRepo $tagRepo;
protected ImageRepo $imageRepo;
protected ReferenceUpdater $referenceUpdater;
public function __construct(TagRepo $tagRepo, ImageRepo $imageRepo)
public function __construct(TagRepo $tagRepo, ImageRepo $imageRepo, ReferenceUpdater $referenceUpdater)
{
$this->tagRepo = $tagRepo;
$this->imageRepo = $imageRepo;
$this->referenceUpdater = $referenceUpdater;
}
/**
@@ -38,6 +41,7 @@ class BaseRepo
$this->tagRepo->saveTagsToEntity($entity, $input['tags']);
}
$entity->refresh();
$entity->rebuildPermissions();
$entity->indexForSearch();
}
@@ -47,10 +51,12 @@ class BaseRepo
*/
public function update(Entity $entity, array $input)
{
$oldUrl = $entity->getUrl();
$entity->fill($input);
$entity->updated_by = user()->id;
if ($entity->isDirty('name')) {
if ($entity->isDirty('name') || empty($entity->slug)) {
$entity->refreshSlug();
}
@@ -58,10 +64,15 @@ class BaseRepo
if (isset($input['tags'])) {
$this->tagRepo->saveTagsToEntity($entity, $input['tags']);
$entity->touch();
}
$entity->rebuildPermissions();
$entity->indexForSearch();
if ($oldUrl !== $entity->getUrl()) {
$this->referenceUpdater->updateEntityPageReferences($entity, $oldUrl);
}
}
/**
@@ -75,14 +86,15 @@ class BaseRepo
public function updateCoverImage($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);
$imageType = $entity->coverImageTypeKey();
$this->imageRepo->destroyImage($entity->cover()->first());
$image = $this->imageRepo->saveNew($coverImage, $imageType, $entity->id, 512, 512, true);
$entity->cover()->associate($image);
$entity->save();
}
if ($removeImage) {
$this->imageRepo->destroyImage($entity->cover);
$this->imageRepo->destroyImage($entity->cover()->first());
$entity->image_id = 0;
$entity->save();
}

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