nextcloud-tasks/js/app/services/calendarservice.js

462 lines
14 KiB
JavaScript

/**
* Nextcloud - Tasks
*
* @author Raghu Nayyar
* @author Georg Ehrke
* @author Raimund Schlüßler
* @copyright 2017 Raghu Nayyar <beingminimal@gmail.com>
* @copyright 2017 Georg Ehrke <oc.list@georgehrke.com>
* @copyright 2017 Raimund Schlüßler <raimund.schluessler@googlemail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
angular.module('Tasks').service('CalendarService', ['DavClient', 'Calendar', function(DavClient, Calendar){
'use strict';
var _this = this;
this._CALENDAR_HOME = null;
this._currentUserPrincipal = null;
this._takenUrls = [];
this._PROPERTIES = [
'{' + DavClient.NS_DAV + '}displayname',
'{' + DavClient.NS_IETF + '}calendar-description',
'{' + DavClient.NS_IETF + '}calendar-timezone',
'{' + DavClient.NS_APPLE + '}calendar-order',
'{' + DavClient.NS_APPLE + '}calendar-color',
'{' + DavClient.NS_IETF + '}supported-calendar-component-set',
'{' + DavClient.NS_OWNCLOUD + '}calendar-enabled',
'{' + DavClient.NS_DAV + '}acl',
'{' + DavClient.NS_DAV + '}owner',
'{' + DavClient.NS_OWNCLOUD + '}invite'
];
function discoverHome(callback) {
return DavClient.propFind(DavClient.buildUrl(OC.linkToRemoteBase('dav')), ['{' + DavClient.NS_DAV + '}current-user-principal'], 0, {'requesttoken': OC.requestToken}).then(function(response) {
if (!DavClient.wasRequestSuccessful(response.status)) {
throw "CalDAV client could not be initialized - Querying current-user-principal failed";
}
if (response.body.propStat.length < 1) {
return;
}
var props = response.body.propStat[0].properties;
_this._currentUserPrincipal = props['{' + DavClient.NS_DAV + '}current-user-principal'][0].textContent;
return DavClient.propFind(DavClient.buildUrl(_this._currentUserPrincipal), ['{' + DavClient.NS_IETF + '}calendar-home-set'], 0, {'requesttoken': OC.requestToken}).then(function (response) {
if (!DavClient.wasRequestSuccessful(response.status)) {
throw "CalDAV client could not be initialized - Querying calendar-home-set failed";
}
if (response.body.propStat.length < 1) {
return;
}
var props = response.body.propStat[0].properties;
_this._CALENDAR_HOME = props['{' + DavClient.NS_IETF + '}calendar-home-set'][0].textContent;
return callback();
});
});
}
function getResponseCodeFromHTTPResponse(t) {
return parseInt(t.split(' ')[1]);
}
this.getAll = function() {
if (this._CALENDAR_HOME === null) {
return discoverHome(function() {
return _this.getAll();
});
}
var prom = DavClient.propFind(DavClient.buildUrl(this._CALENDAR_HOME), this._PROPERTIES, 1, {'requesttoken': OC.requestToken}).then(function(response) {
var calendars = [];
if (!DavClient.wasRequestSuccessful(response.status)) {
throw "CalDAV client could not be initialized - Querying calendars failed";
}
for (var i = 0; i < response.body.length; i++) {
var body = response.body[i];
if (body.propStat.length < 1) {
continue;
}
_this._takenUrls.push(body.href);
var responseCode = getResponseCodeFromHTTPResponse(body.propStat[0].status);
if (!DavClient.wasRequestSuccessful(responseCode)) {
continue;
}
var doesSupportVTodo = false;
var components = body.propStat[0].properties['{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set'];
if (components) {
for (var j=0; j < components.length; j++) {
var name = components[j].attributes.getNamedItem('name').textContent.toLowerCase();
if (name === 'vtodo') {
doesSupportVTodo = true;
}
}
}
if (!doesSupportVTodo) {
continue;
}
_this._getACLFromResponse(body);
var uri = body.href.substr(_this._CALENDAR_HOME.length).replace(/[^\w\-]+/g, '');
var calendar = new Calendar(body.href, body.propStat[0].properties, uri);
calendars.push(calendar);
}
return calendars;
});
return prom;
};
this.get = function(url) {
if (this._CALENDAR_HOME === null) {
return discoverHome(function() {
return _this.get(url);
});
}
return DavClient.propFind(DavClient.buildUrl(url), this._PROPERTIES, 0, {'requesttoken': OC.requestToken}).then(function(response) {
var body = response.body;
if (body.propStat.length < 1) {
//TODO - something went wrong
return;
}
var responseCode = getResponseCodeFromHTTPResponse(body.propStat[0].status);
if (!DavClient.wasRequestSuccessful(responseCode)) {
//TODO - something went wrong
return;
}
_this._getACLFromResponse(body);
var uri = body.href.substr(_this._CALENDAR_HOME.length).replace(/[^\w\-]+/g, '');
return new Calendar(body.href, body.propStat[0].properties, uri);
});
};
this.create = function(name, color, components) {
if (this._CALENDAR_HOME === null) {
return discoverHome(function() {
return _this.create(name, color);
});
}
if (typeof components === 'undefined') {
components = ['vtodo'];
}
var xmlDoc = document.implementation.createDocument('', '', null);
var cMkcalendar = xmlDoc.createElement('c:mkcalendar');
cMkcalendar.setAttribute('xmlns:c', 'urn:ietf:params:xml:ns:caldav');
cMkcalendar.setAttribute('xmlns:d', 'DAV:');
cMkcalendar.setAttribute('xmlns:a', 'http://apple.com/ns/ical/');
cMkcalendar.setAttribute('xmlns:o', 'http://owncloud.org/ns');
xmlDoc.appendChild(cMkcalendar);
var dSet = xmlDoc.createElement('d:set');
cMkcalendar.appendChild(dSet);
var dProp = xmlDoc.createElement('d:prop');
dSet.appendChild(dProp);
dProp.appendChild(this._createXMLForProperty(xmlDoc, 'displayname', name));
dProp.appendChild(this._createXMLForProperty(xmlDoc, 'enabled', true));
dProp.appendChild(this._createXMLForProperty(xmlDoc, 'color', color));
dProp.appendChild(this._createXMLForProperty(xmlDoc, 'components', components));
var body = cMkcalendar.outerHTML;
var uri = this._suggestUri(name);
var url = this._CALENDAR_HOME + uri + '/';
var headers = {
'Content-Type' : 'application/xml; charset=utf-8',
'requesttoken' : OC.requestToken
};
return DavClient.request('MKCALENDAR', url, headers, body).then(function(response) {
if (response.status === 201) {
_this._takenUrls.push(url);
return _this.get(url).then(function(calendar) {
calendar.enabled = true;
return _this.update(calendar);
});
}
});
};
this.update = function(calendar) {
var xmlDoc = document.implementation.createDocument('', '', null);
var dPropUpdate = xmlDoc.createElement('d:propertyupdate');
dPropUpdate.setAttribute('xmlns:c', 'urn:ietf:params:xml:ns:caldav');
dPropUpdate.setAttribute('xmlns:d', 'DAV:');
dPropUpdate.setAttribute('xmlns:a', 'http://apple.com/ns/ical/');
dPropUpdate.setAttribute('xmlns:o', 'http://owncloud.org/ns');
xmlDoc.appendChild(dPropUpdate);
var dSet = xmlDoc.createElement('d:set');
dPropUpdate.appendChild(dSet);
var dProp = xmlDoc.createElement('d:prop');
dSet.appendChild(dProp);
var updatedProperties = calendar.updatedProperties;
calendar.resetUpdatedProperties();
for (var i=0; i < updatedProperties.length; i++) {
dProp.appendChild(this._createXMLForProperty(
xmlDoc,
updatedProperties[i],
calendar[updatedProperties[i]]
));
}
var url = calendar.url;
var body = dPropUpdate.outerHTML;
var headers = {
'Content-Type' : 'application/xml; charset=utf-8',
'requesttoken' : OC.requestToken
};
return DavClient.request('PROPPATCH', url, headers, body).then(function(response) {
return calendar;
});
};
this.delete = function(calendar) {
return DavClient.request('DELETE', calendar.url, {'requesttoken': OC.requestToken}, '').then(function(response) {
if (response.status === 204) {
return true;
} else {
// TODO - handle error case
return false;
}
});
};
this.share = function(calendar, shareType, shareWith, writable, existingShare) {
var xmlDoc = document.implementation.createDocument('', '', null);
var oShare = xmlDoc.createElement('o:share');
oShare.setAttribute('xmlns:d', 'DAV:');
oShare.setAttribute('xmlns:o', 'http://owncloud.org/ns');
xmlDoc.appendChild(oShare);
var oSet = xmlDoc.createElement('o:set');
oShare.appendChild(oSet);
var dHref = xmlDoc.createElement('d:href');
if (shareType === OC.Share.SHARE_TYPE_USER) {
dHref.textContent = 'principal:principals/users/';
} else if (shareType === OC.Share.SHARE_TYPE_GROUP) {
dHref.textContent = 'principal:principals/groups/';
}
dHref.textContent += shareWith;
oSet.appendChild(dHref);
var oSummary = xmlDoc.createElement('o:summary');
oSummary.textContent = t('calendar', '{calendar} shared by {owner}', {
calendar: calendar.displayname,
owner: calendar.owner
});
oSet.appendChild(oSummary);
if (writable) {
var oRW = xmlDoc.createElement('o:read-write');
oSet.appendChild(oRW);
}
var headers = {
'Content-Type' : 'application/xml; charset=utf-8',
requesttoken : oc_requesttoken
};
var body = oShare.outerHTML;
return DavClient.request('POST', calendar.url, headers, body).then(function(response) {
if (response.status === 200) {
if (!existingShare) {
if (shareType === OC.Share.SHARE_TYPE_USER) {
calendar.sharedWith.users.push({
id: shareWith,
displayname: shareWith,
writable: writable
});
} else if (shareType === OC.Share.SHARE_TYPE_GROUP) {
calendar.sharedWith.groups.push({
id: shareWith,
displayname: shareWith,
writable: writable
});
}
}
}
});
};
this.unshare = function(calendar, shareType, shareWith) {
var xmlDoc = document.implementation.createDocument('', '', null);
var oShare = xmlDoc.createElement('o:share');
oShare.setAttribute('xmlns:d', 'DAV:');
oShare.setAttribute('xmlns:o', 'http://owncloud.org/ns');
xmlDoc.appendChild(oShare);
var oRemove = xmlDoc.createElement('o:remove');
oShare.appendChild(oRemove);
var dHref = xmlDoc.createElement('d:href');
if (shareType === OC.Share.SHARE_TYPE_USER) {
dHref.textContent = 'principal:principals/users/';
} else if (shareType === OC.Share.SHARE_TYPE_GROUP) {
dHref.textContent = 'principal:principals/groups/';
}
dHref.textContent += shareWith;
oRemove.appendChild(dHref);
var headers = {
'Content-Type' : 'application/xml; charset=utf-8',
requesttoken: oc_requesttoken
};
var body = oShare.outerHTML;
return DavClient.request('POST', calendar.url, headers, body).then(function(response) {
if (response.status === 200) {
if (shareType === OC.Share.SHARE_TYPE_USER) {
calendar.sharedWith.users = calendar.sharedWith.users.filter(function(user) {
return user.id !== shareWith;
});
} else if (shareType === OC.Share.SHARE_TYPE_GROUP) {
calendar.sharedWith.groups = calendar.sharedWith.groups.filter(function(groups) {
return groups.id !== shareWith;
});
}
//todo - remove entry from calendar object
return true;
} else {
return false;
}
});
};
this._createXMLForProperty = function(xmlDoc, propName, value) {
switch(propName) {
case 'enabled':
var oEnabled = xmlDoc.createElement('o:calendar-enabled');
oEnabled.textContent = value ? '1' : '0';
return oEnabled;
case 'displayname':
var dDisplayname = xmlDoc.createElement('d:displayname');
dDisplayname.textContent = value;
return dDisplayname;
case 'order':
var aOrder = xmlDoc.createElement('a:calendar-color');
aOrder.textContent = value;
return aOrder;
case 'color':
var aColor = xmlDoc.createElement('a:calendar-color');
aColor.textContent = value;
return aColor;
case 'components':
var cComponents = xmlDoc.createElement('c:supported-calendar-component-set');
for (var i=0; i < value.length; i++) {
var cComp = xmlDoc.createElement('c:comp');
cComp.setAttribute('name', value[i].toUpperCase());
cComponents.appendChild(cComp);
}
return cComponents;
}
};
this._getACLFromResponse = function(body) {
var canWrite = false;
var acl = body.propStat[0].properties['{' + DavClient.NS_DAV + '}acl'];
if (acl) {
for (var k=0; k < acl.length; k++) {
var href = acl[k].getElementsByTagNameNS('DAV:', 'href');
if (href.length === 0) {
continue;
}
href = href[0].textContent;
if (href !== _this._currentUserPrincipal) {
continue;
}
var writeNode = acl[k].getElementsByTagNameNS('DAV:', 'write');
if (writeNode.length > 0) {
canWrite = true;
}
}
}
body.propStat[0].properties.canWrite = canWrite;
};
this._isUriAlreadyTaken = function(uri) {
return (this._takenUrls.indexOf(this._CALENDAR_HOME + uri + '/') !== -1);
};
this._suggestUri = function(displayname) {
var uri = displayname.toString().toLowerCase()
.replace(/\s+/g, '-') // Replace spaces with -
.replace(/[^\w\-]+/g, '') // Remove all non-word chars
.replace(/\-\-+/g, '-') // Replace multiple - with single -
.replace(/^-+/, '') // Trim - from start of text
.replace(/-+$/, ''); // Trim - from end of text
if (!this._isUriAlreadyTaken(uri)) {
return uri;
}
if (uri.indexOf('-') === -1) {
uri = uri + '-1';
if (!this._isUriAlreadyTaken(uri)) {
return uri;
}
}
while (this._isUriAlreadyTaken(uri)) {
var positionLastDash = uri.lastIndexOf('-');
var firstPart = uri.substr(0, positionLastDash);
var lastPart = uri.substr(positionLastDash + 1);
if (lastPart.match(/^\d+$/)) {
lastPart = parseInt(lastPart);
lastPart++;
uri = firstPart + '-' + lastPart;
} else if (lastPart === '') {
uri = uri + '1';
} else {
uri = uri = '-1';
}
}
return uri;
};
}]);