2013-02-19 02:21:29 +01:00
|
|
|
<?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/>.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2013-03-05 11:36:35 +01:00
|
|
|
namespace OCA\Contacts\VObject;
|
2013-02-19 02:21:29 +01:00
|
|
|
|
2013-04-09 17:20:30 +02:00
|
|
|
use OCA\Contacts\Utils;
|
2013-02-19 02:21:29 +01:00
|
|
|
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 ....
|
|
|
|
*/
|
2013-03-05 11:36:35 +01:00
|
|
|
class VCard extends VObject\Component\VCard {
|
2013-02-19 02:21:29 +01:00
|
|
|
|
2013-11-04 15:59:07 +01:00
|
|
|
const THUMBNAIL_PREFIX = 'contact-thumbnail-';
|
|
|
|
const THUMBNAIL_SIZE = 28;
|
|
|
|
|
2013-02-19 02:21:29 +01:00
|
|
|
/**
|
|
|
|
* The following constants are used by the validate() method.
|
|
|
|
*/
|
|
|
|
const REPAIR = 1;
|
|
|
|
const UPGRADE = 2;
|
|
|
|
|
2013-06-22 08:50:58 +02:00
|
|
|
/**
|
|
|
|
* The groups in the contained properties
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
protected $groups = array();
|
|
|
|
|
2013-02-19 02:21:29 +01:00
|
|
|
/**
|
|
|
|
* 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';
|
|
|
|
|
|
|
|
/**
|
2013-05-10 02:14:19 +02:00
|
|
|
* The vCard 2.1 specification allows parameter values without a name.
|
|
|
|
* The parameter name is then determined from the unique parameter value.
|
2013-02-19 02:21:29 +01:00
|
|
|
* 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.
|
2013-05-10 02:14:19 +02:00
|
|
|
*
|
|
|
|
* From: https://github.com/barnabywalters/vcard/blob/master/barnabywalters/VCard/VCard.php
|
|
|
|
*
|
|
|
|
* @param string value
|
|
|
|
* @return string
|
2013-02-19 02:21:29 +01:00
|
|
|
*/
|
2013-05-10 02:14:19 +02:00
|
|
|
protected function paramName($value) {
|
|
|
|
static $types = array (
|
|
|
|
'DOM', 'INTL', 'POSTAL', 'PARCEL','HOME', 'WORK',
|
|
|
|
'PREF', 'VOICE', 'FAX', 'MSG', 'CELL', 'PAGER',
|
|
|
|
'BBS', 'MODEM', 'CAR', 'ISDN', 'VIDEO',
|
|
|
|
'AOL', 'APPLELINK', 'ATTMAIL', 'CIS', 'EWORLD',
|
|
|
|
'INTERNET', 'IBMMAIL', 'MCIMAIL',
|
|
|
|
'POWERSHARE', 'PRODIGY', 'TLX', 'X400',
|
|
|
|
'GIF', 'CGM', 'WMF', 'BMP', 'MET', 'PMB', 'DIB',
|
|
|
|
'PICT', 'TIFF', 'PDF', 'PS', 'JPEG', 'QTIME',
|
|
|
|
'MPEG', 'MPEG2', 'AVI',
|
|
|
|
'WAVE', 'AIFF', 'PCM',
|
|
|
|
'X509', 'PGP');
|
|
|
|
static $values = array (
|
|
|
|
'INLINE', 'URL', 'CID');
|
|
|
|
static $encodings = array (
|
|
|
|
'7BIT', 'QUOTED-PRINTABLE', 'BASE64');
|
|
|
|
$name = 'UNKNOWN';
|
|
|
|
if (in_array($value, $types)) {
|
|
|
|
$name = 'TYPE';
|
|
|
|
} elseif (in_array($value, $values)) {
|
|
|
|
$name = 'VALUE';
|
|
|
|
} elseif (in_array($value, $encodings)) {
|
|
|
|
$name = 'ENCODING';
|
2013-02-19 02:21:29 +01:00
|
|
|
}
|
2013-05-10 02:14:19 +02:00
|
|
|
return $name;
|
2013-02-19 02:21:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2013-09-26 21:16:32 +02:00
|
|
|
* Decode properties for upgrading from v. 2.1
|
|
|
|
*
|
|
|
|
* @param Sabre_VObject_Property $property Reference to a \Sabre\VObject\Property.
|
2013-02-19 02:21:29 +01:00
|
|
|
* 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) {
|
|
|
|
foreach($property->parameters as $key=>&$parameter) {
|
2013-05-10 02:14:19 +02:00
|
|
|
// Check for values without names which Sabre interprets
|
|
|
|
// as names without values.
|
|
|
|
if(trim($parameter->value) === '') {
|
|
|
|
$parameter->value = $parameter->name;
|
|
|
|
$parameter->name = $this->paramName($parameter->name);
|
|
|
|
}
|
|
|
|
// Check out for encoded string and decode them :-[
|
2013-02-19 02:21:29 +01:00
|
|
|
if(strtoupper($parameter->name) == 'ENCODING') {
|
|
|
|
if(strtoupper($parameter->value) == 'QUOTED-PRINTABLE') {
|
2013-02-23 00:31:36 +01:00
|
|
|
$property->value = str_replace(
|
|
|
|
"\r\n", "\n",
|
|
|
|
VObject\StringUtil::convertToUTF8(
|
|
|
|
quoted_printable_decode($property->value)
|
|
|
|
)
|
2013-02-19 02:21:29 +01:00
|
|
|
);
|
|
|
|
unset($property->parameters[$key]);
|
2013-05-10 02:14:19 +02:00
|
|
|
} elseif(strtoupper($parameter->value) == 'BASE64') {
|
2013-02-23 16:24:09 +01:00
|
|
|
$parameter->value = 'b';
|
2013-02-19 02:21:29 +01:00
|
|
|
}
|
|
|
|
} elseif(strtoupper($parameter->name) == 'CHARSET') {
|
2013-02-23 16:24:09 +01:00
|
|
|
unset($property->parameters[$key]);
|
2013-02-19 02:21:29 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-09-26 21:16:32 +02:00
|
|
|
/**
|
|
|
|
* Work around issue in older VObject sersions
|
|
|
|
* https://github.com/fruux/sabre-vobject/issues/24
|
|
|
|
*
|
|
|
|
* @param Sabre_VObject_Property $property Reference to a Sabre_VObject_Property.
|
|
|
|
*/
|
|
|
|
public function fixPropertyParameters(&$property) {
|
|
|
|
// Work around issue in older VObject sersions
|
|
|
|
// https://github.com/fruux/sabre-vobject/issues/24
|
|
|
|
foreach($property->parameters as $key=>$parameter) {
|
|
|
|
$delim = '';
|
|
|
|
if(strpos($parameter->value, ',') === false) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
$values = explode(',', $parameter->value);
|
|
|
|
$values = array_map('trim', $values);
|
|
|
|
$parameter->value = array_shift($values);
|
|
|
|
foreach($values as $value) {
|
|
|
|
$property->add($parameter->name, $value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-02-19 02:21:29 +01:00
|
|
|
/**
|
|
|
|
* Validates the node for correctness.
|
|
|
|
*
|
|
|
|
* The following options are supported:
|
2013-03-05 11:36:35 +01:00
|
|
|
* - VCard::REPAIR - If something is broken, and automatic repair may
|
2013-02-19 02:21:29 +01:00
|
|
|
* be attempted.
|
2013-03-05 11:36:35 +01:00
|
|
|
* - VCard::UPGRADE - If needed the vCard will be upgraded to version 3.0.
|
2013-02-19 02:21:29 +01:00
|
|
|
*
|
|
|
|
* 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();
|
|
|
|
|
2013-05-15 22:58:07 +02:00
|
|
|
if ($options & self::UPGRADE) {
|
|
|
|
$this->VERSION = self::DEFAULT_VERSION;
|
|
|
|
foreach($this->children as &$property) {
|
|
|
|
$this->decodeProperty($property);
|
2013-09-26 21:16:32 +02:00
|
|
|
$this->fixPropertyParameters($property);
|
|
|
|
/* What exactly was I thinking here?
|
2013-05-15 22:58:07 +02:00
|
|
|
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']);
|
|
|
|
}
|
2013-09-26 21:16:32 +02:00
|
|
|
}*/
|
2013-05-15 22:58:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-02-19 02:21:29 +01:00
|
|
|
$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)) {
|
2013-03-29 06:50:47 +01:00
|
|
|
$this->FN = (string)$this->EMAIL;
|
2013-02-19 02:21:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$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) {
|
2013-04-09 17:20:30 +02:00
|
|
|
$this->UID = Utils\Properties::generateUID();
|
2013-02-19 02:21:29 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-09-26 21:16:32 +02:00
|
|
|
if (($options & self::REPAIR) || ($options & self::UPGRADE)) {
|
|
|
|
$now = new \DateTime;
|
|
|
|
$this->REV = $now->format(\DateTime::W3C);
|
|
|
|
}
|
|
|
|
|
2013-02-19 02:21:29 +01:00
|
|
|
return array_merge(
|
|
|
|
parent::validate($options),
|
|
|
|
$warnings
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
2013-06-22 08:50:58 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get all group names in the vCards properties
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function propertyGroups() {
|
|
|
|
foreach($this->children as $property) {
|
|
|
|
if($property->group && !isset($this->groups[$property->group])) {
|
|
|
|
$this->groups[] = $property->group;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(count($this->groups) > 1) {
|
|
|
|
sort($this->groups);
|
|
|
|
}
|
|
|
|
return $this->groups;
|
|
|
|
}
|
|
|
|
|
2013-11-04 15:59:07 +01:00
|
|
|
// TODO: Cleanup these parameters and move method to Utils class
|
|
|
|
public function cacheThumbnail(\OCP\Image $image = null, $remove = false, $update = false) {
|
|
|
|
$key = self::THUMBNAIL_PREFIX . $this->combinedKey();
|
|
|
|
//\OC_Cache::remove($key);
|
|
|
|
if(\OC_Cache::hasKey($key) && $image === null && $remove === false && $update === false) {
|
|
|
|
return \OC_Cache::get($key);
|
|
|
|
}
|
|
|
|
if($remove) {
|
|
|
|
\OC_Cache::remove($key);
|
|
|
|
if(!$update) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(is_null($image)) {
|
|
|
|
$this->retrieve();
|
|
|
|
$image = new \OCP\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',
|
|
|
|
__METHOD__ .'. Couldn\'t crop thumbnail for ID ' . $key,
|
|
|
|
\OCP\Util::ERROR);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if(!$image->resize(self::THUMBNAIL_SIZE)) {
|
|
|
|
\OCP\Util::writeLog('contacts',
|
|
|
|
__METHOD__ . '. 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);
|
|
|
|
}
|
|
|
|
|
2013-02-19 02:21:29 +01:00
|
|
|
}
|