roundcubemail-plugins-kolab/plugins/libkolab/lib/kolab_storage_dav_folder.php

542 lines
15 KiB
PHP
Raw Normal View History

2022-10-06 15:59:53 +02:00
<?php
/**
* A class representing a DAV folder object.
*
* @author Aleksander Machniak <machniak@apheleia-it.ch>
*
* Copyright (C) 2014-2022, Apheleia IT AG <contact@apheleia-it.ch>
*
* 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/>.
*/
class kolab_storage_dav_folder extends kolab_storage_folder
{
public $dav;
public $href;
public $attributes;
/**
* Object constructor
*/
public function __construct($dav, $attributes, $type_annotation = '')
{
$this->attributes = $attributes;
$this->href = $this->attributes['href'];
// Here we assume the last element of the folder path is the folder ID
// if that's not the case, we should consider generating an ID
$href = explode('/', unslashify($this->href));
$this->id = $href[count($href) - 1];
$this->dav = $dav;
$this->valid = true;
list($this->type, $suffix) = explode('.', $type_annotation);
$this->default = $suffix == 'default';
$this->subtype = $this->default ? '' : $suffix;
// Init cache
$this->cache = kolab_storage_dav_cache::factory($this);
}
/**
* Returns the owner of the folder.
*
* @param bool Return a fully qualified owner name (i.e. including domain for shared folders)
*
* @return string The owner of this folder.
*/
public function get_owner($fully_qualified = false)
{
// return cached value
if (isset($this->owner)) {
return $this->owner;
}
$rcube = rcube::get_instance();
$this->owner = $rcube->get_user_name();
$this->valid = true;
// TODO: Support shared folders
return $this->owner;
}
/**
* Get a folder Etag identifier
*/
public function get_ctag()
{
return $this->attributes['ctag'];
}
/**
* Getter for the name of the namespace to which the folder belongs
*
* @return string Name of the namespace (personal, other, shared)
*/
public function get_namespace()
{
// TODO: Support shared folders
return 'personal';
}
/**
* Get the display name value of this folder
*
* @return string Folder name
*/
public function get_name()
{
return kolab_storage_dav::object_name($this->attributes['name']);
}
/**
* Getter for the top-end folder name (not the entire path)
*
* @return string Name of this folder
*/
public function get_foldername()
{
return $this->attributes['name'];
}
2022-10-11 15:27:59 +02:00
public function get_folder_info()
{
return []; // todo ?
}
2022-10-06 15:59:53 +02:00
/**
* Getter for parent folder path
*
* @return string Full path to parent folder
*/
public function get_parent()
{
// TODO
return '';
}
/**
* Compose a unique resource URI for this folder
*/
public function get_resource_uri()
{
if (!empty($this->resource_uri)) {
return $this->resource_uri;
}
// compose fully qualified ressource uri for this instance
$host = preg_replace('|^https?://|', 'dav://' . urlencode($this->get_owner(true)) . '@', $this->dav->url);
$path = $this->href[0] == '/' ? $this->href : "/{$this->href}";
$this->resource_uri = unslashify($host) . $path;
return $this->resource_uri;
}
/**
* Getter for the Cyrus mailbox identifier corresponding to this folder
* (e.g. user/john.doe/Calendar/Personal@example.org)
*
* @return string Mailbox ID
*/
public function get_mailbox_id()
{
// TODO: This is used with Bonnie related features
return '';
}
/**
* Get the color value stored in metadata
*
* @param string Default color value to return if not set
*
* @return mixed Color value from the folder metadata or $default if not set
*/
public function get_color($default = null)
{
return !empty($this->attributes['color']) ? $this->attributes['color'] : $default;
}
/**
* Get ACL information for this folder
*
* @return string Permissions as string
*/
public function get_myrights()
{
// TODO
return '';
}
/**
* Helper method to extract folder UID
*
* @return string Folder's UID
*/
public function get_uid()
{
// TODO ???
return '';
}
/**
* Check activation status of this folder
*
* @return bool True if enabled, false if not
*/
public function is_active()
{
// TODO
return true;
}
/**
* Change activation status of this folder
*
* @param bool The desired subscription status: true = active, false = not active
*
* @return bool True on success, false on error
*/
public function activate($active)
{
// TODO
return true;
}
/**
* Check subscription status of this folder
*
* @return bool True if subscribed, false if not
*/
public function is_subscribed()
{
// TODO
return true;
}
/**
* Change subscription status of this folder
*
* @param bool The desired subscription status: true = subscribed, false = not subscribed
*
* @return True on success, false on error
*/
public function subscribe($subscribed)
{
// TODO
return true;
}
/**
* Delete the specified object from this folder.
*
* @param array|string $object The Kolab object to delete or object UID
* @param bool $expunge Should the folder be expunged?
*
* @return bool True if successful, false on error
*/
public function delete($object, $expunge = true)
{
if (!$this->valid) {
return false;
}
$uid = is_array($object) ? $object['uid'] : $object;
$success = $this->dav->delete($this->object_location($uid), $content);
if ($success) {
$this->cache->set($uid, false);
}
return $success;
}
/**
*
*/
public function delete_all()
{
if (!$this->valid) {
return false;
}
// TODO: This method is used by kolab_addressbook plugin only
$this->cache->purge();
return false;
}
/**
* Restore a previously deleted object
*
* @param string $uid Object UID
*
* @return mixed Message UID on success, false on error
*/
public function undelete($uid)
{
if (!$this->valid) {
return false;
}
// TODO
return false;
}
/**
* Move a Kolab object message to another IMAP folder
*
* @param string Object UID
* @param string IMAP folder to move object to
*
* @return bool True on success, false on failure
*/
public function move($uid, $target_folder)
{
if (!$this->valid) {
return false;
}
// TODO
return false;
}
/**
* Save an object in this folder.
*
* @param array $object The array that holds the data of the object.
* @param string $type The type of the kolab object.
* @param string $uid The UID of the old object if it existed before
*
* @return mixed False on error or object UID on success
*/
public function save(&$object, $type = null, $uid = null)
{
if (!$this->valid || empty($object)) {
return false;
}
if (!$type) {
$type = $this->type;
}
/*
// copy attachments from old message
$copyfrom = $object['_copyfrom'] ?: $object['_msguid'];
if (!empty($copyfrom) && ($old = $this->cache->get($copyfrom, $type, $object['_mailbox']))) {
foreach ((array)$old['_attachments'] as $key => $att) {
if (!isset($object['_attachments'][$key])) {
$object['_attachments'][$key] = $old['_attachments'][$key];
}
// unset deleted attachment entries
if ($object['_attachments'][$key] == false) {
unset($object['_attachments'][$key]);
}
// load photo.attachment from old Kolab2 format to be directly embedded in xcard block
else if ($type == 'contact' && ($key == 'photo.attachment' || $key == 'kolab-picture.png') && $att['id']) {
if (!isset($object['photo']))
$object['photo'] = $this->get_attachment($copyfrom, $att['id'], $object['_mailbox']);
unset($object['_attachments'][$key]);
}
}
}
// process attachments
if (is_array($object['_attachments'])) {
$numatt = count($object['_attachments']);
foreach ($object['_attachments'] as $key => $attachment) {
// FIXME: kolab_storage and Roundcube attachment hooks use different fields!
if (empty($attachment['content']) && !empty($attachment['data'])) {
$attachment['content'] = $attachment['data'];
unset($attachment['data'], $object['_attachments'][$key]['data']);
}
// make sure size is set, so object saved in cache contains this info
if (!isset($attachment['size'])) {
if (!empty($attachment['content'])) {
if (is_resource($attachment['content'])) {
// this need to be a seekable resource, otherwise
// fstat() failes and we're unable to determine size
// here nor in rcube_imap_generic before IMAP APPEND
$stat = fstat($attachment['content']);
$attachment['size'] = $stat ? $stat['size'] : 0;
}
else {
$attachment['size'] = strlen($attachment['content']);
}
}
else if (!empty($attachment['path'])) {
$attachment['size'] = filesize($attachment['path']);
}
$object['_attachments'][$key] = $attachment;
}
// generate unique keys (used as content-id) for attachments
if (is_numeric($key) && $key < $numatt) {
// derrive content-id from attachment file name
$ext = preg_match('/(\.[a-z0-9]{1,6})$/i', $attachment['name'], $m) ? $m[1] : null;
$basename = preg_replace('/[^a-z0-9_.-]/i', '', basename($attachment['name'], $ext)); // to 7bit ascii
if (!$basename) $basename = 'noname';
$cid = $basename . '.' . microtime(true) . $key . $ext;
$object['_attachments'][$cid] = $attachment;
unset($object['_attachments'][$key]);
}
}
}
*/
$rcmail = rcube::get_instance();
$result = false;
// generate and save object message
if ($content = $this->to_dav($object)) {
2022-10-11 15:27:59 +02:00
$method = $uid ? 'update' : 'create';
$result = $this->dav->{$method}($this->object_location($object['uid']), $content);
2022-10-06 15:59:53 +02:00
2022-10-11 15:27:59 +02:00
// Note: $result can be NULL if the request was successful, but ETag wasn't returned
2022-10-06 15:59:53 +02:00
if ($result !== false) {
// insert/update object in the cache
$object['etag'] = $result;
$this->cache->save($object, $uid);
2022-10-11 15:27:59 +02:00
$result = true;
2022-10-06 15:59:53 +02:00
}
}
return $result;
}
/**
* Fetch the object the DAV server and convert to internal format
*
* @param string The object UID to fetch
* @param string The object type expected (use wildcard '*' to accept all types)
*
* @return mixed Hash array representing the Kolab object, a kolab_format instance or false if not found
*/
public function read_object($uid, $type = null)
{
if (!$this->valid) {
return false;
}
$href = $this->object_location($uid);
2022-10-11 15:27:59 +02:00
$objects = $this->dav->getData($this->href, $this->get_dav_type(), [$href]);
2022-10-06 15:59:53 +02:00
if (!is_array($objects) || count($objects) != 1) {
rcube::raise_error([
'code' => 900,
'message' => "Failed to fetch {$href}"
], true);
return false;
}
return $this->from_dav($objects[0]);
}
/**
* Convert DAV object into PHP array
*
* @param array Object data in kolab_dav_client::fetchData() format
*
* @return array Object properties
*/
public function from_dav($object)
{
if ($this->type == 'event') {
$ical = libcalendaring::get_ical();
$events = $ical->import($object['data']);
if (!count($events) || empty($events[0]['uid'])) {
return false;
}
$result = $events[0];
}
$result['etag'] = $object['etag'];
$result['href'] = $object['href'];
$result['uid'] = $object['uid'] ?: $result['uid'];
return $result;
}
/**
* Convert Kolab object into DAV format (iCalendar)
*/
public function to_dav($object)
{
$result = '';
if ($this->type == 'event') {
$ical = libcalendaring::get_ical();
2022-10-11 15:27:59 +02:00
if (!empty($object['exceptions'])) {
$object['recurrence']['EXCEPTIONS'] = $object['exceptions'];
}
2022-10-06 15:59:53 +02:00
$result = $ical->export([$object]);
}
return $result;
}
protected function object_location($uid)
{
return unslashify($this->href) . '/' . urlencode($uid) . '.' . $this->get_dav_ext();
}
/**
* Get a folder DAV content type
*/
public function get_dav_type()
{
$types = [
'event' => 'VEVENT',
'task' => 'VTODO',
'contact' => 'VCARD',
];
return $types[$this->type];
}
/**
* Get a DAV file extension for specified Kolab type
*/
public function get_dav_ext()
{
$types = [
'event' => 'ics',
'task' => 'ics',
'contact' => 'vcf',
];
return $types[$this->type];
}
/**
* Return folder name as string representation of this object
*
* @return string Full IMAP folder name
*/
public function __toString()
{
return $this->attributes['name'];
}
}