Compare commits

...

303 Commits

Author SHA1 Message Date
Dan Brown
bde66a1396 Updated version and assets for release v23.08.1 2023-09-03 17:40:19 +01:00
Dan Brown
4de5a2d9bf Merge branch 'development' into release 2023-09-03 17:39:56 +01:00
Dan Brown
2abbcf5c0f Updated translator attribution before release v23.08.1 2023-09-03 17:35:57 +01:00
Dan Brown
7a48516bf4 Updated translations with latest Crowdin changes (#4481) 2023-09-03 17:23:40 +01:00
Dan Brown
e31b50dabd Preferences: Fixed section screen flexibility
Improved wrapping and flex control to prevent button text force wrapping
to newlines.

For #4502
2023-09-03 16:58:29 +01:00
Dan Brown
817581aa0c Watching: Prevent issues when watchable or user is deleted
- Adds filtering to the watched items list in notification preferences
  so that deleted (recycle bin) items are removed via query.
- Adds relations and logic to properly remove watches upon user and
  entity delete events, to old watches in database do not linger.
- Adds testing to cover the above.

Did not add migration for existing data, since patch will be close to
introduction, and lingering DB entries don't open a security concern,
just some potential confusion in specific potential scenarios.
Probably not work extra migration risk, although could add in future if
concerns/issues are found.

Related to #4499
2023-09-03 14:19:43 +01:00
Dan Brown
1cd19c76ba Merge pull request #4497 from BookStackApp/notification_language
Notifications: User language for notification text
2023-09-02 15:47:26 +01:00
Dan Brown
5d38ae3c97 Merge pull request #4484 from omahs/patch-1
Fix typos
2023-09-02 15:44:01 +01:00
Dan Brown
a720b3725d Testing: Added entity decode flag and phpunit env option
- Passed decode flags to provide consistent behaviour across PHP
  versions during testing.
- Added env option to prevent local option taking action in PHPunit
  tests.
2023-09-02 15:39:45 +01:00
Dan Brown
3847a76134 Notifications: Aligned how user language is used
- This ensures content notifications are not translated to receiver
  language.
- This adds actual plaintext support for content notifications (Was
  previously just HTML as text view).
- Shares same base class across all mail notifications.
- Also cleaned up existing notification classes.

Future cleanup requested via #4501
2023-09-02 15:11:42 +01:00
Dan Brown
f91049a3f2 Notifications: Add test to check notification language 2023-09-01 16:30:37 +01:00
Dan Brown
4e6b74f2a1 WYSIWYG: Added filtering of page pointer elements
For #4474
2023-09-01 13:50:55 +01:00
omahs
976f241ae0 fix typo 2023-08-31 10:01:56 +02:00
omahs
415dab9936 fix typos 2023-08-31 10:00:45 +02:00
omahs
54715d40ef fix typo 2023-08-31 09:58:59 +02:00
Dan Brown
27bf4299cf Updated version and assets for release v23.08 2023-08-30 12:38:48 +01:00
Dan Brown
164f01bb25 Merge branch 'development' into release 2023-08-30 12:38:22 +01:00
Dan Brown
c6d0e690f9 Updated translations with latest Crowdin changes (#4462) 2023-08-30 12:35:10 +01:00
Dan Brown
77d65d1ca1 Updated translator attribution before v23.08 2023-08-30 11:49:45 +01:00
Dan Brown
dc77233ec3 MD Editor: Fixed scroll on mobile widths
Added min-height to flex elements to ensure they properly flex within
the container rathen than adjust to content.

For #4466
2023-08-30 02:41:51 +01:00
Dan Brown
3622c440d7 SSR: Added new option to complete env example file 2023-08-30 02:31:36 +01:00
Dan Brown
642210ab4c Merge branch 'srr_host_allowlist' into development 2023-08-27 12:45:00 +01:00
Dan Brown
e176aae940 Updated translations with latest Crowdin changes (#4380) 2023-08-27 12:43:59 +01:00
Dan Brown
903895814a SSR: Updated allow list handling & covered webhook usage
- Covered webhook SSR allow list useage via test.
- Updated allow list handling to use trailing slash, or hash, or end of
  line as late anchor for better handling for hosts (prevent .co.uk
passing for .co domain host)
2023-08-26 20:13:37 +01:00
Dan Brown
c324ad928d Security: Added new SSR allow list and validator
Included unit tests to cover validator functionality.
Added to webhooks.
Still need to do testing specifically for webhooks.
2023-08-26 15:28:29 +01:00
Dan Brown
9100a82b47 Guests: Prevented access to profile routes
Prevention of action on certain routes for guest user when public access
is enabled. Could not see a way this could be a security issue, beyond a
mild nuisance that'd only be visible if public users can edit, which
would present larger potential nuisance anyway.
2023-08-26 14:07:48 +01:00
Dan Brown
32516f7b68 Merge pull request #4457 from BookStackApp/drawing_backup_store
Browser-based drawing backup storage system
2023-08-23 19:12:29 +01:00
Dan Brown
69ac425903 Updated readme attribution and fixed eslint issues 2023-08-23 19:02:23 +01:00
Dan Brown
3917e50c90 Drawio: Tweaked fail backup handling during testing
- Tweaked wording of popup title.
- Updated WYSIWYG create handling to properly remove drawing container
  on failure.

Tested across FF and chrome, in both editors for create & editing.
2023-08-23 18:50:37 +01:00
Dan Brown
dd71658d70 Drawio: Added unsaved restore prompt and logic 2023-08-23 14:16:20 +01:00
Dan Brown
a4fbde9185 Drawio: Started browser drawing backup store system
Adds just the part to store image data, and remove on successfull save.
Alters save events to properly throw upon error.
Adds IDB-Keyval library for local large-size store.
For #4421
2023-08-22 19:30:39 +01:00
Dan Brown
cbcec189fd RTL: Fixed screen-reader-only elements pushout out view
For #4429
2023-08-22 18:25:14 +01:00
Dan Brown
0628c28f66 Cache: Increases database cache value size
Upped from text to medium text.
Aligns with modern Laravel default.
Fixes #4453 where were reaching the limit of TEXT.
2023-08-21 23:01:42 +01:00
Dan Brown
391478465a Merge branch 'add-priority' into development 2023-08-21 15:43:16 +01:00
Dan Brown
9ca1139ab0 API: Reviewed changes for API priority control
Review of #4313
- Made constructor changes while reviewing some classes.
- Updated API examples for consistency.
- Tweaked formatting for some array changes.
- Simplified added tests.
- Tweaked chapter/page repo priority handling to be simpler.

Performed manual API endpoint testing of page/chapter create/update.
2023-08-21 15:42:47 +01:00
Dan Brown
7bf5425c6b Updated PHP and npm deps, Upped node version 2023-08-19 20:22:19 +01:00
Dan Brown
e44ef57219 Status: Updated cache check to use unique key
Updated status endpoint cache check to include a random component in the
key to avoid conflict during simultaneous checks.
For #4396
2023-08-17 21:24:35 +01:00
Dan Brown
fef433a9cb Merge pull request #4390 from BookStackApp/content_notifications
Content user notifications
2023-08-17 21:09:52 +01:00
Dan Brown
e709caa005 Notifications: Switched testing from string to reference levels 2023-08-17 18:10:34 +01:00
Dan Brown
38829f8a38 Notifications: Fixed send content permission checking
Added test and changed logic to properly check the view permissions for
the notification receiver before sending.
Required change to permissions applicator to allow the user to be
manually determined, and a service provider update to provide the class
as a singleton without a specific user, so it checks the current logged
in user on demand.
2023-08-17 17:57:31 +01:00
Dan Brown
ee9e342b58 Notifications: Fixed issues causing failing tests
- Ensured watch options passed in all meta template usage to fix failing
  scenarios where watch options did not exist.
- Fixed testing issue caused by guest user permission caching.
2023-08-17 14:59:28 +01:00
Dan Brown
79470ea4b7 Notifications: Made improvements from manual testing
- Added titles for preference pages.
- Added extra check for non-guest for notifications on preferences page.
2023-08-16 20:15:49 +01:00
Dan Brown
565908ef52 Notifications: Add phpunit test for notification sending
Covers core case scenarios, and check of notification content.
2023-08-16 16:02:00 +01:00
Dan Brown
bc6e19b2a1 Notifications: Added testing to cover controls 2023-08-15 20:08:27 +01:00
Dan Brown
615741af9d Notifications: Cleaned up mails, added debounce for updates
- Updated mail notification design to be a bit prettier, and extracted
  text to new lang file for translation.
- Added debounce logic for page update notifications.
- Fixed watch options not being filtered to current user.
2023-08-15 14:39:39 +01:00
Dan Brown
371779205a Notifications: Added new preferences view and access control
- Added general user preferences view and updated link in profile menu
  to suit.
- Made notification permission required for notification preferences
  view, added test to cover.
2023-08-14 17:29:12 +01:00
Dan Brown
d9fdecd902 Notifications: User watch list and differnt page watch options
- Adds option filtering and alternative text for page watch options.
- Adds "Watched & Ignored Items" list to user notification preferences
  page to show existing watched items.
2023-08-14 13:11:18 +01:00
Dan Brown
c47b3f805a Notifications: Updated watch control to show parent status 2023-08-09 14:53:31 +01:00
Dan Brown
ecab2c8e42 Notifications: Added logic and classes for remaining notification types 2023-08-05 14:19:23 +01:00
Dan Brown
18ae67a138 Notifications: Got core notification logic working for new pages
Also rolled out watch UI to chapter and page views
2023-08-04 16:51:29 +01:00
Dan Brown
9779c1a357 Notifications: Started core user notification logic
Put together an initial notification.
Started logic to query and identify watchers.
2023-08-04 12:27:29 +01:00
Dan Brown
9d149e4d36 Notifications: Linked watch functionality to UI
Got watch system working to an initial base state.
Moved some existing logic where it makes sense.
2023-08-02 13:14:00 +01:00
Dan Brown
8cdf3203ef Notifications: Started back-end for watch system
Added DB and started controller method.
2023-07-31 16:08:29 +01:00
Dan Brown
6100b99828 Notifications: Extracted watch options, updated UI further 2023-07-31 15:23:28 +01:00
Dan Brown
730f539029 Notifications: Started entity watch UI 2023-07-27 14:27:45 +01:00
Dan Brown
ff2674c464 Notifications: Added role receive-notifications permission 2023-07-25 17:59:04 +01:00
Dan Brown
100b28707c Notifications: added user preference UI & logic
Includes testing to cover.
Also added file missing from previous commit.
2023-07-25 17:08:40 +01:00
Dan Brown
45e75edf05 Notifications: Started activity->notification core framework 2023-07-19 11:03:05 +01:00
Dan Brown
1c922be4c7 Comments: Added text for new activity types 2023-07-19 10:11:53 +01:00
Dan Brown
0359e2490a Comments: Updated testing to check for new activities 2023-07-19 10:09:08 +01:00
Dan Brown
422e50302a Comments: Added extra comment-specific activities
Kept existing "COMMENTED_ON" activity for upgrade compatibility,
specifically for existing webhook usage and for showing comment
activities in activity lists.

Precursor to content notifications.
Currently untested.
Also applied some type updates.
2023-07-18 15:07:31 +01:00
Dan Brown
f563a005f5 Updated version and assets for release v23.06.2 2023-07-12 22:34:25 +01:00
Dan Brown
a14d8e30cc Merge branch 'development' into release 2023-07-12 22:34:15 +01:00
Dan Brown
7504ad32a7 Updated translator attribution before release v23.06.2 2023-07-12 22:34:04 +01:00
Dan Brown
fca18862d2 Updated translations with latest Crowdin changes (#4367) 2023-07-12 22:22:43 +01:00
Dan Brown
ae834050f5 Shelf permissions: reverted create removal
Reverted work in 847a57a49a.
Left test in but updated to new expectation.
Left migration in but removed content to prevent new pre-v23.06
upgraders loosing shelf create permission status.
Added note to permission to describe use-case.

For #4375
2023-07-12 22:04:05 +01:00
Dan Brown
a83150131a Webhooks: Fixed failing delete-based events
Due to queue serialization.
Added a test to check a couple of delete events.
Added ApiTokenFactory to support.
Also made a couple of typing/doc updates while there.

Related to #4373
2023-07-12 16:16:12 +01:00
Jean-René ROUET
3a36d3c847 add tests for priority 2023-07-11 14:11:13 +02:00
Jean-René ROUET
4d399f6ba7 add priority on page and chapter create 2023-07-11 13:28:20 +02:00
Jean-René Rouet
b1b8067cbe Merge branch 'BookStackApp:development' into add-priority 2023-07-11 08:57:14 +02:00
Dan Brown
a9194ffb63 Updated version and assets for release v23.06.1 2023-07-05 13:04:51 +01:00
Dan Brown
2f9c1b7127 Merge branch 'development' into release 2023-07-05 13:04:30 +01:00
Dan Brown
18979e84d6 Updated tranlsator attribution and sponsors 2023-07-05 12:40:49 +01:00
Dan Brown
bf5e886d76 Updated translations with latest Crowdin changes (#4352) 2023-07-05 12:28:19 +01:00
Dan Brown
e04a1af444 Merge pull request #4344 from devdot/update-api-docs
Update API Docs
2023-07-05 12:08:51 +01:00
Dan Brown
eb2c5d00cb Audit log: Added IP address wrapping
Primarily to support long ipv6 addresses which would overflow over the
activity date.
For #4349
2023-07-05 11:37:49 +01:00
Dan Brown
96819b7bd9 Images: Updated image timestamp upon file change
For #4354
2023-07-05 11:28:03 +01:00
Dan Brown
18ee80a743 Roles: fixed error upon created_at sorting
Added test to cover core role sorting functionality.
For #4350
2023-07-04 21:52:46 +01:00
Dan Brown
1a56de6cb4 Testing: Split out role tests to management and permissions 2023-07-04 21:40:05 +01:00
Dan Brown
465989efa9 Mail: Updated to forked symfony/mailer to allow assurance of tls
Related to #4358
2023-07-04 15:21:31 +01:00
Dan Brown
bbea76668b Updated version and assets for release v23.06 2023-06-30 11:06:19 +01:00
Dan Brown
becc630acf Merge branch 'development' into release 2023-06-30 11:05:57 +01:00
Dan Brown
80635144b1 Meta: Updated dev version and translation attribution 2023-06-30 10:55:54 +01:00
Thomas Kuschan
d293171da2 API Docs: Add Missing Fields in Example Responses 2023-06-30 09:36:46 +02:00
Thomas Kuschan
174cd5a893 API Docs: Add Missing editor fields in Example Responses 2023-06-30 09:35:47 +02:00
Thomas Kuschan
ccfe38e963 API Docs: Add book_slug to Example Responses
Remove the book attribute in responses because it is never returned by the API. Currently, Chapters Create does not return book_slug! (The example response is consistent with the inconsistent API behavior)
2023-06-30 09:33:53 +02:00
Thomas Kuschan
23ae332c1b API Docs: Sort a few example responses 2023-06-30 09:27:18 +02:00
Thomas Kuschan
3a39f13420 API Docs: Remove Dates from Tags in Example Responses 2023-06-30 09:24:46 +02:00
Thomas Kuschan
ca2d2c97d4 API Docs: Add User Slugs to Example Responses 2023-06-30 09:23:02 +02:00
Dan Brown
d23cfc3d32 Updated test to match German translation 2023-06-28 23:46:59 +01:00
Dan Brown
5ea2d0c57b WYSIWYG: Fixed growing rows on Firefox
Occured when the cell contained any block content with a differnt line
height to the table cell itself.
In firefox, cells with a height would end up with an actual greater
real cell height, which messed up TinyMCE resize calculations, causing
tables to grow.
Adding default vertical-align: top, changes this behaviour to get proper
cell heights.
Related to Firefox issue: https://bugzilla.mozilla.org/show_bug.cgi?id=569645
Have tested that editor cell text align options can still be used with
this.

For #4337
2023-06-28 23:28:31 +01:00
Dan Brown
b425d0f65c Updated tinymce to v6.5.1 2023-06-28 22:45:21 +01:00
Dan Brown
63f03046b3 Updated translations with latest Crowdin changes (#4256) 2023-06-28 17:54:32 +01:00
Dan Brown
7f98906b0f Comments: Tweaked design to be more consistent and compact 2023-06-28 13:41:14 +01:00
Dan Brown
b24246085f CSS: Tweaked css heading font to fall back to body font 2023-06-28 09:35:30 +01:00
Thomas Kuschan
e47870794d API Docs: Add Missing Type in Response
Type is always returned when pages/chapters are in a contents array.
2023-06-26 10:14:10 +02:00
Thomas Kuschan
e43d85b801 API Docs: Remove id from Tag in Response 2023-06-26 10:13:02 +02:00
Dan Brown
bae0e80cee Merge pull request #4320 from devdot/improve-api-auth-exception
Improve ApiAuthException control flow
2023-06-25 23:35:19 +01:00
Dan Brown
847a57a49a Shelf permissions: Removed unused 'create' permission from view
Was causing confusion.
Added test to cover.
Also added migration to remove existing create entries to pre-emptively
avoid issues in future if 'create' is used again.
2023-06-25 23:22:49 +01:00
Dan Brown
c74a2608c4 Updated php dependencies 2023-06-24 11:32:54 +01:00
Dan Brown
dbb6c87580 Mail Config: Updated how TLS is configured
After full review of current MAIL_ENCRYPTION usage in laravel and
smyfony mailer, this updates the options in BookStack to be simplified
and specific in usage:

- Removed mail.mailers.smtp.encryption option since it did not actually
  affect anything in the current state of dependancies.
- Updated MAIL_ENCRYPTION so values of tls OR ssl will force-enable tls
  via 'scheme' option with laravel passes to the SMTP transfport, which
  Smyfony uses as an indicator to force TLS.

When MAIL_ENCRYPTION is not used, STARTTLS will still be attempted by
symfony mailer.
Updated .env files to refer to BookStack docs (which was updated for
this) and to reflect correct default port.
Related to #4342
2023-06-24 11:32:07 +01:00
Dan Brown
9ae17efce9 Shelf view: Updated books to be database sorted
Fixes issue where sorting would not match other database-sorted parts of
app due to case sensitivity differences.
Added test to cover.

For #4341
2023-06-23 16:42:40 +01:00
Dan Brown
0a485baf8b Merge pull request #4332 from BookStackApp/api_docs_tweaks
API Docs: Allowed multi-paragraph descriptions
2023-06-20 23:47:58 +01:00
Dan Brown
38883e8d46 API Docs: Allowed multi-paragraph descriptions
Added support for mulit-line endpoint descriptions via blank
intermediate lines in php controller method docblocks.

Also tweaks endpoint header design for better flexing and alignment.
2023-06-20 23:44:39 +01:00
Dan Brown
4bb2cf5c5f Pages API: Added extra helper text to read endpoint 2023-06-20 17:15:32 +01:00
Dan Brown
8b935e71d1 Pages API: Made raw_html available on page responses
To provide a way to see the original un-pre-processed database HTML
content.

For #4310
2023-06-20 17:07:46 +01:00
Dan Brown
41c3ed154b Content Permissions API: Fixed param combination bug
Fixes issue where providing owner_id alongside certain
fallback_permissions would cause the owner change not to take affect,
due to bad variable shadowing.

For #4323
2023-06-20 14:13:26 +01:00
Dan Brown
f5396ecaf0 Merge pull request #4317 from devdot/http-fetch-improve-exception-logging
Modify HttpFetchException flow to log the exception
2023-06-20 13:49:23 +01:00
Thomas Kuschan
97d46f43a7 Revert some changes to HttpFetchException 2023-06-19 08:47:47 +02:00
Dan Brown
22fc720c22 Merge pull request #4318 from devdot/improve-json-debug-exception
Change JsonDebugException to Responsable interface
2023-06-18 17:52:57 +01:00
Dan Brown
eb44748084 Merge branch 'development' of github.com:BookStackApp/BookStack into development 2023-06-17 18:22:01 +01:00
Dan Brown
00b5dd7852 Users API: Fixed incorrect created_at date on index endpoint
For #4325
2023-06-17 18:18:17 +01:00
Dan Brown
9f4450fea9 Merge pull request #4322 from BookStackApp/comments_in_editor
Added read-only comments listing into page editor
2023-06-16 13:23:40 +01:00
Dan Brown
88aae5b004 Comments: Fixed failing tests due to unset template variable 2023-06-16 13:17:11 +01:00
Dan Brown
9a2ef7ef44 Comments: Added read-only listing into page editor 2023-06-16 13:08:04 +01:00
Thomas Kuschan
74097bd47c Simplify ApiAuthException control flow
Remove unnecessary UnauthorizedException
and make ApiAuthException compatible with HttpExceptionInterface.

Move the creation of a rsponse for the exception
from ApiAuthenticate middleware into the application exception handler.
2023-06-16 10:00:02 +02:00
Thomas Kuschan
7249d947ec Change JsonDebugException to Responsable interface
In all other exceptions, when a Response is supposed to be returned,
the Responsable interface is used instead of render.
2023-06-16 09:53:12 +02:00
Thomas Kuschan
c35080d6ce Modify HttpFetchException handle to log exception
Within the flow of HttpFetchException, the actual exception from curl is preserved and logged. Make HttpFetchException a pretty exception for when it is shown to users.
2023-06-16 09:21:25 +02:00
Dan Brown
ec775aec02 Merge branch 'fix-api-404' into development 2023-06-15 17:08:51 +01:00
Dan Brown
e72cf61f7e Exceptions: Added some types, simplified some classes
During review of #4291
2023-06-15 17:07:40 +01:00
Jean-René Rouet
bb3ce845b4 Merge branch 'BookStackApp:development' into add-priority 2023-06-15 16:55:14 +02:00
Dan Brown
70be2e8c9e CSS: Reduced styles used in export formats
Extracted many main page content styles to own scss partial.
Styles could do with a more general clean-up.

Closes #4303
2023-06-14 13:19:29 +01:00
Dan Brown
610ad0d613 Updated fonts to be defined via CSS variables
Exports system remains separate due to lacking css variable support.
2023-06-14 12:53:48 +01:00
Thomas Kuschan
34d8268b2b Refactor notify exception to clean up api exception handling 2023-06-14 11:08:20 +02:00
Thomas Kuschan
321a459421 Refactor exception handling by using interface 2023-06-13 18:52:02 +02:00
Dan Brown
56a40f1b23 Merge pull request #4301 from BookStackApp/css_color_variables
CSS: Updated status colors to be CSS variables, Added dark variants
2023-06-13 15:54:27 +01:00
Dan Brown
f7ad387a10 CSS: Updated status colors to be CSS variables, Added dark variants
Needed some level of harcoding though due to callouts using colors,
which can't be css colors as DOMPDF won't understand these.
Use css variables elsewhere and added new dark variants to fit a bit
better.
2023-06-13 15:52:33 +01:00
Dan Brown
b01bbf9c89 Page Drafts: Added new "Delete Draft" action to draft menu
Provides a way for users to actually delte their user drafts where
required.
For #3927

Added test to cover new endpoint.

Makes update to MD editor #setText so that new selection is within new
range, otherwise it errors and fails operation.
2023-06-13 15:13:07 +01:00
Dan Brown
f39938c4e3 Added activity text for each activity type
Ensures some sensible text is always in webhook text data.
Also aligned some notification reporting to use centralised activity
system instead of custom success events.

For #4216
2023-06-12 16:47:36 +01:00
Jean-René ROUET
458cea3644 [API] add priority in book read
[API] add priority in chapter create and update
[API] add priority in page create and update
2023-06-12 15:12:46 +02:00
Dan Brown
af0b4fa851 Search: Updated popular items query, load parent book for chapters/pages
Primarily intended to show parent book for chapters when moving/copying
pages, since the default parent selector interfaces, which used the
entity-selector search endpoint, would run this popular query when no
term was present as a default backup.

For #4264
2023-06-10 15:08:07 +01:00
Dan Brown
777027bc48 Permissions: Updated guest user handling so additional roles apply
Previously additional roles would only partially apply (system or "all"
permissions). This aligns the query-handling of permissions so that
additional roles will be used for permission queries.

Adds migration to detach existing roles as a safety precaution since
this is likely to widen permissions in scenarios that the public user
has other roles assigned already.

For #1229
2023-06-10 11:37:01 +01:00
Dan Brown
1e220c473f API: Fixed misaligned image datetime format
For #4294
2023-06-10 10:54:56 +01:00
Dan Brown
59c7077fd9 Fixed error on pages without comments 2023-06-09 19:21:49 +01:00
Dan Brown
07de6ecdc5 Merge pull request #4286 from BookStackApp/comment_threads
Comment threads
2023-06-09 17:39:02 +01:00
Dan Brown
19e39ddd1f Comments: Updated reply-to and general styling
Updated reply inidicator to fit with new nesting system, only showing on
view when nest within nesting structure.

Updated the general design to be a bit cleaner and better adapt on
mobile.

Tested on FF+Chrome, inc. dark mode.
2023-06-09 17:36:30 +01:00
Dan Brown
3bede42121 Comments: Added visual nesting limit, added nesting test 2023-06-09 11:12:39 +01:00
Dan Brown
3b46b92bb9 Comments: Updated to show form in expected location
Includes a change of create response to use a branch as a template.
2023-06-08 15:09:54 +01:00
Thomas Kuschan
9ba7d1e6c5 Fix "HTTP 500 on not found" bug #4290 2023-06-08 10:50:12 +02:00
Thomas Kuschan
ecf99fa0ed Add test showing the "HTTP 500 on not found" bug 2023-06-08 09:53:53 +02:00
Dan Brown
154924cc0c Comments: updated component and split out code
Split out comment component code so single-comment actions (delete, edit) are handled within their own compontent.
Modernised existing component code.
2023-06-07 17:47:37 +01:00
Dan Brown
4b9f6beb37 Comments: Updated to show as nested threads
Initial functional implementation, a lot of tweaking and adapting to be
done.
2023-06-07 13:24:49 +01:00
Dan Brown
88785aa71b Page display pointer: Considerably improved accessibility
- Updated pointer to move within content DOM so that you can back-focus
  into the pointer if desired.
- Added new "Section select mode" which toggles focusabiltiy for main
  content sections, with ability to show pointer via enter press on
  these.
- Updated pointer with proper input/button labelling.

Tested via orca screen reader on Firefox/Fedora/Gnome.
For #3975
2023-05-31 16:44:20 +01:00
Dan Brown
0323ebccd3 Chapters API: Allowed move via book_id property
Aligns it with pages and with the book_id property already being part of
the API.
For #4272.
2023-05-30 20:55:24 +01:00
Dan Brown
3f5dc10cd4 Altered ldap_connect usage, cleaned up LDAP classes
Primarily updated ldap_connect to avoid usage of deprecated syntax.
Updated tests and service to handle as expected.
Cleaned up syntax and types in classes while there.

Closes #4274
2023-05-30 13:12:00 +01:00
Dan Brown
242d23788d Merge pull request #4265 from BookStackApp/image_manager_responsive
Enhanced Responsive Image Manager
2023-05-29 16:52:55 +01:00
Dan Brown
08c73f02c9 Removed forced initial image manager display 2023-05-29 16:23:37 +01:00
Dan Brown
a139c2a8a2 Image manager: Improved screen reader usage
Added extra labels, or removed duplicate info, to improve screen reader
ux after testing via gnome/fedora/firefox screen reader usage testing.
2023-05-29 16:21:44 +01:00
Dan Brown
f5ef52ca59 Image manager: cleaned up style changes, dark mode support
- Updated tab handling to be smarter on initial tab selection, to first
  target non-hidden tab panels where they may be handled server-side.
- Extracted contained search box handling styles to _forms.scss, after
  merging with image-manager-specific styles since this is only usage of
  contained variant.
- Aligned focus handling on image manager UI elements.
2023-05-29 15:50:36 +01:00
Dan Brown
948e95e1ad Updated test to align with image manager HTML changes 2023-05-29 15:16:16 +01:00
Dan Brown
cd4b612019 Image update API: added update image file ability 2023-05-29 15:06:17 +01:00
Dan Brown
f78c0635ee Fixed bad /api docs redirection on sub path
Direct route redirect does not seem to go via standard URL generator so
misses off generation via base URL.
2023-05-29 14:41:59 +01:00
Dan Brown
e3c4a9d167 Added the ability to replace existing image files
- Updated UI with image form dropdown containing delete and replace
  image actions.
- Adds new endpoint and service/repo handling for replacing existing
  image.
- Includes tests to cover.
2023-05-28 17:32:22 +01:00
Dan Brown
9ff7c97911 Image manager: Added extra detail below image edit form 2023-05-28 12:07:19 +01:00
Dan Brown
89d6d862fa Image manager: extracted lang text, updated anims and search cancel
- Updated search cancel to only show when a search is active.
- Updated gallery image load animation to be much faster.
2023-05-28 11:37:49 +01:00
Dan Brown
946c9ae804 Image manager: supported a tabbed interface on mobile
Makes interface relatively usable now on mobile sizes.
Required updating of tab handling to support tabs being active at only
mobile screen sizes, include change on resize, upon support for
potentially nested tab usage.
Tab component will now search within sensible depths for finding its own
tabs and panels to control.
2023-05-27 16:58:10 +01:00
Dan Brown
dc6133c4c4 Image manager: added ability to trigger load more via scroll 2023-05-26 18:05:29 +01:00
Dan Brown
6c91e09c73 Image manager: Redesigned header bar(s) 2023-05-26 14:30:59 +01:00
Dan Brown
e467324658 Updated image manager to use grid-based css 2023-05-24 17:07:32 +01:00
Dan Brown
4c726201f9 Merge pull request #4262 from BookStackApp/command_cleanup
Command cleanup & alignment
2023-05-24 13:22:25 +01:00
Dan Brown
431aeefdda Updated command classes to include "Command" in name 2023-05-24 13:21:46 +01:00
Dan Brown
c0620da9f8 Aligned command class code
- Aligned usage of injecting through handler.
- Aligned handler return type.
- Aligned argument and arg desc format.
- Aligned lack of constructor.
2023-05-24 12:59:50 +01:00
Dan Brown
0704f1bd0d Covered untested commands with testing 2023-05-24 10:34:43 +01:00
Dan Brown
3b31ac75ec Merge pull request #4247 from BookStackApp/controller_cleanup
Revised `app/` folder layout/structure
2023-05-24 09:12:49 +01:00
Dan Brown
df6326e5ab Fixed failing references after controller/file reshuffle 2023-05-24 09:06:15 +01:00
Dan Brown
4ac8ecad6b Updated version and assets for release v23.05.2 2023-05-23 12:36:46 +01:00
Dan Brown
903e88c700 Merge branch 'development' into release 2023-05-23 12:36:29 +01:00
Dan Brown
c0d5e158d7 Updated translation attribution before v23.05.2 2023-05-23 12:32:39 +01:00
Dan Brown
99377d43c1 Updated php deps 2023-05-22 20:52:50 +01:00
Dan Brown
ebb1942fb8 Updated translations with latest Crowdin changes (#4239) 2023-05-22 20:51:22 +01:00
Dan Brown
152f7f3ad0 Merge pull request #4252 from BookStackApp/cli_update_2
Updated System CLI
2023-05-22 20:45:32 +01:00
Dan Brown
8a03442b5b Merge pull request #4254 from BookStackApp/code_active_line
Updated code view block line highlighting to only show on focus
2023-05-22 20:44:05 +01:00
Dan Brown
e591f4896e Allowed attachment drag via main text link
Enables easier sorting and dragging of box into content.
Related to #591
2023-05-22 20:23:19 +01:00
Dan Brown
6a7bc68b61 Allowed button-based multi-file uploads
Likely something that worked via dropzone before.
This adds support for our custom dropzone file handling.
Related to #4241
2023-05-22 14:20:20 +01:00
Dan Brown
924f517217 Updated code view block line highlighting to only show on focus
The default 1st line highlighting confused users when existing on
read-only blocks as it was not clear this represented the active line.
This changes the highlight to only show when the block is focused upon.
2023-05-22 14:05:07 +01:00
Dan Brown
150b40edc1 Updated System CLI
- Fixed a range of additional issues involving symlinks.
- Fixed incorrect app locating relative to system cli.
2023-05-22 10:28:12 +01:00
Dan Brown
141eecb858 Cleaned up namespacing in routes
Also moved home controller and moved controllers up a level in http.
2023-05-18 20:57:05 +01:00
Dan Brown
295cd01605 Played around with a new app structure 2023-05-17 17:56:55 +01:00
Dan Brown
ed96aa820e Updated version and assets for release v23.05.1 2023-05-08 16:05:50 +01:00
Dan Brown
63ec079b7b Merge branch 'development' into release 2023-05-08 16:04:51 +01:00
Dan Brown
c17906c758 Updated translator attribution before release v23.05.1 2023-05-08 16:04:02 +01:00
Dan Brown
62d5701578 Merge pull request #4229 from BookStackApp/cli-update
Updated system CLI
2023-05-08 15:21:04 +01:00
Dan Brown
9f1a6947ab Updated system CLI
- Fixed wrong env details being used on restore.
- Updated update-url on restore actually work.
- Added better support for symlinked locations.
- Added warning against updating in docker-like (non git controlled)
  environments.
2023-05-08 15:16:30 +01:00
Dan Brown
ae90776927 Updated translations with latest Crowdin changes (#4211) 2023-05-08 14:49:01 +01:00
Dan Brown
4489f65371 Fixed code block line-number bar showing in exports
Also fixed in print view.
Likely crept in during CM6 changes.

For #4215
2023-05-08 14:45:45 +01:00
Dan Brown
ee1e047964 Updated php deps, formatted command changes 2023-05-08 14:37:01 +01:00
Dan Brown
8846f7d255 Prevented shorcuts activating when in codemirror areas
For #4227
2023-05-08 14:28:03 +01:00
Dan Brown
2523cee0e2 WYSWIYG code blocks: copied head styles into shadow root
Currently only link-based styles are made available in the shadow root
code editor environment, this adds normal styles to apply any user-added
via custom head content.

Fixes #4228
2023-05-08 12:21:53 +01:00
Dan Brown
b5cc0a8e38 Fixed added padding around hr tags in details blocks
Due to manual handling & wrapping of non-block content in details block
not taking hr elements into account.
For #3963
2023-05-08 12:01:52 +01:00
Dan Brown
3bcbf6b9c5 Added WYSWIYG editor code editor cancel focus return
Focus now returns to the editor properly when you quit out the code
editor without saving.
This also sets the return location to be correct on normal saving (Would
sometimes jump to the end of the document).

For #4109.
2023-05-07 19:36:10 +01:00
Dan Brown
573bc3ec45 Added force option for update-url command
Includes test to cover.
Closes #4223
2023-05-06 23:05:25 +01:00
Dan Brown
d485fcb3db Updated version and assets for release v23.05 2023-05-03 11:05:33 +01:00
Dan Brown
0f895668a4 Merge branch 'development' into release 2023-05-03 11:03:29 +01:00
Dan Brown
57bdd83d8c Added mostodon badge in readme, updated CLI 2023-05-03 10:57:09 +01:00
Dan Brown
ce0b75294f Set page include limit to be 3 as expected instead of 4 2023-05-02 12:44:55 +01:00
Dan Brown
4bb2b31bc9 Updated translator attribution pre v23.05 release 2023-05-01 19:39:20 +01:00
Dan Brown
9d74508ae3 Updated translations with latest Crowdin changes (#4163) 2023-05-01 19:37:49 +01:00
Dan Brown
c41baa1b76 Updated CLI & PHP deps, added gitignore for local composer 2023-05-01 18:44:46 +01:00
Dan Brown
cd32597d4d Fixed broken favourites in code editor 2023-05-01 18:43:03 +01:00
Dan Brown
8594656f6e Merge pull request #4206 from BookStackApp/system_cli
Added System CLI
2023-04-28 19:17:38 +01:00
Dan Brown
0aca1c2332 Added system cli, and created backups directory 2023-04-28 19:08:45 +01:00
Dan Brown
8c738aedee Added sessionindex to SAML2 single logout request to idp
related to  #3936
2023-04-28 13:55:25 +01:00
Dan Brown
f64ce71afc Added oidc_id_token_pre_validate logical theme event
For #4200
2023-04-27 23:40:14 +01:00
Dan Brown
277d5392fb Merge branch 'esakkiraja100116/development' into development 2023-04-27 16:34:14 +01:00
Dan Brown
23c35af9ef Review of #4202, Rolled out to other searches, added testing 2023-04-27 16:33:24 +01:00
esakkiraja100116
78fecdfcb0 suggesstion issue fix (#4175) 2023-04-27 16:32:39 +01:00
SnowCode
a9d952560d Adding a video { width: 100%; } (#4204)
* Adding a video { width: 100%; }

This is to prevent that videos included in pages don't exceed the page border

* Reverting precedent commit

* Adding a video { max-width: 100% } instead
2023-04-27 15:58:35 +01:00
Dan Brown
56f234d1ee Review of #4192, Fixed formatting and added test 2023-04-27 15:52:16 +01:00
jasonF1000
011800d425 changed PageContent.php to accept nested includes (#4192)
* changed app/Entities/Tools/PageContent.php to accept nested include levels. Tested it and it works.

* changed recommendations

This loop is now only around parsePageIncludes and bugfixes the space indentation.

* Update PageContent.php

fix spaces
2023-04-27 15:51:46 +01:00
Dan Brown
647ce6c237 Fixed sort urls with no params not building full path
The provided partial path would be return which may not resolve to the
full URL when used on systems like those hosting BookStack on a
sub-path.
Fixes #4201
2023-04-27 13:49:22 +01:00
Dan Brown
607da73109 Merge pull request #4193 from BookStackApp/custom_dropzone
Custom dropzone implementation
2023-04-27 13:43:38 +01:00
Dan Brown
1135d477ba Fixed linting and failing test issues from dropzone work 2023-04-27 13:31:03 +01:00
Dan Brown
a4a96a3df7 Dropzone: Adjusted styles for dark mode 2023-04-27 12:55:05 +01:00
Dan Brown
38e8a96dcd Removed dropzone from package and attribution list 2023-04-26 23:35:25 +01:00
Dan Brown
9a17656f88 dropzone: Addressed existing todos, cleaned attachment ux
Updated dom layout of attahcments to prevent nested dropzones (No issue
but potential to be one) and updated edit form dropzone handling so the
dropzone item card was not as distracting.
2023-04-26 23:31:38 +01:00
Dan Brown
e36cdaad0d Updated attachments to work with new dropzone
- Fixes existing broken attachment edit tabs.
- Redesigns area to move away from old tabbed interface.
- Integrates new dropzone system, for both addition and edit.
2023-04-26 16:41:34 +01:00
Dan Brown
722c38d576 Image manager: fix upload control for drawing, updated styles
- Tightened image manager styles to address things that looked akward.
- Prevented visiblity/use of upload controls for drawings.
- Updated dropzone to use error handling from validation messages.
2023-04-26 14:25:56 +01:00
Dan Brown
8cd6c797e8 Merge branch 'development' of github.com:BookStackApp/BookStack into development 2023-04-26 01:43:16 +01:00
Dan Brown
dff45e2c5d Fixed broken shortcut hint overlay
Also updated event handler usage to use abort controller while there.
2023-04-26 01:42:12 +01:00
Dan Brown
61d2ea6ac7 Dropzone: Polished image manager elements
- Added file placeholder for non-image uploads.
- Added use of upload limits.
- Removed upload timeout variable.
- Added pass-through and usage of filetypes.
- Extracted some view text to language files and made use of existing
  text.
2023-04-25 16:41:39 +01:00
Esakkiraja
752562d23d .vscode folder is added in .gitignore file (#4197)
Squash of 7 commits.

---------

Co-authored-by: esakkiraja100116 <esakkiraja100116@gmai.com>
2023-04-25 15:25:31 +01:00
Dan Brown
b21a9007c5 Dropzone: Developed ux further
- Added image manager button for uploads.
- Added image manager placeholder sidebar text for guidance.
- Improved dropzone layer styling.
- Removed old dropzone styles.
- Got success events and auto-hide working.
- Updated upload items to animate out.
2023-04-25 13:10:25 +01:00
Dan Brown
a8fc29a31e Dropzone: started on design/ui of uploading
- Added new wider target handling.
- Updated upload item dom with design and seperate "landing" zone.
- Added new helper for simple dom element creation.
2023-04-24 23:24:58 +01:00
Dan Brown
36116a45d4 Dropzone: Swapped fetch for XHR for progress tracking 2023-04-24 18:18:08 +01:00
Dan Brown
23915c3b1a Started custom dropzone implementation 2023-04-24 16:19:20 +01:00
Dan Brown
55af22b487 Merge pull request #4191 from tigsikram/fix-api-docs-timestamp
Fix timestamp in API docs example response
2023-04-24 14:46:40 +01:00
Mark Weiler
01f3f4d315 Fix timestamp in API docs example response 2023-04-24 11:19:00 +02:00
Dan Brown
58cadce052 Merge branch 'feature/mail-verify-peer' into development 2023-04-23 15:05:13 +01:00
Dan Brown
1de72d09ca Mail: updated peer verify option name and added test 2023-04-23 15:04:35 +01:00
Dan Brown
fa6fcc1c1c Added clojure code language option
For #4112
2023-04-23 14:16:31 +01:00
Dan Brown
a46b438a4c Merge branch 'wkhtmltopdf-env-example' into development 2023-04-21 11:56:31 +01:00
Dan Brown
7505443a0c Updated complete env wkhtml text and added advisory
Added advisory to start to refer to docs for full details.
Updated added WKHTMLTOPDF option text.
2023-04-21 11:54:23 +01:00
Dan Brown
f837083c12 Updated php deps 2023-04-21 11:37:41 +01:00
Dan Brown
e1bd13f481 Edits from reviewing public events page 2023-04-20 16:54:11 +01:00
Dan Brown
c74f7cc628 Documented public JS events used
Related to #4179
2023-04-20 16:25:48 +01:00
Dan Brown
9f467f4052 Merge pull request #4181 from BookStackApp/js_formatting
Added standard JS formatting via ESLint
2023-04-19 23:01:10 +01:00
Dan Brown
974390688d ESLINT: Added GH action and details to dev docs 2023-04-19 22:56:55 +01:00
Dan Brown
da3ae3ba8b ESLINT: Addressed remaining detected issues 2023-04-19 15:20:04 +01:00
Dan Brown
0519e58fbf ESLINT: Started inital pass at addressing issues 2023-04-19 10:46:13 +01:00
Dan Brown
e711290d8b Ran eslint fix on existing codebase
Had to do some manual fixing of the app.js file due to misplaced
comments
2023-04-18 22:20:02 +01:00
Dan Brown
752ee664c2 Added code formatting standard via eslint 2023-04-18 22:19:27 +01:00
Dan Brown
69d03042c6 Merge pull request #3617 from BookStackApp/codemirror6
Upgrade to codemirror 6
2023-04-18 15:35:39 +01:00
Dan Brown
baf5edd73a CM6: Further fixes/improvements after testing
- Updated event naming to be "cm6" when codemirror-specific.
- Removed cm block border in md editor to prevent double bordering.
- Updated copy handling to fallback to execCommand.
2023-04-18 15:08:17 +01:00
Dan Brown
3e738b1471 CM6: Fixed a range of issues during browser testing
- Fixed some keybindings not running as expected, due to some editor
  defaults overriding or further actions taking place since the action
  would not indicate it's been dealt with (by returning boolean).
- Fixed spacing/border-radius being used on codeblocks on non-intended
  areas like the MD editor.
- Fixed lack of BG on default light theme, visible on full screen md
  editor.
- Fixed error thrown when the user does not have access to change the
  current editor (Likely non-cm related existing issue)
2023-04-18 14:21:22 +01:00
Dan Brown
94f464cd14 CM6: Added tabbing, fixed dark mode border in WYSIWYG 2023-04-18 13:43:59 +01:00
Dan Brown
900571ac9c CM6: Updated for popup editor, added new interface
New simple interface added for abstraction of CM editor in simple
use-cases, just to provide common actions like get/set content, focus
and set mode.
2023-04-17 13:24:29 +01:00
Dan Brown
09fd0bc5b7 CM6: Got WYSIWYG code blocks working
Required monkey-patch to work around potential codemirror issue with
shadowdom+iframe usage.
Also updated JS packages to latest versions.
2023-04-16 23:50:11 +01:00
Dan Brown
74b4751a1c CM6: Aligned styling with existing, improved theme handling 2023-04-16 16:05:16 +01:00
Dan Brown
74b76ecdb9 Updated cm6 theme handling to allow extension via API
Uses our custom event system, uses methods that take callables so that
internal dependancies can be passed.
2023-04-15 15:35:41 +01:00
Dan Brown
9874a53206 Added cm6 strategy for splitting and dyn. loading langs
Split out legacy modes to their own dynamically imported bundle to
reduce main code bundle size.
2023-04-14 18:08:57 +01:00
Dan Brown
257a703878 Addressed existing cm6 todos
- Updated clipboard handling
  - Removed old clipboard package for browser-native API.
- Updated codemirror editor events to use new props for new data types.
2023-04-14 14:08:40 +01:00
Dan Brown
fdda813d5f Cleaned up change handling in cm6 editor action handling 2023-04-13 17:38:11 +01:00
Dan Brown
6f45d34bf8 Finished update pass of all md editor actions to cm6 2023-04-13 17:18:32 +01:00
Dan Brown
32c765d0c3 Updated another range of actions for cm6 2023-04-13 12:51:52 +01:00
Dan Brown
9813c94720 Made a start on updating editor actions 2023-04-11 13:16:04 +01:00
Dan Brown
da3e4f5f75 Got md shortcuts working, marked actions for update 2023-04-11 11:48:58 +01:00
Dan Brown
572037ef1f Got markdown editor barely functional
Updated content sync and preview scoll sync to work.
Many features commented out until they can be updated.
2023-04-10 15:01:44 +01:00
Dan Brown
50f3c10f19 Merge branch 'v23.02-branch' into development 2023-04-07 18:12:00 +01:00
Dan Brown
6c577ac3bf Updated version and assets for release v23.02.3 2023-04-07 18:07:32 +01:00
Dan Brown
31cc2423d2 Merge branch 'v23.02-branch' into release 2023-04-07 18:07:09 +01:00
Dan Brown
3f3f221e0d Updated translator attribution before release v23.02.3 2023-04-07 18:06:44 +01:00
Dan Brown
d0f970fe4f Updated translations with latest Crowdin changes (#4131) 2023-04-07 18:00:03 +01:00
Dan Brown
95b75c067f Updated translations with latest Crowdin changes (#4131) 2023-04-07 17:59:34 +01:00
Dan Brown
81134e7071 Fixed tag numbering in last commit 2023-04-07 17:54:17 +01:00
Dan Brown
e722ee4268 Fixed click issue with tag suggestions in safari
Updated selectable elements to be divs instead of buttons since Safari
akwardly does not focus on buttons on click.
Also standardised keyboard handling to our standard nav class.
Also addressed empty tag values showing in results.
For #4139
2023-04-07 17:50:57 +01:00
Dan Brown
fd674d10e3 Fixed error upon user delete with no migration id
Fixes #4162
2023-04-07 15:57:21 +01:00
Dan Brown
4835a0dcb1 Cleaned up old token services 2023-04-04 10:44:38 +01:00
Daiki Urata
d353e87ca1 Add WKHTMLTOPDF to .env.example.complete 2023-03-30 17:58:17 +09:00
Dan Brown
8e64324d62 Merge branch 'v23.02-branch' into development 2023-03-25 12:33:59 +00:00
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
dde38e91b5 Fixed delete role failing with no migrate role provided
For #4128
2023-03-25 12:08:45 +00:00
Dan Brown
970088a8a1 Updated php deps 2023-03-24 14:46:30 +00:00
Dan Brown
0e43618dda 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-24 14:43:48 +00:00
Vincent Bernat
f2293a70f8 Allow a user to disable peer check when using TLS/STARTTLS
This is useful when developing and on Docker setups. Despite setting
encryption to null, if a server supports STARTTLS with a self-signed
certificate, the mailer try to upgrade the connection with STARTTLS.
2023-03-24 09:34:37 +01:00
Dan Brown
dce5123452 Added own twig/smarty packages for cm6 lang support 2023-03-21 20:53:35 +00:00
Dan Brown
c81cb6f2af Merge branch 'development' into codemirror6 2023-03-19 10:22:44 +00:00
Dan Brown
9b66e93b15 Merge pull request #4103 from BookStackApp/image_api
Image API Endpoints
2023-03-15 11:45:36 +00:00
Dan Brown
402eb845ab Added examples, updated docs for image gallery api endpoints 2023-03-15 11:37:03 +00:00
Dan Brown
3a808fd768 Added phpunit tests to cover image API endpoints 2023-03-14 19:29:08 +00:00
Dan Brown
d9eec6d82c Started Image API build 2023-03-14 12:19:19 +00:00
Dan Brown
6357056d7b Updated php deps 2023-03-13 21:03:00 +00:00
Dan Brown
a369971e04 Merge pull request #4099 from BookStackApp/permissions_api
Content-Permissions API Endpoints
2023-03-13 20:55:44 +00:00
Dan Brown
1903924829 Added content-perms API examples and docs tweaks 2023-03-13 20:41:32 +00:00
Dan Brown
0de7530059 Tweaked content permission endpoints, covered with tests 2023-03-13 20:06:52 +00:00
Dan Brown
c42956bcaf Started build of content-permissions API endpoints 2023-03-13 13:18:33 +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
c148e2f3d9 Added esbuild bundle inspection metafile 2023-02-17 22:37:13 +00:00
Dan Brown
f51036b203 Added newer languages where possible
Cannot find existing option for twig/smarty, need to look other methods.
2023-02-17 22:14:34 +00:00
Dan Brown
9135a85de4 Merge branch 'codemirror6' into codemirror6_take2 2023-02-17 21:28:23 +00:00
Dan Brown
9fd7a6abed Added dark theme handling 2022-08-04 14:19:04 +01:00
Dan Brown
4757ed9453 Converted codemirror languges to new packages where available
Does increase bundle size massively though, Will need to think about
solutions for this.
2022-08-04 13:33:51 +01:00
Dan Brown
97146a6359 Added handling of codemirror 6 code languages 2022-08-03 19:40:16 +01:00
Dan Brown
d4f2fcdf79 Started codemirror update, In broken state 2022-08-02 20:11:02 +01:00
1001 changed files with 23369 additions and 11685 deletions

View File

@@ -37,8 +37,10 @@ MAIL_FROM=bookstack@example.com
# SMTP mail options
# These settings can be checked using the "Send a Test Email"
# feature found in the "Settings > Maintenance" area of the system.
# For more detailed documentation on mail options, refer to:
# https://www.bookstackapp.com/docs/admin/email-webhooks/#email-configuration
MAIL_HOST=localhost
MAIL_PORT=1025
MAIL_PORT=587
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null

View File

@@ -3,6 +3,10 @@
# Each option is shown with it's default value.
# Do not copy this whole file to use as your '.env' file.
# The details here only serve as a quick reference.
# Please refer to the BookStack documentation for full details:
# https://www.bookstackapp.com/docs/
# Application environment
# Can be 'production', 'development', 'testing' or 'demo'
APP_ENV=production
@@ -65,22 +69,19 @@ DB_PASSWORD=database_user_password
# 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 configuration
# Refer to https://www.bookstackapp.com/docs/admin/email-webhooks/#email-configuration
MAIL_DRIVER=smtp
# Mail sending options
MAIL_FROM=mail@bookstackapp.com
MAIL_FROM_NAME=BookStack
# SMTP mail options
MAIL_HOST=localhost
MAIL_PORT=1025
MAIL_PORT=587
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_VERIFY_SSL=true
# Command to use when email is sent via sendmail
MAIL_SENDMAIL_COMMAND="/usr/sbin/sendmail -bs"
# Cache & Session driver to use
@@ -322,6 +323,13 @@ FILE_UPLOAD_SIZE_LIMIT=50
# Can be 'a4' or 'letter'.
EXPORT_PAGE_SIZE=a4
# Set path to wkhtmltopdf binary for PDF generation.
# Can be 'false' or a path path like: '/home/bins/wkhtmltopdf'
# When false, BookStack will attempt to find a wkhtmltopdf in the application
# root folder then fall back to the default dompdf renderer if no binary exists.
# Only used if 'ALLOW_UNTRUSTED_SERVER_FETCHING=true' which disables security protections.
WKHTMLTOPDF=false
# Allow <script> tags in page content
# Note, if set to 'true' the page editor may still escape scripts.
ALLOW_CONTENT_SCRIPTS=false
@@ -351,6 +359,15 @@ ALLOWED_IFRAME_HOSTS=null
# 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"
# A list of the sources/hostnames that can be reached by application SSR calls.
# This is used wherever users can provide URLs/hosts in-platform, like for webhooks.
# Host-specific functionality (usually controlled via other options) like auth
# or user avatars for example, won't use this list.
# Space seperated if multiple. Can use '*' as a wildcard.
# Values will be compared prefix-matched, case-insensitive, against called SSR urls.
# Defaults to allow all hosts.
ALLOWED_SSR_HOSTS="*"
# The default and maximum item-counts for listing API requests.
API_DEFAULT_ITEM_COUNT=100
API_MAX_ITEM_COUNT=500
@@ -372,4 +389,4 @@ LOG_FAILED_LOGIN_CHANNEL=errorlog_plain_webserver
# 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
IP_ADDRESS_PRECISION=4

View File

@@ -311,3 +311,49 @@ m4tthi4s :: French
toras9000 :: Japanese
pathab :: German
MichelSchoon85 :: Dutch
Jøran Haugli (haugli92) :: Norwegian Bokmal
Vasileios Kouvelis (VasilisKouvelis) :: Greek
Dremski :: Bulgarian
Frédéric SENE (nothingfr) :: French
bendem :: French
kostasdizas :: Greek
Ricardo Schroeder (brownstone666) :: Portuguese, Brazilian
Eitan MG (EitanMG) :: Hebrew
Robin Flikkema (RobinFlikkema) :: Dutch
Michal Gurcik (mgurcik) :: Slovak
Pooyan Arab (pooyanarab) :: Persian
Ochi Darma Putra (troke12) :: Indonesian
H.-H. Peng (Hsins) :: Chinese Traditional
Mosi Wang (mosiwang) :: Chinese Traditional
骆言 (LawssssCat) :: Chinese Simplified
Stickers Gaming Shøw (StickerSGSHOW) :: French
Le Van Chinh (Chino) (lvanchinh86) :: Vietnamese
Rubens nagios (rubenix) :: Catalan
Patrick Dantas (pa-tiq) :: Portuguese, Brazilian
Michal (michalgurcik) :: Slovak
Nepomacs :: German
Rubens (rubenix) :: Catalan
m4z :: German; German Informal
TheRazvy :: Romanian
Yossi Zilber (lortens) :: Hebrew; Uzbek
desdinova :: French
Ingus Rūķis (ingus.rukis) :: Latvian
Eugene Pershin (SilentEugene) :: Russian
周盛道 (zhoushengdao) :: Chinese Simplified
hamidreza amini (hamidrezaamini2022) :: Persian
Tomislav Kraljević (tomislav.kraljevic) :: Croatian
Taygun Yıldırım (yildirimtaygun) :: Turkish
robing29 :: German
Bruno Eduardo de Jesus Barroso (brunoejb) :: Portuguese, Brazilian
Igor V Belousov (biv) :: Russian
David Bauer (davbauer) :: German
Guttorm Hveem (guttormhveem) :: Norwegian Bokmal
Minh Giang Truong (minhgiang1204) :: Vietnamese
Ioannis Ioannides (i.ioannides) :: Greek
Vadim (vadrozh) :: Russian
Flip333 :: German Informal; German
Paulo Henrique (paulohsantos114) :: Portuguese, Brazilian
Dženan (Dzenan) :: Swedish
Péter Péli (peter.peli) :: Hungarian
TWME :: Chinese Traditional
Sascha (Man-in-Black) :: German

16
.github/workflows/lint-js.yml vendored Normal file
View File

@@ -0,0 +1,16 @@
name: lint-js
on: [push, pull_request]
jobs:
build:
if: ${{ github.ref != 'refs/heads/l10n_development' }}
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v1
- name: Install NPM deps
run: npm ci
- name: Run formatting check
run: npm run lint

6
.gitignore vendored
View File

@@ -1,5 +1,7 @@
/vendor
/node_modules
/.vscode
/composer
Homestead.yaml
.env
.idea
@@ -21,8 +23,10 @@ yarn.lock
nbproject
.buildpath
.project
.nvmrc
.settings/
webpack-stats.json
.phpunit.result.cache
.DS_Store
phpstan.neon
phpstan.neon
esbuild-meta.json

View File

@@ -1,34 +1,24 @@
<?php
namespace BookStack\Http\Controllers\Auth;
namespace BookStack\Access\Controllers;
use BookStack\Auth\Access\EmailConfirmationService;
use BookStack\Auth\Access\LoginService;
use BookStack\Auth\UserRepo;
use BookStack\Access\EmailConfirmationService;
use BookStack\Access\LoginService;
use BookStack\Exceptions\ConfirmationEmailException;
use BookStack\Exceptions\UserTokenExpiredException;
use BookStack\Exceptions\UserTokenNotFoundException;
use BookStack\Http\Controllers\Controller;
use BookStack\Http\Controller;
use BookStack\Users\UserRepo;
use Exception;
use Illuminate\Http\Request;
class ConfirmEmailController extends Controller
{
protected EmailConfirmationService $emailConfirmationService;
protected LoginService $loginService;
protected UserRepo $userRepo;
/**
* Create a new controller instance.
*/
public function __construct(
EmailConfirmationService $emailConfirmationService,
LoginService $loginService,
UserRepo $userRepo
protected EmailConfirmationService $emailConfirmationService,
protected LoginService $loginService,
protected UserRepo $userRepo
) {
$this->emailConfirmationService = $emailConfirmationService;
$this->loginService = $loginService;
$this->userRepo = $userRepo;
}
/**

View File

@@ -1,9 +1,9 @@
<?php
namespace BookStack\Http\Controllers\Auth;
namespace BookStack\Access\Controllers;
use BookStack\Actions\ActivityType;
use BookStack\Http\Controllers\Controller;
use BookStack\Activity\ActivityType;
use BookStack\Http\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;

View File

@@ -1,10 +1,10 @@
<?php
namespace BookStack\Http\Controllers\Auth;
namespace BookStack\Access\Controllers;
use BookStack\Auth\Access\LoginService;
use BookStack\Auth\User;
use BookStack\Access\LoginService;
use BookStack\Exceptions\NotFoundException;
use BookStack\Users\Models\User;
trait HandlesPartialLogins
{

View File

@@ -1,13 +1,13 @@
<?php
namespace BookStack\Http\Controllers\Auth;
namespace BookStack\Access\Controllers;
use BookStack\Auth\Access\LoginService;
use BookStack\Auth\Access\SocialAuthService;
use BookStack\Access\LoginService;
use BookStack\Access\SocialAuthService;
use BookStack\Exceptions\LoginAttemptEmailNeededException;
use BookStack\Exceptions\LoginAttemptException;
use BookStack\Facades\Activity;
use BookStack\Http\Controllers\Controller;
use BookStack\Http\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

View File

@@ -1,14 +1,14 @@
<?php
namespace BookStack\Http\Controllers\Auth;
namespace BookStack\Access\Controllers;
use BookStack\Actions\ActivityType;
use BookStack\Auth\Access\LoginService;
use BookStack\Auth\Access\Mfa\BackupCodeService;
use BookStack\Auth\Access\Mfa\MfaSession;
use BookStack\Auth\Access\Mfa\MfaValue;
use BookStack\Access\LoginService;
use BookStack\Access\Mfa\BackupCodeService;
use BookStack\Access\Mfa\MfaSession;
use BookStack\Access\Mfa\MfaValue;
use BookStack\Activity\ActivityType;
use BookStack\Exceptions\NotFoundException;
use BookStack\Http\Controllers\Controller;
use BookStack\Http\Controller;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;

View File

@@ -1,10 +1,10 @@
<?php
namespace BookStack\Http\Controllers\Auth;
namespace BookStack\Access\Controllers;
use BookStack\Actions\ActivityType;
use BookStack\Auth\Access\Mfa\MfaValue;
use BookStack\Http\Controllers\Controller;
use BookStack\Access\Mfa\MfaValue;
use BookStack\Activity\ActivityType;
use BookStack\Http\Controller;
use Illuminate\Http\Request;
class MfaController extends Controller

View File

@@ -1,15 +1,15 @@
<?php
namespace BookStack\Http\Controllers\Auth;
namespace BookStack\Access\Controllers;
use BookStack\Actions\ActivityType;
use BookStack\Auth\Access\LoginService;
use BookStack\Auth\Access\Mfa\MfaSession;
use BookStack\Auth\Access\Mfa\MfaValue;
use BookStack\Auth\Access\Mfa\TotpService;
use BookStack\Auth\Access\Mfa\TotpValidationRule;
use BookStack\Access\LoginService;
use BookStack\Access\Mfa\MfaSession;
use BookStack\Access\Mfa\MfaValue;
use BookStack\Access\Mfa\TotpService;
use BookStack\Access\Mfa\TotpValidationRule;
use BookStack\Activity\ActivityType;
use BookStack\Exceptions\NotFoundException;
use BookStack\Http\Controllers\Controller;
use BookStack\Http\Controller;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;

View File

@@ -1,10 +1,10 @@
<?php
namespace BookStack\Http\Controllers\Auth;
namespace BookStack\Access\Controllers;
use BookStack\Auth\Access\Oidc\OidcException;
use BookStack\Auth\Access\Oidc\OidcService;
use BookStack\Http\Controllers\Controller;
use BookStack\Access\Oidc\OidcException;
use BookStack\Access\Oidc\OidcService;
use BookStack\Http\Controller;
use Illuminate\Http\Request;
class OidcController extends Controller

View File

@@ -1,13 +1,13 @@
<?php
namespace BookStack\Http\Controllers\Auth;
namespace BookStack\Access\Controllers;
use BookStack\Auth\Access\LoginService;
use BookStack\Auth\Access\RegistrationService;
use BookStack\Auth\Access\SocialAuthService;
use BookStack\Access\LoginService;
use BookStack\Access\RegistrationService;
use BookStack\Access\SocialAuthService;
use BookStack\Exceptions\StoppedAuthenticationException;
use BookStack\Exceptions\UserRegistrationException;
use BookStack\Http\Controllers\Controller;
use BookStack\Http\Controller;
use Illuminate\Contracts\Validation\Validator as ValidatorContract;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;

View File

@@ -1,11 +1,11 @@
<?php
namespace BookStack\Http\Controllers\Auth;
namespace BookStack\Access\Controllers;
use BookStack\Actions\ActivityType;
use BookStack\Auth\Access\LoginService;
use BookStack\Auth\User;
use BookStack\Http\Controllers\Controller;
use BookStack\Access\LoginService;
use BookStack\Activity\ActivityType;
use BookStack\Http\Controller;
use BookStack\Users\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;

View File

@@ -1,9 +1,9 @@
<?php
namespace BookStack\Http\Controllers\Auth;
namespace BookStack\Access\Controllers;
use BookStack\Auth\Access\Saml2Service;
use BookStack\Http\Controllers\Controller;
use BookStack\Access\Saml2Service;
use BookStack\Http\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Str;

View File

@@ -1,15 +1,15 @@
<?php
namespace BookStack\Http\Controllers\Auth;
namespace BookStack\Access\Controllers;
use BookStack\Auth\Access\LoginService;
use BookStack\Auth\Access\RegistrationService;
use BookStack\Auth\Access\SocialAuthService;
use BookStack\Access\LoginService;
use BookStack\Access\RegistrationService;
use BookStack\Access\SocialAuthService;
use BookStack\Exceptions\SocialDriverNotConfigured;
use BookStack\Exceptions\SocialSignInAccountNotUsed;
use BookStack\Exceptions\SocialSignInException;
use BookStack\Exceptions\UserRegistrationException;
use BookStack\Http\Controllers\Controller;
use BookStack\Http\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Laravel\Socialite\Contracts\User as SocialUser;

View File

@@ -1,6 +1,6 @@
<?php
namespace BookStack\Http\Controllers\Auth;
namespace BookStack\Access\Controllers;
use Illuminate\Cache\RateLimiter;
use Illuminate\Http\Request;

View File

@@ -1,12 +1,12 @@
<?php
namespace BookStack\Http\Controllers\Auth;
namespace BookStack\Access\Controllers;
use BookStack\Auth\Access\UserInviteService;
use BookStack\Auth\UserRepo;
use BookStack\Access\UserInviteService;
use BookStack\Exceptions\UserTokenExpiredException;
use BookStack\Exceptions\UserTokenNotFoundException;
use BookStack\Http\Controllers\Controller;
use BookStack\Http\Controller;
use BookStack\Users\UserRepo;
use Exception;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

View File

@@ -1,15 +1,15 @@
<?php
namespace BookStack\Auth\Access;
namespace BookStack\Access;
use BookStack\Auth\User;
use BookStack\Exceptions\ConfirmationEmailException;
use BookStack\Notifications\ConfirmEmail;
use BookStack\Users\Models\User;
class EmailConfirmationService extends UserTokenService
{
protected $tokenTable = 'email_confirmations';
protected $expiryTime = 24;
protected string $tokenTable = 'email_confirmations';
protected int $expiryTime = 24;
/**
* Create new confirmation for a user,

View File

@@ -1,6 +1,6 @@
<?php
namespace BookStack\Auth\Access;
namespace BookStack\Access;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;

View File

@@ -1,9 +1,9 @@
<?php
namespace BookStack\Auth\Access;
namespace BookStack\Access;
use BookStack\Auth\Role;
use BookStack\Auth\User;
use BookStack\Users\Models\Role;
use BookStack\Users\Models\User;
use Illuminate\Support\Collection;
class GroupSyncService

View File

@@ -1,6 +1,6 @@
<?php
namespace BookStack\Auth\Access\Guards;
namespace BookStack\Access\Guards;
/**
* Saml2 Session Guard.

View File

@@ -1,8 +1,8 @@
<?php
namespace BookStack\Auth\Access\Guards;
namespace BookStack\Access\Guards;
use BookStack\Auth\Access\RegistrationService;
use BookStack\Access\RegistrationService;
use Illuminate\Auth\GuardHelpers;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\StatefulGuard;

View File

@@ -1,15 +1,15 @@
<?php
namespace BookStack\Auth\Access\Guards;
namespace BookStack\Access\Guards;
use BookStack\Auth\Access\LdapService;
use BookStack\Auth\Access\RegistrationService;
use BookStack\Auth\User;
use BookStack\Access\LdapService;
use BookStack\Access\RegistrationService;
use BookStack\Exceptions\JsonDebugException;
use BookStack\Exceptions\LdapException;
use BookStack\Exceptions\LoginAttemptEmailNeededException;
use BookStack\Exceptions\LoginAttemptException;
use BookStack\Exceptions\UserRegistrationException;
use BookStack\Users\Models\User;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Session\Session;
use Illuminate\Support\Str;

110
app/Access/Ldap.php Normal file
View File

@@ -0,0 +1,110 @@
<?php
namespace BookStack\Access;
/**
* Class Ldap
* An object-orientated thin abstraction wrapper for common PHP LDAP functions.
* Allows the standard LDAP functions to be mocked for testing.
*/
class Ldap
{
/**
* Connect to an LDAP server.
*
* @return resource|\LDAP\Connection|false
*/
public function connect(string $hostName)
{
return ldap_connect($hostName);
}
/**
* Set the value of an LDAP option for the given connection.
*
* @param resource|\LDAP\Connection|null $ldapConnection
*/
public function setOption($ldapConnection, int $option, mixed $value): bool
{
return ldap_set_option($ldapConnection, $option, $value);
}
/**
* Start TLS on the given LDAP connection.
*/
public function startTls($ldapConnection): bool
{
return ldap_start_tls($ldapConnection);
}
/**
* Set the version number for the given LDAP connection.
*
* @param resource|\LDAP\Connection $ldapConnection
*/
public function setVersion($ldapConnection, int $version): bool
{
return $this->setOption($ldapConnection, LDAP_OPT_PROTOCOL_VERSION, $version);
}
/**
* Search LDAP tree using the provided filter.
*
* @param resource|\LDAP\Connection $ldapConnection
*
* @return resource|\LDAP\Result
*/
public function search($ldapConnection, string $baseDn, string $filter, array $attributes = null)
{
return ldap_search($ldapConnection, $baseDn, $filter, $attributes);
}
/**
* Get entries from an LDAP search result.
*
* @param resource|\LDAP\Connection $ldapConnection
* @param resource|\LDAP\Result $ldapSearchResult
*/
public function getEntries($ldapConnection, $ldapSearchResult): array|false
{
return ldap_get_entries($ldapConnection, $ldapSearchResult);
}
/**
* Search and get entries immediately.
*
* @param resource|\LDAP\Connection $ldapConnection
*/
public function searchAndGetEntries($ldapConnection, string $baseDn, string $filter, array $attributes = null): array|false
{
$search = $this->search($ldapConnection, $baseDn, $filter, $attributes);
return $this->getEntries($ldapConnection, $search);
}
/**
* Bind to LDAP directory.
*
* @param resource|\LDAP\Connection $ldapConnection
*/
public function bind($ldapConnection, string $bindRdn = null, string $bindPassword = null): bool
{
return ldap_bind($ldapConnection, $bindRdn, $bindPassword);
}
/**
* Explode an LDAP dn string into an array of components.
*/
public function explodeDn(string $dn, int $withAttrib): array|false
{
return ldap_explode_dn($dn, $withAttrib);
}
/**
* Escape a string for use in an LDAP filter.
*/
public function escape(string $value, string $ignore = '', int $flags = 0): string
{
return ldap_escape($value, $ignore, $flags);
}
}

View File

@@ -1,11 +1,11 @@
<?php
namespace BookStack\Auth\Access;
namespace BookStack\Access;
use BookStack\Auth\User;
use BookStack\Exceptions\JsonDebugException;
use BookStack\Exceptions\LdapException;
use BookStack\Uploads\UserAvatars;
use BookStack\Users\Models\User;
use ErrorException;
use Illuminate\Support\Facades\Log;
@@ -15,26 +15,19 @@ use Illuminate\Support\Facades\Log;
*/
class LdapService
{
protected Ldap $ldap;
protected GroupSyncService $groupSyncService;
protected UserAvatars $userAvatars;
/**
* @var resource
* @var resource|\LDAP\Connection
*/
protected $ldapConnection;
protected array $config;
protected bool $enabled;
/**
* LdapService constructor.
*/
public function __construct(Ldap $ldap, UserAvatars $userAvatars, GroupSyncService $groupSyncService)
{
$this->ldap = $ldap;
$this->userAvatars = $userAvatars;
$this->groupSyncService = $groupSyncService;
public function __construct(
protected Ldap $ldap,
protected UserAvatars $userAvatars,
protected GroupSyncService $groupSyncService
) {
$this->config = config('services.ldap');
$this->enabled = config('auth.method') === 'ldap';
}
@@ -59,7 +52,7 @@ class LdapService
// Clean attributes
foreach ($attributes as $index => $attribute) {
if (strpos($attribute, 'BIN;') === 0) {
if (str_starts_with($attribute, 'BIN;')) {
$attributes[$index] = substr($attribute, strlen('BIN;'));
}
}
@@ -82,7 +75,7 @@ class LdapService
* Get the details of a user from LDAP using the given username.
* User found via configurable user filter.
*
* @throws LdapException
* @throws LdapException|JsonDebugException
*/
public function getUserDetails(string $userName): ?array
{
@@ -126,7 +119,7 @@ class LdapService
*/
protected function getUserResponseProperty(array $userDetails, string $propertyKey, $defaultValue)
{
$isBinary = strpos($propertyKey, 'BIN;') === 0;
$isBinary = str_starts_with($propertyKey, 'BIN;');
$propertyKey = strtolower($propertyKey);
$value = $defaultValue;
@@ -170,11 +163,11 @@ class LdapService
* Bind the system user to the LDAP connection using the given credentials
* otherwise anonymous access is attempted.
*
* @param resource $connection
* @param resource|\LDAP\Connection $connection
*
* @throws LdapException
*/
protected function bindSystemUser($connection)
protected function bindSystemUser($connection): void
{
$ldapDn = $this->config['dn'];
$ldapPass = $this->config['pass'];
@@ -197,7 +190,7 @@ class LdapService
*
* @throws LdapException
*
* @return resource
* @return resource|\LDAP\Connection
*/
protected function getConnection()
{
@@ -216,8 +209,8 @@ class LdapService
$this->ldap->setOption(null, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_NEVER);
}
$serverDetails = $this->parseServerString($this->config['server']);
$ldapConnection = $this->ldap->connect($serverDetails['host'], $serverDetails['port']);
$ldapHost = $this->parseServerString($this->config['server']);
$ldapConnection = $this->ldap->connect($ldapHost);
if ($ldapConnection === false) {
throw new LdapException(trans('errors.ldap_cannot_connect'));
@@ -242,23 +235,16 @@ class LdapService
}
/**
* Parse a LDAP server string and return the host and port for a connection.
* Parse an LDAP server string and return the host suitable for a connection.
* Is flexible to formats such as 'ldap.example.com:8069' or 'ldaps://ldap.example.com'.
*/
protected function parseServerString(string $serverString): array
protected function parseServerString(string $serverString): string
{
$serverNameParts = explode(':', $serverString);
// If we have a protocol just return the full string since PHP will ignore a separate port.
if ($serverNameParts[0] === 'ldaps' || $serverNameParts[0] === 'ldap') {
return ['host' => $serverString, 'port' => 389];
if (str_starts_with($serverString, 'ldaps://') || str_starts_with($serverString, 'ldap://')) {
return $serverString;
}
// Otherwise, extract the port out
$hostName = $serverNameParts[0];
$ldapPort = (count($serverNameParts) > 1) ? intval($serverNameParts[1]) : 389;
return ['host' => $hostName, 'port' => $ldapPort];
return "ldap://{$serverString}";
}
/**
@@ -386,7 +372,7 @@ class LdapService
* @throws LdapException
* @throws JsonDebugException
*/
public function syncGroups(User $user, string $username)
public function syncGroups(User $user, string $username): void
{
$userLdapGroups = $this->getUserGroups($username);
$this->groupSyncService->syncUserWithFoundGroups($user, $userLdapGroups, $this->config['remove_from_groups']);

View File

@@ -1,15 +1,15 @@
<?php
namespace BookStack\Auth\Access;
namespace BookStack\Access;
use BookStack\Actions\ActivityType;
use BookStack\Auth\Access\Mfa\MfaSession;
use BookStack\Auth\User;
use BookStack\Access\Mfa\MfaSession;
use BookStack\Activity\ActivityType;
use BookStack\Exceptions\LoginAttemptException;
use BookStack\Exceptions\StoppedAuthenticationException;
use BookStack\Facades\Activity;
use BookStack\Facades\Theme;
use BookStack\Theming\ThemeEvents;
use BookStack\Users\Models\User;
use Exception;
class LoginService

View File

@@ -1,6 +1,6 @@
<?php
namespace BookStack\Auth\Access\Mfa;
namespace BookStack\Access\Mfa;
use Illuminate\Support\Str;

View File

@@ -1,8 +1,8 @@
<?php
namespace BookStack\Auth\Access\Mfa;
namespace BookStack\Access\Mfa;
use BookStack\Auth\User;
use BookStack\Users\Models\User;
class MfaSession
{

View File

@@ -1,8 +1,8 @@
<?php
namespace BookStack\Auth\Access\Mfa;
namespace BookStack\Access\Mfa;
use BookStack\Auth\User;
use BookStack\Users\Models\User;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;

View File

@@ -1,6 +1,6 @@
<?php
namespace BookStack\Auth\Access\Mfa;
namespace BookStack\Access\Mfa;
use BaconQrCode\Renderer\Color\Rgb;
use BaconQrCode\Renderer\Image\SvgImageBackEnd;
@@ -8,7 +8,7 @@ use BaconQrCode\Renderer\ImageRenderer;
use BaconQrCode\Renderer\RendererStyle\Fill;
use BaconQrCode\Renderer\RendererStyle\RendererStyle;
use BaconQrCode\Writer;
use BookStack\Auth\User;
use BookStack\Users\Models\User;
use PragmaRX\Google2FA\Google2FA;
use PragmaRX\Google2FA\Support\Constants;

View File

@@ -1,6 +1,6 @@
<?php
namespace BookStack\Auth\Access\Mfa;
namespace BookStack\Access\Mfa;
use Illuminate\Contracts\Validation\Rule;

View File

@@ -1,6 +1,6 @@
<?php
namespace BookStack\Auth\Access\Oidc;
namespace BookStack\Access\Oidc;
use InvalidArgumentException;
use League\OAuth2\Client\Token\AccessToken;

View File

@@ -1,6 +1,6 @@
<?php
namespace BookStack\Auth\Access\Oidc;
namespace BookStack\Access\Oidc;
use Exception;

View File

@@ -1,38 +1,19 @@
<?php
namespace BookStack\Auth\Access\Oidc;
namespace BookStack\Access\Oidc;
class OidcIdToken
{
/**
* @var array
*/
protected $header;
/**
* @var array
*/
protected $payload;
/**
* @var string
*/
protected $signature;
protected array $header;
protected array $payload;
protected string $signature;
protected string $issuer;
protected array $tokenParts = [];
/**
* @var array[]|string[]
*/
protected $keys;
/**
* @var string
*/
protected $issuer;
/**
* @var array
*/
protected $tokenParts = [];
protected array $keys;
public function __construct(string $token, string $issuer, array $keys)
{
@@ -106,6 +87,14 @@ class OidcIdToken
return $this->payload;
}
/**
* Replace the existing claim data of this token with that provided.
*/
public function replaceClaims(array $claims): void
{
$this->payload = $claims;
}
/**
* Validate the structure of the given token and ensure we have the required pieces.
* As per https://datatracker.ietf.org/doc/html/rfc7519#section-7.2.

View File

@@ -1,6 +1,6 @@
<?php
namespace BookStack\Auth\Access\Oidc;
namespace BookStack\Access\Oidc;
class OidcInvalidKeyException extends \Exception
{

View File

@@ -1,6 +1,6 @@
<?php
namespace BookStack\Auth\Access\Oidc;
namespace BookStack\Access\Oidc;
use Exception;

View File

@@ -1,6 +1,6 @@
<?php
namespace BookStack\Auth\Access\Oidc;
namespace BookStack\Access\Oidc;
use Exception;

View File

@@ -1,6 +1,6 @@
<?php
namespace BookStack\Auth\Access\Oidc;
namespace BookStack\Access\Oidc;
use phpseclib3\Crypt\Common\PublicKey;
use phpseclib3\Crypt\PublicKeyLoader;

View File

@@ -1,6 +1,6 @@
<?php
namespace BookStack\Auth\Access\Oidc;
namespace BookStack\Access\Oidc;
use League\OAuth2\Client\Grant\AbstractGrant;
use League\OAuth2\Client\Provider\AbstractProvider;

View File

@@ -1,6 +1,6 @@
<?php
namespace BookStack\Auth\Access\Oidc;
namespace BookStack\Access\Oidc;
use GuzzleHttp\Psr7\Request;
use Illuminate\Contracts\Cache\Repository;

View File

@@ -1,14 +1,16 @@
<?php
namespace BookStack\Auth\Access\Oidc;
namespace BookStack\Access\Oidc;
use BookStack\Auth\Access\GroupSyncService;
use BookStack\Auth\Access\LoginService;
use BookStack\Auth\Access\RegistrationService;
use BookStack\Auth\User;
use BookStack\Access\GroupSyncService;
use BookStack\Access\LoginService;
use BookStack\Access\RegistrationService;
use BookStack\Exceptions\JsonDebugException;
use BookStack\Exceptions\StoppedAuthenticationException;
use BookStack\Exceptions\UserRegistrationException;
use BookStack\Facades\Theme;
use BookStack\Theming\ThemeEvents;
use BookStack\Users\Models\User;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Cache;
use League\OAuth2\Client\OptionProvider\HttpBasicAuthOptionProvider;
@@ -21,24 +23,12 @@ use Psr\Http\Client\ClientInterface as HttpClient;
*/
class OidcService
{
protected RegistrationService $registrationService;
protected LoginService $loginService;
protected HttpClient $httpClient;
protected GroupSyncService $groupService;
/**
* OpenIdService constructor.
*/
public function __construct(
RegistrationService $registrationService,
LoginService $loginService,
HttpClient $httpClient,
GroupSyncService $groupService
protected RegistrationService $registrationService,
protected LoginService $loginService,
protected HttpClient $httpClient,
protected GroupSyncService $groupService
) {
$this->registrationService = $registrationService;
$this->loginService = $loginService;
$this->httpClient = $httpClient;
$this->groupService = $groupService;
}
/**
@@ -226,6 +216,16 @@ class OidcService
$settings->keys,
);
$returnClaims = Theme::dispatch(ThemeEvents::OIDC_ID_TOKEN_PRE_VALIDATE, $idToken->getAllClaims(), [
'access_token' => $accessToken->getToken(),
'expires_in' => $accessToken->getExpires(),
'refresh_token' => $accessToken->getRefreshToken(),
]);
if (!is_null($returnClaims)) {
$idToken->replaceClaims($returnClaims);
}
if ($this->config()['dump_user_details']) {
throw new JsonDebugException($idToken->getAllClaims());
}

View File

@@ -1,15 +1,14 @@
<?php
namespace BookStack\Auth\Access;
namespace BookStack\Access;
use BookStack\Actions\ActivityType;
use BookStack\Auth\SocialAccount;
use BookStack\Auth\User;
use BookStack\Auth\UserRepo;
use BookStack\Activity\ActivityType;
use BookStack\Exceptions\UserRegistrationException;
use BookStack\Facades\Activity;
use BookStack\Facades\Theme;
use BookStack\Theming\ThemeEvents;
use BookStack\Users\Models\User;
use BookStack\Users\UserRepo;
use Exception;
use Illuminate\Support\Str;

View File

@@ -1,12 +1,12 @@
<?php
namespace BookStack\Auth\Access;
namespace BookStack\Access;
use BookStack\Auth\User;
use BookStack\Exceptions\JsonDebugException;
use BookStack\Exceptions\SamlException;
use BookStack\Exceptions\StoppedAuthenticationException;
use BookStack\Exceptions\UserRegistrationException;
use BookStack\Users\Models\User;
use Exception;
use OneLogin\Saml2\Auth;
use OneLogin\Saml2\Constants;
@@ -67,7 +67,7 @@ class Saml2Service
$returnRoute,
[],
$user->email,
null,
session()->get('saml2_session_index'),
true,
Constants::NAMEID_EMAIL_ADDRESS
);
@@ -118,6 +118,7 @@ class Saml2Service
$attrs = $toolkit->getAttributes();
$id = $toolkit->getNameId();
session()->put('saml2_session_index', $toolkit->getSessionIndex());
return $this->processLoginCallback($id, $attrs);
}

View File

@@ -1,9 +1,10 @@
<?php
namespace BookStack\Auth;
namespace BookStack\Access;
use BookStack\Interfaces\Loggable;
use BookStack\Model;
use BookStack\Activity\Models\Loggable;
use BookStack\App\Model;
use BookStack\Users\Models\User;
/**
* Class SocialAccount.

View File

@@ -1,12 +1,12 @@
<?php
namespace BookStack\Auth\Access;
namespace BookStack\Access;
use BookStack\Auth\SocialAccount;
use BookStack\Auth\User;
use BookStack\Auth\Access\handler;
use BookStack\Exceptions\SocialDriverNotConfigured;
use BookStack\Exceptions\SocialSignInAccountNotUsed;
use BookStack\Exceptions\UserRegistrationException;
use BookStack\Users\Models\User;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Str;
use Laravel\Socialite\Contracts\Factory as Socialite;

View File

@@ -1,20 +1,18 @@
<?php
namespace BookStack\Auth\Access;
namespace BookStack\Access;
use BookStack\Auth\User;
use BookStack\Notifications\UserInvite;
use BookStack\Users\Models\User;
class UserInviteService extends UserTokenService
{
protected $tokenTable = 'user_invites';
protected $expiryTime = 336; // Two weeks
protected string $tokenTable = 'user_invites';
protected int $expiryTime = 336; // Two weeks
/**
* Send an invitation to a user to sign into BookStack
* Removes existing invitation tokens.
*
* @param User $user
*/
public function sendInvitation(User $user)
{

View File

@@ -1,10 +1,10 @@
<?php
namespace BookStack\Auth\Access;
namespace BookStack\Access;
use BookStack\Auth\User;
use BookStack\Exceptions\UserTokenExpiredException;
use BookStack\Exceptions\UserTokenNotFoundException;
use BookStack\Users\Models\User;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
@@ -14,41 +14,29 @@ class UserTokenService
{
/**
* Name of table where user tokens are stored.
*
* @var string
*/
protected $tokenTable = 'user_tokens';
protected string $tokenTable = 'user_tokens';
/**
* Token expiry time in hours.
*
* @var int
*/
protected $expiryTime = 24;
protected int $expiryTime = 24;
/**
* Delete all email confirmations that belong to a user.
*
* @param User $user
*
* @return mixed
* Delete all tokens that belong to a user.
*/
public function deleteByUser(User $user)
public function deleteByUser(User $user): void
{
return DB::table($this->tokenTable)
DB::table($this->tokenTable)
->where('user_id', '=', $user->id)
->delete();
}
/**
* Get the user id from a token, while check the token exists and has not expired.
*
* @param string $token
* Get the user id from a token, while checking the token exists and has not expired.
*
* @throws UserTokenNotFoundException
* @throws UserTokenExpiredException
*
* @return int
*/
public function checkTokenAndGetUserId(string $token): int
{
@@ -67,8 +55,6 @@ class UserTokenService
/**
* Creates a unique token within the email confirmation database.
*
* @return string
*/
protected function generateToken(): string
{
@@ -82,10 +68,6 @@ class UserTokenService
/**
* Generate and store a token for the given user.
*
* @param User $user
*
* @return string
*/
protected function createTokenForUser(User $user): string
{
@@ -102,10 +84,6 @@ class UserTokenService
/**
* Check if the given token exists.
*
* @param string $token
*
* @return bool
*/
protected function tokenExists(string $token): bool
{
@@ -115,12 +93,8 @@ class UserTokenService
/**
* Get a token entry for the given token.
*
* @param string $token
*
* @return object|null
*/
protected function getEntryByToken(string $token)
protected function getEntryByToken(string $token): ?stdClass
{
return DB::table($this->tokenTable)
->where('token', '=', $token)
@@ -129,10 +103,6 @@ class UserTokenService
/**
* Check if the given token entry has expired.
*
* @param stdClass $tokenEntry
*
* @return bool
*/
protected function entryExpired(stdClass $tokenEntry): bool
{

View File

@@ -1,13 +1,14 @@
<?php
namespace BookStack\Actions;
namespace BookStack\Activity;
use BookStack\Auth\Permissions\PermissionApplicator;
use BookStack\Auth\User;
use BookStack\Activity\Models\Activity;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\Page;
use BookStack\Permissions\PermissionApplicator;
use BookStack\Users\Models\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\Relation;

View File

@@ -1,6 +1,6 @@
<?php
namespace BookStack\Actions;
namespace BookStack\Activity;
class ActivityType
{
@@ -27,6 +27,10 @@ class ActivityType
const BOOKSHELF_DELETE = 'bookshelf_delete';
const COMMENTED_ON = 'commented_on';
const COMMENT_CREATE = 'comment_create';
const COMMENT_UPDATE = 'comment_update';
const COMMENT_DELETE = 'comment_delete';
const PERMISSIONS_UPDATE = 'permissions_update';
const REVISION_RESTORE = 'revision_restore';

View File

@@ -1,32 +1,20 @@
<?php
namespace BookStack\Actions;
namespace BookStack\Activity;
use BookStack\Activity\Models\Comment;
use BookStack\Entities\Models\Entity;
use BookStack\Facades\Activity as ActivityService;
use League\CommonMark\CommonMarkConverter;
/**
* Class CommentRepo.
*/
class CommentRepo
{
/**
* @var Comment
*/
protected $comment;
public function __construct(Comment $comment)
{
$this->comment = $comment;
}
/**
* Get a comment by ID.
*/
public function getById(int $id): Comment
{
return $this->comment->newQuery()->findOrFail($id);
return Comment::query()->findOrFail($id);
}
/**
@@ -35,7 +23,7 @@ class CommentRepo
public function create(Entity $entity, string $text, ?int $parent_id): Comment
{
$userId = user()->id;
$comment = $this->comment->newInstance();
$comment = new Comment();
$comment->text = $text;
$comment->html = $this->commentToHtml($text);
@@ -45,6 +33,7 @@ class CommentRepo
$comment->parent_id = $parent_id;
$entity->comments()->save($comment);
ActivityService::add(ActivityType::COMMENT_CREATE, $comment);
ActivityService::add(ActivityType::COMMENTED_ON, $entity);
return $comment;
@@ -60,6 +49,8 @@ class CommentRepo
$comment->html = $this->commentToHtml($text);
$comment->save();
ActivityService::add(ActivityType::COMMENT_UPDATE, $comment);
return $comment;
}
@@ -69,6 +60,8 @@ class CommentRepo
public function delete(Comment $comment): void
{
$comment->delete();
ActivityService::add(ActivityType::COMMENT_DELETE, $comment);
}
/**
@@ -82,7 +75,7 @@ class CommentRepo
'allow_unsafe_links' => false,
]);
return $converter->convertToHtml($commentText);
return $converter->convert($commentText);
}
/**
@@ -90,9 +83,8 @@ class CommentRepo
*/
protected function getNextLocalId(Entity $entity): int
{
/** @var Comment $comment */
$comment = $entity->comments(false)->orderBy('local_id', 'desc')->first();
$currentMaxId = $entity->comments()->max('local_id');
return ($comment->local_id ?? 0) + 1;
return $currentMaxId + 1;
}
}

View File

@@ -1,12 +1,12 @@
<?php
namespace BookStack\Http\Controllers;
namespace BookStack\Activity\Controllers;
use BookStack\Actions\Activity;
use BookStack\Actions\ActivityType;
use BookStack\Activity\ActivityType;
use BookStack\Activity\Models\Activity;
use BookStack\Http\Controller;
use BookStack\Util\SimpleListOptions;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class AuditLogController extends Controller
{

View File

@@ -1,19 +1,18 @@
<?php
namespace BookStack\Http\Controllers;
namespace BookStack\Activity\Controllers;
use BookStack\Actions\CommentRepo;
use BookStack\Activity\CommentRepo;
use BookStack\Entities\Models\Page;
use BookStack\Http\Controller;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
class CommentController extends Controller
{
protected $commentRepo;
public function __construct(CommentRepo $commentRepo)
{
$this->commentRepo = $commentRepo;
public function __construct(
protected CommentRepo $commentRepo
) {
}
/**
@@ -42,7 +41,13 @@ class CommentController extends Controller
$this->checkPermission('comment-create-all');
$comment = $this->commentRepo->create($page, $request->get('text'), $request->get('parent_id'));
return view('comments.comment', ['comment' => $comment]);
return view('comments.comment-branch', [
'readOnly' => false,
'branch' => [
'comment' => $comment,
'children' => [],
]
]);
}
/**
@@ -62,7 +67,7 @@ class CommentController extends Controller
$comment = $this->commentRepo->update($comment, $request->get('text'));
return view('comments.comment', ['comment' => $comment]);
return view('comments.comment', ['comment' => $comment, 'readOnly' => false]);
}
/**

View File

@@ -1,11 +1,12 @@
<?php
namespace BookStack\Http\Controllers;
namespace BookStack\Activity\Controllers;
use BookStack\Activity\Models\Favouritable;
use BookStack\App\Model;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Queries\TopFavourites;
use BookStack\Interfaces\Favouritable;
use BookStack\Model;
use BookStack\Http\Controller;
use Illuminate\Http\Request;
class FavouriteController extends Controller

View File

@@ -1,18 +1,17 @@
<?php
namespace BookStack\Http\Controllers;
namespace BookStack\Activity\Controllers;
use BookStack\Actions\TagRepo;
use BookStack\Activity\TagRepo;
use BookStack\Http\Controller;
use BookStack\Util\SimpleListOptions;
use Illuminate\Http\Request;
class TagController extends Controller
{
protected TagRepo $tagRepo;
public function __construct(TagRepo $tagRepo)
{
$this->tagRepo = $tagRepo;
public function __construct(
protected TagRepo $tagRepo
) {
}
/**

View File

@@ -0,0 +1,65 @@
<?php
namespace BookStack\Activity\Controllers;
use BookStack\Activity\Tools\UserEntityWatchOptions;
use BookStack\App\Model;
use BookStack\Entities\Models\Entity;
use BookStack\Http\Controller;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
class WatchController extends Controller
{
public function update(Request $request)
{
$this->checkPermission('receive-notifications');
$this->preventGuestAccess();
$requestData = $this->validate($request, [
'level' => ['required', 'string'],
]);
$watchable = $this->getValidatedModelFromRequest($request);
$watchOptions = new UserEntityWatchOptions(user(), $watchable);
$watchOptions->updateLevelByName($requestData['level']);
$this->showSuccessNotification(trans('activities.watch_update_level_notification'));
return redirect()->back();
}
/**
* @throws ValidationException
* @throws Exception
*/
protected function getValidatedModelFromRequest(Request $request): Entity
{
$modelInfo = $this->validate($request, [
'type' => ['required', 'string'],
'id' => ['required', 'integer'],
]);
if (!class_exists($modelInfo['type'])) {
throw new Exception('Model not found');
}
/** @var Model $model */
$model = new $modelInfo['type']();
if (!$model instanceof Entity) {
throw new Exception('Model not an entity');
}
$modelInstance = $model->newQuery()
->where('id', '=', $modelInfo['id'])
->first(['id', 'name', 'owned_by']);
$inaccessibleEntity = ($modelInstance instanceof Entity && !userCan('view', $modelInstance));
if (is_null($modelInstance) || $inaccessibleEntity) {
throw new Exception('Model instance not found');
}
return $modelInstance;
}
}

View File

@@ -1,10 +1,11 @@
<?php
namespace BookStack\Http\Controllers;
namespace BookStack\Activity\Controllers;
use BookStack\Actions\ActivityType;
use BookStack\Actions\Queries\WebhooksAllPaginatedAndSorted;
use BookStack\Actions\Webhook;
use BookStack\Activity\ActivityType;
use BookStack\Activity\Models\Webhook;
use BookStack\Activity\Queries\WebhooksAllPaginatedAndSorted;
use BookStack\Http\Controller;
use BookStack\Util\SimpleListOptions;
use Illuminate\Http\Request;

View File

@@ -1,11 +1,14 @@
<?php
namespace BookStack\Actions;
namespace BookStack\Activity;
use BookStack\Auth\User;
use BookStack\Activity\Models\Loggable;
use BookStack\Activity\Models\Webhook;
use BookStack\Activity\Tools\WebhookFormatter;
use BookStack\Facades\Theme;
use BookStack\Interfaces\Loggable;
use BookStack\Theming\ThemeEvents;
use BookStack\Users\Models\User;
use BookStack\Util\SsrUrlValidator;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
@@ -22,27 +25,23 @@ class DispatchWebhookJob implements ShouldQueue
use SerializesModels;
protected Webhook $webhook;
protected string $event;
protected User $initiator;
protected int $initiatedTime;
/**
* @var string|Loggable
*/
protected $detail;
protected array $webhookData;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Webhook $webhook, string $event, $detail)
public function __construct(Webhook $webhook, string $event, Loggable|string $detail)
{
$this->webhook = $webhook;
$this->event = $event;
$this->detail = $detail;
$this->initiator = user();
$this->initiatedTime = time();
$themeResponse = Theme::dispatch(ThemeEvents::WEBHOOK_CALL_BEFORE, $event, $this->webhook, $detail, $this->initiator, $this->initiatedTime);
$this->webhookData = $themeResponse ?? WebhookFormatter::getDefault($event, $this->webhook, $detail, $this->initiator, $this->initiatedTime)->format();
}
/**
@@ -52,15 +51,15 @@ class DispatchWebhookJob implements ShouldQueue
*/
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 {
(new SsrUrlValidator())->ensureAllowed($this->webhook->endpoint);
$response = Http::asJson()
->withOptions(['allow_redirects' => ['strict' => true]])
->timeout($this->webhook->timeout)
->post($this->webhook->endpoint, $webhookData);
->post($this->webhook->endpoint, $this->webhookData);
} catch (\Exception $exception) {
$lastError = $exception->getMessage();
Log::error("Webhook call to endpoint {$this->webhook->endpoint} failed with error \"{$lastError}\"");

View File

@@ -1,11 +1,11 @@
<?php
namespace BookStack\Actions;
namespace BookStack\Activity\Models;
use BookStack\Auth\Permissions\JointPermission;
use BookStack\Auth\User;
use BookStack\App\Model;
use BookStack\Entities\Models\Entity;
use BookStack\Model;
use BookStack\Permissions\Models\JointPermission;
use BookStack\Users\Models\User;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphTo;
@@ -19,6 +19,8 @@ use Illuminate\Support\Str;
* @property string $entity_type
* @property int $entity_id
* @property int $user_id
* @property Carbon $created_at
* @property Carbon $updated_at
*/
class Activity extends Model
{

View File

@@ -1,10 +1,11 @@
<?php
namespace BookStack\Actions;
namespace BookStack\Activity\Models;
use BookStack\Model;
use BookStack\Traits\HasCreatorAndUpdater;
use BookStack\App\Model;
use BookStack\Users\Models\HasCreatorAndUpdater;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;
/**
@@ -13,8 +14,10 @@ use Illuminate\Database\Eloquent\Relations\MorphTo;
* @property string $html
* @property int|null $parent_id
* @property int $local_id
* @property string $entity_type
* @property int $entity_id
*/
class Comment extends Model
class Comment extends Model implements Loggable
{
use HasFactory;
use HasCreatorAndUpdater;
@@ -30,6 +33,14 @@ class Comment extends Model
return $this->morphTo('entity');
}
/**
* Get the parent comment this is in reply to (if existing).
*/
public function parent(): BelongsTo
{
return $this->belongsTo(Comment::class);
}
/**
* Check if a comment has been updated since creation.
*/
@@ -40,21 +51,22 @@ class Comment extends Model
/**
* Get created date as a relative diff.
*
* @return mixed
*/
public function getCreatedAttribute()
public function getCreatedAttribute(): string
{
return $this->created_at->diffForHumans();
}
/**
* Get updated date as a relative diff.
*
* @return mixed
*/
public function getUpdatedAttribute()
public function getUpdatedAttribute(): string
{
return $this->updated_at->diffForHumans();
}
public function logDescriptor(): string
{
return "Comment #{$this->local_id} (ID: {$this->id}) for {$this->entity_type} (ID: {$this->entity_id})";
}
}

View File

@@ -1,6 +1,6 @@
<?php
namespace BookStack\Interfaces;
namespace BookStack\Activity\Models;
use Illuminate\Database\Eloquent\Relations\MorphMany;

View File

@@ -1,9 +1,9 @@
<?php
namespace BookStack\Actions;
namespace BookStack\Activity\Models;
use BookStack\Auth\Permissions\JointPermission;
use BookStack\Model;
use BookStack\App\Model;
use BookStack\Permissions\Models\JointPermission;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphTo;

View File

@@ -1,6 +1,6 @@
<?php
namespace BookStack\Interfaces;
namespace BookStack\Activity\Models;
interface Loggable
{

View File

@@ -1,9 +1,9 @@
<?php
namespace BookStack\Actions;
namespace BookStack\Activity\Models;
use BookStack\Auth\Permissions\JointPermission;
use BookStack\Model;
use BookStack\App\Model;
use BookStack\Permissions\Models\JointPermission;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphTo;

View File

@@ -1,10 +1,9 @@
<?php
namespace BookStack\Actions;
namespace BookStack\Activity\Models;
use BookStack\Auth\Permissions\JointPermission;
use BookStack\Interfaces\Viewable;
use BookStack\Model;
use BookStack\App\Model;
use BookStack\Permissions\Models\JointPermission;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphTo;
@@ -55,12 +54,4 @@ class View extends Model
return $view->views;
}
/**
* Clear all views from the system.
*/
public static function clearAll()
{
static::query()->truncate();
}
}

View File

@@ -1,6 +1,6 @@
<?php
namespace BookStack\Interfaces;
namespace BookStack\Activity\Models;
use Illuminate\Database\Eloquent\Relations\MorphMany;

View File

@@ -0,0 +1,45 @@
<?php
namespace BookStack\Activity\Models;
use BookStack\Activity\WatchLevels;
use BookStack\Permissions\Models\JointPermission;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphTo;
/**
* @property int $id
* @property int $user_id
* @property int $watchable_id
* @property string $watchable_type
* @property int $level
* @property Carbon $created_at
* @property Carbon $updated_at
*/
class Watch extends Model
{
protected $guarded = [];
public function watchable(): MorphTo
{
return $this->morphTo();
}
public function jointPermissions(): HasMany
{
return $this->hasMany(JointPermission::class, 'entity_id', 'watchable_id')
->whereColumn('watches.watchable_type', '=', 'joint_permissions.entity_type');
}
public function getLevelName(): string
{
return WatchLevels::levelValueToName($this->level);
}
public function ignoring(): bool
{
return $this->level === WatchLevels::IGNORE;
}
}

View File

@@ -1,8 +1,8 @@
<?php
namespace BookStack\Actions;
namespace BookStack\Activity\Models;
use BookStack\Interfaces\Loggable;
use BookStack\Activity\ActivityType;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;

View File

@@ -1,6 +1,6 @@
<?php
namespace BookStack\Actions;
namespace BookStack\Activity\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

View File

@@ -0,0 +1,42 @@
<?php
namespace BookStack\Activity\Notifications\Handlers;
use BookStack\Activity\Models\Loggable;
use BookStack\Activity\Notifications\Messages\BaseActivityNotification;
use BookStack\Entities\Models\Entity;
use BookStack\Permissions\PermissionApplicator;
use BookStack\Users\Models\User;
abstract class BaseNotificationHandler implements NotificationHandler
{
/**
* @param class-string<BaseActivityNotification> $notification
* @param int[] $userIds
*/
protected function sendNotificationToUserIds(string $notification, array $userIds, User $initiator, string|Loggable $detail, Entity $relatedModel): void
{
$users = User::query()->whereIn('id', array_unique($userIds))->get();
foreach ($users as $user) {
// Prevent sending to the user that initiated the activity
if ($user->id === $initiator->id) {
continue;
}
// Prevent sending of the user does not have notification permissions
if (!$user->can('receive-notifications')) {
continue;
}
// Prevent sending if the user does not have access to the related content
$permissions = new PermissionApplicator($user);
if (!$permissions->checkOwnableUserAccess($relatedModel, 'view')) {
continue;
}
// Send the notification
$user->notify(new $notification($detail, $initiator));
}
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace BookStack\Activity\Notifications\Handlers;
use BookStack\Activity\Models\Activity;
use BookStack\Activity\Models\Comment;
use BookStack\Activity\Models\Loggable;
use BookStack\Activity\Notifications\Messages\CommentCreationNotification;
use BookStack\Activity\Tools\EntityWatchers;
use BookStack\Activity\WatchLevels;
use BookStack\Entities\Models\Page;
use BookStack\Settings\UserNotificationPreferences;
use BookStack\Users\Models\User;
class CommentCreationNotificationHandler extends BaseNotificationHandler
{
public function handle(Activity $activity, Loggable|string $detail, User $user): void
{
if (!($detail instanceof Comment)) {
throw new \InvalidArgumentException("Detail for comment creation notifications must be a comment");
}
// Main watchers
/** @var Page $page */
$page = $detail->entity;
$watchers = new EntityWatchers($page, WatchLevels::COMMENTS);
$watcherIds = $watchers->getWatcherUserIds();
// Page owner if user preferences allow
if (!$watchers->isUserIgnoring($page->owned_by) && $page->ownedBy) {
$userNotificationPrefs = new UserNotificationPreferences($page->ownedBy);
if ($userNotificationPrefs->notifyOnOwnPageComments()) {
$watcherIds[] = $page->owned_by;
}
}
// Parent comment creator if preferences allow
$parentComment = $detail->parent()->first();
if ($parentComment && !$watchers->isUserIgnoring($parentComment->created_by) && $parentComment->createdBy) {
$parentCommenterNotificationsPrefs = new UserNotificationPreferences($parentComment->createdBy);
if ($parentCommenterNotificationsPrefs->notifyOnCommentReplies()) {
$watcherIds[] = $parentComment->created_by;
}
}
$this->sendNotificationToUserIds(CommentCreationNotification::class, $watcherIds, $user, $detail, $page);
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace BookStack\Activity\Notifications\Handlers;
use BookStack\Activity\Models\Activity;
use BookStack\Activity\Models\Loggable;
use BookStack\Users\Models\User;
interface NotificationHandler
{
/**
* Run this handler.
* Provides the activity, related activity detail/model
* along with the user that triggered the activity.
*/
public function handle(Activity $activity, string|Loggable $detail, User $user): void;
}

View File

@@ -0,0 +1,24 @@
<?php
namespace BookStack\Activity\Notifications\Handlers;
use BookStack\Activity\Models\Activity;
use BookStack\Activity\Models\Loggable;
use BookStack\Activity\Notifications\Messages\PageCreationNotification;
use BookStack\Activity\Tools\EntityWatchers;
use BookStack\Activity\WatchLevels;
use BookStack\Entities\Models\Page;
use BookStack\Users\Models\User;
class PageCreationNotificationHandler extends BaseNotificationHandler
{
public function handle(Activity $activity, Loggable|string $detail, User $user): void
{
if (!($detail instanceof Page)) {
throw new \InvalidArgumentException("Detail for page create notifications must be a page");
}
$watchers = new EntityWatchers($detail, WatchLevels::NEW);
$this->sendNotificationToUserIds(PageCreationNotification::class, $watchers->getWatcherUserIds(), $user, $detail, $detail);
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace BookStack\Activity\Notifications\Handlers;
use BookStack\Activity\ActivityType;
use BookStack\Activity\Models\Activity;
use BookStack\Activity\Models\Loggable;
use BookStack\Activity\Notifications\Messages\PageUpdateNotification;
use BookStack\Activity\Tools\EntityWatchers;
use BookStack\Activity\WatchLevels;
use BookStack\Entities\Models\Page;
use BookStack\Settings\UserNotificationPreferences;
use BookStack\Users\Models\User;
class PageUpdateNotificationHandler extends BaseNotificationHandler
{
public function handle(Activity $activity, Loggable|string $detail, User $user): void
{
if (!($detail instanceof Page)) {
throw new \InvalidArgumentException("Detail for page update notifications must be a page");
}
// Get last update from activity
$lastUpdate = $detail->activity()
->where('type', '=', ActivityType::PAGE_UPDATE)
->where('id', '!=', $activity->id)
->latest('created_at')
->first();
// Return if the same user has already updated the page in the last 15 mins
if ($lastUpdate && $lastUpdate->user_id === $user->id) {
if ($lastUpdate->created_at->gt(now()->subMinutes(15))) {
return;
}
}
// Get active watchers
$watchers = new EntityWatchers($detail, WatchLevels::UPDATES);
$watcherIds = $watchers->getWatcherUserIds();
// Add page owner if preferences allow
if (!$watchers->isUserIgnoring($detail->owned_by) && $detail->ownedBy) {
$userNotificationPrefs = new UserNotificationPreferences($detail->ownedBy);
if ($userNotificationPrefs->notifyOnOwnPageChanges()) {
$watcherIds[] = $detail->owned_by;
}
}
$this->sendNotificationToUserIds(PageUpdateNotification::class, $watcherIds, $user, $detail, $detail);
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace BookStack\Activity\Notifications\MessageParts;
use Illuminate\Contracts\Support\Htmlable;
use Stringable;
/**
* A line of text with linked text included, intended for use
* in MailMessages. The line should have a ':link' placeholder for
* where the link should be inserted within the line.
*/
class LinkedMailMessageLine implements Htmlable, Stringable
{
public function __construct(
protected string $url,
protected string $line,
protected string $linkText,
) {
}
public function toHtml(): string
{
$link = '<a href="' . e($this->url) . '">' . e($this->linkText) . '</a>';
return str_replace(':link', $link, e($this->line));
}
public function __toString(): string
{
$link = "{$this->linkText} ({$this->url})";
return str_replace(':link', $link, $this->line);
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace BookStack\Activity\Notifications\MessageParts;
use Illuminate\Contracts\Support\Htmlable;
use Stringable;
/**
* A bullet point list of content, where the keys of the given list array
* are bolded header elements, and the values follow.
*/
class ListMessageLine implements Htmlable, Stringable
{
public function __construct(
protected array $list
) {
}
public function toHtml(): string
{
$list = [];
foreach ($this->list as $header => $content) {
$list[] = '<strong>' . e($header) . '</strong> ' . e($content);
}
return implode("<br>\n", $list);
}
public function __toString(): string
{
$list = [];
foreach ($this->list as $header => $content) {
$list[] = $header . ' ' . $content;
}
return implode("\n", $list);
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace BookStack\Activity\Notifications\Messages;
use BookStack\Activity\Models\Loggable;
use BookStack\Activity\Notifications\MessageParts\LinkedMailMessageLine;
use BookStack\Notifications\MailNotification;
use BookStack\Users\Models\User;
use Illuminate\Bus\Queueable;
abstract class BaseActivityNotification extends MailNotification
{
use Queueable;
public function __construct(
protected Loggable|string $detail,
protected User $user,
) {
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
'activity_detail' => $this->detail,
'activity_creator' => $this->user,
];
}
/**
* Build the common reason footer line used in mail messages.
*/
protected function buildReasonFooterLine(string $language): LinkedMailMessageLine
{
return new LinkedMailMessageLine(
url('/preferences/notifications'),
trans('notifications.footer_reason', [], $language),
trans('notifications.footer_reason_link', [], $language),
);
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace BookStack\Activity\Notifications\Messages;
use BookStack\Activity\Models\Comment;
use BookStack\Activity\Notifications\MessageParts\ListMessageLine;
use BookStack\Entities\Models\Page;
use BookStack\Users\Models\User;
use Illuminate\Notifications\Messages\MailMessage;
class CommentCreationNotification extends BaseActivityNotification
{
public function toMail(User $notifiable): MailMessage
{
/** @var Comment $comment */
$comment = $this->detail;
/** @var Page $page */
$page = $comment->entity;
$language = $notifiable->getLanguage();
return $this->newMailMessage($language)
->subject(trans('notifications.new_comment_subject', ['pageName' => $page->getShortName()], $language))
->line(trans('notifications.new_comment_intro', ['appName' => setting('app-name')], $language))
->line(new ListMessageLine([
trans('notifications.detail_page_name', [], $language) => $page->name,
trans('notifications.detail_commenter', [], $language) => $this->user->name,
trans('notifications.detail_comment', [], $language) => strip_tags($comment->html),
]))
->action(trans('notifications.action_view_comment', [], $language), $page->getUrl('#comment' . $comment->local_id))
->line($this->buildReasonFooterLine($language));
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace BookStack\Activity\Notifications\Messages;
use BookStack\Activity\Notifications\MessageParts\ListMessageLine;
use BookStack\Entities\Models\Page;
use BookStack\Users\Models\User;
use Illuminate\Notifications\Messages\MailMessage;
class PageCreationNotification extends BaseActivityNotification
{
public function toMail(User $notifiable): MailMessage
{
/** @var Page $page */
$page = $this->detail;
$language = $notifiable->getLanguage();
return $this->newMailMessage($language)
->subject(trans('notifications.new_page_subject', ['pageName' => $page->getShortName()], $language))
->line(trans('notifications.new_page_intro', ['appName' => setting('app-name')], $language))
->line(new ListMessageLine([
trans('notifications.detail_page_name', [], $language) => $page->name,
trans('notifications.detail_created_by', [], $language) => $this->user->name,
]))
->action(trans('notifications.action_view_page', [], $language), $page->getUrl())
->line($this->buildReasonFooterLine($language));
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace BookStack\Activity\Notifications\Messages;
use BookStack\Activity\Notifications\MessageParts\ListMessageLine;
use BookStack\Entities\Models\Page;
use BookStack\Users\Models\User;
use Illuminate\Notifications\Messages\MailMessage;
class PageUpdateNotification extends BaseActivityNotification
{
public function toMail(User $notifiable): MailMessage
{
/** @var Page $page */
$page = $this->detail;
$language = $notifiable->getLanguage();
return $this->newMailMessage($language)
->subject(trans('notifications.updated_page_subject', ['pageName' => $page->getShortName()], $language))
->line(trans('notifications.updated_page_intro', ['appName' => setting('app-name')], $language))
->line(new ListMessageLine([
trans('notifications.detail_page_name', [], $language) => $page->name,
trans('notifications.detail_updated_by', [], $language) => $this->user->name,
]))
->line(trans('notifications.updated_page_debounce', [], $language))
->action(trans('notifications.action_view_page', [], $language), $page->getUrl())
->line($this->buildReasonFooterLine($language));
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace BookStack\Activity\Notifications;
use BookStack\Activity\ActivityType;
use BookStack\Activity\Models\Activity;
use BookStack\Activity\Models\Loggable;
use BookStack\Activity\Notifications\Handlers\CommentCreationNotificationHandler;
use BookStack\Activity\Notifications\Handlers\NotificationHandler;
use BookStack\Activity\Notifications\Handlers\PageCreationNotificationHandler;
use BookStack\Activity\Notifications\Handlers\PageUpdateNotificationHandler;
use BookStack\Users\Models\User;
class NotificationManager
{
/**
* @var class-string<NotificationHandler>[]
*/
protected array $handlers = [];
public function handle(Activity $activity, string|Loggable $detail, User $user): void
{
$activityType = $activity->type;
$handlersToRun = $this->handlers[$activityType] ?? [];
foreach ($handlersToRun as $handlerClass) {
/** @var NotificationHandler $handler */
$handler = new $handlerClass();
$handler->handle($activity, $detail, $user);
}
}
/**
* @param class-string<NotificationHandler> $handlerClass
*/
public function registerHandler(string $activityType, string $handlerClass): void
{
if (!isset($this->handlers[$activityType])) {
$this->handlers[$activityType] = [];
}
if (!in_array($handlerClass, $this->handlers[$activityType])) {
$this->handlers[$activityType][] = $handlerClass;
}
}
public function loadDefaultHandlers(): void
{
$this->registerHandler(ActivityType::PAGE_CREATE, PageCreationNotificationHandler::class);
$this->registerHandler(ActivityType::PAGE_UPDATE, PageUpdateNotificationHandler::class);
$this->registerHandler(ActivityType::COMMENT_CREATE, CommentCreationNotificationHandler::class);
}
}

View File

@@ -1,8 +1,8 @@
<?php
namespace BookStack\Actions\Queries;
namespace BookStack\Activity\Queries;
use BookStack\Actions\Webhook;
use BookStack\Activity\Models\Webhook;
use BookStack\Util\SimpleListOptions;
use Illuminate\Pagination\LengthAwarePaginator;

View File

@@ -1,9 +1,10 @@
<?php
namespace BookStack\Actions;
namespace BookStack\Activity;
use BookStack\Auth\Permissions\PermissionApplicator;
use BookStack\Activity\Models\Tag;
use BookStack\Entities\Models\Entity;
use BookStack\Permissions\PermissionApplicator;
use BookStack\Util\SimpleListOptions;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
@@ -11,11 +12,9 @@ use Illuminate\Support\Facades\DB;
class TagRepo
{
protected PermissionApplicator $permissions;
public function __construct(PermissionApplicator $permissions)
{
$this->permissions = $permissions;
public function __construct(
protected PermissionApplicator $permissions
) {
}
/**
@@ -90,6 +89,7 @@ class TagRepo
{
$query = Tag::query()
->select('*', DB::raw('count(*) as count'))
->where('value', '!=', '')
->groupBy('value');
if ($searchTerm) {

View File

@@ -1,22 +1,30 @@
<?php
namespace BookStack\Actions;
namespace BookStack\Activity\Tools;
use BookStack\Activity\DispatchWebhookJob;
use BookStack\Activity\Models\Activity;
use BookStack\Activity\Models\Loggable;
use BookStack\Activity\Models\Webhook;
use BookStack\Activity\Notifications\NotificationManager;
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
{
public function __construct(
protected NotificationManager $notifications
) {
$this->notifications->loadDefaultHandlers();
}
/**
* Add a generic activity event to the database.
*
* @param string|Loggable $detail
*/
public function add(string $type, $detail = '')
public function add(string $type, string|Loggable $detail = ''): void
{
$detailToStore = ($detail instanceof Loggable) ? $detail->logDescriptor() : $detail;
@@ -32,6 +40,7 @@ class ActivityLogger
$this->setNotification($type);
$this->dispatchWebhooks($type, $detail);
$this->notifications->handle($activity, $detail, user());
Theme::dispatch(ThemeEvents::ACTIVITY_LOGGED, $type, $detail);
}
@@ -52,7 +61,7 @@ class ActivityLogger
* and instead uses the 'extra' field with the entities name.
* Used when an entity is deleted.
*/
public function removeEntity(Entity $entity)
public function removeEntity(Entity $entity): void
{
$entity->activity()->update([
'detail' => $entity->name,
@@ -73,10 +82,7 @@ class ActivityLogger
}
}
/**
* @param string|Loggable $detail
*/
protected function dispatchWebhooks(string $type, $detail): void
protected function dispatchWebhooks(string $type, string|Loggable $detail): void
{
$webhooks = Webhook::query()
->whereHas('trackedEvents', function (Builder $query) use ($type) {
@@ -95,7 +101,7 @@ class ActivityLogger
* 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)
public function logFailedLogin(string $username): void
{
$message = config('logging.failed_login.message');
if (!$message) {

View File

@@ -0,0 +1,102 @@
<?php
namespace BookStack\Activity\Tools;
use BookStack\Activity\Models\Comment;
use BookStack\Entities\Models\Page;
class CommentTree
{
/**
* The built nested tree structure array.
* @var array{comment: Comment, depth: int, children: array}[]
*/
protected array $tree;
protected array $comments;
public function __construct(
protected Page $page
) {
$this->comments = $this->loadComments();
$this->tree = $this->createTree($this->comments);
}
public function enabled(): bool
{
return !setting('app-disable-comments');
}
public function empty(): bool
{
return count($this->tree) === 0;
}
public function count(): int
{
return count($this->comments);
}
public function get(): array
{
return $this->tree;
}
/**
* @param Comment[] $comments
*/
protected function createTree(array $comments): array
{
$byId = [];
foreach ($comments as $comment) {
$byId[$comment->local_id] = $comment;
}
$childMap = [];
foreach ($comments as $comment) {
$parent = $comment->parent_id;
if (is_null($parent) || !isset($byId[$parent])) {
$parent = 0;
}
if (!isset($childMap[$parent])) {
$childMap[$parent] = [];
}
$childMap[$parent][] = $comment->local_id;
}
$tree = [];
foreach ($childMap[0] ?? [] as $childId) {
$tree[] = $this->createTreeForId($childId, 0, $byId, $childMap);
}
return $tree;
}
protected function createTreeForId(int $id, int $depth, array &$byId, array &$childMap): array
{
$childIds = $childMap[$id] ?? [];
$children = [];
foreach ($childIds as $childId) {
$children[] = $this->createTreeForId($childId, $depth + 1, $byId, $childMap);
}
return [
'comment' => $byId[$id],
'depth' => $depth,
'children' => $children,
];
}
protected function loadComments(): array
{
if (!$this->enabled()) {
return [];
}
return $this->page->comments()
->with('createdBy')
->get()
->all();
}
}

View File

@@ -0,0 +1,86 @@
<?php
namespace BookStack\Activity\Tools;
use BookStack\Activity\Models\Watch;
use BookStack\Entities\Models\BookChild;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\Page;
use Illuminate\Database\Eloquent\Builder;
class EntityWatchers
{
/**
* @var int[]
*/
protected array $watchers = [];
/**
* @var int[]
*/
protected array $ignorers = [];
public function __construct(
protected Entity $entity,
protected int $watchLevel,
) {
$this->build();
}
public function getWatcherUserIds(): array
{
return $this->watchers;
}
public function isUserIgnoring(int $userId): bool
{
return in_array($userId, $this->ignorers);
}
protected function build(): void
{
$watches = $this->getRelevantWatches();
// Sort before de-duping, so that the order looped below follows book -> chapter -> page ordering
usort($watches, function (Watch $watchA, Watch $watchB) {
$entityTypeDiff = $watchA->watchable_type <=> $watchB->watchable_type;
return $entityTypeDiff === 0 ? ($watchA->user_id <=> $watchB->user_id) : $entityTypeDiff;
});
// De-dupe by user id to get their most relevant level
$levelByUserId = [];
foreach ($watches as $watch) {
$levelByUserId[$watch->user_id] = $watch->level;
}
// Populate the class arrays
$this->watchers = array_keys(array_filter($levelByUserId, fn(int $level) => $level >= $this->watchLevel));
$this->ignorers = array_keys(array_filter($levelByUserId, fn(int $level) => $level === 0));
}
/**
* @return Watch[]
*/
protected function getRelevantWatches(): array
{
/** @var Entity[] $entitiesInvolved */
$entitiesInvolved = array_filter([
$this->entity,
$this->entity instanceof BookChild ? $this->entity->book : null,
$this->entity instanceof Page ? $this->entity->chapter : null,
]);
$query = Watch::query()->where(function (Builder $query) use ($entitiesInvolved) {
foreach ($entitiesInvolved as $entity) {
$query->orWhere(function (Builder $query) use ($entity) {
$query->where('watchable_type', '=', $entity->getMorphClass())
->where('watchable_id', '=', $entity->id);
});
}
});
return $query->get([
'level', 'watchable_id', 'watchable_type', 'user_id'
])->all();
}
}

View File

@@ -1,6 +1,6 @@
<?php
namespace BookStack\Actions;
namespace BookStack\Activity\Tools;
class IpFormatter
{

View File

@@ -1,6 +1,8 @@
<?php
namespace BookStack\Actions;
namespace BookStack\Activity\Tools;
use BookStack\Activity\Models\Tag;
class TagClassGenerator
{

View File

@@ -0,0 +1,131 @@
<?php
namespace BookStack\Activity\Tools;
use BookStack\Activity\Models\Watch;
use BookStack\Activity\WatchLevels;
use BookStack\Entities\Models\BookChild;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\Page;
use BookStack\Users\Models\User;
use Illuminate\Database\Eloquent\Builder;
class UserEntityWatchOptions
{
protected ?array $watchMap = null;
public function __construct(
protected User $user,
protected Entity $entity,
) {
}
public function canWatch(): bool
{
return $this->user->can('receive-notifications') && !$this->user->isDefault();
}
public function getWatchLevel(): string
{
return WatchLevels::levelValueToName($this->getWatchLevelValue());
}
public function isWatching(): bool
{
return $this->getWatchLevelValue() !== WatchLevels::DEFAULT;
}
public function getWatchedParent(): ?WatchedParentDetails
{
$watchMap = $this->getWatchMap();
unset($watchMap[$this->entity->getMorphClass()]);
if (isset($watchMap['chapter'])) {
return new WatchedParentDetails('chapter', $watchMap['chapter']);
}
if (isset($watchMap['book'])) {
return new WatchedParentDetails('book', $watchMap['book']);
}
return null;
}
public function updateLevelByName(string $level): void
{
$levelValue = WatchLevels::levelNameToValue($level);
$this->updateLevelByValue($levelValue);
}
public function updateLevelByValue(int $level): void
{
if ($level < 0) {
$this->remove();
return;
}
$this->updateLevel($level);
}
public function getWatchMap(): array
{
if (!is_null($this->watchMap)) {
return $this->watchMap;
}
$entities = [$this->entity];
if ($this->entity instanceof BookChild) {
$entities[] = $this->entity->book;
}
if ($this->entity instanceof Page && $this->entity->chapter) {
$entities[] = $this->entity->chapter;
}
$query = Watch::query()
->where('user_id', '=', $this->user->id)
->where(function (Builder $subQuery) use ($entities) {
foreach ($entities as $entity) {
$subQuery->orWhere(function (Builder $whereQuery) use ($entity) {
$whereQuery->where('watchable_type', '=', $entity->getMorphClass())
->where('watchable_id', '=', $entity->id);
});
}
});
$this->watchMap = $query->get(['watchable_type', 'level'])
->pluck('level', 'watchable_type')
->toArray();
return $this->watchMap;
}
protected function getWatchLevelValue()
{
return $this->getWatchMap()[$this->entity->getMorphClass()] ?? WatchLevels::DEFAULT;
}
protected function updateLevel(int $levelValue): void
{
Watch::query()->updateOrCreate([
'watchable_id' => $this->entity->id,
'watchable_type' => $this->entity->getMorphClass(),
'user_id' => $this->user->id,
], [
'level' => $levelValue,
]);
$this->watchMap = null;
}
protected function remove(): void
{
$this->entityQuery()->delete();
$this->watchMap = null;
}
protected function entityQuery(): Builder
{
return Watch::query()->where('watchable_id', '=', $this->entity->id)
->where('watchable_type', '=', $this->entity->getMorphClass())
->where('user_id', '=', $this->user->id);
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace BookStack\Activity\Tools;
use BookStack\Activity\WatchLevels;
class WatchedParentDetails
{
public function __construct(
public string $type,
public int $level,
) {
}
public function ignoring(): bool
{
return $this->level === WatchLevels::IGNORE;
}
}

View File

@@ -1,12 +1,14 @@
<?php
namespace BookStack\Actions;
namespace BookStack\Activity\Tools;
use BookStack\Auth\User;
use BookStack\Activity\ActivityType;
use BookStack\Activity\Models\Loggable;
use BookStack\Activity\Models\Webhook;
use BookStack\App\Model;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\Page;
use BookStack\Interfaces\Loggable;
use BookStack\Model;
use BookStack\Users\Models\User;
use Illuminate\Support\Carbon;
class WebhookFormatter
@@ -15,18 +17,14 @@ class WebhookFormatter
protected string $event;
protected User $initiator;
protected int $initiatedTime;
/**
* @var string|Loggable
*/
protected $detail;
protected string|Loggable $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)
public function __construct(string $event, Webhook $webhook, string|Loggable $detail, User $initiator, int $initiatedTime)
{
$this->webhook = $webhook;
$this->event = $event;

View File

@@ -0,0 +1,91 @@
<?php
namespace BookStack\Activity;
use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\Page;
class WatchLevels
{
/**
* Default level, No specific option set
* Typically not a stored status
*/
const DEFAULT = -1;
/**
* Ignore all notifications.
*/
const IGNORE = 0;
/**
* Watch for new content.
*/
const NEW = 1;
/**
* Watch for updates and new content
*/
const UPDATES = 2;
/**
* Watch for comments, updates and new content.
*/
const COMMENTS = 3;
/**
* Get all the possible values as an option_name => value array.
* @returns array<string, int>
*/
public static function all(): array
{
$options = [];
foreach ((new \ReflectionClass(static::class))->getConstants() as $name => $value) {
$options[strtolower($name)] = $value;
}
return $options;
}
/**
* Get the watch options suited for the given entity.
* @returns array<string, int>
*/
public static function allSuitedFor(Entity $entity): array
{
$options = static::all();
if ($entity instanceof Page) {
unset($options['new']);
} elseif ($entity instanceof Bookshelf) {
return [];
}
return $options;
}
/**
* Convert the given name to a level value.
* Defaults to default value if the level does not exist.
*/
public static function levelNameToValue(string $level): int
{
return static::all()[$level] ?? static::DEFAULT;
}
/**
* Convert the given int level value to a level name.
* Defaults to 'default' level name if not existing.
*/
public static function levelValueToName(int $level): string
{
foreach (static::all() as $name => $value) {
if ($level === $value) {
return $name;
}
}
return 'default';
}
}

View File

@@ -1,8 +1,8 @@
<?php
namespace BookStack\Http\Controllers\Api;
namespace BookStack\Api;
use BookStack\Api\ApiDocsGenerator;
use BookStack\Http\ApiController;
class ApiDocsController extends ApiController
{
@@ -28,4 +28,12 @@ class ApiDocsController extends ApiController
return response()->json($docs);
}
/**
* Redirect to the API docs page.
*/
public function redirect()
{
return redirect('/api/docs');
}
}

View File

@@ -2,11 +2,12 @@
namespace BookStack\Api;
use BookStack\Http\Controllers\Api\ApiController;
use BookStack\Http\ApiController;
use Exception;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules\Password;
@@ -16,8 +17,8 @@ use ReflectionMethod;
class ApiDocsGenerator
{
protected $reflectionClasses = [];
protected $controllerClasses = [];
protected array $reflectionClasses = [];
protected array $controllerClasses = [];
/**
* Load the docs form the cache if existing
@@ -27,13 +28,16 @@ class ApiDocsGenerator
{
$appVersion = trim(file_get_contents(base_path('version')));
$cacheKey = 'api-docs::' . $appVersion;
if (Cache::has($cacheKey) && config('app.env') === 'production') {
$docs = Cache::get($cacheKey);
} else {
$docs = (new ApiDocsGenerator())->generate();
Cache::put($cacheKey, $docs, 60 * 24);
$isProduction = config('app.env') === 'production';
$cacheVal = $isProduction ? Cache::get($cacheKey) : null;
if (!is_null($cacheVal)) {
return $cacheVal;
}
$docs = (new ApiDocsGenerator())->generate();
Cache::put($cacheKey, $docs, 60 * 24);
return $docs;
}
@@ -139,9 +143,10 @@ class ApiDocsGenerator
protected function parseDescriptionFromMethodComment(string $comment): string
{
$matches = [];
preg_match_all('/^\s*?\*\s((?![@\s]).*?)$/m', $comment, $matches);
preg_match_all('/^\s*?\*\s?($|((?![\/@\s]).*?))$/m', $comment, $matches);
return implode(' ', $matches[1] ?? []);
$text = implode(' ', $matches[1] ?? []);
return str_replace(' ', "\n", $text);
}
/**

View File

@@ -10,7 +10,7 @@ class ApiEntityListFormatter
* The list to be formatted.
* @var Entity[]
*/
protected $list = [];
protected array $list = [];
/**
* The fields to show in the formatted data.
@@ -19,9 +19,9 @@ class ApiEntityListFormatter
* 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',
protected array $fields = [
'id', 'name', 'slug', 'book_id', 'chapter_id', 'draft',
'template', 'priority', 'created_at', 'updated_at',
];
public function __construct(array $list)

View File

@@ -2,8 +2,9 @@
namespace BookStack\Api;
use BookStack\Auth\User;
use BookStack\Interfaces\Loggable;
use BookStack\Activity\Models\Loggable;
use BookStack\Users\Models\User;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Carbon;
@@ -20,6 +21,8 @@ use Illuminate\Support\Carbon;
*/
class ApiToken extends Model implements Loggable
{
use HasFactory;
protected $fillable = ['name', 'expires_at'];
protected $casts = [
'expires_at' => 'date:Y-m-d',

View File

@@ -2,7 +2,7 @@
namespace BookStack\Api;
use BookStack\Auth\Access\LoginService;
use BookStack\Access\LoginService;
use BookStack\Exceptions\ApiAuthException;
use Illuminate\Auth\GuardHelpers;
use Illuminate\Contracts\Auth\Authenticatable;

View File

@@ -1,10 +1,10 @@
<?php
namespace BookStack\Http\Controllers;
namespace BookStack\Api;
use BookStack\Actions\ActivityType;
use BookStack\Api\ApiToken;
use BookStack\Auth\User;
use BookStack\Activity\ActivityType;
use BookStack\Http\Controller;
use BookStack\Users\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
@@ -58,7 +58,6 @@ class UserApiTokenController extends Controller
$token->save();
session()->flash('api-token-secret:' . $token->id, $secret);
$this->showSuccessNotification(trans('settings.user_api_token_create_success'));
$this->logActivity(ActivityType::API_TOKEN_CREATE, $token);
return redirect($user->getEditUrl('/api-tokens/' . $token->id));
@@ -96,7 +95,6 @@ class UserApiTokenController extends Controller
'expires_at' => $request->get('expires_at') ?: ApiToken::defaultExpiry(),
])->save();
$this->showSuccessNotification(trans('settings.user_api_token_update_success'));
$this->logActivity(ActivityType::API_TOKEN_UPDATE, $token);
return redirect($user->getEditUrl('/api-tokens/' . $token->id));
@@ -123,7 +121,6 @@ class UserApiTokenController extends Controller
[$user, $token] = $this->checkPermissionAndFetchUserToken($userId, $tokenId);
$token->delete();
$this->showSuccessNotification(trans('settings.user_api_token_delete_success'));
$this->logActivity(ActivityType::API_TOKEN_DELETE, $token);
return redirect($user->getEditUrl('#api_tokens'));

View File

@@ -1,6 +1,6 @@
<?php
namespace BookStack;
namespace BookStack\App;
class Application extends \Illuminate\Foundation\Application
{

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