summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
m---------3rdparty0
-rw-r--r--apps/comments/js/commentstabview.js2
-rw-r--r--apps/comments/tests/js/commentstabviewSpec.js4
-rw-r--r--apps/dav/appinfo/info.xml5
-rw-r--r--apps/dav/composer/composer/autoload_classmap.php1
-rw-r--r--apps/dav/composer/composer/autoload_static.php1
-rw-r--r--apps/dav/lib/CardDAV/SyncJob.php42
-rw-r--r--apps/dav/lib/Connector/Sabre/ExceptionLoggerPlugin.php4
-rw-r--r--apps/files/css/files.scss19
-rw-r--r--apps/files/css/mobile.scss27
-rw-r--r--apps/files/js/breadcrumb.js2
-rw-r--r--apps/files/js/file-upload.js2
-rw-r--r--apps/files/js/fileactions.js26
-rw-r--r--apps/files/js/filelist.js23
-rw-r--r--apps/files/js/files.js30
-rw-r--r--apps/files/lib/Helper.php2
-rw-r--r--apps/files/templates/appnavigation.php2
-rw-r--r--apps/files/templates/list.php4
-rw-r--r--apps/files/tests/js/appSpec.js19
-rw-r--r--apps/files/tests/js/breadcrumbSpec.js43
-rw-r--r--apps/files/tests/js/fileactionsSpec.js73
-rw-r--r--apps/files/tests/js/filelistSpec.js24
-rw-r--r--apps/files_external/js/mountsfilelist.js3
-rw-r--r--apps/files_external/js/statusmanager.js10
-rw-r--r--apps/files_external/lib/Lib/Storage/SMB.php14
-rw-r--r--apps/files_sharing/css/authenticate.css1
-rw-r--r--apps/files_sharing/js/public.js7
-rw-r--r--apps/files_sharing/lib/External/Storage.php2
-rw-r--r--apps/files_sharing/templates/authenticate.php1
-rw-r--r--apps/files_sharing/templates/public.php6
-rw-r--r--apps/files_trashbin/templates/index.php4
-rw-r--r--apps/theming/lib/ThemingDefaults.php6
-rw-r--r--apps/theming/tests/ThemingDefaultsTest.php2
-rw-r--r--apps/user_ldap/lib/Access.php7
-rw-r--r--apps/user_ldap/lib/Connection.php4
-rw-r--r--apps/user_ldap/lib/LDAP.php17
-rw-r--r--apps/user_ldap/lib/Proxy.php2
-rw-r--r--build/package.json1
-rw-r--r--core/Command/Db/AddMissingIndices.php91
-rw-r--r--core/Command/Maintenance/Repair.php2
-rw-r--r--core/Command/Maintenance/UpdateTheme.php2
-rw-r--r--core/Controller/LoginController.php1
-rw-r--r--core/Migrations/Version13000Date20170718121200.php1
-rw-r--r--core/css/fixes.scss7
-rw-r--r--core/css/header.scss14
-rw-r--r--core/css/mobile.scss2
-rw-r--r--core/css/styles.scss16
-rw-r--r--core/css/variables.scss2
-rw-r--r--core/js/jquery.avatar.js77
-rw-r--r--core/js/js.js40
-rw-r--r--core/js/lostpassword.js1
-rw-r--r--core/js/placeholder.js5
-rw-r--r--core/js/tests/specs/coreSpec.js427
-rw-r--r--core/js/tests/specs/jquery.avatarSpec.js137
-rw-r--r--core/register_command.php1
-rw-r--r--core/templates/login.php2
-rw-r--r--core/vendor/core.js6
-rw-r--r--core/vendor/snapjs/dist/latest/snap.js6
-rw-r--r--issue_template.md4
-rw-r--r--lib/composer/composer/autoload_classmap.php2
-rw-r--r--lib/composer/composer/autoload_static.php2
-rw-r--r--lib/private/App/AppManager.php2
-rw-r--r--lib/private/App/AppStore/Fetcher/Fetcher.php3
-rw-r--r--lib/private/AppFramework/DependencyInjection/DIContainer.php14
-rw-r--r--lib/private/AppFramework/Http/Request.php1
-rw-r--r--lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php81
-rw-r--r--lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php17
-rw-r--r--lib/private/Avatar.php17
-rw-r--r--lib/private/Collaboration/Collaborators/MailPlugin.php30
-rw-r--r--lib/private/Console/TimestampFormatter.php6
-rw-r--r--lib/private/Files/ObjectStore/Swift.php2
-rw-r--r--lib/private/Files/Storage/Common.php3
-rw-r--r--lib/private/Installer.php20
-rw-r--r--lib/private/IntegrityCheck/Checker.php2
-rw-r--r--lib/private/OCS/DiscoveryService.php2
-rw-r--r--lib/private/Security/RateLimiting/Backend/MemoryCache.php2
-rw-r--r--lib/private/Server.php31
-rw-r--r--lib/private/Template/CSSResourceLocator.php4
-rw-r--r--lib/private/Template/JSConfigHelper.php4
-rw-r--r--lib/private/Template/SCSSCacher.php31
-rw-r--r--lib/private/TemplateLayout.php2
-rw-r--r--lib/private/URLGenerator.php2
-rw-r--r--lib/private/Updater/VersionCheck.php4
-rw-r--r--lib/private/legacy/db.php4
-rw-r--r--lib/private/legacy/helper.php2
-rw-r--r--lib/public/IAvatar.php6
-rw-r--r--settings/Activity/SecurityProvider.php2
-rw-r--r--settings/ajax/uninstallapp.php4
-rw-r--r--settings/js/apps.js12
-rw-r--r--settings/js/settings/personalInfo.js14
-rw-r--r--settings/js/users/users.js3
-rw-r--r--tests/Core/Command/Maintenance/UpdateTheme.php2
-rw-r--r--tests/Core/Controller/LoginControllerTest.php33
-rw-r--r--tests/Settings/Activity/SecurityProviderTest.php4
-rw-r--r--tests/acceptance/features/app-files.feature11
-rw-r--r--tests/acceptance/features/bootstrap/FilesAppContext.php37
-rw-r--r--tests/acceptance/features/bootstrap/FilesSharingAppContext.php84
-rw-r--r--tests/karma.config.js2
-rw-r--r--tests/lib/App/AppManagerTest.php2
-rw-r--r--tests/lib/AppFramework/Middleware/Security/PasswordConfirmationMiddlewareTest.php129
-rw-r--r--tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php8
-rw-r--r--tests/lib/AvatarTest.php2
-rw-r--r--tests/lib/Collaboration/Collaborators/MailPluginTest.php139
-rw-r--r--tests/lib/IntegrityCheck/CheckerTest.php2
-rw-r--r--tests/lib/LegacyHelperTest.php11
-rw-r--r--tests/lib/Security/RateLimiting/Backend/MemoryCacheTest.php2
-rw-r--r--tests/lib/Template/SCSSCacherTest.php64
-rw-r--r--tests/lib/Updater/VersionCheckTest.php53
-rw-r--r--tests/lib/UrlGeneratorTest.php10
109 files changed, 1838 insertions, 374 deletions
diff --git a/3rdparty b/3rdparty
-Subproject f4e328bc4cc67011d206ca024483531a3b8c544
+Subproject 696f7683651fa2ee1f59cbde08c6f5fefbcaad0
diff --git a/apps/comments/js/commentstabview.js b/apps/comments/js/commentstabview.js
index 9b75cb4671e..bd89c8bbb49 100644
--- a/apps/comments/js/commentstabview.js
+++ b/apps/comments/js/commentstabview.js
@@ -512,7 +512,7 @@
_onTypeComment: function(ev) {
var $field = $(ev.target);
- var len = $field.val().length;
+ var len = $field.text().length;
var $submitButton = $field.data('submitButtonEl');
if (!$submitButton) {
$submitButton = $field.closest('form').find('.submit');
diff --git a/apps/comments/tests/js/commentstabviewSpec.js b/apps/comments/tests/js/commentstabviewSpec.js
index 813b2a72eae..0131bc7bce3 100644
--- a/apps/comments/tests/js/commentstabviewSpec.js
+++ b/apps/comments/tests/js/commentstabviewSpec.js
@@ -411,7 +411,7 @@ describe('OCA.Comments.CommentsTabView tests', function() {
expect($message.hasClass('error')).toEqual(false);
});
it('displays tooltip when limit is almost reached', function() {
- $message.val(createMessageWithLength(view._commentMaxLength - 2));
+ $message.text(createMessageWithLength(view._commentMaxLength - 2));
$message.trigger('change');
expect(tooltipStub.calledWith('show')).toEqual(true);
@@ -419,7 +419,7 @@ describe('OCA.Comments.CommentsTabView tests', function() {
expect($message.hasClass('error')).toEqual(false);
});
it('displays tooltip and disabled button when limit is exceeded', function() {
- $message.val(createMessageWithLength(view._commentMaxLength + 2));
+ $message.text(createMessageWithLength(view._commentMaxLength + 2));
$message.trigger('change');
expect(tooltipStub.calledWith('show')).toEqual(true);
diff --git a/apps/dav/appinfo/info.xml b/apps/dav/appinfo/info.xml
index 0f97289ba37..25a0d542e85 100644
--- a/apps/dav/appinfo/info.xml
+++ b/apps/dav/appinfo/info.xml
@@ -5,7 +5,7 @@
<description>WebDAV endpoint</description>
<licence>AGPL</licence>
<author>owncloud.org</author>
- <version>1.4.5</version>
+ <version>1.4.6</version>
<default_enable/>
<types>
<filesystem/>
@@ -17,9 +17,6 @@
<dependencies>
<nextcloud min-version="13" max-version="13" />
</dependencies>
- <background-jobs>
- <job>OCA\DAV\CardDAV\SyncJob</job>
- </background-jobs>
<repair-steps>
<post-migration>
<step>OCA\DAV\Migration\FixBirthdayCalendarComponent</step>
diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php
index 0687981a099..0bf6b25751e 100644
--- a/apps/dav/composer/composer/autoload_classmap.php
+++ b/apps/dav/composer/composer/autoload_classmap.php
@@ -59,7 +59,6 @@ return array(
'OCA\\DAV\\CardDAV\\ImageExportPlugin' => $baseDir . '/../lib/CardDAV/ImageExportPlugin.php',
'OCA\\DAV\\CardDAV\\PhotoCache' => $baseDir . '/../lib/CardDAV/PhotoCache.php',
'OCA\\DAV\\CardDAV\\Plugin' => $baseDir . '/../lib/CardDAV/Plugin.php',
- 'OCA\\DAV\\CardDAV\\SyncJob' => $baseDir . '/../lib/CardDAV/SyncJob.php',
'OCA\\DAV\\CardDAV\\SyncService' => $baseDir . '/../lib/CardDAV/SyncService.php',
'OCA\\DAV\\CardDAV\\UserAddressBooks' => $baseDir . '/../lib/CardDAV/UserAddressBooks.php',
'OCA\\DAV\\CardDAV\\Xml\\Groups' => $baseDir . '/../lib/CardDAV/Xml/Groups.php',
diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php
index 5c45a80e7dd..4be9b759695 100644
--- a/apps/dav/composer/composer/autoload_static.php
+++ b/apps/dav/composer/composer/autoload_static.php
@@ -74,7 +74,6 @@ class ComposerStaticInitDAV
'OCA\\DAV\\CardDAV\\ImageExportPlugin' => __DIR__ . '/..' . '/../lib/CardDAV/ImageExportPlugin.php',
'OCA\\DAV\\CardDAV\\PhotoCache' => __DIR__ . '/..' . '/../lib/CardDAV/PhotoCache.php',
'OCA\\DAV\\CardDAV\\Plugin' => __DIR__ . '/..' . '/../lib/CardDAV/Plugin.php',
- 'OCA\\DAV\\CardDAV\\SyncJob' => __DIR__ . '/..' . '/../lib/CardDAV/SyncJob.php',
'OCA\\DAV\\CardDAV\\SyncService' => __DIR__ . '/..' . '/../lib/CardDAV/SyncService.php',
'OCA\\DAV\\CardDAV\\UserAddressBooks' => __DIR__ . '/..' . '/../lib/CardDAV/UserAddressBooks.php',
'OCA\\DAV\\CardDAV\\Xml\\Groups' => __DIR__ . '/..' . '/../lib/CardDAV/Xml/Groups.php',
diff --git a/apps/dav/lib/CardDAV/SyncJob.php b/apps/dav/lib/CardDAV/SyncJob.php
deleted file mode 100644
index f0f8d51c2ca..00000000000
--- a/apps/dav/lib/CardDAV/SyncJob.php
+++ /dev/null
@@ -1,42 +0,0 @@
-<?php
-/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Joas Schilling <coding@schilljs.com>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- *
- * @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 OC\BackgroundJob\TimedJob;
-use OCA\DAV\AppInfo\Application;
-
-class SyncJob extends TimedJob {
-
- public function __construct() {
- // Run once a day
- $this->setInterval(24 * 60 * 60);
- }
-
- protected function run($argument) {
- $app = new Application();
- /** @var SyncService $ss */
- $ss = $app->getSyncService();
- $ss->syncInstance();
- }
-}
diff --git a/apps/dav/lib/Connector/Sabre/ExceptionLoggerPlugin.php b/apps/dav/lib/Connector/Sabre/ExceptionLoggerPlugin.php
index 68c9a1b415f..26e29e20d12 100644
--- a/apps/dav/lib/Connector/Sabre/ExceptionLoggerPlugin.php
+++ b/apps/dav/lib/Connector/Sabre/ExceptionLoggerPlugin.php
@@ -29,6 +29,7 @@ namespace OCA\DAV\Connector\Sabre;
use OCA\DAV\Connector\Sabre\Exception\PasswordLoginForbidden;
use OCP\Files\StorageNotAvailableException;
use OCP\ILogger;
+use Sabre\DAV\Exception\Conflict;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\InvalidSyncToken;
use Sabre\DAV\Exception\NotAuthenticated;
@@ -61,6 +62,9 @@ class ExceptionLoggerPlugin extends \Sabre\DAV\ServerPlugin {
// happens if some a client uses the wrong method for a given URL
// the error message itself is visible on the client side anyways
NotImplemented::class => true,
+ // happens when the parent directory is not present (for example when a
+ // move is done to a non-existent directory)
+ Conflict::class => true,
];
/** @var string */
diff --git a/apps/files/css/files.scss b/apps/files/css/files.scss
index 1be3c9216f0..b29ce9ea950 100644
--- a/apps/files/css/files.scss
+++ b/apps/files/css/files.scss
@@ -20,7 +20,11 @@
.actions.creatable {
position: relative;
- z-index: -30;
+ display: flex;
+ flex: 1 1;
+ .button:not(:last-child) {
+ margin-right: 3px;
+ }
}
#trash {
@@ -43,10 +47,6 @@
width: 100%;
}
-#filestable.has-controls {
- top: 44px;
-}
-
#filestable tbody tr {
height: 51px;
}
@@ -242,12 +242,12 @@ table th.column-last, table td.column-last {
/* Multiselect bar */
#filestable.multiselect {
- top: 95px;
+ top: 51px;
}
table.multiselect thead {
position: fixed;
top: 89px;
- z-index: 10;
+ z-index: 55;
-moz-box-sizing: border-box;
box-sizing: border-box;
left: 250px; /* sidebar */
@@ -542,6 +542,10 @@ a.action > img {
vertical-align: text-bottom;
margin-bottom: -1px;
}
+/* hide the delete icon in name column normal resolutions */
+table th#headerName .selectedActions .delete-selected {
+ display: none;
+}
#fileList td a {
a.action {
@@ -649,6 +653,7 @@ table tr.summary td {
table.dragshadow {
width:auto;
+ z-index: 100;
}
table.dragshadow td.filename {
padding-left:60px;
diff --git a/apps/files/css/mobile.scss b/apps/files/css/mobile.scss
index 12c9e4fa2d3..703ae499835 100644
--- a/apps/files/css/mobile.scss
+++ b/apps/files/css/mobile.scss
@@ -12,7 +12,6 @@
min-width: initial !important;
}
-/* hide size and date columns */
table th#headerSize,
table td.filesize,
table th#headerDate,
@@ -52,6 +51,11 @@ table td.filename .nametext .innernametext {
max-width: calc(100% - 175px) !important;
}
+/* show the delete icon in name column in lower resolutions */
+table th#headerName .selectedActions .delete-selected {
+ display: inline;
+}
+
/* proper notification area for multi line messages */
#notification-container {
display: flex;
@@ -69,4 +73,25 @@ table td.filename .nametext .innernametext {
display: block !important;
}
+/* ensure that it is visible over #app-content */
+table.dragshadow {
+ z-index: 1000;
+}
+
+}
+@media only screen and (max-width: 480px) {
+ /* Only show icons */
+ table th .selectedActions a span:not(.icon) {
+ display: none;
+ }
+
+ /* Increase touch area for the icons */
+ table th .selectedActions a {
+ padding: 17px 14px;
+ }
+
+ /* Remove the margin to reduce the overlap between the name and the icons */
+ table.multiselect th .columntitle.name {
+ margin-left: 0;
+ }
}
diff --git a/apps/files/js/breadcrumb.js b/apps/files/js/breadcrumb.js
index 35aeb8d357d..20b15e3cb93 100644
--- a/apps/files/js/breadcrumb.js
+++ b/apps/files/js/breadcrumb.js
@@ -331,7 +331,7 @@
// Used for testing since this.$el.parent fails
if (!this.availableWidth) {
- this.usedWidth = this.$el.parent().width() - (this.$el.parent().find('.button').length + 1) * 44;
+ this.usedWidth = this.$el.parent().width() - this.$el.parent().find('.actions.creatable').width();
} else {
this.usedWidth = this.availableWidth;
}
diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js
index 8e72b3cdb41..8e95abcb5e8 100644
--- a/apps/files/js/file-upload.js
+++ b/apps/files/js/file-upload.js
@@ -130,7 +130,7 @@ OC.FileUpload.prototype = {
},
/**
- * Get full path for the target file,
+ * Get full path for the target file,
* including relative path and file name.
*
* @return {String} full path
diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js
index 6c031ab06d5..2fb7dfba29f 100644
--- a/apps/files/js/fileactions.js
+++ b/apps/files/js/fileactions.js
@@ -47,14 +47,6 @@
*/
$el: null,
- /**
- * List of handlers to be notified whenever a register() or
- * setDefault() was called.
- *
- * @member {Function[]}
- */
- _updateListeners: {},
-
_fileActionTriggerTemplate: null,
/**
@@ -142,7 +134,22 @@
var mime = action.mime;
var name = action.name;
var actionSpec = {
- action: action.actionHandler,
+ action: function(fileName, context) {
+ // Actions registered in one FileAction may be executed on a
+ // different one (for example, due to the "merge" function),
+ // so the listeners have to be updated on the FileActions
+ // from the context instead of on the one in which it was
+ // originally registered.
+ if (context && context.fileActions) {
+ context.fileActions._notifyUpdateListeners('beforeTriggerAction', {action: actionSpec, fileName: fileName, context: context});
+ }
+
+ action.actionHandler(fileName, context);
+
+ if (context && context.fileActions) {
+ context.fileActions._notifyUpdateListeners('afterTriggerAction', {action: actionSpec, fileName: fileName, context: context});
+ }
+ },
name: name,
displayName: action.displayName,
mime: mime,
@@ -174,7 +181,6 @@
this.defaults = {};
this.icons = {};
this.currentFile = null;
- this._updateListeners = [];
},
/**
* Sets the default action for a given mime type.
diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js
index fa9819b78b5..d0c0fc1a7fc 100644
--- a/apps/files/js/filelist.js
+++ b/apps/files/js/filelist.js
@@ -676,8 +676,28 @@
$(event.target).closest('a').blur();
}
} else {
- this._updateDetailsView($tr.attr('data-file'));
+ // Even if there is no Details action the default event
+ // handler is prevented for consistency (although there
+ // should always be a Details action); otherwise the link
+ // would be downloaded by the browser when the user expected
+ // the details to be shown.
event.preventDefault();
+ var filename = $tr.attr('data-file');
+ this.fileActions.currentFile = $tr.find('td');
+ var mime = this.fileActions.getCurrentMimeType();
+ var type = this.fileActions.getCurrentType();
+ var permissions = this.fileActions.getCurrentPermissions();
+ var action = this.fileActions.get(mime, type, permissions)['Details'];
+ if (action) {
+ // also set on global object for legacy apps
+ window.FileActions.currentFile = this.fileActions.currentFile;
+ action(filename, {
+ $file: $tr,
+ fileList: this,
+ fileActions: this.fileActions,
+ dir: $tr.attr('data-path') || this.getCurrentDirectory()
+ });
+ }
}
}
},
@@ -1751,7 +1771,6 @@
return true;
}
- // TODO: parse remaining quota from PROPFIND response
this.updateStorageStatistics(true);
// first entry is the root
diff --git a/apps/files/js/files.js b/apps/files/js/files.js
index 017bf7ecf41..a1e59015b1d 100644
--- a/apps/files/js/files.js
+++ b/apps/files/js/files.js
@@ -29,6 +29,7 @@
state.dir = null;
state.call = null;
Files.updateMaxUploadFilesize(response);
+ Files.updateQuota(response);
});
},
/**
@@ -77,6 +78,32 @@
},
+ updateQuota:function(response) {
+ if (response === undefined) {
+ return;
+ }
+ if (response.data !== undefined
+ && response.data.quota !== undefined
+ && response.data.used !== undefined
+ && response.data.usedSpacePercent !== undefined) {
+ var humanUsed = OC.Util.humanFileSize(response.data.used, true);
+ var humanQuota = OC.Util.humanFileSize(response.data.quota, true);
+ if (response.data.quota > 0) {
+ $('#quota').attr('data-original-title', Math.floor(response.data.used/response.data.quota*1000)/10 + '%');
+ $('#quota progress').val(response.data.usedSpacePercent);
+ $('#quotatext').text(t('files', '{used} of {quota} used', {used: humanUsed, quota: humanQuota}));
+ } else {
+ $('#quotatext').text(t('files', '{used} used', {used: humanUsed}));
+ }
+ if (response.data.usedSpacePercent > 80) {
+ $('#quota progress').addClass('warn');
+ } else {
+ $('#quota progress').removeClass('warn');
+ }
+ }
+
+ },
+
/**
* Fix path name by removing double slash at the beginning, if any
*/
@@ -101,6 +128,8 @@
throw t('files', '"{name}" is an invalid file name.', {name: name});
} else if (trimmedName.length === 0) {
throw t('files', 'File name cannot be empty.');
+ } else if (trimmedName.indexOf('/') !== -1) {
+ throw t('files', '"/" is not allowed inside a file name.');
} else if (OC.fileIsBlacklisted(trimmedName)) {
throw t('files', '"{name}" is not an allowed filetype', {name: name});
}
@@ -383,7 +412,6 @@ var dragOptions={
revert: 'invalid',
revertDuration: 300,
opacity: 0.7,
- zIndex: 100,
appendTo: 'body',
cursorAt: { left: 24, top: 18 },
helper: createDragShadow,
diff --git a/apps/files/lib/Helper.php b/apps/files/lib/Helper.php
index ab952c97dfb..9d9717c9401 100644
--- a/apps/files/lib/Helper.php
+++ b/apps/files/lib/Helper.php
@@ -56,6 +56,8 @@ class Helper {
'uploadMaxFilesize' => $maxUploadFileSize,
'maxHumanFilesize' => $maxHumanFileSize,
'freeSpace' => $storageInfo['free'],
+ 'quota' => $storageInfo['quota'],
+ 'used' => $storageInfo['used'],
'usedSpacePercent' => (int)$storageInfo['relative'],
'owner' => $storageInfo['owner'],
'ownerDisplayName' => $storageInfo['ownerDisplayName'],
diff --git a/apps/files/templates/appnavigation.php b/apps/files/templates/appnavigation.php
index 955cd03a019..5d270914ff1 100644
--- a/apps/files/templates/appnavigation.php
+++ b/apps/files/templates/appnavigation.php
@@ -11,7 +11,7 @@
</a>
</li>
<?php } ?>
- <li id="quota" class="pinned <?php
+ <li id="quota" class="pinned <?php p($pinned===0?'first-pinned ':'') ?><?php
if ($_['quota'] !== \OCP\Files\FileInfo::SPACE_UNLIMITED) {
?>has-tooltip" title="<?php p($_['usage_relative'] . '%');
} ?>">
diff --git a/apps/files/templates/list.php b/apps/files/templates/list.php
index 92f64a52a28..fd5423c334f 100644
--- a/apps/files/templates/list.php
+++ b/apps/files/templates/list.php
@@ -59,6 +59,10 @@
<span class="icon icon-download"></span>
<span><?php p($l->t('Download'))?></span>
</a>
+ <a href="" class="delete-selected">
+ <span class="icon icon-delete"></span>
+ <span><?php p($l->t('Delete'))?></span>
+ </a>
</span>
</div>
</th>
diff --git a/apps/files/tests/js/appSpec.js b/apps/files/tests/js/appSpec.js
index b9c323e7c12..5728991e197 100644
--- a/apps/files/tests/js/appSpec.js
+++ b/apps/files/tests/js/appSpec.js
@@ -112,9 +112,22 @@ describe('OCA.Files.App tests', function() {
App.initialize();
var actions = App.fileList.fileActions.actions;
- expect(actions.all.OverwriteThis.action).toBe(actionStub);
- expect(actions.all.LegacyTest.action).toBe(legacyActionStub);
- expect(actions.all.RegularTest.action).toBe(actionStub);
+ var context = { fileActions: sinon.createStubInstance(OCA.Files.FileActions) };
+ actions.all.OverwriteThis.action('testFileName', context);
+ expect(actionStub.calledOnce).toBe(true);
+ expect(context.fileActions._notifyUpdateListeners.callCount).toBe(2);
+ expect(context.fileActions._notifyUpdateListeners.getCall(0).calledWith('beforeTriggerAction')).toBe(true);
+ expect(context.fileActions._notifyUpdateListeners.getCall(1).calledWith('afterTriggerAction')).toBe(true);
+ actions.all.LegacyTest.action('testFileName', context);
+ expect(legacyActionStub.calledOnce).toBe(true);
+ expect(context.fileActions._notifyUpdateListeners.callCount).toBe(4);
+ expect(context.fileActions._notifyUpdateListeners.getCall(2).calledWith('beforeTriggerAction')).toBe(true);
+ expect(context.fileActions._notifyUpdateListeners.getCall(3).calledWith('afterTriggerAction')).toBe(true);
+ actions.all.RegularTest.action('testFileName', context);
+ expect(actionStub.calledTwice).toBe(true);
+ expect(context.fileActions._notifyUpdateListeners.callCount).toBe(6);
+ expect(context.fileActions._notifyUpdateListeners.getCall(4).calledWith('beforeTriggerAction')).toBe(true);
+ expect(context.fileActions._notifyUpdateListeners.getCall(5).calledWith('afterTriggerAction')).toBe(true);
// default one still there
expect(actions.dir.Open.action).toBeDefined();
});
diff --git a/apps/files/tests/js/breadcrumbSpec.js b/apps/files/tests/js/breadcrumbSpec.js
index dd3eac017ec..5ec5ad2d6e8 100644
--- a/apps/files/tests/js/breadcrumbSpec.js
+++ b/apps/files/tests/js/breadcrumbSpec.js
@@ -242,6 +242,17 @@ describe('OCA.Files.BreadCrumb tests', function() {
dummyDir = '/short name/longer name/looooooooooooonger/' +
'even longer long long long longer long/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/last one';
+ bc = new BreadCrumb();
+ // append dummy navigation and controls
+ // as they are currently used for measurements
+ $('#testArea').append(
+ '<div id="controls"></div>'
+ );
+ $('#controls').append(bc.$el);
+
+ // triggers resize implicitly
+ bc.setDirectory(dummyDir);
+
// using hard-coded widths (pre-measured) to avoid getting different
// results on different browsers due to font engine differences
// 51px is default size for menu and home
@@ -250,14 +261,6 @@ describe('OCA.Files.BreadCrumb tests', function() {
$('div.crumb').each(function(index){
$(this).css('width', widths[index]);
});
-
- bc = new BreadCrumb();
- // append dummy navigation and controls
- // as they are currently used for measurements
- $('#testArea').append(
- '<div id="controls"></div>'
- );
- $('#controls').append(bc.$el);
});
afterEach(function() {
bc = null;
@@ -267,8 +270,6 @@ describe('OCA.Files.BreadCrumb tests', function() {
bc.setMaxWidth(500);
- // triggers resize implicitly
- bc.setDirectory(dummyDir);
$crumbs = bc.$el.find('.crumb');
// Menu and home are always visible
@@ -282,6 +283,24 @@ describe('OCA.Files.BreadCrumb tests', function() {
expect($crumbs.eq(6).hasClass('hidden')).toEqual(true);
expect($crumbs.eq(7).hasClass('hidden')).toEqual(false);
});
+ it('Hides breadcrumbs to fit max allowed width', function() {
+ var $crumbs;
+
+ bc.setMaxWidth(700);
+
+ $crumbs = bc.$el.find('.crumb');
+
+ // Menu and home are always visible
+ expect($crumbs.eq(0).hasClass('hidden')).toEqual(false);
+ expect($crumbs.eq(1).hasClass('hidden')).toEqual(false);
+
+ expect($crumbs.eq(2).hasClass('hidden')).toEqual(false);
+ expect($crumbs.eq(3).hasClass('hidden')).toEqual(false);
+ expect($crumbs.eq(4).hasClass('hidden')).toEqual(true);
+ expect($crumbs.eq(5).hasClass('hidden')).toEqual(true);
+ expect($crumbs.eq(6).hasClass('hidden')).toEqual(false);
+ expect($crumbs.eq(7).hasClass('hidden')).toEqual(false);
+ });
it('Updates the breadcrumbs when reducing max allowed width', function() {
var $crumbs;
@@ -290,7 +309,7 @@ describe('OCA.Files.BreadCrumb tests', function() {
$crumbs = bc.$el.find('.crumb');
// Menu is hidden
- expect($crumbs.eq(0).hasClass('hidden')).toEqual(false);
+ expect($crumbs.eq(0).hasClass('hidden')).toEqual(true);
// triggers resize implicitly
bc.setDirectory(dummyDir);
@@ -304,7 +323,7 @@ describe('OCA.Files.BreadCrumb tests', function() {
expect($crumbs.eq(2).hasClass('hidden')).toEqual(false);
expect($crumbs.eq(3).hasClass('hidden')).toEqual(false);
- expect($crumbs.eq(4).hasClass('hidden')).toEqual(false);
+ expect($crumbs.eq(4).hasClass('hidden')).toEqual(true);
expect($crumbs.eq(5).hasClass('hidden')).toEqual(false);
expect($crumbs.eq(6).hasClass('hidden')).toEqual(false);
expect($crumbs.eq(7).hasClass('hidden')).toEqual(false);
diff --git a/apps/files/tests/js/fileactionsSpec.js b/apps/files/tests/js/fileactionsSpec.js
index 75a18713696..2dc8bb50920 100644
--- a/apps/files/tests/js/fileactionsSpec.js
+++ b/apps/files/tests/js/fileactionsSpec.js
@@ -299,6 +299,7 @@ describe('OCA.Files.FileActions tests', function() {
clock.restore();
});
it('passes context to action handler', function() {
+ var notifyUpdateListenersSpy = sinon.spy(fileList.fileActions, '_notifyUpdateListeners');
$tr.find('.action-test').click();
expect(actionStub.calledOnce).toEqual(true);
expect(actionStub.getCall(0).args[0]).toEqual('testName.txt');
@@ -309,6 +310,22 @@ describe('OCA.Files.FileActions tests', function() {
expect(context.dir).toEqual('/subdir');
expect(context.fileInfoModel.get('name')).toEqual('testName.txt');
+ expect(notifyUpdateListenersSpy.calledTwice).toEqual(true);
+ expect(notifyUpdateListenersSpy.calledBefore(actionStub)).toEqual(true);
+ expect(notifyUpdateListenersSpy.calledAfter(actionStub)).toEqual(true);
+ expect(notifyUpdateListenersSpy.getCall(0).args[0]).toEqual('beforeTriggerAction');
+ expect(notifyUpdateListenersSpy.getCall(0).args[1]).toEqual({
+ action: fileActions.getActions('all', OCA.Files.FileActions.TYPE_INLINE, OC.PERMISSION_READ)['Test'],
+ fileName: 'testName.txt',
+ context: context
+ });
+ expect(notifyUpdateListenersSpy.getCall(1).args[0]).toEqual('afterTriggerAction');
+ expect(notifyUpdateListenersSpy.getCall(1).args[1]).toEqual({
+ action: fileActions.getActions('all', OCA.Files.FileActions.TYPE_INLINE, OC.PERMISSION_READ)['Test'],
+ fileName: 'testName.txt',
+ context: context
+ });
+
// when data-path is defined
actionStub.reset();
$tr.attr('data-path', '/somepath');
@@ -317,6 +334,7 @@ describe('OCA.Files.FileActions tests', function() {
expect(context.dir).toEqual('/somepath');
});
it('also triggers action handler when calling triggerAction()', function() {
+ var notifyUpdateListenersSpy = sinon.spy(fileList.fileActions, '_notifyUpdateListeners');
var model = new OCA.Files.FileInfoModel({
id: 1,
name: 'Test.txt',
@@ -331,7 +349,62 @@ describe('OCA.Files.FileActions tests', function() {
expect(actionStub.getCall(0).args[1].fileList).toEqual(fileList);
expect(actionStub.getCall(0).args[1].fileActions).toEqual(fileActions);
expect(actionStub.getCall(0).args[1].fileInfoModel).toEqual(model);
+
+ expect(notifyUpdateListenersSpy.calledTwice).toEqual(true);
+ expect(notifyUpdateListenersSpy.calledBefore(actionStub)).toEqual(true);
+ expect(notifyUpdateListenersSpy.calledAfter(actionStub)).toEqual(true);
+ expect(notifyUpdateListenersSpy.getCall(0).args[0]).toEqual('beforeTriggerAction');
+ expect(notifyUpdateListenersSpy.getCall(0).args[1]).toEqual({
+ action: fileActions.getActions('all', OCA.Files.FileActions.TYPE_INLINE, OC.PERMISSION_READ)['Test'],
+ fileName: 'Test.txt',
+ context: {
+ fileActions: fileActions,
+ fileInfoModel: model,
+ dir: '/subdir',
+ fileList: fileList,
+ $file: fileList.findFileEl('Test.txt')
+ }
+ });
+ expect(notifyUpdateListenersSpy.getCall(1).args[0]).toEqual('afterTriggerAction');
+ expect(notifyUpdateListenersSpy.getCall(1).args[1]).toEqual({
+ action: fileActions.getActions('all', OCA.Files.FileActions.TYPE_INLINE, OC.PERMISSION_READ)['Test'],
+ fileName: 'Test.txt',
+ context: {
+ fileActions: fileActions,
+ fileInfoModel: model,
+ dir: '/subdir',
+ fileList: fileList,
+ $file: fileList.findFileEl('Test.txt')
+ }
+ });
});
+ it('triggers listener events when invoked directly', function() {
+ var context = {fileActions: new OCA.Files.FileActions()}
+ var notifyUpdateListenersSpy = sinon.spy(context.fileActions, '_notifyUpdateListeners');
+ var testAction = fileActions.get('all', OCA.Files.FileActions.TYPE_INLINE, OC.PERMISSION_READ)['Test'];
+
+ testAction('Test.txt', context);
+
+ expect(actionStub.calledOnce).toEqual(true);
+ expect(actionStub.getCall(0).args[0]).toEqual('Test.txt');
+ expect(actionStub.getCall(0).args[1]).toBe(context);
+
+ expect(notifyUpdateListenersSpy.calledTwice).toEqual(true);
+ expect(notifyUpdateListenersSpy.calledBefore(actionStub)).toEqual(true);
+ expect(notifyUpdateListenersSpy.calledAfter(actionStub)).toEqual(true);
+ expect(notifyUpdateListenersSpy.getCall(0).args[0]).toEqual('beforeTriggerAction');
+ expect(notifyUpdateListenersSpy.getCall(0).args[1]).toEqual({
+ action: fileActions.getActions('all', OCA.Files.FileActions.TYPE_INLINE, OC.PERMISSION_READ)['Test'],
+ fileName: 'Test.txt',
+ context: context
+ });
+ expect(notifyUpdateListenersSpy.getCall(1).args[0]).toEqual('afterTriggerAction');
+ expect(notifyUpdateListenersSpy.getCall(1).args[1]).toEqual({
+ action: fileActions.getActions('all', OCA.Files.FileActions.TYPE_INLINE, OC.PERMISSION_READ)['Test'],
+ fileName: 'Test.txt',
+ context: context
+ });
+ }),
describe('actions menu', function() {
it('shows actions menu inside row when clicking the menu trigger', function() {
expect($tr.find('td.filename .fileActionsMenu').length).toEqual(0);
diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js
index 83926b24fee..fc5a6c18f95 100644
--- a/apps/files/tests/js/filelistSpec.js
+++ b/apps/files/tests/js/filelistSpec.js
@@ -2489,6 +2489,30 @@ describe('OCA.Files.FileList tests', function() {
expect(context.fileActions).toBeDefined();
expect(context.dir).toEqual('/subdir');
});
+ it('Clicking on an empty space of the file row will trigger the "Details" action', function() {
+ var detailsActionStub = sinon.stub();
+ fileList.setFiles(testFiles);
+ // Override the "Details" action set internally by the FileList for
+ // easier testing.
+ fileList.fileActions.registerAction({
+ mime: 'all',
+ name: 'Details',
+ permissions: OC.PERMISSION_NONE,
+ actionHandler: detailsActionStub
+ });
+ // Ensure that the action works even if fileActions.currentFile is
+ // not set.
+ fileList.fileActions.currentFile = null;
+ var $tr = fileList.findFileEl('One.txt');
+ $tr.find('td.filename a.name').click();
+ expect(detailsActionStub.calledOnce).toEqual(true);
+ expect(detailsActionStub.getCall(0).args[0]).toEqual('One.txt');
+ var context = detailsActionStub.getCall(0).args[1];
+ expect(context.$file.is($tr)).toEqual(true);
+ expect(context.fileList).toBe(fileList);
+ expect(context.fileActions).toBe(fileList.fileActions);
+ expect(context.dir).toEqual('/subdir');
+ });
it('redisplays actions when new actions have been registered', function() {
var actionStub = sinon.stub();
var readyHandler = sinon.stub();
diff --git a/apps/files_external/js/mountsfilelist.js b/apps/files_external/js/mountsfilelist.js
index 3beee6f73b2..90b90e38745 100644
--- a/apps/files_external/js/mountsfilelist.js
+++ b/apps/files_external/js/mountsfilelist.js
@@ -30,6 +30,8 @@
/** @lends OCA.External.FileList.prototype */ {
appName: 'External storages',
+ _allowSelection: false,
+
/**
* @private
*/
@@ -56,7 +58,6 @@
$scopeColumn.find('span').text(scopeText);
$backendColumn.text(fileData.backend);
$tr.find('td.filename').after($scopeColumn).after($backendColumn);
- $tr.find('td.filename input:checkbox').remove();
return $tr;
},
diff --git a/apps/files_external/js/statusmanager.js b/apps/files_external/js/statusmanager.js
index 6c1a965c33d..3850351d213 100644
--- a/apps/files_external/js/statusmanager.js
+++ b/apps/files_external/js/statusmanager.js
@@ -489,7 +489,7 @@ OCA.External.StatusManager.Utils = {
}
});
- var icon = trFolder.find('td:first-child div.thumbnail');
+ var icon = trFolder.find('td.filename div.thumbnail');
icon.each(function () {
var thisElement = $(this);
if (thisElement.data('oldImage') === undefined) {
@@ -510,7 +510,7 @@ OCA.External.StatusManager.Utils = {
trFolder = $('#fileList tr[data-file=\"' + OCA.External.StatusManager.Utils.jqSelEscape(folder) + '\"]');
}
trFolder.removeClass('externalErroredRow').removeClass('externalDisabledRow');
- var tdChilds = trFolder.find("td:first-child div.thumbnail");
+ var tdChilds = trFolder.find("td.filename div.thumbnail");
tdChilds.each(function () {
var thisElement = $(this);
thisElement.css('background-image', thisElement.data('oldImage'));
@@ -529,10 +529,10 @@ OCA.External.StatusManager.Utils = {
$.each(filename, function (index) {
route = OCA.External.StatusManager.Utils.getIconRoute($(this));
$(this).attr("data-icon", route);
- $(this).find('td:first-child div.thumbnail').css('background-image', "url(" + route + ")").css('display', 'none').css('display', 'inline');
+ $(this).find('td.filename div.thumbnail').css('background-image', "url(" + route + ")").css('display', 'none').css('display', 'inline');
});
} else {
- file = $("#fileList tr[data-file=\"" + this.jqSelEscape(filename) + "\"] > td:first-child div.thumbnail");
+ file = $("#fileList tr[data-file=\"" + this.jqSelEscape(filename) + "\"] > td.filename div.thumbnail");
var parentTr = file.parents('tr:first');
route = OCA.External.StatusManager.Utils.getIconRoute(parentTr);
parentTr.attr("data-icon", route);
@@ -573,7 +573,7 @@ OCA.External.StatusManager.Utils = {
if (filename instanceof $) {
link = filename;
} else {
- link = $("#fileList tr[data-file=\"" + this.jqSelEscape(filename) + "\"] > td:first-child a.name");
+ link = $("#fileList tr[data-file=\"" + this.jqSelEscape(filename) + "\"] > td.filename a.name");
}
if (active) {
link.off('click.connectivity');
diff --git a/apps/files_external/lib/Lib/Storage/SMB.php b/apps/files_external/lib/Lib/Storage/SMB.php
index 557dafda72c..d8bbe8c4718 100644
--- a/apps/files_external/lib/Lib/Storage/SMB.php
+++ b/apps/files_external/lib/Lib/Storage/SMB.php
@@ -52,6 +52,7 @@ use OCP\Files\Notify\IChange;
use OCP\Files\Notify\IRenameChange;
use OCP\Files\Storage\INotifyStorage;
use OCP\Files\StorageNotAvailableException;
+use OCP\Util;
class SMB extends Common implements INotifyStorage {
/**
@@ -199,18 +200,21 @@ class SMB extends Common implements INotifyStorage {
$this->remove($target);
$result = $this->share->rename($absoluteSource, $absoluteTarget);
} catch (\Exception $e) {
+ \OC::$server->getLogger()->logException($e, ['level' => Util::WARN]);
return false;
}
unset($this->statCache[$absoluteSource], $this->statCache[$absoluteTarget]);
return $result;
}
- /**
- * @param string $path
- * @return array
- */
public function stat($path) {
- $result = $this->formatInfo($this->getFileInfo($path));
+ try {
+ $result = $this->formatInfo($this->getFileInfo($path));
+ } catch (ForbiddenException $e) {
+ return false;
+ } catch (NotFoundException $e) {
+ return false;
+ }
if ($this->remoteIsShare() && $this->isRootDir($path)) {
$result['mtime'] = $this->shareMTime();
}
diff --git a/apps/files_sharing/css/authenticate.css b/apps/files_sharing/css/authenticate.css
index 9b2d6769289..7f83e0b41e7 100644
--- a/apps/files_sharing/css/authenticate.css
+++ b/apps/files_sharing/css/authenticate.css
@@ -8,6 +8,7 @@ form fieldset {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
height: 45px;
+ box-sizing: border-box;
flex: 1 1 auto;
width: 100% !important;
min-width: 0; /* FF hack for to override default value */
diff --git a/apps/files_sharing/js/public.js b/apps/files_sharing/js/public.js
index 2142dec1218..ae19500080b 100644
--- a/apps/files_sharing/js/public.js
+++ b/apps/files_sharing/js/public.js
@@ -434,10 +434,13 @@ $(document).ready(function () {
$(document).mouseup(function(e) {
+ var toggle = $('#share-menutoggle');
var container = $('#share-menu');
- // if the target of the click isn't the container nor a descendant of the container
- if (!container.is(e.target) && container.has(e.target).length === 0) {
+ // if the target of the click isn't the menu toggle, nor a descendant of the
+ // menu toggle, nor the container nor a descendant of the container
+ if (!toggle.is(e.target) && toggle.has(e.target).length === 0 &&
+ !container.is(e.target) && container.has(e.target).length === 0) {
container.removeClass('open');
}
});
diff --git a/apps/files_sharing/lib/External/Storage.php b/apps/files_sharing/lib/External/Storage.php
index bae24e89e64..638f82f7027 100644
--- a/apps/files_sharing/lib/External/Storage.php
+++ b/apps/files_sharing/lib/External/Storage.php
@@ -261,7 +261,7 @@ class Storage extends DAV implements ISharedStorage {
* @return bool
*/
private function testRemoteUrl($url) {
- $cache = $this->memcacheFactory->create('files_sharing_remote_url');
+ $cache = $this->memcacheFactory->createDistributed('files_sharing_remote_url');
if($cache->hasKey($url)) {
return (bool)$cache->get($url);
}
diff --git a/apps/files_sharing/templates/authenticate.php b/apps/files_sharing/templates/authenticate.php
index 20e200ef1ca..6f270c2851a 100644
--- a/apps/files_sharing/templates/authenticate.php
+++ b/apps/files_sharing/templates/authenticate.php
@@ -1,6 +1,7 @@
<?php
/** @var $_ array */
/** @var $l \OCP\IL10N */
+ style('core', 'guest');
style('files_sharing', 'authenticate');
script('files_sharing', 'authenticate');
?>
diff --git a/apps/files_sharing/templates/public.php b/apps/files_sharing/templates/public.php
index e17595d548b..9d28c178dde 100644
--- a/apps/files_sharing/templates/public.php
+++ b/apps/files_sharing/templates/public.php
@@ -50,7 +50,7 @@ $maxUploadFilesize = min($upload_max_filesize, $post_max_size);
<div class="header-right">
<?php if (!isset($_['hideFileList']) || (isset($_['hideFileList']) && $_['hideFileList'] === false)) { ?>
- <a href="#" id="share-menutoggle" class="menutoggle icon-more-white"><span class="share-menutoggle-text"><?php p($l->t('Download')) ?></span></a>
+ <a id="share-menutoggle" class="menutoggle icon-more-white"><span class="share-menutoggle-text"><?php p($l->t('Download')) ?></span></a>
<div id="share-menu" class="popovermenu menu">
<ul>
<li>
@@ -60,7 +60,7 @@ $maxUploadFilesize = min($upload_max_filesize, $post_max_size);
</a>
</li>
<li>
- <a href="#" id="directLink-container">
+ <a id="directLink-container">
<span class="icon icon-public"></span>
<label for="directLink"><?php p($l->t('Direct link')) ?></label>
<input id="directLink" type="text" readonly value="<?php p($_['previewURL']); ?>">
@@ -68,7 +68,7 @@ $maxUploadFilesize = min($upload_max_filesize, $post_max_size);
</li>
<?php if ($_['server2serversharing']) { ?>
<li>
- <a href="#" id="save" data-protected="<?php p($_['protected']) ?>"
+ <a id="save" data-protected="<?php p($_['protected']) ?>"
data-owner-display-name="<?php p($_['displayName']) ?>" data-owner="<?php p($_['owner']) ?>" data-name="<?php p($_['filename']) ?>">
<span class="icon icon-external"></span>
<span id="save-button"><?php p($l->t('Add to your Nextcloud')) ?></span>
diff --git a/apps/files_trashbin/templates/index.php b/apps/files_trashbin/templates/index.php
index b3ebca2867b..a4459947d09 100644
--- a/apps/files_trashbin/templates/index.php
+++ b/apps/files_trashbin/templates/index.php
@@ -35,6 +35,10 @@
<span class="icon icon-history"></span>
<span><?php p($l->t('Restore'))?></span>
</a>
+ <a href="" class="delete-selected">
+ <span class="icon icon-delete"></span>
+ <span><?php p($l->t('Delete'))?></span>
+ </a>
</span>
</div>
</th>
diff --git a/apps/theming/lib/ThemingDefaults.php b/apps/theming/lib/ThemingDefaults.php
index fa43dd50ccd..05d387e6273 100644
--- a/apps/theming/lib/ThemingDefaults.php
+++ b/apps/theming/lib/ThemingDefaults.php
@@ -240,7 +240,7 @@ class ThemingDefaults extends \OC_Defaults {
* @return array scss variables to overwrite
*/
public function getScssVariables() {
- $cache = $this->cacheFactory->create('theming');
+ $cache = $this->cacheFactory->createDistributed('theming');
if ($value = $cache->get('getScssVariables')) {
return $value;
}
@@ -307,7 +307,7 @@ class ThemingDefaults extends \OC_Defaults {
* @return bool
*/
public function shouldReplaceIcons() {
- $cache = $this->cacheFactory->create('theming');
+ $cache = $this->cacheFactory->createDistributed('theming');
if($value = $cache->get('shouldReplaceIcons')) {
return (bool)$value;
}
@@ -329,7 +329,7 @@ class ThemingDefaults extends \OC_Defaults {
private function increaseCacheBuster() {
$cacheBusterKey = $this->config->getAppValue('theming', 'cachebuster', '0');
$this->config->setAppValue('theming', 'cachebuster', (int)$cacheBusterKey+1);
- $this->cacheFactory->create('theming')->clear('getScssVariables');
+ $this->cacheFactory->createDistributed('theming')->clear('getScssVariables');
}
/**
diff --git a/apps/theming/tests/ThemingDefaultsTest.php b/apps/theming/tests/ThemingDefaultsTest.php
index 52bf88e51dd..843c1d34f9e 100644
--- a/apps/theming/tests/ThemingDefaultsTest.php
+++ b/apps/theming/tests/ThemingDefaultsTest.php
@@ -78,7 +78,7 @@ class ThemingDefaultsTest extends TestCase {
$this->defaults = new \OC_Defaults();
$this->cacheFactory
->expects($this->any())
- ->method('create')
+ ->method('createDistributed')
->with('theming')
->willReturn($this->cache);
$this->template = new ThemingDefaults(
diff --git a/apps/user_ldap/lib/Access.php b/apps/user_ldap/lib/Access.php
index 27fda38a737..14d5b826f63 100644
--- a/apps/user_ldap/lib/Access.php
+++ b/apps/user_ldap/lib/Access.php
@@ -1095,8 +1095,11 @@ class Access extends LDAPUtility implements IUserTools {
$this->pagedSearchedSuccessful = true;
}
} else {
- if(!is_null($limit)) {
- \OCP\Util::writeLog('user_ldap', 'Paged search was not available', \OCP\Util::INFO);
+ if(!is_null($limit) && intval($this->connection->ldapPagingSize) !== 0) {
+ \OC::$server->getLogger()->debug(
+ 'Paged search was not available',
+ [ 'app' => 'user_ldap' ]
+ );
}
}
/* ++ Fixing RHDS searches with pages with zero results ++
diff --git a/apps/user_ldap/lib/Connection.php b/apps/user_ldap/lib/Connection.php
index 1dcf9b72d7c..c73a35e6bf1 100644
--- a/apps/user_ldap/lib/Connection.php
+++ b/apps/user_ldap/lib/Connection.php
@@ -50,7 +50,7 @@ use OC\ServerNotAvailableException;
* @property boolean turnOnPasswordChange
* @property boolean hasPagedResultSupport
* @property string[] ldapBaseUsers
- * @property int|string ldapPagingSize holds an integer
+ * @property int|null ldapPagingSize holds an integer
* @property bool|mixed|void ldapGroupMemberAssocAttr
* @property string ldapUuidUserAttribute
* @property string ldapUuidGroupAttribute
@@ -100,7 +100,7 @@ class Connection extends LDAPUtility {
!is_null($configID));
$memcache = \OC::$server->getMemCacheFactory();
if($memcache->isAvailable()) {
- $this->cache = $memcache->create();
+ $this->cache = $memcache->createDistributed();
}
$helper = new Helper(\OC::$server->getConfig());
$this->doNotValidate = !in_array($this->configPrefix,
diff --git a/apps/user_ldap/lib/LDAP.php b/apps/user_ldap/lib/LDAP.php
index eafd8eacd06..bdc2f204225 100644
--- a/apps/user_ldap/lib/LDAP.php
+++ b/apps/user_ldap/lib/LDAP.php
@@ -63,8 +63,8 @@ class LDAP implements ILDAPWrapper {
}
/**
- * @param LDAP $link
- * @param LDAP $result
+ * @param resource $link
+ * @param resource $result
* @param string $cookie
* @return bool|LDAP
*/
@@ -331,6 +331,8 @@ class LDAP implements ILDAPWrapper {
//referrals, we switch them off, but then there is AD :)
} else if ($errorCode === -1) {
throw new ServerNotAvailableException('Lost connection to LDAP server.');
+ } else if ($errorCode === 52) {
+ throw new ServerNotAvailableException('LDAP server is shutting down.');
} else if ($errorCode === 48) {
throw new \Exception('LDAP authentication method rejected', $errorCode);
} else if ($errorCode === 1) {
@@ -339,11 +341,12 @@ class LDAP implements ILDAPWrapper {
ldap_get_option($this->curArgs[0], LDAP_OPT_ERROR_STRING, $extended_error);
throw new ConstraintViolationException(!empty($extended_error)?$extended_error:$errorMsg, $errorCode);
} else {
- \OCP\Util::writeLog('user_ldap',
- 'LDAP error '.$errorMsg.' (' .
- $errorCode.') after calling '.
- $this->curFunc,
- \OCP\Util::DEBUG);
+ \OC::$server->getLogger()->debug('LDAP error {message} ({code}) after calling {func}', [
+ 'app' => 'user_ldap',
+ 'message' => $errorMsg,
+ 'code' => $errorCode,
+ 'func' => $this->curFunc,
+ ]);
}
}
diff --git a/apps/user_ldap/lib/Proxy.php b/apps/user_ldap/lib/Proxy.php
index d372ff9c026..dc8c6fc77cc 100644
--- a/apps/user_ldap/lib/Proxy.php
+++ b/apps/user_ldap/lib/Proxy.php
@@ -50,7 +50,7 @@ abstract class Proxy {
$this->ldap = $ldap;
$memcache = \OC::$server->getMemCacheFactory();
if($memcache->isAvailable()) {
- $this->cache = $memcache->create();
+ $this->cache = $memcache->createDistributed();
}
}
diff --git a/build/package.json b/build/package.json
index b078727805d..e6688966693 100644
--- a/build/package.json
+++ b/build/package.json
@@ -22,6 +22,7 @@
"karma-jasmine-sinon": "^1.0.4",
"karma-junit-reporter": "*",
"karma-phantomjs-launcher": "*",
+ "karma-viewport": "^0.4.2",
"phantomjs-prebuilt": "*",
"node-sass": "~4.1.1",
"sinon": "*"
diff --git a/core/Command/Db/AddMissingIndices.php b/core/Command/Db/AddMissingIndices.php
new file mode 100644
index 00000000000..314bed8ccb1
--- /dev/null
+++ b/core/Command/Db/AddMissingIndices.php
@@ -0,0 +1,91 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Bjoern Schiessle <bjoern@schiessle.org>
+ *
+ * @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 OC\Core\Command\Db;
+
+use OC\DB\SchemaWrapper;
+use OCP\IDBConnection;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * Class AddMissingIndices
+ *
+ * if you added any new indices to the database, this is the right place to add
+ * it your update routine for existing instances
+ *
+ * @package OC\Core\Command\Db
+ */
+class AddMissingIndices extends Command {
+
+ /** @var IDBConnection */
+ private $connection;
+
+ /**
+ * @param IDBConnection $connection
+ */
+ public function __construct(IDBConnection $connection) {
+ $this->connection = $connection;
+ parent::__construct();
+ }
+
+ protected function configure() {
+ $this
+ ->setName('db:add-missing-indices')
+ ->setDescription('Add missing indices to the database tables');
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output) {
+ $this->addShareTableIndicies($output);
+
+ }
+
+ /**
+ * add missing indices to the share table
+ *
+ * @param OutputInterface $output
+ * @throws \Doctrine\DBAL\Schema\SchemaException
+ */
+ private function addShareTableIndicies(OutputInterface $output) {
+
+ $output->writeln('<info>Check indices of the share table.</info>');
+
+ $schema = new SchemaWrapper($this->connection);
+ $updated = false;
+
+ if ($schema->hasTable("share")) {
+ $table = $schema->getTable("share");
+ if (!$table->hasIndex('share_with_index')) {
+ $output->writeln('<info>Adding additional index to the share table, this can take some time...</info>');
+ $table->addIndex(['share_with'], 'share_with_index');
+ $this->connection->migrateToSchema($schema->getWrappedSchema());
+ $updated = true;
+ $output->writeln('<info>Share table updated successfully.</info>');
+ }
+ }
+
+ if (!$updated) {
+ $output->writeln('<info>Done.</info>');
+ }
+ }
+}
diff --git a/core/Command/Maintenance/Repair.php b/core/Command/Maintenance/Repair.php
index 9401dafd26b..71d13cd29f3 100644
--- a/core/Command/Maintenance/Repair.php
+++ b/core/Command/Maintenance/Repair.php
@@ -86,7 +86,7 @@ class Repair extends Command {
$apps = $this->appManager->getInstalledApps();
foreach ($apps as $app) {
- if (!$appManager->isEnabledForUser($app)) {
+ if (!$this->appManager->isEnabledForUser($app)) {
continue;
}
$info = \OC_App::getAppInfo($app);
diff --git a/core/Command/Maintenance/UpdateTheme.php b/core/Command/Maintenance/UpdateTheme.php
index cf015b82635..2ab66a4ce75 100644
--- a/core/Command/Maintenance/UpdateTheme.php
+++ b/core/Command/Maintenance/UpdateTheme.php
@@ -57,7 +57,7 @@ class UpdateTheme extends UpdateJS {
parent::execute($input, $output);
// cleanup image cache
- $c = $this->cacheFactory->create('imagePath');
+ $c = $this->cacheFactory->createDistributed('imagePath');
$c->clear('');
$output->writeln('<info>Image cache cleared');
}
diff --git a/core/Controller/LoginController.php b/core/Controller/LoginController.php
index e87e097e423..e53095a7de7 100644
--- a/core/Controller/LoginController.php
+++ b/core/Controller/LoginController.php
@@ -179,6 +179,7 @@ class LoginController extends Controller {
$parameters['alt_login'] = OC_App::getAlternativeLogIns();
$parameters['rememberLoginState'] = !empty($remember_login) ? $remember_login : 0;
+ $parameters['hideRemeberLoginState'] = !empty($redirect_url) && $this->session->exists('client.flow.state.token');
if (!is_null($user) && $user !== '') {
$parameters['loginName'] = $user;
diff --git a/core/Migrations/Version13000Date20170718121200.php b/core/Migrations/Version13000Date20170718121200.php
index 0ab777f6de2..e71debfcb4b 100644
--- a/core/Migrations/Version13000Date20170718121200.php
+++ b/core/Migrations/Version13000Date20170718121200.php
@@ -400,6 +400,7 @@ class Version13000Date20170718121200 extends SimpleMigrationStep {
$table->addIndex(['item_type', 'share_type'], 'item_share_type_index');
$table->addIndex(['file_source'], 'file_source_index');
$table->addIndex(['token'], 'token_index');
+ $table->addIndex(['share_with'], 'share_with_index');
}
if (!$schema->hasTable('jobs')) {
diff --git a/core/css/fixes.scss b/core/css/fixes.scss
index 3cb89c6599f..0303b4d751a 100644
--- a/core/css/fixes.scss
+++ b/core/css/fixes.scss
@@ -16,3 +16,10 @@ select {
visibility: hidden;
}
+.ie #header .menu,
+.ie .header-left #navigation,
+.ie .ui-datepicker,
+.ie .ui-timepicker.ui-widget,
+.ie #appmenu li span {
+ box-shadow: 0 1px 10px $color-box-shadow;
+}
diff --git a/core/css/header.scss b/core/css/header.scss
index 21305de0d02..b38c0bcb401 100644
--- a/core/css/header.scss
+++ b/core/css/header.scss
@@ -76,13 +76,16 @@
.menu {
top: 45px;
background-color: $color-main-background;
- filter: drop-shadow(0 1px 3px $color-box-shadow);
+ filter: drop-shadow(0 1px 10px $color-box-shadow);
border-radius: 0 0 3px 3px;
- display: none;
box-sizing: border-box;
z-index: 2000;
position: absolute;
+ &:not(.popovermenu) {
+ display: none;
+ }
+
/* Dropdown arrow */
&:after {
border: 10px solid transparent;
@@ -210,7 +213,7 @@ nav {
left: -100%;
width: 160px;
background-color: $color-main-background;
- filter: drop-shadow(0 1px 3px $color-box-shadow);
+ filter: drop-shadow(0 1px 10px $color-box-shadow);
&:after {
/* position of dropdown arrow */
left: 47%;
@@ -408,7 +411,6 @@ nav {
#expanddiv {
right: 13px;
background: $color-main-background;
- box-shadow: 0 1px 10px $color-box-shadow;
&:after {
/* position of dropdown arrow */
right: 13px;
@@ -483,7 +485,7 @@ nav {
display: none;
position: absolute;
overflow: visible;
- background-color: rgba($color-main-background, .97);
+ background-color: $color-main-background;
white-space: nowrap;
border: none;
border-radius: $border-radius;
@@ -496,7 +498,7 @@ nav {
top: 45px;
transform: translateX(-50%);
padding: 4px 10px;
- box-shadow: 0 1px 10px $color-box-shadow;
+ filter: drop-shadow(0 1px 10px $color-box-shadow);
}
li:hover span {
diff --git a/core/css/mobile.scss b/core/css/mobile.scss
index 19518479987..6f1583cb77a 100644
--- a/core/css/mobile.scss
+++ b/core/css/mobile.scss
@@ -83,9 +83,7 @@
/* position controls for apps with app-navigation */
#app-navigation+#app-content #controls {
- left: 0 !important;
padding-left: 44px;
- width: 100%;
}
/* .viewer-mode is when text editor, PDF viewer, etc is open */
diff --git a/core/css/styles.scss b/core/css/styles.scss
index 5474b41a2b4..1b76e3c68de 100644
--- a/core/css/styles.scss
+++ b/core/css/styles.scss
@@ -223,29 +223,23 @@ body {
#controls {
box-sizing: border-box;
- position: fixed;
- top: 45px;
- right: 0;
- left: 0;
+ position: -webkit-sticky;
+ position: sticky;
height: 44px;
- width: calc(100% - 250px);
padding: 0;
margin: 0;
background-color: rgba($color-main-background, 0.95);
- z-index: 50;
+ z-index: 55;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
- display: inline-flex;
+ display: flex;
+ top: 0;
}
/* position controls for apps with app-navigation */
-#app-navigation + #app-content #controls {
- left: 250px;
-}
-
.viewer-mode #app-navigation + #app-content #controls {
left: 0;
}
diff --git a/core/css/variables.scss b/core/css/variables.scss
index edeebd5403f..2e4e9028b30 100644
--- a/core/css/variables.scss
+++ b/core/css/variables.scss
@@ -15,7 +15,7 @@ $color-primary-element: $color-primary;
@return lighten($color, $value);
}
-$image-logo: '../img/logo-icon.svg?v=1';
+$image-logo: '../img/logo.svg?v=1';
$image-login-background: '../img/background.png?v=2';
$color-loading: #969696;
diff --git a/core/js/jquery.avatar.js b/core/js/jquery.avatar.js
index 54518c75cc7..958f0f9edd7 100644
--- a/core/js/jquery.avatar.js
+++ b/core/js/jquery.avatar.js
@@ -106,54 +106,39 @@
});
}
- // If the displayname is not defined we use the old code path
- if (typeof(displayname) === 'undefined') {
- $.get(url).always(function(result, status) {
- // if there is an error or an object returned (contains user information):
- // -> show the fallback placeholder
- if (typeof(result) === 'object' || status === 'error') {
- if (!hidedefault) {
- if (result.data && result.data.displayname) {
- $div.imageplaceholder(user, result.data.displayname);
- } else {
- // User does not exist
- setAvatarForUnknownUser($div);
- }
- } else {
- $div.hide();
- }
- // else an image is transferred and should be shown
- } else {
- $div.show();
- if (ie8fix === true) {
- $div.html('<img width="' + size + '" height="' + size + '" src="'+url+'#'+Math.floor(Math.random()*1000)+'" alt="">');
- } else {
- $div.html('<img width="' + size + '" height="' + size + '" src="'+url+'" alt="">');
- }
- }
- if(typeof callback === 'function') {
- callback();
- }
- });
- } else {
- // We already have the displayname so set the placeholder (to show at least something)
- if (!hidedefault) {
- $div.imageplaceholder(displayname);
- }
+ var img = new Image();
+
+ // If the new image loads successfully set it.
+ img.onload = function() {
+ $div.text('');
+ $div.append(img);
+ $div.clearimageplaceholder();
- var img = new Image();
+ if(typeof callback === 'function') {
+ callback();
+ }
+ };
+ // Fallback when avatar loading fails:
+ // Use old placeholder when a displayname attribute is defined,
+ // otherwise show the unknown user placeholder.
+ img.onerror = function () {
+ $div.clearimageplaceholder();
+ if (typeof(displayname) !== 'undefined') {
+ $div.imageplaceholder(user, displayname);
+ } else {
+ setAvatarForUnknownUser($div);
+ $div.removeClass('icon-loading');
+ }
- // If the new image loads successfully set it.
- img.onload = function() {
- $div.show();
- $div.text('');
- $div.append(img);
- $div.clearimageplaceholder();
- };
+ if(typeof callback === 'function') {
+ callback();
+ }
+ };
- img.width = size;
- img.height = size;
- img.src = url;
- }
+ $div.addClass('icon-loading');
+ $div.show();
+ img.width = size;
+ img.height = size;
+ img.src = url;
};
}(jQuery));
diff --git a/core/js/js.js b/core/js/js.js
index 9af80676d5e..0b28310c728 100644
--- a/core/js/js.js
+++ b/core/js/js.js
@@ -1612,12 +1612,47 @@ function initCore() {
snapper.close();
});
+ var navigationBarSlideGestureEnabled = false;
+ var navigationBarSlideGestureAllowed = true;
+ var navigationBarSlideGestureEnablePending = false;
+
+ OC.allowNavigationBarSlideGesture = function() {
+ navigationBarSlideGestureAllowed = true;
+
+ if (navigationBarSlideGestureEnablePending) {
+ snapper.enable();
+
+ navigationBarSlideGestureEnabled = true;
+ navigationBarSlideGestureEnablePending = false;
+ }
+ };
+
+ OC.disallowNavigationBarSlideGesture = function() {
+ navigationBarSlideGestureAllowed = false;
+
+ if (navigationBarSlideGestureEnabled) {
+ var endCurrentDrag = true;
+ snapper.disable(endCurrentDrag);
+
+ navigationBarSlideGestureEnabled = false;
+ navigationBarSlideGestureEnablePending = true;
+ }
+ };
+
var toggleSnapperOnSize = function() {
if($(window).width() > 768) {
snapper.close();
snapper.disable();
- } else {
+
+ navigationBarSlideGestureEnabled = false;
+ navigationBarSlideGestureEnablePending = false;
+ } else if (navigationBarSlideGestureAllowed) {
snapper.enable();
+
+ navigationBarSlideGestureEnabled = true;
+ navigationBarSlideGestureEnablePending = false;
+ } else {
+ navigationBarSlideGestureEnablePending = true;
}
};
@@ -1647,7 +1682,8 @@ OC.PasswordConfirmation = {
requiresPasswordConfirmation: function() {
var timeSinceLogin = moment.now() - (nc_lastLogin * 1000);
- return timeSinceLogin > 30 * 60 * 1000; // 30 minutes
+ // if timeSinceLogin > 30 minutes and user backend allows password confirmation
+ return (backendAllowsPasswordConfirmation && timeSinceLogin > 30 * 60 * 1000);
},
/**
diff --git a/core/js/lostpassword.js b/core/js/lostpassword.js
index 446d70d991e..b44962f552e 100644
--- a/core/js/lostpassword.js
+++ b/core/js/lostpassword.js
@@ -16,6 +16,7 @@ OC.Lostpassword = {
$('#lost-password').click(OC.Lostpassword.resetLink);
$('#lost-password-back').click(OC.Lostpassword.backToLogin);
$('form[name=login]').submit(OC.Lostpassword.onSendLink);
+ $('#reset-password #submit').click(OC.Lostpassword.resetPassword);
OC.Lostpassword.resetButtons();
},
diff --git a/core/js/placeholder.js b/core/js/placeholder.js
index f173e738676..5cf7b9095ad 100644
--- a/core/js/placeholder.js
+++ b/core/js/placeholder.js
@@ -2,7 +2,7 @@
* ownCloud
*
* @author John Molakvoæ
- * @copyright 2016 John Molakvoæ <fremulon@protonmail.com>
+ * @copyright 2016-2017 John Molakvoæ <skjnldsv@protonmail.com>
* @author Morris Jobke
* @copyright 2013 Morris Jobke <morris.jobke@gmail.com>
*
@@ -47,7 +47,7 @@
* <div id="albumart" style="background-color: hsl(123, 90%, 65%); ... ">A</div>
*
*/
-
+
/*
* Alternatively, you can use the prototype function to convert your string to hsl colors:
*
@@ -156,5 +156,6 @@
this.css('text-align', '');
this.css('line-height', '');
this.css('font-size', '');
+ this.removeClass('icon-loading');
};
}(jQuery));
diff --git a/core/js/tests/specs/coreSpec.js b/core/js/tests/specs/coreSpec.js
index 9848fb46ffc..b6c617303cf 100644
--- a/core/js/tests/specs/coreSpec.js
+++ b/core/js/tests/specs/coreSpec.js
@@ -1112,4 +1112,431 @@ describe('Core base tests', function() {
expect(OC._ajaxConnectionLostHandler.calls.count()).toBe(1);
});
});
+ describe('Snapper', function() {
+ var snapConstructorStub;
+ var snapperStub;
+ var clock;
+
+ beforeEach(function() {
+ snapConstructorStub = sinon.stub(window, 'Snap');
+
+ snapperStub = {};
+ snapperStub.enable = sinon.stub();
+ snapperStub.disable = sinon.stub();
+ snapperStub.close = sinon.stub();
+
+ snapConstructorStub.returns(snapperStub);
+
+ clock = sinon.useFakeTimers();
+
+ // _.now could have been set to Date.now before Sinon replaced it
+ // with a fake version, so _.now must be stubbed to ensure that the
+ // fake Date.now will be called instead of the original one.
+ _.now = sinon.stub(_, 'now').callsFake(function() {
+ return new Date().getTime();
+ });
+
+ $('#testArea').append('<div id="app-navigation">The navigation bar</div><div id="app-content">Content</div>');
+ });
+ afterEach(function() {
+ snapConstructorStub.restore();
+
+ clock.restore();
+
+ _.now.restore();
+
+ // Remove the event handler for the resize event added to the window
+ // due to calling window.initCore() when there is an #app-navigation
+ // element.
+ $(window).off('resize');
+
+ viewport.reset();
+ });
+
+ it('is enabled on a narrow screen', function() {
+ viewport.set(480);
+
+ window.initCore();
+
+ expect(snapConstructorStub.calledOnce).toBe(true);
+ expect(snapperStub.enable.calledOnce).toBe(true);
+ expect(snapperStub.disable.called).toBe(false);
+ });
+ it('is disabled when disallowing the gesture on a narrow screen', function() {
+ viewport.set(480);
+
+ window.initCore();
+
+ expect(snapperStub.enable.calledOnce).toBe(true);
+ expect(snapperStub.disable.called).toBe(false);
+ expect(snapperStub.close.called).toBe(false);
+
+ OC.disallowNavigationBarSlideGesture();
+
+ expect(snapperStub.enable.calledOnce).toBe(true);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+ expect(snapperStub.disable.alwaysCalledWithExactly(true)).toBe(true);
+ expect(snapperStub.close.called).toBe(false);
+ });
+ it('is not disabled again when disallowing the gesture twice on a narrow screen', function() {
+ viewport.set(480);
+
+ window.initCore();
+
+ expect(snapperStub.enable.calledOnce).toBe(true);
+ expect(snapperStub.disable.called).toBe(false);
+ expect(snapperStub.close.called).toBe(false);
+
+ OC.disallowNavigationBarSlideGesture();
+
+ expect(snapperStub.enable.calledOnce).toBe(true);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+ expect(snapperStub.disable.alwaysCalledWithExactly(true)).toBe(true);
+ expect(snapperStub.close.called).toBe(false);
+
+ OC.disallowNavigationBarSlideGesture();
+
+ expect(snapperStub.enable.calledOnce).toBe(true);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+ expect(snapperStub.close.called).toBe(false);
+ });
+ it('is enabled when allowing the gesture after disallowing it on a narrow screen', function() {
+ viewport.set(480);
+
+ window.initCore();
+
+ expect(snapperStub.enable.calledOnce).toBe(true);
+ expect(snapperStub.disable.called).toBe(false);
+ expect(snapperStub.close.called).toBe(false);
+
+ OC.disallowNavigationBarSlideGesture();
+
+ expect(snapperStub.enable.calledOnce).toBe(true);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+ expect(snapperStub.disable.alwaysCalledWithExactly(true)).toBe(true);
+ expect(snapperStub.close.called).toBe(false);
+
+ OC.allowNavigationBarSlideGesture();
+
+ expect(snapperStub.enable.calledTwice).toBe(true);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+ expect(snapperStub.close.called).toBe(false);
+ });
+ it('is not enabled again when allowing the gesture twice after disallowing it on a narrow screen', function() {
+ viewport.set(480);
+
+ window.initCore();
+
+ expect(snapperStub.enable.calledOnce).toBe(true);
+ expect(snapperStub.disable.called).toBe(false);
+ expect(snapperStub.close.called).toBe(false);
+
+ OC.disallowNavigationBarSlideGesture();
+
+ expect(snapperStub.enable.calledOnce).toBe(true);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+ expect(snapperStub.disable.alwaysCalledWithExactly(true)).toBe(true);
+ expect(snapperStub.close.called).toBe(false);
+
+ OC.allowNavigationBarSlideGesture();
+
+ expect(snapperStub.enable.calledTwice).toBe(true);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+ expect(snapperStub.close.called).toBe(false);
+
+ OC.allowNavigationBarSlideGesture();
+
+ expect(snapperStub.enable.calledTwice).toBe(true);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+ expect(snapperStub.close.called).toBe(false);
+ });
+ it('is disabled on a wide screen', function() {
+ viewport.set(1280);
+
+ window.initCore();
+
+ expect(snapConstructorStub.calledOnce).toBe(true);
+ expect(snapperStub.enable.called).toBe(false);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+ });
+ it('is not disabled again when disallowing the gesture on a wide screen', function() {
+ viewport.set(1280);
+
+ window.initCore();
+
+ expect(snapperStub.enable.called).toBe(false);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+ expect(snapperStub.close.calledOnce).toBe(true);
+
+ OC.disallowNavigationBarSlideGesture();
+
+ expect(snapperStub.enable.called).toBe(false);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+ expect(snapperStub.close.calledOnce).toBe(true);
+ });
+ it('is not enabled when allowing the gesture after disallowing it on a wide screen', function() {
+ viewport.set(1280);
+
+ window.initCore();
+
+ expect(snapperStub.enable.called).toBe(false);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+ expect(snapperStub.close.calledOnce).toBe(true);
+
+ OC.disallowNavigationBarSlideGesture();
+
+ expect(snapperStub.enable.called).toBe(false);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+ expect(snapperStub.close.calledOnce).toBe(true);
+
+ OC.allowNavigationBarSlideGesture();
+
+ expect(snapperStub.enable.called).toBe(false);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+ expect(snapperStub.close.calledOnce).toBe(true);
+ });
+ it('is enabled when resizing to a narrow screen', function() {
+ viewport.set(1280);
+
+ window.initCore();
+
+ expect(snapperStub.enable.called).toBe(false);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+
+ viewport.set(480);
+
+ // Setting the viewport width does not automatically trigger a
+ // resize.
+ $(window).resize();
+
+ // The resize handler is debounced to be executed a few milliseconds
+ // after the resize event.
+ clock.tick(1000);
+
+ expect(snapperStub.enable.calledOnce).toBe(true);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+ });
+ it('is not enabled when resizing to a narrow screen after disallowing the gesture', function() {
+ viewport.set(1280);
+
+ window.initCore();
+
+ expect(snapperStub.enable.called).toBe(false);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+
+ OC.disallowNavigationBarSlideGesture();
+
+ expect(snapperStub.enable.called).toBe(false);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+
+ viewport.set(480);
+
+ // Setting the viewport width does not automatically trigger a
+ // resize.
+ $(window).resize();
+
+ // The resize handler is debounced to be executed a few milliseconds
+ // after the resize event.
+ clock.tick(1000);
+
+ expect(snapperStub.enable.called).toBe(false);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+ });
+ it('is enabled when resizing to a narrow screen after disallowing the gesture and allowing it', function() {
+ viewport.set(1280);
+
+ window.initCore();
+
+ expect(snapperStub.enable.called).toBe(false);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+
+ OC.disallowNavigationBarSlideGesture();
+
+ expect(snapperStub.enable.called).toBe(false);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+
+ OC.allowNavigationBarSlideGesture();
+
+ expect(snapperStub.enable.called).toBe(false);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+
+ viewport.set(480);
+
+ // Setting the viewport width does not automatically trigger a
+ // resize.
+ $(window).resize();
+
+ // The resize handler is debounced to be executed a few milliseconds
+ // after the resize event.
+ clock.tick(1000);
+
+ expect(snapperStub.enable.calledOnce).toBe(true);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+ });
+ it('is enabled when allowing the gesture after disallowing it and resizing to a narrow screen', function() {
+ viewport.set(1280);
+
+ window.initCore();
+
+ expect(snapperStub.enable.called).toBe(false);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+
+ OC.disallowNavigationBarSlideGesture();
+
+ expect(snapperStub.enable.called).toBe(false);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+
+ viewport.set(480);
+
+ // Setting the viewport width does not automatically trigger a
+ // resize.
+ $(window).resize();
+
+ // The resize handler is debounced to be executed a few milliseconds
+ // after the resize event.
+ clock.tick(1000);
+
+ expect(snapperStub.enable.called).toBe(false);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+
+ OC.allowNavigationBarSlideGesture();
+
+ expect(snapperStub.enable.calledOnce).toBe(true);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+ });
+ it('is disabled when disallowing the gesture after disallowing it, resizing to a narrow screen and allowing it', function() {
+ viewport.set(1280);
+
+ window.initCore();
+
+ expect(snapperStub.enable.called).toBe(false);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+
+ OC.disallowNavigationBarSlideGesture();
+
+ expect(snapperStub.enable.called).toBe(false);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+
+ viewport.set(480);
+
+ // Setting the viewport width does not automatically trigger a
+ // resize.
+ $(window).resize();
+
+ // The resize handler is debounced to be executed a few milliseconds
+ // after the resize event.
+ clock.tick(1000);
+
+ expect(snapperStub.enable.called).toBe(false);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+
+ OC.allowNavigationBarSlideGesture();
+
+ expect(snapperStub.enable.calledOnce).toBe(true);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+
+ OC.disallowNavigationBarSlideGesture();
+
+ expect(snapperStub.enable.calledOnce).toBe(true);
+ expect(snapperStub.disable.calledTwice).toBe(true);
+ expect(snapperStub.disable.getCall(1).calledWithExactly(true)).toBe(true);
+ });
+ it('is disabled when resizing to a wide screen', function() {
+ viewport.set(480);
+
+ window.initCore();
+
+ expect(snapperStub.enable.calledOnce).toBe(true);
+ expect(snapperStub.disable.called).toBe(false);
+ expect(snapperStub.close.called).toBe(false);
+
+ viewport.set(1280);
+
+ // Setting the viewport width does not automatically trigger a
+ // resize.
+ $(window).resize();
+
+ // The resize handler is debounced to be executed a few milliseconds
+ // after the resize event.
+ clock.tick(1000);
+
+ expect(snapperStub.enable.calledOnce).toBe(true);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+ expect(snapperStub.close.calledOnce).toBe(true);
+ });
+ it('is not disabled again when disallowing the gesture after resizing to a wide screen', function() {
+ viewport.set(480);
+
+ window.initCore();
+
+ expect(snapperStub.enable.calledOnce).toBe(true);
+ expect(snapperStub.disable.called).toBe(false);
+ expect(snapperStub.close.called).toBe(false);
+
+ viewport.set(1280);
+
+ // Setting the viewport width does not automatically trigger a
+ // resize.
+ $(window).resize();
+
+ // The resize handler is debounced to be executed a few milliseconds
+ // after the resize event.
+ clock.tick(1000);
+
+ expect(snapperStub.enable.calledOnce).toBe(true);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+ expect(snapperStub.close.calledOnce).toBe(true);
+
+ OC.disallowNavigationBarSlideGesture();
+
+ expect(snapperStub.enable.calledOnce).toBe(true);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+ expect(snapperStub.close.calledOnce).toBe(true);
+ });
+ it('is not enabled when allowing the gesture after disallowing it, resizing to a narrow screen and resizing to a wide screen', function() {
+ viewport.set(1280);
+
+ window.initCore();
+
+ expect(snapperStub.enable.called).toBe(false);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+ expect(snapperStub.close.calledOnce).toBe(true);
+
+ OC.disallowNavigationBarSlideGesture();
+
+ expect(snapperStub.enable.called).toBe(false);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+ expect(snapperStub.close.calledOnce).toBe(true);
+
+ viewport.set(480);
+
+ // Setting the viewport width does not automatically trigger a
+ // resize.
+ $(window).resize();
+
+ // The resize handler is debounced to be executed a few milliseconds
+ // after the resize event.
+ clock.tick(1000);
+
+ expect(snapperStub.enable.called).toBe(false);
+ expect(snapperStub.disable.calledOnce).toBe(true);
+ expect(snapperStub.close.calledOnce).toBe(true);
+
+ viewport.set(1280);
+
+ $(window).resize();
+
+ clock.tick(1000);
+
+ expect(snapperStub.enable.called).toBe(false);
+ expect(snapperStub.disable.calledTwice).toBe(true);
+ expect(snapperStub.close.calledTwice).toBe(true);
+
+ OC.allowNavigationBarSlideGesture();
+
+ expect(snapperStub.enable.called).toBe(false);
+ expect(snapperStub.disable.calledTwice).toBe(true);
+ expect(snapperStub.close.calledTwice).toBe(true);
+ });
+ });
});
diff --git a/core/js/tests/specs/jquery.avatarSpec.js b/core/js/tests/specs/jquery.avatarSpec.js
index b9351d2a8a0..bdd1fdcc163 100644
--- a/core/js/tests/specs/jquery.avatarSpec.js
+++ b/core/js/tests/specs/jquery.avatarSpec.js
@@ -19,6 +19,13 @@ describe('jquery.avatar tests', function() {
devicePixelRatio = window.devicePixelRatio;
window.devicePixelRatio = 1;
+
+ spyOn(window, 'Image').and.returnValue({
+ onload: function() {
+ },
+ onerror: function() {
+ }
+ });
});
afterEach(function() {
@@ -39,6 +46,9 @@ describe('jquery.avatar tests', function() {
$div.height(9);
$div.avatar('foo');
+ expect(window.Image).toHaveBeenCalled();
+ window.Image().onerror();
+
expect($div.height()).toEqual(9);
expect($div.width()).toEqual(9);
});
@@ -47,6 +57,9 @@ describe('jquery.avatar tests', function() {
$div.data('size', 10);
$div.avatar('foo');
+ expect(window.Image).toHaveBeenCalled();
+ window.Image().onerror();
+
expect($div.height()).toEqual(10);
expect($div.width()).toEqual(10);
});
@@ -55,6 +68,9 @@ describe('jquery.avatar tests', function() {
it('defined', function() {
$div.avatar('foo', 8);
+ expect(window.Image).toHaveBeenCalled();
+ window.Image().onerror();
+
expect($div.height()).toEqual(8);
expect($div.width()).toEqual(8);
});
@@ -73,16 +89,10 @@ describe('jquery.avatar tests', function() {
describe('no avatar', function() {
it('show placeholder for existing user', function() {
spyOn($div, 'imageplaceholder');
- $div.avatar('foo');
-
- fakeServer.requests[0].respond(
- 200,
- { 'Content-Type': 'application/json' },
- JSON.stringify({
- data: {displayname: 'bar'}
- })
- );
+ $div.avatar('foo', undefined, undefined, undefined, undefined, 'bar');
+ expect(window.Image).toHaveBeenCalled();
+ window.Image().onerror();
expect($div.imageplaceholder).toHaveBeenCalledWith('foo', 'bar');
});
@@ -91,32 +101,23 @@ describe('jquery.avatar tests', function() {
spyOn($div, 'css');
$div.avatar('foo');
- fakeServer.requests[0].respond(
- 200,
- { 'Content-Type': 'application/json' },
- JSON.stringify({
- data: {}
- })
- );
+ expect(window.Image).toHaveBeenCalled();
+ window.Image().onerror();
expect($div.imageplaceholder).toHaveBeenCalledWith('?');
expect($div.css).toHaveBeenCalledWith('background-color', '#b9b9b9');
});
- it('show no placeholder', function() {
+ it('show no placeholder is ignored', function() {
spyOn($div, 'imageplaceholder');
+ spyOn($div, 'css');
$div.avatar('foo', undefined, undefined, true);
- fakeServer.requests[0].respond(
- 200,
- { 'Content-Type': 'application/json' },
- JSON.stringify({
- data: {}
- })
- );
+ expect(window.Image).toHaveBeenCalled();
+ window.Image().onerror();
- expect($div.imageplaceholder.calls.any()).toEqual(false);
- expect($div.css('display')).toEqual('none');
+ expect($div.imageplaceholder).toHaveBeenCalledWith('?');
+ expect($div.css).toHaveBeenCalledWith('background-color', '#b9b9b9');
});
});
@@ -129,24 +130,24 @@ describe('jquery.avatar tests', function() {
window.devicePixelRatio = 1;
$div.avatar('foo', 32);
- expect(fakeServer.requests[0].method).toEqual('GET');
- expect(fakeServer.requests[0].url).toEqual('http://localhost/index.php/avatar/foo/32');
+ expect(window.Image).toHaveBeenCalled();
+ expect(window.Image().src).toEqual('http://localhost/index.php/avatar/foo/32');
});
it('high DPI icon', function() {
window.devicePixelRatio = 4;
$div.avatar('foo', 32);
- expect(fakeServer.requests[0].method).toEqual('GET');
- expect(fakeServer.requests[0].url).toEqual('http://localhost/index.php/avatar/foo/128');
+ expect(window.Image).toHaveBeenCalled();
+ expect(window.Image().src).toEqual('http://localhost/index.php/avatar/foo/128');
});
it('high DPI icon round up size', function() {
window.devicePixelRatio = 1.9;
$div.avatar('foo', 32);
- expect(fakeServer.requests[0].method).toEqual('GET');
- expect(fakeServer.requests[0].url).toEqual('http://localhost/index.php/avatar/foo/61');
+ expect(window.Image).toHaveBeenCalled();
+ expect(window.Image().src).toEqual('http://localhost/index.php/avatar/foo/61');
});
});
@@ -158,17 +159,12 @@ describe('jquery.avatar tests', function() {
it('default (no ie8 fix)', function() {
$div.avatar('foo', 32);
- fakeServer.requests[0].respond(
- 200,
- { 'Content-Type': 'image/jpeg' },
- ''
- );
+ expect(window.Image).toHaveBeenCalled();
+ window.Image().onload();
- var img = $div.children('img')[0];
-
- expect(img.height).toEqual(32);
- expect(img.width).toEqual(32);
- expect(img.src).toEqual('http://localhost/index.php/avatar/foo/32');
+ expect(window.Image().height).toEqual(32);
+ expect(window.Image().width).toEqual(32);
+ expect(window.Image().src).toEqual('http://localhost/index.php/avatar/foo/32');
});
it('default high DPI icon', function() {
@@ -176,37 +172,23 @@ describe('jquery.avatar tests', function() {
$div.avatar('foo', 32);
- fakeServer.requests[0].respond(
- 200,
- { 'Content-Type': 'image/jpeg' },
- ''
- );
-
- var img = $div.children('img')[0];
+ expect(window.Image).toHaveBeenCalled();
+ window.Image().onload();
- expect(img.height).toEqual(32);
- expect(img.width).toEqual(32);
- expect(img.src).toEqual('http://localhost/index.php/avatar/foo/61');
+ expect(window.Image().height).toEqual(32);
+ expect(window.Image().width).toEqual(32);
+ expect(window.Image().src).toEqual('http://localhost/index.php/avatar/foo/61');
});
- it('with ie8 fix', function() {
- sinon.stub(Math, 'random').callsFake(function() {
- return 0.5;
- });
-
+ it('with ie8 fix (ignored)', function() {
$div.avatar('foo', 32, true);
- fakeServer.requests[0].respond(
- 200,
- { 'Content-Type': 'image/jpeg' },
- ''
- );
+ expect(window.Image).toHaveBeenCalled();
+ window.Image().onload();
- var img = $div.children('img')[0];
-
- expect(img.height).toEqual(32);
- expect(img.width).toEqual(32);
- expect(img.src).toEqual('http://localhost/index.php/avatar/foo/32#500');
+ expect(window.Image().height).toEqual(32);
+ expect(window.Image().width).toEqual(32);
+ expect(window.Image().src).toEqual('http://localhost/index.php/avatar/foo/32');
});
it('unhide div', function() {
@@ -214,11 +196,12 @@ describe('jquery.avatar tests', function() {
$div.avatar('foo', 32);
- fakeServer.requests[0].respond(
- 200,
- { 'Content-Type': 'image/jpeg' },
- ''
- );
+ expect(window.Image).toHaveBeenCalled();
+ window.Image().onload();
+
+ expect(window.Image().height).toEqual(32);
+ expect(window.Image().width).toEqual(32);
+ expect(window.Image().src).toEqual('http://localhost/index.php/avatar/foo/32');
expect($div.css('display')).toEqual('block');
});
@@ -232,12 +215,12 @@ describe('jquery.avatar tests', function() {
observer.callback();
});
- fakeServer.requests[0].respond(
- 200,
- { 'Content-Type': 'image/jpeg' },
- ''
- );
+ expect(window.Image).toHaveBeenCalled();
+ window.Image().onload();
+ expect(window.Image().height).toEqual(32);
+ expect(window.Image().width).toEqual(32);
+ expect(window.Image().src).toEqual('http://localhost/index.php/avatar/foo/32');
expect(observer.callback).toHaveBeenCalled();
});
});
diff --git a/core/register_command.php b/core/register_command.php
index 60e151a5f2c..372d775dc14 100644
--- a/core/register_command.php
+++ b/core/register_command.php
@@ -90,6 +90,7 @@ if (\OC::$server->getConfig()->getSystemValue('installed', false)) {
$application->add(new OC\Core\Command\Db\ConvertType(\OC::$server->getConfig(), new \OC\DB\ConnectionFactory(\OC::$server->getSystemConfig())));
$application->add(new OC\Core\Command\Db\ConvertMysqlToMB4(\OC::$server->getConfig(), \OC::$server->getDatabaseConnection(), \OC::$server->getURLGenerator(), \OC::$server->getLogger()));
$application->add(new OC\Core\Command\Db\ConvertFilecacheBigInt(\OC::$server->getDatabaseConnection()));
+ $application->add(new OC\Core\Command\Db\AddMissingIndices(\OC::$server->getDatabaseConnection()));
$application->add(new OC\Core\Command\Db\Migrations\StatusCommand(\OC::$server->getDatabaseConnection()));
$application->add(new OC\Core\Command\Db\Migrations\MigrateCommand(\OC::$server->getDatabaseConnection()));
$application->add(new OC\Core\Command\Db\Migrations\GenerateCommand(\OC::$server->getDatabaseConnection()));
diff --git a/core/templates/login.php b/core/templates/login.php
index 82827bbef03..d28c92e36ef 100644
--- a/core/templates/login.php
+++ b/core/templates/login.php
@@ -70,6 +70,7 @@ script('core', 'merged-login');
<?php } ?>
<div class="login-additional">
+ <?php if (!$_['hideRemeberLoginState']) { ?>
<div class="remember-login-container">
<?php if ($_['rememberLoginState'] === 0) { ?>
<input type="checkbox" name="remember_login" value="1" id="remember_login" class="checkbox checkbox--white">
@@ -78,6 +79,7 @@ script('core', 'merged-login');
<?php } ?>
<label for="remember_login"><?php p($l->t('Stay logged in')); ?></label>
</div>
+ <?php } ?>
<?php if (!empty($_['canResetPassword'])) { ?>
<div class="lost-password-container">
<a id="lost-password" href="<?php p($_['resetPasswordLink']); ?>">
diff --git a/core/vendor/core.js b/core/vendor/core.js
index bda270892ad..e573ef10642 100644
--- a/core/vendor/core.js
+++ b/core/vendor/core.js
@@ -6704,9 +6704,13 @@ dav.Client.prototype = {
/**
* Disables Snap.js events
+ * @param {Boolean} endCurrentDrag Whether to end the current drag (if any) or not.
*/
- disable: function() {
+ disable: function(endCurrentDrag) {
utils.dispatchEvent('disable');
+ if (endCurrentDrag) {
+ this.action.drag.endDrag();
+ }
this.action.drag.stopListening();
},
diff --git a/core/vendor/snapjs/dist/latest/snap.js b/core/vendor/snapjs/dist/latest/snap.js
index a0274138de0..7ae088d0aea 100644
--- a/core/vendor/snapjs/dist/latest/snap.js
+++ b/core/vendor/snapjs/dist/latest/snap.js
@@ -744,9 +744,13 @@
/**
* Disables Snap.js events
+ * @param {Boolean} endCurrentDrag Whether to end the current drag (if any) or not.
*/
- disable: function() {
+ disable: function(endCurrentDrag) {
utils.dispatchEvent('disable');
+ if (endCurrentDrag) {
+ this.action.drag.endDrag();
+ }
this.action.drag.stopListening();
},
diff --git a/issue_template.md b/issue_template.md
index ecf2f62ea68..e614ea0aed0 100644
--- a/issue_template.md
+++ b/issue_template.md
@@ -7,6 +7,10 @@ For reporting potential security issues please see https://nextcloud.com/securit
To make it possible for us to help you please fill out below information carefully.
You can also use the Issue Template application to prefill most of the required information: https://apps.nextcloud.com/apps/issuetemplate
+
+If you are a customer, please submit your issue directly in the Nextcloud Portal https://portal.nextcloud.com so it gets resolved more quickly by our dedicated engineers.
+
+Note that Nextcloud is an open source project backed by Nextcloud GmbH. Most of our volunteers are home users and thus primarily care about issues that affect home users. Our paid engineers prioritize issues of our customers. If you are neither a home user nor a customer, consider paying somebody to fix your issue, do it yourself or become a customer.
-->
### Steps to reproduce
1.
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index a3b85349274..974a6343869 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -335,6 +335,7 @@ return array(
'OC\\AppFramework\\Middleware\\Security\\Exceptions\\NotLoggedInException' => $baseDir . '/lib/private/AppFramework/Middleware/Security/Exceptions/NotLoggedInException.php',
'OC\\AppFramework\\Middleware\\Security\\Exceptions\\SecurityException' => $baseDir . '/lib/private/AppFramework/Middleware/Security/Exceptions/SecurityException.php',
'OC\\AppFramework\\Middleware\\Security\\Exceptions\\StrictCookieMissingException' => $baseDir . '/lib/private/AppFramework/Middleware/Security/Exceptions/StrictCookieMissingException.php',
+ 'OC\\AppFramework\\Middleware\\Security\\PasswordConfirmationMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php',
'OC\\AppFramework\\Middleware\\Security\\RateLimitingMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/Security/RateLimitingMiddleware.php',
'OC\\AppFramework\\Middleware\\Security\\SameSiteCookieMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php',
'OC\\AppFramework\\Middleware\\Security\\SecurityMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php',
@@ -459,6 +460,7 @@ return array(
'OC\\Core\\Command\\Config\\System\\DeleteConfig' => $baseDir . '/core/Command/Config/System/DeleteConfig.php',
'OC\\Core\\Command\\Config\\System\\GetConfig' => $baseDir . '/core/Command/Config/System/GetConfig.php',
'OC\\Core\\Command\\Config\\System\\SetConfig' => $baseDir . '/core/Command/Config/System/SetConfig.php',
+ 'OC\\Core\\Command\\Db\\AddMissingIndices' => $baseDir . '/core/Command/Db/AddMissingIndices.php',
'OC\\Core\\Command\\Db\\ConvertFilecacheBigInt' => $baseDir . '/core/Command/Db/ConvertFilecacheBigInt.php',
'OC\\Core\\Command\\Db\\ConvertMysqlToMB4' => $baseDir . '/core/Command/Db/ConvertMysqlToMB4.php',
'OC\\Core\\Command\\Db\\ConvertType' => $baseDir . '/core/Command/Db/ConvertType.php',
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index 408a30e2540..fc71d085f0a 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -365,6 +365,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\AppFramework\\Middleware\\Security\\Exceptions\\NotLoggedInException' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/Exceptions/NotLoggedInException.php',
'OC\\AppFramework\\Middleware\\Security\\Exceptions\\SecurityException' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/Exceptions/SecurityException.php',
'OC\\AppFramework\\Middleware\\Security\\Exceptions\\StrictCookieMissingException' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/Exceptions/StrictCookieMissingException.php',
+ 'OC\\AppFramework\\Middleware\\Security\\PasswordConfirmationMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php',
'OC\\AppFramework\\Middleware\\Security\\RateLimitingMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/RateLimitingMiddleware.php',
'OC\\AppFramework\\Middleware\\Security\\SameSiteCookieMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php',
'OC\\AppFramework\\Middleware\\Security\\SecurityMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php',
@@ -489,6 +490,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Core\\Command\\Config\\System\\DeleteConfig' => __DIR__ . '/../../..' . '/core/Command/Config/System/DeleteConfig.php',
'OC\\Core\\Command\\Config\\System\\GetConfig' => __DIR__ . '/../../..' . '/core/Command/Config/System/GetConfig.php',
'OC\\Core\\Command\\Config\\System\\SetConfig' => __DIR__ . '/../../..' . '/core/Command/Config/System/SetConfig.php',
+ 'OC\\Core\\Command\\Db\\AddMissingIndices' => __DIR__ . '/../../..' . '/core/Command/Db/AddMissingIndices.php',
'OC\\Core\\Command\\Db\\ConvertFilecacheBigInt' => __DIR__ . '/../../..' . '/core/Command/Db/ConvertFilecacheBigInt.php',
'OC\\Core\\Command\\Db\\ConvertMysqlToMB4' => __DIR__ . '/../../..' . '/core/Command/Db/ConvertMysqlToMB4.php',
'OC\\Core\\Command\\Db\\ConvertType' => __DIR__ . '/../../..' . '/core/Command/Db/ConvertType.php',
diff --git a/lib/private/App/AppManager.php b/lib/private/App/AppManager.php
index 6be892b7f49..e7d4668931c 100644
--- a/lib/private/App/AppManager.php
+++ b/lib/private/App/AppManager.php
@@ -306,7 +306,7 @@ class AppManager implements IAppManager {
* Clear the cached list of apps when enabling/disabling an app
*/
public function clearAppsCache() {
- $settingsMemCache = $this->memCacheFactory->create('settings');
+ $settingsMemCache = $this->memCacheFactory->createDistributed('settings');
$settingsMemCache->clear('listApps');
}
diff --git a/lib/private/App/AppStore/Fetcher/Fetcher.php b/lib/private/App/AppStore/Fetcher/Fetcher.php
index 5ce64671ffa..8bf9ca15349 100644
--- a/lib/private/App/AppStore/Fetcher/Fetcher.php
+++ b/lib/private/App/AppStore/Fetcher/Fetcher.php
@@ -36,6 +36,7 @@ use OCP\Files\NotFoundException;
use OCP\Http\Client\IClientService;
use OCP\IConfig;
use OCP\ILogger;
+use OCP\Util;
abstract class Fetcher {
const INVALIDATE_AFTER_SECONDS = 300;
@@ -170,7 +171,7 @@ abstract class Fetcher {
$file->putContent(json_encode($responseJson));
return json_decode($file->getContent(), true)['data'];
} catch (ConnectException $e) {
- $this->logger->logException($e, ['app' => 'appstoreFetcher']);
+ $this->logger->logException($e, ['app' => 'appstoreFetcher', 'level' => Util::INFO, 'message' => 'Could not connect to appstore']);
return [];
} catch (\Exception $e) {
return [];
diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php
index 0b6291d46de..612728d1356 100644
--- a/lib/private/AppFramework/DependencyInjection/DIContainer.php
+++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php
@@ -54,6 +54,7 @@ use OCP\AppFramework\Http\IOutput;
use OCP\AppFramework\IApi;
use OCP\AppFramework\IAppContainer;
use OCP\AppFramework\QueryException;
+use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Files\Folder;
use OCP\Files\IAppData;
use OCP\GlobalScale\IConfig;
@@ -227,7 +228,6 @@ class DIContainer extends SimpleContainer implements IAppContainer {
$server->getNavigationManager(),
$server->getURLGenerator(),
$server->getLogger(),
- $server->getSession(),
$c['AppName'],
$app->isLoggedIn(),
$app->isAdminUser(),
@@ -236,7 +236,18 @@ class DIContainer extends SimpleContainer implements IAppContainer {
$server->getContentSecurityPolicyNonceManager(),
$server->getAppManager()
);
+ });
+
+ $this->registerService(OC\AppFramework\Middleware\Security\PasswordConfirmationMiddleware::class, function ($c) use ($app) {
+ /** @var \OC\Server $server */
+ $server = $app->getServer();
+ return new OC\AppFramework\Middleware\Security\PasswordConfirmationMiddleware(
+ $c['ControllerMethodReflector'],
+ $server->getSession(),
+ $server->getUserSession(),
+ $server->query(ITimeFactory::class)
+ );
});
$this->registerService('BruteForceMiddleware', function($c) use ($app) {
@@ -309,6 +320,7 @@ class DIContainer extends SimpleContainer implements IAppContainer {
$dispatcher->registerMiddleware($c['CORSMiddleware']);
$dispatcher->registerMiddleware($c['OCSMiddleware']);
$dispatcher->registerMiddleware($c['SecurityMiddleware']);
+ $dispatcher->registerMiddleware($c[OC\AppFramework\Middleware\Security\PasswordConfirmationMiddleware::class]);
$dispatcher->registerMiddleware($c['TwoFactorMiddleware']);
$dispatcher->registerMiddleware($c['BruteForceMiddleware']);
$dispatcher->registerMiddleware($c['RateLimitingMiddleware']);
diff --git a/lib/private/AppFramework/Http/Request.php b/lib/private/AppFramework/Http/Request.php
index 072dd9f172f..77ecb02165b 100644
--- a/lib/private/AppFramework/Http/Request.php
+++ b/lib/private/AppFramework/Http/Request.php
@@ -406,6 +406,7 @@ class Request implements \ArrayAccess, \Countable, IRequest {
if ($this->method === 'PUT'
&& $this->getHeader('Content-Length') !== 0
&& $this->getHeader('Content-Length') !== null
+ && $this->getHeader('Content-Length') !== ''
&& strpos($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded') === false
&& strpos($this->getHeader('Content-Type'), 'application/json') === false
) {
diff --git a/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php b/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php
new file mode 100644
index 00000000000..463e7cd93c9
--- /dev/null
+++ b/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php
@@ -0,0 +1,81 @@
+<?php
+/**
+ * @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OC\AppFramework\Middleware\Security;
+
+use OC\AppFramework\Middleware\Security\Exceptions\NotConfirmedException;
+use OC\AppFramework\Utility\ControllerMethodReflector;
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Middleware;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\ISession;
+use OCP\IUserSession;
+
+class PasswordConfirmationMiddleware extends Middleware {
+ /** @var ControllerMethodReflector */
+ private $reflector;
+ /** @var ISession */
+ private $session;
+ /** @var IUserSession */
+ private $userSession;
+ /** @var ITimeFactory */
+ private $timeFactory;
+
+ /**
+ * PasswordConfirmationMiddleware constructor.
+ *
+ * @param ControllerMethodReflector $reflector
+ * @param ISession $session
+ * @param IUserSession $userSession
+ * @param ITimeFactory $timeFactory
+ */
+ public function __construct(ControllerMethodReflector $reflector,
+ ISession $session,
+ IUserSession $userSession,
+ ITimeFactory $timeFactory) {
+ $this->reflector = $reflector;
+ $this->session = $session;
+ $this->userSession = $userSession;
+ $this->timeFactory = $timeFactory;
+ }
+
+ /**
+ * @param Controller $controller
+ * @param string $methodName
+ * @throws NotConfirmedException
+ */
+ public function beforeController($controller, $methodName) {
+ if ($this->reflector->hasAnnotation('PasswordConfirmationRequired')) {
+ $user = $this->userSession->getUser();
+ $backendClassName = '';
+ if ($user !== null) {
+ $backendClassName = $user->getBackendClassName();
+ }
+
+ $lastConfirm = (int) $this->session->get('last-password-confirm');
+ // we can't check the password against a SAML backend, so skip password confirmation in this case
+ if ($backendClassName !== 'user_saml' && $lastConfirm < ($this->timeFactory->getTime() - (30 * 60 + 15))) { // allow 15 seconds delay
+ throw new NotConfirmedException();
+ }
+ }
+ }
+}
diff --git a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php
index ecd7b1bad5e..c147b5b2475 100644
--- a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php
+++ b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php
@@ -33,7 +33,6 @@ namespace OC\AppFramework\Middleware\Security;
use OC\AppFramework\Middleware\Security\Exceptions\AppNotEnabledException;
use OC\AppFramework\Middleware\Security\Exceptions\CrossSiteRequestForgeryException;
use OC\AppFramework\Middleware\Security\Exceptions\NotAdminException;
-use OC\AppFramework\Middleware\Security\Exceptions\NotConfirmedException;
use OC\AppFramework\Middleware\Security\Exceptions\NotLoggedInException;
use OC\AppFramework\Middleware\Security\Exceptions\StrictCookieMissingException;
use OC\AppFramework\Utility\ControllerMethodReflector;
@@ -50,7 +49,6 @@ use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\OCSController;
use OCP\INavigationManager;
-use OCP\ISession;
use OCP\IURLGenerator;
use OCP\IRequest;
use OCP\ILogger;
@@ -77,8 +75,6 @@ class SecurityMiddleware extends Middleware {
private $urlGenerator;
/** @var ILogger */
private $logger;
- /** @var ISession */
- private $session;
/** @var bool */
private $isLoggedIn;
/** @var bool */
@@ -98,7 +94,6 @@ class SecurityMiddleware extends Middleware {
* @param INavigationManager $navigationManager
* @param IURLGenerator $urlGenerator
* @param ILogger $logger
- * @param ISession $session
* @param string $appName
* @param bool $isLoggedIn
* @param bool $isAdminUser
@@ -112,21 +107,20 @@ class SecurityMiddleware extends Middleware {
INavigationManager $navigationManager,
IURLGenerator $urlGenerator,
ILogger $logger,
- ISession $session,
$appName,
$isLoggedIn,
$isAdminUser,
ContentSecurityPolicyManager $contentSecurityPolicyManager,
CsrfTokenManager $csrfTokenManager,
ContentSecurityPolicyNonceManager $cspNonceManager,
- IAppManager $appManager) {
+ IAppManager $appManager
+ ) {
$this->navigationManager = $navigationManager;
$this->request = $request;
$this->reflector = $reflector;
$this->appName = $appName;
$this->urlGenerator = $urlGenerator;
$this->logger = $logger;
- $this->session = $session;
$this->isLoggedIn = $isLoggedIn;
$this->isAdminUser = $isAdminUser;
$this->contentSecurityPolicyManager = $contentSecurityPolicyManager;
@@ -163,13 +157,6 @@ class SecurityMiddleware extends Middleware {
}
}
- if ($this->reflector->hasAnnotation('PasswordConfirmationRequired')) {
- $lastConfirm = (int) $this->session->get('last-password-confirm');
- if ($lastConfirm < (time() - (30 * 60 + 15))) { // allow 15 seconds delay
- throw new NotConfirmedException();
- }
- }
-
// Check for strict cookie requirement
if($this->reflector->hasAnnotation('StrictCookieRequired') || !$this->reflector->hasAnnotation('NoCSRFRequired')) {
if(!$this->request->passesStrictCookieCheck()) {
diff --git a/lib/private/Avatar.php b/lib/private/Avatar.php
index 5893daa1804..afa9118c509 100644
--- a/lib/private/Avatar.php
+++ b/lib/private/Avatar.php
@@ -141,6 +141,7 @@ class Avatar implements IAvatar {
try {
$generated = $this->folder->getFile('generated');
+ $this->config->setUserValue($this->user->getUID(), 'avatar', 'generated', 'false');
$generated->delete();
} catch (NotFoundException $e) {
//
@@ -161,6 +162,7 @@ class Avatar implements IAvatar {
foreach ($avatars as $avatar) {
$avatar->delete();
}
+ $this->config->setUserValue($this->user->getUID(), 'avatar', 'generated', 'true');
$this->user->triggerChange('avatar', '');
}
@@ -177,6 +179,7 @@ class Avatar implements IAvatar {
$ext = 'png';
$this->folder->newFile('generated');
+ $this->config->setUserValue($this->user->getUID(), 'avatar', 'generated', 'true');
}
if ($size === -1) {
@@ -393,4 +396,18 @@ class Avatar implements IAvatar {
return array(round($r * 255), round($g * 255), round($b * 255));
}
+ public function userChanged($feature, $oldValue, $newValue) {
+ // We only change the avatar on display name changes
+ if ($feature !== 'displayName') {
+ return;
+ }
+
+ // If the avatar is not generated (so an uploaded image) we skip this
+ if (!$this->folder->fileExists('generated')) {
+ return;
+ }
+
+ $this->remove();
+ }
+
}
diff --git a/lib/private/Collaboration/Collaborators/MailPlugin.php b/lib/private/Collaboration/Collaborators/MailPlugin.php
index d28bd3692a4..2e946c4a872 100644
--- a/lib/private/Collaboration/Collaborators/MailPlugin.php
+++ b/lib/private/Collaboration/Collaborators/MailPlugin.php
@@ -30,10 +30,13 @@ use OCP\Collaboration\Collaborators\SearchResultType;
use OCP\Contacts\IManager;
use OCP\Federation\ICloudIdManager;
use OCP\IConfig;
+use OCP\IGroupManager;
+use OCP\IUserSession;
use OCP\Share;
class MailPlugin implements ISearchPlugin {
protected $shareeEnumeration;
+ protected $shareWithGroupOnly;
/** @var IManager */
private $contactsManager;
@@ -42,12 +45,21 @@ class MailPlugin implements ISearchPlugin {
/** @var IConfig */
private $config;
- public function __construct(IManager $contactsManager, ICloudIdManager $cloudIdManager, IConfig $config) {
+ /** @var IGroupManager */
+ private $groupManager;
+
+ /** @var IUserSession */
+ private $userSession;
+
+ public function __construct(IManager $contactsManager, ICloudIdManager $cloudIdManager, IConfig $config, IGroupManager $groupManager, IUserSession $userSession) {
$this->contactsManager = $contactsManager;
$this->cloudIdManager = $cloudIdManager;
$this->config = $config;
+ $this->groupManager = $groupManager;
+ $this->userSession = $userSession;
$this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
+ $this->shareWithGroupOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
}
/**
@@ -77,6 +89,22 @@ class MailPlugin implements ISearchPlugin {
$exactEmailMatch = strtolower($emailAddress) === $lowerSearch;
if (isset($contact['isLocalSystemBook'])) {
+ if ($this->shareWithGroupOnly) {
+ /*
+ * Check if the user may share with the user associated with the e-mail of the just found contact
+ */
+ $userGroups = $this->groupManager->getUserGroupIds($this->userSession->getUser());
+ $found = false;
+ foreach ($userGroups as $userGroup) {
+ if ($this->groupManager->isInGroup($contact['UID'], $userGroup)) {
+ $found = true;
+ break;
+ }
+ }
+ if (!$found) {
+ continue;
+ }
+ }
if ($exactEmailMatch) {
try {
$cloud = $this->cloudIdManager->resolveCloudId($contact['CLOUD'][0]);
diff --git a/lib/private/Console/TimestampFormatter.php b/lib/private/Console/TimestampFormatter.php
index 66dd38e6ac3..9ced9e18b31 100644
--- a/lib/private/Console/TimestampFormatter.php
+++ b/lib/private/Console/TimestampFormatter.php
@@ -31,6 +31,9 @@ class TimestampFormatter implements OutputFormatterInterface {
/** @var IConfig */
protected $config;
+ /** @var OutputFormatterInterface */
+ protected $formatter;
+
/**
* @param IConfig $config
* @param OutputFormatterInterface $formatter
@@ -75,7 +78,7 @@ class TimestampFormatter implements OutputFormatterInterface {
* @return bool
*/
public function hasStyle($name) {
- $this->formatter->hasStyle($name);
+ return $this->formatter->hasStyle($name);
}
/**
@@ -83,6 +86,7 @@ class TimestampFormatter implements OutputFormatterInterface {
*
* @param string $name
* @return OutputFormatterStyleInterface
+ * @throws \InvalidArgumentException When style isn't defined
*/
public function getStyle($name) {
return $this->formatter->getStyle($name);
diff --git a/lib/private/Files/ObjectStore/Swift.php b/lib/private/Files/ObjectStore/Swift.php
index ecfbe136e4c..629fb3ba7ff 100644
--- a/lib/private/Files/ObjectStore/Swift.php
+++ b/lib/private/Files/ObjectStore/Swift.php
@@ -82,7 +82,7 @@ class Swift implements IObjectStore {
}
$cacheFactory = \OC::$server->getMemCacheFactory();
- $this->memcache = $cacheFactory->create('swift::' . $cacheKey);
+ $this->memcache = $cacheFactory->createDistributed('swift::' . $cacheKey);
$this->params = $params;
}
diff --git a/lib/private/Files/Storage/Common.php b/lib/private/Files/Storage/Common.php
index 715b7b18499..56d683ffa25 100644
--- a/lib/private/Files/Storage/Common.php
+++ b/lib/private/Files/Storage/Common.php
@@ -229,6 +229,9 @@ abstract class Common implements Storage, ILockingStorage {
$source = $this->fopen($path1, 'r');
$target = $this->fopen($path2, 'w');
list(, $result) = \OC_Helper::streamCopy($source, $target);
+ if (!$result) {
+ \OC::$server->getLogger()->warning("Failed to write data while copying $path1 to $path2");
+ }
$this->removeCachedFile($path2);
return $result;
}
diff --git a/lib/private/Installer.php b/lib/private/Installer.php
index eb1f8a456bf..4dcf5a8dad9 100644
--- a/lib/private/Installer.php
+++ b/lib/private/Installer.php
@@ -395,6 +395,10 @@ class Installer {
return false;
}
+ if ($this->isInstalledFromGit($appId) === true) {
+ return false;
+ }
+
if ($this->apps === null) {
$this->apps = $this->appFetcher->get();
}
@@ -415,6 +419,22 @@ class Installer {
}
/**
+ * Check if app has been installed from git
+ * @param string $name name of the application to remove
+ * @return boolean
+ *
+ * The function will check if the path contains a .git folder
+ */
+ private function isInstalledFromGit($appId) {
+ $app = \OC_App::findAppInDirectories($appId);
+ if($app === false) {
+ return false;
+ }
+ $basedir = $app['path'].'/'.$appId;
+ return file_exists($basedir.'/.git/');
+ }
+
+ /**
* Check if app is already downloaded
* @param string $name name of the application to remove
* @return boolean
diff --git a/lib/private/IntegrityCheck/Checker.php b/lib/private/IntegrityCheck/Checker.php
index ee7e35550a6..771ac891ab4 100644
--- a/lib/private/IntegrityCheck/Checker.php
+++ b/lib/private/IntegrityCheck/Checker.php
@@ -87,7 +87,7 @@ class Checker {
$this->fileAccessHelper = $fileAccessHelper;
$this->appLocator = $appLocator;
$this->config = $config;
- $this->cache = $cacheFactory->create(self::CACHE_KEY);
+ $this->cache = $cacheFactory->createDistributed(self::CACHE_KEY);
$this->appManager = $appManager;
$this->tempManager = $tempManager;
}
diff --git a/lib/private/OCS/DiscoveryService.php b/lib/private/OCS/DiscoveryService.php
index e547747da25..4425947c55d 100644
--- a/lib/private/OCS/DiscoveryService.php
+++ b/lib/private/OCS/DiscoveryService.php
@@ -46,7 +46,7 @@ class DiscoveryService implements IDiscoveryService {
public function __construct(ICacheFactory $cacheFactory,
IClientService $clientService
) {
- $this->cache = $cacheFactory->create('ocs-discovery');
+ $this->cache = $cacheFactory->createDistributed('ocs-discovery');
$this->client = $clientService->newClient();
}
diff --git a/lib/private/Security/RateLimiting/Backend/MemoryCache.php b/lib/private/Security/RateLimiting/Backend/MemoryCache.php
index 212df664c17..700fa624ed4 100644
--- a/lib/private/Security/RateLimiting/Backend/MemoryCache.php
+++ b/lib/private/Security/RateLimiting/Backend/MemoryCache.php
@@ -45,7 +45,7 @@ class MemoryCache implements IBackend {
*/
public function __construct(ICacheFactory $cacheFactory,
ITimeFactory $timeFactory) {
- $this->cache = $cacheFactory->create(__CLASS__);
+ $this->cache = $cacheFactory->createDistributed(__CLASS__);
$this->timeFactory = $timeFactory;
}
diff --git a/lib/private/Server.php b/lib/private/Server.php
index 6898e93e3bb..4a851d67226 100644
--- a/lib/private/Server.php
+++ b/lib/private/Server.php
@@ -394,9 +394,10 @@ class Server extends ServerContainer implements IServerContainer {
$userSession->listen('\OC\User', 'logout', function () {
\OC_Hook::emit('OC_User', 'logout', array());
});
- $userSession->listen('\OC\User', 'changeUser', function ($user, $feature, $value, $oldValue) {
+ $userSession->listen('\OC\User', 'changeUser', function ($user, $feature, $value, $oldValue) use ($dispatcher) {
/** @var $user \OC\User\User */
\OC_Hook::emit('OC_User', 'changeUser', array('run' => true, 'user' => $user, 'feature' => $feature, 'value' => $value, 'old_value' => $oldValue));
+ $dispatcher->dispatch('OCP\IUser::changeUser', new GenericEvent($user, ['feature' => $feature, 'oldValue' => $oldValue, 'value' => $value]));
});
return $userSession;
});
@@ -952,7 +953,7 @@ class Server extends ServerContainer implements IServerContainer {
$c->getConfig(),
$c->getThemingDefaults(),
\OC::$SERVERROOT,
- $cacheFactory->create('SCSS')
+ $cacheFactory->createDistributed('SCSS')
);
});
$this->registerService(EventDispatcher::class, function () {
@@ -1093,16 +1094,6 @@ class Server extends ServerContainer implements IServerContainer {
return new CloudIdManager();
});
- /* To trick DI since we don't extend the DIContainer here */
- $this->registerService(CleanPreviewsBackgroundJob::class, function (Server $c) {
- return new CleanPreviewsBackgroundJob(
- $c->getRootFolder(),
- $c->getLogger(),
- $c->getJobList(),
- new TimeFactory()
- );
- });
-
$this->registerAlias(\OCP\AppFramework\Utility\IControllerMethodReflector::class, \OC\AppFramework\Utility\ControllerMethodReflector::class);
$this->registerAlias('ControllerMethodReflector', \OCP\AppFramework\Utility\IControllerMethodReflector::class);
@@ -1185,6 +1176,22 @@ class Server extends ServerContainer implements IServerContainer {
$logger->info('Could not cleanup avatar of ' . $user->getUID());
}
});
+
+ $dispatcher->addListener('OCP\IUser::changeUser', function (GenericEvent $e) {
+ $manager = $this->getAvatarManager();
+ /** @var IUser $user */
+ $user = $e->getSubject();
+ $feature = $e->getArgument('feature');
+ $oldValue = $e->getArgument('oldValue');
+ $value = $e->getArgument('value');
+
+ try {
+ $avatar = $manager->getAvatar($user->getUID());
+ $avatar->userChanged($feature, $oldValue, $value);
+ } catch (NotFoundException $e) {
+ // no avatar to remove
+ }
+ });
}
/**
diff --git a/lib/private/Template/CSSResourceLocator.php b/lib/private/Template/CSSResourceLocator.php
index 3c30a9d3356..5ca05d1b953 100644
--- a/lib/private/Template/CSSResourceLocator.php
+++ b/lib/private/Template/CSSResourceLocator.php
@@ -108,7 +108,7 @@ class CSSResourceLocator extends ResourceLocator {
if($this->scssCacher !== null) {
if($this->scssCacher->process($root, $file, $app)) {
- $this->append($root, $this->scssCacher->getCachedSCSS($app, $file), false, true, true);
+ $this->append($root, $this->scssCacher->getCachedSCSS($app, $file), \OC::$WEBROOT, true, true);
return true;
} else {
$this->logger->warning('Failed to compile and/or save '.$root.'/'.$file, ['app' => 'core']);
@@ -145,7 +145,7 @@ class CSSResourceLocator extends ResourceLocator {
}
}
- $this->resources[] = array($webRoot? : '/', $webRoot, $file);
+ $this->resources[] = array($webRoot? : \OC::$WEBROOT, $webRoot, $file);
}
}
}
diff --git a/lib/private/Template/JSConfigHelper.php b/lib/private/Template/JSConfigHelper.php
index 60ac4bfecb0..bdb747e1c9f 100644
--- a/lib/private/Template/JSConfigHelper.php
+++ b/lib/private/Template/JSConfigHelper.php
@@ -101,8 +101,10 @@ class JSConfigHelper {
if ($this->currentUser !== null) {
$uid = $this->currentUser->getUID();
+ $userBackend = $this->currentUser->getBackendClassName();
} else {
$uid = null;
+ $userBackend = '';
}
// Get the config
@@ -147,6 +149,7 @@ class JSConfigHelper {
$array = [
"oc_debug" => $this->config->getSystemValue('debug', false) ? 'true' : 'false',
"oc_isadmin" => $this->groupManager->isAdmin($uid) ? 'true' : 'false',
+ "backendAllowsPasswordConfirmation" => $userBackend === 'user_saml'? 'false' : 'true',
"oc_dataURL" => is_string($dataLocation) ? "\"".$dataLocation."\"" : 'false',
"oc_webroot" => "\"".\OC::$WEBROOT."\"",
"oc_appswebroots" => str_replace('\\/', '/', json_encode($apps_paths)), // Ugly unescape slashes waiting for better solution
@@ -254,6 +257,7 @@ class JSConfigHelper {
$array['oc_userconfig'] = json_encode([
'avatar' => [
'version' => (int)$this->config->getUserValue($uid, 'avatar', 'version', 0),
+ 'generated' => $this->config->getUserValue($uid, 'avatar', 'generated', 'true') === 'true',
]
]);
}
diff --git a/lib/private/Template/SCSSCacher.php b/lib/private/Template/SCSSCacher.php
index 8f6cb85a120..a4604425544 100644
--- a/lib/private/Template/SCSSCacher.php
+++ b/lib/private/Template/SCSSCacher.php
@@ -102,8 +102,7 @@ class SCSSCacher {
$fileNameCSS = $this->prependBaseurlPrefix(str_replace('.scss', '.css', $fileNameSCSS));
$path = implode('/', $path);
-
- $webDir = substr($path, strlen($this->serverRoot)+1);
+ $webDir = $this->getWebDir($path, $app, $this->serverRoot, \OC::$WEBROOT);
try {
$folder = $this->appData->getFolder($app);
@@ -188,7 +187,7 @@ class SCSSCacher {
$scss = new Compiler();
$scss->setImportPaths([
$path,
- \OC::$SERVERROOT . '/core/css/',
+ $this->serverRoot . '/core/css/',
]);
// Continue after throw
$scss->setIgnoreErrors(true);
@@ -283,12 +282,7 @@ class SCSSCacher {
*/
private function rebaseUrls($css, $webDir) {
$re = '/url\([\'"]([\.\w?=\/-]*)[\'"]\)/x';
- // OC\Route\Router:75
- if(($this->config->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true')) {
- $subst = 'url(\'../../'.$webDir.'/$1\')';
- } else {
- $subst = 'url(\'../../../'.$webDir.'/$1\')';
- }
+ $subst = 'url(\''.$webDir.'/$1\')';
return preg_replace($re, $subst, $css);
}
@@ -315,4 +309,23 @@ class SCSSCacher {
$frontendController = ($this->config->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true');
return substr(md5($this->urlGenerator->getBaseUrl() . $frontendController), 0, 8) . '-' . $cssFile;
}
+
+ /**
+ * Get WebDir root
+ * @param string $path the css file path
+ * @param string $appName the app name
+ * @param string $serverRoot the server root path
+ * @param string $webRoot the nextcloud installation root path
+ * @return string the webDir
+ */
+ private function getWebDir($path, $appName, $serverRoot, $webRoot) {
+ // Detect if path is within server root AND if path is within an app path
+ if ( strpos($path, $serverRoot) === false && $appWebPath = \OC_App::getAppWebPath($appName)) {
+ // Get the file path within the app directory
+ $appDirectoryPath = explode($appName, $path)[1];
+ // Remove the webroot
+ return str_replace($webRoot, '', $appWebPath.$appDirectoryPath);
+ }
+ return $webRoot.substr($path, strlen($serverRoot));
+ }
}
diff --git a/lib/private/TemplateLayout.php b/lib/private/TemplateLayout.php
index 997980aa5d7..264c10f5f1b 100644
--- a/lib/private/TemplateLayout.php
+++ b/lib/private/TemplateLayout.php
@@ -291,7 +291,7 @@ class TemplateLayout extends \OC_Template {
new JSCombiner(
\OC::$server->getAppDataDir('js'),
\OC::$server->getURLGenerator(),
- \OC::$server->getMemCacheFactory()->create('JS'),
+ \OC::$server->getMemCacheFactory()->createDistributed('JS'),
\OC::$server->getSystemConfig(),
\OC::$server->getLogger()
)
diff --git a/lib/private/URLGenerator.php b/lib/private/URLGenerator.php
index 6fd22b99a6b..f7d80d41b4f 100644
--- a/lib/private/URLGenerator.php
+++ b/lib/private/URLGenerator.php
@@ -151,7 +151,7 @@ class URLGenerator implements IURLGenerator {
* Returns the path to the image.
*/
public function imagePath($app, $image) {
- $cache = $this->cacheFactory->create('imagePath-'.md5($this->getBaseUrl()).'-');
+ $cache = $this->cacheFactory->createDistributed('imagePath-'.md5($this->getBaseUrl()).'-');
$cacheKey = $app.'-'.$image;
if($key = $cache->get($cacheKey)) {
return $key;
diff --git a/lib/private/Updater/VersionCheck.php b/lib/private/Updater/VersionCheck.php
index 6774ef307b5..c7b829c9ec5 100644
--- a/lib/private/Updater/VersionCheck.php
+++ b/lib/private/Updater/VersionCheck.php
@@ -101,12 +101,10 @@ class VersionCheck {
} else {
libxml_clear_errors();
}
- } else {
- $data = [];
}
// Cache the result
- $this->config->setAppValue('core', 'lastupdateResult', json_encode($data));
+ $this->config->setAppValue('core', 'lastupdateResult', json_encode($tmp));
return $tmp;
}
diff --git a/lib/private/legacy/db.php b/lib/private/legacy/db.php
index da21729f123..6e487e25ad5 100644
--- a/lib/private/legacy/db.php
+++ b/lib/private/legacy/db.php
@@ -105,11 +105,11 @@ class OC_DB {
* @param mixed $stmt OC_DB_StatementWrapper,
* an array with 'sql' and optionally 'limit' and 'offset' keys
* .. or a simple sql query string
- * @param array|null $parameters
+ * @param array $parameters
* @return OC_DB_StatementWrapper
* @throws \OC\DatabaseException
*/
- static public function executeAudited( $stmt, array $parameters = null) {
+ static public function executeAudited( $stmt, array $parameters = []) {
if (is_string($stmt)) {
// convert to an array with 'sql'
if (stripos($stmt, 'LIMIT') !== false) { //OFFSET requires LIMIT, so we only need to check for LIMIT
diff --git a/lib/private/legacy/helper.php b/lib/private/legacy/helper.php
index e611b4d0732..8bbfb235ee2 100644
--- a/lib/private/legacy/helper.php
+++ b/lib/private/legacy/helper.php
@@ -497,7 +497,7 @@ class OC_Helper {
* @return null|string
*/
public static function findBinaryPath($program) {
- $memcache = \OC::$server->getMemCacheFactory()->create('findBinaryPath');
+ $memcache = \OC::$server->getMemCacheFactory()->createDistributed('findBinaryPath');
if ($memcache->hasKey($program)) {
return $memcache->get($program);
}
diff --git a/lib/public/IAvatar.php b/lib/public/IAvatar.php
index 369cafa00c1..a6731b63be9 100644
--- a/lib/public/IAvatar.php
+++ b/lib/public/IAvatar.php
@@ -77,4 +77,10 @@ interface IAvatar {
* @since 9.0.0
*/
public function getFile($size);
+
+ /**
+ * Handle a changed user
+ * @since 13.0.0
+ */
+ public function userChanged($feature, $oldValue, $newValue);
}
diff --git a/settings/Activity/SecurityProvider.php b/settings/Activity/SecurityProvider.php
index f0789842e82..680881b6e31 100644
--- a/settings/Activity/SecurityProvider.php
+++ b/settings/Activity/SecurityProvider.php
@@ -53,7 +53,7 @@ class SecurityProvider implements IProvider {
throw new InvalidArgumentException();
}
- $l = $this->l10n->get('core', $language);
+ $l = $this->l10n->get('settings', $language);
switch ($event->getSubject()) {
case 'twofactor_success':
diff --git a/settings/ajax/uninstallapp.php b/settings/ajax/uninstallapp.php
index b4a2468bd2a..a932e2d79e9 100644
--- a/settings/ajax/uninstallapp.php
+++ b/settings/ajax/uninstallapp.php
@@ -43,8 +43,8 @@ $appId = OC_App::cleanAppId($appId);
$result = OC_App::removeApp($appId);
if($result !== false) {
// FIXME: Clear the cache - move that into some sane helper method
- \OC::$server->getMemCacheFactory()->create('settings')->remove('listApps-0');
- \OC::$server->getMemCacheFactory()->create('settings')->remove('listApps-1');
+ \OC::$server->getMemCacheFactory()->createDistributed('settings')->remove('listApps-0');
+ \OC::$server->getMemCacheFactory()->createDistributed('settings')->remove('listApps-1');
OC_JSON::success(array('data' => array('appid' => $appId)));
} else {
$l = \OC::$server->getL10N('settings');
diff --git a/settings/js/apps.js b/settings/js/apps.js
index 6406e37cbcb..0a6e86ed701 100644
--- a/settings/js/apps.js
+++ b/settings/js/apps.js
@@ -535,20 +535,20 @@ OC.Settings.Apps = OC.Settings.Apps || {
showEmptyUpdates: function() {
$('#apps-list').addClass('hidden');
- $('#apps-list-empty').removeClass('hidden').find('h2').text(t('settings', 'No app updates available'));
+ $('#apps-list-empty').removeClass('hidden').find('h2').text(t('settings', 'App up to date'));
$('#app-list-empty-icon').removeClass('icon-search').addClass('icon-download');
},
updateApp:function(appId, element) {
var oldButtonText = element.val();
- element.val(t('settings','Updating....'));
+ element.val(t('settings','Upgrading …'));
OC.Settings.Apps.hideErrorMessage(appId);
$.post(OC.filePath('settings','ajax','updateapp.php'),{appid:appId},function(result) {
if(!result || result.status !== 'success') {
if (result.data && result.data.message) {
OC.Settings.Apps.showErrorMessage(appId, result.data.message);
} else {
- OC.Settings.Apps.showErrorMessage(appId, t('settings','Error while updating app'));
+ OC.Settings.Apps.showErrorMessage(appId, t('settings','Could not upgrade app'));
}
element.val(oldButtonText);
}
@@ -584,7 +584,7 @@ OC.Settings.Apps = OC.Settings.Apps || {
element.val(t('settings','Removing …'));
$.post(OC.filePath('settings','ajax','uninstallapp.php'),{appid:appId},function(result) {
if(!result || result.status !== 'success') {
- OC.Settings.Apps.showErrorMessage(appId, t('settings','Error while removing app'));
+ OC.Settings.Apps.showErrorMessage(appId, t('settings','Could not remove app'));
element.val(t('settings','Remove'));
} else {
OC.Settings.Apps.rebuildNavigation();
@@ -722,9 +722,9 @@ OC.Settings.Apps = OC.Settings.Apps || {
OC.dialogs.info(
t(
'settings',
- 'The app has been enabled but needs to be updated. You will be redirected to the update page in 5 seconds.'
+ 'The app has been enabled but needs to be upgraded. You will be redirected to the upgrade page in 5 seconds.'
),
- t('settings','App update'),
+ t('settings','App upgrade'),
function () {
window.location.reload();
},
diff --git a/settings/js/settings/personalInfo.js b/settings/js/settings/personalInfo.js
index 3a4542df748..0a39e607762 100644
--- a/settings/js/settings/personalInfo.js
+++ b/settings/js/settings/personalInfo.js
@@ -60,7 +60,7 @@ function updateAvatar (hidedefault) {
$displaydiv.avatar(user.uid, 145, true, null, function() {
$displaydiv.removeClass('loading');
$('#displayavatar img').show();
- if($('#displayavatar img').length === 0) {
+ if($('#displayavatar img').length === 0 || oc_userconfig.avatar.generated) {
$('#removeavatar').removeClass('inlineblock').addClass('hidden');
} else {
$('#removeavatar').removeClass('hidden').addClass('inlineblock');
@@ -129,6 +129,7 @@ function avatarResponseHandler (data) {
$warning.hide();
if (data.status === "success") {
$('#displayavatar .avatardiv').removeClass('icon-loading');
+ oc_userconfig.avatar.generated = false;
updateAvatar();
} else if (data.data === "notsquare") {
showAvatarCropper();
@@ -256,8 +257,14 @@ $(document).ready(function () {
});
+ var userSettings = new OC.Settings.UserSettings();
var federationSettingsView = new OC.Settings.FederationSettingsView({
- el: '#personal-settings'
+ el: '#personal-settings',
+ config: userSettings
+ });
+
+ userSettings.on("sync", function() {
+ updateAvatar(false);
});
federationSettingsView.render();
@@ -362,6 +369,7 @@ $(document).ready(function () {
type: 'DELETE',
url: OC.generateUrl('/avatar/'),
success: function () {
+ oc_userconfig.avatar.generated = true;
updateAvatar(true);
}
});
@@ -392,7 +400,7 @@ $(document).ready(function () {
// Load the big avatar
var user = OC.getCurrentUser();
$('#avatarform .avatardiv').avatar(user.uid, 145, true, null, function() {
- if($('#displayavatar img').length === 0) {
+ if($('#displayavatar img').length === 0 || oc_userconfig.avatar.generated) {
$('#removeavatar').removeClass('inlineblock').addClass('hidden');
} else {
$('#removeavatar').removeClass('hidden').addClass('inlineblock');
diff --git a/settings/js/users/users.js b/settings/js/users/users.js
index af41790a7c4..1d6cb93452a 100644
--- a/settings/js/users/users.js
+++ b/settings/js/users/users.js
@@ -970,8 +970,9 @@ $(document).ready(function () {
$tr.addClass('active');
});
- $(document.body).click(function () {
+ $(document).on('mouseup', function () {
$('#userlist tr.active').removeClass('active');
+ $('#userlist .popovermenu.open').removeClass('open');
});
$userListBody.on('click', '.action-togglestate', function (event) {
diff --git a/tests/Core/Command/Maintenance/UpdateTheme.php b/tests/Core/Command/Maintenance/UpdateTheme.php
index fbdea0b72b4..cbc417dbdba 100644
--- a/tests/Core/Command/Maintenance/UpdateTheme.php
+++ b/tests/Core/Command/Maintenance/UpdateTheme.php
@@ -74,7 +74,7 @@ class UpdateThemeTest extends TestCase {
->method('clear')
->with('');
$this->cacheFactory->expects($this->once())
- ->method('create')
+ ->method('createDistributed')
->with('imagePath')
->willReturn($cache);
self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]);
diff --git a/tests/Core/Controller/LoginControllerTest.php b/tests/Core/Controller/LoginControllerTest.php
index e02b8403a2a..ddf7a865d66 100644
--- a/tests/Core/Controller/LoginControllerTest.php
+++ b/tests/Core/Controller/LoginControllerTest.php
@@ -182,12 +182,43 @@ class LoginControllerTest extends TestCase {
'alt_login' => [],
'rememberLoginState' => 0,
'resetPasswordLink' => null,
+ 'hideRemeberLoginState' => false,
],
'guest'
);
$this->assertEquals($expectedResponse, $this->loginController->showLoginForm('', '', ''));
}
+ public function testShowLoginFormForFlowAuth() {
+ $this->userSession
+ ->expects($this->once())
+ ->method('isLoggedIn')
+ ->willReturn(false);
+ $this->session
+ ->expects($this->once())
+ ->method('exists')
+ ->with('client.flow.state.token')
+ ->willReturn(true);
+
+ $expectedResponse = new TemplateResponse(
+ 'core',
+ 'login',
+ [
+ 'messages' => [],
+ 'redirect_url' => 'login/flow',
+ 'loginName' => '',
+ 'user_autofocus' => true,
+ 'canResetPassword' => true,
+ 'alt_login' => [],
+ 'rememberLoginState' => 0,
+ 'resetPasswordLink' => null,
+ 'hideRemeberLoginState' => true,
+ ],
+ 'guest'
+ );
+ $this->assertEquals($expectedResponse, $this->loginController->showLoginForm('', 'login/flow', ''));
+ }
+
/**
* @return array
*/
@@ -240,6 +271,7 @@ class LoginControllerTest extends TestCase {
'alt_login' => [],
'rememberLoginState' => 0,
'resetPasswordLink' => false,
+ 'hideRemeberLoginState' => false,
],
'guest'
);
@@ -278,6 +310,7 @@ class LoginControllerTest extends TestCase {
'alt_login' => [],
'rememberLoginState' => 0,
'resetPasswordLink' => false,
+ 'hideRemeberLoginState' => false,
],
'guest'
);
diff --git a/tests/Settings/Activity/SecurityProviderTest.php b/tests/Settings/Activity/SecurityProviderTest.php
index 21fc28f3c3b..552548984d7 100644
--- a/tests/Settings/Activity/SecurityProviderTest.php
+++ b/tests/Settings/Activity/SecurityProviderTest.php
@@ -87,7 +87,7 @@ class SecurityProviderTest extends TestCase {
->willReturn('security');
$this->l10n->expects($this->once())
->method('get')
- ->with('core', $lang)
+ ->with('settings', $lang)
->willReturn($l);
$this->urlGenerator->expects($this->once())
->method('imagePath')
@@ -119,7 +119,7 @@ class SecurityProviderTest extends TestCase {
->willReturn('security');
$this->l10n->expects($this->once())
->method('get')
- ->with('core', $lang)
+ ->with('settings', $lang)
->willReturn($l);
$event->expects($this->once())
->method('getSubject')
diff --git a/tests/acceptance/features/app-files.feature b/tests/acceptance/features/app-files.feature
index ac2d05fac2c..2cb43611b9a 100644
--- a/tests/acceptance/features/app-files.feature
+++ b/tests/acceptance/features/app-files.feature
@@ -23,6 +23,17 @@ Feature: app-files
When I open the details view for "welcome.txt"
Then I see that the details view for "All files" section is open
+ Scenario: open the menu in a public shared link
+ Given I act as John
+ And I am logged in
+ And I share the link for "welcome.txt"
+ And I write down the shared link
+ When I act as Jane
+ And I visit the shared link I wrote down
+ And I see that the current page is the shared link I wrote down
+ And I open the Share menu
+ Then I see that the Share menu is shown
+
Scenario: set a password to a shared link
Given I am logged in
And I share the link for "welcome.txt"
diff --git a/tests/acceptance/features/bootstrap/FilesAppContext.php b/tests/acceptance/features/bootstrap/FilesAppContext.php
index 338823a9478..4951dc43f1d 100644
--- a/tests/acceptance/features/bootstrap/FilesAppContext.php
+++ b/tests/acceptance/features/bootstrap/FilesAppContext.php
@@ -452,7 +452,16 @@ class FilesAppContext implements Context, ActorAwareInterface {
* @Given I write down the shared link
*/
public function iWriteDownTheSharedLink() {
- $this->actor->getSharedNotebook()["shared link"] = $this->actor->find(self::shareLinkField(), 10)->getValue();
+ // The shared link field always exists in the DOM (once the "Sharing"
+ // tab is loaded), but its value is the actual shared link only when it
+ // is visible.
+ if (!$this->waitForElementToBeEventuallyShown(
+ self::shareLinkField(),
+ $timeout = 10 * $this->actor->getFindTimeoutMultiplier())) {
+ PHPUnit_Framework_Assert::fail("The shared link was not shown yet after $timeout seconds");
+ }
+
+ $this->actor->getSharedNotebook()["shared link"] = $this->actor->find(self::shareLinkField())->getValue();
}
/**
@@ -606,7 +615,9 @@ class FilesAppContext implements Context, ActorAwareInterface {
* @When I see that the :tabName tab in the details view is eventually loaded
*/
public function iSeeThatTheTabInTheDetailsViewIsEventuallyLoaded($tabName) {
- if (!$this->waitForElementToBeEventuallyNotShown(self::loadingIconForTabInCurrentSectionDetailsViewNamed($tabName), $timeout = 10)) {
+ if (!$this->waitForElementToBeEventuallyNotShown(
+ self::loadingIconForTabInCurrentSectionDetailsViewNamed($tabName),
+ $timeout = 10 * $this->actor->getFindTimeoutMultiplier())) {
PHPUnit_Framework_Assert::fail("The $tabName tab in the details view has not been loaded after $timeout seconds");
}
}
@@ -622,7 +633,9 @@ class FilesAppContext implements Context, ActorAwareInterface {
* @Then I see that the working icon for password protect is eventually not shown
*/
public function iSeeThatTheWorkingIconForPasswordProtectIsEventuallyNotShown() {
- if (!$this->waitForElementToBeEventuallyNotShown(self::passwordProtectWorkingIcon(), $timeout = 10)) {
+ if (!$this->waitForElementToBeEventuallyNotShown(
+ self::passwordProtectWorkingIcon(),
+ $timeout = 10 * $this->actor->getFindTimeoutMultiplier())) {
PHPUnit_Framework_Assert::fail("The working icon for password protect is still shown after $timeout seconds");
}
}
@@ -637,10 +650,24 @@ class FilesAppContext implements Context, ActorAwareInterface {
$this->iSeeThatTheWorkingIconForPasswordProtectIsEventuallyNotShown();
}
+ private function waitForElementToBeEventuallyShown($elementLocator, $timeout = 10, $timeoutStep = 1) {
+ $actor = $this->actor;
+
+ $elementShownCallback = function() use ($actor, $elementLocator) {
+ try {
+ return $actor->find($elementLocator)->isVisible();
+ } catch (NoSuchElementException $exception) {
+ return false;
+ }
+ };
+
+ return Utils::waitFor($elementShownCallback, $timeout, $timeoutStep);
+ }
+
private function waitForElementToBeEventuallyNotShown($elementLocator, $timeout = 10, $timeoutStep = 1) {
$actor = $this->actor;
- $elementNotFoundCallback = function() use ($actor, $elementLocator) {
+ $elementNotShownCallback = function() use ($actor, $elementLocator) {
try {
return !$actor->find($elementLocator)->isVisible();
} catch (NoSuchElementException $exception) {
@@ -648,6 +675,6 @@ class FilesAppContext implements Context, ActorAwareInterface {
}
};
- return Utils::waitFor($elementNotFoundCallback, $timeout, $timeoutStep);
+ return Utils::waitFor($elementNotShownCallback, $timeout, $timeoutStep);
}
}
diff --git a/tests/acceptance/features/bootstrap/FilesSharingAppContext.php b/tests/acceptance/features/bootstrap/FilesSharingAppContext.php
index 88c1180c753..f3386b46db9 100644
--- a/tests/acceptance/features/bootstrap/FilesSharingAppContext.php
+++ b/tests/acceptance/features/bootstrap/FilesSharingAppContext.php
@@ -54,6 +54,49 @@ class FilesSharingAppContext implements Context, ActorAwareInterface {
/**
* @return Locator
*/
+ public static function shareMenuButton() {
+ return Locator::forThe()->id("share-menutoggle")->
+ describedAs("Share menu button in Shared file page");
+ }
+
+ /**
+ * @return Locator
+ */
+ public static function shareMenu() {
+ return Locator::forThe()->id("share-menu")->
+ describedAs("Share menu in Shared file page");
+ }
+
+ /**
+ * @return Locator
+ */
+ public static function downloadItemInShareMenu() {
+ return Locator::forThe()->id("download")->
+ descendantOf(self::shareMenu())->
+ describedAs("Download item in Share menu in Shared file page");
+ }
+
+ /**
+ * @return Locator
+ */
+ public static function directLinkItemInShareMenu() {
+ return Locator::forThe()->id("directLink-container")->
+ descendantOf(self::shareMenu())->
+ describedAs("Direct link item in Share menu in Shared file page");
+ }
+
+ /**
+ * @return Locator
+ */
+ public static function saveItemInShareMenu() {
+ return Locator::forThe()->id("save")->
+ descendantOf(self::shareMenu())->
+ describedAs("Save item in Share menu in Shared file page");
+ }
+
+ /**
+ * @return Locator
+ */
public static function textPreview() {
return Locator::forThe()->css(".text-preview")->
describedAs("Text preview in Shared file page");
@@ -75,6 +118,13 @@ class FilesSharingAppContext implements Context, ActorAwareInterface {
}
/**
+ * @When I open the Share menu
+ */
+ public function iOpenTheShareMenu() {
+ $this->actor->find(self::shareMenuButton(), 10)->click();
+ }
+
+ /**
* @Then I see that the current page is the Authenticate page for the shared link I wrote down
*/
public function iSeeThatTheCurrentPageIsTheAuthenticatePageForTheSharedLinkIWroteDown() {
@@ -101,10 +151,44 @@ class FilesSharingAppContext implements Context, ActorAwareInterface {
}
/**
+ * @Then I see that the Share menu is shown
+ */
+ public function iSeeThatTheShareMenuIsShown() {
+ // Unlike other menus, the Share menu is always present in the DOM, so
+ // the element could be found when it was no made visible yet due to the
+ // command not having been processed by the browser.
+ if (!$this->waitForElementToBeEventuallyShown(
+ self::shareMenu(), $timeout = 10 * $this->actor->getFindTimeoutMultiplier())) {
+ PHPUnit_Framework_Assert::fail("The Share menu is not visible yet after $timeout seconds");
+ }
+
+ PHPUnit_Framework_Assert::assertTrue(
+ $this->actor->find(self::downloadItemInShareMenu())->isVisible());
+ PHPUnit_Framework_Assert::assertTrue(
+ $this->actor->find(self::directLinkItemInShareMenu())->isVisible());
+ PHPUnit_Framework_Assert::assertTrue(
+ $this->actor->find(self::saveItemInShareMenu())->isVisible());
+ }
+
+ /**
* @Then I see that the shared file preview shows the text :text
*/
public function iSeeThatTheSharedFilePreviewShowsTheText($text) {
PHPUnit_Framework_Assert::assertContains($text, $this->actor->find(self::textPreview(), 10)->getText());
}
+ private function waitForElementToBeEventuallyShown($elementLocator, $timeout = 10, $timeoutStep = 1) {
+ $actor = $this->actor;
+
+ $elementShownCallback = function() use ($actor, $elementLocator) {
+ try {
+ return $actor->find($elementLocator)->isVisible();
+ } catch (NoSuchElementException $exception) {
+ return false;
+ }
+ };
+
+ return Utils::waitFor($elementShownCallback, $timeout, $timeoutStep);
+ }
+
}
diff --git a/tests/karma.config.js b/tests/karma.config.js
index fb613857e91..0254d6a3335 100644
--- a/tests/karma.config.js
+++ b/tests/karma.config.js
@@ -237,7 +237,7 @@ module.exports = function(config) {
basePath: '..',
// frameworks to use
- frameworks: ['jasmine', 'jasmine-sinon'],
+ frameworks: ['jasmine', 'jasmine-sinon', 'viewport'],
// list of files / patterns to load in the browser
files: files,
diff --git a/tests/lib/App/AppManagerTest.php b/tests/lib/App/AppManagerTest.php
index c2c0ea55072..c361db7b76b 100644
--- a/tests/lib/App/AppManagerTest.php
+++ b/tests/lib/App/AppManagerTest.php
@@ -100,7 +100,7 @@ class AppManagerTest extends TestCase {
$this->cache = $this->createMock(ICache::class);
$this->eventDispatcher = $this->createMock(EventDispatcherInterface::class);
$this->cacheFactory->expects($this->any())
- ->method('create')
+ ->method('createDistributed')
->with('settings')
->willReturn($this->cache);
$this->manager = new AppManager($this->userSession, $this->appConfig, $this->groupManager, $this->cacheFactory, $this->eventDispatcher);
diff --git a/tests/lib/AppFramework/Middleware/Security/PasswordConfirmationMiddlewareTest.php b/tests/lib/AppFramework/Middleware/Security/PasswordConfirmationMiddlewareTest.php
new file mode 100644
index 00000000000..2c610736f4a
--- /dev/null
+++ b/tests/lib/AppFramework/Middleware/Security/PasswordConfirmationMiddlewareTest.php
@@ -0,0 +1,129 @@
+<?php
+/**
+ * @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace Test\AppFramework\Middleware\Security;
+
+use OC\AppFramework\Middleware\Security\Exceptions\NotConfirmedException;
+use OC\AppFramework\Middleware\Security\PasswordConfirmationMiddleware;
+use OC\AppFramework\Utility\ControllerMethodReflector;
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\ISession;
+use OCP\IUser;
+use OCP\IUserSession;
+use Test\TestCase;
+
+class PasswordConfirmationMiddlewareTest extends TestCase {
+ /** @var ControllerMethodReflector */
+ private $reflector;
+ /** @var ISession|\PHPUnit_Framework_MockObject_MockObject */
+ private $session;
+ /** @var IUserSession|\PHPUnit_Framework_MockObject_MockObject */
+ private $userSession;
+ /** @var IUser|\PHPUnit_Framework_MockObject_MockObject */
+ private $user;
+ /** @var PasswordConfirmationMiddleware */
+ private $middleware;
+ /** @var Controller */
+ private $contoller;
+ /** @var ITimeFactory|\PHPUnit_Framework_MockObject_MockObject */
+ private $timeFactory;
+
+ protected function setUp() {
+ $this->reflector = new ControllerMethodReflector();
+ $this->session = $this->createMock(ISession::class);
+ $this->userSession = $this->createMock(IUserSession::class);
+ $this->user = $this->createMock(IUser::class);
+ $this->contoller = $this->createMock(Controller::class);
+ $this->timeFactory = $this->createMock(ITimeFactory::class);
+
+ $this->middleware = new PasswordConfirmationMiddleware(
+ $this->reflector,
+ $this->session,
+ $this->userSession,
+ $this->timeFactory
+ );
+ }
+
+ public function testNoAnnotation() {
+ $this->reflector->reflect(__CLASS__, __FUNCTION__);
+ $this->session->expects($this->never())
+ ->method($this->anything());
+ $this->userSession->expects($this->never())
+ ->method($this->anything());
+
+ $this->middleware->beforeController($this->contoller, __FUNCTION__);
+ }
+
+ /**
+ * @TestAnnotation
+ */
+ public function testDifferentAnnotation() {
+ $this->reflector->reflect(__CLASS__, __FUNCTION__);
+ $this->session->expects($this->never())
+ ->method($this->anything());
+ $this->userSession->expects($this->never())
+ ->method($this->anything());
+
+ $this->middleware->beforeController($this->contoller, __FUNCTION__);
+ }
+
+ /**
+ * @PasswordConfirmationRequired
+ * @dataProvider testProvider
+ */
+ public function testAnnotation($backend, $lastConfirm, $currentTime, $exception) {
+ $this->reflector->reflect(__CLASS__, __FUNCTION__);
+
+ $this->user->method('getBackendClassName')
+ ->willReturn($backend);
+ $this->userSession->method('getUser')
+ ->willReturn($this->user);
+
+ $this->session->method('get')
+ ->with('last-password-confirm')
+ ->willReturn($lastConfirm);
+
+ $this->timeFactory->method('getTime')
+ ->willReturn($currentTime);
+
+ $thrown = false;
+ try {
+ $this->middleware->beforeController($this->contoller, __FUNCTION__);
+ } catch (NotConfirmedException $e) {
+ $thrown = true;
+ }
+
+ $this->assertSame($exception, $thrown);
+ }
+
+ public function testProvider() {
+ return [
+ ['foo', 2000, 4000, true],
+ ['foo', 2000, 3000, false],
+ ['user_saml', 2000, 4000, false],
+ ['user_saml', 2000, 3000, false],
+ ['foo', 2000, 3815, false],
+ ['foo', 2000, 3816, true],
+ ];
+ }
+}
diff --git a/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php b/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php
index 6b311c7ae15..151d6935e7f 100644
--- a/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php
+++ b/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php
@@ -50,6 +50,8 @@ use OCP\INavigationManager;
use OCP\IRequest;
use OCP\ISession;
use OCP\IURLGenerator;
+use OCP\IUser;
+use OCP\IUserSession;
use OCP\Security\ISecureRandom;
class SecurityMiddlewareTest extends \Test\TestCase {
@@ -62,8 +64,6 @@ class SecurityMiddlewareTest extends \Test\TestCase {
private $secException;
/** @var SecurityException */
private $secAjaxException;
- /** @var ISession|\PHPUnit_Framework_MockObject_MockObject */
- private $session;
/** @var IRequest|\PHPUnit_Framework_MockObject_MockObject */
private $request;
/** @var ControllerMethodReflector */
@@ -82,6 +82,8 @@ class SecurityMiddlewareTest extends \Test\TestCase {
private $cspNonceManager;
/** @var IAppManager|\PHPUnit_Framework_MockObject_MockObject */
private $appManager;
+ /** @var IUserSession|\PHPUnit_Framework_MockObject_MockObject */
+ private $userSession;
protected function setUp() {
parent::setUp();
@@ -91,7 +93,6 @@ class SecurityMiddlewareTest extends \Test\TestCase {
$this->logger = $this->createMock(ILogger::class);
$this->navigationManager = $this->createMock(INavigationManager::class);
$this->urlGenerator = $this->createMock(IURLGenerator::class);
- $this->session = $this->createMock(ISession::class);
$this->request = $this->createMock(IRequest::class);
$this->contentSecurityPolicyManager = $this->createMock(ContentSecurityPolicyManager::class);
$this->csrfTokenManager = $this->createMock(CsrfTokenManager::class);
@@ -117,7 +118,6 @@ class SecurityMiddlewareTest extends \Test\TestCase {
$this->navigationManager,
$this->urlGenerator,
$this->logger,
- $this->session,
'files',
$isLoggedIn,
$isAdminUser,
diff --git a/tests/lib/AvatarTest.php b/tests/lib/AvatarTest.php
index 240aecc115e..9da719c26de 100644
--- a/tests/lib/AvatarTest.php
+++ b/tests/lib/AvatarTest.php
@@ -210,7 +210,7 @@ class AvatarTest extends \Test\TestCase {
->method('putContent')
->with($image->data());
- $this->config->expects($this->once())
+ $this->config->expects($this->exactly(3))
->method('setUserValue');
$this->config->expects($this->once())
->method('getUserValue');
diff --git a/tests/lib/Collaboration/Collaborators/MailPluginTest.php b/tests/lib/Collaboration/Collaborators/MailPluginTest.php
index 9c9d9cff909..b728ae521e2 100644
--- a/tests/lib/Collaboration/Collaborators/MailPluginTest.php
+++ b/tests/lib/Collaboration/Collaborators/MailPluginTest.php
@@ -31,6 +31,8 @@ use OCP\Collaboration\Collaborators\SearchResultType;
use OCP\Contacts\IManager;
use OCP\Federation\ICloudIdManager;
use OCP\IConfig;
+use OCP\IGroupManager;
+use OCP\IUserSession;
use OCP\Share;
use Test\TestCase;
@@ -50,17 +52,25 @@ class MailPluginTest extends TestCase {
/** @var SearchResult */
protected $searchResult;
+ /** @var IGroupManager|\PHPUnit_Framework_MockObject_MockObject */
+ protected $groupManager;
+
+ /** @var IUserSession|\PHPUnit_Framework_MockObject_MockObject */
+ protected $userSession;
+
public function setUp() {
parent::setUp();
$this->config = $this->createMock(IConfig::class);
$this->contactsManager = $this->createMock(IManager::class);
+ $this->groupManager = $this->createMock(IGroupManager::class);
+ $this->userSession = $this->createMock(IUserSession::class);
$this->cloudIdManager = new CloudIdManager();
$this->searchResult = new SearchResult();
}
public function instantiatePlugin() {
- $this->plugin = new MailPlugin($this->contactsManager, $this->cloudIdManager, $this->config);
+ $this->plugin = new MailPlugin($this->contactsManager, $this->cloudIdManager, $this->config, $this->groupManager, $this->userSession);
}
/**
@@ -333,4 +343,131 @@ class MailPluginTest extends TestCase {
]
];
}
+
+ /**
+ * @dataProvider dataGetEmailGroupsOnly
+ *
+ * @param string $searchTerm
+ * @param array $contacts
+ * @param array $expected
+ * @param bool $exactIdMatch
+ * @param bool $reachedEnd
+ * @param array groups
+ */
+ public function testSearchGroupsOnly($searchTerm, $contacts, $expected, $exactIdMatch, $reachedEnd, $userToGroupMapping) {
+ $this->config->expects($this->any())
+ ->method('getAppValue')
+ ->willReturnCallback(
+ function($appName, $key, $default) {
+ if ($appName === 'core' && $key === 'shareapi_allow_share_dialog_user_enumeration') {
+ return 'yes';
+ } else if ($appName === 'core' && $key === 'shareapi_only_share_with_group_members') {
+ return 'yes';
+ }
+ return $default;
+ }
+ );
+
+ $this->instantiatePlugin();
+
+ /** @var \OCP\IUser | \PHPUnit_Framework_MockObject_MockObject */
+ $currentUser = $this->createMock('\OCP\IUser');
+
+ $currentUser->expects($this->any())
+ ->method('getUID')
+ ->willReturn('currentUser');
+
+ $this->contactsManager->expects($this->any())
+ ->method('search')
+ ->with($searchTerm, ['EMAIL', 'FN'])
+ ->willReturn($contacts);
+
+ $this->userSession->expects($this->any())
+ ->method('getUser')
+ ->willReturn($currentUser);
+
+ $this->groupManager->expects($this->any())
+ ->method('getUserGroupIds')
+ ->willReturnCallback(function(\OCP\IUser $user) use ($userToGroupMapping) {
+ return $userToGroupMapping[$user->getUID()];
+ });
+
+ $this->groupManager->expects($this->any())
+ ->method('isInGroup')
+ ->willReturnCallback(function($userId, $group) use ($userToGroupMapping) {
+ return in_array($group, $userToGroupMapping[$userId]);
+ });
+
+ $moreResults = $this->plugin->search($searchTerm, 0, 0, $this->searchResult);
+ $result = $this->searchResult->asArray();
+
+ $this->assertSame($exactIdMatch, $this->searchResult->hasExactIdMatch(new SearchResultType('emails')));
+ $this->assertEquals($expected, $result);
+ $this->assertSame($reachedEnd, $moreResults);
+ }
+
+ public function dataGetEmailGroupsOnly() {
+ return [
+ // The user `User` can share with the current user
+ [
+ 'test',
+ [
+ [
+ 'FN' => 'User',
+ 'EMAIL' => ['test@example.com'],
+ 'CLOUD' => ['test@localhost'],
+ 'isLocalSystemBook' => true,
+ 'UID' => 'User'
+ ]
+ ],
+ ['users' => [['label' => 'User (test@example.com)','value' => ['shareType' => 0, 'shareWith' => 'test'],]], 'emails' => [], 'exact' => ['emails' => [], 'users' => []]],
+ false,
+ true,
+ [
+ "currentUser" => ["group1"],
+ "User" => ["group1"]
+ ]
+ ],
+ // The user `User` cannot share with the current user
+ [
+ 'test',
+ [
+ [
+ 'FN' => 'User',
+ 'EMAIL' => ['test@example.com'],
+ 'CLOUD' => ['test@localhost'],
+ 'isLocalSystemBook' => true,
+ 'UID' => 'User'
+ ]
+ ],
+ ['emails'=> [], 'exact' => ['emails' => []]],
+ false,
+ true,
+ [
+ "currentUser" => ["group1"],
+ "User" => ["group2"]
+ ]
+ ],
+ // The user `User` cannot share with the current user, but there is an exact match on the e-mail address -> share by e-mail
+ [
+ 'test@example.com',
+ [
+ [
+ 'FN' => 'User',
+ 'EMAIL' => ['test@example.com'],
+ 'CLOUD' => ['test@localhost'],
+ 'isLocalSystemBook' => true,
+ 'UID' => 'User'
+ ]
+ ],
+ ['emails' => [], 'exact' => ['emails' => [['label' => 'test@example.com', 'value' => ['shareType' => 4,'shareWith' => 'test@example.com']]]]],
+ false,
+ true,
+ [
+ "currentUser" => ["group1"],
+ "User" => ["group2"]
+ ]
+ ]
+ ];
+ }
}
diff --git a/tests/lib/IntegrityCheck/CheckerTest.php b/tests/lib/IntegrityCheck/CheckerTest.php
index 049017cb5e8..09e6990a0f3 100644
--- a/tests/lib/IntegrityCheck/CheckerTest.php
+++ b/tests/lib/IntegrityCheck/CheckerTest.php
@@ -60,7 +60,7 @@ class CheckerTest extends TestCase {
$this->cacheFactory
->expects($this->any())
- ->method('create')
+ ->method('createDistributed')
->with('oc.integritycheck.checker')
->will($this->returnValue(new NullCache()));
diff --git a/tests/lib/LegacyHelperTest.php b/tests/lib/LegacyHelperTest.php
index f1e22ea600e..736c5bf7fad 100644
--- a/tests/lib/LegacyHelperTest.php
+++ b/tests/lib/LegacyHelperTest.php
@@ -12,6 +12,17 @@ use OC\Files\View;
use OC_Helper;
class LegacyHelperTest extends \Test\TestCase {
+ /** @var string */
+ private $originalWebRoot;
+
+ public function setUp() {
+ $this->originalWebRoot = \OC::$WEBROOT;
+ }
+
+ public function tearDown() {
+ // Reset webRoot
+ \OC::$WEBROOT = $this->originalWebRoot;
+ }
/**
* @dataProvider humanFileSizeProvider
diff --git a/tests/lib/Security/RateLimiting/Backend/MemoryCacheTest.php b/tests/lib/Security/RateLimiting/Backend/MemoryCacheTest.php
index 34c326e72e1..bacd2b7bf6f 100644
--- a/tests/lib/Security/RateLimiting/Backend/MemoryCacheTest.php
+++ b/tests/lib/Security/RateLimiting/Backend/MemoryCacheTest.php
@@ -46,7 +46,7 @@ class MemoryCacheTest extends TestCase {
$this->cacheFactory
->expects($this->once())
- ->method('create')
+ ->method('createDistributed')
->with('OC\Security\RateLimiting\Backend\MemoryCache')
->willReturn($this->cache);
diff --git a/tests/lib/Template/SCSSCacherTest.php b/tests/lib/Template/SCSSCacherTest.php
index 3825bc44c59..fca9500810e 100644
--- a/tests/lib/Template/SCSSCacherTest.php
+++ b/tests/lib/Template/SCSSCacherTest.php
@@ -352,19 +352,10 @@ class SCSSCacherTest extends \Test\TestCase {
}
public function testRebaseUrls() {
- $webDir = 'apps/files/css';
+ $webDir = '/apps/files/css';
$css = '#id { background-image: url(\'../img/image.jpg\'); }';
$actual = self::invokePrivate($this->scssCacher, 'rebaseUrls', [$css, $webDir]);
- $expected = '#id { background-image: url(\'../../../apps/files/css/../img/image.jpg\'); }';
- $this->assertEquals($expected, $actual);
- }
-
- public function testRebaseUrlsIgnoreFrontendController() {
- $this->config->expects($this->once())->method('getSystemValue')->with('htaccess.IgnoreFrontController', false)->willReturn(true);
- $webDir = 'apps/files/css';
- $css = '#id { background-image: url(\'../img/image.jpg\'); }';
- $actual = self::invokePrivate($this->scssCacher, 'rebaseUrls', [$css, $webDir]);
- $expected = '#id { background-image: url(\'../../apps/files/css/../img/image.jpg\'); }';
+ $expected = '#id { background-image: url(\'/apps/files/css/../img/image.jpg\'); }';
$this->assertEquals($expected, $actual);
}
@@ -393,4 +384,55 @@ class SCSSCacherTest extends \Test\TestCase {
$this->assertEquals(substr($result, 1), $actual);
}
+ private function randomString() {
+ return sha1(uniqid(mt_rand(), true));
+ }
+
+ private function rrmdir($directory) {
+ $files = array_diff(scandir($directory), array('.','..'));
+ foreach ($files as $file) {
+ if (is_dir($directory . '/' . $file)) {
+ $this->rrmdir($directory . '/' . $file);
+ } else {
+ unlink($directory . '/' . $file);
+ }
+ }
+ return rmdir($directory);
+ }
+
+ public function dataGetWebDir() {
+ return [
+ // Root installation
+ ['/http/core/css', 'core', '', '/http', '/core/css'],
+ ['/http/apps/scss/css', 'scss', '', '/http', '/apps/scss/css'],
+ ['/srv/apps2/scss/css', 'scss', '', '/http', '/apps2/scss/css'],
+ // Sub directory install
+ ['/http/nextcloud/core/css', 'core', '/nextcloud', '/http/nextcloud', '/nextcloud/core/css'],
+ ['/http/nextcloud/apps/scss/css', 'scss', '/nextcloud', '/http/nextcloud', '/nextcloud/apps/scss/css'],
+ ['/srv/apps2/scss/css', 'scss', '/nextcloud', '/http/nextcloud', '/apps2/scss/css']
+ ];
+ }
+
+ /**
+ * @param $path
+ * @param $appName
+ * @param $webRoot
+ * @param $serverRoot
+ * @dataProvider dataGetWebDir
+ */
+ public function testgetWebDir($path, $appName, $webRoot, $serverRoot, $correctWebDir) {
+ $tmpDir = sys_get_temp_dir().'/'.$this->randomString();
+ // Adding fake apps folder and create fake app install
+ \OC::$APPSROOTS[] = [
+ 'path' => $tmpDir.'/srv/apps2',
+ 'url' => '/apps2',
+ 'writable' => false
+ ];
+ mkdir($tmpDir.$path, 0777, true);
+ $actual = self::invokePrivate($this->scssCacher, 'getWebDir', [$tmpDir.$path, $appName, $tmpDir.$serverRoot, $webRoot]);
+ $this->assertEquals($correctWebDir, $actual);
+ array_pop(\OC::$APPSROOTS);
+ $this->rrmdir($tmpDir.$path);
+ }
+
}
diff --git a/tests/lib/Updater/VersionCheckTest.php b/tests/lib/Updater/VersionCheckTest.php
index ff04aa17681..89a335722d7 100644
--- a/tests/lib/Updater/VersionCheckTest.php
+++ b/tests/lib/Updater/VersionCheckTest.php
@@ -161,7 +161,7 @@ class VersionCheckTest extends \Test\TestCase {
$this->config
->expects($this->at(6))
->method('setAppValue')
- ->with('core', 'lastupdateResult', 'false');
+ ->with('core', 'lastupdateResult', '[]');
$updateXml = 'Invalid XML Response!';
$this->updater
@@ -265,4 +265,55 @@ class VersionCheckTest extends \Test\TestCase {
$this->assertSame($expectedResult, $this->updater->check());
}
+
+ public function testCheckWithMissingAttributeXmlResponse() {
+ $expectedResult = [
+ 'version' => '',
+ 'versionstring' => '',
+ 'url' => '',
+ 'web' => '',
+ 'autoupdater' => '',
+ ];
+
+ $this->config
+ ->expects($this->at(0))
+ ->method('getAppValue')
+ ->with('core', 'lastupdatedat')
+ ->will($this->returnValue(0));
+ $this->config
+ ->expects($this->at(1))
+ ->method('getSystemValue')
+ ->with('updater.server.url', 'https://updates.nextcloud.com/updater_server/')
+ ->willReturnArgument(1);
+ $this->config
+ ->expects($this->at(2))
+ ->method('setAppValue')
+ ->with('core', 'lastupdatedat', $this->isType('integer'));
+ $this->config
+ ->expects($this->at(4))
+ ->method('getAppValue')
+ ->with('core', 'installedat')
+ ->will($this->returnValue('installedat'));
+ $this->config
+ ->expects($this->at(5))
+ ->method('getAppValue')
+ ->with('core', 'lastupdatedat')
+ ->will($this->returnValue('lastupdatedat'));
+
+ // missing autoupdater element should still not fail
+ $updateXml = '<?xml version="1.0"?>
+<owncloud>
+ <version></version>
+ <versionstring></versionstring>
+ <url></url>
+ <web></web>
+</owncloud>';
+ $this->updater
+ ->expects($this->once())
+ ->method('getUrlContent')
+ ->with($this->buildUpdateUrl('https://updates.nextcloud.com/updater_server/'))
+ ->will($this->returnValue($updateXml));
+
+ $this->assertSame($expectedResult, $this->updater->check());
+ }
}
diff --git a/tests/lib/UrlGeneratorTest.php b/tests/lib/UrlGeneratorTest.php
index 69067f51e08..340c9c7082d 100644
--- a/tests/lib/UrlGeneratorTest.php
+++ b/tests/lib/UrlGeneratorTest.php
@@ -27,6 +27,8 @@ class UrlGeneratorTest extends \Test\TestCase {
private $request;
/** @var IURLGenerator */
private $urlGenerator;
+ /** @var string */
+ private $originalWebRoot;
public function setUp() {
parent::setUp();
@@ -38,6 +40,12 @@ class UrlGeneratorTest extends \Test\TestCase {
$this->cacheFactory,
$this->request
);
+ $this->originalWebRoot = \OC::$WEBROOT;
+ }
+
+ public function tearDown() {
+ // Reset webRoot
+ \OC::$WEBROOT = $this->originalWebRoot;
}
private function mockBaseUrl() {
@@ -47,7 +55,6 @@ class UrlGeneratorTest extends \Test\TestCase {
$this->request->expects($this->once())
->method('getServerHost')
->willReturn('localhost');
-
}
/**
@@ -156,4 +163,3 @@ class UrlGeneratorTest extends \Test\TestCase {
}
}
-