557 lines
17 KiB
PHP
557 lines
17 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Kolab format model class wrapping libkolabxml bindings
|
|
*
|
|
* Abstract base class for different Kolab groupware objects read from/written
|
|
* to the new Kolab 3 format using the PHP bindings of libkolabxml.
|
|
*
|
|
* @version @package_version@
|
|
* @author Thomas Bruederli <bruederli@kolabsys.com>
|
|
*
|
|
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program 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 program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
abstract class kolab_format
|
|
{
|
|
public static $timezone;
|
|
|
|
public /*abstract*/ $CTYPE;
|
|
public /*abstract*/ $CTYPEv2;
|
|
|
|
protected /*abstract*/ $objclass;
|
|
protected /*abstract*/ $read_func;
|
|
protected /*abstract*/ $write_func;
|
|
|
|
protected $obj;
|
|
protected $data;
|
|
protected $xmldata;
|
|
protected $xmlobject;
|
|
protected $formaterror;
|
|
protected $loaded = false;
|
|
protected $version = '3.0';
|
|
|
|
const KTYPE_PREFIX = 'application/x-vnd.kolab.';
|
|
const PRODUCT_ID = 'Roundcube-libkolab-0.9';
|
|
|
|
/**
|
|
* Factory method to instantiate a kolab_format object of the given type and version
|
|
*
|
|
* @param string Object type to instantiate
|
|
* @param float Format version
|
|
* @param string Cached xml data to initialize with
|
|
* @return object kolab_format
|
|
*/
|
|
public static function factory($type, $version = '3.0', $xmldata = null)
|
|
{
|
|
if (!isset(self::$timezone))
|
|
self::$timezone = new DateTimeZone('UTC');
|
|
|
|
if (!self::supports($version))
|
|
return PEAR::raiseError("No support for Kolab format version " . $version);
|
|
|
|
$type = preg_replace('/configuration\.[a-z.]+$/', 'configuration', $type);
|
|
$suffix = preg_replace('/[^a-z]+/', '', $type);
|
|
$classname = 'kolab_format_' . $suffix;
|
|
if (class_exists($classname))
|
|
return new $classname($xmldata, $version);
|
|
|
|
return PEAR::raiseError("Failed to load Kolab Format wrapper for type " . $type);
|
|
}
|
|
|
|
/**
|
|
* Determine support for the given format version
|
|
*
|
|
* @param float Format version to check
|
|
* @return boolean True if supported, False otherwise
|
|
*/
|
|
public static function supports($version)
|
|
{
|
|
if ($version == '2.0')
|
|
return class_exists('kolabobject');
|
|
// default is version 3
|
|
return class_exists('kolabformat');
|
|
}
|
|
|
|
/**
|
|
* Convert the given date/time value into a cDateTime object
|
|
*
|
|
* @param mixed Date/Time value either as unix timestamp, date string or PHP DateTime object
|
|
* @param DateTimeZone The timezone the date/time is in. Use global default if Null, local time if False
|
|
* @param boolean True of the given date has no time component
|
|
* @return object The libkolabxml date/time object
|
|
*/
|
|
public static function get_datetime($datetime, $tz = null, $dateonly = false)
|
|
{
|
|
// use timezone information from datetime of global setting
|
|
if (!$tz && $tz !== false) {
|
|
if ($datetime instanceof DateTime)
|
|
$tz = $datetime->getTimezone();
|
|
if (!$tz)
|
|
$tz = self::$timezone;
|
|
}
|
|
$result = new cDateTime();
|
|
|
|
try {
|
|
// got a unix timestamp (in UTC)
|
|
if (is_numeric($datetime)) {
|
|
$datetime = new DateTime('@'.$datetime, new DateTimeZone('UTC'));
|
|
if ($tz) $datetime->setTimezone($tz);
|
|
}
|
|
else if (is_string($datetime) && strlen($datetime)) {
|
|
$datetime = new DateTime($datetime, $tz ?: null);
|
|
}
|
|
}
|
|
catch (Exception $e) {}
|
|
|
|
if ($datetime instanceof DateTime) {
|
|
$result->setDate($datetime->format('Y'), $datetime->format('n'), $datetime->format('j'));
|
|
|
|
if (!$dateonly)
|
|
$result->setTime($datetime->format('G'), $datetime->format('i'), $datetime->format('s'));
|
|
|
|
if ($tz && in_array($tz->getName(), array('UTC', 'GMT', '+00:00', 'Z')))
|
|
$result->setUTC(true);
|
|
else if ($tz !== false)
|
|
$result->setTimezone($tz->getName());
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Convert the given cDateTime into a PHP DateTime object
|
|
*
|
|
* @param object cDateTime The libkolabxml datetime object
|
|
* @return object DateTime PHP datetime instance
|
|
*/
|
|
public static function php_datetime($cdt)
|
|
{
|
|
if (!is_object($cdt) || !$cdt->isValid())
|
|
return null;
|
|
|
|
$d = new DateTime;
|
|
$d->setTimezone(self::$timezone);
|
|
|
|
try {
|
|
if ($tzs = $cdt->timezone()) {
|
|
$tz = new DateTimeZone($tzs);
|
|
$d->setTimezone($tz);
|
|
}
|
|
else if ($cdt->isUTC()) {
|
|
$d->setTimezone(new DateTimeZone('UTC'));
|
|
}
|
|
}
|
|
catch (Exception $e) { }
|
|
|
|
$d->setDate($cdt->year(), $cdt->month(), $cdt->day());
|
|
|
|
if ($cdt->isDateOnly()) {
|
|
$d->_dateonly = true;
|
|
$d->setTime(12, 0, 0); // set time to noon to avoid timezone troubles
|
|
}
|
|
else {
|
|
$d->setTime($cdt->hour(), $cdt->minute(), $cdt->second());
|
|
}
|
|
|
|
return $d;
|
|
}
|
|
|
|
/**
|
|
* Convert a libkolabxml vector to a PHP array
|
|
*
|
|
* @param object vector Object
|
|
* @return array Indexed array contaning vector elements
|
|
*/
|
|
public static function vector2array($vec, $max = PHP_INT_MAX)
|
|
{
|
|
$arr = array();
|
|
for ($i=0; $i < $vec->size() && $i < $max; $i++)
|
|
$arr[] = $vec->get($i);
|
|
return $arr;
|
|
}
|
|
|
|
/**
|
|
* Build a libkolabxml vector (string) from a PHP array
|
|
*
|
|
* @param array Array with vector elements
|
|
* @return object vectors
|
|
*/
|
|
public static function array2vector($arr)
|
|
{
|
|
$vec = new vectors;
|
|
foreach ((array)$arr as $val) {
|
|
if (strlen($val))
|
|
$vec->push($val);
|
|
}
|
|
return $vec;
|
|
}
|
|
|
|
/**
|
|
* Parse the X-Kolab-Type header from MIME messages and return the object type in short form
|
|
*
|
|
* @param string X-Kolab-Type header value
|
|
* @return string Kolab object type (contact,event,task,note,etc.)
|
|
*/
|
|
public static function mime2object_type($x_kolab_type)
|
|
{
|
|
return preg_replace('/dictionary.[a-z.]+$/', 'dictionary', substr($x_kolab_type, strlen(self::KTYPE_PREFIX)));
|
|
}
|
|
|
|
|
|
/**
|
|
* Default constructor of all kolab_format_* objects
|
|
*/
|
|
public function __construct($xmldata = null, $version = null)
|
|
{
|
|
$this->obj = new $this->objclass;
|
|
$this->xmldata = $xmldata;
|
|
|
|
if ($version)
|
|
$this->version = $version;
|
|
|
|
// use libkolab module if available
|
|
if (class_exists('kolabobject'))
|
|
$this->xmlobject = new XMLObject();
|
|
}
|
|
|
|
/**
|
|
* Check for format errors after calling kolabformat::write*()
|
|
*
|
|
* @return boolean True if there were errors, False if OK
|
|
*/
|
|
protected function format_errors()
|
|
{
|
|
$ret = $log = false;
|
|
switch (kolabformat::error()) {
|
|
case kolabformat::NoError:
|
|
$ret = false;
|
|
break;
|
|
case kolabformat::Warning:
|
|
$ret = false;
|
|
$uid = is_object($this->obj) ? $this->obj->uid() : $this->data['uid'];
|
|
$log = "Warning @ $uid";
|
|
break;
|
|
default:
|
|
$ret = true;
|
|
$log = "Error";
|
|
}
|
|
|
|
if ($log && !isset($this->formaterror)) {
|
|
rcube::raise_error(array(
|
|
'code' => 660,
|
|
'type' => 'php',
|
|
'file' => __FILE__,
|
|
'line' => __LINE__,
|
|
'message' => "kolabformat $log: " . kolabformat::errorMessage(),
|
|
), true);
|
|
|
|
$this->formaterror = $ret;
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* Save the last generated UID to the object properties.
|
|
* Should be called after kolabformat::writeXXXX();
|
|
*/
|
|
protected function update_uid()
|
|
{
|
|
// get generated UID
|
|
if (!$this->data['uid']) {
|
|
if ($this->xmlobject) {
|
|
$this->data['uid'] = $this->xmlobject->getSerializedUID();
|
|
}
|
|
if (empty($this->data['uid'])) {
|
|
$this->data['uid'] = kolabformat::getSerializedUID();
|
|
}
|
|
$this->obj->setUid($this->data['uid']);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize libkolabxml object with cached xml data
|
|
*/
|
|
protected function init()
|
|
{
|
|
if (!$this->loaded) {
|
|
if ($this->xmldata) {
|
|
$this->load($this->xmldata);
|
|
$this->xmldata = null;
|
|
}
|
|
$this->loaded = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get constant value for libkolab's version parameter
|
|
*
|
|
* @param float Version value to convert
|
|
* @return int Constant value of either kolabobject::KolabV2 or kolabobject::KolabV3 or false if kolabobject module isn't available
|
|
*/
|
|
protected function libversion($v = null)
|
|
{
|
|
if (class_exists('kolabobject')) {
|
|
$version = $v ?: $this->version;
|
|
if ($version <= '2.0')
|
|
return kolabobject::KolabV2;
|
|
else
|
|
return kolabobject::KolabV3;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Determine the correct libkolab(xml) wrapper function for the given call
|
|
* depending on the available PHP modules
|
|
*/
|
|
protected function libfunc($func)
|
|
{
|
|
if (is_array($func) || strpos($func, '::'))
|
|
return $func;
|
|
else if (class_exists('kolabobject'))
|
|
return array($this->xmlobject, $func);
|
|
else
|
|
return 'kolabformat::' . $func;
|
|
}
|
|
|
|
/**
|
|
* Direct getter for object properties
|
|
*/
|
|
public function __get($var)
|
|
{
|
|
return $this->data[$var];
|
|
}
|
|
|
|
/**
|
|
* Load Kolab object data from the given XML block
|
|
*
|
|
* @param string XML data
|
|
* @return boolean True on success, False on failure
|
|
*/
|
|
public function load($xml)
|
|
{
|
|
$this->formaterror = null;
|
|
$read_func = $this->libfunc($this->read_func);
|
|
|
|
if (is_array($read_func))
|
|
$r = call_user_func($read_func, $xml, $this->libversion());
|
|
else
|
|
$r = call_user_func($read_func, $xml, false);
|
|
|
|
if (is_resource($r))
|
|
$this->obj = new $this->objclass($r);
|
|
else if (is_a($r, $this->objclass))
|
|
$this->obj = $r;
|
|
|
|
$this->loaded = !$this->format_errors();
|
|
}
|
|
|
|
/**
|
|
* Write object data to XML format
|
|
*
|
|
* @param float Format version to write
|
|
* @return string XML data
|
|
*/
|
|
public function write($version = null)
|
|
{
|
|
$this->formaterror = null;
|
|
|
|
$this->init();
|
|
$write_func = $this->libfunc($this->write_func);
|
|
if (is_array($write_func))
|
|
$this->xmldata = call_user_func($write_func, $this->obj, $this->libversion($version), self::PRODUCT_ID);
|
|
else
|
|
$this->xmldata = call_user_func($write_func, $this->obj, self::PRODUCT_ID);
|
|
|
|
if (!$this->format_errors())
|
|
$this->update_uid();
|
|
else
|
|
$this->xmldata = null;
|
|
|
|
return $this->xmldata;
|
|
}
|
|
|
|
/**
|
|
* Set properties to the kolabformat object
|
|
*
|
|
* @param array Object data as hash array
|
|
*/
|
|
public function set(&$object)
|
|
{
|
|
$this->init();
|
|
|
|
if (!empty($object['uid']))
|
|
$this->obj->setUid($object['uid']);
|
|
|
|
// set some automatic values if missing
|
|
if (empty($object['created']) && method_exists($this->obj, 'setCreated')) {
|
|
$cdt = $this->obj->created();
|
|
$object['created'] = $cdt && $cdt->isValid() ? self::php_datetime($cdt) : new DateTime('now', new DateTimeZone('UTC'));
|
|
if (!$cdt || !$cdt->isValid())
|
|
$this->obj->setCreated(self::get_datetime($object['created']));
|
|
}
|
|
|
|
$object['changed'] = new DateTime('now', new DateTimeZone('UTC'));
|
|
$this->obj->setLastModified(self::get_datetime($object['changed']));
|
|
|
|
// Save custom properties of the given object
|
|
if (isset($object['x-custom'])) {
|
|
$vcustom = new vectorcs;
|
|
foreach ((array)$object['x-custom'] as $cp) {
|
|
if (is_array($cp))
|
|
$vcustom->push(new CustomProperty($cp[0], $cp[1]));
|
|
}
|
|
$this->obj->setCustomProperties($vcustom);
|
|
}
|
|
else { // load custom properties from XML for caching (#2238)
|
|
$object['x-custom'] = array();
|
|
$vcustom = $this->obj->customProperties();
|
|
for ($i=0; $i < $vcustom->size(); $i++) {
|
|
$cp = $vcustom->get($i);
|
|
$object['x-custom'][] = array($cp->identifier, $cp->value);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convert the Kolab object into a hash array data structure
|
|
*
|
|
* @param array Additional data for merge
|
|
*
|
|
* @return array Kolab object data as hash array
|
|
*/
|
|
public function to_array($data = array())
|
|
{
|
|
$this->init();
|
|
|
|
// read object properties into local data object
|
|
$object = array(
|
|
'uid' => $this->obj->uid(),
|
|
'changed' => self::php_datetime($this->obj->lastModified()),
|
|
);
|
|
|
|
// not all container support the created property
|
|
if (method_exists($this->obj, 'created')) {
|
|
$object['created'] = self::php_datetime($this->obj->created());
|
|
}
|
|
|
|
// read custom properties
|
|
$vcustom = $this->obj->customProperties();
|
|
for ($i=0; $i < $vcustom->size(); $i++) {
|
|
$cp = $vcustom->get($i);
|
|
$object['x-custom'][] = array($cp->identifier, $cp->value);
|
|
}
|
|
|
|
// merge with additional data, e.g. attachments from the message
|
|
if ($data) {
|
|
foreach ($data as $idx => $value) {
|
|
if (is_array($value)) {
|
|
$object[$idx] = array_merge((array)$object[$idx], $value);
|
|
}
|
|
else {
|
|
$object[$idx] = $value;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $object;
|
|
}
|
|
|
|
/**
|
|
* Object validation method to be implemented by derived classes
|
|
*/
|
|
abstract public function is_valid();
|
|
|
|
/**
|
|
* Callback for kolab_storage_cache to get object specific tags to cache
|
|
*
|
|
* @return array List of tags to save in cache
|
|
*/
|
|
public function get_tags()
|
|
{
|
|
return array();
|
|
}
|
|
|
|
/**
|
|
* Callback for kolab_storage_cache to get words to index for fulltext search
|
|
*
|
|
* @return array List of words to save in cache
|
|
*/
|
|
public function get_words()
|
|
{
|
|
return array();
|
|
}
|
|
|
|
protected function get_attachments(&$object)
|
|
{
|
|
// handle attachments
|
|
$vattach = $this->obj->attachments();
|
|
for ($i=0; $i < $vattach->size(); $i++) {
|
|
$attach = $vattach->get($i);
|
|
|
|
// skip cid: attachments which are mime message parts handled by kolab_storage_folder
|
|
if (substr($attach->uri(), 0, 4) != 'cid:' && $attach->label()) {
|
|
$name = $attach->label();
|
|
$content = $attach->data();
|
|
$object['_attachments'][$name] = array(
|
|
'name' => $name,
|
|
'mimetype' => $attach->mimetype(),
|
|
'size' => strlen($content),
|
|
'content' => $content,
|
|
);
|
|
}
|
|
else if (substr($attach->uri(), 0, 4) == 'http') {
|
|
$object['links'][] = $attach->uri();
|
|
}
|
|
}
|
|
}
|
|
|
|
protected function set_attachments($object)
|
|
{
|
|
// save attachments
|
|
$vattach = new vectorattachment;
|
|
foreach ((array) $object['_attachments'] as $cid => $attr) {
|
|
if (empty($attr))
|
|
continue;
|
|
$attach = new Attachment;
|
|
$attach->setLabel((string)$attr['name']);
|
|
$attach->setUri('cid:' . $cid, $attr['mimetype'] ?: 'application/octet-stream');
|
|
if ($attach->isValid()) {
|
|
$vattach->push($attach);
|
|
}
|
|
else {
|
|
rcube::raise_error(array(
|
|
'code' => 660,
|
|
'type' => 'php',
|
|
'file' => __FILE__,
|
|
'line' => __LINE__,
|
|
'message' => "Invalid attributes for attachment $cid: " . var_export($attr, true),
|
|
), true);
|
|
}
|
|
}
|
|
|
|
foreach ((array) $object['links'] as $link) {
|
|
$attach = new Attachment;
|
|
$attach->setUri($link, 'unknown');
|
|
$vattach->push($attach);
|
|
}
|
|
|
|
$this->obj->setAttachments($vattach);
|
|
}
|
|
}
|