summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--.lgtm2
-rw-r--r--.travis.yml3
-rw-r--r--CONTRIBUTING.md2
-rw-r--r--apps/comments/js/commentstabview.js12
-rw-r--r--apps/comments/l10n/bg_BG.js1
-rw-r--r--apps/comments/l10n/bg_BG.json1
-rw-r--r--apps/comments/l10n/cs_CZ.js3
-rw-r--r--apps/comments/l10n/cs_CZ.json3
-rw-r--r--apps/comments/l10n/de.js3
-rw-r--r--apps/comments/l10n/de.json3
-rw-r--r--apps/comments/l10n/de_DE.js3
-rw-r--r--apps/comments/l10n/de_DE.json3
-rw-r--r--apps/comments/l10n/fi_FI.js3
-rw-r--r--apps/comments/l10n/fi_FI.json3
-rw-r--r--apps/comments/l10n/he.js3
-rw-r--r--apps/comments/l10n/he.json3
-rw-r--r--apps/comments/l10n/it.js3
-rw-r--r--apps/comments/l10n/it.json3
-rw-r--r--apps/comments/l10n/lb.js3
-rw-r--r--apps/comments/l10n/lb.json3
-rw-r--r--apps/comments/l10n/pl.js3
-rw-r--r--apps/comments/l10n/pl.json3
-rw-r--r--apps/comments/l10n/sq.js3
-rw-r--r--apps/comments/l10n/sq.json3
-rw-r--r--apps/dav/appinfo/v1/carddav.php1
-rw-r--r--apps/dav/lib/AppInfo/Application.php3
-rw-r--r--apps/dav/lib/CardDAV/AddressBookImpl.php44
-rw-r--r--apps/dav/lib/CardDAV/CardDavBackend.php10
-rw-r--r--apps/dav/lib/CardDAV/ContactsManager.php14
-rw-r--r--apps/dav/lib/CardDAV/ImageExportPlugin.php146
-rw-r--r--apps/dav/lib/Comments/CommentNode.php2
-rw-r--r--apps/dav/lib/Server.php2
-rw-r--r--apps/dav/tests/unit/CardDAV/AddressBookImplTest.php37
-rw-r--r--apps/dav/tests/unit/CardDAV/CardDavBackendTest.php19
-rw-r--r--apps/dav/tests/unit/CardDAV/ContactsManagerTest.php3
-rw-r--r--apps/dav/tests/unit/CardDAV/ImageExportPluginTest.php151
-rw-r--r--apps/dav/tests/unit/Comments/CommentsNodeTest.php6
-rw-r--r--apps/encryption/l10n/sv.js6
-rw-r--r--apps/encryption/l10n/sv.json6
-rw-r--r--apps/encryption/settings/settings-admin.php2
-rw-r--r--apps/federatedfilesharing/l10n/ast.js5
-rw-r--r--apps/federatedfilesharing/l10n/ast.json5
-rw-r--r--apps/federatedfilesharing/lib/DiscoveryManager.php5
-rw-r--r--apps/federatedfilesharing/lib/FederatedShareProvider.php4
-rw-r--r--apps/federatedfilesharing/lib/Notifications.php4
-rw-r--r--apps/federatedfilesharing/settings-admin.php2
-rw-r--r--apps/federatedfilesharing/tests/DiscoveryManagerTest.php25
-rw-r--r--apps/federation/settings/settings-admin.php2
-rw-r--r--apps/files/admin.php2
-rw-r--r--apps/files/download.php3
-rw-r--r--apps/files/js/file-upload.js3
-rw-r--r--apps/files/js/filelist.js22
-rw-r--r--apps/files/js/search.js11
-rw-r--r--apps/files/l10n/ast.js28
-rw-r--r--apps/files/l10n/ast.json28
-rw-r--r--apps/files/l10n/bg_BG.js3
-rw-r--r--apps/files/l10n/bg_BG.json3
-rw-r--r--apps/files/l10n/lb.js2
-rw-r--r--apps/files/l10n/lb.json2
-rw-r--r--apps/files/l10n/sv.js11
-rw-r--r--apps/files/l10n/sv.json11
-rw-r--r--apps/files/list.php3
-rw-r--r--apps/files/tests/js/filelistSpec.js11
-rw-r--r--apps/files_external/l10n/lb.js1
-rw-r--r--apps/files_external/l10n/lb.json1
-rw-r--r--apps/files_external/l10n/sv.js1
-rw-r--r--apps/files_external/l10n/sv.json1
-rw-r--r--apps/files_external/lib/AppInfo/Application.php6
-rw-r--r--apps/files_external/lib/Command/Export.php6
-rw-r--r--apps/files_external/lib/Command/ListCommand.php48
-rw-r--r--apps/files_external/lib/Controller/AjaxController.php42
-rw-r--r--apps/files_external/lib/Lib/Auth/Password/LoginCredentials.php92
-rw-r--r--apps/files_external/lib/Lib/Auth/Password/UserProvided.php88
-rw-r--r--apps/files_external/lib/Service/DBConfigService.php12
-rw-r--r--apps/files_external/lib/Service/GlobalStoragesService.php19
-rw-r--r--apps/files_external/settings.php2
-rw-r--r--apps/files_external/tests/Controller/AjaxControllerTest.php177
-rw-r--r--apps/files_external/tests/Service/DBConfigServiceTest.php10
-rwxr-xr-xapps/files_external/tests/env/start-swift-ceph.sh5
-rw-r--r--apps/files_sharing/ajax/external.php5
-rw-r--r--apps/files_sharing/l10n/lb.js1
-rw-r--r--apps/files_sharing/l10n/lb.json1
-rw-r--r--apps/files_sharing/lib/API/Share20OCS.php22
-rw-r--r--apps/files_sharing/lib/External/Storage.php11
-rw-r--r--apps/files_sharing/lib/MountProvider.php5
-rw-r--r--apps/files_sharing/tests/API/Share20OCSTest.php77
-rw-r--r--apps/files_sharing/tests/ApiTest.php16
-rw-r--r--apps/files_sharing/tests/MountProviderTest.php136
-rw-r--r--apps/files_trashbin/js/filelist.js2
-rw-r--r--apps/files_trashbin/tests/js/filelistSpec.js22
-rw-r--r--apps/files_versions/lib/Storage.php9
-rw-r--r--apps/systemtags/l10n/pl.js1
-rw-r--r--apps/systemtags/l10n/pl.json1
-rw-r--r--apps/theming/appinfo/app.php38
-rw-r--r--apps/theming/appinfo/info.xml15
-rw-r--r--apps/theming/appinfo/routes.php57
-rw-r--r--apps/theming/css/settings-admin.css34
-rw-r--r--apps/theming/js/3rdparty/jscolor/LICENSE.txt674
-rw-r--r--apps/theming/js/3rdparty/jscolor/jscolor.js1844
-rw-r--r--apps/theming/js/3rdparty/jscolor/jscolor.min.js10
-rw-r--r--apps/theming/js/settings-admin.js141
-rw-r--r--apps/theming/lib/controller/themingcontroller.php241
-rw-r--r--apps/theming/lib/template.php150
-rw-r--r--apps/theming/settings/settings-admin.php51
-rw-r--r--apps/theming/templates/settings-admin.php57
-rw-r--r--apps/theming/tests/lib/TemplateTest.php301
-rw-r--r--apps/theming/tests/lib/controller/ThemingControllerTest.php428
-rw-r--r--apps/updatenotification/l10n/bg_BG.js3
-rw-r--r--apps/updatenotification/l10n/bg_BG.json3
-rw-r--r--apps/updatenotification/l10n/lb.js15
-rw-r--r--apps/updatenotification/l10n/lb.json13
-rw-r--r--apps/user_ldap/l10n/ast.js57
-rw-r--r--apps/user_ldap/l10n/ast.json57
-rw-r--r--apps/user_ldap/l10n/lb.js39
-rw-r--r--apps/user_ldap/l10n/lb.json39
-rw-r--r--apps/user_ldap/lib/Access.php9
-rw-r--r--apps/user_ldap/settings.php2
-rwxr-xr-xautotest.sh8
-rw-r--r--build/integration/features/provisioning-v1.feature1
-rw-r--r--build/integration/features/sharing-v1.feature6
-rw-r--r--core/Application.php13
-rw-r--r--core/Command/Encryption/DecryptAll.php3
-rw-r--r--core/Controller/OccController.php147
-rw-r--r--core/css/apps.css8
-rw-r--r--core/css/fixes.css116
-rw-r--r--core/css/multiselect.css7
-rw-r--r--core/css/styles.css10
-rw-r--r--core/js/config.php2
-rw-r--r--core/js/files/iedavclient.js1
-rw-r--r--core/js/js.js30
-rw-r--r--core/js/setupchecks.js6
-rw-r--r--core/js/sharedialoglinkshareview.js2
-rw-r--r--core/l10n/lb.js1
-rw-r--r--core/l10n/lb.json1
-rw-r--r--core/l10n/pl.js7
-rw-r--r--core/l10n/pl.json7
-rw-r--r--core/l10n/ro.js16
-rw-r--r--core/l10n/ro.json16
-rw-r--r--core/l10n/sv.js1
-rw-r--r--core/l10n/sv.json1
-rw-r--r--core/routes.php1
-rw-r--r--core/shipped.json1
-rw-r--r--core/templates/layout.base.php4
-rw-r--r--core/templates/layout.guest.php4
-rw-r--r--core/templates/layout.user.php4
-rw-r--r--db_structure.xml9
-rw-r--r--l10n/.tx/config31
-rw-r--r--lib/base.php19
-rw-r--r--lib/l10n/ast.js57
-rw-r--r--lib/l10n/ast.json57
-rw-r--r--lib/private/Authentication/Token/DefaultToken.php23
-rw-r--r--lib/private/Authentication/Token/DefaultTokenMapper.php4
-rw-r--r--lib/private/Authentication/Token/DefaultTokenProvider.php55
-rw-r--r--lib/private/Authentication/Token/IProvider.php26
-rw-r--r--lib/private/Authentication/Token/IToken.php14
-rw-r--r--lib/private/Console/Application.php3
-rw-r--r--lib/private/Files/View.php23
-rw-r--r--lib/private/IntegrityCheck/Checker.php15
-rw-r--r--lib/private/Repair/RepairInvalidShares.php23
-rw-r--r--lib/private/Server.php25
-rw-r--r--lib/private/Share20/Manager.php32
-rw-r--r--lib/private/User/Database.php10
-rw-r--r--lib/private/User/Session.php203
-rw-r--r--lib/private/legacy/template.php2
-rw-r--r--lib/private/legacy/util.php11
-rw-r--r--public.php4
-rw-r--r--settings/ChangePassword/Controller.php30
-rw-r--r--settings/Controller/AuthSettingsController.php1
-rw-r--r--settings/css/settings.css43
-rw-r--r--settings/js/authtoken.js6
-rw-r--r--settings/js/authtoken_collection.js6
-rw-r--r--settings/js/authtoken_view.js68
-rw-r--r--settings/js/personal.js41
-rw-r--r--settings/l10n/ar.js3
-rw-r--r--settings/l10n/ar.json3
-rw-r--r--settings/l10n/ast.js2
-rw-r--r--settings/l10n/ast.json2
-rw-r--r--settings/l10n/az.js3
-rw-r--r--settings/l10n/az.json3
-rw-r--r--settings/l10n/bg_BG.js3
-rw-r--r--settings/l10n/bg_BG.json3
-rw-r--r--settings/l10n/bn_BD.js3
-rw-r--r--settings/l10n/bn_BD.json3
-rw-r--r--settings/l10n/bs.js2
-rw-r--r--settings/l10n/bs.json2
-rw-r--r--settings/l10n/ca.js2
-rw-r--r--settings/l10n/ca.json2
-rw-r--r--settings/l10n/cs_CZ.js13
-rw-r--r--settings/l10n/cs_CZ.json13
-rw-r--r--settings/l10n/da.js3
-rw-r--r--settings/l10n/da.json3
-rw-r--r--settings/l10n/de.js13
-rw-r--r--settings/l10n/de.json13
-rw-r--r--settings/l10n/de_DE.js13
-rw-r--r--settings/l10n/de_DE.json13
-rw-r--r--settings/l10n/el.js4
-rw-r--r--settings/l10n/el.json4
-rw-r--r--settings/l10n/en_GB.js14
-rw-r--r--settings/l10n/en_GB.json14
-rw-r--r--settings/l10n/eo.js3
-rw-r--r--settings/l10n/eo.json3
-rw-r--r--settings/l10n/es.js8
-rw-r--r--settings/l10n/es.json8
-rw-r--r--settings/l10n/es_AR.js2
-rw-r--r--settings/l10n/es_AR.json2
-rw-r--r--settings/l10n/es_MX.js2
-rw-r--r--settings/l10n/es_MX.json2
-rw-r--r--settings/l10n/et_EE.js3
-rw-r--r--settings/l10n/et_EE.json3
-rw-r--r--settings/l10n/eu.js3
-rw-r--r--settings/l10n/eu.json3
-rw-r--r--settings/l10n/fa.js2
-rw-r--r--settings/l10n/fa.json2
-rw-r--r--settings/l10n/fi_FI.js13
-rw-r--r--settings/l10n/fi_FI.json13
-rw-r--r--settings/l10n/fr.js8
-rw-r--r--settings/l10n/fr.json8
-rw-r--r--settings/l10n/gl.js3
-rw-r--r--settings/l10n/gl.json3
-rw-r--r--settings/l10n/he.js13
-rw-r--r--settings/l10n/he.json13
-rw-r--r--settings/l10n/hr.js2
-rw-r--r--settings/l10n/hr.json2
-rw-r--r--settings/l10n/hu_HU.js3
-rw-r--r--settings/l10n/hu_HU.json3
-rw-r--r--settings/l10n/hy.js2
-rw-r--r--settings/l10n/hy.json2
-rw-r--r--settings/l10n/ia.js2
-rw-r--r--settings/l10n/ia.json2
-rw-r--r--settings/l10n/id.js3
-rw-r--r--settings/l10n/id.json3
-rw-r--r--settings/l10n/is.js2
-rw-r--r--settings/l10n/is.json2
-rw-r--r--settings/l10n/it.js13
-rw-r--r--settings/l10n/it.json13
-rw-r--r--settings/l10n/ja.js8
-rw-r--r--settings/l10n/ja.json8
-rw-r--r--settings/l10n/ka_GE.js2
-rw-r--r--settings/l10n/ka_GE.json2
-rw-r--r--settings/l10n/km.js2
-rw-r--r--settings/l10n/km.json2
-rw-r--r--settings/l10n/kn.js2
-rw-r--r--settings/l10n/kn.json2
-rw-r--r--settings/l10n/ko.js3
-rw-r--r--settings/l10n/ko.json3
-rw-r--r--settings/l10n/lb.js7
-rw-r--r--settings/l10n/lb.json7
-rw-r--r--settings/l10n/lt_LT.js2
-rw-r--r--settings/l10n/lt_LT.json2
-rw-r--r--settings/l10n/lv.js3
-rw-r--r--settings/l10n/lv.json3
-rw-r--r--settings/l10n/mk.js2
-rw-r--r--settings/l10n/mk.json2
-rw-r--r--settings/l10n/mn.js1
-rw-r--r--settings/l10n/mn.json1
-rw-r--r--settings/l10n/ms_MY.js2
-rw-r--r--settings/l10n/ms_MY.json2
-rw-r--r--settings/l10n/nb_NO.js3
-rw-r--r--settings/l10n/nb_NO.json3
-rw-r--r--settings/l10n/nl.js8
-rw-r--r--settings/l10n/nl.json8
-rw-r--r--settings/l10n/nn_NO.js2
-rw-r--r--settings/l10n/nn_NO.json2
-rw-r--r--settings/l10n/oc.js2
-rw-r--r--settings/l10n/oc.json2
-rw-r--r--settings/l10n/pl.js6
-rw-r--r--settings/l10n/pl.json6
-rw-r--r--settings/l10n/pt_BR.js13
-rw-r--r--settings/l10n/pt_BR.json13
-rw-r--r--settings/l10n/pt_PT.js8
-rw-r--r--settings/l10n/pt_PT.json8
-rw-r--r--settings/l10n/ro.js24
-rw-r--r--settings/l10n/ro.json24
-rw-r--r--settings/l10n/ru.js13
-rw-r--r--settings/l10n/ru.json13
-rw-r--r--settings/l10n/si_LK.js2
-rw-r--r--settings/l10n/si_LK.json2
-rw-r--r--settings/l10n/sk_SK.js3
-rw-r--r--settings/l10n/sk_SK.json3
-rw-r--r--settings/l10n/sl.js6
-rw-r--r--settings/l10n/sl.json6
-rw-r--r--settings/l10n/sq.js13
-rw-r--r--settings/l10n/sq.json13
-rw-r--r--settings/l10n/sr.js3
-rw-r--r--settings/l10n/sr.json3
-rw-r--r--settings/l10n/sr@latin.js2
-rw-r--r--settings/l10n/sr@latin.json2
-rw-r--r--settings/l10n/sv.js10
-rw-r--r--settings/l10n/sv.json10
-rw-r--r--settings/l10n/ta_LK.js2
-rw-r--r--settings/l10n/ta_LK.json2
-rw-r--r--settings/l10n/te.js2
-rw-r--r--settings/l10n/te.json2
-rw-r--r--settings/l10n/th_TH.js3
-rw-r--r--settings/l10n/th_TH.json3
-rw-r--r--settings/l10n/tr.js3
-rw-r--r--settings/l10n/tr.json3
-rw-r--r--settings/l10n/ug.js2
-rw-r--r--settings/l10n/ug.json2
-rw-r--r--settings/l10n/uk.js3
-rw-r--r--settings/l10n/uk.json3
-rw-r--r--settings/l10n/vi.js2
-rw-r--r--settings/l10n/vi.json2
-rw-r--r--settings/l10n/zh_CN.js2
-rw-r--r--settings/l10n/zh_CN.json2
-rw-r--r--settings/l10n/zh_HK.js2
-rw-r--r--settings/l10n/zh_HK.json2
-rw-r--r--settings/l10n/zh_TW.js2
-rw-r--r--settings/l10n/zh_TW.json2
-rw-r--r--settings/personal.php2
-rw-r--r--settings/templates/personal.php87
-rw-r--r--tests/Core/Controller/OccControllerTest.php143
-rw-r--r--tests/lib/Authentication/Token/DefaultTokenMapperTest.php4
-rw-r--r--tests/lib/Authentication/Token/DefaultTokenProviderTest.php72
-rw-r--r--tests/lib/Repair/RepairInvalidSharesTest.php87
-rw-r--r--tests/lib/Share20/ManagerTest.php73
-rw-r--r--tests/lib/User/DatabaseTest.php47
-rw-r--r--tests/lib/User/SessionTest.php250
-rw-r--r--version.php2
320 files changed, 7524 insertions, 1009 deletions
diff --git a/.gitignore b/.gitignore
index 69b977aee03..215682c4081 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,6 +25,7 @@
!/apps/testing
!/apps/admin_audit
!/apps/updatenotification
+!/apps/theming
/apps/files_external/3rdparty/irodsphp/PHPUnitTest
/apps/files_external/3rdparty/irodsphp/web
/apps/files_external/3rdparty/irodsphp/prods/test
diff --git a/.lgtm b/.lgtm
index 5625d21b1dc..612c35f520e 100644
--- a/.lgtm
+++ b/.lgtm
@@ -1,2 +1,2 @@
pattern = "(?i):shipit:|:\\+1:|LGTM|👍"
-self_approval_off=true
+self_approval_off = true
diff --git a/.travis.yml b/.travis.yml
index 242ba71e395..15413d3413e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -30,6 +30,7 @@ install:
script:
- sh -c "if [ '$TC' = 'syntax' ]; then composer install && lib/composer/bin/parallel-lint --exclude lib/composer/jakub-onderka/ --exclude 3rdparty/symfony/polyfill-php70/Resources/stubs/ --exclude 3rdparty/patchwork/utf8/src/Patchwork/Utf8/Bootup/ --exclude 3rdparty/paragonie/random_compat/lib/ --exclude lib/composer/composer/autoload_static.php --exclude 3rdparty/composer/autoload_static.php .; fi"
+ - sh -c "if [ '$TC' = 'app:check-code' ]; then ./occ app:check-code admin_audit; ./occ app:check-code comments; ./occ app:check-code federation; fi"
- sh -c "if [ '$TEST_DAV' != '1' ]; then echo \"Not testing DAV\"; fi"
- sh -c "if [ '$TEST_DAV' = '1' ]; then echo \"Testing DAV\"; fi"
@@ -51,5 +52,7 @@ matrix:
env: DB=sqlite;TC=syntax;TEST_DAV=0
- php: 7.0
env: DB=sqlite;TC=syntax;TEST_DAV=0
+ - php: 5.4
+ env: DB=sqlite;TC=app:check-code;TEST_DAV=0
fast_finish: true
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 7b30d5ff46c..952fad734ba 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -27,7 +27,7 @@ Help us to maximize the effort we can spend fixing issues and adding new feature
Thanks for wanting to contribute source code to Nextcloud. That's great!
-Please read the [Developer Manuals][devmanual] to learn how to create your first application or how to test the ownCloud code with PHPUnit.
+Please read the [Developer Manuals][devmanual] to learn how to create your first application or how to test the Nextcloud code with PHPUnit.
In order to constantly increase the quality of our software we can no longer accept pull request which submit un-tested code.
It is a must have that changed and added code segments are unit tested.
diff --git a/apps/comments/js/commentstabview.js b/apps/comments/js/commentstabview.js
index 9475dc53fc0..f71567f04d1 100644
--- a/apps/comments/js/commentstabview.js
+++ b/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'));
}
});
}
diff --git a/apps/comments/l10n/bg_BG.js b/apps/comments/l10n/bg_BG.js
index c5fbba69f7b..ca60bb48bf6 100644
--- a/apps/comments/l10n/bg_BG.js
+++ b/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]" : "[Изтрит потребител]",
diff --git a/apps/comments/l10n/bg_BG.json b/apps/comments/l10n/bg_BG.json
index 64f516861ca..2b79857f6bb 100644
--- a/apps/comments/l10n/bg_BG.json
+++ b/apps/comments/l10n/bg_BG.json
@@ -1,6 +1,7 @@
{ "translations": {
"Type in a new comment..." : "Напиши нов коментар...",
"Delete comment" : "Изтрий коментар",
+ "Post" : "Публикация",
"Cancel" : "Отказ",
"Edit comment" : "Редактирай коментра",
"[Deleted user]" : "[Изтрит потребител]",
diff --git a/apps/comments/l10n/cs_CZ.js b/apps/comments/l10n/cs_CZ.js
index d41b0f99f42..0653209616c 100644
--- a/apps/comments/l10n/cs_CZ.js
+++ b/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>",
diff --git a/apps/comments/l10n/cs_CZ.json b/apps/comments/l10n/cs_CZ.json
index c695e50c646..efdd227bfb7 100644
--- a/apps/comments/l10n/cs_CZ.json
+++ b/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>",
diff --git a/apps/comments/l10n/de.js b/apps/comments/l10n/de.js
index ebf473c6b9c..b46dcb20905 100644
--- a/apps/comments/l10n/de.js
+++ b/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>",
diff --git a/apps/comments/l10n/de.json b/apps/comments/l10n/de.json
index 0e4bc1b53bd..ff7d59cf5c5 100644
--- a/apps/comments/l10n/de.json
+++ b/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>",
diff --git a/apps/comments/l10n/de_DE.js b/apps/comments/l10n/de_DE.js
index 74e129e0a29..7330b6ab068 100644
--- a/apps/comments/l10n/de_DE.js
+++ b/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>",
diff --git a/apps/comments/l10n/de_DE.json b/apps/comments/l10n/de_DE.json
index 6c18cf15fc1..611521dc907 100644
--- a/apps/comments/l10n/de_DE.json
+++ b/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>",
diff --git a/apps/comments/l10n/fi_FI.js b/apps/comments/l10n/fi_FI.js
index 7b4de946763..b541b9987fe 100644
--- a/apps/comments/l10n/fi_FI.js
+++ b/apps/comments/l10n/fi_FI.js
@@ -12,6 +12,9 @@ OC.L10N.register(
"More comments..." : "Lisää kommentteja...",
"Save" : "Tallenna",
"Allowed characters {count} of {max}" : "Sallittujen merkkien määrä {count}/{max}",
+ "Error occurred while retrieving comment with id {id}" : "Virhe noutaessa kommenttia tunnisteella {id}",
+ "Error occurred while updating comment with id {id}" : "Virhe päivittäessä kommenttia tunnisteella {id}",
+ "Error occurred while posting comment" : "Virhe kommenttia lähettäessä",
"{count} unread comments" : "{count} lukematonta kommenttia",
"Comment" : "Kommentti",
"<strong>Comments</strong> for files <em>(always listed in stream)</em>" : "<strong>Kommentit</strong> tiedostoille <em>(aina listattu luettelossa)</em>",
diff --git a/apps/comments/l10n/fi_FI.json b/apps/comments/l10n/fi_FI.json
index 5e9bcc74402..b5be601d905 100644
--- a/apps/comments/l10n/fi_FI.json
+++ b/apps/comments/l10n/fi_FI.json
@@ -10,6 +10,9 @@
"More comments..." : "Lisää kommentteja...",
"Save" : "Tallenna",
"Allowed characters {count} of {max}" : "Sallittujen merkkien määrä {count}/{max}",
+ "Error occurred while retrieving comment with id {id}" : "Virhe noutaessa kommenttia tunnisteella {id}",
+ "Error occurred while updating comment with id {id}" : "Virhe päivittäessä kommenttia tunnisteella {id}",
+ "Error occurred while posting comment" : "Virhe kommenttia lähettäessä",
"{count} unread comments" : "{count} lukematonta kommenttia",
"Comment" : "Kommentti",
"<strong>Comments</strong> for files <em>(always listed in stream)</em>" : "<strong>Kommentit</strong> tiedostoille <em>(aina listattu luettelossa)</em>",
diff --git a/apps/comments/l10n/he.js b/apps/comments/l10n/he.js
index 02168b4f8ec..e7b2454b1c4 100644
--- a/apps/comments/l10n/he.js
+++ b/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>",
diff --git a/apps/comments/l10n/he.json b/apps/comments/l10n/he.json
index 66ac46ce2d6..075e86f9672 100644
--- a/apps/comments/l10n/he.json
+++ b/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>",
diff --git a/apps/comments/l10n/it.js b/apps/comments/l10n/it.js
index 27844003c03..ce98270c06d 100644
--- a/apps/comments/l10n/it.js
+++ b/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>",
diff --git a/apps/comments/l10n/it.json b/apps/comments/l10n/it.json
index 73605cd6f03..50b8aa17698 100644
--- a/apps/comments/l10n/it.json
+++ b/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>",
diff --git a/apps/comments/l10n/lb.js b/apps/comments/l10n/lb.js
index f63640ac6f7..d7f8c5884ac 100644
--- a/apps/comments/l10n/lb.js
+++ b/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);");
diff --git a/apps/comments/l10n/lb.json b/apps/comments/l10n/lb.json
index c015c4dd2a8..bfa307a7e8d 100644
--- a/apps/comments/l10n/lb.json
+++ b/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);"
} \ No newline at end of file
diff --git a/apps/comments/l10n/pl.js b/apps/comments/l10n/pl.js
index d4a492e1da2..b8df4dea02b 100644
--- a/apps/comments/l10n/pl.js
+++ b/apps/comments/l10n/pl.js
@@ -12,6 +12,9 @@ OC.L10N.register(
"More comments..." : "Więcej komentarzy...",
"Save" : "Zapisz",
"Allowed characters {count} of {max}" : "Dozwolone znaki {count} z {max}",
+ "Error occurred while retrieving comment with id {id}" : "W trakcie otrzymywania komentarza o identyfikatorze {id} wystąpił błąd.",
+ "Error occurred while updating comment with id {id}" : "W trakcie aktualizacji komentarza o identyfikatorze {id} wystąpił błąd.",
+ "Error occurred while posting comment" : "Podczas wysyłania komentarza wystąpił błąd",
"{count} unread comments" : "{count} nieprzeczytanych komentarzy",
"Comment" : "Komentarz",
"<strong>Comments</strong> for files <em>(always listed in stream)</em>" : "<strong>Komentarze</strong> dla plików <em>(zawsze wypisane w strumieniu)</em>",
diff --git a/apps/comments/l10n/pl.json b/apps/comments/l10n/pl.json
index 78e9f0ff210..47e4b101f98 100644
--- a/apps/comments/l10n/pl.json
+++ b/apps/comments/l10n/pl.json
@@ -10,6 +10,9 @@
"More comments..." : "Więcej komentarzy...",
"Save" : "Zapisz",
"Allowed characters {count} of {max}" : "Dozwolone znaki {count} z {max}",
+ "Error occurred while retrieving comment with id {id}" : "W trakcie otrzymywania komentarza o identyfikatorze {id} wystąpił błąd.",
+ "Error occurred while updating comment with id {id}" : "W trakcie aktualizacji komentarza o identyfikatorze {id} wystąpił błąd.",
+ "Error occurred while posting comment" : "Podczas wysyłania komentarza wystąpił błąd",
"{count} unread comments" : "{count} nieprzeczytanych komentarzy",
"Comment" : "Komentarz",
"<strong>Comments</strong> for files <em>(always listed in stream)</em>" : "<strong>Komentarze</strong> dla plików <em>(zawsze wypisane w strumieniu)</em>",
diff --git a/apps/comments/l10n/sq.js b/apps/comments/l10n/sq.js
index 0f0366d87f6..1cc9966f1d7 100644
--- a/apps/comments/l10n/sq.js
+++ b/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>",
diff --git a/apps/comments/l10n/sq.json b/apps/comments/l10n/sq.json
index e4fdcbaeb52..71f01a27223 100644
--- a/apps/comments/l10n/sq.json
+++ b/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>",
diff --git a/apps/dav/appinfo/v1/carddav.php b/apps/dav/appinfo/v1/carddav.php
index 88582d64ee5..fc7aff4a63c 100644
--- a/apps/dav/appinfo/v1/carddav.php
+++ b/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!
diff --git a/apps/dav/lib/AppInfo/Application.php b/apps/dav/lib/AppInfo/Application.php
index de2056ebc35..b7b24ba7632 100644
--- a/apps/dav/lib/AppInfo/Application.php
+++ b/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() {
diff --git a/apps/dav/lib/CardDAV/AddressBookImpl.php b/apps/dav/lib/CardDAV/AddressBookImpl.php
index 310be695185..b26f81766c6 100644
--- a/apps/dav/lib/CardDAV/AddressBookImpl.php
+++ b/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') {
diff --git a/apps/dav/lib/CardDAV/CardDavBackend.php b/apps/dav/lib/CardDAV/CardDavBackend.php
index 3d2904df39c..39f76a4b420 100644
--- a/apps/dav/lib/CardDAV/CardDavBackend.php
+++ b/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);
}
/**
@@ -846,7 +848,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
$query = $this->db->getQueryBuilder();
$query->select('*')->from($this->dbCardsTable)
->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
- ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
+ ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
$queryResult = $query->execute();
$contact = $queryResult->fetch();
$queryResult->closeCursor();
diff --git a/apps/dav/lib/CardDAV/ContactsManager.php b/apps/dav/lib/CardDAV/ContactsManager.php
index 7900c6ccae0..ad633483fdd 100644
--- a/apps/dav/lib/CardDAV/ContactsManager.php
+++ b/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
)
);
}
diff --git a/apps/dav/lib/CardDAV/ImageExportPlugin.php b/apps/dav/lib/CardDAV/ImageExportPlugin.php
new file mode 100644
index 00000000000..3f505222491
--- /dev/null
+++ b/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 '';
+ }
+}
diff --git a/apps/dav/lib/Comments/CommentNode.php b/apps/dav/lib/Comments/CommentNode.php
index edf46d210fc..5c22f8a8a71 100644
--- a/apps/dav/lib/Comments/CommentNode.php
+++ b/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;
}
}
diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php
index e150f441b82..7fa1b13783b 100644
--- a/apps/dav/lib/Server.php
+++ b/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(
diff --git a/apps/dav/tests/unit/CardDAV/AddressBookImplTest.php b/apps/dav/tests/unit/CardDAV/AddressBookImplTest.php
index ddfec3dc3ac..4ccc841157f 100644
--- a/apps/dav/tests/unit/CardDAV/AddressBookImplTest.php
+++ b/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'])
diff --git a/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php b/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php
index c7cd4a30052..9845d2d6909 100644
--- a/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php
+++ b/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']]]
];
}
@@ -606,6 +606,11 @@ class CardDavBackendTest extends TestCase {
$this->assertSame(5489543, (int)$result['lastmodified']);
$this->assertSame('etag0', $result['etag']);
$this->assertSame(120, (int)$result['size']);
+
+ // this shouldn't return any result because 'uri1' is in address book 1
+ // see https://github.com/nextcloud/server/issues/229
+ $result = $this->backend->getContact(0, 'uri1');
+ $this->assertEmpty($result);
}
public function testGetContactFail() {
diff --git a/apps/dav/tests/unit/CardDAV/ContactsManagerTest.php b/apps/dav/tests/unit/CardDAV/ContactsManagerTest.php
index 2abd9da66e7..23a49a1962f 100644
--- a/apps/dav/tests/unit/CardDAV/ContactsManagerTest.php
+++ b/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);
}
}
diff --git a/apps/dav/tests/unit/CardDAV/ImageExportPluginTest.php b/apps/dav/tests/unit/CardDAV/ImageExportPluginTest.php
new file mode 100644
index 00000000000..8583df0b6f9
--- /dev/null
+++ b/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"],
+ ];
+ }
+}
diff --git a/apps/dav/tests/unit/Comments/CommentsNodeTest.php b/apps/dav/tests/unit/Comments/CommentsNodeTest.php
index 6bf06375a43..18b18ab8d3b 100644
--- a/apps/dav/tests/unit/Comments/CommentsNodeTest.php
+++ b/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);
}
/**
diff --git a/apps/encryption/l10n/sv.js b/apps/encryption/l10n/sv.js
index b8541cfbb25..2e00f5d35ff 100644
--- a/apps/encryption/l10n/sv.js
+++ b/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.",
diff --git a/apps/encryption/l10n/sv.json b/apps/encryption/l10n/sv.json
index 9a6919b4888..b06c1bc9189 100644
--- a/apps/encryption/l10n/sv.json
+++ b/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.",
diff --git a/apps/encryption/settings/settings-admin.php b/apps/encryption/settings/settings-admin.php
index 87de11bba0f..bca3d5168a0 100644
--- a/apps/encryption/settings/settings-admin.php
+++ b/apps/encryption/settings/settings-admin.php
@@ -22,8 +22,6 @@
*
*/
-\OC_Util::checkAdminUser();
-
$tmpl = new OCP\Template('encryption', 'settings-admin');
$crypt = new \OCA\Encryption\Crypto\Crypt(
diff --git a/apps/federatedfilesharing/l10n/ast.js b/apps/federatedfilesharing/l10n/ast.js
index 4ae5f2b9cc1..3b5affabbb9 100644
--- a/apps/federatedfilesharing/l10n/ast.js
+++ b/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);");
diff --git a/apps/federatedfilesharing/l10n/ast.json b/apps/federatedfilesharing/l10n/ast.json
index 70d90ab6578..e5eb10cf14e 100644
--- a/apps/federatedfilesharing/l10n/ast.json
+++ b/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);"
} \ No newline at end of file
diff --git a/apps/federatedfilesharing/lib/DiscoveryManager.php b/apps/federatedfilesharing/lib/DiscoveryManager.php
index 279f86eda30..d65dad3085e 100644
--- a/apps/federatedfilesharing/lib/DiscoveryManager.php
+++ b/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)) {
diff --git a/apps/federatedfilesharing/lib/FederatedShareProvider.php b/apps/federatedfilesharing/lib/FederatedShareProvider.php
index 6f9b6644eed..40e2e8a472f 100644
--- a/apps/federatedfilesharing/lib/FederatedShareProvider.php
+++ b/apps/federatedfilesharing/lib/FederatedShareProvider.php
@@ -473,6 +473,8 @@ class FederatedShareProvider implements IShareProvider {
$isOwner = false;
+ $this->removeShareFromTable($share);
+
// if the local user is the owner we can send the unShare request directly...
if ($this->userManager->userExists($share->getShareOwner())) {
$this->notifications->sendRemoteUnShare($remote, $share->getId(), $share->getToken());
@@ -494,8 +496,6 @@ class FederatedShareProvider implements IShareProvider {
}
$this->notifications->sendRevokeShare($remote, $remoteId, $share->getToken());
}
-
- $this->removeShareFromTable($share);
}
/**
diff --git a/apps/federatedfilesharing/lib/Notifications.php b/apps/federatedfilesharing/lib/Notifications.php
index b2e665ed5fd..ac55642b858 100644
--- a/apps/federatedfilesharing/lib/Notifications.php
+++ b/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;
diff --git a/apps/federatedfilesharing/settings-admin.php b/apps/federatedfilesharing/settings-admin.php
index 33851129942..9875dcf3b5b 100644
--- a/apps/federatedfilesharing/settings-admin.php
+++ b/apps/federatedfilesharing/settings-admin.php
@@ -22,8 +22,6 @@
use OCA\FederatedFileSharing\AppInfo\Application;
-\OC_Util::checkAdminUser();
-
$app = new Application('federatedfilesharing');
$federatedShareProvider = $app->getFederatedShareProvider();
diff --git a/apps/federatedfilesharing/tests/DiscoveryManagerTest.php b/apps/federatedfilesharing/tests/DiscoveryManagerTest.php
index 73f79b2c169..a9c324f0244 100644
--- a/apps/federatedfilesharing/tests/DiscoveryManagerTest.php
+++ b/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))
diff --git a/apps/federation/settings/settings-admin.php b/apps/federation/settings/settings-admin.php
index b5fd7710efc..4511ff954b5 100644
--- a/apps/federation/settings/settings-admin.php
+++ b/apps/federation/settings/settings-admin.php
@@ -19,8 +19,6 @@
*
*/
-\OC_Util::checkAdminUser();
-
$template = new OCP\Template('federation', 'settings-admin');
$dbHandler = new \OCA\Federation\DbHandler(
diff --git a/apps/files/admin.php b/apps/files/admin.php
index 60f041a400b..48a6100838f 100644
--- a/apps/files/admin.php
+++ b/apps/files/admin.php
@@ -27,8 +27,6 @@
*
*/
-OCP\User::checkAdminUser();
-
$htaccessWorking=(getenv('htaccessWorking')=='true');
$upload_max_filesize = OC::$server->getIniWrapper()->getBytes('upload_max_filesize');
$post_max_size = OC::$server->getIniWrapper()->getBytes('post_max_size');
diff --git a/apps/files/download.php b/apps/files/download.php
index 38ac2a074c3..da01f98ed62 100644
--- a/apps/files/download.php
+++ b/apps/files/download.php
@@ -26,9 +26,6 @@
*
*/
-// Check if we are a user
-OCP\User::checkLoggedIn();
-
$filename = $_GET["file"];
if(!\OC\Files\Filesystem::file_exists($filename)) {
diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js
index 2453cc5207c..d5470d0be55 100644
--- a/apps/files/js/file-upload.js
+++ b/apps/files/js/file-upload.js
@@ -669,13 +669,14 @@ OC.Upload = {
});
fileupload.on('fileuploaddragover', function(e){
$('#app-content').addClass('file-drag');
+ $('#emptycontent .icon-folder').addClass('icon-filetype-folder-drag-accept');
var filerow = $(e.delegatedEvent.target).closest('tr');
if(!filerow.hasClass('dropping-to-dir')){
+ $('.dropping-to-dir .icon-filetype-folder-drag-accept').removeClass('icon-filetype-folder-drag-accept');
$('.dropping-to-dir').removeClass('dropping-to-dir');
$('.dir-drop').removeClass('dir-drop');
- $('.icon-filetype-folder-drag-accept').removeClass('icon-filetype-folder-drag-accept');
}
if(filerow.attr('data-type') === 'dir'){
diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js
index 3ab5032599c..e483882fcc5 100644
--- a/apps/files/js/filelist.js
+++ b/apps/files/js/filelist.js
@@ -2351,22 +2351,36 @@
* @param filter
*/
setFilter:function(filter) {
+ var total = 0;
this._filter = filter;
this.fileSummary.setFilter(filter, this.files);
+ total = this.fileSummary.getTotal();
if (!this.$el.find('.mask').exists()) {
this.hideIrrelevantUIWhenNoFilesMatch();
}
var that = this;
+ var visibleCount = 0;
filter = filter.toLowerCase();
- this.$fileList.find('tr').each(function(i,e) {
- var $e = $(e);
+
+ function filterRows(tr) {
+ var $e = $(tr);
if ($e.data('file').toString().toLowerCase().indexOf(filter) === -1) {
$e.addClass('hidden');
} else {
+ visibleCount++;
$e.removeClass('hidden');
}
- });
- that.$container.trigger('scroll');
+ }
+
+ var $trs = this.$fileList.find('tr');
+ do {
+ _.each($trs, filterRows);
+ if (visibleCount < total) {
+ $trs = this._nextPage(false);
+ }
+ } while (visibleCount < total);
+
+ this.$container.trigger('scroll');
},
hideIrrelevantUIWhenNoFilesMatch:function() {
if (this._filter && this.fileSummary.summary.totalDirs + this.fileSummary.summary.totalFiles === 0) {
diff --git a/apps/files/js/search.js b/apps/files/js/search.js
index 394bcb48603..625e4b13f4b 100644
--- a/apps/files/js/search.js
+++ b/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;
diff --git a/apps/files/l10n/ast.js b/apps/files/l10n/ast.js
index 6fe07fed765..092610b7140 100644
--- a/apps/files/l10n/ast.js
+++ b/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",
diff --git a/apps/files/l10n/ast.json b/apps/files/l10n/ast.json
index 5b554ab8f93..7631a9149f2 100644
--- a/apps/files/l10n/ast.json
+++ b/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",
diff --git a/apps/files/l10n/bg_BG.js b/apps/files/l10n/bg_BG.js
index 3a00853fa18..d080e4b7c69 100644
--- a/apps/files/l10n/bg_BG.js
+++ b/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);");
diff --git a/apps/files/l10n/bg_BG.json b/apps/files/l10n/bg_BG.json
index 4d60be20692..080390bd8a6 100644
--- a/apps/files/l10n/bg_BG.json
+++ b/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);"
} \ No newline at end of file
diff --git a/apps/files/l10n/lb.js b/apps/files/l10n/lb.js
index 30a86b000bb..d7888f9f197 100644
--- a/apps/files/l10n/lb.js
+++ b/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",
diff --git a/apps/files/l10n/lb.json b/apps/files/l10n/lb.json
index 99a6b4845da..a736e06570b 100644
--- a/apps/files/l10n/lb.json
+++ b/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",
diff --git a/apps/files/l10n/sv.js b/apps/files/l10n/sv.js
index d2f7df4a3a4..b07bdf8b59b 100644
--- a/apps/files/l10n/sv.js
+++ b/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",
diff --git a/apps/files/l10n/sv.json b/apps/files/l10n/sv.json
index c2d1d464bc9..957088d80fc 100644
--- a/apps/files/l10n/sv.json
+++ b/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",
diff --git a/apps/files/list.php b/apps/files/list.php
index 6d2c463a2d9..2d7a304df24 100644
--- a/apps/files/list.php
+++ b/apps/files/list.php
@@ -20,9 +20,6 @@
*
*/
-// Check if we are a user
-OCP\User::checkLoggedIn();
-
$config = \OC::$server->getConfig();
// TODO: move this to the generated config.js
$publicUploadEnabled = $config->getAppValue('core', 'shareapi_allow_public_upload', 'yes');
diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js
index 44f271d8084..ae4b75f7771 100644
--- a/apps/files/tests/js/filelistSpec.js
+++ b/apps/files/tests/js/filelistSpec.js
@@ -989,6 +989,17 @@ describe('OCA.Files.FileList tests', function() {
expect($summary.find('.info').text()).toEqual("1 folder and 3 files");
expect($nofilterresults.hasClass('hidden')).toEqual(true);
});
+ it('filters the list of non-rendered rows using filter()', function() {
+ var $summary = $('#filestable .summary');
+ var $nofilterresults = fileList.$el.find(".nofilterresults");
+ fileList.setFiles(generateFiles(0, 64));
+
+ fileList.setFilter('63');
+ expect($('#fileList tr:not(.hidden)').length).toEqual(1);
+ expect($summary.hasClass('hidden')).toEqual(false);
+ expect($summary.find('.info').text()).toEqual("0 folders and 1 file matches '63'");
+ expect($nofilterresults.hasClass('hidden')).toEqual(true);
+ });
it('hides the emptyfiles notice when using filter()', function() {
expect(fileList.files.length).toEqual(0);
expect(fileList.files).toEqual([]);
diff --git a/apps/files_external/l10n/lb.js b/apps/files_external/l10n/lb.js
index 598458c731f..50bb06ff7f9 100644
--- a/apps/files_external/l10n/lb.js
+++ b/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",
diff --git a/apps/files_external/l10n/lb.json b/apps/files_external/l10n/lb.json
index 265d8ffda96..9f7aa84bb1a 100644
--- a/apps/files_external/l10n/lb.json
+++ b/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",
diff --git a/apps/files_external/l10n/sv.js b/apps/files_external/l10n/sv.js
index dfa978f202a..3df7763a5fe 100644
--- a/apps/files_external/l10n/sv.js
+++ b/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",
diff --git a/apps/files_external/l10n/sv.json b/apps/files_external/l10n/sv.json
index 528588bd702..1070a322e92 100644
--- a/apps/files_external/l10n/sv.json
+++ b/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",
diff --git a/apps/files_external/lib/AppInfo/Application.php b/apps/files_external/lib/AppInfo/Application.php
index ee773d03d8c..0ee4e9b3d5c 100644
--- a/apps/files_external/lib/AppInfo/Application.php
+++ b/apps/files_external/lib/AppInfo/Application.php
@@ -52,6 +52,10 @@ class Application extends App implements IBackendProvider, IAuthMechanismProvide
$backendService->registerBackendProvider($this);
$backendService->registerAuthMechanismProvider($this);
+ // force-load auth mechanisms since some will register hooks
+ // TODO: obsolete these and use the TokenProvider to get the user's password from the session
+ $this->getAuthMechanisms();
+
// app developers: do NOT depend on this! it will disappear with oC 9.0!
\OC::$server->getEventDispatcher()->dispatch(
'OCA\\Files_External::loadAdditionalBackends'
@@ -112,6 +116,8 @@ class Application extends App implements IBackendProvider, IAuthMechanismProvide
// AuthMechanism::SCHEME_PASSWORD mechanisms
$container->query('OCA\Files_External\Lib\Auth\Password\Password'),
$container->query('OCA\Files_External\Lib\Auth\Password\SessionCredentials'),
+ $container->query('OCA\Files_External\Lib\Auth\Password\LoginCredentials'),
+ $container->query('OCA\Files_External\Lib\Auth\Password\UserProvided'),
$container->query('OCA\Files_External\Lib\Auth\Password\GlobalAuth'),
// AuthMechanism::SCHEME_OAUTH1 mechanisms
diff --git a/apps/files_external/lib/Command/Export.php b/apps/files_external/lib/Command/Export.php
index 09c5ea8a9df..89655c1efc7 100644
--- a/apps/files_external/lib/Command/Export.php
+++ b/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);
diff --git a/apps/files_external/lib/Command/ListCommand.php b/apps/files_external/lib/Command/ListCommand.php
index 26133ea5ed5..e29a5f58f40 100644
--- a/apps/files_external/lib/Command/ListCommand.php
+++ b/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);
diff --git a/apps/files_external/lib/Controller/AjaxController.php b/apps/files_external/lib/Controller/AjaxController.php
index 8dcfb2a47eb..068abbb809d 100644
--- a/apps/files_external/lib/Controller/AjaxController.php
+++ b/apps/files_external/lib/Controller/AjaxController.php
@@ -26,25 +26,48 @@ namespace OCA\Files_External\Controller;
use OCA\Files_External\Lib\Auth\Password\GlobalAuth;
use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\Response;
+use OCP\IGroupManager;
use OCP\IRequest;
use OCP\AppFramework\Http\JSONResponse;
use OCA\Files_External\Lib\Auth\PublicKey\RSA;
+use OCP\IUserSession;
class AjaxController extends Controller {
/** @var RSA */
private $rsaMechanism;
/** @var GlobalAuth */
private $globalAuth;
+ /** @var IUserSession */
+ private $userSession;
+ /** @var IGroupManager */
+ private $groupManager;
+ /**
+ * @param string $appName
+ * @param IRequest $request
+ * @param RSA $rsaMechanism
+ * @param GlobalAuth $globalAuth
+ * @param IUserSession $userSession
+ * @param IGroupManager $groupManager
+ */
public function __construct($appName,
IRequest $request,
RSA $rsaMechanism,
- GlobalAuth $globalAuth) {
+ GlobalAuth $globalAuth,
+ IUserSession $userSession,
+ IGroupManager $groupManager) {
parent::__construct($appName, $request);
$this->rsaMechanism = $rsaMechanism;
$this->globalAuth = $globalAuth;
+ $this->userSession = $userSession;
+ $this->groupManager = $groupManager;
}
+ /**
+ * @return array
+ */
private function generateSshKeys() {
$key = $this->rsaMechanism->createKey();
// Replace the placeholder label with a more meaningful one
@@ -70,13 +93,26 @@ class AjaxController extends Controller {
}
/**
+ * @NoAdminRequired
+ *
* @param string $uid
* @param string $user
* @param string $password
* @return bool
*/
public function saveGlobalCredentials($uid, $user, $password) {
- $this->globalAuth->saveAuth($uid, $user, $password);
- return true;
+ $currentUser = $this->userSession->getUser();
+
+ // Non-admins can only edit their own credentials
+ $allowedToEdit = (
+ $this->groupManager->isAdmin($currentUser->getUID()) || $currentUser->getUID() === $uid
+ ) ? true : false;
+
+ if ($allowedToEdit) {
+ $this->globalAuth->saveAuth($uid, $user, $password);
+ return true;
+ } else {
+ return false;
+ }
}
}
diff --git a/apps/files_external/lib/Lib/Auth/Password/LoginCredentials.php b/apps/files_external/lib/Lib/Auth/Password/LoginCredentials.php
new file mode 100644
index 00000000000..25bd66fb41a
--- /dev/null
+++ b/apps/files_external/lib/Lib/Auth/Password/LoginCredentials.php
@@ -0,0 +1,92 @@
+<?php
+/**
+ * @author Robin McCorkell <rmccorkell@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, 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_External\Lib\Auth\Password;
+
+use \OCP\IL10N;
+use \OCP\IUser;
+use \OCA\Files_External\Lib\DefinitionParameter;
+use \OCA\Files_External\Lib\Auth\AuthMechanism;
+use \OCA\Files_External\Lib\StorageConfig;
+use \OCP\ISession;
+use \OCP\Security\ICredentialsManager;
+use \OCP\Files\Storage;
+use \OCA\Files_External\Lib\SessionStorageWrapper;
+use \OCA\Files_External\Lib\InsufficientDataForMeaningfulAnswerException;
+
+/**
+ * Username and password from login credentials, saved in DB
+ */
+class LoginCredentials extends AuthMechanism {
+
+ const CREDENTIALS_IDENTIFIER = 'password::logincredentials/credentials';
+
+ /** @var ISession */
+ protected $session;
+
+ /** @var ICredentialsManager */
+ protected $credentialsManager;
+
+ public function __construct(IL10N $l, ISession $session, ICredentialsManager $credentialsManager) {
+ $this->session = $session;
+ $this->credentialsManager = $credentialsManager;
+
+ $this
+ ->setIdentifier('password::logincredentials')
+ ->setScheme(self::SCHEME_PASSWORD)
+ ->setText($l->t('Log-in credentials, save in database'))
+ ->addParameters([
+ ])
+ ;
+
+ \OCP\Util::connectHook('OC_User', 'post_login', $this, 'authenticate');
+ }
+
+ /**
+ * Hook listener on post login
+ *
+ * @param array $params
+ */
+ public function authenticate(array $params) {
+ $userId = $params['uid'];
+ $credentials = [
+ 'user' => $this->session->get('loginname'),
+ 'password' => $params['password']
+ ];
+ $this->credentialsManager->store($userId, self::CREDENTIALS_IDENTIFIER, $credentials);
+ }
+
+ public function manipulateStorageConfig(StorageConfig &$storage, IUser $user = null) {
+ if (!isset($user)) {
+ throw new InsufficientDataForMeaningfulAnswerException('No login credentials saved');
+ }
+ $uid = $user->getUID();
+ $credentials = $this->credentialsManager->retrieve($uid, self::CREDENTIALS_IDENTIFIER);
+
+ if (!isset($credentials)) {
+ throw new InsufficientDataForMeaningfulAnswerException('No login credentials saved');
+ }
+
+ $storage->setBackendOption('user', $credentials['user']);
+ $storage->setBackendOption('password', $credentials['password']);
+ }
+
+}
diff --git a/apps/files_external/lib/Lib/Auth/Password/UserProvided.php b/apps/files_external/lib/Lib/Auth/Password/UserProvided.php
new file mode 100644
index 00000000000..2f277163184
--- /dev/null
+++ b/apps/files_external/lib/Lib/Auth/Password/UserProvided.php
@@ -0,0 +1,88 @@
+<?php
+/**
+ * @author Robin Appelman <icewind@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, 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_External\Lib\Auth\Password;
+
+use OCA\Files_External\Lib\Auth\IUserProvided;
+use OCA\Files_External\Lib\DefinitionParameter;
+use OCA\Files_External\Service\BackendService;
+use OCP\IL10N;
+use OCP\IUser;
+use OCA\Files_External\Lib\Auth\AuthMechanism;
+use OCA\Files_External\Lib\StorageConfig;
+use OCP\Security\ICredentialsManager;
+use OCP\Files\Storage;
+use OCA\Files_External\Lib\InsufficientDataForMeaningfulAnswerException;
+
+/**
+ * User provided Username and Password
+ */
+class UserProvided extends AuthMechanism implements IUserProvided {
+
+ const CREDENTIALS_IDENTIFIER_PREFIX = 'password::userprovided/';
+
+ /** @var ICredentialsManager */
+ protected $credentialsManager;
+
+ public function __construct(IL10N $l, ICredentialsManager $credentialsManager) {
+ $this->credentialsManager = $credentialsManager;
+
+ $this
+ ->setIdentifier('password::userprovided')
+ ->setVisibility(BackendService::VISIBILITY_ADMIN)
+ ->setScheme(self::SCHEME_PASSWORD)
+ ->setText($l->t('User entered, store in database'))
+ ->addParameters([
+ (new DefinitionParameter('user', $l->t('Username')))
+ ->setFlag(DefinitionParameter::FLAG_USER_PROVIDED),
+ (new DefinitionParameter('password', $l->t('Password')))
+ ->setType(DefinitionParameter::VALUE_PASSWORD)
+ ->setFlag(DefinitionParameter::FLAG_USER_PROVIDED),
+ ]);
+ }
+
+ private function getCredentialsIdentifier($storageId) {
+ return self::CREDENTIALS_IDENTIFIER_PREFIX . $storageId;
+ }
+
+ public function saveBackendOptions(IUser $user, $id, array $options) {
+ $this->credentialsManager->store($user->getUID(), $this->getCredentialsIdentifier($id), [
+ 'user' => $options['user'], // explicitly copy the fields we want instead of just passing the entire $options array
+ 'password' => $options['password'] // this way we prevent users from being able to modify any other field
+ ]);
+ }
+
+ public function manipulateStorageConfig(StorageConfig &$storage, IUser $user = null) {
+ if (!isset($user)) {
+ throw new InsufficientDataForMeaningfulAnswerException('No credentials saved');
+ }
+ $uid = $user->getUID();
+ $credentials = $this->credentialsManager->retrieve($uid, $this->getCredentialsIdentifier($storage->getId()));
+
+ if (!isset($credentials)) {
+ throw new InsufficientDataForMeaningfulAnswerException('No credentials saved');
+ }
+
+ $storage->setBackendOption('user', $credentials['user']);
+ $storage->setBackendOption('password', $credentials['password']);
+ }
+
+}
diff --git a/apps/files_external/lib/Service/DBConfigService.php b/apps/files_external/lib/Service/DBConfigService.php
index 9f7061eb938..3e38f4aed94 100644
--- a/apps/files_external/lib/Service/DBConfigService.php
+++ b/apps/files_external/lib/Service/DBConfigService.php
@@ -77,6 +77,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
*
* @return array
diff --git a/apps/files_external/lib/Service/GlobalStoragesService.php b/apps/files_external/lib/Service/GlobalStoragesService.php
index 96d7aa46e8d..39d3da5d803 100644
--- a/apps/files_external/lib/Service/GlobalStoragesService.php
+++ b/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);
+ }
}
diff --git a/apps/files_external/settings.php b/apps/files_external/settings.php
index e2c70062bf3..24cd9cd9aeb 100644
--- a/apps/files_external/settings.php
+++ b/apps/files_external/settings.php
@@ -26,8 +26,6 @@
use \OCA\Files_External\Service\BackendService;
-\OCP\User::checkAdminUser();
-
// we must use the same container
$appContainer = \OC_Mount_Config::$app->getContainer();
$backendService = $appContainer->query('OCA\Files_External\Service\BackendService');
diff --git a/apps/files_external/tests/Controller/AjaxControllerTest.php b/apps/files_external/tests/Controller/AjaxControllerTest.php
new file mode 100644
index 00000000000..c51172f8995
--- /dev/null
+++ b/apps/files_external/tests/Controller/AjaxControllerTest.php
@@ -0,0 +1,177 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OCA\Files_External\Tests\Controller;
+
+use OCA\Files_External\Controller\AjaxController;
+use OCA\Files_External\Lib\Auth\Password\GlobalAuth;
+use OCA\Files_External\Lib\Auth\PublicKey\RSA;
+use OCP\AppFramework\Http\JSONResponse;
+use OCP\IGroupManager;
+use OCP\IRequest;
+use OCP\IUserSession;
+use Test\TestCase;
+
+class AjaxControllerTest extends TestCase {
+ /** @var IRequest */
+ private $request;
+ /** @var RSA */
+ private $rsa;
+ /** @var GlobalAuth */
+ private $globalAuth;
+ /** @var IUserSession */
+ private $userSession;
+ /** @var IGroupManager */
+ private $groupManager;
+ /** @var AjaxController */
+ private $ajaxController;
+
+ public function setUp() {
+ $this->request = $this->getMock('\\OCP\\IRequest');
+ $this->rsa = $this->getMockBuilder('\\OCA\\Files_External\\Lib\\Auth\\PublicKey\\RSA')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->globalAuth = $this->getMockBuilder('\\OCA\\Files_External\\Lib\\Auth\\Password\GlobalAuth')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->userSession = $this->getMock('\\OCP\\IUserSession');
+ $this->groupManager = $this->getMock('\\OCP\\IGroupManager');
+
+ $this->ajaxController = new AjaxController(
+ 'files_external',
+ $this->request,
+ $this->rsa,
+ $this->globalAuth,
+ $this->userSession,
+ $this->groupManager
+ );
+
+ parent::setUp();
+ }
+
+ public function testGetSshKeys() {
+ $this->rsa
+ ->expects($this->once())
+ ->method('createKey')
+ ->willReturn([
+ 'privatekey' => 'MyPrivateKey',
+ 'publickey' => 'MyPublicKey',
+ ]);
+
+ $expected = new JSONResponse(
+ [
+ 'data' => [
+ 'private_key' => 'MyPrivateKey',
+ 'public_key' => 'MyPublicKey',
+ ],
+ 'status' => 'success',
+ ]
+ );
+ $this->assertEquals($expected, $this->ajaxController->getSshKeys());
+ }
+
+ public function testSaveGlobalCredentialsAsAdminForAnotherUser() {
+ $user = $this->getMock('\\OCP\\IUser');
+ $user
+ ->expects($this->once())
+ ->method('getUID')
+ ->willReturn('MyAdminUid');
+ $this->userSession
+ ->expects($this->once())
+ ->method('getUser')
+ ->willReturn($user);
+ $this->groupManager
+ ->expects($this->once())
+ ->method('isAdmin')
+ ->with('MyAdminUid')
+ ->willReturn(true);
+ $this->globalAuth
+ ->expects($this->once())
+ ->method('saveAuth')
+ ->with('UidOfTestUser', 'test', 'password');
+
+ $this->assertSame(true, $this->ajaxController->saveGlobalCredentials('UidOfTestUser', 'test', 'password'));
+ }
+
+ public function testSaveGlobalCredentialsAsAdminForSelf() {
+ $user = $this->getMock('\\OCP\\IUser');
+ $user
+ ->expects($this->once())
+ ->method('getUID')
+ ->willReturn('MyAdminUid');
+ $this->userSession
+ ->expects($this->once())
+ ->method('getUser')
+ ->willReturn($user);
+ $this->groupManager
+ ->expects($this->once())
+ ->method('isAdmin')
+ ->with('MyAdminUid')
+ ->willReturn(true);
+ $this->globalAuth
+ ->expects($this->once())
+ ->method('saveAuth')
+ ->with('MyAdminUid', 'test', 'password');
+
+ $this->assertSame(true, $this->ajaxController->saveGlobalCredentials('MyAdminUid', 'test', 'password'));
+ }
+
+ public function testSaveGlobalCredentialsAsNormalUserForSelf() {
+ $user = $this->getMock('\\OCP\\IUser');
+ $user
+ ->expects($this->exactly(2))
+ ->method('getUID')
+ ->willReturn('MyUserUid');
+ $this->userSession
+ ->expects($this->once())
+ ->method('getUser')
+ ->willReturn($user);
+ $this->groupManager
+ ->expects($this->once())
+ ->method('isAdmin')
+ ->with('MyUserUid')
+ ->willReturn(false);
+ $this->globalAuth
+ ->expects($this->once())
+ ->method('saveAuth')
+ ->with('MyUserUid', 'test', 'password');
+
+ $this->assertSame(true, $this->ajaxController->saveGlobalCredentials('MyUserUid', 'test', 'password'));
+ }
+
+ public function testSaveGlobalCredentialsAsNormalUserForAnotherUser() {
+ $user = $this->getMock('\\OCP\\IUser');
+ $user
+ ->expects($this->exactly(2))
+ ->method('getUID')
+ ->willReturn('MyUserUid');
+ $this->userSession
+ ->expects($this->once())
+ ->method('getUser')
+ ->willReturn($user);
+ $this->groupManager
+ ->expects($this->once())
+ ->method('isAdmin')
+ ->with('MyUserUid')
+ ->willReturn(false);
+
+ $this->assertSame(false, $this->ajaxController->saveGlobalCredentials('AnotherUserUid', 'test', 'password'));
+ }
+}
diff --git a/apps/files_external/tests/Service/DBConfigServiceTest.php b/apps/files_external/tests/Service/DBConfigServiceTest.php
index b088a7078d1..3cfa33f0bc8 100644
--- a/apps/files_external/tests/Service/DBConfigServiceTest.php
+++ b/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']);
+ }
}
diff --git a/apps/files_external/tests/env/start-swift-ceph.sh b/apps/files_external/tests/env/start-swift-ceph.sh
index ba17b8f42dd..b73fa899a6d 100755
--- a/apps/files_external/tests/env/start-swift-ceph.sh
+++ b/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
diff --git a/apps/files_sharing/ajax/external.php b/apps/files_sharing/ajax/external.php
index 5cf86087f94..4a7a6096c91 100644
--- a/apps/files_sharing/ajax/external.php
+++ b/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;
diff --git a/apps/files_sharing/l10n/lb.js b/apps/files_sharing/l10n/lb.js
index fcbfd04c64f..0679858d818 100644
--- a/apps/files_sharing/l10n/lb.js
+++ b/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",
diff --git a/apps/files_sharing/l10n/lb.json b/apps/files_sharing/l10n/lb.json
index 5a466e560c6..9355d70a6fb 100644
--- a/apps/files_sharing/l10n/lb.json
+++ b/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",
diff --git a/apps/files_sharing/lib/API/Share20OCS.php b/apps/files_sharing/lib/API/Share20OCS.php
index 3d6a715be99..436b8d15ac8 100644
--- a/apps/files_sharing/lib/API/Share20OCS.php
+++ b/apps/files_sharing/lib/API/Share20OCS.php
@@ -354,7 +354,8 @@ class Share20OCS {
$share->setPermissions(
\OCP\Constants::PERMISSION_READ |
\OCP\Constants::PERMISSION_CREATE |
- \OCP\Constants::PERMISSION_UPDATE
+ \OCP\Constants::PERMISSION_UPDATE |
+ \OCP\Constants::PERMISSION_DELETE
);
} else {
$share->setPermissions(\OCP\Constants::PERMISSION_READ);
@@ -591,7 +592,7 @@ class Share20OCS {
$newPermissions = null;
if ($publicUpload === 'true') {
- $newPermissions = \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE;
+ $newPermissions = \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE;
} else if ($publicUpload === 'false') {
$newPermissions = \OCP\Constants::PERMISSION_READ;
}
@@ -602,13 +603,21 @@ class Share20OCS {
if ($newPermissions !== null &&
$newPermissions !== \OCP\Constants::PERMISSION_READ &&
- $newPermissions !== (\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE) &&
- $newPermissions !== (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE)) {
+ // legacy
+ $newPermissions !== (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE) &&
+ // correct
+ $newPermissions !== (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE)
+ ) {
$share->getNode()->unlock(ILockingProvider::LOCK_SHARED);
return new \OC_OCS_Result(null, 400, $this->l->t('Can\'t change permissions for public share links'));
}
- if ($newPermissions === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE)) {
+ if (
+ // legacy
+ $newPermissions === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE) ||
+ // correct
+ $newPermissions === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE)
+ ) {
if (!$this->shareManager->shareApiLinkAllowPublicUpload()) {
$share->getNode()->unlock(ILockingProvider::LOCK_SHARED);
return new \OC_OCS_Result(null, 403, $this->l->t('Public upload disabled by the administrator'));
@@ -618,6 +627,9 @@ class Share20OCS {
$share->getNode()->unlock(ILockingProvider::LOCK_SHARED);
return new \OC_OCS_Result(null, 400, $this->l->t('Public upload is only possible for publicly shared folders'));
}
+
+ // normalize to correct public upload permissions
+ $newPermissions = \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE;
}
if ($newPermissions !== null) {
diff --git a/apps/files_sharing/lib/External/Storage.php b/apps/files_sharing/lib/External/Storage.php
index ca99393a1e0..29b9c7b563c 100644
--- a/apps/files_sharing/lib/External/Storage.php
+++ b/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();
diff --git a/apps/files_sharing/lib/MountProvider.php b/apps/files_sharing/lib/MountProvider.php
index a9ae48860c2..c71c0e8ddea 100644
--- a/apps/files_sharing/lib/MountProvider.php
+++ b/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 = [];
diff --git a/apps/files_sharing/tests/API/Share20OCSTest.php b/apps/files_sharing/tests/API/Share20OCSTest.php
index b760a0f47a0..6435c992f25 100644
--- a/apps/files_sharing/tests/API/Share20OCSTest.php
+++ b/apps/files_sharing/tests/API/Share20OCSTest.php
@@ -1035,7 +1035,7 @@ class Share20OCSTest extends \Test\TestCase {
$this->callback(function (\OCP\Share\IShare $share) use ($path) {
return $share->getNode() === $path &&
$share->getShareType() === \OCP\Share::SHARE_TYPE_LINK &&
- $share->getPermissions() === \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_DELETE &&
+ $share->getPermissions() === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE) &&
$share->getSharedBy() === 'currentUser' &&
$share->getPassword() === null &&
$share->getExpirationDate() === null;
@@ -1366,7 +1366,7 @@ class Share20OCSTest extends \Test\TestCase {
$date = new \DateTime('2000-01-01');
$date->setTime(0,0,0);
- return $share->getPermissions() === \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE && \OCP\Constants::PERMISSION_DELETE &&
+ return $share->getPermissions() === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE) &&
$share->getPassword() === 'password' &&
$share->getExpirationDate() == $date;
})
@@ -1379,6 +1379,44 @@ class Share20OCSTest extends \Test\TestCase {
$this->assertEquals($expected->getData(), $result->getData());
}
+ /**
+ * @dataProvider publicUploadParamsProvider
+ */
+ public function testUpdateLinkShareEnablePublicUpload($params) {
+ $ocs = $this->mockFormatShare();
+
+ $folder = $this->getMock('\OCP\Files\Folder');
+
+ $share = \OC::$server->getShareManager()->newShare();
+ $share->setPermissions(\OCP\Constants::PERMISSION_ALL)
+ ->setSharedBy($this->currentUser->getUID())
+ ->setShareType(\OCP\Share::SHARE_TYPE_LINK)
+ ->setPassword('password')
+ ->setNode($folder);
+
+ $this->request
+ ->method('getParam')
+ ->will($this->returnValueMap($params));
+
+ $this->shareManager->method('getShareById')->with('ocinternal:42')->willReturn($share);
+ $this->shareManager->method('shareApiLinkAllowPublicUpload')->willReturn(true);
+ $this->shareManager->method('getSharedWith')->willReturn([]);
+
+ $this->shareManager->expects($this->once())->method('updateShare')->with(
+ $this->callback(function (\OCP\Share\IShare $share) {
+ return $share->getPermissions() === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE) &&
+ $share->getPassword() === 'password' &&
+ $share->getExpirationDate() === null;
+ })
+ )->will($this->returnArgument(0));
+
+ $expected = new \OC_OCS_Result(null);
+ $result = $ocs->updateShare(42);
+
+ $this->assertEquals($expected->getMeta(), $result->getMeta());
+ $this->assertEquals($expected->getData(), $result->getData());
+ }
+
public function testUpdateLinkShareInvalidDate() {
$ocs = $this->mockFormatShare();
@@ -1408,7 +1446,30 @@ class Share20OCSTest extends \Test\TestCase {
$this->assertEquals($expected->getData(), $result->getData());
}
- public function testUpdateLinkSharePublicUploadNotAllowed() {
+ public function publicUploadParamsProvider() {
+ return [
+ [[
+ ['publicUpload', null, 'true'],
+ ['expireDate', '', null],
+ ['password', '', 'password'],
+ ]], [[
+ // legacy had no delete
+ ['permissions', null, \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE],
+ ['expireDate', '', null],
+ ['password', '', 'password'],
+ ]], [[
+ // correct
+ ['permissions', null, \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE],
+ ['expireDate', '', null],
+ ['password', '', 'password'],
+ ]],
+ ];
+ }
+
+ /**
+ * @dataProvider publicUploadParamsProvider
+ */
+ public function testUpdateLinkSharePublicUploadNotAllowed($params) {
$ocs = $this->mockFormatShare();
$folder = $this->getMock('\OCP\Files\Folder');
@@ -1421,11 +1482,7 @@ class Share20OCSTest extends \Test\TestCase {
$this->request
->method('getParam')
- ->will($this->returnValueMap([
- ['publicUpload', null, 'true'],
- ['expireDate', '', null],
- ['password', '', 'password'],
- ]));
+ ->will($this->returnValueMap($params));
$this->shareManager->method('getShareById')->with('ocinternal:42')->willReturn($share);
$this->shareManager->method('shareApiLinkAllowPublicUpload')->willReturn(false);
@@ -1585,7 +1642,7 @@ class Share20OCSTest extends \Test\TestCase {
$this->shareManager->expects($this->once())->method('updateShare')->with(
$this->callback(function (\OCP\Share\IShare $share) use ($date) {
- return $share->getPermissions() === \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_DELETE &&
+ return $share->getPermissions() === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE) &&
$share->getPassword() === 'password' &&
$share->getExpirationDate() === $date;
})
@@ -1625,7 +1682,7 @@ class Share20OCSTest extends \Test\TestCase {
$this->shareManager->expects($this->once())->method('updateShare')->with(
$this->callback(function (\OCP\Share\IShare $share) use ($date) {
- return $share->getPermissions() === \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_DELETE &&
+ return $share->getPermissions() === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE) &&
$share->getPassword() === 'password' &&
$share->getExpirationDate() === $date;
})
diff --git a/apps/files_sharing/tests/ApiTest.php b/apps/files_sharing/tests/ApiTest.php
index 058b0c4758c..40c9085353c 100644
--- a/apps/files_sharing/tests/ApiTest.php
+++ b/apps/files_sharing/tests/ApiTest.php
@@ -257,7 +257,13 @@ class ApiTest extends TestCase {
$this->assertTrue($result->succeeded());
$data = $result->getData();
- $this->assertEquals(7, $data['permissions']);
+ $this->assertEquals(
+ \OCP\Constants::PERMISSION_READ |
+ \OCP\Constants::PERMISSION_CREATE |
+ \OCP\Constants::PERMISSION_UPDATE |
+ \OCP\Constants::PERMISSION_DELETE,
+ $data['permissions']
+ );
$this->assertEmpty($data['expiration']);
$this->assertTrue(is_string($data['token']));
@@ -1081,7 +1087,13 @@ class ApiTest extends TestCase {
$this->assertTrue($result->succeeded());
$share1 = $this->shareManager->getShareById($share1->getFullId());
- $this->assertEquals(7, $share1->getPermissions());
+ $this->assertEquals(
+ \OCP\Constants::PERMISSION_READ |
+ \OCP\Constants::PERMISSION_CREATE |
+ \OCP\Constants::PERMISSION_UPDATE |
+ \OCP\Constants::PERMISSION_DELETE,
+ $share1->getPermissions()
+ );
// cleanup
$this->shareManager->deleteShare($share1);
diff --git a/apps/files_sharing/tests/MountProviderTest.php b/apps/files_sharing/tests/MountProviderTest.php
new file mode 100644
index 00000000000..f69098cde7b
--- /dev/null
+++ b/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());
+ }
+}
+
diff --git a/apps/files_trashbin/js/filelist.js b/apps/files_trashbin/js/filelist.js
index feed28d8fc7..510ab2c21bc 100644
--- a/apps/files_trashbin/js/filelist.js
+++ b/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
diff --git a/apps/files_trashbin/tests/js/filelistSpec.js b/apps/files_trashbin/tests/js/filelistSpec.js
index 05caaf27865..5e9a4cf27d1 100644
--- a/apps/files_trashbin/tests/js/filelistSpec.js
+++ b/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');
diff --git a/apps/files_versions/lib/Storage.php b/apps/files_versions/lib/Storage.php
index 638a1916f6a..93f8b848ce8 100644
--- a/apps/files_versions/lib/Storage.php
+++ b/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)) {
diff --git a/apps/systemtags/l10n/pl.js b/apps/systemtags/l10n/pl.js
index f5e4775d540..fca05665ec8 100644
--- a/apps/systemtags/l10n/pl.js
+++ b/apps/systemtags/l10n/pl.js
@@ -14,6 +14,7 @@ OC.L10N.register(
"%1$s updated system tag %3$s to %2$s" : "%1$s zaktualizowany system etykiet%3$s do %2$s",
"%1$s assigned system tag %3$s to %2$s" : "%1$s przypisywalny system etykiet%3$s do %2$s",
"%1$s unassigned system tag %3$s from %2$s" : "%1$s nieprzypisany system etykiet %3$s z %2$s",
+ "%s (restricted)" : "%s (ograniczone)",
"%s (invisible)" : "%s (niewidoczny)",
"No files in here" : "Brak plików",
"No entries found in this folder" : "Brak wpisów w tym folderze",
diff --git a/apps/systemtags/l10n/pl.json b/apps/systemtags/l10n/pl.json
index 6cb103ed4a5..97ae0230be4 100644
--- a/apps/systemtags/l10n/pl.json
+++ b/apps/systemtags/l10n/pl.json
@@ -12,6 +12,7 @@
"%1$s updated system tag %3$s to %2$s" : "%1$s zaktualizowany system etykiet%3$s do %2$s",
"%1$s assigned system tag %3$s to %2$s" : "%1$s przypisywalny system etykiet%3$s do %2$s",
"%1$s unassigned system tag %3$s from %2$s" : "%1$s nieprzypisany system etykiet %3$s z %2$s",
+ "%s (restricted)" : "%s (ograniczone)",
"%s (invisible)" : "%s (niewidoczny)",
"No files in here" : "Brak plików",
"No entries found in this folder" : "Brak wpisów w tym folderze",
diff --git a/apps/theming/appinfo/app.php b/apps/theming/appinfo/app.php
new file mode 100644
index 00000000000..edf2c7d345a
--- /dev/null
+++ b/apps/theming/appinfo/app.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Bjoern Schiessle <bjoern@schiessle.org>
+ * @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+\OCP\App::registerAdmin('theming', 'settings/settings-admin');
+
+$linkToCSS = \OC::$server->getURLGenerator()->linkToRoute(
+ 'theming.Theming.getStylesheet',
+ [
+ 'v' => \OC::$server->getConfig()->getAppValue('theming', 'cachebuster', '0'),
+ ]
+);
+\OC_Util::addHeader(
+ 'link',
+ [
+ 'rel' => 'stylesheet',
+ 'href' => $linkToCSS,
+ ]
+);
+
diff --git a/apps/theming/appinfo/info.xml b/apps/theming/appinfo/info.xml
new file mode 100644
index 00000000000..58c839f2758
--- /dev/null
+++ b/apps/theming/appinfo/info.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<info>
+ <id>theming</id>
+ <name>Theming</name>
+ <description>Adjust the Nextcloud theme</description>
+ <licence>AGPL</licence>
+ <author>Nextcloud</author>
+ <version>0.1.0</version>
+ <namespace>Theming</namespace>
+ <category>other</category>
+ <dependencies>
+ <owncloud min-version="9.0" max-version="9.1" />
+ </dependencies>
+ <default_enable/>
+</info>
diff --git a/apps/theming/appinfo/routes.php b/apps/theming/appinfo/routes.php
new file mode 100644
index 00000000000..ac0463e2c26
--- /dev/null
+++ b/apps/theming/appinfo/routes.php
@@ -0,0 +1,57 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Bjoern Schiessle <bjoern@schiessle.org>
+ * @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Theming\AppInfo;
+
+(new \OCP\AppFramework\App('theming'))->registerRoutes($this, array('routes' => array(
+ [
+ 'name' => 'Theming#updateStylesheet',
+ 'url' => '/ajax/updateStylesheet',
+ 'verb' => 'POST'
+ ],
+ [
+ 'name' => 'Theming#undo',
+ 'url' => '/ajax/undoChanges',
+ 'verb' => 'POST'
+ ],
+ [
+ 'name' => 'Theming#updateLogo',
+ 'url' => '/ajax/updateLogo',
+ 'verb' => 'POST'
+ ],
+ [
+ 'name' => 'Theming#getStylesheet',
+ 'url' => '/styles.css',
+ 'verb' => 'GET',
+ ],
+ [
+ 'name' => 'Theming#getLogo',
+ 'url' => '/logo',
+ 'verb' => 'GET',
+ ],
+ [
+ 'name' => 'Theming#getLoginBackground',
+ 'url' => '/loginbackground',
+ 'verb' => 'GET',
+ ],
+)));
+
diff --git a/apps/theming/css/settings-admin.css b/apps/theming/css/settings-admin.css
new file mode 100644
index 00000000000..53214b245c6
--- /dev/null
+++ b/apps/theming/css/settings-admin.css
@@ -0,0 +1,34 @@
+#theming input {
+ width: 17em;
+}
+
+#theming .upload-logo-field {
+ display: none;
+}
+
+#theming .theme-undo {
+ cursor: pointer;
+ opacity: .5;
+ padding: 9px;
+ vertical-align: bottom;
+}
+
+#theming .icon {
+ display: inline-block;
+}
+
+#theming label span {
+ display: inline-block;
+ min-width: 90px;
+ padding: 8px 0px;
+}
+
+#theming .icon-upload {
+ display: inline-flex;
+ padding: 8px;
+ margin: 0;
+}
+
+div#theming_settings_msg {
+ margin-left: 10px;
+}
diff --git a/apps/theming/js/3rdparty/jscolor/LICENSE.txt b/apps/theming/js/3rdparty/jscolor/LICENSE.txt
new file mode 100644
index 00000000000..94a9ed024d3
--- /dev/null
+++ b/apps/theming/js/3rdparty/jscolor/LICENSE.txt
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/apps/theming/js/3rdparty/jscolor/jscolor.js b/apps/theming/js/3rdparty/jscolor/jscolor.js
new file mode 100644
index 00000000000..2bdd4607b41
--- /dev/null
+++ b/apps/theming/js/3rdparty/jscolor/jscolor.js
@@ -0,0 +1,1844 @@
+/**
+ * jscolor - JavaScript Color Picker
+ *
+ * @link http://jscolor.com
+ * @license For open source use: GPLv3
+ * For commercial use: JSColor Commercial License
+ * @author Jan Odvarko
+ * @version 2.0.4
+ *
+ * See usage examples at http://jscolor.com/examples/
+ */
+
+
+"use strict";
+
+
+if (!window.jscolor) { window.jscolor = (function () {
+
+
+var jsc = {
+
+
+ register : function () {
+ jsc.attachDOMReadyEvent(jsc.init);
+ jsc.attachEvent(document, 'mousedown', jsc.onDocumentMouseDown);
+ jsc.attachEvent(document, 'touchstart', jsc.onDocumentTouchStart);
+ jsc.attachEvent(window, 'resize', jsc.onWindowResize);
+ },
+
+
+ init : function () {
+ if (jsc.jscolor.lookupClass) {
+ jsc.jscolor.installByClassName(jsc.jscolor.lookupClass);
+ }
+ },
+
+
+ tryInstallOnElements : function (elms, className) {
+ var matchClass = new RegExp('(^|\\s)(' + className + ')(\\s*(\\{[^}]*\\})|\\s|$)', 'i');
+
+ for (var i = 0; i < elms.length; i += 1) {
+ if (elms[i].type !== undefined && elms[i].type.toLowerCase() == 'color') {
+ if (jsc.isColorAttrSupported) {
+ // skip inputs of type 'color' if supported by the browser
+ continue;
+ }
+ }
+ var m;
+ if (!elms[i].jscolor && elms[i].className && (m = elms[i].className.match(matchClass))) {
+ var targetElm = elms[i];
+ var optsStr = null;
+
+ var dataOptions = jsc.getDataAttr(targetElm, 'jscolor');
+ if (dataOptions !== null) {
+ optsStr = dataOptions;
+ } else if (m[4]) {
+ optsStr = m[4];
+ }
+
+ var opts = {};
+ if (optsStr) {
+ try {
+ opts = (new Function ('return (' + optsStr + ')'))();
+ } catch(eParseError) {
+ jsc.warn('Error parsing jscolor options: ' + eParseError + ':\n' + optsStr);
+ }
+ }
+ targetElm.jscolor = new jsc.jscolor(targetElm, opts);
+ }
+ }
+ },
+
+
+ isColorAttrSupported : (function () {
+ var elm = document.createElement('input');
+ if (elm.setAttribute) {
+ elm.setAttribute('type', 'color');
+ if (elm.type.toLowerCase() == 'color') {
+ return true;
+ }
+ }
+ return false;
+ })(),
+
+
+ isCanvasSupported : (function () {
+ var elm = document.createElement('canvas');
+ return !!(elm.getContext && elm.getContext('2d'));
+ })(),
+
+
+ fetchElement : function (mixed) {
+ return typeof mixed === 'string' ? document.getElementById(mixed) : mixed;
+ },
+
+
+ isElementType : function (elm, type) {
+ return elm.nodeName.toLowerCase() === type.toLowerCase();
+ },
+
+
+ getDataAttr : function (el, name) {
+ var attrName = 'data-' + name;
+ var attrValue = el.getAttribute(attrName);
+ if (attrValue !== null) {
+ return attrValue;
+ }
+ return null;
+ },
+
+
+ attachEvent : function (el, evnt, func) {
+ if (el.addEventListener) {
+ el.addEventListener(evnt, func, false);
+ } else if (el.attachEvent) {
+ el.attachEvent('on' + evnt, func);
+ }
+ },
+
+
+ detachEvent : function (el, evnt, func) {
+ if (el.removeEventListener) {
+ el.removeEventListener(evnt, func, false);
+ } else if (el.detachEvent) {
+ el.detachEvent('on' + evnt, func);
+ }
+ },
+
+
+ _attachedGroupEvents : {},
+
+
+ attachGroupEvent : function (groupName, el, evnt, func) {
+ if (!jsc._attachedGroupEvents.hasOwnProperty(groupName)) {
+ jsc._attachedGroupEvents[groupName] = [];
+ }
+ jsc._attachedGroupEvents[groupName].push([el, evnt, func]);
+ jsc.attachEvent(el, evnt, func);
+ },
+
+
+ detachGroupEvents : function (groupName) {
+ if (jsc._attachedGroupEvents.hasOwnProperty(groupName)) {
+ for (var i = 0; i < jsc._attachedGroupEvents[groupName].length; i += 1) {
+ var evt = jsc._attachedGroupEvents[groupName][i];
+ jsc.detachEvent(evt[0], evt[1], evt[2]);
+ }
+ delete jsc._attachedGroupEvents[groupName];
+ }
+ },
+
+
+ attachDOMReadyEvent : function (func) {
+ var fired = false;
+ var fireOnce = function () {
+ if (!fired) {
+ fired = true;
+ func();
+ }
+ };
+
+ if (document.readyState === 'complete') {
+ setTimeout(fireOnce, 1); // async
+ return;
+ }
+
+ if (document.addEventListener) {
+ document.addEventListener('DOMContentLoaded', fireOnce, false);
+
+ // Fallback
+ window.addEventListener('load', fireOnce, false);
+
+ } else if (document.attachEvent) {
+ // IE
+ document.attachEvent('onreadystatechange', function () {
+ if (document.readyState === 'complete') {
+ document.detachEvent('onreadystatechange', arguments.callee);
+ fireOnce();
+ }
+ })
+
+ // Fallback
+ window.attachEvent('onload', fireOnce);
+
+ // IE7/8
+ if (document.documentElement.doScroll && window == window.top) {
+ var tryScroll = function () {
+ if (!document.body) { return; }
+ try {
+ document.documentElement.doScroll('left');
+ fireOnce();
+ } catch (e) {
+ setTimeout(tryScroll, 1);
+ }
+ };
+ tryScroll();
+ }
+ }
+ },
+
+
+ warn : function (msg) {
+ if (window.console && window.console.warn) {
+ window.console.warn(msg);
+ }
+ },
+
+
+ preventDefault : function (e) {
+ if (e.preventDefault) { e.preventDefault(); }
+ e.returnValue = false;
+ },
+
+
+ captureTarget : function (target) {
+ // IE
+ if (target.setCapture) {
+ jsc._capturedTarget = target;
+ jsc._capturedTarget.setCapture();
+ }
+ },
+
+
+ releaseTarget : function () {
+ // IE
+ if (jsc._capturedTarget) {
+ jsc._capturedTarget.releaseCapture();
+ jsc._capturedTarget = null;
+ }
+ },
+
+
+ fireEvent : function (el, evnt) {
+ if (!el) {
+ return;
+ }
+ if (document.createEvent) {
+ var ev = document.createEvent('HTMLEvents');
+ ev.initEvent(evnt, true, true);
+ el.dispatchEvent(ev);
+ } else if (document.createEventObject) {
+ var ev = document.createEventObject();
+ el.fireEvent('on' + evnt, ev);
+ } else if (el['on' + evnt]) { // alternatively use the traditional event model
+ el['on' + evnt]();
+ }
+ },
+
+
+ classNameToList : function (className) {
+ return className.replace(/^\s+|\s+$/g, '').split(/\s+/);
+ },
+
+
+ // The className parameter (str) can only contain a single class name
+ hasClass : function (elm, className) {
+ if (!className) {
+ return false;
+ }
+ return -1 != (' ' + elm.className.replace(/\s+/g, ' ') + ' ').indexOf(' ' + className + ' ');
+ },
+
+
+ // The className parameter (str) can contain multiple class names separated by whitespace
+ setClass : function (elm, className) {
+ var classList = jsc.classNameToList(className);
+ for (var i = 0; i < classList.length; i += 1) {
+ if (!jsc.hasClass(elm, classList[i])) {
+ elm.className += (elm.className ? ' ' : '') + classList[i];
+ }
+ }
+ },
+
+
+ // The className parameter (str) can contain multiple class names separated by whitespace
+ unsetClass : function (elm, className) {
+ var classList = jsc.classNameToList(className);
+ for (var i = 0; i < classList.length; i += 1) {
+ var repl = new RegExp(
+ '^\\s*' + classList[i] + '\\s*|' +
+ '\\s*' + classList[i] + '\\s*$|' +
+ '\\s+' + classList[i] + '(\\s+)',
+ 'g'
+ );
+ elm.className = elm.className.replace(repl, '$1');
+ }
+ },
+
+
+ getStyle : function (elm) {
+ return window.getComputedStyle ? window.getComputedStyle(elm) : elm.currentStyle;
+ },
+
+
+ setStyle : (function () {
+ var helper = document.createElement('div');
+ var getSupportedProp = function (names) {
+ for (var i = 0; i < names.length; i += 1) {
+ if (names[i] in helper.style) {
+ return names[i];
+ }
+ }
+ };
+ var props = {
+ borderRadius: getSupportedProp(['borderRadius', 'MozBorderRadius', 'webkitBorderRadius']),
+ boxShadow: getSupportedProp(['boxShadow', 'MozBoxShadow', 'webkitBoxShadow'])
+ };
+ return function (elm, prop, value) {
+ switch (prop.toLowerCase()) {
+ case 'opacity':
+ var alphaOpacity = Math.round(parseFloat(value) * 100);
+ elm.style.opacity = value;
+ elm.style.filter = 'alpha(opacity=' + alphaOpacity + ')';
+ break;
+ default:
+ elm.style[props[prop]] = value;
+ break;
+ }
+ };
+ })(),
+
+
+ setBorderRadius : function (elm, value) {
+ jsc.setStyle(elm, 'borderRadius', value || '0');
+ },
+
+
+ setBoxShadow : function (elm, value) {
+ jsc.setStyle(elm, 'boxShadow', value || 'none');
+ },
+
+
+ getElementPos : function (e, relativeToViewport) {
+ var x=0, y=0;
+ var rect = e.getBoundingClientRect();
+ x = rect.left;
+ y = rect.top;
+ if (!relativeToViewport) {
+ var viewPos = jsc.getViewPos();
+ x += viewPos[0];
+ y += viewPos[1];
+ }
+ return [x, y];
+ },
+
+
+ getElementSize : function (e) {
+ return [e.offsetWidth, e.offsetHeight];
+ },
+
+
+ // get pointer's X/Y coordinates relative to viewport
+ getAbsPointerPos : function (e) {
+ if (!e) { e = window.event; }
+ var x = 0, y = 0;
+ if (typeof e.changedTouches !== 'undefined' && e.changedTouches.length) {
+ // touch devices
+ x = e.changedTouches[0].clientX;
+ y = e.changedTouches[0].clientY;
+ } else if (typeof e.clientX === 'number') {
+ x = e.clientX;
+ y = e.clientY;
+ }
+ return { x: x, y: y };
+ },
+
+
+ // get pointer's X/Y coordinates relative to target element
+ getRelPointerPos : function (e) {
+ if (!e) { e = window.event; }
+ var target = e.target || e.srcElement;
+ var targetRect = target.getBoundingClientRect();
+
+ var x = 0, y = 0;
+
+ var clientX = 0, clientY = 0;
+ if (typeof e.changedTouches !== 'undefined' && e.changedTouches.length) {
+ // touch devices
+ clientX = e.changedTouches[0].clientX;
+ clientY = e.changedTouches[0].clientY;
+ } else if (typeof e.clientX === 'number') {
+ clientX = e.clientX;
+ clientY = e.clientY;
+ }
+
+ x = clientX - targetRect.left;
+ y = clientY - targetRect.top;
+ return { x: x, y: y };
+ },
+
+
+ getViewPos : function () {
+ var doc = document.documentElement;
+ return [
+ (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0),
+ (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0)
+ ];
+ },
+
+
+ getViewSize : function () {
+ var doc = document.documentElement;
+ return [
+ (window.innerWidth || doc.clientWidth),
+ (window.innerHeight || doc.clientHeight),
+ ];
+ },
+
+
+ redrawPosition : function () {
+
+ if (jsc.picker && jsc.picker.owner) {
+ var thisObj = jsc.picker.owner;
+
+ var tp, vp;
+
+ if (thisObj.fixed) {
+ // Fixed elements are positioned relative to viewport,
+ // therefore we can ignore the scroll offset
+ tp = jsc.getElementPos(thisObj.targetElement, true); // target pos
+ vp = [0, 0]; // view pos
+ } else {
+ tp = jsc.getElementPos(thisObj.targetElement); // target pos
+ vp = jsc.getViewPos(); // view pos
+ }
+
+ var ts = jsc.getElementSize(thisObj.targetElement); // target size
+ var vs = jsc.getViewSize(); // view size
+ var ps = jsc.getPickerOuterDims(thisObj); // picker size
+ var a, b, c;
+ switch (thisObj.position.toLowerCase()) {
+ case 'left': a=1; b=0; c=-1; break;
+ case 'right':a=1; b=0; c=1; break;
+ case 'top': a=0; b=1; c=-1; break;
+ default: a=0; b=1; c=1; break;
+ }
+ var l = (ts[b]+ps[b])/2;
+
+ // compute picker position
+ if (!thisObj.smartPosition) {
+ var pp = [
+ tp[a],
+ tp[b]+ts[b]-l+l*c
+ ];
+ } else {
+ var pp = [
+ -vp[a]+tp[a]+ps[a] > vs[a] ?
+ (-vp[a]+tp[a]+ts[a]/2 > vs[a]/2 && tp[a]+ts[a]-ps[a] >= 0 ? tp[a]+ts[a]-ps[a] : tp[a]) :
+ tp[a],
+ -vp[b]+tp[b]+ts[b]+ps[b]-l+l*c > vs[b] ?
+ (-vp[b]+tp[b]+ts[b]/2 > vs[b]/2 && tp[b]+ts[b]-l-l*c >= 0 ? tp[b]+ts[b]-l-l*c : tp[b]+ts[b]-l+l*c) :
+ (tp[b]+ts[b]-l+l*c >= 0 ? tp[b]+ts[b]-l+l*c : tp[b]+ts[b]-l-l*c)
+ ];
+ }
+
+ var x = pp[a];
+ var y = pp[b];
+ var positionValue = thisObj.fixed ? 'fixed' : 'absolute';
+ var contractShadow =
+ (pp[0] + ps[0] > tp[0] || pp[0] < tp[0] + ts[0]) &&
+ (pp[1] + ps[1] < tp[1] + ts[1]);
+
+ jsc._drawPosition(thisObj, x, y, positionValue, contractShadow);
+ }
+ },
+
+
+ _drawPosition : function (thisObj, x, y, positionValue, contractShadow) {
+ var vShadow = contractShadow ? 0 : thisObj.shadowBlur; // px
+
+ jsc.picker.wrap.style.position = positionValue;
+ jsc.picker.wrap.style.left = x + 'px';
+ jsc.picker.wrap.style.top = y + 'px';
+
+ jsc.setBoxShadow(
+ jsc.picker.boxS,
+ thisObj.shadow ?
+ new jsc.BoxShadow(0, vShadow, thisObj.shadowBlur, 0, thisObj.shadowColor) :
+ null);
+ },
+
+
+ getPickerDims : function (thisObj) {
+ var displaySlider = !!jsc.getSliderComponent(thisObj);
+ var dims = [
+ 2 * thisObj.insetWidth + 2 * thisObj.padding + thisObj.width +
+ (displaySlider ? 2 * thisObj.insetWidth + jsc.getPadToSliderPadding(thisObj) + thisObj.sliderSize : 0),
+ 2 * thisObj.insetWidth + 2 * thisObj.padding + thisObj.height +
+ (thisObj.closable ? 2 * thisObj.insetWidth + thisObj.padding + thisObj.buttonHeight : 0)
+ ];
+ return dims;
+ },
+
+
+ getPickerOuterDims : function (thisObj) {
+ var dims = jsc.getPickerDims(thisObj);
+ return [
+ dims[0] + 2 * thisObj.borderWidth,
+ dims[1] + 2 * thisObj.borderWidth
+ ];
+ },
+
+
+ getPadToSliderPadding : function (thisObj) {
+ return Math.max(thisObj.padding, 1.5 * (2 * thisObj.pointerBorderWidth + thisObj.pointerThickness));
+ },
+
+
+ getPadYComponent : function (thisObj) {
+ switch (thisObj.mode.charAt(1).toLowerCase()) {
+ case 'v': return 'v'; break;
+ }
+ return 's';
+ },
+
+
+ getSliderComponent : function (thisObj) {
+ if (thisObj.mode.length > 2) {
+ switch (thisObj.mode.charAt(2).toLowerCase()) {
+ case 's': return 's'; break;
+ case 'v': return 'v'; break;
+ }
+ }
+ return null;
+ },
+
+
+ onDocumentMouseDown : function (e) {
+ if (!e) { e = window.event; }
+ var target = e.target || e.srcElement;
+
+ if (target._jscLinkedInstance) {
+ if (target._jscLinkedInstance.showOnClick) {
+ target._jscLinkedInstance.show();
+ }
+ } else if (target._jscControlName) {
+ jsc.onControlPointerStart(e, target, target._jscControlName, 'mouse');
+ } else {
+ // Mouse is outside the picker controls -> hide the color picker!
+ if (jsc.picker && jsc.picker.owner) {
+ jsc.picker.owner.hide();
+ }
+ }
+ },
+
+
+ onDocumentTouchStart : function (e) {
+ if (!e) { e = window.event; }
+ var target = e.target || e.srcElement;
+
+ if (target._jscLinkedInstance) {
+ if (target._jscLinkedInstance.showOnClick) {
+ target._jscLinkedInstance.show();
+ }
+ } else if (target._jscControlName) {
+ jsc.onControlPointerStart(e, target, target._jscControlName, 'touch');
+ } else {
+ if (jsc.picker && jsc.picker.owner) {
+ jsc.picker.owner.hide();
+ }
+ }
+ },
+
+
+ onWindowResize : function (e) {
+ jsc.redrawPosition();
+ },
+
+
+ onParentScroll : function (e) {
+ // hide the picker when one of the parent elements is scrolled
+ if (jsc.picker && jsc.picker.owner) {
+ jsc.picker.owner.hide();
+ }
+ },
+
+
+ _pointerMoveEvent : {
+ mouse: 'mousemove',
+ touch: 'touchmove'
+ },
+ _pointerEndEvent : {
+ mouse: 'mouseup',
+ touch: 'touchend'
+ },
+
+
+ _pointerOrigin : null,
+ _capturedTarget : null,
+
+
+ onControlPointerStart : function (e, target, controlName, pointerType) {
+ var thisObj = target._jscInstance;
+
+ jsc.preventDefault(e);
+ jsc.captureTarget(target);
+
+ var registerDragEvents = function (doc, offset) {
+ jsc.attachGroupEvent('drag', doc, jsc._pointerMoveEvent[pointerType],
+ jsc.onDocumentPointerMove(e, target, controlName, pointerType, offset));
+ jsc.attachGroupEvent('drag', doc, jsc._pointerEndEvent[pointerType],
+ jsc.onDocumentPointerEnd(e, target, controlName, pointerType));
+ };
+
+ registerDragEvents(document, [0, 0]);
+
+ if (window.parent && window.frameElement) {
+ var rect = window.frameElement.getBoundingClientRect();
+ var ofs = [-rect.left, -rect.top];
+ registerDragEvents(window.parent.window.document, ofs);
+ }
+
+ var abs = jsc.getAbsPointerPos(e);
+ var rel = jsc.getRelPointerPos(e);
+ jsc._pointerOrigin = {
+ x: abs.x - rel.x,
+ y: abs.y - rel.y
+ };
+
+ switch (controlName) {
+ case 'pad':
+ // if the slider is at the bottom, move it up
+ switch (jsc.getSliderComponent(thisObj)) {
+ case 's': if (thisObj.hsv[1] === 0) { thisObj.fromHSV(null, 100, null); }; break;
+ case 'v': if (thisObj.hsv[2] === 0) { thisObj.fromHSV(null, null, 100); }; break;
+ }
+ jsc.setPad(thisObj, e, 0, 0);
+ break;
+
+ case 'sld':
+ jsc.setSld(thisObj, e, 0);
+ break;
+ }
+
+ jsc.dispatchFineChange(thisObj);
+ },
+
+
+ onDocumentPointerMove : function (e, target, controlName, pointerType, offset) {
+ return function (e) {
+ var thisObj = target._jscInstance;
+ switch (controlName) {
+ case 'pad':
+ if (!e) { e = window.event; }
+ jsc.setPad(thisObj, e, offset[0], offset[1]);
+ jsc.dispatchFineChange(thisObj);
+ break;
+
+ case 'sld':
+ if (!e) { e = window.event; }
+ jsc.setSld(thisObj, e, offset[1]);
+ jsc.dispatchFineChange(thisObj);
+ break;
+ }
+ }
+ },
+
+
+ onDocumentPointerEnd : function (e, target, controlName, pointerType) {
+ return function (e) {
+ var thisObj = target._jscInstance;
+ jsc.detachGroupEvents('drag');
+ jsc.releaseTarget();
+ // Always dispatch changes after detaching outstanding mouse handlers,
+ // in case some user interaction will occur in user's onchange callback
+ // that would intrude with current mouse events
+ jsc.dispatchChange(thisObj);
+ };
+ },
+
+
+ dispatchChange : function (thisObj) {
+ if (thisObj.valueElement) {
+ if (jsc.isElementType(thisObj.valueElement, 'input')) {
+ jsc.fireEvent(thisObj.valueElement, 'change');
+ }
+ }
+ },
+
+
+ dispatchFineChange : function (thisObj) {
+ if (thisObj.onFineChange) {
+ var callback;
+ if (typeof thisObj.onFineChange === 'string') {
+ callback = new Function (thisObj.onFineChange);
+ } else {
+ callback = thisObj.onFineChange;
+ }
+ callback.call(thisObj);
+ }
+ },
+
+
+ setPad : function (thisObj, e, ofsX, ofsY) {
+ var pointerAbs = jsc.getAbsPointerPos(e);
+ var x = ofsX + pointerAbs.x - jsc._pointerOrigin.x - thisObj.padding - thisObj.insetWidth;
+ var y = ofsY + pointerAbs.y - jsc._pointerOrigin.y - thisObj.padding - thisObj.insetWidth;
+
+ var xVal = x * (360 / (thisObj.width - 1));
+ var yVal = 100 - (y * (100 / (thisObj.height - 1)));
+
+ switch (jsc.getPadYComponent(thisObj)) {
+ case 's': thisObj.fromHSV(xVal, yVal, null, jsc.leaveSld); break;
+ case 'v': thisObj.fromHSV(xVal, null, yVal, jsc.leaveSld); break;
+ }
+ },
+
+
+ setSld : function (thisObj, e, ofsY) {
+ var pointerAbs = jsc.getAbsPointerPos(e);
+ var y = ofsY + pointerAbs.y - jsc._pointerOrigin.y - thisObj.padding - thisObj.insetWidth;
+
+ var yVal = 100 - (y * (100 / (thisObj.height - 1)));
+
+ switch (jsc.getSliderComponent(thisObj)) {
+ case 's': thisObj.fromHSV(null, yVal, null, jsc.leavePad); break;
+ case 'v': thisObj.fromHSV(null, null, yVal, jsc.leavePad); break;
+ }
+ },
+
+
+ _vmlNS : 'jsc_vml_',
+ _vmlCSS : 'jsc_vml_css_',
+ _vmlReady : false,
+
+
+ initVML : function () {
+ if (!jsc._vmlReady) {
+ // init VML namespace
+ var doc = document;
+ if (!doc.namespaces[jsc._vmlNS]) {
+ doc.namespaces.add(jsc._vmlNS, 'urn:schemas-microsoft-com:vml');
+ }
+ if (!doc.styleSheets[jsc._vmlCSS]) {
+ var tags = ['shape', 'shapetype', 'group', 'background', 'path', 'formulas', 'handles', 'fill', 'stroke', 'shadow', 'textbox', 'textpath', 'imagedata', 'line', 'polyline', 'curve', 'rect', 'roundrect', 'oval', 'arc', 'image'];
+ var ss = doc.createStyleSheet();
+ ss.owningElement.id = jsc._vmlCSS;
+ for (var i = 0; i < tags.length; i += 1) {
+ ss.addRule(jsc._vmlNS + '\\:' + tags[i], 'behavior:url(#default#VML);');
+ }
+ }
+ jsc._vmlReady = true;
+ }
+ },
+
+
+ createPalette : function () {
+
+ var paletteObj = {
+ elm: null,
+ draw: null
+ };
+
+ if (jsc.isCanvasSupported) {
+ // Canvas implementation for modern browsers
+
+ var canvas = document.createElement('canvas');
+ var ctx = canvas.getContext('2d');
+
+ var drawFunc = function (width, height, type) {
+ canvas.width = width;
+ canvas.height = height;
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ var hGrad = ctx.createLinearGradient(0, 0, canvas.width, 0);
+ hGrad.addColorStop(0 / 6, '#F00');
+ hGrad.addColorStop(1 / 6, '#FF0');
+ hGrad.addColorStop(2 / 6, '#0F0');
+ hGrad.addColorStop(3 / 6, '#0FF');
+ hGrad.addColorStop(4 / 6, '#00F');
+ hGrad.addColorStop(5 / 6, '#F0F');
+ hGrad.addColorStop(6 / 6, '#F00');
+
+ ctx.fillStyle = hGrad;
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+
+ var vGrad = ctx.createLinearGradient(0, 0, 0, canvas.height);
+ switch (type.toLowerCase()) {
+ case 's':
+ vGrad.addColorStop(0, 'rgba(255,255,255,0)');
+ vGrad.addColorStop(1, 'rgba(255,255,255,1)');
+ break;
+ case 'v':
+ vGrad.addColorStop(0, 'rgba(0,0,0,0)');
+ vGrad.addColorStop(1, 'rgba(0,0,0,1)');
+ break;
+ }
+ ctx.fillStyle = vGrad;
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+ };
+
+ paletteObj.elm = canvas;
+ paletteObj.draw = drawFunc;
+
+ } else {
+ // VML fallback for IE 7 and 8
+
+ jsc.initVML();
+
+ var vmlContainer = document.createElement('div');
+ vmlContainer.style.position = 'relative';
+ vmlContainer.style.overflow = 'hidden';
+
+ var hGrad = document.createElement(jsc._vmlNS + ':fill');
+ hGrad.type = 'gradient';
+ hGrad.method = 'linear';
+ hGrad.angle = '90';
+ hGrad.colors = '16.67% #F0F, 33.33% #00F, 50% #0FF, 66.67% #0F0, 83.33% #FF0'
+
+ var hRect = document.createElement(jsc._vmlNS + ':rect');
+ hRect.style.position = 'absolute';
+ hRect.style.left = -1 + 'px';
+ hRect.style.top = -1 + 'px';
+ hRect.stroked = false;
+ hRect.appendChild(hGrad);
+ vmlContainer.appendChild(hRect);
+
+ var vGrad = document.createElement(jsc._vmlNS + ':fill');
+ vGrad.type = 'gradient';
+ vGrad.method = 'linear';
+ vGrad.angle = '180';
+ vGrad.opacity = '0';
+
+ var vRect = document.createElement(jsc._vmlNS + ':rect');
+ vRect.style.position = 'absolute';
+ vRect.style.left = -1 + 'px';
+ vRect.style.top = -1 + 'px';
+ vRect.stroked = false;
+ vRect.appendChild(vGrad);
+ vmlContainer.appendChild(vRect);
+
+ var drawFunc = function (width, height, type) {
+ vmlContainer.style.width = width + 'px';
+ vmlContainer.style.height = height + 'px';
+
+ hRect.style.width =
+ vRect.style.width =
+ (width + 1) + 'px';
+ hRect.style.height =
+ vRect.style.height =
+ (height + 1) + 'px';
+
+ // Colors must be specified during every redraw, otherwise IE won't display
+ // a full gradient during a subsequential redraw
+ hGrad.color = '#F00';
+ hGrad.color2 = '#F00';
+
+ switch (type.toLowerCase()) {
+ case 's':
+ vGrad.color = vGrad.color2 = '#FFF';
+ break;
+ case 'v':
+ vGrad.color = vGrad.color2 = '#000';
+ break;
+ }
+ };
+
+ paletteObj.elm = vmlContainer;
+ paletteObj.draw = drawFunc;
+ }
+
+ return paletteObj;
+ },
+
+
+ createSliderGradient : function () {
+
+ var sliderObj = {
+ elm: null,
+ draw: null
+ };
+
+ if (jsc.isCanvasSupported) {
+ // Canvas implementation for modern browsers
+
+ var canvas = document.createElement('canvas');
+ var ctx = canvas.getContext('2d');
+
+ var drawFunc = function (width, height, color1, color2) {
+ canvas.width = width;
+ canvas.height = height;
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ var grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
+ grad.addColorStop(0, color1);
+ grad.addColorStop(1, color2);
+
+ ctx.fillStyle = grad;
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+ };
+
+ sliderObj.elm = canvas;
+ sliderObj.draw = drawFunc;
+
+ } else {
+ // VML fallback for IE 7 and 8
+
+ jsc.initVML();
+
+ var vmlContainer = document.createElement('div');
+ vmlContainer.style.position = 'relative';
+ vmlContainer.style.overflow = 'hidden';
+
+ var grad = document.createElement(jsc._vmlNS + ':fill');
+ grad.type = 'gradient';
+ grad.method = 'linear';
+ grad.angle = '180';
+
+ var rect = document.createElement(jsc._vmlNS + ':rect');
+ rect.style.position = 'absolute';
+ rect.style.left = -1 + 'px';
+ rect.style.top = -1 + 'px';
+ rect.stroked = false;
+ rect.appendChild(grad);
+ vmlContainer.appendChild(rect);
+
+ var drawFunc = function (width, height, color1, color2) {
+ vmlContainer.style.width = width + 'px';
+ vmlContainer.style.height = height + 'px';
+
+ rect.style.width = (width + 1) + 'px';
+ rect.style.height = (height + 1) + 'px';
+
+ grad.color = color1;
+ grad.color2 = color2;
+ };
+
+ sliderObj.elm = vmlContainer;
+ sliderObj.draw = drawFunc;
+ }
+
+ return sliderObj;
+ },
+
+
+ leaveValue : 1<<0,
+ leaveStyle : 1<<1,
+ leavePad : 1<<2,
+ leaveSld : 1<<3,
+
+
+ BoxShadow : (function () {
+ var BoxShadow = function (hShadow, vShadow, blur, spread, color, inset) {
+ this.hShadow = hShadow;
+ this.vShadow = vShadow;
+ this.blur = blur;
+ this.spread = spread;
+ this.color = color;
+ this.inset = !!inset;
+ };
+
+ BoxShadow.prototype.toString = function () {
+ var vals = [
+ Math.round(this.hShadow) + 'px',
+ Math.round(this.vShadow) + 'px',
+ Math.round(this.blur) + 'px',
+ Math.round(this.spread) + 'px',
+ this.color
+ ];
+ if (this.inset) {
+ vals.push('inset');
+ }
+ return vals.join(' ');
+ };
+
+ return BoxShadow;
+ })(),
+
+
+ //
+ // Usage:
+ // var myColor = new jscolor(<targetElement> [, <options>])
+ //
+
+ jscolor : function (targetElement, options) {
+
+ // General options
+ //
+ this.value = null; // initial HEX color. To change it later, use methods fromString(), fromHSV() and fromRGB()
+ this.valueElement = targetElement; // element that will be used to display and input the color code
+ this.styleElement = targetElement; // element that will preview the picked color using CSS backgroundColor
+ this.required = true; // whether the associated text <input> can be left empty
+ this.refine = true; // whether to refine the entered color code (e.g. uppercase it and remove whitespace)
+ this.hash = false; // whether to prefix the HEX color code with # symbol
+ this.uppercase = true; // whether to uppercase the color code
+ this.onFineChange = null; // called instantly every time the color changes (value can be either a function or a string with javascript code)
+ this.activeClass = 'jscolor-active'; // class to be set to the target element when a picker window is open on it
+ this.minS = 0; // min allowed saturation (0 - 100)
+ this.maxS = 100; // max allowed saturation (0 - 100)
+ this.minV = 0; // min allowed value (brightness) (0 - 100)
+ this.maxV = 100; // max allowed value (brightness) (0 - 100)
+
+ // Accessing the picked color
+ //
+ this.hsv = [0, 0, 100]; // read-only [0-360, 0-100, 0-100]
+ this.rgb = [255, 255, 255]; // read-only [0-255, 0-255, 0-255]
+
+ // Color Picker options
+ //
+ this.width = 181; // width of color palette (in px)
+ this.height = 101; // height of color palette (in px)
+ this.showOnClick = true; // whether to display the color picker when user clicks on its target element
+ this.mode = 'HSV'; // HSV | HVS | HS | HV - layout of the color picker controls
+ this.position = 'bottom'; // left | right | top | bottom - position relative to the target element
+ this.smartPosition = true; // automatically change picker position when there is not enough space for it
+ this.sliderSize = 16; // px
+ this.crossSize = 8; // px
+ this.closable = false; // whether to display the Close button
+ this.closeText = 'Close';
+ this.buttonColor = '#000000'; // CSS color
+ this.buttonHeight = 18; // px
+ this.padding = 12; // px
+ this.backgroundColor = '#FFFFFF'; // CSS color
+ this.borderWidth = 1; // px
+ this.borderColor = '#BBBBBB'; // CSS color
+ this.borderRadius = 8; // px
+ this.insetWidth = 1; // px
+ this.insetColor = '#BBBBBB'; // CSS color
+ this.shadow = true; // whether to display shadow
+ this.shadowBlur = 15; // px
+ this.shadowColor = 'rgba(0,0,0,0.2)'; // CSS color
+ this.pointerColor = '#4C4C4C'; // px
+ this.pointerBorderColor = '#FFFFFF'; // px
+ this.pointerBorderWidth = 1; // px
+ this.pointerThickness = 2; // px
+ this.zIndex = 1000;
+ this.container = null; // where to append the color picker (BODY element by default)
+
+
+ for (var opt in options) {
+ if (options.hasOwnProperty(opt)) {
+ this[opt] = options[opt];
+ }
+ }
+
+
+ this.hide = function () {
+ if (isPickerOwner()) {
+ detachPicker();
+ }
+ };
+
+
+ this.show = function () {
+ drawPicker();
+ };
+
+
+ this.redraw = function () {
+ if (isPickerOwner()) {
+ drawPicker();
+ }
+ };
+
+
+ this.importColor = function () {
+ if (!this.valueElement) {
+ this.exportColor();
+ } else {
+ if (jsc.isElementType(this.valueElement, 'input')) {
+ if (!this.refine) {
+ if (!this.fromString(this.valueElement.value, jsc.leaveValue)) {
+ if (this.styleElement) {
+ this.styleElement.style.backgroundImage = this.styleElement._jscOrigStyle.backgroundImage;
+ this.styleElement.style.backgroundColor = this.styleElement._jscOrigStyle.backgroundColor;
+ this.styleElement.style.color = this.styleElement._jscOrigStyle.color;
+ }
+ this.exportColor(jsc.leaveValue | jsc.leaveStyle);
+ }
+ } else if (!this.required && /^\s*$/.test(this.valueElement.value)) {
+ this.valueElement.value = '';
+ if (this.styleElement) {
+ this.styleElement.style.backgroundImage = this.styleElement._jscOrigStyle.backgroundImage;
+ this.styleElement.style.backgroundColor = this.styleElement._jscOrigStyle.backgroundColor;
+ this.styleElement.style.color = this.styleElement._jscOrigStyle.color;
+ }
+ this.exportColor(jsc.leaveValue | jsc.leaveStyle);
+
+ } else if (this.fromString(this.valueElement.value)) {
+ // managed to import color successfully from the value -> OK, don't do anything
+ } else {
+ this.exportColor();
+ }
+ } else {
+ // not an input element -> doesn't have any value
+ this.exportColor();
+ }
+ }
+ };
+
+
+ this.exportColor = function (flags) {
+ if (!(flags & jsc.leaveValue) && this.valueElement) {
+ var value = this.toString();
+ if (this.uppercase) { value = value.toUpperCase(); }
+ if (this.hash) { value = '#' + value; }
+
+ if (jsc.isElementType(this.valueElement, 'input')) {
+ this.valueElement.value = value;
+ } else {
+ this.valueElement.innerHTML = value;
+ }
+ }
+ if (!(flags & jsc.leaveStyle)) {
+ if (this.styleElement) {
+ this.styleElement.style.backgroundImage = 'none';
+ this.styleElement.style.backgroundColor = '#' + this.toString();
+ this.styleElement.style.color = this.isLight() ? '#000' : '#FFF';
+ }
+ }
+ if (!(flags & jsc.leavePad) && isPickerOwner()) {
+ redrawPad();
+ }
+ if (!(flags & jsc.leaveSld) && isPickerOwner()) {
+ redrawSld();
+ }
+ };
+
+
+ // h: 0-360
+ // s: 0-100
+ // v: 0-100
+ //
+ this.fromHSV = function (h, s, v, flags) { // null = don't change
+ if (h !== null) {
+ if (isNaN(h)) { return false; }
+ h = Math.max(0, Math.min(360, h));
+ }
+ if (s !== null) {
+ if (isNaN(s)) { return false; }
+ s = Math.max(0, Math.min(100, this.maxS, s), this.minS);
+ }
+ if (v !== null) {
+ if (isNaN(v)) { return false; }
+ v = Math.max(0, Math.min(100, this.maxV, v), this.minV);
+ }
+
+ this.rgb = HSV_RGB(
+ h===null ? this.hsv[0] : (this.hsv[0]=h),
+ s===null ? this.hsv[1] : (this.hsv[1]=s),
+ v===null ? this.hsv[2] : (this.hsv[2]=v)
+ );
+
+ this.exportColor(flags);
+ };
+
+
+ // r: 0-255
+ // g: 0-255
+ // b: 0-255
+ //
+ this.fromRGB = function (r, g, b, flags) { // null = don't change
+ if (r !== null) {
+ if (isNaN(r)) { return false; }
+ r = Math.max(0, Math.min(255, r));
+ }
+ if (g !== null) {
+ if (isNaN(g)) { return false; }
+ g = Math.max(0, Math.min(255, g));
+ }
+ if (b !== null) {
+ if (isNaN(b)) { return false; }
+ b = Math.max(0, Math.min(255, b));
+ }
+
+ var hsv = RGB_HSV(
+ r===null ? this.rgb[0] : r,
+ g===null ? this.rgb[1] : g,
+ b===null ? this.rgb[2] : b
+ );
+ if (hsv[0] !== null) {
+ this.hsv[0] = Math.max(0, Math.min(360, hsv[0]));
+ }
+ if (hsv[2] !== 0) {
+ this.hsv[1] = hsv[1]===null ? null : Math.max(0, this.minS, Math.min(100, this.maxS, hsv[1]));
+ }
+ this.hsv[2] = hsv[2]===null ? null : Math.max(0, this.minV, Math.min(100, this.maxV, hsv[2]));
+
+ // update RGB according to final HSV, as some values might be trimmed
+ var rgb = HSV_RGB(this.hsv[0], this.hsv[1], this.hsv[2]);
+ this.rgb[0] = rgb[0];
+ this.rgb[1] = rgb[1];
+ this.rgb[2] = rgb[2];
+
+ this.exportColor(flags);
+ };
+
+
+ this.fromString = function (str, flags) {
+ var m;
+ if (m = str.match(/^\W*([0-9A-F]{3}([0-9A-F]{3})?)\W*$/i)) {
+ // HEX notation
+ //
+
+ if (m[1].length === 6) {
+ // 6-char notation
+ this.fromRGB(
+ parseInt(m[1].substr(0,2),16),
+ parseInt(m[1].substr(2,2),16),
+ parseInt(m[1].substr(4,2),16),
+ flags
+ );
+ } else {
+ // 3-char notation
+ this.fromRGB(
+ parseInt(m[1].charAt(0) + m[1].charAt(0),16),
+ parseInt(m[1].charAt(1) + m[1].charAt(1),16),
+ parseInt(m[1].charAt(2) + m[1].charAt(2),16),
+ flags
+ );
+ }
+ return true;
+
+ } else if (m = str.match(/^\W*rgba?\(([^)]*)\)\W*$/i)) {
+ var params = m[1].split(',');
+ var re = /^\s*(\d*)(\.\d+)?\s*$/;
+ var mR, mG, mB;
+ if (
+ params.length >= 3 &&
+ (mR = params[0].match(re)) &&
+ (mG = params[1].match(re)) &&
+ (mB = params[2].match(re))
+ ) {
+ var r = parseFloat((mR[1] || '0') + (mR[2] || ''));
+ var g = parseFloat((mG[1] || '0') + (mG[2] || ''));
+ var b = parseFloat((mB[1] || '0') + (mB[2] || ''));
+ this.fromRGB(r, g, b, flags);
+ return true;
+ }
+ }
+ return false;
+ };
+
+
+ this.toString = function () {
+ return (
+ (0x100 | Math.round(this.rgb[0])).toString(16).substr(1) +
+ (0x100 | Math.round(this.rgb[1])).toString(16).substr(1) +
+ (0x100 | Math.round(this.rgb[2])).toString(16).substr(1)
+ );
+ };
+
+
+ this.toHEXString = function () {
+ return '#' + this.toString().toUpperCase();
+ };
+
+
+ this.toRGBString = function () {
+ return ('rgb(' +
+ Math.round(this.rgb[0]) + ',' +
+ Math.round(this.rgb[1]) + ',' +
+ Math.round(this.rgb[2]) + ')'
+ );
+ };
+
+
+ this.isLight = function () {
+ return (
+ 0.213 * this.rgb[0] +
+ 0.715 * this.rgb[1] +
+ 0.072 * this.rgb[2] >
+ 255 / 2
+ );
+ };
+
+
+ this._processParentElementsInDOM = function () {
+ if (this._linkedElementsProcessed) { return; }
+ this._linkedElementsProcessed = true;
+
+ var elm = this.targetElement;
+ do {
+ // If the target element or one of its parent nodes has fixed position,
+ // then use fixed positioning instead
+ //
+ // Note: In Firefox, getComputedStyle returns null in a hidden iframe,
+ // that's why we need to check if the returned style object is non-empty
+ var currStyle = jsc.getStyle(elm);
+ if (currStyle && currStyle.position.toLowerCase() === 'fixed') {
+ this.fixed = true;
+ }
+
+ if (elm !== this.targetElement) {
+ // Ensure to attach onParentScroll only once to each parent element
+ // (multiple targetElements can share the same parent nodes)
+ //
+ // Note: It's not just offsetParents that can be scrollable,
+ // that's why we loop through all parent nodes
+ if (!elm._jscEventsAttached) {
+ jsc.attachEvent(elm, 'scroll', jsc.onParentScroll);
+ elm._jscEventsAttached = true;
+ }
+ }
+ } while ((elm = elm.parentNode) && !jsc.isElementType(elm, 'body'));
+ };
+
+
+ // r: 0-255
+ // g: 0-255
+ // b: 0-255
+ //
+ // returns: [ 0-360, 0-100, 0-100 ]
+ //
+ function RGB_HSV (r, g, b) {
+ r /= 255;
+ g /= 255;
+ b /= 255;
+ var n = Math.min(Math.min(r,g),b);
+ var v = Math.max(Math.max(r,g),b);
+ var m = v - n;
+ if (m === 0) { return [ null, 0, 100 * v ]; }
+ var h = r===n ? 3+(b-g)/m : (g===n ? 5+(r-b)/m : 1+(g-r)/m);
+ return [
+ 60 * (h===6?0:h),
+ 100 * (m/v),
+ 100 * v
+ ];
+ }
+
+
+ // h: 0-360
+ // s: 0-100
+ // v: 0-100
+ //
+ // returns: [ 0-255, 0-255, 0-255 ]
+ //
+ function HSV_RGB (h, s, v) {
+ var u = 255 * (v / 100);
+
+ if (h === null) {
+ return [ u, u, u ];
+ }
+
+ h /= 60;
+ s /= 100;
+
+ var i = Math.floor(h);
+ var f = i%2 ? h-i : 1-(h-i);
+ var m = u * (1 - s);
+ var n = u * (1 - s * f);
+ switch (i) {
+ case 6:
+ case 0: return [u,n,m];
+ case 1: return [n,u,m];
+ case 2: return [m,u,n];
+ case 3: return [m,n,u];
+ case 4: return [n,m,u];
+ case 5: return [u,m,n];
+ }
+ }
+
+
+ function detachPicker () {
+ jsc.unsetClass(THIS.targetElement, THIS.activeClass);
+ jsc.picker.wrap.parentNode.removeChild(jsc.picker.wrap);
+ delete jsc.picker.owner;
+ }
+
+
+ function drawPicker () {
+
+ // At this point, when drawing the picker, we know what the parent elements are
+ // and we can do all related DOM operations, such as registering events on them
+ // or checking their positioning
+ THIS._processParentElementsInDOM();
+
+ if (!jsc.picker) {
+ jsc.picker = {
+ owner: null,
+ wrap : document.createElement('div'),
+ box : document.createElement('div'),
+ boxS : document.createElement('div'), // shadow area
+ boxB : document.createElement('div'), // border
+ pad : document.createElement('div'),
+ padB : document.createElement('div'), // border
+ padM : document.createElement('div'), // mouse/touch area
+ padPal : jsc.createPalette(),
+ cross : document.createElement('div'),
+ crossBY : document.createElement('div'), // border Y
+ crossBX : document.createElement('div'), // border X
+ crossLY : document.createElement('div'), // line Y
+ crossLX : document.createElement('div'), // line X
+ sld : document.createElement('div'),
+ sldB : document.createElement('div'), // border
+ sldM : document.createElement('div'), // mouse/touch area
+ sldGrad : jsc.createSliderGradient(),
+ sldPtrS : document.createElement('div'), // slider pointer spacer
+ sldPtrIB : document.createElement('div'), // slider pointer inner border
+ sldPtrMB : document.createElement('div'), // slider pointer middle border
+ sldPtrOB : document.createElement('div'), // slider pointer outer border
+ btn : document.createElement('div'),
+ btnT : document.createElement('span') // text
+ };
+
+ jsc.picker.pad.appendChild(jsc.picker.padPal.elm);
+ jsc.picker.padB.appendChild(jsc.picker.pad);
+ jsc.picker.cross.appendChild(jsc.picker.crossBY);
+ jsc.picker.cross.appendChild(jsc.picker.crossBX);
+ jsc.picker.cross.appendChild(jsc.picker.crossLY);
+ jsc.picker.cross.appendChild(jsc.picker.crossLX);
+ jsc.picker.padB.appendChild(jsc.picker.cross);
+ jsc.picker.box.appendChild(jsc.picker.padB);
+ jsc.picker.box.appendChild(jsc.picker.padM);
+
+ jsc.picker.sld.appendChild(jsc.picker.sldGrad.elm);
+ jsc.picker.sldB.appendChild(jsc.picker.sld);
+ jsc.picker.sldB.appendChild(jsc.picker.sldPtrOB);
+ jsc.picker.sldPtrOB.appendChild(jsc.picker.sldPtrMB);
+ jsc.picker.sldPtrMB.appendChild(jsc.picker.sldPtrIB);
+ jsc.picker.sldPtrIB.appendChild(jsc.picker.sldPtrS);
+ jsc.picker.box.appendChild(jsc.picker.sldB);
+ jsc.picker.box.appendChild(jsc.picker.sldM);
+
+ jsc.picker.btn.appendChild(jsc.picker.btnT);
+ jsc.picker.box.appendChild(jsc.picker.btn);
+
+ jsc.picker.boxB.appendChild(jsc.picker.box);
+ jsc.picker.wrap.appendChild(jsc.picker.boxS);
+ jsc.picker.wrap.appendChild(jsc.picker.boxB);
+ }
+
+ var p = jsc.picker;
+
+ var displaySlider = !!jsc.getSliderComponent(THIS);
+ var dims = jsc.getPickerDims(THIS);
+ var crossOuterSize = (2 * THIS.pointerBorderWidth + THIS.pointerThickness + 2 * THIS.crossSize);
+ var padToSliderPadding = jsc.getPadToSliderPadding(THIS);
+ var borderRadius = Math.min(
+ THIS.borderRadius,
+ Math.round(THIS.padding * Math.PI)); // px
+ var padCursor = 'crosshair';
+
+ // wrap
+ p.wrap.style.clear = 'both';
+ p.wrap.style.width = (dims[0] + 2 * THIS.borderWidth) + 'px';
+ p.wrap.style.height = (dims[1] + 2 * THIS.borderWidth) + 'px';
+ p.wrap.style.zIndex = THIS.zIndex;
+
+ // picker
+ p.box.style.width = dims[0] + 'px';
+ p.box.style.height = dims[1] + 'px';
+
+ p.boxS.style.position = 'absolute';
+ p.boxS.style.left = '0';
+ p.boxS.style.top = '0';
+ p.boxS.style.width = '100%';
+ p.boxS.style.height = '100%';
+ jsc.setBorderRadius(p.boxS, borderRadius + 'px');
+
+ // picker border
+ p.boxB.style.position = 'relative';
+ p.boxB.style.border = THIS.borderWidth + 'px solid';
+ p.boxB.style.borderColor = THIS.borderColor;
+ p.boxB.style.background = THIS.backgroundColor;
+ jsc.setBorderRadius(p.boxB, borderRadius + 'px');
+
+ // IE hack:
+ // If the element is transparent, IE will trigger the event on the elements under it,
+ // e.g. on Canvas or on elements with border
+ p.padM.style.background =
+ p.sldM.style.background =
+ '#FFF';
+ jsc.setStyle(p.padM, 'opacity', '0');
+ jsc.setStyle(p.sldM, 'opacity', '0');
+
+ // pad
+ p.pad.style.position = 'relative';
+ p.pad.style.width = THIS.width + 'px';
+ p.pad.style.height = THIS.height + 'px';
+
+ // pad palettes (HSV and HVS)
+ p.padPal.draw(THIS.width, THIS.height, jsc.getPadYComponent(THIS));
+
+ // pad border
+ p.padB.style.position = 'absolute';
+ p.padB.style.left = THIS.padding + 'px';
+ p.padB.style.top = THIS.padding + 'px';
+ p.padB.style.border = THIS.insetWidth + 'px solid';
+ p.padB.style.borderColor = THIS.insetColor;
+
+ // pad mouse area
+ p.padM._jscInstance = THIS;
+ p.padM._jscControlName = 'pad';
+ p.padM.style.position = 'absolute';
+ p.padM.style.left = '0';
+ p.padM.style.top = '0';
+ p.padM.style.width = (THIS.padding + 2 * THIS.insetWidth + THIS.width + padToSliderPadding / 2) + 'px';
+ p.padM.style.height = dims[1] + 'px';
+ p.padM.style.cursor = padCursor;
+
+ // pad cross
+ p.cross.style.position = 'absolute';
+ p.cross.style.left =
+ p.cross.style.top =
+ '0';
+ p.cross.style.width =
+ p.cross.style.height =
+ crossOuterSize + 'px';
+
+ // pad cross border Y and X
+ p.crossBY.style.position =
+ p.crossBX.style.position =
+ 'absolute';
+ p.crossBY.style.background =
+ p.crossBX.style.background =
+ THIS.pointerBorderColor;
+ p.crossBY.style.width =
+ p.crossBX.style.height =
+ (2 * THIS.pointerBorderWidth + THIS.pointerThickness) + 'px';
+ p.crossBY.style.height =
+ p.crossBX.style.width =
+ crossOuterSize + 'px';
+ p.crossBY.style.left =
+ p.crossBX.style.top =
+ (Math.floor(crossOuterSize / 2) - Math.floor(THIS.pointerThickness / 2) - THIS.pointerBorderWidth) + 'px';
+ p.crossBY.style.top =
+ p.crossBX.style.left =
+ '0';
+
+ // pad cross line Y and X
+ p.crossLY.style.position =
+ p.crossLX.style.position =
+ 'absolute';
+ p.crossLY.style.background =
+ p.crossLX.style.background =
+ THIS.pointerColor;
+ p.crossLY.style.height =
+ p.crossLX.style.width =
+ (crossOuterSize - 2 * THIS.pointerBorderWidth) + 'px';
+ p.crossLY.style.width =
+ p.crossLX.style.height =
+ THIS.pointerThickness + 'px';
+ p.crossLY.style.left =
+ p.crossLX.style.top =
+ (Math.floor(crossOuterSize / 2) - Math.floor(THIS.pointerThickness / 2)) + 'px';
+ p.crossLY.style.top =
+ p.crossLX.style.left =
+ THIS.pointerBorderWidth + 'px';
+
+ // slider
+ p.sld.style.overflow = 'hidden';
+ p.sld.style.width = THIS.sliderSize + 'px';
+ p.sld.style.height = THIS.height + 'px';
+
+ // slider gradient
+ p.sldGrad.draw(THIS.sliderSize, THIS.height, '#000', '#000');
+
+ // slider border
+ p.sldB.style.display = displaySlider ? 'block' : 'none';
+ p.sldB.style.position = 'absolute';
+ p.sldB.style.right = THIS.padding + 'px';
+ p.sldB.style.top = THIS.padding + 'px';
+ p.sldB.style.border = THIS.insetWidth + 'px solid';
+ p.sldB.style.borderColor = THIS.insetColor;
+
+ // slider mouse area
+ p.sldM._jscInstance = THIS;
+ p.sldM._jscControlName = 'sld';
+ p.sldM.style.display = displaySlider ? 'block' : 'none';
+ p.sldM.style.position = 'absolute';
+ p.sldM.style.right = '0';
+ p.sldM.style.top = '0';
+ p.sldM.style.width = (THIS.sliderSize + padToSliderPadding / 2 + THIS.padding + 2 * THIS.insetWidth) + 'px';
+ p.sldM.style.height = dims[1] + 'px';
+ p.sldM.style.cursor = 'default';
+
+ // slider pointer inner and outer border
+ p.sldPtrIB.style.border =
+ p.sldPtrOB.style.border =
+ THIS.pointerBorderWidth + 'px solid ' + THIS.pointerBorderColor;
+
+ // slider pointer outer border
+ p.sldPtrOB.style.position = 'absolute';
+ p.sldPtrOB.style.left = -(2 * THIS.pointerBorderWidth + THIS.pointerThickness) + 'px';
+ p.sldPtrOB.style.top = '0';
+
+ // slider pointer middle border
+ p.sldPtrMB.style.border = THIS.pointerThickness + 'px solid ' + THIS.pointerColor;
+
+ // slider pointer spacer
+ p.sldPtrS.style.width = THIS.sliderSize + 'px';
+ p.sldPtrS.style.height = sliderPtrSpace + 'px';
+
+ // the Close button
+ function setBtnBorder () {
+ var insetColors = THIS.insetColor.split(/\s+/);
+ var outsetColor = insetColors.length < 2 ? insetColors[0] : insetColors[1] + ' ' + insetColors[0] + ' ' + insetColors[0] + ' ' + insetColors[1];
+ p.btn.style.borderColor = outsetColor;
+ }
+ p.btn.style.display = THIS.closable ? 'block' : 'none';
+ p.btn.style.position = 'absolute';
+ p.btn.style.left = THIS.padding + 'px';
+ p.btn.style.bottom = THIS.padding + 'px';
+ p.btn.style.padding = '0 15px';
+ p.btn.style.height = THIS.buttonHeight + 'px';
+ p.btn.style.border = THIS.insetWidth + 'px solid';
+ setBtnBorder();
+ p.btn.style.color = THIS.buttonColor;
+ p.btn.style.font = '12px sans-serif';
+ p.btn.style.textAlign = 'center';
+ try {
+ p.btn.style.cursor = 'pointer';
+ } catch(eOldIE) {
+ p.btn.style.cursor = 'hand';
+ }
+ p.btn.onmousedown = function () {
+ THIS.hide();
+ };
+ p.btnT.style.lineHeight = THIS.buttonHeight + 'px';
+ p.btnT.innerHTML = '';
+ p.btnT.appendChild(document.createTextNode(THIS.closeText));
+
+ // place pointers
+ redrawPad();
+ redrawSld();
+
+ // If we are changing the owner without first closing the picker,
+ // make sure to first deal with the old owner
+ if (jsc.picker.owner && jsc.picker.owner !== THIS) {
+ jsc.unsetClass(jsc.picker.owner.targetElement, THIS.activeClass);
+ }
+
+ // Set the new picker owner
+ jsc.picker.owner = THIS;
+
+ // The redrawPosition() method needs picker.owner to be set, that's why we call it here,
+ // after setting the owner
+ if (jsc.isElementType(container, 'body')) {
+ jsc.redrawPosition();
+ } else {
+ jsc._drawPosition(THIS, 0, 0, 'relative', false);
+ }
+
+ if (p.wrap.parentNode != container) {
+ container.appendChild(p.wrap);
+ }
+
+ jsc.setClass(THIS.targetElement, THIS.activeClass);
+ }
+
+
+ function redrawPad () {
+ // redraw the pad pointer
+ switch (jsc.getPadYComponent(THIS)) {
+ case 's': var yComponent = 1; break;
+ case 'v': var yComponent = 2; break;
+ }
+ var x = Math.round((THIS.hsv[0] / 360) * (THIS.width - 1));
+ var y = Math.round((1 - THIS.hsv[yComponent] / 100) * (THIS.height - 1));
+ var crossOuterSize = (2 * THIS.pointerBorderWidth + THIS.pointerThickness + 2 * THIS.crossSize);
+ var ofs = -Math.floor(crossOuterSize / 2);
+ jsc.picker.cross.style.left = (x + ofs) + 'px';
+ jsc.picker.cross.style.top = (y + ofs) + 'px';
+
+ // redraw the slider
+ switch (jsc.getSliderComponent(THIS)) {
+ case 's':
+ var rgb1 = HSV_RGB(THIS.hsv[0], 100, THIS.hsv[2]);
+ var rgb2 = HSV_RGB(THIS.hsv[0], 0, THIS.hsv[2]);
+ var color1 = 'rgb(' +
+ Math.round(rgb1[0]) + ',' +
+ Math.round(rgb1[1]) + ',' +
+ Math.round(rgb1[2]) + ')';
+ var color2 = 'rgb(' +
+ Math.round(rgb2[0]) + ',' +
+ Math.round(rgb2[1]) + ',' +
+ Math.round(rgb2[2]) + ')';
+ jsc.picker.sldGrad.draw(THIS.sliderSize, THIS.height, color1, color2);
+ break;
+ case 'v':
+ var rgb = HSV_RGB(THIS.hsv[0], THIS.hsv[1], 100);
+ var color1 = 'rgb(' +
+ Math.round(rgb[0]) + ',' +
+ Math.round(rgb[1]) + ',' +
+ Math.round(rgb[2]) + ')';
+ var color2 = '#000';
+ jsc.picker.sldGrad.draw(THIS.sliderSize, THIS.height, color1, color2);
+ break;
+ }
+ }
+
+
+ function redrawSld () {
+ var sldComponent = jsc.getSliderComponent(THIS);
+ if (sldComponent) {
+ // redraw the slider pointer
+ switch (sldComponent) {
+ case 's': var yComponent = 1; break;
+ case 'v': var yComponent = 2; break;
+ }
+ var y = Math.round((1 - THIS.hsv[yComponent] / 100) * (THIS.height - 1));
+ jsc.picker.sldPtrOB.style.top = (y - (2 * THIS.pointerBorderWidth + THIS.pointerThickness) - Math.floor(sliderPtrSpace / 2)) + 'px';
+ }
+ }
+
+
+ function isPickerOwner () {
+ return jsc.picker && jsc.picker.owner === THIS;
+ }
+
+
+ function blurValue () {
+ THIS.importColor();
+ }
+
+
+ // Find the target element
+ if (typeof targetElement === 'string') {
+ var id = targetElement;
+ var elm = document.getElementById(id);
+ if (elm) {
+ this.targetElement = elm;
+ } else {
+ jsc.warn('Could not find target element with ID \'' + id + '\'');
+ }
+ } else if (targetElement) {
+ this.targetElement = targetElement;
+ } else {
+ jsc.warn('Invalid target element: \'' + targetElement + '\'');
+ }
+
+ if (this.targetElement._jscLinkedInstance) {
+ jsc.warn('Cannot link jscolor twice to the same element. Skipping.');
+ return;
+ }
+ this.targetElement._jscLinkedInstance = this;
+
+ // Find the value element
+ this.valueElement = jsc.fetchElement(this.valueElement);
+ // Find the style element
+ this.styleElement = jsc.fetchElement(this.styleElement);
+
+ var THIS = this;
+ var container =
+ this.container ?
+ jsc.fetchElement(this.container) :
+ document.getElementsByTagName('body')[0];
+ var sliderPtrSpace = 3; // px
+
+ // For BUTTON elements it's important to stop them from sending the form when clicked
+ // (e.g. in Safari)
+ if (jsc.isElementType(this.targetElement, 'button')) {
+ if (this.targetElement.onclick) {
+ var origCallback = this.targetElement.onclick;
+ this.targetElement.onclick = function (evt) {
+ origCallback.call(this, evt);
+ return false;
+ };
+ } else {
+ this.targetElement.onclick = function () { return false; };
+ }
+ }
+
+ /*
+ var elm = this.targetElement;
+ do {
+ // If the target element or one of its offsetParents has fixed position,
+ // then use fixed positioning instead
+ //
+ // Note: In Firefox, getComputedStyle returns null in a hidden iframe,
+ // that's why we need to check if the returned style object is non-empty
+ var currStyle = jsc.getStyle(elm);
+ if (currStyle && currStyle.position.toLowerCase() === 'fixed') {
+ this.fixed = true;
+ }
+
+ if (elm !== this.targetElement) {
+ // attach onParentScroll so that we can recompute the picker position
+ // when one of the offsetParents is scrolled
+ if (!elm._jscEventsAttached) {
+ jsc.attachEvent(elm, 'scroll', jsc.onParentScroll);
+ elm._jscEventsAttached = true;
+ }
+ }
+ } while ((elm = elm.offsetParent) && !jsc.isElementType(elm, 'body'));
+ */
+
+ // valueElement
+ if (this.valueElement) {
+ if (jsc.isElementType(this.valueElement, 'input')) {
+ var updateField = function () {
+ THIS.fromString(THIS.valueElement.value, jsc.leaveValue);
+ jsc.dispatchFineChange(THIS);
+ };
+ jsc.attachEvent(this.valueElement, 'keyup', updateField);
+ jsc.attachEvent(this.valueElement, 'input', updateField);
+ jsc.attachEvent(this.valueElement, 'blur', blurValue);
+ this.valueElement.setAttribute('autocomplete', 'off');
+ }
+ }
+
+ // styleElement
+ if (this.styleElement) {
+ this.styleElement._jscOrigStyle = {
+ backgroundImage : this.styleElement.style.backgroundImage,
+ backgroundColor : this.styleElement.style.backgroundColor,
+ color : this.styleElement.style.color
+ };
+ }
+
+ if (this.value) {
+ // Try to set the color from the .value option and if unsuccessful,
+ // export the current color
+ this.fromString(this.value) || this.exportColor();
+ } else {
+ this.importColor();
+ }
+ }
+
+};
+
+
+//================================
+// Public properties and methods
+//================================
+
+
+// By default, search for all elements with class="jscolor" and install a color picker on them.
+//
+// You can change what class name will be looked for by setting the property jscolor.lookupClass
+// anywhere in your HTML document. To completely disable the automatic lookup, set it to null.
+//
+jsc.jscolor.lookupClass = 'jscolor';
+
+
+jsc.jscolor.installByClassName = function (className) {
+ var inputElms = document.getElementsByTagName('input');
+ var buttonElms = document.getElementsByTagName('button');
+
+ jsc.tryInstallOnElements(inputElms, className);
+ jsc.tryInstallOnElements(buttonElms, className);
+};
+
+
+jsc.register();
+
+
+return jsc.jscolor;
+
+
+})(); }
diff --git a/apps/theming/js/3rdparty/jscolor/jscolor.min.js b/apps/theming/js/3rdparty/jscolor/jscolor.min.js
new file mode 100644
index 00000000000..2a7a788bed2
--- /dev/null
+++ b/apps/theming/js/3rdparty/jscolor/jscolor.min.js
@@ -0,0 +1,10 @@
+/**
+ * jscolor - JavaScript Color Picker
+ *
+ * @link http://jscolor.com
+ * @license For open source use: GPLv3
+ * For commercial use: JSColor Commercial License
+ * @author Jan Odvarko
+ *
+ * See usage examples at http://jscolor.com/examples/
+ */"use strict";window.jscolor||(window.jscolor=function(){var e={register:function(){e.attachDOMReadyEvent(e.init),e.attachEvent(document,"mousedown",e.onDocumentMouseDown),e.attachEvent(document,"touchstart",e.onDocumentTouchStart),e.attachEvent(window,"resize",e.onWindowResize)},init:function(){e.jscolor.lookupClass&&e.jscolor.installByClassName(e.jscolor.lookupClass)},tryInstallOnElements:function(t,n){var r=new RegExp("(^|\\s)("+n+")(\\s*(\\{[^}]*\\})|\\s|$)","i");for(var i=0;i<t.length;i+=1){if(t[i].type!==undefined&&t[i].type.toLowerCase()=="color"&&e.isColorAttrSupported)continue;var s;if(!t[i].jscolor&&t[i].className&&(s=t[i].className.match(r))){var o=t[i],u=null,a=e.getDataAttr(o,"jscolor");a!==null?u=a:s[4]&&(u=s[4]);var f={};if(u)try{f=(new Function("return ("+u+")"))()}catch(l){e.warn("Error parsing jscolor options: "+l+":\n"+u)}o.jscolor=new e.jscolor(o,f)}}},isColorAttrSupported:function(){var e=document.createElement("input");if(e.setAttribute){e.setAttribute("type","color");if(e.type.toLowerCase()=="color")return!0}return!1}(),isCanvasSupported:function(){var e=document.createElement("canvas");return!!e.getContext&&!!e.getContext("2d")}(),fetchElement:function(e){return typeof e=="string"?document.getElementById(e):e},isElementType:function(e,t){return e.nodeName.toLowerCase()===t.toLowerCase()},getDataAttr:function(e,t){var n="data-"+t,r=e.getAttribute(n);return r!==null?r:null},attachEvent:function(e,t,n){e.addEventListener?e.addEventListener(t,n,!1):e.attachEvent&&e.attachEvent("on"+t,n)},detachEvent:function(e,t,n){e.removeEventListener?e.removeEventListener(t,n,!1):e.detachEvent&&e.detachEvent("on"+t,n)},_attachedGroupEvents:{},attachGroupEvent:function(t,n,r,i){e._attachedGroupEvents.hasOwnProperty(t)||(e._attachedGroupEvents[t]=[]),e._attachedGroupEvents[t].push([n,r,i]),e.attachEvent(n,r,i)},detachGroupEvents:function(t){if(e._attachedGroupEvents.hasOwnProperty(t)){for(var n=0;n<e._attachedGroupEvents[t].length;n+=1){var r=e._attachedGroupEvents[t][n];e.detachEvent(r[0],r[1],r[2])}delete e._attachedGroupEvents[t]}},attachDOMReadyEvent:function(e){var t=!1,n=function(){t||(t=!0,e())};if(document.readyState==="complete"){setTimeout(n,1);return}if(document.addEventListener)document.addEventListener("DOMContentLoaded",n,!1),window.addEventListener("load",n,!1);else if(document.attachEvent){document.attachEvent("onreadystatechange",function(){document.readyState==="complete"&&(document.detachEvent("onreadystatechange",arguments.callee),n())}),window.attachEvent("onload",n);if(document.documentElement.doScroll&&window==window.top){var r=function(){if(!document.body)return;try{document.documentElement.doScroll("left"),n()}catch(e){setTimeout(r,1)}};r()}}},warn:function(e){window.console&&window.console.warn&&window.console.warn(e)},preventDefault:function(e){e.preventDefault&&e.preventDefault(),e.returnValue=!1},captureTarget:function(t){t.setCapture&&(e._capturedTarget=t,e._capturedTarget.setCapture())},releaseTarget:function(){e._capturedTarget&&(e._capturedTarget.releaseCapture(),e._capturedTarget=null)},fireEvent:function(e,t){if(!e)return;if(document.createEvent){var n=document.createEvent("HTMLEvents");n.initEvent(t,!0,!0),e.dispatchEvent(n)}else if(document.createEventObject){var n=document.createEventObject();e.fireEvent("on"+t,n)}else e["on"+t]&&e["on"+t]()},classNameToList:function(e){return e.replace(/^\s+|\s+$/g,"").split(/\s+/)},hasClass:function(e,t){return t?-1!=(" "+e.className.replace(/\s+/g," ")+" ").indexOf(" "+t+" "):!1},setClass:function(t,n){var r=e.classNameToList(n);for(var i=0;i<r.length;i+=1)e.hasClass(t,r[i])||(t.className+=(t.className?" ":"")+r[i])},unsetClass:function(t,n){var r=e.classNameToList(n);for(var i=0;i<r.length;i+=1){var s=new RegExp("^\\s*"+r[i]+"\\s*|"+"\\s*"+r[i]+"\\s*$|"+"\\s+"+r[i]+"(\\s+)","g");t.className=t.className.replace(s,"$1")}},getStyle:function(e){return window.getComputedStyle?window.getComputedStyle(e):e.currentStyle},setStyle:function(){var e=document.createElement("div"),t=function(t){for(var n=0;n<t.length;n+=1)if(t[n]in e.style)return t[n]},n={borderRadius:t(["borderRadius","MozBorderRadius","webkitBorderRadius"]),boxShadow:t(["boxShadow","MozBoxShadow","webkitBoxShadow"])};return function(e,t,r){switch(t.toLowerCase()){case"opacity":var i=Math.round(parseFloat(r)*100);e.style.opacity=r,e.style.filter="alpha(opacity="+i+")";break;default:e.style[n[t]]=r}}}(),setBorderRadius:function(t,n){e.setStyle(t,"borderRadius",n||"0")},setBoxShadow:function(t,n){e.setStyle(t,"boxShadow",n||"none")},getElementPos:function(t,n){var r=0,i=0,s=t.getBoundingClientRect();r=s.left,i=s.top;if(!n){var o=e.getViewPos();r+=o[0],i+=o[1]}return[r,i]},getElementSize:function(e){return[e.offsetWidth,e.offsetHeight]},getAbsPointerPos:function(e){e||(e=window.event);var t=0,n=0;return typeof e.changedTouches!="undefined"&&e.changedTouches.length?(t=e.changedTouches[0].clientX,n=e.changedTouches[0].clientY):typeof e.clientX=="number"&&(t=e.clientX,n=e.clientY),{x:t,y:n}},getRelPointerPos:function(e){e||(e=window.event);var t=e.target||e.srcElement,n=t.getBoundingClientRect(),r=0,i=0,s=0,o=0;return typeof e.changedTouches!="undefined"&&e.changedTouches.length?(s=e.changedTouches[0].clientX,o=e.changedTouches[0].clientY):typeof e.clientX=="number"&&(s=e.clientX,o=e.clientY),r=s-n.left,i=o-n.top,{x:r,y:i}},getViewPos:function(){var e=document.documentElement;return[(window.pageXOffset||e.scrollLeft)-(e.clientLeft||0),(window.pageYOffset||e.scrollTop)-(e.clientTop||0)]},getViewSize:function(){var e=document.documentElement;return[window.innerWidth||e.clientWidth,window.innerHeight||e.clientHeight]},redrawPosition:function(){if(e.picker&&e.picker.owner){var t=e.picker.owner,n,r;t.fixed?(n=e.getElementPos(t.targetElement,!0),r=[0,0]):(n=e.getElementPos(t.targetElement),r=e.getViewPos());var i=e.getElementSize(t.targetElement),s=e.getViewSize(),o=e.getPickerOuterDims(t),u,a,f;switch(t.position.toLowerCase()){case"left":u=1,a=0,f=-1;break;case"right":u=1,a=0,f=1;break;case"top":u=0,a=1,f=-1;break;default:u=0,a=1,f=1}var l=(i[a]+o[a])/2;if(!t.smartPosition)var c=[n[u],n[a]+i[a]-l+l*f];else var c=[-r[u]+n[u]+o[u]>s[u]?-r[u]+n[u]+i[u]/2>s[u]/2&&n[u]+i[u]-o[u]>=0?n[u]+i[u]-o[u]:n[u]:n[u],-r[a]+n[a]+i[a]+o[a]-l+l*f>s[a]?-r[a]+n[a]+i[a]/2>s[a]/2&&n[a]+i[a]-l-l*f>=0?n[a]+i[a]-l-l*f:n[a]+i[a]-l+l*f:n[a]+i[a]-l+l*f>=0?n[a]+i[a]-l+l*f:n[a]+i[a]-l-l*f];var h=c[u],p=c[a],d=t.fixed?"fixed":"absolute",v=(c[0]+o[0]>n[0]||c[0]<n[0]+i[0])&&c[1]+o[1]<n[1]+i[1];e._drawPosition(t,h,p,d,v)}},_drawPosition:function(t,n,r,i,s){var o=s?0:t.shadowBlur;e.picker.wrap.style.position=i,e.picker.wrap.style.left=n+"px",e.picker.wrap.style.top=r+"px",e.setBoxShadow(e.picker.boxS,t.shadow?new e.BoxShadow(0,o,t.shadowBlur,0,t.shadowColor):null)},getPickerDims:function(t){var n=!!e.getSliderComponent(t),r=[2*t.insetWidth+2*t.padding+t.width+(n?2*t.insetWidth+e.getPadToSliderPadding(t)+t.sliderSize:0),2*t.insetWidth+2*t.padding+t.height+(t.closable?2*t.insetWidth+t.padding+t.buttonHeight:0)];return r},getPickerOuterDims:function(t){var n=e.getPickerDims(t);return[n[0]+2*t.borderWidth,n[1]+2*t.borderWidth]},getPadToSliderPadding:function(e){return Math.max(e.padding,1.5*(2*e.pointerBorderWidth+e.pointerThickness))},getPadYComponent:function(e){switch(e.mode.charAt(1).toLowerCase()){case"v":return"v"}return"s"},getSliderComponent:function(e){if(e.mode.length>2)switch(e.mode.charAt(2).toLowerCase()){case"s":return"s";case"v":return"v"}return null},onDocumentMouseDown:function(t){t||(t=window.event);var n=t.target||t.srcElement;n._jscLinkedInstance?n._jscLinkedInstance.showOnClick&&n._jscLinkedInstance.show():n._jscControlName?e.onControlPointerStart(t,n,n._jscControlName,"mouse"):e.picker&&e.picker.owner&&e.picker.owner.hide()},onDocumentTouchStart:function(t){t||(t=window.event);var n=t.target||t.srcElement;n._jscLinkedInstance?n._jscLinkedInstance.showOnClick&&n._jscLinkedInstance.show():n._jscControlName?e.onControlPointerStart(t,n,n._jscControlName,"touch"):e.picker&&e.picker.owner&&e.picker.owner.hide()},onWindowResize:function(t){e.redrawPosition()},onParentScroll:function(t){e.picker&&e.picker.owner&&e.picker.owner.hide()},_pointerMoveEvent:{mouse:"mousemove",touch:"touchmove"},_pointerEndEvent:{mouse:"mouseup",touch:"touchend"},_pointerOrigin:null,_capturedTarget:null,onControlPointerStart:function(t,n,r,i){var s=n._jscInstance;e.preventDefault(t),e.captureTarget(n);var o=function(s,o){e.attachGroupEvent("drag",s,e._pointerMoveEvent[i],e.onDocumentPointerMove(t,n,r,i,o)),e.attachGroupEvent("drag",s,e._pointerEndEvent[i],e.onDocumentPointerEnd(t,n,r,i))};o(document,[0,0]);if(window.parent&&window.frameElement){var u=window.frameElement.getBoundingClientRect(),a=[-u.left,-u.top];o(window.parent.window.document,a)}var f=e.getAbsPointerPos(t),l=e.getRelPointerPos(t);e._pointerOrigin={x:f.x-l.x,y:f.y-l.y};switch(r){case"pad":switch(e.getSliderComponent(s)){case"s":s.hsv[1]===0&&s.fromHSV(null,100,null);break;case"v":s.hsv[2]===0&&s.fromHSV(null,null,100)}e.setPad(s,t,0,0);break;case"sld":e.setSld(s,t,0)}e.dispatchFineChange(s)},onDocumentPointerMove:function(t,n,r,i,s){return function(t){var i=n._jscInstance;switch(r){case"pad":t||(t=window.event),e.setPad(i,t,s[0],s[1]),e.dispatchFineChange(i);break;case"sld":t||(t=window.event),e.setSld(i,t,s[1]),e.dispatchFineChange(i)}}},onDocumentPointerEnd:function(t,n,r,i){return function(t){var r=n._jscInstance;e.detachGroupEvents("drag"),e.releaseTarget(),e.dispatchChange(r)}},dispatchChange:function(t){t.valueElement&&e.isElementType(t.valueElement,"input")&&e.fireEvent(t.valueElement,"change")},dispatchFineChange:function(e){if(e.onFineChange){var t;typeof e.onFineChange=="string"?t=new Function(e.onFineChange):t=e.onFineChange,t.call(e)}},setPad:function(t,n,r,i){var s=e.getAbsPointerPos(n),o=r+s.x-e._pointerOrigin.x-t.padding-t.insetWidth,u=i+s.y-e._pointerOrigin.y-t.padding-t.insetWidth,a=o*(360/(t.width-1)),f=100-u*(100/(t.height-1));switch(e.getPadYComponent(t)){case"s":t.fromHSV(a,f,null,e.leaveSld);break;case"v":t.fromHSV(a,null,f,e.leaveSld)}},setSld:function(t,n,r){var i=e.getAbsPointerPos(n),s=r+i.y-e._pointerOrigin.y-t.padding-t.insetWidth,o=100-s*(100/(t.height-1));switch(e.getSliderComponent(t)){case"s":t.fromHSV(null,o,null,e.leavePad);break;case"v":t.fromHSV(null,null,o,e.leavePad)}},_vmlNS:"jsc_vml_",_vmlCSS:"jsc_vml_css_",_vmlReady:!1,initVML:function(){if(!e._vmlReady){var t=document;t.namespaces[e._vmlNS]||t.namespaces.add(e._vmlNS,"urn:schemas-microsoft-com:vml");if(!t.styleSheets[e._vmlCSS]){var n=["shape","shapetype","group","background","path","formulas","handles","fill","stroke","shadow","textbox","textpath","imagedata","line","polyline","curve","rect","roundrect","oval","arc","image"],r=t.createStyleSheet();r.owningElement.id=e._vmlCSS;for(var i=0;i<n.length;i+=1)r.addRule(e._vmlNS+"\\:"+n[i],"behavior:url(#default#VML);")}e._vmlReady=!0}},createPalette:function(){var t={elm:null,draw:null};if(e.isCanvasSupported){var n=document.createElement("canvas"),r=n.getContext("2d"),i=function(e,t,i){n.width=e,n.height=t,r.clearRect(0,0,n.width,n.height);var s=r.createLinearGradient(0,0,n.width,0);s.addColorStop(0,"#F00"),s.addColorStop(1/6,"#FF0"),s.addColorStop(2/6,"#0F0"),s.addColorStop(.5,"#0FF"),s.addColorStop(4/6,"#00F"),s.addColorStop(5/6,"#F0F"),s.addColorStop(1,"#F00"),r.fillStyle=s,r.fillRect(0,0,n.width,n.height);var o=r.createLinearGradient(0,0,0,n.height);switch(i.toLowerCase()){case"s":o.addColorStop(0,"rgba(255,255,255,0)"),o.addColorStop(1,"rgba(255,255,255,1)");break;case"v":o.addColorStop(0,"rgba(0,0,0,0)"),o.addColorStop(1,"rgba(0,0,0,1)")}r.fillStyle=o,r.fillRect(0,0,n.width,n.height)};t.elm=n,t.draw=i}else{e.initVML();var s=document.createElement("div");s.style.position="relative",s.style.overflow="hidden";var o=document.createElement(e._vmlNS+":fill");o.type="gradient",o.method="linear",o.angle="90",o.colors="16.67% #F0F, 33.33% #00F, 50% #0FF, 66.67% #0F0, 83.33% #FF0";var u=document.createElement(e._vmlNS+":rect");u.style.position="absolute",u.style.left="-1px",u.style.top="-1px",u.stroked=!1,u.appendChild(o),s.appendChild(u);var a=document.createElement(e._vmlNS+":fill");a.type="gradient",a.method="linear",a.angle="180",a.opacity="0";var f=document.createElement(e._vmlNS+":rect");f.style.position="absolute",f.style.left="-1px",f.style.top="-1px",f.stroked=!1,f.appendChild(a),s.appendChild(f);var i=function(e,t,n){s.style.width=e+"px",s.style.height=t+"px",u.style.width=f.style.width=e+1+"px",u.style.height=f.style.height=t+1+"px",o.color="#F00",o.color2="#F00";switch(n.toLowerCase()){case"s":a.color=a.color2="#FFF";break;case"v":a.color=a.color2="#000"}};t.elm=s,t.draw=i}return t},createSliderGradient:function(){var t={elm:null,draw:null};if(e.isCanvasSupported){var n=document.createElement("canvas"),r=n.getContext("2d"),i=function(e,t,i,s){n.width=e,n.height=t,r.clearRect(0,0,n.width,n.height);var o=r.createLinearGradient(0,0,0,n.height);o.addColorStop(0,i),o.addColorStop(1,s),r.fillStyle=o,r.fillRect(0,0,n.width,n.height)};t.elm=n,t.draw=i}else{e.initVML();var s=document.createElement("div");s.style.position="relative",s.style.overflow="hidden";var o=document.createElement(e._vmlNS+":fill");o.type="gradient",o.method="linear",o.angle="180";var u=document.createElement(e._vmlNS+":rect");u.style.position="absolute",u.style.left="-1px",u.style.top="-1px",u.stroked=!1,u.appendChild(o),s.appendChild(u);var i=function(e,t,n,r){s.style.width=e+"px",s.style.height=t+"px",u.style.width=e+1+"px",u.style.height=t+1+"px",o.color=n,o.color2=r};t.elm=s,t.draw=i}return t},leaveValue:1,leaveStyle:2,leavePad:4,leaveSld:8,BoxShadow:function(){var e=function(e,t,n,r,i,s){this.hShadow=e,this.vShadow=t,this.blur=n,this.spread=r,this.color=i,this.inset=!!s};return e.prototype.toString=function(){var e=[Math.round(this.hShadow)+"px",Math.round(this.vShadow)+"px",Math.round(this.blur)+"px",Math.round(this.spread)+"px",this.color];return this.inset&&e.push("inset"),e.join(" ")},e}(),jscolor:function(t,n){function i(e,t,n){e/=255,t/=255,n/=255;var r=Math.min(Math.min(e,t),n),i=Math.max(Math.max(e,t),n),s=i-r;if(s===0)return[null,0,100*i];var o=e===r?3+(n-t)/s:t===r?5+(e-n)/s:1+(t-e)/s;return[60*(o===6?0:o),100*(s/i),100*i]}function s(e,t,n){var r=255*(n/100);if(e===null)return[r,r,r];e/=60,t/=100;var i=Math.floor(e),s=i%2?e-i:1-(e-i),o=r*(1-t),u=r*(1-t*s);switch(i){case 6:case 0:return[r,u,o];case 1:return[u,r,o];case 2:return[o,r,u];case 3:return[o,u,r];case 4:return[u,o,r];case 5:return[r,o,u]}}function o(){e.unsetClass(d.targetElement,d.activeClass),e.picker.wrap.parentNode.removeChild(e.picker.wrap),delete e.picker.owner}function u(){function l(){var e=d.insetColor.split(/\s+/),n=e.length<2?e[0]:e[1]+" "+e[0]+" "+e[0]+" "+e[1];t.btn.style.borderColor=n}d._processParentElementsInDOM(),e.picker||(e.picker={owner:null,wrap:document.createElement("div"),box:document.createElement("div"),boxS:document.createElement("div"),boxB:document.createElement("div"),pad:document.createElement("div"),padB:document.createElement("div"),padM:document.createElement("div"),padPal:e.createPalette(),cross:document.createElement("div"),crossBY:document.createElement("div"),crossBX:document.createElement("div"),crossLY:document.createElement("div"),crossLX:document.createElement("div"),sld:document.createElement("div"),sldB:document.createElement("div"),sldM:document.createElement("div"),sldGrad:e.createSliderGradient(),sldPtrS:document.createElement("div"),sldPtrIB:document.createElement("div"),sldPtrMB:document.createElement("div"),sldPtrOB:document.createElement("div"),btn:document.createElement("div"),btnT:document.createElement("span")},e.picker.pad.appendChild(e.picker.padPal.elm),e.picker.padB.appendChild(e.picker.pad),e.picker.cross.appendChild(e.picker.crossBY),e.picker.cross.appendChild(e.picker.crossBX),e.picker.cross.appendChild(e.picker.crossLY),e.picker.cross.appendChild(e.picker.crossLX),e.picker.padB.appendChild(e.picker.cross),e.picker.box.appendChild(e.picker.padB),e.picker.box.appendChild(e.picker.padM),e.picker.sld.appendChild(e.picker.sldGrad.elm),e.picker.sldB.appendChild(e.picker.sld),e.picker.sldB.appendChild(e.picker.sldPtrOB),e.picker.sldPtrOB.appendChild(e.picker.sldPtrMB),e.picker.sldPtrMB.appendChild(e.picker.sldPtrIB),e.picker.sldPtrIB.appendChild(e.picker.sldPtrS),e.picker.box.appendChild(e.picker.sldB),e.picker.box.appendChild(e.picker.sldM),e.picker.btn.appendChild(e.picker.btnT),e.picker.box.appendChild(e.picker.btn),e.picker.boxB.appendChild(e.picker.box),e.picker.wrap.appendChild(e.picker.boxS),e.picker.wrap.appendChild(e.picker.boxB));var t=e.picker,n=!!e.getSliderComponent(d),r=e.getPickerDims(d),i=2*d.pointerBorderWidth+d.pointerThickness+2*d.crossSize,s=e.getPadToSliderPadding(d),o=Math.min(d.borderRadius,Math.round(d.padding*Math.PI)),u="crosshair";t.wrap.style.clear="both",t.wrap.style.width=r[0]+2*d.borderWidth+"px",t.wrap.style.height=r[1]+2*d.borderWidth+"px",t.wrap.style.zIndex=d.zIndex,t.box.style.width=r[0]+"px",t.box.style.height=r[1]+"px",t.boxS.style.position="absolute",t.boxS.style.left="0",t.boxS.style.top="0",t.boxS.style.width="100%",t.boxS.style.height="100%",e.setBorderRadius(t.boxS,o+"px"),t.boxB.style.position="relative",t.boxB.style.border=d.borderWidth+"px solid",t.boxB.style.borderColor=d.borderColor,t.boxB.style.background=d.backgroundColor,e.setBorderRadius(t.boxB,o+"px"),t.padM.style.background=t.sldM.style.background="#FFF",e.setStyle(t.padM,"opacity","0"),e.setStyle(t.sldM,"opacity","0"),t.pad.style.position="relative",t.pad.style.width=d.width+"px",t.pad.style.height=d.height+"px",t.padPal.draw(d.width,d.height,e.getPadYComponent(d)),t.padB.style.position="absolute",t.padB.style.left=d.padding+"px",t.padB.style.top=d.padding+"px",t.padB.style.border=d.insetWidth+"px solid",t.padB.style.borderColor=d.insetColor,t.padM._jscInstance=d,t.padM._jscControlName="pad",t.padM.style.position="absolute",t.padM.style.left="0",t.padM.style.top="0",t.padM.style.width=d.padding+2*d.insetWidth+d.width+s/2+"px",t.padM.style.height=r[1]+"px",t.padM.style.cursor=u,t.cross.style.position="absolute",t.cross.style.left=t.cross.style.top="0",t.cross.style.width=t.cross.style.height=i+"px",t.crossBY.style.position=t.crossBX.style.position="absolute",t.crossBY.style.background=t.crossBX.style.background=d.pointerBorderColor,t.crossBY.style.width=t.crossBX.style.height=2*d.pointerBorderWidth+d.pointerThickness+"px",t.crossBY.style.height=t.crossBX.style.width=i+"px",t.crossBY.style.left=t.crossBX.style.top=Math.floor(i/2)-Math.floor(d.pointerThickness/2)-d.pointerBorderWidth+"px",t.crossBY.style.top=t.crossBX.style.left="0",t.crossLY.style.position=t.crossLX.style.position="absolute",t.crossLY.style.background=t.crossLX.style.background=d.pointerColor,t.crossLY.style.height=t.crossLX.style.width=i-2*d.pointerBorderWidth+"px",t.crossLY.style.width=t.crossLX.style.height=d.pointerThickness+"px",t.crossLY.style.left=t.crossLX.style.top=Math.floor(i/2)-Math.floor(d.pointerThickness/2)+"px",t.crossLY.style.top=t.crossLX.style.left=d.pointerBorderWidth+"px",t.sld.style.overflow="hidden",t.sld.style.width=d.sliderSize+"px",t.sld.style.height=d.height+"px",t.sldGrad.draw(d.sliderSize,d.height,"#000","#000"),t.sldB.style.display=n?"block":"none",t.sldB.style.position="absolute",t.sldB.style.right=d.padding+"px",t.sldB.style.top=d.padding+"px",t.sldB.style.border=d.insetWidth+"px solid",t.sldB.style.borderColor=d.insetColor,t.sldM._jscInstance=d,t.sldM._jscControlName="sld",t.sldM.style.display=n?"block":"none",t.sldM.style.position="absolute",t.sldM.style.right="0",t.sldM.style.top="0",t.sldM.style.width=d.sliderSize+s/2+d.padding+2*d.insetWidth+"px",t.sldM.style.height=r[1]+"px",t.sldM.style.cursor="default",t.sldPtrIB.style.border=t.sldPtrOB.style.border=d.pointerBorderWidth+"px solid "+d.pointerBorderColor,t.sldPtrOB.style.position="absolute",t.sldPtrOB.style.left=-(2*d.pointerBorderWidth+d.pointerThickness)+"px",t.sldPtrOB.style.top="0",t.sldPtrMB.style.border=d.pointerThickness+"px solid "+d.pointerColor,t.sldPtrS.style.width=d.sliderSize+"px",t.sldPtrS.style.height=m+"px",t.btn.style.display=d.closable?"block":"none",t.btn.style.position="absolute",t.btn.style.left=d.padding+"px",t.btn.style.bottom=d.padding+"px",t.btn.style.padding="0 15px",t.btn.style.height=d.buttonHeight+"px",t.btn.style.border=d.insetWidth+"px solid",l(),t.btn.style.color=d.buttonColor,t.btn.style.font="12px sans-serif",t.btn.style.textAlign="center";try{t.btn.style.cursor="pointer"}catch(c){t.btn.style.cursor="hand"}t.btn.onmousedown=function(){d.hide()},t.btnT.style.lineHeight=d.buttonHeight+"px",t.btnT.innerHTML="",t.btnT.appendChild(document.createTextNode(d.closeText)),a(),f(),e.picker.owner&&e.picker.owner!==d&&e.unsetClass(e.picker.owner.targetElement,d.activeClass),e.picker.owner=d,e.isElementType(v,"body")?e.redrawPosition():e._drawPosition(d,0,0,"relative",!1),t.wrap.parentNode!=v&&v.appendChild(t.wrap),e.setClass(d.targetElement,d.activeClass)}function a(){switch(e.getPadYComponent(d)){case"s":var t=1;break;case"v":var t=2}var n=Math.round(d.hsv[0]/360*(d.width-1)),r=Math.round((1-d.hsv[t]/100)*(d.height-1)),i=2*d.pointerBorderWidth+d.pointerThickness+2*d.crossSize,o=-Math.floor(i/2);e.picker.cross.style.left=n+o+"px",e.picker.cross.style.top=r+o+"px";switch(e.getSliderComponent(d)){case"s":var u=s(d.hsv[0],100,d.hsv[2]),a=s(d.hsv[0],0,d.hsv[2]),f="rgb("+Math.round(u[0])+","+Math.round(u[1])+","+Math.round(u[2])+")",l="rgb("+Math.round(a[0])+","+Math.round(a[1])+","+Math.round(a[2])+")";e.picker.sldGrad.draw(d.sliderSize,d.height,f,l);break;case"v":var c=s(d.hsv[0],d.hsv[1],100),f="rgb("+Math.round(c[0])+","+Math.round(c[1])+","+Math.round(c[2])+")",l="#000";e.picker.sldGrad.draw(d.sliderSize,d.height,f,l)}}function f(){var t=e.getSliderComponent(d);if(t){switch(t){case"s":var n=1;break;case"v":var n=2}var r=Math.round((1-d.hsv[n]/100)*(d.height-1));e.picker.sldPtrOB.style.top=r-(2*d.pointerBorderWidth+d.pointerThickness)-Math.floor(m/2)+"px"}}function l(){return e.picker&&e.picker.owner===d}function c(){d.importColor()}this.value=null,this.valueElement=t,this.styleElement=t,this.required=!0,this.refine=!0,this.hash=!1,this.uppercase=!0,this.onFineChange=null,this.activeClass="jscolor-active",this.minS=0,this.maxS=100,this.minV=0,this.maxV=100,this.hsv=[0,0,100],this.rgb=[255,255,255],this.width=181,this.height=101,this.showOnClick=!0,this.mode="HSV",this.position="bottom",this.smartPosition=!0,this.sliderSize=16,this.crossSize=8,this.closable=!1,this.closeText="Close",this.buttonColor="#000000",this.buttonHeight=18,this.padding=12,this.backgroundColor="#FFFFFF",this.borderWidth=1,this.borderColor="#BBBBBB",this.borderRadius=8,this.insetWidth=1,this.insetColor="#BBBBBB",this.shadow=!0,this.shadowBlur=15,this.shadowColor="rgba(0,0,0,0.2)",this.pointerColor="#4C4C4C",this.pointerBorderColor="#FFFFFF",this.pointerBorderWidth=1,this.pointerThickness=2,this.zIndex=1e3,this.container=null;for(var r in n)n.hasOwnProperty(r)&&(this[r]=n[r]);this.hide=function(){l()&&o()},this.show=function(){u()},this.redraw=function(){l()&&u()},this.importColor=function(){this.valueElement?e.isElementType(this.valueElement,"input")?this.refine?!this.required&&/^\s*$/.test(this.valueElement.value)?(this.valueElement.value="",this.styleElement&&(this.styleElement.style.backgroundImage=this.styleElement._jscOrigStyle.backgroundImage,this.styleElement.style.backgroundColor=this.styleElement._jscOrigStyle.backgroundColor,this.styleElement.style.color=this.styleElement._jscOrigStyle.color),this.exportColor(e.leaveValue|e.leaveStyle)):this.fromString(this.valueElement.value)||this.exportColor():this.fromString(this.valueElement.value,e.leaveValue)||(this.styleElement&&(this.styleElement.style.backgroundImage=this.styleElement._jscOrigStyle.backgroundImage,this.styleElement.style.backgroundColor=this.styleElement._jscOrigStyle.backgroundColor,this.styleElement.style.color=this.styleElement._jscOrigStyle.color),this.exportColor(e.leaveValue|e.leaveStyle)):this.exportColor():this.exportColor()},this.exportColor=function(t){if(!(t&e.leaveValue)&&this.valueElement){var n=this.toString();this.uppercase&&(n=n.toUpperCase()),this.hash&&(n="#"+n),e.isElementType(this.valueElement,"input")?this.valueElement.value=n:this.valueElement.innerHTML=n}t&e.leaveStyle||this.styleElement&&(this.styleElement.style.backgroundImage="none",this.styleElement.style.backgroundColor="#"+this.toString(),this.styleElement.style.color=this.isLight()?"#000":"#FFF"),!(t&e.leavePad)&&l()&&a(),!(t&e.leaveSld)&&l()&&f()},this.fromHSV=function(e,t,n,r){if(e!==null){if(isNaN(e))return!1;e=Math.max(0,Math.min(360,e))}if(t!==null){if(isNaN(t))return!1;t=Math.max(0,Math.min(100,this.maxS,t),this.minS)}if(n!==null){if(isNaN(n))return!1;n=Math.max(0,Math.min(100,this.maxV,n),this.minV)}this.rgb=s(e===null?this.hsv[0]:this.hsv[0]=e,t===null?this.hsv[1]:this.hsv[1]=t,n===null?this.hsv[2]:this.hsv[2]=n),this.exportColor(r)},this.fromRGB=function(e,t,n,r){if(e!==null){if(isNaN(e))return!1;e=Math.max(0,Math.min(255,e))}if(t!==null){if(isNaN(t))return!1;t=Math.max(0,Math.min(255,t))}if(n!==null){if(isNaN(n))return!1;n=Math.max(0,Math.min(255,n))}var o=i(e===null?this.rgb[0]:e,t===null?this.rgb[1]:t,n===null?this.rgb[2]:n);o[0]!==null&&(this.hsv[0]=Math.max(0,Math.min(360,o[0]))),o[2]!==0&&(this.hsv[1]=o[1]===null?null:Math.max(0,this.minS,Math.min(100,this.maxS,o[1]))),this.hsv[2]=o[2]===null?null:Math.max(0,this.minV,Math.min(100,this.maxV,o[2]));var u=s(this.hsv[0],this.hsv[1],this.hsv[2]);this.rgb[0]=u[0],this.rgb[1]=u[1],this.rgb[2]=u[2],this.exportColor(r)},this.fromString=function(e,t){var n;if(n=e.match(/^\W*([0-9A-F]{3}([0-9A-F]{3})?)\W*$/i))return n[1].length===6?this.fromRGB(parseInt(n[1].substr(0,2),16),parseInt(n[1].substr(2,2),16),parseInt(n[1].substr(4,2),16),t):this.fromRGB(parseInt(n[1].charAt(0)+n[1].charAt(0),16),parseInt(n[1].charAt(1)+n[1].charAt(1),16),parseInt(n[1].charAt(2)+n[1].charAt(2),16),t),!0;if(n=e.match(/^\W*rgba?\(([^)]*)\)\W*$/i)){var r=n[1].split(","),i=/^\s*(\d*)(\.\d+)?\s*$/,s,o,u;if(r.length>=3&&(s=r[0].match(i))&&(o=r[1].match(i))&&(u=r[2].match(i))){var a=parseFloat((s[1]||"0")+(s[2]||"")),f=parseFloat((o[1]||"0")+(o[2]||"")),l=parseFloat((u[1]||"0")+(u[2]||""));return this.fromRGB(a,f,l,t),!0}}return!1},this.toString=function(){return(256|Math.round(this.rgb[0])).toString(16).substr(1)+(256|Math.round(this.rgb[1])).toString(16).substr(1)+(256|Math.round(this.rgb[2])).toString(16).substr(1)},this.toHEXString=function(){return"#"+this.toString().toUpperCase()},this.toRGBString=function(){return"rgb("+Math.round(this.rgb[0])+","+Math.round(this.rgb[1])+","+Math.round(this.rgb[2])+")"},this.isLight=function(){return.213*this.rgb[0]+.715*this.rgb[1]+.072*this.rgb[2]>127.5},this._processParentElementsInDOM=function(){if(this._linkedElementsProcessed)return;this._linkedElementsProcessed=!0;var t=this.targetElement;do{var n=e.getStyle(t);n&&n.position.toLowerCase()==="fixed"&&(this.fixed=!0),t!==this.targetElement&&(t._jscEventsAttached||(e.attachEvent(t,"scroll",e.onParentScroll),t._jscEventsAttached=!0))}while((t=t.parentNode)&&!e.isElementType(t,"body"))};if(typeof t=="string"){var h=t,p=document.getElementById(h);p?this.targetElement=p:e.warn("Could not find target element with ID '"+h+"'")}else t?this.targetElement=t:e.warn("Invalid target element: '"+t+"'");if(this.targetElement._jscLinkedInstance){e.warn("Cannot link jscolor twice to the same element. Skipping.");return}this.targetElement._jscLinkedInstance=this,this.valueElement=e.fetchElement(this.valueElement),this.styleElement=e.fetchElement(this.styleElement);var d=this,v=this.container?e.fetchElement(this.container):document.getElementsByTagName("body")[0],m=3;if(e.isElementType(this.targetElement,"button"))if(this.targetElement.onclick){var g=this.targetElement.onclick;this.targetElement.onclick=function(e){return g.call(this,e),!1}}else this.targetElement.onclick=function(){return!1};if(this.valueElement&&e.isElementType(this.valueElement,"input")){var y=function(){d.fromString(d.valueElement.value,e.leaveValue),e.dispatchFineChange(d)};e.attachEvent(this.valueElement,"keyup",y),e.attachEvent(this.valueElement,"input",y),e.attachEvent(this.valueElement,"blur",c),this.valueElement.setAttribute("autocomplete","off")}this.styleElement&&(this.styleElement._jscOrigStyle={backgroundImage:this.styleElement.style.backgroundImage,backgroundColor:this.styleElement.style.backgroundColor,color:this.styleElement.style.color}),this.value?this.fromString(this.value)||this.exportColor():this.importColor()}};return e.jscolor.lookupClass="jscolor",e.jscolor.installByClassName=function(t){var n=document.getElementsByTagName("input"),r=document.getElementsByTagName("button");e.tryInstallOnElements(n,t),e.tryInstallOnElements(r,t)},e.register(),e.jscolor}()); \ No newline at end of file
diff --git a/apps/theming/js/settings-admin.js b/apps/theming/js/settings-admin.js
new file mode 100644
index 00000000000..bd4b4b34ed1
--- /dev/null
+++ b/apps/theming/js/settings-admin.js
@@ -0,0 +1,141 @@
+/**
+ * @author Björn Schießle <bjoern@schiessle.org>
+ *
+ * @copyright Copyright (c) 2016, Bjoern Schiessle
+ * @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 as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your opinion) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+function setThemingValue(setting, value) {
+ OC.msg.startSaving('#theming_settings_msg');
+ $.post(
+ OC.generateUrl('/apps/theming/ajax/updateStylesheet'), {'setting' : setting, 'value' : value}
+ ).done(function(response) {
+ OC.msg.finishedSaving('#theming_settings_msg', response);
+ }).fail(function(response) {
+ OC.msg.finishedSaving('#theming_settings_msg', response);
+ });
+ preview(setting, value);
+}
+
+function preview(setting, value) {
+ if (setting === 'color') {
+ var headerClass = document.getElementById('header');
+ headerClass.style.background = value;
+ headerClass.style.backgroundImage = '../img/logo-icon.svg';
+ }
+ if (setting === 'logoMime') {
+ console.log(setting);
+ var logos = document.getElementsByClassName('logo-icon');
+ var timestamp = new Date().getTime();
+ if(value !== '') {
+ logos[0].style.background = "url('" + OC.generateUrl('/apps/theming/logo') + "?v" + timestamp + "')";
+ logos[0].style.backgroundSize = "62px 34px";
+ } else {
+ logos[0].style.background = "url('" + OC.getRootPath() + '/core/img/logo-icon.svg?v' + timestamp +"')";
+ logos[0].style.backgroundSize = "62px 34px";
+ }
+ }
+}
+
+$(document).ready(function () {
+ $('#theming [data-toggle="tooltip"]').tooltip();
+
+ var uploadParamsLogo = {
+ pasteZone: null,
+ dropZone: null,
+ done: function (e, response) {
+ preview('logoMime', response.result.data.name);
+ OC.msg.finishedSaving('#theming_settings_msg', response.result);
+ },
+ submit: function(e, response) {
+ OC.msg.startSaving('#theming_settings_msg');
+ },
+ fail: function (e, data){
+ OC.msg.finishedSaving('#theming_settings_msg', response);
+ }
+ };
+ var uploadParamsLogin = {
+ pasteZone: null,
+ dropZone: null,
+ done: function (e, response) {
+ preview('backgroundMime', response.result.data.name);
+ OC.msg.finishedSaving('#theming_settings_msg', response.result);
+ },
+ submit: function(e, response) {
+ OC.msg.startSaving('#theming_settings_msg');
+ },
+ fail: function (e, data){
+ OC.msg.finishedSaving('#theming_settings_msg', response);
+ }
+ };
+
+ $('#uploadlogo').fileupload(uploadParamsLogo);
+ $('#upload-login-background').fileupload(uploadParamsLogin);
+
+ $('#theming-name').change(function(e) {
+ var el = $(this);
+ $.when(el.focusout()).then(function() {
+ setThemingValue('name', $(this).val());
+ });
+ if (e.keyCode == 13) {
+ setThemingValue('name', $(this).val());
+ }
+ });
+
+ $('#theming-url').change(function(e) {
+ var el = $(this);
+ $.when(el.focusout()).then(function() {
+ setThemingValue('url', $(this).val());
+ });
+ if (e.keyCode == 13) {
+ setThemingValue('url', $(this).val());
+ }
+ });
+
+ $('#theming-slogan').change(function(e) {
+ var el = $(this);
+ $.when(el.focusout()).then(function() {
+ setThemingValue('slogan', $(this).val());
+ });
+ if (e.keyCode == 13) {
+ setThemingValue('slogan', $(this).val());
+ }
+ });
+
+ $('#theming-color').change(function (e) {
+ setThemingValue('color', '#' + $(this).val());
+ });
+
+ $('.theme-undo').click(function (e) {
+ var setting = $(this).data('setting');
+ OC.msg.startSaving('#theming_settings_msg');
+ $.post(
+ OC.generateUrl('/apps/theming/ajax/undoChanges'), {'setting' : setting}
+ ).done(function(response) {
+ if (setting === 'color') {
+ var colorPicker = document.getElementById('theming-color');
+ colorPicker.style.backgroundColor = response.data.value;
+ colorPicker.value = response.data.value.slice(1);
+ } else if (setting !== 'logoMime' && setting !== 'backgroundMime') {
+ var input = document.getElementById('theming-'+setting);
+ input.value = response.data.value;
+ }
+ preview(setting, response.data.value);
+ OC.msg.finishedSaving('#theming_settings_msg', response);
+ });
+ });
+});
diff --git a/apps/theming/lib/controller/themingcontroller.php b/apps/theming/lib/controller/themingcontroller.php
new file mode 100644
index 00000000000..995f929b510
--- /dev/null
+++ b/apps/theming/lib/controller/themingcontroller.php
@@ -0,0 +1,241 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Bjoern Schiessle <bjoern@schiessle.org>
+ * @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Theming\Controller;
+
+use OCA\Theming\Template;
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\Files\IRootFolder;
+use OCP\IConfig;
+use OCP\IL10N;
+use OCP\IRequest;
+
+/**
+ * Class ThemingController
+ *
+ * handle ajax requests to update the theme
+ *
+ * @package OCA\Theming\Controller
+ */
+class ThemingController extends Controller {
+ /** @var Template */
+ private $template;
+ /** @var IL10N */
+ private $l;
+ /** @var IConfig */
+ private $config;
+ /** @var IRootFolder */
+ private $rootFolder;
+
+ /**
+ * ThemingController constructor.
+ *
+ * @param string $appName
+ * @param IRequest $request
+ * @param IConfig $config
+ * @param Template $template
+ * @param IL10N $l
+ * @param IRootFolder $rootFolder
+ */
+ public function __construct(
+ $appName,
+ IRequest $request,
+ IConfig $config,
+ Template $template,
+ IL10N $l,
+ IRootFolder $rootFolder
+ ) {
+ parent::__construct($appName, $request);
+
+ $this->template = $template;
+ $this->l = $l;
+ $this->config = $config;
+ $this->rootFolder = $rootFolder;
+ }
+
+ /**
+ * @param string $setting
+ * @param string $value
+ * @return DataResponse
+ * @internal param string $color
+ */
+ public function updateStylesheet($setting, $value) {
+ $this->template->set($setting, $value);
+ return new DataResponse(
+ [
+ 'data' =>
+ [
+ 'message' => $this->l->t('Saved')
+ ],
+ 'status' => 'success'
+ ]
+ );
+ }
+
+ /**
+ * Update the logos and background image
+ *
+ * @return DataResponse
+ */
+ public function updateLogo() {
+ $newLogo = $this->request->getUploadedFile('uploadlogo');
+ $newBackgroundLogo = $this->request->getUploadedFile('upload-login-background');
+ if (empty($newLogo) && empty($newBackgroundLogo)) {
+ return new DataResponse(
+ [
+ 'data' => [
+ 'message' => $this->l->t('No file uploaded')
+ ]
+ ],
+ Http::STATUS_UNPROCESSABLE_ENTITY);
+ }
+ $name = '';
+ if(!empty($newLogo)) {
+ $target = $this->rootFolder->newFile('themedinstancelogo');
+ stream_copy_to_stream(fopen($newLogo['tmp_name'], 'r'), $target->fopen('w'));
+ $this->template->set('logoMime', $newLogo['type']);
+ $name = $newLogo['name'];
+ }
+ if(!empty($newBackgroundLogo)) {
+ $target = $this->rootFolder->newFile('themedbackgroundlogo');
+ stream_copy_to_stream(fopen($newBackgroundLogo['tmp_name'], 'r'), $target->fopen('w'));
+ $this->template->set('backgroundMime', $newBackgroundLogo['type']);
+ $name = $newBackgroundLogo['name'];
+ }
+
+ return new DataResponse(
+ [
+ 'data' =>
+ [
+ 'name' => $name,
+ 'message' => $this->l->t('Saved')
+ ],
+ 'status' => 'success'
+ ]
+ );
+ }
+
+ /**
+ * Revert setting to default value
+ *
+ * @param string $setting setting which should be reverted
+ * @return DataResponse
+ */
+ public function undo($setting) {
+ $value = $this->template->undo($setting);
+ return new DataResponse(
+ [
+ 'data' =>
+ [
+ 'value' => $value,
+ 'message' => $this->l->t('Saved')
+ ],
+ 'status' => 'success'
+ ]
+ );
+ }
+
+ /**
+ * @PublicPage
+ * @NoCSRFRequired
+ *
+ * @return Http\StreamResponse
+ */
+ public function getLogo() {
+ $pathToLogo = $this->config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data/') . '/themedinstancelogo';
+ if(!file_exists($pathToLogo)) {
+ return new DataResponse();
+ }
+
+ \OC_Response::setExpiresHeader(gmdate('D, d M Y H:i:s', time() + (60*60*24*45)) . ' GMT');
+ \OC_Response::enableCaching();
+ $response = new Http\StreamResponse($pathToLogo);
+ $response->cacheFor(3600);
+ $response->addHeader('Content-Disposition', 'attachment');
+ $response->addHeader('Content-Type', $this->config->getAppValue($this->appName, 'logoMime', ''));
+ return $response;
+ }
+
+ /**
+ * @PublicPage
+ * @NoCSRFRequired
+ *
+ * @return Http\StreamResponse
+ */
+ public function getLoginBackground() {
+ $pathToLogo = $this->config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data/') . '/themedbackgroundlogo';
+ if(!file_exists($pathToLogo)) {
+ return new DataResponse();
+ }
+
+ \OC_Response::setExpiresHeader(gmdate('D, d M Y H:i:s', time() + (60*60*24*45)) . ' GMT');
+ \OC_Response::enableCaching();
+ $response = new Http\StreamResponse($pathToLogo);
+ $response->cacheFor(3600);
+ $response->addHeader('Content-Disposition', 'attachment');
+ $response->addHeader('Content-Type', $this->config->getAppValue($this->appName, 'backgroundMime', ''));
+ return $response;
+ }
+
+ /**
+ * @NoCSRFRequired
+ * @PublicPage
+ *
+ * @return Http\DataDownloadResponse
+ */
+ public function getStylesheet() {
+ $cacheBusterValue = $this->config->getAppValue('theming', 'cachebuster', '0');
+ $responseCss = '';
+ $color = $this->config->getAppValue($this->appName, 'color');
+ if($color !== '') {
+ $responseCss .= sprintf(
+ '#body-user #header,#body-settings #header,#body-public #header {background-color: %s}',
+ $color
+ );
+ }
+ $logo = $this->config->getAppValue($this->appName, 'logoMime');
+ if($logo !== '') {
+ $responseCss .= sprintf('#header .logo {
+ background-image: url(\'./logo?v='.$cacheBusterValue.'\');
+ }
+ #header .logo-icon {
+ background-image: url(\'./logo?v='.$cacheBusterValue.'\');
+ background-size: 62px 34px;
+ }'
+ );
+ }
+ $backgroundLogo = $this->config->getAppValue($this->appName, 'backgroundMime');
+ if($backgroundLogo !== '') {
+ $responseCss .= '#body-login {
+ background-image: url(\'./loginbackground?v='.$cacheBusterValue.'\');
+ }';
+ }
+
+ \OC_Response::setExpiresHeader(gmdate('D, d M Y H:i:s', time() + (60*60*24*45)) . ' GMT');
+ \OC_Response::enableCaching();
+ $response = new Http\DataDownloadResponse($responseCss, 'style.css', 'text/css');
+ $response->cacheFor(3600);
+ return $response;
+ }
+}
diff --git a/apps/theming/lib/template.php b/apps/theming/lib/template.php
new file mode 100644
index 00000000000..01e3ca8b7c0
--- /dev/null
+++ b/apps/theming/lib/template.php
@@ -0,0 +1,150 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Bjoern Schiessle <bjoern@schiessle.org>
+ * @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Theming;
+
+use OCP\IConfig;
+use OCP\IL10N;
+use OCP\IURLGenerator;
+
+/**
+ * Class Template
+ *
+ * Handle all the values which can be modified by this app
+ *
+ * @package OCA\Theming
+ */
+class Template extends \OC_Defaults {
+ /** @var IConfig */
+ private $config;
+ /** @var IL10N */
+ private $l;
+ /** @var IURLGenerator */
+ private $urlGenerator;
+ /** @var string */
+ private $name;
+ /** @var string */
+ private $url;
+ /** @var string */
+ private $slogan;
+ /** @var string */
+ private $color;
+
+ /**
+ * Template constructor.
+ *
+ * @param IConfig $config
+ * @param IL10N $l
+ * @param IURLGenerator $urlGenerator
+ * @param \OC_Defaults $defaults
+ */
+ public function __construct(IConfig $config,
+ IL10N $l,
+ IURLGenerator $urlGenerator,
+ \OC_Defaults $defaults
+ ) {
+ parent::__construct();
+ $this->config = $config;
+ $this->l = $l;
+ $this->urlGenerator = $urlGenerator;
+
+ $this->name = $defaults->getName();
+ $this->url = $defaults->getBaseUrl();
+ $this->slogan = $defaults->getSlogan();
+ $this->color = $defaults->getMailHeaderColor();
+ }
+
+ public function getName() {
+ return $this->config->getAppValue('theming', 'name', $this->name);
+ }
+
+ public function getEntity() {
+ return $this->config->getAppValue('theming', 'name', $this->name);
+ }
+
+ public function getBaseUrl() {
+ return $this->config->getAppValue('theming', 'url', $this->url);
+ }
+
+ public function getSlogan() {
+ return $this->config->getAppValue('theming', 'slogan', $this->slogan);
+ }
+
+ /**
+ * Color that is used for the header as well as for mail headers
+ *
+ * @return string
+ */
+ public function getMailHeaderColor() {
+ return $this->config->getAppValue('theming', 'color', $this->color);
+ }
+
+ /**
+ * Increases the cache buster key
+ */
+ private function increaseCacheBuster() {
+ $cacheBusterKey = $this->config->getAppValue('theming', 'cachebuster', '0');
+ $this->config->setAppValue('theming', 'cachebuster', (int)$cacheBusterKey+1);
+ }
+
+ /**
+ * Update setting in the database
+ *
+ * @param string $setting
+ * @param string $value
+ */
+ public function set($setting, $value) {
+ $this->config->setAppValue('theming', $setting, $value);
+ $this->increaseCacheBuster();
+ }
+
+ /**
+ * Revert settings to the default value
+ *
+ * @param string $setting setting which should be reverted
+ * @return string default value
+ */
+ public function undo($setting) {
+ $this->config->deleteAppValue('theming', $setting);
+ $this->increaseCacheBuster();
+
+ switch ($setting) {
+ case 'name':
+ $returnValue = $this->getEntity();
+ break;
+ case 'url':
+ $returnValue = $this->getBaseUrl();
+ break;
+ case 'slogan':
+ $returnValue = $this->getSlogan();
+ break;
+ case 'color':
+ $returnValue = $this->getMailHeaderColor();
+ break;
+ default:
+ $returnValue = '';
+ break;
+ }
+
+ return $returnValue;
+ }
+}
diff --git a/apps/theming/settings/settings-admin.php b/apps/theming/settings/settings-admin.php
new file mode 100644
index 00000000000..a7643960adc
--- /dev/null
+++ b/apps/theming/settings/settings-admin.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Bjoern Schiessle <bjoern@schiessle.org>
+ * @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+\OC_Util::checkAdminUser();
+
+$config = \OC::$server->getConfig();
+$l = \OC::$server->getL10N('theming');
+$urlGenerator = \OC::$server->getURLGenerator();
+
+$theming = \OC::$server->getThemingDefaults();
+
+$themable = true;
+$errorMessage = '';
+$theme = $config->getSystemValue('theme', '');
+
+if ($theme !== '') {
+ $themable = false;
+ $errorMessage = $l->t('You already use a custom theme');
+}
+
+$template = new OCP\Template('theming', 'settings-admin');
+
+$template->assign('themable', $themable);
+$template->assign('errorMessage', $errorMessage);
+$template->assign('name', $theming->getEntity());
+$template->assign('url', $theming->getBaseUrl());
+$template->assign('slogan', $theming->getSlogan());
+$template->assign('color', $theming->getMailHeaderColor());
+$path = $urlGenerator->linkToRoute('theming.Theming.updateLogo');
+$template->assign('uploadLogoRoute', $path);
+
+return $template->fetchPage();
diff --git a/apps/theming/templates/settings-admin.php b/apps/theming/templates/settings-admin.php
new file mode 100644
index 00000000000..27cdd8b60a3
--- /dev/null
+++ b/apps/theming/templates/settings-admin.php
@@ -0,0 +1,57 @@
+<?php
+/** @var array $_ */
+/** @var OC_L10N $l */
+script('theming', 'settings-admin');
+script('theming', '3rdparty/jscolor/jscolor');
+style('theming', 'settings-admin');
+?>
+<div id="theming" class="section">
+ <h2 class="inlineblock"><?php p($l->t('Theming')); ?></h2>
+ <div id="theming_settings_msg" class="msg success inlineblock" style="display: none;">Saved</div>
+ <?php if ($_['themable'] === false) { ?>
+ <p>
+ <?php p($_['errorMessage']) ?>
+ </p>
+ <?php } else { ?>
+ <p>
+ <label><span><?php p($l->t('Name')) ?></span>
+ <input id="theming-name" type="text" placeholder="<?php p($l->t('Name')); ?>" value="<?php p($_['name']) ?>" />
+ </label>
+ <span data-setting="name" data-toggle="tooltip" data-original-title="<?php p($l->t('reset to default')); ?>" class="theme-undo icon icon-history"></span>
+ </p>
+ <p>
+ <label><span><?php p($l->t('Web address')) ?></span>
+ <input id="theming-url" type="text" placeholder="<?php p($l->t('Web address https://…')); ?>" value="<?php p($_['url']) ?>" />
+ </label>
+ <span data-setting="url" data-toggle="tooltip" data-original-title="<?php p($l->t('reset to default')); ?>" class="theme-undo icon icon-history"></span>
+ </p>
+ <p>
+ <label><span><?php p($l->t('Slogan')) ?></span>
+ <input id="theming-slogan" type="text" placeholder="<?php p($l->t('Slogan')); ?>" value="<?php p($_['slogan']) ?>" />
+ </label>
+ <span data-setting="slogan" data-toggle="tooltip" data-original-title="<?php p($l->t('reset to default')); ?>" class="theme-undo icon icon-history"></span>
+ </p>
+ <p>
+ <label><span><?php p($l->t('Color')) ?></span>
+ <input id="theming-color" type="text" class="jscolor" value="<?php p($_['color']) ?>" />
+ </label>
+ <span data-setting="color" data-toggle="tooltip" data-original-title="<?php p($l->t('reset to default')); ?>" class="theme-undo icon icon-history"></span>
+ </p>
+ <p>
+ <form class="uploadButton" method="post" action="<?php p($_['uploadLogoRoute']) ?>">
+ <label for="uploadlogo"><span><?php p($l->t('Logo')) ?></span></label>
+ <input id="uploadlogo" class="upload-logo-field" name="uploadlogo" type="file">
+ <label for="uploadlogo" class="button icon-upload svg" id="uploadlogo" title="<?php p($l->t('Upload new logo')) ?>"></label>
+ <span data-setting="logoMime" data-toggle="tooltip" data-original-title="<?php p($l->t('reset to default')); ?>" class="theme-undo icon icon-history"></span>
+ </form>
+ </p>
+ <p>
+ <form class="uploadButton" method="post" action="<?php p($_['uploadLogoRoute']) ?>">
+ <label for="upload-login-background"><span><?php p($l->t('Log in image')) ?></span></label>
+ <input id="upload-login-background" class="upload-logo-field" name="upload-login-background" type="file">
+ <label for="upload-login-background" class="button icon-upload svg" id="upload-login-background" title="<?php p($l->t("Upload new login background")) ?>"></label>
+ <span data-setting="backgroundMime" data-toggle="tooltip" data-original-title="<?php p($l->t('reset to default')); ?>" class="theme-undo icon icon-history"></span>
+ </form>
+ </p>
+ <?php } ?>
+</div>
diff --git a/apps/theming/tests/lib/TemplateTest.php b/apps/theming/tests/lib/TemplateTest.php
new file mode 100644
index 00000000000..b9623e437b7
--- /dev/null
+++ b/apps/theming/tests/lib/TemplateTest.php
@@ -0,0 +1,301 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OCA\Theming\Tests;
+
+use OCA\Theming\Template;
+use OCP\IConfig;
+use OCP\IL10N;
+use OCP\IURLGenerator;
+use Test\TestCase;
+
+class TemplateTest extends TestCase {
+ /** @var IConfig */
+ private $config;
+ /** @var IL10N */
+ private $l10n;
+ /** @var IURLGenerator */
+ private $urlGenerator;
+ /** @var \OC_Defaults */
+ private $defaults;
+ /** @var Template */
+ private $template;
+
+ public function setUp() {
+ $this->config = $this->getMock('\\OCP\\IConfig');
+ $this->l10n = $this->getMock('\\OCP\\IL10N');
+ $this->urlGenerator = $this->getMock('\\OCP\\IURLGenerator');
+ $this->defaults = $this->getMockBuilder('\\OC_Defaults')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->defaults
+ ->expects($this->at(0))
+ ->method('getName')
+ ->willReturn('Nextcloud');
+ $this->defaults
+ ->expects($this->at(1))
+ ->method('getBaseUrl')
+ ->willReturn('https://nextcloud.com/');
+ $this->defaults
+ ->expects($this->at(2))
+ ->method('getSlogan')
+ ->willReturn('Safe Data');
+ $this->defaults
+ ->expects($this->at(3))
+ ->method('getMailHeaderColor')
+ ->willReturn('#000');
+ $this->template = new Template(
+ $this->config,
+ $this->l10n,
+ $this->urlGenerator,
+ $this->defaults
+ );
+
+ return parent::setUp();
+ }
+
+ public function testGetNameWithDefault() {
+ $this->config
+ ->expects($this->once())
+ ->method('getAppValue')
+ ->with('theming', 'name', 'Nextcloud')
+ ->willReturn('Nextcloud');
+
+ $this->assertEquals('Nextcloud', $this->template->getName());
+ }
+
+ public function testGetNameWithCustom() {
+ $this->config
+ ->expects($this->once())
+ ->method('getAppValue')
+ ->with('theming', 'name', 'Nextcloud')
+ ->willReturn('MyCustomCloud');
+
+ $this->assertEquals('MyCustomCloud', $this->template->getName());
+ }
+
+ public function testGetEntityWithDefault() {
+ $this->config
+ ->expects($this->once())
+ ->method('getAppValue')
+ ->with('theming', 'name', 'Nextcloud')
+ ->willReturn('Nextcloud');
+
+ $this->assertEquals('Nextcloud', $this->template->getEntity());
+ }
+
+ public function testGetEntityWithCustom() {
+ $this->config
+ ->expects($this->once())
+ ->method('getAppValue')
+ ->with('theming', 'name', 'Nextcloud')
+ ->willReturn('MyCustomCloud');
+
+ $this->assertEquals('MyCustomCloud', $this->template->getEntity());
+ }
+
+ public function testGetBaseUrlWithDefault() {
+ $this->config
+ ->expects($this->once())
+ ->method('getAppValue')
+ ->with('theming', 'url', 'https://nextcloud.com/')
+ ->willReturn('https://nextcloud.com/');
+
+ $this->assertEquals('https://nextcloud.com/', $this->template->getBaseUrl());
+ }
+
+ public function testGetBaseUrlWithCustom() {
+ $this->config
+ ->expects($this->once())
+ ->method('getAppValue')
+ ->with('theming', 'url', 'https://nextcloud.com/')
+ ->willReturn('https://example.com/');
+
+ $this->assertEquals('https://example.com/', $this->template->getBaseUrl());
+ }
+
+ public function testGetSloganWithDefault() {
+ $this->config
+ ->expects($this->once())
+ ->method('getAppValue')
+ ->with('theming', 'slogan', 'Safe Data')
+ ->willReturn('Safe Data');
+
+ $this->assertEquals('Safe Data', $this->template->getSlogan());
+ }
+
+ public function testGetSloganWithCustom() {
+ $this->config
+ ->expects($this->once())
+ ->method('getAppValue')
+ ->with('theming', 'slogan', 'Safe Data')
+ ->willReturn('My custom Slogan');
+
+ $this->assertEquals('My custom Slogan', $this->template->getSlogan());
+ }
+
+ public function testGetMailHeaderColorWithDefault() {
+ $this->config
+ ->expects($this->once())
+ ->method('getAppValue')
+ ->with('theming', 'color', '#000')
+ ->willReturn('#000');
+
+ $this->assertEquals('#000', $this->template->getMailHeaderColor());
+ }
+
+ public function testGetMailHeaderColorWithCustom() {
+ $this->config
+ ->expects($this->once())
+ ->method('getAppValue')
+ ->with('theming', 'color', '#000')
+ ->willReturn('#fff');
+
+ $this->assertEquals('#fff', $this->template->getMailHeaderColor());
+ }
+
+ public function testSet() {
+ $this->config
+ ->expects($this->at(0))
+ ->method('setAppValue')
+ ->with('theming', 'MySetting', 'MyValue');
+ $this->config
+ ->expects($this->at(1))
+ ->method('getAppValue')
+ ->with('theming', 'cachebuster', '0')
+ ->willReturn('15');
+ $this->config
+ ->expects($this->at(2))
+ ->method('setAppValue')
+ ->with('theming', 'cachebuster', 16);
+
+ $this->template->set('MySetting', 'MyValue');
+ }
+
+ public function testUndoName() {
+ $this->config
+ ->expects($this->at(0))
+ ->method('deleteAppValue')
+ ->with('theming', 'name');
+ $this->config
+ ->expects($this->at(1))
+ ->method('getAppValue')
+ ->with('theming', 'cachebuster', '0')
+ ->willReturn('15');
+ $this->config
+ ->expects($this->at(2))
+ ->method('setAppValue')
+ ->with('theming', 'cachebuster', 16);
+ $this->config
+ ->expects($this->at(3))
+ ->method('getAppValue')
+ ->with('theming', 'name', 'Nextcloud')
+ ->willReturn('Nextcloud');
+
+ $this->assertSame('Nextcloud', $this->template->undo('name'));
+ }
+
+ public function testUndoBaseUrl() {
+ $this->config
+ ->expects($this->at(0))
+ ->method('deleteAppValue')
+ ->with('theming', 'url');
+ $this->config
+ ->expects($this->at(1))
+ ->method('getAppValue')
+ ->with('theming', 'cachebuster', '0')
+ ->willReturn('15');
+ $this->config
+ ->expects($this->at(2))
+ ->method('setAppValue')
+ ->with('theming', 'cachebuster', 16);
+ $this->config
+ ->expects($this->at(3))
+ ->method('getAppValue')
+ ->with('theming', 'url', 'https://nextcloud.com/')
+ ->willReturn('https://nextcloud.com/');
+
+ $this->assertSame('https://nextcloud.com/', $this->template->undo('url'));
+ }
+
+ public function testUndoSlogan() {
+ $this->config
+ ->expects($this->at(0))
+ ->method('deleteAppValue')
+ ->with('theming', 'slogan');
+ $this->config
+ ->expects($this->at(1))
+ ->method('getAppValue')
+ ->with('theming', 'cachebuster', '0')
+ ->willReturn('15');
+ $this->config
+ ->expects($this->at(2))
+ ->method('setAppValue')
+ ->with('theming', 'cachebuster', 16);
+ $this->config
+ ->expects($this->at(3))
+ ->method('getAppValue')
+ ->with('theming', 'slogan', 'Safe Data')
+ ->willReturn('Safe Data');
+
+ $this->assertSame('Safe Data', $this->template->undo('slogan'));
+ }
+
+ public function testUndoColor() {
+ $this->config
+ ->expects($this->at(0))
+ ->method('deleteAppValue')
+ ->with('theming', 'color');
+ $this->config
+ ->expects($this->at(1))
+ ->method('getAppValue')
+ ->with('theming', 'cachebuster', '0')
+ ->willReturn('15');
+ $this->config
+ ->expects($this->at(2))
+ ->method('setAppValue')
+ ->with('theming', 'cachebuster', 16);
+ $this->config
+ ->expects($this->at(3))
+ ->method('getAppValue')
+ ->with('theming', 'color', '#000')
+ ->willReturn('#000');
+
+ $this->assertSame('#000', $this->template->undo('color'));
+ }
+
+ public function testUndoDefaultAction() {
+ $this->config
+ ->expects($this->at(0))
+ ->method('deleteAppValue')
+ ->with('theming', 'defaultitem');
+ $this->config
+ ->expects($this->at(1))
+ ->method('getAppValue')
+ ->with('theming', 'cachebuster', '0')
+ ->willReturn('15');
+ $this->config
+ ->expects($this->at(2))
+ ->method('setAppValue')
+ ->with('theming', 'cachebuster', 16);
+
+ $this->assertSame('', $this->template->undo('defaultitem'));
+ }
+}
diff --git a/apps/theming/tests/lib/controller/ThemingControllerTest.php b/apps/theming/tests/lib/controller/ThemingControllerTest.php
new file mode 100644
index 00000000000..7fba27316a2
--- /dev/null
+++ b/apps/theming/tests/lib/controller/ThemingControllerTest.php
@@ -0,0 +1,428 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OCA\Theming\Tests\Controller;
+
+use OCA\Theming\Controller\ThemingController;
+use OCA\Theming\Template;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\Files\IRootFolder;
+use OCP\IConfig;
+use OCP\IL10N;
+use OCP\IRequest;
+use Test\TestCase;
+
+class ThemingControllerTest extends TestCase {
+ /** @var IRequest */
+ private $request;
+ /** @var IConfig */
+ private $config;
+ /** @var Template */
+ private $template;
+ /** @var IL10N */
+ private $l10n;
+ /** @var ThemingController */
+ private $themingController;
+ /** @var IRootFolder */
+ private $rootFolder;
+
+ public function setUp() {
+ $this->request = $this->getMock('\\OCP\\IRequest');
+ $this->config = $this->getMock('\\OCP\\IConfig');
+ $this->template = $this->getMockBuilder('\\OCA\\Theming\\Template')
+ ->disableOriginalConstructor()->getMock();
+ $this->l10n = $this->getMock('\\OCP\\IL10N');
+ $this->rootFolder = $this->getMock('\\OCP\\Files\\IRootFolder');
+
+ $this->themingController = new ThemingController(
+ 'theming',
+ $this->request,
+ $this->config,
+ $this->template,
+ $this->l10n,
+ $this->rootFolder
+ );
+
+ return parent::setUp();
+ }
+
+ public function testUpdateStylesheet() {
+ $this->template
+ ->expects($this->once())
+ ->method('set')
+ ->with('MySetting', 'MyValue');
+ $this->l10n
+ ->expects($this->once())
+ ->method('t')
+ ->with('Saved')
+ ->willReturn('Saved');
+
+ $expected = new DataResponse(
+ [
+ 'data' =>
+ [
+ 'message' => 'Saved',
+ ],
+ 'status' => 'success'
+ ]
+ );
+ $this->assertEquals($expected, $this->themingController->updateStylesheet('MySetting', 'MyValue'));
+ }
+
+ public function testUpdateLogoNoData() {
+ $this->request
+ ->expects($this->at(0))
+ ->method('getUploadedFile')
+ ->with('uploadlogo')
+ ->willReturn(null);
+ $this->request
+ ->expects($this->at(1))
+ ->method('getUploadedFile')
+ ->with('upload-login-background')
+ ->willReturn(null);
+ $this->l10n
+ ->expects($this->once())
+ ->method('t')
+ ->with('No file uploaded')
+ ->willReturn('No file uploaded');
+
+ $expected = new DataResponse(
+ [
+ 'data' =>
+ [
+ 'message' => 'No file uploaded',
+ ],
+ ],
+ Http::STATUS_UNPROCESSABLE_ENTITY
+ );
+
+ $this->assertEquals($expected, $this->themingController->updateLogo());
+ }
+
+ public function testUpdateLogoNormalLogoUpload() {
+ $tmpLogo = \OC::$server->getTempManager()->getTemporaryFolder() . '/logo.svg';
+ $destination = \OC::$server->getTempManager()->getTemporaryFolder();
+
+ touch($tmpLogo);
+ $this->request
+ ->expects($this->at(0))
+ ->method('getUploadedFile')
+ ->with('uploadlogo')
+ ->willReturn([
+ 'tmp_name' => $tmpLogo,
+ 'type' => 'text/svg',
+ 'name' => 'logo.svg',
+ ]);
+ $this->request
+ ->expects($this->at(1))
+ ->method('getUploadedFile')
+ ->with('upload-login-background')
+ ->willReturn(null);
+ $this->l10n
+ ->expects($this->once())
+ ->method('t')
+ ->with('Saved')
+ ->willReturn('Saved');
+ $file = $this->getMockBuilder('\\OCP\\Files\\File')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->rootFolder
+ ->expects($this->once())
+ ->method('newFile')
+ ->with('themedinstancelogo')
+ ->willReturn($file);
+ $file
+ ->expects($this->once())
+ ->method('fopen')
+ ->with('w')
+ ->willReturn(fopen($destination . '/themedinstancelogo', 'w'));
+
+ $expected = new DataResponse(
+ [
+ 'data' =>
+ [
+ 'name' => 'logo.svg',
+ 'message' => 'Saved',
+ ],
+ 'status' => 'success'
+ ]
+ );
+
+ $this->assertEquals($expected, $this->themingController->updateLogo());
+ }
+
+ public function testUpdateLogoLoginScreenUpload() {
+ $tmpLogo = \OC::$server->getTempManager()->getTemporaryFolder() . '/logo.svg';
+ $destination = \OC::$server->getTempManager()->getTemporaryFolder();
+
+ touch($tmpLogo);
+ $this->request
+ ->expects($this->at(0))
+ ->method('getUploadedFile')
+ ->with('uploadlogo')
+ ->willReturn(null);
+ $this->request
+ ->expects($this->at(1))
+ ->method('getUploadedFile')
+ ->with('upload-login-background')
+ ->willReturn([
+ 'tmp_name' => $tmpLogo,
+ 'type' => 'text/svg',
+ 'name' => 'logo.svg',
+ ]);
+ $this->l10n
+ ->expects($this->once())
+ ->method('t')
+ ->with('Saved')
+ ->willReturn('Saved');
+ $file = $this->getMockBuilder('\\OCP\\Files\\File')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->rootFolder
+ ->expects($this->once())
+ ->method('newFile')
+ ->with('themedbackgroundlogo')
+ ->willReturn($file);
+ $file
+ ->expects($this->once())
+ ->method('fopen')
+ ->with('w')
+ ->willReturn(fopen($destination . '/themedbackgroundlogo', 'w'));
+
+
+ $expected = new DataResponse(
+ [
+ 'data' =>
+ [
+ 'name' => 'logo.svg',
+ 'message' => 'Saved',
+ ],
+ 'status' => 'success'
+ ]
+ );
+ $this->assertEquals($expected, $this->themingController->updateLogo());
+ }
+
+ public function testUndo() {
+ $this->l10n
+ ->expects($this->once())
+ ->method('t')
+ ->with('Saved')
+ ->willReturn('Saved');
+ $this->template
+ ->expects($this->once())
+ ->method('undo')
+ ->with('MySetting')
+ ->willReturn('MyValue');
+
+ $expected = new DataResponse(
+ [
+ 'data' =>
+ [
+ 'value' => 'MyValue',
+ 'message' => 'Saved',
+ ],
+ 'status' => 'success'
+ ]
+ );
+ $this->assertEquals($expected, $this->themingController->undo('MySetting'));
+ }
+
+ public function testGetLogoNotExistent() {
+ $expected = new DataResponse();
+ $this->assertEquals($expected, $this->themingController->getLogo());
+ }
+
+ public function testGetLogo() {
+ $dataFolder = \OC::$server->getTempManager()->getTemporaryFolder();
+ $tmpLogo = $dataFolder . '/themedinstancelogo';
+ touch($tmpLogo);
+ $this->config
+ ->expects($this->once())
+ ->method('getSystemValue')
+ ->with('datadirectory', \OC::$SERVERROOT . '/data/')
+ ->willReturn($dataFolder);
+ $this->config
+ ->expects($this->once())
+ ->method('getAppValue')
+ ->with('theming', 'logoMime', '')
+ ->willReturn('text/svg');
+
+ @$expected = new Http\StreamResponse($tmpLogo);
+ $expected->cacheFor(3600);
+ $expected->addHeader('Content-Disposition', 'attachment');
+ $expected->addHeader('Content-Type', 'text/svg');
+ @$this->assertEquals($expected, $this->themingController->getLogo());
+ }
+
+
+ public function testGetLoginBackgroundNotExistent() {
+ $expected = new DataResponse();
+ $this->assertEquals($expected, $this->themingController->getLoginBackground());
+ }
+
+ public function testGetLoginBackground() {
+ $dataFolder = \OC::$server->getTempManager()->getTemporaryFolder();
+ $tmpLogo = $dataFolder . '/themedbackgroundlogo';
+ touch($tmpLogo);
+ $this->config
+ ->expects($this->once())
+ ->method('getSystemValue')
+ ->with('datadirectory', \OC::$SERVERROOT . '/data/')
+ ->willReturn($dataFolder);
+ $this->config
+ ->expects($this->once())
+ ->method('getAppValue')
+ ->with('theming', 'backgroundMime', '')
+ ->willReturn('image/png');
+
+ @$expected = new Http\StreamResponse($tmpLogo);
+ $expected->cacheFor(3600);
+ $expected->addHeader('Content-Disposition', 'attachment');
+ $expected->addHeader('Content-Type', 'image/png');
+ @$this->assertEquals($expected, $this->themingController->getLoginBackground());
+ }
+
+ public function testGetStylesheetWithOnlyColor() {
+ $this->config
+ ->expects($this->at(0))
+ ->method('getAppValue')
+ ->with('theming', 'cachebuster', '0')
+ ->willReturn('0');
+ $this->config
+ ->expects($this->at(1))
+ ->method('getAppValue')
+ ->with('theming', 'color', '')
+ ->willReturn('#fff');
+ $this->config
+ ->expects($this->at(2))
+ ->method('getAppValue')
+ ->with('theming', 'logoMime', '')
+ ->willReturn('');
+ $this->config
+ ->expects($this->at(3))
+ ->method('getAppValue')
+ ->with('theming', 'backgroundMime', '')
+ ->willReturn('');
+
+ $expected = new Http\DataDownloadResponse('#body-user #header,#body-settings #header,#body-public #header {background-color: #fff}', 'style.css', 'text/css');
+ $expected->cacheFor(3600);
+ @$this->assertEquals($expected, $this->themingController->getStylesheet());
+ }
+
+ public function testGetStylesheetWithOnlyHeaderLogo() {
+ $this->config
+ ->expects($this->at(0))
+ ->method('getAppValue')
+ ->with('theming', 'cachebuster', '0')
+ ->willReturn('0');
+ $this->config
+ ->expects($this->at(1))
+ ->method('getAppValue')
+ ->with('theming', 'color', '')
+ ->willReturn('');
+ $this->config
+ ->expects($this->at(2))
+ ->method('getAppValue')
+ ->with('theming', 'logoMime', '')
+ ->willReturn('image/png');
+ $this->config
+ ->expects($this->at(3))
+ ->method('getAppValue')
+ ->with('theming', 'backgroundMime', '')
+ ->willReturn('');
+
+ $expected = new Http\DataDownloadResponse('#header .logo {
+ background-image: url(\'./logo?v=0\');
+ }
+ #header .logo-icon {
+ background-image: url(\'./logo?v=0\');
+ background-size: 62px 34px;
+ }', 'style.css', 'text/css');
+ $expected->cacheFor(3600);
+ @$this->assertEquals($expected, $this->themingController->getStylesheet());
+ }
+
+ public function testGetStylesheetWithOnlyBackgroundLogin() {
+ $this->config
+ ->expects($this->at(0))
+ ->method('getAppValue')
+ ->with('theming', 'cachebuster', '0')
+ ->willReturn('0');
+ $this->config
+ ->expects($this->at(1))
+ ->method('getAppValue')
+ ->with('theming', 'color', '')
+ ->willReturn('');
+ $this->config
+ ->expects($this->at(2))
+ ->method('getAppValue')
+ ->with('theming', 'logoMime', '')
+ ->willReturn('');
+ $this->config
+ ->expects($this->at(3))
+ ->method('getAppValue')
+ ->with('theming', 'backgroundMime', '')
+ ->willReturn('text/svg');
+
+ $expected = new Http\DataDownloadResponse('#body-login {
+ background-image: url(\'./loginbackground?v=0\');
+ }', 'style.css', 'text/css');
+ $expected->cacheFor(3600);
+ @$this->assertEquals($expected, $this->themingController->getStylesheet());
+ }
+
+ public function testGetStylesheetWithAllCombined() {
+ $this->config
+ ->expects($this->at(0))
+ ->method('getAppValue')
+ ->with('theming', 'cachebuster', '0')
+ ->willReturn('0');
+ $this->config
+ ->expects($this->at(1))
+ ->method('getAppValue')
+ ->with('theming', 'color', '')
+ ->willReturn('#abc');
+ $this->config
+ ->expects($this->at(2))
+ ->method('getAppValue')
+ ->with('theming', 'logoMime', '')
+ ->willReturn('text/svg');
+ $this->config
+ ->expects($this->at(3))
+ ->method('getAppValue')
+ ->with('theming', 'backgroundMime', '')
+ ->willReturn('image/png');
+
+ $expected = new Http\DataDownloadResponse('#body-user #header,#body-settings #header,#body-public #header {background-color: #abc}#header .logo {
+ background-image: url(\'./logo?v=0\');
+ }
+ #header .logo-icon {
+ background-image: url(\'./logo?v=0\');
+ background-size: 62px 34px;
+ }#body-login {
+ background-image: url(\'./loginbackground?v=0\');
+ }', 'style.css', 'text/css');
+ $expected->cacheFor(3600);
+ @$this->assertEquals($expected, $this->themingController->getStylesheet());
+ }
+
+}
diff --git a/apps/updatenotification/l10n/bg_BG.js b/apps/updatenotification/l10n/bg_BG.js
index bfe17bf4533..00e2fbf90d9 100644
--- a/apps/updatenotification/l10n/bg_BG.js
+++ b/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);");
diff --git a/apps/updatenotification/l10n/bg_BG.json b/apps/updatenotification/l10n/bg_BG.json
index 7d4b4a5940b..f4b312190d6 100644
--- a/apps/updatenotification/l10n/bg_BG.json
+++ b/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);"
} \ No newline at end of file
diff --git a/apps/updatenotification/l10n/lb.js b/apps/updatenotification/l10n/lb.js
new file mode 100644
index 00000000000..e877f169fc4
--- /dev/null
+++ b/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);");
diff --git a/apps/updatenotification/l10n/lb.json b/apps/updatenotification/l10n/lb.json
new file mode 100644
index 00000000000..a43883f6c3e
--- /dev/null
+++ b/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);"
+} \ No newline at end of file
diff --git a/apps/user_ldap/l10n/ast.js b/apps/user_ldap/l10n/ast.js
index 9f7b3e66934..d7c76703db1 100644
--- a/apps/user_ldap/l10n/ast.js
+++ b/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",
diff --git a/apps/user_ldap/l10n/ast.json b/apps/user_ldap/l10n/ast.json
index 7b097287bb7..c1fbc128506 100644
--- a/apps/user_ldap/l10n/ast.json
+++ b/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",
diff --git a/apps/user_ldap/l10n/lb.js b/apps/user_ldap/l10n/lb.js
index b340887548e..f62d2924488 100644
--- a/apps/user_ldap/l10n/lb.js
+++ b/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);");
diff --git a/apps/user_ldap/l10n/lb.json b/apps/user_ldap/l10n/lb.json
index 4b4d46427b8..e869a5821b1 100644
--- a/apps/user_ldap/l10n/lb.json
+++ b/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);"
} \ No newline at end of file
diff --git a/apps/user_ldap/lib/Access.php b/apps/user_ldap/lib/Access.php
index daeb7d942a4..4d0753696ff 100644
--- a/apps/user_ldap/lib/Access.php
+++ b/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']
+ );
+ }
}
}
diff --git a/apps/user_ldap/settings.php b/apps/user_ldap/settings.php
index fb4d983517e..5c69b2ed961 100644
--- a/apps/user_ldap/settings.php
+++ b/apps/user_ldap/settings.php
@@ -27,8 +27,6 @@
*
*/
-OC_Util::checkAdminUser();
-
// fill template
$tmpl = new OCP\Template('user_ldap', 'settings');
diff --git a/autotest.sh b/autotest.sh
index b56394dc9df..5f5633fcf0f 100755
--- a/autotest.sh
+++ b/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
@@ -225,8 +225,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
diff --git a/build/integration/features/provisioning-v1.feature b/build/integration/features/provisioning-v1.feature
index 1e0df08a631..135c67dc3a6 100644
--- a/build/integration/features/provisioning-v1.feature
+++ b/build/integration/features/provisioning-v1.feature
@@ -293,6 +293,7 @@ Feature: provisioning
| files_versions |
| provisioning_api |
| systemtags |
+ | theming |
| updatenotification |
Scenario: get app info
diff --git a/build/integration/features/sharing-v1.feature b/build/integration/features/sharing-v1.feature
index 79dc1326f3c..3878e741f60 100644
--- a/build/integration/features/sharing-v1.feature
+++ b/build/integration/features/sharing-v1.feature
@@ -61,7 +61,7 @@ Feature: sharing
And the HTTP status code should be "200"
And Share fields of last share match with
| id | A_NUMBER |
- | permissions | 7 |
+ | permissions | 15 |
| expiration | +3 days |
| url | AN_URL |
| token | A_TOKEN |
@@ -159,7 +159,7 @@ Feature: sharing
| share_type | 3 |
| file_source | A_NUMBER |
| file_target | /FOLDER |
- | permissions | 7 |
+ | permissions | 15 |
| stime | A_NUMBER |
| token | A_TOKEN |
| storage | A_NUMBER |
@@ -189,7 +189,7 @@ Feature: sharing
| share_type | 3 |
| file_source | A_NUMBER |
| file_target | /FOLDER |
- | permissions | 7 |
+ | permissions | 15 |
| stime | A_NUMBER |
| token | A_TOKEN |
| storage | A_NUMBER |
diff --git a/core/Application.php b/core/Application.php
index a87917b626a..8ea2672e54e 100644
--- a/core/Application.php
+++ b/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
diff --git a/core/Command/Encryption/DecryptAll.php b/core/Command/Encryption/DecryptAll.php
index 83c6c1dc168..cda7853b8b4 100644
--- a/core/Command/Encryption/DecryptAll.php
+++ b/core/Command/Encryption/DecryptAll.php
@@ -150,6 +150,9 @@ class DecryptAll extends Command {
$output->writeln(' aborted.');
$output->writeln('Server side encryption remains enabled');
$this->config->setAppValue('core', 'encryption_enabled', 'yes');
+ } else if ($uid !== '') {
+ $output->writeln('Server side encryption remains enabled');
+ $this->config->setAppValue('core', 'encryption_enabled', 'yes');
}
$this->resetSingleUserAndTrashbin();
} else {
diff --git a/core/Controller/OccController.php b/core/Controller/OccController.php
new file mode 100644
index 00000000000..917d02f37f1
--- /dev/null
+++ b/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'
+ );
+ }
+ }
+}
diff --git a/core/css/apps.css b/core/css/apps.css
index e8b33ecba65..3ffa7d87098 100644
--- a/core/css/apps.css
+++ b/core/css/apps.css
@@ -162,7 +162,6 @@
text-overflow: ellipsis;
overflow: hidden;
display: inline-block;
- width: 201px; /* fallback for IE8 */
width: calc(100% - 49px);
line-height: 44px;
float: left;
@@ -309,12 +308,6 @@
.edge #app-navigation .app-navigation-entry-menu:after {
border: 1px solid #eee;
}
-.ie8 .bubble {
- margin-top: 18px;
-}
-.ie8 .bubble:after {
- display: none;
-}
/* miraculous border arrow stuff */
.bubble:after,
#app-navigation .app-navigation-entry-menu:after {
@@ -392,7 +385,6 @@
#app-navigation .app-navigation-entry-edit input {
border-bottom-right-radius: 0;
border-top-right-radius: 0;
- width: 204px; /* fallback for IE8 */
width: calc(100% - 36px);
padding: 5px;
margin-right: 0;
diff --git a/core/css/fixes.css b/core/css/fixes.css
index b660cd47aa8..3cb89c6599f 100644
--- a/core/css/fixes.css
+++ b/core/css/fixes.css
@@ -10,125 +10,9 @@ select {
height: 32px;
}
-/* reset typeface for IE8 because OpenSans renders too small */
-.ie8 body {
- font-family: Frutiger, Calibri, 'Myriad Pro', Myriad, Arial, sans-serif;
-}
-
-.lte8 .icon-delete { background-image: url('../img/actions/delete.png'); }
-.lte8 .icon-delete:hover, .icon-delete:focus {
- background-image: url('../img/actions/delete-hover.png');
-}
-
-.ie8 .icon-checkmark {
- background-image: url('../img/actions/checkmark.png');
-}
-
-.ie8 .icon-close {
- background-image: url('../img/actions/close.png');
-}
-
-.lte9 .icon-triangle-e {
- background-image: url('../img/actions/triangle-e.png');
-}
-.lte9 .icon-triangle-n {
- background-image: url('../img/actions/triangle-n.png');
-}
-.lte9 .icon-triangle-s {
- background-image: url('../img/actions/triangle-s.png');
-}
-.lte9 .icon-settings,
-.lte9 .settings-button {
- background-image: url('../img/actions/settings.png');
-}
-
-.lte9 input[type="submit"], .lte9 input[type="button"],
-.lte9 button, .lte9 .button,
-.lte9 #quota, .lte9 select, .lte9 .pager li a {
- background-color: #f1f1f1;
-}
-
-/* IE8 needs PNG image for header logo */
-.ie8 #header .logo {
- background-image: url(../img/logo-icon-175px.png);
-}
-
-/* IE8 needs background to be set to same color to make transparency look good. */
-.lte9 #body-login form input[type="text"] {
- border: 1px solid lightgrey; /* use border to add 1px line between input fields */
- background-color: white; /* don't change background on hover */
-}
-.lte9 #body-login form input[type="password"] {
- /* leave out top border for 1px line between input fields*/
- border-left: 1px solid lightgrey;
- border-right: 1px solid lightgrey;
- border-bottom: 1px solid lightgrey;
- background-color: white; /* don't change background on hover */
-}
-.ie8 #body-login input[type="submit"] {
- padding: 10px 5px;
- margin-top: 3px;
-}
-/* for whatever unexplained reason */
-.ie8 #password {
- width: 271px !important;
- min-width: auto !important;
-}
-
-/* disable opacity of info text on gradient
- since we cannot set a good backround color to use the filter&background hack as with the input labels */
-.lte9 #body-login p.info {
- filter: initial;
-}
-
/* deactivate show password toggle for IE. Does not work for 8 and 9+ have their own implementation. */
.ie #show, .ie #show+label {
display: none;
visibility: hidden;
}
-/* fix installation screen rendering issue for IE8+9 */
-.lte9 #body-login {
- min-height: 100%;
- height: auto !important;
-}
-
-/* oc-dialog only uses box shadow which is not supported by ie8 */
-.ie8 .oc-dialog {
- border: 1px solid #888888;
-}
-
-/* IE8 doesn't support transparent background - let's emulate black with an opacity of .3 on a dark blue background*/
-.ie8 fieldset .warning, .ie8 #body-login .error {
- background-color: #1B314D;
-}
-
-/* IE8 isn't able to display transparent background. So it is specified using a gradient */
-.ie8 #nojavascript {
- filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='#4c320000', endColorstr='#4c320000');
-}
-
-/* fix background of navigation popup in IE8 */
-.ie8 #navigation,
-.ie8 #expanddiv {
- background-color: #111;
-}
-
-/* needed else IE8 will randomly hide the borders... */
-.ie8 table th#headerDate, table td.date,
-.ie8 table th.column-last, table td.column-last {
- position: static;
-}
-
-.ie8 #controls {
- background-color: white;
-}
-
-.ie8 #content-wrapper {
- overflow-y: auto;
-}
-
-.ie8 #app-navigation .app-navigation-entry-edit input {
- line-height: 38px;
-}
-
diff --git a/core/css/multiselect.css b/core/css/multiselect.css
index a4b03319156..ef56044fd05 100644
--- a/core/css/multiselect.css
+++ b/core/css/multiselect.css
@@ -111,10 +111,3 @@ ul.multiselectoptions > li.creator > input {
padding: 5px;
margin: -5px;
}
-
-.ie8 div.multiselect span:first-child {
- display:block;
- position:relative;
- width: 90%;
- margin-right:-1px;
-}
diff --git a/core/css/styles.css b/core/css/styles.css
index d46e9700e87..c1bd76acbcf 100644
--- a/core/css/styles.css
+++ b/core/css/styles.css
@@ -189,7 +189,6 @@ a.two-factor-cancel {
.emptycontent {
color: #888;
text-align: center;
- margin-top: 100px; /* ie8 */
margin-top: 30vh;
width: 100%;
}
@@ -418,14 +417,7 @@ label.infield {
-ms-user-select: none;
user-select: none;
}
-html.ie8 #body-login form input[type="checkbox"]+label {
- margin-left: -28px;
- margin-top: -3px;
- vertical-align: auto;
-}
-html.ie8 #body-login form input[type="checkbox"] {
- margin-top: 5px;
-}
+
#body-login form .errors { background:#fed7d7; border:1px solid #f00; list-style-indent:inside; margin:0 0 2em; padding:1em; }
#body-login .success { background:#d7fed7; border:1px solid #0f0; width: 35%; margin: 30px auto; padding:1em; text-align: center;}
diff --git a/core/js/config.php b/core/js/config.php
index dc84d1cf2bf..197047ed8b8 100644
--- a/core/js/config.php
+++ b/core/js/config.php
@@ -43,7 +43,7 @@ header("Expires: Sat, 26 Jul 1997 05:00:00 GMT");
$l = \OC::$server->getL10N('core');
// Enable OC_Defaults support
-$defaults = new OC_Defaults();
+$defaults = \OC::$server->getThemingDefaults();
// Get the config
$apps_paths = array();
diff --git a/core/js/files/iedavclient.js b/core/js/files/iedavclient.js
index 9e83f5b9a22..a0185fb3bec 100644
--- a/core/js/files/iedavclient.js
+++ b/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);
diff --git a/core/js/js.js b/core/js/js.js
index 1c49d38f950..7f98668dcb2 100644
--- a/core/js/js.js
+++ b/core/js/js.js
@@ -788,7 +788,6 @@ var OC={
$(document).trigger(new $.Event('ajaxError'), xhr);
};
- // FIXME: also needs an IE8 way
if (xhr.addEventListener) {
xhr.addEventListener('load', loadCallback);
xhr.addEventListener('error', errorCallback);
@@ -1857,30 +1856,10 @@ OC.Util = {
* This scales the image to the element's actual size, the URL is
* taken from the "background-image" CSS attribute.
*
+ * @deprecated IE8 isn't supported since 9.0
* @param {Object} $el image element
*/
- scaleFixForIE8: function($el) {
- if (!this.isIE8()) {
- return;
- }
- var self = this;
- $($el).each(function() {
- var url = $(this).css('background-image');
- var r = url.match(/url\(['"]?([^'")]*)['"]?\)/);
- if (!r) {
- return;
- }
- url = r[1];
- url = self.replaceSVGIcon(url);
- // TODO: escape
- url = url.replace(/'/g, '%27');
- $(this).css({
- 'filter': 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + url + '\', sizingMethod=\'scale\')',
- 'background-image': ''
- });
- });
- return $el;
- },
+ scaleFixForIE8: function($el) {},
/**
* Returns whether this is IE
@@ -1894,10 +1873,11 @@ OC.Util = {
/**
* Returns whether this is IE8
*
- * @return {bool} true if this is IE8, false otherwise
+ * @deprecated IE8 isn't supported since 9.0
+ * @return {bool} false (IE8 isn't supported anymore)
*/
isIE8: function() {
- return $('html').hasClass('ie8');
+ return false;
},
/**
diff --git a/core/js/setupchecks.js b/core/js/setupchecks.js
index f987c9f04e6..280c8d08c99 100644
--- a/core/js/setupchecks.js
+++ b/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();
},
diff --git a/core/js/sharedialoglinkshareview.js b/core/js/sharedialoglinkshareview.js
index 512da81da08..ff83b40e0a0 100644
--- a/core/js/sharedialoglinkshareview.js
+++ b/core/js/sharedialoglinkshareview.js
@@ -215,7 +215,7 @@
var permissions = OC.PERMISSION_READ;
if($checkbox.is(':checked')) {
- permissions = OC.PERMISSION_UPDATE | OC.PERMISSION_CREATE | OC.PERMISSION_READ;
+ permissions = OC.PERMISSION_UPDATE | OC.PERMISSION_CREATE | OC.PERMISSION_READ | OC.PERMISSION_DELETE;
}
this.model.saveLinkShare({
diff --git a/core/l10n/lb.js b/core/l10n/lb.js
index 92d10ed3661..dfd8e7a0f62 100644
--- a/core/l10n/lb.js
+++ b/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",
diff --git a/core/l10n/lb.json b/core/l10n/lb.json
index 382da7f58d2..e1cfc10d33c 100644
--- a/core/l10n/lb.json
+++ b/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",
diff --git a/core/l10n/pl.js b/core/l10n/pl.js
index 787904ed794..50a9b458cf1 100644
--- a/core/l10n/pl.js
+++ b/core/l10n/pl.js
@@ -166,9 +166,13 @@ OC.L10N.register(
"delete" : "usuń",
"access control" : "kontrola dostępu",
"Could not unshare" : "Nie udało się usunąć udostępnienia",
+ "Share details could not be loaded for this item." : "Szczegóły udziału nie mogły zostać wczytane dla tego obiektu.",
"No users or groups found for {search}" : "Nie znaleziono użytkowników lub grup dla {search}",
"No users found for {search}" : "Nie znaleziono użytkowników dla {search}",
"An error occurred. Please try again" : "Wystąpił błąd. Proszę spróbować ponownie.",
+ "{sharee} (group)" : "{sharee} (grupa)",
+ "{sharee} (at {server})" : "{sharee} (na {server})",
+ "{sharee} (remote)" : "{sharee} (zdalny)",
"Share" : "Udostępnij",
"Share with people on other ownClouds using the syntax username@example.com/owncloud" : "Współdziel z użytkownikami innych chmur ownCloud używając wzorca uzytkownik@example.com/owncloud",
"Share with users…" : "Współdziel z użytkownikami...",
@@ -181,6 +185,7 @@ OC.L10N.register(
"Non-existing tag #{tag}" : "Znacznik #{tag} nie istnieje",
"restricted" : "ograniczone",
"invisible" : "niewidoczny",
+ "({scope})" : "({scope})",
"Delete" : "Usuń",
"Rename" : "Zmień nazwę",
"The object type is not specified." : "Nie określono typu obiektu.",
@@ -237,6 +242,7 @@ OC.L10N.register(
"Data folder" : "Katalog danych",
"Configure the database" : "Skonfiguruj bazę danych",
"Only %s is available." : "Dostępne jest wyłącznie %s.",
+ "Install and activate additional PHP modules to choose other database types." : "Zainstaluj lub aktywuj dodatkowe moduły PHP, aby uzyskać możliwość wyboru innych typów baz danych.",
"For more details check out the documentation." : "Aby uzyskać więcej informacji zapoznaj się z dokumentacją.",
"Database user" : "Użytkownik bazy danych",
"Database password" : "Hasło do bazy danych",
@@ -289,6 +295,7 @@ OC.L10N.register(
"To avoid timeouts with larger installations, you can instead run the following command from your installation directory:" : "Aby uniknąć timeout-ów przy większych instalacjach, możesz zamiast tego uruchomić następującą komendę w katalogu Twojej instalacji:",
"Detailed logs" : "Szczegółowe logi",
"Update needed" : "Wymagana aktualizacja",
+ "Please use the command line updater because you have a big instance." : "Ze względu na rozmiar Twojej instalacji użyj programu do aktualizacji z linii poleceń.",
"For help, see the <a target=\"_blank\" rel=\"noreferrer\" href=\"%s\">documentation</a>." : "Aby uzyskać pomoc, zajrzyj do <a target=\"_blank\" rel=\"noreferrer\" href=\"%s\">dokumentacji</a>.",
"This page will refresh itself when the %s instance is available again." : "Strona odświeży się gdy instancja %s będzie ponownie dostępna."
},
diff --git a/core/l10n/pl.json b/core/l10n/pl.json
index aed5fd35c6d..96391c796d9 100644
--- a/core/l10n/pl.json
+++ b/core/l10n/pl.json
@@ -164,9 +164,13 @@
"delete" : "usuń",
"access control" : "kontrola dostępu",
"Could not unshare" : "Nie udało się usunąć udostępnienia",
+ "Share details could not be loaded for this item." : "Szczegóły udziału nie mogły zostać wczytane dla tego obiektu.",
"No users or groups found for {search}" : "Nie znaleziono użytkowników lub grup dla {search}",
"No users found for {search}" : "Nie znaleziono użytkowników dla {search}",
"An error occurred. Please try again" : "Wystąpił błąd. Proszę spróbować ponownie.",
+ "{sharee} (group)" : "{sharee} (grupa)",
+ "{sharee} (at {server})" : "{sharee} (na {server})",
+ "{sharee} (remote)" : "{sharee} (zdalny)",
"Share" : "Udostępnij",
"Share with people on other ownClouds using the syntax username@example.com/owncloud" : "Współdziel z użytkownikami innych chmur ownCloud używając wzorca uzytkownik@example.com/owncloud",
"Share with users…" : "Współdziel z użytkownikami...",
@@ -179,6 +183,7 @@
"Non-existing tag #{tag}" : "Znacznik #{tag} nie istnieje",
"restricted" : "ograniczone",
"invisible" : "niewidoczny",
+ "({scope})" : "({scope})",
"Delete" : "Usuń",
"Rename" : "Zmień nazwę",
"The object type is not specified." : "Nie określono typu obiektu.",
@@ -235,6 +240,7 @@
"Data folder" : "Katalog danych",
"Configure the database" : "Skonfiguruj bazę danych",
"Only %s is available." : "Dostępne jest wyłącznie %s.",
+ "Install and activate additional PHP modules to choose other database types." : "Zainstaluj lub aktywuj dodatkowe moduły PHP, aby uzyskać możliwość wyboru innych typów baz danych.",
"For more details check out the documentation." : "Aby uzyskać więcej informacji zapoznaj się z dokumentacją.",
"Database user" : "Użytkownik bazy danych",
"Database password" : "Hasło do bazy danych",
@@ -287,6 +293,7 @@
"To avoid timeouts with larger installations, you can instead run the following command from your installation directory:" : "Aby uniknąć timeout-ów przy większych instalacjach, możesz zamiast tego uruchomić następującą komendę w katalogu Twojej instalacji:",
"Detailed logs" : "Szczegółowe logi",
"Update needed" : "Wymagana aktualizacja",
+ "Please use the command line updater because you have a big instance." : "Ze względu na rozmiar Twojej instalacji użyj programu do aktualizacji z linii poleceń.",
"For help, see the <a target=\"_blank\" rel=\"noreferrer\" href=\"%s\">documentation</a>." : "Aby uzyskać pomoc, zajrzyj do <a target=\"_blank\" rel=\"noreferrer\" href=\"%s\">dokumentacji</a>.",
"This page will refresh itself when the %s instance is available again." : "Strona odświeży się gdy instancja %s będzie ponownie dostępna."
},"pluralForm" :"nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"
diff --git a/core/l10n/ro.js b/core/l10n/ro.js
index e73ec5cf79f..fad21a73e58 100644
--- a/core/l10n/ro.js
+++ b/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));");
diff --git a/core/l10n/ro.json b/core/l10n/ro.json
index c154312dab6..b922cf154a6 100644
--- a/core/l10n/ro.json
+++ b/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));"
} \ No newline at end of file
diff --git a/core/l10n/sv.js b/core/l10n/sv.js
index ce2c918a839..abc3a916707 100644
--- a/core/l10n/sv.js
+++ b/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.",
diff --git a/core/l10n/sv.json b/core/l10n/sv.json
index 63b69ec557f..a0c32823747 100644
--- a/core/l10n/sv.json
+++ b/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.",
diff --git a/core/routes.php b/core/routes.php
index 402277d8f3e..c473408e2e9 100644
--- a/core/routes.php
+++ b/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'],
diff --git a/core/shipped.json b/core/shipped.json
index a3abe22d8d5..ed9cf52fa72 100644
--- a/core/shipped.json
+++ b/core/shipped.json
@@ -32,6 +32,7 @@
"systemtags",
"systemtags_management",
"templateeditor",
+ "theming",
"updatenotification",
"user_external",
"user_ldap",
diff --git a/core/templates/layout.base.php b/core/templates/layout.base.php
index 29c2ca6696d..7301ae690cc 100644
--- a/core/templates/layout.base.php
+++ b/core/templates/layout.base.php
@@ -1,7 +1,5 @@
<!DOCTYPE html>
-<!--[if lte IE 8]><html class="ng-csp ie ie8 lte9 lte8" data-placeholder-focus="false" lang="<?php p($_['language']); ?>" ><![endif]-->
-<!--[if IE 9]><html class="ng-csp ie ie9 lte9" data-placeholder-focus="false" lang="<?php p($_['language']); ?>" ><![endif]-->
-<!--[if (gt IE 9)|!(IE)]><!--><html class="ng-csp" data-placeholder-focus="false" lang="<?php p($_['language']); ?>" ><!--<![endif]-->
+<html class="ng-csp" data-placeholder-focus="false" lang="<?php p($_['language']); ?>" >
<head data-requesttoken="<?php p($_['requesttoken']); ?>">
<meta charset="utf-8">
<title>
diff --git a/core/templates/layout.guest.php b/core/templates/layout.guest.php
index 3f9c47f9aa6..985e95294ad 100644
--- a/core/templates/layout.guest.php
+++ b/core/templates/layout.guest.php
@@ -1,7 +1,5 @@
<!DOCTYPE html>
-<!--[if lte IE 8]><html class="ng-csp ie ie8 lte9 lte8" data-placeholder-focus="false" lang="<?php p($_['language']); ?>" ><![endif]-->
-<!--[if IE 9]><html class="ng-csp ie ie9 lte9" data-placeholder-focus="false" lang="<?php p($_['language']); ?>" ><![endif]-->
-<!--[if (gt IE 9)|!(IE)]><!--><html class="ng-csp" data-placeholder-focus="false" lang="<?php p($_['language']); ?>" ><!--<![endif]-->
+<html class="ng-csp" data-placeholder-focus="false" lang="<?php p($_['language']); ?>" >
<head data-requesttoken="<?php p($_['requesttoken']); ?>">
<meta charset="utf-8">
<title>
diff --git a/core/templates/layout.user.php b/core/templates/layout.user.php
index d762bd9897a..d42a392f40f 100644
--- a/core/templates/layout.user.php
+++ b/core/templates/layout.user.php
@@ -1,7 +1,5 @@
<!DOCTYPE html>
-<!--[if lte IE 8]><html class="ng-csp ie ie8 lte9 lte8" data-placeholder-focus="false" lang="<?php p($_['language']); ?>" ><![endif]-->
-<!--[if IE 9]><html class="ng-csp ie ie9 lte9" data-placeholder-focus="false" lang="<?php p($_['language']); ?>" ><![endif]-->
-<!--[if (gt IE 9)|!(IE)]><!--><html class="ng-csp" data-placeholder-focus="false" lang="<?php p($_['language']); ?>" ><!--<![endif]-->
+<html class="ng-csp" data-placeholder-focus="false" lang="<?php p($_['language']); ?>" >
<head data-user="<?php p($_['user_uid']); ?>" data-user-displayname="<?php p($_['user_displayname']); ?>" data-requesttoken="<?php p($_['requesttoken']); ?>">
<meta charset="utf-8">
<title>
diff --git a/db_structure.xml b/db_structure.xml
index b7dacc05d92..6b91c3c4c5d 100644
--- a/db_structure.xml
+++ b/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>
diff --git a/l10n/.tx/config b/l10n/.tx/config
index acb2daec6c7..78f1cae978c 100644
--- a/l10n/.tx/config
+++ b/l10n/.tx/config
@@ -2,93 +2,92 @@
host = https://www.transifex.com
lang_map = ja_JP: ja
-[owncloud.core]
+[nextcloud.core]
file_filter = <lang>/core.po
source_file = templates/core.pot
source_lang = en
type = PO
-[owncloud.files]
+[nextcloud.files]
file_filter = <lang>/files.po
source_file = templates/files.pot
source_lang = en
type = PO
-[owncloud.settings-1]
+[nextcloud.settings-1]
file_filter = <lang>/settings.po
source_file = templates/settings.pot
source_lang = en
type = PO
-[owncloud.lib]
+[nextcloud.lib]
file_filter = <lang>/lib.po
source_file = templates/lib.pot
source_lang = en
type = PO
-[owncloud.files_encryption]
+[nextcloud.files_encryption]
file_filter = <lang>/encryption.po
source_file = templates/encryption.pot
source_lang = en
type = PO
-[owncloud.files_external]
+[nextcloud.files_external]
file_filter = <lang>/files_external.po
source_file = templates/files_external.pot
source_lang = en
type = PO
-[owncloud.files_sharing]
+[nextcloud.files_sharing]
file_filter = <lang>/files_sharing.po
source_file = templates/files_sharing.pot
source_lang = en
type = PO
-[owncloud.files_trashbin]
+[nextcloud.files_trashbin]
file_filter = <lang>/files_trashbin.po
source_file = templates/files_trashbin.pot
source_lang = en
type = PO
-[owncloud.files_versions]
+[nextcloud.files_versions]
file_filter = <lang>/files_versions.po
source_file = templates/files_versions.pot
source_lang = en
type = PO
-[owncloud.user_ldap]
+[nextcloud.user_ldap]
file_filter = <lang>/user_ldap.po
source_file = templates/user_ldap.pot
source_lang = en
type = PO
-[owncloud.comments]
+[nextcloud.comments]
file_filter = <lang>/comments.po
source_file = templates/comments.pot
source_lang = en
type = PO
-[owncloud.federatedfilesharing]
+[nextcloud.federatedfilesharing]
file_filter = <lang>/federatedfilesharing.po
source_file = templates/federatedfilesharing.pot
source_lang = en
type = PO
-[owncloud.federation]
+[nextcloud.federation]
file_filter = <lang>/federation.po
source_file = templates/federation.pot
source_lang = en
type = PO
-[owncloud.systemtags]
+[nextcloud.systemtags]
file_filter = <lang>/systemtags.po
source_file = templates/systemtags.pot
source_lang = en
type = PO
-[owncloud.updatenotification]
+[nextcloud.updatenotification]
file_filter = <lang>/updatenotification.po
source_file = templates/updatenotification.pot
source_lang = en
type = PO
-
diff --git a/lib/base.php b/lib/base.php
index a2630093c94..9e5304b1634 100644
--- a/lib/base.php
+++ b/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();
}
diff --git a/lib/l10n/ast.js b/lib/l10n/ast.js
index d134495dc88..548ca59e8e8 100644
--- a/lib/l10n/ast.js
+++ b/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);");
diff --git a/lib/l10n/ast.json b/lib/l10n/ast.json
index be1adf37875..48bd836a8e6 100644
--- a/lib/l10n/ast.json
+++ b/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);"
} \ No newline at end of file
diff --git a/lib/private/Authentication/Token/DefaultToken.php b/lib/private/Authentication/Token/DefaultToken.php
index 299291e34af..79b03eed27f 100644
--- a/lib/private/Authentication/Token/DefaultToken.php
+++ b/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);
+ }
+
}
diff --git a/lib/private/Authentication/Token/DefaultTokenMapper.php b/lib/private/Authentication/Token/DefaultTokenMapper.php
index 9450ed6b9f3..2e105dd4a5d 100644
--- a/lib/private/Authentication/Token/DefaultTokenMapper.php
+++ b/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);
diff --git a/lib/private/Authentication/Token/DefaultTokenProvider.php b/lib/private/Authentication/Token/DefaultTokenProvider.php
index 84effc5f875..b9d06829572 100644
--- a/lib/private/Authentication/Token/DefaultTokenProvider.php
+++ b/lib/private/Authentication/Token/DefaultTokenProvider.php
@@ -92,19 +92,34 @@ class DefaultTokenProvider implements IProvider {
}
/**
+ * 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);
+ }
}
/**
@@ -151,6 +166,23 @@ class DefaultTokenProvider implements IProvider {
}
/**
+ * 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
*
* @param string $token
@@ -180,21 +212,6 @@ class DefaultTokenProvider implements IProvider {
/**
* @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
*/
private function hashToken($token) {
diff --git a/lib/private/Authentication/Token/IProvider.php b/lib/private/Authentication/Token/IProvider.php
index fece7dcb567..d4bbe158e0a 100644
--- a/lib/private/Authentication/Token/IProvider.php
+++ b/lib/private/Authentication/Token/IProvider.php
@@ -50,13 +50,6 @@ interface IProvider {
public function getToken($tokenId) ;
/**
- * @param string $token
- * @throws InvalidTokenException
- * @return IToken
- */
- public function validateToken($token);
-
- /**
* Invalidate (delete) the given session token
*
* @param string $token
@@ -72,13 +65,20 @@ 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
*
* The provider may limit the number of result rows in case of an abuse
@@ -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);
}
diff --git a/lib/private/Authentication/Token/IToken.php b/lib/private/Authentication/Token/IToken.php
index a34bdc2c43d..096550fd091 100644
--- a/lib/private/Authentication/Token/IToken.php
+++ b/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);
}
diff --git a/lib/private/Console/Application.php b/lib/private/Console/Application.php
index ec91064278e..8a9191a4c53 100644
--- a/lib/private/Console/Application.php
+++ b/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);
}
diff --git a/lib/private/Files/View.php b/lib/private/Files/View.php
index e9daa123470..31549c93cb2 100644
--- a/lib/private/Files/View.php
+++ b/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;
+ }
}
diff --git a/lib/private/IntegrityCheck/Checker.php b/lib/private/IntegrityCheck/Checker.php
index ab68f752206..57127f280c4 100644
--- a/lib/private/IntegrityCheck/Checker.php
+++ b/lib/private/IntegrityCheck/Checker.php
@@ -108,7 +108,11 @@ class Checker {
* applicable for very specific scenarios and we should not advertise it
* too prominent. So please do not add it to config.sample.php.
*/
- $isIntegrityCheckDisabled = $this->config->getSystemValue('integrity.check.disabled', false);
+ if ($this->config !== null) {
+ $isIntegrityCheckDisabled = $this->config->getSystemValue('integrity.check.disabled', false);
+ } else {
+ $isIntegrityCheckDisabled = false;
+ }
if($isIntegrityCheckDisabled === true) {
return false;
}
@@ -401,7 +405,10 @@ class Checker {
return json_decode($cachedResults, true);
}
- return json_decode($this->config->getAppValue('core', self::CACHE_KEY, '{}'), true);
+ if ($this->config !== null) {
+ return json_decode($this->config->getAppValue('core', self::CACHE_KEY, '{}'), true);
+ }
+ return [];
}
/**
@@ -416,7 +423,9 @@ class Checker {
if(!empty($result)) {
$resultArray[$scope] = $result;
}
- $this->config->setAppValue('core', self::CACHE_KEY, json_encode($resultArray));
+ if ($this->config !== null) {
+ $this->config->setAppValue('core', self::CACHE_KEY, json_encode($resultArray));
+ }
$this->cache->set(self::CACHE_KEY, json_encode($resultArray));
}
diff --git a/lib/private/Repair/RepairInvalidShares.php b/lib/private/Repair/RepairInvalidShares.php
index 30f67a1f394..728632486d0 100644
--- a/lib/private/Repair/RepairInvalidShares.php
+++ b/lib/private/Repair/RepairInvalidShares.php
@@ -72,6 +72,25 @@ class RepairInvalidShares implements IRepairStep {
}
/**
+ * In the past link shares with public upload enabled were missing the delete permission.
+ */
+ private function addShareLinkDeletePermission(IOutput $out) {
+ $oldPerms = \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE;
+ $newPerms = \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE;
+ $builder = $this->connection->getQueryBuilder();
+ $builder
+ ->update('share')
+ ->set('permissions', $builder->expr()->literal($newPerms))
+ ->where($builder->expr()->eq('share_type', $builder->expr()->literal(\OC\Share\Constants::SHARE_TYPE_LINK)))
+ ->andWhere($builder->expr()->eq('permissions', $builder->expr()->literal($oldPerms)));
+
+ $updatedEntries = $builder->execute();
+ if ($updatedEntries > 0) {
+ $out->info('Fixed link share permissions for ' . $updatedEntries . ' shares');
+ }
+ }
+
+ /**
* Remove shares where the parent share does not exist anymore
*/
private function removeSharesNonExistingParent(IOutput $out) {
@@ -113,6 +132,10 @@ class RepairInvalidShares implements IRepairStep {
// this situation was only possible before 8.2
$this->removeExpirationDateFromNonLinkShares($out);
}
+ if (version_compare($ocVersionFromBeforeUpdate, '9.1.0.9', '<')) {
+ // this situation was only possible before 9.1
+ $this->addShareLinkDeletePermission($out);
+ }
$this->removeSharesNonExistingParent($out);
}
diff --git a/lib/private/Server.php b/lib/private/Server.php
index 8345a0b66e0..c663bc44261 100644
--- a/lib/private/Server.php
+++ b/lib/private/Server.php
@@ -78,6 +78,7 @@ use OC\Security\SecureRandom;
use OC\Security\TrustedDomainHelper;
use OC\Session\CryptoWrapper;
use OC\Tagging\TagMapper;
+use OCA\Theming\Template;
use OCP\IL10N;
use OCP\IServerContainer;
use OCP\Security\IContentSecurityPolicyManager;
@@ -235,7 +236,7 @@ class Server extends ServerContainer implements IServerContainer {
} else {
$defaultTokenProvider = null;
}
-
+
$userSession = new \OC\User\Session($manager, $session, $timeFactory, $defaultTokenProvider, $c->getConfig());
$userSession->listen('\OC\User', 'preCreateUser', function ($uid, $password) {
\OC_Hook::emit('OC_User', 'pre_createUser', array('run' => true, 'uid' => $uid, 'password' => $password));
@@ -618,6 +619,17 @@ class Server extends ServerContainer implements IServerContainer {
$factory = new $factoryClass($this);
return $factory->getManager();
});
+ $this->registerService('ThemingDefaults', function(Server $c) {
+ if($this->getConfig()->getSystemValue('installed', false) && $this->getAppManager()->isInstalled('theming')) {
+ return new Template(
+ $this->getConfig(),
+ $this->getL10N('theming'),
+ $this->getURLGenerator(),
+ new \OC_Defaults()
+ );
+ }
+ return new \OC_Defaults();
+ });
$this->registerService('EventDispatcher', function () {
return new EventDispatcher();
});
@@ -674,7 +686,8 @@ class Server extends ServerContainer implements IServerContainer {
$c->getL10N('core'),
$factory,
$c->getUserManager(),
- $c->getLazyRootFolder()
+ $c->getLazyRootFolder(),
+ $c->getEventDispatcher()
);
return $manager;
@@ -1289,6 +1302,14 @@ class Server extends ServerContainer implements IServerContainer {
}
/**
+ * @internal Not public by intention.
+ * @return \OC_Defaults
+ */
+ public function getThemingDefaults() {
+ return $this->query('ThemingDefaults');
+ }
+
+ /**
* @return \OC\IntegrityCheck\Checker
*/
public function getIntegrityCodeChecker() {
diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php
index 2857a394e1e..9383255bc73 100644
--- a/lib/private/Share20/Manager.php
+++ b/lib/private/Share20/Manager.php
@@ -26,6 +26,7 @@ namespace OC\Share20;
use OC\Cache\CappedMemoryCache;
use OC\Files\Mount\MoveableMount;
+use OC\HintException;
use OCP\Files\File;
use OCP\Files\Folder;
use OCP\Files\IRootFolder;
@@ -42,6 +43,8 @@ use OCP\Share\Exceptions\GenericShareException;
use OCP\Share\Exceptions\ShareNotFound;
use OCP\Share\IManager;
use OCP\Share\IProviderFactory;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\EventDispatcher\GenericEvent;
/**
* This class is the communication hub for all sharing related operations.
@@ -70,6 +73,8 @@ class Manager implements IManager {
private $rootFolder;
/** @var CappedMemoryCache */
private $sharingDisabledForUsersCache;
+ /** @var EventDispatcher */
+ private $eventDispatcher;
/**
@@ -85,6 +90,7 @@ class Manager implements IManager {
* @param IProviderFactory $factory
* @param IUserManager $userManager
* @param IRootFolder $rootFolder
+ * @param EventDispatcher $eventDispatcher
*/
public function __construct(
ILogger $logger,
@@ -96,7 +102,8 @@ class Manager implements IManager {
IL10N $l,
IProviderFactory $factory,
IUserManager $userManager,
- IRootFolder $rootFolder
+ IRootFolder $rootFolder,
+ EventDispatcher $eventDispatcher
) {
$this->logger = $logger;
$this->config = $config;
@@ -108,6 +115,7 @@ class Manager implements IManager {
$this->factory = $factory;
$this->userManager = $userManager;
$this->rootFolder = $rootFolder;
+ $this->eventDispatcher = $eventDispatcher;
$this->sharingDisabledForUsersCache = new CappedMemoryCache();
}
@@ -138,16 +146,11 @@ class Manager implements IManager {
}
// Let others verify the password
- $accepted = true;
- $message = '';
- \OCP\Util::emitHook('\OC\Share', 'verifyPassword', [
- 'password' => $password,
- 'accepted' => &$accepted,
- 'message' => &$message
- ]);
-
- if (!$accepted) {
- throw new \Exception($message);
+ try {
+ $event = new GenericEvent($password);
+ $this->eventDispatcher->dispatch('OCP\PasswordPolicy::validate', $event);
+ } catch (HintException $e) {
+ throw new \Exception($e->getHint());
}
}
@@ -449,14 +452,9 @@ class Manager implements IManager {
throw new \InvalidArgumentException('Link shares can\'t have reshare permissions');
}
- // We don't allow deletion on link shares
- if ($share->getPermissions() & \OCP\Constants::PERMISSION_DELETE) {
- throw new \InvalidArgumentException('Link shares can\'t have delete permissions');
- }
-
// Check if public upload is allowed
if (!$this->shareApiLinkAllowPublicUpload() &&
- ($share->getPermissions() & (\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE))) {
+ ($share->getPermissions() & (\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE))) {
throw new \InvalidArgumentException('Public upload not allowed');
}
}
diff --git a/lib/private/User/Database.php b/lib/private/User/Database.php
index 1dcac287e1e..85cbddca359 100644
--- a/lib/private/User/Database.php
+++ b/lib/private/User/Database.php
@@ -51,6 +51,8 @@
namespace OC\User;
use OC\Cache\CappedMemoryCache;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\EventDispatcher\GenericEvent;
/**
* Class for user management in a SQL Database (e.g. MySQL, SQLite)
@@ -58,12 +60,14 @@ use OC\Cache\CappedMemoryCache;
class Database extends \OC\User\Backend implements \OCP\IUserBackend {
/** @var CappedMemoryCache */
private $cache;
-
+ /** @var EventDispatcher */
+ private $eventDispatcher;
/**
* OC_User_Database constructor.
*/
- public function __construct() {
+ public function __construct($eventDispatcher = null) {
$this->cache = new CappedMemoryCache();
+ $this->eventDispatcher = $eventDispatcher ? $eventDispatcher : \OC::$server->getEventDispatcher();
}
/**
@@ -115,6 +119,8 @@ class Database extends \OC\User\Backend implements \OCP\IUserBackend {
*/
public function setPassword($uid, $password) {
if ($this->userExists($uid)) {
+ $event = new GenericEvent($password);
+ $this->eventDispatcher->dispatch('OCP\PasswordPolicy::validate', $event);
$query = \OC_DB::prepare('UPDATE `*PREFIX*users` SET `password` = ? WHERE `uid` = ?');
$result = $query->execute(array(\OC::$server->getHasher()->hash($password), $uid));
diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php
index 4e9c827448d..6219a89e5b3 100644
--- a/lib/private/User/Session.php
+++ b/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();
+ }
}
/**
@@ -298,21 +280,22 @@ class Session implements IUserSession, Emitter {
*/
public function login($uid, $password) {
$this->session->regenerateId();
- if ($this->validateToken($password)) {
- $user = $this->getUser();
-
+ if ($this->validateToken($password, $uid)) {
// 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,80 @@ 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
+ * @param string $user login name
+ * @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, $user = null) {
+ try {
+ $dbToken = $this->tokenProvider->getToken($token);
+ } catch (InvalidTokenException $ex) {
+ return false;
+ }
+
+ // Check if login names match
+ if (!is_null($user) && $dbToken->getLoginName() !== $user) {
+ // TODO: this makes it imposssible to use different login names on browser and client
+ // e.g. login by e-mail 'user@example.com' on browser for generating the token will not
+ // allow to use the client token with the login name 'user'.
+ return false;
}
+
+ if (!$this->checkTokenCredentials($dbToken, $token)) {
+ return false;
+ }
+
+ $this->tokenProvider->updateTokenActivity($dbToken);
+
+ return true;
}
/**
@@ -578,15 +622,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 +726,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
+ }
+ }
+
}
diff --git a/lib/private/legacy/template.php b/lib/private/legacy/template.php
index 5023e3a60c8..e2956508090 100644
--- a/lib/private/legacy/template.php
+++ b/lib/private/legacy/template.php
@@ -80,7 +80,7 @@ class OC_Template extends \OC\Template\Base {
$parts = explode('/', $app); // fix translation when app is something like core/lostpassword
$l10n = \OC::$server->getL10N($parts[0]);
- $themeDefaults = new OC_Defaults();
+ $themeDefaults = \OC::$server->getThemingDefaults();
list($path, $template) = $this->findTemplate($theme, $app, $name);
diff --git a/lib/private/legacy/util.php b/lib/private/legacy/util.php
index 4c9419d6eda..3c56008a48b 100644
--- a/lib/private/legacy/util.php
+++ b/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();
}
diff --git a/public.php b/public.php
index 964ed03c1aa..b7125502ee8 100644
--- a/public.php
+++ b/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', '') === '') {
diff --git a/settings/ChangePassword/Controller.php b/settings/ChangePassword/Controller.php
index 5a6c985f181..94fb1e4e7a2 100644
--- a/settings/ChangePassword/Controller.php
+++ b/settings/ChangePassword/Controller.php
@@ -30,6 +30,8 @@
*/
namespace OC\Settings\ChangePassword;
+use OC\HintException;
+
class Controller {
public static function changePersonalPassword($args) {
// Check if we are an user
@@ -39,16 +41,22 @@ class Controller {
$username = \OC_User::getUser();
$password = isset($_POST['personal-password']) ? $_POST['personal-password'] : null;
$oldPassword = isset($_POST['oldpassword']) ? $_POST['oldpassword'] : '';
+ $l = new \OC_L10n('settings');
if (!\OC_User::checkPassword($username, $oldPassword)) {
- $l = new \OC_L10n('settings');
\OC_JSON::error(array("data" => array("message" => $l->t("Wrong password")) ));
exit();
}
- if (!is_null($password) && \OC_User::setPassword($username, $password)) {
- \OC_JSON::success();
- } else {
- \OC_JSON::error();
+
+ try {
+ if (!is_null($password) && \OC_User::setPassword($username, $password)) {
+ \OC::$server->getUserSession()->updateSessionTokenPassword($password);
+ \OC_JSON::success(['data' => ['message' => $l->t('Saved')]]);
+ } else {
+ \OC_JSON::error();
+ }
+ } catch (HintException $e) {
+ \OC_JSON::error(['data' => ['message' => $e->getHint()]]);
}
}
@@ -149,10 +157,14 @@ class Controller {
}
} else { // if encryption is disabled, proceed
- if (!is_null($password) && \OC_User::setPassword($username, $password)) {
- \OC_JSON::success(array('data' => array('username' => $username)));
- } else {
- \OC_JSON::error(array('data' => array('message' => $l->t('Unable to change password'))));
+ try {
+ if (!is_null($password) && \OC_User::setPassword($username, $password)) {
+ \OC_JSON::success(array('data' => array('username' => $username)));
+ } else {
+ \OC_JSON::error(array('data' => array('message' => $l->t('Unable to change password'))));
+ }
+ } catch (HintException $e) {
+ \OC_JSON::error(array('data' => array('message' => $e->getHint())));
}
}
}
diff --git a/settings/Controller/AuthSettingsController.php b/settings/Controller/AuthSettingsController.php
index fba663b034d..db2db6e5bfc 100644
--- a/settings/Controller/AuthSettingsController.php
+++ b/settings/Controller/AuthSettingsController.php
@@ -118,6 +118,7 @@ class AuthSettingsController extends Controller {
return [
'token' => $token,
+ 'loginName' => $loginName,
'deviceToken' => $deviceToken
];
}
diff --git a/settings/css/settings.css b/settings/css/settings.css
index 78d37e0b876..0af53cebf31 100644
--- a/settings/css/settings.css
+++ b/settings/css/settings.css
@@ -101,37 +101,49 @@ table.nostyle label { margin-right: 2em; }
table.nostyle td { padding: 0.2em 0; }
#sessions table,
-#devices table {
+#apppasswords table {
width: 100%;
min-height: 150px;
padding-top: 25px;
}
#sessions table th,
-#devices table th {
+#apppasswords table th {
font-weight: 800;
}
#sessions table th,
#sessions table td,
-#devices table th,
-#devices table td {
+#apppasswords table th,
+#apppasswords table td {
padding: 10px;
}
#sessions .token-list td,
-#devices .token-list td {
+#apppasswords .token-list td {
border-top: 1px solid #DDD;
+ text-overflow: ellipsis;
+ max-width: 200px;
+ white-space: nowrap;
+ overflow: hidden;
}
#sessions .token-list td a.icon-delete,
-#devices .token-list td a.icon-delete {
+#apppasswords .token-list td a.icon-delete {
display: block;
opacity: 0.6;
}
-#device-new-token {
+#new-app-login-name,
+#new-app-password {
width: 186px;
font-family: monospace;
background-color: lightyellow;
}
+.app-password-row {
+ display: table-row;
+}
+.app-password-label {
+ display: table-cell;
+ padding-right: 1em;
+}
/* USERS */
#newgroup-init a span { margin-left: 20px; }
@@ -157,10 +169,6 @@ table.nostyle td { padding: 0.2em 0; }
width: 32px;
}
-.ie8 #newgroup-form .icon-add {
- height: 30px;
-}
-
.isgroup .groupname {
width: 85%;
display: block;
@@ -277,10 +285,6 @@ input.userFilter {width: 200px;}
width: 32px;
}
-
-.ie8 table.hascontrols{border-collapse:collapse;width: 100%;}
-.ie8 table.hascontrols tbody tr{border-collapse:collapse;border: 1px solid #ddd !important;}
-
/* used to highlight a user row in red */
#userlist tr.row-warning {
background-color: #FDD;
@@ -592,3 +596,12 @@ doesnotexist:-o-prefocus, .strengthify-wrapper {
#warning {
color: red;
}
+
+/* SELECT */
+
+.select2-container-multi .select2-choices .select2-search-choice {
+ background-color: rgba(240,240,240,.9)!important;
+ border-color: rgba(240,240,240,.9)!important;
+ box-shadow: none!important;
+ background-image: none!important;
+}
diff --git a/settings/js/authtoken.js b/settings/js/authtoken.js
index 215192d7163..1d958a4d675 100644
--- a/settings/js/authtoken.js
+++ b/settings/js/authtoken.js
@@ -20,14 +20,14 @@
*
*/
-(function(OC, Backbone) {
+(function(OC) {
'use strict';
OC.Settings = OC.Settings || {};
- var AuthToken = Backbone.Model.extend({
+ var AuthToken = OC.Backbone.Model.extend({
});
OC.Settings.AuthToken = AuthToken;
-})(OC, Backbone);
+})(OC);
diff --git a/settings/js/authtoken_collection.js b/settings/js/authtoken_collection.js
index a78e053995f..ab7f7d5804a 100644
--- a/settings/js/authtoken_collection.js
+++ b/settings/js/authtoken_collection.js
@@ -20,12 +20,12 @@
*
*/
-(function(OC, Backbone) {
+(function(OC) {
'use strict';
OC.Settings = OC.Settings || {};
- var AuthTokenCollection = Backbone.Collection.extend({
+ var AuthTokenCollection = OC.Backbone.Collection.extend({
model: OC.Settings.AuthToken,
@@ -49,4 +49,4 @@
OC.Settings.AuthTokenCollection = AuthTokenCollection;
-})(OC, Backbone);
+})(OC);
diff --git a/settings/js/authtoken_view.js b/settings/js/authtoken_view.js
index 664dfd28148..01fc1b2ea34 100644
--- a/settings/js/authtoken_view.js
+++ b/settings/js/authtoken_view.js
@@ -1,4 +1,4 @@
-/* global Backbone, Handlebars, moment */
+/* global Handlebars, moment */
/**
* @author Christoph Wurst <christoph@owncloud.com>
@@ -20,19 +20,19 @@
*
*/
-(function(OC, _, Backbone, $, Handlebars, moment) {
+(function(OC, _, $, Handlebars, moment) {
'use strict';
OC.Settings = OC.Settings || {};
var TEMPLATE_TOKEN =
'<tr data-id="{{id}}">'
- + '<td>{{name}}</td>'
- + '<td><span class="last-activity" title="{{lastActivityTime}}">{{lastActivity}}</span></td>'
- + '<td><a class="icon-delete" title="' + t('core', 'Disconnect') + '"></a></td>'
+ + '<td class="has-tooltip" title="{{name}}"><span class="token-name">{{name}}</span></td>'
+ + '<td><span class="last-activity has-tooltip" title="{{lastActivityTime}}">{{lastActivity}}</span></td>'
+ + '<td><a class="icon-delete has-tooltip" title="' + t('core', 'Disconnect') + '"></a></td>'
+ '<tr>';
- var SubView = Backbone.View.extend({
+ var SubView = OC.Backbone.View.extend({
collection: null,
/**
@@ -80,8 +80,7 @@
viewData.lastActivityTime = OC.Util.formatDate(ts, 'LLL');
var html = _this.template(viewData);
var $html = $(html);
- $html.find('.last-activity').tooltip();
- $html.find('.icon-delete').tooltip();
+ $html.find('.has-tooltip').tooltip({container: 'body'});
list.append($html);
});
},
@@ -95,7 +94,7 @@
}
});
- var AuthTokenView = Backbone.View.extend({
+ var AuthTokenView = OC.Backbone.View.extend({
collection: null,
_views: [],
@@ -104,13 +103,15 @@
_tokenName: undefined,
- _addTokenBtn: undefined,
+ _addAppPasswordBtn: undefined,
_result: undefined,
- _newToken: undefined,
+ _newAppLoginName: undefined,
- _hideTokenBtn: undefined,
+ _newAppPassword: undefined,
+
+ _hideAppPasswordBtn: undefined,
_addingToken: false,
@@ -120,7 +121,7 @@
var tokenTypes = [0, 1];
var _this = this;
_.each(tokenTypes, function(type) {
- var el = type === 0 ? '#sessions' : '#devices';
+ var el = type === 0 ? '#sessions' : '#apppasswords';
_this._views.push(new SubView({
el: el,
type: type,
@@ -131,16 +132,18 @@
$el.on('click', 'a.icon-delete', _.bind(_this._onDeleteToken, _this));
});
- this._form = $('#device-token-form');
- this._tokenName = $('#device-token-name');
- this._addTokenBtn = $('#device-add-token');
- this._addTokenBtn.click(_.bind(this._addDeviceToken, this));
-
- this._result = $('#device-token-result');
- this._newToken = $('#device-new-token');
- this._newToken.on('focus', _.bind(this._onNewTokenFocus, this));
- this._hideTokenBtn = $('#device-token-hide');
- this._hideTokenBtn.click(_.bind(this._hideToken, this));
+ this._form = $('#app-password-form');
+ this._tokenName = $('#app-password-name');
+ this._addAppPasswordBtn = $('#add-app-password');
+ this._addAppPasswordBtn.click(_.bind(this._addAppPassword, this));
+
+ this._result = $('#app-password-result');
+ this._newAppLoginName = $('#new-app-login-name');
+ this._newAppLoginName.on('focus', _.bind(this._onNewTokenLoginNameFocus, this));
+ this._newAppPassword = $('#new-app-password');
+ this._newAppPassword.on('focus', _.bind(this._onNewTokenFocus, this));
+ this._hideAppPasswordBtn = $('#app-password-hide');
+ this._hideAppPasswordBtn.click(_.bind(this._hideToken, this));
},
render: function() {
@@ -167,7 +170,7 @@
});
},
- _addDeviceToken: function() {
+ _addAppPassword: function() {
var _this = this;
this._toggleAddingToken(true);
@@ -182,9 +185,10 @@
$.when(creatingToken).done(function(resp) {
_this.collection.add(resp.deviceToken);
_this.render();
- _this._newToken.val(resp.token);
+ _this._newAppLoginName.val(resp.loginName);
+ _this._newAppPassword.val(resp.token);
_this._toggleFormResult(false);
- _this._newToken.select();
+ _this._newAppPassword.select();
_this._tokenName.val('');
});
$.when(creatingToken).fail(function() {
@@ -195,8 +199,12 @@
});
},
+ _onNewTokenLoginNameFocus: function() {
+ this._newAppLoginName.select();
+ },
+
_onNewTokenFocus: function() {
- this._newToken.select();
+ this._newAppPassword.select();
},
_hideToken: function() {
@@ -205,7 +213,7 @@
_toggleAddingToken: function(state) {
this._addingToken = state;
- this._addTokenBtn.toggleClass('icon-loading-small', state);
+ this._addAppPasswordBtn.toggleClass('icon-loading-small', state);
},
_onDeleteToken: function(event) {
@@ -221,6 +229,8 @@
var destroyingToken = token.destroy();
+ $row.find('.icon-delete').tooltip('hide');
+
var _this = this;
$.when(destroyingToken).fail(function() {
OC.Notification.showTemporary(t('core', 'Error while deleting the token'));
@@ -238,4 +248,4 @@
OC.Settings.AuthTokenView = AuthTokenView;
-})(OC, _, Backbone, $, Handlebars, moment);
+})(OC, _, $, Handlebars, moment);
diff --git a/settings/js/personal.js b/settings/js/personal.js
index d270a49f3ad..16a8d184da6 100644
--- a/settings/js/personal.js
+++ b/settings/js/personal.js
@@ -192,6 +192,7 @@ $(document).ready(function () {
$('#pass2').showPassword().keyup();
}
$("#passwordbutton").click(function () {
+ OC.msg.startSaving('#password-error-msg');
var isIE8or9 = $('html').hasClass('lte9');
// FIXME - TODO - once support for IE8 and IE9 is dropped
// for IE8 and IE9 this will check additionally if the typed in password
@@ -208,25 +209,32 @@ $(document).ready(function () {
if (data.status === "success") {
$('#pass1').val('');
$('#pass2').val('').change();
- // Hide a possible errormsg and show successmsg
- $('#password-changed').removeClass('hidden').addClass('inlineblock');
- $('#password-error').removeClass('inlineblock').addClass('hidden');
+ OC.msg.finishedSaving('#password-error-msg', data);
} else {
if (typeof(data.data) !== "undefined") {
- $('#password-error').text(data.data.message);
+ OC.msg.finishedSaving('#password-error-msg', data);
} else {
- $('#password-error').text(t('Unable to change password'));
+ OC.msg.finishedSaving('#password-error-msg',
+ {
+ 'status' : 'error',
+ 'data' : {
+ 'message' : t('core', 'Unable to change password')
+ }
+ }
+ );
}
- // Hide a possible successmsg and show errormsg
- $('#password-changed').removeClass('inlineblock').addClass('hidden');
- $('#password-error').removeClass('hidden').addClass('inlineblock');
}
});
return false;
} else {
- // Hide a possible successmsg and show errormsg
- $('#password-changed').removeClass('inlineblock').addClass('hidden');
- $('#password-error').removeClass('hidden').addClass('inlineblock');
+ OC.msg.finishedSaving('#password-error-msg',
+ {
+ 'status' : 'error',
+ 'data' : {
+ 'message' : t('core', 'Unable to change password')
+ }
+ }
+ );
return false;
}
@@ -369,6 +377,17 @@ $(document).ready(function () {
collection: collection
});
view.reload();
+
+ // 'redirect' to anchor sections
+ // anchors are lost on redirects (e.g. while solving the 2fa challenge) otherwise
+ // example: /settings/person?section=devices will result in /settings/person?#devices
+ if (!window.location.hash) {
+ var query = OC.parseQueryString(location.search);
+ if (query && query.section) {
+ OC.Util.History.replaceState({});
+ window.location.hash = query.section;
+ }
+ }
});
if (!OC.Encryption) {
diff --git a/settings/l10n/ar.js b/settings/l10n/ar.js
index 092d2650102..96eeb9004e6 100644
--- a/settings/l10n/ar.js
+++ b/settings/l10n/ar.js
@@ -91,12 +91,13 @@ OC.L10N.register(
"Current password" : "كلمات السر الحالية",
"New password" : "كلمات سر جديدة",
"Change password" : "عدل كلمة السر",
- "Name" : "الاسم",
"Language" : "اللغة",
"Help translate" : "ساعد في الترجمه",
+ "Name" : "الاسم",
"Get the apps to sync your files" : "احصل على التطبيقات لمزامنة ملفاتك",
"Show First Run Wizard again" : "ابدأ خطوات بداية التشغيل من جديد",
"Username" : "إسم المستخدم",
+ "E-Mail" : "بريد إلكتروني",
"Create" : "انشئ",
"Admin Recovery Password" : "استعادة كلمة المرور للمسؤول",
"Enter the recovery password in order to recover the users files during password change" : "ادخل كلمة المرور المستعادة من اجل استرداد ملفات المستخدمين اثناء تغيير كلمة المرور",
diff --git a/settings/l10n/ar.json b/settings/l10n/ar.json
index 8236e017779..d7033ff2566 100644
--- a/settings/l10n/ar.json
+++ b/settings/l10n/ar.json
@@ -89,12 +89,13 @@
"Current password" : "كلمات السر الحالية",
"New password" : "كلمات سر جديدة",
"Change password" : "عدل كلمة السر",
- "Name" : "الاسم",
"Language" : "اللغة",
"Help translate" : "ساعد في الترجمه",
+ "Name" : "الاسم",
"Get the apps to sync your files" : "احصل على التطبيقات لمزامنة ملفاتك",
"Show First Run Wizard again" : "ابدأ خطوات بداية التشغيل من جديد",
"Username" : "إسم المستخدم",
+ "E-Mail" : "بريد إلكتروني",
"Create" : "انشئ",
"Admin Recovery Password" : "استعادة كلمة المرور للمسؤول",
"Enter the recovery password in order to recover the users files during password change" : "ادخل كلمة المرور المستعادة من اجل استرداد ملفات المستخدمين اثناء تغيير كلمة المرور",
diff --git a/settings/l10n/ast.js b/settings/l10n/ast.js
index 55d4f658d64..88f5a9c0a1f 100644
--- a/settings/l10n/ast.js
+++ b/settings/l10n/ast.js
@@ -130,9 +130,9 @@ OC.L10N.register(
"Current password" : "Contraseña actual",
"New password" : "Contraseña nueva",
"Change password" : "Camudar contraseña",
- "Name" : "Nome",
"Language" : "Llingua",
"Help translate" : "Ayúdanos nes traducciones",
+ "Name" : "Nome",
"Get the apps to sync your files" : "Obtener les aplicaciones pa sincronizar ficheros",
"Desktop client" : "Cliente d'escritoriu",
"Android app" : "Aplicación d'Android",
diff --git a/settings/l10n/ast.json b/settings/l10n/ast.json
index ae21852898e..70c61c9da53 100644
--- a/settings/l10n/ast.json
+++ b/settings/l10n/ast.json
@@ -128,9 +128,9 @@
"Current password" : "Contraseña actual",
"New password" : "Contraseña nueva",
"Change password" : "Camudar contraseña",
- "Name" : "Nome",
"Language" : "Llingua",
"Help translate" : "Ayúdanos nes traducciones",
+ "Name" : "Nome",
"Get the apps to sync your files" : "Obtener les aplicaciones pa sincronizar ficheros",
"Desktop client" : "Cliente d'escritoriu",
"Android app" : "Aplicación d'Android",
diff --git a/settings/l10n/az.js b/settings/l10n/az.js
index 5c23484726a..c9de6fb0c3b 100644
--- a/settings/l10n/az.js
+++ b/settings/l10n/az.js
@@ -183,9 +183,10 @@ OC.L10N.register(
"Current password" : "Hazırkı şifrə",
"New password" : "Yeni şifrə",
"Change password" : "Şifrəni dəyiş",
- "Name" : "Ad",
"Language" : "Dil",
"Help translate" : "Tərcüməyə kömək",
+ "Name" : "Ad",
+ "Done" : "Edildi",
"Get the apps to sync your files" : "Fayllarınızın sinxronizasiyası üçün proqramları götürün",
"Desktop client" : "Desktop client",
"Android app" : "Android proqramı",
diff --git a/settings/l10n/az.json b/settings/l10n/az.json
index c2081933421..37413d0e37d 100644
--- a/settings/l10n/az.json
+++ b/settings/l10n/az.json
@@ -181,9 +181,10 @@
"Current password" : "Hazırkı şifrə",
"New password" : "Yeni şifrə",
"Change password" : "Şifrəni dəyiş",
- "Name" : "Ad",
"Language" : "Dil",
"Help translate" : "Tərcüməyə kömək",
+ "Name" : "Ad",
+ "Done" : "Edildi",
"Get the apps to sync your files" : "Fayllarınızın sinxronizasiyası üçün proqramları götürün",
"Desktop client" : "Desktop client",
"Android app" : "Android proqramı",
diff --git a/settings/l10n/bg_BG.js b/settings/l10n/bg_BG.js
index 12795d66eb0..8a41c42474c 100644
--- a/settings/l10n/bg_BG.js
+++ b/settings/l10n/bg_BG.js
@@ -184,9 +184,10 @@ OC.L10N.register(
"Current password" : "Текуща парола",
"New password" : "Нова парола",
"Change password" : "Промяна на паролата",
- "Name" : "Име",
"Language" : "Език",
"Help translate" : "Помогни с превода",
+ "Name" : "Име",
+ "Done" : "Завършен",
"Get the apps to sync your files" : "Изтегли програми за синхронизиране на файловете ти",
"Desktop client" : "Клиент за настолен компютър",
"Android app" : "Андроид приложение",
diff --git a/settings/l10n/bg_BG.json b/settings/l10n/bg_BG.json
index 42f13242a04..da25c47d906 100644
--- a/settings/l10n/bg_BG.json
+++ b/settings/l10n/bg_BG.json
@@ -182,9 +182,10 @@
"Current password" : "Текуща парола",
"New password" : "Нова парола",
"Change password" : "Промяна на паролата",
- "Name" : "Име",
"Language" : "Език",
"Help translate" : "Помогни с превода",
+ "Name" : "Име",
+ "Done" : "Завършен",
"Get the apps to sync your files" : "Изтегли програми за синхронизиране на файловете ти",
"Desktop client" : "Клиент за настолен компютър",
"Android app" : "Андроид приложение",
diff --git a/settings/l10n/bn_BD.js b/settings/l10n/bn_BD.js
index 8b03ab3a141..d1d0ea94cb9 100644
--- a/settings/l10n/bn_BD.js
+++ b/settings/l10n/bn_BD.js
@@ -60,9 +60,10 @@ OC.L10N.register(
"Current password" : "বর্তমান কূটশব্দ",
"New password" : "নতুন কূটশব্দ",
"Change password" : "কূটশব্দ পরিবর্তন করুন",
- "Name" : "নাম",
"Language" : "ভাষা",
"Help translate" : "অনুবাদ করতে সহায়তা করুন",
+ "Name" : "নাম",
+ "Done" : "শেষ হলো",
"Get the apps to sync your files" : "আপনার ফাইলসমূহ সিংক করতে অ্যাপস নিন",
"Show First Run Wizard again" : "প্রথমবার চালানোর যাদুকর পূনরায় প্রদর্শন কর",
"Username" : "ব্যবহারকারী",
diff --git a/settings/l10n/bn_BD.json b/settings/l10n/bn_BD.json
index 682f4dbab2e..7c1df558601 100644
--- a/settings/l10n/bn_BD.json
+++ b/settings/l10n/bn_BD.json
@@ -58,9 +58,10 @@
"Current password" : "বর্তমান কূটশব্দ",
"New password" : "নতুন কূটশব্দ",
"Change password" : "কূটশব্দ পরিবর্তন করুন",
- "Name" : "নাম",
"Language" : "ভাষা",
"Help translate" : "অনুবাদ করতে সহায়তা করুন",
+ "Name" : "নাম",
+ "Done" : "শেষ হলো",
"Get the apps to sync your files" : "আপনার ফাইলসমূহ সিংক করতে অ্যাপস নিন",
"Show First Run Wizard again" : "প্রথমবার চালানোর যাদুকর পূনরায় প্রদর্শন কর",
"Username" : "ব্যবহারকারী",
diff --git a/settings/l10n/bs.js b/settings/l10n/bs.js
index 0f587b4fada..fefc3ef7559 100644
--- a/settings/l10n/bs.js
+++ b/settings/l10n/bs.js
@@ -151,9 +151,9 @@ OC.L10N.register(
"Current password" : "Trenutna lozinka",
"New password" : "Nova lozinka",
"Change password" : "Promijeni lozinku",
- "Name" : "Ime",
"Language" : "Jezik",
"Help translate" : "Pomozi prevesti",
+ "Name" : "Ime",
"Get the apps to sync your files" : "Koristite aplikacije za sinhronizaciju svojih datoteka",
"Desktop client" : "Desktop klijent",
"Android app" : "Android aplikacija",
diff --git a/settings/l10n/bs.json b/settings/l10n/bs.json
index 9e1f6ea1b37..3fc14020f6f 100644
--- a/settings/l10n/bs.json
+++ b/settings/l10n/bs.json
@@ -149,9 +149,9 @@
"Current password" : "Trenutna lozinka",
"New password" : "Nova lozinka",
"Change password" : "Promijeni lozinku",
- "Name" : "Ime",
"Language" : "Jezik",
"Help translate" : "Pomozi prevesti",
+ "Name" : "Ime",
"Get the apps to sync your files" : "Koristite aplikacije za sinhronizaciju svojih datoteka",
"Desktop client" : "Desktop klijent",
"Android app" : "Android aplikacija",
diff --git a/settings/l10n/ca.js b/settings/l10n/ca.js
index c89bafed900..127d268cef6 100644
--- a/settings/l10n/ca.js
+++ b/settings/l10n/ca.js
@@ -208,9 +208,9 @@ OC.L10N.register(
"Current password" : "Contrasenya actual",
"New password" : "Contrasenya nova",
"Change password" : "Canvia la contrasenya",
- "Name" : "Nom",
"Language" : "Idioma",
"Help translate" : "Ajudeu-nos amb la traducció",
+ "Name" : "Nom",
"Get the apps to sync your files" : "Obtingueu les aplicacions per sincronitzar els vostres fitxers",
"Desktop client" : "Client d'escriptori",
"Android app" : "aplicació para Android",
diff --git a/settings/l10n/ca.json b/settings/l10n/ca.json
index 6956f339122..0d43ef445d4 100644
--- a/settings/l10n/ca.json
+++ b/settings/l10n/ca.json
@@ -206,9 +206,9 @@
"Current password" : "Contrasenya actual",
"New password" : "Contrasenya nova",
"Change password" : "Canvia la contrasenya",
- "Name" : "Nom",
"Language" : "Idioma",
"Help translate" : "Ajudeu-nos amb la traducció",
+ "Name" : "Nom",
"Get the apps to sync your files" : "Obtingueu les aplicacions per sincronitzar els vostres fitxers",
"Desktop client" : "Client d'escriptori",
"Android app" : "aplicació para Android",
diff --git a/settings/l10n/cs_CZ.js b/settings/l10n/cs_CZ.js
index 7075dcfff20..46055a95cbd 100644
--- a/settings/l10n/cs_CZ.js
+++ b/settings/l10n/cs_CZ.js
@@ -119,7 +119,7 @@ OC.L10N.register(
"Unlimited" : "Neomezeně",
"Personal info" : "Osobní informace",
"Sessions" : "Sezení",
- "Devices" : "Přístroje",
+ "App passwords" : "Hesla aplikací",
"Sync clients" : "Synchronizační klienti",
"Everything (fatal issues, errors, warnings, info, debug)" : "Vše (fatální problémy, chyby, varování, informační, ladící)",
"Info, warnings, errors and fatal issues" : "Informace, varování, chyby a fatální problémy",
@@ -269,14 +269,17 @@ OC.L10N.register(
"Current password" : "Současné heslo",
"New password" : "Nové heslo",
"Change password" : "Změnit heslo",
+ "Language" : "Jazyk",
+ "Help translate" : "Pomoci s překladem",
"These are the web, desktop and mobile clients currently logged in to your ownCloud." : "Toto jsou klienti aktuálně přihlášení do této instance ownCloud přes web, počítač, či telefon.",
"Browser" : "Prohlížeč",
"Most recent activity" : "Nejnovější aktivity",
- "You've linked these devices." : "Připojili jste tyto přístroje.",
+ "You've linked these apps." : "Připojili jste tyto aplikace.",
"Name" : "Název",
- "A device password is a passcode that gives an app or device permissions to access your ownCloud account." : "Heslo přístroje je přihlašovací údaj umožňující aplikaci nebo přístroji přístup k ownCloud účtu.",
- "Language" : "Jazyk",
- "Help translate" : "Pomoci s překladem",
+ "An app password is a passcode that gives an app or device permissions to access your %s account." : "Heslo aplikace je přihlašovací údaj umožňující aplikaci nebo přístroji přístup k %s účtu.",
+ "App name" : "Jméno aplikace",
+ "Create new app password" : "Vytvořit nové heslo aplikace",
+ "Done" : "Dokončeno",
"Get the apps to sync your files" : "Získat aplikace pro synchronizaci vašich souborů",
"Desktop client" : "Aplikace pro počítač",
"Android app" : "Aplikace pro Android",
diff --git a/settings/l10n/cs_CZ.json b/settings/l10n/cs_CZ.json
index 64d2fb9af8b..1fabc5a46ad 100644
--- a/settings/l10n/cs_CZ.json
+++ b/settings/l10n/cs_CZ.json
@@ -117,7 +117,7 @@
"Unlimited" : "Neomezeně",
"Personal info" : "Osobní informace",
"Sessions" : "Sezení",
- "Devices" : "Přístroje",
+ "App passwords" : "Hesla aplikací",
"Sync clients" : "Synchronizační klienti",
"Everything (fatal issues, errors, warnings, info, debug)" : "Vše (fatální problémy, chyby, varování, informační, ladící)",
"Info, warnings, errors and fatal issues" : "Informace, varování, chyby a fatální problémy",
@@ -267,14 +267,17 @@
"Current password" : "Současné heslo",
"New password" : "Nové heslo",
"Change password" : "Změnit heslo",
+ "Language" : "Jazyk",
+ "Help translate" : "Pomoci s překladem",
"These are the web, desktop and mobile clients currently logged in to your ownCloud." : "Toto jsou klienti aktuálně přihlášení do této instance ownCloud přes web, počítač, či telefon.",
"Browser" : "Prohlížeč",
"Most recent activity" : "Nejnovější aktivity",
- "You've linked these devices." : "Připojili jste tyto přístroje.",
+ "You've linked these apps." : "Připojili jste tyto aplikace.",
"Name" : "Název",
- "A device password is a passcode that gives an app or device permissions to access your ownCloud account." : "Heslo přístroje je přihlašovací údaj umožňující aplikaci nebo přístroji přístup k ownCloud účtu.",
- "Language" : "Jazyk",
- "Help translate" : "Pomoci s překladem",
+ "An app password is a passcode that gives an app or device permissions to access your %s account." : "Heslo aplikace je přihlašovací údaj umožňující aplikaci nebo přístroji přístup k %s účtu.",
+ "App name" : "Jméno aplikace",
+ "Create new app password" : "Vytvořit nové heslo aplikace",
+ "Done" : "Dokončeno",
"Get the apps to sync your files" : "Získat aplikace pro synchronizaci vašich souborů",
"Desktop client" : "Aplikace pro počítač",
"Android app" : "Aplikace pro Android",
diff --git a/settings/l10n/da.js b/settings/l10n/da.js
index d00e2408483..62f43d5908a 100644
--- a/settings/l10n/da.js
+++ b/settings/l10n/da.js
@@ -231,9 +231,10 @@ OC.L10N.register(
"Current password" : "Nuværende adgangskode",
"New password" : "Nyt kodeord",
"Change password" : "Skift kodeord",
- "Name" : "Navn",
"Language" : "Sprog",
"Help translate" : "Hjælp med oversættelsen",
+ "Name" : "Navn",
+ "Done" : "Færdig",
"Get the apps to sync your files" : "Hent applikationerne for at synkronisere dine filer",
"Desktop client" : "Skrivebordsklient",
"Android app" : "Android-app",
diff --git a/settings/l10n/da.json b/settings/l10n/da.json
index 2504fc439d7..fc9209dad9a 100644
--- a/settings/l10n/da.json
+++ b/settings/l10n/da.json
@@ -229,9 +229,10 @@
"Current password" : "Nuværende adgangskode",
"New password" : "Nyt kodeord",
"Change password" : "Skift kodeord",
- "Name" : "Navn",
"Language" : "Sprog",
"Help translate" : "Hjælp med oversættelsen",
+ "Name" : "Navn",
+ "Done" : "Færdig",
"Get the apps to sync your files" : "Hent applikationerne for at synkronisere dine filer",
"Desktop client" : "Skrivebordsklient",
"Android app" : "Android-app",
diff --git a/settings/l10n/de.js b/settings/l10n/de.js
index d7571a61475..fcbe62b4316 100644
--- a/settings/l10n/de.js
+++ b/settings/l10n/de.js
@@ -119,7 +119,7 @@ OC.L10N.register(
"Unlimited" : "Unbegrenzt",
"Personal info" : "Persönliche Informationen",
"Sessions" : "Sitzungen",
- "Devices" : "Geräte",
+ "App passwords" : "App-Passwörter",
"Sync clients" : "Sync-Clients",
"Everything (fatal issues, errors, warnings, info, debug)" : "Alles (fatale Probleme, Fehler, Warnungen, Infos, Debug-Meldungen)",
"Info, warnings, errors and fatal issues" : "Infos, Warnungen, Fehler und fatale Probleme",
@@ -269,14 +269,17 @@ OC.L10N.register(
"Current password" : "Aktuelles Passwort",
"New password" : "Neues Passwort",
"Change password" : "Passwort ändern",
+ "Language" : "Sprache",
+ "Help translate" : "Hilf bei der Übersetzung",
"These are the web, desktop and mobile clients currently logged in to your ownCloud." : "Dies sind die Web-, Desktop und mobilen Clients, mit denen du aktuell in deiner ownCloud angemeldet bist.",
"Browser" : "Browser",
"Most recent activity" : "Neueste Aktivität",
- "You've linked these devices." : "Du hast diese Geräte verbunden.",
+ "You've linked these apps." : "Du hast diese Apps verbunden.",
"Name" : "Name",
- "A device password is a passcode that gives an app or device permissions to access your ownCloud account." : "Ein Gerätepasswort ist ein Passwort, dass einer App oder einem Gerät erlaubt auf deinen owncloud-Konto zuzugreifen,",
- "Language" : "Sprache",
- "Help translate" : "Hilf bei der Übersetzung",
+ "An app password is a passcode that gives an app or device permissions to access your %s account." : "Ein App-Passwort ist ein Passwort, dass einer App oder einem Gerät erlaubt auf Ihren %s-Konto zuzugreifen.",
+ "App name" : "App-Name",
+ "Create new app password" : "Neues App-Passwort erstellen",
+ "Done" : "Erledigt",
"Get the apps to sync your files" : "Lade die Apps zur Synchronisierung Deiner Daten herunter",
"Desktop client" : "Desktop-Client",
"Android app" : "Android-App",
diff --git a/settings/l10n/de.json b/settings/l10n/de.json
index 7860c26b07e..0ca5e040957 100644
--- a/settings/l10n/de.json
+++ b/settings/l10n/de.json
@@ -117,7 +117,7 @@
"Unlimited" : "Unbegrenzt",
"Personal info" : "Persönliche Informationen",
"Sessions" : "Sitzungen",
- "Devices" : "Geräte",
+ "App passwords" : "App-Passwörter",
"Sync clients" : "Sync-Clients",
"Everything (fatal issues, errors, warnings, info, debug)" : "Alles (fatale Probleme, Fehler, Warnungen, Infos, Debug-Meldungen)",
"Info, warnings, errors and fatal issues" : "Infos, Warnungen, Fehler und fatale Probleme",
@@ -267,14 +267,17 @@
"Current password" : "Aktuelles Passwort",
"New password" : "Neues Passwort",
"Change password" : "Passwort ändern",
+ "Language" : "Sprache",
+ "Help translate" : "Hilf bei der Übersetzung",
"These are the web, desktop and mobile clients currently logged in to your ownCloud." : "Dies sind die Web-, Desktop und mobilen Clients, mit denen du aktuell in deiner ownCloud angemeldet bist.",
"Browser" : "Browser",
"Most recent activity" : "Neueste Aktivität",
- "You've linked these devices." : "Du hast diese Geräte verbunden.",
+ "You've linked these apps." : "Du hast diese Apps verbunden.",
"Name" : "Name",
- "A device password is a passcode that gives an app or device permissions to access your ownCloud account." : "Ein Gerätepasswort ist ein Passwort, dass einer App oder einem Gerät erlaubt auf deinen owncloud-Konto zuzugreifen,",
- "Language" : "Sprache",
- "Help translate" : "Hilf bei der Übersetzung",
+ "An app password is a passcode that gives an app or device permissions to access your %s account." : "Ein App-Passwort ist ein Passwort, dass einer App oder einem Gerät erlaubt auf Ihren %s-Konto zuzugreifen.",
+ "App name" : "App-Name",
+ "Create new app password" : "Neues App-Passwort erstellen",
+ "Done" : "Erledigt",
"Get the apps to sync your files" : "Lade die Apps zur Synchronisierung Deiner Daten herunter",
"Desktop client" : "Desktop-Client",
"Android app" : "Android-App",
diff --git a/settings/l10n/de_DE.js b/settings/l10n/de_DE.js
index 8d6e0d5c140..8d9e821a067 100644
--- a/settings/l10n/de_DE.js
+++ b/settings/l10n/de_DE.js
@@ -119,7 +119,7 @@ OC.L10N.register(
"Unlimited" : "Unbegrenzt",
"Personal info" : "Persönliche Informationen",
"Sessions" : "Sitzungen",
- "Devices" : "Geräte",
+ "App passwords" : "App-Passwörter",
"Sync clients" : "Sync-Clients",
"Everything (fatal issues, errors, warnings, info, debug)" : "Alles (fatale Probleme, Fehler, Warnungen, Infos, Fehlerdiagnose)",
"Info, warnings, errors and fatal issues" : "Infos, Warnungen, Fehler und fatale Probleme",
@@ -269,14 +269,17 @@ OC.L10N.register(
"Current password" : "Aktuelles Passwort",
"New password" : "Neues Passwort",
"Change password" : "Passwort ändern",
+ "Language" : "Sprache",
+ "Help translate" : "Helfen Sie bei der Übersetzung",
"These are the web, desktop and mobile clients currently logged in to your ownCloud." : "Dies sind die Web-, Desktop und mobilen Clients, mit denen Sie aktuell in Ihrer ownCloud angemeldet sind.",
"Browser" : "Browser",
"Most recent activity" : "Neueste Aktivität",
- "You've linked these devices." : "Sie haben diese Geräte verbunden.",
+ "You've linked these apps." : "Sie haben diese Apps verbunden.",
"Name" : "Name",
- "A device password is a passcode that gives an app or device permissions to access your ownCloud account." : "Ein Gerätepasswort ist ein Passwort, dass einer App oder einem Gerät erlaubt auf Ihren owncloud-Konto zuzugreifen,",
- "Language" : "Sprache",
- "Help translate" : "Helfen Sie bei der Übersetzung",
+ "An app password is a passcode that gives an app or device permissions to access your %s account." : "Ein App-Passwort ist ein Passwort, dass einer App oder einem Gerät erlaubt auf Ihren %s-Konto zuzugreifen.",
+ "App name" : "App-Name",
+ "Create new app password" : "Neues App-Passwort erstellen",
+ "Done" : "Erledigt",
"Get the apps to sync your files" : "Installieren Sie die Anwendungen, um Ihre Dateien zu synchronisieren",
"Desktop client" : "Desktop-Client",
"Android app" : "Android-App",
diff --git a/settings/l10n/de_DE.json b/settings/l10n/de_DE.json
index 4c05b2862ae..436bc01b6c4 100644
--- a/settings/l10n/de_DE.json
+++ b/settings/l10n/de_DE.json
@@ -117,7 +117,7 @@
"Unlimited" : "Unbegrenzt",
"Personal info" : "Persönliche Informationen",
"Sessions" : "Sitzungen",
- "Devices" : "Geräte",
+ "App passwords" : "App-Passwörter",
"Sync clients" : "Sync-Clients",
"Everything (fatal issues, errors, warnings, info, debug)" : "Alles (fatale Probleme, Fehler, Warnungen, Infos, Fehlerdiagnose)",
"Info, warnings, errors and fatal issues" : "Infos, Warnungen, Fehler und fatale Probleme",
@@ -267,14 +267,17 @@
"Current password" : "Aktuelles Passwort",
"New password" : "Neues Passwort",
"Change password" : "Passwort ändern",
+ "Language" : "Sprache",
+ "Help translate" : "Helfen Sie bei der Übersetzung",
"These are the web, desktop and mobile clients currently logged in to your ownCloud." : "Dies sind die Web-, Desktop und mobilen Clients, mit denen Sie aktuell in Ihrer ownCloud angemeldet sind.",
"Browser" : "Browser",
"Most recent activity" : "Neueste Aktivität",
- "You've linked these devices." : "Sie haben diese Geräte verbunden.",
+ "You've linked these apps." : "Sie haben diese Apps verbunden.",
"Name" : "Name",
- "A device password is a passcode that gives an app or device permissions to access your ownCloud account." : "Ein Gerätepasswort ist ein Passwort, dass einer App oder einem Gerät erlaubt auf Ihren owncloud-Konto zuzugreifen,",
- "Language" : "Sprache",
- "Help translate" : "Helfen Sie bei der Übersetzung",
+ "An app password is a passcode that gives an app or device permissions to access your %s account." : "Ein App-Passwort ist ein Passwort, dass einer App oder einem Gerät erlaubt auf Ihren %s-Konto zuzugreifen.",
+ "App name" : "App-Name",
+ "Create new app password" : "Neues App-Passwort erstellen",
+ "Done" : "Erledigt",
"Get the apps to sync your files" : "Installieren Sie die Anwendungen, um Ihre Dateien zu synchronisieren",
"Desktop client" : "Desktop-Client",
"Android app" : "Android-App",
diff --git a/settings/l10n/el.js b/settings/l10n/el.js
index 8bd3f1a435e..0440c528d24 100644
--- a/settings/l10n/el.js
+++ b/settings/l10n/el.js
@@ -106,7 +106,6 @@ OC.L10N.register(
"__language_name__" : "__όνομα_γλώσσας__",
"Unlimited" : "Απεριόριστο",
"Personal info" : "Προσωπικές Πληροφορίες",
- "Devices" : "Συσκευές",
"Sync clients" : "Συγχρονισμός πελατών",
"Everything (fatal issues, errors, warnings, info, debug)" : "Όλα (καίρια ζητήματα, σφάλματα, προειδοποιήσεις, πληροφορίες, αποσφαλμάτωση)",
"Info, warnings, errors and fatal issues" : "Πληροφορίες, προειδοποιήσεις, σφάλματα και καίρια ζητήματα",
@@ -234,9 +233,10 @@ OC.L10N.register(
"Current password" : "Τρέχων συνθηματικό",
"New password" : "Νέο συνθηματικό",
"Change password" : "Αλλαγή συνθηματικού",
- "Name" : "Όνομα",
"Language" : "Γλώσσα",
"Help translate" : "Βοηθήστε στη μετάφραση",
+ "Name" : "Όνομα",
+ "Done" : "Ολοκληρώθηκε",
"Get the apps to sync your files" : "Λήψη της εφαρμογής για συγχρονισμό των αρχείων σας",
"Desktop client" : "Πελάτης σταθερού υπολογιστή",
"Android app" : "Εφαρμογή Android",
diff --git a/settings/l10n/el.json b/settings/l10n/el.json
index 25cbbc73e7b..8170fc813b0 100644
--- a/settings/l10n/el.json
+++ b/settings/l10n/el.json
@@ -104,7 +104,6 @@
"__language_name__" : "__όνομα_γλώσσας__",
"Unlimited" : "Απεριόριστο",
"Personal info" : "Προσωπικές Πληροφορίες",
- "Devices" : "Συσκευές",
"Sync clients" : "Συγχρονισμός πελατών",
"Everything (fatal issues, errors, warnings, info, debug)" : "Όλα (καίρια ζητήματα, σφάλματα, προειδοποιήσεις, πληροφορίες, αποσφαλμάτωση)",
"Info, warnings, errors and fatal issues" : "Πληροφορίες, προειδοποιήσεις, σφάλματα και καίρια ζητήματα",
@@ -232,9 +231,10 @@
"Current password" : "Τρέχων συνθηματικό",
"New password" : "Νέο συνθηματικό",
"Change password" : "Αλλαγή συνθηματικού",
- "Name" : "Όνομα",
"Language" : "Γλώσσα",
"Help translate" : "Βοηθήστε στη μετάφραση",
+ "Name" : "Όνομα",
+ "Done" : "Ολοκληρώθηκε",
"Get the apps to sync your files" : "Λήψη της εφαρμογής για συγχρονισμό των αρχείων σας",
"Desktop client" : "Πελάτης σταθερού υπολογιστή",
"Android app" : "Εφαρμογή Android",
diff --git a/settings/l10n/en_GB.js b/settings/l10n/en_GB.js
index 94ddadf854b..4b4194002b0 100644
--- a/settings/l10n/en_GB.js
+++ b/settings/l10n/en_GB.js
@@ -119,7 +119,7 @@ OC.L10N.register(
"Unlimited" : "Unlimited",
"Personal info" : "Personal info",
"Sessions" : "Sessions",
- "Devices" : "Devices",
+ "App passwords" : "App passwords",
"Sync clients" : "Sync clients",
"Everything (fatal issues, errors, warnings, info, debug)" : "Everything (fatal issues, errors, warnings, info, debug)",
"Info, warnings, errors and fatal issues" : "Info, warnings, errors and fatal issues",
@@ -223,6 +223,7 @@ OC.L10N.register(
"Documentation:" : "Documentation:",
"User documentation" : "User documentation",
"Admin documentation" : "Admin documentation",
+ "Visit website" : "Visit website",
"Report a bug" : "Report a bug",
"Show description …" : "Show description …",
"Hide description …" : "Hide description …",
@@ -268,14 +269,17 @@ OC.L10N.register(
"Current password" : "Current password",
"New password" : "New password",
"Change password" : "Change password",
+ "Language" : "Language",
+ "Help translate" : "Help translate",
"These are the web, desktop and mobile clients currently logged in to your ownCloud." : "These are the web, desktop and mobile clients currently logged in to your ownCloud.",
"Browser" : "Browser",
"Most recent activity" : "Most recent activity",
- "You've linked these devices." : "You've linked these devices.",
+ "You've linked these apps." : "You've linked these apps.",
"Name" : "Name",
- "A device password is a passcode that gives an app or device permissions to access your ownCloud account." : "A device password is a passcode that gives an app or device permissions to access your ownCloud account.",
- "Language" : "Language",
- "Help translate" : "Help translate",
+ "An app password is a passcode that gives an app or device permissions to access your %s account." : "An app password is a passcode that gives an app or device permissions to access your %s account.",
+ "App name" : "App name",
+ "Create new app password" : "Create new app password",
+ "Done" : "Done",
"Get the apps to sync your files" : "Get the apps to sync your files",
"Desktop client" : "Desktop client",
"Android app" : "Android app",
diff --git a/settings/l10n/en_GB.json b/settings/l10n/en_GB.json
index 9b9380ef1f3..ef3a53fcf5b 100644
--- a/settings/l10n/en_GB.json
+++ b/settings/l10n/en_GB.json
@@ -117,7 +117,7 @@
"Unlimited" : "Unlimited",
"Personal info" : "Personal info",
"Sessions" : "Sessions",
- "Devices" : "Devices",
+ "App passwords" : "App passwords",
"Sync clients" : "Sync clients",
"Everything (fatal issues, errors, warnings, info, debug)" : "Everything (fatal issues, errors, warnings, info, debug)",
"Info, warnings, errors and fatal issues" : "Info, warnings, errors and fatal issues",
@@ -221,6 +221,7 @@
"Documentation:" : "Documentation:",
"User documentation" : "User documentation",
"Admin documentation" : "Admin documentation",
+ "Visit website" : "Visit website",
"Report a bug" : "Report a bug",
"Show description …" : "Show description …",
"Hide description …" : "Hide description …",
@@ -266,14 +267,17 @@
"Current password" : "Current password",
"New password" : "New password",
"Change password" : "Change password",
+ "Language" : "Language",
+ "Help translate" : "Help translate",
"These are the web, desktop and mobile clients currently logged in to your ownCloud." : "These are the web, desktop and mobile clients currently logged in to your ownCloud.",
"Browser" : "Browser",
"Most recent activity" : "Most recent activity",
- "You've linked these devices." : "You've linked these devices.",
+ "You've linked these apps." : "You've linked these apps.",
"Name" : "Name",
- "A device password is a passcode that gives an app or device permissions to access your ownCloud account." : "A device password is a passcode that gives an app or device permissions to access your ownCloud account.",
- "Language" : "Language",
- "Help translate" : "Help translate",
+ "An app password is a passcode that gives an app or device permissions to access your %s account." : "An app password is a passcode that gives an app or device permissions to access your %s account.",
+ "App name" : "App name",
+ "Create new app password" : "Create new app password",
+ "Done" : "Done",
"Get the apps to sync your files" : "Get the apps to sync your files",
"Desktop client" : "Desktop client",
"Android app" : "Android app",
diff --git a/settings/l10n/eo.js b/settings/l10n/eo.js
index c34ab8141fe..55bad706b82 100644
--- a/settings/l10n/eo.js
+++ b/settings/l10n/eo.js
@@ -122,9 +122,10 @@ OC.L10N.register(
"Current password" : "Nuna pasvorto",
"New password" : "Nova pasvorto",
"Change password" : "Ŝanĝi la pasvorton",
- "Name" : "Nomo",
"Language" : "Lingvo",
"Help translate" : "Helpu traduki",
+ "Name" : "Nomo",
+ "Done" : "Farita",
"Get the apps to sync your files" : "Ekhavu la aplikaĵojn por sinkronigi viajn dosierojn",
"Desktop client" : "Labortabla kliento",
"Android app" : "Android-aplikaĵo",
diff --git a/settings/l10n/eo.json b/settings/l10n/eo.json
index 38a365fc68d..dc0d29c1de7 100644
--- a/settings/l10n/eo.json
+++ b/settings/l10n/eo.json
@@ -120,9 +120,10 @@
"Current password" : "Nuna pasvorto",
"New password" : "Nova pasvorto",
"Change password" : "Ŝanĝi la pasvorton",
- "Name" : "Nomo",
"Language" : "Lingvo",
"Help translate" : "Helpu traduki",
+ "Name" : "Nomo",
+ "Done" : "Farita",
"Get the apps to sync your files" : "Ekhavu la aplikaĵojn por sinkronigi viajn dosierojn",
"Desktop client" : "Labortabla kliento",
"Android app" : "Android-aplikaĵo",
diff --git a/settings/l10n/es.js b/settings/l10n/es.js
index fb86eed826e..7360a987cd0 100644
--- a/settings/l10n/es.js
+++ b/settings/l10n/es.js
@@ -119,7 +119,6 @@ OC.L10N.register(
"Unlimited" : "Ilimitado",
"Personal info" : "Información personal",
"Sessions" : "Sesiones",
- "Devices" : "Dispositivos",
"Sync clients" : "Sincronizar clientes",
"Everything (fatal issues, errors, warnings, info, debug)" : "Todo (Información, Avisos, Errores, debug y problemas fatales)",
"Info, warnings, errors and fatal issues" : "Información, Avisos, Errores y problemas fatales",
@@ -269,14 +268,13 @@ OC.L10N.register(
"Current password" : "Contraseña actual",
"New password" : "Nueva contraseña",
"Change password" : "Cambiar contraseña",
+ "Language" : "Idioma",
+ "Help translate" : "Ayúdanos a traducir",
"These are the web, desktop and mobile clients currently logged in to your ownCloud." : "Estos son los clientes en web, en escritorio y en móvil actualmente conectados en su OwnCloud.",
"Browser" : "Navegador",
"Most recent activity" : "Actividad más reciente",
- "You've linked these devices." : "Has enlazado estos dispositivos",
"Name" : "Nombre",
- "A device password is a passcode that gives an app or device permissions to access your ownCloud account." : "Una contraseña de dispositivo es un código que proporciona permisos a una app o dispositivo para acceder a tu cuenta de ownCloud",
- "Language" : "Idioma",
- "Help translate" : "Ayúdanos a traducir",
+ "Done" : "Hecho",
"Get the apps to sync your files" : "Obtener las aplicaciones para sincronizar sus archivos",
"Desktop client" : "Cliente de escritorio",
"Android app" : "Aplicación de Android",
diff --git a/settings/l10n/es.json b/settings/l10n/es.json
index 2c67d870ccd..06154e70e05 100644
--- a/settings/l10n/es.json
+++ b/settings/l10n/es.json
@@ -117,7 +117,6 @@
"Unlimited" : "Ilimitado",
"Personal info" : "Información personal",
"Sessions" : "Sesiones",
- "Devices" : "Dispositivos",
"Sync clients" : "Sincronizar clientes",
"Everything (fatal issues, errors, warnings, info, debug)" : "Todo (Información, Avisos, Errores, debug y problemas fatales)",
"Info, warnings, errors and fatal issues" : "Información, Avisos, Errores y problemas fatales",
@@ -267,14 +266,13 @@
"Current password" : "Contraseña actual",
"New password" : "Nueva contraseña",
"Change password" : "Cambiar contraseña",
+ "Language" : "Idioma",
+ "Help translate" : "Ayúdanos a traducir",
"These are the web, desktop and mobile clients currently logged in to your ownCloud." : "Estos son los clientes en web, en escritorio y en móvil actualmente conectados en su OwnCloud.",
"Browser" : "Navegador",
"Most recent activity" : "Actividad más reciente",
- "You've linked these devices." : "Has enlazado estos dispositivos",
"Name" : "Nombre",
- "A device password is a passcode that gives an app or device permissions to access your ownCloud account." : "Una contraseña de dispositivo es un código que proporciona permisos a una app o dispositivo para acceder a tu cuenta de ownCloud",
- "Language" : "Idioma",
- "Help translate" : "Ayúdanos a traducir",
+ "Done" : "Hecho",
"Get the apps to sync your files" : "Obtener las aplicaciones para sincronizar sus archivos",
"Desktop client" : "Cliente de escritorio",
"Android app" : "Aplicación de Android",
diff --git a/settings/l10n/es_AR.js b/settings/l10n/es_AR.js
index 4553975267b..7c99d3e5d7a 100644
--- a/settings/l10n/es_AR.js
+++ b/settings/l10n/es_AR.js
@@ -102,9 +102,9 @@ OC.L10N.register(
"Current password" : "Contraseña actual",
"New password" : "Nueva contraseña:",
"Change password" : "Cambiar contraseña",
- "Name" : "Nombre",
"Language" : "Idioma",
"Help translate" : "Ayudanos a traducir",
+ "Name" : "Nombre",
"Get the apps to sync your files" : "Obtené Apps para sincronizar tus archivos",
"Desktop client" : "Cliente de escritorio",
"Android app" : "App para Android",
diff --git a/settings/l10n/es_AR.json b/settings/l10n/es_AR.json
index f1d988e59a1..46cf5526f69 100644
--- a/settings/l10n/es_AR.json
+++ b/settings/l10n/es_AR.json
@@ -100,9 +100,9 @@
"Current password" : "Contraseña actual",
"New password" : "Nueva contraseña:",
"Change password" : "Cambiar contraseña",
- "Name" : "Nombre",
"Language" : "Idioma",
"Help translate" : "Ayudanos a traducir",
+ "Name" : "Nombre",
"Get the apps to sync your files" : "Obtené Apps para sincronizar tus archivos",
"Desktop client" : "Cliente de escritorio",
"Android app" : "App para Android",
diff --git a/settings/l10n/es_MX.js b/settings/l10n/es_MX.js
index 292bfae47ef..3472c25389f 100644
--- a/settings/l10n/es_MX.js
+++ b/settings/l10n/es_MX.js
@@ -77,9 +77,9 @@ OC.L10N.register(
"Current password" : "Contraseña actual",
"New password" : "Nueva contraseña",
"Change password" : "Cambiar contraseña",
- "Name" : "Nombre",
"Language" : "Idioma",
"Help translate" : "Ayúdanos a traducir",
+ "Name" : "Nombre",
"Get the apps to sync your files" : "Obtener las aplicaciones para sincronizar sus archivos",
"Show First Run Wizard again" : "Mostrar nuevamente el Asistente de ejecución inicial",
"Username" : "Nombre de usuario",
diff --git a/settings/l10n/es_MX.json b/settings/l10n/es_MX.json
index bf00a148f83..cefc0f8c6e3 100644
--- a/settings/l10n/es_MX.json
+++ b/settings/l10n/es_MX.json
@@ -75,9 +75,9 @@
"Current password" : "Contraseña actual",
"New password" : "Nueva contraseña",
"Change password" : "Cambiar contraseña",
- "Name" : "Nombre",
"Language" : "Idioma",
"Help translate" : "Ayúdanos a traducir",
+ "Name" : "Nombre",
"Get the apps to sync your files" : "Obtener las aplicaciones para sincronizar sus archivos",
"Show First Run Wizard again" : "Mostrar nuevamente el Asistente de ejecución inicial",
"Username" : "Nombre de usuario",
diff --git a/settings/l10n/et_EE.js b/settings/l10n/et_EE.js
index c2fa0631fb8..8448dd500fd 100644
--- a/settings/l10n/et_EE.js
+++ b/settings/l10n/et_EE.js
@@ -197,9 +197,10 @@ OC.L10N.register(
"Current password" : "Praegune parool",
"New password" : "Uus parool",
"Change password" : "Muuda parooli",
- "Name" : "Nimi",
"Language" : "Keel",
"Help translate" : "Aita tõlkida",
+ "Name" : "Nimi",
+ "Done" : "Valmis",
"Get the apps to sync your files" : "Hangi rakendusi failide sünkroniseerimiseks",
"Desktop client" : "Töölaua klient",
"Android app" : "Androidi rakendus",
diff --git a/settings/l10n/et_EE.json b/settings/l10n/et_EE.json
index d65f6fa7c16..8d74443002d 100644
--- a/settings/l10n/et_EE.json
+++ b/settings/l10n/et_EE.json
@@ -195,9 +195,10 @@
"Current password" : "Praegune parool",
"New password" : "Uus parool",
"Change password" : "Muuda parooli",
- "Name" : "Nimi",
"Language" : "Keel",
"Help translate" : "Aita tõlkida",
+ "Name" : "Nimi",
+ "Done" : "Valmis",
"Get the apps to sync your files" : "Hangi rakendusi failide sünkroniseerimiseks",
"Desktop client" : "Töölaua klient",
"Android app" : "Androidi rakendus",
diff --git a/settings/l10n/eu.js b/settings/l10n/eu.js
index 7e45f144525..a8703668ac2 100644
--- a/settings/l10n/eu.js
+++ b/settings/l10n/eu.js
@@ -161,9 +161,10 @@ OC.L10N.register(
"Current password" : "Uneko pasahitza",
"New password" : "Pasahitz berria",
"Change password" : "Aldatu pasahitza",
- "Name" : "Izena",
"Language" : "Hizkuntza",
"Help translate" : "Lagundu itzultzen",
+ "Name" : "Izena",
+ "Done" : "Egina",
"Get the apps to sync your files" : "Lortu aplikazioak zure fitxategiak sinkronizatzeko",
"Desktop client" : "Mahaigaineko bezeroa",
"Android app" : "Android aplikazioa",
diff --git a/settings/l10n/eu.json b/settings/l10n/eu.json
index 204d1a45107..efb94b66a8a 100644
--- a/settings/l10n/eu.json
+++ b/settings/l10n/eu.json
@@ -159,9 +159,10 @@
"Current password" : "Uneko pasahitza",
"New password" : "Pasahitz berria",
"Change password" : "Aldatu pasahitza",
- "Name" : "Izena",
"Language" : "Hizkuntza",
"Help translate" : "Lagundu itzultzen",
+ "Name" : "Izena",
+ "Done" : "Egina",
"Get the apps to sync your files" : "Lortu aplikazioak zure fitxategiak sinkronizatzeko",
"Desktop client" : "Mahaigaineko bezeroa",
"Android app" : "Android aplikazioa",
diff --git a/settings/l10n/fa.js b/settings/l10n/fa.js
index c6d8a17b4fb..591e7c429cf 100644
--- a/settings/l10n/fa.js
+++ b/settings/l10n/fa.js
@@ -186,9 +186,9 @@ OC.L10N.register(
"Current password" : "گذرواژه کنونی",
"New password" : "گذرواژه جدید",
"Change password" : "تغییر گذر واژه",
- "Name" : "نام",
"Language" : "زبان",
"Help translate" : "به ترجمه آن کمک کنید",
+ "Name" : "نام",
"Get the apps to sync your files" : "برنامه ها را دریافت کنید تا فایل هایتان را همگام سازید",
"Desktop client" : "نرم افزار دسکتاپ",
"Android app" : "اپ اندروید",
diff --git a/settings/l10n/fa.json b/settings/l10n/fa.json
index 19c9426a8bd..b1c27a0c265 100644
--- a/settings/l10n/fa.json
+++ b/settings/l10n/fa.json
@@ -184,9 +184,9 @@
"Current password" : "گذرواژه کنونی",
"New password" : "گذرواژه جدید",
"Change password" : "تغییر گذر واژه",
- "Name" : "نام",
"Language" : "زبان",
"Help translate" : "به ترجمه آن کمک کنید",
+ "Name" : "نام",
"Get the apps to sync your files" : "برنامه ها را دریافت کنید تا فایل هایتان را همگام سازید",
"Desktop client" : "نرم افزار دسکتاپ",
"Android app" : "اپ اندروید",
diff --git a/settings/l10n/fi_FI.js b/settings/l10n/fi_FI.js
index 0b3a1fe3a79..9dce4b9a089 100644
--- a/settings/l10n/fi_FI.js
+++ b/settings/l10n/fi_FI.js
@@ -115,7 +115,7 @@ OC.L10N.register(
"Unlimited" : "Rajoittamaton",
"Personal info" : "Henkilökohtaiset tiedot",
"Sessions" : "Istunnot",
- "Devices" : "Laitteet",
+ "App passwords" : "Sovellusten salasanat",
"Sync clients" : "Synkronointisovellukset",
"Everything (fatal issues, errors, warnings, info, debug)" : "Kaikki (vakavat ongelmat, virheet, varoitukset, tiedot, vianjäljitys)",
"Info, warnings, errors and fatal issues" : "Tiedot, varoitukset, virheet ja vakavat ongelmat",
@@ -254,14 +254,17 @@ OC.L10N.register(
"Current password" : "Nykyinen salasana",
"New password" : "Uusi salasana",
"Change password" : "Vaihda salasana",
+ "Language" : "Kieli",
+ "Help translate" : "Auta kääntämisessä",
"These are the web, desktop and mobile clients currently logged in to your ownCloud." : "Nämä ovat parhaillaan ownCloudiisi kirjautuneet verkko-, työpöytä- ja mobiilisovellukset.",
"Browser" : "Selain",
"Most recent activity" : "Viimeisimmät toimet",
- "You've linked these devices." : "Olet linkittänyt nämä laitteet.",
+ "You've linked these apps." : "Olet linkittänyt nämä sovellukset.",
"Name" : "Nimi",
- "A device password is a passcode that gives an app or device permissions to access your ownCloud account." : "Laitesalasana on suojakoodi, jonka avulla sovellus tai laite saa oikeuden käyttää ownCloud-tiliäsi.",
- "Language" : "Kieli",
- "Help translate" : "Auta kääntämisessä",
+ "An app password is a passcode that gives an app or device permissions to access your %s account." : "Sovellussalasana on suojakoodi, joka antaa sovellukselle tai laitteelle käyttöoikeuden %s-tiliisi.",
+ "App name" : "Sovelluksen nimi",
+ "Create new app password" : "Luo uusi sovellussalasana",
+ "Done" : "Valmis",
"Get the apps to sync your files" : "Aseta sovellukset synkronoimaan tiedostosi",
"Desktop client" : "Työpöytäsovellus",
"Android app" : "Android-sovellus",
diff --git a/settings/l10n/fi_FI.json b/settings/l10n/fi_FI.json
index 3b808944443..efa330bc210 100644
--- a/settings/l10n/fi_FI.json
+++ b/settings/l10n/fi_FI.json
@@ -113,7 +113,7 @@
"Unlimited" : "Rajoittamaton",
"Personal info" : "Henkilökohtaiset tiedot",
"Sessions" : "Istunnot",
- "Devices" : "Laitteet",
+ "App passwords" : "Sovellusten salasanat",
"Sync clients" : "Synkronointisovellukset",
"Everything (fatal issues, errors, warnings, info, debug)" : "Kaikki (vakavat ongelmat, virheet, varoitukset, tiedot, vianjäljitys)",
"Info, warnings, errors and fatal issues" : "Tiedot, varoitukset, virheet ja vakavat ongelmat",
@@ -252,14 +252,17 @@
"Current password" : "Nykyinen salasana",
"New password" : "Uusi salasana",
"Change password" : "Vaihda salasana",
+ "Language" : "Kieli",
+ "Help translate" : "Auta kääntämisessä",
"These are the web, desktop and mobile clients currently logged in to your ownCloud." : "Nämä ovat parhaillaan ownCloudiisi kirjautuneet verkko-, työpöytä- ja mobiilisovellukset.",
"Browser" : "Selain",
"Most recent activity" : "Viimeisimmät toimet",
- "You've linked these devices." : "Olet linkittänyt nämä laitteet.",
+ "You've linked these apps." : "Olet linkittänyt nämä sovellukset.",
"Name" : "Nimi",
- "A device password is a passcode that gives an app or device permissions to access your ownCloud account." : "Laitesalasana on suojakoodi, jonka avulla sovellus tai laite saa oikeuden käyttää ownCloud-tiliäsi.",
- "Language" : "Kieli",
- "Help translate" : "Auta kääntämisessä",
+ "An app password is a passcode that gives an app or device permissions to access your %s account." : "Sovellussalasana on suojakoodi, joka antaa sovellukselle tai laitteelle käyttöoikeuden %s-tiliisi.",
+ "App name" : "Sovelluksen nimi",
+ "Create new app password" : "Luo uusi sovellussalasana",
+ "Done" : "Valmis",
"Get the apps to sync your files" : "Aseta sovellukset synkronoimaan tiedostosi",
"Desktop client" : "Työpöytäsovellus",
"Android app" : "Android-sovellus",
diff --git a/settings/l10n/fr.js b/settings/l10n/fr.js
index 3ad530cd138..8e7d471da92 100644
--- a/settings/l10n/fr.js
+++ b/settings/l10n/fr.js
@@ -118,7 +118,6 @@ OC.L10N.register(
"__language_name__" : "Français",
"Unlimited" : "Illimité",
"Personal info" : "Informations personnelles",
- "Devices" : "Appareils",
"Sync clients" : "Clients de synchronisation",
"Everything (fatal issues, errors, warnings, info, debug)" : "Tout (erreurs fatales, erreurs, avertissements, informations, debogage)",
"Info, warnings, errors and fatal issues" : "Informations, avertissements, erreurs et erreurs fatales",
@@ -267,14 +266,13 @@ OC.L10N.register(
"Current password" : "Mot de passe actuel",
"New password" : "Nouveau mot de passe",
"Change password" : "Changer de mot de passe",
+ "Language" : "Langue",
+ "Help translate" : "Aidez à traduire",
"These are the web, desktop and mobile clients currently logged in to your ownCloud." : "Voici les clients web, de bureau et mobiles actuellement connectés à votre ownCloud.",
"Browser" : "Navigateur",
"Most recent activity" : "Activité la plus récente",
- "You've linked these devices." : "Vous avez liés ces périphériques.",
"Name" : "Nom",
- "A device password is a passcode that gives an app or device permissions to access your ownCloud account." : "Un mot de passe d'un appareil est un code d'accès qui donne à une application ou à un appareil les droits d'accès à votre compte ownCloud.",
- "Language" : "Langue",
- "Help translate" : "Aidez à traduire",
+ "Done" : "Terminé",
"Get the apps to sync your files" : "Obtenez les applications de synchronisation de vos fichiers",
"Desktop client" : "Client de bureau",
"Android app" : "Application Android",
diff --git a/settings/l10n/fr.json b/settings/l10n/fr.json
index dd781fde08c..3f2d630a254 100644
--- a/settings/l10n/fr.json
+++ b/settings/l10n/fr.json
@@ -116,7 +116,6 @@
"__language_name__" : "Français",
"Unlimited" : "Illimité",
"Personal info" : "Informations personnelles",
- "Devices" : "Appareils",
"Sync clients" : "Clients de synchronisation",
"Everything (fatal issues, errors, warnings, info, debug)" : "Tout (erreurs fatales, erreurs, avertissements, informations, debogage)",
"Info, warnings, errors and fatal issues" : "Informations, avertissements, erreurs et erreurs fatales",
@@ -265,14 +264,13 @@
"Current password" : "Mot de passe actuel",
"New password" : "Nouveau mot de passe",
"Change password" : "Changer de mot de passe",
+ "Language" : "Langue",
+ "Help translate" : "Aidez à traduire",
"These are the web, desktop and mobile clients currently logged in to your ownCloud." : "Voici les clients web, de bureau et mobiles actuellement connectés à votre ownCloud.",
"Browser" : "Navigateur",
"Most recent activity" : "Activité la plus récente",
- "You've linked these devices." : "Vous avez liés ces périphériques.",
"Name" : "Nom",
- "A device password is a passcode that gives an app or device permissions to access your ownCloud account." : "Un mot de passe d'un appareil est un code d'accès qui donne à une application ou à un appareil les droits d'accès à votre compte ownCloud.",
- "Language" : "Langue",
- "Help translate" : "Aidez à traduire",
+ "Done" : "Terminé",
"Get the apps to sync your files" : "Obtenez les applications de synchronisation de vos fichiers",
"Desktop client" : "Client de bureau",
"Android app" : "Application Android",
diff --git a/settings/l10n/gl.js b/settings/l10n/gl.js
index 205405dc1cb..2d8d5de8fe4 100644
--- a/settings/l10n/gl.js
+++ b/settings/l10n/gl.js
@@ -223,9 +223,10 @@ OC.L10N.register(
"Current password" : "Contrasinal actual",
"New password" : "Novo contrasinal",
"Change password" : "Cambiar o contrasinal",
- "Name" : "Nome",
"Language" : "Idioma",
"Help translate" : "Axude na tradución",
+ "Name" : "Nome",
+ "Done" : "Feito",
"Get the apps to sync your files" : "Obteña as aplicacións para sincronizar os seus ficheiros",
"Desktop client" : "Cliente de escritorio",
"Android app" : "Aplicación Android",
diff --git a/settings/l10n/gl.json b/settings/l10n/gl.json
index 904cca1bb5d..bad67ea9dc0 100644
--- a/settings/l10n/gl.json
+++ b/settings/l10n/gl.json
@@ -221,9 +221,10 @@
"Current password" : "Contrasinal actual",
"New password" : "Novo contrasinal",
"Change password" : "Cambiar o contrasinal",
- "Name" : "Nome",
"Language" : "Idioma",
"Help translate" : "Axude na tradución",
+ "Name" : "Nome",
+ "Done" : "Feito",
"Get the apps to sync your files" : "Obteña as aplicacións para sincronizar os seus ficheiros",
"Desktop client" : "Cliente de escritorio",
"Android app" : "Aplicación Android",
diff --git a/settings/l10n/he.js b/settings/l10n/he.js
index 1b6d4616cbf..5f817228785 100644
--- a/settings/l10n/he.js
+++ b/settings/l10n/he.js
@@ -119,7 +119,7 @@ OC.L10N.register(
"Unlimited" : "ללא הגבלה",
"Personal info" : "מידע אישי",
"Sessions" : "שיחות",
- "Devices" : "התקנים",
+ "App passwords" : "סיסמת יישום",
"Sync clients" : "סנכרון לקוחות",
"Everything (fatal issues, errors, warnings, info, debug)" : "הכול (נושאים חמורים, שגיאות, אזהרות, מידע, ניפוי שגיאות)",
"Info, warnings, errors and fatal issues" : "מידע, אזהרות, שגיאות ונושאים חמורים",
@@ -269,14 +269,17 @@ OC.L10N.register(
"Current password" : "סיסמא נוכחית",
"New password" : "סיסמא חדשה",
"Change password" : "שינוי סיסמא",
+ "Language" : "שפה",
+ "Help translate" : "עזרה בתרגום",
"These are the web, desktop and mobile clients currently logged in to your ownCloud." : "אלו הם לקוחות האינטרנט, המחשב השולחני והטלפון שכרגע מחוברים ל- ownCloud שלך.",
"Browser" : "דפדפן",
"Most recent activity" : "פעילות אחרונה",
- "You've linked these devices." : "ההתקנים האלו קושרו.",
+ "You've linked these apps." : "יישומים אלו כבר קושרו על ידך.",
"Name" : "שם",
- "A device password is a passcode that gives an app or device permissions to access your ownCloud account." : "סיסמת התקן הנה קוד סודי שמאפשר הרשאות ליישום או התקן להכנס לחשבון ה- ownCloud שלך.",
- "Language" : "שפה",
- "Help translate" : "עזרה בתרגום",
+ "An app password is a passcode that gives an app or device permissions to access your %s account." : "סיסמת יישום הנה קוד סיסמא שמאפשרת ליישום או התקן הרשאות גישה לחשבון %s שלך.",
+ "App name" : "שם יישום",
+ "Create new app password" : "יצירת סיסמת יישום חדשה",
+ "Done" : "הסתיים",
"Get the apps to sync your files" : "קבלת היישומים לסנכרון הקבצים שלך",
"Desktop client" : "מחשב אישי",
"Android app" : "יישום אנדרואיד",
diff --git a/settings/l10n/he.json b/settings/l10n/he.json
index ae81455336c..ebf865f7447 100644
--- a/settings/l10n/he.json
+++ b/settings/l10n/he.json
@@ -117,7 +117,7 @@
"Unlimited" : "ללא הגבלה",
"Personal info" : "מידע אישי",
"Sessions" : "שיחות",
- "Devices" : "התקנים",
+ "App passwords" : "סיסמת יישום",
"Sync clients" : "סנכרון לקוחות",
"Everything (fatal issues, errors, warnings, info, debug)" : "הכול (נושאים חמורים, שגיאות, אזהרות, מידע, ניפוי שגיאות)",
"Info, warnings, errors and fatal issues" : "מידע, אזהרות, שגיאות ונושאים חמורים",
@@ -267,14 +267,17 @@
"Current password" : "סיסמא נוכחית",
"New password" : "סיסמא חדשה",
"Change password" : "שינוי סיסמא",
+ "Language" : "שפה",
+ "Help translate" : "עזרה בתרגום",
"These are the web, desktop and mobile clients currently logged in to your ownCloud." : "אלו הם לקוחות האינטרנט, המחשב השולחני והטלפון שכרגע מחוברים ל- ownCloud שלך.",
"Browser" : "דפדפן",
"Most recent activity" : "פעילות אחרונה",
- "You've linked these devices." : "ההתקנים האלו קושרו.",
+ "You've linked these apps." : "יישומים אלו כבר קושרו על ידך.",
"Name" : "שם",
- "A device password is a passcode that gives an app or device permissions to access your ownCloud account." : "סיסמת התקן הנה קוד סודי שמאפשר הרשאות ליישום או התקן להכנס לחשבון ה- ownCloud שלך.",
- "Language" : "שפה",
- "Help translate" : "עזרה בתרגום",
+ "An app password is a passcode that gives an app or device permissions to access your %s account." : "סיסמת יישום הנה קוד סיסמא שמאפשרת ליישום או התקן הרשאות גישה לחשבון %s שלך.",
+ "App name" : "שם יישום",
+ "Create new app password" : "יצירת סיסמת יישום חדשה",
+ "Done" : "הסתיים",
"Get the apps to sync your files" : "קבלת היישומים לסנכרון הקבצים שלך",
"Desktop client" : "מחשב אישי",
"Android app" : "יישום אנדרואיד",
diff --git a/settings/l10n/hr.js b/settings/l10n/hr.js
index 653b9c540ba..04562940a61 100644
--- a/settings/l10n/hr.js
+++ b/settings/l10n/hr.js
@@ -132,9 +132,9 @@ OC.L10N.register(
"Current password" : "Trenutna lozinka",
"New password" : "Nova lozinka",
"Change password" : "Promijenite lozinku",
- "Name" : "Naziv",
"Language" : "Jezik",
"Help translate" : "Pomozite prevesti",
+ "Name" : "Naziv",
"Get the apps to sync your files" : "Koristite aplikacije za sinkronizaciju svojih datoteka",
"Show First Run Wizard again" : "Opet pokažite First Run Wizard",
"Show storage location" : "Prikaži mjesto pohrane",
diff --git a/settings/l10n/hr.json b/settings/l10n/hr.json
index d663d9152a7..0d06db8f80a 100644
--- a/settings/l10n/hr.json
+++ b/settings/l10n/hr.json
@@ -130,9 +130,9 @@
"Current password" : "Trenutna lozinka",
"New password" : "Nova lozinka",
"Change password" : "Promijenite lozinku",
- "Name" : "Naziv",
"Language" : "Jezik",
"Help translate" : "Pomozite prevesti",
+ "Name" : "Naziv",
"Get the apps to sync your files" : "Koristite aplikacije za sinkronizaciju svojih datoteka",
"Show First Run Wizard again" : "Opet pokažite First Run Wizard",
"Show storage location" : "Prikaži mjesto pohrane",
diff --git a/settings/l10n/hu_HU.js b/settings/l10n/hu_HU.js
index 3f339c6238f..a4b072beb00 100644
--- a/settings/l10n/hu_HU.js
+++ b/settings/l10n/hu_HU.js
@@ -258,9 +258,10 @@ OC.L10N.register(
"Current password" : "A jelenlegi jelszó",
"New password" : "Az új jelszó",
"Change password" : "A jelszó megváltoztatása",
- "Name" : "Név",
"Language" : "Nyelv",
"Help translate" : "Segítsen a fordításban!",
+ "Name" : "Név",
+ "Done" : "Kész",
"Get the apps to sync your files" : "Töltse le az állományok szinkronizációjához szükséges programokat!",
"Desktop client" : "Asztali kliens",
"Android app" : "Android applikáció",
diff --git a/settings/l10n/hu_HU.json b/settings/l10n/hu_HU.json
index d02ce1be6b9..f46eb68a9e0 100644
--- a/settings/l10n/hu_HU.json
+++ b/settings/l10n/hu_HU.json
@@ -256,9 +256,10 @@
"Current password" : "A jelenlegi jelszó",
"New password" : "Az új jelszó",
"Change password" : "A jelszó megváltoztatása",
- "Name" : "Név",
"Language" : "Nyelv",
"Help translate" : "Segítsen a fordításban!",
+ "Name" : "Név",
+ "Done" : "Kész",
"Get the apps to sync your files" : "Töltse le az állományok szinkronizációjához szükséges programokat!",
"Desktop client" : "Asztali kliens",
"Android app" : "Android applikáció",
diff --git a/settings/l10n/hy.js b/settings/l10n/hy.js
index 16e00699138..cef2aec5e7f 100644
--- a/settings/l10n/hy.js
+++ b/settings/l10n/hy.js
@@ -23,9 +23,9 @@ OC.L10N.register(
"Password" : "Գաղտնաբառ",
"New password" : "Նոր գաղտնաբառ",
"Change password" : "Փոխել գաղտնաբառը",
- "Name" : "Անուն",
"Language" : "Լեզու",
"Help translate" : "Օգնել թարգմանել",
+ "Name" : "Անուն",
"Username" : "Օգտանուն",
"Group" : "Խումբ",
"Other" : "Այլ"
diff --git a/settings/l10n/hy.json b/settings/l10n/hy.json
index 50db186ef45..90f9363d450 100644
--- a/settings/l10n/hy.json
+++ b/settings/l10n/hy.json
@@ -21,9 +21,9 @@
"Password" : "Գաղտնաբառ",
"New password" : "Նոր գաղտնաբառ",
"Change password" : "Փոխել գաղտնաբառը",
- "Name" : "Անուն",
"Language" : "Լեզու",
"Help translate" : "Օգնել թարգմանել",
+ "Name" : "Անուն",
"Username" : "Օգտանուն",
"Group" : "Խումբ",
"Other" : "Այլ"
diff --git a/settings/l10n/ia.js b/settings/l10n/ia.js
index d8ee2f7b95c..e772ddb4e68 100644
--- a/settings/l10n/ia.js
+++ b/settings/l10n/ia.js
@@ -29,9 +29,9 @@ OC.L10N.register(
"Current password" : "Contrasigno currente",
"New password" : "Nove contrasigno",
"Change password" : "Cambiar contrasigno",
- "Name" : "Nomine",
"Language" : "Linguage",
"Help translate" : "Adjuta a traducer",
+ "Name" : "Nomine",
"Get the apps to sync your files" : "Obtene le apps (applicationes) pro synchronizar tu files",
"Username" : "Nomine de usator",
"Create" : "Crear",
diff --git a/settings/l10n/ia.json b/settings/l10n/ia.json
index 5d8b38f65be..d8b66b08779 100644
--- a/settings/l10n/ia.json
+++ b/settings/l10n/ia.json
@@ -27,9 +27,9 @@
"Current password" : "Contrasigno currente",
"New password" : "Nove contrasigno",
"Change password" : "Cambiar contrasigno",
- "Name" : "Nomine",
"Language" : "Linguage",
"Help translate" : "Adjuta a traducer",
+ "Name" : "Nomine",
"Get the apps to sync your files" : "Obtene le apps (applicationes) pro synchronizar tu files",
"Username" : "Nomine de usator",
"Create" : "Crear",
diff --git a/settings/l10n/id.js b/settings/l10n/id.js
index 6abd73732b0..df28898b884 100644
--- a/settings/l10n/id.js
+++ b/settings/l10n/id.js
@@ -233,9 +233,10 @@ OC.L10N.register(
"Current password" : "Sandi saat ini",
"New password" : "Sandi baru",
"Change password" : "Ubah sandi",
- "Name" : "Nama",
"Language" : "Bahasa",
"Help translate" : "Bantu menerjemahkan",
+ "Name" : "Nama",
+ "Done" : "Selesai",
"Get the apps to sync your files" : "Dapatkan aplikasi untuk sinkronisasi berkas Anda",
"Desktop client" : "Klien desktop",
"Android app" : "Aplikasi Android",
diff --git a/settings/l10n/id.json b/settings/l10n/id.json
index 502697cc8b1..401e2f83c3c 100644
--- a/settings/l10n/id.json
+++ b/settings/l10n/id.json
@@ -231,9 +231,10 @@
"Current password" : "Sandi saat ini",
"New password" : "Sandi baru",
"Change password" : "Ubah sandi",
- "Name" : "Nama",
"Language" : "Bahasa",
"Help translate" : "Bantu menerjemahkan",
+ "Name" : "Nama",
+ "Done" : "Selesai",
"Get the apps to sync your files" : "Dapatkan aplikasi untuk sinkronisasi berkas Anda",
"Desktop client" : "Klien desktop",
"Android app" : "Aplikasi Android",
diff --git a/settings/l10n/is.js b/settings/l10n/is.js
index 2a4c8cb4cb9..5a8239024cd 100644
--- a/settings/l10n/is.js
+++ b/settings/l10n/is.js
@@ -243,9 +243,9 @@ OC.L10N.register(
"Current password" : "Núverandi lykilorð",
"New password" : "Nýtt lykilorð",
"Change password" : "Breyta lykilorði",
- "Name" : "Heiti",
"Language" : "Tungumál",
"Help translate" : "Hjálpa við þýðingu",
+ "Name" : "Heiti",
"Get the apps to sync your files" : "Náðu í forrit til að samstilla skrárnar þínar",
"Desktop client" : "Skjáborðsforrit",
"Android app" : "Android-forrit",
diff --git a/settings/l10n/is.json b/settings/l10n/is.json
index d2cf8430267..1978017663b 100644
--- a/settings/l10n/is.json
+++ b/settings/l10n/is.json
@@ -241,9 +241,9 @@
"Current password" : "Núverandi lykilorð",
"New password" : "Nýtt lykilorð",
"Change password" : "Breyta lykilorði",
- "Name" : "Heiti",
"Language" : "Tungumál",
"Help translate" : "Hjálpa við þýðingu",
+ "Name" : "Heiti",
"Get the apps to sync your files" : "Náðu í forrit til að samstilla skrárnar þínar",
"Desktop client" : "Skjáborðsforrit",
"Android app" : "Android-forrit",
diff --git a/settings/l10n/it.js b/settings/l10n/it.js
index 265605305ce..4eebb5a5223 100644
--- a/settings/l10n/it.js
+++ b/settings/l10n/it.js
@@ -119,7 +119,7 @@ OC.L10N.register(
"Unlimited" : "Illimitata",
"Personal info" : "Informazioni personali",
"Sessions" : "Sessioni",
- "Devices" : "Dispositivi",
+ "App passwords" : "Password di applicazione",
"Sync clients" : "Client di sincronizzazione",
"Everything (fatal issues, errors, warnings, info, debug)" : "Tutto (problemi gravi, errori, avvisi, informazioni, debug)",
"Info, warnings, errors and fatal issues" : "Informazioni, avvisi, errori e problemi gravi",
@@ -269,14 +269,17 @@ OC.L10N.register(
"Current password" : "Password attuale",
"New password" : "Nuova password",
"Change password" : "Modifica password",
+ "Language" : "Lingua",
+ "Help translate" : "Migliora la traduzione",
"These are the web, desktop and mobile clients currently logged in to your ownCloud." : "Questi sono i client web, desktop e mobile che hanno effettuato attualmente l'accesso al tuo ownCloud.",
"Browser" : "Browser",
"Most recent activity" : "Attività più recenti",
- "You've linked these devices." : "Hai collegato questi dispositivi.",
+ "You've linked these apps." : "Hai collegato queste applicazioni.",
"Name" : "Nome",
- "A device password is a passcode that gives an app or device permissions to access your ownCloud account." : "Una password di dispositivo è un codice di sicurezza che fornisce a un'applicazione o a un dispositivo i permessi per accedere al tuo account ownCloud.",
- "Language" : "Lingua",
- "Help translate" : "Migliora la traduzione",
+ "An app password is a passcode that gives an app or device permissions to access your %s account." : "Una password di applicazione è un codice di sicurezza che fornisce a un'applicazione o a un dispositivo i permessi per accedere al tuo account %s.",
+ "App name" : "Nome applicazione",
+ "Create new app password" : "Crea nuova password di applicazione",
+ "Done" : "Completato",
"Get the apps to sync your files" : "Scarica le applicazioni per sincronizzare i tuoi file",
"Desktop client" : "Client desktop",
"Android app" : "Applicazione Android",
diff --git a/settings/l10n/it.json b/settings/l10n/it.json
index 1c689f921c9..d41cd92359f 100644
--- a/settings/l10n/it.json
+++ b/settings/l10n/it.json
@@ -117,7 +117,7 @@
"Unlimited" : "Illimitata",
"Personal info" : "Informazioni personali",
"Sessions" : "Sessioni",
- "Devices" : "Dispositivi",
+ "App passwords" : "Password di applicazione",
"Sync clients" : "Client di sincronizzazione",
"Everything (fatal issues, errors, warnings, info, debug)" : "Tutto (problemi gravi, errori, avvisi, informazioni, debug)",
"Info, warnings, errors and fatal issues" : "Informazioni, avvisi, errori e problemi gravi",
@@ -267,14 +267,17 @@
"Current password" : "Password attuale",
"New password" : "Nuova password",
"Change password" : "Modifica password",
+ "Language" : "Lingua",
+ "Help translate" : "Migliora la traduzione",
"These are the web, desktop and mobile clients currently logged in to your ownCloud." : "Questi sono i client web, desktop e mobile che hanno effettuato attualmente l'accesso al tuo ownCloud.",
"Browser" : "Browser",
"Most recent activity" : "Attività più recenti",
- "You've linked these devices." : "Hai collegato questi dispositivi.",
+ "You've linked these apps." : "Hai collegato queste applicazioni.",
"Name" : "Nome",
- "A device password is a passcode that gives an app or device permissions to access your ownCloud account." : "Una password di dispositivo è un codice di sicurezza che fornisce a un'applicazione o a un dispositivo i permessi per accedere al tuo account ownCloud.",
- "Language" : "Lingua",
- "Help translate" : "Migliora la traduzione",
+ "An app password is a passcode that gives an app or device permissions to access your %s account." : "Una password di applicazione è un codice di sicurezza che fornisce a un'applicazione o a un dispositivo i permessi per accedere al tuo account %s.",
+ "App name" : "Nome applicazione",
+ "Create new app password" : "Crea nuova password di applicazione",
+ "Done" : "Completato",
"Get the apps to sync your files" : "Scarica le applicazioni per sincronizzare i tuoi file",
"Desktop client" : "Client desktop",
"Android app" : "Applicazione Android",
diff --git a/settings/l10n/ja.js b/settings/l10n/ja.js
index f294ac64f97..a466d7cf225 100644
--- a/settings/l10n/ja.js
+++ b/settings/l10n/ja.js
@@ -119,7 +119,6 @@ OC.L10N.register(
"Unlimited" : "無制限",
"Personal info" : "個人情報",
"Sessions" : "セッション",
- "Devices" : "デバイス",
"Sync clients" : "同期用クライアント",
"Everything (fatal issues, errors, warnings, info, debug)" : "すべて (致命的な問題、エラー、警告、情報、デバッグ)",
"Info, warnings, errors and fatal issues" : "情報、警告、エラー、致命的な問題",
@@ -267,13 +266,12 @@ OC.L10N.register(
"Current password" : "現在のパスワード",
"New password" : "新しいパスワード",
"Change password" : "パスワードを変更",
+ "Language" : "言語",
+ "Help translate" : "翻訳に協力する",
"Browser" : "ブラウザ",
"Most recent activity" : "最新のアクティビティ",
- "You've linked these devices." : "以下のデバイスをリンクしました。",
"Name" : "名前",
- "A device password is a passcode that gives an app or device permissions to access your ownCloud account." : "デバイスパスワードはownCloudアカウントがアプリにアクセスするためアクセス許可をデバイスに与えるパスコードです。",
- "Language" : "言語",
- "Help translate" : "翻訳に協力する",
+ "Done" : "完了",
"Get the apps to sync your files" : "ファイルを同期するアプリを取得しましょう",
"Desktop client" : "デスクトップクライアント",
"Android app" : "Androidアプリ",
diff --git a/settings/l10n/ja.json b/settings/l10n/ja.json
index 43b0d8a7da8..94cab8e2ed5 100644
--- a/settings/l10n/ja.json
+++ b/settings/l10n/ja.json
@@ -117,7 +117,6 @@
"Unlimited" : "無制限",
"Personal info" : "個人情報",
"Sessions" : "セッション",
- "Devices" : "デバイス",
"Sync clients" : "同期用クライアント",
"Everything (fatal issues, errors, warnings, info, debug)" : "すべて (致命的な問題、エラー、警告、情報、デバッグ)",
"Info, warnings, errors and fatal issues" : "情報、警告、エラー、致命的な問題",
@@ -265,13 +264,12 @@
"Current password" : "現在のパスワード",
"New password" : "新しいパスワード",
"Change password" : "パスワードを変更",
+ "Language" : "言語",
+ "Help translate" : "翻訳に協力する",
"Browser" : "ブラウザ",
"Most recent activity" : "最新のアクティビティ",
- "You've linked these devices." : "以下のデバイスをリンクしました。",
"Name" : "名前",
- "A device password is a passcode that gives an app or device permissions to access your ownCloud account." : "デバイスパスワードはownCloudアカウントがアプリにアクセスするためアクセス許可をデバイスに与えるパスコードです。",
- "Language" : "言語",
- "Help translate" : "翻訳に協力する",
+ "Done" : "完了",
"Get the apps to sync your files" : "ファイルを同期するアプリを取得しましょう",
"Desktop client" : "デスクトップクライアント",
"Android app" : "Androidアプリ",
diff --git a/settings/l10n/ka_GE.js b/settings/l10n/ka_GE.js
index 41f84a34d7a..2d125ba7995 100644
--- a/settings/l10n/ka_GE.js
+++ b/settings/l10n/ka_GE.js
@@ -52,9 +52,9 @@ OC.L10N.register(
"Current password" : "მიმდინარე პაროლი",
"New password" : "ახალი პაროლი",
"Change password" : "პაროლის შეცვლა",
- "Name" : "სახელი",
"Language" : "ენა",
"Help translate" : "თარგმნის დახმარება",
+ "Name" : "სახელი",
"Get the apps to sync your files" : "აპლიკაცია ფაილების სინქრონიზაციისთვის",
"Show First Run Wizard again" : "მაჩვენე თავიდან გაშვებული ვიზარდი",
"Username" : "მომხმარებლის სახელი",
diff --git a/settings/l10n/ka_GE.json b/settings/l10n/ka_GE.json
index 87ead0dce93..11fb2e4e2d4 100644
--- a/settings/l10n/ka_GE.json
+++ b/settings/l10n/ka_GE.json
@@ -50,9 +50,9 @@
"Current password" : "მიმდინარე პაროლი",
"New password" : "ახალი პაროლი",
"Change password" : "პაროლის შეცვლა",
- "Name" : "სახელი",
"Language" : "ენა",
"Help translate" : "თარგმნის დახმარება",
+ "Name" : "სახელი",
"Get the apps to sync your files" : "აპლიკაცია ფაილების სინქრონიზაციისთვის",
"Show First Run Wizard again" : "მაჩვენე თავიდან გაშვებული ვიზარდი",
"Username" : "მომხმარებლის სახელი",
diff --git a/settings/l10n/km.js b/settings/l10n/km.js
index b4222fb7142..a5de441b361 100644
--- a/settings/l10n/km.js
+++ b/settings/l10n/km.js
@@ -71,9 +71,9 @@ OC.L10N.register(
"Current password" : "ពាក្យសម្ងាត់​បច្ចុប្បន្ន",
"New password" : "ពាក្យ​សម្ងាត់​ថ្មី",
"Change password" : "ប្តូរ​ពាក្យសម្ងាត់",
- "Name" : "ឈ្មោះ",
"Language" : "ភាសា",
"Help translate" : "ជួយ​បក​ប្រែ",
+ "Name" : "ឈ្មោះ",
"Get the apps to sync your files" : "ដាក់​អោយកម្មវិធីផ្សេងៗ ​ធ្វើសមកាលកម្ម​ឯកសារ​អ្នក",
"Show First Run Wizard again" : "បង្ហាញ First Run Wizard ម្តង​ទៀត",
"Username" : "ឈ្មោះ​អ្នកប្រើ",
diff --git a/settings/l10n/km.json b/settings/l10n/km.json
index 4fa00aca327..91f83037b42 100644
--- a/settings/l10n/km.json
+++ b/settings/l10n/km.json
@@ -69,9 +69,9 @@
"Current password" : "ពាក្យសម្ងាត់​បច្ចុប្បន្ន",
"New password" : "ពាក្យ​សម្ងាត់​ថ្មី",
"Change password" : "ប្តូរ​ពាក្យសម្ងាត់",
- "Name" : "ឈ្មោះ",
"Language" : "ភាសា",
"Help translate" : "ជួយ​បក​ប្រែ",
+ "Name" : "ឈ្មោះ",
"Get the apps to sync your files" : "ដាក់​អោយកម្មវិធីផ្សេងៗ ​ធ្វើសមកាលកម្ម​ឯកសារ​អ្នក",
"Show First Run Wizard again" : "បង្ហាញ First Run Wizard ម្តង​ទៀត",
"Username" : "ឈ្មោះ​អ្នកប្រើ",
diff --git a/settings/l10n/kn.js b/settings/l10n/kn.js
index de48a66a0d3..987cb134912 100644
--- a/settings/l10n/kn.js
+++ b/settings/l10n/kn.js
@@ -106,9 +106,9 @@ OC.L10N.register(
"Current password" : "ಪ್ರಸ್ತುತ ಗುಪ್ತಪದ",
"New password" : "ಹೊಸ ಗುಪ್ತಪದ",
"Change password" : "ಗುಪ್ತ ಪದವನ್ನು ಬದಲಾಯಿಸಿ",
- "Name" : "ಹೆಸರು",
"Language" : "ಭಾಷೆ",
"Help translate" : "ಭಾಷಾಂತರಿಸಲು ಸಹಾಯ ಮಾಡಿ",
+ "Name" : "ಹೆಸರು",
"Username" : "ಬಳಕೆಯ ಹೆಸರು",
"E-Mail" : "ಇ-ಅಂಚೆ ವಿಳಾಸ",
"Create" : "ಸೃಷ್ಟಿಸಿ",
diff --git a/settings/l10n/kn.json b/settings/l10n/kn.json
index 2422d0ee688..91d08cc0b5d 100644
--- a/settings/l10n/kn.json
+++ b/settings/l10n/kn.json
@@ -104,9 +104,9 @@
"Current password" : "ಪ್ರಸ್ತುತ ಗುಪ್ತಪದ",
"New password" : "ಹೊಸ ಗುಪ್ತಪದ",
"Change password" : "ಗುಪ್ತ ಪದವನ್ನು ಬದಲಾಯಿಸಿ",
- "Name" : "ಹೆಸರು",
"Language" : "ಭಾಷೆ",
"Help translate" : "ಭಾಷಾಂತರಿಸಲು ಸಹಾಯ ಮಾಡಿ",
+ "Name" : "ಹೆಸರು",
"Username" : "ಬಳಕೆಯ ಹೆಸರು",
"E-Mail" : "ಇ-ಅಂಚೆ ವಿಳಾಸ",
"Create" : "ಸೃಷ್ಟಿಸಿ",
diff --git a/settings/l10n/ko.js b/settings/l10n/ko.js
index 0f29e5b9db1..6a0d74e43a0 100644
--- a/settings/l10n/ko.js
+++ b/settings/l10n/ko.js
@@ -250,9 +250,10 @@ OC.L10N.register(
"Current password" : "현재 암호",
"New password" : "새 암호",
"Change password" : "암호 변경",
- "Name" : "이름",
"Language" : "언어",
"Help translate" : "번역 돕기",
+ "Name" : "이름",
+ "Done" : "완료",
"Get the apps to sync your files" : "파일 동기화 앱 가져오기",
"Desktop client" : "데스크톱 클라이언트",
"Android app" : "Android 앱",
diff --git a/settings/l10n/ko.json b/settings/l10n/ko.json
index 41a7173ebf7..81efe3a01ee 100644
--- a/settings/l10n/ko.json
+++ b/settings/l10n/ko.json
@@ -248,9 +248,10 @@
"Current password" : "현재 암호",
"New password" : "새 암호",
"Change password" : "암호 변경",
- "Name" : "이름",
"Language" : "언어",
"Help translate" : "번역 돕기",
+ "Name" : "이름",
+ "Done" : "완료",
"Get the apps to sync your files" : "파일 동기화 앱 가져오기",
"Desktop client" : "데스크톱 클라이언트",
"Android app" : "Android 앱",
diff --git a/settings/l10n/lb.js b/settings/l10n/lb.js
index 0668fddcf76..42376d82028 100644
--- a/settings/l10n/lb.js
+++ b/settings/l10n/lb.js
@@ -9,8 +9,10 @@ OC.L10N.register(
"Email saved" : "E-mail gespäichert",
"APCu" : "APCu",
"Redis" : "Redis",
+ "Sharing" : "Gedeelt",
"Cron" : "Cron",
"Log" : "Log",
+ "Updates" : "Updates",
"Language changed" : "Sprooch huet geännert",
"Invalid request" : "Ongülteg Requête",
"Admins can't remove themself from the admin group" : "Admins kennen sech selwer net aus enger Admin Group läschen.",
@@ -23,9 +25,11 @@ OC.L10N.register(
"undo" : "réckgängeg man",
"never" : "ni",
"__language_name__" : "__language_name__",
+ "None" : "Keng",
"Login" : "Login",
"Open documentation" : "Dokumentatioun opmaachen",
"Allow apps to use the Share API" : "Erlab Apps d'Share API ze benotzen",
+ "days" : "Deeg",
"Allow resharing" : "Resharing erlaben",
"Authentication required" : "Authentifizéierung néideg",
"Server address" : "Server Adress",
@@ -41,13 +45,14 @@ OC.L10N.register(
"Current password" : "Momentan 't Passwuert",
"New password" : "Neit Passwuert",
"Change password" : "Passwuert änneren",
- "Name" : "Numm",
"Language" : "Sprooch",
"Help translate" : "Hëllef iwwersetzen",
+ "Name" : "Numm",
"Desktop client" : "Desktop-Programm",
"Android app" : "Android-App",
"iOS app" : "iOS-App",
"Username" : "Benotzernumm",
+ "E-Mail" : "E-Mail",
"Create" : "Erstellen",
"Group" : "Grupp",
"Default Quota" : "Standard Quota",
diff --git a/settings/l10n/lb.json b/settings/l10n/lb.json
index 20500fbc909..d59f06cf834 100644
--- a/settings/l10n/lb.json
+++ b/settings/l10n/lb.json
@@ -7,8 +7,10 @@
"Email saved" : "E-mail gespäichert",
"APCu" : "APCu",
"Redis" : "Redis",
+ "Sharing" : "Gedeelt",
"Cron" : "Cron",
"Log" : "Log",
+ "Updates" : "Updates",
"Language changed" : "Sprooch huet geännert",
"Invalid request" : "Ongülteg Requête",
"Admins can't remove themself from the admin group" : "Admins kennen sech selwer net aus enger Admin Group läschen.",
@@ -21,9 +23,11 @@
"undo" : "réckgängeg man",
"never" : "ni",
"__language_name__" : "__language_name__",
+ "None" : "Keng",
"Login" : "Login",
"Open documentation" : "Dokumentatioun opmaachen",
"Allow apps to use the Share API" : "Erlab Apps d'Share API ze benotzen",
+ "days" : "Deeg",
"Allow resharing" : "Resharing erlaben",
"Authentication required" : "Authentifizéierung néideg",
"Server address" : "Server Adress",
@@ -39,13 +43,14 @@
"Current password" : "Momentan 't Passwuert",
"New password" : "Neit Passwuert",
"Change password" : "Passwuert änneren",
- "Name" : "Numm",
"Language" : "Sprooch",
"Help translate" : "Hëllef iwwersetzen",
+ "Name" : "Numm",
"Desktop client" : "Desktop-Programm",
"Android app" : "Android-App",
"iOS app" : "iOS-App",
"Username" : "Benotzernumm",
+ "E-Mail" : "E-Mail",
"Create" : "Erstellen",
"Group" : "Grupp",
"Default Quota" : "Standard Quota",
diff --git a/settings/l10n/lt_LT.js b/settings/l10n/lt_LT.js
index 83c2e715dc6..18b71c4553c 100644
--- a/settings/l10n/lt_LT.js
+++ b/settings/l10n/lt_LT.js
@@ -92,9 +92,9 @@ OC.L10N.register(
"Current password" : "Dabartinis slaptažodis",
"New password" : "Naujas slaptažodis",
"Change password" : "Pakeisti slaptažodį",
- "Name" : "Pavadinimas",
"Language" : "Kalba",
"Help translate" : "Padėkite išversti",
+ "Name" : "Pavadinimas",
"Get the apps to sync your files" : "Atsisiųskite programėlių, kad sinchronizuotumėte savo failus",
"Desktop client" : "Darbastalio klientas",
"Android app" : "Android programa",
diff --git a/settings/l10n/lt_LT.json b/settings/l10n/lt_LT.json
index 3cbf7c8bc98..4a26ae8bf75 100644
--- a/settings/l10n/lt_LT.json
+++ b/settings/l10n/lt_LT.json
@@ -90,9 +90,9 @@
"Current password" : "Dabartinis slaptažodis",
"New password" : "Naujas slaptažodis",
"Change password" : "Pakeisti slaptažodį",
- "Name" : "Pavadinimas",
"Language" : "Kalba",
"Help translate" : "Padėkite išversti",
+ "Name" : "Pavadinimas",
"Get the apps to sync your files" : "Atsisiųskite programėlių, kad sinchronizuotumėte savo failus",
"Desktop client" : "Darbastalio klientas",
"Android app" : "Android programa",
diff --git a/settings/l10n/lv.js b/settings/l10n/lv.js
index 373468f4868..a24704e6360 100644
--- a/settings/l10n/lv.js
+++ b/settings/l10n/lv.js
@@ -134,9 +134,10 @@ OC.L10N.register(
"Current password" : "Pašreizējā parole",
"New password" : "Jauna parole",
"Change password" : "Mainīt paroli",
- "Name" : "Nosaukums",
"Language" : "Valoda",
"Help translate" : "Palīdzi tulkot",
+ "Name" : "Nosaukums",
+ "Done" : "Pabeigts",
"Get the apps to sync your files" : "Saņem lietotnes, lai sinhronizētu savas datnes",
"Desktop client" : "Darbvirsmas klients",
"Android app" : "Android lietotne",
diff --git a/settings/l10n/lv.json b/settings/l10n/lv.json
index 4cd06ecd22a..990d454f2a0 100644
--- a/settings/l10n/lv.json
+++ b/settings/l10n/lv.json
@@ -132,9 +132,10 @@
"Current password" : "Pašreizējā parole",
"New password" : "Jauna parole",
"Change password" : "Mainīt paroli",
- "Name" : "Nosaukums",
"Language" : "Valoda",
"Help translate" : "Palīdzi tulkot",
+ "Name" : "Nosaukums",
+ "Done" : "Pabeigts",
"Get the apps to sync your files" : "Saņem lietotnes, lai sinhronizētu savas datnes",
"Desktop client" : "Darbvirsmas klients",
"Android app" : "Android lietotne",
diff --git a/settings/l10n/mk.js b/settings/l10n/mk.js
index e184f72513b..088ea3c14db 100644
--- a/settings/l10n/mk.js
+++ b/settings/l10n/mk.js
@@ -165,9 +165,9 @@ OC.L10N.register(
"Current password" : "Моментална лозинка",
"New password" : "Нова лозинка",
"Change password" : "Смени лозинка",
- "Name" : "Име",
"Language" : "Јазик",
"Help translate" : "Помогни во преводот",
+ "Name" : "Име",
"Get the apps to sync your files" : "Преземете апликации за синхронизирање на вашите датотеки",
"Show First Run Wizard again" : "Прикажи го повторно волшебникот при првото стартување",
"Username" : "Корисничко име",
diff --git a/settings/l10n/mk.json b/settings/l10n/mk.json
index ce7291c9cb1..169f2328fbe 100644
--- a/settings/l10n/mk.json
+++ b/settings/l10n/mk.json
@@ -163,9 +163,9 @@
"Current password" : "Моментална лозинка",
"New password" : "Нова лозинка",
"Change password" : "Смени лозинка",
- "Name" : "Име",
"Language" : "Јазик",
"Help translate" : "Помогни во преводот",
+ "Name" : "Име",
"Get the apps to sync your files" : "Преземете апликации за синхронизирање на вашите датотеки",
"Show First Run Wizard again" : "Прикажи го повторно волшебникот при првото стартување",
"Username" : "Корисничко име",
diff --git a/settings/l10n/mn.js b/settings/l10n/mn.js
index cea2aa37c9a..0bc562b05f7 100644
--- a/settings/l10n/mn.js
+++ b/settings/l10n/mn.js
@@ -15,6 +15,7 @@ OC.L10N.register(
"All" : "Бүгд",
"Email" : "И-мэйл",
"Password" : "Нууц үг",
+ "Done" : "Болсон",
"Username" : "Хэрэглэгчийн нэр"
},
"nplurals=2; plural=(n != 1);");
diff --git a/settings/l10n/mn.json b/settings/l10n/mn.json
index 8e834210fb5..1f888f6ef2d 100644
--- a/settings/l10n/mn.json
+++ b/settings/l10n/mn.json
@@ -13,6 +13,7 @@
"All" : "Бүгд",
"Email" : "И-мэйл",
"Password" : "Нууц үг",
+ "Done" : "Болсон",
"Username" : "Хэрэглэгчийн нэр"
},"pluralForm" :"nplurals=2; plural=(n != 1);"
} \ No newline at end of file
diff --git a/settings/l10n/ms_MY.js b/settings/l10n/ms_MY.js
index e474482799b..87f26cef187 100644
--- a/settings/l10n/ms_MY.js
+++ b/settings/l10n/ms_MY.js
@@ -24,9 +24,9 @@ OC.L10N.register(
"Current password" : "Kata laluan semasa",
"New password" : "Kata laluan baru",
"Change password" : "Ubah kata laluan",
- "Name" : "Nama",
"Language" : "Bahasa",
"Help translate" : "Bantu terjemah",
+ "Name" : "Nama",
"Username" : "Nama pengguna",
"Create" : "Buat",
"Default Quota" : "Kuota Lalai",
diff --git a/settings/l10n/ms_MY.json b/settings/l10n/ms_MY.json
index 353c93059a2..3a48456243b 100644
--- a/settings/l10n/ms_MY.json
+++ b/settings/l10n/ms_MY.json
@@ -22,9 +22,9 @@
"Current password" : "Kata laluan semasa",
"New password" : "Kata laluan baru",
"Change password" : "Ubah kata laluan",
- "Name" : "Nama",
"Language" : "Bahasa",
"Help translate" : "Bantu terjemah",
+ "Name" : "Nama",
"Username" : "Nama pengguna",
"Create" : "Buat",
"Default Quota" : "Kuota Lalai",
diff --git a/settings/l10n/nb_NO.js b/settings/l10n/nb_NO.js
index 1f45d814963..8c4814d954d 100644
--- a/settings/l10n/nb_NO.js
+++ b/settings/l10n/nb_NO.js
@@ -259,9 +259,10 @@ OC.L10N.register(
"Current password" : "Nåværende passord",
"New password" : "Nytt passord",
"Change password" : "Endre passord",
- "Name" : "Navn",
"Language" : "Språk",
"Help translate" : "Bidra til oversettelsen",
+ "Name" : "Navn",
+ "Done" : "Ferdig",
"Get the apps to sync your files" : "Hent apper som synkroniserer filene dine",
"Desktop client" : "Skrivebordsklient",
"Android app" : "Android-app",
diff --git a/settings/l10n/nb_NO.json b/settings/l10n/nb_NO.json
index e8218be13e9..f5897d902e4 100644
--- a/settings/l10n/nb_NO.json
+++ b/settings/l10n/nb_NO.json
@@ -257,9 +257,10 @@
"Current password" : "Nåværende passord",
"New password" : "Nytt passord",
"Change password" : "Endre passord",
- "Name" : "Navn",
"Language" : "Språk",
"Help translate" : "Bidra til oversettelsen",
+ "Name" : "Navn",
+ "Done" : "Ferdig",
"Get the apps to sync your files" : "Hent apper som synkroniserer filene dine",
"Desktop client" : "Skrivebordsklient",
"Android app" : "Android-app",
diff --git a/settings/l10n/nl.js b/settings/l10n/nl.js
index 23a8052f114..8a46f240067 100644
--- a/settings/l10n/nl.js
+++ b/settings/l10n/nl.js
@@ -119,7 +119,6 @@ OC.L10N.register(
"Unlimited" : "Ongelimiteerd",
"Personal info" : "Persoonlijke info",
"Sessions" : "Sessies",
- "Devices" : "Apparaten",
"Sync clients" : "Sync clients",
"Everything (fatal issues, errors, warnings, info, debug)" : "Alles (fatale problemen, fouten, waarschuwingen, info, debug)",
"Info, warnings, errors and fatal issues" : "Info, waarschuwingen, fouten en fatale problemen",
@@ -269,14 +268,13 @@ OC.L10N.register(
"Current password" : "Huidig wachtwoord",
"New password" : "Nieuw",
"Change password" : "Wijzig wachtwoord",
+ "Language" : "Taal",
+ "Help translate" : "Help met vertalen",
"These are the web, desktop and mobile clients currently logged in to your ownCloud." : "Dit zijn de web, desktop en mobiele clients die momenteel zijn verbonden met uw ownCloud.",
"Browser" : "Browser",
"Most recent activity" : "Meest recente activiteit",
- "You've linked these devices." : "U hebt deze apparaten gekoppeld.",
"Name" : "Naam",
- "A device password is a passcode that gives an app or device permissions to access your ownCloud account." : "Een apparaat wachtwoord is een toegangscode waarmee een app of apparaat toegang krijgt om uw ownCloud account te gebruiken.",
- "Language" : "Taal",
- "Help translate" : "Help met vertalen",
+ "Done" : "Gedaan",
"Get the apps to sync your files" : "Download de apps om bestanden te synchroniseren",
"Desktop client" : "Desktop client",
"Android app" : "Android app",
diff --git a/settings/l10n/nl.json b/settings/l10n/nl.json
index edaa1313e0e..658b4c16690 100644
--- a/settings/l10n/nl.json
+++ b/settings/l10n/nl.json
@@ -117,7 +117,6 @@
"Unlimited" : "Ongelimiteerd",
"Personal info" : "Persoonlijke info",
"Sessions" : "Sessies",
- "Devices" : "Apparaten",
"Sync clients" : "Sync clients",
"Everything (fatal issues, errors, warnings, info, debug)" : "Alles (fatale problemen, fouten, waarschuwingen, info, debug)",
"Info, warnings, errors and fatal issues" : "Info, waarschuwingen, fouten en fatale problemen",
@@ -267,14 +266,13 @@
"Current password" : "Huidig wachtwoord",
"New password" : "Nieuw",
"Change password" : "Wijzig wachtwoord",
+ "Language" : "Taal",
+ "Help translate" : "Help met vertalen",
"These are the web, desktop and mobile clients currently logged in to your ownCloud." : "Dit zijn de web, desktop en mobiele clients die momenteel zijn verbonden met uw ownCloud.",
"Browser" : "Browser",
"Most recent activity" : "Meest recente activiteit",
- "You've linked these devices." : "U hebt deze apparaten gekoppeld.",
"Name" : "Naam",
- "A device password is a passcode that gives an app or device permissions to access your ownCloud account." : "Een apparaat wachtwoord is een toegangscode waarmee een app of apparaat toegang krijgt om uw ownCloud account te gebruiken.",
- "Language" : "Taal",
- "Help translate" : "Help met vertalen",
+ "Done" : "Gedaan",
"Get the apps to sync your files" : "Download de apps om bestanden te synchroniseren",
"Desktop client" : "Desktop client",
"Android app" : "Android app",
diff --git a/settings/l10n/nn_NO.js b/settings/l10n/nn_NO.js
index 9ab1943bf73..33421d700bd 100644
--- a/settings/l10n/nn_NO.js
+++ b/settings/l10n/nn_NO.js
@@ -65,9 +65,9 @@ OC.L10N.register(
"Current password" : "Passord",
"New password" : "Nytt passord",
"Change password" : "Endra passord",
- "Name" : "Namn",
"Language" : "Språk",
"Help translate" : "Hjelp oss å omsetja",
+ "Name" : "Namn",
"Get the apps to sync your files" : "Få app-ar som kan synkronisera filene dine",
"Show First Run Wizard again" : "Vis Oppstartvegvisaren igjen",
"Username" : "Brukarnamn",
diff --git a/settings/l10n/nn_NO.json b/settings/l10n/nn_NO.json
index 23f857d38ad..14b0aea0bcb 100644
--- a/settings/l10n/nn_NO.json
+++ b/settings/l10n/nn_NO.json
@@ -63,9 +63,9 @@
"Current password" : "Passord",
"New password" : "Nytt passord",
"Change password" : "Endra passord",
- "Name" : "Namn",
"Language" : "Språk",
"Help translate" : "Hjelp oss å omsetja",
+ "Name" : "Namn",
"Get the apps to sync your files" : "Få app-ar som kan synkronisera filene dine",
"Show First Run Wizard again" : "Vis Oppstartvegvisaren igjen",
"Username" : "Brukarnamn",
diff --git a/settings/l10n/oc.js b/settings/l10n/oc.js
index a349a258414..25ebdb6ab17 100644
--- a/settings/l10n/oc.js
+++ b/settings/l10n/oc.js
@@ -234,9 +234,9 @@ OC.L10N.register(
"Current password" : "Senhal actual",
"New password" : "Senhal novèl",
"Change password" : "Cambiar de senhal",
- "Name" : "Nom",
"Language" : "Lenga",
"Help translate" : "Ajudatz a tradusir",
+ "Name" : "Nom",
"Get the apps to sync your files" : "Obtenètz las aplicacions de sincronizacion de vòstres fichièrs",
"Desktop client" : "Client de burèu",
"Android app" : "Aplicacion Android",
diff --git a/settings/l10n/oc.json b/settings/l10n/oc.json
index 0b43042a397..2e51aff97eb 100644
--- a/settings/l10n/oc.json
+++ b/settings/l10n/oc.json
@@ -232,9 +232,9 @@
"Current password" : "Senhal actual",
"New password" : "Senhal novèl",
"Change password" : "Cambiar de senhal",
- "Name" : "Nom",
"Language" : "Lenga",
"Help translate" : "Ajudatz a tradusir",
+ "Name" : "Nom",
"Get the apps to sync your files" : "Obtenètz las aplicacions de sincronizacion de vòstres fichièrs",
"Desktop client" : "Client de burèu",
"Android app" : "Aplicacion Android",
diff --git a/settings/l10n/pl.js b/settings/l10n/pl.js
index 7278c7aa73b..903b374e0af 100644
--- a/settings/l10n/pl.js
+++ b/settings/l10n/pl.js
@@ -28,6 +28,7 @@ OC.L10N.register(
"Email saved" : "E-mail zapisany",
"Your full name has been changed." : "Twoja pełna nazwa została zmieniona.",
"Unable to change full name" : "Nie można zmienić pełnej nazwy",
+ "Redis" : "Redis",
"Security & setup warnings" : "Ostrzeżenia bezpieczeństwa i konfiguracji",
"Sharing" : "Udostępnianie",
"Server-side encryption" : "Szyfrowanie po stronie serwera",
@@ -48,6 +49,7 @@ OC.L10N.register(
"Migration in progress. Please wait until the migration is finished" : "Trwa migracja. Proszę poczekać, aż migracja dobiegnie końca.",
"Migration started …" : "Migracja rozpoczęta...",
"Sending..." : "Wysyłam...",
+ "Experimental" : "Eksperymentalny",
"All" : "Wszystkie",
"No apps found for your version" : "Nie znaleziono aplikacji dla twojej wersji",
"Update to %s" : "Uaktualnij do %s",
@@ -93,7 +95,6 @@ OC.L10N.register(
"Unlimited" : "Bez limitu",
"Personal info" : "Informacje osobiste",
"Sessions" : "Sesje",
- "Devices" : "Urządzenia",
"Sync clients" : "Klienty synchronizacji",
"Everything (fatal issues, errors, warnings, info, debug)" : "Wszystko (Informacje, ostrzeżenia, błędy i poważne problemy, debug)",
"Info, warnings, errors and fatal issues" : "Informacje, ostrzeżenia, błędy i poważne problemy",
@@ -192,9 +193,10 @@ OC.L10N.register(
"Current password" : "Bieżące hasło",
"New password" : "Nowe hasło",
"Change password" : "Zmień hasło",
- "Name" : "Nazwa",
"Language" : "Język",
"Help translate" : "Pomóż w tłumaczeniu",
+ "Name" : "Nazwa",
+ "Done" : "Ukończono",
"Get the apps to sync your files" : "Pobierz aplikacje żeby synchronizować swoje pliki",
"Desktop client" : "Klient na komputer",
"Android app" : "Aplikacja Android",
diff --git a/settings/l10n/pl.json b/settings/l10n/pl.json
index cc7022746ca..f1c1994dd1d 100644
--- a/settings/l10n/pl.json
+++ b/settings/l10n/pl.json
@@ -26,6 +26,7 @@
"Email saved" : "E-mail zapisany",
"Your full name has been changed." : "Twoja pełna nazwa została zmieniona.",
"Unable to change full name" : "Nie można zmienić pełnej nazwy",
+ "Redis" : "Redis",
"Security & setup warnings" : "Ostrzeżenia bezpieczeństwa i konfiguracji",
"Sharing" : "Udostępnianie",
"Server-side encryption" : "Szyfrowanie po stronie serwera",
@@ -46,6 +47,7 @@
"Migration in progress. Please wait until the migration is finished" : "Trwa migracja. Proszę poczekać, aż migracja dobiegnie końca.",
"Migration started …" : "Migracja rozpoczęta...",
"Sending..." : "Wysyłam...",
+ "Experimental" : "Eksperymentalny",
"All" : "Wszystkie",
"No apps found for your version" : "Nie znaleziono aplikacji dla twojej wersji",
"Update to %s" : "Uaktualnij do %s",
@@ -91,7 +93,6 @@
"Unlimited" : "Bez limitu",
"Personal info" : "Informacje osobiste",
"Sessions" : "Sesje",
- "Devices" : "Urządzenia",
"Sync clients" : "Klienty synchronizacji",
"Everything (fatal issues, errors, warnings, info, debug)" : "Wszystko (Informacje, ostrzeżenia, błędy i poważne problemy, debug)",
"Info, warnings, errors and fatal issues" : "Informacje, ostrzeżenia, błędy i poważne problemy",
@@ -190,9 +191,10 @@
"Current password" : "Bieżące hasło",
"New password" : "Nowe hasło",
"Change password" : "Zmień hasło",
- "Name" : "Nazwa",
"Language" : "Język",
"Help translate" : "Pomóż w tłumaczeniu",
+ "Name" : "Nazwa",
+ "Done" : "Ukończono",
"Get the apps to sync your files" : "Pobierz aplikacje żeby synchronizować swoje pliki",
"Desktop client" : "Klient na komputer",
"Android app" : "Aplikacja Android",
diff --git a/settings/l10n/pt_BR.js b/settings/l10n/pt_BR.js
index 64fd998218c..baf510b46d0 100644
--- a/settings/l10n/pt_BR.js
+++ b/settings/l10n/pt_BR.js
@@ -119,7 +119,7 @@ OC.L10N.register(
"Unlimited" : "Ilimitado",
"Personal info" : "Informação pessoal",
"Sessions" : "Sessões",
- "Devices" : "Dispositivos",
+ "App passwords" : "Senhas de aplicativos",
"Sync clients" : "Clientes de Sincronização",
"Everything (fatal issues, errors, warnings, info, debug)" : "Tudo (questões fatais, erros, avisos, informações, depuração)",
"Info, warnings, errors and fatal issues" : "Informações, avisos, erros e problemas fatais",
@@ -269,14 +269,17 @@ OC.L10N.register(
"Current password" : "Senha atual",
"New password" : "Nova senha",
"Change password" : "Alterar senha",
+ "Language" : "Idioma",
+ "Help translate" : "Ajude a traduzir",
"These are the web, desktop and mobile clients currently logged in to your ownCloud." : "Estes são os clientes web, desktop e clientes móveis atualmente conectado ao seu ownCloud.",
"Browser" : "Navegador",
"Most recent activity" : "Atividade mais recente",
- "You've linked these devices." : "Você vinculou esses dispositivos.",
+ "You've linked these apps." : "Você vinculou esses aplicativos.",
"Name" : "Nome",
- "A device password is a passcode that gives an app or device permissions to access your ownCloud account." : "A senha do dispositivo é uma senha que dá a um aplicativo ou dispositivo permissões para acessar sua conta ownCloud.",
- "Language" : "Idioma",
- "Help translate" : "Ajude a traduzir",
+ "An app password is a passcode that gives an app or device permissions to access your %s account." : "A senha do aplicativo é um código de acesso que dá ao aplicativo ou dispositivo permissões para acessar sua conta %s.",
+ "App name" : "Nome do aplicativo",
+ "Create new app password" : "Criar uma nova senha do aplicativo",
+ "Done" : "Concluída",
"Get the apps to sync your files" : "Obtenha apps para sincronizar seus arquivos",
"Desktop client" : "Cliente Desktop",
"Android app" : "App Android",
diff --git a/settings/l10n/pt_BR.json b/settings/l10n/pt_BR.json
index 9bd110996e7..f271429535b 100644
--- a/settings/l10n/pt_BR.json
+++ b/settings/l10n/pt_BR.json
@@ -117,7 +117,7 @@
"Unlimited" : "Ilimitado",
"Personal info" : "Informação pessoal",
"Sessions" : "Sessões",
- "Devices" : "Dispositivos",
+ "App passwords" : "Senhas de aplicativos",
"Sync clients" : "Clientes de Sincronização",
"Everything (fatal issues, errors, warnings, info, debug)" : "Tudo (questões fatais, erros, avisos, informações, depuração)",
"Info, warnings, errors and fatal issues" : "Informações, avisos, erros e problemas fatais",
@@ -267,14 +267,17 @@
"Current password" : "Senha atual",
"New password" : "Nova senha",
"Change password" : "Alterar senha",
+ "Language" : "Idioma",
+ "Help translate" : "Ajude a traduzir",
"These are the web, desktop and mobile clients currently logged in to your ownCloud." : "Estes são os clientes web, desktop e clientes móveis atualmente conectado ao seu ownCloud.",
"Browser" : "Navegador",
"Most recent activity" : "Atividade mais recente",
- "You've linked these devices." : "Você vinculou esses dispositivos.",
+ "You've linked these apps." : "Você vinculou esses aplicativos.",
"Name" : "Nome",
- "A device password is a passcode that gives an app or device permissions to access your ownCloud account." : "A senha do dispositivo é uma senha que dá a um aplicativo ou dispositivo permissões para acessar sua conta ownCloud.",
- "Language" : "Idioma",
- "Help translate" : "Ajude a traduzir",
+ "An app password is a passcode that gives an app or device permissions to access your %s account." : "A senha do aplicativo é um código de acesso que dá ao aplicativo ou dispositivo permissões para acessar sua conta %s.",
+ "App name" : "Nome do aplicativo",
+ "Create new app password" : "Criar uma nova senha do aplicativo",
+ "Done" : "Concluída",
"Get the apps to sync your files" : "Obtenha apps para sincronizar seus arquivos",
"Desktop client" : "Cliente Desktop",
"Android app" : "App Android",
diff --git a/settings/l10n/pt_PT.js b/settings/l10n/pt_PT.js
index 8e3b02a2c5b..fcee766ed1d 100644
--- a/settings/l10n/pt_PT.js
+++ b/settings/l10n/pt_PT.js
@@ -119,7 +119,6 @@ OC.L10N.register(
"Unlimited" : "Ilimitado",
"Personal info" : "Informação pessoal",
"Sessions" : "Sessões",
- "Devices" : "Dispositivos",
"Sync clients" : "Clientes de sync",
"Everything (fatal issues, errors, warnings, info, debug)" : "Tudo (problemas fatais, erros, avisos, informação, depuração)",
"Info, warnings, errors and fatal issues" : "Informação, avisos, erros e problemas fatais",
@@ -268,14 +267,13 @@ OC.L10N.register(
"Current password" : "Palavra-passe atual",
"New password" : "Nova palavra-passe",
"Change password" : "Alterar palavra-passe",
+ "Language" : "Idioma",
+ "Help translate" : "Ajude a traduzir",
"These are the web, desktop and mobile clients currently logged in to your ownCloud." : "Estes são os clientes web, desktop e móveis actualmente ligados à sua ownCloud.",
"Browser" : "Navegador",
"Most recent activity" : "Atividade mais recente",
- "You've linked these devices." : "Associou estes dispositivos.",
"Name" : "Nome",
- "A device password is a passcode that gives an app or device permissions to access your ownCloud account." : "Uma palavra-passe de dispositivo é o código que dá permissões à app ou dispositivo para aceder à sua conta ownCloud.",
- "Language" : "Idioma",
- "Help translate" : "Ajude a traduzir",
+ "Done" : "Concluído",
"Get the apps to sync your files" : "Obtenha as aplicações para sincronizar os seus ficheiros",
"Desktop client" : "Cliente Desktop",
"Android app" : "Aplicação Android",
diff --git a/settings/l10n/pt_PT.json b/settings/l10n/pt_PT.json
index 713448bafe3..0eab5a66356 100644
--- a/settings/l10n/pt_PT.json
+++ b/settings/l10n/pt_PT.json
@@ -117,7 +117,6 @@
"Unlimited" : "Ilimitado",
"Personal info" : "Informação pessoal",
"Sessions" : "Sessões",
- "Devices" : "Dispositivos",
"Sync clients" : "Clientes de sync",
"Everything (fatal issues, errors, warnings, info, debug)" : "Tudo (problemas fatais, erros, avisos, informação, depuração)",
"Info, warnings, errors and fatal issues" : "Informação, avisos, erros e problemas fatais",
@@ -266,14 +265,13 @@
"Current password" : "Palavra-passe atual",
"New password" : "Nova palavra-passe",
"Change password" : "Alterar palavra-passe",
+ "Language" : "Idioma",
+ "Help translate" : "Ajude a traduzir",
"These are the web, desktop and mobile clients currently logged in to your ownCloud." : "Estes são os clientes web, desktop e móveis actualmente ligados à sua ownCloud.",
"Browser" : "Navegador",
"Most recent activity" : "Atividade mais recente",
- "You've linked these devices." : "Associou estes dispositivos.",
"Name" : "Nome",
- "A device password is a passcode that gives an app or device permissions to access your ownCloud account." : "Uma palavra-passe de dispositivo é o código que dá permissões à app ou dispositivo para aceder à sua conta ownCloud.",
- "Language" : "Idioma",
- "Help translate" : "Ajude a traduzir",
+ "Done" : "Concluído",
"Get the apps to sync your files" : "Obtenha as aplicações para sincronizar os seus ficheiros",
"Desktop client" : "Cliente Desktop",
"Android app" : "Aplicação Android",
diff --git a/settings/l10n/ro.js b/settings/l10n/ro.js
index bf874df1b6d..d0a0b107d0a 100644
--- a/settings/l10n/ro.js
+++ b/settings/l10n/ro.js
@@ -58,6 +58,7 @@ OC.L10N.register(
"All" : "Toate ",
"No apps found for your version" : "Nu au fost găsite aplicații pentru versiunea ta",
"The app will be downloaded from the app store" : "Aplicația va fi descărcată din magazin",
+ "_You have %n app update pending_::_You have %n app updates pending_" : ["Ai %n actualizare de aplicație în așteptare","Ai %n actualizări de aplicație în așteptare","Ai %n actualizări de aplicație în așteptare"],
"Please wait...." : "Aşteptaţi vă rog....",
"Error while disabling app" : "Eroare în timpul dezactivării aplicației",
"Disable" : "Dezactivați",
@@ -67,7 +68,10 @@ OC.L10N.register(
"Error while updating app" : "Eroare în timpul actualizării aplicaţiei",
"Updated" : "Actualizat",
"Uninstalling ...." : "Dezinstalaza ....",
+ "Error while uninstalling app" : "Eroare la dezinstalarea aplicației",
"Uninstall" : "Dezinstalați",
+ "App update" : "Actualizare aplicație",
+ "Disconnect" : "Deconectare",
"Valid until {date}" : "Valabil până la {date}",
"Delete" : "Șterge",
"An error occurred: {message}" : "A apărut o eroare: {message}",
@@ -79,27 +83,40 @@ OC.L10N.register(
"Strong password" : "Parolă puternică",
"Groups" : "Grupuri",
"Unable to delete {objName}" : "Nu s-a putut șterge {objName}",
+ "Error creating group: {message}" : "Eroare la crearea grupului: {message}",
+ "A valid group name must be provided" : "Trebuie furnizat un nume de grup valid",
"deleted {groupName}" : "{groupName} s-a șters",
"undo" : "Anulează ultima acțiune",
"no group" : "niciun grup",
"never" : "niciodată",
"deleted {userName}" : "{userName} șters",
"add group" : "adăugaţi grupul",
+ "Changing the password will result in data loss, because data recovery is not available for this user" : "Schimbarea parolei va rezulta în pierderea datelor deoarece recuperarea acestora nu este disponibilă pentru acest utilizator",
"A valid username must be provided" : "Trebuie să furnizaţi un nume de utilizator valid",
+ "Error creating user: {message}" : "Eroare la crearea utilizatorului: {message}",
"A valid password must be provided" : "Trebuie să furnizaţi o parolă validă",
+ "A valid email must be provided" : "Trebuie furnizată o adresă email validă",
"__language_name__" : "_language_name_",
"Unlimited" : "Nelimitată",
"Personal info" : "Informații personale",
"Sessions" : "Sesiuni",
- "Devices" : "Dispozitive",
+ "App passwords" : "Parole aplicații",
"Sync clients" : "Sincronizează clienții",
+ "Everything (fatal issues, errors, warnings, info, debug)" : "Tot (erori fatale, erori, avertizări, informări, depanare)",
+ "Info, warnings, errors and fatal issues" : "Informări, avertizări, erori și erori fatale",
+ "Warnings, errors and fatal issues" : "Avertizări, erori și erori fatale",
+ "Errors and fatal issues" : "Erori și erori fatale",
+ "Fatal issues only" : "Doar erori fatale",
"None" : "Niciuna",
"Login" : "Autentificare",
+ "NT LAN Manager" : "NT LAN Manager",
"SSL" : "SSL",
"TLS" : "TLS",
"The PHP module 'fileinfo' is missing. We strongly recommend to enable this module to get best results with mime-type detection." : "Modulul PHP \"Fileinfo\" lipsește. Va recomandam sa activaţi acest modul pentru a obține cele mai bune rezultate cu detectarea mime-type.",
+ "All checks passed." : "Toate verificările s-au terminat fără erori.",
"Open documentation" : "Deschide documentația",
"Allow apps to use the Share API" : "Permite aplicațiilor să folosească API-ul de partajare",
+ "Allow users to share via link" : "Permite utilizatorilor să partajeze via link",
"Allow public uploads" : "Permite încărcări publice",
"Enforce password protection" : "Impune protecția prin parolă",
"Set default expiration date" : "Setează data implicită de expirare",
@@ -178,11 +195,10 @@ OC.L10N.register(
"Current password" : "Parola curentă",
"New password" : "Noua parolă",
"Change password" : "Schimbă parola",
- "Most recent activity" : "Cea mai recentă activitate",
- "You've linked these devices." : "Ai legat aceste dispozitive.",
- "Name" : "Nume",
"Language" : "Limba",
"Help translate" : "Ajută la traducere",
+ "Most recent activity" : "Cea mai recentă activitate",
+ "Name" : "Nume",
"Get the apps to sync your files" : "Ia acum aplicatia pentru sincronizarea fisierelor ",
"Desktop client" : "Client Desktop",
"Android app" : "Aplicatie Android",
diff --git a/settings/l10n/ro.json b/settings/l10n/ro.json
index 441dfef60cf..4f3a30ab5c3 100644
--- a/settings/l10n/ro.json
+++ b/settings/l10n/ro.json
@@ -56,6 +56,7 @@
"All" : "Toate ",
"No apps found for your version" : "Nu au fost găsite aplicații pentru versiunea ta",
"The app will be downloaded from the app store" : "Aplicația va fi descărcată din magazin",
+ "_You have %n app update pending_::_You have %n app updates pending_" : ["Ai %n actualizare de aplicație în așteptare","Ai %n actualizări de aplicație în așteptare","Ai %n actualizări de aplicație în așteptare"],
"Please wait...." : "Aşteptaţi vă rog....",
"Error while disabling app" : "Eroare în timpul dezactivării aplicației",
"Disable" : "Dezactivați",
@@ -65,7 +66,10 @@
"Error while updating app" : "Eroare în timpul actualizării aplicaţiei",
"Updated" : "Actualizat",
"Uninstalling ...." : "Dezinstalaza ....",
+ "Error while uninstalling app" : "Eroare la dezinstalarea aplicației",
"Uninstall" : "Dezinstalați",
+ "App update" : "Actualizare aplicație",
+ "Disconnect" : "Deconectare",
"Valid until {date}" : "Valabil până la {date}",
"Delete" : "Șterge",
"An error occurred: {message}" : "A apărut o eroare: {message}",
@@ -77,27 +81,40 @@
"Strong password" : "Parolă puternică",
"Groups" : "Grupuri",
"Unable to delete {objName}" : "Nu s-a putut șterge {objName}",
+ "Error creating group: {message}" : "Eroare la crearea grupului: {message}",
+ "A valid group name must be provided" : "Trebuie furnizat un nume de grup valid",
"deleted {groupName}" : "{groupName} s-a șters",
"undo" : "Anulează ultima acțiune",
"no group" : "niciun grup",
"never" : "niciodată",
"deleted {userName}" : "{userName} șters",
"add group" : "adăugaţi grupul",
+ "Changing the password will result in data loss, because data recovery is not available for this user" : "Schimbarea parolei va rezulta în pierderea datelor deoarece recuperarea acestora nu este disponibilă pentru acest utilizator",
"A valid username must be provided" : "Trebuie să furnizaţi un nume de utilizator valid",
+ "Error creating user: {message}" : "Eroare la crearea utilizatorului: {message}",
"A valid password must be provided" : "Trebuie să furnizaţi o parolă validă",
+ "A valid email must be provided" : "Trebuie furnizată o adresă email validă",
"__language_name__" : "_language_name_",
"Unlimited" : "Nelimitată",
"Personal info" : "Informații personale",
"Sessions" : "Sesiuni",
- "Devices" : "Dispozitive",
+ "App passwords" : "Parole aplicații",
"Sync clients" : "Sincronizează clienții",
+ "Everything (fatal issues, errors, warnings, info, debug)" : "Tot (erori fatale, erori, avertizări, informări, depanare)",
+ "Info, warnings, errors and fatal issues" : "Informări, avertizări, erori și erori fatale",
+ "Warnings, errors and fatal issues" : "Avertizări, erori și erori fatale",
+ "Errors and fatal issues" : "Erori și erori fatale",
+ "Fatal issues only" : "Doar erori fatale",
"None" : "Niciuna",
"Login" : "Autentificare",
+ "NT LAN Manager" : "NT LAN Manager",
"SSL" : "SSL",
"TLS" : "TLS",
"The PHP module 'fileinfo' is missing. We strongly recommend to enable this module to get best results with mime-type detection." : "Modulul PHP \"Fileinfo\" lipsește. Va recomandam sa activaţi acest modul pentru a obține cele mai bune rezultate cu detectarea mime-type.",
+ "All checks passed." : "Toate verificările s-au terminat fără erori.",
"Open documentation" : "Deschide documentația",
"Allow apps to use the Share API" : "Permite aplicațiilor să folosească API-ul de partajare",
+ "Allow users to share via link" : "Permite utilizatorilor să partajeze via link",
"Allow public uploads" : "Permite încărcări publice",
"Enforce password protection" : "Impune protecția prin parolă",
"Set default expiration date" : "Setează data implicită de expirare",
@@ -176,11 +193,10 @@
"Current password" : "Parola curentă",
"New password" : "Noua parolă",
"Change password" : "Schimbă parola",
- "Most recent activity" : "Cea mai recentă activitate",
- "You've linked these devices." : "Ai legat aceste dispozitive.",
- "Name" : "Nume",
"Language" : "Limba",
"Help translate" : "Ajută la traducere",
+ "Most recent activity" : "Cea mai recentă activitate",
+ "Name" : "Nume",
"Get the apps to sync your files" : "Ia acum aplicatia pentru sincronizarea fisierelor ",
"Desktop client" : "Client Desktop",
"Android app" : "Aplicatie Android",
diff --git a/settings/l10n/ru.js b/settings/l10n/ru.js
index b215de42429..3c0cbcda098 100644
--- a/settings/l10n/ru.js
+++ b/settings/l10n/ru.js
@@ -119,7 +119,7 @@ OC.L10N.register(
"Unlimited" : "Неограничено",
"Personal info" : "Личная информация",
"Sessions" : "Сессии",
- "Devices" : "Устройства",
+ "App passwords" : "Пароль приложения",
"Sync clients" : "Синхронизирующиеся клиенты",
"Everything (fatal issues, errors, warnings, info, debug)" : "Все (критические проблемы, ошибки, предупреждения, информационные, отладочные)",
"Info, warnings, errors and fatal issues" : "Информационные, предупреждения, ошибки и критические проблемы",
@@ -269,14 +269,17 @@ OC.L10N.register(
"Current password" : "Текущий пароль",
"New password" : "Новый пароль",
"Change password" : "Сменить пароль",
+ "Language" : "Язык",
+ "Help translate" : "Помочь с переводом",
"These are the web, desktop and mobile clients currently logged in to your ownCloud." : "Это сессии, вошедшие в настоящий момент в ваш ownCloud через веб, клиенты для ПК или мобильных устройств.",
"Browser" : "Браузер",
"Most recent activity" : "Последняя активность",
- "You've linked these devices." : "Вы привязали следующие устройства.",
+ "You've linked these apps." : "Вы привязали это приложение.",
"Name" : "Название",
- "A device password is a passcode that gives an app or device permissions to access your ownCloud account." : "Пароль устройства — это контрольный код, который даёт приложению или устройству доступ к вашей учётной записи в ownCloud.",
- "Language" : "Язык",
- "Help translate" : "Помочь с переводом",
+ "An app password is a passcode that gives an app or device permissions to access your %s account." : "Пароль приложения представляет собой код доступа, который дает приложению или устройству разрешения на доступ к вашему аккаунту %s.",
+ "App name" : "Название приложения",
+ "Create new app password" : "Создать новый пароль для приложения",
+ "Done" : "Выполнено",
"Get the apps to sync your files" : "Получить приложения для синхронизации ваших файлов",
"Desktop client" : "Клиент для ПК",
"Android app" : "Android приложение",
diff --git a/settings/l10n/ru.json b/settings/l10n/ru.json
index adec4aa9fce..e589da4ac0e 100644
--- a/settings/l10n/ru.json
+++ b/settings/l10n/ru.json
@@ -117,7 +117,7 @@
"Unlimited" : "Неограничено",
"Personal info" : "Личная информация",
"Sessions" : "Сессии",
- "Devices" : "Устройства",
+ "App passwords" : "Пароль приложения",
"Sync clients" : "Синхронизирующиеся клиенты",
"Everything (fatal issues, errors, warnings, info, debug)" : "Все (критические проблемы, ошибки, предупреждения, информационные, отладочные)",
"Info, warnings, errors and fatal issues" : "Информационные, предупреждения, ошибки и критические проблемы",
@@ -267,14 +267,17 @@
"Current password" : "Текущий пароль",
"New password" : "Новый пароль",
"Change password" : "Сменить пароль",
+ "Language" : "Язык",
+ "Help translate" : "Помочь с переводом",
"These are the web, desktop and mobile clients currently logged in to your ownCloud." : "Это сессии, вошедшие в настоящий момент в ваш ownCloud через веб, клиенты для ПК или мобильных устройств.",
"Browser" : "Браузер",
"Most recent activity" : "Последняя активность",
- "You've linked these devices." : "Вы привязали следующие устройства.",
+ "You've linked these apps." : "Вы привязали это приложение.",
"Name" : "Название",
- "A device password is a passcode that gives an app or device permissions to access your ownCloud account." : "Пароль устройства — это контрольный код, который даёт приложению или устройству доступ к вашей учётной записи в ownCloud.",
- "Language" : "Язык",
- "Help translate" : "Помочь с переводом",
+ "An app password is a passcode that gives an app or device permissions to access your %s account." : "Пароль приложения представляет собой код доступа, который дает приложению или устройству разрешения на доступ к вашему аккаунту %s.",
+ "App name" : "Название приложения",
+ "Create new app password" : "Создать новый пароль для приложения",
+ "Done" : "Выполнено",
"Get the apps to sync your files" : "Получить приложения для синхронизации ваших файлов",
"Desktop client" : "Клиент для ПК",
"Android app" : "Android приложение",
diff --git a/settings/l10n/si_LK.js b/settings/l10n/si_LK.js
index 9d0343b3ab0..49bfe0179a0 100644
--- a/settings/l10n/si_LK.js
+++ b/settings/l10n/si_LK.js
@@ -33,9 +33,9 @@ OC.L10N.register(
"Current password" : "වත්මන් මුරපදය",
"New password" : "නව මුරපදය",
"Change password" : "මුරපදය වෙනස් කිරීම",
- "Name" : "නම",
"Language" : "භාෂාව",
"Help translate" : "පරිවර්ථන සහය",
+ "Name" : "නම",
"Username" : "පරිශීලක නම",
"Create" : "තනන්න",
"Default Quota" : "සාමාන්‍ය සලාකය",
diff --git a/settings/l10n/si_LK.json b/settings/l10n/si_LK.json
index 24bee43d9a7..8fd007d4c50 100644
--- a/settings/l10n/si_LK.json
+++ b/settings/l10n/si_LK.json
@@ -31,9 +31,9 @@
"Current password" : "වත්මන් මුරපදය",
"New password" : "නව මුරපදය",
"Change password" : "මුරපදය වෙනස් කිරීම",
- "Name" : "නම",
"Language" : "භාෂාව",
"Help translate" : "පරිවර්ථන සහය",
+ "Name" : "නම",
"Username" : "පරිශීලක නම",
"Create" : "තනන්න",
"Default Quota" : "සාමාන්‍ය සලාකය",
diff --git a/settings/l10n/sk_SK.js b/settings/l10n/sk_SK.js
index fcd54cdc92b..4ca631bb8b6 100644
--- a/settings/l10n/sk_SK.js
+++ b/settings/l10n/sk_SK.js
@@ -211,9 +211,10 @@ OC.L10N.register(
"Current password" : "Aktuálne heslo",
"New password" : "Nové heslo",
"Change password" : "Zmeniť heslo",
- "Name" : "Názov",
"Language" : "Jazyk",
"Help translate" : "Pomôcť s prekladom",
+ "Name" : "Názov",
+ "Done" : "Hotovo",
"Get the apps to sync your files" : "Získať aplikácie na synchronizáciu vašich súborov",
"Desktop client" : "Desktopový klient",
"Android app" : "Android aplikácia",
diff --git a/settings/l10n/sk_SK.json b/settings/l10n/sk_SK.json
index 7b84596c60d..fbb0a5fe24f 100644
--- a/settings/l10n/sk_SK.json
+++ b/settings/l10n/sk_SK.json
@@ -209,9 +209,10 @@
"Current password" : "Aktuálne heslo",
"New password" : "Nové heslo",
"Change password" : "Zmeniť heslo",
- "Name" : "Názov",
"Language" : "Jazyk",
"Help translate" : "Pomôcť s prekladom",
+ "Name" : "Názov",
+ "Done" : "Hotovo",
"Get the apps to sync your files" : "Získať aplikácie na synchronizáciu vašich súborov",
"Desktop client" : "Desktopový klient",
"Android app" : "Android aplikácia",
diff --git a/settings/l10n/sl.js b/settings/l10n/sl.js
index a875fc6437e..f75df25d7fe 100644
--- a/settings/l10n/sl.js
+++ b/settings/l10n/sl.js
@@ -111,7 +111,6 @@ OC.L10N.register(
"Unlimited" : "Neomejeno",
"Personal info" : "Osebni podatki",
"Sessions" : "Seje",
- "Devices" : "Naprave",
"Sync clients" : "Uskladi odjemalce",
"Everything (fatal issues, errors, warnings, info, debug)" : "Vse (podrobnosti, opozorila, hrošče, napake in usodne dogodke)",
"Info, warnings, errors and fatal issues" : "Podrobnosti, opozorila, napake in usodne dogodke",
@@ -232,11 +231,12 @@ OC.L10N.register(
"Current password" : "Trenutno geslo",
"New password" : "Novo geslo",
"Change password" : "Spremeni geslo",
+ "Language" : "Jezik",
+ "Help translate" : "Sodelujte pri prevajanju",
"Browser" : "Brskalnik",
"Most recent activity" : "Zadnja dejavnost",
"Name" : "Ime",
- "Language" : "Jezik",
- "Help translate" : "Sodelujte pri prevajanju",
+ "Done" : "Končano",
"Get the apps to sync your files" : "Pridobi programe za usklajevanje datotek",
"Desktop client" : "Namizni odjemalec",
"Android app" : "Program za Android",
diff --git a/settings/l10n/sl.json b/settings/l10n/sl.json
index 41a60f1f9da..b7eb34f46f3 100644
--- a/settings/l10n/sl.json
+++ b/settings/l10n/sl.json
@@ -109,7 +109,6 @@
"Unlimited" : "Neomejeno",
"Personal info" : "Osebni podatki",
"Sessions" : "Seje",
- "Devices" : "Naprave",
"Sync clients" : "Uskladi odjemalce",
"Everything (fatal issues, errors, warnings, info, debug)" : "Vse (podrobnosti, opozorila, hrošče, napake in usodne dogodke)",
"Info, warnings, errors and fatal issues" : "Podrobnosti, opozorila, napake in usodne dogodke",
@@ -230,11 +229,12 @@
"Current password" : "Trenutno geslo",
"New password" : "Novo geslo",
"Change password" : "Spremeni geslo",
+ "Language" : "Jezik",
+ "Help translate" : "Sodelujte pri prevajanju",
"Browser" : "Brskalnik",
"Most recent activity" : "Zadnja dejavnost",
"Name" : "Ime",
- "Language" : "Jezik",
- "Help translate" : "Sodelujte pri prevajanju",
+ "Done" : "Končano",
"Get the apps to sync your files" : "Pridobi programe za usklajevanje datotek",
"Desktop client" : "Namizni odjemalec",
"Android app" : "Program za Android",
diff --git a/settings/l10n/sq.js b/settings/l10n/sq.js
index aef86d2d43b..eae031657e7 100644
--- a/settings/l10n/sq.js
+++ b/settings/l10n/sq.js
@@ -119,7 +119,7 @@ OC.L10N.register(
"Unlimited" : "E pakufizuar",
"Personal info" : "Të dhëna personale",
"Sessions" : "Sesione",
- "Devices" : "Pajisje",
+ "App passwords" : "Fjalëkalim aplikacioni",
"Sync clients" : "Klientë njëkohësimi",
"Everything (fatal issues, errors, warnings, info, debug)" : "Gjithçka (probleme fatale, gabime, sinjalizime, të dhëna, diagnostikim)",
"Info, warnings, errors and fatal issues" : "Të dhëna, sinjalizime, gabime dhe probleme fatale",
@@ -269,14 +269,17 @@ OC.L10N.register(
"Current password" : "Fjalëkalimi i tanishëm",
"New password" : "Fjalëkalimi i ri",
"Change password" : "Ndrysho fjalëkalimin",
+ "Language" : "Gjuhë",
+ "Help translate" : "Ndihmoni në përkthim",
"These are the web, desktop and mobile clients currently logged in to your ownCloud." : "Këta janë klientët web, desktop dhe celular të futur në këtë çast në ownCloud-in tuaj.",
"Browser" : "Shfletues",
"Most recent activity" : "Veprimtaria më e freskët",
- "You've linked these devices." : "I keni të lidhura këto pajisje.",
+ "You've linked these apps." : "I keni lidhur këto aplikacione.",
"Name" : "Emër",
- "A device password is a passcode that gives an app or device permissions to access your ownCloud account." : "Fjalëkalimi për një pajisje është një kodkalim që i jep një pajisjeje apo një aplikacioni leje të hyjë në llogarinë tuaj ownCloud.",
- "Language" : "Gjuhë",
- "Help translate" : "Ndihmoni në përkthim",
+ "An app password is a passcode that gives an app or device permissions to access your %s account." : "Fjalëkalimet e aplikacioneve janë kodkalime që u japin leje një aplikacioni ose pajisjeje të hyjnë në llogarinë tuaj %s.",
+ "App name" : "Emër aplikacioni",
+ "Create new app password" : "Krijoni fjalëkalim aplikacioni të ri",
+ "Done" : "U bë",
"Get the apps to sync your files" : "Merrni aplikacionet për njëkohësim të kartelave tuaja",
"Desktop client" : "Klient desktopi",
"Android app" : "Aplikacion për Android",
diff --git a/settings/l10n/sq.json b/settings/l10n/sq.json
index b909c617c29..a983e86dd57 100644
--- a/settings/l10n/sq.json
+++ b/settings/l10n/sq.json
@@ -117,7 +117,7 @@
"Unlimited" : "E pakufizuar",
"Personal info" : "Të dhëna personale",
"Sessions" : "Sesione",
- "Devices" : "Pajisje",
+ "App passwords" : "Fjalëkalim aplikacioni",
"Sync clients" : "Klientë njëkohësimi",
"Everything (fatal issues, errors, warnings, info, debug)" : "Gjithçka (probleme fatale, gabime, sinjalizime, të dhëna, diagnostikim)",
"Info, warnings, errors and fatal issues" : "Të dhëna, sinjalizime, gabime dhe probleme fatale",
@@ -267,14 +267,17 @@
"Current password" : "Fjalëkalimi i tanishëm",
"New password" : "Fjalëkalimi i ri",
"Change password" : "Ndrysho fjalëkalimin",
+ "Language" : "Gjuhë",
+ "Help translate" : "Ndihmoni në përkthim",
"These are the web, desktop and mobile clients currently logged in to your ownCloud." : "Këta janë klientët web, desktop dhe celular të futur në këtë çast në ownCloud-in tuaj.",
"Browser" : "Shfletues",
"Most recent activity" : "Veprimtaria më e freskët",
- "You've linked these devices." : "I keni të lidhura këto pajisje.",
+ "You've linked these apps." : "I keni lidhur këto aplikacione.",
"Name" : "Emër",
- "A device password is a passcode that gives an app or device permissions to access your ownCloud account." : "Fjalëkalimi për një pajisje është një kodkalim që i jep një pajisjeje apo një aplikacioni leje të hyjë në llogarinë tuaj ownCloud.",
- "Language" : "Gjuhë",
- "Help translate" : "Ndihmoni në përkthim",
+ "An app password is a passcode that gives an app or device permissions to access your %s account." : "Fjalëkalimet e aplikacioneve janë kodkalime që u japin leje një aplikacioni ose pajisjeje të hyjnë në llogarinë tuaj %s.",
+ "App name" : "Emër aplikacioni",
+ "Create new app password" : "Krijoni fjalëkalim aplikacioni të ri",
+ "Done" : "U bë",
"Get the apps to sync your files" : "Merrni aplikacionet për njëkohësim të kartelave tuaja",
"Desktop client" : "Klient desktopi",
"Android app" : "Aplikacion për Android",
diff --git a/settings/l10n/sr.js b/settings/l10n/sr.js
index 30a290ea0eb..5b77f607971 100644
--- a/settings/l10n/sr.js
+++ b/settings/l10n/sr.js
@@ -220,9 +220,10 @@ OC.L10N.register(
"Current password" : "Тренутна лозинка",
"New password" : "Нова лозинка",
"Change password" : "Измени лозинку",
- "Name" : "назив",
"Language" : "Језик",
"Help translate" : " Помозите у превођењу",
+ "Name" : "назив",
+ "Done" : "Завршено",
"Get the apps to sync your files" : "Преузмите апликације ради синхронизовања ваших фајлова",
"Desktop client" : "Клијент за рачунар",
"Android app" : "Андроид апликација",
diff --git a/settings/l10n/sr.json b/settings/l10n/sr.json
index b34706ea7b6..77513de8fac 100644
--- a/settings/l10n/sr.json
+++ b/settings/l10n/sr.json
@@ -218,9 +218,10 @@
"Current password" : "Тренутна лозинка",
"New password" : "Нова лозинка",
"Change password" : "Измени лозинку",
- "Name" : "назив",
"Language" : "Језик",
"Help translate" : " Помозите у превођењу",
+ "Name" : "назив",
+ "Done" : "Завршено",
"Get the apps to sync your files" : "Преузмите апликације ради синхронизовања ваших фајлова",
"Desktop client" : "Клијент за рачунар",
"Android app" : "Андроид апликација",
diff --git a/settings/l10n/sr@latin.js b/settings/l10n/sr@latin.js
index 587d565c0c7..148b1580597 100644
--- a/settings/l10n/sr@latin.js
+++ b/settings/l10n/sr@latin.js
@@ -25,8 +25,8 @@ OC.L10N.register(
"Current password" : "Trenutna lozinka",
"New password" : "Nova lozinka",
"Change password" : "Izmeni lozinku",
- "Name" : "naziv",
"Language" : "Jezik",
+ "Name" : "naziv",
"Get the apps to sync your files" : "Preuzmite aplikacije za sinhronizaciju Vaših fajlova",
"Desktop client" : "Desktop klijent",
"Android app" : "Android aplikacija",
diff --git a/settings/l10n/sr@latin.json b/settings/l10n/sr@latin.json
index 80858b4dd18..c328b49d9b4 100644
--- a/settings/l10n/sr@latin.json
+++ b/settings/l10n/sr@latin.json
@@ -23,8 +23,8 @@
"Current password" : "Trenutna lozinka",
"New password" : "Nova lozinka",
"Change password" : "Izmeni lozinku",
- "Name" : "naziv",
"Language" : "Jezik",
+ "Name" : "naziv",
"Get the apps to sync your files" : "Preuzmite aplikacije za sinhronizaciju Vaših fajlova",
"Desktop client" : "Desktop klijent",
"Android app" : "Android aplikacija",
diff --git a/settings/l10n/sv.js b/settings/l10n/sv.js
index 91a2dcc6a3c..f34eb08688e 100644
--- a/settings/l10n/sv.js
+++ b/settings/l10n/sv.js
@@ -119,7 +119,6 @@ OC.L10N.register(
"Unlimited" : "Obegränsad",
"Personal info" : "Personlig information",
"Sessions" : "Sessioner",
- "Devices" : "Enheter",
"Sync clients" : "Synk-klienter",
"Everything (fatal issues, errors, warnings, info, debug)" : "Allting (allvarliga fel, fel, varningar, info, debug)",
"Info, warnings, errors and fatal issues" : "Info, varningar och allvarliga fel",
@@ -223,6 +222,8 @@ OC.L10N.register(
"Documentation:" : "Dokumentation:",
"User documentation" : "Användardokumentation",
"Admin documentation" : "Administratörsdokumentation",
+ "Visit website" : "Besök webbsida",
+ "Report a bug" : "Rapportera ett problem",
"Show description …" : "Visa beskrivning",
"Hide description …" : "Dölj beskrivning",
"This app has an update available." : "Denna applikation har en uppdatering tillgänglig.",
@@ -267,14 +268,13 @@ OC.L10N.register(
"Current password" : "Nuvarande lösenord",
"New password" : "Nytt lösenord",
"Change password" : "Ändra lösenord",
+ "Language" : "Språk",
+ "Help translate" : "Hjälp att översätta",
"These are the web, desktop and mobile clients currently logged in to your ownCloud." : "Dessa webbläsare,pc och mobila klienter är för tillfället inloggade på din ownCloud.",
"Browser" : "Webbläsare",
"Most recent activity" : "Senaste aktivitet",
- "You've linked these devices." : "Du har länkat dessa enheter.",
"Name" : "Namn",
- "A device password is a passcode that gives an app or device permissions to access your ownCloud account." : "Ett enhetslösenord är ett lösenord som ger en app eller enhet tillåtelse att komma åt ditt ownCloud-konto.",
- "Language" : "Språk",
- "Help translate" : "Hjälp att översätta",
+ "Done" : "Färdig",
"Get the apps to sync your files" : "Skaffa apparna för att synkronisera dina filer",
"Desktop client" : "Skrivbordsklient",
"Android app" : "Android-app",
diff --git a/settings/l10n/sv.json b/settings/l10n/sv.json
index f471a5318cd..b6a85c364b8 100644
--- a/settings/l10n/sv.json
+++ b/settings/l10n/sv.json
@@ -117,7 +117,6 @@
"Unlimited" : "Obegränsad",
"Personal info" : "Personlig information",
"Sessions" : "Sessioner",
- "Devices" : "Enheter",
"Sync clients" : "Synk-klienter",
"Everything (fatal issues, errors, warnings, info, debug)" : "Allting (allvarliga fel, fel, varningar, info, debug)",
"Info, warnings, errors and fatal issues" : "Info, varningar och allvarliga fel",
@@ -221,6 +220,8 @@
"Documentation:" : "Dokumentation:",
"User documentation" : "Användardokumentation",
"Admin documentation" : "Administratörsdokumentation",
+ "Visit website" : "Besök webbsida",
+ "Report a bug" : "Rapportera ett problem",
"Show description …" : "Visa beskrivning",
"Hide description …" : "Dölj beskrivning",
"This app has an update available." : "Denna applikation har en uppdatering tillgänglig.",
@@ -265,14 +266,13 @@
"Current password" : "Nuvarande lösenord",
"New password" : "Nytt lösenord",
"Change password" : "Ändra lösenord",
+ "Language" : "Språk",
+ "Help translate" : "Hjälp att översätta",
"These are the web, desktop and mobile clients currently logged in to your ownCloud." : "Dessa webbläsare,pc och mobila klienter är för tillfället inloggade på din ownCloud.",
"Browser" : "Webbläsare",
"Most recent activity" : "Senaste aktivitet",
- "You've linked these devices." : "Du har länkat dessa enheter.",
"Name" : "Namn",
- "A device password is a passcode that gives an app or device permissions to access your ownCloud account." : "Ett enhetslösenord är ett lösenord som ger en app eller enhet tillåtelse att komma åt ditt ownCloud-konto.",
- "Language" : "Språk",
- "Help translate" : "Hjälp att översätta",
+ "Done" : "Färdig",
"Get the apps to sync your files" : "Skaffa apparna för att synkronisera dina filer",
"Desktop client" : "Skrivbordsklient",
"Android app" : "Android-app",
diff --git a/settings/l10n/ta_LK.js b/settings/l10n/ta_LK.js
index e374ff28886..4274f397930 100644
--- a/settings/l10n/ta_LK.js
+++ b/settings/l10n/ta_LK.js
@@ -32,9 +32,9 @@ OC.L10N.register(
"Current password" : "தற்போதைய கடவுச்சொல்",
"New password" : "புதிய கடவுச்சொல்",
"Change password" : "கடவுச்சொல்லை மாற்றுக",
- "Name" : "பெயர்",
"Language" : "மொழி",
"Help translate" : "மொழிபெயர்க்க உதவி",
+ "Name" : "பெயர்",
"Username" : "பயனாளர் பெயர்",
"Create" : "உருவாக்குக",
"Default Quota" : "பொது இருப்பு பங்கு",
diff --git a/settings/l10n/ta_LK.json b/settings/l10n/ta_LK.json
index b386bdcc31a..bb07a0a5d16 100644
--- a/settings/l10n/ta_LK.json
+++ b/settings/l10n/ta_LK.json
@@ -30,9 +30,9 @@
"Current password" : "தற்போதைய கடவுச்சொல்",
"New password" : "புதிய கடவுச்சொல்",
"Change password" : "கடவுச்சொல்லை மாற்றுக",
- "Name" : "பெயர்",
"Language" : "மொழி",
"Help translate" : "மொழிபெயர்க்க உதவி",
+ "Name" : "பெயர்",
"Username" : "பயனாளர் பெயர்",
"Create" : "உருவாக்குக",
"Default Quota" : "பொது இருப்பு பங்கு",
diff --git a/settings/l10n/te.js b/settings/l10n/te.js
index d1cbfae0065..10698414059 100644
--- a/settings/l10n/te.js
+++ b/settings/l10n/te.js
@@ -9,8 +9,8 @@ OC.L10N.register(
"Your email address" : "మీ ఈమెయిలు చిరునామా",
"Password" : "సంకేతపదం",
"New password" : "కొత్త సంకేతపదం",
- "Name" : "పేరు",
"Language" : "భాష",
+ "Name" : "పేరు",
"Username" : "వాడుకరి పేరు"
},
"nplurals=2; plural=(n != 1);");
diff --git a/settings/l10n/te.json b/settings/l10n/te.json
index 7e039c31d98..a3e720f08ae 100644
--- a/settings/l10n/te.json
+++ b/settings/l10n/te.json
@@ -7,8 +7,8 @@
"Your email address" : "మీ ఈమెయిలు చిరునామా",
"Password" : "సంకేతపదం",
"New password" : "కొత్త సంకేతపదం",
- "Name" : "పేరు",
"Language" : "భాష",
+ "Name" : "పేరు",
"Username" : "వాడుకరి పేరు"
},"pluralForm" :"nplurals=2; plural=(n != 1);"
} \ No newline at end of file
diff --git a/settings/l10n/th_TH.js b/settings/l10n/th_TH.js
index f2d58d720ce..5e7b549ba3e 100644
--- a/settings/l10n/th_TH.js
+++ b/settings/l10n/th_TH.js
@@ -255,9 +255,10 @@ OC.L10N.register(
"Current password" : "รหัสผ่านปัจจุบัน",
"New password" : "รหัสผ่านใหม่",
"Change password" : "เปลี่ยนรหัสผ่าน",
- "Name" : "ชื่อ",
"Language" : "ภาษา",
"Help translate" : "มาช่วยกันแปลสิ!",
+ "Name" : "ชื่อ",
+ "Done" : "เสร็จสิ้น",
"Get the apps to sync your files" : "ใช้แอพพลิเคชันในการประสานไฟล์ของคุณ",
"Desktop client" : "เดสก์ทอปผู้ใช้",
"Android app" : "แอพฯ แอนดรอยด์",
diff --git a/settings/l10n/th_TH.json b/settings/l10n/th_TH.json
index db80cc6f4c8..22070291bb6 100644
--- a/settings/l10n/th_TH.json
+++ b/settings/l10n/th_TH.json
@@ -253,9 +253,10 @@
"Current password" : "รหัสผ่านปัจจุบัน",
"New password" : "รหัสผ่านใหม่",
"Change password" : "เปลี่ยนรหัสผ่าน",
- "Name" : "ชื่อ",
"Language" : "ภาษา",
"Help translate" : "มาช่วยกันแปลสิ!",
+ "Name" : "ชื่อ",
+ "Done" : "เสร็จสิ้น",
"Get the apps to sync your files" : "ใช้แอพพลิเคชันในการประสานไฟล์ของคุณ",
"Desktop client" : "เดสก์ทอปผู้ใช้",
"Android app" : "แอพฯ แอนดรอยด์",
diff --git a/settings/l10n/tr.js b/settings/l10n/tr.js
index a0e6d10dcca..313aed59be2 100644
--- a/settings/l10n/tr.js
+++ b/settings/l10n/tr.js
@@ -260,9 +260,10 @@ OC.L10N.register(
"Current password" : "Mevcut parola",
"New password" : "Yeni parola",
"Change password" : "Parola değiştir",
- "Name" : "Ad",
"Language" : "Dil",
"Help translate" : "Çevirilere yardım edin",
+ "Name" : "Ad",
+ "Done" : "Bitti",
"Get the apps to sync your files" : "Dosyalarınızı eşitlemek için uygulamaları indirin",
"Desktop client" : "Masaüstü istemcisi",
"Android app" : "Android uygulaması",
diff --git a/settings/l10n/tr.json b/settings/l10n/tr.json
index 2d8b69b2606..fdab4293468 100644
--- a/settings/l10n/tr.json
+++ b/settings/l10n/tr.json
@@ -258,9 +258,10 @@
"Current password" : "Mevcut parola",
"New password" : "Yeni parola",
"Change password" : "Parola değiştir",
- "Name" : "Ad",
"Language" : "Dil",
"Help translate" : "Çevirilere yardım edin",
+ "Name" : "Ad",
+ "Done" : "Bitti",
"Get the apps to sync your files" : "Dosyalarınızı eşitlemek için uygulamaları indirin",
"Desktop client" : "Masaüstü istemcisi",
"Android app" : "Android uygulaması",
diff --git a/settings/l10n/ug.js b/settings/l10n/ug.js
index 616f5e51816..ba8a6d5e722 100644
--- a/settings/l10n/ug.js
+++ b/settings/l10n/ug.js
@@ -44,9 +44,9 @@ OC.L10N.register(
"Current password" : "نۆۋەتتىكى ئىم",
"New password" : "يېڭى ئىم",
"Change password" : "ئىم ئۆزگەرت",
- "Name" : "ئاتى",
"Language" : "تىل",
"Help translate" : "تەرجىمىگە ياردەم",
+ "Name" : "ئاتى",
"Username" : "ئىشلەتكۈچى ئاتى",
"Create" : "قۇر",
"Other" : "باشقا",
diff --git a/settings/l10n/ug.json b/settings/l10n/ug.json
index 3fc60618bdc..1bea1a3997f 100644
--- a/settings/l10n/ug.json
+++ b/settings/l10n/ug.json
@@ -42,9 +42,9 @@
"Current password" : "نۆۋەتتىكى ئىم",
"New password" : "يېڭى ئىم",
"Change password" : "ئىم ئۆزگەرت",
- "Name" : "ئاتى",
"Language" : "تىل",
"Help translate" : "تەرجىمىگە ياردەم",
+ "Name" : "ئاتى",
"Username" : "ئىشلەتكۈچى ئاتى",
"Create" : "قۇر",
"Other" : "باشقا",
diff --git a/settings/l10n/uk.js b/settings/l10n/uk.js
index 1dd65c9b97b..ab8df47d67a 100644
--- a/settings/l10n/uk.js
+++ b/settings/l10n/uk.js
@@ -224,9 +224,10 @@ OC.L10N.register(
"Current password" : "Поточний пароль",
"New password" : "Новий пароль",
"Change password" : "Змінити пароль",
- "Name" : "Ім’я",
"Language" : "Мова",
"Help translate" : "Допомогти з перекладом",
+ "Name" : "Ім’я",
+ "Done" : "Готово",
"Get the apps to sync your files" : "Отримати додатки для синхронізації ваших файлів",
"Desktop client" : "Клієнт для ПК",
"Android app" : "Android-додаток",
diff --git a/settings/l10n/uk.json b/settings/l10n/uk.json
index 463f3c8d314..f8fa06b7a0d 100644
--- a/settings/l10n/uk.json
+++ b/settings/l10n/uk.json
@@ -222,9 +222,10 @@
"Current password" : "Поточний пароль",
"New password" : "Новий пароль",
"Change password" : "Змінити пароль",
- "Name" : "Ім’я",
"Language" : "Мова",
"Help translate" : "Допомогти з перекладом",
+ "Name" : "Ім’я",
+ "Done" : "Готово",
"Get the apps to sync your files" : "Отримати додатки для синхронізації ваших файлів",
"Desktop client" : "Клієнт для ПК",
"Android app" : "Android-додаток",
diff --git a/settings/l10n/vi.js b/settings/l10n/vi.js
index 6865aedd4b6..f2decd23b0b 100644
--- a/settings/l10n/vi.js
+++ b/settings/l10n/vi.js
@@ -56,9 +56,9 @@ OC.L10N.register(
"Current password" : "Mật khẩu cũ",
"New password" : "Mật khẩu mới",
"Change password" : "Đổi mật khẩu",
- "Name" : "Tên",
"Language" : "Ngôn ngữ",
"Help translate" : "Hỗ trợ dịch thuật",
+ "Name" : "Tên",
"Get the apps to sync your files" : "Nhận ứng dụng để đồng bộ file của bạn",
"Show First Run Wizard again" : "Hiện lại việc chạy đồ thuật khởi đầu",
"Username" : "Tên đăng nhập",
diff --git a/settings/l10n/vi.json b/settings/l10n/vi.json
index 9318a78eb1d..bd843122822 100644
--- a/settings/l10n/vi.json
+++ b/settings/l10n/vi.json
@@ -54,9 +54,9 @@
"Current password" : "Mật khẩu cũ",
"New password" : "Mật khẩu mới",
"Change password" : "Đổi mật khẩu",
- "Name" : "Tên",
"Language" : "Ngôn ngữ",
"Help translate" : "Hỗ trợ dịch thuật",
+ "Name" : "Tên",
"Get the apps to sync your files" : "Nhận ứng dụng để đồng bộ file của bạn",
"Show First Run Wizard again" : "Hiện lại việc chạy đồ thuật khởi đầu",
"Username" : "Tên đăng nhập",
diff --git a/settings/l10n/zh_CN.js b/settings/l10n/zh_CN.js
index 471b38e1ec4..6243996d0cf 100644
--- a/settings/l10n/zh_CN.js
+++ b/settings/l10n/zh_CN.js
@@ -251,9 +251,9 @@ OC.L10N.register(
"Current password" : "当前密码",
"New password" : "新密码",
"Change password" : "修改密码",
- "Name" : "名称",
"Language" : "语言",
"Help translate" : "帮助翻译",
+ "Name" : "名称",
"Get the apps to sync your files" : "安装应用进行文件同步",
"Desktop client" : "桌面客户端",
"Android app" : "Android 应用",
diff --git a/settings/l10n/zh_CN.json b/settings/l10n/zh_CN.json
index 37ddc31729e..ab4fa47531e 100644
--- a/settings/l10n/zh_CN.json
+++ b/settings/l10n/zh_CN.json
@@ -249,9 +249,9 @@
"Current password" : "当前密码",
"New password" : "新密码",
"Change password" : "修改密码",
- "Name" : "名称",
"Language" : "语言",
"Help translate" : "帮助翻译",
+ "Name" : "名称",
"Get the apps to sync your files" : "安装应用进行文件同步",
"Desktop client" : "桌面客户端",
"Android app" : "Android 应用",
diff --git a/settings/l10n/zh_HK.js b/settings/l10n/zh_HK.js
index cc00f6ada77..6f1cd57b792 100644
--- a/settings/l10n/zh_HK.js
+++ b/settings/l10n/zh_HK.js
@@ -43,9 +43,9 @@ OC.L10N.register(
"Password" : "密碼",
"New password" : "新密碼",
"Change password" : "更改密碼",
- "Name" : "名稱",
"Language" : "語言",
"Help translate" : "幫忙翻譯",
+ "Name" : "名稱",
"Android app" : "Android 應用程式",
"iOS app" : "iOS 應用程式",
"Username" : "用戶名稱",
diff --git a/settings/l10n/zh_HK.json b/settings/l10n/zh_HK.json
index af66c2b5e02..ac76df209b3 100644
--- a/settings/l10n/zh_HK.json
+++ b/settings/l10n/zh_HK.json
@@ -41,9 +41,9 @@
"Password" : "密碼",
"New password" : "新密碼",
"Change password" : "更改密碼",
- "Name" : "名稱",
"Language" : "語言",
"Help translate" : "幫忙翻譯",
+ "Name" : "名稱",
"Android app" : "Android 應用程式",
"iOS app" : "iOS 應用程式",
"Username" : "用戶名稱",
diff --git a/settings/l10n/zh_TW.js b/settings/l10n/zh_TW.js
index e5184f3a09d..6bdc3504227 100644
--- a/settings/l10n/zh_TW.js
+++ b/settings/l10n/zh_TW.js
@@ -245,9 +245,9 @@ OC.L10N.register(
"Current password" : "目前密碼",
"New password" : "新密碼",
"Change password" : "變更密碼",
- "Name" : "名稱",
"Language" : "語言",
"Help translate" : "幫助翻譯",
+ "Name" : "名稱",
"Get the apps to sync your files" : "下載應用程式來同步您的檔案",
"Desktop client" : "桌面客戶端",
"Android app" : "Android 應用程式",
diff --git a/settings/l10n/zh_TW.json b/settings/l10n/zh_TW.json
index e8e9b42b93a..46fc0c9e00b 100644
--- a/settings/l10n/zh_TW.json
+++ b/settings/l10n/zh_TW.json
@@ -243,9 +243,9 @@
"Current password" : "目前密碼",
"New password" : "新密碼",
"Change password" : "變更密碼",
- "Name" : "名稱",
"Language" : "語言",
"Help translate" : "幫助翻譯",
+ "Name" : "名稱",
"Get the apps to sync your files" : "下載應用程式來同步您的檔案",
"Desktop client" : "桌面客戶端",
"Android app" : "Android 應用程式",
diff --git a/settings/personal.php b/settings/personal.php
index 0b2781fb21b..e7a928f88bf 100644
--- a/settings/personal.php
+++ b/settings/personal.php
@@ -177,7 +177,7 @@ $tmpl->assign('groups', $groups2);
$formsAndMore = [];
$formsAndMore[]= ['anchor' => 'avatar', 'section-name' => $l->t('Personal info')];
$formsAndMore[]= ['anchor' => 'sessions', 'section-name' => $l->t('Sessions')];
-$formsAndMore[]= ['anchor' => 'devices', 'section-name' => $l->t('Devices')];
+$formsAndMore[]= ['anchor' => 'apppasswords', 'section-name' => $l->t('App passwords')];
$formsAndMore[]= ['anchor' => 'clientsbox', 'section-name' => $l->t('Sync clients')];
$forms=OC_App::getForms('personal');
diff --git a/settings/templates/personal.php b/settings/templates/personal.php
index feafb00683e..bcc803938ba 100644
--- a/settings/templates/personal.php
+++ b/settings/templates/personal.php
@@ -118,8 +118,7 @@ if($_['passwordChangeSupported']) {
?>
<form id="passwordform" class="section">
<h2 class="inlineblock"><?php p($l->t('Password'));?></h2>
- <div class="hidden icon-checkmark" id="password-changed"></div>
- <div class="hidden" id="password-error"><?php p($l->t('Unable to change your password'));?></div>
+ <div id="password-error-msg" class="msg success inlineblock" style="display: none;">Saved</div>
<br>
<label for="pass1" class="hidden-visually"><?php echo $l->t('Current password');?>: </label>
<input type="password" id="pass1" name="oldpassword"
@@ -138,6 +137,34 @@ if($_['passwordChangeSupported']) {
}
?>
+<form id="language" class="section">
+ <h2>
+ <label for="languageinput"><?php p($l->t('Language'));?></label>
+ </h2>
+ <select id="languageinput" name="lang" data-placeholder="<?php p($l->t('Language'));?>">
+ <option value="<?php p($_['activelanguage']['code']);?>">
+ <?php p($_['activelanguage']['name']);?>
+ </option>
+ <?php foreach($_['commonlanguages'] as $language):?>
+ <option value="<?php p($language['code']);?>">
+ <?php p($language['name']);?>
+ </option>
+ <?php endforeach;?>
+ <optgroup label="––––––––––"></optgroup>
+ <?php foreach($_['languages'] as $language):?>
+ <option value="<?php p($language['code']);?>">
+ <?php p($language['name']);?>
+ </option>
+ <?php endforeach;?>
+ </select>
+ <?php if (OC_Util::getEditionString() === ''): ?>
+ <a href="https://www.transifex.com/projects/p/owncloud/"
+ target="_blank" rel="noreferrer">
+ <em><?php p($l->t('Help translate'));?></em>
+ </a>
+ <?php endif; ?>
+</form>
+
<div id="sessions" class="section">
<h2><?php p($l->t('Sessions'));?></h2>
<span class="hidden-when-empty"><?php p($l->t('These are the web, desktop and mobile clients currently logged in to your account.'));?></span>
@@ -154,9 +181,9 @@ if($_['passwordChangeSupported']) {
</table>
</div>
-<div id="devices" class="section">
- <h2><?php p($l->t('Devices'));?></h2>
- <span class="hidden-when-empty"><?php p($l->t("You've linked these devices."));?></span>
+<div id="apppasswords" class="section">
+ <h2><?php p($l->t('App passwords'));?></h2>
+ <span class="hidden-when-empty"><?php p($l->t("You've linked these apps."));?></span>
<table>
<thead class="hidden-when-empty">
<tr>
@@ -168,45 +195,25 @@ if($_['passwordChangeSupported']) {
<tbody class="token-list icon-loading">
</tbody>
</table>
- <p><?php p($l->t('A device password is a passcode that gives an app or device permissions to access your account.'));?></p>
- <div id="device-token-form">
- <input id="device-token-name" type="text" placeholder="Device name">
- <button id="device-add-token" class="button">Create new device password</button>
+ <p><?php p($l->t('An app password is a passcode that gives an app or device permissions to access your %s account.', [$theme->getName()]));?></p>
+ <div id="app-password-form">
+ <input id="app-password-name" type="text" placeholder="<?php p($l->t('App name')); ?>">
+ <button id="add-app-password" class="button"><?php p($l->t('Create new app password')); ?></button>
</div>
- <div id="device-token-result" class="hidden">
- <input id="device-new-token" type="text" readonly="readonly"/>
- <button id="device-token-hide" class="button">Done</button>
+ <div id="app-password-result" class="hidden">
+ <span><?php p($l->t('Use the credentials below to configure your app or device.')); ?></span>
+ <div class="app-password-row">
+ <span class="app-password-label"><?php p($l->t('Username')); ?></span>
+ <input id="new-app-login-name" type="text" readonly="readonly"/>
+ </div>
+ <div class="app-password-row">
+ <span class="app-password-label"><?php p($l->t('Password')); ?></span>
+ <input id="new-app-password" type="text" readonly="readonly"/>
+ <button id="app-password-hide" class="button"><?php p($l->t('Done')); ?></button>
+ </div>
</div>
</div>
-<form id="language" class="section">
- <h2>
- <label for="languageinput"><?php p($l->t('Language'));?></label>
- </h2>
- <select id="languageinput" name="lang" data-placeholder="<?php p($l->t('Language'));?>">
- <option value="<?php p($_['activelanguage']['code']);?>">
- <?php p($_['activelanguage']['name']);?>
- </option>
- <?php foreach($_['commonlanguages'] as $language):?>
- <option value="<?php p($language['code']);?>">
- <?php p($language['name']);?>
- </option>
- <?php endforeach;?>
- <optgroup label="––––––––––"></optgroup>
- <?php foreach($_['languages'] as $language):?>
- <option value="<?php p($language['code']);?>">
- <?php p($language['name']);?>
- </option>
- <?php endforeach;?>
- </select>
- <?php if (false && OC_Util::getEditionString() === ''): ?>
- <a href="https://www.transifex.com/projects/p/owncloud/"
- target="_blank" rel="noreferrer">
- <em><?php p($l->t('Help translate'));?></em>
- </a>
- <?php endif; ?>
-</form>
-
<div id="clientsbox" class="section clientsbox">
<h2><?php p($l->t('Get the apps to sync your files'));?></h2>
<a href="<?php p($_['clients']['desktop']); ?>" rel="noreferrer" target="_blank">
diff --git a/tests/Core/Controller/OccControllerTest.php b/tests/Core/Controller/OccControllerTest.php
new file mode 100644
index 00000000000..682d9170096
--- /dev/null
+++ b/tests/Core/Controller/OccControllerTest.php
@@ -0,0 +1,143 @@
+<?php
+/**
+ * @author Victor Dubiniuk <dubiniuk@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, 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 Tests\Core\Controller;
+
+use OC\Console\Application;
+use OC\Core\Controller\OccController;
+use OCP\IConfig;
+use Symfony\Component\Console\Output\Output;
+use Test\TestCase;
+
+/**
+ * Class OccControllerTest
+ *
+ * @package OC\Core\Controller
+ */
+class OccControllerTest extends TestCase {
+
+ const TEMP_SECRET = 'test';
+
+ /** @var \OC\AppFramework\Http\Request | \PHPUnit_Framework_MockObject_MockObject */
+ private $request;
+ /** @var \OC\Core\Controller\OccController | \PHPUnit_Framework_MockObject_MockObject */
+ private $controller;
+ /** @var IConfig | \PHPUnit_Framework_MockObject_MockObject */
+ private $config;
+ /** @var Application | \PHPUnit_Framework_MockObject_MockObject */
+ private $console;
+
+ public function testFromInvalidLocation(){
+ $this->getControllerMock('example.org');
+
+ $response = $this->controller->execute('status', '');
+ $responseData = $response->getData();
+
+ $this->assertArrayHasKey('exitCode', $responseData);
+ $this->assertEquals(126, $responseData['exitCode']);
+
+ $this->assertArrayHasKey('details', $responseData);
+ $this->assertEquals('Web executor is not allowed to run from a different host', $responseData['details']);
+ }
+
+ public function testNotWhiteListedCommand(){
+ $this->getControllerMock('localhost');
+
+ $response = $this->controller->execute('missing_command', '');
+ $responseData = $response->getData();
+
+ $this->assertArrayHasKey('exitCode', $responseData);
+ $this->assertEquals(126, $responseData['exitCode']);
+
+ $this->assertArrayHasKey('details', $responseData);
+ $this->assertEquals('Command "missing_command" is not allowed to run via web request', $responseData['details']);
+ }
+
+ public function testWrongToken(){
+ $this->getControllerMock('localhost');
+
+ $response = $this->controller->execute('status', self::TEMP_SECRET . '-');
+ $responseData = $response->getData();
+
+ $this->assertArrayHasKey('exitCode', $responseData);
+ $this->assertEquals(126, $responseData['exitCode']);
+
+ $this->assertArrayHasKey('details', $responseData);
+ $this->assertEquals('updater.secret does not match the provided token', $responseData['details']);
+ }
+
+ public function testSuccess(){
+ $this->getControllerMock('localhost');
+ $this->console->expects($this->once())->method('run')
+ ->willReturnCallback(
+ function ($input, $output) {
+ /** @var Output $output */
+ $output->writeln('{"installed":true,"version":"9.1.0.8","versionstring":"9.1.0 beta 2","edition":""}');
+ return 0;
+ }
+ );
+
+ $response = $this->controller->execute('status', self::TEMP_SECRET, ['--output'=>'json']);
+ $responseData = $response->getData();
+
+ $this->assertArrayHasKey('exitCode', $responseData);
+ $this->assertEquals(0, $responseData['exitCode']);
+
+ $this->assertArrayHasKey('response', $responseData);
+ $decoded = json_decode($responseData['response'], true);
+
+ $this->assertArrayHasKey('installed', $decoded);
+ $this->assertEquals(true, $decoded['installed']);
+ }
+
+ private function getControllerMock($host){
+ $this->request = $this->getMockBuilder('OC\AppFramework\Http\Request')
+ ->setConstructorArgs([
+ ['server' => []],
+ \OC::$server->getSecureRandom(),
+ \OC::$server->getConfig()
+ ])
+ ->setMethods(['getRemoteAddress'])
+ ->getMock();
+
+ $this->request->expects($this->any())->method('getRemoteAddress')
+ ->will($this->returnValue($host));
+
+ $this->config = $this->getMockBuilder('\OCP\IConfig')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->config->expects($this->any())->method('getSystemValue')
+ ->with('updater.secret')
+ ->willReturn(password_hash(self::TEMP_SECRET, PASSWORD_DEFAULT));
+
+ $this->console = $this->getMockBuilder('\OC\Console\Application')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->controller = new OccController(
+ 'core',
+ $this->request,
+ $this->config,
+ $this->console
+ );
+ }
+
+}
diff --git a/tests/lib/Authentication/Token/DefaultTokenMapperTest.php b/tests/lib/Authentication/Token/DefaultTokenMapperTest.php
index 5d49f75aaa4..6b73cab5ed0 100644
--- a/tests/lib/Authentication/Token/DefaultTokenMapperTest.php
+++ b/tests/lib/Authentication/Token/DefaultTokenMapperTest.php
@@ -63,6 +63,7 @@ class DefaultTokenMapperTest extends TestCase {
'token' => $qb->createNamedParameter('9c5a2e661482b65597408a6bb6c4a3d1af36337381872ac56e445a06cdb7fea2b1039db707545c11027a4966919918b19d875a8b774840b18c6cbb7ae56fe206'),
'type' => $qb->createNamedParameter(IToken::TEMPORARY_TOKEN),
'last_activity' => $qb->createNamedParameter($this->time - 120, IQueryBuilder::PARAM_INT), // Two minutes ago
+ 'last_check' => $this->time - 60 * 10, // 10mins ago
])->execute();
$qb->insert('authtoken')->values([
'uid' => $qb->createNamedParameter('user2'),
@@ -72,6 +73,7 @@ class DefaultTokenMapperTest extends TestCase {
'token' => $qb->createNamedParameter('1504445f1524fc801035448a95681a9378ba2e83930c814546c56e5d6ebde221198792fd900c88ed5ead0555780dad1ebce3370d7e154941cd5de87eb419899b'),
'type' => $qb->createNamedParameter(IToken::TEMPORARY_TOKEN),
'last_activity' => $qb->createNamedParameter($this->time - 60 * 60 * 24 * 3, IQueryBuilder::PARAM_INT), // Three days ago
+ 'last_check' => $this->time - 10, // 10secs ago
])->execute();
$qb->insert('authtoken')->values([
'uid' => $qb->createNamedParameter('user1'),
@@ -81,6 +83,7 @@ class DefaultTokenMapperTest extends TestCase {
'token' => $qb->createNamedParameter('47af8697ba590fb82579b5f1b3b6e8066773a62100abbe0db09a289a62f5d980dc300fa3d98b01d7228468d1ab05c1aa14c8d14bd5b6eee9cdf1ac14864680c3'),
'type' => $qb->createNamedParameter(IToken::TEMPORARY_TOKEN),
'last_activity' => $qb->createNamedParameter($this->time - 120, IQueryBuilder::PARAM_INT), // Two minutes ago
+ 'last_check' => $this->time - 60 * 10, // 10mins ago
])->execute();
}
@@ -127,6 +130,7 @@ class DefaultTokenMapperTest extends TestCase {
$token->setToken('1504445f1524fc801035448a95681a9378ba2e83930c814546c56e5d6ebde221198792fd900c88ed5ead0555780dad1ebce3370d7e154941cd5de87eb419899b');
$token->setType(IToken::TEMPORARY_TOKEN);
$token->setLastActivity($this->time - 60 * 60 * 24 * 3);
+ $token->setLastCheck($this->time - 10);
$dbToken = $this->mapper->getToken($token->getToken());
diff --git a/tests/lib/Authentication/Token/DefaultTokenProviderTest.php b/tests/lib/Authentication/Token/DefaultTokenProviderTest.php
index 98cee208065..28a59529dec 100644
--- a/tests/lib/Authentication/Token/DefaultTokenProviderTest.php
+++ b/tests/lib/Authentication/Token/DefaultTokenProviderTest.php
@@ -97,14 +97,25 @@ class DefaultTokenProviderTest extends TestCase {
public function testUpdateToken() {
$tk = new DefaultToken();
+ $tk->setLastActivity($this->time - 200);
$this->mapper->expects($this->once())
->method('update')
->with($tk);
- $this->tokenProvider->updateToken($tk);
+ $this->tokenProvider->updateTokenActivity($tk);
$this->assertEquals($this->time, $tk->getLastActivity());
}
+
+ public function testUpdateTokenDebounce() {
+ $tk = new DefaultToken();
+ $tk->setLastActivity($this->time - 30);
+ $this->mapper->expects($this->never())
+ ->method('update')
+ ->with($tk);
+
+ $this->tokenProvider->updateTokenActivity($tk);
+ }
public function testGetTokenByUser() {
$user = $this->getMock('\OCP\IUser');
@@ -175,6 +186,39 @@ class DefaultTokenProviderTest extends TestCase {
$tokenProvider->getPassword($tk, $token);
}
+ public function testSetPassword() {
+ $token = new DefaultToken();
+ $tokenId = 'token123';
+ $password = '123456';
+
+ $this->config->expects($this->once())
+ ->method('getSystemValue')
+ ->with('secret')
+ ->will($this->returnValue('ocsecret'));
+ $this->crypto->expects($this->once())
+ ->method('encrypt')
+ ->with($password, $tokenId . 'ocsecret')
+ ->will($this->returnValue('encryptedpassword'));
+ $this->mapper->expects($this->once())
+ ->method('update')
+ ->with($token);
+
+ $this->tokenProvider->setPassword($token, $tokenId, $password);
+
+ $this->assertEquals('encryptedpassword', $token->getPassword());
+ }
+
+ /**
+ * @expectedException \OC\Authentication\Exceptions\InvalidTokenException
+ */
+ public function testSetPasswordInvalidToken() {
+ $token = $this->getMock('\OC\Authentication\Token\IToken');
+ $tokenId = 'token123';
+ $password = '123456';
+
+ $this->tokenProvider->setPassword($token, $tokenId, $password);
+ }
+
public function testInvalidateToken() {
$this->mapper->expects($this->once())
->method('invalidate')
@@ -207,30 +251,4 @@ class DefaultTokenProviderTest extends TestCase {
$this->tokenProvider->invalidateOldTokens();
}
- public function testValidateToken() {
- $token = 'sometoken';
- $dbToken = new DefaultToken();
- $this->mapper->expects($this->once())
- ->method('getToken')
- ->with(hash('sha512', $token))
- ->will($this->returnValue($dbToken));
-
- $actual = $this->tokenProvider->validateToken($token);
-
- $this->assertEquals($dbToken, $actual);
- }
-
- /**
- * @expectedException \OC\Authentication\Exceptions\InvalidTokenException
- */
- public function testValidateInvalidToken() {
- $token = 'sometoken';
- $this->mapper->expects($this->once())
- ->method('getToken')
- ->with(hash('sha512', $token))
- ->will($this->throwException(new DoesNotExistException('')));
-
- $this->tokenProvider->validateToken($token);
- }
-
}
diff --git a/tests/lib/Repair/RepairInvalidSharesTest.php b/tests/lib/Repair/RepairInvalidSharesTest.php
index a1e871bcc80..1ac42e53bf6 100644
--- a/tests/lib/Repair/RepairInvalidSharesTest.php
+++ b/tests/lib/Repair/RepairInvalidSharesTest.php
@@ -124,6 +124,93 @@ class RepairInvalidSharesTest extends TestCase {
}
/**
+ * Test remove expiration date for non-link shares
+ */
+ public function testAddShareLinkDeletePermission() {
+ $oldPerms = \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE;
+ $newPerms = \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE;
+
+ // share with old permissions
+ $qb = $this->connection->getQueryBuilder();
+ $qb->insert('share')
+ ->values([
+ 'share_type' => $qb->expr()->literal(Constants::SHARE_TYPE_LINK),
+ 'uid_owner' => $qb->expr()->literal('user1'),
+ 'item_type' => $qb->expr()->literal('folder'),
+ 'item_source' => $qb->expr()->literal(123),
+ 'item_target' => $qb->expr()->literal('/123'),
+ 'file_source' => $qb->expr()->literal(123),
+ 'file_target' => $qb->expr()->literal('/test'),
+ 'permissions' => $qb->expr()->literal($oldPerms),
+ 'stime' => $qb->expr()->literal(time()),
+ ])
+ ->execute();
+
+ $bogusShareId = $this->getLastShareId();
+
+ // share with read-only permissions
+ $qb = $this->connection->getQueryBuilder();
+ $qb->insert('share')
+ ->values([
+ 'share_type' => $qb->expr()->literal(Constants::SHARE_TYPE_LINK),
+ 'uid_owner' => $qb->expr()->literal('user1'),
+ 'item_type' => $qb->expr()->literal('folder'),
+ 'item_source' => $qb->expr()->literal(123),
+ 'item_target' => $qb->expr()->literal('/123'),
+ 'file_source' => $qb->expr()->literal(123),
+ 'file_target' => $qb->expr()->literal('/test'),
+ 'permissions' => $qb->expr()->literal(\OCP\Constants::PERMISSION_READ),
+ 'stime' => $qb->expr()->literal(time()),
+ ])
+ ->execute();
+
+ $keepThisShareId = $this->getLastShareId();
+
+ // user share to keep
+ $qb = $this->connection->getQueryBuilder();
+ $qb->insert('share')
+ ->values([
+ 'share_type' => $qb->expr()->literal(Constants::SHARE_TYPE_USER),
+ 'share_with' => $qb->expr()->literal('recipientuser1'),
+ 'uid_owner' => $qb->expr()->literal('user1'),
+ 'item_type' => $qb->expr()->literal('folder'),
+ 'item_source' => $qb->expr()->literal(123),
+ 'item_target' => $qb->expr()->literal('/123'),
+ 'file_source' => $qb->expr()->literal(123),
+ 'file_target' => $qb->expr()->literal('/test'),
+ 'permissions' => $qb->expr()->literal(3),
+ 'stime' => $qb->expr()->literal(time()),
+ ])
+ ->execute();
+
+ $keepThisShareId2 = $this->getLastShareId();
+
+ /** @var IOutput | \PHPUnit_Framework_MockObject_MockObject $outputMock */
+ $outputMock = $this->getMockBuilder('\OCP\Migration\IOutput')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->repair->run($outputMock);
+
+ $results = $this->connection->getQueryBuilder()
+ ->select('*')
+ ->from('share')
+ ->orderBy('permissions', 'ASC')
+ ->execute()
+ ->fetchAll();
+
+ $this->assertCount(3, $results);
+
+ $untouchedShare = $results[0];
+ $untouchedShare2 = $results[1];
+ $updatedShare = $results[2];
+ $this->assertEquals($keepThisShareId, $untouchedShare['id'], 'sanity check');
+ $this->assertEquals($keepThisShareId2, $untouchedShare2['id'], 'sanity check');
+ $this->assertEquals($bogusShareId, $updatedShare['id'], 'sanity check');
+ $this->assertEquals($newPerms, $updatedShare['permissions'], 'delete permission was added');
+ }
+
+ /**
* Test remove shares where the parent share does not exist anymore
*/
public function testSharesNonExistingParent() {
diff --git a/tests/lib/Share20/ManagerTest.php b/tests/lib/Share20/ManagerTest.php
index 4d4fcf1db73..689e47d8c52 100644
--- a/tests/lib/Share20/ManagerTest.php
+++ b/tests/lib/Share20/ManagerTest.php
@@ -20,6 +20,7 @@
*/
namespace Test\Share20;
+use OC\HintException;
use OCP\Files\IRootFolder;
use OCP\IUserManager;
use OCP\Share\Exceptions\ShareNotFound;
@@ -37,6 +38,8 @@ use OCP\Security\ISecureRandom;
use OCP\Security\IHasher;
use OCP\Files\Mount\IMountManager;
use OCP\IGroupManager;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\EventDispatcher\GenericEvent;
/**
* Class ManagerTest
@@ -70,9 +73,11 @@ class ManagerTest extends \Test\TestCase {
protected $userManager;
/** @var IRootFolder | \PHPUnit_Framework_MockObject_MockObject */
protected $rootFolder;
+ /** @var EventDispatcher | \PHPUnit_Framework_MockObject_MockObject */
+ protected $eventDispatcher;
public function setUp() {
-
+
$this->logger = $this->getMock('\OCP\ILogger');
$this->config = $this->getMock('\OCP\IConfig');
$this->secureRandom = $this->getMock('\OCP\Security\ISecureRandom');
@@ -81,6 +86,7 @@ class ManagerTest extends \Test\TestCase {
$this->groupManager = $this->getMock('\OCP\IGroupManager');
$this->userManager = $this->getMock('\OCP\IUserManager');
$this->rootFolder = $this->getMock('\OCP\Files\IRootFolder');
+ $this->eventDispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcher');
$this->l = $this->getMock('\OCP\IL10N');
$this->l->method('t')
@@ -100,7 +106,8 @@ class ManagerTest extends \Test\TestCase {
$this->l,
$this->factory,
$this->userManager,
- $this->rootFolder
+ $this->rootFolder,
+ $this->eventDispatcher
);
$this->defaultProvider = $this->getMockBuilder('\OC\Share20\DefaultShareProvider')
@@ -127,7 +134,8 @@ class ManagerTest extends \Test\TestCase {
$this->l,
$this->factory,
$this->userManager,
- $this->rootFolder
+ $this->rootFolder,
+ $this->eventDispatcher
]);
}
@@ -146,7 +154,7 @@ class ManagerTest extends \Test\TestCase {
$group = $this->getMock('\OCP\IGroup');
$group->method('getGID')->willReturn('sharedWithGroup');
-
+
return [
[\OCP\Share::SHARE_TYPE_USER, 'sharedWithUser'],
[\OCP\Share::SHARE_TYPE_GROUP, 'sharedWithGroup'],
@@ -543,16 +551,12 @@ class ManagerTest extends \Test\TestCase {
['core', 'shareapi_enforce_links_password', 'no', 'no'],
]));
- $hookListner = $this->getMockBuilder('Dummy')->setMethods(['listner'])->getMock();
- \OCP\Util::connectHook('\OC\Share', 'verifyPassword', $hookListner, 'listner');
-
- $hookListner->expects($this->once())
- ->method('listner')
- ->with([
- 'password' => 'password',
- 'accepted' => true,
- 'message' => ''
- ]);
+ $this->eventDispatcher->expects($this->once())->method('dispatch')
+ ->willReturnCallback(function($eventName, GenericEvent $event) {
+ $this->assertSame('OCP\PasswordPolicy::validate', $eventName);
+ $this->assertSame('password', $event->getSubject());
+ }
+ );
$result = $this->invokePrivate($this->manager, 'verifyPassword', ['password']);
$this->assertNull($result);
@@ -567,8 +571,14 @@ class ManagerTest extends \Test\TestCase {
['core', 'shareapi_enforce_links_password', 'no', 'no'],
]));
- $dummy = new DummyPassword();
- \OCP\Util::connectHook('\OC\Share', 'verifyPassword', $dummy, 'listner');
+ $this->eventDispatcher->expects($this->once())->method('dispatch')
+ ->willReturnCallback(function($eventName, GenericEvent $event) {
+ $this->assertSame('OCP\PasswordPolicy::validate', $eventName);
+ $this->assertSame('password', $event->getSubject());
+ throw new HintException('message', 'password not accepted');
+ }
+ );
+
$this->invokePrivate($this->manager, 'verifyPassword', ['password']);
}
@@ -1319,24 +1329,6 @@ class ManagerTest extends \Test\TestCase {
/**
* @expectedException Exception
- * @expectedExceptionMessage Link shares can't have delete permissions
- */
- public function testLinkCreateChecksDeletePermissions() {
- $share = $this->manager->newShare();
-
- $share->setPermissions(\OCP\Constants::PERMISSION_DELETE);
-
- $this->config
- ->method('getAppValue')
- ->will($this->returnValueMap([
- ['core', 'shareapi_allow_links', 'yes', 'yes'],
- ]));
-
- $this->invokePrivate($this->manager, 'linkCreateChecks', [$share]);
- }
-
- /**
- * @expectedException Exception
* @expectedExceptionMessage Public upload not allowed
*/
public function testLinkCreateChecksNoPublicUpload() {
@@ -2022,7 +2014,8 @@ class ManagerTest extends \Test\TestCase {
$this->l,
$factory,
$this->userManager,
- $this->rootFolder
+ $this->rootFolder,
+ $this->eventDispatcher
);
$share = $this->getMock('\OCP\Share\IShare');
@@ -2054,7 +2047,8 @@ class ManagerTest extends \Test\TestCase {
$this->l,
$factory,
$this->userManager,
- $this->rootFolder
+ $this->rootFolder,
+ $this->eventDispatcher
);
$share = $this->getMock('\OCP\Share\IShare');
@@ -2531,13 +2525,6 @@ class ManagerTest extends \Test\TestCase {
}
}
-class DummyPassword {
- public function listner($array) {
- $array['accepted'] = false;
- $array['message'] = 'password not accepted';
- }
-}
-
class DummyFactory implements IProviderFactory {
/** @var IShareProvider */
diff --git a/tests/lib/User/DatabaseTest.php b/tests/lib/User/DatabaseTest.php
index 270d90b35bc..16275f3b6c5 100644
--- a/tests/lib/User/DatabaseTest.php
+++ b/tests/lib/User/DatabaseTest.php
@@ -21,6 +21,9 @@
*/
namespace Test\User;
+use OC\HintException;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\EventDispatcher\GenericEvent;
/**
* Class DatabaseTest
@@ -30,6 +33,8 @@ namespace Test\User;
class DatabaseTest extends Backend {
/** @var array */
private $users;
+ /** @var EventDispatcher | \PHPUnit_Framework_MockObject_MockObject */
+ private $eventDispatcher;
public function getUser() {
$user = parent::getUser();
@@ -39,7 +44,10 @@ class DatabaseTest extends Backend {
protected function setUp() {
parent::setUp();
- $this->backend=new \OC\User\Database();
+
+ $this->eventDispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcher');
+
+ $this->backend=new \OC\User\Database($this->eventDispatcher);
}
protected function tearDown() {
@@ -51,4 +59,41 @@ class DatabaseTest extends Backend {
}
parent::tearDown();
}
+
+ public function testVerifyPasswordEvent() {
+ $user = $this->getUser();
+ $this->backend->createUser($user, 'pass1');
+
+ $this->eventDispatcher->expects($this->once())->method('dispatch')
+ ->willReturnCallback(
+ function ($eventName, GenericEvent $event) {
+ $this->assertSame('OCP\PasswordPolicy::validate', $eventName);
+ $this->assertSame('newpass', $event->getSubject());
+ }
+ );
+
+ $this->backend->setPassword($user, 'newpass');
+ $this->assertSame($user, $this->backend->checkPassword($user, 'newpass'));
+ }
+
+ /**
+ * @expectedException \OC\HintException
+ * @expectedExceptionMessage password change failed
+ */
+ public function testVerifyPasswordEventFail() {
+ $user = $this->getUser();
+ $this->backend->createUser($user, 'pass1');
+
+ $this->eventDispatcher->expects($this->once())->method('dispatch')
+ ->willReturnCallback(
+ function ($eventName, GenericEvent $event) {
+ $this->assertSame('OCP\PasswordPolicy::validate', $eventName);
+ $this->assertSame('newpass', $event->getSubject());
+ throw new HintException('password change failed', 'password change failed');
+ }
+ );
+
+ $this->backend->setPassword($user, 'newpass');
+ $this->assertSame($user, $this->backend->checkPassword($user, 'newpass'));
+ }
}
diff --git a/tests/lib/User/SessionTest.php b/tests/lib/User/SessionTest.php
index 7a34d42a2bc..447c6142f34 100644
--- a/tests/lib/User/SessionTest.php
+++ b/tests/lib/User/SessionTest.php
@@ -41,6 +41,7 @@ class SessionTest extends \Test\TestCase {
public function testGetUser() {
$token = new \OC\Authentication\Token\DefaultToken();
$token->setLoginName('User123');
+ $token->setLastCheck(200);
$expectedUser = $this->getMock('\OCP\IUser');
$expectedUser->expects($this->any())
@@ -56,41 +57,32 @@ class SessionTest extends \Test\TestCase {
$manager = $this->getMockBuilder('\OC\User\Manager')
->disableOriginalConstructor()
->getMock();
+ $session->expects($this->at(1))
+ ->method('get')
+ ->with('app_password')
+ ->will($this->returnValue(null)); // No password set -> browser session
$session->expects($this->once())
->method('getId')
->will($this->returnValue($sessionId));
$this->tokenProvider->expects($this->once())
->method('getToken')
+ ->with($sessionId)
->will($this->returnValue($token));
- $session->expects($this->at(2))
- ->method('get')
- ->with('last_login_check')
- ->will($this->returnValue(null)); // No check has been run yet
$this->tokenProvider->expects($this->once())
->method('getPassword')
->with($token, $sessionId)
- ->will($this->returnValue('password123'));
+ ->will($this->returnValue('passme'));
$manager->expects($this->once())
->method('checkPassword')
- ->with('User123', 'password123')
+ ->with('User123', 'passme')
->will($this->returnValue(true));
$expectedUser->expects($this->once())
->method('isEnabled')
->will($this->returnValue(true));
- $session->expects($this->at(3))
- ->method('set')
- ->with('last_login_check', 10000);
- $session->expects($this->at(4))
- ->method('get')
- ->with('last_token_update')
- ->will($this->returnValue(null)); // No check run so far
$this->tokenProvider->expects($this->once())
- ->method('updateToken')
+ ->method('updateTokenActivity')
->with($token);
- $session->expects($this->at(5))
- ->method('set')
- ->with('last_token_update', $this->equalTo(10000));
$manager->expects($this->any())
->method('get')
@@ -100,6 +92,7 @@ class SessionTest extends \Test\TestCase {
$userSession = new \OC\User\Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config);
$user = $userSession->getUser();
$this->assertSame($expectedUser, $user);
+ $this->assertSame(10000, $token->getLastCheck());
}
public function isLoggedInData() {
@@ -155,6 +148,10 @@ class SessionTest extends \Test\TestCase {
$session = $this->getMock('\OC\Session\Memory', array(), array(''));
$session->expects($this->once())
->method('regenerateId');
+ $this->tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with('bar')
+ ->will($this->throwException(new \OC\Authentication\Exceptions\InvalidTokenException()));
$session->expects($this->exactly(2))
->method('set')
->with($this->callback(function ($key) {
@@ -219,6 +216,10 @@ class SessionTest extends \Test\TestCase {
->method('set');
$session->expects($this->once())
->method('regenerateId');
+ $this->tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with('bar')
+ ->will($this->throwException(new \OC\Authentication\Exceptions\InvalidTokenException()));
$managerMethods = get_class_methods('\OC\User\Manager');
//keep following methods intact in order to ensure hooks are
@@ -252,11 +253,6 @@ class SessionTest extends \Test\TestCase {
public function testLoginInvalidPassword() {
$session = $this->getMock('\OC\Session\Memory', array(), array(''));
- $session->expects($this->never())
- ->method('set');
- $session->expects($this->once())
- ->method('regenerateId');
-
$managerMethods = get_class_methods('\OC\User\Manager');
//keep following methods intact in order to ensure hooks are
//working
@@ -268,10 +264,20 @@ class SessionTest extends \Test\TestCase {
}
}
$manager = $this->getMock('\OC\User\Manager', $managerMethods, array());
-
$backend = $this->getMock('\Test\Util\User\Dummy');
+ $userSession = new \OC\User\Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config);
$user = $this->getMock('\OC\User\User', array(), array('foo', $backend));
+
+ $session->expects($this->never())
+ ->method('set');
+ $session->expects($this->once())
+ ->method('regenerateId');
+ $this->tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with('bar')
+ ->will($this->throwException(new \OC\Authentication\Exceptions\InvalidTokenException()));
+
$user->expects($this->never())
->method('isEnabled');
$user->expects($this->never())
@@ -282,27 +288,59 @@ class SessionTest extends \Test\TestCase {
->with('foo', 'bar')
->will($this->returnValue(false));
- $userSession = new \OC\User\Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config);
$userSession->login('foo', 'bar');
}
public function testLoginNonExisting() {
$session = $this->getMock('\OC\Session\Memory', array(), array(''));
+ $manager = $this->getMock('\OC\User\Manager');
+ $backend = $this->getMock('\Test\Util\User\Dummy');
+ $userSession = new \OC\User\Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config);
+
$session->expects($this->never())
->method('set');
$session->expects($this->once())
->method('regenerateId');
+ $this->tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with('bar')
+ ->will($this->throwException(new \OC\Authentication\Exceptions\InvalidTokenException()));
- $manager = $this->getMock('\OC\User\Manager');
+ $manager->expects($this->once())
+ ->method('checkPassword')
+ ->with('foo', 'bar')
+ ->will($this->returnValue(false));
+
+ $userSession->login('foo', 'bar');
+ }
+ /**
+ * When using a device token, the loginname must match the one that was used
+ * when generating the token on the browser.
+ */
+ public function testLoginWithDifferentTokenLoginName() {
+ $session = $this->getMock('\OC\Session\Memory', array(), array(''));
+ $manager = $this->getMock('\OC\User\Manager');
$backend = $this->getMock('\Test\Util\User\Dummy');
+ $userSession = new \OC\User\Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config);
+ $username = 'user123';
+ $token = new \OC\Authentication\Token\DefaultToken();
+ $token->setLoginName($username);
+
+ $session->expects($this->never())
+ ->method('set');
+ $session->expects($this->once())
+ ->method('regenerateId');
+ $this->tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with('bar')
+ ->will($this->returnValue($token));
$manager->expects($this->once())
->method('checkPassword')
->with('foo', 'bar')
->will($this->returnValue(false));
- $userSession = new \OC\User\Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config);
$userSession->login('foo', 'bar');
}
@@ -354,24 +392,14 @@ class SessionTest extends \Test\TestCase {
->will($this->returnValue(true));
$userSession->expects($this->once())
->method('login')
- ->with('john', 'doe')
+ ->with('john', 'I-AM-AN-APP-PASSWORD')
->will($this->returnValue(true));
- $userSession->expects($this->once())
- ->method('supportsCookies')
- ->with($request)
- ->will($this->returnValue(true));
- $userSession->expects($this->once())
- ->method('getUser')
- ->will($this->returnValue($user));
- $user->expects($this->once())
- ->method('getUID')
- ->will($this->returnValue('user123'));
- $userSession->expects($this->once())
- ->method('createSessionToken')
- ->with($request, 'user123', 'john', 'doe');
-
- $this->assertTrue($userSession->logClientIn('john', 'doe', $request));
+ $session->expects($this->once())
+ ->method('set')
+ ->with('app_password', 'I-AM-AN-APP-PASSWORD');
+
+ $this->assertTrue($userSession->logClientIn('john', 'I-AM-AN-APP-PASSWORD', $request));
}
/**
@@ -706,9 +734,15 @@ class SessionTest extends \Test\TestCase {
->disableOriginalConstructor()
->getMock();
$session = new Memory('');
- $token = $this->getMock('\OC\Authentication\Token\IToken');
+ $token = new \OC\Authentication\Token\DefaultToken();
+ $token->setLoginName('fritz');
+ $token->setUid('fritz0');
+ $token->setLastCheck(100); // Needs check
$user = $this->getMock('\OCP\IUser');
- $userSession = new \OC\User\Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config);
+ $userSession = $this->getMockBuilder('\OC\User\Session')
+ ->setMethods(['logout'])
+ ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config])
+ ->getMock();
$request = $this->getMock('\OCP\IRequest');
$request->expects($this->once())
@@ -716,15 +750,12 @@ class SessionTest extends \Test\TestCase {
->with('Authorization')
->will($this->returnValue('token xxxxx'));
$this->tokenProvider->expects($this->once())
- ->method('validateToken')
+ ->method('getToken')
->with('xxxxx')
->will($this->returnValue($token));
- $token->expects($this->once())
- ->method('getUID')
- ->will($this->returnValue('user123'));
$manager->expects($this->once())
->method('get')
- ->with('user123')
+ ->with('fritz0')
->will($this->returnValue($user));
$user->expects($this->once())
->method('isEnabled')
@@ -744,40 +775,40 @@ class SessionTest extends \Test\TestCase {
->getMock();
$user = $this->getMock('\OCP\IUser');
- $token = $this->getMock('\OC\Authentication\Token\IToken');
+ $token = new \OC\Authentication\Token\DefaultToken();
+ $token->setLoginName('susan');
+ $token->setLastCheck(20);
$session->expects($this->once())
- ->method('getId')
- ->will($this->returnValue('sessionid'));
+ ->method('get')
+ ->with('app_password')
+ ->will($this->returnValue('APP-PASSWORD'));
$tokenProvider->expects($this->once())
->method('getToken')
- ->with('sessionid')
+ ->with('APP-PASSWORD')
->will($this->returnValue($token));
- $session->expects($this->once())
- ->method('get')
- ->with('last_login_check')
- ->will($this->returnValue(1000));
$timeFactory->expects($this->once())
->method('getTime')
- ->will($this->returnValue(5000));
+ ->will($this->returnValue(1000)); // more than 5min since last check
$tokenProvider->expects($this->once())
->method('getPassword')
- ->with($token, 'sessionid')
+ ->with($token, 'APP-PASSWORD')
->will($this->returnValue('123456'));
- $token->expects($this->once())
- ->method('getLoginName')
- ->will($this->returnValue('User5'));
$userManager->expects($this->once())
->method('checkPassword')
- ->with('User5', '123456')
+ ->with('susan', '123456')
->will($this->returnValue(true));
$user->expects($this->once())
->method('isEnabled')
->will($this->returnValue(false));
+ $tokenProvider->expects($this->once())
+ ->method('invalidateToken')
+ ->with('APP-PASSWORD');
$userSession->expects($this->once())
->method('logout');
- $this->invokePrivate($userSession, 'validateSession', [$user]);
+ $userSession->setUser($user);
+ $this->invokePrivate($userSession, 'validateSession');
}
public function testValidateSessionNoPassword() {
@@ -791,31 +822,96 @@ class SessionTest extends \Test\TestCase {
->getMock();
$user = $this->getMock('\OCP\IUser');
- $token = $this->getMock('\OC\Authentication\Token\IToken');
+ $token = new \OC\Authentication\Token\DefaultToken();
+ $token->setLastCheck(20);
$session->expects($this->once())
- ->method('getId')
- ->will($this->returnValue('sessionid'));
+ ->method('get')
+ ->with('app_password')
+ ->will($this->returnValue('APP-PASSWORD'));
$tokenProvider->expects($this->once())
->method('getToken')
- ->with('sessionid')
+ ->with('APP-PASSWORD')
->will($this->returnValue($token));
- $session->expects($this->once())
- ->method('get')
- ->with('last_login_check')
- ->will($this->returnValue(1000));
$timeFactory->expects($this->once())
->method('getTime')
- ->will($this->returnValue(5000));
+ ->will($this->returnValue(1000)); // more than 5min since last check
$tokenProvider->expects($this->once())
->method('getPassword')
- ->with($token, 'sessionid')
+ ->with($token, 'APP-PASSWORD')
->will($this->throwException(new \OC\Authentication\Exceptions\PasswordlessTokenException()));
- $session->expects($this->once())
- ->method('set')
- ->with('last_login_check', 5000);
+ $tokenProvider->expects($this->once())
+ ->method('updateToken')
+ ->with($token);
$this->invokePrivate($userSession, 'validateSession', [$user]);
+
+ $this->assertEquals(1000, $token->getLastCheck());
+ }
+
+ public function testUpdateSessionTokenPassword() {
+ $userManager = $this->getMock('\OCP\IUserManager');
+ $session = $this->getMock('\OCP\ISession');
+ $timeFactory = $this->getMock('\OCP\AppFramework\Utility\ITimeFactory');
+ $tokenProvider = $this->getMock('\OC\Authentication\Token\IProvider');
+ $userSession = new \OC\User\Session($userManager, $session, $timeFactory, $tokenProvider, $this->config);
+
+ $password = '123456';
+ $sessionId ='session1234';
+ $token = new \OC\Authentication\Token\DefaultToken();
+
+ $session->expects($this->once())
+ ->method('getId')
+ ->will($this->returnValue($sessionId));
+ $tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with($sessionId)
+ ->will($this->returnValue($token));
+ $tokenProvider->expects($this->once())
+ ->method('setPassword')
+ ->with($token, $sessionId, $password);
+
+ $userSession->updateSessionTokenPassword($password);
+ }
+
+ public function testUpdateSessionTokenPasswordNoSessionAvailable() {
+ $userManager = $this->getMock('\OCP\IUserManager');
+ $session = $this->getMock('\OCP\ISession');
+ $timeFactory = $this->getMock('\OCP\AppFramework\Utility\ITimeFactory');
+ $tokenProvider = $this->getMock('\OC\Authentication\Token\IProvider');
+ $userSession = new \OC\User\Session($userManager, $session, $timeFactory, $tokenProvider, $this->config);
+
+ $session->expects($this->once())
+ ->method('getId')
+ ->will($this->throwException(new \OCP\Session\Exceptions\SessionNotAvailableException()));
+
+ $userSession->updateSessionTokenPassword('1234');
+ }
+
+ public function testUpdateSessionTokenPasswordInvalidTokenException() {
+ $userManager = $this->getMock('\OCP\IUserManager');
+ $session = $this->getMock('\OCP\ISession');
+ $timeFactory = $this->getMock('\OCP\AppFramework\Utility\ITimeFactory');
+ $tokenProvider = $this->getMock('\OC\Authentication\Token\IProvider');
+ $userSession = new \OC\User\Session($userManager, $session, $timeFactory, $tokenProvider, $this->config);
+
+ $password = '123456';
+ $sessionId ='session1234';
+ $token = new \OC\Authentication\Token\DefaultToken();
+
+ $session->expects($this->once())
+ ->method('getId')
+ ->will($this->returnValue($sessionId));
+ $tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with($sessionId)
+ ->will($this->returnValue($token));
+ $tokenProvider->expects($this->once())
+ ->method('setPassword')
+ ->with($token, $sessionId, $password)
+ ->will($this->throwException(new \OC\Authentication\Exceptions\InvalidTokenException()));
+
+ $userSession->updateSessionTokenPassword($password);
}
}
diff --git a/version.php b/version.php
index 698636a2196..b439ffbbdad 100644
--- a/version.php
+++ b/version.php
@@ -25,7 +25,7 @@
// We only can count up. The 4. digit is only for the internal patchlevel to trigger DB upgrades
// between betas, final and RCs. This is _not_ the public version number. Reset minor/patchlevel
// when updating major/minor version number.
-$OC_Version = array(9, 1, 0, 8);
+$OC_Version = array(9, 1, 0, 10);
// The human readable string
$OC_VersionString = '9.1.0 beta 2';