Issues (493)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

js/addressbooks.js (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
OC.Contacts = OC.Contacts || {};
2
3
4
(function(window, $, OC) {
5
	'use strict';
6
7
	var AddressBook = function(storage, book, template, isFileAction) {
8
		this.isFileAction = isFileAction || false;
9
		this.storage = storage;
10
		this.book = book;
11
		this.$template = template;
12
		this.addressBooks = new OC.Contacts.AddressBookList(
13
			this.storage,
14
			$('#app-settings-content'),
15
			$('#addressBookTemplate')
16
		);
17
	};
18
19
	AddressBook.prototype.render = function() {
20
		var self = this;
21
		//var dialog = OC.Contacts.Dialog(null, null);
22
		
23
		this.$li = this.$template.octemplate({
24
			id: this.book.id,
25
			displayname: this.book.displayname,
26
			backend: this.book.backend,
27
			permissions: this.book.permissions
28
		});
29
		if (this.isFileAction) {
30
			return this.$li;
31
		}
32
		this.$li.find('a.action').tipsy({gravity: 'w'});
33
		if (!this.hasPermission(OC.PERMISSION_DELETE)) {
34
			this.$li.find('a.action.delete').hide();
35
		}
36
		if (!this.hasPermission(OC.PERMISSION_UPDATE)) {
37
			this.$li.find('a.action.edit').hide();
38
		}
39
		if (!this.hasPermission(OC.PERMISSION_SHARE)) {
40
			this.$li.find('a.action.share').hide();
41
		}
42
		if (['local', 'ldap', 'shared'].indexOf(this.getBackend()) === -1) {
43
			this.$li.find('a.action.carddav').hide();
44
		}
45
		this.$li.find('input:checkbox').prop('checked', this.book.active).on('change', function() {
46
			console.log('activate', self.getId());
47
			var checkbox = $(this).get(0);
48
			self.setActive(checkbox.checked, function(response) {
49
				if(!response.error) {
50
					self.book.active = checkbox.checked;
51
				} else {
52
					checkbox.checked = !checkbox.checked;
53
				}
54
			});
55
		});
56
		this.$li.find('a.action.download')
57
			.attr('href', OC.generateUrl(
58
				'apps/contacts/addressbook/{backend}/{addressBookId}/export',
59
				{
60
					backend: this.getBackend(),
61
					addressBookId: this.getId()
62
				}
63
			));
64
		this.$li.find('a.action.delete').on('click keypress', function() {
65
			OC.dialogs.confirm(t('contacts', 'Are you sure you want to delete the addressbook {addressbook} ?', {addressbook: self.getDisplayName()}),
66
					t('contacts', 'Remove addressbook'), function(answer) {
67
				if (answer) {
68
					$('.tipsy').remove();
69
					console.log('delete', self.getId());
70
					self.destroy();
71
				}
72
			});
73
		});
74
		this.$li.find('a.action.carddav').on('click keypress', function() {
75
			var uri = (self.book.owner === oc_current_user ) ? self.book.uri : self.book.uri + '_shared_by_' + self.book.owner;
76
			var link = OC.linkToRemote('carddav')+'/addressbooks/'+encodeURIComponent(oc_current_user)+'/'+encodeURIComponent(uri);
77
			var $dropdown = $('<li><div id="dropdown" class="drop"><input type="text" value="{link}" readonly /></div></li>')
78
				.octemplate({link:link}).insertAfter(self.$li);
79
			var $input = $dropdown.find('input');
80
			$input.focus().get(0).select();
81
			$input.on('blur', function() {
82
				$dropdown.hide('blind', function() {
83
					$dropdown.remove();
84
				});
85
			});
86
		});
87
		$('#add-ldap-address-book-element').on('click keypress', function() {
88
			var $rightContent = $('#app-content');
89
			$rightContent.append('<div id="addressbook-ui-dialog"></div>');
90
			var $dlg = $('#addressBookConfigTemplate').octemplate({backend: 'ldap'});
91
			var $divDlg = $('#addressbook-ui-dialog');
92
			$divDlg.html($dlg).ocdialog({
93
				modal: true,
94
				closeOnEscape: true,
95
				title: t('contacts', 'Add new LDAP Addressbook'),
96
				height: 'auto',
97
				width: 'auto',
98
				buttons: [
99
					{
100
						text: t('contacts', 'Ok'),
101
						click: function() {
102
							OC.Contacts.otherBackendConfig.addressbookUiOk($divDlg);
103
						},
104
						defaultButton: true
105
					},
106
					{
107
						text: t('contacts', 'Cancel'),
108
						click: function() {
109
							OC.Contacts.otherBackendConfig.addressbookUiClose($divDlg);
110
						}
111
					}
112
				],
113
				close: function(/*event, ui*/) {
114
					OC.Contacts.otherBackendConfig.addressbookUiClose($divDlg);
115
				},
116
				open: function(/*event, ui*/) {
117
					OC.Contacts.otherBackendConfig.openAddressbookUi();
118
				}
119
			});
120
		});
121
		this.$li.find('a.action.edit').on('click keypress', function(event) {
122
			$.when(self.storage.getAddressBook(self.getBackend(), self.getId()))
123
			.then(function(response) {
124
			if(!response.error) {
125
				if(response.data) {
126
					var addressbook = response.data;
127
					console.log('addressbook', addressbook);
128
					if (addressbook.backend === 'local') {
129
						if($(this).data('open')) {
130
							return;
131
						}
132
						var editor = this;
133
						event.stopPropagation();
134
						event.preventDefault();
135
						var $dropdown = $('<li><div><input type="text" value="{name}" /></div></li>')
136
							.octemplate({name:self.getDisplayName()}).insertAfter(self.$li);
137
						var $input = $dropdown.find('input');
138
						//$input.focus().get(0).select();
139
						$input.addnew({
140
							autoOpen: true,
141
							//autoClose: false,
142
							addText: t('contacts', 'Save'),
143
							ok: function(event, name) {
144
								console.log('edit-address-book ok', name);
145
								$input.addClass('loading');
146
								self.update({displayname:name}, function(response) {
147
									console.log('response', response);
148
									if(response.error) {
149
										$(document).trigger('status.contacts.error', response);
150
									} else {
151
										self.setDisplayName(response.data.displayname);
152
										$input.addnew('close');
153
									}
154
									$input.removeClass('loading');
155
								});
156
							},
157
							close: function() {
158
								$dropdown.remove();
159
								$(editor).data('open', false);
160
							}
161
						});
162
						$(this).data('open', true);
163
					} else {
164
						var $rightContent = $('#app-content');
165
						$rightContent.append('<div id="addressbook-ui-dialog"></div>');
166
						var $dlg = $('#addressBookConfigTemplate').octemplate({backend: 'ldap'});
167
						var $divDlg = $('#addressbook-ui-dialog');
168
						//var $divDlg = $('#addressbook-ui-dialog');
169
						$divDlg.html($dlg).ocdialog({
170
							modal: true,
171
							closeOnEscape: true,
172
							title: t('contacts', 'Edit Addressbook'),
173
							height: 'auto', width: 'auto',
174
							buttons: [
175
								{
176
									text: t('contacts', 'Ok'),
177
									click: function() {
178
										OC.Contacts.otherBackendConfig.addressbookUiEditOk($divDlg);
179
										self.setDisplayName($('#addressbooks-ui-name').val());
180
									},
181
									defaultButton: true
182
								},
183
								{
184
									text: t('contacts', 'Cancel'),
185
									click: function() {
186
										OC.Contacts.otherBackendConfig.addressbookUiClose($divDlg);
187
									}
188
								}
189
							],
190
							close: function() {
191
								OC.Contacts.otherBackendConfig.addressbookUiClose($divDlg);
192
							},
193
							open: function() {
194
								OC.Contacts.otherBackendConfig.editAddressbookUI(addressbook);
195
							}
196
						});
197
					}
198
					return this.$li;
199
				}
200
			} else {
201
				console.warn('Addressbook getAddressbook - no data !!');
202
			}
203
			})
204
			.fail(function(response) {
205
				console.warn('Request Failed:', response.message);
206
				$(document).trigger('status.contacts.error', response);
207
			});
208
		});
209
		return this.$li;
210
	};
211
212
	AddressBook.prototype.getId = function() {
213
		return String(this.book.id);
214
	};
215
216
	AddressBook.prototype.getBackend = function() {
217
		return this.book.backend;
218
	};
219
220
	AddressBook.prototype.getDisplayName = function() {
221
		return this.book.displayname;
222
	};
223
224
	AddressBook.prototype.setDisplayName = function(name) {
225
		this.book.displayname = name;
226
		this.$li.find('label').text(escapeHTML(name));
227
	};
228
229
	AddressBook.prototype.getPermissions = function() {
230
		return this.book.permissions;
231
	};
232
233
	AddressBook.prototype.hasPermission = function(permission) {
234
		return (this.getPermissions() & permission);
235
	};
236
237
	AddressBook.prototype.getOwner = function() {
238
		return this.book.owner;
239
	};
240
241
	AddressBook.prototype.getMetaData = function() {
242
		return {
243
			permissions:this.getPermissions,
244
			backend: this.getBackend(),
245
			id: this.getId(),
246
			displayname: this.getDisplayName()
247
		};
248
	};
249
250
	/**
251
	 * Update address book in data store
252
	 * @param object properties An object current only supporting the property 'displayname'
253
	 * @param cb Optional callback function which
254
	 * @return An object with a boolean variable 'error'.
255
	 */
256
	AddressBook.prototype.update = function(properties, cb) {
257
		return $.when(this.storage.updateAddressBook(this.getBackend(), this.getId(), {properties:properties}))
258
			.then(function(response) {
259
			if(response.error) {
260
				$(document).trigger('status.contacts.error', response);
261
			}
262
			cb(response);
263
		});
264
	};
265
266
	AddressBook.prototype.isActive = function() {
267
		return this.book.active;
268
	};
269
270
	/**
271
	 * Save an address books active state to data store.
272
	 * @param bool state
273
	 * @param cb Optional callback function which
274
	 * @return An object with a boolean variable 'error'.
275
	 */
276
	AddressBook.prototype.setActive = function(state, cb) {
277
		var self = this;
278
		return $.when(this.storage.activateAddressBook(this.getBackend(), this.getId(), state))
279
			.then(function(response) {
280
			if(response.error) {
281
				$(document).trigger('status.contacts.error', response);
282
			} else {
283
				$(document).trigger('status.addressbook.activated', {
284
					addressbook: self,
285
					state: state
286
				});
287
			}
288
			cb(response);
289
		});
290
	};
291
292
	/**
293
	 * Delete a list of contacts from the data store
294
	 * @param array contactsIds An array of contact ids to be deleted.
295
	 * @param cb Optional callback function which will be passed:
296
	 * @return An object with a boolean variable 'error'.
297
	 */
298
	AddressBook.prototype.deleteContacts = function(contactsIds, cb) {
299
		console.log('deleteContacts', contactsIds);
300
		return $.when(this.storage.deleteContacts(this.getBackend(), this.getId(), contactsIds))
301
			.then(function(response) {
302
			if(response.error) {
303
				$(document).trigger('status.contacts.error', response);
304
			}
305
			if(typeof cb === 'function') {
306
				cb(response);
307
			}
308
		});
309
	};
310
311
	/**
312
	 * Delete address book from data store and remove it from the DOM
313
	 * @return An object with a boolean variable 'error'.
314
	 */
315
	AddressBook.prototype.destroy = function() {
316
		var self = this;
317
		$.when(this.storage.deleteAddressBook(this.getBackend(), self.getId()))
318
			.then(function(response) {
319
			if(!response.error) {
320
				self.$li.remove();
321
				$(document).trigger('status.addressbook.removed', {
322
					addressbook: self
323
				});
324
			} else {
325
				$(document).trigger('status.contacts.error', response);
326
			}
327
		}).fail(function(response) {
328
			console.log(response.message);
329
			$(document).trigger('status.contacts.error', response);
330
		});
331
	};
332
333
	/**
334
	 * Controls access to address books
335
	 */
336
	var AddressBookList = function(
337
			storage,
338
			bookTemplate,
339
			bookItemTemplate,
340
			isFileAction
341
		) {
342
		var self = this;
343
		this.isFileAction = isFileAction || false;
344
		this.storage = storage;
345
		this.$bookTemplate = bookTemplate;
346
		this.$bookList = this.$bookTemplate.find('.addressbooklist');
347
		this.$bookItemTemplate = bookItemTemplate;
348
		this.$importIntoSelect = $('#contacts-import-into');
349
		this.$importFormatSelect = $('#contacts-import-format');
350
		this.$importProgress = $('#import-status-progress');
351
		this.$importStatusText = $('#import-status-text');
352
		this.addressBooks = [];
353
354
		if(this.isFileAction) {
355
			return;
356
		}
357
		this.$importFileInput = $('#contacts-import-upload-start');
358
		var $addInput = this.$bookTemplate.find('#add-address-book');
359
		$addInput.addnew({
360
			ok: function(event, name) {
361
				console.log('add-address-book ok', name);
362
				$addInput.addClass('loading');
363
				self.add(name, function(response) {
364
					console.log('response', response);
365
					if(response.error) {
366
						$(document).trigger('status.contacts.error', response);
367
					} else {
368
						$(this).addnew('close');
369
					}
370
					$addInput.removeClass('loading');
371
				});
372
			}
373
		});
374
375
		$(document).bind('status.addressbook.removed', function(e, data) {
376
			var addressBook = data.addressbook;
377
			self.addressBooks.splice(self.addressBooks.indexOf(addressBook), 1);
378
		});
379
		$('#oc-import-nocontact').unbind('click').click(function(event) {
380
			console.log('triggered', event);
381
			self.importDialog();
382
		});
383
		$('#import-contacts').unbind('click').click(function() {
384
			console.log('Import clicked');
385
			self.importDialog();
386
		});
387
	};
388
	
389
	AddressBookList.prototype.importDialog = function() {
390
		var $parent = $('body');
391
		$parent.append('<div id="import-dialog"></div>');
392
		var $dlg = $('#contactsImportTemplate').clone().octemplate();
393
		var $divDlg = $('#import-dialog');
394
		var self = this;
395
		$divDlg.html($dlg).ocdialog({
396
			modal: true,
397
			closeOnEscape: true,
398
			title: t('contacts', 'Import contacts'),
399
			height: 'auto',
400
			width: 'auto',
401
			buttons: [
402
				{
403
					text: t('contacts', 'Upload file...'),
404
					click: function() {
405
						$('#contacts-import-upload-start').click();
406
					}
407
				}
408
			],
409
			close: function(/*event, ui*/) {
410
				$('#import-dialog').ocdialog('close').ocdialog('destroy').remove();
411
			},
412
			open: function(/*event, ui*/) {
413
				self.openImportDialog();
414
			}
415
		});
416
	};
417
	
418
	AddressBookList.prototype.openImportDialog = function() {
419
		this.$importIntoSelect = $('#contacts-import-into');
420
		this.$importFormatSelect = $('#contacts-import-format');
421
		this.$importProgress = $('#import-status-progress');
422
		this.$importStatusText = $('#import-status-text');
423
		this.$importFileInput = $('#contacts-import-upload-start');
424
		var me = this;
425
		var self = this;
426
		this.$importFileInput.fileupload({
427
			dataType: 'json',
428
			start: function(e, data) {
429
				me.$importProgress.progressbar({value:false});
430
				$('.tipsy').remove();
431
				$('.import-status').show();
432
				me.$importProgress.fadeIn();
433
				me.$importStatusText.text(t('contacts', 'Starting file import'));
434
				$('.oc-dialog-buttonrow button, #contacts-import-into-p, #contacts-import-format-p').hide();
435
			},
436
			done: function (e, data) {
437
				if (me.$importFormatSelect.find('option:selected').val() != 'automatic') {
438
					me.$importStatusText.text(t('contacts', 'Format selected: {format}',
439
													{format: $('#contacts-import-format').find('option:selected').text() }));
440
				} else {
441
					me.$importStatusText.text(t('contacts', 'Automatic format detection'));
442
				}
443
				console.log('Upload done:', self.addressBooks);
444
				self.doImport(self.storage.formatResponse(data.jqXHR));
445
			},
446
			fail: function(e, data) {
447
				console.log('fail', data);
448
				OC.notify({message:data.errorThrown + ': ' + data.textStatus});
449
				$('.import-status').hide();
450
				$('.oc-dialog-buttonrow button, #contacts-import-into-p, #contacts-import-format-p').show();
451
			}
452
		});
453
		var $import_into = $('#contacts-import-into');
454
		$import_into.change(function() {
455
			if ($(this).val() !== '-1') {
456
				var url = OC.generateUrl(
457
					'apps/contacts/addressbook/{backend}/{addressBookId}/{importType}/import/upload',
458
					{
459
						addressBookId:$import_into.val(),
460
						importType:me.$importFormatSelect.find('option:selected').val(),
461
						backend: $import_into.find('option:selected').attr('backend')
462
					}
463
				);
464
				me.$importFileInput.fileupload().fileupload('option', 'url', url);
465
				me.$importFileInput.attr('disabled', false);
466
			} else {
467
				me.$importFileInput.attr('disabled', true);
468
			}
469
		});
470
		$.when(self.storage.getAddressBooksForUser()).then(function(response) {
471
			if(!response.error) {
472
				$import_into.empty();
473
				var $option = $('<option value="-1">' + t('contacts', 'Import into...') + '</option>');
474
				$import_into.append($option);
475
				var nbOptions = 0;
476
				$.each(response.data.addressbooks, function(idx, addressBook) {
477
					if (addressBook.permissions & OC.PERMISSION_UPDATE) {
478
						var $option=$('<option></option>').val(addressBook.id).html(addressBook.displayname).attr('backend', addressBook.backend);
479
						self.insertAddressBookWithoutRender(addressBook);
480
						$import_into.append($option);
481
						nbOptions++;
482
					}
483
				});
484
				if (nbOptions === 1) {
485
					$import_into.val($import_into.find('option:not([value="-1"])').first().val());
486
					$import_into.attr('disabled', true);
487
					me.$importFileInput.attr('disabled', false);
488
					var url = OC.generateUrl(
489
						'apps/contacts/addressbook/{backend}/{addressBookId}/{importType}/import/upload',
490
						{
491
							addressBookId:$import_into.val(),
492
							importType:me.$importFormatSelect.find('option:selected').val(),
493
							backend: $import_into.find('option:selected').attr('backend')
494
						}
495
					);
496
					me.$importFileInput.fileupload('option', 'url', url);
497
				}
498
			} else {
499
				console.log('status.contacts.error', response);
500
			}
501
		});
502
	};
503
504
	AddressBookList.prototype.count = function() {
505
		return this.addressBooks.length;
506
	};
507
508
	/**
509
	 * For importing from oC filesystem
510
	 */
511
	AddressBookList.prototype.prepareImport = function(backend, addressBookId, importType, path, fileName) {
512
		console.log('prepareImport', backend, addressBookId, importType, path, fileName);
513
		this.$importStatusText = $('#import-status-text');
514
		this.$importProgress = $('#import-status-progress');
515
		this.$importProgress.progressbar({value:false});
516
		if (importType != 'automatic') {
517
			this.$importStatusText.text(t('contacts', 'Format selected: {format}',
518
											{format: self.$importFormatSelect.find('option:selected').val() }));
519
		} else {
520
			this.$importStatusText.text(t('contacts', 'Automatic format detection'));
521
		}
522
		return this.storage.prepareImport(
523
				backend, addressBookId, importType,
524
				{filename:fileName, path:path}
525
			);
526
	};
527
528
	AddressBookList.prototype.doImport = function(response) {
529
		console.log('doImport', response);
530
		this.$importProgress = $('#import-status-progress');
531
		this.$importStatusText = $('#import-status-text');
532
		var defer = $.Deferred();
533
		var done = false;
534
		var interval = null, isChecking = false;
535
		var self = this;
536
		var closeImport = function() {
537
			defer.resolve();
538
		};
539
		if(!response.error) {
540
			this.$importProgress.progressbar('value', 0);
541
			var data = response.data;
542
			var getStatus = function(backend, addressbookid, importType, progresskey, interval, done) {
0 ignored issues
show
getStatus does not seem to be used.
Loading history...
543
				if(done) {
544
					clearInterval(interval);
545
					closeImport();
546
					return;
547
				}
548
				if(isChecking) {
549
					return;
550
				}
551
				isChecking = true;
552
				$.when(
553
					self.storage.importStatus(
554
						backend, addressbookid, importType,
555
						{progresskey:progresskey}
556
					))
557
				.then(function(response) {
558
					if(!response.error) {
559
						console.log('status, response: ', response);
560
						if (response.data.total != null && response.data.progress != null) {
561
							console.log('response.data', response.data);
562
							self.$importProgress.progressbar('option', 'max', Number(response.data.total));
563
							self.$importProgress.progressbar('value', Number(response.data.progress));
564
							self.$importStatusText.text(t('contacts', 'Processing {count}/{total} cards',
565
														{count: response.data.progress, total: response.data.total}));
566
						}
567
					} else {
568
						console.warn('Error', response.message);
569
						self.$importStatusText.text(response.message);
570
					}
571
					isChecking = false;
572
				}).fail(function(response) {
573
					console.log(response.message);
574
					$(document).trigger('status.contacts.error', response);
575
					isChecking = false;
576
				});
577
			};
578
			console.log('vertige', data);
579
			$.when(
580
				self.storage.startImport(
581
					/* this is a hack for a workaround on a bug. (described in contacts/issues/#488 for example)
582
 					 * instead of using data.backend, data.addressBookId and data.importType, I use
583
 					 * this.$importIntoSelect.find('option:selected').data('backend'),
584
 					 * this.$importIntoSelect.val() and this.$importFormatSelect.val()
585
 					 * This is because for some unknown reason yet, data isn't updated with select data after an import has been made
586
	 				 * so every import was made on the same addressbook with the same import type
587
 					 * I don't understand yet why the data object isn't updated after an upload, so I put this patch which works
588
 					 * but is ugly as hell
589
 					 */
590
					this.$importIntoSelect.find('option:selected').attr('backend'), this.$importIntoSelect.val(), this.$importFormatSelect.val(),
591
					{filename:data.filename, progresskey:data.progresskey}
592
				)
593
			)
594
			.then(function(response) {
595
				console.log('response', response);
596
				if(!response.error) {
597
					console.log('Import done');
598
					$('#contacts-import-upload').hide();
599
					self.$importStatusText.text(t('contacts', 'Total: {total}, Success: {imported}, Errors: {failed}',
600
													  {total: response.data.total, imported:response.data.imported, failed: response.data.failed}));
601
					self.$importProgress.progressbar('option', 'max', response.data.total);
602
					self.$importProgress.progressbar('value', response.data.total);
603
					var addressBook = self.find({id:data.addressBookId, backend: data.backend});
604
					console.log('addressBook', self.count(), self.addressBooks);
605
					$(document).trigger('status.addressbook.imported', {
606
						addressbook: addressBook
607
					});
608
					defer.resolve();
609
				} else {
610
					defer.reject(response);
611
					self.$importStatusText.text(response.message);
612
					$(document).trigger('status.contacts.error', response);
613
				}
614
				done = true;
615
			}).fail(function(response) {
616
				defer.reject(response);
617
				console.log(response.message);
618
				$(document).trigger('status.contacts.error', response);
619
				done = true;
620
			});
621
		} else {
622
			defer.reject(response);
623
			done = true;
624
			self.$importStatusText.text(response.message);
625
			closeImport();
626
			$(document).trigger('status.contacts.error', response);
627
		}
628
		return defer;
629
	};
630
631
	/**
632
	 * Create an AddressBook object, save it in internal list and append it's rendered result to the list
633
	 *
634
	 * @param object addressBook
635
	 * @param bool rendered If true add the addressbook to the addressbook list
636
	 * @return AddressBook
637
	 */
638
	AddressBookList.prototype.insertAddressBook = function(addressBook) {
639
		var book = new AddressBook(this.storage, addressBook, this.$bookItemTemplate, this.isFileAction);
640
		if(!this.isFileAction) {
641
			var result = book.render();
642
			this.$bookList.append(result);
643
		}
644
		this.addressBooks.push(book);
645
		return book;
646
	};
647
	
648
	/**
649
	 * Create an AddressBook object, save it in internal list and append it's rendered result to the list
650
	 *
651
	 * @param object addressBook
652
	 * @param bool rendered If true add the addressbook to the addressbook list
653
	 * @return AddressBook
654
	 */
655
	AddressBookList.prototype.insertAddressBookWithoutRender = function(addressBook) {
656
		var book = new AddressBook(this.storage, addressBook, this.$bookItemTemplate, this.isFileAction);
657
		this.addressBooks.push(book);
658
		return book;
659
	};
660
	
661
	/**
662
	 * Get an AddressBook
663
	 *
664
	 * @param object info An object with the string  properties 'id' and 'backend'
665
	 * @return AddressBook|null
666
	 */
667
	AddressBookList.prototype.find = function(info) {
668
		console.log('AddressBookList.find', info);
669
		var addressBook = null;
670
		$.each(this.addressBooks, function(idx, book) {
671
			if(book.getId() === String(info.id) && book.getBackend() === info.backend) {
672
				addressBook = book;
673
				return false; // break loop
674
			}
675
		});
676
		return addressBook;
677
	};
678
679
	/**
680
	 * Move a contacts from one address book to another..
681
	 *
682
	 * @param Contact The contact to move
683
	 * @param object from An object with properties 'id' and 'backend'.
684
	 * @param object target An object with properties 'id' and 'backend'.
685
	 */
686
	AddressBookList.prototype.moveContact = function(contact, from, target) {
687
		console.log('AddressBookList.moveContact, contact', contact, from, target);
688
		$.when(this.storage.moveContact(from.backend, from.id, contact.getId(), {target:target}))
689
			.then(function(response) {
690
			if(!response.error) {
691
				console.log('Contact moved', response);
692
				$(document).trigger('status.contact.moved', {
693
					contact: contact,
694
					data: response.data
695
				});
696
			} else {
697
				$(document).trigger('status.contacts.error', response);
698
			}
699
		});
700
	};
701
702
	/**
703
	 * Get an array of address books with at least the required permission.
704
	 *
705
	 * @param int permission
706
	 * @param bool noClone If true the original objects will be returned and can be manipulated.
707
	 */
708
	AddressBookList.prototype.selectByPermission = function(permission, noClone) {
709
		var books = [];
710
		$.each(this.addressBooks, function(idx, book) {
711
			if(book.getPermissions() & permission) {
712
				// Clone the address book not to mess with with original
713
				books.push(noClone ? book : $.extend(true, {}, book));
714
			}
715
		});
716
		return books;
717
	};
718
719
	/**
720
	 * Add a new address book.
721
	 *
722
	 * @param string name
723
	 * @param function cb
724
	 * @return jQuery.Deferred
725
	 * @throws Error
726
	 */
727
	AddressBookList.prototype.add = function(name, cb) {
728
		console.log('AddressBookList.add', name, typeof cb);
729
		var defer = $.Deferred();
730
		// Check for wrong, duplicate or empty name
731
		if(typeof name !== 'string') {
732
			throw new TypeError('BadArgument: AddressBookList.add() only takes String arguments.');
733
		}
734
		if(name.trim() === '') {
735
			throw new Error('BadArgument: Cannot add an address book with an empty name.');
736
		}
737
		var error = '';
738
		$.each(this.addressBooks, function(idx, book) {
739
			if(book.getDisplayName() === name) {
740
				console.log('Dupe');
741
				error = t('contacts', 'An address book called {name} already exists', {name:name});
742
				if(typeof cb === 'function') {
743
					cb({error:true, message:error});
744
				}
745
				defer.reject({error:true, message:error});
746
				return false; // break loop
747
			}
748
		});
749
		if(error.length) {
750
			console.warn('Error:', error);
751
			return defer;
752
		}
753
		var self = this;
754
		$.when(this.storage.addAddressBook('local',
755
		{displayname: name, description: ''})).then(function(response) {
756
			if(response.error) {
757
				error = response.message;
758
				if(typeof cb === 'function') {
759
					cb({error:true, message:error});
760
				}
761
				defer.reject(response);
762
			} else {
763
				var book = self.insertAddressBook(response.data);
764
				$(document).trigger('status.addressbook.added');
765
				if(typeof cb === 'function') {
766
					cb({error:false, addressbook: book});
767
				}
768
				defer.resolve({error:false, addressbook: book});
769
			}
770
		})
771
		.fail(function(jqxhr, textStatus, error) {
772
			$(this).removeClass('loading');
773
			var err = textStatus + ', ' + error;
774
			console.log('Request Failed', + err);
775
			error = t('contacts', 'Failed adding address book: {error}', {error:err});
776
			if(typeof cb === 'function') {
777
				cb({error:true, message:error});
778
			}
779
			defer.reject({error:true, message:error});
780
		});
781
		return defer;
782
	};
783
784
	/**
785
	* Load address books
786
	*/
787
	AddressBookList.prototype.loadAddressBooks = function() {
788
		var self = this;
789
		var defer = $.Deferred();
790
		$.when(this.storage.getAddressBooksForUser()).then(function(response) {
791
			if(!response.error) {
792
				$.each(response.data.addressbooks, function(idx, addressBook) {
793
					self.insertAddressBook(addressBook);
794
				});
795
				if(!self.isFileAction) {
796
					if(typeof OC.Share !== 'undefined') {
797
						OC.Share.loadIcons('addressbook');
798
					} else {
799
						self.$bookList.find('a.action.share').css('display', 'none');
800
					}
801
				}
802
				console.log('Before resolve');
803
				defer.resolve(self.addressBooks);
804
				console.log('After resolve');
805
			} else {
806
				defer.reject(response);
807
				$(document).trigger('status.contacts.error', response);
808
			}
809
		})
810
		.fail(function(response) {
811
			console.warn('Request Failed:', response);
812
			defer.reject({
813
				error: true,
814
				message: t('contacts', 'Failed loading address books: {error}', {error:response.message})
815
			});
816
		});
817
		return defer.promise();
818
	};
819
820
	OC.Contacts.AddressBookList = AddressBookList;
821
822
})(window, jQuery, OC);
823