Compare commits

...

200 Commits

Author SHA1 Message Date
blizzz 20ea9a2535 Merge pull request #34627 from nextcloud/release/25.0.0
25.0.0
2022-10-18 10:06:07 +02:00
Arthur Schiwon 6827b6c232 25.0.0
Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
2022-10-17 09:55:52 +02:00
blizzz 0368c65e05 Merge pull request #34603 from nextcloud/release/25.0.0_rc5
25.0.0 RC5
2022-10-14 18:03:03 +02:00
Vincent Petry e487d822ee Merge pull request #34612 from nextcloud/stable25-fix-background-appdata-scrope
[stable25] Scope the appdata theming storage for global and users
2022-10-14 18:01:02 +02:00
Vincent Petry b99e478840 Merge pull request #34608 from nextcloud/backport/34569/stable25
[stable25] Filter out backup user status (those beginning with _ as userId)
2022-10-14 17:04:22 +02:00
Vincent Petry d094884057 Improve cache buster for user backgrounds
Signed-off-by: Vincent Petry <vincent@nextcloud.com>
2022-10-14 16:19:44 +02:00
John Molakvoæ 3b076350d1 Fix tests
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
2022-10-14 16:19:41 +02:00
John Molakvoæ 98a95fb986 Fix migration
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
2022-10-14 16:19:38 +02:00
John Molakvoæ 6690df39dc Properly use user background cachebuster
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
2022-10-14 16:19:34 +02:00
John Molakvoæ f4a14cfe69 Scope the appdata theming storage for global and users
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
2022-10-14 16:19:32 +02:00
Carl Schwan 25efb43ac4 Properly escape underscore in db query
Co-authored-by: Joas Schilling <213943+nickvergessen@users.noreply.github.com>
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
2022-10-14 12:59:03 +00:00
Carl Schwan 446bb96ba8 Do the filtering on the DB instead
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
2022-10-14 12:59:03 +00:00
tobiasKaminsky 726d857690 Filter out backup user status (those beginning with _ as userId) 2022-10-14 12:59:03 +00:00
Arthur Schiwon 373ba3ecee 25.0.0 RC5
Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
2022-10-14 12:15:31 +02:00
blizzz 684bd274b8 Merge pull request #34468 from nextcloud/release/25.0.0_rc4
25.0.0 RC4
2022-10-13 20:51:47 +02:00
Pytal 7f498d0fa0 Merge pull request #34587 from nextcloud/backport/34461/stable25
[stable25] Fix missing background on upgrade
2022-10-13 11:26:44 -07:00
John Molakvoæ 7c4a5b0dd1 Merge pull request #34588 from nextcloud/stable25-theming-default-system-value
[stable25]  Use default system primary
2022-10-13 20:16:56 +02:00
John Molakvoæ 08227ca28d Adjust testing
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2022-10-13 18:04:01 +02:00
Arthur Schiwon 1309459fee graceful background image handling
- fallback to background image from old location
- migrate background images to new location as insensitive job

Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
2022-10-13 18:01:48 +02:00
Joas Schilling 37cca49630 Fix migration parameter handling
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-10-13 18:01:43 +02:00
Arthur Schiwon 982534349a fix querybuilder instance recuse
...and execute delete query

Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
2022-10-13 18:01:38 +02:00
Christopher Ng 61d2937bf4 Delete theming background preferences
Signed-off-by: Christopher Ng <chrng8@gmail.com>
2022-10-13 18:01:34 +02:00
Joas Schilling 5c7613fd67 New code being new
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-10-13 18:01:28 +02:00
Joas Schilling 10cf013e19 Fix SQL errors
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-10-13 18:01:15 +02:00
Christopher Ng df218bc4db Fix missing background on upgrade
Signed-off-by: Christopher Ng <chrng8@gmail.com>
2022-10-13 18:01:05 +02:00
John Molakvoæ f17f24ffad Fix primary and debounce to avoid infinite loop
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
2022-10-13 18:00:01 +02:00
John Molakvoæ (skjnldsv) b3832bcd05 Allow to pick custom colour
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
2022-10-13 17:59:57 +02:00
John Molakvoæ (skjnldsv) a7211e6ab0 Use default system primary
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
2022-10-13 17:59:48 +02:00
Simon L faab6c8072 Merge pull request #34573 from nextcloud/backport/34571/stable25
[stable25] Fix avatar menu icons in darkmode
2022-10-13 14:28:54 +02:00
Julius Härtl ae6a8d5cf5 Merge pull request #34568 from nextcloud/backport/34544/stable25
[stable25] More flexible date validation
2022-10-13 11:47:33 +02:00
Joas Schilling 6c47b26102 Fix avatar menu icons in darkmode
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-10-13 09:24:04 +00:00
blizzz 2a42dd4f4e Merge pull request #34564 from nextcloud/backport/34559/stable25
[stable25] Require token for local editing
2022-10-13 10:07:05 +02:00
Vincent Petry 1fb7567294 Merge pull request #34566 from nextcloud/backport/34558/stable25
[stable25] fix reading updater token for web updater
2022-10-13 09:21:26 +02:00
Simon L 1360771be8 Merge pull request #34567 from nextcloud/backport/34545/stable25
[stable25] Disable translucency on high contrast themes
2022-10-13 08:02:23 +02:00
Joas Schilling 7da871437b Bump version
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-10-13 07:45:15 +02:00
Carl Schwan 1dd634c0e6 More flexible date validation
Fix #34542

Signed-off-by: Carl Schwan <carl@carlschwan.eu>
2022-10-12 23:14:24 +00:00
szaimen 5e2c5e5fb6 fix color-main-background-rgb
Signed-off-by: szaimen <szaimen@e.mail.de>
2022-10-12 23:02:01 +00:00
Carl Schwan c2510a481e Disable translucency on high contrast themes
Fix #34514

Signed-off-by: Carl Schwan <carl@carlschwan.eu>
2022-10-12 23:02:01 +00:00
blizzz 39938297ee Merge pull request #34459 from nextcloud/backport/31947/stable25
[stable25] Fix Error: Undefined index: redirect_url
2022-10-12 23:02:20 +02:00
Arthur Schiwon 267f3896ae fix reading updater token for web updater
the previously called endpoint returns exactly one value, no JSON
structure

Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
2022-10-12 20:34:22 +00:00
Joas Schilling be005877ff Also throttle on expiration
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-10-12 17:52:13 +00:00
Joas Schilling c48b15fb70 Fix psalm error on migration
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-10-12 17:52:13 +00:00
Joas Schilling ec230f9290 Add a background job to delete expired tokens
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-10-12 17:52:13 +00:00
Joas Schilling 1db6d96e94 Also remove token when we successfully validated
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-10-12 17:52:13 +00:00
Joas Schilling 1d02efdd8b Require a token for "Edit locally"
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-10-12 17:52:13 +00:00
Vincent Petry 8e89c281a5 Merge pull request #34551 from nextcloud/backport/34537/stable25
[stable25] Avoid allocating too much memory for the buffer on s3 uploads
2022-10-12 14:49:35 +02:00
Vincent Petry ad0405a18b Merge pull request #34547 from nextcloud/backport/34299/stable25
[stable25] Add color variables for text on blurred background
2022-10-12 12:49:16 +02:00
Julius Härtl 775e4e023d Avoid allocating too much memory for the buffer
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-10-12 08:30:58 +00:00
Simon L be7b7f5d13 Merge pull request #34543 from nextcloud/backport/34473/stable25
[stable25] Fix excessive increase of cachebuster value
2022-10-12 07:54:14 +02:00
Christopher Ng 8caffb784f Add color variables for text on blurred background
Signed-off-by: Christopher Ng <chrng8@gmail.com>
2022-10-12 01:50:17 +00:00
blizzz ce7bfc6b33 Merge pull request #34531 from nextcloud/backport/34500/stable25
[stable25] Fix password length limitation
2022-10-11 23:31:44 +02:00
blizzz c98334fdac Merge pull request #34446 from nextcloud/backport/34401/stable25
[stable25] File reference: fallback icon to file type if no preview
2022-10-11 23:30:37 +02:00
Christopher Ng f477a22404 Fix excessive increase of cachebuster value
Signed-off-by: Christopher Ng <chrng8@gmail.com>
2022-10-11 19:13:55 +00:00
Joas Schilling cecd778c2b Fix password length limitation
Signed-off-by: Joas Schilling <coding@schilljs.com>
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2022-10-11 16:55:20 +00:00
Simon L b9b201cc9a Merge pull request #34524 from nextcloud/backport/34513/stable25
[stable25] do not show editlocally on mobile
2022-10-11 16:19:33 +02:00
Joas Schilling b885e44dde Merge pull request #34526 from nextcloud/backport/34522/stable25
[stable25] Update security.txt expires field
2022-10-11 15:06:58 +02:00
Joas Schilling 4beffc4209 Merge pull request #34525 from nextcloud/backport/34517/stable25
[stable25] Update ca-cert bundle
2022-10-11 12:52:42 +02:00
Joas Schilling 4cbfeec38e Update security.txt expires field
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-10-11 08:07:39 +00:00
nextcloud-command 66b1cf3a19 Update CA certificate bundle
Signed-off-by: GitHub <noreply@github.com>
2022-10-11 08:07:37 +00:00
szaimen 873ba714ae do not show editlocally on mobile
Signed-off-by: szaimen <szaimen@e.mail.de>
2022-10-11 08:04:49 +00:00
blizzz 9068bf5788 Merge pull request #34505 from nextcloud/backport/30608/stable25
[stable25] Harden disk_free_space check in CheckSetupController
2022-10-10 23:28:46 +02:00
blizzz 0644eab947 Merge pull request #34453 from nextcloud/backport/33737/stable25
[stable25] Makes untrusted domain error on info
2022-10-10 23:28:01 +02:00
Simon L 116897fc90 Merge pull request #34494 from nextcloud/backport/33915/stable25
[stable25] Replace vue2-datepicker with native date picker for share expiration dates
2022-10-10 22:39:10 +02:00
acsfer 061833b83d Harden some PHP functions
To avoid things like https://github.com/nextcloud/server/issues/26034

Signed-off-by: Louis Chemineau <louis@chmn.me>
2022-10-10 18:44:52 +00:00
julia.kirschenheuter e50f95eab6 Replace moment.js date with Date Object.
Replace vue2-datepicker with native date picker for expiration date.

Signed-off-by: julia.kirschenheuter <julia.kirschenheuter@nextcloud.com>
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2022-10-10 07:37:15 +00:00
Arthur Schiwon 8fcb1c1d76 25.0.0 RC4
Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
2022-10-07 12:44:57 +02:00
Andy Xheli 263e3e829e Fix Error: Undefined index: redirect_url
Signed-off-by: Andy Xheli <axheli@axtsolutions.com>

Should fix Undefined index: redirect_url at core/templates/twofactorsetupselection.php #21968
2022-10-06 21:57:24 +00:00
Andy Xheli 1921685e4e Makes untrusted domain error on info
Signed-off-by: Andy Xheli <axheli@axtsolutions.com>

Since https://github.com/nextcloud/server/commit/e6d9ef2e38daffcab808eaa41b18ab16c6253b97 was applied logs get filled up with Trusted domain error. "X.X.X.X tried to access using "X.X.X.X" as host alot of users missed important errors do tohttps://github.com/nextcloud/server/commit/e6d9ef2e38daffcab808eaa41b18ab16c6253b97   please see https://github.com/nextcloud/server/issues/32599

This should fix. 
https://github.com/nextcloud/server/issues/32599#event-7281164903


Signed-off-by: Andy Xheli <axheli@axtsolutions.com>
2022-10-06 16:28:30 +00:00
blizzz 402143d4e8 Merge pull request #34394 from nextcloud/release/25.0.0_rc3
25.0.0 RC3
2022-10-06 14:11:40 +02:00
Julien Veyssier 4807c23e09 File reference: use the file type icon as fallback when no preview provider for this type
Signed-off-by: Julien Veyssier <eneiluj@posteo.net>
2022-10-06 04:33:51 +00:00
Simon L 0c6b5cde95 Merge pull request #34432 from nextcloud/backport/34298/stable25
[stable25] Use color preset of shipped background as primary color
2022-10-05 13:18:41 +02:00
blizzz ab959249d3 Merge pull request #34434 from nextcloud/backport/32635/stable25
[stable25] Fix User profile picture when performing the search
2022-10-05 13:10:55 +02:00
blizzz c4a6f5c7b2 Merge pull request #34370 from nextcloud/backport/34364/stable25
[stable25] also use updatedir for cleanup of backups
2022-10-05 13:08:46 +02:00
blizzz 0612e008d4 Merge pull request #34405 from nextcloud/backport/34302/stable25
[stable25] Fix: Prevent deadlocks during mtime/size/etag propagation
2022-10-05 12:52:29 +02:00
Andy Xheli 47e950dbee Update ContactsStoreTest.php
Signed-off-by: Andy Xheli <axheli@axtsolutions.com>
2022-10-05 08:42:09 +00:00
Andy Xheli 36473a3f63 Update lib/private/Contacts/ContactsMenu/ContactsStore.php
Signed-off-by: Andy Xheli <axheli@axtsolutions.com>

Tested no issues.

Co-authored-by: Pytal <24800714+Pytal@users.noreply.github.com>
Signed-off-by: Andy Xheli <axheli@axtsolutions.com>
2022-10-05 08:42:09 +00:00
Andy Xheli f4fc7daf73 Update ContactsStore.php
Signed-off-by: Andy Xheli <axheli@axtsolutions.com>
2022-10-05 08:42:08 +00:00
Andy Xheli 31e5e102c1 Apply suggestions from code review
Signed-off-by: Andy Xheli <axheli@axtsolutions.com>

Co-authored-by: Carl Schwan <carl@carlschwan.eu>
Signed-off-by: Andy Xheli <axheli@axtsolutions.com>
2022-10-05 08:42:08 +00:00
Andy Xheli 511ade9497 Fix User profile picture when performing the search
Signed-off-by: Andy Xheli <axheli@axtsolutions.com>

