Browse Source

Merge remote-tracking branch 'upstream/master' into fix_file_move

Signed-off-by: Tomasz Grobelny <tomasz@grobelny.net>
tags/v15.0.0beta1
Tomasz Grobelny 5 years ago
parent
commit
1fa6e0be23
100 changed files with 2029 additions and 438 deletions
  1. 1
    1
      3rdparty
  2. 84
    41
      README.md
  3. 1
    0
      apps/dav/composer/composer/autoload_classmap.php
  4. 1
    0
      apps/dav/composer/composer/autoload_static.php
  5. 1
    1
      apps/dav/lib/CalDAV/Schedule/IMipPlugin.php
  6. 29
    6
      apps/dav/lib/CardDAV/AddressBookImpl.php
  7. 28
    3
      apps/dav/lib/CardDAV/CardDavBackend.php
  8. 3
    2
      apps/dav/lib/Connector/Sabre/Auth.php
  9. 16
    11
      apps/dav/lib/Connector/Sabre/File.php
  10. 52
    0
      apps/dav/lib/Migration/Version1008Date20181030113700.php
  11. 6
    6
      apps/dav/tests/unit/CardDAV/BirthdayServiceTest.php
  12. 27
    20
      apps/dav/tests/unit/CardDAV/CardDavBackendTest.php
  13. 3
    3
      apps/dav/tests/unit/Connector/Sabre/FileTest.php
  14. 8
    0
      apps/federatedfilesharing/l10n/pl.js
  15. 8
    0
      apps/federatedfilesharing/l10n/pl.json
  16. 8
    2
      apps/files/css/detailsView.scss
  17. 14
    2
      apps/files/css/files.scss
  18. 3
    0
      apps/files/js/detailsview.js
  19. 15
    0
      apps/files/js/detailtabview.js
  20. 1
    0
      apps/files/l10n/cs.js
  21. 1
    0
      apps/files/l10n/cs.json
  22. 14
    14
      apps/files_external/js/app.js
  23. 5
    5
      apps/files_external/js/mountsfilelist.js
  24. 3
    3
      apps/files_external/js/oauth1.js
  25. 3
    3
      apps/files_external/js/oauth2.js
  26. 2
    2
      apps/files_external/js/public_key.js
  27. 5
    5
      apps/files_external/js/rollingqueue.js
  28. 27
    27
      apps/files_external/js/settings.js
  29. 28
    28
      apps/files_external/js/statusmanager.js
  30. 1
    1
      apps/files_external/js/templates.js
  31. 2
    2
      apps/files_external/tests/appSpec.js
  32. 2
    2
      apps/files_external/tests/js/mountsfilelistSpec.js
  33. 2
    2
      apps/files_external/tests/js/settingsSpec.js
  34. 54
    31
      apps/files_sharing/css/sharetabview.scss
  35. 7
    7
      apps/files_sharing/js/share.js
  36. 1
    1
      apps/files_sharing/l10n/cs.js
  37. 1
    1
      apps/files_sharing/l10n/cs.json
  38. 1
    1
      apps/files_sharing/l10n/de.js
  39. 1
    1
      apps/files_sharing/l10n/de.json
  40. 1
    1
      apps/files_sharing/l10n/de_DE.js
  41. 1
    1
      apps/files_sharing/l10n/de_DE.json
  42. 2
    2
      apps/files_sharing/l10n/es.js
  43. 2
    2
      apps/files_sharing/l10n/es.json
  44. 1
    1
      apps/files_sharing/l10n/fr.js
  45. 1
    1
      apps/files_sharing/l10n/fr.json
  46. 3
    0
      apps/files_sharing/l10n/is.js
  47. 3
    0
      apps/files_sharing/l10n/is.json
  48. 1
    1
      apps/files_sharing/l10n/it.js
  49. 1
    1
      apps/files_sharing/l10n/it.json
  50. 1
    1
      apps/files_sharing/l10n/nl.js
  51. 1
    1
      apps/files_sharing/l10n/nl.json
  52. 1
    1
      apps/files_sharing/l10n/pl.js
  53. 1
    1
      apps/files_sharing/l10n/pl.json
  54. 1
    1
      apps/files_sharing/l10n/pt_BR.js
  55. 1
    1
      apps/files_sharing/l10n/pt_BR.json
  56. 1
    1
      apps/files_sharing/l10n/ru.js
  57. 1
    1
      apps/files_sharing/l10n/ru.json
  58. 1
    1
      apps/files_sharing/l10n/sk.js
  59. 1
    1
      apps/files_sharing/l10n/sk.json
  60. 1
    1
      apps/files_sharing/l10n/sr.js
  61. 1
    1
      apps/files_sharing/l10n/sr.json
  62. 1
    1
      apps/files_sharing/l10n/sv.js
  63. 1
    1
      apps/files_sharing/l10n/sv.json
  64. 1
    1
      apps/files_sharing/l10n/tr.js
  65. 1
    1
      apps/files_sharing/l10n/tr.json
  66. 1
    1
      apps/files_sharing/l10n/zh_CN.js
  67. 1
    1
      apps/files_sharing/l10n/zh_CN.json
  68. 59
    16
      apps/files_sharing/lib/Controller/ShareAPIController.php
  69. 9
    6
      apps/files_sharing/lib/Controller/ShareController.php
  70. 6
    3
      apps/files_sharing/templates/public.php
  71. 400
    18
      apps/files_sharing/tests/Controller/ShareAPIControllerTest.php
  72. 116
    1
      apps/files_sharing/tests/Controller/ShareControllerTest.php
  73. 44
    0
      apps/files_trashbin/tests/CapabilitiesTest.php
  74. 4
    0
      apps/files_versions/appinfo/info.xml
  75. 7
    0
      apps/files_versions/composer/composer/autoload_classmap.php
  76. 7
    0
      apps/files_versions/composer/composer/autoload_static.php
  77. 4
    0
      apps/files_versions/js/versionstabview.js
  78. 3
    3
      apps/files_versions/l10n/es.js
  79. 3
    3
      apps/files_versions/l10n/es.json
  80. 40
    8
      apps/files_versions/lib/AppInfo/Application.php
  81. 26
    21
      apps/files_versions/lib/Controller/PreviewController.php
  82. 3
    9
      apps/files_versions/lib/Sabre/RestoreFolder.php
  83. 20
    5
      apps/files_versions/lib/Sabre/RootCollection.php
  84. 15
    7
      apps/files_versions/lib/Sabre/VersionCollection.php
  85. 18
    23
      apps/files_versions/lib/Sabre/VersionFile.php
  86. 28
    9
      apps/files_versions/lib/Sabre/VersionHome.php
  87. 12
    6
      apps/files_versions/lib/Sabre/VersionRoot.php
  88. 11
    14
      apps/files_versions/lib/Storage.php
  89. 26
    0
      apps/files_versions/lib/Versions/BackendNotFoundException.php
  90. 99
    0
      apps/files_versions/lib/Versions/IVersion.php
  91. 81
    0
      apps/files_versions/lib/Versions/IVersionBackend.php
  92. 36
    0
      apps/files_versions/lib/Versions/IVersionManager.php
  93. 105
    0
      apps/files_versions/lib/Versions/LegacyVersionsBackend.php
  94. 113
    0
      apps/files_versions/lib/Versions/Version.php
  95. 93
    0
      apps/files_versions/lib/Versions/VersionManager.php
  96. 37
    18
      apps/files_versions/tests/Controller/PreviewControllerTest.php
  97. 10
    2
      apps/oauth2/lib/Controller/OauthApiController.php
  98. 40
    4
      apps/oauth2/tests/Controller/OauthApiControllerTest.php
  99. 51
    0
      apps/sharebymail/tests/CapabilitiesTest.php
  100. 0
    0
      apps/systemtags/tests/Activity/SettingTest.php

+ 1
- 1
3rdparty

@@ -1 +1 @@
Subproject commit 8633304ce214a7c0ff8e4fd72052a5ed7a444b6d
Subproject commit 214c4155f587f5178d792fe4a839044bdc9982f1

+ 84
- 41
README.md View File

