aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/comments/css/comments.css6
-rw-r--r--apps/comments/js/commentstabview.js20
-rw-r--r--apps/comments/tests/js/commentstabviewSpec.js2
-rw-r--r--apps/dav/lib/Connector/Sabre/File.php1
-rw-r--r--apps/files/js/gotoplugin.js1
-rw-r--r--apps/files_sharing/js/share.js17
-rw-r--r--apps/files_sharing/tests/js/shareSpec.js78
-rw-r--r--apps/sharebymail/lib/Activity.php8
-rw-r--r--apps/user_ldap/lib/Notification/Notifier.php14
-rw-r--r--apps/user_ldap/lib/User/User.php2
-rw-r--r--core/Controller/ContactsMenuController.php17
-rw-r--r--core/css/inputs.scss5
-rw-r--r--core/css/share.scss19
-rw-r--r--core/js/core.json1
-rw-r--r--core/js/jquery.contactsmenu.js117
-rw-r--r--core/js/merged-template-prepend.json3
-rw-r--r--core/js/sharedialogresharerinfoview.js5
-rw-r--r--core/js/sharedialogshareelistview.js15
-rw-r--r--core/js/shareitemmodel.js14
-rw-r--r--core/js/tests/specs/jquery.contactsmenuSpec.js213
-rw-r--r--core/js/tests/specs/shareitemmodelSpec.js61
-rw-r--r--core/routes.php1
-rw-r--r--lib/base.php11
-rw-r--r--lib/composer/composer/autoload_classmap.php7
-rw-r--r--lib/composer/composer/autoload_static.php7
-rw-r--r--lib/private/App/AppStore/Bundles/Bundle.php59
-rw-r--r--lib/private/App/AppStore/Bundles/BundleFetcher.php80
-rw-r--r--lib/private/App/AppStore/Bundles/CoreBundle.php42
-rw-r--r--lib/private/App/AppStore/Bundles/EnterpriseBundle.php47
-rw-r--r--lib/private/App/AppStore/Bundles/GroupwareBundle.php44
-rw-r--r--lib/private/App/AppStore/Bundles/SocialSharingBundle.php46
-rw-r--r--lib/private/Contacts/ContactsMenu/ContactsStore.php44
-rw-r--r--lib/private/Contacts/ContactsMenu/Manager.php17
-rw-r--r--lib/private/Files/Utils/Scanner.php45
-rw-r--r--lib/private/Installer.php33
-rw-r--r--lib/private/Repair.php7
-rw-r--r--lib/private/Repair/NC12/InstallCoreBundle.php78
-rw-r--r--lib/private/Server.php8
-rw-r--r--lib/private/Setup.php20
-rw-r--r--lib/private/Updater.php7
-rw-r--r--lib/private/legacy/app.php6
-rw-r--r--settings/Controller/AppSettingsController.php63
-rw-r--r--settings/Controller/MailSettingsController.php2
-rw-r--r--settings/ajax/disableapp.php9
-rw-r--r--settings/ajax/enableapp.php17
-rw-r--r--settings/ajax/updateapp.php3
-rw-r--r--settings/css/settings.css31
-rw-r--r--settings/js/apps.js221
-rw-r--r--settings/templates/apps.php11
-rw-r--r--tests/Core/Controller/ContactsMenuControllerTest.php31
-rw-r--r--tests/Settings/Controller/AppSettingsControllerTest.php12
-rw-r--r--tests/acceptance/features/app-files.feature23
-rw-r--r--tests/acceptance/features/bootstrap/FilesAppContext.php190
-rw-r--r--tests/lib/App/AppStore/Bundles/BundleBase.php60
-rw-r--r--tests/lib/App/AppStore/Bundles/BundleFetcherTest.php78
-rw-r--r--tests/lib/App/AppStore/Bundles/CoreBundleTest.php36
-rw-r--r--tests/lib/App/AppStore/Bundles/EnterpriseBundleTest.php41
-rw-r--r--tests/lib/App/AppStore/Bundles/GroupwareBundleTest.php38
-rw-r--r--tests/lib/App/AppStore/Bundles/SocialSharingBundleTest.php40
-rw-r--r--tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php95
-rw-r--r--tests/lib/Contacts/ContactsMenu/ManagerTest.php45
-rw-r--r--tests/lib/InstallerTest.php17
-rw-r--r--tests/lib/Repair/NC12/InstallCoreBundleTest.php144
-rw-r--r--version.php2
64 files changed, 2311 insertions, 126 deletions
diff --git a/apps/comments/css/comments.css b/apps/comments/css/comments.css
index 796a550227b..2d794d52708 100644
--- a/apps/comments/css/comments.css
+++ b/apps/comments/css/comments.css
@@ -54,7 +54,6 @@
#commentsTabView .comment {
position: relative;
- z-index: 1;
margin-bottom: 30px;
}
@@ -108,6 +107,11 @@
vertical-align: middle;
}
+#commentsTabView .authorRow>div.hidden {
+ display: none !important;
+}
+
+#commentsTabView .comments li .message .avatar-name-wrapper,
#commentsTabView .comment .authorRow {
position: relative;
}
diff --git a/apps/comments/js/commentstabview.js b/apps/comments/js/commentstabview.js
index 2256bea943e..ace0862ad2e 100644
--- a/apps/comments/js/commentstabview.js
+++ b/apps/comments/js/commentstabview.js
@@ -232,6 +232,21 @@
var $this = $(this);
$this.avatar($this.attr('data-username'), 32);
});
+
+ var username = $el.find('.avatar').data('username');
+ if (username !== oc_current_user) {
+ $el.find('.authorRow .avatar, .authorRow .author').contactsMenu(
+ username, 0, $el.find('.authorRow'));
+ }
+
+ var message = $el.find('.message');
+ message.find('.avatar').each(function() {
+ var avatar = $(this);
+ var strong = $(this).next();
+ var appendTo = $(this).parent();
+
+ $.merge(avatar, strong).contactsMenu(avatar.data('user'), 0, appendTo);
+ });
},
/**
@@ -251,7 +266,10 @@
// escape possible regex characters in the name
mention = mention.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
- var displayName = avatar + ' <strong>'+ _.escape(mentions[i].mentionDisplayName)+'</strong>';
+ var displayName = ''
+ + '<span class="avatar-name-wrapper">'
+ + avatar + ' <strong>'+ _.escape(mentions[i].mentionDisplayName)+'</strong>'
+ + '</span>';
// replace every mention either at the start of the input or after a whitespace
// followed by a non-word character.
diff --git a/apps/comments/tests/js/commentstabviewSpec.js b/apps/comments/tests/js/commentstabviewSpec.js
index 0bbfaa1f295..c961548d806 100644
--- a/apps/comments/tests/js/commentstabviewSpec.js
+++ b/apps/comments/tests/js/commentstabviewSpec.js
@@ -153,7 +153,7 @@ describe('OCA.Comments.CommentsTabView tests', function() {
expect($comment.find('strong:first').text()).toEqual('Thane of Cawdor');
expect($comment.find('.avatar[data-user=banquo]').length).toEqual(1);
- expect($comment.find('strong:last-child').text()).toEqual('Lord Banquo');
+ expect($comment.find('.avatar-name-wrapper:last-child strong').text()).toEqual('Lord Banquo');
});
});
diff --git a/apps/dav/lib/Connector/Sabre/File.php b/apps/dav/lib/Connector/Sabre/File.php
index 1f878df1564..d0a01ef255b 100644
--- a/apps/dav/lib/Connector/Sabre/File.php
+++ b/apps/dav/lib/Connector/Sabre/File.php
@@ -512,6 +512,7 @@ class File extends Node implements IFile {
// TODO: in the future use ChunkHandler provided by storage
return !$storage->instanceOfStorage('OCA\Files_Sharing\External\Storage') &&
!$storage->instanceOfStorage('OC\Files\Storage\OwnCloud') &&
+ !$storage->instanceOfStorage('OC\Files\ObjectStore\ObjectStoreStorage') &&
$storage->needsPartFile();
}
diff --git a/apps/files/js/gotoplugin.js b/apps/files/js/gotoplugin.js
index 69ec64b0266..4793420ed2d 100644
--- a/apps/files/js/gotoplugin.js
+++ b/apps/files/js/gotoplugin.js
@@ -40,6 +40,7 @@
type: OCA.Files.FileActions.TYPE_DROPDOWN,
actionHandler: function (fileName, context) {
var fileModel = context.fileInfoModel;
+ OC.Apps.hideAppSidebar($('.detailsView'));
OCA.Files.App.setActiveView('files', {silent: true});
OCA.Files.App.fileList.changeDirectory(fileModel.get('path'), true, true).then(function() {
OCA.Files.App.fileList.scrollTo(fileModel.get('name'));
diff --git a/apps/files_sharing/js/share.js b/apps/files_sharing/js/share.js
index c40ca07b76b..5cd04ece446 100644
--- a/apps/files_sharing/js/share.js
+++ b/apps/files_sharing/js/share.js
@@ -189,13 +189,16 @@
// remove icon, if applicable
OC.Share.markFileAsShared($tr, false, false);
}
- var newIcon = $tr.attr('data-icon');
- // in case markFileAsShared decided to change the icon,
- // we need to modify the model
- // (FIXME: yes, this is hacky)
- if (fileInfoModel.get('icon') !== newIcon) {
- fileInfoModel.set('icon', newIcon);
- }
+
+ // FIXME: this is too convoluted. We need to get rid of the above updates
+ // and only ever update the model and let the events take care of rerendering
+ fileInfoModel.set({
+ shareTypes: shareModel.getShareTypes(),
+ // in case markFileAsShared decided to change the icon,
+ // we need to modify the model
+ // (FIXME: yes, this is hacky)
+ icon: $tr.attr('data-icon')
+ });
});
fileList.registerTabView(shareTab);
diff --git a/apps/files_sharing/tests/js/shareSpec.js b/apps/files_sharing/tests/js/shareSpec.js
index 7568b06f27c..ea2f427df75 100644
--- a/apps/files_sharing/tests/js/shareSpec.js
+++ b/apps/files_sharing/tests/js/shareSpec.js
@@ -470,4 +470,82 @@ describe('OCA.Sharing.Util tests', function() {
});
});
+ describe('ShareTabView interaction', function() {
+ var shareTabSpy;
+ var fileInfoModel;
+ var configModel;
+ var shareModel;
+
+ beforeEach(function() {
+ shareTabSpy = sinon.spy(OCA.Sharing, 'ShareTabView');
+
+ var attributes = {
+ itemType: 'file',
+ itemSource: 123,
+ possiblePermissions: 31,
+ permissions: 31
+ };
+ fileInfoModel = new OCA.Files.FileInfoModel(testFiles[0]);
+ configModel = new OC.Share.ShareConfigModel({
+ enforcePasswordForPublicLink: false,
+ isResharingAllowed: true,
+ isDefaultExpireDateEnabled: false,
+ isDefaultExpireDateEnforced: false,
+ defaultExpireDate: 7
+ });
+ shareModel = new OC.Share.ShareItemModel(attributes, {
+ configModel: configModel,
+ fileInfoModel: fileInfoModel
+ });
+
+ /* jshint camelcase: false */
+ shareModel.set({
+ reshare: {},
+ shares: [{
+ id: 100,
+ item_source: 1,
+ permissions: 31,
+ share_type: OC.Share.SHARE_TYPE_USER,
+ share_with: 'user1',
+ share_with_displayname: 'User One'
+ }, {
+ id: 102,
+ item_source: 1,
+ permissions: 31,
+ share_type: OC.Share.SHARE_TYPE_REMOTE,
+ share_with: 'foo@bar.com/baz',
+ share_with_displayname: 'foo@bar.com/baz'
+
+ }]
+ }, {parse: true});
+
+ fileList.destroy();
+ fileList = new OCA.Files.FileList(
+ $('#listContainer'), {
+ id: 'files',
+ fileActions: new OCA.Files.FileActions()
+ }
+ );
+ OCA.Sharing.Util.attach(fileList);
+ fileList.setFiles(testFiles);
+ });
+ afterEach(function() {
+ shareTabSpy.restore();
+ });
+
+ it('updates fileInfoModel when shares changed', function() {
+ var changeHandler = sinon.stub();
+ fileInfoModel.on('change', changeHandler);
+
+ shareTabSpy.getCall(0).thisValue.trigger('sharesChanged', shareModel);
+
+ expect(changeHandler.calledOnce).toEqual(true);
+ expect(changeHandler.getCall(0).args[0].changed).toEqual({
+ shareTypes: [
+ OC.Share.SHARE_TYPE_USER,
+ OC.Share.SHARE_TYPE_REMOTE
+ ]
+ });
+ });
+ });
});
diff --git a/apps/sharebymail/lib/Activity.php b/apps/sharebymail/lib/Activity.php
index 6dc462bf492..73751cb241e 100644
--- a/apps/sharebymail/lib/Activity.php
+++ b/apps/sharebymail/lib/Activity.php
@@ -173,17 +173,17 @@ class Activity implements IProvider {
->setRichSubject($this->l->t('{actor} shared {file} with {email} by mail'), $parsedParameters)
->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/share.svg')));
} else if ($event->getSubject() === self::SUBJECT_SHARED_EMAIL_PASSWORD_SEND) {
- $event->setParsedSubject($this->l->t('Password to access %1$s was send to %2s', [
+ $event->setParsedSubject($this->l->t('Password to access %1$s was sent to %2s', [
$parsedParameters['file']['path'],
$parsedParameters['email']['name']
]))
- ->setRichSubject($this->l->t('Password to access {file} was send to {email}'), $parsedParameters)
+ ->setRichSubject($this->l->t('Password to access {file} was sent to {email}'), $parsedParameters)
->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/share.svg')));
} else if ($event->getSubject() === self::SUBJECT_SHARED_EMAIL_PASSWORD_SEND_SELF) {
$event->setParsedSubject(
- $this->l->t('Password to access %1$s was send to you',
+ $this->l->t('Password to access %1$s was sent to you',
[$parsedParameters['file']['path']]))
- ->setRichSubject($this->l->t('Password to access {file} was send to you'), $parsedParameters)
+ ->setRichSubject($this->l->t('Password to access {file} was sent to you'), $parsedParameters)
->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/share.svg')));
} else {
diff --git a/apps/user_ldap/lib/Notification/Notifier.php b/apps/user_ldap/lib/Notification/Notifier.php
index a6053cfcb19..0099d764f03 100644
--- a/apps/user_ldap/lib/Notification/Notifier.php
+++ b/apps/user_ldap/lib/Notification/Notifier.php
@@ -60,7 +60,19 @@ class Notifier implements INotifier {
switch ($notification->getSubject()) {
// Deal with known subjects
case 'pwd_exp_warn_days':
- $notification->setParsedSubject($l->t('Your password will expire within %s day(s).', $notification->getSubjectParameters()));
+ $params = $notification->getSubjectParameters();
+ $days = (int) $params[0];
+ if ($days === 2) {
+ $notification->setParsedSubject($l->t('Your password will expire tomorrow.', $days));
+ } else if ($days === 1) {
+ $notification->setParsedSubject($l->t('Your password will expire today.', $days));
+ } else {
+ $notification->setParsedSubject($l->n(
+ 'Your password will expire within %n day.',
+ 'Your password will expire within %n days.',
+ $days
+ ));
+ }
return $notification;
default:
diff --git a/apps/user_ldap/lib/User/User.php b/apps/user_ldap/lib/User/User.php
index a9e7eb6cc0c..5017f35ed0a 100644
--- a/apps/user_ldap/lib/User/User.php
+++ b/apps/user_ldap/lib/User/User.php
@@ -683,7 +683,7 @@ class User {
->setUser($uid)
->setDateTime($currentDateTime)
->setObject('pwd_exp_warn', $uid)
- ->setSubject('pwd_exp_warn_days', [strval(ceil($secondsToExpiry / 60 / 60 / 24))])
+ ->setSubject('pwd_exp_warn_days', [(int) ceil($secondsToExpiry / 60 / 60 / 24)])
;
$this->notificationManager->notify($notification);
}
diff --git a/core/Controller/ContactsMenuController.php b/core/Controller/ContactsMenuController.php
index b0e0e0c6a77..bbb990f1a4f 100644
--- a/core/Controller/ContactsMenuController.php
+++ b/core/Controller/ContactsMenuController.php
@@ -26,6 +26,7 @@ namespace OC\Core\Controller;
use OC\Contacts\ContactsMenu\Manager;
use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IRequest;
use OCP\IUserSession;
@@ -59,4 +60,20 @@ class ContactsMenuController extends Controller {
return $this->manager->getEntries($this->userSession->getUser(), $filter);
}
+ /**
+ * @NoAdminRequired
+ *
+ * @param integer $shareType
+ * @param string $shareWith
+ * @return JSONResponse
+ */
+ public function findOne($shareType, $shareWith) {
+ $contact = $this->manager->findOne($this->userSession->getUser(), $shareType, $shareWith);
+
+ if ($contact) {
+ return $contact;
+ } else {
+ return new JSONResponse([], Http::STATUS_NOT_FOUND);
+ }
+ }
}
diff --git a/core/css/inputs.scss b/core/css/inputs.scss
index e76e5bea150..13a164e13f2 100644
--- a/core/css/inputs.scss
+++ b/core/css/inputs.scss
@@ -206,7 +206,6 @@ input {
height: 1px;
overflow: hidden;
+ label {
- padding: 6px 0;
user-select: none;
}
&:disabled + label,
@@ -220,8 +219,8 @@ input {
width: 12px;
vertical-align: middle;
border-radius: 50%;
- margin: 6px;
- margin-top: -2px;
+ margin: 3px;
+ margin-top: 1px;
border: 1px solid nc-lighten($color-main-text, 53%);
}
&:not(:disabled):not(:checked) + label:hover:before,
diff --git a/core/css/share.scss b/core/css/share.scss
index 552e20c80cc..2e1c99b6f41 100644
--- a/core/css/share.scss
+++ b/core/css/share.scss
@@ -87,6 +87,7 @@
list-style-type: none;
padding: 8px;
> li {
+ position: relative;
padding-top: 10px;
padding-bottom: 10px;
font-weight: bold;
@@ -103,6 +104,7 @@
padding: 3px 6px;
}
}
+
.shareOption {
white-space: nowrap;
display: inline-block;
@@ -159,6 +161,10 @@ a {
padding: 6px 4px;
}
+.resharerInfoView.subView {
+ position: relative;
+}
+
#defaultExpireMessage, .reshare {
/* fix shared by text going out of box */
white-space: normal;
@@ -185,6 +191,19 @@ a {
color: rgba($color-main-text, .4);
}
+.contactsmenu-popover {
+ left: -8px;
+ right: auto;
+ padding: 3px 6px;
+ li.hidden {
+ display: none !important;
+ }
+ &:after {
+ left: 8px;
+ right: auto;
+ }
+}
+
.popovermenu .datepicker {
margin-left: 35px;
}
diff --git a/core/js/core.json b/core/js/core.json
index aadd66a0558..15e406bf2d2 100644
--- a/core/js/core.json
+++ b/core/js/core.json
@@ -20,6 +20,7 @@
"libraries": [
"jquery-showpassword.js",
"jquery.avatar.js",
+ "jquery.contactsmenu.js",
"placeholder.js"
],
"modules": [
diff --git a/core/js/jquery.contactsmenu.js b/core/js/jquery.contactsmenu.js
new file mode 100644
index 00000000000..1ea9f732f79
--- /dev/null
+++ b/core/js/jquery.contactsmenu.js
@@ -0,0 +1,117 @@
+/**
+ * Copyright (c) 2017 Georg Ehrke <oc.list@georgehrke.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+(function ($) {
+ var ENTRY = ''
+ + '<li>'
+ + ' <a href="{{hyperlink}}">'
+ + ' {{#if icon}}<img src="{{icon}}">{{/if}}'
+ + ' <span>{{title}}</span>'
+ + ' </a>'
+ + '</li>';
+
+ var LIST = ''
+ + '<div class="menu popovermenu bubble hidden contactsmenu-popover">'
+ + ' <ul>'
+ + ' <li>'
+ + ' <a>'
+ + ' <span class="icon-loading-small"></span>'
+ + ' </a>'
+ + ' </li>'
+ + ' </ul>'
+ + '</div>';
+
+ $.fn.contactsMenu = function(shareWith, shareType, appendTo) {
+ // 0 - user, 4 - email, 6 - remote
+ var allowedTypes = [0, 4, 6];
+ if (allowedTypes.indexOf(shareType) === -1) {
+ return;
+ }
+
+ var $div = this;
+ appendTo.append(LIST);
+ var $list = appendTo.find('div.contactsmenu-popover');
+
+ $div.click(function() {
+ if (!$list.hasClass('hidden')) {
+ $list.addClass('hidden');
+ $list.hide();
+ return;
+ }
+
+ $list.removeClass('hidden');
+ $list.show();
+
+ if ($list.hasClass('loaded')) {
+ return;
+ }
+
+ $list.addClass('loaded');
+ $.ajax(OC.generateUrl('/contactsmenu/findOne'), {
+ method: 'POST',
+ data: {
+ shareType: shareType,
+ shareWith: shareWith
+ }
+ }).then(function(data) {
+ $list.find('ul').find('li').addClass('hidden');
+
+ var actions;
+ if (!data.topAction) {
+ actions = [{
+ hyperlink: '#',
+ title: t('core', 'No action available')
+ }];
+ } else {
+ actions = [data.topAction].concat(data.actions);
+ }
+
+ actions.forEach(function(action) {
+ var template = Handlebars.compile(ENTRY);
+ $list.find('ul').append(template(action));
+ });
+
+ if (actions.length === 0) {
+
+ }
+ }, function(jqXHR) {
+ $list.find('ul').find('li').addClass('hidden');
+
+ var title;
+ if (jqXHR.status === 404) {
+ title = t('core', 'No action available');
+ } else {
+ title = t('core', 'Error fetching contact actions');
+ }
+
+ var template = Handlebars.compile(ENTRY);
+ $list.find('ul').append(template({
+ hyperlink: '#',
+ title: title
+ }));
+ });
+ });
+
+ $(document).click(function(event) {
+ var clickedList = ($list.has(event.target).length > 0);
+ var clickedTarget = ($div.has(event.target).length > 0);
+
+ $div.each(function() {
+ if ($(this).is(event.target)) {
+ clickedTarget = true;
+ }
+ });
+
+ if (clickedList || clickedTarget) {
+ return;
+ }
+
+ $list.addClass('hidden');
+ $list.hide();
+ });
+ };
+}(jQuery));
diff --git a/core/js/merged-template-prepend.json b/core/js/merged-template-prepend.json
index 12b7ca8faa3..0dd6bed5329 100644
--- a/core/js/merged-template-prepend.json
+++ b/core/js/merged-template-prepend.json
@@ -13,5 +13,6 @@
"mimetypelist.js",
"oc-backbone.js",
"placeholder.js",
- "jquery.avatar.js"
+ "jquery.avatar.js",
+ "jquery.contactsmenu.js"
]
diff --git a/core/js/sharedialogresharerinfoview.js b/core/js/sharedialogresharerinfoview.js
index a82b495bdcc..201484c52a8 100644
--- a/core/js/sharedialogresharerinfoview.js
+++ b/core/js/sharedialogresharerinfoview.js
@@ -100,6 +100,11 @@
$this.avatar($this.data('username'), 32);
});
+ this.$el.find('.reshare').contactsMenu(
+ this.model.getReshareOwner(),
+ OC.Share.SHARE_TYPE_USER,
+ this.$el);
+
return this;
},
diff --git a/core/js/sharedialogshareelistview.js b/core/js/sharedialogshareelistview.js
index 3a481e53dde..f513eb75848 100644
--- a/core/js/sharedialogshareelistview.js
+++ b/core/js/sharedialogshareelistview.js
@@ -26,7 +26,7 @@
'{{#each sharees}}' +
'<li data-share-id="{{shareId}}" data-share-type="{{shareType}}" data-share-with="{{shareWith}}">' +
'<div class="avatar {{#if modSeed}}imageplaceholderseed{{/if}}" data-username="{{shareWith}}" data-displayname="{{shareWithDisplayName}}" {{#if modSeed}}data-seed="{{shareWith}} {{shareType}}"{{/if}}></div>' +
- '<span class="has-tooltip username" title="{{shareWithTitle}}">{{shareWithDisplayName}}</span>' +
+ '<span class="username" title="{{shareWithTitle}}">{{shareWithDisplayName}}</span>' +
'<span class="sharingOptionsGroup">' +
'{{#if editPermissionPossible}}' +
'<span class="shareOption">' +
@@ -361,6 +361,15 @@
this.$('.has-tooltip').tooltip({
placement: 'bottom'
});
+
+ this.$('ul.shareWithList > li').each(function() {
+ var $this = $(this);
+
+ var shareWith = $this.data('share-with');
+ var shareType = $this.data('share-type');
+
+ $this.find('div.avatar, span.username').contactsMenu(shareWith, shareType, $this);
+ })
} else {
var permissionChangeShareId = parseInt(this._renderPermissionChange, 10);
var shareWithIndex = this.model.findShareWithIndex(permissionChangeShareId);
@@ -399,7 +408,7 @@
var shareId = parseInt(this._menuOpen, 10);
if(!_.isNaN(shareId)) {
var liSelector = 'li[data-share-id=' + shareId + ']';
- OC.showMenu(null, this.$(liSelector + ' .popovermenu'));
+ OC.showMenu(null, this.$(liSelector + '.sharingOptionsGroup .popovermenu'));
}
}
@@ -476,7 +485,7 @@
event.stopPropagation();
var $element = $(event.target);
var $li = $element.closest('li[data-share-id]');
- var $menu = $li.find('.popovermenu');
+ var $menu = $li.find('.sharingOptionsGroup .popovermenu');
OC.showMenu(null, $menu);
this._menuOpen = $li.data('share-id');
diff --git a/core/js/shareitemmodel.js b/core/js/shareitemmodel.js
index 41f9eb5e0aa..4118a8a0188 100644
--- a/core/js/shareitemmodel.js
+++ b/core/js/shareitemmodel.js
@@ -841,6 +841,20 @@
}
}
return time;
+ },
+
+ /**
+ * Returns a list of share types from the existing shares.
+ *
+ * @return {Array.<int>} array of share types
+ */
+ getShareTypes: function() {
+ var result;
+ result = _.pluck(this.getSharesWithCurrentItem(), 'share_type');
+ if (this.hasLinkShare()) {
+ result.push(OC.Share.SHARE_TYPE_LINK);
+ }
+ return _.uniq(result);
}
});
diff --git a/core/js/tests/specs/jquery.contactsmenuSpec.js b/core/js/tests/specs/jquery.contactsmenuSpec.js
new file mode 100644
index 00000000000..7287648f5a2
--- /dev/null
+++ b/core/js/tests/specs/jquery.contactsmenuSpec.js
@@ -0,0 +1,213 @@
+/**
+ * Copyright (c) 2017 Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * This file is licensed under the Affero General Public License version 3
+ * or later.
+ *
+ * See the COPYING-README file.
+ *
+ */
+
+describe('jquery.contactsMenu tests', function() {
+
+ var $selector1, $selector2, $appendTo;
+
+ beforeEach(function() {
+ $('#testArea').append($('<div id="selector1">'));
+ $('#testArea').append($('<div id="selector2">'));
+ $('#testArea').append($('<div id="appendTo">'));
+ $selector1 = $('#selector1');
+ $selector2 = $('#selector2');
+ $appendTo = $('#appendTo');
+ });
+
+ afterEach(function() {
+ $selector1.remove();
+ $selector2.remove();
+ $appendTo.remove();
+ });
+
+ describe('shareType', function() {
+ it('stops if type not supported', function() {
+ $selector1.contactsMenu('user', 1, $appendTo);
+ expect($appendTo.children().length).toEqual(0);
+
+ $selector1.contactsMenu('user', 2, $appendTo);
+ expect($appendTo.children().length).toEqual(0);
+
+ $selector1.contactsMenu('user', 3, $appendTo);
+ expect($appendTo.children().length).toEqual(0);
+
+ $selector1.contactsMenu('user', 5, $appendTo);
+ expect($appendTo.children().length).toEqual(0);
+ });
+
+ it('append list if shareType supported', function() {
+ $selector1.contactsMenu('user', 0, $appendTo);
+ expect($appendTo.children().length).toEqual(1);
+ expect($appendTo.html()).toEqual('<div class="menu popovermenu bubble hidden contactsmenu-popover"> <ul> <li> <a> <span class="icon-loading-small"></span> </a> </li> </ul></div>');
+ });
+ });
+
+ describe('open on click', function() {
+ it('with one selector', function() {
+ $selector1.contactsMenu('user', 0, $appendTo);
+ expect($appendTo.children().length).toEqual(1);
+ expect($appendTo.find('div.contactsmenu-popover').hasClass('hidden')).toEqual(true);
+ $selector1.click();
+ expect($appendTo.find('div.contactsmenu-popover').hasClass('hidden')).toEqual(false);
+ });
+
+ it('with multiple selectors - 1', function() {
+ $('#selector1, #selector2').contactsMenu('user', 0, $appendTo);
+
+ expect($appendTo.children().length).toEqual(1);
+ expect($appendTo.find('div.contactsmenu-popover').hasClass('hidden')).toEqual(true);
+ $selector1.click();
+ expect($appendTo.find('div.contactsmenu-popover').hasClass('hidden')).toEqual(false);
+ });
+
+ it('with multiple selectors - 2', function() {
+ $('#selector1, #selector2').contactsMenu('user', 0, $appendTo);
+
+ expect($appendTo.children().length).toEqual(1);
+ expect($appendTo.find('div.contactsmenu-popover').hasClass('hidden')).toEqual(true);
+ $selector2.click();
+ expect($appendTo.find('div.contactsmenu-popover').hasClass('hidden')).toEqual(false);
+ });
+
+ it ('should close when clicking the selector again - 1', function() {
+ $('#selector1, #selector2').contactsMenu('user', 0, $appendTo);
+
+ expect($appendTo.children().length).toEqual(1);
+ expect($appendTo.find('div').hasClass('hidden')).toEqual(true);
+ $selector1.click();
+ expect($appendTo.find('div').hasClass('hidden')).toEqual(false);
+ $selector1.click();
+ expect($appendTo.find('div').hasClass('hidden')).toEqual(true);
+ });
+
+ it ('should close when clicking the selector again - 1', function() {
+ $('#selector1, #selector2').contactsMenu('user', 0, $appendTo);
+
+ expect($appendTo.children().length).toEqual(1);
+ expect($appendTo.find('div').hasClass('hidden')).toEqual(true);
+ $selector1.click();
+ expect($appendTo.find('div').hasClass('hidden')).toEqual(false);
+ $selector2.click();
+ expect($appendTo.find('div').hasClass('hidden')).toEqual(true);
+ });
+ });
+
+ describe('send requests to the server and render', function() {
+ it('load a topaction only', function() {
+ $('#selector1, #selector2').contactsMenu('user', 0, $appendTo);
+ $selector1.click();
+
+ fakeServer.requests[0].respond(
+ 200,
+ { 'Content-Type': 'application/json; charset=utf-8' },
+ JSON.stringify({
+ "id": null,
+ "fullName": "Name 123",
+ "topAction": {
+ "title": "bar@baz.wtf",
+ "icon": "foo.svg",
+ "hyperlink": "mailto:bar%40baz.wtf"},
+ "actions": []
+ })
+ );
+ expect(fakeServer.requests[0].method).toEqual('POST');
+ expect(fakeServer.requests[0].url).toEqual('http://localhost/index.php/contactsmenu/findOne');
+
+ expect($appendTo.html()).toEqual('<div class="menu popovermenu bubble contactsmenu-popover loaded" style="display: block;"> <ul> <li class="hidden"> <a> <span class="icon-loading-small"></span> </a> </li> <li> <a href="mailto:bar%40baz.wtf"> <img src="foo.svg"> <span>bar@baz.wtf</span> </a></li></ul></div>');
+ });
+
+ it('load topaction and more actions', function() {
+ $('#selector1, #selector2').contactsMenu('user', 0, $appendTo);
+ $selector1.click();
+
+ fakeServer.requests[0].respond(
+ 200,
+ { 'Content-Type': 'application/json; charset=utf-8' },
+ JSON.stringify({
+ "id": null,
+ "fullName": "Name 123",
+ "topAction": {
+ "title": "bar@baz.wtf",
+ "icon": "foo.svg",
+ "hyperlink": "mailto:bar%40baz.wtf"},
+ "actions": [{
+ "title": "Details",
+ "icon": "details.svg",
+ "hyperlink": "http:\/\/localhost\/index.php\/apps\/contacts"
+ }]
+ })
+ );
+ expect(fakeServer.requests[0].method).toEqual('POST');
+ expect(fakeServer.requests[0].url).toEqual('http://localhost/index.php/contactsmenu/findOne');
+
+ expect($appendTo.html()).toEqual('<div class="menu popovermenu bubble contactsmenu-popover loaded" style="display: block;"> <ul> <li class="hidden"> <a> <span class="icon-loading-small"></span> </a> </li> <li> <a href="mailto:bar%40baz.wtf"> <img src="foo.svg"> <span>bar@baz.wtf</span> </a></li><li> <a href="http://localhost/index.php/apps/contacts"> <img src="details.svg"> <span>Details</span> </a></li></ul></div>');
+ });
+
+ it('load no actions', function() {
+ $('#selector1, #selector2').contactsMenu('user', 0, $appendTo);
+ $selector1.click();
+
+ fakeServer.requests[0].respond(
+ 200,
+ { 'Content-Type': 'application/json; charset=utf-8' },
+ JSON.stringify({
+ "id": null,
+ "fullName": "Name 123",
+ "topAction": null,
+ "actions": []
+ })
+ );
+ expect(fakeServer.requests[0].method).toEqual('POST');
+ expect(fakeServer.requests[0].url).toEqual('http://localhost/index.php/contactsmenu/findOne');
+
+ expect($appendTo.html()).toEqual('<div class="menu popovermenu bubble contactsmenu-popover loaded" style="display: block;"> <ul> <li class="hidden"> <a> <span class="icon-loading-small"></span> </a> </li> <li> <a href="#"> <span>No action available</span> </a></li></ul></div>');
+ });
+
+ it('should throw an error', function() {
+ $('#selector1, #selector2').contactsMenu('user', 0, $appendTo);
+ $selector1.click();
+
+ fakeServer.requests[0].respond(
+ 400,
+ { 'Content-Type': 'application/json; charset=utf-8' },
+ JSON.stringify([])
+ );
+ expect(fakeServer.requests[0].method).toEqual('POST');
+ expect(fakeServer.requests[0].url).toEqual('http://localhost/index.php/contactsmenu/findOne');
+
+ expect($appendTo.html()).toEqual('<div class="menu popovermenu bubble contactsmenu-popover loaded" style="display: block;"> <ul> <li class="hidden"> <a> <span class="icon-loading-small"></span> </a> </li> <li> <a href="#"> <span>Error fetching contact actions</span> </a></li></ul></div>');
+ });
+
+ it('should handle 404', function() {
+ $('#selector1, #selector2').contactsMenu('user', 0, $appendTo);
+ $selector1.click();
+
+ fakeServer.requests[0].respond(
+ 404,
+ { 'Content-Type': 'application/json; charset=utf-8' },
+ JSON.stringify([])
+ );
+ expect(fakeServer.requests[0].method).toEqual('POST');
+ expect(fakeServer.requests[0].url).toEqual('http://localhost/index.php/contactsmenu/findOne');
+
+ expect($appendTo.html()).toEqual('<div class="menu popovermenu bubble contactsmenu-popover loaded" style="display: block;"> <ul> <li class="hidden"> <a> <span class="icon-loading-small"></span> </a> </li> <li> <a href="#"> <span>No action available</span> </a></li></ul></div>');
+ });
+ });
+
+ it('click anywhere else to close the menu', function() {
+ $('#selector1, #selector2').contactsMenu('user', 0, $appendTo);
+
+ expect($appendTo.find('div').hasClass('hidden')).toEqual(true);
+ $selector1.click();
+ expect($appendTo.find('div').hasClass('hidden')).toEqual(false);
+ $(document).click();
+ expect($appendTo.find('div').hasClass('hidden')).toEqual(true);
+ });
+});
diff --git a/core/js/tests/specs/shareitemmodelSpec.js b/core/js/tests/specs/shareitemmodelSpec.js
index 771a9263709..3b17051508e 100644
--- a/core/js/tests/specs/shareitemmodelSpec.js
+++ b/core/js/tests/specs/shareitemmodelSpec.js
@@ -924,5 +924,66 @@ describe('OC.Share.ShareItemModel', function() {
expect(errorStub.lastCall.args[1]).toEqual('Some error message');
});
});
+
+ describe('getShareTypes', function() {
+
+ var dataProvider = [
+ [
+ ],
+ [
+ OC.Share.SHARE_TYPE_USER,
+ OC.Share.SHARE_TYPE_USER,
+ ],
+ [
+ OC.Share.SHARE_TYPE_USER,
+ OC.Share.SHARE_TYPE_GROUP,
+ OC.Share.SHARE_TYPE_LINK,
+ OC.Share.SHARE_TYPE_REMOTE
+ ],
+ [
+ OC.Share.SHARE_TYPE_USER,
+ OC.Share.SHARE_TYPE_GROUP,
+ OC.Share.SHARE_TYPE_GROUP,
+ OC.Share.SHARE_TYPE_LINK,
+ OC.Share.SHARE_TYPE_LINK,
+ OC.Share.SHARE_TYPE_REMOTE,
+ OC.Share.SHARE_TYPE_REMOTE,
+ OC.Share.SHARE_TYPE_REMOTE
+ ],
+ [
+ OC.Share.SHARE_TYPE_LINK,
+ OC.Share.SHARE_TYPE_LINK,
+ OC.Share.SHARE_TYPE_USER
+ ]
+ ];
+
+ _.each(dataProvider, function testCase(shareTypes, i) {
+ it('returns set of share types for case ' + i, function() {
+ /* jshint camelcase: false */
+ fetchReshareDeferred.resolve(makeOcsResponse([]));
+
+ var id = 100;
+ var shares = _.map(shareTypes, function(shareType) {
+ return {
+ id: id++,
+ item_source: 123,
+ permissions: 31,
+ share_type: shareType,
+ uid_owner: 'root'
+ };
+ });
+
+ var expectedResult = _.uniq(shareTypes).sort();
+
+ fetchSharesDeferred.resolve(makeOcsResponse(shares));
+
+ OC.currentUser = 'root';
+
+ model.fetch();
+
+ expect(model.getShareTypes().sort()).toEqual(expectedResult);
+ });
+ });
+ });
});
diff --git a/core/routes.php b/core/routes.php
index 37db2642c1b..c167dad2f9f 100644
--- a/core/routes.php
+++ b/core/routes.php
@@ -61,6 +61,7 @@ $application->registerRoutes($this, [
['name' => 'Css#getCss', 'url' => '/css/{appName}/{fileName}', 'verb' => 'GET'],
['name' => 'Js#getJs', 'url' => '/js/{appName}/{fileName}', 'verb' => 'GET'],
['name' => 'contactsMenu#index', 'url' => '/contactsmenu/contacts', 'verb' => 'POST'],
+ ['name' => 'contactsMenu#findOne', 'url' => '/contactsmenu/findOne', 'verb' => 'POST'],
],
'ocs' => [
['root' => '/cloud', 'name' => 'OCS#getCapabilities', 'url' => '/capabilities', 'verb' => 'GET'],
diff --git a/lib/base.php b/lib/base.php
index 1db6b84c5fb..3ca4775dbe2 100644
--- a/lib/base.php
+++ b/lib/base.php
@@ -935,14 +935,15 @@ class OC {
// emergency app disabling
if ($requestPath === '/disableapp'
&& $request->getMethod() === 'POST'
- && ((string)$request->getParam('appid')) !== ''
+ && ((array)$request->getParam('appid')) !== ''
) {
\OCP\JSON::callCheck();
\OCP\JSON::checkAdminUser();
- $appId = (string)$request->getParam('appid');
- $appId = \OC_App::cleanAppId($appId);
-
- \OC_App::disable($appId);
+ $appIds = (array)$request->getParam('appid');
+ foreach($appIds as $appId) {
+ $appId = \OC_App::cleanAppId($appId);
+ \OC_App::disable($appId);
+ }
\OC_JSON::success();
exit();
}
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index 9dea4d10fb2..23aff9df870 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -318,6 +318,12 @@ return array(
'OC\\AppFramework\\Utility\\TimeFactory' => $baseDir . '/lib/private/AppFramework/Utility/TimeFactory.php',
'OC\\AppHelper' => $baseDir . '/lib/private/AppHelper.php',
'OC\\App\\AppManager' => $baseDir . '/lib/private/App/AppManager.php',
+ 'OC\\App\\AppStore\\Bundles\\Bundle' => $baseDir . '/lib/private/App/AppStore/Bundles/Bundle.php',
+ 'OC\\App\\AppStore\\Bundles\\BundleFetcher' => $baseDir . '/lib/private/App/AppStore/Bundles/BundleFetcher.php',
+ 'OC\\App\\AppStore\\Bundles\\CoreBundle' => $baseDir . '/lib/private/App/AppStore/Bundles/CoreBundle.php',
+ 'OC\\App\\AppStore\\Bundles\\EnterpriseBundle' => $baseDir . '/lib/private/App/AppStore/Bundles/EnterpriseBundle.php',
+ 'OC\\App\\AppStore\\Bundles\\GroupwareBundle' => $baseDir . '/lib/private/App/AppStore/Bundles/GroupwareBundle.php',
+ 'OC\\App\\AppStore\\Bundles\\SocialSharingBundle' => $baseDir . '/lib/private/App/AppStore/Bundles/SocialSharingBundle.php',
'OC\\App\\AppStore\\Fetcher\\AppFetcher' => $baseDir . '/lib/private/App/AppStore/Fetcher/AppFetcher.php',
'OC\\App\\AppStore\\Fetcher\\CategoryFetcher' => $baseDir . '/lib/private/App/AppStore/Fetcher/CategoryFetcher.php',
'OC\\App\\AppStore\\Fetcher\\Fetcher' => $baseDir . '/lib/private/App/AppStore/Fetcher/Fetcher.php',
@@ -727,6 +733,7 @@ return array(
'OC\\Repair\\NC11\\FixMountStorages' => $baseDir . '/lib/private/Repair/NC11/FixMountStorages.php',
'OC\\Repair\\NC11\\MoveAvatars' => $baseDir . '/lib/private/Repair/NC11/MoveAvatars.php',
'OC\\Repair\\NC11\\MoveAvatarsBackgroundJob' => $baseDir . '/lib/private/Repair/NC11/MoveAvatarsBackgroundJob.php',
+ 'OC\\Repair\\NC12\\InstallCoreBundle' => $baseDir . '/lib/private/Repair/NC12/InstallCoreBundle.php',
'OC\\Repair\\NC12\\UpdateLanguageCodes' => $baseDir . '/lib/private/Repair/NC12/UpdateLanguageCodes.php',
'OC\\Repair\\OldGroupMembershipShares' => $baseDir . '/lib/private/Repair/OldGroupMembershipShares.php',
'OC\\Repair\\RemoveRootShares' => $baseDir . '/lib/private/Repair/RemoveRootShares.php',
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index 11d949de34a..709d59ff3d0 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -348,6 +348,12 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\AppFramework\\Utility\\TimeFactory' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Utility/TimeFactory.php',
'OC\\AppHelper' => __DIR__ . '/../../..' . '/lib/private/AppHelper.php',
'OC\\App\\AppManager' => __DIR__ . '/../../..' . '/lib/private/App/AppManager.php',
+ 'OC\\App\\AppStore\\Bundles\\Bundle' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Bundles/Bundle.php',
+ 'OC\\App\\AppStore\\Bundles\\BundleFetcher' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Bundles/BundleFetcher.php',
+ 'OC\\App\\AppStore\\Bundles\\CoreBundle' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Bundles/CoreBundle.php',
+ 'OC\\App\\AppStore\\Bundles\\EnterpriseBundle' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Bundles/EnterpriseBundle.php',
+ 'OC\\App\\AppStore\\Bundles\\GroupwareBundle' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Bundles/GroupwareBundle.php',
+ 'OC\\App\\AppStore\\Bundles\\SocialSharingBundle' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Bundles/SocialSharingBundle.php',
'OC\\App\\AppStore\\Fetcher\\AppFetcher' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Fetcher/AppFetcher.php',
'OC\\App\\AppStore\\Fetcher\\CategoryFetcher' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Fetcher/CategoryFetcher.php',
'OC\\App\\AppStore\\Fetcher\\Fetcher' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Fetcher/Fetcher.php',
@@ -757,6 +763,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Repair\\NC11\\FixMountStorages' => __DIR__ . '/../../..' . '/lib/private/Repair/NC11/FixMountStorages.php',
'OC\\Repair\\NC11\\MoveAvatars' => __DIR__ . '/../../..' . '/lib/private/Repair/NC11/MoveAvatars.php',
'OC\\Repair\\NC11\\MoveAvatarsBackgroundJob' => __DIR__ . '/../../..' . '/lib/private/Repair/NC11/MoveAvatarsBackgroundJob.php',
+ 'OC\\Repair\\NC12\\InstallCoreBundle' => __DIR__ . '/../../..' . '/lib/private/Repair/NC12/InstallCoreBundle.php',
'OC\\Repair\\NC12\\UpdateLanguageCodes' => __DIR__ . '/../../..' . '/lib/private/Repair/NC12/UpdateLanguageCodes.php',
'OC\\Repair\\OldGroupMembershipShares' => __DIR__ . '/../../..' . '/lib/private/Repair/OldGroupMembershipShares.php',
'OC\\Repair\\RemoveRootShares' => __DIR__ . '/../../..' . '/lib/private/Repair/RemoveRootShares.php',
diff --git a/lib/private/App/AppStore/Bundles/Bundle.php b/lib/private/App/AppStore/Bundles/Bundle.php
new file mode 100644
index 00000000000..47efc4e0cce
--- /dev/null
+++ b/lib/private/App/AppStore/Bundles/Bundle.php
@@ -0,0 +1,59 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\App\AppStore\Bundles;
+
+use OCP\IL10N;
+
+abstract class Bundle {
+ /** @var IL10N */
+ protected $l10n;
+
+ /**
+ * @param IL10N $l10n
+ */
+ public function __construct(IL10N $l10n) {
+ $this->l10n = $l10n;
+ }
+
+ /**
+ * Get the identifier of the bundle
+ *
+ * @return string
+ */
+ public final function getIdentifier() {
+ return substr(strrchr(get_class($this), '\\'), 1);
+ }
+
+ /**
+ * Get the name of the bundle
+ *
+ * @return string
+ */
+ public abstract function getName();
+
+ /**
+ * Get the list of app identifiers in the bundle
+ *
+ * @return array
+ */
+ public abstract function getAppIdentifiers();
+}
diff --git a/lib/private/App/AppStore/Bundles/BundleFetcher.php b/lib/private/App/AppStore/Bundles/BundleFetcher.php
new file mode 100644
index 00000000000..01cd4d6a518
--- /dev/null
+++ b/lib/private/App/AppStore/Bundles/BundleFetcher.php
@@ -0,0 +1,80 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\App\AppStore\Bundles;
+
+use OCP\IL10N;
+
+class BundleFetcher {
+ /** @var IL10N */
+ private $l10n;
+
+ /**
+ * @param IL10N $l10n
+ */
+ public function __construct(IL10N $l10n) {
+ $this->l10n = $l10n;
+ }
+
+ /**
+ * @return Bundle[]
+ */
+ public function getBundles() {
+ return [
+ new EnterpriseBundle($this->l10n),
+ new GroupwareBundle($this->l10n),
+ new SocialSharingBundle($this->l10n),
+ ];
+ }
+
+ /**
+ * Bundles that should be installed by default after installation
+ *
+ * @return Bundle[]
+ */
+ public function getDefaultInstallationBundle() {
+ return [
+ new CoreBundle($this->l10n),
+ ];
+ }
+
+ /**
+ * Get the bundle with the specified identifier
+ *
+ * @param string $identifier
+ * @return Bundle
+ * @throws \BadMethodCallException If the bundle does not exist
+ */
+ public function getBundleByIdentifier($identifier) {
+ /** @var Bundle[] $bundles */
+ $bundles = array_merge(
+ $this->getBundles(),
+ $this->getDefaultInstallationBundle()
+ );
+ foreach($bundles as $bundle) {
+ if($bundle->getIdentifier() === $identifier) {
+ return $bundle;
+ }
+ }
+
+ throw new \BadMethodCallException('Bundle with specified identifier does not exist');
+ }
+}
diff --git a/lib/private/App/AppStore/Bundles/CoreBundle.php b/lib/private/App/AppStore/Bundles/CoreBundle.php
new file mode 100644
index 00000000000..a87292b9ec9
--- /dev/null
+++ b/lib/private/App/AppStore/Bundles/CoreBundle.php
@@ -0,0 +1,42 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\App\AppStore\Bundles;
+
+class CoreBundle extends Bundle {
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getName() {
+ return 'Core bundle';
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getAppIdentifiers() {
+ return [
+ 'bruteforcesettings',
+ ];
+ }
+
+}
diff --git a/lib/private/App/AppStore/Bundles/EnterpriseBundle.php b/lib/private/App/AppStore/Bundles/EnterpriseBundle.php
new file mode 100644
index 00000000000..6d43a6210fa
--- /dev/null
+++ b/lib/private/App/AppStore/Bundles/EnterpriseBundle.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\App\AppStore\Bundles;
+
+class EnterpriseBundle extends Bundle {
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getName() {
+ return (string)$this->l10n->t('Enterprise bundle');
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getAppIdentifiers() {
+ return [
+ 'admin_audit',
+ 'user_ldap',
+ 'files_retention',
+ 'files_automatedtagging',
+ 'user_saml',
+ 'files_accesscontrol',
+ ];
+ }
+
+}
diff --git a/lib/private/App/AppStore/Bundles/GroupwareBundle.php b/lib/private/App/AppStore/Bundles/GroupwareBundle.php
new file mode 100644
index 00000000000..7e7414f69c7
--- /dev/null
+++ b/lib/private/App/AppStore/Bundles/GroupwareBundle.php
@@ -0,0 +1,44 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\App\AppStore\Bundles;
+
+class GroupwareBundle extends Bundle {
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getName() {
+ return (string)$this->l10n->t('Groupware bundle');
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getAppIdentifiers() {
+ return [
+ 'calendar',
+ 'contacts',
+ 'spreed',
+ ];
+ }
+
+}
diff --git a/lib/private/App/AppStore/Bundles/SocialSharingBundle.php b/lib/private/App/AppStore/Bundles/SocialSharingBundle.php
new file mode 100644
index 00000000000..8da84e8d1ef
--- /dev/null
+++ b/lib/private/App/AppStore/Bundles/SocialSharingBundle.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\App\AppStore\Bundles;
+
+class SocialSharingBundle extends Bundle {
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getName() {
+ return (string)$this->l10n->t('Social sharing bundle');
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getAppIdentifiers() {
+ return [
+ 'socialsharing_twitter',
+ 'socialsharing_googleplus',
+ 'socialsharing_facebook',
+ 'socialsharing_email',
+ 'socialsharing_diaspora',
+ ];
+ }
+
+}
diff --git a/lib/private/Contacts/ContactsMenu/ContactsStore.php b/lib/private/Contacts/ContactsMenu/ContactsStore.php
index 1cdb5d6fc5f..40a0bf87031 100644
--- a/lib/private/Contacts/ContactsMenu/ContactsStore.php
+++ b/lib/private/Contacts/ContactsMenu/ContactsStore.php
@@ -60,6 +60,50 @@ class ContactsStore {
}
/**
+ * @param IUser $user
+ * @param integer $shareType
+ * @param string $shareWith
+ * @return IEntry|null
+ */
+ public function findOne(IUser $user, $shareType, $shareWith) {
+ switch($shareType) {
+ case 0:
+ case 6:
+ $filter = ['UID'];
+ break;
+ case 4:
+ $filter = ['EMAIL'];
+ break;
+ default:
+ return null;
+ }
+
+ $userId = $user->getUID();
+ $allContacts = $this->contactsManager->search($shareWith, $filter);
+ $contacts = array_filter($allContacts, function($contact) use ($userId) {
+ return $contact['UID'] !== $userId;
+ });
+ $match = null;
+
+ foreach ($contacts as $contact) {
+ if ($shareType === 4 && isset($contact['EMAIL'])) {
+ if (in_array($shareWith, $contact['EMAIL'])) {
+ $match = $contact;
+ break;
+ }
+ }
+ if ($shareType === 0 || $shareType === 6) {
+ if ($contact['UID'] === $shareWith && $contact['isLocalSystemBook'] === true) {
+ $match = $contact;
+ break;
+ }
+ }
+ }
+
+ return $match ? $this->contactArrayToEntry($match) : null;
+ }
+
+ /**
* @param array $contact
* @return Entry
*/
diff --git a/lib/private/Contacts/ContactsMenu/Manager.php b/lib/private/Contacts/ContactsMenu/Manager.php
index 16d77c2df08..766b4623253 100644
--- a/lib/private/Contacts/ContactsMenu/Manager.php
+++ b/lib/private/Contacts/ContactsMenu/Manager.php
@@ -51,7 +51,7 @@ class Manager {
}
/**
- * @param string $user
+ * @param IUser $user
* @param string $filter
* @return array
*/
@@ -70,6 +70,21 @@ class Manager {
}
/**
+ * @param IUser $user
+ * @param integer $shareType
+ * @param string $shareWith
+ * @return IEntry
+ */
+ public function findOne(IUser $user, $shareType, $shareWith) {
+ $entry = $this->store->findOne($user, $shareType, $shareWith);
+ if ($entry) {
+ $this->processEntries([$entry], $user);
+ }
+
+ return $entry;
+ }
+
+ /**
* @param IEntry[] $entries
* @return IEntry[]
*/
diff --git a/lib/private/Files/Utils/Scanner.php b/lib/private/Files/Utils/Scanner.php
index 02f355fd4d9..fac95462ce5 100644
--- a/lib/private/Files/Utils/Scanner.php
+++ b/lib/private/Files/Utils/Scanner.php
@@ -47,6 +47,8 @@ use OCP\ILogger;
* @package OC\Files\Utils
*/
class Scanner extends PublicEmitter {
+ const MAX_ENTRIES_TO_COMMIT = 10000;
+
/**
* @var string $user
*/
@@ -63,6 +65,20 @@ class Scanner extends PublicEmitter {
protected $logger;
/**
+ * Whether to use a DB transaction
+ *
+ * @var bool
+ */
+ protected $useTransaction;
+
+ /**
+ * Number of entries scanned to commit
+ *
+ * @var int
+ */
+ protected $entriesToCommit;
+
+ /**
* @param string $user
* @param \OCP\IDBConnection $db
* @param ILogger $logger
@@ -71,6 +87,8 @@ class Scanner extends PublicEmitter {
$this->logger = $logger;
$this->user = $user;
$this->db = $db;
+ // when DB locking is used, no DB transactions will be used
+ $this->useTransaction = !(\OC::$server->getLockingProvider() instanceof DBLockingProvider);
}
/**
@@ -200,22 +218,22 @@ class Scanner extends PublicEmitter {
$scanner = $storage->getScanner();
$scanner->setUseTransactions(false);
$this->attachListener($mount);
- $isDbLocking = \OC::$server->getLockingProvider() instanceof DBLockingProvider;
$scanner->listen('\OC\Files\Cache\Scanner', 'removeFromCache', function ($path) use ($storage) {
- $this->triggerPropagator($storage, $path);
+ $this->postProcessEntry($storage, $path);
});
$scanner->listen('\OC\Files\Cache\Scanner', 'updateCache', function ($path) use ($storage) {
- $this->triggerPropagator($storage, $path);
+ $this->postProcessEntry($storage, $path);
});
$scanner->listen('\OC\Files\Cache\Scanner', 'addToCache', function ($path) use ($storage) {
- $this->triggerPropagator($storage, $path);
+ $this->postProcessEntry($storage, $path);
});
if (!$storage->file_exists($relativePath)) {
throw new NotFoundException($dir);
}
- if (!$isDbLocking) {
+
+ if ($this->useTransaction) {
$this->db->beginTransaction();
}
try {
@@ -233,7 +251,7 @@ class Scanner extends PublicEmitter {
$this->logger->logException($e);
$this->emit('\OC\Files\Utils\Scanner', 'StorageNotAvailable', [$e]);
}
- if (!$isDbLocking) {
+ if ($this->useTransaction) {
$this->db->commit();
}
}
@@ -242,5 +260,20 @@ class Scanner extends PublicEmitter {
private function triggerPropagator(IStorage $storage, $internalPath) {
$storage->getPropagator()->propagateChange($internalPath, time());
}
+
+ private function postProcessEntry(IStorage $storage, $internalPath) {
+ $this->triggerPropagator($storage, $internalPath);
+ if ($this->useTransaction) {
+ $this->entriesToCommit++;
+ if ($this->entriesToCommit >= self::MAX_ENTRIES_TO_COMMIT) {
+ $propagator = $storage->getPropagator();
+ $this->entriesToCommit = 0;
+ $this->db->commit();
+ $propagator->commitBatch();
+ $this->db->beginTransaction();
+ $propagator->beginBatch();
+ }
+ }
+ }
}
diff --git a/lib/private/Installer.php b/lib/private/Installer.php
index 0d6030d5744..8702f264e54 100644
--- a/lib/private/Installer.php
+++ b/lib/private/Installer.php
@@ -42,6 +42,8 @@
namespace OC;
use Doctrine\DBAL\Exception\TableExistsException;
+use OC\App\AppManager;
+use OC\App\AppStore\Bundles\Bundle;
use OC\App\AppStore\Fetcher\AppFetcher;
use OC\App\CodeChecker\CodeChecker;
use OC\App\CodeChecker\EmptyCheck;
@@ -50,7 +52,9 @@ use OC\Archive\TAR;
use OC_App;
use OC_DB;
use OC_Helper;
+use OCP\App\IAppManager;
use OCP\Http\Client\IClientService;
+use OCP\IConfig;
use OCP\ILogger;
use OCP\ITempManager;
use phpseclib\File\X509;
@@ -67,21 +71,26 @@ class Installer {
private $tempManager;
/** @var ILogger */
private $logger;
+ /** @var IConfig */
+ private $config;
/**
* @param AppFetcher $appFetcher
* @param IClientService $clientService
* @param ITempManager $tempManager
* @param ILogger $logger
+ * @param IConfig $config
*/
public function __construct(AppFetcher $appFetcher,
IClientService $clientService,
ITempManager $tempManager,
- ILogger $logger) {
+ ILogger $logger,
+ IConfig $config) {
$this->appFetcher = $appFetcher;
$this->clientService = $clientService;
$this->tempManager = $tempManager;
$this->logger = $logger;
+ $this->config = $config;
}
/**
@@ -109,6 +118,7 @@ class Installer {
}
}
+ \OC_App::registerAutoloading($appId, $basedir);
\OC_App::setupBackgroundJobs($info['background-jobs']);
//run appinfo/install.php
@@ -420,6 +430,27 @@ class Installer {
}
/**
+ * Installs the app within the bundle and marks the bundle as installed
+ *
+ * @param Bundle $bundle
+ * @throws \Exception If app could not get installed
+ */
+ public function installAppBundle(Bundle $bundle) {
+ $appIds = $bundle->getAppIdentifiers();
+ foreach($appIds as $appId) {
+ if(!$this->isDownloaded($appId)) {
+ $this->downloadApp($appId);
+ }
+ $this->installApp($appId);
+ $app = new OC_App();
+ $app->enable($appId);
+ }
+ $bundles = json_decode($this->config->getAppValue('core', 'installed.bundles', json_encode([])), true);
+ $bundles[] = $bundle->getIdentifier();
+ $this->config->setAppValue('core', 'installed.bundles', json_encode($bundles));
+ }
+
+ /**
* Installs shipped apps
*
* This function installs all apps found in the 'apps' directory that should be enabled by default;
diff --git a/lib/private/Repair.php b/lib/private/Repair.php
index e808774ec93..65e0342905a 100644
--- a/lib/private/Repair.php
+++ b/lib/private/Repair.php
@@ -30,12 +30,14 @@
namespace OC;
+use OC\App\AppStore\Bundles\BundleFetcher;
use OC\Repair\CleanTags;
use OC\Repair\Collation;
use OC\Repair\MoveUpdaterStepFile;
use OC\Repair\NC11\CleanPreviews;
use OC\Repair\NC11\FixMountStorages;
use OC\Repair\NC11\MoveAvatars;
+use OC\Repair\NC12\InstallCoreBundle;
use OC\Repair\NC12\UpdateLanguageCodes;
use OC\Repair\OldGroupMembershipShares;
use OC\Repair\RemoveRootShares;
@@ -136,6 +138,11 @@ class Repair implements IOutput{
),
new FixMountStorages(\OC::$server->getDatabaseConnection()),
new UpdateLanguageCodes(\OC::$server->getDatabaseConnection(), \OC::$server->getConfig()),
+ new InstallCoreBundle(
+ \OC::$server->query(BundleFetcher::class),
+ \OC::$server->getConfig(),
+ \OC::$server->query(Installer::class)
+ )
];
}
diff --git a/lib/private/Repair/NC12/InstallCoreBundle.php b/lib/private/Repair/NC12/InstallCoreBundle.php
new file mode 100644
index 00000000000..38583b09a89
--- /dev/null
+++ b/lib/private/Repair/NC12/InstallCoreBundle.php
@@ -0,0 +1,78 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\Repair\NC12;
+
+use OC\App\AppStore\Bundles\BundleFetcher;
+use OC\Installer;
+use OCP\IConfig;
+use OCP\Migration\IOutput;
+use OCP\Migration\IRepairStep;
+
+class InstallCoreBundle implements IRepairStep {
+ /** @var BundleFetcher */
+ private $bundleFetcher;
+ /** @var IConfig */
+ private $config;
+ /** @var Installer */
+ private $installer;
+
+ /**
+ * @param BundleFetcher $bundleFetcher
+ * @param IConfig $config
+ * @param Installer $installer
+ */
+ public function __construct(BundleFetcher $bundleFetcher,
+ IConfig $config,
+ Installer $installer) {
+ $this->bundleFetcher = $bundleFetcher;
+ $this->config = $config;
+ $this->installer = $installer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName() {
+ return 'Install new core bundle components';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function run(IOutput $output) {
+ $versionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0');
+
+ if (version_compare($versionFromBeforeUpdate, '12.0.0.14', '>')) {
+ return;
+ }
+
+ $defaultBundle = $this->bundleFetcher->getDefaultInstallationBundle();
+ foreach($defaultBundle as $bundle) {
+ try {
+ $this->installer->installAppBundle($bundle);
+ $output->info('Successfully installed core app bundle.');
+ } catch (\Exception $e) {
+ $output->warning('Could not install core app bundle: ' . $e->getMessage());
+ }
+ }
+ }
+}
diff --git a/lib/private/Server.php b/lib/private/Server.php
index 7724feb551b..b05e05660b0 100644
--- a/lib/private/Server.php
+++ b/lib/private/Server.php
@@ -43,6 +43,7 @@ namespace OC;
use bantu\IniGetWrapper\IniGetWrapper;
use OC\App\AppManager;
+use OC\App\AppStore\Bundles\BundleFetcher;
use OC\App\AppStore\Fetcher\AppFetcher;
use OC\App\AppStore\Fetcher\CategoryFetcher;
use OC\AppFramework\Http\Request;
@@ -816,7 +817,12 @@ class Server extends ServerContainer implements IServerContainer {
);
});
$this->registerAlias('MimeTypeLoader', \OCP\Files\IMimeTypeLoader::class);
-
+ $this->registerService(BundleFetcher::class, function () {
+ return new BundleFetcher($this->getL10N('lib'));
+ });
+ $this->registerService(AppFetcher::class, function() {
+ return $this->getAppFetcher();
+ });
$this->registerService(\OCP\Notification\IManager::class, function (Server $c) {
return new Manager(
$c->query(IValidator::class)
diff --git a/lib/private/Setup.php b/lib/private/Setup.php
index e2806efad48..b1cf289d9aa 100644
--- a/lib/private/Setup.php
+++ b/lib/private/Setup.php
@@ -41,6 +41,7 @@ namespace OC;
use bantu\IniGetWrapper\IniGetWrapper;
use Exception;
+use OC\App\AppStore\Bundles\BundleFetcher;
use OCP\Defaults;
use OCP\IL10N;
use OCP\ILogger;
@@ -63,11 +64,12 @@ class Setup {
/**
* @param SystemConfig $config
* @param IniGetWrapper $iniWrapper
+ * @param IL10N $l10n
* @param Defaults $defaults
* @param ILogger $logger
* @param ISecureRandom $random
*/
- function __construct(SystemConfig $config,
+ public function __construct(SystemConfig $config,
IniGetWrapper $iniWrapper,
IL10N $l10n,
Defaults $defaults,
@@ -364,8 +366,22 @@ class Setup {
$group =\OC::$server->getGroupManager()->createGroup('admin');
$group->addUser($user);
- //guess what this does
+ // Install shipped apps and specified app bundles
Installer::installShippedApps();
+ $installer = new Installer(
+ \OC::$server->getAppFetcher(),
+ \OC::$server->getHTTPClientService(),
+ \OC::$server->getTempManager(),
+ \OC::$server->getLogger(),
+ \OC::$server->getConfig()
+ );
+ $bundleFetcher = new BundleFetcher(\OC::$server->getL10N('lib'));
+ $defaultInstallationBundles = $bundleFetcher->getDefaultInstallationBundle();
+ foreach($defaultInstallationBundles as $bundle) {
+ try {
+ $installer->installAppBundle($bundle);
+ } catch (Exception $e) {}
+ }
// create empty file in data dir, so we can later find
// out that this is indeed an ownCloud data directory
diff --git a/lib/private/Updater.php b/lib/private/Updater.php
index 4427e4c48dc..c080ee0eb43 100644
--- a/lib/private/Updater.php
+++ b/lib/private/Updater.php
@@ -243,11 +243,11 @@ class Updater extends BasicEmitter {
}
// update all shipped apps
- $disabledApps = $this->checkAppsRequirements();
+ $this->checkAppsRequirements();
$this->doAppUpgrade();
// upgrade appstore apps
- $this->upgradeAppStoreApps($disabledApps);
+ $this->upgradeAppStoreApps(\OC::$server->getAppManager()->getInstalledApps());
// install new shipped apps on upgrade
OC_App::loadApps('authentication');
@@ -441,7 +441,8 @@ class Updater extends BasicEmitter {
\OC::$server->getAppFetcher(),
\OC::$server->getHTTPClientService(),
\OC::$server->getTempManager(),
- $this->log
+ $this->log,
+ \OC::$server->getConfig()
);
if (Installer::isUpdateAvailable($app, \OC::$server->getAppFetcher())) {
$this->emit('\OC\Updater', 'upgradeAppStoreApp', [$app]);
diff --git a/lib/private/legacy/app.php b/lib/private/legacy/app.php
index 3800b8b770e..111da7d0d40 100644
--- a/lib/private/legacy/app.php
+++ b/lib/private/legacy/app.php
@@ -365,7 +365,8 @@ class OC_App {
\OC::$server->getAppFetcher(),
\OC::$server->getHTTPClientService(),
\OC::$server->getTempManager(),
- \OC::$server->getLogger()
+ \OC::$server->getLogger(),
+ \OC::$server->getConfig()
);
$isDownloaded = $installer->isDownloaded($appId);
@@ -427,7 +428,8 @@ class OC_App {
\OC::$server->getAppFetcher(),
\OC::$server->getHTTPClientService(),
\OC::$server->getTempManager(),
- \OC::$server->getLogger()
+ \OC::$server->getLogger(),
+ \OC::$server->getConfig()
);
return $installer->removeApp($app);
}
diff --git a/settings/Controller/AppSettingsController.php b/settings/Controller/AppSettingsController.php
index 7be6c2bf562..ac77b2e7dd6 100644
--- a/settings/Controller/AppSettingsController.php
+++ b/settings/Controller/AppSettingsController.php
@@ -27,6 +27,7 @@
namespace OC\Settings\Controller;
+use OC\App\AppStore\Bundles\BundleFetcher;
use OC\App\AppStore\Fetcher\AppFetcher;
use OC\App\AppStore\Fetcher\CategoryFetcher;
use OC\App\AppStore\Version\VersionParser;
@@ -50,6 +51,7 @@ class AppSettingsController extends Controller {
const CAT_ENABLED = 0;
const CAT_DISABLED = 1;
const CAT_ALL_INSTALLED = 2;
+ const CAT_APP_BUNDLES = 3;
/** @var \OCP\IL10N */
private $l10n;
@@ -65,6 +67,8 @@ class AppSettingsController extends Controller {
private $appFetcher;
/** @var IFactory */
private $l10nFactory;
+ /** @var BundleFetcher */
+ private $bundleFetcher;
/**
* @param string $appName
@@ -76,6 +80,7 @@ class AppSettingsController extends Controller {
* @param CategoryFetcher $categoryFetcher
* @param AppFetcher $appFetcher
* @param IFactory $l10nFactory
+ * @param BundleFetcher $bundleFetcher
*/
public function __construct($appName,
IRequest $request,
@@ -85,7 +90,8 @@ class AppSettingsController extends Controller {
IAppManager $appManager,
CategoryFetcher $categoryFetcher,
AppFetcher $appFetcher,
- IFactory $l10nFactory) {
+ IFactory $l10nFactory,
+ BundleFetcher $bundleFetcher) {
parent::__construct($appName, $request);
$this->l10n = $l10n;
$this->config = $config;
@@ -94,6 +100,7 @@ class AppSettingsController extends Controller {
$this->categoryFetcher = $categoryFetcher;
$this->appFetcher = $appFetcher;
$this->l10nFactory = $l10nFactory;
+ $this->bundleFetcher = $bundleFetcher;
}
/**
@@ -120,18 +127,14 @@ class AppSettingsController extends Controller {
return $templateResponse;
}
- /**
- * Get all available categories
- *
- * @return JSONResponse
- */
- public function listCategories() {
+ private function getAllCategories() {
$currentLanguage = substr($this->l10nFactory->findLanguage(), 0, 2);
$formattedCategories = [
['id' => self::CAT_ALL_INSTALLED, 'ident' => 'installed', 'displayName' => (string)$this->l10n->t('Your apps')],
['id' => self::CAT_ENABLED, 'ident' => 'enabled', 'displayName' => (string)$this->l10n->t('Enabled apps')],
['id' => self::CAT_DISABLED, 'ident' => 'disabled', 'displayName' => (string)$this->l10n->t('Disabled apps')],
+ ['id' => self::CAT_APP_BUNDLES, 'ident' => 'app-bundles', 'displayName' => (string)$this->l10n->t('App bundles')],
];
$categories = $this->categoryFetcher->get();
foreach($categories as $category) {
@@ -142,7 +145,16 @@ class AppSettingsController extends Controller {
];
}
- return new JSONResponse($formattedCategories);
+ return $formattedCategories;
+ }
+
+ /**
+ * Get all available categories
+ *
+ * @return JSONResponse
+ */
+ public function listCategories() {
+ return new JSONResponse($this->getAllCategories());
}
/**
@@ -334,6 +346,41 @@ class AppSettingsController extends Controller {
return ($a < $b) ? -1 : 1;
});
break;
+ case 'app-bundles':
+ $bundles = $this->bundleFetcher->getBundles();
+ $apps = [];
+ foreach($bundles as $bundle) {
+ $newCategory = true;
+ $allApps = $appClass->listAllApps();
+ $categories = $this->getAllCategories();
+ foreach($categories as $singleCategory) {
+ $newApps = $this->getAppsForCategory($singleCategory['id']);
+ foreach($allApps as $app) {
+ foreach($newApps as $key => $newApp) {
+ if($app['id'] === $newApp['id']) {
+ unset($newApps[$key]);
+ }
+ }
+ }
+ $allApps = array_merge($allApps, $newApps);
+ }
+
+ foreach($bundle->getAppIdentifiers() as $identifier) {
+ foreach($allApps as $app) {
+ if($app['id'] === $identifier) {
+ if($newCategory) {
+ $app['newCategory'] = true;
+ $app['categoryName'] = $bundle->getName();
+ }
+ $app['bundleId'] = $bundle->getIdentifier();
+ $newCategory = false;
+ $apps[] = $app;
+ continue;
+ }
+ }
+ }
+ }
+ break;
default:
$apps = $this->getAppsForCategory($category);
diff --git a/settings/Controller/MailSettingsController.php b/settings/Controller/MailSettingsController.php
index df13b46b13c..de10c077ede 100644
--- a/settings/Controller/MailSettingsController.php
+++ b/settings/Controller/MailSettingsController.php
@@ -160,7 +160,7 @@ class MailSettingsController extends Controller {
$message->setPlainBody($template->renderText());
$errors = $this->mailer->send($message);
if (!empty($errors)) {
- throw new \RuntimeException($this->l10n->t('Mail could not be sent. Check your mail server log'));
+ throw new \RuntimeException($this->l10n->t('Email could not be sent. Check your mail server log'));
}
return new DataResponse();
} catch (\Exception $e) {
diff --git a/settings/ajax/disableapp.php b/settings/ajax/disableapp.php
index 8edd1c1453e..9b76236a15b 100644
--- a/settings/ajax/disableapp.php
+++ b/settings/ajax/disableapp.php
@@ -36,8 +36,9 @@ if (!array_key_exists('appid', $_POST)) {
exit;
}
-$appId = (string)$_POST['appid'];
-$appId = OC_App::cleanAppId($appId);
-
-OC_App::disable($appId);
+$appIds = (array)$_POST['appid'];
+foreach($appIds as $appId) {
+ $appId = OC_App::cleanAppId($appId);
+ OC_App::disable($appId);
+}
OC_JSON::success();
diff --git a/settings/ajax/enableapp.php b/settings/ajax/enableapp.php
index b6d62671a63..4c4fa0be666 100644
--- a/settings/ajax/enableapp.php
+++ b/settings/ajax/enableapp.php
@@ -36,13 +36,20 @@ if ($lastConfirm < (time() - 30 * 60 + 15)) { // allow 15 seconds delay
}
$groups = isset($_POST['groups']) ? (array)$_POST['groups'] : null;
+$appIds = isset($_POST['appIds']) ? (array)$_POST['appIds'] : [];
try {
- $app = new OC_App();
- $appId = (string)$_POST['appid'];
- $appId = OC_App::cleanAppId($appId);
- $app->enable($appId, $groups);
- OC_JSON::success(['data' => ['update_required' => \OC_App::shouldUpgrade($appId)]]);
+ $updateRequired = false;
+ foreach($appIds as $appId) {
+ $app = new OC_App();
+ $appId = OC_App::cleanAppId($appId);
+ $app->enable($appId, $groups);
+ if(\OC_App::shouldUpgrade($appId)) {
+ $updateRequired = true;
+ }
+ }
+
+ OC_JSON::success(['data' => ['update_required' => $updateRequired]]);
} catch (Exception $e) {
\OCP\Util::writeLog('core', $e->getMessage(), \OCP\Util::ERROR);
OC_JSON::error(array("data" => array("message" => $e->getMessage()) ));
diff --git a/settings/ajax/updateapp.php b/settings/ajax/updateapp.php
index 3020f828577..bcf8e149140 100644
--- a/settings/ajax/updateapp.php
+++ b/settings/ajax/updateapp.php
@@ -44,7 +44,8 @@ try {
\OC::$server->getAppFetcher(),
\OC::$server->getHTTPClientService(),
\OC::$server->getTempManager(),
- \OC::$server->getLogger()
+ \OC::$server->getLogger(),
+ \OC::$server->getConfig()
);
$result = $installer->updateAppstoreApp($appId);
$config->setSystemValue('maintenance', false);
diff --git a/settings/css/settings.css b/settings/css/settings.css
index 7e91877773e..0a1d4e046fe 100644
--- a/settings/css/settings.css
+++ b/settings/css/settings.css
@@ -519,13 +519,37 @@ input.userFilter {width: 200px;}
/* APPS */
+/* Bundle header */
+#apps-list .apps-header {
+ display: table-row;
+ position: relative;
+}
+#apps-list .apps-header div {
+ display: table-cell;
+ height: 70px;
+}
+#apps-list .apps-header h2 {
+ display: table-cell;
+ position: absolute;
+ padding-left: 6px;
+ padding-top: 15px;
+}
+#apps-list .apps-header h2 .enable {
+ position: relative;
+ top: -1px;
+ margin-left: 12px;
+}
+#apps-list .apps-header h2 + .section {
+ margin-top: 50px;
+}
+
#app-content > svg.app-filter {
float: left;
height: 0;
width: 0;
}
-#app-category-disabled {
+#app-category-app-bundles {
margin-bottom: 20px;
}
@@ -558,6 +582,10 @@ span.version {
border-radius: 3px;
padding: 3px 6px;
}
+.app-level a {
+ padding: 10px;
+ white-space: nowrap;
+}
.app-level .official {
border-color: #37ce02;
background-position: left center;
@@ -737,6 +765,7 @@ form.section {
display: table;
width: 100%;
height: auto;
+ margin-bottom: 100px;
}
#apps-list.installed .section {
diff --git a/settings/js/apps.js b/settings/js/apps.js
index 3326886951f..6bad2cc842c 100644
--- a/settings/js/apps.js
+++ b/settings/js/apps.js
@@ -29,6 +29,7 @@ OC.Settings.Apps = OC.Settings.Apps || {
State: {
currentCategory: null,
+ currentCategoryElements: null,
apps: null,
$updateNotification: null,
availableUpdates: 0
@@ -90,14 +91,15 @@ OC.Settings.Apps = OC.Settings.Apps || {
}), {
type:'GET',
success: function (apps) {
+ OC.Settings.Apps.State.currentCategoryElements = apps.apps;
var appListWithIndex = _.indexBy(apps.apps, 'id');
OC.Settings.Apps.State.apps = appListWithIndex;
var appList = _.map(appListWithIndex, function(app) {
// default values for missing fields
return _.extend({level: 0}, app);
});
- var source
- if (categoryId === 'enabled' || categoryId === 'disabled' || categoryId === 'installed') {
+ var source;
+ if (categoryId === 'enabled' || categoryId === 'disabled' || categoryId === 'installed' || categoryId === 'app-bundles') {
source = $("#app-template-installed").html();
$('#apps-list').addClass('installed');
} else {
@@ -107,17 +109,19 @@ OC.Settings.Apps = OC.Settings.Apps || {
var template = Handlebars.compile(source);
if (appList.length) {
- appList.sort(function(a,b) {
- if (a.active !== b.active) {
- return (a.active ? -1 : 1)
- } else {
- var levelDiff = b.level - a.level;
- if (levelDiff === 0) {
- return OC.Util.naturalSortCompare(a.name, b.name);
+ if(categoryId !== 'app-bundles') {
+ appList.sort(function (a, b) {
+ if (a.active !== b.active) {
+ return (a.active ? -1 : 1)
+ } else {
+ var levelDiff = b.level - a.level;
+ if (levelDiff === 0) {
+ return OC.Util.naturalSortCompare(a.name, b.name);
+ }
+ return levelDiff;
}
- return levelDiff;
- }
- });
+ });
+ }
var firstExperimental = false;
_.each(appList, function(app) {
@@ -303,56 +307,126 @@ OC.Settings.Apps = OC.Settings.Apps || {
return $.get(OC.generateUrl('apps/files'));
},
- enableApp:function(appId, active, element, groups) {
+ enableAppBundle:function(bundleId, active, element, groups) {
+ if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
+ OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this.enableAppBundle, this, bundleId, active, element, groups));
+ return;
+ }
+
+ var apps = OC.Settings.Apps.State.currentCategoryElements;
+ var appsToEnable = [];
+ apps.forEach(function(app) {
+ if(app['bundleId'] === bundleId) {
+ if(app['active'] === false) {
+ appsToEnable.push(app['id']);
+ }
+ }
+ });
+
+ OC.Settings.Apps.enableApp(appsToEnable, false, groups);
+ },
+
+ /**
+ * @param {string[]} appId
+ * @param {boolean} active
+ * @param {array} groups
+ */
+ enableApp:function(appId, active, groups) {
if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
- OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this.enableApp, this, appId, active, element, groups));
+ OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this.enableApp, this, appId, active, groups));
return;
}
+ var elements = [];
+ appId.forEach(function(appId) {
+ elements.push($('#app-'+appId+' .enable'));
+ });
+
var self = this;
- OC.Settings.Apps.hideErrorMessage(appId);
+ appId.forEach(function(appId) {
+ OC.Settings.Apps.hideErrorMessage(appId);
+ });
groups = groups || [];
- var appItem = $('div#app-'+appId+'');
+ var appItems = [];
+ appId.forEach(function(appId) {
+ appItems.push($('div#app-'+appId+''));
+ });
+
if(active && !groups.length) {
- element.val(t('settings','Disabling app …'));
+ elements.forEach(function(element) {
+ element.val(t('settings','Disabling app …'));
+ });
$.post(OC.filePath('settings','ajax','disableapp.php'),{appid:appId},function(result) {
if(!result || result.status !== 'success') {
if (result.data && result.data.message) {
OC.Settings.Apps.showErrorMessage(appId, result.data.message);
- appItem.data('errormsg', result.data.message);
+ appItems.forEach(function(appItem) {
+ appItem.data('errormsg', result.data.message);
+ })
} else {
OC.Settings.Apps.showErrorMessage(appId, t('settings', 'Error while disabling app'));
- appItem.data('errormsg', t('settings', 'Error while disabling app'));
+ appItems.forEach(function(appItem) {
+ appItem.data('errormsg', t('settings', 'Error while disabling app'));
+ });
}
- element.val(t('settings','Disable'));
- appItem.addClass('appwarning');
+ elements.forEach(function(element) {
+ element.val(t('settings','Disable'));
+ });
+ appItems.forEach(function(appItem) {
+ appItem.addClass('appwarning');
+ });
} else {
OC.Settings.Apps.rebuildNavigation();
- appItem.data('active',false);
- appItem.data('groups', '');
- element.data('active',false);
- appItem.removeClass('active');
- element.val(t('settings','Enable'));
- element.parent().find(".groups-enable").hide();
- element.parent().find('#group_select').hide().val(null);
+ appItems.forEach(function(appItem) {
+ appItem.data('active', false);
+ appItem.data('groups', '');
+ });
+ elements.forEach(function(element) {
+ element.data('active', false);
+ });
+ appItems.forEach(function(appItem) {
+ appItem.removeClass('active');
+ });
+ elements.forEach(function(element) {
+ element.val(t('settings', 'Enable'));
+ element.parent().find(".groups-enable").hide();
+ element.parent().find('#group_select').hide().val(null);
+ });
OC.Settings.Apps.State.apps[appId].active = false;
}
},'json');
} else {
// TODO: display message to admin to not refresh the page!
// TODO: lock UI to prevent further operations
- element.val(t('settings','Enabling app …'));
- $.post(OC.filePath('settings','ajax','enableapp.php'),{appid: appId, groups: groups},function(result) {
+ elements.forEach(function(element) {
+ element.val(t('settings', 'Enabling app …'));
+ });
+
+ var appIdArray = [];
+ if( typeof appId === 'string' ) {
+ appIdArray = [appId];
+ } else {
+ appIdArray = appId;
+ }
+ $.post(OC.filePath('settings','ajax','enableapp.php'),{appIds: appIdArray, groups: groups},function(result) {
if(!result || result.status !== 'success') {
if (result.data && result.data.message) {
OC.Settings.Apps.showErrorMessage(appId, result.data.message);
- appItem.data('errormsg', result.data.message);
+ appItems.forEach(function(appItem) {
+ appItem.data('errormsg', result.data.message);
+ });
} else {
OC.Settings.Apps.showErrorMessage(appId, t('settings', 'Error while enabling app'));
- appItem.data('errormsg', t('settings', 'Error while disabling app'));
+ appItems.forEach(function(appItem) {
+ appItem.data('errormsg', t('settings', 'Error while disabling app'));
+ });
}
- element.val(t('settings','Enable'));
- appItem.addClass('appwarning');
+ elements.forEach(function(element) {
+ element.val(t('settings', 'Enable'));
+ });
+ appItems.forEach(function(appItem) {
+ appItem.addClass('appwarning');
+ });
} else {
self._checkServerHealth().done(function() {
if (result.data.update_required) {
@@ -364,24 +438,40 @@ OC.Settings.Apps = OC.Settings.Apps || {
}
OC.Settings.Apps.rebuildNavigation();
- appItem.data('active',true);
- element.data('active',true);
- appItem.addClass('active');
- element.val(t('settings','Disable'));
+ appItems.forEach(function(appItem) {
+ appItem.data('active', true);
+ });
+ elements.forEach(function(element) {
+ element.data('active', true);
+ });
+ appItems.forEach(function(appItem) {
+ appItem.addClass('active');
+ });
+ elements.forEach(function(element) {
+ element.val(t('settings', 'Disable'));
+ });
var app = OC.Settings.Apps.State.apps[appId];
app.active = true;
if (OC.Settings.Apps.isType(app, 'filesystem') || OC.Settings.Apps.isType(app, 'prelogin') ||
OC.Settings.Apps.isType(app, 'authentication') || OC.Settings.Apps.isType(app, 'logging')) {
- element.parent().find(".groups-enable").prop('checked', true);
- element.parent().find(".groups-enable").hide();
- element.parent().find('#group_select').hide().val(null);
+ elements.forEach(function(element) {
+ element.parent().find(".groups-enable").prop('checked', true);
+ element.parent().find(".groups-enable").hide();
+ element.parent().find('#group_select').hide().val(null);
+ });
} else {
- element.parent().find("#groups-enable").show();
+ elements.forEach(function(element) {
+ element.parent().find("#groups-enable").show();
+ });
if (groups) {
- appItem.data('groups', JSON.stringify(groups));
+ appItems.forEach(function(appItem) {
+ appItem.data('groups', JSON.stringify(groups));
+ });
} else {
- appItem.data('groups', '');
+ appItems.forEach(function(appItem) {
+ appItem.data('groups', '');
+ });
}
}
}).fail(function() {
@@ -391,26 +481,40 @@ OC.Settings.Apps = OC.Settings.Apps || {
appId,
t('settings', 'Error: this app cannot be enabled because it makes the server unstable')
);
- appItem.data('errormsg', t('settings', 'Error while enabling app'));
- element.val(t('settings','Enable'));
- appItem.addClass('appwarning');
+ appItems.forEach(function(appItem) {
+ appItem.data('errormsg', t('settings', 'Error while enabling app'));
+ });
+ elements.forEach(function(element) {
+ element.val(t('settings', 'Enable'));
+ });
+ appItems.forEach(function(appItem) {
+ appItem.addClass('appwarning');
+ });
}).fail(function() {
OC.Settings.Apps.showErrorMessage(
appId,
t('settings', 'Error: could not disable broken app')
);
- appItem.data('errormsg', t('settings', 'Error while disabling broken app'));
- element.val(t('settings','Enable'));
+ appItems.forEach(function(appItem) {
+ appItem.data('errormsg', t('settings', 'Error while disabling broken app'));
+ });
+ elements.forEach(function(element) {
+ element.val(t('settings', 'Enable'));
+ });
});
});
}
},'json')
.fail(function() {
OC.Settings.Apps.showErrorMessage(appId, t('settings', 'Error while enabling app'));
- appItem.data('errormsg', t('settings', 'Error while enabling app'));
- appItem.data('active',false);
- appItem.addClass('appwarning');
- element.val(t('settings','Enable'));
+ appItems.forEach(function(appItem) {
+ appItem.data('errormsg', t('settings', 'Error while enabling app'));
+ appItem.data('active', false);
+ appItem.addClass('appwarning');
+ });
+ elements.forEach(function(element) {
+ element.val(t('settings', 'Enable'));
+ });
});
}
},
@@ -774,10 +878,17 @@ OC.Settings.Apps = OC.Settings.Apps || {
$(document).on('click', '#apps-list input.enable', function () {
var appId = $(this).data('appid');
+ var bundleId = $(this).data('bundleid');
var element = $(this);
var active = $(this).data('active');
- OC.Settings.Apps.enableApp(appId, active, element);
+ var category = $('#app-navigation').attr('data-category');
+ if(bundleId) {
+ OC.Settings.Apps.enableAppBundle(bundleId, active, element);
+ element.val(t('settings', 'Enable all'));
+ } else {
+ OC.Settings.Apps.enableApp([appId], active);
+ }
});
$(document).on('click', '#apps-list input.uninstall', function () {
@@ -805,7 +916,7 @@ OC.Settings.Apps = OC.Settings.Apps || {
var appId = element.data('appid');
if (appId) {
- OC.Settings.Apps.enableApp(appId, false, element, groups);
+ OC.Settings.Apps.enableApp([appId], false, groups);
OC.Settings.Apps.State.apps[appId].groups = groups;
}
});
diff --git a/settings/templates/apps.php b/settings/templates/apps.php
index 310513722cf..260b042c078 100644
--- a/settings/templates/apps.php
+++ b/settings/templates/apps.php
@@ -29,8 +29,17 @@ script(
<?php endif; ?>
</script>
-
<script id="app-template-installed" type="text/x-handlebars">
+{{#if newCategory}}
+<div class="apps-header">
+ <div class="app-image"></div>
+ <h2>{{categoryName}} <input class="enable" type="submit" data-bundleid="{{bundleId}}" data-active="true" value="<?php p($l->t('Enable all'));?>"/></h2>
+ <div class="app-version"></div>
+ <div class="app-level"></div>
+ <div class="app-groups"></div>
+ <div class="actions">&nbsp;</div>
+</div>
+{{/if}}
<div class="section" id="app-{{id}}">
<div class="app-image app-image-icon"></div>
<div class="app-name">
diff --git a/tests/Core/Controller/ContactsMenuControllerTest.php b/tests/Core/Controller/ContactsMenuControllerTest.php
index bf6188e9097..92a185cf2ad 100644
--- a/tests/Core/Controller/ContactsMenuControllerTest.php
+++ b/tests/Core/Controller/ContactsMenuControllerTest.php
@@ -76,4 +76,35 @@ class ContactsMenuControllerTest extends TestCase {
$this->assertEquals($entries, $response);
}
+ public function testFindOne() {
+ $user = $this->createMock(IUser::class);
+ $entry = $this->createMock(IEntry::class);
+ $this->userSession->expects($this->once())
+ ->method('getUser')
+ ->willReturn($user);
+ $this->contactsManager->expects($this->once())
+ ->method('findOne')
+ ->with($this->equalTo($user), $this->equalTo(42), $this->equalTo('test-search-phrase'))
+ ->willReturn($entry);
+
+ $response = $this->controller->findOne(42, 'test-search-phrase');
+
+ $this->assertEquals($entry, $response);
+ }
+
+ public function testFindOne404() {
+ $user = $this->createMock(IUser::class);
+ $this->userSession->expects($this->once())
+ ->method('getUser')
+ ->willReturn($user);
+ $this->contactsManager->expects($this->once())
+ ->method('findOne')
+ ->with($this->equalTo($user), $this->equalTo(42), $this->equalTo('test-search-phrase'))
+ ->willReturn(null);
+
+ $response = $this->controller->findOne(42, 'test-search-phrase');
+
+ $this->assertEquals([], $response->getData());
+ $this->assertEquals(404, $response->getStatus());
+ }
}
diff --git a/tests/Settings/Controller/AppSettingsControllerTest.php b/tests/Settings/Controller/AppSettingsControllerTest.php
index 14dc33ca191..9633c771596 100644
--- a/tests/Settings/Controller/AppSettingsControllerTest.php
+++ b/tests/Settings/Controller/AppSettingsControllerTest.php
@@ -22,6 +22,7 @@
namespace Tests\Settings\Controller;
+use OC\App\AppStore\Bundles\BundleFetcher;
use OC\App\AppStore\Fetcher\AppFetcher;
use OC\App\AppStore\Fetcher\CategoryFetcher;
use OC\Settings\Controller\AppSettingsController;
@@ -60,6 +61,8 @@ class AppSettingsControllerTest extends TestCase {
private $appFetcher;
/** @var IFactory|\PHPUnit_Framework_MockObject_MockObject */
private $l10nFactory;
+ /** @var BundleFetcher|\PHPUnit_Framework_MockObject_MockObject */
+ private $bundleFetcher;
public function setUp() {
parent::setUp();
@@ -75,6 +78,7 @@ class AppSettingsControllerTest extends TestCase {
$this->categoryFetcher = $this->createMock(CategoryFetcher::class);
$this->appFetcher = $this->createMock(AppFetcher::class);
$this->l10nFactory = $this->createMock(IFactory::class);
+ $this->bundleFetcher = $this->createMock(BundleFetcher::class);
$this->appSettingsController = new AppSettingsController(
'settings',
@@ -85,7 +89,8 @@ class AppSettingsControllerTest extends TestCase {
$this->appManager,
$this->categoryFetcher,
$this->appFetcher,
- $this->l10nFactory
+ $this->l10nFactory,
+ $this->bundleFetcher
);
}
@@ -107,6 +112,11 @@ class AppSettingsControllerTest extends TestCase {
'displayName' => 'Disabled apps',
],
[
+ 'id' => 3,
+ 'ident' => 'app-bundles',
+ 'displayName' => 'App bundles',
+ ],
+ [
'id' => 'auth',
'ident' => 'auth',
'displayName' => 'Authentication & authorization',
diff --git a/tests/acceptance/features/app-files.feature b/tests/acceptance/features/app-files.feature
index 7adc618e02e..6779b37e145 100644
--- a/tests/acceptance/features/app-files.feature
+++ b/tests/acceptance/features/app-files.feature
@@ -1,5 +1,28 @@
Feature: app-files
+ Scenario: viewing a favorite file in its folder closes the details view
+ Given I am logged in
+ And I mark "welcome.txt" as favorite
+ And I see that "welcome.txt" is marked as favorite
+ And I open the "Favorites" section
+ And I open the details view for "welcome.txt"
+ And I see that the details view for "Favorites" section is open
+ When I view "welcome.txt" in folder
+ Then I see that the current section is "All files"
+ And I see that the details view is closed
+
+ Scenario: viewing a favorite file in its folder does not prevent opening the details view in "All files" section
+ Given I am logged in
+ And I mark "welcome.txt" as favorite
+ And I see that "welcome.txt" is marked as favorite
+ And I open the "Favorites" section
+ And I open the details view for "welcome.txt"
+ And I see that the details view for "Favorites" section is open
+ And I view "welcome.txt" in folder
+ And I see that the current section is "All files"
+ When I open the details view for "welcome.txt"
+ Then I see that the details view for "All files" section is open
+
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 7e7f592a44e..bc926fbe52f 100644
--- a/tests/acceptance/features/bootstrap/FilesAppContext.php
+++ b/tests/acceptance/features/bootstrap/FilesAppContext.php
@@ -28,6 +28,55 @@ class FilesAppContext implements Context, ActorAwareInterface {
use ActorAware;
/**
+ * @return array
+ */
+ public static function sections() {
+ return [ "All files" => "files",
+ "Recent" => "recent",
+ "Favorites" => "favorites",
+ "Shared with you" => "sharingin",
+ "Shared with others" => "sharingout",
+ "Shared by link" => "sharinglinks",
+ "Tags" => "systemtagsfilter",
+ "Deleted files" => "trashbin" ];
+ }
+
+ /**
+ * @return Locator
+ */
+ public static function appNavigation() {
+ return Locator::forThe()->id("app-navigation")->
+ describedAs("App navigation");
+ }
+
+ /**
+ * @return Locator
+ */
+ public static function appNavigationSectionItemFor($sectionText) {
+ return Locator::forThe()->xpath("//li[normalize-space() = '$sectionText']")->
+ descendantOf(self::appNavigation())->
+ describedAs($sectionText . " section item in App Navigation");
+ }
+
+ /**
+ * @return Locator
+ */
+ public static function appNavigationCurrentSectionItem() {
+ return Locator::forThe()->css(".active")->descendantOf(self::appNavigation())->
+ describedAs("Current section item in App Navigation");
+ }
+
+ /**
+ * @return Locator
+ */
+ public static function mainViewForSection($section) {
+ $sectionId = self::sections()[$section];
+
+ return Locator::forThe()->id("app-content-$sectionId")->
+ describedAs("Main view for section $section in Files app");
+ }
+
+ /**
* @return Locator
*/
public static function currentSectionMainView() {
@@ -38,6 +87,15 @@ class FilesAppContext implements Context, ActorAwareInterface {
/**
* @return Locator
*/
+ public static function detailsViewForSection($section) {
+ return Locator::forThe()->xpath("/preceding-sibling::*[position() = 1 and @id = 'app-sidebar']")->
+ descendantOf(self::mainViewForSection($section))->
+ describedAs("Details view for section $section in Files app");
+ }
+
+ /**
+ * @return Locator
+ */
public static function currentSectionDetailsView() {
return Locator::forThe()->xpath("/preceding-sibling::*[position() = 1 and @id = 'app-sidebar']")->
descendantOf(self::currentSectionMainView())->
@@ -96,12 +154,88 @@ class FilesAppContext implements Context, ActorAwareInterface {
/**
* @return Locator
*/
+ public static function favoriteActionForFile($fileName) {
+ return Locator::forThe()->css(".action-favorite")->descendantOf(self::rowForFile($fileName))->
+ describedAs("Favorite action for file $fileName in Files app");
+ }
+
+ /**
+ * @return Locator
+ */
+ public static function favoritedStateIconForFile($fileName) {
+ return Locator::forThe()->content("Favorited")->descendantOf(self::favoriteActionForFile($fileName))->
+ describedAs("Favorited state icon for file $fileName in Files app");
+ }
+
+ /**
+ * @return Locator
+ */
+ public static function mainLinkForFile($fileName) {
+ return Locator::forThe()->css(".name")->descendantOf(self::rowForFile($fileName))->
+ describedAs("Main link for file $fileName in Files app");
+ }
+
+ /**
+ * @return Locator
+ */
public static function shareActionForFile($fileName) {
return Locator::forThe()->css(".action-share")->descendantOf(self::rowForFile($fileName))->
describedAs("Share action for file $fileName in Files app");
}
/**
+ * @return Locator
+ */
+ public static function fileActionsMenuButtonForFile($fileName) {
+ return Locator::forThe()->css(".action-menu")->descendantOf(self::rowForFile($fileName))->
+ describedAs("File actions menu button for file $fileName in Files app");
+ }
+
+ /**
+ * @return Locator
+ */
+ public static function fileActionsMenu() {
+ return Locator::forThe()->css(".fileActionsMenu")->
+ describedAs("File actions menu in Files app");
+ }
+
+ /**
+ * @return Locator
+ */
+ public static function viewFileInFolderMenuItem() {
+ return self::fileActionsMenuItemFor("View in folder");
+ }
+
+ /**
+ * @return Locator
+ */
+ private static function fileActionsMenuItemFor($itemText) {
+ return Locator::forThe()->content($itemText)->descendantOf(self::fileActionsMenu())->
+ describedAs($itemText . " item in file actions menu in Files app");
+ }
+
+ /**
+ * @Given I open the :section section
+ */
+ public function iOpenTheSection($section) {
+ $this->actor->find(self::appNavigationSectionItemFor($section), 10)->click();
+ }
+
+ /**
+ * @Given I open the details view for :fileName
+ */
+ public function iOpenTheDetailsViewFor($fileName) {
+ $this->actor->find(self::mainLinkForFile($fileName), 10)->click();
+ }
+
+ /**
+ * @Given I mark :fileName as favorite
+ */
+ public function iMarkAsFavorite($fileName) {
+ $this->actor->find(self::favoriteActionForFile($fileName), 10)->click();
+ }
+
+ /**
* @Given I share the link for :fileName
*/
public function iShareTheLinkFor($fileName) {
@@ -118,6 +252,15 @@ class FilesAppContext implements Context, ActorAwareInterface {
}
/**
+ * @When I view :fileName in folder
+ */
+ public function iViewInFolder($fileName) {
+ $this->actor->find(self::fileActionsMenuButtonForFile($fileName), 10)->click();
+
+ $this->actor->find(self::viewFileInFolderMenuItem(), 2)->click();
+ }
+
+ /**
* @When I protect the shared link with the password :password
*/
public function iProtectTheSharedLinkWithThePassword($password) {
@@ -136,6 +279,53 @@ class FilesAppContext implements Context, ActorAwareInterface {
}
/**
+ * @Then I see that the current section is :section
+ */
+ public function iSeeThatTheCurrentSectionIs($section) {
+ PHPUnit_Framework_Assert::assertEquals($this->actor->find(self::appNavigationCurrentSectionItem(), 10)->getText(), $section);
+ }
+
+ /**
+ * @Then I see that the details view for :section section is open
+ */
+ public function iSeeThatTheDetailsViewForSectionIsOpen($section) {
+ PHPUnit_Framework_Assert::assertTrue(
+ $this->actor->find(self::detailsViewForSection($section), 10)->isVisible());
+
+ $otherSections = self::sections();
+ unset($otherSections[$section]);
+
+ $this->assertDetailsViewForSectionsAreClosed($otherSections);
+ }
+
+ /**
+ * @Then I see that the details view is closed
+ */
+ public function iSeeThatTheDetailsViewIsClosed() {
+ PHPUnit_Framework_Assert::assertNotNull($this->actor->find(self::currentSectionMainView(), 10));
+
+ $this->assertDetailsViewForSectionsAreClosed(self::sections());
+ }
+
+ private function assertDetailsViewForSectionsAreClosed($sections) {
+ foreach ($sections as $section => $id) {
+ try {
+ PHPUnit_Framework_Assert::assertFalse(
+ $this->actor->find(self::detailsViewForSection($section))->isVisible(),
+ "Details view for section $section is open but it should be closed");
+ } catch (NoSuchElementException $exception) {
+ }
+ }
+ }
+
+ /**
+ * @Then I see that :fileName is marked as favorite
+ */
+ public function iSeeThatIsMarkedAsFavorite($fileName) {
+ PHPUnit_Framework_Assert::assertNotNull($this->actor->find(self::favoritedStateIconForFile($fileName), 10));
+ }
+
+ /**
* @Then I see that the working icon for password protect is shown
*/
public function iSeeThatTheWorkingIconForPasswordProtectIsShown() {
diff --git a/tests/lib/App/AppStore/Bundles/BundleBase.php b/tests/lib/App/AppStore/Bundles/BundleBase.php
new file mode 100644
index 00000000000..23af1cda927
--- /dev/null
+++ b/tests/lib/App/AppStore/Bundles/BundleBase.php
@@ -0,0 +1,60 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Test\App\AppStore\Bundles;
+
+use OC\App\AppStore\Bundles\Bundle;
+use OCP\IL10N;
+use Test\TestCase;
+
+abstract class BundleBase extends TestCase {
+ /** @var IL10N|\PHPUnit_Framework_MockObject_MockObject */
+ protected $l10n;
+ /** @var Bundle */
+ protected $bundle;
+ /** @var string */
+ protected $bundleIdentifier;
+ /** @var string */
+ protected $bundleName;
+ /** @var array */
+ protected $bundleAppIds;
+
+ public function setUp() {
+ parent::setUp();
+ $this->l10n = $this->createMock(IL10N::class);
+ $this->l10n->method('t')
+ ->will($this->returnCallback(function ($text, $parameters = []) {
+ return vsprintf($text, $parameters);
+ }));
+ }
+
+ public function testGetIdentifier() {
+ $this->assertSame($this->bundleIdentifier, $this->bundle->getIdentifier());
+ }
+
+ public function testGetName() {
+ $this->assertSame($this->bundleName, $this->bundle->getName());
+ }
+
+ public function testGetAppIdentifiers() {
+ $this->assertSame($this->bundleAppIds, $this->bundle->getAppIdentifiers());
+ }
+}
diff --git a/tests/lib/App/AppStore/Bundles/BundleFetcherTest.php b/tests/lib/App/AppStore/Bundles/BundleFetcherTest.php
new file mode 100644
index 00000000000..71f9820fc72
--- /dev/null
+++ b/tests/lib/App/AppStore/Bundles/BundleFetcherTest.php
@@ -0,0 +1,78 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Test\App\AppStore\Bundles;
+
+use OC\App\AppStore\Bundles\BundleFetcher;
+use OC\App\AppStore\Bundles\CoreBundle;
+use OC\App\AppStore\Bundles\EnterpriseBundle;
+use OC\App\AppStore\Bundles\GroupwareBundle;
+use OC\App\AppStore\Bundles\SocialSharingBundle;
+use OCP\IL10N;
+use Test\TestCase;
+
+class BundleFetcherTest extends TestCase {
+ /** @var IL10N|\PHPUnit_Framework_MockObject_MockObject */
+ private $l10n;
+ /** @var BundleFetcher */
+ private $bundleFetcher;
+
+ public function setUp() {
+ parent::setUp();
+
+ $this->l10n = $this->createMock(IL10N::class);
+
+ $this->bundleFetcher = new BundleFetcher(
+ $this->l10n
+ );
+ }
+
+ public function testGetBundles() {
+ $expected = [
+ new EnterpriseBundle($this->l10n),
+ new GroupwareBundle($this->l10n),
+ new SocialSharingBundle($this->l10n),
+ ];
+ $this->assertEquals($expected, $this->bundleFetcher->getBundles());
+ }
+
+ public function testGetDefaultInstallationBundle() {
+ $expected = [
+ new CoreBundle($this->l10n),
+ ];
+ $this->assertEquals($expected, $this->bundleFetcher->getDefaultInstallationBundle());
+ }
+
+ public function testGetBundleByIdentifier() {
+ $this->assertEquals(new EnterpriseBundle($this->l10n), $this->bundleFetcher->getBundleByIdentifier('EnterpriseBundle'));
+ $this->assertEquals(new CoreBundle($this->l10n), $this->bundleFetcher->getBundleByIdentifier('CoreBundle'));
+ $this->assertEquals(new GroupwareBundle($this->l10n), $this->bundleFetcher->getBundleByIdentifier('GroupwareBundle'));
+ }
+
+ /**
+ * @expectedException \BadMethodCallException
+ * @expectedExceptionMessage Bundle with specified identifier does not exist
+ */
+ public function testGetBundleByIdentifierWithException() {
+ $this->bundleFetcher->getBundleByIdentifier('NotExistingBundle');
+ }
+
+}
diff --git a/tests/lib/App/AppStore/Bundles/CoreBundleTest.php b/tests/lib/App/AppStore/Bundles/CoreBundleTest.php
new file mode 100644
index 00000000000..235e2ec84fe
--- /dev/null
+++ b/tests/lib/App/AppStore/Bundles/CoreBundleTest.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Test\App\AppStore\Bundles;
+
+use OC\App\AppStore\Bundles\CoreBundle;
+
+class CoreBundleTest extends BundleBase {
+ public function setUp() {
+ parent::setUp();
+ $this->bundle = new CoreBundle($this->l10n);
+ $this->bundleIdentifier = 'CoreBundle';
+ $this->bundleName = 'Core bundle';
+ $this->bundleAppIds = [
+ 'bruteforcesettings',
+ ];
+ }
+}
diff --git a/tests/lib/App/AppStore/Bundles/EnterpriseBundleTest.php b/tests/lib/App/AppStore/Bundles/EnterpriseBundleTest.php
new file mode 100644
index 00000000000..e75486b3ed5
--- /dev/null
+++ b/tests/lib/App/AppStore/Bundles/EnterpriseBundleTest.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Test\App\AppStore\Bundles;
+
+use OC\App\AppStore\Bundles\EnterpriseBundle;
+
+class EnterpriseBundleTest extends BundleBase {
+ public function setUp() {
+ parent::setUp();
+ $this->bundle = new EnterpriseBundle($this->l10n);
+ $this->bundleIdentifier = 'EnterpriseBundle';
+ $this->bundleName = 'Enterprise bundle';
+ $this->bundleAppIds = [
+ 'admin_audit',
+ 'user_ldap',
+ 'files_retention',
+ 'files_automatedtagging',
+ 'user_saml',
+ 'files_accesscontrol',
+ ];
+ }
+}
diff --git a/tests/lib/App/AppStore/Bundles/GroupwareBundleTest.php b/tests/lib/App/AppStore/Bundles/GroupwareBundleTest.php
new file mode 100644
index 00000000000..f2f9dcc5ccc
--- /dev/null
+++ b/tests/lib/App/AppStore/Bundles/GroupwareBundleTest.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Test\App\AppStore\Bundles;
+
+use OC\App\AppStore\Bundles\GroupwareBundle;
+
+class GroupwareBundleTest extends BundleBase {
+ public function setUp() {
+ parent::setUp();
+ $this->bundle = new GroupwareBundle($this->l10n);
+ $this->bundleIdentifier = 'GroupwareBundle';
+ $this->bundleName = 'Groupware bundle';
+ $this->bundleAppIds = [
+ 'calendar',
+ 'contacts',
+ 'spreed',
+ ];
+ }
+}
diff --git a/tests/lib/App/AppStore/Bundles/SocialSharingBundleTest.php b/tests/lib/App/AppStore/Bundles/SocialSharingBundleTest.php
new file mode 100644
index 00000000000..02ea0eb6ae5
--- /dev/null
+++ b/tests/lib/App/AppStore/Bundles/SocialSharingBundleTest.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Test\App\AppStore\Bundles;
+
+use OC\App\AppStore\Bundles\SocialSharingBundle;
+
+class SocialSharingBundleTest extends BundleBase {
+ public function setUp() {
+ parent::setUp();
+ $this->bundle = new SocialSharingBundle($this->l10n);
+ $this->bundleIdentifier = 'SocialSharingBundle';
+ $this->bundleName = 'Social sharing bundle';
+ $this->bundleAppIds = [
+ 'socialsharing_twitter',
+ 'socialsharing_googleplus',
+ 'socialsharing_facebook',
+ 'socialsharing_email',
+ 'socialsharing_diaspora',
+ ];
+ }
+}
diff --git a/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php b/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php
index 80c26a9078e..08da360388f 100644
--- a/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php
+++ b/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php
@@ -157,4 +157,99 @@ class ContactsStoreTest extends TestCase {
$this->assertEquals('https://photo', $entries[1]->getAvatar());
}
+ public function testFindOneUser() {
+ $user = $this->createMock(IUser::class);
+ $this->contactsManager->expects($this->once())
+ ->method('search')
+ ->with($this->equalTo('a567'), $this->equalTo(['UID']))
+ ->willReturn([
+ [
+ 'UID' => 123,
+ 'isLocalSystemBook' => false
+ ],
+ [
+ 'UID' => 'a567',
+ 'FN' => 'Darren Roner',
+ 'EMAIL' => [
+ 'darren@roner.au'
+ ],
+ 'isLocalSystemBook' => true
+ ],
+ ]);
+ $user->expects($this->once())
+ ->method('getUID')
+ ->willReturn('user123');
+
+ $entry = $this->contactsStore->findOne($user, 0, 'a567');
+
+ $this->assertEquals([
+ 'darren@roner.au'
+ ], $entry->getEMailAddresses());
+ }
+
+ public function testFindOneEMail() {
+ $user = $this->createMock(IUser::class);
+ $this->contactsManager->expects($this->once())
+ ->method('search')
+ ->with($this->equalTo('darren@roner.au'), $this->equalTo(['EMAIL']))
+ ->willReturn([
+ [
+ 'UID' => 123,
+ 'isLocalSystemBook' => false
+ ],
+ [
+ 'UID' => 'a567',
+ 'FN' => 'Darren Roner',
+ 'EMAIL' => [
+ 'darren@roner.au'
+ ],
+ 'isLocalSystemBook' => false
+ ],
+ ]);
+ $user->expects($this->once())
+ ->method('getUID')
+ ->willReturn('user123');
+
+ $entry = $this->contactsStore->findOne($user, 4, 'darren@roner.au');
+
+ $this->assertEquals([
+ 'darren@roner.au'
+ ], $entry->getEMailAddresses());
+ }
+
+ public function testFindOneNotSupportedType() {
+ $user = $this->createMock(IUser::class);
+
+ $entry = $this->contactsStore->findOne($user, 42, 'darren@roner.au');
+
+ $this->assertEquals(null, $entry);
+ }
+
+ public function testFindOneNoMatches() {
+ $user = $this->createMock(IUser::class);
+ $this->contactsManager->expects($this->once())
+ ->method('search')
+ ->with($this->equalTo('a567'), $this->equalTo(['UID']))
+ ->willReturn([
+ [
+ 'UID' => 123,
+ 'isLocalSystemBook' => false
+ ],
+ [
+ 'UID' => 'a567',
+ 'FN' => 'Darren Roner',
+ 'EMAIL' => [
+ 'darren@roner.au123'
+ ],
+ 'isLocalSystemBook' => false
+ ],
+ ]);
+ $user->expects($this->once())
+ ->method('getUID')
+ ->willReturn('user123');
+
+ $entry = $this->contactsStore->findOne($user, 0, 'a567');
+
+ $this->assertEquals(null, $entry);
+ }
}
diff --git a/tests/lib/Contacts/ContactsMenu/ManagerTest.php b/tests/lib/Contacts/ContactsMenu/ManagerTest.php
index 9c92ec54b9f..783e5590a29 100644
--- a/tests/lib/Contacts/ContactsMenu/ManagerTest.php
+++ b/tests/lib/Contacts/ContactsMenu/ManagerTest.php
@@ -99,4 +99,49 @@ class ManagerTest extends TestCase {
$this->assertEquals($expected, $data);
}
+ public function testFindOne() {
+ $shareTypeFilter = 42;
+ $shareWithFilter = 'foobar';
+
+ $user = $this->createMock(IUser::class);
+ $entry = current($this->generateTestEntries());
+ $provider = $this->createMock(IProvider::class);
+ $this->contactsStore->expects($this->once())
+ ->method('findOne')
+ ->with($user, $shareTypeFilter, $shareWithFilter)
+ ->willReturn($entry);
+ $this->actionProviderStore->expects($this->once())
+ ->method('getProviders')
+ ->with($user)
+ ->willReturn([$provider]);
+ $provider->expects($this->once())
+ ->method('process');
+
+ $data = $this->manager->findOne($user, $shareTypeFilter, $shareWithFilter);
+
+ $this->assertEquals($entry, $data);
+ }
+
+ public function testFindOne404() {
+ $shareTypeFilter = 42;
+ $shareWithFilter = 'foobar';
+
+ $user = $this->createMock(IUser::class);
+ $provider = $this->createMock(IProvider::class);
+ $this->contactsStore->expects($this->once())
+ ->method('findOne')
+ ->with($user, $shareTypeFilter, $shareWithFilter)
+ ->willReturn(null);
+ $this->actionProviderStore->expects($this->never())
+ ->method('getProviders')
+ ->with($user)
+ ->willReturn([$provider]);
+ $provider->expects($this->never())
+ ->method('process');
+
+ $data = $this->manager->findOne($user, $shareTypeFilter, $shareWithFilter);
+
+ $this->assertEquals(null, $data);
+ }
+
}
diff --git a/tests/lib/InstallerTest.php b/tests/lib/InstallerTest.php
index d1923970588..a31c8826bd9 100644
--- a/tests/lib/InstallerTest.php
+++ b/tests/lib/InstallerTest.php
@@ -9,11 +9,13 @@
namespace Test;
+use OC\App\AppStore\Bundles\Bundle;
use OC\App\AppStore\Fetcher\AppFetcher;
use OC\Archive\ZIP;
use OC\Installer;
use OCP\Http\Client\IClient;
use OCP\Http\Client\IClientService;
+use OCP\IConfig;
use OCP\ILogger;
use OCP\ITempManager;
@@ -29,6 +31,8 @@ class InstallerTest extends TestCase {
private $tempManager;
/** @var ILogger|\PHPUnit_Framework_MockObject_MockObject */
private $logger;
+ /** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */
+ private $config;
/** @var Installer */
private $installer;
@@ -40,11 +44,13 @@ class InstallerTest extends TestCase {
$this->clientService = $this->createMock(IClientService::class);
$this->tempManager = $this->createMock(ITempManager::class);
$this->logger = $this->createMock(ILogger::class);
+ $this->config = $this->createMock(IConfig::class);
$this->installer = new Installer(
$this->appFetcher,
$this->clientService,
$this->tempManager,
- $this->logger
+ $this->logger,
+ $this->config
);
$config = \OC::$server->getConfig();
@@ -54,7 +60,8 @@ class InstallerTest extends TestCase {
\OC::$server->getAppFetcher(),
\OC::$server->getHTTPClientService(),
\OC::$server->getTempManager(),
- \OC::$server->getLogger()
+ \OC::$server->getLogger(),
+ $config
);
$installer->removeApp(self::$appid);
}
@@ -64,7 +71,8 @@ class InstallerTest extends TestCase {
\OC::$server->getAppFetcher(),
\OC::$server->getHTTPClientService(),
\OC::$server->getTempManager(),
- \OC::$server->getLogger()
+ \OC::$server->getLogger(),
+ \OC::$server->getConfig()
);
$installer->removeApp(self::$appid);
\OC::$server->getConfig()->setSystemValue('appstoreenabled', $this->appstore);
@@ -86,7 +94,8 @@ class InstallerTest extends TestCase {
\OC::$server->getAppFetcher(),
\OC::$server->getHTTPClientService(),
\OC::$server->getTempManager(),
- \OC::$server->getLogger()
+ \OC::$server->getLogger(),
+ \OC::$server->getConfig()
);
$installer->installApp(self::$appid);
$isInstalled = Installer::isInstalled(self::$appid);
diff --git a/tests/lib/Repair/NC12/InstallCoreBundleTest.php b/tests/lib/Repair/NC12/InstallCoreBundleTest.php
new file mode 100644
index 00000000000..3a72934df86
--- /dev/null
+++ b/tests/lib/Repair/NC12/InstallCoreBundleTest.php
@@ -0,0 +1,144 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @author Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Test\Repair\NC12;
+
+use OC\App\AppStore\Bundles\Bundle;
+use OC\App\AppStore\Bundles\BundleFetcher;
+use OC\Installer;
+use OC\Repair\NC12\InstallCoreBundle;
+use OCP\IConfig;
+use OCP\Migration\IOutput;
+use Test\TestCase;
+
+
+class InstallCoreBundleTest extends TestCase {
+ /** @var BundleFetcher|\PHPUnit_Framework_MockObject_MockObject */
+ private $bundleFetcher;
+ /** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */
+ private $config;
+ /** @var Installer|\PHPUnit_Framework_MockObject_MockObject */
+ private $installer;
+ /** @var InstallCoreBundle */
+ private $installCoreBundle;
+
+ public function setUp() {
+ parent::setUp();
+ $this->bundleFetcher = $this->createMock(BundleFetcher::class);
+ $this->config = $this->createMock(IConfig::class);
+ $this->installer = $this->createMock(Installer::class);
+
+ $this->installCoreBundle = new InstallCoreBundle(
+ $this->bundleFetcher,
+ $this->config,
+ $this->installer
+ );
+ }
+
+ public function testGetName() {
+ $this->assertSame('Install new core bundle components', $this->installCoreBundle->getName());
+ }
+
+ public function testRunOlder() {
+ $this->config
+ ->expects($this->once())
+ ->method('getSystemValue')
+ ->with('version', '0.0.0')
+ ->willReturn('12.0.0.15');
+ $this->bundleFetcher
+ ->expects($this->never())
+ ->method('getDefaultInstallationBundle');
+ /** @var IOutput|\PHPUnit_Framework_MockObject_MockObject $output */
+ $output = $this->createMock(IOutput::class);
+ $output
+ ->expects($this->never())
+ ->method('info');
+ $output
+ ->expects($this->never())
+ ->method('warning');
+
+ $this->installCoreBundle->run($output);
+ }
+
+ public function testRunWithException() {
+ $this->config
+ ->expects($this->once())
+ ->method('getSystemValue')
+ ->with('version', '0.0.0')
+ ->willReturn('12.0.0.14');
+ $bundle = $this->createMock(Bundle::class);
+ $this->bundleFetcher
+ ->expects($this->once())
+ ->method('getDefaultInstallationBundle')
+ ->willReturn([
+ $bundle,
+ ]);
+ $this->installer
+ ->expects($this->once())
+ ->method('installAppBundle')
+ ->with($bundle)
+ ->willThrowException(new \Exception('ExceptionText'));
+ /** @var IOutput|\PHPUnit_Framework_MockObject_MockObject $output */
+ $output = $this->createMock(IOutput::class);
+ $output
+ ->expects($this->never())
+ ->method('info');
+ $output
+ ->expects($this->once())
+ ->method('warning')
+ ->with('Could not install core app bundle: ExceptionText');
+
+ $this->installCoreBundle->run($output);
+ }
+
+ public function testRun() {
+ $this->config
+ ->expects($this->once())
+ ->method('getSystemValue')
+ ->with('version', '0.0.0')
+ ->willReturn('12.0.0.14');
+ $bundle = $this->createMock(Bundle::class);
+ $this->bundleFetcher
+ ->expects($this->once())
+ ->method('getDefaultInstallationBundle')
+ ->willReturn([
+ $bundle,
+ ]);
+ $this->installer
+ ->expects($this->once())
+ ->method('installAppBundle')
+ ->with($bundle);
+ /** @var IOutput|\PHPUnit_Framework_MockObject_MockObject $output */
+ $output = $this->createMock(IOutput::class);
+ $output
+ ->expects($this->once())
+ ->method('info')
+ ->with('Successfully installed core app bundle.');
+ $output
+ ->expects($this->never())
+ ->method('warning');
+
+ $this->installCoreBundle->run($output);
+ }
+
+}
diff --git a/version.php b/version.php
index 0d1d327cb7f..011c693d7b3 100644
--- a/version.php
+++ b/version.php
@@ -26,7 +26,7 @@
// between betas, final and RCs. This is _not_ the public version number. Reset minor/patchlevel
// when updating major/minor version number.
-$OC_Version = array(12, 0, 0, 14);
+$OC_Version = array(12, 0, 0, 15);
// The human readable string
$OC_VersionString = '12.0 alpha';