Browse Source

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

Lukas Reschke 7 years ago
parent
commit
7a9d60d87e
100 changed files with 1678 additions and 204 deletions
  1. 6 6
      apps/comments/js/commentstabview.js
  2. 1 0
      apps/comments/l10n/bg_BG.js
  3. 1 0
      apps/comments/l10n/bg_BG.json
  4. 3 0
      apps/comments/l10n/cs_CZ.js
  5. 3 0
      apps/comments/l10n/cs_CZ.json
  6. 3 0
      apps/comments/l10n/de.js
  7. 3 0
      apps/comments/l10n/de.json
  8. 3 0
      apps/comments/l10n/de_DE.js
  9. 3 0
      apps/comments/l10n/de_DE.json
  10. 3 0
      apps/comments/l10n/he.js
  11. 3 0
      apps/comments/l10n/he.json
  12. 3 0
      apps/comments/l10n/it.js
  13. 3 0
      apps/comments/l10n/it.json
  14. 2 1
      apps/comments/l10n/lb.js
  15. 2 1
      apps/comments/l10n/lb.json
  16. 3 0
      apps/comments/l10n/sq.js
  17. 3 0
      apps/comments/l10n/sq.json
  18. 1 0
      apps/dav/appinfo/v1/carddav.php
  19. 2 1
      apps/dav/lib/AppInfo/Application.php
  20. 34 10
      apps/dav/lib/CardDAV/AddressBookImpl.php
  21. 5 3
      apps/dav/lib/CardDAV/CardDavBackend.php
  22. 9 5
      apps/dav/lib/CardDAV/ContactsManager.php
  23. 146 0
      apps/dav/lib/CardDAV/ImageExportPlugin.php
  24. 1 1
      apps/dav/lib/Comments/CommentNode.php
  25. 2 0
      apps/dav/lib/Server.php
  26. 25 12
      apps/dav/tests/unit/CardDAV/AddressBookImplTest.php
  27. 7 7
      apps/dav/tests/unit/CardDAV/CardDavBackendTest.php
  28. 2 1
      apps/dav/tests/unit/CardDAV/ContactsManagerTest.php
  29. 151 0
      apps/dav/tests/unit/CardDAV/ImageExportPluginTest.php
  30. 5 1
      apps/dav/tests/unit/Comments/CommentsNodeTest.php
  31. 5 1
      apps/encryption/l10n/sv.js
  32. 5 1
      apps/encryption/l10n/sv.json
  33. 4 1
      apps/federatedfilesharing/l10n/ast.js
  34. 4 1
      apps/federatedfilesharing/l10n/ast.json
  35. 4 1
      apps/federatedfilesharing/lib/DiscoveryManager.php
  36. 3 1
      apps/federatedfilesharing/lib/Notifications.php
  37. 20 5
      apps/federatedfilesharing/tests/DiscoveryManagerTest.php
  38. 9 2
      apps/files/js/search.js
  39. 28 0
      apps/files/l10n/ast.js
  40. 28 0
      apps/files/l10n/ast.json
  41. 2 1
      apps/files/l10n/bg_BG.js
  42. 2 1
      apps/files/l10n/bg_BG.json
  43. 2 0
      apps/files/l10n/lb.js
  44. 2 0
      apps/files/l10n/lb.json
  45. 11 0
      apps/files/l10n/sv.js
  46. 11 0
      apps/files/l10n/sv.json
  47. 1 0
      apps/files_external/l10n/lb.js
  48. 1 0
      apps/files_external/l10n/lb.json
  49. 1 0
      apps/files_external/l10n/sv.js
  50. 1 0
      apps/files_external/l10n/sv.json
  51. 6 0
      apps/files_external/lib/Command/Export.php
  52. 37 11
      apps/files_external/lib/Command/ListCommand.php
  53. 12 0
      apps/files_external/lib/Service/DBConfigService.php
  54. 19 0
      apps/files_external/lib/Service/GlobalStoragesService.php
  55. 10 0
      apps/files_external/tests/Service/DBConfigServiceTest.php
  56. 5 0
      apps/files_external/tests/env/start-swift-ceph.sh
  57. 4 1
      apps/files_sharing/ajax/external.php
  58. 1 0
      apps/files_sharing/l10n/lb.js
  59. 1 0
      apps/files_sharing/l10n/lb.json
  60. 9 2
      apps/files_sharing/lib/External/Storage.php
  61. 3 2
      apps/files_sharing/lib/MountProvider.php
  62. 136 0
      apps/files_sharing/tests/MountProviderTest.php
  63. 2 0
      apps/files_trashbin/js/filelist.js
  64. 22 0
      apps/files_trashbin/tests/js/filelistSpec.js
  65. 8 1
      apps/files_versions/lib/Storage.php
  66. 2 1
      apps/updatenotification/l10n/bg_BG.js
  67. 2 1
      apps/updatenotification/l10n/bg_BG.json
  68. 15 0
      apps/updatenotification/l10n/lb.js
  69. 13 0
      apps/updatenotification/l10n/lb.json
  70. 57 0
      apps/user_ldap/l10n/ast.js
  71. 57 0
      apps/user_ldap/l10n/ast.json
  72. 38 1
      apps/user_ldap/l10n/lb.js
  73. 38 1
      apps/user_ldap/l10n/lb.json
  74. 8 1
      apps/user_ldap/lib/Access.php
  75. 4 4
      autotest.sh
  76. 13 0
      core/Application.php
  77. 147 0
      core/Controller/OccController.php
  78. 1 0
      core/js/files/iedavclient.js
  79. 4 2
      core/js/setupchecks.js
  80. 1 0
      core/l10n/lb.js
  81. 1 0
      core/l10n/lb.json
  82. 15 1
      core/l10n/ro.js
  83. 15 1
      core/l10n/ro.json
  84. 1 0
      core/l10n/sv.js
  85. 1 0
      core/l10n/sv.json
  86. 1 0
      core/routes.php
  87. 9 0
      db_structure.xml
  88. 16 3
      lib/base.php
  89. 56 1
      lib/l10n/ast.js
  90. 56 1
      lib/l10n/ast.json
  91. 23 0
      lib/private/Authentication/Token/DefaultToken.php
  92. 2 2
      lib/private/Authentication/Token/DefaultTokenMapper.php
  93. 36 19
      lib/private/Authentication/Token/DefaultTokenProvider.php
  94. 18 8
      lib/private/Authentication/Token/IProvider.php
  95. 14 0
      lib/private/Authentication/Token/IToken.php
  96. 2 1
      lib/private/Console/Application.php
  97. 22 1
      lib/private/Files/View.php
  98. 125 67
      lib/private/User/Session.php
  99. 6 5
      lib/private/legacy/util.php
  100. 2 2
      public.php

+ 6 - 6
apps/comments/js/commentstabview.js

@@ -337,10 +337,10 @@
 					$comment.data('commentEl').remove();
 					$comment.remove();
 				},
-				error: function(msg) {
+				error: function() {
 					$loading.addClass('hidden');
 					$comment.removeClass('disabled');
-					OC.Notification.showTemporary(msg);
+					OC.Notification.showTemporary(t('comments', 'Error occurred while retrieving comment with id {id}', {id: commentId}));
 				}
 			});
 
@@ -388,12 +388,12 @@
 							.html(self._formatMessage(model.get('message')));
 						$row.remove();
 					},
-					error: function(msg) {
+					error: function() {
 						$submit.removeClass('hidden');
 						$loading.addClass('hidden');
 						$textArea.prop('disabled', false);
 
-						OC.Notification.showTemporary(msg);
+						OC.Notification.showTemporary(t('comments', 'Error occurred while updating comment with id {id}', {id: commentId}));
 					}
 				});
 			} else {
@@ -413,12 +413,12 @@
 						$loading.addClass('hidden');
 						$textArea.val('').prop('disabled', false);
 					},
-					error: function(msg) {
+					error: function() {
 						$submit.removeClass('hidden');
 						$loading.addClass('hidden');
 						$textArea.prop('disabled', false);
 
-						OC.Notification.showTemporary(msg);
+						OC.Notification.showTemporary(t('comments', 'Error occurred while posting comment'));
 					}
 				});
 			}

+ 1 - 0
apps/comments/l10n/bg_BG.js