@@ -1,4 +1,4 @@
# Nextcloud Server
# Nextcloud Server
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/nextcloud/server/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/nextcloud/server/?branch=master)
[![codecov](https://codecov.io/gh/nextcloud/server/branch/master/graph/badge.svg)](https://codecov.io/gh/nextcloud/server)
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/209/badge)](https://bestpractices.coreinfrastructure.org/projects/209)
@@ -7,45 +7,102 @@

**A safe home for all your data.**

![](https://github.com/nextcloud/screenshots/blob/master/files/filelist.png)
![](https://raw.githubusercontent.com/nextcloud/screenshots/master/files/Files%20Sharing.png)

## Why is this so awesome?
## Why is this so awesome? 🤩

* :file_folder: **Access your Data** You can store your files, contacts, calendars and more on a server of your choosing.
* :package: **Sync your Data** You keep your files, contacts, calendars and more synchronized amongst your devices.
* :arrows_counterclockwise: **Share your Data** …by giving others access to the stuff you want them to see or to collaborate with.
* :rocket: **Expandable with dozens of Apps** ...like [Calendar](https://github.com/nextcloud/calendar), [Contacts](https://github.com/nextcloud/contacts), [Mail](https://github.com/nextcloud/mail) and all those you can discover in our [App Store](https://apps.nextcloud.com)
* :lock: **Security** with our encryption mechanisms, [HackerOne bounty program](https://hackerone.com/nextcloud) and two-factor authentication.
* 📁 **Access your Data** You can store your files, contacts, calendars and more on a server of your choosing.
* 🔄 **Sync your Data** You keep your files, contacts, calendars and more synchronized amongst your devices.
* 🙌 **Share your Data** …by giving others access to the stuff you want them to see or to collaborate with.
* 🚀 **Expandable with dozens of Apps** ...like [Calendar](https://github.com/nextcloud/calendar), [Contacts](https://github.com/nextcloud/contacts), [Mail](https://github.com/nextcloud/mail) and all those you can discover in our [App Store](https://apps.nextcloud.com)
* 🔒 **Security** with our encryption mechanisms, [HackerOne bounty program](https://hackerone.com/nextcloud) and two-factor authentication.

*You want to learn more about how you can use Nextcloud to access, share and protect your files, calendars, contacts, communication & more at home and at your Enterprise?* [**Learn about all our Features**](https://nextcloud.com/features).
You want to learn more about how you can use Nextcloud to access, share and protect your files, calendars, contacts, communication & more at home and at your Enterprise? [**Learn about all our Features**](https://nextcloud.com/features).

## Get your Nextcloud
## Get your Nextcloud 🚚

- [**Install** a server by yourself on your own hardware or by using one of our ready to use **Appliances**](https://nextcloud.com/install/#instructions-server)
- [Buy one of the awesome **devices** coming with a preinstalled Nextcloud](https://nextcloud.com/devices/)
- [Find a service **provider** who is hosting Nextcloud for you or your company](https://nextcloud.com/providers/)
- ☑️ [**Simply sign up**](https://nextcloud.com/signup/) either through our website or through the apps directly.
- 🖥 [**Install** a server by yourself](https://nextcloud.com/install/#instructions-server) on your own hardware or by using one of our ready to use **appliances**
- 📦 Buy one of the [awesome **devices** coming with a preinstalled Nextcloud](https://nextcloud.com/devices/)
- 🏢 Find a [service **provider**](https://nextcloud.com/providers/) who hosts Nextcloud for you or your company

*Enterprise? Public Sector or Education user? You may want to have a look into the [**Enterprise Support Subscription**](https://nextcloud.com/enterprise/) provided by the Nextcloud GmbH*
Enterprise? Public Sector or Education user? You may want to have a look into the [**Enterprise Support Subscription**](https://nextcloud.com/enterprise/) provided by the Nextcloud GmbH.

## Get in touch
## Get in touch 💬

* :clipboard: [Forum](https://help.nextcloud.com)
* :busts_in_silhouette: [Facebook](https://facebook.com/nextclouders)
* :hatching_chick: [Twitter](https://twitter.com/Nextclouders)
* :elephant: [Mastodon](https://mastodon.xyz/@nextcloud)
* [📋 Forum](https://help.nextcloud.com)
* [👥 Facebook](https://facebook.com/nextclouders)
* [🐣 Twitter](https://twitter.com/Nextclouders)
* [🐘 Mastodon](https://mastodon.xyz/@nextcloud)

[…learn more about how to get support for Nextcloud here!](https://nextcloud.com/support)
You can also [get support for Nextcloud](https://nextcloud.com/support)!

## Join the team :family:

### How to contribute
## Join the team 👪

1. [Set up your local development environment](https://docs.nextcloud.com/server/14/developer_manual/general/devenv.html) :rocket:
2. [Pick a good first issue](https://github.com/nextcloud/server/labels/good%20first%20issue) :notebook:
3. Create a branch, a [Pull Request](https://opensource.guide/how-to-contribute/#opening-a-pull-request) and `@mention` the people from the issue :computer:
4. Wait for it to get merged and :tada:
There are many ways to contribute, of which development is only one! Find out [how to get involved](https://nextcloud.com/contribute/), including as translator, designer, tester, helping others and much more! 😍

### Contribution Guidelines

### Development setup 👩‍💻

1. 🚀 [Set up your local development environment](https://docs.nextcloud.com/server/14/developer_manual/general/devenv.html)
2. 🐛 [Pick a good first issue](https://github.com/nextcloud/server/labels/good%20first%20issue)
3. 👩‍🔧 Create a branch and make your changes. Remember to sign off your commits using `git commit -sm "Your commit message"`
4. ⬆ Create a [pull request](https://opensource.guide/how-to-contribute/#opening-a-pull-request) and `@mention` the people from the issue to review
5. 👍 Fix things that come up during review
6. 🎉 Wait for it to get merged!

Third-party components are handled as git submodules which have to be initialized first. So aside from the regular git checkout invoking `git submodule update --init` or a similar command is needed, for details see Git documentation.

Several apps that are included by default in regular releases such as [First run wizard](https://github.com/nextcloud/firstrunwizard) or [Activity](https://github.com/nextcloud/activity) are missing in `master` and have to be installed manually by cloning them into the `apps` subfolder.

Otherwise, git checkouts can be handled the same as release archives, by using the `stable*` branches. Note they should never be used on production systems.


### Building front-end code 🏗

We move more and more towards using Vue.js in the frontend, starting with Settings. For building the code on changes, use these terminal commands in the `settings` subfolder:

``` bash
# install dependencies
make dev-setup

# build for development
make build-js

# build for development and watch edits
make watch-js

# build for production with minification
make build-js-production

# clean output files
make clean
```

**When making changes, also commit the compiled files!**

We still use Handlebars templates some places in Files and Settings. We will replace these step-by-step with Vue.js, but in the meantime you need to compile them separately.

If you don’t have Handlebars installed yet, you can do it with this terminal command:
```
sudo npm install -g handlebars
```

Then inside the root folder of your local Nextcloud development installation, run this command in the terminal every time you changed a `.handlebars` file to compile it:
```
./build/compile-handlebars-templates.sh
```


### Tools we use 🛠

- [👀 BrowserStack](https://browserstack.com) for cross-browser testing
- [🌊 WAVE](https://wave.webaim.org/extension/) for accessibility testing
- [🚨 Lighthouse](https://developers.google.com/web/tools/lighthouse/) for testing of performance, accessibility and more


## Contribution guidelines 📜

All contributions to this repository from June, 16 2016 on are considered to be
licensed under the AGPLv3 or any later version.
@@ -64,17 +121,3 @@ Please read the [Code of Conduct](https://nextcloud.com/community/code-of-conduc
Please review the [guidelines for contributing](.github/CONTRIBUTING.md) to this repository.

More information how to contribute: [https://nextcloud.com/contribute/](https://nextcloud.com/contribute/)

### Running master checkouts

Third-party components are handled as git submodules which have to be initialized first. So aside from the regular git checkout invoking `git submodule update --init` or a similar command is needed, for details see Git documentation.

Several apps that are included by default in regular releases such as [firstrunwizard](https://github.com/nextcloud/firstrunwizard) or [gallery](https://github.com/nextcloud/gallery) are missing in `master` and have to be installed manually.

That aside Git checkouts can be handled the same as release archives.

Note they should never be used on production systems.

## Tools we use

[![BrowserStack](https://user-images.githubusercontent.com/45821/41675934-61fa3442-74c4-11e8-8c8e-90768c56ba08.png)](https://www.browserstack.com/)

+ 1
- 0
apps/dav/composer/composer/autoload_classmap.php View File

@@ -156,6 +156,7 @@ return array(
'OCA\\DAV\\Migration\\Version1005Date20180530124431' => $baseDir . '/../lib/Migration/Version1005Date20180530124431.php',
'OCA\\DAV\\Migration\\Version1006Date20180619154313' => $baseDir . '/../lib/Migration/Version1006Date20180619154313.php',
'OCA\\DAV\\Migration\\Version1007Date20181007225117' => $baseDir . '/../lib/Migration/Version1007Date20181007225117.php',
'OCA\\DAV\\Migration\\Version1008Date20181030113700' => $baseDir . '/../lib/Migration/Version1008Date20181030113700.php',
'OCA\\DAV\\RootCollection' => $baseDir . '/../lib/RootCollection.php',
'OCA\\DAV\\Server' => $baseDir . '/../lib/Server.php',
'OCA\\DAV\\Settings\\CalDAVSettings' => $baseDir . '/../lib/Settings/CalDAVSettings.php',

+ 1
- 0
apps/dav/composer/composer/autoload_static.php View File

@@ -171,6 +171,7 @@ class ComposerStaticInitDAV
'OCA\\DAV\\Migration\\Version1005Date20180530124431' => __DIR__ . '/..' . '/../lib/Migration/Version1005Date20180530124431.php',
'OCA\\DAV\\Migration\\Version1006Date20180619154313' => __DIR__ . '/..' . '/../lib/Migration/Version1006Date20180619154313.php',
'OCA\\DAV\\Migration\\Version1007Date20181007225117' => __DIR__ . '/..' . '/../lib/Migration/Version1007Date20181007225117.php',
'OCA\\DAV\\Migration\\Version1008Date20181030113700' => __DIR__ . '/..' . '/../lib/Migration/Version1008Date20181030113700.php',
'OCA\\DAV\\RootCollection' => __DIR__ . '/..' . '/../lib/RootCollection.php',
'OCA\\DAV\\Server' => __DIR__ . '/..' . '/../lib/Server.php',
'OCA\\DAV\\Settings\\CalDAVSettings' => __DIR__ . '/..' . '/../lib/Settings/CalDAVSettings.php',

+ 1
- 1
apps/dav/lib/CalDAV/Schedule/IMipPlugin.php View File

@@ -170,7 +170,7 @@ class IMipPlugin extends SabreIMipPlugin {
$vevent = $iTipMessage->message->VEVENT;

$attendee = $this->getCurrentAttendee($iTipMessage);
$defaultLang = $this->config->getUserValue($this->userId, 'core', 'lang', $this->l10nFactory->findLanguage());
$defaultLang = $this->l10nFactory->findLanguage();
$lang = $this->getAttendeeLangOrDefault($defaultLang, $attendee);
$l10n = $this->l10nFactory->get('dav', $lang);


+ 29
- 6
apps/dav/lib/CardDAV/AddressBookImpl.php View File

@@ -88,16 +88,26 @@ class AddressBookImpl implements IAddressBook {
/**
* @param string $pattern which should match within the $searchProperties
* @param array $searchProperties defines the properties within the query pattern should match
* @param array $options - for future use. One should always have options!
* @param array $options Options to define the output format
* - types boolean (since 15.0.0) If set to true, fields that come with a TYPE property will be an array
* example: ['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['type => 'HOME', 'value' => 'g@h.i']]
* @return array an array of contacts which are arrays of key-value-pairs
* example result:
* [
* ['id' => 0, 'FN' => 'Thomas Müller', 'EMAIL' => 'a@b.c', 'GEO' => '37.386013;-122.082932'],
* ['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['d@e.f', 'g@h.i']]
* ]
* @return array an array of contacts which are arrays of key-value-pairs
* @since 5.0.0
*/
public function search($pattern, $searchProperties, $options) {
$results = $this->backend->search($this->getKey(), $pattern, $searchProperties);

$withTypes = \array_key_exists('types', $options) && $options['types'] === true;

$vCards = [];
foreach ($results as $result) {
$vCards[] = $this->vCard2Array($result['uri'], $this->readCard($result['carddata']));
$vCards[] = $this->vCard2Array($result['uri'], $this->readCard($result['carddata']), $withTypes);
}

return $vCards;
@@ -220,7 +230,7 @@ class AddressBookImpl implements IAddressBook {
* @param VCard $vCard
* @return array
*/
protected function vCard2Array($uri, VCard $vCard) {
protected function vCard2Array($uri, VCard $vCard, $withTypes = false) {
$result = [
'URI' => $uri,
];
@@ -255,15 +265,28 @@ class AddressBookImpl implements IAddressBook {
$result[$property->name] = [];
}

$result[$property->name][] = $property->getValue();
$type = $this->getTypeFromProperty($property);
if ($withTypes) {
$result[$property->name][] = [
'type' => $type,
'value' => $property->getValue()
];
} else {
$result[$property->name][] = $property->getValue();
}


} else {
$result[$property->name] = $property->getValue();
}
}

if ($this->addressBookInfo['principaluri'] === 'principals/system/system' &&
$this->addressBookInfo['uri'] === 'system') {
if (
$this->addressBookInfo['principaluri'] === 'principals/system/system' && (
$this->addressBookInfo['uri'] === 'system' ||
$this->addressBookInfo['{DAV:}displayname'] === $this->urlGenerator->getBaseUrl()
)
) {
$result['isLocalSystemBook'] = true;
}
return $result;

+ 28
- 3
apps/dav/lib/CardDAV/CardDavBackend.php View File

@@ -494,7 +494,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
*/
function getCards($addressBookId) {
$query = $this->db->getQueryBuilder();
$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata'])
$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
->from('cards')
->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));

@@ -525,7 +525,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
*/
function getCard($addressBookId, $cardUri) {
$query = $this->db->getQueryBuilder();
$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata'])
$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
->from('cards')
->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
@@ -563,7 +563,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
$cards = [];

$query = $this->db->getQueryBuilder();
$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata'])
$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
->from('cards')
->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
->andWhere($query->expr()->in('uri', $query->createParameter('uri')));
@@ -609,6 +609,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
*/
function createCard($addressBookId, $cardUri, $cardData) {
$etag = md5($cardData);
$uid = $this->getUID($cardData);

$query = $this->db->getQueryBuilder();
$query->insert('cards')
@@ -619,6 +620,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
'addressbookid' => $query->createNamedParameter($addressBookId),
'size' => $query->createNamedParameter(strlen($cardData)),
'etag' => $query->createNamedParameter($etag),
'uid' => $query->createNamedParameter($uid),
])
->execute();

@@ -661,6 +663,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
*/
function updateCard($addressBookId, $cardUri, $cardData) {

$uid = $this->getUID($cardData);
$etag = md5($cardData);
$query = $this->db->getQueryBuilder();
$query->update('cards')
@@ -668,6 +671,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
->set('lastmodified', $query->createNamedParameter(time()))
->set('size', $query->createNamedParameter(strlen($cardData)))
->set('etag', $query->createNamedParameter($etag))
->set('uid', $query->createNamedParameter($uid))
->where($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
->execute();
@@ -1125,4 +1129,25 @@ class CardDavBackend implements BackendInterface, SyncSupport {
$addressbookInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
}
}

/**
* Extract UID from vcard
*
* @param string $cardData the vcard raw data
* @return string the uid
* @throws BadRequest if no UID is available
*/
private function getUID($cardData) {
if ($cardData != '') {
$vCard = Reader::read($cardData);
if ($vCard->UID) {
$uid = $vCard->UID->getValue();
return $uid;
}
// should already be handled, but just in case
throw new BadRequest('vCards on CardDAV servers MUST have a UID property');
}
// should already be handled, but just in case
throw new BadRequest('vCard can not be empty');
}
}

+ 3
- 2
apps/dav/lib/Connector/Sabre/Auth.php View File

@@ -228,11 +228,12 @@ class Auth extends AbstractBasic {
if($this->twoFactorManager->needsSecondFactor($this->userSession->getUser())) {
throw new \Sabre\DAV\Exception\NotAuthenticated('2FA challenge not passed.');
}
if (\OC_User::handleApacheAuth() ||
if (
//Fix for broken webdav clients
($this->userSession->isLoggedIn() && is_null($this->session->get(self::DAV_AUTHENTICATED))) ||
//Well behaved clients that only send the cookie are allowed
($this->userSession->isLoggedIn() && $this->session->get(self::DAV_AUTHENTICATED) === $this->userSession->getUser()->getUID() && $request->getHeader('Authorization') === null)
($this->userSession->isLoggedIn() && $this->session->get(self::DAV_AUTHENTICATED) === $this->userSession->getUser()->getUID() && $request->getHeader('Authorization') === null) ||
\OC_User::handleApacheAuth()
) {
$user = $this->userSession->getUser()->getUID();
\OC_Util::setupFS($user);

+ 16
- 11
apps/dav/lib/Connector/Sabre/File.php View File

@@ -164,14 +164,19 @@ class File extends Node implements IFile {
$this->changeLock(ILockingProvider::LOCK_EXCLUSIVE);
}

$target = $partStorage->fopen($internalPartPath, 'wb');
if ($target === false) {
\OC::$server->getLogger()->error('\OC\Files\Filesystem::fopen() failed', ['app' => 'webdav']);
// because we have no clue about the cause we can only throw back a 500/Internal Server Error
throw new Exception('Could not write file contents');
if ($partStorage->instanceOfStorage(Storage\IWriteStreamStorage::class)) {
$count = $partStorage->writeStream($internalPartPath, $data);
$result = $count > 0;
} else {
$target = $partStorage->fopen($internalPartPath, 'wb');
if ($target === false) {
\OC::$server->getLogger()->error('\OC\Files\Filesystem::fopen() failed', ['app' => 'webdav']);
// because we have no clue about the cause we can only throw back a 500/Internal Server Error
throw new Exception('Could not write file contents');
}
list($count, $result) = \OC_Helper::streamCopy($data, $target);
fclose($target);
}
list($count, $result) = \OC_Helper::streamCopy($data, $target);
fclose($target);

if ($result === false) {
$expected = -1;
@@ -185,7 +190,7 @@ class File extends Node implements IFile {
// double check if the file was fully received
// compare expected and actual size
if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
$expected = (int) $_SERVER['CONTENT_LENGTH'];
$expected = (int)$_SERVER['CONTENT_LENGTH'];
if ($count !== $expected) {
throw new BadRequest('expected filesize ' . $expected . ' got ' . $count);
}
@@ -219,7 +224,7 @@ class File extends Node implements IFile {
$renameOkay = $storage->moveFromStorage($partStorage, $internalPartPath, $internalPath);
$fileExists = $storage->file_exists($internalPath);
if ($renameOkay === false || $fileExists === false) {
\OC::$server->getLogger()->error('renaming part file to final file failed ($run: ' . ( $run ? 'true' : 'false' ) . ', $renameOkay: ' . ( $renameOkay ? 'true' : 'false' ) . ', $fileExists: ' . ( $fileExists ? 'true' : 'false' ) . ')', ['app' => 'webdav']);
\OC::$server->getLogger()->error('renaming part file to final file failed $renameOkay: ' . ($renameOkay ? 'true' : 'false') . ', $fileExists: ' . ($fileExists ? 'true' : 'false') . ')', ['app' => 'webdav']);
throw new Exception('Could not rename part file to final file');
}
} catch (ForbiddenException $ex) {
@@ -246,7 +251,7 @@ class File extends Node implements IFile {
$this->header('X-OC-MTime: accepted');
}
}
if ($view) {
$this->emitPostHooks($exists);
}
@@ -443,7 +448,7 @@ class File extends Node implements IFile {
//detect aborted upload
if (isset ($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
if (isset($_SERVER['CONTENT_LENGTH'])) {
$expected = (int) $_SERVER['CONTENT_LENGTH'];
$expected = (int)$_SERVER['CONTENT_LENGTH'];
if ($bytesWritten !== $expected) {
$chunk_handler->remove($info['index']);
throw new BadRequest(

+ 52
- 0
apps/dav/lib/Migration/Version1008Date20181030113700.php View File

@@ -0,0 +1,52 @@
<?php
/**
* @copyright Copyright (c) 2018, John Molakvoæ (skjnldsv@protonmail.com)
*
* @author John Molakvoæ (skjnldsv) <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\DAV\Migration;

use Closure;

use Doctrine\DBAL\Types\Type;
use OCP\DB\ISchemaWrapper;
use OCP\Migration\SimpleMigrationStep;
use OCP\Migration\IOutput;

/**
* add column for share notes
*
* Class Version15000Date20180927120000
*/
class Version1008Date20181030113700 extends SimpleMigrationStep {
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {

/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();

$table = $schema->getTable('cards');
$table->addColumn('uid', Type::STRING, [
'notnull' => false,
'length' => 255
]);

return $schema;
}
}

+ 6
- 6
apps/dav/tests/unit/CardDAV/BirthdayServiceTest.php View File

@@ -231,9 +231,9 @@ class BirthdayServiceTest extends TestCase {
if ($expectedOp === 'create') {
$service->expects($this->exactly(3))->method('buildDateFromContact')->willReturn(new VCalendar());
$this->calDav->expects($this->exactly(3))->method('createCalendarObject')->withConsecutive(
[1234, 'default-gump.vcf.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.2//EN\r\nCALSCALE:GREGORIAN\r\nEND:VCALENDAR\r\n"],
[1234, 'default-gump.vcf-death.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.2//EN\r\nCALSCALE:GREGORIAN\r\nEND:VCALENDAR\r\n"],
[1234, 'default-gump.vcf-anniversary.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.2//EN\r\nCALSCALE:GREGORIAN\r\nEND:VCALENDAR\r\n"]
[1234, 'default-gump.vcf.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.6//EN\r\nCALSCALE:GREGORIAN\r\nEND:VCALENDAR\r\n"],
[1234, 'default-gump.vcf-death.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.6//EN\r\nCALSCALE:GREGORIAN\r\nEND:VCALENDAR\r\n"],
[1234, 'default-gump.vcf-anniversary.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.6//EN\r\nCALSCALE:GREGORIAN\r\nEND:VCALENDAR\r\n"]
);
}
if ($expectedOp === 'update') {
@@ -241,9 +241,9 @@ class BirthdayServiceTest extends TestCase {
$service->expects($this->exactly(3))->method('birthdayEvenChanged')->willReturn(true);
$this->calDav->expects($this->exactly(3))->method('getCalendarObject')->willReturn(['calendardata' => '']);
$this->calDav->expects($this->exactly(3))->method('updateCalendarObject')->withConsecutive(
[1234, 'default-gump.vcf.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.2//EN\r\nCALSCALE:GREGORIAN\r\nEND:VCALENDAR\r\n"],
[1234, 'default-gump.vcf-death.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.2//EN\r\nCALSCALE:GREGORIAN\r\nEND:VCALENDAR\r\n"],
[1234, 'default-gump.vcf-anniversary.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.2//EN\r\nCALSCALE:GREGORIAN\r\nEND:VCALENDAR\r\n"]
[1234, 'default-gump.vcf.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.6//EN\r\nCALSCALE:GREGORIAN\r\nEND:VCALENDAR\r\n"],
[1234, 'default-gump.vcf-death.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.6//EN\r\nCALSCALE:GREGORIAN\r\nEND:VCALENDAR\r\n"],
[1234, 'default-gump.vcf-anniversary.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.6//EN\r\nCALSCALE:GREGORIAN\r\nEND:VCALENDAR\r\n"]
);
}


+ 27
- 20
apps/dav/tests/unit/CardDAV/CardDavBackendTest.php View File

@@ -87,6 +87,14 @@ class CardDavBackendTest extends TestCase {
const UNIT_TEST_USER1 = 'principals/users/carddav-unit-test1';
const UNIT_TEST_GROUP = 'principals/groups/carddav-unit-test-group';

private $vcardTest = 'BEGIN:VCARD'.PHP_EOL.
'VERSION:3.0'.PHP_EOL.
'PRODID:-//Sabre//Sabre VObject 4.1.2//EN'.PHP_EOL.
'UID:Test'.PHP_EOL.
'FN:Test'.PHP_EOL.
'N:Test;;;;'.PHP_EOL.
'END:VCARD';

public function setUp() {
parent::setUp();

@@ -121,7 +129,6 @@ class CardDavBackendTest extends TestCase {
$query = $this->db->getQueryBuilder();
$query->delete('cards')->execute();


$this->tearDown();
}

@@ -217,8 +224,8 @@ class CardDavBackendTest extends TestCase {

$uri = $this->getUniqueID('card');
// updateProperties is expected twice, once for createCard and once for updateCard
$backend->expects($this->at(0))->method('updateProperties')->with($bookId, $uri, '');
$backend->expects($this->at(1))->method('updateProperties')->with($bookId, $uri, '***');
$backend->expects($this->at(0))->method('updateProperties')->with($bookId, $uri, $this->vcardTest);
$backend->expects($this->at(1))->method('updateProperties')->with($bookId, $uri, $this->vcardTest);

// Expect event
$this->dispatcher->expects($this->at(0))
@@ -226,16 +233,16 @@ class CardDavBackendTest extends TestCase {
->with('\OCA\DAV\CardDAV\CardDavBackend::createCard', $this->callback(function(GenericEvent $e) use ($bookId, $uri) {
return $e->getArgument('addressBookId') === $bookId &&
$e->getArgument('cardUri') === $uri &&
$e->getArgument('cardData') === '';
$e->getArgument('cardData') === $this->vcardTest;
}));

// create a card
$backend->createCard($bookId, $uri, '');
$backend->createCard($bookId, $uri, $this->vcardTest);

// get all the cards
$cards = $backend->getCards($bookId);
$this->assertEquals(1, count($cards));
$this->assertEquals('', $cards[0]['carddata']);
$this->assertEquals($this->vcardTest, $cards[0]['carddata']);

// get the cards
$card = $backend->getCard($bookId, $uri);
@@ -245,7 +252,7 @@ class CardDavBackendTest extends TestCase {
$this->assertArrayHasKey('lastmodified', $card);
$this->assertArrayHasKey('etag', $card);
$this->assertArrayHasKey('size', $card);
$this->assertEquals('', $card['carddata']);
$this->assertEquals($this->vcardTest, $card['carddata']);

// Expect event
$this->dispatcher->expects($this->at(0))
@@ -253,13 +260,13 @@ class CardDavBackendTest extends TestCase {
->with('\OCA\DAV\CardDAV\CardDavBackend::updateCard', $this->callback(function(GenericEvent $e) use ($bookId, $uri) {
return $e->getArgument('addressBookId') === $bookId &&
$e->getArgument('cardUri') === $uri &&
$e->getArgument('cardData') === '***';
$e->getArgument('cardData') === $this->vcardTest;
}));

// update the card
$backend->updateCard($bookId, $uri, '***');
$backend->updateCard($bookId, $uri, $this->vcardTest);
$card = $backend->getCard($bookId, $uri);
$this->assertEquals('***', $card['carddata']);
$this->assertEquals($this->vcardTest, $card['carddata']);

// Expect event
$this->dispatcher->expects($this->at(0))
@@ -290,18 +297,18 @@ class CardDavBackendTest extends TestCase {

// create a card
$uri0 = $this->getUniqueID('card');
$this->backend->createCard($bookId, $uri0, '');
$this->backend->createCard($bookId, $uri0, $this->vcardTest);
$uri1 = $this->getUniqueID('card');
$this->backend->createCard($bookId, $uri1, '');
$this->backend->createCard($bookId, $uri1, $this->vcardTest);
$uri2 = $this->getUniqueID('card');
$this->backend->createCard($bookId, $uri2, '');
$this->backend->createCard($bookId, $uri2, $this->vcardTest);

// get all the cards
$cards = $this->backend->getCards($bookId);
$this->assertEquals(3, count($cards));
$this->assertEquals('', $cards[0]['carddata']);
$this->assertEquals('', $cards[1]['carddata']);
$this->assertEquals('', $cards[2]['carddata']);
$this->assertEquals($this->vcardTest, $cards[0]['carddata']);
$this->assertEquals($this->vcardTest, $cards[1]['carddata']);
$this->assertEquals($this->vcardTest, $cards[2]['carddata']);

// get the cards
$cards = $this->backend->getMultipleCards($bookId, [$uri1, $uri2]);
@@ -312,7 +319,7 @@ class CardDavBackendTest extends TestCase {
$this->assertArrayHasKey('lastmodified', $card);
$this->assertArrayHasKey('etag', $card);
$this->assertArrayHasKey('size', $card);
$this->assertEquals('', $card['carddata']);
$this->assertEquals($this->vcardTest, $card['carddata']);
}

// delete the card
@@ -357,7 +364,7 @@ class CardDavBackendTest extends TestCase {
->method('purgeProperties');

// create a card
$this->backend->createCard($bookId, $uri, '');
$this->backend->createCard($bookId, $uri, $this->vcardTest);

// delete the card
$this->assertTrue($this->backend->deleteCard($bookId, $uri));
@@ -380,7 +387,7 @@ class CardDavBackendTest extends TestCase {

// add a change
$uri0 = $this->getUniqueID('card');
$this->backend->createCard($bookId, $uri0, '');
$this->backend->createCard($bookId, $uri0, $this->vcardTest);

// look for changes
$changes = $this->backend->getChangesForAddressBook($bookId, $syncToken, 1);
@@ -683,7 +690,7 @@ class CardDavBackendTest extends TestCase {
}

$result = $this->backend->getContact(0, 'uri0');
$this->assertSame(7, count($result));
$this->assertSame(8, count($result));
$this->assertSame(0, (int)$result['addressbookid']);
$this->assertSame('uri0', $result['uri']);
$this->assertSame(5489543, (int)$result['lastmodified']);

+ 3
- 3
apps/dav/tests/unit/Connector/Sabre/FileTest.php View File

@@ -164,7 +164,7 @@ class FileTest extends \Test\TestCase {
public function testSimplePutFails($thrownException, $expectedException, $checkPreviousClass = true) {
// setup
$storage = $this->getMockBuilder(Local::class)
->setMethods(['fopen'])
->setMethods(['writeStream'])
->setConstructorArgs([['datadir' => \OC::$server->getTempManager()->getTemporaryFolder()]])
->getMock();
\OC\Files\Filesystem::mount($storage, [], $this->user . '/');
@@ -182,11 +182,11 @@ class FileTest extends \Test\TestCase {

if ($thrownException !== null) {
$storage->expects($this->once())
->method('fopen')
->method('writeStream')
->will($this->throwException($thrownException));
} else {
$storage->expects($this->once())
->method('fopen')
->method('writeStream')
->will($this->returnValue(false));
}


+ 8
- 0
apps/federatedfilesharing/l10n/pl.js View File

@@ -17,8 +17,12 @@ OC.L10N.register(
"Couldn't establish a federated share." : "Nie udało się ustalić Stowarzyszonego udostępnienia.",
"Couldn't establish a federated share, maybe the password was wrong." : "Nie udało się ustalić Stowarzyszonego udostępnienia, może być błędne hasło.",
"Federated Share request sent, you will receive an invitation. Check your notifications." : "Wysłano żądanie Udostępniania Stowarzyszonego, otrzymasz zaproszenie. Sprawdzaj swoje powiadomienia.",
"Couldn't establish a federated share, it looks like the server to federate with is too old (Nextcloud <= 9)." : "Nie można ustanowić stowarzyszonego udziału, wygląda na to, że serwer wybrany do stowarzyszenia jest zbyt stary (Nextcloud <= 9).",
"It is not allowed to send federated group shares from this server." : "Wysyłanie stowarzyszonych udziałów grupowych z tego serwera jest zabronione.",
"Sharing %1$s failed, because this item is already shared with %2$s" : "Udostępnianie %1$snie powiodło się, ponieważ ten element jest już udostępniony jako %2$s",
"Not allowed to create a federated share with the same user" : "Nie można tworzyć stowarzyszonego udziału z tym samym użytkownikiem",
"File is already shared with %s" : "Plik jest już współdzielony z %s",
"Sharing %1$s failed, could not find %2$s, maybe the server is currently unreachable or uses a self-signed certificate." : "Udostępnianie %1$s nie powiodło się, nie można odnaleźć %2$s, być może serwer jest nieosiągalny lub używa certyfikatu z podpisem własnym.",
"Could not find share" : "Nie można znaleźć powiązania",
"You received \"%3$s\" as a remote share from %1$s (on behalf of %2$s)" : "Otrzymałeś \"%3$s\" w zdalnym udziale z %1$s (w imieniu %2$s)",
"You received {share} as a remote share from {user} (on behalf of {behalf})" : "Otrzymałeś {share} w zdalnym udziale z {user} (w imieniu {behalf})",
@@ -29,11 +33,15 @@ OC.L10N.register(
"Share with me through my #Nextcloud Federated Cloud ID, see %s" : "Podziel się ze mną przez mój ID #Nextcloud Stowarzyszonej Chmury, zobacz %s",
"Share with me through my #Nextcloud Federated Cloud ID" : "Podziel się ze mną przez mój ID #Nextcloud Stowarzyszonej Chmury",
"Sharing" : "Udostępnianie",
"Federated file sharing" : "Stowarzyszone udziały grupowe",
"Provide federated file sharing across servers" : "Zezwól na stowarzyszone udostępnianie plików na serwerach",
"Federated Cloud Sharing" : "Dzielenie się ze Stowarzyszoną Chmurą",
"Open documentation" : "Otwórz dokumentację",
"Adjust how people can share between servers." : "Dostosuj ustawienia współdzielenia między serwerami.",
"Allow users on this server to send shares to other servers" : "Zezwalaj użytkownikom na tym serwerze wysłać udostępnienia do innych serwerów",
"Allow users on this server to receive shares from other servers" : "Zezwalaj użytkownikom na tym serwerze do otrzymania udostępnień z innych serwerów",
"Allow users on this server to send shares to groups on other servers" : "Pozwól użytkownikom na tym serwerze na wysyłanie udziałów do grup na innych serwerach",
"Allow users on this server to receive group shares from other servers" : "Pozwól użytkownikom na tym serwerze na odbieranie udziałów grupy z innych serwerów",
"Search global and public address book for users" : "Szukaj użytkowników w globalnej i publicznej książce adresowej",
"Allow users to publish their data to a global and public address book" : "Pozwól użytkownikom na publikację ich danych do globalnej i publicznej książki adresowej",
"Federated Cloud" : "Stowarzyszona Chmura",

+ 8
- 0
apps/federatedfilesharing/l10n/pl.json View File

@@ -15,8 +15,12 @@
"Couldn't establish a federated share." : "Nie udało się ustalić Stowarzyszonego udostępnienia.",
"Couldn't establish a federated share, maybe the password was wrong." : "Nie udało się ustalić Stowarzyszonego udostępnienia, może być błędne hasło.",
"Federated Share request sent, you will receive an invitation. Check your notifications." : "Wysłano żądanie Udostępniania Stowarzyszonego, otrzymasz zaproszenie. Sprawdzaj swoje powiadomienia.",
"Couldn't establish a federated share, it looks like the server to federate with is too old (Nextcloud <= 9)." : "Nie można ustanowić stowarzyszonego udziału, wygląda na to, że serwer wybrany do stowarzyszenia jest zbyt stary (Nextcloud <= 9).",
"It is not allowed to send federated group shares from this server." : "Wysyłanie stowarzyszonych udziałów grupowych z tego serwera jest zabronione.",
"Sharing %1$s failed, because this item is already shared with %2$s" : "Udostępnianie %1$snie powiodło się, ponieważ ten element jest już udostępniony jako %2$s",
"Not allowed to create a federated share with the same user" : "Nie można tworzyć stowarzyszonego udziału z tym samym użytkownikiem",
"File is already shared with %s" : "Plik jest już współdzielony z %s",
"Sharing %1$s failed, could not find %2$s, maybe the server is currently unreachable or uses a self-signed certificate." : "Udostępnianie %1$s nie powiodło się, nie można odnaleźć %2$s, być może serwer jest nieosiągalny lub używa certyfikatu z podpisem własnym.",
"Could not find share" : "Nie można znaleźć powiązania",
"You received \"%3$s\" as a remote share from %1$s (on behalf of %2$s)" : "Otrzymałeś \"%3$s\" w zdalnym udziale z %1$s (w imieniu %2$s)",
"You received {share} as a remote share from {user} (on behalf of {behalf})" : "Otrzymałeś {share} w zdalnym udziale z {user} (w imieniu {behalf})",
@@ -27,11 +31,15 @@
"Share with me through my #Nextcloud Federated Cloud ID, see %s" : "Podziel się ze mną przez mój ID #Nextcloud Stowarzyszonej Chmury, zobacz %s",
"Share with me through my #Nextcloud Federated Cloud ID" : "Podziel się ze mną przez mój ID #Nextcloud Stowarzyszonej Chmury",
"Sharing" : "Udostępnianie",
"Federated file sharing" : "Stowarzyszone udziały grupowe",
"Provide federated file sharing across servers" : "Zezwól na stowarzyszone udostępnianie plików na serwerach",
"Federated Cloud Sharing" : "Dzielenie się ze Stowarzyszoną Chmurą",
"Open documentation" : "Otwórz dokumentację",
"Adjust how people can share between servers." : "Dostosuj ustawienia współdzielenia między serwerami.",
"Allow users on this server to send shares to other servers" : "Zezwalaj użytkownikom na tym serwerze wysłać udostępnienia do innych serwerów",
"Allow users on this server to receive shares from other servers" : "Zezwalaj użytkownikom na tym serwerze do otrzymania udostępnień z innych serwerów",
"Allow users on this server to send shares to groups on other servers" : "Pozwól użytkownikom na tym serwerze na wysyłanie udziałów do grup na innych serwerach",
"Allow users on this server to receive group shares from other servers" : "Pozwól użytkownikom na tym serwerze na odbieranie udziałów grupy z innych serwerów",
"Search global and public address book for users" : "Szukaj użytkowników w globalnej i publicznej książce adresowej",
"Allow users to publish their data to a global and public address book" : "Pozwól użytkownikom na publikację ich danych do globalnej i publicznej książki adresowej",
"Federated Cloud" : "Stowarzyszona Chmura",

+ 8
- 2
apps/files/css/detailsView.scss View File

@@ -15,7 +15,13 @@

#app-sidebar .mainFileInfoView .permalink {
padding: 6px 10px;
vertical-align: text-top;
vertical-align: top;
opacity: .6;

&:hover,
&:focus {
opacity: 1;
}
}
#app-sidebar .mainFileInfoView .permalink-field>input {
clear: both;
@@ -87,7 +93,7 @@
}

#app-sidebar .fileName h3 {
width: calc(100% - 36px); /* 36px is the with of the copy link icon */
width: calc(100% - 42px); /* 36px is the with of the copy link icon, but this breaks so we add some more to be sure */
display: inline-block;
padding: 5px 0;
margin: -5px 0;

+ 14
- 2
apps/files/css/files.scss View File

@@ -8,7 +8,12 @@
}

/* FILE MENU */
.actions { padding:5px; height:32px; display: inline-block; float: left; }
.actions {
padding: 5px;
height: 100%;
display: inline-block;
float: left;
}
.actions input, .actions button, .actions .button { margin:0; float:left; }
.actions .button a { color: #555; }
.actions .button a:hover,
@@ -316,6 +321,7 @@ table td.filename .thumbnail {
background-size: 32px;
margin-left: 9px;
margin-top: 9px;
border-radius: var(--border-radius);
cursor: pointer;
position: absolute;
z-index: 4;
@@ -658,8 +664,14 @@ table.dragshadow td.size {
top: 100%;
margin-top: 4px;
min-width: 100px;
margin-left: 7px;
margin-left: 22px; /* Align left edge below center of + button … */
transform: translateX(-50%); /* … then center it below button */
z-index: 1001;

/* Center triangle */
&::after {
left: calc(50% - 8px) !important;
}
}

#filestable .filename .action .icon,

+ 3
- 0
apps/files/js/detailsview.js View File

@@ -174,6 +174,9 @@
// hide other tabs
$tabsContainer.find('.tab').addClass('hidden');

$tabsContainer.attr('class', 'tabsContainer');
$tabsContainer.addClass(tabView.getTabsContainerExtraClasses());

// tab already rendered ?
if (!$tabEl.length) {
// render tab

+ 15
- 0
apps/files/js/detailtabview.js View File

@@ -40,6 +40,21 @@
}
},

/**
* Returns the extra CSS classes used by the tabs container when this
* tab is the selected one.
*
* In general you should not extend this method, as tabs should not
* modify the classes of its container; this is reserved as a last
* resort for very specific cases in which there is no other way to get
* the proper style or behaviour.
*
* @return {String} space-separated CSS classes
*/
getTabsContainerExtraClasses: function() {
return '';
},

/**
* Returns the tab label
*

+ 1
- 0
apps/files/l10n/cs.js View File

@@ -142,6 +142,7 @@ OC.L10N.register(
"WebDAV" : "WebDAV",
"Use this address to <a href=\"%s\" target=\"_blank\" rel=\"noreferrer noopener\">access your Files via WebDAV</a>" : "Použijte tuto adresu pro <a href=\"%s\" target=\"_blank\" rel=\"noreferrer noopener\">přístup k vašim souborům přes WebDAV</a>",
"Cancel upload" : "Zrušit nahrávání",
"Toggle grid view" : "Přepnout zobrazení mřížky",
"No files in here" : "Žádné soubory",
"Upload some content or sync with your devices!" : "Nahrajte nějaký obsah nebo synchronizujte se svými přístroji!",
"No entries found in this folder" : "V této složce nebylo nic nalezeno",

+ 1
- 0
apps/files/l10n/cs.json View File

@@ -140,6 +140,7 @@
"WebDAV" : "WebDAV",
"Use this address to <a href=\"%s\" target=\"_blank\" rel=\"noreferrer noopener\">access your Files via WebDAV</a>" : "Použijte tuto adresu pro <a href=\"%s\" target=\"_blank\" rel=\"noreferrer noopener\">přístup k vašim souborům přes WebDAV</a>",
"Cancel upload" : "Zrušit nahrávání",
"Toggle grid view" : "Přepnout zobrazení mřížky",
"No files in here" : "Žádné soubory",
"Upload some content or sync with your devices!" : "Nahrajte nějaký obsah nebo synchronizujte se svými přístroji!",
"No entries found in this folder" : "V této složce nebylo nic nalezeno",

+ 14
- 14
apps/files_external/js/app.js View File

@@ -8,16 +8,16 @@
*
*/

if (!OCA.External) {
if (!OCA.Files_External) {
/**
* @namespace
*/
OCA.External = {};
OCA.Files_External = {};
}
/**
* @namespace
*/
OCA.External.App = {
OCA.Files_External.App = {

fileList: null,

@@ -26,7 +26,7 @@ OCA.External.App = {
return this.fileList;
}

this.fileList = new OCA.External.FileList(
this.fileList = new OCA.Files_External.FileList(
$el,
{
fileActions: this._createFileActions()
@@ -67,10 +67,10 @@ OCA.External.App = {

$(document).ready(function() {
$('#app-content-extstoragemounts').on('show', function(e) {
OCA.External.App.initList($(e.target));
OCA.Files_External.App.initList($(e.target));
});
$('#app-content-extstoragemounts').on('hide', function() {
OCA.External.App.removeList();
OCA.Files_External.App.removeList();
});

/* Status Manager */
@@ -82,27 +82,27 @@ $(document).ready(function() {
if (e.dir === '/') {
var mount_point = e.previousDir.split('/', 2)[1];
// Every time that we return to / root folder from a mountpoint, mount_point status is rechecked
OCA.External.StatusManager.getMountPointList(function() {
OCA.External.StatusManager.recheckConnectivityForMount([mount_point], true);
OCA.Files_External.StatusManager.getMountPointList(function() {
OCA.Files_External.StatusManager.recheckConnectivityForMount([mount_point], true);
});
}
})
.on('fileActionsReady', function(e){
if ($.isArray(e.$files)) {
if (OCA.External.StatusManager.mountStatus === null ||
OCA.External.StatusManager.mountPointList === null ||
_.size(OCA.External.StatusManager.mountStatus) !== _.size(OCA.External.StatusManager.mountPointList)) {
if (OCA.Files_External.StatusManager.mountStatus === null ||
OCA.Files_External.StatusManager.mountPointList === null ||
_.size(OCA.Files_External.StatusManager.mountStatus) !== _.size(OCA.Files_External.StatusManager.mountPointList)) {
// Will be the very first check when the files view will be loaded
OCA.External.StatusManager.launchFullConnectivityCheckOneByOne();
OCA.Files_External.StatusManager.launchFullConnectivityCheckOneByOne();
} else {
// When we change between general files view and external files view
OCA.External.StatusManager.getMountPointList(function(){
OCA.Files_External.StatusManager.getMountPointList(function(){
var fileNames = [];
$.each(e.$files, function(key, value){
fileNames.push(value.attr('data-file'));
});
// Recheck if launched but work from cache
OCA.External.StatusManager.recheckConnectivityForMount(fileNames, false);
OCA.Files_External.StatusManager.recheckConnectivityForMount(fileNames, false);
});
}
}

+ 5
- 5
apps/files_external/js/mountsfilelist.js View File

@@ -10,7 +10,7 @@
(function() {

/**
* @class OCA.External.FileList
* @class OCA.Files_External.FileList
* @augments OCA.Files.FileList
*
* @classdesc External storage file list.
@@ -27,7 +27,7 @@
};

FileList.prototype = _.extend({}, OCA.Files.FileList.prototype,
/** @lends OCA.External.FileList.prototype */ {
/** @lends OCA.Files_External.FileList.prototype */ {
appName: 'External storages',

_allowSelection: false,
@@ -43,7 +43,7 @@
},

/**
* @param {OCA.External.MountPointInfo} fileData
* @param {OCA.Files_External.MountPointInfo} fileData
*/
_createRow: function(fileData) {
// TODO: hook earlier and render the whole row here
@@ -138,12 +138,12 @@
/**
* Mount point info attributes.
*
* @typedef {Object} OCA.External.MountPointInfo
* @typedef {Object} OCA.Files_External.MountPointInfo
*
* @property {String} name mount point name
* @property {String} scope mount point scope "personal" or "system"
* @property {String} backend external storage backend name
*/

OCA.External.FileList = FileList;
OCA.Files_External.FileList = FileList;
})();

+ 3
- 3
apps/files_external/js/oauth1.js View File

@@ -4,7 +4,7 @@ $(document).ready(function() {
$tr.find('.configuration input.auth-param').attr('disabled', 'disabled').addClass('disabled-success');
}

OCA.External.Settings.mountConfig.whenSelectAuthMechanism(function($tr, authMechanism, scheme, onCompletion) {
OCA.Files_External.Settings.mountConfig.whenSelectAuthMechanism(function($tr, authMechanism, scheme, onCompletion) {
if (authMechanism === 'oauth1::oauth1') {
var config = $tr.find('.configuration');
config.append($(document.createElement('input'))
@@ -34,7 +34,7 @@ $(document).ready(function() {
$(token).val(result.access_token);
$(token_secret).val(result.access_token_secret);
$(configured).val('true');
OCA.External.Settings.mountConfig.saveStorageConfig($tr, function(status) {
OCA.Files_External.Settings.mountConfig.saveStorageConfig($tr, function(status) {
if (status) {
displayGranted($tr);
}
@@ -64,7 +64,7 @@ $(document).ready(function() {
$(configured).val('false');
$(token).val(result.data.request_token);
$(token_secret).val(result.data.request_token_secret);
OCA.External.Settings.mountConfig.saveStorageConfig(tr, function() {
OCA.Files_External.Settings.mountConfig.saveStorageConfig(tr, function() {
window.location = result.data.url;
});
} else {

+ 3
- 3
apps/files_external/js/oauth2.js View File

@@ -4,7 +4,7 @@ $(document).ready(function() {
$tr.find('.configuration input.auth-param').attr('disabled', 'disabled').addClass('disabled-success');
}

OCA.External.Settings.mountConfig.whenSelectAuthMechanism(function($tr, authMechanism, scheme, onCompletion) {
OCA.Files_External.Settings.mountConfig.whenSelectAuthMechanism(function($tr, authMechanism, scheme, onCompletion) {
if (authMechanism === 'oauth2::oauth2') {
var config = $tr.find('.configuration');
config.append($(document.createElement('input'))
@@ -43,7 +43,7 @@ $(document).ready(function() {
if (result && result.status == 'success') {
$(token).val(result.data.token);
$(configured).val('true');
OCA.External.Settings.mountConfig.saveStorageConfig($tr, function(status) {
OCA.Files_External.Settings.mountConfig.saveStorageConfig($tr, function(status) {
if (status) {
displayGranted($tr);
}
@@ -80,7 +80,7 @@ $(document).ready(function() {
if (result && result.status == 'success') {
$(configured).val('false');
$(token).val('false');
OCA.External.Settings.mountConfig.saveStorageConfig(tr, function(status) {
OCA.Files_External.Settings.mountConfig.saveStorageConfig(tr, function(status) {
window.location = result.data.url;
});
} else {

+ 2
- 2
apps/files_external/js/public_key.js View File

@@ -1,6 +1,6 @@
$(document).ready(function() {

OCA.External.Settings.mountConfig.whenSelectAuthMechanism(function($tr, authMechanism, scheme, onCompletion) {
OCA.Files_External.Settings.mountConfig.whenSelectAuthMechanism(function($tr, authMechanism, scheme, onCompletion) {
if (scheme === 'publickey' && authMechanism === 'publickey::rsa') {
var config = $tr.find('.configuration');
if ($(config).find('[name="public_key_generate"]').length === 0) {
@@ -53,7 +53,7 @@ $(document).ready(function() {
if (result && result.status === 'success') {
$(config).find('[data-parameter="public_key"]').val(result.data.public_key).keyup();
$(config).find('[data-parameter="private_key"]').val(result.data.private_key);
OCA.External.Settings.mountConfig.saveStorageConfig(tr, function() {
OCA.Files_External.Settings.mountConfig.saveStorageConfig(tr, function() {
// Nothing to do
});
} else {

+ 5
- 5
apps/files_external/js/rollingqueue.js View File

@@ -124,14 +124,14 @@ var RollingQueue = function (functionList, queueWindow, callback) {
};
};

if (!OCA.External) {
OCA.External = {};
if (!OCA.Files_External) {
OCA.Files_External = {};
}

if (!OCA.External.StatusManager) {
OCA.External.StatusManager = {};
if (!OCA.Files_External.StatusManager) {
OCA.Files_External.StatusManager = {};
}

OCA.External.StatusManager.RollingQueue = RollingQueue;
OCA.Files_External.StatusManager.RollingQueue = RollingQueue;

})();

+ 27
- 27
apps/files_external/js/settings.js View File

@@ -163,7 +163,7 @@ function addSelect2 ($elements, userListLimit) {
}

/**
* @class OCA.External.Settings.StorageConfig
* @class OCA.Files_External.Settings.StorageConfig
*
* @classdesc External storage config
*/
@@ -185,7 +185,7 @@ StorageConfig.Visibility = {
DEFAULT: 3
};
/**
* @memberof OCA.External.Settings
* @memberof OCA.Files_External.Settings
*/
StorageConfig.prototype = {
_url: null,
@@ -348,8 +348,8 @@ StorageConfig.prototype = {
};

/**
* @class OCA.External.Settings.GlobalStorageConfig
* @augments OCA.External.Settings.StorageConfig
* @class OCA.Files_External.Settings.GlobalStorageConfig
* @augments OCA.Files_External.Settings.StorageConfig
*
* @classdesc Global external storage config
*/
@@ -359,10 +359,10 @@ var GlobalStorageConfig = function(id) {
this.applicableGroups = [];
};
/**
* @memberOf OCA.External.Settings
* @memberOf OCA.Files_External.Settings
*/
GlobalStorageConfig.prototype = _.extend({}, StorageConfig.prototype,
/** @lends OCA.External.Settings.GlobalStorageConfig.prototype */ {
/** @lends OCA.Files_External.Settings.GlobalStorageConfig.prototype */ {
_url: 'apps/files_external/globalstorages',

/**
@@ -402,8 +402,8 @@ GlobalStorageConfig.prototype = _.extend({}, StorageConfig.prototype,
});

/**
* @class OCA.External.Settings.UserStorageConfig
* @augments OCA.External.Settings.StorageConfig
* @class OCA.Files_External.Settings.UserStorageConfig
* @augments OCA.Files_External.Settings.StorageConfig
*
* @classdesc User external storage config
*/
@@ -411,13 +411,13 @@ var UserStorageConfig = function(id) {
this.id = id;
};
UserStorageConfig.prototype = _.extend({}, StorageConfig.prototype,
/** @lends OCA.External.Settings.UserStorageConfig.prototype */ {
/** @lends OCA.Files_External.Settings.UserStorageConfig.prototype */ {
_url: 'apps/files_external/userstorages'
});

/**
* @class OCA.External.Settings.UserGlobalStorageConfig
* @augments OCA.External.Settings.StorageConfig
* @class OCA.Files_External.Settings.UserGlobalStorageConfig
* @augments OCA.Files_External.Settings.StorageConfig
*
* @classdesc User external storage config
*/
@@ -425,13 +425,13 @@ var UserGlobalStorageConfig = function (id) {
this.id = id;
};
UserGlobalStorageConfig.prototype = _.extend({}, StorageConfig.prototype,
/** @lends OCA.External.Settings.UserStorageConfig.prototype */ {
/** @lends OCA.Files_External.Settings.UserStorageConfig.prototype */ {

_url: 'apps/files_external/userglobalstorages'
});

/**
* @class OCA.External.Settings.MountOptionsDropdown
* @class OCA.Files_External.Settings.MountOptionsDropdown
*
* @classdesc Dropdown for mount options
*
@@ -440,7 +440,7 @@ UserGlobalStorageConfig.prototype = _.extend({}, StorageConfig.prototype,
var MountOptionsDropdown = function() {
};
/**
* @memberof OCA.External.Settings
* @memberof OCA.Files_External.Settings
*/
MountOptionsDropdown.prototype = {
/**
@@ -462,7 +462,7 @@ MountOptionsDropdown.prototype = {
MountOptionsDropdown._last.hide();
}

var $el = $(OCA.External.Templates.mountOptionsDropDown({
var $el = $(OCA.Files_External.Templates.mountOptionsDropDown({
mountOptionsEncodingLabel: t('files_external', 'Compatibility with Mac NFD encoding (slow)'),
mountOptionsEncryptLabel: t('files_external', 'Enable encryption'),
mountOptionsPreviewsLabel: t('files_external', 'Enable previews'),
@@ -549,7 +549,7 @@ MountOptionsDropdown.prototype = {
};

/**
* @class OCA.External.Settings.MountConfigListView
* @class OCA.Files_External.Settings.MountConfigListView
*
* @classdesc Mount configuration list view
*
@@ -574,7 +574,7 @@ MountConfigListView.ParameterTypes = {
};

/**
* @memberOf OCA.External.Settings
* @memberOf OCA.Files_External.Settings
*/
MountConfigListView.prototype = _.extend({

@@ -633,9 +633,9 @@ MountConfigListView.prototype = _.extend({
this.$el = $el;
this._isPersonal = ($el.data('admin') !== true);
if (this._isPersonal) {
this._storageConfigClass = OCA.External.Settings.UserStorageConfig;
this._storageConfigClass = OCA.Files_External.Settings.UserStorageConfig;
} else {
this._storageConfigClass = OCA.External.Settings.GlobalStorageConfig;
this._storageConfigClass = OCA.Files_External.Settings.GlobalStorageConfig;
}

if (options && !_.isUndefined(options.userListLimit)) {
@@ -1008,7 +1008,7 @@ MountConfigListView.prototype = _.extend({
* Gets the storage model from the given row
*
* @param $tr row element
* @return {OCA.External.StorageConfig} storage model instance
* @return {OCA.Files_External.StorageConfig} storage model instance
*/
getStorageConfig: function($tr) {
var storageId = $tr.data('id');
@@ -1367,13 +1367,13 @@ $(document).ready(function() {
});

// global instance
OCA.External.Settings.mountConfig = mountConfigListView;
OCA.Files_External.Settings.mountConfig = mountConfigListView;

/**
* Legacy
*
* @namespace
* @deprecated use OCA.External.Settings.mountConfig instead
* @deprecated use OCA.Files_External.Settings.mountConfig instead
*/
OC.MountConfig = {
saveStorage: _.bind(mountConfigListView.saveStorageConfig, mountConfigListView)
@@ -1382,14 +1382,14 @@ $(document).ready(function() {

// export

OCA.External = OCA.External || {};
OCA.Files_External = OCA.Files_External || {};
/**
* @namespace
*/
OCA.External.Settings = OCA.External.Settings || {};
OCA.Files_External.Settings = OCA.Files_External.Settings || {};

OCA.External.Settings.GlobalStorageConfig = GlobalStorageConfig;
OCA.External.Settings.UserStorageConfig = UserStorageConfig;
OCA.External.Settings.MountConfigListView = MountConfigListView;
OCA.Files_External.Settings.GlobalStorageConfig = GlobalStorageConfig;
OCA.Files_External.Settings.UserStorageConfig = UserStorageConfig;
OCA.Files_External.Settings.MountConfigListView = MountConfigListView;

})();

+ 28
- 28
apps/files_external/js/statusmanager.js View File

@@ -14,15 +14,15 @@

/** @global Handlebars */

if (!OCA.External) {
OCA.External = {};
if (!OCA.Files_External) {
OCA.Files_External = {};
}

if (!OCA.External.StatusManager) {
OCA.External.StatusManager = {};
if (!OCA.Files_External.StatusManager) {
OCA.Files_External.StatusManager = {};
}

OCA.External.StatusManager = {
OCA.Files_External.StatusManager = {

mountStatus: null,
mountPointList: null,
@@ -209,18 +209,18 @@ OCA.External.StatusManager = {

var mountPoint = mountData.mount_point;
if (mountStatus.status > 0) {
var trElement = FileList.findFileEl(OCA.External.StatusManager.Utils.jqSelEscape(mountPoint));
var trElement = FileList.findFileEl(OCA.Files_External.StatusManager.Utils.jqSelEscape(mountPoint));

var route = OCA.External.StatusManager.Utils.getIconRoute(trElement) + '-error';
var route = OCA.Files_External.StatusManager.Utils.getIconRoute(trElement) + '-error';

if (OCA.External.StatusManager.Utils.isCorrectViewAndRootFolder()) {
OCA.External.StatusManager.Utils.showIconError(mountPoint, $.proxy(OCA.External.StatusManager.manageMountPointError, OCA.External.StatusManager), route);
if (OCA.Files_External.StatusManager.Utils.isCorrectViewAndRootFolder()) {
OCA.Files_External.StatusManager.Utils.showIconError(mountPoint, $.proxy(OCA.Files_External.StatusManager.manageMountPointError, OCA.Files_External.StatusManager), route);
}
return false;
} else {
if (OCA.External.StatusManager.Utils.isCorrectViewAndRootFolder()) {
OCA.External.StatusManager.Utils.restoreFolder(mountPoint);
OCA.External.StatusManager.Utils.toggleLink(mountPoint, true, true);
if (OCA.Files_External.StatusManager.Utils.isCorrectViewAndRootFolder()) {
OCA.Files_External.StatusManager.Utils.restoreFolder(mountPoint);
OCA.Files_External.StatusManager.Utils.toggleLink(mountPoint, true, true);
}
return true;
}
@@ -235,7 +235,7 @@ OCA.External.StatusManager = {
processMountList: function (mountList) {
var elementList = null;
$.each(mountList, function (name, value) {
var trElement = $('#fileList tr[data-file=\"' + OCA.External.StatusManager.Utils.jqSelEscape(value.mount_point) + '\"]'); //FileList.findFileEl(OCA.External.StatusManager.Utils.jqSelEscape(value.mount_point));
var trElement = $('#fileList tr[data-file=\"' + OCA.Files_External.StatusManager.Utils.jqSelEscape(value.mount_point) + '\"]'); //FileList.findFileEl(OCA.Files_External.StatusManager.Utils.jqSelEscape(value.mount_point));
trElement.attr('data-external-backend', value.backend);
if (elementList) {
elementList = elementList.add(trElement);
@@ -245,14 +245,14 @@ OCA.External.StatusManager = {
});

if (elementList instanceof $) {
if (OCA.External.StatusManager.Utils.isCorrectViewAndRootFolder()) {
if (OCA.Files_External.StatusManager.Utils.isCorrectViewAndRootFolder()) {
// Put their custom icon
OCA.External.StatusManager.Utils.changeFolderIcon(elementList);
OCA.Files_External.StatusManager.Utils.changeFolderIcon(elementList);
// Save default view
OCA.External.StatusManager.Utils.storeDefaultFolderIconAndBgcolor(elementList);
OCA.Files_External.StatusManager.Utils.storeDefaultFolderIconAndBgcolor(elementList);
// Disable row until check status
elementList.addClass('externalDisabledRow');
OCA.External.StatusManager.Utils.toggleLink(elementList.find('a.name'), false, false);
OCA.Files_External.StatusManager.Utils.toggleLink(elementList.find('a.name'), false, false);
}
}
},
@@ -289,7 +289,7 @@ OCA.External.StatusManager = {
ajaxQueue.push(queueElement);
});

var rolQueue = new OCA.External.StatusManager.RollingQueue(ajaxQueue, 4, function () {
var rolQueue = new OCA.Files_External.StatusManager.RollingQueue(ajaxQueue, 4, function () {
if (!self.notificationHasShown) {
var showNotification = false;
$.each(self.mountStatus, function (key, value) {
@@ -335,7 +335,7 @@ OCA.External.StatusManager = {
};
ajaxQueue.push(queueElement);
});
new OCA.External.StatusManager.RollingQueue(ajaxQueue, 4).runQueue();
new OCA.Files_External.StatusManager.RollingQueue(ajaxQueue, 4).runQueue();
},


@@ -392,7 +392,7 @@ OCA.External.StatusManager = {
* @param mountData
*/
showCredentialsDialog: function (mountPoint, mountData) {
var dialog = $(OCA.External.Templates.credentialsDialog({
var dialog = $(OCA.Files_External.Templates.credentialsDialog({
credentials_text: t('files_external', 'Please enter the credentials for the {mount} mount', {
'mount': mountPoint
}),
@@ -422,7 +422,7 @@ OCA.External.StatusManager = {
OC.Notification.show(t('files_external', 'Credentials saved'), {type: 'error'});
dialog.ocdialog('close');
/* Trigger status check again */
OCA.External.StatusManager.recheckConnectivityForMount([OC.basename(data.mountPoint)], true);
OCA.Files_External.StatusManager.recheckConnectivityForMount([OC.basename(data.mountPoint)], true);
},
error: function () {
$('.oc-dialog-close').show();
@@ -461,11 +461,11 @@ OCA.External.StatusManager = {
}
};

OCA.External.StatusManager.Utils = {
OCA.Files_External.StatusManager.Utils = {

showIconError: function (folder, clickAction, errorImageUrl) {
var imageUrl = "url(" + errorImageUrl + ")";
var trFolder = $('#fileList tr[data-file=\"' + OCA.External.StatusManager.Utils.jqSelEscape(folder) + '\"]'); //FileList.findFileEl(OCA.External.StatusManager.Utils.jqSelEscape(folder));
var trFolder = $('#fileList tr[data-file=\"' + OCA.Files_External.StatusManager.Utils.jqSelEscape(folder) + '\"]'); //FileList.findFileEl(OCA.Files_External.StatusManager.Utils.jqSelEscape(folder));
this.changeFolderIcon(folder, imageUrl);
this.toggleLink(folder, false, clickAction);
trFolder.addClass('externalErroredRow');
@@ -479,7 +479,7 @@ OCA.External.StatusManager.Utils = {
if (folder instanceof $) {
trFolder = folder;
} else {
trFolder = $('#fileList tr[data-file=\"' + OCA.External.StatusManager.Utils.jqSelEscape(folder) + '\"]'); //FileList.findFileEl(OCA.External.StatusManager.Utils.jqSelEscape(folder)); //$('#fileList tr[data-file=\"' + OCA.External.StatusManager.Utils.jqSelEscape(folder) + '\"]');
trFolder = $('#fileList tr[data-file=\"' + OCA.Files_External.StatusManager.Utils.jqSelEscape(folder) + '\"]'); //FileList.findFileEl(OCA.Files_External.StatusManager.Utils.jqSelEscape(folder)); //$('#fileList tr[data-file=\"' + OCA.Files_External.StatusManager.Utils.jqSelEscape(folder) + '\"]');
}
trFolder.each(function () {
var thisElement = $(this);
@@ -505,8 +505,8 @@ OCA.External.StatusManager.Utils = {
if (folder instanceof $) {
trFolder = folder;
} else {
// can't use here FileList.findFileEl(OCA.External.StatusManager.Utils.jqSelEscape(folder)); return incorrect instance of filelist
trFolder = $('#fileList tr[data-file=\"' + OCA.External.StatusManager.Utils.jqSelEscape(folder) + '\"]');
// can't use here FileList.findFileEl(OCA.Files_External.StatusManager.Utils.jqSelEscape(folder)); return incorrect instance of filelist
trFolder = $('#fileList tr[data-file=\"' + OCA.Files_External.StatusManager.Utils.jqSelEscape(folder) + '\"]');
}
trFolder.removeClass('externalErroredRow').removeClass('externalDisabledRow');
var tdChilds = trFolder.find("td.filename div.thumbnail");
@@ -526,14 +526,14 @@ OCA.External.StatusManager.Utils = {
if (filename instanceof $) {
//trElementList
$.each(filename, function (index) {
route = OCA.External.StatusManager.Utils.getIconRoute($(this));
route = OCA.Files_External.StatusManager.Utils.getIconRoute($(this));
$(this).attr("data-icon", route);
$(this).find('td.filename div.thumbnail').css('background-image', "url(" + route + ")").css('display', 'none').css('display', 'inline');
});
} else {
file = $("#fileList tr[data-file=\"" + this.jqSelEscape(filename) + "\"] > td.filename div.thumbnail");
var parentTr = file.parents('tr:first');
route = OCA.External.StatusManager.Utils.getIconRoute(parentTr);
route = OCA.Files_External.StatusManager.Utils.getIconRoute(parentTr);
parentTr.attr("data-icon", route);
file.css('background-image', "url(" + route + ")").css('display', 'none').css('display', 'inline');
}

+ 1
- 1
apps/files_external/js/templates.js View File

@@ -1,5 +1,5 @@
(function() {
var template = Handlebars.template, templates = OCA.External.Templates = OCA.External.Templates || {};
var template = Handlebars.template, templates = OCA.Files_External.Templates = OCA.Files_External.Templates || {};
templates['credentialsDialog'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;


+ 2
- 2
apps/files_external/tests/appSpec.js View File

@@ -19,8 +19,8 @@
*
*/

describe('OCA.External.App tests', function() {
var App = OCA.External.App;
describe('OCA.Files_External.App tests', function() {
var App = OCA.Files_External.App;
var fileList;

beforeEach(function() {

+ 2
- 2
apps/files_external/tests/js/mountsfilelistSpec.js View File

@@ -8,7 +8,7 @@
*
*/

describe('OCA.External.FileList tests', function() {
describe('OCA.Files_External.FileList tests', function() {
var testFiles, alertStub, notificationStub, fileList;

beforeEach(function() {
@@ -62,7 +62,7 @@ describe('OCA.External.FileList tests', function() {
var ocsResponse;

beforeEach(function() {
fileList = new OCA.External.FileList(
fileList = new OCA.Files_External.FileList(
$('#app-content-container')
);


+ 2
- 2
apps/files_external/tests/js/settingsSpec.js View File

@@ -8,7 +8,7 @@
*
*/

describe('OCA.External.Settings tests', function() {
describe('OCA.Files_External.Settings tests', function() {
var clock;
var select2Stub;
var select2ApplicableUsers;
@@ -156,7 +156,7 @@ describe('OCA.External.Settings tests', function() {

beforeEach(function() {
var $el = $('#externalStorage');
view = new OCA.External.Settings.MountConfigListView($el, {encryptionEnabled: false});
view = new OCA.Files_External.Settings.MountConfigListView($el, {encryptionEnabled: false});
});
afterEach(function() {
view = null;

+ 54
- 31
apps/files_sharing/css/sharetabview.scss View File

@@ -4,6 +4,10 @@

.share-autocomplete-item {
display: flex;

&.merged {
margin-left: 32px;
}
.autocomplete-item-text {
margin-left: 10px;
margin-right: 10px;
@@ -12,6 +16,27 @@
overflow: hidden;
line-height: 32px;
vertical-align: middle;
flex-grow: 1;
.ui-state-highlight {
border: none;
margin: 0;
}
}
&.with-description {
.autocomplete-item-text {
line-height: 100%;
}
}
.autocomplete-item-details {
display: block;
line-height: 130%;
font-size: 90%;
opacity: 0.7;
}

.icon {
opacity: .7;
margin-right: 7px;
}
}

@@ -26,7 +51,6 @@
top: 0px;
}
.shareWithConfirm,
.clipboardButton,
.linkPass .icon-loading-small {
position: absolute;
right: 2px;
@@ -49,21 +73,17 @@
.datepicker {
margin-left: 35px;
}
.clipboardButton {
position: relative;
top: initial;
right: initial;
padding: 0;
}
.share-add {
input.share-note-delete {
display: none;
border: none;
background-color: transparent;
width: 44px !important;
padding: 0;
flex: 0 0 44px;
margin-left: auto;
&.hidden {
display: none;
}
}
}
// note
@@ -98,6 +118,11 @@
margin-bottom: 10px;
}
}

/* Border above last entry '+ Add another share' to separate it from current link settings */
.new-share {
border-top: 1px solid var(--color-border);
}
}
.linkPass .icon-loading-small {
margin-right: 0px;
@@ -157,7 +182,7 @@
.avatar {
width: 32px;
height: 32px;
background-color: var(--color-background-darker);
background-color: var(--color-primary);
}
}
.unshare img {
@@ -169,31 +194,29 @@
display: flex;
align-items: center;
white-space: nowrap;
// can edit label
> .shareOption > label {
padding: 13px;
padding-right: 0;
}
// more menu
> .share-menu {
position: relative;
// icons
> .icon:not(.hidden),
.share-menu > .icon:not(.hidden) {
padding: 14px;
height: 44px;
width: 44px;
opacity: .5;
display: block;
.icon-more {
padding: 14px;
height: 44px;
width: 44px;
opacity: .5;
display: block;
cursor: pointer;
}
cursor: pointer;

&:hover,
&:focus,
&:active {
.icon-more {
opacity: .7;;
}
opacity: .7;;
}
}

// more menu
> .share-menu {
position: relative;
display: block;
}
}
.username {
padding: 0 8px;
@@ -204,8 +227,8 @@
}

.ui-autocomplete {
/* limit dropdown height to 4 1/2 entries */
max-height: calc(36px * 4.5);;
/* limit dropdown height to 6 1/2 entries */
max-height: calc(36px * 6.5);
overflow-y: auto;
overflow-x: hidden;
z-index: 1550 !important;
@@ -244,4 +267,4 @@

.resharerInfoView.subView {
position: relative;
}
}

+ 7
- 7
apps/files_sharing/js/share.js View File

@@ -193,15 +193,15 @@
var $tr = fileList.findFileEl(fileInfoModel.get('name'));

// We count email shares as link share
var hasLinkShare = shareModel.hasLinkShare();
var hasLinkShares = shareModel.hasLinkShares();
shareModel.get('shares').forEach(function (share) {
if (share.share_type === OC.Share.SHARE_TYPE_EMAIL) {
hasLinkShare = true;
hasLinkShares = true;
}
});

OCA.Sharing.Util._updateFileListDataAttributes(fileList, $tr, shareModel);
if (!OCA.Sharing.Util._updateFileActionIcon($tr, shareModel.hasUserShares(), hasLinkShare)) {
if (!OCA.Sharing.Util._updateFileActionIcon($tr, shareModel.hasUserShares(), hasLinkShares)) {
// remove icon, if applicable
OC.Share.markFileAsShared($tr, false, false);
}
@@ -249,15 +249,15 @@
*
* @param $tr file element of the file to update
* @param {boolean} hasUserShares true if a user share exists
* @param {boolean} hasLinkShare true if a link share exists
* @param {boolean} hasLinkShares true if a link share exists
*
* @return {boolean} true if the icon was set, false otherwise
*/
_updateFileActionIcon: function($tr, hasUserShares, hasLinkShare) {
_updateFileActionIcon: function($tr, hasUserShares, hasLinkShares) {
// if the statuses are loaded already, use them for the icon
// (needed when scrolling to the next page)
if (hasUserShares || hasLinkShare || $tr.attr('data-share-recipient-data') || $tr.attr('data-share-owner')) {
OC.Share.markFileAsShared($tr, true, hasLinkShare);
if (hasUserShares || hasLinkShares || $tr.attr('data-share-recipient-data') || $tr.attr('data-share-owner')) {
OC.Share.markFileAsShared($tr, true, hasLinkShares);
return true;
}
return false;

+ 1
- 1
apps/files_sharing/l10n/cs.js View File

@@ -88,9 +88,9 @@ OC.L10N.register(
"Public link sharing is disabled by the administrator" : "Sdílení veřejným odkazem je zakázáno správcem",
"Public upload disabled by the administrator" : "Nahrávání veřejností zakázáno správcem",
"Public upload is only possible for publicly shared folders" : "Veřejné nahrávání je možné pouze do veřejně sdílených šložek",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Sdílení %s posláním hesla přes Nextcloud Talk se nezdařilo, protože Nextcloud Talk není zapnutý",
"Invalid date, date format must be YYYY-MM-DD" : "Neplatné datum – je třeba, aby jeho formát byl RRRR-MM-DD",
"Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Sdílení %1$s se nezdařilo, protože podpůrná vrstva nepodporuje typ sdílení %2$s",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Sdílení %s posláním hesla přes Nextcloud Talk se nezdařilo, protože Nextcloud Talk není zapnutý",
"You cannot share to a Circle if the app is not enabled" : "Do okruhu nemůžete sdílet, pokud není aplikace zapnuta",
"Please specify a valid circle" : "Zadejte platný okruh",
"Sharing %s failed because the back end does not support room shares" : "Sdílení %s se nezdařilo protože podpůrná vrstva nepodporuje sdílení místností",

+ 1
- 1
apps/files_sharing/l10n/cs.json View File

@@ -86,9 +86,9 @@
"Public link sharing is disabled by the administrator" : "Sdílení veřejným odkazem je zakázáno správcem",
"Public upload disabled by the administrator" : "Nahrávání veřejností zakázáno správcem",
"Public upload is only possible for publicly shared folders" : "Veřejné nahrávání je možné pouze do veřejně sdílených šložek",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Sdílení %s posláním hesla přes Nextcloud Talk se nezdařilo, protože Nextcloud Talk není zapnutý",
"Invalid date, date format must be YYYY-MM-DD" : "Neplatné datum – je třeba, aby jeho formát byl RRRR-MM-DD",
"Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Sdílení %1$s se nezdařilo, protože podpůrná vrstva nepodporuje typ sdílení %2$s",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Sdílení %s posláním hesla přes Nextcloud Talk se nezdařilo, protože Nextcloud Talk není zapnutý",
"You cannot share to a Circle if the app is not enabled" : "Do okruhu nemůžete sdílet, pokud není aplikace zapnuta",
"Please specify a valid circle" : "Zadejte platný okruh",
"Sharing %s failed because the back end does not support room shares" : "Sdílení %s se nezdařilo protože podpůrná vrstva nepodporuje sdílení místností",

+ 1
- 1
apps/files_sharing/l10n/de.js View File

@@ -88,9 +88,9 @@ OC.L10N.register(
"Public link sharing is disabled by the administrator" : "Die öffentliche Freigabe von Links ist durch den Administrator deaktiviert",
"Public upload disabled by the administrator" : "Das öffentliche Hochladen ist durch den Administrator deaktiviert",
"Public upload is only possible for publicly shared folders" : "Das öffentliche Hochladen ist nur für öffentlich freigegebene Ordner erlaubt",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "%s Freigeben: Senden des Passwortes über Nextcloud Talk gescheitert, da Nextcloud Talk nicht verfügbar ist",
"Invalid date, date format must be YYYY-MM-DD" : "Ungültiges Datum, das Datumsformat muss JJJJ-MM-TT sein",
"Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Freigabe von %1$s fehlgeschlagen, da das Backend die Freigabe vom Typ %2$s nicht erlaubt",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "%s Freigeben: Senden des Passwortes über Nextcloud Talk gescheitert, da Nextcloud Talk nicht verfügbar ist",
"You cannot share to a Circle if the app is not enabled" : "Du kannst nichts mit einem Kreis teilen, wenn die App nicht aktiviert ist",
"Please specify a valid circle" : "Bitte einen gültigen Kreis angeben",
"Sharing %s failed because the back end does not support room shares" : "Freigabe von %s fehlgeschlagen, da das Backend die Freigabe von Räumen nicht unterstützt",

+ 1
- 1
apps/files_sharing/l10n/de.json View File

@@ -86,9 +86,9 @@
"Public link sharing is disabled by the administrator" : "Die öffentliche Freigabe von Links ist durch den Administrator deaktiviert",
"Public upload disabled by the administrator" : "Das öffentliche Hochladen ist durch den Administrator deaktiviert",
"Public upload is only possible for publicly shared folders" : "Das öffentliche Hochladen ist nur für öffentlich freigegebene Ordner erlaubt",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "%s Freigeben: Senden des Passwortes über Nextcloud Talk gescheitert, da Nextcloud Talk nicht verfügbar ist",
"Invalid date, date format must be YYYY-MM-DD" : "Ungültiges Datum, das Datumsformat muss JJJJ-MM-TT sein",
"Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Freigabe von %1$s fehlgeschlagen, da das Backend die Freigabe vom Typ %2$s nicht erlaubt",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "%s Freigeben: Senden des Passwortes über Nextcloud Talk gescheitert, da Nextcloud Talk nicht verfügbar ist",
"You cannot share to a Circle if the app is not enabled" : "Du kannst nichts mit einem Kreis teilen, wenn die App nicht aktiviert ist",
"Please specify a valid circle" : "Bitte einen gültigen Kreis angeben",
"Sharing %s failed because the back end does not support room shares" : "Freigabe von %s fehlgeschlagen, da das Backend die Freigabe von Räumen nicht unterstützt",

+ 1
- 1
apps/files_sharing/l10n/de_DE.js View File

@@ -88,9 +88,9 @@ OC.L10N.register(
"Public link sharing is disabled by the administrator" : "Die öffentliche Freigabe von Links ist durch den Administrator deaktiviert",
"Public upload disabled by the administrator" : "Das öffentliche Hochladen ist durch den Administrator deaktiviert",
"Public upload is only possible for publicly shared folders" : "Das öffentliche Hochladen ist nur für öffentlich freigegebene Ordner erlaubt",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "%s Freigeben: Senden des Passwortes über Nextcloud Talk gescheitert, da Nextcloud Talk nicht verfügbar ist",
"Invalid date, date format must be YYYY-MM-DD" : "Ungültiges Datum, das Datumsformat muss JJJJ-MM-TT sein",
"Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Freigabe von %1$s fehlgeschlagen, da das Backend die Freigabe vom Typ %2$s nicht erlaubt",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "%s Freigeben: Senden des Passwortes über Nextcloud Talk gescheitert, da Nextcloud Talk nicht verfügbar ist",
"You cannot share to a Circle if the app is not enabled" : "Sie können nichts mit einem Kreis teilen, wenn die App nicht aktiviert ist",
"Please specify a valid circle" : "Bitte einen gültigen Kreis angeben",
"Sharing %s failed because the back end does not support room shares" : "Freigabe von %s fehlgeschlagen, da das Backend die Freigabe von Räumen nicht unterstützt",

+ 1
- 1
apps/files_sharing/l10n/de_DE.json View File

@@ -86,9 +86,9 @@
"Public link sharing is disabled by the administrator" : "Die öffentliche Freigabe von Links ist durch den Administrator deaktiviert",
"Public upload disabled by the administrator" : "Das öffentliche Hochladen ist durch den Administrator deaktiviert",
"Public upload is only possible for publicly shared folders" : "Das öffentliche Hochladen ist nur für öffentlich freigegebene Ordner erlaubt",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "%s Freigeben: Senden des Passwortes über Nextcloud Talk gescheitert, da Nextcloud Talk nicht verfügbar ist",
"Invalid date, date format must be YYYY-MM-DD" : "Ungültiges Datum, das Datumsformat muss JJJJ-MM-TT sein",
"Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Freigabe von %1$s fehlgeschlagen, da das Backend die Freigabe vom Typ %2$s nicht erlaubt",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "%s Freigeben: Senden des Passwortes über Nextcloud Talk gescheitert, da Nextcloud Talk nicht verfügbar ist",
"You cannot share to a Circle if the app is not enabled" : "Sie können nichts mit einem Kreis teilen, wenn die App nicht aktiviert ist",
"Please specify a valid circle" : "Bitte einen gültigen Kreis angeben",
"Sharing %s failed because the back end does not support room shares" : "Freigabe von %s fehlgeschlagen, da das Backend die Freigabe von Räumen nicht unterstützt",

+ 2
- 2
apps/files_sharing/l10n/es.js View File

@@ -88,9 +88,9 @@ OC.L10N.register(
"Public link sharing is disabled by the administrator" : "Compartir enlaces de forma pública está deshabilitado por el administrador",
"Public upload disabled by the administrator" : "La subida pública está deshabilitado por el administrador",
"Public upload is only possible for publicly shared folders" : "La subida publica solo es posible para las carpetas publicas compartidas",
"Invalid date, date format must be YYYY-MM-DD" : "Fecha inválida, el formato de las fechas debe ser YYYY-MM-DD",
"Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Compartir %1$s ha fallado porque el motor no permite compartidos del tipo %2$s",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Compartir %s enviando la contraseña por Nextcloud Talk ha falllado porque Nextcloud Talk no está activado",
"Invalid date, date format must be YYYY-MM-DD" : "Fecha inválida, el formato de las fechas debe ser YYYY-MM-DD",
"Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Compartir %1$s ha fallado porque el backend no permite compartidos del tipo %2$s",
"You cannot share to a Circle if the app is not enabled" : "No puede compartir a un Circulo si la aplicación no esta activada",
"Please specify a valid circle" : "Por favor especifique un circulo valido",
"Sharing %s failed because the back end does not support room shares" : "Compartir %s ha fallado porque el backend no soporta habitaciones compartidas",

+ 2
- 2
apps/files_sharing/l10n/es.json View File

@@ -86,9 +86,9 @@
"Public link sharing is disabled by the administrator" : "Compartir enlaces de forma pública está deshabilitado por el administrador",
"Public upload disabled by the administrator" : "La subida pública está deshabilitado por el administrador",
"Public upload is only possible for publicly shared folders" : "La subida publica solo es posible para las carpetas publicas compartidas",
"Invalid date, date format must be YYYY-MM-DD" : "Fecha inválida, el formato de las fechas debe ser YYYY-MM-DD",
"Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Compartir %1$s ha fallado porque el motor no permite compartidos del tipo %2$s",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Compartir %s enviando la contraseña por Nextcloud Talk ha falllado porque Nextcloud Talk no está activado",
"Invalid date, date format must be YYYY-MM-DD" : "Fecha inválida, el formato de las fechas debe ser YYYY-MM-DD",
"Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Compartir %1$s ha fallado porque el backend no permite compartidos del tipo %2$s",
"You cannot share to a Circle if the app is not enabled" : "No puede compartir a un Circulo si la aplicación no esta activada",
"Please specify a valid circle" : "Por favor especifique un circulo valido",
"Sharing %s failed because the back end does not support room shares" : "Compartir %s ha fallado porque el backend no soporta habitaciones compartidas",

+ 1
- 1
apps/files_sharing/l10n/fr.js View File

@@ -88,9 +88,9 @@ OC.L10N.register(
"Public link sharing is disabled by the administrator" : "Le partage de lien public a été désactivé par l'administrateur",
"Public upload disabled by the administrator" : "Téléversement public désactivé par l'administrateur",
"Public upload is only possible for publicly shared folders" : "Le téléversement public est possible uniquement pour les dossiers partagés publiquement",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Partager %s en envoyant le mot de passe par Nextcloud Talk a échoué car Nextcloud Talk n'est pas activé.",
"Invalid date, date format must be YYYY-MM-DD" : "Date non valide, le format doit être DD/MM/YYYY",
"Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Le partage %1$s a échoué parce que l'infrastructure n'autorise pas les partages du type %2$s",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Partager %s en envoyant le mot de passe par Nextcloud Talk a échoué car Nextcloud Talk n'est pas activé.",
"You cannot share to a Circle if the app is not enabled" : "Vous ne pouvez pas partager au Cercle si l'application n'est pas activée",
"Please specify a valid circle" : "Veuillez entrer un cercle valide",
"Sharing %s failed because the back end does not support room shares" : "Le partage %s a échoué parce que l'arrière-plan ne prend pas en charge les partages.",

+ 1
- 1
apps/files_sharing/l10n/fr.json View File

@@ -86,9 +86,9 @@
"Public link sharing is disabled by the administrator" : "Le partage de lien public a été désactivé par l'administrateur",
"Public upload disabled by the administrator" : "Téléversement public désactivé par l'administrateur",
"Public upload is only possible for publicly shared folders" : "Le téléversement public est possible uniquement pour les dossiers partagés publiquement",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Partager %s en envoyant le mot de passe par Nextcloud Talk a échoué car Nextcloud Talk n'est pas activé.",
"Invalid date, date format must be YYYY-MM-DD" : "Date non valide, le format doit être DD/MM/YYYY",
"Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Le partage %1$s a échoué parce que l'infrastructure n'autorise pas les partages du type %2$s",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Partager %s en envoyant le mot de passe par Nextcloud Talk a échoué car Nextcloud Talk n'est pas activé.",
"You cannot share to a Circle if the app is not enabled" : "Vous ne pouvez pas partager au Cercle si l'application n'est pas activée",
"Please specify a valid circle" : "Veuillez entrer un cercle valide",
"Sharing %s failed because the back end does not support room shares" : "Le partage %s a échoué parce que l'arrière-plan ne prend pas en charge les partages.",

+ 3
- 0
apps/files_sharing/l10n/is.js View File

@@ -88,15 +88,18 @@ OC.L10N.register(
"Public link sharing is disabled by the administrator" : "Deiling opinberra sameignartengla hefur verið gerð óvirk af kerfisstjóra.",
"Public upload disabled by the administrator" : "Opinber innsending hefur verið gerð óvirk af kerfisstjóra.",
"Public upload is only possible for publicly shared folders" : "Opinber innsending er einungis möguleg í möppur sem er deilt opinberlega",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Deiling á %s með því að senda lykilorð með Nextcloud Talk mistókst því að Nextcloud Talk er ekki virkt",
"Invalid date, date format must be YYYY-MM-DD" : "Ógild dagsetning, dagsetningasniðið verður að vera ÁÁÁÁ-MM-DD",
"Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Deiling %1$s mistókst, því bakvinnslukerfið leyfir ekki sameignir af gerðinni %2$s",
"You cannot share to a Circle if the app is not enabled" : "Þú getur ekki deilt með hring ef forritið er ekki virkt",
"Please specify a valid circle" : "Settu inn gildan hring",
"Sharing %s failed because the back end does not support room shares" : "Deiling %s mistókst því bakvinnslukerfið leyfir ekki spjallsvæðasameignir",
"Unknown share type" : "Óþekkt tegund sameignar",
"Not a directory" : "Er ekki mappa",
"Could not lock path" : "Gat ekki læst slóð",
"Wrong or no update parameter given" : "Rangt eða ekkert uppfærsluviðfang gefið",
"Can't change permissions for public share links" : "Ekki tókst að breyta aðgangsheimildum fyrir opinbera deilingartengla",
"Sharing sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Deiling með því að senda lykilorð með Nextcloud Talk mistókst því að Nextcloud Talk er ekki virkt",
"Cannot increase permissions" : "Get ekki aukið aðgangsheimildir",
"shared by %s" : "Deilt af %s",
"Direct link" : "Beinn tengill",

+ 3
- 0
apps/files_sharing/l10n/is.json View File

@@ -86,15 +86,18 @@
"Public link sharing is disabled by the administrator" : "Deiling opinberra sameignartengla hefur verið gerð óvirk af kerfisstjóra.",
"Public upload disabled by the administrator" : "Opinber innsending hefur verið gerð óvirk af kerfisstjóra.",
"Public upload is only possible for publicly shared folders" : "Opinber innsending er einungis möguleg í möppur sem er deilt opinberlega",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Deiling á %s með því að senda lykilorð með Nextcloud Talk mistókst því að Nextcloud Talk er ekki virkt",
"Invalid date, date format must be YYYY-MM-DD" : "Ógild dagsetning, dagsetningasniðið verður að vera ÁÁÁÁ-MM-DD",
"Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Deiling %1$s mistókst, því bakvinnslukerfið leyfir ekki sameignir af gerðinni %2$s",
"You cannot share to a Circle if the app is not enabled" : "Þú getur ekki deilt með hring ef forritið er ekki virkt",
"Please specify a valid circle" : "Settu inn gildan hring",
"Sharing %s failed because the back end does not support room shares" : "Deiling %s mistókst því bakvinnslukerfið leyfir ekki spjallsvæðasameignir",
"Unknown share type" : "Óþekkt tegund sameignar",
"Not a directory" : "Er ekki mappa",
"Could not lock path" : "Gat ekki læst slóð",
"Wrong or no update parameter given" : "Rangt eða ekkert uppfærsluviðfang gefið",
"Can't change permissions for public share links" : "Ekki tókst að breyta aðgangsheimildum fyrir opinbera deilingartengla",
"Sharing sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Deiling með því að senda lykilorð með Nextcloud Talk mistókst því að Nextcloud Talk er ekki virkt",
"Cannot increase permissions" : "Get ekki aukið aðgangsheimildir",
"shared by %s" : "Deilt af %s",
"Direct link" : "Beinn tengill",

+ 1
- 1
apps/files_sharing/l10n/it.js View File

@@ -88,9 +88,9 @@ OC.L10N.register(
"Public link sharing is disabled by the administrator" : "La condivisione pubblica di collegamenti è disabilitata dall'amministratore",
"Public upload disabled by the administrator" : "Caricamento pubblico disabilitato dall'amministratore",
"Public upload is only possible for publicly shared folders" : "Il caricamento pubblico è possibile solo per cartelle condivise pubblicamente",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "La condivisione di %s tramite invio della password da Nextcloud Talk non è riuscito poiché Nextcloud Talk non è abilitato",
"Invalid date, date format must be YYYY-MM-DD" : "Data non valida, il formato della data deve essere YYYY-MM-DD",
"Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Condivisione di %1$s non riuscita poiché il motore non consente condivisioni del tipo %2$s",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "La condivisione di %s tramite invio della password da Nextcloud Talk non è riuscito poiché Nextcloud Talk non è abilitato",
"You cannot share to a Circle if the app is not enabled" : "Non puoi condividere con una cerchia se l'applicazione non è abilitata",
"Please specify a valid circle" : "Specifica una cerchia valida",
"Sharing %s failed because the back end does not support room shares" : "Condivisione di %s non riuscita poiché il motore non supporta condivisioni di stanza",

+ 1
- 1
apps/files_sharing/l10n/it.json View File

@@ -86,9 +86,9 @@
"Public link sharing is disabled by the administrator" : "La condivisione pubblica di collegamenti è disabilitata dall'amministratore",
"Public upload disabled by the administrator" : "Caricamento pubblico disabilitato dall'amministratore",
"Public upload is only possible for publicly shared folders" : "Il caricamento pubblico è possibile solo per cartelle condivise pubblicamente",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "La condivisione di %s tramite invio della password da Nextcloud Talk non è riuscito poiché Nextcloud Talk non è abilitato",
"Invalid date, date format must be YYYY-MM-DD" : "Data non valida, il formato della data deve essere YYYY-MM-DD",
"Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Condivisione di %1$s non riuscita poiché il motore non consente condivisioni del tipo %2$s",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "La condivisione di %s tramite invio della password da Nextcloud Talk non è riuscito poiché Nextcloud Talk non è abilitato",
"You cannot share to a Circle if the app is not enabled" : "Non puoi condividere con una cerchia se l'applicazione non è abilitata",
"Please specify a valid circle" : "Specifica una cerchia valida",
"Sharing %s failed because the back end does not support room shares" : "Condivisione di %s non riuscita poiché il motore non supporta condivisioni di stanza",

+ 1
- 1
apps/files_sharing/l10n/nl.js View File

@@ -88,9 +88,9 @@ OC.L10N.register(
"Public link sharing is disabled by the administrator" : "Delen van openbare links is uitgeschakeld door de beheerder",
"Public upload disabled by the administrator" : "Publieke upload uitgeschakeld door de systeembeheerder",
"Public upload is only possible for publicly shared folders" : "Publieke upload is alleen mogelijk voor publiek gedeelde mappen",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Delen %s versturen van het wachtwoord via Nextcloud Talk is mislukt omdat Nextcloud Talk niet is ingeschakeld",
"Invalid date, date format must be YYYY-MM-DD" : "Ongeldige datum, datumnotatie moet in de vorm YYYY-MM-DD",
"Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Delen van %1$s mislukte omdat de backend het delen van type %2$s niet ondersteunt",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Delen %s versturen van het wachtwoord via Nextcloud Talk is mislukt omdat Nextcloud Talk niet is ingeschakeld",
"You cannot share to a Circle if the app is not enabled" : "Je kunt niets met een Kring delen als de app niet is ingeschakeld.",
"Please specify a valid circle" : "Geef een geldige kring op",
"Sharing %s failed because the back end does not support room shares" : "Delen van %s mislukte omdat de backend het delen in ruimtes niet ondersteunt",

+ 1
- 1
apps/files_sharing/l10n/nl.json View File

@@ -86,9 +86,9 @@
"Public link sharing is disabled by the administrator" : "Delen van openbare links is uitgeschakeld door de beheerder",
"Public upload disabled by the administrator" : "Publieke upload uitgeschakeld door de systeembeheerder",
"Public upload is only possible for publicly shared folders" : "Publieke upload is alleen mogelijk voor publiek gedeelde mappen",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Delen %s versturen van het wachtwoord via Nextcloud Talk is mislukt omdat Nextcloud Talk niet is ingeschakeld",
"Invalid date, date format must be YYYY-MM-DD" : "Ongeldige datum, datumnotatie moet in de vorm YYYY-MM-DD",
"Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Delen van %1$s mislukte omdat de backend het delen van type %2$s niet ondersteunt",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Delen %s versturen van het wachtwoord via Nextcloud Talk is mislukt omdat Nextcloud Talk niet is ingeschakeld",
"You cannot share to a Circle if the app is not enabled" : "Je kunt niets met een Kring delen als de app niet is ingeschakeld.",
"Please specify a valid circle" : "Geef een geldige kring op",
"Sharing %s failed because the back end does not support room shares" : "Delen van %s mislukte omdat de backend het delen in ruimtes niet ondersteunt",

+ 1
- 1
apps/files_sharing/l10n/pl.js View File

@@ -88,8 +88,8 @@ OC.L10N.register(
"Public link sharing is disabled by the administrator" : "Udostępnianie linków publicznych zostało zablokowane przez twojego administratora",
"Public upload disabled by the administrator" : "Publiczne wczytywanie zostało zablokowane przez twojego administratora",
"Public upload is only possible for publicly shared folders" : "Publiczne wczytywanie jest możliwe wyłącznie do katalogów publicznych",
"Invalid date, date format must be YYYY-MM-DD" : "Nieprawidłowa data, format daty musi być w formacie YYYY-MM-DD",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Udostępnianie %s wysyłanie hasła przez Nextcloud Talk nie powiodło się, ponieważ usługa Nextcloud Talk nie jest włączona",
"Invalid date, date format must be YYYY-MM-DD" : "Nieprawidłowa data, format daty musi być w formacie YYYY-MM-DD",
"You cannot share to a Circle if the app is not enabled" : "Nie możesz udostępnić do Kręgów jeśli aplikacja jest wyłączona",
"Please specify a valid circle" : "Proszę podać właściwy krąg",
"Unknown share type" : "Nieznany typ udziału",

+ 1
- 1
apps/files_sharing/l10n/pl.json View File

@@ -86,8 +86,8 @@
"Public link sharing is disabled by the administrator" : "Udostępnianie linków publicznych zostało zablokowane przez twojego administratora",
"Public upload disabled by the administrator" : "Publiczne wczytywanie zostało zablokowane przez twojego administratora",
"Public upload is only possible for publicly shared folders" : "Publiczne wczytywanie jest możliwe wyłącznie do katalogów publicznych",
"Invalid date, date format must be YYYY-MM-DD" : "Nieprawidłowa data, format daty musi być w formacie YYYY-MM-DD",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Udostępnianie %s wysyłanie hasła przez Nextcloud Talk nie powiodło się, ponieważ usługa Nextcloud Talk nie jest włączona",
"Invalid date, date format must be YYYY-MM-DD" : "Nieprawidłowa data, format daty musi być w formacie YYYY-MM-DD",
"You cannot share to a Circle if the app is not enabled" : "Nie możesz udostępnić do Kręgów jeśli aplikacja jest wyłączona",
"Please specify a valid circle" : "Proszę podać właściwy krąg",
"Unknown share type" : "Nieznany typ udziału",

+ 1
- 1
apps/files_sharing/l10n/pt_BR.js View File

@@ -88,9 +88,9 @@ OC.L10N.register(
"Public link sharing is disabled by the administrator" : "O compartilhamento por link público foi desativado pelo administrador",
"Public upload disabled by the administrator" : "O envio público foi desativado pelo administrador",
"Public upload is only possible for publicly shared folders" : "O envio público só é possível para pastas compartilhadas publicamente",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "O compartilhamento %s falhou ao enviar a senha ao Nextcloud Talk porque este não está ativado",
"Invalid date, date format must be YYYY-MM-DD" : "Data inválida, o formato da data deve ser YYYY-MM-DD",
"Sharing %1$s failed because the back end does not allow shares from type %2$s" : "O compartilhamento %1$s falhou porque a infraestrutura não permite compartilhamentos do tipo %2$s",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "O compartilhamento %s falhou ao enviar a senha ao Nextcloud Talk porque este não está ativado",
"You cannot share to a Circle if the app is not enabled" : "Você não pode compartilhar para um círculo se o aplicativo não está habilitado",
"Please specify a valid circle" : "Por favor especifique um círculo válido",
"Sharing %s failed because the back end does not support room shares" : "Falhou ao compartilhar %s porque o sistema não suporta compartilhamento de salas",

+ 1
- 1
apps/files_sharing/l10n/pt_BR.json View File

@@ -86,9 +86,9 @@
"Public link sharing is disabled by the administrator" : "O compartilhamento por link público foi desativado pelo administrador",
"Public upload disabled by the administrator" : "O envio público foi desativado pelo administrador",
"Public upload is only possible for publicly shared folders" : "O envio público só é possível para pastas compartilhadas publicamente",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "O compartilhamento %s falhou ao enviar a senha ao Nextcloud Talk porque este não está ativado",
"Invalid date, date format must be YYYY-MM-DD" : "Data inválida, o formato da data deve ser YYYY-MM-DD",
"Sharing %1$s failed because the back end does not allow shares from type %2$s" : "O compartilhamento %1$s falhou porque a infraestrutura não permite compartilhamentos do tipo %2$s",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "O compartilhamento %s falhou ao enviar a senha ao Nextcloud Talk porque este não está ativado",
"You cannot share to a Circle if the app is not enabled" : "Você não pode compartilhar para um círculo se o aplicativo não está habilitado",
"Please specify a valid circle" : "Por favor especifique um círculo válido",
"Sharing %s failed because the back end does not support room shares" : "Falhou ao compartilhar %s porque o sistema não suporta compartilhamento de salas",

+ 1
- 1
apps/files_sharing/l10n/ru.js View File

@@ -88,9 +88,9 @@ OC.L10N.register(
"Public link sharing is disabled by the administrator" : "Возможность предоставления общего доступа созданием общедоступных ссылок отключена администратором",
"Public upload disabled by the administrator" : "Загрузка в общедоступную папку запрещена администратором",
"Public upload is only possible for publicly shared folders" : "Общедоступная загрузка возможна только в общедоступные папки",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Не удалось отправить пароль для доступа к «%s»: приложение Nextcloud Talk отключено.",
"Invalid date, date format must be YYYY-MM-DD" : "Неверная дата, формат даты должен быть ГГГГ-ММ-ДД",
"Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Не удалось предоставить общий доступ к «%1$s», поскольку механизм удалённого обмена не разрешает публикации типа %2$s",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Не удалось отправить пароль для доступа к «%s»: приложение Nextcloud Talk отключено.",
"You cannot share to a Circle if the app is not enabled" : "Вы не можете поделиться с кругом, если приложение «Круг» не включено",
"Please specify a valid circle" : "Укажите верный круг",
"Sharing %s failed because the back end does not support room shares" : "Не удалось предоставить общий доступ к «%s» поскольку механизм обмена не поддерживает общий доступ такого типа",

+ 1
- 1
apps/files_sharing/l10n/ru.json View File

@@ -86,9 +86,9 @@
"Public link sharing is disabled by the administrator" : "Возможность предоставления общего доступа созданием общедоступных ссылок отключена администратором",
"Public upload disabled by the administrator" : "Загрузка в общедоступную папку запрещена администратором",
"Public upload is only possible for publicly shared folders" : "Общедоступная загрузка возможна только в общедоступные папки",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Не удалось отправить пароль для доступа к «%s»: приложение Nextcloud Talk отключено.",
"Invalid date, date format must be YYYY-MM-DD" : "Неверная дата, формат даты должен быть ГГГГ-ММ-ДД",
"Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Не удалось предоставить общий доступ к «%1$s», поскольку механизм удалённого обмена не разрешает публикации типа %2$s",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Не удалось отправить пароль для доступа к «%s»: приложение Nextcloud Talk отключено.",
"You cannot share to a Circle if the app is not enabled" : "Вы не можете поделиться с кругом, если приложение «Круг» не включено",
"Please specify a valid circle" : "Укажите верный круг",
"Sharing %s failed because the back end does not support room shares" : "Не удалось предоставить общий доступ к «%s» поскольку механизм обмена не поддерживает общий доступ такого типа",

+ 1
- 1
apps/files_sharing/l10n/sk.js View File

@@ -88,9 +88,9 @@ OC.L10N.register(
"Public link sharing is disabled by the administrator" : "Sprístupnenie pomocou verejných odkazov je zakázané administrátorom",
"Public upload disabled by the administrator" : "Verejné nahrávanie je zakázané administrátorom",
"Public upload is only possible for publicly shared folders" : "Verejné nahrávanie je možné len do verejne sprístupnených priečinkov",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Zdieľanie %s odoslaním hesla cez Nextcloud Talk zlyhalo, pretože Nextcloud Talk nie je zapnutý",
"Invalid date, date format must be YYYY-MM-DD" : "Neplatný dátum, formát musí byť v tvare YYYY-MM-DD",
"Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Sprístupnenie %1$s zlyhalo, backend nepodporuje typ sprístupnenia %2$s",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Zdieľanie %s odoslaním hesla cez Nextcloud Talk zlyhalo, pretože Nextcloud Talk nie je zapnutý",
"You cannot share to a Circle if the app is not enabled" : "Ak aplikácia nie je povolená, nemôžete ju zdieľať s Kruhom",
"Please specify a valid circle" : "Zadajte platný kruh",
"Sharing %s failed because the back end does not support room shares" : "Zdieľanie %s sa nepodarilo, pretože backend nepodporuje zdieľanie miestností",

+ 1
- 1
apps/files_sharing/l10n/sk.json View File

@@ -86,9 +86,9 @@
"Public link sharing is disabled by the administrator" : "Sprístupnenie pomocou verejných odkazov je zakázané administrátorom",
"Public upload disabled by the administrator" : "Verejné nahrávanie je zakázané administrátorom",
"Public upload is only possible for publicly shared folders" : "Verejné nahrávanie je možné len do verejne sprístupnených priečinkov",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Zdieľanie %s odoslaním hesla cez Nextcloud Talk zlyhalo, pretože Nextcloud Talk nie je zapnutý",
"Invalid date, date format must be YYYY-MM-DD" : "Neplatný dátum, formát musí byť v tvare YYYY-MM-DD",
"Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Sprístupnenie %1$s zlyhalo, backend nepodporuje typ sprístupnenia %2$s",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Zdieľanie %s odoslaním hesla cez Nextcloud Talk zlyhalo, pretože Nextcloud Talk nie je zapnutý",
"You cannot share to a Circle if the app is not enabled" : "Ak aplikácia nie je povolená, nemôžete ju zdieľať s Kruhom",
"Please specify a valid circle" : "Zadajte platný kruh",
"Sharing %s failed because the back end does not support room shares" : "Zdieľanie %s sa nepodarilo, pretože backend nepodporuje zdieľanie miestností",

+ 1
- 1
apps/files_sharing/l10n/sr.js View File

@@ -88,9 +88,9 @@ OC.L10N.register(
"Public link sharing is disabled by the administrator" : "Администратор је забранио дељење јавном везом",
"Public upload disabled by the administrator" : "Администратор је забранио отпремања са јавним приступом",
"Public upload is only possible for publicly shared folders" : "Отпремања са јавним приступом су могућа само за јавно дељене фасцикле",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Дељење %s слањем лозинке преко Nextcloud Talk-а није успело пошто Nextcloud Talk није укључен",
"Invalid date, date format must be YYYY-MM-DD" : "Неисправан датим, формат датума мора бити ГГГГ-ММ-ДД",
"Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Дељење %1$s није успело зато што позадина не дозвољава дељење које је типа %2$s",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Дељење %s слањем лозинке преко Nextcloud Talk-а није успело пошто Nextcloud Talk није укључен",
"You cannot share to a Circle if the app is not enabled" : "Не можете делити са Круговима ако та апликација није укључена",
"Please specify a valid circle" : "Одаберите исправан круг",
"Sharing %s failed because the back end does not support room shares" : "Није успело дељење %s зато што позадински мотор дељења не подржава дељења у соби",

+ 1
- 1
apps/files_sharing/l10n/sr.json View File

@@ -86,9 +86,9 @@
"Public link sharing is disabled by the administrator" : "Администратор је забранио дељење јавном везом",
"Public upload disabled by the administrator" : "Администратор је забранио отпремања са јавним приступом",
"Public upload is only possible for publicly shared folders" : "Отпремања са јавним приступом су могућа само за јавно дељене фасцикле",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Дељење %s слањем лозинке преко Nextcloud Talk-а није успело пошто Nextcloud Talk није укључен",
"Invalid date, date format must be YYYY-MM-DD" : "Неисправан датим, формат датума мора бити ГГГГ-ММ-ДД",
"Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Дељење %1$s није успело зато што позадина не дозвољава дељење које је типа %2$s",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Дељење %s слањем лозинке преко Nextcloud Talk-а није успело пошто Nextcloud Talk није укључен",
"You cannot share to a Circle if the app is not enabled" : "Не можете делити са Круговима ако та апликација није укључена",
"Please specify a valid circle" : "Одаберите исправан круг",
"Sharing %s failed because the back end does not support room shares" : "Није успело дељење %s зато што позадински мотор дељења не подржава дељења у соби",

+ 1
- 1
apps/files_sharing/l10n/sv.js View File

@@ -88,9 +88,9 @@ OC.L10N.register(
"Public link sharing is disabled by the administrator" : "Offentlig delningslänk är avstängt",
"Public upload disabled by the administrator" : "Offentlig uppladdning är avstängt",
"Public upload is only possible for publicly shared folders" : "Offentlig uppladdning fungerar endast i offentligt delade mappar",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Dela %s och skicka lösenordet via Nextcloud Talk går inte eftersom Nextcloud Talk är inte aktiverad",
"Invalid date, date format must be YYYY-MM-DD" : "Ogiltigt datum, måste anges: ÅÅÅÅ-MM-DD",
"Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Delning %1$s misslyckades. Ej tillåtet med delningar från typ %2$s",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Dela %s och skicka lösenordet via Nextcloud Talk går inte eftersom Nextcloud Talk är inte aktiverad",
"You cannot share to a Circle if the app is not enabled" : "Du kan inte dela till en cirkel om appen inte är aktiverad",
"Please specify a valid circle" : "Vänligen ange en giltig cirkel",
"Sharing %s failed because the back end does not support room shares" : "Dela %s misslyckades eftersom systemet inte stödjer rum-delningar",

+ 1
- 1
apps/files_sharing/l10n/sv.json View File

@@ -86,9 +86,9 @@
"Public link sharing is disabled by the administrator" : "Offentlig delningslänk är avstängt",
"Public upload disabled by the administrator" : "Offentlig uppladdning är avstängt",
"Public upload is only possible for publicly shared folders" : "Offentlig uppladdning fungerar endast i offentligt delade mappar",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Dela %s och skicka lösenordet via Nextcloud Talk går inte eftersom Nextcloud Talk är inte aktiverad",
"Invalid date, date format must be YYYY-MM-DD" : "Ogiltigt datum, måste anges: ÅÅÅÅ-MM-DD",
"Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Delning %1$s misslyckades. Ej tillåtet med delningar från typ %2$s",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Dela %s och skicka lösenordet via Nextcloud Talk går inte eftersom Nextcloud Talk är inte aktiverad",
"You cannot share to a Circle if the app is not enabled" : "Du kan inte dela till en cirkel om appen inte är aktiverad",
"Please specify a valid circle" : "Vänligen ange en giltig cirkel",
"Sharing %s failed because the back end does not support room shares" : "Dela %s misslyckades eftersom systemet inte stödjer rum-delningar",

+ 1
- 1
apps/files_sharing/l10n/tr.js View File

@@ -88,9 +88,9 @@ OC.L10N.register(
"Public link sharing is disabled by the administrator" : "Herkese açık bağlantı paylaşımı yönetici tarafından devre dışı bırakılmış",
"Public upload disabled by the administrator" : "Herkese açık yükleme yönetici tarafından devre dışı bırakılmış",
"Public upload is only possible for publicly shared folders" : "Herkese açık yükleme ancak herkese açık paylaşılmış klasörlere yapılabilir",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Nextcloud Talk etkinleştirilmemiş olduğundan %sNextcloud Talk ile parola gönderilerek paylaşılamadı",
"Invalid date, date format must be YYYY-MM-DD" : "Tarih geçersiz. Tarih biçimi YYYY-AA-GG olmalıdır",
"Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Yönetim bölümünden %2$s türündeki paylaşımlar yapılamadığından %1$s paylaşılamadı",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Nextcloud Talk etkinleştirilmemiş olduğundan %sNextcloud Talk ile parola gönderilerek paylaşılamadı",
"You cannot share to a Circle if the app is not enabled" : "Uygulama etkinleştirilmemiş ise bir Çevre ile paylaşamazsınız",
"Please specify a valid circle" : "Lütfen geçerli bir çevre belirtin",
"Sharing %s failed because the back end does not support room shares" : "Arka uç oda paylaşımlarına izin vermediğinden %s paylaşılamadı",

+ 1
- 1
apps/files_sharing/l10n/tr.json View File

@@ -86,9 +86,9 @@
"Public link sharing is disabled by the administrator" : "Herkese açık bağlantı paylaşımı yönetici tarafından devre dışı bırakılmış",
"Public upload disabled by the administrator" : "Herkese açık yükleme yönetici tarafından devre dışı bırakılmış",
"Public upload is only possible for publicly shared folders" : "Herkese açık yükleme ancak herkese açık paylaşılmış klasörlere yapılabilir",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Nextcloud Talk etkinleştirilmemiş olduğundan %sNextcloud Talk ile parola gönderilerek paylaşılamadı",
"Invalid date, date format must be YYYY-MM-DD" : "Tarih geçersiz. Tarih biçimi YYYY-AA-GG olmalıdır",
"Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Yönetim bölümünden %2$s türündeki paylaşımlar yapılamadığından %1$s paylaşılamadı",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Nextcloud Talk etkinleştirilmemiş olduğundan %sNextcloud Talk ile parola gönderilerek paylaşılamadı",
"You cannot share to a Circle if the app is not enabled" : "Uygulama etkinleştirilmemiş ise bir Çevre ile paylaşamazsınız",
"Please specify a valid circle" : "Lütfen geçerli bir çevre belirtin",
"Sharing %s failed because the back end does not support room shares" : "Arka uç oda paylaşımlarına izin vermediğinden %s paylaşılamadı",

+ 1
- 1
apps/files_sharing/l10n/zh_CN.js View File

@@ -87,8 +87,8 @@ OC.L10N.register(
"Public link sharing is disabled by the administrator" : "公共链接共享已被管理员禁用",
"Public upload disabled by the administrator" : "公共上传已被管理员禁用",
"Public upload is only possible for publicly shared folders" : "公共上传仅适用于公共共享文件夹",
"Invalid date, date format must be YYYY-MM-DD" : "无效的日期,日期格式必须是 YYYY-MM-DD",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "由于 Nextcloud Talk 没有启用, 所以通过 Nextcloud Talk 发送 %s 共享密码失败.",
"Invalid date, date format must be YYYY-MM-DD" : "无效的日期,日期格式必须是 YYYY-MM-DD",
"You cannot share to a Circle if the app is not enabled" : "如果这个应用程序不可用,你不能共享到圈子",
"Please specify a valid circle" : "请指明一个可用圈子",
"Sharing %s failed because the back end does not support room shares" : "由于后端不支持房间共享, 所以共享 %s 失败.",

+ 1
- 1
apps/files_sharing/l10n/zh_CN.json View File

@@ -85,8 +85,8 @@
"Public link sharing is disabled by the administrator" : "公共链接共享已被管理员禁用",
"Public upload disabled by the administrator" : "公共上传已被管理员禁用",
"Public upload is only possible for publicly shared folders" : "公共上传仅适用于公共共享文件夹",
"Invalid date, date format must be YYYY-MM-DD" : "无效的日期,日期格式必须是 YYYY-MM-DD",
"Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "由于 Nextcloud Talk 没有启用, 所以通过 Nextcloud Talk 发送 %s 共享密码失败.",
"Invalid date, date format must be YYYY-MM-DD" : "无效的日期,日期格式必须是 YYYY-MM-DD",
"You cannot share to a Circle if the app is not enabled" : "如果这个应用程序不可用,你不能共享到圈子",
"Please specify a valid circle" : "请指明一个可用圈子",
"Sharing %s failed because the back end does not support room shares" : "由于后端不支持房间共享, 所以共享 %s 失败.",

+ 59
- 16
apps/files_sharing/lib/Controller/ShareAPIController.php View File

@@ -160,6 +160,7 @@ class ShareAPIController extends OCSController {
'token' => null,
'uid_file_owner' => $share->getShareOwner(),
'note' => $share->getNote(),
'label' => $share->getLabel(),
'displayname_file_owner' => $shareOwner !== null ? $shareOwner->getDisplayName() : $share->getShareOwner(),
];

@@ -211,6 +212,8 @@ class ShareAPIController extends OCSController {
$result['share_with'] = $share->getPassword();
$result['share_with_displayname'] = $share->getPassword();

$result['send_password_by_talk'] = $share->getSendPasswordByTalk();

$result['token'] = $share->getToken();
$result['url'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $share->getToken()]);

@@ -252,6 +255,7 @@ class ShareAPIController extends OCSController {


$result['mail_send'] = $share->getMailSend() ? 1 : 0;
$result['hide_download'] = $share->getHideDownload() ? 1 : 0;

return $result;
}
@@ -353,15 +357,17 @@ class ShareAPIController extends OCSController {
* @param string $shareWith
* @param string $publicUpload
* @param string $password
* @param bool $sendPasswordByTalk
* @param string $sendPasswordByTalk
* @param string $expireDate
* @param string $label
*
* @return DataResponse
* @throws OCSNotFoundException
* @throws OCSForbiddenException
* @throws NotFoundException
* @throws OCSBadRequestException
* @throws OCSException
*
* @throws OCSForbiddenException
* @throws OCSNotFoundException
* @throws \OCP\Files\InvalidPathException
* @suppress PhanUndeclaredClassMethod
*/
public function createShare(
@@ -372,7 +378,8 @@ class ShareAPIController extends OCSController {
string $publicUpload = 'false',
string $password = '',
string $sendPasswordByTalk = null,
string $expireDate = ''
string $expireDate = '',
string $label = ''
): DataResponse {
$share = $this->shareManager->newShare();

@@ -446,15 +453,6 @@ class ShareAPIController extends OCSController {
throw new OCSNotFoundException($this->l->t('Public link sharing is disabled by the administrator'));
}

/*
* For now we only allow 1 link share.
* Return the existing link share if this is a duplicate
*/
$existingShares = $this->shareManager->getSharesBy($this->currentUser, Share::SHARE_TYPE_LINK, $path, false, 1, 0);
if (!empty($existingShares)) {
return new DataResponse($this->formatShare($existingShares[0]));
}

if ($publicUpload === 'true') {
// Check if public upload is allowed
if (!$this->shareManager->shareApiLinkAllowPublicUpload()) {
@@ -481,6 +479,19 @@ class ShareAPIController extends OCSController {
$share->setPassword($password);
}


if (!empty($label)) {
$share->setLabel($label);
}

if ($sendPasswordByTalk === 'true') {
if (!$this->appManager->isEnabledForUser('spreed')) {
throw new OCSForbiddenException($this->l->t('Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled', [$path->getPath()]));
}

$share->setSendPasswordByTalk(true);
}

//Expire date
if ($expireDate !== '') {
try {
@@ -745,6 +756,8 @@ class ShareAPIController extends OCSController {
* @param string $publicUpload
* @param string $expireDate
* @param string $note
* @param string $label
* @param string $hideDownload
* @return DataResponse
* @throws LockedException
* @throws NotFoundException
@@ -759,7 +772,9 @@ class ShareAPIController extends OCSController {
string $sendPasswordByTalk = null,
string $publicUpload = null,
string $expireDate = null,
string $note = null
string $note = null,
string $label = null,
string $hideDownload = null
): DataResponse {
try {
$share = $this->getShareById($id);
@@ -773,7 +788,15 @@ class ShareAPIController extends OCSController {
throw new OCSNotFoundException($this->l->t('Wrong share ID, share doesn\'t exist'));
}

if ($permissions === null && $password === null && $sendPasswordByTalk === null && $publicUpload === null && $expireDate === null && $note === null) {
if ($permissions === null &&
$password === null &&
$sendPasswordByTalk === null &&
$publicUpload === null &&
$expireDate === null &&
$note === null &&
$label === null &&
$hideDownload === null
) {
throw new OCSBadRequestException($this->l->t('Wrong or no update parameter given'));
}

@@ -786,6 +809,13 @@ class ShareAPIController extends OCSController {
*/
if ($share->getShareType() === Share::SHARE_TYPE_LINK) {

// Update hide download state
if ($hideDownload === 'true') {
$share->setHideDownload(true);
} else if ($hideDownload === 'false') {
$share->setHideDownload(false);
}

$newPermissions = null;
if ($publicUpload === 'true') {
$newPermissions = Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE;
@@ -850,6 +880,19 @@ class ShareAPIController extends OCSController {
$share->setPassword($password);
}

if ($label !== null) {
$share->setLabel($label);
}

if ($sendPasswordByTalk === 'true') {
if (!$this->appManager->isEnabledForUser('spreed')) {
throw new OCSForbiddenException($this->l->t('Sharing sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled'));
}

$share->setSendPasswordByTalk(true);
} else if ($sendPasswordByTalk !== null) {
$share->setSendPasswordByTalk(false);
}
} else {
if ($permissions !== null) {
$permissions = (int)$permissions;

+ 9
- 6
apps/files_sharing/lib/Controller/ShareController.php View File

@@ -321,6 +321,7 @@ class ShareController extends AuthPublicShareController {
$shareTmpl['dir'] = '';
$shareTmpl['nonHumanFileSize'] = $share->getNode()->getSize();
$shareTmpl['fileSize'] = \OCP\Util::humanFileSize($share->getNode()->getSize());
$shareTmpl['hideDownload'] = $share->getHideDownload();

// Show file list
$hideFileList = false;
@@ -444,12 +445,14 @@ class ShareController extends AuthPublicShareController {
$response = new PublicTemplateResponse($this->appName, 'public', $shareTmpl);
$response->setHeaderTitle($shareTmpl['filename']);
$response->setHeaderDetails($this->l10n->t('shared by %s', [$shareTmpl['displayName']]));
$response->setHeaderActions([
new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download-white', $shareTmpl['downloadURL'], 0),
new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download', $shareTmpl['downloadURL'], 10, $shareTmpl['fileSize']),
new LinkMenuAction($this->l10n->t('Direct link'), 'icon-public', $shareTmpl['previewURL']),
new ExternalShareMenuAction($this->l10n->t('Add to your Nextcloud'), 'icon-external', $shareTmpl['owner'], $shareTmpl['displayName'], $shareTmpl['filename']),
]);
if (!$share->getHideDownload()) {
$response->setHeaderActions([
new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download-white', $shareTmpl['downloadURL'], 0),
new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download', $shareTmpl['downloadURL'], 10, $shareTmpl['fileSize']),
new LinkMenuAction($this->l10n->t('Direct link'), 'icon-public', $shareTmpl['previewURL']),
new ExternalShareMenuAction($this->l10n->t('Add to your Nextcloud'), 'icon-external', $shareTmpl['owner'], $shareTmpl['displayName'], $shareTmpl['filename']),
]);
}

$response->setContentSecurityPolicy($csp);


+ 6
- 3
apps/files_sharing/templates/public.php View File

@@ -11,13 +11,16 @@
<input type="hidden" id="filesApp" name="filesApp" value="1">
<input type="hidden" id="isPublic" name="isPublic" value="1">
<input type="hidden" name="dir" value="<?php p($_['dir']) ?>" id="dir">
<input type="hidden" name="downloadURL" value="<?php p($_['downloadURL']) ?>" id="downloadURL">
<?php if (!$_['hideDownload']): ?>
<input type="hidden" name="downloadURL" value="<?php p($_['downloadURL']) ?>" id="downloadURL">
<?php endif; ?>
<input type="hidden" name="previewURL" value="<?php p($_['previewURL']) ?>" id="previewURL">
<input type="hidden" name="sharingToken" value="<?php p($_['sharingToken']) ?>" id="sharingToken">
<input type="hidden" name="filename" value="<?php p($_['filename']) ?>" id="filename">
<input type="hidden" name="mimetype" value="<?php p($_['mimetype']) ?>" id="mimetype">
<input type="hidden" name="previewSupported" value="<?php p($_['previewSupported'] ? 'true' : 'false'); ?>" id="previewSupported">
<input type="hidden" name="mimetypeIcon" value="<?php p(\OC::$server->getMimeTypeDetector()->mimeTypeIcon($_['mimetype'])); ?>" id="mimetypeIcon">
<input type="hidden" name="hideDownload" value="<?php p($_['hideDownload'] ? 'true' : 'false'); ?>" id="hideDownload">
<?php
$upload_max_filesize = OC::$server->getIniWrapper()->getBytes('upload_max_filesize');
$post_max_size = OC::$server->getIniWrapper()->getBytes('post_max_size');
@@ -58,7 +61,7 @@ $maxUploadFilesize = min($upload_max_filesize, $post_max_size);
<!-- Preview frame is filled via JS to support SVG images for modern browsers -->
<div id="imgframe"></div>
<?php endif; ?>
<?php if ($_['previewURL'] === $_['downloadURL']): ?>
<?php if ($_['previewURL'] === $_['downloadURL'] && !$_['hideDownload']): ?>
<div class="directDownload">
<a href="<?php p($_['downloadURL']); ?>" id="downloadFile" class="button">
<span class="icon icon-download"></span>
@@ -97,4 +100,4 @@ $maxUploadFilesize = min($upload_max_filesize, $post_max_size);
data-url="<?php p(\OC::$server->getURLGenerator()->linkTo('files', 'ajax/upload.php')); ?>" />
</div>
<?php endif; ?>
</div>
</div>

+ 400
- 18
apps/files_sharing/tests/Controller/ShareAPIControllerTest.php View File

@@ -253,7 +253,7 @@ class ShareAPIControllerTest extends TestCase {

public function createShare($id, $shareType, $sharedWith, $sharedBy, $shareOwner, $path, $permissions,
$shareTime, $expiration, $parent, $target, $mail_send, $note = '', $token=null,
$password=null) {
$password=null, $label = '') {
$share = $this->getMockBuilder(IShare::class)->getMock();
$share->method('getId')->willReturn($id);
$share->method('getShareType')->willReturn($shareType);
@@ -263,6 +263,7 @@ class ShareAPIControllerTest extends TestCase {
$share->method('getNode')->willReturn($path);
$share->method('getPermissions')->willReturn($permissions);
$share->method('getNote')->willReturn($note);
$share->method('getLabel')->willReturn($label);
$time = new \DateTime();
$time->setTimestamp($shareTime);
$share->method('getShareTime')->willReturn($time);
@@ -351,8 +352,10 @@ class ShareAPIControllerTest extends TestCase {
'mail_send' => 0,
'uid_file_owner' => 'ownerId',
'note' => 'personal note',
'label' => '',
'displayname_file_owner' => 'ownerDisplay',
'mimetype' => 'myMimeType',
'hide_download' => 0,
];
$data[] = [$share, $expected];

@@ -395,8 +398,10 @@ class ShareAPIControllerTest extends TestCase {
'mail_send' => 0,
'uid_file_owner' => 'ownerId',
'note' => 'personal note',
'label' => '',
'displayname_file_owner' => 'ownerDisplay',
'mimetype' => 'myFolderMimeType',
'hide_download' => 0,
];
$data[] = [$share, $expected];

@@ -417,13 +422,15 @@ class ShareAPIControllerTest extends TestCase {
0,
'personal note',
'token',
'password'
'password',
'first link share'
);
$expected = [
'id' => 101,
'share_type' => \OCP\Share::SHARE_TYPE_LINK,
'share_with' => 'password',
'share_with_displayname' => 'password',
'send_password_by_talk' => false,
'uid_owner' => 'initiatorId',
'displayname_owner' => 'initiatorDisplay',
'item_type' => 'folder',
@@ -443,8 +450,10 @@ class ShareAPIControllerTest extends TestCase {
'url' => 'url',
'uid_file_owner' => 'ownerId',
'note' => 'personal note',
'label' => 'first link share',
'displayname_file_owner' => 'ownerDisplay',
'mimetype' => 'myFolderMimeType',
'hide_download' => 0,
];
$data[] = [$share, $expected];

@@ -1127,6 +1136,71 @@ class ShareAPIControllerTest extends TestCase {
$this->assertEquals($expected->getData(), $result->getData());
}

public function testCreateShareLinkSendPasswordByTalk() {
$ocs = $this->mockFormatShare();

$path = $this->getMockBuilder(Folder::class)->getMock();
$storage = $this->getMockBuilder(Storage::class)->getMock();
$storage->method('instanceOfStorage')
->with('OCA\Files_Sharing\External\Storage')
->willReturn(false);
$path->method('getStorage')->willReturn($storage);
$this->rootFolder->method('getUserFolder')->with($this->currentUser)->will($this->returnSelf());
$this->rootFolder->method('get')->with('valid-path')->willReturn($path);

$this->shareManager->method('newShare')->willReturn(\OC::$server->getShareManager()->newShare());
$this->shareManager->method('shareApiAllowLinks')->willReturn(true);
$this->shareManager->method('shareApiLinkAllowPublicUpload')->willReturn(true);

$this->appManager->method('isEnabledForUser')->with('spreed')->willReturn(true);

$this->shareManager->expects($this->once())->method('createShare')->with(
$this->callback(function (\OCP\Share\IShare $share) use ($path) {
return $share->getNode() === $path &&
$share->getShareType() === \OCP\Share::SHARE_TYPE_LINK &&
$share->getPermissions() === \OCP\Constants::PERMISSION_READ &&
$share->getSharedBy() === 'currentUser' &&
$share->getPassword() === 'password' &&
$share->getSendPasswordByTalk() === true &&
$share->getExpirationDate() === null;
})
)->will($this->returnArgument(0));

$expected = new DataResponse([]);
$result = $ocs->createShare('valid-path', \OCP\Constants::PERMISSION_ALL, \OCP\Share::SHARE_TYPE_LINK, null, 'false', 'password', 'true', '');

$this->assertInstanceOf(get_class($expected), $result);
$this->assertEquals($expected->getData(), $result->getData());
}

/**
* @expectedException \OCP\AppFramework\OCS\OCSForbiddenException
* @expectedExceptionMessage Sharing valid-path sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled
*/
public function testCreateShareLinkSendPasswordByTalkWithTalkDisabled() {
$ocs = $this->mockFormatShare();

$path = $this->getMockBuilder(Folder::class)->getMock();
$storage = $this->getMockBuilder(Storage::class)->getMock();
$storage->method('instanceOfStorage')
->with('OCA\Files_Sharing\External\Storage')
->willReturn(false);
$path->method('getStorage')->willReturn($storage);
$path->method('getPath')->willReturn('valid-path');
$this->rootFolder->method('getUserFolder')->with($this->currentUser)->will($this->returnSelf());
$this->rootFolder->method('get')->with('valid-path')->willReturn($path);

$this->shareManager->method('newShare')->willReturn(\OC::$server->getShareManager()->newShare());
$this->shareManager->method('shareApiAllowLinks')->willReturn(true);
$this->shareManager->method('shareApiLinkAllowPublicUpload')->willReturn(true);

$this->appManager->method('isEnabledForUser')->with('spreed')->willReturn(false);

$this->shareManager->expects($this->never())->method('createShare');

$ocs->createShare('valid-path', \OCP\Constants::PERMISSION_ALL, \OCP\Share::SHARE_TYPE_LINK, null, 'false', 'password', 'true', '');
}

public function testCreateShareValidExpireDate() {
$ocs = $this->mockFormatShare();

@@ -1504,6 +1578,9 @@ class ShareAPIControllerTest extends TestCase {
->setShareType(\OCP\Share::SHARE_TYPE_LINK)
->setPassword('password')
->setExpirationDate(new \DateTime())
->setNote('note')
->setLabel('label')
->setHideDownload(true)
->setPermissions(\OCP\Constants::PERMISSION_ALL)
->setNode($node);

@@ -1517,7 +1594,12 @@ class ShareAPIControllerTest extends TestCase {
$this->callback(function (\OCP\Share\IShare $share) {
return $share->getPermissions() === \OCP\Constants::PERMISSION_READ &&
$share->getPassword() === null &&
$share->getExpirationDate() === null;
$share->getExpirationDate() === null &&
// Once set a note or a label are never back to null, only to an
// empty string.
$share->getNote() === '' &&
$share->getLabel() === '' &&
$share->getHideDownload() === false;
})
)->will($this->returnArgument(0));

@@ -1525,7 +1607,7 @@ class ShareAPIControllerTest extends TestCase {
->willReturn([]);

$expected = new DataResponse([]);
$result = $ocs->updateShare(42, null, '', null, 'false', '');
$result = $ocs->updateShare(42, null, '', null, 'false', '', '', '', 'false');

$this->assertInstanceOf(get_class($expected), $result);
$this->assertEquals($expected->getData(), $result->getData());
@@ -1552,7 +1634,10 @@ class ShareAPIControllerTest extends TestCase {

return $share->getPermissions() === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE) &&
$share->getPassword() === 'password' &&
$share->getExpirationDate() == $date;
$share->getExpirationDate() == $date &&
$share->getNote() === 'note' &&
$share->getLabel() === 'label' &&
$share->getHideDownload() === true;
})
)->will($this->returnArgument(0));

@@ -1560,7 +1645,7 @@ class ShareAPIControllerTest extends TestCase {
->willReturn([]);

$expected = new DataResponse([]);
$result = $ocs->updateShare(42, null, 'password', null, 'true', '2000-01-01');
$result = $ocs->updateShare(42, null, 'password', null, 'true', '2000-01-01', 'note', 'label', 'true');

$this->assertInstanceOf(get_class($expected), $result);
$this->assertEquals($expected->getData(), $result->getData());
@@ -1692,7 +1777,11 @@ class ShareAPIControllerTest extends TestCase {
->setSharedBy($this->currentUser)
->setShareType(\OCP\Share::SHARE_TYPE_LINK)
->setPassword('password')
->setSendPasswordByTalk(true)
->setExpirationDate($date)
->setNote('note')
->setLabel('label')
->setHideDownload(true)
->setPermissions(\OCP\Constants::PERMISSION_ALL)
->setNode($node);

@@ -1706,12 +1795,194 @@ class ShareAPIControllerTest extends TestCase {
$this->callback(function (\OCP\Share\IShare $share) use ($date) {
return $share->getPermissions() === \OCP\Constants::PERMISSION_ALL &&
$share->getPassword() === 'newpassword' &&
$share->getExpirationDate() === $date;
$share->getSendPasswordByTalk() === true &&
$share->getExpirationDate() === $date &&
$share->getNote() === 'note' &&
$share->getLabel() === 'label' &&
$share->getHideDownload() === true;
})
)->will($this->returnArgument(0));

$expected = new DataResponse([]);
$result = $ocs->updateShare(42, null, 'newpassword', null, null, null);
$result = $ocs->updateShare(42, null, 'newpassword', null, null, null, null, null, null);

$this->assertInstanceOf(get_class($expected), $result);
$this->assertEquals($expected->getData(), $result->getData());
}

public function testUpdateLinkShareSendPasswordByTalkDoesNotChangeOther() {
$ocs = $this->mockFormatShare();

$date = new \DateTime('2000-01-01');
$date->setTime(0,0,0);

$node = $this->getMockBuilder(File::class)->getMock();
$share = $this->newShare();
$share->setPermissions(\OCP\Constants::PERMISSION_ALL)
->setSharedBy($this->currentUser)
->setShareType(\OCP\Share::SHARE_TYPE_LINK)
->setPassword('password')
->setSendPasswordByTalk(false)
->setExpirationDate($date)
->setNote('note')
->setLabel('label')
->setHideDownload(true)
->setPermissions(\OCP\Constants::PERMISSION_ALL)
->setNode($node);

$node->expects($this->once())
->method('lock')
->with(\OCP\Lock\ILockingProvider::LOCK_SHARED);

$this->shareManager->method('getShareById')->with('ocinternal:42')->willReturn($share);

$this->appManager->method('isEnabledForUser')->with('spreed')->willReturn(true);

$this->shareManager->expects($this->once())->method('updateShare')->with(
$this->callback(function (\OCP\Share\IShare $share) use ($date) {
return $share->getPermissions() === \OCP\Constants::PERMISSION_ALL &&
$share->getPassword() === 'password' &&
$share->getSendPasswordByTalk() === true &&
$share->getExpirationDate() === $date &&
$share->getNote() === 'note' &&
$share->getLabel() === 'label' &&
$share->getHideDownload() === true;
})
)->will($this->returnArgument(0));

$expected = new DataResponse([]);
$result = $ocs->updateShare(42, null, null, 'true', null, null, null, null, null);

$this->assertInstanceOf(get_class($expected), $result);
$this->assertEquals($expected->getData(), $result->getData());
}

/**
* @expectedException \OCP\AppFramework\OCS\OCSForbiddenException
* @expectedExceptionMessage Sharing sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled
*/
public function testUpdateLinkShareSendPasswordByTalkWithTalkDisabledDoesNotChangeOther() {
$ocs = $this->mockFormatShare();

$date = new \DateTime('2000-01-01');
$date->setTime(0,0,0);

$node = $this->getMockBuilder(File::class)->getMock();
$share = $this->newShare();
$share->setPermissions(\OCP\Constants::PERMISSION_ALL)
->setSharedBy($this->currentUser)
->setShareType(\OCP\Share::SHARE_TYPE_LINK)
->setPassword('password')
->setSendPasswordByTalk(false)
->setExpirationDate($date)
->setNote('note')
->setLabel('label')
->setHideDownload(true)
->setPermissions(\OCP\Constants::PERMISSION_ALL)
->setNode($node);

$node->expects($this->once())
->method('lock')
->with(\OCP\Lock\ILockingProvider::LOCK_SHARED);

$this->shareManager->method('getShareById')->with('ocinternal:42')->willReturn($share);

$this->appManager->method('isEnabledForUser')->with('spreed')->willReturn(false);

$this->shareManager->expects($this->never())->method('updateShare');

$ocs->updateShare(42, null, null, 'true', null, null, null, null, null);
}

public function testUpdateLinkShareDoNotSendPasswordByTalkDoesNotChangeOther() {
$ocs = $this->mockFormatShare();

$date = new \DateTime('2000-01-01');
$date->setTime(0,0,0);

$node = $this->getMockBuilder(File::class)->getMock();
$share = $this->newShare();
$share->setPermissions(\OCP\Constants::PERMISSION_ALL)
->setSharedBy($this->currentUser)
->setShareType(\OCP\Share::SHARE_TYPE_LINK)
->setPassword('password')
->setSendPasswordByTalk(true)
->setExpirationDate($date)
->setNote('note')
->setLabel('label')
->setHideDownload(true)
->setPermissions(\OCP\Constants::PERMISSION_ALL)
->setNode($node);

$node->expects($this->once())
->method('lock')
->with(\OCP\Lock\ILockingProvider::LOCK_SHARED);

$this->shareManager->method('getShareById')->with('ocinternal:42')->willReturn($share);

$this->appManager->method('isEnabledForUser')->with('spreed')->willReturn(true);

$this->shareManager->expects($this->once())->method('updateShare')->with(
$this->callback(function (\OCP\Share\IShare $share) use ($date) {
return $share->getPermissions() === \OCP\Constants::PERMISSION_ALL &&
$share->getPassword() === 'password' &&
$share->getSendPasswordByTalk() === false &&
$share->getExpirationDate() === $date &&
$share->getNote() === 'note' &&
$share->getLabel() === 'label' &&
$share->getHideDownload() === true;
})
)->will($this->returnArgument(0));

$expected = new DataResponse([]);
$result = $ocs->updateShare(42, null, null, 'false', null, null, null, null, null);

$this->assertInstanceOf(get_class($expected), $result);
$this->assertEquals($expected->getData(), $result->getData());
}

public function testUpdateLinkShareDoNotSendPasswordByTalkWithTalkDisabledDoesNotChangeOther() {
$ocs = $this->mockFormatShare();

$date = new \DateTime('2000-01-01');
$date->setTime(0,0,0);

$node = $this->getMockBuilder(File::class)->getMock();
$share = $this->newShare();
$share->setPermissions(\OCP\Constants::PERMISSION_ALL)
->setSharedBy($this->currentUser)
->setShareType(\OCP\Share::SHARE_TYPE_LINK)
->setPassword('password')
->setSendPasswordByTalk(true)
->setExpirationDate($date)
->setNote('note')
->setLabel('label')
->setHideDownload(true)
->setPermissions(\OCP\Constants::PERMISSION_ALL)
->setNode($node);

$node->expects($this->once())
->method('lock')
->with(\OCP\Lock\ILockingProvider::LOCK_SHARED);

$this->shareManager->method('getShareById')->with('ocinternal:42')->willReturn($share);

$this->appManager->method('isEnabledForUser')->with('spreed')->willReturn(false);

$this->shareManager->expects($this->once())->method('updateShare')->with(
$this->callback(function (\OCP\Share\IShare $share) use ($date) {
return $share->getPermissions() === \OCP\Constants::PERMISSION_ALL &&
$share->getPassword() === 'password' &&
$share->getSendPasswordByTalk() === false &&
$share->getExpirationDate() === $date &&
$share->getNote() === 'note' &&
$share->getLabel() === 'label' &&
$share->getHideDownload() === true;
})
)->will($this->returnArgument(0));

$expected = new DataResponse([]);
$result = $ocs->updateShare(42, null, null, 'false', null, null, null, null, null);

$this->assertInstanceOf(get_class($expected), $result);
$this->assertEquals($expected->getData(), $result->getData());
@@ -1726,7 +1997,11 @@ class ShareAPIControllerTest extends TestCase {
->setSharedBy($this->currentUser)
->setShareType(\OCP\Share::SHARE_TYPE_LINK)
->setPassword('password')
->setSendPasswordByTalk(true)
->setExpirationDate(new \DateTime())
->setNote('note')
->setLabel('label')
->setHideDownload(true)
->setPermissions(\OCP\Constants::PERMISSION_ALL)
->setNode($node);

@@ -1743,12 +2018,16 @@ class ShareAPIControllerTest extends TestCase {

return $share->getPermissions() === \OCP\Constants::PERMISSION_ALL &&
$share->getPassword() === 'password' &&
$share->getExpirationDate() == $date;
$share->getSendPasswordByTalk() === true &&
$share->getExpirationDate() == $date &&
$share->getNote() === 'note' &&
$share->getLabel() === 'label' &&
$share->getHideDownload() === true;
})
)->will($this->returnArgument(0));

$expected = new DataResponse([]);
$result = $ocs->updateShare(42, null, null, null, null, '2010-12-23');
$result = $ocs->updateShare(42, null, null, null, null, '2010-12-23', null, null, null);

$this->assertInstanceOf(get_class($expected), $result);
$this->assertEquals($expected->getData(), $result->getData());
@@ -1766,7 +2045,11 @@ class ShareAPIControllerTest extends TestCase {
->setSharedBy($this->currentUser)
->setShareType(\OCP\Share::SHARE_TYPE_LINK)
->setPassword('password')
->setSendPasswordByTalk(true)
->setExpirationDate($date)
->setNote('note')
->setLabel('label')
->setHideDownload(true)
->setPermissions(\OCP\Constants::PERMISSION_ALL)
->setNode($folder);

@@ -1777,7 +2060,11 @@ class ShareAPIControllerTest extends TestCase {
$this->callback(function (\OCP\Share\IShare $share) use ($date) {
return $share->getPermissions() === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE) &&
$share->getPassword() === 'password' &&
$share->getExpirationDate() === $date;
$share->getSendPasswordByTalk() === true &&
$share->getExpirationDate() === $date &&
$share->getNote() === 'note' &&
$share->getLabel() === 'label' &&
$share->getHideDownload() === true;
})
)->will($this->returnArgument(0));

@@ -1785,7 +2072,7 @@ class ShareAPIControllerTest extends TestCase {
->willReturn([]);

$expected = new DataResponse([]);
$result = $ocs->updateShare(42, null, null, null, 'true', null);
$result = $ocs->updateShare(42, null, null, null, 'true', null, null, null, null);

$this->assertInstanceOf(get_class($expected), $result);
$this->assertEquals($expected->getData(), $result->getData());
@@ -1803,7 +2090,11 @@ class ShareAPIControllerTest extends TestCase {
->setSharedBy($this->currentUser)
->setShareType(\OCP\Share::SHARE_TYPE_LINK)
->setPassword('password')
->setSendPasswordByTalk(true)
->setExpirationDate($date)
->setNote('note')
->setLabel('label')
->setHideDownload(true)
->setPermissions(\OCP\Constants::PERMISSION_ALL)
->setNode($folder);

@@ -1814,14 +2105,18 @@ class ShareAPIControllerTest extends TestCase {
$this->callback(function (\OCP\Share\IShare $share) use ($date) {
return $share->getPermissions() === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE) &&
$share->getPassword() === 'password' &&
$share->getExpirationDate() === $date;
$share->getSendPasswordByTalk() === true &&
$share->getExpirationDate() === $date &&
$share->getNote() === 'note' &&
$share->getLabel() === 'label' &&
$share->getHideDownload() === true;
})
)->will($this->returnArgument(0));

$this->shareManager->method('getSharedWith')->willReturn([]);

$expected = new DataResponse([]);
$result = $ocs->updateShare(42, 7, null, null, null, null);
$result = $ocs->updateShare(42, 7, null, null, null, null, null, null, null);

$this->assertInstanceOf(get_class($expected), $result);
$this->assertEquals($expected->getData(), $result->getData());
@@ -1839,7 +2134,11 @@ class ShareAPIControllerTest extends TestCase {
->setSharedBy($this->currentUser)
->setShareType(\OCP\Share::SHARE_TYPE_LINK)
->setPassword('password')
->setSendPasswordByTalk(true)
->setExpirationDate($date)
->setNote('note')
->setLabel('label')
->setHideDownload(true)
->setPermissions(\OCP\Constants::PERMISSION_READ)
->setNode($folder);

@@ -1850,14 +2149,18 @@ class ShareAPIControllerTest extends TestCase {
$this->callback(function (\OCP\Share\IShare $share) use ($date) {
return $share->getPermissions() === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE) &&
$share->getPassword() === 'password' &&
$share->getExpirationDate() === $date;
$share->getSendPasswordByTalk() === true &&
$share->getExpirationDate() === $date &&
$share->getNote() === 'note' &&
$share->getLabel() === 'label' &&
$share->getHideDownload() === true;
})
)->will($this->returnArgument(0));

$this->shareManager->method('getSharedWith')->willReturn([]);

$expected = new DataResponse([]);
$result = $ocs->updateShare(42, 31, null, null, null, null);
$result = $ocs->updateShare(42, 31, null, null, null, null, null, null, null);

$this->assertInstanceOf(get_class($expected), $result);
$this->assertEquals($expected->getData(), $result->getData());
@@ -2173,8 +2476,10 @@ class ShareAPIControllerTest extends TestCase {
'share_with' => 'recipient',
'share_with_displayname' => 'recipient',
'note' => 'personal note',
'label' => null,
'mail_send' => 0,
'mimetype' => 'myMimeType',
'hide_download' => 0,
], $share, [], false
];
// User backend up
@@ -2192,6 +2497,7 @@ class ShareAPIControllerTest extends TestCase {
'uid_file_owner' => 'owner',
'displayname_file_owner' => 'ownerDN',
'note' => 'personal note',
'label' => null,
'path' => 'file',
'item_type' => 'file',
'storage_id' => 'storageId',
@@ -2204,6 +2510,7 @@ class ShareAPIControllerTest extends TestCase {
'share_with_displayname' => 'recipientDN',
'mail_send' => 0,
'mimetype' => 'myMimeType',
'hide_download' => 0,
], $share, [
['owner', $owner],
['initiator', $initiator],
@@ -2237,6 +2544,7 @@ class ShareAPIControllerTest extends TestCase {
'uid_file_owner' => 'owner',
'displayname_file_owner' => 'owner',
'note' => 'personal note',
'label' => null,
'path' => 'file',
'item_type' => 'file',
'storage_id' => 'storageId',
@@ -2249,6 +2557,7 @@ class ShareAPIControllerTest extends TestCase {
'share_with_displayname' => 'recipient',
'mail_send' => 0,
'mimetype' => 'myMimeType',
'hide_download' => 0,
], $share, [], false
];

@@ -2280,6 +2589,7 @@ class ShareAPIControllerTest extends TestCase {
'uid_file_owner' => 'owner',
'displayname_file_owner' => 'owner',
'note' => 'personal note',
'label' => null,
'path' => 'file',
'item_type' => 'file',
'storage_id' => 'storageId',
@@ -2292,6 +2602,7 @@ class ShareAPIControllerTest extends TestCase {
'share_with_displayname' => 'recipientGroupDisplayName',
'mail_send' => 0,
'mimetype' => 'myMimeType',
'hide_download' => 0,
], $share, [], false
];

@@ -2321,6 +2632,7 @@ class ShareAPIControllerTest extends TestCase {
'uid_file_owner' => 'owner',
'displayname_file_owner' => 'owner',
'note' => 'personal note',
'label' => null,
'path' => 'file',
'item_type' => 'file',
'storage_id' => 'storageId',
@@ -2333,6 +2645,55 @@ class ShareAPIControllerTest extends TestCase {
'share_with_displayname' => 'recipientGroup2',
'mail_send' => 0,
'mimetype' => 'myMimeType',
'hide_download' => 0,
], $share, [], false
];

$share = \OC::$server->getShareManager()->newShare();
$share->setShareType(\OCP\Share::SHARE_TYPE_LINK)
->setSharedBy('initiator')
->setShareOwner('owner')
->setPermissions(\OCP\Constants::PERMISSION_READ)
->setNode($file)
->setShareTime(new \DateTime('2000-01-01T00:01:02'))
->setTarget('myTarget')
->setPassword('mypassword')
->setExpirationDate(new \DateTime('2001-01-02T00:00:00'))
->setToken('myToken')
->setNote('personal note')
->setLabel('new link share')
->setId(42);

$result[] = [
[
'id' => 42,
'share_type' => \OCP\Share::SHARE_TYPE_LINK,
'uid_owner' => 'initiator',
'displayname_owner' => 'initiator',
'permissions' => 1,
'stime' => 946684862,
'parent' => null,
'expiration' => '2001-01-02 00:00:00',
'token' => 'myToken',
'uid_file_owner' => 'owner',
'displayname_file_owner' => 'owner',
'note' => 'personal note',
'label' => 'new link share',
'path' => 'file',
'item_type' => 'file',
'storage_id' => 'storageId',
'storage' => 100,
'item_source' => 3,
'file_source' => 3,
'file_parent' => 1,
'file_target' => 'myTarget',
'share_with' => 'mypassword',
'share_with_displayname' => 'mypassword',
'send_password_by_talk' => false,
'mail_send' => 0,
'url' => 'myLink',
'mimetype' => 'myMimeType',
'hide_download' => 0,
], $share, [], false
];

@@ -2345,9 +2706,11 @@ class ShareAPIControllerTest extends TestCase {
->setShareTime(new \DateTime('2000-01-01T00:01:02'))
->setTarget('myTarget')
->setPassword('mypassword')
->setSendPasswordByTalk(true)
->setExpirationDate(new \DateTime('2001-01-02T00:00:00'))
->setToken('myToken')
->setNote('personal note')
->setLabel('new link share')
->setId(42);

$result[] = [
@@ -2364,6 +2727,7 @@ class ShareAPIControllerTest extends TestCase {
'uid_file_owner' => 'owner',
'displayname_file_owner' => 'owner',
'note' => 'personal note',
'label' => 'new link share',
'path' => 'file',
'item_type' => 'file',
'storage_id' => 'storageId',
@@ -2374,9 +2738,11 @@ class ShareAPIControllerTest extends TestCase {
'file_target' => 'myTarget',
'share_with' => 'mypassword',
'share_with_displayname' => 'mypassword',
'send_password_by_talk' => true,
'mail_send' => 0,
'url' => 'myLink',
'mimetype' => 'myMimeType',
'hide_download' => 0,
], $share, [], false
];

@@ -2406,6 +2772,7 @@ class ShareAPIControllerTest extends TestCase {
'uid_file_owner' => 'owner',
'displayname_file_owner' => 'owner',
'note' => 'personal note',
'label' => null,
'path' => 'folder',
'item_type' => 'folder',
'storage_id' => 'storageId',
@@ -2418,6 +2785,7 @@ class ShareAPIControllerTest extends TestCase {
'share_with_displayname' => 'foobar',
'mail_send' => 0,
'mimetype' => 'myFolderMimeType',
'hide_download' => 0,
], $share, [], false
];

@@ -2449,6 +2817,7 @@ class ShareAPIControllerTest extends TestCase {
'uid_file_owner' => 'owner',
'displayname_file_owner' => 'owner',
'note' => '',
'label' => null,
'path' => 'folder',
'item_type' => 'folder',
'storage_id' => 'storageId',
@@ -2462,6 +2831,7 @@ class ShareAPIControllerTest extends TestCase {
'share_with_avatar' => 'path/to/the/avatar',
'mail_send' => 0,
'mimetype' => 'myFolderMimeType',
'hide_download' => 0,
], $share, [], false
];

@@ -2491,6 +2861,7 @@ class ShareAPIControllerTest extends TestCase {
'uid_file_owner' => 'owner',
'displayname_file_owner' => 'owner',
'note' => '',
'label' => null,
'path' => 'folder',
'item_type' => 'folder',
'storage_id' => 'storageId',
@@ -2504,6 +2875,7 @@ class ShareAPIControllerTest extends TestCase {
'share_with_avatar' => '',
'mail_send' => 0,
'mimetype' => 'myFolderMimeType',
'hide_download' => 0,
], $share, [], false
];

@@ -2533,6 +2905,7 @@ class ShareAPIControllerTest extends TestCase {
'uid_file_owner' => 'owner',
'displayname_file_owner' => 'owner',
'note' => '',
'label' => null,
'path' => 'folder',
'item_type' => 'folder',
'storage_id' => 'storageId',
@@ -2546,6 +2919,7 @@ class ShareAPIControllerTest extends TestCase {
'share_with_avatar' => '',
'mail_send' => 0,
'mimetype' => 'myFolderMimeType',
'hide_download' => 0,
], $share, [], false
];

@@ -2590,6 +2964,7 @@ class ShareAPIControllerTest extends TestCase {
'uid_file_owner' => 'owner',
'displayname_file_owner' => 'owner',
'note' => '',
'label' => null,
'path' => 'folder',
'item_type' => 'folder',
'storage_id' => 'storageId',
@@ -2603,7 +2978,8 @@ class ShareAPIControllerTest extends TestCase {
'mail_send' => 0,
'mimetype' => 'myFolderMimeType',
'password' => 'password',
'send_password_by_talk' => false
'send_password_by_talk' => false,
'hide_download' => 0,
], $share, [], false
];

@@ -2634,6 +3010,7 @@ class ShareAPIControllerTest extends TestCase {
'uid_file_owner' => 'owner',
'displayname_file_owner' => 'owner',
'note' => '',
'label' => null,
'path' => 'folder',
'item_type' => 'folder',
'storage_id' => 'storageId',
@@ -2647,7 +3024,8 @@ class ShareAPIControllerTest extends TestCase {
'mail_send' => 0,
'mimetype' => 'myFolderMimeType',
'password' => 'password',
'send_password_by_talk' => true
'send_password_by_talk' => true,
'hide_download' => 0,
], $share, [], false
];

@@ -2787,6 +3165,8 @@ class ShareAPIControllerTest extends TestCase {
'share_with_displayname' => '',
'mail_send' => 0,
'mimetype' => 'myMimeType',
'hide_download' => 0,
'label' => '',
], $share, false, []
];

@@ -2828,6 +3208,8 @@ class ShareAPIControllerTest extends TestCase {
'share_with_displayname' => 'recipientRoomName',
'mail_send' => 0,
'mimetype' => 'myMimeType',
'hide_download' => 0,
'label' => '',
], $share, true, [
'share_with_displayname' => 'recipientRoomName'
]

+ 116
- 1
apps/files_sharing/tests/Controller/ShareControllerTest.php View File

@@ -287,7 +287,8 @@ class ShareControllerTest extends \Test\TestCase {
'shareUrl' => null,
'previewImage' => null,
'previewURL' => 'downloadURL',
'note' => $note
'note' => $note,
'hideDownload' => false
);

$csp = new \OCP\AppFramework\Http\ContentSecurityPolicy();
@@ -306,6 +307,120 @@ class ShareControllerTest extends \Test\TestCase {
$this->assertEquals($expectedResponse, $response);
}

public function testShowShareHideDownload() {
$note = 'personal note';

$this->shareController->setToken('token');

$owner = $this->getMockBuilder(IUser::class)->getMock();
$owner->method('getDisplayName')->willReturn('ownerDisplay');
$owner->method('getUID')->willReturn('ownerUID');

$file = $this->getMockBuilder('OCP\Files\File')->getMock();
$file->method('getName')->willReturn('file1.txt');
$file->method('getMimetype')->willReturn('text/plain');
$file->method('getSize')->willReturn(33);
$file->method('isReadable')->willReturn(true);
$file->method('isShareable')->willReturn(true);

$share = \OC::$server->getShareManager()->newShare();
$share->setId(42);
$share->setPassword('password')
->setShareOwner('ownerUID')
->setNode($file)
->setNote($note)
->setTarget('/file1.txt')
->setHideDownload(true);

$this->session->method('exists')->with('public_link_authenticated')->willReturn(true);
$this->session->method('get')->with('public_link_authenticated')->willReturn('42');

// Even if downloads are disabled the "downloadURL" parameter is
// provided to the template, as it is needed to preview audio and GIF
// files.
$this->urlGenerator->expects($this->at(0))
->method('linkToRouteAbsolute')
->with('files_sharing.sharecontroller.downloadShare', ['token' => 'token'])
->willReturn('downloadURL');

$this->previewManager->method('isMimeSupported')->with('text/plain')->willReturn(true);

$this->config->method('getSystemValue')
->willReturnMap(
[
['max_filesize_animated_gifs_public_sharing', 10, 10],
['enable_previews', true, true],
['preview_max_x', 1024, 1024],
['preview_max_y', 1024, 1024],
]
);
$shareTmpl['maxSizeAnimateGif'] = $this->config->getSystemValue('max_filesize_animated_gifs_public_sharing', 10);
$shareTmpl['previewEnabled'] = $this->config->getSystemValue('enable_previews', true);

$this->shareManager
->expects($this->once())
->method('getShareByToken')
->with('token')
->willReturn($share);
$this->config
->expects($this->once())
->method('getAppValue')
->with('core', 'shareapi_public_link_disclaimertext', null)
->willReturn('My disclaimer text');

$this->userManager->method('get')->with('ownerUID')->willReturn($owner);

$this->eventDispatcher->expects($this->once())
->method('dispatch')
->with('OCA\Files_Sharing::loadAdditionalScripts');

$this->l10n->expects($this->any())
->method('t')
->will($this->returnCallback(function($text, $parameters) {
return vsprintf($text, $parameters);
}));

$response = $this->shareController->showShare();
$sharedTmplParams = array(
'displayName' => 'ownerDisplay',
'owner' => 'ownerUID',
'filename' => 'file1.txt',
'directory_path' => '/file1.txt',
'mimetype' => 'text/plain',
'dirToken' => 'token',
'sharingToken' => 'token',
'server2serversharing' => true,
'protected' => 'true',
'dir' => '',
'downloadURL' => 'downloadURL',
'fileSize' => '33 B',
'nonHumanFileSize' => 33,
'maxSizeAnimateGif' => 10,
'previewSupported' => true,
'previewEnabled' => true,
'previewMaxX' => 1024,
'previewMaxY' => 1024,
'hideFileList' => false,
'shareOwner' => 'ownerDisplay',
'disclaimer' => 'My disclaimer text',
'shareUrl' => null,
'previewImage' => null,
'previewURL' => 'downloadURL',
'note' => $note,
'hideDownload' => true
);

$csp = new \OCP\AppFramework\Http\ContentSecurityPolicy();
$csp->addAllowedFrameDomain('\'self\'');
$expectedResponse = new PublicTemplateResponse($this->appName, 'public', $sharedTmplParams);
$expectedResponse->setContentSecurityPolicy($csp);
$expectedResponse->setHeaderTitle($sharedTmplParams['filename']);
$expectedResponse->setHeaderDetails('shared by ' . $sharedTmplParams['displayName']);
$expectedResponse->setHeaderActions([]);

$this->assertEquals($expectedResponse, $response);
}

/**
* @expectedException \OCP\Files\NotFoundException
*/

+ 44
- 0
apps/files_trashbin/tests/CapabilitiesTest.php View File

@@ -0,0 +1,44 @@
<?php
/**
* @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_Trashbin\Tests;

use OCA\Files_Trashbin\Capabilities;
use Test\TestCase;

class CapabilitiesTest extends TestCase {

/** @var Capabilities */
private $capabilities;

public function setUp() {
parent::setUp();
$this->capabilities = new Capabilities();
}
public function testGetCapabilities() {
$capabilities = [
'files' => [
'undelete' => true
]
];

$this->assertSame($capabilities, $this->capabilities->getCapabilities());
}
}

+ 4
- 0
apps/files_versions/appinfo/info.xml View File

@@ -41,4 +41,8 @@
<collection>OCA\Files_Versions\Sabre\RootCollection</collection>
</collections>
</sabre>

<versions>
<backend for="OCP\Files\Storage\IStorage">OCA\Files_Versions\Versions\LegacyVersionsBackend</backend>
</versions>
</info>

+ 7
- 0
apps/files_versions/composer/composer/autoload_classmap.php View File

@@ -23,4 +23,11 @@ return array(
'OCA\\Files_Versions\\Sabre\\VersionHome' => $baseDir . '/../lib/Sabre/VersionHome.php',
'OCA\\Files_Versions\\Sabre\\VersionRoot' => $baseDir . '/../lib/Sabre/VersionRoot.php',
'OCA\\Files_Versions\\Storage' => $baseDir . '/../lib/Storage.php',
'OCA\\Files_Versions\\Versions\\BackendNotFoundException' => $baseDir . '/../lib/Versions/BackendNotFoundException.php',
'OCA\\Files_Versions\\Versions\\IVersion' => $baseDir . '/../lib/Versions/IVersion.php',
'OCA\\Files_Versions\\Versions\\IVersionBackend' => $baseDir . '/../lib/Versions/IVersionBackend.php',
'OCA\\Files_Versions\\Versions\\IVersionManager' => $baseDir . '/../lib/Versions/IVersionManager.php',
'OCA\\Files_Versions\\Versions\\LegacyVersionsBackend' => $baseDir . '/../lib/Versions/LegacyVersionsBackend.php',
'OCA\\Files_Versions\\Versions\\Version' => $baseDir . '/../lib/Versions/Version.php',
'OCA\\Files_Versions\\Versions\\VersionManager' => $baseDir . '/../lib/Versions/VersionManager.php',
);

+ 7
- 0
apps/files_versions/composer/composer/autoload_static.php View File

@@ -38,6 +38,13 @@ class ComposerStaticInitFiles_Versions
'OCA\\Files_Versions\\Sabre\\VersionHome' => __DIR__ . '/..' . '/../lib/Sabre/VersionHome.php',
'OCA\\Files_Versions\\Sabre\\VersionRoot' => __DIR__ . '/..' . '/../lib/Sabre/VersionRoot.php',
'OCA\\Files_Versions\\Storage' => __DIR__ . '/..' . '/../lib/Storage.php',
'OCA\\Files_Versions\\Versions\\BackendNotFoundException' => __DIR__ . '/..' . '/../lib/Versions/BackendNotFoundException.php',
'OCA\\Files_Versions\\Versions\\IVersion' => __DIR__ . '/..' . '/../lib/Versions/IVersion.php',
'OCA\\Files_Versions\\Versions\\IVersionBackend' => __DIR__ . '/..' . '/../lib/Versions/IVersionBackend.php',
'OCA\\Files_Versions\\Versions\\IVersionManager' => __DIR__ . '/..' . '/../lib/Versions/IVersionManager.php',
'OCA\\Files_Versions\\Versions\\LegacyVersionsBackend' => __DIR__ . '/..' . '/../lib/Versions/LegacyVersionsBackend.php',
'OCA\\Files_Versions\\Versions\\Version' => __DIR__ . '/..' . '/../lib/Versions/Version.php',
'OCA\\Files_Versions\\Versions\\VersionManager' => __DIR__ . '/..' . '/../lib/Versions/VersionManager.php',
);

public static function getInitializer(ClassLoader $loader)

+ 4
- 0
apps/files_versions/js/versionstabview.js View File

@@ -38,6 +38,10 @@
return t('files_versions', 'Versions');
},

getIcon: function() {
return 'icon-history';
},

nextPage: function() {
if (this._loading) {
return;

+ 3
- 3
apps/files_versions/l10n/es.js View File

@@ -5,12 +5,12 @@ OC.L10N.register(
"Failed to revert {file} to revision {timestamp}." : "No se ha podido restaurar {file} a versión {timestamp}.",
"_%n byte_::_%n bytes_" : ["%n byte","%n bytes"],
"Restore" : "Recuperar",
"No other versions available" : "No hay otras versiones disponibles",
"No other versions available" : "No hay s versiones disponibles",
"This application automatically maintains older versions of files that are changed." : "Esta aplicación mantiene automáticamente versiones antiguas de los archivos que cambian.",
"This application automatically maintains older versions of files that are changed. When enabled, a hidden versions folder is provisioned in every user’s directory and is used to store old file versions. A user can revert to an older version through the web interface at any time, with the replaced file becoming a version. The app automatically manages the versions folder to ensure the user doesn’t run out of Quota because of versions.\n\t\tIn addition to the expiry of versions, the versions app makes certain never to use more than 50% of the user’s currently available free space. If stored versions exceed this limit, the app will delete the oldest versions first until it meets this limit. More information is available in the Versions documentation." : "Esta aplicación mantiene automáticamente versiones antiguas de los archivos que cambian. Al activarse, se crea una carpeta oculta de versiones que se usa para almacenar versiones antiguas de archivos. Un usuario puede volver a una versión anterior a través de la interfaz web en cualquier momento, con el archivo reemplazado convirtiéndose en una versión. La app maneja automáticamente la carpeta de versiones para asegurarse de que el usuario no se queda sin espacio debido a las versiones.\n\nAdemás de la expiración de versiones, la app de versiones se asegura de no usar nunca más del 50% del espacio libre actualmente disponible para un usuario. Si las versiones almacenadas exceden este límite, la app borrará las versiones más antiguas hasta alcanzar este límite. Más información disponible en la documentación de Versiones.",
"This application automatically maintains older versions of files that are changed. When enabled, a hidden versions folder is provisioned in every user’s directory and is used to store old file versions. A user can revert to an older version through the web interface at any time, with the replaced file becoming a version. The app automatically manages the versions folder to ensure the user doesn’t run out of Quota because of versions.\n\t\tIn addition to the expiry of versions, the versions app makes certain never to use more than 50% of the user’s currently available free space. If stored versions exceed this limit, the app will delete the oldest versions first until it meets this limit. More information is available in the Versions documentation." : "Esta aplicación mantiene automáticamente versiones antiguas de los archivos que cambian. Al activarse, se crea una carpeta oculta de versiones que se usa para almacenar versiones antiguas de archivos. Un usuario puede volver a una versión anterior a través de la interfaz web en cualquier momento, con el archivo reemplazado convirtiéndose en una versión. La app maneja automáticamente la carpeta de versiones para asegurarse de que el usuario no se queda sin espacio debido a las versiones.\n\n\t\tAdemás de la caducidad de versiones, la app de versiones se asegura de no usar nunca más del 50% del espacio libre actualmente disponible para un usuario. Si las versiones almacenadas exceden este límite, la app borrará las versiones más antiguas hasta alcanzar este límite. Más información disponible en la documentación de Versiones.",
"Could not revert: %s" : "No se puede restaurar: %s",
"No earlier versions available" : "No hay versiones previas disponibles",
"More versions …" : "Más versiones ...",
"This application automatically maintains older versions of files that are changed. When enabled, a hidden versions folder is provisioned in every user’s directory and is used to store old file versions. A user can revert to an older version through the web interface at any time, with the replaced file becoming a version. The app automatically manages the versions folder to ensure the user doesn’t run out of Quota because of versions.\nIn addition to the expiry of versions, the versions app makes certain never to use more than 50% of the user’s currently available free space. If stored versions exceed this limit, the app will delete the oldest versions first until it meets this limit. More information is available in the Versions documentation." : "Esta aplicación mantiene automáticamente versiones antiguas de los archivos que cambian. Al activarse, se crea una carpeta oculta de versiones que se usa para almacenar versiones antiguas de archivos. Un usuario puede volver a una versión anterior a través de la interfaz web en cualquier momento, con el archivo reemplazado convirtiéndose en una versión. La app maneja automáticamente la carpeta de versiones para asegurarse de que el usuario no se queda sin espacio debido a las versiones.Además de la expiración de versiones, la app de versiones se asegura de no usar nunca más del 50% del espacio libre actualmente disponible para un usuario. Si las versiones almacenadas exceden este límite, la app borrará las versiones más antiguas hasta alcanzar este límite. Más información disponible en la documentación de Versiones."
"This application automatically maintains older versions of files that are changed. When enabled, a hidden versions folder is provisioned in every user’s directory and is used to store old file versions. A user can revert to an older version through the web interface at any time, with the replaced file becoming a version. The app automatically manages the versions folder to ensure the user doesn’t run out of Quota because of versions.\nIn addition to the expiry of versions, the versions app makes certain never to use more than 50% of the user’s currently available free space. If stored versions exceed this limit, the app will delete the oldest versions first until it meets this limit. More information is available in the Versions documentation." : "Esta aplicación mantiene automáticamente versiones antiguas de los archivos que cambian. Al activarse, se crea una carpeta oculta de versiones que se usa para almacenar versiones antiguas de archivos. Un usuario puede volver a una versión anterior a través de la interfaz web en cualquier momento, con el archivo reemplazado convirtiéndose en una versión. La app maneja automáticamente la carpeta de versiones para asegurarse de que el usuario no se queda sin espacio debido a las versiones.\nAdemás de la caducidad de versiones, la app de versiones se asegura de no usar nunca más del 50% del espacio libre actualmente disponible para un usuario. Si las versiones almacenadas exceden este límite, la app borrará las versiones más antiguas hasta alcanzar este límite. Más información disponible en la documentación de Versiones."
},
"nplurals=2; plural=(n != 1);");

+ 3
- 3
apps/files_versions/l10n/es.json View File

@@ -3,12 +3,12 @@
"Failed to revert {file} to revision {timestamp}." : "No se ha podido restaurar {file} a versión {timestamp}.",
"_%n byte_::_%n bytes_" : ["%n byte","%n bytes"],
"Restore" : "Recuperar",
"No other versions available" : "No hay otras versiones disponibles",
"No other versions available" : "No hay s versiones disponibles",
"This application automatically maintains older versions of files that are changed." : "Esta aplicación mantiene automáticamente versiones antiguas de los archivos que cambian.",
"This application automatically maintains older versions of files that are changed. When enabled, a hidden versions folder is provisioned in every user’s directory and is used to store old file versions. A user can revert to an older version through the web interface at any time, with the replaced file becoming a version. The app automatically manages the versions folder to ensure the user doesn’t run out of Quota because of versions.\n\t\tIn addition to the expiry of versions, the versions app makes certain never to use more than 50% of the user’s currently available free space. If stored versions exceed this limit, the app will delete the oldest versions first until it meets this limit. More information is available in the Versions documentation." : "Esta aplicación mantiene automáticamente versiones antiguas de los archivos que cambian. Al activarse, se crea una carpeta oculta de versiones que se usa para almacenar versiones antiguas de archivos. Un usuario puede volver a una versión anterior a través de la interfaz web en cualquier momento, con el archivo reemplazado convirtiéndose en una versión. La app maneja automáticamente la carpeta de versiones para asegurarse de que el usuario no se queda sin espacio debido a las versiones.\n\nAdemás de la expiración de versiones, la app de versiones se asegura de no usar nunca más del 50% del espacio libre actualmente disponible para un usuario. Si las versiones almacenadas exceden este límite, la app borrará las versiones más antiguas hasta alcanzar este límite. Más información disponible en la documentación de Versiones.",
"This application automatically maintains older versions of files that are changed. When enabled, a hidden versions folder is provisioned in every user’s directory and is used to store old file versions. A user can revert to an older version through the web interface at any time, with the replaced file becoming a version. The app automatically manages the versions folder to ensure the user doesn’t run out of Quota because of versions.\n\t\tIn addition to the expiry of versions, the versions app makes certain never to use more than 50% of the user’s currently available free space. If stored versions exceed this limit, the app will delete the oldest versions first until it meets this limit. More information is available in the Versions documentation." : "Esta aplicación mantiene automáticamente versiones antiguas de los archivos que cambian. Al activarse, se crea una carpeta oculta de versiones que se usa para almacenar versiones antiguas de archivos. Un usuario puede volver a una versión anterior a través de la interfaz web en cualquier momento, con el archivo reemplazado convirtiéndose en una versión. La app maneja automáticamente la carpeta de versiones para asegurarse de que el usuario no se queda sin espacio debido a las versiones.\n\n\t\tAdemás de la caducidad de versiones, la app de versiones se asegura de no usar nunca más del 50% del espacio libre actualmente disponible para un usuario. Si las versiones almacenadas exceden este límite, la app borrará las versiones más antiguas hasta alcanzar este límite. Más información disponible en la documentación de Versiones.",
"Could not revert: %s" : "No se puede restaurar: %s",
"No earlier versions available" : "No hay versiones previas disponibles",
"More versions …" : "Más versiones ...",
"This application automatically maintains older versions of files that are changed. When enabled, a hidden versions folder is provisioned in every user’s directory and is used to store old file versions. A user can revert to an older version through the web interface at any time, with the replaced file becoming a version. The app automatically manages the versions folder to ensure the user doesn’t run out of Quota because of versions.\nIn addition to the expiry of versions, the versions app makes certain never to use more than 50% of the user’s currently available free space. If stored versions exceed this limit, the app will delete the oldest versions first until it meets this limit. More information is available in the Versions documentation." : "Esta aplicación mantiene automáticamente versiones antiguas de los archivos que cambian. Al activarse, se crea una carpeta oculta de versiones que se usa para almacenar versiones antiguas de archivos. Un usuario puede volver a una versión anterior a través de la interfaz web en cualquier momento, con el archivo reemplazado convirtiéndose en una versión. La app maneja automáticamente la carpeta de versiones para asegurarse de que el usuario no se queda sin espacio debido a las versiones.Además de la expiración de versiones, la app de versiones se asegura de no usar nunca más del 50% del espacio libre actualmente disponible para un usuario. Si las versiones almacenadas exceden este límite, la app borrará las versiones más antiguas hasta alcanzar este límite. Más información disponible en la documentación de Versiones."
"This application automatically maintains older versions of files that are changed. When enabled, a hidden versions folder is provisioned in every user’s directory and is used to store old file versions. A user can revert to an older version through the web interface at any time, with the replaced file becoming a version. The app automatically manages the versions folder to ensure the user doesn’t run out of Quota because of versions.\nIn addition to the expiry of versions, the versions app makes certain never to use more than 50% of the user’s currently available free space. If stored versions exceed this limit, the app will delete the oldest versions first until it meets this limit. More information is available in the Versions documentation." : "Esta aplicación mantiene automáticamente versiones antiguas de los archivos que cambian. Al activarse, se crea una carpeta oculta de versiones que se usa para almacenar versiones antiguas de archivos. Un usuario puede volver a una versión anterior a través de la interfaz web en cualquier momento, con el archivo reemplazado convirtiéndose en una versión. La app maneja automáticamente la carpeta de versiones para asegurarse de que el usuario no se queda sin espacio debido a las versiones.\nAdemás de la caducidad de versiones, la app de versiones se asegura de no usar nunca más del 50% del espacio libre actualmente disponible para un usuario. Si las versiones almacenadas exceden este límite, la app borrará las versiones más antiguas hasta alcanzar este límite. Más información disponible en la documentación de Versiones."
},"pluralForm" :"nplurals=2; plural=(n != 1);"
}

+ 40
- 8
apps/files_versions/lib/AppInfo/Application.php View File

@@ -24,9 +24,10 @@
namespace OCA\Files_Versions\AppInfo;

use OCA\DAV\Connector\Sabre\Principal;
use OCA\Files_Versions\Versions\IVersionManager;
use OCA\Files_Versions\Versions\VersionManager;
use OCP\AppFramework\App;
use OCA\Files_Versions\Expiration;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\AppFramework\IAppContainer;
use OCA\Files_Versions\Capabilities;

class Application extends App {
@@ -43,14 +44,45 @@ class Application extends App {
/*
* Register $principalBackend for the DAV collection
*/
$container->registerService('principalBackend', function () {
$container->registerService('principalBackend', function (IAppContainer $c) {
$server = $c->getServer();
return new Principal(
\OC::$server->getUserManager(),
\OC::$server->getGroupManager(),
\OC::$server->getShareManager(),
\OC::$server->getUserSession(),
\OC::$server->getConfig()
$server->getUserManager(),
$server->getGroupManager(),
$server->getShareManager(),
$server->getUserSession(),
$server->getConfig()
);
});

$container->registerService(IVersionManager::class, function(IAppContainer $c) {
return new VersionManager();
});

$this->registerVersionBackends();
}

public function registerVersionBackends() {
$server = $this->getContainer()->getServer();
$logger = $server->getLogger();
$appManager = $server->getAppManager();
/** @var IVersionManager $versionManager */
$versionManager = $this->getContainer()->getServer()->query(IVersionManager::class);
foreach($appManager->getInstalledApps() as $app) {
$appInfo = $appManager->getAppInfo($app);
if (isset($appInfo['versions'])) {
$backends = $appInfo['versions'];
foreach($backends as $backend) {
$class = $backend['@value'];
$for = $backend['@attributes']['for'];
try {
$backendObject = $server->query($class);
$versionManager->registerBackend($for, $backendObject);
} catch (\Exception $e) {
$logger->logException($e);
}
}
}
}
}
}

+ 26
- 21
apps/files_versions/lib/Controller/PreviewController.php View File

@@ -21,45 +21,53 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\Files_Versions\Controller;

use OCA\Files_Versions\Versions\IVersionManager;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\FileDisplayResponse;
use OCP\Files\File;
use OCP\Files\Folder;
use OCP\Files\IMimeTypeDetector;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\IPreview;
use OCP\IRequest;
use OCP\IUserSession;

class PreviewController extends Controller {

/** @var IRootFolder */
private $rootFolder;

/** @var string */
private $userId;
/** @var IUserSession */
private $userSession;

/** @var IMimeTypeDetector */
private $mimeTypeDetector;

/** @var IVersionManager */
private $versionManager;

/** @var IPreview */
private $previewManager;

public function __construct($appName,
IRequest $request,
IRootFolder $rootFolder,
$userId,
IMimeTypeDetector $mimeTypeDetector,
IPreview $previewManager) {
public function __construct(
$appName,
IRequest $request,
IRootFolder $rootFolder,
IUserSession $userSession,
IMimeTypeDetector $mimeTypeDetector,
IVersionManager $versionManager,
IPreview $previewManager
) {
parent::__construct($appName, $request);

$this->rootFolder = $rootFolder;
$this->userId = $userId;
$this->userSession = $userSession;
$this->mimeTypeDetector = $mimeTypeDetector;
$this->versionManager = $versionManager;
$this->previewManager = $previewManager;
}

@@ -79,20 +87,17 @@ class PreviewController extends Controller {
$y = 44,
$version = ''
) {
if($file === '' || $version === '' || $x === 0 || $y === 0) {
if ($file === '' || $version === '' || $x === 0 || $y === 0) {
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}

try {
$userFolder = $this->rootFolder->getUserFolder($this->userId);
/** @var Folder $versionFolder */
$versionFolder = $userFolder->getParent()->get('files_versions');
$mimeType = $this->mimeTypeDetector->detectPath($file);
$file = $versionFolder->get($file.'.v'.$version);

/** @var File $file */
$f = $this->previewManager->getPreview($file, $x, $y, true, IPreview::MODE_FILL, $mimeType);
return new FileDisplayResponse($f, Http::STATUS_OK, ['Content-Type' => $f->getMimeType()]);
$user = $this->userSession->getUser();
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
$file = $userFolder->get($file);
$versionFile = $this->versionManager->getVersionFile($user, $file, (int)$version);
$preview = $this->previewManager->getPreview($versionFile, $x, $y, true, IPreview::MODE_FILL, $versionFile->getMimetype());
return new FileDisplayResponse($preview, Http::STATUS_OK, ['Content-Type' => $preview->getMimeType()]);
} catch (NotFoundException $e) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
} catch (\InvalidArgumentException $e) {

+ 3
- 9
apps/files_versions/lib/Sabre/RestoreFolder.php View File

@@ -24,6 +24,7 @@ declare(strict_types=1);

namespace OCA\Files_Versions\Sabre;

use OCP\IUser;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\ICollection;
use Sabre\DAV\IMoveTarget;
@@ -31,14 +32,6 @@ use Sabre\DAV\INode;


class RestoreFolder implements ICollection, IMoveTarget {

/** @var string */
protected $userId;

public function __construct(string $userId) {
$this->userId = $userId;
}

public function createFile($name, $data = null) {
throw new Forbidden();
}
@@ -80,7 +73,8 @@ class RestoreFolder implements ICollection, IMoveTarget {
return false;
}

return $sourceNode->rollBack();
$sourceNode->rollBack();
return true;
}

}

+ 20
- 5
apps/files_versions/lib/Sabre/RootCollection.php View File

@@ -20,10 +20,13 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\Files_Versions\Sabre;

use OCA\Files_Versions\Versions\IVersionManager;
use OCP\Files\IRootFolder;
use OCP\IConfig;
use OCP\IUserManager;
use Sabre\DAV\INode;
use Sabre\DAVACL\AbstractPrincipalCollection;
use Sabre\DAVACL\PrincipalBackend;
@@ -33,12 +36,24 @@ class RootCollection extends AbstractPrincipalCollection {
/** @var IRootFolder */
private $rootFolder;

public function __construct(PrincipalBackend\BackendInterface $principalBackend,
IRootFolder $rootFolder,
IConfig $config) {
/** @var IUserManager */
private $userManager;

/** @var IVersionManager */
private $versionManager;

public function __construct(
PrincipalBackend\BackendInterface $principalBackend,
IRootFolder $rootFolder,
IConfig $config,
IUserManager $userManager,
IVersionManager $versionManager
) {
parent::__construct($principalBackend, 'principals/users');

$this->rootFolder = $rootFolder;
$this->userManager = $userManager;
$this->versionManager = $versionManager;

$this->disableListing = !$config->getSystemValue('debug', false);
}
@@ -54,12 +69,12 @@ class RootCollection extends AbstractPrincipalCollection {
* @return INode
*/
public function getChildForPrincipal(array $principalInfo) {
list(,$name) = \Sabre\Uri\split($principalInfo['uri']);
list(, $name) = \Sabre\Uri\split($principalInfo['uri']);
$user = \OC::$server->getUserSession()->getUser();
if (is_null($user) || $name !== $user->getUID()) {
throw new \Sabre\DAV\Exception\Forbidden();
}
return new VersionHome($principalInfo, $this->rootFolder);
return new VersionHome($principalInfo, $this->rootFolder, $this->userManager, $this->versionManager);
}

public function getName() {

+ 15
- 7
apps/files_versions/lib/Sabre/VersionCollection.php View File

@@ -21,11 +21,15 @@ declare(strict_types=1);
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\Files_Versions\Sabre;

use OCA\Files_Versions\Storage;
use OCA\Files_Versions\Versions\IVersion;
use OCA\Files_Versions\Versions\IVersionManager;
use OCP\Files\File;
use OCP\Files\Folder;
use OCP\IUser;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\ICollection;
@@ -37,13 +41,17 @@ class VersionCollection implements ICollection {
/** @var File */
private $file;

/** @var string */
private $userId;
/** @var IUser */
private $user;

/** @var IVersionManager */
private $versionManager;

public function __construct(Folder $userFolder, File $file, string $userId) {
public function __construct(Folder $userFolder, File $file, IUser $user, IVersionManager $versionManager) {
$this->userFolder = $userFolder;
$this->file = $file;
$this->userId = $userId;
$this->user = $user;
$this->versionManager = $versionManager;
}

public function createFile($name, $data = null) {
@@ -68,10 +76,10 @@ class VersionCollection implements ICollection {
}

public function getChildren(): array {
$versions = Storage::getVersions($this->userId, $this->userFolder->getRelativePath($this->file->getPath()));
$versions = $this->versionManager->getVersionsForFile($this->user, $this->file);

return array_map(function (array $data) {
return new VersionFile($data, $this->userFolder->getParent());
return array_map(function (IVersion $version) {
return new VersionFile($version, $this->versionManager);
}, $versions);
}


+ 18
- 23
apps/files_versions/lib/Sabre/VersionFile.php View File

@@ -21,26 +21,26 @@ declare(strict_types=1);
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\Files_Versions\Sabre;

use OCA\Files_Versions\Storage;
use OCP\Files\File;
use OCP\Files\Folder;
use OCA\Files_Versions\Versions\IVersion;
use OCA\Files_Versions\Versions\IVersionManager;
use OCP\Files\NotFoundException;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\IFile;

class VersionFile implements IFile {
/** @var array */
private $data;
/** @var IVersion */
private $version;

/** @var Folder */
private $userRoot;
/** @var IVersionManager */
private $versionManager;

public function __construct(array $data, Folder $userRoot) {
$this->data = $data;
$this->userRoot = $userRoot;
public function __construct(IVersion $version, IVersionManager $versionManager) {
$this->version = $version;
$this->versionManager = $versionManager;
}

public function put($data) {
@@ -49,27 +49,22 @@ class VersionFile implements IFile {

public function get() {
try {
/** @var Folder $versions */
$versions = $this->userRoot->get('files_versions');
/** @var File $version */
$version = $versions->get($this->data['path'].'.v'.$this->data['version']);
return $this->versionManager->read($this->version);
} catch (NotFoundException $e) {
throw new NotFound();
}

return $version->fopen('rb');
}

public function getContentType(): string {
return $this->data['mimetype'];
return $this->version->getMimeType();
}

public function getETag(): string {
return $this->data['version'];
return (string)$this->version->getRevisionId();
}

public function getSize(): int {
return $this->data['size'];
return $this->version->getSize();
}

public function delete() {
@@ -77,7 +72,7 @@ class VersionFile implements IFile {
}

public function getName(): string {
return $this->data['version'];
return (string)$this->version->getRevisionId();
}

public function setName($name) {
@@ -85,10 +80,10 @@ class VersionFile implements IFile {
}

public function getLastModified(): int {
return (int)$this->data['version'];
return $this->version->getTimestamp();
}

public function rollBack(): bool {
return Storage::rollback($this->data['path'], $this->data['version']);
public function rollBack() {
$this->versionManager->rollback($this->version);
}
}

+ 28
- 9
apps/files_versions/lib/Sabre/VersionHome.php View File

@@ -20,9 +20,13 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\Files_Versions\Sabre;

use OC\User\NoUserException;
use OCA\Files_Versions\Versions\IVersionManager;
use OCP\Files\IRootFolder;
use OCP\IUserManager;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\ICollection;

@@ -34,9 +38,25 @@ class VersionHome implements ICollection {
/** @var IRootFolder */
private $rootFolder;

public function __construct(array $principalInfo, IRootFolder $rootFolder) {
/** @var IUserManager */
private $userManager;

/** @var IVersionManager */
private $versionManager;

public function __construct(array $principalInfo, IRootFolder $rootFolder, IUserManager $userManager, IVersionManager $versionManager) {
$this->principalInfo = $principalInfo;
$this->rootFolder = $rootFolder;
$this->userManager = $userManager;
$this->versionManager = $versionManager;
}

private function getUser() {
list(, $name) = \Sabre\Uri\split($this->principalInfo['uri']);
$user = $this->userManager->get($name);
if (!$user) {
throw new NoUserException();
}
}

public function delete() {
@@ -44,8 +64,7 @@ class VersionHome implements ICollection {
}

public function getName(): string {
list(,$name) = \Sabre\Uri\split($this->principalInfo['uri']);
return $name;
return $this->getUser()->getUID();
}

public function setName($name) {
@@ -61,22 +80,22 @@ class VersionHome implements ICollection {
}

public function getChild($name) {
list(,$userId) = \Sabre\Uri\split($this->principalInfo['uri']);
$user = $this->getUser();

if ($name === 'versions') {
return new VersionRoot($userId, $this->rootFolder);
return new VersionRoot($user, $this->rootFolder, $this->versionManager);
}
if ($name === 'restore') {
return new RestoreFolder($userId);
return new RestoreFolder();
}
}

public function getChildren() {
list(,$userId) = \Sabre\Uri\split($this->principalInfo['uri']);
$user = $this->getUser();

return [
new VersionRoot($userId, $this->rootFolder),
new RestoreFolder($userId),
new VersionRoot($user, $this->rootFolder, $this->versionManager),
new RestoreFolder(),
];
}


+ 12
- 6
apps/files_versions/lib/Sabre/VersionRoot.php View File

@@ -23,23 +23,29 @@ declare(strict_types=1);
*/
namespace OCA\Files_Versions\Sabre;

use OCA\Files_Versions\Versions\IVersionManager;
use OCP\Files\File;
use OCP\Files\IRootFolder;
use OCP\IUser;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\ICollection;

class VersionRoot implements ICollection {

/** @var string */
private $userId;
/** @var IUser */
private $user;

/** @var IRootFolder */
private $rootFolder;

public function __construct(string $userId, IRootFolder $rootFolder) {
$this->userId = $userId;
/** @var IVersionManager */
private $versionManager;

public function __construct(IUser $user, IRootFolder $rootFolder, IVersionManager $versionManager) {
$this->user = $user;
$this->rootFolder = $rootFolder;
$this->versionManager = $versionManager;
}

public function delete() {
@@ -63,7 +69,7 @@ class VersionRoot implements ICollection {
}

public function getChild($name) {
$userFolder = $this->rootFolder->getUserFolder($this->userId);
$userFolder = $this->rootFolder->getUserFolder($this->user->getUID());

$fileId = (int)$name;
$nodes = $userFolder->getById($fileId);
@@ -78,7 +84,7 @@ class VersionRoot implements ICollection {
throw new NotFound();
}

return new VersionCollection($userFolder, $node, $this->userId);
return new VersionCollection($userFolder, $node, $this->user, $this->versionManager);
}

public function getChildren(): array {

+ 11
- 14
apps/files_versions/lib/Storage.php View File

@@ -48,6 +48,7 @@ use OC\Files\View;
use OCA\Files_Versions\AppInfo\Application;
use OCA\Files_Versions\Command\Expire;
use OCA\Files_Versions\Events\CreateVersionEvent;
use OCA\Files_Versions\Versions\IVersionManager;
use OCP\Files\NotFoundException;
use OCP\Lock\ILockingProvider;
use OCP\User;
@@ -178,10 +179,10 @@ class Storage {
list($uid, $filename) = self::getUidAndFilename($filename);

$files_view = new View('/'.$uid .'/files');
$users_view = new View('/'.$uid);

$eventDispatcher = \OC::$server->getEventDispatcher();
$id = $files_view->getFileInfo($filename)->getId();
$fileInfo = $files_view->getFileInfo($filename);
$id = $fileInfo->getId();
$nodes = \OC::$server->getRootFolder()->getById($id);
foreach ($nodes as $node) {
$event = new CreateVersionEvent($node);
@@ -192,20 +193,16 @@ class Storage {
}

// no use making versions for empty files
if ($files_view->filesize($filename) === 0) {
if ($fileInfo->getSize() === 0) {
return false;
}

// create all parent folders
self::createMissingDirectories($filename, $users_view);
self::scheduleExpire($uid, $filename);
/** @var IVersionManager $versionManager */
$versionManager = \OC::$server->query(IVersionManager::class);
$userManager = \OC::$server->getUserManager();
$user = $userManager->get($uid);

// store a new version of a file
$mtime = $users_view->filemtime('files/' . $filename);
$users_view->copy('files/' . $filename, 'files_versions/' . $filename . '.v' . $mtime);
// call getFileInfo to enforce a file cache entry for the new version
$users_view->getFileInfo('files_versions/' . $filename . '.v' . $mtime);
$versionManager->createVersion($user, $fileInfo);
}


@@ -695,7 +692,7 @@ class Storage {
* @param string $uid owner of the file
* @param string $fileName file/folder for which to schedule expiration
*/
private static function scheduleExpire($uid, $fileName) {
public static function scheduleExpire($uid, $fileName) {
// let the admin disable auto expire
$expiration = self::getExpiration();
if ($expiration->isEnabled()) {
@@ -833,7 +830,7 @@ class Storage {
* "files" folder
* @param View $view view on data/user/
*/
private static function createMissingDirectories($filename, $view) {
public static function createMissingDirectories($filename, $view) {
$dirname = Filesystem::normalizePath(dirname($filename));
$dirParts = explode('/', $dirname);
$dir = "/files_versions";

+ 26
- 0
apps/files_versions/lib/Versions/BackendNotFoundException.php View File

@@ -0,0 +1,26 @@
<?php
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @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_Versions\Versions;

class BackendNotFoundException extends \Exception {

}

+ 99
- 0
apps/files_versions/lib/Versions/IVersion.php View File

@@ -0,0 +1,99 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @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_Versions\Versions;

use OCP\Files\FileInfo;
use OCP\IUser;

/**
* @since 15.0.0
*/
interface IVersion {
/**
* @return IVersionBackend
* @since 15.0.0
*/
public function getBackend(): IVersionBackend;

/**
* Get the file info of the source file
*
* @return FileInfo
* @since 15.0.0
*/
public function getSourceFile(): FileInfo;

/**
* Get the id of the revision for the file
*
* @return int
* @since 15.0.0
*/
public function getRevisionId(): int;

/**
* Get the timestamp this version was created
*
* @return int
* @since 15.0.0
*/
public function getTimestamp(): int;

/**
* Get the size of this version
*
* @return int
* @since 15.0.0
*/
public function getSize(): int;

/**
* Get the name of the source file at the time of making this version
*
* @return string
* @since 15.0.0
*/
public function getSourceFileName(): string;

/**
* Get the mimetype of this version
*
* @return string
* @since 15.0.0
*/
public function getMimeType(): string;

/**
* Get the path of this version
*
* @return string
* @since 15.0.0
*/
public function getVersionPath(): string;

/**
* @return IUser
* @since 15.0.0
*/
public function getUser(): IUser;
}

+ 81
- 0
apps/files_versions/lib/Versions/IVersionBackend.php View File

@@ -0,0 +1,81 @@
<?php declare(strict_types=1);
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @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_Versions\Versions;

use OCP\Files\File;
use OCP\Files\FileInfo;
use OCP\Files\NotFoundException;
use OCP\Files\SimpleFS\ISimpleFile;
use OCP\IUser;

/**
* @since 15.0.0
*/
interface IVersionBackend {
/**
* Get all versions for a file
*
* @param IUser $user
* @param FileInfo $file
* @return IVersion[]
* @since 15.0.0
*/
public function getVersionsForFile(IUser $user, FileInfo $file): array;

/**
* Create a new version for a file
*
* @param IUser $user
* @param FileInfo $file
* @since 15.0.0
*/
public function createVersion(IUser $user, FileInfo $file);

/**
* Restore this version
*
* @param IVersion $version
* @since 15.0.0
*/
public function rollback(IVersion $version);

/**
* Open the file for reading
*
* @param IVersion $version
* @return resource
* @throws NotFoundException
* @since 15.0.0
*/
public function read(IVersion $version);

/**
* Get the preview for a specific version of a file
*
* @param IUser $user
* @param FileInfo $sourceFile
* @param int $revision
* @return ISimpleFile
* @since 15.0.0
*/
public function getVersionFile(IUser $user, FileInfo $sourceFile, int $revision): File;
}

+ 36
- 0
apps/files_versions/lib/Versions/IVersionManager.php View File

@@ -0,0 +1,36 @@
<?php declare(strict_types=1);
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @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_Versions\Versions;

/**
* @since 15.0.0
*/
interface IVersionManager extends IVersionBackend {
/**
* Register a new backend
*
* @param string $storageType
* @param IVersionBackend $backend
* @since 15.0.0
*/
public function registerBackend(string $storageType, IVersionBackend $backend);
}

+ 105
- 0
apps/files_versions/lib/Versions/LegacyVersionsBackend.php View File

@@ -0,0 +1,105 @@
<?php declare(strict_types=1);
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @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_Versions\Versions;

use OC\Files\View;
use OCA\Files_Versions\Storage;
use OCP\Files\File;
use OCP\Files\FileInfo;
use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\IUser;

class LegacyVersionsBackend implements IVersionBackend {
/** @var IRootFolder */
private $rootFolder;

public function __construct(IRootFolder $rootFolder) {
$this->rootFolder = $rootFolder;
}

public function getVersionsForFile(IUser $user, FileInfo $file): array {
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
$versions = Storage::getVersions($user->getUID(), $userFolder->getRelativePath($file->getPath()));

return array_map(function (array $data) use ($file, $user) {
return new Version(
(int)$data['version'],
(int)$data['version'],
$data['name'],
(int)$data['size'],
$data['mimetype'],
$data['path'],
$file,
$this,
$user
);
}, $versions);
}

public function createVersion(IUser $user, FileInfo $file) {
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
$relativePath = $userFolder->getRelativePath($file->getPath());
$userView = new View('/' . $user->getUID());
// create all parent folders
Storage::createMissingDirectories($relativePath, $userView);

Storage::scheduleExpire($user->getUID(), $relativePath);

// store a new version of a file
$userView->copy('files/' . $relativePath, 'files_versions/' . $relativePath . '.v' . $file->getMtime());
// ensure the file is scanned
$userView->getFileInfo('files_versions/' . $relativePath . '.v' . $file->getMtime());
}

public function rollback(IVersion $version) {
return Storage::rollback($version->getVersionPath(), $version->getRevisionId());
}

private function getVersionFolder(IUser $user): Folder {
$userRoot = $this->rootFolder->getUserFolder($user->getUID())
->getParent();
try {
/** @var Folder $folder */
$folder = $userRoot->get('files_versions');
return $folder;
} catch (NotFoundException $e) {
return $userRoot->newFolder('files_versions');
}
}

public function read(IVersion $version) {
$versions = $this->getVersionFolder($version->getUser());
/** @var File $file */
$file = $versions->get($version->getVersionPath() . '.v' . $version->getRevisionId());
return $file->fopen('r');
}

public function getVersionFile(IUser $user, FileInfo $sourceFile, int $revision): File {
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
$versionFolder = $this->getVersionFolder($user);
/** @var File $file */
$file = $versionFolder->get($userFolder->getRelativePath($sourceFile->getPath()) . '.v' . $revision);
return $file;
}
}

+ 113
- 0
apps/files_versions/lib/Versions/Version.php View File

@@ -0,0 +1,113 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @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_Versions\Versions;

use OCP\Files\FileInfo;
use OCP\IUser;

class Version implements IVersion {
/** @var int */
private $timestamp;

/** @var int */
private $revisionId;

/** @var string */
private $name;

/** @var int */
private $size;

/** @var string */
private $mimetype;

/** @var string */
private $path;

/** @var FileInfo */
private $sourceFileInfo;

/** @var IVersionBackend */
private $backend;

/** @var IUser */
private $user;

public function __construct(
int $timestamp,
int $revisionId,
string $name,
int $size,
string $mimetype,
string $path,
FileInfo $sourceFileInfo,
IVersionBackend $backend,
IUser $user
) {
$this->timestamp = $timestamp;
$this->revisionId = $revisionId;
$this->name = $name;
$this->size = $size;
$this->mimetype = $mimetype;
$this->path = $path;
$this->sourceFileInfo = $sourceFileInfo;
$this->backend = $backend;
$this->user = $user;
}

public function getBackend(): IVersionBackend {
return $this->backend;
}

public function getSourceFile(): FileInfo {
return $this->sourceFileInfo;
}

public function getRevisionId(): int {
return $this->revisionId;
}

public function getTimestamp(): int {
return $this->timestamp;
}

public function getSize(): int {
return $this->size;
}

public function getSourceFileName(): string {
return $this->name;
}

public function getMimeType(): string {
return $this->mimetype;
}

public function getVersionPath(): string {
return $this->path;
}

public function getUser(): IUser {
return $this->user;
}
}

+ 93
- 0
apps/files_versions/lib/Versions/VersionManager.php View File

@@ -0,0 +1,93 @@
<?php declare(strict_types=1);
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @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_Versions\Versions;

use OCP\Files\File;
use OCP\Files\FileInfo;
use OCP\Files\Storage\IStorage;
use OCP\IUser;

class VersionManager implements IVersionManager {
/** @var IVersionBackend[] */
private $backends = [];

public function registerBackend(string $storageType, IVersionBackend $backend) {
$this->backends[$storageType] = $backend;
}

/**
* @return IVersionBackend[]
*/
private function getBackends(): array {
return $this->backends;
}

/**
* @param IStorage $storage
* @return IVersionBackend
* @throws BackendNotFoundException
*/
public function getBackendForStorage(IStorage $storage): IVersionBackend {
$fullType = get_class($storage);
$backends = $this->getBackends();
$foundType = array_reduce(array_keys($backends), function ($type, $registeredType) use ($storage) {
if (
$storage->instanceOfStorage($registeredType) &&
($type === '' || is_subclass_of($registeredType, $type))
) {
return $registeredType;
} else {
return $type;
}
}, '');
if ($foundType === '') {
throw new BackendNotFoundException("Version backend for $fullType not found");
} else {
return $backends[$foundType];
}
}

public function getVersionsForFile(IUser $user, FileInfo $file): array {
$backend = $this->getBackendForStorage($file->getStorage());
return $backend->getVersionsForFile($user, $file);
}

public function createVersion(IUser $user, FileInfo $file) {
$backend = $this->getBackendForStorage($file->getStorage());
$backend->createVersion($user, $file);
}

public function rollback(IVersion $version) {
$backend = $version->getBackend();
return $backend->rollback($version);
}

public function read(IVersion $version) {
$backend = $version->getBackend();
return $backend->read($version);
}

public function getVersionFile(IUser $user, FileInfo $sourceFile, int $revision): File {
$backend = $this->getBackendForStorage($sourceFile->getStorage());
return $backend->getVersionFile($user, $sourceFile, $revision);
}
}

+ 37
- 18
apps/files_versions/tests/Controller/PreviewControllerTest.php View File

@@ -20,9 +20,12 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\Files_Versions\Tests\Controller;

use OC\User\User;
use OCA\Files_Versions\Controller\PreviewController;
use OCA\Files_Versions\Versions\IVersionManager;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\FileDisplayResponse;
@@ -34,6 +37,8 @@ use OCP\Files\NotFoundException;
use OCP\Files\SimpleFS\ISimpleFile;
use OCP\IPreview;
use OCP\IRequest;
use OCP\IUser;
use OCP\IUserSession;
use Test\TestCase;

class PreviewControllerTest extends TestCase {
@@ -50,23 +55,39 @@ class PreviewControllerTest extends TestCase {
/** @var IPreview|\PHPUnit_Framework_MockObject_MockObject */
private $previewManager;

/** @var PreviewController */
/** @var PreviewController|\PHPUnit_Framework_MockObject_MockObject */
private $controller;

/** @var IUserSession|\PHPUnit_Framework_MockObject_MockObject */
private $userSession;

/** @var IVersionManager|\PHPUnit_Framework_MockObject_MockObject */
private $versionManager;

public function setUp() {
parent::setUp();

$this->rootFolder = $this->createMock(IRootFolder::class);
$this->userId = 'user';
$user = $this->createMock(IUser::class);
$user->expects($this->any())
->method('getUID')
->willReturn($this->userId);
$this->mimeTypeDetector = $this->createMock(IMimeTypeDetector::class);
$this->previewManager = $this->createMock(IPreview::class);
$this->userSession = $this->createMock(IUserSession::class);
$this->userSession->expects($this->any())
->method('getUser')
->willReturn($user);
$this->versionManager = $this->createMock(IVersionManager::class);

$this->controller = new PreviewController(
'files_versions',
$this->createMock(IRequest::class),
$this->rootFolder,
$this->userId,
$this->userSession,
$this->mimeTypeDetector,
$this->versionManager,
$this->previewManager
);
}
@@ -102,24 +123,23 @@ class PreviewControllerTest extends TestCase {
public function testValidPreview() {
$userFolder = $this->createMock(Folder::class);
$userRoot = $this->createMock(Folder::class);
$versions = $this->createMock(Folder::class);

$this->rootFolder->method('getUserFolder')
->with($this->userId)
->willReturn($userFolder);
$userFolder->method('getParent')
->willReturn($userRoot);
$userRoot->method('get')
->with('files_versions')
->willReturn($versions);

$this->mimeTypeDetector->method('detectPath')
->with($this->equalTo('file'))
->willReturn('myMime');
$sourceFile = $this->createMock(File::class);
$userFolder->method('get')
->with('file')
->willReturn($sourceFile);

$file = $this->createMock(File::class);
$versions->method('get')
->with($this->equalTo('file.v42'))
$file->method('getMimetype')
->willReturn('myMime');

$this->versionManager->method('getVersionFile')
->willReturn($file);

$preview = $this->createMock(ISimpleFile::class);
@@ -138,24 +158,23 @@ class PreviewControllerTest extends TestCase {
public function testVersionNotFound() {
$userFolder = $this->createMock(Folder::class);
$userRoot = $this->createMock(Folder::class);
$versions = $this->createMock(Folder::class);

$this->rootFolder->method('getUserFolder')
->with($this->userId)
->willReturn($userFolder);
$userFolder->method('getParent')
->willReturn($userRoot);
$userRoot->method('get')
->with('files_versions')
->willReturn($versions);

$sourceFile = $this->createMock(File::class);
$userFolder->method('get')
->with('file')
->willReturn($sourceFile);

$this->mimeTypeDetector->method('detectPath')
->with($this->equalTo('file'))
->willReturn('myMime');

$file = $this->createMock(File::class);
$versions->method('get')
->with($this->equalTo('file.v42'))
$this->versionManager->method('getVersionFile')
->willThrowException(new NotFoundException());

$res = $this->controller->getPreview('file', 10, 10, '42');

+ 10
- 2
apps/oauth2/lib/Controller/OauthApiController.php View File

@@ -22,8 +22,9 @@
namespace OCA\OAuth2\Controller;

use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Token\ExpiredTokenException;
use OC\Authentication\Exceptions\ExpiredTokenException;
use OC\Authentication\Token\IProvider as TokenProvider;
use OC\Security\Bruteforce\Throttler;
use OCA\OAuth2\Db\AccessTokenMapper;
use OCA\OAuth2\Db\ClientMapper;
use OCA\OAuth2\Exceptions\AccessTokenNotFoundException;
@@ -49,6 +50,8 @@ class OauthApiController extends Controller {
private $secureRandom;
/** @var ITimeFactory */
private $time;
/** @var Throttler */
private $throttler;

/**
* @param string $appName
@@ -59,6 +62,7 @@ class OauthApiController extends Controller {
* @param TokenProvider $tokenProvider
* @param ISecureRandom $secureRandom
* @param ITimeFactory $time
* @param Throttler $throttler
*/
public function __construct($appName,
IRequest $request,
@@ -67,7 +71,8 @@ class OauthApiController extends Controller {
ClientMapper $clientMapper,
TokenProvider $tokenProvider,
ISecureRandom $secureRandom,
ITimeFactory $time) {
ITimeFactory $time,
Throttler $throttler) {
parent::__construct($appName, $request);
$this->crypto = $crypto;
$this->accessTokenMapper = $accessTokenMapper;
@@ -75,6 +80,7 @@ class OauthApiController extends Controller {
$this->tokenProvider = $tokenProvider;
$this->secureRandom = $secureRandom;
$this->time = $time;
$this->throttler = $throttler;
}

/**
@@ -164,6 +170,8 @@ class OauthApiController extends Controller {
$accessToken->setEncryptedToken($this->crypto->encrypt($newToken, $newCode));
$this->accessTokenMapper->update($accessToken);

$this->throttler->resetDelay($this->request->getRemoteAddress(), 'login', ['user' => $appToken->getUID()]);

return new JSONResponse(
[
'access_token' => $newToken,

+ 40
- 4
apps/oauth2/tests/Controller/OauthApiControllerTest.php View File

@@ -22,11 +22,10 @@
namespace OCA\OAuth2\Tests\Controller;

use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Exceptions\ExpiredTokenException;
use OC\Authentication\Token\DefaultToken;
use OC\Authentication\Token\DefaultTokenMapper;
use OC\Authentication\Token\ExpiredTokenException;
use OC\Authentication\Token\IProvider as TokenProvider;
use OC\Authentication\Token\IToken;
use OC\Security\Bruteforce\Throttler;
use OCA\OAuth2\Controller\OauthApiController;
use OCA\OAuth2\Db\AccessToken;
use OCA\OAuth2\Db\AccessTokenMapper;
@@ -57,6 +56,8 @@ class OauthApiControllerTest extends TestCase {
private $secureRandom;
/** @var ITimeFactory|\PHPUnit_Framework_MockObject_MockObject */
private $time;
/** @var Throttler|\PHPUnit_Framework_MockObject_MockObject */
private $throttler;
/** @var OauthApiController */
private $oauthApiController;

@@ -70,6 +71,7 @@ class OauthApiControllerTest extends TestCase {
$this->tokenProvider = $this->createMock(TokenProvider::class);
$this->secureRandom = $this->createMock(ISecureRandom::class);
$this->time = $this->createMock(ITimeFactory::class);
$this->throttler = $this->createMock(Throttler::class);

$this->oauthApiController = new OauthApiController(
'oauth2',
@@ -79,7 +81,8 @@ class OauthApiControllerTest extends TestCase {
$this->clientMapper,
$this->tokenProvider,
$this->secureRandom,
$this->time
$this->time,
$this->throttler
);
}

@@ -286,6 +289,17 @@ class OauthApiControllerTest extends TestCase {
'user_id' => 'userId',
]);

$this->request->method('getRemoteAddress')
->willReturn('1.2.3.4');

$this->throttler->expects($this->once())
->method('resetDelay')
->with(
'1.2.3.4',
'login',
['user' => 'userId']
);

$this->assertEquals($expected, $this->oauthApiController->getToken('refresh_token', null, 'validrefresh', 'clientId', 'clientSecret'));
}

@@ -370,6 +384,17 @@ class OauthApiControllerTest extends TestCase {
$this->request->server['PHP_AUTH_USER'] = 'clientId';
$this->request->server['PHP_AUTH_PW'] = 'clientSecret';

$this->request->method('getRemoteAddress')
->willReturn('1.2.3.4');

$this->throttler->expects($this->once())
->method('resetDelay')
->with(
'1.2.3.4',
'login',
['user' => 'userId']
);

$this->assertEquals($expected, $this->oauthApiController->getToken('refresh_token', null, 'validrefresh', null, null));
}

@@ -451,6 +476,17 @@ class OauthApiControllerTest extends TestCase {
'user_id' => 'userId',
]);

$this->request->method('getRemoteAddress')
->willReturn('1.2.3.4');

$this->throttler->expects($this->once())
->method('resetDelay')
->with(
'1.2.3.4',
'login',
['user' => 'userId']
);

$this->assertEquals($expected, $this->oauthApiController->getToken('refresh_token', null, 'validrefresh', 'clientId', 'clientSecret'));
}
}

+ 51
- 0
apps/sharebymail/tests/CapabilitiesTest.php View File

@@ -0,0 +1,51 @@
<?php
/**
* @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\ShareByMail\Tests;

use OCA\ShareByMail\Capabilities;
use Test\TestCase;

class CapabilitiesTest extends TestCase {
/** @var Capabilities */
private $capabilities;

public function setUp() {
parent::setUp();

$this->capabilities = new Capabilities();
}

public function testGetCapabilities() {
$capabilities = [
'files_sharing' =>
[
'sharebymail' =>
[
'enabled' => true,
'upload_files_drop' => ['enabled' => true],
'password' => ['enabled' => true],
'expire_date' => ['enabled' => true]
]
]
];

$this->assertSame($capabilities, $this->capabilities->getCapabilities());
}
}

+ 0
- 0
apps/systemtags/tests/Activity/SettingTest.php View File


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

Loading…
Cancel
Save