Compare commits

...

310 Commits

Author SHA1 Message Date
blizzz 8896b40164 Merge pull request #34913 from nextcloud/release/25.0.1
25.0.1
2022-11-03 10:57:09 +01:00
Arthur Schiwon 7c6d99e8ea 25.0.1
Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
2022-11-01 20:10:14 +01:00
John Molakvoæ 7170b141ba Merge pull request #34860 from nextcloud/backport/34683/stable25
[stable25] Display invalid input message
2022-10-28 10:08:51 +02:00
Christopher Ng 39b47e1a78 Display invalid input message
Signed-off-by: Christopher Ng <chrng8@gmail.com>
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2022-10-27 22:49:11 +00:00
John Molakvoæ 7508e5d5be Merge pull request #34854 from nextcloud/backport/34799/stable25
[stable25] Emit typed event when preview is requested
2022-10-27 18:00:46 +02:00
Julius Härtl 677451427e Emit typed event when preview is requested
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-10-27 13:51:28 +00:00
John Molakvoæ aab74bea40 Merge pull request #34837 from nextcloud/release/25.0.1_rc1
25.0.1 RC1
2022-10-27 15:37:52 +02:00
John Molakvoæ 4e635933c0 Merge pull request #34848 from nextcloud/backport/34736/stable25
[stable25] Fix autotest creating PostgreSQL database before install
2022-10-27 15:24:43 +02:00
John Molakvoæ f30b8df317 Merge pull request #34657 from nextcloud/backport/34625/stable25
[stable25] Fix icon of encrypted folders in filepicker
2022-10-27 14:26:18 +02:00
John Molakvoæ b45848d91e Merge pull request #34658 from nextcloud/backport/34632/stable25
[stable25] Add rate limiting on lost password emails
2022-10-27 14:26:06 +02:00
Vitor Mattos 31694ca314 Create database to autotest
Followed:
https://github.com/docker-library/docs/blob/master/postgres/README.md#postgres_db

Signed-off-by: Vitor Mattos <vitor@php.rio>
2022-10-27 11:55:49 +00:00
John Molakvoæ c4e4aff418 Merge pull request #34725 from nextcloud/backport/34579/stable25
[stable25] fix null error in getUnencryptedSize
2022-10-27 12:07:35 +02:00
John Molakvoæ 7ca983750b Merge pull request #34721 from nextcloud/backport/34508/stable25
[stable25] WebDAV - use file/folder name for dav:displayname
2022-10-27 12:06:39 +02:00
Simon L 76f60d04d8 Merge pull request #34827 from nextcloud/backport/34825/stable25
[stable25] Fix empty content regressions in comments app
2022-10-27 11:58:12 +02:00
Simon L 592739dd97 Merge pull request #34597 from nextcloud/backport/34595/stable25
[stable25] Fix file creation issue if no action are registered
2022-10-27 11:57:14 +02:00
Simon L a3263fb329 Merge pull request #34649 from nextcloud/backport/34636/stable25
[stable25] Fix vertical alignment of user status icon
2022-10-27 11:55:49 +02:00
Carl Schwan 2887ee0518 Fix icon of encrypted folders in filepicker
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2022-10-27 09:36:30 +00:00
Côme Chilliet 5fa94ae9af Fix LostController test
Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
2022-10-27 09:21:41 +00:00
Côme Chilliet 36d3ef1c7c Add rate limiting on lost password emails
Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
2022-10-27 09:21:40 +00:00
Vincent Petry 883bda7be4 Merge pull request #34726 from nextcloud/backport/34136/stable25
[stable25] on installation save channel to config.php if not stable
2022-10-27 11:01:02 +02:00
Vincent Petry c1c502ba4a Merge pull request #34704 from nextcloud/backport/34502/stable25
[stable25] Propagate attributes when resharing
2022-10-27 10:44:07 +02:00
nextcloud-command 794bbd36f3 Compile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2022-10-27 10:39:32 +02:00
Joas Schilling a2b5ed52da Recompile
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-10-27 10:34:41 +02:00
Carl Schwan 4bb481cbb3 Make css rule important
Co-authored-by: Joas Schilling <213943+nickvergessen@users.noreply.github.com>
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2022-10-27 10:31:55 +02:00
Carl Schwan 826f0aeae0 Fix vertical alignment of user status icon
Now it is properly centered even if the font size is bigger (when using
the dislexia font)

Signed-off-by: Carl Schwan <carl@carlschwan.eu>
2022-10-27 10:31:55 +02:00
John Molakvoæ a2d29eddf4 Fix file creation issue if no action are registered
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
2022-10-27 10:30:59 +02:00
Vincent Petry 4a448f31b2 Merge pull request #34581 from nextcloud/backport/34438/stable25
[stable25] return proper error code when reporting exception fails in remote.php
2022-10-27 10:26:08 +02:00
John Molakvoæ 44233f7c23 25.0.1 RC1
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
2022-10-27 10:16:33 +02:00
Richard Steinmetz 98977247da Fix empty content regressions in comments app
Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2022-10-26 21:50:48 +00:00
Louis 62e5313cda Merge pull request #34821 from nextcloud/backport/34809/stable25
[stable25] Fix getMetadata return type
2022-10-26 16:50:18 +02:00
Julius Härtl fa570bd0e4 Merge pull request #34823 from nextcloud/backport/34788/stable25
[stable25] Check share attributes on preview endpoints
2022-10-26 16:48:49 +02:00
Julius Härtl 2117736e34 Check share attributes on preview endpoints
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2022-10-26 13:20:46 +00:00
Louis Chemineau 9cf71f2b5a Fix getMetadata return type
Signed-off-by: Louis Chemineau <louis@chmn.me>
2022-10-26 12:38:16 +00:00
Julien Veyssier eb785ec9b6 Merge pull request #34601 from nextcloud/backport/34135/stable25
[stable25] Fix "email changed" activity email check
2022-10-26 12:33:43 +02:00
Simon L 9df81090f1 Merge pull request #34648 from nextcloud/backport/34634/stable25
[stable25] Fix unnecessary highlighting of selected element
2022-10-26 01:06:34 +02:00
Simon L 429c512ab7 Merge pull request #34779 from nextcloud/backport/34706/stable25
[stable25] update notifications for one-click instances
2022-10-25 18:09:01 +02:00
Côme Chilliet dba67aa021 Merge pull request #34796 from nextcloud/backport/34774/stable25
[stable25] Expose mapped user count from LDAP and use that for user limit check
2022-10-25 17:22:20 +02:00
Julien Veyssier df583faba3 check disable_activity.email_address_changed_by_admin when email is changed by admin via the OCS API
Signed-off-by: Julien Veyssier <eneiluj@posteo.net>
2022-10-25 16:13:03 +02:00
Côme Chilliet d49c7a3bdc Merge pull request #34791 from nextcloud/backport/32859/stable25
[stable25] Fix Uninitialized string offset 0 at GenerateMimetypeFileBuilder.php#39
2022-10-25 14:10:09 +02:00
Côme Chilliet afeaf93bdb Fix autoloaders
Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
2022-10-25 10:14:04 +00:00
Côme Chilliet ef1957d42e Update lib/public/User/Backend/ICountMappedUsersBackend.php
Co-authored-by: Simon L. <szaimen@e.mail.de>
Signed-off-by: Côme Chilliet <91878298+come-nc@users.noreply.github.com>
2022-10-25 10:14:04 +00:00
Côme Chilliet f8f357aa1b Add missing file
Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
2022-10-25 10:14:03 +00:00
Côme Chilliet e9d1c4afdb Expose mapped user count from LDAP and use that for user limit check
Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
2022-10-25 10:14:03 +00:00
Daniel Kesselberg 487759652b Fix Uninitialized string offset 0 at GenerateMimetypeFileBuilder.php#39
Signed-off-by: Daniel Kesselberg <mail@danielkesselberg.de>
2022-10-25 07:42:33 +00:00
Louis 05c9f0acb6 Merge pull request #34764 from nextcloud/backport/34740/stable25
[stable25] Add ico image/x-icon
2022-10-24 20:40:48 +02:00
szaimen f9d40847f1 update notifications for one-click instances
Signed-off-by: szaimen <szaimen@e.mail.de>
2022-10-24 18:01:55 +00:00
Simon L dc2fe31250 Merge pull request #34728 from nextcloud/backport/33945/stable25
[stable25] LDAP to not register new users when outside of fair use or over limits
2022-10-24 10:42:47 +02:00
John Molakvoæ 415fe8d8fe Sort alphabetically
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
2022-10-24 08:35:12 +00:00
John Molakvoæ 0dc2ed24f1 Add ico image/x-icon
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
2022-10-24 08:35:12 +00:00
blizzz 611a6c6751 extended hint message
Co-authored-by: Simon L. <szaimen@e.mail.de>
Signed-off-by: blizzz <blizzz@arthur-schiwon.de>
2022-10-21 16:58:13 +00:00
Arthur Schiwon 625d6d4d0c improve admin notification experience
- do not stack notifications, replace them
- and replace them once a day only
- with LDAP it might end in total spam terror (also push) otherwise

Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
2022-10-21 16:58:12 +00:00
Arthur Schiwon 6a3f2ff022 [LDAP] throw exception only against prov api
- unbreaks functionality for end users when on demand mapping takes
  place

Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
2022-10-21 16:58:12 +00:00
Arthur Schiwon 0fd9d8b132 LDAP to no register new users when outside of fair use or over limits
Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
2022-10-21 16:58:12 +00:00
Simon L be1ef4296f Merge pull request #34712 from nextcloud/backport/34688/stable25
[stable25] Don't crash with outdated share provider on update with the web updater
2022-10-21 18:54:13 +02:00
Arthur Schiwon 102093b1a2 silence psalm
Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
2022-10-21 15:19:03 +00:00
Arthur Schiwon a0930d01b1 on installation save channel to config.php if not stable
- the default channel to the NC server is what is provided in /version.php
  unless it is overridden in config.php
- the default channel to the NC Updater however is 'stable'
- this resultant in inconsistent results and confusing admin experience
- therefore "stable" is considered default and other channels are being
  written to config.php now upon installation

Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
2022-10-21 15:19:03 +00:00
Robin Appelman 5733fccdd1 fix null error in getUnencryptedSize
Signed-off-by: Robin Appelman <robin@icewind.nl>
2022-10-21 14:59:40 +00:00
Simon L d2333d1081 Merge pull request #34718 from nextcloud/backport/34714/stable25
[stable25] Fix more icon color
2022-10-21 16:27:14 +02:00
Dariusz Olszewski 71511ddd37 WebDAV - use file/folder name for dav:displayname
Signed-off-by: Dariusz Olszewski <starypatyk@users.noreply.github.com>
2022-10-21 13:49:07 +00:00
Vincent Petry 1d9d6c63bc Merge pull request #34610 from nextcloud/backport/34471/stable25
[stable25] Reduce number of database queries during WebDAV propfind request
2022-10-21 15:40:08 +02:00
John Molakvoæ c9b20c0b23 Merge pull request #34705 from nextcloud/backport/34685/stable25 2022-10-21 10:43:50 +02:00
Christopher Ng b772dbd416 Fix more icon color
Signed-off-by: Christopher Ng <chrng8@gmail.com>
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2022-10-21 08:35:07 +00:00
Simon L 5bb1d81c71 Merge pull request #34686 from nextcloud/backport/34665/stable25
[stable25] Invert header icons on bright preset backgrounds
2022-10-21 00:10:03 +02:00
Joas Schilling 707fc6599f Fix PHP CS
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-10-20 21:55:07 +00:00
Joas Schilling b851446c6a Also catch in getProvider
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-10-20 21:55:07 +00:00
Joas Schilling 9a5a80438a Don't crash with outdated share provider
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-10-20 21:55:07 +00:00
Joas Schilling 9429ba0e9a Merge pull request #34709 from nextcloud/backport/34703/stable25
[stable25] Use vue lib multiselect tags component
2022-10-20 21:54:37 +02:00
Joas Schilling 893078cebf Recompile
Signed-off-by: Joas Schilling <coding@schilljs.com>
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2022-10-20 17:26:15 +00:00
Joas Schilling 0484885d38 Use @nextcloud/vue MultiselectTags component
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-10-20 17:18:59 +00:00
Pytal 271ceb0e6c Merge pull request #34647 from nextcloud/backport/34620/stable25
[stable25] Add a repair step to cleanup old theming cache
2022-10-20 10:04:35 -07:00
zorn-v c6dea6e609 Fix alternative logins custom css class
In NC 25 login page was changed, and alternative login css class get from `class` prop but not `style`. 
It is correct change in my opinion, so just add backend fix.

