Compare commits

..

336 Commits

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

Added test to cover.

Likely related to #3922.
2022-12-16 17:44:13 +00:00
Dan Brown
559e392f1b Merge branch 'development' of https://github.com/jhit/BookStack into jhit-development 2022-12-16 17:12:57 +00:00
Dan Brown
8468b632a1 Updated crowdin config with PR title and labels
Aligns to the title and labelling I already do manually.
2022-12-16 17:11:01 +00:00
Dan Brown
7053a8669f New Crowdin updates (#3881) 2022-12-16 17:06:52 +00:00
Dan Brown
2c0a7346b1 Prevent search focus change on left/right arrow press
For #3920
2022-12-16 17:03:48 +00:00
Dan Brown
bf6a6af683 Updated version and assets for release v22.11 2022-11-30 12:30:21 +00:00
Dan Brown
914790fd99 Merge branch 'development' into release 2022-11-30 12:29:52 +00:00
Jürgen Hörmann
d505642336 Add popular PHP templating languages to code editor
Smarty and Twig are two very popular PHP templating engines and might be
useful to some Bookstack users too.
2022-11-29 14:53:41 +01:00
Dan Brown
edb0c6a9e8 Updated version and assets for release v22.10.2 2022-11-02 15:22:13 +00:00
Dan Brown
84049de696 Merge branch 'v22-10' into release 2022-11-02 15:19:33 +00:00
Dan Brown
da0531e63b Updated version and assets for release v22.10.1 2022-10-21 21:52:32 +01:00
Dan Brown
421dc75f4e Merge branch 'development' into release 2022-10-21 21:52:16 +01:00
Dan Brown
8ae91df038 Updated version and assets for release v22.10 2022-10-21 11:16:45 +01:00
Dan Brown
64b41dd626 Merge branch 'development' into release 2022-10-21 11:16:25 +01:00
Dan Brown
ebd6e4d3a2 Updated version and assets for release v22.09.1 2022-09-20 13:19:34 +01:00
Dan Brown
80374aea5c Merge branch 'development' into release 2022-09-20 13:19:03 +01:00
Dan Brown
2ac9efae7d Updated version and assets for release v22.09 2022-09-08 12:41:09 +01:00
Dan Brown
a11d565ba4 Merge branch 'development' into release 2022-09-08 12:40:57 +01:00
Dan Brown
1fdf854ea7 Updated version and assets for release v22.07.3 2022-08-11 15:17:06 +01:00
Dan Brown
e9c9792cb9 Merge branch 'development' into release 2022-08-11 15:16:34 +01:00
Dan Brown
5ae524c25a Updated version and assets for release v22.07.2 2022-08-09 13:55:52 +01:00
Dan Brown
0d7287fc8b Merge branch 'development' into release 2022-08-09 13:55:40 +01:00
Dan Brown
e77c96f6b7 Updated version and assets for release v22.07.1 2022-08-02 11:47:25 +01:00
Dan Brown
9b8a10dd3a Merge branch 'development' into release 2022-08-02 11:47:08 +01:00
Dan Brown
49200ca5ce Updated version and assets for release v22.07 2022-07-28 14:53:15 +01:00
Dan Brown
34aa4dbf10 Merge branch 'development' into release 2022-07-28 14:53:01 +01:00
Dan Brown
5ee79d16c9 Updated version and assets for release v22.06.2 2022-06-28 11:57:37 +01:00
Dan Brown
a1ea4006e0 Merge branch 'development' into release 2022-06-28 11:57:24 +01:00
Dan Brown
9078188939 Updated version and assets for release v22.06.1 2022-06-25 14:33:07 +01:00
Dan Brown
ed0aad1a7a Merge branch 'development' into release 2022-06-25 14:32:49 +01:00
Dan Brown
5c59cfb020 Updated version and assets for release v22.06 2022-06-24 11:50:56 +01:00
Dan Brown
3ca15ad68a Merge branch 'development' into release 2022-06-24 11:45:29 +01:00
Dan Brown
60014989f5 Updated version and assets for release v22.04.2 2022-05-09 16:10:16 +01:00
Dan Brown
57b10f195e Merge branch 'development' into release 2022-05-09 16:09:54 +01:00
Dan Brown
b1e95eb39f Updated version and assets for release v22.04.1 2022-05-04 21:26:58 +01:00
Dan Brown
b3da77b8f9 Merge branch 'development' into release 2022-05-04 21:26:31 +01:00
Dan Brown
1a345b74bb Updated version and assets for release v22.04 2022-04-29 15:55:32 +01:00
Dan Brown
8ffc3a4abf Merge branch 'development' into release 2022-04-29 15:55:05 +01:00
Dan Brown
7233c1c7b2 Updated version and assets for release v22.03.1 2022-03-30 19:37:07 +01:00
Dan Brown
1309a01131 Merge branch 'development' into release 2022-03-30 19:36:45 +01:00
Dan Brown
0333185b6d Updated version and assets for release v22.03 2022-03-30 13:49:17 +01:00
Dan Brown
83f89f64e8 Merge branch 'development' into release 2022-03-30 13:49:05 +01:00
Dan Brown
11a1a6fb16 Updated version and assets for release v22.02.3 2022-03-07 15:12:22 +00:00
Dan Brown
882c609296 Merge branch 'development' into release 2022-03-07 15:12:09 +00:00
Dan Brown
176a0dcd59 Updated version and assets for release v22.02.2 2022-03-01 22:45:41 +00:00
Dan Brown
94b0f70bfa Merge branch 'development' into release 2022-03-01 22:45:12 +00:00
Dan Brown
08b2a77d41 Updated version and assets for release v22.02.1 2022-02-27 17:46:06 +00:00
Dan Brown
3e8e9a23cf Merge branch 'development' into release 2022-02-27 17:45:49 +00:00
Dan Brown
58b83b64c8 Updated version and assets for release v22.02 2022-02-26 12:01:44 +00:00
Dan Brown
dfe4cde6ee Merge branch 'development' into release 2022-02-26 12:00:46 +00:00
Dan Brown
d11144d9e2 Updated version and assets for release v21.12.5 2022-02-06 15:49:23 +00:00
Dan Brown
f96b0ea5f3 Merge branch 'development' into release 2022-02-06 15:48:55 +00:00
Dan Brown
815f8d79ed Updated version and assets for release v21.12.4 2022-02-01 11:52:24 +00:00
Dan Brown
b62dab32e0 Merge branch 'development' into release 2022-02-01 11:51:48 +00:00
Dan Brown
262f863981 Updated version and assets for release v21.12.3 2022-01-24 22:49:42 +00:00
Dan Brown
a4c94390a1 Merge branch 'master' into release 2022-01-24 22:49:31 +00:00
Dan Brown
53f3cca85d Updated version and assets for release v21.12.2 2022-01-10 18:23:44 +00:00
Dan Brown
ed08bbcecc Merge branch 'master' into release 2022-01-10 18:23:19 +00:00
Dan Brown
de97ebf9b7 Updated version and assets for release v21.12.1 2022-01-06 12:20:37 +00:00
Dan Brown
f492a660a8 Merge branch 'master' into release 2022-01-06 12:20:26 +00:00
Dan Brown
09436836a5 Updated version and assets for release v21.12 2021-12-22 17:04:18 +00:00
Dan Brown
bb455d7788 Merge branch 'master' into release 2021-12-22 17:03:50 +00:00
Dan Brown
009212ab80 Updated version and assets for release v21.11.3 2021-12-15 14:08:37 +00:00
Dan Brown
ba9cb591c8 Merge branch 'master' into release 2021-12-15 14:08:17 +00:00
Dan Brown
d00ac2f34e Updated version and assets for release v21.11.2 2021-11-30 14:30:19 +00:00
Dan Brown
bd4dc6d463 Merge branch 'master' into release 2021-11-30 14:29:53 +00:00
Dan Brown
d91180a909 Updated version and assets for release v21.11.1 2021-11-23 20:44:36 +00:00
Dan Brown
bc2913a5cb Merge branch 'master' into release 2021-11-23 20:44:12 +00:00
Dan Brown
4802394562 Updated version and assets for release v21.11 2021-11-16 13:22:24 +00:00
Dan Brown
1755556468 Merge branch 'master' into release 2021-11-16 13:21:44 +00:00
Dan Brown
01cdbdb7ae Updated version and assets for release v21.10.3 2021-11-01 13:31:10 +00:00
Dan Brown
fc8bbf3eab Merge branch 'master' into release 2021-11-01 13:30:36 +00:00
Dan Brown
3cdab19319 Updated version and assets for release v21.10.2 2021-10-28 15:57:04 +01:00
Dan Brown
5661d20e87 Merge branch 'master' into release 2021-10-28 15:56:49 +01:00
Dan Brown
91f80123e8 Merge branch 'master' into release 2021-10-27 12:35:00 +01:00
Dan Brown
7a0636d0f8 Updated version and assets for release v21.10.1 2021-10-27 12:31:40 +01:00
Dan Brown
0fe5bdfbac Updated version and assets for release v21.10 2021-10-25 15:59:23 +01:00
Dan Brown
f88687e977 Merge branch 'master' into release 2021-10-25 15:58:59 +01:00
Dan Brown
68d437d05b Updated version and assets for release v21.08.6 2021-10-15 14:34:44 +01:00
Dan Brown
1e56aaea04 Merge branch 'master' into release 2021-10-15 14:34:23 +01:00
Dan Brown
dab170a6fe Updated version and assets for release v21.08.5 2021-10-08 22:25:36 +01:00
Dan Brown
a8de717d9b Merge branch 'master' into release 2021-10-08 22:25:05 +01:00
Dan Brown
78fe95b6fc Updated version and assets for release v21.08.4 2021-10-04 16:25:24 +01:00
Dan Brown
e0c24e41aa Merge branch 'master' into release 2021-10-04 16:24:54 +01:00
Dan Brown
fa8553839b Updated version and assets for release v21.08.3 2021-09-12 16:31:02 +01:00
Dan Brown
b8fcefc794 Merge branch 'master' into release 2021-09-12 16:30:35 +01:00
Dan Brown
88bcb68fcb Updated version and assets for release v21.08.2 2021-09-04 15:07:20 +01:00
Dan Brown
7c000553ae Merge branch 'master' into release 2021-09-04 15:06:33 +01:00
Dan Brown
391fa35c80 Updated version and assets for release v21.08.1 2021-09-02 21:13:09 +01:00
Dan Brown
c6773a8c9f Merge branch 'master' into release 2021-09-02 21:12:06 +01:00
Dan Brown
9b226e7d39 Updated version and assets for release v21.08 2021-08-31 22:07:53 +01:00
Dan Brown
9865446267 Merge branch 'master' into release 2021-08-31 22:07:23 +01:00
Dan Brown
926abbe776 Updated version and assets for release v21.05.4 2021-08-04 21:29:10 +01:00
Dan Brown
4fabef3a57 Merge branch 'v21.05.x' into release 2021-08-04 21:28:45 +01:00
Dan Brown
5ef4cd80c3 Updated version and assets for release v21.05.3 2021-07-03 11:59:52 +01:00
Dan Brown
e01f23583f Merge branch 'v21.05.x' into release 2021-07-03 11:59:21 +01:00
Dan Brown
7792cb3915 Updated version and assets for release v21.05.2 2021-06-13 14:26:34 +01:00
Dan Brown
be26253a18 Merge branch 'master' into release 2021-06-13 14:25:39 +01:00
Dan Brown
1bdd1f8189 Updated version for release v21.05.1 2021-06-04 23:09:42 +01:00
Dan Brown
fa62c79b17 Merge branch 'master' into release 2021-06-04 23:08:59 +01:00
Dan Brown
d7d8fa1e5b Updated version and assets for release v21.05 2021-05-30 16:17:56 +01:00
Dan Brown
18562f1e10 Merge branch 'master' into release 2021-05-30 16:17:44 +01:00
Dan Brown
86090a694f Updated version and assets for release v21.04.6 2021-05-24 13:06:03 +01:00
Dan Brown
1ee8287c73 Merge branch 'v21.04.x' into release 2021-05-24 13:05:34 +01:00
Dan Brown
8eb98cd591 Updated version and assets for release v21.04.5 2021-05-15 17:56:29 +01:00
Dan Brown
0f9ba21b05 Merge branch 'v21.04.x' into release 2021-05-15 17:56:03 +01:00
Dan Brown
834f8e7046 Updated version and assets for release v21.04.4 2021-05-09 14:46:05 +01:00
Dan Brown
32e3399334 Merge branch 'master' into release 2021-05-09 14:45:36 +01:00
Dan Brown
2d8698a218 Updated version and assets for release v21.04.3 2021-04-27 22:01:37 +01:00
Dan Brown
454fb883a2 Merge branch 'master' into release 2021-04-27 22:01:15 +01:00
Dan Brown
6f4a6ab8ea Updated version for release v21.04.2 2021-04-20 22:37:05 +01:00
Dan Brown
9c4b6f36f1 Merge branch 'master' into release 2021-04-20 22:36:35 +01:00
Dan Brown
78886b1e67 Updated version and assets for release v21.04.1 2021-04-19 22:26:19 +01:00
Dan Brown
d9debaf032 Merge branch 'master' into release 2021-04-19 22:25:29 +01:00
Dan Brown
d4360d6347 Updated version and assets for release v21.04 2021-04-09 21:18:32 +01:00
Dan Brown
175b1785c0 Merge branch 'master' into release 2021-04-09 21:18:09 +01:00
Dan Brown
c8740c0171 Updated version for release v0.31.8 2021-03-13 15:32:54 +00:00
Dan Brown
91ee895a74 Merge branch 'v0.31.x' into release 2021-03-13 15:32:06 +00:00
Dan Brown
a045e46571 Updated version for release v0.31.7 2021-03-02 21:19:17 +00:00
Dan Brown
44eaa65c3b Merge branch 'v0.31.x' into release 2021-03-02 21:18:31 +00:00
Dan Brown
0a22af7b14 Updated version for release v0.31.6 2021-02-06 14:41:19 +00:00
Dan Brown
b54702ab08 Merge branch 'v0.31.x' into release 2021-02-06 14:40:47 +00:00
Dan Brown
c4fdcfc5d1 Updated version for release v0.31.5 2021-02-02 20:58:06 +00:00
Dan Brown
cb8117e8df Merge branch 'v0.31.x' into release 2021-02-02 20:57:41 +00:00
Dan Brown
5a218d5056 Updated version and assets for release v0.31.4 2021-01-16 17:50:45 +00:00
Dan Brown
8dbc5cf9c6 Merge branch 'master' into release 2021-01-16 17:50:11 +00:00
Dan Brown
71e81615a3 Updated version for release v0.31.3 2021-01-10 23:29:58 +00:00
Dan Brown
611d37da04 Merge branch 'master' into release 2021-01-10 23:29:11 +00:00
Dan Brown
0e799a3857 Updated version and assets for release v0.31.2 2021-01-10 14:05:16 +00:00
Dan Brown
b91d6e2bfa Merge branch 'master' into release 2021-01-10 14:04:59 +00:00
Dan Brown
ea16ad7e94 Updated version and assets for release v0.31.1 2021-01-04 18:41:55 +00:00
Dan Brown
ba6eb54552 Merge branch 'master' into release 2021-01-04 18:41:26 +00:00
Dan Brown
f705e7683b Updated assets for release v0.31.0 again 2021-01-03 22:33:36 +00:00
Dan Brown
dc996adb20 Merge branch 'master' into release 2021-01-03 22:32:40 +00:00
Dan Brown
a64c638ccc Updated version and assets for release v0.31.0 2021-01-03 21:52:37 +00:00
Dan Brown
359c067279 Merge branch 'master' into release 2021-01-03 21:52:00 +00:00
Dan Brown
66a746e297 Updated version for release v0.30.7 2020-12-18 14:13:40 +00:00
Dan Brown
a4d43ee24b Merge branch 'v0.30.x' into release 2020-12-18 14:13:19 +00:00
Dan Brown
f7793a70a9 Updated version for release v0.30.6 2020-12-17 21:07:06 +00:00
Dan Brown
ceba3d31fb Merge branch 'v0.30.x' into release 2020-12-17 21:03:20 +00:00
Dan Brown
eecc08edde Updated version for release v0.30.5 2020-12-06 21:05:43 +00:00
Dan Brown
eb19aadc75 Merge branch 'v0.30.x' into release 2020-12-06 21:05:11 +00:00
Dan Brown
06c81e69b9 Updated version and assets for release v0.30.4 2020-10-31 16:52:33 +00:00
Dan Brown
3dc3d4a639 Merge branch 'master' into release 2020-10-31 16:51:54 +00:00
Dan Brown
94c59c1e3d Updated version and assets for release v0.30.3 2020-10-13 22:50:52 +01:00
Dan Brown
4d2205853a Merge branch 'master' into release 2020-10-13 22:50:30 +01:00
Dan Brown
751772b87a Updated version and assets for release v0.30.2 2020-09-30 22:44:58 +01:00
Dan Brown
76e30869e1 Merge branch 'master' into release 2020-09-30 22:44:17 +01:00
Dan Brown
3edc9fe9eb Updated version and assets for release v0.30.1 2020-09-26 17:51:37 +01:00
Dan Brown
616c62703e Merge branch 'master' into release 2020-09-26 17:50:25 +01:00
Dan Brown
ecd56917e7 Updated version and assets for release v0.30.0 2020-09-20 10:33:18 +01:00
Dan Brown
e22c9cae91 Merge branch 'master' into release 2020-09-20 10:30:10 +01:00
Dan Brown
29ddb6e1b9 Updated version and assets for release v0.29.3 2020-05-12 22:34:01 +01:00
Dan Brown
2ff90e2ff0 Merge branch 'master' into release 2020-05-12 22:33:27 +01:00
Dan Brown
04ecc128a2 Updated version and assets for release v0.29.2 2020-05-02 11:49:21 +01:00
Dan Brown
87d1d3423b Merge branch 'master' into release 2020-05-02 11:48:48 +01:00
Dan Brown
4818192a2a Updated version and assets for release v0.29.1 2020-04-28 12:30:31 +01:00
Dan Brown
965dd97f54 Merge branch 'master' into release 2020-04-28 12:30:09 +01:00
Dan Brown
195b74926c Updated version and assets for release v0.29.0 2020-04-13 16:10:23 +01:00
Dan Brown
2120db12b2 Merge branch 'master' into release 2020-04-13 16:10:11 +01:00
Dan Brown
ed563fef28 Updated version and assets for release v0.28.3 2020-03-14 22:31:42 +00:00
Dan Brown
0d31a8e3f1 Merge branch 'master' into release 2020-03-14 22:31:11 +00:00
Dan Brown
b8354b974b Updated version and assets for release v0.28.2 2020-02-15 22:36:08 +00:00
Dan Brown
034c1e289d Merge branch 'master' into release 2020-02-15 22:35:46 +00:00
Dan Brown
f31605a3de Updated version and assets for release v0.28.1 2020-02-15 22:08:06 +00:00
Dan Brown
e7cc75c74d Merge branch 'master' into release 2020-02-15 22:07:17 +00:00
Dan Brown
4b79d5e4e8 Updated version and assets for release v0.28.0 2020-02-03 22:44:45 +00:00
Dan Brown
34854915b3 Merge branch 'master' into release 2020-02-03 22:43:58 +00:00
Dan Brown
af6f34b529 Updated version and assets for release v0.27.5 2019-10-16 16:35:50 +01:00
Dan Brown
fb82a2b896 Merge branch 'patching-v0.27' into release 2019-10-16 16:35:10 +01:00
Dan Brown
5b464938b6 Updated version and assets for release v0.27.4 2019-09-07 13:30:08 +01:00
Dan Brown
81f954890d Merge branch 'patching-v0.27' into release 2019-09-07 13:29:53 +01:00
Dan Brown
0e2bbcec62 Updated version and assets for release v0.27.3 2019-09-03 21:50:12 +01:00
Dan Brown
fdd339f525 Merge branch 'master' into release 2019-09-03 21:49:46 +01:00
Dan Brown
8cf7d6a83d Updated version and assets for release v0.27.2 2019-09-01 12:12:23 +01:00
Dan Brown
58a5008718 Merge branch 'master' into release 2019-09-01 12:12:10 +01:00
Dan Brown
c44a8df55d Updated version and assets for release v0.27.1 2019-09-01 11:13:50 +01:00
Dan Brown
ff1494c519 Merge branch 'master' into release 2019-09-01 11:13:18 +01:00
Dan Brown
b8ce8fd852 Updated assets for release v0.27 2019-08-31 14:16:14 +01:00
Dan Brown
75e7454a5f Merge branch 'master' into release and set version 2019-08-31 14:15:18 +01:00
Dan Brown
2558ea8931 Updated version for release v0.26.4 2019-08-06 21:42:09 +01:00
Dan Brown
ac0f47a4b2 Merge branch 'v0.26' into release 2019-08-06 21:41:06 +01:00
Dan Brown
4f16129869 Updated version for release v0.26.3 2019-07-10 20:21:22 +01:00
Dan Brown
64a8037fdd Merge branch 'v0.26' into release 2019-07-10 20:19:54 +01:00
Dan Brown
7502ba1bc8 Updated version and assets for release v0.26.2 2019-05-27 13:48:20 +01:00
Dan Brown
33a04697ef Merge branch 'master' into release 2019-05-27 13:47:47 +01:00
Dan Brown
b70a5c0cdb Updated version and assets for release v0.26.1 2019-05-07 23:05:47 +01:00
Dan Brown
9443ae9f40 Merge branch 'master' into release 2019-05-07 23:05:10 +01:00
Dan Brown
220c2a4102 Updated version and assets for release v0.26.0 2019-05-06 18:58:56 +01:00
Dan Brown
e9914eb301 Merge branch 'master' into release 2019-05-06 18:57:58 +01:00
Dan Brown
934512d09c Updated version and assets for release v0.25.5 2019-03-24 19:45:17 +00:00
Dan Brown
9102c90986 Merge branch 'master' into release 2019-03-24 19:45:00 +00:00
Dan Brown
c3e74219c4 Updated version and assets for release v0.25.4 2019-03-21 19:46:19 +00:00
Dan Brown
13c9d7bc2d Merge branch 'master' into release 2019-03-21 19:43:48 +00:00
Dan Brown
119b539586 Updated version and assets for release v0.25.3 2019-03-21 00:03:26 +00:00
Dan Brown
29a5c180f0 Merge branch 'master' into release 2019-03-21 00:02:33 +00:00
Dan Brown
7906602291 Updated version and assets for release v0.25.2 2019-03-10 13:45:21 +00:00
Dan Brown
6dafe773ff Merge branch 'master' into release 2019-03-10 13:44:29 +00:00
Dan Brown
25bc28a1be Updated version and assets for release v0.25.1 2019-01-20 15:42:32 +00:00
Dan Brown
4c561c7fa0 Merge branch 'master' into release 2019-01-20 15:41:24 +00:00
Dan Brown
95b3e78573 Updated version and assets for release v0.25.0 2019-01-12 22:48:53 +00:00
Dan Brown
63a345bc93 Merge branch 'master' into release 2019-01-12 22:47:07 +00:00
Dan Brown
e093a172cb Updated assets and version for release v0.24.3 2018-11-27 21:52:20 +00:00
Dan Brown
4b01f8934b Merge branch 'master' into release 2018-11-27 21:51:32 +00:00
Dan Brown
bc116b45b5 Re-updated assets for release v0.24.2 2018-11-10 16:10:22 +00:00
Dan Brown
a059960b9e Merge branch 'master' into release 2018-11-10 16:09:14 +00:00
Dan Brown
7770966fed Updated assets for release v0.24.2 2018-11-10 16:01:55 +00:00
Dan Brown
d7adcf6c69 Merge branch 'master' into release 2018-11-10 16:01:01 +00:00
Dan Brown
04a364dcc3 Incremented version for v0.24.1 2018-09-24 16:34:16 +01:00
Dan Brown
db83ac7eaa Merge branch 'master' into release 2018-09-24 16:32:30 +01:00
Dan Brown
3ca9dddf61 Merge branch 'master' into release 2018-09-24 15:59:39 +01:00
Dan Brown
bf74f53ca7 Updated assets for release and incremented version 2018-09-24 12:18:27 +01:00
Dan Brown
9d67efb4a4 Merge branch 'master' into release 2018-09-24 12:08:21 +01:00
Dan Brown
3a39b9f440 Merge pull request #1022 from BookStackApp/revert-983-master
Revert "Update german translation"
2018-09-22 18:33:29 +01:00
Dan Brown
27f7aab375 Revert "Update german translation" 2018-09-22 18:33:15 +01:00
Dan Brown
337da0c467 Merge pull request #983 from vriic/master
Update german translation
2018-09-22 18:27:04 +01:00
Nikolai Nikolajevic
f56b3560c4 Update german translation 2018-08-23 16:17:46 +02:00
Dan Brown
02dfe11ce6 Increment version for release v0.23.2 2018-08-19 15:33:23 +01:00
Dan Brown
83d06beb70 Merge branch 'master' into release 2018-08-19 15:33:10 +01:00
Dan Brown
a8cfc059c8 Updated version for release v0.23.1 2018-08-12 14:22:53 +01:00
Dan Brown
1614b2bab0 Merge branch 'master' into release 2018-08-12 14:22:17 +01:00
Dan Brown
4bdec0d214 Updated version and assets for release v0.23 2018-07-29 20:28:49 +01:00
Dan Brown
6a7d7e7c2b Merge branch 'master' into release 2018-07-29 20:26:00 +01:00
Dan Brown
30d4674657 Updated assets for release v0.22 2018-05-28 14:19:14 +01:00
Dan Brown
9f961f95f8 Merge branch 'master' into release 2018-05-28 14:19:04 +01:00
Dan Brown
bab99a26ec Updated assets and version for v0.21 release 2018-04-22 20:21:22 +01:00
Dan Brown
9a7fecd269 Merge branch 'master' into release 2018-04-22 20:19:02 +01:00
Dan Brown
a8dc0d449b Updated the version because i'm such a plonker
And forgot to do this last release.
I wonder if there's a simple commit hook that could prevent the same two
versions twice in a row?
2018-03-30 15:41:46 +01:00
Dan Brown
a0381f76bf Merge branch 'v0.20' into release 2018-03-30 15:33:23 +01:00
Dan Brown
6102f66daa Updated assets for release v0.20.1 2018-03-25 16:58:14 +01:00
Dan Brown
c6134d162d Merge branch 'master' into release 2018-03-25 16:54:48 +01:00
Dan Brown
2046f9b9de Updated assets for release v0.20.0 2018-02-11 18:20:17 +00:00
Dan Brown
ac3ba594a4 Merge branch 'master' into release and updated version 2018-02-11 18:19:38 +00:00
Dan Brown
22df25a480 Updated assets and version for v0.19.0 2017-12-10 18:21:07 +00:00
Dan Brown
8b30c7f02e Merge branch 'master' into release 2017-12-10 18:19:20 +00:00
Dan Brown
757cdddc7c Updated version and JS for release v0.18.5 2017-11-11 18:33:04 +00:00
Dan Brown
df95e99680 Updated assets and version for release v0.18.4 2017-10-15 19:28:29 +01:00
Dan Brown
5a6d544db7 Merge branch 'master' into release 2017-10-15 19:27:50 +01:00
Dan Brown
16117d329c Merge branch 'master' into release, Updated version 2017-10-06 21:05:45 +01:00
Dan Brown
e90da18ada Updated assets and version for v0.18.2 release 2017-10-01 18:12:59 +01:00
Dan Brown
a08d80e1cc Merge branch 'master' into release 2017-10-01 18:12:07 +01:00
Dan Brown
6258175922 Updated assets and version for v0.18.1 release 2017-09-20 21:36:17 +01:00
Dan Brown
15736777a0 Merge branch 'master' into release 2017-09-20 21:35:33 +01:00
Dan Brown
75915e8a94 Updated assets for release v0.18 2017-09-10 17:07:57 +01:00
Dan Brown
9bde0ae4ea Merge branch 'master' into release 2017-09-10 17:05:05 +01:00
Dan Brown
0c802d1f86 Updated assets and version for release v0.17.4 2017-07-28 13:04:21 +01:00
Dan Brown
b7a96c6466 Merge branch 'master' into release 2017-07-28 13:03:36 +01:00
Dan Brown
4b645a82c7 Updated version for release 2017-07-22 17:27:01 +01:00
Dan Brown
d599b77b6f Merge branch 'master' into release 2017-07-22 17:26:44 +01:00
Dan Brown
26e93dc8c1 Updated assets and version for release v0.17.2 2017-07-22 16:49:07 +01:00
Dan Brown
a4c9a8491b Merge branch 'master' into release 2017-07-22 16:46:57 +01:00
Dan Brown
70ee636d87 Updated css and version for release 2017-07-10 20:52:32 +01:00
Dan Brown
b35f6dbb03 Merge branch 'master' into release 2017-07-10 20:51:25 +01:00
Dan Brown
67d9e24d8f Merge branch 'master' into release
Also updated assets, Version number
2017-07-02 22:52:26 +01:00
Dan Brown
3903fda6ca Incremented version 2017-06-04 15:38:49 +01:00
Dan Brown
441e46ebaa Merge branch 'v0.16' into release 2017-06-04 15:38:29 +01:00
Dan Brown
1f4260f359 Updated version for release v0.16.2 2017-05-07 19:35:51 +01:00
Dan Brown
dc0bf8ad4e Merge branch 'master' into release 2017-05-07 19:35:34 +01:00
Dan Brown
102e326e6a Updated JS and version for release v0.16.1 2017-04-30 19:51:23 +01:00
Dan Brown
2b25bf6f3b Merge branch 'master' into release 2017-04-30 19:50:29 +01:00
Dan Brown
f93280696d Updated assets for release v0.16 2017-04-23 20:42:28 +01:00
Dan Brown
1787391b07 Merge branch 'master' into release 2017-04-23 20:41:45 +01:00
Dan Brown
a74a8ee483 Updated version for v0.15.3 2017-03-23 22:22:16 +00:00
Dan Brown
7fa5405cb7 Merge branch 'master' into release 2017-03-23 22:21:04 +00:00
Dan Brown
6725ddcc41 Updated version for release v0.15.2 2017-03-05 15:50:52 +00:00
Dan Brown
bce941db3f Merge branch 'master' into release 2017-03-05 15:49:47 +00:00
Dan Brown
6d926048ec Updated to version v0.15.1 2017-02-27 16:59:10 +00:00
Dan Brown
5335c973b4 Merge branch 'master' into release 2017-02-27 16:58:20 +00:00
Dan Brown
15c3e5c96e Updated assets for release v0.15 2017-02-27 14:58:02 +00:00
Dan Brown
a5d5904969 Merge branch 'master' into release 2017-02-27 14:57:38 +00:00
Dan Brown
598758b991 Updated version for v0.14.3 2017-02-05 21:23:27 +00:00
Dan Brown
9926e23bc8 Merge branch 'v0.14' into release 2017-02-05 21:21:54 +00:00
Dan Brown
5d3264bc63 Updated assets for release v0.14.2 2017-02-01 22:27:04 +00:00
Dan Brown
d71f819f95 Merge branch 'v0.14' into release 2017-02-01 22:22:38 +00:00
Dan Brown
ee13509760 Updated version number 2017-01-23 22:28:31 +00:00
Dan Brown
82d7bb1f32 Merge branch 'master' into release 2017-01-23 22:28:02 +00:00
Dan Brown
cdfda508d8 Updated assets for release v0.14 2017-01-22 12:36:10 +00:00
Dan Brown
da941e584f Merge branch 'master' into release ready for v0.14 2017-01-22 12:31:27 +00:00
Dan Brown
65874d7b96 Updated assets for release v0.13.1 2016-11-27 19:42:33 +00:00
Dan Brown
ac9b8f405c Merge fixes from master for release v0.13.1 2016-11-27 19:41:12 +00:00
Dan Brown
8d1419a12e Update assets and version for release v0.13 2016-11-13 12:29:52 +00:00
Dan Brown
04f7a7d301 Merge branch 'master' into release 2016-11-13 12:26:56 +00:00
Dan Brown
c10d2a1493 Updated assets for release v0.12.2 2016-10-30 13:19:19 +00:00
Dan Brown
97bbf79ffd Merge branch 'v0.12' into release 2016-10-30 13:18:23 +00:00
Dan Brown
f7b01ae53d Updated assets for release v0.12.1 2016-09-06 20:50:15 +01:00
Dan Brown
d704e1dbba Merge branch 'master' into release 2016-09-06 20:49:15 +01:00
Dan Brown
ef2ff5e093 Updated assets for release v0.12 2016-09-05 19:49:42 +01:00
Dan Brown
7caed3b0db Merge branch 'master' into release 2016-09-05 19:35:21 +01:00
Dan Brown
45641d0754 Updated assets for release v0.11.2 2016-08-21 14:56:29 +01:00
Dan Brown
4b1d08ba99 Merge branch 'v0.11' into release 2016-08-21 14:55:11 +01:00
Dan Brown
160fa99ba4 Updated assets for release v0.11.1 2016-08-14 12:40:55 +01:00
Dan Brown
d2a5ab49ed Merge branch 'v0.11' into release 2016-08-14 12:37:48 +01:00
Dan Brown
c6404d8917 Updated assets for release v0.11 2016-07-03 10:56:16 +01:00
Dan Brown
7113807f12 Merge branch 'master' into release 2016-07-03 10:52:04 +01:00
Dan Brown
be711215e8 Updated assets for release v0.10 2016-05-22 15:12:47 +01:00
Dan Brown
7e3b404240 Merge branch 'master' into release for version v0.10 2016-05-22 15:11:50 +01:00
Dan Brown
e86901ca20 Updated assets for release v0.9.3 2016-05-03 21:13:02 +01:00
Dan Brown
bdfa61c8b2 Merge branch 'v0.9' into release 2016-05-03 21:11:01 +01:00
Dan Brown
2cc36787f5 Updated assets for release 0.9.2 2016-04-15 19:57:02 +01:00
Dan Brown
448ac61b48 Merge branch 'master' into release 2016-04-15 19:52:59 +01:00
Dan Brown
753f6394f7 Merge branch 'master' into release 2016-04-12 20:09:14 +01:00
Dan Brown
b1faf65934 Updated assets for release 0.9.0 2016-04-09 15:49:02 +01:00
Dan Brown
09f478bd74 Merge branch 'master' into release 2016-04-09 15:47:14 +01:00
Dan Brown
a0497feddd Updated assets for release 0.8.2 2016-03-30 21:44:30 +01:00
Dan Brown
789693bde9 Merge branch 'v0.8' into release 2016-03-30 21:32:46 +01:00
Dan Brown
1fe933e4ea Merge branch 'master' into release 2016-03-13 15:38:06 +00:00
Dan Brown
724b4b5a70 Updated assets for release 0.8.0 2016-03-13 15:15:14 +00:00
Dan Brown
1778a56146 Merge branch 'master' into release 2016-03-13 15:13:23 +00:00
Dan Brown
744865fcb2 Updated assets for release 0.7.6 2016-03-06 13:28:44 +00:00
Dan Brown
7f8c8b448d Merged branch master into release 2016-03-06 13:26:29 +00:00
Dan Brown
a67c53826d Updated assets for release 0.7.5 2016-02-25 21:24:09 +00:00
Dan Brown
14b131e850 Merge branch 'master' into release 2016-02-25 21:23:06 +00:00
Dan Brown
9b55a52b85 Updated assets for release 0.7.4 2016-02-11 22:35:01 +00:00
Dan Brown
db1d10e80f Merge branch 'master' into release 2016-02-11 22:29:29 +00:00
Dan Brown
1be576966f Updated assets for release 0.7.3 2016-02-08 20:47:33 +00:00
Dan Brown
b97e792c5f Merge branch 'master' into release 2016-02-08 20:45:48 +00:00
Dan Brown
8dec674cc3 Merge branch 'master' into release 2016-02-02 07:35:20 +00:00
Dan Brown
f784c03746 Merge branch 'master' into release 2016-02-01 18:31:04 +00:00
Dan Brown
148e172fe8 Updated assets for release 0.7 2016-01-31 18:03:55 +00:00
Dan Brown
56ae86646f Merge branch 'master' into release 2016-01-31 18:01:25 +00:00
Dan Brown
1d2b6fdfa2 Add updated assets 2016-01-02 14:50:59 +00:00
Dan Brown
4fc75beed4 Merge branch 'master' into release 2016-01-02 14:49:05 +00:00
Dan Brown
3b3bc0c4bf Updated compiled assets 2015-12-31 17:26:22 +00:00
Dan Brown
910faab88e Merge branch 'master' into release 2015-12-31 17:22:03 +00:00
Dan Brown
f184d763ad Added build folder to release 2015-12-16 17:53:53 +00:00
Dan Brown
a91d42634d Merge branch 'master' into release 2015-12-16 17:29:34 +00:00
Dan Brown
f517ef3616 Added new asset structure 2015-12-16 17:27:53 +00:00
Dan Brown
e99507ddcf Merge branch 'master' into release 2015-12-16 17:21:21 +00:00
Dan Brown
d2cacf1945 Release update 2015-12-01 21:30:21 +00:00
Dan Brown
448ac1405b Merge branch 'master' into release 2015-12-01 21:15:08 +00:00
Dan Brown
6ad21ce885 Added built assets for release 2015-11-30 21:59:34 +00:00
164 changed files with 1585 additions and 2780 deletions

View File

@@ -283,13 +283,13 @@ Kuchinashi Hoshikawa (kuchinashi) :: Chinese Simplified
digilady :: Greek
Linus (LinusOP) :: Swedish
Felipe Cardoso (felipecardosoruff) :: Portuguese, Brazilian
RandomUser0815 :: German
RandomUser0815 :: German Informal; German
Ismael Mesquita (mesquitoliveira) :: Portuguese, Brazilian
구인회 (laskdjlaskdj12) :: Korean
LiZerui (CNLiZerui) :: Chinese Traditional
Fabrice Boyer (FabriceBoyer) :: French
mikael (bitcanon) :: Swedish
Matthias Mai (schnapsidee) :: German
Matthias Mai (schnapsidee) :: German; German Informal
Ufuk Ayyıldız (ufukayyildiz) :: Turkish
Jan Mitrof (jan.kachlik) :: Czech
edwardsmirnov :: Russian
@@ -298,3 +298,7 @@ shotu :: French
Cesar_Lopez_Aguillon :: Spanish
bdewoop :: German
dina davoudi (dina.davoudi) :: Persian
Angelos Chouvardas (achouvardas) :: Greek
rndrss :: Portuguese, Brazilian
rirac294 :: Russian
David Furman (thefourCraft) :: Hebrew

6
.gitignore vendored
View File

@@ -5,10 +5,10 @@ Homestead.yaml
.idea
npm-debug.log
yarn-error.log
/public/dist
/public/dist/*.map
/public/plugins
/public/css
/public/js
/public/css/*.map
/public/js/*.map
/public/bower
/public/build/
/storage/images

View File

@@ -15,8 +15,6 @@ class ActivityQueries
{
protected PermissionApplicator $permissions;
protected array $fieldsForLists = ['id', 'type', 'detail', 'activities.entity_type', 'activities.entity_id', 'user_id', 'created_at'];
public function __construct(PermissionApplicator $permissions)
{
$this->permissions = $permissions;
@@ -27,11 +25,9 @@ class ActivityQueries
*/
public function latest(int $count = 20, int $page = 0): array
{
$query = Activity::query()->select($this->fieldsForLists);
$activityList = $this->permissions
->restrictEntityRelationQuery($query, 'activities', 'entity_id', 'entity_type')
->restrictEntityRelationQuery(Activity::query(), 'activities', 'entity_id', 'entity_type')
->orderBy('created_at', 'desc')
->whereNotNull('activities.entity_id')
->with(['user', 'entity'])
->skip($count * $page)
->take($count)
@@ -82,12 +78,10 @@ class ActivityQueries
*/
public function userActivity(User $user, int $count = 20, int $page = 0): array
{
$query = Activity::query()->select($this->fieldsForLists);
$activityList = $this->permissions
->restrictEntityRelationQuery($query, 'activities', 'entity_id', 'entity_type')
->restrictEntityRelationQuery(Activity::query(), 'activities', 'entity_id', 'entity_type')
->orderBy('created_at', 'desc')
->where('user_id', '=', $user->id)
->whereNotNull('activities.entity_id')
->skip($count * $page)
->take($count)
->get();

View File

@@ -29,16 +29,15 @@ class TagRepo
$sort = 'value';
}
$entityTypeCol = DB::getTablePrefix() . 'tags.entity_type';
$query = Tag::query()
->select([
'name',
($searchTerm || $nameFilter) ? 'value' : DB::raw('COUNT(distinct value) as `values`'),
DB::raw('COUNT(id) as usages'),
DB::raw("SUM(IF({$entityTypeCol} = 'page', 1, 0)) as page_count"),
DB::raw("SUM(IF({$entityTypeCol} = 'chapter', 1, 0)) as chapter_count"),
DB::raw("SUM(IF({$entityTypeCol} = 'book', 1, 0)) as book_count"),
DB::raw("SUM(IF({$entityTypeCol} = 'bookshelf', 1, 0)) as shelf_count"),
DB::raw('SUM(IF(entity_type = \'page\', 1, 0)) as page_count'),
DB::raw('SUM(IF(entity_type = \'chapter\', 1, 0)) as chapter_count'),
DB::raw('SUM(IF(entity_type = \'book\', 1, 0)) as book_count'),
DB::raw('SUM(IF(entity_type = \'bookshelf\', 1, 0)) as shelf_count'),
])
->orderBy($sort, $listOptions->getOrder());

View File

@@ -1,18 +0,0 @@
<?php
namespace BookStack\Auth\Permissions;
use BookStack\Model;
/**
* @property int $id
* @property ?int $role_id
* @property ?int $user_id
* @property string $entity_type
* @property int $entity_id
* @property bool $view
*/
class CollapsedPermission extends Model
{
protected $table = 'entity_permissions_collapsed';
}

View File

@@ -1,278 +0,0 @@
<?php
namespace BookStack\Auth\Permissions;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\BookChild;
use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\Page;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Support\Facades\DB;
/**
* Collapsed permissions act as a "flattened" view of entity-level permissions in the system
* so inheritance does not have to managed as part of permission querying.
*/
class CollapsedPermissionBuilder
{
/**
* Re-generate all collapsed permissions from scratch.
*/
public function rebuildForAll()
{
DB::table('entity_permissions_collapsed')->truncate();
// Chunk through all books
$this->bookFetchQuery()->chunk(5, function (EloquentCollection $books) {
$this->buildForBooks($books, false);
});
// Chunk through all bookshelves
Bookshelf::query()->withTrashed()
->select(['id'])
->chunk(50, function (EloquentCollection $shelves) {
$this->generateCollapsedPermissions($shelves->all());
});
}
/**
* Rebuild the collapsed permissions for a particular entity.
*/
public function rebuildForEntity(Entity $entity)
{
$entities = [$entity];
if ($entity instanceof Book) {
$books = $this->bookFetchQuery()->where('id', '=', $entity->id)->get();
$this->buildForBooks($books, true);
return;
}
/** @var BookChild $entity */
if ($entity->book) {
$entities[] = $entity->book;
}
if ($entity instanceof Page && $entity->chapter_id) {
$entities[] = $entity->chapter;
}
if ($entity instanceof Chapter) {
foreach ($entity->pages as $page) {
$entities[] = $page;
}
}
$this->buildForEntities($entities);
}
/**
* Get a query for fetching a book with its children.
*/
protected function bookFetchQuery(): Builder
{
return Book::query()->withTrashed()
->select(['id'])->with([
'chapters' => function ($query) {
$query->withTrashed()->select(['id', 'book_id']);
},
'pages' => function ($query) {
$query->withTrashed()->select(['id', 'book_id', 'chapter_id']);
},
]);
}
/**
* Build collapsed permissions for the given books.
*/
protected function buildForBooks(EloquentCollection $books, bool $deleteOld)
{
$entities = clone $books;
/** @var Book $book */
foreach ($books->all() as $book) {
foreach ($book->getRelation('chapters') as $chapter) {
$entities->push($chapter);
}
foreach ($book->getRelation('pages') as $page) {
$entities->push($page);
}
}
if ($deleteOld) {
$this->deleteForEntities($entities->all());
}
$this->generateCollapsedPermissions($entities->all());
}
/**
* Rebuild the collapsed permissions for a collection of entities.
*/
protected function buildForEntities(array $entities)
{
$this->deleteForEntities($entities);
$this->generateCollapsedPermissions($entities);
}
/**
* Delete the stored collapsed permissions for a list of entities.
*
* @param Entity[] $entities
*/
protected function deleteForEntities(array $entities)
{
$simpleEntities = $this->entitiesToSimpleEntities($entities);
$idsByType = $this->entitiesToTypeIdMap($simpleEntities);
DB::transaction(function () use ($idsByType) {
foreach ($idsByType as $type => $ids) {
foreach (array_chunk($ids, 1000) as $idChunk) {
DB::table('entity_permissions_collapsed')
->where('entity_type', '=', $type)
->whereIn('entity_id', $idChunk)
->delete();
}
}
});
}
/**
* Convert the given list of entities into "SimpleEntityData" representations
* for faster usage and property access.
*
* @param Entity[] $entities
*
* @return SimpleEntityData[]
*/
protected function entitiesToSimpleEntities(array $entities): array
{
$simpleEntities = [];
foreach ($entities as $entity) {
$attrs = $entity->getAttributes();
$simple = new SimpleEntityData();
$simple->id = $attrs['id'];
$simple->type = $entity->getMorphClass();
$simple->book_id = $attrs['book_id'] ?? null;
$simple->chapter_id = $attrs['chapter_id'] ?? null;
$simpleEntities[] = $simple;
}
return $simpleEntities;
}
/**
* Create & Save collapsed entity permissions.
*
* @param Entity[] $originalEntities
*/
protected function generateCollapsedPermissions(array $originalEntities)
{
$entities = $this->entitiesToSimpleEntities($originalEntities);
$collapsedPermData = [];
// Fetch related entity permissions
$permissions = $this->getEntityPermissionsForEntities($entities);
// Create a mapping of explicit entity permissions
$permissionMap = new EntityPermissionMap($permissions);
// Create Joint Permission Data
foreach ($entities as $entity) {
array_push($collapsedPermData, ...$this->createCollapsedPermissionData($entity, $permissionMap));
}
DB::transaction(function () use ($collapsedPermData) {
foreach (array_chunk($collapsedPermData, 1000) as $dataChunk) {
DB::table('entity_permissions_collapsed')->insert($dataChunk);
}
});
}
/**
* Create collapsed permission data for the given entity using the given permission map.
*/
protected function createCollapsedPermissionData(SimpleEntityData $entity, EntityPermissionMap $permissionMap): array
{
$chain = [
$entity->type . ':' . $entity->id,
$entity->chapter_id ? ('chapter:' . $entity->chapter_id) : null,
$entity->book_id ? ('book:' . $entity->book_id) : null,
];
$permissionData = [];
$overridesApplied = [];
foreach ($chain as $entityTypeId) {
if ($entityTypeId === null) {
continue;
}
$permissions = $permissionMap->getForEntity($entityTypeId);
foreach ($permissions as $permission) {
$related = $permission->getAssignedType() . ':' . $permission->getAssignedTypeId();
if (!isset($overridesApplied[$related])) {
$permissionData[] = [
'role_id' => $permission->role_id,
'user_id' => $permission->user_id,
'view' => $permission->view,
'entity_type' => $entity->type,
'entity_id' => $entity->id,
];
$overridesApplied[$related] = true;
}
}
}
return $permissionData;
}
/**
* From the given entity list, provide back a mapping of entity types to
* the ids of that given type. The type used is the DB morph class.
*
* @param SimpleEntityData[] $entities
*
* @return array<string, int[]>
*/
protected function entitiesToTypeIdMap(array $entities): array
{
$idsByType = [];
foreach ($entities as $entity) {
if (!isset($idsByType[$entity->type])) {
$idsByType[$entity->type] = [];
}
$idsByType[$entity->type][] = $entity->id;
}
return $idsByType;
}
/**
* Get the entity permissions for all the given entities.
*
* @param SimpleEntityData[] $entities
*
* @return EntityPermission[]
*/
protected function getEntityPermissionsForEntities(array $entities): array
{
$idsByType = $this->entitiesToTypeIdMap($entities);
$permissionFetch = EntityPermission::query()
->where(function (Builder $query) use ($idsByType) {
foreach ($idsByType as $type => $ids) {
$query->orWhere(function (Builder $query) use ($type, $ids) {
$query->where('entity_type', '=', $type)->whereIn('entity_id', $ids);
});
}
});
return $permissionFetch->get()->all();
}
}

View File

@@ -3,14 +3,13 @@
namespace BookStack\Auth\Permissions;
use BookStack\Auth\Role;
use BookStack\Auth\User;
use BookStack\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;
/**
* @property int $id
* @property int $role_id
* @property int $user_id
* @property int $entity_id
* @property string $entity_type
* @property boolean $view
@@ -22,9 +21,17 @@ class EntityPermission extends Model
{
public const PERMISSIONS = ['view', 'create', 'update', 'delete'];
protected $fillable = ['role_id', 'user_id', 'view', 'create', 'update', 'delete'];
protected $fillable = ['role_id', 'view', 'create', 'update', 'delete'];
public $timestamps = false;
/**
* Get this restriction's attached entity.
*/
public function restrictable(): MorphTo
{
return $this->morphTo('restrictable');
}
/**
* Get the role assigned to this entity permission.
*/
@@ -32,38 +39,4 @@ class EntityPermission extends Model
{
return $this->belongsTo(Role::class);
}
/**
* Get the user assigned to this entity permission.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* Get the type of entity permission this is.
* Will be one of: user, role, fallback
*/
public function getAssignedType(): string
{
if ($this->user_id) {
return 'user';
}
if ($this->role_id) {
return 'role';
}
return 'fallback';
}
/**
* Get the ID for the assigned type of permission.
* (Role/User ID). Defaults to 0 for fallback.
*/
public function getAssignedTypeId(): int
{
return $this->user_id ?? $this->role_id ?? 0;
}
}

View File

@@ -1,37 +0,0 @@
<?php
namespace BookStack\Auth\Permissions;
class EntityPermissionMap
{
protected array $map = [];
/**
* @param EntityPermission[] $permissions
*/
public function __construct(array $permissions = [])
{
foreach ($permissions as $entityPermission) {
$this->addPermission($entityPermission);
}
}
protected function addPermission(EntityPermission $permission)
{
$entityCombinedId = $permission->entity_type . ':' . $permission->entity_id;
if (!isset($this->map[$entityCombinedId])) {
$this->map[$entityCombinedId] = [];
}
$this->map[$entityCombinedId][] = $permission;
}
/**
* @return EntityPermission[]
*/
public function getForEntity(string $typeIdString): array
{
return $this->map[$typeIdString] ?? [];
}
}

View File

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

View File

@@ -0,0 +1,408 @@
<?php
namespace BookStack\Auth\Permissions;
use BookStack\Auth\Role;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\BookChild;
use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\Page;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Support\Facades\DB;
/**
* Joint permissions provide a pre-query "cached" table of view permissions for all core entity
* types for all roles in the system. This class generates out that table for different scenarios.
*/
class JointPermissionBuilder
{
/**
* @var array<string, array<int, SimpleEntityData>>
*/
protected array $entityCache;
/**
* Re-generate all entity permission from scratch.
*/
public function rebuildForAll()
{
JointPermission::query()->truncate();
// Get all roles (Should be the most limited dimension)
$roles = Role::query()->with('permissions')->get()->all();
// Chunk through all books
$this->bookFetchQuery()->chunk(5, function (EloquentCollection $books) use ($roles) {
$this->buildJointPermissionsForBooks($books, $roles);
});
// Chunk through all bookshelves
Bookshelf::query()->withTrashed()->select(['id', 'owned_by'])
->chunk(50, function (EloquentCollection $shelves) use ($roles) {
$this->createManyJointPermissions($shelves->all(), $roles);
});
}
/**
* Rebuild the entity jointPermissions for a particular entity.
*/
public function rebuildForEntity(Entity $entity)
{
$entities = [$entity];
if ($entity instanceof Book) {
$books = $this->bookFetchQuery()->where('id', '=', $entity->id)->get();
$this->buildJointPermissionsForBooks($books, Role::query()->with('permissions')->get()->all(), true);
return;
}
/** @var BookChild $entity */
if ($entity->book) {
$entities[] = $entity->book;
}
if ($entity instanceof Page && $entity->chapter_id) {
$entities[] = $entity->chapter;
}
if ($entity instanceof Chapter) {
foreach ($entity->pages as $page) {
$entities[] = $page;
}
}
$this->buildJointPermissionsForEntities($entities);
}
/**
* Build the entity jointPermissions for a particular role.
*/
public function rebuildForRole(Role $role)
{
$roles = [$role];
$role->jointPermissions()->delete();
$role->load('permissions');
// Chunk through all books
$this->bookFetchQuery()->chunk(20, function ($books) use ($roles) {
$this->buildJointPermissionsForBooks($books, $roles);
});
// Chunk through all bookshelves
Bookshelf::query()->select(['id', 'owned_by'])
->chunk(50, function ($shelves) use ($roles) {
$this->createManyJointPermissions($shelves->all(), $roles);
});
}
/**
* Prepare the local entity cache and ensure it's empty.
*
* @param SimpleEntityData[] $entities
*/
protected function readyEntityCache(array $entities)
{
$this->entityCache = [];
foreach ($entities as $entity) {
if (!isset($this->entityCache[$entity->type])) {
$this->entityCache[$entity->type] = [];
}
$this->entityCache[$entity->type][$entity->id] = $entity;
}
}
/**
* Get a book via ID, Checks local cache.
*/
protected function getBook(int $bookId): SimpleEntityData
{
return $this->entityCache['book'][$bookId];
}
/**
* Get a chapter via ID, Checks local cache.
*/
protected function getChapter(int $chapterId): SimpleEntityData
{
return $this->entityCache['chapter'][$chapterId];
}
/**
* Get a query for fetching a book with its children.
*/
protected function bookFetchQuery(): Builder
{
return Book::query()->withTrashed()
->select(['id', 'owned_by'])->with([
'chapters' => function ($query) {
$query->withTrashed()->select(['id', 'owned_by', 'book_id']);
},
'pages' => function ($query) {
$query->withTrashed()->select(['id', 'owned_by', 'book_id', 'chapter_id']);
},
]);
}
/**
* Build joint permissions for the given book and role combinations.
*/
protected function buildJointPermissionsForBooks(EloquentCollection $books, array $roles, bool $deleteOld = false)
{
$entities = clone $books;
/** @var Book $book */
foreach ($books->all() as $book) {
foreach ($book->getRelation('chapters') as $chapter) {
$entities->push($chapter);
}
foreach ($book->getRelation('pages') as $page) {
$entities->push($page);
}
}
if ($deleteOld) {
$this->deleteManyJointPermissionsForEntities($entities->all());
}
$this->createManyJointPermissions($entities->all(), $roles);
}
/**
* Rebuild the entity jointPermissions for a collection of entities.
*/
protected function buildJointPermissionsForEntities(array $entities)
{
$roles = Role::query()->get()->values()->all();
$this->deleteManyJointPermissionsForEntities($entities);
$this->createManyJointPermissions($entities, $roles);
}
/**
* Delete all the entity jointPermissions for a list of entities.
*
* @param Entity[] $entities
*/
protected function deleteManyJointPermissionsForEntities(array $entities)
{
$simpleEntities = $this->entitiesToSimpleEntities($entities);
$idsByType = $this->entitiesToTypeIdMap($simpleEntities);
DB::transaction(function () use ($idsByType) {
foreach ($idsByType as $type => $ids) {
foreach (array_chunk($ids, 1000) as $idChunk) {
DB::table('joint_permissions')
->where('entity_type', '=', $type)
->whereIn('entity_id', $idChunk)
->delete();
}
}
});
}
/**
* @param Entity[] $entities
*
* @return SimpleEntityData[]
*/
protected function entitiesToSimpleEntities(array $entities): array
{
$simpleEntities = [];
foreach ($entities as $entity) {
$attrs = $entity->getAttributes();
$simple = new SimpleEntityData();
$simple->id = $attrs['id'];
$simple->type = $entity->getMorphClass();
$simple->owned_by = $attrs['owned_by'] ?? 0;
$simple->book_id = $attrs['book_id'] ?? null;
$simple->chapter_id = $attrs['chapter_id'] ?? null;
$simpleEntities[] = $simple;
}
return $simpleEntities;
}
/**
* Create & Save entity jointPermissions for many entities and roles.
*
* @param Entity[] $originalEntities
* @param Role[] $roles
*/
protected function createManyJointPermissions(array $originalEntities, array $roles)
{
$entities = $this->entitiesToSimpleEntities($originalEntities);
$this->readyEntityCache($entities);
$jointPermissions = [];
// Fetch related entity permissions
$permissions = $this->getEntityPermissionsForEntities($entities);
// Create a mapping of explicit entity permissions
$permissionMap = [];
foreach ($permissions as $permission) {
$key = $permission->entity_type . ':' . $permission->entity_id . ':' . $permission->role_id;
$permissionMap[$key] = $permission->view;
}
// Create a mapping of role permissions
$rolePermissionMap = [];
foreach ($roles as $role) {
foreach ($role->permissions as $permission) {
$rolePermissionMap[$role->getRawAttribute('id') . ':' . $permission->getRawAttribute('name')] = true;
}
}
// Create Joint Permission Data
foreach ($entities as $entity) {
foreach ($roles as $role) {
$jointPermissions[] = $this->createJointPermissionData(
$entity,
$role->getRawAttribute('id'),
$permissionMap,
$rolePermissionMap,
$role->system_name === 'admin'
);
}
}
DB::transaction(function () use ($jointPermissions) {
foreach (array_chunk($jointPermissions, 1000) as $jointPermissionChunk) {
DB::table('joint_permissions')->insert($jointPermissionChunk);
}
});
}
/**
* From the given entity list, provide back a mapping of entity types to
* the ids of that given type. The type used is the DB morph class.
*
* @param SimpleEntityData[] $entities
*
* @return array<string, int[]>
*/
protected function entitiesToTypeIdMap(array $entities): array
{
$idsByType = [];
foreach ($entities as $entity) {
if (!isset($idsByType[$entity->type])) {
$idsByType[$entity->type] = [];
}
$idsByType[$entity->type][] = $entity->id;
}
return $idsByType;
}
/**
* Get the entity permissions for all the given entities.
*
* @param SimpleEntityData[] $entities
*
* @return EntityPermission[]
*/
protected function getEntityPermissionsForEntities(array $entities): array
{
$idsByType = $this->entitiesToTypeIdMap($entities);
$permissionFetch = EntityPermission::query()
->where(function (Builder $query) use ($idsByType) {
foreach ($idsByType as $type => $ids) {
$query->orWhere(function (Builder $query) use ($type, $ids) {
$query->where('entity_type', '=', $type)->whereIn('entity_id', $ids);
});
}
});
return $permissionFetch->get()->all();
}
/**
* Create entity permission data for an entity and role
* for a particular action.
*/
protected function createJointPermissionData(SimpleEntityData $entity, int $roleId, array $permissionMap, array $rolePermissionMap, bool $isAdminRole): array
{
$permissionPrefix = $entity->type . '-view';
$roleHasPermission = isset($rolePermissionMap[$roleId . ':' . $permissionPrefix . '-all']);
$roleHasPermissionOwn = isset($rolePermissionMap[$roleId . ':' . $permissionPrefix . '-own']);
if ($isAdminRole) {
return $this->createJointPermissionDataArray($entity, $roleId, true, true);
}
if ($this->entityPermissionsActiveForRole($permissionMap, $entity, $roleId)) {
$hasAccess = $this->mapHasActiveRestriction($permissionMap, $entity, $roleId);
return $this->createJointPermissionDataArray($entity, $roleId, $hasAccess, $hasAccess);
}
if ($entity->type === 'book' || $entity->type === 'bookshelf') {
return $this->createJointPermissionDataArray($entity, $roleId, $roleHasPermission, $roleHasPermissionOwn);
}
// For chapters and pages, Check if explicit permissions are set on the Book.
$book = $this->getBook($entity->book_id);
$hasExplicitAccessToParents = $this->mapHasActiveRestriction($permissionMap, $book, $roleId);
$hasPermissiveAccessToParents = !$this->entityPermissionsActiveForRole($permissionMap, $book, $roleId);
// For pages with a chapter, Check if explicit permissions are set on the Chapter
if ($entity->type === 'page' && $entity->chapter_id !== 0) {
$chapter = $this->getChapter($entity->chapter_id);
$chapterRestricted = $this->entityPermissionsActiveForRole($permissionMap, $chapter, $roleId);
$hasPermissiveAccessToParents = $hasPermissiveAccessToParents && !$chapterRestricted;
if ($chapterRestricted) {
$hasExplicitAccessToParents = $this->mapHasActiveRestriction($permissionMap, $chapter, $roleId);
}
}
return $this->createJointPermissionDataArray(
$entity,
$roleId,
($hasExplicitAccessToParents || ($roleHasPermission && $hasPermissiveAccessToParents)),
($hasExplicitAccessToParents || ($roleHasPermissionOwn && $hasPermissiveAccessToParents))
);
}
/**
* Check if entity permissions are defined within the given map, for the given entity and role.
* Checks for the default `role_id=0` backup option as a fallback.
*/
protected function entityPermissionsActiveForRole(array $permissionMap, SimpleEntityData $entity, int $roleId): bool
{
$keyPrefix = $entity->type . ':' . $entity->id . ':';
return isset($permissionMap[$keyPrefix . $roleId]) || isset($permissionMap[$keyPrefix . '0']);
}
/**
* Check for an active restriction in an entity map.
*/
protected function mapHasActiveRestriction(array $entityMap, SimpleEntityData $entity, int $roleId): bool
{
$roleKey = $entity->type . ':' . $entity->id . ':' . $roleId;
$defaultKey = $entity->type . ':' . $entity->id . ':0';
return $entityMap[$roleKey] ?? $entityMap[$defaultKey] ?? false;
}
/**
* Create an array of data with the information of an entity jointPermissions.
* Used to build data for bulk insertion.
*/
protected function createJointPermissionDataArray(SimpleEntityData $entity, int $roleId, bool $permissionAll, bool $permissionOwn): array
{
return [
'entity_id' => $entity->id,
'entity_type' => $entity->type,
'has_permission' => $permissionAll,
'has_permission_own' => $permissionOwn,
'owned_by' => $entity->owned_by,
'role_id' => $roleId,
];
}
}

View File

@@ -12,8 +12,6 @@ use BookStack\Traits\HasCreatorAndUpdater;
use BookStack\Traits\HasOwner;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\Builder as QueryBuilder;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Support\Facades\DB;
use InvalidArgumentException;
class PermissionApplicator
@@ -50,7 +48,7 @@ class PermissionApplicator
return $hasRolePermission;
}
$hasApplicableEntityPermissions = $this->hasEntityPermission($ownable, $userRoleIds, $user->id, $action);
$hasApplicableEntityPermissions = $this->hasEntityPermission($ownable, $userRoleIds, $action);
return is_null($hasApplicableEntityPermissions) ? $hasRolePermission : $hasApplicableEntityPermissions;
}
@@ -59,7 +57,7 @@ class PermissionApplicator
* Check if there are permissions that are applicable for the given entity item, action and roles.
* Returns null when no entity permissions are in force.
*/
protected function hasEntityPermission(Entity $entity, array $userRoleIds, int $userId, string $action): ?bool
protected function hasEntityPermission(Entity $entity, array $userRoleIds, string $action): ?bool
{
$this->ensureValidEntityAction($action);
@@ -68,63 +66,38 @@ class PermissionApplicator
return true;
}
// The array order here is very important due to the fact we walk up the chain
// in the flattening loop below. Earlier items in the chain have higher priority.
$typeIdList = [$entity->getMorphClass() . ':' . $entity->id];
// The chain order here is very important due to the fact we walk up the chain
// in the loop below. Earlier items in the chain have higher priority.
$chain = [$entity];
if ($entity instanceof Page && $entity->chapter_id) {
$typeIdList[] = 'chapter:' . $entity->chapter_id;
$chain[] = $entity->chapter;
}
if ($entity instanceof Page || $entity instanceof Chapter) {
$typeIdList[] = 'book:' . $entity->book_id;
$chain[] = $entity->book;
}
$relevantPermissions = EntityPermission::query()
->where(function (Builder $query) use ($typeIdList) {
foreach ($typeIdList as $typeId) {
$query->orWhere(function (Builder $query) use ($typeId) {
[$type, $id] = explode(':', $typeId);
$query->where('entity_type', '=', $type)
->where('entity_id', '=', $id);
});
}
})->where(function (Builder $query) use ($userRoleIds, $userId) {
$query->whereIn('role_id', $userRoleIds)
->orWhere('user_id', '=', $userId)
->orWhere(function (Builder $query) {
$query->whereNull(['role_id', 'user_id']);
});
})->get(['entity_id', 'entity_type', 'role_id', 'user_id', $action])
->all();
foreach ($chain as $currentEntity) {
$allowedByRoleId = $currentEntity->permissions()
->whereIn('role_id', [0, ...$userRoleIds])
->pluck($action, 'role_id');
$permissionMap = new EntityPermissionMap($relevantPermissions);
$permitsByType = ['user' => [], 'fallback' => [], 'role' => []];
// Collapse and simplify permission structure
foreach ($typeIdList as $typeId) {
$permissions = $permissionMap->getForEntity($typeId);
foreach ($permissions as $permission) {
$related = $permission->getAssignedType();
$relatedId = $permission->getAssignedTypeId();
if (!isset($permitsByType[$related][$relatedId])) {
$permitsByType[$related][$relatedId] = $permission->$action;
}
// Continue up the chain if no applicable entity permission overrides.
if ($allowedByRoleId->isEmpty()) {
continue;
}
}
// Return user-level permission if exists
if (count($permitsByType['user']) > 0) {
return boolval(array_values($permitsByType['user'])[0]);
}
// If we have user-role-specific permissions set, allow if any of those
// role permissions allow access.
$hasDefault = $allowedByRoleId->has(0);
if (!$hasDefault || $allowedByRoleId->count() > 1) {
return $allowedByRoleId->search(function (bool $allowed, int $roleId) {
return $roleId !== 0 && $allowed;
}) !== false;
}
// Return grant or reject from role-level if exists
if (count($permitsByType['role']) > 0) {
return boolval(max($permitsByType['role']));
}
// Return fallback permission if exists
if (count($permitsByType['fallback']) > 0) {
return boolval($permitsByType['fallback'][0]);
// Otherwise, return the default "Other roles" fallback value.
return $allowedByRoleId->get(0);
}
return null;
@@ -140,10 +113,7 @@ class PermissionApplicator
$permissionQuery = EntityPermission::query()
->where($action, '=', true)
->where(function (Builder $query) {
$query->whereIn('role_id', $this->getCurrentUserRoleIds())
->orWhere('user_id', '=', $this->currentUser()->id);
});
->whereIn('role_id', $this->getCurrentUserRoleIds());
if (!empty($entityClass)) {
/** @var Entity $entityInstance */
@@ -160,140 +130,18 @@ class PermissionApplicator
* Limit the given entity query so that the query will only
* return items that the user has view permission for.
*/
public function restrictEntityQuery(Builder $query, string $morphClass): Builder
public function restrictEntityQuery(Builder $query): Builder
{
$this->applyPermissionsToQuery($query, $query->getModel()->getTable(), $morphClass, 'id', '');
return $query;
}
/**
* @param Builder|QueryBuilder $query
*/
protected function applyPermissionsToQuery($query, string $queryTable, string $entityTypeLimiter, string $entityIdColumn, string $entityTypeColumn): void
{
if ($this->currentUser()->hasSystemRole('admin')) {
return;
}
$this->applyFallbackJoin($query, $queryTable, $entityTypeLimiter, $entityIdColumn, $entityTypeColumn);
$this->applyRoleJoin($query, $queryTable, $entityTypeLimiter, $entityIdColumn, $entityTypeColumn);
$this->applyUserJoin($query, $queryTable, $entityTypeLimiter, $entityIdColumn, $entityTypeColumn);
$this->applyPermissionWhereFilter($query, $queryTable, $entityTypeLimiter, $entityTypeColumn);
}
/**
* Apply the where condition to a permission restricting query, to limit based upon the values of the joined
* permission data. Query must have joins pre-applied.
* Either entityTypeLimiter or entityTypeColumn should be supplied, with the other empty.
* Both should not be applied since that would conflict upon intent.
* @param Builder|QueryBuilder $query
*/
protected function applyPermissionWhereFilter($query, string $queryTable, string $entityTypeLimiter, string $entityTypeColumn)
{
$abilities = ['all' => [], 'own' => []];
$types = $entityTypeLimiter ? [$entityTypeLimiter] : ['page', 'chapter', 'bookshelf', 'book'];
$fullEntityTypeColumn = $queryTable . '.' . $entityTypeColumn;
foreach ($types as $type) {
$abilities['all'][$type] = userCan($type . '-view-all');
$abilities['own'][$type] = userCan($type . '-view-own');
}
$abilities['all'] = array_filter($abilities['all']);
$abilities['own'] = array_filter($abilities['own']);
$query->where(function (Builder $query) use ($abilities, $fullEntityTypeColumn, $entityTypeColumn) {
$query->where('perms_user', '=', 1)
->orWhere(function (Builder $query) {
$query->whereNull('perms_user')->where('perms_role', '=', 1);
})->orWhere(function (Builder $query) {
$query->whereNull(['perms_user', 'perms_role'])
->where('perms_fallback', '=', 1);
});
if (count($abilities['all']) > 0) {
$query->orWhere(function (Builder $query) use ($abilities, $fullEntityTypeColumn, $entityTypeColumn) {
$query->whereNull(['perms_user', 'perms_role', 'perms_fallback']);
if ($entityTypeColumn) {
$query->whereIn($fullEntityTypeColumn, array_keys($abilities['all']));
}
});
}
if (count($abilities['own']) > 0) {
$query->orWhere(function (Builder $query) use ($abilities, $fullEntityTypeColumn, $entityTypeColumn) {
$query->whereNull(['perms_user', 'perms_role', 'perms_fallback'])
->where('owned_by', '=', $this->currentUser()->id);
if ($entityTypeColumn) {
$query->whereIn($fullEntityTypeColumn, array_keys($abilities['all']));
}
});
}
return $query->where(function (Builder $parentQuery) {
$parentQuery->whereHas('jointPermissions', function (Builder $permissionQuery) {
$permissionQuery->whereIn('role_id', $this->getCurrentUserRoleIds())
->where(function (Builder $query) {
$this->addJointHasPermissionCheck($query, $this->currentUser()->id);
});
});
});
}
/**
* @param Builder|QueryBuilder $query
*/
protected function applyPermissionJoin(callable $joinCallable, string $subAlias, $query, string $queryTable, string $entityTypeLimiter, string $entityIdColumn, string $entityTypeColumn)
{
$joinCondition = $this->getJoinCondition($queryTable, $subAlias, $entityIdColumn, $entityTypeColumn);
$query->joinSub(function (QueryBuilder $joinQuery) use ($joinCallable, $entityTypeLimiter) {
$joinQuery->select(['entity_id', 'entity_type'])->from('entity_permissions_collapsed')
->groupBy('entity_id', 'entity_type');
$joinCallable($joinQuery);
if ($entityTypeLimiter) {
$joinQuery->where('entity_type', '=', $entityTypeLimiter);
}
}, $subAlias, $joinCondition, null, null, 'left');
}
/**
* @param Builder|QueryBuilder $query
*/
protected function applyUserJoin($query, string $queryTable, string $entityTypeLimiter, string $entityIdColumn, string $entityTypeColumn)
{
$this->applyPermissionJoin(function (QueryBuilder $joinQuery) {
$joinQuery->selectRaw('max(view) as perms_user')
->where('user_id', '=', $this->currentUser()->id);
}, 'p_u', $query, $queryTable, $entityTypeLimiter, $entityIdColumn, $entityTypeColumn);
}
/**
* @param Builder|QueryBuilder $query
*/
protected function applyRoleJoin($query, string $queryTable, string $entityTypeLimiter, string $entityIdColumn, string $entityTypeColumn)
{
$this->applyPermissionJoin(function (QueryBuilder $joinQuery) {
$joinQuery->selectRaw('max(view) as perms_role')
->whereIn('role_id', $this->getCurrentUserRoleIds());
}, 'p_r', $query, $queryTable, $entityTypeLimiter, $entityIdColumn, $entityTypeColumn);
}
/**
* @param Builder|QueryBuilder $query
*/
protected function applyFallbackJoin($query, string $queryTable, string $entityTypeLimiter, string $entityIdColumn, string $entityTypeColumn)
{
$this->applyPermissionJoin(function (QueryBuilder $joinQuery) {
$joinQuery->selectRaw('max(view) as perms_fallback')
->whereNull(['role_id', 'user_id']);
}, 'p_f', $query, $queryTable, $entityTypeLimiter, $entityIdColumn, $entityTypeColumn);
}
protected function getJoinCondition(string $queryTable, string $joinTableName, string $entityIdColumn, string $entityTypeColumn): callable
{
return function (JoinClause $join) use ($queryTable, $joinTableName, $entityIdColumn, $entityTypeColumn) {
$join->on($queryTable . '.' . $entityIdColumn, '=', $joinTableName . '.entity_id');
if ($entityTypeColumn) {
$join->on($queryTable . '.' . $entityTypeColumn, '=', $joinTableName . '.entity_type');
}
};
}
/**
* Extend the given page query to ensure draft items are not visible
* unless created by the given user.
@@ -318,23 +166,30 @@ class PermissionApplicator
*/
public function restrictEntityRelationQuery($query, string $tableName, string $entityIdColumn, string $entityTypeColumn)
{
$query->leftJoinSub(function (QueryBuilder $query) {
$query->select(['id as entity_id', DB::raw("'page' as entity_type"), 'owned_by', 'deleted_at', 'draft'])->from('pages');
$tablesByType = ['page' => 'pages', 'book' => 'books', 'chapter' => 'chapters', 'bookshelf' => 'bookshelves'];
foreach ($tablesByType as $type => $table) {
$query->unionAll(function (QueryBuilder $query) use ($type, $table) {
$query->select(['id as entity_id', DB::raw("'{$type}' as entity_type"), 'owned_by', 'deleted_at', DB::raw('0 as draft')])->from($table);
$tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn, 'entityTypeColumn' => $entityTypeColumn];
$pageMorphClass = (new Page())->getMorphClass();
$q = $query->whereExists(function ($permissionQuery) use (&$tableDetails) {
/** @var Builder $permissionQuery */
$permissionQuery->select(['role_id'])->from('joint_permissions')
->whereColumn('joint_permissions.entity_id', '=', $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
->whereColumn('joint_permissions.entity_type', '=', $tableDetails['tableName'] . '.' . $tableDetails['entityTypeColumn'])
->whereIn('joint_permissions.role_id', $this->getCurrentUserRoleIds())
->where(function (QueryBuilder $query) {
$this->addJointHasPermissionCheck($query, $this->currentUser()->id);
});
})->where(function ($query) use ($tableDetails, $pageMorphClass) {
/** @var Builder $query */
$query->where($tableDetails['entityTypeColumn'], '!=', $pageMorphClass)
->orWhereExists(function (QueryBuilder $query) use ($tableDetails, $pageMorphClass) {
$query->select('id')->from('pages')
->whereColumn('pages.id', '=', $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
->where($tableDetails['tableName'] . '.' . $tableDetails['entityTypeColumn'], '=', $pageMorphClass)
->where('pages.draft', '=', false);
});
}
}, 'entities', function (JoinClause $join) use ($tableName, $entityIdColumn, $entityTypeColumn) {
$join->on($tableName . '.' . $entityIdColumn, '=', 'entities.entity_id')
->on($tableName . '.' . $entityTypeColumn, '=', 'entities.entity_type');
});
$this->applyPermissionsToQuery($query, $tableName, '', $entityIdColumn, $entityTypeColumn);
// TODO - Test page draft access (Might allow drafts which should not be seen)
return $query;
return $q;
}
/**
@@ -345,12 +200,50 @@ class PermissionApplicator
*/
public function restrictPageRelationQuery(Builder $query, string $tableName, string $pageIdColumn): Builder
{
$fullPageIdColumn = $tableName . '.' . $pageIdColumn;
$morphClass = (new Page())->getMorphClass();
$this->applyPermissionsToQuery($query, $tableName, $morphClass, $pageIdColumn, '');
// TODO - Draft display
// TODO - Likely need owned_by entity join workaround as used above
return $query;
$existsQuery = function ($permissionQuery) use ($fullPageIdColumn, $morphClass) {
/** @var Builder $permissionQuery */
$permissionQuery->select('joint_permissions.role_id')->from('joint_permissions')
->whereColumn('joint_permissions.entity_id', '=', $fullPageIdColumn)
->where('joint_permissions.entity_type', '=', $morphClass)
->whereIn('joint_permissions.role_id', $this->getCurrentUserRoleIds())
->where(function (QueryBuilder $query) {
$this->addJointHasPermissionCheck($query, $this->currentUser()->id);
});
};
$q = $query->where(function ($query) use ($existsQuery, $fullPageIdColumn) {
$query->whereExists($existsQuery)
->orWhere($fullPageIdColumn, '=', 0);
});
// Prevent visibility of non-owned draft pages
$q->whereExists(function (QueryBuilder $query) use ($fullPageIdColumn) {
$query->select('id')->from('pages')
->whereColumn('pages.id', '=', $fullPageIdColumn)
->where(function (QueryBuilder $query) {
$query->where('pages.draft', '=', false)
->orWhere('pages.owned_by', '=', $this->currentUser()->id);
});
});
return $q;
}
/**
* Add the query for checking the given user id has permission
* within the join_permissions table.
*
* @param QueryBuilder|Builder $query
*/
protected function addJointHasPermissionCheck($query, int $userIdToCheck)
{
$query->where('joint_permissions.has_permission', '=', true)->orWhere(function ($query) use ($userIdToCheck) {
$query->where('joint_permissions.has_permission_own', '=', true)
->where('joint_permissions.owned_by', '=', $userIdToCheck);
});
}
/**

View File

@@ -21,32 +21,19 @@ class PermissionFormData
{
return $this->entity->permissions()
->with('role')
->whereNotNull('role_id')
->where('role_id', '!=', 0)
->get()
->sortBy('role.display_name')
->all();
}
/**
* Get the permissions with assigned users.
*/
public function permissionsWithUsers(): array
{
return $this->entity->permissions()
->with('user')
->whereNotNull('user_id')
->get()
->sortBy('user.name')
->all();
}
/**
* Get the roles that don't yet have specific permissions for the
* entity we're managing permissions for.
*/
public function rolesNotAssigned(): array
{
$assigned = $this->entity->permissions()->whereNotNull('role_id')->pluck('role_id');
$assigned = $this->entity->permissions()->pluck('role_id');
return Role::query()
->where('system_name', '!=', 'admin')
->whereNotIn('id', $assigned)
@@ -62,19 +49,20 @@ class PermissionFormData
{
/** @var ?EntityPermission $permission */
$permission = $this->entity->permissions()
->whereNull(['role_id', 'user_id'])
->where('role_id', '=', 0)
->first();
return $permission ?? (new EntityPermission());
}
/**
* Check if the "Everyone else" option is inheriting default role system permissions.
* Is determined by any system entity_permission existing for the current entity.
* Get the "Everyone Else" role entry.
*/
public function everyoneElseInheriting(): bool
public function everyoneElseRole(): Role
{
return !$this->entity->permissions()
->whereNull(['role_id', 'user_id'])
->exists();
return (new Role())->forceFill([
'id' => 0,
'display_name' => trans('entities.permissions_role_everyone_else'),
'description' => trans('entities.permissions_role_everyone_else_desc'),
]);
}
}

View File

@@ -11,13 +11,13 @@ use Illuminate\Database\Eloquent\Collection;
class PermissionsRepo
{
protected CollapsedPermissionBuilder $permissionBuilder;
protected array $systemRoles = ['admin', 'public'];
protected JointPermissionBuilder $permissionBuilder;
protected $systemRoles = ['admin', 'public'];
/**
* PermissionsRepo constructor.
*/
public function __construct(CollapsedPermissionBuilder $permissionBuilder)
public function __construct(JointPermissionBuilder $permissionBuilder)
{
$this->permissionBuilder = $permissionBuilder;
}
@@ -57,6 +57,7 @@ class PermissionsRepo
$permissions = isset($roleData['permissions']) ? array_keys($roleData['permissions']) : [];
$this->assignRolePermissions($role, $permissions);
$this->permissionBuilder->rebuildForRole($role);
Activity::add(ActivityType::ROLE_CREATE, $role);
@@ -87,6 +88,7 @@ class PermissionsRepo
$role->fill($roleData);
$role->mfa_enforced = ($roleData['mfa_enforced'] ?? 'false') === 'true';
$role->save();
$this->permissionBuilder->rebuildForRole($role);
Activity::add(ActivityType::ROLE_UPDATE, $role);
}
@@ -138,7 +140,7 @@ class PermissionsRepo
}
$role->entityPermissions()->delete();
$role->collapsedPermissions()->delete();
$role->jointPermissions()->delete();
Activity::add(ActivityType::ROLE_DELETE, $role);
$role->delete();
}

View File

@@ -6,6 +6,7 @@ class SimpleEntityData
{
public int $id;
public string $type;
public int $owned_by;
public ?int $book_id;
public ?int $chapter_id;
}

View File

@@ -2,8 +2,8 @@
namespace BookStack\Auth;
use BookStack\Auth\Permissions\CollapsedPermission;
use BookStack\Auth\Permissions\EntityPermission;
use BookStack\Auth\Permissions\JointPermission;
use BookStack\Auth\Permissions\RolePermission;
use BookStack\Interfaces\Loggable;
use BookStack\Model;
@@ -39,6 +39,14 @@ class Role extends Model implements Loggable
return $this->belongsToMany(User::class)->orderBy('name', 'asc');
}
/**
* Get all related JointPermissions.
*/
public function jointPermissions(): HasMany
{
return $this->hasMany(JointPermission::class);
}
/**
* The RolePermissions that belong to the role.
*/
@@ -55,14 +63,6 @@ class Role extends Model implements Loggable
return $this->hasMany(EntityPermission::class);
}
/**
* Get all related entity collapsed permissions.
*/
public function collapsedPermissions(): HasMany
{
return $this->hasMany(CollapsedPermission::class);
}
/**
* Check if this role has a permission.
*/

View File

@@ -5,8 +5,6 @@ namespace BookStack\Auth;
use BookStack\Actions\Favourite;
use BookStack\Api\ApiToken;
use BookStack\Auth\Access\Mfa\MfaValue;
use BookStack\Auth\Permissions\CollapsedPermission;
use BookStack\Auth\Permissions\EntityPermission;
use BookStack\Entities\Tools\SlugGenerator;
use BookStack\Interfaces\Loggable;
use BookStack\Interfaces\Sluggable;
@@ -300,22 +298,6 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
}, 'activities', 'users.id', '=', 'activities.user_id');
}
/**
* Get the entity permissions assigned to this specific user.
*/
public function entityPermissions(): HasMany
{
return $this->hasMany(EntityPermission::class);
}
/**
* Get all related entity collapsed permissions.
*/
public function collapsedPermissions(): HasMany
{
return $this->hasMany(CollapsedPermission::class);
}
/**
* Get the url for editing this user.
*/

View File

@@ -153,8 +153,6 @@ class UserRepo
$user->apiTokens()->delete();
$user->favourites()->delete();
$user->mfaValues()->delete();
$user->collapsedPermissions()->delete();
$user->entityPermissions()->delete();
$user->delete();
// Delete user profile images
@@ -236,6 +234,8 @@ class UserRepo
*/
protected function setUserRoles(User $user, array $roles)
{
$roles = array_filter(array_values($roles));
if ($this->demotingLastAdmin($user, $roles)) {
throw new UserUpdateException(trans('errors.role_cannot_remove_only_admin'), $user->getEditUrl());
}

View File

@@ -2,7 +2,7 @@
namespace BookStack\Console\Commands;
use BookStack\Auth\Permissions\CollapsedPermissionBuilder;
use BookStack\Auth\Permissions\JointPermissionBuilder;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
@@ -22,12 +22,12 @@ class RegeneratePermissions extends Command
*/
protected $description = 'Regenerate all system permissions';
protected CollapsedPermissionBuilder $permissionBuilder;
protected JointPermissionBuilder $permissionBuilder;
/**
* Create a new command instance.
*/
public function __construct(CollapsedPermissionBuilder $permissionBuilder)
public function __construct(JointPermissionBuilder $permissionBuilder)
{
$this->permissionBuilder = $permissionBuilder;
parent::__construct();

View File

@@ -7,9 +7,9 @@ use BookStack\Actions\Comment;
use BookStack\Actions\Favourite;
use BookStack\Actions\Tag;
use BookStack\Actions\View;
use BookStack\Auth\Permissions\CollapsedPermission;
use BookStack\Auth\Permissions\EntityPermission;
use BookStack\Auth\Permissions\CollapsedPermissionBuilder;
use BookStack\Auth\Permissions\JointPermission;
use BookStack\Auth\Permissions\JointPermissionBuilder;
use BookStack\Auth\Permissions\PermissionApplicator;
use BookStack\Entities\Tools\SlugGenerator;
use BookStack\Interfaces\Deletable;
@@ -69,7 +69,7 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
*/
public function scopeVisible(Builder $query): Builder
{
return app()->make(PermissionApplicator::class)->restrictEntityQuery($query, $this->getMorphClass());
return app()->make(PermissionApplicator::class)->restrictEntityQuery($query);
}
/**
@@ -187,11 +187,11 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
}
/**
* Get the entity collapsed permissions this is connected to.
* Get the entity jointPermissions this is connected to.
*/
public function collapsedPermissions(): MorphMany
public function jointPermissions(): MorphMany
{
return $this->morphMany(CollapsedPermission::class, 'entity');
return $this->morphMany(JointPermission::class, 'entity');
}
/**
@@ -292,7 +292,7 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
*/
public function rebuildPermissions()
{
app()->make(CollapsedPermissionBuilder::class)->rebuildForEntity(clone $this);
app()->make(JointPermissionBuilder::class)->rebuildForEntity(clone $this);
}
/**

View File

@@ -10,6 +10,7 @@ use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Models\Entity;
use BookStack\Facades\Activity;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
class PermissionsUpdater
{
@@ -57,30 +58,13 @@ class PermissionsUpdater
protected function formatPermissionsFromRequestToEntityPermissions(array $permissions): array
{
$formatted = [];
$columnsByType = [
'role' => 'role_id',
'user' => 'user_id',
'fallback' => '',
];
foreach ($permissions as $type => $byId) {
$column = $columnsByType[$type] ?? null;
if (is_null($column)) {
continue;
}
foreach ($byId as $id => $info) {
$entityPermissionData = [];
if (!empty($column)) {
$entityPermissionData[$column] = $id;
}
foreach (EntityPermission::PERMISSIONS as $permission) {
$entityPermissionData[$permission] = (($info[$permission] ?? false) === "true");
}
$formatted[] = $entityPermissionData;
foreach ($permissions as $roleId => $info) {
$entityPermissionData = ['role_id' => $roleId];
foreach (EntityPermission::PERMISSIONS as $permission) {
$entityPermissionData[$permission] = (($info[$permission] ?? false) === "true");
}
$formatted[] = $entityPermissionData;
}
return $formatted;

View File

@@ -372,7 +372,7 @@ class TrashCan
$entity->permissions()->delete();
$entity->tags()->delete();
$entity->comments()->delete();
$entity->collapsedPermissions()->delete();
$entity->jointPermissions()->delete();
$entity->searchTerms()->delete();
$entity->deletions()->delete();
$entity->favourites()->delete();

View File

@@ -5,7 +5,6 @@ namespace BookStack\Http\Controllers;
use BookStack\Auth\Permissions\EntityPermission;
use BookStack\Auth\Permissions\PermissionFormData;
use BookStack\Auth\Role;
use BookStack\Auth\User;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Models\Chapter;
@@ -163,35 +162,10 @@ class PermissionsController extends Controller
{
$this->checkPermissionOr('restrictions-manage-all', fn() => userCan('restrictions-manage-own'));
/** @var Role $role */
$role = Role::query()->findOrFail($roleId);
return view('form.entity-permissions-row', [
'modelType' => 'role',
'modelId' => $role->id,
'modelName' => $role->display_name,
'modelDescription' => $role->description,
'permission' => new EntityPermission(),
'entityType' => $entityType,
'inheriting' => false,
]);
}
/**
* Get an empty entity permissions form row for the given user.
*/
public function formRowForUser(string $entityType, string $userId)
{
$this->checkPermissionOr('restrictions-manage-all', fn() => userCan('restrictions-manage-own'));
/** @var User $user */
$user = User::query()->findOrFail($userId);
return view('form.entity-permissions-row', [
'modelType' => 'user',
'modelId' => $user->id,
'modelName' => $user->name,
'modelDescription' => '',
'role' => $role,
'permission' => new EntityPermission(),
'entityType' => $entityType,
'inheriting' => false,

View File

@@ -223,7 +223,7 @@ class SearchRunner
});
$subQuery->groupBy('entity_type', 'entity_id');
$entityQuery->joinSub($subQuery, 's', 'id', '=', 's.entity_id');
$entityQuery->joinSub($subQuery, 's', 'id', '=', 'entity_id');
$entityQuery->addSelect('s.score');
$entityQuery->orderBy('score', 'desc');
}

View File

@@ -55,9 +55,8 @@ function hasAppAccess(): bool
}
/**
* Check if the current user has a permission.
* Checks a generic role permission or, if an ownable model is passed in, it will
* check against the given entity model, taking into account entity-level permissions.
* Check if the current user has a permission. If an ownable element
* is passed in the jointPermissions are checked against that particular item.
*/
function userCan(string $permission, Model $ownable = null): bool
{

View File

@@ -1,6 +1,9 @@
project_identifier: bookstack
base_path: .
preserve_hierarchy: false
pull_request_title: Updated translations with latest Crowdin changes
pull_request_labels:
- ":earth_africa: Translations"
files:
- source: /resources/lang/en/*.php
translation: /resources/lang/%two_letters_code%/%original_file_name%

View File

@@ -1,47 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class AddUserIdToEntityPermissions extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('entity_permissions', function (Blueprint $table) {
$table->unsignedInteger('role_id')->nullable()->default(null)->change();
$table->unsignedInteger('user_id')->nullable()->default(null)->index();
});
DB::table('entity_permissions')
->where('role_id', '=', 0)
->update(['role_id' => null]);
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
DB::table('entity_permissions')
->whereNull('role_id')
->update(['role_id' => 0]);
DB::table('entity_permissions')
->whereNotNull('user_id')
->delete();
Schema::table('entity_permissions', function (Blueprint $table) {
$table->unsignedInteger('role_id')->nullable(false)->change();
$table->dropColumn('user_id');
});
}
}

View File

@@ -1,40 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateCollapsedRolePermissionsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
// TODO - Drop joint permissions
// TODO - Run collapsed table rebuild.
Schema::create('entity_permissions_collapsed', function (Blueprint $table) {
$table->id();
$table->unsignedInteger('role_id')->nullable()->index();
$table->unsignedInteger('user_id')->nullable()->index();
$table->string('entity_type');
$table->unsignedInteger('entity_id');
$table->boolean('view')->index();
$table->index(['entity_type', 'entity_id']);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('entity_permissions_collapsed');
}
}

View File

@@ -3,7 +3,7 @@
namespace Database\Seeders;
use BookStack\Api\ApiToken;
use BookStack\Auth\Permissions\CollapsedPermissionBuilder;
use BookStack\Auth\Permissions\JointPermissionBuilder;
use BookStack\Auth\Permissions\RolePermission;
use BookStack\Auth\Role;
use BookStack\Auth\User;
@@ -69,7 +69,7 @@ class DummyContentSeeder extends Seeder
]);
$token->save();
app(CollapsedPermissionBuilder::class)->rebuildForAll();
app(JointPermissionBuilder::class)->rebuildForAll();
app(SearchIndex::class)->indexAllEntities();
}
}

View File

@@ -2,7 +2,7 @@
namespace Database\Seeders;
use BookStack\Auth\Permissions\CollapsedPermissionBuilder;
use BookStack\Auth\Permissions\JointPermissionBuilder;
use BookStack\Auth\Role;
use BookStack\Auth\User;
use BookStack\Entities\Models\Book;
@@ -35,7 +35,7 @@ class LargeContentSeeder extends Seeder
$largeBook->chapters()->saveMany($chapters);
$all = array_merge([$largeBook], array_values($pages->all()), array_values($chapters->all()));
app()->make(CollapsedPermissionBuilder::class)->rebuildForEntity($largeBook);
app()->make(JointPermissionBuilder::class)->rebuildForEntity($largeBook);
app()->make(SearchIndex::class)->indexEntities($all);
}
}

View File

@@ -1,421 +0,0 @@
# Permission Scenario Testing
Due to complexity that can arise in the various combinations of permissions, this document details scenarios and their expected results.
Test cases are written ability abstract, since all abilities should act the same in theory. Functional test cases may test abilities separate due to implementation differences.
Tests are categorised by the most specific element involved in the scenario, where the below list is most specific to least:
- User entity permissions.
- Role entity permissions.
- Fallback entity permissions.
- Role permissions.
- TODO - Test fallback in the context of the above.
## General Permission Logical Rules
The below are some general rules we follow to standardise the behaviour of permissions in the platform:
- Most specific permission application (as above) take priority and can deny less specific permissions.
- Parent user/role entity permissions that may be inherited, are considered to essentially be applied on the item they are inherited to unless a lower level has its own permission rule for an already specific role/user.
- Where both grant and deny exist at the same specificity, we side towards grant.
## Cases
### Content Role Permissions
These are tests related to item/entity permissions that are set only at a role level.
#### test_01_allow
- Role A has role all-page permission.
- User has Role A.
User granted page permission.
#### test_02_deny
- Role A has no page permission.
- User has Role A.
User denied page permission.
#### test_10_allow_on_own_with_own
- Role A has role own-page permission.
- User has Role A.
- User is owner of page.
User granted page permission.
#### test_11_deny_on_other_with_own
- Role A has role own-page permission.
- User has Role A.
- User is not owner of page.
User denied page permission.
#### test_20_multiple_role_conflicting_all
- Role A has role all-page permission.
- Role B has no page permission.
- User has Role A & B.
User granted page permission.
#### test_21_multiple_role_conflicting_own
- Role A has role own-page permission.
- Role B has no page permission.
- User has Role A & B.
- User is owner of page.
User granted page permission.
---
### Entity Role Permissions
These are tests related to entity-level role-specific permission overrides.
#### test_01_explicit_allow
- Page permissions have inherit disabled.
- Role A has entity allow page permission.
- User has Role A.
User granted page permission.
#### test_02_explicit_deny
- Page permissions have inherit disabled.
- Role A has entity deny page permission.
- User has Role A.
User denied page permission.
#### test_03_same_level_conflicting
- Page permissions have inherit disabled.
- Role A has entity allow page permission.
- Role B has entity deny page permission.
- User has both Role A & B.
User granted page permission.
Explicit grant overrides entity deny at same level.
#### test_20_inherit_allow
- Page permissions have inherit enabled.
- Chapter permissions has inherit disabled.
- Role A has entity allow chapter permission.
- User has Role A.
User granted page permission.
#### test_21_inherit_deny
- Page permissions have inherit enabled.
- Chapter permissions has inherit disabled.
- Role A has entity deny chapter permission.
- User has Role A.
User denied page permission.
#### test_22_same_level_conflict_inherit
- Page permissions have inherit enabled.
- Chapter permissions has inherit disabled.
- Role A has entity deny chapter permission.
- Role B has entity allow chapter permission.
- User has both Role A & B.
User granted page permission.
#### test_30_child_inherit_override_allow
- Page permissions have inherit enabled.
- Chapter permissions has inherit disabled.
- Role A has entity deny chapter permission.
- Role A has entity allow page permission.
- User has Role A.
User granted page permission.
#### test_31_child_inherit_override_deny
- Page permissions have inherit enabled.
- Chapter permissions has inherit disabled.
- Role A has entity allow chapter permission.
- Role A has entity deny page permission.
- User has Role A.
User denied page permission.
#### test_40_multi_role_inherit_conflict_override_deny
- Page permissions have inherit enabled.
- Chapter permissions has inherit disabled.
- Role A has entity deny page permission.
- Role B has entity allow chapter permission.
- User has Role A & B.
User granted page permission.
#### test_41_multi_role_inherit_conflict_retain_allow
- Page permissions have inherit enabled.
- Chapter permissions has inherit disabled.
- Role A has entity allow page permission.
- Role B has entity deny chapter permission.
- User has Role A & B.
User granted page permission.
#### test_50_role_override_allow
- Page permissions have inherit enabled.
- Role A has no page role permission.
- Role A has entity allow page permission.
- User has Role A.
User granted page permission.
#### test_51_role_override_deny
- Page permissions have inherit enabled.
- Role A has no page-view-all role permission.
- Role A has entity deny page permission.
- User has Role A.
User denied page permission.
#### test_60_inherited_role_override_allow
- Page permissions have inherit enabled.
- Chapter permissions have inherit enabled.
- Role A has no page role permission.
- Role A has entity allow chapter permission.
- User has Role A.
User granted page permission.
#### test_61_inherited_role_override_deny
- Page permissions have inherit enabled.
- Chapter permissions have inherit enabled.
- Role A has page role permission.
- Role A has entity denied chapter permission.
- User has Role A.
User denied page permission.
#### test_62_inherited_role_override_deny_on_own
- Page permissions have inherit enabled.
- Chapter permissions have inherit enabled.
- Role A has own-page role permission.
- Role A has entity denied chapter permission.
- User has Role A.
- User owns Page.
User denied page permission.
#### test_70_multi_role_inheriting_deny
- Page permissions have inherit enabled.
- Role A has all page role permission.
- Role B has entity denied page permission.
- User has Role A and B.
User denied page permission.
#### test_80_multi_role_inherited_deny_via_parent
- Page permissions have inherit enabled.
- Chapter permissions have inherit enabled.
- Role A has all-pages role permission.
- Role B has entity denied chapter permission.
- User has Role A & B.
User denied page permission.
---
### Entity User Permissions
These are tests related to entity-level user-specific permission overrides.
#### test_01_explicit_allow
- Page permissions have inherit disabled.
- User has entity allow page permission.
User granted page permission.
#### test_02_explicit_deny
- Page permissions have inherit disabled.
- User has entity deny page permission.
User denied page permission.
#### test_10_allow_inherit
- Page permissions have inherit enabled.
- Chapter permissions have inherit disabled.
- User has entity allow chapter permission.
User granted page permission.
#### test_11_deny_inherit
- Page permissions have inherit enabled.
- Chapter permissions have inherit disabled.
- User has entity deny chapter permission.
User denied page permission.
#### test_12_allow_inherit_override
- Page permissions have inherit enabled.
- Chapter permissions have inherit disabled.
- User has entity deny chapter permission.
- User has entity allow page permission.
User granted page permission.
#### test_13_deny_inherit_override
- Page permissions have inherit enabled.
- Chapter permissions have inherit disabled.
- User has entity allow chapter permission.
- User has entity deny page permission.
User denied page permission.
#### test_40_entity_role_override_allow
- Page permissions have inherit disabled.
- User has entity allow page permission.
- Role A has entity deny page permission.
- User has role A.
User granted page permission.
#### test_41_entity_role_override_deny
- Page permissions have inherit disabled.
- User has entity deny page permission.
- Role A has entity allow page permission.
- User has role A.
User denied page permission.
#### test_42_entity_role_override_allow_via_inherit
- Page permissions have inherit enabled.
- Chapter permissions have inherit disabled.
- User has entity allow chapter permission.
- Role A has entity deny page permission.
- User has role A.
User granted page permission.
#### test_43_entity_role_override_deny_via_inherit
- Page permissions have inherit enabled.
- Chapter permissions have inherit disabled.
- User has entity deny chapter permission.
- Role A has entity allow page permission.
- User has role A.
User denied page permission.
#### test_50_role_override_allow
- Page permissions have inherit enabled.
- Role A has no page role permission.
- User has entity allow page permission.
- User has Role A.
User granted page permission.
#### test_51_role_override_deny
- Page permissions have inherit enabled.
- Role A has all-page role permission.
- User has entity deny page permission.
- User has Role A.
User denied page permission.
#### test_60_inherited_role_override_allow
- Page permissions have inherit enabled.
- Role A has no page role permission.
- User has entity allow chapter permission.
- User has Role A.
User granted page permission.
#### test_61_inherited_role_override_deny
- Page permissions have inherit enabled.
- Role A has view-all page role permission.
- User has entity deny chapter permission.
- User has Role A.
User denied page permission.
#### test_61_inherited_role_override_deny_on_own
- Page permissions have inherit enabled.
- Role A has view-own page role permission.
- User has entity deny chapter permission.
- User has Role A.
- User owns Page.
User denied page permission.
#### test_70_all_override_allow
- Page permissions have inherit enabled.
- Role A has no page role permission.
- Role A has entity deny page permission.
- User has entity allow page permission.
- User has Role A.
User granted page permission.
#### test_71_all_override_deny
- Page permissions have inherit enabled.
- Role A has page-all role permission.
- Role A has entity allow page permission.
- User has entity deny page permission.
- User has Role A.
User denied page permission.
#### test_80_inherited_all_override_allow
- Page permissions have inherit enabled.
- Role A has no page role permission.
- Role A has entity deny chapter permission.
- User has entity allow chapter permission.
- User has Role A.
User granted page permission.
#### test_81_inherited_all_override_deny
- Page permissions have inherit enabled.
- Role A has view-all page role permission.
- Role A has entity allow chapter permission.
- User has entity deny chapter permission.
- User has Role A.
User denied page permission.

62
public/dist/app.js vendored Normal file

File diff suppressed because one or more lines are too long

34
public/dist/code.js vendored Normal file

File diff suppressed because one or more lines are too long

1
public/dist/export-styles.css vendored Normal file

File diff suppressed because one or more lines are too long

1
public/dist/print-styles.css vendored Normal file
View File

@@ -0,0 +1 @@
:root{--color-primary: #206ea7;--color-primary-light: rgba(32,110,167,0.15);--color-page: #206ea7;--color-page-draft: #7e50b1;--color-chapter: #af4d0d;--color-book: #077b70;--color-bookshelf: #a94747;--bg-disabled: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='100%25' width='100%25'%3E%3Cdefs%3E%3Cpattern id='doodad' width='19' height='19' viewBox='0 0 40 40' patternUnits='userSpaceOnUse' patternTransform='rotate(143)'%3E%3Crect width='100%25' height='100%25' fill='rgba(42, 67, 101,0)'/%3E%3Cpath d='M-10 30h60v20h-60zM-10-10h60v20h-60' fill='rgba(26, 32, 44,0)'/%3E%3Cpath d='M-10 10h60v20h-60zM-10-30h60v20h-60z' fill='rgba(0, 0, 0,0.05)'/%3E%3C/pattern%3E%3C/defs%3E%3Crect fill='url(%23doodad)' height='200%25' width='200%25'/%3E%3C/svg%3E")}:root.dark-mode{--bg-disabled: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='100%25' width='100%25'%3E%3Cdefs%3E%3Cpattern id='doodad' width='19' height='19' viewBox='0 0 40 40' patternUnits='userSpaceOnUse' patternTransform='rotate(143)'%3E%3Crect width='100%25' height='100%25' fill='rgba(42, 67, 101,0)'/%3E%3Cpath d='M-10 30h60v20h-60zM-10-10h60v20h-60' fill='rgba(26, 32, 44,0)'/%3E%3Cpath d='M-10 10h60v20h-60zM-10-30h60v20h-60z' fill='rgba(255, 255, 255,0.05)'/%3E%3C/pattern%3E%3C/defs%3E%3Crect fill='url(%23doodad)' height='200%25' width='200%25'/%3E%3C/svg%3E");color-scheme:only dark}:root:not(.dark-mode){color-scheme:only light}header{display:none}html,body{font-size:12px;background-color:#fff}.page-content{margin:0 auto}.print-hidden{display:none !important}.tri-layout-container{grid-template-columns:1fr;grid-template-areas:"b";margin-inline-start:0;margin-inline-end:0;display:block}.card{box-shadow:none}.content-wrap.card{padding-inline-start:0;padding-inline-end:0}/*# sourceMappingURL=print-styles.css.map */

1
public/dist/styles.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -26,10 +26,12 @@ import 'codemirror/mode/python/python';
import 'codemirror/mode/ruby/ruby';
import 'codemirror/mode/rust/rust';
import 'codemirror/mode/shell/shell';
import 'codemirror/mode/smarty/smarty';
import 'codemirror/mode/sql/sql';
import 'codemirror/mode/stex/stex';
import 'codemirror/mode/swift/swift';
import 'codemirror/mode/toml/toml';
import 'codemirror/mode/twig/twig';
import 'codemirror/mode/vb/vb';
import 'codemirror/mode/vbscript/vbscript';
import 'codemirror/mode/xml/xml';
@@ -94,11 +96,13 @@ const modeMap = {
rs: 'rust',
shell: 'shell',
sh: 'shell',
smarty: 'smarty',
sql: 'text/x-sql',
stext: 'text/x-stex',
swift: 'text/x-swift',
toml: 'toml',
ts: 'text/typescript',
twig: 'twig',
typescript: 'text/typescript',
vbs: 'vbscript',
vbscript: 'vbscript',

View File

@@ -10,8 +10,6 @@ export class EntityPermissions extends Component {
this.everyoneInheritToggle = this.$refs.everyoneInherit;
this.roleSelect = this.$refs.roleSelect;
this.roleContainer = this.$refs.roleContainer;
this.userContainer = this.$refs.userContainer;
this.userSelectContainer = this.$refs.userSelectContainer;
this.setupListeners();
}
@@ -20,7 +18,7 @@ export class EntityPermissions extends Component {
// "Everyone Else" inherit toggle
this.everyoneInheritToggle.addEventListener('change', event => {
const inherit = event.target.checked;
const permissions = document.querySelectorAll('input[name^="permissions[fallback]"]');
const permissions = document.querySelectorAll('input[name^="permissions[0]["]');
for (const permission of permissions) {
permission.disabled = inherit;
permission.checked = false;
@@ -30,7 +28,7 @@ export class EntityPermissions extends Component {
// Remove role row button click
this.container.addEventListener('click', event => {
const button = event.target.closest('button');
if (button && button.dataset.modelType) {
if (button && button.dataset.roleId) {
this.removeRowOnButtonClick(button)
}
});
@@ -42,14 +40,6 @@ export class EntityPermissions extends Component {
this.addRoleRow(roleId);
}
});
// User select change
this.userSelectContainer.querySelector('input[name="user_select"]').addEventListener('change', event => {
const userId = event.target.value;
if (userId) {
this.addUserRow(userId);
}
});
}
async addRoleRow(roleId) {
@@ -62,50 +52,23 @@ export class EntityPermissions extends Component {
}
// Get and insert new row
const resp = await window.$http.get(`/permissions/role-form-row/${this.entityType}/${roleId}`);
const resp = await window.$http.get(`/permissions/form-row/${this.entityType}/${roleId}`);
const row = htmlToDom(resp.data);
this.roleContainer.append(row);
this.roleSelect.disabled = false;
}
async addUserRow(userId) {
const exists = this.userContainer.querySelector(`[name^="permissions[user][${userId}]"]`) !== null;
if (exists) {
return;
}
const toggle = this.userSelectContainer.querySelector('.dropdown-search-toggle-select');
toggle.classList.add('disabled');
this.userContainer.style.pointerEvents = 'none';
// Get and insert new row
const resp = await window.$http.get(`/permissions/user-form-row/${this.entityType}/${userId}`);
const row = htmlToDom(resp.data);
this.userContainer.append(row);
toggle.classList.remove('disabled');
this.userContainer.style.pointerEvents = null;
/** @var {UserSelect} **/
const userSelect = window.$components.firstOnElement(this.userSelectContainer.querySelector('.dropdown-search'), 'user-select');
userSelect.reset();
}
removeRowOnButtonClick(button) {
const row = button.closest('.item-list-row');
const modelId = button.dataset.modelId;
const modelName = button.dataset.modelName;
const modelType = button.dataset.modelType;
const roleId = button.dataset.roleId;
const roleName = button.dataset.roleName;
const option = document.createElement('option');
option.value = modelId;
option.textContent = modelName;
if (modelType === 'role') {
this.roleSelect.append(option);
}
option.value = roleId;
option.textContent = roleName;
this.roleSelect.append(option);
row.remove();
}

View File

@@ -8,9 +8,6 @@ export class UserSelect extends Component {
this.input = this.$refs.input;
this.userInfoContainer = this.$refs.userInfo;
this.initialValue = this.input.value;
this.initialContent = this.userInfoContainer.innerHTML;
onChildEvent(this.container, 'a.dropdown-search-item', 'click', this.selectUser.bind(this));
}
@@ -22,13 +19,6 @@ export class UserSelect extends Component {
this.hide();
}
reset() {
this.input.value = this.initialValue;
this.userInfoContainer.innerHTML = this.initialContent;
this.input.dispatchEvent(new Event('change', {bubbles: true}));
this.hide();
}
hide() {
/** @var {Dropdown} **/
const dropdown = window.$components.firstOnElement(this.container, 'dropdown');

View File

@@ -57,6 +57,12 @@ export class KeyboardNavigationHandler {
* @param {KeyboardEvent} event
*/
#keydownHandler(event) {
// Ignore certain key events in inputs to allow text editing.
if (event.target.matches('input') && (event.key === 'ArrowRight' || event.key === 'ArrowLeft')) {
return;
}
if (event.key === 'ArrowDown' || event.key === 'ArrowRight') {
this.focusNext();
event.preventDefault();

View File

@@ -26,7 +26,7 @@ return [
'app_public_viewing' => 'Povolit prohlížení veřejností?',
'app_secure_images' => 'Nahrávat obrázky neveřejně a zabezpečeně',
'app_secure_images_toggle' => 'Zapnout bezpečnější nahrávání obrázků',
'app_secure_images_desc' => 'Z výkonnostních důvodů jsou všechny obrázky veřejně dostupné. Tato volba přidá do adresy obrázku náhodný řetězec, aby nikdo neodhadnul adresu obrázku. Ujistěte se, že server nezobrazuje v adresáři seznam souborů, což by přístup k přístup opět otevřelo.',
'app_secure_images_desc' => 'Z výkonnostních důvodů jsou všechny obrázky veřejně dostupné. Tato volba přidá do adresy obrázku náhodný řetězec, aby nikdo neodhadnul adresu obrázku. Ujistěte se, že server nezobrazuje v adresáři seznam souborů, což by přístup k obrázkům opět otevřelo.',
'app_default_editor' => 'Výchozí editor',
'app_default_editor_desc' => 'Vyberte, který editor bude použit ve výchozím nastavení při úpravách nových stránek. To může být přepsáno na úrovni stránky, kde to dovolují oprávnění.',
'app_custom_html' => 'Vlastní obsah hlavičky HTML',

View File

@@ -8,7 +8,7 @@ return [
// Pages
'page_create' => 'erstellte Seite',
'page_create_notification' => 'Seite erfolgreich erstellt',
'page_update' => 'hat die Seite aktualisiert',
'page_update' => 'aktualisierte Seite',
'page_update_notification' => 'Seite erfolgreich aktualisiert',
'page_delete' => 'gelöschte Seite',
'page_delete_notification' => 'Seite erfolgreich gelöscht',
@@ -19,32 +19,32 @@ return [
// Chapters
'chapter_create' => 'erstellte Kapitel',
'chapter_create_notification' => 'Kapitel erfolgreich erstellt',
'chapter_update' => 'hat das Kapitel geändert',
'chapter_update' => 'aktualisierte Kapitel',
'chapter_update_notification' => 'Kapitel erfolgreich aktualisiert',
'chapter_delete' => 'hat das Kapitel gelöscht',
'chapter_delete' => 'löschte Kapitel',
'chapter_delete_notification' => 'Kapitel erfolgreich gelöscht',
'chapter_move' => 'hat das Kapitel verschoben',
'chapter_move' => 'verschob Kapitel',
// Books
'book_create' => 'hat das Buch erstellt',
'book_create' => 'erstellte Buch',
'book_create_notification' => 'Buch erfolgreich erstellt',
'book_create_from_chapter' => 'umgewandeltes Kapitel zum Buch',
'book_create_from_chapter_notification' => 'Kapitel erfolgreich in ein Buch umgewandelt',
'book_update' => 'hat das Buch aktualisiert',
'book_create_from_chapter' => 'konvertierte Kapitel zu Buch',
'book_create_from_chapter_notification' => 'Kapitel erfolgreich in ein Buch konvertiert',
'book_update' => 'aktualisierte Buch',
'book_update_notification' => 'Buch erfolgreich aktualisiert',
'book_delete' => 'hat das Buch gelöscht',
'book_delete' => 'löschte Buch',
'book_delete_notification' => 'Buch erfolgreich gelöscht',
'book_sort' => 'hat die Buch-Sortierung geändert',
'book_sort' => 'sortierte Buch',
'book_sort_notification' => 'Das Buch wurde erfolgreich umsortiert',
// Bookshelves
'bookshelf_create' => 'erstelltes Regal',
'bookshelf_create' => 'erstellte Regal',
'bookshelf_create_notification' => 'Regal erfolgreich erstellt',
'bookshelf_create_from_book' => 'zu Regal konvertiertes Buch',
'bookshelf_create_from_book' => 'konvertierte Buch zu Regal',
'bookshelf_create_from_book_notification' => 'Buch erfolgreich in ein Regal konvertiert',
'bookshelf_update' => 'aktualisiertes Regal',
'bookshelf_update' => 'aktualisierte Regal',
'bookshelf_update_notification' => 'Regal erfolgreich aktualisiert',
'bookshelf_delete' => 'gelöschtes Regal',
'bookshelf_delete' => 'löschte Regal',
'bookshelf_delete_notification' => 'Regal erfolgreich gelöscht',
// Favourites

View File

@@ -61,8 +61,8 @@ return [
'email_confirm_send_error' => 'Leider konnte die für die Registrierung notwendige E-Mail zur Bestätigung Ihrer E-Mail-Adresse nicht versandt werden. Bitte kontaktieren Sie den Systemadministrator!',
'email_confirm_success' => 'Ihre E-Mail wurde bestätigt! Sie sollten nun in der Lage sein, sich mit dieser E-Mail-Adresse anzumelden.',
'email_confirm_resent' => 'Bestätigungs-E-Mail wurde erneut versendet, bitte überprüfen Sie Ihren Posteingang.',
'email_confirm_thanks' => 'Thanks for confirming!',
'email_confirm_thanks_desc' => 'Please wait a moment while your confirmation is handled. If you are not redirected after 3 seconds press the "Continue" link below to proceed.',
'email_confirm_thanks' => 'Vielen Dank für das Bestätigen!',
'email_confirm_thanks_desc' => 'Bitte warten Sie einen Augenblick, während Ihre Bestätigung bearbeitet wird. Wenn Sie nach 3 Sekunden nicht weitergeleitet werden, drücken Sie unten den "Weiter" Link, um fortzufahren.',
'email_not_confirmed' => 'E-Mail-Adresse ist nicht bestätigt',
'email_not_confirmed_text' => 'Ihre E-Mail-Adresse ist bisher nicht bestätigt.',

View File

@@ -25,7 +25,7 @@ return [
'actions' => 'Aktionen',
'view' => 'Anzeigen',
'view_all' => 'Alle anzeigen',
'new' => 'New',
'new' => 'Neu',
'create' => 'Erstellen',
'update' => 'Aktualisieren',
'edit' => 'Bearbeiten',
@@ -81,14 +81,14 @@ return [
'none' => 'Nichts',
// Header
'homepage' => 'Homepage',
'homepage' => 'Startseite',
'header_menu_expand' => 'Header-Menü erweitern',
'profile_menu' => 'Profilmenü',
'view_profile' => 'Profil ansehen',
'edit_profile' => 'Profil bearbeiten',
'dark_mode' => 'Dunkler Modus',
'light_mode' => 'Heller Modus',
'global_search' => 'Global Search',
'global_search' => 'Globale Suche',
// Layout tabs
'tab_info' => 'Info',

View File

@@ -66,7 +66,7 @@ return [
'insert_link_title' => 'Link einfügen/ändern',
'insert_horizontal_line' => 'Horizontale Linie einfügen',
'insert_code_block' => 'Code-Block einfügen',
'edit_code_block' => 'Edit code block',
'edit_code_block' => 'Code-Block bearbeiten',
'insert_drawing' => 'Zeichnung einfügen/ändern',
'drawing_manager' => 'Zeichnungsmanager',
'insert_media' => 'Medien einfügen/ändern',
@@ -144,11 +144,11 @@ return [
'url' => 'URL',
'text_to_display' => 'Anzuzeigender Text',
'title' => 'Titel',
'open_link' => 'Open link',
'open_link_in' => 'Open link in...',
'open_link' => 'Link öffnen',
'open_link_in' => 'Link öffnen in...',
'open_link_current' => 'Aktuelles Fenster',
'open_link_new' => 'Neues Fenster',
'remove_link' => 'Remove link',
'remove_link' => 'Link entfernen',
'insert_collapsible' => 'Einklappbarer Block einfügen',
'collapsible_unwrap' => 'Auspacken',
'edit_label' => 'Label bearbeiten',

View File

@@ -50,7 +50,7 @@ return [
'permissions_role_everyone_else' => 'Alle anderen',
'permissions_role_everyone_else_desc' => 'Berechtigungen für alle Rollen setzen, die nicht explizit überschrieben wurden.',
'permissions_role_override' => 'Berechtigungen für Rolle überschreiben',
'permissions_inherit_defaults' => 'Inherit defaults',
'permissions_inherit_defaults' => 'Standardeinstellungen vererben',
// Search
'search_results' => 'Suchergebnisse',
@@ -224,8 +224,8 @@ return [
'pages_md_insert_image' => 'Bild einfügen',
'pages_md_insert_link' => 'Link zu einem Objekt einfügen',
'pages_md_insert_drawing' => 'Zeichnung einfügen',
'pages_md_show_preview' => 'Show preview',
'pages_md_sync_scroll' => 'Sync preview scroll',
'pages_md_show_preview' => 'Vorschau anzeigen',
'pages_md_sync_scroll' => 'Vorschau synchronisieren',
'pages_not_in_chapter' => 'Seite ist in keinem Kapitel',
'pages_move' => 'Seite verschieben',
'pages_move_success' => 'Seite nach ":parentName" verschoben',
@@ -236,14 +236,14 @@ return [
'pages_permissions_success' => 'Seiten Berechtigungen aktualisiert',
'pages_revision' => 'Version',
'pages_revisions' => 'Seitenversionen',
'pages_revisions_desc' => 'Listed below are all the past revisions of this page. You can look back upon, compare, and restore old page versions if permissions allow. The full history of the page may not be fully reflected here since, depending on system configuration, old revisions could be auto-deleted.',
'pages_revisions_desc' => 'Alle vorherhigen Revisionen dieser Seite sind unten aufgelistet. Sie können zurückschauen, vergleichen und alte Seitenversionen wiederherstellen, wenn die Berechtigungen dies erlauben. Der vollständige Verlauf der Seite kann hier möglicherweise nicht vollständig wiedergegeben werden, da je nach Systemkonfiguration alte Revisionen automatisch hätten gelöscht werden können.',
'pages_revisions_named' => 'Seitenversionen von ":pageName"',
'pages_revision_named' => 'Seitenversion von ":pageName"',
'pages_revision_restored_from' => 'Wiederhergestellt von #:id; :summary',
'pages_revisions_created_by' => 'Erstellt von',
'pages_revisions_date' => 'Versionsdatum',
'pages_revisions_number' => '#',
'pages_revisions_sort_number' => 'Revision Number',
'pages_revisions_sort_number' => 'Revisionsnummer',
'pages_revisions_numbered' => 'Revision #:id',
'pages_revisions_numbered_changes' => 'Revision #:id Änderungen',
'pages_revisions_editor' => 'Editor-Typ',
@@ -280,7 +280,7 @@ return [
'shelf_tags' => 'Regal-Schlagwörter',
'tag' => 'Schlagwort',
'tags' => 'Schlagwörter',
'tags_index_desc' => 'Tags can be applied to content within the system to apply a flexible form of categorization. Tags can have both a key and value, with the value being optional. Once applied, content can then be queried using the tag name and value.',
'tags_index_desc' => 'Tags können auf Inhalte im System angewendet werden, um eine flexible Form der Kategorisierung anzuwenden. Tags können sowohl einen Schlüssel als auch einen Wert haben, wobei der Wert optional ist. Einmal angewendet, können Inhalte unter Verwendung des Tag-Namens und Wertes abgefragt werden.',
'tag_name' => 'Schlagwort Name',
'tag_value' => 'Inhalt (Optional)',
'tags_explain' => "Fügen Sie Schlagwörter hinzu, um Ihren Inhalt zu kategorisieren.\nSie können einen erklärenden Inhalt hinzufügen, um eine genauere Unterteilung vorzunehmen.",

View File

@@ -6,12 +6,12 @@
return [
'shortcuts' => 'Tastenkürzel',
'shortcuts_interface' => 'Interface Keyboard Shortcuts',
'shortcuts_toggle_desc' => 'Here you can enable or disable keyboard system interface shortcuts, used for navigation and actions.',
'shortcuts_customize_desc' => 'You can customize each of the shortcuts below. Just press your desired key combination after selecting the input for a shortcut.',
'shortcuts_toggle_label' => 'Keyboard shortcuts enabled',
'shortcuts_interface' => 'Oberflächen-Tastaturkürzel',
'shortcuts_toggle_desc' => 'Hier können Sie Tastaturkürzel für die Systemoberfläche für Navigation und Aktionen aktivieren oder deaktivieren.',
'shortcuts_customize_desc' => 'Unten können Sie alle Tastenkürzel anpassen. Drücken Sie einfach die gewünschte Tastenkombination, nachdem Sie die Eingabe für eine Tastenkombination ausgewählt haben.',
'shortcuts_toggle_label' => 'Tastaturkürzel aktiviert',
'shortcuts_section_navigation' => 'Navigation',
'shortcuts_section_actions' => 'Gemeinsame Aktionen',
'shortcuts_section_actions' => 'Häufige Aktionen',
'shortcuts_save' => 'Tastenkürzel speichern',
'shortcuts_overlay_desc' => 'Hinweis: Wenn Tastenkürzel aktiviert sind, ist ein Hilfsoverlay durch Drücken von "?" verfügbar, welches die verfügbaren Tastenkürzel für Aktionen hervorhebt, die aktuell auf dem Bildschirm sichtbar sind.',
'shortcuts_update_success' => 'Tastenkürzel Einstellungen wurden aktualisiert!',

View File

@@ -136,11 +136,11 @@ Hinweis: Benutzer können ihre E-Mail-Adresse nach erfolgreicher Registrierung
// Role Settings
'roles' => 'Rollen',
'role_user_roles' => 'Benutzer-Rollen',
'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.',
'roles_x_users_assigned' => '1 user assigned|:count users assigned',
'roles_x_permissions_provided' => '1 permission|:count permissions',
'roles_assigned_users' => 'Assigned Users',
'roles_permissions_provided' => 'Provided Permissions',
'roles_index_desc' => 'Rollen werden verwendet, um Benutzer zu gruppieren System-Berechtigung für ihre Mitglieder zuzuweisen. Wenn ein Benutzer Mitglied mehrerer Rollen ist, stapeln die gewährten Berechtigungen und der Benutzer wird alle Fähigkeiten erben.',
'roles_x_users_assigned' => '1 Benutzer zugewiesen|:count Benutzer zugewiesen',
'roles_x_permissions_provided' => '1 Berechtigung|:count Berechtigungen',
'roles_assigned_users' => 'Zugewiesene Benutzer',
'roles_permissions_provided' => 'Genutzte Berechtigungen',
'role_create' => 'Neue Rolle anlegen',
'role_create_success' => 'Rolle erfolgreich angelegt',
'role_delete' => 'Rolle löschen',
@@ -180,7 +180,7 @@ Hinweis: Benutzer können ihre E-Mail-Adresse nach erfolgreicher Registrierung
// Users
'users' => 'Benutzer',
'users_index_desc' => 'Create & manage individual user accounts within the system. User accounts are used for login and attribution of content & activity. Access permissions are primarily role-based but user content ownership, among other factors, may also affect permissions & access.',
'users_index_desc' => 'Erstellen und Verwalten Sie individuelle Benutzerkonten innerhalb des Systems. Benutzerkonten werden zur Anmeldung und Besitz von Inhalten und Aktivitäten verwendet. Zugriffsberechtigungen sind in erster Linie rollenbasiert, aber Besitz von Benutzerinhalten kann unter anderem auch Berechtigungen beeinflussen.',
'user_profile' => 'Benutzerprofil',
'users_add_new' => 'Benutzer hinzufügen',
'users_search' => 'Benutzer suchen',
@@ -250,8 +250,8 @@ Hinweis: Benutzer können ihre E-Mail-Adresse nach erfolgreicher Registrierung
// Webhooks
'webhooks' => 'Webhooks',
'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.',
'webhooks_x_trigger_events' => '1 trigger event|:count trigger events',
'webhooks_index_desc' => 'Webhooks sind eine Möglichkeit, Daten an externe URLs zu senden, wenn bestimmte Aktionen und Ereignisse im System auftreten, was eine ereignisbasierte Integration mit externen Plattformen wie Messaging- oder Benachrichtigungssystemen ermöglicht.',
'webhooks_x_trigger_events' => '1 Triggerereignis|:count Triggerereignisse',
'webhooks_create' => 'Neuen Webhook erstellen',
'webhooks_none_created' => 'Es wurden noch keine Webhooks erstellt.',
'webhooks_edit' => 'Webhook bearbeiten',

View File

@@ -30,10 +30,10 @@ return [
'dont_have_account' => 'Noch kein Konto erstellt?',
'social_login' => 'Mit Sozialem Netzwerk anmelden',
'social_registration' => 'Mit Sozialem Netzwerk registrieren',
'social_registration_text' => 'Mit einer dieser Dienste registrieren oder anmelden',
'social_registration_text' => 'Mit einem dieser Dienste registrieren oder anmelden',
'register_thanks' => 'Vielen Dank für deine Registrierung!',
'register_confirm' => 'Bitte prüfe Deinen Posteingang und bestätig die Registrierung.',
'register_confirm' => 'Bitte prüfe deinen Posteingang und bestätige die Registrierung.',
'registrations_disabled' => 'Eine Registrierung ist momentan nicht möglich',
'registration_email_domain_invalid' => 'Du kannst dich mit dieser E-Mail nicht registrieren.',
'register_success' => 'Vielen Dank für deine Registrierung! Du bist jetzt registriert und eingeloggt.',
@@ -45,12 +45,12 @@ return [
// Password Reset
'reset_password' => 'Passwort vergessen',
'reset_password_send_instructions' => 'Bitte gib Deine E-Mail-Adresse ein. Danach erhältst Du eine E-Mail mit einem Link zum Zurücksetzen Deines Passwortes.',
'reset_password_send_instructions' => 'Bitte gib Deine E-Mail-Adresse ein. Danach erhältst Du eine E-Mail mit einem Link zum Zurücksetzen deines Passwortes.',
'reset_password_send_button' => 'Passwort zurücksetzen',
'reset_password_sent' => 'Ein Link zum Zurücksetzen des Passworts wird an :email gesendet, wenn diese E-Mail-Adresse im System gefunden wird.',
'reset_password_success' => 'Dein Passwort wurde erfolgreich zurückgesetzt.',
'email_reset_subject' => 'Passwort zurücksetzen für :appName',
'email_reset_text' => 'Du erhältst diese E-Mail, weil jemand versucht hat, Dein Passwort zurückzusetzen.',
'email_reset_text' => 'Du erhältst diese E-Mail, weil jemand versucht hat, dein Passwort zurückzusetzen.',
'email_reset_not_requested' => 'Wenn du das Zurücksetzen des Passworts nicht angefordert hast, ist keine weitere Aktion erforderlich.',
// Email Confirmation
@@ -58,15 +58,15 @@ return [
'email_confirm_greeting' => 'Danke, dass Du dich für :appName registrierst hast!',
'email_confirm_text' => 'Bitte bestätige Deine E-Mail-Adresse, indem Du auf die Schaltfläche klickst:',
'email_confirm_action' => 'E-Mail-Adresse bestätigen',
'email_confirm_send_error' => 'Leider konnte die für die Registrierung notwendige E-Mail zur Bestätigung Deiner E-Mail-Adresse nicht versandt werden. Bitte kontaktiere den Systemadministrator!',
'email_confirm_success' => 'Ihre E-Mail wurde bestätigt! Sie sollten nun in der Lage sein, sich mit dieser E-Mail-Adresse anzumelden.',
'email_confirm_resent' => 'Bestätigungs-E-Mail wurde erneut versendet, bitte überprüfe Deinen Posteingang.',
'email_confirm_thanks' => 'Thanks for confirming!',
'email_confirm_thanks_desc' => 'Please wait a moment while your confirmation is handled. If you are not redirected after 3 seconds press the "Continue" link below to proceed.',
'email_confirm_send_error' => 'Leider konnte die für die Registrierung notwendige E-Mail zur Bestätigung deiner E-Mail-Adresse nicht versandt werden. Bitte kontaktiere deinen Systemadministrator!',
'email_confirm_success' => 'Deine E-Mail Adresse wurde bestätigt! Du solltest nun in der Lage sein, dich mit deiner E-Mail-Adresse anzumelden.',
'email_confirm_resent' => 'Bestätigungs-E-Mail wurde erneut versendet, bitte überprüfe deinen Posteingang.',
'email_confirm_thanks' => 'Vielen Dank für das Bestätigen!',
'email_confirm_thanks_desc' => 'Bitte warte einen Augenblick, während deine Bestätigung bearbeitet wird. Wenn Du nach 3 Sekunden nicht weitergeleitet wirst, drücke unten den "Weiter" Link, um fortzufahren.',
'email_not_confirmed' => 'E-Mail-Adresse ist nicht bestätigt',
'email_not_confirmed_text' => 'Deine E-Mail-Adresse ist bisher nicht bestätigt.',
'email_not_confirmed_click_link' => 'Bitte klicke auf den Link in der E-Mail, die Du nach der Registrierung erhalten hast.',
'email_not_confirmed_click_link' => 'Bitte klicke auf den Link in der E-Mail, die du nach der Registrierung erhalten hast.',
'email_not_confirmed_resend' => 'Wenn Du die E-Mail nicht erhalten hast, kannst Du die Nachricht erneut anfordern. Fülle hierzu bitte das folgende Formular aus:',
'email_not_confirmed_resend_button' => 'Bestätigungs-E-Mail erneut senden',
@@ -76,30 +76,30 @@ return [
'user_invite_email_text' => 'Klicke auf die Schaltfläche unten, um ein Passwort festzulegen und Zugriff zu erhalten:',
'user_invite_email_action' => 'Konto-Passwort festlegen',
'user_invite_page_welcome' => 'Willkommen bei :appName!',
'user_invite_page_text' => 'Um die Anmeldung abzuschließen und Zugriff auf :appName zu bekommen muss noch ein Passwort festgelegt werden. Dieses wird in Zukunft zum Einloggen benötigt.',
'user_invite_page_text' => 'Um die Anmeldung abzuschließen und Zugriff auf :appName zu bekommen, muss noch ein Passwort festgelegt werden. Dieses wird in Zukunft für die Anmeldung benötigt.',
'user_invite_page_confirm_button' => 'Passwort bestätigen',
'user_invite_success_login' => 'Passwort gesetzt, Sie sollten nun in der Lage sein, sich mit Ihrem Passwort an :appName anzumelden!',
'user_invite_success_login' => 'Passwort gesetzt, du solltest nun in der Lage sein, dich mit deinem Passwort an :appName anzumelden!',
// Multi-factor Authentication
'mfa_setup' => 'Multi-Faktor-Authentifizierung einrichten',
'mfa_setup_desc' => 'Richten Sie Multi-Faktor-Authentifizierung als zusätzliche Sicherheitsstufe für Ihr Benutzerkonto ein.',
'mfa_setup_desc' => 'Richte eine Multi-Faktor-Authentifizierung als zusätzliche Sicherheitsstufe für dein Benutzerkonto ein.',
'mfa_setup_configured' => 'Bereits konfiguriert',
'mfa_setup_reconfigure' => 'Umkonfigurieren',
'mfa_setup_remove_confirmation' => 'Sind Sie sicher, dass Sie diese Multi-Faktor-Authentifizierungsmethode entfernen möchten?',
'mfa_setup_remove_confirmation' => 'Bist du sicher, dass du diese Multi-Faktor-Authentifizierungsmethode entfernen möchtest?',
'mfa_setup_action' => 'Einrichtung',
'mfa_backup_codes_usage_limit_warning' => 'Sie haben weniger als 5 Backup-Codes übrig, Bitte erstellen und speichern Sie ein neues set bevor Sie keine codes mehr haben, um zu verhindern, dass Sie von Ihrem konto gesperrt werden.',
'mfa_backup_codes_usage_limit_warning' => 'Du hast weniger als 5 Backup-Codes übrig. Bitte erstelle und speichere einen neuen Satz, bevor Du keine Codes mehr hast, um zu verhindern, dass du von deinem Konto ausgesperrt wirst.',
'mfa_option_totp_title' => 'Mobile App',
'mfa_option_totp_desc' => 'Um Mehrfach-Faktor-Authentifizierung nutzen zu können, benötigen Sie eine mobile Anwendung, die TOTP unterstützt, wie Google Authenticator, Authy oder Microsoft Authenticator.',
'mfa_option_totp_desc' => 'Um Mehrfach-Faktor-Authentifizierung nutzen zu können, benötigst du eine mobile Anwendung, die TOTP unterstützt, wie Google Authenticator, Authy oder Microsoft Authenticator.',
'mfa_option_backup_codes_title' => 'Backup Code',
'mfa_option_backup_codes_desc' => 'Speichern Sie sicher eine reihe von einmaligen Backup-Codes, die Sie eingeben können, um ihre identität zu überprüfen.',
'mfa_option_backup_codes_desc' => 'Speichere eine Reihe von einmaligen Backup-Codes an einem sicheren Ort. Du kannst damit deine identität bestätigen.',
'mfa_gen_confirm_and_enable' => 'Bestätigen und aktivieren',
'mfa_gen_backup_codes_title' => 'Backup-Codes einrichten',
'mfa_gen_backup_codes_desc' => 'Speichern Sie die folgende Liste der Codes an einem sicheren Ort. Wenn Sie auf das System zugreifen, können Sie einen der Codes als zweiten Authentifizierungsmechanismus verwenden.',
'mfa_gen_backup_codes_download' => 'Download Codes',
'mfa_gen_backup_codes_desc' => 'Speichere die folgende Liste der Codes an einem sicheren Ort. Wenn du auf das System zugreifst, kannst du einen der Codes als zweiten Authentifizierungsmechanismus verwenden.',
'mfa_gen_backup_codes_download' => 'Codes herunterladen',
'mfa_gen_backup_codes_usage_warning' => 'Jeder Code kann nur einmal verwendet werden',
'mfa_gen_totp_title' => 'Mobile App einrichten',
'mfa_gen_totp_desc' => 'Um Mehrfach-Faktor-Authentifizierung nutzen zu können, benötigen Sie eine mobile Anwendung, die TOTP unterstützt, wie Google Authenticator, Authy oder Microsoft Authenticator.',
'mfa_gen_totp_scan' => 'Scannen Sie den QR-Code unten mit ihrer bevorzugten Authentifizierungs-App, um loszulegen.',
'mfa_gen_totp_desc' => 'Um Mehrfach-Faktor-Authentifizierung nutzen zu können, benötigst du eine mobile Anwendung, die TOTP unterstützt, wie Google Authenticator, Authy oder Microsoft Authenticator.',
'mfa_gen_totp_scan' => 'Scanne den QR-Code unten mit deiner bevorzugten Authentifizierungs-App, um zu beginnen.',
'mfa_gen_totp_verify_setup' => 'Setup überprüfen',
'mfa_gen_totp_verify_setup_desc' => 'Überprüfe, dass alles funktioniert, indem du einen Code aus deiner Authentifizierungs-App in das Eingabefeld unten eingibst:',
'mfa_gen_totp_provide_code_here' => 'Gib hier den von der App generierten Code ein',

View File

@@ -25,7 +25,7 @@ return [
'actions' => 'Aktionen',
'view' => 'Anzeigen',
'view_all' => 'Alle anzeigen',
'new' => 'New',
'new' => 'Neu',
'create' => 'Anlegen',
'update' => 'Aktualisieren',
'edit' => 'Bearbeiten',
@@ -81,14 +81,14 @@ return [
'none' => 'Keine',
// Header
'homepage' => 'Homepage',
'homepage' => 'Startseite',
'header_menu_expand' => 'Header-Menü erweitern',
'profile_menu' => 'Profilmenü',
'view_profile' => 'Profil ansehen',
'edit_profile' => 'Profil bearbeiten',
'dark_mode' => 'Dunkler Modus',
'light_mode' => 'Heller Modus',
'global_search' => 'Global Search',
'global_search' => 'Globale Suche',
// Layout tabs
'tab_info' => 'Info',

View File

@@ -14,7 +14,7 @@ return [
'image_uploaded' => 'Hochgeladen am :uploadedDate',
'image_load_more' => 'Mehr',
'image_image_name' => 'Bildname',
'image_delete_used' => 'Dieses Bild wird auf den folgenden Seiten benutzt. ',
'image_delete_used' => 'Dieses Bild wird auf den folgenden Seiten benutzt.',
'image_delete_confirm_text' => 'Bist Du sicher, dass Du diese Seite löschen möchtest?',
'image_select_image' => 'Bild auswählen',
'image_dropzone' => 'Ziehe Bilder hierher oder klicke hier, um ein Bild auszuwählen',

View File

@@ -66,7 +66,7 @@ return [
'insert_link_title' => 'Link einfügen/bearbeiten',
'insert_horizontal_line' => 'Horizontale Linie einfügen',
'insert_code_block' => 'Codeblock einfügen',
'edit_code_block' => 'Edit code block',
'edit_code_block' => 'Codeblock bearbeiten',
'insert_drawing' => 'Zeichnung einfügen/bearbeiten',
'drawing_manager' => 'Zeichnungsmanager',
'insert_media' => 'Medien einfügen/bearbeiten',
@@ -121,7 +121,7 @@ return [
'paste_column_before' => 'Spalte davor einfügen',
'paste_column_after' => 'Spalte danach einfügen',
'cell_padding' => 'Zellenabstand',
'cell_spacing' => 'Zellen-Außenabstand',
'cell_spacing' => 'Zellenaußenabstand',
'caption' => 'Überschrift',
'show_caption' => 'Überschrift anzeigen',
'constrain' => 'Proportionen festsetzen',
@@ -144,11 +144,11 @@ return [
'url' => 'URL',
'text_to_display' => 'Anzuzeigender Text',
'title' => 'Titel',
'open_link' => 'Open link',
'open_link_in' => 'Open link in...',
'open_link' => 'Link öffnen',
'open_link_in' => 'Link öffnen in...',
'open_link_current' => 'Aktuellem Fenster',
'open_link_new' => 'Neuem Fenster',
'remove_link' => 'Remove link',
'remove_link' => 'Link entfernen',
'insert_collapsible' => 'Einklappbaren Block einfügen',
'collapsible_unwrap' => 'Entfernen',
'edit_label' => 'Beschriftung bearbeiten',

View File

@@ -38,11 +38,11 @@ return [
'export_html' => 'HTML-Datei',
'export_pdf' => 'PDF-Datei',
'export_text' => 'Textdatei',
'export_md' => 'Markdown-Dateir',
'export_md' => 'Markdown-Datei',
// Permissions and restrictions
'permissions' => 'Berechtigungen',
'permissions_desc' => 'Legen Sie hier Berechtigungen fest, um die Standardberechtigungen von Benutzerrollen zu überschreiben.',
'permissions_desc' => 'Lege hier Berechtigungen fest, um die Standardberechtigungen von Benutzerrollen zu überschreiben.',
'permissions_book_cascade' => 'In Büchern festgelegte Berechtigungen werden automatisch in untergeordnete Kapitel und Seiten kaskadiert, es sei denn, sie haben eigene Berechtigungen definiert.',
'permissions_chapter_cascade' => 'In Kapiteln festgelegte Berechtigungen werden automatisch in untergeordnete Seiten kaskadiert, es sei denn, sie haben eigene Berechtigungen definiert.',
'permissions_save' => 'Berechtigungen speichern',
@@ -50,7 +50,7 @@ return [
'permissions_role_everyone_else' => 'Alle anderen',
'permissions_role_everyone_else_desc' => 'Berechtigungen für alle Rollen setzen, die nicht explizit überschrieben wurden.',
'permissions_role_override' => 'Berechtigungen für Rolle überschreiben',
'permissions_inherit_defaults' => 'Inherit defaults',
'permissions_inherit_defaults' => 'Standardeinstellungen vererben',
// Search
'search_results' => 'Suchergebnisse',
@@ -71,7 +71,7 @@ return [
'search_created_by_me' => 'Von mir erstellt',
'search_updated_by_me' => 'Von mir aktualisiert',
'search_owned_by_me' => 'Besitzt von mir',
'search_date_options' => 'Datums Optionen',
'search_date_options' => 'Datumsoptionen',
'search_updated_before' => 'Aktualisiert vor',
'search_updated_after' => 'Aktualisiert nach',
'search_created_before' => 'Erstellt vor',
@@ -93,7 +93,7 @@ return [
'shelves_save' => 'Regal speichern',
'shelves_books' => 'Bücher in diesem Regal',
'shelves_add_books' => 'Buch zu diesem Regal hinzufügen',
'shelves_drag_books' => 'Ziehen die Bücher nach unten, um sie zu diesem Regal hinzuzufügen',
'shelves_drag_books' => 'Ziehe die Bücher nach unten, um sie zu diesem Regal hinzuzufügen',
'shelves_empty_contents' => 'Diesem Regal sind keine Bücher zugewiesen',
'shelves_edit_and_assign' => 'Regal bearbeiten um Bücher hinzuzufügen',
'shelves_edit_named' => 'Regal :name bearbeiten',
@@ -126,7 +126,7 @@ return [
'books_delete' => 'Buch löschen',
'books_delete_named' => 'Buch ":bookName" löschen',
'books_delete_explain' => 'Das Buch ":bookName" wird gelöscht und alle zugehörigen Kapitel und Seiten entfernt.',
'books_delete_confirmation' => 'Bist Du sicher, dass Du dieses Buch löschen möchtest?',
'books_delete_confirmation' => 'Bist Du sicher, dass du dieses Buch löschen möchtest?',
'books_edit' => 'Buch bearbeiten',
'books_edit_named' => 'Buch ":bookName" bearbeiten',
'books_form_book_name' => 'Name des Buches',
@@ -161,8 +161,8 @@ return [
'chapters_create' => 'Neues Kapitel anlegen',
'chapters_delete' => 'Kapitel entfernen',
'chapters_delete_named' => 'Kapitel ":chapterName" entfernen',
'chapters_delete_explain' => 'Hiermit löschen Sie das Kapitel mit dem Namen \':chapterName\'. Alle Seiten, die innerhalb dieses Kapitels existieren, werden ebenfalls gelöscht.',
'chapters_delete_confirm' => 'Bist Du sicher, dass Du dieses Kapitel löschen möchtest?',
'chapters_delete_explain' => 'Hiermit löscht du das Kapitel mit dem Namen \':chapterName\'. Alle Seiten, die innerhalb dieses Kapitels existieren, werden ebenfalls gelöscht.',
'chapters_delete_confirm' => 'Bist du sicher, dass du dieses Kapitel löschen möchtest?',
'chapters_edit' => 'Kapitel bearbeiten',
'chapters_edit_named' => 'Kapitel ":chapterName" bearbeiten',
'chapters_save' => 'Kapitel speichern',
@@ -192,8 +192,8 @@ return [
'pages_delete_draft' => 'Seitenentwurf löschen',
'pages_delete_success' => 'Seite gelöscht',
'pages_delete_draft_success' => 'Seitenentwurf gelöscht',
'pages_delete_confirm' => 'Bist Du sicher, dass Du diese Seite löschen möchtest?',
'pages_delete_draft_confirm' => 'Bist Du sicher, dass Du diesen Seitenentwurf löschen möchtest?',
'pages_delete_confirm' => 'Bist du sicher, dass du diese Seite löschen möchtest?',
'pages_delete_draft_confirm' => 'Bist du sicher, dass du diesen Seitenentwurf löschen möchtest?',
'pages_editing_named' => 'Seite ":pageName" bearbeiten',
'pages_edit_draft_options' => 'Entwurfsoptionen',
'pages_edit_save_draft' => 'Entwurf speichern',
@@ -208,11 +208,11 @@ return [
'pages_edit_switch_to_markdown_stable' => '(Stabiler Inhalt)',
'pages_edit_switch_to_wysiwyg' => 'Zum WYSIWYG Editor wechseln',
'pages_edit_set_changelog' => 'Änderungsprotokoll hinzufügen',
'pages_edit_enter_changelog_desc' => 'Bitte gib eine kurze Zusammenfassung Deiner Änderungen ein',
'pages_edit_enter_changelog_desc' => 'Bitte gib eine kurze Zusammenfassung deiner Änderungen ein',
'pages_edit_enter_changelog' => 'Änderungsprotokoll eingeben',
'pages_editor_switch_title' => 'Editor wechseln',
'pages_editor_switch_are_you_sure' => 'Bist du dir sicher, dass du den Editor für diese Seite ändern willst?',
'pages_editor_switch_consider_following' => 'Beachte beim Wechsel des Editors Folgendes:',
'pages_editor_switch_consider_following' => 'Beachte beim Wechsel des Editors folgendes:',
'pages_editor_switch_consideration_a' => 'Nach dem Speichern wird der neue Editor von allen zukünftigen Bearbeitern verwendet, auch von denen, die den Editortyp nicht selbst ändern können.',
'pages_editor_switch_consideration_b' => 'Dies kann unter Umständen zu einem Verlust von Details und Syntax führen.',
'pages_editor_switch_consideration_c' => 'Tag- oder Changelog-Änderungen, die seit dem letzten Speichern vorgenommen wurden, bleiben bei dieser Änderung nicht erhalten.',
@@ -224,8 +224,8 @@ return [
'pages_md_insert_image' => 'Bild einfügen',
'pages_md_insert_link' => 'Link zu einem Objekt einfügen',
'pages_md_insert_drawing' => 'Zeichnung einfügen',
'pages_md_show_preview' => 'Show preview',
'pages_md_sync_scroll' => 'Sync preview scroll',
'pages_md_show_preview' => 'Vorschau anzeigen',
'pages_md_sync_scroll' => 'Vorschau synchronisieren',
'pages_not_in_chapter' => 'Seite ist in keinem Kapitel',
'pages_move' => 'Seite verschieben',
'pages_move_success' => 'Seite nach ":parentName" verschoben',
@@ -236,14 +236,14 @@ return [
'pages_permissions_success' => 'Seiten Berechtigungen aktualisiert',
'pages_revision' => 'Version',
'pages_revisions' => 'Seitenversionen',
'pages_revisions_desc' => 'Listed below are all the past revisions of this page. You can look back upon, compare, and restore old page versions if permissions allow. The full history of the page may not be fully reflected here since, depending on system configuration, old revisions could be auto-deleted.',
'pages_revisions_desc' => 'Alle vorherhigen Revisionen dieser Seite sind unten aufgelistet. Du kannst zurückschauen, vergleichen und alte Seitenversionen wiederherstellen, wenn die Berechtigungen dies erlauben. Der vollständige Verlauf der Seite kann hier möglicherweise nicht vollständig wiedergegeben werden, da je nach Systemkonfiguration alte Revisionen automatisch gelöscht werden könnten.',
'pages_revisions_named' => 'Seitenversionen von ":pageName"',
'pages_revision_named' => 'Seitenversion von ":pageName"',
'pages_revision_restored_from' => 'Wiederhergestellt von #:id; :summary',
'pages_revisions_created_by' => 'Erstellt von',
'pages_revisions_date' => 'Versionsdatum',
'pages_revisions_number' => '#',
'pages_revisions_sort_number' => 'Revision Number',
'pages_revisions_sort_number' => 'Revisionsnummer',
'pages_revisions_numbered' => 'Revision #:id',
'pages_revisions_numbered_changes' => 'Revision #:id Änderungen',
'pages_revisions_editor' => 'Editortyp',
@@ -260,7 +260,7 @@ return [
'pages_references_update_revision' => 'Automatische Systemaktualisierung interner Links',
'pages_initial_name' => 'Neue Seite',
'pages_editing_draft_notification' => 'Du bearbeitest momenten einen Entwurf, der zuletzt :timeDiff gespeichert wurde.',
'pages_draft_edited_notification' => 'Diese Seite wurde seit diesem Zeitpunkt verändert. Wir empfehlen Ihnen, diesen Entwurf zu verwerfen.',
'pages_draft_edited_notification' => 'Diese Seite wurde seit diesem Zeitpunkt verändert. Wir empfehlen diesen Entwurf zu verwerfen.',
'pages_draft_page_changed_since_creation' => 'Diese Seite wurde seit der Erstellung dieses Entwurfs aktualisiert. Es wird empfohlen, diesen Entwurf zu verwerfen oder darauf zu achten, dass keine Seitenänderungen überschrieben werden.',
'pages_draft_edit_active' => [
'start_a' => ':count Benutzer bearbeiten derzeit diese Seite.',
@@ -280,7 +280,7 @@ return [
'shelf_tags' => 'Regal-Schlagwörter',
'tag' => 'Schlagwort',
'tags' => 'Schlagwörter',
'tags_index_desc' => 'Tags can be applied to content within the system to apply a flexible form of categorization. Tags can have both a key and value, with the value being optional. Once applied, content can then be queried using the tag name and value.',
'tags_index_desc' => 'Tags können auf Inhalte im System angewendet werden, um eine flexible Form der Kategorisierung anzuwenden. Tags können sowohl einen Schlüssel als auch einen Wert haben, wobei der Wert optional ist. Einmal angewendet, können Inhalte unter Verwendung des Tag-Namens und Wertes abgefragt werden.',
'tag_name' => 'Schlagwort Name',
'tag_value' => 'Inhalt (Optional)',
'tags_explain' => "Füge Schlagwörter hinzu, um ihren Inhalt zu kategorisieren.\nDu kannst einen erklärenden Inhalt hinzufügen, um eine genauere Unterteilung vorzunehmen.",
@@ -297,16 +297,16 @@ return [
'tags_view_existing_tags' => 'Vorhandene Tags anzeigen',
'tags_list_empty_hint' => 'Tags können über die Seitenleiste des Seiteneditors oder bei der Bearbeitung der Details eines Buches, Kapitels oder Regals vergeben werden.',
'attachments' => 'Anhänge',
'attachments_explain' => 'Du kannst auf Deiner Seite Dateien hochladen oder Links hinzufügen. Diese werden in der Seitenleiste angezeigt.',
'attachments_explain' => 'Du kannst auf deiner Seite Dateien hochladen oder Links hinzufügen. Diese werden in der Seitenleiste angezeigt.',
'attachments_explain_instant_save' => 'Änderungen werden direkt gespeichert.',
'attachments_items' => 'Angefügte Elemente',
'attachments_upload' => 'Datei hochladen',
'attachments_link' => 'Link hinzufügen',
'attachments_set_link' => 'Link setzen',
'attachments_delete' => 'Bist Du sicher, dass Du diesen Anhang löschen möchtest?',
'attachments_delete' => 'Bist du sicher, dass du diesen Anhang löschen möchtest?',
'attachments_dropzone' => 'Ziehe Dateien hierher oder klicke hier, um eine Datei auszuwählen',
'attachments_no_files' => 'Es wurden bisher keine Dateien hochgeladen.',
'attachments_explain_link' => 'Wenn Du keine Datei hochladen möchtest, kannst Du stattdessen einen Link hinzufügen. Dieser Link kann auf eine andere Seite oder eine Datei im Internet verweisen.',
'attachments_explain_link' => 'Wenn du keine Datei hochladen möchtest, kannst du stattdessen einen Link hinzufügen. Dieser Link kann auf eine andere Seite oder eine Datei im Internet verweisen.',
'attachments_link_name' => 'Link-Name',
'attachment_link' => 'Link zum Anhang',
'attachments_link_url' => 'Link zu einer Datei',
@@ -341,7 +341,7 @@ return [
'comment' => 'Kommentar',
'comments' => 'Kommentare',
'comment_add' => 'Kommentieren',
'comment_placeholder' => 'Gib hier Deine Kommentare ein (Markdown unterstützt)',
'comment_placeholder' => 'Gib hier deine Kommentare ein (Markdown unterstützt)',
'comment_count' => '{0} Keine Kommentare|{1} 1 Kommentar|[2,*] :count Kommentare',
'comment_save' => 'Kommentar speichern',
'comment_saving' => 'Kommentar wird gespeichert...',
@@ -352,12 +352,12 @@ return [
'comment_deleted_success' => 'Kommentar gelöscht',
'comment_created_success' => 'Kommentar hinzugefügt',
'comment_updated_success' => 'Kommentar aktualisiert',
'comment_delete_confirm' => 'Möchtst Du diesen Kommentar wirklich löschen?',
'comment_delete_confirm' => 'Möchtst du diesen Kommentar wirklich löschen?',
'comment_in_reply_to' => 'Antwort auf :commentId',
// Revision
'revision_delete_confirm' => 'Bist Du sicher, dass Du diese Revision löschen möchtest?',
'revision_restore_confirm' => 'Sind Sie sicher, dass Sie diese Revision wiederherstellen wollen? Der aktuelle Seiteninhalt wird ersetzt.',
'revision_delete_confirm' => 'Bist du sicher, dass du diese Revision löschen möchtest?',
'revision_restore_confirm' => 'Bist du sicher, dass du diese Revision wiederherstellen willst? Der aktuelle Seiteninhalt wird ersetzt.',
'revision_delete_success' => 'Revision gelöscht',
'revision_cannot_delete_latest' => 'Die letzte Version kann nicht gelöscht werden.',

View File

@@ -14,14 +14,14 @@ return [
'email_confirmation_invalid' => 'Der Bestätigungslink ist nicht gültig oder wurde bereits verwendet. Bitte registriere dich erneut.',
'email_confirmation_expired' => 'Der Bestätigungslink ist abgelaufen. Es wurde eine neue Bestätigungs-E-Mail gesendet.',
'email_confirmation_awaiting' => 'Die E-Mail-Adresse für das verwendete Konto muss bestätigt werden',
'ldap_fail_anonymous' => 'Anonymer LDAP-Zugriff ist fehlgeschlafgen',
'ldap_fail_anonymous' => 'Anonymer LDAP-Zugriff ist fehlgeschlagen',
'ldap_fail_authed' => 'LDAP-Zugriff mit DN und Passwort ist fehlgeschlagen',
'ldap_extension_not_installed' => 'LDAP-PHP-Erweiterung ist nicht installiert',
'ldap_cannot_connect' => 'Die Verbindung zum LDAP-Server ist fehlgeschlagen. Beim initialen Verbindungsaufbau trat ein Fehler auf',
'saml_already_logged_in' => 'Du bist bereits angemeldet',
'saml_user_not_registered' => 'Kein Benutzer mit ID :name registriert und die automatische Registrierung ist deaktiviert',
'saml_no_email_address' => 'Es konnte keine E-Mail-Adresse für diesen Benutzer in den vom externen Authentifizierungssystem zur Verfügung gestellten Daten gefunden werden',
'saml_invalid_response_id' => 'Die Anfrage vom externen Authentifizierungssystem wird von einem von dieser Anwendung gestarteten Prozess nicht erkannt. Das Zurückgehen nach einem Login könnte dieses Problem verursachen.',
'saml_invalid_response_id' => 'Die Anfrage vom externen Authentifizierungssystem wird von einem von dieser Anwendung gestarteten Prozess nicht erkannt. Das Zurückblättern nach einem Login könnte dieses Problem verursachen.',
'saml_fail_authed' => 'Anmeldung mit :system fehlgeschlagen, System konnte keine erfolgreiche Autorisierung bereitstellen',
'oidc_already_logged_in' => 'Bereits angemeldet',
'oidc_user_not_registered' => 'Der Benutzer :name ist nicht registriert und die automatische Registrierung ist deaktiviert',
@@ -30,13 +30,13 @@ return [
'social_no_action_defined' => 'Es ist keine Aktion definiert',
'social_login_bad_response' => "Fehler bei :socialAccount Login: \n:error",
'social_account_in_use' => 'Dieses :socialAccount-Konto wird bereits verwendet. Bitte melde dich mit dem :socialAccount-Konto an.',
'social_account_email_in_use' => 'Die E-Mail-Adresse ":email" ist bereits registriert. Wenn Du bereits registriert bist, kannst Du Dein :socialAccount-Konto in Deinen Profil-Einstellungen verknüpfen.',
'social_account_existing' => 'Dieses :socialAccount-Konto ist bereits mit Ihrem Profil verknüpft.',
'social_account_email_in_use' => 'Die E-Mail-Adresse ":email" ist bereits registriert. Wenn du bereits registriert bist, kannst du Dein :socialAccount-Konto in Deinen Profil-Einstellungen verknüpfen.',
'social_account_existing' => 'Dieses :socialAccount-Konto ist bereits mit deinem Profil verknüpft.',
'social_account_already_used_existing' => 'Dieses :socialAccount-Konto wird bereits von einem anderen Benutzer verwendet.',
'social_account_not_used' => 'Dieses :socialAccount-Konto ist bisher keinem Benutzer zugeordnet. Du kannst das in Deinen Profil-Einstellungen tun.',
'social_account_register_instructions' => 'Wenn Du bisher kein Social-Media Konto besitzt, kannst Du ein solches Konto mit der :socialAccount Option anlegen.',
'social_account_not_used' => 'Dieses :socialAccount-Konto ist bisher keinem Benutzer zugeordnet. Du kannst das in deinen Profil-Einstellungen tun.',
'social_account_register_instructions' => 'Wenn du bisher kein Social-Media Konto besitzt, kannst du ein solches Konto mit der :socialAccount Option anlegen.',
'social_driver_not_found' => 'Treiber für Social-Media-Konten nicht gefunden',
'social_driver_not_configured' => 'Ihr :socialAccount-Konto ist nicht korrekt konfiguriert.',
'social_driver_not_configured' => 'Dein :socialAccount-Konto ist nicht korrekt konfiguriert.',
'invite_token_expired' => 'Dieser Einladungslink ist abgelaufen. Du kannst stattdessen versuchen, dein Passwort zurückzusetzen.',
// System
@@ -44,7 +44,7 @@ return [
'cannot_get_image_from_url' => 'Bild konnte nicht von der URL :url geladen werden.',
'cannot_create_thumbs' => 'Der Server kann keine Vorschau-Bilder erzeugen. Bitte prüfe, ob die GD PHP-Erweiterung installiert ist.',
'server_upload_limit' => 'Der Server verbietet das Hochladen von Dateien mit dieser Dateigröße. Bitte versuche es mit einer kleineren Datei.',
'uploaded' => 'Der Server verbietet das Hochladen von Dateien mit dieser Dateigröße. Bitte versuchen Sie es mit einer kleineren Datei.',
'uploaded' => 'Der Server verbietet das Hochladen von Dateien mit dieser Dateigröße. Bitte versuche es mit einer kleineren Datei.',
'image_upload_error' => 'Beim Hochladen des Bildes trat ein Fehler auf.',
'image_upload_type_error' => 'Der Bildtyp der hochgeladenen Datei ist ungültig.',
'file_upload_timeout' => 'Der Upload der Datei ist abgelaufen.',
@@ -53,7 +53,7 @@ return [
'attachment_not_found' => 'Anhang konnte nicht gefunden werden.',
// Pages
'page_draft_autosave_fail' => 'Fehler beim Speichern des Entwurfs. Stelle sicher, dass Du mit dem Internet verbunden bist, bevor Du den Entwurf dieser Seite speicherst.',
'page_draft_autosave_fail' => 'Fehler beim Speichern des Entwurfs. Stelle sicher, dass du mit dem Internet verbunden bist, bevor du den Entwurf dieser Seite speicherst.',
'page_custom_home_deletion' => 'Eine als Startseite gesetzte Seite kann nicht gelöscht werden.',
// Entities
@@ -74,7 +74,7 @@ return [
'role_cannot_be_edited' => 'Diese Rolle kann nicht bearbeitet werden.',
'role_system_cannot_be_deleted' => 'Dies ist eine Systemrolle und kann nicht gelöscht werden',
'role_registration_default_cannot_delete' => 'Diese Rolle kann nicht gelöscht werden, solange sie als Standardrolle für neue Registrierungen gesetzt ist',
'role_cannot_remove_only_admin' => 'Dieser Benutzer ist der einzige Benutzer, welchem die Administratorrolle zugeordnet ist. Ordnen Sie die Administratorrolle einem anderen Benutzer zu, bevor Sie versuchen, sie hier zu entfernen.',
'role_cannot_remove_only_admin' => 'Dieser Benutzer ist der einzige Benutzer, welchem die Administratorrolle zugeordnet ist. Ordne die Administratorrolle einem anderen Benutzer zu, bevor du versuchst, sie hier zu entfernen.',
// Comments
'comment_list' => 'Beim Abrufen der Kommentare ist ein Fehler aufgetreten.',
@@ -85,7 +85,7 @@ return [
// Error pages
'404_page_not_found' => 'Seite nicht gefunden',
'sorry_page_not_found' => 'Entschuldigung. Die Seite, die Du angefordert hast, wurde nicht gefunden.',
'sorry_page_not_found' => 'Entschuldigung. Die Seite, die du angefordert hast, wurde nicht gefunden.',
'sorry_page_not_found_permission_warning' => 'Wenn du erwartet hast, dass diese Seite existiert, hast du möglicherweise nicht die Berechtigung, sie anzuzeigen.',
'image_not_found' => 'Bild nicht gefunden',
'image_not_found_subtitle' => 'Sorry. Die Bilddatei, nach der du suchst, konnte nicht gefunden werden.',
@@ -99,7 +99,7 @@ return [
'api_no_authorization_found' => 'Kein Autorisierungs-Token für die Anfrage gefunden',
'api_bad_authorization_format' => 'Ein Autorisierungs-Token wurde auf die Anfrage gefunden, aber das Format schien falsch zu sein',
'api_user_token_not_found' => 'Es wurde kein passender API-Token für den angegebenen Autorisierungs-Token gefunden',
'api_incorrect_token_secret' => 'Das für den API-Token angegebene geheimen Token ist falsch',
'api_incorrect_token_secret' => 'Das für den API-Token angegebene geheime Token ist falsch',
'api_user_no_api_permission' => 'Der Besitzer des verwendeten API-Token hat keine Berechtigung für API-Aufrufe',
'api_user_token_expired' => 'Das verwendete Autorisierungs-Token ist abgelaufen',

View File

@@ -5,14 +5,14 @@
*/
return [
'shortcuts' => 'Tastenkürzel',
'shortcuts_interface' => 'Interface Keyboard Shortcuts',
'shortcuts_toggle_desc' => 'Here you can enable or disable keyboard system interface shortcuts, used for navigation and actions.',
'shortcuts_customize_desc' => 'You can customize each of the shortcuts below. Just press your desired key combination after selecting the input for a shortcut.',
'shortcuts_toggle_label' => 'Keyboard shortcuts enabled',
'shortcuts' => 'Kürzel',
'shortcuts_interface' => 'Oberflächen-Tastaturkürzel',
'shortcuts_toggle_desc' => 'Hier kannst du Tastaturkürzel für die Systemoberfläche für Navigation und Aktionen aktivieren oder deaktivieren.',
'shortcuts_customize_desc' => 'Unten kannst du alle Tastenkürzel anpassen. Drücke einfach die gewünschte Tastenkombination, nachdem du die Eingabe für eine Tastenkombination ausgewählt hast.',
'shortcuts_toggle_label' => 'Tastaturkürzel aktiviert',
'shortcuts_section_navigation' => 'Navigation',
'shortcuts_section_actions' => 'Gemeinsame Aktionen',
'shortcuts_section_actions' => 'Häufige Aktionen',
'shortcuts_save' => 'Tastenkürzel speichern',
'shortcuts_overlay_desc' => 'Hinweis: Wenn Tastenkürzel aktiviert sind, ist ein Hilfsoverlay durch Drücken von "?" verfügbar, welches die verfügbaren Tastenkürzel für Aktionen hervorhebt, die aktuell auf dem Bildschirm sichtbar sind.',
'shortcuts_overlay_desc' => 'Hinweis: Wenn Tastenkürzel aktiviert sind, ist ein Hilfefähnchen durch Drücken von "?" verfügbar, welches die verfügbaren Tastenkürzel für Aktionen hervorhebt, die aktuell auf dem Bildschirm sichtbar sind.',
'shortcuts_update_success' => 'Tastenkürzel Einstellungen wurden aktualisiert!',
];

View File

@@ -20,13 +20,13 @@ return [
'app_name_desc' => 'Dieser Name wird im Header und in E-Mails angezeigt.',
'app_name_header' => 'Anwendungsname im Header anzeigen?',
'app_public_access' => 'Öffentlicher Zugriff',
'app_public_access_desc' => 'Wenn Sie diese Option aktivieren, können Besucher, die nicht angemeldet sind, auf Inhalte in Ihrer BookStack-Instanz zugreifen.',
'app_public_access_desc' => 'Wenn du diese Option aktivierst, können Besucher, die nicht angemeldet sind, auf Inhalte in deiner BookStack-Instanz zugreifen.',
'app_public_access_desc_guest' => 'Der Zugang für öffentliche Besucher kann über den Benutzer "Guest" gesteuert werden.',
'app_public_access_toggle' => 'Öffentlichen Zugriff erlauben',
'app_public_viewing' => 'Öffentliche Ansicht erlauben?',
'app_secure_images' => 'Erhöhte Sicherheit für hochgeladene Bilder aktivieren?',
'app_secure_images_toggle' => 'Aktiviere Bild-Upload mit höherer Sicherheit',
'app_secure_images_desc' => 'Aus Leistungsgründen sind alle Bilder öffentlich sichtbar. Diese Option fügt zufällige, schwer zu eratene, Zeichenketten zu Bild-URLs hinzu. Stellen sie sicher, dass Verzeichnisindizes deaktiviert sind, um einen einfachen Zugriff zu verhindern.',
'app_secure_images_desc' => 'Aus Leistungsgründen sind alle Bilder öffentlich sichtbar. Diese Option fügt zufällige, schwer zu erratene, Zeichenketten zu Bild-URLs hinzu. Stelle sicher, dass Verzeichnisindizes deaktiviert sind, um einen einfachen Zugriff zu verhindern.',
'app_default_editor' => 'Standard Seiteneditor',
'app_default_editor_desc' => 'Wähle aus, welcher Editor bei der Bearbeitung neuer Seiten standardmäßig verwendet werden soll. Dies kann auf Seitenebene außer Kraft gesetzt werden, sofern die Berechtigungen dies zulassen.',
'app_custom_html' => 'Benutzerdefinierter HTML <head> Inhalt',
@@ -37,12 +37,12 @@ return [
Größere Bilder werden verkleinert.',
'app_primary_color' => 'Primäre Anwendungsfarbe',
'app_primary_color_desc' => 'Dies sollte ein HEX Wert sein.
Wenn Du nichts eingibst, wird die Anwendung auf die Standardfarbe zurückgesetzt.',
Wenn du nichts eingibst, wird die Anwendung auf die Standardfarbe zurückgesetzt.',
'app_homepage' => 'Startseite der Anwendung',
'app_homepage_desc' => 'Wähle eine Seite als Startseite aus, die statt der Standardansicht angezeigt werden soll. Seitenberechtigungen werden für die ausgewählten Seiten ignoriert.',
'app_homepage_select' => 'Wählen Sie eine Seite aus',
'app_homepage_select' => 'Wähle eine Seite aus',
'app_footer_links' => 'Fußzeilen-Links',
'app_footer_links_desc' => 'Fügen Sie Links hinzu, die innerhalb der Seitenfußzeile angezeigt werden. Diese werden am unteren Ende der meisten Seiten angezeigt, einschließlich derjenigen, die keinen Login benötigen. Sie können die Bezeichnung "trans::<key>" verwenden, um systemdefinierte Übersetzungen zu verwenden. Beispiel: Mit "trans::common.privacy_policy" wird der übersetzte Text "Privacy Policy" bereitgestellt, und "trans::common.terms_of_service" liefert den übersetzten Text "Terms of Service".',
'app_footer_links_desc' => 'Füge Links hinzu, die innerhalb der Seitenfußzeile angezeigt werden. Diese werden am unteren Ende der meisten Seiten angezeigt, einschließlich derjenigen, die keinen Login benötigen. Du kannst die Bezeichnung "trans::<key>" verwenden, um systemdefinierte Übersetzungen zu verwenden. Beispiel: Mit "trans::common.privacy_policy" wird der übersetzte Text "Privacy Policy" bereitgestellt, und "trans::common.terms_of_service" liefert den übersetzten Text "Terms of Service".',
'app_footer_links_label' => 'Link-Label',
'app_footer_links_url' => 'Link-URL',
'app_footer_links_add' => 'Fußzeilenlink hinzufügen',
@@ -70,7 +70,7 @@ Wenn Du nichts eingibst, wird die Anwendung auf die Standardfarbe zurückgesetzt
'reg_email_confirmation_toggle' => 'Bestätigung per E-Mail erforderlich',
'reg_confirm_email_desc' => 'Falls die Einschränkung für Domains genutzt wird, ist die Bestätigung per E-Mail zwingend erforderlich und der untenstehende Wert wird ignoriert.',
'reg_confirm_restrict_domain' => 'Registrierung auf bestimmte Domains einschränken',
'reg_confirm_restrict_domain_desc' => 'Fügen sie eine durch Komma getrennte Liste von Domains hinzu, auf die die Registrierung eingeschränkt werden soll. Benutzern wird eine E-Mail gesendet, um ihre E-Mail Adresse zu bestätigen, bevor sie diese Anwendung nutzen können.
'reg_confirm_restrict_domain_desc' => 'Füge eine durch Komma getrennte Liste von Domains hinzu, auf die die Registrierung eingeschränkt werden soll. Benutzern wird eine E-Mail gesendet, um ihre E-Mail Adresse zu bestätigen, bevor sie diese Anwendung nutzen können.
Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung ändern.',
'reg_confirm_restrict_domain_placeholder' => 'Keine Einschränkung gesetzt',
@@ -80,10 +80,10 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung
'maint_image_cleanup_desc' => 'Überprüft Seiten- und Versionsinhalte auf ungenutzte und mehrfach vorhandene Bilder. Erstelle vor dem Start ein Backup Deiner Datenbank und Bilder.',
'maint_delete_images_only_in_revisions' => 'Lösche auch Bilder, die nur in alten Seitenüberarbeitungen vorhanden sind',
'maint_image_cleanup_run' => 'Reinigung starten',
'maint_image_cleanup_warning' => ':count eventuell unbenutze Bilder wurden gefunden. Möchtest Du diese Bilder löschen?',
'maint_image_cleanup_warning' => ':count eventuell unbenutze Bilder wurden gefunden. Möchtest du diese Bilder löschen?',
'maint_image_cleanup_success' => ':count eventuell unbenutze Bilder wurden gefunden und gelöscht.',
'maint_image_cleanup_nothing_found' => 'Keine unbenutzen Bilder gefunden. Nichts zu löschen!',
'maint_send_test_email' => 'Test Email versenden',
'maint_send_test_email' => 'Test E-Mail versenden',
'maint_send_test_email_desc' => 'Dies sendet eine Test E-Mail an die in deinem Profil angegebene E-Mail-Adresse.',
'maint_send_test_email_run' => 'Sende eine Test E-Mail',
'maint_send_test_email_success' => 'E-Mail wurde an :address gesendet',
@@ -99,7 +99,7 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung
// Recycle Bin
'recycle_bin' => 'Papierkorb',
'recycle_bin_desc' => 'Hier können Sie gelöschte Einträge wiederherstellen oder sie dauerhaft aus dem System entfernen. Diese Liste ist nicht gefiltert, im Gegensatz zu ähnlichen Aktivitätslisten im System, wo Berechtigungsfilter angewendet werden.',
'recycle_bin_desc' => 'Hier kannst du gelöschte Einträge wiederherstellen oder sie dauerhaft aus dem System entfernen. Diese Liste ist nicht gefiltert, im Gegensatz zu ähnlichen Aktivitätslisten im System, wo Berechtigungsfilter angewendet werden.',
'recycle_bin_deleted_item' => 'Gelöschter Eintrag',
'recycle_bin_deleted_parent' => 'Übergeordnet',
'recycle_bin_deleted_by' => 'Gelöscht von',
@@ -108,11 +108,11 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung
'recycle_bin_restore' => 'Wiederherstellen',
'recycle_bin_contents_empty' => 'Der Papierkorb ist derzeit leer',
'recycle_bin_empty' => 'Papierkorb leeren',
'recycle_bin_empty_confirm' => 'Dies wird alle Einträge im Papierkorb dauerhaft entfernen, einschließlich der Inhalte, die darin enthalten sind. Sind Sie sicher, dass Sie den Papierkorb leeren möchten?',
'recycle_bin_destroy_confirm' => 'Diese Aktion wird diesen Eintrag zusammen mit allen unten aufgeführten Untereinträgen dauerhaft aus dem System löschen und Sie werden nicht in der Lage sein, diesen Inhalt wiederherzustellen. Sind Sie sicher, dass Sie diesen Eintrag endgültig löschen möchten?',
'recycle_bin_empty_confirm' => 'Dies wird alle Einträge im Papierkorb dauerhaft entfernen, einschließlich der Inhalte, die darin enthalten sind. Bist du sicher, dass du den Papierkorb leeren möchtest?',
'recycle_bin_destroy_confirm' => 'Diese Aktion wird diesen Eintrag zusammen mit allen unten aufgeführten Untereinträgen dauerhaft aus dem System löschen und du wirst nicht in der Lage sein, diesen Inhalt wiederherzustellen. Bist du sicher, dass du diesen Eintrag endgültig löschen möchtest?',
'recycle_bin_destroy_list' => 'Zu löschende Einträge',
'recycle_bin_restore_list' => 'Wiederherzustellende Einträge',
'recycle_bin_restore_confirm' => 'Mit dieser Aktion wird der gelöschte Eintrag einschließlich aller untergeordneten Einträge an seinen ursprünglichen Ort wiederherstellen. Wenn der ursprüngliche Ort gelöscht wurde und sich nun im Papierkorb befindet, muss auch der übergeordnete Eintrag wiederhergestellt werden.',
'recycle_bin_restore_confirm' => 'Mit dieser Aktion wird der gelöschte Eintrag einschließlich aller untergeordneten Einträge an seinem ursprünglichen Ort wiederhergestellt. Wenn der ursprüngliche Ort gelöscht wurde und sich nun im Papierkorb befindet, muss auch der übergeordnete Eintrag wiederhergestellt werden.',
'recycle_bin_restore_deleted_parent' => 'Der übergeordnete Eintrag wurde ebenfalls gelöscht. Dieser Eintrag wird weiterhin als gelöscht zählen, bis auch der übergeordnete Eintrag wiederhergestellt wurde.',
'recycle_bin_restore_parent' => 'Übergeordneter Eintrag wiederherstellen',
'recycle_bin_destroy_notification' => ':count Einträge wurden aus dem Papierkorb gelöscht.',
@@ -136,18 +136,18 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung
// Role Settings
'roles' => 'Rollen',
'role_user_roles' => 'Benutzer-Rollen',
'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.',
'roles_x_users_assigned' => '1 user assigned|:count users assigned',
'roles_x_permissions_provided' => '1 permission|:count permissions',
'roles_assigned_users' => 'Assigned Users',
'roles_permissions_provided' => 'Provided Permissions',
'roles_index_desc' => 'Rollen werden verwendet, um Benutzer zu gruppieren und System-Berechtigungen für ihre Mitglieder zuzuweisen. Wenn ein Benutzer Mitglied mehrerer Rollen ist, stapeln die gewährten Berechtigungen und der Benutzer wird alle Fähigkeiten erben.',
'roles_x_users_assigned' => '1 Benutzer zugewiesen|:count Benutzer zugewiesen',
'roles_x_permissions_provided' => '1 Berechtigung|:count Berechtigungen',
'roles_assigned_users' => 'Zugewiesene Benutzer',
'roles_permissions_provided' => 'Genutzte Berechtigungen',
'role_create' => 'Neue Rolle anlegen',
'role_create_success' => 'Rolle erfolgreich angelegt',
'role_delete' => 'Rolle löschen',
'role_delete_confirm' => 'Du möchtest die Rolle ":roleName" löschen.',
'role_delete_users_assigned' => 'Diese Rolle ist :userCount Benutzern zugeordnet. Du kannst unten eine neue Rolle auswählen, die Du diesen Benutzern zuordnen möchtest.',
'role_delete_confirm' => 'Dies wird die Rolle ":roleName" löschen.',
'role_delete_users_assigned' => 'Diese Rolle ist :userCount Benutzern zugeordnet. Du kannst unten eine neue Rolle auswählen, die du diesen Benutzern zuordnen möchtest.',
'role_delete_no_migration' => "Den Benutzern keine andere Rolle zuordnen",
'role_delete_sure' => 'Bist Du sicher, dass Du diese Rolle löschen möchtest?',
'role_delete_sure' => 'Bist du sicher, dass du diese Rolle löschen möchtest?',
'role_delete_success' => 'Rolle erfolgreich gelöscht',
'role_edit' => 'Rolle bearbeiten',
'role_details' => 'Rollendetails',
@@ -166,8 +166,8 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung
'role_export_content' => 'Inhalt exportieren',
'role_editor_change' => 'Seiteneditor ändern',
'role_asset' => 'Berechtigungen',
'roles_system_warning' => 'Beachten Sie, dass der Zugriff auf eine der oben genannten drei Berechtigungen einem Benutzer erlauben kann, seine eigenen Berechtigungen oder die Rechte anderer im System zu ändern. Weisen Sie nur Rollen, mit diesen Berechtigungen, vertrauenswürdigen Benutzern zu.',
'role_asset_desc' => 'Diese Berechtigungen gelten für den Standard-Zugriff innerhalb des Systems. Berechtigungen für Bücher, Kapitel und Seiten überschreiben diese Berechtigungenen.',
'roles_system_warning' => 'Beachte, dass der Zugriff auf eine der oben genannten drei Berechtigungen einem Benutzer erlauben kann, seine eigenen Berechtigungen oder die Rechte anderer im System zu ändern. Weise nur Rollen mit diesen Berechtigungen vertrauenswürdigen Benutzern zu.',
'role_asset_desc' => 'Diese Berechtigungen gelten für den Standard-Zugriff innerhalb des Systems. Berechtigungen für Bücher, Kapitel und Seiten überschreiben diese Berechtigungen.',
'role_asset_admins' => 'Administratoren erhalten automatisch Zugriff auf alle Inhalte, aber diese Optionen können Oberflächenoptionen ein- oder ausblenden.',
'role_asset_image_view_note' => 'Das bezieht sich auf die Sichtbarkeit innerhalb des Bildmanagers. Der tatsächliche Zugriff auf hochgeladene Bilddateien hängt von der Speicheroption des Systems für Bilder ab.',
'role_all' => 'Alle',
@@ -180,14 +180,14 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung
// Users
'users' => 'Benutzer',
'users_index_desc' => 'Create & manage individual user accounts within the system. User accounts are used for login and attribution of content & activity. Access permissions are primarily role-based but user content ownership, among other factors, may also affect permissions & access.',
'users_index_desc' => 'Erstelle und Verwalte individuelle Benutzerkonten innerhalb des Systems. Benutzerkonten werden zur Anmeldung und der Zuordnung von Inhalten und Aktivitäten verwendet. Zugriffsberechtigungen sind in erster Linie rollenbasiert, aber der Besitz von Benutzerinhalten kann unter anderem auch Berechtigungen beeinflussen.',
'user_profile' => 'Benutzerprofil',
'users_add_new' => 'Benutzer hinzufügen',
'users_search' => 'Benutzer suchen',
'users_latest_activity' => 'Neueste Aktivitäten',
'users_details' => 'Benutzerdetails',
'users_details_desc' => 'Legen Sie für diesen Benutzer einen Anzeigenamen und eine E-Mail-Adresse fest. Die E-Mail-Adresse wird bei der Anmeldung verwendet.',
'users_details_desc_no_email' => 'Legen Sie für diesen Benutzer einen Anzeigenamen fest, damit andere ihn erkennen können.',
'users_details_desc' => 'Lege für diesen Benutzer einen Anzeigenamen und eine E-Mail-Adresse fest. Die E-Mail-Adresse wird bei der Anmeldung verwendet.',
'users_details_desc_no_email' => 'Lege für diesen Benutzer einen Anzeigenamen fest, damit andere ihn erkennen können.',
'users_role' => 'Benutzerrollen',
'users_role_desc' => 'Wählen Sie aus, welchen Rollen dieser Benutzer zugeordnet werden soll. Wenn ein Benutzer mehreren Rollen zugeordnet ist, werden die Berechtigungen dieser Rollen gestapelt und er erhält alle Fähigkeiten der zugewiesenen Rollen.',
'users_password' => 'Benutzerpasswort',
@@ -196,14 +196,14 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung
'users_send_invite_option' => 'Benutzer-Einladungs-E-Mail senden',
'users_external_auth_id' => 'Externe Authentifizierungs-ID',
'users_external_auth_id_desc' => 'Dies ist die ID, die verwendet wird, um diesen Benutzer bei der Kommunikation mit deinem externen Authentifizierungssystem abzugleichen.',
'users_password_warning' => 'Fülle die folgenden Felder nur aus, wenn Du Dein Passwort ändern möchtest:',
'users_password_warning' => 'Fülle die folgenden Felder nur aus, wenn du dein Passwort ändern möchtest:',
'users_system_public' => 'Dieser Benutzer repräsentiert alle unangemeldeten Benutzer, die diese Seite betrachten. Er kann nicht zum Anmelden benutzt werden, sondern wird automatisch zugeordnet.',
'users_delete' => 'Benutzer löschen',
'users_delete_named' => 'Benutzer ":userName" löschen',
'users_delete_warning' => 'Der Benutzer ":userName" wird aus dem System gelöscht.',
'users_delete_confirm' => 'Bist Du sicher, dass Du diesen Benutzer löschen möchtest?',
'users_delete_confirm' => 'Bist du sicher, dass du diesen Benutzer löschen möchtest?',
'users_migrate_ownership' => 'Besitz migrieren',
'users_migrate_ownership_desc' => 'Wählen Sie hier einen Benutzer, wenn Sie möchten, dass ein anderer Benutzer der Besitzer aller Einträge wird, die diesem Benutzer derzeit gehören.',
'users_migrate_ownership_desc' => 'Wähle hier einen Benutzer, wenn du möchtest, dass ein anderer Benutzer der Besitzer aller Einträge wird, die diesem Benutzer derzeit gehören.',
'users_none_selected' => 'Kein Benutzer ausgewählt',
'users_edit' => 'Benutzer bearbeiten',
'users_edit_profile' => 'Profil bearbeiten',
@@ -218,7 +218,7 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung
'users_social_connected' => ':socialAccount-Konto wurde erfolgreich mit dem Profil verknüpft.',
'users_social_disconnected' => ':socialAccount-Konto wurde erfolgreich vom Profil gelöst.',
'users_api_tokens' => 'API-Token',
'users_api_tokens_none' => 'Für diesen Benutzer wurden keine API-Token erstellt',
'users_api_tokens_none' => 'Für diesen Benutzer wurden kein API-Token erstellt',
'users_api_tokens_create' => 'Token erstellen',
'users_api_tokens_expires' => 'Endet',
'users_api_tokens_docs' => 'API Dokumentation',
@@ -232,7 +232,7 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung
'user_api_token_name' => 'Name',
'user_api_token_name_desc' => 'Gebe deinem Token einen aussagekräftigen Namen als spätere Erinnerung an seinen Verwendungszweck.',
'user_api_token_expiry' => 'Ablaufdatum',
'user_api_token_expiry_desc' => 'Lege ein Datum fest, an dem dieser Token abläuft. Nach diesem Datum funktionieren Anfragen, die mit diesem Token gestellt werden, nicht mehr. Wenn du dieses Feld leer lässt, wird ein Ablaufdatum von 100 Jahren in der Zukunft festgelegt.',
'user_api_token_expiry_desc' => 'Lege ein Datum fest, zu dem dieser Token abläuft. Nach diesem Datum funktionieren Anfragen, die mit diesem Token gestellt werden, nicht mehr. Wenn du dieses Feld leer lässt, wird ein Ablaufdatum von 100 Jahren in der Zukunft festgelegt.',
'user_api_token_create_secret_message' => 'Unmittelbar nach der Erstellung dieses Tokens wird eine "Token ID" & ein "Token Kennwort" generiert und angezeigt. Das Kennwort wird nur ein einziges Mal angezeigt. Stelle also sicher, dass du den Inhalt an einen sicheren Ort kopierst, bevor du fortfährst.',
'user_api_token_create_success' => 'API-Token erfolgreich erstellt',
'user_api_token_update_success' => 'API-Token erfolgreich aktualisiert',
@@ -250,8 +250,8 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung
// Webhooks
'webhooks' => 'Webhooks',
'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.',
'webhooks_x_trigger_events' => '1 trigger event|:count trigger events',
'webhooks_index_desc' => 'Webhooks sind eine Möglichkeit, Daten an externe URLs zu senden, wenn bestimmte Aktionen und Ereignisse im System auftreten, was eine ereignisbasierte Integration mit externen Plattformen wie Messaging- oder Benachrichtigungssystemen ermöglicht.',
'webhooks_x_trigger_events' => '1 Triggerereignis|:count Triggerereignisse',
'webhooks_create' => 'Neuen Webhook erstellen',
'webhooks_none_created' => 'Es wurden noch keine Webhooks erstellt.',
'webhooks_edit' => 'Webhook bearbeiten',

View File

@@ -61,8 +61,8 @@ return [
'email_confirm_send_error' => 'Απαιτείται επιβεβαίωση μέσω email, αλλά το σύστημα δεν μπόρεσε να στείλει το email. Επικοινωνήστε με τον διαχειριστή για να βεβαιωθείτε ότι το email έχει ρυθμιστεί σωστά.',
'email_confirm_success' => 'Το email σας επιβεβαιώθηκε! Θα πρέπει τώρα να μπορείτε να συνδεθείτε χρησιμοποιώντας αυτήν τη διεύθυνση email.',
'email_confirm_resent' => 'Το email επιβεβαίωσης στάλθηκε εκ νέου. Ελέγξτε τα εισερχόμενά σας.',
'email_confirm_thanks' => 'Thanks for confirming!',
'email_confirm_thanks_desc' => 'Please wait a moment while your confirmation is handled. If you are not redirected after 3 seconds press the "Continue" link below to proceed.',
'email_confirm_thanks' => 'Ευχαριστούμε για την επιβεβαίωση!',
'email_confirm_thanks_desc' => 'Παρακαλώ περιμένετε λίγο όσο διεκπεραιώνεται η επιβεβαίωσή σας. Εάν δεν ανακατευθυνθείτε μετά από 3 δευτερόλεπτα, πατήστε τον παρακάτω σύνδεσμο "Συνέχεια" για να συνεχίσετε.',
'email_not_confirmed' => 'Η διεύθυνση email δεν επιβεβαιώθηκε',
'email_not_confirmed_text' => 'Η διεύθυνση email σας δεν έχει ακόμη επιβεβαιωθεί.',

View File

@@ -25,7 +25,7 @@ return [
'actions' => 'Ενέργειες',
'view' => 'Προβολή',
'view_all' => 'Προβολή όλων',
'new' => 'New',
'new' => 'Νέο',
'create' => 'Δημιουργία',
'update' => 'Ενημέρωση',
'edit' => 'Επεξεργασία',
@@ -81,14 +81,14 @@ return [
'none' => 'Κανένας',
// Header
'homepage' => 'Homepage',
'homepage' => 'Αρχική σελίδα',
'header_menu_expand' => 'Αναπτύξτε το Head Menu',
'profile_menu' => 'Μενού Προφίλ',
'view_profile' => 'Προβολή προφίλ',
'edit_profile' => 'Επεξεργασία προφίλ',
'dark_mode' => 'Σκουρόχρωμη εμφάνιση',
'light_mode' => 'Ανοιχτόχρωμη εμφάνιση',
'global_search' => 'Global Search',
'global_search' => 'Καθολική αναζήτηση',
// Layout tabs
'tab_info' => 'Πληροφορίες',

View File

@@ -66,7 +66,7 @@ return [
'insert_link_title' => 'Εισαγωγή/Επεξεργασία συνδέσμου',
'insert_horizontal_line' => 'Εισαγωγή οριζόντιας γραμμής',
'insert_code_block' => 'Εισαγωγή μπλοκ κώδικα',
'edit_code_block' => 'Edit code block',
'edit_code_block' => 'Επεξεργασία μπλοκ κώδικα',
'insert_drawing' => 'Εισαγωγή/Επεξεργασία σχεδίου',
'drawing_manager' => 'Διαχειριστής σχεδίασης',
'insert_media' => 'Εισαγωγή/Επεξεργασία πολυμέσων',
@@ -144,11 +144,11 @@ return [
'url' => 'URL',
'text_to_display' => 'Κείμενο εμφάνισης',
'title' => 'Τίτλος',
'open_link' => 'Open link',
'open_link_in' => 'Open link in...',
'open_link' => 'Άνοιγμα συνδέσμου',
'open_link_in' => 'Άνοιγμα συνδέσμου σε...',
'open_link_current' => 'Τρέχον παράθυρο',
'open_link_new' => 'Νέο παράθυρο',
'remove_link' => 'Remove link',
'remove_link' => 'Αφαίρεση συνδέσμου',
'insert_collapsible' => 'Εισαγωγή πτυσσόμενου μπλοκ',
'collapsible_unwrap' => 'Μετατροπή πτυσσόμενου μπλοκ σε παράγραφο',
'edit_label' => 'Επεξεργασία ετικέτας',

View File

@@ -42,15 +42,15 @@ return [
// Permissions and restrictions
'permissions' => 'Δικαιώματα',
'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.',
'permissions_desc' => 'Ορίστε εδώ δικαιώματα για να παρακάμψετε τα προκαθορισμένα δικαιώματα που παρέχονται από τους ρόλους των χρηστών.',
'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.',
'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.',
'permissions_save' => 'Αποθήκευση Δικαιωμάτων',
'permissions_owner' => 'Ιδιοκτήτης / Κάτοχος',
'permissions_role_everyone_else' => 'Everyone Else',
'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.',
'permissions_role_override' => 'Override permissions for role',
'permissions_inherit_defaults' => 'Inherit defaults',
'permissions_role_everyone_else_desc' => 'Ορίστε δικαιώματα για όλους τους ρόλους που δεν παραβλέπονται συγκεκριμένα.',
'permissions_role_override' => 'Παράκαμψη δικαιωμάτων για ρόλο',
'permissions_inherit_defaults' => 'Κληρονόμηση προεπιλογών',
// Search
'search_results' => 'Αποτελέσματα αναζήτησης',
@@ -224,8 +224,8 @@ return [
'pages_md_insert_image' => 'Εισαγωγή Εικόνας',
'pages_md_insert_link' => 'Εισαγωγή/Επεξεργασία συνδέσμου',
'pages_md_insert_drawing' => 'Εισαγωγή Σχεδίου',
'pages_md_show_preview' => 'Show preview',
'pages_md_sync_scroll' => 'Sync preview scroll',
'pages_md_show_preview' => 'Εμφάνιση προεπισκόπησης',
'pages_md_sync_scroll' => 'Συγχρονισμός προεπισκόπησης',
'pages_not_in_chapter' => 'Η σελίδα δεν είναι σε κεφάλαιο',
'pages_move' => 'Μετακίνηση Σελίδας',
'pages_move_success' => 'Η σελίδα μετακινήθηκε στο ":parentName"',
@@ -243,7 +243,7 @@ return [
'pages_revisions_created_by' => 'Δημιουργήθηκε από',
'pages_revisions_date' => 'Ημερομηνία Αναθεώρησης',
'pages_revisions_number' => '#',
'pages_revisions_sort_number' => 'Revision Number',
'pages_revisions_sort_number' => 'Αριθμός αναθεώρησης',
'pages_revisions_numbered' => 'Αναθεώρηση #',
'pages_revisions_numbered_changes' => 'Αναθεώρηση #:id Αλλαγές',
'pages_revisions_editor' => 'Τύπος Επεξεργαστή',

View File

@@ -5,14 +5,14 @@
*/
return [
'shortcuts' => 'Shortcuts',
'shortcuts_interface' => 'Interface Keyboard Shortcuts',
'shortcuts_toggle_desc' => 'Here you can enable or disable keyboard system interface shortcuts, used for navigation and actions.',
'shortcuts_customize_desc' => 'You can customize each of the shortcuts below. Just press your desired key combination after selecting the input for a shortcut.',
'shortcuts_toggle_label' => 'Keyboard shortcuts enabled',
'shortcuts_section_navigation' => 'Navigation',
'shortcuts_section_actions' => 'Common Actions',
'shortcuts_save' => 'Save Shortcuts',
'shortcuts_overlay_desc' => 'Note: When shortcuts are enabled a helper overlay is available via pressing "?" which will highlight the available shortcuts for actions currently visible on the screen.',
'shortcuts_update_success' => 'Shortcut preferences have been updated!',
'shortcuts' => 'Συντομεύσεις',
'shortcuts_interface' => 'Συντομεύσεις Πληκτρολογίου Διεπαφής',
'shortcuts_toggle_desc' => 'Εδώ μπορείτε να ενεργοποιήσετε ή να απενεργοποιήσετε τις συντομεύσεις του συστήματος πληκτρολογίου, που χρησιμοποιούνται για την πλοήγηση και τις ενέργειες.',
'shortcuts_customize_desc' => 'Μπορείτε να προσαρμόσετε κάθε μία από τις παρακάτω συντομεύσεις. Απλά πατήστε το επιθυμητό συνδυασμό πλήκτρων μετά την επιλογή της εισόδου για μια συντόμευση.',
'shortcuts_toggle_label' => 'Ενεργοποίηση συντομεύσεων πληκτρολογίου',
'shortcuts_section_navigation' => 'Πλοήγηση',
'shortcuts_section_actions' => 'Κοινές ενέργειες',
'shortcuts_save' => 'Αποθήκευση Συντομεύσεων',
'shortcuts_overlay_desc' => 'Σημείωση: Όταν οι συντομεύσεις είναι ενεργοποιημένες μια βοηθητική επικάλυψη είναι διαθέσιμη πατώντας "?" που θα τονίσει τις διαθέσιμες συντομεύσεις για ενέργειες που είναι ορατές στην οθόνη.',
'shortcuts_update_success' => 'Οι προτιμήσεις σας αποθηκεύτηκαν!',
];

View File

@@ -136,8 +136,8 @@ return [
'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.',
'roles_x_users_assigned' => '1 user assigned|:count users assigned',
'roles_x_permissions_provided' => '1 permission|:count permissions',
'roles_assigned_users' => 'Assigned Users',
'roles_permissions_provided' => 'Provided Permissions',
'roles_assigned_users' => 'Εκχωρημένοι χρήστες',
'roles_permissions_provided' => 'Παρεχόμενα Δικαιώματα',
'role_create' => 'Δημιουργία νέου ρόλου',
'role_create_success' => 'Ο Ρόλος δημιουργήθηκε με επιτυχία',
'role_delete' => 'Διαγραφή Ρόλου',

View File

@@ -18,7 +18,6 @@ return [
'name' => 'Name',
'description' => 'Description',
'role' => 'Role',
'user' => 'User',
'cover_image' => 'Cover image',
'cover_image_description' => 'This image should be approx 440x250px.',

View File

@@ -50,7 +50,6 @@ return [
'permissions_role_everyone_else' => 'Everyone Else',
'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.',
'permissions_role_override' => 'Override permissions for role',
'permissions_user_override' => 'Override permissions for user',
'permissions_inherit_defaults' => 'Inherit defaults',
// Search

View File

@@ -5,14 +5,14 @@
*/
return [
'shortcuts' => 'Accesos rápidos',
'shortcuts_interface' => 'Atajos del Teclado de la Interfaz',
'shortcuts_toggle_desc' => 'Aquí puede activar o desactivar los accesos rápidos de la interfaz, utilizados para la navegación y las acciones.',
'shortcuts' => 'Accesos directos',
'shortcuts_interface' => 'Accesos directos de la interfaz',
'shortcuts_toggle_desc' => 'Aquí puede activar o desactivar los accesos directos de la interfaz, utilizados para la navegación y las acciones.',
'shortcuts_customize_desc' => 'Puede personalizar cada uno de los accesos directos a continuación. Simplemente pulse la combinación de teclas deseada después de seleccionar la entrada para un acceso directo.',
'shortcuts_toggle_label' => 'Accesos directos habilitados',
'shortcuts_section_navigation' => 'Navegación',
'shortcuts_section_actions' => 'Acciones comunes',
'shortcuts_save' => 'Guardar atajos',
'shortcuts_save' => 'Guardar accesos directos',
'shortcuts_overlay_desc' => 'Nota: Cuando se activan los accesos directos se puede mostrar la ayuda presionando la tecla "?" que resaltará los accesos rápidos disponibles para las acciones actualmente visibles en la pantalla.',
'shortcuts_update_success' => '¡Las preferencias de accesos rápidos han sido actualizadas!',
'shortcuts_update_success' => '¡Las preferencias de accesos directos han sido actualizadas!',
];

View File

@@ -224,8 +224,8 @@ return [
'pages_md_insert_image' => 'Lisa pilt',
'pages_md_insert_link' => 'Lisa viide',
'pages_md_insert_drawing' => 'Lisa joonis',
'pages_md_show_preview' => 'Show preview',
'pages_md_sync_scroll' => 'Sync preview scroll',
'pages_md_show_preview' => 'Näita eelvaadet',
'pages_md_sync_scroll' => 'Sünkrooni eelvaate kerimine',
'pages_not_in_chapter' => 'Leht ei kuulu peatüki alla',
'pages_move' => 'Liiguta leht',
'pages_move_success' => 'Leht liigutatud ":parentName" alla',

View File

@@ -28,8 +28,8 @@ return [
// Books
'book_create' => 'liburua sortuta',
'book_create_notification' => 'Liburua ongi sortu da',
'book_create_from_chapter' => 'converted chapter to book',
'book_create_from_chapter_notification' => 'Chapter successfully converted to a book',
'book_create_from_chapter' => 'bihurtu kapitulua liburu',
'book_create_from_chapter_notification' => 'Kapitulua ongi bilakatu da liburu',
'book_update' => 'liburua eguneratuta',
'book_update_notification' => 'Liburua egoki eguneratua',
'book_delete' => 'liburua ezabatua',
@@ -38,14 +38,14 @@ return [
'book_sort_notification' => 'Liburua ongi bersailaktu da',
// Bookshelves
'bookshelf_create' => 'created shelf',
'bookshelf_create_notification' => 'Shelf successfully created',
'bookshelf_create_from_book' => 'converted book to shelf',
'bookshelf_create_from_book_notification' => 'Book successfully converted to a shelf',
'bookshelf_update' => 'updated shelf',
'bookshelf_update_notification' => 'Shelf successfully updated',
'bookshelf_delete' => 'deleted shelf',
'bookshelf_delete_notification' => 'Shelf successfully deleted',
'bookshelf_create' => 'apalategia sortuta',
'bookshelf_create_notification' => 'Apalategia egoki sortuta',
'bookshelf_create_from_book' => 'liburua apalategi bihurtuta',
'bookshelf_create_from_book_notification' => 'Kapitulua ongi bilakatu da liburu',
'bookshelf_update' => 'apalategia eguneratuta',
'bookshelf_update_notification' => 'Erabiltzailea egoki eguneratua',
'bookshelf_delete' => 'apalategia ezabatua',
'bookshelf_delete_notification' => 'Apalategia egoki ezabatua',
// Favourites
'favourite_add_notification' => '":name" zure gogoetara gehitua izan da',

View File

@@ -5,14 +5,14 @@
*/
return [
'shortcuts' => 'Shortcuts',
'shortcuts_interface' => 'Interface Keyboard Shortcuts',
'shortcuts_toggle_desc' => 'Here you can enable or disable keyboard system interface shortcuts, used for navigation and actions.',
'shortcuts_customize_desc' => 'You can customize each of the shortcuts below. Just press your desired key combination after selecting the input for a shortcut.',
'shortcuts_toggle_label' => 'Keyboard shortcuts enabled',
'shortcuts_section_navigation' => 'Navigation',
'shortcuts_section_actions' => 'Common Actions',
'shortcuts_save' => 'Save Shortcuts',
'shortcuts_overlay_desc' => 'Note: When shortcuts are enabled a helper overlay is available via pressing "?" which will highlight the available shortcuts for actions currently visible on the screen.',
'shortcuts_update_success' => 'Shortcut preferences have been updated!',
'shortcuts' => 'Lastertekla',
'shortcuts_interface' => 'Teklatuko lasterbideak ikusi',
'shortcuts_toggle_desc' => 'Hemen, nabigaziorako eta ekintzetarako erabiltzen diren teklatu-sistemako lasterbideak gaitu edo desgaitu daitezke.',
'shortcuts_customize_desc' => 'Beheko lasterbide bakoitza pertsonalizatu dezakezu. Sakatu nahi duzun tekla konbinazioa lasterbide baterako sarrera aukeratu ondoren.',
'shortcuts_toggle_label' => 'Teklatu-lasterbideak aktibatuta',
'shortcuts_section_navigation' => 'Nabigazioa',
'shortcuts_section_actions' => 'Ohiko ekintzak',
'shortcuts_save' => 'Gorde lasterbideak',
'shortcuts_overlay_desc' => 'Oharra: Lasterbideak gaituta daudenean, "?" sakagailuaren bidez laguntzaileen gainjartze bat egongo da, eta horrek pantailan gaur egun ikus daitezkeen ekintzetarako dauden lasterbideak nabarmenduko ditu.',
'shortcuts_update_success' => 'Zure lehentasunak gorde dira!',
];

View File

@@ -74,7 +74,7 @@ return [
'search_date_options' => 'گزینه های تاریخ',
'search_updated_before' => 'قبلا به روز شده',
'search_updated_after' => 'پس از به روز رسانی',
'search_created_before' => 'قبلا ایجاد شده است',
'search_created_before' => 'ایجاد شده قبل از',
'search_created_after' => 'ایجاد شده پس از',
'search_set_date' => 'تنظیم تاریخ',
'search_update' => 'جستجو را به روز کنید',

View File

@@ -113,7 +113,7 @@ return [
'paste_row_after' => 'Paste row after',
'row_type' => 'Row type',
'row_type_header' => 'Header',
'row_type_body' => 'Body',
'row_type_body' => 'תוכן',
'row_type_footer' => 'Footer',
'alignment' => 'Alignment',
'cut_column' => 'Cut column',

View File

@@ -224,8 +224,8 @@ return [
'pages_md_insert_image' => 'Inserisci Immagina',
'pages_md_insert_link' => 'Inserisci Link Entità',
'pages_md_insert_drawing' => 'Inserisci Disegno',
'pages_md_show_preview' => 'Show preview',
'pages_md_sync_scroll' => 'Sync preview scroll',
'pages_md_show_preview' => 'Visualizza anteprima',
'pages_md_sync_scroll' => 'Sincronizza scorrimento anteprima',
'pages_not_in_chapter' => 'La pagina non è in un capitolo',
'pages_move' => 'Muovi Pagina',
'pages_move_success' => 'Pagina mossa in ":parentName"',

View File

@@ -61,8 +61,8 @@ return [
'email_confirm_send_error' => 'Eメールの確認が必要でしたが、システム上でEメールの送信ができませんでした。管理者に連絡し、Eメールが正しく設定されていることを確認してください。',
'email_confirm_success' => 'メールアドレスが確認されました!このメールアドレスでログインできるようになりました。',
'email_confirm_resent' => '確認メールを再送信しました。受信トレイを確認してください。',
'email_confirm_thanks' => 'Thanks for confirming!',
'email_confirm_thanks_desc' => 'Please wait a moment while your confirmation is handled. If you are not redirected after 3 seconds press the "Continue" link below to proceed.',
'email_confirm_thanks' => '確認いただきありがとうございます!',
'email_confirm_thanks_desc' => '確認のため、しばらくお待ちください。3秒後にリダイレクトされない場合は、下の「続ける」リンクを押して次に進んでください。',
'email_not_confirmed' => 'Eメールアドレスが確認できていません',
'email_not_confirmed_text' => 'Eメールアドレスの確認が完了していません。',

View File

@@ -25,7 +25,7 @@ return [
'actions' => '実行',
'view' => '表示',
'view_all' => 'すべて表示',
'new' => 'New',
'new' => '新規作成',
'create' => '作成',
'update' => '更新',
'edit' => '編集',
@@ -40,7 +40,7 @@ return [
'reset' => 'リセット',
'remove' => '削除',
'add' => '追加',
'configure' => 'Configure',
'configure' => '設定',
'fullscreen' => '全画面',
'favourite' => 'お気に入り',
'unfavourite' => 'お気に入りから削除',
@@ -81,14 +81,14 @@ return [
'none' => 'なし',
// Header
'homepage' => 'Homepage',
'homepage' => 'ホームページ',
'header_menu_expand' => 'ヘッダーメニューを展開',
'profile_menu' => 'プロフィールメニュー',
'view_profile' => 'プロフィール表示',
'edit_profile' => 'プロフィール編集',
'dark_mode' => 'ダークモード',
'light_mode' => 'ライトモード',
'global_search' => 'Global Search',
'global_search' => 'グローバル検索',
// Layout tabs
'tab_info' => '情報',

View File

@@ -66,7 +66,7 @@ return [
'insert_link_title' => 'リンクの挿入・編集',
'insert_horizontal_line' => '水平線を挿入',
'insert_code_block' => 'コードブロックを挿入',
'edit_code_block' => 'Edit code block',
'edit_code_block' => 'コードブロックを編集',
'insert_drawing' => '描画を挿入・編集',
'drawing_manager' => '描画マネージャー',
'insert_media' => 'メディアの挿入・編集',
@@ -144,11 +144,11 @@ return [
'url' => 'リンク先URL',
'text_to_display' => 'リンク元テキスト',
'title' => 'タイトル',
'open_link' => 'Open link',
'open_link_in' => 'Open link in...',
'open_link' => 'リンクを開く',
'open_link_in' => 'リンク先の表示場所',
'open_link_current' => '同じウィンドウ',
'open_link_new' => '新規ウィンドウ',
'remove_link' => 'Remove link',
'remove_link' => 'リンクを削除',
'insert_collapsible' => '折りたたみブロックを追加',
'collapsible_unwrap' => 'ブロックの解除',
'edit_label' => 'ラベルを編集',

View File

@@ -42,15 +42,15 @@ return [
// Permissions and restrictions
'permissions' => '権限',
'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.',
'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.',
'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.',
'permissions_desc' => 'ユーザーの役割によって提供されるデフォルトの権限を上書きするため、ここで権限を設定します。',
'permissions_book_cascade' => 'ブックに設定された権限は、子チャプターや子ページに独自の権限が定義されていない限り、自動的に子チャプターや子ページに継承されます。',
'permissions_chapter_cascade' => 'チャプターに設定された権限は、子ページに独自の権限が定義されていない限り、自動的に子ページに継承されます。',
'permissions_save' => '権限を保存',
'permissions_owner' => '所有者',
'permissions_role_everyone_else' => 'Everyone Else',
'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.',
'permissions_role_override' => 'Override permissions for role',
'permissions_inherit_defaults' => 'Inherit defaults',
'permissions_role_everyone_else' => 'その他の全員',
'permissions_role_everyone_else_desc' => '明示的に上書きされていないすべての役割の権限を設定します。',
'permissions_role_override' => '権限を上書きする役割',
'permissions_inherit_defaults' => 'デフォルトを継承',
// Search
'search_results' => '検索結果',
@@ -224,8 +224,8 @@ return [
'pages_md_insert_image' => '画像を挿入',
'pages_md_insert_link' => 'エンティティへのリンクを挿入',
'pages_md_insert_drawing' => '描画を追加',
'pages_md_show_preview' => 'Show preview',
'pages_md_sync_scroll' => 'Sync preview scroll',
'pages_md_show_preview' => 'プレビューを表示',
'pages_md_sync_scroll' => 'プレビューとスクロールを同期',
'pages_not_in_chapter' => 'チャプターが設定されていません',
'pages_move' => 'ページを移動',
'pages_move_success' => 'ページを ":parentName" へ移動しました',
@@ -236,14 +236,14 @@ return [
'pages_permissions_success' => 'ページの権限を更新しました',
'pages_revision' => '編集履歴',
'pages_revisions' => '編集履歴',
'pages_revisions_desc' => 'Listed below are all the past revisions of this page. You can look back upon, compare, and restore old page versions if permissions allow. The full history of the page may not be fully reflected here since, depending on system configuration, old revisions could be auto-deleted.',
'pages_revisions_desc' => '以下はこのページの過去の全リビジョンです。権限があれば、古いバージョンのページの見返しや比較、復元ができます。システムの設定によっては、古いリビジョンが自動削除されることがあるため、このページの全履歴がここに反映されないことがあります。',
'pages_revisions_named' => ':pageName のリビジョン',
'pages_revision_named' => ':pageName のリビジョン',
'pages_revision_restored_from' => '#:id :summary から復元',
'pages_revisions_created_by' => '作成者',
'pages_revisions_date' => '日付',
'pages_revisions_number' => '#',
'pages_revisions_sort_number' => 'Revision Number',
'pages_revisions_sort_number' => 'リビジョン番号',
'pages_revisions_numbered' => 'リビジョン #:id',
'pages_revisions_numbered_changes' => 'リビジョン #:id の変更',
'pages_revisions_editor' => 'エディタの種類',
@@ -280,7 +280,7 @@ return [
'shelf_tags' => '本棚のタグ',
'tag' => 'タグ',
'tags' => 'タグ',
'tags_index_desc' => 'Tags can be applied to content within the system to apply a flexible form of categorization. Tags can have both a key and value, with the value being optional. Once applied, content can then be queried using the tag name and value.',
'tags_index_desc' => 'システム内のコンテンツにタグを適用して柔軟なカテゴリ分けを行うことができます。タグはキーと値の両方を持つことができ、値は任意です。タグを適用すると、タグの名前と値を使ってコンテンツを検索することができます。',
'tag_name' => 'タグの名前',
'tag_value' => '内容 (オプション)',
'tags_explain' => "タグを設定すると、コンテンツの管理が容易になります。\nより高度な管理をしたい場合、タグに内容を設定できます。",

View File

@@ -5,14 +5,14 @@
*/
return [
'shortcuts' => 'Shortcuts',
'shortcuts_interface' => 'Interface Keyboard Shortcuts',
'shortcuts_toggle_desc' => 'Here you can enable or disable keyboard system interface shortcuts, used for navigation and actions.',
'shortcuts_customize_desc' => 'You can customize each of the shortcuts below. Just press your desired key combination after selecting the input for a shortcut.',
'shortcuts_toggle_label' => 'Keyboard shortcuts enabled',
'shortcuts_section_navigation' => 'Navigation',
'shortcuts_section_actions' => 'Common Actions',
'shortcuts_save' => 'Save Shortcuts',
'shortcuts_overlay_desc' => 'Note: When shortcuts are enabled a helper overlay is available via pressing "?" which will highlight the available shortcuts for actions currently visible on the screen.',
'shortcuts_update_success' => 'Shortcut preferences have been updated!',
'shortcuts' => 'ショートカット',
'shortcuts_interface' => 'インターフェイスのキーボードショートカット',
'shortcuts_toggle_desc' => 'ここでは、ナビゲーションやアクションに使用されるキーボードシステムインターフェイスのショートカットを有効または無効にすることができます。',
'shortcuts_customize_desc' => '以下の各ショートカットをカスタマイズできます。ショートカットの入力を選択した後、希望のキーの組み合わせを押してください。',
'shortcuts_toggle_label' => 'キーボードショートカットを有効にする',
'shortcuts_section_navigation' => 'ナビゲーション',
'shortcuts_section_actions' => '共通のアクション',
'shortcuts_save' => 'ショートカットを保存',
'shortcuts_overlay_desc' => '注:ショートカットが有効な場合はヘルパーオーバーレイが利用できます。「?」を押すと現在画面に表示されているアクションで利用可能なショートカットをハイライト表示します。',
'shortcuts_update_success' => 'ショートカットの設定が更新されました!',
];

View File

@@ -133,9 +133,9 @@ return [
// Role Settings
'roles' => '役割',
'role_user_roles' => '役割',
'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.',
'roles_x_users_assigned' => '1 user assigned|:count users assigned',
'roles_x_permissions_provided' => '1 permission|:count permissions',
'roles_index_desc' => '役割は、ユーザーをグループ化しメンバーにシステム権限を与えるために使用されます。ユーザーが複数の役割のメンバーである場合、与えられた権限は積み重なり、ユーザーはすべての能力を継承します。',
'roles_x_users_assigned' => '1人のユーザーに割り当て|:count人のユーザーに割り当て',
'roles_x_permissions_provided' => '1件の権限|:count件の権限',
'roles_assigned_users' => 'Assigned Users',
'roles_permissions_provided' => 'Provided Permissions',
'role_create' => '役割を作成',
@@ -177,7 +177,7 @@ return [
// Users
'users' => 'ユーザー',
'users_index_desc' => 'Create & manage individual user accounts within the system. User accounts are used for login and attribution of content & activity. Access permissions are primarily role-based but user content ownership, among other factors, may also affect permissions & access.',
'users_index_desc' => 'システム内で個々のユーザーアカウントを作成し、管理します。ユーザーアカウントは、ログインおよびコンテンツとアクティビティの帰属のために使用されます。アクセス許可は主に役割ベースですが、ユーザーコンテンツの所有権やその他の要因も、許可とアクセスに影響する場合があります。',
'user_profile' => 'ユーザプロフィール',
'users_add_new' => 'ユーザーを追加',
'users_search' => 'ユーザー検索',
@@ -247,8 +247,8 @@ return [
// Webhooks
'webhooks' => 'Webhook',
'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.',
'webhooks_x_trigger_events' => '1 trigger event|:count trigger events',
'webhooks_index_desc' => 'Webhookは、システム内で特定のアクションやイベントが発生したときに外部URLにデータを送信する方法で、メッセージングシステムや通知システムなどの外部プラットフォームとのイベントベースの統合を可能にします。',
'webhooks_x_trigger_events' => '1個のトリガーイベント|:count個のトリガーイベント',
'webhooks_create' => 'Webhookを作成',
'webhooks_none_created' => 'Webhookはまだ作成されていません。',
'webhooks_edit' => 'Webhookを編集',

View File

@@ -61,8 +61,8 @@ return [
'email_confirm_send_error' => 'Wymagane jest potwierdzenie hasła, lecz wiadomość nie mogła zostać wysłana. Skontaktuj się z administratorem w celu upewnienia się, że skrzynka została skonfigurowana prawidłowo.',
'email_confirm_success' => 'Twój e-mail został potwierdzony! Powinieneś teraz mieć możliwość zalogowania się za pomocą tego adresu e-mail.',
'email_confirm_resent' => 'E-mail z potwierdzeniem został wysłany ponownie, sprawdź swoją skrzynkę odbiorczą.',
'email_confirm_thanks' => 'Thanks for confirming!',
'email_confirm_thanks_desc' => 'Please wait a moment while your confirmation is handled. If you are not redirected after 3 seconds press the "Continue" link below to proceed.',
'email_confirm_thanks' => 'Dzięki za potwierdzenie!',
'email_confirm_thanks_desc' => 'Poczekaj chwilę, Twoje potwierdzenie jest obsługiwane. Jeśli nie zostaniesz przekierowany po 3 sekundach, naciśnij poniższy link "Kontynuuj", aby kontynuować.',
'email_not_confirmed' => 'Adres e-mail nie został potwierdzony',
'email_not_confirmed_text' => 'Twój adres e-mail nie został jeszcze potwierdzony.',

View File

@@ -224,8 +224,8 @@ return [
'pages_md_insert_image' => 'Wstaw obrazek',
'pages_md_insert_link' => 'Wstaw łącze do obiektu',
'pages_md_insert_drawing' => 'Wstaw rysunek',
'pages_md_show_preview' => 'Show preview',
'pages_md_sync_scroll' => 'Sync preview scroll',
'pages_md_show_preview' => 'Pokaż podgląd',
'pages_md_sync_scroll' => 'Synchronizuj przewijanie podglądu',
'pages_not_in_chapter' => 'Strona nie została umieszczona w rozdziale',
'pages_move' => 'Przenieś stronę',
'pages_move_success' => 'Strona przeniesiona do ":parentName"',

View File

@@ -224,8 +224,8 @@ return [
'pages_md_insert_image' => 'Inserir Imagem',
'pages_md_insert_link' => 'Inserir Link para Entidade',
'pages_md_insert_drawing' => 'Inserir Desenho',
'pages_md_show_preview' => 'Show preview',
'pages_md_sync_scroll' => 'Sync preview scroll',
'pages_md_show_preview' => 'Mostrar pré-visualização',
'pages_md_sync_scroll' => 'Sincronizar pré-visualização',
'pages_not_in_chapter' => 'Página não está dentro de um capítulo',
'pages_move' => 'Mover Página',
'pages_move_success' => 'Pagina movida para ":parentName"',

View File

@@ -24,7 +24,7 @@ return [
'password_hint' => '必须至少有 8 个字符',
'forgot_password' => '忘记密码?',
'remember_me' => '记住我',
'ldap_email_hint' => '请输入用于此户的电子邮件。',
'ldap_email_hint' => '请输入用于此户的电子邮件。',
'create_account' => '创建账户',
'already_have_account' => '已经有账号了?',
'dont_have_account' => '您还没有账号吗?',
@@ -50,7 +50,7 @@ return [
'reset_password_sent' => '重置密码的链接将通过您的电子邮箱发送:email。',
'reset_password_success' => '您的密码已成功重置。',
'email_reset_subject' => '重置您的:appName密码',
'email_reset_text' => '您收到此电子邮件是因为我们收到了您的户的密码重置请求。',
'email_reset_text' => '您收到此电子邮件是因为我们收到了您的户的密码重置请求。',
'email_reset_not_requested' => '如果您没有要求重置密码,则不需要采取进一步的操作。',
// Email Confirmation
@@ -61,8 +61,8 @@ return [
'email_confirm_send_error' => '需要Email验证但系统无法发送电子邮件请联系网站管理员。',
'email_confirm_success' => '您已成功验证电子邮件地址!您现在可以使用此电子邮件地址登录。',
'email_confirm_resent' => '验证邮件已重新发送,请检查收件箱。',
'email_confirm_thanks' => 'Thanks for confirming!',
'email_confirm_thanks_desc' => 'Please wait a moment while your confirmation is handled. If you are not redirected after 3 seconds press the "Continue" link below to proceed.',
'email_confirm_thanks' => '感谢您的确认!',
'email_confirm_thanks_desc' => '请稍等您的确认正在处理。如果您在3秒后未被重定向请按下面的“继续“链接继续。',
'email_not_confirmed' => 'Email地址未验证',
'email_not_confirmed_text' => '您的电子邮件地址尚未确认。',
@@ -72,11 +72,11 @@ return [
// User Invite
'user_invite_email_subject' => '您已受邀加入 :appName',
'user_invite_email_greeting' => ' :appName 已为您创建了一个户。',
'user_invite_email_text' => '点击下面的按钮以设置户密码并获得访问权限:',
'user_invite_email_action' => '设置号密码',
'user_invite_email_greeting' => ':appName 已为您创建了一个户。',
'user_invite_email_text' => '点击下面的按钮以设置户密码并获得访问权限:',
'user_invite_email_action' => '设置号密码',
'user_invite_page_welcome' => '欢迎来到 :appName',
'user_invite_page_text' => '要完成您的户并获得访问权限,您需要设置一个密码,该密码将在以后访问时用于登录 :appName。',
'user_invite_page_text' => '要完成您的户并获得访问权限,您需要设置一个密码,该密码将在以后访问时用于登录 :appName。',
'user_invite_page_confirm_button' => '确认密码',
'user_invite_success_login' => '密码已设置,您现在可以使用您设置的密码登录 :appName!',
@@ -87,7 +87,7 @@ return [
'mfa_setup_reconfigure' => '重新配置',
'mfa_setup_remove_confirmation' => '您确定想要移除多重身份认证吗?',
'mfa_setup_action' => '设置',
'mfa_backup_codes_usage_limit_warning' => '您剩余的备用认证码少于 5 个,请在用完认证码之前生成并保存新的认证码,以防止您的户被锁定。',
'mfa_backup_codes_usage_limit_warning' => '您剩余的备用认证码少于 5 个,请在用完认证码之前生成并保存新的认证码,以防止您的户被锁定。',
'mfa_option_totp_title' => '移动设备 App',
'mfa_option_totp_desc' => '要使用多重身份认证功能,您需要一个支持 TOTP基于时间的一次性密码算法 的移动设备 App如谷歌身份验证器Google Authenticator、Authy 或微软身份验证器Microsoft Authenticator。',
'mfa_option_backup_codes_title' => '备用认证码',

View File

@@ -25,7 +25,7 @@ return [
'actions' => '操作',
'view' => '浏览',
'view_all' => '查看全部',
'new' => 'New',
'new' => '',
'create' => '创建',
'update' => '更新',
'edit' => '编辑',
@@ -81,14 +81,14 @@ return [
'none' => '无',
// Header
'homepage' => 'Homepage',
'homepage' => '主页',
'header_menu_expand' => '展开标头菜单',
'profile_menu' => '个人资料',
'view_profile' => '查看个人资料',
'edit_profile' => '编辑个人资料',
'dark_mode' => '夜间模式',
'light_mode' => '日间模式',
'global_search' => 'Global Search',
'global_search' => '全局搜索',
// Layout tabs
'tab_info' => '信息',

View File

@@ -144,11 +144,11 @@ return [
'url' => '网址',
'text_to_display' => '要显示的文本',
'title' => '标题',
'open_link' => 'Open link',
'open_link_in' => 'Open link in...',
'open_link' => '打开链接',
'open_link_in' => '打开链接于……',
'open_link_current' => '覆盖当前窗口',
'open_link_new' => '新建窗口',
'remove_link' => 'Remove link',
'remove_link' => '移除链接',
'insert_collapsible' => '插入可折叠块',
'collapsible_unwrap' => '展开',
'edit_label' => '编辑标签',

View File

@@ -50,7 +50,7 @@ return [
'permissions_role_everyone_else' => '其他所有人',
'permissions_role_everyone_else_desc' => '为所有未被特别覆盖的角色设置权限。',
'permissions_role_override' => '覆盖角色权限',
'permissions_inherit_defaults' => 'Inherit defaults',
'permissions_inherit_defaults' => '继承默认值',
// Search
'search_results' => '搜索结果',
@@ -224,8 +224,8 @@ return [
'pages_md_insert_image' => '插入图片',
'pages_md_insert_link' => '插入项目链接',
'pages_md_insert_drawing' => '插入图表',
'pages_md_show_preview' => 'Show preview',
'pages_md_sync_scroll' => 'Sync preview scroll',
'pages_md_show_preview' => '显示预览',
'pages_md_sync_scroll' => '同步预览滚动',
'pages_not_in_chapter' => '本页面不在某章节中',
'pages_move' => '移动页面',
'pages_move_success' => '页面已移动到「:parentName」',
@@ -236,14 +236,14 @@ return [
'pages_permissions_success' => '页面权限已更新',
'pages_revision' => '修订',
'pages_revisions' => '页面修订',
'pages_revisions_desc' => 'Listed below are all the past revisions of this page. You can look back upon, compare, and restore old page versions if permissions allow. The full history of the page may not be fully reflected here since, depending on system configuration, old revisions could be auto-deleted.',
'pages_revisions_desc' => '下面列出的是该页面的所有过去修订。如果权限允许,您可以回顾、比较和恢复旧的页面版本。页面的完整历史可能不会在这里完全反映出来,因为根据系统配置,旧的修订可能会被自动删除。',
'pages_revisions_named' => '“:pageName”页面修订',
'pages_revision_named' => '“:pageName”页面修订',
'pages_revision_restored_from' => '恢复到 #:id :summary',
'pages_revisions_created_by' => '创建者',
'pages_revisions_date' => '修订日期',
'pages_revisions_number' => '#',
'pages_revisions_sort_number' => 'Revision Number',
'pages_revisions_sort_number' => '修订号',
'pages_revisions_numbered' => '修订 #:id',
'pages_revisions_numbered_changes' => '修改 #:id ',
'pages_revisions_editor' => '编辑器类型',
@@ -280,7 +280,7 @@ return [
'shelf_tags' => '书架标签',
'tag' => '标签',
'tags' => '标签',
'tags_index_desc' => 'Tags can be applied to content within the system to apply a flexible form of categorization. Tags can have both a key and value, with the value being optional. Once applied, content can then be queried using the tag name and value.',
'tags_index_desc' => '标签是一种灵活的分类形式,可以应用于系统内的内容。标签可以有一个键和值,值是可选的。应用后就可以使用标签的名称和值来搜索内容。',
'tag_name' => '标签名称',
'tag_value' => '标签值 (可选)',
'tags_explain' => "添加一些标签以更好地对您的内容进行分类。\n您可以为标签分配一个值,以进行更好的进行管理。",

View File

@@ -30,14 +30,14 @@ return [
'social_no_action_defined' => '没有定义行为',
'social_login_bad_response' => "在 :socialAccount 登录时遇到错误:\n:error",
'social_account_in_use' => ':socialAccount 账户已被使用,请尝试通过 :socialAccount 选项登录。',
'social_account_email_in_use' => 'Email :email 已经被使用。如果您已有户,则可以在个人资料设置中绑定您的 :socialAccount。',
'social_account_email_in_use' => 'Email :email 已经被使用。如果您已有户,则可以在个人资料设置中绑定您的 :socialAccount。',
'social_account_existing' => ':socialAccount已经被绑定到您的账户。',
'social_account_already_used_existing' => ':socialAccount账户已经被其他用户使用。',
'social_account_not_used' => ':socialAccount账户没有绑定到任何用户请在您的个人资料设置中绑定。',
'social_account_register_instructions' => '如果您还没有户,您可以使用 :socialAccount 选项注册账户。',
'social_account_register_instructions' => '如果您还没有户,您可以使用 :socialAccount 选项注册账户。',
'social_driver_not_found' => '未找到社交驱动程序',
'social_driver_not_configured' => '您的:socialAccount社交设置不正确。',
'invite_token_expired' => '此邀请链接已过期。 您可以尝试重置您的户密码。',
'invite_token_expired' => '此邀请链接已过期。 您可以尝试重置您的户密码。',
// System
'path_not_writable' => '无法上传到文件路径“:filePath”请确保它可写入服务器。',

View File

@@ -5,14 +5,14 @@
*/
return [
'shortcuts' => 'Shortcuts',
'shortcuts_interface' => 'Interface Keyboard Shortcuts',
'shortcuts_toggle_desc' => 'Here you can enable or disable keyboard system interface shortcuts, used for navigation and actions.',
'shortcuts_customize_desc' => 'You can customize each of the shortcuts below. Just press your desired key combination after selecting the input for a shortcut.',
'shortcuts_toggle_label' => 'Keyboard shortcuts enabled',
'shortcuts_section_navigation' => 'Navigation',
'shortcuts_section_actions' => 'Common Actions',
'shortcuts_save' => 'Save Shortcuts',
'shortcuts_overlay_desc' => 'Note: When shortcuts are enabled a helper overlay is available via pressing "?" which will highlight the available shortcuts for actions currently visible on the screen.',
'shortcuts_update_success' => 'Shortcut preferences have been updated!',
'shortcuts' => '快捷键',
'shortcuts_interface' => '界面键盘快捷键',
'shortcuts_toggle_desc' => '你可以启用或禁用键盘系统界面快捷键,这些快捷键用于导航和操作。',
'shortcuts_customize_desc' => '您可以自定义下面的每个快捷键。选择快捷方式输入后按下您想使用的按键组合即可。',
'shortcuts_toggle_label' => '启用键盘快捷键',
'shortcuts_section_navigation' => '导航',
'shortcuts_section_actions' => '通用操作',
'shortcuts_save' => '保存快捷键',
'shortcuts_overlay_desc' => '注意:当快捷键启用时,可以按"?"键来打开帮助,它将突出显示当前屏幕上可见操作的快捷键。',
'shortcuts_update_success' => '快捷键设置已更新!',
];

View File

@@ -133,11 +133,11 @@ return [
// Role Settings
'roles' => '角色',
'role_user_roles' => '用户角色',
'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.',
'roles_x_users_assigned' => '1 user assigned|:count users assigned',
'roles_x_permissions_provided' => '1 permission|:count permissions',
'roles_assigned_users' => 'Assigned Users',
'roles_permissions_provided' => 'Provided Permissions',
'roles_index_desc' => '角色用于对用户进行分组并为其成员提供系统权限。当一个用户是多个角色的成员时,授予的权限将叠加,用户将继承所有角色的能力。',
'roles_x_users_assigned' => '1 位用户已分配|:count 位用户已分配',
'roles_x_permissions_provided' => '1 个权限|:count 个权限',
'roles_assigned_users' => '已分配用户',
'roles_permissions_provided' => '已提供权限',
'role_create' => '创建角色',
'role_create_success' => '角色创建成功',
'role_delete' => '删除角色',
@@ -177,7 +177,7 @@ return [
// Users
'users' => '用户',
'users_index_desc' => 'Create & manage individual user accounts within the system. User accounts are used for login and attribution of content & activity. Access permissions are primarily role-based but user content ownership, among other factors, may also affect permissions & access.',
'users_index_desc' => '在系统内创建和管理个人用户账户。用户账户用于登录和内容及活动的归属。访问权限主要是基于角色的,但用户的内容所有权以及其他因素,也可能影响到权限和访问。',
'user_profile' => '用户资料',
'users_add_new' => '添加用户',
'users_search' => '搜索用户',
@@ -209,7 +209,7 @@ return [
'users_preferred_language' => '语言',
'users_preferred_language_desc' => '此选项将更改用于应用程序用户界面的语言。 这不会影响任何用户创建的内容。',
'users_social_accounts' => '社交账户',
'users_social_accounts_info' => '在这里,您可以绑定您的其他以便更快更轻松地登录。如果您选择解除绑定之后将不能通过此社交账户登录请设置社交账户来取消本App的访问权限。',
'users_social_accounts_info' => '在这里,您可以绑定您的其他以便更快更轻松地登录。如果您选择解除绑定之后将不能通过此社交账户登录请设置社交账户来取消本App的访问权限。',
'users_social_connect' => '绑定账户',
'users_social_disconnect' => '解除绑定账户',
'users_social_connected' => ':socialAccount 账户已经成功绑定到您的资料。',
@@ -247,8 +247,8 @@ return [
// Webhooks
'webhooks' => 'Webhooks',
'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.',
'webhooks_x_trigger_events' => '1 trigger event|:count trigger events',
'webhooks_index_desc' => 'Webhook 是一种在系统内发生某些操作和事件时将数据发送到外部 URL 的方法,它允许与外部平台(例如消息传递或通知系统)进行基于事件的集成。',
'webhooks_x_trigger_events' => '1 个触发事件 |:count 个触发事件',
'webhooks_create' => '新建 Webhook',
'webhooks_none_created' => '尚未创建任何 Webhook。',
'webhooks_edit' => '编辑 Webhook',

View File

@@ -318,14 +318,6 @@ body.tox-fullscreen, body.markdown-fullscreen {
@include lightDark(color, #444, #EEE);
background-color: rgba(0, 0, 0, 0.1);
}
div[toolbox-tab-content] {
padding-bottom: 45px;
display: flex;
flex: 1;
flex-direction: column;
min-height: 0;
overflow-y: scroll;
}
h4 {
font-size: 24px;
margin: $-m 0 0 0;
@@ -359,6 +351,8 @@ body.tox-fullscreen, body.markdown-fullscreen {
.toolbox-tab-content {
display: none;
overflow-y: auto;
padding-bottom: 45px;
}
.suggestion-box {

View File

@@ -1,8 +1,5 @@
{{--
$modelType - The type of permission model; String matching one of: user, role, fallback
$modelId - The ID of the permission model.
$modelName - The name of the permission model.
$modelDescription - The description of the permission model.
$role - The Role to display this row for.
$entityType - String identifier for type of entity having permissions applied.
$permission - The entity permission containing the permissions.
$inheriting - Boolean if the current row should be marked as inheriting default permissions. Used for "Everyone Else" role.
@@ -10,21 +7,21 @@ $inheriting - Boolean if the current row should be marked as inheriting default
<div component="permissions-table" class="item-list-row flex-container-row justify-space-between wrap">
<div class="gap-x-m flex-container-row items-center px-l py-m flex">
<div class="text-large" title="{{ $modelType === 'fallback' ? trans('entities.permissions_role_everyone_else') : ($modelType === 'role' ? trans('common.role') : trans('common.user')) }}">
@icon($modelType === 'fallback' ? 'groups' : ($modelType === 'role' ? 'role' : 'user'))
<div class="text-large" title="{{ $role->id === 0 ? trans('entities.permissions_role_everyone_else') : trans('common.role') }}">
@icon($role->id === 0 ? 'groups' : 'role')
</div>
<span>
<strong>{{ $modelName }}</strong> <br>
<small class="text-muted">{{ $modelDescription }}</small>
<strong>{{ $role->display_name }}</strong> <br>
<small class="text-muted">{{ $role->description }}</small>
</span>
@if($modelType !== 'fallback')
@if($role->id !== 0)
<button type="button"
class="ml-auto flex-none text-small text-primary text-button hover-underline item-list-row-toggle-all hide-under-s"
refs="permissions-table@toggle-all"
><strong>{{ trans('common.toggle_all') }}</strong></button>
@endif
</div>
@if($modelType === 'fallback')
@if($role->id === 0)
<div class="px-l flex-container-row items-center" refs="entity-permissions@everyone-inherit">
@include('form.custom-checkbox', [
'name' => 'entity-permissions-inherit',
@@ -35,12 +32,12 @@ $inheriting - Boolean if the current row should be marked as inheriting default
</div>
@endif
<div class="flex-container-row justify-space-between gap-x-xl wrap items-center">
<input type="hidden" name="permissions[{{ $modelType }}][{{ $modelId }}][active]"
<input type="hidden" name="permissions[{{ $role->id }}][active]"
@if($inheriting) disabled="disabled" @endif
value="true">
<div class="px-l">
@include('form.custom-checkbox', [
'name' => 'permissions[' . $modelType . '][' . $modelId . '][view]',
'name' => 'permissions[' . $role->id . '][view]',
'label' => trans('common.view'),
'value' => 'true',
'checked' => $permission->view,
@@ -50,7 +47,7 @@ $inheriting - Boolean if the current row should be marked as inheriting default
@if($entityType !== 'page')
<div class="px-l">
@include('form.custom-checkbox', [
'name' => 'permissions[' . $modelType . '][' . $modelId . '][create]',
'name' => 'permissions[' . $role->id . '][create]',
'label' => trans('common.create'),
'value' => 'true',
'checked' => $permission->create,
@@ -60,7 +57,7 @@ $inheriting - Boolean if the current row should be marked as inheriting default
@endif
<div class="px-l">
@include('form.custom-checkbox', [
'name' => 'permissions[' . $modelType . '][' . $modelId . '][update]',
'name' => 'permissions[' . $role->id . '][update]',
'label' => trans('common.update'),
'value' => 'true',
'checked' => $permission->update,
@@ -69,7 +66,7 @@ $inheriting - Boolean if the current row should be marked as inheriting default
</div>
<div class="px-l">
@include('form.custom-checkbox', [
'name' => 'permissions[' . $modelType . '][' . $modelId . '][delete]',
'name' => 'permissions[' . $role->id . '][delete]',
'label' => trans('common.delete'),
'value' => 'true',
'checked' => $permission->delete,
@@ -77,13 +74,12 @@ $inheriting - Boolean if the current row should be marked as inheriting default
])
</div>
</div>
@if($modelType !== 'fallback')
@if($role->id !== 0)
<div class="flex-container-row items-center px-m py-s">
<button type="button"
class="text-neg p-m icon-button"
data-model-type="{{ $modelType }}"
data-model-id="{{ $modelId }}"
data-model-name="{{ $modelName }}"
data-role-id="{{ $role->id }}"
data-role-name="{{ $role->display_name }}"
title="{{ trans('common.remove') }}">
@icon('close') <span class="hide-over-m ml-xs">{{ trans('common.remove') }}</span>
</button>

View File

@@ -35,35 +35,11 @@
<hr>
<div refs="entity-permissions@user-container" class="item-list mt-m mb-m">
@foreach($data->permissionsWithUsers() as $permission)
@include('form.entity-permissions-row', [
'permission' => $permission,
'modelType' => 'user',
'modelId' => $permission->user->id,
'modelName' => $permission->user->name,
'modelDescription' => '',
'entityType' => $model->getType(),
'inheriting' => false,
])
@endforeach
</div>
<div class="flex-container-row justify-flex-end mb-xl">
<div refs="entity-permissions@user-select-container" class="flex-container-row items-center gap-m">
<label for="user_select" class="m-none p-none"><span class="bold">{{ trans('entities.permissions_user_override') }}</span></label>
@include('form.user-select', ['name' => 'user_select', 'user' => null])
</div>
</div>
<div refs="entity-permissions@role-container" class="item-list mt-m mb-m">
@foreach($data->permissionsWithRoles() as $permission)
@include('form.entity-permissions-row', [
'permission' => $permission,
'modelType' => 'role',
'modelId' => $permission->role->id,
'modelName' => $permission->role->display_name,
'modelDescription' => $permission->role->description,
'role' => $permission->role,
'entityType' => $model->getType(),
'inheriting' => false,
])
@@ -84,13 +60,10 @@
<div class="item-list mt-m mb-xl">
@include('form.entity-permissions-row', [
'modelType' => 'fallback',
'modelId' => 0,
'modelName' => trans('entities.permissions_role_everyone_else'),
'modelDescription' => trans('entities.permissions_role_everyone_else_desc'),
'role' => $data->everyoneElseRole(),
'permission' => $data->everyoneElseEntityPermission(),
'entityType' => $model->getType(),
'inheriting' => $data->everyoneElseInheriting(),
'inheriting' => !$model->permissions()->where('role_id', '=', 0)->exists(),
])
</div>

View File

@@ -1,5 +1,6 @@
<div class="toggle-switch-list dual-column-content">
<input type="hidden" name="{{ $name }}[0]" value="0">
@foreach($roles as $role)
<div>
@include('form.custom-checkbox', [

View File

@@ -25,8 +25,8 @@
$languages = [
'Bash', 'CSS', 'C', 'C++', 'C#', 'Dart', 'Diff', 'Fortran', 'F#', 'Go', 'Haskell', 'HTML', 'INI',
'Java', 'JavaScript', 'JSON', 'Julia', 'Kotlin', 'LaTeX', 'Lua', 'MarkDown', 'MATLAB', 'Nginx', 'OCaml',
'Octave', 'Pascal', 'Perl', 'PHP', 'Powershell', 'Python', 'Ruby', 'Rust', 'Shell', 'SQL', 'Swift',
'TypeScript', 'VBScript', 'VB.NET', 'XML', 'YAML',
'Octave', 'Pascal', 'Perl', 'PHP', 'Powershell', 'Python', 'Ruby', 'Rust', 'Shell', 'Smarty', 'SQL', 'Swift',
'Twig', 'TypeScript', 'VBScript', 'VB.NET', 'XML', 'YAML',
];
@endphp

View File

@@ -217,8 +217,7 @@ Route::middleware('auth')->group(function () {
Route::get('/home', [HomeController::class, 'index']);
// Permissions
Route::get('/permissions/role-form-row/{entityType}/{roleId}', [PermissionsController::class, 'formRowForRole']);
Route::get('/permissions/user-form-row/{entityType}/{userId}', [PermissionsController::class, 'formRowForUser']);
Route::get('/permissions/form-row/{entityType}/{roleId}', [PermissionsController::class, 'formRowForRole']);
// Maintenance
Route::get('/settings/maintenance', [MaintenanceController::class, 'index']);

View File

@@ -23,17 +23,17 @@ class AuditLogTest extends TestCase
public function test_only_accessible_with_right_permissions()
{
$viewer = $this->users->viewer();
$viewer = $this->getViewer();
$this->actingAs($viewer);
$resp = $this->get('/settings/audit');
$this->assertPermissionError($resp);
$this->permissions->grantUserRolePermissions($viewer, ['settings-manage']);
$this->giveUserPermissions($viewer, ['settings-manage']);
$resp = $this->get('/settings/audit');
$this->assertPermissionError($resp);
$this->permissions->grantUserRolePermissions($viewer, ['users-manage']);
$this->giveUserPermissions($viewer, ['users-manage']);
$resp = $this->get('/settings/audit');
$resp->assertStatus(200);
$resp->assertSeeText('Audit Log');
@@ -41,7 +41,7 @@ class AuditLogTest extends TestCase
public function test_shows_activity()
{
$admin = $this->users->admin();
$admin = $this->getAdmin();
$this->actingAs($admin);
$page = $this->entities->page();
$this->activityService->add(ActivityType::PAGE_CREATE, $page);
@@ -56,7 +56,7 @@ class AuditLogTest extends TestCase
public function test_shows_name_for_deleted_items()
{
$this->actingAs($this->users->admin());
$this->actingAs($this->getAdmin());
$page = $this->entities->page();
$pageName = $page->name;
$this->activityService->add(ActivityType::PAGE_CREATE, $page);
@@ -71,12 +71,12 @@ class AuditLogTest extends TestCase
public function test_shows_activity_for_deleted_users()
{
$viewer = $this->users->viewer();
$viewer = $this->getViewer();
$this->actingAs($viewer);
$page = $this->entities->page();
$this->activityService->add(ActivityType::PAGE_CREATE, $page);
$this->actingAs($this->users->admin());
$this->actingAs($this->getAdmin());
app(UserRepo::class)->destroy($viewer);
$resp = $this->get('settings/audit');
@@ -85,7 +85,7 @@ class AuditLogTest extends TestCase
public function test_filters_by_key()
{
$this->actingAs($this->users->admin());
$this->actingAs($this->getAdmin());
$page = $this->entities->page();
$this->activityService->add(ActivityType::PAGE_CREATE, $page);
@@ -98,7 +98,7 @@ class AuditLogTest extends TestCase
public function test_date_filters()
{
$this->actingAs($this->users->admin());
$this->actingAs($this->getAdmin());
$page = $this->entities->page();
$this->activityService->add(ActivityType::PAGE_CREATE, $page);
@@ -120,8 +120,8 @@ class AuditLogTest extends TestCase
public function test_user_filter()
{
$admin = $this->users->admin();
$editor = $this->users->editor();
$admin = $this->getAdmin();
$editor = $this->getEditor();
$this->actingAs($admin);
$page = $this->entities->page();
$this->activityService->add(ActivityType::PAGE_CREATE, $page);
@@ -142,7 +142,7 @@ class AuditLogTest extends TestCase
public function test_ip_address_logged_and_visible()
{
config()->set('app.proxies', '*');
$editor = $this->users->editor();
$editor = $this->getEditor();
$page = $this->entities->page();
$this->actingAs($editor)->put($page->getUrl(), [
@@ -166,7 +166,7 @@ class AuditLogTest extends TestCase
public function test_ip_address_is_searchable()
{
config()->set('app.proxies', '*');
$editor = $this->users->editor();
$editor = $this->getEditor();
$page = $this->entities->page();
$this->actingAs($editor)->put($page->getUrl(), [
@@ -192,7 +192,7 @@ class AuditLogTest extends TestCase
{
config()->set('app.proxies', '*');
config()->set('app.env', 'demo');
$editor = $this->users->editor();
$editor = $this->getEditor();
$page = $this->entities->page();
$this->actingAs($editor)->put($page->getUrl(), [
@@ -215,7 +215,7 @@ class AuditLogTest extends TestCase
{
config()->set('app.proxies', '*');
config()->set('app.ip_address_precision', 2);
$editor = $this->users->editor();
$editor = $this->getEditor();
$page = $this->entities->page();
$this->actingAs($editor)->put($page->getUrl(), [

View File

@@ -88,7 +88,7 @@ class WebhookCallTest extends TestCase
]);
$webhook = $this->newWebhook(['active' => true, 'endpoint' => 'https://wh.example.com'], ['all']);
$page = $this->entities->page();
$editor = $this->users->editor();
$editor = $this->getEditor();
$this->runEvent(ActivityType::PAGE_UPDATE, $page, $editor);
@@ -111,7 +111,7 @@ class WebhookCallTest extends TestCase
protected function runEvent(string $event, $detail = '', ?User $user = null)
{
if (is_null($user)) {
$user = $this->users->editor();
$user = $this->getEditor();
}
$this->actingAs($user);

View File

@@ -41,7 +41,7 @@ class WebhookFormatTesting extends TestCase
protected function getWebhookData(string $event, $detail): array
{
$webhook = Webhook::factory()->make();
$user = $this->users->editor();
$user = $this->getEditor();
$formatter = WebhookFormatter::getDefault($event, $webhook, $detail, $user, time());
return $formatter->format();

View File

@@ -135,7 +135,7 @@ class WebhookManagementTest extends TestCase
public function test_settings_manage_permission_required_for_webhook_routes()
{
$editor = $this->users->editor();
$editor = $this->getEditor();
$this->actingAs($editor);
$routes = [
@@ -153,7 +153,7 @@ class WebhookManagementTest extends TestCase
$this->assertPermissionError($resp);
}
$this->permissions->grantUserRolePermissions($editor, ['settings-manage']);
$this->giveUserPermissions($editor, ['settings-manage']);
foreach ($routes as [$method, $endpoint]) {
$resp = $this->call($method, $endpoint);

View File

@@ -16,8 +16,8 @@ class ApiAuthTest extends TestCase
public function test_requests_succeed_with_default_auth()
{
$viewer = $this->users->viewer();
$this->permissions->grantUserRolePermissions($viewer, ['access-api']);
$viewer = $this->getViewer();
$this->giveUserPermissions($viewer, ['access-api']);
$resp = $this->get($this->endpoint);
$resp->assertStatus(401);
@@ -63,7 +63,7 @@ class ApiAuthTest extends TestCase
auth()->logout();
$accessApiPermission = RolePermission::getByName('access-api');
$editorRole = $this->users->editor()->roles()->first();
$editorRole = $this->getEditor()->roles()->first();
$editorRole->detachPermission($accessApiPermission);
$resp = $this->get($this->endpoint, $this->apiAuthHeader());
@@ -73,7 +73,7 @@ class ApiAuthTest extends TestCase
public function test_api_access_permission_required_to_access_api_with_session_auth()
{
$editor = $this->users->editor();
$editor = $this->getEditor();
$this->actingAs($editor, 'standard');
$resp = $this->get($this->endpoint);
@@ -81,7 +81,7 @@ class ApiAuthTest extends TestCase
auth('standard')->logout();
$accessApiPermission = RolePermission::getByName('access-api');
$editorRole = $this->users->editor()->roles()->first();
$editorRole = $this->getEditor()->roles()->first();
$editorRole->detachPermission($accessApiPermission);
$editor = User::query()->where('id', '=', $editor->id)->first();
@@ -114,7 +114,7 @@ class ApiAuthTest extends TestCase
public function test_token_expiry_checked()
{
$editor = $this->users->editor();
$editor = $this->getEditor();
$token = $editor->apiTokens()->first();
$resp = $this->get($this->endpoint, $this->apiAuthHeader());
@@ -130,7 +130,7 @@ class ApiAuthTest extends TestCase
public function test_email_confirmation_checked_using_api_auth()
{
$editor = $this->users->editor();
$editor = $this->getEditor();
$editor->email_confirmed = false;
$editor->save();

View File

@@ -50,7 +50,7 @@ class AttachmentsApiTest extends TestCase
],
]]);
$this->permissions->setEntityPermissions($page, [], []);
$this->entities->setPermissions($page, [], []);
$resp = $this->getJson($this->baseEndpoint . '?count=1&sort=+id');
$resp->assertJsonMissing(['data' => [
@@ -246,13 +246,13 @@ class AttachmentsApiTest extends TestCase
public function test_attachment_not_visible_on_other_users_draft()
{
$this->actingAsApiAdmin();
$editor = $this->users->editor();
$editor = $this->getEditor();
$page = $this->entities->page();
$page->draft = true;
$page->owned_by = $editor->id;
$page->save();
$this->permissions->regenerateForEntity($page);
$this->entities->regenPermissions($page);
$attachment = $this->createAttachmentForPage($page, [
'name' => 'my attachment',
@@ -342,7 +342,7 @@ class AttachmentsApiTest extends TestCase
protected function createAttachmentForPage(Page $page, $attributes = []): Attachment
{
$admin = $this->users->admin();
$admin = $this->getAdmin();
/** @var Attachment $attachment */
$attachment = $page->attachments()->forceCreate(array_merge([
'uploaded_to' => $page->id,

View File

@@ -246,7 +246,7 @@ class BooksApiTest extends TestCase
{
$types = ['html', 'plaintext', 'pdf', 'markdown'];
$this->actingAsApiEditor();
$this->permissions->removeUserRolePermissions($this->users->editor(), ['content-export']);
$this->removePermissionFromUser($this->getEditor(), 'content-export');
$book = $this->entities->book();
foreach ($types as $type) {

View File

@@ -221,7 +221,7 @@ class ChaptersApiTest extends TestCase
{
$types = ['html', 'plaintext', 'pdf', 'markdown'];
$this->actingAsApiEditor();
$this->permissions->removeUserRolePermissions($this->users->editor(), ['content-export']);
$this->removePermissionFromUser($this->getEditor(), 'content-export');
$chapter = Chapter::visible()->has('pages')->first();
foreach ($types as $type) {

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