Before 
![image](https://user-images.githubusercontent.com/59488153/140980158-b9108161-57ab-48b4-ae6f-98ec4e72775a.png)

After




Fix for #31065
2022-10-05 08:42:07 +00:00
Christopher Ng d1dc38dc1f Use brand color for background only and keep accessible color as color primary
Signed-off-by: Christopher Ng <chrng8@gmail.com>
2022-10-05 08:03:16 +00:00
Christopher Ng fad56d683c Use color preset of shipped background as primary color
Signed-off-by: Christopher Ng <chrng8@gmail.com>
2022-10-05 08:03:16 +00:00
blizzz c765dac633 Merge pull request #34409 from nextcloud/backport/33566/stable25
[stable25] trigger a rescan when trying to fopen a file that exists in cache but not on disk
2022-10-04 12:43:10 +02:00
blizzz bbd972b2ca Merge pull request #34413 from nextcloud/backport/34350/stable25
[stable25] Add a capability for the reference API and expose the regex to clients
2022-10-03 21:28:08 +02:00
Julius Härtl a4bfea7f37 Merge pull request #34408 from nextcloud/backport/34396/stable25
[stable25] Fix positioning of the public page footer
2022-10-03 21:22:35 +02:00
Joas Schilling 7b841c060d Add a capability for the reference API and expose the regex to clients
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-10-03 16:27:05 +00:00
Robin Appelman 0a2744a998 trigger a rescan when trying to fopen a file that exists in cache but not on disk
Signed-off-by: Robin Appelman <robin@icewind.nl>
2022-10-03 15:28:30 +00:00
Robin Appelman c26e17104c add test for trying to fopen a file which no longer exists on disk
Signed-off-by: Robin Appelman <robin@icewind.nl>
2022-10-03 15:28:29 +00:00
szaimen 1f0245e68f fix footer
Signed-off-by: szaimen <szaimen@e.mail.de>
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2022-10-03 15:12:33 +00:00
Julius Härtl a3febe2a40 Fix positioning of the public page footer
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-10-03 15:12:33 +00:00
raul 7ed5679f2c Fix unencrypted size calculation for files created before the Encryption storage is enabled
Signed-off-by: raul <raul@nextcloud.com>
2022-10-03 14:29:01 +00:00
raul 380aaef96e Fix: Prevent deadlocks during mtime/size/etag propagation
Signed-off-by: raul <raul@nextcloud.com>
2022-10-03 14:29:01 +00:00
blizzz 1b4c82156d Merge pull request #34382 from nextcloud/backport/34339/stable25
[stable25] Fix URLs on reference resolving
2022-10-03 11:18:26 +02:00
blizzz 07ac47812b Merge pull request #34380 from nextcloud/backport/34289/stable25
[stable25] Url encode the web dav url for copying
2022-10-03 11:18:03 +02:00
blizzz 46180dd9f3 Merge pull request #34374 from nextcloud/backport/34219/stable25
[stable25] Add fallback options for contacts array
2022-10-03 11:17:38 +02:00
blizzz d847a89436 Merge pull request #34371 from nextcloud/backport/34343/stable25
[stable25] Fix missing maintenance mode header for OCS request
2022-10-03 11:16:50 +02:00
blizzz eafcc4e3a1 Merge pull request #34357 from nextcloud/backport/34353/stable25
[stable25] Reference url can always fallback to the link itself
2022-10-03 11:15:39 +02:00
Arthur Schiwon 51c249d9ab 25.0.0 RC3
Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
2022-10-03 11:07:20 +02:00
Anna Larch b1617bca39 Add fallback options for contacts array
when the UID or FN is null, the email address / dsiplay name will be used instead

Signed-off-by: Anna Larch <anna@nextcloud.com>
2022-10-02 12:44:04 +00:00
Joas Schilling 14a59c461b Fix URLs on reference resolving
The vue-richtext app currently sends leading spaces if they are in the text.

Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-10-02 12:43:22 +00:00
Anna Larch a2d8bd4c11 Url encode the web dav url for copying
Signed-off-by: Anna Larch <anna@nextcloud.com>
2022-10-02 12:32:25 +00:00
Simon L 115058720a Merge pull request #34373 from nextcloud/backport/34331/stable25
[stable25] fix public view
2022-10-02 11:17:09 +02:00
nextcloud-command 65144258d2 Compile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2022-10-01 22:24:53 +00:00
szaimen 7b2f226ce6 fix public view - attempt 2
Signed-off-by: szaimen <szaimen@e.mail.de>
2022-10-01 22:24:53 +00:00
Christoph Wurst ac5ec1af7d Fix missing maintenance mode header for OCS request
Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
2022-10-01 19:46:34 +00:00
blizzz 34ca126b37 Merge pull request #34365 from nextcloud/backport/34363/stable25
[stable25] Update OpenDyslexia to v0.91.12
2022-10-01 21:39:23 +02:00
blizzz 02d07e14b3 Merge pull request #34354 from nextcloud/backport/34348/stable25
[stable25] Fix renaming in shared with you file list
2022-10-01 20:08:40 +02:00
Arthur Schiwon 61def5e276 also use updatedir for cleanup of backups
Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
2022-10-01 17:29:44 +00:00
John Molakvoæ ffecfc6441 Merge pull request #34367 from nextcloud/backport/34323/stable25
[stable25] Update theming screenshots
2022-10-01 18:22:24 +02:00
Simon L 94b86b22ce Merge pull request #34356 from nextcloud/backport/34333/stable25
[stable25] fix the tertiary button text
2022-10-01 18:15:55 +02:00
John Molakvoæ (skjnldsv) cfd01baf04 Update theming screenshots
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
2022-10-01 15:47:36 +00:00
Simon L 036af3556c Merge pull request #34332 from nextcloud/backport/34327/stable25
[stable25] Fix starred favourite icon
2022-10-01 17:45:13 +02:00
Joas Schilling a3b96282ed Update opendyslexia
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-10-01 15:24:55 +00:00
Julien Veyssier 56abd79050 reference url can always fallback to the link itself
Signed-off-by: Julien Veyssier <eneiluj@posteo.net>
2022-09-30 22:47:07 +00:00
szaimen 5267da34b1 fix test
Signed-off-by: szaimen <szaimen@e.mail.de>
2022-09-30 18:37:17 +00:00
szaimen 446e495c59 fix the tertiary button text
Signed-off-by: szaimen <szaimen@e.mail.de>
2022-09-30 18:37:17 +00:00
Vincent Petry cae61e00f6 Fix renaming in shared with you file list
Signed-off-by: Vincent Petry <vincent@nextcloud.com>
2022-09-30 17:56:21 +00:00
blizzz 5660589ca9 Merge pull request #34340 from nextcloud/backport/34328/stable25
[stable25] Correctly handle Redis::keys returning false
2022-09-30 12:55:30 +02:00
Côme Chilliet 8aed08e058 Correctly handle Redis::keys returning false
Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
2022-09-30 08:19:15 +00:00
Simon L f2d7d13579 Merge pull request #34335 from nextcloud/backport/34321/stable25
[stable25] Fix invisible status
2022-09-30 09:18:11 +02:00
Joas Schilling 4dea095a76 Merge pull request #34337 from nextcloud/backport/34322/stable25
[stable25] Only match http(s) URLs for references
2022-09-30 09:13:09 +02:00
Joas Schilling 2ea723d1e1 Adjust tests
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-09-30 06:11:46 +00:00
Joas Schilling 019ffa2ec0 Only match http(s) URLs for references
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-09-30 06:11:45 +00:00
nextcloud-command 885b55b7af Compile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2022-09-29 21:09:21 +00:00
John Molakvoæ (skjnldsv) 467a0eaee2 Fix invisible status
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
2022-09-29 21:09:21 +00:00
John Molakvoæ (skjnldsv) 77da30a906 Fix starred favourite icon
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
2022-09-29 14:23:25 +00:00
blizzz ce1dddcc64 Merge pull request #34276 from nextcloud/release/25.0.0_rc2
25.0.0 RC2
2022-09-29 15:20:44 +02:00
blizzz b84eb26f72 Merge pull request #34325 from nextcloud/backport/34313/stable25
[stable25] unbundle files_videoplayer and clean up a little
2022-09-29 15:19:30 +02:00
blizzz b6fd615bf7 Merge pull request #34320 from nextcloud/backport/34308/stable25
[stable25] Fix user status emoji picking
2022-09-29 13:49:26 +02:00
Arthur Schiwon 68284b87f2 unbundle files_videoplayer and clean up a little
Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
2022-09-29 11:27:33 +00:00
blizzz 904ac63310 Merge pull request #34316 from nextcloud/backport/34215/stable25
[stable25] Add cache header for image endpoint if link previews
2022-09-29 12:16:56 +02:00
blizzz 1f27bd153b Merge pull request #34310 from nextcloud/backport/34306/stable25
[stable25] fix default value for updatedirectory setting
2022-09-29 12:13:36 +02:00
nextcloud-command 92a4ce9f3b Compile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2022-09-28 23:25:51 +00:00
John Molakvoæ (skjnldsv) 729b78708f Fix user status emoji picking
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
2022-09-28 23:25:50 +00:00
Simon L 8b34878771 Merge pull request #34319 from nextcloud/backport/34311/stable25
[stable25] Remove unwanted settings button background
2022-09-29 01:21:41 +02:00
John Molakvoæ (skjnldsv) a5c5740e84 Remove unwanted settings button background
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2022-09-28 22:38:22 +00:00
blizzz ab51e4ef24 Merge pull request #34317 from nextcloud/backport/34287/stable25
[stable25] fix highcontrast theme
2022-09-28 21:53:04 +02:00
blizzz d442a3c1be Merge pull request #34315 from nextcloud/backport/34204/stable25
[stable25] Fix various theming bugs
2022-09-28 21:52:27 +02:00
szaimen 6ac50c26ef fix highcontrast theme icons
Signed-off-by: szaimen <szaimen@e.mail.de>
2022-09-28 18:38:24 +00:00
Julius Härtl 4aeb701b87 Add cache header for image endpoint if link previews
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-09-28 18:29:45 +00:00
Christopher Ng 9c073fd760 Fix various theming bugs
Signed-off-by: Christopher Ng <chrng8@gmail.com>
2022-09-28 18:01:41 +00:00
Arthur Schiwon 413610ede4 fix default value for updatedirectory setting
Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
2022-09-28 13:56:48 +00:00
blizzz c12f26867d Merge pull request #34285 from nextcloud/backport/34263/stable25
[stable25] Add endpoint to fetch a cachable reference data
2022-09-28 10:23:37 +02:00
blizzz 847535ee88 Merge pull request #34295 from nextcloud/backport/34288/stable25
[stable25] Fix focussing to internal link after copy
2022-09-27 23:20:22 +02:00
blizzz d9c07af32a Merge pull request #34294 from nextcloud/backport/34292/stable25
[stable25] Fix grid view button in public page
2022-09-27 23:19:01 +02:00
blizzz e85870afa7 Merge pull request #34293 from nextcloud/backport/34272/stable25
[stable25] Add primary color presets for shipped backgrounds
2022-09-27 23:18:28 +02:00
blizzz 7bdaa9fa04 Merge pull request #34286 from nextcloud/backport/34134/stable25
[stable25] l10n: Fix text string
2022-09-27 23:17:49 +02:00
blizzz 9c21bda303 Merge pull request #34221 from nextcloud/backport/34212/stable25
[stable25] Fix loading custom background
2022-09-27 23:08:13 +02:00
Vincent Petry 3bb4d01a7c Update apps/files_sharing/src/components/SharingEntryInternal.vue
Co-authored-by: Joas Schilling <213943+nickvergessen@users.noreply.github.com>
Signed-off-by: Vincent Petry <vincent@nextcloud.com>
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2022-09-27 17:11:17 +00:00
Vincent Petry 99cb56cda2 Fix focussing to internal link after copy
Since a recent change to NcActions it's not possible to add a "ref"
attribute on a NcAction* component.

As a workaround, a ref was added on the NcActions parent component
instead.

Signed-off-by: Vincent Petry <vincent@nextcloud.com>
2022-09-27 17:11:16 +00:00
Vincent Petry a85d1cad6a Fix grid view button in public page
Signed-off-by: Vincent Petry <vincent@nextcloud.com>
2022-09-27 17:07:09 +00:00
Christopher Ng 6965d4b4af Add preset primary colors for shipped backgrounds
Signed-off-by: Christopher Ng <chrng8@gmail.com>
2022-09-27 16:24:52 +00:00
Joas Schilling 4441416245 Merge pull request #34213 from nextcloud/backport/34191/stable25
[stable25] DashBoard : Allow up to 5 widgets side by side
2022-09-27 17:10:29 +02:00
blizzz 96423a1172 Merge pull request #34251 from nextcloud/backport/34222/stable25
[stable25] Fix install page
2022-09-27 16:58:17 +02:00
Joas Schilling 2d86948c34 Merge pull request #34270 from nextcloud/backport/34260/stable25
[stable25] Remove unneeded and problematic vue-cli-plugin-unit-test
2022-09-27 12:34:59 +02:00
Valdnet dbc421580e l10n: Correct text string
Signed-off-by: Valdnet <47037905+Valdnet@users.noreply.github.com>
2022-09-27 09:58:44 +00:00
Julius Härtl 89f8179e64 Add endpoint to fetch a cachable reference data
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-09-27 09:58:13 +00:00
Vincent Petry bc9f3d3d4c Remove unneeded and problematic vue-cli-plugin-unit-test
It was causing issues during dependency upgrades and wasn't really used
except for its preset definition.

Signed-off-by: Vincent Petry <vincent@nextcloud.com>
2022-09-27 10:45:00 +02:00
Vincent Petry 9fe747f2c5 Fix loading custom background
Fix the route for loading the custom background.
Because the controller is UserThemeController, so the route path is
not "theming" but "userTheme".

Signed-off-by: Vincent Petry <vincent@nextcloud.com>
2022-09-27 08:24:41 +00:00
Vincent Petry 6f8b3916ad Merge pull request #34257 from nextcloud/backport/34247/stable25
[stable25] Bump nth-check and @vue/cli-service
2022-09-27 09:14:32 +02:00
Arthur Schiwon 82e9af439e 25.0.0 RC2
Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
2022-09-27 08:25:00 +02:00
Vincent Petry c471c8d6de Merge pull request #34268 from nextcloud/dependabot/npm_and_yarn/stable25/moment-2.29.4
[stable25] Bump moment from 2.29.3 to 2.29.4
2022-09-26 21:28:26 +02:00
Vincent Petry 42ca267d83 Fix install page
- Use normal labels instead of hiding them
- Fix position of the eye button (this is still very hacky)
- Define width on the box and not on the text fields

This really needs to be ported to vue

Signed-off-by: Carl Schwan <carl@carlschwan.eu>
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2022-09-26 19:01:35 +00:00
Joas Schilling 981da3d49f Recompile
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-09-26 19:32:19 +02:00
Joas Schilling 7083596dc1 Bump moment from 2.29.3 to 2.29.4
Bumps [moment](https://github.com/moment/moment) from 2.29.3 to 2.29.4.
- [Release notes](https://github.com/moment/moment/releases)
- [Changelog](https://github.com/moment/moment/blob/develop/CHANGELOG.md)
- [Commits](moment/moment@2.29.3...2.29.4)

---
updated-dependencies:
- dependency-name: moment
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-09-26 19:31:55 +02:00
Joas Schilling ac2e94f121 Merge pull request #34258 from nextcloud/backport/33887/stable25
[stable25] Bump moment-timezone from 0.5.34 to 0.5.37
2022-09-26 19:22:40 +02:00
Joas Schilling de295ba8dd Recompile
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-09-26 15:21:20 +02:00
Joas Schilling 5d078383b6 Merge pull request #34237 from nextcloud/backport/34220/stable25
[stable25] Make phpcs happy and remove unused setup thing
2022-09-26 15:07:48 +02:00
Joas Schilling e938bbec19 Merge pull request #34253 from nextcloud/backport/34249/stable25
[stable25] Move Reference class to public namespace
2022-09-26 15:07:32 +02:00
Joas Schilling 1f0f227b43 Bump moment-timezone from 0.5.34 to 0.5.37
Bumps [moment-timezone](https://github.com/moment/moment-timezone) from 0.5.34 to 0.5.37.
- [Release notes](https://github.com/moment/moment-timezone/releases)
- [Changelog](https://github.com/moment/moment-timezone/blob/develop/changelog.md)
- [Commits](https://github.com/moment/moment-timezone/compare/0.5.34...0.5.37)

---
updated-dependencies:
- dependency-name: moment-timezone
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-09-26 12:42:41 +00:00
dependabot[bot] b2fa292ee8 Bump nth-check and @vue/cli-service
Bumps [nth-check](https://github.com/fb55/nth-check) and [@vue/cli-service](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-service). These dependencies needed to be updated together.

Updates `nth-check` from 1.0.2 to 2.1.1
- [Release notes](https://github.com/fb55/nth-check/releases)
- [Commits](https://github.com/fb55/nth-check/compare/v1.0.2...v2.1.1)

Updates `@vue/cli-service` from 4.5.17 to 5.0.8
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v5.0.8/packages/@vue/cli-service)

---
updated-dependencies:
- dependency-name: nth-check
  dependency-type: indirect
- dependency-name: "@vue/cli-service"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-26 12:40:09 +00:00
Joas Schilling cc6b12a166 Add since
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-09-26 10:44:24 +00:00
Julius Härtl b45d342654 Move Reference class to public namespace
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-09-26 10:44:24 +00:00
Jérôme Herbinet e43814f74d DashBoard : Allow up to 5 widgets side by side
Signed-off-by: Jérôme Herbinet <j.herbinet@protonmail.ch>

Signed-off-by: Jérôme Herbinet <33763786+Jerome-Herbinet@users.noreply.github.com>
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2022-09-26 06:07:05 +00:00
Joas Schilling 611b920e3d Merge pull request #34236 from nextcloud/backport/34210/stable25
[stable25] Fixing missing theming variables
2022-09-26 06:38:47 +02:00
Joas Schilling c25d61c7b8 Fix CS completely
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-09-26 06:38:01 +02:00
Joas Schilling 4be6d23d00 Merge pull request #34187 from nextcloud/bugfix/noid/fix-talk-testing
[stable25] Fix Talk checkout in video verification test
2022-09-26 06:13:03 +02:00
Vincent Petry f2bd15229a Make phpcs happy and remove unused setup thing
Signed-off-by: Vincent Petry <vincent@nextcloud.com>
2022-09-24 05:57:45 +00:00
John Molakvoæ a703818ee7 Refactor primary computation and fix a few missing theme parity variables
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
2022-09-24 05:38:47 +00:00
John Molakvoæ 5ee61a5fc6 Disable background blur on highcontrast
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
2022-09-24 05:38:47 +00:00
John Molakvoæ 019e85d92c Fixing dark primary element variables
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
2022-09-24 05:38:47 +00:00
blizzz 1fae3ae4ae Merge pull request #34185 from nextcloud/release/25.0.0_rc_1
25.0.0 RC 1
2022-09-22 18:12:42 +02:00
blizzz c6c4328e2a Merge pull request #34196 from nextcloud/backport/34190/stable25
[stable25] remove CoreBundle as we now ship the lat app that was referenced there
2022-09-22 18:12:26 +02:00
Arthur Schiwon aff9302638 update autoloader
Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
2022-09-22 17:36:13 +02:00
Arthur Schiwon 1115199ae3 remove getDefaultInstallationBundle
- because all apps are shipped now it was returning an empty result

Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
2022-09-22 17:36:09 +02:00
Arthur Schiwon 86b787d21e remove InstallCoreBundle repair step
- core bundle was empty and thus removed
- all former apps installed this way are shipped

Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
2022-09-22 17:36:05 +02:00
Arthur Schiwon 359a37fd10 remove Core Bundle
Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
2022-09-22 17:36:00 +02:00
blizzz 079a8c0e0f Merge pull request #34192 from nextcloud/move-background-clearing-stable25
[stable25] Move avatar clearing in the background
2022-09-22 17:33:43 +02:00
Vincent Petry 42bc4a0b2a Merge pull request #34195 from nextcloud/backport/34160/stable25
[stable25] Detect weird local ips
2022-09-22 17:32:22 +02:00
Côme Chilliet 205760a3aa Fix idn_to_utf8 stub signature
Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
2022-09-22 16:39:58 +02:00
Côme Chilliet 2948697257 Update 3rdparty to master
Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
2022-09-22 16:39:50 +02:00
Côme Chilliet 3c47caf08b Fix tests for nested v4 in v6
Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
2022-09-22 16:39:42 +02:00
Côme Chilliet 515e05cf16 Use new dependency to normalize IPs
Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
2022-09-22 16:39:34 +02:00
Côme Chilliet 060230eec7 Add mlocati/ip-lib dependency
Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
2022-09-22 16:39:22 +02:00
Côme Chilliet b2a893abad Add missing urldecode and idn_to_utf8 calls to local address checker
The call to idn_to_utf8 call is actually to apply normalization

Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
2022-09-22 16:39:14 +02:00
Côme Chilliet 95dcc610fc Harden tests for local IP detection in URLs
Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
2022-09-22 16:38:57 +02:00
Carl Schwan a5a8f4e9ef Move avatar clearing in the background
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
(cherry picked from commit e21e53670c60b74ca2c49c01bf510ccdc5115b0d)
2022-09-22 15:42:13 +02:00
blizzz 9366ec0fb8 Merge pull request #34184 from nextcloud/backport/34183/stable25
[stable25] Bumped shipped apps list
2022-09-22 12:56:07 +02:00
Joas Schilling bfaa31af61 Fix Talk checkout in video verification test
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-09-22 11:49:28 +02:00
Arthur Schiwon 063aac8ebc 25.0.0 RC 1
Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
2022-09-22 11:21:54 +02:00
Joas Schilling 773826f9e1 Bumped shipped apps list
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-09-22 11:21:09 +02:00
190 changed files with 7890 additions and 29624 deletions
+1 -1
View File
@@ -1214,7 +1214,7 @@ steps:
commands:
# JavaScript files are not used in integration tests so it is not needed to
# build them.
- git clone --depth 1 https://github.com/nextcloud/spreed apps/spreed
- git clone --depth 1 --branch stable25 https://github.com/nextcloud/spreed apps/spreed
- name: integration-sharing-v1-video-verification
image: ghcr.io/nextcloud/continuous-integration-integration-php7.4:latest
commands:
+2 -28
View File
@@ -88,7 +88,7 @@
</template>
<script>
import { generateUrl, imagePath } from '@nextcloud/router'
import { generateUrl } from '@nextcloud/router'
import { getCurrentUser } from '@nextcloud/auth'
import { loadState } from '@nextcloud/initial-state'
import axios from '@nextcloud/axios'
@@ -99,13 +99,11 @@ import Pencil from 'vue-material-design-icons/Pencil.vue'
import Vue from 'vue'
import isMobile from './mixins/isMobile.js'
import { getBackgroundUrl } from './helpers/getBackgroundUrl.js'
const panels = loadState('dashboard', 'panels')
const firstRun = loadState('dashboard', 'firstRun')
const background = loadState('theming', 'background')
const backgroundVersion = loadState('theming', 'backgroundVersion')
const themingDefaultBackground = loadState('theming', 'themingDefaultBackground')
const shippedBackgroundList = loadState('theming', 'shippedBackgrounds')
@@ -155,19 +153,6 @@ export default {
}
},
computed: {
backgroundImage() {
return getBackgroundUrl(this.background, backgroundVersion, this.themingDefaultBackground)
},
backgroundStyle() {
if ((this.background === 'default' && this.themingDefaultBackground === 'backgroundColor')
|| this.background.match(/#[0-9A-Fa-f]{6}/g)) {
return null
}
return {
backgroundImage: this.background === 'default' ? 'var(--image-main-background)' : `url('${this.backgroundImage}')`,
}
},
greeting() {
const time = this.timer.getHours()
@@ -286,17 +271,6 @@ export default {
// document.body.removeAttribute('data-theme-light')
// document.body.setAttribute('data-theme-dark', 'true')
}
const themeElements = [document.documentElement, document.querySelector('#header'), document.querySelector('body')]
for (const element of themeElements) {
if (this.background === 'default') {
element.style.setProperty('--image-main-background', `url('${imagePath('core', 'app-background.jpg')}')`)
} else if (this.background.match(/#[0-9A-Fa-f]{6}/g)) {
element.style.setProperty('--image-main-background', undefined)
} else {
element.style.setProperty('--image-main-background', this.backgroundStyle.backgroundImage)
}
}
},
/**
* Method to register panels that will be called by the integrating apps
@@ -441,7 +415,7 @@ export default {
.panels {
width: auto;
margin: auto;
max-width: 1500px;
max-width: 1800px;
display: flex;
justify-content: center;
flex-direction: row;
@@ -1,49 +0,0 @@
/**
* @copyright Copyright (c) 2020 Julius Härtl <jus@bitgrid.net>
*
* @author Avior <florian.bouillon@delta-wings.net>
* @author Julien Veyssier <eneiluj@posteo.net>
* @author Julius Härtl <jus@bitgrid.net>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import { generateUrl } from '@nextcloud/router'
import { prefixWithBaseUrl } from './prefixWithBaseUrl.js'
export const getBackgroundUrl = (background, time = 0, themingDefaultBackground = '') => {
const enabledThemes = window.OCA?.Theming?.enabledThemes || []
const isDarkTheme = (enabledThemes.length === 0 || enabledThemes[0] === 'default')
? window.matchMedia('(prefers-color-scheme: dark)').matches
: enabledThemes.join('').indexOf('dark') !== -1
if (background === 'default') {
if (themingDefaultBackground && themingDefaultBackground !== 'backgroundColor') {
return generateUrl('/apps/theming/image/background') + '?v=' + window.OCA.Theming.cacheBuster
}
if (isDarkTheme) {
return prefixWithBaseUrl('eduardo-neves-pedra-azul.jpg')
}
return prefixWithBaseUrl('kamil-porembinski-clouds.jpg')
} else if (background === 'custom') {
return generateUrl('/apps/theming/background') + '?v=' + time
}
return prefixWithBaseUrl(background)
}
@@ -1,25 +0,0 @@
/**
* @copyright Copyright (c) 2020 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import { generateFilePath } from '@nextcloud/router'
export const prefixWithBaseUrl = (url) => generateFilePath('theming', '', 'img/background/') + url
@@ -1151,7 +1151,7 @@ class FileTest extends TestCase {
$info = new \OC\Files\FileInfo('/test.txt', $this->getMockStorage(), null, [
'permissions' => \OCP\Constants::PERMISSION_ALL,
'type' => FileInfo::TYPE_FOLDER,
'type' => FileInfo::TYPE_FILE,
], null);
$file = new \OCA\DAV\Connector\Sabre\File($view, $info);
@@ -1172,7 +1172,7 @@ class FileTest extends TestCase {
$info = new \OC\Files\FileInfo('/test.txt', $this->getMockStorage(), null, [
'permissions' => \OCP\Constants::PERMISSION_ALL,
'type' => FileInfo::TYPE_FOLDER,
'type' => FileInfo::TYPE_FILE,
], null);
$file = new \OCA\DAV\Connector\Sabre\File($view, $info);
+2 -1
View File
@@ -5,7 +5,7 @@
<name>Files</name>
<summary>File Management</summary>
<description>File Management</description>
<version>1.20.0</version>
<version>1.20.1</version>
<licence>agpl</licence>
<author>Robin Appelman</author>
<author>Vincent Petry</author>
@@ -26,6 +26,7 @@
<job>OCA\Files\BackgroundJob\DeleteOrphanedItems</job>
<job>OCA\Files\BackgroundJob\CleanupFileLocks</job>
<job>OCA\Files\BackgroundJob\CleanupDirectEditingTokens</job>
<job>OCA\Files\BackgroundJob\DeleteExpiredOpenLocalEditor</job>
</background-jobs>
<commands>
+14
View File
@@ -37,6 +37,8 @@ declare(strict_types=1);
*/
namespace OCA\Files\AppInfo;
use OCA\Files\Controller\OpenLocalEditorController;
/** @var Application $application */
$application = \OC::$server->query(Application::class);
$application->registerRoutes(
@@ -169,6 +171,18 @@ $application->registerRoutes(
'url' => '/api/v1/transferownership/{id}',
'verb' => 'DELETE',
],
[
/** @see OpenLocalEditorController::create() */
'name' => 'OpenLocalEditor#create',
'url' => '/api/v1/openlocaleditor',
'verb' => 'POST',
],
[
/** @see OpenLocalEditorController::validate() */
'name' => 'OpenLocalEditor#validate',
'url' => '/api/v1/openlocaleditor/{token}',
'verb' => 'POST',
],
],
]
);
@@ -20,6 +20,7 @@ return array(
'OCA\\Files\\AppInfo\\Application' => $baseDir . '/../lib/AppInfo/Application.php',
'OCA\\Files\\BackgroundJob\\CleanupDirectEditingTokens' => $baseDir . '/../lib/BackgroundJob/CleanupDirectEditingTokens.php',
'OCA\\Files\\BackgroundJob\\CleanupFileLocks' => $baseDir . '/../lib/BackgroundJob/CleanupFileLocks.php',
'OCA\\Files\\BackgroundJob\\DeleteExpiredOpenLocalEditor' => $baseDir . '/../lib/BackgroundJob/DeleteExpiredOpenLocalEditor.php',
'OCA\\Files\\BackgroundJob\\DeleteOrphanedItems' => $baseDir . '/../lib/BackgroundJob/DeleteOrphanedItems.php',
'OCA\\Files\\BackgroundJob\\ScanFiles' => $baseDir . '/../lib/BackgroundJob/ScanFiles.php',
'OCA\\Files\\BackgroundJob\\TransferOwnership' => $baseDir . '/../lib/BackgroundJob/TransferOwnership.php',
@@ -35,9 +36,12 @@ return array(
'OCA\\Files\\Controller\\ApiController' => $baseDir . '/../lib/Controller/ApiController.php',
'OCA\\Files\\Controller\\DirectEditingController' => $baseDir . '/../lib/Controller/DirectEditingController.php',
'OCA\\Files\\Controller\\DirectEditingViewController' => $baseDir . '/../lib/Controller/DirectEditingViewController.php',
'OCA\\Files\\Controller\\OpenLocalEditorController' => $baseDir . '/../lib/Controller/OpenLocalEditorController.php',
'OCA\\Files\\Controller\\TemplateController' => $baseDir . '/../lib/Controller/TemplateController.php',
'OCA\\Files\\Controller\\TransferOwnershipController' => $baseDir . '/../lib/Controller/TransferOwnershipController.php',
'OCA\\Files\\Controller\\ViewController' => $baseDir . '/../lib/Controller/ViewController.php',
'OCA\\Files\\Db\\OpenLocalEditor' => $baseDir . '/../lib/Db/OpenLocalEditor.php',
'OCA\\Files\\Db\\OpenLocalEditorMapper' => $baseDir . '/../lib/Db/OpenLocalEditorMapper.php',
'OCA\\Files\\Db\\TransferOwnership' => $baseDir . '/../lib/Db/TransferOwnership.php',
'OCA\\Files\\Db\\TransferOwnershipMapper' => $baseDir . '/../lib/Db/TransferOwnershipMapper.php',
'OCA\\Files\\DirectEditingCapabilities' => $baseDir . '/../lib/DirectEditingCapabilities.php',
@@ -48,6 +52,7 @@ return array(
'OCA\\Files\\Listener\\LegacyLoadAdditionalScriptsAdapter' => $baseDir . '/../lib/Listener/LegacyLoadAdditionalScriptsAdapter.php',
'OCA\\Files\\Listener\\LoadSidebarListener' => $baseDir . '/../lib/Listener/LoadSidebarListener.php',
'OCA\\Files\\Migration\\Version11301Date20191205150729' => $baseDir . '/../lib/Migration/Version11301Date20191205150729.php',
'OCA\\Files\\Migration\\Version12101Date20221011153334' => $baseDir . '/../lib/Migration/Version12101Date20221011153334.php',
'OCA\\Files\\Notification\\Notifier' => $baseDir . '/../lib/Notification/Notifier.php',
'OCA\\Files\\Search\\FilesSearchProvider' => $baseDir . '/../lib/Search/FilesSearchProvider.php',
'OCA\\Files\\Service\\DirectEditingService' => $baseDir . '/../lib/Service/DirectEditingService.php',
@@ -35,6 +35,7 @@ class ComposerStaticInitFiles
'OCA\\Files\\AppInfo\\Application' => __DIR__ . '/..' . '/../lib/AppInfo/Application.php',
'OCA\\Files\\BackgroundJob\\CleanupDirectEditingTokens' => __DIR__ . '/..' . '/../lib/BackgroundJob/CleanupDirectEditingTokens.php',
'OCA\\Files\\BackgroundJob\\CleanupFileLocks' => __DIR__ . '/..' . '/../lib/BackgroundJob/CleanupFileLocks.php',
'OCA\\Files\\BackgroundJob\\DeleteExpiredOpenLocalEditor' => __DIR__ . '/..' . '/../lib/BackgroundJob/DeleteExpiredOpenLocalEditor.php',
'OCA\\Files\\BackgroundJob\\DeleteOrphanedItems' => __DIR__ . '/..' . '/../lib/BackgroundJob/DeleteOrphanedItems.php',
'OCA\\Files\\BackgroundJob\\ScanFiles' => __DIR__ . '/..' . '/../lib/BackgroundJob/ScanFiles.php',
'OCA\\Files\\BackgroundJob\\TransferOwnership' => __DIR__ . '/..' . '/../lib/BackgroundJob/TransferOwnership.php',
@@ -50,9 +51,12 @@ class ComposerStaticInitFiles
'OCA\\Files\\Controller\\ApiController' => __DIR__ . '/..' . '/../lib/Controller/ApiController.php',
'OCA\\Files\\Controller\\DirectEditingController' => __DIR__ . '/..' . '/../lib/Controller/DirectEditingController.php',
'OCA\\Files\\Controller\\DirectEditingViewController' => __DIR__ . '/..' . '/../lib/Controller/DirectEditingViewController.php',
'OCA\\Files\\Controller\\OpenLocalEditorController' => __DIR__ . '/..' . '/../lib/Controller/OpenLocalEditorController.php',
'OCA\\Files\\Controller\\TemplateController' => __DIR__ . '/..' . '/../lib/Controller/TemplateController.php',
'OCA\\Files\\Controller\\TransferOwnershipController' => __DIR__ . '/..' . '/../lib/Controller/TransferOwnershipController.php',
'OCA\\Files\\Controller\\ViewController' => __DIR__ . '/..' . '/../lib/Controller/ViewController.php',
'OCA\\Files\\Db\\OpenLocalEditor' => __DIR__ . '/..' . '/../lib/Db/OpenLocalEditor.php',
'OCA\\Files\\Db\\OpenLocalEditorMapper' => __DIR__ . '/..' . '/../lib/Db/OpenLocalEditorMapper.php',
'OCA\\Files\\Db\\TransferOwnership' => __DIR__ . '/..' . '/../lib/Db/TransferOwnership.php',
'OCA\\Files\\Db\\TransferOwnershipMapper' => __DIR__ . '/..' . '/../lib/Db/TransferOwnershipMapper.php',
'OCA\\Files\\DirectEditingCapabilities' => __DIR__ . '/..' . '/../lib/DirectEditingCapabilities.php',
@@ -63,6 +67,7 @@ class ComposerStaticInitFiles
'OCA\\Files\\Listener\\LegacyLoadAdditionalScriptsAdapter' => __DIR__ . '/..' . '/../lib/Listener/LegacyLoadAdditionalScriptsAdapter.php',
'OCA\\Files\\Listener\\LoadSidebarListener' => __DIR__ . '/..' . '/../lib/Listener/LoadSidebarListener.php',
'OCA\\Files\\Migration\\Version11301Date20191205150729' => __DIR__ . '/..' . '/../lib/Migration/Version11301Date20191205150729.php',
'OCA\\Files\\Migration\\Version12101Date20221011153334' => __DIR__ . '/..' . '/../lib/Migration/Version12101Date20221011153334.php',
'OCA\\Files\\Notification\\Notifier' => __DIR__ . '/..' . '/../lib/Notification/Notifier.php',
'OCA\\Files\\Search\\FilesSearchProvider' => __DIR__ . '/..' . '/../lib/Search/FilesSearchProvider.php',
'OCA\\Files\\Service\\DirectEditingService' => __DIR__ . '/..' . '/../lib/Service/DirectEditingService.php',
+1 -2
View File
@@ -973,8 +973,7 @@ table.dragshadow td.size {
background-image: none;
}
.files-filestable .filename .favorite-mark .icon-starred {
/* $dir is the app name, so we add this to the icon var to avoid conflicts between apps */
background-image: var(--icon-star-dark-yellow);
background-image: var(--icon-starred-yellow) !important;
}
.files-filestable .filename .action .icon.hidden,
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -874,7 +874,7 @@ table.dragshadow td.size {
background-image: none;
}
& .icon-starred {
@include icon-color('star-dark', 'actions', variables.$color-yellow, 1, true);
background-image: var(--icon-starred-yellow) !important;
}
}
+1 -2
View File
@@ -973,8 +973,7 @@ table.dragshadow td.size {
background-image: none;
}
.files-filestable .filename .favorite-mark .icon-starred {
/* $dir is the app name, so we add this to the icon var to avoid conflicts between apps */
background-image: var(--icon-star-dark-yellow);
background-image: var(--icon-starred-yellow) !important;
}
.files-filestable .filename .action .icon.hidden,
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -114,13 +114,13 @@
OCA.Files.FileList.MultiSelectMenuActions.ToggleSelectionModeAction,
{
name: 'delete',
displayName: t('files', 'Delete'),
displayName: t('files', 'Delete'),
iconClass: 'icon-delete',
order: 99,
},
{
name: 'tags',
displayName: 'Tags',
displayName: t('files', 'Tags'),
iconClass: 'icon-tag',
order: 100,
},
+25 -23
View File
@@ -710,29 +710,31 @@
}
});
this.registerAction({
name: 'EditLocally',
displayName: function(context) {
var locked = context.$file.data('locked');
if (!locked) {
return t('files', 'Edit locally');
}
},
mime: 'all',
order: -23,
icon: function(filename, context) {
var locked = context.$file.data('locked');
if (!locked) {
return OC.imagePath('files', 'computer.svg')
}
},
permissions: OC.PERMISSION_UPDATE,
actionHandler: function (filename, context) {
var dir = context.dir || context.fileList.getCurrentDirectory();
var path = dir === '/' ? dir + filename : dir + '/' + filename;
context.fileList.openLocalClient(path);
},
});
if (!/Android|iPhone|iPad|iPod/i.test(navigator.userAgent)) {
this.registerAction({
name: 'EditLocally',
displayName: function(context) {
var locked = context.$file.data('locked');
if (!locked) {
return t('files', 'Edit locally');
}
},
mime: 'all',
order: -23,
icon: function(filename, context) {
var locked = context.$file.data('locked');
if (!locked) {
return OC.imagePath('files', 'computer.svg')
}
},
permissions: OC.PERMISSION_UPDATE,
actionHandler: function (filename, context) {
var dir = context.dir || context.fileList.getCurrentDirectory();
var path = dir === '/' ? dir + filename : dir + '/' + filename;
context.fileList.openLocalClient(path);
},
});
}
this.registerAction({
name: 'Open',
+16 -5
View File
@@ -2808,12 +2808,23 @@
},
openLocalClient: function(path) {
var scheme = 'nc://';
var command = 'open';
var uid = OC.getCurrentUser().uid;
var url = scheme + command + '/' + uid + '@' + window.location.host + OC.encodePath(path);
var link = OC.linkToOCS('apps/files/api/v1', 2) + 'openlocaleditor?format=json';
window.location.href = url;
$.post(link, {
path
})
.success(function(result) {
var scheme = 'nc://';
var command = 'open';
var uid = OC.getCurrentUser().uid;
var url = scheme + command + '/' + uid + '@' + window.location.host + OC.encodePath(path);
url += '?token=' + result.ocs.data.token;
window.location.href = url;
})
.fail(function() {
OC.Notification.show(t('files', 'Failed to redirect to client'))
})
},
/**
@@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2022 Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files\BackgroundJob;
use OCA\Files\Controller\OpenLocalEditorController;
use OCA\Files\Db\OpenLocalEditorMapper;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\IJob;
use OCP\BackgroundJob\TimedJob;
/**
* Delete all expired "Open local editor" token
*/
class DeleteExpiredOpenLocalEditor extends TimedJob {
protected OpenLocalEditorMapper $mapper;
public function __construct(
ITimeFactory $time,
OpenLocalEditorMapper $mapper
) {
parent::__construct($time);
$this->mapper = $mapper;
// Run every 12h
$this->interval = 12 * 3600;
$this->setTimeSensitivity(IJob::TIME_INSENSITIVE);
}
/**
* Makes the background job do its work
*
* @param array $argument unused argument
*/
public function run($argument): void {
$this->mapper->deleteExpiredTokens($this->time->getTime());
}
}
@@ -0,0 +1,138 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2022 Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files\Controller;
use OCA\Files\Db\OpenLocalEditor;
use OCA\Files\Db\OpenLocalEditorMapper;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\DB\Exception;
use OCP\IRequest;
use OCP\Security\ISecureRandom;
use Psr\Log\LoggerInterface;
class OpenLocalEditorController extends OCSController {
public const TOKEN_LENGTH = 128;
public const TOKEN_DURATION = 600; // 10 Minutes
public const TOKEN_RETRIES = 50;
protected ITimeFactory $timeFactory;
protected OpenLocalEditorMapper $mapper;
protected ISecureRandom $secureRandom;
protected LoggerInterface $logger;
protected ?string $userId;
public function __construct(
string $appName,
IRequest $request,
ITimeFactory $timeFactory,
OpenLocalEditorMapper $mapper,
ISecureRandom $secureRandom,
LoggerInterface $logger,
?string $userId
) {
parent::__construct($appName, $request);
$this->timeFactory = $timeFactory;
$this->mapper = $mapper;
$this->secureRandom = $secureRandom;
$this->logger = $logger;
$this->userId = $userId;
}
/**
* @NoAdminRequired
* @UserRateThrottle(limit=10, period=120)
*/
public function create(string $path): DataResponse {
$pathHash = sha1($path);
$entity = new OpenLocalEditor();
$entity->setUserId($this->userId);
$entity->setPathHash($pathHash);
$entity->setExpirationTime($this->timeFactory->getTime() + self::TOKEN_DURATION); // Expire in 10 minutes
for ($i = 1; $i <= self::TOKEN_RETRIES; $i++) {
$token = $this->secureRandom->generate(self::TOKEN_LENGTH, ISecureRandom::CHAR_ALPHANUMERIC);
$entity->setToken($token);
try {
$this->mapper->insert($entity);
return new DataResponse([
'userId' => $this->userId,
'pathHash' => $pathHash,
'expirationTime' => $entity->getExpirationTime(),
'token' => $entity->getToken(),
]);
} catch (Exception $e) {
if ($e->getCode() !== Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
// Only retry on unique constraint violation
throw $e;
}
}
}
$this->logger->error('Giving up after ' . self::TOKEN_RETRIES . ' retries to generate a unique local editor token for path hash: ' . $pathHash);
return new DataResponse([], Http::STATUS_INTERNAL_SERVER_ERROR);
}
/**
* @NoAdminRequired
* @BruteForceProtection(action=openLocalEditor)
*/
public function validate(string $path, string $token): DataResponse {
$pathHash = sha1($path);
try {
$entity = $this->mapper->verifyToken($this->userId, $pathHash, $token);
} catch (DoesNotExistException $e) {
$response = new DataResponse([], Http::STATUS_NOT_FOUND);
$response->throttle(['userId' => $this->userId, 'pathHash' => $pathHash]);
return $response;
}
$this->mapper->delete($entity);
if ($entity->getExpirationTime() <= $this->timeFactory->getTime()) {
$response = new DataResponse([], Http::STATUS_NOT_FOUND);
$response->throttle(['userId' => $this->userId, 'pathHash' => $pathHash]);
return $response;
}
return new DataResponse([
'userId' => $this->userId,
'pathHash' => $pathHash,
'expirationTime' => $entity->getExpirationTime(),
'token' => $entity->getToken(),
]);
}
}
+1 -1
View File
@@ -266,7 +266,7 @@ class ViewController extends Controller {
$nav->assign('quota', $storageInfo['quota']);
$nav->assign('usage_relative', $storageInfo['relative']);
$nav->assign('webdav_url', \OCP\Util::linkToRemote('dav/files/' . $user));
$nav->assign('webdav_url', \OCP\Util::linkToRemote('dav/files/' . rawurlencode($user)));
$contentItems = [];
+60
View File
@@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2022 Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files\Db;
use OCP\AppFramework\Db\Entity;
/**
* @method void setUserId(string $userId)
* @method string getUserId()
* @method void setPathHash(string $pathHash)
* @method string getPathHash()
* @method void setExpirationTime(int $expirationTime)
* @method int getExpirationTime()
* @method void setToken(string $token)
* @method string getToken()
*/
class OpenLocalEditor extends Entity {
/** @var string */
protected $userId;
/** @var string */
protected $pathHash;
/** @var int */
protected $expirationTime;
/** @var string */
protected $token;
public function __construct() {
$this->addType('userId', 'string');
$this->addType('pathHash', 'string');
$this->addType('expirationTime', 'integer');
$this->addType('token', 'string');
}
}
@@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2022 Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files\Db;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\Exception;
use OCP\IDBConnection;
class OpenLocalEditorMapper extends QBMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'open_local_editor', OpenLocalEditor::class);
}
/**
* @throws DoesNotExistException
* @throws MultipleObjectsReturnedException
* @throws Exception
*/
public function verifyToken(string $userId, string $pathHash, string $token): OpenLocalEditor {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)))
->andWhere($qb->expr()->eq('path_hash', $qb->createNamedParameter($pathHash)))
->andWhere($qb->expr()->eq('token', $qb->createNamedParameter($token)));
return $this->findEntity($qb);
}
public function deleteExpiredTokens(int $time): void {
$qb = $this->db->getQueryBuilder();
$qb->delete($this->getTableName())
->where($qb->expr()->lt('expiration_time', $qb->createNamedParameter($time)));
$qb->executeStatement();
}
}
@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2022 Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files\Migration;
use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\DB\Types;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
class Version12101Date20221011153334 extends SimpleMigrationStep {
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
$table = $schema->createTable('open_local_editor');
$table->addColumn('id',Types::BIGINT, [
'autoincrement' => true,
'notnull' => true,
'length' => 20,
'unsigned' => true,
]);
$table->addColumn('user_id', Types::STRING, [
'notnull' => true,
'length' => 64,
]);
$table->addColumn('path_hash', Types::STRING, [
'notnull' => true,
'length' => 64,
]);
$table->addColumn('expiration_time', Types::BIGINT, [
'notnull' => true,
'unsigned' => true,
]);
$table->addColumn('token', Types::STRING, [
'notnull' => true,
'length' => 128,
]);
$table->setPrimaryKey(['id']);
$table->addUniqueIndex(['user_id', 'path_hash', 'token'], 'openlocal_user_path_token');
return $schema;
}
}
+8 -4
View File
@@ -47,13 +47,17 @@
}
#imgframe img {
max-height: calc(100vh - var(--header-height) - 65px - 200px) !important;
max-height: calc(100vh - var(--header-height) - 65px - 200px - 16px) !important;
max-width: 100% !important;
width: unset !important;
}
#imgframe :not(#viewer) img {
min-width: 100px;
}
#imgframe video {
max-height: calc(100vh - var(--header-height) - 65px - 200px);
max-height: calc(100vh - var(--header-height) - 65px - 200px - 16px);
}
#imgframe audio {
@@ -94,8 +98,8 @@
max-height: 100%;
}
.app-files_sharing #app-content {
max-height: calc(100vh - var(--header-height) - 65px);
.app-files_sharing #app-content footer {
position: sticky !important;
}
/* fix multiselect bar offset on shared page */
+1 -1
View File
@@ -1 +1 @@
{"version":3,"sourceRoot":"","sources":["../../../core/css/variables.scss","public.scss"],"names":[],"mappings":";AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACKA;EACC;;;AAGD;EACC;;;AAGD;EACC;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;EACA;EACA;;;AAGD;EAEI;;;AAGJ;EACI;;;AAGJ;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;EACA;;;AAGD;EACC;;;AAID;EACI;;;AAGJ;AACA;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;AACA;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;AACA;EACC;;;AAED;AAAA;AAAA;EAGC;EACA;;;AAED;AAAA;AAAA;AAGC;EACA;;;AAGD;EACC;;;AAIA;EACC;;;AAIF;EACC;;;AAGD;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;AAAA;EAEC;EACA;EACA;;;AAGD;EACC;EACA;EACA;;AACA;EACC;EACA;;;AAIF;EACC;EACA;;;AAKD;EAII;IACC;;;AAQL;EAGG;IACC","file":"public.css"}
{"version":3,"sourceRoot":"","sources":["../../../core/css/variables.scss","public.scss"],"names":[],"mappings":";AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACMA;EACC;;;AAGD;EACC;;;AAGD;EACC;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACI;;;AAGJ;EACC;;;AAGD;EACC;EACA;EACA;;;AAGD;EAEI;;;AAGJ;EACI;;;AAGJ;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;EACA;;;AAGD;EACC;;;AAID;EACC;;;AAGD;AACA;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;AACA;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;AACA;EACC;;;AAED;AAAA;AAAA;EAGC;EACA;;;AAED;AAAA;AAAA;AAGC;EACA;;;AAGD;EACC;;;AAIA;EACC;;;AAIF;EACC;;;AAGD;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;AAAA;EAEC;EACA;EACA;;;AAGD;EACC;EACA;EACA;;AACA;EACC;EACA;;;AAIF;EACC;EACA;;;AAKD;EAII;IACC;;;AAQL;EAGG;IACC","file":"public.css"}
+10 -5
View File
@@ -1,6 +1,7 @@
@use 'variables';
$footer-height: 65px;
$footer-padding-height: 16px;
$download-button-section-height: 200px;
#preview {
@@ -30,13 +31,17 @@ $download-button-section-height: 200px;
}
#imgframe img {
max-height: calc(100vh - var(--header-height) - #{$footer-height} - #{$download-button-section-height}) !important;
max-height: calc(100vh - var(--header-height) - #{$footer-height} - #{$download-button-section-height} - #{$footer-padding-height}) !important;
max-width: 100% !important;
width: unset !important;
}
#imgframe :not(#viewer) img {
min-width: 100px;
}
#imgframe video {
max-height: calc(100vh - var(--header-height) - #{$footer-height} - #{$download-button-section-height});
max-height: calc(100vh - var(--header-height) - #{$footer-height} - #{$download-button-section-height} - #{$footer-padding-height});
}
#imgframe audio {
@@ -78,9 +83,9 @@ $download-button-section-height: 200px;
max-height: 100%;
}
// Fix footer overlapping with app-content
.app-files_sharing #app-content {
max-height: calc(100vh - var(--header-height) - #{$footer-height});
.app-files_sharing #app-content footer {
position: sticky !important;
}
/* fix multiselect bar offset on shared page */
+8 -4
View File
@@ -47,13 +47,17 @@
}
#imgframe img {
max-height: calc(100vh - var(--header-height) - 65px - 200px) !important;
max-height: calc(100vh - var(--header-height) - 65px - 200px - 16px) !important;
max-width: 100% !important;
width: unset !important;
}
#imgframe :not(#viewer) img {
min-width: 100px;
}
#imgframe video {
max-height: calc(100vh - var(--header-height) - 65px - 200px);
max-height: calc(100vh - var(--header-height) - 65px - 200px - 16px);
}
#imgframe audio {
@@ -94,8 +98,8 @@
max-height: 100%;
}
.app-files_sharing #app-content {
max-height: calc(100vh - var(--header-height) - 65px);
.app-files_sharing #app-content footer {
position: sticky !important;
}
/* fix multiselect bar offset on shared page */
+1 -1
View File
@@ -1 +1 @@
{"version":3,"sourceRoot":"","sources":["../../../core/css/variables.scss","public.scss","mobile.scss"],"names":[],"mappings":";AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACKA;EACC;;;AAGD;EACC;;;AAGD;EACC;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;EACA;EACA;;;AAGD;EAEI;;;AAGJ;EACI;;;AAGJ;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;EACA;;;AAGD;EACC;;;AAID;EACI;;;AAGJ;AACA;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;AACA;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;AACA;EACC;;;AAED;AAAA;AAAA;EAGC;EACA;;;AAED;AAAA;AAAA;AAGC;EACA;;;AAGD;EACC;;;AAIA;EACC;;;AAIF;EACC;;;AAGD;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;AAAA;EAEC;EACA;EACA;;;AAGD;EACC;EACA;EACA;;AACA;EACC;EACA;;;AAIF;EACC;EACA;;;AAKD;EAII;IACC;;;AAQL;EAGG;IACC;;;ADpQJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AEEA;AAEA;EACA;IACC;;;AAGD;EACA;AAAA;AAAA;AAAA;IAIC;;;AAGD;EACA;IACC;;;AAGD;EACA;IACC;IACA;;;AAED;EACA;IACC;;;AAGD;EACA;IACC;;;AAED;EACA;IACC;;;AAGD;EACA;IACC;IACA;IACA;IACA;;;EAGD;IACI;IACA;;;EAEJ;IACC;;;EAGD;IACC","file":"publicView.css"}
{"version":3,"sourceRoot":"","sources":["../../../core/css/variables.scss","public.scss","mobile.scss"],"names":[],"mappings":";AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACMA;EACC;;;AAGD;EACC;;;AAGD;EACC;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACI;;;AAGJ;EACC;;;AAGD;EACC;EACA;EACA;;;AAGD;EAEI;;;AAGJ;EACI;;;AAGJ;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;EACA;;;AAGD;EACC;;;AAID;EACC;;;AAGD;AACA;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;AACA;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;AACA;EACC;;;AAED;AAAA;AAAA;EAGC;EACA;;;AAED;AAAA;AAAA;AAGC;EACA;;;AAGD;EACC;;;AAIA;EACC;;;AAIF;EACC;;;AAGD;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;AAAA;EAEC;EACA;EACA;;;AAGD;EACC;EACA;EACA;;AACA;EACC;EACA;;;AAIF;EACC;EACA;;;AAKD;EAII;IACC;;;AAQL;EAGG;IACC;;;ADzQJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AEEA;AAEA;EACA;IACC;;;AAGD;EACA;AAAA;AAAA;AAAA;IAIC;;;AAGD;EACA;IACC;;;AAGD;EACA;IACC;IACA;;;AAED;EACA;IACC;;;AAGD;EACA;IACC;;;AAED;EACA;IACC;;;AAGD;EACA;IACC;IACA;IACA;IACA;;;EAGD;IACI;IACA;;;EAEJ;IACC;;;EAGD;IACC","file":"publicView.css"}
+25
View File
@@ -62,6 +62,11 @@ OCA.Sharing.PublicApp = {
// file list mode ?
if ($el.find('.files-filestable').length) {
// Toggle for grid view
this.$showGridView = $('input#showgridview');
this.$showGridView.on('change', _.bind(this._onGridviewChange, this));
$('#view-toggle').tooltip({placement: 'bottom', trigger: 'hover'});
var filesClient = new OC.Files.Client({
host: OC.getHost(),
port: OC.getPort(),
@@ -364,6 +369,26 @@ OCA.Sharing.PublicApp = {
}
},
/**
* Toggle showing gridview by default or not
*
* @returns {undefined}
*/
_onGridviewChange: function() {
const isGridView = this.$showGridView.is(':checked');
this.$showGridView.next('#view-toggle')
.removeClass('icon-toggle-filelist icon-toggle-pictures')
.addClass(isGridView ? 'icon-toggle-filelist' : 'icon-toggle-pictures')
this.$showGridView.next('#view-toggle').attr(
'data-original-title',
isGridView ? t('files', 'Show list view') : t('files', 'Show grid view'),
)
if (this.fileList) {
this.fileList.setGridView(isGridView);
}
},
_onDirectoryChanged: function (e) {
OC.Util.History.pushState({
// arghhhh, why is this not called "dir" !?
-5
View File
@@ -179,11 +179,6 @@
// storage info like free space / used space
},
updateRow: function($tr, fileInfo, options) {
// no-op, suppress re-rendering
return $tr
},
reload: function() {
this.showMask()
if (this._reloadCall) {
@@ -1555,7 +1555,7 @@ class ShareAPIController extends OCSController {
*/
private function parseDate(string $expireDate): \DateTime {
try {
$date = new \DateTime($expireDate);
$date = new \DateTime(trim($expireDate, "\""));
} catch (\Exception $e) {
throw new \Exception('Invalid date. Format must be YYYY-MM-DD');
}
@@ -95,20 +95,15 @@
</NcActionCheckbox>
<NcActionInput v-if="hasExpirationDate"
ref="expireDate"
v-tooltip.auto="{
content: errors.expireDate,
show: errors.expireDate,
trigger: 'manual'
}"
:is-native-picker="true"
:hide-label="true"
:class="{ error: errors.expireDate}"
:disabled="saving"
:lang="lang"
:value="share.expireDate"
value-type="format"
icon="icon-calendar-dark"
type="date"
:disabled-date="disabledDate"
@update:value="onExpirationChange">
:min="dateTomorrow"
:max="dateMaxEnforced"
@input="onExpirationChange">
{{ t('files_sharing', 'Enter a date') }}
</NcActionInput>
@@ -380,21 +375,20 @@ export default {
},
set(enabled) {
this.share.expireDate = enabled
? this.config.defaultInternalExpirationDateString !== ''
? this.config.defaultInternalExpirationDateString
: moment().format('YYYY-MM-DD')
? this.config.defaultInternalExpirationDate !== ''
? this.config.defaultInternalExpirationDate
: new Date()
: ''
},
},
dateMaxEnforced() {
if (!this.isRemote) {
return this.config.isDefaultInternalExpireDateEnforced
&& moment().add(1 + this.config.defaultInternalExpireDate, 'days')
} else {
return this.config.isDefaultRemoteExpireDateEnforced
&& moment().add(1 + this.config.defaultRemoteExpireDate, 'days')
if (!this.isRemote && this.config.isDefaultInternalExpireDateEnforced) {
return new Date(new Date().setDate(new Date().getDate() + 1 + this.config.defaultInternalExpireDate))
} else if (this.config.isDefaultRemoteExpireDateEnforced) {
return new Date(new Date().setDate(new Date().getDate() + 1 + this.config.defaultRemoteExpireDate))
}
return null
},
/**
@@ -1,15 +1,15 @@
<template>
<ul>
<SharingEntrySimple class="sharing-entry__internal"
<SharingEntrySimple ref="shareEntrySimple"
class="sharing-entry__internal"
:title="t('files_sharing', 'Internal link')"
:subtitle="internalLinkSubtitle">
<template #avatar>
<div class="avatar-external icon-external-white" />
</template>
<NcActionLink ref="copyButton"
:href="internalLink"
<NcActionLink :href="internalLink"
:aria-label="t('files_sharing', 'Copy internal link to clipboard')"
target="_blank"
:icon="copied && copySuccess ? 'icon-checkmark-color' : 'icon-clippy'"
@@ -84,8 +84,8 @@ export default {
async copyLink() {
try {
await this.$copyText(this.internalLink)
// focus and show the tooltip
this.$refs.copyButton.$el.focus()
// focus and show the tooltip (note: cannot set ref on NcActionLink)
this.$refs.shareEntrySimple.$refs.actionsComponent.$el.focus()
this.copySuccess = true
this.copied = true
} catch (error) {
@@ -98,20 +98,13 @@
</NcActionText>
<NcActionInput v-if="pendingExpirationDate"
v-model="share.expireDate"
v-tooltip.auto="{
content: errors.expireDate,
show: errors.expireDate,
trigger: 'manual',
defaultContainer: '#app-sidebar'
}"
class="share-link-expire-date"
:disabled="saving"
:lang="lang"
icon=""
:is-native-picker="true"
:hide-label="true"
type="date"
value-type="format"
:disabled-date="disabledDate">
:min="dateTomorrow"
:max="dateMaxEnforced">
<!-- let's not submit when picked, the user
might want to still edit or copy the password -->
{{ t('files_sharing', 'Enter a date') }}
@@ -220,22 +213,16 @@
</NcActionCheckbox>
<NcActionInput v-if="hasExpirationDate"
ref="expireDate"
v-tooltip.auto="{
content: errors.expireDate,
show: errors.expireDate,
trigger: 'manual',
defaultContainer: '#app-sidebar'
}"
:is-native-picker="true"
:hide-label="true"
class="share-link-expire-date"
:class="{ error: errors.expireDate}"
:disabled="saving"
:lang="lang"
:value="share.expireDate"
value-type="format"
icon="icon-calendar-dark"
type="date"
:disabled-date="disabledDate"
@update:value="onExpirationChange">
:min="dateTomorrow"
:max="dateMaxEnforced"
@input="onExpirationChange">
{{ t('files_sharing', 'Enter a date') }}
</NcActionInput>
@@ -435,20 +422,22 @@ export default {
|| !!this.share.expireDate
},
set(enabled) {
let dateString = moment(this.config.defaultExpirationDateString)
if (!dateString.isValid()) {
dateString = moment()
let defaultExpirationDate = this.config.defaultExpirationDate
if (!defaultExpirationDate) {
defaultExpirationDate = new Date()
}
this.share.state.expiration = enabled
? dateString.format('YYYY-MM-DD')
? defaultExpirationDate
: ''
console.debug('Expiration date status', enabled, this.share.expireDate)
},
},
dateMaxEnforced() {
return this.config.isDefaultExpireDateEnforced
&& moment().add(1 + this.config.defaultExpireDate, 'days')
if (this.config.isDefaultExpireDateEnforced) {
return new Date(new Date().setDate(new Date().getDate() + 1 + this.config.defaultExpireDate))
}
return null
},
/**
@@ -631,7 +620,7 @@ export default {
if (this.config.isDefaultExpireDateEnforced) {
// default is empty string if not set
// expiration is the share object key, not expireDate
shareDefaults.expiration = this.config.defaultExpirationDateString
shareDefaults.expiration = this.config.defaultExpirationDate
}
if (this.config.enableLinkPasswordByDefault) {
shareDefaults.password = await GeneratePassword()
@@ -29,7 +29,8 @@
{{ subtitle }}
</p>
</div>
<NcActions v-if="$slots['default']"
<NcActions ref="actionsComponent"
v-if="$slots['default']"
class="sharing-entry__actions"
menu-align="right"
:aria-expanded="ariaExpandedValue">
+5 -21
View File
@@ -97,7 +97,7 @@ export default {
},
dateTomorrow() {
return moment().add(1, 'days')
return new Date(new Date().setDate(new Date().getDate() + 1))
},
// Datepicker language
@@ -142,7 +142,7 @@ export default {
}
}
if (share.expirationDate) {
const date = moment(share.expirationDate)
const date = share.expirationDate
if (!date.isValid()) {
return false
}
@@ -151,16 +151,12 @@ export default {
},
/**
* ActionInput can be a little tricky to work with.
* Since we expect a string and not a Date,
* we need to process the value here
* Save given value to expireDate and trigger queueUpdate
*
* @param {Date} date js date to be parsed by moment.js
* @param {Date} date
*/
onExpirationChange(date) {
// format to YYYY-MM-DD
const value = moment(date).format('YYYY-MM-DD')
this.share.expireDate = value
this.share.expireDate = date
this.queueUpdate('expireDate')
},
@@ -318,17 +314,5 @@ export default {
debounceQueueUpdate: debounce(function(property) {
this.queueUpdate(property)
}, 500),
/**
* Returns which dates are disabled for the datepicker
*
* @param {Date} date date to check
* @return {boolean}
*/
disabledDate(date) {
const dateMoment = moment(date)
return (this.dateTomorrow && dateMoment.isBefore(this.dateTomorrow, 'day'))
|| (this.dateMaxEnforced && dateMoment.isSameOrAfter(this.dateMaxEnforced, 'day'))
},
},
}
+4 -5
View File
@@ -248,9 +248,9 @@ export default class Share {
}
/**
* Get the expiration date as a string format
* Get the expiration date
*
* @return {string}
* @return {Date|null}
* @readonly
* @memberof Share
*/
@@ -259,10 +259,9 @@ export default class Share {
}
/**
* Set the expiration date as a string format
* e.g. YYYY-MM-DD
* Set the expiration date
*
* @param {string} date the share expiration date
* @param {Date|null} date the share expiration date
* @memberof Share
*/
set expireDate(date) {
@@ -60,57 +60,45 @@ export default class Config {
}
/**
* Get the default link share expiration date as string
* Get the default link share expiration date
*
* @return {string}
* @return {Date|null}
* @readonly
* @memberof Config
*/
get defaultExpirationDateString() {
let expireDateString = ''
get defaultExpirationDate() {
if (this.isDefaultExpireDateEnabled) {
const date = window.moment.utc()
const expireAfterDays = this.defaultExpireDate
date.add(expireAfterDays, 'days')
expireDateString = date.format('YYYY-MM-DD')
return new Date(new Date().setDate(new Date().getDate() + this.defaultExpireDate))
}
return expireDateString
return null
}
/**
* Get the default internal expiration date as string
* Get the default internal expiration date
*
* @return {string}
* @return {Date|null}
* @readonly
* @memberof Config
*/
get defaultInternalExpirationDateString() {
let expireDateString = ''
get defaultInternalExpirationDate() {
if (this.isDefaultInternalExpireDateEnabled) {
const date = window.moment.utc()
const expireAfterDays = this.defaultInternalExpireDate
date.add(expireAfterDays, 'days')
expireDateString = date.format('YYYY-MM-DD')
return new Date(new Date().setDate(new Date().getDate() + this.defaultInternalExpireDate))
}
return expireDateString
return null
}
/**
* Get the default remote expiration date as string
* Get the default remote expiration date
*
* @return {string}
* @return {Date|null}
* @readonly
* @memberof Config
*/
get defaultRemoteExpirationDateString() {
let expireDateString = ''
if (this.isDefaultRemoteExpireDateEnabled) {
const date = window.moment.utc()
const expireAfterDays = this.defaultRemoteExpireDate
date.add(expireAfterDays, 'days')
expireDateString = date.format('YYYY-MM-DD')
return new Date(new Date().setDate(new Date().getDate() + this.defaultRemoteExpireDate))
}
return expireDateString
return null
}
/**
@@ -190,6 +178,17 @@ export default class Config {
return OC.appConfig.core.defaultInternalExpireDateEnabled === true
}
/**
* Is there a default expiration date for new remote shares ?
*
* @return {boolean}
* @readonly
* @memberof Config
*/
get isDefaultRemoteExpireDateEnabled() {
return OC.appConfig.core.defaultRemoteExpireDateEnabled === true
}
/**
* Are users on this server allowed to send shares to other servers ?
*
@@ -389,6 +389,9 @@ class UsersController extends AUserData {
}
$generatePasswordResetToken = false;
if (strlen($password) > 469) {
throw new OCSException('Invalid password value', 101);
}
if ($password === '') {
if ($email === '') {
throw new OCSException('To send a password link to the user an email address is required.', 108);
@@ -882,6 +885,9 @@ class UsersController extends AUserData {
break;
case self::USER_FIELD_PASSWORD:
try {
if (strlen($value) > 469) {
throw new OCSException('Invalid password value', 102);
}
if (!$targetUser->canChangePassword()) {
throw new OCSException('Setting the password is not supported by the users backend', 103);
}
@@ -379,7 +379,7 @@ class CheckSetupController extends Controller {
return true;
}
// there are two different memcached modules for PHP
// there are two different memcache modules for PHP
// we only support memcached and not memcache
// https://code.google.com/p/memcached/wiki/PHPClientComparison
return !(!extension_loaded('memcached') && extension_loaded('memcache'));
@@ -392,7 +392,7 @@ class CheckSetupController extends Controller {
*/
private function isSettimelimitAvailable() {
if (function_exists('set_time_limit')
&& strpos(@ini_get('disable_functions'), 'set_time_limit') === false) {
&& strpos(ini_get('disable_functions'), 'set_time_limit') === false) {
return true;
}
@@ -819,12 +819,12 @@ Raw output
$tempPath = sys_get_temp_dir();
if (!is_dir($tempPath)) {
$this->logger->error('Error while checking the temporary PHP path - it was not properly set to a directory. value: ' . $tempPath);
$this->logger->error('Error while checking the temporary PHP path - it was not properly set to a directory. Returned value: ' . $tempPath);
return false;
}
$freeSpaceInTemp = disk_free_space($tempPath);
$freeSpaceInTemp = function_exists('disk_free_space') ? disk_free_space($tempPath) : false;
if ($freeSpaceInTemp === false) {
$this->logger->error('Error while checking the available disk space of temporary PHP path - no free disk space returned. temporary path: ' . $tempPath);
$this->logger->error('Error while checking the available disk space of temporary PHP path or no free disk space returned. Temporary path: ' . $tempPath);
return false;
}
@@ -38,10 +38,11 @@ class SecurityTxtHandler implements IHandler {
}
$response = "Contact: https://hackerone.com/nextcloud
Expires: 2021-12-31T23:00:00.000Z
Expires: 2023-04-31T23:00:00.000Z
Acknowledgments: https://hackerone.com/nextcloud/thanks
Acknowledgments: https://github.com/nextcloud/security-advisories/security/advisories
Policy: https://hackerone.com/nextcloud";
Policy: https://hackerone.com/nextcloud
Preferred-Languages: en";
return new GenericResponse(new TextPlainResponse($response, 200));
}
@@ -56,6 +56,7 @@
ref="newuserpassword"
v-model="newUser.password"
:minlength="minPasswordLength"
:maxlength="469"
:placeholder="t('settings', 'Password')"
:required="newUser.mailAddress===''"
autocapitalize="none"
+3
View File
@@ -31,6 +31,9 @@
<pre-migration>
<step>OCA\Theming\Migration\MigrateUserConfig</step>
</pre-migration>
<post-migration>
<step>OCA\Theming\Migration\InitBackgroundImagesMigration</step>
</post-migration>
</repair-steps>
<commands>
+19 -15
View File
@@ -1,5 +1,6 @@
:root {
--color-main-background: #ffffff;
--color-main-background-not-plain: #0082c9;
--color-main-background-rgb: 255,255,255;
--color-main-background-translucent: rgba(var(--color-main-background-rgb), .97);
--color-main-background-blur: rgba(var(--color-main-background-rgb), .8);
@@ -10,23 +11,10 @@
--color-background-darker: #dbdbdb;
--color-placeholder-light: #e6e6e6;
--color-placeholder-dark: #cccccc;
--color-primary: #0082c9;
--color-primary-text: #ffffff;
--color-primary-hover: #329bd3;
--color-primary-light: #e5f2f9;
--color-primary-light-text: #0082c9;
--color-primary-light-hover: #dbe7ee;
--color-primary-text-dark: #ededed;
--color-primary-element: #0082c9;
--color-primary-element-text: #ffffff;
--color-primary-element-hover: #329bd3;
--color-primary-element-light: #e5f2f9;
--color-primary-element-light-text: #0082c9;
--color-primary-element-light-hover: #dbe7ee;
--color-primary-element-text-dark: #ededed;
--gradient-primary-background: linear-gradient(40deg, var(--color-primary) 0%, var(--color-primary-hover) 100%);
--color-main-text: #222222;
--color-text-maxcontrast: #767676;
--color-text-maxcontrast-default: #767676;
--color-text-maxcontrast-background-blur: #646464;
--color-text-light: #222222;
--color-text-lighter: #767676;
--color-scrollbar: rgba(34,34,34, .15);
@@ -68,4 +56,20 @@
--background-invert-if-dark: no;
--background-invert-if-bright: invert(100%);
--image-main-background: url('/core/img/app-background.jpg');
--color-primary: #00639a;
--color-primary-default: #0082c9;
--color-primary-text: #ffffff;
--color-primary-hover: #3282ae;
--color-primary-light: #e5eff4;
--color-primary-light-text: #00273d;
--color-primary-light-hover: #dbe4e9;
--color-primary-text-dark: #ededed;
--color-primary-element: #00639a;
--color-primary-element-text: #ffffff;
--color-primary-element-hover: #3282ae;
--color-primary-element-light: #e5eff4;
--color-primary-element-light-text: #00273d;
--color-primary-element-light-hover: #dbe4e9;
--color-primary-element-text-dark: #ededed;
--gradient-primary-background: linear-gradient(40deg, var(--color-primary) 0%, var(--color-primary-hover) 100%);
}
+1 -1
View File
@@ -89,7 +89,7 @@
margin-top: 10px;
margin-bottom: 20px;
cursor: pointer;
background-color: var(--color-primary);
background-color: var(--color-primary-default);
background-image: var(--image-background, var(--image-background-plain, url("../../../core/img/app-background.jpg"), linear-gradient(40deg, #0082c9 0%, #30b6ff 100%)));
}
#theming #theming-preview #theming-preview-logo {
+2 -2
View File
@@ -100,7 +100,7 @@
margin-top: 10px;
margin-bottom: 20px;
cursor: pointer;
background-color: var(--color-primary);
background-color: var(--color-primary-default);
background-image: var(--image-background, var(--image-background-plain, url('../../../core/img/app-background.jpg'), linear-gradient(40deg, #0082c9 0%, #30b6ff 100%)));
#theming-preview-logo {
@@ -145,4 +145,4 @@
svg, img {
transition: 500ms filter linear;
}
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 422 KiB

After

Width:  |  Height:  |  Size: 419 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 369 KiB

After

Width:  |  Height:  |  Size: 410 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 419 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 382 KiB

After

Width:  |  Height:  |  Size: 428 KiB

@@ -34,6 +34,7 @@ use OCA\Theming\AppInfo\Application;
use OCA\Theming\ITheme;
use OCA\Theming\Service\BackgroundService;
use OCA\Theming\Service\ThemesService;
use OCA\Theming\ThemingDefaults;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\FileDisplayResponse;
@@ -53,6 +54,7 @@ class UserThemeController extends OCSController {
private IConfig $config;
private IUserSession $userSession;
private ThemesService $themesService;
private ThemingDefaults $themingDefaults;
private BackgroundService $backgroundService;
/**
@@ -63,11 +65,13 @@ class UserThemeController extends OCSController {
IConfig $config,
IUserSession $userSession,
ThemesService $themesService,
ThemingDefaults $themingDefaults,
BackgroundService $backgroundService) {
parent::__construct($appName, $request);
$this->config = $config;
$this->userSession = $userSession;
$this->themesService = $themesService;
$this->themingDefaults = $themingDefaults;
$this->backgroundService = $backgroundService;
$this->userId = $userSession->getUser()->getUID();
}
@@ -152,7 +156,8 @@ class UserThemeController extends OCSController {
* @NoAdminRequired
*/
public function setBackground(string $type = 'default', string $value = ''): JSONResponse {
$currentVersion = (int)$this->config->getUserValue($this->userId, Application::APP_ID, 'backgroundVersion', '0');
$currentVersion = (int)$this->config->getUserValue($this->userId, Application::APP_ID, 'userCacheBuster', '0');
try {
switch ($type) {
case 'shipped':
@@ -175,12 +180,14 @@ class UserThemeController extends OCSController {
} catch (\Throwable $e) {
return new JSONResponse(['error' => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR);
}
$currentVersion++;
$this->config->setUserValue($this->userId, Application::APP_ID, 'backgroundVersion', (string)$currentVersion);
$this->config->setUserValue($this->userId, Application::APP_ID, 'userCacheBuster', (string)$currentVersion);
return new JSONResponse([
'type' => $type,
'value' => $value,
'version' => $this->config->getUserValue($this->userId, Application::APP_ID, 'backgroundVersion', $currentVersion)
'version' => $currentVersion,
]);
}
}
+21 -11
View File
@@ -66,14 +66,13 @@ class ImageManager {
IURLGenerator $urlGenerator,
ICacheFactory $cacheFactory,
ILogger $logger,
ITempManager $tempManager
) {
ITempManager $tempManager) {
$this->config = $config;
$this->appData = $appData;
$this->urlGenerator = $urlGenerator;
$this->cacheFactory = $cacheFactory;
$this->logger = $logger;
$this->tempManager = $tempManager;
$this->appData = $appData;
}
public function getImageUrl(string $key, bool $useSvg = true): string {
@@ -106,10 +105,12 @@ class ImageManager {
*/
public function getImage(string $key, bool $useSvg = true): ISimpleFile {
$logo = $this->config->getAppValue('theming', $key . 'Mime', '');
$folder = $this->appData->getFolder('images');
$folder = $this->getRootFolder()->getFolder('images');
if ($logo === '' || !$folder->fileExists($key)) {
throw new NotFoundException();
}
if (!$useSvg && $this->shouldReplaceIcons()) {
if (!$folder->fileExists($key . '.png')) {
try {
@@ -127,6 +128,7 @@ class ImageManager {
return $folder->getFile($key . '.png');
}
}
return $folder->getFile($key);
}
@@ -158,9 +160,9 @@ class ImageManager {
public function getCacheFolder(): ISimpleFolder {
$cacheBusterValue = $this->config->getAppValue('theming', 'cachebuster', '0');
try {
$folder = $this->appData->getFolder($cacheBusterValue);
$folder = $this->getRootFolder()->getFolder($cacheBusterValue);
} catch (NotFoundException $e) {
$folder = $this->appData->newFolder($cacheBusterValue);
$folder = $this->getRootFolder()->newFolder($cacheBusterValue);
$this->cleanup();
}
return $folder;
@@ -202,13 +204,13 @@ class ImageManager {
public function delete(string $key): void {
/* ignore exceptions, since we don't want to fail hard if something goes wrong during cleanup */
try {
$file = $this->appData->getFolder('images')->getFile($key);
$file = $this->getRootFolder()->getFolder('images')->getFile($key);
$file->delete();
} catch (NotFoundException $e) {
} catch (NotPermittedException $e) {
}
try {
$file = $this->appData->getFolder('images')->getFile($key . '.png');
$file = $this->getRootFolder()->getFolder('images')->getFile($key . '.png');
$file->delete();
} catch (NotFoundException $e) {
} catch (NotPermittedException $e) {
@@ -219,9 +221,9 @@ class ImageManager {
$this->delete($key);
try {
$folder = $this->appData->getFolder('images');
$folder = $this->getRootFolder()->getFolder('images');
} catch (NotFoundException $e) {
$folder = $this->appData->newFolder('images');
$folder = $this->getRootFolder()->newFolder('images');
}
$target = $folder->newFile($key);
@@ -288,7 +290,7 @@ class ImageManager {
*/
public function cleanup() {
$currentFolder = $this->getCacheFolder();
$folders = $this->appData->getDirectoryListing();
$folders = $this->getRootFolder()->getDirectoryListing();
foreach ($folders as $folder) {
if ($folder->getName() !== 'images' && $folder->getName() !== $currentFolder->getName()) {
$folder->delete();
@@ -316,4 +318,12 @@ class ImageManager {
$cache->set('shouldReplaceIcons', $value);
return $value;
}
private function getRootFolder(): ISimpleFolder {
try {
return $this->appData->getFolder('global');
} catch (NotFoundException $e) {
return $this->appData->newFolder('global');
}
}
}
@@ -0,0 +1,123 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2022 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace OCA\Theming\Jobs;
use OCA\Theming\AppInfo\Application;
use OCP\App\IAppManager;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\IJobList;
use OCP\BackgroundJob\QueuedJob;
use OCP\Files\AppData\IAppDataFactory;
use OCP\Files\NotFoundException;
use OCP\Files\NotPermittedException;
use OCP\Files\SimpleFS\ISimpleFolder;
use OCP\IConfig;
class MigrateBackgroundImages extends QueuedJob {
public const TIME_SENSITIVE = 0;
private IConfig $config;
private IAppManager $appManager;
private IAppDataFactory $appDataFactory;
private IJobList $jobList;
public function __construct(ITimeFactory $time, IAppDataFactory $appDataFactory, IConfig $config, IAppManager $appManager, IJobList $jobList) {
parent::__construct($time);
$this->config = $config;
$this->appManager = $appManager;
$this->appDataFactory = $appDataFactory;
$this->jobList = $jobList;
}
protected function run($argument): void {
if (!$this->appManager->isEnabledForUser('dashboard')) {
return;
}
$dashboardData = $this->appDataFactory->get('dashboard');
$userIds = $this->config->getUsersForUserValue('theming', 'background', 'custom');
$notSoFastMode = \count($userIds) > 5000;
$reTrigger = false;
$processed = 0;
foreach ($userIds as $userId) {
try {
// precondition
if ($notSoFastMode) {
if ($this->config->getUserValue($userId, 'theming', 'background-migrated', '0') === '1') {
// already migrated
continue;
}
$reTrigger = true;
}
// migration
$file = $dashboardData->getFolder($userId)->getFile('background.jpg');
$targetDir = $this->getUserFolder($userId);
if (!$targetDir->fileExists('background.jpg')) {
$targetDir->newFile('background.jpg', $file->getContent());
}
$file->delete();
} catch (NotFoundException|NotPermittedException $e) {
}
// capture state
if ($notSoFastMode) {
$this->config->setUserValue($userId, 'theming', 'background-migrated', '1');
$processed++;
}
if ($processed > 4999) {
break;
}
}
if ($reTrigger) {
$this->jobList->add(self::class);
}
}
/**
* Get the root location for users theming data
*/
protected function getUserFolder(string $userId): ISimpleFolder {
$themingData = $this->appDataFactory->get(Application::APP_ID);
try {
$rootFolder = $themingData->getFolder('users');
} catch (NotFoundException $e) {
$rootFolder = $themingData->newFolder('users');
}
try {
return $rootFolder->getFolder($userId);
} catch (NotFoundException $e) {
return $rootFolder->newFolder($userId);
}
}
}
@@ -89,11 +89,6 @@ class BeforeTemplateRenderedListener implements IEventListener {
$this->config->getUserValue($userId, Application::APP_ID, 'background', 'default'),
);
$this->initialState->provideInitialState(
'backgroundVersion',
$this->config->getUserValue($userId, Application::APP_ID, 'backgroundVersion', 0),
);
$this->initialState->provideInitialState(
'themingDefaultBackground',
$this->config->getAppValue('theming', 'backgroundMime', ''),
@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2022 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace OCA\Theming\Migration;
use OCA\Theming\Jobs\MigrateBackgroundImages;
use OCP\BackgroundJob\IJobList;
use OCP\Migration\IOutput;
class InitBackgroundImagesMigration implements \OCP\Migration\IRepairStep {
private IJobList $jobList;
public function __construct(IJobList $jobList) {
$this->jobList = $jobList;
}
public function getName() {
return 'Initialize migration of background images from dashboard to theming app';
}
public function run(IOutput $output) {
$this->jobList->add(MigrateBackgroundImages::class);
}
}
+45 -10
View File
@@ -30,6 +30,7 @@ namespace OCA\Theming\Service;
use InvalidArgumentException;
use OC\User\NoUserException;
use OCA\Theming\AppInfo\Application;
use OCP\Files\AppData\IAppDataFactory;
use OCP\Files\File;
use OCP\Files\IAppData;
use OCP\Files\IRootFolder;
@@ -44,92 +45,111 @@ use OCP\PreConditionNotMetException;
class BackgroundService {
// true when the background is bright and need dark icons
public const THEMING_MODE_DARK = 'dark';
public const DEFAULT_COLOR = '#0082c9';
public const DEFAULT_ACCESSIBLE_COLOR = '#00639a';
public const SHIPPED_BACKGROUNDS = [
'anatoly-mikhaltsov-butterfly-wing-scale.jpg' => [
'attribution' => 'Butterfly wing scale (Anatoly Mikhaltsov, CC BY-SA)',
'attribution_url' => 'https://commons.wikimedia.org/wiki/File:%D0%A7%D0%B5%D1%88%D1%83%D0%B9%D0%BA%D0%B8_%D0%BA%D1%80%D1%8B%D0%BB%D0%B0_%D0%B1%D0%B0%D0%B1%D0%BE%D1%87%D0%BA%D0%B8.jpg',
'primary_color' => '#a53c17',
],
'bernie-cetonia-aurata-take-off-composition.jpg' => [
'attribution' => 'Cetonia aurata take off composition (Bernie, Public Domain)',
'attribution_url' => 'https://commons.wikimedia.org/wiki/File:Cetonia_aurata_take_off_composition_05172009.jpg',
'theming' => self::THEMING_MODE_DARK,
'primary_color' => '#56633d',
],
'dejan-krsmanovic-ribbed-red-metal.jpg' => [
'attribution' => 'Ribbed red metal (Dejan Krsmanovic, CC BY)',
'attribution_url' => 'https://www.flickr.com/photos/dejankrsmanovic/42971456774/',
'primary_color' => '#9c4236',
],
'eduardo-neves-pedra-azul.jpg' => [
'attribution' => 'Pedra azul milky way (Eduardo Neves, CC BY-SA)',
'attribution_url' => 'https://commons.wikimedia.org/wiki/File:Pedra_Azul_Milky_Way.jpg',
'primary_color' => '#4f6071',
],
'european-space-agency-barents-bloom.jpg' => [
'attribution' => 'Barents bloom (European Space Agency, CC BY-SA)',
'attribution_url' => 'https://www.esa.int/ESA_Multimedia/Images/2016/08/Barents_bloom',
'primary_color' => '#396475',
],
'hannes-fritz-flippity-floppity.jpg' => [
'attribution' => 'Flippity floppity (Hannes Fritz, CC BY-SA)',
'attribution_url' => 'http://hannes.photos/flippity-floppity',
'primary_color' => '#98415a',
],
'hannes-fritz-roulette.jpg' => [
'attribution' => 'Roulette (Hannes Fritz, CC BY-SA)',
'attribution_url' => 'http://hannes.photos/roulette',
'primary_color' => '#845334',
],
'hannes-fritz-sea-spray.jpg' => [
'attribution' => 'Sea spray (Hannes Fritz, CC BY-SA)',
'attribution_url' => 'http://hannes.photos/sea-spray',
'primary_color' => '#4f6071',
],
'kamil-porembinski-clouds.jpg' => [
'attribution' => 'Clouds (Kamil Porembiński, CC BY-SA)',
'attribution_url' => 'https://www.flickr.com/photos/paszczak000/8715851521/',
'primary_color' => self::DEFAULT_COLOR,
],
'bernard-spragg-new-zealand-fern.jpg' => [
'attribution' => 'New zealand fern (Bernard Spragg, CC0)',
'attribution_url' => 'https://commons.wikimedia.org/wiki/File:NZ_Fern.(Blechnum_chambersii)_(11263534936).jpg',
'primary_color' => '#316b26',
],
'rawpixel-pink-tapioca-bubbles.jpg' => [
'attribution' => 'Pink tapioca bubbles (Rawpixel, CC BY)',
'attribution_url' => 'https://www.flickr.com/photos/byrawpixel/27665140298/in/photostream/',
'theming' => self::THEMING_MODE_DARK,
'primary_color' => '#7b4e7e',
],
'nasa-waxing-crescent-moon.jpg' => [
'attribution' => 'Waxing crescent moon (NASA, Public Domain)',
'attribution_url' => 'https://www.nasa.gov/image-feature/a-waxing-crescent-moon',
'primary_color' => '#005ac1',
],
'tommy-chau-already.jpg' => [
'attribution' => 'Cityscape (Tommy Chau, CC BY)',
'attribution_url' => 'https://www.flickr.com/photos/90975693@N05/16910999368',
'primary_color' => '#6a2af4',
],
'tommy-chau-lion-rock-hill.jpg' => [
'attribution' => 'Lion rock hill (Tommy Chau, CC BY)',
'attribution_url' => 'https://www.flickr.com/photos/90975693@N05/17136440246',
'theming' => self::THEMING_MODE_DARK,
'primary_color' => '#7f4f70',
],
'lali-masriera-yellow-bricks.jpg' => [
'attribution' => 'Yellow bricks (Lali Masriera, CC BY)',
'attribution_url' => 'https://www.flickr.com/photos/visualpanic/3982464447',
'theming' => self::THEMING_MODE_DARK,
]
'primary_color' => '#7f5700',
],
];
private IRootFolder $rootFolder;
private IAppData $appData;
private IConfig $config;
private string $userId;
private IAppDataFactory $appDataFactory;
public function __construct(
IRootFolder $rootFolder,
IAppData $appData,
IConfig $config,
?string $userId
) {
public function __construct(IRootFolder $rootFolder,
IAppData $appData,
IConfig $config,
?string $userId,
IAppDataFactory $appDataFactory) {
if ($userId === null) {
return;
}
$this->rootFolder = $rootFolder;
$this->appData = $appData;
$this->config = $config;
$this->userId = $userId;
$this->appData = $appData;
$this->appDataFactory = $appDataFactory;
}
public function setDefaultBackground(): void {
@@ -147,12 +167,15 @@ class BackgroundService {
public function setFileBackground($path): void {
$this->config->setUserValue($this->userId, Application::APP_ID, 'background', 'custom');
$userFolder = $this->rootFolder->getUserFolder($this->userId);
/** @var File $file */
$file = $userFolder->get($path);
$image = new \OCP\Image();
if ($image->loadFromFileHandle($file->fopen('r')) === false) {
throw new InvalidArgumentException('Invalid image file');
}
$this->getAppDataFolder()->newFile('background.jpg', $file->fopen('r'));
}
@@ -176,20 +199,32 @@ class BackgroundService {
try {
return $this->getAppDataFolder()->getFile('background.jpg');
} catch (NotFoundException | NotPermittedException $e) {
try {
// Fallback can be removed in 26
$dashboardFolder = $this->appDataFactory->get('dashboard');
return $dashboardFolder->getFolder($this->userId)->getFile('background.jpg');
} catch (\Throwable $t) {}
}
}
return null;
}
/**
* Storing the data in appdata/theming/users/USERID
*
* @return ISimpleFolder
* @throws NotPermittedException
*/
private function getAppDataFolder(): ISimpleFolder {
try {
return $this->appData->getFolder($this->userId);
$rootFolder = $this->appData->getFolder('users');
} catch (NotFoundException $e) {
return $this->appData->newFolder($this->userId);
$rootFolder = $this->appData->newFolder('users');
}
try {
return $rootFolder->getFolder($this->userId);
} catch (NotFoundException $e) {
return $rootFolder->newFolder($this->userId);
}
}
}
@@ -55,6 +55,7 @@ class JSDataService implements \JsonSerializable {
'url' => $this->themingDefaults->getBaseUrl(),
'slogan' => $this->themingDefaults->getSlogan(),
'color' => $this->themingDefaults->getColorPrimary(),
'defaultColor' => $this->themingDefaults->getDefaultColorPrimary(),
'imprintUrl' => $this->themingDefaults->getImprintUrl(),
'privacyUrl' => $this->themingDefaults->getPrivacyUrl(),
'inverted' => $this->util->invertTextColor($this->themingDefaults->getColorPrimary()),
@@ -22,8 +22,11 @@
*/
namespace OCA\Theming\Service;
use OCA\Theming\AppInfo\Application;
use OCA\Theming\Themes\DefaultTheme;
use OCP\IConfig;
use OCP\IURLGenerator;
use OCP\IUserSession;
use OCP\Util;
class ThemeInjectionService {
@@ -31,13 +34,23 @@ class ThemeInjectionService {
private IURLGenerator $urlGenerator;
private ThemesService $themesService;
private DefaultTheme $defaultTheme;
private IConfig $config;
private ?string $userId;
public function __construct(IURLGenerator $urlGenerator,
ThemesService $themesService,
DefaultTheme $defaultTheme) {
DefaultTheme $defaultTheme,
IConfig $config,
IUserSession $userSession) {
$this->urlGenerator = $urlGenerator;
$this->themesService = $themesService;
$this->defaultTheme = $defaultTheme;
$this->config = $config;
if ($userSession->getUser() !== null) {
$this->userId = $userSession->getUser()->getUID();
} else {
$this->userId = null;
}
}
public function injectHeaders() {
@@ -50,13 +63,13 @@ class ThemeInjectionService {
// Default theme fallback
$this->addThemeHeader($defaultTheme->getId());
// Themes applied by media queries
foreach($mediaThemes as $theme) {
$this->addThemeHeader($theme->getId(), true, $theme->getMediaQuery());
}
// Themes
// Themes
foreach($this->themesService->getThemes() as $theme) {
// Ignore default theme as already processed first
if ($theme->getId() === $this->defaultTheme->getId()) {
@@ -68,15 +81,24 @@ class ThemeInjectionService {
/**
* Inject theme header into rendered page
*
*
* @param string $themeId the theme ID
* @param bool $plain request the :root syntax
* @param string $media media query to use in the <link> element
*/
private function addThemeHeader(string $themeId, bool $plain = true, string $media = null) {
$cacheBuster = $this->config->getAppValue('theming', 'cachebuster', '0');
if ($this->userId !== null) {
// need to bust the cache for the CSS file when the user background changed as its
// URL is served in those files
$userCacheBuster = $this->config->getUserValue($this->userId, Application::APP_ID, 'userCacheBuster', '0');
$cacheBuster .= $this->userId . '_' . $userCacheBuster;
}
$linkToCSS = $this->urlGenerator->linkToRoute('theming.Theming.getThemeStylesheet', [
'themeId' => $themeId,
'plain' => $plain,
'v' => substr(sha1($cacheBuster), 0, 8),
]);
Util::addHeader('link', [
'rel' => 'stylesheet',
+2 -2
View File
@@ -87,9 +87,9 @@ class ThemesService {
}
/** @var ITheme[] */
$themes = array_map(function($themeId) {
$themes = array_filter(array_map(function($themeId) {
return $this->getThemes()[$themeId];
}, $themesIds);
}, $themesIds));
// Filtering all themes with the same type
$filteredThemes = array_filter($themes, function(ITheme $t) use ($theme) {
+1 -1
View File
@@ -75,7 +75,7 @@ class Admin implements IDelegatedSettings {
'name' => $this->themingDefaults->getEntity(),
'url' => $this->themingDefaults->getBaseUrl(),
'slogan' => $this->themingDefaults->getSlogan(),
'color' => $this->themingDefaults->getColorPrimary(),
'color' => $this->themingDefaults->getDefaultColorPrimary(),
'uploadLogoRoute' => $this->urlGenerator->linkToRoute('theming.Theming.uploadImage'),
'canThemeIcons' => $this->imageManager->shouldReplaceIcons(),
'iconDocs' => $this->urlGenerator->linkToDocs('admin-theming-icons'),
@@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2022 Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Theming\Themes;
use OCA\Theming\Util;
trait CommonThemeTrait {
public Util $util;
/**
* Generate primary-related variables
* This is shared between multiple themes because colorMainBackground and colorMainText
* will change in between.
*/
protected function generatePrimaryVariables(string $colorMainBackground, string $colorMainText): array {
$colorPrimaryLight = $this->util->mix($this->primaryColor, $colorMainBackground, -80);
$colorPrimaryElement = $this->util->elementColor($this->primaryColor);
$colorPrimaryElementLight = $this->util->mix($colorPrimaryElement, $colorMainBackground, -80);
// primary related colours
return [
'--color-primary' => $this->primaryColor,
'--color-primary-default' => $this->defaultPrimaryColor,
'--color-primary-text' => $this->util->invertTextColor($this->primaryColor) ? '#000000' : '#ffffff',
'--color-primary-hover' => $this->util->mix($this->primaryColor, $colorMainBackground, 60),
'--color-primary-light' => $colorPrimaryLight,
'--color-primary-light-text' => $this->util->mix($this->primaryColor, $this->util->invertTextColor($colorPrimaryLight) ? '#000000' : '#ffffff', -20),
'--color-primary-light-hover' => $this->util->mix($colorPrimaryLight, $colorMainText, 90),
'--color-primary-text-dark' => $this->util->darken($this->util->invertTextColor($this->primaryColor) ? '#000000' : '#ffffff', 7),
// used for buttons, inputs...
'--color-primary-element' => $colorPrimaryElement,
'--color-primary-element-text' => $this->util->invertTextColor($colorPrimaryElement) ? '#000000' : '#ffffff',
'--color-primary-element-hover' => $this->util->mix($colorPrimaryElement, $colorMainBackground, 60),
'--color-primary-element-light' => $colorPrimaryElementLight,
'--color-primary-element-light-text' => $this->util->mix($colorPrimaryElement, $this->util->invertTextColor($colorPrimaryElementLight) ? '#000000' : '#ffffff', -20),
'--color-primary-element-light-hover' => $this->util->mix($colorPrimaryElementLight, $colorMainText, 90),
'--color-primary-element-text-dark' => $this->util->darken($this->util->invertTextColor($colorPrimaryElement) ? '#000000' : '#ffffff', 7),
// to use like this: background-image: var(--gradient-primary-background);
'--gradient-primary-background' => 'linear-gradient(40deg, var(--color-primary) 0%, var(--color-primary-hover) 100%)',
];
}
}
@@ -49,42 +49,51 @@ class DarkHighContrastTheme extends DarkTheme implements ITheme {
}
/**
* Try to keep this consistent with HighContrastTheme
* Keep this consistent with other HighContrast Themes
*/
public function getCSSVariables(): array {
$variables = parent::getCSSVariables();
$defaultVariables = parent::getCSSVariables();
$colorMainText = '#ffffff';
$colorMainBackground = '#000000';
$colorMainBackgroundRGB = join(',', $this->util->hexToRGB($colorMainBackground));
$variables['--color-main-background'] = $colorMainBackground;
$variables['--color-main-background-translucent'] = 'rgba(var(--color-main-background-rgb), .1)';
$variables['--color-main-text'] = $colorMainText;
return array_merge(
$defaultVariables,
$this->generatePrimaryVariables($colorMainBackground, $colorMainText),
[
'--color-main-background' => $colorMainBackground,
'--color-main-background-rgb' => $colorMainBackgroundRGB,
'--color-main-background-translucent' => 'rgba(var(--color-main-background-rgb), 1)',
'--color-main-text' => $colorMainText,
$variables['--color-background-dark'] = $this->util->lighten($colorMainBackground, 30);
$variables['--color-background-darker'] = $this->util->lighten($colorMainBackground, 30);
'--color-background-dark' => $this->util->lighten($colorMainBackground, 30),
'--color-background-darker' => $this->util->lighten($colorMainBackground, 30),
$variables['--color-placeholder-light'] = $this->util->lighten($colorMainBackground, 30);
$variables['--color-placeholder-dark'] = $this->util->lighten($colorMainBackground, 45);
'--color-main-background-blur' => $colorMainBackground,
'--filter-background-blur' => 'none',
$variables['--color-text-maxcontrast'] = $colorMainText;
$variables['--color-text-light'] = $colorMainText;
$variables['--color-text-lighter'] = $colorMainText;
'--color-placeholder-light' => $this->util->lighten($colorMainBackground, 30),
'--color-placeholder-dark' => $this->util->lighten($colorMainBackground, 45),
$variables['--color-scrollbar'] = $this->util->lighten($colorMainBackground, 35);
'--color-text-maxcontrast' => $colorMainText,
'--color-text-maxcontrast-background-blur' => $colorMainText,
'--color-text-light' => $colorMainText,
'--color-text-lighter' => $colorMainText,
// used for the icon loading animation
$variables['--color-loading-light'] = '#000000';
$variables['--color-loading-dark'] = '#dddddd';
'--color-scrollbar' => $this->util->lighten($colorMainBackground, 35),
// used for the icon loading animation
'--color-loading-light' => '#000000',
'--color-loading-dark' => '#dddddd',
$variables['--color-box-shadow-rgb'] = 'var(--color-main-text)';
$variables['--color-box-shadow'] = 'var(--color-main-text)';
'--color-box-shadow-rgb' => $colorMainText,
'--color-box-shadow' => $colorMainText,
$variables['--color-border'] = $this->util->lighten($colorMainBackground, 50);
$variables['--color-border-dark'] = $this->util->lighten($colorMainBackground, 50);
return $variables;
'--color-border' => $this->util->lighten($colorMainBackground, 50),
'--color-border-dark' => $this->util->lighten($colorMainBackground, 50),
]
);
}
public function getCustomCss(): string {
+31 -29
View File
@@ -54,45 +54,47 @@ class DarkTheme extends DefaultTheme implements ITheme {
$colorMainText = '#D8D8D8';
$colorMainBackground = '#171717';
$colorMainBackgroundRGB = join(',', $this->util->hexToRGB($colorMainBackground));
$colorTextMaxcontrast = $this->util->darken($colorMainText, 30);
$colorBoxShadow = $this->util->darken($colorMainBackground, 70);
$colorBoxShadowRGB = join(',', $this->util->hexToRGB($colorBoxShadow));
$colorPrimaryLight = $this->util->mix($this->primaryColor, $colorMainBackground, -80);
return array_merge($defaultVariables, [
'--color-main-text' => $colorMainText,
'--color-main-background' => $colorMainBackground,
'--color-main-background-rgb' => $colorMainBackgroundRGB,
return array_merge(
$defaultVariables,
$this->generatePrimaryVariables($colorMainBackground, $colorMainText),
[
'--color-main-text' => $colorMainText,
'--color-main-background' => $colorMainBackground,
'--color-main-background-rgb' => $colorMainBackgroundRGB,
'--color-scrollbar' => $this->util->lighten($colorMainBackground, 15),
'--color-scrollbar' => $this->util->lighten($colorMainBackground, 15),
'--color-background-hover' => $this->util->lighten($colorMainBackground, 4),
'--color-background-dark' => $this->util->lighten($colorMainBackground, 7),
'--color-background-darker' => $this->util->lighten($colorMainBackground, 14),
'--color-background-hover' => $this->util->lighten($colorMainBackground, 4),
'--color-background-dark' => $this->util->lighten($colorMainBackground, 7),
'--color-background-darker' => $this->util->lighten($colorMainBackground, 14),
'--color-placeholder-light' => $this->util->lighten($colorMainBackground, 10),
'--color-placeholder-dark' => $this->util->lighten($colorMainBackground, 20),
'--color-placeholder-light' => $this->util->lighten($colorMainBackground, 10),
'--color-placeholder-dark' => $this->util->lighten($colorMainBackground, 20),
'--color-primary-hover' => $this->util->mix($this->primaryColor, $colorMainBackground, 60),
'--color-primary-light' => $colorPrimaryLight,
'--color-primary-light-hover' => $this->util->mix($colorPrimaryLight, $colorMainText, 90),
'--color-primary-element' => $this->util->elementColor($this->primaryColor, false),
'--color-primary-element-hover' => $this->util->mix($this->util->elementColor($this->primaryColor, false), $colorMainBackground, 80),
'--color-primary-element-light' => $this->util->lighten($this->util->elementColor($this->primaryColor, false), 15),
'--color-text-maxcontrast' => $colorTextMaxcontrast,
'--color-text-maxcontrast-default' => $colorTextMaxcontrast,
'--color-text-maxcontrast-background-blur' => $this->util->lighten($colorTextMaxcontrast, 2),
'--color-text-light' => $this->util->darken($colorMainText, 10),
'--color-text-lighter' => $this->util->darken($colorMainText, 20),
'--color-text-maxcontrast' => $this->util->darken($colorMainText, 30),
'--color-text-light' => $this->util->darken($colorMainText, 10),
'--color-text-lighter' => $this->util->darken($colorMainText, 20),
// used for the icon loading animation
'--color-loading-light' => '#777',
'--color-loading-dark' => '#CCC',
'--color-loading-light' => '#777',
'--color-loading-dark' => '#CCC',
'--color-box-shadow' => $colorBoxShadow,
'--color-box-shadow-rgb' => $colorBoxShadowRGB,
'--color-box-shadow-rgb' => $colorBoxShadowRGB,
'--color-border' => $this->util->lighten($colorMainBackground, 7),
'--color-border-dark' => $this->util->lighten($colorMainBackground, 14),
'--color-border' => $this->util->lighten($colorMainBackground, 7),
'--color-border-dark' => $this->util->lighten($colorMainBackground, 14),
'--background-invert-if-dark' => 'invert(100%)',
'--background-invert-if-bright' => 'no',
]);
'--background-invert-if-dark' => 'invert(100%)',
'--background-invert-if-bright' => 'no',
]
);
}
}
+29 -28
View File
@@ -27,6 +27,7 @@ namespace OCA\Theming\Themes;
use OCA\Theming\AppInfo\Application;
use OCA\Theming\ImageManager;
use OCA\Theming\ITheme;
use OCA\Theming\Service\BackgroundService;
use OCA\Theming\ThemingDefaults;
use OCA\Theming\Util;
use OCP\App\IAppManager;
@@ -37,29 +38,41 @@ use OCP\IUserSession;
use OCP\Server;
class DefaultTheme implements ITheme {
use CommonThemeTrait;
public Util $util;
public ThemingDefaults $themingDefaults;
public IUserSession $userSession;
public IURLGenerator $urlGenerator;
public ImageManager $imageManager;
public IConfig $config;
public IL10N $l;
public string $defaultPrimaryColor;
public string $primaryColor;
public function __construct(Util $util,
ThemingDefaults $themingDefaults,
IUserSession $userSession,
IURLGenerator $urlGenerator,
ImageManager $imageManager,
IConfig $config,
IL10N $l) {
$this->util = $util;
$this->themingDefaults = $themingDefaults;
$this->userSession = $userSession;
$this->urlGenerator = $urlGenerator;
$this->imageManager = $imageManager;
$this->config = $config;
$this->l = $l;
$this->defaultPrimaryColor = $this->themingDefaults->getDefaultColorPrimary();
$this->primaryColor = $this->themingDefaults->getColorPrimary();
// Override default defaultPrimaryColor if set to improve accessibility
if ($this->primaryColor === BackgroundService::DEFAULT_COLOR) {
$this->primaryColor = BackgroundService::DEFAULT_ACCESSIBLE_COLOR;
}
}
public function getId(): string {
@@ -89,20 +102,18 @@ class DefaultTheme implements ITheme {
public function getCSSVariables(): array {
$colorMainText = '#222222';
$colorMainTextRgb = join(',', $this->util->hexToRGB($colorMainText));
$colorTextMaxcontrast = $this->util->lighten($colorMainText, 33);
$colorMainBackground = '#ffffff';
$colorMainBackgroundRGB = join(',', $this->util->hexToRGB($colorMainBackground));
$colorBoxShadow = $this->util->darken($colorMainBackground, 70);
$colorBoxShadowRGB = join(',', $this->util->hexToRGB($colorBoxShadow));
$colorPrimaryLight = $this->util->mix($this->primaryColor, $colorMainBackground, -80);
$colorPrimaryElement = $this->util->elementColor($this->primaryColor);
$colorPrimaryElementLight = $this->util->mix($colorPrimaryElement, $colorMainBackground, -80);
$hasCustomLogoHeader = $this->imageManager->hasImage('logo') || $this->imageManager->hasImage('logoheader');
$hasCustomPrimaryColour = !empty($this->config->getAppValue(Application::APP_ID, 'color'));
$variables = [
'--color-main-background' => $colorMainBackground,
'--color-main-background-not-plain' => $this->themingDefaults->getColorPrimary(),
'--color-main-background-rgb' => $colorMainBackgroundRGB,
'--color-main-background-translucent' => 'rgba(var(--color-main-background-rgb), .97)',
'--color-main-background-blur' => 'rgba(var(--color-main-background-rgb), .8)',
@@ -119,28 +130,11 @@ class DefaultTheme implements ITheme {
'--color-placeholder-light' => $this->util->darken($colorMainBackground, 10),
'--color-placeholder-dark' => $this->util->darken($colorMainBackground, 20),
// primary related colours
'--color-primary' => $this->primaryColor,
'--color-primary-text' => $this->util->invertTextColor($this->primaryColor) ? '#000000' : '#ffffff',
'--color-primary-hover' => $this->util->mix($this->primaryColor, $colorMainBackground, 60),
'--color-primary-light' => $colorPrimaryLight,
'--color-primary-light-text' => $this->primaryColor,
'--color-primary-light-hover' => $this->util->mix($colorPrimaryLight, $colorMainText, 90),
'--color-primary-text-dark' => $this->util->darken($this->util->invertTextColor($this->primaryColor) ? '#000000' : '#ffffff', 7),
// used for buttons, inputs...
'--color-primary-element' => $colorPrimaryElement,
'--color-primary-element-text' => $this->util->invertTextColor($colorPrimaryElement) ? '#000000' : '#ffffff',
'--color-primary-element-hover' => $this->util->mix($colorPrimaryElement, $colorMainBackground, 60),
'--color-primary-element-light' => $colorPrimaryElementLight,
'--color-primary-element-light-text' => $colorPrimaryElement,
'--color-primary-element-light-hover' => $this->util->mix($colorPrimaryElementLight, $colorMainText, 90),
'--color-primary-element-text-dark' => $this->util->darken($this->util->invertTextColor($colorPrimaryElement) ? '#000000' : '#ffffff', 7),
// to use like this: background-image: var(--gradient-primary-background);
'--gradient-primary-background' => 'linear-gradient(40deg, var(--color-primary) 0%, var(--color-primary-hover) 100%)',
// max contrast for WCAG compliance
'--color-main-text' => $colorMainText,
'--color-text-maxcontrast' => $this->util->lighten($colorMainText, 33),
'--color-text-maxcontrast' => $colorTextMaxcontrast,
'--color-text-maxcontrast-default' => $colorTextMaxcontrast,
'--color-text-maxcontrast-background-blur' => $this->util->darken($colorTextMaxcontrast, 7),
'--color-text-light' => $colorMainText,
'--color-text-lighter' => $this->util->lighten($colorMainText, 33),
@@ -211,6 +205,9 @@ class DefaultTheme implements ITheme {
'--image-main-background' => "url('" . $this->urlGenerator->imagePath('core', 'app-background.jpg') . "')",
];
// Primary variables
$variables = array_merge($variables, $this->generatePrimaryVariables($colorMainBackground, $colorMainText));
$backgroundDeleted = $this->config->getAppValue(Application::APP_ID, 'backgroundMime', '') === 'backgroundColor';
// If primary as background has been request or if we have a custom primary colour
// let's not define the background image
@@ -239,15 +236,19 @@ class DefaultTheme implements ITheme {
}
$appManager = Server::get(IAppManager::class);
$userSession = Server::get(IUserSession::class);
$user = $userSession->getUser();
$user = $this->userSession->getUser();
if ($appManager->isEnabledForUser(Application::APP_ID) && $user !== null) {
$themingBackground = $this->config->getUserValue($user->getUID(), Application::APP_ID, 'background', 'default');
$currentVersion = (int)$this->config->getUserValue($user->getUID(), Application::APP_ID, 'userCacheBuster', '0');
if ($themingBackground === 'custom') {
$variables['--image-main-background'] = "url('" . $this->urlGenerator->linkToRouteAbsolute('theming.theming.getBackground') . "')";
} elseif ($themingBackground !== 'default' && substr($themingBackground, 0, 1) !== '#') {
$cacheBuster = substr(sha1($user->getUID() . '_' . $currentVersion), 0, 8);
$variables['--image-main-background'] = "url('" . $this->urlGenerator->linkToRouteAbsolute('theming.userTheme.getBackground') . "?v=$cacheBuster')";
} elseif (isset(BackgroundService::SHIPPED_BACKGROUNDS[$themingBackground])) {
$variables['--image-main-background'] = "url('" . $this->urlGenerator->linkTo(Application::APP_ID, "/img/background/$themingBackground") . "')";
} elseif (substr($themingBackground, 0, 1) === '#') {
unset($variables['--image-main-background']);
$variables['--color-main-background-plain'] = $this->themingDefaults->getColorPrimary();
}
}
+35 -24
View File
@@ -29,7 +29,7 @@ use OCA\Theming\ITheme;
class HighContrastTheme extends DefaultTheme implements ITheme {
public function getId(): string {
return 'highcontrast';
return 'light-highcontrast';
}
public function getMediaQuery(): string {
@@ -48,41 +48,52 @@ class HighContrastTheme extends DefaultTheme implements ITheme {
return $this->l->t('A high contrast mode to ease your navigation. Visual quality will be reduced but clarity will be increased.');
}
/**
* Keep this consistent with other HighContrast Themes
*/
public function getCSSVariables(): array {
$variables = parent::getCSSVariables();
$defaultVariables = parent::getCSSVariables();
$colorMainText = '#000000';
$colorMainBackground = '#ffffff';
$colorMainBackgroundRGB = join(',', $this->util->hexToRGB($colorMainBackground));
$variables['--color-main-background'] = $colorMainBackground;
$variables['--color-main-background-translucent'] = 'rgba(var(--color-main-background-rgb), .1)';
$variables['--color-main-text'] = $colorMainText;
return array_merge(
$defaultVariables,
$this->generatePrimaryVariables($colorMainBackground, $colorMainText),
[
'--color-main-background' => $colorMainBackground,
'--color-main-background-rgb' => $colorMainBackgroundRGB,
'--color-main-background-translucent' => 'rgba(var(--color-main-background-rgb), 1)',
'--color-main-text' => $colorMainText,
$variables['--color-background-dark'] = $this->util->darken($colorMainBackground, 30);
$variables['--color-background-darker'] = $this->util->darken($colorMainBackground, 30);
'--color-background-dark' => $this->util->darken($colorMainBackground, 30),
'--color-background-darker' => $this->util->darken($colorMainBackground, 30),
$variables['--color-main-background-blur'] = $colorMainBackground;
$variables['--filter-background-blur'] = 'none';
'--color-main-background-blur' => $colorMainBackground,
'--filter-background-blur' => 'none',
$variables['--color-placeholder-light'] = $this->util->darken($colorMainBackground, 30);
$variables['--color-placeholder-dark'] = $this->util->darken($colorMainBackground, 45);
'--color-placeholder-light' => $this->util->darken($colorMainBackground, 30),
'--color-placeholder-dark' => $this->util->darken($colorMainBackground, 45),
$variables['--color-text-maxcontrast'] = 'var(--color-main-text)';
$variables['--color-text-light'] = 'var(--color-main-text)';
$variables['--color-text-lighter'] = 'var(--color-main-text)';
'--color-text-maxcontrast' => $colorMainText,
'--color-text-maxcontrast-background-blur' => $colorMainText,
'--color-text-light' => $colorMainText,
'--color-text-lighter' => $colorMainText,
$variables['--color-scrollbar'] = $this->util->darken($colorMainBackground, 25);
'--color-scrollbar' => $this->util->darken($colorMainBackground, 25),
// used for the icon loading animation
$variables['--color-loading-light'] = '#dddddd';
$variables['--color-loading-dark'] = '#000000';
// used for the icon loading animation
'--color-loading-light' => '#dddddd',
'--color-loading-dark' => '#000000',
$variables['--color-box-shadow-rgb'] = 'var(--color-main-text)';
$variables['--color-box-shadow'] = 'var(--color-main-text)';
'--color-box-shadow-rgb' => $colorMainText,
'--color-box-shadow' => $colorMainText,
$variables['--color-border'] = $this->util->darken($colorMainBackground, 50);
$variables['--color-border-dark'] = $this->util->darken($colorMainBackground, 50);
return $variables;
'--color-border' => $this->util->darken($colorMainBackground, 50),
'--color-border-dark' => $this->util->darken($colorMainBackground, 50),
]
);
}
public function getCustomCss(): string {
+66 -41
View File
@@ -40,6 +40,8 @@
*/
namespace OCA\Theming;
use OCA\Theming\AppInfo\Application;
use OCA\Theming\Service\BackgroundService;
use OCP\App\AppPathNotFoundException;
use OCP\App\IAppManager;
use OCP\Files\NotFoundException;
@@ -49,47 +51,31 @@ use OCP\IConfig;
use OCP\IL10N;
use OCP\INavigationManager;
use OCP\IURLGenerator;
use OCP\IUserSession;
class ThemingDefaults extends \OC_Defaults {
/** @var IConfig */
private $config;
/** @var IL10N */
private $l;
/** @var ImageManager */
private $imageManager;
/** @var IURLGenerator */
private $urlGenerator;
/** @var ICacheFactory */
private $cacheFactory;
/** @var Util */
private $util;
/** @var IAppManager */
private $appManager;
/** @var INavigationManager */
private $navigationManager;
private IConfig $config;
private IL10N $l;
private ImageManager $imageManager;
private IUserSession $userSession;
private IURLGenerator $urlGenerator;
private ICacheFactory $cacheFactory;
private Util $util;
private IAppManager $appManager;
private INavigationManager $navigationManager;
/** @var string */
private $name;
/** @var string */
private $title;
/** @var string */
private $entity;
/** @var string */
private $productName;
/** @var string */
private $url;
/** @var string */
private $color;
private string $name;
private string $title;
private string $entity;
private string $productName;
private string $url;
private string $color;
/** @var string */
private $iTunesAppId;
/** @var string */
private $iOSClientUrl;
/** @var string */
private $AndroidClientUrl;
/** @var string */
private $FDroidClientUrl;
private string $iTunesAppId;
private string $iOSClientUrl;
private string $AndroidClientUrl;
private string $FDroidClientUrl;
/**
* ThemingDefaults constructor.
@@ -97,6 +83,7 @@ class ThemingDefaults extends \OC_Defaults {
* @param IConfig $config
* @param IL10N $l
* @param ImageManager $imageManager
* @param IUserSession $userSession
* @param IURLGenerator $urlGenerator
* @param ICacheFactory $cacheFactory
* @param Util $util
@@ -104,6 +91,7 @@ class ThemingDefaults extends \OC_Defaults {
*/
public function __construct(IConfig $config,
IL10N $l,
IUserSession $userSession,
IURLGenerator $urlGenerator,
ICacheFactory $cacheFactory,
Util $util,
@@ -115,6 +103,7 @@ class ThemingDefaults extends \OC_Defaults {
$this->config = $config;
$this->l = $l;
$this->imageManager = $imageManager;
$this->userSession = $userSession;
$this->urlGenerator = $urlGenerator;
$this->cacheFactory = $cacheFactory;
$this->util = $util;
@@ -225,11 +214,47 @@ class ThemingDefaults extends \OC_Defaults {
/**
* Color that is used for the header as well as for mail headers
*
* @return string
*/
public function getColorPrimary() {
$color = $this->config->getAppValue('theming', 'color', $this->color);
public function getColorPrimary(): string {
$user = $this->userSession->getUser();
// admin-defined primary color
$defaultColor = $this->getDefaultColorPrimary();
// user-defined primary color
$themingBackground = '';
if (!empty($user)) {
$themingBackground = $this->config->getUserValue($user->getUID(), Application::APP_ID, 'background', '');
// If the user selected the default background
if ($themingBackground === '') {
return BackgroundService::DEFAULT_COLOR;
}
// If the user selected a specific colour
if (preg_match('/^\#([0-9a-f]{3}|[0-9a-f]{6})$/i', $themingBackground)) {
return $themingBackground;
}
// if the user-selected background is a background reference
if (isset(BackgroundService::SHIPPED_BACKGROUNDS[$themingBackground]['primary_color'])) {
return BackgroundService::SHIPPED_BACKGROUNDS[$themingBackground]['primary_color'];
}
}
// If the default color is not valid, return the default background one
if (!preg_match('/^\#([0-9a-f]{3}|[0-9a-f]{6})$/i', $defaultColor)) {
return BackgroundService::DEFAULT_COLOR;
}
// Finally, return the system global primary color
return $defaultColor;
}
/**
* Return the default color primary
*/
public function getDefaultColorPrimary(): string {
$color = $this->config->getAppValue(Application::APP_ID, 'color');
if (!preg_match('/^\#([0-9a-f]{3}|[0-9a-f]{6})$/i', $color)) {
$color = '#0082c9';
}
@@ -408,7 +433,7 @@ class ThemingDefaults extends \OC_Defaults {
/**
* Increases the cache buster key
*/
private function increaseCacheBuster(): void {
public function increaseCacheBuster(): void {
$cacheBusterKey = (int)$this->config->getAppValue('theming', 'cachebuster', '0');
$this->config->setAppValue('theming', 'cachebuster', (string)($cacheBusterKey + 1));
$this->cacheFactory->createDistributed('theming-')->clear();
+7 -32
View File
@@ -74,7 +74,7 @@
</template>
<script>
import { generateOcsUrl, imagePath } from '@nextcloud/router'
import { generateOcsUrl } from '@nextcloud/router'
import { loadState } from '@nextcloud/initial-state'
import axios from '@nextcloud/axios'
import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch'
@@ -83,14 +83,11 @@ import NcSettingsSection from '@nextcloud/vue/dist/Components/NcSettingsSection'
import BackgroundSettings from './components/BackgroundSettings.vue'
import ItemPreview from './components/ItemPreview.vue'
import { getBackgroundUrl } from '../src/helpers/getBackgroundUrl.js'
const availableThemes = loadState('theming', 'themes', [])
const enforceTheme = loadState('theming', 'enforceTheme', '')
const shortcutsDisabled = loadState('theming', 'shortcutsDisabled', false)
const background = loadState('theming', 'background')
const backgroundVersion = loadState('theming', 'backgroundVersion')
const themingDefaultBackground = loadState('theming', 'themingDefaultBackground')
const shippedBackgroundList = loadState('theming', 'shippedBackgrounds')
@@ -116,19 +113,6 @@ export default {
},
computed: {
backgroundImage() {
return getBackgroundUrl(this.background, backgroundVersion, this.themingDefaultBackground)
},
backgroundStyle() {
if ((this.background === 'default' && this.themingDefaultBackground === 'backgroundColor')
|| this.background.match(/#[0-9A-Fa-f]{6}/g)) {
return null
}
return {
backgroundImage: this.background === 'default' ? 'var(--image-main-background)' : `url('${this.backgroundImage}')`,
}
},
themes() {
return this.availableThemes.filter(theme => theme.type === 1)
},
@@ -170,21 +154,23 @@ export default {
},
},
mounted() {
this.updateGlobalStyles()
},
watch: {
shortcutsDisabled(newState) {
this.changeShortcutsDisabled(newState)
},
},
mounted() {
this.updateGlobalStyles()
},
methods: {
updateBackground(data) {
this.background = (data.type === 'custom' || data.type === 'default') ? data.type : data.value
this.updateGlobalStyles()
this.$emit('update:background')
},
updateGlobalStyles() {
// Override primary-invert-if-bright and color-primary-text if background is set
const isBackgroundBright = shippedBackgroundList[this.background]?.theming === 'dark'
@@ -199,17 +185,6 @@ export default {
// document.body.removeAttribute('data-theme-light')
// document.body.setAttribute('data-theme-dark', 'true')
}
const themeElements = [document.documentElement, document.querySelector('#header'), document.querySelector('body')]
for (const element of themeElements) {
if (this.background === 'default') {
element.style.setProperty('--image-main-background', `url('${imagePath('core', 'app-background.jpg')}')`)
} else if (this.background.match(/#[0-9A-Fa-f]{6}/g)) {
element.style.setProperty('--image-main-background', undefined)
} else {
element.style.setProperty('--image-main-background', this.backgroundStyle.backgroundImage)
}
}
},
changeTheme({ enabled, id }) {
// Reset selected and select new one
@@ -25,42 +25,67 @@
<template>
<div class="background-selector">
<!-- Custom background -->
<button class="background filepicker"
:class="{ active: background === 'custom' }"
tabindex="0"
@click="pickFile">
{{ t('theming', 'Pick from Files') }}
</button>
<!-- Default background -->
<button class="background default"
tabindex="0"
:class="{ 'icon-loading': loading === 'default', active: background === 'default' }"
@click="setDefault">
{{ t('theming', 'Default image') }}
</button>
<!-- Custom color picker -->
<NcColorPicker v-model="Theming.color" @input="debouncePickColor">
<button class="background color"
:class="{ active: background === Theming.color}"
tabindex="0"
:data-color="Theming.color"
:data-color-bright="invertTextColor(Theming.color)"
:style="{ backgroundColor: Theming.color, color: invertTextColor(Theming.color) ? '#000000' : '#ffffff'}">
{{ t('theming', 'Custom color') }}
</button>
</NcColorPicker>
<!-- Default admin primary color -->
<button class="background color"
:class="{ active: background === 'custom' }"
:class="{ active: background === Theming.defaultColor }"
tabindex="0"
@click="pickColor">
:data-color="Theming.defaultColor"
:data-color-bright="invertTextColor(Theming.defaultColor)"
:style="{ color: invertTextColor(Theming.defaultColor) ? '#000000' : '#ffffff'}"
@click="debouncePickColor">
{{ t('theming', 'Plain background') }}
</button>
<!-- Background set selection -->
<button v-for="shippedBackground in shippedBackgrounds"
:key="shippedBackground.name"
v-tooltip="shippedBackground.details.attribution"
:class="{ 'icon-loading': loading === shippedBackground.name, active: background === shippedBackground.name }"
tabindex="0"
class="background"
:data-color-bright="shippedBackground.details.theming === 'dark'"
:style="{ 'background-image': 'url(' + shippedBackground.preview + ')' }"
@click="setShipped(shippedBackground.name)" />
</div>
</template>
<script>
import axios from '@nextcloud/axios'
import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip'
import { generateUrl } from '@nextcloud/router'
import { loadState } from '@nextcloud/initial-state'
import { getBackgroundUrl } from '../helpers/getBackgroundUrl.js'
import { loadState } from '@nextcloud/initial-state'
import { prefixWithBaseUrl } from '../helpers/prefixWithBaseUrl.js'
import axios from '@nextcloud/axios'
import debounce from 'debounce'
import NcColorPicker from '@nextcloud/vue/dist/Components/NcColorPicker'
import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip'
const shippedBackgroundList = loadState('theming', 'shippedBackgrounds')
@@ -69,6 +94,11 @@ export default {
directives: {
Tooltip,
},
components: {
NcColorPicker,
},
props: {
background: {
type: String,
@@ -79,12 +109,15 @@ export default {
default: '',
},
},
data() {
return {
backgroundImage: generateUrl('/apps/theming/background') + '?v=' + Date.now(),
loading: false,
Theming: loadState('theming', 'data', {}),
}
},
computed: {
shippedBackgrounds() {
return Object.keys(shippedBackgroundList).map(fileName => {
@@ -97,7 +130,39 @@ export default {
})
},
},
methods: {
/**
* Do we need to invert the text if color is too bright?
*
* @param {string} color the hex color
*/
invertTextColor(color) {
return this.calculateLuma(color) > 0.6
},
/**
* Calculate luminance of provided hex color
*
* @param {string} color the hex color
*/
calculateLuma(color) {
const [red, green, blue] = this.hexToRGB(color)
return (0.2126 * red + 0.7152 * green + 0.0722 * blue) / 255
},
/**
* Convert hex color to RGB
*
* @param {string} hex the hex color
*/
hexToRGB(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
return result
? [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)]
: null
},
async update(data) {
const background = data.type === 'custom' || data.type === 'default' ? data.type : data.value
this.backgroundImage = getBackgroundUrl(background, data.version, this.themingDefaultBackground)
@@ -113,27 +178,35 @@ export default {
}
image.src = this.backgroundImage
},
async setDefault() {
this.loading = 'default'
const result = await axios.post(generateUrl('/apps/theming/background/default'))
this.update(result.data)
},
async setShipped(shipped) {
this.loading = shipped
const result = await axios.post(generateUrl('/apps/theming/background/shipped'), { value: shipped })
this.update(result.data)
},
async setFile(path) {
this.loading = 'custom'
const result = await axios.post(generateUrl('/apps/theming/background/custom'), { value: path })
this.update(result.data)
},
async pickColor() {
debouncePickColor: debounce(function() {
this.pickColor(...arguments)
}, 200),
async pickColor(event) {
this.loading = 'color'
const color = OCA && OCA.Theming ? OCA.Theming.color : '#0082c9'
const color = event?.target?.dataset?.color || this.Theming?.color || '#0082c9'
const result = await axios.post(generateUrl('/apps/theming/background/color'), { value: color })
this.update(result.data)
},
pickFile() {
window.OC.dialogs.filepicker(t('theming', 'Insert from {productName}', { productName: OC.theme.name }), (path, type) => {
if (type === OC.dialogs.FILEPICKER_TYPE_CHOOSE) {
@@ -171,7 +244,7 @@ export default {
}
&.color {
background-color: var(--color-primary);
background-color: var(--color-primary-default);
color: var(--color-primary-text);
}
@@ -181,14 +254,20 @@ export default {
border: 2px solid var(--color-primary);
}
&.active:not(.icon-loading):after {
background-image: var(--icon-checkmark-white);
background-repeat: no-repeat;
background-position: center;
background-size: 44px;
content: '';
display: block;
height: 100%;
&.active:not(.icon-loading) {
&:after {
background-image: var(--icon-checkmark-white);
background-repeat: no-repeat;
background-position: center;
background-size: 44px;
content: '';
display: block;
height: 100%;
}
&[data-color-bright]:after {
background-image: var(--icon-checkmark-dark);
}
}
}
}
@@ -22,8 +22,6 @@
*
*/
// FIXME hoist this into a package? The same logic is used in `apps/dashboard/src/helpers/getBackgroundUrl.js`
import { generateUrl } from '@nextcloud/router'
import { prefixWithBaseUrl } from './prefixWithBaseUrl.js'
@@ -20,8 +20,6 @@
*
*/
// FIXME hoist this into a package? The same logic is used in `apps/dashboard/src/helpers/prefixWithBaseUrl.js`
import { generateFilePath } from '@nextcloud/router'
export const prefixWithBaseUrl = (url) => generateFilePath('theming', '', 'img/background/') + url
+12
View File
@@ -30,3 +30,15 @@ Vue.prototype.t = t
const View = Vue.extend(App)
const theming = new View()
theming.$mount('#theming')
theming.$on('update:background', () => {
// Refresh server-side generated theming CSS
[...document.head.querySelectorAll('link.theme')].forEach(theme => {
const url = new URL(theme.href)
url.searchParams.set('v', Date.now())
const newTheme = theme.cloneNode()
newTheme.href = url.toString()
newTheme.onload = () => theme.remove()
document.head.append(newTheme)
})
})
@@ -33,6 +33,7 @@ use OCA\Theming\Themes\DyslexiaFont;
use OCA\Theming\Themes\HighContrastTheme;
use OCA\Theming\Service\ThemesService;
use OCA\Theming\Themes\LightTheme;
use OCA\Theming\ThemingDefaults;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCS\OCSBadRequestException;
use OCP\IConfig;
@@ -54,6 +55,8 @@ class UserThemeControllerTest extends TestCase {
private $userSession;
/** @var ThemeService|MockObject */
private $themesService;
/** @var ThemingDefaults */
private $themingDefaults;
/** @var BackgroundService|MockObject */
private $backgroundService;
@@ -66,13 +69,14 @@ class UserThemeControllerTest extends TestCase {
$this->config = $this->createMock(IConfig::class);
$this->userSession = $this->createMock(IUserSession::class);
$this->themesService = $this->createMock(ThemesService::class);
$this->themingDefaults = $this->createMock(ThemingDefaults::class);
$this->backgroundService = $this->createMock(BackgroundService::class);
$this->themes = [
'default' => $this->createMock(DefaultTheme::class),
'light' => $this->createMock(LightTheme::class),
'dark' => $this->createMock(DarkTheme::class),
'highcontrast' => $this->createMock(HighContrastTheme::class),
'light-highcontrast' => $this->createMock(HighContrastTheme::class),
'dark-highcontrast' => $this->createMock(DarkHighContrastTheme::class),
'opendyslexic' => $this->createMock(DyslexiaFont::class),
];
@@ -91,6 +95,7 @@ class UserThemeControllerTest extends TestCase {
$this->config,
$this->userSession,
$this->themesService,
$this->themingDefaults,
$this->backgroundService,
);
@@ -102,7 +107,7 @@ class UserThemeControllerTest extends TestCase {
['default'],
['light'],
['dark'],
['highcontrast'],
['light-highcontrast'],
['dark-highcontrast'],
['opendyslexic'],
['', OCSBadRequestException::class],
+21 -11
View File
@@ -56,6 +56,8 @@ class ImageManagerTest extends TestCase {
private $logger;
/** @var ITempManager|MockObject */
private $tempManager;
/** @var ISimpleFolder|MockObject */
private $rootFolder;
protected function setUp(): void {
parent::setUp();
@@ -65,6 +67,7 @@ class ImageManagerTest extends TestCase {
$this->cacheFactory = $this->createMock(ICacheFactory::class);
$this->logger = $this->createMock(ILogger::class);
$this->tempManager = $this->createMock(ITempManager::class);
$this->rootFolder = $this->createMock(ISimpleFolder::class);
$this->imageManager = new ImageManager(
$this->config,
$this->appData,
@@ -73,6 +76,11 @@ class ImageManagerTest extends TestCase {
$this->logger,
$this->tempManager
);
$this->appData
->expects($this->any())
->method('getFolder')
->with('global')
->willReturn($this->rootFolder);
}
private function checkImagick() {
@@ -120,7 +128,7 @@ class ImageManagerTest extends TestCase {
->willReturn($newFile);
$newFile->expects($this->once())
->method('putContent');
$this->appData->expects($this->once())
$this->rootFolder->expects($this->once())
->method('getFolder')
->with('images')
->willReturn($folder);
@@ -200,7 +208,7 @@ class ImageManagerTest extends TestCase {
->method('getAppValue')
->with('theming', 'cachebuster', '0')
->willReturn('0');
$this->appData->expects($this->once())
$this->rootFolder->expects($this->once())
->method('getFolder')
->with('0')
->willReturn($folder);
@@ -212,18 +220,18 @@ class ImageManagerTest extends TestCase {
->method('getAppValue')
->with('theming', 'cachebuster', '0')
->willReturn('0');
$this->appData->expects($this->exactly(2))
$this->rootFolder->expects($this->exactly(2))
->method('getFolder')
->with('0')
->willReturnOnConsecutiveCalls(
$this->throwException(new NotFoundException()),
$folder,
);
$this->appData->expects($this->once())
$this->rootFolder->expects($this->once())
->method('newFolder')
->with('0')
->willReturn($folder);
$this->appData->expects($this->once())
$this->rootFolder->expects($this->once())
->method('getDirectoryListing')
->willReturn([]);
$this->assertEquals($folder, $this->imageManager->getCacheFolder());
@@ -291,7 +299,7 @@ class ImageManagerTest extends TestCase {
->method('getAppValue')
->with('theming', 'cachebuster', '0')
->willReturn('0');
$this->appData->expects($this->once())
$this->rootFolder->expects($this->once())
->method('getFolder')
->with('0')
->willReturn($folder);
@@ -316,10 +324,10 @@ class ImageManagerTest extends TestCase {
->method('getAppValue')
->with('theming','cachebuster','0')
->willReturn('2');
$this->appData->expects($this->once())
$this->rootFolder->expects($this->once())
->method('getDirectoryListing')
->willReturn($folders);
$this->appData->expects($this->once())
$this->rootFolder->expects($this->once())
->method('getFolder')
->with('2')
->willReturn($folders[2]);
@@ -346,24 +354,26 @@ class ImageManagerTest extends TestCase {
$folder->expects($this->any())
->method('getFile')
->willReturn($oldFile);
if ($folderExists) {
$this->appData
$this->rootFolder
->expects($this->any())
->method('getFolder')
->with('images')
->willReturn($folder);
} else {
$this->appData
$this->rootFolder
->expects($this->any())
->method('getFolder')
->with('images')
->willThrowException(new NotFoundException());
$this->appData
$this->rootFolder
->expects($this->any())
->method('newFolder')
->with('images')
->willReturn($folder);
}
$folder->expects($this->once())
->method('newFile')
->with($key)
@@ -68,6 +68,10 @@ class ThemesServiceTest extends TestCase {
->method('getColorPrimary')
->willReturn('#0082c9');
$this->themingDefaults->expects($this->any())
->method('getDefaultColorPrimary')
->willReturn('#0082c9');
$this->initThemes();
$this->themesService = new ThemesService(
@@ -84,7 +88,7 @@ class ThemesServiceTest extends TestCase {
'default',
'light',
'dark',
'highcontrast',
'light-highcontrast',
'dark-highcontrast',
'opendyslexic',
];
@@ -98,7 +102,7 @@ class ThemesServiceTest extends TestCase {
['dark', [], ['dark']],
['dark', ['dark'], ['dark']],
['opendyslexic', ['dark'], ['dark', 'opendyslexic']],
['dark', ['highcontrast', 'opendyslexic'], ['opendyslexic', 'dark']],
['dark', ['light-highcontrast', 'opendyslexic'], ['opendyslexic', 'dark']],
];
}
@@ -132,7 +136,7 @@ class ThemesServiceTest extends TestCase {
['dark', [], []],
['dark', ['dark'], []],
['opendyslexic', ['dark', 'opendyslexic'], ['dark'], ],
['highcontrast', ['opendyslexic'], ['opendyslexic']],
['light-highcontrast', ['opendyslexic'], ['opendyslexic']],
];
}
@@ -156,7 +160,7 @@ class ThemesServiceTest extends TestCase {
->method('getUserValue')
->with('user', Application::APP_ID, 'enabled-themes', '[]')
->willReturn(json_encode($enabledThemes));
$this->assertEquals($expectedEnabled, $this->themesService->disableTheme($this->themes[$toDisable]));
}
@@ -167,7 +171,7 @@ class ThemesServiceTest extends TestCase {
['dark', [], false],
['dark', ['dark'], true],
['opendyslexic', ['dark', 'opendyslexic'], true],
['highcontrast', ['opendyslexic'], false],
['light-highcontrast', ['opendyslexic'], false],
];
}
@@ -190,7 +194,7 @@ class ThemesServiceTest extends TestCase {
->method('getUserValue')
->with('user', Application::APP_ID, 'enabled-themes', '[]')
->willReturn(json_encode($enabledThemes));
$this->assertEquals($expected, $this->themesService->isEnabled($this->themes[$themeId]));
}
@@ -281,6 +285,7 @@ class ThemesServiceTest extends TestCase {
'default' => new DefaultTheme(
$util,
$this->themingDefaults,
$this->userSession,
$urlGenerator,
$imageManager,
$this->config,
@@ -289,6 +294,7 @@ class ThemesServiceTest extends TestCase {
'light' => new LightTheme(
$util,
$this->themingDefaults,
$this->userSession,
$urlGenerator,
$imageManager,
$this->config,
@@ -297,14 +303,16 @@ class ThemesServiceTest extends TestCase {
'dark' => new DarkTheme(
$util,
$this->themingDefaults,
$this->userSession,
$urlGenerator,
$imageManager,
$this->config,
$l10n,
),
'highcontrast' => new HighContrastTheme(
'light-highcontrast' => new HighContrastTheme(
$util,
$this->themingDefaults,
$this->userSession,
$urlGenerator,
$imageManager,
$this->config,
@@ -313,6 +321,7 @@ class ThemesServiceTest extends TestCase {
'dark-highcontrast' => new DarkHighContrastTheme(
$util,
$this->themingDefaults,
$this->userSession,
$urlGenerator,
$imageManager,
$this->config,
@@ -321,6 +330,7 @@ class ThemesServiceTest extends TestCase {
'opendyslexic' => new DyslexiaFont(
$util,
$this->themingDefaults,
$this->userSession,
$urlGenerator,
$imageManager,
$this->config,
+2 -2
View File
@@ -97,7 +97,7 @@ class AdminTest extends TestCase {
->willReturn('MySlogan');
$this->themingDefaults
->expects($this->once())
->method('getColorPrimary')
->method('getDefaultColorPrimary')
->willReturn('#fff');
$this->urlGenerator
->expects($this->once())
@@ -156,7 +156,7 @@ class AdminTest extends TestCase {
->willReturn('MySlogan');
$this->themingDefaults
->expects($this->once())
->method('getColorPrimary')
->method('getDefaultColorPrimary')
->willReturn('#fff');
$this->urlGenerator
->expects($this->once())
+14 -2
View File
@@ -45,6 +45,7 @@ use OCP\AppFramework\Services\IInitialState;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\IUserSession;
use Test\TestCase;
class PersonalTest extends TestCase {
@@ -83,7 +84,7 @@ class PersonalTest extends TestCase {
$this->formatThemeForm('default'),
$this->formatThemeForm('light'),
$this->formatThemeForm('dark'),
$this->formatThemeForm('highcontrast'),
$this->formatThemeForm('light-highcontrast'),
$this->formatThemeForm('dark-highcontrast'),
$this->formatThemeForm('opendyslexic'),
]],
@@ -128,6 +129,7 @@ class PersonalTest extends TestCase {
private function initThemes() {
$util = $this->createMock(Util::class);
$themingDefaults = $this->createMock(ThemingDefaults::class);
$userSession = $this->createMock(IUserSession::class);
$urlGenerator = $this->createMock(IURLGenerator::class);
$imageManager = $this->createMock(ImageManager::class);
$config = $this->createMock(IConfig::class);
@@ -136,11 +138,16 @@ class PersonalTest extends TestCase {
$themingDefaults->expects($this->any())
->method('getColorPrimary')
->willReturn('#0082c9');
$themingDefaults->expects($this->any())
->method('getDefaultColorPrimary')
->willReturn('#0082c9');
$this->themes = [
'default' => new DefaultTheme(
$util,
$themingDefaults,
$userSession,
$urlGenerator,
$imageManager,
$config,
@@ -149,6 +156,7 @@ class PersonalTest extends TestCase {
'light' => new LightTheme(
$util,
$themingDefaults,
$userSession,
$urlGenerator,
$imageManager,
$config,
@@ -157,14 +165,16 @@ class PersonalTest extends TestCase {
'dark' => new DarkTheme(
$util,
$themingDefaults,
$userSession,
$urlGenerator,
$imageManager,
$config,
$l10n,
),
'highcontrast' => new HighContrastTheme(
'light-highcontrast' => new HighContrastTheme(
$util,
$themingDefaults,
$userSession,
$urlGenerator,
$imageManager,
$config,
@@ -173,6 +183,7 @@ class PersonalTest extends TestCase {
'dark-highcontrast' => new DarkHighContrastTheme(
$util,
$themingDefaults,
$userSession,
$urlGenerator,
$imageManager,
$config,
@@ -181,6 +192,7 @@ class PersonalTest extends TestCase {
'opendyslexic' => new DyslexiaFont(
$util,
$themingDefaults,
$userSession,
$urlGenerator,
$imageManager,
$config,
@@ -32,6 +32,7 @@ use OCP\Files\IAppData;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\IUserSession;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
@@ -52,6 +53,7 @@ class DefaultThemeTest extends TestCase {
protected function setUp(): void {
$this->themingDefaults = $this->createMock(ThemingDefaults::class);
$this->userSession = $this->createMock(IUserSession::class);
$this->urlGenerator = $this->createMock(IURLGenerator::class);
$this->imageManager = $this->createMock(ImageManager::class);
$this->config = $this->createMock(IConfig::class);
@@ -68,6 +70,11 @@ class DefaultThemeTest extends TestCase {
->method('getColorPrimary')
->willReturn('#0082c9');
$this->themingDefaults
->expects($this->any())
->method('getDefaultColorPrimary')
->willReturn('#0082c9');
$this->l10n
->expects($this->any())
->method('t')
@@ -85,6 +92,7 @@ class DefaultThemeTest extends TestCase {
$this->defaultTheme = new DefaultTheme(
$util,
$this->themingDefaults,
$this->userSession,
$this->urlGenerator,
$this->imageManager,
$this->config,
@@ -56,6 +56,7 @@ class DyslexiaFontTest extends TestCase {
protected function setUp(): void {
$this->themingDefaults = $this->createMock(ThemingDefaults::class);
$this->userSession = $this->createMock(IUserSession::class);
$this->imageManager = $this->createMock(ImageManager::class);
$this->config = $this->createMock(IConfig::class);
$this->l10n = $this->createMock(IL10N::class);
@@ -83,6 +84,11 @@ class DyslexiaFontTest extends TestCase {
->method('getColorPrimary')
->willReturn('#0082c9');
$this->themingDefaults
->expects($this->any())
->method('getDefaultColorPrimary')
->willReturn('#0082c9');
$this->l10n
->expects($this->any())
->method('t')
@@ -93,6 +99,7 @@ class DyslexiaFontTest extends TestCase {
$this->dyslexiaFont = new DyslexiaFont(
$util,
$this->themingDefaults,
$this->userSession,
$this->urlGenerator,
$this->imageManager,
$this->config,
@@ -142,7 +149,7 @@ class DyslexiaFontTest extends TestCase {
/**
* @dataProvider dataTestGetCustomCss
*
*
* Ensure the fonts are always loaded from the web root
* despite having url rewriting enabled or not
*
@@ -155,7 +162,7 @@ class DyslexiaFontTest extends TestCase {
->method('getSystemValue')
->with('htaccess.IgnoreFrontController', false)
->willReturn($prettyUrlsEnabled);
$this->assertStringContainsString("'$webRoot/apps/theming/fonts/OpenDyslexic-Regular.woff'", $this->dyslexiaFont->getCustomCss());
$this->assertStringNotContainsString('index.php', $this->dyslexiaFont->getCustomCss());
}
+79 -4
View File
@@ -35,6 +35,7 @@
namespace OCA\Theming\Tests;
use OCA\Theming\ImageManager;
use OCA\Theming\Service\BackgroundService;
use OCA\Theming\ThemingDefaults;
use OCA\Theming\Util;
use OCP\App\IAppManager;
@@ -46,6 +47,8 @@ use OCP\IConfig;
use OCP\IL10N;
use OCP\INavigationManager;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserSession;
use Test\TestCase;
class ThemingDefaultsTest extends TestCase {
@@ -53,6 +56,8 @@ class ThemingDefaultsTest extends TestCase {
private $config;
/** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */
private $l10n;
/** @var IUserSession|\PHPUnit\Framework\MockObject\MockObject */
private $userSession;
/** @var IURLGenerator|\PHPUnit\Framework\MockObject\MockObject */
private $urlGenerator;
/** @var \OC_Defaults|\PHPUnit\Framework\MockObject\MockObject */
@@ -78,6 +83,7 @@ class ThemingDefaultsTest extends TestCase {
parent::setUp();
$this->config = $this->createMock(IConfig::class);
$this->l10n = $this->createMock(IL10N::class);
$this->userSession = $this->createMock(IUserSession::class);
$this->urlGenerator = $this->createMock(IURLGenerator::class);
$this->cacheFactory = $this->createMock(ICacheFactory::class);
$this->cache = $this->createMock(ICache::class);
@@ -93,6 +99,7 @@ class ThemingDefaultsTest extends TestCase {
$this->template = new ThemingDefaults(
$this->config,
$this->l10n,
$this->userSession,
$this->urlGenerator,
$this->cacheFactory,
$this->util,
@@ -415,11 +422,11 @@ class ThemingDefaultsTest extends TestCase {
$this->assertEquals('<a href="url" target="_blank" rel="noreferrer noopener" class="entity-name">Name</a> Slogan', $this->template->getShortFooter());
}
public function testgetColorPrimaryWithDefault() {
public function testGetColorPrimaryWithDefault() {
$this->config
->expects($this->once())
->method('getAppValue')
->with('theming', 'color', $this->defaults->getColorPrimary())
->with('theming', 'color', null)
->willReturn($this->defaults->getColorPrimary());
$this->assertEquals($this->defaults->getColorPrimary(), $this->template->getColorPrimary());
@@ -429,12 +436,80 @@ class ThemingDefaultsTest extends TestCase {
$this->config
->expects($this->once())
->method('getAppValue')
->with('theming', 'color', $this->defaults->getColorPrimary())
->with('theming', 'color', null)
->willReturn('#fff');
$this->assertEquals('#fff', $this->template->getColorPrimary());
}
public function testGetColorPrimaryWithDefaultBackground() {
$user = $this->createMock(IUser::class);
$this->userSession->expects($this->any())
->method('getUser')
->willReturn($user);
$user->expects($this->any())
->method('getUID')
->willReturn('user');
$this->assertEquals(BackgroundService::DEFAULT_COLOR, $this->template->getColorPrimary());
}
public function testGetColorPrimaryWithCustomBackground() {
$backgroundIndex = 2;
$background = array_values(BackgroundService::SHIPPED_BACKGROUNDS)[$backgroundIndex];
$user = $this->createMock(IUser::class);
$this->userSession->expects($this->any())
->method('getUser')
->willReturn($user);
$user->expects($this->any())
->method('getUID')
->willReturn('user');
$this->config
->expects($this->once())
->method('getUserValue')
->with('user', 'theming', 'background', '')
->willReturn(array_keys(BackgroundService::SHIPPED_BACKGROUNDS)[$backgroundIndex]);
$this->assertEquals($background['primary_color'], $this->template->getColorPrimary());
}
public function testGetColorPrimaryWithCustomBackgroundColor() {
$user = $this->createMock(IUser::class);
$this->userSession->expects($this->any())
->method('getUser')
->willReturn($user);
$user->expects($this->any())
->method('getUID')
->willReturn('user');
$this->config
->expects($this->once())
->method('getUserValue')
->with('user', 'theming', 'background', '')
->willReturn('#fff');
$this->assertEquals('#fff', $this->template->getColorPrimary());
}
public function testGetColorPrimaryWithInvalidCustomBackgroundColor() {
$user = $this->createMock(IUser::class);
$this->userSession->expects($this->any())
->method('getUser')
->willReturn($user);
$user->expects($this->any())
->method('getUID')
->willReturn('user');
$this->config
->expects($this->once())
->method('getUserValue')
->with('user', 'theming', 'background', '')
->willReturn('nextcloud');
$this->assertEquals($this->template->getDefaultColorPrimary(), $this->template->getColorPrimary());
}
public function testSet() {
$this->config
->expects($this->exactly(2))
@@ -542,7 +617,7 @@ class ThemingDefaultsTest extends TestCase {
->method('getAppValue')
->withConsecutive(
['theming', 'cachebuster', '0'],
['theming', 'color', $this->defaults->getColorPrimary()],
['theming', 'color', null],
)->willReturnOnConsecutiveCalls(
'15',
$this->defaults->getColorPrimary(),
@@ -406,7 +406,7 @@ export default {
const hiddenField = document.createElement('input')
hiddenField.setAttribute('type', 'hidden')
hiddenField.setAttribute('name', 'updater-secret-input')
hiddenField.setAttribute('value', data.token)
hiddenField.setAttribute('value', data)
form.appendChild(hiddenField)
+2 -2
View File
@@ -94,8 +94,8 @@
}
.icon-user-status-invisible {
/* $dir is the app name, so we add this to the icon var to avoid conflicts between apps */
background-image: var(--icon-user-status-invisible-dark);
background-image: url("../img/user-status-invisible.svg");
filter: var(--background-invert-if-dark);
}
/*# sourceMappingURL=user-status-menu.css.map */
@@ -1 +1 @@
{"version":3,"sourceRoot":"","sources":["../../../core/css/variables.scss","user-status-menu.scss","../../../core/css/functions.scss"],"names":[],"mappings":";AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsBA;AAAA;AAAA;AA4BA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AD1BA;ACuCC;EAEA;;;ADrCD;EACC;;;AAGD;EACC;;;AAGD;EACC;;;AAID;ACsBC;EAEA","file":"user-status-menu.css"}
{"version":3,"sourceRoot":"","sources":["../../../core/css/variables.scss","user-status-menu.scss","../../../core/css/functions.scss"],"names":[],"mappings":";AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsBA;AAAA;AAAA;AA4BA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AD1BA;ACuCC;EAEA;;;ADrCD;EACC;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;EACA","file":"user-status-menu.css"}
+2 -2
View File
@@ -38,7 +38,7 @@
background-image: url('../img/user-status-dnd.svg');
}
// TODO: debug why icon-black-white does not work here
.icon-user-status-invisible {
@include icon-color('user-status-invisible', 'user_status', variables.$color-black, 1);
background-image: url('../img/user-status-invisible.svg');
filter: var(--background-invert-if-dark);
}
+9 -4
View File
@@ -76,10 +76,15 @@ class UserStatusMapper extends QBMapper {
->select('*')
->from($this->tableName)
->orderBy('status_timestamp', 'DESC')
->where($qb->expr()->notIn('status', $qb->createNamedParameter([IUserStatus::ONLINE, IUserStatus::AWAY, IUserStatus::OFFLINE], IQueryBuilder::PARAM_STR_ARRAY)))
->orWhere($qb->expr()->isNotNull('message_id'))
->orWhere($qb->expr()->isNotNull('custom_icon'))
->orWhere($qb->expr()->isNotNull('custom_message'));
->where($qb->expr()->andX(
$qb->expr()->orX(
$qb->expr()->notIn('status', $qb->createNamedParameter([IUserStatus::ONLINE, IUserStatus::AWAY, IUserStatus::OFFLINE], IQueryBuilder::PARAM_STR_ARRAY)),
$qb->expr()->isNotNull('message_id'),
$qb->expr()->isNotNull('custom_icon'),
$qb->expr()->isNotNull('custom_message'),
),
$qb->expr()->notLike('user_id', $qb->createNamedParameter($this->db->escapeLikeParameter('_') . '%'))
));
if ($limit !== null) {
$qb->setMaxResults($limit);
@@ -48,11 +48,17 @@ import NcEmojiPicker from '@nextcloud/vue/dist/Components/NcEmojiPicker.js'
export default {
name: 'CustomMessageInput',
components: {
NcButton,
NcEmojiPicker,
},
props: {
icon: {
type: String,
default: '😀',
},
message: {
type: String,
required: true,
@@ -63,11 +69,13 @@ export default {
default: false,
},
},
emits: [
'change',
'submit',
'icon-selected',
],
computed: {
/**
* Returns the user-set icon or a smiley in case no icon is set
@@ -78,6 +86,7 @@ export default {
return this.icon || '😀'
},
},
methods: {
focus() {
this.$refs.input.focus()
@@ -96,8 +105,8 @@ export default {
this.$emit('submit', event.target.value)
},
setIcon(event) {
this.$emit('icon-selected', event)
setIcon(icon) {
this.$emit('select-icon', icon)
},
},
}
@@ -27,8 +27,9 @@
type="radio"
name="user-status-online"
@change="onChange">
<label :for="id" :class="icon" class="user-status-online-select__label">
<label :for="id" class="user-status-online-select__label">
{{ label }}
<span :class="icon" role="img" />
<em class="user-status-online-select__subline">{{ subline }}</em>
</label>
</div>
@@ -76,6 +77,7 @@ export default {
</script>
<style lang="scss" scoped>
@use 'sass:math';
$icon-size: 24px;
$label-padding: 8px;
@@ -91,6 +93,7 @@ $label-padding: 8px;
}
&__label {
position: relative;
display: block;
margin: $label-padding;
padding: $label-padding;
@@ -105,6 +108,15 @@ $label-padding: 8px;
& {
cursor: pointer;
}
span {
position: absolute;
top: calc(50% - math.div($icon-size, 2));
left: $label-padding;
display: block;
width: $icon-size;
height: $icon-size;
}
}
&__input:checked + &__label,
@@ -42,10 +42,11 @@
</div>
<div class="set-status-modal__custom-input">
<CustomMessageInput ref="customMessageInput"
:icon="icon"
:message="message"
@change="setMessage"
@submit="saveStatus"
@iconSelected="setIcon" />
@select-icon="setIcon" />
</div>
<PredefinedStatusesList @select-status="selectPredefinedMessage" />
<ClearAtSelect :clear-at="clearAt"
@@ -74,12 +75,12 @@
import { showError } from '@nextcloud/dialogs'
import NcModal from '@nextcloud/vue/dist/Components/NcModal'
import NcButton from '@nextcloud/vue/dist/Components/NcButton'
import { getAllStatusOptions } from '../services/statusOptionsService'
import OnlineStatusMixin from '../mixins/OnlineStatusMixin'
import PredefinedStatusesList from './PredefinedStatusesList'
import CustomMessageInput from './CustomMessageInput'
import ClearAtSelect from './ClearAtSelect'
import OnlineStatusSelect from './OnlineStatusSelect'
import { getAllStatusOptions } from '../services/statusOptionsService.js'
import OnlineStatusMixin from '../mixins/OnlineStatusMixin.js'
import PredefinedStatusesList from './PredefinedStatusesList.vue'
import CustomMessageInput from './CustomMessageInput.vue'
import ClearAtSelect from './ClearAtSelect.vue'
import OnlineStatusSelect from './OnlineStatusSelect.vue'
export default {
name: 'SetStatusModal',
+1 -1
View File
@@ -1 +1 @@
3650d-5e41fd9674803
3707b-5eab9a4124dc3
+2 -2
View File
@@ -4622,7 +4622,7 @@ function idn_to_ascii($domain, $options = 0, $variant = INTL_IDNA_VARIANT_2003,
* @param int $variant [optional] <p>
* Either INTL_IDNA_VARIANT_2003 for IDNA 2003 or INTL_IDNA_VARIANT_UTS46 for UTS #46.
* </p>
* @param int &$idna_info [optional] <p>
* @param array &$idna_info [optional] <p>
* This parameter can be used only if INTL_IDNA_VARIANT_UTS46 was used for variant.
* In that case, it will be filled with an array with the keys 'result',
* the possibly illegal result of the transformation, 'isTransitionalDifferent',
@@ -4634,7 +4634,7 @@ function idn_to_ascii($domain, $options = 0, $variant = INTL_IDNA_VARIANT_2003,
* RFC 3490 4.2 states though "ToUnicode never fails. If any step fails, then the original input
* sequence is returned immediately in that step."
*/
function idn_to_utf8($domain, $options = 0, $variant = INTL_IDNA_VARIANT_2003, array &$idna_info) { }
function idn_to_utf8($domain, $options = 0, $variant = INTL_IDNA_VARIANT_2003, array &$idna_info = null) { }
/**
* (PHP 5 &gt;=5.5.0 PECL intl &gt;= 3.0.0a1)<br/>
+1 -1
View File
@@ -2177,7 +2177,7 @@ class Redis
*
* @param string $pattern pattern, using '*' as a wildcard
*
* @return array string[] The keys that match a certain pattern.
* @return string[]|false The keys that match a certain pattern.
*
* @link https://redis.io/commands/keys
* @example
@@ -45,14 +45,14 @@ class BackgroundCleanupUpdaterBackupsJob extends QueuedJob {
* This job cleans up all backups except the latest 3 from the updaters backup directory
*/
public function run($arguments) {
$dataDir = $this->config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data');
$updateDir = $this->config->getSystemValue('updatedirectory', null) ?? $this->config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data');
$instanceId = $this->config->getSystemValue('instanceid', null);
if (!is_string($instanceId) || empty($instanceId)) {
return;
}
$updaterFolderPath = $dataDir . '/updater-' . $instanceId;
$updaterFolderPath = $updateDir . '/updater-' . $instanceId;
$backupFolderPath = $updaterFolderPath . '/backups';
if (file_exists($backupFolderPath)) {
$this->log->info("$backupFolderPath exists - start to clean it up");

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