Signed-off-by: zorn-v <zorn7@yandex.ru>
2022-10-20 15:19:42 +00:00
Simon L 0da59de01d Merge pull request #34699 from nextcloud/backport/34622/stable25
[stable25] Bump jquery-ui-dist from 1.13.1 to 1.13.2
2022-10-20 17:15:52 +02:00
Carl Schwan a318699f6d More fixes
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
2022-10-20 15:08:33 +00:00
Carl Schwan 26dac925b3 Small progress with fixing unit tests
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
2022-10-20 15:08:33 +00:00
Carl Schwan 49ddbb3655 Propagate attributes when resharing
When updating a share, load the node from the initiator instead of the
owner similar to how this is done when creating the share.

Signed-off-by: Carl Schwan <carl@carlschwan.eu>
2022-10-20 15:08:33 +00:00
Joas Schilling 69c6229b0b Recompile
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-10-20 15:27:43 +02:00
Joas Schilling 263108c889 Bump jquery-ui-dist from 1.13.1 to 1.13.2
Bumps [jquery-ui-dist](https://github.com/jquery/jquery-ui) from 1.13.1 to 1.13.2.
- [Release notes](https://github.com/jquery/jquery-ui/releases)
- [Commits](jquery/jquery-ui@1.13.1...1.13.2)

---
updated-dependencies:
- dependency-name: jquery-ui-dist
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-10-20 15:25:51 +02:00
Louis a94cc32f1e Merge pull request #34535 from nextcloud/backport/33511/stable25
[stable25] Extract GPS data from EXIF
2022-10-20 15:25:20 +02:00
Carl Schwan 7b001abd08 Allow scanning for metadata with occ scan:file --generate-metadata
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
Signed-off-by: Louis Chemineau <louis@chmn.me>
2022-10-20 11:56:02 +00:00
Louis Chemineau f5d5f019fa Extract GPS data from EXIF
Signed-off-by: Louis Chemineau <louis@chmn.me>
2022-10-20 11:56:02 +00:00
Christopher Ng 4fc531bd03 Invert header icons on bright preset backgrounds
Signed-off-by: Christopher Ng <chrng8@gmail.com>
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2022-10-20 06:47:29 +00:00
Simon L 5437573914 Merge pull request #34681 from nextcloud/backport/34576/stable25
[stable25] Add admin user customization kill switch
2022-10-20 08:03:33 +02:00
Christopher Ng cdf6e03215 Add a repair step to cleanup old theming cache
Signed-off-by: Christopher Ng <chrng8@gmail.com>
2022-10-20 00:18:33 +00:00
John Molakvoæ 7a96c80afb Simplify variable names
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2022-10-19 23:47:58 +00:00
John Molakvoæ 3c6da41c8a Fix tests
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
2022-10-19 23:36:44 +00:00
John Molakvoæ 40e2b11a68 Add admin user customization kill switch
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
2022-10-19 23:36:44 +00:00
John Molakvoæ 69eafb3bc0 Adjust testing
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
2022-10-19 23:36:44 +00:00
Pytal 372db926e9 Merge pull request #34590 from nextcloud/backport/34541/stable25 2022-10-19 11:35:07 -07:00
Christopher Ng adbaff71de Fix empty password string being submitted to server
- Fix reveal password not working

Signed-off-by: Christopher Ng <chrng8@gmail.com>
(cherry picked from commit dbaf8f3b89)
2022-10-19 17:20:00 +00:00
Christopher Ng 6fe73aee37 Bump @nextcloud/password-confirmation from 4.0.1 to 4.0.2
Signed-off-by: Christopher Ng <chrng8@gmail.com>
(cherry picked from commit 4d306fc647)
2022-10-19 17:18:31 +00:00
Joas Schilling c63f61d62e Merge pull request #34669 from nextcloud/bugfix/noid/npm-audit
[stable25] Run npm audit
2022-10-19 14:07:00 +02:00
Joas Schilling 73e278a8af Recompile
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-10-19 12:15:46 +02:00
Joas Schilling 414a8d7de7 Run npm audit on stable25
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-10-19 12:13:00 +02:00
Joas Schilling b568587783 Merge pull request #34594 from nextcloud/backport/34450/stable25
[stable25] Fix reference preview when no server-side cache configured
2022-10-19 12:08:47 +02:00
Joas Schilling 7a38af2d28 Merge pull request #34667 from nextcloud/backport/34664/stable25
[stable25] Update OCI workflow of server
2022-10-19 11:48:08 +02:00
blizzz f5fa046ed9 Merge pull request #34616 from nextcloud/backport/34188/stable25
[stable25] Expose clearing the profiles and fix it
2022-10-19 11:18:11 +02:00
blizzz c7a9514aa6 Merge pull request #34646 from nextcloud/backport/34609/stable25
[stable25] set theming app to alwaysEnabled
2022-10-19 11:17:32 +02:00
blizzz 05e320a034 Merge pull request #34661 from nextcloud/backport/34614/stable25
[stable25] Fix regexp for unified searching in apps
2022-10-19 11:14:26 +02:00
Joas Schilling a24edd5587 Update OCI workflow of server
* Only on pull requests
* Concurrency group
* Bump used actions
* Add summary

Signed-off-by: Joas Schilling <213943+nickvergessen@users.noreply.github.com>
2022-10-19 07:52:42 +00:00
Joas Schilling 433f8011a8 Merge pull request #34663 from nextcloud/backport/34650/stable25
[stable25] Fix rebuild navigation
2022-10-19 05:37:18 +02:00
nextcloud-command bf526631fa Compile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2022-10-18 20:48:57 +00:00
Joas Schilling 6b1901f0e7 🧭 Fix rebuilding the navigation
Signed-off-by: Joas Schilling <coding@schilljs.com>
2022-10-18 20:36:28 +00:00
Carl Schwan fda3af7915 Expose clearing the profiles and fix it
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
2022-10-18 18:58:04 +00:00
Julien Veyssier 53458967b3 fix reference preview endpoint when no server-side cache configured
Signed-off-by: Julien Veyssier <eneiluj@posteo.net>
2022-10-18 17:56:35 +00:00
Vincent Petry 08adbbd7ef Fix regexp for unified searching in apps
Use the correct match for operators.

Prevent the dropdown to close itself when selecting an app to filter by.

Signed-off-by: Vincent Petry <vincent@nextcloud.com>
2022-10-18 17:49:06 +00:00
Carl Schwan 8052cd9d08 Fix unnecessary highlighting of selected element
Use focus-visible to only show focus ring on keyboard navigation

Fix #34589

Signed-off-by: Carl Schwan <carl@carlschwan.eu>
2022-10-18 08:59:30 +00:00
szaimen f5e5d47fd9 set theming app to alwaysEnabled
Signed-off-by: szaimen <szaimen@e.mail.de>
2022-10-18 08:21:56 +00:00
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
Dariusz Olszewski 52a5d0cea6 Review comment - remove redundant empty() call
Signed-off-by: Dariusz Olszewski <starypatyk@users.noreply.github.com>
2022-10-14 13:38:39 +00:00
Dariusz Olszewski 6119604f71 Read notes from share already loaded into memory
Signed-off-by: Dariusz Olszewski <starypatyk@users.noreply.github.com>
2022-10-14 13:38:39 +00: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
Robin Appelman b695d2f107 return proper error code when reporting exception fails in remote.php
Signed-off-by: Robin Appelman <robin@icewind.nl>
2022-10-13 14:54:00 +00: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
313 changed files with 9581 additions and 30848 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:
+30 -23
View File
@@ -1,46 +1,39 @@
name: PHPUnit
on:
pull_request:
push:
branches:
- master
- stable*
on: pull_request
permissions:
contents: read
concurrency:
group: phpunit-oci-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
phpunit-oci8:
runs-on: ubuntu-20.04
phpunit-oci:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php-versions: [ '7.4', '8.0', '8.1']
databases: [ 'oci' ]
name: php${{ matrix.php-versions }}-${{ matrix.databases }}
services:
oracle:
image: deepdiver/docker-oracle-xe-11g # "wnameless/oracle-xe-11g-r2"
image: deepdiver/docker-oracle-xe-11g # 'wnameless/oracle-xe-11g-r2'
ports:
- "1521:1521"
- 1521:1521/tcp
steps:
- name: Checkout server
uses: actions/checkout@v2
- name: Checkout submodules
shell: bash
run: |
auth_header="$(git config --local --get http.https://github.com/.extraheader)"
git submodule sync --recursive
git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1
uses: actions/checkout@v3
with:
submodules: true
- name: Set up php ${{ matrix.php-versions }}
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
extensions: ctype,curl,dom,fileinfo,gd,imagick,intl,json,mbstring,oci8,openssl,pdo_sqlite,posix,sqlite,xml,zip
extensions: ctype, curl, dom, fileinfo, gd, imagick, intl, json, mbstring, oci8, openssl, pdo_sqlite, posix, sqlite, xml, zip
tools: phpunit:9
coverage: none
@@ -53,3 +46,17 @@ jobs:
- name: PHPUnit
working-directory: tests
run: phpunit --configuration phpunit-autotest.xml --group DB,SLOWDB
summary:
permissions:
contents: none
runs-on: ubuntu-latest
needs: phpunit-oci
if: always()
name: phpunit-oci-summary
steps:
- name: Summary status
run: if ${{ needs.phpunit-oci.result != 'success' }}; then exit 1; fi
+35 -12
View File
@@ -2,6 +2,7 @@
- @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com>
-
- @author John Molakvoæ <skjnldsv@protonmail.com>
- @author Richard Steinmetz <richard@steinmetz.cloud>
-
- @license GNU AGPL version 3 or any later version
-
@@ -31,8 +32,12 @@
@new="onNewComment" />
<template v-if="!isFirstLoading">
<NcEmptyContent v-if="!hasComments && done" icon="icon-comment">
{{ t('comments', 'No comments yet, start the conversation!') }}
<NcEmptyContent v-if="!hasComments && done"
class="comments__empty"
:title="t('comments', 'No comments yet, start the conversation!')">
<template #icon>
<MessageReplyTextIcon />
</template>
</NcEmptyContent>
<!-- Comments -->
@@ -55,14 +60,19 @@
</div>
<!-- Error message -->
<NcEmptyContent v-else-if="error" class="comments__error" icon="icon-error">
{{ error }}
<template #desc>
<button icon="icon-history" @click="getComments">
{{ t('comments', 'Retry') }}
</button>
</template>
</NcEmptyContent>
<template v-else-if="error">
<NcEmptyContent class="comments__error" :title="error">
<template #icon>
<AlertCircleOutlineIcon />
</template>
</NcEmptyContent>
<NcButton class="comments__retry" @click="getComments">
<template #icon>
<RefreshIcon />
</template>
{{ t('comments', 'Retry') }}
</NcButton>
</template>
</template>
</div>
</template>
@@ -76,6 +86,10 @@ import VTooltip from 'v-tooltip'
import Vue from 'vue'
import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent'
import NcButton from '@nextcloud/vue/dist/Components/NcButton'
import RefreshIcon from 'vue-material-design-icons/Refresh'
import MessageReplyTextIcon from 'vue-material-design-icons/MessageReplyText'
import AlertCircleOutlineIcon from 'vue-material-design-icons/AlertCircleOutline'
import Comment from '../components/Comment'
import getComments, { DEFAULT_LIMIT } from '../services/GetComments'
@@ -90,6 +104,10 @@ export default {
// Avatar,
Comment,
NcEmptyContent,
NcButton,
RefreshIcon,
MessageReplyTextIcon,
AlertCircleOutlineIcon,
},
data() {
@@ -276,8 +294,13 @@ export default {
<style lang="scss" scoped>
.comments {
// Do not add emptycontent top margin
&__error{
margin-top: 0;
&__empty,
&__error {
margin-top: 0 !important;
}
&__retry {
margin: 0 auto;
}
&__info {
+2 -50
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,16 +99,10 @@ 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')
const statusInfo = {
weather: {
text: t('dashboard', 'Weather'),
@@ -150,24 +144,9 @@ export default {
modal: false,
appStoreUrl: generateUrl('/settings/apps/dashboard'),
statuses: {},
background,
themingDefaultBackground,
}
},
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()
@@ -255,7 +234,6 @@ export default {
},
mounted() {
this.updateGlobalStyles()
this.updateSkipLink()
window.addEventListener('scroll', this.handleScroll)
@@ -272,32 +250,6 @@ export default {
},
methods: {
updateGlobalStyles() {
// Override primary-invert-if-bright and color-primary-text if background is set
const isBackgroundBright = shippedBackgroundList[this.background]?.theming === 'dark'
if (isBackgroundBright) {
document.querySelector('#header').style.setProperty('--primary-invert-if-bright', 'invert(100%)')
document.querySelector('#header').style.setProperty('--color-primary-text', '#000000')
// document.body.removeAttribute('data-theme-dark')
// document.body.setAttribute('data-theme-light', 'true')
} else {
document.querySelector('#header').style.setProperty('--primary-invert-if-bright', 'no')
document.querySelector('#header').style.setProperty('--color-primary-text', '#ffffff')
// 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 +393,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)
}
@@ -71,6 +71,7 @@ class FilesPlugin extends ServerPlugin {
public const GETETAG_PROPERTYNAME = '{DAV:}getetag';
public const LASTMODIFIED_PROPERTYNAME = '{DAV:}lastmodified';
public const CREATIONDATE_PROPERTYNAME = '{DAV:}creationdate';
public const DISPLAYNAME_PROPERTYNAME = '{DAV:}displayname';
public const OWNER_ID_PROPERTYNAME = '{http://owncloud.org/ns}owner-id';
public const OWNER_DISPLAY_NAME_PROPERTYNAME = '{http://owncloud.org/ns}owner-display-name';
public const CHECKSUMS_PROPERTYNAME = '{http://owncloud.org/ns}checksums';
@@ -379,6 +380,15 @@ class FilesPlugin extends ServerPlugin {
$propFind->handle(self::CREATION_TIME_PROPERTYNAME, function () use ($node) {
return $node->getFileInfo()->getCreationTime();
});
/**
* Return file/folder name as displayname. The primary reason to
* implement it this way is to avoid costly fallback to
* CustomPropertiesBackend (esp. visible when querying all files
* in a folder).
*/
$propFind->handle(self::DISPLAYNAME_PROPERTYNAME, function () use ($node) {
return $node->getName();
});
}
if ($node instanceof \OCA\DAV\Connector\Sabre\File) {
@@ -554,6 +564,13 @@ class FilesPlugin extends ServerPlugin {
$node->setCreationTime((int) $time);
return true;
});
/**
* Disable modification of the displayname property for files and
* folders via PROPPATCH. See PROPFIND for more information.
*/
$propPatch->handle(self::DISPLAYNAME_PROPERTYNAME, function ($displayName) {
return 403;
});
}
/**
+11 -15
View File
@@ -355,23 +355,19 @@ abstract class Node implements \Sabre\DAV\INode {
return '';
}
$types = [
IShare::TYPE_USER,
IShare::TYPE_GROUP,
IShare::TYPE_CIRCLE,
IShare::TYPE_ROOM
];
foreach ($types as $shareType) {
$shares = $this->shareManager->getSharedWith($user, $shareType, $this, -1);
foreach ($shares as $share) {
$note = $share->getNote();
if ($share->getShareOwner() !== $user && !empty($note)) {
return $note;
}
}
// Retrieve note from the share object already loaded into
// memory, to avoid additional database queries.
$storage = $this->getNode()->getStorage();
if (!$storage->instanceOfStorage(\OCA\Files_Sharing\SharedStorage::class)) {
return '';
}
/** @var \OCA\Files_Sharing\SharedStorage $storage */
$share = $storage->getShare();
$note = $share->getNote();
if ($share->getShareOwner() !== $user) {
return $note;
}
return '';
}
@@ -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',
+3 -4
View File
@@ -609,8 +609,8 @@ table td.selection {
.select-all + label {
padding: 16px;
}
.files-fileList tr td.selection > .selectCheckBox:focus + label,
.select-all:focus + label {
.files-fileList tr td.selection > .selectCheckBox:focus-visible + label,
.select-all:focus-visible + label {
background-color: var(--color-background-hover);
border-radius: var(--border-radius-pill);
outline: none !important;
@@ -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
@@ -504,7 +504,7 @@ table td.selection {
padding: 16px;
}
&:focus + label {
&:focus-visible + label {
background-color: var(--color-background-hover);
border-radius: var(--border-radius-pill);
outline: none !important;
@@ -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;
}
}
+3 -4
View File
@@ -609,8 +609,8 @@ table td.selection {
.select-all + label {
padding: 16px;
}
.files-fileList tr td.selection > .selectCheckBox:focus + label,
.select-all:focus + label {
.files-fileList tr td.selection > .selectCheckBox:focus-visible + label,
.select-all:focus-visible + label {
background-color: var(--color-background-hover);
border-radius: var(--border-radius-pill);
outline: none !important;
@@ -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());
}
}
+33 -16
View File
@@ -37,8 +37,11 @@ use OC\Core\Command\Base;
use OC\Core\Command\InterruptedException;
use OC\DB\Connection;
use OC\DB\ConnectionAdapter;
use OCP\Files\File;
use OC\ForbiddenException;
use OC\Metadata\MetadataManager;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\IRootFolder;
use OCP\Files\Mount\IMountPoint;
use OCP\Files\NotFoundException;
use OCP\Files\StorageNotAvailableException;
@@ -51,19 +54,22 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class Scan extends Base {
private IUserManager $userManager;
protected float $execTime = 0;
protected int $foldersCounter = 0;
protected int $filesCounter = 0;
private IRootFolder $root;
private MetadataManager $metadataManager;
/** @var IUserManager $userManager */
private $userManager;
/** @var float */
protected $execTime = 0;
/** @var int */
protected $foldersCounter = 0;
/** @var int */
protected $filesCounter = 0;
public function __construct(IUserManager $userManager) {
public function __construct(
IUserManager $userManager,
IRootFolder $rootFolder,
MetadataManager $metadataManager
) {
$this->userManager = $userManager;
parent::__construct();
$this->root = $rootFolder;
$this->metadataManager = $metadataManager;
}
protected function configure() {
@@ -83,6 +89,12 @@ class Scan extends Base {
InputArgument::OPTIONAL,
'limit rescan to this path, eg. --path="/alice/files/Music", the user_id is determined by the path and the user_id parameter and --all are ignored'
)
->addOption(
'generate-metadata',
null,
InputOption::VALUE_NONE,
'Generate metadata for all scanned files'
)
->addOption(
'all',
null,
@@ -106,21 +118,26 @@ class Scan extends Base {
);
}
protected function scanFiles($user, $path, OutputInterface $output, $backgroundScan = false, $recursive = true, $homeOnly = false) {
protected function scanFiles(string $user, string $path, bool $scanMetadata, OutputInterface $output, bool $backgroundScan = false, bool $recursive = true, bool $homeOnly = false): void {
$connection = $this->reconnectToDatabase($output);
$scanner = new \OC\Files\Utils\Scanner(
$user,
new ConnectionAdapter($connection),
\OC::$server->query(IEventDispatcher::class),
\OC::$server->get(IEventDispatcher::class),
\OC::$server->get(LoggerInterface::class)
);
# check on each file/folder if there was a user interrupt (ctrl-c) and throw an exception
$scanner->listen('\OC\Files\Utils\Scanner', 'scanFile', function ($path) use ($output) {
$scanner->listen('\OC\Files\Utils\Scanner', 'scanFile', function (string $path) use ($output, $scanMetadata) {
$output->writeln("\tFile\t<info>$path</info>", OutputInterface::VERBOSITY_VERBOSE);
++$this->filesCounter;
$this->abortIfInterrupted();
if ($scanMetadata) {
$node = $this->root->get($path);
if ($node instanceof File) {
$this->metadataManager->generateMetadata($node, false);
}
}
});
$scanner->listen('\OC\Files\Utils\Scanner', 'scanFolder', function ($path) use ($output) {
@@ -197,7 +214,7 @@ class Scan extends Base {
++$user_count;
if ($this->userManager->userExists($user)) {
$output->writeln("Starting scan for user $user_count out of $users_total ($user)");
$this->scanFiles($user, $path, $output, $input->getOption('unscanned'), !$input->getOption('shallow'), $input->getOption('home-only'));
$this->scanFiles($user, $path, $input->getOption('generate-metadata'), $output, $input->getOption('unscanned'), !$input->getOption('shallow'), $input->getOption('home-only'));
$output->writeln('', OutputInterface::VERBOSITY_VERBOSE);
} else {
$output->writeln("<error>Unknown user $user_count $user</error>");
@@ -291,7 +308,7 @@ class Scan extends Base {
protected function formatExecTime() {
$secs = round($this->execTime);
# convert seconds into HH:MM:SS form
return sprintf('%02d:%02d:%02d', (int)($secs / 3600), ( (int)($secs / 60) % 60), $secs % 60);
return sprintf('%02d:%02d:%02d', (int)($secs / 3600), ((int)($secs / 60) % 60), $secs % 60);
}
protected function reconnectToDatabase(OutputInterface $output): Connection {
@@ -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;
}
}
+11 -8
View File
@@ -215,20 +215,23 @@ export default {
)
this.logger.debug('Created new file', fileInfo)
// Fetch FileInfo and model
const data = await fileList?.addAndFetchFileInfo(this.name).then((status, data) => data)
const model = new OCA.Files.FileInfoModel(data, {
filesClient: fileList?.filesClient,
})
// Run default action
const fileAction = OCA.Files.fileActions.getDefaultFileAction(fileInfo.mime, 'file', OC.PERMISSION_ALL)
fileAction.action(fileInfo.basename, {
$file: fileList?.findFileEl(this.name),
dir: currentDirectory,
fileList,
fileActions: fileList?.fileActions,
fileInfoModel: model,
})
if (fileAction) {
fileAction.action(fileInfo.basename, {
$file: fileList?.findFileEl(this.name),
dir: currentDirectory,
fileList,
fileActions: fileList?.fileActions,
fileInfoModel: model,
})
}
this.close()
} catch (error) {
+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) {
@@ -109,6 +109,11 @@ class PublicPreviewController extends PublicShareController {
return new DataResponse([], Http::STATUS_FORBIDDEN);
}
$attributes = $share->getAttributes();
if ($attributes !== null && $attributes->getAttribute('permissions', 'download') === false) {
return new DataResponse([], Http::STATUS_FORBIDDEN);
}
try {
$node = $share->getNode();
if ($node instanceof Folder) {
@@ -159,6 +164,11 @@ class PublicPreviewController extends PublicShareController {
return new DataResponse([], Http::STATUS_FORBIDDEN);
}
$attributes = $share->getAttributes();
if ($attributes !== null && $attributes->getAttribute('permissions', 'download') === false) {
return new DataResponse([], Http::STATUS_FORBIDDEN);
}
try {
$node = $share->getNode();
if ($node instanceof Folder) {
@@ -533,6 +533,11 @@ class ShareAPIController extends OCSController {
$permissions &= ~($permissions & ~$node->getPermissions());
}
if ($attributes !== null) {
$share = $this->setShareAttributes($share, $attributes);
}
$share->setSharedBy($this->currentUser);
$this->checkInheritedAttributes($share);
if ($shareType === IShare::TYPE_USER) {
@@ -687,16 +692,11 @@ class ShareAPIController extends OCSController {
}
$share->setShareType($shareType);
$share->setSharedBy($this->currentUser);
if ($note !== '') {
$share->setNote($note);
}
if ($attributes !== null) {
$share = $this->setShareAttributes($share, $attributes);
}
try {
$share = $this->shareManager->createShare($share);
} catch (GenericShareException $e) {
@@ -1112,24 +1112,10 @@ class ShareAPIController extends OCSController {
$share->setNote($note);
}
$userFolder = $this->rootFolder->getUserFolder($this->currentUser);
// get the node with the point of view of the current user
$nodes = $userFolder->getById($share->getNode()->getId());
if (count($nodes) > 0) {
$node = $nodes[0];
$storage = $node->getStorage();
if ($storage && $storage->instanceOfStorage(SharedStorage::class)) {
/** @var \OCA\Files_Sharing\SharedStorage $storage */
$inheritedAttributes = $storage->getShare()->getAttributes();
if ($inheritedAttributes !== null && $inheritedAttributes->getAttribute('permissions', 'download') === false) {
if ($hideDownload === 'false') {
throw new OCSBadRequestException($this->l->t('Cannot increase permissions'));
}
$share->setHideDownload(true);
}
}
if ($attributes !== null) {
$share = $this->setShareAttributes($share, $attributes);
}
$this->checkInheritedAttributes($share);
/**
* expirationdate, password and publicUpload only make sense for link shares
@@ -1263,10 +1249,6 @@ class ShareAPIController extends OCSController {
}
}
if ($attributes !== null) {
$share = $this->setShareAttributes($share, $attributes);
}
try {
$share = $this->shareManager->updateShare($share);
} catch (GenericShareException $e) {
@@ -1555,7 +1537,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');
}
@@ -1912,8 +1894,17 @@ class ShareAPIController extends OCSController {
}
private function checkInheritedAttributes(IShare $share): void {
if ($share->getNode()->getStorage()->instanceOfStorage(SharedStorage::class)) {
$storage = $share->getNode()->getStorage();
if (!$share->getSharedBy()) {
return; // Probably in a test
}
$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
$nodes = $userFolder->getById($share->getNodeId());
if (empty($nodes)) {
return;
}
$node = $nodes[0];
if ($node->getStorage()->instanceOfStorage(SharedStorage::class)) {
$storage = $node->getStorage();
if ($storage instanceof Wrapper) {
$storage = $storage->getInstanceOfStorage(SharedStorage::class);
if ($storage === null) {
@@ -1926,6 +1917,11 @@ class ShareAPIController extends OCSController {
$inheritedAttributes = $storage->getShare()->getAttributes();
if ($inheritedAttributes !== null && $inheritedAttributes->getAttribute('permissions', 'download') === false) {
$share->setHideDownload(true);
$attributes = $share->getAttributes();
if ($attributes) {
$attributes->setAttribute('permissions', 'download', false);
$share->setAttributes($attributes);
}
}
}
+7
View File
@@ -229,6 +229,13 @@ class MountProvider implements IMountProvider {
->setShareType($shares[0]->getShareType())
->setTarget($shares[0]->getTarget());
// Gather notes from all the shares.
// Since these are readly available here, storing them
// enables the DAV FilesPlugin to avoid executing many
// DB queries to retrieve the same information.
$allNotes = implode("\n", array_map(function ($sh) { return $sh->getNote(); }, $shares));
$superShare->setNote($allNotes);
// use most permissive permissions
// this covers the case where there are multiple shares for the same
// file e.g. from different groups and different permissions
@@ -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 ?
*
@@ -1667,20 +1667,12 @@ class ShareAPIControllerTest extends TestCase {
$share = $this->newShare();
$this->shareManager->method('newShare')->willReturn($share);
$userFolder = $this->getMockBuilder(Folder::class)->getMock();
$this->rootFolder->expects($this->once())
[$userFolder, $path] = $this->getNonSharedUserFile();
$this->rootFolder->expects($this->exactly(2))
->method('getUserFolder')
->with('currentUser')
->willReturn($userFolder);
$path = $this->getMockBuilder(File::class)->getMock();
$storage = $this->createMock(Storage::class);
$storage->method('instanceOfStorage')
->willReturnMap([
['OCA\Files_Sharing\External\Storage', false],
['OCA\Files_Sharing\SharedStorage', false],
]);
$path->method('getStorage')->willReturn($storage);
$userFolder->expects($this->once())
->method('get')
->with('valid-path')
@@ -1703,20 +1695,12 @@ class ShareAPIControllerTest extends TestCase {
$share = $this->newShare();
$this->shareManager->method('newShare')->willReturn($share);
$userFolder = $this->getMockBuilder(Folder::class)->getMock();
$this->rootFolder->expects($this->once())
[$userFolder, $path] = $this->getNonSharedUserFile();
$this->rootFolder->expects($this->exactly(2))
->method('getUserFolder')
->with('currentUser')
->willReturn($userFolder);
$path = $this->getMockBuilder(File::class)->getMock();
$storage = $this->createMock(Storage::class);
$storage->method('instanceOfStorage')
->willReturnMap([
['OCA\Files_Sharing\External\Storage', false],
['OCA\Files_Sharing\SharedStorage', false],
]);
$path->method('getStorage')->willReturn($storage);
$userFolder->expects($this->once())
->method('get')
->with('valid-path')
@@ -1757,20 +1741,12 @@ class ShareAPIControllerTest extends TestCase {
])->setMethods(['formatShare'])
->getMock();
$userFolder = $this->getMockBuilder(Folder::class)->getMock();
$this->rootFolder->expects($this->once())
[$userFolder, $path] = $this->getNonSharedUserFile();
$this->rootFolder->expects($this->exactly(2))
->method('getUserFolder')
->with('currentUser')
->willReturn($userFolder);
$path = $this->getMockBuilder(File::class)->getMock();
$storage = $this->createMock(Storage::class);
$storage->method('instanceOfStorage')
->willReturnMap([
['OCA\Files_Sharing\External\Storage', false],
['OCA\Files_Sharing\SharedStorage', false],
]);
$path->method('getStorage')->willReturn($storage);
$userFolder->expects($this->once())
->method('get')
->with('valid-path')
@@ -1815,20 +1791,12 @@ class ShareAPIControllerTest extends TestCase {
$this->shareManager->method('createShare')->willReturnArgument(0);
$this->shareManager->method('allowGroupSharing')->willReturn(true);
$userFolder = $this->getMockBuilder(Folder::class)->getMock();
$this->rootFolder->expects($this->once())
[$userFolder, $path] = $this->getNonSharedUserFile();
$this->rootFolder->expects($this->exactly(2))
->method('getUserFolder')
->with('currentUser')
->willReturn($userFolder);
$path = $this->getMockBuilder(File::class)->getMock();
$storage = $this->createMock(Storage::class);
$storage->method('instanceOfStorage')
->willReturnMap([
['OCA\Files_Sharing\External\Storage', false],
['OCA\Files_Sharing\SharedStorage', false],
]);
$path->method('getStorage')->willReturn($storage);
$userFolder->expects($this->once())
->method('get')
->with('valid-path')
@@ -1876,20 +1844,12 @@ class ShareAPIControllerTest extends TestCase {
['shareWith', null, 'validGroup'],
]);
$userFolder = $this->getMockBuilder(Folder::class)->getMock();
$this->rootFolder->expects($this->once())
[$userFolder, $path] = $this->getNonSharedUserFolder();
$this->rootFolder->expects($this->exactly(2))
->method('getUserFolder')
->with('currentUser')
->willReturn($userFolder);
$path = $this->getMockBuilder(Folder::class)->getMock();
$storage = $this->createMock(Storage::class);
$storage->method('instanceOfStorage')
->willReturnMap([
['OCA\Files_Sharing\External\Storage', false],
['OCA\Files_Sharing\SharedStorage', false],
]);
$path->method('getStorage')->willReturn($storage);
$userFolder->expects($this->once())
->method('get')
->with('valid-path')
@@ -1932,20 +1892,12 @@ class ShareAPIControllerTest extends TestCase {
$share = $this->newShare();
$this->shareManager->method('newShare')->willReturn($share);
$userFolder = $this->getMockBuilder(Folder::class)->getMock();
$this->rootFolder->expects($this->once())
[$userFolder, $path] = $this->getNonSharedUserFolder();
$this->rootFolder->expects($this->exactly(2))
->method('getUserFolder')
->with('currentUser')
->willReturn($userFolder);
$path = $this->getMockBuilder(Folder::class)->getMock();
$storage = $this->createMock(Storage::class);
$storage->method('instanceOfStorage')
->willReturnMap([
['OCA\Files_Sharing\External\Storage', false],
['OCA\Files_Sharing\SharedStorage', false],
]);
$path->method('getStorage')->willReturn($storage);
$userFolder->expects($this->once())
->method('get')
->with('valid-path')
@@ -2292,20 +2244,12 @@ class ShareAPIControllerTest extends TestCase {
])->setMethods(['formatShare'])
->getMock();
$userFolder = $this->getMockBuilder(Folder::class)->getMock();
$this->rootFolder->expects($this->once())
[$userFolder, $path] = $this->getNonSharedUserFile();
$this->rootFolder->expects($this->exactly(2))
->method('getUserFolder')
->with('currentUser')
->willReturn($userFolder);
$path = $this->getMockBuilder(File::class)->getMock();
$storage = $this->createMock(Storage::class);
$storage->method('instanceOfStorage')
->willReturnMap([
['OCA\Files_Sharing\External\Storage', false],
['OCA\Files_Sharing\SharedStorage', false],
]);
$path->method('getStorage')->willReturn($storage);
$userFolder->expects($this->once())
->method('get')
->with('valid-path')
@@ -2366,20 +2310,12 @@ class ShareAPIControllerTest extends TestCase {
])->setMethods(['formatShare'])
->getMock();
$userFolder = $this->getMockBuilder(Folder::class)->getMock();
$this->rootFolder->expects($this->once())
[$userFolder, $path] = $this->getNonSharedUserFile();
$this->rootFolder->expects($this->exactly(2))
->method('getUserFolder')
->with('currentUser')
->willReturn($userFolder);
$path = $this->getMockBuilder(File::class)->getMock();
$storage = $this->createMock(Storage::class);
$storage->method('instanceOfStorage')
->willReturnMap([
['OCA\Files_Sharing\External\Storage', false],
['OCA\Files_Sharing\SharedStorage', false],
]);
$path->method('getStorage')->willReturn($storage);
$userFolder->expects($this->once())
->method('get')
->with('valid-path')
@@ -2422,20 +2358,12 @@ class ShareAPIControllerTest extends TestCase {
$share = $this->newShare();
$this->shareManager->method('newShare')->willReturn($share);
$userFolder = $this->getMockBuilder(Folder::class)->getMock();
$this->rootFolder->expects($this->once())
[$userFolder, $path] = $this->getNonSharedUserFile();
$this->rootFolder->expects($this->exactly(2))
->method('getUserFolder')
->with('currentUser')
->willReturn($userFolder);
$path = $this->getMockBuilder(File::class)->getMock();
$storage = $this->createMock(Storage::class);
$storage->method('instanceOfStorage')
->willReturnMap([
['OCA\Files_Sharing\External\Storage', false],
['OCA\Files_Sharing\SharedStorage', false],
]);
$path->method('getStorage')->willReturn($storage);
$userFolder->expects($this->once())
->method('get')
->with('valid-path')
@@ -2508,20 +2436,12 @@ class ShareAPIControllerTest extends TestCase {
$share = $this->newShare();
$this->shareManager->method('newShare')->willReturn($share);
$userFolder = $this->getMockBuilder(Folder::class)->getMock();
$this->rootFolder->expects($this->once())
[$userFolder, $path] = $this->getNonSharedUserFolder();
$this->rootFolder->expects($this->exactly(2))
->method('getUserFolder')
->with('currentUser')
->willReturn($userFolder);
$path = $this->getMockBuilder(File::class)->getMock();
$storage = $this->createMock(Storage::class);
$storage->method('instanceOfStorage')
->willReturnMap([
['OCA\Files_Sharing\External\Storage', false],
['OCA\Files_Sharing\SharedStorage', false],
]);
$path->method('getStorage')->willReturn($storage);
$path->method('getPath')->willReturn('valid-path');
$userFolder->expects($this->once())
->method('get')
@@ -2551,22 +2471,15 @@ class ShareAPIControllerTest extends TestCase {
$ocs = $this->mockFormatShare();
$share = $this->newShare();
$share->setSharedBy('currentUser');
$this->shareManager->method('newShare')->willReturn($share);
$userFolder = $this->getMockBuilder(Folder::class)->getMock();
$this->rootFolder->expects($this->once())
[$userFolder, $path] = $this->getNonSharedUserFile();
$this->rootFolder->expects($this->exactly(2))
->method('getUserFolder')
->with('currentUser')
->willReturn($userFolder);
$path = $this->getMockBuilder(File::class)->getMock();
$storage = $this->createMock(Storage::class);
$storage->method('instanceOfStorage')
->willReturnMap([
['OCA\Files_Sharing\External\Storage', false],
['OCA\Files_Sharing\SharedStorage', false],
]);
$path->method('getStorage')->willReturn($storage);
$userFolder->expects($this->once())
->method('get')
->with('valid-path')
@@ -2637,7 +2550,7 @@ class ShareAPIControllerTest extends TestCase {
->getMock();
$userFolder = $this->getMockBuilder(Folder::class)->getMock();
$this->rootFolder->expects($this->once())
$this->rootFolder->expects($this->exactly(2))
->method('getUserFolder')
->with('currentUser')
->willReturn($userFolder);
@@ -2649,7 +2562,9 @@ class ShareAPIControllerTest extends TestCase {
['OCA\Files_Sharing\External\Storage', true],
['OCA\Files_Sharing\SharedStorage', false],
]);
$userFolder->method('getStorage')->willReturn($storage);
$path->method('getStorage')->willReturn($storage);
$path->method('getPermissions')->willReturn(\OCP\Constants::PERMISSION_READ);
$userFolder->expects($this->once())
->method('get')
@@ -2676,7 +2591,7 @@ class ShareAPIControllerTest extends TestCase {
$this->expectException(\OCP\AppFramework\OCS\OCSNotFoundException::class);
$this->expectExceptionMessage('Wrong share ID, share does not exist');
$node = $this->getMockBuilder(Folder::class)->getMock();
[$userFolder, $node] = $this->getNonSharedUserFolder();
$share = $this->newShare();
$share->setNode($node);
@@ -2686,7 +2601,6 @@ class ShareAPIControllerTest extends TestCase {
$this->shareManager->method('getShareById')->with('ocinternal:42')->willReturn($share);
$userFolder = $this->getMockBuilder('OCP\Files\Folder')->getMock();
$this->rootFolder->method('getUserFolder')
->with($this->currentUser)
->willReturn($userFolder);
@@ -2743,7 +2657,7 @@ class ShareAPIControllerTest extends TestCase {
public function testUpdateLinkShareClear() {
$ocs = $this->mockFormatShare();
$node = $this->getMockBuilder(Folder::class)->getMock();
[$userFolder, $node] = $this->getNonSharedUserFolder();
$node->method('getId')
->willReturn(42);
$share = $this->newShare();
@@ -2780,7 +2694,6 @@ class ShareAPIControllerTest extends TestCase {
$this->shareManager->method('getSharedWith')
->willReturn([]);
$userFolder = $this->createMock(Folder::class);
$this->rootFolder->method('getUserFolder')
->with($this->currentUser)
->willReturn($userFolder);
@@ -2805,7 +2718,7 @@ class ShareAPIControllerTest extends TestCase {
public function testUpdateLinkShareSet() {
$ocs = $this->mockFormatShare();
$folder = $this->getMockBuilder(Folder::class)->getMock();
[$userFolder, $folder] = $this->getNonSharedUserFolder();
$folder->method('getId')
->willReturn(42);
@@ -2835,7 +2748,6 @@ class ShareAPIControllerTest extends TestCase {
$this->shareManager->method('getSharedWith')
->willReturn([]);
$userFolder = $this->createMock(Folder::class);
$this->rootFolder->method('getUserFolder')
->with($this->currentUser)
->willReturn($userFolder);
@@ -2863,7 +2775,7 @@ class ShareAPIControllerTest extends TestCase {
public function testUpdateLinkShareEnablePublicUpload($permissions, $publicUpload, $expireDate, $password) {
$ocs = $this->mockFormatShare();
$folder = $this->getMockBuilder(Folder::class)->getMock();
[$userFolder, $folder] = $this->getNonSharedUserFolder();
$folder->method('getId')
->willReturn(42);
@@ -2886,7 +2798,6 @@ class ShareAPIControllerTest extends TestCase {
})
)->willReturnArgument(0);
$userFolder = $this->createMock(Folder::class);
$this->rootFolder->method('getUserFolder')
->with($this->currentUser)
->willReturn($userFolder);
@@ -2925,7 +2836,7 @@ class ShareAPIControllerTest extends TestCase {
public function testUpdateLinkShareSetCRUDPermissions($permissions) {
$ocs = $this->mockFormatShare();
$folder = $this->getMockBuilder(Folder::class)->getMock();
[$userFolder, $folder] = $this->getNonSharedUserFolder();
$folder->method('getId')
->willReturn(42);
@@ -2945,7 +2856,6 @@ class ShareAPIControllerTest extends TestCase {
->method('updateShare')
->willReturnArgument(0);
$userFolder = $this->createMock(Folder::class);
$this->rootFolder->method('getUserFolder')
->with($this->currentUser)
->willReturn($userFolder);
@@ -3007,15 +2917,14 @@ class ShareAPIControllerTest extends TestCase {
$this->expectExceptionMessage('Invalid date. Format must be YYYY-MM-DD');
$ocs = $this->mockFormatShare();
$userFolder = $this->createMock(Folder::class);
[$userFolder, $folder] = $this->getNonSharedUserFolder();
$userFolder->method('getById')
->with(42)
->willReturn([]);
->willReturn([$folder]);
$this->rootFolder->method('getUserFolder')
->with($this->currentUser)
->willReturn($userFolder);
$folder = $this->getMockBuilder(Folder::class)->getMock();
$folder->method('getId')
->willReturn(42);
@@ -3055,15 +2964,14 @@ class ShareAPIControllerTest extends TestCase {
$this->expectExceptionMessage('Public upload disabled by the administrator');
$ocs = $this->mockFormatShare();
$userFolder = $this->createMock(Folder::class);
[$userFolder, $folder] = $this->getNonSharedUserFolder();
$userFolder->method('getById')
->with(42)
->willReturn([]);
->willReturn([$folder]);
$this->rootFolder->method('getUserFolder')
->with($this->currentUser)
->willReturn($userFolder);
$folder = $this->getMockBuilder(Folder::class)->getMock();
$folder->method('getId')->willReturn(42);
$share = \OC::$server->getShareManager()->newShare();
@@ -3088,10 +2996,10 @@ class ShareAPIControllerTest extends TestCase {
$file = $this->getMockBuilder(File::class)->getMock();
$file->method('getId')
->willReturn(42);
$userFolder = $this->createMock(Folder::class);
[$userFolder, $folder] = $this->getNonSharedUserFolder();
$userFolder->method('getById')
->with(42)
->willReturn([]);
->willReturn([$folder]);
$this->rootFolder->method('getUserFolder')
->with($this->currentUser)
->willReturn($userFolder);
@@ -3114,12 +3022,11 @@ class ShareAPIControllerTest extends TestCase {
$date = new \DateTime('2000-01-01');
$date->setTime(0,0,0);
$node = $this->getMockBuilder(File::class)->getMock();
[$userFolder, $node] = $this->getNonSharedUserFolder();
$node->method('getId')->willReturn(42);
$userFolder = $this->createMock(Folder::class);
$userFolder->method('getById')
->with(42)
->willReturn([]);
->willReturn([$node]);
$this->rootFolder->method('getUserFolder')
->with($this->currentUser)
->willReturn($userFolder);
@@ -3167,14 +3074,13 @@ class ShareAPIControllerTest extends TestCase {
$date = new \DateTime('2000-01-01');
$date->setTime(0,0,0);
$userFolder = $this->createMock(Folder::class);
[$userFolder, $node] = $this->getNonSharedUserFolder();
$userFolder->method('getById')
->with(42)
->willReturn([]);
->willReturn([$node]);
$this->rootFolder->method('getUserFolder')
->with($this->currentUser)
->willReturn($userFolder);
$node = $this->getMockBuilder(File::class)->getMock();
$node->method('getId')->willReturn(42);
$share = $this->newShare();
$share->setPermissions(\OCP\Constants::PERMISSION_ALL)
@@ -3226,14 +3132,13 @@ class ShareAPIControllerTest extends TestCase {
$date = new \DateTime('2000-01-01');
$date->setTime(0,0,0);
$userFolder = $this->createMock(Folder::class);
[$userFolder, $node] = $this->getNonSharedUserFolder();
$userFolder->method('getById')
->with(42)
->willReturn([]);
->willReturn([$node]);
$this->rootFolder->method('getUserFolder')
->with($this->currentUser)
->willReturn($userFolder);
$node = $this->getMockBuilder(File::class)->getMock();
$node->method('getId')->willReturn(42);
$share = $this->newShare();
$share->setPermissions(\OCP\Constants::PERMISSION_ALL)
@@ -3267,14 +3172,13 @@ class ShareAPIControllerTest extends TestCase {
$date = new \DateTime('2000-01-01');
$date->setTime(0,0,0);
$userFolder = $this->createMock(Folder::class);
[$userFolder, $node] = $this->getNonSharedUserFolder();
$userFolder->method('getById')
->with(42)
->willReturn([]);
->willReturn([$node]);
$this->rootFolder->method('getUserFolder')
->with($this->currentUser)
->willReturn($userFolder);
$node = $this->getMockBuilder(File::class)->getMock();
$node->method('getId')->willReturn(42);
$share = $this->newShare();
$share->setPermissions(\OCP\Constants::PERMISSION_ALL)
@@ -3322,7 +3226,7 @@ class ShareAPIControllerTest extends TestCase {
$date = new \DateTime('2000-01-01');
$date->setTime(0,0,0);
$node = $this->getMockBuilder(File::class)->getMock();
[$userFolder, $node] = $this->getNonSharedUserFolder();
$node->method('getId')
->willReturn(42);
@@ -3359,7 +3263,6 @@ class ShareAPIControllerTest extends TestCase {
})
)->willReturnArgument(0);
$userFolder = $this->createMock(Folder::class);
$this->rootFolder->method('getUserFolder')
->with($this->currentUser)
->willReturn($userFolder);
@@ -3390,7 +3293,7 @@ class ShareAPIControllerTest extends TestCase {
public function testUpdateLinkShareExpireDateDoesNotChangeOther() {
$ocs = $this->mockFormatShare();
$node = $this->getMockBuilder(File::class)->getMock();
[$userFolder, $node] = $this->getNonSharedUserFolder();
$node->method('getId')
->willReturn(42);
@@ -3428,7 +3331,6 @@ class ShareAPIControllerTest extends TestCase {
})
)->willReturnArgument(0);
$userFolder = $this->createMock(Folder::class);
$this->rootFolder->method('getUserFolder')
->with($this->currentUser)
->willReturn($userFolder);
@@ -3455,7 +3357,7 @@ class ShareAPIControllerTest extends TestCase {
$date = new \DateTime('2000-01-01');
$folder = $this->getMockBuilder(Folder::class)->getMock();
[$userFolder, $folder] = $this->getNonSharedUserFolder();
$folder->method('getId')
->willReturn(42);
@@ -3490,7 +3392,6 @@ class ShareAPIControllerTest extends TestCase {
$this->shareManager->method('getSharedWith')
->willReturn([]);
$userFolder = $this->createMock(Folder::class);
$this->rootFolder->method('getUserFolder')
->with($this->currentUser)
->willReturn($userFolder);
@@ -3517,7 +3418,7 @@ class ShareAPIControllerTest extends TestCase {
$date = new \DateTime('2000-01-01');
$folder = $this->getMockBuilder(Folder::class)->getMock();
[$userFolder, $folder] = $this->getNonSharedUserFolder();
$folder->method('getId')
->willReturn(42);
@@ -3551,7 +3452,6 @@ class ShareAPIControllerTest extends TestCase {
$this->shareManager->method('getSharedWith')->willReturn([]);
$userFolder = $this->createMock(Folder::class);
$this->rootFolder->method('getUserFolder')
->with($this->currentUser)
->willReturn($userFolder);
@@ -3578,7 +3478,7 @@ class ShareAPIControllerTest extends TestCase {
$date = new \DateTime('2000-01-01');
$folder = $this->getMockBuilder(Folder::class)->getMock();
[$userFolder, $folder] = $this->getNonSharedUserFolder();
$folder->method('getId')
->willReturn(42);
@@ -3610,7 +3510,6 @@ class ShareAPIControllerTest extends TestCase {
})
)->willReturnArgument(0);
$userFolder = $this->createMock(Folder::class);
$this->rootFolder->method('getUserFolder')
->with($this->currentUser)
->willReturn($userFolder);
@@ -3637,7 +3536,7 @@ class ShareAPIControllerTest extends TestCase {
public function testUpdateOtherPermissions() {
$ocs = $this->mockFormatShare();
$file = $this->getMockBuilder(File::class)->getMock();
[$userFolder, $file] = $this->getNonSharedUserFolder();
$file->method('getId')
->willReturn(42);
@@ -3658,7 +3557,7 @@ class ShareAPIControllerTest extends TestCase {
$this->shareManager->method('getSharedWith')->willReturn([]);
$userFolder = $this->createMock(Folder::class);
[$userFolder, $folder] = $this->getNonSharedUserFolder();
$this->rootFolder->method('getUserFolder')
->with($this->currentUser)
->willReturn($userFolder);
@@ -3683,7 +3582,7 @@ class ShareAPIControllerTest extends TestCase {
public function testUpdateShareCannotIncreasePermissions() {
$ocs = $this->mockFormatShare();
$folder = $this->createMock(Folder::class);
[$userFolder, $folder] = $this->getNonSharedUserFolder();
$folder->method('getId')
->willReturn(42);
@@ -3725,7 +3624,6 @@ class ShareAPIControllerTest extends TestCase {
['currentUser', IShare::TYPE_ROOM, $share->getNode(), -1, 0, []]
]);
$userFolder = $this->createMock(Folder::class);
$this->rootFolder->method('getUserFolder')
->with($this->currentUser)
->willReturn($userFolder);
@@ -3756,7 +3654,7 @@ class ShareAPIControllerTest extends TestCase {
public function testUpdateShareCanIncreasePermissionsIfOwner() {
$ocs = $this->mockFormatShare();
$folder = $this->createMock(Folder::class);
[$userFolder, $folder] = $this->getNonSharedUserFolder();
$folder->method('getId')
->willReturn(42);
@@ -3796,7 +3694,6 @@ class ShareAPIControllerTest extends TestCase {
->with($share)
->willReturn($share);
$userFolder = $this->createMock(Folder::class);
$this->rootFolder->method('getUserFolder')
->with($this->currentUser)
->willReturn($userFolder);
@@ -4916,4 +4813,32 @@ class ShareAPIControllerTest extends TestCase {
$result = $this->invokePrivate($this->ocs, 'formatShare', [$share]);
$this->assertEquals($expects, $result);
}
private function getNonSharedUserFolder(): array {
$node = $this->getMockBuilder(Folder::class)->getMock();
$userFolder = $this->getMockBuilder(Folder::class)->getMock();
$storage = $this->createMock(Storage::class);
$storage->method('instanceOfStorage')
->willReturnMap([
['OCA\Files_Sharing\External\Storage', false],
['OCA\Files_Sharing\SharedStorage', false],
]);
$userFolder->method('getStorage')->willReturn($storage);
$node->method('getStorage')->willReturn($storage);
return [$userFolder, $node];
}
private function getNonSharedUserFile(): array {
$node = $this->getMockBuilder(File::class)->getMock();
$userFolder = $this->getMockBuilder(Folder::class)->getMock();
$storage = $this->createMock(Storage::class);
$storage->method('instanceOfStorage')
->willReturnMap([
['OCA\Files_Sharing\External\Storage', false],
['OCA\Files_Sharing\SharedStorage', false],
]);
$userFolder->method('getStorage')->willReturn($storage);
$node->method('getStorage')->willReturn($storage);
return [$userFolder, $node];
}
}
@@ -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;
}
+5
View File
@@ -178,12 +178,17 @@ class Hooks {
if ($actor instanceof IUser) {
$subject = Provider::EMAIL_CHANGED_SELF;
if ($actor->getUID() !== $user->getUID()) {
// set via the OCS API
if ($this->config->getAppValue('settings', 'disable_activity.email_address_changed_by_admin', 'no') === 'yes') {
return;
}
$subject = Provider::EMAIL_CHANGED;
}
$text = $l->t('Your email address on %s was changed.', [$instanceUrl]);
$event->setAuthor($actor->getUID())
->setSubject($subject);
} else {
// set with occ
if ($this->config->getAppValue('settings', 'disable_activity.email_address_changed_by_admin', 'no') === 'yes') {
return;
}
@@ -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));
}
@@ -28,6 +28,7 @@
type="email"
:placeholder="inputPlaceholder"
:value="email"
:aria-describedby="helperText ? `${inputId}-helper-text` : ''"
autocapitalize="none"
autocomplete="on"
autocorrect="off"
@@ -71,6 +72,13 @@
</div>
</div>
<p v-if="helperText"
:id="`${inputId}-helper-text`"
class="email__helper-text-message email__helper-text-message--error">
<AlertCircle class="email__helper-text-message__icon" :size="18" />
{{ helperText }}
</p>
<em v-if="isNotificationEmail">
{{ t('settings', 'Primary email for password reset and notifications') }}
</em>
@@ -78,9 +86,9 @@
</template>
<script>
import NcActions from '@nextcloud/vue/dist/Components/NcActions'
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton'
import AlertOctagon from 'vue-material-design-icons/AlertOctagon'
import { NcActions, NcActionButton } from '@nextcloud/vue'
import AlertCircle from 'vue-material-design-icons/AlertCircleOutline.vue'
import AlertOctagon from 'vue-material-design-icons/AlertOctagon.vue'
import Check from 'vue-material-design-icons/Check'
import { showError } from '@nextcloud/dialogs'
import debounce from 'debounce'
@@ -105,6 +113,7 @@ export default {
components: {
NcActions,
NcActionButton,
AlertCircle,
AlertOctagon,
Check,
FederationControl,
@@ -143,6 +152,7 @@ export default {
initialEmail: this.email,
localScope: this.scope,
saveAdditionalEmailScope,
helperText: null,
showCheckmarkIcon: false,
showErrorIcon: false,
}
@@ -218,6 +228,11 @@ export default {
},
debounceEmailChange: debounce(async function(email) {
this.helperText = null
if (this.$refs.email?.validationMessage) {
this.helperText = this.$refs.email.validationMessage
return
}
if (validateEmail(email) || email === '') {
if (this.primary) {
await this.updatePrimaryEmail(email)
@@ -393,6 +408,22 @@ export default {
}
}
}
&__helper-text-message {
padding: 4px 0;
display: flex;
align-items: center;
&__icon {
margin-right: 8px;
align-self: start;
margin-top: 4px;
}
&--error {
color: var(--color-error);
}
}
}
.fade-enter,
@@ -38,10 +38,12 @@
autocorrect="off"
@input="onPropertyChange" />
<input v-else
ref="input"
:id="inputId"
:placeholder="placeholder"
:type="type"
:value="value"
:aria-describedby="helperText ? `${name}-helper-text` : ''"
autocapitalize="none"
autocomplete="on"
autocorrect="off"
@@ -57,6 +59,13 @@
<span v-else>
{{ value || t('settings', 'No {property} set', { property: readable.toLocaleLowerCase() }) }}
</span>
<p v-if="helperText"
:id="`${name}-helper-text`"
class="property__helper-text-message property__helper-text-message--error">
<AlertCircle class="property__helper-text-message__icon" :size="18" />
{{ helperText }}
</p>
</section>
</template>
@@ -64,8 +73,9 @@
import debounce from 'debounce'
import { showError } from '@nextcloud/dialogs'
import Check from 'vue-material-design-icons/Check'
import AlertCircle from 'vue-material-design-icons/AlertCircleOutline.vue'
import AlertOctagon from 'vue-material-design-icons/AlertOctagon'
import Check from 'vue-material-design-icons/Check'
import HeaderBar from '../shared/HeaderBar.vue'
@@ -76,6 +86,7 @@ export default {
name: 'AccountPropertySection',
components: {
AlertCircle,
AlertOctagon,
Check,
HeaderBar,
@@ -127,6 +138,7 @@ export default {
data() {
return {
initialValue: this.value,
helperText: null,
showCheckmarkIcon: false,
showErrorIcon: false,
}
@@ -145,6 +157,11 @@ export default {
},
debouncePropertyChange: debounce(async function(value) {
this.helperText = null
if (this.$refs.input && this.$refs.input.validationMessage) {
this.helperText = this.$refs.input.validationMessage
return
}
if (this.onValidate && !this.onValidate(value)) {
return
}
@@ -225,6 +242,22 @@ section {
}
}
.property__helper-text-message {
padding: 4px 0;
display: flex;
align-items: center;
&__icon {
margin-right: 8px;
align-self: start;
margin-top: 4px;
}
&--error {
color: var(--color-error);
}
}
.fade-enter,
.fade-leave-to {
opacity: 0;
@@ -56,6 +56,7 @@
ref="newuserpassword"
v-model="newUser.password"
:minlength="minPasswordLength"
:maxlength="469"
:placeholder="t('settings', 'Password')"
:required="newUser.mailAddress===''"
autocapitalize="none"
+2 -117
View File
@@ -1,5 +1,6 @@
import axios from '@nextcloud/axios'
import { generateOcsUrl } from '@nextcloud/router'
import { emit } from '@nextcloud/event-bus'
export default () => {
return axios.get(generateOcsUrl('core/navigation', 2) + '/apps?format=json')
@@ -8,123 +9,7 @@ export default () => {
return
}
const addedApps = {}
const navEntries = data.ocs.data
const container = document.querySelector('#navigation #apps ul')
// remove disabled apps
navEntries.forEach((entry) => {
if (!container.querySelector('li[data-id="' + entry.id + '"]')) {
addedApps[entry.id] = true
}
})
container.querySelectorAll('li[data-id]').forEach((el, index) => {
const id = el.dataset.id
// remove all apps that are not in the correct order
if (!navEntries[index] || (navEntries[index] && navEntries[index].id !== id)) {
el.remove()
document.querySelector(`#appmenu li[data-id=${id}]`).remove()
}
})
let previousEntry = {}
// add enabled apps to #navigation and #appmenu
navEntries.forEach((entry) => {
if (container.querySelector(`li[data-id="${entry.id}"]`) === null) {
const li = document.createElement('li')
li.dataset.id = entry.id
const img = `<svg width="20" height="20" viewBox="0 0 20 20" alt="">
<defs>
<filter id="invertMenuMore-${entry.id}"><feColorMatrix in="SourceGraphic" type="matrix" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"></feColorMatrix></filter>
<mask id="hole">
<rect width="100%" height="100%" fill="white"></rect>
<circle r="4.5" cx="17" cy="3" fill="black"></circle>
</mask>
</defs>
<image x="0" y="0" width="16" height="16" filter="url(#invertMenuMore-${entry.id})" preserveAspectRatio="xMinYMin meet" xlink:href="${entry.icon}" class="app-icon" />
</svg>`
const imgElement = document.createElement('template')
imgElement.innerHTML = img
const a = document.createElement('a')
a.setAttribute('href', entry.href)
const filename = document.createElement('span')
filename.appendChild(document.createTextNode(entry.name))
const loading = document.createElement('div')
loading.setAttribute('class', 'unread-counter')
loading.style.display = 'none'
// draw attention to the newly added app entry
// by flashing twice the more apps menu
if (addedApps[entry.id]) {
a.classList.add('animated')
}
a.prepend(imgElement.content.firstChild, loading, filename)
li.append(a)
// add app icon to the navigation
const previousElement = document.querySelector(`#navigation li[data-id=${previousEntry.id}]`)
if (previousElement) {
previousElement.insertAdjacentElement('afterend', li)
} else {
document.querySelector('#navigation #apps ul').prepend(li)
}
}
if (document.getElementById('appmenu').querySelector(`li[data-id="${entry.id}"]`) === null) {
const li = document.createElement('li')
li.dataset.id = entry.id
// Generating svg embedded image (see layout.user.php)
let img
if (OCA.Theming && OCA.Theming.inverted) {
img = `<svg width="20" height="20" viewBox="0 0 20 20" alt="">
<defs>
<filter id="invert"><feColorMatrix in="SourceGraphic" type="matrix" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0" /></filter>
</defs>
<image x="0" y="0" width="20" height="20" preserveAspectRatio="xMinYMin meet" filter="url(#invert)" xlink:href="${entry.icon}" class="app-icon" />
</svg>`
} else {
img = `<svg width="20" height="20" viewBox="0 0 20 20" alt="">
<image x="0" y="0" width="20" height="20" preserveAspectRatio="xMinYMin meet" xlink:href="${entry.icon}" class="app-icon" />
</svg>`
}
const imgElement = document.createElement('template')
imgElement.innerHTML = img
const a = document.createElement('a')
a.setAttribute('href', entry.href)
const filename = document.createElement('span')
filename.appendChild(document.createTextNode(entry.name))
const loading = document.createElement('div')
loading.setAttribute('class', 'icon-loading-dark')
loading.style.display = 'none'
// draw attention to the newly added app entry
// by flashing twice the more apps menu
if (addedApps[entry.id]) {
a.classList.add('animated')
}
a.prepend(loading, filename, imgElement.content.firstChild)
li.append(a)
// add app icon to the navigation
const previousElement = document.querySelector('#appmenu li[data-id=' + previousEntry.id + ']')
if (previousElement) {
previousElement.insertAdjacentElement('afterend', li)
} else {
document.queryElementById('appmenu').prepend(li)
}
}
previousEntry = entry
})
emit('nextcloud:app-menu.refresh', { apps: data.ocs.data })
window.dispatchEvent(new Event('resize'))
})
}
+5 -1
View File
@@ -5,7 +5,7 @@
<name>Theming</name>
<summary>Adjust the Nextcloud theme</summary>
<description>Adjust the Nextcloud theme</description>
<version>2.0.0</version>
<version>2.0.1</version>
<licence>agpl</licence>
<author>Nextcloud</author>
<namespace>Theming</namespace>
@@ -31,6 +31,10 @@
<pre-migration>
<step>OCA\Theming\Migration\MigrateUserConfig</step>
</pre-migration>
<post-migration>
<step>OCA\Theming\Migration\InitBackgroundImagesMigration</step>
<step>OCA\Theming\Migration\CleanupOldCache</step>
</post-migration>
</repair-steps>
<commands>
+22 -17
View File
@@ -10,23 +10,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);
@@ -64,8 +51,26 @@
--header-menu-item-height: 44px;
--header-menu-profile-item-height: 66px;
--breakpoint-mobile: 1024px;
--primary-invert-if-bright: no;
--background-invert-if-dark: no;
--background-invert-if-bright: invert(100%);
--image-main-background: url('/core/img/app-background.jpg');
--background-image-invert-if-bright: no;
--image-background: url('/core/img/app-background.jpg');
--color-background-plain: #0082c9;
--primary-invert-if-bright: no;
--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%);
}
+17 -1
View File
@@ -26,6 +26,8 @@
}
#theming form.uploadButton {
width: 411px;
display: flex;
align-items: center;
}
#theming form .theme-undo,
#theming .theme-remove-bg {
@@ -41,6 +43,10 @@
visibility: visible;
height: 32px;
width: 32px;
margin-left: auto;
}
#theming form .theme-undo:not([style*="display:"]) ~ .theme-remove-bg {
margin-left: 0;
}
#theming input[type=text]:hover + .theme-undo,
#theming input[type=text] + .theme-undo:hover,
@@ -55,6 +61,8 @@
#theming label span {
display: inline-block;
min-width: 175px;
max-width: 175px;
white-space: wrap;
padding: 8px 0px;
vertical-align: top;
}
@@ -89,7 +97,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 {
@@ -120,6 +128,14 @@
#theming #theming-preview-favicon {
background-image: var(--image-favicon);
}
#theming #user-theming {
margin-top: 44px;
display: flex;
}
#theming #user-theming > div {
max-width: 400px;
margin-bottom: 44px;
}
/* transition effects for theming value changes */
#header {
+1 -1
View File
@@ -1 +1 @@
{"version":3,"sourceRoot":"","sources":["settings-admin.scss"],"names":[],"mappings":"AACI;EACI;;AAGJ;AAAA;EAEI;;AAGJ;EACI;;AAGJ;EACI;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEJ;EACI;;AAEJ;AAAA;EAEI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EAQI;;AAGJ;EACI;EACA;EACA;EACA;;AAGJ;AAAA;EAEI;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;;AAGJ;EACI;EACA;EACA;;AAGJ;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIR;EACI;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;;AAGP;EAEO;;AAGP;EACO;;;AAIR;AACA;EACI;;AACA;EACI","file":"settings-admin.css"}
{"version":3,"sourceRoot":"","sources":["settings-admin.scss"],"names":[],"mappings":"AACI;EACI;;AAGJ;AAAA;EAEI;;AAGJ;EACI;;AAGJ;EACI;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;;AAEJ;AAAA;EAEI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;;AAEJ;EAEI;;AAGJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EAQI;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;;AAGJ;AAAA;EAEI;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;;AAGJ;EACI;EACA;EACA;;AAGJ;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIR;EACI;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;;AAGP;EAEO;;AAGP;EACO;;AAGJ;EACI;EACA;;AACD;EACK;EACA;;;AAKZ;AACA;EACI;;AACA;EACI","file":"settings-admin.css"}
+22 -2
View File
@@ -31,6 +31,8 @@
}
form.uploadButton {
width: 411px;
display: flex;
align-items: center;
}
form .theme-undo,
.theme-remove-bg {
@@ -46,7 +48,14 @@
visibility: visible;
height: 32px;
width: 32px;
// right align
margin-left: auto;
}
form .theme-undo:not([style*="display:"]) ~ .theme-remove-bg {
// Only align the undo button if both are shown
margin-left: 0;
}
input[type='text']:hover + .theme-undo,
input[type='text'] + .theme-undo:hover,
input[type='text']:focus + .theme-undo,
@@ -61,6 +70,8 @@
label span {
display: inline-block;
min-width: 175px;
max-width: 175px;
white-space: wrap;
padding: 8px 0px;
vertical-align: top;
}
@@ -100,7 +111,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 {
@@ -137,6 +148,15 @@
#theming-preview-favicon {
background-image: var(--image-favicon);
}
#user-theming {
margin-top: 44px;
display: flex;
& > div {
max-width: 400px;
margin-bottom: 44px;
}
}
}
/* transition effects for theming value changes */
@@ -145,4 +165,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

+5
View File
@@ -173,6 +173,11 @@ window.addEventListener('DOMContentLoaded', function () {
var el = $(this);
});
$('#userThemingDisabled').change(function(e) {
var checked = e.target.checked
setThemingValue('disable-user-theming', checked ? 'yes' : 'no')
});
function onChange(e) {
var el = $(this);
var setting = el.parent().find('div[data-setting]').data('setting');
+1 -1
View File
@@ -33,7 +33,7 @@ use Symfony\Component\Console\Output\OutputInterface;
class UpdateConfig extends Command {
public const SUPPORTED_KEYS = [
'name', 'url', 'imprintUrl', 'privacyUrl', 'slogan', 'color'
'name', 'url', 'imprintUrl', 'privacyUrl', 'slogan', 'color', 'disable-user-theming'
];
public const SUPPORTED_IMAGE_KEYS = [
@@ -151,6 +151,11 @@ class ThemingController extends Controller {
$error = $this->l10n->t('The given color is invalid');
}
break;
case 'disable-user-theming':
if ($value !== "yes" && $value !== "no") {
$error = $this->l10n->t('Disable-user-theming should be true or false');
}
break;
}
if ($error !== null) {
return new DataResponse([
@@ -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,
]);
}
}
+23 -13
View File
@@ -45,6 +45,7 @@ use OCP\ITempManager;
use OCP\IURLGenerator;
class ImageManager {
public const SupportedImageKeys = ['background', 'logo', 'logoheader', 'favicon'];
/** @var IConfig */
private $config;
@@ -53,7 +54,6 @@ class ImageManager {
/** @var IURLGenerator */
private $urlGenerator;
/** @var array */
private $supportedImageKeys = ['background', 'logo', 'logoheader', 'favicon'];
/** @var ICacheFactory */
private $cacheFactory;
/** @var ILogger */
@@ -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);
}
@@ -140,7 +142,7 @@ class ImageManager {
*/
public function getCustomImages(): array {
$images = [];
foreach ($this->supportedImageKeys as $key) {
foreach ($this::SupportedImageKeys as $key) {
$images[$key] = [
'mime' => $this->config->getAppValue('theming', $key . 'Mime', ''),
'url' => $this->getImageUrl($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,76 @@
<?php
declare(strict_types=1);
/**
* @copyright 2022 Christopher Ng <chrng8@gmail.com>
*
* @author Christopher Ng <chrng8@gmail.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\Migration;
use OCP\Files\IAppData;
use OCP\Files\SimpleFS\ISimpleFolder;
use OCP\IL10N;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
use Throwable;
class CleanupOldCache implements IRepairStep {
private const CACHE_FOLDERS = [
'global',
'users',
];
private IAppData $appData;
private IL10N $l10n;
public function __construct(
IAppData $appData,
IL10N $l10n
) {
$this->appData = $appData;
$this->l10n = $l10n;
}
public function getName(): string {
return $this->l10n->t('Cleanup old theming cache');
}
public function run(IOutput $output): void {
$folders = array_filter(
$this->appData->getDirectoryListing(),
fn (ISimpleFolder $folder): bool => !in_array($folder->getName(), static::CACHE_FOLDERS, true),
);
$output->startProgress(count($folders));
foreach ($folders as $folder) {
try {
$folder->delete();
} catch (Throwable $e) {
$output->warning($this->l10n->t('Failed to delete folder: "%1$s", error: %2$s', [$folder->getName(), $e->getMessage()]));
}
$output->advance();
}
$output->finishProgress();
}
}
@@ -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) {
+2 -1
View File
@@ -75,13 +75,14 @@ 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'),
'images' => $this->imageManager->getCustomImages(),
'imprintUrl' => $this->themingDefaults->getImprintUrl(),
'privacyUrl' => $this->themingDefaults->getPrivacyUrl(),
'userThemingDisabled' => $this->themingDefaults->isUserThemingDisabled(),
];
return new TemplateResponse($this->appName, 'settings-admin', $parameters, '');
+6 -1
View File
@@ -27,6 +27,7 @@ namespace OCA\Theming\Settings;
use OCA\Theming\ITheme;
use OCA\Theming\Service\ThemesService;
use OCA\Theming\ThemingDefaults;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IInitialState;
use OCP\IConfig;
@@ -39,15 +40,18 @@ class Personal implements ISettings {
private IConfig $config;
private ThemesService $themesService;
private IInitialState $initialStateService;
private ThemingDefaults $themingDefaults;
public function __construct(string $appName,
IConfig $config,
ThemesService $themesService,
IInitialState $initialStateService) {
IInitialState $initialStateService,
ThemingDefaults $themingDefaults) {
$this->appName = $appName;
$this->config = $config;
$this->themesService = $themesService;
$this->initialStateService = $initialStateService;
$this->themingDefaults = $themingDefaults;
}
public function getForm(): TemplateResponse {
@@ -72,6 +76,7 @@ class Personal implements ISettings {
$this->initialStateService->provideInitialState('themes', array_values($themes));
$this->initialStateService->provideInitialState('enforceTheme', $enforcedTheme);
$this->initialStateService->provideInitialState('isUserThemingDisabled', $this->themingDefaults->isUserThemingDisabled());
Util::addScript($this->appName, 'theming-settings');
return new TemplateResponse($this->appName, 'settings-personal');
@@ -0,0 +1,157 @@
<?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\AppInfo\Application;
use OCA\Theming\ImageManager;
use OCA\Theming\Service\BackgroundService;
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 [
// invert filter if primary is too bright
// to be used for legacy reasons only. Use inline
// svg with proper css variable instead or material
// design icons.
// ⚠️ Using 'no' as a value to make sure we specify an
// invalid one with no fallback. 'unset' could here fallback to some
// other theme with media queries
'--primary-invert-if-bright' => $this->util->invertTextColor($this->primaryColor) ? 'invert(100%)' : 'no',
'--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%)',
];
}
/**
* Generate admin theming background-related variables
*/
protected function generateGlobalBackgroundVariables(): array {
$backgroundDeleted = $this->config->getAppValue(Application::APP_ID, 'backgroundMime', '') === 'backgroundColor';
$hasCustomLogoHeader = $this->imageManager->hasImage('logo') || $this->imageManager->hasImage('logoheader');
$variables = [];
// If primary as background has been request or if we have a custom primary colour
// let's not define the background image
if ($backgroundDeleted && $this->themingDefaults->isUserThemingDisabled()) {
$variables['--image-background-plain'] = 'true';
$variables['--color-background-plain'] = $this->themingDefaults->getColorPrimary();
}
// Register image variables only if custom-defined
foreach (ImageManager::SupportedImageKeys as $image) {
if ($this->imageManager->hasImage($image)) {
$imageUrl = $this->imageManager->getImageUrl($image);
if ($image === 'background') {
// If background deleted is set, ignoring variable
if ($backgroundDeleted) {
continue;
}
$variables['--image-background-size'] = 'cover';
}
$variables["--image-$image"] = "url('" . $imageUrl . "')";
}
}
if ($hasCustomLogoHeader) {
$variables["--image-logoheader-custom"] = 'true';
}
return $variables;
}
/**
* Generate user theming background-related variables
*/
protected function generateUserBackgroundVariables(): array {
$user = $this->userSession->getUser();
if ($user !== null
&& !$this->themingDefaults->isUserThemingDisabled()
&& $this->appManager->isEnabledForUser(Application::APP_ID)) {
$themingBackground = $this->config->getUserValue($user->getUID(), Application::APP_ID, 'background', 'default');
$currentVersion = (int)$this->config->getUserValue($user->getUID(), Application::APP_ID, 'userCacheBuster', '0');
// The user uploaded a custom background
if ($themingBackground === 'custom') {
$cacheBuster = substr(sha1($user->getUID() . '_' . $currentVersion), 0, 8);
return [
'--image-background' => "url('" . $this->urlGenerator->linkToRouteAbsolute('theming.userTheme.getBackground') . "?v=$cacheBuster')",
// TODO: implement primary color from custom background --color-background-plain
];
}
// The user picked a shipped background
if (isset(BackgroundService::SHIPPED_BACKGROUNDS[$themingBackground])) {
return [
'--image-background' => "url('" . $this->urlGenerator->linkTo(Application::APP_ID, "/img/background/$themingBackground") . "')",
'--color-background-plain' => $this->themingDefaults->getColorPrimary(),
'--background-image-invert-if-bright' => BackgroundService::SHIPPED_BACKGROUNDS[$themingBackground]['theming'] ?? null === BackgroundService::THEMING_MODE_DARK ? 'invert(100%)' : 'no',
];
}
// The user picked a static colour
if (substr($themingBackground, 0, 1) === '#') {
return [
'--image-background' => 'no',
'--color-background-plain' => $this->themingDefaults->getColorPrimary(),
];
}
}
return [];
}
}
@@ -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 -79
View File
@@ -24,9 +24,9 @@ declare(strict_types=1);
*/
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;
@@ -34,32 +34,46 @@ use OCP\IConfig;
use OCP\IL10N;
use OCP\IURLGenerator;
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 IAppManager $appManager;
public string $defaultPrimaryColor;
public string $primaryColor;
public function __construct(Util $util,
ThemingDefaults $themingDefaults,
IUserSession $userSession,
IURLGenerator $urlGenerator,
ImageManager $imageManager,
IConfig $config,
IL10N $l) {
IL10N $l,
IAppManager $appManager) {
$this->util = $util;
$this->themingDefaults = $themingDefaults;
$this->userSession = $userSession;
$this->urlGenerator = $urlGenerator;
$this->imageManager = $imageManager;
$this->config = $config;
$this->l = $l;
$this->appManager = $appManager;
$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,17 +103,11 @@ 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,
@@ -119,28 +127,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),
@@ -196,60 +187,19 @@ class DefaultTheme implements ITheme {
// mobile. Keep in sync with core/js/js.js
'--breakpoint-mobile' => '1024px',
// invert filter if primary is too bright
// to be used for legacy reasons only. Use inline
// svg with proper css variable instead or material
// design icons.
// ⚠️ Using 'no' as a value to make sure we specify an
// invalid one with no fallback. 'unset' could here fallback to some
// other theme with media queries
'--primary-invert-if-bright' => $this->util->invertTextColor($this->primaryColor) ? 'invert(100%)' : 'no',
'--background-invert-if-dark' => 'no',
'--background-invert-if-bright' => 'invert(100%)',
'--background-image-invert-if-bright' => 'no',
'--image-main-background' => "url('" . $this->urlGenerator->imagePath('core', 'app-background.jpg') . "')",
// Default last fallback values
'--image-background' => "url('" . $this->urlGenerator->imagePath('core', 'app-background.jpg') . "')",
'--color-background-plain' => $this->defaultPrimaryColor,
];
$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
if ($backgroundDeleted || $hasCustomPrimaryColour) {
$variables["--image-background-plain"] = 'true';
}
// Register image variables only if custom-defined
foreach (['logo', 'logoheader', 'favicon', 'background'] as $image) {
if ($this->imageManager->hasImage($image)) {
$imageUrl = $this->imageManager->getImageUrl($image);
if ($image === 'background') {
// If background deleted is set, ignoring variable
if ($backgroundDeleted) {
continue;
}
$variables['--image-background-size'] = 'cover';
$variables['--image-main-background'] = "url('" . $imageUrl . "')";
}
$variables["--image-$image"] = "url('" . $imageUrl . "')";
}
}
if ($hasCustomLogoHeader) {
$variables["--image-logoheader-custom"] = 'true';
}
$appManager = Server::get(IAppManager::class);
$userSession = Server::get(IUserSession::class);
$user = $userSession->getUser();
if ($appManager->isEnabledForUser(Application::APP_ID) && $user !== null) {
$themingBackground = $this->config->getUserValue($user->getUID(), Application::APP_ID, 'background', 'default');
if ($themingBackground === 'custom') {
$variables['--image-main-background'] = "url('" . $this->urlGenerator->linkToRouteAbsolute('theming.theming.getBackground') . "')";
} elseif ($themingBackground !== 'default' && substr($themingBackground, 0, 1) !== '#') {
$variables['--image-main-background'] = "url('" . $this->urlGenerator->linkTo(Application::APP_ID, "/img/background/$themingBackground") . "')";
}
}
// Primary variables
$variables = array_merge($variables, $this->generatePrimaryVariables($colorMainBackground, $colorMainText));
$variables = array_merge($variables, $this->generateGlobalBackgroundVariables());
$variables = array_merge($variables, $this->generateUserBackgroundVariables());
return $variables;
}
+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 {
+77 -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,51 @@ 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();
if ($this->isUserThemingDisabled()) {
return $defaultColor;
}
// 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 +437,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();
@@ -469,4 +498,11 @@ class ThemingDefaults extends \OC_Defaults {
public function getTextColorPrimary() {
return $this->util->invertTextColor($this->getColorPrimary()) ? '#000000' : '#ffffff';
}
/**
* Has the admin disabled user customization
*/
public function isUserThemingDisabled(): bool {
return $this->config->getAppValue('theming', 'disable-user-theming', 'no') === 'yes';
}
}
+21 -53
View File
@@ -64,17 +64,22 @@
<NcSettingsSection :title="t('theming', 'Background')"
class="background">
<p>{{ t('theming', 'Set a custom background') }}</p>
<BackgroundSettings class="background__grid"
:background="background"
:theming-default-background="themingDefaultBackground"
@update:background="updateBackground" />
<template v-if="isUserThemingDisabled">
<p>{{ t('theming', 'Customization has been disabled by your administrator') }}</p>
</template>
<template v-else>
<p>{{ t('theming', 'Set a custom background') }}</p>
<BackgroundSettings class="background__grid"
:background="background"
:theming-default-background="themingDefaultBackground"
@update:background="updateBackground" />
</template>
</NcSettingsSection>
</section>
</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,21 +88,19 @@ 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')
const isUserThemingDisabled = loadState('theming', 'isUserThemingDisabled')
console.debug('Available themes', availableThemes)
export default {
name: 'UserThemes',
components: {
ItemPreview,
NcCheckboxRadioSwitch,
@@ -112,26 +115,15 @@ export default {
shortcutsDisabled,
background,
themingDefaultBackground,
isUserThemingDisabled,
}
},
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)
},
fonts() {
return this.availableThemes.filter(theme => theme.type === 2)
},
@@ -150,9 +142,11 @@ export default {
.replace('{guidelines}', this.guidelinesLink)
.replace('{linkend}', '</a>')
},
guidelinesLink() {
return '<a target="_blank" href="https://www.w3.org/WAI/standards-guidelines/wcag/" rel="noreferrer nofollow">'
},
descriptionDetail() {
return t(
'theming',
@@ -162,18 +156,16 @@ export default {
.replace('{designteam}', this.designteamLink)
.replace(/\{linkend\}/g, '</a>')
},
issuetrackerLink() {
return '<a target="_blank" href="https://github.com/nextcloud/server/issues/" rel="noreferrer nofollow">'
},
designteamLink() {
return '<a target="_blank" href="https://nextcloud.com/design" rel="noreferrer nofollow">'
},
},
mounted() {
this.updateGlobalStyles()
},
watch: {
shortcutsDisabled(newState) {
this.changeShortcutsDisabled(newState)
@@ -183,34 +175,9 @@ export default {
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'
if (isBackgroundBright) {
document.querySelector('#header').style.setProperty('--primary-invert-if-bright', 'invert(100%)')
document.querySelector('#header').style.setProperty('--color-primary-text', '#000000')
// document.body.removeAttribute('data-theme-dark')
// document.body.setAttribute('data-theme-light', 'true')
} else {
document.querySelector('#header').style.setProperty('--primary-invert-if-bright', 'no')
document.querySelector('#header').style.setProperty('--color-primary-text', '#ffffff')
// 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
this.themes.forEach(theme => {
@@ -224,6 +191,7 @@ export default {
this.updateBodyAttributes()
this.selectItem(enabled, id)
},
changeFont({ enabled, id }) {
// Reset selected and select new one
this.fonts.forEach(font => {
@@ -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)
})
})
+11 -2
View File
@@ -81,7 +81,7 @@ style('theming', 'settings-admin');
<form class="uploadButton" method="post" action="<?php p($_['uploadLogoRoute']) ?>" data-image-key="background">
<input type="hidden" id="theming-backgroundMime" value="<?php p($_['images']['background']['mime']); ?>" />
<input type="hidden" name="key" value="background" />
<label for="upload-login-background"><span><?php p($l->t('Login image')) ?></span></label>
<label for="upload-login-background"><span><?php p($l->t('Background and login image')) ?></span></label>
<input id="upload-login-background" class="fileupload" name="image" type="file">
<label for="upload-login-background" class="button icon-upload svg" id="upload-login-background" title="<?php p($l->t("Upload new login background")) ?>"></label>
<div data-setting="backgroundMime" data-toggle="tooltip" data-original-title="<?php p($l->t('Reset to default')); ?>" class="theme-undo icon icon-history"></div>
@@ -93,7 +93,6 @@ style('theming', 'settings-admin');
</div>
<h3 class="inlineblock"><?php p($l->t('Advanced options')); ?></h3>
<div class="advanced-options">
<div>
<label>
@@ -131,6 +130,16 @@ style('theming', 'settings-admin');
<div data-setting="faviconMime" data-toggle="tooltip" data-original-title="<?php p($l->t('Reset to default')); ?>" class="theme-undo icon icon-history"></div>
</form>
</div>
<div class="advanced-options" id="user-theming">
<label><span><?php p($l->t('User settings')); ?></span></label>
<div>
<p class="info">
<?php p($l->t('Although you can select and customize your instance, users can change their background and colors. If you want to enforce your customization, you can check this box.')); ?>
</p>
<input id="userThemingDisabled" class="checkbox" type="checkbox" <?php p($_['userThemingDisabled'] ? 'checked="checked"' : ''); ?> />
<label for="userThemingDisabled"><?php p($l->t('Disable user theming')) ?></label>
</div>
</div>
</div>
<div class="theming-hints">
@@ -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],

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