mirror of
https://github.com/BookStackApp/BookStack.git
synced 2026-02-13 19:06:31 +03:00
Compare commits
327 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b70a5c0cdb | ||
|
|
9443ae9f40 | ||
|
|
d62d2384cb | ||
|
|
a981dc41cb | ||
|
|
97ffbaa740 | ||
|
|
8051558d3a | ||
|
|
7ef059e254 | ||
|
|
4329fee2c9 | ||
|
|
b1cf5ab309 | ||
|
|
b67d9f4036 | ||
|
|
224d9e7a7d | ||
|
|
31419c3913 | ||
|
|
ba36d36597 | ||
|
|
ac7d6b8737 | ||
|
|
e52dab825a | ||
|
|
33f51d5b78 | ||
|
|
ae7529376b | ||
|
|
aefd4d423c | ||
|
|
bad44391d4 | ||
|
|
1880633be3 | ||
|
|
8273f8b16e | ||
|
|
0600f752eb | ||
|
|
342dc8948c | ||
|
|
3e24e44106 | ||
|
|
404d11d0eb | ||
|
|
07b889547d | ||
|
|
220c2a4102 | ||
|
|
e9914eb301 | ||
|
|
e47a7e0b97 | ||
|
|
989309fdff | ||
|
|
6797c91eeb | ||
|
|
b9ad3f9f65 | ||
|
|
7a8678e5f7 | ||
|
|
ba09dad1fe | ||
|
|
5910e00fb8 | ||
|
|
3f83c548f8 | ||
|
|
adc866cb3d | ||
|
|
ad542f0407 | ||
|
|
15786e2630 | ||
|
|
8c190324ac | ||
|
|
79f6dc00a3 | ||
|
|
cb832a2c10 | ||
|
|
a87ae16010 | ||
|
|
aeb1fc4d49 | ||
|
|
6428f32483 | ||
|
|
884e20cc5e | ||
|
|
fc761784a1 | ||
|
|
e0c229114f | ||
|
|
4e49d06182 | ||
|
|
2bb06463d5 | ||
|
|
4d6df37963 | ||
|
|
553d3ce861 | ||
|
|
0bc5ccba32 | ||
|
|
ed330f246c | ||
|
|
6c66a8935a | ||
|
|
c653618eab | ||
|
|
efc034bd8d | ||
|
|
c24764018a | ||
|
|
c8cf6731e2 | ||
|
|
d0db0f8e26 | ||
|
|
c380c10d54 | ||
|
|
95d4149d5e | ||
|
|
7f3f6e65b9 | ||
|
|
29f17fd154 | ||
|
|
84419005e7 | ||
|
|
d3cd369247 | ||
|
|
50a9c71de0 | ||
|
|
faa3a8b842 | ||
|
|
c836862d89 | ||
|
|
03073dd9e4 | ||
|
|
ee58bea8b7 | ||
|
|
9406b4d4c9 | ||
|
|
01be72d5e2 | ||
|
|
21e1123d12 | ||
|
|
8d358e4894 | ||
|
|
f797d2da20 | ||
|
|
d3cc261320 | ||
|
|
cc24d478aa | ||
|
|
07adfb2ff1 | ||
|
|
36481bb73f | ||
|
|
4d5e47a2d2 | ||
|
|
d66fab8bee | ||
|
|
2694bb8fab | ||
|
|
b12ae6d11b | ||
|
|
221a483b40 | ||
|
|
4a127c29a5 | ||
|
|
8c21b5345d | ||
|
|
0a06e2bce3 | ||
|
|
d9cde4123d | ||
|
|
7cda9b026e | ||
|
|
67ed4710b6 | ||
|
|
745a0bb98d | ||
|
|
aedff7dc6d | ||
|
|
17969c0bbf | ||
|
|
666ced9c3b | ||
|
|
37bf7f11e4 | ||
|
|
bda8aa414b | ||
|
|
60d175a9b9 | ||
|
|
42e908c7f0 | ||
|
|
53a26a365c | ||
|
|
4ee0fde0ac | ||
|
|
934512d09c | ||
|
|
9102c90986 | ||
|
|
9879a0d12c | ||
|
|
5d2e80bff3 | ||
|
|
83818234c8 | ||
|
|
5c00187138 | ||
|
|
193e2ffebe | ||
|
|
b2a5d07787 | ||
|
|
15cf9f8b32 | ||
|
|
f9adfee47a | ||
|
|
2e988277f0 | ||
|
|
4e8e28c35f | ||
|
|
4cbfb2bf13 | ||
|
|
c3e74219c4 | ||
|
|
13c9d7bc2d | ||
|
|
f5fe524e6c | ||
|
|
119b539586 | ||
|
|
29a5c180f0 | ||
|
|
37b91b6b0e | ||
|
|
b3a4d8af2a | ||
|
|
8b7bee7c67 | ||
|
|
837d2bc582 | ||
|
|
64cd499542 | ||
|
|
5f2d226f09 | ||
|
|
7906602291 | ||
|
|
6dafe773ff | ||
|
|
00703fa817 | ||
|
|
44c537de1a | ||
|
|
6bccf0e64a | ||
|
|
042a6f9760 | ||
|
|
04287745e4 | ||
|
|
5c9b528517 | ||
|
|
6be2d3f28c | ||
|
|
6d20bdc1fb | ||
|
|
502ea608bf | ||
|
|
55b07c7076 | ||
|
|
33e999909f | ||
|
|
6be95cd2ac | ||
|
|
f467185e86 | ||
|
|
646fd822c5 | ||
|
|
d96baf2d4a | ||
|
|
1c312906bc | ||
|
|
9126c87f2b | ||
|
|
579d98a908 | ||
|
|
98a4359198 | ||
|
|
6f710225b5 | ||
|
|
b273b9d6d0 | ||
|
|
e471d0c52a | ||
|
|
0a431e3223 | ||
|
|
035a0d8efb | ||
|
|
e70423c73f | ||
|
|
8445304fe9 | ||
|
|
f1e571a57c | ||
|
|
e9be2b7174 | ||
|
|
352cbbd074 | ||
|
|
b00b319e83 | ||
|
|
a112c11df8 | ||
|
|
058cc2cbd6 | ||
|
|
edd98c00e5 | ||
|
|
19bb11a1c9 | ||
|
|
9511d10ec8 | ||
|
|
3286f29a61 | ||
|
|
77d3bd31a6 | ||
|
|
56004abdf4 | ||
|
|
df6f6e2d77 | ||
|
|
ba1b3fc181 | ||
|
|
67d4fa0c65 | ||
|
|
49deab3a02 | ||
|
|
5325870271 | ||
|
|
138f5d5c4f | ||
|
|
880d4f35da | ||
|
|
20988962fe | ||
|
|
32603362a6 | ||
|
|
9dba9ca178 | ||
|
|
8a4a81629f | ||
|
|
5ef0992d5b | ||
|
|
25bc28a1be | ||
|
|
4c561c7fa0 | ||
|
|
12be7d0086 | ||
|
|
ba0af9214e | ||
|
|
36424a24b5 | ||
|
|
a70ee9664a | ||
|
|
156c0a88e9 | ||
|
|
0efed43389 | ||
|
|
163a57cf70 | ||
|
|
95b3e78573 | ||
|
|
63a345bc93 | ||
|
|
a3ccde8698 | ||
|
|
9700b7ccea | ||
|
|
54c428c375 | ||
|
|
ebe5d643f3 | ||
|
|
e66ddbc17b | ||
|
|
0e0a17cc30 | ||
|
|
ffceb4092e | ||
|
|
50e5527483 | ||
|
|
f63fd4beca | ||
|
|
d682a0157f | ||
|
|
70ad707c3c | ||
|
|
3062bf1876 | ||
|
|
a2087fe3ff | ||
|
|
19770d2792 | ||
|
|
99c6d70c51 | ||
|
|
0830521e60 | ||
|
|
2317bf2350 | ||
|
|
2c48f4f7e8 | ||
|
|
456afdcd4c | ||
|
|
68017e2553 | ||
|
|
866187830a | ||
|
|
b56fc21aaf | ||
|
|
d673bf61c2 | ||
|
|
18b10153e5 | ||
|
|
7c8edf5673 | ||
|
|
f4ea5f1f55 | ||
|
|
f62843c861 | ||
|
|
5fe630b8d2 | ||
|
|
d910defbfd | ||
|
|
153adb055c | ||
|
|
26ec1cc3dc | ||
|
|
d476e30df0 | ||
|
|
37ab97af8c | ||
|
|
7fcd7a5d91 | ||
|
|
c67f76f776 | ||
|
|
9a444b4a04 | ||
|
|
106f32591d | ||
|
|
7f6929d716 | ||
|
|
651ae2f3be | ||
|
|
101a7b40b9 | ||
|
|
1930ed4d6a | ||
|
|
2753629dbe | ||
|
|
86a00a59d4 | ||
|
|
d6dd96e7fc | ||
|
|
1b1ddb6794 | ||
|
|
f65ff3a9a8 | ||
|
|
323bff7d6d | ||
|
|
0e3d507ec2 | ||
|
|
f943f0d401 | ||
|
|
1b01d65965 | ||
|
|
a2acd063f3 | ||
|
|
e9e3e8b6b1 | ||
|
|
7f95b51b00 | ||
|
|
ff0b9004bc | ||
|
|
8fd8652bbf | ||
|
|
e1474194db | ||
|
|
4c574c22a8 | ||
|
|
0b976d9f91 | ||
|
|
2a882a43ff | ||
|
|
aabd4c0412 | ||
|
|
d39fc84301 | ||
|
|
e093a172cb | ||
|
|
4b01f8934b | ||
|
|
75ca430fd4 | ||
|
|
4cf43f67d6 | ||
|
|
86899864dd | ||
|
|
3c796b1ae7 | ||
|
|
b7915cc7b0 | ||
|
|
eac82c47a5 | ||
|
|
d3d3e2ad3e | ||
|
|
54b36cd305 | ||
|
|
f8396d3632 | ||
|
|
4df11701e7 | ||
|
|
4a872012c5 | ||
|
|
bc116b45b5 | ||
|
|
a059960b9e | ||
|
|
302b53562d | ||
|
|
7770966fed | ||
|
|
d7adcf6c69 | ||
|
|
ebd4c3327d | ||
|
|
d0c166c207 | ||
|
|
321b53c827 | ||
|
|
5e6c039b08 | ||
|
|
178b5af83a | ||
|
|
4be0c567cc | ||
|
|
0724ae3640 | ||
|
|
62a475e464 | ||
|
|
cfc1a2f045 | ||
|
|
b012f27ae3 | ||
|
|
58bec7287f | ||
|
|
9341ae4910 | ||
|
|
1328755f95 | ||
|
|
038b2418f7 | ||
|
|
e3230f8f21 | ||
|
|
fd37d95ffc | ||
|
|
3abde8bfe2 | ||
|
|
2ca8038df2 | ||
|
|
89de328439 | ||
|
|
c37e73b626 | ||
|
|
0283ab11b5 | ||
|
|
5b36ddb12f | ||
|
|
ffc1aa873e | ||
|
|
19b7093438 | ||
|
|
7799ba5c79 | ||
|
|
c356612612 | ||
|
|
1c89fcd20a | ||
|
|
730cb78b45 | ||
|
|
8e7f703af7 | ||
|
|
6c14c09880 | ||
|
|
773ab9d7ff | ||
|
|
0e395b1e21 | ||
|
|
6c7d87c836 | ||
|
|
89be30ff0e | ||
|
|
e8ab4fd91f | ||
|
|
5d1162fb64 | ||
|
|
216358c6e4 | ||
|
|
57d99130ee | ||
|
|
79afec9737 | ||
|
|
90929baa52 | ||
|
|
85f330c79a | ||
|
|
77d7f764f1 | ||
|
|
a76599bd2a | ||
|
|
4afc67a962 | ||
|
|
042d8b3274 | ||
|
|
1c0a196b9d | ||
|
|
a1fda37896 | ||
|
|
43758a7d60 | ||
|
|
c7d3db9751 | ||
|
|
fca7689e1a | ||
|
|
18bac4e673 | ||
|
|
ca2a9fbf1c | ||
|
|
cbebe7c8de | ||
|
|
0943221902 | ||
|
|
17ed1b7faf | ||
|
|
36d18f28ee | ||
|
|
495d18814a | ||
|
|
257a5a23ec | ||
|
|
919660678b | ||
|
|
19751ed1cb |
89
.env.example
89
.env.example
@@ -1,11 +1,14 @@
|
||||
# Environment
|
||||
APP_ENV=production
|
||||
APP_DEBUG=false
|
||||
# Application key
|
||||
# Used for encryption where needed.
|
||||
# Run `php artisan key:generate` to generate a valid key.
|
||||
APP_KEY=SomeRandomString
|
||||
|
||||
# The below url has to be set if using social auth options
|
||||
# or if you are not using BookStack at the root path of your domain.
|
||||
# APP_URL=http://bookstack.dev
|
||||
# Application URL
|
||||
# Remove the hash below and set a URL if using BookStack behind
|
||||
# a proxy, if using a third-party authentication option.
|
||||
# This must be the root URL that you want to host BookStack on.
|
||||
# All URL's in BookStack will be generated using this value.
|
||||
#APP_URL=https://example.com
|
||||
|
||||
# Database details
|
||||
DB_HOST=localhost
|
||||
@@ -13,76 +16,16 @@ DB_DATABASE=database_database
|
||||
DB_USERNAME=database_username
|
||||
DB_PASSWORD=database_user_password
|
||||
|
||||
# Cache and session
|
||||
CACHE_DRIVER=file
|
||||
SESSION_DRIVER=file
|
||||
# If using Memcached, comment the above and uncomment these
|
||||
#CACHE_DRIVER=memcached
|
||||
#SESSION_DRIVER=memcached
|
||||
QUEUE_DRIVER=sync
|
||||
# A different prefix is useful when multiple BookStack instances use the same caching server
|
||||
CACHE_PREFIX=bookstack
|
||||
|
||||
# Memcached settings
|
||||
# If using a UNIX socket path for the host, set the port to 0
|
||||
# This follows the following format: HOST:PORT:WEIGHT
|
||||
# For multiple servers separate with a comma
|
||||
MEMCACHED_SERVERS=127.0.0.1:11211:100
|
||||
|
||||
# Storage
|
||||
STORAGE_TYPE=local
|
||||
# Amazon S3 Config
|
||||
STORAGE_S3_KEY=false
|
||||
STORAGE_S3_SECRET=false
|
||||
STORAGE_S3_REGION=false
|
||||
STORAGE_S3_BUCKET=false
|
||||
# Storage URL
|
||||
# Used to prefix image urls for when using custom domains/cdns
|
||||
STORAGE_URL=false
|
||||
|
||||
# General auth
|
||||
AUTH_METHOD=standard
|
||||
|
||||
# Social Authentication information. Defaults as off.
|
||||
GITHUB_APP_ID=false
|
||||
GITHUB_APP_SECRET=false
|
||||
GOOGLE_APP_ID=false
|
||||
GOOGLE_APP_SECRET=false
|
||||
OKTA_BASE_URL=false
|
||||
OKTA_APP_ID=false
|
||||
OKTA_APP_SECRET=false
|
||||
TWITCH_APP_ID=false
|
||||
TWITCH_APP_SECRET=false
|
||||
GITLAB_APP_ID=false
|
||||
GITLAB_APP_SECRET=false
|
||||
GITLAB_BASE_URI=false
|
||||
DISCORD_APP_ID=false
|
||||
DISCORD_APP_SECRET=false
|
||||
|
||||
# External services such as Gravatar and Draw.IO
|
||||
DISABLE_EXTERNAL_SERVICES=false
|
||||
|
||||
# LDAP Settings
|
||||
LDAP_SERVER=false
|
||||
LDAP_BASE_DN=false
|
||||
LDAP_DN=false
|
||||
LDAP_PASS=false
|
||||
LDAP_USER_FILTER=false
|
||||
LDAP_VERSION=false
|
||||
# Do you want to sync LDAP groups to BookStack roles for a user
|
||||
LDAP_USER_TO_GROUPS=false
|
||||
# What is the LDAP attribute for group memberships
|
||||
LDAP_GROUP_ATTRIBUTE="memberOf"
|
||||
# Would you like to remove users from roles on BookStack if they do not match on LDAP
|
||||
# If false, the ldap groups-roles sync will only add users to roles
|
||||
LDAP_REMOVE_FROM_GROUPS=false
|
||||
|
||||
# Mail settings
|
||||
# Mail system to use
|
||||
# Can be 'smtp', 'mail' or 'sendmail'
|
||||
MAIL_DRIVER=smtp
|
||||
|
||||
# SMTP mail options
|
||||
MAIL_HOST=localhost
|
||||
MAIL_PORT=1025
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=null
|
||||
MAIL_FROM=null
|
||||
MAIL_FROM_NAME=null
|
||||
|
||||
|
||||
# A full list of options can be found in the '.env.example.complete' file.
|
||||
227
.env.example.complete
Normal file
227
.env.example.complete
Normal file
@@ -0,0 +1,227 @@
|
||||
# Full list of environment variables that can be used with BookStack.
|
||||
# Selectively copy these to your '.env' file as required.
|
||||
# Each option is shown with it's default value.
|
||||
# Do not copy this whole file to use as your '.env' file.
|
||||
|
||||
# Application environment
|
||||
# Can be 'production', 'development', 'testing' or 'demo'
|
||||
APP_ENV=production
|
||||
|
||||
# Enable debug mode
|
||||
# Shows advanced debug information and errors.
|
||||
# CAN EXPOSE OTHER VARIABLES, LEAVE DISABLED
|
||||
APP_DEBUG=false
|
||||
|
||||
# Application key
|
||||
# Used for encryption where needed.
|
||||
# Run `php artisan key:generate` to generate a valid key.
|
||||
APP_KEY=SomeRandomString
|
||||
|
||||
# Application URL
|
||||
# This must be the root URL that you want to host BookStack on.
|
||||
# All URL's in BookStack will be generated using this value.
|
||||
APP_URL=https://example.com
|
||||
|
||||
# Application default language
|
||||
# The default language choice to show.
|
||||
# May be overridden by user-preference or visitor browser settings.
|
||||
APP_LANG=en
|
||||
|
||||
# Auto-detect language for public visitors.
|
||||
# Uses browser-sent headers to infer a language.
|
||||
# APP_LANG will be used if such a header is not provided.
|
||||
APP_AUTO_LANG_PUBLIC=true
|
||||
|
||||
# Application timezone
|
||||
# Used where dates are displayed such as on exported content.
|
||||
# Valid timezone values can be found here: https://www.php.net/manual/en/timezones.php
|
||||
APP_TIMEZONE=UTC
|
||||
|
||||
# Database details
|
||||
# Host can contain a port (localhost:3306) or a separate DB_PORT option can be used.
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=database_database
|
||||
DB_USERNAME=database_username
|
||||
DB_PASSWORD=database_user_password
|
||||
|
||||
# Mail system to use
|
||||
# Can be 'smtp', 'mail' or 'sendmail'
|
||||
MAIL_DRIVER=smtp
|
||||
|
||||
# Mail sending options
|
||||
MAIL_FROM=mail@bookstackapp.com
|
||||
MAIL_FROM_NAME=BookStack
|
||||
|
||||
# SMTP mail options
|
||||
MAIL_HOST=localhost
|
||||
MAIL_PORT=1025
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=null
|
||||
|
||||
# Cache & Session driver to use
|
||||
# Can be 'file', 'database', 'memcached' or 'redis'
|
||||
CACHE_DRIVER=file
|
||||
SESSION_DRIVER=file
|
||||
|
||||
# Session configuration
|
||||
SESSION_LIFETIME=120
|
||||
SESSION_COOKIE_NAME=bookstack_session
|
||||
SESSION_SECURE_COOKIE=false
|
||||
|
||||
# Cache key prefix
|
||||
# Can be used to prevent conflicts multiple BookStack instances use the same store.
|
||||
CACHE_PREFIX=bookstack
|
||||
|
||||
# Memcached server configuration
|
||||
# If using a UNIX socket path for the host, set the port to 0
|
||||
# This follows the following format: HOST:PORT:WEIGHT
|
||||
# For multiple servers separate with a comma
|
||||
MEMCACHED_SERVERS=127.0.0.1:11211:100
|
||||
|
||||
# Redis server configuration
|
||||
# This follows the following format: HOST:PORT:DATABASE
|
||||
# or, if using a password: HOST:PORT:DATABASE:PASSWORD
|
||||
# For multiple servers separate with a comma. These will be clustered.
|
||||
REDIS_SERVERS=127.0.0.1:6379:0
|
||||
|
||||
# Queue driver to use
|
||||
# Queue not really currently used but may be configurable in the future.
|
||||
# Would advise not to change this for now.
|
||||
QUEUE_DRIVER=sync
|
||||
|
||||
# Storage system to use
|
||||
# Can be 'local', 'local_secure' or 's3'
|
||||
STORAGE_TYPE=local
|
||||
|
||||
# Amazon S3 storage configuration
|
||||
STORAGE_S3_KEY=your-s3-key
|
||||
STORAGE_S3_SECRET=your-s3-secret
|
||||
STORAGE_S3_BUCKET=s3-bucket-name
|
||||
STORAGE_S3_REGION=s3-bucket-region
|
||||
|
||||
# S3 endpoint to use for storage calls
|
||||
# Only set this if using a non-Amazon s3-compatible service such as Minio
|
||||
STORAGE_S3_ENDPOINT=https://my-custom-s3-compatible.service.com:8001
|
||||
|
||||
# Storage URL prefix
|
||||
# Used as a base for any generated image urls.
|
||||
# An s3-format URL will be generated if not set.
|
||||
STORAGE_URL=false
|
||||
|
||||
# Authentication method to use
|
||||
# Can be 'standard' or 'ldap'
|
||||
AUTH_METHOD=standard
|
||||
|
||||
# Social authentication configuration
|
||||
# All disabled by default.
|
||||
# Refer to https://www.bookstackapp.com/docs/admin/third-party-auth/
|
||||
|
||||
AZURE_APP_ID=false
|
||||
AZURE_APP_SECRET=false
|
||||
AZURE_TENANT=false
|
||||
AZURE_AUTO_REGISTER=false
|
||||
AZURE_AUTO_CONFIRM_EMAIL=false
|
||||
|
||||
DISCORD_APP_ID=false
|
||||
DISCORD_APP_SECRET=false
|
||||
DISCORD_AUTO_REGISTER=false
|
||||
DISCORD_AUTO_CONFIRM_EMAIL=false
|
||||
|
||||
FACEBOOK_APP_ID=false
|
||||
FACEBOOK_APP_SECRET=false
|
||||
FACEBOOK_AUTO_REGISTER=false
|
||||
FACEBOOK_AUTO_CONFIRM_EMAIL=false
|
||||
|
||||
GITHUB_APP_ID=false
|
||||
GITHUB_APP_SECRET=false
|
||||
GITHUB_AUTO_REGISTER=false
|
||||
GITHUB_AUTO_CONFIRM_EMAIL=false
|
||||
|
||||
GITLAB_APP_ID=false
|
||||
GITLAB_APP_SECRET=false
|
||||
GITLAB_BASE_URI=false
|
||||
GITLAB_AUTO_REGISTER=false
|
||||
GITLAB_AUTO_CONFIRM_EMAIL=false
|
||||
|
||||
GOOGLE_APP_ID=false
|
||||
GOOGLE_APP_SECRET=false
|
||||
GOOGLE_SELECT_ACCOUNT=false
|
||||
GOOGLE_AUTO_REGISTER=false
|
||||
GOOGLE_AUTO_CONFIRM_EMAIL=false
|
||||
|
||||
OKTA_BASE_URL=false
|
||||
OKTA_APP_ID=false
|
||||
OKTA_APP_SECRET=false
|
||||
OKTA_AUTO_REGISTER=false
|
||||
OKTA_AUTO_CONFIRM_EMAIL=false
|
||||
|
||||
SLACK_APP_ID=false
|
||||
SLACK_APP_SECRET=false
|
||||
SLACK_AUTO_REGISTER=false
|
||||
SLACK_AUTO_CONFIRM_EMAIL=false
|
||||
|
||||
TWITCH_APP_ID=false
|
||||
TWITCH_APP_SECRET=false
|
||||
TWITCH_AUTO_REGISTER=false
|
||||
TWITCH_AUTO_CONFIRM_EMAIL=false
|
||||
|
||||
TWITTER_APP_ID=false
|
||||
TWITTER_APP_SECRET=false
|
||||
TWITTER_AUTO_REGISTER=false
|
||||
TWITTER_AUTO_CONFIRM_EMAIL=false
|
||||
|
||||
# LDAP authentication configuration
|
||||
# Refer to https://www.bookstackapp.com/docs/admin/ldap-auth/
|
||||
LDAP_SERVER=false
|
||||
LDAP_BASE_DN=false
|
||||
LDAP_DN=false
|
||||
LDAP_PASS=false
|
||||
LDAP_USER_FILTER=false
|
||||
LDAP_VERSION=false
|
||||
LDAP_TLS_INSECURE=false
|
||||
LDAP_EMAIL_ATTRIBUTE=mail
|
||||
LDAP_DISPLAY_NAME_ATTRIBUTE=cn
|
||||
LDAP_FOLLOW_REFERRALS=true
|
||||
|
||||
# LDAP group sync configuration
|
||||
# Refer to https://www.bookstackapp.com/docs/admin/ldap-auth/
|
||||
LDAP_USER_TO_GROUPS=false
|
||||
LDAP_GROUP_ATTRIBUTE="memberOf"
|
||||
LDAP_REMOVE_FROM_GROUPS=false
|
||||
|
||||
# Disable default third-party services such as Gravatar and Draw.IO
|
||||
# Service-specific options will override this option
|
||||
DISABLE_EXTERNAL_SERVICES=false
|
||||
|
||||
# Use custom avatar service, Sets fetch URL
|
||||
# Possible placeholders: ${hash} ${size} ${email}
|
||||
# If set, Avatars will be fetched regardless of DISABLE_EXTERNAL_SERVICES option.
|
||||
# Example: AVATAR_URL=https://seccdn.libravatar.org/avatar/${hash}?s=${size}&d=identicon
|
||||
AVATAR_URL=
|
||||
|
||||
# Enable Draw.io integration
|
||||
DRAWIO=true
|
||||
|
||||
# Default item listing view
|
||||
# Used for public visitors and user's without a preference
|
||||
# Can be 'list' or 'grid'
|
||||
APP_VIEWS_BOOKS=list
|
||||
APP_VIEWS_BOOKSHELVES=grid
|
||||
|
||||
# Page revision limit
|
||||
# Number of page revisions to keep in the system before deleting old revisions.
|
||||
# If set to 'false' a limit will not be enforced.
|
||||
REVISION_LIMIT=50
|
||||
|
||||
# Allow <script> tags in page content
|
||||
# Note, if set to 'true' the page editor may still escape scripts.
|
||||
ALLOW_CONTENT_SCRIPTS=false
|
||||
|
||||
# Indicate if robots/crawlers should crawl your instance.
|
||||
# Can be 'true', 'false' or 'null'.
|
||||
# The behaviour of the default 'null' option will depend on the 'app-public' admin setting.
|
||||
# Contents of the robots.txt file can be overridden, making this option obsolete.
|
||||
ALLOW_ROBOTS=null
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack;
|
||||
namespace BookStack\Actions;
|
||||
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Model;
|
||||
|
||||
/**
|
||||
* @property string key
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php namespace BookStack\Services;
|
||||
<?php namespace BookStack\Actions;
|
||||
|
||||
use BookStack\Activity;
|
||||
use BookStack\Entity;
|
||||
use BookStack\Auth\Permissions\PermissionService;
|
||||
use BookStack\Entities\Entity;
|
||||
use Session;
|
||||
|
||||
class ActivityService
|
||||
@@ -12,7 +12,7 @@ class ActivityService
|
||||
|
||||
/**
|
||||
* ActivityService constructor.
|
||||
* @param Activity $activity
|
||||
* @param \BookStack\Actions\Activity $activity
|
||||
* @param PermissionService $permissionService
|
||||
*/
|
||||
public function __construct(Activity $activity, PermissionService $permissionService)
|
||||
@@ -103,18 +103,22 @@ class ActivityService
|
||||
* @param int $page
|
||||
* @return array
|
||||
*/
|
||||
public function entityActivity($entity, $count = 20, $page = 0)
|
||||
public function entityActivity($entity, $count = 20, $page = 1)
|
||||
{
|
||||
if ($entity->isA('book')) {
|
||||
$query = $this->activity->where('book_id', '=', $entity->id);
|
||||
} else {
|
||||
$query = $this->activity->where('entity_type', '=', get_class($entity))
|
||||
$query = $this->activity->where('entity_type', '=', $entity->getMorphClass())
|
||||
->where('entity_id', '=', $entity->id);
|
||||
}
|
||||
|
||||
$activity = $this->permissionService
|
||||
->filterRestrictedEntityRelations($query, 'activities', 'entity_id', 'entity_type')
|
||||
->orderBy('created_at', 'desc')->with(['entity', 'user.avatar'])->skip($count * $page)->take($count)->get();
|
||||
->orderBy('created_at', 'desc')
|
||||
->with(['entity', 'user.avatar'])
|
||||
->skip($count * ($page - 1))
|
||||
->take($count)
|
||||
->get();
|
||||
|
||||
return $this->filterSimilar($activity);
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
<?php namespace BookStack;
|
||||
<?php namespace BookStack\Actions;
|
||||
|
||||
use BookStack\Ownable;
|
||||
|
||||
class Comment extends Ownable
|
||||
{
|
||||
@@ -1,7 +1,6 @@
|
||||
<?php namespace BookStack\Repos;
|
||||
<?php namespace BookStack\Actions;
|
||||
|
||||
use BookStack\Comment;
|
||||
use BookStack\Entity;
|
||||
use BookStack\Entities\Entity;
|
||||
|
||||
/**
|
||||
* Class CommentRepo
|
||||
@@ -11,13 +10,13 @@ class CommentRepo
|
||||
{
|
||||
|
||||
/**
|
||||
* @var Comment $comment
|
||||
* @var \BookStack\Actions\Comment $comment
|
||||
*/
|
||||
protected $comment;
|
||||
|
||||
/**
|
||||
* CommentRepo constructor.
|
||||
* @param Comment $comment
|
||||
* @param \BookStack\Actions\Comment $comment
|
||||
*/
|
||||
public function __construct(Comment $comment)
|
||||
{
|
||||
@@ -27,7 +26,7 @@ class CommentRepo
|
||||
/**
|
||||
* Get a comment by ID.
|
||||
* @param $id
|
||||
* @return Comment|\Illuminate\Database\Eloquent\Model
|
||||
* @return \BookStack\Actions\Comment|\Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function getById($id)
|
||||
{
|
||||
@@ -36,9 +35,9 @@ class CommentRepo
|
||||
|
||||
/**
|
||||
* Create a new comment on an entity.
|
||||
* @param Entity $entity
|
||||
* @param \BookStack\Entities\Entity $entity
|
||||
* @param array $data
|
||||
* @return Comment
|
||||
* @return \BookStack\Actions\Comment
|
||||
*/
|
||||
public function create(Entity $entity, $data = [])
|
||||
{
|
||||
@@ -53,7 +52,7 @@ class CommentRepo
|
||||
|
||||
/**
|
||||
* Update an existing comment.
|
||||
* @param Comment $comment
|
||||
* @param \BookStack\Actions\Comment $comment
|
||||
* @param array $input
|
||||
* @return mixed
|
||||
*/
|
||||
@@ -66,7 +65,7 @@ class CommentRepo
|
||||
|
||||
/**
|
||||
* Delete a comment from the system.
|
||||
* @param Comment $comment
|
||||
* @param \BookStack\Actions\Comment $comment
|
||||
* @return mixed
|
||||
*/
|
||||
public function delete($comment)
|
||||
@@ -76,7 +75,7 @@ class CommentRepo
|
||||
|
||||
/**
|
||||
* Get the next local ID relative to the linked entity.
|
||||
* @param Entity $entity
|
||||
* @param \BookStack\Entities\Entity $entity
|
||||
* @return int
|
||||
*/
|
||||
protected function getNextLocalId(Entity $entity)
|
||||
@@ -1,4 +1,6 @@
|
||||
<?php namespace BookStack;
|
||||
<?php namespace BookStack\Actions;
|
||||
|
||||
use BookStack\Model;
|
||||
|
||||
/**
|
||||
* Class Attribute
|
||||
@@ -1,8 +1,7 @@
|
||||
<?php namespace BookStack\Repos;
|
||||
<?php namespace BookStack\Actions;
|
||||
|
||||
use BookStack\Tag;
|
||||
use BookStack\Entity;
|
||||
use BookStack\Services\PermissionService;
|
||||
use BookStack\Auth\Permissions\PermissionService;
|
||||
use BookStack\Entities\Entity;
|
||||
|
||||
/**
|
||||
* Class TagRepo
|
||||
@@ -17,9 +16,9 @@ class TagRepo
|
||||
|
||||
/**
|
||||
* TagRepo constructor.
|
||||
* @param Tag $attr
|
||||
* @param Entity $ent
|
||||
* @param PermissionService $ps
|
||||
* @param \BookStack\Actions\Tag $attr
|
||||
* @param \BookStack\Entities\Entity $ent
|
||||
* @param \BookStack\Auth\Permissions\PermissionService $ps
|
||||
*/
|
||||
public function __construct(Tag $attr, Entity $ent, PermissionService $ps)
|
||||
{
|
||||
@@ -107,7 +106,7 @@ class TagRepo
|
||||
|
||||
/**
|
||||
* Save an array of tags to an entity
|
||||
* @param Entity $entity
|
||||
* @param \BookStack\Entities\Entity $entity
|
||||
* @param array $tags
|
||||
* @return array|\Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
@@ -128,7 +127,7 @@ class TagRepo
|
||||
/**
|
||||
* Create a new Tag instance from user input.
|
||||
* @param $input
|
||||
* @return Tag
|
||||
* @return \BookStack\Actions\Tag
|
||||
*/
|
||||
protected function newInstanceFromInput($input)
|
||||
{
|
||||
@@ -1,4 +1,6 @@
|
||||
<?php namespace BookStack;
|
||||
<?php namespace BookStack\Actions;
|
||||
|
||||
use BookStack\Model;
|
||||
|
||||
class View extends Model
|
||||
{
|
||||
@@ -1,22 +1,27 @@
|
||||
<?php namespace BookStack\Services;
|
||||
<?php namespace BookStack\Actions;
|
||||
|
||||
use BookStack\Entity;
|
||||
use BookStack\View;
|
||||
use BookStack\Auth\Permissions\PermissionService;
|
||||
use BookStack\Entities\Entity;
|
||||
use BookStack\Entities\EntityProvider;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class ViewService
|
||||
{
|
||||
protected $view;
|
||||
protected $permissionService;
|
||||
protected $entityProvider;
|
||||
|
||||
/**
|
||||
* ViewService constructor.
|
||||
* @param View $view
|
||||
* @param PermissionService $permissionService
|
||||
* @param \BookStack\Actions\View $view
|
||||
* @param \BookStack\Auth\Permissions\PermissionService $permissionService
|
||||
* @param EntityProvider $entityProvider
|
||||
*/
|
||||
public function __construct(View $view, PermissionService $permissionService)
|
||||
public function __construct(View $view, PermissionService $permissionService, EntityProvider $entityProvider)
|
||||
{
|
||||
$this->view = $view;
|
||||
$this->permissionService = $permissionService;
|
||||
$this->entityProvider = $entityProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,22 +55,21 @@ class ViewService
|
||||
* Get the entities with the most views.
|
||||
* @param int $count
|
||||
* @param int $page
|
||||
* @param bool|false|array $filterModel
|
||||
* @param string|array $filterModels
|
||||
* @param string $action - used for permission checking
|
||||
* @return
|
||||
* @return Collection
|
||||
*/
|
||||
public function getPopular($count = 10, $page = 0, $filterModel = false, $action = 'view')
|
||||
public function getPopular(int $count = 10, int $page = 0, $filterModels = null, string $action = 'view')
|
||||
{
|
||||
$skipCount = $count * $page;
|
||||
$query = $this->permissionService->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type', $action)
|
||||
$query = $this->permissionService
|
||||
->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type', $action)
|
||||
->select('*', 'viewable_id', 'viewable_type', \DB::raw('SUM(views) as view_count'))
|
||||
->groupBy('viewable_id', 'viewable_type')
|
||||
->orderBy('view_count', 'desc');
|
||||
|
||||
if ($filterModel && is_array($filterModel)) {
|
||||
$query->whereIn('viewable_type', $filterModel);
|
||||
} else if ($filterModel) {
|
||||
$query->where('viewable_type', '=', get_class($filterModel));
|
||||
if ($filterModels) {
|
||||
$query->whereIn('viewable_type', $this->entityProvider->getMorphClasses($filterModels));
|
||||
}
|
||||
|
||||
return $query->with('viewable')->skip($skipCount)->take($count)->get()->pluck('viewable');
|
||||
@@ -89,7 +93,7 @@ class ViewService
|
||||
->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type');
|
||||
|
||||
if ($filterModel) {
|
||||
$query = $query->where('viewable_type', '=', get_class($filterModel));
|
||||
$query = $query->where('viewable_type', '=', $filterModel->getMorphClass());
|
||||
}
|
||||
$query = $query->where('user_id', '=', $user->id);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php namespace BookStack\Services;
|
||||
<?php namespace BookStack\Auth\Access;
|
||||
|
||||
use BookStack\Notifications\ConfirmEmail;
|
||||
use BookStack\Repos\UserRepo;
|
||||
use Carbon\Carbon;
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Auth\UserRepo;
|
||||
use BookStack\Exceptions\ConfirmationEmailException;
|
||||
use BookStack\Exceptions\UserRegistrationException;
|
||||
use BookStack\User;
|
||||
use BookStack\Notifications\ConfirmEmail;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Connection as Database;
|
||||
|
||||
class EmailConfirmationService
|
||||
@@ -16,7 +16,7 @@ class EmailConfirmationService
|
||||
/**
|
||||
* EmailConfirmationService constructor.
|
||||
* @param Database $db
|
||||
* @param UserRepo $users
|
||||
* @param \BookStack\Auth\UserRepo $users
|
||||
*/
|
||||
public function __construct(Database $db, UserRepo $users)
|
||||
{
|
||||
@@ -27,7 +27,7 @@ class EmailConfirmationService
|
||||
/**
|
||||
* Create new confirmation for a user,
|
||||
* Also removes any existing old ones.
|
||||
* @param User $user
|
||||
* @param \BookStack\Auth\User $user
|
||||
* @throws ConfirmationEmailException
|
||||
*/
|
||||
public function sendConfirmation(User $user)
|
||||
@@ -88,7 +88,7 @@ class EmailConfirmationService
|
||||
|
||||
/**
|
||||
* Delete all email confirmations that belong to a user.
|
||||
* @param User $user
|
||||
* @param \BookStack\Auth\User $user
|
||||
* @return mixed
|
||||
*/
|
||||
public function deleteConfirmationsByUser(User $user)
|
||||
@@ -1,4 +1,4 @@
|
||||
<?php namespace BookStack\Services;
|
||||
<?php namespace BookStack\Auth\Access;
|
||||
|
||||
/**
|
||||
* Class Ldap
|
||||
@@ -92,4 +92,27 @@ class Ldap
|
||||
{
|
||||
return ldap_bind($ldapConnection, $bindRdn, $bindPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* Explode a LDAP dn string into an array of components.
|
||||
* @param string $dn
|
||||
* @param int $withAttrib
|
||||
* @return array
|
||||
*/
|
||||
public function explodeDn(string $dn, int $withAttrib)
|
||||
{
|
||||
return ldap_explode_dn($dn, $withAttrib);
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape a string for use in an LDAP filter.
|
||||
* @param string $value
|
||||
* @param string $ignore
|
||||
* @param int $flags
|
||||
* @return string
|
||||
*/
|
||||
public function escape(string $value, string $ignore = "", int $flags = 0)
|
||||
{
|
||||
return ldap_escape($value, $ignore, $flags);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
<?php namespace BookStack\Services;
|
||||
<?php namespace BookStack\Auth\Access;
|
||||
|
||||
use BookStack\Auth\Access;
|
||||
use BookStack\Auth\Role;
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Auth\UserRepo;
|
||||
use BookStack\Exceptions\LdapException;
|
||||
use BookStack\Repos\UserRepo;
|
||||
use BookStack\Role;
|
||||
use BookStack\User;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
@@ -24,9 +25,9 @@ class LdapService
|
||||
/**
|
||||
* LdapService constructor.
|
||||
* @param Ldap $ldap
|
||||
* @param UserRepo $userRepo
|
||||
* @param \BookStack\Auth\UserRepo $userRepo
|
||||
*/
|
||||
public function __construct(Ldap $ldap, UserRepo $userRepo)
|
||||
public function __construct(Access\Ldap $ldap, UserRepo $userRepo)
|
||||
{
|
||||
$this->ldap = $ldap;
|
||||
$this->config = config('services.ldap');
|
||||
@@ -79,24 +80,44 @@ class LdapService
|
||||
public function getUserDetails($userName)
|
||||
{
|
||||
$emailAttr = $this->config['email_attribute'];
|
||||
$user = $this->getUserWithAttributes($userName, ['cn', 'uid', 'dn', $emailAttr]);
|
||||
$displayNameAttr = $this->config['display_name_attribute'];
|
||||
|
||||
$user = $this->getUserWithAttributes($userName, ['cn', 'uid', 'dn', $emailAttr, $displayNameAttr]);
|
||||
|
||||
if ($user === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$userCn = $this->getUserResponseProperty($user, 'cn', null);
|
||||
return [
|
||||
'uid' => (isset($user['uid'])) ? $user['uid'][0] : $user['dn'],
|
||||
'name' => $user['cn'][0],
|
||||
'uid' => $this->getUserResponseProperty($user, 'uid', $user['dn']),
|
||||
'name' => $this->getUserResponseProperty($user, $displayNameAttr, $userCn),
|
||||
'dn' => $user['dn'],
|
||||
'email' => (isset($user[$emailAttr])) ? (is_array($user[$emailAttr]) ? $user[$emailAttr][0] : $user[$emailAttr]) : null
|
||||
'email' => $this->getUserResponseProperty($user, $emailAttr, null),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a property from an LDAP user response fetch.
|
||||
* Handles properties potentially being part of an array.
|
||||
* @param array $userDetails
|
||||
* @param string $propertyKey
|
||||
* @param $defaultValue
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getUserResponseProperty(array $userDetails, string $propertyKey, $defaultValue)
|
||||
{
|
||||
if (isset($userDetails[$propertyKey])) {
|
||||
return (is_array($userDetails[$propertyKey]) ? $userDetails[$propertyKey][0] : $userDetails[$propertyKey]);
|
||||
}
|
||||
|
||||
return $defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Authenticatable $user
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @return bool
|
||||
* @throws LdapException
|
||||
*/
|
||||
@@ -106,6 +127,7 @@ class LdapService
|
||||
if ($ldapUser === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($ldapUser['uid'] !== $user->external_auth_id) {
|
||||
return false;
|
||||
}
|
||||
@@ -160,15 +182,14 @@ class LdapService
|
||||
throw new LdapException(trans('errors.ldap_extension_not_installed'));
|
||||
}
|
||||
|
||||
// Get port from server string and protocol if specified.
|
||||
$ldapServer = explode(':', $this->config['server']);
|
||||
$hasProtocol = preg_match('/^ldaps{0,1}\:\/\//', $this->config['server']) === 1;
|
||||
if (!$hasProtocol) {
|
||||
array_unshift($ldapServer, '');
|
||||
// Check if TLS_INSECURE is set. The handle is set to NULL due to the nature of
|
||||
// the LDAP_OPT_X_TLS_REQUIRE_CERT option. It can only be set globally and not per handle.
|
||||
if ($this->config['tls_insecure']) {
|
||||
$this->ldap->setOption(null, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_NEVER);
|
||||
}
|
||||
$hostName = $ldapServer[0] . ($hasProtocol?':':'') . $ldapServer[1];
|
||||
$defaultPort = $ldapServer[0] === 'ldaps' ? 636 : 389;
|
||||
$ldapConnection = $this->ldap->connect($hostName, count($ldapServer) > 2 ? intval($ldapServer[2]) : $defaultPort);
|
||||
|
||||
$serverDetails = $this->parseServerString($this->config['server']);
|
||||
$ldapConnection = $this->ldap->connect($serverDetails['host'], $serverDetails['port']);
|
||||
|
||||
if ($ldapConnection === false) {
|
||||
throw new LdapException(trans('errors.ldap_cannot_connect'));
|
||||
@@ -183,6 +204,27 @@ class LdapService
|
||||
return $this->ldapConnection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a LDAP server string and return the host and port for
|
||||
* a connection. Is flexible to formats such as 'ldap.example.com:8069' or 'ldaps://ldap.example.com'
|
||||
* @param $serverString
|
||||
* @return array
|
||||
*/
|
||||
protected function parseServerString($serverString)
|
||||
{
|
||||
$serverNameParts = explode(':', $serverString);
|
||||
|
||||
// If we have a protocol just return the full string since PHP will ignore a separate port.
|
||||
if ($serverNameParts[0] === 'ldaps' || $serverNameParts[0] === 'ldap') {
|
||||
return ['host' => $serverString, 'port' => 389];
|
||||
}
|
||||
|
||||
// Otherwise, extract the port out
|
||||
$hostName = $serverNameParts[0];
|
||||
$ldapPort = (count($serverNameParts) > 1) ? intval($serverNameParts[1]) : 389;
|
||||
return ['host' => $hostName, 'port' => $ldapPort];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a filter string by injecting common variables.
|
||||
* @param string $filterString
|
||||
@@ -194,7 +236,7 @@ class LdapService
|
||||
$newAttrs = [];
|
||||
foreach ($attrs as $key => $attrText) {
|
||||
$newKey = '${' . $key . '}';
|
||||
$newAttrs[$newKey] = $attrText;
|
||||
$newAttrs[$newKey] = $this->ldap->escape($attrText);
|
||||
}
|
||||
return strtr($filterString, $newAttrs);
|
||||
}
|
||||
@@ -264,7 +306,8 @@ class LdapService
|
||||
$baseDn = $this->config['base_dn'];
|
||||
$groupsAttr = strtolower($this->config['group_attribute']);
|
||||
|
||||
$groups = $this->ldap->searchAndGetEntries($ldapConnection, $baseDn, 'CN='.$groupName, [$groupsAttr]);
|
||||
$groupFilter = 'CN=' . $this->ldap->escape($groupName);
|
||||
$groups = $this->ldap->searchAndGetEntries($ldapConnection, $baseDn, $groupFilter, [$groupsAttr]);
|
||||
if ($groups['count'] === 0) {
|
||||
return [];
|
||||
}
|
||||
@@ -276,29 +319,32 @@ class LdapService
|
||||
/**
|
||||
* Filter out LDAP CN and DN language in a ldap search return
|
||||
* Gets the base CN (common name) of the string
|
||||
* @param string $ldapSearchReturn
|
||||
* @param array $userGroupSearchResponse
|
||||
* @return array
|
||||
*/
|
||||
protected function groupFilter($ldapSearchReturn)
|
||||
protected function groupFilter(array $userGroupSearchResponse)
|
||||
{
|
||||
$groupsAttr = strtolower($this->config['group_attribute']);
|
||||
$ldapGroups = [];
|
||||
$count = 0;
|
||||
if (isset($ldapSearchReturn[$groupsAttr]['count'])) {
|
||||
$count = (int) $ldapSearchReturn[$groupsAttr]['count'];
|
||||
|
||||
if (isset($userGroupSearchResponse[$groupsAttr]['count'])) {
|
||||
$count = (int)$userGroupSearchResponse[$groupsAttr]['count'];
|
||||
}
|
||||
for ($i=0; $i<$count; $i++) {
|
||||
$dnComponents = ldap_explode_dn($ldapSearchReturn[$groupsAttr][$i], 1);
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$dnComponents = $this->ldap->explodeDn($userGroupSearchResponse[$groupsAttr][$i], 1);
|
||||
if (!in_array($dnComponents[0], $ldapGroups)) {
|
||||
$ldapGroups[] = $dnComponents[0];
|
||||
}
|
||||
}
|
||||
|
||||
return $ldapGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync the LDAP groups to the user roles for the current user
|
||||
* @param \BookStack\User $user
|
||||
* @param \BookStack\Auth\User $user
|
||||
* @param string $username
|
||||
* @throws LdapException
|
||||
*/
|
||||
@@ -347,7 +393,7 @@ class LdapService
|
||||
/**
|
||||
* Check a role against an array of group names to see if it matches.
|
||||
* Checked against role 'external_auth_id' if set otherwise the name of the role.
|
||||
* @param Role $role
|
||||
* @param \BookStack\Auth\Role $role
|
||||
* @param array $groupNames
|
||||
* @return bool
|
||||
*/
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php namespace BookStack\Services;
|
||||
<?php namespace BookStack\Auth\Access;
|
||||
|
||||
use BookStack\Exceptions\SocialSignInAccountNotUsed;
|
||||
use Laravel\Socialite\Contracts\Factory as Socialite;
|
||||
use BookStack\Auth\SocialAccount;
|
||||
use BookStack\Auth\UserRepo;
|
||||
use BookStack\Exceptions\SocialDriverNotConfigured;
|
||||
use BookStack\Exceptions\SocialSignInAccountNotUsed;
|
||||
use BookStack\Exceptions\UserRegistrationException;
|
||||
use BookStack\Repos\UserRepo;
|
||||
use BookStack\SocialAccount;
|
||||
use Laravel\Socialite\Contracts\Factory as Socialite;
|
||||
use Laravel\Socialite\Contracts\User as SocialUser;
|
||||
|
||||
class SocialAuthService
|
||||
@@ -19,7 +19,7 @@ class SocialAuthService
|
||||
|
||||
/**
|
||||
* SocialAuthService constructor.
|
||||
* @param UserRepo $userRepo
|
||||
* @param \BookStack\Auth\UserRepo $userRepo
|
||||
* @param Socialite $socialite
|
||||
* @param SocialAccount $socialAccount
|
||||
*/
|
||||
@@ -40,7 +40,7 @@ class SocialAuthService
|
||||
public function startLogIn($socialDriver)
|
||||
{
|
||||
$driver = $this->validateDriver($socialDriver);
|
||||
return $this->socialite->driver($driver)->redirect();
|
||||
return $this->getSocialDriver($driver)->redirect();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,7 +52,7 @@ class SocialAuthService
|
||||
public function startRegister($socialDriver)
|
||||
{
|
||||
$driver = $this->validateDriver($socialDriver);
|
||||
return $this->socialite->driver($driver)->redirect();
|
||||
return $this->getSocialDriver($driver)->redirect();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -247,4 +247,20 @@ class SocialAuthService
|
||||
session()->flash('success', trans('settings.users_social_disconnected', ['socialAccount' => title_case($socialDriver)]));
|
||||
return redirect(user()->getEditUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide redirect options per service for the Laravel Socialite driver
|
||||
* @param $driverName
|
||||
* @return \Laravel\Socialite\Contracts\Provider
|
||||
*/
|
||||
public function getSocialDriver(string $driverName)
|
||||
{
|
||||
$driver = $this->socialite->driver($driverName);
|
||||
|
||||
if ($driverName === 'google' && config('services.google.select_account')) {
|
||||
$driver->with(['prompt' => 'select_account']);
|
||||
}
|
||||
|
||||
return $driver;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
<?php namespace BookStack;
|
||||
<?php namespace BookStack\Auth\Permissions;
|
||||
|
||||
use BookStack\Model;
|
||||
|
||||
class EntityPermission extends Model
|
||||
{
|
||||
@@ -1,4 +1,8 @@
|
||||
<?php namespace BookStack;
|
||||
<?php namespace BookStack\Auth\Permissions;
|
||||
|
||||
use BookStack\Auth\Role;
|
||||
use BookStack\Entities\Entity;
|
||||
use BookStack\Model;
|
||||
|
||||
class JointPermission extends Model
|
||||
{
|
||||
@@ -1,15 +1,14 @@
|
||||
<?php namespace BookStack\Services;
|
||||
<?php namespace BookStack\Auth\Permissions;
|
||||
|
||||
use BookStack\Book;
|
||||
use BookStack\Bookshelf;
|
||||
use BookStack\Chapter;
|
||||
use BookStack\Entity;
|
||||
use BookStack\EntityPermission;
|
||||
use BookStack\JointPermission;
|
||||
use BookStack\Auth\Permissions;
|
||||
use BookStack\Auth\Role;
|
||||
use BookStack\Entities\Book;
|
||||
use BookStack\Entities\Bookshelf;
|
||||
use BookStack\Entities\Chapter;
|
||||
use BookStack\Entities\Entity;
|
||||
use BookStack\Entities\EntityProvider;
|
||||
use BookStack\Entities\Page;
|
||||
use BookStack\Ownable;
|
||||
use BookStack\Page;
|
||||
use BookStack\Role;
|
||||
use BookStack\User;
|
||||
use Illuminate\Database\Connection;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Query\Builder as QueryBuilder;
|
||||
@@ -23,17 +22,31 @@ class PermissionService
|
||||
protected $userRoles = false;
|
||||
protected $currentUserModel = false;
|
||||
|
||||
public $book;
|
||||
public $chapter;
|
||||
public $page;
|
||||
public $bookshelf;
|
||||
|
||||
/**
|
||||
* @var Connection
|
||||
*/
|
||||
protected $db;
|
||||
|
||||
/**
|
||||
* @var JointPermission
|
||||
*/
|
||||
protected $jointPermission;
|
||||
|
||||
/**
|
||||
* @var Role
|
||||
*/
|
||||
protected $role;
|
||||
|
||||
/**
|
||||
* @var EntityPermission
|
||||
*/
|
||||
protected $entityPermission;
|
||||
|
||||
/**
|
||||
* @var EntityProvider
|
||||
*/
|
||||
protected $entityProvider;
|
||||
|
||||
protected $entityCache;
|
||||
|
||||
/**
|
||||
@@ -42,29 +55,20 @@ class PermissionService
|
||||
* @param EntityPermission $entityPermission
|
||||
* @param Role $role
|
||||
* @param Connection $db
|
||||
* @param Bookshelf $bookshelf
|
||||
* @param Book $book
|
||||
* @param Chapter $chapter
|
||||
* @param Page $page
|
||||
* @param EntityProvider $entityProvider
|
||||
*/
|
||||
public function __construct(
|
||||
JointPermission $jointPermission,
|
||||
EntityPermission $entityPermission,
|
||||
Permissions\EntityPermission $entityPermission,
|
||||
Role $role,
|
||||
Connection $db,
|
||||
Bookshelf $bookshelf,
|
||||
Book $book,
|
||||
Chapter $chapter,
|
||||
Page $page
|
||||
EntityProvider $entityProvider
|
||||
) {
|
||||
$this->db = $db;
|
||||
$this->jointPermission = $jointPermission;
|
||||
$this->entityPermission = $entityPermission;
|
||||
$this->role = $role;
|
||||
$this->bookshelf = $bookshelf;
|
||||
$this->book = $book;
|
||||
$this->chapter = $chapter;
|
||||
$this->page = $page;
|
||||
$this->entityProvider = $entityProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,7 +82,7 @@ class PermissionService
|
||||
|
||||
/**
|
||||
* Prepare the local entity cache and ensure it's empty
|
||||
* @param Entity[] $entities
|
||||
* @param \BookStack\Entities\Entity[] $entities
|
||||
*/
|
||||
protected function readyEntityCache($entities = [])
|
||||
{
|
||||
@@ -104,7 +108,7 @@ class PermissionService
|
||||
return $this->entityCache['book']->get($bookId);
|
||||
}
|
||||
|
||||
$book = $this->book->find($bookId);
|
||||
$book = $this->entityProvider->book->find($bookId);
|
||||
if ($book === null) {
|
||||
$book = false;
|
||||
}
|
||||
@@ -115,7 +119,7 @@ class PermissionService
|
||||
/**
|
||||
* Get a chapter via ID, Checks local cache
|
||||
* @param $chapterId
|
||||
* @return Book
|
||||
* @return \BookStack\Entities\Book
|
||||
*/
|
||||
protected function getChapter($chapterId)
|
||||
{
|
||||
@@ -123,7 +127,7 @@ class PermissionService
|
||||
return $this->entityCache['chapter']->get($chapterId);
|
||||
}
|
||||
|
||||
$chapter = $this->chapter->find($chapterId);
|
||||
$chapter = $this->entityProvider->chapter->find($chapterId);
|
||||
if ($chapter === null) {
|
||||
$chapter = false;
|
||||
}
|
||||
@@ -172,7 +176,7 @@ class PermissionService
|
||||
});
|
||||
|
||||
// Chunk through all bookshelves
|
||||
$this->bookshelf->newQuery()->select(['id', 'restricted', 'created_by'])
|
||||
$this->entityProvider->bookshelf->newQuery()->select(['id', 'restricted', 'created_by'])
|
||||
->chunk(50, function ($shelves) use ($roles) {
|
||||
$this->buildJointPermissionsForShelves($shelves, $roles);
|
||||
});
|
||||
@@ -184,11 +188,12 @@ class PermissionService
|
||||
*/
|
||||
protected function bookFetchQuery()
|
||||
{
|
||||
return $this->book->newQuery()->select(['id', 'restricted', 'created_by'])->with(['chapters' => function ($query) {
|
||||
$query->select(['id', 'restricted', 'created_by', 'book_id']);
|
||||
}, 'pages' => function ($query) {
|
||||
$query->select(['id', 'restricted', 'created_by', 'book_id', 'chapter_id']);
|
||||
}]);
|
||||
return $this->entityProvider->book->newQuery()
|
||||
->select(['id', 'restricted', 'created_by'])->with(['chapters' => function ($query) {
|
||||
$query->select(['id', 'restricted', 'created_by', 'book_id']);
|
||||
}, 'pages' => function ($query) {
|
||||
$query->select(['id', 'restricted', 'created_by', 'book_id', 'chapter_id']);
|
||||
}]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -234,7 +239,7 @@ class PermissionService
|
||||
|
||||
/**
|
||||
* Rebuild the entity jointPermissions for a particular entity.
|
||||
* @param Entity $entity
|
||||
* @param \BookStack\Entities\Entity $entity
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function buildJointPermissionsForEntity(Entity $entity)
|
||||
@@ -290,7 +295,7 @@ class PermissionService
|
||||
});
|
||||
|
||||
// Chunk through all bookshelves
|
||||
$this->bookshelf->newQuery()->select(['id', 'restricted', 'created_by'])
|
||||
$this->entityProvider->bookshelf->newQuery()->select(['id', 'restricted', 'created_by'])
|
||||
->chunk(50, function ($shelves) use ($roles) {
|
||||
$this->buildJointPermissionsForShelves($shelves, $roles);
|
||||
});
|
||||
@@ -329,7 +334,7 @@ class PermissionService
|
||||
|
||||
/**
|
||||
* Delete all of the entity jointPermissions for a list of entities.
|
||||
* @param Entity[] $entities
|
||||
* @param \BookStack\Entities\Entity[] $entities
|
||||
* @throws \Throwable
|
||||
*/
|
||||
protected function deleteManyJointPermissionsForEntities($entities)
|
||||
@@ -410,7 +415,7 @@ class PermissionService
|
||||
|
||||
/**
|
||||
* Get the actions related to an entity.
|
||||
* @param Entity $entity
|
||||
* @param \BookStack\Entities\Entity $entity
|
||||
* @return array
|
||||
*/
|
||||
protected function getActions(Entity $entity)
|
||||
@@ -496,7 +501,7 @@ class PermissionService
|
||||
/**
|
||||
* Create an array of data with the information of an entity jointPermissions.
|
||||
* Used to build data for bulk insertion.
|
||||
* @param Entity $entity
|
||||
* @param \BookStack\Entities\Entity $entity
|
||||
* @param Role $role
|
||||
* @param $action
|
||||
* @param $permissionAll
|
||||
@@ -551,10 +556,43 @@ class PermissionService
|
||||
return $q;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a user has the given permission for any items in the system.
|
||||
* Can be passed an entity instance to filter on a specific type.
|
||||
* @param string $permission
|
||||
* @param string $entityClass
|
||||
* @return bool
|
||||
*/
|
||||
public function checkUserHasPermissionOnAnything(string $permission, string $entityClass = null)
|
||||
{
|
||||
$userRoleIds = $this->currentUser()->roles()->select('id')->pluck('id')->toArray();
|
||||
$userId = $this->currentUser()->id;
|
||||
|
||||
$permissionQuery = $this->db->table('joint_permissions')
|
||||
->where('action', '=', $permission)
|
||||
->whereIn('role_id', $userRoleIds)
|
||||
->where(function ($query) use ($userId) {
|
||||
$query->where('has_permission', '=', 1)
|
||||
->orWhere(function ($query2) use ($userId) {
|
||||
$query2->where('has_permission_own', '=', 1)
|
||||
->where('created_by', '=', $userId);
|
||||
});
|
||||
});
|
||||
|
||||
if (!is_null($entityClass)) {
|
||||
$entityInstance = app()->make($entityClass);
|
||||
$permissionQuery = $permissionQuery->where('entity_type', '=', $entityInstance->getMorphClass());
|
||||
}
|
||||
|
||||
$hasPermission = $permissionQuery->count() > 0;
|
||||
$this->clean();
|
||||
return $hasPermission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an entity has restrictions set on itself or its
|
||||
* parent tree.
|
||||
* @param Entity $entity
|
||||
* @param \BookStack\Entities\Entity $entity
|
||||
* @param $action
|
||||
* @return bool|mixed
|
||||
*/
|
||||
@@ -604,15 +642,17 @@ class PermissionService
|
||||
*/
|
||||
public function bookChildrenQuery($book_id, $filterDrafts = false, $fetchPageContent = false)
|
||||
{
|
||||
$pageSelect = $this->db->table('pages')->selectRaw($this->page->entityRawQuery($fetchPageContent))->where('book_id', '=', $book_id)->where(function ($query) use ($filterDrafts) {
|
||||
$query->where('draft', '=', 0);
|
||||
if (!$filterDrafts) {
|
||||
$query->orWhere(function ($query) {
|
||||
$query->where('draft', '=', 1)->where('created_by', '=', $this->currentUser()->id);
|
||||
});
|
||||
}
|
||||
});
|
||||
$chapterSelect = $this->db->table('chapters')->selectRaw($this->chapter->entityRawQuery())->where('book_id', '=', $book_id);
|
||||
$entities = $this->entityProvider;
|
||||
$pageSelect = $this->db->table('pages')->selectRaw($entities->page->entityRawQuery($fetchPageContent))
|
||||
->where('book_id', '=', $book_id)->where(function ($query) use ($filterDrafts) {
|
||||
$query->where('draft', '=', 0);
|
||||
if (!$filterDrafts) {
|
||||
$query->orWhere(function ($query) {
|
||||
$query->where('draft', '=', 1)->where('created_by', '=', $this->currentUser()->id);
|
||||
});
|
||||
}
|
||||
});
|
||||
$chapterSelect = $this->db->table('chapters')->selectRaw($entities->chapter->entityRawQuery())->where('book_id', '=', $book_id);
|
||||
$query = $this->db->query()->select('*')->from($this->db->raw("({$pageSelect->toSql()} UNION {$chapterSelect->toSql()}) AS U"))
|
||||
->mergeBindings($pageSelect)->mergeBindings($chapterSelect);
|
||||
|
||||
@@ -635,7 +675,7 @@ class PermissionService
|
||||
/**
|
||||
* Add restrictions for a generic entity
|
||||
* @param string $entityType
|
||||
* @param Builder|Entity $query
|
||||
* @param Builder|\BookStack\Entities\Entity $query
|
||||
* @param string $action
|
||||
* @return Builder
|
||||
*/
|
||||
@@ -664,7 +704,7 @@ class PermissionService
|
||||
* @param string $entityIdColumn
|
||||
* @param string $entityTypeColumn
|
||||
* @param string $action
|
||||
* @return mixed
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function filterRestrictedEntityRelations($query, $tableName, $entityIdColumn, $entityTypeColumn, $action = 'view')
|
||||
{
|
||||
@@ -692,23 +732,27 @@ class PermissionService
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters pages that are a direct relation to another item.
|
||||
* Add conditions to a query to filter the selection to related entities
|
||||
* where permissions are granted.
|
||||
* @param $entityType
|
||||
* @param $query
|
||||
* @param $tableName
|
||||
* @param $entityIdColumn
|
||||
* @return mixed
|
||||
*/
|
||||
public function filterRelatedPages($query, $tableName, $entityIdColumn)
|
||||
public function filterRelatedEntity($entityType, $query, $tableName, $entityIdColumn)
|
||||
{
|
||||
$this->currentAction = 'view';
|
||||
$tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn];
|
||||
|
||||
$q = $query->where(function ($query) use ($tableDetails) {
|
||||
$query->where(function ($query) use (&$tableDetails) {
|
||||
$query->whereExists(function ($permissionQuery) use (&$tableDetails) {
|
||||
$pageMorphClass = $this->entityProvider->get($entityType)->getMorphClass();
|
||||
|
||||
$q = $query->where(function ($query) use ($tableDetails, $pageMorphClass) {
|
||||
$query->where(function ($query) use (&$tableDetails, $pageMorphClass) {
|
||||
$query->whereExists(function ($permissionQuery) use (&$tableDetails, $pageMorphClass) {
|
||||
$permissionQuery->select('id')->from('joint_permissions')
|
||||
->whereRaw('joint_permissions.entity_id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
|
||||
->where('entity_type', '=', 'Bookstack\\Page')
|
||||
->where('entity_type', '=', $pageMorphClass)
|
||||
->where('action', '=', $this->currentAction)
|
||||
->whereIn('role_id', $this->getRoles())
|
||||
->where(function ($query) {
|
||||
@@ -720,13 +764,15 @@ class PermissionService
|
||||
});
|
||||
})->orWhere($tableDetails['entityIdColumn'], '=', 0);
|
||||
});
|
||||
|
||||
$this->clean();
|
||||
|
||||
return $q;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current user
|
||||
* @return User
|
||||
* @return \BookStack\Auth\User
|
||||
*/
|
||||
private function currentUser()
|
||||
{
|
||||
@@ -1,10 +1,8 @@
|
||||
<?php namespace BookStack\Repos;
|
||||
<?php namespace BookStack\Auth\Permissions;
|
||||
|
||||
use BookStack\Auth\Permissions;
|
||||
use BookStack\Auth\Role;
|
||||
use BookStack\Exceptions\PermissionsException;
|
||||
use BookStack\RolePermission;
|
||||
use BookStack\Role;
|
||||
use BookStack\Services\PermissionService;
|
||||
use Setting;
|
||||
|
||||
class PermissionsRepo
|
||||
{
|
||||
@@ -19,9 +17,9 @@ class PermissionsRepo
|
||||
* PermissionsRepo constructor.
|
||||
* @param RolePermission $permission
|
||||
* @param Role $role
|
||||
* @param PermissionService $permissionService
|
||||
* @param \BookStack\Auth\Permissions\PermissionService $permissionService
|
||||
*/
|
||||
public function __construct(RolePermission $permission, Role $role, PermissionService $permissionService)
|
||||
public function __construct(RolePermission $permission, Role $role, Permissions\PermissionService $permissionService)
|
||||
{
|
||||
$this->permission = $permission;
|
||||
$this->role = $role;
|
||||
@@ -1,4 +1,7 @@
|
||||
<?php namespace BookStack;
|
||||
<?php namespace BookStack\Auth\Permissions;
|
||||
|
||||
use BookStack\Auth\Role;
|
||||
use BookStack\Model;
|
||||
|
||||
class RolePermission extends Model
|
||||
{
|
||||
@@ -1,4 +1,8 @@
|
||||
<?php namespace BookStack;
|
||||
<?php namespace BookStack\Auth;
|
||||
|
||||
use BookStack\Auth\Permissions\JointPermission;
|
||||
use BookStack\Auth\Permissions\RolePermission;
|
||||
use BookStack\Model;
|
||||
|
||||
class Role extends Model
|
||||
{
|
||||
@@ -10,7 +14,7 @@ class Role extends Model
|
||||
*/
|
||||
public function users()
|
||||
{
|
||||
return $this->belongsToMany(User::class);
|
||||
return $this->belongsToMany(User::class)->orderBy('name', 'asc');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1,4 +1,6 @@
|
||||
<?php namespace BookStack;
|
||||
<?php namespace BookStack\Auth;
|
||||
|
||||
use BookStack\Model;
|
||||
|
||||
class SocialAccount extends Model
|
||||
{
|
||||
@@ -1,6 +1,8 @@
|
||||
<?php namespace BookStack;
|
||||
<?php namespace BookStack\Auth;
|
||||
|
||||
use BookStack\Model;
|
||||
use BookStack\Notifications\ResetPassword;
|
||||
use BookStack\Uploads\Image;
|
||||
use Illuminate\Auth\Authenticatable;
|
||||
use Illuminate\Auth\Passwords\CanResetPassword;
|
||||
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
|
||||
@@ -22,7 +24,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
||||
* The attributes that are mass assignable.
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = ['name', 'email', 'image_id'];
|
||||
protected $fillable = ['name', 'email'];
|
||||
|
||||
/**
|
||||
* The attributes excluded from the model's JSON form.
|
||||
@@ -1,11 +1,12 @@
|
||||
<?php namespace BookStack\Repos;
|
||||
<?php namespace BookStack\Auth;
|
||||
|
||||
use Activity;
|
||||
use BookStack\Entities\Repos\EntityRepo;
|
||||
use BookStack\Exceptions\NotFoundException;
|
||||
use BookStack\Image;
|
||||
use BookStack\Role;
|
||||
use BookStack\User;
|
||||
use BookStack\Exceptions\UserUpdateException;
|
||||
use BookStack\Uploads\Image;
|
||||
use Exception;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Images;
|
||||
|
||||
class UserRepo
|
||||
@@ -43,12 +44,12 @@ class UserRepo
|
||||
*/
|
||||
public function getById($id)
|
||||
{
|
||||
return $this->user->findOrFail($id);
|
||||
return $this->user->newQuery()->findOrFail($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the users with their permissions.
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
* @return Builder|static
|
||||
*/
|
||||
public function getAllUsers()
|
||||
{
|
||||
@@ -59,7 +60,7 @@ class UserRepo
|
||||
* Get all the users with their permissions in a paginated format.
|
||||
* @param int $count
|
||||
* @param $sortData
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
* @return Builder|static
|
||||
*/
|
||||
public function getAllUsersPaginatedAndSorted($count, $sortData)
|
||||
{
|
||||
@@ -80,15 +81,13 @@ class UserRepo
|
||||
* Creates a new user and attaches a role to them.
|
||||
* @param array $data
|
||||
* @param boolean $verifyEmail
|
||||
* @return User
|
||||
* @return \BookStack\Auth\User
|
||||
*/
|
||||
public function registerNew(array $data, $verifyEmail = false)
|
||||
{
|
||||
$user = $this->create($data, $verifyEmail);
|
||||
$this->attachDefaultRole($user);
|
||||
|
||||
// Get avatar from gravatar and save
|
||||
$this->downloadGravatarToUserAvatar($user);
|
||||
$this->downloadAndAssignUserAvatar($user);
|
||||
|
||||
return $user;
|
||||
}
|
||||
@@ -122,7 +121,7 @@ class UserRepo
|
||||
|
||||
/**
|
||||
* Checks if the give user is the only admin.
|
||||
* @param User $user
|
||||
* @param \BookStack\Auth\User $user
|
||||
* @return bool
|
||||
*/
|
||||
public function isOnlyAdmin(User $user)
|
||||
@@ -138,15 +137,48 @@ class UserRepo
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the assigned user roles via an array of role IDs.
|
||||
* @param User $user
|
||||
* @param array $roles
|
||||
* @throws UserUpdateException
|
||||
*/
|
||||
public function setUserRoles(User $user, array $roles)
|
||||
{
|
||||
if ($this->demotingLastAdmin($user, $roles)) {
|
||||
throw new UserUpdateException(trans('errors.role_cannot_remove_only_admin'), $user->getEditUrl());
|
||||
}
|
||||
|
||||
$user->roles()->sync($roles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given user is the last admin and their new roles no longer
|
||||
* contains the admin role.
|
||||
* @param User $user
|
||||
* @param array $newRoles
|
||||
* @return bool
|
||||
*/
|
||||
protected function demotingLastAdmin(User $user, array $newRoles) : bool
|
||||
{
|
||||
if ($this->isOnlyAdmin($user)) {
|
||||
$adminRole = $this->role->getSystemRole('admin');
|
||||
if (!in_array(strval($adminRole->id), $newRoles)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new basic instance of user.
|
||||
* @param array $data
|
||||
* @param boolean $verifyEmail
|
||||
* @return User
|
||||
* @return \BookStack\Auth\User
|
||||
*/
|
||||
public function create(array $data, $verifyEmail = false)
|
||||
{
|
||||
|
||||
return $this->user->forceCreate([
|
||||
'name' => $data['name'],
|
||||
'email' => $data['email'],
|
||||
@@ -157,7 +189,7 @@ class UserRepo
|
||||
|
||||
/**
|
||||
* Remove the given user from storage, Delete all related content.
|
||||
* @param User $user
|
||||
* @param \BookStack\Auth\User $user
|
||||
* @throws Exception
|
||||
*/
|
||||
public function destroy(User $user)
|
||||
@@ -166,7 +198,7 @@ class UserRepo
|
||||
$user->delete();
|
||||
|
||||
// Delete user profile images
|
||||
$profileImages = $images = Image::where('type', '=', 'user')->where('created_by', '=', $user->id)->get();
|
||||
$profileImages = Image::where('type', '=', 'user')->where('uploaded_to', '=', $user->id)->get();
|
||||
foreach ($profileImages as $image) {
|
||||
Images::destroy($image);
|
||||
}
|
||||
@@ -174,7 +206,7 @@ class UserRepo
|
||||
|
||||
/**
|
||||
* Get the latest activity for a user.
|
||||
* @param User $user
|
||||
* @param \BookStack\Auth\User $user
|
||||
* @param int $count
|
||||
* @param int $page
|
||||
* @return array
|
||||
@@ -186,36 +218,36 @@ class UserRepo
|
||||
|
||||
/**
|
||||
* Get the recently created content for this given user.
|
||||
* @param User $user
|
||||
* @param \BookStack\Auth\User $user
|
||||
* @param int $count
|
||||
* @return mixed
|
||||
*/
|
||||
public function getRecentlyCreated(User $user, $count = 20)
|
||||
{
|
||||
$createdByUserQuery = function (Builder $query) use ($user) {
|
||||
$query->where('created_by', '=', $user->id);
|
||||
};
|
||||
|
||||
return [
|
||||
'pages' => $this->entityRepo->getRecentlyCreated('page', $count, 0, function ($query) use ($user) {
|
||||
$query->where('created_by', '=', $user->id);
|
||||
}),
|
||||
'chapters' => $this->entityRepo->getRecentlyCreated('chapter', $count, 0, function ($query) use ($user) {
|
||||
$query->where('created_by', '=', $user->id);
|
||||
}),
|
||||
'books' => $this->entityRepo->getRecentlyCreated('book', $count, 0, function ($query) use ($user) {
|
||||
$query->where('created_by', '=', $user->id);
|
||||
})
|
||||
'pages' => $this->entityRepo->getRecentlyCreated('page', $count, 0, $createdByUserQuery),
|
||||
'chapters' => $this->entityRepo->getRecentlyCreated('chapter', $count, 0, $createdByUserQuery),
|
||||
'books' => $this->entityRepo->getRecentlyCreated('book', $count, 0, $createdByUserQuery),
|
||||
'shelves' => $this->entityRepo->getRecentlyCreated('bookshelf', $count, 0, $createdByUserQuery)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get asset created counts for the give user.
|
||||
* @param User $user
|
||||
* @param \BookStack\Auth\User $user
|
||||
* @return array
|
||||
*/
|
||||
public function getAssetCounts(User $user)
|
||||
{
|
||||
return [
|
||||
'pages' => $this->entityRepo->page->where('created_by', '=', $user->id)->count(),
|
||||
'chapters' => $this->entityRepo->chapter->where('created_by', '=', $user->id)->count(),
|
||||
'books' => $this->entityRepo->book->where('created_by', '=', $user->id)->count(),
|
||||
'pages' => $this->entityRepo->getUserTotalCreated('page', $user),
|
||||
'chapters' => $this->entityRepo->getUserTotalCreated('chapter', $user),
|
||||
'books' => $this->entityRepo->getUserTotalCreated('book', $user),
|
||||
'shelves' => $this->entityRepo->getUserTotalCreated('bookshelf', $user),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -225,7 +257,7 @@ class UserRepo
|
||||
*/
|
||||
public function getAllRoles()
|
||||
{
|
||||
return $this->role->all();
|
||||
return $this->role->newQuery()->orderBy('name', 'asc')->get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -239,25 +271,24 @@ class UserRepo
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a gravatar image for a user and set it as their avatar.
|
||||
* Does not run if gravatar disabled in config.
|
||||
* Get an avatar image for a user and set it as their avatar.
|
||||
* Returns early if avatars disabled or not set in config.
|
||||
* @param User $user
|
||||
* @return bool
|
||||
*/
|
||||
public function downloadGravatarToUserAvatar(User $user)
|
||||
public function downloadAndAssignUserAvatar(User $user)
|
||||
{
|
||||
// Get avatar from gravatar and save
|
||||
if (!config('services.gravatar')) {
|
||||
if (!Images::avatarFetchEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$avatar = Images::saveUserGravatar($user);
|
||||
$avatar = Images::saveUserAvatar($user);
|
||||
$user->avatar()->associate($avatar);
|
||||
$user->save();
|
||||
return true;
|
||||
} catch (Exception $e) {
|
||||
\Log::error('Failed to save user gravatar image');
|
||||
\Log::error('Failed to save user avatar image');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace BookStack\Console\Commands;
|
||||
|
||||
use BookStack\Services\ImageService;
|
||||
use BookStack\Uploads\ImageService;
|
||||
use Illuminate\Console\Command;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
@@ -30,7 +30,7 @@ class CleanupImages extends Command
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
* @param ImageService $imageService
|
||||
* @param \BookStack\Uploads\ImageService $imageService
|
||||
*/
|
||||
public function __construct(ImageService $imageService)
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace BookStack\Console\Commands;
|
||||
|
||||
use BookStack\Activity;
|
||||
use BookStack\Actions\Activity;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ClearActivity extends Command
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace BookStack\Console\Commands;
|
||||
|
||||
use BookStack\PageRevision;
|
||||
use BookStack\Entities\PageRevision;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ClearRevisions extends Command
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace BookStack\Console\Commands;
|
||||
|
||||
use BookStack\Repos\UserRepo;
|
||||
use BookStack\Auth\UserRepo;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class CreateAdmin extends Command
|
||||
@@ -76,7 +76,7 @@ class CreateAdmin extends Command
|
||||
|
||||
$user = $this->userRepo->create(['email' => $email, 'name' => $name, 'password' => $password]);
|
||||
$this->userRepo->attachSystemRole($user, 'admin');
|
||||
$this->userRepo->downloadGravatarToUserAvatar($user);
|
||||
$this->userRepo->downloadAndAssignUserAvatar($user);
|
||||
$user->email_confirmed = true;
|
||||
$user->save();
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace BookStack\Console\Commands;
|
||||
|
||||
use BookStack\User;
|
||||
use BookStack\Repos\UserRepo;
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Auth\UserRepo;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class DeleteUsers extends Command
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace BookStack\Console\Commands;
|
||||
|
||||
use BookStack\Services\PermissionService;
|
||||
use BookStack\Auth\Permissions\PermissionService;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class RegeneratePermissions extends Command
|
||||
@@ -31,7 +31,7 @@ class RegeneratePermissions extends Command
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @param PermissionService $permissionService
|
||||
* @param \BookStack\Auth\\BookStack\Auth\Permissions\PermissionService $permissionService
|
||||
*/
|
||||
public function __construct(PermissionService $permissionService)
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace BookStack\Console\Commands;
|
||||
|
||||
use BookStack\Services\SearchService;
|
||||
use BookStack\Entities\SearchService;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class RegenerateSearch extends Command
|
||||
@@ -26,7 +26,7 @@ class RegenerateSearch extends Command
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @param SearchService $searchService
|
||||
* @param \BookStack\Entities\SearchService $searchService
|
||||
*/
|
||||
public function __construct(SearchService $searchService)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<?php namespace BookStack;
|
||||
<?php namespace BookStack\Entities;
|
||||
|
||||
use BookStack\Uploads\Image;
|
||||
|
||||
class Book extends Entity
|
||||
{
|
||||
@@ -6,6 +8,15 @@ class Book extends Entity
|
||||
|
||||
protected $fillable = ['name', 'description', 'image_id'];
|
||||
|
||||
/**
|
||||
* Get the morph class for this model.
|
||||
* @return string
|
||||
*/
|
||||
public function getMorphClass()
|
||||
{
|
||||
return 'BookStack\\Book';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the url for this book.
|
||||
* @param string|bool $path
|
||||
@@ -27,7 +38,7 @@ class Book extends Entity
|
||||
*/
|
||||
public function getBookCover($width = 440, $height = 250)
|
||||
{
|
||||
$default = baseUrl('/book_default_cover.png');
|
||||
$default = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
|
||||
if (!$this->image_id) {
|
||||
return $default;
|
||||
}
|
||||
@@ -58,6 +69,15 @@ class Book extends Entity
|
||||
return $this->hasMany(Page::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the direct child pages of this book.
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function directPages()
|
||||
{
|
||||
return $this->pages()->where('chapter_id', '=', '0');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all chapters within this book.
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
@@ -81,7 +101,7 @@ class Book extends Entity
|
||||
* @param int $length
|
||||
* @return string
|
||||
*/
|
||||
public function getExcerpt($length = 100)
|
||||
public function getExcerpt(int $length = 100)
|
||||
{
|
||||
$description = $this->description;
|
||||
return strlen($description) > $length ? substr($description, 0, $length-3) . '...' : $description;
|
||||
@@ -1,4 +1,6 @@
|
||||
<?php namespace BookStack;
|
||||
<?php namespace BookStack\Entities;
|
||||
|
||||
use BookStack\Uploads\Image;
|
||||
|
||||
class Bookshelf extends Entity
|
||||
{
|
||||
@@ -8,6 +10,15 @@ class Bookshelf extends Entity
|
||||
|
||||
protected $fillable = ['name', 'description', 'image_id'];
|
||||
|
||||
/**
|
||||
* Get the morph class for this model.
|
||||
* @return string
|
||||
*/
|
||||
public function getMorphClass()
|
||||
{
|
||||
return 'BookStack\\Bookshelf';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the books in this shelf.
|
||||
* Should not be used directly since does not take into account permissions.
|
||||
@@ -15,7 +26,9 @@ class Bookshelf extends Entity
|
||||
*/
|
||||
public function books()
|
||||
{
|
||||
return $this->belongsToMany(Book::class, 'bookshelves_books', 'bookshelf_id', 'book_id')->orderBy('order', 'asc');
|
||||
return $this->belongsToMany(Book::class, 'bookshelves_books', 'bookshelf_id', 'book_id')
|
||||
->withPivot('order')
|
||||
->orderBy('order', 'asc');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,7 +52,8 @@ class Bookshelf extends Entity
|
||||
*/
|
||||
public function getBookCover($width = 440, $height = 250)
|
||||
{
|
||||
$default = baseUrl('/book_default_cover.png');
|
||||
// TODO - Make generic, focused on books right now, Perhaps set-up a better image
|
||||
$default = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
|
||||
if (!$this->image_id) {
|
||||
return $default;
|
||||
}
|
||||
@@ -53,7 +67,7 @@ class Bookshelf extends Entity
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cover image of the book
|
||||
* Get the cover image of the shelf
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function cover()
|
||||
@@ -66,7 +80,7 @@ class Bookshelf extends Entity
|
||||
* @param int $length
|
||||
* @return string
|
||||
*/
|
||||
public function getExcerpt($length = 100)
|
||||
public function getExcerpt(int $length = 100)
|
||||
{
|
||||
$description = $this->description;
|
||||
return strlen($description) > $length ? substr($description, 0, $length-3) . '...' : $description;
|
||||
@@ -80,4 +94,14 @@ class Bookshelf extends Entity
|
||||
{
|
||||
return "'BookStack\\\\BookShelf' as entity_type, id, id as entity_id, slug, name, {$this->textField} as text,'' as html, '0' as book_id, '0' as priority, '0' as chapter_id, '0' as draft, created_by, updated_by, updated_at, created_at";
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this shelf contains the given book.
|
||||
* @param Book $book
|
||||
* @return bool
|
||||
*/
|
||||
public function contains(Book $book)
|
||||
{
|
||||
return $this->books()->where('id', '=', $book->id)->count() > 0;
|
||||
}
|
||||
}
|
||||
34
app/Entities/BreadcrumbsViewComposer.php
Normal file
34
app/Entities/BreadcrumbsViewComposer.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php namespace BookStack\Entities;
|
||||
|
||||
use Illuminate\View\View;
|
||||
|
||||
class BreadcrumbsViewComposer
|
||||
{
|
||||
|
||||
protected $entityContextManager;
|
||||
|
||||
/**
|
||||
* BreadcrumbsViewComposer constructor.
|
||||
* @param EntityContextManager $entityContextManager
|
||||
*/
|
||||
public function __construct(EntityContextManager $entityContextManager)
|
||||
{
|
||||
$this->entityContextManager = $entityContextManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify data when the view is composed.
|
||||
* @param View $view
|
||||
*/
|
||||
public function compose(View $view)
|
||||
{
|
||||
$crumbs = $view->getData()['crumbs'];
|
||||
if (array_first($crumbs) instanceof Book) {
|
||||
$shelf = $this->entityContextManager->getContextualShelfForBook(array_first($crumbs));
|
||||
if ($shelf) {
|
||||
array_unshift($crumbs, $shelf);
|
||||
$view->with('crumbs', $crumbs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<?php namespace BookStack;
|
||||
<?php namespace BookStack\Entities;
|
||||
|
||||
class Chapter extends Entity
|
||||
{
|
||||
@@ -6,6 +6,15 @@ class Chapter extends Entity
|
||||
|
||||
protected $fillable = ['name', 'description', 'priority', 'book_id'];
|
||||
|
||||
/**
|
||||
* Get the morph class for this model.
|
||||
* @return string
|
||||
*/
|
||||
public function getMorphClass()
|
||||
{
|
||||
return 'BookStack\\Chapter';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the book this chapter is within.
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
@@ -44,9 +53,9 @@ class Chapter extends Entity
|
||||
* @param int $length
|
||||
* @return string
|
||||
*/
|
||||
public function getExcerpt($length = 100)
|
||||
public function getExcerpt(int $length = 100)
|
||||
{
|
||||
$description = $this->description;
|
||||
$description = $this->text ?? $this->description;
|
||||
return strlen($description) > $length ? substr($description, 0, $length-3) . '...' : $description;
|
||||
}
|
||||
|
||||
@@ -58,4 +67,13 @@ class Chapter extends Entity
|
||||
{
|
||||
return "'BookStack\\\\Chapter' as entity_type, id, id as entity_id, slug, name, {$this->textField} as text, '' as html, book_id, priority, '0' as chapter_id, '0' as draft, created_by, updated_by, updated_at, created_at";
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this chapter has any child pages.
|
||||
* @return bool
|
||||
*/
|
||||
public function hasChildren()
|
||||
{
|
||||
return count($this->pages) > 0;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,31 @@
|
||||
<?php namespace BookStack;
|
||||
<?php namespace BookStack\Entities;
|
||||
|
||||
use BookStack\Actions\Activity;
|
||||
use BookStack\Actions\Comment;
|
||||
use BookStack\Actions\Tag;
|
||||
use BookStack\Actions\View;
|
||||
use BookStack\Auth\Permissions\EntityPermission;
|
||||
use BookStack\Auth\Permissions\JointPermission;
|
||||
use BookStack\Ownable;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
|
||||
/**
|
||||
* Class Entity
|
||||
* The base class for book-like items such as pages, chapters & books.
|
||||
* This is not a database model in itself but extended.
|
||||
*
|
||||
* @property integer $id
|
||||
* @property string $name
|
||||
* @property string $slug
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
* @property int $created_by
|
||||
* @property int $updated_by
|
||||
* @property boolean $restricted
|
||||
*
|
||||
* @package BookStack\Entities
|
||||
*/
|
||||
class Entity extends Ownable
|
||||
{
|
||||
|
||||
@@ -15,6 +39,17 @@ class Entity extends Ownable
|
||||
*/
|
||||
public $searchFactor = 1.0;
|
||||
|
||||
/**
|
||||
* Get the morph class for this model.
|
||||
* Set here since, due to folder changes, the namespace used
|
||||
* in the database no longer matches the class namespace.
|
||||
* @return string
|
||||
*/
|
||||
public function getMorphClass()
|
||||
{
|
||||
return 'BookStack\\Entity';
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares this entity to another given entity.
|
||||
* Matches by comparing class and id.
|
||||
@@ -67,6 +102,11 @@ class Entity extends Ownable
|
||||
return $this->morphMany(View::class, 'viewable');
|
||||
}
|
||||
|
||||
public function viewCountQuery()
|
||||
{
|
||||
return $this->views()->selectRaw('viewable_id, sum(views) as view_count')->groupBy('viewable_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Tag models that have been user assigned to this entity.
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
|
||||
@@ -158,7 +198,7 @@ class Entity extends Ownable
|
||||
return null;
|
||||
}
|
||||
|
||||
return app('BookStack\\' . $className);
|
||||
return app('BookStack\\Entities\\' . $className);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,6 +223,20 @@ class Entity extends Ownable
|
||||
return $this->{$this->textField};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an excerpt of this entity's descriptive content to the specified length.
|
||||
* @param int $length
|
||||
* @return mixed
|
||||
*/
|
||||
public function getExcerpt(int $length = 100)
|
||||
{
|
||||
$text = $this->getText();
|
||||
if (mb_strlen($text) > $length) {
|
||||
$text = mb_substr($text, 0, $length-3) . '...';
|
||||
}
|
||||
return trim($text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a generalised, common raw query that can be 'unioned' across entities.
|
||||
* @return string
|
||||
60
app/Entities/EntityContextManager.php
Normal file
60
app/Entities/EntityContextManager.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php namespace BookStack\Entities;
|
||||
|
||||
use BookStack\Entities\Repos\EntityRepo;
|
||||
use Illuminate\Session\Store;
|
||||
|
||||
class EntityContextManager
|
||||
{
|
||||
protected $session;
|
||||
protected $entityRepo;
|
||||
|
||||
protected $KEY_SHELF_CONTEXT_ID = 'context_bookshelf_id';
|
||||
|
||||
/**
|
||||
* EntityContextManager constructor.
|
||||
* @param Store $session
|
||||
* @param EntityRepo $entityRepo
|
||||
*/
|
||||
public function __construct(Store $session, EntityRepo $entityRepo)
|
||||
{
|
||||
$this->session = $session;
|
||||
$this->entityRepo = $entityRepo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current bookshelf context for the given book.
|
||||
* @param Book $book
|
||||
* @return Bookshelf|null
|
||||
*/
|
||||
public function getContextualShelfForBook(Book $book)
|
||||
{
|
||||
$contextBookshelfId = $this->session->get($this->KEY_SHELF_CONTEXT_ID, null);
|
||||
if (is_int($contextBookshelfId)) {
|
||||
|
||||
/** @var Bookshelf $shelf */
|
||||
$shelf = $this->entityRepo->getById('bookshelf', $contextBookshelfId);
|
||||
|
||||
if ($shelf && $shelf->contains($book)) {
|
||||
return $shelf;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the current contextual shelf ID.
|
||||
* @param int $shelfId
|
||||
*/
|
||||
public function setShelfContext(int $shelfId)
|
||||
{
|
||||
$this->session->put($this->KEY_SHELF_CONTEXT_ID, $shelfId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the session stored shelf context id.
|
||||
*/
|
||||
public function clearShelfContext()
|
||||
{
|
||||
$this->session->forget($this->KEY_SHELF_CONTEXT_ID);
|
||||
}
|
||||
}
|
||||
106
app/Entities/EntityProvider.php
Normal file
106
app/Entities/EntityProvider.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?php namespace BookStack\Entities;
|
||||
|
||||
/**
|
||||
* Class EntityProvider
|
||||
*
|
||||
* Provides access to the core entity models.
|
||||
* Wrapped up in this provider since they are often used together
|
||||
* so this is a neater alternative to injecting all in individually.
|
||||
*
|
||||
* @package BookStack\Entities
|
||||
*/
|
||||
class EntityProvider
|
||||
{
|
||||
|
||||
/**
|
||||
* @var Bookshelf
|
||||
*/
|
||||
public $bookshelf;
|
||||
|
||||
/**
|
||||
* @var Book
|
||||
*/
|
||||
public $book;
|
||||
|
||||
/**
|
||||
* @var Chapter
|
||||
*/
|
||||
public $chapter;
|
||||
|
||||
/**
|
||||
* @var Page
|
||||
*/
|
||||
public $page;
|
||||
|
||||
/**
|
||||
* @var PageRevision
|
||||
*/
|
||||
public $pageRevision;
|
||||
|
||||
/**
|
||||
* EntityProvider constructor.
|
||||
* @param Bookshelf $bookshelf
|
||||
* @param Book $book
|
||||
* @param Chapter $chapter
|
||||
* @param Page $page
|
||||
* @param PageRevision $pageRevision
|
||||
*/
|
||||
public function __construct(
|
||||
Bookshelf $bookshelf,
|
||||
Book $book,
|
||||
Chapter $chapter,
|
||||
Page $page,
|
||||
PageRevision $pageRevision
|
||||
) {
|
||||
$this->bookshelf = $bookshelf;
|
||||
$this->book = $book;
|
||||
$this->chapter = $chapter;
|
||||
$this->page = $page;
|
||||
$this->pageRevision = $pageRevision;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all core entity types as an associated array
|
||||
* with their basic names as the keys.
|
||||
* @return Entity[]
|
||||
*/
|
||||
public function all()
|
||||
{
|
||||
return [
|
||||
'bookshelf' => $this->bookshelf,
|
||||
'book' => $this->book,
|
||||
'chapter' => $this->chapter,
|
||||
'page' => $this->page,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an entity instance by it's basic name.
|
||||
* @param string $type
|
||||
* @return Entity
|
||||
*/
|
||||
public function get(string $type)
|
||||
{
|
||||
$type = strtolower($type);
|
||||
return $this->all()[$type];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the morph classes, as an array, for a single or multiple types.
|
||||
* @param string|array $types
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getMorphClasses($types)
|
||||
{
|
||||
if (is_string($types)) {
|
||||
$types = [$types];
|
||||
}
|
||||
|
||||
$morphClasses = [];
|
||||
foreach ($types as $type) {
|
||||
$model = $this->get($type);
|
||||
$morphClasses[] = $model->getMorphClass();
|
||||
}
|
||||
return $morphClasses;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
<?php namespace BookStack\Services;
|
||||
<?php namespace BookStack\Entities;
|
||||
|
||||
use BookStack\Book;
|
||||
use BookStack\Chapter;
|
||||
use BookStack\Page;
|
||||
use BookStack\Repos\EntityRepo;
|
||||
use BookStack\Entities\Repos\EntityRepo;
|
||||
use BookStack\Uploads\ImageService;
|
||||
|
||||
class ExportService
|
||||
{
|
||||
@@ -13,7 +11,8 @@ class ExportService
|
||||
|
||||
/**
|
||||
* ExportService constructor.
|
||||
* @param $entityRepo
|
||||
* @param EntityRepo $entityRepo
|
||||
* @param ImageService $imageService
|
||||
*/
|
||||
public function __construct(EntityRepo $entityRepo, ImageService $imageService)
|
||||
{
|
||||
@@ -24,7 +23,7 @@ class ExportService
|
||||
/**
|
||||
* Convert a page to a self-contained HTML file.
|
||||
* Includes required CSS & image content. Images are base64 encoded into the HTML.
|
||||
* @param Page $page
|
||||
* @param \BookStack\Entities\Page $page
|
||||
* @return mixed|string
|
||||
* @throws \Throwable
|
||||
*/
|
||||
@@ -39,7 +38,7 @@ class ExportService
|
||||
|
||||
/**
|
||||
* Convert a chapter to a self-contained HTML file.
|
||||
* @param Chapter $chapter
|
||||
* @param \BookStack\Entities\Chapter $chapter
|
||||
* @return mixed|string
|
||||
* @throws \Throwable
|
||||
*/
|
||||
@@ -89,7 +88,7 @@ class ExportService
|
||||
|
||||
/**
|
||||
* Convert a chapter to a PDF file.
|
||||
* @param Chapter $chapter
|
||||
* @param \BookStack\Entities\Chapter $chapter
|
||||
* @return mixed|string
|
||||
* @throws \Throwable
|
||||
*/
|
||||
@@ -108,7 +107,7 @@ class ExportService
|
||||
|
||||
/**
|
||||
* Convert a book to a PDF file
|
||||
* @param Book $book
|
||||
* @param \BookStack\Entities\Book $book
|
||||
* @return string
|
||||
* @throws \Throwable
|
||||
*/
|
||||
@@ -208,7 +207,7 @@ class ExportService
|
||||
|
||||
/**
|
||||
* Convert a chapter into a plain text string.
|
||||
* @param Chapter $chapter
|
||||
* @param \BookStack\Entities\Chapter $chapter
|
||||
* @return string
|
||||
*/
|
||||
public function chapterToPlainText(Chapter $chapter)
|
||||
@@ -1,4 +1,6 @@
|
||||
<?php namespace BookStack;
|
||||
<?php namespace BookStack\Entities;
|
||||
|
||||
use BookStack\Uploads\Attachment;
|
||||
|
||||
class Page extends Entity
|
||||
{
|
||||
@@ -8,6 +10,15 @@ class Page extends Entity
|
||||
|
||||
public $textField = 'text';
|
||||
|
||||
/**
|
||||
* Get the morph class for this model.
|
||||
* @return string
|
||||
*/
|
||||
public function getMorphClass()
|
||||
{
|
||||
return 'BookStack\\Page';
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this page into a simplified array.
|
||||
* @return mixed
|
||||
@@ -91,17 +102,6 @@ class Page extends Entity
|
||||
return baseUrl('/books/' . urlencode($bookSlug) . $midText . $idComponent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an excerpt of this page's content to the specified length.
|
||||
* @param int $length
|
||||
* @return mixed
|
||||
*/
|
||||
public function getExcerpt($length = 100)
|
||||
{
|
||||
$text = strlen($this->text) > $length ? substr($this->text, 0, $length-3) . '...' : $this->text;
|
||||
return mb_convert_encoding($text, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a generalised, common raw query that can be 'unioned' across entities.
|
||||
* @param bool $withContent
|
||||
@@ -115,7 +115,7 @@ class Page extends Entity
|
||||
|
||||
/**
|
||||
* Get the current revision for the page if existing
|
||||
* @return \BookStack\PageRevision|null
|
||||
* @return \BookStack\Entities\PageRevision|null
|
||||
*/
|
||||
public function getCurrentRevision()
|
||||
{
|
||||
@@ -1,4 +1,7 @@
|
||||
<?php namespace BookStack;
|
||||
<?php namespace BookStack\Entities;
|
||||
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Model;
|
||||
|
||||
class PageRevision extends Model
|
||||
{
|
||||
File diff suppressed because it is too large
Load Diff
524
app/Entities/Repos/PageRepo.php
Normal file
524
app/Entities/Repos/PageRepo.php
Normal file
@@ -0,0 +1,524 @@
|
||||
<?php namespace BookStack\Entities\Repos;
|
||||
|
||||
use BookStack\Entities\Book;
|
||||
use BookStack\Entities\Chapter;
|
||||
use BookStack\Entities\Entity;
|
||||
use BookStack\Entities\Page;
|
||||
use BookStack\Entities\PageRevision;
|
||||
use Carbon\Carbon;
|
||||
use DOMDocument;
|
||||
use DOMElement;
|
||||
use DOMXPath;
|
||||
|
||||
class PageRepo extends EntityRepo
|
||||
{
|
||||
|
||||
/**
|
||||
* Get page by slug.
|
||||
* @param string $pageSlug
|
||||
* @param string $bookSlug
|
||||
* @return Page
|
||||
* @throws \BookStack\Exceptions\NotFoundException
|
||||
*/
|
||||
public function getPageBySlug(string $pageSlug, string $bookSlug)
|
||||
{
|
||||
return $this->getBySlug('page', $pageSlug, $bookSlug);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search through page revisions and retrieve the last page in the
|
||||
* current book that has a slug equal to the one given.
|
||||
* @param string $pageSlug
|
||||
* @param string $bookSlug
|
||||
* @return null|Page
|
||||
*/
|
||||
public function getPageByOldSlug(string $pageSlug, string $bookSlug)
|
||||
{
|
||||
$revision = $this->entityProvider->pageRevision->where('slug', '=', $pageSlug)
|
||||
->whereHas('page', function ($query) {
|
||||
$this->permissionService->enforceEntityRestrictions('page', $query);
|
||||
})
|
||||
->where('type', '=', 'version')
|
||||
->where('book_slug', '=', $bookSlug)
|
||||
->orderBy('created_at', 'desc')
|
||||
->with('page')->first();
|
||||
return $revision !== null ? $revision->page : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a page with any fillable data and saves it into the database.
|
||||
* @param Page $page
|
||||
* @param int $book_id
|
||||
* @param array $input
|
||||
* @return Page
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function updatePage(Page $page, int $book_id, array $input)
|
||||
{
|
||||
// Hold the old details to compare later
|
||||
$oldHtml = $page->html;
|
||||
$oldName = $page->name;
|
||||
|
||||
// Prevent slug being updated if no name change
|
||||
if ($page->name !== $input['name']) {
|
||||
$page->slug = $this->findSuitableSlug('page', $input['name'], $page->id, $book_id);
|
||||
}
|
||||
|
||||
// Save page tags if present
|
||||
if (isset($input['tags'])) {
|
||||
$this->tagRepo->saveTagsToEntity($page, $input['tags']);
|
||||
}
|
||||
|
||||
// Update with new details
|
||||
$userId = user()->id;
|
||||
$page->fill($input);
|
||||
$page->html = $this->formatHtml($input['html']);
|
||||
$page->text = $this->pageToPlainText($page);
|
||||
if (setting('app-editor') !== 'markdown') {
|
||||
$page->markdown = '';
|
||||
}
|
||||
$page->updated_by = $userId;
|
||||
$page->revision_count++;
|
||||
$page->save();
|
||||
|
||||
// Remove all update drafts for this user & page.
|
||||
$this->userUpdatePageDraftsQuery($page, $userId)->delete();
|
||||
|
||||
// Save a revision after updating
|
||||
if ($oldHtml !== $input['html'] || $oldName !== $input['name'] || $input['summary'] !== null) {
|
||||
$this->savePageRevision($page, $input['summary']);
|
||||
}
|
||||
|
||||
$this->searchService->indexEntity($page);
|
||||
|
||||
return $page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a page revision into the system.
|
||||
* @param Page $page
|
||||
* @param null|string $summary
|
||||
* @return PageRevision
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function savePageRevision(Page $page, string $summary = null)
|
||||
{
|
||||
$revision = $this->entityProvider->pageRevision->newInstance($page->toArray());
|
||||
if (setting('app-editor') !== 'markdown') {
|
||||
$revision->markdown = '';
|
||||
}
|
||||
$revision->page_id = $page->id;
|
||||
$revision->slug = $page->slug;
|
||||
$revision->book_slug = $page->book->slug;
|
||||
$revision->created_by = user()->id;
|
||||
$revision->created_at = $page->updated_at;
|
||||
$revision->type = 'version';
|
||||
$revision->summary = $summary;
|
||||
$revision->revision_number = $page->revision_count;
|
||||
$revision->save();
|
||||
|
||||
$revisionLimit = config('app.revision_limit');
|
||||
if ($revisionLimit !== false) {
|
||||
$revisionsToDelete = $this->entityProvider->pageRevision->where('page_id', '=', $page->id)
|
||||
->orderBy('created_at', 'desc')->skip(intval($revisionLimit))->take(10)->get(['id']);
|
||||
if ($revisionsToDelete->count() > 0) {
|
||||
$this->entityProvider->pageRevision->whereIn('id', $revisionsToDelete->pluck('id'))->delete();
|
||||
}
|
||||
}
|
||||
|
||||
return $revision;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a page's html to be tagged correctly within the system.
|
||||
* @param string $htmlText
|
||||
* @return string
|
||||
*/
|
||||
protected function formatHtml(string $htmlText)
|
||||
{
|
||||
if ($htmlText == '') {
|
||||
return $htmlText;
|
||||
}
|
||||
|
||||
libxml_use_internal_errors(true);
|
||||
$doc = new DOMDocument();
|
||||
$doc->loadHTML(mb_convert_encoding($htmlText, 'HTML-ENTITIES', 'UTF-8'));
|
||||
|
||||
$container = $doc->documentElement;
|
||||
$body = $container->childNodes->item(0);
|
||||
$childNodes = $body->childNodes;
|
||||
|
||||
// Set ids on top-level nodes
|
||||
$idMap = [];
|
||||
foreach ($childNodes as $index => $childNode) {
|
||||
$this->setUniqueId($childNode, $idMap);
|
||||
}
|
||||
|
||||
// Ensure no duplicate ids within child items
|
||||
$xPath = new DOMXPath($doc);
|
||||
$idElems = $xPath->query('//body//*//*[@id]');
|
||||
foreach ($idElems as $domElem) {
|
||||
$this->setUniqueId($domElem, $idMap);
|
||||
}
|
||||
|
||||
// Generate inner html as a string
|
||||
$html = '';
|
||||
foreach ($childNodes as $childNode) {
|
||||
$html .= $doc->saveHTML($childNode);
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a unique id on the given DOMElement.
|
||||
* A map for existing ID's should be passed in to check for current existence.
|
||||
* @param DOMElement $element
|
||||
* @param array $idMap
|
||||
*/
|
||||
protected function setUniqueId($element, array &$idMap)
|
||||
{
|
||||
if (get_class($element) !== 'DOMElement') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Overwrite id if not a BookStack custom id
|
||||
$existingId = $element->getAttribute('id');
|
||||
if (strpos($existingId, 'bkmrk') === 0 && !isset($idMap[$existingId])) {
|
||||
$idMap[$existingId] = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Create an unique id for the element
|
||||
// Uses the content as a basis to ensure output is the same every time
|
||||
// the same content is passed through.
|
||||
$contentId = 'bkmrk-' . substr(strtolower(preg_replace('/\s+/', '-', trim($element->nodeValue))), 0, 20);
|
||||
$newId = urlencode($contentId);
|
||||
$loopIndex = 0;
|
||||
|
||||
while (isset($idMap[$newId])) {
|
||||
$newId = urlencode($contentId . '-' . $loopIndex);
|
||||
$loopIndex++;
|
||||
}
|
||||
|
||||
$element->setAttribute('id', $newId);
|
||||
$idMap[$newId] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the plain text version of a page's content.
|
||||
* @param \BookStack\Entities\Page $page
|
||||
* @return string
|
||||
*/
|
||||
protected function pageToPlainText(Page $page) : string
|
||||
{
|
||||
$html = $this->renderPage($page, true);
|
||||
return strip_tags($html);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new draft page instance.
|
||||
* @param Book $book
|
||||
* @param Chapter|null $chapter
|
||||
* @return \BookStack\Entities\Page
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function getDraftPage(Book $book, Chapter $chapter = null)
|
||||
{
|
||||
$page = $this->entityProvider->page->newInstance();
|
||||
$page->name = trans('entities.pages_initial_name');
|
||||
$page->created_by = user()->id;
|
||||
$page->updated_by = user()->id;
|
||||
$page->draft = true;
|
||||
|
||||
if ($chapter) {
|
||||
$page->chapter_id = $chapter->id;
|
||||
}
|
||||
|
||||
$book->pages()->save($page);
|
||||
$page = $this->entityProvider->page->find($page->id);
|
||||
$this->permissionService->buildJointPermissionsForEntity($page);
|
||||
return $page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a page update draft.
|
||||
* @param Page $page
|
||||
* @param array $data
|
||||
* @return PageRevision|Page
|
||||
*/
|
||||
public function updatePageDraft(Page $page, array $data = [])
|
||||
{
|
||||
// If the page itself is a draft simply update that
|
||||
if ($page->draft) {
|
||||
$page->fill($data);
|
||||
if (isset($data['html'])) {
|
||||
$page->text = $this->pageToPlainText($page);
|
||||
}
|
||||
$page->save();
|
||||
return $page;
|
||||
}
|
||||
|
||||
// Otherwise save the data to a revision
|
||||
$userId = user()->id;
|
||||
$drafts = $this->userUpdatePageDraftsQuery($page, $userId)->get();
|
||||
|
||||
if ($drafts->count() > 0) {
|
||||
$draft = $drafts->first();
|
||||
} else {
|
||||
$draft = $this->entityProvider->pageRevision->newInstance();
|
||||
$draft->page_id = $page->id;
|
||||
$draft->slug = $page->slug;
|
||||
$draft->book_slug = $page->book->slug;
|
||||
$draft->created_by = $userId;
|
||||
$draft->type = 'update_draft';
|
||||
}
|
||||
|
||||
$draft->fill($data);
|
||||
if (setting('app-editor') !== 'markdown') {
|
||||
$draft->markdown = '';
|
||||
}
|
||||
|
||||
$draft->save();
|
||||
return $draft;
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish a draft page to make it a normal page.
|
||||
* Sets the slug and updates the content.
|
||||
* @param Page $draftPage
|
||||
* @param array $input
|
||||
* @return Page
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function publishPageDraft(Page $draftPage, array $input)
|
||||
{
|
||||
$draftPage->fill($input);
|
||||
|
||||
// Save page tags if present
|
||||
if (isset($input['tags'])) {
|
||||
$this->tagRepo->saveTagsToEntity($draftPage, $input['tags']);
|
||||
}
|
||||
|
||||
$draftPage->slug = $this->findSuitableSlug('page', $draftPage->name, false, $draftPage->book->id);
|
||||
$draftPage->html = $this->formatHtml($input['html']);
|
||||
$draftPage->text = $this->pageToPlainText($draftPage);
|
||||
$draftPage->draft = false;
|
||||
$draftPage->revision_count = 1;
|
||||
|
||||
$draftPage->save();
|
||||
$this->savePageRevision($draftPage, trans('entities.pages_initial_revision'));
|
||||
$this->searchService->indexEntity($draftPage);
|
||||
return $draftPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* The base query for getting user update drafts.
|
||||
* @param Page $page
|
||||
* @param $userId
|
||||
* @return mixed
|
||||
*/
|
||||
protected function userUpdatePageDraftsQuery(Page $page, int $userId)
|
||||
{
|
||||
return $this->entityProvider->pageRevision->where('created_by', '=', $userId)
|
||||
->where('type', 'update_draft')
|
||||
->where('page_id', '=', $page->id)
|
||||
->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest updated draft revision for a particular page and user.
|
||||
* @param Page $page
|
||||
* @param $userId
|
||||
* @return PageRevision|null
|
||||
*/
|
||||
public function getUserPageDraft(Page $page, int $userId)
|
||||
{
|
||||
return $this->userUpdatePageDraftsQuery($page, $userId)->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification message that informs the user that they are editing a draft page.
|
||||
* @param PageRevision $draft
|
||||
* @return string
|
||||
*/
|
||||
public function getUserPageDraftMessage(PageRevision $draft)
|
||||
{
|
||||
$message = trans('entities.pages_editing_draft_notification', ['timeDiff' => $draft->updated_at->diffForHumans()]);
|
||||
if ($draft->page->updated_at->timestamp <= $draft->updated_at->timestamp) {
|
||||
return $message;
|
||||
}
|
||||
return $message . "\n" . trans('entities.pages_draft_edited_notification');
|
||||
}
|
||||
|
||||
/**
|
||||
* A query to check for active update drafts on a particular page.
|
||||
* @param Page $page
|
||||
* @param int $minRange
|
||||
* @return mixed
|
||||
*/
|
||||
protected function activePageEditingQuery(Page $page, int $minRange = null)
|
||||
{
|
||||
$query = $this->entityProvider->pageRevision->where('type', '=', 'update_draft')
|
||||
->where('page_id', '=', $page->id)
|
||||
->where('updated_at', '>', $page->updated_at)
|
||||
->where('created_by', '!=', user()->id)
|
||||
->with('createdBy');
|
||||
|
||||
if ($minRange !== null) {
|
||||
$query = $query->where('updated_at', '>=', Carbon::now()->subMinutes($minRange));
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a page is being actively editing.
|
||||
* Checks for edits since last page updated.
|
||||
* Passing in a minuted range will check for edits
|
||||
* within the last x minutes.
|
||||
* @param Page $page
|
||||
* @param int $minRange
|
||||
* @return bool
|
||||
*/
|
||||
public function isPageEditingActive(Page $page, int $minRange = null)
|
||||
{
|
||||
$draftSearch = $this->activePageEditingQuery($page, $minRange);
|
||||
return $draftSearch->count() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a notification message concerning the editing activity on a particular page.
|
||||
* @param Page $page
|
||||
* @param int $minRange
|
||||
* @return string
|
||||
*/
|
||||
public function getPageEditingActiveMessage(Page $page, int $minRange = null)
|
||||
{
|
||||
$pageDraftEdits = $this->activePageEditingQuery($page, $minRange)->get();
|
||||
|
||||
$userMessage = $pageDraftEdits->count() > 1 ? trans('entities.pages_draft_edit_active.start_a', ['count' => $pageDraftEdits->count()]): trans('entities.pages_draft_edit_active.start_b', ['userName' => $pageDraftEdits->first()->createdBy->name]);
|
||||
$timeMessage = $minRange === null ? trans('entities.pages_draft_edit_active.time_a') : trans('entities.pages_draft_edit_active.time_b', ['minCount'=>$minRange]);
|
||||
return trans('entities.pages_draft_edit_active.message', ['start' => $userMessage, 'time' => $timeMessage]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the headers on the page to get a navigation menu
|
||||
* @param string $pageContent
|
||||
* @return array
|
||||
*/
|
||||
public function getPageNav(string $pageContent)
|
||||
{
|
||||
if ($pageContent == '') {
|
||||
return [];
|
||||
}
|
||||
libxml_use_internal_errors(true);
|
||||
$doc = new DOMDocument();
|
||||
$doc->loadHTML(mb_convert_encoding($pageContent, 'HTML-ENTITIES', 'UTF-8'));
|
||||
$xPath = new DOMXPath($doc);
|
||||
$headers = $xPath->query("//h1|//h2|//h3|//h4|//h5|//h6");
|
||||
|
||||
if (is_null($headers)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$tree = collect([]);
|
||||
foreach ($headers as $header) {
|
||||
$text = $header->nodeValue;
|
||||
$tree->push([
|
||||
'nodeName' => strtolower($header->nodeName),
|
||||
'level' => intval(str_replace('h', '', $header->nodeName)),
|
||||
'link' => '#' . $header->getAttribute('id'),
|
||||
'text' => strlen($text) > 30 ? substr($text, 0, 27) . '...' : $text
|
||||
]);
|
||||
}
|
||||
|
||||
// Normalise headers if only smaller headers have been used
|
||||
if (count($tree) > 0) {
|
||||
$minLevel = $tree->pluck('level')->min();
|
||||
$tree = $tree->map(function ($header) use ($minLevel) {
|
||||
$header['level'] -= ($minLevel - 2);
|
||||
return $header;
|
||||
});
|
||||
}
|
||||
return $tree->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores a revision's content back into a page.
|
||||
* @param Page $page
|
||||
* @param Book $book
|
||||
* @param int $revisionId
|
||||
* @return Page
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function restorePageRevision(Page $page, Book $book, int $revisionId)
|
||||
{
|
||||
$page->revision_count++;
|
||||
$this->savePageRevision($page);
|
||||
$revision = $page->revisions()->where('id', '=', $revisionId)->first();
|
||||
$page->fill($revision->toArray());
|
||||
$page->slug = $this->findSuitableSlug('page', $page->name, $page->id, $book->id);
|
||||
$page->text = $this->pageToPlainText($page);
|
||||
$page->updated_by = user()->id;
|
||||
$page->save();
|
||||
$this->searchService->indexEntity($page);
|
||||
return $page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the page's parent to the given entity.
|
||||
* @param Page $page
|
||||
* @param Entity $parent
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function changePageParent(Page $page, Entity $parent)
|
||||
{
|
||||
$book = $parent->isA('book') ? $parent : $parent->book;
|
||||
$page->chapter_id = $parent->isA('chapter') ? $parent->id : 0;
|
||||
$page->save();
|
||||
if ($page->book->id !== $book->id) {
|
||||
$page = $this->changeBook('page', $book->id, $page);
|
||||
}
|
||||
$page->load('book');
|
||||
$this->permissionService->buildJointPermissionsForEntity($book);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a copy of a page in a new location with a new name.
|
||||
* @param \BookStack\Entities\Page $page
|
||||
* @param \BookStack\Entities\Entity $newParent
|
||||
* @param string $newName
|
||||
* @return \BookStack\Entities\Page
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function copyPage(Page $page, Entity $newParent, string $newName = '')
|
||||
{
|
||||
$newBook = $newParent->isA('book') ? $newParent : $newParent->book;
|
||||
$newChapter = $newParent->isA('chapter') ? $newParent : null;
|
||||
$copyPage = $this->getDraftPage($newBook, $newChapter);
|
||||
$pageData = $page->getAttributes();
|
||||
|
||||
// Update name
|
||||
if (!empty($newName)) {
|
||||
$pageData['name'] = $newName;
|
||||
}
|
||||
|
||||
// Copy tags from previous page if set
|
||||
if ($page->tags) {
|
||||
$pageData['tags'] = [];
|
||||
foreach ($page->tags as $tag) {
|
||||
$pageData['tags'][] = ['name' => $tag->name, 'value' => $tag->value];
|
||||
}
|
||||
}
|
||||
|
||||
// Set priority
|
||||
if ($newParent->isA('chapter')) {
|
||||
$pageData['priority'] = $this->getNewChapterPriority($newParent);
|
||||
} else {
|
||||
$pageData['priority'] = $this->getNewBookPriority($newParent);
|
||||
}
|
||||
|
||||
return $this->publishPageDraft($copyPage, $pageData);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,34 @@
|
||||
<?php namespace BookStack\Services;
|
||||
<?php namespace BookStack\Entities;
|
||||
|
||||
use BookStack\Book;
|
||||
use BookStack\Bookshelf;
|
||||
use BookStack\Chapter;
|
||||
use BookStack\Entity;
|
||||
use BookStack\Page;
|
||||
use BookStack\SearchTerm;
|
||||
use BookStack\Auth\Permissions\PermissionService;
|
||||
use Illuminate\Database\Connection;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class SearchService
|
||||
{
|
||||
/**
|
||||
* @var SearchTerm
|
||||
*/
|
||||
protected $searchTerm;
|
||||
protected $bookshelf;
|
||||
protected $book;
|
||||
protected $chapter;
|
||||
protected $page;
|
||||
protected $db;
|
||||
protected $permissionService;
|
||||
|
||||
/**
|
||||
* @var Entity[]
|
||||
* @var EntityProvider
|
||||
*/
|
||||
protected $entities;
|
||||
protected $entityProvider;
|
||||
|
||||
/**
|
||||
* @var Connection
|
||||
*/
|
||||
protected $db;
|
||||
|
||||
/**
|
||||
* @var PermissionService
|
||||
*/
|
||||
protected $permissionService;
|
||||
|
||||
|
||||
/**
|
||||
* Acceptable operators to be used in a query
|
||||
@@ -35,27 +39,15 @@ class SearchService
|
||||
/**
|
||||
* SearchService constructor.
|
||||
* @param SearchTerm $searchTerm
|
||||
* @param Bookshelf $bookshelf
|
||||
* @param Book $book
|
||||
* @param Chapter $chapter
|
||||
* @param Page $page
|
||||
* @param EntityProvider $entityProvider
|
||||
* @param Connection $db
|
||||
* @param PermissionService $permissionService
|
||||
*/
|
||||
public function __construct(SearchTerm $searchTerm, Bookshelf $bookshelf, Book $book, Chapter $chapter, Page $page, Connection $db, PermissionService $permissionService)
|
||||
public function __construct(SearchTerm $searchTerm, EntityProvider $entityProvider, Connection $db, PermissionService $permissionService)
|
||||
{
|
||||
$this->searchTerm = $searchTerm;
|
||||
$this->bookshelf = $bookshelf;
|
||||
$this->book = $book;
|
||||
$this->chapter = $chapter;
|
||||
$this->page = $page;
|
||||
$this->entityProvider = $entityProvider;
|
||||
$this->db = $db;
|
||||
$this->entities = [
|
||||
'bookshelf' => $this->bookshelf,
|
||||
'page' => $this->page,
|
||||
'chapter' => $this->chapter,
|
||||
'book' => $this->book
|
||||
];
|
||||
$this->permissionService = $permissionService;
|
||||
}
|
||||
|
||||
@@ -80,7 +72,7 @@ class SearchService
|
||||
public function searchEntities($searchString, $entityType = 'all', $page = 1, $count = 20, $action = 'view')
|
||||
{
|
||||
$terms = $this->parseSearchString($searchString);
|
||||
$entityTypes = array_keys($this->entities);
|
||||
$entityTypes = array_keys($this->entityProvider->all());
|
||||
$entityTypesToSearch = $entityTypes;
|
||||
|
||||
if ($entityType !== 'all') {
|
||||
@@ -177,17 +169,17 @@ class SearchService
|
||||
* @param array $terms
|
||||
* @param string $entityType
|
||||
* @param string $action
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
* @return EloquentBuilder
|
||||
*/
|
||||
protected function buildEntitySearchQuery($terms, $entityType = 'page', $action = 'view')
|
||||
{
|
||||
$entity = $this->getEntity($entityType);
|
||||
$entity = $this->entityProvider->get($entityType);
|
||||
$entitySelect = $entity->newQuery();
|
||||
|
||||
// Handle normal search terms
|
||||
if (count($terms['search']) > 0) {
|
||||
$subQuery = $this->db->table('search_terms')->select('entity_id', 'entity_type', \DB::raw('SUM(score) as score'));
|
||||
$subQuery->where('entity_type', '=', 'BookStack\\' . ucfirst($entityType));
|
||||
$subQuery->where('entity_type', '=', $entity->getMorphClass());
|
||||
$subQuery->where(function (Builder $query) use ($terms) {
|
||||
foreach ($terms['search'] as $inputTerm) {
|
||||
$query->orWhere('term', 'like', $inputTerm .'%');
|
||||
@@ -201,9 +193,9 @@ class SearchService
|
||||
|
||||
// Handle exact term matching
|
||||
if (count($terms['exact']) > 0) {
|
||||
$entitySelect->where(function (\Illuminate\Database\Eloquent\Builder $query) use ($terms, $entity) {
|
||||
$entitySelect->where(function (EloquentBuilder $query) use ($terms, $entity) {
|
||||
foreach ($terms['exact'] as $inputTerm) {
|
||||
$query->where(function (\Illuminate\Database\Eloquent\Builder $query) use ($inputTerm, $entity) {
|
||||
$query->where(function (EloquentBuilder $query) use ($inputTerm, $entity) {
|
||||
$query->where('name', 'like', '%'.$inputTerm .'%')
|
||||
->orWhere($entity->textField, 'like', '%'.$inputTerm .'%');
|
||||
});
|
||||
@@ -291,14 +283,14 @@ class SearchService
|
||||
|
||||
/**
|
||||
* Apply a tag search term onto a entity query.
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param EloquentBuilder $query
|
||||
* @param string $tagTerm
|
||||
* @return mixed
|
||||
*/
|
||||
protected function applyTagSearch(\Illuminate\Database\Eloquent\Builder $query, $tagTerm)
|
||||
protected function applyTagSearch(EloquentBuilder $query, $tagTerm)
|
||||
{
|
||||
preg_match("/^(.*?)((".$this->getRegexEscapedOperators().")(.*?))?$/", $tagTerm, $tagSplit);
|
||||
$query->whereHas('tags', function (\Illuminate\Database\Eloquent\Builder $query) use ($tagSplit) {
|
||||
$query->whereHas('tags', function (EloquentBuilder $query) use ($tagSplit) {
|
||||
$tagName = $tagSplit[1];
|
||||
$tagOperator = count($tagSplit) > 2 ? $tagSplit[3] : '';
|
||||
$tagValue = count($tagSplit) > 3 ? $tagSplit[4] : '';
|
||||
@@ -323,16 +315,6 @@ class SearchService
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an entity instance via type.
|
||||
* @param $type
|
||||
* @return Entity
|
||||
*/
|
||||
protected function getEntity($type)
|
||||
{
|
||||
return $this->entities[strtolower($type)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Index the given entity.
|
||||
* @param Entity $entity
|
||||
@@ -352,7 +334,7 @@ class SearchService
|
||||
|
||||
/**
|
||||
* Index multiple Entities at once
|
||||
* @param Entity[] $entities
|
||||
* @param \BookStack\Entities\Entity[] $entities
|
||||
*/
|
||||
protected function indexEntities($entities)
|
||||
{
|
||||
@@ -380,7 +362,7 @@ class SearchService
|
||||
{
|
||||
$this->searchTerm->truncate();
|
||||
|
||||
foreach ($this->entities as $entityModel) {
|
||||
foreach ($this->entityProvider->all() as $entityModel) {
|
||||
$selectFields = ['id', 'name', $entityModel->textField];
|
||||
$entityModel->newQuery()->select($selectFields)->chunk(1000, function ($entities) {
|
||||
$this->indexEntities($entities);
|
||||
@@ -434,7 +416,7 @@ class SearchService
|
||||
* Custom entity search filters
|
||||
*/
|
||||
|
||||
protected function filterUpdatedAfter(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
|
||||
protected function filterUpdatedAfter(EloquentBuilder $query, Entity $model, $input)
|
||||
{
|
||||
try {
|
||||
$date = date_create($input);
|
||||
@@ -444,7 +426,7 @@ class SearchService
|
||||
$query->where('updated_at', '>=', $date);
|
||||
}
|
||||
|
||||
protected function filterUpdatedBefore(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
|
||||
protected function filterUpdatedBefore(EloquentBuilder $query, Entity $model, $input)
|
||||
{
|
||||
try {
|
||||
$date = date_create($input);
|
||||
@@ -454,7 +436,7 @@ class SearchService
|
||||
$query->where('updated_at', '<', $date);
|
||||
}
|
||||
|
||||
protected function filterCreatedAfter(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
|
||||
protected function filterCreatedAfter(EloquentBuilder $query, Entity $model, $input)
|
||||
{
|
||||
try {
|
||||
$date = date_create($input);
|
||||
@@ -464,7 +446,7 @@ class SearchService
|
||||
$query->where('created_at', '>=', $date);
|
||||
}
|
||||
|
||||
protected function filterCreatedBefore(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
|
||||
protected function filterCreatedBefore(EloquentBuilder $query, Entity $model, $input)
|
||||
{
|
||||
try {
|
||||
$date = date_create($input);
|
||||
@@ -474,7 +456,7 @@ class SearchService
|
||||
$query->where('created_at', '<', $date);
|
||||
}
|
||||
|
||||
protected function filterCreatedBy(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
|
||||
protected function filterCreatedBy(EloquentBuilder $query, Entity $model, $input)
|
||||
{
|
||||
if (!is_numeric($input) && $input !== 'me') {
|
||||
return;
|
||||
@@ -485,7 +467,7 @@ class SearchService
|
||||
$query->where('created_by', '=', $input);
|
||||
}
|
||||
|
||||
protected function filterUpdatedBy(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
|
||||
protected function filterUpdatedBy(EloquentBuilder $query, Entity $model, $input)
|
||||
{
|
||||
if (!is_numeric($input) && $input !== 'me') {
|
||||
return;
|
||||
@@ -496,41 +478,41 @@ class SearchService
|
||||
$query->where('updated_by', '=', $input);
|
||||
}
|
||||
|
||||
protected function filterInName(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
|
||||
protected function filterInName(EloquentBuilder $query, Entity $model, $input)
|
||||
{
|
||||
$query->where('name', 'like', '%' .$input. '%');
|
||||
}
|
||||
|
||||
protected function filterInTitle(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
|
||||
protected function filterInTitle(EloquentBuilder $query, Entity $model, $input)
|
||||
{
|
||||
$this->filterInName($query, $model, $input);
|
||||
}
|
||||
|
||||
protected function filterInBody(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
|
||||
protected function filterInBody(EloquentBuilder $query, Entity $model, $input)
|
||||
{
|
||||
$query->where($model->textField, 'like', '%' .$input. '%');
|
||||
}
|
||||
|
||||
protected function filterIsRestricted(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
|
||||
protected function filterIsRestricted(EloquentBuilder $query, Entity $model, $input)
|
||||
{
|
||||
$query->where('restricted', '=', true);
|
||||
}
|
||||
|
||||
protected function filterViewedByMe(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
|
||||
protected function filterViewedByMe(EloquentBuilder $query, Entity $model, $input)
|
||||
{
|
||||
$query->whereHas('views', function ($query) {
|
||||
$query->where('user_id', '=', user()->id);
|
||||
});
|
||||
}
|
||||
|
||||
protected function filterNotViewedByMe(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
|
||||
protected function filterNotViewedByMe(EloquentBuilder $query, Entity $model, $input)
|
||||
{
|
||||
$query->whereDoesntHave('views', function ($query) {
|
||||
$query->where('user_id', '=', user()->id);
|
||||
});
|
||||
}
|
||||
|
||||
protected function filterSortBy(\Illuminate\Database\Eloquent\Builder $query, Entity $model, $input)
|
||||
protected function filterSortBy(EloquentBuilder $query, Entity $model, $input)
|
||||
{
|
||||
$functionName = camel_case('sort_by_' . $input);
|
||||
if (method_exists($this, $functionName)) {
|
||||
@@ -543,7 +525,7 @@ class SearchService
|
||||
* Sorting filter options
|
||||
*/
|
||||
|
||||
protected function sortByLastCommented(\Illuminate\Database\Eloquent\Builder $query, Entity $model)
|
||||
protected function sortByLastCommented(EloquentBuilder $query, Entity $model)
|
||||
{
|
||||
$commentsTable = $this->db->getTablePrefix() . 'comments';
|
||||
$morphClass = str_replace('\\', '\\\\', $model->getMorphClass());
|
||||
@@ -1,4 +1,6 @@
|
||||
<?php namespace BookStack;
|
||||
<?php namespace BookStack\Entities;
|
||||
|
||||
use BookStack\Model;
|
||||
|
||||
class SearchTerm extends Model
|
||||
{
|
||||
@@ -3,12 +3,12 @@
|
||||
namespace BookStack\Exceptions;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Auth\AuthenticationException;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Auth\AuthenticationException;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
class Handler extends ExceptionHandler
|
||||
|
||||
7
app/Exceptions/HttpFetchException.php
Normal file
7
app/Exceptions/HttpFetchException.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php namespace BookStack\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class HttpFetchException extends Exception
|
||||
{
|
||||
}
|
||||
@@ -11,7 +11,7 @@ class NotifyException extends \Exception
|
||||
* @param string $message
|
||||
* @param string $redirectLocation
|
||||
*/
|
||||
public function __construct($message, $redirectLocation)
|
||||
public function __construct(string $message, string $redirectLocation = "/")
|
||||
{
|
||||
$this->message = $message;
|
||||
$this->redirectLocation = $redirectLocation;
|
||||
|
||||
5
app/Exceptions/UserUpdateException.php
Normal file
5
app/Exceptions/UserUpdateException.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php namespace BookStack\Exceptions;
|
||||
|
||||
class UserUpdateException extends NotifyException
|
||||
{
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<?php namespace BookStack\Services\Facades;
|
||||
<?php namespace BookStack\Facades;
|
||||
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?php namespace BookStack\Services\Facades;
|
||||
<?php namespace BookStack\Facades;
|
||||
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?php namespace BookStack\Services\Facades;
|
||||
<?php namespace BookStack\Facades;
|
||||
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?php namespace BookStack\Services\Facades;
|
||||
<?php namespace BookStack\Facades;
|
||||
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<?php namespace BookStack\Http\Controllers;
|
||||
|
||||
use BookStack\Entities\Repos\EntityRepo;
|
||||
use BookStack\Exceptions\FileUploadException;
|
||||
use BookStack\Attachment;
|
||||
use BookStack\Exceptions\NotFoundException;
|
||||
use BookStack\Repos\EntityRepo;
|
||||
use BookStack\Services\AttachmentService;
|
||||
use BookStack\Uploads\Attachment;
|
||||
use BookStack\Uploads\AttachmentService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class AttachmentController extends Controller
|
||||
@@ -15,7 +15,7 @@ class AttachmentController extends Controller
|
||||
|
||||
/**
|
||||
* AttachmentController constructor.
|
||||
* @param AttachmentService $attachmentService
|
||||
* @param \BookStack\Uploads\AttachmentService $attachmentService
|
||||
* @param Attachment $attachment
|
||||
* @param EntityRepo $entityRepo
|
||||
*/
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
namespace BookStack\Http\Controllers\Auth;
|
||||
|
||||
use BookStack\Auth\Access\LdapService;
|
||||
use BookStack\Auth\Access\SocialAuthService;
|
||||
use BookStack\Auth\UserRepo;
|
||||
use BookStack\Exceptions\AuthException;
|
||||
use BookStack\Http\Controllers\Controller;
|
||||
use BookStack\Repos\UserRepo;
|
||||
use BookStack\Services\LdapService;
|
||||
use BookStack\Services\SocialAuthService;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -43,9 +43,9 @@ class LoginController extends Controller
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*
|
||||
* @param SocialAuthService $socialAuthService
|
||||
* @param \BookStack\Auth\\BookStack\Auth\Access\SocialAuthService $socialAuthService
|
||||
* @param LdapService $ldapService
|
||||
* @param UserRepo $userRepo
|
||||
* @param \BookStack\Auth\UserRepo $userRepo
|
||||
*/
|
||||
public function __construct(SocialAuthService $socialAuthService, LdapService $ldapService, UserRepo $userRepo)
|
||||
{
|
||||
@@ -128,7 +128,7 @@ class LoginController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
return view('auth/login', ['socialDrivers' => $socialDrivers, 'authMethod' => $authMethod]);
|
||||
return view('auth.login', ['socialDrivers' => $socialDrivers, 'authMethod' => $authMethod]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,21 +2,19 @@
|
||||
|
||||
namespace BookStack\Http\Controllers\Auth;
|
||||
|
||||
use BookStack\Auth\SocialAccount;
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Auth\UserRepo;
|
||||
use BookStack\Exceptions\SocialSignInAccountNotUsed;
|
||||
use BookStack\Exceptions\SocialSignInException;
|
||||
use BookStack\Exceptions\UserRegistrationException;
|
||||
use BookStack\Repos\UserRepo;
|
||||
use BookStack\Services\EmailConfirmationService;
|
||||
use BookStack\Services\SocialAuthService;
|
||||
use BookStack\SocialAccount;
|
||||
use BookStack\User;
|
||||
use BookStack\Http\Controllers\Controller;
|
||||
use Exception;
|
||||
use Illuminate\Foundation\Auth\RegistersUsers;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Validator;
|
||||
use BookStack\Http\Controllers\Controller;
|
||||
use Illuminate\Foundation\Auth\RegistersUsers;
|
||||
use Laravel\Socialite\Contracts\User as SocialUser;
|
||||
use Validator;
|
||||
|
||||
class RegisterController extends Controller
|
||||
{
|
||||
@@ -48,11 +46,11 @@ class RegisterController extends Controller
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*
|
||||
* @param SocialAuthService $socialAuthService
|
||||
* @param EmailConfirmationService $emailConfirmationService
|
||||
* @param UserRepo $userRepo
|
||||
* @param \BookStack\Auth\Access\SocialAuthService $socialAuthService
|
||||
* @param \BookStack\Auth\EmailConfirmationService $emailConfirmationService
|
||||
* @param \BookStack\Auth\UserRepo $userRepo
|
||||
*/
|
||||
public function __construct(SocialAuthService $socialAuthService, EmailConfirmationService $emailConfirmationService, UserRepo $userRepo)
|
||||
public function __construct(\BookStack\Auth\Access\SocialAuthService $socialAuthService, \BookStack\Auth\Access\EmailConfirmationService $emailConfirmationService, UserRepo $userRepo)
|
||||
{
|
||||
$this->middleware('guest')->only(['getRegister', 'postRegister', 'socialRegister']);
|
||||
$this->socialAuthService = $socialAuthService;
|
||||
@@ -72,7 +70,7 @@ class RegisterController extends Controller
|
||||
protected function validator(array $data)
|
||||
{
|
||||
return Validator::make($data, [
|
||||
'name' => 'required|max:255',
|
||||
'name' => 'required|min:2|max:255',
|
||||
'email' => 'required|email|max:255|unique:users',
|
||||
'password' => 'required|min:6',
|
||||
]);
|
||||
@@ -119,7 +117,7 @@ class RegisterController extends Controller
|
||||
/**
|
||||
* Create a new user instance after a valid registration.
|
||||
* @param array $data
|
||||
* @return User
|
||||
* @return \BookStack\Auth\User
|
||||
*/
|
||||
protected function create(array $data)
|
||||
{
|
||||
@@ -178,7 +176,7 @@ class RegisterController extends Controller
|
||||
*/
|
||||
public function getRegisterConfirmation()
|
||||
{
|
||||
return view('auth/register-confirm');
|
||||
return view('auth.register-confirm');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -206,7 +204,7 @@ class RegisterController extends Controller
|
||||
*/
|
||||
public function showAwaitingConfirmation()
|
||||
{
|
||||
return view('auth/user-unconfirmed');
|
||||
return view('auth.user-unconfirmed');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<?php namespace BookStack\Http\Controllers;
|
||||
|
||||
use Activity;
|
||||
use BookStack\Book;
|
||||
use BookStack\Repos\EntityRepo;
|
||||
use BookStack\Repos\UserRepo;
|
||||
use BookStack\Services\ExportService;
|
||||
use BookStack\Auth\UserRepo;
|
||||
use BookStack\Entities\Book;
|
||||
use BookStack\Entities\EntityContextManager;
|
||||
use BookStack\Entities\Repos\EntityRepo;
|
||||
use BookStack\Entities\ExportService;
|
||||
use BookStack\Uploads\ImageRepo;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Views;
|
||||
@@ -15,18 +17,29 @@ class BookController extends Controller
|
||||
protected $entityRepo;
|
||||
protected $userRepo;
|
||||
protected $exportService;
|
||||
protected $entityContextManager;
|
||||
protected $imageRepo;
|
||||
|
||||
/**
|
||||
* BookController constructor.
|
||||
* @param EntityRepo $entityRepo
|
||||
* @param UserRepo $userRepo
|
||||
* @param ExportService $exportService
|
||||
* @param EntityContextManager $entityContextManager
|
||||
* @param ImageRepo $imageRepo
|
||||
*/
|
||||
public function __construct(EntityRepo $entityRepo, UserRepo $userRepo, ExportService $exportService)
|
||||
{
|
||||
public function __construct(
|
||||
EntityRepo $entityRepo,
|
||||
UserRepo $userRepo,
|
||||
ExportService $exportService,
|
||||
EntityContextManager $entityContextManager,
|
||||
ImageRepo $imageRepo
|
||||
) {
|
||||
$this->entityRepo = $entityRepo;
|
||||
$this->userRepo = $userRepo;
|
||||
$this->exportService = $exportService;
|
||||
$this->entityContextManager = $entityContextManager;
|
||||
$this->imageRepo = $imageRepo;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
@@ -36,67 +49,117 @@ class BookController extends Controller
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$books = $this->entityRepo->getAllPaginated('book', 18);
|
||||
$view = setting()->getUser($this->currentUser, 'books_view_type', config('app.views.books'));
|
||||
$sort = setting()->getUser($this->currentUser, 'books_sort', 'name');
|
||||
$order = setting()->getUser($this->currentUser, 'books_sort_order', 'asc');
|
||||
$sortOptions = [
|
||||
'name' => trans('common.sort_name'),
|
||||
'created_at' => trans('common.sort_created_at'),
|
||||
'updated_at' => trans('common.sort_updated_at'),
|
||||
];
|
||||
|
||||
$books = $this->entityRepo->getAllPaginated('book', 18, $sort, $order);
|
||||
$recents = $this->signedIn ? $this->entityRepo->getRecentlyViewed('book', 4, 0) : false;
|
||||
$popular = $this->entityRepo->getPopular('book', 4, 0);
|
||||
$new = $this->entityRepo->getRecentlyCreated('book', 4, 0);
|
||||
$booksViewType = setting()->getUser($this->currentUser, 'books_view_type', config('app.views.books', 'list'));
|
||||
|
||||
$this->entityContextManager->clearShelfContext();
|
||||
|
||||
$this->setPageTitle(trans('entities.books'));
|
||||
return view('books/index', [
|
||||
return view('books.index', [
|
||||
'books' => $books,
|
||||
'recents' => $recents,
|
||||
'popular' => $popular,
|
||||
'new' => $new,
|
||||
'booksViewType' => $booksViewType
|
||||
'view' => $view,
|
||||
'sort' => $sort,
|
||||
'order' => $order,
|
||||
'sortOptions' => $sortOptions,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new book.
|
||||
* @param string $shelfSlug
|
||||
* @return Response
|
||||
* @throws \BookStack\Exceptions\NotFoundException
|
||||
*/
|
||||
public function create()
|
||||
public function create(string $shelfSlug = null)
|
||||
{
|
||||
$bookshelf = null;
|
||||
if ($shelfSlug !== null) {
|
||||
$bookshelf = $this->entityRepo->getBySlug('bookshelf', $shelfSlug);
|
||||
$this->checkOwnablePermission('bookshelf-update', $bookshelf);
|
||||
}
|
||||
|
||||
$this->checkPermission('book-create-all');
|
||||
$this->setPageTitle(trans('entities.books_create'));
|
||||
return view('books/create');
|
||||
return view('books.create', [
|
||||
'bookshelf' => $bookshelf
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created book in storage.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Request $request
|
||||
* @param string $shelfSlug
|
||||
* @return Response
|
||||
* @throws \BookStack\Exceptions\NotFoundException
|
||||
* @throws \BookStack\Exceptions\ImageUploadException
|
||||
*/
|
||||
public function store(Request $request)
|
||||
public function store(Request $request, string $shelfSlug = null)
|
||||
{
|
||||
$this->checkPermission('book-create-all');
|
||||
$this->validate($request, [
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'string|max:1000'
|
||||
'description' => 'string|max:1000',
|
||||
'image' => $this->imageRepo->getImageValidationRules(),
|
||||
]);
|
||||
|
||||
$bookshelf = null;
|
||||
if ($shelfSlug !== null) {
|
||||
$bookshelf = $this->entityRepo->getBySlug('bookshelf', $shelfSlug);
|
||||
$this->checkOwnablePermission('bookshelf-update', $bookshelf);
|
||||
}
|
||||
|
||||
$book = $this->entityRepo->createFromInput('book', $request->all());
|
||||
$this->bookUpdateActions($book, $request);
|
||||
Activity::add($book, 'book_create', $book->id);
|
||||
|
||||
if ($bookshelf) {
|
||||
$this->entityRepo->appendBookToShelf($bookshelf, $book);
|
||||
Activity::add($bookshelf, 'bookshelf_update');
|
||||
}
|
||||
|
||||
return redirect($book->getUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified book.
|
||||
* @param $slug
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
* @throws \BookStack\Exceptions\NotFoundException
|
||||
*/
|
||||
public function show($slug)
|
||||
public function show($slug, Request $request)
|
||||
{
|
||||
$book = $this->entityRepo->getBySlug('book', $slug);
|
||||
$this->checkOwnablePermission('book-view', $book);
|
||||
|
||||
$bookChildren = $this->entityRepo->getBookChildren($book);
|
||||
|
||||
Views::add($book);
|
||||
if ($request->has('shelf')) {
|
||||
$this->entityContextManager->setShelfContext(intval($request->get('shelf')));
|
||||
}
|
||||
|
||||
$this->setPageTitle($book->getShortName());
|
||||
return view('books/show', [
|
||||
return view('books.show', [
|
||||
'book' => $book,
|
||||
'current' => $book,
|
||||
'bookChildren' => $bookChildren,
|
||||
'activity' => Activity::entityActivity($book, 20, 0)
|
||||
'activity' => Activity::entityActivity($book, 20, 1)
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -110,25 +173,32 @@ class BookController extends Controller
|
||||
$book = $this->entityRepo->getBySlug('book', $slug);
|
||||
$this->checkOwnablePermission('book-update', $book);
|
||||
$this->setPageTitle(trans('entities.books_edit_named', ['bookName'=>$book->getShortName()]));
|
||||
return view('books/edit', ['book' => $book, 'current' => $book]);
|
||||
return view('books.edit', ['book' => $book, 'current' => $book]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified book in storage.
|
||||
* @param Request $request
|
||||
* @param Request $request
|
||||
* @param $slug
|
||||
* @return Response
|
||||
* @throws \BookStack\Exceptions\ImageUploadException
|
||||
* @throws \BookStack\Exceptions\NotFoundException
|
||||
*/
|
||||
public function update(Request $request, $slug)
|
||||
public function update(Request $request, string $slug)
|
||||
{
|
||||
$book = $this->entityRepo->getBySlug('book', $slug);
|
||||
$this->checkOwnablePermission('book-update', $book);
|
||||
$this->validate($request, [
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'string|max:1000'
|
||||
'description' => 'string|max:1000',
|
||||
'image' => $this->imageRepo->getImageValidationRules(),
|
||||
]);
|
||||
|
||||
$book = $this->entityRepo->updateFromInput('book', $book, $request->all());
|
||||
$this->bookUpdateActions($book, $request);
|
||||
|
||||
Activity::add($book, 'book_update', $book->id);
|
||||
|
||||
return redirect($book->getUrl());
|
||||
}
|
||||
|
||||
@@ -142,22 +212,24 @@ class BookController extends Controller
|
||||
$book = $this->entityRepo->getBySlug('book', $bookSlug);
|
||||
$this->checkOwnablePermission('book-delete', $book);
|
||||
$this->setPageTitle(trans('entities.books_delete_named', ['bookName'=>$book->getShortName()]));
|
||||
return view('books/delete', ['book' => $book, 'current' => $book]);
|
||||
return view('books.delete', ['book' => $book, 'current' => $book]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the view which allows pages to be re-ordered and sorted.
|
||||
* @param string $bookSlug
|
||||
* @return \Illuminate\View\View
|
||||
* @throws \BookStack\Exceptions\NotFoundException
|
||||
*/
|
||||
public function sort($bookSlug)
|
||||
{
|
||||
$book = $this->entityRepo->getBySlug('book', $bookSlug);
|
||||
$this->checkOwnablePermission('book-update', $book);
|
||||
|
||||
$bookChildren = $this->entityRepo->getBookChildren($book, true);
|
||||
$books = $this->entityRepo->getAll('book', false, 'update');
|
||||
|
||||
$this->setPageTitle(trans('entities.books_sort_named', ['bookName'=>$book->getShortName()]));
|
||||
return view('books/sort', ['book' => $book, 'current' => $book, 'books' => $books, 'bookChildren' => $bookChildren]);
|
||||
return view('books.sort', ['book' => $book, 'current' => $book, 'bookChildren' => $bookChildren]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -170,7 +242,7 @@ class BookController extends Controller
|
||||
{
|
||||
$book = $this->entityRepo->getBySlug('book', $bookSlug);
|
||||
$bookChildren = $this->entityRepo->getBookChildren($book);
|
||||
return view('books/sort-box', ['book' => $book, 'bookChildren' => $bookChildren]);
|
||||
return view('books.sort-box', ['book' => $book, 'bookChildren' => $bookChildren]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -204,7 +276,7 @@ class BookController extends Controller
|
||||
|
||||
// Get the books involved in the sort
|
||||
$bookIdsInvolved = $bookIdsInvolved->unique()->toArray();
|
||||
$booksInvolved = $this->entityRepo->book->newQuery()->whereIn('id', $bookIdsInvolved)->get();
|
||||
$booksInvolved = $this->entityRepo->getManyById('book', $bookIdsInvolved, false, true);
|
||||
// Throw permission error if invalid ids or inaccessible books given.
|
||||
if (count($bookIdsInvolved) !== count($booksInvolved)) {
|
||||
$this->showPermissionError();
|
||||
@@ -254,7 +326,12 @@ class BookController extends Controller
|
||||
$book = $this->entityRepo->getBySlug('book', $bookSlug);
|
||||
$this->checkOwnablePermission('book-delete', $book);
|
||||
Activity::addMessage('book_delete', 0, $book->name);
|
||||
|
||||
if ($book->cover) {
|
||||
$this->imageRepo->destroyImage($book->cover);
|
||||
}
|
||||
$this->entityRepo->destroyBook($book);
|
||||
|
||||
return redirect('/books');
|
||||
}
|
||||
|
||||
@@ -263,12 +340,12 @@ class BookController extends Controller
|
||||
* @param $bookSlug
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
*/
|
||||
public function showRestrict($bookSlug)
|
||||
public function showPermissions($bookSlug)
|
||||
{
|
||||
$book = $this->entityRepo->getBySlug('book', $bookSlug);
|
||||
$this->checkOwnablePermission('restrictions-manage', $book);
|
||||
$roles = $this->userRepo->getRestrictableRoles();
|
||||
return view('books/restrictions', [
|
||||
return view('books.permissions', [
|
||||
'book' => $book,
|
||||
'roles' => $roles
|
||||
]);
|
||||
@@ -277,11 +354,12 @@ class BookController extends Controller
|
||||
/**
|
||||
* Set the restrictions for this book.
|
||||
* @param $bookSlug
|
||||
* @param $bookSlug
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
* @throws \BookStack\Exceptions\NotFoundException
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function restrict($bookSlug, Request $request)
|
||||
public function permissions($bookSlug, Request $request)
|
||||
{
|
||||
$book = $this->entityRepo->getBySlug('book', $bookSlug);
|
||||
$this->checkOwnablePermission('restrictions-manage', $book);
|
||||
@@ -325,4 +403,29 @@ class BookController extends Controller
|
||||
$textContent = $this->exportService->bookToPlainText($book);
|
||||
return $this->downloadResponse($textContent, $bookSlug . '.txt');
|
||||
}
|
||||
|
||||
/**
|
||||
* Common actions to run on book update.
|
||||
* Handles updating the cover image.
|
||||
* @param Book $book
|
||||
* @param Request $request
|
||||
* @throws \BookStack\Exceptions\ImageUploadException
|
||||
*/
|
||||
protected function bookUpdateActions(Book $book, Request $request)
|
||||
{
|
||||
// Update the cover image if in request
|
||||
if ($request->has('image')) {
|
||||
$this->imageRepo->destroyImage($book->cover);
|
||||
$newImage = $request->file('image');
|
||||
$image = $this->imageRepo->saveNew($newImage, 'cover_book', $book->id, 512, 512, true);
|
||||
$book->image_id = $image->id;
|
||||
$book->save();
|
||||
}
|
||||
|
||||
if ($request->has('image_reset')) {
|
||||
$this->imageRepo->destroyImage($book->cover);
|
||||
$book->image_id = 0;
|
||||
$book->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php namespace BookStack\Http\Controllers;
|
||||
|
||||
use Activity;
|
||||
use BookStack\Book;
|
||||
use BookStack\Bookshelf;
|
||||
use BookStack\Repos\EntityRepo;
|
||||
use BookStack\Repos\UserRepo;
|
||||
use BookStack\Services\ExportService;
|
||||
use BookStack\Auth\UserRepo;
|
||||
use BookStack\Entities\Bookshelf;
|
||||
use BookStack\Entities\EntityContextManager;
|
||||
use BookStack\Entities\Repos\EntityRepo;
|
||||
use BookStack\Uploads\ImageRepo;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Views;
|
||||
@@ -15,19 +15,22 @@ class BookshelfController extends Controller
|
||||
|
||||
protected $entityRepo;
|
||||
protected $userRepo;
|
||||
protected $exportService;
|
||||
protected $entityContextManager;
|
||||
protected $imageRepo;
|
||||
|
||||
/**
|
||||
* BookController constructor.
|
||||
* @param EntityRepo $entityRepo
|
||||
* @param UserRepo $userRepo
|
||||
* @param ExportService $exportService
|
||||
* @param EntityContextManager $entityContextManager
|
||||
* @param ImageRepo $imageRepo
|
||||
*/
|
||||
public function __construct(EntityRepo $entityRepo, UserRepo $userRepo, ExportService $exportService)
|
||||
public function __construct(EntityRepo $entityRepo, UserRepo $userRepo, EntityContextManager $entityContextManager, ImageRepo $imageRepo)
|
||||
{
|
||||
$this->entityRepo = $entityRepo;
|
||||
$this->userRepo = $userRepo;
|
||||
$this->exportService = $exportService;
|
||||
$this->entityContextManager = $entityContextManager;
|
||||
$this->imageRepo = $imageRepo;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
@@ -37,19 +40,35 @@ class BookshelfController extends Controller
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$shelves = $this->entityRepo->getAllPaginated('bookshelf', 18);
|
||||
$view = setting()->getUser($this->currentUser, 'bookshelves_view_type', config('app.views.bookshelves', 'grid'));
|
||||
$sort = setting()->getUser($this->currentUser, 'bookshelves_sort', 'name');
|
||||
$order = setting()->getUser($this->currentUser, 'bookshelves_sort_order', 'asc');
|
||||
$sortOptions = [
|
||||
'name' => trans('common.sort_name'),
|
||||
'created_at' => trans('common.sort_created_at'),
|
||||
'updated_at' => trans('common.sort_updated_at'),
|
||||
];
|
||||
|
||||
$shelves = $this->entityRepo->getAllPaginated('bookshelf', 18, $sort, $order);
|
||||
foreach ($shelves as $shelf) {
|
||||
$shelf->books = $this->entityRepo->getBookshelfChildren($shelf);
|
||||
}
|
||||
|
||||
$recents = $this->signedIn ? $this->entityRepo->getRecentlyViewed('bookshelf', 4, 0) : false;
|
||||
$popular = $this->entityRepo->getPopular('bookshelf', 4, 0);
|
||||
$new = $this->entityRepo->getRecentlyCreated('bookshelf', 4, 0);
|
||||
$shelvesViewType = setting()->getUser($this->currentUser, 'bookshelves_view_type', config('app.views.bookshelves', 'grid'));
|
||||
|
||||
$this->entityContextManager->clearShelfContext();
|
||||
$this->setPageTitle(trans('entities.shelves'));
|
||||
return view('shelves/index', [
|
||||
return view('shelves.index', [
|
||||
'shelves' => $shelves,
|
||||
'recents' => $recents,
|
||||
'popular' => $popular,
|
||||
'new' => $new,
|
||||
'shelvesViewType' => $shelvesViewType
|
||||
'view' => $view,
|
||||
'sort' => $sort,
|
||||
'order' => $order,
|
||||
'sortOptions' => $sortOptions,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -62,13 +81,14 @@ class BookshelfController extends Controller
|
||||
$this->checkPermission('bookshelf-create-all');
|
||||
$books = $this->entityRepo->getAll('book', false, 'update');
|
||||
$this->setPageTitle(trans('entities.shelves_create'));
|
||||
return view('shelves/create', ['books' => $books]);
|
||||
return view('shelves.create', ['books' => $books]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created bookshelf in storage.
|
||||
* @param Request $request
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
* @throws \BookStack\Exceptions\ImageUploadException
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
@@ -76,13 +96,14 @@ class BookshelfController extends Controller
|
||||
$this->validate($request, [
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'string|max:1000',
|
||||
'image' => $this->imageRepo->getImageValidationRules(),
|
||||
]);
|
||||
|
||||
$bookshelf = $this->entityRepo->createFromInput('bookshelf', $request->all());
|
||||
$this->entityRepo->updateShelfBooks($bookshelf, $request->get('books', ''));
|
||||
Activity::add($bookshelf, 'bookshelf_create');
|
||||
$shelf = $this->entityRepo->createFromInput('bookshelf', $request->all());
|
||||
$this->shelfUpdateActions($shelf, $request);
|
||||
|
||||
return redirect($bookshelf->getUrl());
|
||||
Activity::add($shelf, 'bookshelf_create');
|
||||
return redirect($shelf->getUrl());
|
||||
}
|
||||
|
||||
|
||||
@@ -94,17 +115,20 @@ class BookshelfController extends Controller
|
||||
*/
|
||||
public function show(string $slug)
|
||||
{
|
||||
$bookshelf = $this->entityRepo->getBySlug('bookshelf', $slug); /** @var $bookshelf Bookshelf */
|
||||
$this->checkOwnablePermission('book-view', $bookshelf);
|
||||
/** @var Bookshelf $shelf */
|
||||
$shelf = $this->entityRepo->getBySlug('bookshelf', $slug);
|
||||
$this->checkOwnablePermission('book-view', $shelf);
|
||||
|
||||
$books = $this->entityRepo->getBookshelfChildren($bookshelf);
|
||||
Views::add($bookshelf);
|
||||
$books = $this->entityRepo->getBookshelfChildren($shelf);
|
||||
Views::add($shelf);
|
||||
$this->entityContextManager->setShelfContext($shelf->id);
|
||||
|
||||
$this->setPageTitle($bookshelf->getShortName());
|
||||
return view('shelves/show', [
|
||||
'shelf' => $bookshelf,
|
||||
$this->setPageTitle($shelf->getShortName());
|
||||
|
||||
return view('shelves.show', [
|
||||
'shelf' => $shelf,
|
||||
'books' => $books,
|
||||
'activity' => Activity::entityActivity($bookshelf, 20, 0)
|
||||
'activity' => Activity::entityActivity($shelf, 20, 1)
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -116,19 +140,19 @@ class BookshelfController extends Controller
|
||||
*/
|
||||
public function edit(string $slug)
|
||||
{
|
||||
$bookshelf = $this->entityRepo->getBySlug('bookshelf', $slug); /** @var $bookshelf Bookshelf */
|
||||
$this->checkOwnablePermission('bookshelf-update', $bookshelf);
|
||||
$shelf = $this->entityRepo->getBySlug('bookshelf', $slug); /** @var $shelf Bookshelf */
|
||||
$this->checkOwnablePermission('bookshelf-update', $shelf);
|
||||
|
||||
$shelfBooks = $this->entityRepo->getBookshelfChildren($bookshelf);
|
||||
$shelfBooks = $this->entityRepo->getBookshelfChildren($shelf);
|
||||
$shelfBookIds = $shelfBooks->pluck('id');
|
||||
$books = $this->entityRepo->getAll('book', false, 'update');
|
||||
$books = $books->filter(function ($book) use ($shelfBookIds) {
|
||||
return !$shelfBookIds->contains($book->id);
|
||||
});
|
||||
|
||||
$this->setPageTitle(trans('entities.shelves_edit_named', ['name' => $bookshelf->getShortName()]));
|
||||
return view('shelves/edit', [
|
||||
'shelf' => $bookshelf,
|
||||
$this->setPageTitle(trans('entities.shelves_edit_named', ['name' => $shelf->getShortName()]));
|
||||
return view('shelves.edit', [
|
||||
'shelf' => $shelf,
|
||||
'books' => $books,
|
||||
'shelfBooks' => $shelfBooks,
|
||||
]);
|
||||
@@ -137,10 +161,11 @@ class BookshelfController extends Controller
|
||||
|
||||
/**
|
||||
* Update the specified bookshelf in storage.
|
||||
* @param Request $request
|
||||
* @param Request $request
|
||||
* @param string $slug
|
||||
* @return Response
|
||||
* @throws \BookStack\Exceptions\NotFoundException
|
||||
* @throws \BookStack\Exceptions\ImageUploadException
|
||||
*/
|
||||
public function update(Request $request, string $slug)
|
||||
{
|
||||
@@ -149,10 +174,12 @@ class BookshelfController extends Controller
|
||||
$this->validate($request, [
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'string|max:1000',
|
||||
'image' => $this->imageRepo->getImageValidationRules(),
|
||||
]);
|
||||
|
||||
$shelf = $this->entityRepo->updateFromInput('bookshelf', $shelf, $request->all());
|
||||
$this->entityRepo->updateShelfBooks($shelf, $request->get('books', ''));
|
||||
$this->shelfUpdateActions($shelf, $request);
|
||||
|
||||
Activity::add($shelf, 'bookshelf_update');
|
||||
|
||||
return redirect($shelf->getUrl());
|
||||
@@ -167,11 +194,11 @@ class BookshelfController extends Controller
|
||||
*/
|
||||
public function showDelete(string $slug)
|
||||
{
|
||||
$bookshelf = $this->entityRepo->getBySlug('bookshelf', $slug); /** @var $bookshelf Bookshelf */
|
||||
$this->checkOwnablePermission('bookshelf-delete', $bookshelf);
|
||||
$shelf = $this->entityRepo->getBySlug('bookshelf', $slug); /** @var $shelf Bookshelf */
|
||||
$this->checkOwnablePermission('bookshelf-delete', $shelf);
|
||||
|
||||
$this->setPageTitle(trans('entities.shelves_delete_named', ['name' => $bookshelf->getShortName()]));
|
||||
return view('shelves/delete', ['shelf' => $bookshelf]);
|
||||
$this->setPageTitle(trans('entities.shelves_delete_named', ['name' => $shelf->getShortName()]));
|
||||
return view('shelves.delete', ['shelf' => $shelf]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,46 +210,52 @@ class BookshelfController extends Controller
|
||||
*/
|
||||
public function destroy(string $slug)
|
||||
{
|
||||
$bookshelf = $this->entityRepo->getBySlug('bookshelf', $slug); /** @var $bookshelf Bookshelf */
|
||||
$this->checkOwnablePermission('bookshelf-delete', $bookshelf);
|
||||
Activity::addMessage('bookshelf_delete', 0, $bookshelf->name);
|
||||
$this->entityRepo->destroyBookshelf($bookshelf);
|
||||
$shelf = $this->entityRepo->getBySlug('bookshelf', $slug); /** @var $shelf Bookshelf */
|
||||
$this->checkOwnablePermission('bookshelf-delete', $shelf);
|
||||
Activity::addMessage('bookshelf_delete', 0, $shelf->name);
|
||||
|
||||
if ($shelf->cover) {
|
||||
$this->imageRepo->destroyImage($shelf->cover);
|
||||
}
|
||||
$this->entityRepo->destroyBookshelf($shelf);
|
||||
|
||||
return redirect('/shelves');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the Restrictions view.
|
||||
* @param $slug
|
||||
* Show the permissions view.
|
||||
* @param string $slug
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
* @throws \BookStack\Exceptions\NotFoundException
|
||||
*/
|
||||
public function showRestrict(string $slug)
|
||||
public function showPermissions(string $slug)
|
||||
{
|
||||
$bookshelf = $this->entityRepo->getBySlug('bookshelf', $slug);
|
||||
$this->checkOwnablePermission('restrictions-manage', $bookshelf);
|
||||
$shelf = $this->entityRepo->getBySlug('bookshelf', $slug);
|
||||
$this->checkOwnablePermission('restrictions-manage', $shelf);
|
||||
|
||||
$roles = $this->userRepo->getRestrictableRoles();
|
||||
return view('shelves.restrictions', [
|
||||
'shelf' => $bookshelf,
|
||||
return view('shelves.permissions', [
|
||||
'shelf' => $shelf,
|
||||
'roles' => $roles
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the restrictions for this bookshelf.
|
||||
* @param $slug
|
||||
* Set the permissions for this bookshelf.
|
||||
* @param string $slug
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
* @throws \BookStack\Exceptions\NotFoundException
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function restrict(string $slug, Request $request)
|
||||
public function permissions(string $slug, Request $request)
|
||||
{
|
||||
$bookshelf = $this->entityRepo->getBySlug('bookshelf', $slug);
|
||||
$this->checkOwnablePermission('restrictions-manage', $bookshelf);
|
||||
$shelf = $this->entityRepo->getBySlug('bookshelf', $slug);
|
||||
$this->checkOwnablePermission('restrictions-manage', $shelf);
|
||||
|
||||
$this->entityRepo->updateEntityPermissionsFromRequest($request, $bookshelf);
|
||||
$this->entityRepo->updateEntityPermissionsFromRequest($request, $shelf);
|
||||
session()->flash('success', trans('entities.shelves_permissions_updated'));
|
||||
return redirect($bookshelf->getUrl());
|
||||
return redirect($shelf->getUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -233,11 +266,38 @@ class BookshelfController extends Controller
|
||||
*/
|
||||
public function copyPermissions(string $slug)
|
||||
{
|
||||
$bookshelf = $this->entityRepo->getBySlug('bookshelf', $slug);
|
||||
$this->checkOwnablePermission('restrictions-manage', $bookshelf);
|
||||
$shelf = $this->entityRepo->getBySlug('bookshelf', $slug);
|
||||
$this->checkOwnablePermission('restrictions-manage', $shelf);
|
||||
|
||||
$updateCount = $this->entityRepo->copyBookshelfPermissions($bookshelf);
|
||||
$updateCount = $this->entityRepo->copyBookshelfPermissions($shelf);
|
||||
session()->flash('success', trans('entities.shelves_copy_permission_success', ['count' => $updateCount]));
|
||||
return redirect($bookshelf->getUrl());
|
||||
return redirect($shelf->getUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* Common actions to run on bookshelf update.
|
||||
* @param Bookshelf $shelf
|
||||
* @param Request $request
|
||||
* @throws \BookStack\Exceptions\ImageUploadException
|
||||
*/
|
||||
protected function shelfUpdateActions(Bookshelf $shelf, Request $request)
|
||||
{
|
||||
// Update the books that the shelf references
|
||||
$this->entityRepo->updateShelfBooks($shelf, $request->get('books', ''));
|
||||
|
||||
// Update the cover image if in request
|
||||
if ($request->has('image')) {
|
||||
$newImage = $request->file('image');
|
||||
$this->imageRepo->destroyImage($shelf->cover);
|
||||
$image = $this->imageRepo->saveNew($newImage, 'cover_shelf', $shelf->id, 512, 512, true);
|
||||
$shelf->image_id = $image->id;
|
||||
$shelf->save();
|
||||
}
|
||||
|
||||
if ($request->has('image_reset')) {
|
||||
$this->imageRepo->destroyImage($shelf->cover);
|
||||
$shelf->image_id = 0;
|
||||
$shelf->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?php namespace BookStack\Http\Controllers;
|
||||
|
||||
use Activity;
|
||||
use BookStack\Repos\EntityRepo;
|
||||
use BookStack\Repos\UserRepo;
|
||||
use BookStack\Services\ExportService;
|
||||
use BookStack\Auth\UserRepo;
|
||||
use BookStack\Entities\Repos\EntityRepo;
|
||||
use BookStack\Entities\ExportService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Views;
|
||||
@@ -19,7 +19,7 @@ class ChapterController extends Controller
|
||||
* ChapterController constructor.
|
||||
* @param EntityRepo $entityRepo
|
||||
* @param UserRepo $userRepo
|
||||
* @param ExportService $exportService
|
||||
* @param \BookStack\Entities\ExportService $exportService
|
||||
*/
|
||||
public function __construct(EntityRepo $entityRepo, UserRepo $userRepo, ExportService $exportService)
|
||||
{
|
||||
@@ -39,7 +39,7 @@ class ChapterController extends Controller
|
||||
$book = $this->entityRepo->getBySlug('book', $bookSlug);
|
||||
$this->checkOwnablePermission('chapter-create', $book);
|
||||
$this->setPageTitle(trans('entities.chapters_create'));
|
||||
return view('chapters/create', ['book' => $book, 'current' => $book]);
|
||||
return view('chapters.create', ['book' => $book, 'current' => $book]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,7 +78,7 @@ class ChapterController extends Controller
|
||||
Views::add($chapter);
|
||||
$this->setPageTitle($chapter->getShortName());
|
||||
$pages = $this->entityRepo->getChapterChildren($chapter);
|
||||
return view('chapters/show', [
|
||||
return view('chapters.show', [
|
||||
'book' => $chapter->book,
|
||||
'chapter' => $chapter,
|
||||
'current' => $chapter,
|
||||
@@ -98,7 +98,7 @@ class ChapterController extends Controller
|
||||
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
|
||||
$this->checkOwnablePermission('chapter-update', $chapter);
|
||||
$this->setPageTitle(trans('entities.chapters_edit_named', ['chapterName' => $chapter->getShortName()]));
|
||||
return view('chapters/edit', ['book' => $chapter->book, 'chapter' => $chapter, 'current' => $chapter]);
|
||||
return view('chapters.edit', ['book' => $chapter->book, 'chapter' => $chapter, 'current' => $chapter]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -130,7 +130,7 @@ class ChapterController extends Controller
|
||||
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
|
||||
$this->checkOwnablePermission('chapter-delete', $chapter);
|
||||
$this->setPageTitle(trans('entities.chapters_delete_named', ['chapterName' => $chapter->getShortName()]));
|
||||
return view('chapters/delete', ['book' => $chapter->book, 'chapter' => $chapter, 'current' => $chapter]);
|
||||
return view('chapters.delete', ['book' => $chapter->book, 'chapter' => $chapter, 'current' => $chapter]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,7 +161,8 @@ class ChapterController extends Controller
|
||||
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
|
||||
$this->setPageTitle(trans('entities.chapters_move_named', ['chapterName' => $chapter->getShortName()]));
|
||||
$this->checkOwnablePermission('chapter-update', $chapter);
|
||||
return view('chapters/move', [
|
||||
$this->checkOwnablePermission('chapter-delete', $chapter);
|
||||
return view('chapters.move', [
|
||||
'chapter' => $chapter,
|
||||
'book' => $chapter->book
|
||||
]);
|
||||
@@ -179,6 +180,7 @@ class ChapterController extends Controller
|
||||
{
|
||||
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
|
||||
$this->checkOwnablePermission('chapter-update', $chapter);
|
||||
$this->checkOwnablePermission('chapter-delete', $chapter);
|
||||
|
||||
$entitySelection = $request->get('entity_selection', null);
|
||||
if ($entitySelection === null || $entitySelection === '') {
|
||||
@@ -212,13 +214,14 @@ class ChapterController extends Controller
|
||||
* @param $bookSlug
|
||||
* @param $chapterSlug
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
* @throws \BookStack\Exceptions\NotFoundException
|
||||
*/
|
||||
public function showRestrict($bookSlug, $chapterSlug)
|
||||
public function showPermissions($bookSlug, $chapterSlug)
|
||||
{
|
||||
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
|
||||
$this->checkOwnablePermission('restrictions-manage', $chapter);
|
||||
$roles = $this->userRepo->getRestrictableRoles();
|
||||
return view('chapters/restrictions', [
|
||||
return view('chapters.permissions', [
|
||||
'chapter' => $chapter,
|
||||
'roles' => $roles
|
||||
]);
|
||||
@@ -230,8 +233,10 @@ class ChapterController extends Controller
|
||||
* @param $chapterSlug
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
* @throws \BookStack\Exceptions\NotFoundException
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function restrict($bookSlug, $chapterSlug, Request $request)
|
||||
public function permissions($bookSlug, $chapterSlug, Request $request)
|
||||
{
|
||||
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
|
||||
$this->checkOwnablePermission('restrictions-manage', $chapter);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php namespace BookStack\Http\Controllers;
|
||||
|
||||
use Activity;
|
||||
use BookStack\Repos\CommentRepo;
|
||||
use BookStack\Repos\EntityRepo;
|
||||
use BookStack\Actions\CommentRepo;
|
||||
use BookStack\Entities\Repos\EntityRepo;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
@@ -13,8 +13,8 @@ class CommentController extends Controller
|
||||
|
||||
/**
|
||||
* CommentController constructor.
|
||||
* @param EntityRepo $entityRepo
|
||||
* @param CommentRepo $commentRepo
|
||||
* @param \BookStack\Entities\Repos\EntityRepo $entityRepo
|
||||
* @param \BookStack\Actions\CommentRepo $commentRepo
|
||||
*/
|
||||
public function __construct(EntityRepo $entityRepo, CommentRepo $commentRepo)
|
||||
{
|
||||
@@ -54,7 +54,7 @@ class CommentController extends Controller
|
||||
$this->checkPermission('comment-create-all');
|
||||
$comment = $this->commentRepo->create($page, $request->only(['html', 'text', 'parent_id']));
|
||||
Activity::add($page, 'commented_on', $page->book->id);
|
||||
return view('comments/comment', ['comment' => $comment]);
|
||||
return view('comments.comment', ['comment' => $comment]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,7 +75,7 @@ class CommentController extends Controller
|
||||
$this->checkOwnablePermission('comment-update', $comment);
|
||||
|
||||
$comment = $this->commentRepo->update($comment, $request->only(['html', 'text']));
|
||||
return view('comments/comment', ['comment' => $comment]);
|
||||
return view('comments.comment', ['comment' => $comment]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
namespace BookStack\Http\Controllers;
|
||||
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Ownable;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Http\Exceptions\HttpResponseException;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use BookStack\User;
|
||||
|
||||
abstract class Controller extends BaseController
|
||||
{
|
||||
@@ -123,6 +123,20 @@ abstract class Controller extends BaseController
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current user has a permission or bypass if the provided user
|
||||
* id matches the current user.
|
||||
* @param string $permissionName
|
||||
* @param int $userId
|
||||
* @return bool
|
||||
*/
|
||||
protected function checkPermissionOrCurrentUser(string $permissionName, int $userId)
|
||||
{
|
||||
return $this->checkPermissionOr($permissionName, function () use ($userId) {
|
||||
return $userId === $this->currentUser->id;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send back a json error message.
|
||||
* @param string $messageText
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<?php namespace BookStack\Http\Controllers;
|
||||
|
||||
use Activity;
|
||||
use BookStack\Repos\EntityRepo;
|
||||
use Illuminate\Http\Request;
|
||||
use BookStack\Entities\Repos\EntityRepo;
|
||||
use Illuminate\Http\Response;
|
||||
use Views;
|
||||
|
||||
@@ -20,7 +19,6 @@ class HomeController extends Controller
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Display the homepage.
|
||||
* @return Response
|
||||
@@ -46,17 +44,39 @@ class HomeController extends Controller
|
||||
'draftPages' => $draftPages,
|
||||
];
|
||||
|
||||
// Add required list ordering & sorting for books & shelves views.
|
||||
if ($homepageOption === 'bookshelves' || $homepageOption === 'books') {
|
||||
$key = $homepageOption;
|
||||
$view = setting()->getUser($this->currentUser, $key . '_view_type', config('app.views.' . $key));
|
||||
$sort = setting()->getUser($this->currentUser, $key . '_sort', 'name');
|
||||
$order = setting()->getUser($this->currentUser, $key . '_sort_order', 'asc');
|
||||
|
||||
$sortOptions = [
|
||||
'name' => trans('common.sort_name'),
|
||||
'created_at' => trans('common.sort_created_at'),
|
||||
'updated_at' => trans('common.sort_updated_at'),
|
||||
];
|
||||
|
||||
$commonData = array_merge($commonData, [
|
||||
'view' => $view,
|
||||
'sort' => $sort,
|
||||
'order' => $order,
|
||||
'sortOptions' => $sortOptions,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($homepageOption === 'bookshelves') {
|
||||
$shelves = $this->entityRepo->getAllPaginated('bookshelf', 18);
|
||||
$shelvesViewType = setting()->getUser($this->currentUser, 'bookshelves_view_type', config('app.views.bookshelves', 'grid'));
|
||||
$data = array_merge($commonData, ['shelves' => $shelves, 'shelvesViewType' => $shelvesViewType]);
|
||||
$shelves = $this->entityRepo->getAllPaginated('bookshelf', 18, $commonData['sort'], $commonData['order']);
|
||||
foreach ($shelves as $shelf) {
|
||||
$shelf->books = $this->entityRepo->getBookshelfChildren($shelf);
|
||||
}
|
||||
$data = array_merge($commonData, ['shelves' => $shelves]);
|
||||
return view('common.home-shelves', $data);
|
||||
}
|
||||
|
||||
if ($homepageOption === 'books') {
|
||||
$books = $this->entityRepo->getAllPaginated('book', 18);
|
||||
$booksViewType = setting()->getUser($this->currentUser, 'books_view_type', config('app.views.books', 'list'));
|
||||
$data = array_merge($commonData, ['books' => $books, 'booksViewType' => $booksViewType]);
|
||||
$books = $this->entityRepo->getAllPaginated('book', 18, $commonData['sort'], $commonData['order']);
|
||||
$data = array_merge($commonData, ['books' => $books]);
|
||||
return view('common.home-book', $data);
|
||||
}
|
||||
|
||||
@@ -80,6 +100,7 @@ class HomeController extends Controller
|
||||
{
|
||||
$locale = app()->getLocale();
|
||||
$cacheKey = 'GLOBAL_TRANSLATIONS_' . $locale;
|
||||
|
||||
if (cache()->has($cacheKey) && config('app.env') !== 'development') {
|
||||
$resp = cache($cacheKey);
|
||||
} else {
|
||||
@@ -90,15 +111,6 @@ class HomeController extends Controller
|
||||
'entities' => trans('entities'),
|
||||
'errors' => trans('errors')
|
||||
];
|
||||
if ($locale !== 'en') {
|
||||
$enTrans = [
|
||||
'common' => trans('common', [], 'en'),
|
||||
'components' => trans('components', [], 'en'),
|
||||
'entities' => trans('entities', [], 'en'),
|
||||
'errors' => trans('errors', [], 'en')
|
||||
];
|
||||
$translations = array_replace_recursive($enTrans, $translations);
|
||||
}
|
||||
$resp = 'window.translations = ' . json_encode($translations);
|
||||
cache()->put($cacheKey, $resp, 120);
|
||||
}
|
||||
@@ -114,7 +126,7 @@ class HomeController extends Controller
|
||||
*/
|
||||
public function customHeadContent()
|
||||
{
|
||||
return view('partials/custom-head-content');
|
||||
return view('partials.custom-head-content');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,7 +141,7 @@ class HomeController extends Controller
|
||||
$allowRobots = $sitePublic;
|
||||
}
|
||||
return response()
|
||||
->view('common/robots', ['allowRobots' => $allowRobots])
|
||||
->view('common.robots', ['allowRobots' => $allowRobots])
|
||||
->header('Content-Type', 'text/plain');
|
||||
}
|
||||
|
||||
@@ -138,6 +150,6 @@ class HomeController extends Controller
|
||||
*/
|
||||
public function getNotFound()
|
||||
{
|
||||
return response()->view('errors/404', [], 404);
|
||||
return response()->view('errors.404', [], 404);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,248 +0,0 @@
|
||||
<?php namespace BookStack\Http\Controllers;
|
||||
|
||||
use BookStack\Exceptions\ImageUploadException;
|
||||
use BookStack\Exceptions\NotFoundException;
|
||||
use BookStack\Repos\EntityRepo;
|
||||
use BookStack\Repos\ImageRepo;
|
||||
use Illuminate\Filesystem\Filesystem as File;
|
||||
use Illuminate\Http\Request;
|
||||
use BookStack\Image;
|
||||
use BookStack\Repos\PageRepo;
|
||||
|
||||
class ImageController extends Controller
|
||||
{
|
||||
protected $image;
|
||||
protected $file;
|
||||
protected $imageRepo;
|
||||
|
||||
/**
|
||||
* ImageController constructor.
|
||||
* @param Image $image
|
||||
* @param File $file
|
||||
* @param ImageRepo $imageRepo
|
||||
*/
|
||||
public function __construct(Image $image, File $file, ImageRepo $imageRepo)
|
||||
{
|
||||
$this->image = $image;
|
||||
$this->file = $file;
|
||||
$this->imageRepo = $imageRepo;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide an image file from storage.
|
||||
* @param string $path
|
||||
* @return mixed
|
||||
*/
|
||||
public function showImage(string $path)
|
||||
{
|
||||
$path = storage_path('uploads/images/' . $path);
|
||||
if (!file_exists($path)) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
return response()->file($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all images for a specific type, Paginated
|
||||
* @param string $type
|
||||
* @param int $page
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function getAllByType($type, $page = 0)
|
||||
{
|
||||
$imgData = $this->imageRepo->getPaginatedByType($type, $page);
|
||||
return response()->json($imgData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search through images within a particular type.
|
||||
* @param $type
|
||||
* @param int $page
|
||||
* @param Request $request
|
||||
* @return mixed
|
||||
*/
|
||||
public function searchByType(Request $request, $type, $page = 0)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'term' => 'required|string'
|
||||
]);
|
||||
|
||||
$searchTerm = $request->get('term');
|
||||
$imgData = $this->imageRepo->searchPaginatedByType($type, $searchTerm, $page, 24);
|
||||
return response()->json($imgData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all images for a user.
|
||||
* @param int $page
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function getAllForUserType($page = 0)
|
||||
{
|
||||
$imgData = $this->imageRepo->getPaginatedByType('user', $page, 24, $this->currentUser->id);
|
||||
return response()->json($imgData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get gallery images with a specific filter such as book or page
|
||||
* @param $filter
|
||||
* @param int $page
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function getGalleryFiltered(Request $request, $filter, $page = 0)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'page_id' => 'required|integer'
|
||||
]);
|
||||
|
||||
$validFilters = collect(['page', 'book']);
|
||||
if (!$validFilters->contains($filter)) {
|
||||
return response('Invalid filter', 500);
|
||||
}
|
||||
|
||||
$pageId = $request->get('page_id');
|
||||
$imgData = $this->imageRepo->getGalleryFiltered(strtolower($filter), $pageId, $page, 24);
|
||||
|
||||
return response()->json($imgData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles image uploads for use on pages.
|
||||
* @param string $type
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function uploadByType($type, Request $request)
|
||||
{
|
||||
$this->checkPermission('image-create-all');
|
||||
$this->validate($request, [
|
||||
'file' => 'is_image'
|
||||
]);
|
||||
|
||||
if (!$this->imageRepo->isValidType($type)) {
|
||||
return $this->jsonError(trans('errors.image_upload_type_error'));
|
||||
}
|
||||
|
||||
$imageUpload = $request->file('file');
|
||||
|
||||
try {
|
||||
$uploadedTo = $request->get('uploaded_to', 0);
|
||||
$image = $this->imageRepo->saveNew($imageUpload, $type, $uploadedTo);
|
||||
} catch (ImageUploadException $e) {
|
||||
return response($e->getMessage(), 500);
|
||||
}
|
||||
|
||||
|
||||
return response()->json($image);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload a drawing to the system.
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function uploadDrawing(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'image' => 'required|string',
|
||||
'uploaded_to' => 'required|integer'
|
||||
]);
|
||||
$this->checkPermission('image-create-all');
|
||||
$imageBase64Data = $request->get('image');
|
||||
|
||||
try {
|
||||
$uploadedTo = $request->get('uploaded_to', 0);
|
||||
$image = $this->imageRepo->saveDrawing($imageBase64Data, $uploadedTo);
|
||||
} catch (ImageUploadException $e) {
|
||||
return response($e->getMessage(), 500);
|
||||
}
|
||||
|
||||
return response()->json($image);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content of an image based64 encoded.
|
||||
* @param $id
|
||||
* @return \Illuminate\Http\JsonResponse|mixed
|
||||
*/
|
||||
public function getBase64Image($id)
|
||||
{
|
||||
$image = $this->imageRepo->getById($id);
|
||||
$imageData = $this->imageRepo->getImageData($image);
|
||||
if ($imageData === null) {
|
||||
return $this->jsonError("Image data could not be found");
|
||||
}
|
||||
return response()->json([
|
||||
'content' => base64_encode($imageData)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a sized thumbnail for an image.
|
||||
* @param $id
|
||||
* @param $width
|
||||
* @param $height
|
||||
* @param $crop
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
* @throws ImageUploadException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getThumbnail($id, $width, $height, $crop)
|
||||
{
|
||||
$this->checkPermission('image-create-all');
|
||||
$image = $this->imageRepo->getById($id);
|
||||
$thumbnailUrl = $this->imageRepo->getThumbnail($image, $width, $height, $crop == 'false');
|
||||
return response()->json(['url' => $thumbnailUrl]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update image details
|
||||
* @param integer $imageId
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
* @throws ImageUploadException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function update($imageId, Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'name' => 'required|min:2|string'
|
||||
]);
|
||||
$image = $this->imageRepo->getById($imageId);
|
||||
$this->checkOwnablePermission('image-update', $image);
|
||||
$image = $this->imageRepo->updateImageDetails($image, $request->all());
|
||||
return response()->json($image);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the usage of an image on pages.
|
||||
* @param EntityRepo $entityRepo
|
||||
* @param $id
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function usage(EntityRepo $entityRepo, $id)
|
||||
{
|
||||
$image = $this->imageRepo->getById($id);
|
||||
$pageSearch = $entityRepo->searchForImage($image->url);
|
||||
return response()->json($pageSearch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an image and all thumbnail/image files
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function destroy($id)
|
||||
{
|
||||
$image = $this->imageRepo->getById($id);
|
||||
$this->checkOwnablePermission('image-delete', $image);
|
||||
|
||||
$this->imageRepo->destroyImage($image);
|
||||
return response()->json(trans('components.images_deleted'));
|
||||
}
|
||||
}
|
||||
88
app/Http/Controllers/Images/DrawioImageController.php
Normal file
88
app/Http/Controllers/Images/DrawioImageController.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Http\Controllers\Images;
|
||||
|
||||
use BookStack\Exceptions\ImageUploadException;
|
||||
use BookStack\Uploads\ImageRepo;
|
||||
use Illuminate\Http\Request;
|
||||
use BookStack\Http\Controllers\Controller;
|
||||
|
||||
class DrawioImageController extends Controller
|
||||
{
|
||||
protected $imageRepo;
|
||||
|
||||
/**
|
||||
* DrawioImageController constructor.
|
||||
* @param ImageRepo $imageRepo
|
||||
*/
|
||||
public function __construct(ImageRepo $imageRepo)
|
||||
{
|
||||
$this->imageRepo = $imageRepo;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of gallery images, in a list.
|
||||
* Can be paged and filtered by entity.
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function list(Request $request)
|
||||
{
|
||||
$page = $request->get('page', 1);
|
||||
$searchTerm = $request->get('search', null);
|
||||
$uploadedToFilter = $request->get('uploaded_to', null);
|
||||
$parentTypeFilter = $request->get('filter_type', null);
|
||||
|
||||
$imgData = $this->imageRepo->getEntityFiltered('drawio', $parentTypeFilter, $page, 24, $uploadedToFilter, $searchTerm);
|
||||
return response()->json($imgData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new gallery image in the system.
|
||||
* @param Request $request
|
||||
* @return Illuminate\Http\JsonResponse
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function create(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'image' => 'required|string',
|
||||
'uploaded_to' => 'required|integer'
|
||||
]);
|
||||
|
||||
$this->checkPermission('image-create-all');
|
||||
$imageBase64Data = $request->get('image');
|
||||
|
||||
try {
|
||||
$uploadedTo = $request->get('uploaded_to', 0);
|
||||
$image = $this->imageRepo->saveDrawing($imageBase64Data, $uploadedTo);
|
||||
} catch (ImageUploadException $e) {
|
||||
return response($e->getMessage(), 500);
|
||||
}
|
||||
|
||||
return response()->json($image);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content of an image based64 encoded.
|
||||
* @param $id
|
||||
* @return \Illuminate\Http\JsonResponse|mixed
|
||||
*/
|
||||
public function getAsBase64($id)
|
||||
{
|
||||
$image = $this->imageRepo->getById($id);
|
||||
$page = $image->getPage();
|
||||
if ($image === null || $image->type !== 'drawio' || !userCan('page-view', $page)) {
|
||||
return $this->jsonError("Image data could not be found");
|
||||
}
|
||||
|
||||
$imageData = $this->imageRepo->getImageData($image);
|
||||
if ($imageData === null) {
|
||||
return $this->jsonError("Image data could not be found");
|
||||
}
|
||||
return response()->json([
|
||||
'content' => base64_encode($imageData)
|
||||
]);
|
||||
}
|
||||
}
|
||||
64
app/Http/Controllers/Images/GalleryImageController.php
Normal file
64
app/Http/Controllers/Images/GalleryImageController.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Http\Controllers\Images;
|
||||
|
||||
use BookStack\Exceptions\ImageUploadException;
|
||||
use BookStack\Uploads\ImageRepo;
|
||||
use Illuminate\Http\Request;
|
||||
use BookStack\Http\Controllers\Controller;
|
||||
|
||||
class GalleryImageController extends Controller
|
||||
{
|
||||
protected $imageRepo;
|
||||
|
||||
/**
|
||||
* GalleryImageController constructor.
|
||||
* @param ImageRepo $imageRepo
|
||||
*/
|
||||
public function __construct(ImageRepo $imageRepo)
|
||||
{
|
||||
$this->imageRepo = $imageRepo;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of gallery images, in a list.
|
||||
* Can be paged and filtered by entity.
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function list(Request $request)
|
||||
{
|
||||
$page = $request->get('page', 1);
|
||||
$searchTerm = $request->get('search', null);
|
||||
$uploadedToFilter = $request->get('uploaded_to', null);
|
||||
$parentTypeFilter = $request->get('filter_type', null);
|
||||
|
||||
$imgData = $this->imageRepo->getEntityFiltered('gallery', $parentTypeFilter, $page, 24, $uploadedToFilter, $searchTerm);
|
||||
return response()->json($imgData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new gallery image in the system.
|
||||
* @param Request $request
|
||||
* @return Illuminate\Http\JsonResponse
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function create(Request $request)
|
||||
{
|
||||
$this->checkPermission('image-create-all');
|
||||
$this->validate($request, [
|
||||
'file' => $this->imageRepo->getImageValidationRules()
|
||||
]);
|
||||
|
||||
try {
|
||||
$imageUpload = $request->file('file');
|
||||
$uploadedTo = $request->get('uploaded_to', 0);
|
||||
$image = $this->imageRepo->saveNew($imageUpload, 'gallery', $uploadedTo);
|
||||
} catch (ImageUploadException $e) {
|
||||
return response($e->getMessage(), 500);
|
||||
}
|
||||
|
||||
return response()->json($image);
|
||||
}
|
||||
}
|
||||
115
app/Http/Controllers/Images/ImageController.php
Normal file
115
app/Http/Controllers/Images/ImageController.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php namespace BookStack\Http\Controllers\Images;
|
||||
|
||||
use BookStack\Entities\Repos\EntityRepo;
|
||||
use BookStack\Exceptions\ImageUploadException;
|
||||
use BookStack\Http\Controllers\Controller;
|
||||
use BookStack\Repos\PageRepo;
|
||||
use BookStack\Uploads\Image;
|
||||
use BookStack\Uploads\ImageRepo;
|
||||
use Illuminate\Filesystem\Filesystem as File;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ImageController extends Controller
|
||||
{
|
||||
protected $image;
|
||||
protected $file;
|
||||
protected $imageRepo;
|
||||
|
||||
/**
|
||||
* ImageController constructor.
|
||||
* @param Image $image
|
||||
* @param File $file
|
||||
* @param ImageRepo $imageRepo
|
||||
*/
|
||||
public function __construct(Image $image, File $file, ImageRepo $imageRepo)
|
||||
{
|
||||
$this->image = $image;
|
||||
$this->file = $file;
|
||||
$this->imageRepo = $imageRepo;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide an image file from storage.
|
||||
* @param string $path
|
||||
* @return mixed
|
||||
*/
|
||||
public function showImage(string $path)
|
||||
{
|
||||
$path = storage_path('uploads/images/' . $path);
|
||||
if (!file_exists($path)) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
return response()->file($path);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update image details
|
||||
* @param integer $id
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
* @throws ImageUploadException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function update($id, Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'name' => 'required|min:2|string'
|
||||
]);
|
||||
|
||||
$image = $this->imageRepo->getById($id);
|
||||
$this->checkImagePermission($image);
|
||||
$this->checkOwnablePermission('image-update', $image);
|
||||
|
||||
$image = $this->imageRepo->updateImageDetails($image, $request->all());
|
||||
return response()->json($image);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the usage of an image on pages.
|
||||
* @param \BookStack\Entities\Repos\EntityRepo $entityRepo
|
||||
* @param $id
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function usage(EntityRepo $entityRepo, $id)
|
||||
{
|
||||
$image = $this->imageRepo->getById($id);
|
||||
$this->checkImagePermission($image);
|
||||
$pageSearch = $entityRepo->searchForImage($image->url);
|
||||
return response()->json($pageSearch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an image and all thumbnail/image files
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function destroy($id)
|
||||
{
|
||||
$image = $this->imageRepo->getById($id);
|
||||
$this->checkOwnablePermission('image-delete', $image);
|
||||
$this->checkImagePermission($image);
|
||||
|
||||
$this->imageRepo->destroyImage($image);
|
||||
return response()->json(trans('components.images_deleted'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check related page permission and ensure type is drawio or gallery.
|
||||
* @param Image $image
|
||||
*/
|
||||
protected function checkImagePermission(Image $image)
|
||||
{
|
||||
if ($image->type !== 'drawio' && $image->type !== 'gallery') {
|
||||
$this->showPermissionError();
|
||||
}
|
||||
|
||||
$relatedPage = $image->getPage();
|
||||
if ($relatedPage) {
|
||||
$this->checkOwnablePermission('page-view', $relatedPage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,32 @@
|
||||
<?php namespace BookStack\Http\Controllers;
|
||||
|
||||
use Activity;
|
||||
use BookStack\Auth\UserRepo;
|
||||
use BookStack\Entities\Repos\EntityRepo;
|
||||
use BookStack\Entities\ExportService;
|
||||
use BookStack\Entities\Repos\PageRepo;
|
||||
use BookStack\Exceptions\NotFoundException;
|
||||
use BookStack\Repos\EntityRepo;
|
||||
use BookStack\Repos\UserRepo;
|
||||
use BookStack\Services\ExportService;
|
||||
use GatherContent\Htmldiff\Htmldiff;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Views;
|
||||
use GatherContent\Htmldiff\Htmldiff;
|
||||
|
||||
class PageController extends Controller
|
||||
{
|
||||
|
||||
protected $entityRepo;
|
||||
protected $pageRepo;
|
||||
protected $exportService;
|
||||
protected $userRepo;
|
||||
|
||||
/**
|
||||
* PageController constructor.
|
||||
* @param EntityRepo $entityRepo
|
||||
* @param ExportService $exportService
|
||||
* @param \BookStack\Entities\Repos\PageRepo $pageRepo
|
||||
* @param \BookStack\Entities\ExportService $exportService
|
||||
* @param UserRepo $userRepo
|
||||
*/
|
||||
public function __construct(EntityRepo $entityRepo, ExportService $exportService, UserRepo $userRepo)
|
||||
public function __construct(PageRepo $pageRepo, ExportService $exportService, UserRepo $userRepo)
|
||||
{
|
||||
$this->entityRepo = $entityRepo;
|
||||
$this->pageRepo = $pageRepo;
|
||||
$this->exportService = $exportService;
|
||||
$this->userRepo = $userRepo;
|
||||
parent::__construct();
|
||||
@@ -42,11 +43,11 @@ class PageController extends Controller
|
||||
public function create($bookSlug, $chapterSlug = null)
|
||||
{
|
||||
if ($chapterSlug !== null) {
|
||||
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
|
||||
$chapter = $this->pageRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
|
||||
$book = $chapter->book;
|
||||
} else {
|
||||
$chapter = null;
|
||||
$book = $this->entityRepo->getBySlug('book', $bookSlug);
|
||||
$book = $this->pageRepo->getBySlug('book', $bookSlug);
|
||||
}
|
||||
|
||||
$parent = $chapter ? $chapter : $book;
|
||||
@@ -54,13 +55,13 @@ class PageController extends Controller
|
||||
|
||||
// Redirect to draft edit screen if signed in
|
||||
if ($this->signedIn) {
|
||||
$draft = $this->entityRepo->getDraftPage($book, $chapter);
|
||||
$draft = $this->pageRepo->getDraftPage($book, $chapter);
|
||||
return redirect($draft->getUrl());
|
||||
}
|
||||
|
||||
// Otherwise show the edit view if they're a guest
|
||||
$this->setPageTitle(trans('entities.pages_new'));
|
||||
return view('pages/guest-create', ['parent' => $parent]);
|
||||
return view('pages.guest-create', ['parent' => $parent]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,18 +79,18 @@ class PageController extends Controller
|
||||
]);
|
||||
|
||||
if ($chapterSlug !== null) {
|
||||
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
|
||||
$chapter = $this->pageRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
|
||||
$book = $chapter->book;
|
||||
} else {
|
||||
$chapter = null;
|
||||
$book = $this->entityRepo->getBySlug('book', $bookSlug);
|
||||
$book = $this->pageRepo->getBySlug('book', $bookSlug);
|
||||
}
|
||||
|
||||
$parent = $chapter ? $chapter : $book;
|
||||
$this->checkOwnablePermission('page-create', $parent);
|
||||
|
||||
$page = $this->entityRepo->getDraftPage($book, $chapter);
|
||||
$this->entityRepo->publishPageDraft($page, [
|
||||
$page = $this->pageRepo->getDraftPage($book, $chapter);
|
||||
$this->pageRepo->publishPageDraft($page, [
|
||||
'name' => $request->get('name'),
|
||||
'html' => ''
|
||||
]);
|
||||
@@ -104,12 +105,12 @@ class PageController extends Controller
|
||||
*/
|
||||
public function editDraft($bookSlug, $pageId)
|
||||
{
|
||||
$draft = $this->entityRepo->getById('page', $pageId, true);
|
||||
$draft = $this->pageRepo->getById('page', $pageId, true);
|
||||
$this->checkOwnablePermission('page-create', $draft->parent);
|
||||
$this->setPageTitle(trans('entities.pages_edit_draft'));
|
||||
|
||||
$draftsEnabled = $this->signedIn;
|
||||
return view('pages/edit', [
|
||||
return view('pages.edit', [
|
||||
'page' => $draft,
|
||||
'book' => $draft->book,
|
||||
'isDraft' => true,
|
||||
@@ -131,19 +132,19 @@ class PageController extends Controller
|
||||
]);
|
||||
|
||||
$input = $request->all();
|
||||
$draftPage = $this->entityRepo->getById('page', $pageId, true);
|
||||
$draftPage = $this->pageRepo->getById('page', $pageId, true);
|
||||
$book = $draftPage->book;
|
||||
|
||||
$parent = $draftPage->parent;
|
||||
$this->checkOwnablePermission('page-create', $parent);
|
||||
|
||||
if ($parent->isA('chapter')) {
|
||||
$input['priority'] = $this->entityRepo->getNewChapterPriority($parent);
|
||||
$input['priority'] = $this->pageRepo->getNewChapterPriority($parent);
|
||||
} else {
|
||||
$input['priority'] = $this->entityRepo->getNewBookPriority($parent);
|
||||
$input['priority'] = $this->pageRepo->getNewBookPriority($parent);
|
||||
}
|
||||
|
||||
$page = $this->entityRepo->publishPageDraft($draftPage, $input);
|
||||
$page = $this->pageRepo->publishPageDraft($draftPage, $input);
|
||||
|
||||
Activity::add($page, 'page_create', $book->id);
|
||||
return redirect($page->getUrl());
|
||||
@@ -160,9 +161,9 @@ class PageController extends Controller
|
||||
public function show($bookSlug, $pageSlug)
|
||||
{
|
||||
try {
|
||||
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
|
||||
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
|
||||
} catch (NotFoundException $e) {
|
||||
$page = $this->entityRepo->getPageByOldSlug($pageSlug, $bookSlug);
|
||||
$page = $this->pageRepo->getPageByOldSlug($pageSlug, $bookSlug);
|
||||
if ($page === null) {
|
||||
throw $e;
|
||||
}
|
||||
@@ -171,9 +172,9 @@ class PageController extends Controller
|
||||
|
||||
$this->checkOwnablePermission('page-view', $page);
|
||||
|
||||
$page->html = $this->entityRepo->renderPage($page);
|
||||
$sidebarTree = $this->entityRepo->getBookChildren($page->book);
|
||||
$pageNav = $this->entityRepo->getPageNav($page->html);
|
||||
$page->html = $this->pageRepo->renderPage($page);
|
||||
$sidebarTree = $this->pageRepo->getBookChildren($page->book);
|
||||
$pageNav = $this->pageRepo->getPageNav($page->html);
|
||||
|
||||
// check if the comment's are enabled
|
||||
$commentsEnabled = !setting('app-disable-comments');
|
||||
@@ -183,7 +184,7 @@ class PageController extends Controller
|
||||
|
||||
Views::add($page);
|
||||
$this->setPageTitle($page->getShortName());
|
||||
return view('pages/show', [
|
||||
return view('pages.show', [
|
||||
'page' => $page,'book' => $page->book,
|
||||
'current' => $page,
|
||||
'sidebarTree' => $sidebarTree,
|
||||
@@ -199,7 +200,7 @@ class PageController extends Controller
|
||||
*/
|
||||
public function getPageAjax($pageId)
|
||||
{
|
||||
$page = $this->entityRepo->getById('page', $pageId);
|
||||
$page = $this->pageRepo->getById('page', $pageId);
|
||||
return response()->json($page);
|
||||
}
|
||||
|
||||
@@ -208,28 +209,29 @@ class PageController extends Controller
|
||||
* @param string $bookSlug
|
||||
* @param string $pageSlug
|
||||
* @return Response
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function edit($bookSlug, $pageSlug)
|
||||
{
|
||||
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
|
||||
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
|
||||
$this->checkOwnablePermission('page-update', $page);
|
||||
$this->setPageTitle(trans('entities.pages_editing_named', ['pageName'=>$page->getShortName()]));
|
||||
$page->isDraft = false;
|
||||
|
||||
// Check for active editing
|
||||
$warnings = [];
|
||||
if ($this->entityRepo->isPageEditingActive($page, 60)) {
|
||||
$warnings[] = $this->entityRepo->getPageEditingActiveMessage($page, 60);
|
||||
if ($this->pageRepo->isPageEditingActive($page, 60)) {
|
||||
$warnings[] = $this->pageRepo->getPageEditingActiveMessage($page, 60);
|
||||
}
|
||||
|
||||
// Check for a current draft version for this user
|
||||
if ($this->entityRepo->hasUserGotPageDraft($page, $this->currentUser->id)) {
|
||||
$draft = $this->entityRepo->getUserPageDraft($page, $this->currentUser->id);
|
||||
$page->name = $draft->name;
|
||||
$page->html = $draft->html;
|
||||
$page->markdown = $draft->markdown;
|
||||
$userPageDraft = $this->pageRepo->getUserPageDraft($page, $this->currentUser->id);
|
||||
if ($userPageDraft !== null) {
|
||||
$page->name = $userPageDraft->name;
|
||||
$page->html = $userPageDraft->html;
|
||||
$page->markdown = $userPageDraft->markdown;
|
||||
$page->isDraft = true;
|
||||
$warnings [] = $this->entityRepo->getUserPageDraftMessage($draft);
|
||||
$warnings [] = $this->pageRepo->getUserPageDraftMessage($userPageDraft);
|
||||
}
|
||||
|
||||
if (count($warnings) > 0) {
|
||||
@@ -237,7 +239,7 @@ class PageController extends Controller
|
||||
}
|
||||
|
||||
$draftsEnabled = $this->signedIn;
|
||||
return view('pages/edit', [
|
||||
return view('pages.edit', [
|
||||
'page' => $page,
|
||||
'book' => $page->book,
|
||||
'current' => $page,
|
||||
@@ -257,9 +259,9 @@ class PageController extends Controller
|
||||
$this->validate($request, [
|
||||
'name' => 'required|string|max:255'
|
||||
]);
|
||||
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
|
||||
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
|
||||
$this->checkOwnablePermission('page-update', $page);
|
||||
$this->entityRepo->updatePage($page, $page->book->id, $request->all());
|
||||
$this->pageRepo->updatePage($page, $page->book->id, $request->all());
|
||||
Activity::add($page, 'page_update', $page->book->id);
|
||||
return redirect($page->getUrl());
|
||||
}
|
||||
@@ -272,7 +274,7 @@ class PageController extends Controller
|
||||
*/
|
||||
public function saveDraft(Request $request, $pageId)
|
||||
{
|
||||
$page = $this->entityRepo->getById('page', $pageId, true);
|
||||
$page = $this->pageRepo->getById('page', $pageId, true);
|
||||
$this->checkOwnablePermission('page-update', $page);
|
||||
|
||||
if (!$this->signedIn) {
|
||||
@@ -282,7 +284,7 @@ class PageController extends Controller
|
||||
], 500);
|
||||
}
|
||||
|
||||
$draft = $this->entityRepo->updatePageDraft($page, $request->only(['name', 'html', 'markdown']));
|
||||
$draft = $this->pageRepo->updatePageDraft($page, $request->only(['name', 'html', 'markdown']));
|
||||
|
||||
$updateTime = $draft->updated_at->timestamp;
|
||||
return response()->json([
|
||||
@@ -300,7 +302,7 @@ class PageController extends Controller
|
||||
*/
|
||||
public function redirectFromLink($pageId)
|
||||
{
|
||||
$page = $this->entityRepo->getById('page', $pageId);
|
||||
$page = $this->pageRepo->getById('page', $pageId);
|
||||
return redirect($page->getUrl());
|
||||
}
|
||||
|
||||
@@ -312,10 +314,10 @@ class PageController extends Controller
|
||||
*/
|
||||
public function showDelete($bookSlug, $pageSlug)
|
||||
{
|
||||
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
|
||||
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
|
||||
$this->checkOwnablePermission('page-delete', $page);
|
||||
$this->setPageTitle(trans('entities.pages_delete_named', ['pageName'=>$page->getShortName()]));
|
||||
return view('pages/delete', ['book' => $page->book, 'page' => $page, 'current' => $page]);
|
||||
return view('pages.delete', ['book' => $page->book, 'page' => $page, 'current' => $page]);
|
||||
}
|
||||
|
||||
|
||||
@@ -328,10 +330,10 @@ class PageController extends Controller
|
||||
*/
|
||||
public function showDeleteDraft($bookSlug, $pageId)
|
||||
{
|
||||
$page = $this->entityRepo->getById('page', $pageId, true);
|
||||
$page = $this->pageRepo->getById('page', $pageId, true);
|
||||
$this->checkOwnablePermission('page-update', $page);
|
||||
$this->setPageTitle(trans('entities.pages_delete_draft_named', ['pageName'=>$page->getShortName()]));
|
||||
return view('pages/delete', ['book' => $page->book, 'page' => $page, 'current' => $page]);
|
||||
return view('pages.delete', ['book' => $page->book, 'page' => $page, 'current' => $page]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -343,10 +345,10 @@ class PageController extends Controller
|
||||
*/
|
||||
public function destroy($bookSlug, $pageSlug)
|
||||
{
|
||||
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
|
||||
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
|
||||
$book = $page->book;
|
||||
$this->checkOwnablePermission('page-delete', $page);
|
||||
$this->entityRepo->destroyPage($page);
|
||||
$this->pageRepo->destroyPage($page);
|
||||
|
||||
Activity::addMessage('page_delete', $book->id, $page->name);
|
||||
session()->flash('success', trans('entities.pages_delete_success'));
|
||||
@@ -362,11 +364,11 @@ class PageController extends Controller
|
||||
*/
|
||||
public function destroyDraft($bookSlug, $pageId)
|
||||
{
|
||||
$page = $this->entityRepo->getById('page', $pageId, true);
|
||||
$page = $this->pageRepo->getById('page', $pageId, true);
|
||||
$book = $page->book;
|
||||
$this->checkOwnablePermission('page-update', $page);
|
||||
session()->flash('success', trans('entities.pages_delete_draft_success'));
|
||||
$this->entityRepo->destroyPage($page);
|
||||
$this->pageRepo->destroyPage($page);
|
||||
return redirect($book->getUrl());
|
||||
}
|
||||
|
||||
@@ -375,12 +377,13 @@ class PageController extends Controller
|
||||
* @param string $bookSlug
|
||||
* @param string $pageSlug
|
||||
* @return \Illuminate\View\View
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function showRevisions($bookSlug, $pageSlug)
|
||||
{
|
||||
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
|
||||
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
|
||||
$this->setPageTitle(trans('entities.pages_revisions_named', ['pageName'=>$page->getShortName()]));
|
||||
return view('pages/revisions', ['page' => $page, 'book' => $page->book, 'current' => $page]);
|
||||
return view('pages.revisions', ['page' => $page, 'current' => $page]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -392,7 +395,7 @@ class PageController extends Controller
|
||||
*/
|
||||
public function showRevision($bookSlug, $pageSlug, $revisionId)
|
||||
{
|
||||
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
|
||||
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
|
||||
$revision = $page->revisions()->where('id', '=', $revisionId)->first();
|
||||
if ($revision === null) {
|
||||
abort(404);
|
||||
@@ -401,9 +404,10 @@ class PageController extends Controller
|
||||
$page->fill($revision->toArray());
|
||||
$this->setPageTitle(trans('entities.pages_revision_named', ['pageName' => $page->getShortName()]));
|
||||
|
||||
return view('pages/revision', [
|
||||
return view('pages.revision', [
|
||||
'page' => $page,
|
||||
'book' => $page->book,
|
||||
'diff' => null,
|
||||
'revision' => $revision
|
||||
]);
|
||||
}
|
||||
@@ -417,7 +421,7 @@ class PageController extends Controller
|
||||
*/
|
||||
public function showRevisionChanges($bookSlug, $pageSlug, $revisionId)
|
||||
{
|
||||
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
|
||||
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
|
||||
$revision = $page->revisions()->where('id', '=', $revisionId)->first();
|
||||
if ($revision === null) {
|
||||
abort(404);
|
||||
@@ -430,7 +434,7 @@ class PageController extends Controller
|
||||
$page->fill($revision->toArray());
|
||||
$this->setPageTitle(trans('entities.pages_revision_named', ['pageName'=>$page->getShortName()]));
|
||||
|
||||
return view('pages/revision', [
|
||||
return view('pages.revision', [
|
||||
'page' => $page,
|
||||
'book' => $page->book,
|
||||
'diff' => $diff,
|
||||
@@ -447,9 +451,9 @@ class PageController extends Controller
|
||||
*/
|
||||
public function restoreRevision($bookSlug, $pageSlug, $revisionId)
|
||||
{
|
||||
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
|
||||
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
|
||||
$this->checkOwnablePermission('page-update', $page);
|
||||
$page = $this->entityRepo->restorePageRevision($page, $page->book, $revisionId);
|
||||
$page = $this->pageRepo->restorePageRevision($page, $page->book, $revisionId);
|
||||
Activity::add($page, 'page_restore', $page->book->id);
|
||||
return redirect($page->getUrl());
|
||||
}
|
||||
@@ -466,7 +470,7 @@ class PageController extends Controller
|
||||
*/
|
||||
public function destroyRevision($bookSlug, $pageSlug, $revId)
|
||||
{
|
||||
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
|
||||
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
|
||||
$this->checkOwnablePermission('page-delete', $page);
|
||||
|
||||
$revision = $page->revisions()->where('id', '=', $revId)->first();
|
||||
@@ -480,12 +484,12 @@ class PageController extends Controller
|
||||
// Check if its the latest revision, cannot delete latest revision.
|
||||
if (intval($currentRevision->id) === intval($revId)) {
|
||||
session()->flash('error', trans('entities.revision_cannot_delete_latest'));
|
||||
return response()->view('pages/revisions', ['page' => $page, 'book' => $page->book, 'current' => $page], 400);
|
||||
return response()->view('pages.revisions', ['page' => $page, 'book' => $page->book, 'current' => $page], 400);
|
||||
}
|
||||
|
||||
$revision->delete();
|
||||
session()->flash('success', trans('entities.revision_delete_success'));
|
||||
return view('pages/revisions', ['page' => $page, 'book' => $page->book, 'current' => $page]);
|
||||
return view('pages.revisions', ['page' => $page, 'book' => $page->book, 'current' => $page]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -497,8 +501,8 @@ class PageController extends Controller
|
||||
*/
|
||||
public function exportPdf($bookSlug, $pageSlug)
|
||||
{
|
||||
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
|
||||
$page->html = $this->entityRepo->renderPage($page);
|
||||
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
|
||||
$page->html = $this->pageRepo->renderPage($page);
|
||||
$pdfContent = $this->exportService->pageToPdf($page);
|
||||
return $this->downloadResponse($pdfContent, $pageSlug . '.pdf');
|
||||
}
|
||||
@@ -511,8 +515,8 @@ class PageController extends Controller
|
||||
*/
|
||||
public function exportHtml($bookSlug, $pageSlug)
|
||||
{
|
||||
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
|
||||
$page->html = $this->entityRepo->renderPage($page);
|
||||
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
|
||||
$page->html = $this->pageRepo->renderPage($page);
|
||||
$containedHtml = $this->exportService->pageToContainedHtml($page);
|
||||
return $this->downloadResponse($containedHtml, $pageSlug . '.html');
|
||||
}
|
||||
@@ -525,54 +529,25 @@ class PageController extends Controller
|
||||
*/
|
||||
public function exportPlainText($bookSlug, $pageSlug)
|
||||
{
|
||||
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
|
||||
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
|
||||
$pageText = $this->exportService->pageToPlainText($page);
|
||||
return $this->downloadResponse($pageText, $pageSlug . '.txt');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a listing of recently created pages
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
*/
|
||||
public function showRecentlyCreated()
|
||||
{
|
||||
$pages = $this->entityRepo->getRecentlyCreatedPaginated('page', 20)->setPath(baseUrl('/pages/recently-created'));
|
||||
return view('pages/detailed-listing', [
|
||||
'title' => trans('entities.recently_created_pages'),
|
||||
'pages' => $pages
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a listing of recently created pages
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
*/
|
||||
public function showRecentlyUpdated()
|
||||
{
|
||||
$pages = $this->entityRepo->getRecentlyUpdatedPaginated('page', 20)->setPath(baseUrl('/pages/recently-updated'));
|
||||
return view('pages/detailed-listing', [
|
||||
// TODO - Still exist?
|
||||
$pages = $this->pageRepo->getRecentlyUpdatedPaginated('page', 20)->setPath(baseUrl('/pages/recently-updated'));
|
||||
return view('pages.detailed-listing', [
|
||||
'title' => trans('entities.recently_updated_pages'),
|
||||
'pages' => $pages
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the Restrictions view.
|
||||
* @param string $bookSlug
|
||||
* @param string $pageSlug
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
*/
|
||||
public function showRestrict($bookSlug, $pageSlug)
|
||||
{
|
||||
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
|
||||
$this->checkOwnablePermission('restrictions-manage', $page);
|
||||
$roles = $this->userRepo->getRestrictableRoles();
|
||||
return view('pages/restrictions', [
|
||||
'page' => $page,
|
||||
'roles' => $roles
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the view to choose a new parent to move a page into.
|
||||
* @param string $bookSlug
|
||||
@@ -582,9 +557,10 @@ class PageController extends Controller
|
||||
*/
|
||||
public function showMove($bookSlug, $pageSlug)
|
||||
{
|
||||
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
|
||||
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
|
||||
$this->checkOwnablePermission('page-update', $page);
|
||||
return view('pages/move', [
|
||||
$this->checkOwnablePermission('page-delete', $page);
|
||||
return view('pages.move', [
|
||||
'book' => $page->book,
|
||||
'page' => $page
|
||||
]);
|
||||
@@ -600,8 +576,9 @@ class PageController extends Controller
|
||||
*/
|
||||
public function move($bookSlug, $pageSlug, Request $request)
|
||||
{
|
||||
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
|
||||
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
|
||||
$this->checkOwnablePermission('page-update', $page);
|
||||
$this->checkOwnablePermission('page-delete', $page);
|
||||
|
||||
$entitySelection = $request->get('entity_selection', null);
|
||||
if ($entitySelection === null || $entitySelection === '') {
|
||||
@@ -614,7 +591,7 @@ class PageController extends Controller
|
||||
|
||||
|
||||
try {
|
||||
$parent = $this->entityRepo->getById($entityType, $entityId);
|
||||
$parent = $this->pageRepo->getById($entityType, $entityId);
|
||||
} catch (\Exception $e) {
|
||||
session()->flash(trans('entities.selected_book_chapter_not_found'));
|
||||
return redirect()->back();
|
||||
@@ -622,7 +599,7 @@ class PageController extends Controller
|
||||
|
||||
$this->checkOwnablePermission('page-create', $parent);
|
||||
|
||||
$this->entityRepo->changePageParent($page, $parent);
|
||||
$this->pageRepo->changePageParent($page, $parent);
|
||||
Activity::add($page, 'page_move', $page->book->id);
|
||||
session()->flash('success', trans('entities.pages_move_success', ['parentName' => $parent->name]));
|
||||
|
||||
@@ -638,10 +615,10 @@ class PageController extends Controller
|
||||
*/
|
||||
public function showCopy($bookSlug, $pageSlug)
|
||||
{
|
||||
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
|
||||
$this->checkOwnablePermission('page-update', $page);
|
||||
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
|
||||
$this->checkOwnablePermission('page-view', $page);
|
||||
session()->flashInput(['name' => $page->name]);
|
||||
return view('pages/copy', [
|
||||
return view('pages.copy', [
|
||||
'book' => $page->book,
|
||||
'page' => $page
|
||||
]);
|
||||
@@ -657,8 +634,8 @@ class PageController extends Controller
|
||||
*/
|
||||
public function copy($bookSlug, $pageSlug, Request $request)
|
||||
{
|
||||
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
|
||||
$this->checkOwnablePermission('page-update', $page);
|
||||
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
|
||||
$this->checkOwnablePermission('page-view', $page);
|
||||
|
||||
$entitySelection = $request->get('entity_selection', null);
|
||||
if ($entitySelection === null || $entitySelection === '') {
|
||||
@@ -669,7 +646,7 @@ class PageController extends Controller
|
||||
$entityId = intval($stringExploded[1]);
|
||||
|
||||
try {
|
||||
$parent = $this->entityRepo->getById($entityType, $entityId);
|
||||
$parent = $this->pageRepo->getById($entityType, $entityId);
|
||||
} catch (\Exception $e) {
|
||||
session()->flash(trans('entities.selected_book_chapter_not_found'));
|
||||
return redirect()->back();
|
||||
@@ -678,7 +655,7 @@ class PageController extends Controller
|
||||
|
||||
$this->checkOwnablePermission('page-create', $parent);
|
||||
|
||||
$pageCopy = $this->entityRepo->copyPage($page, $parent, $request->get('name', ''));
|
||||
$pageCopy = $this->pageRepo->copyPage($page, $parent, $request->get('name', ''));
|
||||
|
||||
Activity::add($pageCopy, 'page_create', $pageCopy->book->id);
|
||||
session()->flash('success', trans('entities.pages_copy_success'));
|
||||
@@ -686,6 +663,24 @@ class PageController extends Controller
|
||||
return redirect($pageCopy->getUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the Permissions view.
|
||||
* @param string $bookSlug
|
||||
* @param string $pageSlug
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function showPermissions($bookSlug, $pageSlug)
|
||||
{
|
||||
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
|
||||
$this->checkOwnablePermission('restrictions-manage', $page);
|
||||
$roles = $this->userRepo->getRestrictableRoles();
|
||||
return view('pages.permissions', [
|
||||
'page' => $page,
|
||||
'roles' => $roles
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the permissions for this page.
|
||||
* @param string $bookSlug
|
||||
@@ -693,12 +688,13 @@ class PageController extends Controller
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
* @throws NotFoundException
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function restrict($bookSlug, $pageSlug, Request $request)
|
||||
public function permissions($bookSlug, $pageSlug, Request $request)
|
||||
{
|
||||
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
|
||||
$page = $this->pageRepo->getPageBySlug($pageSlug, $bookSlug);
|
||||
$this->checkOwnablePermission('restrictions-manage', $page);
|
||||
$this->entityRepo->updateEntityPermissionsFromRequest($request, $page);
|
||||
$this->pageRepo->updateEntityPermissionsFromRequest($request, $page);
|
||||
session()->flash('success', trans('entities.pages_permissions_success'));
|
||||
return redirect($page->getUrl());
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php namespace BookStack\Http\Controllers;
|
||||
|
||||
use BookStack\Auth\Permissions\PermissionsRepo;
|
||||
use BookStack\Exceptions\PermissionsException;
|
||||
use BookStack\Repos\PermissionsRepo;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class PermissionController extends Controller
|
||||
@@ -11,7 +11,7 @@ class PermissionController extends Controller
|
||||
|
||||
/**
|
||||
* PermissionController constructor.
|
||||
* @param PermissionsRepo $permissionsRepo
|
||||
* @param \BookStack\Auth\Permissions\PermissionsRepo $permissionsRepo
|
||||
*/
|
||||
public function __construct(PermissionsRepo $permissionsRepo)
|
||||
{
|
||||
@@ -26,7 +26,7 @@ class PermissionController extends Controller
|
||||
{
|
||||
$this->checkPermission('user-roles-manage');
|
||||
$roles = $this->permissionsRepo->getAllRoles();
|
||||
return view('settings/roles/index', ['roles' => $roles]);
|
||||
return view('settings.roles.index', ['roles' => $roles]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -36,7 +36,7 @@ class PermissionController extends Controller
|
||||
public function createRole()
|
||||
{
|
||||
$this->checkPermission('user-roles-manage');
|
||||
return view('settings/roles/create');
|
||||
return view('settings.roles.create');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,7 +70,7 @@ class PermissionController extends Controller
|
||||
if ($role->hidden) {
|
||||
throw new PermissionsException(trans('errors.role_cannot_be_edited'));
|
||||
}
|
||||
return view('settings/roles/edit', ['role' => $role]);
|
||||
return view('settings.roles.edit', ['role' => $role]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -106,7 +106,7 @@ class PermissionController extends Controller
|
||||
$roles = $this->permissionsRepo->getAllRolesExcept($role);
|
||||
$blankRole = $role->newInstance(['display_name' => trans('settings.role_delete_no_migration')]);
|
||||
$roles->prepend($blankRole);
|
||||
return view('settings/roles/delete', ['role' => $role, 'roles' => $roles]);
|
||||
return view('settings.roles.delete', ['role' => $role, 'roles' => $roles]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,34 +1,45 @@
|
||||
<?php namespace BookStack\Http\Controllers;
|
||||
|
||||
use BookStack\Repos\EntityRepo;
|
||||
use BookStack\Services\SearchService;
|
||||
use BookStack\Services\ViewService;
|
||||
use BookStack\Actions\ViewService;
|
||||
use BookStack\Entities\EntityContextManager;
|
||||
use BookStack\Entities\Repos\EntityRepo;
|
||||
use BookStack\Entities\SearchService;
|
||||
use BookStack\Exceptions\NotFoundException;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class SearchController extends Controller
|
||||
{
|
||||
protected $entityRepo;
|
||||
protected $viewService;
|
||||
protected $searchService;
|
||||
protected $entityContextManager;
|
||||
|
||||
/**
|
||||
* SearchController constructor.
|
||||
* @param EntityRepo $entityRepo
|
||||
* @param ViewService $viewService
|
||||
* @param SearchService $searchService
|
||||
* @param EntityContextManager $entityContextManager
|
||||
*/
|
||||
public function __construct(EntityRepo $entityRepo, ViewService $viewService, SearchService $searchService)
|
||||
{
|
||||
public function __construct(
|
||||
EntityRepo $entityRepo,
|
||||
ViewService $viewService,
|
||||
SearchService $searchService,
|
||||
EntityContextManager $entityContextManager
|
||||
) {
|
||||
$this->entityRepo = $entityRepo;
|
||||
$this->viewService = $viewService;
|
||||
$this->searchService = $searchService;
|
||||
$this->entityContextManager = $entityContextManager;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches all entities.
|
||||
* @param Request $request
|
||||
* @return \Illuminate\View\View
|
||||
* @return View
|
||||
* @internal param string $searchTerm
|
||||
*/
|
||||
public function search(Request $request)
|
||||
@@ -41,7 +52,7 @@ class SearchController extends Controller
|
||||
|
||||
$results = $this->searchService->searchEntities($searchTerm, 'all', $page, 20);
|
||||
|
||||
return view('search/all', [
|
||||
return view('search.all', [
|
||||
'entities' => $results['results'],
|
||||
'totalResults' => $results['total'],
|
||||
'searchTerm' => $searchTerm,
|
||||
@@ -55,28 +66,28 @@ class SearchController extends Controller
|
||||
* Searches all entities within a book.
|
||||
* @param Request $request
|
||||
* @param integer $bookId
|
||||
* @return \Illuminate\View\View
|
||||
* @return View
|
||||
* @internal param string $searchTerm
|
||||
*/
|
||||
public function searchBook(Request $request, $bookId)
|
||||
{
|
||||
$term = $request->get('term', '');
|
||||
$results = $this->searchService->searchBook($bookId, $term);
|
||||
return view('partials/entity-list', ['entities' => $results]);
|
||||
return view('partials.entity-list', ['entities' => $results]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches all entities within a chapter.
|
||||
* @param Request $request
|
||||
* @param integer $chapterId
|
||||
* @return \Illuminate\View\View
|
||||
* @return View
|
||||
* @internal param string $searchTerm
|
||||
*/
|
||||
public function searchChapter(Request $request, $chapterId)
|
||||
{
|
||||
$term = $request->get('term', '');
|
||||
$results = $this->searchService->searchChapter($chapterId, $term);
|
||||
return view('partials/entity-list', ['entities' => $results]);
|
||||
return view('partials.entity-list', ['entities' => $results]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,21 +98,64 @@ class SearchController extends Controller
|
||||
*/
|
||||
public function searchEntitiesAjax(Request $request)
|
||||
{
|
||||
$entityTypes = $request->filled('types') ? collect(explode(',', $request->get('types'))) : collect(['page', 'chapter', 'book']);
|
||||
$entityTypes = $request->filled('types') ? explode(',', $request->get('types')) : ['page', 'chapter', 'book'];
|
||||
$searchTerm = $request->get('term', false);
|
||||
$permission = $request->get('permission', 'view');
|
||||
|
||||
// Search for entities otherwise show most popular
|
||||
if ($searchTerm !== false) {
|
||||
$searchTerm .= ' {type:'. implode('|', $entityTypes->toArray()) .'}';
|
||||
$searchTerm .= ' {type:'. implode('|', $entityTypes) .'}';
|
||||
$entities = $this->searchService->searchEntities($searchTerm, 'all', 1, 20, $permission)['results'];
|
||||
} else {
|
||||
$entityNames = $entityTypes->map(function ($type) {
|
||||
return 'BookStack\\' . ucfirst($type);
|
||||
})->toArray();
|
||||
$entities = $this->viewService->getPopular(20, 0, $entityNames, $permission);
|
||||
$entities = $this->viewService->getPopular(20, 0, $entityTypes, $permission);
|
||||
}
|
||||
|
||||
return view('search/entity-ajax-list', ['entities' => $entities]);
|
||||
return view('search.entity-ajax-list', ['entities' => $entities]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search siblings items in the system.
|
||||
* @param Request $request
|
||||
* @return Factory|View|mixed
|
||||
*/
|
||||
public function searchSiblings(Request $request)
|
||||
{
|
||||
$type = $request->get('entity_type', null);
|
||||
$id = $request->get('entity_id', null);
|
||||
|
||||
$entity = $this->entityRepo->getById($type, $id);
|
||||
if (!$entity) {
|
||||
return $this->jsonError(trans('errors.entity_not_found'), 404);
|
||||
}
|
||||
|
||||
$entities = [];
|
||||
|
||||
// Page in chapter
|
||||
if ($entity->isA('page') && $entity->chapter) {
|
||||
$entities = $this->entityRepo->getChapterChildren($entity->chapter);
|
||||
}
|
||||
|
||||
// Page in book or chapter
|
||||
if (($entity->isA('page') && !$entity->chapter) || $entity->isA('chapter')) {
|
||||
$entities = $this->entityRepo->getBookDirectChildren($entity->book);
|
||||
}
|
||||
|
||||
// Book
|
||||
// Gets just the books in a shelf if shelf is in context
|
||||
if ($entity->isA('book')) {
|
||||
$contextShelf = $this->entityContextManager->getContextualShelfForBook($entity);
|
||||
if ($contextShelf) {
|
||||
$entities = $this->entityRepo->getBookshelfChildren($contextShelf);
|
||||
} else {
|
||||
$entities = $this->entityRepo->getAll('book');
|
||||
}
|
||||
}
|
||||
|
||||
// Shelve
|
||||
if ($entity->isA('bookshelf')) {
|
||||
$entities = $this->entityRepo->getAll('bookshelf');
|
||||
}
|
||||
|
||||
return view('partials.entity-list-basic', ['entities' => $entities, 'style' => 'compact']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,27 @@
|
||||
<?php namespace BookStack\Http\Controllers;
|
||||
|
||||
use BookStack\Services\ImageService;
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Uploads\ImageRepo;
|
||||
use BookStack\Uploads\ImageService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Setting;
|
||||
|
||||
class SettingController extends Controller
|
||||
{
|
||||
protected $imageRepo;
|
||||
|
||||
/**
|
||||
* SettingController constructor.
|
||||
* @param $imageRepo
|
||||
*/
|
||||
public function __construct(ImageRepo $imageRepo)
|
||||
{
|
||||
$this->imageRepo = $imageRepo;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Display a listing of the settings.
|
||||
* @return Response
|
||||
@@ -19,7 +34,10 @@ class SettingController extends Controller
|
||||
// Get application version
|
||||
$version = trim(file_get_contents(base_path('version')));
|
||||
|
||||
return view('settings/index', ['version' => $version]);
|
||||
return view('settings.index', [
|
||||
'version' => $version,
|
||||
'guestUser' => User::getDefault()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -31,6 +49,9 @@ class SettingController extends Controller
|
||||
{
|
||||
$this->preventAccessForDemoUsers();
|
||||
$this->checkPermission('settings-manage');
|
||||
$this->validate($request, [
|
||||
'app_logo' => $this->imageRepo->getImageValidationRules(),
|
||||
]);
|
||||
|
||||
// Cycles through posted settings and update them
|
||||
foreach ($request->all() as $name => $value) {
|
||||
@@ -38,7 +59,21 @@ class SettingController extends Controller
|
||||
continue;
|
||||
}
|
||||
$key = str_replace('setting-', '', trim($name));
|
||||
Setting::put($key, $value);
|
||||
setting()->put($key, $value);
|
||||
}
|
||||
|
||||
// Update logo image if set
|
||||
if ($request->has('app_logo')) {
|
||||
$logoFile = $request->file('app_logo');
|
||||
$this->imageRepo->destroyByType('system');
|
||||
$image = $this->imageRepo->saveNew($logoFile, 'system', 0, null, 86);
|
||||
setting()->put('app-logo', $image->url);
|
||||
}
|
||||
|
||||
// Clear logo image if requested
|
||||
if ($request->get('app_logo_reset', null)) {
|
||||
$this->imageRepo->destroyByType('system');
|
||||
setting()->remove('app-logo');
|
||||
}
|
||||
|
||||
session()->flash('success', trans('settings.settings_save_success'));
|
||||
@@ -57,7 +92,7 @@ class SettingController extends Controller
|
||||
// Get application version
|
||||
$version = trim(file_get_contents(base_path('version')));
|
||||
|
||||
return view('settings/maintenance', ['version' => $version]);
|
||||
return view('settings.maintenance', ['version' => $version]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php namespace BookStack\Http\Controllers;
|
||||
|
||||
use BookStack\Repos\TagRepo;
|
||||
use BookStack\Actions\TagRepo;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TagController extends Controller
|
||||
|
||||
@@ -1,27 +1,31 @@
|
||||
<?php namespace BookStack\Http\Controllers;
|
||||
|
||||
use Exception;
|
||||
use BookStack\Auth\Access\SocialAuthService;
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Auth\UserRepo;
|
||||
use BookStack\Exceptions\UserUpdateException;
|
||||
use BookStack\Uploads\ImageRepo;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use BookStack\Repos\UserRepo;
|
||||
use BookStack\Services\SocialAuthService;
|
||||
use BookStack\User;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
|
||||
protected $user;
|
||||
protected $userRepo;
|
||||
protected $imageRepo;
|
||||
|
||||
/**
|
||||
* UserController constructor.
|
||||
* @param User $user
|
||||
* @param User $user
|
||||
* @param UserRepo $userRepo
|
||||
* @param ImageRepo $imageRepo
|
||||
*/
|
||||
public function __construct(User $user, UserRepo $userRepo)
|
||||
public function __construct(User $user, UserRepo $userRepo, ImageRepo $imageRepo)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->userRepo = $userRepo;
|
||||
$this->imageRepo = $imageRepo;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
@@ -41,7 +45,7 @@ class UserController extends Controller
|
||||
$users = $this->userRepo->getAllUsersPaginatedAndSorted(20, $listDetails);
|
||||
$this->setPageTitle(trans('settings.users'));
|
||||
$users->appends($listDetails);
|
||||
return view('users/index', ['users' => $users, 'listDetails' => $listDetails]);
|
||||
return view('users.index', ['users' => $users, 'listDetails' => $listDetails]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,13 +57,14 @@ class UserController extends Controller
|
||||
$this->checkPermission('users-manage');
|
||||
$authMethod = config('auth.method');
|
||||
$roles = $this->userRepo->getAllRoles();
|
||||
return view('users/create', ['authMethod' => $authMethod, 'roles' => $roles]);
|
||||
return view('users.create', ['authMethod' => $authMethod, 'roles' => $roles]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created user in storage.
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
* @throws UserUpdateException
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
@@ -90,10 +95,10 @@ class UserController extends Controller
|
||||
|
||||
if ($request->filled('roles')) {
|
||||
$roles = $request->get('roles');
|
||||
$user->roles()->sync($roles);
|
||||
$this->userRepo->setUserRoles($user, $roles);
|
||||
}
|
||||
|
||||
$this->userRepo->downloadGravatarToUserAvatar($user);
|
||||
$this->userRepo->downloadAndAssignUserAvatar($user);
|
||||
|
||||
return redirect('/settings/users');
|
||||
}
|
||||
@@ -101,14 +106,12 @@ class UserController extends Controller
|
||||
/**
|
||||
* Show the form for editing the specified user.
|
||||
* @param int $id
|
||||
* @param SocialAuthService $socialAuthService
|
||||
* @param \BookStack\Auth\Access\SocialAuthService $socialAuthService
|
||||
* @return Response
|
||||
*/
|
||||
public function edit($id, SocialAuthService $socialAuthService)
|
||||
{
|
||||
$this->checkPermissionOr('users-manage', function () use ($id) {
|
||||
return $this->currentUser->id == $id;
|
||||
});
|
||||
$this->checkPermissionOrCurrentUser('users-manage', $id);
|
||||
|
||||
$user = $this->user->findOrFail($id);
|
||||
|
||||
@@ -117,37 +120,38 @@ class UserController extends Controller
|
||||
$activeSocialDrivers = $socialAuthService->getActiveDrivers();
|
||||
$this->setPageTitle(trans('settings.user_profile'));
|
||||
$roles = $this->userRepo->getAllRoles();
|
||||
return view('users/edit', ['user' => $user, 'activeSocialDrivers' => $activeSocialDrivers, 'authMethod' => $authMethod, 'roles' => $roles]);
|
||||
return view('users.edit', ['user' => $user, 'activeSocialDrivers' => $activeSocialDrivers, 'authMethod' => $authMethod, 'roles' => $roles]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified user in storage.
|
||||
* @param Request $request
|
||||
* @param int $id
|
||||
* @param Request $request
|
||||
* @param int $id
|
||||
* @return Response
|
||||
* @throws UserUpdateException
|
||||
* @throws \BookStack\Exceptions\ImageUploadException
|
||||
*/
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$this->preventAccessForDemoUsers();
|
||||
$this->checkPermissionOr('users-manage', function () use ($id) {
|
||||
return $this->currentUser->id == $id;
|
||||
});
|
||||
$this->checkPermissionOrCurrentUser('users-manage', $id);
|
||||
|
||||
$this->validate($request, [
|
||||
'name' => 'min:2',
|
||||
'email' => 'min:2|email|unique:users,email,' . $id,
|
||||
'password' => 'min:5|required_with:password_confirm',
|
||||
'password-confirm' => 'same:password|required_with:password',
|
||||
'setting' => 'array'
|
||||
'setting' => 'array',
|
||||
'profile_image' => $this->imageRepo->getImageValidationRules(),
|
||||
]);
|
||||
|
||||
$user = $this->user->findOrFail($id);
|
||||
$user = $this->userRepo->getById($id);
|
||||
$user->fill($request->all());
|
||||
|
||||
// Role updates
|
||||
if (userCan('users-manage') && $request->filled('roles')) {
|
||||
$roles = $request->get('roles');
|
||||
$user->roles()->sync($roles);
|
||||
$this->userRepo->setUserRoles($user, $roles);
|
||||
}
|
||||
|
||||
// Password updates
|
||||
@@ -168,10 +172,23 @@ class UserController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
// Save profile image if in request
|
||||
if ($request->has('profile_image')) {
|
||||
$imageUpload = $request->file('profile_image');
|
||||
$this->imageRepo->destroyImage($user->avatar);
|
||||
$image = $this->imageRepo->saveNew($imageUpload, 'user', $user->id);
|
||||
$user->image_id = $image->id;
|
||||
}
|
||||
|
||||
// Delete the profile image if set to
|
||||
if ($request->has('profile_image_reset')) {
|
||||
$this->imageRepo->destroyImage($user->avatar);
|
||||
}
|
||||
|
||||
$user->save();
|
||||
session()->flash('success', trans('settings.users_edit_success'));
|
||||
|
||||
$redirectUrl = userCan('users-manage') ? '/settings/users' : '/settings/users/' . $user->id;
|
||||
$redirectUrl = userCan('users-manage') ? '/settings/users' : ('/settings/users/' . $user->id);
|
||||
return redirect($redirectUrl);
|
||||
}
|
||||
|
||||
@@ -182,26 +199,23 @@ class UserController extends Controller
|
||||
*/
|
||||
public function delete($id)
|
||||
{
|
||||
$this->checkPermissionOr('users-manage', function () use ($id) {
|
||||
return $this->currentUser->id == $id;
|
||||
});
|
||||
$this->checkPermissionOrCurrentUser('users-manage', $id);
|
||||
|
||||
$user = $this->user->findOrFail($id);
|
||||
$user = $this->userRepo->getById($id);
|
||||
$this->setPageTitle(trans('settings.users_delete_named', ['userName' => $user->name]));
|
||||
return view('users/delete', ['user' => $user]);
|
||||
return view('users.delete', ['user' => $user]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified user from storage.
|
||||
* @param int $id
|
||||
* @return Response
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function destroy($id)
|
||||
{
|
||||
$this->preventAccessForDemoUsers();
|
||||
$this->checkPermissionOr('users-manage', function () use ($id) {
|
||||
return $this->currentUser->id == $id;
|
||||
});
|
||||
$this->checkPermissionOrCurrentUser('users-manage', $id);
|
||||
|
||||
$user = $this->userRepo->getById($id);
|
||||
|
||||
@@ -229,10 +243,12 @@ class UserController extends Controller
|
||||
public function showProfilePage($id)
|
||||
{
|
||||
$user = $this->userRepo->getById($id);
|
||||
|
||||
$userActivity = $this->userRepo->getActivity($user);
|
||||
$recentlyCreated = $this->userRepo->getRecentlyCreated($user, 5, 0);
|
||||
$assetCounts = $this->userRepo->getAssetCounts($user);
|
||||
return view('users/profile', [
|
||||
|
||||
return view('users.profile', [
|
||||
'user' => $user,
|
||||
'activity' => $userActivity,
|
||||
'recentlyCreated' => $recentlyCreated,
|
||||
@@ -248,19 +264,7 @@ class UserController extends Controller
|
||||
*/
|
||||
public function switchBookView($id, Request $request)
|
||||
{
|
||||
$this->checkPermissionOr('users-manage', function () use ($id) {
|
||||
return $this->currentUser->id == $id;
|
||||
});
|
||||
|
||||
$viewType = $request->get('view_type');
|
||||
if (!in_array($viewType, ['grid', 'list'])) {
|
||||
$viewType = 'list';
|
||||
}
|
||||
|
||||
$user = $this->user->findOrFail($id);
|
||||
setting()->putUser($user, 'books_view_type', $viewType);
|
||||
|
||||
return redirect()->back(302, [], "/settings/users/$id");
|
||||
return $this->switchViewType($id, $request, 'books');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -271,18 +275,97 @@ class UserController extends Controller
|
||||
*/
|
||||
public function switchShelfView($id, Request $request)
|
||||
{
|
||||
$this->checkPermissionOr('users-manage', function () use ($id) {
|
||||
return $this->currentUser->id == $id;
|
||||
});
|
||||
return $this->switchViewType($id, $request, 'bookshelves');
|
||||
}
|
||||
|
||||
/**
|
||||
* For a type of list, switch with stored view type for a user.
|
||||
* @param integer $userId
|
||||
* @param Request $request
|
||||
* @param string $listName
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
protected function switchViewType($userId, Request $request, string $listName)
|
||||
{
|
||||
$this->checkPermissionOrCurrentUser('users-manage', $userId);
|
||||
|
||||
$viewType = $request->get('view_type');
|
||||
if (!in_array($viewType, ['grid', 'list'])) {
|
||||
$viewType = 'list';
|
||||
}
|
||||
|
||||
$user = $this->user->findOrFail($id);
|
||||
setting()->putUser($user, 'bookshelves_view_type', $viewType);
|
||||
$user = $this->userRepo->getById($userId);
|
||||
$key = $listName . '_view_type';
|
||||
setting()->putUser($user, $key, $viewType);
|
||||
|
||||
return redirect()->back(302, [], "/settings/users/$id");
|
||||
return redirect()->back(302, [], "/settings/users/$userId");
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the stored sort type for a particular view.
|
||||
* @param string $id
|
||||
* @param string $type
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function changeSort(string $id, string $type, Request $request)
|
||||
{
|
||||
$validSortTypes = ['books', 'bookshelves'];
|
||||
if (!in_array($type, $validSortTypes)) {
|
||||
return redirect()->back(500);
|
||||
}
|
||||
return $this->changeListSort($id, $request, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the stored section expansion preference for the given user.
|
||||
* @param string $id
|
||||
* @param string $key
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function updateExpansionPreference(string $id, string $key, Request $request)
|
||||
{
|
||||
$this->checkPermissionOrCurrentUser('users-manage', $id);
|
||||
$keyWhitelist = ['home-details'];
|
||||
if (!in_array($key, $keyWhitelist)) {
|
||||
return response("Invalid key", 500);
|
||||
}
|
||||
|
||||
$newState = $request->get('expand', 'false');
|
||||
|
||||
$user = $this->user->findOrFail($id);
|
||||
setting()->putUser($user, 'section_expansion#' . $key, $newState);
|
||||
return response("", 204);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changed the stored preference for a list sort order.
|
||||
* @param int $userId
|
||||
* @param Request $request
|
||||
* @param string $listName
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
protected function changeListSort(int $userId, Request $request, string $listName)
|
||||
{
|
||||
$this->checkPermissionOrCurrentUser('users-manage', $userId);
|
||||
|
||||
$sort = $request->get('sort');
|
||||
if (!in_array($sort, ['name', 'created_at', 'updated_at'])) {
|
||||
$sort = 'name';
|
||||
}
|
||||
|
||||
$order = $request->get('order');
|
||||
if (!in_array($order, ['asc', 'desc'])) {
|
||||
$order = 'asc';
|
||||
}
|
||||
|
||||
$user = $this->user->findOrFail($userId);
|
||||
$sortKey = $listName . '_sort';
|
||||
$orderKey = $listName . '_sort_order';
|
||||
setting()->putUser($user, $sortKey, $sort);
|
||||
setting()->putUser($user, $orderKey, $order);
|
||||
|
||||
return redirect()->back(302, [], "/settings/users/$userId");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ class Authenticate
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->auth->guest() && !setting('app-public')) {
|
||||
if (!hasAppAccess()) {
|
||||
if ($request->ajax()) {
|
||||
return response('Unauthorized.', 401);
|
||||
} else {
|
||||
|
||||
@@ -7,8 +7,40 @@ use Illuminate\Http\Request;
|
||||
class Localization
|
||||
{
|
||||
|
||||
/**
|
||||
* Array of right-to-left locales
|
||||
* @var array
|
||||
*/
|
||||
protected $rtlLocales = ['ar'];
|
||||
|
||||
/**
|
||||
* Map of BookStack locale names to best-estimate system locale names.
|
||||
* @var array
|
||||
*/
|
||||
protected $localeMap = [
|
||||
'ar' => 'ar',
|
||||
'de' => 'de_DE',
|
||||
'de_informal' => 'de_DE',
|
||||
'en' => 'en_GB',
|
||||
'es' => 'es_ES',
|
||||
'es_AR' => 'es_AR',
|
||||
'fr' => 'fr_FR',
|
||||
'it' => 'it_IT',
|
||||
'ja' => 'ja',
|
||||
'kr' => 'ko_KR',
|
||||
'nl' => 'nl_NL',
|
||||
'pl' => 'pl_PL',
|
||||
'pt_BR' => 'pt_BR',
|
||||
'pt_BR' => 'pt_BR',
|
||||
'ru' => 'ru',
|
||||
'sk' => 'sk_SK',
|
||||
'sv' => 'sv_SE',
|
||||
'uk' => 'uk_UA',
|
||||
'uk' => 'uk_UA',
|
||||
'zh_CN' => 'zh_CN',
|
||||
'zh_TW' => 'zh_TW',
|
||||
];
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
@@ -19,6 +51,7 @@ class Localization
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
$defaultLang = config('app.locale');
|
||||
config()->set('app.default_locale', $defaultLang);
|
||||
|
||||
if (user()->isDefault() && config('app.auto_detect_locale')) {
|
||||
$locale = $this->autoDetectLocale($request, $defaultLang);
|
||||
@@ -33,6 +66,7 @@ class Localization
|
||||
|
||||
app()->setLocale($locale);
|
||||
Carbon::setLocale($locale);
|
||||
$this->setSystemDateLocale($locale);
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
@@ -53,4 +87,18 @@ class Localization
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the system date locale for localized date formatting.
|
||||
* Will try both the standard locale name and the UTF8 variant.
|
||||
* @param string $locale
|
||||
*/
|
||||
protected function setSystemDateLocale(string $locale)
|
||||
{
|
||||
$systemLocale = $this->localeMap[$locale] ?? $locale;
|
||||
$set = setlocale(LC_TIME, $systemLocale);
|
||||
if ($set === false) {
|
||||
setlocale(LC_TIME, $systemLocale . '.utf8');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
namespace BookStack\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Fideloper\Proxy\TrustProxies as Middleware;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TrustProxies extends Middleware
|
||||
{
|
||||
|
||||
@@ -1,17 +1,7 @@
|
||||
<?php
|
||||
<?php namespace BookStack\Notifications;
|
||||
|
||||
namespace BookStack\Notifications;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
|
||||
class ConfirmEmail extends Notification implements ShouldQueue
|
||||
class ConfirmEmail extends MailNotification
|
||||
{
|
||||
|
||||
use Queueable;
|
||||
|
||||
public $token;
|
||||
|
||||
/**
|
||||
@@ -23,17 +13,6 @@ class ConfirmEmail extends Notification implements ShouldQueue
|
||||
$this->token = $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification's delivery channels.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function via($notifiable)
|
||||
{
|
||||
return ['mail'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mail representation of the notification.
|
||||
*
|
||||
@@ -43,10 +22,10 @@ class ConfirmEmail extends Notification implements ShouldQueue
|
||||
public function toMail($notifiable)
|
||||
{
|
||||
$appName = ['appName' => setting('app-name')];
|
||||
return (new MailMessage)
|
||||
->subject(trans('auth.email_confirm_subject', $appName))
|
||||
->greeting(trans('auth.email_confirm_greeting', $appName))
|
||||
->line(trans('auth.email_confirm_text'))
|
||||
->action(trans('auth.email_confirm_action'), baseUrl('/register/confirm/' . $this->token));
|
||||
return $this->newMailMessage()
|
||||
->subject(trans('auth.email_confirm_subject', $appName))
|
||||
->greeting(trans('auth.email_confirm_greeting', $appName))
|
||||
->line(trans('auth.email_confirm_text'))
|
||||
->action(trans('auth.email_confirm_action'), baseUrl('/register/confirm/' . $this->token));
|
||||
}
|
||||
}
|
||||
|
||||
34
app/Notifications/MailNotification.php
Normal file
34
app/Notifications/MailNotification.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php namespace BookStack\Notifications;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class MailNotification extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
/**
|
||||
* Get the notification's channels.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array|string
|
||||
*/
|
||||
public function via($notifiable)
|
||||
{
|
||||
return ['mail'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new mail message.
|
||||
* @return MailMessage
|
||||
*/
|
||||
protected function newMailMessage()
|
||||
{
|
||||
return (new MailMessage)->view([
|
||||
'html' => 'vendor.notifications.email',
|
||||
'text' => 'vendor.notifications.email-plain'
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,6 @@
|
||||
<?php
|
||||
<?php namespace BookStack\Notifications;
|
||||
|
||||
namespace BookStack\Notifications;
|
||||
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
|
||||
class ResetPassword extends Notification
|
||||
class ResetPassword extends MailNotification
|
||||
{
|
||||
/**
|
||||
* The password reset token.
|
||||
@@ -24,17 +19,6 @@ class ResetPassword extends Notification
|
||||
$this->token = $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification's channels.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array|string
|
||||
*/
|
||||
public function via($notifiable)
|
||||
{
|
||||
return ['mail'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the mail representation of the notification.
|
||||
*
|
||||
@@ -42,7 +26,7 @@ class ResetPassword extends Notification
|
||||
*/
|
||||
public function toMail()
|
||||
{
|
||||
return (new MailMessage)
|
||||
return $this->newMailMessage()
|
||||
->subject(trans('auth.email_reset_subject', ['appName' => setting('app-name')]))
|
||||
->line(trans('auth.email_reset_text'))
|
||||
->action(trans('auth.reset_password'), baseUrl('password/reset/' . $this->token))
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php namespace BookStack;
|
||||
|
||||
use BookStack\Auth\User;
|
||||
|
||||
abstract class Ownable extends Model
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
<?php namespace BookStack\Providers;
|
||||
|
||||
use BookStack\Services\SettingService;
|
||||
use BookStack\Setting;
|
||||
use Blade;
|
||||
use BookStack\Entities\Book;
|
||||
use BookStack\Entities\Bookshelf;
|
||||
use BookStack\Entities\BreadcrumbsViewComposer;
|
||||
use BookStack\Entities\Chapter;
|
||||
use BookStack\Entities\Page;
|
||||
use BookStack\Settings\Setting;
|
||||
use BookStack\Settings\SettingService;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Schema;
|
||||
use Validator;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
@@ -15,17 +25,34 @@ class AppServiceProvider extends ServiceProvider
|
||||
public function boot()
|
||||
{
|
||||
// Custom validation methods
|
||||
Validator::extend('is_image', function ($attribute, $value, $parameters, $validator) {
|
||||
$imageMimes = ['image/png', 'image/bmp', 'image/gif', 'image/jpeg', 'image/jpg', 'image/tiff', 'image/webp'];
|
||||
return in_array($value->getMimeType(), $imageMimes);
|
||||
Validator::extend('image_extension', function ($attribute, $value, $parameters, $validator) {
|
||||
$validImageExtensions = ['png', 'jpg', 'jpeg', 'bmp', 'gif', 'tiff', 'webp'];
|
||||
return in_array(strtolower($value->getClientOriginalExtension()), $validImageExtensions);
|
||||
});
|
||||
|
||||
\Blade::directive('icon', function ($expression) {
|
||||
Validator::extend('no_double_extension', function ($attribute, $value, $parameters, $validator) {
|
||||
$uploadName = $value->getClientOriginalName();
|
||||
return substr_count($uploadName, '.') < 2;
|
||||
});
|
||||
|
||||
// Custom blade view directives
|
||||
Blade::directive('icon', function ($expression) {
|
||||
return "<?php echo icon($expression); ?>";
|
||||
});
|
||||
|
||||
// Allow longer string lengths after upgrade to utf8mb4
|
||||
\Schema::defaultStringLength(191);
|
||||
Schema::defaultStringLength(191);
|
||||
|
||||
// Set morph-map due to namespace changes
|
||||
Relation::morphMap([
|
||||
'BookStack\\Bookshelf' => Bookshelf::class,
|
||||
'BookStack\\Book' => Book::class,
|
||||
'BookStack\\Chapter' => Chapter::class,
|
||||
'BookStack\\Page' => Page::class,
|
||||
]);
|
||||
|
||||
// View Composers
|
||||
View::composer('partials.breadcrumbs', BreadcrumbsViewComposer::class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace BookStack\Providers;
|
||||
|
||||
use Auth;
|
||||
use BookStack\Services\LdapService;
|
||||
use BookStack\Auth\Access\LdapService;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AuthServiceProvider extends ServiceProvider
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace BookStack\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Support\Facades\Broadcast;
|
||||
|
||||
class BroadcastServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
||||
@@ -2,19 +2,11 @@
|
||||
|
||||
namespace BookStack\Providers;
|
||||
|
||||
use BookStack\Activity;
|
||||
use BookStack\Image;
|
||||
use BookStack\Services\ImageService;
|
||||
use BookStack\Services\PermissionService;
|
||||
use BookStack\Services\ViewService;
|
||||
use BookStack\Setting;
|
||||
use BookStack\View;
|
||||
use Illuminate\Contracts\Cache\Repository;
|
||||
use Illuminate\Contracts\Filesystem\Factory;
|
||||
use BookStack\Actions\ActivityService;
|
||||
use BookStack\Actions\ViewService;
|
||||
use BookStack\Settings\SettingService;
|
||||
use BookStack\Uploads\ImageService;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use BookStack\Services\ActivityService;
|
||||
use BookStack\Services\SettingService;
|
||||
use Intervention\Image\ImageManager;
|
||||
|
||||
class CustomFacadeProvider extends ServiceProvider
|
||||
{
|
||||
@@ -36,33 +28,19 @@ class CustomFacadeProvider extends ServiceProvider
|
||||
public function register()
|
||||
{
|
||||
$this->app->bind('activity', function () {
|
||||
return new ActivityService(
|
||||
$this->app->make(Activity::class),
|
||||
$this->app->make(PermissionService::class)
|
||||
);
|
||||
return $this->app->make(ActivityService::class);
|
||||
});
|
||||
|
||||
$this->app->bind('views', function () {
|
||||
return new ViewService(
|
||||
$this->app->make(View::class),
|
||||
$this->app->make(PermissionService::class)
|
||||
);
|
||||
return $this->app->make(ViewService::class);
|
||||
});
|
||||
|
||||
$this->app->bind('setting', function () {
|
||||
return new SettingService(
|
||||
$this->app->make(Setting::class),
|
||||
$this->app->make(Repository::class)
|
||||
);
|
||||
return $this->app->make(SettingService::class);
|
||||
});
|
||||
|
||||
$this->app->bind('images', function () {
|
||||
return new ImageService(
|
||||
$this->app->make(Image::class),
|
||||
$this->app->make(ImageManager::class),
|
||||
$this->app->make(Factory::class),
|
||||
$this->app->make(Repository::class)
|
||||
);
|
||||
return $this->app->make(ImageService::class);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace BookStack\Providers;
|
||||
|
||||
use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
|
||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||
use SocialiteProviders\Manager\SocialiteWasCalled;
|
||||
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
|
||||
namespace BookStack\Providers;
|
||||
|
||||
use BookStack\Role;
|
||||
use BookStack\Services\LdapService;
|
||||
use BookStack\User;
|
||||
use BookStack\Auth\Access\LdapService;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Contracts\Auth\UserProvider;
|
||||
|
||||
@@ -19,7 +17,7 @@ class LdapUserProvider implements UserProvider
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* @var LdapService
|
||||
* @var \BookStack\Auth\LdapService
|
||||
*/
|
||||
protected $ldapService;
|
||||
|
||||
@@ -27,7 +25,7 @@ class LdapUserProvider implements UserProvider
|
||||
/**
|
||||
* LdapUserProvider constructor.
|
||||
* @param $model
|
||||
* @param LdapService $ldapService
|
||||
* @param \BookStack\Auth\LdapService $ldapService
|
||||
*/
|
||||
public function __construct($model, LdapService $ldapService)
|
||||
{
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace BookStack\Providers;
|
||||
|
||||
use Illuminate\Routing\Router;
|
||||
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
||||
use Route;
|
||||
|
||||
|
||||
31
app/Providers/TranslationServiceProvider.php
Normal file
31
app/Providers/TranslationServiceProvider.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php namespace BookStack\Providers;
|
||||
|
||||
use BookStack\Translation\Translator;
|
||||
|
||||
class TranslationServiceProvider extends \Illuminate\Translation\TranslationServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register the service provider.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$this->registerLoader();
|
||||
|
||||
$this->app->singleton('translator', function ($app) {
|
||||
$loader = $app['translation.loader'];
|
||||
|
||||
// When registering the translator component, we'll need to set the default
|
||||
// locale as well as the fallback locale. So, we'll grab the application
|
||||
// configuration so we can easily get both of these values from there.
|
||||
$locale = $app['config']['app.locale'];
|
||||
|
||||
$trans = new Translator($loader, $locale);
|
||||
|
||||
$trans->setFallback($app['config']['app.fallback_locale']);
|
||||
|
||||
return $trans;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
<?php namespace BookStack;
|
||||
<?php namespace BookStack\Settings;
|
||||
|
||||
use BookStack\Model;
|
||||
|
||||
class Setting extends Model
|
||||
{
|
||||
@@ -1,15 +1,11 @@
|
||||
<?php namespace BookStack\Services;
|
||||
<?php namespace BookStack\Settings;
|
||||
|
||||
use BookStack\Setting;
|
||||
use BookStack\User;
|
||||
use Illuminate\Contracts\Cache\Repository as Cache;
|
||||
|
||||
/**
|
||||
* Class SettingService
|
||||
*
|
||||
* The settings are a simple key-value database store.
|
||||
*
|
||||
* @package BookStack\Services
|
||||
* For non-authenticated users, user settings are stored via the session instead.
|
||||
*/
|
||||
class SettingService
|
||||
{
|
||||
@@ -43,6 +39,7 @@ class SettingService
|
||||
if ($default === false) {
|
||||
$default = config('setting-defaults.' . $key, false);
|
||||
}
|
||||
|
||||
if (isset($this->localCache[$key])) {
|
||||
return $this->localCache[$key];
|
||||
}
|
||||
@@ -53,18 +50,45 @@ class SettingService
|
||||
return $formatted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value from the session instead of the main store option.
|
||||
* @param $key
|
||||
* @param bool $default
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getFromSession($key, $default = false)
|
||||
{
|
||||
$value = session()->get($key, $default);
|
||||
$formatted = $this->formatValue($value, $default);
|
||||
return $formatted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a user-specific setting from the database or cache.
|
||||
* @param User $user
|
||||
* @param \BookStack\Auth\User $user
|
||||
* @param $key
|
||||
* @param bool $default
|
||||
* @return bool|string
|
||||
*/
|
||||
public function getUser($user, $key, $default = false)
|
||||
{
|
||||
if ($user->isDefault()) {
|
||||
return $this->getFromSession($key, $default);
|
||||
}
|
||||
return $this->get($this->userKey($user->id, $key), $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value for the current logged-in user.
|
||||
* @param $key
|
||||
* @param bool $default
|
||||
* @return bool|string
|
||||
*/
|
||||
public function getForCurrentUser($key, $default = false)
|
||||
{
|
||||
return $this->getUser(user(), $key, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a setting value from the cache or database.
|
||||
* Looks at the system defaults if not cached or in database.
|
||||
@@ -174,13 +198,16 @@ class SettingService
|
||||
|
||||
/**
|
||||
* Put a user-specific setting into the database.
|
||||
* @param User $user
|
||||
* @param \BookStack\Auth\User $user
|
||||
* @param $key
|
||||
* @param $value
|
||||
* @return bool
|
||||
*/
|
||||
public function putUser($user, $key, $value)
|
||||
{
|
||||
if ($user->isDefault()) {
|
||||
return session()->put($key, $value);
|
||||
}
|
||||
return $this->put($this->userKey($user->id, $key), $value);
|
||||
}
|
||||
|
||||
72
app/Translation/Translator.php
Normal file
72
app/Translation/Translator.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php namespace BookStack\Translation;
|
||||
|
||||
class Translator extends \Illuminate\Translation\Translator
|
||||
{
|
||||
|
||||
/**
|
||||
* Mapping of locales to their base locales
|
||||
* @var array
|
||||
*/
|
||||
protected $baseLocaleMap = [
|
||||
'de_informal' => 'de',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the translation for a given key.
|
||||
*
|
||||
* @param string $key
|
||||
* @param array $replace
|
||||
* @param string $locale
|
||||
* @return string|array|null
|
||||
*/
|
||||
public function trans($key, array $replace = [], $locale = null)
|
||||
{
|
||||
$translation = $this->get($key, $replace, $locale);
|
||||
|
||||
if (is_array($translation)) {
|
||||
$translation = $this->mergeBackupTranslations($translation, $key, $locale);
|
||||
}
|
||||
|
||||
return $translation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the fallback translations, and base translations if existing,
|
||||
* into the provided core key => value array of translations content.
|
||||
* @param array $translationArray
|
||||
* @param string $key
|
||||
* @param null $locale
|
||||
* @return array
|
||||
*/
|
||||
protected function mergeBackupTranslations(array $translationArray, string $key, $locale = null)
|
||||
{
|
||||
$fallback = $this->get($key, [], $this->fallback);
|
||||
$baseLocale = $this->getBaseLocale($locale ?? $this->locale);
|
||||
$baseTranslations = $baseLocale ? $this->get($key, [], $baseLocale) : [];
|
||||
|
||||
return array_replace_recursive($fallback, $baseTranslations, $translationArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array of locales to be checked.
|
||||
*
|
||||
* @param string|null $locale
|
||||
* @return array
|
||||
*/
|
||||
protected function localeArray($locale)
|
||||
{
|
||||
$primaryLocale = $locale ?: $this->locale;
|
||||
return array_filter([$primaryLocale, $this->getBaseLocale($primaryLocale), $this->fallback]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the locale to extend for the given locale.
|
||||
*
|
||||
* @param string $locale
|
||||
* @return string|null
|
||||
*/
|
||||
protected function getBaseLocale($locale)
|
||||
{
|
||||
return $this->baseLocaleMap[$locale] ?? null;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
<?php namespace BookStack;
|
||||
<?php namespace BookStack\Uploads;
|
||||
|
||||
use BookStack\Entities\Page;
|
||||
use BookStack\Ownable;
|
||||
|
||||
class Attachment extends Ownable
|
||||
{
|
||||
@@ -18,7 +21,7 @@ class Attachment extends Ownable
|
||||
|
||||
/**
|
||||
* Get the page this file was uploaded to.
|
||||
* @return Page
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function page()
|
||||
{
|
||||
@@ -1,7 +1,6 @@
|
||||
<?php namespace BookStack\Services;
|
||||
<?php namespace BookStack\Uploads;
|
||||
|
||||
use BookStack\Exceptions\FileUploadException;
|
||||
use BookStack\Attachment;
|
||||
use Exception;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
|
||||
@@ -45,7 +44,7 @@ class AttachmentService extends UploadService
|
||||
public function saveNewUpload(UploadedFile $uploadedFile, $page_id)
|
||||
{
|
||||
$attachmentName = $uploadedFile->getClientOriginalName();
|
||||
$attachmentPath = $this->putFileInStorage($attachmentName, $uploadedFile);
|
||||
$attachmentPath = $this->putFileInStorage($uploadedFile);
|
||||
$largestExistingOrder = Attachment::where('uploaded_to', '=', $page_id)->max('order');
|
||||
|
||||
$attachment = Attachment::forceCreate([
|
||||
@@ -76,7 +75,7 @@ class AttachmentService extends UploadService
|
||||
}
|
||||
|
||||
$attachmentName = $uploadedFile->getClientOriginalName();
|
||||
$attachmentPath = $this->putFileInStorage($attachmentName, $uploadedFile);
|
||||
$attachmentPath = $this->putFileInStorage($uploadedFile);
|
||||
|
||||
$attachment->name = $attachmentName;
|
||||
$attachment->path = $attachmentPath;
|
||||
@@ -175,19 +174,18 @@ class AttachmentService extends UploadService
|
||||
|
||||
/**
|
||||
* Store a file in storage with the given filename
|
||||
* @param $attachmentName
|
||||
* @param UploadedFile $uploadedFile
|
||||
* @return string
|
||||
* @throws FileUploadException
|
||||
*/
|
||||
protected function putFileInStorage($attachmentName, UploadedFile $uploadedFile)
|
||||
protected function putFileInStorage(UploadedFile $uploadedFile)
|
||||
{
|
||||
$attachmentData = file_get_contents($uploadedFile->getRealPath());
|
||||
|
||||
$storage = $this->getStorage();
|
||||
$basePath = 'uploads/files/' . Date('Y-m-M') . '/';
|
||||
|
||||
$uploadFileName = $attachmentName;
|
||||
$uploadFileName = str_random(16) . '.' . $uploadedFile->getClientOriginalExtension();
|
||||
while ($storage->exists($basePath . $uploadFileName)) {
|
||||
$uploadFileName = str_random(3) . $uploadFileName;
|
||||
}
|
||||
33
app/Uploads/HttpFetcher.php
Normal file
33
app/Uploads/HttpFetcher.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php namespace BookStack\Uploads;
|
||||
|
||||
use BookStack\Exceptions\HttpFetchException;
|
||||
|
||||
class HttpFetcher
|
||||
{
|
||||
|
||||
/**
|
||||
* Fetch content from an external URI.
|
||||
* @param string $uri
|
||||
* @return bool|string
|
||||
* @throws HttpFetchException
|
||||
*/
|
||||
public function fetch(string $uri)
|
||||
{
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $uri,
|
||||
CURLOPT_RETURNTRANSFER => 1,
|
||||
CURLOPT_CONNECTTIMEOUT => 5
|
||||
]);
|
||||
|
||||
$data = curl_exec($ch);
|
||||
$err = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($err) {
|
||||
throw new HttpFetchException($err);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php namespace BookStack;
|
||||
<?php namespace BookStack\Uploads;
|
||||
|
||||
use BookStack\Entities\Page;
|
||||
use BookStack\Ownable;
|
||||
use Images;
|
||||
|
||||
class Image extends Ownable
|
||||
@@ -19,4 +21,14 @@ class Image extends Ownable
|
||||
{
|
||||
return Images::getThumbnail($this, $width, $height, $keepRatio);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the page this image has been uploaded to.
|
||||
* Only applicable to gallery or drawio image types.
|
||||
* @return Page|null
|
||||
*/
|
||||
public function getPage()
|
||||
{
|
||||
return $this->belongsTo(Page::class, 'uploaded_to')->first();
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
<?php namespace BookStack\Repos;
|
||||
<?php namespace BookStack\Uploads;
|
||||
|
||||
use BookStack\Image;
|
||||
use BookStack\Page;
|
||||
use BookStack\Services\ImageService;
|
||||
use BookStack\Services\PermissionService;
|
||||
use BookStack\Auth\Permissions\PermissionService;
|
||||
use BookStack\Entities\Page;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
|
||||
class ImageRepo
|
||||
@@ -18,11 +17,15 @@ class ImageRepo
|
||||
* ImageRepo constructor.
|
||||
* @param Image $image
|
||||
* @param ImageService $imageService
|
||||
* @param PermissionService $permissionService
|
||||
* @param Page $page
|
||||
* @param \BookStack\Auth\Permissions\PermissionService $permissionService
|
||||
* @param \BookStack\Entities\Page $page
|
||||
*/
|
||||
public function __construct(Image $image, ImageService $imageService, PermissionService $permissionService, Page $page)
|
||||
{
|
||||
public function __construct(
|
||||
Image $image,
|
||||
ImageService $imageService,
|
||||
PermissionService $permissionService,
|
||||
Page $page
|
||||
) {
|
||||
$this->image = $image;
|
||||
$this->imageService = $imageService;
|
||||
$this->restrictionService = $permissionService;
|
||||
@@ -33,7 +36,7 @@ class ImageRepo
|
||||
/**
|
||||
* Get an image with the given id.
|
||||
* @param $id
|
||||
* @return mixed
|
||||
* @return Image
|
||||
*/
|
||||
public function getById($id)
|
||||
{
|
||||
@@ -46,95 +49,113 @@ class ImageRepo
|
||||
* @param $query
|
||||
* @param int $page
|
||||
* @param int $pageSize
|
||||
* @param bool $filterOnPage
|
||||
* @return array
|
||||
*/
|
||||
private function returnPaginated($query, $page = 0, $pageSize = 24)
|
||||
private function returnPaginated($query, $page = 1, $pageSize = 24)
|
||||
{
|
||||
$images = $this->restrictionService->filterRelatedPages($query, 'images', 'uploaded_to');
|
||||
$images = $images->orderBy('created_at', 'desc')->skip($pageSize * $page)->take($pageSize + 1)->get();
|
||||
$images = $query->orderBy('created_at', 'desc')->skip($pageSize * ($page - 1))->take($pageSize + 1)->get();
|
||||
$hasMore = count($images) > $pageSize;
|
||||
|
||||
$returnImages = $images->take(24);
|
||||
$returnImages = $images->take($pageSize);
|
||||
$returnImages->each(function ($image) {
|
||||
$this->loadThumbs($image);
|
||||
});
|
||||
|
||||
return [
|
||||
'images' => $returnImages,
|
||||
'hasMore' => $hasMore
|
||||
'has_more' => $hasMore
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a load images paginated, filtered by image type.
|
||||
* Fetch a list of images in a paginated format, filtered by image type.
|
||||
* Can be filtered by uploaded to and also by name.
|
||||
* @param string $type
|
||||
* @param int $page
|
||||
* @param int $pageSize
|
||||
* @param bool|int $userFilter
|
||||
* @param int $uploadedTo
|
||||
* @param string|null $search
|
||||
* @param callable|null $whereClause
|
||||
* @return array
|
||||
*/
|
||||
public function getPaginatedByType($type, $page = 0, $pageSize = 24, $userFilter = false)
|
||||
{
|
||||
$images = $this->image->where('type', '=', strtolower($type));
|
||||
public function getPaginatedByType(
|
||||
string $type,
|
||||
int $page = 0,
|
||||
int $pageSize = 24,
|
||||
int $uploadedTo = null,
|
||||
string $search = null,
|
||||
callable $whereClause = null
|
||||
) {
|
||||
$imageQuery = $this->image->newQuery()->where('type', '=', strtolower($type));
|
||||
|
||||
if ($userFilter !== false) {
|
||||
$images = $images->where('created_by', '=', $userFilter);
|
||||
if ($uploadedTo !== null) {
|
||||
$imageQuery = $imageQuery->where('uploaded_to', '=', $uploadedTo);
|
||||
}
|
||||
|
||||
return $this->returnPaginated($images, $page, $pageSize);
|
||||
if ($search !== null) {
|
||||
$imageQuery = $imageQuery->where('name', 'LIKE', '%' . $search . '%');
|
||||
}
|
||||
|
||||
// Filter by page access
|
||||
$imageQuery = $this->restrictionService->filterRelatedEntity('page', $imageQuery, 'images', 'uploaded_to');
|
||||
|
||||
if ($whereClause !== null) {
|
||||
$imageQuery = $imageQuery->where($whereClause);
|
||||
}
|
||||
|
||||
return $this->returnPaginated($imageQuery, $page, $pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for images by query, of a particular type.
|
||||
* Get paginated gallery images within a specific page or book.
|
||||
* @param string $type
|
||||
* @param string $filterType
|
||||
* @param int $page
|
||||
* @param int $pageSize
|
||||
* @param string $searchTerm
|
||||
* @param int|null $uploadedTo
|
||||
* @param string|null $search
|
||||
* @return array
|
||||
*/
|
||||
public function searchPaginatedByType($type, $searchTerm, $page = 0, $pageSize = 24)
|
||||
{
|
||||
$images = $this->image->where('type', '=', strtolower($type))->where('name', 'LIKE', '%' . $searchTerm . '%');
|
||||
return $this->returnPaginated($images, $page, $pageSize);
|
||||
}
|
||||
public function getEntityFiltered(
|
||||
string $type,
|
||||
string $filterType = null,
|
||||
int $page = 0,
|
||||
int $pageSize = 24,
|
||||
int $uploadedTo = null,
|
||||
string $search = null
|
||||
) {
|
||||
$contextPage = $this->page->findOrFail($uploadedTo);
|
||||
$parentFilter = null;
|
||||
|
||||
/**
|
||||
* Get gallery images with a particular filter criteria such as
|
||||
* being within the current book or page.
|
||||
* @param $filter
|
||||
* @param $pageId
|
||||
* @param int $pageNum
|
||||
* @param int $pageSize
|
||||
* @return array
|
||||
*/
|
||||
public function getGalleryFiltered($filter, $pageId, $pageNum = 0, $pageSize = 24)
|
||||
{
|
||||
$images = $this->image->where('type', '=', 'gallery');
|
||||
|
||||
$page = $this->page->findOrFail($pageId);
|
||||
|
||||
if ($filter === 'page') {
|
||||
$images = $images->where('uploaded_to', '=', $page->id);
|
||||
} elseif ($filter === 'book') {
|
||||
$validPageIds = $page->book->pages->pluck('id')->toArray();
|
||||
$images = $images->whereIn('uploaded_to', $validPageIds);
|
||||
if ($filterType === 'book' || $filterType === 'page') {
|
||||
$parentFilter = function (Builder $query) use ($filterType, $contextPage) {
|
||||
if ($filterType === 'page') {
|
||||
$query->where('uploaded_to', '=', $contextPage->id);
|
||||
} elseif ($filterType === 'book') {
|
||||
$validPageIds = $contextPage->book->pages()->get(['id'])->pluck('id')->toArray();
|
||||
$query->whereIn('uploaded_to', $validPageIds);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return $this->returnPaginated($images, $pageNum, $pageSize);
|
||||
return $this->getPaginatedByType($type, $page, $pageSize, null, $search, $parentFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a new image into storage and return the new image.
|
||||
* @param UploadedFile $uploadFile
|
||||
* @param string $type
|
||||
* @param string $type
|
||||
* @param int $uploadedTo
|
||||
* @param int|null $resizeWidth
|
||||
* @param int|null $resizeHeight
|
||||
* @param bool $keepRatio
|
||||
* @return Image
|
||||
* @throws \BookStack\Exceptions\ImageUploadException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function saveNew(UploadedFile $uploadFile, $type, $uploadedTo = 0)
|
||||
public function saveNew(UploadedFile $uploadFile, $type, $uploadedTo = 0, int $resizeWidth = null, int $resizeHeight = null, bool $keepRatio = true)
|
||||
{
|
||||
$image = $this->imageService->saveNewFromUpload($uploadFile, $type, $uploadedTo);
|
||||
$image = $this->imageService->saveNewFromUpload($uploadFile, $type, $uploadedTo, $resizeWidth, $resizeHeight, $keepRatio);
|
||||
$this->loadThumbs($image);
|
||||
return $image;
|
||||
}
|
||||
@@ -177,12 +198,27 @@ class ImageRepo
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function destroyImage(Image $image)
|
||||
public function destroyImage(Image $image = null)
|
||||
{
|
||||
$this->imageService->destroy($image);
|
||||
if ($image) {
|
||||
$this->imageService->destroy($image);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy all images of a certain type.
|
||||
* @param string $imageType
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function destroyByType(string $imageType)
|
||||
{
|
||||
$images = $this->image->where('type', '=', $imageType)->get();
|
||||
foreach ($images as $image) {
|
||||
$this->destroyImage($image);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load thumbnails onto an image object.
|
||||
@@ -193,8 +229,8 @@ class ImageRepo
|
||||
protected function loadThumbs(Image $image)
|
||||
{
|
||||
$image->thumbs = [
|
||||
'gallery' => $this->getThumbnail($image, 150, 150),
|
||||
'display' => $this->getThumbnail($image, 840, 0, true)
|
||||
'gallery' => $this->getThumbnail($image, 150, 150, false),
|
||||
'display' => $this->getThumbnail($image, 840, null, true)
|
||||
];
|
||||
}
|
||||
|
||||
@@ -210,7 +246,7 @@ class ImageRepo
|
||||
* @throws \BookStack\Exceptions\ImageUploadException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getThumbnail(Image $image, $width = 220, $height = 220, $keepRatio = false)
|
||||
protected function getThumbnail(Image $image, $width = 220, $height = 220, $keepRatio = false)
|
||||
{
|
||||
try {
|
||||
return $this->imageService->getThumbnail($image, $width, $height, $keepRatio);
|
||||
@@ -234,13 +270,11 @@ class ImageRepo
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the provided image type is valid.
|
||||
* @param $type
|
||||
* @return bool
|
||||
* Get the validation rules for image files.
|
||||
* @return string
|
||||
*/
|
||||
public function isValidType($type)
|
||||
public function getImageValidationRules()
|
||||
{
|
||||
$validTypes = ['gallery', 'cover', 'system', 'user'];
|
||||
return in_array($type, $validTypes);
|
||||
return 'image_extension|no_double_extension|mimes:jpeg,png,gif,bmp,webp,tiff';
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
<?php namespace BookStack\Services;
|
||||
<?php namespace BookStack\Uploads;
|
||||
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Exceptions\HttpFetchException;
|
||||
use BookStack\Exceptions\ImageUploadException;
|
||||
use BookStack\Image;
|
||||
use BookStack\User;
|
||||
use DB;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Cache\Repository as Cache;
|
||||
use Illuminate\Contracts\Filesystem\Factory as FileSystem;
|
||||
use Intervention\Image\Exception\NotSupportedException;
|
||||
use Intervention\Image\ImageManager;
|
||||
use Illuminate\Contracts\Filesystem\Factory as FileSystem;
|
||||
use Illuminate\Contracts\Cache\Repository as Cache;
|
||||
use phpDocumentor\Reflection\Types\Integer;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
|
||||
class ImageService extends UploadService
|
||||
@@ -18,6 +19,7 @@ class ImageService extends UploadService
|
||||
protected $cache;
|
||||
protected $storageUrl;
|
||||
protected $image;
|
||||
protected $http;
|
||||
|
||||
/**
|
||||
* ImageService constructor.
|
||||
@@ -25,12 +27,14 @@ class ImageService extends UploadService
|
||||
* @param ImageManager $imageTool
|
||||
* @param FileSystem $fileSystem
|
||||
* @param Cache $cache
|
||||
* @param HttpFetcher $http
|
||||
*/
|
||||
public function __construct(Image $image, ImageManager $imageTool, FileSystem $fileSystem, Cache $cache)
|
||||
public function __construct(Image $image, ImageManager $imageTool, FileSystem $fileSystem, Cache $cache, HttpFetcher $http)
|
||||
{
|
||||
$this->image = $image;
|
||||
$this->imageTool = $imageTool;
|
||||
$this->cache = $cache;
|
||||
$this->http = $http;
|
||||
parent::__construct($fileSystem);
|
||||
}
|
||||
|
||||
@@ -54,15 +58,29 @@ class ImageService extends UploadService
|
||||
/**
|
||||
* Saves a new image from an upload.
|
||||
* @param UploadedFile $uploadedFile
|
||||
* @param string $type
|
||||
* @param string $type
|
||||
* @param int $uploadedTo
|
||||
* @param int|null $resizeWidth
|
||||
* @param int|null $resizeHeight
|
||||
* @param bool $keepRatio
|
||||
* @return mixed
|
||||
* @throws ImageUploadException
|
||||
*/
|
||||
public function saveNewFromUpload(UploadedFile $uploadedFile, $type, $uploadedTo = 0)
|
||||
{
|
||||
public function saveNewFromUpload(
|
||||
UploadedFile $uploadedFile,
|
||||
string $type,
|
||||
int $uploadedTo = 0,
|
||||
int $resizeWidth = null,
|
||||
int $resizeHeight = null,
|
||||
bool $keepRatio = true
|
||||
) {
|
||||
$imageName = $uploadedFile->getClientOriginalName();
|
||||
$imageData = file_get_contents($uploadedFile->getRealPath());
|
||||
|
||||
if ($resizeWidth !== null || $resizeHeight !== null) {
|
||||
$imageData = $this->resizeImage($imageData, $resizeWidth, $resizeHeight, $keepRatio);
|
||||
}
|
||||
|
||||
return $this->saveNew($imageName, $imageData, $type, $uploadedTo);
|
||||
}
|
||||
|
||||
@@ -96,8 +114,9 @@ class ImageService extends UploadService
|
||||
private function saveNewFromUrl($url, $type, $imageName = false)
|
||||
{
|
||||
$imageName = $imageName ? $imageName : basename($url);
|
||||
$imageData = file_get_contents($url);
|
||||
if ($imageData === false) {
|
||||
try {
|
||||
$imageData = $this->http->fetch($url);
|
||||
} catch (HttpFetchException $exception) {
|
||||
throw new \Exception(trans('errors.cannot_get_image_from_url', ['url' => $url]));
|
||||
}
|
||||
return $this->saveNew($imageName, $imageData, $type);
|
||||
@@ -118,7 +137,7 @@ class ImageService extends UploadService
|
||||
$secureUploads = setting('app-secure-images');
|
||||
$imageName = str_replace(' ', '-', $imageName);
|
||||
|
||||
$imagePath = '/uploads/images/' . $type . '/' . Date('Y-m-M') . '/';
|
||||
$imagePath = '/uploads/images/' . $type . '/' . Date('Y-m') . '/';
|
||||
|
||||
while ($storage->exists($imagePath . $imageName)) {
|
||||
$imageName = str_random(3) . $imageName;
|
||||
@@ -197,8 +216,28 @@ class ImageService extends UploadService
|
||||
return $this->getPublicUrl($thumbFilePath);
|
||||
}
|
||||
|
||||
$thumbData = $this->resizeImage($storage->get($imagePath), $width, $height, $keepRatio);
|
||||
|
||||
$storage->put($thumbFilePath, $thumbData);
|
||||
$storage->setVisibility($thumbFilePath, 'public');
|
||||
$this->cache->put('images-' . $image->id . '-' . $thumbFilePath, $thumbFilePath, 60 * 72);
|
||||
|
||||
return $this->getPublicUrl($thumbFilePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize image data.
|
||||
* @param string $imageData
|
||||
* @param int $width
|
||||
* @param int $height
|
||||
* @param bool $keepRatio
|
||||
* @return string
|
||||
* @throws ImageUploadException
|
||||
*/
|
||||
protected function resizeImage(string $imageData, $width = 220, $height = null, bool $keepRatio = true)
|
||||
{
|
||||
try {
|
||||
$thumb = $this->imageTool->make($storage->get($imagePath));
|
||||
$thumb = $this->imageTool->make($imageData);
|
||||
} catch (Exception $e) {
|
||||
if ($e instanceof \ErrorException || $e instanceof NotSupportedException) {
|
||||
throw new ImageUploadException(trans('errors.cannot_create_thumbs'));
|
||||
@@ -207,20 +246,14 @@ class ImageService extends UploadService
|
||||
}
|
||||
|
||||
if ($keepRatio) {
|
||||
$thumb->resize($width, null, function ($constraint) {
|
||||
$thumb->resize($width, $height, function ($constraint) {
|
||||
$constraint->aspectRatio();
|
||||
$constraint->upsize();
|
||||
});
|
||||
} else {
|
||||
$thumb->fit($width, $height);
|
||||
}
|
||||
|
||||
$thumbData = (string)$thumb->encode();
|
||||
$storage->put($thumbFilePath, $thumbData);
|
||||
$storage->setVisibility($thumbFilePath, 'public');
|
||||
$this->cache->put('images-' . $image->id . '-' . $thumbFilePath, $thumbFilePath, 60 * 72);
|
||||
|
||||
return $this->getPublicUrl($thumbFilePath);
|
||||
return (string)$thumb->encode();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -280,24 +313,58 @@ class ImageService extends UploadService
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a gravatar image and set a the profile image for a user.
|
||||
* @param User $user
|
||||
* Save an avatar image from an external service.
|
||||
* @param \BookStack\Auth\User $user
|
||||
* @param int $size
|
||||
* @return mixed
|
||||
* @return Image
|
||||
* @throws Exception
|
||||
*/
|
||||
public function saveUserGravatar(User $user, $size = 500)
|
||||
public function saveUserAvatar(User $user, $size = 500)
|
||||
{
|
||||
$emailHash = md5(strtolower(trim($user->email)));
|
||||
$url = 'https://www.gravatar.com/avatar/' . $emailHash . '?s=' . $size . '&d=identicon';
|
||||
$imageName = str_replace(' ', '-', $user->name . '-gravatar.png');
|
||||
$image = $this->saveNewFromUrl($url, 'user', $imageName);
|
||||
$avatarUrl = $this->getAvatarUrl();
|
||||
$email = strtolower(trim($user->email));
|
||||
|
||||
$replacements = [
|
||||
'${hash}' => md5($email),
|
||||
'${size}' => $size,
|
||||
'${email}' => urlencode($email),
|
||||
];
|
||||
|
||||
$userAvatarUrl = strtr($avatarUrl, $replacements);
|
||||
$imageName = str_replace(' ', '-', $user->name . '-avatar.png');
|
||||
$image = $this->saveNewFromUrl($userAvatarUrl, 'user', $imageName);
|
||||
$image->created_by = $user->id;
|
||||
$image->updated_by = $user->id;
|
||||
$image->uploaded_to = $user->id;
|
||||
$image->save();
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if fetching external avatars is enabled.
|
||||
* @return bool
|
||||
*/
|
||||
public function avatarFetchEnabled()
|
||||
{
|
||||
$fetchUrl = $this->getAvatarUrl();
|
||||
return is_string($fetchUrl) && strpos($fetchUrl, 'http') === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL to fetch avatars from.
|
||||
* @return string|mixed
|
||||
*/
|
||||
protected function getAvatarUrl()
|
||||
{
|
||||
$url = trim(config('services.avatar_url'));
|
||||
|
||||
if (empty($url) && !config('services.disable_services')) {
|
||||
$url = 'https://www.gravatar.com/avatar/${hash}?s=${size}&d=identicon';
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete gallery and drawings that are not within HTML content of pages or page revisions.
|
||||
@@ -366,14 +433,7 @@ class ImageService extends UploadService
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [CURLOPT_URL => $uri, CURLOPT_RETURNTRANSFER => 1, CURLOPT_CONNECTTIMEOUT => 5]);
|
||||
$imageData = curl_exec($ch);
|
||||
$err = curl_error($ch);
|
||||
curl_close($ch);
|
||||
if ($err) {
|
||||
throw new \Exception("Image fetch failed, Received error: " . $err);
|
||||
}
|
||||
$imageData = $this->http->fetch($uri);
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
<?php namespace BookStack\Services;
|
||||
<?php namespace BookStack\Uploads;
|
||||
|
||||
use Illuminate\Contracts\Filesystem\Factory as FileSystem;
|
||||
use Illuminate\Contracts\Filesystem\Filesystem as FileSystemInstance;
|
||||
|
||||
class UploadService
|
||||
abstract class UploadService
|
||||
{
|
||||
|
||||
/**
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
use BookStack\Auth\Permissions\PermissionService;
|
||||
use BookStack\Entities\Entity;
|
||||
use BookStack\Ownable;
|
||||
|
||||
/**
|
||||
@@ -30,50 +32,72 @@ function versioned_asset($file = '')
|
||||
/**
|
||||
* Helper method to get the current User.
|
||||
* Defaults to public 'Guest' user if not logged in.
|
||||
* @return \BookStack\User
|
||||
* @return \BookStack\Auth\User
|
||||
*/
|
||||
function user()
|
||||
{
|
||||
return auth()->user() ?: \BookStack\User::getDefault();
|
||||
return auth()->user() ?: \BookStack\Auth\User::getDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if current user is a signed in user.
|
||||
* @return bool
|
||||
*/
|
||||
function signedInUser()
|
||||
function signedInUser() : bool
|
||||
{
|
||||
return auth()->user() && !auth()->user()->isDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current user has general access.
|
||||
* @return bool
|
||||
*/
|
||||
function hasAppAccess() : bool
|
||||
{
|
||||
return !auth()->guest() || setting('app-public');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current user has a permission.
|
||||
* If an ownable element is passed in the jointPermissions are checked against
|
||||
* that particular item.
|
||||
* @param $permission
|
||||
* @param string $permission
|
||||
* @param Ownable $ownable
|
||||
* @return mixed
|
||||
*/
|
||||
function userCan($permission, Ownable $ownable = null)
|
||||
function userCan(string $permission, Ownable $ownable = null)
|
||||
{
|
||||
if ($ownable === null) {
|
||||
return user() && user()->can($permission);
|
||||
}
|
||||
|
||||
// Check permission on ownable item
|
||||
$permissionService = app(\BookStack\Services\PermissionService::class);
|
||||
$permissionService = app(PermissionService::class);
|
||||
return $permissionService->checkOwnableUserAccess($ownable, $permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current user has the given permission
|
||||
* on any item in the system.
|
||||
* @param string $permission
|
||||
* @param string|null $entityClass
|
||||
* @return bool
|
||||
*/
|
||||
function userCanOnAny(string $permission, string $entityClass = null)
|
||||
{
|
||||
$permissionService = app(PermissionService::class);
|
||||
return $permissionService->checkUserHasPermissionOnAnything($permission, $entityClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to access system settings.
|
||||
* @param $key
|
||||
* @param bool $default
|
||||
* @return bool|string|\BookStack\Services\SettingService
|
||||
* @return bool|string|\BookStack\Settings\SettingService
|
||||
*/
|
||||
function setting($key = null, $default = false)
|
||||
{
|
||||
$settingService = resolve(\BookStack\Services\SettingService::class);
|
||||
$settingService = resolve(\BookStack\Settings\SettingService::class);
|
||||
if (is_null($key)) {
|
||||
return $settingService;
|
||||
}
|
||||
@@ -92,10 +116,15 @@ function baseUrl($path, $forceAppDomain = false)
|
||||
if ($isFullUrl && !$forceAppDomain) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
$path = trim($path, '/');
|
||||
$base = rtrim(config('app.url'), '/');
|
||||
|
||||
// Remove non-specified domain if forced and we have a domain
|
||||
if ($isFullUrl && $forceAppDomain) {
|
||||
if (!empty($base) && strpos($path, $base) === 0) {
|
||||
$path = trim(substr($path, strlen($base) - 1));
|
||||
}
|
||||
$explodedPath = explode('/', $path);
|
||||
$path = implode('/', array_splice($explodedPath, 3));
|
||||
}
|
||||
@@ -105,7 +134,7 @@ function baseUrl($path, $forceAppDomain = false)
|
||||
return url($path);
|
||||
}
|
||||
|
||||
return rtrim(config('app.url'), '/') . '/' . $path;
|
||||
return $base . '/' . $path;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,13 +5,18 @@
|
||||
"license": "MIT",
|
||||
"type": "project",
|
||||
"require": {
|
||||
"php": ">=7.0.0",
|
||||
"php": ">=7.0.5",
|
||||
"ext-json": "*",
|
||||
"ext-tidy": "*",
|
||||
"ext-dom": "*",
|
||||
"laravel/framework": "~5.5.42",
|
||||
"ext-xml": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-gd": "*",
|
||||
"ext-curl": "*",
|
||||
"laravel/framework": "~5.5.44",
|
||||
"fideloper/proxy": "~3.3",
|
||||
"intervention/image": "^2.4",
|
||||
"laravel/socialite": "^3.0",
|
||||
"laravel/socialite": "3.0.x-dev",
|
||||
"league/flysystem-aws-s3-v3": "^1.0",
|
||||
"barryvdh/laravel-dompdf": "^0.8.1",
|
||||
"predis/predis": "^1.1",
|
||||
@@ -82,7 +87,7 @@
|
||||
"optimize-autoloader": true,
|
||||
"preferred-install": "dist",
|
||||
"platform": {
|
||||
"php": "7.0"
|
||||
"php": "7.0.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
981
composer.lock
generated
981
composer.lock
generated
File diff suppressed because it is too large
Load Diff
234
config/app.php
234
config/app.php
@@ -1,175 +1,85 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Global app configuration options.
|
||||
*
|
||||
* Changes to these config files are not supported by BookStack and may break upon updates.
|
||||
* Configuration should be altered via the `.env` file or environment variables.
|
||||
* Do not edit this file unless you're happy to maintain any changes yourself.
|
||||
*/
|
||||
|
||||
return [
|
||||
|
||||
// The environment to run BookStack in.
|
||||
// Options: production, development, demo, testing
|
||||
'env' => env('APP_ENV', 'production'),
|
||||
|
||||
/**
|
||||
* Set the default view type for various lists. Can be overridden by user preferences.
|
||||
* This will be used for public viewers and users that have not set a preference.
|
||||
*/
|
||||
'views' => [
|
||||
'books' => env('APP_VIEWS_BOOKS', 'list')
|
||||
],
|
||||
|
||||
/**
|
||||
* The number of revisions to keep in the database.
|
||||
* Once this limit is reached older revisions will be deleted.
|
||||
* If set to false then a limit will not be enforced.
|
||||
*/
|
||||
'revision_limit' => env('REVISION_LIMIT', 50),
|
||||
|
||||
/**
|
||||
* Allow <script> tags to entered within page content.
|
||||
* <script> tags are escaped by default.
|
||||
* Even when overridden the WYSIWYG editor may still escape script content.
|
||||
*/
|
||||
'allow_content_scripts' => env('ALLOW_CONTENT_SCRIPTS', false),
|
||||
|
||||
/**
|
||||
* Override the default behaviour for allowing crawlers to crawl the instance.
|
||||
* May be ignored if view has be overridden or modified.
|
||||
* Defaults to null since, if not set, 'app-public' status used instead.
|
||||
*/
|
||||
'allow_robots' => env('ALLOW_ROBOTS', null),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Debug Mode
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When your application is in debug mode, detailed error messages with
|
||||
| stack traces will be shown on every error that occurs within your
|
||||
| application. If disabled, a simple generic error page is shown.
|
||||
|
|
||||
*/
|
||||
|
||||
// Enter the application in debug mode.
|
||||
// Shows much more verbose error messages. Has potential to show
|
||||
// private configuration variables so should remain disabled in public.
|
||||
'debug' => env('APP_DEBUG', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application URL
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This URL is used by the console to properly generate URLs when using
|
||||
| the Artisan command line tool. You should set this to the root of
|
||||
| your application so that it is used when running Artisan tasks.
|
||||
|
|
||||
*/
|
||||
// Set the default view type for various lists. Can be overridden by user preferences.
|
||||
// These will be used for public viewers and users that have not set a preference.
|
||||
'views' => [
|
||||
'books' => env('APP_VIEWS_BOOKS', 'list'),
|
||||
'bookshelves' => env('APP_VIEWS_BOOKSHELVES', 'grid'),
|
||||
],
|
||||
|
||||
// The number of revisions to keep in the database.
|
||||
// Once this limit is reached older revisions will be deleted.
|
||||
// If set to false then a limit will not be enforced.
|
||||
'revision_limit' => env('REVISION_LIMIT', 50),
|
||||
|
||||
// Allow <script> tags to entered within page content.
|
||||
// <script> tags are escaped by default.
|
||||
// Even when overridden the WYSIWYG editor may still escape script content.
|
||||
'allow_content_scripts' => env('ALLOW_CONTENT_SCRIPTS', false),
|
||||
|
||||
// Override the default behaviour for allowing crawlers to crawl the instance.
|
||||
// May be ignored if view has be overridden or modified.
|
||||
// Defaults to null since, if not set, 'app-public' status used instead.
|
||||
'allow_robots' => env('ALLOW_ROBOTS', null),
|
||||
|
||||
// Application Base URL, Used by laravel in development commands
|
||||
// and used by BookStack in URL generation.
|
||||
'url' => env('APP_URL', '') === 'http://bookstack.dev' ? '' : env('APP_URL', ''),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Timezone
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the default timezone for your application, which
|
||||
| will be used by the PHP date and date-time functions. We have gone
|
||||
| ahead and set this to a sensible default for you out of the box.
|
||||
|
|
||||
*/
|
||||
|
||||
'timezone' => 'UTC',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Locale Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The application locale determines the default locale that will be used
|
||||
| by the translation service provider. You are free to set this value
|
||||
| to any of the locales which will be supported by the application.
|
||||
|
|
||||
*/
|
||||
// Application timezone for back-end date functions.
|
||||
'timezone' => env('APP_TIMEZONE', 'UTC'),
|
||||
|
||||
// Default locale to use
|
||||
'locale' => env('APP_LANG', 'en'),
|
||||
'locales' => ['en', 'ar', 'de', 'es', 'es_AR', 'fr', 'nl', 'pt_BR', 'sk', 'sv', 'ja', 'pl', 'it', 'ru', 'zh_CN', 'zh_TW'],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Right-to-left text control
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Right-to-left text control is set to false by default since English
|
||||
| is the primary supported application but this may be dynamically
|
||||
| altered by the applications localization system.
|
||||
|
|
||||
*/
|
||||
|
||||
'rtl' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Auto-detect the locale for public users
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| For public users their locale can be guessed by headers sent by their
|
||||
| browser. This is usually set by users in their browser settings.
|
||||
| If not found the default app locale will be used.
|
||||
|
|
||||
*/
|
||||
'auto_detect_locale' => env('APP_AUTO_LANG_PUBLIC', true),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Fallback Locale
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The fallback locale determines the locale to use when the current one
|
||||
| is not available. You may change the value to correspond to any of
|
||||
| the language folders that are provided through your application.
|
||||
|
|
||||
*/
|
||||
// Locales available
|
||||
'locales' => ['en', 'ar', 'de', 'de_informal', 'es', 'es_AR', 'fr', 'nl', 'pt_BR', 'sk', 'cs', 'sv', 'kr', 'ja', 'pl', 'it', 'ru', 'uk', 'zh_CN', 'zh_TW'],
|
||||
|
||||
// Application Fallback Locale
|
||||
'fallback_locale' => 'en',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Encryption Key
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This key is used by the Illuminate encrypter service and should be set
|
||||
| to a random, 32 character string, otherwise these encrypted strings
|
||||
| will not be safe. Please do this before deploying an application!
|
||||
|
|
||||
*/
|
||||
// Enable right-to-left text control.
|
||||
'rtl' => false,
|
||||
|
||||
// Auto-detect the locale for public users
|
||||
// For public users their locale can be guessed by headers sent by their
|
||||
// browser. This is usually set by users in their browser settings.
|
||||
// If not found the default app locale will be used.
|
||||
'auto_detect_locale' => env('APP_AUTO_LANG_PUBLIC', true),
|
||||
|
||||
// Encryption key
|
||||
'key' => env('APP_KEY', 'AbAZchsay4uBTU33RubBzLKw203yqSqr'),
|
||||
|
||||
// Encryption cipher
|
||||
'cipher' => 'AES-256-CBC',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Logging Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure the log settings for your application. Out of
|
||||
| the box, Laravel uses the Monolog PHP logging library. This gives
|
||||
| you a variety of powerful log handlers / formatters to utilize.
|
||||
|
|
||||
| Available Settings: "single", "daily", "syslog", "errorlog"
|
||||
|
|
||||
*/
|
||||
|
||||
// Logging configuration
|
||||
// Options: single, daily, syslog, errorlog
|
||||
'log' => env('APP_LOGGING', 'single'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Autoloaded Service Providers
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The service providers listed here will be automatically loaded on the
|
||||
| request to your application. Feel free to add your own services to
|
||||
| this array to grant expanded functionality to your applications.
|
||||
|
|
||||
*/
|
||||
|
||||
// Application Services Provides
|
||||
'providers' => [
|
||||
|
||||
/*
|
||||
* Laravel Framework Service Providers...
|
||||
*/
|
||||
// Laravel Framework Service Providers...
|
||||
Illuminate\Auth\AuthServiceProvider::class,
|
||||
Illuminate\Broadcasting\BroadcastServiceProvider::class,
|
||||
Illuminate\Bus\BusServiceProvider::class,
|
||||
@@ -187,25 +97,22 @@ return [
|
||||
Illuminate\Redis\RedisServiceProvider::class,
|
||||
Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
|
||||
Illuminate\Session\SessionServiceProvider::class,
|
||||
Illuminate\Translation\TranslationServiceProvider::class,
|
||||
Illuminate\Validation\ValidationServiceProvider::class,
|
||||
Illuminate\View\ViewServiceProvider::class,
|
||||
Illuminate\Notifications\NotificationServiceProvider::class,
|
||||
SocialiteProviders\Manager\ServiceProvider::class,
|
||||
|
||||
/**
|
||||
* Third Party
|
||||
*/
|
||||
// Third party service providers
|
||||
Intervention\Image\ImageServiceProvider::class,
|
||||
Barryvdh\DomPDF\ServiceProvider::class,
|
||||
Barryvdh\Snappy\ServiceProvider::class,
|
||||
|
||||
|
||||
/*
|
||||
* Application Service Providers...
|
||||
*/
|
||||
// BookStack replacement service providers (Extends Laravel)
|
||||
BookStack\Providers\PaginationServiceProvider::class,
|
||||
BookStack\Providers\TranslationServiceProvider::class,
|
||||
|
||||
// BookStack custom service providers
|
||||
BookStack\Providers\AuthServiceProvider::class,
|
||||
BookStack\Providers\AppServiceProvider::class,
|
||||
BookStack\Providers\BroadcastServiceProvider::class,
|
||||
@@ -225,8 +132,10 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
// Class aliases, Registered on application start
|
||||
'aliases' => [
|
||||
|
||||
// Laravel
|
||||
'App' => Illuminate\Support\Facades\App::class,
|
||||
'Artisan' => Illuminate\Support\Facades\Artisan::class,
|
||||
'Auth' => Illuminate\Support\Facades\Auth::class,
|
||||
@@ -262,25 +171,20 @@ return [
|
||||
'View' => Illuminate\Support\Facades\View::class,
|
||||
'Socialite' => Laravel\Socialite\Facades\Socialite::class,
|
||||
|
||||
/**
|
||||
* Third Party
|
||||
*/
|
||||
|
||||
// Third Party
|
||||
'ImageTool' => Intervention\Image\Facades\Image::class,
|
||||
'DomPDF' => Barryvdh\DomPDF\Facade::class,
|
||||
'SnappyPDF' => Barryvdh\Snappy\Facades\SnappyPdf::class,
|
||||
|
||||
/**
|
||||
* Custom
|
||||
*/
|
||||
|
||||
'Activity' => BookStack\Services\Facades\Activity::class,
|
||||
'Setting' => BookStack\Services\Facades\Setting::class,
|
||||
'Views' => BookStack\Services\Facades\Views::class,
|
||||
'Images' => BookStack\Services\Facades\Images::class,
|
||||
// Custom BookStack
|
||||
'Activity' => BookStack\Facades\Activity::class,
|
||||
'Setting' => BookStack\Facades\Setting::class,
|
||||
'Views' => BookStack\Facades\Views::class,
|
||||
'Images' => BookStack\Facades\Images::class,
|
||||
|
||||
],
|
||||
|
||||
// Proxy configuration
|
||||
'proxies' => env('APP_PROXIES', ''),
|
||||
|
||||
];
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user