462 lines
14 KiB
JavaScript
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;
|
|
};
|
|
|
|
}]);
|