diff options
author | Thomas Tanghus <thomas@tanghus.net> | 2012-03-09 10:29:58 +0100 |
---|---|---|
committer | Thomas Tanghus <thomas@tanghus.net> | 2012-03-09 10:29:58 +0100 |
commit | 2cbd2816375b00cbfce39f9b29bd70e63997a2b8 (patch) | |
tree | 4e98e1a61fd1bac90c8f8a10069666a191ccb023 | |
parent | bc5d9f58684acad4602bc7d43170b040a15f6dd5 (diff) | |
parent | 8228a554a4d2cbdadb7187d026e1b5c25d2b404a (diff) | |
download | nextcloud-server-2cbd2816375b00cbfce39f9b29bd70e63997a2b8.tar.gz nextcloud-server-2cbd2816375b00cbfce39f9b29bd70e63997a2b8.zip |
Merge branch 'vcategories'
-rw-r--r-- | apps/contacts/ajax/addproperty.php | 15 | ||||
-rw-r--r-- | apps/contacts/ajax/categories/add.php | 39 | ||||
-rw-r--r-- | apps/contacts/ajax/categories/checksumfor.php | 28 | ||||
-rw-r--r-- | apps/contacts/ajax/categories/delete.php | 60 | ||||
-rw-r--r-- | apps/contacts/ajax/categories/edit.php | 28 | ||||
-rw-r--r-- | apps/contacts/ajax/categories/list.php | 17 | ||||
-rw-r--r-- | apps/contacts/ajax/contactdetails.php | 2 | ||||
-rw-r--r-- | apps/contacts/ajax/saveproperty.php | 32 | ||||
-rw-r--r-- | apps/contacts/css/contacts.css | 15 | ||||
-rw-r--r-- | apps/contacts/index.php | 4 | ||||
-rw-r--r-- | apps/contacts/js/contacts.js | 368 | ||||
-rw-r--r-- | apps/contacts/js/jquery.multi-autocomplete.js | 47 | ||||
-rw-r--r-- | apps/contacts/lib/app.php | 6 | ||||
-rw-r--r-- | apps/contacts/lib/vcard.php | 69 | ||||
-rw-r--r-- | apps/contacts/templates/index.php | 1 | ||||
-rw-r--r-- | apps/contacts/templates/part.contact.php | 17 | ||||
-rw-r--r-- | lib/vcategories.php | 209 |
17 files changed, 838 insertions, 119 deletions
diff --git a/apps/contacts/ajax/addproperty.php b/apps/contacts/ajax/addproperty.php index b6b5dc7c416..e1a31292837 100644 --- a/apps/contacts/ajax/addproperty.php +++ b/apps/contacts/ajax/addproperty.php @@ -27,14 +27,16 @@ require_once('../../../lib/base.php'); OC_JSON::checkLoggedIn(); OC_JSON::checkAppEnabled('contacts'); -$id = $_POST['id']; -$vcard = OC_Contacts_App::getContactVCard( $id ); +$id = isset($_POST['id'])?$_POST['id']:null; +$name = isset($_POST['name'])?$_POST['name']:null; +$value = isset($_POST['value'])?$_POST['value']:null; +$parameters = isset($_POST['parameters'])?$_POST['parameters']:array(); + +$vcard = OC_Contacts_App::getContactVCard($id); -$name = $_POST['name']; -$value = $_POST['value']; if(!is_array($value)){ $value = trim($value); - if(!$value && in_array($name, array('TEL', 'EMAIL', 'ORG', 'BDAY', 'NICKNAME'))) { + if(!$value && in_array($name, array('TEL', 'EMAIL', 'ORG', 'BDAY', 'NICKNAME', 'NOTE'))) { OC_JSON::error(array('data' => array('message' => OC_Contacts_App::$l10n->t('Cannot add empty property.')))); exit(); } @@ -51,7 +53,6 @@ if(!is_array($value)){ exit(); } } -$parameters = isset($_POST['parameters']) ? $_POST['parameters'] : array(); // Prevent setting a duplicate entry $current = $vcard->select($name); @@ -82,7 +83,9 @@ switch($name) { } case 'N': case 'ORG': + case 'NOTE': case 'NICKNAME': + // TODO: Escape commas and semicolons. break; case 'EMAIL': $value = strtolower($value); diff --git a/apps/contacts/ajax/categories/add.php b/apps/contacts/ajax/categories/add.php new file mode 100644 index 00000000000..9b6c262978b --- /dev/null +++ b/apps/contacts/ajax/categories/add.php @@ -0,0 +1,39 @@ +<?php +/** + * Copyright (c) 2012 Thomas Tanghus <thomas@tanghus.net> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +require_once('../../../../lib/base.php'); +OC_JSON::checkLoggedIn(); +OC_JSON::checkAppEnabled('contacts'); + +function bailOut($msg) { + OC_JSON::error(array('data' => array('message' => $msg))); + OC_Log::write('contacts','ajax/categories/add.php: '.$msg, OC_Log::DEBUG); + exit(); +} +function debug($msg) { + OC_Log::write('contacts','ajax/categories/add.php: '.$msg, OC_Log::DEBUG); +} + +$category = isset($_GET['category'])?strip_tags($_GET['category']):null; + +if(is_null($category)) { + bailOut(OC_Contacts_App::$l10n->t('No category to add?')); +} + +debug(print_r($category, true)); + +$categories = new OC_VCategories('contacts'); +if($categories->hasCategory($category)) { + bailOut(OC_Contacts_App::$l10n->t('This category already exists: '.$category)); +} else { + $categories->add($category, true); +} + +OC_JSON::success(array('data' => array('categories'=>$categories->categories()))); + +?> diff --git a/apps/contacts/ajax/categories/checksumfor.php b/apps/contacts/ajax/categories/checksumfor.php new file mode 100644 index 00000000000..ff535866bf0 --- /dev/null +++ b/apps/contacts/ajax/categories/checksumfor.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright (c) 2012 Thomas Tanghus <thomas@tanghus.net> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +require_once('../../../../lib/base.php'); +OC_JSON::checkLoggedIn(); +OC_JSON::checkAppEnabled('contacts'); + +$id = isset($_GET['id'])?$_GET['id']:null; +if(is_null($id)) { + OC_JSON::error(array('data' => array('message' => OC_Contacts_App::$l10n->t('No ID provided')))); + exit(); +} +$vcard = OC_Contacts_App::getContactVCard( $id ); +foreach($vcard->children as $property){ + //OC_Log::write('contacts','ajax/categories/checksumfor.php: '.$property->name, OC_Log::DEBUG); + if($property->name == 'CATEGORIES') { + $checksum = md5($property->serialize()); + OC_JSON::success(array('data' => array('checksum'=>$checksum))); + exit(); + } +} +OC_JSON::error(array('data' => array('message' => OC_Contacts_App::$l10n->t('Error setting checksum.')))); +?> diff --git a/apps/contacts/ajax/categories/delete.php b/apps/contacts/ajax/categories/delete.php new file mode 100644 index 00000000000..3ba5aa16068 --- /dev/null +++ b/apps/contacts/ajax/categories/delete.php @@ -0,0 +1,60 @@ +<?php +/** + * Copyright (c) 2012 Thomas Tanghus <thomas@tanghus.net> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +require_once('../../../../lib/base.php'); +OC_JSON::checkLoggedIn(); +OC_JSON::checkAppEnabled('contacts'); + +foreach ($_POST as $key=>$element) { + debug('_POST: '.$key.'=>'.print_r($element, true)); +} + +function bailOut($msg) { + OC_JSON::error(array('data' => array('message' => $msg))); + OC_Log::write('contacts','ajax/categories/delete.php: '.$msg, OC_Log::DEBUG); + exit(); +} +function debug($msg) { + OC_Log::write('contacts','ajax/categories/delete.php: '.$msg, OC_Log::DEBUG); +} + +$categories = isset($_POST['categories'])?$_POST['categories']:null; + +if(is_null($categories)) { + bailOut(OC_Contacts_App::$l10n->t('No categories selected for deletion.')); +} + +debug(print_r($categories, true)); + +$addressbooks = OC_Contacts_Addressbook::all(OC_User::getUser()); +if(count($addressbooks) == 0) { + bailOut(OC_Contacts_App::$l10n->t('No address books found.')); +} +$addressbookids = array(); +foreach($addressbooks as $addressbook) { + $addressbookids[] = $addressbook['id']; +} +$contacts = OC_Contacts_VCard::all($addressbookids); +if(count($contacts) == 0) { + bailOut(OC_Contacts_App::$l10n->t('No contacts found.')); +} + +$cards = array(); +foreach($contacts as $contact) { + $cards[] = array($contact['id'], $contact['carddata']); +} + +debug('Before delete: '.print_r($categories, true)); + +$catman = new OC_VCategories('contacts'); +$catman->delete($categories, $cards); +debug('After delete: '.print_r($catman->categories(), true)); +OC_Contacts_VCard::updateDataByID($cards); +OC_JSON::success(array('data' => array('categories'=>$catman->categories()))); + +?> diff --git a/apps/contacts/ajax/categories/edit.php b/apps/contacts/ajax/categories/edit.php new file mode 100644 index 00000000000..8ecc3540b11 --- /dev/null +++ b/apps/contacts/ajax/categories/edit.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright (c) 2012 Thomas Tanghus <thomas@tanghus.net> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +require_once('../../../../lib/base.php'); +OC_JSON::checkLoggedIn(); +OC_JSON::checkAppEnabled('contacts'); +function bailOut($msg) { + OC_JSON::error(array('data' => array('message' => $msg))); + OC_Log::write('contacts','ajax/categories/edit.php: '.$msg, OC_Log::DEBUG); + exit(); +} +function debug($msg) { + OC_Log::write('contacts','ajax/categories/edit.php: '.$msg, OC_Log::DEBUG); +} + +$tmpl = new OC_TEMPLATE("contacts", "part.edit_categories_dialog"); + +$categories = OC_Contacts_App::$categories->categories(); +debug(print_r($categories, true)); +$tmpl->assign('categories',$categories); +$tmpl->printpage(); + +?> diff --git a/apps/contacts/ajax/categories/list.php b/apps/contacts/ajax/categories/list.php new file mode 100644 index 00000000000..3b41b7bfa95 --- /dev/null +++ b/apps/contacts/ajax/categories/list.php @@ -0,0 +1,17 @@ +<?php +/** + * Copyright (c) 2012 Thomas Tanghus <thomas@tanghus.net> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +require_once('../../../../lib/base.php'); +OC_JSON::checkLoggedIn(); +OC_JSON::checkAppEnabled('contacts'); + +$categories = OC_Contacts_App::$categories->categories(); + +OC_JSON::success(array('data' => array('categories'=>$categories))); + +?> diff --git a/apps/contacts/ajax/contactdetails.php b/apps/contacts/ajax/contactdetails.php index f35fd595c56..03895c862aa 100644 --- a/apps/contacts/ajax/contactdetails.php +++ b/apps/contacts/ajax/contactdetails.php @@ -71,5 +71,5 @@ if(isset($details['PHOTO'])) { $details['PHOTO'] = false; } $details['id'] = $id; - +OC_Contacts_App::setLastModifiedHeader($vcard); OC_JSON::success(array('data' => $details)); diff --git a/apps/contacts/ajax/saveproperty.php b/apps/contacts/ajax/saveproperty.php index db209fedfc7..0769791ea36 100644 --- a/apps/contacts/ajax/saveproperty.php +++ b/apps/contacts/ajax/saveproperty.php @@ -36,7 +36,7 @@ function debug($msg) { OC_Log::write('contacts','ajax/saveproperty.php: '.$msg, OC_Log::DEBUG); } foreach ($_POST as $key=>$element) { - debug('_POST: '.$key.'=>'.$element); + debug('_POST: '.$key.'=>'.print_r($element, true)); } $id = isset($_POST['id'])?$_POST['id']:null; @@ -51,12 +51,8 @@ $checksum = isset($_POST['checksum'])?$_POST['checksum']:null; // } // } -if(is_array($value)){ - $value = array_map('strip_tags', $value); - ksort($value); // NOTE: Important, otherwise the compound value will be set in the order the fields appear in the form! - $value = OC_VObject::escapeSemicolons($value); -} else { - $value = trim(strip_tags($value)); +if(!$name) { + bailOut(OC_Contacts_App::$l10n->t('element name is not set.')); } if(!$id) { bailOut(OC_Contacts_App::$l10n->t('id is not set.')); @@ -64,14 +60,22 @@ if(!$id) { if(!$checksum) { bailOut(OC_Contacts_App::$l10n->t('checksum is not set.')); } -if(!$name) { - bailOut(OC_Contacts_App::$l10n->t('element name is not set.')); +if(is_array($value)){ + $value = array_map('strip_tags', $value); + ksort($value); // NOTE: Important, otherwise the compound value will be set in the order the fields appear in the form! + if($name == 'CATEGORIES') { + $value = OC_Contacts_VCard::escapeDelimiters($value, ','); + } else { + $value = OC_Contacts_VCard::escapeDelimiters($value, ';'); + } +} else { + $value = trim(strip_tags($value)); } $vcard = OC_Contacts_App::getContactVCard( $id ); $line = OC_Contacts_App::getPropertyLineByChecksum($vcard, $checksum); if(is_null($line)) { - bailOut(OC_Contacts_App::$l10n->t('Information about vCard is incorrect. Please reload the page.'.$checksum.' "'.$line.'"')); + bailOut(OC_Contacts_App::$l10n->t('Information about vCard is incorrect. Please reload the page: ').$checksum); } $element = $vcard->children[$line]->name; @@ -91,7 +95,9 @@ switch($element) { } case 'N': case 'ORG': + case 'NOTE': case 'NICKNAME': + case 'CATEGORIES': debug('Setting string:'.$name.' '.$value); $vcard->setString($name, $value); break; @@ -123,12 +129,8 @@ $checksum = md5($vcard->children[$line]->serialize()); debug('New checksum: '.$checksum); if(!OC_Contacts_VCard::edit($id,$vcard)) { - OC_JSON::error(array('data' => array('message' => OC_Contacts_App::$l10n->t('Error updating contact property.')))); - OC_Log::write('contacts','ajax/setproperty.php: Error updating contact property: '.$value, OC_Log::ERROR); + bailOut(OC_Contacts_App::$l10n->t('Error updating contact property.')); exit(); } -//$adr_types = OC_Contacts_App::getTypesOfProperty('ADR'); -//$phone_types = OC_Contacts_App::getTypesOfProperty('TEL'); - OC_JSON::success(array('data' => array( 'line' => $line, 'checksum' => $checksum, 'oldchecksum' => $_POST['checksum'] ))); diff --git a/apps/contacts/css/contacts.css b/apps/contacts/css/contacts.css index b24ec438f24..a6f7d9316f8 100644 --- a/apps/contacts/css/contacts.css +++ b/apps/contacts/css/contacts.css @@ -20,7 +20,8 @@ #firstrun { width: 100%; position: absolute; top: 5em; left: 0; text-align: center; font-weight:bold; font-size:1.5em; color:#777; } #firstrun #selections { font-size:0.8em; margin: 2em auto auto auto; clear: both; } -#card input[type="text"].contacts_property,input[type="email"].contacts_property { width: 16em; } +#card input[type="text"].contacts_property,input[type="email"].contacts_property { width: 14em; } +.categories { float: left; width: 16em; } #card input[type="text"],input[type="email"],input[type="tel"],input[type="date"], select { background-color: #f8f8f8; border: 0 !important; -webkit-appearance:none !important; -moz-appearance:none !important; -webkit-box-sizing:none !important; -moz-box-sizing:none !important; box-sizing:none !important; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; -moz-border-radius: 0px; -webkit-border-radius: 0px; border-radius: 0px; float: left; } #card input[type="text"]:hover, input[type="text"]:focus, input[type="text"]:active,input[type="email"]:hover,input[type="tel"]:hover,input[type="date"]:hover,input[type="date"],input[type="date"]:hover,input[type="date"]:active,input[type="date"]:active,input[type="date"]:active,input[type="email"]:active,input[type="tel"]:active, select:hover, select:focus, select:active { border: 0 !important; -webkit-appearance:textfield; -moz-appearance:textfield; -webkit-box-sizing:content-box; -moz-box-sizing:content-box; box-sizing:content-box; background:#fff; color:#333; border:1px solid #ddd; -moz-box-shadow:0 1px 1px #fff, 0 2px 0 #bbb inset; -webkit-box-shadow:0 1px 1px #fff, 0 1px 0 #bbb inset; box-shadow:0 1px 1px #fff, 0 1px 0 #bbb inset; -moz-border-radius:.5em; -webkit-border-radius:.5em; border-radius:.5em; outline:none; float: left; } input[type="text"]:invalid,input[type="email"]:invalid,input[type="tel"]:invalid,input[type="date"]:invalid { background-color: #ffc0c0 !important; } @@ -68,7 +69,7 @@ dl.form /*background-color: yellow;*/ } -.loading { background: url('../../../core/img/loading.gif') no-repeat center !important;} +.loading { background: url('../../../core/img/loading.gif') no-repeat center !important; /*cursor: progress; */ cursor: wait; } /*.add { cursor: pointer; width: 25px; height: 25px; margin: 0px; float: right; position:relative; content: "\+"; font-weight: bold; color: #666; font-size: large; bottom: 0px; right: 0px; clear: both; text-align: center; vertical-align: bottom; display: none; }*/ @@ -185,4 +186,12 @@ input[type="checkbox"] { width: 20px; height: 20px; vertical-align: bottom; } .propertylist li > input[type="checkbox"],input[type="radio"] { float: left; clear: left; width: 20px; height: 20px; vertical-align: middle; } .propertylist li > select { float: left; max-width: 8em; } .typelist { float: left; max-width: 10em; } /* for multiselect */ -.addresslist { clear: both; }
\ No newline at end of file +.addresslist { clear: both; } + +#categoryform .scrollarea { position: absolute; left: 10px; top: 10px; right: 10px; bottom: 50px; overflow: auto; border:1px solid #ddd; background: #f8f8f8; } +#categoryform .bottombuttons { position: absolute; bottom: 10px;} +#categoryform .bottombuttons * { float: left;} +/*#categorylist { border:1px solid #ddd;}*/ +#categorylist li { background:#f8f8f8; padding:.3em .8em; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; -webkit-transition:background-color 500ms; -moz-transition:background-color 500ms; -o-transition:background-color 500ms; transition:background-color 500ms; } +#categorylist li:hover, li:active { background:#eee; } +#category_addinput { width: 10em; } diff --git a/apps/contacts/index.php b/apps/contacts/index.php index 0a21ddd04b6..48b2b8b4f01 100644 --- a/apps/contacts/index.php +++ b/apps/contacts/index.php @@ -34,6 +34,7 @@ if(!is_null($id)) { } $property_types = OC_Contacts_App::getAddPropertyOptions(); $phone_types = OC_Contacts_App::getTypesOfProperty('TEL'); +$categories = OC_Contacts_App::$categories->categories(); $upload_max_filesize = OC_Helper::computerFileSize(ini_get('upload_max_filesize')); $post_max_size = OC_Helper::computerFileSize(ini_get('post_max_size')); @@ -48,6 +49,7 @@ OC_Util::addScript('contacts','contacts'); OC_Util::addScript('contacts','jquery.combobox'); OC_Util::addScript('contacts','jquery.inview'); OC_Util::addScript('contacts','jquery.Jcrop'); +OC_Util::addScript('contacts','jquery.multi-autocomplete'); OC_Util::addStyle('','jquery.multiselect'); //OC_Util::addStyle('contacts','styles'); OC_Util::addStyle('contacts','jquery.combobox'); @@ -58,7 +60,9 @@ $tmpl = new OC_Template( "contacts", "index", "user" ); $tmpl->assign('uploadMaxFilesize', $maxUploadFilesize); $tmpl->assign('uploadMaxHumanFilesize', OC_Helper::humanFileSize($maxUploadFilesize)); $tmpl->assign('property_types',$property_types); +$tmpl->assign('categories',OC_Contacts_App::getCategories()); $tmpl->assign('phone_types',$phone_types); +$tmpl->assign('categories',$categories); $tmpl->assign('addressbooks', $addressbooks); $tmpl->assign('contacts', $contacts); $tmpl->assign('details', $details ); diff --git a/apps/contacts/js/contacts.js b/apps/contacts/js/contacts.js index 4ede2ff4336..ccedf1d3032 100644 --- a/apps/contacts/js/contacts.js +++ b/apps/contacts/js/contacts.js @@ -10,17 +10,115 @@ String.prototype.strip_tags = function(){ return stripped; }; +Categories={ + edit:function(){ + console.log('Categories.edit'); + $('body').append('<div id="category_dialog"></div>'); + $('#category_dialog').load(OC.filePath('contacts', 'ajax', 'categories/edit.php'), function(response, status, xhr){ + try { + var response = jQuery.parseJSON(response); + console.log('status: ' + status + ', response: ' + response + ', response.status:' + response.status); + if(response.status == 'error'){ + OC.dialogs.alert(response.data.message, 'Error'); + } else { + OC.dialogs.alert(response, 'Error'); + } + } catch(e) { + $('#edit_categories_dialog').dialog({ + modal: true, + height: 350, minHeight:200, width: 250, minWidth: 200, + buttons: { + 'Delete':function() { + Categories.delete(); + }, + 'Rescan':function() { + Categories.rescan(); + } + }, + close : function(event, ui) { + //alert('close'); + $(this).dialog('destroy').remove(); + $('#category_dialog').remove(); + }, + open : function(event, ui) { + $('#category_addinput').live('input',function(){ + if($(this).val().length > 0) { + $('#category_addbutton').removeAttr('disabled'); + } + }); + $('#categoryform').submit(function() { + Categories.add($('#category_addinput').val()); + $('#category_addinput').val(''); + $('#category_addbutton').attr('disabled', 'disabled'); + return false; + }); + $('#category_addbutton').live('click',function(e){ + e.preventDefault(); + if($('#category_addinput').val().length > 0) { + Categories.add($('#category_addinput').val()); + $('#category_addinput').val(''); + } + }); + } + }); + } + }); + }, + delete:function(){ + var categories = $('#categorylist').find('input[type="checkbox"]').serialize(); + console.log('Categories.delete: ' + categories); + $.post(OC.filePath('contacts', 'ajax', 'categories/delete.php'),categories,function(jsondata){ + if(jsondata.status == 'success'){ + Categories._update(jsondata.data.categories); + } else { + OC.dialogs.alert(jsondata.data.message, 'Error'); + } + }); + }, + add:function(category){ + console.log('Categories.add ' + category); + $.getJSON(OC.filePath('contacts', 'ajax', 'categories/add.php'),{'category':category},function(jsondata){ + if(jsondata.status == 'success'){ + Categories._update(jsondata.data.categories); + } else { + OC.dialogs.alert(jsondata.data.message, 'Error'); + } + }); + return false; + }, + rescan:function(){ + console.log('Categories.rescan'); + $.getJSON(OC.filePath('contacts', 'ajax', 'categories/rescan.php'),{},function(jsondata){ + if(jsondata.status == 'success'){ + Categories._update(jsondata.data.categories); + } else { + OC.dialogs.alert(jsondata.data.message, 'Error'); + } + }); + }, + _update:function(categories){ + var categorylist = $('#categorylist'); + categorylist.find('li').remove(); + for(var category in categories) { + var item = '<li><input type="checkbox" name="categories" value="' + categories[category] + '" />' + categories[category] + '</li>'; + $(item).appendTo(categorylist); + } + if(Categories.changed != undefined) { + Categories.changed(categories); + } + } +} Contacts={ UI:{ notImplemented:function() { - Contacts.UI.messageBox(t('contacts', 'Not implemented'), t('contacts', 'Sorry, this functionality has not been implemented yet')); + OC.dialogs.alert(t('contacts', 'Sorry, this functionality has not been implemented yet'), t('contacts', 'Not implemented')); }, searchOSM:function(obj) { var adr = Contacts.UI.propertyContainerFor(obj).find('.adr').val(); console.log('adr 1: ' + adr); if(adr == undefined) { - Contacts.UI.messageBox(t('contacts', 'Error'), t('contacts', 'Couldn\'t get a valid address.')); + OC.dialogs.alert(t('contacts', 'Couldn\'t get a valid address.'), t('contacts', 'Error')); return; } // FIXME: I suck at regexp. /Tanghus @@ -53,7 +151,7 @@ Contacts={ mailTo:function(obj) { var adr = Contacts.UI.propertyContainerFor($(obj)).find('input[type="email"]').val().trim(); if(adr == '') { - Contacts.UI.messageBox(t('contacts', 'Error'), t('contacts', 'Please enter an email address.')); + OC.dialogs.alert(t('contacts', 'Please enter an email address.'), t('contacts', 'Error')); return; } window.location.href='mailto:' + adr; @@ -116,34 +214,6 @@ Contacts={ $('#carddav_url').show(); $('#carddav_url_close').show(); }, - messageBox:function(title, msg) { - if(msg.toLowerCase().indexOf('auth') != -1) { - // fugly hack, I know - alert(msg); - } - if($('#messagebox').dialog('isOpen') == true){ - // NOTE: Do we ever get here? - $('#messagebox').dialog('moveToTop'); - }else{ - $('#dialog_holder').load(OC.filePath('contacts', 'ajax', 'messagebox.php'), function(){ - $('#messagebox').dialog( - { - autoOpen: true, - title: title, - buttons: [{ - text: "Ok", - click: function() { $(this).dialog("close"); } - }], - close: function(event, ui) { - $(this).dialog('destroy').remove(); - }, - open: function(event, ui) { - $('#messagebox_msg').html(msg); - } - }); - }); - }; - }, loadListHandlers:function() { //$('.add,.delete').hide(); $('.globe,.mail,.delete,.edit').tipsy(); @@ -182,6 +252,12 @@ Contacts={ $('#bday').datepicker({ dateFormat : 'dd-mm-yy' }); + $('#categories_value').find('select').multiselect({ + noneSelectedText: t('contacts', 'Select categories'), + header: false, + selectedList: 6, + classes: 'categories' + }); // Style phone types $('#phonelist').find('select.contacts_property').multiselect({ noneSelectedText: t('contacts', 'Select type'), @@ -223,6 +299,7 @@ Contacts={ click: function() { $(this).dialog('close'); } } ] ); + //$('#categories').multiple_autocomplete({source: categories}); Contacts.UI.loadListHandlers(); }, Card:{ @@ -252,12 +329,12 @@ Contacts={ if(jsondata.status == 'success'){ Contacts.UI.Card.loadContact(jsondata.data); } else{ - Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message); + OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); } }); } } else{ - Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message); + OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); } }); } @@ -269,7 +346,7 @@ Contacts={ $('#rightcontent').data('id',''); $('#rightcontent').html(jsondata.data.page); } else { - Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message); + OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); } }); } @@ -309,7 +386,7 @@ Contacts={ } else{ - Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message); + OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); //alert(jsondata.data.message); } }); @@ -318,7 +395,7 @@ Contacts={ // TODO: Add to contacts list. } else{ - Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message); + OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); //alert(jsondata.data.message); } }); @@ -344,13 +421,13 @@ Contacts={ $('#rightcontent').html(jsondata.data.page); } else{ - Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message); + OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); } }); } } else{ - Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message); + OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); //alert(jsondata.data.message); } }); @@ -361,7 +438,7 @@ Contacts={ this.data = jsondata; this.id = this.data.id; $('#rightcontent').data('id',this.id); - //console.log('loaded: ' + this.data.FN[0]['value']); + console.log('loaded: ' + this.data.FN[0]['value']); this.populateNameFields(); this.loadCategories(); this.loadPhoto(); @@ -369,9 +446,19 @@ Contacts={ this.loadPhones(); this.loadAddresses(); this.loadSingleProperties(); + // TODO: load NOTE ;-) + if(this.data.NOTE) { + $('#note').data('checksum', this.data.NOTE[0]['checksum']); + $('#note').find('textarea').val(this.data.NOTE[0]['value']); + $('#note').show(); + } else { + $('#note').data('checksum', ''); + $('#note').find('textarea').val(''); + $('#note').hide(); + } }, loadSingleProperties:function() { - var props = ['BDAY', 'NICKNAME', 'ORG']; + var props = ['BDAY', 'NICKNAME', 'ORG']; //, 'CATEGORIES']; // Clear all elements $('#ident .propertycontainer').each(function(){ if(props.indexOf($(this).data('element')) > -1) { @@ -407,6 +494,12 @@ Contacts={ $('#contact_identity').find('#org_label').show(); $('#contact_identity').find('#org_value').show(); break; + /*case 'CATEGORIES': + $('#contact_identity').find('#categories').val(value); + $('#contact_identity').find('#categories_value').data('checksum', checksum); + $('#contact_identity').find('#categories_label').show(); + $('#contact_identity').find('#categories_value').show(); + break;*/ } } else { $('#contacts_propertymenu a[data-type="'+props[prop]+'"]').parent().show(); @@ -460,36 +553,84 @@ Contacts={ $('#contact_identity').find('*[data-element="FN"]').data('checksum', this.data.FN[0]['checksum']); $('#contact_identity').show(); }, - loadCategories:function(){ + hasCategory:function(category) { if(this.data.CATEGORIES) { - // + for(var c in this.data.CATEGORIES[0]['value']) { + var cat = this.data.CATEGORIES[0]['value'][c]; + //console.log('hasCategory: ' + cat + ' === ' + category + '?'); + if(typeof cat === 'string' && (cat.toUpperCase() === category.toUpperCase())) { + //console.log('Yes'); + return true; + } + } } + return false; + }, + categoriesChanged:function(categories) { // Categories added/deleted. + console.log('categoriesChanged for ' + Contacts.UI.Card.id + ' : ' + categories); + var categorylist = $('#categories_value').find('select'); + categorylist.find('option').remove(); + for(var category in categories) { + console.log('categoriesChanged: ' + categories[category]); + var selected = Contacts.UI.Card.hasCategory(categories[category]) ? ' selected="selected"' : ''; + var item = '<option value="' + categories[category] + '"' + selected + '>' + categories[category] + '</option>'; + $(item).appendTo(categorylist); + } + $('#categories_value').find('select').multiselect('refresh'); + $.getJSON(OC.filePath('contacts', 'ajax', 'categories/checksumfor.php'),{'id':Contacts.UI.Card.id},function(jsondata){ + if(jsondata.status == 'success'){ + console.log('Setting checksum: ' + jsondata.data.checksum); + $('#categories_value').data('checksum', jsondata.data.checksum); + } else { + OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); + } + }); + }, + loadCategories:function(){ // On loading contact. + var categories = $('#categories_value').find('select'); + if(this.data.CATEGORIES) { + $('#categories_value').data('checksum', this.data.CATEGORIES[0]['checksum']); + } else { + $('#categories_value').data('checksum', ''); + } + categories.find('option').each(function(){ + if(Contacts.UI.Card.hasCategory($(this).val())) { + $(this).attr('selected', 'selected'); + } else { + $(this).removeAttr('selected'); + } + }); + categories.multiselect('refresh'); }, editNew:function(){ // add a new contact this.id = ''; this.fn = ''; this.fullname = ''; this.givname = ''; this.famname = ''; this.addname = ''; this.honpre = ''; this.honsuf = ''; - $.getJSON('ajax/newcontact.php',{},function(jsondata){ + $.getJSON(OC.filePath('contacts', 'ajax', 'newcontact.php'),{},function(jsondata){ if(jsondata.status == 'success'){ id = ''; $('#rightcontent').data('id',''); $('#rightcontent').html(jsondata.data.page); Contacts.UI.Card.editName(); - } - else{ - Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message); + } else { + OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); //alert(jsondata.data.message); } }); }, savePropertyInternal:function(name, fields, oldchecksum, checksum){ // TODO: Add functionality for new fields. - //console.log('savePropertyInternal: ' + name + ', checksum: ' + checksum); - //console.log('savePropertyInternal: ' + this.data[name]); + console.log('savePropertyInternal: ' + name + ', fields: ' + fields + 'checksum: ' + checksum); + console.log('savePropertyInternal: ' + this.data[name]); + var multivalue = ['CATEGORIES']; var params = {}; - var value = undefined; + var value = multivalue.indexOf(name) != -1 ? new Array() : undefined; jQuery.each(fields, function(i, field){ //.substring(11,'parameters[TYPE][]'.indexOf(']')) if(field.name.substring(0, 5) === 'value') { - value = field.value; + if(multivalue.indexOf(name) != -1) { + value.push(field.value); + } else { + value = field.value; + } } else if(field.name.substring(0, 10) === 'parameters') { var p = field.name.substring(11,'parameters[TYPE][]'.indexOf(']')); if(!(p in params)) { @@ -509,11 +650,11 @@ Contacts={ saveProperty:function(obj){ // I couldn't get the selector to filter on 'contacts_property' so I filter by hand here :-/ if(!$(obj).hasClass('contacts_property')) { - //console.log('Filtering out object.' + obj); + console.log('Filtering out object.' + obj); return false; } if($(obj).hasClass('nonempty') && $(obj).val().trim() == '') { - Contacts.UI.messageBox(t('contacts', 'Error'), t('contacts', 'This property has to be non-empty.')); + OC.dialogs.alert(t('contacts', 'This property has to be non-empty.'), t('contacts', 'Error')); return false; } container = $(obj).parents('.propertycontainer').first(); // get the parent holding the metadata. @@ -532,32 +673,38 @@ Contacts={ if(checksum != undefined && checksum != '') { // save q = q + '&checksum=' + checksum; console.log('Saving: ' + q); + $(obj).attr('disabled', 'disabled'); $.post('ajax/saveproperty.php',q,function(jsondata){ if(jsondata.status == 'success'){ container.data('checksum', jsondata.data.checksum); Contacts.UI.Card.savePropertyInternal(name, fields, checksum, jsondata.data.checksum); Contacts.UI.loading(container, false); + $(obj).removeAttr('disabled'); return true; } else{ - Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message); + OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); Contacts.UI.loading(container, false); + $(obj).removeAttr('disabled'); return false; } },'json'); } else { // add console.log('Adding: ' + q); + $(obj).attr('disabled', 'disabled'); $.post('ajax/addproperty.php',q,function(jsondata){ if(jsondata.status == 'success'){ container.data('checksum', jsondata.data.checksum); // TODO: savePropertyInternal doesn't know about new fields //Contacts.UI.Card.savePropertyInternal(name, fields, checksum, jsondata.data.checksum); Contacts.UI.loading(container, false); + $(obj).removeAttr('disabled'); return true; } else{ - Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message); + OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); Contacts.UI.loading(container, false); + $(obj).removeAttr('disabled'); return false; } },'json'); @@ -568,10 +715,14 @@ Contacts={ console.log('addProperty:' + type); switch (type) { case 'PHOTO': - this.loadPhoto(); + this.loadPhoto(true); $('#file_upload_form').show(); $('#contacts_propertymenu a[data-type="'+type+'"]').parent().hide(); break; + case 'NOTE': + $('#note').show(); + $('#contacts_propertymenu a[data-type="'+type+'"]').parent().hide(); + break; case 'EMAIL': if($('#emaillist>li').length == 1) { $('#emails').show(); @@ -596,6 +747,7 @@ Contacts={ case 'NICKNAME': case 'ORG': case 'BDAY': + case 'CATEGORIES': $('dl dt[data-element="'+type+'"],dd[data-element="'+type+'"]').show(); $('#contacts_propertymenu a[data-type="'+type+'"]').parent().hide(); break; @@ -619,13 +771,13 @@ Contacts={ $('#contacts_propertymenu a[data-type="'+proptype+'"]').parent().show(); Contacts.UI.loading(obj, false); } else { - Contacts.UI.messageBox(t('contacts', 'Error'), t('contacts', '\'deleteProperty\' called without type argument. Please report at bugs.owncloud.org')); + OC.dialogs.alert(t('contacts', '\'deleteProperty\' called without type argument. Please report at bugs.owncloud.org'), t('contacts', 'Error')); Contacts.UI.loading(obj, false); } } else{ Contacts.UI.loading(obj, false); - Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message); + OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); } }); } else { // Property hasn't been saved so there's nothing to delete. @@ -640,7 +792,7 @@ Contacts={ $('#contacts_propertymenu a[data-type="'+proptype+'"]').parent().show(); Contacts.UI.loading(obj, false); } else { - Contacts.UI.messageBox(t('contacts', 'Error'), t('contacts', '\'deleteProperty\' called without type argument. Please report at bugs.owncloud.org')); + OC.dialogs.alert(t('contacts', '\'deleteProperty\' called without type argument. Please report at bugs.owncloud.org'), t('contacts', 'Error')); } } }, @@ -650,8 +802,9 @@ Contacts={ if($('#edit_name_dialog').dialog('isOpen') == true){ $('#edit_name_dialog').dialog('moveToTop'); }else{ // TODO: If id=='' call addcontact.php (or whatever name) instead and reload view with id. - $('#dialog_holder').load(OC.filePath('contacts', 'ajax', 'editname.php')+'?id='+this.id, function(){ - $('#edit_name_dialog' ).dialog({ + $('#dialog_holder').load(OC.filePath('contacts', 'ajax', 'editname.php')+'?id='+this.id, function(jsondata){ + if(jsondata.status != 'error'){ + $('#edit_name_dialog' ).dialog({ modal: (isnew && true || false), closeOnEscape: (isnew == '' && false || true), title: (isnew && t('contacts', 'Add contact') || t('contacts', 'Edit name')), @@ -670,7 +823,10 @@ Contacts={ open : function(event, ui) { // load 'N' property - maybe :-P }*/ - }); + }); + } else { + alert(jsondata.data.message); + } }); } }, @@ -695,7 +851,14 @@ Contacts={ $('#fn_select option').remove(); //$('#fn_select').combobox('value', this.fn); - var names = [this.fullname, this.givname + ' ' + this.famname, this.famname + ' ' + this.givname, this.famname + ', ' + this.givname]; + var tmp = [this.fullname, this.givname + ' ' + this.famname, this.famname + ' ' + this.givname, this.famname + ', ' + this.givname]; + var names = new Array(); + for(var name in tmp) { + console.log('idx: ' + names.indexOf(tmp[name])); + if(names.indexOf(tmp[name]) == -1) { + names.push(tmp[name]); + } + } $.each(names, function(key, value) { $('#fn_select') .append($('<option></option>') @@ -774,8 +937,9 @@ Contacts={ if($('#edit_address_dialog').dialog('isOpen') == true){ $('#edit_address_dialog').dialog('moveToTop'); }else{ - $('#dialog_holder').load(OC.filePath('contacts', 'ajax', 'editaddress.php')+q, function(){ - $('#edit_address_dialog' ).dialog({ + $('#dialog_holder').load(OC.filePath('contacts', 'ajax', 'editaddress.php')+q, function(jsondata){ + if(jsondata.status != 'error'){ + $('#edit_address_dialog' ).dialog({ /*modal: true,*/ height: 'auto', width: 'auto', buttons: { @@ -806,7 +970,10 @@ Contacts={ open : function(event, ui) { // load 'ADR' property - maybe :-P }*/ - }); + }); + } else { + alert(jsondata.data.message); + } }); } }, @@ -846,7 +1013,7 @@ Contacts={ }, uploadPhoto:function(filelist) { if(!filelist) { - Contacts.UI.messageBox(t('contacts', 'Error'), t('contacts','No files selected for upload.')); + OC.dialogs.alert(t('contacts','No files selected for upload.'), t('contacts', 'Error')); return; } //var file = filelist.item(0); @@ -855,7 +1022,7 @@ Contacts={ var form = $('#file_upload_form'); var totalSize=0; if(file.size > $('#max_upload').val()){ - Contacts.UI.messageBox(t('Upload too large'), t('contacts','The file you are trying to upload exceed the maximum size for file uploads on this server.')); + OC.dialogs.alert(t('contacts','The file you are trying to upload exceed the maximum size for file uploads on this server.'), t('contacts', 'Error')); return; } else { target.load(function(){ @@ -864,21 +1031,21 @@ Contacts={ Contacts.UI.Card.editPhoto(response.data.id, response.data.tmp); //alert('File: ' + file.tmp + ' ' + file.name + ' ' + file.mime); }else{ - Contacts.UI.messageBox(t('contacts', 'Error'), response.data.message); + OC.dialogs.alert(response.data.message, t('contacts', 'Error')); } }); form.submit(); } }, - loadPhoto:function(){ - if(this.data.PHOTO) { + loadPhoto:function(force){ + if(this.data.PHOTO||force==true) { $.getJSON('ajax/loadphoto.php',{'id':this.id},function(jsondata){ if(jsondata.status == 'success'){ //alert(jsondata.data.page); $('#contacts_details_photo_wrapper').html(jsondata.data.page); } else{ - Contacts.UI.messageBox(jsondata.data.message); + OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); } }); $('#file_upload_form').show(); @@ -897,7 +1064,7 @@ Contacts={ $('#edit_photo_dialog_img').html(jsondata.data.page); } else{ - Contacts.UI.messageBox(jsondata.data.message); + OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); } }); if($('#edit_photo_dialog').dialog('isOpen') == true){ @@ -916,7 +1083,7 @@ Contacts={ // load cropped photo. $('#contacts_details_photo_wrapper').html(response.data.page); }else{ - Contacts.UI.messageBox(t('contacts','Error'), response.data.message); + OC.dialogs.alert(response.data.message, t('contacts', 'Error')); } }); $('#contacts [data-id="'+this.id+'"]').find('a').css('background','url(thumbnail.php?id='+this.id+'&refresh=1'+Math.random()+') no-repeat'); @@ -1009,13 +1176,17 @@ Contacts={ if($('#chooseaddressbook_dialog').dialog('isOpen') == true){ $('#chooseaddressbook_dialog').dialog('moveToTop'); }else{ - $('#dialog_holder').load(OC.filePath('contacts', 'ajax', 'chooseaddressbook.php'), function(){ - $('#chooseaddressbook_dialog').dialog({ - width : 600, - close : function(event, ui) { - $(this).dialog('destroy').remove(); - } - }); + $('#dialog_holder').load(OC.filePath('contacts', 'ajax', 'chooseaddressbook.php'), function(jsondata){ + if(jsondata.status != 'error'){ + $('#chooseaddressbook_dialog').dialog({ + width : 600, + close : function(event, ui) { + $(this).dialog('destroy').remove(); + } + }); + } else { + alert(jsondata.data.message); + } }); } }, @@ -1052,7 +1223,7 @@ Contacts={ Contacts.UI.Contacts.update(); Contacts.UI.Addressbooks.overview(); } else { - Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message); + OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); //alert('Error: ' + data.message); } }); @@ -1067,7 +1238,7 @@ Contacts={ var description = $("#description_"+bookid).val(); if(displayname.length == 0) { - Contacts.UI.messageBox(t('contacts', 'Error'), t('contacts', 'Displayname cannot be empty.')); + OC.dialogs.alert(t('contacts', 'Displayname cannot be empty.'), t('contacts', 'Error')); return false; } var url; @@ -1082,7 +1253,7 @@ Contacts={ $(button).closest('tr').prev().html(jsondata.page).show().next().remove(); Contacts.UI.Contacts.update(); } else { - Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message); + OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); } }); }, @@ -1102,7 +1273,7 @@ Contacts={ Contacts.UI.Card.update(); } else{ - Contacts.UI.messageBox(t('contacts', 'Error'),jsondata.data.message); + OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); //alert(jsondata.data.message); } }); @@ -1124,6 +1295,7 @@ Contacts={ $(document).ready(function(){ Contacts.UI.loadHandlers(); + Categories.changed = Contacts.UI.Card.categoriesChanged; /** * Show the Addressbook chooser @@ -1151,7 +1323,7 @@ $(document).ready(function(){ Contacts.UI.Card.loadContact(jsondata.data); } else{ - Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message); + OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); //alert(jsondata.data.message); } }); @@ -1192,7 +1364,8 @@ $(document).ready(function(){ // NOTE: For some reason the selector doesn't work when I select by '.contacts_property' too... // I do the filtering in the event handler instead. - $('input[type="text"],input[type="checkbox"],input[type="email"],input[type="tel"],input[type="date"], select').live('change', function(){ + //$('input[type="text"],input[type="checkbox"],input[type="email"],input[type="tel"],input[type="date"], select').live('change', function(){ + $('.contacts_property').live('change', function(){ Contacts.UI.Card.saveProperty(this); }); @@ -1249,17 +1422,17 @@ $(document).ready(function(){ var file = files[0]; console.log('size: '+file.size); if(file.size > $('#max_upload').val()){ - Contacts.UI.messageBox(t('contacts','Upload too large'), t('contacts','The file you are trying to upload exceed the maximum size for file uploads on this server.')); + 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) { - Contacts.UI.messageBox(t('contacts','Wrong file type'), t('contacts','Only image files can be used as profile picture.')); + 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) { - Contacts.UI.messageBox(t('contacts', 'Error'), t('contacts', 'Your browser doesn\'t support AJAX upload. Please click on the profile picture to select a photo to 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() { @@ -1269,11 +1442,11 @@ $(document).ready(function(){ if(xhr.status == 200) { Contacts.UI.Card.editPhoto(response.data.id, response.data.tmp); } else { - Contacts.UI.messageBox(t('contacts', 'Error'), xhr.status + ': ' + xhr.responseText); + OC.dialogs.alert(xhr.status + ': ' + xhr.responseText, t('contacts', 'Error')); } } else { //alert(xhr.responseText); - Contacts.UI.messageBox(t('contacts', 'Error'), response.data.message); + OC.dialogs.alert(response.data.message, t('contacts', 'Error')); } // stop loading indicator //$('#contacts_details_photo_progress').hide(); @@ -1301,8 +1474,19 @@ $(document).ready(function(){ xhr.send(file); } + $('body').live('click',function(e){ + if(!$(e.target).is('#contacts_propertymenu_button')) { + $('#contacts_propertymenu').hide(); + } + }); $('#contacts_propertymenu_button').live('click',function(){ - $('#contacts_propertymenu').is(':hidden') && $('#contacts_propertymenu').slideDown() || $('#contacts_propertymenu').slideUp(); + var menu = $('#contacts_propertymenu'); + if(menu.is(':hidden')) { + menu.show(); + menu.find('ul').focus(); + } else { + menu.hide(); + } }); $('#contacts_propertymenu a').live('click',function(){ Contacts.UI.Card.addProperty(this); diff --git a/apps/contacts/js/jquery.multi-autocomplete.js b/apps/contacts/js/jquery.multi-autocomplete.js new file mode 100644 index 00000000000..4be8d901c96 --- /dev/null +++ b/apps/contacts/js/jquery.multi-autocomplete.js @@ -0,0 +1,47 @@ +/** + * Inspired by http://jqueryui.com/demos/autocomplete/#multiple + */ + +(function( $ ) { + $.widget('ui.multiple_autocomplete', { + _create: function() { + function split( val ) { + return val.split( /,\s*/ ); + } + function extractLast( term ) { + return split( term ).pop(); + } + //console.log('_create: ' + this.options['id']); + var self = this; + this.element.bind( "keydown", function( event ) { + if ( event.keyCode === $.ui.keyCode.TAB && + $( this ).data( "autocomplete" ).menu.active ) { + event.preventDefault(); + } + }) + .autocomplete({ + minLength: 0, + source: function( request, response ) { + // delegate back to autocomplete, but extract the last term + response( $.ui.autocomplete.filter( + self.options.source, extractLast( request.term ) ) ); + }, + focus: function() { + // prevent value inserted on focus + return false; + }, + select: function( event, ui ) { + var terms = split( this.value ); + // remove the current input + terms.pop(); + // add the selected item + terms.push( ui.item.value ); + // add placeholder to get the comma-and-space at the end + terms.push( "" ); + this.value = terms.join( ", " ); + return false; + } + }); + }, + }); +})( jQuery ); diff --git a/apps/contacts/lib/app.php b/apps/contacts/lib/app.php index e8c3087c8a2..cc33c733007 100644 --- a/apps/contacts/lib/app.php +++ b/apps/contacts/lib/app.php @@ -10,8 +10,10 @@ * This class manages our app actions */ OC_Contacts_App::$l10n = new OC_L10N('contacts'); +OC_Contacts_App::$categories = new OC_VCategories('contacts'); class OC_Contacts_App { public static $l10n; + public static $categories; /** * Render templates/part.details to json output @@ -153,6 +155,10 @@ class OC_Contacts_App { } } + public static function getCategories() { + return self::$categories->categories(); + } + public static function setLastModifiedHeader($contact) { $rev = $contact->getAsString('REV'); if ($rev) { diff --git a/apps/contacts/lib/vcard.php b/apps/contacts/lib/vcard.php index eade8859e76..a7e1817d7c8 100644 --- a/apps/contacts/lib/vcard.php +++ b/apps/contacts/lib/vcard.php @@ -4,6 +4,7 @@ * * @author Jakob Sack * @copyright 2011 Jakob Sack mail@jakobsack.de + * @copyright 2012 Thomas Tanghus <thomas@tanghus.net> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE @@ -233,6 +234,8 @@ class OC_Contacts_VCard{ return null; }; + OC_Contacts_App::$categories->loadFromVObject($card); + self::updateValuesFromAdd($card); $fn = $card->getAsString('FN'); @@ -268,6 +271,29 @@ class OC_Contacts_VCard{ } /** + * @brief Mass updates an array of cards + * @param array $objects An array of [id, carddata]. + */ + public static function updateDataByID($objects){ + $stmt = OC_DB::prepare( 'UPDATE *PREFIX*contacts_cards SET carddata = ?, lastmodified = ? WHERE id = ?' ); + $now = new DateTime; + foreach($objects as $object) { + $vcard = OC_VObject::parse($object[1]); + if(!is_null($vcard)){ + $vcard->setString('REV', $now->format(DateTime::W3C)); + $data = $vcard->serialize(); + try { + $result = $stmt->execute(array($data,time(),$object[0])); + //OC_Log::write('contacts','OC_Contacts_VCard::updateDataByID, id: '.$object[0].': '.$object[1],OC_Log::DEBUG); + } catch(Exception $e) { + OC_Log::write('contacts','OC_Contacts_VCard::updateDataByID:, exception: '.$e->getMessage(),OC_Log::DEBUG); + OC_Log::write('contacts','OC_Contacts_VCard::updateDataByID, id: '.$object[0],OC_Log::DEBUG); + } + } + } + } + + /** * @brief edits a card * @param integer $id id of card * @param OC_VObject $card vCard file @@ -280,6 +306,8 @@ class OC_Contacts_VCard{ return false; } + OC_Contacts_App::$categories->loadFromVObject($card); + $fn = $card->getAsString('FN'); if (empty($fn)) { $fn = null; @@ -339,6 +367,43 @@ class OC_Contacts_VCard{ } /** + * @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;$i<count($array);$i++) { + if(substr($array[$i],-1,1)=="\\") { + if(isset($array[$i+1])) { + $array[$i] = substr($array[$i],0,count($array[$i])-2).$delimiter.$array[$i+1]; + unset($array[$i+1]); + } else { + $array[$i] = substr($array[$i],0,count($array[$i])-2).$delimiter; + } + $i = $i - 1; + } + } + $array = array_map('trim', $array); + return $array; + } + + /** * @brief Data structure of vCard * @param object $property * @return associative array @@ -376,7 +441,9 @@ class OC_Contacts_VCard{ $value = $property->value; //$value = htmlspecialchars($value); if($property->name == 'ADR' || $property->name == 'N'){ - $value = OC_VObject::unescapeSemicolons($value); + $value = self::unescapeDelimiters($value); + } elseif($property->name == 'CATEGORIES') { + $value = self::unescapeDelimiters($value, ','); } $temp = array( 'name' => $property->name, diff --git a/apps/contacts/templates/index.php b/apps/contacts/templates/index.php index e81597f23d6..efd797e25cb 100644 --- a/apps/contacts/templates/index.php +++ b/apps/contacts/templates/index.php @@ -1,5 +1,6 @@ <script type='text/javascript'> var totalurl = '<?php echo OC_Helper::linkToAbsolute('contacts', 'carddav.php'); ?>/addressbooks'; + var categories = <?php echo json_encode($_['categories']); ?>; </script> <div id="controls"> <form> diff --git a/apps/contacts/templates/part.contact.php b/apps/contacts/templates/part.contact.php index 5be20964f4b..67838238e2b 100644 --- a/apps/contacts/templates/part.contact.php +++ b/apps/contacts/templates/part.contact.php @@ -13,6 +13,8 @@ $id = isset($_['id']) ? $_['id'] : ''; <li><a data-type="TEL"><?php echo $l->t('Phone'); ?></a></li> <li><a data-type="EMAIL"><?php echo $l->t('Email'); ?></a></li> <li><a data-type="ADR"><?php echo $l->t('Address'); ?></a></li> + <li><a data-type="NOTE"><?php echo $l->t('Note'); ?></a></li> + <li><a data-type="CATEGORIES"><?php echo $l->t('Categories'); ?></a></li> </ul> </div> <img onclick="Contacts.UI.Card.export();" class="svg action" id="contacts_downloadcard" src="<?php echo image_path('', 'actions/download.svg'); ?>" title="<?php echo $l->t('Download contact');?>" /> @@ -45,7 +47,7 @@ $id = isset($_['id']) ? $_['id'] : ''; <dt><label for="fn"><?php echo $l->t('Display name'); ?></label></dt> <dd class="propertycontainer" data-element="FN"> <select id="fn_select" title="<?php echo $l->t('Format custom, Short name, Full name, Reverse or Reverse with comma'); ?>" style="width:16em;"> - </select><a id="edit_name" class="edit" title="<?php echo $l->t('Edit name details'); ?>"></a> + </select><a id="edit_name" class="action edit" title="<?php echo $l->t('Edit name details'); ?>"></a> </dd> <dt style="display:none;" id="org_label" data-element="ORG"><label for="org"><?php echo $l->t('Organization'); ?></label></dt> <dd style="display:none;" class="propertycontainer" id="org_value" data-element="ORG"><input id="org" required="required" name="value[ORG]" type="text" class="contacts_property" style="width:16em;" name="value" value="" placeholder="<?php echo $l->t('Organization'); ?>" /><a class="delete" onclick="$(this).tipsy('hide');Contacts.UI.Card.deleteProperty(this, 'single');" title="<?php echo $l->t('Delete'); ?>"></a></dd> @@ -53,8 +55,21 @@ $id = isset($_['id']) ? $_['id'] : ''; <dd style="display:none;" class="propertycontainer" id="nickname_value" data-element="NICKNAME"><input id="nickname" required="required" name="value[NICKNAME]" type="text" class="contacts_property" style="width:16em;" name="value" value="" placeholder="<?php echo $l->t('Enter nickname'); ?>" /><a class="delete" onclick="$(this).tipsy('hide');Contacts.UI.Card.deleteProperty(this, 'single');" title="<?php echo $l->t('Delete'); ?>"></a></dd> <dt style="display:none;" id="bday_label" data-element="BDAY"><label for="bday"><?php echo $l->t('Birthday'); ?></label></dt> <dd style="display:none;" class="propertycontainer" id="bday_value" data-element="BDAY"><input id="bday" required="required" name="value" type="text" class="contacts_property" value="" placeholder="<?php echo $l->t('dd-mm-yyyy'); ?>" /><a class="delete" onclick="$(this).tipsy('hide');Contacts.UI.Card.deleteProperty(this, 'single');" title="<?php echo $l->t('Delete'); ?>"></a></dd> + <dt id="categories_label" data-element="CATEGORIES"><label for="categories"><?php echo $l->t('Categories'); ?></label></dt> + <dd class="propertycontainer" id="categories_value" data-element="CATEGORIES"> + <select class="contacts_property" multiple="multiple" id="categories" name="value[]"> + <?php echo html_select_options($_['categories'], array(), array('combine'=>true)) ?> + </select> + <a class="action edit" onclick="$(this).tipsy('hide');Categories.edit();" title="<?php echo $l->t('Edit categories'); ?>"></a> + </dd> + <!-- dt style="display:none;" id="categories_label" data-element="CATEGORIES"><label for="categories"><?php echo $l->t('Categories'); ?></label></dt> + <dd style="display:none;" class="propertycontainer" id="categories_value" data-element="CATEGORIES"><input id="categories" required="required" name="value[CATEGORIES]" type="text" class="contacts_property" style="width:16em;" name="value" value="" placeholder="<?php echo $l->t('Categories'); ?>" /><a class="delete" onclick="$(this).tipsy('hide');Contacts.UI.Card.deleteProperty(this, 'single');" title="<?php echo $l->t('Delete'); ?>"></a><a class="action edit" onclick="$(this).tipsy('hide');Categories.edit();" title="<?php echo $l->t('Edit categories'); ?>"></a></dd --> </dl> </fieldset> + <fieldset id="note" class="formfloat propertycontainer" style="display:none;" data-element="NOTE"> + <legend><?php echo $l->t('Note'); ?></legend> + <textarea class="contacts_property note" name="value"></textarea> + </fieldset> </form> </div> diff --git a/lib/vcategories.php b/lib/vcategories.php new file mode 100644 index 00000000000..7a31a5268d1 --- /dev/null +++ b/lib/vcategories.php @@ -0,0 +1,209 @@ +<?php +/** +* ownCloud +* +* @author Thomas Tanghus +* @copyright 2012 Thomas Tanghus <thomas@tanghus.net> +* @copyright 2012 Bart Visscher bartv@thisnet.nl +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU AFFERO GENERAL PUBLIC LICENSE for more details. +* +* You should have received a copy of the GNU Affero General Public +* License along with this library. If not, see <http://www.gnu.org/licenses/>. +* +*/ + + +/** + * Class for easy access to categories in VCARD, VEVENT, VTODO and VJOURNAL. + * A Category can be e.g. 'Family', 'Work', 'Chore', 'Special Occation' or + * anything else that is either parsed from a vobject or that the user chooses + * to add. + * Category names are not case-sensitive, but will be saved with the case they + * are entered in. If a user already has a category 'family' for an app, and + * tries to add a category named 'Family' it will be silently ignored. + * NOTE: There is a limitation in that the the configvalue field in the + * preferences table is a varchar(255). + */ +class OC_VCategories { + const PREF_CATEGORIES_LABEL = 'extra_categories'; + /** + * Categories + */ + private $categories = array(); + + private $app = null; + private $user = null; + + /** + * @brief Constructor. + * @param $app The application identifier e.g. 'contacts' or 'calendar'. + * @param $user The user whos data the object will operate on. This + * parameter should normally be omitted but to make an app able to + * update categories for all users it is made possible to provide it. + * @param $defcategories An array of default categories to be used if none is stored. + * NOTE: Not implemented. + */ + public function __construct($app, $user=null, $defcategories=null) { + $this->app = $app; + $this->user = is_null($user) ? OC_User::getUser() : $user; + $categories = trim(OC_Preferences::getValue($this->user, $app, self::PREF_CATEGORIES_LABEL, '')); + $this->categories = $categories != '' ? unserialize($categories) : array(); + } + + /** + * @brief Get the categories for a specific user. + * @returns array containing the categories as strings. + */ + public function categories() { + OC_Log::write('core','OC_VCategories::categories: '.print_r($this->categories, true), OC_Log::DEBUG); + return $this->categories; + } + + /** + * @brief Checks whether a category is already saved. + * @param $name The name to check for. + * @returns bool + */ + public function hasCategory($name) { + return $this->in_arrayi($name, $this->categories); + } + + /** + * @brief Add a new category name. + * @param $names A string with a name or an array of strings containing + * the name(s) of the categor(y|ies) to add. + * @param $sync bool When true, save the categories + * @returns bool Returns false on error. + */ + public function add($names, $sync=false) { + if(!is_array($names)) { + $names = array($names); + } + $names = array_map('trim', $names); + $newones = array(); + foreach($names as $name) { + if(($this->in_arrayi($name, $this->categories) == false) && $name != '') { + $newones[] = $name; + } + } + if(count($newones) > 0) { + $this->categories = array_merge($this->categories, $newones); + natcasesort($this->categories); // Dunno if this is necessary + if($sync === true) { + $this->save(); + } + } + return true; + } + + /** + * @brief Extracts categories from a vobject and add the ones not already present. + * @param $vobject The instance of OC_VObject to load the categories from. + */ + public function loadFromVObject($vobject, $sync=false) { + $this->add($vobject->getAsArray('CATEGORIES'), $sync); + } + + /** + * @brief Reset saved categories and rescan supplied vobjects for categories. + * @param $objects An array of vobjects (as text). + * To get the object array, do something like: + * // For Addressbook: + * $categories = new OC_VCategories('contacts'); + * $stmt = OC_DB::prepare( 'SELECT carddata FROM *PREFIX*contacts_cards' ); + * $result = $stmt->execute(); + * $objects = array(); + * if(!is_null($result)) { + * while( $row = $result->fetchRow()){ + * $objects[] = $row['carddata']; + * } + * } + * $categories->rescan($objects); + */ + public function rescan($objects, $sync=true) { + $this->categories = array(); + foreach($objects as $object) { + //OC_Log::write('core','OC_VCategories::rescan: '.substr($object, 0, 100).'(...)', OC_Log::DEBUG); + $vobject = OC_VObject::parse($object); + if(!is_null($vobject)) { + $this->loadFromVObject($vobject, $sync); + } else { + OC_Log::write('core','OC_VCategories::rescan, unable to parse. ID: '.$value[0].', '.substr($value[1], 0, 50).'(...)', OC_Log::DEBUG); + } + } + $this->save(); + } + + /** + * @brief Save the list with categories + */ + private function save() { + $escaped_categories = serialize($this->categories); + OC_Log::write('core','OC_VCategories::save: '.print_r($this->categories, true), OC_Log::DEBUG); + OC_Preferences::setValue($this->user, $this->app, self::PREF_CATEGORIES_LABEL, $escaped_categories); + } + + /** + * @brief Delete categories from the db and from all the vobject supplied + * @param $names An array of categories to delete + * @param $objects An array of arrays with [id,vobject] (as text) pairs suitable for updating the apps object table. + */ + public function delete($names, array &$objects) { + if(!is_array($names)) { + $names = array($names); + } + OC_Log::write('core','OC_VCategories::delete, before: '.print_r($this->categories, true), OC_Log::DEBUG); + foreach($names as $name) { + OC_Log::write('core','OC_VCategories::delete: '.$name, OC_Log::DEBUG); + if($this->hasCategory($name)) { + OC_Log::write('core','OC_VCategories::delete: '.$name.' got it', OC_Log::DEBUG); + unset($this->categories[$this->array_searchi($name, $this->categories)]); + } + } + $this->save(); + OC_Log::write('core','OC_VCategories::delete, after: '.print_r($this->categories, true), OC_Log::DEBUG); + foreach($objects as $key=>&$value) { + $vobject = OC_VObject::parse($value[1]); + if(!is_null($vobject)){ + $categories = $vobject->getAsArray('CATEGORIES'); + //OC_Log::write('core','OC_VCategories::delete, before: '.$key.': '.print_r($categories, true), OC_Log::DEBUG); + foreach($names as $name) { + $idx = $this->array_searchi($name, $categories); + OC_Log::write('core','OC_VCategories::delete, loop: '.$name.', '.print_r($idx, true), OC_Log::DEBUG); + if($idx !== false) { + OC_Log::write('core','OC_VCategories::delete, unsetting: '.$categories[$this->array_searchi($name, $categories)], OC_Log::DEBUG); + unset($categories[$this->array_searchi($name, $categories)]); + //unset($categories[$idx]); + } + } + OC_Log::write('core','OC_VCategories::delete, after: '.$key.': '.print_r($categories, true), OC_Log::DEBUG); + $vobject->setString('CATEGORIES', implode(',', $categories)); + $value[1] = $vobject->serialize(); + $objects[$key] = $value; + } else { + OC_Log::write('core','OC_VCategories::delete, unable to parse. ID: '.$value[0].', '.substr($value[1], 0, 50).'(...)', OC_Log::DEBUG); + } + } + } + + // case-insensitive in_array + private function in_arrayi($needle, $haystack) { + return in_array(strtolower($needle), array_map('strtolower', $haystack)); + } + + // case-insensitive array_search + private function array_searchi($needle, $haystack) { + return array_search(strtolower($needle),array_map('strtolower',$haystack)); + } + +} +?> |