aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/comments/css/comments.css27
-rw-r--r--apps/comments/js/commentmodel.js13
-rw-r--r--apps/comments/js/commentstabview.js202
-rw-r--r--apps/comments/tests/js/commentstabviewSpec.js134
-rw-r--r--apps/dav/appinfo/register_command.php2
-rw-r--r--apps/dav/command/createcalendar.php15
-rw-r--r--apps/dav/lib/caldav/caldavbackend.php90
-rw-r--r--apps/dav/lib/caldav/calendar.php102
-rw-r--r--apps/dav/lib/caldav/calendarhome.php78
-rw-r--r--apps/dav/lib/caldav/calendarroot.php10
-rw-r--r--apps/dav/lib/carddav/addressbook.php29
-rw-r--r--apps/dav/lib/carddav/carddavbackend.php29
-rw-r--r--apps/dav/lib/dav/sharing/backend.php33
-rw-r--r--apps/dav/lib/dav/sharing/plugin.php27
-rw-r--r--apps/dav/lib/dav/sharing/xml/invite.php149
-rw-r--r--apps/dav/lib/rootcollection.php4
-rw-r--r--apps/dav/tests/travis/caldav/install.sh1
-rw-r--r--apps/dav/tests/travis/caldav/script.sh4
-rw-r--r--apps/dav/tests/travis/caldavtest/data/Resource/CalDAV/sharing/calendars/read-write/1.xml8
-rw-r--r--apps/dav/tests/travis/caldavtest/data/Resource/CalDAV/sharing/calendars/read-write/4.xml8
-rw-r--r--apps/dav/tests/travis/caldavtest/data/Resource/CalDAV/sharing/calendars/read-write/5.ics29
-rw-r--r--apps/dav/tests/travis/caldavtest/data/Resource/CalDAV/sharing/calendars/read-write/5.xml6
-rw-r--r--apps/dav/tests/travis/caldavtest/data/Resource/CalDAV/sharing/calendars/read-write/6.ics29
-rw-r--r--apps/dav/tests/travis/caldavtest/data/Resource/CalDAV/sharing/calendars/read-write/7.ics29
-rw-r--r--apps/dav/tests/travis/caldavtest/data/Resource/CalDAV/sharing/calendars/read-write/8.ics29
-rw-r--r--apps/dav/tests/travis/caldavtest/data/Resource/CalDAV/sharing/calendars/read-write/9.ics29
-rw-r--r--apps/dav/tests/travis/caldavtest/tests/CalDAV/sharing-calendars.xml180
-rw-r--r--apps/dav/tests/travis/caldavtest/tests/CardDAV/sharing-addressbooks.xml65
-rw-r--r--apps/dav/tests/unit/caldav/caldavbackendtest.php132
-rw-r--r--apps/dav/tests/unit/caldav/calendartest.php64
-rw-r--r--apps/dav/tests/unit/carddav/addressbooktest.php64
-rw-r--r--apps/dav/tests/unit/carddav/carddavbackendtest.php33
-rw-r--r--apps/files_external/l10n/it.js1
-rw-r--r--apps/files_external/l10n/it.json1
-rw-r--r--apps/files_external/l10n/pt_PT.js1
-rw-r--r--apps/files_external/l10n/pt_PT.json1
-rw-r--r--core/css/systemtags.css8
-rw-r--r--lib/private/comments/comment.php4
-rw-r--r--lib/private/comments/manager.php6
-rw-r--r--lib/private/user/manager.php4
-rw-r--r--lib/private/user/user.php2
-rw-r--r--lib/public/comments/icomment.php4
-rw-r--r--lib/public/comments/icommentsmanager.php10
-rw-r--r--tests/lib/comments/comment.php8
-rw-r--r--tests/lib/comments/manager.php56
45 files changed, 1541 insertions, 219 deletions
diff --git a/apps/comments/css/comments.css b/apps/comments/css/comments.css
index 5e247aaeb71..b86ed38efe7 100644
--- a/apps/comments/css/comments.css
+++ b/apps/comments/css/comments.css
@@ -50,6 +50,33 @@
right: 0;
}
+#commentsTabView .comment .action {
+ opacity: 0;
+ vertical-align: middle;
+ display: inline-block;
+}
+
+#commentsTabView .comment:hover .action {
+ opacity: 0.3;
+}
+
+#commentsTabView .comment .action:hover {
+ opacity: 1;
+}
+
+#commentsTabView .comment .action.delete {
+ position: absolute;
+ right: 0;
+}
+
+#commentsTabView .comment.disabled {
+ opacity: 0.3;
+}
+
+#commentsTabView .comment.disabled .action {
+ visibility: hidden;
+}
+
.app-files .action-comment>img {
margin-right: 5px;
}
diff --git a/apps/comments/js/commentmodel.js b/apps/comments/js/commentmodel.js
index ba04fd61de3..89492707b61 100644
--- a/apps/comments/js/commentmodel.js
+++ b/apps/comments/js/commentmodel.js
@@ -39,8 +39,17 @@
},
parse: function(data) {
- data.isUnread = (data.isUnread === 'true');
- return data;
+ return {
+ id: data.id,
+ message: data.message,
+ actorType: data.actorType,
+ actorId: data.actorId,
+ actorDisplayName: data.actorDisplayName,
+ creationDateTime: data.creationDateTime,
+ objectType: data.objectType,
+ objectId: data.objectId,
+ isUnread: (data.isUnread === 'true')
+ };
}
});
diff --git a/apps/comments/js/commentstabview.js b/apps/comments/js/commentstabview.js
index 188d8c5943c..2c5e9414751 100644
--- a/apps/comments/js/commentstabview.js
+++ b/apps/comments/js/commentstabview.js
@@ -8,27 +8,37 @@
*
*/
+/* global Handlebars */
+
(function(OC, OCA) {
var TEMPLATE =
- '<div class="newCommentRow comment">' +
+ '<ul class="comments">' +
+ '</ul>' +
+ '<div class="empty hidden">{{emptyResultLabel}}</div>' +
+ '<input type="button" class="showMore hidden" value="{{moreLabel}}"' +
+ ' name="show-more" id="show-more" />' +
+ '<div class="loading hidden" style="height: 50px"></div>';
+
+ var EDIT_COMMENT_TEMPLATE =
+ '<div class="newCommentRow comment" data-id="{{id}}">' +
' <div class="authorRow">' +
' {{#if avatarEnabled}}' +
- ' <div class="avatar" data-username="{{userId}}"></div>' +
+ ' <div class="avatar" data-username="{{actorId}}"></div>' +
' {{/if}}' +
- ' <div class="author">{{userDisplayName}}</div>' +
+ ' <div class="author">{{actorDisplayName}}</div>' +
+ '{{#if isEditMode}}' +
+ ' <a href="#" class="action delete icon icon-delete has-tooltip" title="{{deleteTooltip}}"></a>' +
+ '{{/if}}' +
' </div>' +
' <form class="newCommentForm">' +
- ' <textarea class="message" placeholder="{{newMessagePlaceholder}}"></textarea>' +
+ ' <textarea class="message" placeholder="{{newMessagePlaceholder}}">{{{message}}}</textarea>' +
' <input class="submit" type="submit" value="{{submitText}}" />' +
+ '{{#if isEditMode}}' +
+ ' <input class="cancel" type="button" value="{{cancelText}}" />' +
+ '{{/if}}' +
' <div class="submitLoading icon-loading-small hidden"></div>'+
' </form>' +
- ' <ul class="comments">' +
- ' </ul>' +
- '</div>' +
- '<div class="empty hidden">{{emptyResultLabel}}</div>' +
- '<input type="button" class="showMore hidden" value="{{moreLabel}}"' +
- ' name="show-more" id="show-more" />' +
- '<div class="loading hidden" style="height: 50px"></div>';
+ '</div>';
var COMMENT_TEMPLATE =
'<li class="comment{{#if isUnread}} unread{{/if}}" data-id="{{id}}">' +
@@ -37,6 +47,9 @@
' <div class="avatar" data-username="{{actorId}}"> </div>' +
' {{/if}}' +
' <div class="author">{{actorDisplayName}}</div>' +
+ '{{#if isUserAuthor}}' +
+ ' <a href="#" class="action edit icon icon-rename has-tooltip" title="{{editTooltip}}"></a>' +
+ '{{/if}}' +
' <div class="date has-tooltip" title="{{altDate}}">{{date}}</div>' +
' </div>' +
' <div class="message">{{{formattedMessage}}}</div>' +
@@ -52,7 +65,10 @@
events: {
'submit .newCommentForm': '_onSubmitComment',
- 'click .showMore': '_onClickShowMore'
+ 'click .showMore': '_onClickShowMore',
+ 'click .action.edit': '_onClickEditComment',
+ 'click .action.delete': '_onClickDeleteComment',
+ 'click .cancel': '_onClickCloseComment'
},
initialize: function() {
@@ -65,7 +81,6 @@
this._avatarsEnabled = !!OC.config.enable_avatars;
// TODO: error handling
- _.bindAll(this, '_onSubmitComment');
},
template: function(params) {
@@ -75,10 +90,24 @@
var currentUser = OC.getCurrentUser();
return this._template(_.extend({
avatarEnabled: this._avatarsEnabled,
- userId: currentUser.uid,
- userDisplayName: currentUser.displayName,
+ actorId: currentUser.uid,
+ actorDisplayName: currentUser.displayName
+ }, params));
+ },
+
+ editCommentTemplate: function(params) {
+ if (!this._editCommentTemplate) {
+ this._editCommentTemplate = Handlebars.compile(EDIT_COMMENT_TEMPLATE);
+ }
+ var currentUser = OC.getCurrentUser();
+ return this._editCommentTemplate(_.extend({
+ avatarEnabled: this._avatarsEnabled,
+ actorId: currentUser.uid,
+ actorDisplayName: currentUser.displayName,
newMessagePlaceholder: t('comments', 'Type in a new comment...'),
- submitText: t('comments', 'Post')
+ deleteTooltip: t('comments', 'Delete comment'),
+ submitText: t('comments', 'Post'),
+ cancelText: t('comments', 'Cancel')
}, params));
},
@@ -87,7 +116,9 @@
this._commentTemplate = Handlebars.compile(COMMENT_TEMPLATE);
}
return this._commentTemplate(_.extend({
- avatarEnabled: this._avatarsEnabled
+ avatarEnabled: this._avatarsEnabled,
+ editTooltip: t('comments', 'Edit comment'),
+ isUserAuthor: OC.getCurrentUser().uid === params.actorId
}, params));
},
@@ -115,6 +146,7 @@
emptyResultLabel: t('comments', 'No other comments available'),
moreLabel: t('comments', 'More comments...')
}));
+ this.$el.find('.comments').before(this.editCommentTemplate({}));
this.$el.find('.has-tooltip').tooltip();
this.$container = this.$el.find('ul.comments');
this.$el.find('.avatar').avatar(OC.getCurrentUser().uid, 28);
@@ -136,9 +168,11 @@
this.$el.find('.loading').toggleClass('hidden', !state);
},
- _onRequest: function() {
- this._toggleLoading(true);
- this.$el.find('.showMore').addClass('hidden');
+ _onRequest: function(type) {
+ if (type === 'REPORT') {
+ this._toggleLoading(true);
+ this.$el.find('.showMore').addClass('hidden');
+ }
},
_onEndRequest: function(type) {
@@ -203,13 +237,69 @@
this.collection.fetchNext();
},
+ _onClickEditComment: function(ev) {
+ ev.preventDefault();
+ var $comment = $(ev.target).closest('.comment');
+ var commentId = $comment.data('id');
+ var commentToEdit = this.collection.get(commentId);
+ var $formRow = $(this.editCommentTemplate(_.extend({
+ isEditMode: true,
+ submitText: t('comments', 'Save')
+ }, commentToEdit.attributes)));
+
+ $comment.addClass('hidden');
+ // spawn form
+ $comment.after($formRow);
+ $formRow.data('commentEl', $comment);
+
+ // copy avatar element from original to avoid flickering
+ $formRow.find('.avatar').replaceWith($comment.find('.avatar').clone());
+ $formRow.find('.has-tooltip').tooltip();
+
+ return false;
+ },
+
+ _onClickCloseComment: function(ev) {
+ ev.preventDefault();
+ var $row = $(ev.target).closest('.comment');
+ $row.data('commentEl').removeClass('hidden');
+ $row.remove();
+ return false;
+ },
+
+ _onClickDeleteComment: function(ev) {
+ ev.preventDefault();
+ var $comment = $(ev.target).closest('.comment');
+ var commentId = $comment.data('id');
+ var $loading = $comment.find('.submitLoading');
+
+ $comment.addClass('disabled');
+ $loading.removeClass('hidden');
+ this.collection.get(commentId).destroy({
+ success: function() {
+ $comment.data('commentEl').remove();
+ $comment.remove();
+ },
+ error: function(msg) {
+ $loading.addClass('hidden');
+ $comment.removeClass('disabled');
+ OC.Notification.showTemporary(msg);
+ }
+ });
+
+
+ return false;
+ },
+
_onClickShowMore: function(ev) {
ev.preventDefault();
this.nextPage();
},
_onSubmitComment: function(e) {
+ var self = this;
var $form = $(e.target);
+ var commentId = $form.closest('.comment').data('id');
var currentUser = OC.getCurrentUser();
var $submit = $form.find('.submit');
var $loading = $form.find('.submitLoading');
@@ -225,28 +315,56 @@
$submit.addClass('hidden');
$loading.removeClass('hidden');
- this.collection.create({
- actorId: currentUser.uid,
- actorDisplayName: currentUser.displayName,
- actorType: 'users',
- verb: 'comment',
- message: $textArea.val(),
- creationDateTime: (new Date()).toUTCString()
- }, {
- at: 0,
- success: function() {
- $submit.removeClass('hidden');
- $loading.addClass('hidden');
- $textArea.val('').prop('disabled', false);
- },
- error: function(msg) {
- $submit.removeClass('hidden');
- $loading.addClass('hidden');
- $textArea.prop('disabled', false);
-
- OC.Notification.showTemporary(msg);
- }
- });
+ if (commentId) {
+ // edit mode
+ var comment = this.collection.get(commentId);
+ comment.save({
+ message: $textArea.val()
+ }, {
+ success: function(model) {
+ var $row = $form.closest('.comment');
+ $submit.removeClass('hidden');
+ $loading.addClass('hidden');
+ $row.data('commentEl')
+ .removeClass('hidden')
+ .find('.message')
+ .html(self._formatMessage(model.get('message')));
+ $row.remove();
+ },
+ error: function(msg) {
+ $submit.removeClass('hidden');
+ $loading.addClass('hidden');
+ $textArea.prop('disabled', false);
+
+ OC.Notification.showTemporary(msg);
+ }
+ });
+ } else {
+ this.collection.create({
+ actorId: currentUser.uid,
+ actorDisplayName: currentUser.displayName,
+ actorType: 'users',
+ verb: 'comment',
+ message: $textArea.val(),
+ creationDateTime: (new Date()).toUTCString()
+ }, {
+ at: 0,
+ // wait for real creation before adding
+ wait: true,
+ success: function() {
+ $submit.removeClass('hidden');
+ $loading.addClass('hidden');
+ $textArea.val('').prop('disabled', false);
+ },
+ error: function(msg) {
+ $submit.removeClass('hidden');
+ $loading.addClass('hidden');
+ $textArea.prop('disabled', false);
+
+ OC.Notification.showTemporary(msg);
+ }
+ });
+ }
return false;
}
diff --git a/apps/comments/tests/js/commentstabviewSpec.js b/apps/comments/tests/js/commentstabviewSpec.js
index 432fa5ddc4c..4c3d38290ba 100644
--- a/apps/comments/tests/js/commentstabviewSpec.js
+++ b/apps/comments/tests/js/commentstabviewSpec.js
@@ -195,6 +195,140 @@ describe('OCA.Comments.CommentsTabView tests', function() {
});
});
+ describe('editing comments', function() {
+ var saveStub;
+ var currentUserStub;
+
+ beforeEach(function() {
+ saveStub = sinon.stub(OCA.Comments.CommentModel.prototype, 'save');
+ currentUserStub = sinon.stub(OC, 'getCurrentUser');
+ currentUserStub.returns({
+ uid: 'testuser',
+ displayName: 'Test User'
+ });
+ view.collection.add({
+ id: 1,
+ actorId: 'testuser',
+ actorDisplayName: 'Test User',
+ actorType: 'users',
+ verb: 'comment',
+ message: 'New message',
+ creationDateTime: new Date(Date.UTC(2016, 1, 3, 10, 5, 9)).toUTCString()
+ });
+ view.collection.add({
+ id: 2,
+ actorId: 'anotheruser',
+ actorDisplayName: 'Another User',
+ actorType: 'users',
+ verb: 'comment',
+ message: 'New message from another user',
+ creationDateTime: new Date(Date.UTC(2016, 1, 3, 10, 5, 9)).toUTCString()
+ });
+ });
+ afterEach(function() {
+ saveStub.restore();
+ currentUserStub.restore();
+ });
+
+ it('shows edit link for owner comments', function() {
+ var $comment = view.$el.find('.comment[data-id=1]');
+ expect($comment.length).toEqual(1);
+ expect($comment.find('.action.edit').length).toEqual(1);
+ });
+
+ it('does not show edit link for other user\'s comments', function() {
+ var $comment = view.$el.find('.comment[data-id=2]');
+ expect($comment.length).toEqual(1);
+ expect($comment.find('.action.edit').length).toEqual(0);
+ });
+
+ it('shows edit form when clicking edit', function() {
+ var $comment = view.$el.find('.comment[data-id=1]');
+ $comment.find('.action.edit').click();
+
+ expect($comment.hasClass('hidden')).toEqual(true);
+ var $formRow = view.$el.find('.newCommentRow.comment[data-id=1]');
+ expect($formRow.length).toEqual(1);
+ });
+
+ it('saves message and updates comment item when clicking save', function() {
+ var $comment = view.$el.find('.comment[data-id=1]');
+ $comment.find('.action.edit').click();
+
+ var $formRow = view.$el.find('.newCommentRow.comment[data-id=1]');
+ expect($formRow.length).toEqual(1);
+
+ $formRow.find('textarea').val('modified\nmessage');
+ $formRow.find('form').submit();
+
+ expect(saveStub.calledOnce).toEqual(true);
+ expect(saveStub.lastCall.args[0]).toEqual({
+ message: 'modified\nmessage'
+ });
+
+ var model = view.collection.get(1);
+ // simulate the fact that save sets the attribute
+ model.set('message', 'modified\nmessage');
+ saveStub.yieldTo('success', model);
+
+ // original comment element is visible again
+ expect($comment.hasClass('hidden')).toEqual(false);
+ // and its message was updated
+ expect($comment.find('.message').html()).toEqual('modified<br>message');
+
+ // form row is gone
+ $formRow = view.$el.find('.newCommentRow.comment[data-id=1]');
+ expect($formRow.length).toEqual(0);
+ });
+
+ it('restores original comment when cancelling', function() {
+ var $comment = view.$el.find('.comment[data-id=1]');
+ $comment.find('.action.edit').click();
+
+ var $formRow = view.$el.find('.newCommentRow.comment[data-id=1]');
+ expect($formRow.length).toEqual(1);
+
+ $formRow.find('textarea').val('modified\nmessage');
+ $formRow.find('.cancel').click();
+
+ expect(saveStub.notCalled).toEqual(true);
+
+ // original comment element is visible again
+ expect($comment.hasClass('hidden')).toEqual(false);
+ // and its message was not updated
+ expect($comment.find('.message').html()).toEqual('New message');
+
+ // form row is gone
+ $formRow = view.$el.find('.newCommentRow.comment[data-id=1]');
+ expect($formRow.length).toEqual(0);
+ });
+
+ it('destroys model when clicking delete', function() {
+ var destroyStub = sinon.stub(OCA.Comments.CommentModel.prototype, 'destroy');
+ var $comment = view.$el.find('.comment[data-id=1]');
+ $comment.find('.action.edit').click();
+
+ var $formRow = view.$el.find('.newCommentRow.comment[data-id=1]');
+ expect($formRow.length).toEqual(1);
+
+ $formRow.find('.delete').click();
+
+ expect(destroyStub.calledOnce).toEqual(true);
+ expect(destroyStub.thisValues[0].id).toEqual(1);
+
+ destroyStub.yieldTo('success');
+
+ // original comment element is gone
+ $comment = view.$el.find('.comment[data-id=1]');
+ expect($comment.length).toEqual(0);
+
+ // form row is gone
+ $formRow = view.$el.find('.newCommentRow.comment[data-id=1]');
+ expect($formRow.length).toEqual(0);
+
+ destroyStub.restore();
+ });
+ });
describe('read marker', function() {
var updateMarkerStub;
diff --git a/apps/dav/appinfo/register_command.php b/apps/dav/appinfo/register_command.php
index e8fea5daf23..e8ca370f84f 100644
--- a/apps/dav/appinfo/register_command.php
+++ b/apps/dav/appinfo/register_command.php
@@ -36,7 +36,7 @@ $app = new Application();
/** @var Symfony\Component\Console\Application $application */
$application->add(new CreateAddressBook($userManager, $groupManager, $dbConnection, $logger));
-$application->add(new CreateCalendar($userManager, $dbConnection));
+$application->add(new CreateCalendar($userManager, $groupManager, $dbConnection));
$application->add(new SyncSystemAddressBook($app->getSyncService()));
// the occ tool is *for now* only available in debug mode for developers to test
diff --git a/apps/dav/command/createcalendar.php b/apps/dav/command/createcalendar.php
index 34bc061c45b..d7f82dd0e52 100644
--- a/apps/dav/command/createcalendar.php
+++ b/apps/dav/command/createcalendar.php
@@ -21,7 +21,9 @@
namespace OCA\DAV\Command;
use OCA\DAV\CalDAV\CalDavBackend;
+use OCA\DAV\Connector\Sabre\Principal;
use OCP\IDBConnection;
+use OCP\IGroupManager;
use OCP\IUserManager;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
@@ -33,6 +35,9 @@ class CreateCalendar extends Command {
/** @var IUserManager */
protected $userManager;
+ /** @var IGroupManager $groupManager */
+ private $groupManager;
+
/** @var \OCP\IDBConnection */
protected $dbConnection;
@@ -40,9 +45,10 @@ class CreateCalendar extends Command {
* @param IUserManager $userManager
* @param IDBConnection $dbConnection
*/
- function __construct(IUserManager $userManager, IDBConnection $dbConnection) {
+ function __construct(IUserManager $userManager, IGroupManager $groupManager, IDBConnection $dbConnection) {
parent::__construct();
$this->userManager = $userManager;
+ $this->groupManager = $groupManager;
$this->dbConnection = $dbConnection;
}
@@ -63,8 +69,13 @@ class CreateCalendar extends Command {
if (!$this->userManager->userExists($user)) {
throw new \InvalidArgumentException("User <$user> in unknown.");
}
+ $principalBackend = new Principal(
+ $this->userManager,
+ $this->groupManager
+ );
+
$name = $input->getArgument('name');
- $caldav = new CalDavBackend($this->dbConnection);
+ $caldav = new CalDavBackend($this->dbConnection, $principalBackend);
$caldav->createCalendar("principals/users/$user", $name, []);
}
}
diff --git a/apps/dav/lib/caldav/caldavbackend.php b/apps/dav/lib/caldav/caldavbackend.php
index 0b70b37967f..3aa493e5087 100644
--- a/apps/dav/lib/caldav/caldavbackend.php
+++ b/apps/dav/lib/caldav/caldavbackend.php
@@ -23,6 +23,8 @@
namespace OCA\DAV\CalDAV;
use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCA\DAV\Connector\Sabre\Principal;
+use OCA\DAV\DAV\Sharing\Backend;
use Sabre\CalDAV\Backend\AbstractBackend;
use Sabre\CalDAV\Backend\SchedulingSupport;
use Sabre\CalDAV\Backend\SubscriptionSupport;
@@ -32,6 +34,7 @@ use Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp;
use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet;
use Sabre\DAV;
use Sabre\DAV\Exception\Forbidden;
+use Sabre\HTTP\URLUtil;
use Sabre\VObject\DateTimeParser;
use Sabre\VObject\Reader;
use Sabre\VObject\RecurrenceIterator;
@@ -86,8 +89,24 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
'{http://calendarserver.org/ns/}subscribed-strip-attachments' => 'stripattachments',
];
- public function __construct(\OCP\IDBConnection $db) {
+ /** @var \OCP\IDBConnection */
+ private $db;
+
+ /** @var Backend */
+ private $sharingBackend;
+
+ /** @var Principal */
+ private $principalBackend;
+
+ /**
+ * CalDavBackend constructor.
+ *
+ * @param \OCP\IDBConnection $db
+ */
+ public function __construct(\OCP\IDBConnection $db, Principal $principalBackend) {
$this->db = $db;
+ $this->principalBackend = $principalBackend;
+ $this->sharingBackend = new Backend($this->db, 'calendar');
}
/**
@@ -153,10 +172,60 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$calendar[$xmlName] = $row[$dbName];
}
- $calendars[] = $calendar;
+ $calendars[$calendar['id']] = $calendar;
+ }
+
+ $stmt->closeCursor();
+
+ // query for shared calendars
+ $principals = $this->principalBackend->getGroupMembership($principalUri);
+ $principals[]= $principalUri;
+
+ $fields = array_values($this->propertyMap);
+ $fields[] = 'a.id';
+ $fields[] = 'a.uri';
+ $fields[] = 'a.synctoken';
+ $fields[] = 'a.components';
+ $fields[] = 'a.principaluri';
+ $fields[] = 'a.transparent';
+ $query = $this->db->getQueryBuilder();
+ $result = $query->select($fields)
+ ->from('dav_shares', 's')
+ ->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
+ ->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
+ ->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
+ ->setParameter('type', 'calendar')
+ ->setParameter('principaluri', $principals, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY)
+ ->execute();
+
+ while($row = $result->fetch()) {
+ list(, $name) = URLUtil::splitPath($row['principaluri']);
+ $uri = $row['uri'] . '_shared_by_' . $name;
+ $row['displayname'] = $row['displayname'] . "($name)";
+ $components = [];
+ if ($row['components']) {
+ $components = explode(',',$row['components']);
+ }
+ $calendar = [
+ 'id' => $row['id'],
+ 'uri' => $uri,
+ 'principaluri' => $principalUri,
+ '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
+ '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
+ '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
+ '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
+ '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'],
+ ];
+
+ foreach($this->propertyMap as $xmlName=>$dbName) {
+ $calendar[$xmlName] = $row[$dbName];
+ }
+
+ $calendars[$calendar['id']] = $calendar;
}
+ $result->closeCursor();
- return $calendars;
+ return array_values($calendars);
}
/**
@@ -271,6 +340,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarchanges` WHERE `calendarid` = ?');
$stmt->execute([$calendarId]);
+
+ $this->sharingBackend->deleteAllShares($calendarId);
}
/**
@@ -1173,4 +1244,17 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
return $cardData;
}
+
+ public function updateShares($shareable, $add, $remove) {
+ $this->sharingBackend->updateShares($shareable, $add, $remove);
+ }
+
+ public function getShares($resourceId) {
+ return $this->sharingBackend->getShares($resourceId);
+ }
+
+ public function applyShareAcl($addressBookId, $acl) {
+ return $this->sharingBackend->applyShareAcl($addressBookId, $acl);
+ }
+
}
diff --git a/apps/dav/lib/caldav/calendar.php b/apps/dav/lib/caldav/calendar.php
new file mode 100644
index 00000000000..8ed5b6563d0
--- /dev/null
+++ b/apps/dav/lib/caldav/calendar.php
@@ -0,0 +1,102 @@
+<?php
+
+namespace OCA\DAV\CalDAV;
+
+use OCA\DAV\DAV\Sharing\IShareable;
+use Sabre\DAV\Exception\Forbidden;
+
+class Calendar extends \Sabre\CalDAV\Calendar implements IShareable {
+
+ /**
+ * Updates the list of shares.
+ *
+ * The first array is a list of people that are to be added to the
+ * resource.
+ *
+ * Every element in the add array has the following properties:
+ * * href - A url. Usually a mailto: address
+ * * commonName - Usually a first and last name, or false
+ * * summary - A description of the share, can also be false
+ * * readOnly - A boolean value
+ *
+ * Every element in the remove array is just the address string.
+ *
+ * @param array $add
+ * @param array $remove
+ * @return void
+ */
+ function updateShares(array $add, array $remove) {
+ /** @var CalDavBackend $calDavBackend */
+ $calDavBackend = $this->caldavBackend;
+ $calDavBackend->updateShares($this, $add, $remove);
+ }
+
+ /**
+ * Returns the list of people whom this resource is shared with.
+ *
+ * Every element in this array should have the following properties:
+ * * href - Often a mailto: address
+ * * commonName - Optional, for example a first + last name
+ * * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
+ * * readOnly - boolean
+ * * summary - Optional, a description for the share
+ *
+ * @return array
+ */
+ function getShares() {
+ /** @var CalDavBackend $calDavBackend */
+ $calDavBackend = $this->caldavBackend;
+ return $calDavBackend->getShares($this->getResourceId());
+ }
+
+ /**
+ * @return int
+ */
+ public function getResourceId() {
+ return $this->calendarInfo['id'];
+ }
+
+ function getACL() {
+ $acl = parent::getACL();
+
+ /** @var CalDavBackend $calDavBackend */
+ $calDavBackend = $this->caldavBackend;
+ return $calDavBackend->applyShareAcl($this->getResourceId(), $acl);
+ }
+
+ function getChildACL() {
+ $acl = parent::getChildACL();
+
+ /** @var CalDavBackend $calDavBackend */
+ $calDavBackend = $this->caldavBackend;
+ return $calDavBackend->applyShareAcl($this->getResourceId(), $acl);
+ }
+
+ function getOwner() {
+ if (isset($this->calendarInfo['{http://owncloud.org/ns}owner-principal'])) {
+ return $this->calendarInfo['{http://owncloud.org/ns}owner-principal'];
+ }
+ return parent::getOwner();
+ }
+
+ function delete() {
+ if (isset($this->calendarInfo['{http://owncloud.org/ns}owner-principal'])) {
+ $principal = 'principal:' . parent::getOwner();
+ $shares = $this->getShares();
+ $shares = array_filter($shares, function($share) use ($principal){
+ return $share['href'] === $principal;
+ });
+ if (empty($shares)) {
+ throw new Forbidden();
+ }
+
+ /** @var CalDavBackend $calDavBackend */
+ $calDavBackend = $this->caldavBackend;
+ $calDavBackend->updateShares($this, [], [
+ 'href' => $principal
+ ]);
+ return;
+ }
+ parent::delete();
+ }
+}
diff --git a/apps/dav/lib/caldav/calendarhome.php b/apps/dav/lib/caldav/calendarhome.php
new file mode 100644
index 00000000000..7f98dfb94e0
--- /dev/null
+++ b/apps/dav/lib/caldav/calendarhome.php
@@ -0,0 +1,78 @@
+<?php
+
+namespace OCA\DAV\CalDAV;
+
+use Sabre\CalDAV\Backend\NotificationSupport;
+use Sabre\CalDAV\Backend\SchedulingSupport;
+use Sabre\CalDAV\Backend\SubscriptionSupport;
+use Sabre\CalDAV\Schedule\Inbox;
+use Sabre\CalDAV\Schedule\Outbox;
+use Sabre\CalDAV\Subscriptions\Subscription;
+use Sabre\DAV\Exception\NotFound;
+
+class CalendarHome extends \Sabre\CalDAV\CalendarHome {
+
+ /**
+ * @inheritdoc
+ */
+ function getChildren() {
+ $calendars = $this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']);
+ $objs = [];
+ foreach ($calendars as $calendar) {
+ $objs[] = new Calendar($this->caldavBackend, $calendar);
+ }
+
+ if ($this->caldavBackend instanceof SchedulingSupport) {
+ $objs[] = new Inbox($this->caldavBackend, $this->principalInfo['uri']);
+ $objs[] = new Outbox($this->principalInfo['uri']);
+ }
+
+ // We're adding a notifications node, if it's supported by the backend.
+ if ($this->caldavBackend instanceof NotificationSupport) {
+ $objs[] = new \Sabre\CalDAV\Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']);
+ }
+
+ // If the backend supports subscriptions, we'll add those as well,
+ if ($this->caldavBackend instanceof SubscriptionSupport) {
+ foreach ($this->caldavBackend->getSubscriptionsForUser($this->principalInfo['uri']) as $subscription) {
+ $objs[] = new Subscription($this->caldavBackend, $subscription);
+ }
+ }
+
+ return $objs;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ function getChild($name) {
+ // Special nodes
+ if ($name === 'inbox' && $this->caldavBackend instanceof SchedulingSupport) {
+ return new Inbox($this->caldavBackend, $this->principalInfo['uri']);
+ }
+ if ($name === 'outbox' && $this->caldavBackend instanceof SchedulingSupport) {
+ return new Outbox($this->principalInfo['uri']);
+ }
+ if ($name === 'notifications' && $this->caldavBackend instanceof NotificationSupport) {
+ return new \Sabre\CalDAv\Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']);
+ }
+
+ // Calendars
+ foreach ($this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']) as $calendar) {
+ if ($calendar['uri'] === $name) {
+ return new Calendar($this->caldavBackend, $calendar);
+ }
+ }
+
+ if ($this->caldavBackend instanceof SubscriptionSupport) {
+ foreach ($this->caldavBackend->getSubscriptionsForUser($this->principalInfo['uri']) as $subscription) {
+ if ($subscription['uri'] === $name) {
+ return new Subscription($this->caldavBackend, $subscription);
+ }
+ }
+
+ }
+
+ throw new NotFound('Node with name \'' . $name . '\' could not be found');
+ }
+} \ No newline at end of file
diff --git a/apps/dav/lib/caldav/calendarroot.php b/apps/dav/lib/caldav/calendarroot.php
new file mode 100644
index 00000000000..ae5fc54cdf3
--- /dev/null
+++ b/apps/dav/lib/caldav/calendarroot.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace OCA\DAV\CalDAV;
+
+class CalendarRoot extends \Sabre\CalDAV\CalendarRoot {
+
+ function getChildForPrincipal(array $principal) {
+ return new CalendarHome($this->caldavBackend, $principal);
+ }
+} \ No newline at end of file
diff --git a/apps/dav/lib/carddav/addressbook.php b/apps/dav/lib/carddav/addressbook.php
index 513eae4d723..ca3f5ba0ef6 100644
--- a/apps/dav/lib/carddav/addressbook.php
+++ b/apps/dav/lib/carddav/addressbook.php
@@ -21,6 +21,7 @@
namespace OCA\DAV\CardDAV;
use OCA\DAV\DAV\Sharing\IShareable;
+use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\NotFound;
class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable {
@@ -132,4 +133,32 @@ class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable {
public function getResourceId() {
return $this->addressBookInfo['id'];
}
+
+ function getOwner() {
+ if (isset($this->addressBookInfo['{http://owncloud.org/ns}owner-principal'])) {
+ return $this->addressBookInfo['{http://owncloud.org/ns}owner-principal'];
+ }
+ return parent::getOwner();
+ }
+
+ function delete() {
+ if (isset($this->addressBookInfo['{http://owncloud.org/ns}owner-principal'])) {
+ $principal = 'principal:' . parent::getOwner();
+ $shares = $this->getShares();
+ $shares = array_filter($shares, function($share) use ($principal){
+ return $share['href'] === $principal;
+ });
+ if (empty($shares)) {
+ throw new Forbidden();
+ }
+
+ /** @var CardDavBackend $cardDavBackend */
+ $cardDavBackend = $this->carddavBackend;
+ $cardDavBackend->updateShares($this, [], [
+ 'href' => $principal
+ ]);
+ return;
+ }
+ parent::delete();
+ }
}
diff --git a/apps/dav/lib/carddav/carddavbackend.php b/apps/dav/lib/carddav/carddavbackend.php
index 3dc5c00e10b..9ca166c22a2 100644
--- a/apps/dav/lib/carddav/carddavbackend.php
+++ b/apps/dav/lib/carddav/carddavbackend.php
@@ -103,7 +103,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
$result = $query->execute();
while($row = $result->fetch()) {
- $addressBooks[] = [
+ $addressBooks[$row['id']] = [
'id' => $row['id'],
'uri' => $row['uri'],
'principaluri' => $row['principaluri'],
@@ -133,7 +133,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
list(, $name) = URLUtil::splitPath($row['principaluri']);
$uri = $row['uri'] . '_shared_by_' . $name;
$displayName = $row['displayname'] . "($name)";
- $addressBooks[] = [
+ $addressBooks[$row['id']] = [
'id' => $row['id'],
'uri' => $uri,
'principaluri' => $principalUri,
@@ -147,7 +147,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
}
$result->closeCursor();
- return $addressBooks;
+ return array_values($addressBooks);
}
/**
@@ -336,10 +336,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
->setParameter('id', $addressBookId)
->execute();
- $query->delete('dav_shares')
- ->where($query->expr()->eq('resourceid', $query->createNamedParameter($addressBookId)))
- ->andWhere($query->expr()->eq('type', $query->createNamedParameter('addressbook')))
- ->execute();
+ $this->sharingBackend->deleteAllShares($addressBookId);
$query->delete($this->dbCardsPropertiesTable)
->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
@@ -920,22 +917,6 @@ class CardDavBackend implements BackendInterface, SyncSupport {
* @return array
*/
public function applyShareAcl($addressBookId, $acl) {
-
- $shares = $this->getShares($addressBookId);
- foreach ($shares as $share) {
- $acl[] = [
- 'privilege' => '{DAV:}read',
- 'principal' => $share['{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}principal'],
- 'protected' => true,
- ];
- if (!$share['readOnly']) {
- $acl[] = [
- 'privilege' => '{DAV:}write',
- 'principal' => $share['{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}principal'],
- 'protected' => true,
- ];
- }
- }
- return $acl;
+ return $this->sharingBackend->applyShareAcl($addressBookId, $acl);
}
}
diff --git a/apps/dav/lib/dav/sharing/backend.php b/apps/dav/lib/dav/sharing/backend.php
index fee864ffe6f..0b28891fbc4 100644
--- a/apps/dav/lib/dav/sharing/backend.php
+++ b/apps/dav/lib/dav/sharing/backend.php
@@ -58,7 +58,7 @@ class Backend {
$this->shareWith($shareable, $element);
}
foreach($remove as $element) {
- $this->unshare($shareable->getResourceId(), $element);
+ $this->unshare($shareable, $element);
}
}
@@ -73,8 +73,13 @@ class Backend {
return;
}
+ // don't share with owner
+ if ($shareable->getOwner() === $parts[1]) {
+ return;
+ }
+
// remove the share if it already exists
- $this->unshare($shareable->getResourceId(), $element['href']);
+ $this->unshare($shareable, $element['href']);
$access = self::ACCESS_READ;
if (isset($element['readOnly'])) {
$access = $element['readOnly'] ? self::ACCESS_READ : self::ACCESS_READ_WRITE;
@@ -92,18 +97,34 @@ class Backend {
}
/**
- * @param int $resourceId
+ * @param $resourceId
+ */
+ public function deleteAllShares($resourceId) {
+ $query = $this->db->getQueryBuilder();
+ $query->delete('dav_shares')
+ ->where($query->expr()->eq('resourceid', $query->createNamedParameter($resourceId)))
+ ->andWhere($query->expr()->eq('type', $query->createNamedParameter($this->resourceType)))
+ ->execute();
+ }
+
+ /**
+ * @param IShareable $shareable
* @param string $element
*/
- private function unshare($resourceId, $element) {
+ private function unshare($shareable, $element) {
$parts = explode(':', $element, 2);
if ($parts[0] !== 'principal') {
return;
}
+ // don't share with owner
+ if ($shareable->getOwner() === $parts[1]) {
+ return;
+ }
+
$query = $this->db->getQueryBuilder();
$query->delete('dav_shares')
- ->where($query->expr()->eq('resourceid', $query->createNamedParameter($resourceId)))
+ ->where($query->expr()->eq('resourceid', $query->createNamedParameter($shareable->getResourceId())))
->andWhere($query->expr()->eq('type', $query->createNamedParameter($this->resourceType)))
->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($parts[1])))
;
@@ -136,7 +157,7 @@ class Backend {
'href' => "principal:${row['principaluri']}",
// 'commonName' => isset($p['{DAV:}displayname']) ? $p['{DAV:}displayname'] : '',
'status' => 1,
- 'readOnly' => ($row['access'] === self::ACCESS_READ),
+ 'readOnly' => ($row['access'] == self::ACCESS_READ),
'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}principal' => $row['principaluri']
];
}
diff --git a/apps/dav/lib/dav/sharing/plugin.php b/apps/dav/lib/dav/sharing/plugin.php
index cbb4408e29b..f6e2cceebd9 100644
--- a/apps/dav/lib/dav/sharing/plugin.php
+++ b/apps/dav/lib/dav/sharing/plugin.php
@@ -23,9 +23,12 @@
namespace OCA\DAV\DAV\Sharing;
use OCA\DAV\Connector\Sabre\Auth;
+use OCA\DAV\DAV\Sharing\Xml\Invite;
use OCP\IRequest;
use Sabre\DAV\Exception\BadRequest;
use Sabre\DAV\Exception\NotFound;
+use Sabre\DAV\INode;
+use Sabre\DAV\PropFind;
use Sabre\DAV\Server;
use Sabre\DAV\ServerPlugin;
use Sabre\HTTP\RequestInterface;
@@ -97,8 +100,10 @@ class Plugin extends ServerPlugin {
function initialize(Server $server) {
$this->server = $server;
$this->server->xml->elementMap['{' . Plugin::NS_OWNCLOUD . '}share'] = 'OCA\\DAV\\DAV\\Sharing\\Xml\\ShareRequest';
+ $this->server->xml->elementMap['{' . Plugin::NS_OWNCLOUD . '}invite'] = 'OCA\\DAV\\DAV\\Sharing\\Xml\\Invite';
$this->server->on('method:POST', [$this, 'httpPost']);
+ $this->server->on('propFind', [$this, 'propFind']);
}
/**
@@ -174,6 +179,28 @@ class Plugin extends ServerPlugin {
}
}
+ /**
+ * This event is triggered when properties are requested for a certain
+ * node.
+ *
+ * This allows us to inject any properties early.
+ *
+ * @param PropFind $propFind
+ * @param INode $node
+ * @return void
+ */
+ function propFind(PropFind $propFind, INode $node) {
+ if ($node instanceof IShareable) {
+
+ $propFind->handle('{' . Plugin::NS_OWNCLOUD . '}invite', function() use ($node) {
+ return new Invite(
+ $node->getShares()
+ );
+ });
+
+ }
+ }
+
private function protectAgainstCSRF() {
$user = $this->auth->getCurrentUser();
if ($this->auth->isDavAuthenticated($user)) {
diff --git a/apps/dav/lib/dav/sharing/xml/invite.php b/apps/dav/lib/dav/sharing/xml/invite.php
new file mode 100644
index 00000000000..659f95d8074
--- /dev/null
+++ b/apps/dav/lib/dav/sharing/xml/invite.php
@@ -0,0 +1,149 @@
+<?php
+
+namespace OCA\DAV\DAV\Sharing\Xml;
+
+use OCA\DAV\DAV\Sharing\Plugin;
+use Sabre\Xml\Writer;
+use Sabre\Xml\XmlSerializable;
+
+/**
+ * Invite property
+ *
+ * This property encodes the 'invite' property, as defined by
+ * the 'caldav-sharing-02' spec, in the http://calendarserver.org/ns/
+ * namespace.
+ *
+ * @see https://trac.calendarserver.org/browser/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Invite implements XmlSerializable {
+
+ /**
+ * The list of users a calendar has been shared to.
+ *
+ * @var array
+ */
+ protected $users;
+
+ /**
+ * The organizer contains information about the person who shared the
+ * object.
+ *
+ * @var array
+ */
+ protected $organizer;
+
+ /**
+ * Creates the property.
+ *
+ * Users is an array. Each element of the array has the following
+ * properties:
+ *
+ * * href - Often a mailto: address
+ * * commonName - Optional, for example a first and lastname for a user.
+ * * status - One of the SharingPlugin::STATUS_* constants.
+ * * readOnly - true or false
+ * * summary - Optional, description of the share
+ *
+ * The organizer key is optional to specify. It's only useful when a
+ * 'sharee' requests the sharing information.
+ *
+ * The organizer may have the following properties:
+ * * href - Often a mailto: address.
+ * * commonName - Optional human-readable name.
+ * * firstName - Optional first name.
+ * * lastName - Optional last name.
+ *
+ * If you wonder why these two structures are so different, I guess a
+ * valid answer is that the current spec is still a draft.
+ *
+ * @param array $users
+ */
+ function __construct(array $users, array $organizer = null) {
+
+ $this->users = $users;
+ $this->organizer = $organizer;
+
+ }
+
+ /**
+ * Returns the list of users, as it was passed to the constructor.
+ *
+ * @return array
+ */
+ function getValue() {
+
+ return $this->users;
+
+ }
+
+ /**
+ * The xmlSerialize metod is called during xml writing.
+ *
+ * Use the $writer argument to write its own xml serialization.
+ *
+ * An important note: do _not_ create a parent element. Any element
+ * implementing XmlSerializble should only ever write what's considered
+ * its 'inner xml'.
+ *
+ * The parent of the current element is responsible for writing a
+ * containing element.
+ *
+ * This allows serializers to be re-used for different element names.
+ *
+ * If you are opening new elements, you must also close them again.
+ *
+ * @param Writer $writer
+ * @return void
+ */
+ function xmlSerialize(Writer $writer) {
+
+ $cs = '{' . Plugin::NS_OWNCLOUD . '}';
+
+ if (!is_null($this->organizer)) {
+
+ $writer->startElement($cs . 'organizer');
+ $writer->writeElement('{DAV:}href', $this->organizer['href']);
+
+ if (isset($this->organizer['commonName']) && $this->organizer['commonName']) {
+ $writer->writeElement($cs . 'common-name', $this->organizer['commonName']);
+ }
+ if (isset($this->organizer['firstName']) && $this->organizer['firstName']) {
+ $writer->writeElement($cs . 'first-name', $this->organizer['firstName']);
+ }
+ if (isset($this->organizer['lastName']) && $this->organizer['lastName']) {
+ $writer->writeElement($cs . 'last-name', $this->organizer['lastName']);
+ }
+ $writer->endElement(); // organizer
+
+ }
+
+ foreach ($this->users as $user) {
+
+ $writer->startElement($cs . 'user');
+ $writer->writeElement('{DAV:}href', $user['href']);
+ if (isset($user['commonName']) && $user['commonName']) {
+ $writer->writeElement($cs . 'common-name', $user['commonName']);
+ }
+ $writer->writeElement($cs . 'invite-accepted');
+
+ $writer->startElement($cs . 'access');
+ if ($user['readOnly']) {
+ $writer->writeElement($cs . 'read');
+ } else {
+ $writer->writeElement($cs . 'read-write');
+ }
+ $writer->endElement(); // access
+
+ if (isset($user['summary']) && $user['summary']) {
+ $writer->writeElement($cs . 'summary', $user['summary']);
+ }
+
+ $writer->endElement(); //user
+
+ }
+
+ }
+}
diff --git a/apps/dav/lib/rootcollection.php b/apps/dav/lib/rootcollection.php
index 5be469e7b0c..2a8f63a2270 100644
--- a/apps/dav/lib/rootcollection.php
+++ b/apps/dav/lib/rootcollection.php
@@ -22,12 +22,12 @@
namespace OCA\DAV;
use OCA\DAV\CalDAV\CalDavBackend;
+use OCA\DAV\CalDAV\CalendarRoot;
use OCA\DAV\CardDAV\AddressBookRoot;
use OCA\DAV\CardDAV\CardDavBackend;
use OCA\DAV\Connector\Sabre\Principal;
use OCA\DAV\DAV\GroupPrincipalBackend;
use OCA\DAV\DAV\SystemPrincipalBackend;
-use Sabre\CalDAV\CalendarRoot;
use Sabre\CalDAV\Principal\Collection;
use Sabre\DAV\SimpleCollection;
@@ -55,7 +55,7 @@ class RootCollection extends SimpleCollection {
$systemPrincipals->disableListing = $disableListing;
$filesCollection = new Files\RootCollection($userPrincipalBackend, 'principals/users');
$filesCollection->disableListing = $disableListing;
- $caldavBackend = new CalDavBackend($db);
+ $caldavBackend = new CalDavBackend($db, $userPrincipalBackend);
$calendarRoot = new CalendarRoot($userPrincipalBackend, $caldavBackend, 'principals/users');
$calendarRoot->disableListing = $disableListing;
diff --git a/apps/dav/tests/travis/caldav/install.sh b/apps/dav/tests/travis/caldav/install.sh
index e836e37f86f..9688ec660de 100644
--- a/apps/dav/tests/travis/caldav/install.sh
+++ b/apps/dav/tests/travis/caldav/install.sh
@@ -15,6 +15,7 @@ fi
cd "$SCRIPTPATH/../../../../../"
OC_PASS=user01 php occ user:add --password-from-env user01
php occ dav:create-calendar user01 calendar
+php occ dav:create-calendar user01 shared
OC_PASS=user02 php occ user:add --password-from-env user02
php occ dav:create-calendar user02 calendar
cd "$SCRIPTPATH/../../../../../"
diff --git a/apps/dav/tests/travis/caldav/script.sh b/apps/dav/tests/travis/caldav/script.sh
index fe3391d5a06..aa5fc732922 100644
--- a/apps/dav/tests/travis/caldav/script.sh
+++ b/apps/dav/tests/travis/caldav/script.sh
@@ -10,9 +10,7 @@ sleep 30
# run the tests
cd "$SCRIPTPATH/CalDAVTester"
PYTHONPATH="$SCRIPTPATH/pycalendar/src" python testcaldav.py --print-details-onfail --basedir "$SCRIPTPATH/../caldavtest/" -o cdt.txt \
- "CalDAV/current-user-principal.xml" \
- "CalDAV/sync-report.xml"
-
+ "CalDAV/sharing-calendars.xml"
RESULT=$?
diff --git a/apps/dav/tests/travis/caldavtest/data/Resource/CalDAV/sharing/calendars/read-write/1.xml b/apps/dav/tests/travis/caldavtest/data/Resource/CalDAV/sharing/calendars/read-write/1.xml
new file mode 100644
index 00000000000..3bcf9dc47f9
--- /dev/null
+++ b/apps/dav/tests/travis/caldavtest/data/Resource/CalDAV/sharing/calendars/read-write/1.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<CS:share xmlns:D="DAV:" xmlns:CS="http://owncloud.org/ns">
+ <CS:set>
+ <D:href>principal:principals/users/user02</D:href>
+ <CS:summary>My Shared Calendar</CS:summary>
+ <CS:read-write/>
+ </CS:set>
+</CS:share>
diff --git a/apps/dav/tests/travis/caldavtest/data/Resource/CalDAV/sharing/calendars/read-write/4.xml b/apps/dav/tests/travis/caldavtest/data/Resource/CalDAV/sharing/calendars/read-write/4.xml
new file mode 100644
index 00000000000..fd0f248bb31
--- /dev/null
+++ b/apps/dav/tests/travis/caldavtest/data/Resource/CalDAV/sharing/calendars/read-write/4.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<D:propfind xmlns:D="DAV:">
+<D:prop>
+<D:resourcetype/>
+<D:owner/>
+<D:current-user-privilege-set/>
+</D:prop>
+</D:propfind>
diff --git a/apps/dav/tests/travis/caldavtest/data/Resource/CalDAV/sharing/calendars/read-write/5.ics b/apps/dav/tests/travis/caldavtest/data/Resource/CalDAV/sharing/calendars/read-write/5.ics
new file mode 100644
index 00000000000..ae21adac8b2
--- /dev/null
+++ b/apps/dav/tests/travis/caldavtest/data/Resource/CalDAV/sharing/calendars/read-write/5.ics
@@ -0,0 +1,29 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VTIMEZONE
+TZID:US/Eastern
+LAST-MODIFIED:20040110T032845Z
+BEGIN:STANDARD
+DTSTART:20001026T020000
+RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:20000404T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:$uid1:
+DTSTART;TZID=US/Eastern:$now.year.1:0101T100000
+DURATION:PT1H
+DTSTAMP:20051222T205953Z
+SUMMARY:event 1
+END:VEVENT
+END:VCALENDAR
diff --git a/apps/dav/tests/travis/caldavtest/data/Resource/CalDAV/sharing/calendars/read-write/5.xml b/apps/dav/tests/travis/caldavtest/data/Resource/CalDAV/sharing/calendars/read-write/5.xml
new file mode 100644
index 00000000000..4862ed195f8
--- /dev/null
+++ b/apps/dav/tests/travis/caldavtest/data/Resource/CalDAV/sharing/calendars/read-write/5.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<D:propfind xmlns:D="DAV:" xmlns:CS="http://owncloud.org/ns">
+<D:prop>
+<CS:invite/>
+</D:prop>
+</D:propfind>
diff --git a/apps/dav/tests/travis/caldavtest/data/Resource/CalDAV/sharing/calendars/read-write/6.ics b/apps/dav/tests/travis/caldavtest/data/Resource/CalDAV/sharing/calendars/read-write/6.ics
new file mode 100644
index 00000000000..145f5f14c7b
--- /dev/null
+++ b/apps/dav/tests/travis/caldavtest/data/Resource/CalDAV/sharing/calendars/read-write/6.ics
@@ -0,0 +1,29 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VTIMEZONE
+TZID:US/Eastern
+LAST-MODIFIED:20040110T032845Z
+BEGIN:STANDARD
+DTSTART:20001026T020000
+RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:20000404T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:$uid1:
+DTSTART;TZID=US/Eastern:$now.year.1:0101T100000
+DURATION:PT4H
+DTSTAMP:20051222T205953Z
+SUMMARY:event 4
+END:VEVENT
+END:VCALENDAR
diff --git a/apps/dav/tests/travis/caldavtest/data/Resource/CalDAV/sharing/calendars/read-write/7.ics b/apps/dav/tests/travis/caldavtest/data/Resource/CalDAV/sharing/calendars/read-write/7.ics
new file mode 100644
index 00000000000..c4e816210df
--- /dev/null
+++ b/apps/dav/tests/travis/caldavtest/data/Resource/CalDAV/sharing/calendars/read-write/7.ics
@@ -0,0 +1,29 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VTIMEZONE
+TZID:US/Eastern
+LAST-MODIFIED:20040110T032845Z
+BEGIN:STANDARD
+DTSTART:20001026T020000
+RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:20000404T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:$uid2:
+DTSTART;TZID=US/Eastern:$now.year.1:0201T100000
+DURATION:PT1H
+DTSTAMP:20051222T205953Z
+SUMMARY:event 7
+END:VEVENT
+END:VCALENDAR
diff --git a/apps/dav/tests/travis/caldavtest/data/Resource/CalDAV/sharing/calendars/read-write/8.ics b/apps/dav/tests/travis/caldavtest/data/Resource/CalDAV/sharing/calendars/read-write/8.ics
new file mode 100644
index 00000000000..2da72d2f601
--- /dev/null
+++ b/apps/dav/tests/travis/caldavtest/data/Resource/CalDAV/sharing/calendars/read-write/8.ics
@@ -0,0 +1,29 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VTIMEZONE
+TZID:US/Eastern
+LAST-MODIFIED:20040110T032845Z
+BEGIN:STANDARD
+DTSTART:20001026T020000
+RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:20000404T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:$uid2:
+DTSTART;TZID=US/Eastern:$now.year.1:0201T100000
+DURATION:PT7H
+DTSTAMP:20051222T205953Z
+SUMMARY:event 7-1
+END:VEVENT
+END:VCALENDAR
diff --git a/apps/dav/tests/travis/caldavtest/data/Resource/CalDAV/sharing/calendars/read-write/9.ics b/apps/dav/tests/travis/caldavtest/data/Resource/CalDAV/sharing/calendars/read-write/9.ics
new file mode 100644
index 00000000000..dfc21bb9c5b
--- /dev/null
+++ b/apps/dav/tests/travis/caldavtest/data/Resource/CalDAV/sharing/calendars/read-write/9.ics
@@ -0,0 +1,29 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VTIMEZONE
+TZID:US/Eastern
+LAST-MODIFIED:20040110T032845Z
+BEGIN:STANDARD
+DTSTART:20001026T020000
+RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:20000404T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:$uid3:
+DTSTART;TZID=US/Eastern:$now.year.1:0201T100000
+DURATION:PT7H
+DTSTAMP:20051222T205953Z
+SUMMARY:event 9.ics
+END:VEVENT
+END:VCALENDAR
diff --git a/apps/dav/tests/travis/caldavtest/tests/CalDAV/sharing-calendars.xml b/apps/dav/tests/travis/caldavtest/tests/CalDAV/sharing-calendars.xml
index fa20a6e4862..334fa561aec 100644
--- a/apps/dav/tests/travis/caldavtest/tests/CalDAV/sharing-calendars.xml
+++ b/apps/dav/tests/travis/caldavtest/tests/CalDAV/sharing-calendars.xml
@@ -27,6 +27,7 @@
</require-feature>
<start>
+ <!--
<request user="$userid1:" pswd="$pswd1:">
<method>DELETEALL</method>
<ruri>$notificationpath1:/</ruri>
@@ -50,6 +51,7 @@
<filepath>Resource/Common/PROPPATCH/calendar-transp-opaque.xml</filepath>
</data>
</request>
+ -->
</start>
<test-suite name='Read-write calendar'>
@@ -67,56 +69,11 @@
</verify>
</request>
</test>
- <test name='2'>
- <description>Check Sharee notification collection</description>
- <request user="$userid2:" pswd="$pswd2:">
- <method>WAITCOUNT 1</method>
- <ruri>$notificationpath2:/</ruri>
- </request>
- <request user="$userid2:" pswd="$pswd2:">
- <method>GETNEW</method>
- <ruri>$notificationpath2:/</ruri>
- <verify>
- <callback>xmlDataMatch</callback>
- <arg>
- <name>filepath</name>
- <value>Resource/CalDAV/sharing/calendars/read-write/2.xml</value>
- </arg>
- <arg>
- <name>filter</name>
- <value>{http://calendarserver.org/ns/}dtstamp</value>
- <value>{http://calendarserver.org/ns/}uid</value>
- </arg>
- </verify>
- <grabelement>
- <name>{http://calendarserver.org/ns/}invite-notification/{http://calendarserver.org/ns/}uid</name>
- <variable>$inviteuid:</variable>
- </grabelement>
- </request>
- </test>
- <test name='3'>
- <description>Sharee replies ACCEPTED</description>
- <request user="$userid2:" pswd="$pswd2:">
- <method>POST</method>
- <ruri>$calendarhome2:/</ruri>
- <data>
- <content-type>application/xml; charset=utf-8</content-type>
- <filepath>Resource/CalDAV/sharing/calendars/read-write/3.xml</filepath>
- </data>
- <verify>
- <callback>statusCode</callback>
- </verify>
- <grabelement>
- <name>{DAV:}href</name>
- <variable>$sharedcalendar:</variable>
- </grabelement>
- </request>
- </test>
<test name='4'>
<description>Shared calendar exists</description>
<request user="$userid2:" pswd="$pswd2:">
<method>PROPFIND</method>
- <ruri>$sharedcalendar:/</ruri>
+ <ruri>$calendarhome1:/shared/</ruri>
<header>
<name>Depth</name>
<value>0</value>
@@ -132,12 +89,12 @@
<value>$verify-property-prefix:/{DAV:}owner/{DAV:}href[=$principaluri1:]</value>
<value>$verify-property-prefix:/{DAV:}resourcetype/{DAV:}collection</value>
<value>$verify-property-prefix:/{DAV:}resourcetype/{urn:ietf:params:xml:ns:caldav}calendar</value>
- <value>$verify-property-prefix:/{DAV:}resourcetype/{http://calendarserver.org/ns/}shared</value>
+ <!-- value>$verify-property-prefix:/{DAV:}resourcetype/{http://calendarserver.org/ns/}shared</value -->
<value>$verify-property-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}read</value>
<value>$verify-property-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}write</value>
<value>$verify-property-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}bind</value>
<value>$verify-property-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}unbind</value>
- <value>$verify-property-prefix:/{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp/{urn:ietf:params:xml:ns:caldav}transparent</value>
+ <!-- value>$verify-property-prefix:/{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp/{urn:ietf:params:xml:ns:caldav}transparent</value -->
</arg>
<arg>
<name>notexists</name>
@@ -151,7 +108,7 @@
<description>Shared calendar exists Depth:1</description>
<request user="$userid2:" pswd="$pswd2:">
<method>PROPFIND</method>
- <ruri>$calendarhome2:/</ruri>
+ <ruri>$calendarhome2:</ruri>
<header>
<name>Depth</name>
<value>1</value>
@@ -164,19 +121,19 @@
<callback>xmlElementMatch</callback>
<arg>
<name>parent</name>
- <value>$multistatus-response-prefix:[^{DAV:}href=$sharedcalendar:/]</value>
+ <value>$multistatus-response-prefix:[^{DAV:}href=$calendarhome2:/shared_shared_by_user01/]</value>
</arg>
<arg>
<name>exists</name>
<value>$verify-response-prefix:/{DAV:}owner/{DAV:}href[=$principaluri1:]</value>
<value>$verify-response-prefix:/{DAV:}resourcetype/{DAV:}collection</value>
<value>$verify-response-prefix:/{DAV:}resourcetype/{urn:ietf:params:xml:ns:caldav}calendar</value>
- <value>$verify-response-prefix:/{DAV:}resourcetype/{http://calendarserver.org/ns/}shared</value>
+ <!-- value>$verify-response-prefix:/{DAV:}resourcetype/{http://calendarserver.org/ns/}shared</value -->
<value>$verify-response-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}read</value>
<value>$verify-response-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}write</value>
<value>$verify-response-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}bind</value>
<value>$verify-response-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}unbind</value>
- <value>$verify-response-prefix:/{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp/{urn:ietf:params:xml:ns:caldav}transparent</value>
+ <!-- value>$verify-response-prefix:/{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp/{urn:ietf:params:xml:ns:caldav}transparent</value -->
</arg>
<arg>
<name>notexists</name>
@@ -186,44 +143,31 @@
</verify>
</request>
</test>
- <test name='4b'>
- <description>Shared calendar has invite property</description>
- <request user="$userid2:" pswd="$pswd2:">
+ <test name='5'>
+ <description>Original calendar unchanged</description>
+ <request>
<method>PROPFIND</method>
- <ruri>$sharedcalendar:/</ruri>
+ <ruri>$calendarhome1:/shared/</ruri>
<header>
<name>Depth</name>
<value>0</value>
</header>
<data>
<content-type>text/xml; charset=utf-8</content-type>
- <filepath>Resource/CalDAV/sharing/calendars/read-write/5.xml</filepath>
+ <filepath>Resource/CalDAV/sharing/calendars/read-write/4.xml</filepath>
</data>
<verify>
- <callback>propfindItems</callback>
- <arg>
- <name>okprops</name>
- <value>{http://calendarserver.org/ns/}invite</value>
- </arg>
- </verify>
- <verify>
<callback>xmlElementMatch</callback>
<arg>
<name>exists</name>
- <value>$verify-property-prefix:/{http://calendarserver.org/ns/}invite</value>
- <value>$verify-property-prefix:/{http://calendarserver.org/ns/}invite/{http://calendarserver.org/ns/}organizer</value>
- <value>$verify-property-prefix:/{http://calendarserver.org/ns/}invite/{http://calendarserver.org/ns/}organizer/{DAV:}href[=$principaluri1:]</value>
- <value>$verify-property-prefix:/{http://calendarserver.org/ns/}invite/{http://calendarserver.org/ns/}organizer/{http://calendarserver.org/ns/}common-name[=$username1:]</value>
- <value>$verify-property-prefix:/{http://calendarserver.org/ns/}invite/{http://calendarserver.org/ns/}user</value>
- <value>$verify-property-prefix:/{http://calendarserver.org/ns/}invite/{http://calendarserver.org/ns/}user/{DAV:}href[=$cuaddrurn2:]</value>
- <value>$verify-property-prefix:/{http://calendarserver.org/ns/}invite/{http://calendarserver.org/ns/}user/{http://calendarserver.org/ns/}access/{http://calendarserver.org/ns/}read-write</value>
- <value>$verify-property-prefix:/{http://calendarserver.org/ns/}invite/{http://calendarserver.org/ns/}user/{http://calendarserver.org/ns/}invite-accepted</value>
+ <value>$verify-property-prefix:/{DAV:}owner/{DAV:}href[=$principaluri1:]</value>
+ <!--<value>$verify-property-prefix:/{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp/{urn:ietf:params:xml:ns:caldav}opaque</value>-->
</arg>
</verify>
</request>
</test>
- <test name='5'>
- <description>Original calendar unchanged</description>
+ <test name='5a'>
+ <description>Invite propfind returns sharees</description>
<request>
<method>PROPFIND</method>
<ruri>$calendarhome1:/shared/</ruri>
@@ -233,14 +177,14 @@
</header>
<data>
<content-type>text/xml; charset=utf-8</content-type>
- <filepath>Resource/CalDAV/sharing/calendars/read-write/4.xml</filepath>
+ <filepath>Resource/CalDAV/sharing/calendars/read-write/5.xml</filepath>
</data>
<verify>
<callback>xmlElementMatch</callback>
<arg>
<name>exists</name>
- <value>$verify-property-prefix:/{DAV:}owner/{DAV:}href[=$principaluri1:]</value>
- <value>$verify-property-prefix:/{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp/{urn:ietf:params:xml:ns:caldav}opaque</value>
+ <value>$verify-property-prefix:/{http://owncloud.org/ns}invite/{http://owncloud.org/ns}user/{DAV:}href</value>
+ <value>$verify-property-prefix:/{http://owncloud.org/ns}invite/{http://owncloud.org/ns}user/{http://owncloud.org/ns}invite-accepted</value>
</arg>
</verify>
</request>
@@ -249,7 +193,7 @@
<description>Sharee creates event</description>
<request user="$userid2:" pswd="$pswd2:">
<method>PUT</method>
- <ruri>$sharedcalendar:/1.ics</ruri>
+ <ruri>$calendarhome1:/shared/1.ics</ruri>
<data>
<content-type>text/calendar; charset=utf-8</content-type>
<filepath>Resource/CalDAV/sharing/calendars/read-write/5.ics</filepath>
@@ -291,7 +235,7 @@
<description>Sharee sees changed event</description>
<request user="$userid2:" pswd="$pswd2:">
<method>GET</method>
- <ruri>$sharedcalendar:/1.ics</ruri>
+ <ruri>$calendarhome1:/shared/1.ics</ruri>
<verify>
<callback>calendarDataMatch</callback>
<arg>
@@ -319,7 +263,7 @@
<description>Sharee sees new event</description>
<request user="$userid2:" pswd="$pswd2:">
<method>GET</method>
- <ruri>$sharedcalendar:/2.ics</ruri>
+ <ruri>$calendarhome1:/shared/2.ics</ruri>
<verify>
<callback>calendarDataMatch</callback>
<arg>
@@ -333,7 +277,7 @@
<description>Sharee changes event</description>
<request user="$userid2:" pswd="$pswd2:">
<method>PUT</method>
- <ruri>$sharedcalendar:/2.ics</ruri>
+ <ruri>$calendarhome1:/shared/2.ics</ruri>
<data>
<content-type>text/calendar; charset=utf-8</content-type>
<filepath>Resource/CalDAV/sharing/calendars/read-write/8.ics</filepath>
@@ -357,8 +301,76 @@
</verify>
</request>
</test>
+ <test name='14'>
+ <description>Un-share by delete</description>
+ <request user="$userid2:" pswd="$pswd2:">
+ <method>DELETE</method>
+ <ruri>$calendarhome2:/shared_shared_by_user01/</ruri>
+ <verify>
+ <callback>statusCode</callback>
+ </verify>
+ </request>
+ </test>
+ <test name='15'>
+ <description>Original calendar still exists</description>
+ <request>
+ <method>PROPFIND</method>
+ <ruri>$calendarhome1:/shared/</ruri>
+ <header>
+ <name>Depth</name>
+ <value>0</value>
+ </header>
+ <data>
+ <content-type>text/xml; charset=utf-8</content-type>
+ <filepath>Resource/CalDAV/sharing/calendars/read-write/4.xml</filepath>
+ </data>
+ <verify>
+ <callback>xmlElementMatch</callback>
+ <arg>
+ <name>exists</name>
+ <value>$verify-property-prefix:/{DAV:}owner/{DAV:}href[=$principaluri1:]</value>
+ <value>$verify-property-prefix:/{DAV:}resourcetype/{DAV:}collection</value>
+ <value>$verify-property-prefix:/{DAV:}resourcetype/{urn:ietf:params:xml:ns:caldav}calendar</value>
+ <!-- value>$verify-property-prefix:/{DAV:}resourcetype/{http://calendarserver.org/ns/}shared</value -->
+ <value>$verify-property-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}read</value>
+ <value>$verify-property-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}write</value>
+ <value>$verify-property-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}bind</value>
+ <value>$verify-property-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}unbind</value>
+ <!-- value>$verify-property-prefix:/{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp/{urn:ietf:params:xml:ns:caldav}transparent</value -->
+ </arg>
+ <arg>
+ <name>notexists</name>
+ <value>$verify-property-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}admin</value>
+ <value>$verify-property-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}all</value>
+ </arg>
+ </verify>
+ </request>
+ </test>
+ <test name='16'>
+ <description>Shared calendar no longer exists Depth:1</description>
+ <request user="$userid2:" pswd="$pswd2:">
+ <method>PROPFIND</method>
+ <ruri>$calendarhome2:</ruri>
+ <header>
+ <name>Depth</name>
+ <value>1</value>
+ </header>
+ <data>
+ <content-type>text/xml; charset=utf-8</content-type>
+ <filepath>Resource/CalDAV/sharing/calendars/read-write/4.xml</filepath>
+ </data>
+ <verify>
+ <callback>xmlElementMatch</callback>
+ <arg>
+ <name>notexists</name>
+ <value>$multistatus-response-prefix:[^{DAV:}href=$calendarhome2:/shared_shared_by_user01/]</value>
+ </arg>
+ </verify>
+ </request>
+ </test>
</test-suite>
-
+
+ <!--
<test-suite name='Default calendar cannot be shared calendar'>
<test name='1'>
<description>Set property on Inbox</description>
@@ -560,7 +572,10 @@
</test>
</test-suite>
+-->
+
<end>
+ <!--
<request user="$useradmin:" pswd="$pswdadmin:">
<method>DELETEALL</method>
<ruri>$notificationpath1:/</ruri>
@@ -568,6 +583,7 @@
<ruri>$notificationpath3:/</ruri>
<ruri>$notificationpath4:/</ruri>
</request>
+ -->
</end>
</caldavtest>
diff --git a/apps/dav/tests/travis/caldavtest/tests/CardDAV/sharing-addressbooks.xml b/apps/dav/tests/travis/caldavtest/tests/CardDAV/sharing-addressbooks.xml
index 37b4941b9f1..84ee6265017 100644
--- a/apps/dav/tests/travis/caldavtest/tests/CardDAV/sharing-addressbooks.xml
+++ b/apps/dav/tests/travis/caldavtest/tests/CardDAV/sharing-addressbooks.xml
@@ -238,7 +238,70 @@
</verify>
</request>
</test>
- </test-suite>
+ <test name='14'>
+ <description>Un-share by delete</description>
+ <request user="$userid2:" pswd="$pswd2:">
+ <method>DELETE</method>
+ <ruri>$addressbookhome2:/addressbook_shared_by_user01/</ruri>
+ <verify>
+ <callback>statusCode</callback>
+ </verify>
+ </request>
+ </test>
+ <test name='15'>
+ <description>Original address book still exists</description>
+ <request>
+ <method>PROPFIND</method>
+ <ruri>$addressbookhome1:/addressbook/</ruri>
+ <header>
+ <name>Depth</name>
+ <value>0</value>
+ </header>
+ <data>
+ <content-type>text/xml; charset=utf-8</content-type>
+ <filepath>Resource/CardDAV/sharing/read-write/4.xml</filepath>
+ </data>
+ <verify>
+ <callback>xmlElementMatch</callback>
+ <arg>
+ <name>exists</name>
+ <value>$verify-property-prefix:/{DAV:}owner/{DAV:}href[=$principaluri1:]</value>
+ <value>$verify-property-prefix:/{DAV:}resourcetype/{DAV:}collection</value>
+ <value>$verify-property-prefix:/{DAV:}resourcetype/{urn:ietf:params:xml:ns:carddav}addressbook</value>
+ <value>$verify-property-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}read</value>
+ <value>$verify-property-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}write</value>
+ <value>$verify-property-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}bind</value>
+ <value>$verify-property-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}unbind</value>
+ </arg>
+ <arg>
+ <name>notexists</name>
+ <value>$verify-property-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}admin</value>
+ <value>$verify-property-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}all</value>
+ </arg>
+ </verify> </request>
+ </test>
+ <test name='16'>
+ <description>Shared calendar no longer exists Depth:1</description>
+ <request user="$userid2:" pswd="$pswd2:">
+ <method>PROPFIND</method>
+ <ruri>$addressbookhome2:</ruri>
+ <header>
+ <name>Depth</name>
+ <value>1</value>
+ </header>
+ <data>
+ <content-type>text/xml; charset=utf-8</content-type>
+ <filepath>Resource/CardDAV/sharing/read-write/4.xml</filepath>
+ </data>
+ <verify>
+ <callback>xmlElementMatch</callback>
+ <arg>
+ <name>notexists</name>
+ <value>$multistatus-response-prefix:[^{DAV:}href=$addressbookhome2:/addressbook_shared_by_user01/]</value>
+ </arg>
+ </verify>
+ </request>
+ </test> </test-suite>
<end>
</end>
diff --git a/apps/dav/tests/unit/caldav/caldavbackendtest.php b/apps/dav/tests/unit/caldav/caldavbackendtest.php
index 939fd36fba8..aece738166a 100644
--- a/apps/dav/tests/unit/caldav/caldavbackendtest.php
+++ b/apps/dav/tests/unit/caldav/caldavbackendtest.php
@@ -23,9 +23,12 @@ namespace Tests\Connector\Sabre;
use DateTime;
use DateTimeZone;
use OCA\DAV\CalDAV\CalDavBackend;
+use OCA\DAV\CalDAV\Calendar;
+use OCA\DAV\Connector\Sabre\Principal;
use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet;
use Sabre\DAV\PropPatch;
use Sabre\DAV\Xml\Property\Href;
+use Sabre\DAVACL\IACL;
use Test\TestCase;
/**
@@ -40,14 +43,30 @@ class CalDavBackendTest extends TestCase {
/** @var CalDavBackend */
private $backend;
- const UNIT_TEST_USER = 'caldav-unit-test';
+ /** @var Principal | \PHPUnit_Framework_MockObject_MockObject */
+ private $principal;
+ const UNIT_TEST_USER = 'principals/users/caldav-unit-test';
+ const UNIT_TEST_USER1 = 'principals/users/caldav-unit-test1';
+ const UNIT_TEST_GROUP = 'principals/groups/caldav-unit-test-group';
public function setUp() {
parent::setUp();
+ $this->principal = $this->getMockBuilder('OCA\DAV\Connector\Sabre\Principal')
+ ->disableOriginalConstructor()
+ ->setMethods(['getPrincipalByPath', 'getGroupMembership'])
+ ->getMock();
+ $this->principal->method('getPrincipalByPath')
+ ->willReturn([
+ 'uri' => 'principals/best-friend'
+ ]);
+ $this->principal->method('getGroupMembership')
+ ->withAnyParameters()
+ ->willReturn([self::UNIT_TEST_GROUP]);
+
$db = \OC::$server->getDatabaseConnection();
- $this->backend = new CalDavBackend($db);
+ $this->backend = new CalDavBackend($db, $this->principal);
$this->tearDown();
}
@@ -90,6 +109,87 @@ class CalDavBackendTest extends TestCase {
$this->assertEquals(0, count($books));
}
+ public function providesSharingData() {
+ return [
+ [true, true, true, false, [
+ [
+ 'href' => 'principal:' . self::UNIT_TEST_USER1,
+ 'readOnly' => false
+ ],
+ [
+ 'href' => 'principal:' . self::UNIT_TEST_GROUP,
+ 'readOnly' => true
+ ]
+ ]],
+ [true, false, false, false, [
+ [
+ 'href' => 'principal:' . self::UNIT_TEST_USER1,
+ 'readOnly' => true
+ ],
+ ]],
+
+ ];
+ }
+
+ /**
+ * @dataProvider providesSharingData
+ */
+ public function testCalendarSharing($userCanRead, $userCanWrite, $groupCanRead, $groupCanWrite, $add) {
+
+ $calendarId = $this->createTestCalendar();
+ $books = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER);
+ $this->assertEquals(1, count($books));
+ $calendar = new Calendar($this->backend, $books[0]);
+ $this->backend->updateShares($calendar, $add, []);
+ $books = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER1);
+ $this->assertEquals(1, count($books));
+ $calendar = new Calendar($this->backend, $books[0]);
+ $acl = $calendar->getACL();
+ $this->assertAcl(self::UNIT_TEST_USER, '{DAV:}read', $acl);
+ $this->assertAcl(self::UNIT_TEST_USER, '{DAV:}write', $acl);
+ $this->assertAccess($userCanRead, self::UNIT_TEST_USER1, '{DAV:}read', $acl);
+ $this->assertAccess($userCanWrite, self::UNIT_TEST_USER1, '{DAV:}write', $acl);
+ $this->assertAccess($groupCanRead, self::UNIT_TEST_GROUP, '{DAV:}read', $acl);
+ $this->assertAccess($groupCanWrite, self::UNIT_TEST_GROUP, '{DAV:}write', $acl);
+ $this->assertEquals(self::UNIT_TEST_USER, $calendar->getOwner());
+
+ // test acls on the child
+ $uri = $this->getUniqueID('calobj');
+ $calData = <<<'EOD'
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:ownCloud Calendar
+BEGIN:VEVENT
+CREATED;VALUE=DATE-TIME:20130910T125139Z
+UID:47d15e3ec8
+LAST-MODIFIED;VALUE=DATE-TIME:20130910T125139Z
+DTSTAMP;VALUE=DATE-TIME:20130910T125139Z
+SUMMARY:Test Event
+DTSTART;VALUE=DATE-TIME:20130912T130000Z
+DTEND;VALUE=DATE-TIME:20130912T140000Z
+CLASS:PUBLIC
+END:VEVENT
+END:VCALENDAR
+EOD;
+
+ $this->backend->createCalendarObject($calendarId, $uri, $calData);
+
+ /** @var IACL $child */
+ $child = $calendar->getChild($uri);
+ $acl = $child->getACL();
+ $this->assertAcl(self::UNIT_TEST_USER, '{DAV:}read', $acl);
+ $this->assertAcl(self::UNIT_TEST_USER, '{DAV:}write', $acl);
+ $this->assertAccess($userCanRead, self::UNIT_TEST_USER1, '{DAV:}read', $acl);
+ $this->assertAccess($userCanWrite, self::UNIT_TEST_USER1, '{DAV:}write', $acl);
+ $this->assertAccess($groupCanRead, self::UNIT_TEST_GROUP, '{DAV:}read', $acl);
+ $this->assertAccess($groupCanWrite, self::UNIT_TEST_GROUP, '{DAV:}write', $acl);
+
+ // delete the address book
+ $this->backend->deleteCalendar($books[0]['id']);
+ $books = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER);
+ $this->assertEquals(0, count($books));
+ }
+
public function testCalendarObjectsOperations() {
$calendarId = $this->createTestCalendar();
@@ -345,4 +445,32 @@ EOD;
$sos = $this->backend->getSchedulingObjects(self::UNIT_TEST_USER);
$this->assertEquals(0, count($sos));
}
+
+ private function assertAcl($principal, $privilege, $acl) {
+ foreach($acl as $a) {
+ if ($a['principal'] === $principal && $a['privilege'] === $privilege) {
+ $this->assertTrue(true);
+ return;
+ }
+ }
+ $this->fail("ACL does not contain $principal / $privilege");
+ }
+
+ private function assertNotAcl($principal, $privilege, $acl) {
+ foreach($acl as $a) {
+ if ($a['principal'] === $principal && $a['privilege'] === $privilege) {
+ $this->fail("ACL contains $principal / $privilege");
+ return;
+ }
+ }
+ $this->assertTrue(true);
+ }
+
+ private function assertAccess($shouldHaveAcl, $principal, $privilege, $acl) {
+ if ($shouldHaveAcl) {
+ $this->assertAcl($principal, $privilege, $acl);
+ } else {
+ $this->assertNotAcl($principal, $privilege, $acl);
+ }
+ }
}
diff --git a/apps/dav/tests/unit/caldav/calendartest.php b/apps/dav/tests/unit/caldav/calendartest.php
new file mode 100644
index 00000000000..93b3f4bff8c
--- /dev/null
+++ b/apps/dav/tests/unit/caldav/calendartest.php
@@ -0,0 +1,64 @@
+<?php
+/**
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\DAV\Tests\Unit\CalDAV;
+
+use OCA\DAV\CalDAV\CalDavBackend;
+use OCA\DAV\CalDAV\Calendar;
+use Test\TestCase;
+
+class CalendarTest extends TestCase {
+
+ public function testDelete() {
+ /** @var \PHPUnit_Framework_MockObject_MockObject | CalDavBackend $backend */
+ $backend = $this->getMockBuilder('OCA\DAV\CalDAV\CalDavBackend')->disableOriginalConstructor()->getMock();
+ $backend->expects($this->once())->method('updateShares');
+ $backend->method('getShares')->willReturn([
+ ['href' => 'principal:user2']
+ ]);
+ $calendarInfo = [
+ '{http://owncloud.org/ns}owner-principal' => 'user1',
+ 'principaluri' => 'user2',
+ 'id' => 666
+ ];
+ $c = new Calendar($backend, $calendarInfo);
+ $c->delete();
+ }
+
+ /**
+ * @expectedException \Sabre\DAV\Exception\Forbidden
+ */
+ public function testDeleteFromGroup() {
+ /** @var \PHPUnit_Framework_MockObject_MockObject | CalDavBackend $backend */
+ $backend = $this->getMockBuilder('OCA\DAV\CalDAV\CalDavBackend')->disableOriginalConstructor()->getMock();
+ $backend->expects($this->never())->method('updateShares');
+ $backend->method('getShares')->willReturn([
+ ['href' => 'principal:group2']
+ ]);
+ $calendarInfo = [
+ '{http://owncloud.org/ns}owner-principal' => 'user1',
+ 'principaluri' => 'user2',
+ 'id' => 666
+ ];
+ $c = new Calendar($backend, $calendarInfo);
+ $c->delete();
+ }
+}
diff --git a/apps/dav/tests/unit/carddav/addressbooktest.php b/apps/dav/tests/unit/carddav/addressbooktest.php
new file mode 100644
index 00000000000..d714fc71679
--- /dev/null
+++ b/apps/dav/tests/unit/carddav/addressbooktest.php
@@ -0,0 +1,64 @@
+<?php
+/**
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\DAV\Tests\Unit\CardDAV;
+
+use OCA\DAV\CardDAV\AddressBook;
+use OCA\DAV\CardDAV\CardDavBackend;
+use Test\TestCase;
+
+class AddressBookTest extends TestCase {
+
+ public function testDelete() {
+ /** @var \PHPUnit_Framework_MockObject_MockObject | CardDavBackend $backend */
+ $backend = $this->getMockBuilder('OCA\DAV\CardDAV\CardDavBackend')->disableOriginalConstructor()->getMock();
+ $backend->expects($this->once())->method('updateShares');
+ $backend->method('getShares')->willReturn([
+ ['href' => 'principal:user2']
+ ]);
+ $calendarInfo = [
+ '{http://owncloud.org/ns}owner-principal' => 'user1',
+ 'principaluri' => 'user2',
+ 'id' => 666
+ ];
+ $c = new AddressBook($backend, $calendarInfo);
+ $c->delete();
+ }
+
+ /**
+ * @expectedException \Sabre\DAV\Exception\Forbidden
+ */
+ public function testDeleteFromGroup() {
+ /** @var \PHPUnit_Framework_MockObject_MockObject | CardDavBackend $backend */
+ $backend = $this->getMockBuilder('OCA\DAV\CardDAV\CardDavBackend')->disableOriginalConstructor()->getMock();
+ $backend->expects($this->never())->method('updateShares');
+ $backend->method('getShares')->willReturn([
+ ['href' => 'principal:group2']
+ ]);
+ $calendarInfo = [
+ '{http://owncloud.org/ns}owner-principal' => 'user1',
+ 'principaluri' => 'user2',
+ 'id' => 666
+ ];
+ $c = new AddressBook($backend, $calendarInfo);
+ $c->delete();
+ }
+}
diff --git a/apps/dav/tests/unit/carddav/carddavbackendtest.php b/apps/dav/tests/unit/carddav/carddavbackendtest.php
index 0158330a194..86bc26b4c0d 100644
--- a/apps/dav/tests/unit/carddav/carddavbackendtest.php
+++ b/apps/dav/tests/unit/carddav/carddavbackendtest.php
@@ -27,7 +27,6 @@ use OCA\DAV\CardDAV\AddressBook;
use OCA\DAV\CardDAV\CardDavBackend;
use OCA\DAV\Connector\Sabre\Principal;
use OCP\IDBConnection;
-use OCP\ILogger;
use Sabre\DAV\PropPatch;
use Sabre\VObject\Component\VCard;
use Sabre\VObject\Property\Text;
@@ -57,19 +56,24 @@ class CardDavBackendTest extends TestCase {
/** @var string */
private $dbCardsPropertiesTable = 'cards_properties';
- const UNIT_TEST_USER = 'carddav-unit-test';
+ const UNIT_TEST_USER = 'principals/users/carddav-unit-test';
+ const UNIT_TEST_USER1 = 'principals/users/carddav-unit-test1';
+ const UNIT_TEST_GROUP = 'principals/groups/carddav-unit-test-group';
public function setUp() {
parent::setUp();
$this->principal = $this->getMockBuilder('OCA\DAV\Connector\Sabre\Principal')
->disableOriginalConstructor()
- ->setMethods(['getPrincipalByPath'])
+ ->setMethods(['getPrincipalByPath', 'getGroupMembership'])
->getMock();
$this->principal->method('getPrincipalByPath')
->willReturn([
'uri' => 'principals/best-friend'
]);
+ $this->principal->method('getGroupMembership')
+ ->withAnyParameters()
+ ->willReturn([self::UNIT_TEST_GROUP]);
$this->db = \OC::$server->getDatabaseConnection();
@@ -124,6 +128,29 @@ class CardDavBackendTest extends TestCase {
$this->assertEquals(0, count($books));
}
+ public function testAddressBookSharing() {
+
+ $this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
+ $books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
+ $this->assertEquals(1, count($books));
+ $addressBook = new AddressBook($this->backend, $books[0]);
+ $this->backend->updateShares($addressBook, [
+ [
+ 'href' => 'principal:' . self::UNIT_TEST_USER1,
+ ],
+ [
+ 'href' => 'principal:' . self::UNIT_TEST_GROUP,
+ ]
+ ], []);
+ $books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER1);
+ $this->assertEquals(1, count($books));
+
+ // delete the address book
+ $this->backend->deleteAddressBook($books[0]['id']);
+ $books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
+ $this->assertEquals(0, count($books));
+ }
+
public function testCardOperations() {
/** @var CardDavBackend | \PHPUnit_Framework_MockObject_MockObject $backend */
diff --git a/apps/files_external/l10n/it.js b/apps/files_external/l10n/it.js
index d9adaad833b..b6fad31111d 100644
--- a/apps/files_external/l10n/it.js
+++ b/apps/files_external/l10n/it.js
@@ -29,6 +29,7 @@ OC.L10N.register(
"Error generating key pair" : "Errore durante la generazione della coppia di chiavi",
"Enable encryption" : "Abilita cifratura",
"Enable previews" : "Abilita le anteprime",
+ "Enable sharing" : "Abilita condivisione",
"Check for changes" : "Controlla le modifiche",
"Never" : "Mai",
"Once every direct access" : "Una volta per ogni accesso diretto",
diff --git a/apps/files_external/l10n/it.json b/apps/files_external/l10n/it.json
index b228cd76604..e253c22b6b1 100644
--- a/apps/files_external/l10n/it.json
+++ b/apps/files_external/l10n/it.json
@@ -27,6 +27,7 @@
"Error generating key pair" : "Errore durante la generazione della coppia di chiavi",
"Enable encryption" : "Abilita cifratura",
"Enable previews" : "Abilita le anteprime",
+ "Enable sharing" : "Abilita condivisione",
"Check for changes" : "Controlla le modifiche",
"Never" : "Mai",
"Once every direct access" : "Una volta per ogni accesso diretto",
diff --git a/apps/files_external/l10n/pt_PT.js b/apps/files_external/l10n/pt_PT.js
index b84c399e34d..ef75f504bcf 100644
--- a/apps/files_external/l10n/pt_PT.js
+++ b/apps/files_external/l10n/pt_PT.js
@@ -29,6 +29,7 @@ OC.L10N.register(
"Error generating key pair" : "Erro ao gerar chave par",
"Enable encryption" : "Ative a encriptação",
"Enable previews" : "Ative as pré-visualizações",
+ "Enable sharing" : "Ativar partilha",
"Check for changes" : "Verifique as suas alterações",
"Never" : "Nunca",
"Once every direct access" : "Uma vez em cada acesso direto",
diff --git a/apps/files_external/l10n/pt_PT.json b/apps/files_external/l10n/pt_PT.json
index ac65ca7744f..686802379ee 100644
--- a/apps/files_external/l10n/pt_PT.json
+++ b/apps/files_external/l10n/pt_PT.json
@@ -27,6 +27,7 @@
"Error generating key pair" : "Erro ao gerar chave par",
"Enable encryption" : "Ative a encriptação",
"Enable previews" : "Ative as pré-visualizações",
+ "Enable sharing" : "Ativar partilha",
"Check for changes" : "Verifique as suas alterações",
"Never" : "Nunca",
"Once every direct access" : "Uma vez em cada acesso direto",
diff --git a/core/css/systemtags.css b/core/css/systemtags.css
index 2a02bc05ea7..588c76e1e3f 100644
--- a/core/css/systemtags.css
+++ b/core/css/systemtags.css
@@ -76,4 +76,10 @@
.systemtags-select2-container .select2-choices .select2-search-field input {
line-height: 20px;
}
-
+.systemtags-select2-dropdown .label {
+ width:85%;
+ display:-moz-inline-box;
+ display:inline-block;
+ overflow:hidden;
+ text-overflow:ellipsis;
+}
diff --git a/lib/private/comments/comment.php b/lib/private/comments/comment.php
index 75964603f9f..36189d9523b 100644
--- a/lib/private/comments/comment.php
+++ b/lib/private/comments/comment.php
@@ -242,7 +242,7 @@ class Comment implements IComment {
/**
* sets (overwrites) the actor type and id
*
- * @param string $actorType e.g. 'user'
+ * @param string $actorType e.g. 'users'
* @param string $actorId e.g. 'zombie234'
* @return IComment
* @since 9.0.0
@@ -328,7 +328,7 @@ class Comment implements IComment {
/**
* sets (overwrites) the object of the comment
*
- * @param string $objectType e.g. 'file'
+ * @param string $objectType e.g. 'files'
* @param string $objectId e.g. '16435'
* @return IComment
* @since 9.0.0
diff --git a/lib/private/comments/manager.php b/lib/private/comments/manager.php
index 28bd3b0916a..0cacc1cce71 100644
--- a/lib/private/comments/manager.php
+++ b/lib/private/comments/manager.php
@@ -384,7 +384,7 @@ class Manager implements ICommentsManager {
* saved in the used data storage. Use save() after setting other fields
* of the comment (e.g. message or verb).
*
- * @param string $actorType the actor type (e.g. 'user')
+ * @param string $actorType the actor type (e.g. 'users')
* @param string $actorId a user id
* @param string $objectType the object type the comment is attached to
* @param string $objectId the object id the comment is attached to
@@ -533,7 +533,7 @@ class Manager implements ICommentsManager {
* removes references to specific actor (e.g. on user delete) of a comment.
* The comment itself must not get lost/deleted.
*
- * @param string $actorType the actor type (e.g. 'user')
+ * @param string $actorType the actor type (e.g. 'users')
* @param string $actorId a user id
* @return boolean
* @since 9.0.0
@@ -560,7 +560,7 @@ class Manager implements ICommentsManager {
/**
* deletes all comments made of a specific object (e.g. on file delete)
*
- * @param string $objectType the object type (e.g. 'file')
+ * @param string $objectType the object type (e.g. 'files')
* @param string $objectId e.g. the file id
* @return boolean
* @since 9.0.0
diff --git a/lib/private/user/manager.php b/lib/private/user/manager.php
index 86750dcd994..6798a7340c3 100644
--- a/lib/private/user/manager.php
+++ b/lib/private/user/manager.php
@@ -265,6 +265,10 @@ class Manager extends PublicEmitter implements IUserManager {
if (trim($uid) == '') {
throw new \Exception($l->t('A valid username must be provided'));
}
+ // No whitespace at the beginning or at the end
+ if (strlen(trim($uid, "\t\n\r\0\x0B\xe2\x80\x8b")) !== strlen(trim($uid))) {
+ throw new \Exception($l->t('Username contains whitespace at the beginning or at the end'));
+ }
// No empty password
if (trim($password) == '') {
throw new \Exception($l->t('A valid password must be provided'));
diff --git a/lib/private/user/user.php b/lib/private/user/user.php
index 5e556575118..516c1d443c6 100644
--- a/lib/private/user/user.php
+++ b/lib/private/user/user.php
@@ -210,7 +210,7 @@ class User implements IUser {
// Delete the users entry in the storage table
\OC\Files\Cache\Storage::remove('home::' . $this->uid);
- \OC::$server->getCommentsManager()->deleteReferencesOfActor('user', $this->uid);
+ \OC::$server->getCommentsManager()->deleteReferencesOfActor('users', $this->uid);
\OC::$server->getCommentsManager()->deleteReadMarksFromUser($this);
}
diff --git a/lib/public/comments/icomment.php b/lib/public/comments/icomment.php
index d97cfe3f1cc..e695b5193f2 100644
--- a/lib/public/comments/icomment.php
+++ b/lib/public/comments/icomment.php
@@ -161,7 +161,7 @@ interface IComment {
/**
* sets (overwrites) the actor type and id
*
- * @param string $actorType e.g. 'user'
+ * @param string $actorType e.g. 'users'
* @param string $actorId e.g. 'zombie234'
* @return IComment
* @since 9.0.0
@@ -223,7 +223,7 @@ interface IComment {
/**
* sets (overwrites) the object of the comment
*
- * @param string $objectType e.g. 'file'
+ * @param string $objectType e.g. 'files'
* @param string $objectId e.g. '16435'
* @return IComment
* @since 9.0.0
diff --git a/lib/public/comments/icommentsmanager.php b/lib/public/comments/icommentsmanager.php
index f5b290bf8b2..46608ca8165 100644
--- a/lib/public/comments/icommentsmanager.php
+++ b/lib/public/comments/icommentsmanager.php
@@ -39,7 +39,7 @@ interface ICommentsManager {
*
* User interfaces shall show "Deleted user" as display name, if needed.
*/
- const DELETED_USER = 'deleted_user';
+ const DELETED_USER = 'deleted_users';
/**
* returns a comment instance
@@ -127,7 +127,7 @@ interface ICommentsManager {
* saved in the used data storage. Use save() after setting other fields
* of the comment (e.g. message or verb).
*
- * @param string $actorType the actor type (e.g. 'user')
+ * @param string $actorType the actor type (e.g. 'users')
* @param string $actorId a user id
* @param string $objectType the object type the comment is attached to
* @param string $objectId the object id the comment is attached to
@@ -170,10 +170,10 @@ interface ICommentsManager {
* removes references to specific actor (e.g. on user delete) of a comment.
* The comment itself must not get lost/deleted.
*
- * A 'user' type actor (type and id) should get replaced by the
+ * A 'users' type actor (type and id) should get replaced by the
* value of the DELETED_USER constant of this interface.
*
- * @param string $actorType the actor type (e.g. 'user')
+ * @param string $actorType the actor type (e.g. 'users')
* @param string $actorId a user id
* @return boolean
* @since 9.0.0
@@ -183,7 +183,7 @@ interface ICommentsManager {
/**
* deletes all comments made of a specific object (e.g. on file delete)
*
- * @param string $objectType the object type (e.g. 'file')
+ * @param string $objectType the object type (e.g. 'files')
* @param string $objectId e.g. the file id
* @return boolean
* @since 9.0.0
diff --git a/tests/lib/comments/comment.php b/tests/lib/comments/comment.php
index 02adea8729e..e6f9c941c96 100644
--- a/tests/lib/comments/comment.php
+++ b/tests/lib/comments/comment.php
@@ -15,10 +15,10 @@ class Test_Comments_Comment extends TestCase
$childrenCount = 6;
$message = 'I like to comment comment';
$verb = 'comment';
- $actor = ['type' => 'user', 'id' => 'alice'];
+ $actor = ['type' => 'users', 'id' => 'alice'];
$creationDT = new \DateTime();
$latestChildDT = new \DateTime('yesterday');
- $object = ['type' => 'file', 'id' => 'file64'];
+ $object = ['type' => 'files', 'id' => 'file64'];
$comment
->setId($id)
@@ -87,11 +87,11 @@ class Test_Comments_Comment extends TestCase
public function roleSetterProvider() {
return [
['Actor', true, true],
- ['Actor', 'user', true],
+ ['Actor', 'users', true],
['Actor', true, 'alice'],
['Actor', ' ', ' '],
['Object', true, true],
- ['Object', 'file', true],
+ ['Object', 'files', true],
['Object', true, 'file64'],
['Object', ' ', ' '],
];
diff --git a/tests/lib/comments/manager.php b/tests/lib/comments/manager.php
index a71f78f2818..c55f4728883 100644
--- a/tests/lib/comments/manager.php
+++ b/tests/lib/comments/manager.php
@@ -35,13 +35,13 @@ class Test_Comments_Manager extends TestCase
'parent_id' => $qb->createNamedParameter($parentId),
'topmost_parent_id' => $qb->createNamedParameter($topmostParentId),
'children_count' => $qb->createNamedParameter(2),
- 'actor_type' => $qb->createNamedParameter('user'),
+ 'actor_type' => $qb->createNamedParameter('users'),
'actor_id' => $qb->createNamedParameter('alice'),
'message' => $qb->createNamedParameter('nice one'),
'verb' => $qb->createNamedParameter('comment'),
'creation_timestamp' => $qb->createNamedParameter($creationDT, 'datetime'),
'latest_child_timestamp' => $qb->createNamedParameter($latestChildDT, 'datetime'),
- 'object_type' => $qb->createNamedParameter('file'),
+ 'object_type' => $qb->createNamedParameter('files'),
'object_id' => $qb->createNamedParameter('file64'),
])
->execute();
@@ -83,13 +83,13 @@ class Test_Comments_Manager extends TestCase
'parent_id' => $qb->createNamedParameter('2'),
'topmost_parent_id' => $qb->createNamedParameter('1'),
'children_count' => $qb->createNamedParameter(2),
- 'actor_type' => $qb->createNamedParameter('user'),
+ 'actor_type' => $qb->createNamedParameter('users'),
'actor_id' => $qb->createNamedParameter('alice'),
'message' => $qb->createNamedParameter('nice one'),
'verb' => $qb->createNamedParameter('comment'),
'creation_timestamp' => $qb->createNamedParameter($creationDT, 'datetime'),
'latest_child_timestamp' => $qb->createNamedParameter($latestChildDT, 'datetime'),
- 'object_type' => $qb->createNamedParameter('file'),
+ 'object_type' => $qb->createNamedParameter('files'),
'object_id' => $qb->createNamedParameter('file64'),
])
->execute();
@@ -102,11 +102,11 @@ class Test_Comments_Manager extends TestCase
$this->assertSame($comment->getParentId(), '2');
$this->assertSame($comment->getTopmostParentId(), '1');
$this->assertSame($comment->getChildrenCount(), 2);
- $this->assertSame($comment->getActorType(), 'user');
+ $this->assertSame($comment->getActorType(), 'users');
$this->assertSame($comment->getActorId(), 'alice');
$this->assertSame($comment->getMessage(), 'nice one');
$this->assertSame($comment->getVerb(), 'comment');
- $this->assertSame($comment->getObjectType(), 'file');
+ $this->assertSame($comment->getObjectType(), 'files');
$this->assertSame($comment->getObjectId(), 'file64');
$this->assertEquals($comment->getCreationDateTime(), $creationDT);
$this->assertEquals($comment->getLatestChildDateTime(), $latestChildDT);
@@ -207,7 +207,7 @@ class Test_Comments_Manager extends TestCase
$this->addDatabaseEntry(0, 0);
$manager = $this->getManager();
- $comments = $manager->getForObject('file', 'file64');
+ $comments = $manager->getForObject('files', 'file64');
$this->assertTrue(is_array($comments));
$this->assertSame(count($comments), 1);
@@ -227,7 +227,7 @@ class Test_Comments_Manager extends TestCase
$manager = $this->getManager();
$offset = 0;
do {
- $comments = $manager->getForObject('file', 'file64', 3, $offset);
+ $comments = $manager->getForObject('files', 'file64', 3, $offset);
$this->assertTrue(is_array($comments));
foreach($comments as $comment) {
@@ -247,7 +247,7 @@ class Test_Comments_Manager extends TestCase
$id2 = $this->addDatabaseEntry(2, 2, new \DateTime('-2 hours'));
$manager = $this->getManager();
- $comments = $manager->getForObject('file', 'file64', 0, 0, new \DateTime('-4 hours'));
+ $comments = $manager->getForObject('files', 'file64', 0, 0, new \DateTime('-4 hours'));
$this->assertSame(count($comments), 2);
$this->assertSame($comments[0]->getId(), strval($id2));
@@ -266,7 +266,7 @@ class Test_Comments_Manager extends TestCase
$manager = $this->getManager();
$offset = 0;
do {
- $comments = $manager->getForObject('file', 'file64', 3, $offset, new \DateTime('-4 hours'));
+ $comments = $manager->getForObject('files', 'file64', 3, $offset, new \DateTime('-4 hours'));
$this->assertTrue(is_array($comments));
foreach($comments as $comment) {
@@ -290,7 +290,7 @@ class Test_Comments_Manager extends TestCase
$amount = $manager->getNumberOfCommentsForObject('untype', '00');
$this->assertSame($amount, 0);
- $amount = $manager->getNumberOfCommentsForObject('file', 'file64');
+ $amount = $manager->getNumberOfCommentsForObject('files', 'file64');
$this->assertSame($amount, 4);
}
@@ -357,8 +357,8 @@ class Test_Comments_Manager extends TestCase
$manager = $this->getManager();
$comment = new \OC\Comments\Comment();
$comment
- ->setActor('user', 'alice')
- ->setObject('file', 'file64')
+ ->setActor('users', 'alice')
+ ->setObject('files', 'file64')
->setMessage('very beautiful, I am impressed!')
->setVerb('comment');
@@ -377,8 +377,8 @@ class Test_Comments_Manager extends TestCase
$manager = $this->getManager();
$comment = new \OC\Comments\Comment();
$comment
- ->setActor('user', 'alice')
- ->setObject('file', 'file64')
+ ->setActor('users', 'alice')
+ ->setObject('files', 'file64')
->setMessage('very beautiful, I am impressed!')
->setVerb('comment');
@@ -398,8 +398,8 @@ class Test_Comments_Manager extends TestCase
$manager = $this->getManager();
$comment = new \OC\Comments\Comment();
$comment
- ->setActor('user', 'alice')
- ->setObject('file', 'file64')
+ ->setActor('users', 'alice')
+ ->setObject('files', 'file64')
->setMessage('very beautiful, I am impressed!')
->setVerb('comment');
@@ -428,8 +428,8 @@ class Test_Comments_Manager extends TestCase
for($i = 0; $i < 3; $i++) {
$comment = new \OC\Comments\Comment();
$comment
- ->setActor('user', 'alice')
- ->setObject('file', 'file64')
+ ->setActor('users', 'alice')
+ ->setObject('files', 'file64')
->setParentId(strval($id))
->setMessage('full ack')
->setVerb('comment')
@@ -450,7 +450,7 @@ class Test_Comments_Manager extends TestCase
[
['', ''],
[1, 'alice'],
- ['user', 1],
+ ['users', 1],
];
}
@@ -473,10 +473,10 @@ class Test_Comments_Manager extends TestCase
// just to make sure they are really set, with correct actor data
$comment = $manager->get(strval($ids[1]));
- $this->assertSame($comment->getActorType(), 'user');
+ $this->assertSame($comment->getActorType(), 'users');
$this->assertSame($comment->getActorId(), 'alice');
- $wasSuccessful = $manager->deleteReferencesOfActor('user', 'alice');
+ $wasSuccessful = $manager->deleteReferencesOfActor('users', 'alice');
$this->assertTrue($wasSuccessful);
foreach($ids as $id) {
@@ -487,7 +487,7 @@ class Test_Comments_Manager extends TestCase
// actor info is gone from DB, but when database interaction is alright,
// we still expect to get true back
- $wasSuccessful = $manager->deleteReferencesOfActor('user', 'alice');
+ $wasSuccessful = $manager->deleteReferencesOfActor('users', 'alice');
$this->assertTrue($wasSuccessful);
}
@@ -496,7 +496,7 @@ class Test_Comments_Manager extends TestCase
$this->assertTrue($user instanceof \OCP\IUser);
$manager = \OC::$server->getCommentsManager();
- $comment = $manager->create('user', $user->getUID(), 'file', 'file64');
+ $comment = $manager->create('users', $user->getUID(), 'files', 'file64');
$comment
->setMessage('Most important comment I ever left on the Internet.')
->setVerb('comment');
@@ -516,7 +516,7 @@ class Test_Comments_Manager extends TestCase
[
['', ''],
[1, 'file64'],
- ['file', 1],
+ ['files', 1],
];
}
@@ -539,10 +539,10 @@ class Test_Comments_Manager extends TestCase
// just to make sure they are really set, with correct actor data
$comment = $manager->get(strval($ids[1]));
- $this->assertSame($comment->getObjectType(), 'file');
+ $this->assertSame($comment->getObjectType(), 'files');
$this->assertSame($comment->getObjectId(), 'file64');
- $wasSuccessful = $manager->deleteCommentsAtObject('file', 'file64');
+ $wasSuccessful = $manager->deleteCommentsAtObject('files', 'file64');
$this->assertTrue($wasSuccessful);
$verified = 0;
@@ -557,7 +557,7 @@ class Test_Comments_Manager extends TestCase
// actor info is gone from DB, but when database interaction is alright,
// we still expect to get true back
- $wasSuccessful = $manager->deleteCommentsAtObject('file', 'file64');
+ $wasSuccessful = $manager->deleteCommentsAtObject('files', 'file64');
$this->assertTrue($wasSuccessful);
}