@@ -3,6 +3,7 @@ OC.L10N.register(
     {
     "Type in a new comment..." : "Напиши нов коментар...",
     "Delete comment" : "Изтрий коментар",
+    "Post" : "Публикация",
     "Cancel" : "Отказ",
     "Edit comment" : "Редактирай коментра",
     "[Deleted user]" : "[Изтрит потребител]",

+ 1 - 0
apps/comments/l10n/bg_BG.json

@@ -1,6 +1,7 @@
 { "translations": {
     "Type in a new comment..." : "Напиши нов коментар...",
     "Delete comment" : "Изтрий коментар",
+    "Post" : "Публикация",
     "Cancel" : "Отказ",
     "Edit comment" : "Редактирай коментра",
     "[Deleted user]" : "[Изтрит потребител]",

+ 3 - 0
apps/comments/l10n/cs_CZ.js

@@ -12,6 +12,9 @@ OC.L10N.register(
     "More comments..." : "Více komentářů...",
     "Save" : "Uložit",
     "Allowed characters {count} of {max}" : "Povolených znaků {count} z {max}",
+    "Error occurred while retrieving comment with id {id}" : "Došlo k chybě při načítání komentáře s id {id}",
+    "Error occurred while updating comment with id {id}" : "Došlo k chybě při aktualizování komentáře s id {id}",
+    "Error occurred while posting comment" : "Došlo k chybě při zveřejňování komentáře",
     "{count} unread comments" : "{count} nepřečtených komentářů",
     "Comment" : "Komentář",
     "<strong>Comments</strong> for files <em>(always listed in stream)</em>" : "<strong>Komentáře</strong> pro soubory <em>(vždy uvedeny v proudu)</em>",

+ 3 - 0
apps/comments/l10n/cs_CZ.json

@@ -10,6 +10,9 @@
     "More comments..." : "Více komentářů...",
     "Save" : "Uložit",
     "Allowed characters {count} of {max}" : "Povolených znaků {count} z {max}",
+    "Error occurred while retrieving comment with id {id}" : "Došlo k chybě při načítání komentáře s id {id}",
+    "Error occurred while updating comment with id {id}" : "Došlo k chybě při aktualizování komentáře s id {id}",
+    "Error occurred while posting comment" : "Došlo k chybě při zveřejňování komentáře",
     "{count} unread comments" : "{count} nepřečtených komentářů",
     "Comment" : "Komentář",
     "<strong>Comments</strong> for files <em>(always listed in stream)</em>" : "<strong>Komentáře</strong> pro soubory <em>(vždy uvedeny v proudu)</em>",

+ 3 - 0
apps/comments/l10n/de.js

@@ -12,6 +12,9 @@ OC.L10N.register(
     "More comments..." : "Weitere Kommentare...",
     "Save" : "Speichern",
     "Allowed characters {count} of {max}" : "Erlaubte Zeichen {count} von {max}",
+    "Error occurred while retrieving comment with id {id}" : "Es ist ein Fehler beim Empfangen des Kommentars mit der ID {id} aufgetreten",
+    "Error occurred while updating comment with id {id}" : "Es ist ein Fehler beim Aktualisieren des Kommentars mit der ID {id} aufgetreten",
+    "Error occurred while posting comment" : "Es ist ein Fehler beim Veröffentlichen des Kommentars aufgetreten",
     "{count} unread comments" : "{count} ungelesene Kommentare",
     "Comment" : "Kommentar",
     "<strong>Comments</strong> for files <em>(always listed in stream)</em>" : "<strong>Kommentare</strong> für Dateien<em>(immer im Stream aufgelistet)</em>",

+ 3 - 0
apps/comments/l10n/de.json

@@ -10,6 +10,9 @@
     "More comments..." : "Weitere Kommentare...",
     "Save" : "Speichern",
     "Allowed characters {count} of {max}" : "Erlaubte Zeichen {count} von {max}",
+    "Error occurred while retrieving comment with id {id}" : "Es ist ein Fehler beim Empfangen des Kommentars mit der ID {id} aufgetreten",
+    "Error occurred while updating comment with id {id}" : "Es ist ein Fehler beim Aktualisieren des Kommentars mit der ID {id} aufgetreten",
+    "Error occurred while posting comment" : "Es ist ein Fehler beim Veröffentlichen des Kommentars aufgetreten",
     "{count} unread comments" : "{count} ungelesene Kommentare",
     "Comment" : "Kommentar",
     "<strong>Comments</strong> for files <em>(always listed in stream)</em>" : "<strong>Kommentare</strong> für Dateien<em>(immer im Stream aufgelistet)</em>",

+ 3 - 0
apps/comments/l10n/de_DE.js

@@ -12,6 +12,9 @@ OC.L10N.register(
     "More comments..." : "Weitere Kommentare...",
     "Save" : "Speichern",
     "Allowed characters {count} of {max}" : "{count} von {max} Zeichen benutzt",
+    "Error occurred while retrieving comment with id {id}" : "Es ist ein Fehler beim Empfangen des Kommentars mit der ID {id} aufgetreten",
+    "Error occurred while updating comment with id {id}" : "Es ist ein Fehler beim Aktualisieren des Kommentars mit der ID {id} aufgetreten",
+    "Error occurred while posting comment" : "Es ist ein Fehler beim Veröffentlichen des Kommentars aufgetreten",
     "{count} unread comments" : "[count] ungelesene Kommentare",
     "Comment" : "Kommentar",
     "<strong>Comments</strong> for files <em>(always listed in stream)</em>" : "<strong>Kommentare</strong> für Dateien<em>(immer im Stream aufgelistet)</em>",

+ 3 - 0
apps/comments/l10n/de_DE.json

@@ -10,6 +10,9 @@
     "More comments..." : "Weitere Kommentare...",
     "Save" : "Speichern",
     "Allowed characters {count} of {max}" : "{count} von {max} Zeichen benutzt",
+    "Error occurred while retrieving comment with id {id}" : "Es ist ein Fehler beim Empfangen des Kommentars mit der ID {id} aufgetreten",
+    "Error occurred while updating comment with id {id}" : "Es ist ein Fehler beim Aktualisieren des Kommentars mit der ID {id} aufgetreten",
+    "Error occurred while posting comment" : "Es ist ein Fehler beim Veröffentlichen des Kommentars aufgetreten",
     "{count} unread comments" : "[count] ungelesene Kommentare",
     "Comment" : "Kommentar",
     "<strong>Comments</strong> for files <em>(always listed in stream)</em>" : "<strong>Kommentare</strong> für Dateien<em>(immer im Stream aufgelistet)</em>",

+ 3 - 0
apps/comments/l10n/he.js

@@ -12,6 +12,9 @@ OC.L10N.register(
     "More comments..." : "תגובות נוספות...",
     "Save" : "שמירה",
     "Allowed characters {count} of {max}" : "תווים מותרים {count} מתוך {max}",
+    "Error occurred while retrieving comment with id {id}" : "שגיאה אירעה כאשר אוחזרה תגובה עם מספר זיהוי {id}",
+    "Error occurred while updating comment with id {id}" : "שגיאה אירעה כאשר עודכנה תגובה עם מספר זיהוי {id}",
+    "Error occurred while posting comment" : "אירעה שגיאה בזמן פרסום תגובה",
     "{count} unread comments" : "{count} תגובות שלא נקראו",
     "Comment" : "תגובה",
     "<strong>Comments</strong> for files <em>(always listed in stream)</em>" : "<strong>תגובות</strong> עבור קבצים <em>(תמיד נרשמים בהזרמת מדיה)</em>",

+ 3 - 0
apps/comments/l10n/he.json

@@ -10,6 +10,9 @@
     "More comments..." : "תגובות נוספות...",
     "Save" : "שמירה",
     "Allowed characters {count} of {max}" : "תווים מותרים {count} מתוך {max}",
+    "Error occurred while retrieving comment with id {id}" : "שגיאה אירעה כאשר אוחזרה תגובה עם מספר זיהוי {id}",
+    "Error occurred while updating comment with id {id}" : "שגיאה אירעה כאשר עודכנה תגובה עם מספר זיהוי {id}",
+    "Error occurred while posting comment" : "אירעה שגיאה בזמן פרסום תגובה",
     "{count} unread comments" : "{count} תגובות שלא נקראו",
     "Comment" : "תגובה",
     "<strong>Comments</strong> for files <em>(always listed in stream)</em>" : "<strong>תגובות</strong> עבור קבצים <em>(תמיד נרשמים בהזרמת מדיה)</em>",

+ 3 - 0
apps/comments/l10n/it.js

@@ -12,6 +12,9 @@ OC.L10N.register(
     "More comments..." : "Altri commenti...",
     "Save" : "Salva",
     "Allowed characters {count} of {max}" : "Caratteri consentiti {count} di {max}",
+    "Error occurred while retrieving comment with id {id}" : "Si è verificato un errore durante il tentativo di recupero del commento con id {id}",
+    "Error occurred while updating comment with id {id}" : "Si è verificato un errore durante il tentativo di aggiornamento del commento con id {id}",
+    "Error occurred while posting comment" : "Si è verificato un errore durante la pubblicazione del commento.",
     "{count} unread comments" : "{count} commenti non letti",
     "Comment" : "Commento",
     "<strong>Comments</strong> for files <em>(always listed in stream)</em>" : "<strong>Commenti</strong> sui file <em>(elencati sempre nel flusso)</em>",

+ 3 - 0
apps/comments/l10n/it.json

@@ -10,6 +10,9 @@
     "More comments..." : "Altri commenti...",
     "Save" : "Salva",
     "Allowed characters {count} of {max}" : "Caratteri consentiti {count} di {max}",
+    "Error occurred while retrieving comment with id {id}" : "Si è verificato un errore durante il tentativo di recupero del commento con id {id}",
+    "Error occurred while updating comment with id {id}" : "Si è verificato un errore durante il tentativo di aggiornamento del commento con id {id}",
+    "Error occurred while posting comment" : "Si è verificato un errore durante la pubblicazione del commento.",
     "{count} unread comments" : "{count} commenti non letti",
     "Comment" : "Commento",
     "<strong>Comments</strong> for files <em>(always listed in stream)</em>" : "<strong>Commenti</strong> sui file <em>(elencati sempre nel flusso)</em>",

+ 2 - 1
apps/comments/l10n/lb.js

@@ -2,6 +2,7 @@ OC.L10N.register(
     "comments",
     {
     "Cancel" : "Ofbriechen",
-    "Save" : "Späicheren"
+    "Save" : "Späicheren",
+    "Comment" : "Kommentar"
 },
 "nplurals=2; plural=(n != 1);");

+ 2 - 1
apps/comments/l10n/lb.json

@@ -1,5 +1,6 @@
 { "translations": {
     "Cancel" : "Ofbriechen",
-    "Save" : "Späicheren"
+    "Save" : "Späicheren",
+    "Comment" : "Kommentar"
 },"pluralForm" :"nplurals=2; plural=(n != 1);"
 }

+ 3 - 0
apps/comments/l10n/sq.js

@@ -12,6 +12,9 @@ OC.L10N.register(
     "More comments..." : "Më tepër komente…",
     "Save" : "Ruaje",
     "Allowed characters {count} of {max}" : "Shenja të lejuara {count} nga {max}",
+    "Error occurred while retrieving comment with id {id}" : "Ndodhi një gabim teksa merrej komenti me id{id}",
+    "Error occurred while updating comment with id {id}" : "Ndodhi një gabim teksa përditësohej komenti me id{id}",
+    "Error occurred while posting comment" : "Ndodhi një gabim teksa postohej komenti",
     "{count} unread comments" : "{count} komente të palexuar",
     "Comment" : "Koment",
     "<strong>Comments</strong> for files <em>(always listed in stream)</em>" : "<strong>Komente</strong> për kartela <em>(përherë të pranishme në rrjedhë)</em>",

+ 3 - 0
apps/comments/l10n/sq.json

@@ -10,6 +10,9 @@
     "More comments..." : "Më tepër komente…",
     "Save" : "Ruaje",
     "Allowed characters {count} of {max}" : "Shenja të lejuara {count} nga {max}",
+    "Error occurred while retrieving comment with id {id}" : "Ndodhi një gabim teksa merrej komenti me id{id}",
+    "Error occurred while updating comment with id {id}" : "Ndodhi një gabim teksa përditësohej komenti me id{id}",
+    "Error occurred while posting comment" : "Ndodhi një gabim teksa postohej komenti",
     "{count} unread comments" : "{count} komente të palexuar",
     "Comment" : "Koment",
     "<strong>Comments</strong> for files <em>(always listed in stream)</em>" : "<strong>Komente</strong> për kartela <em>(përherë të pranishme në rrjedhë)</em>",

+ 1 - 0
apps/dav/appinfo/v1/carddav.php

@@ -75,6 +75,7 @@ if ($debugging) {
 }
 
 $server->addPlugin(new \Sabre\CardDAV\VCFExportPlugin());
+$server->addPlugin(new \OCA\DAV\CardDAV\ImageExportPlugin(\OC::$server->getLogger()));
 $server->addPlugin(new ExceptionLoggerPlugin('carddav', \OC::$server->getLogger()));
 
 // And off we go!

+ 2 - 1
apps/dav/lib/AppInfo/Application.php

@@ -136,7 +136,8 @@ class Application extends App {
 	public function setupContactsProvider(IManager $contactsManager, $userID) {
 		/** @var ContactsManager $cm */
 		$cm = $this->getContainer()->query('ContactsManager');
-		$cm->setupContactsProvider($contactsManager, $userID);
+		$urlGenerator = $this->getContainer()->getServer()->getURLGenerator();
+		$cm->setupContactsProvider($contactsManager, $userID, $urlGenerator);
 	}
 
 	public function registerHooks() {

+ 34 - 10
apps/dav/lib/CardDAV/AddressBookImpl.php

@@ -24,6 +24,7 @@ namespace OCA\DAV\CardDAV;
 
 use OCP\Constants;
 use OCP\IAddressBook;
+use OCP\IURLGenerator;
 use Sabre\VObject\Component\VCard;
 use Sabre\VObject\Property\Text;
 use Sabre\VObject\Reader;
@@ -40,21 +41,27 @@ class AddressBookImpl implements IAddressBook {
 	/** @var AddressBook */
 	private $addressBook;
 
+	/** @var IURLGenerator */
+	private $urlGenerator;
+
 	/**
 	 * AddressBookImpl constructor.
 	 *
 	 * @param AddressBook $addressBook
 	 * @param array $addressBookInfo
 	 * @param CardDavBackend $backend
+	 * @param IUrlGenerator $urlGenerator
 	 */
 	public function __construct(
 			AddressBook $addressBook,
 			array $addressBookInfo,
-			CardDavBackend $backend) {
+			CardDavBackend $backend,
+			IURLGenerator $urlGenerator) {
 
 		$this->addressBook = $addressBook;
 		$this->addressBookInfo = $addressBookInfo;
 		$this->backend = $backend;
+		$this->urlGenerator = $urlGenerator;
 	}
 
 	/**
@@ -83,11 +90,11 @@ class AddressBookImpl implements IAddressBook {
 	 * @since 5.0.0
 	 */
 	public function search($pattern, $searchProperties, $options) {
-		$result = $this->backend->search($this->getKey(), $pattern, $searchProperties);
+		$results = $this->backend->search($this->getKey(), $pattern, $searchProperties);
 
 		$vCards = [];
-		foreach ($result as $cardData) {
-			$vCards[] = $this->vCard2Array($this->readCard($cardData));
+		foreach ($results as $result) {
+			$vCards[] = $this->vCard2Array($result['uri'], $this->readCard($result['carddata']));
 		}
 
 		return $vCards;
@@ -100,13 +107,12 @@ class AddressBookImpl implements IAddressBook {
 	 */
 	public function createOrUpdate($properties) {
 		$update = false;
-		if (!isset($properties['UID'])) { // create a new contact
+		if (!isset($properties['URI'])) { // create a new contact
 			$uid = $this->createUid();
 			$uri = $uid . '.vcf';
 			$vCard = $this->createEmptyVCard($uid);
 		} else { // update existing contact
-			$uid = $properties['UID'];
-			$uri = $uid . '.vcf';
+			$uri = $properties['URI'];
 			$vCardData = $this->backend->getCard($this->getKey(), $uri);
 			$vCard = $this->readCard($vCardData['carddata']);
 			$update = true;
@@ -122,7 +128,7 @@ class AddressBookImpl implements IAddressBook {
 			$this->backend->createCard($this->getKey(), $uri, $vCard->serialize());
 		}
 
-		return $this->vCard2Array($vCard);
+		return $this->vCard2Array($uri, $vCard);
 
 	}
 
@@ -207,13 +213,31 @@ class AddressBookImpl implements IAddressBook {
 	/**
 	 * create array with all vCard properties
 	 *
+	 * @param string $uri
 	 * @param VCard $vCard
 	 * @return array
 	 */
-	protected function vCard2Array(VCard $vCard) {
-		$result = [];
+	protected function vCard2Array($uri, VCard $vCard) {
+		$result = [
+			'URI' => $uri,
+		];
+
 		foreach ($vCard->children as $property) {
 			$result[$property->name] = $property->getValue();
+			if ($property->name === 'PHOTO' && $property->getValueType() === 'BINARY') {
+				$url = $this->urlGenerator->getAbsoluteURL(
+					$this->urlGenerator->linkTo('', 'remote.php') . '/dav/');
+				$url .= implode('/', [
+					'addressbooks',
+					substr($this->addressBookInfo['principaluri'], 11), //cut off 'principals/'
+					$this->addressBookInfo['uri'],
+					$uri
+				]) . '?photo';
+
+				$result['PHOTO'] = 'VALUE=uri:' . $url;
+			} else {
+				$result[$property->name] = $property->getValue();
+			}
 		}
 		if ($this->addressBookInfo['principaluri'] === 'principals/system/system' &&
 			$this->addressBookInfo['uri'] === 'system') {

+ 5 - 3
apps/dav/lib/CardDAV/CardDavBackend.php

@@ -780,7 +780,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
 		}
 		$query2->andWhere($query2->expr()->eq('cp.addressbookid', $query->createNamedParameter($addressBookId)));
 
-		$query->select('c.carddata')->from($this->dbCardsTable, 'c')
+		$query->select('c.carddata', 'c.uri')->from($this->dbCardsTable, 'c')
 			->where($query->expr()->in('c.id', $query->createFunction($query2->getSQL())));
 
 		$result = $query->execute();
@@ -788,8 +788,10 @@ class CardDavBackend implements BackendInterface, SyncSupport {
 
 		$result->closeCursor();
 
-		return array_map(function($array) {return $this->readBlob($array['carddata']);}, $cards);
-
+		return array_map(function($array) {
+			$array['carddata'] = $this->readBlob($array['carddata']);
+			return $array;
+		}, $cards);
 	}
 
 	/**

+ 9 - 5
apps/dav/lib/CardDAV/ContactsManager.php

@@ -22,6 +22,7 @@
 namespace OCA\DAV\CardDAV;
 
 use OCP\Contacts\IManager;
+use OCP\IURLGenerator;
 
 class ContactsManager {
 
@@ -37,26 +38,29 @@ class ContactsManager {
 	/**
 	 * @param IManager $cm
 	 * @param string $userId
+	 * @param IURLGenerator $urlGenerator
 	 */
-	public function setupContactsProvider(IManager $cm, $userId) {
+	public function setupContactsProvider(IManager $cm, $userId, IURLGenerator $urlGenerator) {
 		$addressBooks = $this->backend->getAddressBooksForUser("principals/users/$userId");
-		$this->register($cm, $addressBooks);
+		$this->register($cm, $addressBooks, $urlGenerator);
 		$addressBooks = $this->backend->getAddressBooksForUser("principals/system/system");
-		$this->register($cm, $addressBooks);
+		$this->register($cm, $addressBooks, $urlGenerator);
 	}
 
 	/**
 	 * @param IManager $cm
 	 * @param $addressBooks
+	 * @param IURLGenerator $urlGenerator
 	 */
-	private function register(IManager $cm, $addressBooks) {
+	private function register(IManager $cm, $addressBooks, $urlGenerator) {
 		foreach ($addressBooks as $addressBookInfo) {
 			$addressBook = new \OCA\DAV\CardDAV\AddressBook($this->backend, $addressBookInfo);
 			$cm->registerAddressBook(
 				new AddressBookImpl(
 					$addressBook,
 					$addressBookInfo,
-					$this->backend
+					$this->backend,
+					$urlGenerator
 				)
 			);
 		}

+ 146 - 0
apps/dav/lib/CardDAV/ImageExportPlugin.php

@@ -0,0 +1,146 @@
+<?php
+/**
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\DAV\CardDAV;
+
+use OCP\ILogger;
+use Sabre\CardDAV\Card;
+use Sabre\DAV\Server;
+use Sabre\DAV\ServerPlugin;
+use Sabre\HTTP\RequestInterface;
+use Sabre\HTTP\ResponseInterface;
+use Sabre\VObject\Parameter;
+use Sabre\VObject\Property\Binary;
+use Sabre\VObject\Reader;
+
+class ImageExportPlugin extends ServerPlugin {
+
+	/** @var Server */
+	protected $server;
+	/** @var ILogger */
+	private $logger;
+
+	public function __construct(ILogger $logger) {
+		$this->logger = $logger;
+	}
+
+	/**
+	 * Initializes the plugin and registers event handlers
+	 *
+	 * @param Server $server
+	 * @return void
+	 */
+	function initialize(Server $server) {
+
+		$this->server = $server;
+		$this->server->on('method:GET', [$this, 'httpGet'], 90);
+	}
+
+	/**
+	 * Intercepts GET requests on addressbook urls ending with ?photo.
+	 *
+	 * @param RequestInterface $request
+	 * @param ResponseInterface $response
+	 * @return bool|void
+	 */
+	function httpGet(RequestInterface $request, ResponseInterface $response) {
+
+		$queryParams = $request->getQueryParameters();
+		// TODO: in addition to photo we should also add logo some point in time
+		if (!array_key_exists('photo', $queryParams)) {
+			return true;
+		}
+
+		$path = $request->getPath();
+		$node = $this->server->tree->getNodeForPath($path);
+
+		if (!($node instanceof Card)) {
+			return true;
+		}
+
+		$this->server->transactionType = 'carddav-image-export';
+
+		// Checking ACL, if available.
+		if ($aclPlugin = $this->server->getPlugin('acl')) {
+			/** @var \Sabre\DAVACL\Plugin $aclPlugin */
+			$aclPlugin->checkPrivileges($path, '{DAV:}read');
+		}
+
+		if ($result = $this->getPhoto($node)) {
+			$response->setHeader('Content-Type', $result['Content-Type']);
+			$response->setStatus(200);
+
+			$response->setBody($result['body']);
+
+			// Returning false to break the event chain
+			return false;
+		}
+		return true;
+	}
+
+	function getPhoto(Card $node) {
+		// TODO: this is kind of expensive - load carddav data from database and parse it
+		//       we might want to build up a cache one day
+		try {
+			$vObject = $this->readCard($node->get());
+			if (!$vObject->PHOTO) {
+				return false;
+			}
+
+			$photo = $vObject->PHOTO;
+			$type = $this->getType($photo);
+
+			$valType = $photo->getValueType();
+			$val = ($valType === 'URI' ? $photo->getRawMimeDirValue() : $photo->getValue());
+			return [
+				'Content-Type' => $type,
+				'body' => $val
+			];
+		} catch(\Exception $ex) {
+			$this->logger->logException($ex);
+		}
+		return false;
+	}
+
+	private function readCard($cardData) {
+		return Reader::read($cardData);
+	}
+
+	/**
+	 * @param Binary $photo
+	 * @return Parameter
+	 */
+	private function getType($photo) {
+		$params = $photo->parameters();
+		if (isset($params['TYPE']) || isset($params['MEDIATYPE'])) {
+			/** @var Parameter $typeParam */
+			$typeParam = isset($params['TYPE']) ? $params['TYPE'] : $params['MEDIATYPE'];
+			$type = $typeParam->getValue();
+
+			if (strpos($type, 'image/') === 0) {
+				return $type;
+			} else {
+				return 'image/' . strtolower($type);
+			}
+		}
+		return '';
+	}
+}

+ 1 - 1
apps/dav/lib/Comments/CommentNode.php

@@ -185,7 +185,7 @@ class CommentNode implements \Sabre\DAV\INode, \Sabre\DAV\IProperties {
 				$msg = 'Message exceeds allowed character limit of ';
 				throw new BadRequest($msg . IComment::MAX_MESSAGE_LENGTH, 0, $e);
 			}
-			return false;
+			throw $e;
 		}
 	}
 

+ 2 - 0
apps/dav/lib/Server.php

@@ -25,6 +25,7 @@
 namespace OCA\DAV;
 
 use OCA\DAV\CalDAV\Schedule\IMipPlugin;
+use OCA\DAV\CardDAV\ImageExportPlugin;
 use OCA\DAV\Connector\Sabre\Auth;
 use OCA\DAV\Connector\Sabre\BlockLegacyClientPlugin;
 use OCA\DAV\Connector\Sabre\DavAclPlugin;
@@ -103,6 +104,7 @@ class Server {
 		// addressbook plugins
 		$this->server->addPlugin(new \OCA\DAV\CardDAV\Plugin());
 		$this->server->addPlugin(new VCFExportPlugin());
+		$this->server->addPlugin(new ImageExportPlugin(\OC::$server->getLogger()));
 
 		// system tags plugins
 		$this->server->addPlugin(new \OCA\DAV\SystemTag\SystemTagPlugin(

+ 25 - 12
apps/dav/tests/unit/CardDAV/AddressBookImplTest.php

@@ -43,6 +43,9 @@ class AddressBookImplTest extends TestCase {
 	/** @var  AddressBook | \PHPUnit_Framework_MockObject_MockObject */
 	private $addressBook;
 
+	/** @var \OCP\IURLGenerator | \PHPUnit_Framework_MockObject_MockObject */
+	private $urlGenerator;
+
 	/** @var  CardDavBackend | \PHPUnit_Framework_MockObject_MockObject */
 	private $backend;
 
@@ -61,11 +64,13 @@ class AddressBookImplTest extends TestCase {
 		$this->backend = $this->getMockBuilder('\OCA\DAV\CardDAV\CardDavBackend')
 			->disableOriginalConstructor()->getMock();
 		$this->vCard = $this->getMock('Sabre\VObject\Component\VCard');
+		$this->urlGenerator = $this->getMock('OCP\IURLGenerator');
 
 		$this->addressBookImpl = new AddressBookImpl(
 			$this->addressBook,
 			$this->addressBookInfo,
-			$this->backend
+			$this->backend,
+			$this->urlGenerator
 		);
 	}
 
@@ -87,7 +92,8 @@ class AddressBookImplTest extends TestCase {
 				[
 					$this->addressBook,
 					$this->addressBookInfo,
-					$this->backend
+					$this->backend,
+					$this->urlGenerator,
 				]
 			)
 			->setMethods(['vCard2Array', 'readCard'])
@@ -100,15 +106,18 @@ class AddressBookImplTest extends TestCase {
 			->with($this->addressBookInfo['id'], $pattern, $searchProperties)
 			->willReturn(
 				[
-					'cardData1',
-					'cardData2'
+					['uri' => 'foo.vcf', 'carddata' => 'cardData1'],
+					['uri' => 'bar.vcf', 'carddata' => 'cardData2']
 				]
 			);
 
 		$addressBookImpl->expects($this->exactly(2))->method('readCard')
 			->willReturn($this->vCard);
 		$addressBookImpl->expects($this->exactly(2))->method('vCard2Array')
-			->with($this->vCard)->willReturn('vCard');
+			->withConsecutive(
+				['foo.vcf', $this->vCard],
+				['bar.vcf', $this->vCard]
+			)->willReturn('vCard');
 
 		$result = $addressBookImpl->search($pattern, $searchProperties, []);
 		$this->assertTrue((is_array($result)));
@@ -130,7 +139,8 @@ class AddressBookImplTest extends TestCase {
 				[
 					$this->addressBook,
 					$this->addressBookInfo,
-					$this->backend
+					$this->backend,
+					$this->urlGenerator,
 				]
 			)
 			->setMethods(['vCard2Array', 'createUid', 'createEmptyVCard'])
@@ -146,7 +156,7 @@ class AddressBookImplTest extends TestCase {
 		$this->backend->expects($this->never())->method('updateCard');
 		$this->backend->expects($this->never())->method('getCard');
 		$addressBookImpl->expects($this->once())->method('vCard2Array')
-			->with($this->vCard)->willReturn(true);
+			->with('uid.vcf', $this->vCard)->willReturn(true);
 
 		$this->assertTrue($addressBookImpl->createOrUpdate($properties));
 	}
@@ -161,7 +171,8 @@ class AddressBookImplTest extends TestCase {
 	public function testUpdate() {
 
 		$uid = 'uid';
-		$properties = ['UID' => $uid, 'FN' => 'John Doe'];
+		$uri = 'bla.vcf';
+		$properties = ['URI' => $uri, 'UID' => $uid, 'FN' => 'John Doe'];
 
 		/** @var \PHPUnit_Framework_MockObject_MockObject | AddressBookImpl $addressBookImpl */
 		$addressBookImpl = $this->getMockBuilder('OCA\DAV\CardDAV\AddressBookImpl')
@@ -169,7 +180,8 @@ class AddressBookImplTest extends TestCase {
 				[
 					$this->addressBook,
 					$this->addressBookInfo,
-					$this->backend
+					$this->backend,
+					$this->urlGenerator,
 				]
 			)
 			->setMethods(['vCard2Array', 'createUid', 'createEmptyVCard', 'readCard'])
@@ -178,7 +190,7 @@ class AddressBookImplTest extends TestCase {
 		$addressBookImpl->expects($this->never())->method('createUid');
 		$addressBookImpl->expects($this->never())->method('createEmptyVCard');
 		$this->backend->expects($this->once())->method('getCard')
-			->with($this->addressBookInfo['id'], $uid . '.vcf')
+			->with($this->addressBookInfo['id'], $uri)
 			->willReturn(['carddata' => 'data']);
 		$addressBookImpl->expects($this->once())->method('readCard')
 			->with('data')->willReturn($this->vCard);
@@ -187,7 +199,7 @@ class AddressBookImplTest extends TestCase {
 		$this->backend->expects($this->never())->method('createCard');
 		$this->backend->expects($this->once())->method('updateCard');
 		$addressBookImpl->expects($this->once())->method('vCard2Array')
-			->with($this->vCard)->willReturn(true);
+			->with($uri, $this->vCard)->willReturn(true);
 
 		$this->assertTrue($addressBookImpl->createOrUpdate($properties));
 	}
@@ -251,7 +263,8 @@ class AddressBookImplTest extends TestCase {
 				[
 					$this->addressBook,
 					$this->addressBookInfo,
-					$this->backend
+					$this->backend,
+					$this->urlGenerator,
 				]
 			)
 			->setMethods(['getUid'])

+ 7 - 7
apps/dav/tests/unit/CardDAV/CardDavBackendTest.php

@@ -535,8 +535,8 @@ class CardDavBackendTest extends TestCase {
 		$found = [];
 		foreach ($result as $r) {
 			foreach ($expected as $exp) {
-				if (strpos($r, $exp) > 0) {
-					$found[$exp] = true;
+				if ($r['uri'] === $exp[0] && strpos($r['carddata'], $exp[1]) > 0) {
+					$found[$exp[1]] = true;
 					break;
 				}
 			}
@@ -547,11 +547,11 @@ class CardDavBackendTest extends TestCase {
 
 	public function dataTestSearch() {
 		return [
-				['John', ['FN'], ['John Doe', 'John M. Doe']],
-				['M. Doe', ['FN'], ['John M. Doe']],
-				['Do', ['FN'], ['John Doe', 'John M. Doe']],
-				'check if duplicates are handled correctly' => ['John', ['FN', 'CLOUD'], ['John Doe', 'John M. Doe']],
-				'case insensitive' => ['john', ['FN'], ['John Doe', 'John M. Doe']]
+				['John', ['FN'], [['uri0', 'John Doe'], ['uri1', 'John M. Doe']]],
+				['M. Doe', ['FN'], [['uri1', 'John M. Doe']]],
+				['Do', ['FN'], [['uri0', 'John Doe'], ['uri1', 'John M. Doe']]],
+				'check if duplicates are handled correctly' => ['John', ['FN', 'CLOUD'], [['uri0', 'John Doe'], ['uri1', 'John M. Doe']]],
+				'case insensitive' => ['john', ['FN'], [['uri0', 'John Doe'], ['uri1', 'John M. Doe']]]
 		];
 	}
 

+ 2 - 1
apps/dav/tests/unit/CardDAV/ContactsManagerTest.php

@@ -32,6 +32,7 @@ class ContactsManagerTest extends TestCase {
 		/** @var IManager | \PHPUnit_Framework_MockObject_MockObject $cm */
 		$cm = $this->getMockBuilder('OCP\Contacts\IManager')->disableOriginalConstructor()->getMock();
 		$cm->expects($this->exactly(2))->method('registerAddressBook');
+		$urlGenerator = $this->getMockBuilder('OCP\IUrlGenerator')->disableOriginalConstructor()->getMock();
 		/** @var CardDavBackend | \PHPUnit_Framework_MockObject_MockObject $backEnd */
 		$backEnd = $this->getMockBuilder('OCA\DAV\CardDAV\CardDavBackend')->disableOriginalConstructor()->getMock();
 		$backEnd->method('getAddressBooksForUser')->willReturn([
@@ -39,6 +40,6 @@ class ContactsManagerTest extends TestCase {
 			]);
 
 		$app = new ContactsManager($backEnd);
-		$app->setupContactsProvider($cm, 'user01');
+		$app->setupContactsProvider($cm, 'user01', $urlGenerator);
 	}
 }

+ 151 - 0
apps/dav/tests/unit/CardDAV/ImageExportPluginTest.php

@@ -0,0 +1,151 @@
+<?php
+/**
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+
+namespace OCA\DAV\Tests\unit\CardDAV;
+
+
+use OCA\DAV\CardDAV\ImageExportPlugin;
+use OCP\ILogger;
+use Sabre\CardDAV\Card;
+use Sabre\DAV\Server;
+use Sabre\DAV\Tree;
+use Sabre\HTTP\RequestInterface;
+use Sabre\HTTP\ResponseInterface;
+use Test\TestCase;
+
+class ImageExportPluginTest extends TestCase {
+
+	/** @var ResponseInterface | \PHPUnit_Framework_MockObject_MockObject */
+	private $response;
+	/** @var RequestInterface | \PHPUnit_Framework_MockObject_MockObject */
+	private $request;
+	/** @var ImageExportPlugin | \PHPUnit_Framework_MockObject_MockObject */
+	private $plugin;
+	/** @var Server */
+	private $server;
+	/** @var Tree | \PHPUnit_Framework_MockObject_MockObject */
+	private $tree;
+	/** @var ILogger | \PHPUnit_Framework_MockObject_MockObject */
+	private $logger;
+
+	function setUp() {
+		parent::setUp();
+
+		$this->request = $this->getMockBuilder('Sabre\HTTP\RequestInterface')->getMock();
+		$this->response = $this->getMockBuilder('Sabre\HTTP\ResponseInterface')->getMock();
+		$this->server = $this->getMockBuilder('Sabre\DAV\Server')->getMock();
+		$this->tree = $this->getMockBuilder('Sabre\DAV\Tree')->disableOriginalConstructor()->getMock();
+		$this->server->tree = $this->tree;
+		$this->logger = $this->getMockBuilder('\OCP\ILogger')->getMock();
+
+		$this->plugin = $this->getMock('OCA\DAV\CardDAV\ImageExportPlugin', ['getPhoto'], [$this->logger]);
+		$this->plugin->initialize($this->server);
+	}
+
+	/**
+	 * @dataProvider providesQueryParams
+	 * @param $param
+	 */
+	public function testQueryParams($param) {
+		$this->request->expects($this->once())->method('getQueryParameters')->willReturn($param);
+		$result = $this->plugin->httpGet($this->request, $this->response);
+		$this->assertTrue($result);
+	}
+
+	public function providesQueryParams() {
+		return [
+			[[]],
+			[['1']],
+			[['foo' => 'bar']],
+		];
+	}
+
+	public function testNotACard() {
+		$this->request->expects($this->once())->method('getQueryParameters')->willReturn(['photo' => true]);
+		$this->request->expects($this->once())->method('getPath')->willReturn('/files/welcome.txt');
+		$this->tree->expects($this->once())->method('getNodeForPath')->with('/files/welcome.txt')->willReturn(null);
+		$result = $this->plugin->httpGet($this->request, $this->response);
+		$this->assertTrue($result);
+	}
+
+	/**
+	 * @dataProvider providesCardWithOrWithoutPhoto
+	 * @param bool $expected
+	 * @param array $getPhotoResult
+	 */
+	public function testCardWithOrWithoutPhoto($expected, $getPhotoResult) {
+		$this->request->expects($this->once())->method('getQueryParameters')->willReturn(['photo' => true]);
+		$this->request->expects($this->once())->method('getPath')->willReturn('/files/welcome.txt');
+
+		$card = $this->getMockBuilder('Sabre\CardDAV\Card')->disableOriginalConstructor()->getMock();
+		$this->tree->expects($this->once())->method('getNodeForPath')->with('/files/welcome.txt')->willReturn($card);
+
+		$this->plugin->expects($this->once())->method('getPhoto')->willReturn($getPhotoResult);
+
+		if (!$expected) {
+			$this->response->expects($this->once())->method('setHeader');
+			$this->response->expects($this->once())->method('setStatus');
+			$this->response->expects($this->once())->method('setBody');
+		}
+
+		$result = $this->plugin->httpGet($this->request, $this->response);
+		$this->assertEquals($expected, $result);
+	}
+
+	public function providesCardWithOrWithoutPhoto() {
+		return [
+			[true, null],
+			[false, ['Content-Type' => 'image/jpeg', 'body' => '1234']],
+		];
+	}
+
+	/**
+	 * @dataProvider providesPhotoData
+	 * @param $expected
+	 * @param $cardData
+	 */
+	public function testGetPhoto($expected, $cardData) {
+		/** @var Card | \PHPUnit_Framework_MockObject_MockObject $card */
+		$card = $this->getMockBuilder('Sabre\CardDAV\Card')->disableOriginalConstructor()->getMock();
+		$card->expects($this->once())->method('get')->willReturn($cardData);
+
+		$this->plugin = new ImageExportPlugin($this->logger);
+		$this->plugin->initialize($this->server);
+
+		$result = $this->plugin->getPhoto($card);
+		$this->assertEquals($expected, $result);
+	}
+
+	public function providesPhotoData() {
+		return [
+			'empty vcard' => [false, ''],
+			'vcard without PHOTO' => [false, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 3.5.0//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nEND:VCARD\r\n"],
+			'vcard 3 with PHOTO' => [['Content-Type' => 'image/jpeg', 'body' => '12345'], "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 3.5.0//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nPHOTO;ENCODING=b;TYPE=JPEG:MTIzNDU=\r\nEND:VCARD\r\n"],
+			//
+			// TODO: these three below are not working - needs debugging
+			//
+			//'vcard 3 with PHOTO URL' => [['Content-Type' => 'image/jpeg', 'body' => '12345'], "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 3.5.0//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nPHOTO;TYPE=JPEG:http://example.org/photo.jpg\r\nEND:VCARD\r\n"],
+			//'vcard 4 with PHOTO' => [['Content-Type' => 'image/jpeg', 'body' => '12345'], "BEGIN:VCARD\r\nVERSION:4.0\r\nPRODID:-//Sabre//Sabre VObject 3.5.0//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nPHOTO:data:image/jpeg;MTIzNDU=\r\nEND:VCARD\r\n"],
+			'vcard 4 with PHOTO URL' => [['Content-Type' => 'image/jpeg', 'body' => 'http://example.org/photo.jpg'], "BEGIN:VCARD\r\nVERSION:4.0\r\nPRODID:-//Sabre//Sabre VObject 3.5.0//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nPHOTO;MEDIATYPE=image/jpeg:http://example.org/photo.jpg\r\nEND:VCARD\r\n"],
+		];
+	}
+}

+ 5 - 1
apps/dav/tests/unit/Comments/CommentsNodeTest.php

@@ -166,6 +166,10 @@ class CommentsNodeTest extends \Test\TestCase {
 		$this->assertTrue($this->node->updateComment($msg));
 	}
 
+	/**
+	 * @expectedException Exception
+	 * @expectedExceptionMessage buh!
+	 */
 	public function testUpdateCommentLogException() {
 		$msg = null;
 
@@ -198,7 +202,7 @@ class CommentsNodeTest extends \Test\TestCase {
 		$this->logger->expects($this->once())
 			->method('logException');
 
-		$this->assertFalse($this->node->updateComment($msg));
+		$this->node->updateComment($msg);
 	}
 
 	/**

+ 5 - 1
apps/encryption/l10n/sv.js

@@ -20,7 +20,7 @@ OC.L10N.register(
     "Could not update the private key password." : "Kunde inte uppdatera lösenord för den privata nyckeln",
     "The old password was not correct, please try again." : "Det gamla lösenordet var inte korrekt. Vänligen försök igen.",
     "The current log-in password was not correct, please try again." : "Det nuvarande inloggningslösenordet var inte korrekt. Vänligen försök igen.",
-    "Private key password successfully updated." : "Den privata nyckelns lösenord uppdaterades utan problem.",
+    "Private key password successfully updated." : "Den privata nyckelns lösenord uppdaterades.",
     "You need to migrate your encryption keys from the old encryption (ownCloud <= 8.0) to the new one. Please run 'occ encryption:migrate' or contact your administrator" : "Du behöver migrera dina krypteringsnycklar från den gamla krypteringen (ownCloud <= 8.0) till den nya. Kör 'occ encryption:migrate' eller kontakta din administratör",
     "Invalid private key for Encryption App. Please update your private key password in your personal settings to recover access to your encrypted files." : "Ogiltig privat nyckel i krypteringsprogrammet. Vänligen uppdatera lösenordet till din privata nyckel under dina personliga inställningar för att återfå tillgång till dina krypterade filer.",
     "Encryption App is enabled but your keys are not initialized, please log-out and log-in again" : "Krypteringsprogrammet är aktiverat men dina nycklar är inte initierade. Vänligen logga ut och  in igen",
@@ -30,8 +30,11 @@ OC.L10N.register(
     "one-time password for server-side-encryption" : "engångslösenord för kryptering på serversidan",
     "Can not decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "Kan ej dekryptera denna fil, förmodligen är det en delad fil. Be ägaren av filen att dela den med dig.",
     "Can not read this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "Filen kan inte läsas, troligtvis är det en delad fil. Be ägaren av filen att dela den med dig igen.",
+    "Hey there,\n\nthe admin enabled server-side-encryption. Your files were encrypted using the password '%s'.\n\nPlease login to the web interface, go to the section 'ownCloud basic encryption module' of your personal settings and update your encryption password by entering this password into the 'old log-in password' field and your current login-password.\n\n" : "Hej,\n\nadministratören har aktiverat kryptering på servern. Dina filer har krypterats med lösenordet '%s'.\n\nVänligen logga in i webbgränssnittet, gå till \"ownCloud baskrypteringsmodul\" i dina personliga inställningar och uppdatera ditt krypteringslösenord genom att mata in det här lösenordet i fältet \"gamla inloggningslösenordet\" och ditt nuvarande inloggningslösenord.\n\n",
     "The share will expire on %s." : "Utdelningen kommer att upphöra %s.",
     "Cheers!" : "Ha de fint!",
+    "Hey there,<br><br>the admin enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>.<br><br>Please login to the web interface, go to the section \"ownCloud basic encryption module\" of your personal settings and update your encryption password by entering this password into the \"old log-in password\" field and your current login-password.<br><br>" : "Hej,<br><br>administratören har aktiverat kryptering på servern. Dina filer har krypterats med lösenordet <strong>%s</strong>.<br><br>Vänligen logga in i webbgränssnittet, gå till \"ownCloud baskrypteringsmodul\" i dina personliga inställningar och uppdatera ditt krypteringslösenord genom att mata in det här lösenordet i fältet \"gamla inloggningslösenordet\" och ditt nuvarande inloggningslösenord.<br><br>",
+    "Encrypt the home storage" : "Kryptera hemmalagringen",
     "Enabling this option encrypts all files stored on the main storage, otherwise only files on external storage will be encrypted" : "Aktivering av det här alternativet krypterar alla filer som är lagrade på huvudlagringsplatsen, annars kommer bara filer på extern lagringsplats att krypteras",
     "Enable recovery key" : "Aktivera återställningsnyckel",
     "Disable recovery key" : "Inaktivera återställningsnyckel",
@@ -43,6 +46,7 @@ OC.L10N.register(
     "New recovery key password" : "Nytt lösenord för återställningsnyckeln",
     "Repeat new recovery key password" : "Upprepa nytt lösenord för återställningsnyckeln",
     "Change Password" : "Byt lösenord",
+    "ownCloud basic encryption module" : "ownCloud baskrypteringsmodul",
     "Your private key password no longer matches your log-in password." : "Ditt lösenord för din privata nyckel matchar inte längre ditt inloggningslösenord.",
     "Set your old private key password to your current log-in password:" : "Sätt ditt gamla privatnyckellösenord till ditt aktuella inloggningslösenord:",
     " If you don't remember your old password you can ask your administrator to recover your files." : "Om du inte kommer ihåg ditt gamla lösenord kan du be din administratör att återställa dina filer.",

+ 5 - 1
apps/encryption/l10n/sv.json

@@ -18,7 +18,7 @@
     "Could not update the private key password." : "Kunde inte uppdatera lösenord för den privata nyckeln",
     "The old password was not correct, please try again." : "Det gamla lösenordet var inte korrekt. Vänligen försök igen.",
     "The current log-in password was not correct, please try again." : "Det nuvarande inloggningslösenordet var inte korrekt. Vänligen försök igen.",
-    "Private key password successfully updated." : "Den privata nyckelns lösenord uppdaterades utan problem.",
+    "Private key password successfully updated." : "Den privata nyckelns lösenord uppdaterades.",
     "You need to migrate your encryption keys from the old encryption (ownCloud <= 8.0) to the new one. Please run 'occ encryption:migrate' or contact your administrator" : "Du behöver migrera dina krypteringsnycklar från den gamla krypteringen (ownCloud <= 8.0) till den nya. Kör 'occ encryption:migrate' eller kontakta din administratör",
     "Invalid private key for Encryption App. Please update your private key password in your personal settings to recover access to your encrypted files." : "Ogiltig privat nyckel i krypteringsprogrammet. Vänligen uppdatera lösenordet till din privata nyckel under dina personliga inställningar för att återfå tillgång till dina krypterade filer.",
     "Encryption App is enabled but your keys are not initialized, please log-out and log-in again" : "Krypteringsprogrammet är aktiverat men dina nycklar är inte initierade. Vänligen logga ut och  in igen",
@@ -28,8 +28,11 @@
     "one-time password for server-side-encryption" : "engångslösenord för kryptering på serversidan",
     "Can not decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "Kan ej dekryptera denna fil, förmodligen är det en delad fil. Be ägaren av filen att dela den med dig.",
     "Can not read this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "Filen kan inte läsas, troligtvis är det en delad fil. Be ägaren av filen att dela den med dig igen.",
+    "Hey there,\n\nthe admin enabled server-side-encryption. Your files were encrypted using the password '%s'.\n\nPlease login to the web interface, go to the section 'ownCloud basic encryption module' of your personal settings and update your encryption password by entering this password into the 'old log-in password' field and your current login-password.\n\n" : "Hej,\n\nadministratören har aktiverat kryptering på servern. Dina filer har krypterats med lösenordet '%s'.\n\nVänligen logga in i webbgränssnittet, gå till \"ownCloud baskrypteringsmodul\" i dina personliga inställningar och uppdatera ditt krypteringslösenord genom att mata in det här lösenordet i fältet \"gamla inloggningslösenordet\" och ditt nuvarande inloggningslösenord.\n\n",
     "The share will expire on %s." : "Utdelningen kommer att upphöra %s.",
     "Cheers!" : "Ha de fint!",
+    "Hey there,<br><br>the admin enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>.<br><br>Please login to the web interface, go to the section \"ownCloud basic encryption module\" of your personal settings and update your encryption password by entering this password into the \"old log-in password\" field and your current login-password.<br><br>" : "Hej,<br><br>administratören har aktiverat kryptering på servern. Dina filer har krypterats med lösenordet <strong>%s</strong>.<br><br>Vänligen logga in i webbgränssnittet, gå till \"ownCloud baskrypteringsmodul\" i dina personliga inställningar och uppdatera ditt krypteringslösenord genom att mata in det här lösenordet i fältet \"gamla inloggningslösenordet\" och ditt nuvarande inloggningslösenord.<br><br>",
+    "Encrypt the home storage" : "Kryptera hemmalagringen",
     "Enabling this option encrypts all files stored on the main storage, otherwise only files on external storage will be encrypted" : "Aktivering av det här alternativet krypterar alla filer som är lagrade på huvudlagringsplatsen, annars kommer bara filer på extern lagringsplats att krypteras",
     "Enable recovery key" : "Aktivera återställningsnyckel",
     "Disable recovery key" : "Inaktivera återställningsnyckel",
@@ -41,6 +44,7 @@
     "New recovery key password" : "Nytt lösenord för återställningsnyckeln",
     "Repeat new recovery key password" : "Upprepa nytt lösenord för återställningsnyckeln",
     "Change Password" : "Byt lösenord",
+    "ownCloud basic encryption module" : "ownCloud baskrypteringsmodul",
     "Your private key password no longer matches your log-in password." : "Ditt lösenord för din privata nyckel matchar inte längre ditt inloggningslösenord.",
     "Set your old private key password to your current log-in password:" : "Sätt ditt gamla privatnyckellösenord till ditt aktuella inloggningslösenord:",
     " If you don't remember your old password you can ask your administrator to recover your files." : "Om du inte kommer ihåg ditt gamla lösenord kan du be din administratör att återställa dina filer.",

+ 4 - 1
apps/federatedfilesharing/l10n/ast.js

@@ -1,6 +1,9 @@
 OC.L10N.register(
     "federatedfilesharing",
     {
-    "Sharing %s failed, because this item is already shared with %s" : "Compartir %s falló, porque esti elementu yá ta compartiéndose con %s"
+    "Invalid Federated Cloud ID" : "Inválidu ID de Ñube Federada",
+    "Sharing %s failed, because this item is already shared with %s" : "Compartir %s falló, porque esti elementu yá ta compartiéndose con %s",
+    "Not allowed to create a federated share with the same user" : "Nun s'almite crear un recursu compartíu federáu col mesmu usuariu",
+    "Sharing %s failed, could not find %s, maybe the server is currently unreachable." : "Compartir %s falló, nun pudo atopase %s, pue qu'el servidor nun seya anguaño algamable."
 },
 "nplurals=2; plural=(n != 1);");

+ 4 - 1
apps/federatedfilesharing/l10n/ast.json

@@ -1,4 +1,7 @@
 { "translations": {
-    "Sharing %s failed, because this item is already shared with %s" : "Compartir %s falló, porque esti elementu yá ta compartiéndose con %s"
+    "Invalid Federated Cloud ID" : "Inválidu ID de Ñube Federada",
+    "Sharing %s failed, because this item is already shared with %s" : "Compartir %s falló, porque esti elementu yá ta compartiéndose con %s",
+    "Not allowed to create a federated share with the same user" : "Nun s'almite crear un recursu compartíu federáu col mesmu usuariu",
+    "Sharing %s failed, could not find %s, maybe the server is currently unreachable." : "Compartir %s falló, nun pudo atopase %s, pue qu'el servidor nun seya anguaño algamable."
 },"pluralForm" :"nplurals=2; plural=(n != 1);"
 }

+ 4 - 1
apps/federatedfilesharing/lib/DiscoveryManager.php

@@ -84,7 +84,10 @@ class DiscoveryManager {
 
 		// Read the data from the response body
 		try {
-			$response = $this->client->get($remote . '/ocs-provider/');
+			$response = $this->client->get($remote . '/ocs-provider/', [
+				'timeout' => 10,
+				'connect_timeout' => 10,
+			]);
 			if($response->getStatusCode() === 200) {
 				$decodedService = json_decode($response->getBody(), true);
 				if(is_array($decodedService)) {

+ 3 - 1
apps/federatedfilesharing/lib/Notifications.php

@@ -287,7 +287,9 @@ class Notifications {
 			$endpoint = $this->discoveryManager->getShareEndpoint($protocol . $remoteDomain);
 			try {
 				$response = $client->post($protocol . $remoteDomain . $endpoint . $urlSuffix . '?format=' . self::RESPONSE_FORMAT, [
-					'body' => $fields
+					'body' => $fields,
+					'timeout' => 10,
+					'connect_timeout' => 10,
 				]);
 				$result['result'] = $response->getBody();
 				$result['success'] = true;

+ 20 - 5
apps/federatedfilesharing/tests/DiscoveryManagerTest.php

@@ -77,7 +77,10 @@ class DiscoveryManagerTest extends \Test\TestCase {
 		$this->client
 			->expects($this->once())
 			->method('get')
-			->with('https://myhost.com/ocs-provider/', [])
+			->with('https://myhost.com/ocs-provider/', [
+				'timeout' => 10,
+				'connect_timeout' => 10,
+			])
 			->willReturn($response);
 		$this->cache
 			->expects($this->at(0))
@@ -111,7 +114,10 @@ class DiscoveryManagerTest extends \Test\TestCase {
 		$this->client
 			->expects($this->once())
 			->method('get')
-			->with('https://myhost.com/ocs-provider/', [])
+			->with('https://myhost.com/ocs-provider/', [
+				'timeout' => 10,
+				'connect_timeout' => 10,
+			])
 			->willReturn($response);
 
 		$expectedResult = '/public.php/MyCustomEndpoint/';
@@ -131,7 +137,10 @@ class DiscoveryManagerTest extends \Test\TestCase {
 		$this->client
 			->expects($this->once())
 			->method('get')
-			->with('https://myhost.com/ocs-provider/', [])
+			->with('https://myhost.com/ocs-provider/', [
+				'timeout' => 10,
+				'connect_timeout' => 10,
+			])
 			->willReturn($response);
 
 		$expectedResult = '/public.php/webdav';
@@ -151,7 +160,10 @@ class DiscoveryManagerTest extends \Test\TestCase {
 		$this->client
 			->expects($this->once())
 			->method('get')
-			->with('https://myhost.com/ocs-provider/', [])
+			->with('https://myhost.com/ocs-provider/', [
+				'timeout' => 10,
+				'connect_timeout' => 10,
+			])
 			->willReturn($response);
 
 		$expectedResult = '/ocs/v2.php/cloud/MyCustomShareEndpoint';
@@ -171,7 +183,10 @@ class DiscoveryManagerTest extends \Test\TestCase {
 		$this->client
 			->expects($this->once())
 			->method('get')
-			->with('https://myhost.com/ocs-provider/', [])
+			->with('https://myhost.com/ocs-provider/', [
+				'timeout' => 10,
+				'connect_timeout' => 10,
+			])
 			->willReturn($response);
 		$this->cache
 			->expects($this->at(0))

+ 9 - 2
apps/files/js/search.js

@@ -133,7 +133,7 @@
 
 			this.handleFolderClick = function($row, result, event) {
 				// open folder
-				if (self.fileAppLoaded()) {
+				if (self.fileAppLoaded() && self.fileList.id === 'files') {
 					self.fileList.changeDirectory(result.path);
 					return false;
 				} else {
@@ -142,7 +142,7 @@
 			};
 
 			this.handleFileClick = function($row, result, event) {
-				if (self.fileAppLoaded()) {
+				if (self.fileAppLoaded() && self.fileList.id === 'files') {
 					self.fileList.changeDirectory(OC.dirname(result.path));
 					self.fileList.scrollTo(result.name);
 					return false;
@@ -184,6 +184,13 @@
 
 			search.setHandler('folder',  this.handleFolderClick.bind(this));
 			search.setHandler(['file', 'audio', 'image'], this.handleFileClick.bind(this));
+
+			if (self.fileAppLoaded()) {
+				// hide results when switching directory outside of search results
+				$('#app-content').delegate('>div', 'changeDirectory', function() {
+					search.clear();
+				});
+			}
 		}
 	};
 	OCA.Search.Files = Files;

+ 28 - 0
apps/files/l10n/ast.js

@@ -21,6 +21,7 @@ OC.L10N.register(
     "Invalid directory." : "Direutoriu non válidu.",
     "Files" : "Ficheros",
     "All files" : "Tolos ficheros",
+    "File could not be found" : "Nun s'atopó el ficheru",
     "Home" : "Casa",
     "Close" : "Zarrar",
     "Favorites" : "Favoritos",
@@ -28,8 +29,19 @@ OC.L10N.register(
     "Unable to upload {filename} as it is a directory or has 0 bytes" : "Nun pudo xubise {filename}, paez que ye un directoriu o tien 0 bytes",
     "Total file size {size1} exceeds upload limit {size2}" : "El tamañu de ficheru total {size1} perpasa la llende de xuba {size2}",
     "Not enough free space, you are uploading {size1} but only {size2} is left" : "Nun hai abondu espaciu llibre, tas xubiendo {size1} pero namái falta {size2}",
+    "Error uploading file \"{fileName}\": {message}" : "Fallu xubiendo'l ficheru \"{fileName}\": {message}",
     "Could not get result from server." : "Nun pudo obtenese'l resultáu del sirvidor.",
     "Uploading..." : "Xubiendo...",
+    "..." : "...",
+    "{hours}:{minutes}:{seconds} hour{plural_s} left" : "Falten {hours}:{minutes}:{seconds} hour{plural_s}",
+    "{hours}:{minutes}h" : "{hours}:{minutes}h",
+    "{minutes}:{seconds} minute{plural_s} left" : "Falten {minutes}:{seconds} minute{plural_s} ",
+    "{minutes}:{seconds}m" : "{minutes}:{seconds}m",
+    "{seconds} second{plural_s} left" : "Falten {seconds} second{plural_s}",
+    "{seconds}s" : "{seconds}s",
+    "Any moment now..." : "En cualquier momentu...",
+    "Soon..." : "Pronto...",
+    "{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} de {totalSize} ({bitrate})",
     "File upload is in progress. Leaving the page now will cancel the upload." : "La xuba del ficheru ta en progresu. Si dexes agora la páxina, va encaboxase la xuba.",
     "Actions" : "Aiciones",
     "Download" : "Descargar",
@@ -43,6 +55,17 @@ OC.L10N.register(
     "Unable to determine date" : "Imposible determinar la fecha",
     "This operation is forbidden" : "La operación ta prohibida",
     "This directory is unavailable, please check the logs or contact the administrator" : "Esti direutoriu nun ta disponible, por favor verifica'l rexistru o contacta l'alministrador",
+    "Could not move \"{file}\", target exists" : "Nun pudo movese \"{file}\", destín yá esiste",
+    "Could not move \"{file}\"" : "Nun pudo movese \"{file}\"",
+    "{newName} already exists" : "{newName} yá esiste",
+    "Could not rename \"{fileName}\", it does not exist any more" : "Nun pudo renomase \"{fileName}\", yá nun esiste",
+    "The name \"{targetName}\" is already used in the folder \"{dir}\". Please choose a different name." : "El nome \"{targetName}\" yá ta n'usu na carpeta \"{dir}\". Por favor, escueyi un nome diferente.",
+    "Could not rename \"{fileName}\"" : "Nun pudo renomase \"{fileName}\"",
+    "Could not create file \"{file}\"" : "Nun pudo crease'l ficheru \"{file}\"",
+    "Could not create file \"{file}\" because it already exists" : "Nun pudo crease'l ficheru \"{file}\" porque yá esiste",
+    "Could not create folder \"{dir}\"" : "Nun pudo crease la carpeta \"{dir}\"",
+    "Could not create folder \"{dir}\" because it already exists" : "Nun pudo crease la carpeta \"{dir}\" porque yá esiste",
+    "Error deleting file \"{fileName}\"." : "Fallu borrando'l ficheru \"{fileName}\".",
     "No entries in this folder match '{filter}'" : "Nun concasa nenguna entrada nesta carpeta '{filter}'",
     "Name" : "Nome",
     "Size" : "Tamañu",
@@ -64,6 +87,7 @@ OC.L10N.register(
     "_%n byte_::_%n bytes_" : ["%n bytes","%n bytes"],
     "Favorited" : "Favoritos",
     "Favorite" : "Favoritu",
+    "Local link" : "Enllaz llocal",
     "Folder" : "Carpeta",
     "New folder" : "Nueva carpeta",
     "{newname} already exists" : "{newname} yá existe",
@@ -91,8 +115,12 @@ OC.L10N.register(
     "Maximum upload size" : "Tamañu máximu de xubida",
     "max. possible: " : "máx. posible:",
     "Save" : "Guardar",
+    "With PHP-FPM it might take 5 minutes for changes to be applied." : "Con PHP-FPM pue retrasase 5 minutos pa que los cambeos s'apliquen.",
+    "Missing permissions to edit from here." : "Falten permisos pa editar dende equí.",
     "Settings" : "Axustes",
+    "Show hidden files" : "Amosar ficheros ocultos",
     "WebDAV" : "WebDAV",
+    "Use this address to <a href=\"%s\" target=\"_blank\" rel=\"noreferrer\">access your Files via WebDAV</a>" : "Usa esta direición <a href=\"%s\" target=\"_blank\" rel=\"noreferrer\">p'acceder a los dos Ficheros via WebDAV</a>",
     "No files in here" : "Nun hai nengún ficheru equí",
     "Upload some content or sync with your devices!" : "¡Xuba algún conteníu o sincroniza colos sos preseos!",
     "No entries found in this folder" : "Nenguna entrada en esta carpeta",

+ 28 - 0
apps/files/l10n/ast.json

@@ -19,6 +19,7 @@
     "Invalid directory." : "Direutoriu non válidu.",
     "Files" : "Ficheros",
     "All files" : "Tolos ficheros",
+    "File could not be found" : "Nun s'atopó el ficheru",
     "Home" : "Casa",
     "Close" : "Zarrar",
     "Favorites" : "Favoritos",
@@ -26,8 +27,19 @@
     "Unable to upload {filename} as it is a directory or has 0 bytes" : "Nun pudo xubise {filename}, paez que ye un directoriu o tien 0 bytes",
     "Total file size {size1} exceeds upload limit {size2}" : "El tamañu de ficheru total {size1} perpasa la llende de xuba {size2}",
     "Not enough free space, you are uploading {size1} but only {size2} is left" : "Nun hai abondu espaciu llibre, tas xubiendo {size1} pero namái falta {size2}",
+    "Error uploading file \"{fileName}\": {message}" : "Fallu xubiendo'l ficheru \"{fileName}\": {message}",
     "Could not get result from server." : "Nun pudo obtenese'l resultáu del sirvidor.",
     "Uploading..." : "Xubiendo...",
+    "..." : "...",
+    "{hours}:{minutes}:{seconds} hour{plural_s} left" : "Falten {hours}:{minutes}:{seconds} hour{plural_s}",
+    "{hours}:{minutes}h" : "{hours}:{minutes}h",
+    "{minutes}:{seconds} minute{plural_s} left" : "Falten {minutes}:{seconds} minute{plural_s} ",
+    "{minutes}:{seconds}m" : "{minutes}:{seconds}m",
+    "{seconds} second{plural_s} left" : "Falten {seconds} second{plural_s}",
+    "{seconds}s" : "{seconds}s",
+    "Any moment now..." : "En cualquier momentu...",
+    "Soon..." : "Pronto...",
+    "{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} de {totalSize} ({bitrate})",
     "File upload is in progress. Leaving the page now will cancel the upload." : "La xuba del ficheru ta en progresu. Si dexes agora la páxina, va encaboxase la xuba.",
     "Actions" : "Aiciones",
     "Download" : "Descargar",
@@ -41,6 +53,17 @@
     "Unable to determine date" : "Imposible determinar la fecha",
     "This operation is forbidden" : "La operación ta prohibida",
     "This directory is unavailable, please check the logs or contact the administrator" : "Esti direutoriu nun ta disponible, por favor verifica'l rexistru o contacta l'alministrador",
+    "Could not move \"{file}\", target exists" : "Nun pudo movese \"{file}\", destín yá esiste",
+    "Could not move \"{file}\"" : "Nun pudo movese \"{file}\"",
+    "{newName} already exists" : "{newName} yá esiste",
+    "Could not rename \"{fileName}\", it does not exist any more" : "Nun pudo renomase \"{fileName}\", yá nun esiste",
+    "The name \"{targetName}\" is already used in the folder \"{dir}\". Please choose a different name." : "El nome \"{targetName}\" yá ta n'usu na carpeta \"{dir}\". Por favor, escueyi un nome diferente.",
+    "Could not rename \"{fileName}\"" : "Nun pudo renomase \"{fileName}\"",
+    "Could not create file \"{file}\"" : "Nun pudo crease'l ficheru \"{file}\"",
+    "Could not create file \"{file}\" because it already exists" : "Nun pudo crease'l ficheru \"{file}\" porque yá esiste",
+    "Could not create folder \"{dir}\"" : "Nun pudo crease la carpeta \"{dir}\"",
+    "Could not create folder \"{dir}\" because it already exists" : "Nun pudo crease la carpeta \"{dir}\" porque yá esiste",
+    "Error deleting file \"{fileName}\"." : "Fallu borrando'l ficheru \"{fileName}\".",
     "No entries in this folder match '{filter}'" : "Nun concasa nenguna entrada nesta carpeta '{filter}'",
     "Name" : "Nome",
     "Size" : "Tamañu",
@@ -62,6 +85,7 @@
     "_%n byte_::_%n bytes_" : ["%n bytes","%n bytes"],
     "Favorited" : "Favoritos",
     "Favorite" : "Favoritu",
+    "Local link" : "Enllaz llocal",
     "Folder" : "Carpeta",
     "New folder" : "Nueva carpeta",
     "{newname} already exists" : "{newname} yá existe",
@@ -89,8 +113,12 @@
     "Maximum upload size" : "Tamañu máximu de xubida",
     "max. possible: " : "máx. posible:",
     "Save" : "Guardar",
+    "With PHP-FPM it might take 5 minutes for changes to be applied." : "Con PHP-FPM pue retrasase 5 minutos pa que los cambeos s'apliquen.",
+    "Missing permissions to edit from here." : "Falten permisos pa editar dende equí.",
     "Settings" : "Axustes",
+    "Show hidden files" : "Amosar ficheros ocultos",
     "WebDAV" : "WebDAV",
+    "Use this address to <a href=\"%s\" target=\"_blank\" rel=\"noreferrer\">access your Files via WebDAV</a>" : "Usa esta direición <a href=\"%s\" target=\"_blank\" rel=\"noreferrer\">p'acceder a los dos Ficheros via WebDAV</a>",
     "No files in here" : "Nun hai nengún ficheru equí",
     "Upload some content or sync with your devices!" : "¡Xuba algún conteníu o sincroniza colos sos preseos!",
     "No entries found in this folder" : "Nenguna entrada en esta carpeta",

+ 2 - 1
apps/files/l10n/bg_BG.js

@@ -90,6 +90,7 @@ OC.L10N.register(
     "The files you are trying to upload exceed the maximum size for file uploads on this server." : "Файловете, които се опитваш да качиш са по-големи от позволеното на този сървър.",
     "No favorites" : "Няма любими",
     "Files and folders you mark as favorite will show up here" : "Файловете и папките които отбелязваш като любими ще се показват тук",
-    "Text file" : "Текстов файл"
+    "Text file" : "Текстов файл",
+    "New text file.txt" : "Нов текст file.txt"
 },
 "nplurals=2; plural=(n != 1);");

+ 2 - 1
apps/files/l10n/bg_BG.json

@@ -88,6 +88,7 @@
     "The files you are trying to upload exceed the maximum size for file uploads on this server." : "Файловете, които се опитваш да качиш са по-големи от позволеното на този сървър.",
     "No favorites" : "Няма любими",
     "Files and folders you mark as favorite will show up here" : "Файловете и папките които отбелязваш като любими ще се показват тук",
-    "Text file" : "Текстов файл"
+    "Text file" : "Текстов файл",
+    "New text file.txt" : "Нов текст file.txt"
 },"pluralForm" :"nplurals=2; plural=(n != 1);"
 }

+ 2 - 0
apps/files/l10n/lb.js

@@ -9,11 +9,13 @@ OC.L10N.register(
     "Missing a temporary folder" : "Et feelt en temporären Dossier",
     "Failed to write to disk" : "Konnt net op den Disk schreiwen",
     "Files" : "Dateien",
+    "All files" : "All d'Fichieren",
     "Home" : "Doheem",
     "Close" : "Zoumaachen",
     "Favorites" : "Favoriten",
     "Upload cancelled." : "Upload ofgebrach.",
     "Uploading..." : "Lueden erop...",
+    "..." : "...",
     "File upload is in progress. Leaving the page now will cancel the upload." : "File Upload am gaang. Wann's de des Säit verléiss gëtt den Upload ofgebrach.",
     "Download" : "Download",
     "Rename" : "Ëmbenennen",

+ 2 - 0
apps/files/l10n/lb.json

@@ -7,11 +7,13 @@
     "Missing a temporary folder" : "Et feelt en temporären Dossier",
     "Failed to write to disk" : "Konnt net op den Disk schreiwen",
     "Files" : "Dateien",
+    "All files" : "All d'Fichieren",
     "Home" : "Doheem",
     "Close" : "Zoumaachen",
     "Favorites" : "Favoriten",
     "Upload cancelled." : "Upload ofgebrach.",
     "Uploading..." : "Lueden erop...",
+    "..." : "...",
     "File upload is in progress. Leaving the page now will cancel the upload." : "File Upload am gaang. Wann's de des Säit verléiss gëtt den Upload ofgebrach.",
     "Download" : "Download",
     "Rename" : "Ëmbenennen",

+ 11 - 0
apps/files/l10n/sv.js

@@ -21,6 +21,7 @@ OC.L10N.register(
     "Invalid directory." : "Felaktig mapp.",
     "Files" : "Filer",
     "All files" : "Alla filer",
+    "File could not be found" : "Fil kunde inte hittas",
     "Home" : "Hem",
     "Close" : "Stäng",
     "Favorites" : "Favoriter",
@@ -32,6 +33,15 @@ OC.L10N.register(
     "Could not get result from server." : "Gick inte att hämta resultat från server.",
     "Uploading..." : "Laddar upp...",
     "..." : "...",
+    "{hours}:{minutes}:{seconds} hour{plural_s} left" : "{hours}:{minutes}:{seconds} timme/ar kvar",
+    "{hours}:{minutes}h" : "{hours}:{minutes}",
+    "{minutes}:{seconds} minute{plural_s} left" : "{minutes}:{seconds} minut(er) kvar",
+    "{minutes}:{seconds}m" : "{minutes}:{seconds}",
+    "{seconds} second{plural_s} left" : "{seconds} sekund(er) kvar",
+    "{seconds}s" : "{seconds}s",
+    "Any moment now..." : "Alldeles strax...",
+    "Soon..." : "Snart...",
+    "{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} av {totalSize} ({bitrate})",
     "File upload is in progress. Leaving the page now will cancel the upload." : "Filuppladdning pågår. Lämnar du sidan så avbryts uppladdningen.",
     "Actions" : "Åtgärder",
     "Download" : "Ladda ner",
@@ -77,6 +87,7 @@ OC.L10N.register(
     "_%n byte_::_%n bytes_" : ["%n bytes","%n bytes"],
     "Favorited" : "Favoriserad",
     "Favorite" : "Favorit",
+    "Local link" : "Lokal länk",
     "Folder" : "Mapp",
     "New folder" : "Ny mapp",
     "{newname} already exists" : "{newname}  existerar redan",

+ 11 - 0
apps/files/l10n/sv.json

@@ -19,6 +19,7 @@
     "Invalid directory." : "Felaktig mapp.",
     "Files" : "Filer",
     "All files" : "Alla filer",
+    "File could not be found" : "Fil kunde inte hittas",
     "Home" : "Hem",
     "Close" : "Stäng",
     "Favorites" : "Favoriter",
@@ -30,6 +31,15 @@
     "Could not get result from server." : "Gick inte att hämta resultat från server.",
     "Uploading..." : "Laddar upp...",
     "..." : "...",
+    "{hours}:{minutes}:{seconds} hour{plural_s} left" : "{hours}:{minutes}:{seconds} timme/ar kvar",
+    "{hours}:{minutes}h" : "{hours}:{minutes}",
+    "{minutes}:{seconds} minute{plural_s} left" : "{minutes}:{seconds} minut(er) kvar",
+    "{minutes}:{seconds}m" : "{minutes}:{seconds}",
+    "{seconds} second{plural_s} left" : "{seconds} sekund(er) kvar",
+    "{seconds}s" : "{seconds}s",
+    "Any moment now..." : "Alldeles strax...",
+    "Soon..." : "Snart...",
+    "{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} av {totalSize} ({bitrate})",
     "File upload is in progress. Leaving the page now will cancel the upload." : "Filuppladdning pågår. Lämnar du sidan så avbryts uppladdningen.",
     "Actions" : "Åtgärder",
     "Download" : "Ladda ner",
@@ -75,6 +85,7 @@
     "_%n byte_::_%n bytes_" : ["%n bytes","%n bytes"],
     "Favorited" : "Favoriserad",
     "Favorite" : "Favorit",
+    "Local link" : "Lokal länk",
     "Folder" : "Mapp",
     "New folder" : "Ny mapp",
     "{newname} already exists" : "{newname}  existerar redan",

+ 1 - 0
apps/files_external/l10n/lb.js

@@ -6,6 +6,7 @@ OC.L10N.register(
     "Username" : "Benotzernumm",
     "Password" : "Passwuert",
     "Save" : "Späicheren",
+    "None" : "Keng",
     "Port" : "Port",
     "Region" : "Regioun",
     "URL" : "URL",

+ 1 - 0
apps/files_external/l10n/lb.json

@@ -4,6 +4,7 @@
     "Username" : "Benotzernumm",
     "Password" : "Passwuert",
     "Save" : "Späicheren",
+    "None" : "Keng",
     "Port" : "Port",
     "Region" : "Regioun",
     "URL" : "URL",

+ 1 - 0
apps/files_external/l10n/sv.js

@@ -1,6 +1,7 @@
 OC.L10N.register(
     "files_external",
     {
+    "Fetching request tokens failed. Verify that your app key and secret are correct." : "Fel vid hämtning av åtkomst-token. Verifiera att din app-nyckel och hemlighet stämmer.",
     "Step 1 failed. Exception: %s" : "Steg 1 flaerade. Undantag: %s",
     "Step 2 failed. Exception: %s" : "Steg 2 falerade. Undantag: %s",
     "External storage" : "Extern lagring",

+ 1 - 0
apps/files_external/l10n/sv.json

@@ -1,4 +1,5 @@
 { "translations": {
+    "Fetching request tokens failed. Verify that your app key and secret are correct." : "Fel vid hämtning av åtkomst-token. Verifiera att din app-nyckel och hemlighet stämmer.",
     "Step 1 failed. Exception: %s" : "Steg 1 flaerade. Undantag: %s",
     "Step 2 failed. Exception: %s" : "Steg 2 falerade. Undantag: %s",
     "External storage" : "Extern lagring",

+ 6 - 0
apps/files_external/lib/Command/Export.php

@@ -41,6 +41,11 @@ class Export extends ListCommand {
 				'user_id',
 				InputArgument::OPTIONAL,
 				'user id to export the personal mounts for, if no user is provided admin mounts will be exported'
+			)->addOption(
+				'all',
+				'a',
+				InputOption::VALUE_NONE,
+				'show both system wide mounts and all personal mounts'
 			);
 	}
 
@@ -48,6 +53,7 @@ class Export extends ListCommand {
 		$listCommand = new ListCommand($this->globalService, $this->userService, $this->userSession, $this->userManager);
 		$listInput = new ArrayInput([], $listCommand->getDefinition());
 		$listInput->setArgument('user_id', $input->getArgument('user_id'));
+		$listInput->setOption('all', $input->getOption('all'));
 		$listInput->setOption('output', 'json_pretty');
 		$listInput->setOption('show-password', true);
 		$listInput->setOption('full', true);

+ 37 - 11
apps/files_external/lib/Command/ListCommand.php

@@ -56,6 +56,8 @@ class ListCommand extends Base {
 	 */
 	protected $userManager;
 
+	const ALL = -1;
+
 	function __construct(GlobalStoragesService $globalService, UserStoragesService $userService, IUserSession $userSession, IUserManager $userManager) {
 		parent::__construct();
 		$this->globalService = $globalService;
@@ -67,7 +69,7 @@ class ListCommand extends Base {
 	protected function configure() {
 		$this
 			->setName('files_external:list')
-			->setDescription('List configured mounts')
+			->setDescription('List configured admin or personal mounts')
 			->addArgument(
 				'user_id',
 				InputArgument::OPTIONAL,
@@ -82,16 +84,27 @@ class ListCommand extends Base {
 				null,
 				InputOption::VALUE_NONE,
 				'don\'t truncate long values in table output'
+			)->addOption(
+				'all',
+				'a',
+				InputOption::VALUE_NONE,
+				'show both system wide mounts and all personal mounts'
 			);
 		parent::configure();
 	}
 
 	protected function execute(InputInterface $input, OutputInterface $output) {
-		$userId = $input->getArgument('user_id');
-		$storageService = $this->getStorageService($userId);
+		if ($input->getOption('all')) {
+			/** @var  $mounts StorageConfig[] */
+			$mounts = $this->globalService->getStorageForAllUsers();
+			$userId = self::ALL;
+		} else {
+			$userId = $input->getArgument('user_id');
+			$storageService = $this->getStorageService($userId);
 
-		/** @var  $mounts StorageConfig[] */
-		$mounts = $storageService->getAllStorages();
+			/** @var  $mounts StorageConfig[] */
+			$mounts = $storageService->getAllStorages();
+		}
 
 		$this->listMounts($userId, $mounts, $input, $output);
 	}
@@ -102,13 +115,15 @@ class ListCommand extends Base {
 	 * @param InputInterface $input
 	 * @param OutputInterface $output
 	 */
-	public function listMounts($userId, array $mounts, InputInterface $input, OutputInterface $output){
+	public function listMounts($userId, array $mounts, InputInterface $input, OutputInterface $output) {
 		$outputType = $input->getOption('output');
 		if (count($mounts) === 0) {
 			if ($outputType === self::OUTPUT_FORMAT_JSON || $outputType === self::OUTPUT_FORMAT_JSON_PRETTY) {
 				$output->writeln('[]');
 			} else {
-				if ($userId) {
+				if ($userId === self::ALL) {
+					$output->writeln("<info>No mounts configured</info>");
+				} else if ($userId) {
 					$output->writeln("<info>No mounts configured by $userId</info>");
 				} else {
 					$output->writeln("<info>No admin mounts configured</info>");
@@ -119,10 +134,13 @@ class ListCommand extends Base {
 
 		$headers = ['Mount ID', 'Mount Point', 'Storage', 'Authentication Type', 'Configuration', 'Options'];
 
-		if (!$userId) {
+		if (!$userId || $userId === self::ALL) {
 			$headers[] = 'Applicable Users';
 			$headers[] = 'Applicable Groups';
 		}
+		if ($userId === self::ALL) {
+			$headers[] = 'Type';
+		}
 
 		if (!$input->getOption('show-password')) {
 			$hideKeys = ['password', 'refresh_token', 'token', 'client_secret', 'public_key', 'private_key'];
@@ -150,10 +168,13 @@ class ListCommand extends Base {
 					$config->getBackendOptions(),
 					$config->getMountOptions()
 				];
-				if (!$userId) {
+				if (!$userId || $userId === self::ALL) {
 					$values[] = $config->getApplicableUsers();
 					$values[] = $config->getApplicableGroups();
 				}
+				if ($userId === self::ALL) {
+					$values[] = $config->getType() === StorageConfig::MOUNT_TYPE_ADMIN ? 'admin' : 'personal';
+				}
 
 				return array_combine($keys, $values);
 			}, $mounts);
@@ -167,7 +188,9 @@ class ListCommand extends Base {
 			$defaultMountOptions = [
 				'encrypt' => true,
 				'previews' => true,
-				'filesystem_check_changes' => 1
+				'filesystem_check_changes' => 1,
+				'enable_sharing' => false,
+				'encoding_compatibility' => false
 			];
 			$rows = array_map(function (StorageConfig $config) use ($userId, $defaultMountOptions, $full) {
 				$storageConfig = $config->getBackendOptions();
@@ -213,7 +236,7 @@ class ListCommand extends Base {
 					$optionsString
 				];
 
-				if (!$userId) {
+				if (!$userId || $userId === self::ALL) {
 					$applicableUsers = implode(', ', $config->getApplicableUsers());
 					$applicableGroups = implode(', ', $config->getApplicableGroups());
 					if ($applicableUsers === '' && $applicableGroups === '') {
@@ -222,6 +245,9 @@ class ListCommand extends Base {
 					$values[] = $applicableUsers;
 					$values[] = $applicableGroups;
 				}
+				if ($userId === self::ALL) {
+					$values[] = $config->getType() === StorageConfig::MOUNT_TYPE_ADMIN ? 'Admin' : 'Personal';
+				}
 
 				return $values;
 			}, $mounts);

+ 12 - 0
apps/files_external/lib/Service/DBConfigService.php

@@ -76,6 +76,18 @@ class DBConfigService {
 		}
 	}
 
+	/**
+	 * Get all configured mounts
+	 *
+	 * @return array
+	 */
+	public function getAllMounts() {
+		$builder = $this->connection->getQueryBuilder();
+		$query = $builder->select(['mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'type'])
+			->from('external_mounts');
+		return $this->getMountsFromQuery($query);
+	}
+
 	/**
 	 * Get admin defined mounts
 	 *

+ 19 - 0
apps/files_external/lib/Service/GlobalStoragesService.php

@@ -162,4 +162,23 @@ class GlobalStoragesService extends StoragesService {
 	protected function isApplicable(StorageConfig $config) {
 		return true;
 	}
+
+	/**
+	 * Get all configured admin and personal mounts
+	 *
+	 * @return array map of storage id to storage config
+	 */
+	public function getStorageForAllUsers() {
+		$mounts = $this->dbConfig->getAllMounts();
+		$configs = array_map([$this, 'getStorageConfigFromDBMount'], $mounts);
+		$configs = array_filter($configs, function ($config) {
+			return $config instanceof StorageConfig;
+		});
+
+		$keys = array_map(function (StorageConfig $config) {
+			return $config->getId();
+		}, $configs);
+
+		return array_combine($keys, $configs);
+	}
 }

+ 10 - 0
apps/files_external/tests/Service/DBConfigServiceTest.php

@@ -282,4 +282,14 @@ class DBConfigServiceTest extends TestCase {
 		$this->assertCount(1, $mounts);
 		$this->assertEquals($id1, $mounts[0]['mount_id']);
 	}
+
+	public function testGetAllMounts() {
+		$id1 = $this->addMount('/test', 'foo', 'bar', 100, DBConfigService::MOUNT_TYPE_ADMIN);
+		$id2 = $this->addMount('/test2', 'foo2', 'bar2', 100, DBConfigService::MOUNT_TYPE_PERSONAl);
+
+		$mounts = $this->dbConfig->getAllMounts();
+		$this->assertCount(2, $mounts);
+		$this->assertEquals($id1, $mounts[0]['mount_id']);
+		$this->assertEquals($id2, $mounts[1]['mount_id']);
+	}
 }

+ 5 - 0
apps/files_external/tests/env/start-swift-ceph.sh

@@ -74,6 +74,11 @@ if [[ $ready != 'READY=1' ]]; then
     docker logs $container
     exit 1
 fi
+if ! "$thisFolder"/env/wait-for-connection ${host} 80 600; then
+    echo "[ERROR] Waited 600 seconds, no response" >&2
+    docker logs $container
+    exit 1
+fi
 echo "Waiting another 15 seconds"
 sleep 15
 

+ 4 - 1
apps/files_sharing/ajax/external.php

@@ -77,7 +77,10 @@ $externalManager = new \OCA\Files_Sharing\External\Manager(
 // check for ssl cert
 if (substr($remote, 0, 5) === 'https') {
 	try {
-		\OC::$server->getHTTPClientService()->newClient()->get($remote)->getBody();
+		\OC::$server->getHTTPClientService()->newClient()->get($remote, [
+			'timeout' => 10,
+			'connect_timeout' => 10,
+		])->getBody();
 	} catch (\Exception $e) {
 		\OCP\JSON::error(array('data' => array('message' => $l->t('Invalid or untrusted SSL certificate'))));
 		exit;

+ 1 - 0
apps/files_sharing/l10n/lb.js

@@ -5,6 +5,7 @@ OC.L10N.register(
     "No shared links" : "Keng gedeelte Linken",
     "Cancel" : "Ofbriechen",
     "Shared by" : "Gedeelt vun",
+    "Sharing" : "Gedeelt",
     "The password is wrong. Try again." : "Den Passwuert ass incorrect. Probeier ed nach eng keier.",
     "Password" : "Passwuert",
     "No entries found in this folder" : "Keng Elementer an dësem Dossier fonnt",

+ 1 - 0
apps/files_sharing/l10n/lb.json

@@ -3,6 +3,7 @@
     "No shared links" : "Keng gedeelte Linken",
     "Cancel" : "Ofbriechen",
     "Shared by" : "Gedeelt vun",
+    "Sharing" : "Gedeelt",
     "The password is wrong. Try again." : "Den Passwuert ass incorrect. Probeier ed nach eng keier.",
     "Password" : "Passwuert",
     "No entries found in this folder" : "Keng Elementer an dësem Dossier fonnt",

+ 9 - 2
apps/files_sharing/lib/External/Storage.php

@@ -254,7 +254,10 @@ class Storage extends DAV implements ISharedStorage {
 
 		$client = $this->httpClient->newClient();
 		try {
-			$result = $client->get($url)->getBody();
+			$result = $client->get($url, [
+				'timeout' => 10,
+				'connect_timeout' => 10,
+			])->getBody();
 			$data = json_decode($result);
 			$returnValue = (is_object($data) && !empty($data->version));
 		} catch (ConnectException $e) {
@@ -301,7 +304,11 @@ class Storage extends DAV implements ISharedStorage {
 		// TODO: DI
 		$client = \OC::$server->getHTTPClientService()->newClient();
 		try {
-			$response = $client->post($url, ['body' => ['password' => $password]]);
+			$response = $client->post($url, [
+				'body' => ['password' => $password],
+				'timeout' => 10,
+				'connect_timeout' => 10,
+			]);
 		} catch (\GuzzleHttp\Exception\RequestException $e) {
 			if ($e->getCode() === 401 || $e->getCode() === 403) {
 				throw new ForbiddenException();

+ 3 - 2
apps/files_sharing/lib/MountProvider.php

@@ -68,8 +68,9 @@ class MountProvider implements IMountProvider {
 	public function getMountsForUser(IUser $user, IStorageFactory $storageFactory) {
 		$shares = $this->shareManager->getSharedWith($user->getUID(), \OCP\Share::SHARE_TYPE_USER, null, -1);
 		$shares = array_merge($shares, $this->shareManager->getSharedWith($user->getUID(), \OCP\Share::SHARE_TYPE_GROUP, null, -1));
-		$shares = array_filter($shares, function (\OCP\Share\IShare $share) {
-			return $share->getPermissions() > 0;
+		// filter out excluded shares and group shares that includes self
+		$shares = array_filter($shares, function (\OCP\Share\IShare $share) use ($user) {
+			return $share->getPermissions() > 0 && $share->getShareOwner() !== $user->getUID();
 		});
 
 		$mounts = [];

+ 136 - 0
apps/files_sharing/tests/MountProviderTest.php

@@ -0,0 +1,136 @@
+<?php
+/**
+ * @author Vincent Petry <pvince81@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\Files_Sharing\Tests;
+
+use OCA\Files_Sharing\MountProvider;
+use OCP\Files\Storage\IStorageFactory;
+use OCP\IConfig;
+use OCP\ILogger;
+use OCP\IUser;
+use OCP\Share\IShare;
+use OCP\Share\IManager;
+use OCP\Files\Mount\IMountPoint;
+
+class MountProviderTest extends \Test\TestCase {
+
+	/** @var MountProvider */
+	private $provider;
+
+	/** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */
+	private $config;
+
+	/** @var IUser|\PHPUnit_Framework_MockObject_MockObject */
+	private $user;
+
+	/** @var IStorageFactory|\PHPUnit_Framework_MockObject_MockObject */
+	private $loader;
+
+	/** @var IManager|\PHPUnit_Framework_MockObject_MockObject */
+	private $shareManager;
+
+	/** @var ILogger | \PHPUnit_Framework_MockObject_MockObject */
+	private $logger;
+
+	public function setUp() {
+		parent::setUp();
+
+		$this->config = $this->getMock('OCP\IConfig');
+		$this->user = $this->getMock('OCP\IUser');
+		$this->loader = $this->getMock('OCP\Files\Storage\IStorageFactory');
+		$this->shareManager = $this->getMock('\OCP\Share\IManager');
+		$this->logger = $this->getMock('\OCP\ILogger');
+
+		$this->provider = new MountProvider($this->config, $this->shareManager, $this->logger);
+	}
+
+	public function testExcludeShares() {
+		/** @var IShare | \PHPUnit_Framework_MockObject_MockObject $share1 */
+		$share1 = $this->getMock('\OCP\Share\IShare');
+		$share1->expects($this->once())
+			->method('getPermissions')
+			->will($this->returnValue(0));
+
+		$share2 = $this->getMock('\OCP\Share\IShare');
+		$share2->expects($this->once())
+			->method('getPermissions')
+			->will($this->returnValue(31));
+		$share2->expects($this->any())
+			->method('getShareOwner')
+			->will($this->returnValue('user2'));
+		$share2->expects($this->any())
+			->method('getTarget')
+			->will($this->returnValue('/share2'));
+
+		$share3 = $this->getMock('\OCP\Share\IShare');
+		$share3->expects($this->once())
+			->method('getPermissions')
+			->will($this->returnValue(0));
+
+		/** @var IShare | \PHPUnit_Framework_MockObject_MockObject $share4 */
+		$share4 = $this->getMock('\OCP\Share\IShare');
+		$share4->expects($this->once())
+			->method('getPermissions')
+			->will($this->returnValue(31));
+		$share4->expects($this->any())
+			->method('getShareOwner')
+			->will($this->returnValue('user2'));
+		$share4->expects($this->any())
+			->method('getTarget')
+			->will($this->returnValue('/share4'));
+
+		$share5 = $this->getMock('\OCP\Share\IShare');
+		$share5->expects($this->once())
+			->method('getPermissions')
+			->will($this->returnValue(31));
+		$share5->expects($this->any())
+			->method('getShareOwner')
+			->will($this->returnValue('user1'));
+
+		$userShares = [$share1, $share2];
+		$groupShares = [$share3, $share4, $share5];
+
+		$this->user->expects($this->any())
+			->method('getUID')
+			->will($this->returnValue('user1'));
+
+		$this->shareManager->expects($this->at(0))
+			->method('getSharedWith')
+			->with('user1', \OCP\Share::SHARE_TYPE_USER)
+			->will($this->returnValue($userShares));
+		$this->shareManager->expects($this->at(1))
+			->method('getSharedWith')
+			->with('user1', \OCP\Share::SHARE_TYPE_GROUP, null, -1)
+			->will($this->returnValue($groupShares));
+
+		$mounts = $this->provider->getMountsForUser($this->user, $this->loader);
+
+		$this->assertCount(2, $mounts);
+		$this->assertSharedMount($share1, $mounts[0]);
+		$this->assertSharedMount($share4, $mounts[1]);
+	}
+
+	private function assertSharedMount(IShare $share, IMountPoint $mount) {
+		$this->assertInstanceOf('OCA\Files_Sharing\SharedMount', $mount);
+		$this->assertEquals($share, $mount->getShare());
+	}
+}
+

+ 2 - 0
apps/files_trashbin/js/filelist.js

@@ -93,6 +93,8 @@
 
 		_renderRow: function(fileData, options) {
 			options = options || {};
+			// make a copy to avoid changing original object
+			fileData = _.extend({}, fileData);
 			var dir = this.getCurrentDirectory();
 			var dirListing = dir !== '' && dir !== '/';
 			// show deleted time as mtime

+ 22 - 0
apps/files_trashbin/tests/js/filelistSpec.js

@@ -163,6 +163,28 @@ describe('OCA.Trashbin.FileList tests', function() {
 
 			expect(fileList.findFileEl('One.txt.d11111')[0]).toEqual($tr[0]);
 		});
+		it('renders rows with the correct data when in root after calling setFiles with the same data set', function() {
+			// dir listing is false when in root
+			$('#dir').val('/');
+			fileList.setFiles(testFiles);
+			fileList.setFiles(fileList.files);
+			var $rows = fileList.$el.find('tbody tr');
+			var $tr = $rows.eq(0);
+			expect($rows.length).toEqual(4);
+			expect($tr.attr('data-id')).toEqual('1');
+			expect($tr.attr('data-type')).toEqual('file');
+			expect($tr.attr('data-file')).toEqual('One.txt.d11111');
+			expect($tr.attr('data-size')).not.toBeDefined();
+			expect($tr.attr('data-etag')).toEqual('abc');
+			expect($tr.attr('data-permissions')).toEqual('9'); // read and delete
+			expect($tr.attr('data-mime')).toEqual('text/plain');
+			expect($tr.attr('data-mtime')).toEqual('11111000');
+			expect($tr.find('a.name').attr('href')).toEqual('#');
+
+			expect($tr.find('.nametext').text().trim()).toEqual('One.txt');
+
+			expect(fileList.findFileEl('One.txt.d11111')[0]).toEqual($tr[0]);
+		});
 		it('renders rows with the correct data when in subdirectory', function() {
 			// dir listing is true when in a subdir
 			$('#dir').val('/subdir');

+ 8 - 1
apps/files_versions/lib/Storage.php

@@ -336,9 +336,16 @@ class Storage {
 			// Restore encrypted version of the old file for the newly restored file
 			// This has to happen manually here since the file is manually copied below
 			$oldVersion = $users_view->getFileInfo($fileToRestore)->getEncryptedVersion();
+			$oldFileInfo = $users_view->getFileInfo($fileToRestore);
 			$newFileInfo = $files_view->getFileInfo($filename);
 			$cache = $newFileInfo->getStorage()->getCache();
-			$cache->update($newFileInfo->getId(), ['encrypted' => $oldVersion, 'encryptedVersion' => $oldVersion]);
+			$cache->update(
+				$newFileInfo->getId(), [
+					'encrypted' => $oldVersion,
+					'encryptedVersion' => $oldVersion,
+					'size' => $oldFileInfo->getSize()
+				]
+			);
 
 			// rollback
 			if (self::copyFileContents($users_view, $fileToRestore, 'files' . $filename)) {

+ 2 - 1
apps/updatenotification/l10n/bg_BG.js

@@ -6,6 +6,7 @@ OC.L10N.register(
     "A new version is available: %s" : "Има Нова Версия: %s",
     "Open updater" : "Отвори обновяването",
     "Your version is up to date." : "Вие разполагате с последна версия",
-    "Update channel:" : "Обновяване отказано:"
+    "Update channel:" : "Обновяване отказано:",
+    "You can always update to a newer version / experimental channel. But you can never downgrade to a more stable channel." : "Винаги може да оновите до по-нова версия / експирементален канал. Но неможете вече да върнете до по-стабилен канал.."
 },
 "nplurals=2; plural=(n != 1);");

+ 2 - 1
apps/updatenotification/l10n/bg_BG.json

@@ -4,6 +4,7 @@
     "A new version is available: %s" : "Има Нова Версия: %s",
     "Open updater" : "Отвори обновяването",
     "Your version is up to date." : "Вие разполагате с последна версия",
-    "Update channel:" : "Обновяване отказано:"
+    "Update channel:" : "Обновяване отказано:",
+    "You can always update to a newer version / experimental channel. But you can never downgrade to a more stable channel." : "Винаги може да оновите до по-нова версия / експирементален канал. Но неможете вече да върнете до по-стабилен канал.."
 },"pluralForm" :"nplurals=2; plural=(n != 1);"
 }

+ 15 - 0
apps/updatenotification/l10n/lb.js

@@ -0,0 +1,15 @@
+OC.L10N.register(
+    "updatenotification",
+    {
+    "Update notifications" : "Notifikatiounen aktualiséieren",
+    "{version} is available. Get more information on how to update." : "{Versioun} ass verfügbar. Kréi méi Informatiounen doriwwer wéi d'Aktualiséierung ofleeft.",
+    "Updated channel" : "Aktualiséierte Kanal",
+    "ownCloud core" : "ownCloud Kär",
+    "Update for %1$s to version %2$s is available." : "D'Aktualiséierung fir %1$s op d'Versioun %2$s ass verfügbar.",
+    "A new version is available: %s" : "Eng nei Versioun ass verfügbar: %s",
+    "Open updater" : "Den Aktualiséierungsprogramm opmaachen",
+    "Your version is up to date." : "Déng Versioun ass aktualiséiert.",
+    "Checked on %s" : "Gepréift um %s",
+    "Update channel:" : "Kanal updaten:"
+},
+"nplurals=2; plural=(n != 1);");

+ 13 - 0
apps/updatenotification/l10n/lb.json

@@ -0,0 +1,13 @@
+{ "translations": {
+    "Update notifications" : "Notifikatiounen aktualiséieren",
+    "{version} is available. Get more information on how to update." : "{Versioun} ass verfügbar. Kréi méi Informatiounen doriwwer wéi d'Aktualiséierung ofleeft.",
+    "Updated channel" : "Aktualiséierte Kanal",
+    "ownCloud core" : "ownCloud Kär",
+    "Update for %1$s to version %2$s is available." : "D'Aktualiséierung fir %1$s op d'Versioun %2$s ass verfügbar.",
+    "A new version is available: %s" : "Eng nei Versioun ass verfügbar: %s",
+    "Open updater" : "Den Aktualiséierungsprogramm opmaachen",
+    "Your version is up to date." : "Déng Versioun ass aktualiséiert.",
+    "Checked on %s" : "Gepréift um %s",
+    "Update channel:" : "Kanal updaten:"
+},"pluralForm" :"nplurals=2; plural=(n != 1);"
+}

+ 57 - 0
apps/user_ldap/l10n/ast.js

@@ -3,6 +3,7 @@ OC.L10N.register(
     {
     "Failed to clear the mappings." : "Hebo un fallu al desaniciar les asignaciones.",
     "Failed to delete the server configuration" : "Fallu al desaniciar la configuración del sirvidor",
+    "The configuration is invalid: anonymous bind is not allowed." : "La configuración nun ye válida: nun s'almite l'enllaz anónimu ",
     "The configuration is valid and the connection could be established!" : "¡La configuración ye válida y pudo afitase la conexón!",
     "The configuration is valid, but the Bind failed. Please check the server settings and credentials." : "La configuración ye válida, pero falló'l vínculu.  Por favor, comprueba la configuración y les credenciales nel sirvidor.",
     "The configuration is invalid. Please have a look at the logs for further details." : "La configuración nun ye válida. Por favor, écha-y un güeyu a los rexistros pa más detalles.",
@@ -10,15 +11,42 @@ OC.L10N.register(
     "No configuration specified" : "Nun s'especificó la configuración",
     "No data specified" : "Nun s'especificaron los datos",
     " Could not set configuration %s" : "Nun pudo afitase la configuración %s",
+    "Action does not exist" : "L'acción nun esiste",
+    "The Base DN appears to be wrong" : "La base DN paez tar mal",
+    "Testing configuration…" : "Probando configuración...",
     "Configuration incorrect" : "Configuración incorreuta",
     "Configuration incomplete" : "Configuración incompleta",
     "Configuration OK" : "Configuración correuta",
     "Select groups" : "Esbillar grupos",
     "Select object classes" : "Seleicionar la clas d'oxetu",
+    "Please check the credentials, they seem to be wrong." : "Por favor, compruebe les credenciales, que paecen tar mal.",
+    "Please specify the port, it could not be auto-detected." : "Por favor especifica'l puertu, nun puede ser detectáu automáticamente .",
+    "Base DN could not be auto-detected, please revise credentials, host and port." : "Base DN nun puede ser detectada automáticamente, por favor revisa les credenciales, host yá'l puertu.",
+    "Could not detect Base DN, please enter it manually." : "Nun se detectó base DN, por favor introduzla manualmente .",
     "{nthServer}. Server" : "{nthServer}. Sirvidor",
+    "No object found in the given Base DN. Please revise." : "Nun s'atopó nengún oxetu na Base DN dada. Por favor, revísalo.",
+    "More than 1,000 directory entries available." : "Más de 1.000 entraes de directoriu disponibles.",
+    " entries available within the provided Base DN" : "entraes disponibles dientro la Base DN proporcionada",
+    "An error occurred. Please check the Base DN, as well as connection settings and credentials." : "Asocedió un erru. Por favor, compruebe la Base DN , amás de la configuración de conexón y les credenciales.",
     "Do you really want to delete the current Server Configuration?" : "¿Daveres que quies desaniciar la configuración actual del sirvidor?",
     "Confirm Deletion" : "Confirmar desaniciu",
+    "Mappings cleared successfully!" : "¡Asignaciones borraes correutamente!",
+    "Error while clearing the mappings." : "Fallu mientres desaniciaben les asignaciones.",
+    "Anonymous bind is not allowed. Please provide a User DN and Password." : "Nun s'almite l'enllaz anónimu. Por favor apurre un usuariu DN y contraseña.",
+    "LDAP Operations error. Anonymous bind might not be allowed." : "Erru d'operaciones LDAP . Enllaz anónimu nun s'almite.",
+    "Saving failed. Please make sure the database is in Operation. Reload before continuing." : "Nun pudo guardase. Por favor asegúrate que la base de datos ta en funcionamientu. Actualiza enantes de siguir.",
+    "Switching the mode will enable automatic LDAP queries. Depending on your LDAP size they may take a while. Do you still want to switch the mode?" : "Cambiar el mou va habilitar les consultes LDAP automátiques . Dependiendo del to tamañu de LDAP puede llevar un tiempu. ¿Inda deseya camudar el mou?",
+    "Mode switch" : "Conmutar mou",
     "Select attributes" : "Esbillar atributos",
+    "User not found. Please check your login attributes and username. Effective filter (to copy-and-paste for command line validation): <br/>" : "Nun s'alcuentra l'usuariu. Encamiéntase consultar los atributos d'accesu y nome d'usuariu. Filtru efectivu (copiar y pegar pa la validación de llínea de comandos): <br/>",
+    "User found and settings verified." : "Usuariu atopáu y la configuración verificada.",
+    "Settings verified, but one user found. Only the first will be able to login. Consider a more narrow filter." : "Axustes verificaos, pero atopose un usuariu . Namá'l primeru d'ellos va ser capaz d'empecipiar sesión. Considere un filtru más acutáu.",
+    "An unspecified error occurred. Please check the settings and the log." : "Asocedió un erru. Por favor, compruebe la configuración y el rexistru.",
+    "The search filter is invalid, probably due to syntax issues like uneven number of opened and closed brackets. Please revise." : "El filtru de busca nun ye válidu , probablemente por cuenta de problemes de sintaxis como'l númberu impar de soportes abiertos y zarraos. Por favor revisalo.",
+    "A connection error to LDAP / AD occurred, please check host, port and credentials." : "Asocedió un erru de conexón a LDAP / AD, por favor, comprueba'l host, el puertu y les credenciales.",
+    "The %uid placeholder is missing. It will be replaced with the login name when querying LDAP / AD." : "El marcador de posición %uid nun s'atopa. Va ser trocáu col nome d'entamu de sesión cuando se consulta LDAP / AD.",
+    "Please provide a login name to test against" : "Por favor, proporcione un nombre de inicio de sesión para comprobar en contra",
+    "The group box was disabled, because the LDAP / AD server does not support memberOf." : "El cuadru de grupu taba desactiváu , por mor qu'el servidor LDAP / AD nun almite memberOf .",
     "_%s group found_::_%s groups found_" : ["%s grupu alcontráu","%s grupos alcontraos"],
     "_%s user found_::_%s users found_" : ["%s usuariu alcontráu","%s usuarios alcontraos"],
     "Could not detect user display name attribute. Please specify it yourself in advanced ldap settings." : "Nun deteutamos el nome  d'atributu na pantalla d'usuariu. Por favor  especifícalu nos axustes avanzaos de ldap",
@@ -26,27 +54,52 @@ OC.L10N.register(
     "Invalid Host" : "Host inválidu",
     "Server" : "Sirvidor",
     "Users" : "Usuarios",
+    "Login Attributes" : "Los atributos d'aniciu de sesión",
     "Groups" : "Grupos",
     "Test Configuration" : "Configuración de prueba",
     "Help" : "Ayuda",
     "Groups meeting these criteria are available in %s:" : "Los grupos que cumplen estos criterios tán disponibles en %s:",
+    "Only these object classes:" : "Namái d'estes clases d'oxetu:",
+    "Only from these groups:" : "Namái d'estos grupos:",
+    "Search groups" : "Esbillar grupos",
+    "Available groups" : "Grupos disponibles",
+    "Selected groups" : "Grupos seleicionaos",
+    "Edit LDAP Query" : "Editar consulta LDAP",
+    "LDAP Filter:" : "Filtru LDAP:",
     "The filter specifies which LDAP groups shall have access to the %s instance." : "El filtru especifica qué grupos LDAP van tener accesu a %s.",
+    "Verify settings and count groups" : "Comprobar la configuración y grupos de recuentu",
+    "When logging in, %s will find the user based on the following attributes:" : "Al empecipiar sesión, %s atópase l'usuariu en función de los siguientes atributos :",
+    "LDAP / AD Username:" : "Nome d'usuariu LDAP / AD:",
+    "Allows login against the LDAP / AD username, which is either uid or samaccountname and will be detected." : "Permite la entrada en contra'l nome d'usuariu LDAP / AD,  yá sía uid o samaccountname y va ser detectada.",
+    "LDAP / AD Email Address:" : "Direición e-mail LDAP / AD:",
+    "Allows login against an email attribute. Mail and mailPrimaryAddress will be allowed." : "Almite la entrada contra un atributu de corréu electrónicu. Almitirase corréu electrónicu y mailPrimaryAddress.",
     "Other Attributes:" : "Otros atributos:",
     "Defines the filter to apply, when login is attempted. %%uid replaces the username in the login action. Example: \"uid=%%uid\"" : "Define'l filtru a aplicar cuando s'intenta identificar. %%uid va trocar al nome d'usuariu nel procesu d'identificación. Por exemplu: \"uid=%%uid\"",
+    "Test Loginname" : "Preba de Nome d'Aniciu de Sesión",
+    "Verify settings" : "Comprobar los axustes",
     "1. Server" : "1. Sirvidor",
     "%s. Server:" : "%s. Sirvidor:",
+    "Add a new and blank configuration" : "Amestar una configuración nueva y en blancu",
+    "Copy current configuration into new directory binding" : "Copiar configuración actual nel nuevu directoriu obligatoriu",
+    "Delete the current configuration" : "Desaniciar la configuración actual",
     "Host" : "Equipu",
     "You can omit the protocol, except you require SSL. Then start with ldaps://" : "Pues omitir el protocolu, sacantes si necesites SSL. Nesi casu, entama con ldaps://",
     "Port" : "Puertu",
+    "Detect Port" : "Detectar Puertu",
     "User DN" : "DN usuariu",
     "The DN of the client user with which the bind shall be done, e.g. uid=agent,dc=example,dc=com. For anonymous access, leave DN and Password empty." : "El DN del usuariu veceru col que va facese l'asociación, p.ex. uid=axente,dc=exemplu,dc=com. P'accesu anónimu, dexa DN y contraseña baleros.",
     "Password" : "Contraseña",
     "For anonymous access, leave DN and Password empty." : "Pa un accesu anónimu, dexar el DN y la contraseña baleros.",
     "One Base DN per line" : "Un DN Base por llinia",
     "You can specify Base DN for users and groups in the Advanced tab" : "Pues especificar el DN base pa usuarios y grupos na llingüeta Avanzáu",
+    "Detect Base DN" : "Detectar Base DN",
+    "Test Base DN" : "Probar Base DN",
     "Avoids automatic LDAP requests. Better for bigger setups, but requires some LDAP knowledge." : "Evita peticiones automátiques de LDAP. Meyor pa grandes configuraciones, pero rique mayor conocimientu de LDAP.",
     "Manually enter LDAP filters (recommended for large directories)" : "Inxerta manualmente los filtros de LDAP (recomendáu pa direutorios llargos)",
+    "%s access is limited to users meeting these criteria:" : "%s accesos llendaos a los usuarios que cumplan estos criterios:",
+    "The most common object classes for users are organizationalPerson, person, user, and inetOrgPerson. If you are not sure which object class to select, please consult your directory admin." : "Les clases d'oxetos más comunes pa los usuarios d'Internet son organizationalPerson, persona, usuariu y inetOrgPerson . Si nun ta seguro de qué clase d'oxetu escoyer, por favor consulte al so alministrador de directorios.",
     "The filter specifies which LDAP users shall have access to the %s instance." : "El filtru especifica qué usuarios LDAP puen tener accesu a %s.",
+    "Verify settings and count users" : "Comprobar la configuración y usuarios de recuentu",
     "Saving" : "Guardando",
     "Back" : "Atrás",
     "Continue" : "Continuar",
@@ -70,6 +123,8 @@ OC.L10N.register(
     "Directory Settings" : "Axustes del direutoriu",
     "User Display Name Field" : "Campu de nome d'usuariu a amosar",
     "The LDAP attribute to use to generate the user's display name." : "El campu LDAP a usar pa xenerar el nome p'amosar del usuariu.",
+    "2nd User Display Name Field" : "2ª usuariu amuesa Nome del campu",
+    "Optional. An LDAP attribute to be added to the display name in brackets. Results in e.g. »John Doe (john.doe@example.org)«." : "Opcional. Un atributu LDAP que s'amesta al nome de visualización ente paréntesis. Los resultaos en, por exemplu, »John Doe (john.doe@example.org)«.",
     "Base User Tree" : "Árbol base d'usuariu",
     "One User Base DN per line" : "Un DN Base d'Usuariu por llinia",
     "User Search Attributes" : "Atributos de la gueta d'usuariu",
@@ -80,6 +135,8 @@ OC.L10N.register(
     "One Group Base DN per line" : "Un DN Base de Grupu por llinia",
     "Group Search Attributes" : "Atributos de gueta de grupu",
     "Group-Member association" : "Asociación Grupu-Miembru",
+    "Dynamic Group Member URL" : "URL Dinámica de Grupu d'Usuarios",
+    "The LDAP attribute that on group objects contains an LDAP search URL that determines what objects belong to the group. (An empty setting disables dynamic group membership functionality.)" : "L'atributu LDAP que nos oxetos de grupu contien una gueta de URLs de LDAP que determina qué oxetos pertenecen al grupu. (Un axuste vacíu desanicia la funcionalidá dinámica de pertenencia al grupu.)",
     "Nested Groups" : "Grupos añeraos",
     "When switched on, groups that contain groups are supported. (Only works if the group member attribute contains DNs.)" : "Cuando s'active, van permitise grupos que contengan otros grupos (namái funciona si l'atributu de miembru de grupu contién DNs).",
     "Paging chunksize" : "Tamañu de los fragmentos de paxinación",

+ 57 - 0
apps/user_ldap/l10n/ast.json

@@ -1,6 +1,7 @@
 { "translations": {
     "Failed to clear the mappings." : "Hebo un fallu al desaniciar les asignaciones.",
     "Failed to delete the server configuration" : "Fallu al desaniciar la configuración del sirvidor",
+    "The configuration is invalid: anonymous bind is not allowed." : "La configuración nun ye válida: nun s'almite l'enllaz anónimu ",
     "The configuration is valid and the connection could be established!" : "¡La configuración ye válida y pudo afitase la conexón!",
     "The configuration is valid, but the Bind failed. Please check the server settings and credentials." : "La configuración ye válida, pero falló'l vínculu.  Por favor, comprueba la configuración y les credenciales nel sirvidor.",
     "The configuration is invalid. Please have a look at the logs for further details." : "La configuración nun ye válida. Por favor, écha-y un güeyu a los rexistros pa más detalles.",
@@ -8,15 +9,42 @@
     "No configuration specified" : "Nun s'especificó la configuración",
     "No data specified" : "Nun s'especificaron los datos",
     " Could not set configuration %s" : "Nun pudo afitase la configuración %s",
+    "Action does not exist" : "L'acción nun esiste",
+    "The Base DN appears to be wrong" : "La base DN paez tar mal",
+    "Testing configuration…" : "Probando configuración...",
     "Configuration incorrect" : "Configuración incorreuta",
     "Configuration incomplete" : "Configuración incompleta",
     "Configuration OK" : "Configuración correuta",
     "Select groups" : "Esbillar grupos",
     "Select object classes" : "Seleicionar la clas d'oxetu",
+    "Please check the credentials, they seem to be wrong." : "Por favor, compruebe les credenciales, que paecen tar mal.",
+    "Please specify the port, it could not be auto-detected." : "Por favor especifica'l puertu, nun puede ser detectáu automáticamente .",
+    "Base DN could not be auto-detected, please revise credentials, host and port." : "Base DN nun puede ser detectada automáticamente, por favor revisa les credenciales, host yá'l puertu.",
+    "Could not detect Base DN, please enter it manually." : "Nun se detectó base DN, por favor introduzla manualmente .",
     "{nthServer}. Server" : "{nthServer}. Sirvidor",
+    "No object found in the given Base DN. Please revise." : "Nun s'atopó nengún oxetu na Base DN dada. Por favor, revísalo.",
+    "More than 1,000 directory entries available." : "Más de 1.000 entraes de directoriu disponibles.",
+    " entries available within the provided Base DN" : "entraes disponibles dientro la Base DN proporcionada",
+    "An error occurred. Please check the Base DN, as well as connection settings and credentials." : "Asocedió un erru. Por favor, compruebe la Base DN , amás de la configuración de conexón y les credenciales.",
     "Do you really want to delete the current Server Configuration?" : "¿Daveres que quies desaniciar la configuración actual del sirvidor?",
     "Confirm Deletion" : "Confirmar desaniciu",
+    "Mappings cleared successfully!" : "¡Asignaciones borraes correutamente!",
+    "Error while clearing the mappings." : "Fallu mientres desaniciaben les asignaciones.",
+    "Anonymous bind is not allowed. Please provide a User DN and Password." : "Nun s'almite l'enllaz anónimu. Por favor apurre un usuariu DN y contraseña.",
+    "LDAP Operations error. Anonymous bind might not be allowed." : "Erru d'operaciones LDAP . Enllaz anónimu nun s'almite.",
+    "Saving failed. Please make sure the database is in Operation. Reload before continuing." : "Nun pudo guardase. Por favor asegúrate que la base de datos ta en funcionamientu. Actualiza enantes de siguir.",
+    "Switching the mode will enable automatic LDAP queries. Depending on your LDAP size they may take a while. Do you still want to switch the mode?" : "Cambiar el mou va habilitar les consultes LDAP automátiques . Dependiendo del to tamañu de LDAP puede llevar un tiempu. ¿Inda deseya camudar el mou?",
+    "Mode switch" : "Conmutar mou",
     "Select attributes" : "Esbillar atributos",
+    "User not found. Please check your login attributes and username. Effective filter (to copy-and-paste for command line validation): <br/>" : "Nun s'alcuentra l'usuariu. Encamiéntase consultar los atributos d'accesu y nome d'usuariu. Filtru efectivu (copiar y pegar pa la validación de llínea de comandos): <br/>",
+    "User found and settings verified." : "Usuariu atopáu y la configuración verificada.",
+    "Settings verified, but one user found. Only the first will be able to login. Consider a more narrow filter." : "Axustes verificaos, pero atopose un usuariu . Namá'l primeru d'ellos va ser capaz d'empecipiar sesión. Considere un filtru más acutáu.",
+    "An unspecified error occurred. Please check the settings and the log." : "Asocedió un erru. Por favor, compruebe la configuración y el rexistru.",
+    "The search filter is invalid, probably due to syntax issues like uneven number of opened and closed brackets. Please revise." : "El filtru de busca nun ye válidu , probablemente por cuenta de problemes de sintaxis como'l númberu impar de soportes abiertos y zarraos. Por favor revisalo.",
+    "A connection error to LDAP / AD occurred, please check host, port and credentials." : "Asocedió un erru de conexón a LDAP / AD, por favor, comprueba'l host, el puertu y les credenciales.",
+    "The %uid placeholder is missing. It will be replaced with the login name when querying LDAP / AD." : "El marcador de posición %uid nun s'atopa. Va ser trocáu col nome d'entamu de sesión cuando se consulta LDAP / AD.",
+    "Please provide a login name to test against" : "Por favor, proporcione un nombre de inicio de sesión para comprobar en contra",
+    "The group box was disabled, because the LDAP / AD server does not support memberOf." : "El cuadru de grupu taba desactiváu , por mor qu'el servidor LDAP / AD nun almite memberOf .",
     "_%s group found_::_%s groups found_" : ["%s grupu alcontráu","%s grupos alcontraos"],
     "_%s user found_::_%s users found_" : ["%s usuariu alcontráu","%s usuarios alcontraos"],
     "Could not detect user display name attribute. Please specify it yourself in advanced ldap settings." : "Nun deteutamos el nome  d'atributu na pantalla d'usuariu. Por favor  especifícalu nos axustes avanzaos de ldap",
@@ -24,27 +52,52 @@
     "Invalid Host" : "Host inválidu",
     "Server" : "Sirvidor",
     "Users" : "Usuarios",
+    "Login Attributes" : "Los atributos d'aniciu de sesión",
     "Groups" : "Grupos",
     "Test Configuration" : "Configuración de prueba",
     "Help" : "Ayuda",
     "Groups meeting these criteria are available in %s:" : "Los grupos que cumplen estos criterios tán disponibles en %s:",
+    "Only these object classes:" : "Namái d'estes clases d'oxetu:",
+    "Only from these groups:" : "Namái d'estos grupos:",
+    "Search groups" : "Esbillar grupos",
+    "Available groups" : "Grupos disponibles",
+    "Selected groups" : "Grupos seleicionaos",
+    "Edit LDAP Query" : "Editar consulta LDAP",
+    "LDAP Filter:" : "Filtru LDAP:",
     "The filter specifies which LDAP groups shall have access to the %s instance." : "El filtru especifica qué grupos LDAP van tener accesu a %s.",
+    "Verify settings and count groups" : "Comprobar la configuración y grupos de recuentu",
+    "When logging in, %s will find the user based on the following attributes:" : "Al empecipiar sesión, %s atópase l'usuariu en función de los siguientes atributos :",
+    "LDAP / AD Username:" : "Nome d'usuariu LDAP / AD:",
+    "Allows login against the LDAP / AD username, which is either uid or samaccountname and will be detected." : "Permite la entrada en contra'l nome d'usuariu LDAP / AD,  yá sía uid o samaccountname y va ser detectada.",
+    "LDAP / AD Email Address:" : "Direición e-mail LDAP / AD:",
+    "Allows login against an email attribute. Mail and mailPrimaryAddress will be allowed." : "Almite la entrada contra un atributu de corréu electrónicu. Almitirase corréu electrónicu y mailPrimaryAddress.",
     "Other Attributes:" : "Otros atributos:",
     "Defines the filter to apply, when login is attempted. %%uid replaces the username in the login action. Example: \"uid=%%uid\"" : "Define'l filtru a aplicar cuando s'intenta identificar. %%uid va trocar al nome d'usuariu nel procesu d'identificación. Por exemplu: \"uid=%%uid\"",
+    "Test Loginname" : "Preba de Nome d'Aniciu de Sesión",
+    "Verify settings" : "Comprobar los axustes",
     "1. Server" : "1. Sirvidor",
     "%s. Server:" : "%s. Sirvidor:",
+    "Add a new and blank configuration" : "Amestar una configuración nueva y en blancu",
+    "Copy current configuration into new directory binding" : "Copiar configuración actual nel nuevu directoriu obligatoriu",
+    "Delete the current configuration" : "Desaniciar la configuración actual",
     "Host" : "Equipu",
     "You can omit the protocol, except you require SSL. Then start with ldaps://" : "Pues omitir el protocolu, sacantes si necesites SSL. Nesi casu, entama con ldaps://",
     "Port" : "Puertu",
+    "Detect Port" : "Detectar Puertu",
     "User DN" : "DN usuariu",
     "The DN of the client user with which the bind shall be done, e.g. uid=agent,dc=example,dc=com. For anonymous access, leave DN and Password empty." : "El DN del usuariu veceru col que va facese l'asociación, p.ex. uid=axente,dc=exemplu,dc=com. P'accesu anónimu, dexa DN y contraseña baleros.",
     "Password" : "Contraseña",
     "For anonymous access, leave DN and Password empty." : "Pa un accesu anónimu, dexar el DN y la contraseña baleros.",
     "One Base DN per line" : "Un DN Base por llinia",
     "You can specify Base DN for users and groups in the Advanced tab" : "Pues especificar el DN base pa usuarios y grupos na llingüeta Avanzáu",
+    "Detect Base DN" : "Detectar Base DN",
+    "Test Base DN" : "Probar Base DN",
     "Avoids automatic LDAP requests. Better for bigger setups, but requires some LDAP knowledge." : "Evita peticiones automátiques de LDAP. Meyor pa grandes configuraciones, pero rique mayor conocimientu de LDAP.",
     "Manually enter LDAP filters (recommended for large directories)" : "Inxerta manualmente los filtros de LDAP (recomendáu pa direutorios llargos)",
+    "%s access is limited to users meeting these criteria:" : "%s accesos llendaos a los usuarios que cumplan estos criterios:",
+    "The most common object classes for users are organizationalPerson, person, user, and inetOrgPerson. If you are not sure which object class to select, please consult your directory admin." : "Les clases d'oxetos más comunes pa los usuarios d'Internet son organizationalPerson, persona, usuariu y inetOrgPerson . Si nun ta seguro de qué clase d'oxetu escoyer, por favor consulte al so alministrador de directorios.",
     "The filter specifies which LDAP users shall have access to the %s instance." : "El filtru especifica qué usuarios LDAP puen tener accesu a %s.",
+    "Verify settings and count users" : "Comprobar la configuración y usuarios de recuentu",
     "Saving" : "Guardando",
     "Back" : "Atrás",
     "Continue" : "Continuar",
@@ -68,6 +121,8 @@
     "Directory Settings" : "Axustes del direutoriu",
     "User Display Name Field" : "Campu de nome d'usuariu a amosar",
     "The LDAP attribute to use to generate the user's display name." : "El campu LDAP a usar pa xenerar el nome p'amosar del usuariu.",
+    "2nd User Display Name Field" : "2ª usuariu amuesa Nome del campu",
+    "Optional. An LDAP attribute to be added to the display name in brackets. Results in e.g. »John Doe (john.doe@example.org)«." : "Opcional. Un atributu LDAP que s'amesta al nome de visualización ente paréntesis. Los resultaos en, por exemplu, »John Doe (john.doe@example.org)«.",
     "Base User Tree" : "Árbol base d'usuariu",
     "One User Base DN per line" : "Un DN Base d'Usuariu por llinia",
     "User Search Attributes" : "Atributos de la gueta d'usuariu",
@@ -78,6 +133,8 @@
     "One Group Base DN per line" : "Un DN Base de Grupu por llinia",
     "Group Search Attributes" : "Atributos de gueta de grupu",
     "Group-Member association" : "Asociación Grupu-Miembru",
+    "Dynamic Group Member URL" : "URL Dinámica de Grupu d'Usuarios",
+    "The LDAP attribute that on group objects contains an LDAP search URL that determines what objects belong to the group. (An empty setting disables dynamic group membership functionality.)" : "L'atributu LDAP que nos oxetos de grupu contien una gueta de URLs de LDAP que determina qué oxetos pertenecen al grupu. (Un axuste vacíu desanicia la funcionalidá dinámica de pertenencia al grupu.)",
     "Nested Groups" : "Grupos añeraos",
     "When switched on, groups that contain groups are supported. (Only works if the group member attribute contains DNs.)" : "Cuando s'active, van permitise grupos que contengan otros grupos (namái funciona si l'atributu de miembru de grupu contién DNs).",
     "Paging chunksize" : "Tamañu de los fragmentos de paxinación",

+ 38 - 1
apps/user_ldap/l10n/lb.js

@@ -1,14 +1,51 @@
 OC.L10N.register(
     "user_ldap",
     {
+    "Failed to delete the server configuration" : "D'Server-Konfiguratioun konnt net geläscht ginn",
+    "The configuration is invalid: anonymous bind is not allowed." : "Dës Konfiguratioun ass ongëlteg: eng anonym Bindung ass net erlaabt.",
+    "Action does not exist" : "Dës Aktioun gëtt et net",
+    "Testing configuration…" : "D'Konfiguratioun gëtt getest...",
+    "Configuration incorrect" : "D'Konfiguratioun ass net korrekt",
+    "Configuration incomplete" : "D'Konfiguratioun ass net komplett",
+    "Configuration OK" : "Konfiguratioun OK",
+    "Select groups" : "Wiel Gruppen äus",
+    "Saving failed. Please make sure the database is in Operation. Reload before continuing." : "D'Späicheren huet net geklappt. W.e.g. géi sécher dass Datebank an der Operatioun ass. Lued nach emol éiers de weider fiers.",
+    "Select attributes" : "Wiel Attributer aus",
+    "User not found. Please check your login attributes and username. Effective filter (to copy-and-paste for command line validation): <br/>" : "De Benotzer konnt net fonnt ginn. W.e.g. kuck deng Login Attributer a Benotzernumm no. \n ",
+    "_%s group found_::_%s groups found_" : ["%s Grupp fonnt","%s Gruppe fonnt"],
+    "_%s user found_::_%s users found_" : ["%s Benotzer fonnt","%s Benotzere fonnt"],
+    "Could not find the desired feature" : "Déi gewënschte Funktioun konnt net fonnt ginn",
+    "Server" : "Server",
     "Users" : "Benotzer",
     "Groups" : "Gruppen",
+    "Test Configuration" : "Konfiguratiounstest",
     "Help" : "Hëllef",
+    "Groups meeting these criteria are available in %s:" : "D'Gruppen, déi dës Critèren erfëllen sinn am %s:",
+    "Only these object classes:" : "Nëmmen des Klass vun Objeten:",
+    "Only from these groups:" : "Nëmme vun dëse Gruppen:",
+    "Search groups" : "Sich Gruppen",
+    "Available groups" : "Disponibel Gruppen",
+    "Selected groups" : "Ausgewielte Gruppen",
+    "Test Loginname" : "Test Benotzernumm",
+    "Verify settings" : "Astellungen iwwerpréiwen",
+    "1. Server" : "1. Server",
+    "%s. Server:" : "%s. Server",
+    "Delete the current configuration" : "Läsch déi aktuell Konfiguratioun",
     "Host" : "Host",
     "Port" : "Port",
+    "User DN" : "Benotzer DN",
     "Password" : "Passwuert",
+    "Saving" : "Speicheren...",
     "Back" : "Zeréck",
     "Continue" : "Weider",
-    "Advanced" : "Avancéiert"
+    "Advanced" : "Erweidert",
+    "Connection Settings" : "D'Astellunge vun der Verbindung",
+    "Configuration Active" : "D'Konfiguratioun ass aktiv",
+    "When unchecked, this configuration will be skipped." : "Ouni Iwwerpréiwung wäert dës Konfiguratioun iwwergaange ginn.",
+    "Directory Settings" : "Dossier's Astellungen",
+    "in bytes" : "A Bytes",
+    "Email Field" : "Email Feld",
+    "Internal Username" : "Interne Benotzernumm",
+    "Internal Username Attribute:" : "Interne Benotzernumm Attribut:"
 },
 "nplurals=2; plural=(n != 1);");

+ 38 - 1
apps/user_ldap/l10n/lb.json

@@ -1,12 +1,49 @@
 { "translations": {
+    "Failed to delete the server configuration" : "D'Server-Konfiguratioun konnt net geläscht ginn",
+    "The configuration is invalid: anonymous bind is not allowed." : "Dës Konfiguratioun ass ongëlteg: eng anonym Bindung ass net erlaabt.",
+    "Action does not exist" : "Dës Aktioun gëtt et net",
+    "Testing configuration…" : "D'Konfiguratioun gëtt getest...",
+    "Configuration incorrect" : "D'Konfiguratioun ass net korrekt",
+    "Configuration incomplete" : "D'Konfiguratioun ass net komplett",
+    "Configuration OK" : "Konfiguratioun OK",
+    "Select groups" : "Wiel Gruppen äus",
+    "Saving failed. Please make sure the database is in Operation. Reload before continuing." : "D'Späicheren huet net geklappt. W.e.g. géi sécher dass Datebank an der Operatioun ass. Lued nach emol éiers de weider fiers.",
+    "Select attributes" : "Wiel Attributer aus",
+    "User not found. Please check your login attributes and username. Effective filter (to copy-and-paste for command line validation): <br/>" : "De Benotzer konnt net fonnt ginn. W.e.g. kuck deng Login Attributer a Benotzernumm no. \n ",
+    "_%s group found_::_%s groups found_" : ["%s Grupp fonnt","%s Gruppe fonnt"],
+    "_%s user found_::_%s users found_" : ["%s Benotzer fonnt","%s Benotzere fonnt"],
+    "Could not find the desired feature" : "Déi gewënschte Funktioun konnt net fonnt ginn",
+    "Server" : "Server",
     "Users" : "Benotzer",
     "Groups" : "Gruppen",
+    "Test Configuration" : "Konfiguratiounstest",
     "Help" : "Hëllef",
+    "Groups meeting these criteria are available in %s:" : "D'Gruppen, déi dës Critèren erfëllen sinn am %s:",
+    "Only these object classes:" : "Nëmmen des Klass vun Objeten:",
+    "Only from these groups:" : "Nëmme vun dëse Gruppen:",
+    "Search groups" : "Sich Gruppen",
+    "Available groups" : "Disponibel Gruppen",
+    "Selected groups" : "Ausgewielte Gruppen",
+    "Test Loginname" : "Test Benotzernumm",
+    "Verify settings" : "Astellungen iwwerpréiwen",
+    "1. Server" : "1. Server",
+    "%s. Server:" : "%s. Server",
+    "Delete the current configuration" : "Läsch déi aktuell Konfiguratioun",
     "Host" : "Host",
     "Port" : "Port",
+    "User DN" : "Benotzer DN",
     "Password" : "Passwuert",
+    "Saving" : "Speicheren...",
     "Back" : "Zeréck",
     "Continue" : "Weider",
-    "Advanced" : "Avancéiert"
+    "Advanced" : "Erweidert",
+    "Connection Settings" : "D'Astellunge vun der Verbindung",
+    "Configuration Active" : "D'Konfiguratioun ass aktiv",
+    "When unchecked, this configuration will be skipped." : "Ouni Iwwerpréiwung wäert dës Konfiguratioun iwwergaange ginn.",
+    "Directory Settings" : "Dossier's Astellungen",
+    "in bytes" : "A Bytes",
+    "Email Field" : "Email Feld",
+    "Internal Username" : "Interne Benotzernumm",
+    "Internal Username Attribute:" : "Interne Benotzernumm Attribut:"
 },"pluralForm" :"nplurals=2; plural=(n != 1);"
 }

+ 8 - 1
apps/user_ldap/lib/Access.php

@@ -732,7 +732,14 @@ class Access extends LDAPUtility implements IUserTools {
 				$user->unmark();
 				$user = $this->userManager->get($ocName);
 			}
-			$user->processAttributes($userRecord);
+			if ($user !== null) {
+				$user->processAttributes($userRecord);
+			} else {
+				\OC::$server->getLogger()->debug(
+					"The ldap user manager returned null for $ocName",
+					['app'=>'user_ldap']
+				);
+			}
 		}
 	}
 

+ 4 - 4
autotest.sh

@@ -192,8 +192,8 @@ function execute_tests {
 			DATABASEHOST=$(docker inspect --format="{{.NetworkSettings.IPAddress}}" "$DOCKER_CONTAINER_ID")
 
 			echo "Waiting for MySQL initialisation ..."
-			if ! apps/files_external/tests/env/wait-for-connection $DATABASEHOST 3306 60; then
-				echo "[ERROR] Waited 60 seconds, no response" >&2
+			if ! apps/files_external/tests/env/wait-for-connection $DATABASEHOST 3306 600; then
+				echo "[ERROR] Waited 600 seconds, no response" >&2
 				exit 1
 			fi
 
@@ -221,8 +221,8 @@ function execute_tests {
 			DATABASEHOST=$(docker inspect --format="{{.NetworkSettings.IPAddress}}" "$DOCKER_CONTAINER_ID")
 
 			echo "Waiting for MariaDB initialisation ..."
-			if ! apps/files_external/tests/env/wait-for-connection $DATABASEHOST 3306 60; then
-				echo "[ERROR] Waited 60 seconds, no response" >&2
+			if ! apps/files_external/tests/env/wait-for-connection $DATABASEHOST 3306 600; then
+				echo "[ERROR] Waited 600 seconds, no response" >&2
 				exit 1
 			fi
 

+ 13 - 0
core/Application.php

@@ -32,6 +32,7 @@ use OC\AppFramework\Utility\TimeFactory;
 use OC\Core\Controller\AvatarController;
 use OC\Core\Controller\LoginController;
 use OC\Core\Controller\LostController;
+use OC\Core\Controller\OccController;
 use OC\Core\Controller\TokenController;
 use OC\Core\Controller\TwoFactorChallengeController;
 use OC\Core\Controller\UserController;
@@ -125,6 +126,18 @@ class Application extends App {
 				$c->query('SecureRandom')
 			);
 		});
+		$container->registerService('OccController', function(SimpleContainer $c) {
+			return new OccController(
+				$c->query('AppName'),
+				$c->query('Request'),
+				$c->query('Config'),
+				new \OC\Console\Application(
+					$c->query('Config'),
+					$c->query('ServerContainer')->getEventDispatcher(),
+					$c->query('Request')
+				)
+			);
+		});
 
 		/**
 		 * Core class wrappers

+ 147 - 0
core/Controller/OccController.php

@@ -0,0 +1,147 @@
+<?php
+/**
+ * @author Victor Dubiniuk <dubiniuk@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\Core\Controller;
+
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\JSONResponse;
+use OC\Console\Application;
+use OCP\IConfig;
+use OCP\IRequest;
+use Symfony\Component\Console\Input\ArrayInput;
+use Symfony\Component\Console\Output\BufferedOutput;
+
+class OccController extends Controller {
+	
+	/** @var array  */
+	private $allowedCommands = [
+		'app:disable',
+		'app:enable',
+		'app:getpath',
+		'app:list',
+		'check',
+		'config:list',
+		'maintenance:mode',
+		'status',
+		'upgrade'
+	];
+
+	/** @var IConfig */
+	private $config;
+	/** @var Application */
+	private $console;
+
+	/**
+	 * OccController constructor.
+	 *
+	 * @param string $appName
+	 * @param IRequest $request
+	 * @param IConfig $config
+	 * @param Application $console
+	 */
+	public function __construct($appName, IRequest $request,
+								IConfig $config, Application $console) {
+		parent::__construct($appName, $request);
+		$this->config = $config;
+		$this->console = $console;
+	}
+
+	/**
+	 * @PublicPage
+	 * @NoCSRFRequired
+	 *
+	 * Execute occ command
+	 * Sample request
+	 *	POST http://domain.tld/index.php/occ/status',
+	 * 		{
+	 *			'params': {
+	 * 					'--no-warnings':'1',
+	 *		 			'--output':'json'
+	 * 			},
+	 * 			'token': 'someToken'
+	 * 		}
+	 *
+	 * @param string $command
+	 * @param string $token
+	 * @param array $params
+	 *
+	 * @return JSONResponse
+	 * @throws \Exception
+	 */
+	public function execute($command, $token, $params = []) {
+		try {
+			$this->validateRequest($command, $token);
+
+			$output = new BufferedOutput();
+			$formatter = $output->getFormatter();
+			$formatter->setDecorated(false);
+			$this->console->setAutoExit(false);
+			$this->console->loadCommands(new ArrayInput([]), $output);
+
+			$inputArray = array_merge(['command' => $command], $params);
+			$input = new ArrayInput($inputArray);
+
+			$exitCode = $this->console->run($input, $output);
+			$response = $output->fetch();
+
+			$json = [
+				'exitCode' => $exitCode,
+				'response' => $response
+			];
+
+		} catch (\UnexpectedValueException $e){
+			$json = [
+				'exitCode' => 126,
+				'response' => 'Not allowed',
+				'details' => $e->getMessage()
+			];
+		}
+		return new JSONResponse($json);
+	}
+
+	/**
+	 * Check if command is allowed and has a valid security token
+	 * @param $command
+	 * @param $token
+	 */
+	protected function validateRequest($command, $token){
+		if (!in_array($this->request->getRemoteAddress(), ['::1', '127.0.0.1', 'localhost'])) {
+			throw new \UnexpectedValueException('Web executor is not allowed to run from a different host');
+		}
+
+		if (!in_array($command, $this->allowedCommands)) {
+			throw new \UnexpectedValueException(sprintf('Command "%s" is not allowed to run via web request', $command));
+		}
+
+		$coreToken = $this->config->getSystemValue('updater.secret', '');
+		if ($coreToken === '') {
+			throw new \UnexpectedValueException(
+				'updater.secret is undefined in config/config.php. Either browse the admin settings in your ownCloud and click "Open updater" or define a strong secret using <pre>php -r \'echo password_hash("MyStrongSecretDoUseYourOwn!", PASSWORD_DEFAULT)."\n";\'</pre> and set this in the config.php.'
+			);
+		}
+
+		if (!password_verify($token, $coreToken)) {
+			throw new \UnexpectedValueException(
+				'updater.secret does not match the provided token'
+			);
+		}
+	}
+}

+ 1 - 0
core/js/files/iedavclient.js

@@ -29,6 +29,7 @@
 
 			var self = this;
 			var xhr = this.xhrProvider();
+			headers = headers || {};
 
 			if (this.userName) {
 				headers['Authorization'] = 'Basic ' + btoa(this.userName + ':' + this.password);

+ 4 - 2
core/js/setupchecks.js

@@ -76,7 +76,8 @@
 			$.ajax({
 				type: 'PROPFIND',
 				url: url,
-				complete: afterCall
+				complete: afterCall,
+				allowAuthErrors: true
 			});
 			return deferred.promise();
 		},
@@ -209,7 +210,8 @@
 			$.ajax({
 				type: 'GET',
 				url: OC.linkTo('', oc_dataURL+'/htaccesstest.txt?t=' + (new Date()).getTime()),
-				complete: afterCall
+				complete: afterCall,
+				allowAuthErrors: true
 			});
 			return deferred.promise();
 		},

+ 1 - 0
core/l10n/lb.js

@@ -83,6 +83,7 @@ OC.L10N.register(
     "can share" : "kann deelen",
     "can edit" : "kann änneren",
     "create" : "erstellen",
+    "change" : "änneren",
     "delete" : "läschen",
     "access control" : "Zougrëffskontroll",
     "Share" : "Deelen",

+ 1 - 0
core/l10n/lb.json

@@ -81,6 +81,7 @@
     "can share" : "kann deelen",
     "can edit" : "kann änneren",
     "create" : "erstellen",
+    "change" : "änneren",
     "delete" : "läschen",
     "access control" : "Zougrëffskontroll",
     "Share" : "Deelen",

+ 15 - 1
core/l10n/ro.js

@@ -9,6 +9,11 @@ OC.L10N.register(
     "Invalid image" : "Imagine invalidă",
     "An error occurred. Please contact your admin." : "A apărut o eroare. Te rugăm să contactezi administratorul.",
     "No temporary profile picture available, try again" : "Nu este disponibilă nicio imagine temporară a profilului, încearcă din nou",
+    "Crop is not square" : "Selecția nu este pătrată",
+    "Couldn't reset password because the token is invalid" : "Parola nu a putut fi resetată deoarece token-ul este invalid",
+    "Couldn't reset password because the token is expired" : "Parola nu a putut fi resetată deoarece token-ul a expirat",
+    "Couldn't send reset email. Please make sure your username is correct." : "Nu a putut fi trimis un email pentru resetare. Asigură-te că numele de utilizator este corect.",
+    "Could not send reset email because there is no email address for this username. Please contact your administrator." : "Nu a putut fi trimis un email pentru resetare deoarece nu există o adresă email pentru acest utilizator. Contactează-ți administratorul.",
     "%s password reset" : "%s resetare parola",
     "Couldn't send reset email. Please contact your administrator." : "Expedierea email-ului de resetare a eşuat. Vă rugăm să contactaţi administratorul dvs.",
     "Error loading tags" : "Eroare la încărcarea etichetelor",
@@ -115,6 +120,7 @@ OC.L10N.register(
     "So-so password" : "Parolă medie",
     "Good password" : "Parolă bună",
     "Strong password" : "Parolă puternică",
+    "Error occurred while checking server setup" : "A apărut o eroare la verificarea configurației serverului",
     "Shared" : "Partajat",
     "Shared with {recipients}" : "Partajat cu {recipients}",
     "Error" : "Eroare",
@@ -151,6 +157,8 @@ OC.L10N.register(
     "access control" : "control acces",
     "Could not unshare" : "Nu s-a putut elimina partajarea",
     "Share details could not be loaded for this item." : "Nu s-au putut încărca detaliile de partajare pentru acest element.",
+    "No users or groups found for {search}" : "Nu au fost găsiți utilizatori sau grupuri pentru {search}",
+    "No users found for {search}" : "Nu au fost găsiți utilizatori pentru {search}",
     "An error occurred. Please try again" : "A apărut o eroare. Încearcă din nou",
     "Share" : "Partajează",
     "Share with people on other ownClouds using the syntax username@example.com/owncloud" : "Partajează cu persoane din alte instanțe ownCloud folosind sintaxa nume_utilizator@exemplu.com/owncloud",
@@ -175,6 +183,7 @@ OC.L10N.register(
     "sunny" : "însorit",
     "Hello {name}" : "Salut {name}",
     "new" : "nou",
+    "_download %n file_::_download %n files_" : ["descarcă %n fișier","descarcă %n fișiere","descarcă %n fișiere"],
     "The upgrade is in progress, leaving this page might interrupt the process in some environments." : "Actualizarea este în progres, părăsirea acestei pagini ar putea duce la întreruperea procesului în unele medii.",
     "Updating to {version}" : "Actualizare la {version}",
     "An error occurred." : "A apărut o eroare.",
@@ -238,8 +247,12 @@ OC.L10N.register(
     "New password" : "Noua parolă",
     "New Password" : "Noua parolă",
     "Reset password" : "Resetează parola",
+    "This ownCloud instance is currently in single user mode." : "Această instanță ownCloud este momentan în modul de utilizare de către un singur utilizator.",
+    "This means only administrators can use the instance." : "Asta înseamnă că doar administratorii pot folosi instanța.",
+    "Contact your system administrator if this message persists or appeared unexpectedly." : "Contactează-ți administratorul de sistem dacă acest mesaj persistă sau a apărut neașteptat.",
     "Thank you for your patience." : "Îți mulțumim pentru răbdare.",
     "Two-step verification" : "Verificare în doi pași",
+    "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "A fost activată securitatea sporită pentru contul tău. Te rugăm să te autentifici folosind un al doilea factor.",
     "Cancel login" : "Anulează autentificarea",
     "Please authenticate using the selected factor." : "Autentifică-te folosind factorul ales.",
     "An error occured while verifying the token" : "A apărut o eroare la verificarea jetonului",
@@ -254,6 +267,7 @@ OC.L10N.register(
     "Detailed logs" : "Loguri detaliate",
     "Update needed" : "E necesară actualizarea",
     "Please use the command line updater because you have a big instance." : "Folosește actualizarea din linia de comandă deoarece ai o instanță mare.",
-    "This %s instance is currently in maintenance mode, which may take a while." : "Instanța %s este acum în modul de mentenanță, ceea ce ar putea dura o vreme."
+    "This %s instance is currently in maintenance mode, which may take a while." : "Instanța %s este acum în modul de mentenanță, ceea ce ar putea dura o vreme.",
+    "This page will refresh itself when the %s instance is available again." : "Această pagină se va reîmprospăta atunci când %s instance e disponibil din nou."
 },
 "nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));");

+ 15 - 1
core/l10n/ro.json

@@ -7,6 +7,11 @@
     "Invalid image" : "Imagine invalidă",
     "An error occurred. Please contact your admin." : "A apărut o eroare. Te rugăm să contactezi administratorul.",
     "No temporary profile picture available, try again" : "Nu este disponibilă nicio imagine temporară a profilului, încearcă din nou",
+    "Crop is not square" : "Selecția nu este pătrată",
+    "Couldn't reset password because the token is invalid" : "Parola nu a putut fi resetată deoarece token-ul este invalid",
+    "Couldn't reset password because the token is expired" : "Parola nu a putut fi resetată deoarece token-ul a expirat",
+    "Couldn't send reset email. Please make sure your username is correct." : "Nu a putut fi trimis un email pentru resetare. Asigură-te că numele de utilizator este corect.",
+    "Could not send reset email because there is no email address for this username. Please contact your administrator." : "Nu a putut fi trimis un email pentru resetare deoarece nu există o adresă email pentru acest utilizator. Contactează-ți administratorul.",
     "%s password reset" : "%s resetare parola",
     "Couldn't send reset email. Please contact your administrator." : "Expedierea email-ului de resetare a eşuat. Vă rugăm să contactaţi administratorul dvs.",
     "Error loading tags" : "Eroare la încărcarea etichetelor",
@@ -113,6 +118,7 @@
     "So-so password" : "Parolă medie",
     "Good password" : "Parolă bună",
     "Strong password" : "Parolă puternică",
+    "Error occurred while checking server setup" : "A apărut o eroare la verificarea configurației serverului",
     "Shared" : "Partajat",
     "Shared with {recipients}" : "Partajat cu {recipients}",
     "Error" : "Eroare",
@@ -149,6 +155,8 @@
     "access control" : "control acces",
     "Could not unshare" : "Nu s-a putut elimina partajarea",
     "Share details could not be loaded for this item." : "Nu s-au putut încărca detaliile de partajare pentru acest element.",
+    "No users or groups found for {search}" : "Nu au fost găsiți utilizatori sau grupuri pentru {search}",
+    "No users found for {search}" : "Nu au fost găsiți utilizatori pentru {search}",
     "An error occurred. Please try again" : "A apărut o eroare. Încearcă din nou",
     "Share" : "Partajează",
     "Share with people on other ownClouds using the syntax username@example.com/owncloud" : "Partajează cu persoane din alte instanțe ownCloud folosind sintaxa nume_utilizator@exemplu.com/owncloud",
@@ -173,6 +181,7 @@
     "sunny" : "însorit",
     "Hello {name}" : "Salut {name}",
     "new" : "nou",
+    "_download %n file_::_download %n files_" : ["descarcă %n fișier","descarcă %n fișiere","descarcă %n fișiere"],
     "The upgrade is in progress, leaving this page might interrupt the process in some environments." : "Actualizarea este în progres, părăsirea acestei pagini ar putea duce la întreruperea procesului în unele medii.",
     "Updating to {version}" : "Actualizare la {version}",
     "An error occurred." : "A apărut o eroare.",
@@ -236,8 +245,12 @@
     "New password" : "Noua parolă",
     "New Password" : "Noua parolă",
     "Reset password" : "Resetează parola",
+    "This ownCloud instance is currently in single user mode." : "Această instanță ownCloud este momentan în modul de utilizare de către un singur utilizator.",
+    "This means only administrators can use the instance." : "Asta înseamnă că doar administratorii pot folosi instanța.",
+    "Contact your system administrator if this message persists or appeared unexpectedly." : "Contactează-ți administratorul de sistem dacă acest mesaj persistă sau a apărut neașteptat.",
     "Thank you for your patience." : "Îți mulțumim pentru răbdare.",
     "Two-step verification" : "Verificare în doi pași",
+    "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "A fost activată securitatea sporită pentru contul tău. Te rugăm să te autentifici folosind un al doilea factor.",
     "Cancel login" : "Anulează autentificarea",
     "Please authenticate using the selected factor." : "Autentifică-te folosind factorul ales.",
     "An error occured while verifying the token" : "A apărut o eroare la verificarea jetonului",
@@ -252,6 +265,7 @@
     "Detailed logs" : "Loguri detaliate",
     "Update needed" : "E necesară actualizarea",
     "Please use the command line updater because you have a big instance." : "Folosește actualizarea din linia de comandă deoarece ai o instanță mare.",
-    "This %s instance is currently in maintenance mode, which may take a while." : "Instanța %s este acum în modul de mentenanță, ceea ce ar putea dura o vreme."
+    "This %s instance is currently in maintenance mode, which may take a while." : "Instanța %s este acum în modul de mentenanță, ceea ce ar putea dura o vreme.",
+    "This page will refresh itself when the %s instance is available again." : "Această pagină se va reîmprospăta atunci când %s instance e disponibil din nou."
 },"pluralForm" :"nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));"
 }

+ 1 - 0
core/l10n/sv.js

@@ -298,6 +298,7 @@ OC.L10N.register(
     "Thank you for your patience." : "Tack för ditt tålamod.",
     "Two-step verification" : "Tvåfaktorsautentisering",
     "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "Utökad säkerhet har aktiverats på ditt konto. Vänligen autentisera med en andra faktor.",
+    "Cancel login" : "Avbryt inloggning",
     "Please authenticate using the selected factor." : "Vänligen autentisera med vald faktor.",
     "An error occured while verifying the token" : "Ett fel uppstod vid verifiering av token.",
     "You are accessing the server from an untrusted domain." : "Du ansluter till servern från en osäker domän.",

+ 1 - 0
core/l10n/sv.json

@@ -296,6 +296,7 @@
     "Thank you for your patience." : "Tack för ditt tålamod.",
     "Two-step verification" : "Tvåfaktorsautentisering",
     "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "Utökad säkerhet har aktiverats på ditt konto. Vänligen autentisera med en andra faktor.",
+    "Cancel login" : "Avbryt inloggning",
     "Please authenticate using the selected factor." : "Vänligen autentisera med vald faktor.",
     "An error occured while verifying the token" : "Ett fel uppstod vid verifiering av token.",
     "You are accessing the server from an untrusted domain." : "Du ansluter till servern från en osäker domän.",

+ 1 - 0
core/routes.php

@@ -48,6 +48,7 @@ $application->registerRoutes($this, [
 		['name' => 'login#showLoginForm', 'url' => '/login', 'verb' => 'GET'],
 		['name' => 'login#logout', 'url' => '/logout', 'verb' => 'GET'],
 		['name' => 'token#generateToken', 'url' => '/token/generate', 'verb' => 'POST'],
+		['name' => 'occ#execute', 'url' => '/occ/{command}', 'verb' => 'POST'],
 		['name' => 'TwoFactorChallenge#selectChallenge', 'url' => '/login/selectchallenge', 'verb' => 'GET'],
 		['name' => 'TwoFactorChallenge#showChallenge', 'url' => '/login/challenge/{challengeProviderId}', 'verb' => 'GET'],
 		['name' => 'TwoFactorChallenge#solveChallenge', 'url' => '/login/challenge/{challengeProviderId}', 'verb' => 'POST'],

+ 9 - 0
db_structure.xml

@@ -1120,6 +1120,15 @@
 				<length>4</length>
 			</field>
 
+			<field>
+				<name>last_check</name>
+				<type>integer</type>
+				<default>0</default>
+				<notnull>true</notnull>
+				<unsigned>true</unsigned>
+				<length>4</length>
+			</field>
+
 			<index>
 				<name>authtoken_token_index</name>
 				<unique>true</unique>

+ 16 - 3
lib/base.php

@@ -49,6 +49,8 @@
  *
  */
 
+use OCP\IRequest;
+
 require_once 'public/Constants.php';
 
 /**
@@ -271,9 +273,20 @@ class OC {
 		}
 	}
 
-	public static function checkMaintenanceMode() {
+	/**
+	 * Limit maintenance mode access
+	 * @param IRequest $request
+	 */
+	public static function checkMaintenanceMode(IRequest $request) {
+		// Check if requested URL matches 'index.php/occ'
+		$isOccControllerRequested = preg_match('|/index\.php$|', $request->getScriptName()) === 1
+				&& strpos($request->getPathInfo(), '/occ/') === 0;
 		// Allow ajax update script to execute without being stopped
-		if (\OC::$server->getSystemConfig()->getValue('maintenance', false) && OC::$SUBURI != '/core/ajax/update.php') {
+		if (
+			\OC::$server->getSystemConfig()->getValue('maintenance', false)
+			&& OC::$SUBURI != '/core/ajax/update.php'
+			&& !$isOccControllerRequested
+		) {
 			// send http status 503
 			header('HTTP/1.1 503 Service Temporarily Unavailable');
 			header('Status: 503 Service Temporarily Unavailable');
@@ -822,7 +835,7 @@ class OC {
 		$request = \OC::$server->getRequest();
 		$requestPath = $request->getRawPathInfo();
 		if (substr($requestPath, -3) !== '.js') { // we need these files during the upgrade
-			self::checkMaintenanceMode();
+			self::checkMaintenanceMode($request);
 			self::checkUpgrade();
 		}
 

+ 56 - 1
lib/l10n/ast.js

@@ -9,6 +9,7 @@ OC.L10N.register(
     "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Detectose que la configuración d'amuesa copiose. Esto pue encaboxar la instalación y dexala ensín soporte. Llee la documentación enantes de facer cambéos en config.php",
     "PHP %s or higher is required." : "Necesítase PHP %s o superior",
     "PHP with a version lower than %s is required." : "Necesítase una versión PHP anterior a %s",
+    "%sbit or higher PHP required." : "Necesítase PHP %sbit o superior",
     "Following databases are supported: %s" : "Les siguientes bases de datos tan sofitaes: %s",
     "The command line tool %s could not be found" : "La ferramienta línea de comandu %s nun pudo alcontrase",
     "The library %s is not available." : "La librería %s nun ta disponible",
@@ -21,24 +22,37 @@ OC.L10N.register(
     "Invalid image" : "Imaxe inválida",
     "today" : "güei",
     "yesterday" : "ayeri",
+    "_%n day ago_::_%n days ago_" : ["hai %n día","hai %n díes"],
     "last month" : "mes caberu",
     "_%n month ago_::_%n months ago_" : ["hai %n mes","hai %n meses"],
     "last year" : "añu caberu",
+    "_%n year ago_::_%n years ago_" : ["hai %n añu","hai %n años"],
     "_%n hour ago_::_%n hours ago_" : ["hai %n hora","hai %n hores"],
     "_%n minute ago_::_%n minutes ago_" : ["hai %n minutu","hai %n minutos"],
     "seconds ago" : "hai segundos",
+    "Module with id: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Nun esiste'l módulu con id:  %s . Por favor, activalu na configuración d'aplicaciones o contauta col alministrador.",
+    "Empty filename is not allowed" : "Nun s'almite un nome de ficheru baleru",
+    "Dot files are not allowed" : "Ficheros Dot nun s'almiten",
+    "4-byte characters are not supported in file names" : "Caracteres de 4-bytes nun tan soportaos en nomes de ficheros",
+    "File name is a reserved word" : "El nome de ficheru ye una pallabra reservada",
     "File name contains at least one invalid character" : "El nome del ficheru contién polo menos un carácter non válidu",
+    "File name is too long" : "El nome de ficheru ye demasiáu llargu",
     "App directory already exists" : "El direutoriu de l'aplicación yá esiste",
     "Can't create app folder. Please fix permissions. %s" : "Nun pue crease la carpeta de l'aplicación. Por favor, igua los permisos. %s",
+    "Archive does not contain a directory named %s" : "L'archivu nun contien un directoriu nomáu %s",
     "No source specified when installing app" : "Nun s'especificó nenguna fonte al instalar app",
     "No href specified when installing app from http" : "Nun s'especificó href al instalar la app dende http",
     "No path specified when installing app from local file" : "Nun s'especificó camín dende un ficheru llocal al instalar l'aplicación",
     "Archives of type %s are not supported" : "Los ficheros de triba %s nun tán sofitaos",
     "Failed to open archive when installing app" : "Falló al abrir el ficheru al instalar l'aplicación",
     "App does not provide an info.xml file" : "L'aplicación nun apurre un ficheru info.xml",
+    "App cannot be installed because appinfo file cannot be read." : "L'aplicación nun puede instalase porque nun se llee'l ficheru appinfo.",
+    "Signature could not get checked. Please contact the app developer and check your admin screen." : "La firma nun puede ser evaluada . Por favor, póngase en contactu col desarrollador de l'aplicación y comprueba la so pantalla d'alministración .",
     "App can't be installed because of not allowed code in the App" : "Nun pue instalase l'aplicación por causa d'un códigu non permitíu na App",
     "App can't be installed because it is not compatible with this version of ownCloud" : "Nun pue instalase l'aplicación porque nun ye compatible con esta versión d'ownCloud.",
     "App can't be installed because it contains the <shipped>true</shipped> tag which is not allowed for non shipped apps" : "L'aplicación nun pue instalase porque contién la etiqueta <shipped>true</shipped> que nun ta permitida p'aplicaciones non distribuyíes",
+    "App can't be installed because the version in info.xml is not the same as the version reported from the app store" : "L'aplicación nun puede instalase porque la versión en info.xml nun ye la mesma que la versión informada dende la tienda d'aplicaciones",
+    "%s enter the database username and name." : "%s introducir el nome d'usuariu y el nome de la base de datos .",
     "%s enter the database username." : "%s introducir l'usuariu de la base de datos.",
     "%s enter the database name." : "%s introducir nome de la base de datos.",
     "%s you may not use dots in the database name" : "%s nun pues usar puntos nel nome de la base de datos",
@@ -51,57 +65,92 @@ OC.L10N.register(
     "PostgreSQL username and/or password not valid" : "Nome d'usuariu o contraseña PostgreSQL non válidos",
     "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X nun ta sofitáu y %s nun furrulará afayadizamente nesta plataforma. ¡Úsalu baxo'l to riesgu!",
     "For the best results, please consider using a GNU/Linux server instead." : "Pa los meyores resultaos, por favor considera l'usu d'un sirvidor GNU/Linux nel so llugar.",
+    "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Paez ser que la instancia %s ta executándose nun entornu de PHP 32 bits y el open_basedir configuróse en php.ini. Esto va dar llugar a problemes colos ficheros de más de 4 GB y nun ye nada recomendable.",
+    "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor, desanicia la configuración open_basedir dientro la so php.ini o camude a PHP 64 bits.",
     "Set an admin username." : "Afitar nome d'usuariu p'almin",
     "Set an admin password." : "Afitar contraseña p'almin",
     "Can't create or write into the data directory %s" : "Nun pue crease o escribir dientro los datos del direutoriu %s",
+    "Invalid Federated Cloud ID" : "Inválidu ID de Ñube Federada",
     "%s shared »%s« with you" : "%s compartió »%s« contigo",
+    "%s via %s" : "%s via %s",
+    "Sharing %s failed, because the backend does not allow shares from type %i" : "Compartir %s falló, por cuenta qu'el backend nun dexa acciones de tipu %i",
     "Sharing %s failed, because the file does not exist" : "Compartir %s falló, porque'l ficheru nun esiste",
     "You are not allowed to share %s" : "Nun tienes permisu pa compartir %s",
+    "Sharing %s failed, because you can not share with yourself" : "Compartir %s falló, porque nun puede compartise contigo mesmu",
     "Sharing %s failed, because the user %s does not exist" : "Compartir %s falló, yá que l'usuariu %s nun esiste",
     "Sharing %s failed, because the user %s is not a member of any groups that %s is a member of" : "Compartir %s falló, yá que l'usuariu %s nun ye miembru de nengún de los grupos de los que ye miembru %s",
     "Sharing %s failed, because this item is already shared with %s" : "Compartir %s falló, porque esti elementu yá ta compartiéndose con %s",
+    "Sharing %s failed, because this item is already shared with user %s" : "Compartir %s falló, porque esti elementu yá ta compartiéndose col usuariu %s",
     "Sharing %s failed, because the group %s does not exist" : "Compartir %s falló, porque'l grupu %s nun esiste",
     "Sharing %s failed, because %s is not a member of the group %s" : "Compartir %s falló, porque %s nun ye miembru del grupu %s",
     "You need to provide a password to create a public link, only protected links are allowed" : "Necesites apurrir una contraseña pa crear un enllaz públicu, namái tan permitíos los enllaces protexíos",
     "Sharing %s failed, because sharing with links is not allowed" : "Compartir %s falló, porque nun se permite compartir con enllaces",
+    "Not allowed to create a federated share with the same user" : "Nun s'almite crear un recursu compartíu federáu col mesmu usuariu",
+    "Sharing %s failed, could not find %s, maybe the server is currently unreachable." : "Compartir %s falló, nun pudo atopase %s, pue qu'el servidor nun seya anguaño algamable.",
     "Share type %s is not valid for %s" : "La triba de compartición %s nun ye válida pa %s",
     "Setting permissions for %s failed, because the permissions exceed permissions granted to %s" : "Falló dar permisos a %s, porque los permisos son mayores que los otorgaos a %s",
     "Setting permissions for %s failed, because the item was not found" : "Falló dar permisos a %s, porque l'elementu nun s'atopó",
     "Cannot set expiration date. Shares cannot expire later than %s after they have been shared" : "Nun pue afitase la data de caducidá. Ficheros compartíos nun puen caducar dempués de %s de compartise",
     "Cannot set expiration date. Expiration date is in the past" : "Nun pue afitase la data d'espiración. La data d'espiración ta nel pasáu",
+    "Cannot clear expiration date. Shares are required to have an expiration date." : "Non puede desaniciar la fecha de caducidá. Compartir obliga a tener una fecha de caducidá.",
     "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El motor compartíu %s tien d'implementar la interfaz OCP\\Share_Backend",
     "Sharing backend %s not found" : "Nun s'alcontró'l botón de compartición %s",
     "Sharing backend for %s not found" : "Nun s'alcontró'l botón de partición pa %s",
+    "Sharing failed, because the user %s is the original sharer" : "Compartir falló, porque l'usuariu %s ye'l compartidor orixinal",
     "Sharing %s failed, because the permissions exceed permissions granted to %s" : "Compartir %s falló, porque los permisos perpasen los otorgaos a %s",
     "Sharing %s failed, because resharing is not allowed" : "Compartir %s falló, porque nun se permite la re-compartición",
     "Sharing %s failed, because the sharing backend for %s could not find its source" : "Compartir %s falló porque'l motor compartíu pa %s podría nun atopar el so orixe",
     "Sharing %s failed, because the file could not be found in the file cache" : "Compartir %s falló, yá que'l ficheru nun pudo atopase na caché de ficheru",
+    "Cannot increase permissions of %s" : "Nun se pueden aumentar los permisos de %s",
+    "Files can't be shared with delete permissions" : "Los ficheros nun pueden compartise con permisos desaniciaos",
+    "Files can't be shared with create permissions" : "Los ficheros nun pueden compartise con crear permisos",
+    "Expiration date is in the past" : "La data de caducidá ta nel pasáu.",
+    "Cannot set expiration date more than %s days in the future" : "Nun pue afitase la data d'espiración más que  %s díes nel futuru",
     "Could not find category \"%s\"" : "Nun pudo alcontrase la estaya \"%s.\"",
     "Apps" : "Aplicaciones",
+    "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Namái tan permitíos los siguientes caráuteres nun nome d'usuariu: \"a-z\", \"A-Z\", \"0-9\", y \"_.@-'\"",
     "A valid username must be provided" : "Tien d'apurrise un nome d'usuariu válidu",
+    "Username contains whitespace at the beginning or at the end" : "El nome d'usuario contién espacios en blancu al entamu o al final",
     "A valid password must be provided" : "Tien d'apurrise una contraseña válida",
     "The username is already being used" : "El nome d'usuariu yá ta usándose",
+    "Login canceled by app" : "Aniciar sesión canceláu pola aplicación",
+    "User disabled" : "Usuariu desactiváu",
     "Help" : "Ayuda",
     "Personal" : "Personal",
     "Users" : "Usuarios",
     "Admin" : "Almin",
     "Recommended" : "Recomendáu",
+    "App \"%s\" cannot be installed because appinfo file cannot be read." : "L'aplicación \"%s\" nun puede instalase porque nun se llee'l ficheru appinfo.",
+    "App \"%s\" cannot be installed because it is not compatible with this version of ownCloud." : "L'aplicación \"%s\" nun puede instalase porque nun ye compatible con esta versión d'ownCloud.",
+    "App \"%s\" cannot be installed because the following dependencies are not fulfilled: %s" : "L'aplicación \"%s\" nun puede instalase porque les siguientes dependencies nun se cumplen: %s",
     "No app name specified" : "Nun s'especificó nome de l'aplicación",
     "web services under your control" : "servicios web baxo'l to control",
+    "File is currently busy, please try again later" : "Fichaeru ta ocupáu, por favor intentelo de nuevu más tarde",
+    "Can't read file" : "Nun ye a lleese'l ficheru",
     "Application is not enabled" : "L'aplicación nun ta habilitada",
     "Authentication error" : "Fallu d'autenticación",
     "Token expired. Please reload page." : "Token caducáu. Recarga la páxina.",
     "Unknown user" : "Usuariu desconocíu",
     "No database drivers (sqlite, mysql, or postgresql) installed." : "Nun hai controladores de bases de datos  (sqlite, mysql, o postgresql)",
+    "Microsoft Windows Platform is not supported" : "Microsoft Windows Platform nun ta soportáu",
+    "Running ownCloud Server on the Microsoft Windows platform is not supported. We suggest you use a Linux server in a virtual machine if you have no option for migrating the server itself. Find Linux packages as well as easy to deploy virtual machine images on <a href=\"%s\">%s</a>. For migrating existing installations to Linux you can find some tips and a migration script in <a href=\"%s\">our documentation</a>." : "Nun s'almite la execución del Sirvidor d'ownCloud na plataforma Microsoft Windows. Suxurímoste qu'utilices un servidor Linux nuna máquina virtual si nun ties nenguna opción pa migrar el mesmu servidor. Atopa paquetes de Linux, fáciles d'implementar imaxes de máquines virtuales en <a href=\"%s\">%s</a>. Pa la migración de les instalaciones esistentes pa Linux puedes atopar dellos conseyos y un script de migración en  <a href=\"%s\">nuesa documentación </a>.",
     "Cannot write into \"config\" directory" : "Nun pue escribise nel direutoriu \"config\"",
     "Cannot write into \"apps\" directory" : "Nun pue escribise nel direutoriu \"apps\"",
     "This can usually be fixed by %sgiving the webserver write access to the apps directory%s or disabling the appstore in the config file." : "Esto pue iguase %sdando permisos d'escritura al sirvidor Web nel direutoriu%s d'apps o deshabilitando la tienda d'apps nel ficheru de configuración.",
     "Cannot create \"data\" directory (%s)" : "Nun pue crease'l direutoriu \"data\" (%s)",
+    "This can usually be fixed by <a href=\"%s\" target=\"_blank\" rel=\"noreferrer\">giving the webserver write access to the root directory</a>." : "Esto pue iguase davezu <a href=\"%s\" target=\"_blank\" rel=\"noreferrer\">dándo-y accesu d'escritura al direutoriu raigañu</a>.",
     "Permissions can usually be fixed by %sgiving the webserver write access to the root directory%s." : "Davezu los permisos puen iguase %sdándo-y al sirvidor web accesu d'escritura al direutoriu raigañu%s.",
     "Setting locale to %s failed" : "Falló l'activación del idioma %s",
     "Please install one of these locales on your system and restart your webserver." : "Instala ún d'estos locales nel to sistema y reanicia'l sirvidor web",
     "Please ask your server administrator to install the module." : "Por favor, entrúga-y al to alministrador del sirvidor pa instalar el módulu.",
     "PHP module %s not installed." : "Nun ta instaláu'l módulu PHP %s",
+    "PHP setting \"%s\" is not set to \"%s\"." : "La configuración de PHP \"%s\" nun s'afita \"%s\".",
+    "Adjusting this setting in php.ini will make ownCloud run again" : "Axuste de la configuración en php.ini va executar de nueves ownCloud",
+    "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload afita \"%s\" en llugar del valor esperáu \"0\"",
+    "To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini" : "Pa solucionar esti problema definíu <code>mbstring.func_overload</code>a <code>0</code> nel so php.ini",
+    "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 ríquese siquier. Anguaño ta instaláu  %s.",
+    "To fix this issue update your libxml2 version and restart your web server." : "Pa solucionar esti problema actualiza latso versión de libxml2 y reanicia'l to sirvidor web.",
+    "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP ta aparentemente configuráu pa desaniciar bloques de documentos en llinia. Esto va facer que delles aplicaciones principales nun tean accesibles.",
     "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Dablemente esto seya culpa d'un caché o acelerador, como por exemplu Zend OPcache o eAccelerator.",
     "PHP modules have been installed, but they are still listed as missing?" : "Instaláronse los módulos PHP, ¿pero tán entá llistaos como faltantes?",
     "Please ask your server administrator to restart the web server." : "Por favor, entruga al to alministrador pa reaniciar el sirvidor web.",
@@ -109,9 +158,15 @@ OC.L10N.register(
     "Please upgrade your database version" : "Por favor, anueva la versión de la to base de datos",
     "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor, camuda los permisos a 0770 pa que'l direutoriu nun pueda llistase por otros usuarios.",
     "Data directory (%s) is readable by other users" : "El direutoriu de datos (%s) ye llexible por otros usuarios",
+    "Data directory (%s) must be an absolute path" : "El directoriu de datos (%s) ha de ser una ruta absoluta",
+    "Check the value of \"datadirectory\" in your configuration" : "Comprobar el valor del \"datadirectory\" na so configuración",
     "Data directory (%s) is invalid" : "Ye inválidu'l direutoriu de datos (%s)",
     "Please check that the data directory contains a file \".ocdata\" in its root." : "Verifica que'l direutoriu de datos contién un ficheru \".ocdata\" nel direutoriu raigañu.",
     "Could not obtain lock type %d on \"%s\"." : "Nun pudo facese'l bloquéu %d en \"%s\".",
-    "Storage not available" : "Almacenamientu non disponible"
+    "Storage unauthorized. %s" : "Almacenamientu desautorizáu. %s",
+    "Storage incomplete configuration. %s" : "Configuración d'almacenamientu incompleta. %s",
+    "Storage connection error. %s" : "Fallu de conexón al almacenamientu. %s",
+    "Storage not available" : "Almacenamientu non disponible",
+    "Storage connection timeout. %s" : "Tiempu escosao de conexón al almacenamientu. %s"
 },
 "nplurals=2; plural=(n != 1);");

+ 56 - 1
lib/l10n/ast.json

@@ -7,6 +7,7 @@
     "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Detectose que la configuración d'amuesa copiose. Esto pue encaboxar la instalación y dexala ensín soporte. Llee la documentación enantes de facer cambéos en config.php",
     "PHP %s or higher is required." : "Necesítase PHP %s o superior",
     "PHP with a version lower than %s is required." : "Necesítase una versión PHP anterior a %s",
+    "%sbit or higher PHP required." : "Necesítase PHP %sbit o superior",
     "Following databases are supported: %s" : "Les siguientes bases de datos tan sofitaes: %s",
     "The command line tool %s could not be found" : "La ferramienta línea de comandu %s nun pudo alcontrase",
     "The library %s is not available." : "La librería %s nun ta disponible",
@@ -19,24 +20,37 @@
     "Invalid image" : "Imaxe inválida",
     "today" : "güei",
     "yesterday" : "ayeri",
+    "_%n day ago_::_%n days ago_" : ["hai %n día","hai %n díes"],
     "last month" : "mes caberu",
     "_%n month ago_::_%n months ago_" : ["hai %n mes","hai %n meses"],
     "last year" : "añu caberu",
+    "_%n year ago_::_%n years ago_" : ["hai %n añu","hai %n años"],
     "_%n hour ago_::_%n hours ago_" : ["hai %n hora","hai %n hores"],
     "_%n minute ago_::_%n minutes ago_" : ["hai %n minutu","hai %n minutos"],
     "seconds ago" : "hai segundos",
+    "Module with id: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Nun esiste'l módulu con id:  %s . Por favor, activalu na configuración d'aplicaciones o contauta col alministrador.",
+    "Empty filename is not allowed" : "Nun s'almite un nome de ficheru baleru",
+    "Dot files are not allowed" : "Ficheros Dot nun s'almiten",
+    "4-byte characters are not supported in file names" : "Caracteres de 4-bytes nun tan soportaos en nomes de ficheros",
+    "File name is a reserved word" : "El nome de ficheru ye una pallabra reservada",
     "File name contains at least one invalid character" : "El nome del ficheru contién polo menos un carácter non válidu",
+    "File name is too long" : "El nome de ficheru ye demasiáu llargu",
     "App directory already exists" : "El direutoriu de l'aplicación yá esiste",
     "Can't create app folder. Please fix permissions. %s" : "Nun pue crease la carpeta de l'aplicación. Por favor, igua los permisos. %s",
+    "Archive does not contain a directory named %s" : "L'archivu nun contien un directoriu nomáu %s",
     "No source specified when installing app" : "Nun s'especificó nenguna fonte al instalar app",
     "No href specified when installing app from http" : "Nun s'especificó href al instalar la app dende http",
     "No path specified when installing app from local file" : "Nun s'especificó camín dende un ficheru llocal al instalar l'aplicación",
     "Archives of type %s are not supported" : "Los ficheros de triba %s nun tán sofitaos",
     "Failed to open archive when installing app" : "Falló al abrir el ficheru al instalar l'aplicación",
     "App does not provide an info.xml file" : "L'aplicación nun apurre un ficheru info.xml",
+    "App cannot be installed because appinfo file cannot be read." : "L'aplicación nun puede instalase porque nun se llee'l ficheru appinfo.",
+    "Signature could not get checked. Please contact the app developer and check your admin screen." : "La firma nun puede ser evaluada . Por favor, póngase en contactu col desarrollador de l'aplicación y comprueba la so pantalla d'alministración .",
     "App can't be installed because of not allowed code in the App" : "Nun pue instalase l'aplicación por causa d'un códigu non permitíu na App",
     "App can't be installed because it is not compatible with this version of ownCloud" : "Nun pue instalase l'aplicación porque nun ye compatible con esta versión d'ownCloud.",
     "App can't be installed because it contains the <shipped>true</shipped> tag which is not allowed for non shipped apps" : "L'aplicación nun pue instalase porque contién la etiqueta <shipped>true</shipped> que nun ta permitida p'aplicaciones non distribuyíes",
+    "App can't be installed because the version in info.xml is not the same as the version reported from the app store" : "L'aplicación nun puede instalase porque la versión en info.xml nun ye la mesma que la versión informada dende la tienda d'aplicaciones",
+    "%s enter the database username and name." : "%s introducir el nome d'usuariu y el nome de la base de datos .",
     "%s enter the database username." : "%s introducir l'usuariu de la base de datos.",
     "%s enter the database name." : "%s introducir nome de la base de datos.",
     "%s you may not use dots in the database name" : "%s nun pues usar puntos nel nome de la base de datos",
@@ -49,57 +63,92 @@
     "PostgreSQL username and/or password not valid" : "Nome d'usuariu o contraseña PostgreSQL non válidos",
     "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X nun ta sofitáu y %s nun furrulará afayadizamente nesta plataforma. ¡Úsalu baxo'l to riesgu!",
     "For the best results, please consider using a GNU/Linux server instead." : "Pa los meyores resultaos, por favor considera l'usu d'un sirvidor GNU/Linux nel so llugar.",
+    "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Paez ser que la instancia %s ta executándose nun entornu de PHP 32 bits y el open_basedir configuróse en php.ini. Esto va dar llugar a problemes colos ficheros de más de 4 GB y nun ye nada recomendable.",
+    "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Por favor, desanicia la configuración open_basedir dientro la so php.ini o camude a PHP 64 bits.",
     "Set an admin username." : "Afitar nome d'usuariu p'almin",
     "Set an admin password." : "Afitar contraseña p'almin",
     "Can't create or write into the data directory %s" : "Nun pue crease o escribir dientro los datos del direutoriu %s",
+    "Invalid Federated Cloud ID" : "Inválidu ID de Ñube Federada",
     "%s shared »%s« with you" : "%s compartió »%s« contigo",
+    "%s via %s" : "%s via %s",
+    "Sharing %s failed, because the backend does not allow shares from type %i" : "Compartir %s falló, por cuenta qu'el backend nun dexa acciones de tipu %i",
     "Sharing %s failed, because the file does not exist" : "Compartir %s falló, porque'l ficheru nun esiste",
     "You are not allowed to share %s" : "Nun tienes permisu pa compartir %s",
+    "Sharing %s failed, because you can not share with yourself" : "Compartir %s falló, porque nun puede compartise contigo mesmu",
     "Sharing %s failed, because the user %s does not exist" : "Compartir %s falló, yá que l'usuariu %s nun esiste",
     "Sharing %s failed, because the user %s is not a member of any groups that %s is a member of" : "Compartir %s falló, yá que l'usuariu %s nun ye miembru de nengún de los grupos de los que ye miembru %s",
     "Sharing %s failed, because this item is already shared with %s" : "Compartir %s falló, porque esti elementu yá ta compartiéndose con %s",
+    "Sharing %s failed, because this item is already shared with user %s" : "Compartir %s falló, porque esti elementu yá ta compartiéndose col usuariu %s",
     "Sharing %s failed, because the group %s does not exist" : "Compartir %s falló, porque'l grupu %s nun esiste",
     "Sharing %s failed, because %s is not a member of the group %s" : "Compartir %s falló, porque %s nun ye miembru del grupu %s",
     "You need to provide a password to create a public link, only protected links are allowed" : "Necesites apurrir una contraseña pa crear un enllaz públicu, namái tan permitíos los enllaces protexíos",
     "Sharing %s failed, because sharing with links is not allowed" : "Compartir %s falló, porque nun se permite compartir con enllaces",
+    "Not allowed to create a federated share with the same user" : "Nun s'almite crear un recursu compartíu federáu col mesmu usuariu",
+    "Sharing %s failed, could not find %s, maybe the server is currently unreachable." : "Compartir %s falló, nun pudo atopase %s, pue qu'el servidor nun seya anguaño algamable.",
     "Share type %s is not valid for %s" : "La triba de compartición %s nun ye válida pa %s",
     "Setting permissions for %s failed, because the permissions exceed permissions granted to %s" : "Falló dar permisos a %s, porque los permisos son mayores que los otorgaos a %s",
     "Setting permissions for %s failed, because the item was not found" : "Falló dar permisos a %s, porque l'elementu nun s'atopó",
     "Cannot set expiration date. Shares cannot expire later than %s after they have been shared" : "Nun pue afitase la data de caducidá. Ficheros compartíos nun puen caducar dempués de %s de compartise",
     "Cannot set expiration date. Expiration date is in the past" : "Nun pue afitase la data d'espiración. La data d'espiración ta nel pasáu",
+    "Cannot clear expiration date. Shares are required to have an expiration date." : "Non puede desaniciar la fecha de caducidá. Compartir obliga a tener una fecha de caducidá.",
     "Sharing backend %s must implement the interface OCP\\Share_Backend" : "El motor compartíu %s tien d'implementar la interfaz OCP\\Share_Backend",
     "Sharing backend %s not found" : "Nun s'alcontró'l botón de compartición %s",
     "Sharing backend for %s not found" : "Nun s'alcontró'l botón de partición pa %s",
+    "Sharing failed, because the user %s is the original sharer" : "Compartir falló, porque l'usuariu %s ye'l compartidor orixinal",
     "Sharing %s failed, because the permissions exceed permissions granted to %s" : "Compartir %s falló, porque los permisos perpasen los otorgaos a %s",
     "Sharing %s failed, because resharing is not allowed" : "Compartir %s falló, porque nun se permite la re-compartición",
     "Sharing %s failed, because the sharing backend for %s could not find its source" : "Compartir %s falló porque'l motor compartíu pa %s podría nun atopar el so orixe",
     "Sharing %s failed, because the file could not be found in the file cache" : "Compartir %s falló, yá que'l ficheru nun pudo atopase na caché de ficheru",
+    "Cannot increase permissions of %s" : "Nun se pueden aumentar los permisos de %s",
+    "Files can't be shared with delete permissions" : "Los ficheros nun pueden compartise con permisos desaniciaos",
+    "Files can't be shared with create permissions" : "Los ficheros nun pueden compartise con crear permisos",
+    "Expiration date is in the past" : "La data de caducidá ta nel pasáu.",
+    "Cannot set expiration date more than %s days in the future" : "Nun pue afitase la data d'espiración más que  %s díes nel futuru",
     "Could not find category \"%s\"" : "Nun pudo alcontrase la estaya \"%s.\"",
     "Apps" : "Aplicaciones",
+    "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Namái tan permitíos los siguientes caráuteres nun nome d'usuariu: \"a-z\", \"A-Z\", \"0-9\", y \"_.@-'\"",
     "A valid username must be provided" : "Tien d'apurrise un nome d'usuariu válidu",
+    "Username contains whitespace at the beginning or at the end" : "El nome d'usuario contién espacios en blancu al entamu o al final",
     "A valid password must be provided" : "Tien d'apurrise una contraseña válida",
     "The username is already being used" : "El nome d'usuariu yá ta usándose",
+    "Login canceled by app" : "Aniciar sesión canceláu pola aplicación",
+    "User disabled" : "Usuariu desactiváu",
     "Help" : "Ayuda",
     "Personal" : "Personal",
     "Users" : "Usuarios",
     "Admin" : "Almin",
     "Recommended" : "Recomendáu",
+    "App \"%s\" cannot be installed because appinfo file cannot be read." : "L'aplicación \"%s\" nun puede instalase porque nun se llee'l ficheru appinfo.",
+    "App \"%s\" cannot be installed because it is not compatible with this version of ownCloud." : "L'aplicación \"%s\" nun puede instalase porque nun ye compatible con esta versión d'ownCloud.",
+    "App \"%s\" cannot be installed because the following dependencies are not fulfilled: %s" : "L'aplicación \"%s\" nun puede instalase porque les siguientes dependencies nun se cumplen: %s",
     "No app name specified" : "Nun s'especificó nome de l'aplicación",
     "web services under your control" : "servicios web baxo'l to control",
+    "File is currently busy, please try again later" : "Fichaeru ta ocupáu, por favor intentelo de nuevu más tarde",
+    "Can't read file" : "Nun ye a lleese'l ficheru",
     "Application is not enabled" : "L'aplicación nun ta habilitada",
     "Authentication error" : "Fallu d'autenticación",
     "Token expired. Please reload page." : "Token caducáu. Recarga la páxina.",
     "Unknown user" : "Usuariu desconocíu",
     "No database drivers (sqlite, mysql, or postgresql) installed." : "Nun hai controladores de bases de datos  (sqlite, mysql, o postgresql)",
+    "Microsoft Windows Platform is not supported" : "Microsoft Windows Platform nun ta soportáu",
+    "Running ownCloud Server on the Microsoft Windows platform is not supported. We suggest you use a Linux server in a virtual machine if you have no option for migrating the server itself. Find Linux packages as well as easy to deploy virtual machine images on <a href=\"%s\">%s</a>. For migrating existing installations to Linux you can find some tips and a migration script in <a href=\"%s\">our documentation</a>." : "Nun s'almite la execución del Sirvidor d'ownCloud na plataforma Microsoft Windows. Suxurímoste qu'utilices un servidor Linux nuna máquina virtual si nun ties nenguna opción pa migrar el mesmu servidor. Atopa paquetes de Linux, fáciles d'implementar imaxes de máquines virtuales en <a href=\"%s\">%s</a>. Pa la migración de les instalaciones esistentes pa Linux puedes atopar dellos conseyos y un script de migración en  <a href=\"%s\">nuesa documentación </a>.",
     "Cannot write into \"config\" directory" : "Nun pue escribise nel direutoriu \"config\"",
     "Cannot write into \"apps\" directory" : "Nun pue escribise nel direutoriu \"apps\"",
     "This can usually be fixed by %sgiving the webserver write access to the apps directory%s or disabling the appstore in the config file." : "Esto pue iguase %sdando permisos d'escritura al sirvidor Web nel direutoriu%s d'apps o deshabilitando la tienda d'apps nel ficheru de configuración.",
     "Cannot create \"data\" directory (%s)" : "Nun pue crease'l direutoriu \"data\" (%s)",
+    "This can usually be fixed by <a href=\"%s\" target=\"_blank\" rel=\"noreferrer\">giving the webserver write access to the root directory</a>." : "Esto pue iguase davezu <a href=\"%s\" target=\"_blank\" rel=\"noreferrer\">dándo-y accesu d'escritura al direutoriu raigañu</a>.",
     "Permissions can usually be fixed by %sgiving the webserver write access to the root directory%s." : "Davezu los permisos puen iguase %sdándo-y al sirvidor web accesu d'escritura al direutoriu raigañu%s.",
     "Setting locale to %s failed" : "Falló l'activación del idioma %s",
     "Please install one of these locales on your system and restart your webserver." : "Instala ún d'estos locales nel to sistema y reanicia'l sirvidor web",
     "Please ask your server administrator to install the module." : "Por favor, entrúga-y al to alministrador del sirvidor pa instalar el módulu.",
     "PHP module %s not installed." : "Nun ta instaláu'l módulu PHP %s",
+    "PHP setting \"%s\" is not set to \"%s\"." : "La configuración de PHP \"%s\" nun s'afita \"%s\".",
+    "Adjusting this setting in php.ini will make ownCloud run again" : "Axuste de la configuración en php.ini va executar de nueves ownCloud",
+    "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload afita \"%s\" en llugar del valor esperáu \"0\"",
+    "To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini" : "Pa solucionar esti problema definíu <code>mbstring.func_overload</code>a <code>0</code> nel so php.ini",
+    "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 ríquese siquier. Anguaño ta instaláu  %s.",
+    "To fix this issue update your libxml2 version and restart your web server." : "Pa solucionar esti problema actualiza latso versión de libxml2 y reanicia'l to sirvidor web.",
+    "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP ta aparentemente configuráu pa desaniciar bloques de documentos en llinia. Esto va facer que delles aplicaciones principales nun tean accesibles.",
     "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Dablemente esto seya culpa d'un caché o acelerador, como por exemplu Zend OPcache o eAccelerator.",
     "PHP modules have been installed, but they are still listed as missing?" : "Instaláronse los módulos PHP, ¿pero tán entá llistaos como faltantes?",
     "Please ask your server administrator to restart the web server." : "Por favor, entruga al to alministrador pa reaniciar el sirvidor web.",
@@ -107,9 +156,15 @@
     "Please upgrade your database version" : "Por favor, anueva la versión de la to base de datos",
     "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor, camuda los permisos a 0770 pa que'l direutoriu nun pueda llistase por otros usuarios.",
     "Data directory (%s) is readable by other users" : "El direutoriu de datos (%s) ye llexible por otros usuarios",
+    "Data directory (%s) must be an absolute path" : "El directoriu de datos (%s) ha de ser una ruta absoluta",
+    "Check the value of \"datadirectory\" in your configuration" : "Comprobar el valor del \"datadirectory\" na so configuración",
     "Data directory (%s) is invalid" : "Ye inválidu'l direutoriu de datos (%s)",
     "Please check that the data directory contains a file \".ocdata\" in its root." : "Verifica que'l direutoriu de datos contién un ficheru \".ocdata\" nel direutoriu raigañu.",
     "Could not obtain lock type %d on \"%s\"." : "Nun pudo facese'l bloquéu %d en \"%s\".",
-    "Storage not available" : "Almacenamientu non disponible"
+    "Storage unauthorized. %s" : "Almacenamientu desautorizáu. %s",
+    "Storage incomplete configuration. %s" : "Configuración d'almacenamientu incompleta. %s",
+    "Storage connection error. %s" : "Fallu de conexón al almacenamientu. %s",
+    "Storage not available" : "Almacenamientu non disponible",
+    "Storage connection timeout. %s" : "Tiempu escosao de conexón al almacenamientu. %s"
 },"pluralForm" :"nplurals=2; plural=(n != 1);"
 }

+ 23 - 0
lib/private/Authentication/Token/DefaultToken.php

@@ -74,6 +74,11 @@ class DefaultToken extends Entity implements IToken {
 	 */
 	protected $lastActivity;
 
+	/**
+	 * @var int
+	 */
+	protected $lastCheck;
+
 	public function getId() {
 		return $this->id;
 	}
@@ -109,4 +114,22 @@ class DefaultToken extends Entity implements IToken {
 		];
 	}
 
+	/**
+	 * Get the timestamp of the last password check
+	 *
+	 * @return int
+	 */
+	public function getLastCheck() {
+		return parent::getLastCheck();
+	}
+
+	/**
+	 * Get the timestamp of the last password check
+	 *
+	 * @param int $time
+	 */
+	public function setLastCheck($time) {
+		return parent::setLastCheck($time);
+	}
+
 }

+ 2 - 2
lib/private/Authentication/Token/DefaultTokenMapper.php

@@ -70,7 +70,7 @@ class DefaultTokenMapper extends Mapper {
 	public function getToken($token) {
 		/* @var $qb IQueryBuilder */
 		$qb = $this->db->getQueryBuilder();
-		$result = $qb->select('id', 'uid', 'login_name', 'password', 'name', 'type', 'token', 'last_activity')
+		$result = $qb->select('id', 'uid', 'login_name', 'password', 'name', 'type', 'token', 'last_activity', 'last_check')
 			->from('authtoken')
 			->where($qb->expr()->eq('token', $qb->createParameter('token')))
 			->setParameter('token', $token)
@@ -96,7 +96,7 @@ class DefaultTokenMapper extends Mapper {
 	public function getTokenByUser(IUser $user) {
 		/* @var $qb IQueryBuilder */
 		$qb = $this->db->getQueryBuilder();
-		$qb->select('id', 'uid', 'login_name', 'password', 'name', 'type', 'token', 'last_activity')
+		$qb->select('id', 'uid', 'login_name', 'password', 'name', 'type', 'token', 'last_activity', 'last_check')
 			->from('authtoken')
 			->where($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID())))
 			->setMaxResults(1000);

+ 36 - 19
lib/private/Authentication/Token/DefaultTokenProvider.php

@@ -91,20 +91,35 @@ class DefaultTokenProvider implements IProvider {
 		return $dbToken;
 	}
 
+	/**
+	 * Save the updated token
+	 *
+	 * @param IToken $token
+	 */
+	public function updateToken(IToken $token) {
+		if (!($token instanceof DefaultToken)) {
+			throw new InvalidTokenException();
+		}
+		$this->mapper->update($token);
+	}
+
 	/**
 	 * Update token activity timestamp
 	 *
 	 * @throws InvalidTokenException
 	 * @param IToken $token
 	 */
-	public function updateToken(IToken $token) {
+	public function updateTokenActivity(IToken $token) {
 		if (!($token instanceof DefaultToken)) {
 			throw new InvalidTokenException();
 		}
 		/** @var DefaultToken $token */
-		$token->setLastActivity($this->time->getTime());
-
-		$this->mapper->update($token);
+		$now = $this->time->getTime();
+		if ($token->getLastActivity() < ($now - 60)) {
+			// Update token only once per minute
+			$token->setLastActivity($now);
+			$this->mapper->update($token);
+		}
 	}
 
 	/**
@@ -150,6 +165,23 @@ class DefaultTokenProvider implements IProvider {
 		return $this->decryptPassword($password, $tokenId);
 	}
 
+	/**
+	 * Encrypt and set the password of the given token
+	 *
+	 * @param IToken $token
+	 * @param string $tokenId
+	 * @param string $password
+	 * @throws InvalidTokenException
+	 */
+	public function setPassword(IToken $token, $tokenId, $password) {
+		if (!($token instanceof DefaultToken)) {
+			throw new InvalidTokenException();
+		}
+		/** @var DefaultToken $token */
+		$token->setPassword($this->encryptPassword($password, $tokenId));
+		$this->mapper->update($token);
+	}
+
 	/**
 	 * Invalidate (delete) the given session token
 	 *
@@ -178,21 +210,6 @@ class DefaultTokenProvider implements IProvider {
 		$this->mapper->invalidateOld($olderThan);
 	}
 
-	/**
-	 * @param string $token
-	 * @throws InvalidTokenException
-	 * @return DefaultToken user UID
-	 */
-	public function validateToken($token) {
-		try {
-			$dbToken = $this->mapper->getToken($this->hashToken($token));
-			$this->logger->debug('valid default token for ' . $dbToken->getUID());
-			return $dbToken;
-		} catch (DoesNotExistException $ex) {
-			throw new InvalidTokenException();
-		}
-	}
-
 	/**
 	 * @param string $token
 	 * @return string

+ 18 - 8
lib/private/Authentication/Token/IProvider.php

@@ -49,13 +49,6 @@ interface IProvider {
 	 */
 	public function getToken($tokenId) ;
 
-	/**
-	 * @param string $token
-	 * @throws InvalidTokenException
-	 * @return IToken
-	 */
-	public function validateToken($token);
-
 	/**
 	 * Invalidate (delete) the given session token
 	 *
@@ -72,12 +65,19 @@ interface IProvider {
 	public function invalidateTokenById(IUser $user, $id);
 
 	/**
-	 * Update token activity timestamp
+	 * Save the updated token
 	 *
 	 * @param IToken $token
 	 */
 	public function updateToken(IToken $token);
 
+	/**
+	 * Update token activity timestamp
+	 *
+	 * @param IToken $token
+	 */
+	public function updateTokenActivity(IToken $token);
+
 	/**
 	 * Get all token of a user
 	 *
@@ -99,4 +99,14 @@ interface IProvider {
 	 * @return string
 	 */
 	public function getPassword(IToken $token, $tokenId);
+
+	/**
+	 * Encrypt and set the password of the given token
+	 *
+	 * @param IToken $token
+	 * @param string $tokenId
+	 * @param string $password
+	 * @throws InvalidTokenException
+	 */
+	public function setPassword(IToken $token, $tokenId, $password);
 }

+ 14 - 0
lib/private/Authentication/Token/IToken.php

@@ -55,4 +55,18 @@ interface IToken extends JsonSerializable {
 	 * @return string
 	 */
 	public function getPassword();
+
+	/**
+	 * Get the timestamp of the last password check
+	 *
+	 * @return int
+	 */
+	public function getLastCheck();
+
+	/**
+	 * Get the timestamp of the last password check
+	 *
+	 * @param int $time
+	 */
+	public function setLastCheck($time);
 }

+ 2 - 1
lib/private/Console/Application.php

@@ -138,9 +138,10 @@ class Application {
 	 * @throws \Exception
 	 */
 	public function run(InputInterface $input = null, OutputInterface $output = null) {
+		$args = isset($this->request->server['argv']) ? $this->request->server['argv'] : [];
 		$this->dispatcher->dispatch(ConsoleEvent::EVENT_RUN, new ConsoleEvent(
 			ConsoleEvent::EVENT_RUN,
-			$this->request->server['argv']
+			$args
 		));
 		return $this->application->run($input, $output);
 	}

+ 22 - 1
lib/private/Files/View.php

@@ -998,7 +998,10 @@ class View {
 
 			// Create the directories if any
 			if (!$this->file_exists($filePath)) {
-				$this->mkdir($filePath);
+				$result = $this->createParentDirectories($filePath);
+				if($result === false) {
+					return false;
+				}
 			}
 
 			$source = fopen($tmpFile, 'r');
@@ -2107,4 +2110,22 @@ class View {
 		}
 		return [$uid, $filename];
 	}
+
+	/**
+	 * Creates parent non-existing folders
+	 *
+	 * @param string $filePath
+	 * @return bool
+	 */
+	private function createParentDirectories($filePath) {
+		$parentDirectory = dirname($filePath);
+		while(!$this->file_exists($parentDirectory)) {
+			$result = $this->createParentDirectories($parentDirectory);
+			if($result === false) {
+				return false;
+			}
+		}
+		$this->mkdir($filePath);
+		return true;
+	}
 }

+ 125 - 67
lib/private/User/Session.php

@@ -193,53 +193,35 @@ class Session implements IUserSession, Emitter {
 			if (is_null($this->activeUser)) {
 				return null;
 			}
-			$this->validateSession($this->activeUser);
+			$this->validateSession();
 		}
 		return $this->activeUser;
 	}
 
-	protected function validateSession(IUser $user) {
-		try {
-			$sessionId = $this->session->getId();
-		} catch (SessionNotAvailableException $ex) {
-			return;
-		}
-		try {
-			$token = $this->tokenProvider->getToken($sessionId);
-		} catch (InvalidTokenException $ex) {
-			// Session was invalidated
-			$this->logout();
-			return;
-		}
+	/**
+	 * Validate whether the current session is valid
+	 *
+	 * - For token-authenticated clients, the token validity is checked
+	 * - For browsers, the session token validity is checked
+	 */
+	protected function validateSession() {
+		$token = null;
+		$appPassword = $this->session->get('app_password');
 
-		// Check whether login credentials are still valid and the user was not disabled
-		// This check is performed each 5 minutes
-		$lastCheck = $this->session->get('last_login_check') ? : 0;
-		$now = $this->timeFacory->getTime();
-		if ($lastCheck < ($now - 60 * 5)) {
+		if (is_null($appPassword)) {
 			try {
-				$pwd = $this->tokenProvider->getPassword($token, $sessionId);
-			} catch (InvalidTokenException $ex) {
-				// An invalid token password was used -> log user out
-				$this->logout();
-				return;
-			} catch (PasswordlessTokenException $ex) {
-				// Token has no password, nothing to check
-				$this->session->set('last_login_check', $now);
-				return;
-			}
-
-			if ($this->manager->checkPassword($token->getLoginName(), $pwd) === false
-				|| !$user->isEnabled()) {
-				// Password has changed or user was disabled -> log user out
-				$this->logout();
+				$token = $this->session->getId();
+			} catch (SessionNotAvailableException $ex) {
 				return;
 			}
-			$this->session->set('last_login_check', $now);
+		} else {
+			$token = $appPassword;
 		}
 
-		// Session is valid, so the token can be refreshed
-		$this->updateToken($token);
+		if (!$this->validateToken($token)) {
+			// Session was invalidated
+			$this->logout();
+		}
 	}
 
 	/**
@@ -299,20 +281,21 @@ class Session implements IUserSession, Emitter {
 	public function login($uid, $password) {
 		$this->session->regenerateId();
 		if ($this->validateToken($password)) {
-			$user = $this->getUser();
-
 			// When logging in with token, the password must be decrypted first before passing to login hook
 			try {
 				$token = $this->tokenProvider->getToken($password);
 				try {
-					$password = $this->tokenProvider->getPassword($token, $password);
-					$this->manager->emit('\OC\User', 'preLogin', array($uid, $password));
+					$loginPassword = $this->tokenProvider->getPassword($token, $password);
+					$this->manager->emit('\OC\User', 'preLogin', array($uid, $loginPassword));
 				} catch (PasswordlessTokenException $ex) {
 					$this->manager->emit('\OC\User', 'preLogin', array($uid, ''));
 				}
 			} catch (InvalidTokenException $ex) {
 				// Invalid token, nothing to do
 			}
+
+			$this->loginWithToken($password);
+			$user = $this->getUser();
 		} else {
 			$this->manager->emit('\OC\User', 'preLogin', array($uid, $password));
 			$user = $this->manager->checkPassword($uid, $password);
@@ -370,7 +353,10 @@ class Session implements IUserSession, Emitter {
 			return false;
 		}
 
-		if ($this->supportsCookies($request)) {
+		if ($isTokenPassword) {
+			$this->session->set('app_password', $password);
+		} else if($this->supportsCookies($request)) {
+			// Password login, but cookies supported -> create (browser) session token
 			$this->createSessionToken($request, $this->getUser()->getUID(), $user, $password);
 		}
 
@@ -463,8 +449,22 @@ class Session implements IUserSession, Emitter {
 		return false;
 	}
 
-	private function loginWithToken($uid) {
-		// TODO: $this->manager->emit('\OC\User', 'preTokenLogin', array($uid));
+	private function loginWithToken($token) {
+		try {
+			$dbToken = $this->tokenProvider->getToken($token);
+		} catch (InvalidTokenException $ex) {
+			return false;
+		}
+		$uid = $dbToken->getUID();
+
+		$password = '';
+		try {
+			$password = $this->tokenProvider->getPassword($dbToken, $token);
+		} catch (PasswordlessTokenException $ex) {
+			// Ignore and use empty string instead
+		}
+		$this->manager->emit('\OC\User', 'preLogin', array($uid, $password));
+
 		$user = $this->manager->get($uid);
 		if (is_null($user)) {
 			// user does not exist
@@ -477,7 +477,8 @@ class Session implements IUserSession, Emitter {
 
 		//login
 		$this->setUser($user);
-		// TODO: $this->manager->emit('\OC\User', 'postTokenLogin', array($user));
+
+		$this->manager->emit('\OC\User', 'postLogin', array($user, $password));
 		return true;
 	}
 
@@ -534,37 +535,71 @@ class Session implements IUserSession, Emitter {
 	}
 
 	/**
+	 * @param IToken $dbToken
 	 * @param string $token
 	 * @return boolean
 	 */
-	private function validateToken($token) {
+	private function checkTokenCredentials(IToken $dbToken, $token) {
+		// Check whether login credentials are still valid and the user was not disabled
+		// This check is performed each 5 minutes
+		$lastCheck = $dbToken->getLastCheck() ? : 0;
+		$now = $this->timeFacory->getTime();
+		if ($lastCheck > ($now - 60 * 5)) {
+			// Checked performed recently, nothing to do now
+			return true;
+		}
+
 		try {
-			$token = $this->tokenProvider->validateToken($token);
-			if (!is_null($token)) {
-				$result = $this->loginWithToken($token->getUID());
-				if ($result) {
-					// Login success
-					$this->updateToken($token);
-					return true;
-				}
-			}
+			$pwd = $this->tokenProvider->getPassword($dbToken, $token);
 		} catch (InvalidTokenException $ex) {
+			// An invalid token password was used -> log user out
+			return false;
+		} catch (PasswordlessTokenException $ex) {
+			// Token has no password
+
+			if (!is_null($this->activeUser) && !$this->activeUser->isEnabled()) {
+				$this->tokenProvider->invalidateToken($token);
+				return false;
+			}
 
+			$dbToken->setLastCheck($now);
+			$this->tokenProvider->updateToken($dbToken);
+			return true;
 		}
-		return false;
+
+		if ($this->manager->checkPassword($dbToken->getLoginName(), $pwd) === false
+			|| (!is_null($this->activeUser) && !$this->activeUser->isEnabled())) {
+			$this->tokenProvider->invalidateToken($token);
+			// Password has changed or user was disabled -> log user out
+			return false;
+		}
+		$dbToken->setLastCheck($now);
+		$this->tokenProvider->updateToken($dbToken);
+		return true;
 	}
 
 	/**
-	 * @param IToken $token
+	 * Check if the given token exists and performs password/user-enabled checks
+	 *
+	 * Invalidates the token if checks fail
+	 *
+	 * @param string $token
+	 * @return boolean
 	 */
-	private function updateToken(IToken $token) {
-		// To save unnecessary DB queries, this is only done once a minute
-		$lastTokenUpdate = $this->session->get('last_token_update') ? : 0;
-		$now = $this->timeFacory->getTime();
-		if ($lastTokenUpdate < ($now - 60)) {
-			$this->tokenProvider->updateToken($token);
-			$this->session->set('last_token_update', $now);
+	private function validateToken($token) {
+		try {
+			$dbToken = $this->tokenProvider->getToken($token);
+		} catch (InvalidTokenException $ex) {
+			return false;
+		}
+
+		if (!$this->checkTokenCredentials($dbToken, $token)) {
+			return false;
 		}
+
+		$this->tokenProvider->updateTokenActivity($dbToken);
+
+		return true;
 	}
 
 	/**
@@ -578,15 +613,21 @@ class Session implements IUserSession, Emitter {
 		if (strpos($authHeader, 'token ') === false) {
 			// No auth header, let's try session id
 			try {
-				$sessionId = $this->session->getId();
-				return $this->validateToken($sessionId);
+				$token = $this->session->getId();
 			} catch (SessionNotAvailableException $ex) {
 				return false;
 			}
 		} else {
 			$token = substr($authHeader, 6);
-			return $this->validateToken($token);
 		}
+
+		if (!$this->loginWithToken($token)) {
+			return false;
+		}
+		if(!$this->validateToken($token)) {
+			return false;
+		}
+		return true;
 	}
 
 	/**
@@ -676,4 +717,21 @@ class Session implements IUserSession, Emitter {
 		setcookie('oc_remember_login', '', time() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
 	}
 
+	/**
+	 * Update password of the browser session token if there is one
+	 *
+	 * @param string $password
+	 */
+	public function updateSessionTokenPassword($password) {
+		try {
+			$sessionId = $this->session->getId();
+			$token = $this->tokenProvider->getToken($sessionId);
+			$this->tokenProvider->setPassword($token, $sessionId, $password);
+		} catch (SessionNotAvailableException $ex) {
+			// Nothing to do
+		} catch (InvalidTokenException $ex) {
+			// Nothing to do
+		}
+	}
+
 }

+ 6 - 5
lib/private/legacy/util.php

@@ -958,11 +958,12 @@ class OC_Util {
 	public static function checkLoggedIn() {
 		// Check if we are a user
 		if (!OC_User::isLoggedIn()) {
-			header('Location: ' . \OCP\Util::linkToAbsolute('', 'index.php',
-					[
-						'redirect_url' => \OC::$server->getRequest()->getRequestUri()
-					]
-				)
+			header('Location: ' . \OC::$server->getURLGenerator()->linkToRoute(
+						'core.login.showLoginForm',
+						[
+							'redirect_url' => urlencode(\OC::$server->getRequest()->getRequestUri()),
+						]
+					)
 			);
 			exit();
 		}

+ 2 - 2
public.php

@@ -35,9 +35,9 @@ try {
 		exit;
 	}
 
-	OC::checkMaintenanceMode();
-	OC::checkSingleUserMode(true);
 	$request = \OC::$server->getRequest();
+	OC::checkMaintenanceMode($request);
+	OC::checkSingleUserMode(true);
 	$pathInfo = $request->getPathInfo();
 
 	if (!$pathInfo && $request->getParam('service', '') === '') {

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