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/files/css/files.scss15
-rw-r--r--apps/files/css/mobile.scss5
-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.js28
-rw-r--r--apps/files/lib/Helper.php2
-rw-r--r--apps/files/templates/appnavigation.php2
-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/lib/Lib/Storage/SMB.php14
-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/public.php6
-rw-r--r--apps/theming/lib/ThemingDefaults.php6
-rw-r--r--apps/theming/tests/ThemingDefaultsTest.php2
-rw-r--r--apps/user_ldap/lib/Connection.php2
-rw-r--r--apps/user_ldap/lib/LDAP.php17
-rw-r--r--apps/user_ldap/lib/Proxy.php2
-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/js/jquery.avatar.js77
-rw-r--r--core/js/lostpassword.js1
-rw-r--r--core/js/placeholder.js5
-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--lib/composer/composer/autoload_classmap.php1
-rw-r--r--lib/composer/composer/autoload_static.php1
-rw-r--r--lib/private/App/AppManager.php2
-rw-r--r--lib/private/App/AppStore/Fetcher/Fetcher.php3
-rw-r--r--lib/private/AppFramework/Http/Request.php1
-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.php1
-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--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/lib/App/AppManagerTest.php2
-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
87 files changed, 1066 insertions, 337 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 ddb8d7ca8d2..4d2db8e2f5e 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 46707a93912..42f2e6da286 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/files/css/files.scss b/apps/files/css/files.scss
index 1be3c9216f0..00060ee7bd6 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 */
@@ -649,6 +649,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..10fa29e7a38 100644
--- a/apps/files/css/mobile.scss
+++ b/apps/files/css/mobile.scss
@@ -69,4 +69,9 @@ table td.filename .nametext .innernametext {
display: block !important;
}
+/* ensure that it is visible over #app-content */
+table.dragshadow {
+ z-index: 1000;
+}
+
}
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 d1730fa7bc7..e9534111e10 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..153307fec52 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
*/
@@ -383,7 +410,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/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/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/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/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/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/Connection.php b/apps/user_ldap/lib/Connection.php
index 1dcf9b72d7c..bde489e2710 100644
--- a/apps/user_ldap/lib/Connection.php
+++ b/apps/user_ldap/lib/Connection.php
@@ -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/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/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/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/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/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index a3b85349274..738054cd377 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -459,6 +459,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..7ffbd4c7882 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -489,6 +489,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/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/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..551fc3b9b0d 100644
--- a/lib/private/Template/JSConfigHelper.php
+++ b/lib/private/Template/JSConfigHelper.php
@@ -254,6 +254,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/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/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/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 {
}
}
-