1
0
mirror of https://github.com/owncloudarchive/contacts.git synced 2024-11-28 10:24:11 +01:00

Merge branch 'contacts_backends' into contacts_oc6

Conflicts:
	contacts/ajax/categories/add.php
	contacts/ajax/contact/add.php
	contacts/appinfo/app.php
	contacts/appinfo/migrate.php
	contacts/css/contacts.css
	contacts/index.php
	contacts/js/app.js
	contacts/js/contacts.js
	contacts/js/groups.js
	contacts/lib/app.php
	contacts/lib/carddav/addressbook.php
	contacts/lib/sabre/backend.php
	contacts/lib/vcard.php
	contacts/templates/contacts.php
	contacts/thumbnail.php
This commit is contained in:
Thomas Tanghus 2013-04-17 15:07:59 +02:00
commit c3fa18e4a7
70 changed files with 7571 additions and 3669 deletions

View File

@ -1,37 +0,0 @@
<?php
/**
* Copyright (c) 2011-2012 Thomas Tanghus <thomas@tanghus.net>
* Copyright (c) 2011 Bart Visscher <bartv@thisnet.nl>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
// Check if we are a user
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
OCP\JSON::callCheck();
require_once __DIR__.'/../loghandler.php';
debug('name: '.$_POST['name']);
$userid = OCP\USER::getUser();
$name = isset($_POST['name'])?trim(strip_tags($_POST['name'])):null;
$description = isset($_POST['description'])
? trim(strip_tags($_POST['description']))
: null;
if(is_null($name)) {
bailOut('Cannot add addressbook with an empty name.');
}
$bookid = OCA\Contacts\Addressbook::add($userid, $name, $description);
if(!$bookid) {
bailOut('Error adding addressbook: '.$name);
}
if(!OCA\Contacts\Addressbook::setActive($bookid, 1)) {
bailOut('Error activating addressbook.');
}
$addressbook = OCA\Contacts\Addressbook::find($bookid);
OCP\JSON::success(array('data' => array('addressbook' => $addressbook)));

View File

@ -1,29 +0,0 @@
<?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.
*/
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
OCP\JSON::callCheck();
require_once __DIR__.'/../loghandler.php';
$category = isset($_POST['category']) ? trim(strip_tags($_POST['category'])) : null;
if(is_null($category) || $category === "") {
bailOut(OCA\Contacts\App::$l10n->t('No category name given.'));
}
$catman = new OC_VCategories('contact');
$id = $catman->add($category);
if($id !== false) {
OCP\JSON::success(array('data' => array('id'=>$id, 'name' => $category)));
} else {
bailOut(OCA\Contacts\App::$l10n->t('Error adding group.'));
}

View File

@ -1,34 +0,0 @@
<?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.
*/
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
OCP\JSON::callCheck();
require_once __DIR__.'/../loghandler.php';
$categoryid = isset($_POST['categoryid']) ? $_POST['categoryid'] : null;
$contactids = isset($_POST['contactids']) ? $_POST['contactids'] : null;
if(is_null($categoryid)) {
bailOut(OCA\Contacts\App::$l10n->t('Group ID missing from request.'));
}
if(is_null($contactids)) {
bailOut(OCA\Contacts\App::$l10n->t('Contact ID missing from request.'));
}
$catmgr = OCA\Contacts\App::getVCategories();
foreach($contactids as $contactid) {
debug('contactid: ' . $contactid . ', categoryid: ' . $categoryid);
$catmgr->addToCategory($contactid, $categoryid);
}
OCP\JSON::success();

View File

@ -1,51 +0,0 @@
<?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.
*/
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
OCP\JSON::callCheck();
require_once __DIR__.'/../loghandler.php';
$categories = isset($_POST['categories']) ? $_POST['categories'] : null;
$fromobjects = (isset($_POST['fromobjects'])
&& ($_POST['fromobjects'] === 'true' || $_POST['fromobjects'] === '1')) ? true : false;
if(is_null($categories)) {
bailOut(OCA\Contacts\App::$l10n->t('No categories selected for deletion.'));
}
debug(print_r($categories, true));
if($fromobjects) {
$addressbooks = OCA\Contacts\Addressbook::all(OCP\USER::getUser());
if(count($addressbooks) == 0) {
bailOut(OCA\Contacts\App::$l10n->t('No address books found.'));
}
$addressbookids = array();
foreach($addressbooks as $addressbook) {
$addressbookids[] = $addressbook['id'];
}
$contacts = OCA\Contacts\VCard::all($addressbookids);
if(count($contacts) == 0) {
bailOut(OCA\Contacts\App::$l10n->t('No contacts found.'));
}
$cards = array();
foreach($contacts as $contact) {
$cards[] = array($contact['id'], $contact['carddata']);
}
}
$catman = new OC_VCategories('contact');
$catman->delete($categories, $cards);
if($fromobjects) {
OCA\Contacts\VCard::updateDataByID($cards);
}
OCP\JSON::success();

View File

@ -1,46 +0,0 @@
<?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.
*/
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
$catmgr = OCA\Contacts\App::getVCategories();
$categories = $catmgr->categories(OC_VCategories::FORMAT_MAP);
foreach($categories as &$category) {
$ids = array();
$contacts = $catmgr->itemsForCategory(
$category['name'],
array(
'tablename' => '*PREFIX*contacts_cards',
'fields' => array('id',),
));
foreach($contacts as $contact) {
$ids[] = $contact['id'];
}
$category['contacts'] = $ids;
}
$favorites = $catmgr->getFavorites();
OCP\JSON::success(array(
'data' => array(
'categories' => $categories,
'favorites' => $favorites,
'shared' => OCP\Share::getItemsSharedWith('addressbook', OCA\Contacts\Share_Backend_Addressbook::FORMAT_ADDRESSBOOKS),
'lastgroup' => OCP\Config::getUserValue(
OCP\User::getUser(),
'contacts',
'lastgroup', 'all'),
'sortorder' => OCP\Config::getUserValue(
OCP\User::getUser(),
'contacts',
'groupsort', ''),
)
)
);

View File

@ -1,34 +0,0 @@
<?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.
*/
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
OCP\JSON::callCheck();
require_once __DIR__.'/../loghandler.php';
$categoryid = isset($_POST['categoryid']) ? $_POST['categoryid'] : null;
$contactids = isset($_POST['contactids']) ? $_POST['contactids'] : null;
if(is_null($categoryid)) {
bailOut(OCA\Contacts\App::$l10n->t('Group ID missing from request.'));
}
if(is_null($contactids)) {
bailOut(OCA\Contacts\App::$l10n->t('Contact ID missing from request.'));
}
$catmgr = OCA\Contacts\App::getVCategories();
foreach($contactids as $contactid) {
debug('id: ' . $contactid .', categoryid: ' . $categoryid);
$catmgr->removeFromCategory($contactid, $categoryid);
}
OCP\JSON::success();

View File

@ -1,73 +0,0 @@
<?php
/**
* ownCloud - Addressbook
*
* @author Thomas Tanghus
* @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
* 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/>.
*
*/
// Check if we are a user
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
OCP\JSON::callCheck();
require_once __DIR__.'/../loghandler.php';
$aid = isset($_POST['aid']) ? $_POST['aid'] : null;
if(!$aid) {
$ids = OCA\Contacts\Addressbook::activeIds();
if(count($ids) > 0) {
$aid = min($ids); // first active addressbook.
} else {
$addressbooks = OCA\Contacts\Addressbook::all(OCP\User::getUser());
if(count($addressbooks) === 0) {
bailOut(OCA\Contacts\App::$l10n->t('You have no addressbooks.'));
} else {
$aid = $addressbooks[0]['id'];
}
}
}
$isnew = isset($_POST['isnew']) ? $_POST['isnew'] : false;
$vcard = Sabre\VObject\Component::create('VCARD');
$uid = substr(md5(rand().time()), 0, 10);
$vcard->add('UID', $uid);
$id = null;
try {
$id = OCA\Contacts\VCard::add($aid, $vcard, null, $isnew);
} catch(Exception $e) {
bailOut($e->getMessage());
}
if(!$id) {
bailOut('There was an error adding the contact.');
}
$lastmodified = OCA\Contacts\App::lastModified($vcard);
if(!$lastmodified) {
$lastmodified = new DateTime();
}
OCP\JSON::success(array(
'data' => array(
'id' => $id,
'aid' => $aid,
'details' => OCA\Contacts\VCard::structureContact($vcard),
'lastmodified' => $lastmodified->format('U')
)
));

View File

@ -1,41 +0,0 @@
<?php
/**
* ownCloud - Addressbook
*
* @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
* 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/>.
*
*/
// Check if we are a user
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
OCP\JSON::callCheck();
require_once __DIR__.'/../loghandler.php';
$id = isset($_POST['id']) ? $_POST['id'] : null;
if(!$id) {
bailOut(OCA\Contacts\App::$l10n->t('id is not set.'));
}
try {
OCA\Contacts\VCard::delete($id);
} catch(Exception $e) {
bailOut($e->getMessage());
}
OCP\JSON::success(array('data' => array( 'id' => $id )));

View File

@ -1,73 +0,0 @@
<?php
/**
* ownCloud - Addressbook
*
* @author Jakob Sack
* @copyright 2011 Jakob Sack mail@jakobsack.de
*
* 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/>.
*
*/
// Check if we are a user
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
OCP\JSON::callCheck();
require_once __DIR__.'/../loghandler.php';
$id = isset($_POST['id']) ? $_POST['id'] : null;
$name = isset($_POST['name']) ? $_POST['name'] : null;
$checksum = isset($_POST['checksum']) ? $_POST['checksum'] : null;
$l10n = OCA\Contacts\App::$l10n;
$multi_properties = array('EMAIL', 'TEL', 'IMPP', 'ADR', 'URL');
if(!$id) {
bailOut(OCA\Contacts\App::$l10n->t('id is not set.'));
}
if(!$name) {
bailOut(OCA\Contacts\App::$l10n->t('element name is not set.'));
}
if(!$checksum && in_array($name, $multi_properties)) {
bailOut(OCA\Contacts\App::$l10n->t('checksum is not set.'));
}
$vcard = OCA\Contacts\App::getContactVCard( $id );
if(!is_null($checksum)) {
$line = OCA\Contacts\App::getPropertyLineByChecksum($vcard, $checksum);
if(is_null($line)) {
bailOut($l10n->t('Information about vCard is incorrect. Please reload the page.'));
exit();
}
unset($vcard->children[$line]);
} else {
unset($vcard->{$name});
}
try {
OCA\Contacts\VCard::edit($id, $vcard);
} catch(Exception $e) {
bailOut($e->getMessage());
}
OCP\JSON::success(array(
'data' => array(
'id' => $id,
'lastmodified' => OCA\Contacts\App::lastModified($vcard)->format('U'),
)
));

View File

@ -23,10 +23,10 @@ $aid = isset($_GET['aid'])?$_GET['aid']:null;
$active_addressbooks = array();
if(is_null($aid)) {
// Called initially to get the active addressbooks.
$active_addressbooks = OCA\Contacts\Addressbook::active(OCP\USER::getUser());
$active_addressbooks = OCA\Contacts\AddressbookLegacy::active(OCP\USER::getUser());
} else {
// called each time more contacts has to be shown.
$active_addressbooks = array(OCA\Contacts\Addressbook::find($aid));
$active_addressbooks = array(OCA\Contacts\AddressbookLegacy::find($aid));
}
$lastModified = OCA\Contacts\App::lastModified();
@ -42,18 +42,6 @@ $contacts_addressbook = array();
$ids = array();
foreach($active_addressbooks as $addressbook) {
$ids[] = $addressbook['id'];
/*if(!isset($contacts_addressbook[$addressbook['id']])) {
$contacts_addressbook[$addressbook['id']]
= array('contacts' => array('type' => 'book',));
$contacts_addressbook[$addressbook['id']]['displayname']
= $addressbook['displayname'];
$contacts_addressbook[$addressbook['id']]['description']
= $addressbook['description'];
$contacts_addressbook[$addressbook['id']]['permissions']
= $addressbook['permissions'];
$contacts_addressbook[$addressbook['id']]['owner']
= $addressbook['userid'];
}*/
}
$contacts_alphabet = array();
@ -63,14 +51,6 @@ $contacts_alphabet = array_merge(
$contacts_alphabet,
OCA\Contacts\VCard::all($ids)
);
/*foreach($ids as $id) {
if($id) {
$contacts_alphabet = array_merge(
$contacts_alphabet,
OCA\Contacts\VCard::all($id, $offset, 50)
);
}
}*/
uasort($contacts_alphabet, 'cmp');
@ -89,38 +69,11 @@ if($contacts_alphabet) {
'data' => $details,
);
} catch (Exception $e) {
\OCP\Util::writeLog('contacts', 'Exception: ' . $e->getMessage(), \OCP\Util::DEBUG);
continue;
}
// This should never execute.
/*if(!isset($contacts_addressbook[$contact['addressbookid']])) {
$contacts_addressbook[$contact['addressbookid']] = array(
'contacts' => array('type' => 'book',)
);
}
$display = trim($contact['fullname']);
if(!$display) {
$vcard = OCA\Contacts\App::getContactVCard($contact['id']);
if(!is_null($vcard)) {
$struct = OCA\Contacts\VCard::structureContact($vcard);
$display = isset($struct['EMAIL'][0])
? $struct['EMAIL'][0]['value']
: '[UNKNOWN]';
}
}
$contacts_addressbook[$contact['addressbookid']]['contacts'][] = array(
'type' => 'contact',
'id' => $contact['id'],
'addressbookid' => $contact['addressbookid'],
'displayname' => htmlspecialchars($display),
'permissions' =>
isset($contacts_addressbook[$contact['addressbookid']]['permissions'])
? $contacts_addressbook[$contact['addressbookid']]['permissions']
: '0',
);*/
}
}
//unset($contacts_alphabet);
//uasort($contacts, 'cmp');
OCP\JSON::success(array(
'data' => array(

View File

@ -1,260 +0,0 @@
<?php
/**
* ownCloud - Addressbook
*
* @author Thomas Tanghus
* @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
* 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/>.
*
*/
namespace OCA\Contacts;
use Sabre\VObject as VObject;
require_once __DIR__.'/../loghandler.php';
function setParameters($property, $parameters, $reset = false) {
if(!$parameters) {
return;
}
if($reset) {
$property->parameters = array();
}
debug('Setting parameters: ' . print_r($parameters, true));
foreach($parameters as $key => $parameter) {
debug('Adding parameter: ' . $key);
if(is_array($parameter)) {
foreach($parameter as $val) {
if(is_array($val)) {
foreach($val as $val2) {
if(trim($key) && trim($val2)) {
debug('Adding parameter: '.$key.'=>'.print_r($val2, true));
$property->add($key, strip_tags($val2));
}
}
} else {
if(trim($key) && trim($val)) {
debug('Adding parameter: '.$key.'=>'.print_r($val, true));
$property->add($key, strip_tags($val));
}
}
}
} else {
if(trim($key) && trim($val)) {
debug('Adding parameter: '.$key.'=>'.print_r($val, true));
$property->add($key, strip_tags($parameter));
}
}
}
}
// Check if we are a user
\OCP\JSON::checkLoggedIn();
\OCP\JSON::checkAppEnabled('contacts');
\OCP\JSON::callCheck();
$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']:null;
$checksum = isset($_POST['checksum'])?$_POST['checksum']:null;
debug('value: ' . print_r($value, 1));
$multi_properties = array('EMAIL', 'TEL', 'IMPP', 'ADR', 'URL');
if(!$name) {
bailOut(App::$l10n->t('element name is not set.'));
}
if(!$id) {
bailOut(App::$l10n->t('id is not set.'));
}
if(!$checksum && in_array($name, $multi_properties)) {
bailOut(App::$l10n->t('checksum is not set.'));
}
if(is_array($value)) {
$value = array_map('strip_tags', $value);
// NOTE: Important, otherwise the compound value will be
// set in the order the fields appear in the form!
ksort($value);
//if($name == 'CATEGORIES') {
// $value = VCard::escapeDelimiters($value, ',');
//} else {
// $value = VCard::escapeDelimiters($value, ';');
//}
} else {
$value = trim(strip_tags($value));
}
$vcard = App::getContactVCard($id);
if(!$vcard) {
bailOut(App::$l10n->t('Couldn\'t find vCard for %d.', array($id)));
}
$property = null;
if(in_array($name, $multi_properties)) {
if($checksum !== 'new') {
$line = App::getPropertyLineByChecksum($vcard, $checksum);
if(is_null($line)) {
bailOut(App::$l10n->t(
'Information about vCard is incorrect. Please reload the page: ').$checksum
);
}
$property = $vcard->children[$line];
$element = $property->name;
if($element != $name) {
bailOut(App::$l10n->t(
'Something went FUBAR. ').$name.' != '.$element
);
}
} else {
// Add new property
$element = $name;
if (!is_scalar($value)) {
$property = VObject\Property::create($name);
if(in_array($name, array('ADR',))) {
$property->setParts($value);
} else {
bailOut(App::$l10n->t(
'Cannot save property of type "%s" as array', array($name,)
));
}
setParameters($property, $parameters);
} else {
$property = VObject\Property::create($name, $value, $parameters);
}
$vcard->add($property);
$checksum = substr(md5($property->serialize()), 0, 8);
try {
VCard::edit($id, $vcard);
} catch(Exception $e) {
bailOut($e->getMessage());
}
\OCP\JSON::success(array('data' => array(
'checksum' => $checksum,
'oldchecksum' => $_POST['checksum'],
)));
exit();
}
} else {
$element = $name;
$property = $vcard->select($name);
if(count($property) === 0) {
$property = VObject\Property::create($name);
$vcard->add($property);
} else {
$property = array_shift($property);
}
}
/* preprocessing value */
switch($element) {
case 'BDAY':
$date = New \DateTime($value);
$value = $date->format('Y-m-d');
break;
case 'FN':
if(!$value) {
// create a method thats returns an alternative for FN.
//$value = getOtherValue();
}
break;
case 'NOTE':
$value = str_replace('\n', '\\n', $value);
break;
case 'EMAIL':
$value = strtolower($value);
break;
case 'IMPP':
if(is_null($parameters) || !isset($parameters['X-SERVICE-TYPE'])) {
bailOut(App::$l10n->t('Missing IM parameter.'));
}
$impp = App::getIMOptions($parameters['X-SERVICE-TYPE']);
if(is_null($impp)) {
bailOut(App::$l10n->t('Unknown IM: '.$parameters['X-SERVICE-TYPE']));
}
$value = $impp['protocol'] . ':' . $value;
break;
}
// If empty remove the property
if(!$value) {
if(in_array($name, $multi_properties)) {
unset($vcard->children[$line]);
$checksum = '';
} else {
unset($vcard->{$name});
}
} else {
/* setting value */
switch($element) {
case 'BDAY':
$vcard->BDAY = $value;
if(!isset($vcard->BDAY['VALUE'])) {
$vcard->BDAY->add('VALUE', 'DATE');
} else {
$vcard->BDAY->VALUE = 'DATE';
}
break;
case 'ADR':
case 'N':
if(is_array($value)) {
$property->setParts($value);
} else {
debug('Saving N ' . $value);
$vcard->N = $value;
}
break;
case 'EMAIL':
case 'TEL':
case 'IMPP':
case 'URL':
debug('Setting element: (EMAIL/TEL/ADR)'.$element);
$property->setValue($value);
break;
default:
$vcard->{$name} = $value;
break;
}
setParameters($property, $parameters, true);
// Do checksum and be happy
if(in_array($name, $multi_properties)) {
$checksum = substr(md5($property->serialize()), 0, 8);
}
}
//debug('New checksum: '.$checksum);
//$vcard->children[$line] = $property; ???
try {
VCard::edit($id, $vcard);
} catch(Exception $e) {
bailOut($e->getMessage());
}
if(in_array($name, $multi_properties)) {
\OCP\JSON::success(array('data' => array(
'line' => $line,
'checksum' => $checksum,
'oldchecksum' => $_POST['checksum'],
'lastmodified' => App::lastModified($vcard)->format('U'),
)));
} else {
\OCP\JSON::success(array('data' => array(
'lastmodified' => App::lastModified($vcard)->format('U'),
)));
}

View File

@ -20,22 +20,39 @@
*
*/
namespace OCA\Contacts;
// Firefox and Konqueror tries to download application/json for me. --Arthur
OCP\JSON::setContentTypeHeader('text/plain');
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
\OCP\JSON::setContentTypeHeader('text/plain');
\OCP\JSON::checkLoggedIn();
\OCP\JSON::checkAppEnabled('contacts');
require_once 'loghandler.php';
if (!isset($_GET['id'])) {
bailOut(OCA\Contacts\App::$l10n->t('No contact ID was submitted.'));
$contactid = isset($_GET['contactid']) ? $_GET['contactid'] : '';
$addressbookid = isset($_GET['addressbookid']) ? $_GET['addressbookid'] : '';
$backend = isset($_GET['backend']) ? $_GET['backend'] : '';
if(!$contactid) {
bailOut('Missing contact id.');
}
$contact = OCA\Contacts\App::getContactVCard($_GET['id']);
if(!$addressbookid) {
bailOut('Missing address book id.');
}
$app = new App();
// FIXME: Get backend and addressbookid
$contact = $app->getContact($backend, $addressbookid, $contactid);
if(!$contact) {
\OC_Cache::remove($tmpkey);
bailOut(App::$l10n
->t('Error getting contact object.'));
}
// invalid vcard
if( is_null($contact)) {
bailOut(OCA\Contacts\App::$l10n->t('Error reading contact photo.'));
if(!$contact) {
bailOut(App::$l10n->t('Error reading contact photo.'));
} else {
$image = new OC_Image();
$image = new \OC_Image();
if(!isset($contact->PHOTO) || !$image->loadFromBase64((string)$contact->PHOTO)) {
if(isset($contact->LOGO)) {
$image->loadFromBase64((string)$contact->LOGO);
@ -43,13 +60,13 @@ if( is_null($contact)) {
}
if($image->valid()) {
$tmpkey = 'contact-photo-'.$contact->UID;
if(OC_Cache::set($tmpkey, $image->data(), 600)) {
OCP\JSON::success(array('data' => array('id'=>$_GET['id'], 'tmp'=>$tmpkey)));
if(\OC_Cache::set($tmpkey, $image->data(), 600)) {
\OCP\JSON::success(array('data' => array('id'=>$_GET['id'], 'tmp'=>$tmpkey)));
exit();
} else {
bailOut(OCA\Contacts\App::$l10n->t('Error saving temporary file.'));
bailOut(App::$l10n->t('Error saving temporary file.'));
}
} else {
bailOut(OCA\Contacts\App::$l10n->t('The loading photo is not valid.'));
bailOut(App::$l10n->t('The loading photo is not valid.'));
}
}

View File

@ -1,41 +0,0 @@
<?php
/**
* Copyright (c) 2011 Thomas Tanghus <thomas@tanghus.net>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
$id = $_GET['id'];
$checksum = isset($_GET['checksum'])?$_GET['checksum']:'';
$vcard = OCA\Contacts\App::getContactVCard($id);
$adr_types = OCA\Contacts\App::getTypesOfProperty('ADR');
$tmpl = new OCP\Template("contacts", "part.edit_address_dialog");
if($checksum) {
$line = OCA\Contacts\App::getPropertyLineByChecksum($vcard, $checksum);
$element = $vcard->children[$line];
$adr = OCA\Contacts\VCard::structureProperty($element);
$types = array();
if(isset($adr['parameters']['TYPE'])) {
if(is_array($adr['parameters']['TYPE'])) {
$types = array_map('htmlspecialchars', $adr['parameters']['TYPE']);
$types = array_map('strtoupper', $types);
} else {
$types = array(strtoupper(htmlspecialchars($adr['parameters']['TYPE'])));
}
}
$tmpl->assign('types', $types, false);
$adr = array_map('htmlspecialchars', $adr['value']);
$tmpl->assign('adr', $adr, false);
}
$tmpl->assign('id', $id);
$tmpl->assign('adr_types', $adr_types);
$page = $tmpl->fetchPage();
OCP\JSON::success(array('data' => array('page'=>$page, 'checksum'=>$checksum)));

View File

@ -1,34 +0,0 @@
<?php
/**
* Copyright (c) 2011 Thomas Tanghus <thomas@tanghus.net>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
require_once 'loghandler.php';
$tmpl = new OCP\Template("contacts", "part.edit_name_dialog");
$id = isset($_GET['id'])?$_GET['id']:'';
if($id) {
$vcard = OCA\Contacts\App::getContactVCard($id);
$name = array('', '', '', '', '');
if($vcard->__isset('N')) {
$property = $vcard->__get('N');
if($property) {
$name = OCA\Contacts\VCard::structureProperty($property);
}
}
$name = array_map('htmlspecialchars', $name['value']);
$tmpl->assign('name', $name, false);
$tmpl->assign('id', $id, false);
} else {
bailOut(OCA\Contacts\App::$l10n->t('Contact ID is missing.'));
}
$page = $tmpl->fetchPage();
OCP\JSON::success(array('data' => array('page'=>$page)));

View File

@ -1,15 +0,0 @@
<?php
/**
* Copyright (c) 2012 Georg Ehrke <ownclouddev at georgswebsite dot de>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
OCP\JSON::checkLoggedIn();
OCP\App::checkAppEnabled('contacts');
$tmpl = new OCP\Template('contacts', 'part.import');
$tmpl->assign('path', $_POST['path']);
$tmpl->assign('filename', $_POST['filename']);
$tmpl->printpage();

View File

@ -24,8 +24,8 @@ OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
require_once 'loghandler.php';
if(!isset($_GET['id'])) {
bailOut(OCA\Contacts\App::$l10n->t('No contact ID was submitted.'));
if(!isset($_GET['contact'])) {
bailOut(OCA\Contacts\App::$l10n->t('No contact info was submitted.'));
}
if(!isset($_GET['path'])) {
@ -33,7 +33,7 @@ if(!isset($_GET['path'])) {
}
$localpath = \OC\Files\Filesystem::getLocalFile($_GET['path']);
$tmpkey = 'contact-photo-'.$_GET['id'];
$tmpkey = 'contact-photo-'.$_GET['contact']['contactid'];
if(!file_exists($localpath)) {
bailOut(OCA\Contacts\App::$l10n->t('File doesn\'t exist:').$localpath);
@ -55,7 +55,7 @@ if(!$image->fixOrientation()) { // No fatal error so we don't bail out.
OCP\Util::DEBUG);
}
if(OC_Cache::set($tmpkey, $image->data(), 600)) {
OCP\JSON::success(array('data' => array('id'=>$_GET['id'], 'tmp'=>$tmpkey)));
OCP\JSON::success(array('data' => array('id'=>$_GET['contact']['contactid'], 'tmp'=>$tmpkey)));
exit();
} else {
bailOut('Couldn\'t save temporary image: '.$tmpkey);

View File

@ -3,7 +3,7 @@
* ownCloud - Addressbook
*
* @author Thomas Tanghus
* @copyright 2012 Thomas Tanghus <thomas@tanghus.net>
* @copyright 2012-13 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
@ -19,13 +19,13 @@
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
// Check if we are a user
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
OCP\JSON::callCheck();
// Firefox and Konqueror tries to download application/json for me. --Arthur
OCP\JSON::setContentTypeHeader('text/plain; charset=utf-8');
namespace OCA\Contacts;
// Check if we are a user
\OCP\JSON::checkLoggedIn();
\OCP\JSON::checkAppEnabled('contacts');
\OCP\JSON::callCheck();
require_once 'loghandler.php';
@ -38,92 +38,101 @@ $y1 = (isset($_POST['y1']) && $_POST['y1']) ? $_POST['y1'] : 0;
$w = (isset($_POST['w']) && $_POST['w']) ? $_POST['w'] : -1;
$h = (isset($_POST['h']) && $_POST['h']) ? $_POST['h'] : -1;
$tmpkey = isset($_POST['tmpkey']) ? $_POST['tmpkey'] : '';
$id = isset($_POST['id']) ? $_POST['id'] : '';
$contactid = isset($_POST['contactid']) ? $_POST['contactid'] : '';
$addressbookid = isset($_POST['addressbookid']) ? $_POST['addressbookid'] : '';
$backend = isset($_POST['backend']) ? $_POST['backend'] : '';
if($tmpkey == '') {
bailOut('Missing key to temporary file.');
}
if($id == '') {
if($contactid == '') {
bailOut('Missing contact id.');
}
OCP\Util::writeLog('contacts', 'savecrop.php: key: '.$tmpkey, OCP\Util::DEBUG);
if($addressbookid == '') {
bailOut('Missing address book id.');
}
$data = OC_Cache::get($tmpkey);
\OCP\Util::writeLog('contacts', 'savecrop.php: key: '.$tmpkey, \OCP\Util::DEBUG);
$app = new App();
// FIXME: Get backend and addressbookid
$contact = $app->getContact($backend, $addressbookid, $contactid);
if(!$contact) {
\OC_Cache::remove($tmpkey);
bailOut(App::$l10n
->t('Error getting contact object.'));
}
$data = \OC_Cache::get($tmpkey);
if($data) {
$image = new OC_Image();
$image = new \OC_Image();
if($image->loadFromData($data)) {
$w = ($w != -1 ? $w : $image->width());
$h = ($h != -1 ? $h : $image->height());
OCP\Util::writeLog('contacts',
\OCP\Util::writeLog('contacts',
'savecrop.php, x: '.$x1.' y: '.$y1.' w: '.$w.' h: '.$h,
OCP\Util::DEBUG);
\OCP\Util::DEBUG);
if($image->crop($x1, $y1, $w, $h)) {
if(($image->width() <= 200 && $image->height() <= 200)
|| $image->resize(200)) {
$vcard = OCA\Contacts\App::getContactVCard($id);
if(!$vcard) {
OC_Cache::remove($tmpkey);
bailOut(OCA\Contacts\App::$l10n
->t('Error getting contact object.'));
}
if($vcard->__isset('PHOTO')) {
OCP\Util::writeLog('contacts',
'savecrop.php: PHOTO property exists.',
OCP\Util::DEBUG);
$property = $vcard->__get('PHOTO');
if(!$property) {
OC_Cache::remove($tmpkey);
bailOut(OCA\Contacts\App::$l10n
->t('Error getting PHOTO property.'));
}
$property->setValue($image->__toString());
$property->parameters[]
= new Sabre\VObject\Parameter('ENCODING', 'b');
$property->parameters[]
= new Sabre\VObject\Parameter('TYPE', $image->mimeType());
$vcard->__set('PHOTO', $property);
} else {
OCP\Util::writeLog('contacts',
'savecrop.php: files: Adding PHOTO property.',
OCP\Util::DEBUG);
// For vCard 3.0 the type must be e.g. JPEG or PNG
// For version 4.0 the full mimetype should be used.
// https://tools.ietf.org/html/rfc2426#section-3.1.4
$type = $vcard->VERSION == '4.0'
$type = strval($contact->VERSION) === '4.0'
? $image->mimeType()
: strtoupper(array_pop(explode('/', $image->mimeType())));
$vcard->add('PHOTO',
$image->__toString(), array('ENCODING' => 'b',
if(isset($contact->PHOTO)) {
\OCP\Util::writeLog('contacts',
'savecrop.php: PHOTO property exists.',
\OCP\Util::DEBUG);
$property = $contact->PHOTO;
if(!$property) {
\OC_Cache::remove($tmpkey);
bailOut(App::$l10n
->t('Error getting PHOTO property.'));
}
$property->setValue(strval($image));
$property->parameters = [];
/*$property->ENCODING = 'b';
$property->TYPE = $type;*/
$property->parameters[]
= new \Sabre\VObject\Parameter('ENCODING', 'b');
$property->parameters[]
= new \Sabre\VObject\Parameter('TYPE', $image->mimeType());
$contact->PHOTO = $property;
} else {
\OCP\Util::writeLog('contacts',
'savecrop.php: files: Adding PHOTO property.',
\OCP\Util::DEBUG);
$contact->add('PHOTO',
strval($image), array('ENCODING' => 'b',
'TYPE' => $type));
}
$now = new DateTime;
$vcard->{'REV'} = $now->format(DateTime::W3C);
if(!OCA\Contacts\VCard::edit($id, $vcard)) {
bailOut(OCA\Contacts\App::$l10n->t('Error saving contact.'));
if(!$contact->save()) {
bailOut(App::$l10n->t('Error saving contact.'));
}
OCA\Contacts\App::cacheThumbnail($id, $image);
OCP\JSON::success(array(
$thumbnail = $contact->cacheThumbnail($image);
\OCP\JSON::success(array(
'data' => array(
'id' => $id,
'width' => $image->width(),
'height' => $image->height(),
'lastmodified' => OCA\Contacts\App::lastModified($vcard)->format('U')
'id' => $contactid,
'thumbnail' => $thumbnail,
)
));
} else {
bailOut(OCA\Contacts\App::$l10n->t('Error resizing image'));
bailOut(App::$l10n->t('Error resizing image'));
}
} else {
bailOut(OCA\Contacts\App::$l10n->t('Error cropping image'));
bailOut(App::$l10n->t('Error cropping image'));
}
} else {
bailOut(OCA\Contacts\App::$l10n->t('Error creating temporary image'));
bailOut(App::$l10n->t('Error creating temporary image'));
}
} else {
bailOut(OCA\Contacts\App::$l10n->t('Error finding image: ').$tmpkey);
bailOut(App::$l10n->t('Error finding image: ').$tmpkey);
}
OC_Cache::remove($tmpkey);
\OC_Cache::remove($tmpkey);

View File

@ -32,57 +32,48 @@ $view = OCP\Files::getStorage('contacts');
if(!$view->file_exists('imports')) {
$view->mkdir('imports');
}
$tmpfile = md5(rand());
// If it is a Drag'n'Drop transfer it's handled here.
$fn = (isset($_SERVER['HTTP_X_FILE_NAME']) ? $_SERVER['HTTP_X_FILE_NAME'] : false);
$fn = strtr($fn, array('/' => '', "\\" => ''));
if($fn) {
if(OC\Files\Filesystem::isFileBlacklisted($fn)) {
bailOut($l10n->t('Upload of blacklisted file:') . $fn);
}
if($view->file_put_contents('/imports/'.$fn, file_get_contents('php://input'))) {
OCP\JSON::success(array('data' => array('file'=>$tmpfile, 'name'=>$fn)));
exit();
} else {
bailOut($l10n->t('Error uploading contacts to storage.'));
}
}
// File input transfers are handled here
if (!isset($_FILES['importfile'])) {
OCP\Util::writeLog('contacts',
'ajax/uploadphoto.php: No file was uploaded. Unknown error.',
OCP\Util::DEBUG);
OCP\JSON::error(array('
data' => array(
'message' => 'No file was uploaded. Unknown error' )));
exit();
if (!isset($_FILES['file'])) {
bailOut($l10n->t('No file was uploaded. Unknown error'));
}
$error = $_FILES['importfile']['error'];
if($error !== UPLOAD_ERR_OK) {
$file=$_FILES['file'];
if($file['error'] !== UPLOAD_ERR_OK) {
$errors = array(
0=>$l10n->t("There is no error, the file uploaded with success"),
1=>$l10n->t("The uploaded file exceeds the upload_max_filesize directive in php.ini").ini_get('upload_max_filesize'),
2=>$l10n->t("The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form"),
3=>$l10n->t("The uploaded file was only partially uploaded"),
4=>$l10n->t("No file was uploaded"),
6=>$l10n->t("Missing a temporary folder")
UPLOAD_ERR_OK => $l10n->t("There is no error, the file uploaded with success"),
UPLOAD_ERR_INI_SIZE => $l10n->t("The uploaded file exceeds the upload_max_filesize directive in php.ini")
.ini_get('upload_max_filesize'),
UPLOAD_ERR_FORM_SIZE => $l10n->t("The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form"),
UPLOAD_ERR_PARTIAL => $l10n->t("The uploaded file was only partially uploaded"),
UPLOAD_ERR_NO_FILE => $l10n->t("No file was uploaded"),
UPLOAD_ERR_NO_TMP_DIR => $l10n->t('Missing a temporary folder'),
UPLOAD_ERR_CANT_WRITE => $l10n->t('Failed to write to disk'),
);
bailOut($errors[$error]);
}
$file=$_FILES['importfile'];
if(file_exists($file['tmp_name'])) {
$maxUploadFilesize = OCP\Util::maxUploadFilesize('/');
$maxHumanFilesize = OCP\Util::humanFileSize($maxUploadFilesize);
$totalSize = $file['size'];
if ($maxUploadFilesize >= 0 and $totalSize > $maxUploadFilesize) {
bailOut($l10n->t('Not enough storage available'));
}
$tmpname = $file['tmp_name'];
$filename = strtr($file['name'], array('/' => '', "\\" => ''));
if(is_uploaded_file($tmpname)) {
if(OC\Files\Filesystem::isFileBlacklisted($filename)) {
bailOut($l10n->t('Upload of blacklisted file:') . $filename);
}
if($view->file_put_contents('/imports/'.$filename, file_get_contents($file['tmp_name']))) {
OCP\JSON::success(array('data' => array('file'=>$filename, 'name'=>$filename)));
if($view->file_put_contents('/imports/'.$filename, file_get_contents($tmpname))) {
OCP\JSON::success(array('file'=>$filename));
} else {
bailOut($l10n->t('Error uploading contacts to storage.'));
}
} else {
bailOut('Temporary file: \''.$file['tmp_name'].'\' has gone AWOL?');
bailOut('Temporary file: \''.$tmpname.'\' has gone AWOL?');
}

View File

@ -29,13 +29,22 @@ OCP\JSON::callCheck();
OCP\JSON::setContentTypeHeader('text/plain; charset=utf-8');
require_once 'loghandler.php';
$l10n = OCA\Contacts\App::$l10n;
$contactid = isset($_POST['contactid']) ? $_POST['contactid'] : '';
$addressbookid = isset($_POST['addressbookid']) ? $_POST['addressbookid'] : '';
$backend = isset($_POST['backend']) ? $_POST['backend'] : '';
if($contactid == '') {
bailOut('Missing contact id.');
}
if($addressbookid == '') {
bailOut('Missing address book id.');
}
// If it is a Drag'n'Drop transfer it's handled here.
$fn = (isset($_SERVER['HTTP_X_FILE_NAME']) ? $_SERVER['HTTP_X_FILE_NAME'] : false);
if ($fn) {
if (!isset($_GET['id'])) {
bailOut($l10n->t('No contact ID was submitted.'));
}
$id = $_GET['id'];
$tmpkey = 'contact-photo-'.md5($fn);
$data = file_get_contents('php://input');
$image = new OC_Image();
@ -52,8 +61,12 @@ if ($fn) {
'data' => array(
'mime'=> $_SERVER['CONTENT_TYPE'],
'name'=> $fn,
'id'=>$id,
'tmp'=>$tmpkey)));
'contactid'=> $id,
'addressbookid'=> addressbookid,
'backend'=> $backend,
'tmp'=>$tmpkey
))
);
exit();
} else {
bailOut($l10n->t('Couldn\'t save temporary image: ').$tmpkey);
@ -63,10 +76,6 @@ if ($fn) {
}
}
// Uploads from file dialog are handled here.
if (!isset($_POST['id'])) {
bailOut($l10n->t('No contact ID was submitted.'));
}
if (!isset($_FILES['imagefile'])) {
bailOut($l10n->t('No file was uploaded. Unknown error'));
}
@ -101,9 +110,14 @@ if(file_exists($file['tmp_name'])) {
'mime'=>$file['type'],
'size'=>$file['size'],
'name'=>$file['name'],
'id'=>$_POST['id'],
'tmp'=>$tmpkey,
)));
),
'metadata' => array(
'contactid'=> $contactid,
'addressbookid'=> $addressbookid,
'backend'=> $backend,
),
));
exit();
} else {
bailOut($l10n->t('Couldn\'t save temporary image: ').$tmpkey);

View File

@ -1,26 +1,32 @@
<?php
OC::$CLASSPATH['OCA\Contacts\App'] = 'contacts/lib/app.php';
OC::$CLASSPATH['OCA\Contacts\Addressbook'] = 'contacts/lib/addressbook.php';
OC::$CLASSPATH['OCA\Contacts\VCard'] = 'contacts/lib/vcard.php';
OC::$CLASSPATH['OCA\Contacts\Hooks'] = 'contacts/lib/hooks.php';
OC::$CLASSPATH['OCA\Contacts\Share_Backend_Contact'] = 'contacts/lib/share/contact.php';
OC::$CLASSPATH['OCA\Contacts\Share_Backend_Addressbook'] = 'contacts/lib/share/addressbook.php';
OC::$CLASSPATH['OCA\Contacts\AddressbookProvider'] = 'contacts/lib/addressbookprovider.php';
OC::$CLASSPATH['OC_Connector_Sabre_CardDAV'] = 'contacts/lib/sabre/backend.php';
OC::$CLASSPATH['OC_Connector_Sabre_CardDAV_AddressBookRoot'] = 'contacts/lib/sabre/addressbookroot.php';
OC::$CLASSPATH['OC_Connector_Sabre_CardDAV_UserAddressBooks'] = 'contacts/lib/sabre/useraddressbooks.php';
OC::$CLASSPATH['OC_Connector_Sabre_CardDAV_AddressBook'] = 'contacts/lib/sabre/addressbook.php';
OC::$CLASSPATH['OC_Connector_Sabre_CardDAV_Card'] = 'contacts/lib/sabre/card.php';
OC::$CLASSPATH['OCA\\Contacts\\SearchProvider'] = 'contacts/lib/search.php';
OCP\Util::connectHook('OC_User', 'post_createUser', 'OCA\Contacts\Hooks', 'createUser');
OCP\Util::connectHook('OC_User', 'post_deleteUser', 'OCA\Contacts\Hooks', 'deleteUser');
//require_once __DIR__ . '/../lib/contact.php';
Sabre\VObject\Component::$classMap['VCARD'] = 'OCA\Contacts\Contact';
Sabre\VObject\Property::$classMap['FN'] = 'OC\VObject\StringProperty';
Sabre\VObject\Property::$classMap['TITLE'] = 'OC\VObject\StringProperty';
Sabre\VObject\Property::$classMap['ROLE'] = 'OC\VObject\StringProperty';
Sabre\VObject\Property::$classMap['NOTE'] = 'OC\VObject\StringProperty';
Sabre\VObject\Property::$classMap['NICKNAME'] = 'OC\VObject\StringProperty';
Sabre\VObject\Property::$classMap['EMAIL'] = 'OC\VObject\StringProperty';
Sabre\VObject\Property::$classMap['TEL'] = 'OC\VObject\StringProperty';
Sabre\VObject\Property::$classMap['IMPP'] = 'OC\VObject\StringProperty';
Sabre\VObject\Property::$classMap['URL'] = 'OC\VObject\StringProperty';
Sabre\VObject\Property::$classMap['GEO'] = 'Sabre\VObject\Property\Compound';
OCP\Util::connectHook('OC_User', 'post_createUser', 'OCA\Contacts\Hooks', 'userCreated');
OCP\Util::connectHook('OC_User', 'post_deleteUser', 'OCA\Contacts\Hooks', 'userDeleted');
OCP\Util::connectHook('OCA\Contacts', 'pre_deleteAddressBook', 'OCA\Contacts\Hooks', 'addressBookDeletion');
OCP\Util::connectHook('OCA\Contacts', 'pre_deleteContact', 'OCA\Contacts\Hooks', 'contactDeletion');
OCP\Util::connectHook('OCA\Contacts', 'post_createContact', 'OCA\Contacts\Hooks', 'contactUpdated');
OCP\Util::connectHook('OCA\Contacts', 'post_updateContact', 'OCA\Contacts\Hooks', 'contactUpdated');
OCP\Util::connectHook('OCA\Contacts', 'scanCategories', 'OCA\Contacts\Hooks', 'scanCategories');
OCP\Util::connectHook('OCA\Contacts', 'indexProperties', 'OCA\Contacts\Hooks', 'indexProperties');
OCP\Util::connectHook('OC_Calendar', 'getEvents', 'OCA\Contacts\Hooks', 'getBirthdayEvents');
OCP\Util::connectHook('OC_Calendar', 'getSources', 'OCA\Contacts\Hooks', 'getCalenderSources');
OCP\App::addNavigationEntry( array(
'id' => 'contacts_index',
'order' => 10,
'href' => OCP\Util::linkTo( 'contacts', 'index.php' ),
'href' => \OC_Helper::linkToRoute('contacts_index'),
'icon' => OCP\Util::imagePath( 'contacts', 'contacts.svg' ),
'name' => OC_L10N::get('contacts')->t('Contacts') ));
@ -30,8 +36,9 @@ OC_Search::registerProvider('OCA\Contacts\SearchProvider');
OCP\Share::registerBackend('contact', 'OCA\Contacts\Share_Backend_Contact');
OCP\Share::registerBackend('addressbook', 'OCA\Contacts\Share_Backend_Addressbook', 'contact');
/*
if(OCP\User::isLoggedIn()) {
foreach(OCA\Contacts\Addressbook::all(OCP\USER::getUser()) as $addressbook) {
OCP\Contacts::registerAddressBook(new OCA\Contacts\AddressbookProvider($addressbook['id']));
}
}
}*/

28
appinfo/classpath.php Normal file
View File

@ -0,0 +1,28 @@
<?php
OC::$CLASSPATH['OCA\Contacts\App'] = 'contacts/lib/app.php';
OC::$CLASSPATH['OCA\Contacts\AddressBook'] = 'contacts/lib/addressbook.php';
OC::$CLASSPATH['OCA\Contacts\Contact'] = 'contacts/lib/contact.php';
OC::$CLASSPATH['OCA\Contacts\AddressbookLegacy'] = 'contacts/lib/addressbooklegacy.php';
OC::$CLASSPATH['OCA\Contacts\IPIMObject'] = 'contacts/lib/ipimobject.php';
OC::$CLASSPATH['OCA\Contacts\PIMCollectionAbstract'] = 'contacts/lib/abstractpimcollection.php';
OC::$CLASSPATH['OCA\Contacts\PIMObjectAbstract'] = 'contacts/lib/abstractpimobject.php';
OC::$CLASSPATH['OCA\Contacts\VCard'] = 'contacts/lib/vcard.php';
OC::$CLASSPATH['OCA\Contacts\Hooks'] = 'contacts/lib/hooks.php';
OC::$CLASSPATH['OCA\Contacts\Request'] = 'contacts/lib/request.php';
OC::$CLASSPATH['OCA\Contacts\Utils\JSONSerializer'] = 'contacts/lib/utils/jsonserializer.php';
OC::$CLASSPATH['OCA\Contacts\Utils\Properties'] = 'contacts/lib/utils/properties.php';
OC::$CLASSPATH['OCA\Contacts\Backend\AbstractBackend'] = 'contacts/lib/backend/abstractbackend.php';
OC::$CLASSPATH['OCA\Contacts\Backend\Database'] = 'contacts/lib/backend/database.php';
OC::$CLASSPATH['OCA\Contacts\Backend\Shared'] = 'contacts/lib/backend/shared.php';
OC::$CLASSPATH['OCA\Contacts\Share_Backend_Contact'] = 'contacts/lib/share/contact.php';
OC::$CLASSPATH['OCA\Contacts\Share_Backend_Addressbook'] = 'contacts/lib/share/addressbook.php';
OC::$CLASSPATH['OCA\Contacts\AddressbookProvider'] = 'contacts/lib/addressbookprovider.php';
OC::$CLASSPATH['OCA\Contacts\VObject\VCard'] = 'contacts/lib/vobject/vcard.php';
//OC::$CLASSPATH['OCA\Contacts\VObject\StringProperty'] = 'contacts/lib/vobject/stringproperty.php';
OC::$CLASSPATH['OCA\Contacts\CardDAV\Backend'] = 'contacts/lib/carddav/backend.php';
OC::$CLASSPATH['OCA\Contacts\CardDAV\Plugin'] = 'contacts/lib/carddav/plugin.php';
OC::$CLASSPATH['OCA\Contacts\CardDAV\AddressBookRoot'] = 'contacts/lib/carddav/addressbookroot.php';
OC::$CLASSPATH['OCA\Contacts\CardDAV\UserAddressBooks'] = 'contacts/lib/carddav/useraddressbooks.php';
OC::$CLASSPATH['OCA\Contacts\CardDAV\AddressBook'] = 'contacts/lib/carddav/addressbook.php';
OC::$CLASSPATH['OCA\Contacts\CardDAV\Card'] = 'contacts/lib/carddav/card.php';
OC::$CLASSPATH['OCA\Contacts\SearchProvider'] = 'contacts/lib/search.php';

View File

@ -4,9 +4,121 @@
<name>*dbname*</name>
<create>true</create>
<overwrite>false</overwrite>
<charset>utf8</charset>
<!--
<table>
<name>*dbprefix*addressbook</name>
<declaration>
<field>
<name>id</name>
<type>integer</type>
<default>0</default>
<notnull>true</notnull>
<autoincrement>1</autoincrement>
<unsigned>true</unsigned>
<length>4</length>
</field>
<field>
<name>backend</name>
<type>text</type>
<default></default>
<notnull>true</notnull>
<length>255</length>
</field>
<field>
<name>addressbookid</name>
<type>text</type>
<default></default>
<notnull>true</notnull>
<length>255</length>
</field>
<field>
<name>userid</name>
<type>text</type>
<default></default>
<notnull>true</notnull>
<length>255</length>
</field>
<field>
<name>displayname</name>
<type>text</type>
<default></default>
<notnull>true</notnull>
<length>255</length>
</field>
<field>
<name>visibility</name>
<type>integer</type>
<default>1</default>
<notnull>true</notnull>
<length>1</length>
</field>
<field>
<name>ctag</name>
<type>integer</type>
<default>0</default>
<notnull>true</notnull>
<unsigned>true</unsigned>
<length>4</length>
</field>
<field>
<name>order</name>
<type>integer</type>
<default>0</default>
<notnull>true</notnull>
<unsigned>true</unsigned>
<length>4</length>
</field>
</declaration>
</table>
<table>
<name>*dbprefix*addressbook_backend</name>
<declaration>
<field>
<name>id</name>
<type>integer</type>
<default>0</default>
<notnull>true</notnull>
<autoincrement>1</autoincrement>
<unsigned>true</unsigned>
<length>4</length>
</field>
<field>
<name>backendname</name>
<type>text</type>
<default></default>
<notnull>true</notnull>
<length>255</length>
</field>
<field>
<name>enabled</name>
<type>integer</type>
<default>1</default>
<notnull>true</notnull>
<length>1</length>
</field>
</declaration>
</table>
-->
<table>
<name>*dbprefix*contacts_addressbooks</name>

View File

@ -1,5 +1,7 @@
<?php
class OC_Migration_Provider_Contacts extends OC_Migration_Provider{
namespace OCA\Contacts;
class MigrationProvider extends OC_Migration_Provider{
// Create the xml for the user supplied
function export( ) {
@ -21,11 +23,7 @@ class OC_Migration_Provider_Contacts extends OC_Migration_Provider{
$ids2 = $this->content->copyRows( $options );
// If both returned some ids then they worked
if(is_array($ids) && is_array($ids2)) {
return true;
} else {
return false;
}
return (is_array($ids) && is_array($ids2));
}
@ -34,13 +32,22 @@ class OC_Migration_Provider_Contacts extends OC_Migration_Provider{
switch($this->appinfo->version) {
default:
// All versions of the app have had the same db structure, so all can use the same import function
$query = $this->content->prepare( 'SELECT * FROM contacts_addressbooks WHERE userid = ?' );
$query = $this->content->prepare('SELECT * FROM `contacts_addressbooks` WHERE `userid` = ?');
$results = $query->execute(array($this->olduid));
$idmap = array();
while($row = $results->fetchRow()) {
// Import each addressbook
$addressbookquery = OCP\DB::prepare( 'INSERT INTO `*PREFIX*contacts_addressbooks` (`userid`, `displayname`, `uri`, `description`, `ctag`) VALUES (?, ?, ?, ?, ?)' );
$addressbookquery->execute( array( $this->uid, $row['displayname'], $row['uri'], $row['description'], $row['ctag'] ) );
$addressbookquery = OCP\DB::prepare('INSERT INTO `*PREFIX*contacts_addressbooks` '
. '(`userid`, `displayname`, `uri`, `description`, `ctag`) VALUES (?, ?, ?, ?, ?)');
$addressbookquery->execute(
array(
$this->uid,
$row['displayname'],
$row['uri'],
$row['description'],
$row['ctag']
)
);
// Map the id
$idmap[$row['id']] = OCP\DB::insertid('*PREFIX*contacts_addressbooks');
// Make the addressbook active
@ -49,12 +56,21 @@ class OC_Migration_Provider_Contacts extends OC_Migration_Provider{
// Now tags
foreach($idmap as $oldid => $newid) {
$query = $this->content->prepare( 'SELECT * FROM contacts_cards WHERE addressbookid = ?' );
$query = $this->content->prepare('SELECT * FROM `contacts_cards` WHERE `addressbookid` = ?');
$results = $query->execute(array($oldid));
while($row = $results->fetchRow()) {
// Import the contacts
$contactquery = OCP\DB::prepare( 'INSERT INTO `*PREFIX*contacts_cards` (`addressbookid`, `fullname`, `carddata`, `uri`, `lastmodified`) VALUES (?, ?, ?, ?, ?)' );
$contactquery->execute( array( $newid, $row['fullname'], $row['carddata'], $row['uri'], $row['lastmodified'] ) );
$contactquery = OCP\DB::prepare('INSERT INTO `*PREFIX*contacts_cards` '
. '(`addressbookid`, `fullname`, `carddata`, `uri`, `lastmodified`) VALUES (?, ?, ?, ?, ?)');
$contactquery->execute(
array(
$newid,
$row['fullname'],
$row['carddata'],
$row['uri'],
$row['lastmodified']
)
);
}
}
// All done!
@ -67,4 +83,4 @@ class OC_Migration_Provider_Contacts extends OC_Migration_Provider{
}
// Load the provider
new OC_Migration_Provider_Contacts( 'contacts' );
new MigrationProvider('contacts');

View File

@ -21,6 +21,7 @@
*/
OCP\App::checkAppEnabled('contacts');
require_once __DIR__ . '/classpath.php';
if(substr(OCP\Util::getRequestUri(), 0, strlen(OC_App::getAppWebPath('contacts').'/carddav.php')) == OC_App::getAppWebPath('contacts').'/carddav.php') {
$baseuri = OC_App::getAppWebPath('contacts').'/carddav.php';
@ -33,15 +34,19 @@ OC_App::loadApps($RUNTIME_APPTYPES);
// Backends
$authBackend = new OC_Connector_Sabre_Auth();
$principalBackend = new OC_Connector_Sabre_Principal();
$carddavBackend = new OC_Connector_Sabre_CardDAV();
$addressbookbackends = array();
$addressbookbackends[] = new OCA\Contacts\Backend\Shared();
$addressbookbackends[] = new OCA\Contacts\Backend\Database();
$carddavBackend = new OCA\Contacts\CardDAV\Backend($addressbookbackends);
$requestBackend = new OC_Connector_Sabre_Request();
// Root nodes
$principalCollection = new Sabre_CalDAV_Principal_Collection($principalBackend);
$principalCollection->disableListing = true; // Disable listening
$principalCollection->disableListing = true; // Disable listing
$addressBookRoot = new OC_Connector_Sabre_CardDAV_AddressBookRoot($principalBackend, $carddavBackend);
$addressBookRoot->disableListing = true; // Disable listening
$addressBookRoot = new OCA\Contacts\CardDAV\AddressBookRoot($principalBackend, $carddavBackend);
$addressBookRoot->disableListing = true; // Disable listing
$nodes = array(
$principalCollection,
@ -54,10 +59,13 @@ $server->httpRequest = $requestBackend;
$server->setBaseUri($baseuri);
// Add plugins
$server->addPlugin(new Sabre_DAV_Auth_Plugin($authBackend, 'ownCloud'));
$server->addPlugin(new Sabre_CardDAV_Plugin());
$server->addPlugin(new OCA\Contacts\CardDAV\Plugin());
$server->addPlugin(new Sabre_DAVACL_Plugin());
$server->addPlugin(new Sabre_DAV_Browser_Plugin(false)); // Show something in the Browser, but no upload
$server->addPlugin(new Sabre_CardDAV_VCFExportPlugin());
if(defined('DEBUG') && DEBUG) {
$server->debugExceptions = true;
}
// And off we go!
$server->exec();

532
appinfo/routes.php Normal file
View File

@ -0,0 +1,532 @@
<?php
/**
* @author Thomas Tanghus
* Copyright (c) 2013 Thomas Tanghus (thomas@tanghus.net)
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
namespace OCA\Contacts;
require_once __DIR__.'/../ajax/loghandler.php';
//define the routes
//for the index
$this->create('contacts_index', '/')
->actionInclude('contacts/index.php');
// ->action(
// function($params){
// //
// }
// );
$this->create('contacts_jsconfig', 'ajax/config.js')
->actionInclude('contacts/js/config.php');
/* TODO:
- Check what it requires to be a RESTful API. I think maybe {user}
shouldn't be in the URI but be authenticated in headers or elsewhere.
- Do security checks: logged in, csrf
- Move the actual code to controllers.
*/
$this->create('contacts_address_books_for_user', 'addressbooks/{user}/')
->get()
->action(
function($params) {
session_write_close();
$app = new App($params['user']);
$addressBooks = $app->getAddressBooksForUser();
$response = array();
foreach($addressBooks as $addressBook) {
$response[] = $addressBook->getMetaData();
}
\OCP\JSON::success(array(
'data' => array(
'addressbooks' => $response,
)
));
}
)
->requirements(array('user'))
->defaults(array('user' => \OCP\User::getUser()));
$this->create('contacts_address_book_collection', 'addressbook/{user}/{backend}/{addressbookid}/contacts')
->get()
->action(
function($params) {
session_write_close();
$app = new App($params['user']);
$addressBook = $app->getAddressBook($params['backend'], $params['addressbookid']);
$lastModified = $addressBook->lastModified();
if(!is_null($lastModified)) {
\OCP\Response::enableCaching();
\OCP\Response::setLastModifiedHeader($lastModified);
\OCP\Response::setETagHeader(md5($lastModified));
}
$contacts = array();
foreach($addressBook->getChildren() as $contact) {
//$contact->retrieve();
//error_log(__METHOD__.' jsondata: '.print_r($contact, true));
$response = Utils\JSONSerializer::serializeContact($contact);
if($response !== null) {
$contacts[] = $response;
}
}
\OCP\JSON::success(array(
'data' => array(
'contacts' => $contacts,
)
));
}
)
->requirements(array('user', 'backend', 'addressbookid'))
->defaults(array('user' => \OCP\User::getUser()));
$this->create('contacts_address_book_add', 'addressbook/{user}/{backend}/add')
->post()
->action(
function($params) {
session_write_close();
$app = new App($params['user']);
$backend = App::getBackend('local', $params['user']);
$id = $backend->createAddressBook($_POST);
if($id === false) {
bailOut(App::$l10n->t('Error creating address book'));
}
\OCP\JSON::success(array(
'data' => $backend->getAddressBook($id)
));
}
)
->requirements(array('user', 'backend', 'addressbookid'))
->defaults(array('user' => \OCP\User::getUser()));
$this->create('contacts_address_book_delete', 'addressbook/{user}/{backend}/{addressbookid}/delete')
->post()
->action(
function($params) {
session_write_close();
$app = new App($params['user']);
$backend = App::getBackend('local', $params['user']);
if(!$backend->deleteAddressBook($params['addressbookid'])) {
bailOut(App::$l10n->t('Error deleting address book'));
}
\OCP\JSON::success();
}
)
->requirements(array('user', 'backend', 'addressbookid'))
->defaults(array('user' => \OCP\User::getUser()));
$this->create('contacts_address_book_add_contact', 'addressbook/{user}/{backend}/{addressbookid}/contact/add')
->post()
->action(
function($params) {
session_write_close();
$app = new App($params['user']);
$addressBook = $app->getAddressBook($params['backend'], $params['addressbookid']);
$id = $addressBook->addChild();
if($id === false) {
bailOut(App::$l10n->t('Error creating contact.'));
}
$contact = $addressBook->getChild($id);
\OCP\JSON::success(array(
'data' => Utils\JSONSerializer::serializeContact($contact),
));
}
)
->requirements(array('user', 'backend', 'addressbookid'))
->defaults(array('user' => \OCP\User::getUser()));
$this->create('contacts_address_book_delete_contact', 'addressbook/{user}/{backend}/{addressbookid}/contact/{contactid}/delete')
->post()
->action(
function($params) {
session_write_close();
$app = new App($params['user']);
$addressBook = $app->getAddressBook($params['backend'], $params['addressbookid']);
$response = $addressBook->deleteChild($params['contactid']);
if($response === false) {
bailOut(App::$l10n->t('Error deleting contact.'));
}
\OCP\JSON::success();
}
)
->requirements(array('user', 'backend', 'addressbookid', 'contactid'))
->defaults(array('user' => \OCP\User::getUser()));
$this->create('contacts_contact_photo', 'addressbook/{user}/{backend}/{addressbookid}/contact/{contactid}/photo')
->get()
->action(
function($params) {
// TODO: Cache resized photo
session_write_close();
$etag = null;
$caching = null;
$max_size = 170;
$app = new App();
$contact = $app->getContact($params['backend'], $params['addressbookid'], $params['contactid']);
$image = new \OC_Image();
if (isset($contact->PHOTO) && $image->loadFromBase64((string)$contact->PHOTO)) {
// OK
$etag = md5($contact->PHOTO);
}
else
// Logo :-/
if (isset($contact->LOGO) && $image->loadFromBase64((string)$contact->LOGO)) {
// OK
$etag = md5($contact->LOGO);
}
if ($image->valid()) {
$modified = $contact->lastModified();
// Force refresh if modified within the last minute.
if(!is_null($modified)) {
$caching = (time() - $modified > 60) ? null : 0;
}
\OCP\Response::enableCaching($caching);
if(!is_null($modified)) {
\OCP\Response::setLastModifiedHeader($modified);
}
if($etag) {
\OCP\Response::setETagHeader($etag);
}
if ($image->width() > $max_size || $image->height() > $max_size) {
$image->resize($max_size);
}
header('Content-Type: ' . $image->mimeType());
$image->show();
}
}
)
->requirements(array('user', 'backend', 'addressbook', 'contactid'))
->defaults(array('user' => \OCP\User::getUser()));
$this->create('contacts_contact_delete_property', 'addressbook/{user}/{backend}/{addressbookid}/contact/{contactid}/property/delete')
->post()
->action(
function($params) {
session_write_close();
$request = Request::getRequest($params);
$name = $request->post['name'];
$checksum = isset($request->post['checksum']) ? $request->post['checksum'] : null;
debug('contacts_contact_delete_property, name: ' . print_r($name, true));
debug('contacts_contact_delete_property, checksum: ' . print_r($checksum, true));
$app = new App($request->parameters['user']);
$contact = $app->getContact(
$request->parameters['backend'],
$request->parameters['addressbookid'],
$request->parameters['contactid']
);
if(!$contact) {
bailOut(App::$l10n->t('Couldn\'t find contact.'));
}
if(!$name) {
bailOut(App::$l10n->t('Property name is not set.'));
}
if(!$checksum && in_array($name, Utils\Properties::$multi_properties)) {
bailOut(App::$l10n->t('Property checksum is not set.'));
}
if(!is_null($checksum)) {
try {
$contact->unsetPropertyByChecksum($checksum);
} catch(Exception $e) {
bailOut(App::$l10n->t('Information about vCard is incorrect. Please reload the page.'));
}
} else {
unset($contact->{$name});
}
if(!$contact->save()) {
bailOut(App::$l10n->t('Error saving contact to backend.'));
}
\OCP\JSON::success(array(
'data' => array(
'backend' => $request->parameters['backend'],
'addressbookid' => $request->parameters['addressbookid'],
'contactid' => $request->parameters['contactid'],
'lastmodified' => $contact->lastModified(),
)
));
}
)
->requirements(array('user', 'backend', 'addressbook', 'contactid'))
->defaults(array('user' => \OCP\User::getUser()));
// Save a single property.
$this->create('contacts_contact_save_property', 'addressbook/{user}/{backend}/{addressbookid}/contact/{contactid}/property/save')
->post()
->action(
function($params) {
session_write_close();
$request = Request::getRequest($params);
// TODO: When value is empty unset the property and return a checksum of 'new' if multi_property
$name = $request->post['name'];
$value = $request->post['value'];
$parameters = isset($request->post['parameters']) ? $request->post['parameters'] : null;
$checksum = isset($request->post['checksum']) ? $request->post['checksum'] : null;
debug('contacts_contact_save_property, name: ' . print_r($name, true));
debug('contacts_contact_save_property, value: ' . print_r($value, true));
debug('contacts_contact_save_property, parameters: ' . print_r($parameters, true));
debug('contacts_contact_save_property, checksum: ' . print_r($checksum, true));
$app = new App($params['user']);
$contact = $app->getContact($params['backend'], $params['addressbookid'], $params['contactid']);
$response = array('contactid' => $params['contactid']);
if(!$contact) {
bailOut(App::$l10n->t('Couldn\'t find contact.'));
}
if(!$name) {
bailOut(App::$l10n->t('Property name is not set.'));
}
if(is_array($value)) {
// NOTE: Important, otherwise the compound value will be
// set in the order the fields appear in the form!
ksort($value);
}
if(!$checksum && in_array($name, Utils\Properties::$multi_properties)) {
bailOut(App::$l10n->t('Property checksum is not set.'));
} elseif($checksum && in_array($name, Utils\Properties::$multi_properties)) {
try {
$checksum = $contact->setPropertyByChecksum($checksum, $name, $value, $parameters);
$response['checksum'] = $checksum;
} catch(Exception $e) {
bailOut(App::$l10n->t('Information about vCard is incorrect. Please reload the page.'));
}
} elseif(!in_array($name, Utils\Properties::$multi_properties)) {
if(!$contact->setPropertyByName($name, $value, $parameters)) {
bailOut(App::$l10n->t('Error setting property'));
}
}
if(!$contact->save()) {
bailOut(App::$l10n->t('Error saving property to backend'));
}
$response['lastmodified'] = $contact->lastModified();
$contact->save();
\OCP\JSON::success(array('data' => $response));
}
)
->requirements(array('user', 'backend', 'addressbook', 'contactid'))
->defaults(array('user' => \OCP\User::getUser()));
// Save all properties. Used for merging contacts.
$this->create('contacts_contact_save_all', 'addressbook/{user}/{backend}/{addressbookid}/contact/{contactid}/save')
->post()
->action(
function($params) {
session_write_close();
$request = Request::getRequest($params);
\OCP\Util::writeLog('contacts', __METHOD__.' params: '.print_r($request->parameters, true), \OCP\Util::DEBUG);
$app = new App($params['user']);
$contact = $app->getContact($params['backend'], $params['addressbookid'], $params['contactid']);
$response = array('contactid' => $params['contactid']);
if(!$contact) {
bailOut(App::$l10n->t('Couldn\'t find contact.'));
}
if(!$contact->mergeFromArray($request->params)) {
bailOut(App::$l10n->t('Error merging into contact.'));
}
if(!$contact->save()) {
bailOut(App::$l10n->t('Error saving contact to backend.'));
}
$data = Utils\JSONSerializer::serializeContact($contact);
\OCP\JSON::success(array('data' => $data));
}
)
->requirements(array('user', 'backend', 'addressbook', 'contactid'))
->defaults(array('user' => \OCP\User::getUser()));
$this->create('contacts_categories_list', 'groups/{user}/')
->get()
->action(
function($params) {
session_write_close();
$catmgr = new \OC_VCategories('contact', $params['user']);
$categories = $catmgr->categories(\OC_VCategories::FORMAT_MAP);
foreach($categories as &$category) {
$ids = $catmgr->idsForCategory($category['name']);
$category['contacts'] = $ids;
}
$favorites = $catmgr->getFavorites();
\OCP\JSON::success(array(
'data' => array(
'categories' => $categories,
'favorites' => $favorites,
'shared' => \OCP\Share::getItemsSharedWith('addressbook', Share_Backend_Addressbook::FORMAT_ADDRESSBOOKS),
'lastgroup' => \OCP\Config::getUserValue(
$params['user'],
'contacts',
'lastgroup', 'all'),
'sortorder' => \OCP\Config::getUserValue(
$params['user'],
'contacts',
'groupsort', ''),
)
)
);
}
)
->requirements(array('user'))
->defaults(array('user' => \OCP\User::getUser()));
$this->create('contacts_categories_add', 'groups/{user}/add')
->post()
->action(
function($params) {
session_write_close();
$request = Request::getRequest($params);
$name = $request->post['name'];
if(is_null($name) || $name === "") {
bailOut(App::$l10n->t('No group name given.'));
}
$catman = new \OC_VCategories('contact', $params['user']);
$id = $catman->add($name);
if($id !== false) {
\OCP\JSON::success(array('data' => array('id'=>$id, 'name' => $name)));
} else {
bailOut(App::$l10n->t('Error adding group.'));
}
}
)
->requirements(array('user'))
->defaults(array('user' => \OCP\User::getUser()));
$this->create('contacts_categories_delete', 'groups/{user}/delete')
->post()
->action(
function($params) {
session_write_close();
$request = Request::getRequest($params);
$name = $request->post['name'];
if(is_null($name) || $name === "") {
bailOut(App::$l10n->t('No group name given.'));
}
$catman = new \OC_VCategories('contact', $params['user']);
$catman->delete($name);
\OCP\JSON::success();
}
)
->requirements(array('user'))
->defaults(array('user' => \OCP\User::getUser()));
$this->create('contacts_categories_addto', 'groups/{user}/addto/{categoryid}')
->post()
->action(
function($params) {
session_write_close();
$request = Request::getRequest($params);
$categoryid = $request['categoryid'];
$ids = $request['contactids'];
debug('request: '.print_r($request->post, true));
if(is_null($categoryid) || $categoryid === '') {
bailOut(App::$l10n->t('Group ID missing from request.'));
}
if(is_null($ids)) {
bailOut(App::$l10n->t('Contact ID missing from request.'));
}
$catman = new \OC_VCategories('contact', $params['user']);
foreach($ids as $contactid) {
debug('contactid: ' . $contactid . ', categoryid: ' . $categoryid);
$catman->addToCategory($contactid, $categoryid);
}
\OCP\JSON::success();
}
)
->requirements(array('user', 'categoryid'))
->defaults(array('user' => \OCP\User::getUser()));
$this->create('contacts_categories_removefrom', 'groups/{user}/removefrom/{categoryid}')
->post()
->action(
function($params) {
session_write_close();
$request = Request::getRequest($params);
$categoryid = $request['categoryid'];
$ids = $request['contactids'];
debug('request: '.print_r($request->post, true));
if(is_null($categoryid) || $categoryid === '') {
bailOut(App::$l10n->t('Group ID missing from request.'));
}
if(is_null($ids)) {
bailOut(App::$l10n->t('Contact ID missing from request.'));
}
$catman = new \OC_VCategories('contact', $params['user']);
foreach($ids as $contactid) {
debug('contactid: ' . $contactid . ', categoryid: ' . $categoryid);
$catman->removeFromCategory($contactid, $categoryid);
}
\OCP\JSON::success();
}
)
->requirements(array('user', 'categoryid'))
->defaults(array('user' => \OCP\User::getUser()));
$this->create('contacts_setpreference', 'preference/{user}/set')
->post()
->action(
function($params) {
session_write_close();
$request = Request::getRequest($params);
$key = $request->post['key'];
$value = $request->post['value'];
if(is_null($key) || $key === "") {
bailOut(App::$l10n->t('No key is given.'));
}
if(is_null($value) || $value === "") {
bailOut(App::$l10n->t('No value is given.'));
}
if(\OCP\Config::setUserValue($params['user'], 'contacts', $key, $value)) {
\OCP\JSON::success(array(
'data' => array(
'key' => $key,
'value' => $value)
)
);
} else {
bailOut(App::$l10n->t(
'Could not set preference: ' . $key . ':' . $value)
);
}
}
)
->requirements(array('user'))
->defaults(array('user' => \OCP\User::getUser()));
$this->create('contacts_index_properties', 'indexproperties/{user}/')
->post()
->action(
function($params) {
session_write_close();
\OC_Hook::emit('OCA\Contacts', 'indexProperties', array());
\OCP\Config::setUserValue($params['user'], 'contacts', 'contacts_properties_indexed', 'yes');
\OCP\JSON::success(array('isIndexed' => true));
}
)
->requirements(array('user'))
->defaults(array('user' => \OCP\User::getUser()));

View File

@ -30,14 +30,14 @@
top: -8px;
left: -6px;
}
#content textarea { font-family: inherit; }
#contact textarea { font-family: inherit; }
#content ::-moz-placeholder, #content input:-moz-placeholder, #content input[placeholder], #content input:placeholder, #content input:-ms-input-placeholder, #content input::-webkit-input-placeholder, #content input:-moz-placeholder {
#contact ::-moz-placeholder, #contact input:-moz-placeholder, #contact input[placeholder], #contact input:placeholder, #contact input:-ms-input-placeholder, #contact input::-webkit-input-placeholder, #contact input:-moz-placeholder {
color: #bbb;
text-overflow: ellipsis;
}
#content input:not([type="checkbox"]), #content select:not(.button), #content textarea {
#contact input:not([type="checkbox"]), #contact select:not(.button), #contact textarea {
background-color: #fefefe; border: 1px solid #fff !important;
-moz-appearance:none !important; -webkit-appearance: none !important;
-moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none;
@ -45,17 +45,17 @@
-moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box;
float: left;
}
#content input[type="button"]:hover, #content select:hover, #content select:focus, #content select:active, #content input[type="button"]:focus, #content .button:hover, button:hover { background-color:#fff; color:#333; }
#contact input[type="button"]:hover, #contact select:hover, #contact select:focus, #contact select:active, #contact input[type="button"]:focus, #contact .button:hover, button:hover { background-color:#fff; color:#333; }
#content fieldset, #content div, #content span, #content section {
#contact fieldset, #contact div, #contact span, #contact section {
-moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box;
}
#content fieldset.editor {
#contact fieldset.editor {
padding:4px;
border: 1px solid #1d2d44;
-moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; outline:none;
}
#content input:invalid, #content input:hover:not([type="checkbox"]), #content input:active:not([type="checkbox"]), #content input:focus:not([type="checkbox"]), #content input.active, #content textarea:focus, #content textarea:hover {
#contact input:invalid, #contact input:hover:not([type="checkbox"]), #contact input:active:not([type="checkbox"]), #contact input:focus:not([type="checkbox"]), #contact input.active, #contact textarea:focus, #contact textarea:hover {
border: 1px solid silver !important;
-moz-border-radius:.3em; -webkit-border-radius:.3em; border-radius:.3em;
outline:none; float: left;
@ -137,6 +137,7 @@
#grouplist { z-index: 100; }
#grouplist h3 .action:not(.starred):not(.checked):not(.favorite) { float: right; display: none; padding: 0; margin: auto; }
#grouplist h3:not([data-type="shared"]):not(.editing):hover .action.numcontacts, #grouplist h3:not([data-type="shared"]):not(.editing) .active.action.numcontacts { display: inline-block; }
.action.numcontacts { width: 6em; }
#grouplist h3.active[data-type="category"]:not(.editing):hover .action.delete { display: inline-block; }
#grouplist h3:not(.editing) .action.delete { width: 20px; height: 20px; }
@ -201,7 +202,6 @@
.no-svg .mail { background-image:url('%webroot%/core/img/actions/mail.png'); }
.no-svg .import, .no-svg .upload { background-image:url('%webroot%/core/img/actions/upload.png'); }
.no-svg .export, .no-svg .download { background-image:url('%webroot%/core/img/actions/download.png'); }
/*.no-svg .cloud { background-image:url('%webroot%/core/img/places/picture.png'); }*/
.no-svg .globe { background-image:url('%webroot%/core/img/actions/public.png'); }
.no-svg .settings { background-image:url('%webroot%/core/img/actions/settings.svg'); }
.no-svg .starred { background-image:url('%appswebroot%/contacts/img/starred.png'); background-size: contain; }
@ -222,7 +222,6 @@
.svg .mail { background-image:url('%webroot%/core/img/actions/mail.svg'); }
.svg .import,.svg .upload { background-image:url('%webroot%/core/img/actions/upload.svg'); }
.svg .export,.svg .download { background-image:url('%webroot%/core/img/actions/download.svg'); }
/*.svg .cloud { background-image:url('%webroot%/core/img/places/picture.svg'); }*/
.svg .globe { background-image:url('%webroot%/core/img/actions/public.svg'); }
.svg .settings { background-image:url('%webroot%/core/img/actions/settings.svg'); }
.svg .starred { background-image:url('%appswebroot%/contacts/img/starred.svg'); background-size: contain; }
@ -246,11 +245,16 @@
}
.control > * { background: none repeat scroll 0 0 #F8F8F8; color: #555 !important; font-size: 100%; margin: 0px; }
.dim {
opacity: .50;filter:Alpha(Opacity=50);
z-index: 0;
}
.ui-draggable { height: 3em; z-index: 1000; }
.ui-draggable { height: 3em; z-index: 1000; }
.ui-draggable-dragging {
.dragContact {
cursor: move;
background-repeat: no-repeat !important;
background-position: .3em .3em !important;
background-color: #fff;
z-index: 5;
width: 200px; height: 30px;
@ -258,6 +262,8 @@
font-weight: bold;
border: thin solid silver; border-radius: 3px;
padding: 3px;
z-index: 1000;
transition: background-image 500ms ease 0s;
}
.ui-state-hover { border: 1px solid dashed; z-index: 1; }
@ -318,7 +324,7 @@ ul.propertylist { width: 450px; }
.propertylist li > input[type="checkbox"].impp { clear: none; }
.propertylist li > label.xab { display: block; color: #bbb; float:left; clear: both; padding: 0.5em 0 0 2.5em; }
.propertylist li > label.xab:hover { color: #777; }
#rightcontent label, #rightcontent dt, #rightcontent th, #rightcontent .label {
#contact label, #contact dt, #contact th, #contact .label {
float: left;
font-size: 0.7em; font-weight: bold;
color: #bbb !important;
@ -327,9 +333,9 @@ ul.propertylist { width: 450px; }
box-sizing: border-box;
-moz-box-sizing: border-box;
}
#rightcontent label:hover, #rightcontent dt:hover, #rightcontent input.label:hover { color: #777 !important; }
#rightcontent input.label, #rightcontent input.label { margin:-2px; }
#rightcontent input.label:hover, #rightcontent input.label:active { border: 0 none !important; border-radius: 0; cursor: pointer; }
#contact label:hover, #contact dt:hover, #contact input.label:hover { color: #777 !important; }
#contact input.label, #contact input.label { margin:-2px; }
#contact input.label:hover, #contact input.label:active { border: 0 none !important; border-radius: 0; cursor: pointer; }
.typelist[type="button"] { float: left; max-width: 8em; border: 0; background-color: #fff; color: #bbb; box-shadow: none; } /* for multiselect */
.typelist[type="button"]:hover { color: #777; } /* for multiselect */
.addresslist { clear: both; font-weight: bold; }
@ -407,7 +413,7 @@ ul.propertylist { width: 450px; }
}
/* Single elements */
#file_upload_target, #import_upload_target, #crop_target { display:none; }
#import_fileupload {
#import_upload_start {
height: 2.29em;
/*width: 2.5em;*/
width: 95%;
@ -417,6 +423,12 @@ ul.propertylist { width: 450px; }
cursor: pointer;
z-index: 1001;
}
/* Those dang Germans and their long sentences ;) */
label[for="import_upload_start"] {
display: inline-block;
max-width: 16em;
white-space: pre-line;
}
.import-upload-button {
background-image: url("%webroot%/core/img/actions/upload.svg");
background-position: center center;
@ -438,6 +450,10 @@ input:not([type="checkbox"]).propertytype {
text-align: right;
margin: 0;
}
.thumbnail {
background-image:url('%appswebroot%/contacts/img/person.png');
}
input[type="checkbox"].propertytype { width: 10px; }
.contactphoto {
float: left; display: inline-block;
@ -466,8 +482,27 @@ input[type="checkbox"].propertytype { width: 10px; }
#phototools li { display: inline; }
#phototools li a { float:left; opacity: 0.6; }
#phototools li a:hover { opacity: 0.8; }
#contactphoto_fileupload, #import_fileupload { -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; filter:alpha(opacity=0); opacity:0; z-index:1001; }
#contactphoto_fileupload, #import_upload_start { -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; filter:alpha(opacity=0); opacity:0; z-index:1001; }
#dialog-merge-contacts .mergelist {
margin: 10px;
}
#dialog-merge-contacts .mergelist > li {
height: 30px;
-webkit-transition:background-image 500ms; -moz-transition:background-image 500ms; -o-transition:background-image 500ms; transition:background-image 500ms;
position:relative;
background-position: 0 .2em !important; background-repeat:no-repeat !important;
}
#dialog-merge-contacts .mergelist > li > input {
opacity: 0;
}
#dialog-merge-contacts .mergelist > li > input:hover, #dialog-merge-contacts .mergelist > li > input:checked {
opacity: 1;
}
#dialog-merge-contacts .mergelist > li > label {
padding-left: 20px;
font-weight: normal;
}
.no-svg .favorite { display: inline-block; float: left; height: 20px; width: 20px; background-image:url('%appswebroot%/contacts/img/inactive_star.png'); }
.no-svg .favorite.active, .favorite:hover { background-image:url('%appswebroot%/contacts/img/active_star.png'); }
.no-svg .favorite.inactive { background-image:url('%appswebroot%/contacts/img/inactive_star.png'); }
@ -558,7 +593,6 @@ input[type="checkbox"].propertytype { width: 10px; }
#contactlist tr > td.name { font-weight: bold; text-indent: 1.6em; -webkit-transition:background-image 500ms; -moz-transition:background-image 500ms; -o-transition:background-image 500ms; transition:background-image 500ms; position:relative; background-position:1em .5em !important; background-repeat:no-repeat !important; }
#contactlist tr > td a.mailto { position: absolute; float: right; clear: none; cursor:pointer; width:16px; height:16px; margin: 2px; z-index: 200; opacity: 0.6; background-image:url('%webroot%/core/img/actions/mail.svg'); }
#contactlist tr > td a.mailto:hover { opacity: 0.8; }
#contactlist.dim { background-image: #ddd; opacity: .50;filter:Alpha(Opacity=50); }
#contact {
background-color: white; color: #333333;

36
css/jquery.ocdialog.css Normal file
View File

@ -0,0 +1,36 @@
.oc-dialog {
background: white;
color: #333333;
border-radius: 3px; box-shadow: 0 0 7px #888888;
padding: 15px;
z-index: 200;
font-size: 100%;
}
.oc-dialog-title {
background: white;
font-weight: bold;
font-size: 110%;
margin-bottom: 10px;
}
.oc-dialog-content {
z-index: 200;
background: white;
overflow-y: auto;
}
.oc-dialog-separator {
}
.oc-dialog-buttonrow {
background: white;
float: right;
position: relative;
bottom: 0;
display: block;
margin-top: 10px;
}
.oc-dialog-close {
position:absolute;
top:7px; right:7px;
height:20px; width:20px;
background:url('%webroot%/core/img/actions/delete.svg') no-repeat center;
}

View File

@ -43,7 +43,9 @@ if(!is_null($bookid)) {
}
} elseif(!is_null($contactid)) {
try {
$data = OCA\Contacts\VCard::find($contactid);
$app = new OCA\Contacts\App();
$contact = $app->getContact($_GET['backend'], $_GET['addressbookid'], $_GET['contactid']);
$data = $contact->serialize();
} catch(Exception $e) {
OCP\JSON::error(
array(
@ -56,8 +58,8 @@ if(!is_null($bookid)) {
}
header('Content-Type: text/vcard');
header('Content-Disposition: inline; filename='
. str_replace(' ', '_', $data['fullname']) . '.vcf');
echo $data['carddata'];
. str_replace(' ', '_', $contact->FN) . '.vcf');
echo $data;
} elseif(!is_null($selectedids)) {
$selectedids = explode(',', $selectedids);
$l10n = \OC_L10N::get('contacts');

View File

@ -5,12 +5,17 @@
* later.
* See the COPYING-README file.
*/
namespace OCA\Contacts;
use Sabre\VObject;
//check for addressbooks rights or create new one
ob_start();
OCP\JSON::checkLoggedIn();
OCP\App::checkAppEnabled('contacts');
OCP\JSON::callCheck();
\OCP\JSON::checkLoggedIn();
\OCP\App::checkAppEnabled('contacts');
\OCP\JSON::callCheck();
session_write_close();
$nl = "\n";
@ -19,70 +24,49 @@ global $progresskey;
$progresskey = 'contacts.import-' . (isset($_GET['progresskey'])?$_GET['progresskey']:'');
if (isset($_GET['progress']) && $_GET['progress']) {
echo OC_Cache::get($progresskey);
echo \OC_Cache::get($progresskey);
die;
}
function writeProgress($pct) {
global $progresskey;
OC_Cache::set($progresskey, $pct, 300);
\OC_Cache::set($progresskey, $pct, 300);
}
writeProgress('10');
$view = null;
$inputfile = strtr($_POST['file'], array('/' => '', "\\" => ''));
if(OC\Files\Filesystem::isFileBlacklisted($inputfile)) {
OCP\JSON::error(array('data' => array('message' => 'Upload of blacklisted file: ' . $inputfile)));
if(\OC\Files\Filesystem::isFileBlacklisted($inputfile)) {
\OCP\JSON::error(array('data' => array('message' => 'Upload of blacklisted file: ' . $inputfile)));
exit();
}
if(isset($_POST['fstype']) && $_POST['fstype'] == 'OC_FilesystemView') {
$view = OCP\Files::getStorage('contacts');
$view = \OCP\Files::getStorage('contacts');
$file = $view->file_get_contents('/imports/' . $inputfile);
} else {
$file = \OC\Files\Filesystem::file_get_contents($_POST['path'] . '/' . $inputfile);
}
if(!$file) {
OCP\JSON::error(array('data' => array('message' => 'Import file was empty.')));
\OCP\JSON::error(array('data' => array('message' => 'Import file was empty.')));
exit();
}
if(isset($_POST['method']) && $_POST['method'] == 'new') {
$id = OCA\Contacts\Addressbook::add(OCP\USER::getUser(),
$_POST['addressbookname']);
if(!$id) {
OCP\JSON::error(
array(
'data' => array('message' => 'Error creating address book.')
)
);
exit();
}
OCA\Contacts\Addressbook::setActive($id, 1);
}else{
$id = $_POST['id'];
if(!$id) {
OCP\JSON::error(
\OCP\JSON::error(
array(
'data' => array(
'message' => 'Error getting the ID of the address book.',
'file'=>OCP\Util::sanitizeHTML($inputfile)
'file'=>\OCP\Util::sanitizeHTML($inputfile)
)
)
);
exit();
}
try {
OCA\Contacts\Addressbook::find($id); // is owner access check
} catch(Exception $e) {
OCP\JSON::error(
array(
'data' => array(
'message' => $e->getMessage(),
'file'=>OCP\Util::sanitizeHTML($inputfile)
)
)
);
exit();
}
}
$app = new App();
$addressBook = $app->getAddressBook('local', $id);
//analyse the contacts file
writeProgress('40');
$file = str_replace(array("\r","\n\n"), array("\n","\n"), $file);
@ -110,69 +94,72 @@ $imported = 0;
$failed = 0;
$partial = 0;
if(!count($parts) > 0) {
OCP\JSON::error(
\OCP\JSON::error(
array(
'data' => array(
'message' => 'No contacts to import in '
. OCP\Util::sanitizeHTML($inputfile).'. Please check if the file is corrupted.',
. \OCP\Util::sanitizeHTML($inputfile).'. Please check if the file is corrupted.',
'file'=>OCP\Util::sanitizeHTML($inputfile)
)
)
);
if(isset($_POST['fstype']) && $_POST['fstype'] == 'OC_FilesystemView') {
if(!$view->unlink('/imports/' . $inputfile)) {
OCP\Util::writeLog('contacts',
'Import: Error unlinking OC_FilesystemView ' . '/' . OCP\Util::sanitizeHTML($inputfile),
OCP\Util::ERROR);
\OCP\Util::writeLog('contacts',
'Import: Error unlinking OC_FilesystemView ' . '/' . \OCP\Util::sanitizeHTML($inputfile),
\OCP\Util::ERROR);
}
}
exit();
}
foreach($parts as $part) {
try {
$vcard = Sabre\VObject\Reader::read($part);
} catch (Sabre\VObject\ParseException $e) {
$vcard = VObject\Reader::read($part);
} catch (VObject\ParseException $e) {
try {
$vcard = Sabre\VObject\Reader::read($part, Sabre\VObject\Reader::OPTION_IGNORE_INVALID_LINES);
$vcard = VObject\Reader::read($part, VObject\Reader::OPTION_IGNORE_INVALID_LINES);
$partial += 1;
OCP\Util::writeLog('contacts',
\OCP\Util::writeLog('contacts',
'Import: Retrying reading card. Error parsing VCard: ' . $e->getMessage(),
OCP\Util::ERROR);
} catch (Exception $e) {
\OCP\Util::ERROR);
} catch (\Exception $e) {
$failed += 1;
OCP\Util::writeLog('contacts',
\OCP\Util::writeLog('contacts',
'Import: skipping card. Error parsing VCard: ' . $e->getMessage(),
OCP\Util::ERROR);
\OCP\Util::ERROR);
continue; // Ditch cards that can't be parsed by Sabre.
}
}
try {
OCA\Contacts\VCard::add($id, $vcard);
if($addressBook->addChild($vcard)) {
$imported += 1;
} catch (Exception $e) {
OCP\Util::writeLog('contacts',
'Error importing vcard: ' . $e->getMessage() . $nl . $vcard,
OCP\Util::ERROR);
} else {
$failed += 1;
}
} catch (\Exception $e) {
\OCP\Util::writeLog('contacts', __LINE__ . ' ' .
'Error importing vcard: ' . $e->getMessage() . $nl . $vcard->serialize(),
\OCP\Util::ERROR);
$failed += 1;
}
}
//done the import
writeProgress('100');
sleep(3);
OC_Cache::remove($progresskey);
\OC_Cache::remove($progresskey);
if(isset($_POST['fstype']) && $_POST['fstype'] == 'OC_FilesystemView') {
if(!$view->unlink('/imports/' . $inputfile)) {
OCP\Util::writeLog('contacts',
\OCP\Util::writeLog('contacts',
'Import: Error unlinking OC_FilesystemView ' . '/' . $inputfile,
OCP\Util::ERROR);
\OCP\Util::ERROR);
}
}
OCP\JSON::success(
\OCP\JSON::success(
array(
'data' => array(
'imported'=>$imported,
'failed'=>$failed,
'file'=>OCP\Util::sanitizeHTML($inputfile),
'file'=>\OCP\Util::sanitizeHTML($inputfile),
)
)
);

View File

@ -1,64 +1,67 @@
<?php
/**
* Copyright (c) 2012 Thomas Tanghus <thomas@tanghus.net>
* Copyright (c) 2012, 2013 Thomas Tanghus <thomas@tanghus.net>
* Copyright (c) 2011 Jakob Sack mail@jakobsack.de
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
namespace OCA\Contacts;
// Check if we are a user
OCP\User::checkLoggedIn();
OCP\App::checkAppEnabled('contacts');
\OCP\User::checkLoggedIn();
\OCP\App::checkAppEnabled('contacts');
// Get active address books. This creates a default one if none exists.
$ids = OCA\Contacts\Addressbook::activeIds(OCP\USER::getUser());
//$ids = OCA\Contacts\Addressbook::activeIds(OCP\USER::getUser());
// Load the files we need
OCP\App::setActiveNavigationEntry('contacts_index');
\OCP\App::setActiveNavigationEntry('contacts_index');
$impp_types = OCA\Contacts\App::getTypesOfProperty('IMPP');
$adr_types = OCA\Contacts\App::getTypesOfProperty('ADR');
$phone_types = OCA\Contacts\App::getTypesOfProperty('TEL');
$email_types = OCA\Contacts\App::getTypesOfProperty('EMAIL');
$ims = OCA\Contacts\App::getIMOptions();
$impp_types = Utils\Properties::getTypesForProperty('IMPP');
$adr_types = Utils\Properties::getTypesForProperty('ADR');
$phone_types = Utils\Properties::getTypesForProperty('TEL');
$email_types = Utils\Properties::getTypesForProperty('EMAIL');
$ims = Utils\Properties::getIMOptions();
$im_protocols = array();
foreach($ims as $name => $values) {
$im_protocols[$name] = $values['displayname'];
}
$categories = OCA\Contacts\App::getCategories();
$maxUploadFilesize = OCP\Util::maxUploadFilesize('/');
$maxUploadFilesize = \OCP\Util::maxUploadFilesize('/');
OCP\Util::addscript('', 'multiselect');
OCP\Util::addscript('', 'jquery.multiselect');
OCP\Util::addscript('', 'oc-vcategories');
OCP\Util::addscript('contacts', 'modernizr.custom');
OCP\Util::addscript('contacts', 'app');
OCP\Util::addscript('contacts', 'contacts');
OCP\Util::addscript('contacts', 'groups');
OCP\Util::addscript('contacts', 'expanding');
OCP\Util::addscript('contacts', 'jquery.combobox');
OCP\Util::addscript('files', 'jquery.fileupload');
OCP\Util::addscript('contacts', 'jquery.Jcrop');
OCP\Util::addStyle('3rdparty/fontawesome', 'font-awesome');
OCP\Util::addStyle('contacts', 'font-awesome');
OCP\Util::addStyle('', 'multiselect');
OCP\Util::addStyle('', 'jquery.multiselect');
OCP\Util::addStyle('contacts', 'jquery.combobox');
OCP\Util::addStyle('contacts', 'jquery.Jcrop');
OCP\Util::addStyle('contacts', 'contacts');
\OCP\Util::addscript('', 'multiselect');
\OCP\Util::addscript('', 'jquery.multiselect');
\OCP\Util::addscript('', 'oc-vcategories');
\OCP\Util::addscript('', 'octemplate');
\OCP\Util::addscript('contacts', 'modernizr.custom');
\OCP\Util::addscript('contacts', 'app');
\OCP\Util::addscript('contacts', 'contacts');
\OCP\Util::addscript('contacts', 'storage');
\OCP\Util::addscript('contacts', 'groups');
//\OCP\Util::addscript('contacts', 'expanding');
\OCP\Util::addscript('contacts', 'jquery.combobox');
\OCP\Util::addscript('contacts', 'jquery.ocdialog');
\OCP\Util::addscript('files', 'jquery.fileupload');
\OCP\Util::addscript('contacts', 'jquery.Jcrop');
\OCP\Util::addStyle('3rdparty/fontawesome', 'font-awesome');
\OCP\Util::addStyle('contacts', 'font-awesome');
\OCP\Util::addStyle('', 'multiselect');
\OCP\Util::addStyle('', 'jquery.multiselect');
\OCP\Util::addStyle('contacts', 'jquery.combobox');
\OCP\Util::addStyle('contacts', 'jquery.ocdialog');
\OCP\Util::addStyle('contacts', 'jquery.Jcrop');
\OCP\Util::addStyle('contacts', 'contacts');
$tmpl = new OCP\Template( "contacts", "contacts", "user" );
$tmpl = new \OCP\Template( "contacts", "contacts", "user" );
$tmpl->assign('uploadMaxFilesize', $maxUploadFilesize);
$tmpl->assign('uploadMaxHumanFilesize',
OCP\Util::humanFileSize($maxUploadFilesize), false);
$tmpl->assign('addressbooks', OCA\Contacts\Addressbook::all(OCP\USER::getUser()));
\OCP\Util::humanFileSize($maxUploadFilesize), false);
//$tmpl->assign('addressbooks', OCA\Contacts\Addressbook::all(OCP\USER::getUser()));
$tmpl->assign('phone_types', $phone_types);
$tmpl->assign('email_types', $email_types);
$tmpl->assign('adr_types', $adr_types);
$tmpl->assign('impp_types', $impp_types);
$tmpl->assign('categories', $categories);
$tmpl->assign('im_protocols', $im_protocols);
$tmpl->printPage();

837
js/app.js
View File

@ -74,14 +74,6 @@ utils.moveCursorToEnd = function(el) {
}
};
if (typeof Object.create !== 'function') {
Object.create = function (o) {
function F() {}
F.prototype = o;
return new F();
};
}
Array.prototype.clone = function() {
return this.slice(0);
};
@ -157,25 +149,25 @@ OC.notify = function(params) {
OC.Contacts = OC.Contacts || {
init:function() {
if(oc_debug === true) {
$.error = console.error;
$(document).ajaxError(function(e, xhr, settings, exception) {
// Don't try to get translation because it's likely a network error.
OC.notify({
message: 'error in: ' + settings.url + ', '+'error: ' + xhr.responseText
});
console.error('Error in: ', settings.url, ' : ', xhr.responseText, exception);
});
}
this.scrollTimeoutMiliSecs = 100;
this.isScrolling = false;
this.cacheElements();
this.storage = new OC.Contacts.Storage();
this.contacts = new OC.Contacts.ContactList(
this.storage,
this.$contactList,
this.$contactListItemTemplate,
this.$contactDragItemTemplate,
this.$contactFullTemplate,
this.detailTemplates
);
this.groups = new OC.Contacts.GroupList(this.$groupList, this.$groupListItemTemplate);
this.groups = new OC.Contacts.GroupList(this.storage, this.$groupList, this.$groupListItemTemplate);
OCCategories.changed = this.groups.categoriesChanged;
OCCategories.app = 'contacts';
OCCategories.type = 'contact';
@ -233,7 +225,7 @@ OC.Contacts = OC.Contacts || {
this.$ninjahelp = $('#ninjahelp');
this.$firstRun = $('#firstrun');
this.$settings = $('#contacts-settings');
this.$importFileInput = $('#import_fileupload');
this.$importFileInput = $('#import_upload_start');
this.$importIntoSelect = $('#import_into');
},
// Build the select to add/remove from groups.
@ -287,13 +279,18 @@ OC.Contacts = OC.Contacts || {
$(window).trigger('beforeunload');
});
$(window).bind('hashchange', function() {
console.log('hashchange', window.location.hash)
this.hashChange = function() {
//console.log('hashchange', window.location.hash)
var id = parseInt(window.location.hash.substr(1));
if(id) {
if(id && id !== self.currentid) {
self.openContact(id);
} else if(!id && self.currentid) {
self.closeContact(self.currentid);
}
});
}
$(window).bind('popstate', this.hashChange);
$(window).bind('hashchange', this.hashChange);
// App specific events
$(document).bind('status.contact.deleted', function(e, data) {
@ -312,7 +309,10 @@ OC.Contacts = OC.Contacts || {
self.hideActions();
});
// Keep error messaging at one place to be able to replace it.
$(document).bind('status.contact.error', function(e, data) {
console.warn(data.message);
console.trace();
OC.notify({message:data.message});
});
@ -344,11 +344,15 @@ OC.Contacts = OC.Contacts || {
self.openContact(self.currentid);
}
});
if(!result.is_indexed) {
if(!contacts_properties_indexed) {
// Wait a couple of mins then check if contacts are indexed.
setTimeout(function() {
$.when($.post(OC.Router.generate('contacts_index_properties')))
.then(function(response) {
if(!response.isIndexed) {
OC.notify({message:t('contacts', 'Indexing contacts'), timeout:20});
$.post(OC.filePath('contacts', 'ajax', 'indexproperties.php'));
}
});
}, 10000);
} else {
console.log('contacts are indexed.');
@ -418,41 +422,79 @@ OC.Contacts = OC.Contacts || {
});
$(document).bind('request.contact.export', function(e, data) {
var id = parseInt(data.id);
var id = String(data.id);
console.log('contact', data.id, 'request.contact.export');
document.location.href = OC.linkTo('contacts', 'export.php') + '?contactid=' + self.currentid;
document.location.href = OC.linkTo('contacts', 'export.php') + '?' + $.param(data);
});
$(document).bind('request.contact.close', function(e, data) {
var id = parseInt(data.id);
var id = String(data.id);
console.log('contact', data.id, 'request.contact.close');
self.closeContact(id);
});
$(document).bind('request.contact.delete', function(e, data) {
var id = parseInt(data.id);
console.log('contact', data.id, 'request.contact.delete');
var id = String(data.contactid);
console.log('contact', data, 'request.contact.delete');
self.closeContact(id);
self.contacts.delayedDelete(id);
self.contacts.delayedDelete(data);
self.$contactList.removeClass('dim');
self.showActions(['add']);
});
$(document).bind('request.select.contactphoto.fromlocal', function(e, result) {
console.log('request.select.contactphoto.fromlocal', result);
$('#contactphoto_fileupload').trigger('click');
$(document).bind('request.contact.merge', function(e, data) {
console.log('contact','request.contact.merge', data);
var merger = self.contacts.findById(data.merger);
var mergees = [];
if(!merger) {
$(document).trigger('status.contact.error', {
message: t('contacts', 'Merge failed. Cannot find contact: {id}', {id:data.merger})
});
return;
}
$.each(data.mergees, function(idx, id) {
var contact = self.contacts.findById(id);
if(!contact) {
console.warn('cannot find', id, 'by id');
}
mergees.push(contact);
});
if(!merger.merge(mergees)) {
$(document).trigger('status.contact.error', {
message: t('contacts', 'Merge failed.')
});
return;
}
merger.saveAll(function(response) {
if(response.error) {
$(document).trigger('status.contact.error', {
message: t('contacts', 'Merge failed. Error saving contact.')
});
return;
} else {
if(data.deleteOther) {
self.contacts.delayedDelete(mergees);
}
self.openContact(merger.getId());
}
});
});
$(document).bind('request.select.contactphoto.fromcloud', function(e, result) {
console.log('request.select.contactphoto.fromcloud', result);
$(document).bind('request.select.contactphoto.fromlocal', function(e, metadata) {
console.log('request.select.contactphoto.fromlocal', metadata);
$('#contactphoto_fileupload').trigger('click', metadata);
});
$(document).bind('request.select.contactphoto.fromcloud', function(e, metadata) {
console.log('request.select.contactphoto.fromcloud', metadata);
OC.dialogs.filepicker(t('contacts', 'Select photo'), function(path) {
self.cloudPhotoSelected(self.currentid, path);
self.cloudPhotoSelected(metadata, path);
}, false, 'image', true);
});
$(document).bind('request.edit.contactphoto', function(e, result) {
console.log('request.edit.contactphoto', result);
self.editCurrentPhoto(result.id);
$(document).bind('request.edit.contactphoto', function(e, metadata) {
console.log('request.edit.contactphoto', metadata);
self.editCurrentPhoto(metadata);
});
$(document).bind('request.addressbook.activate', function(e, result) {
@ -476,9 +518,8 @@ OC.Contacts = OC.Contacts || {
}
$.each(result.contacts, function(idx, contactid) {
var contact = self.contacts.findById(contactid);
console.log('contactid', contactid, contact);
self.contacts.findById(contactid).removeFromGroup(result.groupname);
contact.removeFromGroup(result.groupname);
});
});
@ -494,10 +535,17 @@ OC.Contacts = OC.Contacts || {
// Group sorted, save the sort order
$(document).bind('status.groups.sorted', function(e, result) {
console.log('status.groups.sorted', result);
$.post(OC.filePath('contacts', 'ajax', 'setpreference.php'), {'key':'groupsort', 'value':result.sortorder}, function(jsondata) {
if(jsondata.status !== 'success') {
OC.notify({message: jsondata ? jsondata.data.message : t('contacts', 'Network or server error. Please inform administrator.')});
$.when(self.storage.setPreference('groupsort', result.sortorder)).then(function(response) {
if(response.error) {
OC.notify({message: response ? response.message : t('contacts', 'Network or server error. Please inform administrator.')});
}
})
.fail(function(jqxhr, textStatus, error) {
var err = textStatus + ', ' + error;
console.log( "Request Failed: " + err);
$(document).trigger('status.contact.error', {
message: t('contacts', 'Failed saving sort order: {error}', {error:err})
});
});
});
// Group selected, only show contacts from that group
@ -520,10 +568,17 @@ OC.Contacts = OC.Contacts || {
} else {
self.contacts.showContacts(self.currentgroup);
}
$.post(OC.filePath('contacts', 'ajax', 'setpreference.php'), {'key':'lastgroup', 'value':self.currentgroup}, function(jsondata) {
if(!jsondata || jsondata.status !== 'success') {
OC.notify({message: (jsondata && jsondata.data) ? jsondata.data.message : t('contacts', 'Network or server error. Please inform administrator.')});
$.when(self.storage.setPreference('lastgroup', self.currentgroup)).then(function(response) {
if(response.error) {
OC.notify({message: response.message});
}
})
.fail(function(jqxhr, textStatus, error) {
var err = textStatus + ', ' + error;
console.log( "Request Failed: " + err);
$(document).trigger('status.contact.error', {
message: t('contacts', 'Failed saving last group: {error}', {error:err})
});
});
self.$rightContent.scrollTop(0);
});
@ -567,6 +622,13 @@ OC.Contacts = OC.Contacts || {
$('body').bind('click', bodyListener);
}
});
$('#contactphoto_fileupload').on('click', function(event, metadata) {
var form = $('#file_upload_form');
form.find('input[name="contactid"]').val(metadata.contactid);
form.find('input[name="addressbookid"]').val(metadata.addressbookid);
form.find('input[name="backend"]').val(metadata.backend);
});
$('#contactphoto_fileupload').on('change', function() {
self.uploadPhoto(this.files);
});
@ -593,38 +655,35 @@ OC.Contacts = OC.Contacts || {
self.buildGroupSelect();
}
if(isChecked) {
self.showActions(['add', 'download', 'groups', 'delete', 'favorite']);
self.showActions(['add', 'download', 'groups', 'delete', 'favorite', 'merge']);
} else {
self.showActions(['add']);
}
});
this.$contactList.on('change', 'input:checkbox', function(event) {
if($(this).is(':checked')) {
if(self.$groups.find('option').length === 1) {
var selected = self.contacts.getSelectedContacts();
console.log('selected', selected.length);
if(selected.length > 0 && self.$groups.find('option').length === 1) {
self.buildGroupSelect();
}
self.showActions(['add', 'download', 'groups', 'delete', 'favorite']);
} else if(self.contacts.getSelectedContacts().length === 0) {
if(selected.length === 0) {
self.showActions(['add']);
} else if(selected.length === 1) {
self.showActions(['add', 'download', 'groups', 'delete', 'favorite']);
} else {
self.showActions(['add', 'download', 'groups', 'delete', 'favorite', 'merge']);
}
});
// Add to/remove from group multiple contacts.
// FIXME: Refactor this to be usable for favoriting also.
this.$groups.on('change', function() {
var $opt = $(this).find('option:selected');
var action = $opt.parent().data('action');
var ids, groupName, groupId, buildnow = false;
var groupName, groupId, buildnow = false;
// If a contact is open the action is only applied to that,
// otherwise on all selected items.
if(self.currentid) {
ids = [self.currentid];
buildnow = true;
} else {
ids = self.contacts.getSelectedContacts();
}
var contacts = self.contacts.getSelectedContacts();
var ids = $.map(contacts, function(c) {return c.getId();});
self.setAllChecked(false);
self.$toggleAll.prop('checked', false);
@ -637,7 +696,7 @@ OC.Contacts = OC.Contacts || {
console.log('add group...');
self.$groups.val(-1);
self.addGroup(function(response) {
if(response.status === 'success') {
if(!response.error) {
groupId = response.id;
groupName = response.name;
self.groups.addTo(ids, groupId, function(result) {
@ -676,7 +735,6 @@ OC.Contacts = OC.Contacts || {
groupName = $opt.text(), groupId = $opt.val();
console.log('trut', groupName, groupId);
if(action === 'add') {
self.groups.addTo(ids, $opt.val(), function(result) {
console.log('after add', result);
@ -751,14 +809,12 @@ OC.Contacts = OC.Contacts || {
if(event.ctrlKey || event.metaKey) {
event.stopPropagation();
event.preventDefault();
console.log('select', event);
self.dontScroll = true;
self.contacts.select($(this).data('id'), true);
return;
}
if($(event.target).is('a.mailto')) {
var mailto = 'mailto:' + $.trim($(this).find('.email').text());
console.log('mailto', mailto);
try {
window.location.href=mailto;
} catch(e) {
@ -774,6 +830,7 @@ OC.Contacts = OC.Contacts || {
*
* @param object $list A jquery object of an unordered list
* @param object book An object with the properties 'id', 'name' and 'permissions'.
* @param bool add Whether the address book list should be updated.
*/
var appendAddressBook = function($list, book, add) {
if(add) {
@ -791,13 +848,9 @@ OC.Contacts = OC.Contacts || {
var id = parseInt($(this).parents('li').first().data('id'));
console.log('delete', id);
var $li = $(this).parents('li').first();
$.ajax({
type:'POST',
url:OC.filePath('contacts', 'ajax', 'addressbook/delete.php'),
data:{ id: id },
success:function(jsondata) {
console.log(jsondata);
if(jsondata.status == 'success') {
$.when(self.storage.deleteAddressBook('local',id))
.then(function(response) {
if(!response.error) {
self.contacts.unsetAddressbook(id);
$li.remove();
OC.notify({
@ -805,7 +858,7 @@ OC.Contacts = OC.Contacts || {
timeout:5,
timeouthandler:function() {
console.log('reloading');
window.location.href = OC.linkTo('contacts', 'index.php');
window.location.href = OC.Router.generate('contacts_index');
},
clickhandler:function() {
console.log('reloading cancelled');
@ -813,12 +866,7 @@ OC.Contacts = OC.Contacts || {
}
});
} else {
OC.notify({message:jsondata.data.message});
}
},
error:function(jqXHR, textStatus, errorThrown) {
OC.notify({message:textStatus + ': ' + errorThrown});
id = false;
OC.notify({message:response.message});
}
});
});
@ -827,7 +875,7 @@ OC.Contacts = OC.Contacts || {
var book = self.contacts.addressbooks[id];
var uri = (book.owner === oc_current_user ) ? book.uri : book.uri + '_shared_by_' + book.owner;
var link = OC.linkToRemote('carddav')+'/addressbooks/'+encodeURIComponent(oc_current_user)+'/'+encodeURIComponent(uri);
var $dropdown = $('<div id="dropdown" class="drop"><input type="text" value="' + link + '" /></div>');
var $dropdown = $('<div id="dropdown" class="drop"><input type="text" value="' + link + '" readonly /></div>');
$dropdown.appendTo($(this).parents('li').first());
var $input = $dropdown.find('input');
$input.focus().get(0).select();
@ -840,60 +888,51 @@ OC.Contacts = OC.Contacts || {
$list.append($li);
};
var $addAddressBookNew = this.$settings.find('.addaddressbook');
var $addAddressBookPart = $addAddressBookNew.next('ul');
var $addInput = $addAddressBookPart.find('input.addaddressbookinput').focus();
$addInput.on('keydown', function(event) {
if(event.keyCode === 13) {
event.stopPropagation();
$addAddressBookPart.find('.addaddressbookok').trigger('click');
}
});
$addAddressBookPart.on('click keydown', 'button', function(event) {
if(wrongKey(event)) {
return;
}
if($(this).is('.addaddressbookok')) {
if($addInput.val().trim() === '') {
return false;
} else {
var name = $addInput.val().trim();
$addInput.addClass('loading');
$addAddressBookPart.find('button input').prop('disabled', true);
console.log('adding', name);
self.addAddressbook({
name: name,
description: ''
}, function(response) {
if(!response || !response.status) {
OC.notify({
message:t('contacts', 'Network or server error. Please inform administrator.')
});
return false;
} else if(response.status === 'error') {
OC.notify({message: response.message});
return false;
} else if(response.status === 'success') {
var book = response.addressbook;
var $list = self.$settings.find('[data-id="addressbooks"]').next('ul');
appendAddressBook($list, book, true);
}
$addInput.removeClass('loading');
$addAddressBookPart.find('button input').prop('disabled', false);
$addAddressBookPart.hide().prev('button').show();
});
}
} else if($(this).is('.addaddressbookcancel')) {
$addAddressBookPart.hide().prev('button').show();
}
});
this.$settings.find('.addaddressbook').on('click keydown', function(event) {
if(wrongKey(event)) {
return;
}
$(this).hide();
$addAddressBookPart.show();
var $addAddressbookPart = $(this).next('ul').show();
var $addinput = $addAddressbookPart.find('input.addaddressbookinput').focus();
$addAddressbookPart.on('click keydown', 'button', function(event) {
if(wrongKey(event)) {
return;
}
if($(this).is('.addaddressbookok')) {
if($addinput.val().trim() === '') {
return false;
} else {
var name = $addinput.val().trim();
$addinput.addClass('loading');
$addAddressbookPart.find('button input').prop('disabled', true);
//console.log('adding', name);
$.when(self.storage.addAddressBook('local',
{displayname: name, description: ''})).then(function(response) {
if(response.error) {
OC.notify({message: response.message});
return false;
} else {
var book = response.data;
var $list = self.$settings.find('[data-id="addressbooks"]').next('ul');
appendAddressBook($list, book, true);
}
$addinput.removeClass('loading');
$addAddressbookPart.find('button input').prop('disabled', false);
$addAddressbookPart.hide().prev('button').show();
})
.fail(function(jqxhr, textStatus, error) {
var err = textStatus + ', ' + error;
console.log( "Request Failed: " + err);
$(document).trigger('status.contact.error', {
message: t('contacts', 'Failed adding address book: {error}', {error:err})
});
});
}
} else if($(this).is('.addaddressbookcancel')) {
$addAddressbookPart.hide().prev('button').show();
}
});
});
this.$settings.find('h2').on('click keydown', function(event) {
@ -903,10 +942,9 @@ OC.Contacts = OC.Contacts || {
if($(this).next('ul').is(':visible')) {
return;
}
console.log('settings');
//console.log('settings');
var $list = $(this).next('ul');
if($(this).data('id') === 'addressbooks') {
console.log('addressbooks');
if(!self.$addressbookTmpl) {
self.$addressbookTmpl = $('#addressbookTemplate');
@ -922,27 +960,26 @@ OC.Contacts = OC.Contacts || {
$list.find('a.action.share').css('display', 'none');
}
} else if($(this).data('id') === 'import') {
console.log('import');
$('.import-upload').show();
$('.import-select').hide();
var addAddressbookCallback = function(select, name) {
var id = false;
self.addAddressbook({
name: name,
description: ''
}, function(response) {
if(!response || !response.status) {
OC.notify({
message:t('contacts', 'Network or server error. Please inform administrator.')
});
return false;
} else if(response.status === 'error') {
$.when(this.storage.addAddressBook('local',
{name: name, description: ''})).then(function(response) {
if(response.error) {
OC.notify({message: response.message});
return false;
} else if(response.status === 'success') {
id = response.addressbook.id;
} else {
id = response.data.id;
}
})
.fail(function(jqxhr, textStatus, error) {
var err = textStatus + ', ' + error;
console.log( "Request Failed: " + err);
$(document).trigger('status.contact.error', {
message: t('contacts', 'Failed adding addres books: {error}', {error:err})
});
});
return id;
};
@ -970,6 +1007,7 @@ OC.Contacts = OC.Contacts || {
self.currentid = 'new';
// Properties that the contact doesn't know
console.log('addContact, groupid', self.currentgroup);
self.$contactList.addClass('dim');
var groupprops = {
favorite: false,
groups: self.groups.categories,
@ -1009,8 +1047,9 @@ OC.Contacts = OC.Contacts || {
}
console.log('delete');
if(self.currentid) {
console.assert(utils.isUInt(self.currentid), 'self.currentid is not an integer');
self.contacts.delayedDelete(self.currentid);
console.assert(typeof self.currentid === 'string', 'self.currentid is not a string');
contactInfo = self.contacts[self.currentid].metaData();
self.contacts.delayedDelete(contactInfo);
} else {
self.contacts.delayedDelete(self.contacts.getSelectedContacts());
}
@ -1022,31 +1061,48 @@ OC.Contacts = OC.Contacts || {
return;
}
console.log('download');
var contacts = self.contacts.getSelectedContacts();
var ids = $.map(contacts, function(c) {return c.getId();});
document.location.href = OC.linkTo('contacts', 'export.php')
+ '?selectedids=' + self.contacts.getSelectedContacts().join(',');
+ '?selectedids=' + ids.join(',');
});
this.$header.on('click keydown', '.merge', function(event) {
if(wrongKey(event)) {
return;
}
console.log('merge');
self.mergeSelectedContacts();
});
this.$header.on('click keydown', '.favorite', function(event) {
if(wrongKey(event)) {
return;
}
if(!utils.isUInt(self.currentid)) {
return;
var contacts = self.contacts.getSelectedContacts();
self.setAllChecked(false);
self.$toggleAll.prop('checked', false);
if(!self.currentid) {
self.showActions(['add']);
}
// FIXME: This should only apply for contacts list.
var state = self.groups.isFavorite(self.currentid);
console.log('Favorite?', this, state);
self.groups.setAsFavorite(self.currentid, !state, function(jsondata) {
if(jsondata.status === 'success') {
if(state) {
self.$header.find('.favorite').switchClass('active', '');
} else {
self.$header.find('.favorite').switchClass('', 'active');
}
} else {
OC.notify({message:t('contacts', jsondata.data.message)});
$.each(contacts, function(idx, contact) {
if(!self.groups.isFavorite(contact.getId())) {
self.groups.setAsFavorite(contact.getId(), true, function(result) {
if(result.status !== 'success') {
OC.notify({message:
t('contacts',
'Error setting {name} as favorite.',
{name:contact.getDisplayName()})
});
}
});
}
});
self.showActions(['add']);
});
this.$contactList.on('mouseenter', 'td.email', function(event) {
@ -1060,13 +1116,27 @@ OC.Contacts = OC.Contacts || {
// Import using jquery.fileupload
$(function() {
var uploadingFiles = {}, numfiles = 0, uploadedfiles = 0, retries = 0;
var uploadingFiles = {},
numfiles = 0,
uploadedfiles = 0,
importedfiles = 0,
retries = 0,
succeded = 0
failed = 0;
var aid, importError = false;
var $progressbar = $('#import-progress');
var $status = $('#import-status-text');
self.$importFileInput.on('fileuploaddone', function(e, data) {
console.log('fileuploaddone', data);
var file = data.result.file;
console.log('fileuploaddone, file', file);
uploadedfiles += 1;
});
var waitForImport = function() {
if(numfiles == 0 && uploadedfiles == 0) {
console.log('waitForImport', numfiles, uploadedfiles, importedfiles);
if(uploadedfiles === importedfiles && importedfiles === numfiles) {
$progressbar.progressbar('value', 100);
if(!importError) {
OC.notify({
@ -1082,7 +1152,9 @@ OC.Contacts = OC.Contacts || {
}
});
}
retries = aid = 0;
$status.text(t('contacts', '{success} imported, {failed} failed.',
{success:succeded, failed:failed})).fadeIn();
numfiles = uploadedfiles = importedfiles = retries = aid = failed = succeded = 0;
$progressbar.fadeOut();
setTimeout(function() {
$status.fadeOut('slow');
@ -1110,11 +1182,11 @@ OC.Contacts = OC.Contacts || {
var importFiles = function(aid, uploadingFiles) {
console.log('importFiles', aid, uploadingFiles);
if(numfiles != uploadedfiles) {
if(numfiles !== uploadedfiles) {
OC.notify({message:t('contacts', 'Not all files uploaded. Retrying...')});
retries += 1;
if(retries > 3) {
numfiles = uploadedfiles = retries = aid = 0;
numfiles = uploadedfiles = importedfiles = retries = failed = succeded = aid = 0;
uploadingFiles = {};
$progressbar.fadeOut();
OC.dialogs.alert(t('contacts', 'Something went wrong with the upload, please retry.'), t('contacts', 'Error'));
@ -1130,14 +1202,19 @@ OC.Contacts = OC.Contacts || {
$status.text(t('contacts', 'Importing from {filename}...', {filename:fileName})).fadeIn();
doImport(fileName, aid, function(response) {
if(response.status === 'success') {
$status.text(t('contacts', '{success} imported, {failed} failed.',
{success:response.data.imported, failed:response.data.failed})).fadeIn();
importedfiles += 1;
succeded += response.data.imported;
failed += response.data.failed;
OC.notify({
message:t('contacts', '{success} imported, {failed} failed from {file}',
{success:response.data.imported, failed:response.data.failed, file:response.data.failed}
)
});
} else {
$('.import-upload').show();
$('.import-select').hide();
}
delete uploadingFiles[fileName];
numfiles -= 1; uploadedfiles -= 1;
$progressbar.progressbar('value',50+(50/(todo-uploadedfiles)));
});
});
@ -1155,80 +1232,34 @@ OC.Contacts = OC.Contacts || {
importFiles(aid, uploadingFiles);
});
$('#import_fileupload').fileupload({
self.$importFileInput.fileupload({
acceptFileTypes: /^text\/(directory|vcard|x-vcard)$/i,
add: function(e, data) {
var files = data.files;
var file = data.files[0];
console.log('add', file.name);
var totalSize=0;
if(files) {
numfiles += files.length; uploadedfiles = 0;
for(var i=0;i<files.length;i++) {
if(files[i].size ==0 && files[i].type== '') {
OC.dialogs.alert(t('files', 'Unable to upload your file as it is a directory or has 0 bytes'), t('files', 'Upload Error'));
if(file) {
numfiles += 1;
if(file.size === 0 || file.type== '') {
OC.dialogs.alert(t('contacts', 'Unable to upload your file as it is a directory or has 0 bytes'), t('contacts', 'Upload Error'));
return;
}
totalSize+=files[i].size;
}
totalSize+=file.size;
}
if(totalSize>$('#max_upload').val()) {
OC.dialogs.alert(t('contacts','The file you are trying to upload exceed the maximum size for file uploads on this server.'), t('contacts','Upload too large'));
numfiles = uploadedfiles = retries = aid = 0;
numfiles = uploadedfiles = importedfiles = retries = failed = succeded = aid = 0;
uploadingFiles = {};
return;
} else {
if($.support.xhrFileUpload) {
$.each(files, function(i, file) {
var fileName = file.name;
console.log('file.name', file.name);
var jqXHR = $('#import_fileupload').fileupload('send',
{
files: file,
formData: function(form) {
var formArray = form.serializeArray();
formArray['aid'] = aid;
return formArray;
}})
.success(function(response, textStatus, jqXHR) {
if(response.status == 'success') {
// import the file
uploadedfiles += 1;
} else {
OC.notify({message:response.data.message});
$('.import-upload').show();
$('.import-select').hide();
$('#import-progress').hide();
$('#import-status-text').hide();
}
return false;
})
.error(function(jqXHR, textStatus, errorThrown) {
console.log(textStatus);
OC.notify({message:errorThrown + ': ' + textStatus});
});
uploadingFiles[fileName] = jqXHR;
});
} else {
data.submit().success(function(data, status) {
response = jQuery.parseJSON(data[0].body.innerText);
if(response[0] != undefined && response[0].status == 'success') {
var file=response[0];
delete uploadingFiles[file.name];
$('tr').filterAttr('data-file',file.name).data('mime',file.mime);
var size = $('tr').filterAttr('data-file',file.name).find('td.filesize').text();
if(size==t('files','Pending')){
$('tr').filterAttr('data-file',file.name).find('td.filesize').text(file.size);
}
FileList.loadingDone(file.name);
} else {
OC.notify({message:response.data.message});
}
});
}
}
var jqXHR = data.submit();
uploadingFiles[file.name] = jqXHR;
},
fail: function(e, data) {
console.log('fail');
OC.notify({message:data.errorThrown + ': ' + data.textStatus});
numfiles = uploadedfiles = importedfiles = retries = failed = succeded = aid = 0;
$('.import-upload').show();
$('.import-select').hide();
// TODO: Remove file from upload queue.
@ -1240,19 +1271,12 @@ OC.Contacts = OC.Contacts || {
start: function(e, data) {
$progressbar.progressbar({value:0});
$progressbar.fadeIn();
if(data.dataType != 'iframe ') {
$('#upload input.stop').show();
}
},
stop: function(e, data) {
console.log('stop, data', data);
// stop only gets fired once so we collect uploaded items here.
$('.import-upload').hide();
$('.import-select').show();
if(data.dataType != 'iframe ') {
$('#upload input.stop').hide();
}
}
});
});
@ -1261,8 +1285,8 @@ OC.Contacts = OC.Contacts || {
event.preventDefault();
});
$(document).on('keypress', function(event) {
if(!$(event.target).is('body')) {
$(document).on('keyup', function(event) {
if(!$(event.target).is('body') || event.isPropagationStopped()) {
return;
}
var keyCode = Math.max(event.keyCode, event.which);
@ -1350,41 +1374,123 @@ OC.Contacts = OC.Contacts || {
$('.tooltipped.downwards.onfocus').tipsy({trigger: 'focus', gravity: 'n'});
$('.tooltipped.rightwards.onfocus').tipsy({trigger: 'focus', gravity: 'w'});
},
mergeSelectedContacts: function() {
var contacts = this.contacts.getSelectedContacts();
var self = this;
this.$rightContent.append('<div id="merge_contacts_dialog"></div>');
if(!this.$mergeContactsTmpl) {
this.$mergeContactsTmpl = $('#mergeContactsTemplate');
}
var $dlg = this.$mergeContactsTmpl.octemplate();
var $liTmpl = $dlg.find('li').detach();
var $mergeList = $dlg.find('.mergelist');
$.each(contacts, function(idx, contact) {
var $li = $liTmpl
.octemplate({idx: idx, id: contact.getId(), displayname: contact.getDisplayName()});
if(!contact.data.thumbnail) {
$li.addClass('thumbnail');
} else {
$li.css('background-image', 'url(data:image/png;base64,' + contact.data.thumbnail + ')');
}
if(idx === 0) {
$li.find('input:radio').prop('checked', true);
}
$mergeList.append($li);
});
this.$contactList.addClass('dim');
$('#merge_contacts_dialog').html($dlg).ocdialog({
closeOnEscape: true,
title: t('contacts', 'Merge contacts'),
height: 'auto', width: 'auto',
buttons: [
{
text: t('contacts', 'Merge contacts'),
click:function() {
// Do the merging, use $(this) to get dialog
var contactid = $(this).find('input:radio:checked').val();
var others = [];
var deleteOther = $(this).find('#delete_other').prop('checked');
console.log('Selected contact', contactid, 'Delete others', deleteOther);
$.each($(this).find('input:radio:not(:checked)'), function(idx, item) {
others.push($(item).val());
});
console.log('others', others);
$(document).trigger('request.contact.merge', {
merger: contactid,
mergees: others,
deleteOther: deleteOther
});
$(this).ocdialog('close');
},
defaultButton: true
},
{
text: t('contacts', 'Cancel'),
click:function(dlg) {
$(this).ocdialog('close');
return false;
}
}
],
close: function(event, ui) {
$(this).ocdialog('destroy').remove();
$('#add_group_dialog').remove();
self.$contactList.removeClass('dim');
},
open: function(event, ui) {
$dlg.find('input').focus();
}
});
},
addGroup: function(cb) {
var self = this;
$('body').append('<div id="add_group_dialog"></div>');
this.$rightContent.append('<div id="add_group_dialog"></div>');
if(!this.$addGroupTmpl) {
this.$addGroupTmpl = $('#addGroupTemplate');
}
this.$contactList.addClass('dim');
var $dlg = this.$addGroupTmpl.octemplate();
$('#add_group_dialog').html($dlg).dialog({
$('#add_group_dialog').html($dlg).ocdialog({
modal: true,
closeOnEscape: true,
title: t('contacts', 'Add group'),
height: 'auto', width: 'auto',
buttons: {
'Ok':function() {
buttons: [
{
text: t('contacts', 'OK'),
click:function() {
var name = $(this).find('input').val();
if(name.trim() === '') {
return false;
}
self.groups.addGroup(
{name:$dlg.find('input:text').val()},
function(response) {
if(typeof cb === 'function') {
cb(response);
} else {
if(response.status !== 'success') {
if(response.error) {
OC.notify({message: response.message});
}
}
});
$(this).dialog('close');
$(this).ocdialog('close');
},
'Cancel':function() {
$(this).dialog('close');
defaultButton: true
},
{
text: t('contacts', 'Cancel'),
click:function(dlg) {
$(this).ocdialog('close');
return false;
}
},
}
],
close: function(event, ui) {
$(this).dialog('destroy').remove();
$(this).ocdialog('destroy').remove();
$('#add_group_dialog').remove();
self.$contactList.removeClass('dim');
},
open: function(event, ui) {
$dlg.find('input').focus();
@ -1401,6 +1507,7 @@ OC.Contacts = OC.Contacts || {
this.$rightContent.scrollTop(this.contacts.contactPos(id)-30);
},
closeContact: function(id) {
$(window).unbind('hashchange', this.hashChange);
if(typeof this.currentid === 'number') {
var contact = this.contacts.findById(id);
if(contact && contact.close()) {
@ -1419,12 +1526,16 @@ OC.Contacts = OC.Contacts || {
$(document).trigger('status.nomorecontacts');
}
//$('body').unbind('click', this.bodyListener);
window.location.hash = '';
$(window).bind('hashchange', this.hashChange);
},
openContact: function(id) {
this.hideActions();
console.log('Contacts.openContact', id);
if(this.currentid) {
this.closeContact(this.currentid);
}
$(window).unbind('hashchange', this.hashChange);
this.currentid = parseInt(id);
console.log('Contacts.openContact, Favorite', this.currentid, this.groups.isFavorite(this.currentid), this.groups);
this.setAllChecked(false);
@ -1458,10 +1569,12 @@ OC.Contacts = OC.Contacts || {
if($contactelem.find($(e.target)).length === 0) {
self.closeContact(self.currentid);
}
};
};*/
window.location.hash = this.currentid.toString();
setTimeout(function() {
$('body').bind('click', self.bodyListener);
}, 500);*/
//$('body').bind('click', self.bodyListener);
$(window).bind('hashchange', this.hashChange);
}, 500);
},
update: function() {
console.log('update');
@ -1475,7 +1588,6 @@ OC.Contacts = OC.Contacts || {
var file = filelist[0];
var target = $('#file_upload_target');
var form = $('#file_upload_form');
form.find('input[name="id"]').val(this.currentid);
var totalSize=0;
if(file.size > $('#max_upload').val()){
OC.notify({
@ -1489,7 +1601,10 @@ OC.Contacts = OC.Contacts || {
var response=jQuery.parseJSON(target.contents().text());
if(response != undefined && response.status == 'success') {
console.log('response', response);
self.editPhoto(self.currentid, response.data.tmp);
self.editPhoto(
response.metadata,
response.data.tmp
);
//alert('File: ' + file.tmp + ' ' + file.name + ' ' + file.mime);
} else {
OC.notify({message:response.data.message});
@ -1498,28 +1613,28 @@ OC.Contacts = OC.Contacts || {
form.submit();
}
},
cloudPhotoSelected:function(id, path) {
cloudPhotoSelected:function(metadata, path) {
var self = this;
console.log('cloudPhotoSelected, id', id);
console.log('cloudPhotoSelected', metadata);
$.getJSON(OC.filePath('contacts', 'ajax', 'oc_photo.php'),
{path: path, id: id},function(jsondata) {
{path: path, contact: metadata},function(jsondata) {
if(jsondata.status == 'success') {
//alert(jsondata.data.page);
self.editPhoto(jsondata.data.id, jsondata.data.tmp);
$('#edit_photo_dialog_img').html(jsondata.data.page);
self.editPhoto(metadata, jsondata.data.tmp);
//$('#edit_photo_dialog_img').html(jsondata.data.page);
}
else{
OC.notify({message: jsondata.data.message});
}
});
},
editCurrentPhoto:function(id) {
editCurrentPhoto:function(metadata) {
var self = this;
$.getJSON(OC.filePath('contacts', 'ajax', 'currentphoto.php'),
{id: id}, function(jsondata) {
$.getJSON(OC.filePath('contacts', 'ajax', 'currentphoto.php'), metadata,
function(jsondata) {
if(jsondata.status == 'success') {
//alert(jsondata.data.page);
self.editPhoto(jsondata.data.id, jsondata.data.tmp);
self.editPhoto(metadata, jsondata.data.tmp);
$('#edit_photo_dialog_img').html(jsondata.data.page);
}
else{
@ -1527,8 +1642,8 @@ OC.Contacts = OC.Contacts || {
}
});
},
editPhoto:function(id, tmpkey) {
console.log('editPhoto', id, tmpkey);
editPhoto:function(metadata, tmpkey) {
console.log('editPhoto', metadata, tmpkey);
$('.tipsy').remove();
// Simple event handler, called from onChange and onSelect
// event handlers, as per the Jcrop invocation above
@ -1550,7 +1665,13 @@ OC.Contacts = OC.Contacts || {
this.$cropBoxTmpl = $('#cropBoxTemplate');
}
$('body').append('<div id="edit_photo_dialog"></div>');
var $dlg = this.$cropBoxTmpl.octemplate({id: id, tmpkey: tmpkey});
var $dlg = this.$cropBoxTmpl.octemplate(
{
backend: metadata.backend,
addressbookid: metadata.addressbookid,
contactid: metadata.contactid,
tmpkey: tmpkey
});
var cropphoto = new Image();
$(cropphoto).load(function () {
@ -1590,208 +1711,38 @@ OC.Contacts = OC.Contacts || {
});
}).error(function () {
OC.notify({message:t('contacts','Error loading profile picture.')});
}).attr('src', OC.linkTo('contacts', 'tmpphoto.php')+'?tmpkey='+tmpkey);
}).attr('src', OC.linkTo('contacts', 'tmpphoto.php')+'?tmpkey='+tmpkey+'&refresh='+Math.random());
},
savePhoto:function($dlg) {
var form = $dlg.find('#cropform');
q = form.serialize();
console.log('savePhoto', q);
$.post(OC.filePath('contacts', 'ajax', 'savecrop.php'), q, function(response) {
var jsondata = $.parseJSON(response);
console.log('savePhoto, jsondata', typeof jsondata);
if(jsondata && jsondata.status === 'success') {
//var jsondata = $.parseJSON(response);
console.log('savePhoto, response', typeof response);
if(response && response.status === 'success') {
// load cropped photo.
$(document).trigger('status.contact.photoupdated', {
id: jsondata.data.id
id: response.data.id,
thumbnail: response.data.thumbnail
});
} else {
if(!jsondata) {
if(!response) {
OC.notify({message:t('contacts', 'Network or server error. Please inform administrator.')});
} else {
OC.notify({message: jsondata.data.message});
OC.notify({message: response.data.message});
}
}
});
},
addAddressbook:function(data, cb) {
$.ajax({
type:'POST',
async:false,
url:OC.filePath('contacts', 'ajax', 'addressbook/add.php'),
data:{ name: data.name, description: data.description },
success:function(jsondata) {
if(jsondata.status == 'success') {
if(typeof cb === 'function') {
cb({
status:'success',
addressbook: jsondata.data.addressbook
});
}
} else {
if(typeof cb === 'function') {
cb({status:'error', message:jsondata.data.message});
} else {
OC.notify({message:textStatus + ': ' + errorThrown});
}
}
},
error:function(jqXHR, textStatus, errorThrown) {
if(typeof cb === 'function') {
cb({
status:'success',
message: textStatus + ': ' + errorThrown
});
} else {
OC.notify({message:textStatus + ': ' + errorThrown});
}
}
});
},
// NOTE: Deprecated
selectAddressbook:function(cb) {
var self = this;
var jqxhr = $.get(OC.filePath('contacts', 'templates', 'selectaddressbook.html'), function(data) {
$('body').append('<div id="addressbook_dialog"></div>');
var $dlg = $('#addressbook_dialog').html(data).octemplate({
nameplaceholder: t('contacts', 'Enter name'),
descplaceholder: t('contacts', 'Enter description')
}).dialog({
modal: true, height: 'auto', width: 'auto',
title: t('contacts', 'Select addressbook'),
buttons: {
'Ok':function() {
aid = $(this).find('input:checked').val();
if(aid == 'new') {
var displayname = $(this).find('input.name').val();
var description = $(this).find('input.desc').val();
if(!$.trim(displayname)) {
OC.dialogs.alert(t('contacts', 'The address book name cannot be empty.'), t('contacts', 'Error'));
return false;
}
console.log('ID, name and desc', aid, displayname, description);
if(typeof cb === 'function') {
// TODO: Create addressbook
var data = {name:displayname, description:description};
self.addAddressbook(data, function(data) {
if(data.status === 'success') {
cb({
status:'success',
addressbook:data.addressbook
});
} else {
cb({status:'error'});
}
});
}
$(this).dialog('close');
} else {
console.log('aid ' + aid);
if(typeof cb === 'function') {
cb({
status:'success',
addressbook:self.contacts.addressbooks[parseInt(aid)]
});
}
$(this).dialog('close');
}
},
'Cancel':function() {
$(this).dialog('close');
}
},
close: function(event, ui) {
$(this).dialog('destroy').remove();
$('#addressbook_dialog').remove();
},
open: function(event, ui) {
console.log('open', $(this));
var $lastrow = $(this).find('tr.new');
$.each(self.contacts.addressbooks, function(i, book) {
console.log('book', i, book);
if(book.owner === OC.currentUser
|| (book.permissions & OC.PERMISSION_UPDATE
|| book.permissions & OC.PERMISSION_CREATE
|| book.permissions & OC.PERMISSION_DELETE)) {
var row = '<tr><td><input id="book_{id}" name="book" type="radio" value="{id}"</td>'
+ '<td><label for="book_{id}">{displayname}</label></td>'
+ '<td>{description}</td></tr>';
var $row = $(row).octemplate({
id:book.id,
displayname:book.displayname,
description:book.description
});
$lastrow.before($row);
}
});
$(this).find('input[type="radio"]').first().prop('checked', true);
$lastrow.find('input.name,input.desc').on('focus', function(e) {
$lastrow.find('input[type="radio"]').prop('checked', true);
});
}
});
}).error(function() {
OC.notify({message: t('contacts', 'Network or server error. Please inform administrator.')});
});
}
};
(function( $ ) {
/**
* Object Template
* Inspired by micro templating done by e.g. underscore.js
*/
var Template = {
init: function(vars, options, elem) {
// Mix in the passed in options with the default options
this.vars = vars;
this.options = $.extend({},this.options,options);
this.elem = elem;
var self = this;
if(typeof this.options.escapeFunction === 'function') {
$.each(this.vars, function(key, val) {
if(typeof val === 'string') {
self.vars[key] = self.options.escapeFunction(val);
}
});
}
var _html = this._build(this.vars);
return $(_html);
},
// From stackoverflow.com/questions/1408289/best-way-to-do-variable-interpolation-in-javascript
_build: function(o){
var data = this.elem.attr('type') === 'text/template' ? this.elem.html() : this.elem.get(0).outerHTML;
try {
return data.replace(/{([^{}]*)}/g,
function (a, b) {
var r = o[b];
return typeof r === 'string' || typeof r === 'number' ? r : a;
}
);
} catch(e) {
console.error(e, 'data:', data)
}
},
options: {
escapeFunction: function(str) {return $('<i></i>').text(str).html();}
}
};
$.fn.octemplate = function(vars, options) {
var vars = vars ? vars : {};
if(this.length) {
var _template = Object.create(Template);
return _template.init(vars, options, this);
}
};
})( jQuery );
$(document).ready(function() {
OC.Router.registerLoadedCallback(function() {
$.getScript(OC.Router.generate('contacts_jsconfig'), function() {
OC.Contacts.init();
});
});
});

View File

@ -2,8 +2,8 @@
/**
* ownCloud - Addressbook
*
* @author Jakob Sack
* @copyright 2011 Jakob Sack mail@jakobsack.de
* @author Thomas Tanghus
* @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
@ -20,21 +20,17 @@
*
*/
// Check if we are a user
OCP\JSON::setContentTypeHeader('text/javascript');
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
OCP\JSON::callCheck();
require_once __DIR__.'/../loghandler.php';
$id = $_POST['id'];
if(!$id) {
bailOut(OCA\Contacts\App::$l10n->t('id is not set.'));
}
$user = OCP\User::getUser();
try {
OCA\Contacts\Addressbook::delete($id);
} catch(Exception $e) {
bailOut($e->getMessage());
}
OCP\JSON::success(array('data' => array( 'id' => $id )));
echo 'var contacts_groups_sortorder=[' . OCP\Config::getUserValue($user, 'contacts', 'groupsort', '') . '],';
echo 'contacts_properties_indexed = '
. (OCP\Config::getUserValue($user, 'contacts', 'contacts_properties_indexed', 'no') === 'no'
? 'false' : 'true') . ',';
echo 'contacts_categories_indexed = '
. (OCP\Config::getUserValue($user, 'contacts', 'contacts_categories_indexed', 'no') === 'no'
? 'false' : 'true') . ',';
echo 'lang=\'' . OCP\Config::getUserValue($user, 'core', 'lang', 'en') . '\';';

View File

@ -7,17 +7,18 @@ OC.Contacts = OC.Contacts || {};
* An item which binds the appropriate html and event handlers
* @param parent the parent ContactList
* @param id The integer contact id.
* @param access An access object containing and 'owner' string variable and an integer 'permissions' variable.
* @param metadata An metadata object containing and 'owner' string variable and an integer 'permissions' variable.
* @param data the data used to populate the contact
* @param listtemplate the jquery object used to render the contact list item
* @param fulltemplate the jquery object used to render the entire contact
* @param detailtemplates A map of jquery objects used to render the contact parts e.g. EMAIL, TEL etc.
*/
var Contact = function(parent, id, access, data, listtemplate, dragtemplate, fulltemplate, detailtemplates) {
//console.log('contact:', id, access); //parent, id, data, listtemplate, fulltemplate);
var Contact = function(parent, id, metadata, data, listtemplate, dragtemplate, fulltemplate, detailtemplates) {
//console.log('contact:', id, metadata, data); //parent, id, data, listtemplate, fulltemplate);
this.parent = parent,
this.storage = parent.storage,
this.id = id,
this.access = access,
this.metadata = metadata,
this.data = data,
this.$dragTemplate = dragtemplate,
this.$listTemplate = listtemplate,
@ -27,6 +28,98 @@ OC.Contacts = OC.Contacts || {};
this.multi_properties = ['EMAIL', 'TEL', 'IMPP', 'ADR', 'URL'];
};
Contact.prototype.metaData = function() {
return {
contactid: this.id,
addressbookid: this.metadata.parent,
backend: this.metadata.backend
}
};
Contact.prototype.getDisplayName = function() {
return this.getPreferredValue('FN');
};
Contact.prototype.getId = function() {
return this.id;
};
Contact.prototype.getParent = function() {
return this.metadata.parent;
};
Contact.prototype.getBackend = function() {
return this.metadata.backend;
};
Contact.prototype.merge = function(mergees) {
console.log('Contact.merge, mergees', mergees);
if(!mergees instanceof Array && !mergees instanceof Contact) {
throw new TypeError('BadArgument: Contact.merge() only takes Contacts');
} else {
if(mergees instanceof Contact) {
mergees = [mergees];
}
}
// For multi_properties
var addIfNotExists = function(name, newproperty) {
// If the property isn't set at all just add it and return.
if(!self.data[name]) {
self.data[name] = [newproperty];
return;
}
var found = false;
$.each(self.data[name], function(idx, property) {
if(name === 'ADR') {
// Do a simple string comparison
if(property.value.join(';').toLowerCase() === newproperty.value.join(';').toLowerCase()) {
found = true;
return false; // break loop
}
} else {
if(property.value.toLowerCase() === newproperty.value.toLowerCase()) {
found = true;
return false; // break loop
}
}
});
if(found) {
return;
}
// Not found, so adding it.
self.data[name].push(newproperty);
}
var self = this;
$.each(mergees, function(idx, mergee) {
console.log('Contact.merge, mergee', mergee);
if(!mergee instanceof Contact) {
throw new TypeError('BadArgument: Contact.merge() only takes Contacts');
}
if(mergee === self) {
throw new Error('BadArgument: Why should I merge with myself?');
}
$.each(mergee.data, function(name, properties) {
if(self.multi_properties.indexOf(name) === -1) {
if(self.data[name] && self.data[name].length > 0) {
// If the property exists don't touch it.
return true; // continue
} else {
// Otherwise add it.
self.data[name] = properties;
}
} else {
$.each(properties, function(idx, property) {
addIfNotExists(name, property);
});
}
});
console.log('Merged', self.data);
});
return true;
};
Contact.prototype.showActions = function(act) {
this.$footer.children().hide();
if(act && act.length > 0) {
@ -68,7 +161,7 @@ OC.Contacts = OC.Contacts || {};
newvalue: params.newvalue,
oldvalue: params.oldvalue
});
console.log('undoQueue', this.undoQueue);
//console.log('undoQueue', this.undoQueue);
}
Contact.prototype.addProperty = function($option, name) {
@ -137,6 +230,8 @@ OC.Contacts = OC.Contacts || {};
console.log('Contact.deleteProperty, element', element, $container);
var params = {
name: element,
backend: this.metadata.backend,
addressbookid: this.metadata.parent,
id: this.id
};
if(this.multi_properties.indexOf(element) !== -1) {
@ -153,16 +248,9 @@ OC.Contacts = OC.Contacts || {};
}
this.setAsSaving(obj, true);
var self = this;
$.post(OC.filePath('contacts', 'ajax', 'contact/deleteproperty.php'), params, function(jsondata) {
if(!jsondata) {
$(document).trigger('status.contact.error', {
status: 'error',
message: t('contacts', 'Network or server error. Please inform administrator.')
});
self.setAsSaving(obj, false);
return false;
}
if(jsondata.status == 'success') {
$.when(this.storage.deleteProperty(this.metadata.backend, this.metadata.parent, this.id, params))
.then(function(response) {
if(!response.error) {
// TODO: Test if removing from internal data structure works
if(self.multi_properties.indexOf(element) !== -1) {
// First find out if an existing element by looking for checksum
@ -206,19 +294,68 @@ OC.Contacts = OC.Contacts || {};
return true;
} else {
$(document).trigger('status.contact.error', {
status: 'error',
message: jsondata.data.message
message: response.message
});
self.setAsSaving(obj, false);
return false;
}
},'json');
})
.fail(function(jqxhr, textStatus, error) {
var err = textStatus + ', ' + error;
console.warn( "Request Failed: " + err);
$(document).trigger('status.contact.error', {
message: t('contacts', 'Failed deleting property: {error}', {error:err})
});
});
;
};
/**
* @brief Save all properties. Used for merging contacts.
* If this is a new contact it will first be saved to the datastore and a
* new datastructure will be added to the object.
*/
Contact.prototype.saveAll = function(cb) {
console.log('Contact.saveAll');
if(!this.id) {
var self = this;
this.add({isnew:true}, function(response) {
if(response.error) {
console.warn('No response object');
return false;
}
self.saveAll();
});
return;
}
var self = this;
this.setAsSaving(this.$fullelem, true);
var data = JSON.stringify(this.data);
//console.log('stringified', data);
$.when(this.storage.saveAllProperties(this.metadata.backend, this.metadata.parent, this.id, data))
.then(function(response) {
if(!response.error) {
self.data = response.data.data;
self.metadata = response.data.metadata;
if(typeof cb === 'function') {
cb({error:false});
}
} else {
$(document).trigger('status.contact.error', {
message: response.message
});
if(typeof cb === 'function') {
cb({error:true, message:response.message});
}
}
self.setAsSaving(self.$fullelem, false);
});
}
/**
* @brief Act on change of a property.
* If this is a new contact it will first be saved to the datastore and a
* new datastructure will be added to the object. FIXME: Not implemented yet.
* new datastructure will be added to the object.
* If the obj argument is not provided 'name' and 'value' MUST be provided
* and this is only allowed for single elements like N, FN, CATEGORIES.
* @param obj. The form form field that has changed.
@ -241,32 +378,25 @@ OC.Contacts = OC.Contacts || {};
}
var obj = null;
var element = null;
var q = '';
var args = [];
if(params.obj) {
obj = params.obj;
q = this.queryStringFor(obj);
args = this.argumentsFor(obj);
args['parameters'] = this.parametersFor(obj);
element = this.propertyTypeFor(obj);
} else {
args = params;
element = params.name;
var value = utils.isArray(params.value)
? $.param(params.value)
: encodeURIComponent(params.value);
q = 'id=' + this.id + '&value=' + value + '&name=' + element;
}
console.log('q', q);
console.log('args', args);
var self = this;
this.setAsSaving(obj, true);
$.post(OC.filePath('contacts', 'ajax', 'contact/saveproperty.php'), q, function(jsondata){
if(!jsondata) {
$(document).trigger('status.contact.error', {
status: 'error',
message: t('contacts', 'Network or server error. Please inform administrator.')
});
$(obj).addClass('error');
self.setAsSaving(obj, false);
return false;
}
if(jsondata.status == 'success') {
$.when(this.storage.saveProperty(this.metadata.backend, this.metadata.parent, this.id, args))
.then(function(response) {
if(!response.error) {
if(!self.data[element]) {
self.data[element] = [];
}
@ -283,7 +413,7 @@ OC.Contacts = OC.Contacts || {};
self.pushToUndo({
action:'save',
name: element,
newchecksum: jsondata.data.checksum,
newchecksum: response.data.checksum,
oldchecksum: checksum,
newvalue: value,
oldvalue: obj.defaultValue
@ -294,7 +424,7 @@ OC.Contacts = OC.Contacts || {};
name: element,
value: value,
parameters: parameters,
checksum: jsondata.data.checksum
checksum: response.data.checksum
};
return false;
}
@ -304,17 +434,17 @@ OC.Contacts = OC.Contacts || {};
self.pushToUndo({
action:'add',
name: element,
newchecksum: jsondata.data.checksum,
newchecksum: response.data.checksum,
newvalue: value,
});
self.data[element].push({
name: element,
value: value,
parameters: parameters,
checksum: jsondata.data.checksum,
checksum: response.data.checksum,
});
}
self.propertyContainerFor(obj).data('checksum', jsondata.data.checksum);
self.propertyContainerFor(obj).data('checksum', response.data.checksum);
} else {
// Save value and parameters internally
var value = obj ? self.valueFor(obj) : params.value;
@ -327,6 +457,16 @@ OC.Contacts = OC.Contacts || {};
case 'CATEGORIES':
// We deal with this in addToGroup()
break;
case 'BDAY':
// reverse order again.
value = $.datepicker.formatDate('yy-mm-dd', $.datepicker.parseDate('dd-mm-yy', value));
self.data[element][0] = {
name: element,
value: value,
parameters: self.parametersFor(obj),
checksum: response.data.checksum
};
break;
case 'FN':
if(!self.data.FN || !self.data.FN.length) {
self.data.FN = [{name:'FN', value:'', parameters:[]}];
@ -380,7 +520,6 @@ OC.Contacts = OC.Contacts || {};
$fullname.val(self.data.FN[0]['value']);
update_fn = true;
} else if($fullname.val() == value[1] + ' ') {
console.log('change', value);
self.data.FN[0]['value'] = value[1] + ' ' + value[0];
$fullname.val(self.data.FN[0]['value']);
update_fn = true;
@ -395,7 +534,6 @@ OC.Contacts = OC.Contacts || {};
}, 1000);
}
case 'NICKNAME':
case 'BDAY':
case 'ORG':
case 'TITLE':
case 'NOTE':
@ -403,7 +541,7 @@ OC.Contacts = OC.Contacts || {};
name: element,
value: value,
parameters: self.parametersFor(obj),
checksum: jsondata.data.checksum
checksum: response.data.checksum
};
break;
default:
@ -418,13 +556,12 @@ OC.Contacts = OC.Contacts || {};
return true;
} else {
$(document).trigger('status.contact.error', {
status: 'error',
message: jsondata.data.message
message: response.message
});
self.setAsSaving(obj, false);
return false;
}
},'json');
});
};
/**
@ -499,22 +636,16 @@ OC.Contacts = OC.Contacts || {};
* @returns The callback gets an object as argument with a variable 'status' of either 'success'
* or 'error'. On success the 'data' property of that object contains the contact id as 'id', the
* addressbook id as 'aid' and the contact data structure as 'details'.
* TODO: Use Storage for adding and make sure to get all metadata.
*/
Contact.prototype.add = function(params, cb) {
var self = this;
$.post(OC.filePath('contacts', 'ajax', 'contact/add.php'),
params, function(jsondata) {
if(!jsondata) {
$(document).trigger('status.contact.error', {
status: 'error',
message: t('contacts', 'Network or server error. Please inform administrator.')
});
return false;
}
if(jsondata.status === 'success') {
self.id = parseInt(jsondata.data.id);
self.access.id = parseInt(jsondata.data.aid);
self.data = jsondata.data.details;
$.when(this.storage.addContact(this.metadata.backend, this.metadata.parent))
.then(function(response) {
if(!response.error) {
self.id = String(response.data.metadata.id);
self.metadata = response.data.metadata;
self.data = response.data.data;
self.$groupSelect.multiselect('enable');
// Add contact to current group
if(self.groupprops && self.groupprops.currentgroup.id !== 'all'
@ -534,9 +665,14 @@ OC.Contacts = OC.Contacts || {};
id: self.id,
contact: self
});
} else {
$(document).trigger('status.contact.error', {
message: response.message
});
return false;
}
if(typeof cb == 'function') {
cb(jsondata);
cb(response);
}
});
};
@ -548,9 +684,14 @@ OC.Contacts = OC.Contacts || {};
*/
Contact.prototype.destroy = function(cb) {
var self = this;
$.post(OC.filePath('contacts', 'ajax', 'contact/delete.php'),
{id: this.id}, function(jsondata) {
if(jsondata && jsondata.status === 'success') {
$.when(this.storage.deleteContact(
this.metadata.backend,
this.metadata.parent,
this.id)
).then(function(response) {
//$.post(OC.filePath('contacts', 'ajax', 'contact/delete.php'),
// {id: this.id}, function(response) {
if(!response.error) {
if(self.$listelem) {
self.$listelem.remove();
}
@ -559,22 +700,49 @@ OC.Contacts = OC.Contacts || {};
}
}
if(typeof cb == 'function') {
var retval = {status: jsondata ? jsondata.status : 'error'};
if(jsondata) {
if(jsondata.status === 'success') {
retval['id'] = jsondata.id;
if(response.error) {
cb(response);
} else {
retval['message'] = jsondata.message;
cb({id:self.id});
}
} else {
retval['message'] = t('contacts', 'There was an unknown error when trying to delete this contact');
retval['id'] = self.id;
}
cb(retval);
}
});
};
Contact.prototype.argumentsFor = function(obj) {
var args = {};
var ptype = this.propertyTypeFor(obj);
args['name'] = ptype;
if(this.multi_properties.indexOf(ptype) !== -1) {
args['checksum'] = this.checksumFor(obj);
}
if($(obj).hasClass('propertycontainer')) {
if($(obj).is('select[data-element="categories"]')) {
args['value'] = [];
$.each($(obj).find(':selected'), function(idx, e) {
args['value'].push($(e).text());
});
} else {
args['value'] = $(obj).val();
}
} else {
var $elements = this.propertyContainerFor(obj)
.find('input.value,select.value,textarea.value');
if($elements.length > 1) {
args['value'] = [];
$.each($elements, function(idx, e) {
args['value'][parseInt($(e).attr('name').substr(6,1))] = $(e).val();
//args['value'].push($(e).val());
});
} else {
args['value'] = $elements.val();
}
}
return args;
};
Contact.prototype.queryStringFor = function(obj) {
var q = 'id=' + this.id;
var ptype = this.propertyTypeFor(obj);
@ -617,7 +785,7 @@ OC.Contacts = OC.Contacts || {};
Contact.prototype.valueFor = function(obj) {
var $container = this.propertyContainerFor(obj);
console.assert($container.length > 0, 'Couldn\'t find container for ' + $(obj));
return $container.is('input')
return $container.is('input.value')
? $container.val()
: (function() {
var $elem = $container.find('textarea.value,input.value:not(:checkbox)');
@ -636,23 +804,29 @@ OC.Contacts = OC.Contacts || {};
Contact.prototype.parametersFor = function(obj, asText) {
var parameters = [];
$.each(this.propertyContainerFor(obj).find('select.parameter,input:checkbox:checked.parameter,textarea'), function(i, elem) {
$.each(this.propertyContainerFor(obj)
.find('select.parameter,input:checkbox:checked.parameter,textarea'), // Why do I look for textarea?
function(i, elem) {
var $elem = $(elem);
var paramname = $elem.data('parameter');
if(!parameters[paramname]) {
parameters[paramname] = [];
}
var val;
if(asText) {
if($elem.is(':checkbox')) {
val = $elem.attr('title');
} else if($elem.is('select')) {
val = $elem.find(':selected').text();
}
if(asText) {
parameters[paramname].push($elem.attr('title'));
} else {
val = $elem.val();
parameters[paramname].push($elem.attr('value'));
}
} else if($elem.is('select')) {
$.each($elem.find(':selected'), function(idx, e) {
if(asText) {
parameters[paramname].push($(e).text());
} else {
parameters[paramname].push($(e).val());
}
});
}
parameters[paramname].push(val);
});
return parameters;
};
@ -673,6 +847,7 @@ OC.Contacts = OC.Contacts || {};
name: this.getPreferredValue('FN', '')
});
}
this.setThumbnail(this.$dragelem);
return this.$dragelem;
}
@ -683,18 +858,23 @@ OC.Contacts = OC.Contacts || {};
Contact.prototype.renderListItem = function(isnew) {
this.$listelem = this.$listTemplate.octemplate({
id: this.id,
name: isnew ? this.getPreferredValue('FN', '') : this.getPreferredValue('FN', ''),
email: isnew ? this.getPreferredValue('EMAIL', '') : this.getPreferredValue('EMAIL', ''),
tel: isnew ? this.getPreferredValue('TEL', '') : this.getPreferredValue('TEL', ''),
adr: isnew ? this.getPreferredValue('ADR', []).clean('').join(', ') : this.getPreferredValue('ADR', []).clean('').join(', '),
parent: this.metadata.parent,
backend: this.metadata.backend,
name: this.getPreferredValue('FN', ''),
email: this.getPreferredValue('EMAIL', ''),
tel: this.getPreferredValue('TEL', ''),
adr: this.getPreferredValue('ADR', []).clean('').join(', '),
categories: this.getPreferredValue('CATEGORIES', [])
.clean('').join(' / ')
});
if(this.access.owner !== OC.currentUser
&& !(this.access.permissions & OC.PERMISSION_UPDATE
|| this.access.permissions & OC.PERMISSION_DELETE)) {
if(this.metadata.owner !== OC.currentUser
&& !(this.metadata.permissions & OC.PERMISSION_UPDATE
|| this.metadata.permissions & OC.PERMISSION_DELETE)) {
this.$listelem.find('input:checkbox').prop('disabled', true).css('opacity', '0');
}
if(isnew) {
this.setThumbnail();
}
return this.$listelem;
};
@ -723,7 +903,7 @@ OC.Contacts = OC.Contacts || {};
});
self.$groupSelect.bind('multiselectclick', function(event, ui) {
var action = ui.checked ? 'addtogroup' : 'removefromgroup';
console.assert(typeof self.id === 'number', 'ID is not a number')
console.assert(typeof self.id === 'string', 'ID is not a string')
$(document).trigger('request.contact.' + action, {
id: self.id,
groupid: parseInt(ui.value)
@ -739,6 +919,34 @@ OC.Contacts = OC.Contacts || {};
}
};
var buildAddressBookSelect = function(availableAddressBooks) {
console.log('address books', availableAddressBooks.length, availableAddressBooks);
/* TODO:
* - Check address books permissions.
* - Add method to change address book.
*/
$.each(availableAddressBooks, function(idx, addressBook) {
//console.log('addressBook', idx, addressBook);
var $option = $('<option data-backend="'
+ addressBook.backend + '" value="' + addressBook.id + '">'
+ addressBook.displayname + '(' + addressBook.backend + ')</option>');
if(self.metadata.parent === addressBook.id
&& self.metadata.backend === addressBook.backend) {
$option.attr('selected', 'selected');
}
self.$addressBookSelect.append($option);
});
self.$addressBookSelect.multiselect({
header: false,
selectedList: 3,
noneSelectedText: self.$addressBookSelect.attr('title'),
selectedText: t('contacts', '# groups')
});
if(self.id) {
self.$addressBookSelect.multiselect('disable');
}
};
var n = this.getPreferredValue('N', ['', '', '', '', '']);
//console.log('Contact.renderContact', this.data);
var values = this.data
@ -770,6 +978,11 @@ OC.Contacts = OC.Contacts || {};
this.$groupSelect = this.$fullelem.find('#contactgroups');
buildGroupSelect(groupprops.groups);
if(Object.keys(this.parent.addressbooks).length > 1) {
this.$addressBookSelect = this.$fullelem.find('#contactaddressbooks');
buildAddressBookSelect(this.parent.addressbooks);
}
this.$addMenu = this.$fullelem.find('#addproperty');
this.$addMenu.on('change', function(event) {
//console.log('add', $(this).val());
@ -820,13 +1033,9 @@ OC.Contacts = OC.Contacts || {};
id: self.id
});
} else if($(this).is('.export')) {
$(document).trigger('request.contact.export', {
id: self.id
});
$(document).trigger('request.contact.export', self.metaData());
} else if($(this).is('.delete')) {
$(document).trigger('request.contact.delete', {
id: self.id
});
$(document).trigger('request.contact.delete', self.metaData());
}
return false;
});
@ -847,7 +1056,8 @@ OC.Contacts = OC.Contacts || {};
if($(this).hasClass('value') && this.value === this.defaultValue) {
return;
}
console.log('change', this.defaultValue, this.value);
//console.log('change', this.defaultValue, this.value);
this.defaultValue = this.value;
self.saveProperty({obj:event.target});
});
@ -971,9 +1181,9 @@ OC.Contacts = OC.Contacts || {};
if($meta.length) {
$meta.html(meta.join('/'));
}
if(self.access.owner === OC.currentUser
|| self.access.permissions & OC.PERMISSION_UPDATE
|| self.access.permissions & OC.PERMISSION_DELETE) {
if(self.metadata.owner === OC.currentUser
|| self.metadata.permissions & OC.PERMISSION_UPDATE
|| self.metadata.permissions & OC.PERMISSION_DELETE) {
$property.find('select.type[name="parameters[TYPE][]"]')
.combobox({
singleclick: true,
@ -985,9 +1195,9 @@ OC.Contacts = OC.Contacts || {};
}
}
});
if(this.access.owner !== OC.currentUser
&& !(this.access.permissions & OC.PERMISSION_UPDATE
|| this.access.permissions & OC.PERMISSION_DELETE)) {
if(this.metadata.owner !== OC.currentUser
&& !(this.metadata.permissions & OC.PERMISSION_UPDATE
|| this.metadata.permissions & OC.PERMISSION_DELETE)) {
this.setEnabled(false);
this.showActions(['close', 'export']);
} else {
@ -998,9 +1208,9 @@ OC.Contacts = OC.Contacts || {};
};
Contact.prototype.isEditable = function() {
return ((this.access.owner === OC.currentUser)
|| (this.access.permissions & OC.PERMISSION_UPDATE
|| this.access.permissions & OC.PERMISSION_DELETE));
return ((this.metadata.owner === OC.currentUser)
|| (this.metadata.permissions & OC.PERMISSION_UPDATE
|| this.metadata.permissions & OC.PERMISSION_DELETE));
};
/**
@ -1063,7 +1273,7 @@ OC.Contacts = OC.Contacts || {};
var val = self.valueFor(input);
var params = self.parametersFor(input, true);
$(this).find('.meta').html(params['TYPE'].join('/'));
$(this).find('.adr').html(escapeHTML(self.valueFor($editor.find('input').first()).clean('').join(', ')));
$(this).find('.adr').html(self.valueFor($editor.find('input').first()).clean('').join(', '));
$(this).next('.listactions').css('display', 'inline-block');
$('body').unbind('click', bodyListener);
});
@ -1151,27 +1361,68 @@ OC.Contacts = OC.Contacts || {};
return this.detailTemplates['impp'].octemplate(values);
};
/**
* Set a thumbnail for the contact if a PHOTO property exists
*/
Contact.prototype.setThumbnail = function($elem, refresh) {
if(!this.data.thumbnail && !refresh) {
return;
}
if(!$elem) {
$elem = this.getListItemElement().find('td.name');
}
if(!$elem.hasClass('thumbnail') && !refresh) {
return;
}
if(this.data.thumbnail) {
$elem.removeClass('thumbnail');
$elem.css('background-image', 'url(data:image/png;base64,' + this.data.thumbnail + ')');
} else {
$elem.addClass('thumbnail');
}
}
/**
* Render the PHOTO property.
*/
Contact.prototype.loadPhoto = function(dontloadhandlers) {
var self = this;
var id = this.id || 'new';
var refreshstr = '&refresh='+Math.random();
this.$photowrapper = this.$fullelem.find('#photowrapper');
this.$photowrapper.addClass('loading').addClass('wait');
var id = this.id || 'new',
backend = this.metadata.backend,
parent = this.metadata.parent,
src;
var $phototools = this.$fullelem.find('#phototools');
delete this.photo;
$('img.contactphoto').remove();
this.photo = new Image();
$(this.photo).load(function () {
$(this).addClass('contactphoto');
self.$photowrapper.css({width: $(this).get(0).width + 10, height: $(this).get(0).height + 10});
if(!this.$photowrapper) {
this.$photowrapper = this.$fullelem.find('#photowrapper');
}
var finishLoad = function(image) {
console.log('finishLoad', self.getDisplayName(), image.width, image.height);
$(image).addClass('contactphoto');
self.$photowrapper.css({width: image.width + 10, height: image.height + 10});
self.$photowrapper.removeClass('loading').removeClass('wait');
$(this).insertAfter($phototools).fadeIn();
}).error(function () {
OC.notify({message:t('contacts','Error loading profile picture.')});
}).attr('src', OC.linkTo('contacts', 'photo.php')+'?id='+id+refreshstr);
$(image).insertAfter($phototools).fadeIn();
};
this.$photowrapper.addClass('loading').addClass('wait');
if(this.getPreferredValue('PHOTO', null) === null) {
$.when(this.storage.getDefaultPhoto())
.then(function(image) {
$('img.contactphoto').detach();
finishLoad(image);
});
} else {
$.when(this.storage.getContactPhoto(backend, parent, id))
.then(function(image) {
$('img.contactphoto').remove();
finishLoad(image);
})
.fail(function(defaultImage) {
$('img.contactphoto').remove();
finishLoad(defaultImage);
});
}
if(!dontloadhandlers && this.isEditable()) {
this.$photowrapper.on('mouseenter', function(event) {
@ -1190,19 +1441,13 @@ OC.Contacts = OC.Contacts || {};
$phototools.find('li a').tipsy();
$phototools.find('.edit').on('click', function() {
$(document).trigger('request.edit.contactphoto', {
id: self.id
});
$(document).trigger('request.edit.contactphoto', self.metaData());
});
$phototools.find('.cloud').on('click', function() {
$(document).trigger('request.select.contactphoto.fromcloud', {
id: self.id
});
$(document).trigger('request.select.contactphoto.fromcloud', self.metaData());
});
$phototools.find('.upload').on('click', function() {
$(document).trigger('request.select.contactphoto.fromlocal', {
id: self.id
});
$(document).trigger('request.select.contactphoto.fromlocal', self.metaData());
});
if(this.data && this.data.PHOTO) {
$phototools.find('.delete').show();
@ -1211,11 +1456,11 @@ OC.Contacts = OC.Contacts || {};
$phototools.find('.delete').hide();
$phototools.find('.edit').hide();
}
$(document).bind('status.contact.photoupdated', function(e, result) {
$(document).bind('status.contact.photoupdated', function(e, data) {
console.log('status.contact.photoupdated', data);
self.loadPhoto(true);
var refreshstr = '&refresh='+Math.random();
self.getListItemElement().find('td.name')
.css('background', 'url(' + OC.filePath('', '', 'remote.php')+'/contactthumbnail?id='+self.id+refreshstr + ')');
self.data.thumbnail = data.thumbnail;
self.setThumbnail(null, true);
});
}
};
@ -1346,12 +1591,14 @@ OC.Contacts = OC.Contacts || {};
};
Contact.prototype.next = function() {
var $next = this.$listelem.next('tr:visible');
// This used to work..?
//var $next = this.$listelem.next('tr:visible');
var $next = this.$listelem.nextAll('tr').filter(':visible').first();
if($next.length > 0) {
this.$listelem.removeClass('active');
$next.addClass('active');
$(document).trigger('status.contact.currentlistitem', {
id: parseInt($next.data('id')),
id: String($next.data('id')),
pos: Math.round($next.position().top),
height: Math.round($next.height())
});
@ -1359,12 +1606,13 @@ OC.Contacts = OC.Contacts || {};
};
Contact.prototype.prev = function() {
var $prev = this.$listelem.prev('tr:visible');
//var $prev = this.$listelem.prev('tr:visible');
var $prev = this.$listelem.prevAll('tr').filter(':visible').first();
if($prev.length > 0) {
this.$listelem.removeClass('active');
$prev.addClass('active');
$(document).trigger('status.contact.currentlistitem', {
id: parseInt($prev.data('id')),
id: String($prev.data('id')),
pos: Math.round($prev.position().top),
height: Math.round($prev.height())
});
@ -1372,6 +1620,7 @@ OC.Contacts = OC.Contacts || {};
};
var ContactList = function(
storage,
contactlist,
contactlistitemtemplate,
contactdragitemtemplate,
@ -1382,17 +1631,19 @@ OC.Contacts = OC.Contacts || {};
var self = this;
this.length = 0;
this.contacts = {};
this.addressbooks = {};
this.deletionQueue = [];
this.storage = storage;
this.$contactList = contactlist;
this.$contactDragItemTemplate = contactdragitemtemplate;
this.$contactListItemTemplate = contactlistitemtemplate;
this.$contactFullTemplate = contactfulltemplate;
this.contactDetailTemplates = contactdetailtemplates;
this.$contactList.scrollTop(0);
this.loadContacts(0);
this.getAddressBooks();
$(document).bind('status.contact.added', function(e, data) {
self.length += 1;
self.contacts[parseInt(data.id)] = data.contact;
self.contacts[String(data.id)] = data.contact;
self.insertContact(data.contact.renderListItem(true));
});
@ -1404,6 +1655,14 @@ OC.Contacts = OC.Contacts || {};
});
};
/**
* Get the number of contacts in the list
* @return integer
*/
ContactList.prototype.count = function() {
return Object.keys(this.contacts.contacts).length
}
/**
* Show/hide contacts belonging to an addressbook.
* @param int aid. Addressbook id.
@ -1412,9 +1671,9 @@ OC.Contacts = OC.Contacts || {};
*/
ContactList.prototype.showFromAddressbook = function(aid, show, hideothers) {
console.log('ContactList.showFromAddressbook', aid, show);
aid = parseInt(aid);
aid = String(aid);
for(var contact in this.contacts) {
if(this.contacts[contact].access.id === aid) {
if(this.contacts[contact].metadata.parent === aid) {
this.contacts[contact].getListItemElement().toggle(show);
} else if(hideothers) {
this.contacts[contact].getListItemElement().hide();
@ -1429,7 +1688,7 @@ OC.Contacts = OC.Contacts || {};
ContactList.prototype.showSharedAddressbooks = function(show) {
console.log('ContactList.showSharedAddressbooks', show);
for(var contact in this.contacts) {
if(this.contacts[contact].access.owner !== OC.currentUser) {
if(this.contacts[contact].metadata.owner !== OC.currentUser) {
if(show) {
this.contacts[contact].getListItemElement().show();
} else {
@ -1444,6 +1703,8 @@ OC.Contacts = OC.Contacts || {};
* @param Array contacts. A list of contact ids.
*/
ContactList.prototype.showContacts = function(contacts) {
console.log('showContacts', contacts);
var self = this;
if(contacts.length === 0) {
// ~5 times faster
$('tr:visible.contact').hide();
@ -1451,7 +1712,16 @@ OC.Contacts = OC.Contacts || {};
}
if(contacts === 'all') {
// ~2 times faster
$('tr.contact:not(:visible)').show();
var $elems = $('tr.contact:not(:visible)');
$elems.show();
$.each($elems, function(idx, elem) {
try {
var id = $(elem).data('id');
self.contacts[id].setThumbnail();
} catch(e) {
console.warn('Failed getting id from', $elem, e);
}
});
return;
}
for(var id in this.contacts) {
@ -1459,10 +1729,11 @@ OC.Contacts = OC.Contacts || {};
if(contact === null) {
continue;
}
if(contacts.indexOf(parseInt(id)) === -1) {
if(contacts.indexOf(String(id)) === -1) {
contact.getListItemElement().hide();
} else {
contact.getListItemElement().show();
contact.setThumbnail();
}
}
};
@ -1503,34 +1774,54 @@ OC.Contacts = OC.Contacts || {};
*/
ContactList.prototype.findById = function(id) {
if(!id) {
console.warn('id missing');
console.warn('ContactList.findById: id missing');
console.trace();
return false;
}
id = parseInt(id);
id = String(id);
if(typeof this.contacts[id] === 'undefined') {
console.warn('Could not find contact with id', id);
console.trace();
return null;
}
return this.contacts[parseInt(id)];
return this.contacts[String(id)];
};
ContactList.prototype.delayedDelete = function(id) {
/**
* @param object data An object or array of objects containing contact identification
* {
* contactid: '1234',
* addressbookid: '4321',
* backend: 'local'
* }
*/
ContactList.prototype.delayedDelete = function(data) {
console.log('delayedDelete, data:', typeof data, data);
var self = this;
if(utils.isUInt(id)) {
if(!utils.isArray(data)) {
this.currentContact = null;
self.$contactList.show();
this.deletionQueue.push(id);
} else if(utils.isArray(id)) {
$.extend(this.deletionQueue, id);
//self.$contactList.show();
if(data instanceof Contact) {
this.deletionQueue.push(data);
} else {
throw { name: 'WrongParameterType', message: 'ContactList.delayedDelete only accept integers or arrays.'};
var contact = this.findById(data.contactid);
this.deletionQueue.push(contact);
}
$.each(this.deletionQueue, function(idx, id) {
self.contacts[id].detach().setChecked(false);
} else if(utils.isArray(data)) {
$.each(data, function(idx, contact) {
//console.log('delayedDelete, meta:', contact);
self.deletionQueue.push(contact);
});
console.log('deletionQueue', this.deletionQueue);
//$.extend(this.deletionQueue, data);
} else {
throw { name: 'WrongParameterType', message: 'ContactList.delayedDelete only accept objects or arrays.'};
}
//console.log('delayedDelete, deletionQueue', this.deletionQueue);
$.each(this.deletionQueue, function(idx, contact) {
//console.log('delayedDelete', contact);
contact.detach().setChecked(false);
});
//console.log('deletionQueue', this.deletionQueue);
if(!window.onbeforeunload) {
window.onbeforeunload = function(e) {
e = e || window.event;
@ -1548,16 +1839,16 @@ OC.Contacts = OC.Contacts || {};
message:t('contacts','Click to undo deletion of {num} contacts', {num: self.deletionQueue.length}),
//timeout:5,
timeouthandler:function() {
console.log('timeout');
//console.log('timeout');
// Don't fire all deletes at once
self.deletionTimer = setInterval(function() {
self.deleteContacts();
}, 500);
},
clickhandler:function() {
console.log('clickhandler');
$.each(self.deletionQueue, function(idx, id) {
self.insertContact(self.contacts[id].getListItemElement());
//console.log('clickhandler');
$.each(self.deletionQueue, function(idx, contact) {
self.insertContact(contact.getListItemElement());
});
OC.notify({cancel:true});
OC.notify({message:t('contacts', 'Cancelled deletion of {num}', {num: self.deletionQueue.length})});
@ -1568,20 +1859,19 @@ OC.Contacts = OC.Contacts || {};
};
/**
* Delete a contact with this id
* @param id the id of the contact
* Delete contacts in the queue
*/
ContactList.prototype.deleteContacts = function() {
var self = this;
console.log('ContactList.deleteContacts, deletionQueue', this.deletionQueue);
//console.log('ContactList.deleteContacts, deletionQueue', this.deletionQueue);
if(typeof this.deletionTimer === 'undefined') {
console.log('No deletion timer!');
window.onbeforeunload = null;
return;
}
var id = this.deletionQueue.shift();
if(typeof id === 'undefined') {
var contact = this.deletionQueue.shift();
if(typeof contact === 'undefined') {
clearInterval(this.deletionTimer);
delete this.deletionTimer;
window.onbeforeunload = null;
@ -1589,13 +1879,10 @@ OC.Contacts = OC.Contacts || {};
}
// Let contact remove itself.
var contact = this.findById(id);
if(contact === null) {
return false;
}
var id = contact.getId();
contact.destroy(function(response) {
console.log('deleteContact', response, self.length);
if(response.status === 'success') {
if(!response.error) {
delete self.contacts[id];
$(document).trigger('status.contact.deleted', {
id: id
@ -1661,10 +1948,17 @@ OC.Contacts = OC.Contacts || {};
* @param object props
*/
ContactList.prototype.addContact = function(props) {
var addressBook = this.addressbooks[Object.keys(this.addressbooks)[0]]
var metadata = {
parent: addressBook.id,
backend: addressBook.backend,
permissions: addressBook.permissions,
owner: addressBook.owner
};
var contact = new Contact(
this,
null,
{owner:OC.currentUser, permissions: 31},
metadata,
null,
this.$contactListItemTemplate,
this.$contactDragItemTemplate,
@ -1672,7 +1966,6 @@ OC.Contacts = OC.Contacts || {};
this.contactDetailTemplates
);
if(utils.isUInt(this.currentContact)) {
console.assert(typeof this.currentContact == 'number', 'this.currentContact is not a number');
this.contacts[this.currentContact].close();
}
return contact.renderContact(props);
@ -1681,13 +1974,15 @@ OC.Contacts = OC.Contacts || {};
/**
* Get contacts selected in list
*
* @returns array of integer contact ids.
* @returns array of contact ids.
*/
ContactList.prototype.getSelectedContacts = function() {
var contacts = [];
var self = this;
$.each(this.$contactList.find('tr > td > input:checkbox:visible:checked'), function(a, b) {
contacts.push(parseInt($(b).parents('tr').first().data('id')));
var id = String($(b).parents('tr').first().data('id'));
contacts.push(self.contacts[id]);
});
return contacts;
};
@ -1703,7 +1998,7 @@ OC.Contacts = OC.Contacts || {};
self.contacts[contact].setCurrent(false);
});
}
this.contacts[parseInt(id)].setCurrent(true);
this.contacts[String(id)].setCurrent(true);
};
// Should only be neccesary with progressive loading, but it's damn fast, so... ;)
@ -1715,6 +2010,7 @@ OC.Contacts = OC.Contacts || {};
return $(a).find('td.name').text().toUpperCase().localeCompare($(b).find('td.name').text().toUpperCase());
});
// TODO: Test if I couldn't just append rows.
var items = [];
$.each(rows, function(index, row) {
items.push(row);
@ -1741,40 +2037,88 @@ OC.Contacts = OC.Contacts || {};
* @param object book
*/
ContactList.prototype.setAddressbook = function(book) {
this.addressbooks[parseInt(book.id)] = {
owner: book.userid,
uri: book.uri,
permissions: parseInt(book.permissions),
id: parseInt(book.id),
displayname: book.displayname,
description: book.description,
active: Boolean(parseInt(book.active))
};
console.log('setAddressbook', book.id, this.addressbooks);
var id = String(book.id);
this.addressbooks[id] = book;
};
/**
* Load contacts
* @param int offset
*/
ContactList.prototype.loadContacts = function(offset, cb) {
ContactList.prototype.getAddressBooks = function() {
var self = this;
// Should the actual ajax call be in the controller?
$.getJSON(OC.filePath('contacts', 'ajax', 'contact/list.php'), {offset: offset}, function(jsondata) {
if (jsondata && jsondata.status == 'success') {
//console.log('ContactList.loadContacts', jsondata.data);
self.addressbooks = {};
$.each(jsondata.data.addressbooks, function(i, book) {
self.setAddressbook(book);
$.when(this.storage.getAddressBooksForUser()).then(function(response) {
console.log('ContactList.getAddressBooks', response);
if(!response.error) {
var num = response.data.addressbooks.length;
$.each(response.data.addressbooks, function(idx, addressBook) {
self.setAddressbook(addressBook);
self.loadContacts(
addressBook['backend'],
addressBook['id'],
function(cbresponse) {
//console.log('loaded', idx, cbresponse);
num -= 1;
if(num === 0) {
if(self.length > 0) {
setTimeout(function() {
self.doSort(); // TODO: Test this
self.setCurrent(self.$contactList.find('tr:visible').first().data('id'), false);
}
, 2000);
}
$(document).trigger('status.contacts.loaded', {
status: true,
numcontacts: self.length
});
var items = [];
if(jsondata.data.contacts.length === 0) {
if(self.length === 0) {
$(document).trigger('status.nomorecontacts');
}
$.each(jsondata.data.contacts, function(c, contact) {
self.contacts[parseInt(contact.id)]
}
if(cbresponse.error) {
$(document).trigger('status.contact.error', {
message:
t('contacts', 'Failed loading contacts from {addressbook}: {error}',
{addressbook:addressBook['displayname'], error:err})
});
}
});
});
} else {
$(document).trigger('status.contact.error', {
message: response.message
});
return false;
}
})
.fail(function(jqxhr, textStatus, error) {
var err = textStatus + ', ' + error;
console.warn( "Request Failed: " + err);
$(document).trigger('status.contact.error', {
message: t('contacts', 'Failed loading address books: {error}', {error:err})
});
});
};
/**
* Load contacts
* @param int offset
*/
ContactList.prototype.loadContacts = function(backend, addressBookId, cb) {
var self = this;
$.when(this.storage.getContacts(backend, addressBookId)).then(function(response) {
//console.log('ContactList.loadContacts', response);
if(!response.error) {
var items = [];
$.each(response.data.contacts, function(c, contact) {
var id = String(contact.metadata.id);
contact.metadata.backend = backend;
self.contacts[id]
= new Contact(
self,
parseInt(contact.id),
self.addressbooks[parseInt(contact.aid)],
id,
contact.metadata,
contact.data,
self.$contactListItemTemplate,
self.$contactDragItemTemplate,
@ -1782,14 +2126,14 @@ OC.Contacts = OC.Contacts || {};
self.contactDetailTemplates
);
self.length +=1;
var $item = self.contacts[parseInt(contact.id)].renderListItem();
var $item = self.contacts[id].renderListItem();
items.push($item.get(0));
$item.find('td.name').draggable({
cursor: 'move',
distance: 10,
revert: 'invalid',
helper: function (e,ui) {
return self.contacts[parseInt(contact.id)].renderDragItem().appendTo('body');
return self.contacts[id].renderDragItem().appendTo('body');
},
opacity: 1,
scope: 'contacts'
@ -1802,22 +2146,24 @@ OC.Contacts = OC.Contacts || {};
if(items.length > 0) {
self.$contactList.append(items);
}
setTimeout(function() {
self.doSort();
self.setCurrent(self.$contactList.find('tr:visible:first-child').data('id'), false);
}
, 2000);
$(document).trigger('status.contacts.loaded', {
status: true,
numcontacts: jsondata.data.contacts.length,
is_indexed: jsondata.data.is_indexed
cb({error:false});
} else {
$(document).trigger('status.contact.error', {
message: response.message
});
cb({
error:true,
message: response.message
});
}
if(typeof cb === 'function') {
cb();
}
})
.fail(function(jqxhr, textStatus, error) {
var err = textStatus + ', ' + error;
console.warn( "Request Failed: " + err);
cb({error: true, message: err});
});
}
};
OC.Contacts.ContactList = ContactList;
})(window, jQuery, OC);

View File

@ -16,7 +16,8 @@ OC.Contacts = OC.Contacts || {};
* 'contacts': An array of contact ids belonging to that group
* 'obj': A reference to the groupList object.
*/
var GroupList = function(groupList, listItemTmpl) {
var GroupList = function(storage, groupList, listItemTmpl) {
this.storage = storage;
this.$groupList = groupList;
var self = this;
var numtypes = ['category', 'fav', 'all'];
@ -29,7 +30,7 @@ OC.Contacts = OC.Contacts || {};
if($(event.target).is('.action.delete')) {
var id = $(event.target).parents('h3').first().data('id');
self.deleteGroup(id, function(response) {
if(response.status !== 'success') {
if(response.error) {
OC.notify({message:response.data.message});
}
});
@ -129,7 +130,6 @@ OC.Contacts = OC.Contacts || {};
* @param function cb. Optional callback function.
*/
GroupList.prototype.setAsFavorite = function(contactid, state, cb) {
contactid = parseInt(contactid);
var $groupelem = this.findById('fav');
var contacts = $groupelem.data('contacts');
if(state) {
@ -185,7 +185,7 @@ OC.Contacts = OC.Contacts || {};
}
var self = this;
var doPost = false;
if(typeof contactid === 'number') {
if(typeof contactid === 'string') {
if(contacts.indexOf(contactid) === -1) {
ids.push(contactid);
doPost = true;
@ -211,14 +211,8 @@ OC.Contacts = OC.Contacts || {};
console.warn('Invalid data type: ' + typeof contactid);
}
if(doPost) {
$.post(OC.filePath('contacts', 'ajax', 'categories/addto.php'), {contactids: ids, categoryid: groupid},function(jsondata) {
if(!jsondata) {
if(typeof cb === 'function') {
cb({status:'error', message:'Network or server error. Please inform administrator.'});
}
return;
}
if(jsondata.status === 'success') {
$.when(this.storage.addToGroup(ids, groupid)).then(function(response) {
if(!response.error) {
contacts = contacts.concat(ids).sort();
$groupelem.data('contacts', contacts);
var $numelem = $groupelem.find('.numcontacts');
@ -237,7 +231,7 @@ OC.Contacts = OC.Contacts || {};
}
} else {
if(typeof cb == 'function') {
cb({status:'error', message:jsondata.data.message});
cb({status:'error', message:response.message});
}
}
});
@ -300,14 +294,8 @@ OC.Contacts = OC.Contacts || {};
}
}
if(doPost) {
$.post(OC.filePath('contacts', 'ajax', 'categories/removefrom.php'), {contactids: ids, categoryid: groupid},function(jsondata) {
if(!jsondata) {
if(typeof cb === 'function') {
cb({status:'error', message:'Network or server error. Please inform administrator.'});
}
return;
}
if(jsondata.status === 'success') {
$.when(this.storage.removeFromGroup(ids, groupid)).then(function(response) {
if(!response.error) {
$.each(ids, function(idx, id) {
contacts.splice(contacts.indexOf(id), 1);
});
@ -323,7 +311,7 @@ OC.Contacts = OC.Contacts || {};
}
} else {
if(typeof cb == 'function') {
cb({status:'error', message:jsondata.data.message});
cb({status:'error', message:response.message});
}
}
});
@ -363,7 +351,7 @@ OC.Contacts = OC.Contacts || {};
var dragitem = ui.draggable, droptarget = $(this);
console.log('dropped', dragitem);
if(dragitem.is('.name')) {
var id = dragitem.parent().data('id');
var id = String(dragitem.parent().data('id'));
console.log('contact dropped', id, 'on', $(this).data('id'));
if($(this).data('type') === 'fav') {
$(this).data('obj').setAsFavorite(id, true);
@ -397,22 +385,35 @@ OC.Contacts = OC.Contacts || {};
var contacts = $elem.data('contacts');
var self = this;
console.log('delete group', groupid, contacts);
$.post(OC.filePath('contacts', 'ajax', 'categories/delete.php'), {categories: name}, function(jsondata) {
if (jsondata && jsondata.status == 'success') {
$.when(this.storage.deleteGroup(name)).then(function(response) {
if (!response.error) {
$.each(self.categories, function(idx, category) {
if(category.id === groupid) {
self.categories.splice(self.categories.indexOf(category), 1);
return false; // Break loop
}
});
$(document).trigger('status.group.groupremoved', {
groupid: groupid,
newgroupid: parseInt($newelem.data('id')),
groupname: self.nameById(groupid),
groupname: name,
contacts: contacts
});
$elem.remove();
self.selectGroup({element:$newelem});
} else {
//
console.log('Error', response);
}
if(typeof cb === 'function') {
cb(jsondata);
cb(response);
}
})
.fail(function(jqxhr, textStatus, error) {
var err = textStatus + ', ' + error;
console.log( "Request Failed: " + err);
$(document).trigger('status.contact.error', {
message: t('contacts', 'Failed deleting group: {error}', {error:err})
});
});
};
@ -441,7 +442,7 @@ OC.Contacts = OC.Contacts || {};
$input.prop('disabled', true);
$elem.data('rawname', '');
self.addGroup({name:name, element: $elem}, function(response) {
if(response.status === 'success') {
if(!response.error) {
$elem.prepend(escapeHTML(response.name)).removeClass('editing').attr('data-id', response.id);
$input.next('.checked').remove();
$input.remove();
@ -545,10 +546,10 @@ OC.Contacts = OC.Contacts || {};
}
return;
}
$.post(OC.filePath('contacts', 'ajax', 'categories/add.php'), {category: name}, function(jsondata) {
if (jsondata && jsondata.status == 'success') {
name = jsondata.data.name;
var id = jsondata.data.id;
$.when(this.storage.addGroup(name)).then(function(response) {
if (!response.error) {
name = response.data.name;
var id = response.data.id;
var tmpl = self.$groupListItemTemplate;
var $elem = params.element
? params.element
@ -578,13 +579,20 @@ OC.Contacts = OC.Contacts || {};
$elem.tipsy({trigger:'manual', gravity:'w', fallback: t('contacts', 'You can drag groups to\narrange them as you like.')});
$elem.tipsy('show');
if(typeof cb === 'function') {
cb({status:'success', id:parseInt(id), name:name});
cb({id:parseInt(id), name:name});
}
} else {
if(typeof cb === 'function') {
cb({status:'error', message:jsondata.data.message});
cb({error:true, message:response.data.message});
}
}
})
.fail(function(jqxhr, textStatus, error) {
var err = textStatus + ', ' + error;
console.log( "Request Failed: " + err);
$(document).trigger('status.contact.error', {
message: t('contacts', 'Failed adding group: {error}', {error:err})
});
});
};
@ -595,15 +603,14 @@ OC.Contacts = OC.Contacts || {};
var tmpl = this.$groupListItemTemplate;
tmpl.octemplate({id: 'all', type: 'all', num: numcontacts, name: t('contacts', 'All')}).appendTo($groupList);
$.getJSON(OC.filePath('contacts', 'ajax', 'categories/list.php'), {}, function(jsondata) {
if (jsondata && jsondata.status == 'success') {
self.lastgroup = jsondata.data.lastgroup;
self.sortorder = jsondata.data.sortorder.length > 0
? $.map(jsondata.data.sortorder.split(','), function(c) {return parseInt(c);})
: [];
$.when(this.storage.getGroupsForUser()).then(function(response) {
if (response && !response.error) {
self.lastgroup = response.data.lastgroup;
self.sortorder = contacts_groups_sortorder;
console.log('sortorder', self.sortorder);
// Favorites
var contacts = $.map(jsondata.data.favorites, function(c) {return parseInt(c);});
// Map to strings easier lookup an contacts list.
var contacts = $.map(response.data.favorites, function(c) {return String(c);});
var $elem = tmpl.octemplate({
id: 'fav',
type: 'fav',
@ -627,8 +634,8 @@ OC.Contacts = OC.Contacts || {};
}
console.log('favorites', $elem.data('contacts'));
// Normal groups
$.each(jsondata.data.categories, function(c, category) {
var contacts = $.map(category.contacts, function(c) {return parseInt(c);});
$.each(response.data.categories, function(c, category) {
var contacts = $.map(category.contacts, function(c) {return String(c);});
var $elem = (tmpl).octemplate({
id: category.id,
type: 'category',
@ -663,13 +670,13 @@ OC.Contacts = OC.Contacts || {};
});
// Shared addressbook
$.each(jsondata.data.shared, function(c, shared) {
$.each(response.data.shared, function(c, shared) {
var sharedindicator = '<img class="shared svg" src="' + OC.imagePath('core', 'actions/shared') + '"'
+ 'title="' + t('contacts', 'Shared by {owner}', {owner:shared.userid}) + '" />';
var $elem = (tmpl).octemplate({
id: shared.id,
type: 'shared',
num: '', //jsondata.data.shared.length,
num: response.data.shared.length,
name: shared.displayname
});
$elem.find('.numcontacts').after(sharedindicator);
@ -703,6 +710,10 @@ OC.Contacts = OC.Contacts || {};
if(typeof cb === 'function') {
cb();
}
})
.fail(function(jqxhr, textStatus, error) {
var err = textStatus + ', ' + error;
console.log( "Request Failed: " + err);
});
};

194
js/jquery.ocdialog.js Normal file
View File

@ -0,0 +1,194 @@
(function($) {
$.widget('oc.ocdialog', {
options: {
width: 'auto',
height: 'auto',
closeButton: true,
closeOnEscape: true
},
_create: function() {
console.log('ocdialog._create');
var self = this;
this.originalCss = {
display: this.element[0].style.display,
width: this.element[0].style.width,
height: this.element[0].style.height,
};
this.originalTitle = this.element.attr('title');
this.options.title = this.options.title || this.originalTitle;
this.$dialog = $('<div class="oc-dialog" />')
.attr({
// Setting tabIndex makes the div focusable
tabIndex: -1,
role: 'dialog'
})
.insertBefore(this.element);
this.$dialog.append(this.element.detach());
this.element.removeAttr('title').addClass('oc-dialog-content').appendTo(this.$dialog);
this.$dialog.css({
display: 'inline-block',
position: 'fixed'
});
$(document).on('keydown keyup', function(event) {
if(event.target !== self.$dialog.get(0) && self.$dialog.find($(event.target)).length === 0) {
return;
}
// Escape
if(event.keyCode === 27 && self.options.closeOnEscape) {
self.close();
return false;
}
// Enter
if(event.keyCode === 13) {
event.stopImmediatePropagation();
if(event.type === 'keyup') {
event.preventDefault();
return false;
}
// If no button is selected we trigger the primary
if(self.$buttonrow && self.$buttonrow.find($(event.target)).length === 0) {
var $button = self.$buttonrow.find('button.primary');
if($button) {
$button.trigger('click');
}
} else if(self.$buttonrow) {
$(event.target).trigger('click');
}
return false;
}
});
$(window).resize(function() {
self.parent = self.$dialog.parent().length > 0 ? self.$dialog.parent() : $('body');
var pos = self.parent.position();
self.$dialog.css({
left: pos.left + (self.parent.width() - self.$dialog.outerWidth())/2,
top: pos.top + (self.parent.height() - self.$dialog.outerHeight())/2
});
});
this._setOptions(this.options);
$(window).trigger('resize');
},
_init: function() {
console.log('ocdialog._init');
this.$dialog.focus();
this._trigger('open');
},
_setOption: function(key, value) {
console.log('_setOption', key, value);
var self = this;
switch(key) {
case 'title':
var $title = $('<h3 class="oc-dialog-title">' + this.options.title
+ '</h3>'); //<hr class="oc-dialog-separator" />');
if(this.$title) {
this.$title.replaceWith($title);
} else {
this.$title = $title.prependTo(this.$dialog);
}
this._setSizes();
break;
case 'buttons':
var $buttonrow = $('<div class="oc-dialog-buttonrow" />');
if(this.$buttonrow) {
this.$buttonrow.replaceWith($buttonrow);
} else {
this.$buttonrow = $buttonrow.appendTo(this.$dialog);
}
$.each(value, function(idx, val) {
var $button = $('<button>' + val.text + '</button>');
if(val.defaultButton) {
$button.addClass('primary');
self.$defaultButton = $button;
}
self.$buttonrow.append($button);
$button.click(function() {
val.click.apply(self.element[0], arguments);
});
});
this.$buttonrow.find('button')
.on('focus', function(event) {
self.$buttonrow.find('button').removeClass('primary');
$(this).addClass('primary');
});
this._setSizes();
break;
case 'closeButton':
console.log('closeButton', value);
if(value) {
var $closeButton = $('<a class="oc-dialog-close svg"></a>');
console.log('closeButton', $closeButton);
this.$dialog.prepend($closeButton);
$closeButton.on('click', function() {
self.close();
});
}
break;
case 'width':
this.$dialog.css('width', value);
break;
case 'height':
this.$dialog.css('height', value);
break;
case 'close':
this.closeCB = value;
break;
}
//this._super(key, value);
$.Widget.prototype._setOption.apply(this, arguments );
},
_setOptions: function(options) {
console.log('_setOptions', options);
//this._super(options);
$.Widget.prototype._setOptions.apply(this, arguments);
},
_setSizes: function() {
var content_height = this.$dialog.height();
if(this.$title) {
content_height -= this.$title.outerHeight(true);
}
if(this.$buttonrow) {
content_height -= this.$buttonrow.outerHeight(true);
}
this.parent = this.$dialog.parent().length > 0 ? this.$dialog.parent() : $('body');
content_height = Math.min(content_height, this.parent.height()-20)
this.element.css({
height: content_height + 'px',
width: this.$dialog.innerWidth() + 'px'
});
},
widget: function() {
return this.$dialog
},
close: function() {
console.log('close');
var self = this;
// Ugly hack to catch remaining keyup events.
setTimeout(function() {
self._trigger('close', self);
self.$dialog.hide();
}, 200);
},
destroy: function() {
console.log('destroy');
if(this.$title) {
this.$title.remove()
}
if(this.$buttonrow) {
this.$buttonrow.remove()
}
if(this.originalTitle) {
this.element.attr('title', this.originalTitle);
}
this.element.removeClass('oc-dialog-content')
.css(this.originalCss).detach().insertBefore(this.$dialog);
this.$dialog.remove();
}
});
}(jQuery));

444
js/storage.js Normal file
View File

@ -0,0 +1,444 @@
OC.Contacts = OC.Contacts || {};
/**
* TODO: Use $.Deferred.
*/
(function(window, $, OC) {
'use strict';
var JSONResponse = function(response) {
if(!response || !response.status || response.status === 'error') {
this.error = true;
this.message = response.data.message || 'Unknown error.';
} else {
this.error = false;
if(response.data) {
this.data = response.data;
} else {
this.data = response;
}
}
}
/**
* An object for saving contact data to backends
*
* All methods returns a jQuery.Deferred object which resolves
* to either the requested response or an error object:
* {
* status: 'error',
* message: The error message
* }
*
* @param string user The user to query for. Defaults to current user
*/
var Storage = function(user) {
this.user = user ? user : OC.currentUser;
}
/**
* Get all address books registered for this user.
*
* @return An array containing object of address book metadata e.g.:
* {
* backend:'local',
* id:'1234'
* permissions:31,
* displayname:'Contacts'
* }
*/
Storage.prototype.getAddressBooksForUser = function() {
return this.requestRoute(
'contacts_address_books_for_user',
'GET',
{user: this.user}
);
}
/**
* Add an address book to a specific backend
*
* @param string backend - currently defaults to 'local'
* @param object params An object {displayname:"My contacts", description:""}
* @return An array containing contact data e.g.:
* {
* metadata:
* {
* id:'1234'
* permissions:31,
* displayname:'My contacts',
* lastmodified: (unix timestamp),
* owner: 'joye',
* }
*/
Storage.prototype.addAddressBook = function(backend, parameters) {
console.log('Storage.addAddressBook', backend);
return this.requestRoute(
'contacts_address_book_add',
'POST',
{user: this.user, backend: 'local'},
parameters
);
}
/**
* Delete an address book from a specific backend
*
* @param string backend
* @param string addressbookid Address book ID
*/
Storage.prototype.deleteAddressBook = function(backend, addressbookid) {
console.log('Storage.deleteAddressBook', backend, addressbookid);
return this.requestRoute(
'contacts_address_book_delete',
'POST',
{user: this.user, backend: 'local', addressbookid: addressbookid}
);
}
/**
* Get contacts from an address book from a specific backend
*
* @param string backend
* @param string addressbookid Address book ID
* @return An array containing contact data e.g.:
* {
* metadata:
* {
* id:'1234'
* permissions:31,
* displayname:'John Q. Public',
* lastmodified: (unix timestamp),
* owner: 'joye',
* parent: (id of the parent address book)
* data: //array of VCard data
* }
*/
Storage.prototype.getContacts = function(backend, addressbookid) {
return this.requestRoute(
'contacts_address_book_collection',
'GET',
{user: this.user, backend: backend, addressbookid: addressbookid}
);
}
/**
* Add a contact to an address book from a specific backend
*
* @param string backend
* @param string addressbookid Address book ID
* @return An array containing contact data e.g.:
* {
* metadata:
* {
* id:'1234'
* permissions:31,
* displayname:'John Q. Public',
* lastmodified: (unix timestamp),
* owner: 'joye',
* parent: (id of the parent address book)
* data: //array of VCard data
* }
*/
Storage.prototype.addContact = function(backend, addressbookid) {
console.log('Storage.addContact', backend, addressbookid);
return this.requestRoute(
'contacts_address_book_add_contact',
'POST',
{user: this.user, backend: backend, addressbookid: addressbookid}
);
}
/**
* Delete a contact from an address book from a specific backend
*
* @param string backend
* @param string addressbookid Address book ID
* @param string contactid Address book ID
*/
Storage.prototype.deleteContact = function(backend, addressbookid, contactid) {
console.log('Storage.deleteContact', backend, addressbookid, contactid);
return this.requestRoute(
'contacts_address_book_delete_contact',
'POST',
{user: this.user, backend: backend, addressbookid: addressbookid, contactid: contactid}
);
}
/**
* Get Image instance for a contacts profile picture
*
* @param string backend
* @param string addressbookid Address book ID
* @param string contactid Address book ID
* @return Image
*/
Storage.prototype.getContactPhoto = function(backend, addressbookid, contactid) {
var photo = new Image();
var url = OC.Router.generate(
'contacts_contact_photo',
{user: this.user, backend: backend, addressbookid: addressbookid, contactid: contactid}
);
var defer = $.Deferred();
$.when(
$(photo).load(function() {
defer.resolve(photo);
})
.error(function() {
console.log('Error loading default photo', arguments)
})
.attr('src', url + '?refresh=' + Math.random())
)
.fail(function(jqxhr, textStatus, error) {
defer.reject();
var err = textStatus + ', ' + error;
console.log( "Request Failed: " + err);
$(document).trigger('status.contact.error', {
message: t('contacts', 'Failed loading photo: {error}', {error:err})
});
});
return defer.promise();
}
/**
* Get Image instance for default profile picture
*
* This method loads the default picture only once and caches it.
*
* @return Image
*/
Storage.prototype.getDefaultPhoto = function() {
console.log('Storage.getDefaultPhoto');
if(!this._defaultPhoto) {
var url = OC.imagePath('contacts', 'person_large.png');
this._defaultPhoto = new Image();
$.when(
$(this._defaultPhoto)
.load(function() {
console.log('Default photo loaded', arguments);
}).error(function() {
console.log('Error loading default photo', arguments)
}).attr('src', url)
)
.then(function(response) {
console.log('Storage.defaultPhoto', response);
})
.fail(function(jqxhr, textStatus, error) {
var err = textStatus + ', ' + error;
console.log( "Request Failed: " + err);
$(document).trigger('status.contact.error', {
message: t('contacts', 'Failed loading default photo: {error}', {error:err})
});
});
};
return this._defaultPhoto;
}
/**
* Delete a single property.
*
* @param string backend
* @param string addressbookid Address book ID
* @param string contactid Contact ID
* @param object params An object with the following properties:
* @param string name The name of the property e.g. EMAIL.
* @param string checksum For non-singular properties such as email this must contain
* an 8 character md5 checksum of the serialized \Sabre\Property
*/
Storage.prototype.deleteProperty = function(backend, addressbookid, contactid, params) {
return this.requestRoute(
'contacts_contact_delete_property',
'POST',
{user: this.user, backend: backend, addressbookid: addressbookid, contactid: contactid},
params
);
}
/**
* Save a property.
*
* @param string backend
* @param string addressbookid Address book ID
* @param string contactid Contact ID
* @param object params An object with the following properties:
* @param string name The name of the property e.g. EMAIL.
* @param string|array value The of the property
* @param array parameters Optional parameters for the property
* @param string checksum For non-singular properties such as email this must contain
* an 8 character md5 checksum of the serialized \Sabre\Property
*/
Storage.prototype.saveProperty = function(backend, addressbookid, contactid, params) {
return this.requestRoute(
'contacts_contact_save_property',
'POST',
{user: this.user, backend: backend, addressbookid: addressbookid, contactid: contactid},
params
);
}
/**
* Save all properties. Used when merging contacts.
*
* @param string backend
* @param string addressbookid Address book ID
* @param string contactid Contact ID
* @param object params An object with the all properties:
*/
Storage.prototype.saveAllProperties = function(backend, addressbookid, contactid, params) {
console.log('Storage.saveAllProperties', params);
return this.requestRoute(
'contacts_contact_save_all',
'POST',
{user: this.user, backend: backend, addressbookid: addressbookid, contactid: contactid},
params
);
}
/**
* Get all groups for this user.
*
* @return An array containing the groups, the favorites, any shared
* address books, the last selected group and the sort order of the groups.
* {
* 'categories': [{'id':1',Family'}, {...}],
* 'favorites': [123,456],
* 'shared': [],
* 'lastgroup':'1',
* 'sortorder':'3,2,4'
* }
*/
Storage.prototype.getGroupsForUser = function() {
console.log('getGroupsForUser');
return this.requestRoute(
'contacts_categories_list',
'GET',
{user: this.user}
);
}
/**
* Add a group
*
* @param string name
* @return A JSON object containing the (maybe sanitized) group name and its ID:
* {
* 'id':1234,
* 'name':'My group'
* }
*/
Storage.prototype.addGroup = function(name) {
console.log('Storage.addGroup', name);
return this.requestRoute(
'contacts_categories_add',
'POST',
{user: this.user},
{name: name}
);
}
/**
* Delete a group
*
* @param string name
*/
Storage.prototype.deleteGroup = function(name) {
return this.requestRoute(
'contacts_categories_delete',
'POST',
{user: this.user},
{name: name}
);
}
/**
* Add contacts to a group
*
* @param array contactids
*/
Storage.prototype.addToGroup = function(contactids, categoryid) {
console.log('Storage.addToGroup', contactids, categoryid);
return this.requestRoute(
'contacts_categories_addto',
'POST',
{user: this.user, categoryid: categoryid},
{contactids: contactids}
);
}
/**
* Remove contacts from a group
*
* @param array contactids
*/
Storage.prototype.removeFromGroup = function(contactids, categoryid) {
console.log('Storage.addToGroup', contactids, categoryid);
return this.requestRoute(
'contacts_categories_removefrom',
'POST',
{user: this.user, categoryid: categoryid},
{contactids: contactids}
);
}
/**
* Set a user preference
*
* @param string key
* @param string value
*/
Storage.prototype.setPreference = function(key, value) {
return this.requestRoute(
'contacts_setpreference',
'POST',
{user: this.user},
{key: key, value:value}
);
}
Storage.prototype.requestRoute = function(route, type, routeParams, params) {
var isJSON = (typeof params === 'string');
var contentType = isJSON ? 'application/json' : 'application/x-www-form-urlencoded';
var processData = !isJSON;
contentType += '; charset=UTF-8';
var self = this;
var url = OC.Router.generate(route, routeParams);
var ajaxParams = {
type: type,
url: url,
dataType: 'json',
contentType: contentType,
processData: processData,
data: params
};
var defer = $.Deferred();
/*$.when($.ajax(ajaxParams)).then(function(response) {
defer.resolve(new JSONResponse(response));
}).fail(function(jqxhr, textStatus, error) {
defer.reject(
new JSONResponse({
status:'error',
data:{message:t('contacts', 'Request failed: {error}', {error:textStatus + ', ' + error})}
})
);
});*/
var jqxhr = $.ajax(ajaxParams)
.done(function(response) {
defer.resolve(new JSONResponse(response));
})
.fail(function(jqxhr, textStatus, error) {
defer.reject(
new JSONResponse({
status:'error',
data:{message:t('contacts', 'Request failed: {error}', {error:textStatus + ', ' + error})}
})
);
});
return defer.promise();
}
OC.Contacts.Storage = Storage;
})(window, jQuery, OC);

View File

@ -0,0 +1,168 @@
<?php
/**
* ownCloud - Collection class for PIM object
*
* @author Thomas Tanghus
* @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
* 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/>.
*
*/
namespace OCA\Contacts;
/**
* Subclass this for PIM collections
*/
abstract class PIMCollectionAbstract extends PIMObjectAbstract implements \Iterator, \Countable, \ArrayAccess {
// Iterator properties
protected $objects = array();
protected $counter = 0;
/**
* This is a collection so return null.
* @return null
*/
function getParent() {
null;
}
/**
* Returns a specific child node, referenced by its id
* TODO: Maybe implement it here?
*
* @param string $id
* @return IPIMObject
*/
abstract function getChild($id);
/**
* Returns an array with all the child nodes
*
* @return IPIMObject[]
*/
abstract function getChildren($limit = null, $offset = null);
/**
* Checks if a child-node with the specified id exists
*
* @param string $id
* @return bool
*/
abstract function childExists($id);
/**
* Add a child to the collection
*
* It's up to the implementations to "keep track" of the children.
*
* @param mixed $data
* @return string ID of the newly added child
*/
abstract public function addChild($data);
/**
* Delete a child from the collection
*
* @param string $id
* @return bool
*/
abstract public function deleteChild($id);
// Iterator methods
public function rewind() {
$this->counter = 0;
}
public function next() {
$this->counter++;
}
public function valid() {
return array_key_exists($this->counter, $this->objects);
}
public function current() {
return $this->objects[$this->counter];
}
/** Implementations can choose to return the current objects ID/UUID
* to be able to iterate over the collection with ID => Object pairs:
* foreach($collection as $id => $object) {}
*/
public function key() {
return $this->counter;
}
// Countable method.
/**
* For implementations using a backend where fetching all object at once
* would give too much overhead, they can maintain an internal count value
* and fetch objects progressively. Simply watch the diffence between
* $this->counter, the value of count($this->objects) and the internal
* value, and fetch more objects when needed.
*/
public function count() {
return count($this->objects);
}
// ArrayAccess methods
public function offsetSet($offset, $value) {
if (is_null($offset)) {
$this->objects[] = $value;
} else {
$this->objects[$offset] = $value;
}
}
public function offsetExists($offset) {
return isset($this->objects[$offset]);
}
public function offsetUnset($offset) {
unset($this->objects[$offset]);
}
public function offsetGet($offset) {
return isset($this->objects[$offset]) ? $this->objects[$offset] : null;
}
// Magic property accessors
// NOTE: They should go in the implementations(?)
/*
public function __set($id, $value) {
$this->objects[$id] = $value;
}
public function __get($id) {
return $this->objects[$id];
}
public function __isset($id) {
return isset($this->objects[$id]);
}
public function __unset($id) {
unset($this->objects[$id]);
}
*/
}

137
lib/abstractpimobject.php Normal file
View File

@ -0,0 +1,137 @@
<?php
/**
* ownCloud - Interface for PIM object
*
* @author Thomas Tanghus
* @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
* 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/>.
*
*/
namespace OCA\Contacts;
/**
* Subclass this class or implement IPIMObject interface for PIM objects
*/
abstract class PIMObjectAbstract implements IPIMObject {
/**
* This variable holds the ID of this object.
* Depending on the backend, this can be either a string
* or an integer, so we treat them all as strings.
*
* @var string
*/
protected $id;
/**
* This variable holds the owner of this object.
*
* @var string
*/
protected $owner;
/**
* This variable holds the parent of this object if any.
*
* @var string|null
*/
protected $parent;
/**
* This variable holds the permissions of this object.
*
* @var integer
*/
protected $permissions;
/**
* @return string
*/
public function getId() {
return $this->id;
}
/**
* @return string|null
*/
function getDisplayName() {
return $this->displayName;
}
/**
* @return string|null
*/
public function getOwner() {
return $this->owner;
}
/**
* If this object is part of a collection return a reference
* to the parent object, otherwise return null.
* @return IPIMObject|null
*/
function getParent() {
return $this->parent;
}
/** CRUDS permissions (Create, Read, Update, Delete, Share) using a bitmask of
*
* \OCP\PERMISSION_CREATE
* \OCP\PERMISSION_READ
* \OCP\PERMISSION_UPDATE
* \OCP\PERMISSION_DELETE
* \OCP\PERMISSION_SHARE
* or
* \OCP\PERMISSION_ALL
*
* @return integer
*/
function getPermissions() {
return $this->permissions;
}
/**
* @return AbstractBackend
*/
function getBackend() {
return $this->backend;
}
/**
* @param integer $permission
* @return boolean
*/
function hasPermission($permission) {
return $this->getPermissions() & $permission;
}
/**
* Save the data to backend
*
* @param array $data
* @return bool
*/
abstract public function update(array $data);
/**
* Delete the data from backend
*
* @return bool
*/
abstract public function delete();
}

View File

@ -2,8 +2,8 @@
/**
* ownCloud - Addressbook
*
* @author Jakob Sack
* @copyright 2011 Jakob Sack mail@jakobsack.de
* @author Thomas Tanghus
* @copyright 2013 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
@ -19,416 +19,262 @@
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
/*
*
* The following SQL statement is just a help for developers and will not be
* executed!
*
* CREATE TABLE contacts_addressbooks (
* id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
* userid VARCHAR(255) NOT NULL,
* displayname VARCHAR(255),
* uri VARCHAR(100),
* description TEXT,
* ctag INT(11) UNSIGNED NOT NULL DEFAULT '1'
* );
*
*/
namespace OCA\Contacts;
/**
* This class manages our addressbooks.
*/
class Addressbook {
class Addressbook extends PIMCollectionAbstract {
protected $_count;
/**
* @brief Returns the list of addressbooks for a specific user.
* @param string $uid
* @param boolean $active Only return addressbooks with this $active state, default(=false) is don't care
* @param boolean $shared Whether to also return shared addressbook. Defaults to true.
* @return array or false.
* @var Backend\AbstractBackend
*/
public static function all($uid, $active = false, $shared = true) {
$values = array($uid);
$active_where = '';
if ($active) {
$active_where = ' AND `active` = ?';
$values[] = 1;
}
try {
$stmt = \OCP\DB::prepare( 'SELECT * FROM `*PREFIX*contacts_addressbooks` WHERE `userid` = ? ' . $active_where . ' ORDER BY `displayname`' );
$result = $stmt->execute($values);
if (\OC_DB::isError($result)) {
\OCP\Util::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
return false;
}
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.' exception: '.$e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::writeLog('contacts', __METHOD__.' uid: '.$uid, \OCP\Util::DEBUG);
return false;
}
protected $backend;
$addressbooks = array();
while( $row = $result->fetchRow()) {
$row['permissions'] = \OCP\PERMISSION_ALL;
$addressbooks[] = $row;
}
/**
* An array containing the mandatory:
* 'displayname'
* 'discription'
* 'permissions'
*
* And the optional:
* 'Etag'
* 'lastModified'
*
* @var array
*/
protected $addressBookInfo;
if($shared === true) {
$addressbooks = array_merge($addressbooks, \OCP\Share::getItemsSharedWith('addressbook', Share_Backend_Addressbook::FORMAT_ADDRESSBOOKS));
/**
* @param AbstractBackend $backend The storage backend
* @param array $addressBookInfo
*/
public function __construct(Backend\AbstractBackend $backend, array $addressBookInfo) {
$this->backend = $backend;
$this->addressBookInfo = $addressBookInfo;
if(is_null($this->getId())) {
$id = $this->backend->createAddressBook($addressBookInfo);
if($id === false) {
throw new \Exception('Error creating address book.');
}
if(!$active && !count($addressbooks)) {
$id = self::addDefault($uid);
return array(self::find($id),);
$this->addressBookInfo = $this->backend->getAddressBook($id);
//print(__METHOD__. ' '. __LINE__ . ' addressBookInfo: ' . print_r($this->backend->getAddressBook($id), true));
}
return $addressbooks;
//\OCP\Util::writeLog('contacts', __METHOD__.' backend: ' . print_r($this->backend, true), \OCP\Util::DEBUG);
}
/**
* @return string|null
*/
public function getId() {
return isset($this->addressBookInfo['id'])
? $this->addressBookInfo['id']
: null;
}
/**
* @brief Get active addressbook IDs for a user.
* @param integer $uid User id. If null current user will be used.
* @return array
*/
public static function activeIds($uid = null) {
if(is_null($uid)) {
$uid = \OCP\USER::getUser();
}
// query all addressbooks to force creation of default if it desn't exist.
$activeaddressbooks = self::all($uid);
$ids = array();
foreach($activeaddressbooks as $addressbook) {
if($addressbook['active']) {
$ids[] = $addressbook['id'];
}
}
return $ids;
public function getMetaData() {
$metadata = $this->addressBookInfo;
$metadata['lastmodified'] = $this->lastModified();
$metadata['backend'] = $this->getBackend()->name;
return $metadata;
}
/**
* @brief Returns the list of active addressbooks for a specific user.
* @param string $uid
* @return array
* @return string
*/
public static function active($uid) {
return self::all($uid, true);
public function getDisplayName() {
return $this->addressBookInfo['displayname'];
}
/**
* @brief Returns the list of addressbooks for a principal (DAV term of user)
* @param string $principaluri
* @return array
* @return string
*/
public static function allWherePrincipalURIIs($principaluri) {
$uid = self::extractUserID($principaluri);
return self::all($uid);
public function getURI() {
return $this->addressBookInfo['uri'];
}
/**
* @brief Gets the data of one address book
* @param integer $id
* @return associative array or false.
* @return string
*/
public static function find($id) {
try {
$stmt = \OCP\DB::prepare( 'SELECT * FROM `*PREFIX*contacts_addressbooks` WHERE `id` = ?' );
$result = $stmt->execute(array($id));
if (\OC_DB::isError($result)) {
\OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OC_Log::ERROR);
public function getOwner() {
return $this->addressBookInfo['owner'];
}
/**
* @return string
*/
public function getPermissions() {
return $this->addressBookInfo['permissions'];
}
function getBackend() {
return $this->backend;
}
/**
* Returns a specific child node, referenced by its id
*
* @param string $id
* @return Contact|null
*/
function getChild($id) {
//\OCP\Util::writeLog('contacts', __METHOD__.' id: '.$id, \OCP\Util::DEBUG);
if(!isset($this->objects[$id])) {
$contact = $this->backend->getContact($this->getId(), $id);
if($contact) {
$this->objects[$id] = new Contact($this, $this->backend, $contact);
}
}
// When requesting a single contact we preparse it
if(isset($this->objects[$id])) {
$this->objects[$id]->retrieve();
return $this->objects[$id];
}
}
/**
* Checks if a child-node with the specified id exists
*
* @param string $id
* @return bool
*/
function childExists($id) {
return ($this->getChild($id) !== null);
}
/**
* Returns an array with all the child nodes
*
* @return Contact[]
*/
function getChildren($limit = null, $offset = null, $omitdata = false) {
//\OCP\Util::writeLog('contacts', __METHOD__.' backend: ' . print_r($this->backend, true), \OCP\Util::DEBUG);
$contacts = array();
foreach($this->backend->getContacts($this->getId(), $limit, $offset, $omitdata) as $contact) {
//\OCP\Util::writeLog('contacts', __METHOD__.' id: '.$contact['id'], \OCP\Util::DEBUG);
if(!isset($this->objects[$contact['id']])) {
$this->objects[$contact['id']] = new Contact($this, $this->backend, $contact);
}
$contacts[] = $this->objects[$contact['id']];
}
return $contacts;
}
/**
* Add a contact to the address book
* This takes an array or a VCard|Contact and return
* the ID or false.
*
* @param array|VObject\VCard $data
* @return int|bool
*/
public function addChild($data = null) {
$contact = new Contact($this, $this->backend, $data);
if($contact->save() === false) {
return false;
}
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.', exception: ' . $e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::writeLog('contacts', __METHOD__.', id: ' . $id, \OCP\Util::DEBUG);
return false;
}
$row = $result->fetchRow();
if($row['userid'] != \OCP\USER::getUser() && !\OC_Group::inGroup(\OCP\User::getUser(), 'admin')) {
$sharedAddressbook = \OCP\Share::getItemSharedWithBySource('addressbook', $id);
if (!$sharedAddressbook || !($sharedAddressbook['permissions'] & \OCP\PERMISSION_READ)) {
throw new \Exception(
App::$l10n->t(
'You do not have the permissions to read this addressbook.'
)
);
}
$row['permissions'] = $sharedAddressbook['permissions'];
} else {
$row['permissions'] = \OCP\PERMISSION_ALL;
}
return $row;
}
/**
* @brief Adds default address book
* @return $id ID of the newly created addressbook or false on error.
*/
public static function addDefault($uid = null) {
if(is_null($uid)) {
$uid = \OCP\USER::getUser();
}
$id = self::add($uid, 'Contacts', 'Default Address Book');
if($id !== false) {
self::setActive($id, true);
$id = $contact->getId();
if($this->count() !== null) {
$this->_count += 1;
}
\OCP\Util::writeLog('contacts', __METHOD__.' id: '.$id, \OCP\Util::DEBUG);
return $id;
}
/**
* @brief Creates a new address book
* @param string $userid
* @param string $name
* @param string $description
* @return insertid
* Delete a contact from the address book
*
* @param string $id
* @return bool
*/
public static function add($uid,$name,$description='') {
try {
$stmt = \OCP\DB::prepare( 'SELECT `uri` FROM `*PREFIX*contacts_addressbooks` WHERE `userid` = ? ' );
$result = $stmt->execute(array($uid));
if (\OC_DB::isError($result)) {
\OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OC_Log::ERROR);
return false;
public function deleteChild($id) {
if($this->backend->deleteContact($this->getId(), $id)) {
if(isset($this->objects[$id])) {
unset($this->objects[$id]);
}
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__ . ' exception: ' . $e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::writeLog('contacts', __METHOD__ . ' uid: ' . $uid, \OCP\Util::DEBUG);
return false;
if($this->count() !== null) {
$this->_count -= 1;
}
$uris = array();
while($row = $result->fetchRow()) {
$uris[] = $row['uri'];
}
$uri = self::createURI($name, $uris );
try {
$stmt = \OCP\DB::prepare( 'INSERT INTO `*PREFIX*contacts_addressbooks` (`userid`,`displayname`,`uri`,`description`,`ctag`) VALUES(?,?,?,?,?)' );
$result = $stmt->execute(array($uid,$name,$uri,$description,1));
if (\OC_DB::isError($result)) {
\OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OC_Log::ERROR);
return false;
}
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::writeLog('contacts', __METHOD__.', uid: '.$uid, \OCP\Util::DEBUG);
return false;
}
return \OCP\DB::insertid('*PREFIX*contacts_addressbooks');
}
/**
* @brief Creates a new address book from the data sabredav provides
* @param string $principaluri
* @param string $uri
* @param string $name
* @param string $description
* @return insertid or false
*/
public static function addFromDAVData($principaluri, $uri, $name, $description) {
$uid = self::extractUserID($principaluri);
try {
$stmt = \OCP\DB::prepare('INSERT INTO `*PREFIX*contacts_addressbooks` '
. '(`userid`,`displayname`,`uri`,`description`,`ctag`) VALUES(?,?,?,?,?)');
$result = $stmt->execute(array($uid, $name, $uri, $description, 1));
if (\OC_DB::isError($result)) {
\OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OC_Log::ERROR);
return false;
}
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.', exception: ' . $e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::writeLog('contacts', __METHOD__.', uid: ' . $uid, \OCP\Util::DEBUG);
\OCP\Util::writeLog('contacts', __METHOD__.', uri: ' . $uri, \OCP\Util::DEBUG);
return false;
}
return \OCP\DB::insertid('*PREFIX*contacts_addressbooks');
}
/**
* @brief Edits an addressbook
* @param integer $id
* @param string $name
* @param string $description
* @return boolean
*/
public static function edit($id,$name,$description) {
// Need these ones for checking uri
$addressbook = self::find($id);
if ($addressbook['userid'] != \OCP\User::getUser() && !\OC_Group::inGroup(OCP\User::getUser(), 'admin')) {
$sharedAddressbook = \OCP\Share::getItemSharedWithBySource('addressbook', $id);
if (!$sharedAddressbook || !($sharedAddressbook['permissions'] & \OCP\PERMISSION_UPDATE)) {
throw new \Exception(
App::$l10n->t(
'You do not have the permissions to update this addressbook.'
)
);
}
}
if(is_null($name)) {
$name = $addressbook['name'];
}
if(is_null($description)) {
$description = $addressbook['description'];
}
try {
$stmt = \OCP\DB::prepare('UPDATE `*PREFIX*contacts_addressbooks` SET `displayname`=?,`description`=?, `ctag`=`ctag`+1 WHERE `id`=?');
$result = $stmt->execute(array($name,$description,$id));
if (\OC_DB::isError($result)) {
\OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OC_Log::ERROR);
throw new \Exception(
App::$l10n->t(
'There was an error updating the addressbook.'
)
);
}
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__ . ', exception: ' . $e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::writeLog('contacts', __METHOD__ . ', id: ' . $id, \OCP\Util::DEBUG);
throw new \Exception(
App::$l10n->t(
'There was an error updating the addressbook.'
)
);
}
return true;
}
/**
* @brief Activates an addressbook
* @param integer $id
* @param boolean $active
* @return boolean
*/
public static function setActive($id,$active) {
$sql = 'UPDATE `*PREFIX*contacts_addressbooks` SET `active` = ? WHERE `id` = ?';
try {
$stmt = \OCP\DB::prepare($sql);
$stmt->execute(array(intval($active), $id));
return true;
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__ . ', exception for ' . $id.': ' . $e->getMessage(), \OCP\Util::ERROR);
return false;
}
/**
* @internal implements Countable
* @return int|null
*/
public function count() {
if(!isset($this->_count)) {
$this->_count = $this->backend->numContacts($this->getId());
}
return $this->_count;
}
/**
* @brief Checks if an addressbook is active.
* @param integer $id ID of the address book.
* @return boolean
* Update and save the address book data to backend
* NOTE: @see IPIMObject::update for consistency considerations.
*
* @param array $data
* @return bool
*/
public static function isActive($id) {
$sql = 'SELECT `active` FROM `*PREFIX*contacts_addressbooks` WHERE `id` = ?';
try {
$stmt = \OCP\DB::prepare( $sql );
$result = $stmt->execute(array($id));
if (\OC_DB::isError($result)) {
\OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OC_Log::ERROR);
public function update(array $data) {
if(count($data) === 0) {
return false;
}
$row = $result->fetchRow();
return (bool)$row['active'];
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.', exception: ' . $e->getMessage(), \OCP\Util::ERROR);
return false;
foreach($data as $key => $value) {
switch($key) {
case 'displayname':
$this->addressBookInfo['displayname'] = $value;
break;
case 'description':
$this->addressBookInfo['description'] = $value;
break;
}
}
return $this->backend->updateAddressBook($this->getId(), $data);
}
/**
* Save the address book data to backend
* NOTE: @see IPIMObject::update for consistency considerations.
*
* @return bool
*/
public function save() {
if(!$this->hasPermission(OCP\PERMISSION_UPDATE)) {
throw new Exception('You don\'t have permissions to update the address book.');
}
}
/**
* @brief removes an address book
* @param integer $id
* @return boolean true on success, otherwise an exception will be thrown
* Delete the address book from backend
*
* @return bool
*/
public static function delete($id) {
$addressbook = self::find($id);
if ($addressbook['userid'] != \OCP\User::getUser() && !\OC_Group::inGroup(\OCP\User::getUser(), 'admin')) {
$sharedAddressbook = \OCP\Share::getItemSharedWithBySource('addressbook', $id);
if (!$sharedAddressbook || !($sharedAddressbook['permissions'] & \OCP\PERMISSION_DELETE)) {
throw new \Exception(
App::$l10n->t(
'You do not have the permissions to delete this addressbook.'
)
);
public function delete() {
if(!$this->hasPermission(OCP\PERMISSION_DELETE)) {
throw new Exception('You don\'t have permissions to delete the address book.');
}
}
// First delete cards belonging to this addressbook.
$cards = VCard::all($id);
foreach($cards as $card) {
try {
VCard::delete($card['id']);
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts',
__METHOD__.', exception deleting vCard '.$card['id'].': '
. $e->getMessage(),
\OCP\Util::ERROR);
}
}
try {
$stmt = \OCP\DB::prepare('DELETE FROM `*PREFIX*contacts_addressbooks` WHERE `id` = ?');
$stmt->execute(array($id));
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts',
__METHOD__.', exception for ' . $id . ': '
. $e->getMessage(),
\OCP\Util::ERROR);
throw new \Exception(
App::$l10n->t(
'There was an error deleting this addressbook.'
)
);
}
\OCP\Share::unshareAll('addressbook', $id);
if(count(self::all(\OCP\User::getUser())) == 0) {
self::addDefault();
}
return true;
return $this->backend->deleteAddressBook($this->getId());
}
/**
* @brief Updates ctag for addressbook
* @param integer $id
* @return boolean
* @brief Get the last modification time for the object.
*
* Must return a UNIX time stamp or null if the backend
* doesn't support it.
*
* @returns int | null
*/
public static function touch($id) {
$stmt = \OCP\DB::prepare( 'UPDATE `*PREFIX*contacts_addressbooks` SET `ctag` = `ctag` + 1 WHERE `id` = ?' );
$stmt->execute(array($id));
return true;
public function lastModified() {
return $this->backend->lastModifiedAddressBook($this->getId());
}
/**
* @brief Creates a URI for Addressbook
* @param string $name name of the addressbook
* @param array $existing existing addressbook URIs
* @return string new name
*/
public static function createURI($name,$existing) {
$name = str_replace(' ', '_', strtolower($name));
$newname = $name;
$i = 1;
while(in_array($newname, $existing)) {
$newname = $name.$i;
$i = $i + 1;
}
return $newname;
}
/**
* @brief gets the userid from a principal path
* @return string
*/
public static function extractUserID($principaluri) {
list($prefix, $userid) = \Sabre_DAV_URLUtil::splitPath($principaluri);
return $userid;
}
}

387
lib/addressbooklegacy.php Normal file
View File

@ -0,0 +1,387 @@
<?php
/**
* ownCloud - Addressbook
*
* @author Jakob Sack
* @copyright 2011 Jakob Sack mail@jakobsack.de
*
* 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/>.
*
*/
/*
*
* The following SQL statement is just a help for developers and will not be
* executed!
*
* CREATE TABLE contacts_addressbooks (
* id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
* userid VARCHAR(255) NOT NULL,
* displayname VARCHAR(255),
* uri VARCHAR(100),
* description TEXT,
* ctag INT(11) UNSIGNED NOT NULL DEFAULT '1'
* );
*
*/
namespace OCA\Contacts;
/**
* This class manages our addressbooks.
*/
class AddressbookLegacy {
/**
* @brief Returns the list of addressbooks for a specific user.
* @param string $uid
* @param boolean $active Only return addressbooks with this $active state, default(=false) is don't care
* @param boolean $shared Whether to also return shared addressbook. Defaults to true.
* @return array or false.
*/
public static function all($uid, $active = false, $shared = true) {
$values = array($uid);
$active_where = '';
if ($active) {
$active_where = ' AND `active` = ?';
$values[] = 1;
}
try {
$stmt = \OCP\DB::prepare( 'SELECT * FROM `*PREFIX*contacts_addressbooks` WHERE `userid` = ? ' . $active_where . ' ORDER BY `displayname`' );
$result = $stmt->execute($values);
if (\OC_DB::isError($result)) {
\OCP\Util::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
return false;
}
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.' exception: '.$e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::writeLog('contacts', __METHOD__.' uid: '.$uid, \OCP\Util::DEBUG);
return false;
}
$addressbooks = array();
while( $row = $result->fetchRow()) {
$row['permissions'] = \OCP\PERMISSION_ALL;
$addressbooks[] = $row;
}
if($shared === true) {
$addressbooks = array_merge($addressbooks, \OCP\Share::getItemsSharedWith('addressbook', Share_Backend_Addressbook::FORMAT_ADDRESSBOOKS));
}
if(!$active && !count($addressbooks)) {
$id = self::addDefault($uid);
return array(self::find($id),);
}
return $addressbooks;
}
/**
* @brief Get active addressbook IDs for a user.
* @param integer $uid User id. If null current user will be used.
* @return array
*/
public static function activeIds($uid = null) {
if(is_null($uid)) {
$uid = \OCP\USER::getUser();
}
// query all addressbooks to force creation of default if it desn't exist.
$activeaddressbooks = self::all($uid);
$ids = array();
foreach($activeaddressbooks as $addressbook) {
if($addressbook['active']) {
$ids[] = $addressbook['id'];
}
}
return $ids;
}
/**
* @brief Returns the list of active addressbooks for a specific user.
* @param string $uid
* @return array
*/
public static function active($uid) {
return self::all($uid, true);
}
/**
* @brief Gets the data of one address book
* @param integer $id
* @return associative array or false.
*/
public static function find($id) {
try {
$stmt = \OCP\DB::prepare( 'SELECT * FROM `*PREFIX*contacts_addressbooks` WHERE `id` = ?' );
$result = $stmt->execute(array($id));
if (\OC_DB::isError($result)) {
\OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OC_Log::ERROR);
return false;
}
} catch(Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.', exception: ' . $e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::writeLog('contacts', __METHOD__.', id: ' . $id, \OCP\Util::DEBUG);
return false;
}
$row = $result->fetchRow();
if($row['userid'] != \OCP\USER::getUser() && !\OC_Group::inGroup(\OCP\User::getUser(), 'admin')) {
$sharedAddressbook = \OCP\Share::getItemSharedWithBySource('addressbook', $id);
if (!$sharedAddressbook || !($sharedAddressbook['permissions'] & \OCP\PERMISSION_READ)) {
throw new Exception(
App::$l10n->t(
'You do not have the permissions to read this addressbook.'
)
);
}
$row['permissions'] = $sharedAddressbook['permissions'];
} else {
$row['permissions'] = \OCP\PERMISSION_ALL;
}
return $row;
}
/**
* @brief Adds default address book
* @return $id ID of the newly created addressbook or false on error.
*/
public static function addDefault($uid = null) {
if(is_null($uid)) {
$uid = \OCP\USER::getUser();
}
$id = self::add($uid, 'Contacts', 'Default Address Book');
if($id !== false) {
self::setActive($id, true);
}
return $id;
}
/**
* @brief Creates a new address book
* @param string $userid
* @param string $name
* @param string $description
* @return insertid
*/
public static function add($uid,$name,$description='') {
try {
$stmt = \OCP\DB::prepare( 'SELECT `uri` FROM `*PREFIX*contacts_addressbooks` WHERE `userid` = ? ' );
$result = $stmt->execute(array($uid));
if (\OC_DB::isError($result)) {
\OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OC_Log::ERROR);
return false;
}
} catch(Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__ . ' exception: ' . $e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::writeLog('contacts', __METHOD__ . ' uid: ' . $uid, \OCP\Util::DEBUG);
return false;
}
$uris = array();
while($row = $result->fetchRow()) {
$uris[] = $row['uri'];
}
$uri = self::createURI($name, $uris );
try {
$stmt = \OCP\DB::prepare( 'INSERT INTO `*PREFIX*contacts_addressbooks` (`userid`,`displayname`,`uri`,`description`,`ctag`) VALUES(?,?,?,?,?)' );
$result = $stmt->execute(array($uid,$name,$uri,$description,1));
if (\OC_DB::isError($result)) {
\OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OC_Log::ERROR);
return false;
}
} catch(Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::writeLog('contacts', __METHOD__.', uid: '.$uid, \OCP\Util::DEBUG);
return false;
}
return \OCP\DB::insertid('*PREFIX*contacts_addressbooks');
}
/**
* @brief Edits an addressbook
* @param integer $id
* @param string $name
* @param string $description
* @return boolean
*/
public static function edit($id,$name,$description) {
// Need these ones for checking uri
$addressbook = self::find($id);
if ($addressbook['userid'] != \OCP\User::getUser() && !\OC_Group::inGroup(OCP\User::getUser(), 'admin')) {
$sharedAddressbook = \OCP\Share::getItemSharedWithBySource('addressbook', $id);
if (!$sharedAddressbook || !($sharedAddressbook['permissions'] & \OCP\PERMISSION_UPDATE)) {
throw new \Exception(
App::$l10n->t(
'You do not have the permissions to update this addressbook.'
)
);
}
}
if(is_null($name)) {
$name = $addressbook['name'];
}
if(is_null($description)) {
$description = $addressbook['description'];
}
try {
$stmt = \OCP\DB::prepare('UPDATE `*PREFIX*contacts_addressbooks` SET `displayname`=?,`description`=?, `ctag`=`ctag`+1 WHERE `id`=?');
$result = $stmt->execute(array($name,$description,$id));
if (\OC_DB::isError($result)) {
\OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OC_Log::ERROR);
throw new Exception(
App::$l10n->t(
'There was an error updating the addressbook.'
)
);
}
} catch(Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__ . ', exception: ' . $e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::writeLog('contacts', __METHOD__ . ', id: ' . $id, \OCP\Util::DEBUG);
throw new Exception(
App::$l10n->t(
'There was an error updating the addressbook.'
)
);
}
return true;
}
/**
* @brief Activates an addressbook
* @param integer $id
* @param boolean $active
* @return boolean
*/
public static function setActive($id,$active) {
$sql = 'UPDATE `*PREFIX*contacts_addressbooks` SET `active` = ? WHERE `id` = ?';
try {
$stmt = \OCP\DB::prepare($sql);
$stmt->execute(array(intval($active), $id));
return true;
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__ . ', exception for ' . $id.': ' . $e->getMessage(), \OCP\Util::ERROR);
return false;
}
}
/**
* @brief Checks if an addressbook is active.
* @param integer $id ID of the address book.
* @return boolean
*/
public static function isActive($id) {
$sql = 'SELECT `active` FROM `*PREFIX*contacts_addressbooks` WHERE `id` = ?';
try {
$stmt = \OCP\DB::prepare( $sql );
$result = $stmt->execute(array($id));
if (\OC_DB::isError($result)) {
\OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OC_Log::ERROR);
return false;
}
$row = $result->fetchRow();
return (bool)$row['active'];
} catch(Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.', exception: ' . $e->getMessage(), \OCP\Util::ERROR);
return false;
}
}
/**
* @brief removes an address book
* @param integer $id
* @return boolean true on success, otherwise an exception will be thrown
*/
public static function delete($id) {
$addressbook = self::find($id);
if ($addressbook['userid'] != \OCP\User::getUser() && !\OC_Group::inGroup(\OCP\User::getUser(), 'admin')) {
$sharedAddressbook = \OCP\Share::getItemSharedWithBySource('addressbook', $id);
if (!$sharedAddressbook || !($sharedAddressbook['permissions'] & \OCP\PERMISSION_DELETE)) {
throw new Exception(
App::$l10n->t(
'You do not have the permissions to delete this addressbook.'
)
);
}
}
// First delete cards belonging to this addressbook.
$cards = VCard::all($id);
foreach($cards as $card) {
try {
VCard::delete($card['id']);
} catch(Exception $e) {
\OCP\Util::writeLog('contacts',
__METHOD__.', exception deleting vCard '.$card['id'].': '
. $e->getMessage(),
\OCP\Util::ERROR);
}
}
try {
$stmt = \OCP\DB::prepare('DELETE FROM `*PREFIX*contacts_addressbooks` WHERE `id` = ?');
$stmt->execute(array($id));
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts',
__METHOD__.', exception for ' . $id . ': '
. $e->getMessage(),
\OCP\Util::ERROR);
throw new Exception(
App::$l10n->t(
'There was an error deleting this addressbook.'
)
);
}
\OCP\Share::unshareAll('addressbook', $id);
if(count(self::all(\OCP\User::getUser())) == 0) {
self::addDefault();
}
return true;
}
/**
* @brief Updates ctag for addressbook
* @param integer $id
* @return boolean
*/
public static function touch($id) {
$stmt = \OCP\DB::prepare( 'UPDATE `*PREFIX*contacts_addressbooks` SET `ctag` = `ctag` + 1 WHERE `id` = ?' );
$stmt->execute(array($id));
return true;
}
/**
* @brief Creates a URI for Addressbook
* @param string $name name of the addressbook
* @param array $existing existing addressbook URIs
* @return string new name
*/
public static function createURI($name,$existing) {
$name = str_replace(' ', '_', strtolower($name));
$newname = $name;
$i = 1;
while(in_array($newname, $existing)) {
$newname = $name.$i;
$i = $i + 1;
}
return $newname;
}
}

View File

@ -24,6 +24,7 @@ namespace OCA\Contacts;
/**
* This class manages our addressbooks.
* TODO: Port this to use the new backend
*/
class AddressbookProvider implements \OCP\IAddressBook {
@ -48,7 +49,6 @@ class AddressbookProvider implements \OCP\IAddressBook {
*/
public function __construct($id) {
$this->id = $id;
\Sabre\VObject\Property::$classMap['GEO'] = 'Sabre\\VObject\\Property\\Compound';
}
public function getAddressbook() {

View File

@ -1,6 +1,6 @@
<?php
/**
* Copyright (c) 2011 Bart Visscher bartv@thisnet.nl
* Copyright (c) 2013 Thomas Tanghus (thomas@tanghus.net)
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
@ -16,183 +16,136 @@ use Sabre\VObject;
App::$l10n = \OC_L10N::get('contacts');
class App {
/*
* @brief language object for calendar app
*/
public static $l10n;
/*
* @brief categories of the user
/**
* @brief Categories of the user
* @var OC_VCategories
*/
public static $categories = null;
/**
* Properties there can be more than one of.
* @brief language object for calendar app
*
* @var OC_L10N
*/
public static $multi_properties = array('EMAIL', 'TEL', 'IMPP', 'ADR', 'URL');
public static $l10n;
/**
* Properties to index.
* An array holding the current users address books.
* @var array
*/
public static $index_properties = array('BDAY', 'UID', 'N', 'FN', 'TITLE', 'ROLE', 'NOTE', 'NICKNAME', 'ORG', 'CATEGORIES', 'EMAIL', 'TEL', 'IMPP', 'ADR', 'URL', 'GEO', 'PHOTO');
const THUMBNAIL_PREFIX = 'contact-thumbnail-';
const THUMBNAIL_SIZE = 28;
protected static $addressBooks = array();
/**
* @brief Gets the VCard as a \Sabre\VObject\Component
* @param integer $id
* @returns \Sabre\VObject\Component|null The card or null if the card could not be parsed.
* If backends are added to this map, they will be automatically mapped
* to their respective classes, if constructed with the 'getBackend' method.
*
* @var array
*/
public static function getContactVCard($id) {
$card = null;
$vcard = null;
try {
$card = VCard::find($id);
} catch(\Exception $e) {
return null;
}
if(!$card) {
return null;
}
try {
$vcard = \Sabre\VObject\Reader::read($card['carddata']);
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.', exception: ' . $e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::writeLog('contacts', __METHOD__.', id: ' . $id, \OCP\Util::DEBUG);
return null;
}
if (!is_null($vcard) && !isset($vcard->REV)) {
$rev = new \DateTime('@'.$card['lastmodified']);
$vcard->REV = $rev->format(\DateTime::W3C);
}
return $vcard;
}
public static function getPropertyLineByChecksum($vcard, $checksum) {
$line = null;
foreach($vcard->children as $i => $property) {
if(substr(md5($property->serialize()), 0, 8) == $checksum ) {
$line = $i;
break;
}
}
return $line;
}
/**
* @return array of vcard prop => label
*/
public static function getIMOptions($im = null) {
$l10n = self::$l10n;
$ims = array(
'jabber' => array(
'displayname' => (string)$l10n->t('Jabber'),
'xname' => 'X-JABBER',
'protocol' => 'xmpp',
),
'aim' => array(
'displayname' => (string)$l10n->t('AIM'),
'xname' => 'X-AIM',
'protocol' => 'aim',
),
'msn' => array(
'displayname' => (string)$l10n->t('MSN'),
'xname' => 'X-MSN',
'protocol' => 'msn',
),
'twitter' => array(
'displayname' => (string)$l10n->t('Twitter'),
'xname' => 'X-TWITTER',
'protocol' => 'twitter',
),
'googletalk' => array(
'displayname' => (string)$l10n->t('GoogleTalk'),
'xname' => null,
'protocol' => 'xmpp',
),
'facebook' => array(
'displayname' => (string)$l10n->t('Facebook'),
'xname' => null,
'protocol' => 'xmpp',
),
'xmpp' => array(
'displayname' => (string)$l10n->t('XMPP'),
'xname' => null,
'protocol' => 'xmpp',
),
'icq' => array(
'displayname' => (string)$l10n->t('ICQ'),
'xname' => 'X-ICQ',
'protocol' => 'icq',
),
'yahoo' => array(
'displayname' => (string)$l10n->t('Yahoo'),
'xname' => 'X-YAHOO',
'protocol' => 'ymsgr',
),
'skype' => array(
'displayname' => (string)$l10n->t('Skype'),
'xname' => 'X-SKYPE',
'protocol' => 'skype',
),
'qq' => array(
'displayname' => (string)$l10n->t('QQ'),
'xname' => 'X-SKYPE',
'protocol' => 'x-apple',
),
'gadugadu' => array(
'displayname' => (string)$l10n->t('GaduGadu'),
'xname' => 'X-SKYPE',
'protocol' => 'x-apple',
),
public static $backendClasses = array(
'local' => 'OCA\Contacts\Backend\Database',
'shared' => 'OCA\Contacts\Backend\Shared',
);
if(is_null($im)) {
return $ims;
public function __construct(
$user = null,
$addressBooksTableName = '*PREFIX*addressbook',
$backendsTableName = '*PREFIX*addressbooks_backend',
$dbBackend = null
) {
$this->user = $user ? $user : \OCP\User::getUser();
$this->addressBooksTableName = $addressBooksTableName;
$this->backendsTableName = $backendsTableName;
$this->dbBackend = $dbBackend
? $dbBackend
: new Backend\Database($user);
}
/**
* Gets backend by name.
*
* @param string $name
* @return \Backend\AbstractBackend
*/
static public function getBackend($name, $user = null) {
$name = $name ? $name : 'local';
if (isset(self::$backendClasses[$name])) {
return new self::$backendClasses[$name]($user);
} else {
$ims['ymsgr'] = $ims['yahoo'];
$ims['gtalk'] = $ims['googletalk'];
return isset($ims[$im]) ? $ims[$im] : null;
throw new \Exception('No backend for: ' . $name);
}
}
/**
* @return types for property $prop
* Return all registered address books for current user.
* For now this is hard-coded to using the Database and
* Shared backends, but eventually admins will be able to
* register additional backends, and users will be able to
* subscribe to address books using those backends.
*
* @return AddressBook[]
*/
public static function getTypesOfProperty($prop) {
$l = self::$l10n;
switch($prop) {
case 'ADR':
case 'IMPP':
return array(
'WORK' => $l->t('Work'),
'HOME' => $l->t('Home'),
'OTHER' => $l->t('Other'),
);
case 'TEL':
return array(
'HOME' => $l->t('Home'),
'CELL' => $l->t('Mobile'),
'WORK' => $l->t('Work'),
'TEXT' => $l->t('Text'),
'VOICE' => $l->t('Voice'),
'MSG' => $l->t('Message'),
'FAX' => $l->t('Fax'),
'VIDEO' => $l->t('Video'),
'PAGER' => $l->t('Pager'),
'OTHER' => $l->t('Other'),
);
case 'EMAIL':
return array(
'WORK' => $l->t('Work'),
'HOME' => $l->t('Home'),
'INTERNET' => $l->t('Internet'),
'OTHER' => $l->t('Other'),
);
public function getAddressBooksForUser() {
if(!self::$addressBooks) {
foreach(array_keys(self::$backendClasses) as $backendName) {
$backend = self::getBackend($backendName, $this->user);
$addressBooks = $backend->getAddressBooksForUser();
if($backendName === 'local' && count($addressBooks) === 0) {
$id = $backend->createAddressBook(array('displayname' => 'Contacts'));
if($id !== false) {
$addressBook = $backend->getAddressBook($id);
$addressBooks = array($addressBook);
} else {
// TODO: Write log
}
}
foreach($addressBooks as $addressBook) {
$addressBook['backend'] = $backendName;
self::$addressBooks[] = new AddressBook($backend, $addressBook);
}
}
}
return self::$addressBooks;
}
/**
* Get an address book from a specific backend.
*
* @param string $backendName
* @param string $addressbookid
* @return AddressBook|null
*/
public function getAddressBook($backendName, $addressbookid) {
foreach(self::$addressBooks as $addressBook) {
if($addressBook->backend->name === $backendName
&& $addressBook->getId() === $addressbookid
) {
return $addressBook;
}
}
// TODO: Check for return values
$backend = self::getBackend($backendName, $this->user);
$info = $backend->getAddressBook($addressbookid);
// FIXME: Backend name should be set by the backend.
$info['backend'] = $backendName;
$addressBook = new AddressBook($backend, $info);
self::$addressBooks[] = $addressBook;
return $addressBook;
}
/**
* Get a Contact from an address book from a specific backend.
*
* @param string $backendName
* @param string $addressbookid
* @param string $id - Contact id
* @return Contact|null
*
*/
public function getContact($backendName, $addressbookid, $id) {
$addressBook = $this->getAddressBook($backendName, $addressbookid);
// TODO: Check for return value
return $addressBook->getChild($id);
}
/**
* @brief returns the vcategories object of the user
@ -209,7 +162,6 @@ class App {
}
return self::$categories;
}
/**
* @brief returns the categories for the user
* @return (Array) $categories
@ -219,19 +171,6 @@ class App {
return ($categories ? $categories : self::getDefaultCategories());
}
/**
* @brief returns the default categories of ownCloud
* @return (array) $categories
*/
public static function getDefaultCategories() {
return array(
(string)self::$l10n->t('Friends'),
(string)self::$l10n->t('Family'),
(string)self::$l10n->t('Work'),
(string)self::$l10n->t('Other'),
);
}
/**
* scan vcards for categories.
* @param $vccontacts VCards to scan. null to check all vcards for the current user.
@ -269,140 +208,4 @@ class App {
}
}
/**
* check VCard for new categories.
* @see OC_VCategories::loadFromVObject
*/
public static function loadCategoriesFromVCard($id, $contact) {
if(!$contact instanceof \OC_VObject) {
$contact = new \OC_VObject($contact);
}
self::getVCategories()->loadFromVObject($id, $contact, true);
}
/**
* @brief Get the last modification time.
* @param OC_VObject|Sabre\VObject\Component|integer|null $contact
* @returns DateTime | null
*/
public static function lastModified($contact = null) {
if(is_null($contact)) {
// FIXME: This doesn't take shared address books into account.
$sql = 'SELECT MAX(`lastmodified`) FROM `*PREFIX*contacts_cards`, `*PREFIX*contacts_addressbooks` ' .
'WHERE `*PREFIX*contacts_cards`.`addressbookid` = `*PREFIX*contacts_addressbooks`.`id` AND ' .
'`*PREFIX*contacts_addressbooks`.`userid` = ?';
$stmt = \OCP\DB::prepare($sql);
$result = $stmt->execute(array(\OCP\USER::getUser()));
if (\OC_DB::isError($result)) {
\OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OC_Log::ERROR);
return null;
}
$lastModified = $result->fetchOne();
if(!is_null($lastModified)) {
return new \DateTime('@' . $lastModified);
}
} else if(is_numeric($contact)) {
$card = VCard::find($contact, array('lastmodified'));
return ($card ? new \DateTime('@' . $card['lastmodified']) : null);
} elseif($contact instanceof \OC_VObject || $contact instanceof VObject\Component) {
return isset($contact->REV)
? \DateTime::createFromFormat(\DateTime::W3C, $contact->REV)
: null;
}
}
public static function cacheThumbnail($id, \OC_Image $image = null) {
if(\OC_Cache::hasKey(self::THUMBNAIL_PREFIX . $id) && $image === null) {
return \OC_Cache::get(self::THUMBNAIL_PREFIX . $id);
}
if(is_null($image)) {
$vcard = self::getContactVCard($id);
// invalid vcard
if(is_null($vcard)) {
\OCP\Util::writeLog('contacts',
__METHOD__.' The VCard for ID ' . $id . ' is not RFC compatible',
\OCP\Util::ERROR);
return false;
}
$image = new \OC_Image();
if(!isset($vcard->PHOTO)) {
return false;
}
if(!$image->loadFromBase64((string)$vcard->PHOTO)) {
return false;
}
}
if(!$image->centerCrop()) {
\OCP\Util::writeLog('contacts',
'thumbnail.php. Couldn\'t crop thumbnail for ID ' . $id,
\OCP\Util::ERROR);
return false;
}
if(!$image->resize(self::THUMBNAIL_SIZE)) {
\OCP\Util::writeLog('contacts',
'thumbnail.php. Couldn\'t resize thumbnail for ID ' . $id,
\OCP\Util::ERROR);
return false;
}
// Cache for around a month
\OC_Cache::set(self::THUMBNAIL_PREFIX . $id, $image->data(), 3000000);
\OCP\Util::writeLog('contacts', 'Caching ' . $id, \OCP\Util::DEBUG);
return \OC_Cache::get(self::THUMBNAIL_PREFIX . $id);
}
public static function updateDBProperties($contactid, $vcard = null) {
$stmt = \OCP\DB::prepare('DELETE FROM `*PREFIX*contacts_cards_properties` WHERE `contactid` = ?');
try {
$stmt->execute(array($contactid));
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.
', exception: ' . $e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::writeLog('contacts', __METHOD__.', id: '
. $id, \OCP\Util::DEBUG);
throw new \Exception(
App::$l10n->t(
'There was an error deleting properties for this contact.'
)
);
}
if(is_null($vcard)) {
return;
}
$stmt = \OCP\DB::prepare( 'INSERT INTO `*PREFIX*contacts_cards_properties` '
. '(`userid`, `contactid`,`name`,`value`,`preferred`) VALUES(?,?,?,?,?)' );
foreach($vcard->children as $property) {
if(!in_array($property->name, self::$index_properties)) {
continue;
}
$preferred = 0;
foreach($property->parameters as $parameter) {
if($parameter->name == 'TYPE' && strtoupper($parameter->value) == 'PREF') {
$preferred = 1;
break;
}
}
try {
$result = $stmt->execute(
array(
\OCP\User::getUser(),
$contactid,
$property->name,
$property->value,
$preferred,
)
);
if (\OC_DB::isError($result)) {
\OCP\Util::writeLog('contacts', __METHOD__. 'DB error: '
. \OC_DB::getErrorMessage($result), \OC_Log::ERROR);
return false;
}
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR);
return false;
}
}
}
}

View File

@ -0,0 +1,315 @@
<?php
/**
* ownCloud - Base class for Contacts backends
*
* @author Thomas Tanghus
* @copyright 2013 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
* 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/>.
*
*/
namespace OCA\Contacts\Backend;
/**
* Subclass this class for address book backends
*
* The following methods MUST be implemented:
* @method array getAddressBooksForUser(string $userid) FIXME: I'm not sure about this one.
* @method array getAddressBook(string $addressbookid)
* @method array getContacts(string $addressbookid)
* @method array getContact(string $addressbookid, mixed $id)
* The following methods MAY be implemented:
* @method bool hasAddressBook(string $addressbookid)
* @method bool updateAddressBook(string $addressbookid, array $updates)
* @method string createAddressBook(string $addressbookid, array $properties)
* @method bool deleteAddressBook(string $addressbookid)
* @method int lastModifiedAddressBook(string $addressbookid)
* @method array numContacts(string $addressbookid)
* @method bool updateContact(string $addressbookid, string $id, array $updates)
* @method string createContact(string $addressbookid, string $id, array $properties)
* @method bool deleteContact(string $addressbookid, string $id)
* @method int lastModifiedContact(string $addressbookid)
*/
abstract class AbstractBackend {
/**
* The name of the backend.
* @var string
*/
public $name;
protected $possibleContactPermissions = array(
\OCP\PERMISSION_CREATE => 'createContact',
\OCP\PERMISSION_READ => 'getContact',
\OCP\PERMISSION_UPDATE => 'updateContact',
\OCP\PERMISSION_DELETE => 'deleteContact',
);
protected $possibleAddressBookPermissions = array(
\OCP\PERMISSION_CREATE => 'createAddressBook',
\OCP\PERMISSION_READ => 'getAddressBook',
\OCP\PERMISSION_UPDATE => 'updateAddressBook',
\OCP\PERMISSION_DELETE => 'deleteAddressBook',
);
/**
* @brief Get all permissions for contacts
* @returns bitwise-or'ed actions
*
* Returns the supported actions as int to be
* compared with \OCP\PERMISSION_CREATE etc.
* TODO: When getting the permissions we also have to check for
* configured permissions and return min() of the two values.
* A user can for example configure an address book with a backend
* that implements deleteContact() but wants to set it read-only.
*/
public function getContactPermissions() {
$permissions = 0;
foreach($this->possibleContactPermissions AS $permission => $methodName) {
if(method_exists($this, $methodName)) {
$permissions |= $permission;
}
}
return $permissions;
}
/**
* @brief Get all permissions for address book.
* @returns bitwise-or'ed actions
*
* Returns the supported actions as int to be
* compared with \OCP\PERMISSION_CREATE etc.
*/
public function getAddressBookPermissions() {
$permissions = 0;
foreach($this->possibleAddressBookPermissions AS $permission => $methodName) {
if(method_exists($this, $methodName)) {
$permissions |= $permission;
}
}
return $permissions;
}
/**
* @brief Check if backend implements action for contacts
* @param $actions bitwise-or'ed actions
* @returns boolean
*
* Returns the supported actions as int to be
* compared with \OCP\PERMISSION_CREATE etc.
*/
public function hasContactPermission($permission) {
return (bool)($this->getContactPermissions() & $permission);
}
/**
* @brief Check if backend implements action for contacts
* @param $actions bitwise-or'ed actions
* @returns boolean
*
* Returns the supported actions as int to be
* compared with \OCP\PERMISSION_CREATE etc.
*/
public function hasAddressBooksPermission($permission) {
return (bool)($this->getAddressBooksPermissions() & $permission);
}
/**
* Check if the backend has the address book
*
* @param string $addressbookid
* @return bool
*/
public function hasAddressBook($addressbookid) {
return count($this->getAddressBook($addressbookid)) > 0;
}
/**
* Returns the number of contacts in an address book.
* Implementations can choose to override this method to either
* get the result more effectively or to return null if the backend
* cannot determine the number.
*
* @param string $addressbookid
* @return integer|null
*/
public function numContacts($addressbookid) {
return count($this->getContacts($addressbookid));
}
/**
* Returns the list of addressbooks for a specific user.
*
* The returned arrays MUST contain a unique 'id' for the
* backend and a 'displayname', and it MAY contain a
* 'description'.
*
* @param string $principaluri
* @return array
public function getAddressBooksForUser($userid) {
}
*/
/**
* Get an addressbook's properties
*
* The returned array MUST contain 'displayname' and an integer 'permissions'
* value using there ownCloud CRUDS constants (which MUST be at least
* \OCP\PERMISSION_READ).
* Currently the only ones supported are 'displayname' and
* 'description', but backends can implement additional.
*
* @param string $addressbookid
* @return array $properties
*/
public abstract function getAddressBook($addressbookid);
/**
* Updates an addressbook's properties
*
* The $properties array contains the changes to be made.
*
* Currently the only ones supported are 'displayname' and
* 'description', but backends can implement additional.
*
* @param string $addressbookid
* @param array $properties
* @return bool
public function updateAddressBook($addressbookid, array $properties) {
}
*/
/**
* Creates a new address book
*
* Currently the only ones supported are 'displayname' and
* 'description', but backends can implement additional.
* 'displayname' MUST be present.
*
* @param array $properties
* @return string|false The ID if the newly created AddressBook or false on error.
public function createAddressBook(array $properties) {
}
*/
/**
* Deletes an entire addressbook and all its contents
*
* @param string $addressbookid
* @return bool
public function deleteAddressBook($addressbookid) {
}
*/
/**
* @brief Get the last modification time for an address book.
*
* Must return a UNIX time stamp or null if the backend
* doesn't support it.
*
* TODO: Implement default methods get/set for backends that
* don't support.
* @param string $addressbookid
* @returns int | null
*/
public function lastModifiedAddressBook($addressbookid) {
}
/**
* Returns all contacts for a specific addressbook id.
*
* The returned array MUST contain the unique ID of the contact mapped to 'id', a
* displayname mapped to 'displayname' and an integer 'permissions' value using there
* ownCloud CRUDS constants (which MUST be at least \OCP\PERMISSION_READ), and SHOULD
* contain the properties of the contact formatted as a vCard 3.0
* https://tools.ietf.org/html/rfc2426 mapped to 'carddata' or as an
* \OCA\Contacts\VObject\VCard object mapped to 'vcard'.
*
* Example:
*
* array(
* 0 => array('id' => '4e111fef5df', 'permissions' => 1, 'displayname' => 'John Q. Public', 'vcard' => $object),
* 1 => array('id' => 'bbcca2d1535', 'permissions' => 32, 'displayname' => 'Jane Doe', 'carddata' => $data)
* );
*
* For contacts that contain loads of data, the 'carddata' or 'vcard' MAY be omitted
* as it can be fetched later.
*
* TODO: Some sort of ETag?
*
* @param string $addressbookid
* @param bool $omitdata Don't fetch the entire carddata or vcard.
* @return array
*/
public abstract function getContacts($addressbookid, $limit = null, $offset = null, $omitdata = false);
/**
* Returns a specfic contact.
*
* Same as getContacts except that either 'carddata' or 'vcard' is mandatory.
*
* @param string $addressbookid
* @param mixed $id
* @return array|bool
*/
public abstract function getContact($addressbookid, $id);
/**
* Creates a new contact
*
* @param string $addressbookid
* @param string $carddata
* @return string|bool The identifier for the new contact or false on error.
public function createContact($addressbookid, $carddata) {
}
*/
/**
* Updates a contact
*
* @param string $addressbookid
* @param mixed $id
* @param string $carddata
* @return bool
public function updateContact($addressbookid, $id, $carddata) {
}
*/
/**
* Deletes a contact
*
* @param string $addressbookid
* @param mixed $id
* @return bool
public function deleteContact($addressbookid, $id) {
}
*/
/**
* @brief Get the last modification time for a contact.
*
* Must return a UNIX time stamp or null if the backend
* doesn't support it.
*
* @param string $addressbookid
* @param mixed $id
* @returns int | null
*/
public function lastModifiedContact($addressbookid, $id) {
}
}

724
lib/backend/database.php Normal file
View File

@ -0,0 +1,724 @@
<?php
/**
* ownCloud - Database backend for Contacts
*
* @author Thomas Tanghus
* @copyright 2013 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
* 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/>.
*
*/
namespace OCA\Contacts\Backend;
use OCA\Contacts\Contact;
use OCA\Contacts\VObject\VCard;
use Sabre\VObject\Reader;
/**
* Subclass this class for Cantacts backends
*/
class Database extends AbstractBackend {
public $name = 'local';
static private $preparedQueries = array();
/**
* Sets up the backend
*
* @param string $addressBooksTableName
* @param string $cardsTableName
*/
public function __construct(
$userid = null,
$addressBooksTableName = '*PREFIX*contacts_addressbooks',
$cardsTableName = '*PREFIX*contacts_cards',
$indexTableName = '*PREFIX*contacts_cards_properties'
) {
$this->userid = $userid ? $userid : \OCP\User::getUser();
$this->addressBooksTableName = $addressBooksTableName;
$this->cardsTableName = $cardsTableName;
$this->indexTableName = $indexTableName;
$this->addressbooks = array();
}
/**
* Returns the list of addressbooks for a specific user.
*
* TODO: Create default if none exists.
*
* @param string $principaluri
* @return array
*/
public function getAddressBooksForUser($userid = null) {
$userid = $userid ? $userid : $this->userid;
try {
if(!isset(self::$preparedQueries['addressbooksforuser'])) {
$sql = 'SELECT `id`, `displayname`, `description`, `ctag` AS `lastmodified`, `userid` AS `owner`, `uri` FROM `'
. $this->addressBooksTableName
. '` WHERE `userid` = ? ORDER BY `displayname`';
self::$preparedQueries['addressbooksforuser'] = \OCP\DB::prepare($sql);
}
$result = self::$preparedQueries['addressbooksforuser']->execute(array($userid));
if (\OC_DB::isError($result)) {
\OCP\Util::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
return $this->addressbooks;
}
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.' exception: ' . $e->getMessage(), \OCP\Util::ERROR);
return $this->addressbooks;
}
while( $row = $result->fetchRow()) {
$row['permissions'] = \OCP\PERMISSION_ALL;
$this->addressbooks[$row['id']] = $row;
}
return $this->addressbooks;
}
public function getAddressBook($addressbookid) {
//\OCP\Util::writeLog('contacts', __METHOD__.' id: '
// . $addressbookid, \OCP\Util::DEBUG);
if($this->addressbooks && isset($this->addressbooks[$addressbookid])) {
//print(__METHOD__ . ' ' . __LINE__ .' addressBookInfo: ' . print_r($this->addressbooks[$addressbookid], true));
return $this->addressbooks[$addressbookid];
}
// Hmm, not found. Lets query the db.
try {
$query = 'SELECT `id`, `displayname`, `description`, `userid` AS `owner`, `ctag` AS `lastmodified`, `uri` FROM `'
. $this->addressBooksTableName
. '` WHERE `id` = ?';
if(!isset(self::$preparedQueries['getaddressbook'])) {
self::$preparedQueries['getaddressbook'] = \OCP\DB::prepare($query);
}
$result = self::$preparedQueries['getaddressbook']->execute(array($addressbookid));
if (\OC_DB::isError($result)) {
\OCP\Util::write('contacts', __METHOD__. 'DB error: '
. \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
return array();
}
$row = $result->fetchRow();
$row['permissions'] = \OCP\PERMISSION_ALL;
return $row;
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.' exception: '
. $e->getMessage(), \OCP\Util::ERROR);
return array();
}
return array();
}
public function hasAddressBook($addressbookid) {
if($this->addressbooks && isset($this->addressbooks[$addressbookid])) {
return true;
}
return count($this->getAddressBook($addressbookid)) > 0;
}
/**
* Updates an addressbook's properties
*
* @param string $addressbookid
* @param array $changes
* @return bool
*/
public function updateAddressBook($addressbookid, array $changes) {
if(count($changes) === 0) {
return false;
}
$query = 'UPDATE `' . $this->addressBooksTableName . '` SET ';
$updates = array();
if(isset($changes['displayname'])) {
$query .= '`displayname` = ?, ';
$updates[] = $changes['displayname'];
if($this->addressbooks && isset($this->addressbooks[$addressbookid])) {
$this->addressbooks[$addressbookid]['displayname'] = $changes['displayname'];
}
}
if(isset($changes['description'])) {
$query .= '`description` = ?, ';
$updates[] = $changes['description'];
if($this->addressbooks && isset($this->addressbooks[$addressbookid])) {
$this->addressbooks[$addressbookid]['description'] = $changes['description'];
}
}
$query .= '`ctag` = `ctag` + 1 WHERE `id` = ?';
$updates[] = $addressbookid;
try {
$stmt = \OCP\DB::prepare($query);
$result = $stmt->execute($updates);
if (\OC_DB::isError($result)) {
\OC_Log::write('contacts',
__METHOD__. 'DB error: '
. \OC_DB::getErrorMessage($result), \OC_Log::ERROR);
return false;
}
} catch(Exception $e) {
\OCP\Util::writeLog('contacts',
__METHOD__ . ', exception: '
. $e->getMessage(), \OCP\Util::ERROR);
return false;
}
return true;
}
/**
* Creates a new address book
*
* Supported properties are 'displayname', 'description' and 'uri'.
* 'uri' is supported to allow to add from CardDAV requests, and MUST
* be used for the 'uri' database field if present.
* 'displayname' MUST be present.
*
* @param array $properties
* @return string|false The ID if the newly created AddressBook or false on error.
*/
public function createAddressBook(array $properties, $userid = null) {
$userid = $userid ? $userid : $this->userid;
if(count($properties) === 0 || !isset($properties['displayname'])) {
return false;
}
$query = 'INSERT INTO `' . $this->addressBooksTableName . '` '
. '(`userid`,`displayname`,`uri`,`description`,`ctag`) VALUES(?,?,?,?,?)';
$updates = array($userid, $properties['displayname']);
$updates[] = isset($properties['uri'])
? $properties['uri']
: $this->createAddressBookURI($properties['displayname']);
$updates[] = isset($properties['description']) ? $properties['description'] : '';
$ctag = time();
$updates[] = $ctag;
try {
if(!isset(self::$preparedQueries['createaddressbook'])) {
self::$preparedQueries['createaddressbook'] = \OCP\DB::prepare($query);
}
$result = self::$preparedQueries['createaddressbook']->execute($updates);
if (\OC_DB::isError($result)) {
\OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OC_Log::ERROR);
return false;
}
} catch(Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__ . ', exception: ' . $e->getMessage(), \OCP\Util::ERROR);
return false;
}
$newid = \OCP\DB::insertid($this->addressBooksTableName);
if($this->addressbooks) {
$updates['id'] = $newid;
$updates['ctag'] = $ctag;
$updates['lastmodified'] = $ctag;
$this->addressbooks[$addressbookid] = $updates;
}
return $newid;
}
/**
* Deletes an entire addressbook and all its contents
*
* @param string $addressbookid
* @return bool
*/
public function deleteAddressBook($addressbookid) {
\OC_Hook::emit('OCA\Contacts', 'pre_deleteAddressBook',
array('id' => $addressbookid)
);
// Clean up sharing
\OCP\Share::unshareAll('addressbook', $addressbookid);
// Get all contact ids for this address book
$ids = array();
$result = null;
$stmt = \OCP\DB::prepare('SELECT `id` FROM `' . $this->cardsTableName . '`'
. ' WHERE `addressbookid` = ?');
try {
$result = $stmt->execute(array($addressbookid));
if (\OC_DB::isError($result)) {
\OCP\Util::writeLog('contacts', __METHOD__. 'DB error: '
. \OC_DB::getErrorMessage($result), \OC_Log::ERROR);
return;
}
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.
', exception: ' . $e->getMessage(), \OCP\Util::ERROR);
return;
}
if(!is_null($result)) {
while($id = $result->fetchOne()) {
$ids[] = $id;
}
}
// Purge contact property indexes
$stmt = \OCP\DB::prepare('DELETE FROM `' . $this->indexTableName
.'` WHERE `contactid` IN ('.str_repeat('?,', count($ids)-1).'?)');
try {
$stmt->execute($ids);
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.
', exception: ' . $e->getMessage(), \OCP\Util::ERROR);
}
// Purge categories
$catctrl = new \OC_VCategories('contact');
$catctrl->purgeObjects($ids);
if(!isset(self::$preparedQueries['deleteaddressbookcontacts'])) {
self::$preparedQueries['deleteaddressbookcontacts'] =
\OCP\DB::prepare('DELETE FROM `' . $this->cardsTableName
. '` WHERE `addressbookid` = ?');
}
try {
self::$preparedQueries['deleteaddressbookcontacts']
->execute(array($addressbookid));
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.
', exception: ' . $e->getMessage(), \OCP\Util::ERROR);
return false;
}
if(!isset(self::$preparedQueries['deleteaddressbook'])) {
self::$preparedQueries['deleteaddressbook'] =
\OCP\DB::prepare('DELETE FROM `'
. $this->addressBooksTableName . '` WHERE `id` = ?');
}
try {
self::$preparedQueries['deleteaddressbook']
->execute(array($addressbookid));
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.
', exception: ' . $e->getMessage(), \OCP\Util::ERROR);
return false;
}
if($this->addressbooks && isset($this->addressbooks[$addressbookid])) {
unset($this->addressbooks[$addressbookid]);
}
return true;
}
/**
* @brief Updates ctag for addressbook
* @param integer $id
* @return boolean
*/
public function touchAddressBook($id) {
$query = 'UPDATE `' . $this->addressBooksTableName
. '` SET `ctag` = `ctag` + 1 WHERE `id` = ?';
if(!isset(self::$preparedQueries['touchaddressbook'])) {
self::$preparedQueries['touchaddressbook'] = \OCP\DB::prepare($query);
}
self::$preparedQueries['touchaddressbook']->execute(array($id));
return true;
}
public function lastModifiedAddressBook($addressbookid) {
if($this->addressbooks && isset($this->addressbooks[$addressbookid])) {
return $this->addressbooks[$addressbookid]['lastmodified'];
}
$sql = 'SELECT MAX(`lastmodified`) FROM `' . $this->cardsTableName . '`, `' . $this->addressBooksTableName . '` ' .
'WHERE `' . $this->cardsTableName . '`.`addressbookid` = `*PREFIX*contacts_addressbooks`.`id` AND ' .
'`' . $this->addressBooksTableName . '`.`userid` = ? AND `' . $this->addressBooksTableName . '`.`id` = ?';
if(!isset(self::$preparedQueries['lastmodifiedaddressbook'])) {
self::$preparedQueries['lastmodifiedaddressbook'] = \OCP\DB::prepare($sql);
}
$result = self::$preparedQueries['lastmodifiedaddressbook']->execute(array($this->userid, $addressbookid));
if (\OC_DB::isError($result)) {
\OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OC_Log::ERROR);
return null;
}
return $result->fetchOne();
}
/**
* Returns the number of contacts in a specific address book.
*
* @param string $addressbookid
* @param bool $omitdata Don't fetch the entire carddata or vcard.
* @return array
*/
public function numContacts($addressbookid) {
$query = 'SELECT COUNT(*) AS `count` FROM `' . $this->cardsTableName . '` WHERE '
. '`addressbookid` = ?';
if(!isset(self::$preparedQueries['count'])) {
self::$preparedQueries['count'] = \OCP\DB::prepare($query);
}
$result = self::$preparedQueries['count']->execute(array($addressbookid));
if (\OC_DB::isError($result)) {
\OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OC_Log::ERROR);
return null;
}
return (int)$result->fetchOne();
}
/**
* Returns all contacts for a specific addressbook id.
*
* @param string $addressbookid
* @param bool $omitdata Don't fetch the entire carddata or vcard.
* @return array
*/
public function getContacts($addressbookid, $limit = null, $offset = null, $omitdata = false) {
//\OCP\Util::writeLog('contacts', __METHOD__.' addressbookid: ' . $addressbookid, \OCP\Util::DEBUG);
$cards = array();
try {
$qfields = $omitdata ? '`id`, `fullname` AS `displayname`' : '*';
$query = 'SELECT ' . $qfields . ' FROM `' . $this->cardsTableName
. '` WHERE `addressbookid` = ? ORDER BY `fullname`';
$stmt = \OCP\DB::prepare($query, $limit, $offset);
$result = $stmt->execute(array($addressbookid));
if (\OC_DB::isError($result)) {
\OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
return $cards;
}
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR);
return $cards;
}
if(!is_null($result)) {
while($row = $result->fetchRow()) {
$row['permissions'] = \OCP\PERMISSION_ALL;
$cards[] = $row;
}
}
return $cards;
}
/**
* Returns a specific contact.
*
* The $id for Database and Shared backends can be an array containing
* either 'id' or 'uri' to be able to play seamlessly with the
* CardDAV backend.
* FIXME: $addressbookid isn't used in the query, so there's no access control.
* OTOH the groups backend - OC_VCategories - doesn't no about parent collections
* only object IDs. Hmm.
* I could make a hack and add an optional, not documented 'nostrict' argument
* so it doesn't look for addressbookid.
*
* @param string $addressbookid
* @param mixed $id Contact ID
* @return array|false
*/
public function getContact($addressbookid, $id, $noCollection = false) {
//\OCP\Util::writeLog('contacts', __METHOD__.' identifier: ' . $addressbookid . ' ' . $id['uri'], \OCP\Util::DEBUG);
$where_query = '`id` = ?';
if(is_array($id)) {
$where_query = '';
if(isset($id['id'])) {
$id = $id['id'];
} elseif(isset($id['uri'])) {
$where_query = '`uri` = ?';
$id = $id['uri'];
} else {
throw new \Exception(
__METHOD__ . ' If second argument is an array, either \'id\' or \'uri\' has to be set.'
);
return false;
}
}
$ids = array($id);
if(!$noCollection) {
$where_query .= ' AND `addressbookid` = ?';
$ids[] = $addressbookid;
}
try {
$query = 'SELECT `id`, `carddata`, `lastmodified`, `fullname` AS `displayname` FROM `'
. $this->cardsTableName . '` WHERE ' . $where_query;
$stmt = \OCP\DB::prepare($query);
$result = $stmt->execute($ids);
if (\OC_DB::isError($result)) {
\OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
return false;
}
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::writeLog('contacts', __METHOD__.', id: '. $id, \OCP\Util::DEBUG);
return false;
}
$row = $result->fetchRow();
$row['permissions'] = \OCP\PERMISSION_ALL;
return $row;
}
public function hasContact($addressbookid, $id) {
return $this->getContact($addressbookid, $id) !== false;
}
/**
* Creates a new contact
*
* In the Database and Shared backends contact be either a Contact object or a string
* with carddata to be able to play seamlessly with the CardDAV backend.
* If this method is called by the CardDAV backend, the carddata is already validated.
* NOTE: It's assumed that this method is called either from the CardDAV backend, the
* import script, or from the ownCloud web UI in which case either the uri parameter is
* set, or the contact has a UID. If neither is set, it will fail.
*
* @param string $addressbookid
* @param mixed $contact
* @return string|bool The identifier for the new contact or false on error.
*/
public function createContact($addressbookid, $contact, $uri = null) {
if(!$contact instanceof Contact) {
try {
$contact = Reader::read($contact);
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR);
return false;
}
}
try {
$contact->validate(VCard::REPAIR|VCard::UPGRADE);
} catch (\Exception $e) {
OCP\Util::writeLog('contacts', __METHOD__ . ' ' .
'Error validating vcard: ' . $e->getMessage(), \OCP\Util::ERROR);
return false;
}
$uri = is_null($uri) ? $contact->UID . '.vcf' : $uri;
$now = new \DateTime;
$contact->REV = $now->format(\DateTime::W3C);
$appinfo = \OCP\App::getAppInfo('contacts');
$appversion = \OCP\App::getAppVersion('contacts');
$prodid = '-//ownCloud//NONSGML '.$appinfo['name'].' '.$appversion.'//EN';
$contact->PRODID = $prodid;
$data = $contact->serialize();
if(!isset(self::$preparedQueries['createcontact'])) {
self::$preparedQueries['createcontact'] = \OCP\DB::prepare('INSERT INTO `'
. $this->cardsTableName
. '` (`addressbookid`,`fullname`,`carddata`,`uri`,`lastmodified`) VALUES(?,?,?,?,?)' );
}
try {
$result = self::$preparedQueries['createcontact']
->execute(
array(
$addressbookid,
(string)$contact->FN,
$contact->serialize(),
$uri,
time()
)
);
if (\OC_DB::isError($result)) {
\OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
return false;
}
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR);
return false;
}
$newid = \OCP\DB::insertid($this->cardsTableName);
$this->touchAddressBook($addressbookid);
\OC_Hook::emit('OCA\Contacts', 'post_createContact',
array('id' => $newid, 'parent' => $addressbookid, 'contact' => $contact)
);
return (string)$newid;
}
/**
* Updates a contact
*
* @param string $addressbookid
* @param mixed $id Contact ID
* @param mixed $contact
* @see getContact
* @return bool
*/
public function updateContact($addressbookid, $id, $contact, $noCollection = false) {
if(!$contact instanceof Contact) {
try {
$contact = Reader::read($contact);
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR);
return false;
}
}
$where_query = '`id` = ?';
if(is_array($id)) {
$where_query = '';
if(isset($id['id'])) {
$id = $id['id'];
$qname = 'createcontactbyid';
} elseif(isset($id['uri'])) {
$where_query = '`id` = ?';
$id = $id['uri'];
$qname = 'createcontactbyuri';
} else {
throw new Exception(
__METHOD__ . ' If second argument is an array, either \'id\' or \'uri\' has to be set.'
);
}
} else {
$qname = 'createcontactbyid';
}
$now = new \DateTime;
$contact->REV = $now->format(\DateTime::W3C);
$data = $contact->serialize();
$updates = array($contact->FN, $data, time(), $id);
if(!$noCollection) {
$where_query .= ' AND `addressbookid` = ?';
$updates[] = $addressbookid;
}
$query = 'UPDATE `' . $this->cardsTableName
. '` SET `fullname` = ?,`carddata` = ?, `lastmodified` = ? WHERE ' . $where_query;
if(!isset(self::$preparedQueries[$qname])) {
self::$preparedQueries[$qname] = \OCP\DB::prepare($query);
}
try {
$result = self::$preparedQueries[$qname]->execute($updates);
if (\OC_DB::isError($result)) {
\OCP\Util::writeLog('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
return false;
}
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.', exception: '
. $e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::writeLog('contacts', __METHOD__.', id' . $id, \OCP\Util::DEBUG);
return false;
}
$this->touchAddressBook($addressbookid);
\OC_Hook::emit('OCA\Contacts', 'post_updateContact',
array('id' => $id, 'parent' => $addressbookid, 'contact' => $contact)
);
return true;
}
/**
* Deletes a contact
*
* @param string $addressbookid
* @param string $id
* @see getContact
* @return bool
*/
public function deleteContact($addressbookid, $id) {
$where_query = '`id` = ?';
if(is_array($id)) {
$where_query = '';
if(isset($id['id'])) {
$id = $id['id'];
$qname = 'deletecontactsbyid';
} elseif(isset($id['uri'])) {
$where_query = '`id` = ?';
$id = $id['uri'];
$qname = 'deletecontactsbyuri';
} else {
throw new Exception(
__METHOD__ . ' If second argument is an array, either \'id\' or \'uri\' has to be set.'
);
}
} else {
$qname = 'deletecontactsbyid';
}
\OC_Hook::emit('OCA\Contacts', 'pre_deleteContact',
array('id' => $id)
);
if(!isset(self::$preparedQueries[$qname])) {
self::$preparedQueries[$qname] = \OCP\DB::prepare('DELETE FROM `'
. $this->cardsTableName
. '` WHERE ' . $where_query . ' AND `addressbookid` = ?');
}
try {
$result = self::$preparedQueries[$qname]->execute(array($id, $addressbookid));
if (\OC_DB::isError($result)) {
\OCP\Util::writeLog('contacts', __METHOD__. 'DB error: '
. \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
return false;
}
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.
', exception: ' . $e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::writeLog('contacts', __METHOD__.', id: '
. $id, \OCP\Util::DEBUG);
return false;
}
return true;
}
/**
* @brief Get the last modification time for a contact.
*
* Must return a UNIX time stamp or null if the backend
* doesn't support it.
*
* @param string $addressbookid
* @param mixed $id
* @returns int | null
*/
public function lastModifiedContact($addressbookid, $id) {
$contact = $this->getContact($addressbookid, $id);
return ($contact ? $contact['lastmodified'] : null);
}
private function createAddressBookURI($displayname, $userid = null) {
$userid = $userid ? $userid : \OCP\User::getUser();
$name = str_replace(' ', '_', strtolower($displayname));
try {
$stmt = \OCP\DB::prepare('SELECT `uri` FROM `' . $this->addressBooksTableName . '` WHERE `userid` = ? ');
$result = $stmt->execute(array($userid));
if (\OC_DB::isError($result)) {
\OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OC_Log::ERROR);
return $name;
}
} catch(Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__ . ' exception: ' . $e->getMessage(), \OCP\Util::ERROR);
return $name;
}
$uris = array();
while($row = $result->fetchRow()) {
$uris[] = $row['uri'];
}
$newname = $name;
$i = 1;
while(in_array($newname, $uris)) {
$newname = $name.$i;
$i = $i + 1;
}
return $newname;
}
}

124
lib/backend/shared.php Normal file
View File

@ -0,0 +1,124 @@
<?php
/**
* ownCloud - Backend for Shared contacts
*
* @author Thomas Tanghus
* @copyright 2013 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
* 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/>.
*
*/
namespace OCA\Contacts\Backend;
use OCA\Contacts;
/**
* Subclass this class for Cantacts backends
*/
class Shared extends Database {
public $name = 'shared';
public $addressbooks = array();
/**
* Returns the list of addressbooks for a specific user.
*
* @param string $principaluri
* @return array
*/
public function getAddressBooksForUser($userid = null) {
$userid = $userid ? $userid : $this->userid;
$this->addressbooks = \OCP\Share::getItemsSharedWith(
'addressbook',
Contacts\Share_Backend_Addressbook::FORMAT_ADDRESSBOOKS
);
return $this->addressbooks;
}
/**
* Returns a specific address book.
*
* @param string $addressbookid
* @param mixed $id Contact ID
* @return mixed
*/
public function getAddressBook($addressbookid) {
$addressbook = \OCP\Share::getItemSharedWithBySource(
'addressbook',
$addressbookid,
Contacts\Share_Backend_Addressbook::FORMAT_ADDRESSBOOKS
);
// Not sure if I'm doing it wrongly, or if its supposed to return
// the info in an array?
return (isset($addressbook['permissions']) ? $addressbook : $addressbook[0]);
}
/**
* Returns all contacts for a specific addressbook id.
*
* TODO: Check for parent permissions
*
* @param string $addressbookid
* @param bool $omitdata Don't fetch the entire carddata or vcard.
* @return array
*/
public function getContacts($addressbookid, $limit = null, $offset = null, $omitdata = false) {
//\OCP\Util::writeLog('contacts', __METHOD__.' addressbookid: '
// . $addressbookid, \OCP\Util::DEBUG);
$addressbook = \OCP\Share::getItemSharedWithBySource(
'addressbook',
$addressbookid,
Contacts\Share_Backend_Addressbook::FORMAT_ADDRESSBOOKS,
null, // parameters
true // includeCollection
);
\OCP\Util::writeLog('contacts', __METHOD__.' shared: '
. print_r($addressbook, true), \OCP\Util::DEBUG);
$addressbook = $this->getAddressBook($addressbookid);
$permissions = $addressbook['permissions'];
$cards = array();
try {
$qfields = $omitdata ? '`id`, `fullname` AS `displayname`, `lastmodified`' : '*';
$query = 'SELECT ' . $qfields . ' FROM `' . $this->cardsTableName
. '` WHERE `addressbookid` = ? ORDER BY `fullname`';
$stmt = \OCP\DB::prepare($query, $limit, $offset);
$result = $stmt->execute(array($addressbookid));
if (\OC_DB::isError($result)) {
\OC_Log::write('contacts', __METHOD__. 'DB error: '
. \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
return $cards;
}
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.', exception: '
. $e->getMessage(), \OCP\Util::ERROR);
return $cards;
}
if(!is_null($result)) {
while( $row = $result->fetchRow()) {
$row['permissions'] = $permissions;
$cards[] = $row;
}
}
return $cards;
}
}

View File

@ -20,14 +20,18 @@
*
*/
namespace OCA\Contacts\CardDAV;
use OCA\Contacts;
/**
* This class overrides __construct to get access to $addressBookInfo and
* $carddavBackend, Sabre_CardDAV_AddressBook::getACL() to return read/write
* permissions based on user and shared state and it overrides
* Sabre_CardDAV_AddressBook::getChild() and Sabre_CardDAV_AddressBook::getChildren()
* to instantiate OC_Connector_Sabre_CardDAV_Cards.
* to instantiate \OCA\Contacts\CardDAV\Cards.
*/
class OC_Connector_Sabre_CardDAV_AddressBook extends Sabre_CardDAV_AddressBook {
class AddressBook extends \Sabre_CardDAV_AddressBook {
/**
* CardDAV backend
@ -43,7 +47,7 @@ class OC_Connector_Sabre_CardDAV_AddressBook extends Sabre_CardDAV_AddressBook {
* @param array $addressBookInfo
*/
public function __construct(
Sabre_CardDAV_Backend_Abstract $carddavBackend,
\Sabre_CardDAV_Backend_Abstract $carddavBackend,
array $addressBookInfo) {
$this->carddavBackend = $carddavBackend;
@ -70,41 +74,42 @@ class OC_Connector_Sabre_CardDAV_AddressBook extends Sabre_CardDAV_AddressBook {
$writeprincipal = $this->getOwner();
$createprincipal = $this->getOwner();
$deleteprincipal = $this->getOwner();
$uid = OCA\Contacts\Addressbook::extractUserID($this->getOwner());
$uid = $this->carddavBackend->userIDByPrincipal($this->getOwner());
$readWriteACL = array(
array(
'privilege' => '{DAV:}read',
'principal' => 'principals/' . OCP\User::getUser(),
'principal' => 'principals/' . \OCP\User::getUser(),
'protected' => true,
),
array(
'privilege' => '{DAV:}write',
'principal' => 'principals/' . OCP\User::getUser(),
'principal' => 'principals/' . \OCP\User::getUser(),
'protected' => true,
),
);
if($uid != OCP\USER::getUser()) {
$sharedAddressbook = OCP\Share::getItemSharedWithBySource('addressbook', $this->addressBookInfo['id']);
if($uid !== \OCP\User::getUser()) {
list($backendName, $id) = explode('::', $this->addressBookInfo['id']);
$sharedAddressbook = \OCP\Share::getItemSharedWithBySource('addressbook', $id);
if($sharedAddressbook) {
if(($sharedAddressbook['permissions'] & OCP\PERMISSION_CREATE)
&& ($sharedAddressbook['permissions'] & OCP\PERMISSION_UPDATE)
&& ($sharedAddressbook['permissions'] & OCP\PERMISSION_DELETE)
if(($sharedAddressbook['permissions'] & \OCP\PERMISSION_CREATE)
&& ($sharedAddressbook['permissions'] & \OCP\PERMISSION_UPDATE)
&& ($sharedAddressbook['permissions'] & \OCP\PERMISSION_DELETE)
) {
return $readWriteACL;
}
if ($sharedAddressbook['permissions'] & OCP\PERMISSION_CREATE) {
$createprincipal = 'principals/' . OCP\USER::getUser();
if ($sharedAddressbook['permissions'] & \OCP\PERMISSION_CREATE) {
$createprincipal = 'principals/' . \OCP\User::getUser();
}
if ($sharedAddressbook['permissions'] & OCP\PERMISSION_READ) {
$readprincipal = 'principals/' . OCP\USER::getUser();
if ($sharedAddressbook['permissions'] & \OCP\PERMISSION_READ) {
$readprincipal = 'principals/' . \OCP\User::getUser();
}
if ($sharedAddressbook['permissions'] & OCP\PERMISSION_UPDATE) {
$writeprincipal = 'principals/' . OCP\USER::getUser();
if ($sharedAddressbook['permissions'] & \OCP\PERMISSION_UPDATE) {
$writeprincipal = 'principals/' . \OCP\User::getUser();
}
if ($sharedAddressbook['permissions'] & OCP\PERMISSION_DELETE) {
$deleteprincipal = 'principals/' . OCP\USER::getUser();
if ($sharedAddressbook['permissions'] & \OCP\PERMISSION_DELETE) {
$deleteprincipal = 'principals/' . \OCP\User::getUser();
}
}
} else {
@ -198,8 +203,10 @@ class OC_Connector_Sabre_CardDAV_AddressBook extends Sabre_CardDAV_AddressBook {
public function getChild($name) {
$obj = $this->carddavBackend->getCard($this->addressBookInfo['id'],$name);
if (!$obj) throw new Sabre_DAV_Exception_NotFound('Card not found');
return new OC_Connector_Sabre_CardDAV_Card($this->carddavBackend,$this->addressBookInfo,$obj);
if (!$obj) {
throw new \Sabre_DAV_Exception_NotFound('Card not found');
}
return new Card($this->carddavBackend,$this->addressBookInfo,$obj);
}
@ -213,7 +220,7 @@ class OC_Connector_Sabre_CardDAV_AddressBook extends Sabre_CardDAV_AddressBook {
$objs = $this->carddavBackend->getCards($this->addressBookInfo['id']);
$children = array();
foreach($objs as $obj) {
$children[] = new OC_Connector_Sabre_CardDAV_Card($this->carddavBackend,$this->addressBookInfo,$obj);
$children[] = new Card($this->carddavBackend,$this->addressBookInfo,$obj);
}
return $children;

View File

@ -20,11 +20,13 @@
*
*/
namespace OCA\Contacts\CardDAV;
/**
* This class overrides Sabre_CardDAV_AddressBookRoot::getChildForPrincipal()
* to instantiate OC_Connector_CardDAV_UserAddressBooks.
*/
class OC_Connector_Sabre_CardDAV_AddressBookRoot extends Sabre_CardDAV_AddressBookRoot {
class AddressBookRoot extends \Sabre_CardDAV_AddressBookRoot {
/**
* This method returns a node for a principal.
@ -38,7 +40,7 @@ class OC_Connector_Sabre_CardDAV_AddressBookRoot extends Sabre_CardDAV_AddressBo
*/
public function getChildForPrincipal(array $principal) {
return new OC_Connector_Sabre_CardDAV_UserAddressBooks($this->carddavBackend, $principal['uri']);
return new UserAddressBooks($this->carddavBackend, $principal['uri']);
}

258
lib/carddav/backend.php Normal file
View File

@ -0,0 +1,258 @@
<?php
/**
* ownCloud - Addressbook
*
* @author Jakob Sack
* @copyright 2011 Jakob Sack mail@jakobsack.de
*
* 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/>.
*
*/
namespace OCA\Contacts\CardDAV;
use OCA\Contacts;
class Backend extends \Sabre_CardDAV_Backend_Abstract {
public function __construct($backends) {
//\OCP\Util::writeLog('contacts', __METHOD__, \OCP\Util::DEBUG);
$this->backends = $backends;
}
/**
* Returns the list of addressbooks for a specific user.
*
* @param string $principaluri
* @return array
*/
public function getAddressBooksForUser($principaluri) {
$userid = $this->userIDByPrincipal($principaluri);
$userAddressBooks = array();
foreach($this->backends as $backend) {
$addressBooks = $backend->getAddressBooksForUser($userid);
foreach($addressBooks as $addressBook) {
if($addressBook['owner'] != \OCP\USER::getUser()) {
$addressBook['uri'] = $addressBook['uri'] . '_shared_by_' . $addressBook['owner'];
$addressBook['displayname'] = $addressBook['displayname'];
}
$userAddressbooks[] = array(
'id' => $backend->name . '::' . $addressBook['id'],
'uri' => $addressBook['uri'],
'principaluri' => 'principals/'.$addressBook['owner'],
'{DAV:}displayname' => $addressBook['displayname'],
'{' . \Sabre_CardDAV_Plugin::NS_CARDDAV . '}addressbook-description'
=> $addressBook['description'],
'{http://calendarserver.org/ns/}getctag' => $addressBook['lastmodified'],
);
}
}
return $userAddressbooks;
}
/**
* Updates an addressbook's properties
*
* See Sabre_DAV_IProperties for a description of the mutations array, as
* well as the return value.
*
* @param mixed $addressbookid
* @param array $mutations
* @see Sabre_DAV_IProperties::updateProperties
* @return bool|array
*/
public function updateAddressBook($addressbookid, array $mutations) {
$name = null;
$description = null;
$changes = array();
foreach($mutations as $property=>$newvalue) {
switch($property) {
case '{DAV:}displayname' :
$changes['name'] = $newvalue;
break;
case '{' . \Sabre_CardDAV_Plugin::NS_CARDDAV
. '}addressbook-description' :
$changes['description'] = $newvalue;
break;
default :
// If any unsupported values were being updated, we must
// let the entire request fail.
return false;
}
}
list($id, $backend) = $this->getBackendForAddressBook($addressbookid);
return $backend->updateAddressBook($id, $changes);
}
/**
* Creates a new address book
*
* @param string $principaluri
* @param string $uri Just the 'basename' of the url.
* @param array $properties
* @return void
*/
public function createAddressBook($principaluri, $uri, array $properties) {
$properties = array();
$userid = $this->userIDByPrincipal($principaluri);
foreach($properties as $property=>$newvalue) {
switch($property) {
case '{DAV:}displayname' :
$properties['displayname'] = $newvalue;
break;
case '{' . \Sabre_CardDAV_Plugin::NS_CARDDAV
. '}addressbook-description' :
$properties['description'] = $newvalue;
break;
default :
throw new \Sabre_DAV_Exception_BadRequest('Unknown property: '
. $property);
}
}
$properties['uri'] = $uri;
list(,$backend) = $this->getBackendForAddressBook($addressbookid);
$backend->createAddressBook($properties, $userid);
}
/**
* Deletes an entire addressbook and all its contents
*
* @param int $addressbookid
* @return void
*/
public function deleteAddressBook($addressbookid) {
list($id, $backend) = $this->getBackendForAddressBook($addressbookid);
$backend->deleteAddressBook($id);
}
/**
* Returns all cards for a specific addressbook id.
*
* @param mixed $addressbookid
* @return array
*/
public function getCards($addressbookid) {
$contacts = array();
list($id, $backend) = $this->getBackendForAddressBook($addressbookid);
$contacts = $backend->getContacts($id);
$cards = array();
foreach($contacts as $contact) {
//OCP\Util::writeLog('contacts', __METHOD__.', uri: ' . $i['uri'], OCP\Util::DEBUG);
$cards[] = array(
'id' => $contact['id'],
//'carddata' => $i['carddata'],
'size' => strlen($contact['carddata']),
'etag' => '"' . md5($contact['carddata']) . '"',
'uri' => $contact['uri'],
'lastmodified' => $contact['lastmodified'] );
}
return $cards;
}
/**
* Returns a specfic card
*
* @param mixed $addressbookid
* @param string $carduri
* @return array
*/
public function getCard($addressbookid, $carduri) {
\OCP\Util::writeLog('contacts', __METHOD__.' identifier: ' . $carduri . ' ' . print_r($addressbookid, true), \OCP\Util::DEBUG);
list($id, $backend) = $this->getBackendForAddressBook($addressbookid);
$contact = $backend->getContact($id, array('uri' => $carduri));
return ($contact ? $contact : false);
}
/**
* Creates a new card
*
* We don't return an Etag as the carddata can have been modified
* by Plugin::validate()
*
* @see Plugin::validate()
* @param mixed $addressbookid
* @param string $carduri
* @param string $carddata
* @return string|null
*/
public function createCard($addressbookid, $carduri, $carddata) {
list($id, $backend) = $this->getBackendForAddressBook($addressbookid);
$backend->createContact($id, $carddata, $carduri);
}
/**
* Updates a card
*
* @param mixed $addressbookid
* @param string $carduri
* @param string $carddata
* @return null
*/
public function updateCard($addressbookid, $carduri, $carddata) {
list($id, $backend) = $this->getBackendForAddressBook($addressbookid);
$backend->updateContact($id, array('uri' => $carduri,), $carddata);
}
/**
* Deletes a card
*
* @param mixed $addressbookid
* @param string $carduri
* @return bool
*/
public function deleteCard($addressbookid, $carduri) {
list($id, $backend) = $this->getBackendForAddressBook($addressbookid);
return $backend->deleteContact($id);
}
/**
* @brief gets the userid from a principal path
* @return string
*/
public function userIDByPrincipal($principaluri) {
list(, $userid) = \Sabre_DAV_URLUtil::splitPath($principaluri);
return $userid;
}
/**
* Get the backend for an address book
*
* @param mixed $addressbookid
* @return array(string, \OCA\Contacts\Backend\AbstractBackend)
*/
public function getBackendForAddressBook($addressbookid) {
list($backendName, $id) = explode('::', $addressbookid);
foreach($this->backends as $backend) {
if($backend->name === $backendName && $backend->hasAddressBook($id)) {
return array($id, $backend);
}
}
throw new \Sabre_DAV_Exception_NotFound('Backend not found: ' . $addressbookid);
}
}

View File

@ -20,11 +20,15 @@
*
*/
namespace OCA\Contacts\CardDAV;
use OCA\Contacts;
/**
* This class overrides Sabre_CardDAV_Card::getACL()
* to return read/write permissions based on user and shared state.
*/
class OC_Connector_Sabre_CardDAV_Card extends Sabre_CardDAV_Card {
class Card extends \Sabre_CardDAV_Card {
/**
* Array with information about the containing addressbook
@ -40,7 +44,7 @@ class OC_Connector_Sabre_CardDAV_Card extends Sabre_CardDAV_Card {
* @param array $addressBookInfo
* @param array $cardData
*/
public function __construct(Sabre_CardDAV_Backend_Abstract $carddavBackend, array $addressBookInfo, array $cardData) {
public function __construct(\Sabre_CardDAV_Backend_Abstract $carddavBackend, array $addressBookInfo, array $cardData) {
$this->addressBookInfo = $addressBookInfo;
parent::__construct($carddavBackend, $addressBookInfo, $cardData);
@ -63,15 +67,16 @@ class OC_Connector_Sabre_CardDAV_Card extends Sabre_CardDAV_Card {
$readprincipal = $this->getOwner();
$writeprincipal = $this->getOwner();
$uid = OCA\Contacts\Addressbook::extractUserID($this->getOwner());
$uid = $this->carddavBackend->userIDByPrincipal($this->getOwner());
if($uid != OCP\USER::getUser()) {
$sharedAddressbook = OCP\Share::getItemSharedWithBySource('addressbook', $this->addressBookInfo['id']);
if ($sharedAddressbook && ($sharedAddressbook['permissions'] & OCP\PERMISSION_READ)) {
$readprincipal = 'principals/' . OCP\USER::getUser();
if($uid != \OCP\USER::getUser()) {
list($backendName, $id) = explode('::', $this->addressBookInfo['id']);
$sharedAddressbook = \OCP\Share::getItemSharedWithBySource('addressbook', $id);
if ($sharedAddressbook && ($sharedAddressbook['permissions'] & \OCP\PERMISSION_READ)) {
$readprincipal = 'principals/' . \OCP\USER::getUser();
}
if ($sharedAddressbook && ($sharedAddressbook['permissions'] & OCP\PERMISSION_UPDATE)) {
$writeprincipal = 'principals/' . OCP\USER::getUser();
if ($sharedAddressbook && ($sharedAddressbook['permissions'] & \OCP\PERMISSION_UPDATE)) {
$writeprincipal = 'principals/' . \OCP\USER::getUser();
}
}

69
lib/carddav/plugin.php Normal file
View File

@ -0,0 +1,69 @@
<?php
/**
* ownCloud - CardDAV plugin
*
* The CardDAV plugin adds CardDAV functionality to the WebDAV server
*
* @author Thomas Tanghus
* @copyright 2013 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
* 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/>.
*
*/
namespace OCA\Contacts\CardDAV;
use Sabre\VObject;
use OCA\Contacts\VObject\VCard;
/**
* This class overrides Sabre_CardDAV_Plugin::validateVCard() to be able
* to import partially invalid vCards by ignoring invalid lines and to
* validate and upgrade using \OCA\Contacts\VCard.
*/
class Plugin extends \Sabre_CardDAV_Plugin {
/**
* Checks if the submitted vCard data is in fact, valid.
*
* An exception is thrown if it's not.
*
* @param resource|string $data
* @return void
*/
protected function validateVCard(&$data) {
\OCP\Util::writeLog('contacts', __METHOD__, \OCP\Util::DEBUG);
// If it's a stream, we convert it to a string first.
if (is_resource($data)) {
$data = stream_get_contents($data);
}
// Converting the data to unicode, if needed.
$data = \Sabre_DAV_StringUtil::ensureUTF8($data);
try {
$vobj = VObject\Reader::read($data, VObject\Reader::OPTION_IGNORE_INVALID_LINES);
} catch (VObject\ParseException $e) {
throw new \Sabre_DAV_Exception_UnsupportedMediaType('This resource only supports valid vcard data. Parse error: ' . $e->getMessage());
}
if ($vobj->name !== 'VCARD') {
throw new \Sabre_DAV_Exception_UnsupportedMediaType('This collection can only support vcard objects.');
}
$vobj->validate(VCard::REPAIR|VCard::UPGRADE);
$data = $vobj->serialize();
}
}

View File

@ -20,11 +20,13 @@
*
*/
namespace OCA\Contacts\CardDAV;
/**
* This class overrides Sabre_CardDAV_UserAddressBooks::getChildren()
* to instantiate OC_Connector_Sabre_CardDAV_AddressBooks.
* to instantiate \OCA\Contacts\CardDAV\AddressBooks.
*/
class OC_Connector_Sabre_CardDAV_UserAddressBooks extends Sabre_CardDAV_UserAddressBooks {
class UserAddressBooks extends \Sabre_CardDAV_UserAddressBooks {
/**
* Returns a list of addressbooks
@ -36,7 +38,7 @@ class OC_Connector_Sabre_CardDAV_UserAddressBooks extends Sabre_CardDAV_UserAddr
$addressbooks = $this->carddavBackend->getAddressbooksForUser($this->principalUri);
$objs = array();
foreach($addressbooks as $addressbook) {
$objs[] = new OC_Connector_Sabre_CardDAV_AddressBook($this->carddavBackend, $addressbook);
$objs[] = new AddressBook($this->carddavBackend, $addressbook);
}
return $objs;

647
lib/contact.php Normal file
View File

@ -0,0 +1,647 @@
<?php
/**
* ownCloud - Contact object
*
* @author Thomas Tanghus
* @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
* 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/>.
*
*/
namespace OCA\Contacts;
use Sabre\VObject\Property;
/**
* Subclass this class or implement IPIMObject interface for PIM objects
*/
class Contact extends VObject\VCard implements IPIMObject {
const THUMBNAIL_PREFIX = 'contact-thumbnail-';
const THUMBNAIL_SIZE = 28;
/**
* The name of the object type in this case VCARD.
*
* This is used when serializing the object.
*
* @var string
*/
public $name = 'VCARD';
protected $props = array();
/**
* Create a new Contact object
*
* @param AddressBook $parent
* @param AbstractBackend $backend
* @param mixed $data
*/
public function __construct($parent, $backend, $data = null) {
//\OCP\Util::writeLog('contacts', __METHOD__ . ' ' . print_r($data, true), \OCP\Util::DEBUG);
//\OCP\Util::writeLog('contacts', __METHOD__, \OCP\Util::DEBUG);
$this->props['parent'] = $parent;
$this->props['backend'] = $backend;
$this->props['retrieved'] = false;
$this->props['saved'] = false;
if(!is_null($data)) {
if($data instanceof VObject\VCard) {
foreach($data->children as $child) {
$this->add($child);
$this->setRetrieved(true);
}
} elseif(is_array($data)) {
foreach($data as $key => $value) {
switch($key) {
case 'id':
$this->props['id'] = $value;
break;
case 'lastmodified':
$this->props['lastmodified'] = $value;
break;
case 'uri':
$this->props['uri'] = $value;
break;
case 'carddata':
$this->props['carddata'] = $value;
$this->retrieve();
break;
case 'vcard':
$this->props['vcard'] = $value;
$this->retrieve();
break;
case 'displayname':
case 'fullname':
$this->props['displayname'] = $value;
$this->FN = $value;
break;
}
}
}
}
}
/**
* @return array|null
*/
public function getMetaData() {
if(!isset($this->props['displayname'])) {
if(!$this->retrieve()) {
\OCP\Util::writeLog('contacts', __METHOD__.' error reading: '.print_r($this->props, true), \OCP\Util::ERROR);
return null;
}
}
return array(
'id' => $this->getId(),
'displayname' => $this->getDisplayName(),
'permissions' => $this->getPermissions(),
'lastmodified' => $this->lastModified(),
'owner' => $this->getOwner(),
'parent' => $this->getParent()->getId(),
'backend' => $this->getBackend()->name,
);
}
/**
* Get a unique key combined of backend name, address book id and contact id.
*
* @return string
*/
public function combinedKey() {
return $this->getBackend()->name . '::' . $this->getParent()->getId() . '::' . $this->getId();
}
/**
* @return string|null
*/
public function getOwner() {
return $this->props['parent']->getOwner();
}
/**
* @return string|null
*/
public function getId() {
return isset($this->props['id']) ? $this->props['id'] : null;
}
/**
* @return string|null
*/
function getDisplayName() {
return isset($this->props['displayname']) ? $this->props['displayname'] : null;
}
/**
* @return string|null
*/
public function getURI() {
return isset($this->props['uri']) ? $this->props['uri'] : null;
}
/**
* If this object is part of a collection return a reference
* to the parent object, otherwise return null.
* @return IPIMObject|null
*/
function getParent() {
return $this->props['parent'];
}
function getBackend() {
return $this->props['backend'];
}
/** CRUDS permissions (Create, Read, Update, Delete, Share)
*
* @return integer
*/
function getPermissions() {
return $this->props['parent']->getPermissions();
}
/**
* @param integer $permission
* @return bool
*/
function hasPermission($permission) {
return $this->getPermissions() & $permission;
}
/**
* Save the address book data to backend
* FIXME
*
* @param array $data
* @return bool
*/
public function update(array $data) {
foreach($data as $key => $value) {
switch($key) {
case 'displayname':
$this->addressBookInfo['displayname'] = $value;
break;
case 'description':
$this->addressBookInfo['description'] = $value;
break;
}
}
return $this->props['backend']->updateContact(
$this->getParent()->getId(),
$this->getId(),
$this
);
}
/**
* Delete the data from backend
*
* @return bool
*/
public function delete() {
return $this->props['backend']->deleteContact(
$this->getParent()->getId(),
$this->getId()
);
}
/**
* Save the contact data to backend
*
* @return bool
*/
public function save($force = false) {
if($this->isSaved() && !$force) {
\OCP\Util::writeLog('contacts', __METHOD__.' Already saved: ' . print_r($this->props, true), \OCP\Util::DEBUG);
return true;
}
if(isset($this->FN)) {
$this->props['displayname'] = (string)$this->FN;
}
if($this->getId()) {
if($this->props['backend']
->updateContact(
$this->getParent()->getId(),
$this->getId(),
$this->serialize()
)
) {
$this->props['lastmodified'] = time();
$this->setSaved(true);
return true;
} else {
return false;
}
} else {
//print(__METHOD__.' ' . print_r($this->getParent(), true));
$this->props['id'] = $this->props['backend']->createContact(
$this->getParent()->getId(), $this
);
$this->setSaved(true);
return $this->getId() !== false;
}
}
/**
* Get the data from the backend
* FIXME: Clean this up and make sure the logic is OK.
*/
public function retrieve() {
//error_log(__METHOD__);
//\OCP\Util::writeLog('contacts', __METHOD__.' ' . print_r($this->props, true), \OCP\Util::DEBUG);
if($this->isRetrieved()) {
//\OCP\Util::writeLog('contacts', __METHOD__. ' children', \OCP\Util::DEBUG);
return true;
} else {
$data = null;
if(isset($this->props['vcard'])
&& $this->props['vcard'] instanceof VObject\VCard) {
foreach($this->props['vcard']->children() as $child) {
$this->add($child);
if($child->name === 'FN') {
$this->props['displayname']
= strtr($child->value, array('\,' => ',', '\;' => ';', '\\\\' => '\\'));
}
}
$this->setRetrieved(true);
//$this->children = $this->props['vcard']->children();
unset($this->props['vcard']);
return true;
} elseif(!isset($this->props['carddata'])) {
$result = $this->props['backend']->getContact(
$this->getParent()->getId(),
$this->id
);
if($result) {
if(isset($result['vcard'])
&& $result['vcard'] instanceof VObject\VCard) {
foreach($result['vcard']->children() as $child) {
$this->add($child);
}
$this->setRetrieved(true);
return true;
} elseif(isset($result['carddata'])) {
// Save internal values
$data = $result['carddata'];
$this->props['carddata'] = $result['carddata'];
$this->props['lastmodified'] = $result['lastmodified'];
$this->props['displayname'] = $result['displayname'];
$this->props['permissions'] = $result['permissions'];
} else {
\OCP\Util::writeLog('contacts', __METHOD__
. ' Could not get vcard or carddata: '
. $this->getId()
. print_r($result, true), \OCP\Util::DEBUG);
return false;
}
} else {
\OCP\Util::writeLog('contacts', __METHOD__.' Error getting contact: ' . $this->getId(), \OCP\Util::DEBUG);
}
} elseif(isset($this->props['carddata'])) {
$data = $this->props['carddata'];
//error_log(__METHOD__.' data: '.print_r($data, true));
}
try {
$obj = \Sabre\VObject\Reader::read(
$data,
\Sabre\VObject\Reader::OPTION_IGNORE_INVALID_LINES
);
if($obj) {
foreach($obj->children as $child) {
$this->add($child);
}
$this->setRetrieved(true);
} else {
\OCP\Util::writeLog('contacts', __METHOD__.' Error reading: ' . print_r($data, true), \OCP\Util::DEBUG);
return false;
}
} catch (\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__ .
' Error parsing carddata for: ' . $this->getId() . ' ' . $e->getMessage(),
\OCP\Util::ERROR);
return false;
}
}
return true;
}
/**
* Get a property index in the contact by the checksum of its serialized value
*
* @param string $checksum An 8 char m5d checksum.
* @return \Sabre\VObject\Property Property by reference
* @throws An exception with error code 404 if the property is not found.
*/
public function getPropertyIndexByChecksum($checksum) {
$this->retrieve();
$idx = 0;
foreach($this->children as $i => &$property) {
if(substr(md5($property->serialize()), 0, 8) == $checksum ) {
return $idx;
}
$idx += 1;
}
throw new Exception('Property not found', 404);
}
/**
* Get a property by the checksum of its serialized value
*
* @param string $checksum An 8 char m5d checksum.
* @return \Sabre\VObject\Property Property by reference
* @throws An exception with error code 404 if the property is not found.
*/
public function getPropertyByChecksum($checksum) {
$this->retrieve();
foreach($this->children as $i => &$property) {
if(substr(md5($property->serialize()), 0, 8) == $checksum ) {
return $property;
}
}
throw new \Exception('Property not found', 404);
}
/**
* Delete a property by the checksum of its serialized value
* It is up to the caller to call ->save()
*
* @param string $checksum An 8 char m5d checksum.
* @throws @see getPropertyByChecksum
*/
public function unsetPropertyByChecksum($checksum) {
$idx = $this->getPropertyIndexByChecksum($checksum);
unset($this->children[$idx]);
$this->setSaved(false);
}
/**
* Set a property by the checksum of its serialized value
* It is up to the caller to call ->save()
*
* @param string $checksum An 8 char m5d checksum.
* @param string $name Property name
* @param mixed $value
* @param array $parameters
* @throws @see getPropertyByChecksum
* @return string new checksum
*/
public function setPropertyByChecksum($checksum, $name, $value, $parameters=array()) {
// FIXME: Change the debug and bailOut calls
if($checksum === 'new') {
$property = Property::create($name);
$this->add($property);
} else {
$property = $this->getPropertyByChecksum($checksum);
}
switch($name) {
case 'EMAIL':
$value = strtolower($value);
$property->setValue($value);
break;
case 'ADR':
if(is_array($value)) {
$property->setParts($value);
} else {
debug('Saving ADR ' . $value);
$property->setValue($value);
}
break;
case 'IMPP':
if(is_null($parameters) || !isset($parameters['X-SERVICE-TYPE'])) {
bailOut(App::$l10n->t('Missing IM parameter.'));
}
$impp = Utils\Properties::getIMOptions($parameters['X-SERVICE-TYPE']);
if(is_null($impp)) {
bailOut(App::$l10n->t('Unknown IM: '.$parameters['X-SERVICE-TYPE']));
}
$value = $impp['protocol'] . ':' . $value;
$property->setValue($value);
break;
default:
\OCP\Util::writeLog('contacts', __METHOD__.' adding: '.$name. ' ' . $value, \OCP\Util::DEBUG);
$property->setValue($value);
break;
}
$this->setParameters($property, $parameters);
$this->setSaved(false);
return substr(md5($property->serialize()), 0, 8);
}
/**
* Set a property by the property name.
* It is up to the caller to call ->save()
*
* @param string $name Property name
* @param mixed $value
* @param array $parameters
* @return bool
*/
public function setPropertyByName($name, $value, $parameters=array()) {
// TODO: parameters are ignored for now.
switch($name) {
case 'BDAY':
try {
$date = New \DateTime($value);
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts',
__METHOD__.' DateTime exception: ' . $e->getMessage(),
\OCP\Util::ERROR
);
return false;
}
$value = $date->format('Y-m-d');
$this->BDAY = $value;
$this->BDAY->add('VALUE', 'DATE');
//\OCP\Util::writeLog('contacts', __METHOD__.' BDAY: '.$this->BDAY->serialize(), \OCP\Util::DEBUG);
break;
case 'CATEGORIES':
case 'N':
case 'ORG':
$property = $this->select($name);
if(count($property) === 0) {
$property = \Sabre\VObject\Property::create($name);
$this->add($property);
} else {
// Actually no idea why this works
$property = array_shift($property);
}
if(is_array($value)) {
$property->setParts($value);
} else {
$this->{$name} = $value;
}
break;
default:
\OCP\Util::writeLog('contacts', __METHOD__.' adding: '.$name. ' ' . $value, \OCP\Util::DEBUG);
$this->{$name} = $value;
break;
}
$this->setSaved(false);
return true;
}
protected function setParameters($property, $parameters, $reset = false) {
if(!$parameters) {
return;
}
if($reset) {
$property->parameters = array();
}
debug('Setting parameters: ' . print_r($parameters, true));
foreach($parameters as $key => $parameter) {
debug('Adding parameter: ' . $key);
if(is_array($parameter)) {
foreach($parameter as $val) {
if(is_array($val)) {
foreach($val as $val2) {
if(trim($key) && trim($val2)) {
debug('Adding parameter: '.$key.'=>'.print_r($val2, true));
$property->add($key, strip_tags($val2));
}
}
} else {
if(trim($key) && trim($val)) {
debug('Adding parameter: '.$key.'=>'.print_r($val, true));
$property->add($key, strip_tags($val));
}
}
}
} else {
if(trim($key) && trim($parameter)) {
debug('Adding parameter: '.$key.'=>'.print_r($parameter, true));
$property->add($key, strip_tags($parameter));
}
}
}
}
public function lastModified() {
if(!isset($this->props['lastmodified'])) {
$this->retrieve();
}
return isset($this->props['lastmodified'])
? $this->props['lastmodified']
: null;
}
/**
* Merge in data from a multi-dimentional array
*
* NOTE: This is *NOT* tested!
* The data array has this structure:
*
* array(
* 'EMAIL' => array(array('value' => 'johndoe@example.com', 'parameters' = array('TYPE' => array('HOME','VOICE'))))
* );
* @param array $data
*/
public function mergeFromArray(array $data) {
foreach($data as $name => $properties) {
if(in_array($name, array('PHOTO', 'UID'))) {
continue;
}
if(!is_array($properties)) {
\OCP\Util::writeLog('contacts', __METHOD__.' not an array?: ' .$name. ' '.print_r($properties, true), \OCP\Util::DEBUG);
}
if(in_array($name, Utils\Properties::$multi_properties)) {
unset($this->{$name});
}
foreach($properties as $parray) {
//$property = Property::create($name, $parray['value'], $parray['parameters']);
\OCP\Util::writeLog('contacts', __METHOD__.' adding: ' .$name. ' '.print_r($parray['value'], true) . ' ' . print_r($parray['parameters'], true), \OCP\Util::DEBUG);
if(in_array($name, Utils\Properties::$multi_properties)) {
// TODO: wrap in try/catch, check return value
$this->setPropertyByChecksum('new', $name, $parray['value'], $parray['parameters']);
} else {
// TODO: Check return value
if(!isset($this->{$name})) {
$this->setPropertyByName($name, $parray['value'], $parray['parameters']);
}
}
//$this->add($name, $parray['value'], $parray['parameters']);
}
}
$this->setSaved(false);
return true;
}
public function cacheThumbnail(\OC_Image $image = null) {
$key = self::THUMBNAIL_PREFIX . $this->combinedKey();
//\OC_Cache::remove($key);
if(\OC_Cache::hasKey($key) && $image === null) {
return \OC_Cache::get($key);
}
if(is_null($image)) {
$this->retrieve();
$image = new \OC_Image();
if(!isset($this->PHOTO) && !isset($this->LOGO)) {
return false;
}
if(!$image->loadFromBase64((string)$this->PHOTO)) {
if(!$image->loadFromBase64((string)$this->LOGO)) {
return false;
}
}
}
if(!$image->centerCrop()) {
\OCP\Util::writeLog('contacts',
'thumbnail.php. Couldn\'t crop thumbnail for ID ' . $key,
\OCP\Util::ERROR);
return false;
}
if(!$image->resize(self::THUMBNAIL_SIZE)) {
\OCP\Util::writeLog('contacts',
'thumbnail.php. Couldn\'t resize thumbnail for ID ' . $key,
\OCP\Util::ERROR);
return false;
}
// Cache as base64 for around a month
\OC_Cache::set($key, strval($image), 3000000);
\OCP\Util::writeLog('contacts', 'Caching ' . $key, \OCP\Util::DEBUG);
return \OC_Cache::get($key);
}
public function __set($key, $value) {
parent::__set($key, $value);
$this->setSaved(false);
}
public function __unset($key) {
parent::__unset($key);
$this->setSaved(false);
}
protected function setRetrieved($state) {
$this->props['retrieved'] = $state;
}
protected function isRetrieved() {
return $this->props['retrieved'];
}
protected function setSaved($state) {
$this->props['saved'] = $state;
}
protected function isSaved() {
return $this->props['saved'];
}
}

View File

@ -42,8 +42,8 @@ class Hooks{
* @param paramters parameters from postCreateUser-Hook
* @return array
*/
static public function createUser($parameters) {
Addressbook::addDefault($parameters['uid']);
public static function userCreated($parameters) {
//Addressbook::addDefault($parameters['uid']);
return true;
}
@ -52,17 +52,101 @@ class Hooks{
* @param paramters parameters from postDeleteUser-Hook
* @return array
*/
static public function deleteUser($parameters) {
$addressbooks = Addressbook::all($parameters['uid']);
public static function userDeleted($parameters) {
$backend = new Backend\Database();
$addressbook = $backend->getAddressBooksForUser($parameters['uid']);
foreach($addressbooks as $addressbook) {
Addressbook::delete($addressbook['id']);
// Purging of contact categories and and properties is done by backend.
$backend->deleteAddressBook($addressbook['id']);
}
}
return true;
/**
* Delete any registred address books (Future)
*/
public static function addressBookDeletion($parameters) {
}
static public function getCalenderSources($parameters) {
public static function contactDeletion($parameters) {
$catctrl = new \OC_VCategories('contact');
$catctrl->purgeObjects(array($parameters['id']));
Utils\Properties::updateIndex($parameters['id']);
// Contact sharing not implemented, but keep for future.
//\OCP\Share::unshareAll('contact', $id);
}
public static function contactUpdated($parameters) {
//\OCP\Util::writeLog('contacts', __METHOD__.' parameters: '.print_r($parameters, true), \OCP\Util::DEBUG);
$catctrl = new \OC_VCategories('contact');
$catctrl->loadFromVObject(
$parameters['id'],
new \OC_VObject($parameters['contact']), // OC_VCategories still uses OC_VObject
true // force save
);
Utils\Properties::updateIndex($parameters['id'], $parameters['contact']);
}
/**
* Scan vCards for categories.
*/
public static function scanCategories() {
$offset = 0;
$limit = 10;
$categories = new \OC_VCategories('contact');
$app = new App();
$backend = $app->getBackend('local');
$addressBookInfos = $backend->getAddressBooksForUser();
foreach($addressBookInfos as $addressBookInfo) {
$addressBook = new AddressBook($backend, $addressBookInfo);
while($contacts = $addressBook->getChildren($limit, $offset, false)) {
foreach($contacts as $contact) {
$cards[] = array($contact['id'], $contact['carddata']);
}
\OCP\Util::writeLog('contacts',
__CLASS__.'::'.__METHOD__
.', scanning: ' . $limit . ' starting from ' . $offset,
\OCP\Util::DEBUG);
// only reset on first batch.
$categories->rescan($cards, true, ($offset === 0 ? true : false));
$offset += $limit;
}
}
}
/**
* Scan vCards for categories.
*/
public static function indexProperties() {
$offset = 0;
$limit = 10;
$app = new App();
$backend = $app->getBackend('local');
$addressBookInfos = $backend->getAddressBooksForUser();
foreach($addressBookInfos as $addressBookInfo) {
$addressBook = new AddressBook($backend, $addressBookInfo);
while($contacts = $addressBook->getChildren($limit, $offset, false)) {
foreach($contacts as $contact) {
$contact->retrieve();
}
\OCP\Util::writeLog('contacts',
__CLASS__.'::'.__METHOD__
.', indexing: ' . $limit . ' starting from ' . $offset,
\OCP\Util::DEBUG);
Utils\Properties::updateIndex($contact->getId(), $contact);
$offset += $limit;
}
}
}
public static function getCalenderSources($parameters) {
/*
$base_url = \OCP\Util::linkTo('calendar', 'ajax/events.php').'?calendar_id=';
foreach(Addressbook::all(\OCP\USER::getUser()) as $addressbook) {
$parameters['sources'][]
@ -75,9 +159,10 @@ class Hooks{
'editable' => false,
);
}
*/
}
static public function getBirthdayEvents($parameters) {
public static function getBirthdayEvents($parameters) {
$name = $parameters['calendar_id'];
if (strpos($name, 'birthday_') != 0) {
return;

139
lib/ipimobject.php Normal file
View File

@ -0,0 +1,139 @@
<?php
/**
* ownCloud - Interface for PIM object
*
* @author Thomas Tanghus
* @copyright 2013 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
* 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/>.
*
*/
namespace OCA\Contacts;
/**
* Implement this interface for PIM objects
*/
interface IPIMObject {
/**
* If this object is part of a collection return a reference
* to the parent object, otherwise return null.
* @return IPIMObject|null
*/
//function getParent();
/**
* Get the identifier for the object.
* @return string
*/
public function getId();
/**
* A convenience method for getting all the info about the object.
*
* The returned array MUST contain:
* 'id' @see getId().
* 'displayname' @see getDisplayName()
* 'owner' @see getOwner()
* 'permissions' @see getPermissions
* 'lastmodified' @see lastModified()
*
* If the object is part of a collection it MUST contain
* 'parent' The identifier for the parent object. @see getParent()
*
* @return array|null
*/
public function getMetaData();
/**
* FIXME: This should probably not be in the interface
* as it's *DAV specific.
* @return string
*/
public function getURI();
/**
* @return string|null
*/
function getDisplayName();
/**
* Get the owner of the object.
* @return string|null
*/
function getOwner();
/**
* If this object is part of a collection return a reference
* to the parent object, otherwise return null.
* @return IPIMObject|null
*/
function getParent();
/** CRUDS permissions (Create, Read, Update, Delete, Share) using a bitmask of
*
* \OCP\PERMISSION_CREATE
* \OCP\PERMISSION_READ
* \OCP\PERMISSION_UPDATE
* \OCP\PERMISSION_DELETE
* \OCP\PERMISSION_SHARE
* or
* \OCP\PERMISSION_ALL
*
* @return integer
*/
function getPermissions();
/**
* @return AbstractBackend
*/
function getBackend();
/**
* @param integer $permission
* @return boolean
*/
function hasPermission($permission);
/**
* Save the contact data to backend
* FIXME: This isn't consistent. We need a proper interface
* for creating new instances and saving to storage.
*
* @param array $data
* @return bool
*/
public function update(array $data);
/**
* @brief Get the last modification time for the object.
*
* Must return a UNIX time stamp or null if the backend
* doesn't support it.
*
* @returns int | null
*/
public function lastModified();
/**
* Delete the data from backend
*
* @return bool
*/
public function delete();
}

208
lib/request.php Normal file
View File

@ -0,0 +1,208 @@
<?php
/**
* ownCloud - Request
*
* @author Thomas Tanghus
* @copyright 2013 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
* 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/>.
*
*/
namespace OCA\Contacts;
/**
* Class for accessing variables in the request.
* This class provides an immutable object with request variables.
*/
class Request implements \ArrayAccess, \Countable {
protected $items = array();
protected $varnames = array('get', 'post', 'files', 'server', 'env', 'session', 'cookies', 'urlParams', 'params', 'parameters', 'method');
/**
* @param array $vars And associative array with the following optional values:
* @param array 'params' the parsed json array
* @param array 'urlParams' the parameters which were matched from the URL
* @param array 'get' the $_GET array
* @param array 'post' the $_POST array
* @param array 'files' the $_FILES array
* @param array 'server' the $_SERVER array
* @param array 'env' the $_ENV array
* @param array 'session' the $_SESSION array
* @param array 'cookies' the $_COOKIE array
* @param string 'method' the request method (GET, POST etc)
* @see http://www.php.net/manual/en/reserved.variables.php
*/
public function __construct(array $vars = array()) {
foreach($this->varnames as $name) {
$this->items[$name] = isset($vars[$name]) ? $vars[$name] : array();
}
$this->items['parameters'] = array_merge(
$this->items['params'],
$this->items['get'],
$this->items['post'],
$this->items['urlParams']
);
}
/**
* Returns an instance of Request using default request variables.
*/
public static function getRequest($urlParams) {
// Ensure that params is an array, not null
$params = json_decode(file_get_contents('php://input'), true);
\OCP\Util::writeLog('contacts', __METHOD__.' params: '.print_r($params, true), \OCP\Util::DEBUG);
$params = is_null($params) ? array() : $params;
return new self(
array(
'get' => $_GET,
'post' => $_POST,
'files' => $_FILES,
'server' => $_SERVER,
'env' => $_ENV,
'session' => $_SESSION,
'cookies' => $_COOKIE,
'method' => (isset($_SERVER) && isset($_SERVER['REQUEST_METHOD']))
? $_SERVER['REQUEST_METHOD']
: '',
'params' => $params,
'urlParams' => $urlParams
)
);
}
// Countable method.
public function count() {
return count(array_keys($this->items['parameters']));
}
/**
* ArrayAccess methods
*
* Gives access to the combined GET, POST and urlParams arrays
*
* Examples:
*
* $var = $request['myvar'];
*
* or
*
* if(!isset($request['myvar']) {
* // Do something
* }
*
* $request['myvar'] = 'something'; // This throws an exception.
*
* @param string offset The key to lookup
* @return string|null
*/
public function offsetExists($offset) {
return isset($this->items['parameters'][$offset]);
}
/**
* @see offsetExists
*/
public function offsetGet($offset) {
return isset($this->items['parameters'][$offset])
? $this->items['parameters'][$offset]
: null;
}
/**
* @see offsetExists
*/
public function offsetSet($offset, $value) {
throw new \RuntimeException('You cannot change the contents of the request object');
}
/**
* @see offsetExists
*/
public function offsetUnset($offset) {
throw new \RuntimeException('You cannot change the contents of the request object');
}
/**
* Get a value from a request variable or the default value e.g:
* $request->get('post', 'some_key', 'default value');
*
* @param string $vars Which variables to look in e.g. 'get', 'post, 'session'
* @param string $name
* @param string $default
*/
public function getVar($vars, $name, $default = null) {
return isset($this->{$vars}[$name]) ? $this->{$vars}[$name] : $default;
}
// Magic property accessors
public function __set($name, $value) {
throw new \RuntimeException('You cannot change the contents of the request object');
}
/**
* Access request variables by method and name.
* Examples:
*
* $request->post['myvar']; // Only look for POST variables
* $request->myvar; or $request->{'myvar'}; or $request->{$myvar}
* Looks in the combined GET, POST and urlParams array.
*
* if($request->method !== 'POST') {
* throw new Exception('This function can only be invoked using POST');
* }
*
* @param string $name The key to look for.
* @return mixed|null
*/
public function __get($name) {
switch($name) {
case 'get':
case 'post':
case 'files':
case 'server':
case 'env':
case 'session':
case 'cookies':
case 'params':
case 'parameters':
case 'urlParams':
return isset($this->items[$name])
? $this->items[$name]
: null;
break;
case 'method':
return $this->items['method'];
break;
default;
return isset($this[$name]) ? $this[$name] : null;
break;
}
}
public function __isset($name) {
return isset($this->items['parameters'][$name]);
}
public function __unset($id) {
throw new \RunTimeException('You cannot change the contents of the request object');
}
}

View File

@ -1,212 +0,0 @@
<?php
/**
* ownCloud - Addressbook
*
* @author Jakob Sack
* @copyright 2011 Jakob Sack mail@jakobsack.de
*
* 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/>.
*
*/
/**
* This CardDAV backend uses PDO to store addressbooks
*/
class OC_Connector_Sabre_CardDAV extends Sabre_CardDAV_Backend_Abstract {
/**
* Returns the list of addressbooks for a specific user.
*
* @param string $principaluri
* @return array
*/
public function getAddressBooksForUser($principaluri) {
$data = OCA\Contacts\Addressbook::allWherePrincipalURIIs($principaluri);
$addressbooks = array();
foreach($data as $i) {
if($i['userid'] != OCP\USER::getUser()) {
$i['uri'] = $i['uri'] . '_shared_by_' . $i['userid'];
}
$addressbooks[] = array(
'id' => $i['id'],
'uri' => $i['uri'],
'principaluri' => 'principals/'.$i['userid'],
'{DAV:}displayname' => $i['displayname'],
'{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}addressbook-description'
=> $i['description'],
'{http://calendarserver.org/ns/}getctag' => $i['ctag'],
);
}
return $addressbooks;
}
/**
* Updates an addressbook's properties
*
* See Sabre_DAV_IProperties for a description of the mutations array, as
* well as the return value.
*
* @param mixed $addressbookid
* @param array $mutations
* @see Sabre_DAV_IProperties::updateProperties
* @return bool|array
*/
public function updateAddressBook($addressbookid, array $mutations) {
$name = null;
$description = null;
foreach($mutations as $property=>$newvalue) {
switch($property) {
case '{DAV:}displayname' :
$name = $newvalue;
break;
case '{' . Sabre_CardDAV_Plugin::NS_CARDDAV
. '}addressbook-description' :
$description = $newvalue;
break;
default :
// If any unsupported values were being updated, we must
// let the entire request fail.
return false;
}
}
OCA\Contacts\Addressbook::edit($addressbookid, $name, $description);
return true;
}
/**
* Creates a new address book
*
* @param string $principaluri
* @param string $url Just the 'basename' of the url.
* @param array $properties
* @return void
*/
public function createAddressBook($principaluri, $url, array $properties) {
$displayname = null;
$description = null;
foreach($properties as $property=>$newvalue) {
switch($property) {
case '{DAV:}displayname' :
$name = $newvalue;
break;
case '{' . Sabre_CardDAV_Plugin::NS_CARDDAV
. '}addressbook-description' :
$description = $newvalue;
break;
default :
throw new Sabre_DAV_Exception_BadRequest('Unknown property: '
. $property);
}
}
OCA\Contacts\Addressbook::addFromDAVData(
$principaluri,
$url,
$name,
$description
);
}
/**
* Deletes an entire addressbook and all its contents
*
* @param int $addressbookid
* @return void
*/
public function deleteAddressBook($addressbookid) {
OCA\Contacts\Addressbook::delete($addressbookid);
}
/**
* Returns all cards for a specific addressbook id.
*
* @param mixed $addressbookid
* @return array
*/
public function getCards($addressbookid) {
$data = OCA\Contacts\VCard::all($addressbookid);
$cards = array();
foreach($data as $i) {
//OCP\Util::writeLog('contacts', __METHOD__.', uri: ' . $i['uri'], OCP\Util::DEBUG);
$cards[] = array(
'id' => $i['id'],
//'carddata' => $i['carddata'],
'size' => strlen($i['carddata']),
'etag' => '"' . md5($i['carddata']) . '"',
'uri' => $i['uri'],
'lastmodified' => $i['lastmodified'] );
}
return $cards;
}
/**
* Returns a specfic card
*
* @param mixed $addressbookid
* @param string $carduri
* @return array
*/
public function getCard($addressbookid, $carduri) {
return OCA\Contacts\VCard::findWhereDAVDataIs($addressbookid, $carduri);
}
/**
* Creates a new card
*
* @param mixed $addressbookid
* @param string $carduri
* @param string $carddata
* @return bool
*/
public function createCard($addressbookid, $carduri, $carddata) {
OCA\Contacts\VCard::addFromDAVData($addressbookid, $carduri, $carddata);
}
/**
* Updates a card
*
* @param mixed $addressbookid
* @param string $carduri
* @param string $carddata
* @return bool
*/
public function updateCard($addressbookid, $carduri, $carddata) {
return OCA\Contacts\VCard::editFromDAVData(
$addressbookid, $carduri, $carddata
);
}
/**
* Deletes a card
*
* @param mixed $addressbookid
* @param string $carduri
* @return bool
*/
public function deleteCard($addressbookid, $carduri) {
return OCA\Contacts\VCard::deleteFromDAVData($addressbookid, $carduri);
}
}

View File

@ -10,6 +10,14 @@ namespace OCA\Contacts;
class Share_Backend_Addressbook implements \OCP\Share_Backend_Collection {
const FORMAT_ADDRESSBOOKS = 1;
const FORMAT_COLLECTION = 2;
public $backend;
public function __construct() {
// Currently only share
$this->backend = new Backend\Database();
}
/**
* @brief Get the source of the item to be stored in the database
@ -23,8 +31,8 @@ class Share_Backend_Addressbook implements \OCP\Share_Backend_Collection {
* The formatItems() function will translate the source returned back into the item
*/
public function isValidSource($itemSource, $uidOwner) {
$addressbook = Addressbook::find( $itemSource );
if( $addressbook === false || $addressbook['userid'] != $uidOwner) {
$addressbook = $this->backend->getAddressBook($itemSource);
if(!$addressbook || $addressbook['userid'] !== $uidOwner) {
return false;
}
return true;
@ -41,17 +49,20 @@ class Share_Backend_Addressbook implements \OCP\Share_Backend_Collection {
* If it does generate a new name e.g. name_#
*/
public function generateTarget($itemSource, $shareWith, $exclude = null) {
$addressbook = Addressbook::find( $itemSource );
$addressbook = $this->backend->getAddressBook($itemSource);
$user_addressbooks = array();
foreach(Addressbook::all($shareWith) as $user_addressbook) {
foreach($this->backend->getAddressBooksForUser($shareWith) as $user_addressbook) {
$user_addressbooks[] = $user_addressbook['displayname'];
}
$name = $addressbook['displayname'];
$name = $addressbook['displayname'] . '(' . $addressbook['userid'] . ')';
$suffix = '';
while (in_array($name.$suffix, $user_addressbooks)) {
$suffix++;
}
$suffix = $suffix ? ' ' . $suffix : '';
return $name.$suffix;
}
@ -68,26 +79,33 @@ class Share_Backend_Addressbook implements \OCP\Share_Backend_Collection {
* This function allows the backend to control the output of shared items with custom formats.
* It is only called through calls to the public getItem(s)Shared(With) functions.
*/
public function formatItems($items, $format, $parameters = null) {
public function formatItems($items, $format, $parameters = null, $include = false) {
//\OCP\Util::writeLog('contacts', __METHOD__
// . ' ' . $include . ' ' . print_r($items, true), \OCP\Util::DEBUG);
$addressbooks = array();
if ($format == self::FORMAT_ADDRESSBOOKS) {
if ($format === self::FORMAT_ADDRESSBOOKS) {
foreach ($items as $item) {
$addressbook = Addressbook::find($item['item_source']);
//\OCP\Util::writeLog('contacts', __METHOD__.' item_source: ' . $item['item_source'] . ' include: '
// . (int)$include, \OCP\Util::DEBUG);
$addressbook = $this->backend->getAddressBook($item['item_source']);
if ($addressbook) {
$addressbook['displayname'] = $item['item_target'];
$addressbook['displayname'] = $addressbook['displayname'] . ' (' . $addressbook['owner'] . ')';
$addressbook['permissions'] = $item['permissions'];
$addressbooks[] = $addressbook;
}
}
} elseif ($format === self::FORMAT_COLLECTION) {
foreach ($items as $item) {
}
}
return $addressbooks;
}
public function getChildren($itemSource) {
$query = \OCP\DB::prepare('SELECT `id`, `fullname` FROM `*PREFIX*contacts_cards` WHERE `addressbookid` = ?');
$result = $query->execute(array($itemSource));
\OCP\Util::writeLog('contacts', __METHOD__.' item_source: ' . $itemSource, \OCP\Util::DEBUG);
$contacts = $this->backend->getContacts($itemSource, null, null, true);
$children = array();
while ($contact = $result->fetchRow()) {
foreach($contacts as $contact) {
$children[] = array('source' => $contact['id'], 'target' => $contact['fullname']);
}
return $children;

View File

@ -27,6 +27,11 @@ class Share_Backend_Contact implements \OCP\Share_Backend {
private static $contact;
public function __construct() {
// Currently only share
$this->backend = new Backend\Database();
}
public function isValidSource($itemSource, $uidOwner) {
self::$contact = VCard::find($itemSource);
if (self::$contact) {

View File

@ -0,0 +1,207 @@
<?php
/**
* ownCloud - JSONSerializer
*
* @author Thomas Tanghus, Jakob Sack
* @copyright 2011 Jakob Sack mail@jakobsack.de
* @copyright 2013 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
* 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/>.
*
*/
namespace OCA\Contacts\Utils;
use OCA\Contacts\VObject;
use OCA\Contacts\Contact;
/**
* This class serializes properties, components an
* arrays of components into a format suitable for
* passing to a JSON response.
* TODO: Return jCard (almost) compliant data, but still omitting unneeded data.
* http://tools.ietf.org/html/draft-kewisch-vcard-in-json-01
*/
class JSONSerializer {
/**
* General method serialize method. Use this for arrays
* of contacts.
*
* @param Contact[] $input
* @return array
*/
public static function serialize($input) {
$response = array();
if(is_array($input)) {
foreach($input as $object) {
if($object instanceof Contact) {
\OCP\Util::writeLog('contacts', __METHOD__.' serializing: ' . print_r($object, true), \OCP\Util::DEBUG);
$tmp = self::serializeContact($object);
if($tmp !== null) {
$response[] = $tmp;
}
} else {
throw new \Exception(
'Only arrays of OCA\\Contacts\\VObject\\VCard '
. 'and Sabre\VObject\Property are accepted.'
);
}
}
} else {
if($input instanceof VObject\VCard) {
return self::serializeContact($input);
} elseif($input instanceof Sabre\VObject\Property) {
return self::serializeProperty($input);
} else {
throw new \Exception(
'Only instances of OCA\\Contacts\\VObject\\VCard '
. 'and Sabre\VObject\Property are accepted.'
);
}
}
return $response;
}
/**
* @brief Data structure of vCard
* @param VObject\VCard $contact
* @return associative array|null
*/
public static function serializeContact(Contact $contact) {
if(!$contact->retrieve()) {
\OCP\Util::writeLog('contacts', __METHOD__.' error reading: ' . print_r($contact, true), \OCP\Util::DEBUG);
return null;
}
$details = array();
if(isset($contact->PHOTO) || isset($contact->LOGO)) {
$details['thumbnail'] = $contact->cacheThumbnail();
}
foreach($contact->children as $property) {
$pname = $property->name;
$temp = self::serializeProperty($property);
if(!is_null($temp)) {
// Get Apple X-ABLabels
if(isset($contact->{$property->group . '.X-ABLABEL'})) {
$temp['label'] = $contact->{$property->group . '.X-ABLABEL'}->value;
if($temp['label'] == '_$!<Other>!$_') {
$temp['label'] = Properties::$l10n->t('Other');
}
if($temp['label'] == '_$!<HomePage>!$_') {
$temp['label'] = Properties::$l10n->t('HomePage');
}
}
if(array_key_exists($pname, $details)) {
$details[$pname][] = $temp;
}
else{
$details[$pname] = array($temp);
}
}
}
return array('data' =>$details, 'metadata' => $contact->getMetaData());
}
/**
* @brief Get data structure of property.
* @param \Sabre\VObject\Property $property
* @return associative array
*
* returns an associative array with
* ['name'] name of property
* ['value'] htmlspecialchars escaped value of property
* ['parameters'] associative array name=>value
* ['checksum'] checksum of whole property
* NOTE: $value is not escaped anymore. It shouldn't make any difference
* but we should look out for any problems.
*/
public static function serializeProperty(\Sabre\VObject\Property $property) {
if(!in_array($property->name, Properties::$index_properties)) {
return;
}
$value = $property->value;
if($property->name == 'ADR' || $property->name == 'N' || $property->name == 'ORG' || $property->name == 'CATEGORIES') {
$value = $property->getParts();
$value = array_map('trim', $value);
}
elseif($property->name == 'BDAY') {
if(strpos($value, '-') === false) {
if(strlen($value) >= 8) {
$value = substr($value, 0, 4).'-'.substr($value, 4, 2).'-'.substr($value, 6, 2);
} else {
return null; // Badly malformed :-(
}
}
} elseif($property->name == 'PHOTO') {
$value = true;
}
elseif($property->name == 'IMPP') {
if(strpos($value, ':') !== false) {
$value = explode(':', $value);
$protocol = array_shift($value);
if(!isset($property['X-SERVICE-TYPE'])) {
$property['X-SERVICE-TYPE'] = strtoupper($protocol);
}
$value = implode('', $value);
}
}
if(is_string($value)) {
$value = strtr($value, array('\,' => ',', '\;' => ';'));
}
$temp = array(
//'name' => $property->name,
'value' => $value,
'parameters' => array()
);
// This cuts around a 3rd off of the json response size.
if(in_array($property->name, Properties::$multi_properties)) {
$temp['checksum'] = substr(md5($property->serialize()), 0, 8);
}
foreach($property->parameters as $parameter) {
// Faulty entries by kaddressbook
// Actually TYPE=PREF is correct according to RFC 2426
// but this way is more handy in the UI. Tanghus.
if($parameter->name == 'TYPE' && strtoupper($parameter->value) == 'PREF') {
$parameter->name = 'PREF';
$parameter->value = '1';
}
// NOTE: Apparently Sabre_VObject_Reader can't always deal with value list parameters
// like TYPE=HOME,CELL,VOICE. Tanghus.
// TODO: Check if parameter is has commas and split + merge if so.
if ($parameter->name == 'TYPE') {
$pvalue = $parameter->value;
if(is_string($pvalue) && strpos($pvalue, ',') !== false) {
$pvalue = array_map('trim', explode(',', $pvalue));
}
$pvalue = is_array($pvalue) ? $pvalue : array($pvalue);
if (isset($temp['parameters'][$parameter->name])) {
$temp['parameters'][$parameter->name][] = \OCP\Util::sanitizeHTML($pvalue);
}
else {
$temp['parameters'][$parameter->name] = \OCP\Util::sanitizeHTML($pvalue);
}
}
else{
$temp['parameters'][$parameter->name] = \OCP\Util::sanitizeHTML($parameter->value);
}
}
return $temp;
}
}

263
lib/utils/properties.php Normal file
View File

@ -0,0 +1,263 @@
<?php
/**
* ownCloud - Interface for PIM object
*
* @author Thomas Tanghus
* @copyright 2013 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
* 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/>.
*
*/
namespace OCA\Contacts\Utils;
Properties::$l10n = \OC_L10N::get('contacts');
Class Properties {
private static $deleteindexstmt;
private static $updateindexstmt;
protected static $cardsTableName = '*PREFIX*contacts_cards';
protected static $indexTableName = '*PREFIX*contacts_cards_properties';
/**
* @brief language object for calendar app
*
* @var OC_L10N
*/
public static $l10n;
/**
* Properties there can be more than one of.
*
* @var array
*/
public static $multi_properties = array('EMAIL', 'TEL', 'IMPP', 'ADR', 'URL');
/**
* Properties to index.
*
* @var array
*/
public static $index_properties = array(
'BDAY', 'UID', 'N', 'FN', 'TITLE', 'ROLE', 'NOTE', 'NICKNAME',
'ORG', 'CATEGORIES', 'EMAIL', 'TEL', 'IMPP', 'ADR', 'URL', 'GEO', 'PHOTO');
/**
* Get options for IMPP properties
* @param string $im
* @return array of vcard prop => label
*/
public static function getIMOptions($im = null) {
$l10n = self::$l10n;
$ims = array(
'jabber' => array(
'displayname' => (string)$l10n->t('Jabber'),
'xname' => 'X-JABBER',
'protocol' => 'xmpp',
),
'aim' => array(
'displayname' => (string)$l10n->t('AIM'),
'xname' => 'X-AIM',
'protocol' => 'aim',
),
'msn' => array(
'displayname' => (string)$l10n->t('MSN'),
'xname' => 'X-MSN',
'protocol' => 'msn',
),
'twitter' => array(
'displayname' => (string)$l10n->t('Twitter'),
'xname' => 'X-TWITTER',
'protocol' => 'twitter',
),
'googletalk' => array(
'displayname' => (string)$l10n->t('GoogleTalk'),
'xname' => null,
'protocol' => 'xmpp',
),
'facebook' => array(
'displayname' => (string)$l10n->t('Facebook'),
'xname' => null,
'protocol' => 'xmpp',
),
'xmpp' => array(
'displayname' => (string)$l10n->t('XMPP'),
'xname' => null,
'protocol' => 'xmpp',
),
'icq' => array(
'displayname' => (string)$l10n->t('ICQ'),
'xname' => 'X-ICQ',
'protocol' => 'icq',
),
'yahoo' => array(
'displayname' => (string)$l10n->t('Yahoo'),
'xname' => 'X-YAHOO',
'protocol' => 'ymsgr',
),
'skype' => array(
'displayname' => (string)$l10n->t('Skype'),
'xname' => 'X-SKYPE',
'protocol' => 'skype',
),
'qq' => array(
'displayname' => (string)$l10n->t('QQ'),
'xname' => 'X-SKYPE',
'protocol' => 'x-apple',
),
'gadugadu' => array(
'displayname' => (string)$l10n->t('GaduGadu'),
'xname' => 'X-SKYPE',
'protocol' => 'x-apple',
),
);
if(is_null($im)) {
return $ims;
} else {
$ims['ymsgr'] = $ims['yahoo'];
$ims['gtalk'] = $ims['googletalk'];
return isset($ims[$im]) ? $ims[$im] : null;
}
}
/**
* Get standard set of TYPE values for different properties.
*
* @param string $prop
* @return array Type values for property $prop
*/
public static function getTypesForProperty($prop) {
$l = self::$l10n;
switch($prop) {
case 'LABEL':
case 'ADR':
case 'IMPP':
return array(
'WORK' => (string)$l->t('Work'),
'HOME' => (string)$l->t('Home'),
'OTHER' => (string)$l->t('Other'),
);
case 'TEL':
return array(
'HOME' => (string)$l->t('Home'),
'CELL' => (string)$l->t('Mobile'),
'WORK' => (string)$l->t('Work'),
'TEXT' => (string)$l->t('Text'),
'VOICE' => (string)$l->t('Voice'),
'MSG' => (string)$l->t('Message'),
'FAX' => (string)$l->t('Fax'),
'VIDEO' => (string)$l->t('Video'),
'PAGER' => (string)$l->t('Pager'),
'OTHER' => (string)$l->t('Other'),
);
case 'EMAIL':
return array(
'WORK' => (string)$l->t('Work'),
'HOME' => (string)$l->t('Home'),
'INTERNET' => (string)$l->t('Internet'),
'OTHER' => (string)$l->t('Other'),
);
}
}
/**
* @brief returns the default categories of ownCloud
* @return (array) $categories
*/
public static function getDefaultCategories() {
$l10n = self::$l10n;
return array(
(string)$l10n->t('Friends'),
(string)$l10n->t('Family'),
(string)$l10n->t('Work'),
(string)$l10n->t('Other'),
);
}
public static function generateUID($app = 'contacts') {
return date('Ymd\\THis') . '.' . time(). '@' . OCP\Util::getServerHostName();
}
/**
* Update the contact property index.
*
* If vcard is null the properties for that contact will be purged.
* If it is a valid object the old properties will first be purged
* and the current properties indexed.
*
* @param string $contactid
* @param \OCA\VObject\VCard|null $vcard
*/
public static function updateIndex($contactid, $vcard = null) {
if(!isset(self::$deleteindexstmt)) {
self::$deleteindexstmt
= \OCP\DB::prepare('DELETE FROM `' . self::$indexTableName . '`'
. ' WHERE `contactid` = ?');
}
try {
self::$deleteindexstmt->execute(array($contactid));
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.
', exception: ' . $e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::writeLog('contacts', __METHOD__.', id: '
. $id, \OCP\Util::DEBUG);
throw new \Exception(
App::$l10n->t(
'There was an error deleting properties for this contact.'
)
);
}
if(is_null($vcard)) {
return;
}
if(!isset(self::$updateindexstmt)) {
self::$updateindexstmt = \OCP\DB::prepare( 'INSERT INTO `' . self::$indexTableName . '` '
. '(`userid`, `contactid`,`name`,`value`,`preferred`) VALUES(?,?,?,?,?)' );
}
foreach($vcard->children as $property) {
if(!in_array($property->name, self::$index_properties)) {
continue;
}
$preferred = 0;
foreach($property->parameters as $parameter) {
if($parameter->name == 'TYPE' && strtoupper($parameter->value) == 'PREF') {
$preferred = 1;
break;
}
}
try {
$result = self::$updateindexstmt->execute(
array(
\OCP\User::getUser(),
$contactid,
$property->name,
$property->value,
$preferred,
)
);
if (\OC_DB::isError($result)) {
\OCP\Util::writeLog('contacts', __METHOD__. 'DB error: '
. \OC_DB::getErrorMessage($result), \OC_Log::ERROR);
return false;
}
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR);
return false;
}
}
}
}

View File

@ -4,7 +4,7 @@
*
* @author Jakob Sack
* @copyright 2011 Jakob Sack mail@jakobsack.de
* @copyright 2012 Thomas Tanghus <thomas@tanghus.net>
* @copyright 2012-2013 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
@ -43,377 +43,6 @@ use Sabre\VObject;
* This class manages our vCards
*/
class VCard {
/**
* @brief Returns all cards of an address book
* @param integer $id
* @param integer $offset
* @param integer $limit
* @param array $fields An array of the fields to return. Defaults to all.
* @return array|false
*
* The cards are associative arrays. You'll find the original vCard in
* ['carddata']
*/
public static function all($id, $offset=null, $limit=null, $fields = array()) {
$result = null;
\OCP\Util::writeLog('contacts', __METHOD__.'count fields:' . count($fields), \OCP\Util::DEBUG);
$qfields = count($fields) > 0
? '`' . implode('`,`', $fields) . '`'
: '*';
if(is_array($id) && count($id)) {
$id_sql = join(',', array_fill(0, count($id), '?'));
$sql = 'SELECT ' . $qfields . ' FROM `*PREFIX*contacts_cards` WHERE `addressbookid` IN ('.$id_sql.') ORDER BY `fullname`';
try {
$stmt = \OCP\DB::prepare($sql, $limit, $offset);
$result = $stmt->execute($id);
if (\OC_DB::isError($result)) {
\OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
return false;
}
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.', exception: ' . $e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::writeLog('contacts', __METHOD__.', ids: ' . join(',', $id), \OCP\Util::DEBUG);
\OCP\Util::writeLog('contacts', __METHOD__.'SQL:' . $sql, \OCP\Util::DEBUG);
return false;
}
} elseif(is_int($id) || is_string($id)) {
try {
$sql = 'SELECT ' . $qfields . ' FROM `*PREFIX*contacts_cards` WHERE `addressbookid` = ? ORDER BY `fullname`';
$stmt = \OCP\DB::prepare($sql, $limit, $offset);
$result = $stmt->execute(array($id));
if (\OC_DB::isError($result)) {
\OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
return false;
}
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::writeLog('contacts', __METHOD__.', ids: '. $id, \OCP\Util::DEBUG);
return false;
}
} else {
\OCP\Util::writeLog('contacts', __METHOD__
. '. Addressbook id(s) argument is empty: '
. print_r($id, true), \OCP\Util::DEBUG);
return false;
}
$cards = array();
if(!is_null($result)) {
while( $row = $result->fetchRow()) {
$cards[] = $row;
}
}
return $cards;
}
/**
* @brief Returns a card
* @param integer $id
* @param array $fields An array of the fields to return. Defaults to all.
* @return associative array or false.
*/
public static function find($id, $fields = array() ) {
if(count($fields) > 0 && !in_array('addressbookid', $fields)) {
$fields[] = 'addressbookid';
}
try {
$qfields = count($fields) > 0
? '`' . implode('`,`', $fields) . '`'
: '*';
$stmt = \OCP\DB::prepare( 'SELECT ' . $qfields . ' FROM `*PREFIX*contacts_cards` WHERE `id` = ?' );
$result = $stmt->execute(array($id));
if (\OC_DB::isError($result)) {
\OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
return false;
}
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::writeLog('contacts', __METHOD__.', id: '. $id, \OCP\Util::DEBUG);
return false;
}
$row = $result->fetchRow();
if($row) {
try {
$addressbook = Addressbook::find($row['addressbookid']);
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::writeLog('contacts', __METHOD__.', id: '. $id, \OCP\Util::DEBUG);
throw $e;
}
}
return $row;
}
/**
* @brief finds a card by its DAV Data
* @param integer $aid Addressbook id
* @param string $uri the uri ('filename')
* @return associative array or false.
*/
public static function findWhereDAVDataIs($aid, $uri) {
try {
$stmt = \OCP\DB::prepare( 'SELECT * FROM `*PREFIX*contacts_cards` WHERE `addressbookid` = ? AND `uri` = ?' );
$result = $stmt->execute(array($aid,$uri));
if (\OC_DB::isError($result)) {
\OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
return false;
}
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::writeLog('contacts', __METHOD__.', aid: '.$aid.' uri'.$uri, \OCP\Util::DEBUG);
return false;
}
return $result->fetchRow();
}
/**
* @brief Format property TYPE parameters for upgrading from v. 2.1
* @param $property Reference to a Sabre_VObject_Property.
* In version 2.1 e.g. a phone can be formatted like: TEL;HOME;CELL:123456789
* This has to be changed to either TEL;TYPE=HOME,CELL:123456789 or TEL;TYPE=HOME;TYPE=CELL:123456789 - both are valid.
*/
public static function formatPropertyTypes(&$property) {
foreach($property->parameters as $key=>&$parameter) {
$types = App::getTypesOfProperty($property->name);
if(is_array($types) && in_array(strtoupper($parameter->name), array_keys($types)) || strtoupper($parameter->name) == 'PREF') {
$property->parameters[] = new \Sabre\VObject\Parameter('TYPE', $parameter->name);
}
unset($property->parameters[$key]);
}
}
/**
* @brief Decode properties for upgrading from v. 2.1
* @param $property Reference to a Sabre_VObject_Property.
* The only encoding allowed in version 3.0 is 'b' for binary. All encoded strings
* must therefor be decoded and the parameters removed.
*/
public static function decodeProperty(&$property) {
// Check out for encoded string and decode them :-[
foreach($property->parameters as $key=>&$parameter) {
if(strtoupper($parameter->name) == 'ENCODING') {
if(strtoupper($parameter->value) == 'QUOTED-PRINTABLE') { // what kind of other encodings could be used?
// Decode quoted-printable and strip any control chars
// except \n and \r
$property->value = preg_replace(
'/[\x00-\x09\x0B\x0C\x0E-\x1F\x7F]/',
'',
quoted_printable_decode($property->value)
);
unset($property->parameters[$key]);
}
} elseif(strtoupper($parameter->name) == 'CHARSET') {
unset($property->parameters[$key]);
}
}
}
/**
* @brief Checks if a contact with the same UID already exist in the address book.
* @param $aid Address book ID.
* @param $uid UID (passed by reference).
* @returns true if the UID has been changed.
*/
protected static function trueUID($aid, &$uid) {
$stmt = \OCP\DB::prepare( 'SELECT * FROM `*PREFIX*contacts_cards` WHERE `addressbookid` = ? AND `uri` = ?' );
$uri = $uid.'.vcf';
try {
$result = $stmt->execute(array($aid,$uri));
if (\OC_DB::isError($result)) {
\OCP\Util::writeLog('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
return false;
}
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::writeLog('contacts', __METHOD__.', aid: '.$aid.' uid'.$uid, \OCP\Util::DEBUG);
return false;
}
if($result->numRows() > 0) {
while(true) {
$tmpuid = substr(md5(rand().time()), 0, 10);
$uri = $tmpuid.'.vcf';
$result = $stmt->execute(array($aid, $uri));
if($result->numRows() > 0) {
continue;
} else {
$uid = $tmpuid;
return true;
}
}
} else {
return false;
}
}
/**
* @brief Tries to update imported VCards to adhere to rfc2426 (VERSION: 3.0) and add mandatory fields if missing.
* @param aid Address book id.
* @param vcard A Sabre\VObject\Component of type VCARD (passed by reference).
*/
protected static function updateValuesFromAdd($aid, &$vcard) { // any suggestions for a better method name? ;-)
$stringprops = array('N', 'FN', 'ORG', 'NICK', 'ADR', 'NOTE');
$typeprops = array('ADR', 'TEL', 'EMAIL');
$upgrade = false;
$fn = $n = $uid = $email = $org = null;
$version = isset($vcard->VERSION) ? $vcard->VERSION : null;
// Add version if needed
if($version && $version < '3.0') {
$upgrade = true;
//OCP\Util::writeLog('contacts', 'OCA\Contacts\VCard::updateValuesFromAdd. Updating from version: '.$version, OCP\Util::DEBUG);
}
foreach($vcard->children as &$property) {
// Decode string properties and remove obsolete properties.
if($upgrade && in_array($property->name, $stringprops)) {
self::decodeProperty($property);
}
if(function_exists('iconv')) {
$property->value = str_replace("\r\n", "\n", iconv(mb_detect_encoding($property->value, 'UTF-8, ISO-8859-1'), 'utf-8', $property->value));
} else {
$property->value = str_replace("\r\n", "\n", mb_convert_encoding($property->value, 'UTF-8', mb_detect_encoding($property->value, 'UTF-8, ISO-8859-1'), $property->value));
}
if(in_array($property->name, $stringprops)) {
$property->value = strip_tags($property->value);
}
// Fix format of type parameters.
if($upgrade && in_array($property->name, $typeprops)) {
//OCP\Util::writeLog('contacts', 'OCA\Contacts\VCard::updateValuesFromAdd. before: '.$property->serialize(), OCP\Util::DEBUG);
self::formatPropertyTypes($property);
//OCP\Util::writeLog('contacts', 'OCA\Contacts\VCard::updateValuesFromAdd. after: '.$property->serialize(), OCP\Util::DEBUG);
}
if($property->name == 'FN') {
$fn = $property->value;
}
else if($property->name == 'N') {
$n = $property->value;
}
else if($property->name == 'UID') {
$uid = $property->value;
}
else if($property->name == 'ORG') {
$org = $property->value;
}
else if($property->name == 'EMAIL' && is_null($email)) { // only use the first email as substitute for missing N or FN.
$email = $property->value;
}
}
// Check for missing 'N', 'FN' and 'UID' properties
if(!$fn) {
if($n && $n != ';;;;') {
$fn = join(' ', array_reverse(array_slice(explode(';', $n), 0, 2)));
} elseif($email) {
$fn = $email;
} elseif($org) {
$fn = $org;
} else {
$fn = 'Unknown Name';
}
$vcard->FN = $fn;
//OCP\Util::writeLog('contacts', 'OCA\Contacts\VCard::updateValuesFromAdd. Added missing \'FN\' field: '.$fn, OCP\Util::DEBUG);
}
if(!$n || $n == ';;;;') { // Fix missing 'N' field. Ugly hack ahead ;-)
$slice = array_reverse(array_slice(explode(' ', $fn), 0, 2)); // Take 2 first name parts of 'FN' and reverse.
if(count($slice) < 2) { // If not enought, add one more...
$slice[] = "";
}
$n = implode(';', $slice).';;;';
$vcard->N = $n;
//OCP\Util::writeLog('contacts', 'OCA\Contacts\VCard::updateValuesFromAdd. Added missing \'N\' field: '.$n, OCP\Util::DEBUG);
}
if(!$uid) {
$uid = substr(md5(rand().time()), 0, 10);
$vcard->add('UID', $uid);
//OCP\Util::writeLog('contacts', 'OCA\Contacts\VCard::updateValuesFromAdd. Added missing \'UID\' field: '.$uid, OCP\Util::DEBUG);
}
if(self::trueUID($aid, $uid)) {
$vcard->{'UID'} = $uid;
}
$now = new \DateTime;
$vcard->{'REV'} = $now->format(\DateTime::W3C);
}
/**
* @brief Adds a card
* @param $aid integer Addressbook id
* @param $card Sabre\VObject\Component vCard file
* @param $uri string the uri of the card, default based on the UID
* @param $isChecked boolean If the vCard should be checked for validity and version.
* @return insertid on success or false.
*/
public static function add($aid, VObject\Component $card, $uri=null, $isChecked=false) {
if(is_null($card)) {
\OCP\Util::writeLog('contacts', __METHOD__ . ', No vCard supplied', \OCP\Util::ERROR);
return null;
};
$addressbook = Addressbook::find($aid);
if ($addressbook['userid'] != \OCP\User::getUser()) {
$sharedAddressbook = \OCP\Share::getItemSharedWithBySource('addressbook', $aid);
if (!$sharedAddressbook || !($sharedAddressbook['permissions'] & \OCP\PERMISSION_CREATE)) {
throw new \Exception(
App::$l10n->t(
'You do not have the permissions to add contacts to this addressbook.'
)
);
}
}
if(!$isChecked) {
self::updateValuesFromAdd($aid, $card);
}
$card->{'VERSION'} = '3.0';
// Add product ID is missing.
//$prodid = trim($card->getAsString('PRODID'));
//if(!$prodid) {
if(!isset($card->PRODID)) {
$appinfo = \OCP\App::getAppInfo('contacts');
$appversion = \OCP\App::getAppVersion('contacts');
$prodid = '-//ownCloud//NONSGML '.$appinfo['name'].' '.$appversion.'//EN';
$card->add('PRODID', $prodid);
}
$fn = isset($card->FN) ? $card->FN : '';
$uri = isset($uri) ? $uri : $card->UID . '.vcf';
$data = $card->serialize();
$stmt = \OCP\DB::prepare( 'INSERT INTO `*PREFIX*contacts_cards` (`addressbookid`,`fullname`,`carddata`,`uri`,`lastmodified`) VALUES(?,?,?,?,?)' );
try {
$result = $stmt->execute(array($aid, $fn, $data, $uri, time()));
if (\OC_DB::isError($result)) {
\OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
return false;
}
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::writeLog('contacts', __METHOD__.', aid: '.$aid.' uri'.$uri, \OCP\Util::DEBUG);
return false;
}
$newid = \OCP\DB::insertid('*PREFIX*contacts_cards');
App::loadCategoriesFromVCard($newid, $card);
App::updateDBProperties($newid, $card);
App::cacheThumbnail($newid);
Addressbook::touch($aid);
\OC_Hook::emit('\OCA\Contacts\VCard', 'post_createVCard', $newid);
return $newid;
}
/**
* @brief Adds a card with the data provided by sabredav
* @param integer $id Addressbook id
* @param string $uri the uri the card will have
* @param string $data vCard file
* @returns integer|false insertid or false on error
*/
public static function addFromDAVData($id, $uri, $data) {
try {
$vcard = \Sabre\VObject\Reader::read($data);
return self::add($id, $vcard, $uri);
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR);
return false;
}
}
/**
* @brief Mass updates an array of cards
@ -435,13 +64,6 @@ class VCard {
return false;
}
$addressbook = Addressbook::find($oldcard['addressbookid']);
if ($addressbook['userid'] != \OCP\User::getUser()) {
$sharedContact = \OCP\Share::getItemSharedWithBySource('contact', $object[0], \OCP\Share::FORMAT_NONE, null, true);
if (!$sharedContact || !($sharedContact['permissions'] & \OCP\PERMISSION_UPDATE)) {
return false;
}
}
$vcard->{'REV'} = $now->format(\DateTime::W3C);
$data = $vcard->serialize();
try {
@ -459,442 +81,4 @@ class VCard {
}
}
/**
* @brief edits a card
* @param integer $id id of card
* @param Sabre\VObject\Component $card vCard file
* @return boolean true on success, otherwise an exception will be thrown
*/
public static function edit($id, VObject\Component $card) {
$oldcard = self::find($id);
if (!$oldcard) {
\OCP\Util::writeLog('contacts', __METHOD__.', id: '
. $id . ' not found.', \OCP\Util::DEBUG);
throw new \Exception(
App::$l10n->t(
'Could not find the vCard with ID.' . $id
)
);
}
if(is_null($card)) {
return false;
}
// NOTE: Owner checks are being made in the ajax files, which should be done
// inside the lib files to prevent any redundancies with sharing checks
$addressbook = Addressbook::find($oldcard['addressbookid']);
if ($addressbook['userid'] != \OCP\User::getUser()) {
$sharedAddressbook = \OCP\Share::getItemSharedWithBySource(
'addressbook',
$oldcard['addressbookid'],
\OCP\Share::FORMAT_NONE, null, true);
$sharedContact = \OCP\Share::getItemSharedWithBySource('contact', $id, \OCP\Share::FORMAT_NONE, null, true);
$addressbook_permissions = 0;
$contact_permissions = 0;
if ($sharedAddressbook) {
$addressbook_permissions = $sharedAddressbook['permissions'];
}
if ($sharedContact) {
$contact_permissions = $sharedEvent['permissions'];
}
$permissions = max($addressbook_permissions, $contact_permissions);
if (!($permissions & \OCP\PERMISSION_UPDATE)) {
throw new \Exception(
App::$l10n->t(
'You do not have the permissions to edit this contact.'
)
);
}
}
App::loadCategoriesFromVCard($id, $card);
$fn = isset($card->FN) ? $card->FN : '';
$now = new \DateTime;
$card->REV = $now->format(\DateTime::W3C);
$data = $card->serialize();
$stmt = \OCP\DB::prepare( 'UPDATE `*PREFIX*contacts_cards` SET `fullname` = ?,`carddata` = ?, `lastmodified` = ? WHERE `id` = ?' );
try {
$result = $stmt->execute(array($fn, $data, time(), $id));
if (\OC_DB::isError($result)) {
\OCP\Util::writeLog('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
return false;
}
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.', exception: '
. $e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::writeLog('contacts', __METHOD__.', id'.$id, \OCP\Util::DEBUG);
return false;
}
App::cacheThumbnail($oldcard['id']);
App::updateDBProperties($id, $card);
Addressbook::touch($oldcard['addressbookid']);
\OC_Hook::emit('\OCA\Contacts\VCard', 'post_updateVCard', $id);
return true;
}
/**
* @brief edits a card with the data provided by sabredav
* @param integer $id Addressbook id
* @param string $uri the uri of the card
* @param string $data vCard file
* @return boolean
*/
public static function editFromDAVData($aid, $uri, $data) {
$oldcard = self::findWhereDAVDataIs($aid, $uri);
try {
$vcard = \Sabre\VObject\Reader::read($data);
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.
', Unable to parse VCARD, : ' . $e->getMessage(), \OCP\Util::ERROR);
return false;
}
try {
self::edit($oldcard['id'], $vcard);
return true;
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.', exception: '
. $e->getMessage() . ', '
. \OCP\USER::getUser(), \OCP\Util::ERROR);
\OCP\Util::writeLog('contacts', __METHOD__.', uri'
. $uri, \OCP\Util::DEBUG);
return false;
}
}
/**
* @brief deletes a card
* @param integer $id id of card
* @return boolean true on success, otherwise an exception will be thrown
*/
public static function delete($id) {
$contact = self::find($id);
if (!$contact) {
\OCP\Util::writeLog('contacts', __METHOD__.', id: '
. $id . ' not found.', \OCP\Util::DEBUG);
throw new \Exception(
App::$l10n->t(
'Could not find the vCard with ID: ' . $id, 404
)
);
}
$addressbook = Addressbook::find($contact['addressbookid']);
if(!$addressbook) {
throw new \Exception(
App::$l10n->t(
'Could not find the Addressbook with ID: '
. $contact['addressbookid'], 404
)
);
}
if ($addressbook['userid'] != \OCP\User::getUser() && !\OC_Group::inGroup(\OCP\User::getUser(), 'admin')) {
\OCP\Util::writeLog('contacts', __METHOD__.', '
. $addressbook['userid'] . ' != ' . \OCP\User::getUser(), \OCP\Util::DEBUG);
$sharedAddressbook = \OCP\Share::getItemSharedWithBySource(
'addressbook',
$contact['addressbookid'],
\OCP\Share::FORMAT_NONE, null, true);
$sharedContact = \OCP\Share::getItemSharedWithBySource(
'contact',
$id,
\OCP\Share::FORMAT_NONE, null, true);
$addressbook_permissions = 0;
$contact_permissions = 0;
if ($sharedAddressbook) {
$addressbook_permissions = $sharedAddressbook['permissions'];
}
if ($sharedContact) {
$contact_permissions = $sharedEvent['permissions'];
}
$permissions = max($addressbook_permissions, $contact_permissions);
if (!($permissions & \OCP\PERMISSION_DELETE)) {
throw new \Exception(
App::$l10n->t(
'You do not have the permissions to delete this contact.', 403
)
);
}
}
$aid = $contact['addressbookid'];
\OC_Hook::emit('\OCA\Contacts\VCard', 'pre_deleteVCard',
array('aid' => null, 'id' => $id, 'uri' => null)
);
$stmt = \OCP\DB::prepare('DELETE FROM `*PREFIX*contacts_cards` WHERE `id` = ?');
try {
$stmt->execute(array($id));
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.
', exception: ' . $e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::writeLog('contacts', __METHOD__.', id: '
. $id, \OCP\Util::DEBUG);
throw new \Exception(
App::$l10n->t(
'There was an error deleting this contact.'
)
);
}
App::updateDBProperties($id);
App::getVCategories()->purgeObjects(array($id));
Addressbook::touch($addressbook['id']);
// Contact sharing not enabled, so comment out this
//\OCP\Share::unshareAll('contact', $id);
return true;
}
/**
* @brief deletes a card with the data provided by sabredav
* @param integer $aid Addressbook id
* @param string $uri the uri of the card
* @return boolean
*/
public static function deleteFromDAVData($aid, $uri) {
$contact = self::findWhereDAVDataIs($aid, $uri);
if(!$contact) {
\OCP\Util::writeLog('contacts', __METHOD__.', contact not found: '
. $uri, \OCP\Util::DEBUG);
throw new \Sabre_DAV_Exception_NotFound(
App::$l10n->t(
'Contact not found.'
)
);
}
$id = $contact['id'];
try {
return self::delete($id);
} catch (\Exception $e) {
switch($e->getCode()) {
case 403:
\OCP\Util::writeLog('contacts', __METHOD__.', forbidden: '
. $uri, \OCP\Util::DEBUG);
throw new \Sabre_DAV_Exception_Forbidden(
App::$l10n->t(
$e->getMessage()
)
);
break;
case 404:
\OCP\Util::writeLog('contacts', __METHOD__.', contact not found: '
. $uri, \OCP\Util::DEBUG);
throw new \Sabre_DAV_Exception_NotFound(
App::$l10n->t(
$e->getMessage()
)
);
break;
default:
throw $e;
break;
}
}
return true;
}
/**
* @brief Data structure of vCard
* @param Sabre\VObject\Component $property
* @return associative array
*
* look at code ...
*/
public static function structureContact($vcard) {
$details = array();
foreach($vcard->children as $property) {
$pname = $property->name;
$temp = self::structureProperty($property);
if(!is_null($temp)) {
// Get Apple X-ABLabels
if(isset($vcard->{$property->group . '.X-ABLABEL'})) {
$temp['label'] = $vcard->{$property->group . '.X-ABLABEL'}->value;
if($temp['label'] == '_$!<Other>!$_') {
$temp['label'] = App::$l10n->t('Other');
}
if($temp['label'] == '_$!<HomePage>!$_') {
$temp['label'] = App::$l10n->t('HomePage');
}
}
if(array_key_exists($pname, $details)) {
$details[$pname][] = $temp;
}
else{
$details[$pname] = array($temp);
}
}
}
return $details;
}
/**
* @brief Data structure of properties
* @param object $property
* @return associative array
*
* returns an associative array with
* ['name'] name of property
* ['value'] htmlspecialchars escaped value of property
* ['parameters'] associative array name=>value
* ['checksum'] checksum of whole property
* NOTE: $value is not escaped anymore. It shouldn't make any difference
* but we should look out for any problems.
*/
public static function structureProperty($property) {
if(!in_array($property->name, App::$index_properties)) {
return;
}
$value = $property->value;
if($property->name == 'ADR' || $property->name == 'N' || $property->name == 'ORG' || $property->name == 'CATEGORIES') {
$value = $property->getParts();
$value = array_map('trim', $value);
}
elseif($property->name == 'BDAY') {
if(strpos($value, '-') === false) {
if(strlen($value) >= 8) {
$value = substr($value, 0, 4).'-'.substr($value, 4, 2).'-'.substr($value, 6, 2);
} else {
return null; // Badly malformed :-(
}
}
} elseif($property->name == 'PHOTO') {
$value = true;
}
elseif($property->name == 'IMPP') {
if(strpos($value, ':') !== false) {
$value = explode(':', $value);
$protocol = array_shift($value);
if(!isset($property['X-SERVICE-TYPE'])) {
$property['X-SERVICE-TYPE'] = strtoupper(\OCP\Util::sanitizeHTML($protocol));
}
$value = implode('', $value);
}
}
if(is_string($value)) {
$value = strtr($value, array('\,' => ',', '\;' => ';'));
}
$temp = array(
//'name' => $property->name,
'value' => $value,
'parameters' => array()
);
// This cuts around a 3rd off of the json response size.
if(in_array($property->name, App::$multi_properties)) {
$temp['checksum'] = substr(md5($property->serialize()), 0, 8);
}
foreach($property->parameters as $parameter) {
// Faulty entries by kaddressbook
// Actually TYPE=PREF is correct according to RFC 2426
// but this way is more handy in the UI. Tanghus.
if($parameter->name == 'TYPE' && strtoupper($parameter->value) == 'PREF') {
$parameter->name = 'PREF';
$parameter->value = '1';
}
// NOTE: Apparently Sabre_VObject_Reader can't always deal with value list parameters
// like TYPE=HOME,CELL,VOICE. Tanghus.
// TODO: Check if parameter is has commas and split + merge if so.
if ($parameter->name == 'TYPE') {
$pvalue = $parameter->value;
if(is_string($pvalue) && strpos($pvalue, ',') !== false) {
$pvalue = array_map('trim', explode(',', $pvalue));
}
$pvalue = is_array($pvalue) ? $pvalue : array($pvalue);
if (isset($temp['parameters'][$parameter->name])) {
$temp['parameters'][$parameter->name][] = \OCP\Util::sanitizeHTML($pvalue);
}
else {
$temp['parameters'][$parameter->name] = \OCP\Util::sanitizeHTML($pvalue);
}
}
else{
$temp['parameters'][$parameter->name] = \OCP\Util::sanitizeHTML($parameter->value);
}
}
return $temp;
}
/**
* @brief Move card(s) to an address book
* @param integer $aid Address book id
* @param $id Array or integer of cards to be moved.
* @return boolean
*
*/
public static function moveToAddressBook($aid, $id, $isAddressbook = false) {
Addressbook::find($aid);
$addressbook = Addressbook::find($aid);
if ($addressbook['userid'] != \OCP\User::getUser()) {
$sharedAddressbook = \OCP\Share::getItemSharedWithBySource('addressbook', $aid);
if (!$sharedAddressbook || !($sharedAddressbook['permissions'] & \OCP\PERMISSION_CREATE)) {
return false;
}
}
if(is_array($id)) {
foreach ($id as $index => $cardId) {
$card = self::find($cardId);
if (!$card) {
unset($id[$index]);
}
$oldAddressbook = Addressbook::find($card['addressbookid']);
if ($oldAddressbook['userid'] != \OCP\User::getUser()) {
$sharedContact = \OCP\Share::getItemSharedWithBySource('contact', $cardId, \OCP\Share::FORMAT_NONE, null, true);
if (!$sharedContact || !($sharedContact['permissions'] & \OCP\PERMISSION_DELETE)) {
unset($id[$index]);
}
}
}
$id_sql = join(',', array_fill(0, count($id), '?'));
$prep = 'UPDATE `*PREFIX*contacts_cards` SET `addressbookid` = ? WHERE `id` IN ('.$id_sql.')';
try {
$stmt = \OCP\DB::prepare( $prep );
//$aid = array($aid);
$vals = array_merge((array)$aid, $id);
$result = $stmt->execute($vals);
if (\OC_DB::isError($result)) {
\OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OC_Log::ERROR);
return false;
}
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR);
\OCP\Util::writeLog('contacts', __METHOD__.', ids: '.join(',', $vals), \OCP\Util::DEBUG);
\OCP\Util::writeLog('contacts', __METHOD__.', SQL:'.$prep, \OCP\Util::DEBUG);
return false;
}
} else {
$stmt = null;
if($isAddressbook) {
$stmt = \OCP\DB::prepare( 'UPDATE `*PREFIX*contacts_cards` SET `addressbookid` = ? WHERE `addressbookid` = ?' );
} else {
$card = self::find($id);
if (!$card) {
return false;
}
$oldAddressbook = Addressbook::find($card['addressbookid']);
if ($oldAddressbook['userid'] != \OCP\User::getUser()) {
$sharedContact = \OCP\Share::getItemSharedWithBySource('contact', $id, \OCP\Share::FORMAT_NONE, null, true);
if (!$sharedContact || !($sharedContact['permissions'] & \OCP\PERMISSION_DELETE)) {
return false;
}
}
$stmt = \OCP\DB::prepare( 'UPDATE `*PREFIX*contacts_cards` SET `addressbookid` = ? WHERE `id` = ?' );
}
try {
$result = $stmt->execute(array($aid, $id));
if (\OC_DB::isError($result)) {
\OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OC_Log::ERROR);
return false;
}
} catch(\Exception $e) {
\OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::DEBUG);
\OCP\Util::writeLog('contacts', __METHOD__.' id: '.$id, \OCP\Util::DEBUG);
return false;
}
}
\OC_Hook::emit('\OCA\Contacts\VCard', 'post_moveToAddressbook', array('aid' => $aid, 'id' => $id));
Addressbook::touch($aid);
return true;
}
}

View File

@ -0,0 +1,82 @@
<?php
/**
* ownCloud - VObject String Property
*
* This class adds escaping/unescaping of simple string properties.
*
* @author Thomas Tanghus
* @author Evert Pot (http://www.rooftopsolutions.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/>.
*
*/
namespace OCA\Contacts\VObject;
use Sabre\VObject;
/**
* This class overrides \Sabre\VObject\Property::serialize() properly
* escape commas and semi-colons in string properties.
*/
class StringProperty extends VObject\Property {
/**
* Turns the object back into a serialized blob.
*
* @return string
*/
public function serialize() {
$str = $this->name;
if ($this->group) {
$str = $this->group . '.' . $this->name;
}
foreach($this->parameters as $param) {
$str.=';' . $param->serialize();
}
$src = array(
'\\',
"\n",
';',
',',
);
$out = array(
'\\\\',
'\n',
'\;',
'\,',
);
$value = strtr($this->value, array('\,' => ',', '\;' => ';', '\\\\' => '\\'));
$str.=':' . str_replace($src, $out, $value);
$out = '';
while(strlen($str) > 0) {
if (strlen($str) > 75) {
$out .= mb_strcut($str, 0, 75, 'utf-8') . "\r\n";
$str = ' ' . mb_strcut($str, 75, strlen($str), 'utf-8');
} else {
$out .= $str . "\r\n";
$str = '';
break;
}
}
return $out;
}
}

229
lib/vobject/vcard.php Normal file
View File

@ -0,0 +1,229 @@
<?php
/**
* ownCloud - VCard component
*
* This component represents the BEGIN:VCARD and END:VCARD found in every
* vcard.
*
* @author Thomas Tanghus
* @author Evert Pot (http://www.rooftopsolutions.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/>.
*
*/
namespace OCA\Contacts\VObject;
use OCA\Contacts\Utils;
use Sabre\VObject;
/**
* This class overrides \Sabre\VObject\Component\VCard::validate() to be add
* to import partially invalid vCards by ignoring invalid lines and to
* validate and upgrade using ....
*/
class VCard extends VObject\Component\VCard {
/**
* The following constants are used by the validate() method.
*/
const REPAIR = 1;
const UPGRADE = 2;
/**
* VCards with version 2.1, 3.0 and 4.0 are found.
*
* If the VCARD doesn't know its version, 3.0 is assumed and if
* option UPGRADE is given it will be upgraded to version 3.0.
*/
const DEFAULT_VERSION = '3.0';
/**
* @brief Format property TYPE parameters for upgrading from v. 2.1
* @param $property Reference to a \Sabre\VObject\Property.
* In version 2.1 e.g. a phone can be formatted like: TEL;HOME;CELL:123456789
* This has to be changed to either TEL;TYPE=HOME,CELL:123456789 or TEL;TYPE=HOME;TYPE=CELL:123456789 - both are valid.
*/
protected function formatPropertyTypes(&$property) {
foreach($property->parameters as $key=>&$parameter) {
$types = Utils\Properties::getTypesForProperty($property->name);
if(is_array($types) && in_array(strtoupper($parameter->name), array_keys($types))
|| strtoupper($parameter->name) == 'PREF') {
unset($property->parameters[$key]);
$property->add('TYPE', $parameter->name);
}
}
}
/**
* @brief Decode properties for upgrading from v. 2.1
* @param $property Reference to a \Sabre\VObject\Property.
* The only encoding allowed in version 3.0 is 'b' for binary. All encoded strings
* must therefore be decoded and the parameters removed.
*/
protected function decodeProperty(&$property) {
// Check out for encoded string and decode them :-[
foreach($property->parameters as $key=>&$parameter) {
if(strtoupper($parameter->name) == 'ENCODING') {
if(strtoupper($parameter->value) == 'QUOTED-PRINTABLE') {
// Decode quoted-printable and strip any control chars
// except \n and \r
$property->value = str_replace(
"\r\n", "\n",
VObject\StringUtil::convertToUTF8(
quoted_printable_decode($property->value)
)
);
unset($property->parameters[$key]);
} else if(strtoupper($parameter->value) == 'BASE64') {
$parameter->value = 'b';
}
} elseif(strtoupper($parameter->name) == 'CHARSET') {
unset($property->parameters[$key]);
}
}
}
/**
* Validates the node for correctness.
*
* The following options are supported:
* - VCard::REPAIR - If something is broken, and automatic repair may
* be attempted.
* - VCard::UPGRADE - If needed the vCard will be upgraded to version 3.0.
*
* An array is returned with warnings.
*
* Every item in the array has the following properties:
* * level - (number between 1 and 3 with severity information)
* * message - (human readable message)
* * node - (reference to the offending node)
*
* @param int $options
* @return array
*/
public function validate($options = 0) {
$warnings = array();
$version = $this->select('VERSION');
if (count($version) !== 1) {
$warnings[] = array(
'level' => 1,
'message' => 'The VERSION property must appear in the VCARD component exactly 1 time',
'node' => $this,
);
if ($options & self::REPAIR) {
$this->VERSION = self::DEFAULT_VERSION;
if (!$options & self::UPGRADE) {
$options |= self::UPGRADE;
}
}
} else {
$version = (string)$this->VERSION;
if ($version!=='2.1' && $version!=='3.0' && $version!=='4.0') {
$warnings[] = array(
'level' => 1,
'message' => 'Only vcard version 4.0 (RFC6350), version 3.0 (RFC2426) or version 2.1 (icm-vcard-2.1) are supported.',
'node' => $this,
);
if ($options & self::REPAIR) {
$this->VERSION = self::DEFAULT_VERSION;
if (!$options & self::UPGRADE) {
$options |= self::UPGRADE;
}
}
}
}
$fn = $this->select('FN');
if (count($fn) !== 1) {
$warnings[] = array(
'level' => 1,
'message' => 'The FN property must appear in the VCARD component exactly 1 time',
'node' => $this,
);
if (($options & self::REPAIR) && count($fn) === 0) {
// We're going to try to see if we can use the contents of the
// N property.
if (isset($this->N)) {
$value = explode(';', (string)$this->N);
if (isset($value[1]) && $value[1]) {
$this->FN = $value[1] . ' ' . $value[0];
} else {
$this->FN = $value[0];
}
// Otherwise, the ORG property may work
} elseif (isset($this->ORG)) {
$this->FN = (string)$this->ORG;
} elseif (isset($this->EMAIL)) {
$this->FN = (string)$this->EMAIL;
}
}
}
$n = $this->select('N');
if (count($n) !== 1) {
$warnings[] = array(
'level' => 1,
'message' => 'The N property must appear in the VCARD component exactly 1 time',
'node' => $this,
);
// TODO: Make a better effort parsing FN.
if (($options & self::REPAIR) && count($n) === 0) {
// Take 2 first name parts of 'FN' and reverse.
$slice = array_reverse(array_slice(explode(' ', (string)$this->FN), 0, 2));
if(count($slice) < 2) { // If not enought, add one more...
$slice[] = "";
}
$this->N = implode(';', $slice).';;;';
}
}
if (!isset($this->UID)) {
$warnings[] = array(
'level' => 1,
'message' => 'Every vCard must have a UID',
'node' => $this,
);
if ($options & self::REPAIR) {
$this->UID = Utils\Properties::generateUID();
}
}
if ($options & self::UPGRADE) {
$this->VERSION = self::DEFAULT_VERSION;
foreach($this->children as &$property) {
$this->decodeProperty($property);
$this->formatPropertyTypes($property);
//\OCP\Util::writeLog('contacts', __METHOD__.' upgrade: '.$property->name, \OCP\Util::DEBUG);
switch((string)$property->name) {
case 'LOGO':
case 'SOUND':
case 'PHOTO':
if(isset($property['TYPE']) && strpos((string)$property['TYPE'], '/') === false) {
$property['TYPE'] = 'image/' . strtolower($property['TYPE']);
}
}
}
}
return array_merge(
parent::validate($options),
$warnings
);
}
}

View File

@ -21,6 +21,8 @@ function getStandardImage() {
}
$id = isset($_GET['id']) ? $_GET['id'] : null;
$parent = isset($_GET['parent']) ? $_GET['parent'] : null;
$backend = isset($_GET['backend']) ? $_GET['backend'] : null;
$etag = null;
$caching = null;
$max_size = 170;
@ -35,7 +37,8 @@ if(!extension_loaded('gd') || !function_exists('gd_info')) {
getStandardImage();
}
$contact = OCA\Contacts\App::getContactVCard($id);
$app = new OCA\Contacts\App();
$contact = $app->getContact($backend, $parent, $id);
$image = new OC_Image();
if (!$image || !$contact) {
getStandardImage();
@ -58,10 +61,10 @@ if (is_null($contact)) {
$etag = md5($contact->LOGO);
}
if ($image->valid()) {
$modified = OCA\Contacts\App::lastModified($contact);
$modified = $contact->lastModified();
// Force refresh if modified within the last minute.
if(!is_null($modified)) {
$caching = (time() - $modified->format('U') > 60) ? null : 0;
$caching = (time() - $modified > 60) ? null : 0;
}
OCP\Response::enableCaching($caching);
if(!is_null($modified)) {

View File

@ -1,5 +1,7 @@
<form class="float" id="file_upload_form" action="<?php print_unescaped(OCP\Util::linkTo('contacts', 'ajax/uploadphoto.php')); ?>" method="post" enctype="multipart/form-data" target="file_upload_target">
<input type="hidden" name="id" value="">
<input type="hidden" name="contactid" value="">
<input type="hidden" name="addressbookid" value="">
<input type="hidden" name="backend" value="">
<input type="hidden" name="requesttoken" value="<?php p($_['requesttoken']) ?>">
<input type="hidden" name="MAX_FILE_SIZE" value="<?php p($_['uploadMaxFilesize']) ?>" id="max_upload">
<input type="hidden" class="max_human_file_size" value="(max <?php p($_['uploadMaxHumanFilesize']); ?>)">
@ -25,17 +27,30 @@
<button class="addaddressbookcancel"><?php p($l->t('Cancel')); ?></button>
</li>
</ul>
<button class="addaddressbook icon-plus text"><?php p($l->t('New')); ?></button>
<ul class="hidden">
<li><input class="addaddressbookinput" type="text" placeholder="<?php p($l->t('Display name')); ?>" /></li>
<li>
<button class="addaddressbookok"><?php p($l->t('OK')); ?></button>
<button class="addaddressbookcancel"><?php p($l->t('Cancel')); ?></button>
</li>
</ul>
<h2 data-id="import" tabindex="0" role="button"><?php p($l->t('Import')); ?></h2>
<ul class="hidden">
<li class="import-upload">
<form id="import_upload_form" action="<?php print_unescaped(OCP\Util::linkTo('contacts', 'ajax/uploadimport.php')); ?>" method="post" enctype="multipart/form-data" target="import_upload_target">
<form
id="import_upload_form"
data-upload-id="1"
action="<?php print_unescaped(OCP\Util::linkTo('contacts', 'ajax/uploadimport.php')); ?>"
method="post" enctype="multipart/form-data"
target="import_upload_target_1">
<input type="hidden" name="MAX_FILE_SIZE" value="<?php p($_['uploadMaxFilesize']) ?>" id="max_upload">
<label for="import_fileupload"><?php p($l->t('Select files to import')); ?>
<label for="import_upload_start"><?php p($l->t('Select files to import')); ?>
<button class="import-upload-button" title="<?php p($l->t('Select files')); ?>"></button>
</label>
<input id="import_fileupload" type="file" accept="text/vcard,text/x-vcard,text/directory" multiple="multiple" name="importfile" />
<input id="import_upload_start" type="file" accept="text/vcard,text/x-vcard,text/directory" multiple="multiple" name="file" />
</form>
<iframe name="import_upload_target" id='import_upload_target' src=""></iframe>
<!-- iframe name="import_upload_target" id='import_upload_target' src=""></iframe -->
</li>
<li class="import-select hidden"><label><?php p($l->t('Import into:')); ?></label></li>
<li class="import-select hidden">
@ -59,6 +74,7 @@
<option value="-1" disabled="disabled" selected="selected"><?php p($l->t('Groups')); ?></option>
</select>
<button class="favorite action svg inactive control" title="<?php p($l->t('Favorite')); ?>"></button>
<button class="merge"><?php p($l->t('Merge selected')); ?></button>
<a class="delete action" title="<?php p($l->t('Delete Contact')); ?>"></a>
</div>
</div>
@ -113,7 +129,9 @@
enctype="multipart/form-data"
target="crop_target"
action="<?php print_unescaped(OCP\Util::linkToAbsolute('contacts', 'ajax/savecrop.php')); ?>">
<input type="hidden" id="id" name="id" value="{id}" />
<input type="hidden" id="contactid" name="contactid" value="{contactid}" />
<input type="hidden" id="addressbookid" name="addressbookid" value="{addressbookid}" />
<input type="hidden" id="backend" name="backend" value="{backend}" />
<input type="hidden" id="tmpkey" name="tmpkey" value="{tmpkey}" />
<fieldset id="coords">
<input type="hidden" id="x1" name="x1" value="" />
@ -134,10 +152,24 @@
</div>
</script>
<script id="mergeContactsTemplate" type="text/template">
<div id="dialog-merge-contacts" title="<?php p($l->t('Merge contacts')); ?>">
<p><?php p($l->t('Which contact should the data be merged into?')); ?></p>
<fieldset>
<ul class="mergelist">
<li><input id="mergee_{idx}" type="radio" name="contact" value="{id}"><label for="mergee_{idx}" >{displayname}</label></li>
</ul>
</fieldset>
<p>
<input type="checkbox" id="delete_other" name="delete_other" />
<label for="delete_other"><?php p($l->t('Delete the other(s) after successful merge?')); ?></label>
</p>
</div>
</script>
<script id="contactListItemTemplate" type="text/template">
<tr class="contact" data-id="{id}">
<td class="name"
style="background: url('<?php print_unescaped(OC_Helper::linkToRemoteBase('contactthumbnail')); ?>?id={id}')">
<td class="name thumbnail">
<input type="checkbox" name="id" value="{id}" /><span class="nametext">{name}</span>
</td>
<td class="email">
@ -151,8 +183,7 @@
</script>
<script id="contactDragItemTemplate" type="text/template">
<div class="dragContact" data-id="{id}"
style="background: url('<?php print_unescaped(OC_Helper::linkToRemoteBase('contactthumbnail')); ?>?id={id}')">
<div class="dragContact thumbnail" data-id="{id}">
{name}
</div>
</script>
@ -173,10 +204,10 @@
<li>
<div id="photowrapper" class="propertycontainer" data-element="photo">
<ul id="phototools" class="transparent hidden">
<li><a class="action delete" title="<?php p($l->t('Delete current photo')); ?>"></a></li>
<li><a class="action edit" title="<?php p($l->t('Edit current photo')); ?>"></a></li>
<li><a class="action upload" title="<?php p($l->t('Upload new photo')); ?>"></a></li>
<li><a class="action cloud icon-cloud" title="<?php p($l->t('Select photo from ownCloud')); ?>"></a></li>
<li><a class="action delete" title="<?php echo $l->t('Delete current photo'); ?>"></a></li>
<li><a class="action edit" title="<?php echo $l->t('Edit current photo'); ?>"></a></li>
<li><a class="action upload" title="<?php echo $l->t('Upload new photo'); ?>"></a></li>
<li><a class="action cloud icon-cloud" title="<?php echo $l->t('Select photo from ownCloud'); ?>"></a></li>
</ul>
<a class="favorite action {favorite}"></a>
</div>
@ -207,6 +238,9 @@
<div class="groupscontainer propertycontainer" data-element="categories">
<select id="contactgroups" title="<?php p($l->t('Select groups')); ?>" name="value" multiple></select>
</div>
<div>
<select class="hidden" id="contactaddressbooks" title="<?php p($l->t('Select address book')); ?>" name="value" multiple></select>
</div>
<dl class="form">
<dt data-element="nickname">
<?php p($l->t('Nickname')); ?>
@ -289,7 +323,7 @@
</script>
<script id="contactDetailsTemplate" class="hidden" type="text/template">
<div class="email">
<div class="email" type="text/template">
<li data-element="email" data-checksum="{checksum}" class="propertycontainer">
<span class="parameters">
<select class="rtl type parameter" data-parameter="TYPE" name="parameters[TYPE][]">
@ -304,7 +338,7 @@
</span>
</li>
</div>
<div class="tel">
<div class="tel" type="text/template">
<li data-element="tel" data-checksum="{checksum}" class="propertycontainer">
<span class="parameters">
<select class="rtl type parameter" data-parameter="TYPE" name="parameters[TYPE][]">
@ -318,7 +352,7 @@
</span>
</li>
</div>
<div class="url">
<div class="url" type="text/template">
<li data-element="url" data-checksum="{checksum}" class="propertycontainer">
<span class="parameters">
<select class="rtl type parameter" data-parameter="TYPE" name="parameters[TYPE][]">
@ -333,7 +367,7 @@
</span>
</li>
</div>
<div class="adr">
<div class="adr" type="text/template">
<li data-element="adr" data-checksum="{checksum}" data-lang="<?php p(OCP\Config::getUserValue(OCP\USER::getUser(), 'core', 'lang', 'en')); ?>" class="propertycontainer">
<span class="float display">
<label class="meta parameters"></label>
@ -380,7 +414,7 @@
</fieldset>
</li>
</div>
<div class="impp">
<div class="impp" type="text/template">
<li data-element="impp" data-checksum="{checksum}" class="propertycontainer">
<span class="parameters">
<select class="type parameter" data-parameter="TYPE" name="parameters[TYPE][]">

121
tests/contacts.php Normal file
View File

@ -0,0 +1,121 @@
<?php
/**
* Copyright (c) 2013 Thomas Tanghus (thomas@tanghus.net)
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
OC_App::loadApp('contacts');
class Test_Contacts_Backend_Datebase extends PHPUnit_Framework_TestCase {
protected static $schema_file = 'static://test_db_scheme';
protected static $test_prefix;
protected static $backend;
protected static $user;
protected static $addressBooksTableName;
protected static $cardsTableName;
public static function setUpBeforeClass() {
$dbfile = __DIR__.'/../appinfo/database.xml';
self::$test_prefix = '_'.OC_Util::generate_random_bytes('4').'_';
$content = file_get_contents($dbfile);
$content = str_replace( '*dbprefix*', '*dbprefix*'.self::$test_prefix, $content );
file_put_contents( self::$schema_file, $content );
OC_DB::createDbFromStructure(self::$schema_file);
self::$addressBooksTableName = '*PREFIX*'.self::$test_prefix.'contacts_addressbooks';
self::$cardsTableName = '*PREFIX*'.self::$test_prefix.'contacts_cards';
OC_User::clearBackends();
OC_User::useBackend('dummy');
self::$user = uniqid('user_');
OC_User::createUser(self::$user, 'pass');
OC_User::setUserId(self::$user);
self::$backend = new OCA\Contacts\Backend\Database(
self::$user,
self::$addressBooksTableName,
self::$cardsTableName
);
}
public static function tearDownAfterClass() {
OC_DB::removeDBStructure(self::$schema_file);
unlink(self::$schema_file);
}
public function testDatabaseBackend() {
$this->assertEquals(array(), self::$backend->getAddressBooksForUser());
$aid = self::$backend->createAddressBook(
array(
'displayname' => 'Contacts',
'description' => 'My Contacts',
)
);
// Test address books
$this->assertEquals(1, count(self::$backend->getAddressBooksForUser()));
$this->assertTrue(self::$backend->hasAddressBook($aid));
$addressBook = self::$backend->getAddressBook($aid);
$this->assertEquals('Contacts', $addressBook['displayname']);
$this->assertEquals('My Contacts', $addressBook['description']);
self::$backend->updateAddressBook($aid, array('description' => 'All my contacts'));
$addressBook = self::$backend->getAddressBook($aid);
$this->assertEquals('All my contacts', $addressBook['description']);
// Test contacts
$this->assertEquals(array(), self::$backend->getContacts($aid));
$carddata = file_get_contents(__DIR__ . '/data/test.vcf');
$id = self::$backend->createContact($aid, $carddata);
$this->assertNotEquals(false, $id); // Isn't there an assertNotFalse() ?
$this->assertEquals(1, count(self::$backend->getContacts($aid)));
$this->assertTrue(self::$backend->hasContact($aid, $id));
$contact = self::$backend->getContact($aid, $id);
$this->assertEquals('Max Mustermann', $contact['displayname']);
$carddata = file_get_contents(__DIR__ . '/data/test2.vcf');
$this->assertTrue(self::$backend->updateContact($aid, $id, $carddata));
$contact = self::$backend->getContact($aid, $id);
$this->assertEquals('John Q. Public', $contact['displayname']);
$this->assertTrue(self::$backend->deleteContact($aid, $id));
$this->assertTrue(self::$backend->deleteAddressBook($aid));
}
public function testAddressBook() {
$addressBook = new OCA\Contacts\AddressBook(
self::$backend,
array(
'displayname' => 'Contacts',
'description' => 'My Contacts',
)
);
$this->assertEquals(0, count($addressBook));
$id = $addressBook->addChild(
array(
'displayname' => 'John Q. Public'
)
);
$this->assertNotEquals(false, $id);
$this->assertEquals(1, count($addressBook));
$contact = $addressBook->getChild($id);
$this->assertEquals('John Q. Public', (string)$contact->FN);
$contact->FN = 'Max Mustermann';
$contact->save();
$contact = $addressBook[$id];
$metadata = $contact->getMetaData();
$this->assertEquals('Max Mustermann', $metadata['displayname']);
// Array access
$this->assertEquals($contact, $addressBook[$id]);
$this->assertTrue(isset($addressBook[$id]));
// Magic accessors
//$this->assertEquals($contact, $addressBook->{$id});
$this->assertTrue($addressBook->deleteChild($id));
$this->assertEquals(0, count($addressBook));
}
}

11
tests/data/test.vcf Normal file
View File

@ -0,0 +1,11 @@
BEGIN:VCARD
VERSION:3.0
N:Mustermann;Max;;;
FN:Max Mustermann
ORG:ownCloud
URL:http://owncloud.org/
UID:e472ea61
EMAIL;TYPE=INTERNET:max.mustermann@example.org
TEL;TYPE=voice,work,pref:+49 1234 56788
ADR;TYPE=intl,work,postal,parcel:;;Musterstraße 1;Musterstadt;;12345;Germany
END:VCARD

11
tests/data/test2.vcf Normal file
View File

@ -0,0 +1,11 @@
BEGIN:VCARD
VERSION:3.0
N:Public;John;Quinlan;;
FN:John Q. Public
ORG:ownCloud
URL:http://owncloud.org/
UID:e472ea61
EMAIL;TYPE=INTERNET:joe.public@example.org
TEL;TYPE=voice,work,pref:+49 1234 56788
ADR;TYPE=intl,work,postal,parcel:;;Elm Street 1;Metropolis;;12345;USA
END:VCARD

91
tests/request.php Normal file
View File

@ -0,0 +1,91 @@
<?php
/**
* Copyright (c) 2013 Thomas Tanghus (thomas@tanghus.net)
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
OC_App::loadApp('contacts');
class Test_Contacts_Request extends PHPUnit_Framework_TestCase {
protected static $user;
public static function setUpBeforeClass() {
OC_User::clearBackends();
OC_User::useBackend('dummy');
self::$user = uniqid('user_');
OC_User::createUser(self::$user, 'pass');
OC_User::setUserId(self::$user);
}
public function testRequestAccessors() {
$vars = array(
'get' => array('name' => 'John Q. Public', 'nickname' => 'Joey'),
'urlParams' => array('user' => self::$user, 'trut' => 'trat'),
);
$request = new OCA\Contacts\Request($vars);
$this->assertEquals(4, count($request));
$this->assertEquals('Joey', $request['nickname']);
$this->assertEquals('Joey', $request->{'nickname'});
$this->assertEquals('Joey', $request->get['nickname']);
$this->assertEquals('Joey', $request->getVar('get', 'nickname'));
$this->assertTrue(isset($request['nickname']));
$this->assertTrue(isset($request->{'nickname'}));
$this->assertEquals(false, isset($request->{'flickname'}));
$this->assertEquals(null, $request->{'flickname'});
}
// urlParams has precedence over POST which has precedence over GET
public function testPrecedence() {
$vars = array(
'get' => array('name' => 'John Q. Public', 'nickname' => 'Joey'),
'post' => array('name' => 'Jane Doe', 'nickname' => 'Janey'),
'urlParams' => array('user' => self::$user, 'name' => 'Johnny Weissmüller'),
);
$request = new OCA\Contacts\Request($vars);
$this->assertEquals(3, count($request));
$this->assertEquals('Janey', $request->{'nickname'});
$this->assertEquals('Johnny Weissmüller', $request->{'name'});
}
// Test default value
public function testDefaultValue() {
$vars = array(
'get' => array('name' => 'John Q. Public', 'nickname' => 'Joey'),
);
$request = new OCA\Contacts\Request($vars);
$this->assertEquals(2, count($request));
$this->assertEquals('Joey', $request->getVar('get', 'nickname', 'Janey'));
$this->assertEquals('Apocalypse Now', $request->getVar('get', 'flickname', 'Apocalypse Now'));
}
/**
* @expectedException RuntimeException
*/
public function testImmutableArrayAccess() {
$vars = array(
'get' => array('name' => 'John Q. Public', 'nickname' => 'Joey'),
);
$request = new OCA\Contacts\Request($vars);
$request['nickname'] = 'Janey';
}
/**
* @expectedException RuntimeException
*/
public function testImmutableMagicAccess() {
$vars = array(
'get' => array('name' => 'John Q. Public', 'nickname' => 'Joey'),
);
$request = new OCA\Contacts\Request($vars);
$request->{'nickname'} = 'Janey';
}
}

View File

@ -27,30 +27,36 @@ session_write_close();
//OCP\Util::writeLog('contacts', OCP\Util::getRequestUri(), OCP\Util::DEBUG);
function getStandardImage() {
$image = new \OC_Image();
$file = __DIR__ . DIRECTORY_SEPARATOR . 'img' . DIRECTORY_SEPARATOR . 'person.png';
OCP\Response::setLastModifiedHeader(filemtime($file));
OCP\Response::enableCaching();
OCP\Response::redirect(OCP\Util::imagePath('contacts', 'person.png'));
$image->loadFromFile($file);
$image();
exit();
}
if(!extension_loaded('gd') || !function_exists('gd_info')) {
OCP\Util::writeLog('contacts',
'thumbnail.php. GD module not installed', OCP\Util::DEBUG);
getStandardImage();
OCP\Response::enableCaching();
OCP\Response::redirect(OCP\Util::imagePath('contacts', 'person.png'));
exit();
}
$id = $_GET['id'];
$parent = $_GET['parent'];
$backend = $_GET['backend'];
$caching = null;
$image = OCA\Contacts\App::cacheThumbnail($id);
$app = new OCA\Contacts\App();
$contact = $app->getContact($backend, $parent, $id);
$image = $contact->cacheThumbnail();
if($image !== false) {
try {
$modified = OCA\Contacts\App::lastModified($id);
} catch(Exception $e) {
getStandardImage();
}
$modified = $contact->lastModified();
// Force refresh if modified within the last minute.
if(!is_null($modified)) {
$caching = (time() - $modified->format('U') > 60) ? null : 0;
$caching = (time() - $modified > 60) ? null : 0;
OCP\Response::setLastModifiedHeader($modified);
}
OCP\Response::enableCaching($caching);