');
- } else {
- if(!$('#contacts h3[data-id="' + b + '"]').length) {
- var item = $('
'
- + book.displayname+sharedindicator+'
');
- var added = false;
- $('#contacts h3').each(function(){
- if ($(this).text().toLowerCase().localeCompare(book.displayname.toLowerCase()) > 0) {
- $(this).before(item).fadeIn('fast');
- added = true;
- return false;
- }
- });
- if(!added) {
- $('#contacts').append(item);
- }
- }
- }
- $('#contacts h3[data-id="'+b+'"]').on('click', function(event) {
- $('#contacts h3').removeClass('active');
- $(this).addClass('active');
- $('#contacts ul[data-id="'+b+'"]').slideToggle(300);
- return false;
- });
- var accept = 'li:not([data-bookid="'+b+'"]),h3:not([data-id="'+b+'"])';
- $('#contacts h3[data-id="'+b+'"],#contacts ul[data-id="'+b+'"]').droppable({
- drop: OC.Contacts.Contacts.drop,
- activeClass: 'ui-state-hover',
- accept: accept
- });
- }
- var contactlist = $('#contacts ul[data-id="'+b+'"]');
- var contacts = $('#contacts ul[data-id="'+b+'"] li');
- for(var c in book.contacts) {
- if(book.contacts[c].id == undefined) { continue; }
- if(!$('#contacts li[data-id="'+book.contacts[c]['id']+'"]').length) {
- var contact = OC.Contacts.Contacts.insertContact({contactlist:contactlist, contacts:contacts, data:book.contacts[c]});
- if(c == self.batchnum-10) {
- contact.bind('inview', function(event, isInView, visiblePartX, visiblePartY) {
- $(this).unbind(event);
- var bookid = $(this).data('bookid');
- var numsiblings = $('.contacts li[data-bookid="'+bookid+'"]').length;
- if (isInView && numsiblings >= self.batchnum) {
- //console.log('This would be a good time to load more contacts.');
- OC.Contacts.Contacts.update({cid:params.cid, aid:bookid, start:$('#contacts li[data-bookid="'+bookid+'"]').length});
- }
- });
- }
- }
- }
- });
- $('#contacts h3 img.shared').tipsy()
- if($('#contacts h3').length > 1) {
- $('#contacts li,#contacts h3').draggable({
- distance: 10,
- revert: 'invalid',
- axis: 'y', containment: '#contacts',
- scroll: true, scrollSensitivity: 40,
- opacity: 0.7, helper: 'clone'
- });
- } else {
- $('#contacts h3').first().addClass('active');
- }
- if(opts['startat'] == 0) { // only update card on first load.
- OC.Contacts.Card.update(params);
- }
- } else {
- OC.Contacts.notify({message:t('contacts', 'Error')+': '+jsondata.data.message});
- }
- });
- },
- refreshThumbnail:function(id){
- var item = $('.contacts li[data-id="'+id+'"]').find('a');
- item.html(OC.Contacts.Card.fn);
- item.css('background','url('+OC.filePath('contacts', '', 'thumbnail.php')+'?id='+id+'&refresh=1'+Math.random()+') no-repeat');
- },
- scrollTo:function(id){
- var item = $('#contacts li[data-id="'+id+'"]');
- if(item && $.isNumeric(item.offset().top)) {
- //console.log('scrollTo ' + parseInt(item.offset().top));
- $('#contacts').animate({
- scrollTop: parseInt(item.offset()).top-40}, 'slow','swing');
- }
+ Contact.prototype.addProperty = function($option, name) {
+ console.log('Contact.addProperty', name)
+ switch(name) {
+ case 'NICKNAME':
+ case 'TITLE':
+ case 'ORG':
+ case 'BDAY':
+ this.$fullelem.find('[data-element="' + name.toLowerCase() + '"]').show();
+ $option.prop('disabled', true);
+ break;
+ case 'TEL':
+ case 'URL':
+ case 'EMAIL':
+ $elem = this.renderStandardProperty(name.toLowerCase());
+ var $list = this.$fullelem.find('ul.' + name.toLowerCase());
+ $list.show();
+ $list.append($elem);
+ break;
+ case 'ADR':
+ $elem = this.renderAddressProperty();
+ var $list = this.$fullelem.find('ul.' + name.toLowerCase());
+ $list.show();
+ $list.append($elem);
+ $elem.find('.adr.display').trigger('click');
+ break;
+ case 'IMPP':
+ $elem = this.renderIMProperty();
+ var $list = this.$fullelem.find('ul.' + name.toLowerCase());
+ $list.show();
+ $list.append($elem);
+ break;
}
}
-}
-$(document).ready(function(){
- OCCategories.changed = OC.Contacts.Card.categoriesChanged;
- OCCategories.app = 'contacts';
-
- var ninjahelp = $('#ninjahelp');
-
- $('#bottomcontrols .settings').on('click keydown', function() {
- try {
- ninjahelp.hide();
- OC.appSettings({appid:'contacts', loadJS:true, cache:false});
- } catch(e) {
- console.log('error:', e.message);
- }
- });
- $('#bottomcontrols .import').click(function() {
- $('#import_upload_start').trigger('click');
- });
- $('#contacts_newcontact').on('click keydown', OC.Contacts.Card.editNew);
-
- ninjahelp.find('.close').on('click keydown',function() {
- ninjahelp.hide();
- });
-
- $(document).on('keyup', function(event) {
- if(event.target.nodeName.toUpperCase() != 'BODY'
- || $('#contacts li').length == 0
- || !OC.Contacts.Card.id) {
+ Contact.prototype.deleteProperty = function(params) {
+ var obj = params.obj;
+ if(!this.enabled) {
return;
}
- //console.log(event.which + ' ' + event.target.nodeName);
- /**
- * To add:
- * Shift-a: add addressbook
- * u (85): hide/show leftcontent
- * f (70): add field
- */
- switch(event.which) {
- case 27: // Esc
- ninjahelp.hide();
- break;
- case 46: // Delete
- if(event.shiftKey) {
- OC.Contacts.Card.delayedDelete();
- }
- break;
- case 40: // down
- case 74: // j
- OC.Contacts.Contacts.next();
- break;
- case 65: // a
- if(event.shiftKey) {
- // add addressbook
- OC.Contacts.notImplemented();
- break;
- }
- OC.Contacts.Card.editNew();
- break;
- case 38: // up
- case 75: // k
- OC.Contacts.Contacts.previous();
- break;
- case 34: // PageDown
- case 78: // n
- // next addressbook
- OC.Contacts.Contacts.nextAddressbook();
- break;
- case 79: // o
- var aid = $('#contacts h3.active').first().data('id');
- if(aid) {
- $('#contacts ul[data-id="'+aid+'"]').slideToggle(300);
- }
- break;
- case 33: // PageUp
- case 80: // p
- // prev addressbook
- OC.Contacts.Contacts.previousAddressbook();
- break;
- case 82: // r
- OC.Contacts.Contacts.update({cid:OC.Contacts.Card.id});
- break;
- case 63: // ? German.
- if(event.shiftKey) {
- ninjahelp.toggle('fast');
- }
- break;
- case 171: // ? Danish
- case 191: // ? Standard qwerty
- ninjahelp.toggle('fast');
- break;
+ var element = this.propertyTypeFor(obj);
+ var $container = this.propertyContainerFor(obj);
+ console.log('Contact.deleteProperty, element', element, $container);
+ var params = {
+ name: element,
+ id: this.id
+ };
+ if(this.multi_properties.indexOf(element) !== -1) {
+ params['checksum'] = this.checksumFor(obj);
}
+ this.setAsSaving(obj, true);
+ var self = this;
+ $.post(OC.filePath('contacts', 'ajax', 'contact/deleteproperty.php'), params, function(jsondata) {
+ if(!jsondata) {
+ $(document).trigger('status.contact.error', {
+ status: 'error',
+ message: t('contacts', 'Network or server error. Please inform administrator.'),
+ });
+ self.setAsSaving(obj, false);
+ return false;
+ }
+ if(jsondata.status == 'success') {
+ // TODO: Test if removing from internal data structure works
+ if(self.multi_properties.indexOf(element) !== -1) {
+ // First find out if an existing element by looking for checksum
+ var checksum = self.checksumFor(obj);
+ if(checksum) {
+ for(var i in self.data[element]) {
+ if(self.data[element][i].checksum === checksum) {
+ // Found it
+ var prop = self.data[element][i];
+ self.data[element].splice(self.data[element].indexOf(prop), 1);
+ delete prop;
+ break;
+ }
+ }
+ }
+ $container.remove();
+ } else {
+ self.setAsSaving(obj, false);
+ self.$fullelem.find('[data-element="' + element.toLowerCase() + '"]').hide();
+ $container.find('input.value').val('');
+ self.$addMenu.find('option[value="' + element.toUpperCase() + '"]').prop('disabled', false);
+ }
+ return true;
+ } else {
+ $(document).trigger('status.contact.error', {
+ status: 'error',
+ message: jsondata.data.message,
+ });
+ self.setAsSaving(obj, false);
+ return false;
+ }
+ },'json');
+ }
- });
-
- //$(window).on('beforeunload', OC.Contacts.Contacts.deleteFilesInQueue);
-
- // Load a contact.
- $('.contacts').keydown(function(event) {
- if(event.which == 13 || event.which == 32) {
- $('.contacts').click();
+ /**
+ * @brief Act on change of a property.
+ * If this is a new contact it will first be saved to the datastore and a
+ * new datastructure will be added to the object. FIXME: Not implemented yet.
+ * If the obj argument is not provided 'name' and 'value' MUST be provided
+ * and this is only allowed for single elements like N, FN, CATEGORIES.
+ * @param obj. The form form field that has changed.
+ * @param name. The optional name of the element.
+ * @param value. The optional value.
+ */
+ Contact.prototype.saveProperty = function(params) {
+ console.log('Contact.saveProperty', params);
+ if(!this.id) {
+ var self = this;
+ this.add({isnew:true}, function(response) {
+ if(!response || response.status === 'error') {
+ console.log('No response object');
+ return false;
+ }
+ console.log('Contact added.' + self.id);
+ self.saveProperty(params);
+ });
+ return;
}
- });
- $(document).on('click', '#contacts', function(event){
- var $tgt = $(event.target);
- if ($tgt.is('li') || $tgt.is('a')) {
- var item = $tgt.is('li')?$($tgt):($tgt).parent();
- var id = item.data('id');
- var bookid = item.data('bookid');
- item.addClass('active');
- var oldid = $('#rightcontent').data('id');
- if(oldid != 0){
- var olditem = $('.contacts li[data-id="'+oldid+'"]');
- var oldbookid = olditem.data('bookid');
- olditem.removeClass('active');
- if(oldbookid != bookid) {
- $('#contacts h3[data-id="'+oldbookid+'"]').removeClass('active');
- $('#contacts h3[data-id="'+bookid+'"]').addClass('active');
+ var obj = null;
+ var element = null;
+ var q = '';
+ if(params.obj) {
+ obj = params.obj;
+ q = this.queryStringFor(obj);
+ element = this.propertyTypeFor(obj);
+ } else {
+ element = params.name;
+ var value = utils.isArray(params.value)
+ ? $.param(params.value)
+ : encodeURIComponent(params.value);
+ q = 'id=' + this.id + '&value=' + value + '&name=' + element;
+ }
+ console.log('q', q);
+ var self = this;
+ this.setAsSaving(obj, true);
+ $.post(OC.filePath('contacts', 'ajax', 'contact/saveproperty.php'), q, function(jsondata){
+ if(!jsondata) {
+ $(document).trigger('status.contact.error', {
+ status: 'error',
+ message: t('contacts', 'Network or server error. Please inform administrator.'),
+ });
+ self.setAsSaving(obj, false);
+ return false;
+ }
+ if(jsondata.status == 'success') {
+ if(!self.data[element]) {
+ self.data[element] = [];
+ }
+ if(self.multi_properties.indexOf(element) !== -1) {
+ // First find out if an existing element by looking for checksum
+ var checksum = self.checksumFor(obj);
+ if(checksum) {
+ for(var i in self.data[element]) {
+ if(self.data[element][i].checksum === checksum) {
+ self.data[element][i] = {
+ name: element,
+ value: self.valueFor(obj),
+ parameters: self.parametersFor(obj),
+ checksum: jsondata.data.checksum,
+ }
+ break;
+ }
+ }
+ } else {
+ self.data[element].push({
+ name: element,
+ value: self.valueFor(obj),
+ parameters: self.parametersFor(obj),
+ checksum: jsondata.data.checksum,
+ });
+ }
+ self.propertyContainerFor(obj).data('checksum', jsondata.data.checksum);
+ } else {
+ // Save value and parameters internally
+ var value = obj ? self.valueFor(obj) : params.value;
+ switch(element) {
+ case 'CATEGORIES':
+ // We deal with this in addToGroup()
+ break;
+ case 'N':
+ if(!utils.isArray(value)) {
+ value = value.split(';');
+ // Then it is auto-generated from FN.
+ var $nelems = self.$fullelem.find('.n.edit input');
+ console.log('nelems', $nelems);
+ $.each(value, function(idx, val) {
+ console.log('nval', val);
+ self.$fullelem.find('#n_' + idx).val(val);
+ });
+ }
+ case 'FN':
+ // Update the list element
+ self.$listelem.find('.nametext').text(value);
+ var nempty = true;
+ if(!self.data.N) {
+ // TODO: Maybe add a method for constructing new elements?
+ self.data.N = [{name:'N',value:['', '', '', '', ''],parameters:[]}];
+ }
+ $.each(self.data.N[0]['value'], function(idx, val) {
+ if(val) {
+ nempty = false;
+ return false;
+ }
+ });
+ if(nempty) {
+ self.data.N[0]['value'] = ['', '', '', '', ''];
+ nvalue = value.split(' ');
+ // Very basic western style parsing. I'm not gonna implement
+ // https://github.com/android/platform_packages_providers_contactsprovider/blob/master/src/com/android/providers/contacts/NameSplitter.java ;)
+ self.data.N[0]['value'][0] = nvalue.length > 2 && nvalue.slice(nvalue.length-1).toString() || nvalue[1] || '';
+ self.data.N[0]['value'][1] = nvalue[0] || '';
+ self.data.N[0]['value'][2] = nvalue.length > 2 && nvalue.slice(1, nvalue.length-1).join(' ') || '';
+ setTimeout(function() {
+ // TODO: Hint to user to check if name is properly formatted
+ console.log('auto creating N', self.data.N[0].value)
+ self.saveProperty({name:'N', value:self.data.N[0].value.join(';')});
+ setTimeout(function() {
+ self.$fullelem.find('.fullname').next('.action.edit').trigger('click');
+ OC.notify({message:t('contacts', 'Is this correct?')});
+ }
+ , 1000);
+ }
+ , 500);
+ }
+ $(document).trigger('status.contact.renamed', {
+ id: self.id,
+ contact: self,
+ });
+ case 'NICKNAME':
+ case 'BDAY':
+ case 'ORG':
+ case 'TITLE':
+ self.data[element][0] = {
+ name: element,
+ value: value,
+ parameters: self.parametersFor(obj),
+ checksum: jsondata.data.checksum,
+ };
+ break;
+ default:
+ break;
+ }
+ }
+ self.setAsSaving(obj, false);
+ return true;
+ } else {
+ $(document).trigger('status.contact.error', {
+ status: 'error',
+ message: jsondata.data.message,
+ });
+ self.setAsSaving(obj, false);
+ return false;
+ }
+ },'json');
+ }
+
+ /**
+ * Hide contact list element.
+ */
+ Contact.prototype.hide = function() {
+ this.getListItemElement().hide();
+ }
+
+ /**
+ * Remove any open contact from the DOM.
+ */
+ Contact.prototype.close = function() {
+ console.log('Contact.close', this);
+ if(this.$fullelem) {
+ this.$fullelem.remove();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Remove any open contact from the DOM and detach it's list
+ * element from the DOM.
+ * @returns The contact object.
+ */
+ Contact.prototype.detach = function() {
+ if(this.$fullelem) {
+ this.$fullelem.remove();
+ }
+ if(this.$listelem) {
+ this.$listelem.detach();
+ return this;
+ }
+ }
+
+ /**
+ * Set a contacts list element as (un)checked
+ * @returns The contact object.
+ */
+ Contact.prototype.setChecked = function(checked) {
+ if(this.$listelem) {
+ this.$listelem.find('input:checkbox').prop('checked', checked);
+ return this;
+ }
+ }
+
+ /**
+ * Set a contact to en/disabled depending on its permissions.
+ * @param boolean enabled
+ */
+ Contact.prototype.setEnabled = function(enabled) {
+ console.log('setEnabled', enabled);
+ if(enabled) {
+ this.$fullelem.find('#addproperty').show();
+ } else {
+ this.$fullelem.find('#addproperty').hide();
+ }
+ this.enabled = enabled;
+ this.$fullelem.find('.value,.action').each(function () {
+ $(this).prop('disabled', !enabled);
+ });
+ $(document).trigger('status.contact.enabled', enabled);
+ }
+
+ /**
+ * Add a contact from data store and remove it from the DOM
+ * @params params. An object which can contain the optional properties:
+ * aid: The id of the addressbook to add the contact to. Per default it will be added to the first.
+ * fn: The formatted name of the contact.
+ * @param cb Optional callback function which
+ * @returns The callback gets an object as argument with a variable 'status' of either 'success'
+ * or 'error'. On success the 'data' property of that object contains the contact id as 'id', the
+ * addressbook id as 'aid' and the contact data structure as 'details'.
+ */
+ Contact.prototype.add = function(params, cb) {
+ var self = this;
+ $.post(OC.filePath('contacts', 'ajax', 'contact/add.php'),
+ params, function(jsondata) {
+ if(!jsondata) {
+ $(document).trigger('status.contact.error', {
+ status: 'error',
+ message: t('contacts', 'Network or server error. Please inform administrator.'),
+ });
+ return false;
+ }
+ if(jsondata.status === 'success') {
+ self.id = parseInt(jsondata.data.id);
+ self.access.id = parseInt(jsondata.data.aid);
+ self.data = jsondata.data.details;
+ $(document).trigger('status.contact.added', {
+ id: self.id,
+ contact: self,
+ });
+ }
+ if(typeof cb == 'function') {
+ cb(jsondata);
+ }
+ });
+ }
+ /**
+ * Delete contact from data store and remove it from the DOM
+ * @param cb Optional callback function which
+ * @returns An object with a variable 'status' of either success
+ * or 'error'
+ */
+ Contact.prototype.destroy = function(cb) {
+ var self = this;
+ $.post(OC.filePath('contacts', 'ajax', 'contact/delete.php'),
+ {id: this.id}, function(jsondata) {
+ if(jsondata && jsondata.status === 'success') {
+ if(self.$listelem) {
+ self.$listelem.remove();
+ }
+ if(self.$fullelem) {
+ self.$fullelem.remove();
}
}
- $.getJSON(OC.filePath('contacts', 'ajax', 'contact/details.php'),{'id':id},function(jsondata){
- if(jsondata.status == 'success'){
- OC.Contacts.Card.loadContact(jsondata.data, bookid);
+ if(typeof cb == 'function') {
+ var retval = {status: jsondata ? jsondata.status : 'error'};
+ if(jsondata) {
+ if(jsondata.status === 'success') {
+ retval['id'] = jsondata.id;
+ } else {
+ retval['message'] = jsondata.message;
+ }
+ } else {
+ retval['message'] = t('contacts', 'There was an unknown error when trying to delete this contact');
+ retval['id'] = self.id;
}
- else{
- OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error'));
+ cb(retval);
+ }
+ });
+ }
+
+ Contact.prototype.queryStringFor = function(obj) {
+ var q = 'id=' + this.id;
+ var ptype = this.propertyTypeFor(obj);
+ q += '&name=' + ptype;
+
+ if(this.multi_properties.indexOf(ptype) !== -1) {
+ q += '&checksum=' + this.checksumFor(obj);
+ }
+
+ if($(obj).hasClass('propertycontainer')) {
+ q += '&value=' + encodeURIComponent($(obj).val());
+ } else {
+ q += '&' + this.propertyContainerFor(obj)
+ .find('input.value,select.value,textarea.value,.parameter').serialize();
+ }
+ return q;
+ }
+
+ Contact.prototype.propertyContainerFor = function(obj) {
+ return $(obj).hasClass('propertycontainer')
+ ? $(obj)
+ : $(obj).parents('.propertycontainer').first();
+ }
+
+ Contact.prototype.checksumFor = function(obj) {
+ return this.propertyContainerFor(obj).data('checksum');
+ }
+
+ Contact.prototype.valueFor = function(obj) {
+ var $container = this.propertyContainerFor(obj);
+ console.assert($container.length > 0, 'Couldn\'t find container for ' + $(obj))
+ return $container.is('input')
+ ? $container.val()
+ : (function() {
+ var $elem = $container.find('input.value:not(:checkbox)');
+ console.assert($elem.length > 0, 'Couldn\'t find value for ' + $container.data('element'));
+ if($elem.length === 1) {
+ return $elem.val;
+ } else if($elem.length > 1) {
+ var retval = [];
+ $.each($elem, function(idx, e) {
+ retval.push($(e).val());
+ });
+ return retval;
+ }
+ })();
+ }
+
+ Contact.prototype.parametersFor = function(obj, asText) {
+ var parameters = [];
+ $.each(this.propertyContainerFor(obj).find('select.parameter,input:checkbox:checked.parameter,textarea'), function(i, elem) {
+ var $elem = $(elem);
+ var paramname = $elem.data('parameter');
+ if(!parameters[paramname]) {
+ parameters[paramname] = [];
+ }
+ var val;
+ if(asText) {
+ if($elem.is(':checkbox')) {
+ val = $elem.attr('title');
+ } else if($elem.is('select')) {
+ val = $elem.find(':selected').text();
+ }
+ } else {
+ val = $elem.val();
+ }
+ parameters[paramname].push(val);
+ });
+ console.log('Contact.parametersFor', parameters);
+ return parameters;
+ }
+
+ Contact.prototype.propertyTypeFor = function(obj) {
+ var ptype = this.propertyContainerFor(obj).data('element');
+ return ptype ? ptype.toUpperCase() : null;
+ }
+
+ /**
+ * Render the list item
+ * @return A jquery object to be inserted in the DOM
+ */
+ Contact.prototype.renderListItem = function() {
+ this.$listelem = this.$listTemplate.octemplate({
+ id: this.id,
+ name: this.getPreferredValue('FN', ''),
+ email: this.getPreferredValue('EMAIL', ''),
+ tel: this.getPreferredValue('TEL', ''),
+ adr: this.getPreferredValue('ADR', []).clean('').join(', '),
+ categories: this.getPreferredValue('CATEGORIES', [])
+ .clean('').join(' / '),
+ });
+ if(this.access.owner !== OC.currentUser
+ && !(this.access.permissions & OC.PERMISSION_UPDATE
+ || this.access.permissions & OC.PERMISSION_DELETE)) {
+ this.$listelem.find('input:checkbox').prop('disabled', true).css('opacity', '0');
+ }
+ return this.$listelem;
+ }
+
+ /**
+ * Render the full contact
+ * @return A jquery object to be inserted in the DOM
+ */
+ Contact.prototype.renderContact = function() {
+ var self = this;
+ var n = this.getPreferredValue('N', ['', '', '', '', '']);
+ console.log('renderContact', this.data);
+ var values = this.data
+ ? {
+ id: this.id,
+ name: this.getPreferredValue('FN', ''),
+ n0: n[0], n1: n[1], n2: n[2], n3: n[3], n4: n[4],
+ nickname: this.getPreferredValue('NICKNAME', ''),
+ title: this.getPreferredValue('TITLE', ''),
+ org: this.getPreferredValue('ORG', []).clean('').join(', '), // TODO Add parts if more than one.
+ bday: this.getPreferredValue('BDAY', '').length >= 10
+ ? $.datepicker.formatDate('dd-mm-yy',
+ $.datepicker.parseDate('yy-mm-dd',
+ this.getPreferredValue('BDAY', '').substring(0, 10)))
+ : '',
+ }
+ : {id: '', name: '', nickname: '', title: '', org: '', bday: '', n0: '', n1: '', n2: '', n3: '', n4: ''};
+ this.$fullelem = this.$fullTemplate.octemplate(values).data('contactobject', this);
+ this.$addMenu = this.$fullelem.find('#addproperty');
+ this.$addMenu.on('change', function(event) {
+ console.log('add', $(this).val());
+ var $opt = $(this).find('option:selected');
+ self.addProperty($opt, $(this).val());
+ $(this).val('');
+ });
+ var $fullname = this.$fullelem.find('.fullname');
+ this.$fullelem.find('.singleproperties').on('mouseenter', function() {
+ $fullname.next('.edit').css('opacity', '1');
+ }).on('mouseleave', function() {
+ $fullname.next('.edit').css('opacity', '0');
+ });
+ $fullname.next('.edit').on('click keydown', function(event) {
+ console.log('edit name', event);
+ $('.tipsy').remove();
+ if(wrongKey(event)) {
+ return;
+ }
+ $(this).css('opacity', '0');
+ var $editor = $(this).next('.n.edit').first();
+ var bodyListener = function(e) {
+ if($editor.find($(e.target)).length == 0) {
+ console.log('this', $(this));
+ $editor.toggle('blind');
+ $('body').unbind('click', bodyListener);
+ }
+ }
+ $editor.toggle('blind', function() {
+ $('body').bind('click', bodyListener);
+ });
+ });
+ var $singleelements = this.$fullelem.find('dd.propertycontainer');
+ $singleelements.find('.action').css('opacity', '0');
+ $singleelements.on('mouseenter', function() {
+ $(this).find('.action').css('opacity', '1');
+ }).on('mouseleave', function() {
+ $(this).find('.action').css('opacity', '0');
+ });
+ this.$fullelem.on('click keydown', '.delete', function(event) {
+ console.log('delete', event);
+ $('.tipsy').remove();
+ if(wrongKey(event)) {
+ return;
+ }
+ self.deleteProperty({obj:event.target});
+ });
+ this.$fullelem.on('change', '.value,.parameter', function(event) {
+ console.log('change', event);
+ self.saveProperty({obj:event.target});
+ });
+
+ this.$fullelem.find('form').on('submit', function(event) {
+ console.log('submit', this, event);
+ return false;
+ });
+ this.$fullelem.find('[data-element="bday"]')
+ .find('input').datepicker({
+ dateFormat : 'dd-mm-yy'
+ });
+ this.loadPhoto();
+ if(!this.data) {
+ // A new contact
+ this.setEnabled(true);
+ return this.$fullelem;
+ }
+ // Loop thru all single occurrence values. If not set hide the
+ // element, if set disable the add menu entry.
+ for(var value in values) {
+ if(this.multi_properties.indexOf(value.toUpperCase()) === -1) {
+ if(!values[value].length) {
+ console.log('hiding', value);
+ this.$fullelem.find('[data-element="' + value + '"]').hide();
+ } else {
+ this.$addMenu.find('option[value="' + value.toUpperCase() + '"]').prop('disabled', true);
+ }
+ }
+ }
+ $.each(this.multi_properties, function(idx, name) {
+ if(self.data[name]) {
+ var $list = self.$fullelem.find('ul.' + name.toLowerCase());
+ $list.show();
+ for(var p in self.data[name]) {
+ if(typeof self.data[name][p] === 'object') {
+ var property = self.data[name][p];
+ //console.log(name, p, property);
+ $property = null;
+ switch(name) {
+ case 'TEL':
+ case 'URL':
+ case 'EMAIL':
+ $property = self.renderStandardProperty(name.toLowerCase(), property);
+ if(self.data[name].length >= 1) {
+ $property.find('input:checkbox[value="PREF"]').hide();
+ }
+ break;
+ case 'ADR':
+ $property = self.renderAddressProperty(idx, property);
+ break;
+ case 'IMPP':
+ $property = self.renderIMProperty(property);
+ break;
+ }
+ if(!$property) {
+ continue;
+ }
+ //console.log('$property', $property);
+ var meta = [];
+ if(property.label) {
+ if(!property.parameters['TYPE']) {
+ property.parameters['TYPE'] = [];
+ }
+ property.parameters['TYPE'].push(property.label);
+ meta.push(property.label);
+ }
+ for(var param in property.parameters) {
+ //console.log('param', param);
+ if(param.toUpperCase() == 'PREF') {
+ var $cb = $property.find('input[type="checkbox"]');
+ $cb.attr('checked', 'checked')
+ meta.push($cb.attr('title'));
+ }
+ else if(param.toUpperCase() == 'TYPE') {
+ for(etype in property.parameters[param]) {
+ var found = false;
+ var et = property.parameters[param][etype];
+ if(typeof et !== 'string') {
+ continue;
+ }
+ //console.log('et', et);
+ if(et.toUpperCase() === 'INTERNET') {
+ continue;
+ }
+ $property.find('select.type option').each(function() {
+ if($(this).val().toUpperCase() === et.toUpperCase()) {
+ $(this).attr('selected', 'selected');
+ meta.push($(this).text());
+ found = true;
+ }
+ });
+ if(!found) {
+ $property.find('select.type option:last-child').after('');
+ }
+ }
+ }
+ else if(param.toUpperCase() == 'X-SERVICE-TYPE') {
+ //console.log('setting', $property.find('select.impp'), 'to', property.parameters[param].toLowerCase());
+ $property.find('select.impp').val(property.parameters[param].toLowerCase());
+ }
+ }
+ var $meta = $property.find('.meta');
+ if($meta.length) {
+ $meta.html(meta.join('/'));
+ }
+ if(self.access.owner === OC.currentUser
+ || self.access.permissions & OC.PERMISSION_UPDATE
+ || self.access.permissions & OC.PERMISSION_DELETE) {
+ $property.find('select.type[name="parameters[TYPE][]"]')
+ .combobox({
+ singleclick: true,
+ classes: ['propertytype', 'float', 'label'],
+ });
+ $property.on('mouseenter', function() {
+ $(this).find('.listactions').css('opacity', '1');
+ }).on('mouseleave', function() {
+ $(this).find('.listactions').css('opacity', '0');
+ });
+ }
+ $list.append($property);
+ }
+ }
+ }
+ });
+ if(this.access.owner !== OC.currentUser
+ && !(this.access.permissions & OC.PERMISSION_UPDATE
+ || this.access.permissions & OC.PERMISSION_DELETE)) {
+ this.setEnabled(false);
+ } else {
+ this.setEnabled(true);
+ }
+ return this.$fullelem;
+ }
+
+ Contact.prototype.isEditable = function() {
+ return ((this.access.owner === OC.currentUser)
+ || (this.access.permissions & OC.PERMISSION_UPDATE
+ || this.access.permissions & OC.PERMISSION_DELETE));
+ }
+
+ /**
+ * Render a simple property. Used for EMAIL and TEL.
+ * @return A jquery object to be injected in the DOM
+ */
+ Contact.prototype.renderStandardProperty = function(name, property) {
+ if(!this.detailTemplates[name]) {
+ console.log('No template for', name);
+ return;
+ }
+ var values = property
+ ? { value: property.value, checksum: property.checksum }
+ : { value: '', checksum: 'new' };
+ $elem = this.detailTemplates[name].octemplate(values);
+ return $elem;
+ }
+
+ /**
+ * Render an ADR (address) property.
+ * @return A jquery object to be injected in the DOM
+ */
+ Contact.prototype.renderAddressProperty = function(idx, property) {
+ console.log('Contact.renderAddressProperty', property)
+ if(!this.detailTemplates['adr']) {
+ console.log('No template for adr', this.detailTemplates);
+ return;
+ }
+ if(typeof idx === 'undefined') {
+ if(this.data && this.data.ADR && this.data.ADR.length > 0) {
+ idx = this.data.ADR.length - 1;
+ } else {
+ idx = 0;
+ }
+ }
+ var values = property ? {
+ value: property.value.clean('').join(', '),
+ checksum: property.checksum,
+ adr0: property.value[0] || '',
+ adr1: property.value[1] || '',
+ adr2: property.value[2] || '',
+ adr3: property.value[3] || '',
+ adr4: property.value[4] || '',
+ adr5: property.value[5] || '',
+ adr6: property.value[6] || '',
+ idx: idx,
+ }
+ : {value:'', checksum:'new', adr0:'', adr1:'', adr2:'', adr3:'', adr4:'', adr5:'', adr6:'', idx: idx};
+ var $elem = this.detailTemplates['adr'].octemplate(values);
+ var self = this;
+ $elem.find('.display').on('click', function() {
+ $(this).next('.listactions').hide();
+ var $editor = $(this).siblings('.adr.edit').first();
+ var $viewer = $(this);
+ var bodyListener = function(e) {
+ if($editor.find($(e.target)).length == 0) {
+ console.log('this', $(this));
+ $editor.toggle('blind');
+ $viewer.slideDown(400, function() {
+ var input = $editor.find('input').first();
+ console.log('input', input);
+ var val = self.valueFor(input);
+ var params = self.parametersFor(input, true);
+ console.log('val', val, 'params', params);
+ $(this).find('.meta').html(params['TYPE'].join('/'));
+ $(this).find('.adr').html(escapeHTML(self.valueFor($editor.find('input').first()).clean('').join(', ')));
+ $(this).next('.listactions').css('display', 'inline-block');
+ $('body').unbind('click', bodyListener);
+ });
+ }
+ }
+ $viewer.slideUp();
+ $editor.toggle('blind', function() {
+ $('body').bind('click', bodyListener);
+ });
+ });
+ return $elem;
+ }
+
+ /**
+ * Render an IMPP (Instant Messaging) property.
+ * @return A jquery object to be injected in the DOM
+ */
+ Contact.prototype.renderIMProperty = function(property) {
+ if(!this.detailTemplates['impp']) {
+ console.log('No template for impp', this.detailTemplates);
+ return;
+ }
+ var values = property ? {
+ value: property.value,
+ checksum: property.checksum,
+ } : {value: '', checksum: 'new'};
+ $elem = this.detailTemplates['impp'].octemplate(values);
+ return $elem;
+ }
+
+ /**
+ * Render the PHOTO property.
+ */
+ Contact.prototype.loadPhoto = function(dontloadhandlers) {
+ var self = this;
+ var id = this.id || 'new';
+ var refreshstr = '&refresh='+Math.random();
+ this.$photowrapper = this.$fullelem.find('#photowrapper');
+ this.$photowrapper.addClass('loading').addClass('wait');
+ var $phototools = this.$fullelem.find('#phototools');
+ console.log('photowrapper', this.$photowrapper.length);
+ delete this.photo;
+ $('img.contactphoto').remove()
+ this.photo = new Image();
+ $(this.photo).load(function () {
+ $(this).addClass('contactphoto');
+ self.$photowrapper.css('width', $(this).get(0).width + 10);
+ self.$photowrapper.removeClass('loading').removeClass('wait');
+ $(this).insertAfter($phototools).fadeIn();
+ }).error(function () {
+ OC.notify({message:t('contacts','Error loading profile picture.')});
+ }).attr('src', OC.linkTo('contacts', 'photo.php')+'?id='+id+refreshstr);
+
+ if(!dontloadhandlers && this.isEditable()) {
+ this.$photowrapper.on('mouseenter', function() {
+ $phototools.slideDown(200);
+ }).on('mouseleave', function() {
+ $phototools.slideUp(200);
+ });
+ $phototools.hover( function () {
+ $(this).removeClass('transparent');
+ }, function () {
+ $(this).addClass('transparent');
+ });
+ $phototools.find('li a').tipsy();
+
+ $phototools.find('.edit').on('click', function() {
+ console.log('TODO: edit photo');
+ $(document).trigger('request.edit.contactphoto', {
+ id: self.id,
+ });
+ });
+ $phototools.find('.cloud').on('click', function() {
+ console.log('select photo from cloud');
+ $(document).trigger('request.select.contactphoto.fromcloud', {
+ id: self.id,
+ });
+ });
+ $phototools.find('.upload').on('click', function() {
+ console.log('select photo from local');
+ $(document).trigger('request.select.contactphoto.fromlocal', {
+ id: self.id,
+ });
+ });
+ if(this.data && this.data.PHOTO) {
+ $phototools.find('.delete').show();
+ $phototools.find('.edit').show();
+ } else {
+ $phototools.find('.delete').hide();
+ $phototools.find('.edit').hide();
+ }
+ $(document).bind('status.contact.photoupdated', function(e, result) {
+ console.log('Contact - photoupdated')
+ self.loadPhoto(true);
+ var refreshstr = '&refresh='+Math.random();
+ self.getListItemElement().find('td.name')
+ .css('background', 'url(' + OC.filePath('', '', 'remote.php')+'/contactthumbnail?id='+self.id+refreshstr + ')');
+ });
+ }
+ }
+
+ /**
+ * Get the jquery element associated with this object
+ */
+ Contact.prototype.getListItemElement = function() {
+ if(!this.$listelem) {
+ this.renderListItem();
+ }
+ return this.$listelem;
+ }
+
+ /**
+ * Get the preferred value for a property.
+ * If a preferred value is not found the first one will be returned.
+ * @param string name The name of the property like EMAIL, TEL or ADR.
+ * @param def A default value to return if nothing is found.
+ */
+ Contact.prototype.getPreferredValue = function(name, def) {
+ var pref = def, found = false;
+ if(this.data && this.data[name]) {
+ var props = this.data[name];
+ //console.log('props', props);
+ $.each(props, function( i, prop ) {
+ //console.log('prop:', i, prop);
+ if(i === 0) { // Choose first to start with
+ pref = prop.value;
+ }
+ for(var param in prop.parameters) {
+ if(param.toUpperCase() == 'PREF') {
+ found = true; //
+ break;
+ }
+ }
+ if(found) {
+ return false; // break out of loop
}
});
}
- return false;
- });
-
- $('.contacts_property').live('change', function(){
- OC.Contacts.Card.saveProperty(this);
- });
-
- $(function() {
- // Upload function for dropped contact photos files. Should go in the Contacts class/object.
- $.fileUpload = function(files){
- var file = files[0];
- if(file.size > $('#max_upload').val()){
- OC.dialogs.alert(t('contacts','The file you are trying to upload exceed the maximum size for file uploads on this server.'), t('contacts','Upload too large'));
- return;
- }
- if (file.type.indexOf("image") != 0) {
- OC.dialogs.alert(t('contacts','Only image files can be used as profile picture.'), t('contacts','Wrong file type'));
- return;
- }
- var xhr = new XMLHttpRequest();
-
- if (!xhr.upload) {
- OC.dialogs.alert(t('contacts', 'Your browser doesn\'t support AJAX upload. Please click on the profile picture to select a photo to upload.'), t('contacts', 'Error'))
- }
- fileUpload = xhr.upload,
- xhr.onreadystatechange = function() {
- if (xhr.readyState == 4){
- response = $.parseJSON(xhr.responseText);
- if(response.status == 'success') {
- if(xhr.status == 200) {
- OC.Contacts.Card.editPhoto(response.data.id, response.data.tmp);
- } else {
- OC.dialogs.alert(xhr.status + ': ' + xhr.responseText, t('contacts', 'Error'));
- }
- } else {
- OC.dialogs.alert(response.data.message, t('contacts', 'Error'));
- }
- }
- };
-
- fileUpload.onprogress = function(e){
- if (e.lengthComputable){
- var _progress = Math.round((e.loaded * 100) / e.total);
- //if (_progress != 100){
- //}
- }
- };
- xhr.open('POST', OC.filePath('contacts', 'ajax', 'uploadphoto.php')+'?id='+OC.Contacts.Card.id+'&requesttoken='+oc_requesttoken+'&imagefile='+encodeURIComponent(file.name), true);
- xhr.setRequestHeader('Cache-Control', 'no-cache');
- xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
- xhr.setRequestHeader('X_FILE_NAME', encodeURIComponent(file.name));
- xhr.setRequestHeader('X-File-Size', file.size);
- xhr.setRequestHeader('Content-Type', file.type);
- xhr.send(file);
- }
- });
-
- $(document).bind('drop dragover', function (e) {
- e.preventDefault(); // prevent browser from doing anything, if file isn't dropped in dropZone
- });
-
- //add multiply file upload attribute to all browsers except konqueror (which crashes when it's used)
- if(navigator.userAgent.search(/konqueror/i)==-1){
- $('#import_upload_start').attr('multiple','multiple')
+ return pref;
}
- // Import using jquery.fileupload
- $(function() {
- var uploadingFiles = {}, numfiles = 0, uploadedfiles = 0, retries = 0;
- var aid;
- $('#import_upload_start').fileupload({
- dropZone: $('#contacts'), // restrict dropZone to contacts list.
- acceptFileTypes: /^text\/(directory|vcard|x-vcard)$/i,
- add: function(e, data) {
- var files = data.files;
- var totalSize=0;
- if(files) {
- numfiles += files.length; uploadedfiles = 0;
- for(var i=0;i$('#max_upload').val()){
- OC.dialogs.alert(t('contacts','The file you are trying to upload exceed the maximum size for file uploads on this server.'), t('contacts','Upload too large'));
- numfiles = uploadedfiles = retries = aid = 0;
- uploadingFiles = {};
- return;
- }else{
- if($.support.xhrFileUpload) {
- for(var i=0;i 3) {
- numfiles = uploadedfiles = retries = aid = 0;
- uploadingFiles = {};
- $('#uploadprogressbar').fadeOut();
- OC.dialogs.alert(t('contacts', 'Something went wrong with the upload, please retry.'), t('contacts', 'Error'));
- return;
- }
- setTimeout(function() { // Just to let any uploads finish
- importFiles(aid, uploadingFiles);
- }, 1000);
- }
- $('#uploadprogressbar').progressbar('value',50);
- var todo = uploadedfiles;
- $.each(fileList, function(fileName, data) {
- OC.Contacts.Contacts.doImport(fileName, aid, function() {
- delete fileList[fileName];
- numfiles -= 1; uploadedfiles -= 1;
- $('#uploadprogressbar').progressbar('value',50+(50/(todo-uploadedfiles)));
- });
- })
- OC.Contacts.notify({message:t('contacts', 'Importing...'), timeout:20});
- waitForImport();
- }
- if(!aid) {
- // Either selected with filepicker or dropped outside of an address book.
- $.getJSON(OC.filePath('contacts', 'ajax', 'selectaddressbook.php'),{},function(jsondata) {
- if(jsondata.status == 'success') {
- if($('#selectaddressbook_dialog').dialog('isOpen') == true) {
- $('#selectaddressbook_dialog').dialog('moveToTop');
- } else {
- $('#dialog_holder').html(jsondata.data.page).ready(function($) {
- var select_dlg = $('#selectaddressbook_dialog');
- select_dlg.dialog({
- modal: true, height: 'auto', width: 'auto',
- buttons: {
- 'Ok':function() {
- aid = select_dlg.find('input:checked').val();
- if(aid == 'new') {
- var displayname = select_dlg.find('input.name').val();
- var description = select_dlg.find('input.desc').val();
- if(!displayname.trim()) {
- OC.dialogs.alert(t('contacts', 'The address book name cannot be empty.'), t('contacts', 'Error'));
- return false;
- }
- $(this).dialog('close');
- OC.Contacts.Contacts.addAddressbook(displayname, description, function(addressbook) {
- aid = addressbook.id;
- setTimeout(function() {
- importFiles(aid, uploadingFiles);
- }, 500);
- //console.log('aid ' + aid);
- });
- } else {
- setTimeout(function() {
- importFiles(aid, uploadingFiles);
- }, 500);
- //console.log('aid ' + aid);
- $(this).dialog('close');
- }
- },
- 'Cancel':function() {
- $(this).dialog('close');
- numfiles = uploadedfiles = retries = aid = 0;
- uploadingFiles = {};
- $('#uploadprogressbar').fadeOut();
- }
- },
- close: function(event, ui) {
- // TODO: If numfiles != 0 delete tmp files after a timeout.
- $(this).dialog('destroy').remove();
- }
- });
- });
- }
- } else {
- $('#uploadprogressbar').fadeOut();
- OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error'));
- }
- });
+ });
+ if(found) {
+ return;
+ }
+ this.data.CATEGORIES[0].value.push(name);
+ console.log('listelem categories', this.getPreferredValue('CATEGORIES', []).clean('').join(' / '));
+ if(this.$listelem) {
+ this.$listelem.find('td.categories')
+ .text(this.getPreferredValue('CATEGORIES', []).clean('').join(' / '));
+ }
+ }
+ this.saveProperty({name:'CATEGORIES', value:this.data.CATEGORIES[0].value.join(',') });
+ }
+
+ /**
+ * Remove this contact to a group
+ * @param String name The group name
+ */
+ Contact.prototype.removeFromGroup = function(name) {
+ console.log('removeFromGroup', name);
+ if(!this.data.CATEGORIES) {
+ return;
+ } else {
+ var found = false;
+ var categories = [];
+ $.each(this.data.CATEGORIES[0].value, function(idx, category) {
+ if(name.toLowerCase() === category.toLowerCase()) {
+ found = true;
} else {
- // Dropped on an address book or it's list.
- setTimeout(function() { // Just to let any uploads finish
- importFiles(aid, uploadingFiles);
- }, 1000);
+ categories.push(category);
}
- if(data.dataType != 'iframe ') {
- $('#upload input.stop').hide();
+ });
+ if(!found) {
+ return;
+ }
+ this.data.CATEGORIES[0].value = categories;
+ //this.data.CATEGORIES[0].value.splice(this.data.CATEGORIES[0].value.indexOf(name), 1);
+ if(this.$listelem) {
+ this.$listelem.find('td.categories')
+ .text(categories.join(' / '));
+ }
+ }
+ this.saveProperty({name:'CATEGORIES', value:this.data.CATEGORIES[0].value.join(',') });
+ }
+
+ Contact.prototype.setCurrent = function(on) {
+ if(on) {
+ this.$listelem.addClass('active');
+ } else {
+ this.$listelem.removeClass('active');
+ }
+ $(document).trigger('status.contact.currentlistitem', {
+ id: this.id,
+ pos: Math.round(this.$listelem.position().top),
+ height: Math.round(this.$listelem.height()),
+ });
+ }
+
+ Contact.prototype.next = function() {
+ var $next = this.$listelem.next('tr');
+ if($next.length > 0) {
+ this.$listelem.removeClass('active');
+ $next.addClass('active');
+ $(document).trigger('status.contact.currentlistitem', {
+ id: parseInt($next.data('id')),
+ pos: Math.round($next.position().top),
+ height: Math.round($next.height()),
+ });
+ }
+ }
+
+ Contact.prototype.prev = function() {
+ var $prev = this.$listelem.prev('tr');
+ if($prev.length > 0) {
+ this.$listelem.removeClass('active');
+ $prev.addClass('active');
+ $(document).trigger('status.contact.currentlistitem', {
+ id: parseInt($prev.data('id')),
+ pos: Math.round($prev.position().top),
+ height: Math.round($prev.height()),
+ });
+ }
+ }
+
+ var ContactList = function(contactlist, contactlistitemtemplate, contactfulltemplate, contactdetailtemplates) {
+ //console.log('ContactList', contactlist, contactlistitemtemplate, contactfulltemplate, contactdetailtemplates);
+ var self = this;
+ this.length = 0;
+ this.contacts = {};
+ this.deletionQueue = [];
+ this.$contactList = contactlist;
+ this.$contactListItemTemplate = contactlistitemtemplate;
+ this.$contactFullTemplate = contactfulltemplate;
+ this.contactDetailTemplates = contactdetailtemplates;
+ this.$contactList.scrollTop(0);
+ this.loadContacts(0);
+ $(document).bind('status.contact.added', function(e, data) {
+ self.contacts[parseInt(data.id)] = data.contact;
+ self.insertContact(data.contact.renderListItem());
+ });
+
+ $(document).bind('status.contact.renamed', function(e, data) {
+ self.insertContact(data.contact.getListItemElement().detach());
+ });
+ }
+
+ /**
+ * Show/hide contacts belonging to an addressbook.
+ * @param int aid. Addressbook id.
+ * @param boolean show. Whether to show or hide.
+ * @param boolean hideothers. Used when showing shared addressbook as a group.
+ */
+ ContactList.prototype.showFromAddressbook = function(aid, show, hideothers) {
+ console.log('ContactList.showFromAddressbook', aid, show);
+ aid = parseInt(aid);
+ for(var contact in this.contacts) {
+ if(this.contacts[contact].access.id === aid) {
+ this.contacts[contact].getListItemElement().toggle(show);
+ } else if(hideothers) {
+ this.contacts[contact].getListItemElement().hide();
+ }
+ }
+ }
+
+ /**
+ * Show/hide contacts belonging to shared addressbooks.
+ * @param boolean show. Whether to show or hide.
+ */
+ ContactList.prototype.showSharedAddressbooks = function(show) {
+ console.log('ContactList.showSharedAddressbooks', show);
+ for(var contact in this.contacts) {
+ if(this.contacts[contact].access.owner !== OC.currentUser) {
+ if(show) {
+ this.contacts[contact].getListItemElement().show();
+ } else {
+ this.contacts[contact].getListItemElement().hide();
}
}
- })
- });
+ }
+ }
- OC.Contacts.loadHandlers();
- OC.Contacts.Contacts.update({cid:id});
-});
+ /**
+ * Show contacts in list
+ * @param Array contacts. A list of contact ids.
+ */
+ ContactList.prototype.showContacts = function(contacts) {
+ if(contacts.length === 0) {
+ // ~5 times faster
+ $('tr:visible.contact').hide();
+ return;
+ }
+ for(var contact in this.contacts) {
+ if(contacts === 'all') {
+ this.contacts[contact].getListItemElement().show();
+ } else {
+ contact = parseInt(contact);
+ if(contacts.indexOf(contact) === -1) {
+ this.contacts[contact].getListItemElement().hide();
+ } else {
+ this.contacts[contact].getListItemElement().show();
+ }
+ }
+ }
+ }
+
+ ContactList.prototype.contactPos = function(id) {
+ if(!id) {
+ console.log('id missing');
+ return false;
+ }
+ var $elem = this.contacts[parseInt(id)].getListItemElement();
+ var pos = $elem.offset().top - this.$contactList.offset().top + this.$contactList.scrollTop();
+ console.log('pos', pos);
+ return pos;
+ }
+
+ ContactList.prototype.hideContact = function(id) {
+ this.contacts[parseInt(id)].hide();
+ }
+
+ ContactList.prototype.closeContact = function(id) {
+ this.contacts[parseInt(id)].close();
+ }
+
+ /**
+ * Jumps to an element in the contact list
+ * @param number the number of the item starting with 0
+ */
+ ContactList.prototype.jumpToContact = function(id) {
+ var pos = this.contactPos(id);
+ console.log('scrollTop', pos);
+ this.$contactList.scrollTop(pos);
+ };
+
+ /**
+ * Returns a Contact object by searching for its id
+ * @param id the id of the node
+ * @return the Contact object or undefined if not found.
+ * FIXME: If continious loading is reintroduced this will have
+ * to load the requested contact if not in list.
+ */
+ ContactList.prototype.findById = function(id) {
+ return this.contacts[parseInt(id)];
+ };
+
+ ContactList.prototype.delayedDelete = function(id) {
+ var self = this;
+ if(utils.isUInt(id)) {
+ this.currentContact = null;
+ self.$contactList.show();
+ this.deletionQueue.push(id);
+ } else if(utils.isArray(id)) {
+ $.extend(this.deletionQueue, id);
+ } else {
+ throw { name: 'WrongParameterType', message: 'ContactList.delayedDelete only accept integers or arrays.'}
+ }
+ $.each(this.deletionQueue, function(idx, id) {
+ self.contacts[id].detach().setChecked(false);
+ });
+ console.log('deletionQueue', this.deletionQueue);
+ if(!window.onbeforeunload) {
+ window.onbeforeunload = function(e) {
+ e = e || window.event;
+ var warn = t('contacts', 'Some contacts are marked for deletion, but not deleted yet. Please wait for them to be deleted.');
+ if (e) {
+ e.returnValue = String(warn);
+ }
+ return warn;
+ }
+ }
+ if(this.$contactList.find('tr:visible').length === 0) {
+ $(document).trigger('status.visiblecontacts');
+ }
+ OC.notify({
+ message:t('contacts','Click to undo deletion of {num} contacts', {num: self.deletionQueue.length}),
+ //timeout:5,
+ timeouthandler:function() {
+ console.log('timeout');
+ // Don't fire all deletes at once
+ self.deletionTimer = setInterval('self.deleteContacts()', 500);
+ },
+ clickhandler:function() {
+ console.log('clickhandler');
+ $.each(self.deletionQueue, function(idx, id) {
+ self.insertContact(self.contacts[id].getListItemElement());
+ });
+ OC.notify({cancel:true});
+ OC.notify({message:t('contacts', 'Cancelled deletion of {num}', {num: self.deletionQueue.length})});
+ self.deletionQueue = [];
+ window.onbeforeunload = null;
+ }
+ });
+ }
+
+ /**
+ * Delete a contact with this id
+ * @param id the id of the contact
+ */
+ ContactList.prototype.deleteContacts = function() {
+ var self = this;
+ console.log('ContactList.deleteContacts, deletionQueue', this.deletionQueue);
+ if(typeof this.deletionTimer === 'undefined') {
+ console.log('No deletion timer!');
+ window.onbeforeunload = null;
+ return;
+ }
+
+ var id = this.deletionQueue.shift();
+ if(typeof id === 'undefined') {
+ clearInterval(this.deletionTimer);
+ delete this.deletionTimer;
+ window.onbeforeunload = null;
+ return;
+ }
+
+ // Let contact remove itself.
+ self.contacts[id].destroy(function(response) {
+ console.log('deleteContact', response);
+ if(response.status === 'success') {
+ delete self.contacts[id];
+ $(document).trigger('status.contact.deleted', {
+ id: id,
+ });
+ self.length -= 1;
+ if(self.length === 0) {
+ $(document).trigger('status.nomorecontacts');
+ }
+ } else {
+ OC.notify({message:response.message});
+ }
+ });
+ }
+
+ /**
+ * Opens the contact with this id in edit mode
+ * @param id the id of the contact
+ * @returns A jquery object to be inserted in the DOM.
+ */
+ ContactList.prototype.showContact = function(id) {
+ console.assert(typeof id === 'number', 'ContactList.showContact called with a non-number');
+ this.currentContact = id;
+ console.log('Contacts.showContact', id, this.contacts[this.currentContact], this.contacts)
+ return this.contacts[this.currentContact].renderContact();
+ };
+
+ /**
+ * Insert a rendered contact list item into the list
+ * @param contact jQuery object.
+ */
+ ContactList.prototype.insertContact = function($contact) {
+ console.log('insertContact', $contact);
+ $contact.draggable({
+ distance: 10,
+ revert: 'invalid',
+ //containment: '#content',
+ opacity: 0.8, helper: 'clone',
+ zIndex: 1000,
+ });
+ var name = $contact.find('.nametext').text().toLowerCase();
+ var added = false
+ this.$contactList.find('tr').each(function() {
+ if ($(this).find('.nametext').text().toLowerCase().localeCompare(name) > 0) {
+ $(this).before($contact);
+ added = true;
+ return false;
+ }
+ });
+ if(!added) {
+ this.$contactList.append($contact);
+ }
+ $contact.show();
+ return $contact;
+ }
+
+ /**
+ * Add contact
+ */
+ ContactList.prototype.addContact = function() {
+ var contact = new Contact(
+ this,
+ null,
+ {owner:OC.currentUser, permissions: 31},
+ null,
+ this.$contactListItemTemplate,
+ this.$contactFullTemplate,
+ this.contactDetailTemplates
+ );
+ if(this.currentContact) {
+ console.assert(typeof this.currentContact == 'number', 'this.currentContact is not a number');
+ this.contacts[this.currentContact].close();
+ }
+ return contact.renderContact();
+ }
+
+ /**
+ * Get contacts selected in list
+ *
+ * @returns array of integer contact ids.
+ */
+ ContactList.prototype.getSelectedContacts = function() {
+ var contacts = [];
+
+ $.each(this.$contactList.find('tr > td > input:checkbox:visible:checked'), function(a, b) {
+ contacts.push(parseInt($(b).parents('tr').first().data('id')));
+ });
+ return contacts;
+ }
+
+ ContactList.prototype.setCurrent = function(id, deselect_other) {
+ self = this;
+ if(deselect_other === true) {
+ $.each(this.contacts, function(contact) {
+ self.contacts[contact].setCurrent(false);
+ });
+ }
+ this.contacts[parseInt(id)].setCurrent(true);
+ }
+
+ // Should only be neccesary with progressive loading, but it's damn fast, so... ;)
+ ContactList.prototype.doSort = function() {
+ var self = this;
+ var rows = this.$contactList.find('tr').get();
+
+ rows.sort(function(a, b) {
+ return $(a).find('td.name').text().toUpperCase().localeCompare($(b).find('td.name').text().toUpperCase());
+ });
+
+ $.each(rows, function(index, row) {
+ self.$contactList.append(row);
+ });
+ }
+
+ /**
+ * Load contacts
+ * @param int offset
+ */
+ ContactList.prototype.loadContacts = function(offset, cb) {
+ var self = this;
+ // Should the actual ajax call be in the controller?
+ $.getJSON(OC.filePath('contacts', 'ajax', 'contact/list.php'), {offset: offset}, function(jsondata) {
+ if (jsondata && jsondata.status == 'success') {
+ console.log('ContactList.loadContacts', jsondata.data);
+ self.addressbooks = {};
+ $.each(jsondata.data.addressbooks, function(i, book) {
+ self.addressbooks[parseInt(book.id)] = {
+ owner: book.userid,
+ permissions: parseInt(book.permissions),
+ id: parseInt(book.id),
+ displayname: book.displayname,
+ description: book.description,
+ active: Boolean(parseInt(book.active)),
+ };
+ });
+ $.each(jsondata.data.contacts, function(c, contact) {
+ self.contacts[parseInt(contact.id)]
+ = new Contact(
+ self,
+ contact.id,
+ self.addressbooks[parseInt(contact.aid)],
+ contact.data,
+ self.$contactListItemTemplate,
+ self.$contactFullTemplate,
+ self.contactDetailTemplates
+ );
+ self.length +=1;
+ var $item = self.contacts[parseInt(contact.id)].renderListItem();
+ $item.draggable({
+ distance: 10,
+ revert: 'invalid',
+ //containment: '#content',
+ opacity: 0.8, helper: 'clone',
+ zIndex: 1000,
+ });
+ self.$contactList.append($item);
+ //self.insertContact(item);
+ });
+ self.doSort();
+ $(document).trigger('status.contacts.loaded', {
+ status: true,
+ numcontacts: jsondata.data.contacts.length
+ });
+ self.setCurrent(self.$contactList.find('tr:first-child').data('id'), false);
+ }
+ if(typeof cb === 'function') {
+ cb();
+ }
+ });
+ }
+ OC.Contacts.ContactList = ContactList;
+
+})( jQuery );
diff --git a/js/jquery.combobox.js b/js/jquery.combobox.js
index c9ffb4c6..b7a5c748 100644
--- a/js/jquery.combobox.js
+++ b/js/jquery.combobox.js
@@ -6,18 +6,21 @@
$.widget('ui.combobox', {
options: {
id: null,
- name: null,
showButton: false,
- editable: true
+ editable: true,
+ singleclick: false,
},
_create: function() {
var self = this,
select = this.element.hide(),
selected = select.children(':selected'),
value = selected.val() ? selected.text() : '';
- var input = this.input = $('')
+ var name = this.element.attr('name');
+ //this.element.attr('name', 'old_' + name)
+ var input = this.input = $('')
.insertAfter( select )
.val( value )
+ //.attr('name', name)
.autocomplete({
delay: 0,
minLength: 0,
@@ -80,10 +83,20 @@
self._setOption(key, value);
});
- input.dblclick(function() {
- // pass empty string as value to search for, displaying all results
- input.autocomplete('search', '');
- });
+ var clickHandler = function(e) {
+ var w = self.input.autocomplete('widget');
+ if(w.is(':visible')) {
+ self.input.autocomplete('close');
+ } else {
+ input.autocomplete('search', '');
+ }
+ }
+
+ if(this.options['singleclick'] === true) {
+ input.click(clickHandler);
+ } else {
+ input.dblclick(clickHandler);
+ }
if(this.options['showButton']) {
this.button = $('')
@@ -128,10 +141,6 @@
this.options['id'] = value;
this.input.attr('id', value);
break;
- case 'name':
- this.options['name'] = value;
- this.input.attr('name', value);
- break;
case 'attributes':
var input = this.input;
$.each(this.options['attributes'], function(key, value) {
diff --git a/js/loader.js b/js/loader.js
index 63e3c271..ce869a70 100644
--- a/js/loader.js
+++ b/js/loader.js
@@ -76,6 +76,15 @@ Contacts_Import={
});
}
}
+
+var openContact = function(id) {
+ if(typeof OC.Contacts !== 'undefined') {
+ OC.Contacts.openContact(id);
+ } else {
+ window.location.href = OC.linkTo('contacts', 'index.php') + '?id=' + id;
+ }
+}
+
$(document).ready(function(){
if(typeof FileActions !== 'undefined'){
FileActions.register('text/vcard','importaddressbook', OC.PERMISSION_READ, '', Contacts_Import.importdialog);
diff --git a/js/modernizr.js b/js/modernizr.js
new file mode 100644
index 00000000..ac23c55f
--- /dev/null
+++ b/js/modernizr.js
@@ -0,0 +1,1394 @@
+/*!
+ * Modernizr v2.6.3pre
+ * www.modernizr.com
+ *
+ * Copyright (c) Faruk Ates, Paul Irish, Alex Sexton
+ * Available under the BSD and MIT licenses: www.modernizr.com/license/
+ */
+
+/*
+ * Modernizr tests which native CSS3 and HTML5 features are available in
+ * the current UA and makes the results available to you in two ways:
+ * as properties on a global Modernizr object, and as classes on the
+ * element. This information allows you to progressively enhance
+ * your pages with a granular level of control over the experience.
+ *
+ * Modernizr has an optional (not included) conditional resource loader
+ * called Modernizr.load(), based on Yepnope.js (yepnopejs.com).
+ * To get a build that includes Modernizr.load(), as well as choosing
+ * which tests to include, go to www.modernizr.com/download/
+ *
+ * Authors Faruk Ates, Paul Irish, Alex Sexton
+ * Contributors Ryan Seddon, Ben Alman
+ */
+
+window.Modernizr = (function( window, document, undefined ) {
+
+ var version = '2.6.3pre',
+
+ Modernizr = {},
+
+ /*>>cssclasses*/
+ // option for enabling the HTML classes to be added
+ enableClasses = true,
+ /*>>cssclasses*/
+
+ docElement = document.documentElement,
+
+ /**
+ * Create our "modernizr" element that we do most feature tests on.
+ */
+ mod = 'modernizr',
+ modElem = document.createElement(mod),
+ mStyle = modElem.style,
+
+ /**
+ * Create the input element for various Web Forms feature tests.
+ */
+ inputElem /*>>inputelem*/ = document.createElement('input') /*>>inputelem*/ ,
+
+ /*>>smile*/
+ smile = ':)',
+ /*>>smile*/
+
+ toString = {}.toString,
+
+ // TODO :: make the prefixes more granular
+ /*>>prefixes*/
+ // List of property values to set for css tests. See ticket #21
+ prefixes = ' -webkit- -moz- -o- -ms- '.split(' '),
+ /*>>prefixes*/
+
+ /*>>domprefixes*/
+ // Following spec is to expose vendor-specific style properties as:
+ // elem.style.WebkitBorderRadius
+ // and the following would be incorrect:
+ // elem.style.webkitBorderRadius
+
+ // Webkit ghosts their properties in lowercase but Opera & Moz do not.
+ // Microsoft uses a lowercase `ms` instead of the correct `Ms` in IE8+
+ // erik.eae.net/archives/2008/03/10/21.48.10/
+
+ // More here: github.com/Modernizr/Modernizr/issues/issue/21
+ omPrefixes = 'Webkit Moz O ms',
+
+ cssomPrefixes = omPrefixes.split(' '),
+
+ domPrefixes = omPrefixes.toLowerCase().split(' '),
+ /*>>domprefixes*/
+
+ /*>>ns*/
+ ns = {'svg': 'http://www.w3.org/2000/svg'},
+ /*>>ns*/
+
+ tests = {},
+ inputs = {},
+ attrs = {},
+
+ classes = [],
+
+ slice = classes.slice,
+
+ featureName, // used in testing loop
+
+
+ /*>>teststyles*/
+ // Inject element with style element and some CSS rules
+ injectElementWithStyles = function( rule, callback, nodes, testnames ) {
+
+ var style, ret, node, docOverflow,
+ div = document.createElement('div'),
+ // After page load injecting a fake body doesn't work so check if body exists
+ body = document.body,
+ // IE6 and 7 won't return offsetWidth or offsetHeight unless it's in the body element, so we fake it.
+ fakeBody = body || document.createElement('body');
+
+ if ( parseInt(nodes, 10) ) {
+ // In order not to give false positives we create a node for each test
+ // This also allows the method to scale for unspecified uses
+ while ( nodes-- ) {
+ node = document.createElement('div');
+ node.id = testnames ? testnames[nodes] : mod + (nodes + 1);
+ div.appendChild(node);
+ }
+ }
+
+ // '].join('');
+ div.id = mod;
+ // IE6 will false positive on some tests due to the style element inside the test div somehow interfering offsetHeight, so insert it into body or fakebody.
+ // Opera will act all quirky when injecting elements in documentElement when page is served as xml, needs fakebody too. #270
+ (body ? div : fakeBody).innerHTML += style;
+ fakeBody.appendChild(div);
+ if ( !body ) {
+ //avoid crashing IE8, if background image is used
+ fakeBody.style.background = '';
+ //Safari 5.13/5.1.4 OSX stops loading if ::-webkit-scrollbar is used and scrollbars are visible
+ fakeBody.style.overflow = 'hidden';
+ docOverflow = docElement.style.overflow;
+ docElement.style.overflow = 'hidden';
+ docElement.appendChild(fakeBody);
+ }
+
+ ret = callback(div, rule);
+ // If this is done after page load we don't want to remove the body so check if body exists
+ if ( !body ) {
+ fakeBody.parentNode.removeChild(fakeBody);
+ docElement.style.overflow = docOverflow;
+ } else {
+ div.parentNode.removeChild(div);
+ }
+
+ return !!ret;
+
+ },
+ /*>>teststyles*/
+
+ /*>>mq*/
+ // adapted from matchMedia polyfill
+ // by Scott Jehl and Paul Irish
+ // gist.github.com/786768
+ testMediaQuery = function( mq ) {
+
+ var matchMedia = window.matchMedia || window.msMatchMedia;
+ if ( matchMedia ) {
+ return matchMedia(mq).matches;
+ }
+
+ var bool;
+
+ injectElementWithStyles('@media ' + mq + ' { #' + mod + ' { position: absolute; } }', function( node ) {
+ bool = (window.getComputedStyle ?
+ getComputedStyle(node, null) :
+ node.currentStyle)['position'] == 'absolute';
+ });
+
+ return bool;
+
+ },
+ /*>>mq*/
+
+
+ /*>>hasevent*/
+ //
+ // isEventSupported determines if a given element supports the given event
+ // kangax.github.com/iseventsupported/
+ //
+ // The following results are known incorrects:
+ // Modernizr.hasEvent("webkitTransitionEnd", elem) // false negative
+ // Modernizr.hasEvent("textInput") // in Webkit. github.com/Modernizr/Modernizr/issues/333
+ // ...
+ isEventSupported = (function() {
+
+ var TAGNAMES = {
+ 'select': 'input', 'change': 'input',
+ 'submit': 'form', 'reset': 'form',
+ 'error': 'img', 'load': 'img', 'abort': 'img'
+ };
+
+ function isEventSupported( eventName, element ) {
+
+ element = element || document.createElement(TAGNAMES[eventName] || 'div');
+ eventName = 'on' + eventName;
+
+ // When using `setAttribute`, IE skips "unload", WebKit skips "unload" and "resize", whereas `in` "catches" those
+ var isSupported = eventName in element;
+
+ if ( !isSupported ) {
+ // If it has no `setAttribute` (i.e. doesn't implement Node interface), try generic element
+ if ( !element.setAttribute ) {
+ element = document.createElement('div');
+ }
+ if ( element.setAttribute && element.removeAttribute ) {
+ element.setAttribute(eventName, '');
+ isSupported = is(element[eventName], 'function');
+
+ // If property was created, "remove it" (by setting value to `undefined`)
+ if ( !is(element[eventName], 'undefined') ) {
+ element[eventName] = undefined;
+ }
+ element.removeAttribute(eventName);
+ }
+ }
+
+ element = null;
+ return isSupported;
+ }
+ return isEventSupported;
+ })(),
+ /*>>hasevent*/
+
+ // TODO :: Add flag for hasownprop ? didn't last time
+
+ // hasOwnProperty shim by kangax needed for Safari 2.0 support
+ _hasOwnProperty = ({}).hasOwnProperty, hasOwnProp;
+
+ if ( !is(_hasOwnProperty, 'undefined') && !is(_hasOwnProperty.call, 'undefined') ) {
+ hasOwnProp = function (object, property) {
+ return _hasOwnProperty.call(object, property);
+ };
+ }
+ else {
+ hasOwnProp = function (object, property) { /* yes, this can give false positives/negatives, but most of the time we don't care about those */
+ return ((property in object) && is(object.constructor.prototype[property], 'undefined'));
+ };
+ }
+
+ // Adapted from ES5-shim https://github.com/kriskowal/es5-shim/blob/master/es5-shim.js
+ // es5.github.com/#x15.3.4.5
+
+ if (!Function.prototype.bind) {
+ Function.prototype.bind = function bind(that) {
+
+ var target = this;
+
+ if (typeof target != "function") {
+ throw new TypeError();
+ }
+
+ var args = slice.call(arguments, 1),
+ bound = function () {
+
+ if (this instanceof bound) {
+
+ var F = function(){};
+ F.prototype = target.prototype;
+ var self = new F();
+
+ var result = target.apply(
+ self,
+ args.concat(slice.call(arguments))
+ );
+ if (Object(result) === result) {
+ return result;
+ }
+ return self;
+
+ } else {
+
+ return target.apply(
+ that,
+ args.concat(slice.call(arguments))
+ );
+
+ }
+
+ };
+
+ return bound;
+ };
+ }
+
+ /**
+ * setCss applies given styles to the Modernizr DOM node.
+ */
+ function setCss( str ) {
+ mStyle.cssText = str;
+ }
+
+ /**
+ * setCssAll extrapolates all vendor-specific css strings.
+ */
+ function setCssAll( str1, str2 ) {
+ return setCss(prefixes.join(str1 + ';') + ( str2 || '' ));
+ }
+
+ /**
+ * is returns a boolean for if typeof obj is exactly type.
+ */
+ function is( obj, type ) {
+ return typeof obj === type;
+ }
+
+ /**
+ * contains returns a boolean for if substr is found within str.
+ */
+ function contains( str, substr ) {
+ return !!~('' + str).indexOf(substr);
+ }
+
+ /*>>testprop*/
+
+ // testProps is a generic CSS / DOM property test.
+
+ // In testing support for a given CSS property, it's legit to test:
+ // `elem.style[styleName] !== undefined`
+ // If the property is supported it will return an empty string,
+ // if unsupported it will return undefined.
+
+ // We'll take advantage of this quick test and skip setting a style
+ // on our modernizr element, but instead just testing undefined vs
+ // empty string.
+
+ // Because the testing of the CSS property names (with "-", as
+ // opposed to the camelCase DOM properties) is non-portable and
+ // non-standard but works in WebKit and IE (but not Gecko or Opera),
+ // we explicitly reject properties with dashes so that authors
+ // developing in WebKit or IE first don't end up with
+ // browser-specific content by accident.
+
+ function testProps( props, prefixed ) {
+ for ( var i in props ) {
+ var prop = props[i];
+ if ( !contains(prop, "-") && mStyle[prop] !== undefined ) {
+ return prefixed == 'pfx' ? prop : true;
+ }
+ }
+ return false;
+ }
+ /*>>testprop*/
+
+ // TODO :: add testDOMProps
+ /**
+ * testDOMProps is a generic DOM property test; if a browser supports
+ * a certain property, it won't return undefined for it.
+ */
+ function testDOMProps( props, obj, elem ) {
+ for ( var i in props ) {
+ var item = obj[props[i]];
+ if ( item !== undefined) {
+
+ // return the property name as a string
+ if (elem === false) return props[i];
+
+ // let's bind a function (and it has a bind method -- certain native objects that report that they are a
+ // function don't [such as webkitAudioContext])
+ if (is(item, 'function') && 'bind' in item){
+ // default to autobind unless override
+ return item.bind(elem || obj);
+ }
+
+ // return the unbound function or obj or value
+ return item;
+ }
+ }
+ return false;
+ }
+
+ /*>>testallprops*/
+ /**
+ * testPropsAll tests a list of DOM properties we want to check against.
+ * We specify literally ALL possible (known and/or likely) properties on
+ * the element including the non-vendor prefixed one, for forward-
+ * compatibility.
+ */
+ function testPropsAll( prop, prefixed, elem ) {
+
+ var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1),
+ props = (prop + ' ' + cssomPrefixes.join(ucProp + ' ') + ucProp).split(' ');
+
+ // did they call .prefixed('boxSizing') or are we just testing a prop?
+ if(is(prefixed, "string") || is(prefixed, "undefined")) {
+ return testProps(props, prefixed);
+
+ // otherwise, they called .prefixed('requestAnimationFrame', window[, elem])
+ } else {
+ props = (prop + ' ' + (domPrefixes).join(ucProp + ' ') + ucProp).split(' ');
+ return testDOMProps(props, prefixed, elem);
+ }
+ }
+ /*>>testallprops*/
+
+
+ /**
+ * Tests
+ * -----
+ */
+
+ // The *new* flexbox
+ // dev.w3.org/csswg/css3-flexbox
+
+ tests['flexbox'] = function() {
+ return testPropsAll('flexWrap');
+ };
+
+ // The *old* flexbox
+ // www.w3.org/TR/2009/WD-css3-flexbox-20090723/
+
+ tests['flexboxlegacy'] = function() {
+ return testPropsAll('boxDirection');
+ };
+
+ // On the S60 and BB Storm, getContext exists, but always returns undefined
+ // so we actually have to call getContext() to verify
+ // github.com/Modernizr/Modernizr/issues/issue/97/
+
+ tests['canvas'] = function() {
+ var elem = document.createElement('canvas');
+ return !!(elem.getContext && elem.getContext('2d'));
+ };
+
+ tests['canvastext'] = function() {
+ return !!(Modernizr['canvas'] && is(document.createElement('canvas').getContext('2d').fillText, 'function'));
+ };
+
+ // webk.it/70117 is tracking a legit WebGL feature detect proposal
+
+ // We do a soft detect which may false positive in order to avoid
+ // an expensive context creation: bugzil.la/732441
+
+ tests['webgl'] = function() {
+ return !!window.WebGLRenderingContext;
+ };
+
+ /*
+ * The Modernizr.touch test only indicates if the browser supports
+ * touch events, which does not necessarily reflect a touchscreen
+ * device, as evidenced by tablets running Windows 7 or, alas,
+ * the Palm Pre / WebOS (touch) phones.
+ *
+ * Additionally, Chrome (desktop) used to lie about its support on this,
+ * but that has since been rectified: crbug.com/36415
+ *
+ * We also test for Firefox 4 Multitouch Support.
+ *
+ * For more info, see: modernizr.github.com/Modernizr/touch.html
+ */
+
+ tests['touch'] = function() {
+ var bool;
+
+ if(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) {
+ bool = true;
+ } else {
+ injectElementWithStyles(['@media (',prefixes.join('touch-enabled),('),mod,')','{#modernizr{top:9px;position:absolute}}'].join(''), function( node ) {
+ bool = node.offsetTop === 9;
+ });
+ }
+
+ return bool;
+ };
+
+
+ // geolocation is often considered a trivial feature detect...
+ // Turns out, it's quite tricky to get right:
+ //
+ // Using !!navigator.geolocation does two things we don't want. It:
+ // 1. Leaks memory in IE9: github.com/Modernizr/Modernizr/issues/513
+ // 2. Disables page caching in WebKit: webk.it/43956
+ //
+ // Meanwhile, in Firefox < 8, an about:config setting could expose
+ // a false positive that would throw an exception: bugzil.la/688158
+
+ tests['geolocation'] = function() {
+ return 'geolocation' in navigator;
+ };
+
+
+ tests['postmessage'] = function() {
+ return !!window.postMessage;
+ };
+
+
+ // Chrome incognito mode used to throw an exception when using openDatabase
+ // It doesn't anymore.
+ tests['websqldatabase'] = function() {
+ return !!window.openDatabase;
+ };
+
+ // Vendors had inconsistent prefixing with the experimental Indexed DB:
+ // - Webkit's implementation is accessible through webkitIndexedDB
+ // - Firefox shipped moz_indexedDB before FF4b9, but since then has been mozIndexedDB
+ // For speed, we don't test the legacy (and beta-only) indexedDB
+ tests['indexedDB'] = function() {
+ return !!testPropsAll("indexedDB", window);
+ };
+
+ // documentMode logic from YUI to filter out IE8 Compat Mode
+ // which false positives.
+ tests['hashchange'] = function() {
+ return isEventSupported('hashchange', window) && (document.documentMode === undefined || document.documentMode > 7);
+ };
+
+ // Per 1.6:
+ // This used to be Modernizr.historymanagement but the longer
+ // name has been deprecated in favor of a shorter and property-matching one.
+ // The old API is still available in 1.6, but as of 2.0 will throw a warning,
+ // and in the first release thereafter disappear entirely.
+ tests['history'] = function() {
+ return !!(window.history && history.pushState);
+ };
+
+ tests['draganddrop'] = function() {
+ var div = document.createElement('div');
+ return ('draggable' in div) || ('ondragstart' in div && 'ondrop' in div);
+ };
+
+ // FF3.6 was EOL'ed on 4/24/12, but the ESR version of FF10
+ // will be supported until FF19 (2/12/13), at which time, ESR becomes FF17.
+ // FF10 still uses prefixes, so check for it until then.
+ // for more ESR info, see: mozilla.org/en-US/firefox/organizations/faq/
+ tests['websockets'] = function() {
+ return 'WebSocket' in window || 'MozWebSocket' in window;
+ };
+
+
+ // css-tricks.com/rgba-browser-support/
+ tests['rgba'] = function() {
+ // Set an rgba() color and check the returned value
+
+ setCss('background-color:rgba(150,255,150,.5)');
+
+ return contains(mStyle.backgroundColor, 'rgba');
+ };
+
+ tests['hsla'] = function() {
+ // Same as rgba(), in fact, browsers re-map hsla() to rgba() internally,
+ // except IE9 who retains it as hsla
+
+ setCss('background-color:hsla(120,40%,100%,.5)');
+
+ return contains(mStyle.backgroundColor, 'rgba') || contains(mStyle.backgroundColor, 'hsla');
+ };
+
+ tests['multiplebgs'] = function() {
+ // Setting multiple images AND a color on the background shorthand property
+ // and then querying the style.background property value for the number of
+ // occurrences of "url(" is a reliable method for detecting ACTUAL support for this!
+
+ setCss('background:url(https://),url(https://),red url(https://)');
+
+ // If the UA supports multiple backgrounds, there should be three occurrences
+ // of the string "url(" in the return value for elemStyle.background
+
+ return (/(url\s*\(.*?){3}/).test(mStyle.background);
+ };
+
+
+
+ // this will false positive in Opera Mini
+ // github.com/Modernizr/Modernizr/issues/396
+
+ tests['backgroundsize'] = function() {
+ return testPropsAll('backgroundSize');
+ };
+
+ tests['borderimage'] = function() {
+ return testPropsAll('borderImage');
+ };
+
+
+ // Super comprehensive table about all the unique implementations of
+ // border-radius: muddledramblings.com/table-of-css3-border-radius-compliance
+
+ tests['borderradius'] = function() {
+ return testPropsAll('borderRadius');
+ };
+
+ // WebOS unfortunately false positives on this test.
+ tests['boxshadow'] = function() {
+ return testPropsAll('boxShadow');
+ };
+
+ // FF3.0 will false positive on this test
+ tests['textshadow'] = function() {
+ return document.createElement('div').style.textShadow === '';
+ };
+
+
+ tests['opacity'] = function() {
+ // Browsers that actually have CSS Opacity implemented have done so
+ // according to spec, which means their return values are within the
+ // range of [0.0,1.0] - including the leading zero.
+
+ setCssAll('opacity:.55');
+
+ // The non-literal . in this regex is intentional:
+ // German Chrome returns this value as 0,55
+ // github.com/Modernizr/Modernizr/issues/#issue/59/comment/516632
+ return (/^0.55$/).test(mStyle.opacity);
+ };
+
+
+ // Note, Android < 4 will pass this test, but can only animate
+ // a single property at a time
+ // daneden.me/2011/12/putting-up-with-androids-bullshit/
+ tests['cssanimations'] = function() {
+ return testPropsAll('animationName');
+ };
+
+
+ tests['csscolumns'] = function() {
+ return testPropsAll('columnCount');
+ };
+
+
+ tests['cssgradients'] = function() {
+ /**
+ * For CSS Gradients syntax, please see:
+ * webkit.org/blog/175/introducing-css-gradients/
+ * developer.mozilla.org/en/CSS/-moz-linear-gradient
+ * developer.mozilla.org/en/CSS/-moz-radial-gradient
+ * dev.w3.org/csswg/css3-images/#gradients-
+ */
+
+ var str1 = 'background-image:',
+ str2 = 'gradient(linear,left top,right bottom,from(#9f9),to(white));',
+ str3 = 'linear-gradient(left top,#9f9, white);';
+
+ setCss(
+ // legacy webkit syntax (FIXME: remove when syntax not in use anymore)
+ (str1 + '-webkit- '.split(' ').join(str2 + str1) +
+ // standard syntax // trailing 'background-image:'
+ prefixes.join(str3 + str1)).slice(0, -str1.length)
+ );
+
+ return contains(mStyle.backgroundImage, 'gradient');
+ };
+
+
+ tests['cssreflections'] = function() {
+ return testPropsAll('boxReflect');
+ };
+
+
+ tests['csstransforms'] = function() {
+ return !!testPropsAll('transform');
+ };
+
+
+ tests['csstransforms3d'] = function() {
+
+ var ret = !!testPropsAll('perspective');
+
+ // Webkit's 3D transforms are passed off to the browser's own graphics renderer.
+ // It works fine in Safari on Leopard and Snow Leopard, but not in Chrome in
+ // some conditions. As a result, Webkit typically recognizes the syntax but
+ // will sometimes throw a false positive, thus we must do a more thorough check:
+ if ( ret && 'webkitPerspective' in docElement.style ) {
+
+ // Webkit allows this media query to succeed only if the feature is enabled.
+ // `@media (transform-3d),(-webkit-transform-3d){ ... }`
+ injectElementWithStyles('@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}', function( node, rule ) {
+ ret = node.offsetLeft === 9 && node.offsetHeight === 3;
+ });
+ }
+ return ret;
+ };
+
+
+ tests['csstransitions'] = function() {
+ return testPropsAll('transition');
+ };
+
+
+ /*>>fontface*/
+ // @font-face detection routine by Diego Perini
+ // javascript.nwbox.com/CSSSupport/
+
+ // false positives:
+ // WebOS github.com/Modernizr/Modernizr/issues/342
+ // WP7 github.com/Modernizr/Modernizr/issues/538
+ tests['fontface'] = function() {
+ var bool;
+
+ injectElementWithStyles('@font-face {font-family:"font";src:url("https://")}', function( node, rule ) {
+ var style = document.getElementById('smodernizr'),
+ sheet = style.sheet || style.styleSheet,
+ cssText = sheet ? (sheet.cssRules && sheet.cssRules[0] ? sheet.cssRules[0].cssText : sheet.cssText || '') : '';
+
+ bool = /src/i.test(cssText) && cssText.indexOf(rule.split(' ')[0]) === 0;
+ });
+
+ return bool;
+ };
+ /*>>fontface*/
+
+ // CSS generated content detection
+ tests['generatedcontent'] = function() {
+ var bool;
+
+ injectElementWithStyles(['#',mod,'{font:0/0 a}#',mod,':after{content:"',smile,'";visibility:hidden;font:3px/1 a}'].join(''), function( node ) {
+ bool = node.offsetHeight >= 3;
+ });
+
+ return bool;
+ };
+
+
+
+ // These tests evaluate support of the video/audio elements, as well as
+ // testing what types of content they support.
+ //
+ // We're using the Boolean constructor here, so that we can extend the value
+ // e.g. Modernizr.video // true
+ // Modernizr.video.ogg // 'probably'
+ //
+ // Codec values from : github.com/NielsLeenheer/html5test/blob/9106a8/index.html#L845
+ // thx to NielsLeenheer and zcorpan
+
+ // Note: in some older browsers, "no" was a return value instead of empty string.
+ // It was live in FF3.5.0 and 3.5.1, but fixed in 3.5.2
+ // It was also live in Safari 4.0.0 - 4.0.4, but fixed in 4.0.5
+
+ tests['video'] = function() {
+ var elem = document.createElement('video'),
+ bool = false;
+
+ // IE9 Running on Windows Server SKU can cause an exception to be thrown, bug #224
+ try {
+ if ( bool = !!elem.canPlayType ) {
+ bool = new Boolean(bool);
+ bool.ogg = elem.canPlayType('video/ogg; codecs="theora"') .replace(/^no$/,'');
+
+ // Without QuickTime, this value will be `undefined`. github.com/Modernizr/Modernizr/issues/546
+ bool.h264 = elem.canPlayType('video/mp4; codecs="avc1.42E01E"') .replace(/^no$/,'');
+
+ bool.webm = elem.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,'');
+ }
+
+ } catch(e) { }
+
+ return bool;
+ };
+
+ tests['audio'] = function() {
+ var elem = document.createElement('audio'),
+ bool = false;
+
+ try {
+ if ( bool = !!elem.canPlayType ) {
+ bool = new Boolean(bool);
+ bool.ogg = elem.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,'');
+ bool.mp3 = elem.canPlayType('audio/mpeg;') .replace(/^no$/,'');
+
+ // Mimetypes accepted:
+ // developer.mozilla.org/En/Media_formats_supported_by_the_audio_and_video_elements
+ // bit.ly/iphoneoscodecs
+ bool.wav = elem.canPlayType('audio/wav; codecs="1"') .replace(/^no$/,'');
+ bool.m4a = ( elem.canPlayType('audio/x-m4a;') ||
+ elem.canPlayType('audio/aac;')) .replace(/^no$/,'');
+ }
+ } catch(e) { }
+
+ return bool;
+ };
+
+
+ // In FF4, if disabled, window.localStorage should === null.
+
+ // Normally, we could not test that directly and need to do a
+ // `('localStorage' in window) && ` test first because otherwise Firefox will
+ // throw bugzil.la/365772 if cookies are disabled
+
+ // Also in iOS5 Private Browsing mode, attempting to use localStorage.setItem
+ // will throw the exception:
+ // QUOTA_EXCEEDED_ERRROR DOM Exception 22.
+ // Peculiarly, getItem and removeItem calls do not throw.
+
+ // Because we are forced to try/catch this, we'll go aggressive.
+
+ // Just FWIW: IE8 Compat mode supports these features completely:
+ // www.quirksmode.org/dom/html5.html
+ // But IE8 doesn't support either with local files
+
+ tests['localstorage'] = function() {
+ try {
+ localStorage.setItem(mod, mod);
+ localStorage.removeItem(mod);
+ return true;
+ } catch(e) {
+ return false;
+ }
+ };
+
+ tests['sessionstorage'] = function() {
+ try {
+ sessionStorage.setItem(mod, mod);
+ sessionStorage.removeItem(mod);
+ return true;
+ } catch(e) {
+ return false;
+ }
+ };
+
+
+ tests['webworkers'] = function() {
+ return !!window.Worker;
+ };
+
+
+ tests['applicationcache'] = function() {
+ return !!window.applicationCache;
+ };
+
+
+ // Thanks to Erik Dahlstrom
+ tests['svg'] = function() {
+ return !!document.createElementNS && !!document.createElementNS(ns.svg, 'svg').createSVGRect;
+ };
+
+ // specifically for SVG inline in HTML, not within XHTML
+ // test page: paulirish.com/demo/inline-svg
+ tests['inlinesvg'] = function() {
+ var div = document.createElement('div');
+ div.innerHTML = '';
+ return (div.firstChild && div.firstChild.namespaceURI) == ns.svg;
+ };
+
+ // SVG SMIL animation
+ tests['smil'] = function() {
+ return !!document.createElementNS && /SVGAnimate/.test(toString.call(document.createElementNS(ns.svg, 'animate')));
+ };
+
+ // This test is only for clip paths in SVG proper, not clip paths on HTML content
+ // demo: srufaculty.sru.edu/david.dailey/svg/newstuff/clipPath4.svg
+
+ // However read the comments to dig into applying SVG clippaths to HTML content here:
+ // github.com/Modernizr/Modernizr/issues/213#issuecomment-1149491
+ tests['svgclippaths'] = function() {
+ return !!document.createElementNS && /SVGClipPath/.test(toString.call(document.createElementNS(ns.svg, 'clipPath')));
+ };
+
+ /*>>webforms*/
+ // input features and input types go directly onto the ret object, bypassing the tests loop.
+ // Hold this guy to execute in a moment.
+ function webforms() {
+ /*>>input*/
+ // Run through HTML5's new input attributes to see if the UA understands any.
+ // We're using f which is the element created early on
+ // Mike Taylr has created a comprehensive resource for testing these attributes
+ // when applied to all input types:
+ // miketaylr.com/code/input-type-attr.html
+ // spec: www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#input-type-attr-summary
+
+ // Only input placeholder is tested while textarea's placeholder is not.
+ // Currently Safari 4 and Opera 11 have support only for the input placeholder
+ // Both tests are available in feature-detects/forms-placeholder.js
+ Modernizr['input'] = (function( props ) {
+ for ( var i = 0, len = props.length; i < len; i++ ) {
+ attrs[ props[i] ] = !!(props[i] in inputElem);
+ }
+ if (attrs.list){
+ // safari false positive's on datalist: webk.it/74252
+ // see also github.com/Modernizr/Modernizr/issues/146
+ attrs.list = !!(document.createElement('datalist') && window.HTMLDataListElement);
+ }
+ return attrs;
+ })('autocomplete autofocus list placeholder max min multiple pattern required step'.split(' '));
+ /*>>input*/
+
+ /*>>inputtypes*/
+ // Run through HTML5's new input types to see if the UA understands any.
+ // This is put behind the tests runloop because it doesn't return a
+ // true/false like all the other tests; instead, it returns an object
+ // containing each input type with its corresponding true/false value
+
+ // Big thanks to @miketaylr for the html5 forms expertise. miketaylr.com/
+ Modernizr['inputtypes'] = (function(props) {
+
+ for ( var i = 0, bool, inputElemType, defaultView, len = props.length; i < len; i++ ) {
+
+ inputElem.setAttribute('type', inputElemType = props[i]);
+ bool = inputElem.type !== 'text';
+
+ // We first check to see if the type we give it sticks..
+ // If the type does, we feed it a textual value, which shouldn't be valid.
+ // If the value doesn't stick, we know there's input sanitization which infers a custom UI
+ if ( bool ) {
+
+ inputElem.value = smile;
+ inputElem.style.cssText = 'position:absolute;visibility:hidden;';
+
+ if ( /^range$/.test(inputElemType) && inputElem.style.WebkitAppearance !== undefined ) {
+
+ docElement.appendChild(inputElem);
+ defaultView = document.defaultView;
+
+ // Safari 2-4 allows the smiley as a value, despite making a slider
+ bool = defaultView.getComputedStyle &&
+ defaultView.getComputedStyle(inputElem, null).WebkitAppearance !== 'textfield' &&
+ // Mobile android web browser has false positive, so must
+ // check the height to see if the widget is actually there.
+ (inputElem.offsetHeight !== 0);
+
+ docElement.removeChild(inputElem);
+
+ } else if ( /^(search|tel)$/.test(inputElemType) ){
+ // Spec doesn't define any special parsing or detectable UI
+ // behaviors so we pass these through as true
+
+ // Interestingly, opera fails the earlier test, so it doesn't
+ // even make it here.
+
+ } else if ( /^(url|email)$/.test(inputElemType) ) {
+ // Real url and email support comes with prebaked validation.
+ bool = inputElem.checkValidity && inputElem.checkValidity() === false;
+
+ } else {
+ // If the upgraded input compontent rejects the :) text, we got a winner
+ bool = inputElem.value != smile;
+ }
+ }
+
+ inputs[ props[i] ] = !!bool;
+ }
+ return inputs;
+ })('search tel url email datetime date month week time datetime-local number range color'.split(' '));
+ /*>>inputtypes*/
+ }
+ /*>>webforms*/
+
+
+ // End of test definitions
+ // -----------------------
+
+
+
+ // Run through all tests and detect their support in the current UA.
+ // todo: hypothetically we could be doing an array of tests and use a basic loop here.
+ for ( var feature in tests ) {
+ if ( hasOwnProp(tests, feature) ) {
+ // run the test, throw the return value into the Modernizr,
+ // then based on that boolean, define an appropriate className
+ // and push it into an array of classes we'll join later.
+ featureName = feature.toLowerCase();
+ Modernizr[featureName] = tests[feature]();
+
+ classes.push((Modernizr[featureName] ? '' : 'no-') + featureName);
+ }
+ }
+
+ /*>>webforms*/
+ // input tests need to run.
+ Modernizr.input || webforms();
+ /*>>webforms*/
+
+
+ /**
+ * addTest allows the user to define their own feature tests
+ * the result will be added onto the Modernizr object,
+ * as well as an appropriate className set on the html element
+ *
+ * @param feature - String naming the feature
+ * @param test - Function returning true if feature is supported, false if not
+ */
+ Modernizr.addTest = function ( feature, test ) {
+ if ( typeof feature == 'object' ) {
+ for ( var key in feature ) {
+ if ( hasOwnProp( feature, key ) ) {
+ Modernizr.addTest( key, feature[ key ] );
+ }
+ }
+ } else {
+
+ feature = feature.toLowerCase();
+
+ if ( Modernizr[feature] !== undefined ) {
+ // we're going to quit if you're trying to overwrite an existing test
+ // if we were to allow it, we'd do this:
+ // var re = new RegExp("\\b(no-)?" + feature + "\\b");
+ // docElement.className = docElement.className.replace( re, '' );
+ // but, no rly, stuff 'em.
+ return Modernizr;
+ }
+
+ test = typeof test == 'function' ? test() : test;
+
+ if (typeof enableClasses !== "undefined" && enableClasses) {
+ docElement.className += ' ' + (test ? '' : 'no-') + feature;
+ }
+ Modernizr[feature] = test;
+
+ }
+
+ return Modernizr; // allow chaining.
+ };
+
+
+ // Reset modElem.cssText to nothing to reduce memory footprint.
+ setCss('');
+ modElem = inputElem = null;
+
+ /*>>shiv*/
+ /*! HTML5 Shiv v3.6.1 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed */
+ ;(function(window, document) {
+ /*jshint evil:true */
+ /** Preset options */
+ var options = window.html5 || {};
+
+ /** Used to skip problem elements */
+ var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i;
+
+ /** Not all elements can be cloned in IE **/
+ var saveClones = /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i;
+
+ /** Detect whether the browser supports default html5 styles */
+ var supportsHtml5Styles;
+
+ /** Name of the expando, to work with multiple documents or to re-shiv one document */
+ var expando = '_html5shiv';
+
+ /** The id for the the documents expando */
+ var expanID = 0;
+
+ /** Cached data for each document */
+ var expandoData = {};
+
+ /** Detect whether the browser supports unknown elements */
+ var supportsUnknownElements;
+
+ (function() {
+ try {
+ var a = document.createElement('a');
+ a.innerHTML = '';
+ //if the hidden property is implemented we can assume, that the browser supports basic HTML5 Styles
+ supportsHtml5Styles = ('hidden' in a);
+
+ supportsUnknownElements = a.childNodes.length == 1 || (function() {
+ // assign a false positive if unable to shiv
+ (document.createElement)('a');
+ var frag = document.createDocumentFragment();
+ return (
+ typeof frag.cloneNode == 'undefined' ||
+ typeof frag.createDocumentFragment == 'undefined' ||
+ typeof frag.createElement == 'undefined'
+ );
+ }());
+ } catch(e) {
+ supportsHtml5Styles = true;
+ supportsUnknownElements = true;
+ }
+
+ }());
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Creates a style sheet with the given CSS text and adds it to the document.
+ * @private
+ * @param {Document} ownerDocument The document.
+ * @param {String} cssText The CSS text.
+ * @returns {StyleSheet} The style element.
+ */
+ function addStyleSheet(ownerDocument, cssText) {
+ var p = ownerDocument.createElement('p'),
+ parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement;
+
+ p.innerHTML = 'x';
+ return parent.insertBefore(p.lastChild, parent.firstChild);
+ }
+
+ /**
+ * Returns the value of `html5.elements` as an array.
+ * @private
+ * @returns {Array} An array of shived element node names.
+ */
+ function getElements() {
+ var elements = html5.elements;
+ return typeof elements == 'string' ? elements.split(' ') : elements;
+ }
+
+ /**
+ * Returns the data associated to the given document
+ * @private
+ * @param {Document} ownerDocument The document.
+ * @returns {Object} An object of data.
+ */
+ function getExpandoData(ownerDocument) {
+ var data = expandoData[ownerDocument[expando]];
+ if (!data) {
+ data = {};
+ expanID++;
+ ownerDocument[expando] = expanID;
+ expandoData[expanID] = data;
+ }
+ return data;
+ }
+
+ /**
+ * returns a shived element for the given nodeName and document
+ * @memberOf html5
+ * @param {String} nodeName name of the element
+ * @param {Document} ownerDocument The context document.
+ * @returns {Object} The shived element.
+ */
+ function createElement(nodeName, ownerDocument, data){
+ if (!ownerDocument) {
+ ownerDocument = document;
+ }
+ if(supportsUnknownElements){
+ return ownerDocument.createElement(nodeName);
+ }
+ if (!data) {
+ data = getExpandoData(ownerDocument);
+ }
+ var node;
+
+ if (data.cache[nodeName]) {
+ node = data.cache[nodeName].cloneNode();
+ } else if (saveClones.test(nodeName)) {
+ node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode();
+ } else {
+ node = data.createElem(nodeName);
+ }
+
+ // Avoid adding some elements to fragments in IE < 9 because
+ // * Attributes like `name` or `type` cannot be set/changed once an element
+ // is inserted into a document/fragment
+ // * Link elements with `src` attributes that are inaccessible, as with
+ // a 403 response, will cause the tab/window to crash
+ // * Script elements appended to fragments will execute when their `src`
+ // or `text` property is set
+ return node.canHaveChildren && !reSkip.test(nodeName) ? data.frag.appendChild(node) : node;
+ }
+
+ /**
+ * returns a shived DocumentFragment for the given document
+ * @memberOf html5
+ * @param {Document} ownerDocument The context document.
+ * @returns {Object} The shived DocumentFragment.
+ */
+ function createDocumentFragment(ownerDocument, data){
+ if (!ownerDocument) {
+ ownerDocument = document;
+ }
+ if(supportsUnknownElements){
+ return ownerDocument.createDocumentFragment();
+ }
+ data = data || getExpandoData(ownerDocument);
+ var clone = data.frag.cloneNode(),
+ i = 0,
+ elems = getElements(),
+ l = elems.length;
+ for(;i>shiv*/
+
+ // Assign private properties to the return object with prefix
+ Modernizr._version = version;
+
+ // expose these for the plugin API. Look in the source for how to join() them against your input
+ /*>>prefixes*/
+ Modernizr._prefixes = prefixes;
+ /*>>prefixes*/
+ /*>>domprefixes*/
+ Modernizr._domPrefixes = domPrefixes;
+ Modernizr._cssomPrefixes = cssomPrefixes;
+ /*>>domprefixes*/
+
+ /*>>mq*/
+ // Modernizr.mq tests a given media query, live against the current state of the window
+ // A few important notes:
+ // * If a browser does not support media queries at all (eg. oldIE) the mq() will always return false
+ // * A max-width or orientation query will be evaluated against the current state, which may change later.
+ // * You must specify values. Eg. If you are testing support for the min-width media query use:
+ // Modernizr.mq('(min-width:0)')
+ // usage:
+ // Modernizr.mq('only screen and (max-width:768)')
+ Modernizr.mq = testMediaQuery;
+ /*>>mq*/
+
+ /*>>hasevent*/
+ // Modernizr.hasEvent() detects support for a given event, with an optional element to test on
+ // Modernizr.hasEvent('gesturestart', elem)
+ Modernizr.hasEvent = isEventSupported;
+ /*>>hasevent*/
+
+ /*>>testprop*/
+ // Modernizr.testProp() investigates whether a given style property is recognized
+ // Note that the property names must be provided in the camelCase variant.
+ // Modernizr.testProp('pointerEvents')
+ Modernizr.testProp = function(prop){
+ return testProps([prop]);
+ };
+ /*>>testprop*/
+
+ /*>>testallprops*/
+ // Modernizr.testAllProps() investigates whether a given style property,
+ // or any of its vendor-prefixed variants, is recognized
+ // Note that the property names must be provided in the camelCase variant.
+ // Modernizr.testAllProps('boxSizing')
+ Modernizr.testAllProps = testPropsAll;
+ /*>>testallprops*/
+
+
+ /*>>teststyles*/
+ // Modernizr.testStyles() allows you to add custom styles to the document and test an element afterwards
+ // Modernizr.testStyles('#modernizr { position:absolute }', function(elem, rule){ ... })
+ Modernizr.testStyles = injectElementWithStyles;
+ /*>>teststyles*/
+
+
+ /*>>prefixed*/
+ // Modernizr.prefixed() returns the prefixed or nonprefixed property name variant of your input
+ // Modernizr.prefixed('boxSizing') // 'MozBoxSizing'
+
+ // Properties must be passed as dom-style camelcase, rather than `box-sizing` hypentated style.
+ // Return values will also be the camelCase variant, if you need to translate that to hypenated style use:
+ //
+ // str.replace(/([A-Z])/g, function(str,m1){ return '-' + m1.toLowerCase(); }).replace(/^ms-/,'-ms-');
+
+ // If you're trying to ascertain which transition end event to bind to, you might do something like...
+ //
+ // var transEndEventNames = {
+ // 'WebkitTransition' : 'webkitTransitionEnd',
+ // 'MozTransition' : 'transitionend',
+ // 'OTransition' : 'oTransitionEnd',
+ // 'msTransition' : 'MSTransitionEnd',
+ // 'transition' : 'transitionend'
+ // },
+ // transEndEventName = transEndEventNames[ Modernizr.prefixed('transition') ];
+
+ Modernizr.prefixed = function(prop, obj, elem){
+ if(!obj) {
+ return testPropsAll(prop, 'pfx');
+ } else {
+ // Testing DOM property e.g. Modernizr.prefixed('requestAnimationFrame', window) // 'mozRequestAnimationFrame'
+ return testPropsAll(prop, obj, elem);
+ }
+ };
+ /*>>prefixed*/
+
+
+ /*>>cssclasses*/
+ // Remove "no-js" class from element, if it exists:
+ docElement.className = docElement.className.replace(/(^|\s)no-js(\s|$)/, '$1$2') +
+
+ // Add the new classes to the element.
+ (enableClasses ? ' js ' + classes.join(' ') : '');
+ /*>>cssclasses*/
+
+ return Modernizr;
+
+})(this, this.document);
diff --git a/js/placeholder.polyfill.jquery.js b/js/placeholder.polyfill.jquery.js
new file mode 100644
index 00000000..16969eb8
--- /dev/null
+++ b/js/placeholder.polyfill.jquery.js
@@ -0,0 +1,212 @@
+/**
+* HTML5 placeholder polyfill
+* @requires jQuery - tested with 1.6.2 but might as well work with older versions
+*
+* code: https://github.com/ginader/HTML5-placeholder-polyfill
+* please report issues at: https://github.com/ginader/HTML5-placeholder-polyfill/issues
+*
+* Copyright (c) 2012 Dirk Ginader (ginader.de)
+* Dual licensed under the MIT and GPL licenses:
+* http://www.opensource.org/licenses/mit-license.php
+* http://www.gnu.org/licenses/gpl.html
+*
+* Version: 2.0.3
+*
+* History:
+* * 1.0 initial release
+* * 1.1 added support for multiline placeholders in textareas
+* * 1.2 Allow label to wrap the input element by noah https://github.com/ginader/HTML5-placeholder-polyfill/pull/1
+* * 1.3 New option to read placeholder to Screenreaders. Turned on by default
+* * 1.4 made placeholder more rubust to allow labels being offscreen + added minified version of the 3rd party libs
+* * 1.5 emptying the native placeholder to prevent double rendering in Browsers with partial support
+* * 1.6 optional reformat when a textarea is being resized - requires http://benalman.com/projects/jquery-resize-plugin/
+* * 1.7 feature detection is now included in the polyfill so you can simply include it without the need for Modernizr
+* * 1.8 replacing the HTML5 Boilerplate .visuallyhidden technique with one that still allows the placeholder to be rendered
+* * 1.8.1 bugfix for implicit labels
+* * 1.9 New option "hideOnFocus" which, if set to false will mimic the behavior of mobile safari and chrome (remove label when typed instead of onfocus)
+* * 1.9.1 added reformat event on window resize
+* * 1.9.2 more flexible way to "fix" labels that are hidden using clip() thanks to grahambates: https://github.com/ginader/HTML5-placeholder-polyfill/issues/12
+* * 2.0 new easier configuration technique and new options forceApply and AutoInit and support for setters and getters
+* * 2.0.1 changed check for empty field so a space character is no longer ignored
+* * 2.0.2 allow rerun of the placeholder() to cover generated elements - existing polyfilled placeholder will be repositioned. Fixing: https://github.com/ginader/HTML5-placeholder-polyfill/issues/15
+* * 2.0.3 turn debugging of for production. fix https://github.com/ginader/HTML5-placeholder-polyfill/issues/18
+*/
+
+(function($) {
+ var debug = false,
+ animId;
+ function showPlaceholderIfEmpty(input,options) {
+ if( input.val() === '' ){
+ input.data('placeholder').removeClass(options.hideClass);
+ }else{
+ input.data('placeholder').addClass(options.hideClass);
+ }
+ }
+ function hidePlaceholder(input,options){
+ input.data('placeholder').addClass(options.hideClass);
+ }
+ function positionPlaceholder(placeholder,input){
+ var ta = input.is('textarea');
+ placeholder.css({
+ width : input.innerWidth()-(ta ? 20 : 4),
+ height : input.innerHeight()-6,
+ lineHeight : input.css('line-height'),
+ whiteSpace : ta ? 'normal' : 'nowrap',
+ overflow : 'hidden'
+ }).offset(input.offset());
+ }
+ function startFilledCheckChange(input,options){
+ var input = input,
+ val = input.val();
+ (function checkloop(){
+ animId = requestAnimationFrame(checkloop);
+ if(input.val() != val){
+ hidePlaceholder(input,options);
+ stopCheckChange();
+ startEmptiedCheckChange(input,options);
+ }
+ })();
+ }
+ function startEmptiedCheckChange(input,options){
+ var input = input,
+ val = input.val();
+ (function checkloop(){
+ animId = requestAnimationFrame(checkloop);
+ showPlaceholderIfEmpty(input,options);
+ })();
+ }
+ function stopCheckChange(){
+ cancelAnimationFrame(animId);
+ }
+ function log(msg){
+ if(debug && window.console && window.console.log){
+ window.console.log(msg);
+ }
+ }
+
+ $.fn.placeHolder = function(config) {
+ log('init placeHolder');
+ var o = this;
+ var l = $(this).length;
+ this.options = $.extend({
+ className: 'placeholder', // css class that is used to style the placeholder
+ visibleToScreenreaders : true, // expose the placeholder text to screenreaders or not
+ visibleToScreenreadersHideClass : 'placeholder-hide-except-screenreader', // css class is used to visually hide the placeholder
+ visibleToNoneHideClass : 'placeholder-hide', // css class used to hide the placeholder for all
+ hideOnFocus : false, // either hide the placeholder on focus or on type
+ removeLabelClass : 'visuallyhidden', // remove this class from a label (to fix hidden labels)
+ hiddenOverrideClass : 'visuallyhidden-with-placeholder', // replace the label above with this class
+ forceHiddenOverride : true, // allow the replace of the removeLabelClass with hiddenOverrideClass or not
+ forceApply : false, // apply the polyfill even for browser with native support
+ autoInit : true // init automatically or not
+ }, config);
+ this.options.hideClass = this.options.visibleToScreenreaders ? this.options.visibleToScreenreadersHideClass : this.options.visibleToNoneHideClass;
+ return $(this).each(function(index) {
+ var input = $(this),
+ text = input.attr('placeholder'),
+ id = input.attr('id'),
+ label,placeholder,titleNeeded,polyfilled;
+ label = input.closest('label');
+ input.removeAttr('placeholder');
+ if(!label.length && !id){
+ log('the input element with the placeholder needs an id!');
+ return;
+ }
+ label = label.length ? label : $('label[for="'+id+'"]').first();
+ if(!label.length){
+ log('the input element with the placeholder needs a label!');
+ return;
+ }
+ polyfilled = $(label).find('.placeholder');
+ if(polyfilled.length) {
+ //log('the input element already has a polyfilled placeholder!');
+ positionPlaceholder(polyfilled,input);
+ return input;
+ }
+
+ if(label.hasClass(o.options.removeLabelClass)){
+ label.removeClass(o.options.removeLabelClass)
+ .addClass(o.options.hiddenOverrideClass);
+ }
+
+ placeholder = $(''+text+'').appendTo(label);
+
+ titleNeeded = (placeholder.width() > input.width());
+ if(titleNeeded){
+ placeholder.attr('title',text);
+ }
+ positionPlaceholder(placeholder,input);
+ input.data('placeholder',placeholder);
+ placeholder.data('input',placeholder);
+ placeholder.click(function(){
+ $(this).data('input').focus();
+ });
+ input.focusin(function() {
+ if(!o.options.hideOnFocus && window.requestAnimationFrame){
+ startFilledCheckChange(input,o.options);
+ }else{
+ hidePlaceholder(input,o.options);
+ }
+ });
+ input.focusout(function(){
+ showPlaceholderIfEmpty($(this),o.options);
+ if(!o.options.hideOnFocus && window.cancelAnimationFrame){
+ stopCheckChange();
+ }
+ });
+ showPlaceholderIfEmpty(input,o.options);
+
+ // reformat on window resize and optional reformat on font resize - requires: http://www.tomdeater.com/jquery/onfontresize/
+ $(document).bind("fontresize resize", function(){
+ positionPlaceholder(placeholder,input);
+ });
+
+ // optional reformat when a textarea is being resized - requires http://benalman.com/projects/jquery-resize-plugin/
+ if($.event.special.resize){
+ $("textarea").bind("resize", function(e){
+ positionPlaceholder(placeholder,input);
+ });
+ }else{
+ // we simply disable the resizeablilty of textareas when we can't react on them resizing
+ $("textarea").css('resize','none');
+ }
+
+ if(index >= l-1){
+ $.attrHooks.placeholder = {
+ get: function(elem) {
+ if (elem.nodeName.toLowerCase() == 'input' || elem.nodeName.toLowerCase() == 'textarea') {
+ if( $(elem).data('placeholder') ){
+ // has been polyfilled
+ return $( $(elem).data('placeholder') ).text();
+ }else{
+ // native / not yet polyfilled
+ return $(elem)[0].placeholder;
+ }
+
+ }else{
+ return undefined;
+ }
+ },
+ set: function(elem, value){
+ return $( $(elem).data('placeholder') ).text(value);
+ }
+ };
+ }
+ });
+
+
+
+ };
+ $(function(){
+ var config = window.placeHolderConfig || {};
+ if(config.autoInit === false){
+ log('placeholder:abort because autoInit is off');
+ return
+ }
+ if('placeholder' in $('')[0] && !config.forceApply){ // don't run the polyfill when the browser has native support
+ log('placeholder:abort because browser has native support');
+ return;
+ }
+ $('input[placeholder], textarea[placeholder]').placeHolder(config);
+ });
+})(jQuery);
\ No newline at end of file
diff --git a/js/settings.js b/js/settings.js
index 8ea99ab5..bab136a8 100644
--- a/js/settings.js
+++ b/js/settings.js
@@ -15,12 +15,11 @@ OC.Contacts.Settings = OC.Contacts.Settings || {
var active = tgt.is(':checked');
//console.log('doActivate: ', id, active);
$.post(OC.filePath('contacts', 'ajax', 'addressbook/activate.php'), {id: id, active: Number(active)}, function(jsondata) {
- if (jsondata.status == 'success'){
- if(!active) {
- $('#contacts h3[data-id="'+id+'"],#contacts ul[data-id="'+id+'"]').remove();
- } else {
- OC.Contacts.Contacts.update();
- }
+ if (jsondata.status == 'success') {
+ $(document).trigger('request.addressbook.activate', {
+ id: id,
+ activate: active,
+ });
} else {
//console.log('Error:', jsondata.data.message);
OC.Contacts.notify(t('contacts', 'Error') + ': ' + jsondata.data.message);
@@ -41,7 +40,7 @@ OC.Contacts.Settings = OC.Contacts.Settings || {
$('#contacts h3[data-id="'+id+'"],#contacts ul[data-id="'+id+'"]').remove();
row.remove()
OC.Contacts.Settings.Addressbook.showActions(['new',]);
- OC.Contacts.Contacts.update();
+ OC.Contacts.update();
} else {
OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error'));
}
@@ -108,7 +107,7 @@ OC.Contacts.Settings = OC.Contacts.Settings || {
row.find('td.name').text(jsondata.data.addressbook.displayname);
row.find('td.description').text(jsondata.data.addressbook.description);
}
- OC.Contacts.Contacts.update();
+ OC.Contacts.update();
} else {
OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error'));
}
@@ -157,11 +156,9 @@ $(document).ready(function() {
event.preventDefault();
if(OC.Contacts.Settings.Addressbook.adrsettings.is(':visible')) {
OC.Contacts.Settings.Addressbook.adrsettings.slideUp();
- OC.Contacts.Settings.Addressbook.adrsettings.prev('dt').hide();
moreless.text(t('contacts', 'More...'));
} else {
OC.Contacts.Settings.Addressbook.adrsettings.slideDown();
- OC.Contacts.Settings.Addressbook.adrsettings.prev('dt').show();
moreless.text(t('contacts', 'Less...'));
}
});
diff --git a/l10n/ar.php b/l10n/ar.php
index c428087e..cab2c41c 100644
--- a/l10n/ar.php
+++ b/l10n/ar.php
@@ -3,7 +3,14 @@
"Cannot add empty property." => "لا يمكنك اضافه صفه خاليه.",
"At least one of the address fields has to be filled out." => "يجب ملء على الاقل خانه واحده من العنوان.",
"Information about vCard is incorrect. Please reload the page." => "المعلومات الموجودة في ال vCard غير صحيحة. الرجاء إعادة تحديث الصفحة.",
+"There is no error, the file uploaded with success" => "تم ترفيع الملفات بنجاح.",
+"The uploaded file exceeds the upload_max_filesize directive in php.ini" => "حجم الملف الذي تريد ترفيعه أعلى مما upload_max_filesize يسمح به في ملف php.ini",
+"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" => "حجم الملف الذي تريد ترفيعه أعلى مما MAX_FILE_SIZE يسمح به في واجهة ال HTML.",
+"The uploaded file was only partially uploaded" => "تم ترفيع جزء من الملفات الذي تريد ترفيعها فقط",
+"No file was uploaded" => "لم يتم ترفيع أي من الملفات",
+"Missing a temporary folder" => "المجلد المؤقت غير موجود",
"Contacts" => "المعارف",
+"Upload too large" => "حجم الترفيع أعلى من المسموح",
"Download" => "انزال",
"Edit" => "تعديل",
"Delete" => "حذف",
@@ -12,16 +19,21 @@
"Contact could not be found." => "لم يتم العثور على الشخص.",
"Work" => "الوظيفة",
"Home" => "البيت",
+"Other" => "شيء آخر",
"Mobile" => "الهاتف المحمول",
"Text" => "معلومات إضافية",
"Voice" => "صوت",
"Fax" => "الفاكس",
"Video" => "الفيديو",
"Pager" => "الرنان",
-"Birthday" => "تاريخ الميلاد",
"Contact" => "معرفه",
"Add Contact" => "أضف شخص ",
+"Import" => "إدخال",
+"Settings" => "اعدادات",
+"Close" => "اغلق",
"Organization" => "المؤسسة",
+"Birthday" => "تاريخ الميلاد",
+"Groups" => "مجموعات",
"Preferred" => "مفضل",
"Phone" => "الهاتف",
"Email" => "البريد الالكتروني",
@@ -36,7 +48,12 @@
"Zipcode" => "رقم المنطقة",
"Country" => "البلد",
"Addressbook" => "كتاب العناوين",
+"more info" => "مزيد من المعلومات",
+"Primary address (Kontact et al)" => "العنوان الرئيسي (جهات الإتصال)",
+"iOS/OS X" => "ط ن ت/ ن ت 10",
"Addressbooks" => "كتب العناوين",
+"Share" => "شارك",
"New Address Book" => "كتاب عناوين جديد",
+"Name" => "اسم",
"Save" => "حفظ"
);
diff --git a/l10n/bg_BG.php b/l10n/bg_BG.php
new file mode 100644
index 00000000..b7d82856
--- /dev/null
+++ b/l10n/bg_BG.php
@@ -0,0 +1,23 @@
+ "Няма избрани категории за изтриване",
+"There is no error, the file uploaded with success" => "Файлът е качен успешно",
+"The uploaded file exceeds the upload_max_filesize directive in php.ini" => "Файлът който се опитвате да качите, надвишава зададените стойности в upload_max_filesize в PHP.INI",
+"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" => "Файлът който се опитвате да качите надвишава стойностите в MAX_FILE_SIZE в HTML формата.",
+"The uploaded file was only partially uploaded" => "Файлът е качен частично",
+"No file was uploaded" => "Фахлът не бе качен",
+"Missing a temporary folder" => "Липсва временната папка",
+"Error" => "Грешка",
+"Upload Error" => "Грешка при качване",
+"Download" => "Изтегляне",
+"Delete" => "Изтриване",
+"Cancel" => "Отказ",
+"Work" => "Работа",
+"Other" => "Друго",
+"Import" => "Внасяне",
+"Birthday" => "Роджен ден",
+"Groups" => "Групи",
+"Email" => "Е-поща",
+"Address" => "Адрес",
+"Share" => "Споделяне",
+"Save" => "Запис"
+);
diff --git a/l10n/ca.php b/l10n/ca.php
index c793f254..80eafd46 100644
--- a/l10n/ca.php
+++ b/l10n/ca.php
@@ -126,19 +126,8 @@
"Video" => "Vídeo",
"Pager" => "Paginador",
"Internet" => "Internet",
-"Birthday" => "Aniversari",
-"Business" => "Negocis",
-"Call" => "Trucada",
-"Clients" => "Clients",
-"Deliverer" => "Emissari",
-"Holidays" => "Vacances",
-"Ideas" => "Idees",
-"Journey" => "Viatge",
-"Jubilee" => "Aniversari",
-"Meeting" => "Reunió",
-"Personal" => "Personal",
-"Projects" => "Projectes",
-"Questions" => "Preguntes",
+"Friends" => "Amics",
+"Family" => "Familia",
"{name}'s Birthday" => "Aniversari de {name}",
"Contact" => "Contacte",
"You do not have the permissions to add contacts to this addressbook." => "No teniu permisos per afegir contactes a aquesta llibreta d'adreces.",
@@ -176,6 +165,7 @@
"Web site" => "Adreça web",
"http://www.somesite.com" => "http://www.somesite.com",
"Go to web site" => "Vés a la web",
+"Birthday" => "Aniversari",
"dd-mm-yyyy" => "dd-mm-yyyy",
"Groups" => "Grups",
"Separate groups with commas" => "Separeu els grups amb comes",
diff --git a/l10n/cs_CZ.php b/l10n/cs_CZ.php
index 46ac3bc1..62e0ad4a 100644
--- a/l10n/cs_CZ.php
+++ b/l10n/cs_CZ.php
@@ -126,19 +126,8 @@
"Video" => "Video",
"Pager" => "Pager",
"Internet" => "Internet",
-"Birthday" => "Narozeniny",
-"Business" => "Pracovní",
-"Call" => "Volat",
-"Clients" => "Klienti",
-"Deliverer" => "Dodavatel",
-"Holidays" => "Svátky",
-"Ideas" => "Nápady",
-"Journey" => "Cestování",
-"Jubilee" => "Jubileum",
-"Meeting" => "Schůze",
-"Personal" => "Osobní",
-"Projects" => "Projekty",
-"Questions" => "Otázky",
+"Friends" => "Přátelé",
+"Family" => "Rodina",
"{name}'s Birthday" => "Narozeniny {name}",
"Contact" => "Kontakt",
"You do not have the permissions to add contacts to this addressbook." => "Nemáte práva pro přidání kontaktů do této knihy adres.",
@@ -176,6 +165,7 @@
"Web site" => "Webová stránka",
"http://www.somesite.com" => "http://www.somesite.com",
"Go to web site" => "Přejít na webovou stránku",
+"Birthday" => "Narozeniny",
"dd-mm-yyyy" => "dd. mm. yyyy",
"Groups" => "Skupiny",
"Separate groups with commas" => "Oddělte skupiny čárkami",
diff --git a/l10n/da.php b/l10n/da.php
index 387a5cf6..a845cf7e 100644
--- a/l10n/da.php
+++ b/l10n/da.php
@@ -126,19 +126,6 @@
"Video" => "Video",
"Pager" => "Personsøger",
"Internet" => "Internet",
-"Birthday" => "Fødselsdag",
-"Business" => "Firma",
-"Call" => "Ring op",
-"Clients" => "Klienter",
-"Deliverer" => "Udbringer",
-"Holidays" => "Ferie",
-"Ideas" => "Ideer",
-"Journey" => "Rejse",
-"Jubilee" => "Jubilæum",
-"Meeting" => "Møde",
-"Personal" => "Personligt",
-"Projects" => "Projekter",
-"Questions" => "Spørgsmål",
"{name}'s Birthday" => "{name}s fødselsdag",
"Contact" => "Kontaktperson",
"You do not have the permissions to add contacts to this addressbook." => "Du har ikke rettigheder til at tilføje kontaktpersoner til denne adressebog",
@@ -176,6 +163,7 @@
"Web site" => "Hjemmeside",
"http://www.somesite.com" => "http://www.somesite.com",
"Go to web site" => "Gå til web site",
+"Birthday" => "Fødselsdag",
"dd-mm-yyyy" => "dd-mm-åååå",
"Groups" => "Grupper",
"Separate groups with commas" => "Opdel gruppenavne med kommaer",
diff --git a/l10n/de.php b/l10n/de.php
index 10dfd04f..3192d01f 100644
--- a/l10n/de.php
+++ b/l10n/de.php
@@ -126,19 +126,8 @@
"Video" => "Video",
"Pager" => "Pager",
"Internet" => "Internet",
-"Birthday" => "Geburtstag",
-"Business" => "Geschäftlich",
-"Call" => "Anruf",
-"Clients" => "Kunden",
-"Deliverer" => "Lieferant",
-"Holidays" => "Feiertage",
-"Ideas" => "Ideen",
-"Journey" => "Reise",
-"Jubilee" => "Jubiläum",
-"Meeting" => "Besprechung",
-"Personal" => "Persönlich",
-"Projects" => "Projekte",
-"Questions" => "Fragen",
+"Friends" => "Freunde",
+"Family" => "Familie",
"{name}'s Birthday" => "Geburtstag von {name}",
"Contact" => "Kontakt",
"You do not have the permissions to add contacts to this addressbook." => "Du besitzt nicht die erforderlichen Rechte, diesem Adressbuch Kontakte hinzuzufügen.",
@@ -176,6 +165,7 @@
"Web site" => "Webseite",
"http://www.somesite.com" => "http://www.somesite.com",
"Go to web site" => "Webseite aufrufen",
+"Birthday" => "Geburtstag",
"dd-mm-yyyy" => "dd.mm.yyyy",
"Groups" => "Gruppen",
"Separate groups with commas" => "Gruppen mit Komma getrennt",
diff --git a/l10n/de_DE.php b/l10n/de_DE.php
index 9b1355e5..c80ccaf2 100644
--- a/l10n/de_DE.php
+++ b/l10n/de_DE.php
@@ -126,19 +126,8 @@
"Video" => "Video",
"Pager" => "Pager",
"Internet" => "Internet",
-"Birthday" => "Geburtstag",
-"Business" => "Geschäftlich",
-"Call" => "Anruf",
-"Clients" => "Kunden",
-"Deliverer" => "Lieferant",
-"Holidays" => "Feiertage",
-"Ideas" => "Ideen",
-"Journey" => "Reise",
-"Jubilee" => "Jubiläum",
-"Meeting" => "Besprechung",
-"Personal" => "Persönlich",
-"Projects" => "Projekte",
-"Questions" => "Fragen",
+"Friends" => "Freunde",
+"Family" => "Familie",
"{name}'s Birthday" => "Geburtstag von {name}",
"Contact" => "Kontakt",
"You do not have the permissions to add contacts to this addressbook." => "Sie besitzen nicht die erforderlichen Rechte, diesem Adressbuch Kontakte hinzuzufügen.",
@@ -176,6 +165,7 @@
"Web site" => "Webseite",
"http://www.somesite.com" => "http://www.somesite.com",
"Go to web site" => "Webseite aufrufen",
+"Birthday" => "Geburtstag",
"dd-mm-yyyy" => "dd.mm.yyyy",
"Groups" => "Gruppen",
"Separate groups with commas" => "Gruppen mit Komma getrennt",
diff --git a/l10n/el.php b/l10n/el.php
index ecd1e1ac..85fab5f2 100644
--- a/l10n/el.php
+++ b/l10n/el.php
@@ -126,19 +126,8 @@
"Video" => "Βίντεο",
"Pager" => "Βομβητής",
"Internet" => "Διαδίκτυο",
-"Birthday" => "Γενέθλια",
-"Business" => "Επιχείρηση",
-"Call" => "Κάλεσε",
-"Clients" => "Πελάτες",
-"Deliverer" => "Προμηθευτής",
-"Holidays" => "Διακοπές",
-"Ideas" => "Ιδέες",
-"Journey" => "Ταξίδι",
-"Jubilee" => "Ιωβηλαίο",
-"Meeting" => "Συνάντηση",
-"Personal" => "Προσωπικό",
-"Projects" => "Έργα",
-"Questions" => "Ερωτήσεις",
+"Friends" => "Φίλοι",
+"Family" => "Οικογένεια",
"{name}'s Birthday" => "Τα Γεννέθλια του/της {name}",
"Contact" => "Επαφή",
"You do not have the permissions to add contacts to this addressbook." => "Δεν έχετε δικαιώματα να προσθέσετε επαφές σε αυτό το βιβλίο διευθύνσεων.",
@@ -176,6 +165,7 @@
"Web site" => "Ιστότοπος",
"http://www.somesite.com" => "http://www.somesite.com",
"Go to web site" => "Πήγαινε στον ιστότοπο",
+"Birthday" => "Γενέθλια",
"dd-mm-yyyy" => "ΗΗ-ΜΜ-ΕΕΕΕ",
"Groups" => "Ομάδες",
"Separate groups with commas" => "Διαχώρισε τις ομάδες με κόμμα ",
diff --git a/l10n/eo.php b/l10n/eo.php
index 96a5150e..2f7472eb 100644
--- a/l10n/eo.php
+++ b/l10n/eo.php
@@ -73,6 +73,7 @@
"Your browser doesn't support AJAX upload. Please click on the profile picture to select a photo to upload." => "Via foliumilo ne kongruas kun AJAX-alŝutado. Bonvolu klaki la profilbildon por elekti foton alŝutotan.",
"Unable to upload your file as it is a directory or has 0 bytes" => "Ne eblis alŝuti vian dosieron ĉar ĝi estas dosierujo aŭ havas 0 duumokojn",
"Upload Error" => "Eraro dum alŝuto",
+"Pending" => "Traktotaj",
"Import done" => "Enporto plenumiĝis",
"Not all files uploaded. Retrying..." => "Ne ĉiuj dosieroj alŝutiĝis. Reprovante...",
"Something went wrong with the upload, please retry." => "Io malsukcesis dum alŝuto, bonvolu reprovi.",
@@ -81,6 +82,7 @@
"Result: " => "Rezulto: ",
" imported, " => " enportoj, ",
" failed." => "malsukcesoj.",
+"Displayname cannot be empty." => "Montronomo devas ne esti malplena.",
"Show CardDav link" => "Montri CardDav-ligilon",
"Show read-only VCF link" => "Montri nur legeblan VCF-ligilon",
"Download" => "Elŝuti",
@@ -120,19 +122,6 @@
"Video" => "Videaĵo",
"Pager" => "Televokilo",
"Internet" => "Interreto",
-"Birthday" => "Naskiĝotago",
-"Business" => "Negoco",
-"Call" => "Voko",
-"Clients" => "Klientoj",
-"Deliverer" => "Liveranto",
-"Holidays" => "Ferioj",
-"Ideas" => "Ideoj",
-"Journey" => "Vojaĝo",
-"Jubilee" => "Jubileo",
-"Meeting" => "Kunveno",
-"Personal" => "Persona",
-"Projects" => "Projektoj",
-"Questions" => "Demandoj",
"{name}'s Birthday" => "Naskiĝtago de {name}",
"Contact" => "Kontakto",
"You do not have the permissions to add contacts to this addressbook." => "Vi ne havas la permeson aldoni kontaktojn al ĉi tiu adresaro.",
@@ -167,6 +156,7 @@
"Web site" => "TTT-ejo",
"http://www.somesite.com" => "http://www.iuejo.com",
"Go to web site" => "Iri al TTT-ejon",
+"Birthday" => "Naskiĝotago",
"dd-mm-yyyy" => "yyyy-mm-dd",
"Groups" => "Grupoj",
"Separate groups with commas" => "Disigi grupojn per komoj",
diff --git a/l10n/es.php b/l10n/es.php
index c5620b3d..edecf4ab 100644
--- a/l10n/es.php
+++ b/l10n/es.php
@@ -126,19 +126,8 @@
"Video" => "Vídeo",
"Pager" => "Localizador",
"Internet" => "Internet",
-"Birthday" => "Cumpleaños",
-"Business" => "Negocio",
-"Call" => "Llamada",
-"Clients" => "Clientes",
-"Deliverer" => "Mensajero",
-"Holidays" => "Vacaciones",
-"Ideas" => "Ideas",
-"Journey" => "Jornada",
-"Jubilee" => "Aniversario",
-"Meeting" => "Reunión",
-"Personal" => "Personal",
-"Projects" => "Proyectos",
-"Questions" => "Preguntas",
+"Friends" => "Amigos",
+"Family" => "Familia",
"{name}'s Birthday" => "Cumpleaños de {name}",
"Contact" => "Contacto",
"You do not have the permissions to add contacts to this addressbook." => "No tiene permisos para añadir contactos a esta libreta de direcciones.",
@@ -176,6 +165,7 @@
"Web site" => "Sitio Web",
"http://www.somesite.com" => "http://www.unsitio.com",
"Go to web site" => "Ir al sitio Web",
+"Birthday" => "Cumpleaños",
"dd-mm-yyyy" => "dd-mm-yyyy",
"Groups" => "Grupos",
"Separate groups with commas" => "Separa los grupos con comas",
diff --git a/l10n/es_AR.php b/l10n/es_AR.php
index 9f5cd1b0..64645023 100644
--- a/l10n/es_AR.php
+++ b/l10n/es_AR.php
@@ -126,19 +126,8 @@
"Video" => "Video",
"Pager" => "Pager",
"Internet" => "Internet",
-"Birthday" => "Cumpleaños",
-"Business" => "Negocio",
-"Call" => "Llamar",
-"Clients" => "Clientes",
-"Deliverer" => "Distribuidor",
-"Holidays" => "Vacaciones",
-"Ideas" => "Ideas",
-"Journey" => "Viaje",
-"Jubilee" => "Aniversario",
-"Meeting" => "Reunión",
-"Personal" => "Personal",
-"Projects" => "Proyectos",
-"Questions" => "Preguntas",
+"Friends" => "Amigos",
+"Family" => "Familia",
"{name}'s Birthday" => "Cumpleaños de {name}",
"Contact" => "Contacto",
"You do not have the permissions to add contacts to this addressbook." => "No tenés permisos para agregar contactos a esta agenda.",
@@ -176,6 +165,7 @@
"Web site" => "Página web",
"http://www.somesite.com" => "http://www.somesite.com",
"Go to web site" => "Ir al sitio web",
+"Birthday" => "Cumpleaños",
"dd-mm-yyyy" => "dd-mm-yyyy",
"Groups" => "Grupos",
"Separate groups with commas" => "Separá los grupos con comas",
diff --git a/l10n/et_EE.php b/l10n/et_EE.php
index 6d783dcd..f1a0b071 100644
--- a/l10n/et_EE.php
+++ b/l10n/et_EE.php
@@ -126,19 +126,8 @@
"Video" => "Video",
"Pager" => "Piipar",
"Internet" => "Internet",
-"Birthday" => "Sünnipäev",
-"Business" => "Ettevõte",
-"Call" => "Helista",
-"Clients" => "Kliendid",
-"Deliverer" => "Kohaletoimetaja",
-"Holidays" => "Puhkused",
-"Ideas" => "Ideed",
-"Journey" => "Teekond",
-"Jubilee" => "Juubel",
-"Meeting" => "Kohtumine",
-"Personal" => "Isiklik",
-"Projects" => "Projektid",
-"Questions" => "Küsimused",
+"Friends" => "Sõbrad",
+"Family" => "Pereliikmed",
"{name}'s Birthday" => "{name} sünnipäev",
"Contact" => "Kontakt",
"You do not have the permissions to add contacts to this addressbook." => "Sul pole õigusi sellesse aadressiraamatusse kontaktide lisamiseks.",
@@ -176,6 +165,7 @@
"Web site" => "Veebisait",
"http://www.somesite.com" => "http://www.mingisait.ee",
"Go to web site" => "Mine veebisaidile",
+"Birthday" => "Sünnipäev",
"dd-mm-yyyy" => "dd.mm.yyyy",
"Groups" => "Grupid",
"Separate groups with commas" => "Eralda grupid komadega",
diff --git a/l10n/eu.php b/l10n/eu.php
index 11451504..eb70e3ff 100644
--- a/l10n/eu.php
+++ b/l10n/eu.php
@@ -124,19 +124,6 @@
"Video" => "Bideoa",
"Pager" => "Bilagailua",
"Internet" => "Internet",
-"Birthday" => "Jaioteguna",
-"Business" => "Negozioak",
-"Call" => "Deia",
-"Clients" => "Bezeroak",
-"Deliverer" => "Banatzailea",
-"Holidays" => "Oporrak",
-"Ideas" => "Ideiak",
-"Journey" => "Bidaia",
-"Jubilee" => "Urteurrenak",
-"Meeting" => "Bilera",
-"Personal" => "Pertsonala",
-"Projects" => "Proiektuak",
-"Questions" => "Galderak",
"{name}'s Birthday" => "{name}ren jaioteguna",
"Contact" => "Kontaktua",
"You do not have the permissions to add contacts to this addressbook." => "Ez duzu helbide-liburu honetara kontaktuak gehitzeko baimenik.",
@@ -174,6 +161,7 @@
"Web site" => "Web orria",
"http://www.somesite.com" => "http://www.webgunea.com",
"Go to web site" => "Web orrira joan",
+"Birthday" => "Jaioteguna",
"dd-mm-yyyy" => "yyyy-mm-dd",
"Groups" => "Taldeak",
"Separate groups with commas" => "Banatu taldeak komekin",
diff --git a/l10n/fa.php b/l10n/fa.php
index 56188202..abf08e10 100644
--- a/l10n/fa.php
+++ b/l10n/fa.php
@@ -49,15 +49,24 @@
"Error" => "خطا",
"Format custom, Short name, Full name, Reverse or Reverse with comma" => "Format custom, Short name, Full name, Reverse or Reverse with comma",
"Select type" => "نوع را انتخاب کنید",
+"Select photo" => "تصویر را انتخاب کنید",
"This property has to be non-empty." => "این ویژگی باید به صورت غیر تهی عمل کند",
"Couldn't serialize elements." => "قابلیت مرتب سازی عناصر وجود ندارد",
"'deleteProperty' called without type argument. Please report at bugs.owncloud.org" => "پاک کردن ویژگی بدون استدلال انجام شده.لطفا این مورد را گزارش دهید:bugs.owncloud.org",
"Edit name" => "نام تغییر",
"No files selected for upload." => "هیچ فایلی برای آپلود انتخاب نشده است",
"The file you are trying to upload exceed the maximum size for file uploads on this server." => "حجم فایل بسیار بیشتر از حجم تنظیم شده در تنظیمات سرور است",
+"Upload too large" => "سایز فایل برای آپلود زیاد است(م.تنظیمات در php.ini)",
+"Only image files can be used as profile picture." => "تنها تصاویر می توانند برای تصویر پروفایل انتخاب شوند",
+"Wrong file type" => "خطا در نوع فایل",
+"Your browser doesn't support AJAX upload. Please click on the profile picture to select a photo to upload." => "مرورگر شما از آژاکس پشتیبانی نمی کند. لطفا از مرورگر بهتری استفاده کرده و یا بر روی تصویر پروفایل کلیک کنید تا تصوبر دیگری را جهت بارگذاری برگزینید",
+"Unable to upload your file as it is a directory or has 0 bytes" => "ناتوان در بارگذاری یا فایل یک پوشه است یا 0بایت دارد",
+"Upload Error" => "خطا در بار گذاری",
+"Pending" => "در انتظار",
"Result: " => "نتیجه:",
" imported, " => "وارد شد،",
" failed." => "ناموفق",
+"Displayname cannot be empty." => "اسم نمایشی نمی تواند خالی باشد",
"Download" => "بارگیری",
"Edit" => "ویرایش",
"Delete" => "پاک کردن",
@@ -66,6 +75,7 @@
"Contact could not be found." => "اتصال ویا تماسی یافت نشد",
"Work" => "کار",
"Home" => "خانه",
+"Other" => "دیگر",
"Mobile" => "موبایل",
"Text" => "متن",
"Voice" => "صدا",
@@ -74,11 +84,11 @@
"Video" => "رسانه تصویری",
"Pager" => "صفحه",
"Internet" => "اینترنت",
-"Birthday" => "روزتولد",
"{name}'s Birthday" => "روز تولد {name} است",
"Contact" => "اشخاص",
"Add Contact" => "افزودن اطلاعات شخص مورد نظر",
"Import" => "وارد کردن",
+"Settings" => "تنظیمات",
"Close" => "بستن",
"Drop photo to upload" => "تصویر را به اینجا بکشید تا بار گذازی شود",
"Delete current photo" => "پاک کردن تصویر کنونی",
@@ -89,6 +99,7 @@
"Organization" => "نهاد(ارگان)",
"Nickname" => "نام مستعار",
"Enter nickname" => "یک نام مستعار وارد کنید",
+"Birthday" => "روزتولد",
"dd-mm-yyyy" => "dd-mm-yyyy",
"Groups" => "گروه ها",
"Separate groups with commas" => "جدا کردن گروه ها به وسیله درنگ نما",
@@ -150,6 +161,8 @@
"Primary address (Kontact et al)" => "نشانی اولیه",
"iOS/OS X" => "iOS/OS X ",
"Addressbooks" => "کتابچه ی نشانی ها",
+"Share" => "اشتراکگزاری",
"New Address Book" => "کتابچه نشانه های جدید",
+"Name" => "نام",
"Save" => "ذخیره سازی"
);
diff --git a/l10n/fi_FI.php b/l10n/fi_FI.php
index 208874aa..6933ccda 100644
--- a/l10n/fi_FI.php
+++ b/l10n/fi_FI.php
@@ -123,19 +123,8 @@
"Video" => "Video",
"Pager" => "Hakulaite",
"Internet" => "Internet",
-"Birthday" => "Syntymäpäivä",
-"Business" => "Työ",
-"Call" => "Kutsu",
-"Clients" => "Asiakkaat",
-"Deliverer" => "Toimittaja",
-"Holidays" => "Vapaapäivät",
-"Ideas" => "Ideat",
-"Journey" => "Matka",
-"Jubilee" => "Juhla",
-"Meeting" => "Kokous",
-"Personal" => "Henkilökohtainen",
-"Projects" => "Projektit",
-"Questions" => "Kysymykset",
+"Friends" => "Kaverit",
+"Family" => "Perhe",
"{name}'s Birthday" => "Henkilön {name} syntymäpäivä",
"Contact" => "Yhteystieto",
"You do not have the permissions to add contacts to this addressbook." => "Sinulla ei ole oikeuksia lisätä yhteystietoja tähän osoitekirjaan.",
@@ -173,6 +162,7 @@
"Web site" => "Verkkosivu",
"http://www.somesite.com" => "http://www.somesite.com",
"Go to web site" => "Siirry verkkosivulle",
+"Birthday" => "Syntymäpäivä",
"dd-mm-yyyy" => "pp-kk-vvvv",
"Groups" => "Ryhmät",
"Separate groups with commas" => "Erota ryhmät pilkuilla",
@@ -236,6 +226,7 @@
"create a new addressbook" => "luo uusi osoitekirja",
"Name of new addressbook" => "Uuden osoitekirjan nimi",
"Importing contacts" => "Tuodaan yhteystietoja",
+"
You have no contacts in your addressbook.
You can import VCF files by dragging them to the contacts list and either drop them on an addressbook to import into it, or on an empty spot to create a new addressbook and import into that. You can also import by clicking on the import button at the bottom of the list.
" => "
Osoitekirjassasi ei ole yhteystietoja.
Voit tuoda VCF-tiedostoja vetämällä ne yhteystietoluetteloon ja pudottamalla ne haluamaasi osoitekirjaan, tai lisätä yhteystiedon uuteen osoitekirjaan pudottamalla sen tyhjään tilaan. Vaihtoehtoisesti voit myös napsauttaa Tuo-painiketta luettelon alaosassa.
",
"Add contact" => "Lisää yhteystieto",
"Select Address Books" => "Valitse osoitekirjat",
"Enter description" => "Anna kuvaus",
diff --git a/l10n/fr.php b/l10n/fr.php
index 3e20bcd6..2d288d9d 100644
--- a/l10n/fr.php
+++ b/l10n/fr.php
@@ -126,19 +126,6 @@
"Video" => "Vidéo",
"Pager" => "Bipeur",
"Internet" => "Internet",
-"Birthday" => "Anniversaire",
-"Business" => "Business",
-"Call" => "Appel",
-"Clients" => "Clients",
-"Deliverer" => "Livreur",
-"Holidays" => "Vacances",
-"Ideas" => "Idées",
-"Journey" => "Trajet",
-"Jubilee" => "Jubilé",
-"Meeting" => "Rendez-vous",
-"Personal" => "Personnel",
-"Projects" => "Projets",
-"Questions" => "Questions",
"{name}'s Birthday" => "Anniversaire de {name}",
"Contact" => "Contact",
"You do not have the permissions to add contacts to this addressbook." => "Vous n'avez pas les droits suffisants pour ajouter des contacts à ce carnet d'adresses.",
@@ -176,6 +163,7 @@
"Web site" => "Page web",
"http://www.somesite.com" => "http://www.somesite.com",
"Go to web site" => "Allez à la page web",
+"Birthday" => "Anniversaire",
"dd-mm-yyyy" => "jj-mm-aaaa",
"Groups" => "Groupes",
"Separate groups with commas" => "Séparer les groupes avec des virgules",
diff --git a/l10n/gl.php b/l10n/gl.php
index 6c05156c..b5385fd7 100644
--- a/l10n/gl.php
+++ b/l10n/gl.php
@@ -1,21 +1,25 @@
"Produciuse un erro (des)activando a axenda.",
"id is not set." => "non se estableceu o id.",
-"Cannot update addressbook with an empty name." => "Non se pode actualizar a libreta de enderezos sen completar o nome.",
-"No ID provided" => "Non se proveeu ID",
+"Cannot update addressbook with an empty name." => "Non se pode actualizar a caderno de enderezos sen completar o nome.",
+"No ID provided" => "Non se deu o ID ",
"Error setting checksum." => "Erro establecendo a suma de verificación",
"No categories selected for deletion." => "Non se seleccionaron categorías para borrado.",
-"No address books found." => "Non se atoparon libretas de enderezos.",
+"No address books found." => "Non se atoparon cadernos de enderezos.",
"No contacts found." => "Non se atoparon contactos.",
"element name is not set." => "non se nomeou o elemento.",
+"Could not parse contact: " => "Non se puido engadir o contacto: ",
"Cannot add empty property." => "Non se pode engadir unha propiedade baleira.",
"At least one of the address fields has to be filled out." => "Polo menos un dos campos do enderezo ten que ser cuberto.",
"Trying to add duplicate property: " => "Tentando engadir propiedade duplicada: ",
-"Information about vCard is incorrect. Please reload the page." => "A información sobre a vCard é incorrecta. Por favor volva cargar a páxina.",
+"Missing IM parameter." => "Falta un parámetro do MI.",
+"Unknown IM: " => "MI descoñecido:",
+"Information about vCard is incorrect. Please reload the page." => "A información sobre a vCard é incorrecta. Volva cargar a páxina.",
"Missing ID" => "ID perdido",
"Error parsing VCard for ID: \"" => "Erro procesando a VCard para o ID: \"",
"checksum is not set." => "non se estableceu a suma de verificación.",
-"Information about vCard is incorrect. Please reload the page: " => "A información sobre a vCard é incorrecta. Por favor, recargue a páxina: ",
+"Information about vCard is incorrect. Please reload the page: " => "A información sobre a vCard é incorrecta. Recargue a páxina: ",
+"Something went FUBAR. " => "Algo se escangallou.",
"No contact ID was submitted." => "Non se enviou ningún ID de contacto.",
"Error reading contact photo." => "Erro lendo a fotografía do contacto.",
"Error saving temporary file." => "Erro gardando o ficheiro temporal.",
@@ -24,7 +28,7 @@
"No photo path was submitted." => "Non se enviou a ruta a unha foto.",
"File doesn't exist:" => "O ficheiro non existe:",
"Error loading image." => "Erro cargando imaxe.",
-"Error getting contact object." => "Erro obtendo o obxeto contacto.",
+"Error getting contact object." => "Erro obtendo o obxecto contacto.",
"Error getting PHOTO property." => "Erro obtendo a propiedade PHOTO.",
"Error saving contact." => "Erro gardando o contacto.",
"Error resizing image" => "Erro cambiando o tamaño da imaxe",
@@ -32,39 +36,88 @@
"Error creating temporary image" => "Erro creando a imaxe temporal",
"Error finding image: " => "Erro buscando a imaxe: ",
"Error uploading contacts to storage." => "Erro subindo os contactos ao almacén.",
-"There is no error, the file uploaded with success" => "Non houbo erros, o ficheiro subeuse con éxito",
+"There is no error, the file uploaded with success" => "Non houbo erros, o ficheiro subiuse con éxito",
"The uploaded file exceeds the upload_max_filesize directive in php.ini" => "O ficheiro subido supera a directiva upload_max_filesize no php.ini",
"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" => "O ficheiro subido supera a directiva MAX_FILE_SIZE especificada no formulario HTML",
"The uploaded file was only partially uploaded" => "O ficheiro so foi parcialmente subido",
-"No file was uploaded" => "Non se subeu ningún ficheiro",
+"No file was uploaded" => "Non se subiu ningún ficheiro",
"Missing a temporary folder" => "Falta o cartafol temporal",
"Couldn't save temporary image: " => "Non se puido gardar a imaxe temporal: ",
"Couldn't load temporary image: " => "Non se puido cargar a imaxe temporal: ",
-"No file was uploaded. Unknown error" => "Non se subeu ningún ficheiro. Erro descoñecido.",
+"No file was uploaded. Unknown error" => "Non se subiu ningún ficheiro. Erro descoñecido.",
"Contacts" => "Contactos",
-"Sorry, this functionality has not been implemented yet" => "Sentímolo, esta función aínda non foi implementada.",
+"Sorry, this functionality has not been implemented yet" => "Esta función aínda non foi implementada.",
"Not implemented" => "Non implementada.",
"Couldn't get a valid address." => "Non se puido obter un enderezo de correo válido.",
"Error" => "Erro",
+"Please enter an email address." => "Introduce unha dirección de correo electrónico.",
+"Enter name" => "Indique o nome",
"Format custom, Short name, Full name, Reverse or Reverse with comma" => "Formato personalizado, Nome corto, Nome completo, Inverso ou Inverso con coma",
"Select type" => "Seleccione tipo",
+"Select photo" => "Seleccione fotografía",
+"You do not have permission to add contacts to " => "Non tes permisos para engadir contactos a",
+"Please select one of your own address books." => "Escolle un das túas axenda.",
+"Permission error" => " Erro nos permisos ",
+"Click to undo deletion of \"" => "Fai clic para desfacer que se eliminara \"",
+"Cancelled deletion of: \"" => "Cancelar a eliminación de: \"",
"This property has to be non-empty." => "Esta propiedade non pode quedar baldeira.",
-"Couldn't serialize elements." => "Non se puido serializar os elementos.",
-"'deleteProperty' called without type argument. Please report at bugs.owncloud.org" => "'deleteProperty' chamado sen argumento. Por favor, informe en bugs.owncloud.org",
+"Couldn't serialize elements." => "Non se puideron poñer os elementos en serie",
+"Unknown error. Please check logs." => "Erro descoñecido. Comproba os rexistros log.",
+"'deleteProperty' called without type argument. Please report at bugs.owncloud.org" => "'deleteProperty' chamado sen argumento. Informe en bugs.owncloud.org",
"Edit name" => "Editar nome",
"No files selected for upload." => "Sen ficheiros escollidos para subir.",
"The file you are trying to upload exceed the maximum size for file uploads on this server." => "O ficheiro que tenta subir supera o tamaño máximo permitido neste servidor.",
+"Error loading profile picture." => "Erro ao cargar a imaxe de perfil.",
+"Some contacts are marked for deletion, but not deleted yet. Please wait for them to be deleted." => "Algúns contactos están marcados para ser eliminados máis aínda non se eliminaron. Espera a que se eliminen.",
+"Do you want to merge these address books?" => "Queres combinar estes cadernos de enderezos?",
+"Shared by " => "Compartido por",
+"Upload too large" => "Subida demasiado grande",
+"Only image files can be used as profile picture." => "Só se poden usar ficheiros de imaxe como foto de perfil.",
+"Wrong file type" => "Tipo de ficheiro incorrecto",
+"Your browser doesn't support AJAX upload. Please click on the profile picture to select a photo to upload." => "O teu navegador non soporta a subida AJAX. Fai clic na foto de perfil para seleccionar unha foto para subir.",
+"Unable to upload your file as it is a directory or has 0 bytes" => "Non se puido subir o ficheiro pois ou é un directorio ou ten 0 bytes",
+"Upload Error" => "Erro na subida",
+"Pending" => "Pendentes",
+"Import done" => "Importación realizada",
+"Not all files uploaded. Retrying..." => "Non se subiron todos os ficheiros. Intentándoo de novo...",
+"Something went wrong with the upload, please retry." => "Algo fallou na subida de ficheiros. Inténtao de novo.",
+"Importing..." => "Importando...",
+"The address book name cannot be empty." => "Non se pode deixar baleiro o nome do caderno de enderezos.",
"Result: " => "Resultado: ",
" imported, " => " importado, ",
" failed." => " fallou.",
+"Displayname cannot be empty." => "Displayname non pode estar baldeiro.",
+"Show CardDav link" => "Mostrar a ligazón de CardDav",
+"Show read-only VCF link" => "Mostrar as ligazóns a VCF de só lectura",
"Download" => "Descargar",
"Edit" => "Editar",
"Delete" => "Eliminar",
"Cancel" => "Cancelar",
-"This is not your addressbook." => "Esta non é a súa axenda.",
+"More..." => "Máis...",
+"Less..." => "Menos...",
+"You do not have the permissions to read this addressbook." => "Non tes permisos para ler este caderno de enderezos.",
+"You do not have the permissions to update this addressbook." => "Non tes permisos para actualizar este caderno de enderezos.",
+"There was an error updating the addressbook." => "Houbo un erro actualizando o caderno de enderezos.",
+"You do not have the permissions to delete this addressbook." => "Non tes permisos para eliminar este caderno de enderezos.",
+"There was an error deleting this addressbook." => "Houbo un erro borrando este caderno de enderezos.",
+"Addressbook not found: " => "Non se atopou o caderno de enderezos:",
+"This is not your addressbook." => "Esta non é a túa axenda.",
"Contact could not be found." => "Non se atopou o contacto.",
+"Jabber" => "Jabber",
+"AIM" => "AIM",
+"MSN" => "MSN",
+"Twitter" => "Twitter",
+"GoogleTalk" => "Google Talk",
+"Facebook" => "Facebook",
+"XMPP" => "XMPP",
+"ICQ" => "ICQ",
+"Yahoo" => "Yahoo",
+"Skype" => "Skype",
+"QQ" => "QQ",
+"GaduGadu" => "GaduGadu",
"Work" => "Traballo",
"Home" => "Casa",
+"Other" => "Outro",
"Mobile" => "Móbil",
"Text" => "Texto",
"Voice" => "Voz",
@@ -73,12 +126,33 @@
"Video" => "Vídeo",
"Pager" => "Paxinador",
"Internet" => "Internet",
-"Birthday" => "Aniversario",
-"{name}'s Birthday" => "Cumpleanos de {name}",
+"Friends" => "Amigos",
+"Family" => "Familia",
+"{name}'s Birthday" => "Aniversario de {name}",
"Contact" => "Contacto",
+"You do not have the permissions to add contacts to this addressbook." => "Non tes permisos para engadir contactos a este caderno de enderezos.",
+"Could not find the vCard with ID." => "Non se atopa a vCard coa ID.",
+"You do not have the permissions to edit this contact." => "Non tes permisos para editar este contacto.",
+"Could not find the vCard with ID: " => "Non se atopa a vCard co ID:",
+"Could not find the Addressbook with ID: " => "Non se pode atopar o caderno de enderezos coa ID:",
+"You do not have the permissions to delete this contact." => "Non tes permisos para eliminar este contacto.",
+"There was an error deleting this contact." => "Houbo un erro eliminando este contacto.",
"Add Contact" => "Engadir contacto",
"Import" => "Importar",
+"Settings" => "Preferencias",
"Close" => "Pechar",
+"Keyboard shortcuts" => "Atallos de teclado",
+"Navigation" => "Navegación",
+"Next contact in list" => "Seguinte contacto na lista",
+"Previous contact in list" => "Contacto anterior na lista",
+"Expand/collapse current addressbook" => "Expandir/contraer o caderno de enderezos actual",
+"Next addressbook" => "Seguinte caderno de enderezos",
+"Previous addressbook" => "Anterior caderno de enderezos",
+"Actions" => "Accións",
+"Refresh contacts list" => "Anovar a lista de contactos",
+"Add new contact" => "Engadir un contacto novo",
+"Add new addressbook" => "Engadir un novo caderno de enderezos",
+"Delete current contact" => "Eliminar o contacto actual",
"Drop photo to upload" => "Solte a foto a subir",
"Delete current photo" => "Borrar foto actual",
"Edit current photo" => "Editar a foto actual",
@@ -86,39 +160,51 @@
"Select photo from ownCloud" => "Escoller foto desde ownCloud",
"Edit name details" => "Editar detalles do nome",
"Organization" => "Organización",
-"Nickname" => "Apodo",
-"Enter nickname" => "Introuza apodo",
+"Nickname" => "Alcume",
+"Enter nickname" => "Introduza o alcume",
+"Web site" => "Sitio web",
+"http://www.somesite.com" => "http://www.unhaligazon.net",
+"Go to web site" => "Ir ao sitio web",
+"Birthday" => "Aniversario",
"dd-mm-yyyy" => "dd-mm-yyyy",
"Groups" => "Grupos",
"Separate groups with commas" => "Separe grupos con comas",
"Edit groups" => "Editar grupos",
"Preferred" => "Preferido",
-"Please specify a valid email address." => "Por favor indique un enderezo de correo electrónico válido.",
-"Enter email address" => "Introduza enderezo de correo electrónico",
-"Mail to address" => "Correo ao enderezo",
-"Delete email address" => "Borrar enderezo de correo electrónico",
+"Please specify a valid email address." => "Indica unha dirección de correo electrónico válida.",
+"Enter email address" => "Introduza unha dirección de correo electrónico",
+"Mail to address" => "Enviar correo ao enderezo",
+"Delete email address" => "Borrar o enderezo de correo electrónico",
"Enter phone number" => "Introducir número de teléfono",
"Delete phone number" => "Borrar número de teléfono",
+"Instant Messenger" => "Mensaxería instantánea",
+"Delete IM" => "Eliminar o MI",
"View on map" => "Ver no mapa",
-"Edit address details" => "Editar detalles do enderezo",
+"Edit address details" => "Editar os detalles do enderezo",
"Add notes here." => "Engadir aquí as notas.",
"Add field" => "Engadir campo",
"Phone" => "Teléfono",
"Email" => "Correo electrónico",
+"Instant Messaging" => "Mensaxería instantánea",
"Address" => "Enderezo",
"Note" => "Nota",
"Download contact" => "Descargar contacto",
"Delete contact" => "Borrar contacto",
"The temporary image has been removed from cache." => "A imaxe temporal foi eliminada da caché.",
-"Edit address" => "Editar enderezo",
+"Edit address" => "Editar o enderezo",
"Type" => "Escribir",
"PO Box" => "Apartado de correos",
+"Street address" => "Enderezo da rúa",
+"Street and number" => "Rúa e número",
"Extended" => "Ampliado",
+"Apartment number etc." => "Número de apartamento etc.",
"City" => "Cidade",
"Region" => "Autonomía",
+"E.g. state or province" => "P.ex estado ou provincia",
"Zipcode" => "Código postal",
+"Postal code" => "Código Postal",
"Country" => "País",
-"Addressbook" => "Axenda",
+"Addressbook" => "Caderno de enderezos",
"Hon. prefixes" => "Prefixos honoríficos",
"Miss" => "Srta",
"Ms" => "Sra/Srta",
@@ -126,7 +212,7 @@
"Sir" => "Sir",
"Mrs" => "Sra",
"Dr" => "Dr",
-"Given name" => "Apodo",
+"Given name" => "Alcume",
"Additional names" => "Nomes adicionais",
"Family name" => "Nome familiar",
"Hon. suffixes" => "Sufixos honorarios",
@@ -139,16 +225,22 @@
"Jr." => "Jr.",
"Sn." => "Sn.",
"Import a contacts file" => "Importar un ficheiro de contactos",
-"Please choose the addressbook" => "Por favor escolla unha libreta de enderezos",
-"create a new addressbook" => "crear unha nova libreta de enderezos",
-"Name of new addressbook" => "Nome da nova libreta de enderezos",
+"Please choose the addressbook" => "Escolle o caderno de enderezos",
+"create a new addressbook" => "crear un novo caderno de enderezos",
+"Name of new addressbook" => "Nome do novo caderno de enderezos",
"Importing contacts" => "Importando contactos",
+"
You have no contacts in your addressbook.
You can import VCF files by dragging them to the contacts list and either drop them on an addressbook to import into it, or on an empty spot to create a new addressbook and import into that. You can also import by clicking on the import button at the bottom of the list.
" => "
Non tes contactos no teu caderno de enderezos.
Podes importar ficheiros VCF arrastrándoos á lista de contactos ou ben tirándoos enriba do caderno de enderezos para importalos alí. Tamén arrastrándoos e deixándoos nun punto baleiro créase un novo caderno de enderezos e impórtanse alí. Igualmente podes empregar o botón de importar que tes no fondo da lista.
",
"Add contact" => "Engadir contacto",
+"Select Address Books" => "Escoller o cadernos de enderezos",
+"Enter description" => "Introducir a descrición",
"CardDAV syncing addresses" => "Enderezos CardDAV a sincronizar",
"more info" => "máis información",
"Primary address (Kontact et al)" => "Enderezo primario (Kontact et al)",
"iOS/OS X" => "iOS/OS X",
-"Addressbooks" => "Axendas",
-"New Address Book" => "Nova axenda",
+"Addressbooks" => "Caderno de enderezos",
+"Share" => "Compartir",
+"New Address Book" => "Novo caderno de enderezos",
+"Name" => "Nome",
+"Description" => "Descrición",
"Save" => "Gardar"
);
diff --git a/l10n/he.php b/l10n/he.php
index 50116453..53f76224 100644
--- a/l10n/he.php
+++ b/l10n/he.php
@@ -37,6 +37,11 @@
"No file was uploaded" => "שום קובץ לא הועלה",
"Missing a temporary folder" => "תקיה זמנית חסרה",
"Contacts" => "אנשי קשר",
+"Error" => "שגיאה",
+"Upload too large" => "העלאה גדולה מידי",
+"Unable to upload your file as it is a directory or has 0 bytes" => "לא יכול להעלות את הקובץ מכיוון שזו תקיה או שמשקל הקובץ 0 בתים",
+"Upload Error" => "שגיאת העלאה",
+"Pending" => "ממתין",
"Download" => "הורדה",
"Edit" => "עריכה",
"Delete" => "מחיקה",
@@ -45,6 +50,7 @@
"Contact could not be found." => "לא ניתן לאתר איש קשר",
"Work" => "עבודה",
"Home" => "בית",
+"Other" => "אחר",
"Mobile" => "נייד",
"Text" => "טקסט",
"Voice" => "קולי",
@@ -53,11 +59,12 @@
"Video" => "וידאו",
"Pager" => "זימונית",
"Internet" => "אינטרנט",
-"Birthday" => "יום הולדת",
"{name}'s Birthday" => "יום ההולדת של {name}",
"Contact" => "איש קשר",
"Add Contact" => "הוספת איש קשר",
"Import" => "יבא",
+"Settings" => "הגדרות",
+"Close" => "סגירה",
"Drop photo to upload" => "גרור ושחרר תמונה בשביל להעלות",
"Delete current photo" => "מחק תמונה נוכחית",
"Edit current photo" => "ערוך תמונה נוכחית",
@@ -67,6 +74,7 @@
"Organization" => "ארגון",
"Nickname" => "כינוי",
"Enter nickname" => "הכנס כינוי",
+"Birthday" => "יום הולדת",
"dd-mm-yyyy" => "dd-mm-yyyy",
"Groups" => "קבוצות",
"Separate groups with commas" => "הפרד קבוצות עם פסיקים",
@@ -127,6 +135,8 @@
"Primary address (Kontact et al)" => "כתובת ראשית",
"iOS/OS X" => "iOS/OS X",
"Addressbooks" => "פנקסי כתובות",
+"Share" => "שתף",
"New Address Book" => "פנקס כתובות חדש",
+"Name" => "שם",
"Save" => "שמירה"
);
diff --git a/l10n/hr.php b/l10n/hr.php
index d0134dfd..5587718d 100644
--- a/l10n/hr.php
+++ b/l10n/hr.php
@@ -33,6 +33,11 @@
"No file was uploaded" => "Datoteka nije poslana",
"Missing a temporary folder" => "Nedostaje privremeni direktorij",
"Contacts" => "Kontakti",
+"Error" => "Greška",
+"Upload too large" => "Prijenos je preobiman",
+"Unable to upload your file as it is a directory or has 0 bytes" => "Nemoguće poslati datoteku jer je prazna ili je direktorij",
+"Upload Error" => "Pogreška pri slanju",
+"Pending" => "U tijeku",
"Download" => "Preuzimanje",
"Edit" => "Uredi",
"Delete" => "Obriši",
@@ -52,6 +57,7 @@
"GaduGadu" => "GaduGadu",
"Work" => "Posao",
"Home" => "Kuća",
+"Other" => "ostali",
"Mobile" => "Mobitel",
"Text" => "Tekst",
"Voice" => "Glasovno",
@@ -60,11 +66,11 @@
"Video" => "Video",
"Pager" => "Pager",
"Internet" => "Internet",
-"Birthday" => "Rođendan",
"{name}'s Birthday" => "{name} Rođendan",
"Contact" => "Kontakt",
"Add Contact" => "Dodaj kontakt",
"Import" => "Uvezi",
+"Settings" => "Postavke",
"Close" => "Zatvori",
"Drop photo to upload" => "Dovucite fotografiju za slanje",
"Delete current photo" => "Izbriši trenutnu sliku",
@@ -75,6 +81,7 @@
"Nickname" => "Nadimak",
"Enter nickname" => "Unesi nadimank",
"http://www.somesite.com" => "http://www.somesite.com",
+"Birthday" => "Rođendan",
"dd-mm-yyyy" => "dd-mm-yyyy",
"Groups" => "Grupe",
"Separate groups with commas" => "Razdvoji grupe sa zarezom",
@@ -111,6 +118,8 @@
"more info" => "više informacija",
"iOS/OS X" => "iOS/OS X",
"Addressbooks" => "Adresari",
+"Share" => "Podijeli",
"New Address Book" => "Novi adresar",
+"Name" => "Ime",
"Save" => "Spremi"
);
diff --git a/l10n/hu_HU.php b/l10n/hu_HU.php
index 5cc6c036..489d8860 100644
--- a/l10n/hu_HU.php
+++ b/l10n/hu_HU.php
@@ -49,15 +49,24 @@
"Error" => "Hiba",
"Format custom, Short name, Full name, Reverse or Reverse with comma" => "Formátum egyedi, Rövid név, Teljes név, Visszafelé vagy Visszafelé vesszővel",
"Select type" => "Típus kiválasztása",
+"Select photo" => "Fotó kiválasztása",
"This property has to be non-empty." => "Ezt a tulajdonságot muszáj kitölteni",
"Couldn't serialize elements." => "Sorbarakás sikertelen",
"'deleteProperty' called without type argument. Please report at bugs.owncloud.org" => "A 'deleteProperty' argumentum nélkül lett meghívva. Kérjük, jelezze a hibát.",
"Edit name" => "Név szerkesztése",
"No files selected for upload." => "Nincs kiválasztva feltöltendő fájl",
"The file you are trying to upload exceed the maximum size for file uploads on this server." => "A feltöltendő fájl mérete meghaladja a megengedett mértéket",
+"Upload too large" => "A feltöltési méret túl nagy",
+"Only image files can be used as profile picture." => "Csak képfájl használható profilképnek",
+"Wrong file type" => "Rossz fájltípus",
+"Your browser doesn't support AJAX upload. Please click on the profile picture to select a photo to upload." => "Az Ön böngészője nem támogatja az AJAX feltöltést. Kérjük, kattintson a profilképre, hogy kiválaszthassa a feltöltendő képet.",
+"Unable to upload your file as it is a directory or has 0 bytes" => "Nem tölthető fel, mert mappa volt, vagy 0 byte méretű",
+"Upload Error" => "Feltöltési hiba",
+"Pending" => "Folyamatban",
"Result: " => "Eredmény: ",
" imported, " => " beimportálva, ",
" failed." => " sikertelen",
+"Displayname cannot be empty." => "Megjelenített név kitöltendő",
"Download" => "Letöltés",
"Edit" => "Szerkesztés",
"Delete" => "Törlés",
@@ -66,6 +75,7 @@
"Contact could not be found." => "Kapcsolat nem található.",
"Work" => "Munkahelyi",
"Home" => "Otthoni",
+"Other" => "Egyéb",
"Mobile" => "Mobiltelefonszám",
"Text" => "Szöveg",
"Voice" => "Hang",
@@ -74,11 +84,11 @@
"Video" => "Video",
"Pager" => "Személyhívó",
"Internet" => "Internet",
-"Birthday" => "Születésnap",
"{name}'s Birthday" => "{name} születésnapja",
"Contact" => "Kapcsolat",
"Add Contact" => "Kapcsolat hozzáadása",
"Import" => "Import",
+"Settings" => "Beállítások",
"Close" => "Bezár",
"Drop photo to upload" => "Húzza ide a feltöltendő képet",
"Delete current photo" => "Aktuális kép törlése",
@@ -89,6 +99,7 @@
"Organization" => "Szervezet",
"Nickname" => "Becenév",
"Enter nickname" => "Becenév megadása",
+"Birthday" => "Születésnap",
"dd-mm-yyyy" => "yyyy-mm-dd",
"Groups" => "Csoportok",
"Separate groups with commas" => "Vesszővel válassza el a csoportokat",
@@ -150,6 +161,8 @@
"Primary address (Kontact et al)" => "Elsődleges cím",
"iOS/OS X" => "iOS/OS X",
"Addressbooks" => "Címlisták",
+"Share" => "Megosztás",
"New Address Book" => "Új címlista",
+"Name" => "Név",
"Save" => "Mentés"
);
diff --git a/l10n/ia.php b/l10n/ia.php
index 61f84198..be28e035 100644
--- a/l10n/ia.php
+++ b/l10n/ia.php
@@ -4,9 +4,11 @@
"Cannot add empty property." => "Non pote adder proprietate vacue.",
"Error saving temporary file." => "Error durante le scriptura in le file temporari",
"Error loading image." => "Il habeva un error durante le cargamento del imagine.",
+"The uploaded file was only partially uploaded" => "Le file incargate solmente esseva incargate partialmente",
"No file was uploaded" => "Nulle file esseva incargate.",
"Missing a temporary folder" => "Manca un dossier temporari",
"Contacts" => "Contactos",
+"Upload too large" => "Incargamento troppo longe",
"Download" => "Discargar",
"Edit" => "Modificar",
"Delete" => "Deler",
@@ -15,6 +17,7 @@
"Contact could not be found." => "Contacto non poterea esser legite",
"Work" => "Travalio",
"Home" => "Domo",
+"Other" => "Altere",
"Mobile" => "Mobile",
"Text" => "Texto",
"Voice" => "Voce",
@@ -23,10 +26,11 @@
"Video" => "Video",
"Pager" => "Pager",
"Internet" => "Internet",
-"Birthday" => "Anniversario",
"Contact" => "Contacto",
"Add Contact" => "Adder contacto",
"Import" => "Importar",
+"Settings" => "Configurationes",
+"Close" => "Clauder",
"Delete current photo" => "Deler photo currente",
"Edit current photo" => "Modificar photo currente",
"Upload new photo" => "Incargar nove photo",
@@ -34,6 +38,7 @@
"Organization" => "Organisation",
"Nickname" => "Pseudonymo",
"Enter nickname" => "Inserer pseudonymo",
+"Birthday" => "Anniversario",
"Groups" => "Gruppos",
"Edit groups" => "Modificar gruppos",
"Preferred" => "Preferite",
@@ -76,6 +81,8 @@
"more info" => "plus info",
"iOS/OS X" => "iOS/OS X",
"Addressbooks" => "Adressarios",
+"Share" => "Compartir",
"New Address Book" => "Nove adressario",
+"Name" => "Nomine",
"Save" => "Salveguardar"
);
diff --git a/l10n/id.php b/l10n/id.php
index 2885333c..b86cac54 100644
--- a/l10n/id.php
+++ b/l10n/id.php
@@ -1,8 +1,21 @@
"Tidak ada kategori terpilih untuk penghapusan.",
"No contacts found." => "kontak tidak ditemukan",
"Cannot add empty property." => "tidak dapat menambahkan properti kosong",
"At least one of the address fields has to be filled out." => "setidaknya satu dari alamat wajib di isi",
+"File doesn't exist:" => "file tidak ditemukan:",
+"There is no error, the file uploaded with success" => "Tidak ada galat, berkas sukses diunggah",
+"The uploaded file exceeds the upload_max_filesize directive in php.ini" => "File yang diunggah melampaui directive upload_max_filesize di php.ini",
+"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" => "File yang diunggah melampaui directive MAX_FILE_SIZE yang disebutan dalam form HTML.",
+"The uploaded file was only partially uploaded" => "Berkas hanya diunggah sebagian",
+"No file was uploaded" => "Tidak ada berkas yang diunggah",
+"Missing a temporary folder" => "Kehilangan folder temporer",
"Contacts" => "kontak",
+"Error" => "kesalahan",
+"Unable to upload your file as it is a directory or has 0 bytes" => "Gagal mengunggah berkas anda karena berupa direktori atau mempunyai ukuran 0 byte",
+"Upload Error" => "Terjadi Galat Pengunggahan",
+"Pending" => "Menunggu",
+"Importing..." => "mengimpor...",
"Download" => "unduh",
"Edit" => "ubah",
"Delete" => "hapus",
@@ -10,6 +23,7 @@
"Contact could not be found." => "kontak tidak dapat ditemukan",
"Work" => "pekerjaan",
"Home" => "rumah",
+"Other" => "Lainnya",
"Mobile" => "ponsel",
"Text" => "teks",
"Voice" => "suara",
@@ -18,14 +32,17 @@
"Video" => "video",
"Pager" => "pager",
"Internet" => "internet",
-"Birthday" => "tanggal lahir",
"{name}'s Birthday" => "hari ulang tahun {name}",
"Contact" => "kontak",
"Add Contact" => "tambah kontak",
+"Import" => "impor",
+"Settings" => "pengaturan",
+"Close" => "tutup",
"Edit name details" => "ubah detail nama",
"Organization" => "organisasi",
"Nickname" => "nama panggilan",
"Enter nickname" => "masukkan nama panggilan",
+"Birthday" => "tanggal lahir",
"dd-mm-yyyy" => "dd-mm-yyyy",
"Groups" => "grup",
"Separate groups with commas" => "pisahkan grup dengan tanda koma",
@@ -42,6 +59,10 @@
"Zipcode" => "kodepos",
"Country" => "negara",
"Addressbook" => "buku alamat",
+"more info" => "lebih lanjut",
+"iOS/OS X" => "iOS/OS X",
"Addressbooks" => "buku alamat",
+"Share" => "berbagi",
+"Name" => "nama",
"Save" => "simpan"
);
diff --git a/l10n/it.php b/l10n/it.php
index 0c7c10b4..46ece932 100644
--- a/l10n/it.php
+++ b/l10n/it.php
@@ -126,19 +126,8 @@
"Video" => "Video",
"Pager" => "Cercapersone",
"Internet" => "Internet",
-"Birthday" => "Compleanno",
-"Business" => "Lavoro",
-"Call" => "Chiama",
-"Clients" => "Client",
-"Deliverer" => "Corriere",
-"Holidays" => "Festività",
-"Ideas" => "Idee",
-"Journey" => "Viaggio",
-"Jubilee" => "Anniversario",
-"Meeting" => "Riunione",
-"Personal" => "Personale",
-"Projects" => "Progetti",
-"Questions" => "Domande",
+"Friends" => "Amici",
+"Family" => "Famiglia",
"{name}'s Birthday" => "Data di nascita di {name}",
"Contact" => "Contatto",
"You do not have the permissions to add contacts to this addressbook." => "Non hai i permessi per aggiungere contatti a questa rubrica.",
@@ -176,6 +165,7 @@
"Web site" => "Sito web",
"http://www.somesite.com" => "http://www.somesite.com",
"Go to web site" => "Vai al sito web",
+"Birthday" => "Compleanno",
"dd-mm-yyyy" => "gg-mm-aaaa",
"Groups" => "Gruppi",
"Separate groups with commas" => "Separa i gruppi con virgole",
diff --git a/l10n/ja_JP.php b/l10n/ja_JP.php
index 6a8cbbcb..1128e6aa 100644
--- a/l10n/ja_JP.php
+++ b/l10n/ja_JP.php
@@ -75,7 +75,7 @@
"Only image files can be used as profile picture." => "画像ファイルのみがプロファイル写真として使用することができます。",
"Wrong file type" => "誤ったファイルタイプ",
"Your browser doesn't support AJAX upload. Please click on the profile picture to select a photo to upload." => "あなたのブラウザはAJAXのアップロードをサポートしていません。プロファイル写真をクリックしてアップロードする写真を選択してください。",
-"Unable to upload your file as it is a directory or has 0 bytes" => "ディレクトリや0バイトのファイルはアップロードできません。",
+"Unable to upload your file as it is a directory or has 0 bytes" => "ディレクトリもしくは0バイトのファイルはアップロードできません",
"Upload Error" => "アップロードエラー",
"Pending" => "中断",
"Import done" => "インポート完了",
@@ -126,19 +126,8 @@
"Video" => "テレビ電話",
"Pager" => "ポケベル",
"Internet" => "インターネット",
-"Birthday" => "誕生日",
-"Business" => "ビジネス",
-"Call" => "電話",
-"Clients" => "顧客",
-"Deliverer" => "運送会社",
-"Holidays" => "休日",
-"Ideas" => "アイデア",
-"Journey" => "旅行",
-"Jubilee" => "記念祭",
-"Meeting" => "打ち合わせ",
-"Personal" => "個人",
-"Projects" => "プロジェクト",
-"Questions" => "質問",
+"Friends" => "友達",
+"Family" => "家族",
"{name}'s Birthday" => "{name}の誕生日",
"Contact" => "連絡先",
"You do not have the permissions to add contacts to this addressbook." => "アドレスブックに連絡先を追加する権限がありません",
@@ -176,6 +165,7 @@
"Web site" => "ウェブサイト",
"http://www.somesite.com" => "http://www.somesite.com",
"Go to web site" => "Webサイトへ移動",
+"Birthday" => "誕生日",
"dd-mm-yyyy" => "yyyy-mm-dd",
"Groups" => "グループ",
"Separate groups with commas" => "コンマでグループを分割",
diff --git a/l10n/ka_GE.php b/l10n/ka_GE.php
index 9610a3aa..a58aa119 100644
--- a/l10n/ka_GE.php
+++ b/l10n/ka_GE.php
@@ -1,11 +1,24 @@
"სარედაქტირებელი კატეგორია არ არის არჩეული ",
+"There is no error, the file uploaded with success" => "ჭოცდომა არ დაფიქსირდა, ფაილი წარმატებით აიტვირთა",
+"The uploaded file exceeds the upload_max_filesize directive in php.ini" => "ატვირთული ფაილი აჭარბებს upload_max_filesize დირექტივას php.ini ფაილში",
+"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" => "ატვირთული ფაილი აჭარბებს MAX_FILE_SIZE დირექტივას, რომელიც მითითებულია HTML ფორმაში",
+"The uploaded file was only partially uploaded" => "ატვირთული ფაილი მხოლოდ ნაწილობრივ აიტვირთა",
+"No file was uploaded" => "ფაილი არ აიტვირთა",
+"Missing a temporary folder" => "დროებითი საქაღალდე არ არსებობს",
"Contacts" => "კონტაქტები",
+"Error" => "შეცდომა",
+"Upload too large" => "ასატვირთი ფაილი ძალიან დიდია",
+"Unable to upload your file as it is a directory or has 0 bytes" => "თქვენი ფაილის ატვირთვა ვერ მოხერხდა. ის არის საქაღალდე და შეიცავს 0 ბაიტს",
+"Upload Error" => "შეცდომა ატვირთვისას",
+"Pending" => "მოცდის რეჟიმში",
"Download" => "ჩამოტვირთვა",
"Edit" => "რედაქტირება",
"Delete" => "წაშლა",
"Cancel" => "უარყოფა",
"Work" => "სამსახური",
"Home" => "სახლი",
+"Other" => "სხვა",
"Mobile" => "მობილური",
"Text" => "ტექსტი",
"Voice" => "ხმა",
@@ -14,18 +27,18 @@
"Video" => "ვიდეო",
"Pager" => "პეიჯერი",
"Internet" => "ინტერნეტი",
-"Birthday" => "დაბადების დრე",
-"Business" => "ბიზნესი",
-"Clients" => "კლიენტები",
"Contact" => "კონტაქტი",
"Add Contact" => "კონტაქტის დამატება",
"Import" => "იმპორტი",
+"Settings" => "პარამეტრები",
+"Close" => "დახურვა",
"Delete current photo" => "მიმდინარე სურათის წაშლა",
"Edit current photo" => "მიმდინარე სურათის რედაქტირება",
"Upload new photo" => "ახალი სურათის ატვირთვა",
"Select photo from ownCloud" => "აირჩიე სურათი ownCloud –იდან",
"Organization" => "ორგანიზაცია",
"Nickname" => "ნიკნეიმი",
+"Birthday" => "დაბადების დრე",
"dd-mm-yyyy" => "dd-mm-yyyy",
"Groups" => "ჯგუფები",
"Edit groups" => "ჯგუფების რედაქტირება",
@@ -52,7 +65,11 @@
"Sir" => "სერ",
"Add contact" => "კონტაქტის დამატება",
"more info" => "უფრო მეტი ინფორმაცია",
+"Primary address (Kontact et al)" => "პირველადი მისამართი (Kontact et al)",
+"iOS/OS X" => "iOS/OS X",
"Addressbooks" => "მისამართის წიგნები",
+"Share" => "გაზიარება",
"New Address Book" => "ახალი მისამართების წიგნი",
+"Name" => "სახელი",
"Save" => "შენახვა"
);
diff --git a/l10n/ko.php b/l10n/ko.php
index bd4ed0fa..3157ac37 100644
--- a/l10n/ko.php
+++ b/l10n/ko.php
@@ -50,14 +50,19 @@
"Not implemented" => "구현되지 않음",
"Couldn't get a valid address." => "유효한 주소를 얻을 수 없습니다.",
"Error" => "오류",
+"Please enter an email address." => "이메일 주소를 입력해 주세요.",
"Enter name" => "이름을 입력",
"Format custom, Short name, Full name, Reverse or Reverse with comma" => "Format custom, Short name, Full name, Reverse or Reverse with comma",
"Select type" => "유형 선택",
+"Select photo" => "사진 선택",
"You do not have permission to add contacts to " => "당신은 연락처를 추가 할 수 있는 권한이 없습니다. ",
"Please select one of your own address books." => "당신의 Own 주소록 중 하나만 선택 하세요.",
"Permission error" => "권한 에러",
+"Click to undo deletion of \"" => "삭제를 되돌리기 위한 클릭",
+"Cancelled deletion of: \"" => "삭제가 취소되었습니다.",
"This property has to be non-empty." => "이 속성은 비어있어서는 안됩니다.",
"Couldn't serialize elements." => "요소를 직렬화 할 수 없습니다.",
+"Unknown error. Please check logs." => "알수없는 에러. 로그를 확인해주세요.",
"'deleteProperty' called without type argument. Please report at bugs.owncloud.org" => "'deleteProperty'가 문서형식이 없이 불려왔습니다. bugs.owncloud.org에 보고해주세요. ",
"Edit name" => "이름 편집",
"No files selected for upload." => "업로드를 위한 파일이 선택되지 않았습니다. ",
@@ -65,6 +70,19 @@
"Error loading profile picture." => "프로필 사진 로딩 에러",
"Some contacts are marked for deletion, but not deleted yet. Please wait for them to be deleted." => "일부 연락처가 삭제 표시 되었으나 아직 삭제되지 않았습니다. 삭제가 끝날 때 까지 기다려 주세요.",
"Do you want to merge these address books?" => "이 주소록을 통합하고 싶으십니까?",
+"Shared by " => "Shared by",
+"Upload too large" => "업로드 용량 초과",
+"Only image files can be used as profile picture." => "이미지 파일만 프로필 사진으로 사용 될 수 있습니다.",
+"Wrong file type" => "옳지 않은 파일 형식",
+"Your browser doesn't support AJAX upload. Please click on the profile picture to select a photo to upload." => "브라우저가 AJAX 업로드를 지원하지 않습니다. 업로드할 사진을 선택하려면 프로필 사진을 클릭하세요.",
+"Unable to upload your file as it is a directory or has 0 bytes" => "이 파일은 디렉토리이거나 0 바이트이기 때문에 업로드 할 수 없습니다.",
+"Upload Error" => "업로드 에러",
+"Pending" => "보류 중",
+"Import done" => "가져오기 완료",
+"Not all files uploaded. Retrying..." => "모든파일이 업로드되지 않았습니다. 재시도중...",
+"Something went wrong with the upload, please retry." => "업로드 하는데 무언가 잘못되었습니다. 재시도 해주세요.",
+"Importing..." => "가져오기중...",
+"The address book name cannot be empty." => "주소록 이름은 비워둘 수 없습니다.",
"Result: " => "결과:",
" imported, " => "불러오기,",
" failed." => "실패.",
@@ -76,6 +94,12 @@
"Delete" => "삭제",
"Cancel" => "취소",
"More..." => "더...",
+"Less..." => "그외...",
+"You do not have the permissions to read this addressbook." => "당신은 이 주소록을 읽기 위한 권한이 없습니다.",
+"You do not have the permissions to update this addressbook." => "당신은 이 주소록을 업데이트하기 위한 권한이 없습니다.",
+"There was an error updating the addressbook." => "주소록을 업데이트 하는중에 에러가 발생하였습니다.",
+"You do not have the permissions to delete this addressbook." => "당신은 이 주소록을 삭제하기 위한 권한이 없습니다.",
+"There was an error deleting this addressbook." => "이 주소록을 제거하는데 에러가 발생하였습니다.",
"Addressbook not found: " => "주소록을 찾지 못하였습니다:",
"This is not your addressbook." => "내 주소록이 아닙니다.",
"Contact could not be found." => "연락처를 찾을 수 없습니다.",
@@ -102,23 +126,17 @@
"Video" => "영상 번호",
"Pager" => "호출기",
"Internet" => "인터넷",
-"Birthday" => "생일",
-"Business" => "비즈니스",
-"Call" => "전화",
-"Clients" => "고객",
-"Deliverer" => "운송자",
-"Holidays" => "휴가",
-"Ideas" => "아이디어",
-"Journey" => "여행",
-"Jubilee" => "축제",
-"Meeting" => "미팅",
-"Personal" => "개인의",
-"Projects" => "프로젝트",
-"Questions" => "질문",
+"Friends" => "친구들",
+"Family" => "가족",
"{name}'s Birthday" => "{이름}의 생일",
"Contact" => "연락처",
+"You do not have the permissions to add contacts to this addressbook." => "당신은 이 주소록에 연락처를 추가하기 위한 권한이 없습니다.",
+"Could not find the vCard with ID." => "ID와 vCard를 찾을수 없습니다.",
"You do not have the permissions to edit this contact." => "당신은 연락처를 수정할 권한이 없습니다. ",
+"Could not find the vCard with ID: " => "ID와 vCard를 찾을수 없습니다.",
+"Could not find the Addressbook with ID: " => "ID와 주소록을 찾을 수 없습니다.",
"You do not have the permissions to delete this contact." => "당신은 연락처를 삭제할 권한이 없습니다. ",
+"There was an error deleting this contact." => "이 연락처를 삭제하는데 에러가 발생하였습니다.",
"Add Contact" => "연락처 추가",
"Import" => "입력",
"Settings" => "설정",
@@ -147,6 +165,7 @@
"Web site" => "웹 사이트",
"http://www.somesite.com" => "http://www.somesite.com",
"Go to web site" => "웹 사이트로 가기",
+"Birthday" => "생일",
"dd-mm-yyyy" => "일-월-년",
"Groups" => "그룹",
"Separate groups with commas" => "쉼표로 그룹 구분",
@@ -210,6 +229,7 @@
"create a new addressbook" => "새 주소록 만들기",
"Name of new addressbook" => "새 주소록 이름",
"Importing contacts" => "연락처 입력",
+"
You have no contacts in your addressbook.
You can import VCF files by dragging them to the contacts list and either drop them on an addressbook to import into it, or on an empty spot to create a new addressbook and import into that. You can also import by clicking on the import button at the bottom of the list.
" => "
당신의 주소록에 연락처가 없습니다.
연락처 리스트에 드래그앤 드롭 함으로써 주소록에 VCF 파일을 가져올 수 있습니다, or on an empty spot to create a new addressbook and import into that. 당신은 리스트 상단에 위치한 가져오기 버튼을 누름으로써 가져올수 있습니다.
",
"Add contact" => "연락처 추가",
"Select Address Books" => "주소록 선택",
"Enter description" => "설명을 입력",
diff --git a/l10n/ku_IQ.php b/l10n/ku_IQ.php
new file mode 100644
index 00000000..4a480879
--- /dev/null
+++ b/l10n/ku_IQ.php
@@ -0,0 +1,13 @@
+ "پهڕگهکه ههبوون نیه:",
+"Error" => "ههڵه",
+"Importing..." => "دههێنرێت...",
+"Download" => "داگرتن",
+"Import" => "هێنان",
+"Settings" => "دهستكاری",
+"Close" => "داخستن",
+"Email" => "ئیمهیل",
+"Address" => "ناونیشان",
+"Name" => "ناو",
+"Save" => "پاشکهوتکردن"
+);
diff --git a/l10n/lb.php b/l10n/lb.php
index a070edd2..496658c0 100644
--- a/l10n/lb.php
+++ b/l10n/lb.php
@@ -17,9 +17,16 @@
"Contact ID is missing." => "Kontakt ID fehlt.",
"File doesn't exist:" => "Fichier existéiert net:",
"Error loading image." => "Fehler beim lueden vum Bild.",
+"There is no error, the file uploaded with success" => "Keen Feeler, Datei ass komplett ropgelueden ginn",
+"The uploaded file exceeds the upload_max_filesize directive in php.ini" => "Déi ropgelueden Datei ass méi grouss wei d'upload_max_filesize Eegenschaft an der php.ini",
+"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" => "Déi ropgelueden Datei ass méi grouss wei d'MAX_FILE_SIZE Eegenschaft déi an der HTML form uginn ass",
+"The uploaded file was only partially uploaded" => "Déi ropgelueden Datei ass nëmmen hallef ropgelueden ginn",
"No file was uploaded" => "Et ass kee Fichier ropgeluede ginn",
+"Missing a temporary folder" => "Et feelt en temporären Dossier",
"Contacts" => "Kontakter",
"Error" => "Fehler",
+"Unable to upload your file as it is a directory or has 0 bytes" => "Kann deng Datei net eroplueden well et en Dossier ass oder 0 byte grouss ass.",
+"Upload Error" => "Fehler beim eroplueden",
"Result: " => "Resultat: ",
" imported, " => " importéiert, ",
"Download" => "Download",
@@ -30,6 +37,7 @@
"Contact could not be found." => "Konnt den Kontakt net fannen.",
"Work" => "Aarbecht",
"Home" => "Doheem",
+"Other" => "Aner",
"Mobile" => "GSM",
"Text" => "SMS",
"Voice" => "Voice",
@@ -38,14 +46,16 @@
"Video" => "Video",
"Pager" => "Pager",
"Internet" => "Internet",
-"Birthday" => "Gebuertsdag",
"{name}'s Birthday" => "{name} säi Gebuertsdag",
"Contact" => "Kontakt",
"Add Contact" => "Kontakt bäisetzen",
+"Import" => "Import",
+"Settings" => "Astellungen",
"Close" => "Zoumaachen",
"Organization" => "Firma",
"Nickname" => "Spëtznumm",
"Enter nickname" => "Gëff e Spëtznumm an",
+"Birthday" => "Gebuertsdag",
"dd-mm-yyyy" => "dd-mm-yyyy",
"Groups" => "Gruppen",
"Edit groups" => "Gruppen editéieren",
@@ -79,6 +89,8 @@
"Sn." => "Sn.",
"iOS/OS X" => "iOS/OS X",
"Addressbooks" => "Adressbicher ",
+"Share" => "Deelen",
"New Address Book" => "Neit Adressbuch",
+"Name" => "Numm",
"Save" => "Späicheren"
);
diff --git a/l10n/lt_LT.php b/l10n/lt_LT.php
index 21d367a4..e740df9a 100644
--- a/l10n/lt_LT.php
+++ b/l10n/lt_LT.php
@@ -1,5 +1,6 @@
"Klaida (de)aktyvuojant adresų knygą.",
+"No categories selected for deletion." => "Trynimui nepasirinkta jokia kategorija.",
"No contacts found." => "Kontaktų nerasta.",
"Information about vCard is incorrect. Please reload the page." => "Informacija apie vCard yra neteisinga. ",
"Error reading contact photo." => "Klaida skaitant kontakto nuotrauką.",
@@ -11,7 +12,14 @@
"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" => "Įkeliamo failo dydis viršija MAX_FILE_SIZE nustatymą, kuris naudojamas HTML formoje.",
"The uploaded file was only partially uploaded" => "Failas buvo įkeltas tik dalinai",
"No file was uploaded" => "Nebuvo įkeltas joks failas",
+"Missing a temporary folder" => "Nėra laikinojo katalogo",
"Contacts" => "Kontaktai",
+"Error" => "Klaida",
+"Upload too large" => "Įkėlimui failas per didelis",
+"Unable to upload your file as it is a directory or has 0 bytes" => "Neįmanoma įkelti failo - jo dydis gali būti 0 bitų arba tai katalogas",
+"Upload Error" => "Įkėlimo klaida",
+"Pending" => "Laukiantis",
+"Importing..." => "Importuojama...",
"Download" => "Atsisiųsti",
"Edit" => "Keisti",
"Delete" => "Trinti",
@@ -20,6 +28,7 @@
"Contact could not be found." => "Kontaktas nerastas",
"Work" => "Darbo",
"Home" => "Namų",
+"Other" => "Kita",
"Mobile" => "Mobilusis",
"Text" => "Žinučių",
"Voice" => "Balso",
@@ -28,12 +37,16 @@
"Video" => "Vaizdo",
"Pager" => "Pranešimų gaviklis",
"Internet" => "Internetas",
-"Birthday" => "Gimtadienis",
"Contact" => "Kontaktas",
"Add Contact" => "Pridėti kontaktą",
+"Import" => "Importuoti",
+"Settings" => "Nustatymai",
+"Close" => "Užverti",
"Organization" => "Organizacija",
"Nickname" => "Slapyvardis",
"Enter nickname" => "Įveskite slapyvardį",
+"Birthday" => "Gimtadienis",
+"Groups" => "Grupės",
"Phone" => "Telefonas",
"Email" => "El. paštas",
"Address" => "Adresas",
@@ -47,6 +60,8 @@
"Country" => "Šalis",
"Addressbook" => "Adresų knyga",
"Addressbooks" => "Adresų knygos",
+"Share" => "Dalintis",
"New Address Book" => "Nauja adresų knyga",
+"Name" => "Pavadinimas",
"Save" => "Išsaugoti"
);
diff --git a/l10n/lv.php b/l10n/lv.php
new file mode 100644
index 00000000..45c56cae
--- /dev/null
+++ b/l10n/lv.php
@@ -0,0 +1,16 @@
+ "Neviens fails netika augšuplādēts",
+"Error" => "Kļūme",
+"Upload too large" => "Fails ir par lielu lai to augšuplādetu",
+"Unable to upload your file as it is a directory or has 0 bytes" => "Nav iespējams augšuplādēt jūsu failu, jo tāds jau eksistē vai arī failam nav izmēra (0 baiti)",
+"Upload Error" => "Augšuplādēšanas laikā radās kļūda",
+"Pending" => "Gaida savu kārtu",
+"Download" => "Lejuplādēt",
+"Delete" => "Izdzēst",
+"Other" => "Cits",
+"Settings" => "Iestatījumi",
+"Groups" => "Grupas",
+"Email" => "Epasts",
+"Share" => "Līdzdalīt",
+"Name" => "Nosaukums"
+);
diff --git a/l10n/mk.php b/l10n/mk.php
index 07e85e41..9c8b588c 100644
--- a/l10n/mk.php
+++ b/l10n/mk.php
@@ -49,15 +49,24 @@
"Error" => "Грешка",
"Format custom, Short name, Full name, Reverse or Reverse with comma" => "Прилагоден формат, кратко име, цело име, обратно или обратно со запирка",
"Select type" => "Одбери тип",
+"Select photo" => "Одбери фотографија",
"This property has to be non-empty." => "Својството не смее да биде празно.",
"Couldn't serialize elements." => "Не може да се серијализираат елементите.",
"'deleteProperty' called without type argument. Please report at bugs.owncloud.org" => "'deleteProperty' повикан без тип на аргументот. Пријавете грешка/проблем на bugs.owncloud.org",
"Edit name" => "Уреди го името",
"No files selected for upload." => "Ниту еден фајл не е избран за вчитување.",
"The file you are trying to upload exceed the maximum size for file uploads on this server." => "Датотеката која се обидувате да ја префрлите ја надминува максималната големина дефинирана за пренос на овој сервер.",
+"Upload too large" => "Фајлот кој се вчитува е преголем",
+"Only image files can be used as profile picture." => "Само слики можат да бидат искористени како фотографија за профилот.",
+"Wrong file type" => "Погрешен тип на фајл",
+"Your browser doesn't support AJAX upload. Please click on the profile picture to select a photo to upload." => "Вашиот прелистувач не подржува AJAX преземања. Ве молиме кликнете на сликата за профилот да ја изберете фотографијата за преземање.",
+"Unable to upload your file as it is a directory or has 0 bytes" => "Не може да се преземе вашата датотека бидејќи фолдерот во кој се наоѓа фајлот има големина од 0 бајти",
+"Upload Error" => "Грешка при преземање",
+"Pending" => "Чека",
"Result: " => "Резултат: ",
" imported, " => "увезено,",
" failed." => "неуспешно.",
+"Displayname cannot be empty." => "Прикажаното име не може да биде празно.",
"Download" => "Преземи",
"Edit" => "Уреди",
"Delete" => "Избриши",
@@ -66,6 +75,7 @@
"Contact could not be found." => "Контактот неможе да биде најден.",
"Work" => "Работа",
"Home" => "Дома",
+"Other" => "Останато",
"Mobile" => "Мобилен",
"Text" => "Текст",
"Voice" => "Глас",
@@ -74,11 +84,11 @@
"Video" => "Видео",
"Pager" => "Пејџер",
"Internet" => "Интернет",
-"Birthday" => "Роденден",
"{name}'s Birthday" => "Роденден на {name}",
"Contact" => "Контакт",
"Add Contact" => "Додади контакт",
"Import" => "Внеси",
+"Settings" => "Параметри",
"Close" => "Затвои",
"Drop photo to upload" => "Довлечкај фотографија за да се подигне",
"Delete current photo" => "Избриши моментална фотографија",
@@ -89,6 +99,7 @@
"Organization" => "Организација",
"Nickname" => "Прекар",
"Enter nickname" => "Внеси прекар",
+"Birthday" => "Роденден",
"dd-mm-yyyy" => "dd-mm-yyyy",
"Groups" => "Групи",
"Separate groups with commas" => "Одвоете ги групите со запирка",
@@ -150,6 +161,8 @@
"Primary address (Kontact et al)" => "Примарна адреса",
"iOS/OS X" => "iOS/OS X",
"Addressbooks" => "Адресари",
+"Share" => "Сподели",
"New Address Book" => "Нов адресар",
+"Name" => "Име",
"Save" => "Сними"
);
diff --git a/l10n/ms_MY.php b/l10n/ms_MY.php
index 28e7f0ee..351c54ca 100644
--- a/l10n/ms_MY.php
+++ b/l10n/ms_MY.php
@@ -50,15 +50,24 @@
"Enter name" => "Masukkan nama",
"Format custom, Short name, Full name, Reverse or Reverse with comma" => "Format bebas, Nama pendek, Nama penuh, Unduran dengan koma",
"Select type" => "PIlih jenis",
+"Select photo" => "Pilih foto",
"This property has to be non-empty." => "Nilai ini tidak boleh kosong.",
"Couldn't serialize elements." => "Tidak boleh menggabungkan elemen.",
"'deleteProperty' called without type argument. Please report at bugs.owncloud.org" => "'deleteProperty' dipanggil tanpa argumen taip. Sila maklumkan di bugs.owncloud.org",
"Edit name" => "Ubah nama",
"No files selected for upload." => "Tiada fail dipilih untuk muatnaik.",
"The file you are trying to upload exceed the maximum size for file uploads on this server." => "Fail yang ingin dimuatnaik melebihi saiz yang dibenarkan.",
+"Upload too large" => "Muatnaik terlalu besar",
+"Only image files can be used as profile picture." => "Hanya fail imej boleh digunakan sebagai gambar profil.",
+"Wrong file type" => "Salah jenis fail",
+"Your browser doesn't support AJAX upload. Please click on the profile picture to select a photo to upload." => "Pelayar web anda tidak menyokong muatnaik AJAX. Sila klik pada gambar profil untuk memilih foto untuk dimuatnail.",
+"Unable to upload your file as it is a directory or has 0 bytes" => "Tidak boleh memuatnaik fail anda kerana mungkin ianya direktori atau saiz fail 0 bytes",
+"Upload Error" => "Muat naik ralat",
+"Pending" => "Dalam proses",
"Result: " => "Hasil: ",
" imported, " => " import, ",
" failed." => " gagal.",
+"Displayname cannot be empty." => "Nama paparan tidak boleh kosong",
"Download" => "Muat naik",
"Edit" => "Sunting",
"Delete" => "Padam",
@@ -78,16 +87,6 @@
"Video" => "Video",
"Pager" => "Alat Kelui",
"Internet" => "Internet",
-"Birthday" => "Hari lahir",
-"Business" => "Perniagaan",
-"Clients" => "klien",
-"Holidays" => "Hari kelepasan",
-"Ideas" => "Idea",
-"Journey" => "Perjalanan",
-"Jubilee" => "Jubli",
-"Meeting" => "Mesyuarat",
-"Personal" => "Peribadi",
-"Projects" => "Projek",
"{name}'s Birthday" => "Hari Lahir {name}",
"Contact" => "Hubungan",
"Add Contact" => "Tambah kenalan",
@@ -105,6 +104,7 @@
"Organization" => "Organisasi",
"Nickname" => "Nama Samaran",
"Enter nickname" => "Masukkan nama samaran",
+"Birthday" => "Hari lahir",
"dd-mm-yyyy" => "dd-mm-yyyy",
"Groups" => "Kumpulan",
"Separate groups with commas" => "Asingkan kumpulan dengan koma",
@@ -168,6 +168,7 @@
"Primary address (Kontact et al)" => "Alamat utama",
"iOS/OS X" => "iOS/OS X",
"Addressbooks" => "Senarai Buku Alamat",
+"Share" => "Kongsi",
"New Address Book" => "Buku Alamat Baru",
"Name" => "Nama",
"Description" => "Keterangan",
diff --git a/l10n/nb_NO.php b/l10n/nb_NO.php
index 63577aec..92e6b3e3 100644
--- a/l10n/nb_NO.php
+++ b/l10n/nb_NO.php
@@ -56,8 +56,13 @@
"No files selected for upload." => "Ingen filer valgt for opplasting.",
"The file you are trying to upload exceed the maximum size for file uploads on this server." => "Filen du prøver å laste opp er for stor.",
"Shared by " => "Delt av",
+"Upload too large" => "Filen er for stor",
"Only image files can be used as profile picture." => "Kun bildefiler kan bli brukt som profilbilde",
"Wrong file type" => "Feil filtype",
+"Your browser doesn't support AJAX upload. Please click on the profile picture to select a photo to upload." => "Din nettleser støtter ikke opplasting med AJAX. Klikk på ønsket bilde for å laste opp.",
+"Unable to upload your file as it is a directory or has 0 bytes" => "Kan ikke laste opp filen din siden det er en mappe eller den har 0 bytes",
+"Upload Error" => "Opplasting feilet",
+"Pending" => "Ventende",
"Importing..." => "Importerer...",
"Result: " => "Resultat:",
" imported, " => "importert,",
@@ -84,16 +89,6 @@
"Video" => "Video",
"Pager" => "Pager",
"Internet" => "Internett",
-"Birthday" => "Bursdag",
-"Call" => "Ring",
-"Clients" => "Klienter",
-"Holidays" => "Helligdager",
-"Ideas" => "Idè",
-"Journey" => "Reise",
-"Meeting" => "Møte",
-"Personal" => "Personlig",
-"Projects" => "Prosjekt",
-"Questions" => "Spørsmål",
"{name}'s Birthday" => "{name}s bursdag",
"Contact" => "Kontakt",
"Could not find the vCard with ID." => "Kunne ikke finne vCard med denne IDen",
@@ -102,6 +97,7 @@
"There was an error deleting this contact." => "Det oppstod en feil ved sletting av denne kontakten",
"Add Contact" => "Ny kontakt",
"Import" => "Importer",
+"Settings" => "Innstillinger",
"Close" => "Lukk",
"Keyboard shortcuts" => "Tastatur snarveier",
"Navigation" => "Navigasjon",
@@ -122,6 +118,7 @@
"Enter nickname" => "Skriv inn kallenavn",
"Web site" => "Hjemmeside",
"http://www.somesite.com" => "http://www.domene.no",
+"Birthday" => "Bursdag",
"dd-mm-yyyy" => "dd-mm-åååå",
"Groups" => "Grupper",
"Separate groups with commas" => "Skill gruppene med komma",
@@ -176,6 +173,8 @@
"Primary address (Kontact et al)" => "Primær adresse (kontakt osv)",
"iOS/OS X" => "iOS/OS X",
"Addressbooks" => "Adressebøker",
+"Share" => "Del",
"New Address Book" => "Ny adressebok",
+"Name" => "Navn",
"Save" => "Lagre"
);
diff --git a/l10n/nl.php b/l10n/nl.php
index 4b9731db..7b4a9225 100644
--- a/l10n/nl.php
+++ b/l10n/nl.php
@@ -126,19 +126,8 @@
"Video" => "Video",
"Pager" => "Pieper",
"Internet" => "Internet",
-"Birthday" => "Verjaardag",
-"Business" => "Business",
-"Call" => "Bel",
-"Clients" => "Klanten",
-"Deliverer" => "Leverancier",
-"Holidays" => "Vakanties",
-"Ideas" => "Ideeën",
-"Journey" => "Reis",
-"Jubilee" => "Jubileum",
-"Meeting" => "Vergadering",
-"Personal" => "Persoonlijk",
-"Projects" => "Projecten",
-"Questions" => "Vragen",
+"Friends" => "Vrienden",
+"Family" => "Familie",
"{name}'s Birthday" => "{name}'s verjaardag",
"Contact" => "Contact",
"You do not have the permissions to add contacts to this addressbook." => "U kunt geen contact personen toevoegen aan dit adresboek.",
@@ -176,6 +165,7 @@
"Web site" => "Website",
"http://www.somesite.com" => "http://www.willekeurigesite.com",
"Go to web site" => "Ga naar website",
+"Birthday" => "Verjaardag",
"dd-mm-yyyy" => "dd-mm-yyyy",
"Groups" => "Groepen",
"Separate groups with commas" => "Gebruik komma bij meerder groepen",
diff --git a/l10n/nn_NO.php b/l10n/nn_NO.php
index 39969b79..79d13fbc 100644
--- a/l10n/nn_NO.php
+++ b/l10n/nn_NO.php
@@ -3,7 +3,15 @@
"Cannot add empty property." => "Kan ikkje leggja til tomt felt.",
"At least one of the address fields has to be filled out." => "Minst eit av adressefelta må fyllast ut.",
"Information about vCard is incorrect. Please reload the page." => "Informasjonen om vCard-et er feil, ver venleg og last sida på nytt.",
+"There is no error, the file uploaded with success" => "Ingen feil, fila vart lasta opp",
+"The uploaded file exceeds the upload_max_filesize directive in php.ini" => "Den opplasta fila er større enn variabelen upload_max_filesize i php.ini",
+"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" => "Den opplasta fila er større enn variabelen MAX_FILE_SIZE i HTML-skjemaet",
+"The uploaded file was only partially uploaded" => "Fila vart berre delvis lasta opp",
+"No file was uploaded" => "Ingen filer vart lasta opp",
+"Missing a temporary folder" => "Manglar ei mellombels mappe",
"Contacts" => "Kotaktar",
+"Error" => "Feil",
+"Upload too large" => "For stor opplasting",
"Download" => "Last ned",
"Edit" => "Endra",
"Delete" => "Slett",
@@ -12,16 +20,21 @@
"Contact could not be found." => "Fann ikkje kontakten.",
"Work" => "Arbeid",
"Home" => "Heime",
+"Other" => "Anna",
"Mobile" => "Mobil",
"Text" => "Tekst",
"Voice" => "Tale",
"Fax" => "Faks",
"Video" => "Video",
"Pager" => "Personsøkjar",
-"Birthday" => "Bursdag",
"Contact" => "Kontakt",
"Add Contact" => "Legg til kontakt",
+"Import" => "Importer",
+"Settings" => "Innstillingar",
+"Close" => "Lukk",
"Organization" => "Organisasjon",
+"Birthday" => "Bursdag",
+"Groups" => "Grupper",
"Preferred" => "Føretrekt",
"Phone" => "Telefonnummer",
"Email" => "Epost",
@@ -38,5 +51,6 @@
"Addressbook" => "Adressebok",
"Addressbooks" => "Adressebøker",
"New Address Book" => "Ny adressebok",
+"Name" => "Namn",
"Save" => "Lagre"
);
diff --git a/l10n/oc.php b/l10n/oc.php
new file mode 100644
index 00000000..3be03b1a
--- /dev/null
+++ b/l10n/oc.php
@@ -0,0 +1,32 @@
+ "Pas de categorias seleccionadas per escafar.",
+"There is no error, the file uploaded with success" => "Amontcargament capitat, pas d'errors",
+"The uploaded file exceeds the upload_max_filesize directive in php.ini" => "Lo fichièr amontcargat es tròp bèl per la directiva «upload_max_filesize » del php.ini",
+"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" => "Lo fichièr amontcargat es mai gròs que la directiva «MAX_FILE_SIZE» especifiada dins lo formulari HTML",
+"The uploaded file was only partially uploaded" => "Lo fichièr foguèt pas completament amontcargat",
+"No file was uploaded" => "Cap de fichièrs son estats amontcargats",
+"Missing a temporary folder" => "Un dorsièr temporari manca",
+"Contacts" => "Contactes",
+"Error" => "Error",
+"Upload too large" => "Amontcargament tròp gròs",
+"Unable to upload your file as it is a directory or has 0 bytes" => "Impossible d'amontcargar lo teu fichièr qu'es un repertòri o que ten pas que 0 octet.",
+"Upload Error" => "Error d'amontcargar",
+"Pending" => "Al esperar",
+"Download" => "Avalcarga",
+"Edit" => "Editar",
+"Delete" => "Escafa",
+"Cancel" => "Annula",
+"Work" => "Trabalh",
+"Other" => "Autres",
+"Import" => "Importa",
+"Settings" => "Configuracion",
+"Birthday" => "Anniversari",
+"Groups" => "Grops",
+"Email" => "Corrièl",
+"more info" => "mai d'entresenhes",
+"Primary address (Kontact et al)" => "Adreiças primarias (Kontact et al)",
+"iOS/OS X" => "iOS/OS X",
+"Share" => "Parteja",
+"Name" => "Nom",
+"Save" => "Enregistra"
+);
diff --git a/l10n/pl.php b/l10n/pl.php
index 2941304c..5e59c7a7 100644
--- a/l10n/pl.php
+++ b/l10n/pl.php
@@ -126,19 +126,8 @@
"Video" => "Połączenie wideo",
"Pager" => "Pager",
"Internet" => "Internet",
-"Birthday" => "Urodziny",
-"Business" => "Biznesowe",
-"Call" => "Wywołanie",
-"Clients" => "Klienci",
-"Deliverer" => "Doręczanie",
-"Holidays" => "Święta",
-"Ideas" => "Pomysły",
-"Journey" => "Podróż",
-"Jubilee" => "Jubileusz",
-"Meeting" => "Spotkanie",
-"Personal" => "Osobiste",
-"Projects" => "Projekty",
-"Questions" => "Pytania",
+"Friends" => "Przyjaciele",
+"Family" => "Rodzina",
"{name}'s Birthday" => "{name} Urodzony",
"Contact" => "Kontakt",
"You do not have the permissions to add contacts to this addressbook." => "Nie masz uprawnień do dodawania kontaktów do tej książki adresowej.",
@@ -176,6 +165,7 @@
"Web site" => "Strona www",
"http://www.somesite.com" => "http://www.jakasstrona.pl",
"Go to web site" => "Idż do strony www",
+"Birthday" => "Urodziny",
"dd-mm-yyyy" => "dd-mm-rrrr",
"Groups" => "Grupy",
"Separate groups with commas" => "Oddziel grupy przecinkami",
diff --git a/l10n/pl_PL.php b/l10n/pl_PL.php
index c09a5078..57f11b18 100644
--- a/l10n/pl_PL.php
+++ b/l10n/pl_PL.php
@@ -1,3 +1,6 @@
You have no contacts in your addressbook.
You can import VCF files by dragging them to the contacts list and either drop them on an addressbook to import into it, or on an empty spot to create a new addressbook and import into that. You can also import by clicking on the import button at the bottom of the list.
" => "
Nie masz kontaktów w książce adresowej.
Możesz zaimportować pliki VCF poprzez przeciągnięcie ich do listy kontaktów i albo upuścić je na książce adresowej w celu zaimportowanie ich do niej lub na pustym miejscu, aby utworzyć nowych nową książke adresową i zaimportować je do niej. Możesz również także zaimportować, klikając przycisk Importuj na dole listy.
You can import VCF files by dragging them to the contacts list and either drop them on an addressbook to import into it, or on an empty spot to create a new addressbook and import into that. You can also import by clicking on the import button at the bottom of the list.
" => "
Nie masz kontaktów w książce adresowej.
Możesz zaimportować pliki VCF poprzez przeciągnięcie ich do listy kontaktów i albo upuścić je na książce adresowej w celu zaimportowanie ich do niej lub na pustym miejscu, aby utworzyć nowych nową książke adresową i zaimportować je do niej. Możesz również także zaimportować, klikając przycisk Importuj na dole listy.
",
+"Save" => "Zapisz"
);
diff --git a/l10n/pt_BR.php b/l10n/pt_BR.php
index c514ab47..b44f57b8 100644
--- a/l10n/pt_BR.php
+++ b/l10n/pt_BR.php
@@ -126,19 +126,6 @@
"Video" => "Vídeo",
"Pager" => "Pager",
"Internet" => "Internet",
-"Birthday" => "Aniversário",
-"Business" => "Trabalho",
-"Call" => "Chamar",
-"Clients" => "Clientes",
-"Deliverer" => "Entrega",
-"Holidays" => "Feriados",
-"Ideas" => "Idéias",
-"Journey" => "Jornada",
-"Jubilee" => "Jubileu",
-"Meeting" => "Reunião",
-"Personal" => "Pessoal",
-"Projects" => "Projetos",
-"Questions" => "Perguntas",
"{name}'s Birthday" => "Aniversário de {name}",
"Contact" => "Contato",
"You do not have the permissions to add contacts to this addressbook." => "Você não tem permissões para adicionar contatos a essa agenda.",
@@ -176,6 +163,7 @@
"Web site" => "Web site",
"http://www.somesite.com" => "http://www.qualquersite.com",
"Go to web site" => "Ir para web site",
+"Birthday" => "Aniversário",
"dd-mm-yyyy" => "dd-mm-aaaa",
"Groups" => "Grupos",
"Separate groups with commas" => "Separe grupos por virgula",
diff --git a/l10n/pt_PT.php b/l10n/pt_PT.php
index 90d6c933..9f3f96b8 100644
--- a/l10n/pt_PT.php
+++ b/l10n/pt_PT.php
@@ -126,19 +126,8 @@
"Video" => "Vídeo",
"Pager" => "Pager",
"Internet" => "Internet",
-"Birthday" => "Aniversário",
-"Business" => "Empresa",
-"Call" => "Telefonar",
-"Clients" => "Clientes",
-"Deliverer" => "Fornecedor",
-"Holidays" => "Férias",
-"Ideas" => "Ideias",
-"Journey" => "Viagem",
-"Jubilee" => "Jubileu",
-"Meeting" => "Encontro",
-"Personal" => "Pessoal",
-"Projects" => "Projectos",
-"Questions" => "Questões",
+"Friends" => "Amigos",
+"Family" => "Familia",
"{name}'s Birthday" => "Aniversário de {name}",
"Contact" => "Contacto",
"You do not have the permissions to add contacts to this addressbook." => "Não tem permissões para acrescentar contactos a este livro de endereços.",
@@ -176,6 +165,7 @@
"Web site" => "Página web",
"http://www.somesite.com" => "http://www.somesite.com",
"Go to web site" => "Ir para página web",
+"Birthday" => "Aniversário",
"dd-mm-yyyy" => "dd-mm-aaaa",
"Groups" => "Grupos",
"Separate groups with commas" => "Separe os grupos usando virgulas",
diff --git a/l10n/ro.php b/l10n/ro.php
index db7cad7d..5751e830 100644
--- a/l10n/ro.php
+++ b/l10n/ro.php
@@ -25,6 +25,8 @@
"Error loading image." => "Eroare la încărcarea imaginii.",
"Error uploading contacts to storage." => "Eroare la încărcarea contactelor.",
"There is no error, the file uploaded with success" => "Nu a apărut nici o eroare, fișierul a fost încărcat cu succes",
+"The uploaded file exceeds the upload_max_filesize directive in php.ini" => "Fișierul are o dimensiune mai mare decât cea specificată în variabila upload_max_filesize din php.ini",
+"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" => "Fișierul are o dimensiune mai mare decât variabile MAX_FILE_SIZE specificată în formularul HTML",
"The uploaded file was only partially uploaded" => "Fișierul a fost încărcat doar parțial",
"No file was uploaded" => "Nu a fost încărcat nici un fișier",
"Missing a temporary folder" => "Lipsește un director temporar",
@@ -34,6 +36,11 @@
"Enter name" => "Specifică nume",
"Select type" => "Selectează tip",
"Edit name" => "Editează nume",
+"Upload too large" => "Fișierul încărcat este prea mare",
+"Unable to upload your file as it is a directory or has 0 bytes" => "Nu s-a putut încărca fișierul tău deoarece pare să fie un director sau are 0 bytes.",
+"Upload Error" => "Eroare la încărcare",
+"Pending" => "În așteptare",
+"Importing..." => "Se importă...",
"Result: " => "Rezultat:",
" imported, " => "importat,",
"Show CardDav link" => "Arată legătură CardDav",
@@ -55,15 +62,6 @@
"Video" => "Video",
"Pager" => "Pager",
"Internet" => "Internet",
-"Birthday" => "Zi de naștere",
-"Business" => "Companie",
-"Call" => "Sună",
-"Clients" => "Clienți",
-"Ideas" => "Idei",
-"Meeting" => "Întâlnire",
-"Personal" => "Personal",
-"Projects" => "Proiecte",
-"Questions" => "Întrebări",
"{name}'s Birthday" => "Ziua de naștere a {name}",
"Contact" => "Contact",
"Add Contact" => "Adaugă contact",
@@ -87,6 +85,7 @@
"Enter nickname" => "Introdu pseudonim",
"Web site" => "Site web",
"Go to web site" => "Vizitează site-ul",
+"Birthday" => "Zi de naștere",
"dd-mm-yyyy" => "zz-ll-aaaa",
"Groups" => "Grupuri",
"Separate groups with commas" => "Separă grupurile cu virgule",
@@ -131,8 +130,11 @@
"Importing contacts" => "Se importă contactele",
"Add contact" => "Adaugă contact",
"Enter description" => "Specifică descriere",
+"more info" => "mai multe informații",
+"Primary address (Kontact et al)" => "Adresa primară (Kontact et al)",
"iOS/OS X" => "iOS/OS X",
"Addressbooks" => "Agende",
+"Share" => "Partajează",
"New Address Book" => "Agendă nouă",
"Name" => "Nume",
"Description" => "Descriere",
diff --git a/l10n/ru.php b/l10n/ru.php
index c9f9c20a..27af3662 100644
--- a/l10n/ru.php
+++ b/l10n/ru.php
@@ -126,19 +126,8 @@
"Video" => "Видео",
"Pager" => "Пейджер",
"Internet" => "Интернет",
-"Birthday" => "День рождения",
-"Business" => "Бизнес",
-"Call" => "Вызов",
-"Clients" => "Клиенты",
-"Deliverer" => "Посыльный",
-"Holidays" => "Праздники",
-"Ideas" => "Идеи",
-"Journey" => "Поездка",
-"Jubilee" => "Юбилей",
-"Meeting" => "Встреча",
-"Personal" => "Личный",
-"Projects" => "Проекты",
-"Questions" => "Вопросы",
+"Friends" => "Друзья",
+"Family" => "Семья",
"{name}'s Birthday" => "День рождения {name}",
"Contact" => "Контакт",
"You do not have the permissions to add contacts to this addressbook." => "У вас нет права создавать контакты в этой адресной книге.",
@@ -176,6 +165,7 @@
"Web site" => "Веб-сайт",
"http://www.somesite.com" => "http://www.somesite.com",
"Go to web site" => "Перейти на веб-сайт",
+"Birthday" => "День рождения",
"dd-mm-yyyy" => "dd-mm-yyyy",
"Groups" => "Группы",
"Separate groups with commas" => "Разделить группы запятыми",
diff --git a/l10n/ru_RU.php b/l10n/ru_RU.php
index b493a23d..ef4237f8 100644
--- a/l10n/ru_RU.php
+++ b/l10n/ru_RU.php
@@ -126,19 +126,8 @@
"Video" => "Видео",
"Pager" => "Пейджер",
"Internet" => "Интернет",
-"Birthday" => "День рождения",
-"Business" => "Бизнес",
-"Call" => "Вызов",
-"Clients" => "Клиенты",
-"Deliverer" => "Поставщик",
-"Holidays" => "Праздники",
-"Ideas" => "Идеи",
-"Journey" => "Путешествие",
-"Jubilee" => "Юбилей",
-"Meeting" => "Встреча",
-"Personal" => "Персональный",
-"Projects" => "Проекты",
-"Questions" => "Вопросы",
+"Friends" => "Друзья",
+"Family" => "Семья",
"{name}'s Birthday" => "{имя} день рождения",
"Contact" => "Контакт",
"You do not have the permissions to add contacts to this addressbook." => "У Вас нет разрешения для добавления контактов в эту адресную книгу.",
@@ -176,6 +165,7 @@
"Web site" => "Веб-сайт",
"http://www.somesite.com" => "http://www.somesite.com",
"Go to web site" => "Перейти к веб-сайту",
+"Birthday" => "День рождения",
"dd-mm-yyyy" => "дд-мм-гггг",
"Groups" => "Группы",
"Separate groups with commas" => "Разделите группы запятыми",
diff --git a/l10n/si_LK.php b/l10n/si_LK.php
index 91587db7..989380e7 100644
--- a/l10n/si_LK.php
+++ b/l10n/si_LK.php
@@ -51,6 +51,7 @@
"Upload too large" => "උඩුගත කිරීම විශාල වැඩිය",
"Upload Error" => "උඩුගත කිරීමේ දෝෂයක්",
"Import done" => "ආයාත කිරීම අවසන්",
+"Importing..." => "ආයාත කරමින් පවති...",
"Result: " => "ප්රතිඵලය:",
" imported, " => "ආයාත කරන ලදී,",
" failed." => "අසාර්ථකයි",
@@ -79,18 +80,6 @@
"Video" => "වීඩියෝව",
"Pager" => "පේජරය",
"Internet" => "අන්තර්ජාලය",
-"Birthday" => "උපන් දිනය",
-"Business" => "ව්යාපාරය",
-"Call" => "අමතන්න",
-"Clients" => "සේවාලාභීන්",
-"Deliverer" => "බාරදෙන්නා",
-"Holidays" => "නිවාඩු",
-"Ideas" => "අදහස්",
-"Journey" => "ගමන",
-"Meeting" => "රැස්වීම",
-"Personal" => "පෞද්ගලික",
-"Projects" => "ව්යාපෘති",
-"Questions" => "ප්රශ්ණ",
"{name}'s Birthday" => "{name}ගේ උපන්දිනය",
"Contact" => "සබඳතාව",
"You do not have the permissions to add contacts to this addressbook." => "මෙම ලිපින පොතට අලුත් සම්බන්ධතා එක් කිරීමට ඔබට අවසර නැත",
@@ -114,6 +103,7 @@
"Web site" => "වෙබ් අඩවිය",
"http://www.somesite.com" => "http://www.somesite.com",
"Go to web site" => "වෙබ් අඩවියට යන්න",
+"Birthday" => "උපන් දිනය",
"dd-mm-yyyy" => "දිදි-මාමා-වවවව",
"Groups" => "කණ්ඩායම්",
"Separate groups with commas" => "කණ්ඩායම් කොමා භාවිතයෙන් වෙන් කරන්න",
@@ -156,6 +146,7 @@
"Add contact" => "සම්බන්ධතාවක් එකතු කරන්න",
"Enter description" => "විස්තරය දෙන්න",
"more info" => "තව විස්තර",
+"Primary address (Kontact et al)" => "ප්රාථමික ලිපිනය(හැම විටම සම්බන්ධ කරගත හැක)",
"iOS/OS X" => "iOS/OS X",
"Addressbooks" => "ලිපින පොත්",
"Share" => "බෙදා හදා ගන්න",
diff --git a/l10n/sk_SK.php b/l10n/sk_SK.php
index 021a5e93..ff93509f 100644
--- a/l10n/sk_SK.php
+++ b/l10n/sk_SK.php
@@ -126,19 +126,6 @@
"Video" => "Video",
"Pager" => "Pager",
"Internet" => "Internet",
-"Birthday" => "Narodeniny",
-"Business" => "Biznis",
-"Call" => "Zavolať",
-"Clients" => "Klienti",
-"Deliverer" => "Dodávateľ",
-"Holidays" => "Prázdniny",
-"Ideas" => "Nápady",
-"Journey" => "Cesta",
-"Jubilee" => "Jubileum",
-"Meeting" => "Stretnutie",
-"Personal" => "Osobné",
-"Projects" => "Projekty",
-"Questions" => "Otázky",
"{name}'s Birthday" => "Narodeniny {name}",
"Contact" => "Kontakt",
"You do not have the permissions to add contacts to this addressbook." => "Nemáte oprávnenie pridať kontakt do adresára.",
@@ -176,6 +163,7 @@
"Web site" => "Web stránka",
"http://www.somesite.com" => "http://www.stranka.sk",
"Go to web site" => "Navštíviť web",
+"Birthday" => "Narodeniny",
"dd-mm-yyyy" => "dd. mm. yyyy",
"Groups" => "Skupiny",
"Separate groups with commas" => "Oddelte skupiny čiarkami",
diff --git a/l10n/sl.php b/l10n/sl.php
index 1e43908e..ef15ed8f 100644
--- a/l10n/sl.php
+++ b/l10n/sl.php
@@ -126,19 +126,8 @@
"Video" => "Video",
"Pager" => "Pozivnik",
"Internet" => "Internet",
-"Birthday" => "Rojstni dan",
-"Business" => "Poslovno",
-"Call" => "Klic",
-"Clients" => "Stranka",
-"Deliverer" => "Dostavljalec",
-"Holidays" => "Prazniki",
-"Ideas" => "Ideje",
-"Journey" => "Potovanje",
-"Jubilee" => "Obletnica",
-"Meeting" => "Srečanje",
-"Personal" => "Osebno",
-"Projects" => "Projekti",
-"Questions" => "Vprašanja",
+"Friends" => "Prijatelji",
+"Family" => "Družina",
"{name}'s Birthday" => "{name} - rojstni dan",
"Contact" => "Stik",
"You do not have the permissions to add contacts to this addressbook." => "Ni ustreznih dovoljenj za dodajanje stikov v ta imenik.",
@@ -176,6 +165,7 @@
"Web site" => "Spletna stran",
"http://www.somesite.com" => "http://www.spletnastran.si",
"Go to web site" => "Pojdi na spletno stran",
+"Birthday" => "Rojstni dan",
"dd-mm-yyyy" => "dd. mm. yyyy",
"Groups" => "Skupine",
"Separate groups with commas" => "Skupine ločite z vejicami",
diff --git a/l10n/sr.php b/l10n/sr.php
index 328f8f61..07f86afc 100644
--- a/l10n/sr.php
+++ b/l10n/sr.php
@@ -1,6 +1,18 @@
"Ни једна категорија није означена за брисање.",
"Information about vCard is incorrect. Please reload the page." => "Подаци о вКарти су неисправни. Поново учитајте страницу.",
+"There is no error, the file uploaded with success" => "Нема грешке, фајл је успешно послат",
+"The uploaded file exceeds the upload_max_filesize directive in php.ini" => "Послати фајл превазилази директиву upload_max_filesize из ",
+"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" => "Послати фајл превазилази директиву MAX_FILE_SIZE која је наведена у ХТМЛ форми",
+"The uploaded file was only partially uploaded" => "Послати фајл је само делимично отпремљен!",
+"No file was uploaded" => "Ниједан фајл није послат",
+"Missing a temporary folder" => "Недостаје привремена фасцикла",
"Contacts" => "Контакти",
+"Error" => "Грешка",
+"Upload too large" => "Пошиљка је превелика",
+"Unable to upload your file as it is a directory or has 0 bytes" => "Није могуће послати датотеку или зато што је директоријуму или јој је величина 0 бајта",
+"Upload Error" => "Грешка у слању",
+"Pending" => "На чекању",
"Download" => "Преузимање",
"Edit" => "Уреди",
"Delete" => "Обриши",
@@ -9,16 +21,21 @@
"Contact could not be found." => "Контакт се не може наћи.",
"Work" => "Посао",
"Home" => "Кућа",
+"Other" => "Друго",
"Mobile" => "Мобилни",
"Text" => "Текст",
"Voice" => "Глас",
"Fax" => "Факс",
"Video" => "Видео",
"Pager" => "Пејџер",
-"Birthday" => "Рођендан",
"Contact" => "Контакт",
"Add Contact" => "Додај контакт",
+"Import" => "Увези",
+"Settings" => "Подешавања",
+"Close" => "Затвори",
"Organization" => "Организација",
+"Birthday" => "Рођендан",
+"Groups" => "Групе",
"Preferred" => "Пожељан",
"Phone" => "Телефон",
"Email" => "Е-маил",
@@ -34,6 +51,8 @@
"Country" => "Земља",
"Addressbook" => "Адресар",
"Addressbooks" => "Адресар",
+"Share" => "Дељење",
"New Address Book" => "Нови адресар",
+"Name" => "Име",
"Save" => "Сними"
);
diff --git a/l10n/sr@latin.php b/l10n/sr@latin.php
index 39f025a0..fcc0ed76 100644
--- a/l10n/sr@latin.php
+++ b/l10n/sr@latin.php
@@ -1,20 +1,33 @@
"Podaci o vKarti su neispravni. Ponovo učitajte stranicu.",
+"There is no error, the file uploaded with success" => "Nema greške, fajl je uspešno poslat",
+"The uploaded file exceeds the upload_max_filesize directive in php.ini" => "Poslati fajl prevazilazi direktivu upload_max_filesize iz ",
+"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" => "Poslati fajl prevazilazi direktivu MAX_FILE_SIZE koja je navedena u HTML formi",
+"The uploaded file was only partially uploaded" => "Poslati fajl je samo delimično otpremljen!",
+"No file was uploaded" => "Nijedan fajl nije poslat",
+"Missing a temporary folder" => "Nedostaje privremena fascikla",
+"Upload too large" => "Pošiljka je prevelika",
+"Download" => "Preuzmi",
"Edit" => "Uredi",
"Delete" => "Obriši",
+"Cancel" => "Otkaži",
"This is not your addressbook." => "Ovo nije vaš adresar.",
"Contact could not be found." => "Kontakt se ne može naći.",
"Work" => "Posao",
"Home" => "Kuća",
+"Other" => "Drugo",
"Mobile" => "Mobilni",
"Text" => "Tekst",
"Voice" => "Glas",
"Fax" => "Faks",
"Video" => "Video",
"Pager" => "Pejdžer",
-"Birthday" => "Rođendan",
"Add Contact" => "Dodaj kontakt",
+"Settings" => "Podešavanja",
+"Close" => "Zatvori",
"Organization" => "Organizacija",
+"Birthday" => "Rođendan",
+"Groups" => "Grupe",
"Phone" => "Telefon",
"Email" => "E-mail",
"Address" => "Adresa",
@@ -23,5 +36,7 @@
"City" => "Grad",
"Region" => "Regija",
"Zipcode" => "Zip kod",
-"Country" => "Zemlja"
+"Country" => "Zemlja",
+"Name" => "Ime",
+"Save" => "Snimi"
);
diff --git a/l10n/sv.php b/l10n/sv.php
index ebed2ae4..1f940343 100644
--- a/l10n/sv.php
+++ b/l10n/sv.php
@@ -126,19 +126,8 @@
"Video" => "Video",
"Pager" => "Personsökare",
"Internet" => "Internet",
-"Birthday" => "Födelsedag",
-"Business" => "Företag",
-"Call" => "Ring",
-"Clients" => "Kunder",
-"Deliverer" => "Leverera",
-"Holidays" => "Helgdagar",
-"Ideas" => "Idéer",
-"Journey" => "Resa",
-"Jubilee" => "Jubileum",
-"Meeting" => "Möte",
-"Personal" => "Privat",
-"Projects" => "Projekt",
-"Questions" => "Frågor",
+"Friends" => "Vänner",
+"Family" => "Familj",
"{name}'s Birthday" => "{name}'s födelsedag",
"Contact" => "Kontakt",
"You do not have the permissions to add contacts to this addressbook." => "Du har inte behörighet att lägga till kontakter i denna adressbok.",
@@ -176,6 +165,7 @@
"Web site" => "Webbplats",
"http://www.somesite.com" => "http://www.somesite.com",
"Go to web site" => "Gå till webbplats",
+"Birthday" => "Födelsedag",
"dd-mm-yyyy" => "dd-mm-åååå",
"Groups" => "Grupper",
"Separate groups with commas" => "Separera grupperna med kommatecken",
diff --git a/l10n/ta_LK.php b/l10n/ta_LK.php
new file mode 100644
index 00000000..1b85e8bd
--- /dev/null
+++ b/l10n/ta_LK.php
@@ -0,0 +1,33 @@
+ "நீக்குவதற்கு எந்தப் பிரிவும் தெரிவுசெய்யப்படவில்லை.",
+"There is no error, the file uploaded with success" => "இங்கு வழு இல்லை, கோப்பு வெற்றிகரமாக பதிவேற்றப்பட்டது",
+"The uploaded file exceeds the upload_max_filesize directive in php.ini" => "பதிவேற்றப்பட்ட கோப்பானது php.ini இலுள்ள upload_max_filesize directive ஐ விட கூடியது",
+"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" => "பதிவேற்றப்பட்ட கோப்பானது HTML படிவத்தில் குறிப்பிடப்பட்டுள்ள MAX_FILE_SIZE directive ஐ விட கூடியது",
+"The uploaded file was only partially uploaded" => "பதிவேற்றப்பட்ட கோப்பானது பகுதியாக மட்டுமே பதிவேற்றப்பட்டுள்ளது",
+"No file was uploaded" => "எந்த கோப்பும் பதிவேற்றப்படவில்லை",
+"Missing a temporary folder" => "ஒரு தற்காலிகமான கோப்புறையை காணவில்லை",
+"Contacts" => "தொடர்புகள்",
+"Error" => "வழு",
+"Upload too large" => "பதிவேற்றல் மிகப்பெரியது",
+"Unable to upload your file as it is a directory or has 0 bytes" => "அடைவு அல்லது 0 bytes ஐ கொண்டுள்ளதால் உங்களுடைய கோப்பை பதிவேற்ற முடியவில்லை",
+"Upload Error" => "பதிவேற்றல் வழு",
+"Pending" => "நிலுவையிலுள்ள",
+"Download" => "பதிவிறக்குக",
+"Edit" => "தொகுக்க",
+"Delete" => "அழிக்க",
+"Cancel" => "இரத்து செய்க",
+"Work" => "வேலை",
+"Other" => "மற்றவை",
+"Text" => "உரை",
+"Import" => "இறக்குமதி",
+"Settings" => "அமைப்புகள்",
+"Close" => "மூடுக",
+"Birthday" => "பிறந்த நாள்",
+"Email" => "மின்னஞ்சல்",
+"more info" => "மேலதிக தகவல்",
+"Primary address (Kontact et al)" => "முதன்மை முகவரி (Kontact et al)",
+"iOS/OS X" => "iOS/OS X",
+"Share" => "பகிர்வு",
+"Name" => "பெயர்",
+"Save" => "சேமிக்க"
+);
diff --git a/l10n/th_TH.php b/l10n/th_TH.php
index 10793f27..67a733e6 100644
--- a/l10n/th_TH.php
+++ b/l10n/th_TH.php
@@ -126,19 +126,8 @@
"Video" => "วีดีโอ",
"Pager" => "เพจเจอร์",
"Internet" => "อินเทอร์เน็ต",
-"Birthday" => "วันเกิด",
-"Business" => "ธุรกิจ",
-"Call" => "โทร",
-"Clients" => "ลูกค้า",
-"Deliverer" => "ผู้จัดส่ง",
-"Holidays" => "วันหยุด",
-"Ideas" => "ไอเดีย",
-"Journey" => "การเดินทาง",
-"Jubilee" => "งานเฉลิมฉลอง",
-"Meeting" => "ประชุม",
-"Personal" => "ส่วนตัว",
-"Projects" => "โปรเจค",
-"Questions" => "คำถาม",
+"Friends" => "เพื่อน",
+"Family" => "ครอบครัว",
"{name}'s Birthday" => "วันเกิดของ {name}",
"Contact" => "ข้อมูลการติดต่อ",
"You do not have the permissions to add contacts to this addressbook." => "คุณไม่ได้รับสิทธิ์ให้เพิ่มข้อมูลผู้ติดต่อเข้าไปในสมุดบันทึกที่อยู่นี้",
@@ -176,6 +165,7 @@
"Web site" => "เว็บไซต์",
"http://www.somesite.com" => "http://www.somesite.com",
"Go to web site" => "ไปที่เว็บไซต์",
+"Birthday" => "วันเกิด",
"dd-mm-yyyy" => "dd-mm-yyyy",
"Groups" => "กลุ่ม",
"Separate groups with commas" => "คั่นระหว่างรายชื่อกลุ่มด้วยเครื่องหมายจุลภาีคหรือคอมม่า",
diff --git a/l10n/tr.php b/l10n/tr.php
index 166203e6..2329cb6e 100644
--- a/l10n/tr.php
+++ b/l10n/tr.php
@@ -53,6 +53,7 @@
"Enter name" => "İsim giriniz",
"Format custom, Short name, Full name, Reverse or Reverse with comma" => "Biçin özel, Kısa isim, Tam isim, Ters veya noktalı ters",
"Select type" => "Tür seç",
+"Select photo" => "Fotograf seç",
"You do not have permission to add contacts to " => "Yeni bir kişi eklemek için yetkiniz yok.",
"Please select one of your own address books." => "Adres defterlerinizden birini seçiniz.",
"Permission error" => "Yetki hatası",
@@ -65,8 +66,11 @@
"Error loading profile picture." => "Profil resmi yüklenirken hata oluştu.",
"Some contacts are marked for deletion, but not deleted yet. Please wait for them to be deleted." => "Bazı kişiler silinmek için işaretlendi, hala silinmedi. Silinmesi için bekleyin.",
"Do you want to merge these address books?" => "Bu adres defterlerini birleştirmek istiyor musunuz?",
+"Upload too large" => "Yükleme çok büyük",
"Wrong file type" => "Yanlış dosya tipi",
+"Unable to upload your file as it is a directory or has 0 bytes" => "Dosyanızın boyutu 0 byte olduğundan veya bir dizin olduğundan yüklenemedi",
"Upload Error" => "Yükleme Hatası",
+"Pending" => "Bekliyor",
"Importing..." => "İçeri aktarılıyor...",
"Result: " => "Sonuç: ",
" imported, " => " içe aktarıldı, ",
@@ -109,19 +113,6 @@
"Video" => "Video",
"Pager" => "Sayfalayıcı",
"Internet" => "İnternet",
-"Birthday" => "Doğum günü",
-"Business" => "İş",
-"Call" => "Çağrı",
-"Clients" => "Müşteriler",
-"Deliverer" => "Dağıtıcı",
-"Holidays" => "Tatiller",
-"Ideas" => "Fikirler",
-"Journey" => "Seyahat",
-"Jubilee" => "Yıl Dönümü",
-"Meeting" => "Toplantı",
-"Personal" => "Kişisel",
-"Projects" => "Projeler",
-"Questions" => "Sorular",
"{name}'s Birthday" => "{name}'nin Doğumgünü",
"Contact" => "Kişi",
"You do not have the permissions to add contacts to this addressbook." => "Bu adres defterine kişi eklemek için yetkiniz yok.",
@@ -156,6 +147,7 @@
"Web site" => "Web sitesi",
"http://www.somesite.com" => "http://www.somesite.com",
"Go to web site" => "Web sitesine git",
+"Birthday" => "Doğum günü",
"dd-mm-yyyy" => "gg-aa-yyyy",
"Groups" => "Gruplar",
"Separate groups with commas" => "Grupları birbirinden virgülle ayırın",
diff --git a/l10n/uk.php b/l10n/uk.php
index 57ea6887..7f1d5567 100644
--- a/l10n/uk.php
+++ b/l10n/uk.php
@@ -1,31 +1,178 @@
"Помилка (де)активації адресної книги.",
+"id is not set." => "ідентифікатор не встановлено.",
+"Cannot update addressbook with an empty name." => "Не можливо оновити адресну книгу з пустим ім'ям.",
+"No ID provided" => "Не надано ідентифікатор",
+"Error setting checksum." => "Помилка визначення контрольної суми.",
+"No categories selected for deletion." => "Не обрано категорії для видалення.",
+"No address books found." => "Не знайдено адресних книг.",
+"No contacts found." => "Контакти не знайдено.",
+"element name is not set." => "не встановлено ім'я елементу",
+"Cannot add empty property." => "Не можливо додати порожню властивість.",
"At least one of the address fields has to be filled out." => "Має бути заповнено щонайменше одне поле.",
+"Trying to add duplicate property: " => "Спроба додати дубльовану властивість: ",
+"Information about vCard is incorrect. Please reload the page." => "Інформація про vCard помилкова. Будь ласка, перезавантажте сторінку.",
+"Missing ID" => "Відсутній ідентифікатор",
+"Error parsing VCard for ID: \"" => "Помилка при зчитування VCard для ідентифікатора: \"",
+"checksum is not set." => "контрольна сума не задана.",
+"Information about vCard is incorrect. Please reload the page: " => "Інформація про vCard помилкова. Будь ласка, перезавантажте сторінку: ",
+"No contact ID was submitted." => "Не вказано жодного ідентифікатора контакту.",
+"Error reading contact photo." => "Помилка читання фото контакту.",
+"Error saving temporary file." => "Помилка збереження тимчасового файлу.",
+"The loading photo is not valid." => "Не допустиме зображення для завантаження.",
+"Contact ID is missing." => "Ідентифікатор контакту відсутній.",
+"No photo path was submitted." => "Не надано шлях до зображення.",
+"File doesn't exist:" => "Файл не існує:",
+"Error loading image." => "Помилка завантаження зображення.",
+"Error getting contact object." => "Помилка при отриманні об'єкту контакту.",
+"Error getting PHOTO property." => "Помилка при отриманні ФОТО властивості.",
+"Error saving contact." => "Помилка при збереженні контакту.",
+"Error resizing image" => "Помилка при зміні розміру зображення",
+"Error cropping image" => "Помилка обрізки зображення",
+"Error creating temporary image" => "Помилка при створенні тимчасового зображення",
+"Error finding image: " => "Не знайдено зображення: ",
+"Error uploading contacts to storage." => "Помилка завантаження контактів у сховище.",
+"There is no error, the file uploaded with success" => "Файл успішно відвантажено без помилок.",
+"The uploaded file exceeds the upload_max_filesize directive in php.ini" => "Розмір відвантаженого файлу перевищує директиву upload_max_filesize в php.ini",
+"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" => "Розмір відвантаженого файлу перевищує директиву MAX_FILE_SIZE вказану в HTML формі",
+"The uploaded file was only partially uploaded" => "Файл відвантажено лише частково",
+"No file was uploaded" => "Не відвантажено жодного файлу",
+"Missing a temporary folder" => "Відсутній тимчасовий каталог",
+"Couldn't save temporary image: " => "Не вдалося зберегти тимчасове зображення: ",
+"Couldn't load temporary image: " => "Не вдалося завантажити тимчасове зображення: ",
+"No file was uploaded. Unknown error" => "Не завантажено жодного файлу. Невідома помилка",
+"Contacts" => "Контакти",
+"Sorry, this functionality has not been implemented yet" => "На жаль, ця функція ще не була реалізована",
+"Not implemented" => "Не реалізовано",
+"Couldn't get a valid address." => "Не вдалося отримати правильну адресу.",
"Error" => "Помилка",
+"Format custom, Short name, Full name, Reverse or Reverse with comma" => "Format custom, Short name, Full name, Reverse or Reverse with comma",
+"You do not have permission to add contacts to " => "Ви не маєте повноважень додавати контакти у",
+"Please select one of your own address books." => "Будь ласка, оберіть одну з власних адресних книг.",
+"Permission error" => "Помилка доступу",
+"This property has to be non-empty." => "Властивість повинна бути заповнена.",
+"Upload too large" => "Файл занадто великий",
+"Unable to upload your file as it is a directory or has 0 bytes" => "Неможливо завантажити ваш файл тому, що він тека або файл розміром 0 байт",
+"Upload Error" => "Помилка завантаження",
+"Pending" => "Очікування",
+"Import done" => "Імпортування виконано",
+"Not all files uploaded. Retrying..." => "Не всі файли завантажено. Повторна спроба...",
+"Importing..." => "Імпортування...",
"Download" => "Завантажити",
+"Edit" => "Редагувати",
"Delete" => "Видалити",
"Cancel" => "Відмінити",
+"You do not have the permissions to update this addressbook." => "Ви не маєте повноважень оновлювати цю адресну книгу.",
+"There was an error updating the addressbook." => "Виникла помилка при оновленні адресної книги.",
+"You do not have the permissions to delete this addressbook." => "Ви не маєте повноважень видаляти цю адресну книгу.",
+"There was an error deleting this addressbook." => "Виникла помилка при видаленні цієї адресної книги.",
"This is not your addressbook." => "Це не ваша адресна книга.",
+"Contact could not be found." => "Контакт не знайдено",
+"GoogleTalk" => "GoogleTalk",
+"Facebook" => "Facebook",
+"XMPP" => "XMPP",
+"ICQ" => "ICQ",
+"Yahoo" => "Yahoo",
+"Skype" => "Skype",
+"QQ" => "QQ",
+"GaduGadu" => "GaduGadu",
+"Work" => "Робота",
+"Home" => "Домашня адреса",
+"Other" => "Інше",
"Mobile" => "Мобільний",
"Text" => "Текст",
"Voice" => "Голос",
+"Message" => "Повідомлення",
"Fax" => "Факс",
"Video" => "Відео",
"Pager" => "Пейджер",
-"Birthday" => "День народження",
+"Internet" => "Інтернет",
+"{name}'s Birthday" => "День народження {name}",
"Contact" => "Контакт",
+"You do not have the permissions to add contacts to this addressbook." => "Ви не маєте повноважень додавати контакти в цю адресну книгу.",
+"Could not find the vCard with ID." => "Не вдалося знайти vCard за ID.",
+"You do not have the permissions to edit this contact." => "Ви не маєте повноважень редагувати цей контакт.",
+"Could not find the vCard with ID: " => "Не вдалося знайти vCard за ID: ",
+"Could not find the Addressbook with ID: " => "Не вдалося знайти адресну книгу за ID: ",
+"You do not have the permissions to delete this contact." => "Ви не маєте повноважень видаляти цей контакт.",
+"There was an error deleting this contact." => "Виникла помилка при видаленні цього контакту.",
"Add Contact" => "Додати контакт",
+"Import" => "Імпорт",
"Settings" => "Налаштування",
+"Close" => "Закрити",
+"Drop photo to upload" => "Піднесіть фото для завантаження",
+"Delete current photo" => "Видалити поточне фото",
+"Edit current photo" => "Редагувати поточне фото",
+"Upload new photo" => "Завантажити нове фото",
+"Select photo from ownCloud" => "Обрати фото з ownCloud",
+"Edit name details" => "Редагувати деталі",
"Organization" => "Організація",
+"Nickname" => "Прізвисько",
+"Enter nickname" => "Ввести прізвисько",
+"Birthday" => "День народження",
+"dd-mm-yyyy" => "дд-мм-рррр",
+"Groups" => "Групи",
+"Separate groups with commas" => "Відділяйте групи комами",
+"Edit groups" => "Редагувати групи",
+"Preferred" => "Пріоритетний",
+"Please specify a valid email address." => "Будь ласка, вкажіть вірну адресу електронної пошти.",
+"Enter email address" => "Ввести адресу електронної пошти",
+"Mail to address" => "Написати листа за адресою",
+"Delete email address" => "Видалити адресу електронної пошти",
+"Enter phone number" => "Ввести номер телефону",
+"Delete phone number" => "Видалити номер телефону",
+"Instant Messenger" => "Instant Messenger",
+"Delete IM" => "Видалити IM",
+"View on map" => "Переглянути на карті",
+"Edit address details" => "Редагувати деталі адреси",
+"Add notes here." => "Додайте сюди примітки.",
+"Add field" => "Додати поле",
"Phone" => "Телефон",
"Email" => "Ел.пошта",
"Address" => "Адреса",
+"Note" => "Примітка",
+"Download contact" => "Завантажити контакт",
"Delete contact" => "Видалити контакт",
+"Edit address" => "Редагувати адреси",
+"Type" => "Тип",
+"PO Box" => "Поштова скринька",
"Extended" => "Розширено",
"City" => "Місто",
+"Region" => "Регіон",
"Zipcode" => "Поштовий індекс",
"Country" => "Країна",
+"Addressbook" => "Адресна книга",
+"Hon. prefixes" => "Високоповажні префікси",
+"Miss" => "Miss",
+"Ms" => "Пані",
+"Mr" => "Пан",
+"Sir" => "Sir",
+"Mrs" => "Mrs",
+"Dr" => "Доктор",
"Given name" => "Ім'я",
+"Additional names" => "Додаткові імена",
+"Family name" => "Прізвище",
+"Hon. suffixes" => "Високоповажні суфікси",
+"J.D." => "J.D.",
+"M.D." => "M.D.",
+"D.O." => "D.O.",
+"D.C." => "D.C.",
+"Ph.D." => "Ph.D.",
+"Esq." => "Esq.",
+"Jr." => "Jr.",
+"Sn." => "Sn.",
+"Import a contacts file" => "Імпортувати файл контактів",
+"Please choose the addressbook" => "Будь ласка, оберіть адресну книгу",
+"create a new addressbook" => "створити нову адресну книгу",
+"Name of new addressbook" => "Ім'я нової адресної книги",
+"Importing contacts" => "Імпортування контактів",
"Add contact" => "Додати контакт",
+"CardDAV syncing addresses" => "CardDAV синхронізує адреси",
+"more info" => "більше інформації",
+"Primary address (Kontact et al)" => "Первинна адреса (Контакт та ін)",
+"iOS/OS X" => "iOS/OS X",
+"Addressbooks" => "Адресні книги",
+"Share" => "Поділитися",
"New Address Book" => "Нова адресна книга",
"Name" => "Ім'я",
"Save" => "Зберегти"
diff --git a/l10n/vi.php b/l10n/vi.php
index b6c9ae18..8658e3c3 100644
--- a/l10n/vi.php
+++ b/l10n/vi.php
@@ -8,6 +8,7 @@
"No address books found." => "Không tìm thấy sổ địa chỉ.",
"No contacts found." => "Không tìm thấy danh sách",
"element name is not set." => "tên phần tử không được thiết lập.",
+"Could not parse contact: " => "Không thể phân tích vui lòng liên hệ :",
"Cannot add empty property." => "Không thể thêm thuộc tính rỗng",
"At least one of the address fields has to be filled out." => "Ít nhất một địa chỉ phải được điền.",
"Trying to add duplicate property: " => "Thêm hai thuộc tính trùng nhau",
@@ -15,8 +16,10 @@
"Unknown IM: " => "Không biết IM:",
"Information about vCard is incorrect. Please reload the page." => "Thông tin vCard không chính xác. Vui lòng tải lại trang.",
"Missing ID" => "Missing ID",
+"Error parsing VCard for ID: \"" => "Lỗi cú pháp VCard ID: \"",
"checksum is not set." => "tổng kiểm tra không được thiết lập.",
"Information about vCard is incorrect. Please reload the page: " => "Thông tin về vCard không chính xác. Vui lòng tải lại trang:",
+"Something went FUBAR. " => "Có cái gì đó không được khắc phục.",
"No contact ID was submitted." => "Không có ID của liên lạc được tìm thấy",
"Error reading contact photo." => "Lỗi đọc liên lạc hình ảnh.",
"Error saving temporary file." => "Lỗi trong quá trình lưu file tạm",
@@ -47,27 +50,57 @@
"Not implemented" => "không thực hiện được",
"Couldn't get a valid address." => "Không thể có được một địa chỉ hợp lệ.",
"Error" => "Lỗi",
+"Please enter an email address." => "Vui lòng nhập địa chỉ email.",
"Enter name" => "Nhập tên",
"Format custom, Short name, Full name, Reverse or Reverse with comma" => "Định dạng tùy chỉnh, Tên viết tắt, Tên đầy đủ, hoặc đảo ngược với dấu phẩy",
"Select type" => "Chọn loại",
+"Select photo" => "Chọn photo",
"You do not have permission to add contacts to " => "Bạn không có quyền để thêm địa chỉ liên lạc",
"Please select one of your own address books." => "Vui lòng chọn một trong những danh bạ địa chỉ của riêng bạn.",
"Permission error" => "Lỗi quyền truy cập",
+"Click to undo deletion of \"" => "Nhấn vào đây để hoàn tất xóa \"",
+"Cancelled deletion of: \"" => "Hủy xóa :\"",
+"This property has to be non-empty." => "Thuộc tính này không được rỗng.",
+"Couldn't serialize elements." => "Không thể nối tiếp các yếu tố.",
+"Unknown error. Please check logs." => "Hệ thống báo lỗi .Vui lòng xem lại",
+"'deleteProperty' called without type argument. Please report at bugs.owncloud.org" => "'deleteProperty' kêu gọi mà không có đối số kiểu. Xin báo cáo tại bugs.owncloud.org",
"Edit name" => "Sửa tên",
"No files selected for upload." => "Không có tập tin nào được chọn để tải lên.",
"The file you are trying to upload exceed the maximum size for file uploads on this server." => "Các tập tin bạn đang cố gắng tải lên vượt quá kích thước tối đa cho tập tin tải lên trên máy chủ.",
"Error loading profile picture." => "Lỗi khi tải lên hồ sơ hình ảnh.",
-"Do you want to merge these address books?" => "Bạn có muốn kết hợp những cuốn sách địa chỉ?",
+"Some contacts are marked for deletion, but not deleted yet. Please wait for them to be deleted." => "Một số địa chỉ liên lạc được đánh dấu để xóa, nhưng chưa bị xóa . Hãy đợi đến khi họ xóa.",
+"Do you want to merge these address books?" => "Bạn có muốn kết hợp những addressbook?",
+"Shared by " => " Được chia sẻ bởi",
+"Upload too large" => "Tải lên quá lớn",
+"Only image files can be used as profile picture." => "Chỉ sử dụng các tập tin hình ảnh. ",
+"Wrong file type" => "Sai kiểu tập tin",
+"Your browser doesn't support AJAX upload. Please click on the profile picture to select a photo to upload." => " Trình duyệt của bạn không hỗ trợ AJAX upload .Xin vui lòng click vào hình ảnh hồ sơ cá nhân để lựa chọn một ảnh để tải lên.",
+"Unable to upload your file as it is a directory or has 0 bytes" => "Không thể tải lên tập tin của bạn ,nó như là một thư mục hoặc có 0 byte",
+"Upload Error" => "Lỗi tải lên",
+"Pending" => "Đang chờ",
+"Import done" => "Thực hiện import",
+"Not all files uploaded. Retrying..." => "Tất cả các tập tin không được tải lên .Đang thử lại...",
+"Something went wrong with the upload, please retry." => "Một cái gì đó đã sai khi tải lên ,hãy thử lại.",
+"Importing..." => "Đang nhập vào...",
+"The address book name cannot be empty." => "Tên Danh bạ không thể để trống .",
"Result: " => "Kết quả:",
+" imported, " => "Nhập vào,",
" failed." => "thất bại.",
+"Displayname cannot be empty." => "Tên hiển thị không được để trống :",
+"Show CardDav link" => "Hiển thị CardDav ",
+"Show read-only VCF link" => "Hiển thị liên kết VCF chỉ đọc",
"Download" => "Tải về",
"Edit" => "Sửa",
"Delete" => "Xóa",
"Cancel" => "Hủy",
+"More..." => "nhiều hơn...",
+"Less..." => " ít hơn...",
+"You do not have the permissions to read this addressbook." => " Bạn không được quyền đọc Danh bạ. ",
"You do not have the permissions to update this addressbook." => "Bạn không có quyền truy cập để cập nhật danh bạ này.",
"There was an error updating the addressbook." => "Có một lỗi khi cập nhật danh bạ.",
"You do not have the permissions to delete this addressbook." => "Bạn không có quyền truy cập để xóa danh bạ này.",
"There was an error deleting this addressbook." => "Có một lỗi khi xóa danh bạ này.",
+"Addressbook not found: " => "Không tìm thấy Addressbook :",
"This is not your addressbook." => "Đây không phải là sổ địa chỉ của bạn.",
"Contact could not be found." => "Liên lạc không được tìm thấy",
"Jabber" => "Jabber",
@@ -84,6 +117,7 @@
"GaduGadu" => "GaduGadu",
"Work" => "Công việc",
"Home" => "Nhà",
+"Other" => "Khác",
"Mobile" => "Di động",
"Text" => "Văn bản",
"Voice" => "Giọng nói",
@@ -92,28 +126,29 @@
"Video" => "Video",
"Pager" => "số trang",
"Internet" => "Mạng internet",
-"Birthday" => "Ngày sinh nhật",
-"Business" => "Kinh doanh",
-"Call" => "Gọi",
-"Holidays" => "Ngày nghỉ",
-"Ideas" => "Ý tưởng",
-"Jubilee" => "Lễ kỉ niệm",
-"Meeting" => "Hội nghị",
+"Friends" => "Bạn bè",
+"Family" => "Gia đình",
"{name}'s Birthday" => "Sinh nhật của {name}",
"Contact" => "Danh sách",
+"You do not have the permissions to add contacts to this addressbook." => "Bạn không có quyền truy cập để thêm số liên lạc vào danh bạ này.",
"Could not find the vCard with ID." => "Không thể tìm thấy ID vCard.",
"You do not have the permissions to edit this contact." => "Bạn không có quyền truy cập để chỉnh sửa địa chỉ liên hệ này.",
"Could not find the vCard with ID: " => "Không thể tìm thấy ID vCard: ",
-"Could not find the Addressbook with ID: " => "Không thể tìm thấy ID danh bạ:",
+"Could not find the Addressbook with ID: " => "Không thể tìm thấy danh bạ với ID:",
"You do not have the permissions to delete this contact." => "Bạn không có quyền truy cập để xóa địa chỉ liên hệ này.",
"There was an error deleting this contact." => "Có một lỗi khi xóa địa chỉ liên lạc này.",
"Add Contact" => "Thêm liên lạc",
"Import" => "Nhập",
"Settings" => "Tùy chỉnh",
"Close" => "Đóng",
+"Keyboard shortcuts" => "Phím tắt",
+"Navigation" => "Navigation",
"Next contact in list" => "Liên lạc tiếp theo",
"Previous contact in list" => "Liên lạc trước",
"Expand/collapse current addressbook" => "Đóng/mở sổ địa chỉ",
+"Next addressbook" => "Tới addressbook",
+"Previous addressbook" => "Lùi lại addressbook",
+"Actions" => "Actions",
"Refresh contacts list" => "Làm mới danh sách liên lạc",
"Add new contact" => "Thêm liên lạc mới",
"Add new addressbook" => "Thêm sổ địa chỉ mới",
@@ -130,6 +165,7 @@
"Web site" => "Web site",
"http://www.somesite.com" => "http://www.somesite.com",
"Go to web site" => "Đi tới website",
+"Birthday" => "Ngày sinh nhật",
"dd-mm-yyyy" => "dd-mm-yyyy",
"Groups" => "Nhóm",
"Separate groups with commas" => "Phân cách bởi dấu phẩy",
@@ -169,13 +205,13 @@
"Postal code" => "Mã bưu chính",
"Country" => "Quốc gia",
"Addressbook" => "Sổ địa chỉ",
-"Hon. prefixes" => "Tiền tốt Hon.",
+"Hon. prefixes" => "Tiền tố",
"Miss" => "Miss",
"Ms" => "Ms",
-"Mr" => "Mr",
-"Sir" => "Sir",
-"Mrs" => "Sir",
-"Dr" => "Sir",
+"Mr" => "Ông",
+"Sir" => "Ngài",
+"Mrs" => "Bà",
+"Dr" => "Dr",
"Given name" => "Được đặt tên",
"Additional names" => "Tên bổ sung",
"Family name" => "Tên gia đình",
@@ -193,6 +229,7 @@
"create a new addressbook" => "Tạo một sổ địa chỉ mới",
"Name of new addressbook" => "Tên danh bạ mới",
"Importing contacts" => "Nhập liên lạc",
+"
You have no contacts in your addressbook.
You can import VCF files by dragging them to the contacts list and either drop them on an addressbook to import into it, or on an empty spot to create a new addressbook and import into that. You can also import by clicking on the import button at the bottom of the list.
" => "
Bạn không có địa chỉ liên lạc trong sổ Danh bạ của bạn h3>
Bạn có thể nhập các file VCF bằng cách kéo chúng vào danh sách liên lạc hoặc thả chúng vào một sổ địa chỉ để nhập vào nó, hoặc trên một khoảng trống để tạo ra mộtbsổ địa chỉ mới và nhập vào đó Bạn cũng có thể nhập vào bằng cách nhấp vào nút nhập khẩu ở dưới cùng của danh sách. p>",
"Add contact" => "Thêm liên lạc",
"Select Address Books" => "Chọn sổ địa chỉ",
"Enter description" => "Nhập mô tả",
@@ -201,7 +238,9 @@
"Primary address (Kontact et al)" => "Địa chỉ chính (Kontact et al)",
"iOS/OS X" => "iOS/OS X",
"Addressbooks" => "Sổ địa chỉ",
-"Share" => "Chia sẽ",
+"Share" => "Chia sẻ",
"New Address Book" => "Sổ địa chỉ mới",
+"Name" => "Tên",
+"Description" => "Mô tả",
"Save" => "Lưu"
);
diff --git a/l10n/zh_CN.GB2312.php b/l10n/zh_CN.GB2312.php
index 11ca48fb..e885641e 100644
--- a/l10n/zh_CN.GB2312.php
+++ b/l10n/zh_CN.GB2312.php
@@ -126,19 +126,6 @@
"Video" => "视频",
"Pager" => "分页",
"Internet" => "互联网",
-"Birthday" => "生日",
-"Business" => "商业",
-"Call" => "呼叫",
-"Clients" => "客户",
-"Deliverer" => "供应商",
-"Holidays" => "假日",
-"Ideas" => "想法",
-"Journey" => "路程",
-"Jubilee" => "娱乐",
-"Meeting" => "会议",
-"Personal" => "私人",
-"Projects" => "项目",
-"Questions" => "问题",
"{name}'s Birthday" => "{name} 的生日",
"Contact" => "联系人",
"You do not have the permissions to add contacts to this addressbook." => "您没有权限添加联系人到此地址薄。",
@@ -176,6 +163,7 @@
"Web site" => "网站",
"http://www.somesite.com" => "http://www.somesite.com",
"Go to web site" => "访问网站",
+"Birthday" => "生日",
"dd-mm-yyyy" => "dd-mm-yyyy",
"Groups" => "群组",
"Separate groups with commas" => "用逗号分隔群组",
diff --git a/l10n/zh_CN.php b/l10n/zh_CN.php
index 3332dd15..027d9153 100644
--- a/l10n/zh_CN.php
+++ b/l10n/zh_CN.php
@@ -126,19 +126,8 @@
"Video" => "视频",
"Pager" => "传呼机",
"Internet" => "互联网",
-"Birthday" => "生日",
-"Business" => "商务",
-"Call" => "电话",
-"Clients" => "客户",
-"Deliverer" => "供应商",
-"Holidays" => "假期",
-"Ideas" => "创意",
-"Journey" => "旅行",
-"Jubilee" => "Jubilee",
-"Meeting" => "会议",
-"Personal" => "个人",
-"Projects" => "项目",
-"Questions" => "问题",
+"Friends" => "朋友",
+"Family" => "家庭",
"{name}'s Birthday" => "{name} 的生日",
"Contact" => "联系人",
"You do not have the permissions to add contacts to this addressbook." => "您没有权限增加联系人到此地址簿",
@@ -176,6 +165,7 @@
"Web site" => "网址",
"http://www.somesite.com" => "http://www.wodewangzhan.com",
"Go to web site" => "访问网址",
+"Birthday" => "生日",
"dd-mm-yyyy" => "yyyy-mm-dd",
"Groups" => "分组",
"Separate groups with commas" => "用逗号隔开分组",
diff --git a/l10n/zh_TW.php b/l10n/zh_TW.php
index 30998fbc..11744708 100644
--- a/l10n/zh_TW.php
+++ b/l10n/zh_TW.php
@@ -9,8 +9,17 @@
"Missing ID" => "遺失ID",
"Information about vCard is incorrect. Please reload the page: " => "vCard資訊不正確. 請重新整理頁面:",
"File doesn't exist:" => "檔案不存在",
+"There is no error, the file uploaded with success" => "無錯誤,檔案上傳成功",
+"The uploaded file exceeds the upload_max_filesize directive in php.ini" => "上傳的檔案超過了 php.ini 中的 upload_max_filesize 設定",
+"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form" => "上傳黨案的超過 HTML 表單中指定 MAX_FILE_SIZE 限制",
+"The uploaded file was only partially uploaded" => "只有部分檔案被上傳",
"No file was uploaded" => "沒有已上傳的檔案",
+"Missing a temporary folder" => "遺失暫存資料夾",
"Contacts" => "通訊錄",
+"Error" => "錯誤",
+"Upload too large" => "上傳過大",
+"Unable to upload your file as it is a directory or has 0 bytes" => "無法上傳您的檔案因為它可能是一個目錄或檔案大小為0",
+"Upload Error" => "上傳發生錯誤",
"Download" => "下載",
"Edit" => "編輯",
"Delete" => "刪除",
@@ -19,6 +28,7 @@
"Contact could not be found." => "通訊錄未發現",
"Work" => "公司",
"Home" => "住宅",
+"Other" => "其他",
"Mobile" => "行動電話",
"Text" => "文字",
"Voice" => "語音",
@@ -27,16 +37,18 @@
"Video" => "影片",
"Pager" => "呼叫器",
"Internet" => "網際網路",
-"Birthday" => "生日",
"{name}'s Birthday" => "{name}的生日",
"Contact" => "通訊錄",
"Add Contact" => "添加通訊錄",
"Import" => "匯入",
+"Settings" => "設定",
+"Close" => "關閉",
"Upload new photo" => "上傳新照片",
"Edit name details" => "編輯姓名詳細資訊",
"Organization" => "組織",
"Nickname" => "綽號",
"Enter nickname" => "輸入綽號",
+"Birthday" => "生日",
"dd-mm-yyyy" => "dd-mm-yyyy",
"Groups" => "群組",
"Separate groups with commas" => "用逗號分隔群組",
@@ -73,7 +85,12 @@
"Given name" => "給定名(名)",
"Additional names" => "額外名",
"Family name" => "家族名(姓)",
+"more info" => "更多資訊",
+"Primary address (Kontact et al)" => "主要地址",
+"iOS/OS X" => "iOS/OS X",
"Addressbooks" => "電話簿",
+"Share" => "分享",
"New Address Book" => "新電話簿",
+"Name" => "名稱",
"Save" => "儲存"
);
diff --git a/lib/addressbook.php b/lib/addressbook.php
index f57e6300..e96b414a 100644
--- a/lib/addressbook.php
+++ b/lib/addressbook.php
@@ -34,17 +34,21 @@
* );
*
*/
+
+namespace OCA\Contacts;
+
/**
* This class manages our addressbooks.
*/
-class OC_Contacts_Addressbook {
+class Addressbook {
/**
* @brief Returns the list of addressbooks for a specific user.
* @param string $uid
* @param boolean $active Only return addressbooks with this $active state, default(=false) is don't care
+ * @param boolean $shared Whether to also return shared addressbook. Defaults to true.
* @return array or false.
*/
- public static function all($uid, $active=false) {
+ public static function all($uid, $active = false, $shared = true) {
$values = array($uid);
$active_where = '';
if ($active) {
@@ -52,22 +56,27 @@ class OC_Contacts_Addressbook {
$values[] = 1;
}
try {
- $stmt = OCP\DB::prepare( 'SELECT * FROM `*PREFIX*contacts_addressbooks` WHERE `userid` = ? ' . $active_where . ' ORDER BY `displayname`' );
+ $stmt = \OCP\DB::prepare( 'SELECT * FROM `*PREFIX*contacts_addressbooks` WHERE `userid` = ? ' . $active_where . ' ORDER BY `displayname`' );
$result = $stmt->execute($values);
- } catch(Exception $e) {
- OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.' exception: '.$e->getMessage(), OCP\Util::ERROR);
- OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.' uid: '.$uid, OCP\Util::DEBUG);
+ if (\OC_DB::isError($result)) {
+ \OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OC_Log::ERROR);
+ return false;
+ }
+ } catch(\Exception $e) {
+ \OCP\Util::writeLog('contacts', __METHOD__.' exception: '.$e->getMessage(), \OCP\Util::ERROR);
+ \OCP\Util::writeLog('contacts', __METHOD__.' uid: '.$uid, \OCP\Util::DEBUG);
return false;
}
$addressbooks = array();
while( $row = $result->fetchRow()) {
- $row['permissions'] = OCP\Share::PERMISSION_CREATE
- | OCP\Share::PERMISSION_READ | OCP\Share::PERMISSION_UPDATE
- | OCP\Share::PERMISSION_DELETE | OCP\Share::PERMISSION_SHARE;
+ $row['permissions'] = \OCP\PERMISSION_ALL;
$addressbooks[] = $row;
}
- $addressbooks = array_merge($addressbooks, OCP\Share::getItemsSharedWith('addressbook', OC_Share_Backend_Addressbook::FORMAT_ADDRESSBOOKS));
+
+ if($shared === true) {
+ $addressbooks = array_merge($addressbooks, \OCP\Share::getItemsSharedWith('addressbook', Share_Backend_Addressbook::FORMAT_ADDRESSBOOKS));
+ }
if(!$active && !count($addressbooks)) {
$id = self::addDefault($uid);
return array(self::find($id),);
@@ -82,7 +91,7 @@ class OC_Contacts_Addressbook {
*/
public static function activeIds($uid = null) {
if(is_null($uid)) {
- $uid = OCP\USER::getUser();
+ $uid = \OCP\USER::getUser();
}
// query all addressbooks to force creation of default if it desn't exist.
@@ -122,28 +131,31 @@ class OC_Contacts_Addressbook {
*/
public static function find($id) {
try {
- $stmt = OCP\DB::prepare( 'SELECT * FROM `*PREFIX*contacts_addressbooks` WHERE `id` = ?' );
+ $stmt = \OCP\DB::prepare( 'SELECT * FROM `*PREFIX*contacts_addressbooks` WHERE `id` = ?' );
$result = $stmt->execute(array($id));
+ if (\OC_DB::isError($result)) {
+ \OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OC_Log::ERROR);
+ return false;
+ }
} catch(Exception $e) {
- OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
- OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', id: '.$id, OCP\Util::DEBUG);
+ \OCP\Util::writeLog('contacts', __METHOD__.', exception: ' . $e->getMessage(), \OCP\Util::ERROR);
+ \OCP\Util::writeLog('contacts', __METHOD__.', id: ' . $id, \OCP\Util::DEBUG);
return false;
}
$row = $result->fetchRow();
- if($row['userid'] != OCP\USER::getUser() && !OC_Group::inGroup(OCP\User::getUser(), 'admin')) {
- $sharedAddressbook = OCP\Share::getItemSharedWithBySource('addressbook', $id);
- if (!$sharedAddressbook || !($sharedAddressbook['permissions'] & OCP\Share::PERMISSION_READ)) {
+
+ if($row['userid'] != \OCP\USER::getUser() && !\OC_Group::inGroup(\OCP\User::getUser(), 'admin')) {
+ $sharedAddressbook = \OCP\Share::getItemSharedWithBySource('addressbook', $id);
+ if (!$sharedAddressbook || !($sharedAddressbook['permissions'] & \OCP\PERMISSION_READ)) {
throw new Exception(
- OC_Contacts_App::$l10n->t(
+ App::$l10n->t(
'You do not have the permissions to read this addressbook.'
)
);
}
$row['permissions'] = $sharedAddressbook['permissions'];
} else {
- $row['permissions'] = OCP\Share::PERMISSION_CREATE
- | OCP\Share::PERMISSION_READ | OCP\Share::PERMISSION_UPDATE
- | OCP\Share::PERMISSION_DELETE | OCP\Share::PERMISSION_SHARE;
+ $row['permissions'] = \OCP\PERMISSION_ALL;
}
return $row;
}
@@ -154,7 +166,7 @@ class OC_Contacts_Addressbook {
*/
public static function addDefault($uid = null) {
if(is_null($uid)) {
- $uid = OCP\USER::getUser();
+ $uid = \OCP\USER::getUser();
}
$id = self::add($uid, 'Contacts', 'Default Address Book');
if($id !== false) {
@@ -172,11 +184,15 @@ class OC_Contacts_Addressbook {
*/
public static function add($uid,$name,$description='') {
try {
- $stmt = OCP\DB::prepare( 'SELECT `uri` FROM `*PREFIX*contacts_addressbooks` WHERE `userid` = ? ' );
+ $stmt = \OCP\DB::prepare( 'SELECT `uri` FROM `*PREFIX*contacts_addressbooks` WHERE `userid` = ? ' );
$result = $stmt->execute(array($uid));
+ if (\OC_DB::isError($result)) {
+ \OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OC_Log::ERROR);
+ return false;
+ }
} catch(Exception $e) {
- OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.' exception: '.$e->getMessage(), OCP\Util::ERROR);
- OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.' uid: '.$uid, OCP\Util::DEBUG);
+ \OCP\Util::writeLog('contacts', __METHOD__ . ' exception: ' . $e->getMessage(), \OCP\Util::ERROR);
+ \OCP\Util::writeLog('contacts', __METHOD__ . ' uid: ' . $uid, \OCP\Util::DEBUG);
return false;
}
$uris = array();
@@ -186,15 +202,19 @@ class OC_Contacts_Addressbook {
$uri = self::createURI($name, $uris );
try {
- $stmt = OCP\DB::prepare( 'INSERT INTO `*PREFIX*contacts_addressbooks` (`userid`,`displayname`,`uri`,`description`,`ctag`) VALUES(?,?,?,?,?)' );
+ $stmt = \OCP\DB::prepare( 'INSERT INTO `*PREFIX*contacts_addressbooks` (`userid`,`displayname`,`uri`,`description`,`ctag`) VALUES(?,?,?,?,?)' );
$result = $stmt->execute(array($uid,$name,$uri,$description,1));
+ if (\OC_DB::isError($result)) {
+ \OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OC_Log::ERROR);
+ return false;
+ }
} catch(Exception $e) {
- OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
- OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', uid: '.$uid, OCP\Util::DEBUG);
+ \OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR);
+ \OCP\Util::writeLog('contacts', __METHOD__.', uid: '.$uid, \OCP\Util::DEBUG);
return false;
}
- return OCP\DB::insertid('*PREFIX*contacts_addressbooks');
+ return \OCP\DB::insertid('*PREFIX*contacts_addressbooks');
}
/**
@@ -205,20 +225,25 @@ class OC_Contacts_Addressbook {
* @param string $description
* @return insertid or false
*/
- public static function addFromDAVData($principaluri,$uri,$name,$description) {
+ public static function addFromDAVData($principaluri, $uri, $name, $description) {
$uid = self::extractUserID($principaluri);
try {
- $stmt = OCP\DB::prepare('INSERT INTO `*PREFIX*contacts_addressbooks` (`userid`,`displayname`,`uri`,`description`,`ctag`) VALUES(?,?,?,?,?)');
- $result = $stmt->execute(array($uid,$name,$uri,$description,1));
+ $stmt = \OCP\DB::prepare('INSERT INTO `*PREFIX*contacts_addressbooks` '
+ . '(`userid`,`displayname`,`uri`,`description`,`ctag`) VALUES(?,?,?,?,?)');
+ $result = $stmt->execute(array($uid, $name, $uri, $description, 1));
+ if (\OC_DB::isError($result)) {
+ \OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OC_Log::ERROR);
+ return false;
+ }
} catch(Exception $e) {
- OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
- OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', uid: '.$uid, OCP\Util::DEBUG);
- OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', uri: '.$uri, OCP\Util::DEBUG);
+ \OCP\Util::writeLog('contacts', __METHOD__.', exception: ' . $e->getMessage(), \OCP\Util::ERROR);
+ \OCP\Util::writeLog('contacts', __METHOD__.', uid: ' . $uid, \OCP\Util::DEBUG);
+ \OCP\Util::writeLog('contacts', __METHOD__.', uri: ' . $uri, \OCP\Util::DEBUG);
return false;
}
- return OCP\DB::insertid('*PREFIX*contacts_addressbooks');
+ return \OCP\DB::insertid('*PREFIX*contacts_addressbooks');
}
/**
@@ -231,11 +256,11 @@ class OC_Contacts_Addressbook {
public static function edit($id,$name,$description) {
// Need these ones for checking uri
$addressbook = self::find($id);
- if ($addressbook['userid'] != OCP\User::getUser() && !OC_Group::inGroup(OCP\User::getUser(), 'admin')) {
- $sharedAddressbook = OCP\Share::getItemSharedWithBySource('addressbook', $id);
- if (!$sharedAddressbook || !($sharedAddressbook['permissions'] & OCP\Share::PERMISSION_UPDATE)) {
- throw new Exception(
- OC_Contacts_App::$l10n->t(
+ if ($addressbook['userid'] != \OCP\User::getUser() && !\OC_Group::inGroup(OCP\User::getUser(), 'admin')) {
+ $sharedAddressbook = \OCP\Share::getItemSharedWithBySource('addressbook', $id);
+ if (!$sharedAddressbook || !($sharedAddressbook['permissions'] & \OCP\PERMISSION_UPDATE)) {
+ throw new \Exception(
+ App::$l10n->t(
'You do not have the permissions to update this addressbook.'
)
);
@@ -249,13 +274,21 @@ class OC_Contacts_Addressbook {
}
try {
- $stmt = OCP\DB::prepare('UPDATE `*PREFIX*contacts_addressbooks` SET `displayname`=?,`description`=?, `ctag`=`ctag`+1 WHERE `id`=?');
+ $stmt = \OCP\DB::prepare('UPDATE `*PREFIX*contacts_addressbooks` SET `displayname`=?,`description`=?, `ctag`=`ctag`+1 WHERE `id`=?');
$result = $stmt->execute(array($name,$description,$id));
+ if (\OC_DB::isError($result)) {
+ \OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OC_Log::ERROR);
+ throw new Exception(
+ App::$l10n->t(
+ 'There was an error updating the addressbook.'
+ )
+ );
+ }
} catch(Exception $e) {
- OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
- OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', id: '.$id, OCP\Util::DEBUG);
+ \OCP\Util::writeLog('contacts', __METHOD__ . ', exception: ' . $e->getMessage(), \OCP\Util::ERROR);
+ \OCP\Util::writeLog('contacts', __METHOD__ . ', id: ' . $id, \OCP\Util::DEBUG);
throw new Exception(
- OC_Contacts_App::$l10n->t(
+ App::$l10n->t(
'There was an error updating the addressbook.'
)
);
@@ -272,13 +305,13 @@ class OC_Contacts_Addressbook {
*/
public static function setActive($id,$active) {
$sql = 'UPDATE `*PREFIX*contacts_addressbooks` SET `active` = ? WHERE `id` = ?';
- OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', id: '.$id.', active: '.intval($active), OCP\Util::ERROR);
+
try {
- $stmt = OCP\DB::prepare($sql);
+ $stmt = \OCP\DB::prepare($sql);
$stmt->execute(array(intval($active), $id));
return true;
- } catch(Exception $e) {
- OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', exception for '.$id.': '.$e->getMessage(), OCP\Util::ERROR);
+ } catch(\Exception $e) {
+ \OCP\Util::writeLog('contacts', __METHOD__ . ', exception for ' . $id.': ' . $e->getMessage(), \OCP\Util::ERROR);
return false;
}
}
@@ -291,12 +324,17 @@ class OC_Contacts_Addressbook {
public static function isActive($id) {
$sql = 'SELECT `active` FROM `*PREFIX*contacts_addressbooks` WHERE `id` = ?';
try {
- $stmt = OCP\DB::prepare( $sql );
+ $stmt = \OCP\DB::prepare( $sql );
$result = $stmt->execute(array($id));
+ if (\OC_DB::isError($result)) {
+ \OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OC_Log::ERROR);
+ return false;
+ }
$row = $result->fetchRow();
return (bool)$row['active'];
} catch(Exception $e) {
- OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
+ \OCP\Util::writeLog('contacts', __METHOD__.', exception: ' . $e->getMessage(), \OCP\Util::ERROR);
+ return false;
}
}
@@ -307,11 +345,12 @@ class OC_Contacts_Addressbook {
*/
public static function delete($id) {
$addressbook = self::find($id);
- if ($addressbook['userid'] != OCP\User::getUser() && !OC_Group::inGroup(OCP\User::getUser(), 'admin')) {
- $sharedAddressbook = OCP\Share::getItemSharedWithBySource('addressbook', $id);
- if (!$sharedAddressbook || !($sharedAddressbook['permissions'] & OCP\Share::PERMISSION_DELETE)) {
+
+ if ($addressbook['userid'] != \OCP\User::getUser() && !\OC_Group::inGroup(OCP\User::getUser(), 'admin')) {
+ $sharedAddressbook = \OCP\Share::getItemSharedWithBySource('addressbook', $id);
+ if (!$sharedAddressbook || !($sharedAddressbook['permissions'] & \OCP\PERMISSION_DELETE)) {
throw new Exception(
- OC_Contacts_App::$l10n->t(
+ App::$l10n->t(
'You do not have the permissions to delete this addressbook.'
)
);
@@ -319,36 +358,36 @@ class OC_Contacts_Addressbook {
}
// First delete cards belonging to this addressbook.
- $cards = OC_Contacts_VCard::all($id);
+ $cards = VCard::all($id);
foreach($cards as $card) {
try {
- OC_Contacts_VCard::delete($card['id']);
+ VCard::delete($card['id']);
} catch(Exception $e) {
- OCP\Util::writeLog('contacts',
+ \OCP\Util::writeLog('contacts',
__METHOD__.', exception deleting vCard '.$card['id'].': '
. $e->getMessage(),
- OCP\Util::ERROR);
+ \OCP\Util::ERROR);
}
}
try {
- $stmt = OCP\DB::prepare('DELETE FROM `*PREFIX*contacts_addressbooks` WHERE `id` = ?');
+ $stmt = \OCP\DB::prepare('DELETE FROM `*PREFIX*contacts_addressbooks` WHERE `id` = ?');
$stmt->execute(array($id));
- } catch(Exception $e) {
- OCP\Util::writeLog('contacts',
+ } catch(\Exception $e) {
+ \OCP\Util::writeLog('contacts',
__METHOD__.', exception for ' . $id . ': '
. $e->getMessage(),
- OCP\Util::ERROR);
+ \OCP\Util::ERROR);
throw new Exception(
- OC_Contacts_App::$l10n->t(
+ App::$l10n->t(
'There was an error deleting this addressbook.'
)
);
}
- OCP\Share::unshareAll('addressbook', $id);
+ \OCP\Share::unshareAll('addressbook', $id);
- if(count(self::all(OCP\User::getUser())) == 0) {
+ if(count(self::all(\OCP\User::getUser())) == 0) {
self::addDefault();
}
@@ -361,7 +400,7 @@ class OC_Contacts_Addressbook {
* @return boolean
*/
public static function touch($id) {
- $stmt = OCP\DB::prepare( 'UPDATE `*PREFIX*contacts_addressbooks` SET `ctag` = `ctag` + 1 WHERE `id` = ?' );
+ $stmt = \OCP\DB::prepare( 'UPDATE `*PREFIX*contacts_addressbooks` SET `ctag` = `ctag` + 1 WHERE `id` = ?' );
$stmt->execute(array($id));
return true;
@@ -389,7 +428,7 @@ class OC_Contacts_Addressbook {
* @return string
*/
public static function extractUserID($principaluri) {
- list($prefix, $userid) = Sabre_DAV_URLUtil::splitPath($principaluri);
+ list($prefix, $userid) = \Sabre_DAV_URLUtil::splitPath($principaluri);
return $userid;
}
}
diff --git a/lib/app.php b/lib/app.php
index a51e4832..a6fcb5af 100644
--- a/lib/app.php
+++ b/lib/app.php
@@ -6,11 +6,16 @@
* See the COPYING-README file.
*/
+namespace OCA\Contacts;
+
+use Sabre\VObject;
+
/**
* This class manages our app actions
*/
-OC_Contacts_App::$l10n = OC_L10N::get('contacts');
-class OC_Contacts_App {
+App::$l10n = \OC_L10N::get('contacts');
+
+class App {
/*
* @brief language object for calendar app
*/
@@ -21,84 +26,51 @@ class OC_Contacts_App {
*/
public static $categories = null;
- public static function getAddressbook($id) {
- // TODO: Throw an exception instead of returning json.
- $addressbook = OC_Contacts_Addressbook::find( $id );
- if($addressbook === false || $addressbook['userid'] != OCP\USER::getUser()) {
- if ($addressbook === false) {
- OCP\Util::writeLog('contacts',
- 'Addressbook not found: '. $id,
- OCP\Util::ERROR);
- //throw new Exception('Addressbook not found: '. $id);
- OCP\JSON::error(
- array(
- 'data' => array(
- 'message' => self::$l10n->t('Addressbook not found: ' . $id)
- )
- )
- );
- } else {
- $sharedAddressbook = OCP\Share::getItemSharedWithBySource('addressbook', $id, OC_Share_Backend_Addressbook::FORMAT_ADDRESSBOOKS);
- if ($sharedAddressbook) {
- return $sharedAddressbook[0];
- } else {
- OCP\Util::writeLog('contacts',
- 'Addressbook('.$id.') is not from '.OCP\USER::getUser(),
- OCP\Util::ERROR);
- //throw new Exception('This is not your addressbook.');
- OCP\JSON::error(
- array(
- 'data' => array(
- 'message' => self::$l10n->t('This is not your addressbook.')
- )
- )
- );
- }
- }
- }
- return $addressbook;
- }
-
- public static function getContactObject($id) {
- $card = OC_Contacts_VCard::find( $id );
- if( $card === false ) {
- OCP\Util::writeLog('contacts',
- 'Contact could not be found: '.$id,
- OCP\Util::ERROR);
- OCP\JSON::error(
- array(
- 'data' => array(
- 'message' => self::$l10n->t('Contact could not be found.')
- .' '.print_r($id, true)
- )
- )
- );
- exit();
- }
-
- self::getAddressbook( $card['addressbookid'] );//access check
- return $card;
- }
+ /**
+ * Properties there can be more than one of.
+ */
+ public static $multi_properties = array('EMAIL', 'TEL', 'IMPP', 'ADR', 'URL');
/**
- * @brief Gets the VCard as an OC_VObject
- * @returns The card or null if the card could not be parsed.
+ * Properties to index.
+ */
+ public static $index_properties = array('N', 'FN', 'NICKNAME', 'ORG', 'CATEGORIES', 'EMAIL', 'TEL', 'IMPP', 'ADR', 'URL', 'GEO', 'PHOTO');
+
+ const THUMBNAIL_PREFIX = 'contact-thumbnail-';
+ const THUMBNAIL_SIZE = 28;
+
+ /**
+ * @brief Gets the VCard as a Sabre\VObject\Component
+ * @returns Sabre\VObject\Component|null The card or null if the card could not be parsed.
*/
public static function getContactVCard($id) {
- $card = self::getContactObject( $id );
+ $card = null;
+ $vcard = null;
+ try {
+ $card = VCard::find($id);
+ } catch(Exception $e) {
+ return null;
+ }
+
+ try {
+ $vcard = \Sabre\VObject\Reader::read($card['carddata']);
+ } catch(Exception $e) {
+ \OCP\Util::writeLog('contacts', __METHOD__.', exception: ' . $e->getMessage(), \OCP\Util::ERROR);
+ \OCP\Util::writeLog('contacts', __METHOD__.', id: ' . $id, \OCP\Util::DEBUG);
+ return null;
+ }
- $vcard = OC_VObject::parse($card['carddata']);
if (!is_null($vcard) && !isset($vcard->REV)) {
- $rev = new DateTime('@'.$card['lastmodified']);
- $vcard->setString('REV', $rev->format(DateTime::W3C));
+ $rev = new \DateTime('@'.$card['lastmodified']);
+ $vcard->REV = $rev->format(\DateTime::W3C);
}
return $vcard;
}
public static function getPropertyLineByChecksum($vcard, $checksum) {
$line = null;
- for($i=0;$ichildren);$i++) {
- if(md5($vcard->children[$i]->serialize()) == $checksum ) {
+ foreach($vcard->children as $i => $property) {
+ if(substr(md5($property->serialize()), 0, 8) == $checksum ) {
$line = $i;
break;
}
@@ -213,6 +185,7 @@ class OC_Contacts_App {
'WORK' => $l->t('Work'),
'HOME' => $l->t('Home'),
'INTERNET' => $l->t('Internet'),
+ 'OTHER' => $l->t('Other'),
);
}
}
@@ -221,9 +194,12 @@ class OC_Contacts_App {
* @brief returns the vcategories object of the user
* @return (object) $vcategories
*/
- protected static function getVCategories() {
+ public static function getVCategories() {
if (is_null(self::$categories)) {
- self::$categories = new OC_VCategories('contacts',
+ if(\OC_VCategories::isEmpty('contact')) {
+ self::scanCategories();
+ }
+ self::$categories = new \OC_VCategories('contact',
null,
self::getDefaultCategories());
}
@@ -234,12 +210,8 @@ class OC_Contacts_App {
* @brief returns the categories for the user
* @return (Array) $categories
*/
- public static function getCategories() {
- $categories = self::getVCategories()->categories();
- if(count($categories) == 0) {
- self::scanCategories();
- $categories = self::$categories->categories();
- }
+ public static function getCategories($format = null) {
+ $categories = self::getVCategories()->categories($format);
return ($categories ? $categories : self::getDefaultCategories());
}
@@ -249,21 +221,10 @@ class OC_Contacts_App {
*/
public static function getDefaultCategories() {
return array(
- (string)self::$l10n->t('Birthday'),
- (string)self::$l10n->t('Business'),
- (string)self::$l10n->t('Call'),
- (string)self::$l10n->t('Clients'),
- (string)self::$l10n->t('Deliverer'),
- (string)self::$l10n->t('Holidays'),
- (string)self::$l10n->t('Ideas'),
- (string)self::$l10n->t('Journey'),
- (string)self::$l10n->t('Jubilee'),
- (string)self::$l10n->t('Meeting'),
- (string)self::$l10n->t('Other'),
- (string)self::$l10n->t('Personal'),
- (string)self::$l10n->t('Projects'),
- (string)self::$l10n->t('Questions'),
+ (string)self::$l10n->t('Friends'),
+ (string)self::$l10n->t('Family'),
(string)self::$l10n->t('Work'),
+ (string)self::$l10n->t('Other'),
);
}
@@ -273,26 +234,29 @@ class OC_Contacts_App {
*/
public static function scanCategories($vccontacts = null) {
if (is_null($vccontacts)) {
- $vcaddressbooks = OC_Contacts_Addressbook::all(OCP\USER::getUser());
+ $vcaddressbooks = Addressbook::all(\OCP\USER::getUser());
if(count($vcaddressbooks) > 0) {
$vcaddressbookids = array();
foreach($vcaddressbooks as $vcaddressbook) {
- $vcaddressbookids[] = $vcaddressbook['id'];
+ if($vcaddressbook['userid'] === \OCP\User::getUser()) {
+ $vcaddressbookids[] = $vcaddressbook['id'];
+ }
}
$start = 0;
$batchsize = 10;
+ $categories = new \OC_VCategories('contact');
while($vccontacts =
- OC_Contacts_VCard::all($vcaddressbookids, $start, $batchsize)) {
+ VCard::all($vcaddressbookids, $start, $batchsize)) {
$cards = array();
foreach($vccontacts as $vccontact) {
- $cards[] = $vccontact['carddata'];
+ $cards[] = array($vccontact['id'], $vccontact['carddata']);
}
- OCP\Util::writeLog('contacts',
+ \OCP\Util::writeLog('contacts',
__CLASS__.'::'.__METHOD__
.', scanning: '.$batchsize.' starting from '.$start,
- OCP\Util::DEBUG);
+ \OCP\Util::DEBUG);
// only reset on first batch.
- self::getVCategories()->rescan($cards,
+ $categories->rescan($cards,
true,
($start == 0 ? true : false));
$start += $batchsize;
@@ -305,27 +269,122 @@ class OC_Contacts_App {
* check VCard for new categories.
* @see OC_VCategories::loadFromVObject
*/
- public static function loadCategoriesFromVCard(OC_VObject $contact) {
- self::getVCategories()->loadFromVObject($contact, true);
+ public static function loadCategoriesFromVCard($id, $contact) {
+ if(!$contact instanceof \OC_VObject) {
+ $contact = new \OC_VObject($contact);
+ }
+ self::getVCategories()->loadFromVObject($id, $contact, true);
}
/**
* @brief Get the last modification time.
- * @param $vcard OC_VObject
- * $return DateTime | null
+ * @param OC_VObject|Sabre\VObject\Component|integer $contact
+ * @returns DateTime | null
*/
- public static function lastModified($vcard) {
- $rev = $vcard->getAsString('REV');
- if ($rev) {
- return DateTime::createFromFormat(DateTime::W3C, $rev);
+ public static function lastModified($contact) {
+ if(is_numeric($contact)) {
+ $card = VCard::find($contact);
+ return ($card ? new \DateTime('@' . $card['lastmodified']) : null);
+ } elseif($contact instanceof \OC_VObject || $contact instanceof VObject\Component) {
+ return isset($contact->REV)
+ ? \DateTime::createFromFormat(\DateTime::W3C, $contact->REV)
+ : null;
}
}
- public static function setLastModifiedHeader($contact) {
- $rev = $contact->getAsString('REV');
- if ($rev) {
- $rev = DateTime::createFromFormat(DateTime::W3C, $rev);
- OCP\Response::setLastModifiedHeader($rev);
+ public static function cacheThumbnail($id, \OC_Image $image = null) {
+ if(\OC_Cache::hasKey(self::THUMBNAIL_PREFIX . $id)) {
+ return \OC_Cache::get(self::THUMBNAIL_PREFIX . $id);
+ }
+ if(is_null($image)) {
+ $vcard = self::getContactVCard($id);
+
+ // invalid vcard
+ if(is_null($vcard)) {
+ \OCP\Util::writeLog('contacts',
+ __METHOD__.' The VCard for ID ' . $id . ' is not RFC compatible',
+ \OCP\Util::ERROR);
+ return false;
+ }
+ $image = new \OC_Image();
+ if(!isset($vcard->PHOTO)) {
+ return false;
+ }
+ if(!$image->loadFromBase64((string)$vcard->PHOTO)) {
+ return false;
+ }
+ }
+ if(!$image->centerCrop()) {
+ \OCP\Util::writeLog('contacts',
+ 'thumbnail.php. Couldn\'t crop thumbnail for ID ' . $id,
+ \OCP\Util::ERROR);
+ return false;
+ }
+ if(!$image->resize(self::THUMBNAIL_SIZE)) {
+ \OCP\Util::writeLog('contacts',
+ 'thumbnail.php. Couldn\'t resize thumbnail for ID ' . $id,
+ \OCP\Util::ERROR);
+ return false;
+ }
+ // Cache for around a month
+ \OC_Cache::set(self::THUMBNAIL_PREFIX . $id, $image->data(), 3000000);
+ \OCP\Util::writeLog('contacts', 'Caching ' . $id, \OCP\Util::DEBUG);
+ return \OC_Cache::get(self::THUMBNAIL_PREFIX . $id);
+ }
+
+ public static function updateDBProperties($contactid, $vcard = null) {
+ $stmt = \OCP\DB::prepare('DELETE FROM `*PREFIX*contacts_cards_properties` WHERE `contactid` = ?');
+ try {
+ $stmt->execute(array($contactid));
+ } catch(\Exception $e) {
+ \OCP\Util::writeLog('contacts', __METHOD__.
+ ', exception: ' . $e->getMessage(), \OCP\Util::ERROR);
+ \OCP\Util::writeLog('contacts', __METHOD__.', id: '
+ . $id, \OCP\Util::DEBUG);
+ throw new \Exception(
+ App::$l10n->t(
+ 'There was an error deleting properties for this contact.'
+ )
+ );
+ }
+
+ if(is_null($vcard)) {
+ return;
+ }
+
+ $stmt = \OCP\DB::prepare( 'INSERT INTO `*PREFIX*contacts_cards_properties` '
+ . '(`userid`, `contactid`,`name`,`value`,`preferred`) VALUES(?,?,?,?,?)' );
+ foreach($vcard->children as $property) {
+ if(!in_array($property->name, self::$index_properties)) {
+ continue;
+ }
+ $preferred = false;
+ foreach($property->parameters as $parameter) {
+ if($parameter->name == 'TYPE' && strtoupper($parameter->value) == 'PREF') {
+ $preferred = true;
+ break;
+ }
+ }
+ try {
+ $result = $stmt->execute(
+ array(
+ \OCP\User::getUser(),
+ $contactid,
+ $property->name,
+ $property->value,
+ $preferred,
+ )
+ );
+ if (\OC_DB::isError($result)) {
+ \OCP\Util::writeLog('contacts', __METHOD__. 'DB error: '
+ . \OC_DB::getErrorMessage($result), \OC_Log::ERROR);
+ return false;
+ }
+ } catch(\Exception $e) {
+ \OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR);
+ \OCP\Util::writeLog('contacts', __METHOD__.', aid: '.$aid.' uri'.$uri, \OCP\Util::DEBUG);
+ return false;
+ }
}
}
}
diff --git a/lib/hooks.php b/lib/hooks.php
index 291f874f..15596088 100644
--- a/lib/hooks.php
+++ b/lib/hooks.php
@@ -23,23 +23,27 @@
/**
* The following signals are being emitted:
*
- * OC_Contacts_VCard::post_moveToAddressbook(array('aid' => $aid, 'id' => $id))
- * OC_Contacts_VCard::pre_deleteVCard(array('aid' => $aid, 'id' => $id, 'uri' = $uri)); (NOTE: the values can be null depending on which method emits them)
- * OC_Contacts_VCard::post_updateVCard($id)
- * OC_Contacts_VCard::post_createVCard($newid)
+ * OCA\Contacts\VCard::post_moveToAddressbook(array('aid' => $aid, 'id' => $id))
+ * OCA\Contacts\VCard::pre_deleteVCard(array('aid' => $aid, 'id' => $id, 'uri' = $uri)); (NOTE: the values can be null depending on which method emits them)
+ * OCA\Contacts\VCard::post_updateVCard($id)
+ * OCA\Contacts\VCard::post_createVCard($newid)
*/
+namespace OCA\Contacts;
+
+use \Sabre\VObject;
+
/**
* This class contains all hooks.
*/
-class OC_Contacts_Hooks{
+class Hooks{
/**
* @brief Add default Addressbook for a certain user
* @param paramters parameters from postCreateUser-Hook
* @return array
*/
static public function createUser($parameters) {
- OC_Contacts_Addressbook::addDefault($parameters['uid']);
+ Addressbook::addDefault($parameters['uid']);
return true;
}
@@ -49,18 +53,18 @@ class OC_Contacts_Hooks{
* @return array
*/
static public function deleteUser($parameters) {
- $addressbooks = OC_Contacts_Addressbook::all($parameters['uid']);
+ $addressbooks = Addressbook::all($parameters['uid']);
foreach($addressbooks as $addressbook) {
- OC_Contacts_Addressbook::delete($addressbook['id']);
+ Addressbook::delete($addressbook['id']);
}
return true;
}
static public function getCalenderSources($parameters) {
- $base_url = OCP\Util::linkTo('calendar', 'ajax/events.php').'?calendar_id=';
- foreach(OC_Contacts_Addressbook::all(OCP\USER::getUser()) as $addressbook) {
+ $base_url = \OCP\Util::linkTo('calendar', 'ajax/events.php').'?calendar_id=';
+ foreach(Addressbook::all(\OCP\USER::getUser()) as $addressbook) {
$parameters['sources'][]
= array(
'url' => $base_url.'birthday_'. $addressbook['id'],
@@ -80,26 +84,28 @@ class OC_Contacts_Hooks{
}
$info = explode('_', $name);
$aid = $info[1];
- OC_Contacts_App::getAddressbook($aid);
- foreach(OC_Contacts_VCard::all($aid) as $card) {
- $vcard = OC_VObject::parse($card['carddata']);
- if (!$vcard) {
+ Addressbook::find($aid);
+ foreach(VCard::all($aid) as $contact) {
+ try {
+ $vcard = VObject\Reader::read($contact['carddata']);
+ } catch (Exception $e) {
continue;
}
$birthday = $vcard->BDAY;
if ($birthday) {
- $date = new DateTime($birthday);
- $vevent = new OC_VObject('VEVENT');
+ $date = new \DateTime($birthday);
+ $vevent = VObject\Component::create('VEVENT');
//$vevent->setDateTime('LAST-MODIFIED', new DateTime($vcard->REV));
- $vevent->setDateTime('DTSTART', $date,
- Sabre\VObject\Property\DateTime::DATE);
- $vevent->setString('DURATION', 'P1D');
- $vevent->setString('UID', substr(md5(rand().time()), 0, 10));
+ $vevent->add('DTSTART');
+ $vevent->DTSTART->setDateTime($date,
+ VObject\Property\DateTime::DATE);
+ $vevent->add('DURATION', 'P1D');
+ $vevent->{'UID'} = substr(md5(rand().time()), 0, 10);
// DESCRIPTION?
- $vevent->setString('RRULE', 'FREQ=YEARLY');
+ $vevent->{'RRULE'} = 'FREQ=YEARLY';
$title = str_replace('{name}',
- $vcard->getAsString('FN'),
- OC_Contacts_App::$l10n->t('{name}\'s Birthday'));
+ $vcard->FN,
+ App::$l10n->t('{name}\'s Birthday'));
$parameters['events'][] = array(
'id' => 0,//$card['id'],
'vevent' => $vevent,
@@ -107,7 +113,7 @@ class OC_Contacts_Hooks{
'summary' => $title,
'calendardata' => "BEGIN:VCALENDAR\nVERSION:2.0\n"
. "PRODID:ownCloud Contacts "
- . OCP\App::getAppVersion('contacts') . "\n"
+ . \OCP\App::getAppVersion('contacts') . "\n"
. $vevent->serialize() . "END:VCALENDAR"
);
}
diff --git a/lib/sabre/addressbook.php b/lib/sabre/addressbook.php
index e9eb7747..a3e92166 100644
--- a/lib/sabre/addressbook.php
+++ b/lib/sabre/addressbook.php
@@ -68,14 +68,14 @@ class OC_Connector_Sabre_CardDAV_AddressBook extends Sabre_CardDAV_AddressBook {
$readprincipal = $this->getOwner();
$writeprincipal = $this->getOwner();
- $uid = OC_Contacts_Addressbook::extractUserID($this->getOwner());
+ $uid = OCA\Contacts\Addressbook::extractUserID($this->getOwner());
if($uid != OCP\USER::getUser()) {
$sharedAddressbook = OCP\Share::getItemSharedWithBySource('addressbook', $this->addressBookInfo['id']);
- if ($sharedAddressbook && ($sharedAddressbook['permissions'] & OCP\Share::PERMISSION_READ)) {
+ if ($sharedAddressbook && ($sharedAddressbook['permissions'] & OCP\PERMISSION_READ)) {
$readprincipal = 'principals/' . OCP\USER::getUser();
}
- if ($sharedAddressbook && ($sharedAddressbook['permissions'] & OCP\Share::PERMISSION_UPDATE)) {
+ if ($sharedAddressbook && ($sharedAddressbook['permissions'] & OCP\PERMISSION_UPDATE)) {
$writeprincipal = 'principals/' . OCP\USER::getUser();
}
}
diff --git a/lib/sabre/backend.php b/lib/sabre/backend.php
index 8951ae0b..c3a09bc0 100644
--- a/lib/sabre/backend.php
+++ b/lib/sabre/backend.php
@@ -31,7 +31,7 @@ class OC_Connector_Sabre_CardDAV extends Sabre_CardDAV_Backend_Abstract {
* @return array
*/
public function getAddressBooksForUser($principaluri) {
- $data = OC_Contacts_Addressbook::allWherePrincipalURIIs($principaluri);
+ $data = OCA\Contacts\Addressbook::allWherePrincipalURIIs($principaluri);
$addressbooks = array();
foreach($data as $i) {
@@ -84,7 +84,7 @@ class OC_Connector_Sabre_CardDAV extends Sabre_CardDAV_Backend_Abstract {
}
}
- OC_Contacts_Addressbook::edit($addressbookid, $name, $description);
+ OCA\Contacts\Addressbook::edit($addressbookid, $name, $description);
return true;
@@ -120,7 +120,7 @@ class OC_Connector_Sabre_CardDAV extends Sabre_CardDAV_Backend_Abstract {
}
- OC_Contacts_Addressbook::addFromDAVData(
+ OCA\Contacts\Addressbook::addFromDAVData(
$principaluri,
$url,
$name,
@@ -135,7 +135,7 @@ class OC_Connector_Sabre_CardDAV extends Sabre_CardDAV_Backend_Abstract {
* @return void
*/
public function deleteAddressBook($addressbookid) {
- OC_Contacts_Addressbook::delete($addressbookid);
+ OCA\Contacts\Addressbook::delete($addressbookid);
}
/**
@@ -145,7 +145,7 @@ class OC_Connector_Sabre_CardDAV extends Sabre_CardDAV_Backend_Abstract {
* @return array
*/
public function getCards($addressbookid) {
- $data = OC_Contacts_VCard::all($addressbookid);
+ $data = OCA\Contacts\VCard::all($addressbookid);
$cards = array();
foreach($data as $i) {
//OCP\Util::writeLog('contacts', __METHOD__.', uri: ' . $i['uri'], OCP\Util::DEBUG);
@@ -169,7 +169,7 @@ class OC_Connector_Sabre_CardDAV extends Sabre_CardDAV_Backend_Abstract {
* @return array
*/
public function getCard($addressbookid, $carduri) {
- return OC_Contacts_VCard::findWhereDAVDataIs($addressbookid, $carduri);
+ return OCA\Contacts\VCard::findWhereDAVDataIs($addressbookid, $carduri);
}
@@ -182,7 +182,7 @@ class OC_Connector_Sabre_CardDAV extends Sabre_CardDAV_Backend_Abstract {
* @return bool
*/
public function createCard($addressbookid, $carduri, $carddata) {
- OC_Contacts_VCard::addFromDAVData($addressbookid, $carduri, $carddata);
+ OCA\Contacts\VCard::addFromDAVData($addressbookid, $carduri, $carddata);
return true;
}
@@ -195,7 +195,7 @@ class OC_Connector_Sabre_CardDAV extends Sabre_CardDAV_Backend_Abstract {
* @return bool
*/
public function updateCard($addressbookid, $carduri, $carddata) {
- return OC_Contacts_VCard::editFromDAVData(
+ return OCA\Contacts\VCard::editFromDAVData(
$addressbookid, $carduri, $carddata
);
}
@@ -208,6 +208,6 @@ class OC_Connector_Sabre_CardDAV extends Sabre_CardDAV_Backend_Abstract {
* @return bool
*/
public function deleteCard($addressbookid, $carduri) {
- return OC_Contacts_VCard::deleteFromDAVData($addressbookid, $carduri);
+ return OCA\Contacts\VCard::deleteFromDAVData($addressbookid, $carduri);
}
}
diff --git a/lib/sabre/card.php b/lib/sabre/card.php
index f4414a25..1d5f780a 100644
--- a/lib/sabre/card.php
+++ b/lib/sabre/card.php
@@ -63,14 +63,14 @@ class OC_Connector_Sabre_CardDAV_Card extends Sabre_CardDAV_Card {
$readprincipal = $this->getOwner();
$writeprincipal = $this->getOwner();
- $uid = OC_Contacts_Addressbook::extractUserID($this->getOwner());
+ $uid = OCA\Contacts\Addressbook::extractUserID($this->getOwner());
if($uid != OCP\USER::getUser()) {
$sharedAddressbook = OCP\Share::getItemSharedWithBySource('addressbook', $this->addressBookInfo['id']);
- if ($sharedAddressbook && ($sharedAddressbook['permissions'] & OCP\Share::PERMISSION_READ)) {
+ if ($sharedAddressbook && ($sharedAddressbook['permissions'] & OCP\PERMISSION_READ)) {
$readprincipal = 'principals/' . OCP\USER::getUser();
}
- if ($sharedAddressbook && ($sharedAddressbook['permissions'] & OCP\Share::PERMISSION_UPDATE)) {
+ if ($sharedAddressbook && ($sharedAddressbook['permissions'] & OCP\PERMISSION_UPDATE)) {
$writeprincipal = 'principals/' . OCP\USER::getUser();
}
}
diff --git a/lib/search.php b/lib/search.php
index 97154c0e..8a5ad7a6 100644
--- a/lib/search.php
+++ b/lib/search.php
@@ -1,18 +1,22 @@
0) {
- $link = OCP\Util::linkTo('contacts', 'index.php').'?id='.urlencode($vcard['id']);
- $results[]=new OC_Search_Result($vcard['fullname'], '', $link, (string)$l->t('Contact'));//$name,$text,$link,$type
+ //$link = \OCP\Util::linkTo('contacts', 'index.php').'?id='.urlencode($vcard['id']);
+ $link = 'javascript:openContact(' . $vcard['id'] . ')';
+ $results[]=new \OC_Search_Result($vcard['fullname'], '', $link, (string)$l->t('Contact'));//$name,$text,$link,$type
}
}
}
diff --git a/lib/share/addressbook.php b/lib/share/addressbook.php
index ef2f4cd6..f90c1e5c 100644
--- a/lib/share/addressbook.php
+++ b/lib/share/addressbook.php
@@ -6,7 +6,9 @@
* See the COPYING-README file.
*/
-class OC_Share_Backend_Addressbook implements OCP\Share_Backend_Collection {
+namespace OCA\Contacts;
+
+class Share_Backend_Addressbook implements \OCP\Share_Backend_Collection {
const FORMAT_ADDRESSBOOKS = 1;
/**
@@ -21,7 +23,7 @@ class OC_Share_Backend_Addressbook implements OCP\Share_Backend_Collection {
* The formatItems() function will translate the source returned back into the item
*/
public function isValidSource($itemSource, $uidOwner) {
- $addressbook = OC_Contacts_Addressbook::find( $itemSource );
+ $addressbook = Addressbook::find( $itemSource );
if( $addressbook === false || $addressbook['userid'] != $uidOwner) {
return false;
}
@@ -39,9 +41,9 @@ class OC_Share_Backend_Addressbook implements OCP\Share_Backend_Collection {
* If it does generate a new name e.g. name_#
*/
public function generateTarget($itemSource, $shareWith, $exclude = null) {
- $addressbook = OC_Contacts_Addressbook::find( $itemSource );
+ $addressbook = Addressbook::find( $itemSource );
$user_addressbooks = array();
- foreach(OC_Contacts_Addressbook::all($shareWith) as $user_addressbook) {
+ foreach(Addressbook::all($shareWith) as $user_addressbook) {
$user_addressbooks[] = $user_addressbook['displayname'];
}
$name = $addressbook['displayname'];
@@ -70,7 +72,7 @@ class OC_Share_Backend_Addressbook implements OCP\Share_Backend_Collection {
$addressbooks = array();
if ($format == self::FORMAT_ADDRESSBOOKS) {
foreach ($items as $item) {
- $addressbook = OC_Contacts_Addressbook::find($item['item_source']);
+ $addressbook = Addressbook::find($item['item_source']);
if ($addressbook) {
$addressbook['displayname'] = $item['item_target'];
$addressbook['permissions'] = $item['permissions'];
@@ -82,7 +84,7 @@ class OC_Share_Backend_Addressbook implements OCP\Share_Backend_Collection {
}
public function getChildren($itemSource) {
- $query = OCP\DB::prepare('SELECT `id`, `fullname` FROM `*PREFIX*contacts_cards` WHERE `addressbookid` = ?');
+ $query = \OCP\DB::prepare('SELECT `id`, `fullname` FROM `*PREFIX*contacts_cards` WHERE `addressbookid` = ?');
$result = $query->execute(array($itemSource));
$children = array();
while ($contact = $result->fetchRow()) {
diff --git a/lib/share/contact.php b/lib/share/contact.php
index 2643a338..f415bf28 100644
--- a/lib/share/contact.php
+++ b/lib/share/contact.php
@@ -19,14 +19,16 @@
* License along with this library. If not, see .
*/
-class OC_Share_Backend_Contact implements OCP\Share_Backend {
+namespace OCA\Contacts;
+
+class Share_Backend_Contact implements \OCP\Share_Backend {
const FORMAT_CONTACT = 0;
private static $contact;
public function isValidSource($itemSource, $uidOwner) {
- self::$contact = OC_Contacts_VCard::find($itemSource);
+ self::$contact = VCard::find($itemSource);
if (self::$contact) {
return true;
}
@@ -42,7 +44,7 @@ class OC_Share_Backend_Contact implements OCP\Share_Backend {
$contacts = array();
if ($format == self::FORMAT_CONTACT) {
foreach ($items as $item) {
- $contact = OC_Contacts_VCard::find($item['item_source']);
+ $contact = VCard::find($item['item_source']);
$contact['fullname'] = $item['item_target'];
$contacts[] = $contact;
}
diff --git a/lib/vcard.php b/lib/vcard.php
index ed644973..a5385402 100644
--- a/lib/vcard.php
+++ b/lib/vcard.php
@@ -35,10 +35,14 @@
* );
*/
+namespace OCA\Contacts;
+
+use Sabre\VObject;
+
/**
* This class manages our vCards
*/
-class OC_Contacts_VCard {
+class VCard {
/**
* @brief Returns all cards of an address book
* @param integer $id
@@ -53,26 +57,36 @@ class OC_Contacts_VCard {
$id_sql = join(',', array_fill(0, count($id), '?'));
$sql = 'SELECT * FROM `*PREFIX*contacts_cards` WHERE `addressbookid` IN ('.$id_sql.') ORDER BY `fullname`';
try {
- $stmt = OCP\DB::prepare($sql, $num, $start);
+ $stmt = \OCP\DB::prepare($sql, $num, $start);
$result = $stmt->execute($id);
- } catch(Exception $e) {
- OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
- OCP\Util::writeLog('contacts', __METHOD__.', ids: '.join(',', $id), OCP\Util::DEBUG);
- OCP\Util::writeLog('contacts', __METHOD__.'SQL:'.$sql, OCP\Util::DEBUG);
+ if (\OC_DB::isError($result)) {
+ \OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
+ return false;
+ }
+ } catch(\Exception $e) {
+ \OCP\Util::writeLog('contacts', __METHOD__.', exception: ' . $e->getMessage(), \OCP\Util::ERROR);
+ \OCP\Util::writeLog('contacts', __METHOD__.', ids: ' . join(',', $id), \OCP\Util::DEBUG);
+ \OCP\Util::writeLog('contacts', __METHOD__.'SQL:' . $sql, \OCP\Util::DEBUG);
return false;
}
} elseif(is_int($id) || is_string($id)) {
try {
$sql = 'SELECT * FROM `*PREFIX*contacts_cards` WHERE `addressbookid` = ? ORDER BY `fullname`';
- $stmt = OCP\DB::prepare($sql, $num, $start);
+ $stmt = \OCP\DB::prepare($sql, $num, $start);
$result = $stmt->execute(array($id));
- } catch(Exception $e) {
- OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
- OCP\Util::writeLog('contacts', __METHOD__.', ids: '. $id, OCP\Util::DEBUG);
+ if (\OC_DB::isError($result)) {
+ \OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
+ return false;
+ }
+ } catch(\Exception $e) {
+ \OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR);
+ \OCP\Util::writeLog('contacts', __METHOD__.', ids: '. $id, \OCP\Util::DEBUG);
return false;
}
} else {
- OCP\Util::writeLog('contacts', __METHOD__.'. Addressbook id(s) argument is empty: '. print_r($id, true), OCP\Util::DEBUG);
+ \OCP\Util::writeLog('contacts', __METHOD__
+ . '. Addressbook id(s) argument is empty: '
+ . print_r($id, true), \OCP\Util::DEBUG);
return false;
}
$cards = array();
@@ -92,11 +106,15 @@ class OC_Contacts_VCard {
*/
public static function find($id) {
try {
- $stmt = OCP\DB::prepare( 'SELECT * FROM `*PREFIX*contacts_cards` WHERE `id` = ?' );
+ $stmt = \OCP\DB::prepare( 'SELECT * FROM `*PREFIX*contacts_cards` WHERE `id` = ?' );
$result = $stmt->execute(array($id));
- } catch(Exception $e) {
- OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
- OCP\Util::writeLog('contacts', __METHOD__.', id: '. $id, OCP\Util::DEBUG);
+ if (\OC_DB::isError($result)) {
+ \OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
+ return false;
+ }
+ } catch(\Exception $e) {
+ \OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR);
+ \OCP\Util::writeLog('contacts', __METHOD__.', id: '. $id, \OCP\Util::DEBUG);
return false;
}
@@ -111,11 +129,15 @@ class OC_Contacts_VCard {
*/
public static function findWhereDAVDataIs($aid,$uri) {
try {
- $stmt = OCP\DB::prepare( 'SELECT * FROM `*PREFIX*contacts_cards` WHERE `addressbookid` = ? AND `uri` = ?' );
+ $stmt = \OCP\DB::prepare( 'SELECT * FROM `*PREFIX*contacts_cards` WHERE `addressbookid` = ? AND `uri` = ?' );
$result = $stmt->execute(array($aid,$uri));
- } catch(Exception $e) {
- OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
- OCP\Util::writeLog('contacts', __METHOD__.', aid: '.$aid.' uri'.$uri, OCP\Util::DEBUG);
+ if (\OC_DB::isError($result)) {
+ \OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
+ return false;
+ }
+ } catch(\Exception $e) {
+ \OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR);
+ \OCP\Util::writeLog('contacts', __METHOD__.', aid: '.$aid.' uri'.$uri, \OCP\Util::DEBUG);
return false;
}
@@ -130,9 +152,9 @@ class OC_Contacts_VCard {
*/
public static function formatPropertyTypes(&$property) {
foreach($property->parameters as $key=>&$parameter) {
- $types = OC_Contacts_App::getTypesOfProperty($property->name);
+ $types = App::getTypesOfProperty($property->name);
if(is_array($types) && in_array(strtoupper($parameter->name), array_keys($types)) || strtoupper($parameter->name) == 'PREF') {
- $property->parameters[] = new Sabre\VObject\Parameter('TYPE', $parameter->name);
+ $property->parameters[] = new \Sabre\VObject\Parameter('TYPE', $parameter->name);
}
unset($property->parameters[$key]);
}
@@ -171,13 +193,17 @@ class OC_Contacts_VCard {
* @returns true if the UID has been changed.
*/
protected static function trueUID($aid, &$uid) {
- $stmt = OCP\DB::prepare( 'SELECT * FROM `*PREFIX*contacts_cards` WHERE `addressbookid` = ? AND `uri` = ?' );
+ $stmt = \OCP\DB::prepare( 'SELECT * FROM `*PREFIX*contacts_cards` WHERE `addressbookid` = ? AND `uri` = ?' );
$uri = $uid.'.vcf';
try {
$result = $stmt->execute(array($aid,$uri));
+ if (\OC_DB::isError($result)) {
+ \OCP\Util::writeLog('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
+ return false;
+ }
} catch(Exception $e) {
- OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
- OCP\Util::writeLog('contacts', __METHOD__.', aid: '.$aid.' uid'.$uid, OCP\Util::DEBUG);
+ \OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR);
+ \OCP\Util::writeLog('contacts', __METHOD__.', aid: '.$aid.' uid'.$uid, \OCP\Util::DEBUG);
return false;
}
if($result->numRows() > 0) {
@@ -200,47 +226,51 @@ class OC_Contacts_VCard {
/**
* @brief Tries to update imported VCards to adhere to rfc2426 (VERSION: 3.0) and add mandatory fields if missing.
* @param aid Address book id.
- * @param vcard An OC_VObject of type VCARD (passed by reference).
+ * @param vcard A Sabre\VObject\Component of type VCARD (passed by reference).
*/
protected static function updateValuesFromAdd($aid, &$vcard) { // any suggestions for a better method name? ;-)
$stringprops = array('N', 'FN', 'ORG', 'NICK', 'ADR', 'NOTE');
$typeprops = array('ADR', 'TEL', 'EMAIL');
$upgrade = false;
$fn = $n = $uid = $email = $org = null;
- $version = $vcard->getAsString('VERSION');
+ $version = isset($vcard->VERSION) ? $vcard->VERSION : null;
// Add version if needed
if($version && $version < '3.0') {
$upgrade = true;
- //OCP\Util::writeLog('contacts', 'OC_Contacts_VCard::updateValuesFromAdd. Updating from version: '.$version, OCP\Util::DEBUG);
+ //OCP\Util::writeLog('contacts', 'OCA\Contacts\VCard::updateValuesFromAdd. Updating from version: '.$version, OCP\Util::DEBUG);
}
foreach($vcard->children as &$property) {
// Decode string properties and remove obsolete properties.
if($upgrade && in_array($property->name, $stringprops)) {
self::decodeProperty($property);
}
- $property->value = str_replace("\r\n", "\n", iconv(mb_detect_encoding($property->value, 'UTF-8, ISO-8859-1'), 'utf-8', $property->value));
+ if(function_exists('iconv')) {
+ $property->value = str_replace("\r\n", "\n", iconv(mb_detect_encoding($property->value, 'UTF-8, ISO-8859-1'), 'utf-8', $property->value));
+ } else {
+ $property->value = str_replace("\r\n", "\n", mb_convert_encoding($property->value, 'UTF-8', mb_detect_encoding($property->value, 'UTF-8, ISO-8859-1'), $property->value));
+ }
if(in_array($property->name, $stringprops)) {
$property->value = strip_tags($property->value);
}
// Fix format of type parameters.
if($upgrade && in_array($property->name, $typeprops)) {
- //OCP\Util::writeLog('contacts', 'OC_Contacts_VCard::updateValuesFromAdd. before: '.$property->serialize(), OCP\Util::DEBUG);
+ //OCP\Util::writeLog('contacts', 'OCA\Contacts\VCard::updateValuesFromAdd. before: '.$property->serialize(), OCP\Util::DEBUG);
self::formatPropertyTypes($property);
- //OCP\Util::writeLog('contacts', 'OC_Contacts_VCard::updateValuesFromAdd. after: '.$property->serialize(), OCP\Util::DEBUG);
+ //OCP\Util::writeLog('contacts', 'OCA\Contacts\VCard::updateValuesFromAdd. after: '.$property->serialize(), OCP\Util::DEBUG);
}
if($property->name == 'FN') {
$fn = $property->value;
}
- if($property->name == 'N') {
+ else if($property->name == 'N') {
$n = $property->value;
}
- if($property->name == 'UID') {
+ else if($property->name == 'UID') {
$uid = $property->value;
}
- if($property->name == 'ORG') {
+ else if($property->name == 'ORG') {
$org = $property->value;
}
- if($property->name == 'EMAIL' && is_null($email)) { // only use the first email as substitute for missing N or FN.
+ else if($property->name == 'EMAIL' && is_null($email)) { // only use the first email as substitute for missing N or FN.
$email = $property->value;
}
}
@@ -255,8 +285,8 @@ class OC_Contacts_VCard {
} else {
$fn = 'Unknown Name';
}
- $vcard->setString('FN', $fn);
- //OCP\Util::writeLog('contacts', 'OC_Contacts_VCard::updateValuesFromAdd. Added missing \'FN\' field: '.$fn, OCP\Util::DEBUG);
+ $vcard->FN = $fn;
+ //OCP\Util::writeLog('contacts', 'OCA\Contacts\VCard::updateValuesFromAdd. Added missing \'FN\' field: '.$fn, OCP\Util::DEBUG);
}
if(!$n || $n == ';;;;') { // Fix missing 'N' field. Ugly hack ahead ;-)
$slice = array_reverse(array_slice(explode(' ', $fn), 0, 2)); // Take 2 first name parts of 'FN' and reverse.
@@ -264,82 +294,83 @@ class OC_Contacts_VCard {
$slice[] = "";
}
$n = implode(';', $slice).';;;';
- $vcard->setString('N', $n);
- //OCP\Util::writeLog('contacts', 'OC_Contacts_VCard::updateValuesFromAdd. Added missing \'N\' field: '.$n, OCP\Util::DEBUG);
+ $vcard->N = $n;
+ //OCP\Util::writeLog('contacts', 'OCA\Contacts\VCard::updateValuesFromAdd. Added missing \'N\' field: '.$n, OCP\Util::DEBUG);
}
if(!$uid) {
- $vcard->setUID();
- $uid = $vcard->getAsString('UID');
- //OCP\Util::writeLog('contacts', 'OC_Contacts_VCard::updateValuesFromAdd. Added missing \'UID\' field: '.$uid, OCP\Util::DEBUG);
+ $uid = substr(md5(rand().time()), 0, 10);
+ $vcard->add('UID', $uid);
+ //OCP\Util::writeLog('contacts', 'OCA\Contacts\VCard::updateValuesFromAdd. Added missing \'UID\' field: '.$uid, OCP\Util::DEBUG);
}
if(self::trueUID($aid, $uid)) {
- $vcard->setString('UID', $uid);
+ $vcard->{'UID'} = $uid;
}
- $now = new DateTime;
- $vcard->setString('REV', $now->format(DateTime::W3C));
+ $now = new \DateTime;
+ $vcard->{'REV'} = $now->format(\DateTime::W3C);
}
/**
* @brief Adds a card
* @param $aid integer Addressbook id
- * @param $card OC_VObject vCard file
+ * @param $card Sabre\VObject\Component vCard file
* @param $uri string the uri of the card, default based on the UID
* @param $isChecked boolean If the vCard should be checked for validity and version.
* @return insertid on success or false.
*/
- public static function add($aid, OC_VObject $card, $uri=null, $isChecked=false) {
+ public static function add($aid, VObject\Component $card, $uri=null, $isChecked=false) {
if(is_null($card)) {
- OCP\Util::writeLog('contacts', 'OC_Contacts_VCard::add. No vCard supplied', OCP\Util::ERROR);
+ \OCP\Util::writeLog('contacts', __METHOD__ . ', No vCard supplied', \OCP\Util::ERROR);
return null;
};
- $addressbook = OC_Contacts_Addressbook::find($aid);
- if ($addressbook['userid'] != OCP\User::getUser()) {
- $sharedAddressbook = OCP\Share::getItemSharedWithBySource('addressbook', $aid);
- if (!$sharedAddressbook || !($sharedAddressbook['permissions'] & OCP\Share::PERMISSION_CREATE)) {
- throw new Exception(
- OC_Contacts_App::$l10n->t(
+ $addressbook = Addressbook::find($aid);
+ if ($addressbook['userid'] != \OCP\User::getUser()) {
+ $sharedAddressbook = \OCP\Share::getItemSharedWithBySource('addressbook', $aid);
+ if (!$sharedAddressbook || !($sharedAddressbook['permissions'] & \OCP\PERMISSION_CREATE)) {
+ throw new \Exception(
+ App::$l10n->t(
'You do not have the permissions to add contacts to this addressbook.'
)
);
}
}
if(!$isChecked) {
- OC_Contacts_App::loadCategoriesFromVCard($card);
self::updateValuesFromAdd($aid, $card);
}
- $card->setString('VERSION', '3.0');
+ $card->{'VERSION'} = '3.0';
// Add product ID is missing.
- $prodid = trim($card->getAsString('PRODID'));
- if(!$prodid) {
- $appinfo = OCP\App::getAppInfo('contacts');
- $appversion = OCP\App::getAppVersion('contacts');
+ //$prodid = trim($card->getAsString('PRODID'));
+ //if(!$prodid) {
+ if(!isset($card->PRODID)) {
+ $appinfo = \OCP\App::getAppInfo('contacts');
+ $appversion = \OCP\App::getAppVersion('contacts');
$prodid = '-//ownCloud//NONSGML '.$appinfo['name'].' '.$appversion.'//EN';
- $card->setString('PRODID', $prodid);
+ $card->add('PRODID', $prodid);
}
- $fn = $card->getAsString('FN');
- if (empty($fn)) {
- $fn = '';
- }
+ $fn = isset($card->FN) ? $card->FN : '';
- if (!$uri) {
- $uid = $card->getAsString('UID');
- $uri = $uid.'.vcf';
- }
+ $uri = isset($uri) ? $uri : $card->UID . '.vcf';
$data = $card->serialize();
- $stmt = OCP\DB::prepare( 'INSERT INTO `*PREFIX*contacts_cards` (`addressbookid`,`fullname`,`carddata`,`uri`,`lastmodified`) VALUES(?,?,?,?,?)' );
+ $stmt = \OCP\DB::prepare( 'INSERT INTO `*PREFIX*contacts_cards` (`addressbookid`,`fullname`,`carddata`,`uri`,`lastmodified`) VALUES(?,?,?,?,?)' );
try {
- $result = $stmt->execute(array($aid,$fn,$data,$uri,time()));
- } catch(Exception $e) {
- OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
- OCP\Util::writeLog('contacts', __METHOD__.', aid: '.$aid.' uri'.$uri, OCP\Util::DEBUG);
+ $result = $stmt->execute(array($aid, $fn, $data, $uri, time()));
+ if (\OC_DB::isError($result)) {
+ \OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
+ return false;
+ }
+ } catch(\Exception $e) {
+ \OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR);
+ \OCP\Util::writeLog('contacts', __METHOD__.', aid: '.$aid.' uri'.$uri, \OCP\Util::DEBUG);
return false;
}
- $newid = OCP\DB::insertid('*PREFIX*contacts_cards');
+ $newid = \OCP\DB::insertid('*PREFIX*contacts_cards');
+ App::loadCategoriesFromVCard($newid, $card);
+ App::updateDBProperties($newid, $card);
+ App::cacheThumbnail($newid);
- OC_Contacts_Addressbook::touch($aid);
- OC_Hook::emit('OC_Contacts_VCard', 'post_createVCard', $newid);
+ Addressbook::touch($aid);
+ \OC_Hook::emit('\OCA\Contacts\VCard', 'post_createVCard', $newid);
return $newid;
}
@@ -348,11 +379,16 @@ class OC_Contacts_VCard {
* @param integer $id Addressbook id
* @param string $uri the uri the card will have
* @param string $data vCard file
- * @return insertid
+ * @returns integer|false insertid or false on error
*/
- public static function addFromDAVData($id,$uri,$data) {
- $card = OC_VObject::parse($data);
- return self::add($id, $card, $uri);
+ public static function addFromDAVData($id, $uri, $data) {
+ try {
+ $vcard = Sabre\VObject\Reader::read($data);
+ return self::add($id, $vcard, $uri);
+ } catch(\Exception $e) {
+ \OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR);
+ return false;
+ }
}
/**
@@ -360,31 +396,41 @@ class OC_Contacts_VCard {
* @param array $objects An array of [id, carddata].
*/
public static function updateDataByID($objects) {
- $stmt = OCP\DB::prepare( 'UPDATE `*PREFIX*contacts_cards` SET `carddata` = ?, `lastmodified` = ? WHERE `id` = ?' );
- $now = new DateTime;
+ $stmt = \OCP\DB::prepare( 'UPDATE `*PREFIX*contacts_cards` SET `carddata` = ?, `lastmodified` = ? WHERE `id` = ?' );
+ $now = new \DateTime;
foreach($objects as $object) {
- $vcard = OC_VObject::parse($object[1]);
+ $vcard = null;
+ try {
+ $vcard = Sabre\VObject\Reader::read($contact['carddata']);
+ } catch(\Exception $e) {
+ \OC_Log::write('contacts', __METHOD__. $e->getMessage(), \OCP\Util::ERROR);
+ }
if(!is_null($vcard)) {
$oldcard = self::find($object[0]);
if (!$oldcard) {
return false;
}
- $addressbook = OC_Contacts_Addressbook::find($oldcard['addressbookid']);
- if ($addressbook['userid'] != OCP\User::getUser()) {
- $sharedContact = OCP\Share::getItemSharedWithBySource('contact', $object[0], OCP\Share::FORMAT_NONE, null, true);
- if (!$sharedContact || !($sharedContact['permissions'] & OCP\Share::PERMISSION_UPDATE)) {
+
+ $addressbook = Addressbook::find($oldcard['addressbookid']);
+ if ($addressbook['userid'] != \OCP\User::getUser()) {
+ $sharedContact = \OCP\Share::getItemSharedWithBySource('contact', $object[0], \OCP\Share::FORMAT_NONE, null, true);
+ if (!$sharedContact || !($sharedContact['permissions'] & \OCP\PERMISSION_UPDATE)) {
return false;
}
}
- $vcard->setString('REV', $now->format(DateTime::W3C));
+ $vcard->{'REV'} = $now->format(\DateTime::W3C);
$data = $vcard->serialize();
try {
$result = $stmt->execute(array($data,time(),$object[0]));
- //OCP\Util::writeLog('contacts','OC_Contacts_VCard::updateDataByID, id: '.$object[0].': '.$object[1],OCP\Util::DEBUG);
+ if (\OC_DB::isError($result)) {
+ \OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
+ }
+ //OCP\Util::writeLog('contacts','OCA\Contacts\VCard::updateDataByID, id: '.$object[0].': '.$object[1],OCP\Util::DEBUG);
} catch(Exception $e) {
- OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
- OCP\Util::writeLog('contacts', __METHOD__.', id: '.$object[0], OCP\Util::DEBUG);
+ \OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR);
+ \OCP\Util::writeLog('contacts', __METHOD__.', id: '.$object[0], \OCP\Util::DEBUG);
}
+ App::updateDBProperties($object[0], $vcard);
}
}
}
@@ -392,16 +438,16 @@ class OC_Contacts_VCard {
/**
* @brief edits a card
* @param integer $id id of card
- * @param OC_VObject $card vCard file
+ * @param Sabre\VObject\Component $card vCard file
* @return boolean true on success, otherwise an exception will be thrown
*/
- public static function edit($id, OC_VObject $card) {
+ public static function edit($id, VObject\Component $card) {
$oldcard = self::find($id);
if (!$oldcard) {
- OCP\Util::writeLog('contacts', __METHOD__.', id: '
- . $id . ' not found.', OCP\Util::DEBUG);
- throw new Exception(
- OC_Contacts_App::$l10n->t(
+ \OCP\Util::writeLog('contacts', __METHOD__.', id: '
+ . $id . ' not found.', \OCP\Util::DEBUG);
+ throw new \Exception(
+ App::$l10n->t(
'Could not find the vCard with ID.' . $id
)
);
@@ -411,10 +457,13 @@ class OC_Contacts_VCard {
}
// NOTE: Owner checks are being made in the ajax files, which should be done
// inside the lib files to prevent any redundancies with sharing checks
- $addressbook = OC_Contacts_Addressbook::find($oldcard['addressbookid']);
- if ($addressbook['userid'] != OCP\User::getUser()) {
- $sharedAddressbook = OCP\Share::getItemSharedWithBySource('addressbook', $oldcard['addressbookid'], OCP\Share::FORMAT_NONE, null, true);
- $sharedContact = OCP\Share::getItemSharedWithBySource('contact', $id, OCP\Share::FORMAT_NONE, null, true);
+ $addressbook = Addressbook::find($oldcard['addressbookid']);
+ if ($addressbook['userid'] != \OCP\User::getUser()) {
+ $sharedAddressbook = \OCP\Share::getItemSharedWithBySource(
+ 'addressbook',
+ $oldcard['addressbookid'],
+ \OCP\Share::FORMAT_NONE, null, true);
+ $sharedContact = \OCP\Share::getItemSharedWithBySource('contact', $id, \OCP\Share::FORMAT_NONE, null, true);
$addressbook_permissions = 0;
$contact_permissions = 0;
if ($sharedAddressbook) {
@@ -424,37 +473,40 @@ class OC_Contacts_VCard {
$contact_permissions = $sharedEvent['permissions'];
}
$permissions = max($addressbook_permissions, $contact_permissions);
- if (!($permissions & OCP\Share::PERMISSION_UPDATE)) {
- throw new Exception(
- OC_Contacts_App::$l10n->t(
+ if (!($permissions & \OCP\PERMISSION_UPDATE)) {
+ throw new \Exception(
+ App::$l10n->t(
'You do not have the permissions to edit this contact.'
)
);
}
}
- OC_Contacts_App::loadCategoriesFromVCard($card);
+ App::loadCategoriesFromVCard($id, $card);
- $fn = $card->getAsString('FN');
- if (empty($fn)) {
- $fn = null;
- }
+ $fn = isset($card->FN) ? $card->FN : '';
- $now = new DateTime;
- $card->setString('REV', $now->format(DateTime::W3C));
+ $now = new \DateTime;
+ $card->{'REV'} = $now->format(\DateTime::W3C);
$data = $card->serialize();
- $stmt = OCP\DB::prepare( 'UPDATE `*PREFIX*contacts_cards` SET `fullname` = ?,`carddata` = ?, `lastmodified` = ? WHERE `id` = ?' );
+ $stmt = \OCP\DB::prepare( 'UPDATE `*PREFIX*contacts_cards` SET `fullname` = ?,`carddata` = ?, `lastmodified` = ? WHERE `id` = ?' );
try {
- $result = $stmt->execute(array($fn,$data,time(),$id));
- } catch(Exception $e) {
- OCP\Util::writeLog('contacts', __METHOD__.', exception: '
- . $e->getMessage(), OCP\Util::ERROR);
- OCP\Util::writeLog('contacts', __METHOD__.', id'.$id, OCP\Util::DEBUG);
+ $result = $stmt->execute(array($fn, $data, time(), $id));
+ if (\OC_DB::isError($result)) {
+ \OCP\Util::writeLog('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
+ return false;
+ }
+ } catch(\Exception $e) {
+ \OCP\Util::writeLog('contacts', __METHOD__.', exception: '
+ . $e->getMessage(), \OCP\Util::ERROR);
+ \OCP\Util::writeLog('contacts', __METHOD__.', id'.$id, \OCP\Util::DEBUG);
return false;
}
- OC_Contacts_Addressbook::touch($oldcard['addressbookid']);
- OC_Hook::emit('OC_Contacts_VCard', 'post_updateVCard', $id);
+ App::cacheThumbnail($oldcard['id']);
+ App::updateDBProperties($id, $card);
+ Addressbook::touch($oldcard['addressbookid']);
+ \OC_Hook::emit('\OCA\Contacts\VCard', 'post_updateVCard', $id);
return true;
}
@@ -467,21 +519,22 @@ class OC_Contacts_VCard {
*/
public static function editFromDAVData($aid, $uri, $data) {
$oldcard = self::findWhereDAVDataIs($aid, $uri);
- $card = OC_VObject::parse($data);
- if(!$card) {
- OCP\Util::writeLog('contacts', __METHOD__.
- ', Unable to parse VCARD, uri: '.$uri, OCP\Util::ERROR);
+ try {
+ $vcard = Sabre\VObject\Reader::read($data);
+ } catch(\Exception $e) {
+ \OCP\Util::writeLog('contacts', __METHOD__.
+ ', Unable to parse VCARD, : ' . $e->getMessage(), \OCP\Util::ERROR);
return false;
}
try {
- self::edit($oldcard['id'], $card);
+ self::edit($oldcard['id'], $vcard);
return true;
- } catch(Exception $e) {
- OCP\Util::writeLog('contacts', __METHOD__.', exception: '
+ } catch(\Exception $e) {
+ \OCP\Util::writeLog('contacts', __METHOD__.', exception: '
. $e->getMessage() . ', '
- . OCP\USER::getUser(), OCP\Util::ERROR);
- OCP\Util::writeLog('contacts', __METHOD__.', uri'
- . $uri, OCP\Util::DEBUG);
+ . \OCP\USER::getUser(), \OCP\Util::ERROR);
+ \OCP\Util::writeLog('contacts', __METHOD__.', uri'
+ . $uri, \OCP\Util::DEBUG);
return false;
}
}
@@ -492,31 +545,37 @@ class OC_Contacts_VCard {
* @return boolean true on success, otherwise an exception will be thrown
*/
public static function delete($id) {
- $card = self::find($id);
- if (!$card) {
- OCP\Util::writeLog('contacts', __METHOD__.', id: '
- . $id . ' not found.', OCP\Util::DEBUG);
- throw new Exception(
- OC_Contacts_App::$l10n->t(
+ $vcard = self::find($id);
+ if (!$vcard) {
+ \OCP\Util::writeLog('contacts', __METHOD__.', id: '
+ . $id . ' not found.', \OCP\Util::DEBUG);
+ throw new \Exception(
+ App::$l10n->t(
'Could not find the vCard with ID: ' . $id
)
);
}
- $addressbook = OC_Contacts_Addressbook::find($card['addressbookid']);
+ $addressbook = Addressbook::find($vcard['addressbookid']);
if(!$addressbook) {
- throw new Exception(
- OC_Contacts_App::$l10n->t(
+ throw new \Exception(
+ App::$l10n->t(
'Could not find the Addressbook with ID: '
- . $card['addressbookid']
+ . $vcard['addressbookid']
)
);
}
- if ($addressbook['userid'] != OCP\User::getUser() && !OC_Group::inGroup(OCP\User::getUser(), 'admin')) {
- OCP\Util::writeLog('contacts', __METHOD__.', '
- . $addressbook['userid'] . ' != ' . OCP\User::getUser(), OCP\Util::DEBUG);
- $sharedAddressbook = OCP\Share::getItemSharedWithBySource('addressbook', $card['addressbookid'], OCP\Share::FORMAT_NONE, null, true);
- $sharedContact = OCP\Share::getItemSharedWithBySource('contact', $id, OCP\Share::FORMAT_NONE, null, true);
+ if ($addressbook['userid'] != \OCP\User::getUser() && !\OC_Group::inGroup(\OCP\User::getUser(), 'admin')) {
+ \OCP\Util::writeLog('contacts', __METHOD__.', '
+ . $addressbook['userid'] . ' != ' . \OCP\User::getUser(), \OCP\Util::DEBUG);
+ $sharedAddressbook = \OCP\Share::getItemSharedWithBySource(
+ 'addressbook',
+ $vcard['addressbookid'],
+ \OCP\Share::FORMAT_NONE, null, true);
+ $sharedContact = \OCP\Share::getItemSharedWithBySource(
+ 'contact',
+ $id,
+ \OCP\Share::FORMAT_NONE, null, true);
$addressbook_permissions = 0;
$contact_permissions = 0;
if ($sharedAddressbook) {
@@ -526,34 +585,39 @@ class OC_Contacts_VCard {
$contact_permissions = $sharedEvent['permissions'];
}
$permissions = max($addressbook_permissions, $contact_permissions);
- if (!($permissions & OCP\Share::PERMISSION_DELETE)) {
- throw new Exception(
- OC_Contacts_App::$l10n->t(
+
+ if (!($permissions & \OCP\PERMISSION_DELETE)) {
+ throw new \Exception(
+ App::$l10n->t(
'You do not have the permissions to delete this contact.'
)
);
}
}
- OC_Hook::emit('OC_Contacts_VCard', 'pre_deleteVCard',
+ $aid = $card['addressbookid'];
+ \OC_Hook::emit('\OCA\Contacts\VCard', 'pre_deleteVCard',
array('aid' => null, 'id' => $id, 'uri' => null)
);
- $stmt = OCP\DB::prepare('DELETE FROM `*PREFIX*contacts_cards` WHERE `id` = ?');
+ $stmt = \OCP\DB::prepare('DELETE FROM `*PREFIX*contacts_cards` WHERE `id` = ?');
try {
$stmt->execute(array($id));
- } catch(Exception $e) {
- OCP\Util::writeLog('contacts', __METHOD__.
- ', exception: ' . $e->getMessage(), OCP\Util::ERROR);
- OCP\Util::writeLog('contacts', __METHOD__.', id: '
- . $id, OCP\Util::DEBUG);
- throw new Exception(
- OC_Contacts_App::$l10n->t(
+ } catch(\Exception $e) {
+ \OCP\Util::writeLog('contacts', __METHOD__.
+ ', exception: ' . $e->getMessage(), \OCP\Util::ERROR);
+ \OCP\Util::writeLog('contacts', __METHOD__.', id: '
+ . $id, \OCP\Util::DEBUG);
+ throw new \Exception(
+ App::$l10n->t(
'There was an error deleting this contact.'
)
);
}
- OCP\Share::unshareAll('contact', $id);
+ App::updateDBProperties($id);
+ App::getVCategories()->purgeObject($id);
+ Addressbook::touch($addressbook['userid']);
+ \OCP\Share::unshareAll('contact', $id);
return true;
}
@@ -563,89 +627,65 @@ class OC_Contacts_VCard {
* @param string $uri the uri of the card
* @return boolean
*/
- public static function deleteFromDAVData($aid,$uri) {
- $addressbook = OC_Contacts_Addressbook::find($aid);
- if ($addressbook['userid'] != OCP\User::getUser()) {
- $query = OCP\DB::prepare( 'SELECT `id` FROM `*PREFIX*contacts_cards` WHERE `addressbookid` = ? AND `uri` = ?' );
+ public static function deleteFromDAVData($aid, $uri) {
+ $id = null;
+ $addressbook = Addressbook::find($aid);
+ if ($addressbook['userid'] != \OCP\User::getUser()) {
+ $query = \OCP\DB::prepare( 'SELECT `id` FROM `*PREFIX*contacts_cards` WHERE `addressbookid` = ? AND `uri` = ?' );
$id = $query->execute(array($aid, $uri))->fetchOne();
if (!$id) {
return false;
}
- $sharedContact = OCP\Share::getItemSharedWithBySource('contact', $id, OCP\Share::FORMAT_NONE, null, true);
- if (!$sharedContact || !($sharedContact['permissions'] & OCP\Share::PERMISSION_DELETE)) {
+
+ $sharedContact = \OCP\Share::getItemSharedWithBySource('contact', $id, \OCP\Share::FORMAT_NONE, null, true);
+ if (!$sharedContact || !($sharedContact['permissions'] & \OCP\PERMISSION_DELETE)) {
return false;
}
}
- OC_Hook::emit('OC_Contacts_VCard', 'pre_deleteVCard', array('aid' => $aid, 'id' => null, 'uri' => $uri));
- $stmt = OCP\DB::prepare( 'DELETE FROM `*PREFIX*contacts_cards` WHERE `addressbookid` = ? AND `uri`=?' );
+ \OC_Hook::emit('\OCA\Contacts\VCard', 'pre_deleteVCard', array('aid' => $aid, 'id' => null, 'uri' => $uri));
+ $stmt = \OCP\DB::prepare( 'DELETE FROM `*PREFIX*contacts_cards` WHERE `addressbookid` = ? AND `uri`=?' );
try {
$stmt->execute(array($aid,$uri));
- } catch(Exception $e) {
- OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
- OCP\Util::writeLog('contacts', __METHOD__.', aid: '.$aid.' uri: '.$uri, OCP\Util::DEBUG);
+ } catch(\Exception $e) {
+ \OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR);
+ \OCP\Util::writeLog('contacts', __METHOD__.', aid: '.$aid.' uri: '.$uri, \OCP\Util::DEBUG);
return false;
}
- OC_Contacts_Addressbook::touch($aid);
+ Addressbook::touch($aid);
+
+ if(!is_null($id)) {
+ App::getVCategories()->purgeObject($id);
+ App::updateDBProperties($id);
+ \OCP\Share::unshareAll('contact', $id);
+ } else {
+ \OCP\Util::writeLog('contacts', __METHOD__.', Could not find id for ' . $uri, \OCP\Util::DEBUG);
+ }
return true;
}
- /**
- * @brief Escapes delimiters from an array and returns a string.
- * @param array $value
- * @param char $delimiter
- * @return string
- */
- public static function escapeDelimiters($value, $delimiter=';') {
- foreach($value as &$i ) {
- $i = implode("\\$delimiter", explode($delimiter, $i));
- }
- return implode($delimiter, $value);
- }
-
-
- /**
- * @brief Creates an array out of a multivalue property
- * @param string $value
- * @param char $delimiter
- * @return array
- */
- public static function unescapeDelimiters($value, $delimiter=';') {
- $array = explode($delimiter, $value);
- for($i=0;$ichildren as $property) {
+ foreach($vcard->children as $property) {
$pname = $property->name;
$temp = self::structureProperty($property);
if(!is_null($temp)) {
// Get Apple X-ABLabels
- if(isset($object->{$property->group . '.X-ABLABEL'})) {
- $temp['label'] = $object->{$property->group . '.X-ABLABEL'}->value;
+ if(isset($vcard->{$property->group . '.X-ABLABEL'})) {
+ $temp['label'] = $vcard->{$property->group . '.X-ABLABEL'}->value;
if($temp['label'] == '_$!!$_') {
- $temp['label'] = OC_Contacts_App::$l10n->t('Other');
+ $temp['label'] = App::$l10n->t('Other');
+ }
+ if($temp['label'] == '_$!!$_') {
+ $temp['label'] = App::$l10n->t('HomePage');
}
}
if(array_key_exists($pname, $details)) {
@@ -673,11 +713,14 @@ class OC_Contacts_VCard {
* but we should look out for any problems.
*/
public static function structureProperty($property) {
+ if(!in_array($property->name, App::$index_properties)) {
+ return;
+ }
$value = $property->value;
- //$value = htmlspecialchars($value);
- if($property->name == 'ADR' || $property->name == 'N') {
- $value = self::unescapeDelimiters($value);
- } elseif($property->name == 'BDAY') {
+ if($property->name == 'ADR' || $property->name == 'N' || $property->name == 'ORG' || $property->name == 'CATEGORIES') {
+ $value = $property->getParts();
+ }
+ elseif($property->name == 'BDAY') {
if(strpos($value, '-') === false) {
if(strlen($value) >= 8) {
$value = substr($value, 0, 4).'-'.substr($value, 4, 2).'-'.substr($value, 6, 2);
@@ -686,16 +729,31 @@ class OC_Contacts_VCard {
}
}
} elseif($property->name == 'PHOTO') {
- $property->value = true;
+ $value = true;
+ }
+ elseif($property->name == 'IMPP') {
+ if(strpos($value, ':') !== false) {
+ $value = explode(':', $value);
+ $protocol = array_shift($value);
+ if(!isset($property['X-SERVICE-TYPE'])) {
+ $property['X-SERVICE-TYPE'] = strtoupper(strip_tags($protocol));
+ }
+ $value = implode('', $value);
+ }
}
if(is_string($value)) {
$value = strtr($value, array('\,' => ',', '\;' => ';'));
}
$temp = array(
- 'name' => $property->name,
+ //'name' => $property->name,
'value' => $value,
- 'parameters' => array(),
- 'checksum' => md5($property->serialize()));
+ 'parameters' => array()
+ );
+
+ // This cuts around a 3rd off of the json response size.
+ if(in_array($property->name, App::$multi_properties)) {
+ $temp['checksum'] = substr(md5($property->serialize()), 0, 8);
+ }
foreach($property->parameters as $parameter) {
// Faulty entries by kaddressbook
// Actually TYPE=PREF is correct according to RFC 2426
@@ -706,12 +764,18 @@ class OC_Contacts_VCard {
}
// NOTE: Apparently Sabre_VObject_Reader can't always deal with value list parameters
// like TYPE=HOME,CELL,VOICE. Tanghus.
- if (in_array($property->name, array('TEL', 'EMAIL')) && $parameter->name == 'TYPE') {
+ // TODO: Check if parameter is has commas and split + merge if so.
+ if ($parameter->name == 'TYPE') {
+ $pvalue = $parameter->value;
+ if(is_string($pvalue) && strpos($pvalue, ',') !== false) {
+ $pvalue = array_map('trim', explode(',', $pvalue));
+ }
+ $pvalue = is_array($pvalue) ? $pvalue : array($pvalue);
if (isset($temp['parameters'][$parameter->name])) {
- $temp['parameters'][$parameter->name][] = $parameter->value;
+ $temp['parameters'][$parameter->name][] = $pvalue;
}
else {
- $temp['parameters'][$parameter->name] = array($parameter->value);
+ $temp['parameters'][$parameter->name] = $pvalue;
}
}
else{
@@ -729,11 +793,11 @@ class OC_Contacts_VCard {
*
*/
public static function moveToAddressBook($aid, $id, $isAddressbook = false) {
- OC_Contacts_App::getAddressbook($aid); // check for user ownership.
- $addressbook = OC_Contacts_Addressbook::find($aid);
- if ($addressbook['userid'] != OCP\User::getUser()) {
- $sharedAddressbook = OCP\Share::getItemSharedWithBySource('addressbook', $aid);
- if (!$sharedAddressbook || !($sharedAddressbook['permissions'] & OCP\Share::PERMISSION_CREATE)) {
+ Addressbook::find($aid);
+ $addressbook = Addressbook::find($aid);
+ if ($addressbook['userid'] != \OCP\User::getUser()) {
+ $sharedAddressbook = \OCP\Share::getItemSharedWithBySource('addressbook', $aid);
+ if (!$sharedAddressbook || !($sharedAddressbook['permissions'] & \OCP\PERMISSION_CREATE)) {
return false;
}
}
@@ -743,10 +807,10 @@ class OC_Contacts_VCard {
if (!$card) {
unset($id[$index]);
}
- $oldAddressbook = OC_Contacts_Addressbook::find($card['addressbookid']);
- if ($oldAddressbook['userid'] != OCP\User::getUser()) {
- $sharedContact = OCP\Share::getItemSharedWithBySource('contact', $cardId, OCP\Share::FORMAT_NONE, null, true);
- if (!$sharedContact || !($sharedContact['permissions'] & OCP\Share::PERMISSION_DELETE)) {
+ $oldAddressbook = Addressbook::find($card['addressbookid']);
+ if ($oldAddressbook['userid'] != \OCP\User::getUser()) {
+ $sharedContact = \OCP\Share::getItemSharedWithBySource('contact', $cardId, \OCP\Share::FORMAT_NONE, null, true);
+ if (!$sharedContact || !($sharedContact['permissions'] & \OCP\PERMISSION_DELETE)) {
unset($id[$index]);
}
}
@@ -754,44 +818,52 @@ class OC_Contacts_VCard {
$id_sql = join(',', array_fill(0, count($id), '?'));
$prep = 'UPDATE `*PREFIX*contacts_cards` SET `addressbookid` = ? WHERE `id` IN ('.$id_sql.')';
try {
- $stmt = OCP\DB::prepare( $prep );
+ $stmt = \OCP\DB::prepare( $prep );
//$aid = array($aid);
$vals = array_merge((array)$aid, $id);
$result = $stmt->execute($vals);
- } catch(Exception $e) {
- OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
- OCP\Util::writeLog('contacts', __METHOD__.', ids: '.join(',', $vals), OCP\Util::DEBUG);
- OCP\Util::writeLog('contacts', __METHOD__.', SQL:'.$prep, OCP\Util::DEBUG);
+ if (\OC_DB::isError($result)) {
+ \OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OC_Log::ERROR);
+ return false;
+ }
+ } catch(\Exception $e) {
+ \OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR);
+ \OCP\Util::writeLog('contacts', __METHOD__.', ids: '.join(',', $vals), \OCP\Util::DEBUG);
+ \OCP\Util::writeLog('contacts', __METHOD__.', SQL:'.$prep, \OCP\Util::DEBUG);
return false;
}
} else {
$stmt = null;
if($isAddressbook) {
- $stmt = OCP\DB::prepare( 'UPDATE `*PREFIX*contacts_cards` SET `addressbookid` = ? WHERE `addressbookid` = ?' );
+ $stmt = \OCP\DB::prepare( 'UPDATE `*PREFIX*contacts_cards` SET `addressbookid` = ? WHERE `addressbookid` = ?' );
} else {
$card = self::find($id);
if (!$card) {
return false;
}
- $oldAddressbook = OC_Contacts_Addressbook::find($card['addressbookid']);
- if ($oldAddressbook['userid'] != OCP\User::getUser()) {
- $sharedContact = OCP\Share::getItemSharedWithBySource('contact', $id, OCP\Share::FORMAT_NONE, null, true);
- if (!$sharedContact || !($sharedContact['permissions'] & OCP\Share::PERMISSION_DELETE)) {
+ $oldAddressbook = Addressbook::find($card['addressbookid']);
+ if ($oldAddressbook['userid'] != \OCP\User::getUser()) {
+ $sharedContact = \OCP\Share::getItemSharedWithBySource('contact', $id, \OCP\Share::FORMAT_NONE, null, true);
+ if (!$sharedContact || !($sharedContact['permissions'] & \OCP\PERMISSION_DELETE)) {
return false;
}
}
- $stmt = OCP\DB::prepare( 'UPDATE `*PREFIX*contacts_cards` SET `addressbookid` = ? WHERE `id` = ?' );
+ $stmt = \OCP\DB::prepare( 'UPDATE `*PREFIX*contacts_cards` SET `addressbookid` = ? WHERE `id` = ?' );
}
try {
$result = $stmt->execute(array($aid, $id));
- } catch(Exception $e) {
- OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), OCP\Util::DEBUG);
- OCP\Util::writeLog('contacts', __METHOD__.' id: '.$id, OCP\Util::DEBUG);
+ if (\OC_DB::isError($result)) {
+ \OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OC_Log::ERROR);
+ return false;
+ }
+ } catch(\Exception $e) {
+ \OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::DEBUG);
+ \OCP\Util::writeLog('contacts', __METHOD__.' id: '.$id, \OCP\Util::DEBUG);
return false;
}
}
- OC_Hook::emit('OC_Contacts_VCard', 'post_moveToAddressbook', array('aid' => $aid, 'id' => $id));
- OC_Contacts_Addressbook::touch($aid);
+ \OC_Hook::emit('\OCA\Contacts\VCard', 'post_moveToAddressbook', array('aid' => $aid, 'id' => $id));
+ Addressbook::touch($aid);
return true;
}
}
diff --git a/photo.php b/photo.php
index 9d4dcb3b..047a27b8 100644
--- a/photo.php
+++ b/photo.php
@@ -17,13 +17,14 @@ function getStandardImage() {
//OCP\Response::setExpiresHeader('P10D');
OCP\Response::enableCaching();
OCP\Response::redirect(OCP\Util::imagePath('contacts', 'person_large.png'));
+ exit;
}
$id = isset($_GET['id']) ? $_GET['id'] : null;
$etag = null;
$caching = null;
-if(is_null($id)) {
+if(!$id || $id === 'new') {
getStandardImage();
}
@@ -33,7 +34,7 @@ if(!extension_loaded('gd') || !function_exists('gd_info')) {
getStandardImage();
}
-$contact = OC_Contacts_App::getContactVCard($id);
+$contact = OCA\Contacts\App::getContactVCard($id);
$image = new OC_Image();
if (!$image) {
getStandardImage();
@@ -45,18 +46,18 @@ if (is_null($contact)) {
OCP\Util::ERROR);
} else {
// Photo :-)
- if ($image->loadFromBase64($contact->getAsString('PHOTO'))) {
+ if (isset($contact->PHOTO) && $image->loadFromBase64((string)$contact->PHOTO)) {
// OK
- $etag = md5($contact->getAsString('PHOTO'));
+ $etag = md5($contact->PHOTO);
}
else
// Logo :-/
- if ($image->loadFromBase64($contact->getAsString('LOGO'))) {
+ if (isset($contact->LOGO) && $image->loadFromBase64((string)$contact->LOGO)) {
// OK
- $etag = md5($contact->getAsString('LOGO'));
+ $etag = md5($contact->LOGO);
}
if ($image->valid()) {
- $modified = OC_Contacts_App::lastModified($contact);
+ $modified = OCA\Contacts\App::lastModified($contact);
// Force refresh if modified within the last minute.
if(!is_null($modified)) {
$caching = (time() - $modified->format('U') > 60) ? null : 0;
diff --git a/settings.php b/settings.php
index 029ce7dc..fa70992f 100644
--- a/settings.php
+++ b/settings.php
@@ -1,6 +1,6 @@
assign('addressbooks', OC_Contacts_Addressbook::all(OCP\USER::getUser()));
+$tmpl->assign('addressbooks', OCA\Contacts\Addressbook::all(OCP\USER::getUser()));
$tmpl->printPage();
diff --git a/templates/contacts.php b/templates/contacts.php
new file mode 100644
index 00000000..4b679e3a
--- /dev/null
+++ b/templates/contacts.php
@@ -0,0 +1,371 @@
+
+
+
+
+
+