--- /dev/null
+<?php
+/**
+ * Ansel external API interface.
+ *
+ * Copyright 2004-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author Jan Schneider <jan@horde.org>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @author Michael J. Rubinsky <mrubinsk@horde.org>
+ * @package Ansel
+ */
+class Ansel_Api extends Horde_Registry_Api
+{
+ public $version = 'H4 (2.0-git)';
+
+ public $services = array(
+ 'perms' => array(
+ 'args' => array(),
+ 'type' => '{urn:horde}stringArray'
+ ),
+
+ 'browse' => array(
+ 'args' => array('path' => 'string'),
+ 'type' => '{urn:horde}hashHash',
+ ),
+
+ 'put' => array(
+ 'args' => array('path' => 'string', 'content' => 'string', 'content_type' => 'string'),
+ 'type' => '{urn:horde}stringArray',
+ ),
+
+ // 'path_delete' => array(
+ // 'args' => array('path' => 'string'),
+ // 'type' => 'boolean',
+ // ),
+
+ 'commentCallback' => array(
+ 'args' => array('image_id' => 'string'),
+ 'type' => 'string'
+ ),
+
+ 'hasComments' => array(
+ 'args' => array(),
+ 'type' => 'boolean'
+ ),
+
+ 'saveImage' => array(
+ 'args' => array('app' => 'string',
+ 'gallery_id' => 'string',
+ 'image' => '{urn:horde}hashHash',
+ 'default' => 'boolean',
+ 'gallery_data' => '{urn:horde}hashHash',
+ 'encoding' => 'string',
+ 'slug' => 'string',
+ 'compression' => 'string',
+ 'skiphook' => 'boolean'),
+ 'type' => '{urn:horde}stringArray'
+ ),
+
+ 'postBatchUpload' => array(
+ 'args' => array('image_ids' => '{urn:horde}hash'),
+ 'type' => 'int'
+ ),
+
+ 'createGallery' => array(
+ 'args' => array('app' => 'string',
+ 'attributes' => '{urn:horde}hashHash',
+ 'perm' => '{urn:horde}hashHash'),
+ 'type' => 'int'
+ ),
+
+ 'removeImage' => array(
+ 'args' => array('app' => 'string',
+ 'gallery_id' => 'integer',
+ 'image_id' => 'integer'),
+ 'type' => 'int'
+ ),
+
+ 'removeGallery' => array(
+ 'args' => array('app' => 'string',
+ 'gallery_id' => 'integer'),
+ 'type' => 'int'
+ ),
+
+ 'getImageUrl' => array(
+ 'args' => array('app' => 'string',
+ 'image_id' => 'integer',
+ 'view' => 'string',
+ 'full' => 'boolean',
+ 'style' => 'string'),
+ 'type' => 'string'
+ ),
+
+ 'getImageContent' => array(
+ 'args' => array('image_id' => 'integer',
+ 'view' => 'string',
+ 'style' => 'string',
+ 'app' => 'string'),
+ 'type' => 'string'
+ ),
+
+ 'count' => array(
+ 'args' => array('app' => 'string',
+ 'gallery_id' => 'integer'),
+ 'type' => 'int'
+ ),
+
+ 'getDefaultImage' => array(
+ 'args' => array('app' => 'string',
+ 'gallery_id' => 'integer',
+ 'style' => 'string'),
+ 'type' => 'string'
+ ),
+
+ 'listGalleries' => array(
+ 'args' => array('app' => 'string',
+ 'perm' => 'integer',
+ 'parent' => 'string',
+ 'allLevels' => 'string',
+ 'from' => 'integer',
+ 'count' => 'integer'),
+ 'type' => 'string'
+ ),
+
+ 'getGalleries' => array(
+ 'args' => array('ids' => '{urn:horde}hash',
+ 'app' => 'string'),
+ 'type' => '{urn:horde}hash'
+ ),
+
+ 'selectGalleries' => array(
+ 'args' => array('app' => 'string',
+ 'perm' => 'integer',
+ 'parent' => 'string',
+ 'allLevels' => 'string',
+ 'from' => 'integer',
+ 'count' => 'integer'),
+ 'type' => 'string'
+ ),
+
+ 'listImages' => array(
+ 'args' => array('app' => 'string',
+ 'gallery_id' => 'integer',
+ 'perm' => 'integer',
+ 'view' => 'string',
+ 'full' => 'boolean',
+ 'from' => 'integer',
+ 'count' => 'integer',
+ 'style' => 'string'),
+ 'type' => 'string'
+ ),
+
+ 'getRecentImages' => array(
+ 'args' => array('app' => 'string',
+ 'galleries' => '{urn:horde}hash',
+ 'perms' => 'integer',
+ 'view' => 'string',
+ 'full' => 'boolean',
+ 'limit' => 'integer',
+ 'style' => 'string',
+ 'slugs' => '{urn:horde}hashHash'),
+ 'type' => '{urn:horde}hash'
+ ),
+
+ 'countGalleries' => array(
+ 'args' => array('app' => 'string',
+ 'perm' => 'string',
+ 'attributes' => '{urn:horde}hash',
+ 'parent' => 'string',
+ 'allLevels' => 'boolean'),
+ 'type' => 'int'
+ ),
+
+ 'listTagInfo' => array(
+ 'args' => array('tags' => '{urn:horde}stringArray'),
+ 'type' => '{urn:horde}hash'
+ ),
+
+ 'searchTags' => array(
+ 'args' => array('tags' => '{urn:horde}stringArray',
+ 'resource_type' => 'string',
+ 'count' => 'int',
+ 'user' => 'string'),
+ 'type' => '{urn:horde}hash'
+ ),
+
+ 'galleryExists' => array(
+ 'args' => array('app' => 'string',
+ 'gallery_name' => 'string'),
+ 'type' => 'boolean'
+ ),
+
+ 'renderView' => array(
+ 'args' => array('parameters' => '{urn:horde]stringArray',
+ 'app' => 'string',
+ 'view' => 'string'),
+ 'type' => 'string'
+ ),
+
+ 'getGalleryStyles' => array(
+ 'args' => array(),
+ 'type' => '{urn:horde}hash'
+ )
+ );
+
+ /**
+ * Returns a list of available permissions.
+ *
+ * @return array An array describing all available permissions.
+ */
+ public function perms()
+ {
+ $perms = array();
+ $perms['tree']['ansel']['admin'] = false;
+ $perms['title']['ansel:admin'] = _("Administrators");
+
+ return $perms;
+ }
+
+ /**
+ * Browse through Ansel's gallery tree.
+ *
+ * @param string $path The level of the tree to browse.
+ * @param array $properties The item properties to return. Defaults to 'name',
+ * 'icon', and 'browseable'.
+ *
+ * @return array The contents of $path
+ */
+ public function browse($path = '', $properties = array())
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ // Default properties.
+ if (!$properties) {
+ $properties = array('name', 'icon', 'browseable');
+ }
+
+ if (substr($path, 0, 5) == 'ansel') {
+ $path = substr($path, 5);
+ }
+ $path = trim($path, '/');
+ $parts = explode('/', $path);
+
+ if (empty($path)) {
+ $owners = array();
+ $galleries = $GLOBALS['ansel_storage']->listGalleries(PERMS_SHOW, null, null, false);
+ foreach ($galleries as $gallery) {
+ $owners[$gallery->data['share_owner']] = true;
+ }
+
+ $results = array();
+ foreach (array_keys($owners) as $owner) {
+ if (in_array('name', $properties)) {
+ $results['ansel/' . $owner]['name'] = $owner;
+ }
+ if (in_array('icon', $properties)) {
+ $results['ansel/' . $owner]['icon'] =
+ $registry->getImageDir('horde') . '/user.png';
+ }
+ if (in_array('browseable', $properties)) {
+ $results['ansel/' . $owner]['browseable'] = true;
+ }
+ if (in_array('contenttype', $properties)) {
+ $results['ansel/' . $owner]['contenttype'] =
+ 'httpd/unix-directory';
+ }
+ if (in_array('contentlength', $properties)) {
+ $results['ansel/' . $owner]['contentlength'] = 0;
+ }
+ if (in_array('modified', $properties)) {
+ $results['ansel/' . $owner]['modified'] = time();
+ }
+ if (in_array('created', $properties)) {
+ $results['ansel/' . $owner]['created'] = 0;
+ }
+ }
+ return $results;
+
+ } else {
+ if (count($parts) == 1) {
+ // This request is for all galleries owned by the requested user.
+ $galleries = $GLOBALS['ansel_storage']->listGalleries(
+ PERMS_SHOW, $parts[0], null, false);
+ $images = array();
+ } elseif ($this->galleryExists(null, end($parts))) {
+ // This request if for a certain gallery, list all sub-galleries
+ // and images.
+ $gallery_id = end($parts);
+ $galleries = $GLOBALS['ansel_storage']->getGalleries(
+ array($gallery_id));
+ if (!isset($galleries[$gallery_id]) ||
+ !$galleries[$gallery_id]->hasPermission(Horde_Auth::getAuth(),
+ PERMS_READ)) {
+ return PEAR::raiseError(_("Invalid gallery specified."), 404);
+ }
+ $galleries = $GLOBALS['ansel_storage']->listGalleries(
+ PERMS_SHOW, null, $gallery_id, false);
+
+ $images = $this->listImages(null, $gallery_id, PERMS_SHOW, 'mini');
+ } elseif (count($parts) > 2 &&
+ $this->galleryExists(null, $parts[count($parts) - 2]) &&
+ !is_a($image = $GLOBALS['ansel_storage']->getImage(end($parts)), 'PEAR_Error')) {
+ return array('data' => $image->raw(),
+ 'mimetype' => $image->type,
+ 'mtime' => $image->uploaded);
+ } else {
+ return PEAR::raiseError(_("File not found."), 404);
+ }
+
+ $results = array();
+ foreach ($galleries as $galleryId => $gallery) {
+ $retpath = 'ansel/' . implode('/', $parts) . '/' . $galleryId;
+ if (in_array('name', $properties)) {
+ $results[$retpath]['name'] = $gallery->data['attribute_name'];
+ }
+ if (in_array('displayname', $properties)) {
+ $results[$retpath]['displayname'] = rawurlencode(
+ $gallery->data['attribute_name']);
+ }
+ if (in_array('icon', $properties)) {
+ $results[$retpath]['icon'] = $registry->getImageDir()
+ . '/ansel.png';
+ }
+ if (in_array('browseable', $properties)) {
+ $results[$retpath]['browseable'] = $gallery->hasPermission(
+ Horde_Auth::getAuth(), PERMS_READ);
+ }
+ if (in_array('contenttype', $properties)) {
+ $results[$retpath]['contenttype'] = 'httpd/unix-directory';
+ }
+ if (in_array('contentlength', $properties)) {
+ $results[$retpath]['contentlength'] = 0;
+ }
+ if (in_array('modified', $properties)) {
+ $results[$retpath]['modified'] = time();
+ }
+ if (in_array('created', $properties)) {
+ $results[$retpath]['created'] = 0;
+ }
+ }
+
+ foreach ($images as $imageId => $image) {
+ $retpath = 'ansel/' . implode('/', $parts) . '/' . $imageId;
+ if (in_array('name', $properties)) {
+ $results[$retpath]['name'] = $image['name'];
+ }
+ if (in_array('displayname', $properties)) {
+ $results[$retpath]['displayname'] = rawurlencode($image['name']);
+ }
+ if (in_array('icon', $properties)) {
+ $results[$retpath]['icon'] = Horde::url($image['url'], true);
+ }
+ if (in_array('browseable', $properties)) {
+ $results[$retpath]['browseable'] = false;
+ }
+ if (in_array('contenttype', $properties)) {
+ $results[$retpath]['contenttype'] = $image['type'];
+ }
+ if (in_array('contentlength', $properties)) {
+ $results[$retpath]['contentlength'] = 0;
+ }
+ if (in_array('modified', $properties)) {
+ $results[$retpath]['modified'] = $image['uploaded'];
+ }
+ if (in_array('created', $properties)) {
+ $results[$retpath]['created'] = $image['uploaded'];
+ }
+ }
+ return $results;
+
+ }
+
+ return PEAR::raiseError(_("File not found."), 404);
+ }
+
+ /**
+ * Saves an image into the gallery tree.
+ *
+ * @param string $path The path where to PUT the file.
+ * @param string $content The file content.
+ * @param string $content_type The file's content type.
+ *
+ * @return array The event UIDs, or a PEAR_Error on failure.
+ */
+ public function put($path, $content, $content_type)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ if (substr($path, 0, 5) == 'ansel') {
+ $path = substr($path, 9);
+ }
+ $path = trim($path, '/');
+ $parts = explode('/', $path);
+
+ if (count($parts) < 3) {
+ return PEAR::raiseError("Gallery does not exist");
+ }
+ $image_name = array_pop($parts);
+ $gallery_id = end($parts);
+ if (!$GLOBALS['ansel_storage']->galleryExists($gallery_id)) {
+ return PEAR::raiseError("Gallery does not exist");
+ }
+ $gallery = $GLOBALS['ansel_storage']->getGallery($gallery_id);
+ if (!$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_EDIT)) {
+ return PEAR::raiseError(_("Access denied adding photos to \"%s\"."));
+ }
+
+ return $gallery->addImage(array('image_type' => $content_type,
+ 'image_filename' => $image_name,
+ 'image_caption' => '',
+ 'data' => $content));
+ }
+
+ /**
+ * Callback for Agora comments.
+ *
+ * @param integer $image_id Image id to check
+ *
+ * @return mixed Image filename on success | false on failure
+ */
+ public function commentCallback($image_id)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ if (!$GLOBALS['conf']['comments']['allow']) {
+ return false;
+ }
+
+ $image = $GLOBALS['ansel_storage']->getImage($image_id);
+ if (!$image || is_a($image, 'PEAR_Error')) {
+ return false;
+ }
+
+ return $image->filename;
+ }
+
+ /**
+ * Checks if applications allows comments
+ *
+ * @return boolean
+ */
+ public function hasComments()
+ {
+ if (($GLOBALS['conf']['comments']['allow'] == 'all' ||
+ ($GLOBALS['conf']['comments']['allow'] == 'authenticated' &&
+ Horde_Auth::getAuth()))) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns decoded image data
+ *
+ * @param string $data The id of the image.
+ * @param string $encoding The encoding type for the image data.
+ * (none, base64, or binhex)
+ * @param string $compression The compression type for the image data.
+ * (none, gzip, or lzf)
+ * @param boolean $upload Process direction (true of encode/compress or false if decode/decompress)
+ *
+ * @return string The image path.
+ */
+ protected function _getImageData($data, $encoding = 'none', $compression = 'none', $upload = true)
+ {
+ switch ($encoding) {
+ case 'base64':
+ $data = $upload ? base64_decode($data) : base64_encode($data);
+ break;
+
+ case 'binhex':
+ $data = $upload ? pack('H*', $data) : unpack('H*', $data);
+ }
+
+ switch ($compression) {
+ case 'gzip':
+ if (Horde_Util::loadExtension('zlib')) {
+ return $upload ? gzuncompress($data) : gzcompress($data);
+ }
+ break;
+
+ case 'lzf':
+ if (Horde_Util::loadExtension('lzf')) {
+ return $upload ? lzf_decompress($data) : lzf_compress($data);
+ }
+ break;
+
+ default:
+ return $data;
+ }
+ }
+
+ /**
+ * Stores an image in a gallery and returns gallery and image data.
+ *
+ * @param integer $app Application used if null then use default.
+ * @param integer $gallery_id The gallery id to add the image to.
+ * @param array $image Image data array. This can either be the return
+ * from Horde_Form_Type_image:: or an array with
+ * the following four fields:
+ * 'filename', 'description', 'data', 'type'
+ * @param integer $default Set this image as default in the gallery?
+ * @param array $gallery_data Any gallery parameters to change at this time.
+ * @param string $encoding The encoding type for the image data.
+ * (none, base64, or binhex)
+ * @param string $slug Use gallery slug instead of id. (Pass '0' or null
+ * to gallery_id parameter).
+ * @param string $compression The compression type for the image data.
+ * (none, gzip, or lzf)
+ *
+ * @return mixed An array of image/gallery data || PEAR_Error
+ */
+ public function saveImage($app = null, $gallery_id, $image, $default = false,
+ $gallery_data = null, $encoding = null, $slug = null,
+ $compression = 'none', $skiphook = false)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ $image_data = null;
+
+ /* If no app is given use Ansel's own gallery which is initialized
+ * in base.php */
+ if (!is_null($app)) {
+ $GLOBALS['ansel_storage'] = new Ansel_Storage($app);
+ }
+
+ if (isset($image['filename']) &&
+ isset($image['description']) &&
+ isset($image['data']) &&
+ isset($image['type'])) {
+ Horde::logMessage(sprintf("Receiving image %s in saveImage() with a raw filesize of %i", $image['filename'], strlen($image['data'])), __FILE__, __LINE__, PEAR_LOG_DEBUG);
+ $image_data = array('image_filename' => $image['filename'],
+ 'image_caption' => $image['description'],
+ 'image_type' => $image['type'],
+ 'data' => $this->_getImageData($image['data'], $encoding, $compression, true));
+ } else {
+ Horde::logMessage(sprintf("Receiving image %s in saveImage() with a raw filesize of %i", $image['file'], filesize($image['file'])), __FILE__, __LINE__, PEAR_LOG_DEBUG);
+ }
+
+ if (is_null($image_data) && getimagesize($image['file']) === false) {
+ return PEAR::raiseError(_("The file you uploaded does not appear to be a valid photo."));
+ }
+ if (empty($slug) && empty($gallery_id)) {
+ return PEAR::raiseError(_("A gallery to add this photo to is required."));
+ }
+ if (!empty($slug)) {
+ $gallery = $GLOBALS['ansel_storage']->getGalleryBySlug($slug);
+ if (is_a($gallery, 'PEAR_Error')) {
+ return $gallery;
+ }
+ } elseif ($GLOBALS['ansel_storage']->galleryExists($gallery_id)) {
+ $gallery = $GLOBALS['ansel_storage']->getGallery($gallery_id);
+ if (is_a($gallery, 'PEAR_Error')) {
+ return $gallery;
+ }
+ }
+ if (!$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_EDIT)) {
+ return PEAR::raiseError(sprintf(_("Access denied adding photos to \"%s\"."), $gallery->get('name')));
+ }
+ if (!empty($gallery_data)) {
+ foreach ($gallery_data as $key => $value) {
+ $gallery->set($key, $value);
+ }
+ $gallery->save();
+ }
+
+ if (is_null($image_data)) {
+ $image_data = array(
+ 'image_filename' => $image['name'],
+ 'image_caption' => $image['name'],
+ 'image_type' => $image['name']['type'],
+ 'data' => file_get_contents($image['file']),
+ );
+ }
+
+ if (isset($image['tags']) && is_array($image['tags']) &&
+ count($image['tags'])) {
+ $image_data['tags'] = $image['tags'];
+ }
+
+ $image_id = $gallery->addImage($image_data, $default);
+ if (is_a($image_id, 'PEAR_Error')) {
+ return $image_id;
+ }
+
+ // Call the postupload hook if needed
+ if (!empty($GLOBALS['conf']['hooks']['postupload']) && !$skiphook) {
+ Horde::callHook('_ansel_hook_postupload', array(array($image_id)), 'ansel');
+ }
+
+ return array('image_id' => (int)$image_id,
+ 'gallery_id' => (int)$gallery->id,
+ 'gallery_slug' => $gallery->get('slug'),
+ 'image_count' => (int)$gallery->countImages());
+ }
+
+ /**
+ * Notify Ansel that a group of images has just been uploaded. Used for when
+ * the _ansel_hook_postupload hook should be called with a group of recently
+ * uploaded images, as opposed to calling it once after each image is saved.
+ *
+ * @param array $image_ids An array of image ids.
+ */
+ public function postBatchUpload($image_ids)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+ if (!empty($conf['hooks']['postupload'])) {
+ return Horde::callHook('_ansel_hook_postupload', array($image_ids), 'ansel');
+ }
+ }
+
+ /**
+ * Removes an image from a gallery.
+ *
+ * @param string $app Application scope to use, if not the default.
+ * @param integer $gallery_id The id of gallery.
+ * @param string $image_id The id of image to remove.
+ */
+ public function removeImage($app = null, $gallery_id, $image_id)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ /* Check global Ansel permissions */
+ if (!($GLOBALS['perms']->getPermissions('ansel'))) {
+ return PEAR::raiseError(_("Access denied deleting galleries."));
+ }
+
+ /* If no app is given use Ansel's own gallery which is initialized in
+ base.php */
+ if (!is_null($app)) {
+ $GLOBALS['ansel_storage'] = new Ansel_Storage($app);
+ }
+
+ $image = $GLOBALS['ansel_storage']->getImage($image_id);
+ if (is_a($image, 'PEAR_Error')) {
+ return $image;
+ }
+ $gallery = $GLOBALS['ansel_storage']->getGallery($image->gallery);
+ if (is_a($gallery, 'PEAR_Error') ||
+ !$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_DELETE)) {
+
+ return PEAR::raiseError(sprintf(_("Access denied deleting photos from \"%s\"."), $gallery->get('name')));
+ }
+ return $gallery->removeImage($image);
+ }
+
+ /**
+ * Add a new gallery to any arbitrary application's Ansel_Shares.
+ *
+ * @param string $app Application scope to use, if not the default.
+ * @param array $attributes The gallery attributes
+ * (@see Ansel_Storage::createGallery).
+ * @param array $perm An array of permission data if Ansel's defaults
+ * are not desired. Takes an array like:
+ * array('guest' => PERMS_SHOW | PERMS_READ,
+ * 'default' => PERMS_SHOW | PERMS_READ);
+ * @param integer $parent The gallery id of the parent gallery, if any.
+ *
+ * @return mixed The gallery id of the new gallery | PEAR_Error
+ */
+ public function createGallery($app = null, $attributes = array(), $perm = null, $parent = null)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ if (!(Horde_Auth::isAdmin() ||
+ (!$GLOBALS['perms']->exists('ansel') && Horde_Auth::getAuth()) ||
+ $GLOBALS['perms']->hasPermission('ansel', Horde_Auth::getAuth(), PERMS_EDIT))) {
+ return PEAR::raiseError(_("Access denied creating new galleries."));
+ }
+
+ if (!is_null($app)) {
+ $GLOBALS['ansel_storage'] = new Ansel_Storage($app);
+ }
+
+ if (!empty($perm)) {
+ // The name is inconsequential; it is only used as a container to
+ // represent permissions when passed to the Ansel backend.
+ $permobj = new Horde_Permission('');
+ $permobj->data = $perm;
+ } else {
+ $permobj = null;
+ }
+
+ $gallery = $GLOBALS['ansel_storage']->createGallery($attributes, $permobj, $parent);
+ if (is_a($gallery, 'PEAR_Error')) {
+ return $gallery;
+ }
+ return $gallery->id;
+ }
+
+ /**
+ * Removes a gallery and its images.
+ *
+ * @param string $app Application scope to use, if not the default.
+ * @param integer $gallery_id The id of gallery.
+ *
+ * @return mixed boolean true | PEAR_Error
+ */
+ public function removeGallery($app = null, $gallery_id)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ /* Check global Ansel permissions */
+ if (!($GLOBALS['perms']->getPermissions('ansel'))) {
+ return PEAR::raiseError(_("Access denied deleting galleries."));
+ }
+
+ /* If no app is given use Ansel's own gallery which is initialized in
+ base.php */
+ if (!is_null($app)) {
+ $GLOBALS['ansel_storage'] = new Ansel_Storage($app);
+ }
+
+ $gallery = $GLOBALS['ansel_storage']->getGallery($gallery_id);
+ if (is_a($gallery, 'PEAR_Error')) {
+ return PEAR::raiseError(sprintf(_("Access denied deleting gallery \"%s\"."),
+ $gallery->getMessage()));
+ } elseif (!$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_DELETE)) {
+ return PEAR::raiseError(sprintf(_("Access denied deleting gallery \"%s\"."),
+ $gallery->get('name')));
+ } else {
+ $imageList = $gallery->listImages();
+ if ($imageList) {
+ foreach ($imageList as $id) {
+ $gallery->removeImage($id);
+ }
+ }
+ $result = $GLOBALS['ansel_storage']->removeGallery($gallery);
+ if (!is_a($result, 'PEAR_Error')) {
+ return true;
+ } else {
+ return PEAR::raiseError(sprintf(_("There was a problem deleting %s: %s"),
+ $gallery->get('name'),
+ $result->getMessage()));
+ }
+ }
+ }
+
+ /**
+ * Returns the number of images in a gallery.
+ *
+ * @param integer $app Application used; if null then use default.
+ * @param integer $gallery_id The gallery id.
+ * @param string $slug The gallery slug.
+ *
+ * @return integer The number of images in the gallery.
+ */
+ public function count($app = null, $gallery_id = null, $slug = '')
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ /* If no app is given use Ansel's own gallery which is initialized in
+ base.php */
+ if (!is_null($app)) {
+ $GLOBALS['ansel_storage'] = new Ansel_Storage($app);
+ }
+
+ if (!empty($slug)) {
+ $gallery = $GLOBALS['ansel_storage']->getGalleryBySlug($slug);
+ } else {
+ $gallery = $GLOBALS['ansel_storage']->getGallery($gallery_id);
+ }
+
+ if (is_a($gallery, 'PEAR_Error')) {
+ return 0;
+ } else {
+ return (int)$gallery->countImages();
+ }
+ }
+
+ /**
+ * Returns the default image id of a gallery.
+ *
+ * @param string $app Application scope to use, if not the default.
+ * @param integer $gallery_id The gallery id.
+ * @param string $style The named style.
+ * @param string $slug The gallery slug.
+ *
+ * @return integer The default image id.
+ */
+ public function getDefaultImage($app = null, $gallery_id = null,
+ $style = 'ansel_default', $slug = '')
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ /* If no app is given use Ansel's own gallery which is initialized in
+ base.php */
+ if (!is_null($app)) {
+ $GLOBALS['ansel_storage'] = new Ansel_Storage($app);
+ }
+
+ if (!empty($slug)) {
+ $gallery = $GLOBALS['ansel_storage']->getGalleryBySlug($slug);
+ } else {
+ $gallery = $GLOBALS['ansel_storage']->getGallery($gallery_id);
+ }
+
+ if (is_a($gallery, 'PEAR_Error')) {
+ return $gallery;
+ } else {
+ return $gallery->getDefaultImage($style);
+ }
+ }
+
+ /**
+ * Returns image URL.
+ *
+ * @param integer $app Application used.
+ * @param integer $image_id The id of the image.
+ * @param string $view The view ('screen', 'thumb', 'full', etc) to show.
+ * @param boolean $full Return a path that includes the server name?
+ * @param string $style Use this gallery style
+ *
+ * @return string The image path.
+ */
+ public function getImageUrl($app = null, $image_id, $view = 'screen',
+ $full = false, $style = null)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ /* If no app is given use Ansel's own gallery which is initialized in
+ base.php */
+ if (!is_null($app)) {
+ $GLOBALS['ansel_storage'] = new Ansel_Storage($app);
+ }
+
+ return Ansel::getImageUrl($image_id, $view, $full, $style);
+ }
+
+ /**
+ * Returns image file content.
+ *
+ * @param integer $image_id The id of the image.
+ * @param string $view The view ('screen', 'thumb', 'full', etc) to show.
+ * @param string $style Force use of this gallery style.
+ * @param integer $app Application used.
+ * @param string $encoding The encoding type for the image data.
+ * (none, base64, or binhex)
+ * @param string $compression The compression type for the image data.
+ * (none, gzip, or lzf)
+ *
+ * @return string The image path.
+ */
+ public function getImageContent($image_id, $view = 'screen', $style = null,
+ $app = null, $encoding = null, $compression = 'none')
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ /* If no app is given use Ansel's own gallery which is initialized in
+ base.php */
+ if (!is_null($app)) {
+ $GLOBALS['ansel_storage'] = new Ansel_Storage($app);
+ }
+
+ // Get image
+ $image = $GLOBALS['ansel_storage']->getImage($image_id);
+ if (is_a($image, 'PEAR_Error')) {
+ return $image;
+ }
+
+ // Get gallery
+ $gallery = $GLOBALS['ansel_storage']->getGallery($image->gallery);
+ if (is_a($gallery, 'PEAR_Error')) {
+ return $gallery;
+ }
+
+ // Check age and password
+ if (!$gallery->hasPasswd() || !$gallery->isOldEnough()) {
+ return PEAR::raiseError(_("Locked galleries are not viewable via the api."));
+ }
+
+ if ($view == 'full') {
+ // Check permissions for full view
+ if (!$gallery->canDownload()) {
+ return PEAR::RaiseError(sprintf(_("Access denied downloading photos from \"%s\"."), $gallery->get('name')));
+ }
+
+ $data = $GLOBALS['ansel_vfs']->read($image->getVFSPath('full'),
+ $image->getVFSName('full'));
+ } else {
+ // Load View
+ $result = $image->load($view, $style);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ // Return image content
+ $data = $image->_image->raw();
+ }
+
+ if (is_a($data, 'PEAR_Error')) {
+ return $data;
+ }
+
+ return $this->_getImageData($data, $encoding, $compression, false);
+ }
+
+ /**
+ * Returns a list of all galleries.
+ *
+ * @param string $app Application scope to use, if not the default.
+ * @param integer $perm The level of permissions to require for a gallery
+ * to be returned.
+ * @param integer $parent The parent gallery id to start searching from.
+ * This should be either a gallery id or null.
+ * @param boolean $allLevels Return all levels, or just the direct children of
+ * $parent?
+ * @param integer $from The gallery to start listing at.
+ * @param integer $count The number of galleries to return.
+ * @param array $attributes Restrict the returned galleries to those matching
+ * $attributes. An array of attribute names => values
+ *
+ * @return array An array of gallery information.
+ */
+ public function listGalleries($app = null, $perm = PERMS_SHOW,
+ $parent = null,
+ $allLevels = true, $from = 0, $count = 0,
+ $attributes = null, $sort_by = null, $direction = 0)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ /* If no app is given use Ansel's own gallery which is initialized in
+ base.php */
+ if (!is_null($app)) {
+ $GLOBALS['ansel_storage'] = new Ansel_Storage($app);
+ }
+
+
+ $galleries = $GLOBALS['ansel_storage']->listGalleries(
+ $perm, $attributes, $parent, $allLevels, $from, $count, $sort_by, $direction);
+
+ if (is_a($galleries, 'PEAR_Error')) {
+ return $galleries;
+ }
+
+ $return = array();
+ foreach ($galleries as $gallery) {
+ $return[$gallery->id] = array_merge($gallery->data, array('crumbs' => $gallery->getGalleryCrumbData()));
+ }
+
+ return $return;
+ }
+
+ /**
+ * Returns an array of gallery information.
+ *
+ * @param array $ids An array of gallery ids.
+ * @param string $app Application scope to use, if not the default.
+ * @param array $slugs An array of gallery slugs.
+ *
+ * @return mixed An array of gallery data arrays | PEAR_Error
+ */
+ public function getGalleries($ids = array(), $app = null, $slugs = array())
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ if (!is_null($app)) {
+ $GLOBALS['ansel_storage'] = new Ansel_Storage($app);
+ }
+
+ if (count($slugs)) {
+ $results = $GLOBALS['ansel_storage']->getGalleriesBySlugs($slugs);
+ } else {
+ $results = $GLOBALS['ansel_storage']->getGalleries($ids);
+ }
+
+ if (is_a($results, 'PEAR_Error')) {
+ return $results;
+ }
+
+ /* We can't just return the results of the getGalleries call - we need to
+ ensure the caller has at least PERMS_READ on the galleries. */
+ $galleries = array();
+ foreach ($results as $gallery) {
+ if ($gallery->hasPermission(Horde_Auth::getAuth(), PERMS_READ)) {
+ $galleries[$gallery->id] = array_merge($gallery->data, array('crumbs' => $gallery->getGalleryCrumbData()));
+ }
+ }
+
+ return $galleries;
+ }
+
+ /**
+ * Returns a 'select' menu from the list of galleries created by
+ * listGalleries().
+ *
+ * @param integer $app Application used if null then use default.
+ * @param integer $perm The permissions filter to use.
+ * @param string $parent The parent share to start listing at.
+ * @param boolean $allLevels Return all levels, or just the direct
+ * @param integer $from The gallery to start listing at.
+ * @param integer $count The number of galleries to return.
+ * @param boolean $default The gallery_id of the gallery that is
+ * selected by default in the returned option
+ * list.
+ */
+ public function selectGalleries($app = null, $perm = PERMS_SHOW,
+ $parent = null,
+ $allLevels = true, $from = 0, $count = 0,
+ $default = null)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ /* If no app is given use Ansel's own gallery which is initialized in
+ base.php */
+ if (!is_null($app)) {
+ $GLOBALS['ansel_storage'] = new Ansel_Storage($app);
+ }
+
+ return Ansel::selectGalleries($default, $perm, null, $parent, $allLevels,
+ $from, $count);
+ }
+
+ /**
+ * Returns a list of all images in a gallery.
+ *
+ * The return has the URL because in a lot of cases you'll want the url
+ * also. Using api call getImageURL results in a lot of overhead when
+ * e.g. generating a select list.
+ *
+ * @param string $app Application scope to use, if not the default.
+ * @param integer $gallery_id Gallery id to get images from.
+ * @param integer $perm The level of permissions to require for a
+ * gallery to return it.
+ * @param string $view Viewsize to generate URLs for.
+ * @param boolean $full Return a full URL
+ * @param integer $from Start image.
+ * @param integer $count End image.
+ * @param string $style Use this gallery style.
+ * @param string $slug Gallery slug
+ *
+ * @return array Two dimensional array with image names ids (key) and urls.
+ */
+ public function listImages($app = null, $gallery_id = null, $perm = PERMS_SHOW,
+ $view = 'screen', $full = false, $from = 0,
+ $count = 0, $style = null, $slug = '')
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ /* If no app is given use Ansel's own gallery which is initialized in
+ base.php */
+ if (!is_null($app)) {
+ $GLOBALS['ansel_storage'] = new Ansel_Storage($app);
+ }
+
+ /* Determine the default gallery when none is given. The first gallery in
+ the list is the default gallery. */
+ if (is_null($gallery_id) && empty($slug)) {
+ $galleries = $GLOBALS['ansel_storage']->listGalleries($perm);
+ if (!count($galleries)) {
+ return array();
+ }
+ $keys = array_keys($galleries);
+ $gallery_names = array_keys($galleries[$keys[0]]['galleries']);
+ $gallery_id = $gallery_names[0];
+ } elseif (!empty($slug)) {
+ $gallery = $GLOBALS['ansel_storage']->getGalleryBySlug($slug);
+ } else {
+ $gallery = $GLOBALS['ansel_storage']->getGallery($gallery_id);
+ }
+ if (is_a($gallery, 'PEAR_Error')) {
+ return $gallery;
+ }
+
+ $images = $gallery->listImages();
+ if (is_a($images, 'PEAR_Error')) {
+ return $images;
+ }
+
+ $counter = 0;
+ $imagelist = array();
+ foreach ($images as $id) {
+ $image = $GLOBALS['ansel_storage']->getImage($id);
+ if (is_a($image, 'PEAR_Error')) {
+ return $image;
+ }
+ $imagelist[$id]['name'] = $image->filename;
+ $imagelist[$id]['caption'] = $image->caption;
+ $imagelist[$id]['type'] = $image->type;
+ $imagelist[$id]['uploaded'] = $image->uploaded;
+ $imagelist[$id]['original_date'] = $image->originalDate;
+ $imagelist[$id]['url'] = Ansel::getImageUrl($id, $view, $full, $style);
+ if (!is_null($app) && $GLOBALS['conf']['vfs']['src'] != 'direct') {
+ $imagelist[$id]['url'] = Horde_Util::addParameter($imagelist[$id]['url'],
+ 'app', $app);
+ }
+ }
+ return $imagelist;
+ }
+
+ /**
+ * Return a list of recently added images
+ *
+ * @param string $app Application used if null then use default.
+ * @param array $galleries An array of gallery ids to check. If left empty,
+ * will search all galleries with the given
+ * permissions for the current user.
+ * @param integer $perms PERMS_* constant
+ * @param string $view The type of image view to return.
+ * @param boolean $full Return a full URL if this is true.
+ * @param integer $limit The maximum number of images to return.
+ * @param string $style Force the use of this gallery style
+ * @param string $slugs An array of gallery slugs
+ *
+ * @return array An array of image objects.
+ */
+ public function getRecentImages($app = null, $galleries = array(),
+ $perms = PERMS_SHOW, $view = 'screen',
+ $full = false, $limit = 10, $style = null,
+ $slugs = array())
+ {
+ require_once dirname(__FILE__) . '/base.php';
+ if (!is_null($app)) {
+ $GLOBALS['ansel_storage'] = new Ansel_Storage($app);
+ }
+ $images = $GLOBALS['ansel_storage']->getRecentImages($galleries, $limit, $slugs);
+ $imagelist = array();
+ foreach ($images as $image) {
+ $id = $image->id;
+ $imagelist[$id]['id'] = $id;
+ $imagelist[$id]['name'] = $image->filename;
+ $imagelist[$id]['url'] = Ansel::getImageUrl($id, $view, $full, $style);
+ $imagelist[$id]['caption'] = $image->caption;
+ $imagelist[$id]['filename'] = $image->filename;
+ $imagelist[$id]['gallery'] = $image->gallery;
+ $imagelist[$id]['uploaded'] = $image->uploaded;
+ $imagelist[$id]['original_date'] = $image->originalDate;
+
+ if (!is_null($app) && $GLOBALS['conf']['vfs']['src'] != 'direct') {
+ $imagelist[$id]['url'] = Horde_Util::addParameter($imagelist[$id]['url'],
+ 'app', $app);
+ }
+ }
+ return $imagelist;
+
+ }
+
+ /**
+ * Counts the number of galleries.
+ *
+ * @param string $app Application scope to use, if not the default.
+ * @param integer $perm The level of permissions to require for a gallery
+ * to return it.
+ * @param mixed $attributes Restrict the galleries counted to those matching
+ * $attributes. An array of attribute/value pairs or
+ * a gallery owner username.
+ * @param integer $parent The parent gallery id to start searching at.
+ * @param boolean $allLevels Return all levels, or just the direct children of
+ * $parent?
+ *
+ * @return integer Returns the number of matching galleries.
+ */
+ public function countGalleries($app = null, $perm = PERMS_SHOW, $attributes = null,
+ $parent = null, $allLevels = true)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ /* If no app is given use Ansel's own gallery which is initialized
+ * in base.php */
+ if (!is_null($app)) {
+ $GLOBALS['ansel_storage'] = new Ansel_Storage($app);
+ }
+
+ return $GLOBALS['ansel_storage']->countGalleries(Horde_Auth::getAuth(), $perm,
+ $attributes, $parent,
+ $allLevels);
+ }
+
+ /**
+ * Retrieve the list of used tag_names, tag_ids and the total number
+ * of resources that are linked to that tag.
+ *
+ * @param array $tags An optional array of tag_ids. If omitted, all tags
+ * will be included.
+ *
+ * @return mixed An array containing tag_name, and total | PEAR_Error
+ */
+ public function listTagInfo($tags = null)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ return Ansel_Tags::listTagInfo($tags);
+ }
+
+ /**
+ * Searches images/galleries tagged with all requested tags.
+ * Returns an application-agnostic array (useful for when doing a tag search
+ * across multiple applications) containing the following keys:
+ * <pre>
+ * 'title' - The title for this resource.
+ * 'desc' - A terse description of this resource.
+ * 'view_url' - The URL to view this resource.
+ * 'app' - The Horde application this resource belongs to.
+ * </pre>
+ *
+ * The 'raw' results array can be returned instead by setting $raw = true.
+ *
+ * @param array $names An array of tag_names to search for.
+ * @param integer $max The maximum number of stories to return.
+ * @param integer $from The number of the story to start with.
+ * @param string $resource_type An array of channel_ids to limit the search to.
+ * @param string $user Restrict results to resources owned by $user.
+ * @param boolean $raw Return the raw story data?
+ * @param string $app Application scope to use, if not the default.
+ *
+ * @return mixed An array of results | PEAR_Error
+ */
+ public function searchTags($names, $max = 10, $from = 0,
+ $resource_type = 'all', $user = null, $raw = false,
+ $app = null)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ if (!is_null($app)) {
+ $GLOBALS['ansel_storage'] = new Ansel_Storage($app);
+ } else {
+ $app = 'ansel';
+ }
+
+ $results = Ansel_Tags::searchTags($names, $max, $from, $resource_type,
+ $user);
+
+ /* Check for error or if we requested the raw data array */
+ if (is_a($results, 'PEAR_Error') || $raw) {
+ return $results;
+ }
+
+ $return = array();
+ if (!empty($results['images'])) {
+ foreach ($results['images'] as $image_id) {
+ $image = $GLOBALS['ansel_storage']->getImage($image_id);
+ $desc = $image->caption;
+ $title = $image->filename;
+ $view_url = Ansel::getUrlFor('view',
+ array('gallery' => $image->gallery,
+ 'image' => $image_id,
+ 'view' => 'Image'),
+ true);
+ $return[] = array('title' => $image->filename,
+ 'desc'=> $image->caption,
+ 'view_url' => $view_url,
+ 'app' => $app);
+ }
+
+ }
+
+ if (!empty($results['galleries'])) {
+ foreach ($results['galleries'] as $gallery) {
+ $gal = $GLOBALS['ansel_storage']->getGallery($gallery);
+ $view_url = Horde_Util::addParameter(Horde::applicationUrl('view.php'), array('gallery' => $gallery,
+ 'view' => 'Gallery'));
+ $return[] = array('title' => $gal->get('name'),
+ 'desc' => $gal->get('desc'),
+ 'view_url' => $view_url,
+ 'app' => $app);
+
+ }
+ }
+
+
+ return $return;
+ }
+
+ /**
+ * Checks if the gallery exists
+ *
+ * @param string $app Application scope to use, if not the default.
+ * @param integer $gallery_id The gallery id
+ * @param string $slug The gallery slug
+ *
+ * @return boolean
+ */
+ public function galleryExists($app, $gallery_id = null, $slug = '')
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ if (!is_null($app)) {
+ $GLOBALS['ansel_storage'] = new Ansel_Storage($app);
+ }
+
+ return $GLOBALS['ansel_storage']->galleryExists($gallery_id, $slug);
+ }
+
+ /**
+ * Get a list of all configured styles.
+ *
+ * @return hash of style definitions.
+ */
+ public function getGalleryStyles()
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ return Ansel::getAvailableStyles();
+ }
+
+ /**
+ * Renders a gallery view
+ *
+ * @param array $params Any parameters that the view might need.
+ * @see Ansel_View_* classes for descriptions of
+ * available parameters to use here.
+ * @param string $app Application scope to use, if not the default.
+ * @param string $view The generic type of view we want.
+ * (Gallery, Image, List, Embedded)
+ *
+ * @return array An array containing 'html' and 'crumbs' keys.
+ */
+ public function renderView($params = array(), $app = null,
+ $view = 'Gallery')
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ if (!is_null($app)) {
+ $GLOBALS['ansel_storage'] = new Ansel_Storage($app);
+ }
+ $classname = 'Ansel_View_' . basename($view);
+ $params['api'] = true;
+ $params['view'] = $view;
+ $trail = array();
+ $return = array();
+ try {
+ $view = new $classname($params);
+ } catch (Horde_Exception $e) {
+ $return['html'] = $e->getMessage();
+ $return['crumbs'] = array();
+ return $return;
+ }
+ $return['html'] = $view->html();
+ if ($params['view'] == 'Gallery' || $params['view'] == 'Image') {
+ $trail = $view->getGalleryCrumbData();
+ }
+ $return['crumbs'] = $trail;
+
+ return $return;
+
+ }
+
+}
+++ /dev/null
-<?php
-/**
- * Ansel external API interface.
- *
- * Copyright 2004-2009 The Horde Project (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
- *
- * @author Jan Schneider <jan@horde.org>
- * @author Chuck Hagenbuch <chuck@horde.org>
- * @author Michael J. Rubinsky <mrubinsk@horde.org>
- * @package Ansel
- */
-
-$_services['perms'] = array(
- 'args' => array(),
- 'type' => '{urn:horde}stringArray'
-);
-
-$_services['browse'] = array(
- 'args' => array('path' => 'string'),
- 'type' => '{urn:horde}hashHash',
-);
-
-$_services['put'] = array(
- 'args' => array('path' => 'string', 'content' => 'string', 'content_type' => 'string'),
- 'type' => '{urn:horde}stringArray',
-);
-
-// $_services['path_delete'] = array(
-// 'args' => array('path' => 'string'),
-// 'type' => 'boolean',
-// );
-
-$_services['commentCallback'] = array(
- 'args' => array('image_id' => 'string'),
- 'type' => 'string'
-);
-
-$_services['hasComments'] = array(
- 'args' => array(),
- 'type' => 'boolean'
-);
-
-$_services['saveImage'] = array(
- 'args' => array('app' => 'string',
- 'gallery_id' => 'string',
- 'image' => '{urn:horde}hashHash',
- 'default' => 'boolean',
- 'gallery_data' => '{urn:horde}hashHash',
- 'encoding' => 'string',
- 'slug' => 'string',
- 'compression' => 'string',
- 'skiphook' => 'boolean'),
- 'type' => '{urn:horde}stringArray'
-);
-
-$_services['postBatchUpload'] = array(
- 'args' => array('image_ids' => '{urn:horde}hash'),
- 'type' => 'int'
-);
-
-$_services['createGallery'] = array(
- 'args' => array('app' => 'string',
- 'attributes' => '{urn:horde}hashHash',
- 'perm' => '{urn:horde}hashHash'),
- 'type' => 'int'
-);
-
-$_services['removeImage'] = array(
- 'args' => array('app' => 'string',
- 'gallery_id' => 'integer',
- 'image_id' => 'integer'),
- 'type' => 'int'
-);
-
-$_services['removeGallery'] = array(
- 'args' => array('app' => 'string',
- 'gallery_id' => 'integer'),
- 'type' => 'int'
-);
-
-$_services['getImageUrl'] = array(
- 'args' => array('app' => 'string',
- 'image_id' => 'integer',
- 'view' => 'string',
- 'full' => 'boolean',
- 'style' => 'string'),
- 'type' => 'string'
-);
-
-$_services['getImageContent'] = array(
- 'args' => array('image_id' => 'integer',
- 'view' => 'string',
- 'style' => 'string',
- 'app' => 'string'),
- 'type' => 'string'
-);
-
-$_services['count'] = array(
- 'args' => array('app' => 'string',
- 'gallery_id' => 'integer'),
- 'type' => 'int'
-);
-
-$_services['getDefaultImage'] = array(
- 'args' => array('app' => 'string',
- 'gallery_id' => 'integer',
- 'style' => 'string'),
- 'type' => 'string'
-);
-
-$_services['listGalleries'] = array(
- 'args' => array('app' => 'string',
- 'perm' => 'integer',
- 'parent' => 'string',
- 'allLevels' => 'string',
- 'from' => 'integer',
- 'count' => 'integer'),
- 'type' => 'string'
-);
-
-$_services['getGalleries'] = array(
- 'args' => array('ids' => '{urn:horde}hash',
- 'app' => 'string'),
- 'type' => '{urn:horde}hash'
-);
-
-$_services['selectGalleries'] = array(
- 'args' => array('app' => 'string',
- 'perm' => 'integer',
- 'parent' => 'string',
- 'allLevels' => 'string',
- 'from' => 'integer',
- 'count' => 'integer'),
- 'type' => 'string'
-);
-
-$_services['listImages'] = array(
- 'args' => array('app' => 'string',
- 'gallery_id' => 'integer',
- 'perm' => 'integer',
- 'view' => 'string',
- 'full' => 'boolean',
- 'from' => 'integer',
- 'count' => 'integer',
- 'style' => 'string'),
- 'type' => 'string'
-);
-
-$_services['getRecentImages'] = array(
- 'args' => array('app' => 'string',
- 'galleries' => '{urn:horde}hash',
- 'perms' => 'integer',
- 'view' => 'string',
- 'full' => 'boolean',
- 'limit' => 'integer',
- 'style' => 'string',
- 'slugs' => '{urn:horde}hashHash'),
- 'type' => '{urn:horde}hash'
-);
-
-$_services['countGalleries'] = array(
- 'args' => array('app' => 'string',
- 'perm' => 'string',
- 'attributes' => '{urn:horde}hash',
- 'parent' => 'string',
- 'allLevels' => 'boolean'),
- 'type' => 'int'
-);
-
-$_services['listTagInfo'] = array(
- 'args' => array('tags' => '{urn:horde}stringArray'),
- 'type' => '{urn:horde}hash'
-);
-
-$_services['searchTags'] = array(
- 'args' => array('tags' => '{urn:horde}stringArray',
- 'resource_type' => 'string',
- 'count' => 'int',
- 'user' => 'string'),
- 'type' => '{urn:horde}hash'
-);
-
-$_services['galleryExists'] = array(
- 'args' => array('app' => 'string',
- 'gallery_name' => 'string'),
- 'type' => 'boolean'
-);
-
-$_services['renderView'] = array(
- 'args' => array('parameters' => '{urn:horde]stringArray',
- 'app' => 'string',
- 'view' => 'string'),
- 'type' => 'string'
-);
-
-$_services['getGalleryStyles'] = array(
- 'args' => array(),
- 'type' => '{urn:horde}hash');
-
-/**
- * Returns a list of available permissions.
- *
- * @return array An array describing all available permissions.
- */
-function _ansel_perms()
-{
- $perms = array();
- $perms['tree']['ansel']['admin'] = false;
- $perms['title']['ansel:admin'] = _("Administrators");
-
- return $perms;
-}
-
-/**
- * Browse through Ansel's gallery tree.
- *
- * @param string $path The level of the tree to browse.
- * @param array $properties The item properties to return. Defaults to 'name',
- * 'icon', and 'browseable'.
- *
- * @return array The contents of $path
- */
-function _ansel_browse($path = '', $properties = array())
-{
- require_once dirname(__FILE__) . '/base.php';
-
- // Default properties.
- if (!$properties) {
- $properties = array('name', 'icon', 'browseable');
- }
-
- if (substr($path, 0, 5) == 'ansel') {
- $path = substr($path, 5);
- }
- $path = trim($path, '/');
- $parts = explode('/', $path);
-
- if (empty($path)) {
- $owners = array();
- $galleries = $GLOBALS['ansel_storage']->listGalleries(PERMS_SHOW, null, null, false);
- foreach ($galleries as $gallery) {
- $owners[$gallery->data['share_owner']] = true;
- }
-
- $results = array();
- foreach (array_keys($owners) as $owner) {
- if (in_array('name', $properties)) {
- $results['ansel/' . $owner]['name'] = $owner;
- }
- if (in_array('icon', $properties)) {
- $results['ansel/' . $owner]['icon'] =
- $registry->getImageDir('horde') . '/user.png';
- }
- if (in_array('browseable', $properties)) {
- $results['ansel/' . $owner]['browseable'] = true;
- }
- if (in_array('contenttype', $properties)) {
- $results['ansel/' . $owner]['contenttype'] =
- 'httpd/unix-directory';
- }
- if (in_array('contentlength', $properties)) {
- $results['ansel/' . $owner]['contentlength'] = 0;
- }
- if (in_array('modified', $properties)) {
- $results['ansel/' . $owner]['modified'] = time();
- }
- if (in_array('created', $properties)) {
- $results['ansel/' . $owner]['created'] = 0;
- }
- }
- return $results;
-
- } else {
- if (count($parts) == 1) {
- // This request is for all galleries owned by the requested user.
- $galleries = $GLOBALS['ansel_storage']->listGalleries(
- PERMS_SHOW, $parts[0], null, false);
- $images = array();
- } elseif (_ansel_galleryExists(null, end($parts))) {
- // This request if for a certain gallery, list all sub-galleries
- // and images.
- $gallery_id = end($parts);
- $galleries = $GLOBALS['ansel_storage']->getGalleries(
- array($gallery_id));
- if (!isset($galleries[$gallery_id]) ||
- !$galleries[$gallery_id]->hasPermission(Horde_Auth::getAuth(),
- PERMS_READ)) {
- return PEAR::raiseError(_("Invalid gallery specified."), 404);
- }
- $galleries = $GLOBALS['ansel_storage']->listGalleries(
- PERMS_SHOW, null, $gallery_id, false);
-
- $images = _ansel_listImages(null, $gallery_id, PERMS_SHOW, 'mini');
- } elseif (count($parts) > 2 &&
- _ansel_galleryExists(null, $parts[count($parts) - 2]) &&
- !is_a($image = $GLOBALS['ansel_storage']->getImage(end($parts)), 'PEAR_Error')) {
- return array('data' => $image->raw(),
- 'mimetype' => $image->type,
- 'mtime' => $image->uploaded);
- } else {
- return PEAR::raiseError(_("File not found."), 404);
- }
-
- $results = array();
- foreach ($galleries as $galleryId => $gallery) {
- $retpath = 'ansel/' . implode('/', $parts) . '/' . $galleryId;
- if (in_array('name', $properties)) {
- $results[$retpath]['name'] = $gallery->data['attribute_name'];
- }
- if (in_array('displayname', $properties)) {
- $results[$retpath]['displayname'] = rawurlencode(
- $gallery->data['attribute_name']);
- }
- if (in_array('icon', $properties)) {
- $results[$retpath]['icon'] = $registry->getImageDir()
- . '/ansel.png';
- }
- if (in_array('browseable', $properties)) {
- $results[$retpath]['browseable'] = $gallery->hasPermission(
- Horde_Auth::getAuth(), PERMS_READ);
- }
- if (in_array('contenttype', $properties)) {
- $results[$retpath]['contenttype'] = 'httpd/unix-directory';
- }
- if (in_array('contentlength', $properties)) {
- $results[$retpath]['contentlength'] = 0;
- }
- if (in_array('modified', $properties)) {
- $results[$retpath]['modified'] = time();
- }
- if (in_array('created', $properties)) {
- $results[$retpath]['created'] = 0;
- }
- }
-
- foreach ($images as $imageId => $image) {
- $retpath = 'ansel/' . implode('/', $parts) . '/' . $imageId;
- if (in_array('name', $properties)) {
- $results[$retpath]['name'] = $image['name'];
- }
- if (in_array('displayname', $properties)) {
- $results[$retpath]['displayname'] = rawurlencode($image['name']);
- }
- if (in_array('icon', $properties)) {
- $results[$retpath]['icon'] = Horde::url($image['url'], true);
- }
- if (in_array('browseable', $properties)) {
- $results[$retpath]['browseable'] = false;
- }
- if (in_array('contenttype', $properties)) {
- $results[$retpath]['contenttype'] = $image['type'];
- }
- if (in_array('contentlength', $properties)) {
- $results[$retpath]['contentlength'] = 0;
- }
- if (in_array('modified', $properties)) {
- $results[$retpath]['modified'] = $image['uploaded'];
- }
- if (in_array('created', $properties)) {
- $results[$retpath]['created'] = $image['uploaded'];
- }
- }
- return $results;
-
- }
-
- return PEAR::raiseError(_("File not found."), 404);
-}
-
-/**
- * Saves an image into the gallery tree.
- *
- * @param string $path The path where to PUT the file.
- * @param string $content The file content.
- * @param string $content_type The file's content type.
- *
- * @return array The event UIDs, or a PEAR_Error on failure.
- */
-function _ansel_put($path, $content, $content_type)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- if (substr($path, 0, 5) == 'ansel') {
- $path = substr($path, 9);
- }
- $path = trim($path, '/');
- $parts = explode('/', $path);
-
- if (count($parts) < 3) {
- return PEAR::raiseError("Gallery does not exist");
- }
- $image_name = array_pop($parts);
- $gallery_id = end($parts);
- if (!$GLOBALS['ansel_storage']->galleryExists($gallery_id)) {
- return PEAR::raiseError("Gallery does not exist");
- }
- $gallery = $GLOBALS['ansel_storage']->getGallery($gallery_id);
- if (!$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_EDIT)) {
- return PEAR::raiseError(_("Access denied adding photos to \"%s\"."));
- }
-
- return $gallery->addImage(array('image_type' => $content_type,
- 'image_filename' => $image_name,
- 'image_caption' => '',
- 'data' => $content));
-}
-
-/**
- * Callback for Agora comments.
- *
- * @param integer $image_id Image id to check
- *
- * @return mixed Image filename on success | false on failure
- */
-function _ansel_commentCallback($image_id)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- if (!$GLOBALS['conf']['comments']['allow']) {
- return false;
- }
-
- $image = $GLOBALS['ansel_storage']->getImage($image_id);
- if (!$image || is_a($image, 'PEAR_Error')) {
- return false;
- }
-
- return $image->filename;
-}
-
-/**
- * Checks if applications allows comments
- *
- * @return boolean
- */
-function _ansel_hasComments()
-{
- if (($GLOBALS['conf']['comments']['allow'] == 'all' ||
- ($GLOBALS['conf']['comments']['allow'] == 'authenticated' &&
- Horde_Auth::getAuth()))) {
- return true;
- } else {
- return false;
- }
-}
-
-/**
- * Returns decoded image data
- *
- * @param string $data The id of the image.
- * @param string $encoding The encoding type for the image data.
- * (none, base64, or binhex)
- * @param string $compression The compression type for the image data.
- * (none, gzip, or lzf)
- * @param boolean $upload Process direction (true of encode/compress or false if decode/decompress)
- *
- * @return string The image path.
- */
-function _getImageData($data, $encoding = 'none', $compression = 'none', $upload = true)
-{
- switch ($encoding) {
- case 'base64':
- $data = $upload ? base64_decode($data) : base64_encode($data);
- break;
-
- case 'binhex':
- $data = $upload ? pack('H*', $data) : unpack('H*', $data);
- }
-
- switch ($compression) {
- case 'gzip':
- if (Horde_Util::loadExtension('zlib')) {
- return $upload ? gzuncompress($data) : gzcompress($data);
- }
- break;
-
- case 'lzf':
- if (Horde_Util::loadExtension('lzf')) {
- return $upload ? lzf_decompress($data) : lzf_compress($data);
- }
- break;
-
- default:
- return $data;
- }
-}
-
-/**
- * Stores an image in a gallery and returns gallery and image data.
- *
- * @param integer $app Application used if null then use default.
- * @param integer $gallery_id The gallery id to add the image to.
- * @param array $image Image data array. This can either be the return
- * from Horde_Form_Type_image:: or an array with
- * the following four fields:
- * 'filename', 'description', 'data', 'type'
- * @param integer $default Set this image as default in the gallery?
- * @param array $gallery_data Any gallery parameters to change at this time.
- * @param string $encoding The encoding type for the image data.
- * (none, base64, or binhex)
- * @param string $slug Use gallery slug instead of id. (Pass '0' or null
- * to gallery_id parameter).
- * @param string $compression The compression type for the image data.
- * (none, gzip, or lzf)
- *
- * @return mixed An array of image/gallery data || PEAR_Error
- */
-function _ansel_saveImage($app = null, $gallery_id, $image, $default = false,
- $gallery_data = null, $encoding = null, $slug = null,
- $compression = 'none', $skiphook = false)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- $image_data = null;
-
- /* If no app is given use Ansel's own gallery which is initialized
- * in base.php */
- if (!is_null($app)) {
- $GLOBALS['ansel_storage'] = new Ansel_Storage($app);
- }
-
- if (isset($image['filename']) &&
- isset($image['description']) &&
- isset($image['data']) &&
- isset($image['type'])) {
- Horde::logMessage(sprintf("Receiving image %s in _ansel_saveImage() with a raw filesize of %i", $image['filename'], strlen($image['data'])), __FILE__, __LINE__, PEAR_LOG_DEBUG);
- $image_data = array('image_filename' => $image['filename'],
- 'image_caption' => $image['description'],
- 'image_type' => $image['type'],
- 'data' => _getImageData($image['data'], $encoding, $compression, true));
- } else {
- Horde::logMessage(sprintf("Receiving image %s in _ansel_saveImage() with a raw filesize of %i", $image['file'], filesize($image['file'])), __FILE__, __LINE__, PEAR_LOG_DEBUG);
- }
-
- if (is_null($image_data) && getimagesize($image['file']) === false) {
- return PEAR::raiseError(_("The file you uploaded does not appear to be a valid photo."));
- }
- if (empty($slug) && empty($gallery_id)) {
- return PEAR::raiseError(_("A gallery to add this photo to is required."));
- }
- if (!empty($slug)) {
- $gallery = $GLOBALS['ansel_storage']->getGalleryBySlug($slug);
- if (is_a($gallery, 'PEAR_Error')) {
- return $gallery;
- }
- } elseif ($GLOBALS['ansel_storage']->galleryExists($gallery_id)) {
- $gallery = $GLOBALS['ansel_storage']->getGallery($gallery_id);
- if (is_a($gallery, 'PEAR_Error')) {
- return $gallery;
- }
- }
- if (!$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_EDIT)) {
- return PEAR::raiseError(sprintf(_("Access denied adding photos to \"%s\"."), $gallery->get('name')));
- }
- if (!empty($gallery_data)) {
- foreach ($gallery_data as $key => $value) {
- $gallery->set($key, $value);
- }
- $gallery->save();
- }
-
- if (is_null($image_data)) {
- $image_data = array(
- 'image_filename' => $image['name'],
- 'image_caption' => $image['name'],
- 'image_type' => $image['name']['type'],
- 'data' => file_get_contents($image['file']),
- );
- }
-
- if (isset($image['tags']) && is_array($image['tags']) &&
- count($image['tags'])) {
- $image_data['tags'] = $image['tags'];
- }
-
- $image_id = $gallery->addImage($image_data, $default);
- if (is_a($image_id, 'PEAR_Error')) {
- return $image_id;
- }
-
- // Call the postupload hook if needed
- if (!empty($GLOBALS['conf']['hooks']['postupload']) && !$skiphook) {
- Horde::callHook('_ansel_hook_postupload', array(array($image_id)), 'ansel');
- }
-
- return array('image_id' => (int)$image_id,
- 'gallery_id' => (int)$gallery->id,
- 'gallery_slug' => $gallery->get('slug'),
- 'image_count' => (int)$gallery->countImages());
-}
-
-/**
- * Notify Ansel that a group of images has just been uploaded. Used for when
- * the _ansel_hook_postupload hook should be called with a group of recently
- * uploaded images, as opposed to calling it once after each image is saved.
- *
- * @param array $image_ids An array of image ids.
- */
-function _ansel_postBatchUpload($image_ids)
-{
- require_once dirname(__FILE__) . '/base.php';
- if (!empty($conf['hooks']['postupload'])) {
- return Horde::callHook('_ansel_hook_postupload', array($image_ids), 'ansel');
- }
-}
-
-/**
- * Removes an image from a gallery.
- *
- * @param string $app Application scope to use, if not the default.
- * @param integer $gallery_id The id of gallery.
- * @param string $image_id The id of image to remove.
- */
-function _ansel_removeImage($app = null, $gallery_id, $image_id)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- /* Check global Ansel permissions */
- if (!($GLOBALS['perms']->getPermissions('ansel'))) {
- return PEAR::raiseError(_("Access denied deleting galleries."));
- }
-
- /* If no app is given use Ansel's own gallery which is initialized in
- base.php */
- if (!is_null($app)) {
- $GLOBALS['ansel_storage'] = new Ansel_Storage($app);
- }
-
- $image = $GLOBALS['ansel_storage']->getImage($image_id);
- if (is_a($image, 'PEAR_Error')) {
- return $image;
- }
- $gallery = $GLOBALS['ansel_storage']->getGallery($image->gallery);
- if (is_a($gallery, 'PEAR_Error') ||
- !$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_DELETE)) {
-
- return PEAR::raiseError(sprintf(_("Access denied deleting photos from \"%s\"."), $gallery->get('name')));
- }
- return $gallery->removeImage($image);
-}
-
-/**
- * Add a new gallery to any arbitrary application's Ansel_Shares.
- *
- * @param string $app Application scope to use, if not the default.
- * @param array $attributes The gallery attributes
- * (@see Ansel_Storage::createGallery).
- * @param array $perm An array of permission data if Ansel's defaults
- * are not desired. Takes an array like:
- * array('guest' => PERMS_SHOW | PERMS_READ,
- * 'default' => PERMS_SHOW | PERMS_READ);
- * @param integer $parent The gallery id of the parent gallery, if any.
- *
- * @return mixed The gallery id of the new gallery | PEAR_Error
- */
-function _ansel_createGallery($app = null, $attributes = array(), $perm = null, $parent = null)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- if (!(Horde_Auth::isAdmin() ||
- (!$GLOBALS['perms']->exists('ansel') && Horde_Auth::getAuth()) ||
- $GLOBALS['perms']->hasPermission('ansel', Horde_Auth::getAuth(), PERMS_EDIT))) {
- return PEAR::raiseError(_("Access denied creating new galleries."));
- }
-
- if (!is_null($app)) {
- $GLOBALS['ansel_storage'] = new Ansel_Storage($app);
- }
-
- if (!empty($perm)) {
- // The name is inconsequential; it is only used as a container to
- // represent permissions when passed to the Ansel backend.
- $permobj = new Horde_Permission('');
- $permobj->data = $perm;
- } else {
- $permobj = null;
- }
-
- $gallery = $GLOBALS['ansel_storage']->createGallery($attributes, $permobj, $parent);
- if (is_a($gallery, 'PEAR_Error')) {
- return $gallery;
- }
- return $gallery->id;
-}
-
-/**
- * Removes a gallery and its images.
- *
- * @param string $app Application scope to use, if not the default.
- * @param integer $gallery_id The id of gallery.
- *
- * @return mixed boolean true | PEAR_Error
- */
-function _ansel_removeGallery($app = null, $gallery_id)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- /* Check global Ansel permissions */
- if (!($GLOBALS['perms']->getPermissions('ansel'))) {
- return PEAR::raiseError(_("Access denied deleting galleries."));
- }
-
- /* If no app is given use Ansel's own gallery which is initialized in
- base.php */
- if (!is_null($app)) {
- $GLOBALS['ansel_storage'] = new Ansel_Storage($app);
- }
-
- $gallery = $GLOBALS['ansel_storage']->getGallery($gallery_id);
- if (is_a($gallery, 'PEAR_Error')) {
- return PEAR::raiseError(sprintf(_("Access denied deleting gallery \"%s\"."),
- $gallery->getMessage()));
- } elseif (!$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_DELETE)) {
- return PEAR::raiseError(sprintf(_("Access denied deleting gallery \"%s\"."),
- $gallery->get('name')));
- } else {
- $imageList = $gallery->listImages();
- if ($imageList) {
- foreach ($imageList as $id) {
- $gallery->removeImage($id);
- }
- }
- $result = $GLOBALS['ansel_storage']->removeGallery($gallery);
- if (!is_a($result, 'PEAR_Error')) {
- return true;
- } else {
- return PEAR::raiseError(sprintf(_("There was a problem deleting %s: %s"),
- $gallery->get('name'),
- $result->getMessage()));
- }
- }
-}
-
-/**
- * Returns the number of images in a gallery.
- *
- * @param integer $app Application used; if null then use default.
- * @param integer $gallery_id The gallery id.
- * @param string $slug The gallery slug.
- *
- * @return integer The number of images in the gallery.
- */
-function _ansel_count($app = null, $gallery_id = null, $slug = '')
-{
- require_once dirname(__FILE__) . '/base.php';
-
- /* If no app is given use Ansel's own gallery which is initialized in
- base.php */
- if (!is_null($app)) {
- $GLOBALS['ansel_storage'] = new Ansel_Storage($app);
- }
-
- if (!empty($slug)) {
- $gallery = $GLOBALS['ansel_storage']->getGalleryBySlug($slug);
- } else {
- $gallery = $GLOBALS['ansel_storage']->getGallery($gallery_id);
- }
-
- if (is_a($gallery, 'PEAR_Error')) {
- return 0;
- } else {
- return (int)$gallery->countImages();
- }
-}
-
-/**
- * Returns the default image id of a gallery.
- *
- * @param string $app Application scope to use, if not the default.
- * @param integer $gallery_id The gallery id.
- * @param string $style The named style.
- * @param string $slug The gallery slug.
- *
- * @return integer The default image id.
- */
-function _ansel_getDefaultImage($app = null, $gallery_id = null,
- $style = 'ansel_default', $slug = '')
-{
- require_once dirname(__FILE__) . '/base.php';
-
- /* If no app is given use Ansel's own gallery which is initialized in
- base.php */
- if (!is_null($app)) {
- $GLOBALS['ansel_storage'] = new Ansel_Storage($app);
- }
-
- if (!empty($slug)) {
- $gallery = $GLOBALS['ansel_storage']->getGalleryBySlug($slug);
- } else {
- $gallery = $GLOBALS['ansel_storage']->getGallery($gallery_id);
- }
-
- if (is_a($gallery, 'PEAR_Error')) {
- return $gallery;
- } else {
- return $gallery->getDefaultImage($style);
- }
-}
-
-/**
- * Returns image URL.
- *
- * @param integer $app Application used.
- * @param integer $image_id The id of the image.
- * @param string $view The view ('screen', 'thumb', 'full', etc) to show.
- * @param boolean $full Return a path that includes the server name?
- * @param string $style Use this gallery style
- *
- * @return string The image path.
- */
-function _ansel_getImageUrl($app = null, $image_id, $view = 'screen',
- $full = false, $style = null)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- /* If no app is given use Ansel's own gallery which is initialized in
- base.php */
- if (!is_null($app)) {
- $GLOBALS['ansel_storage'] = new Ansel_Storage($app);
- }
-
- return Ansel::getImageUrl($image_id, $view, $full, $style);
-}
-
-/**
- * Returns image file content.
- *
- * @param integer $image_id The id of the image.
- * @param string $view The view ('screen', 'thumb', 'full', etc) to show.
- * @param string $style Force use of this gallery style.
- * @param integer $app Application used.
- * @param string $encoding The encoding type for the image data.
- * (none, base64, or binhex)
- * @param string $compression The compression type for the image data.
- * (none, gzip, or lzf)
- *
- * @return string The image path.
- */
-function _ansel_getImageContent($image_id, $view = 'screen', $style = null,
- $app = null, $encoding = null, $compression = 'none')
-{
- require_once dirname(__FILE__) . '/base.php';
-
- /* If no app is given use Ansel's own gallery which is initialized in
- base.php */
- if (!is_null($app)) {
- $GLOBALS['ansel_storage'] = new Ansel_Storage($app);
- }
-
- // Get image
- $image = $GLOBALS['ansel_storage']->getImage($image_id);
- if (is_a($image, 'PEAR_Error')) {
- return $image;
- }
-
- // Get gallery
- $gallery = $GLOBALS['ansel_storage']->getGallery($image->gallery);
- if (is_a($gallery, 'PEAR_Error')) {
- return $gallery;
- }
-
- // Check age and password
- if (!$gallery->hasPasswd() || !$gallery->isOldEnough()) {
- return PEAR::raiseError(_("Locked galleries are not viewable via the api."));
- }
-
- if ($view == 'full') {
- // Check permissions for full view
- if (!$gallery->canDownload()) {
- return PEAR::RaiseError(sprintf(_("Access denied downloading photos from \"%s\"."), $gallery->get('name')));
- }
-
- $data = $GLOBALS['ansel_vfs']->read($image->getVFSPath('full'),
- $image->getVFSName('full'));
- } else {
- // Load View
- $result = $image->load($view, $style);
- if (is_a($result, 'PEAR_Error')) {
- return $result;
- }
-
- // Return image content
- $data = $image->_image->raw();
- }
-
- if (is_a($data, 'PEAR_Error')) {
- return $data;
- }
-
- return _getImageData($data, $encoding, $compression, false);
-}
-
-/**
- * Returns a list of all galleries.
- *
- * @param string $app Application scope to use, if not the default.
- * @param integer $perm The level of permissions to require for a gallery
- * to be returned.
- * @param integer $parent The parent gallery id to start searching from.
- * This should be either a gallery id or null.
- * @param boolean $allLevels Return all levels, or just the direct children of
- * $parent?
- * @param integer $from The gallery to start listing at.
- * @param integer $count The number of galleries to return.
- * @param array $attributes Restrict the returned galleries to those matching
- * $attributes. An array of attribute names => values
- *
- * @return array An array of gallery information.
- */
-function _ansel_listGalleries($app = null, $perm = PERMS_SHOW,
- $parent = null,
- $allLevels = true, $from = 0, $count = 0,
- $attributes = null, $sort_by = null, $direction = 0)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- /* If no app is given use Ansel's own gallery which is initialized in
- base.php */
- if (!is_null($app)) {
- $GLOBALS['ansel_storage'] = new Ansel_Storage($app);
- }
-
-
- $galleries = $GLOBALS['ansel_storage']->listGalleries(
- $perm, $attributes, $parent, $allLevels, $from, $count, $sort_by, $direction);
-
- if (is_a($galleries, 'PEAR_Error')) {
- return $galleries;
- }
-
- $return = array();
- foreach ($galleries as $gallery) {
- $return[$gallery->id] = array_merge($gallery->data, array('crumbs' => $gallery->getGalleryCrumbData()));
- }
-
- return $return;
-}
-
-/**
- * Returns an array of gallery information.
- *
- * @param array $ids An array of gallery ids.
- * @param string $app Application scope to use, if not the default.
- * @param array $slugs An array of gallery slugs.
- *
- * @return mixed An array of gallery data arrays | PEAR_Error
- */
-function _ansel_getGalleries($ids = array(), $app = null, $slugs = array())
-{
- require_once dirname(__FILE__) . '/base.php';
-
- if (!is_null($app)) {
- $GLOBALS['ansel_storage'] = new Ansel_Storage($app);
- }
-
- if (count($slugs)) {
- $results = $GLOBALS['ansel_storage']->getGalleriesBySlugs($slugs);
- } else {
- $results = $GLOBALS['ansel_storage']->getGalleries($ids);
- }
-
- if (is_a($results, 'PEAR_Error')) {
- return $results;
- }
-
- /* We can't just return the results of the getGalleries call - we need to
- ensure the caller has at least PERMS_READ on the galleries. */
- $galleries = array();
- foreach ($results as $gallery) {
- if ($gallery->hasPermission(Horde_Auth::getAuth(), PERMS_READ)) {
- $galleries[$gallery->id] = array_merge($gallery->data, array('crumbs' => $gallery->getGalleryCrumbData()));
- }
- }
-
- return $galleries;
-}
-
-/**
- * Returns a 'select' menu from the list of galleries created by
- * _ansel_listGalleries().
- *
- *
- * @param integer $app Application used if null then use default.
- * @param integer $perm The permissions filter to use.
- * @param string $parent The parent share to start listing at.
- * @param boolean $allLevels Return all levels, or just the direct
- * @param integer $from The gallery to start listing at.
- * @param integer $count The number of galleries to return.
- * @param boolean $default The gallery_id of the gallery that is
- * selected by default in the returned option
- * list.
- */
-function _ansel_selectGalleries($app = null, $perm = PERMS_SHOW,
- $parent = null,
- $allLevels = true, $from = 0, $count = 0,
- $default = null)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- /* If no app is given use Ansel's own gallery which is initialized in
- base.php */
- if (!is_null($app)) {
- $GLOBALS['ansel_storage'] = new Ansel_Storage($app);
- }
-
- return Ansel::selectGalleries($default, $perm, null, $parent, $allLevels,
- $from, $count);
-}
-
-/**
- * Returns a list of all images in a gallery.
- *
- * The return has the URL because in a lot of cases you'll want the url
- * also. Using api call getImageURL results in a lot of overhead when
- * e.g. generating a select list.
- *
- * @param string $app Application scope to use, if not the default.
- * @param integer $gallery_id Gallery id to get images from.
- * @param integer $perm The level of permissions to require for a
- * gallery to return it.
- * @param string $view Viewsize to generate URLs for.
- * @param boolean $full Return a full URL
- * @param integer $from Start image.
- * @param integer $count End image.
- * @param string $style Use this gallery style.
- * @param string $slug Gallery slug
- *
- * @return array Two dimensional array with image names ids (key) and urls.
- */
-function _ansel_listImages($app = null, $gallery_id = null, $perm = PERMS_SHOW,
- $view = 'screen', $full = false, $from = 0,
- $count = 0, $style = null, $slug = '')
-{
- require_once dirname(__FILE__) . '/base.php';
-
- /* If no app is given use Ansel's own gallery which is initialized in
- base.php */
- if (!is_null($app)) {
- $GLOBALS['ansel_storage'] = new Ansel_Storage($app);
- }
-
- /* Determine the default gallery when none is given. The first gallery in
- the list is the default gallery. */
- if (is_null($gallery_id) && empty($slug)) {
- $galleries = $GLOBALS['ansel_storage']->listGalleries($perm);
- if (!count($galleries)) {
- return array();
- }
- $keys = array_keys($galleries);
- $gallery_names = array_keys($galleries[$keys[0]]['galleries']);
- $gallery_id = $gallery_names[0];
- } elseif (!empty($slug)) {
- $gallery = $GLOBALS['ansel_storage']->getGalleryBySlug($slug);
- } else {
- $gallery = $GLOBALS['ansel_storage']->getGallery($gallery_id);
- }
- if (is_a($gallery, 'PEAR_Error')) {
- return $gallery;
- }
-
- $images = $gallery->listImages();
- if (is_a($images, 'PEAR_Error')) {
- return $images;
- }
-
- $counter = 0;
- $imagelist = array();
- foreach ($images as $id) {
- $image = $GLOBALS['ansel_storage']->getImage($id);
- if (is_a($image, 'PEAR_Error')) {
- return $image;
- }
- $imagelist[$id]['name'] = $image->filename;
- $imagelist[$id]['caption'] = $image->caption;
- $imagelist[$id]['type'] = $image->type;
- $imagelist[$id]['uploaded'] = $image->uploaded;
- $imagelist[$id]['original_date'] = $image->originalDate;
- $imagelist[$id]['url'] = Ansel::getImageUrl($id, $view, $full, $style);
- if (!is_null($app) && $GLOBALS['conf']['vfs']['src'] != 'direct') {
- $imagelist[$id]['url'] = Horde_Util::addParameter($imagelist[$id]['url'],
- 'app', $app);
- }
- }
- return $imagelist;
-}
-
-/**
- * Return a list of recently added images
- *
- * @param string $app Application used if null then use default.
- * @param array $galleries An array of gallery ids to check. If left empty,
- * will search all galleries with the given
- * permissions for the current user.
- * @param integer $perms PERMS_* constant
- * @param string $view The type of image view to return.
- * @param boolean $full Return a full URL if this is true.
- * @param integer $limit The maximum number of images to return.
- * @param string $style Force the use of this gallery style
- * @param string $slugs An array of gallery slugs
- *
- * @return array An array of image objects.
- */
-function _ansel_getRecentImages($app = null, $galleries = array(),
- $perms = PERMS_SHOW, $view = 'screen',
- $full = false, $limit = 10, $style = null,
- $slugs = array())
-{
- require_once dirname(__FILE__) . '/base.php';
- if (!is_null($app)) {
- $GLOBALS['ansel_storage'] = new Ansel_Storage($app);
- }
- $images = $GLOBALS['ansel_storage']->getRecentImages($galleries, $limit, $slugs);
- $imagelist = array();
- foreach ($images as $image) {
- $id = $image->id;
- $imagelist[$id]['id'] = $id;
- $imagelist[$id]['name'] = $image->filename;
- $imagelist[$id]['url'] = Ansel::getImageUrl($id, $view, $full, $style);
- $imagelist[$id]['caption'] = $image->caption;
- $imagelist[$id]['filename'] = $image->filename;
- $imagelist[$id]['gallery'] = $image->gallery;
- $imagelist[$id]['uploaded'] = $image->uploaded;
- $imagelist[$id]['original_date'] = $image->originalDate;
-
- if (!is_null($app) && $GLOBALS['conf']['vfs']['src'] != 'direct') {
- $imagelist[$id]['url'] = Horde_Util::addParameter($imagelist[$id]['url'],
- 'app', $app);
- }
- }
- return $imagelist;
-
-}
-
-/**
- * Counts the number of galleries.
- *
- * @param string $app Application scope to use, if not the default.
- * @param integer $perm The level of permissions to require for a gallery
- * to return it.
- * @param mixed $attributes Restrict the galleries counted to those matching
- * $attributes. An array of attribute/value pairs or
- * a gallery owner username.
- * @param integer $parent The parent gallery id to start searching at.
- * @param boolean $allLevels Return all levels, or just the direct children of
- * $parent?
- *
- * @return integer Returns the number of matching galleries.
- */
-function _ansel_countGalleries($app = null, $perm = PERMS_SHOW, $attributes = null,
- $parent = null, $allLevels = true)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- /* If no app is given use Ansel's own gallery which is initialized
- * in base.php */
- if (!is_null($app)) {
- $GLOBALS['ansel_storage'] = new Ansel_Storage($app);
- }
-
- return $GLOBALS['ansel_storage']->countGalleries(Horde_Auth::getAuth(), $perm,
- $attributes, $parent,
- $allLevels);
-}
-
-/**
- * Retrieve the list of used tag_names, tag_ids and the total number
- * of resources that are linked to that tag.
- *
- * @param array $tags An optional array of tag_ids. If omitted, all tags
- * will be included.
- *
- * @return mixed An array containing tag_name, and total | PEAR_Error
- */
-function _ansel_listTagInfo($tags = null)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- return Ansel_Tags::listTagInfo($tags);
-}
-
-/**
- * Searches images/galleries tagged with all requested tags.
- * Returns an application-agnostic array (useful for when doing a tag search
- * across multiple applications) containing the following keys:
- * <pre>
- * 'title' - The title for this resource.
- * 'desc' - A terse description of this resource.
- * 'view_url' - The URL to view this resource.
- * 'app' - The Horde application this resource belongs to.
- * </pre>
- *
- * The 'raw' results array can be returned instead by setting $raw = true.
- *
- * @param array $names An array of tag_names to search for.
- * @param integer $max The maximum number of stories to return.
- * @param integer $from The number of the story to start with.
- * @param string $resource_type An array of channel_ids to limit the search to.
- * @param string $user Restrict results to resources owned by $user.
- * @param boolean $raw Return the raw story data?
- * @param string $app Application scope to use, if not the default.
- *
- * @return mixed An array of results | PEAR_Error
- */
-function _ansel_searchTags($names, $max = 10, $from = 0,
- $resource_type = 'all', $user = null, $raw = false,
- $app = null)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- if (!is_null($app)) {
- $GLOBALS['ansel_storage'] = new Ansel_Storage($app);
- } else {
- $app = 'ansel';
- }
-
- $results = Ansel_Tags::searchTags($names, $max, $from, $resource_type,
- $user);
-
- /* Check for error or if we requested the raw data array */
- if (is_a($results, 'PEAR_Error') || $raw) {
- return $results;
- }
-
- $return = array();
- if (!empty($results['images'])) {
- foreach ($results['images'] as $image_id) {
- $image = $GLOBALS['ansel_storage']->getImage($image_id);
- $desc = $image->caption;
- $title = $image->filename;
- $view_url = Ansel::getUrlFor('view',
- array('gallery' => $image->gallery,
- 'image' => $image_id,
- 'view' => 'Image'),
- true);
- $return[] = array('title' => $image->filename,
- 'desc'=> $image->caption,
- 'view_url' => $view_url,
- 'app' => $app);
- }
-
- }
-
- if (!empty($results['galleries'])) {
- foreach ($results['galleries'] as $gallery) {
- $gal = $GLOBALS['ansel_storage']->getGallery($gallery);
- $view_url = Horde_Util::addParameter(Horde::applicationUrl('view.php'), array('gallery' => $gallery,
- 'view' => 'Gallery'));
- $return[] = array('title' => $gal->get('name'),
- 'desc' => $gal->get('desc'),
- 'view_url' => $view_url,
- 'app' => $app);
-
- }
- }
-
-
- return $return;
-}
-
-/**
- * Checks if the gallery exists
- *
- * @param string $app Application scope to use, if not the default.
- * @param integer $gallery_id The gallery id
- * @param string $slug The gallery slug
- *
- * @return boolean
- */
-function _ansel_galleryExists($app, $gallery_id = null, $slug = '')
-{
- require_once dirname(__FILE__) . '/base.php';
-
- if (!is_null($app)) {
- $GLOBALS['ansel_storage'] = new Ansel_Storage($app);
- }
-
- return $GLOBALS['ansel_storage']->galleryExists($gallery_id, $slug);
-}
-
-/**
- * Get a list of all configured styles.
- *
- * @return hash of style definitions.
- */
-function _ansel_getGalleryStyles()
-{
- require_once dirname(__FILE__) . '/base.php';
-
- return Ansel::getAvailableStyles();
-}
-
-/**
- * Renders a gallery view
- *
- * @param array $params Any parameters that the view might need.
- * @see Ansel_View_* classes for descriptions of
- * available parameters to use here.
- * @param string $app Application scope to use, if not the default.
- * @param string $view The generic type of view we want.
- * (Gallery, Image, List, Embedded)
- *
- * @return array An array containing 'html' and 'crumbs' keys.
- */
-function _ansel_renderView($params = array(), $app = null,
- $view = 'Gallery')
-{
- require_once dirname(__FILE__) . '/base.php';
-
- if (!is_null($app)) {
- $GLOBALS['ansel_storage'] = new Ansel_Storage($app);
- }
- $classname = 'Ansel_View_' . basename($view);
- $params['api'] = true;
- $params['view'] = $view;
- $trail = array();
- $return = array();
- try {
- $view = new $classname($params);
- } catch (Horde_Exception $e) {
- $return['html'] = $e->getMessage();
- $return['crumbs'] = array();
- return $return;
- }
- $return['html'] = $view->html();
- if ($params['view'] == 'Gallery' || $params['view'] == 'Image') {
- $trail = $view->getGalleryCrumbData();
- }
- $return['crumbs'] = $trail;
-
- return $return;
-
-}
+++ /dev/null
-<?php define('ANSEL_VERSION', '2.0-git') ?>
$ansel_session_control = 'readonly';
require_once dirname(__FILE__) . '/lib/base.php';
-require_once ANSEL_BASE . '/lib/version.php';
// Get form data
$stream_type = Horde_Util::getFormData('stream_type', 'all');
$image_url = htmlspecialchars($params['image_url']);
$image_link = htmlspecialchars($params['image_link']);
$image_alt = htmlspecialchars($params['image_alt']);
- $ansel = 'Ansel ' . ANSEL_VERSION . ' (http://www.horde.org/)';
+ $ansel = 'Ansel ' . $registry->getVersion('ansel') . ' (http://www.horde.org/)';
if ($stream_type != 'all' && $type != 'rss2') {
$getparams = array('stream_type' => $stream_type, 'type' => $type);
/* Ansel version. */
$module = 'Ansel';
-require_once ANSEL_BASE . '/lib/version.php';
-$module_version = ANSEL_VERSION;
+require_once dirname(__FILE__) . '/lib/Api.php';
+$api = new Ansel_Api();
+$module_version = $api->version;
/* Ansel configuration files. */
$file_list = array(
--- /dev/null
+<?php
+/**
+ * Babel external API interface.
+ *
+ * This file defines Babel's external API interface. Other applications can
+ * interact with Babel through this API.
+ *
+ * Copyright 2009 The Horde Project (http://www.horde.org/)
+ *
+ * @author Joel Vandal <joel@scopserv.com>
+ * @package Babel
+ */
+class Babel_Api extends Horde_Registry_Api
+{
+ public function perms()
+ {
+ global $registry;
+
+ static $perms = array();
+ if (!empty($perms)) {
+ return $perms;
+ }
+
+ $perms['tree']['babel']['language'] = array();
+ $perms['title']['babel:language'] = _("Languages");
+ $perms['type']['babel:language'] = 'none';
+
+ foreach(Horde_Nls::$config['languages'] as $langcode => $langdesc) {
+ $perms['tree']['babel']['language'][$langcode] = false;
+ $perms['title']['babel:language:' . $langcode] = sprintf("%s (%s)", $langdesc, $langcode);
+ $perms['type']['babel:language:' . $langcode] = 'boolean';
+ }
+
+ $perms['tree']['babel']['module'] = array();
+ $perms['title']['babel:module'] = _("Modules");
+ $perms['type']['babel:module'] = 'none';
+
+ foreach ($registry->applications as $app => $params) {
+ if ($params['status'] == 'heading' || $params['status'] == 'block') {
+ continue;
+ }
+
+ if (isset($params['fileroot']) && !is_dir($params['fileroot'])) {
+ continue;
+ }
+
+ if (preg_match('/_reports$/', $app) || preg_match('/_tools$/', $app)) {
+ continue;
+ }
+
+ $perms['tree']['babel']['module'][$app] = false;
+ $perms['title']['babel:module:' . $app] = sprintf("%s (%s)", $params['name'], $app);
+ $perms['type']['babel:module:' . $app] = 'boolean';
+ }
+
+ $tabdesc['download'] = _("Download");
+ $tabdesc['upload'] = _("Upload");
+ $tabdesc['stats'] = _("Statistics");
+ $tabdesc['view'] = _("View/Edit");
+ $tabdesc['viewsource'] = _("View Source");
+ $tabdesc['extract'] = _("Extract");
+ $tabdesc['make'] = _("Make");
+ $tabdesc['commit'] = _("Commit");
+ $tabdesc['reset'] = _("Reset");
+
+ foreach ($tabdesc as $cat => $desc) {
+ $perms['tree']['babel'][$cat] = array();
+ $perms['title']['babel:' . $cat] = $desc;
+ }
+
+ return $perms;
+ }
+
+}
+++ /dev/null
-<?php
-/**
- * Babel external API interface.
- *
- * This file defines Babel's external API interface. Other applications can
- * interact with Babel through this API.
- *
- * Copyright 2009 The Horde Project (http://www.horde.org/)
- *
- * @author Joel Vandal <joel@scopserv.com>
- * @package Babel
- */
-
-$_services['perms'] = array(
- 'args' => array(),
- // This is actually a hash of hashes
- 'type' => '{urn:horde}hash'
-);
-
-function _babel_perms()
-{
-
- global $registry;
-
- static $perms = array();
- if (!empty($perms)) {
- return $perms;
- }
-
- $perms['tree']['babel']['language'] = array();
- $perms['title']['babel:language'] = _("Languages");
- $perms['type']['babel:language'] = 'none';
-
- foreach(Horde_Nls::$config['languages'] as $langcode => $langdesc) {
- $perms['tree']['babel']['language'][$langcode] = false;
- $perms['title']['babel:language:' . $langcode] = sprintf("%s (%s)", $langdesc, $langcode);
- $perms['type']['babel:language:' . $langcode] = 'boolean';
- }
-
- $perms['tree']['babel']['module'] = array();
- $perms['title']['babel:module'] = _("Modules");
- $perms['type']['babel:module'] = 'none';
-
- foreach ($registry->applications as $app => $params) {
- if ($params['status'] == 'heading' || $params['status'] == 'block') {
- continue;
- }
-
- if (isset($params['fileroot']) && !is_dir($params['fileroot'])) {
- continue;
- }
-
- if (preg_match('/_reports$/', $app) || preg_match('/_tools$/', $app)) {
- continue;
- }
-
- $perms['tree']['babel']['module'][$app] = false;
- $perms['title']['babel:module:' . $app] = sprintf("%s (%s)", $params['name'], $app);
- $perms['type']['babel:module:' . $app] = 'boolean';
- }
-
- $tabdesc['download'] = _("Download");
- $tabdesc['upload'] = _("Upload");
- $tabdesc['stats'] = _("Statistics");
- $tabdesc['view'] = _("View/Edit");
- $tabdesc['viewsource'] = _("View Source");
- $tabdesc['extract'] = _("Extract");
- $tabdesc['make'] = _("Make");
- $tabdesc['commit'] = _("Commit");
- $tabdesc['reset'] = _("Reset");
-
- foreach ($tabdesc as $cat => $desc) {
- $perms['tree']['babel'][$cat] = array();
- $perms['title']['babel:' . $cat] = $desc;
- }
-
- return $perms;
-
-}
--- /dev/null
+<?php
+/**
+ * Chora external API interface.
+ *
+ * This file defines Chora's external API interface. Other applications can
+ * interact with Chora through this API.
+ *
+ * @package Chora
+ */
+class Chora_Api extends Horde_Registry_Api
+{
+ /**
+ * The application's version.
+ *
+ * @var string
+ */
+ public $version = 'H4 (3.0-git)';
+
+ public function perms()
+ {
+ static $perms = array();
+
+ if (!empty($perms)) {
+ return $perms;
+ }
+
+ require_once dirname(__FILE__) . '/../config/sourceroots.php';
+
+ $perms['tree']['chora']['sourceroots'] = false;
+ $perms['title']['chora:sourceroots'] = _("Repositories");
+
+ // Run through every source repository
+ foreach ($sourceroots as $sourceroot => $srconfig) {
+ $perms['tree']['chora']['sourceroots'][$sourceroot] = false;
+ $perms['title']['chora:sourceroots:' . $sourceroot] = $srconfig['name'];
+ }
+
+ return $perms;
+ }
+
+}
+++ /dev/null
-<?php
-/**
- * Chora external API interface.
- *
- * This file defines Chora's external API interface. Other applications can
- * interact with Chora through this API.
- *
- * @package Chora
- */
-@define('CHORA_BASE', dirname(__FILE__) . "/..");
-
-$_services['perms'] = array(
- 'args' => array(),
- 'type' => '{urn:horde}stringArray'
-);
-
-function _chora_perms()
-{
- static $perms = array();
-
- if (!empty($perms)) {
- return $perms;
- }
-
- @define('CHORA_BASE', dirname(__FILE__) . '/..');
- require_once CHORA_BASE . '/config/sourceroots.php';
-
- $perms['tree']['chora']['sourceroots'] = false;
- $perms['title']['chora:sourceroots'] = _("Repositories");
-
- // Run through every source repository
- foreach ($sourceroots as $sourceroot => $srconfig) {
- $perms['tree']['chora']['sourceroots'][$sourceroot] = false;
- $perms['title']['chora:sourceroots:' . $sourceroot] = $srconfig['name'];
- }
-
- return $perms;
-}
-
+++ /dev/null
-<?php define('CHORA_VERSION', 'H4 (3.0-git)') ?>
/* Chora version. */
$module = 'Chora';
-require_once CHORA_BASE . '/lib/version.php';
-$module_version = CHORA_VERSION;
+require_once dirname(__FILE__) . '/lib/Api.php';
+$api = new Chora_Api();
+$module_version = $api->version;
/* Chora configuration files. */
$file_list = array(
--- /dev/null
+<?php
+class Crumb_Api extends Horde_Registry_Api
+{
+ public $version = 'H4 (0.1-git)';
+}
+++ /dev/null
-<?php define('CRUMB_VERSION', 'H4 (0.1-git)') ?>
--- /dev/null
+<?php
+class Fima_Api extends Horde_Regsitry_Api
+{
+ public $version = '1.0.1';
+}
+++ /dev/null
-<?php define('FIMA_VERSION', '1.0.1') ?>
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * Folks api
+ *
+ * Copyright 2008 Obala d.o.o (www.obala.si)
+ *
+ * See the enclosed file LICENSE for license information (BSD). If you
+ * did not receive this file, see http://cvs.horde.org/co.php/folks/LICENSE.
+ *
+ * $Id: api.php 1235 2009-01-28 19:25:04Z duck $
+ *
+ * @author Duck <duck@obala.net>
+ * @package Folks
+ */
+class Folks_Api extends Horde_Registry_Api
+{
+ public $version = 'H4 (0.1-git)';
+
+ public $services = array(
+ 'commentCallback' => array(
+ 'args' => array('id' => 'string'),
+ 'type' => 'string'
+ ),
+
+ 'removeUserData' => array(
+ 'args' => array('user' => 'string'),
+ 'type' => 'boolean'
+ ),
+
+ 'hasComments' => array(
+ 'args' => array(),
+ 'type' => 'boolean'
+ ),
+
+ 'getOnlineUsers' => array(
+ 'args' => array(),
+ 'type' => 'array'
+ ),
+
+ 'getProfile' => array(
+ 'args' => array(),
+ 'type' => 'array'
+ ),
+
+ 'getFriends' => array(
+ 'args' => array('user' => 'string'),
+ 'type' => 'array'
+ ),
+
+ 'addFriend' => array(
+ 'args' => array('friend' => 'string'),
+ 'type' => 'boolean'
+ ),
+
+ 'getBlacklist' => array(
+ 'args' => array('user' => 'string'),
+ 'type' => 'array'
+ ),
+
+ 'addBlacklisted' => array(
+ 'args' => array('user' => 'string'),
+ 'type' => 'boolean'
+ ),
+
+ 'removeBlacklisted' => array(
+ 'args' => array('user' => 'string'),
+ 'type' => 'boolean'
+ ),
+
+ 'isBlacklisted' => array(
+ 'args' => array('user' => 'string'),
+ 'type' => 'boolean'
+ ),
+
+ 'show' => array(
+ 'link' => '%application%/user.php?user=|user|'
+ ),
+
+ 'listTimeObjectCategories' => array(
+ 'type' => '{urn:horde}stringArray'
+ ),
+
+ 'listTimeObjects' => array(
+ 'args' => array('start' => 'int', 'end' => 'int'),
+ 'type' => '{urn:horde}hashHash'
+ ),
+
+ 'logActivity' => array(
+ 'args' => array('activity_message' => 'string',
+ 'scope' => 'string',
+ 'user' => 'string'),
+ 'type' => 'boolean'
+ ),
+
+ 'getActivity' => array(
+ 'args' => array('user' => 'string'),
+ 'type' => 'boolean'
+ ),
+
+ 'setStatus' => array(
+ 'args' => array('status' => 'boolean'),
+ 'args' => array('user' => 'string'),
+ 'type' => 'array'
+ ),
+
+ 'getStatus' => array(
+ 'args' => array('user' => 'string'),
+ 'type' => 'array'
+ ),
+
+ 'authenticate' => array(
+ 'args' => array('userID' => 'string',
+ 'credentials' => '{urn:horde}hash',
+ 'params' => '{urn:horde}hash'),
+ 'checkperms' => false,
+ 'type' => 'boolean'
+ ),
+
+ 'userExists' => array(
+ 'args' => array('userId' => 'string'),
+ 'type' => 'boolean'
+ ),
+
+ 'addUser' => array(
+ 'args' => array('userId' => 'string')
+ ),
+
+ 'getImageUrl' => array(
+ 'args' => array(
+ 'user' => 'string',
+ 'view' => 'string',
+ 'full' => 'boolean'
+ ),
+ 'type' => 'string'
+ ),
+
+ 'userList' => array(
+ 'type' => '{urn:horde}stringArray'
+ ),
+
+ 'removeUser' => array(
+ 'args' => array('userId' => 'string')
+ )
+ );
+
+ public function __construct()
+ {
+ if (!Horde_Auth::isAdmin()) {
+ unset($this->services['userList'], $this->services['removeUser']);
+ }
+ }
+
+ /**
+ * Returns profile image URL.
+ *
+ * @param string $user User uid
+ * @param string $view The view ('small', 'big') to show.
+ * @param boolean $full Return a path that includes the server name?
+ *
+ * @return string The image path.
+ */
+ public function getImageUrl($user, $view = 'small', $full = false)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+ return Folks::getImageUrl($user, $view, $full);
+ }
+
+ /**
+ * Callback for comment API.
+ *
+ * @param int $id Internal data identifier.
+ * @param string $type Type of data to retreive (title, owner...).
+ * @param array $params Parameter to be passed to callback function
+ */
+ public function commentCallback($id, $type = 'title', $params = null)
+ {
+ static $info;
+
+ if (!empty($info[$id][$type])) {
+ return $info[$id][$type];
+ }
+
+ require_once dirname(__FILE__) . '/base.php';
+
+ switch ($type) {
+
+ case 'owner':
+ return $id;
+
+ case 'link':
+ return Folks::getUrlFor('user', $id);
+
+ case 'messages':
+
+ // Update comments count
+ $result = $GLOBALS['folks_driver']->updateComments($id);
+ if ($result instanceof PEAR_Error) {
+ return $result;
+ }
+
+ // Update activity log
+ $link = '<a href="' . Folks::getUrlFor('user', $id) . '">' . $id . '</a>';
+ return $GLOBALS['folks_driver']->logActivity(sprintf(_("Commented user %s."), $link), 'folks:comments');
+
+ return true;
+
+ default:
+ return $id;
+ }
+ }
+
+ /**
+ * Comments are enebled
+ */
+ public function hasComments()
+ {
+ return $GLOBALS['conf']['comments']['allow'];
+ }
+
+ /**
+ * Get online users
+ */
+ public function getOnlineUsers()
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ return $GLOBALS['folks_driver']->getOnlineUsers();
+ }
+
+ /**
+ * Get user profile
+ *
+ * @param string $user User to get profile for
+ */
+ public function getProfile($user = null)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ return $GLOBALS['folks_driver']->getProfile($user);
+ }
+
+
+ /**
+ * Get user friends
+ *
+ * @param string $user Username to get friends for
+ *
+ * @return array of users
+ */
+ public function getFriends($user = null)
+ {
+ require_once dirname(__FILE__) . '/Friends.php';
+
+ $friends = Folks_Friends::singleton('sql', array('user' => $user));
+
+ return $friends->getFriends();
+ }
+
+ /**
+ * Add user to our friend list
+ *
+ * @param string $friend Friend's usersame
+ *
+ * @return true or PEAR_Error
+ */
+ public function addFriend($user = null)
+ {
+ require_once dirname(__FILE__) . '/Friends.php';
+
+ $friends = Folks_Friends::singleton('sql', array('user' => $user));
+
+ return $friends->addFriend($user);
+ }
+
+ /**
+ * Remove user from a fiend list
+ *
+ * @param string $friend Friend's usersame
+ *
+ * @return true or PEAR_Error
+ */
+ public function removeFriend($user = null)
+ {
+ require_once dirname(__FILE__) . '/Friends.php';
+
+ $friends = Folks_Friends::singleton('sql', array('user' => $user));
+
+ return $friends->removeFriend($user);
+ }
+
+ /**
+ * Get user blacklist
+ *
+ * @param string $user Username to get blacklist for
+ *
+ * @return array of users
+ */
+ public function _getBlacklist($user = null)
+ {
+ require_once dirname(__FILE__) . '/Friends.php';
+
+ $friends = Folks_Friends::singleton('sql', array('user' => $user));
+
+ return $friends->getBlacklist();
+ }
+
+ /**
+ * Add user to a blacklist list
+ *
+ * @param string $user Usersame
+ */
+ public function addBlacklisted($user = null)
+ {
+ require_once dirname(__FILE__) . '/Friends.php';
+
+ $friends = Folks_Friends::singleton('sql', array('user' => $user));
+
+ return $friends->addBlacklisted($user);
+ }
+
+ /**
+ * Remove user from a blacklist list
+ *
+ * @param string $user Usersame
+ */
+ public function removeBlacklisted($user = null)
+ {
+ require_once dirname(__FILE__) . '/Friends.php';
+
+ $friends = Folks_Friends::singleton('sql', array('user' => $user));
+
+ return $friends->removeBlacklisted($user);
+ }
+
+ /**
+ * Are we blackisted by user this user?
+ *
+ * @param string $user Username to get blacklist for
+ *
+ * @return array of users
+ */
+ public function isBlacklisted($user = null)
+ {
+ require_once dirname(__FILE__) . '/Friends.php';
+
+ $friends = Folks_Friends::singleton('sql', array('user' => $user));
+
+ return $friends->isBlacklisted(Horde_Auth::getAuth());
+ }
+
+ /**
+ * Users categories
+ */
+ public function listTimeObjectCategories()
+ {
+ return array('birthday_friends' => _("Friends Birthdays"));
+ }
+
+ /**
+ * Lists users with birthdays/goout dates as time objects.
+ *
+ * @param array $categories The time categories (from listTimeObjectCategories) to list.
+ * @param Horde_Date $start The start date of the period.
+ * @param Horde_Date $end The end date of the period.
+ */
+ public function listTimeObjects($categories, $start, $end)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+ require_once FOLKS_BASE . '/lib/Friends.php';
+
+ $friends_driver = Folks_Friends::singleton('sql');
+ $friends = $friends_driver->getFriends();
+ if ($friends instanceof PEAR_Error) {
+ return array();
+ }
+
+ $objects = array();
+
+ foreach ($friends as $friend) {
+ $user = $GLOBALS['folks_driver']->getProfile($friend);
+ if ($user instanceof PEAR_Error) {
+ continue;
+ }
+
+ $user['user_birthday'] = date('Y') . substr($user['user_birthday'], 4);
+ $born = strtotime($user['user_birthday']);
+ if ($born === false ||
+ $born < $start->timestamp() ||
+ $born > $end->timestamp()) {
+ continue;
+ }
+
+ $age = Folks::calcAge($user['user_birthday']);
+ $desc = $age['age'] . ' (' . $age['sign'] . ')';
+
+ $objects[$friend] = array(
+ 'title' => $friend,
+ 'description' => $desc,
+ 'id' => $friend,
+ 'start' => date('Y-m-d\TH:i:s', $born),
+ 'end' => date('Y-m-d\TH:i:s', $born + 1),
+ 'params' => array('user' => $friend),
+ 'link' => Folks::getUrlFor('user', $friend, true));
+ }
+
+ return $objects;
+ }
+
+ /**
+ * Log user's activity
+ *
+ * @param mixed $message Activity message or details
+ * @param string $scope Scope
+ * @param string $user $user
+ *
+ * @return boolean True on success or a PEAR_Error object on failure.
+ */
+ public function logActivity($message, $scope = 'folks', $user = null)
+ {
+ if (empty($user)) {
+ $user = Horde_Auth::getAuth();
+ } elseif ($user !== Horde_Auth::getAuth() && !Horde_Auth::isAdmin('admin:' . $scope)) {
+ return PEAR::raiseError(_("You cannot log activities for other users."));
+ }
+
+ require_once dirname(__FILE__) . '/base.php';
+
+ return $GLOBALS['folks_driver']->logActivity($message, $scope, $user);
+ }
+
+ /**
+ * Get user's activity
+ *
+ * @param string $user Username
+ * @param int $limit Number of actions to return
+ *
+ * @return array Activity log
+ */
+ public function getActivity($user, $limit = 10)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ return $GLOBALS['folks_driver']->getActivity($user, $limit);
+ }
+
+ /**
+ * Set user status
+ *
+ * @param booelan $online True to set user online, false to push it offline
+ * @param string $user Username
+ *
+ * @return boolean True if succes, PEAR_Error on failure
+ */
+ public function setStatus($online = true, $user = null)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ if ($user == null) {
+ $user = Horde_Auth::getAuth();
+ }
+
+ if ($online) {
+ return $GLOBALS['folks_driver']->resetOnlineUsers();
+ } else {
+ $result = $GLOBALS['folks_driver']->deleteOnlineUser($user);
+ $GLOBALS['cache']->expire('folksOnlineUsers');
+ return $result;
+ }
+ }
+
+ /**
+ * Get user status
+ *
+ * @param string $user Username
+ *
+ * @return boolean True if user is online, false otherwise
+ */
+ public function getStatus($user = null)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ if ($user == null) {
+ $user = Horde_Auth::getAuth();
+ }
+
+ return $GLOBALS['folks_driver']->isOnline($user);
+ }
+
+ /**
+ * Authenticate a givern user
+ *
+ * @param string $userID Username
+ * @param array $credentials Array of criedentials (password requied)
+ * @param array $params Additional params
+ *
+ * @return boolean Whether IMP authentication was successful.
+ */
+ public function authenticate($userID, $credentials, $params)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ return $GLOBALS['folks_driver']->comparePassword($userID, $credentials['password']);
+ }
+
+ /**
+ * Check if a user exists
+ *
+ * @param string $userID Username
+ *
+ * @return boolean True if user exists
+ */
+ public function userExists($userId)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ return $GLOBALS['folks_driver']->userExists($userId);
+ }
+
+ /**
+ * Lists all users in the system.
+ *
+ * @return array The array of userIds, or a PEAR_Error object on failure.
+ */
+ public function userList()
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ $users = array();
+ foreach ($GLOBALS['folks_driver']->getUsers() as $user) {
+ $users[] = $user['user_uid'];
+ }
+
+ return $users;
+ }
+
+ /**
+ * Adds a set of authentication credentials.
+ *
+ * @param string $userId The userId to add.
+ *
+ * @return boolean True on success or a PEAR_Error object on failure.
+ */
+ public function addUser($userId)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ return $GLOBALS['folks_driver']->addUser($userId);
+ }
+
+ /**
+ * Deletes a set of authentication credentials.
+ *
+ * @param string $userId The userId to delete.
+ *
+ * @return boolean True on success or a PEAR_Error object on failure.
+ */
+ public function removeUser($userId)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ return $GLOBALS['folks_driver']->deleteUser($userId);
+ }
+
+ /**
+ * Deletes a user and its data
+ *
+ * @param string $userId The userId to delete.
+ *
+ * @return boolean True on success or a PEAR_Error object on failure.
+ */
+ public function removeUserData($user = null)
+ {
+ return $this->removeUser($user);
+ }
+
+}
+++ /dev/null
-<?php
-/**
- * Folks api
- *
- * Copyright 2008 Obala d.o.o (www.obala.si)
- *
- * See the enclosed file LICENSE for license information (BSD). If you
- * did not receive this file, see http://cvs.horde.org/co.php/folks/LICENSE.
- *
- * $Id: api.php 1235 2009-01-28 19:25:04Z duck $
- *
- * @author Duck <duck@obala.net>
- * @package Folks
- */
-
-$_services['commentCallback'] = array(
- 'args' => array('id' => 'string'),
- 'type' => 'string'
-);
-
-$_services['removeUserData'] = array(
- 'args' => array('user' => 'string'),
- 'type' => 'boolean'
-);
-
-$_services['hasComments'] = array(
- 'args' => array(),
- 'type' => 'boolean'
-);
-
-$_services['getOnlineUsers'] = array(
- 'args' => array(),
- 'type' => 'array'
-);
-
-$_services['getProfile'] = array(
- 'args' => array(),
- 'type' => 'array'
-);
-
-$_services['getFriends'] = array(
- 'args' => array('user' => 'string'),
- 'type' => 'array'
-);
-
-$_services['addFriend'] = array(
- 'args' => array('friend' => 'string'),
- 'type' => 'boolean'
-);
-
-$_services['getBlacklist'] = array(
- 'args' => array('user' => 'string'),
- 'type' => 'array'
-);
-
-$_services['addBlacklisted'] = array(
- 'args' => array('user' => 'string'),
- 'type' => 'boolean'
-);
-
-$_services['removeBlacklisted'] = array(
- 'args' => array('user' => 'string'),
- 'type' => 'boolean'
-);
-
-$_services['isBlacklisted'] = array(
- 'args' => array('user' => 'string'),
- 'type' => 'boolean'
-);
-
-$_services['show'] = array(
- 'link' => '%application%/user.php?user=|user|'
-);
-
-$_services['listTimeObjectCategories'] = array(
- 'type' => '{urn:horde}stringArray'
-);
-
-$_services['listTimeObjects'] = array(
- 'args' => array('start' => 'int', 'end' => 'int'),
- 'type' => '{urn:horde}hashHash'
-);
-
-$_services['logActivity'] = array(
- 'args' => array('activity_message' => 'string',
- 'scope' => 'string',
- 'user' => 'string'),
- 'type' => 'boolean'
-);
-
-$_services['getActivity'] = array(
- 'args' => array('user' => 'string'),
- 'type' => 'boolean'
-);
-
-$_services['setStatus'] = array(
- 'args' => array('status' => 'boolean'),
- 'args' => array('user' => 'string'),
- 'type' => 'array'
-);
-
-$_services['getStatus'] = array(
- 'args' => array('user' => 'string'),
- 'type' => 'array'
-);
-
-$_services['authenticate'] = array(
- 'args' => array('userID' => 'string',
- 'credentials' => '{urn:horde}hash',
- 'params' => '{urn:horde}hash'),
- 'checkperms' => false,
- 'type' => 'boolean'
-);
-
-$_services['userExists'] = array(
- 'args' => array('userId' => 'string'),
- 'type' => 'boolean'
-);
-
-$_services['addUser'] = array(
- 'args' => array('userId' => 'string')
-);
-
-$_services['getImageUrl'] = array(
- 'args' => array('user' => 'string',
- 'view' => 'string',
- 'full' => 'boolean'),
- 'type' => 'string'
-);
-
-if (Horde_Auth::isAdmin()) {
- $_services['userList'] = array(
- 'type' => '{urn:horde}stringArray'
- );
-
- $_services['removeUser'] = array(
- 'args' => array('userId' => 'string')
- );
-}
-
-/**
- * Returns profile image URL.
- *
- * @param string $user User uid
- * @param string $view The view ('small', 'big') to show.
- * @param boolean $full Return a path that includes the server name?
- *
- * @return string The image path.
- */
-function _folks_getImageUrl($user, $view = 'small', $full = false)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- return Folks::getImageUrl($user, $view, $full);
-}
-
-/**
- * Callback for comment API.
- *
- * @param int $id Internal data identifier.
- * @param string $type Type of data to retreive (title, owner...).
- * @param array $params Parameter to be passed to callback function
- */
-function _folks_commentCallback($id, $type = 'title', $params = null)
-{
- static $info;
-
- if (!empty($info[$id][$type])) {
- return $info[$id][$type];
- }
-
- require_once dirname(__FILE__) . '/base.php';
-
- switch ($type) {
-
- case 'owner':
- return $id;
-
- case 'link':
- return Folks::getUrlFor('user', $id);
-
- case 'messages':
-
- // Update comments count
- $result = $GLOBALS['folks_driver']->updateComments($id);
- if ($result instanceof PEAR_Error) {
- return $result;
- }
-
- // Update activity log
- $link = '<a href="' . Folks::getUrlFor('user', $id) . '">' . $id . '</a>';
- return $GLOBALS['folks_driver']->logActivity(sprintf(_("Commented user %s."), $link), 'folks:comments');
-
- return true;
-
- default:
- return $id;
- }
-}
-
-/**
- * Comments are enebled
- */
-function _folks_hasComments()
-{
- return $GLOBALS['conf']['comments']['allow'];
-}
-
-/**
- * Get online users
- */
-function _folks_getOnlineUsers()
-{
- require_once dirname(__FILE__) . '/base.php';
-
- return $GLOBALS['folks_driver']->getOnlineUsers();
-}
-
-/**
- * Get user profile
- *
- * @param string $user User to get profile for
- */
-function _folks_getProfile($user = null)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- return $GLOBALS['folks_driver']->getProfile($user);
-}
-
-
-/**
- * Get user friends
- *
- * @param string $user Username to get friends for
- *
- * @return array of users
- */
-function _folks_getFriends($user = null)
-{
- require_once dirname(__FILE__) . '/Friends.php';
-
- $friends = Folks_Friends::singleton('sql', array('user' => $user));
-
- return $friends->getFriends();
-}
-
-/**
- * Add user to our friend list
- *
- * @param string $friend Friend's usersame
- *
- * @return true or PEAR_Error
- */
-function _folks_addFriend($user = null)
-{
- require_once dirname(__FILE__) . '/Friends.php';
-
- $friends = Folks_Friends::singleton('sql', array('user' => $user));
-
- return $friends->addFriend($user);
-}
-
-/**
- * Remove user from a fiend list
- *
- * @param string $friend Friend's usersame
- *
- * @return true or PEAR_Error
- */
-function _folks_removeFriend($user = null)
-{
- require_once dirname(__FILE__) . '/Friends.php';
-
- $friends = Folks_Friends::singleton('sql', array('user' => $user));
-
- return $friends->removeFriend($user);
-}
-
-/**
- * Get user blacklist
- *
- * @param string $user Username to get blacklist for
- *
- * @return array of users
- */
-function _folks_getBlacklist($user = null)
-{
- require_once dirname(__FILE__) . '/Friends.php';
-
- $friends = Folks_Friends::singleton('sql', array('user' => $user));
-
- return $friends->getBlacklist();
-}
-
-/**
- * Add user to a blacklist list
- *
- * @param string $user Usersame
- */
-function _folks_addBlacklisted($user = null)
-{
- require_once dirname(__FILE__) . '/Friends.php';
-
- $friends = Folks_Friends::singleton('sql', array('user' => $user));
-
- return $friends->addBlacklisted($user);
-}
-
-/**
- * Remove user from a blacklist list
- *
- * @param string $user Usersame
- */
-function _folks_removeBlacklisted($user = null)
-{
- require_once dirname(__FILE__) . '/Friends.php';
-
- $friends = Folks_Friends::singleton('sql', array('user' => $user));
-
- return $friends->removeBlacklisted($user);
-}
-
-/**
- * Are we blackisted by user this user?
- *
- * @param string $user Username to get blacklist for
- *
- * @return array of users
- */
-function _folks_isBlacklisted($user = null)
-{
- require_once dirname(__FILE__) . '/Friends.php';
-
- $friends = Folks_Friends::singleton('sql', array('user' => $user));
-
- return $friends->isBlacklisted(Horde_Auth::getAuth());
-}
-
-/**
- * Users categories
- */
-function _folks_listTimeObjectCategories()
-{
- return array('birthday_friends' => _("Friends Birthdays"));
-}
-
-/**
- * Lists users with birthdays/goout dates as time objects.
- *
- * @param array $categories The time categories (from listTimeObjectCategories) to list.
- * @param Horde_Date $start The start date of the period.
- * @param Horde_Date $end The end date of the period.
- */
-function _folks_listTimeObjects($categories, $start, $end)
-{
- require_once dirname(__FILE__) . '/base.php';
- require_once FOLKS_BASE . '/lib/Friends.php';
-
- $friends_driver = Folks_Friends::singleton('sql');
- $friends = $friends_driver->getFriends();
- if ($friends instanceof PEAR_Error) {
- return array();
- }
-
- $objects = array();
-
- foreach ($friends as $friend) {
- $user = $GLOBALS['folks_driver']->getProfile($friend);
- if ($user instanceof PEAR_Error) {
- continue;
- }
-
- $user['user_birthday'] = date('Y') . substr($user['user_birthday'], 4);
- $born = strtotime($user['user_birthday']);
- if ($born === false ||
- $born < $start->timestamp() ||
- $born > $end->timestamp()) {
- continue;
- }
-
- $age = Folks::calcAge($user['user_birthday']);
- $desc = $age['age'] . ' (' . $age['sign'] . ')';
-
- $objects[$friend] = array(
- 'title' => $friend,
- 'description' => $desc,
- 'id' => $friend,
- 'start' => date('Y-m-d\TH:i:s', $born),
- 'end' => date('Y-m-d\TH:i:s', $born + 1),
- 'params' => array('user' => $friend),
- 'link' => Folks::getUrlFor('user', $friend, true));
- }
-
- return $objects;
-}
-
-/**
- * Log user's activity
- *
- * @param mixed $message Activity message or details
- * @param string $scope Scope
- * @param string $user $user
- *
- * @return boolean True on success or a PEAR_Error object on failure.
- */
-function _folks_logActivity($message, $scope = 'folks', $user = null)
-{
- if (empty($user)) {
- $user = Horde_Auth::getAuth();
- } elseif ($user !== Horde_Auth::getAuth() && !Horde_Auth::isAdmin('admin:' . $scope)) {
- return PEAR::raiseError(_("You cannot log activities for other users."));
- }
-
- require_once dirname(__FILE__) . '/base.php';
-
- return $GLOBALS['folks_driver']->logActivity($message, $scope, $user);
-}
-
-/**
- * Get user's activity
- *
- * @param string $user Username
- * @param int $limit Number of actions to return
- *
- * @return array Activity log
- */
-function _folks_getActivity($user, $limit = 10)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- return $GLOBALS['folks_driver']->getActivity($user, $limit);
-}
-
-/**
- * Set user status
- *
- * @param booelan $online True to set user online, false to push it offline
- * @param string $user Username
- *
- * @return boolean True if succes, PEAR_Error on failure
- */
-function _folks_setStatus($online = true, $user = null)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- if ($user == null) {
- $user = Horde_Auth::getAuth();
- }
-
- if ($online) {
- return $GLOBALS['folks_driver']->resetOnlineUsers();
- } else {
- $result = $GLOBALS['folks_driver']->deleteOnlineUser($user);
- $GLOBALS['cache']->expire('folksOnlineUsers');
- return $result;
- }
-}
-
-/**
- * Get user status
- *
- * @param string $user Username
- *
- * @return boolean True if user is online, false otherwise
- */
-function _folks_getStatus($user = null)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- if ($user == null) {
- $user = Horde_Auth::getAuth();
- }
-
- return $GLOBALS['folks_driver']->isOnline($user);
-}
-
-/**
- * Authenticate a givern user
- *
- * @param string $userID Username
- * @param array $credentials Array of criedentials (password requied)
- * @param array $params Additional params
- *
- * @return boolean Whether IMP authentication was successful.
- */
-function _folks_authenticate($userID, $credentials, $params)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- return $GLOBALS['folks_driver']->comparePassword($userID, $credentials['password']);
-}
-
-/**
- * Check if a user exists
- *
- * @param string $userID Username
- *
- * @return boolean True if user exists
- */
-function _folks_userExists($userId)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- return $GLOBALS['folks_driver']->userExists($userId);
-}
-
-/**
- * Lists all users in the system.
- *
- * @return array The array of userIds, or a PEAR_Error object on failure.
- */
-function _folks_userList()
-{
- require_once dirname(__FILE__) . '/base.php';
-
- $users = array();
- foreach ($GLOBALS['folks_driver']->getUsers() as $user) {
- $users[] = $user['user_uid'];
- }
-
- return $users;
-}
-
-/**
- * Adds a set of authentication credentials.
- *
- * @param string $userId The userId to add.
- *
- * @return boolean True on success or a PEAR_Error object on failure.
- */
-function _folks_addUser($userId)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- return $GLOBALS['folks_driver']->addUser($userId);
-}
-
-/**
- * Deletes a set of authentication credentials.
- *
- * @param string $userId The userId to delete.
- *
- * @return boolean True on success or a PEAR_Error object on failure.
- */
-function _folks_removeUser($userId)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- return $GLOBALS['folks_driver']->deleteUser($userId);
-}
-
-/**
- * Deletes a user and its data
- *
- * @param string $userId The userId to delete.
- *
- * @return boolean True on success or a PEAR_Error object on failure.
- */
-function _folks_removeUserData($user = null)
-{
- return _folks_removeUser($user);
-}
+++ /dev/null
-<?php define('FOLKS_VERSION', 'H4 (0.1-git)') ?>
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * Gollem external API interface.
+ *
+ * This file defines Gollem's external API interface. Other
+ * applications can interact with Gollem through this API.
+ *
+ * @author Amith Varghese (amith@xalan.com)
+ * @author Michael Slusarz (slusarz@curecanti.org)
+ * @author Ben Klang (bklang@alkaloid.net)
+ * @package Gollem
+ */
+class Gollem_Api extends Horde_Registry_Api
+{
+ /**
+ * The application's version.
+ *
+ * @var string
+ */
+ public $version = 'H4 (5.0-git)';
+
+ /**
+ * The services provided by this application.
+ *
+ * @var array
+ */
+ public $services = array(
+ 'perms' => array(
+ 'args' => array(),
+ 'type' => '{urn:horde}hashHash'
+ ),
+
+ 'browse' => array(
+ 'args' => array('path' => 'string'),
+ 'type' => '{urn:horde}hashHash',
+ ),
+
+ 'put' => array(
+ 'args' => array(
+ 'path' => 'string',
+ 'content' => 'string',
+ 'content_type' => 'string'
+ ),
+ 'type' => 'int',
+ ),
+
+ 'mkcol' => array(
+ 'args' => array('path' => 'string'),
+ 'type' => 'int',
+ ),
+
+ 'move' => array(
+ 'args' => array(
+ 'path' => 'string',
+ 'dest' => 'string'
+ ),
+ 'type' => 'int',
+ ),
+
+ 'path_delete' => array(
+ 'args' => array('path' => 'string'),
+ 'type' => 'int',
+ ),
+
+ 'selectlistLink' => array(
+ 'args' => array(
+ 'link_text' => 'string',
+ 'link_style' => 'string',
+ 'formid' => 'string',
+ 'icon' => 'boolean',
+ 'selectid' => 'string'
+ ),
+ 'type' => 'string'
+ ),
+
+ 'selectlistResults' => array(
+ 'args' => array('selectid' => 'string'),
+ 'type' => 'array'
+ ),
+
+ 'returnFromSelectlist' => array(
+ 'args' => array(
+ 'selectid' => 'string',
+ 'index' => 'string'
+ ),
+ 'type' => 'string'
+ ),
+
+ 'setSelectList' => array(
+ 'args' => array(
+ 'selectid' => 'string',
+ 'files' => 'array'
+ ),
+ 'type' => 'string'
+ ),
+
+ 'getViewLink' => array(
+ 'args' => array(
+ 'dir' => 'string',
+ 'file' => 'string',
+ 'backend' => 'string'
+ ),
+ 'type' => 'string'
+ )
+ );
+
+ /**
+ * Browses through the VFS tree.
+ *
+ * Each VFS backend is listed as a directory at the top level. No modify
+ * operations are allowed outside any VFS area.
+ *
+ * @param string $path The level of the tree to browse.
+ * @param array $properties The item properties to return. Defaults to 'name',
+ * 'icon', and 'browseable'.
+ *
+ * @return array The contents of $path.
+ */
+ public function browse($path = '', $properties = array())
+ {
+ $GLOBALS['gollem_authentication'] = 'none';
+ require_once dirname(__FILE__) . '/base.php';
+ require GOLLEM_BASE . '/config/backends.php';
+ require GOLLEM_BASE . '/config/credentials.php';
+
+ $path = Gollem::stripAPIPath($path);
+
+ // Default properties.
+ if (!$properties) {
+ $properties = array('name', 'icon', 'browseable');
+ }
+
+ $results = array();
+ if ($path == '') {
+ // We are at the root of gollem. Return a set of folders, one for
+ // each backend available.
+ foreach ($backends as $backend => $curBackend) {
+ if (Gollem::checkPermissions('backend', PERMS_SHOW, $backend)) {
+ $results['gollem/' . $backend]['name'] = $curBackend['name'];
+ $results['gollem/' . $backend]['browseable'] = true;
+ }
+ }
+ } else {
+ // A file or directory has been requested.
+
+ // Locate the backend_key in the path.
+ if (strchr($path, '/')) {
+ $backend_key = substr($path, 0, strpos($path, '/'));
+ } else {
+ $backend_key = $path;
+ }
+
+ // Validate and perform permissions checks on the requested backend
+ if (!isset($backends[$backend_key])) {
+ return PEAR::raiseError(sprintf(_("Invalid backend requested: %s"), $backend_key));
+ }
+ //if (!Gollem::canAutoLogin($backend_key)) {
+ // // FIXME: Is it possible to request secondary authentication
+ // // credentials here for backends that require it?
+ // return PEAR::raiseError(_("Additional authentication required."));
+ //}
+ if (!Gollem_Session::createSession($backend_key)) {
+ return PEAR::raiseError(_("Unable to create Gollem session"));
+ }
+ if (!Gollem::checkPermissions('backend', PERMS_READ)) {
+ return PEAR::raiseError(_("Permission denied to this backend."));
+ }
+
+ // Trim off the backend_key (and '/') to get the VFS relative path
+ $fullpath = substr($path, strlen($backend_key) + 1);
+
+ // Get the VFS-standard $name,$path pair
+ list($name, $path) = Gollem::getVFSPath($fullpath);
+
+ // Check to see if the request is a file or folder
+ if ($GLOBALS['gollem_vfs']->isFolder($path, $name)) {
+ // This is a folder request. Return a directory listing.
+ $list = Gollem::listFolder($path . '/' . $name);
+ if (is_a($list, 'PEAR_Error')) {
+ return $list;
+ }
+
+ // Iterate over the directory contents
+ if (is_array($list) && count($list)) {
+ $index = 'gollem/' . $backend_key . '/' . $fullpath;
+ foreach ($list as $key => $val) {
+ $entry = Gollem::pathEncode($index . '/' . $val['name']);
+ $results[$entry]['name'] = $val['name'];
+ $results[$entry]['modified'] = $val['date'];
+ if ($val['type'] == '**dir') {
+ $results[$entry]['browseable'] = true;
+ } else {
+ $results[$entry]['browseable'] = false;
+ $results[$entry]['contentlength'] = $val['size'];
+ }
+ }
+ }
+ } else {
+ // A file has been requested. Return the contents of the file.
+
+ // Get the file meta-data
+ $list = Gollem::listFolder($path);
+ $i = false;
+ foreach ($list as $key => $file) {
+ if ($file['name'] == $name) {
+ $i = $key;
+ break;
+ }
+ }
+ if ($i === false) {
+ // File not found
+ return $i;
+ }
+
+ // Read the file contents
+ $data = $GLOBALS['gollem_vfs']->read($path, $name);
+ if (is_a($data, 'PEAR_Error')) {
+ return false;
+ }
+
+ // Send the file
+ $results['name'] = $name;
+ $results['data'] = $data;
+ $results['contentlength'] = $list[$i]['size'];
+ $results['mtime'] = $list[$i]['date'];
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Accepts a file for storage into the VFS
+ *
+ * @param string $path Path to store file
+ * @param string $content Contents of file
+ * @param string $content_type MIME type of file
+ *
+ * @return mixed True on success; PEAR_Error on failure
+ */
+ public function put($path, $content, $content_type)
+ {
+ // Gollem does not handle authentication
+ $GLOBALS['gollem_authentication'] = 'none';
+
+ // Include Gollem base libraries
+ require_once dirname(__FILE__) . '/base.php';
+ require GOLLEM_BASE . '/config/backends.php';
+ require GOLLEM_BASE . '/config/credentials.php';
+
+ // Clean off the irrelevant portions of the path
+ $path = Gollem::stripAPIPath($path);
+
+ if ($path == '') {
+ // We are at the root of gollem. Any writes at this level are
+ // disallowed.
+ return PEAR::raiseError(_("Files must be written inside a VFS backend."));
+ } else {
+ // We must be inside one of the VFS areas. Determine which one.
+ // Locate the backend_key in the path
+ if (strchr($path, '/')) {
+ $backend_key = substr($path, 0, strpos($path, '/'));
+ } else {
+ $backend_key = $path;
+ }
+
+ // Validate and perform permissions checks on the requested backend
+ if (!isset($backends[$backend_key])) {
+ return PEAR::raiseError(sprintf(_("Invalid backend requested: %s"), $backend_key));
+ }
+ //if (!Gollem::canAutoLogin($backend_key)) {
+ // // FIXME: Is it possible to request secondary authentication
+ // // credentials here for backends that require it?
+ // return PEAR::raiseError(_("Additional authentication required."));
+ //}
+ if (!Gollem_Session::createSession($backend_key)) {
+ return PEAR::raiseError(_("Unable to create Gollem session"));
+ }
+ if (!Gollem::checkPermissions('backend', PERMS_EDIT)) {
+ return PEAR::raiseError(_("Permission denied to this backend."));
+ }
+
+ // Trim off the backend_key (and '/') to get the VFS relative path
+ $fullpath = substr($path, strlen($backend_key) + 1);
+
+ // Get the VFS-standard $name,$path pair
+ list($name, $path) = Gollem::getVFSPath($fullpath);
+
+ return $GLOBALS['gollem_vfs']->writeData($path, $name, $content);
+ }
+ }
+
+ /**
+ * Creates a directory ("collection" in WebDAV-speak) within the VFS
+ *
+ * @param string $path Path of directory to create
+ *
+ * @return mixed True on success; PEAR_Error on failure
+ */
+ public function mkcol($path)
+ {
+ // Gollem does not handle authentication
+ $GLOBALS['gollem_authentication'] = 'none';
+
+ // Include Gollem base libraries
+ require_once dirname(__FILE__) . '/base.php';
+ require GOLLEM_BASE . '/config/backends.php';
+ require GOLLEM_BASE . '/config/credentials.php';
+
+ // Clean off the irrelevant portions of the path
+ $path = Gollem::stripAPIPath($path);
+
+ if ($path == '') {
+ // We are at the root of gollem. Any writes at this level are
+ // disallowed.
+ return PEAR::raiseError(_('Folders must be created inside a VFS backend.'));
+ } else {
+ // We must be inside one of the VFS areas. Determine which one.
+ // Locate the backend_key in the path
+ if (!strchr($path, '/')) {
+ // Disallow attempts to create a share-level directory.
+ return PEAR::raiseError(_('Folders must be created inside a VFS backend.'));
+ } else {
+ $backend_key = substr($path, 0, strpos($path, '/'));
+ }
+
+ // Validate and perform permissions checks on the requested backend
+ if (!isset($backends[$backend_key])) {
+ return PEAR::raiseError(sprintf(_("Invalid backend requested: %s"), $backend_key));
+ }
+ //if (!Gollem::canAutoLogin($backend_key)) {
+ // // FIXME: Is it possible to request secondary authentication
+ // // credentials here for backends that require it?
+ // return PEAR::raiseError(_("Additional authentication required."));
+ //}
+ if (!Gollem_Session::createSession($backend_key)) {
+ return PEAR::raiseError(_("Unable to create Gollem session"));
+ }
+ if (!Gollem::checkPermissions('backend', PERMS_EDIT)) {
+ return PEAR::raiseError(_("Permission denied to this backend."));
+ }
+
+ // Trim off the backend_key (and '/') to get the VFS relative path
+ $fullpath = substr($path, strlen($backend_key) + 1);
+
+ // Get the VFS-standard $name,$path pair
+ list($name, $path) = Gollem::getVFSPath($fullpath);
+
+ return $GLOBALS['gollem_vfs']->createFolder($path, $name);
+ }
+ }
+
+ /**
+ * Renames a file or directory
+ *
+ * @param string $path Path to source object to be renamed
+ * @param string $dest Path to new name
+ *
+ * @return mixed True on success; PEAR_Error on failure
+ */
+ public function move($path, $dest)
+ {
+ // Gollem does not handle authentication
+ $GLOBALS['gollem_authentication'] = 'none';
+
+ // Include Gollem base libraries
+ require_once dirname(__FILE__) . '/base.php';
+ require GOLLEM_BASE . '/config/backends.php';
+ require GOLLEM_BASE . '/config/credentials.php';
+
+ // Clean off the irrelevant portions of the path
+ $path = Gollem::stripAPIPath($path);
+ $dest = Gollem::stripAPIPath($dest);
+
+ if ($path == '') {
+ // We are at the root of gollem. Any writes at this level are
+ // disallowed.
+ return PEAR::raiseError(_('Folders must be created inside a VFS backend.'));
+ } else {
+ // We must be inside one of the VFS areas. Determine which one.
+ // Locate the backend_key in the path
+ if (!strchr($path, '/')) {
+ // Disallow attempts to rename a share-level directory.
+ return PEAR::raiseError(_('Renaming of backends is not allowed.'));
+ } else {
+ $backend_key = substr($path, 0, strpos($path, '/'));
+ }
+
+ // Ensure that the destination is within the same backend
+ if (!strchr($dest, '/')) {
+ // Disallow attempts to rename a share-level directory.
+ return PEAR::raiseError(_('Renaming of backends is not allowed.'));
+ } else {
+ $dest_backend_key = substr($path, 0, strpos($path, '/'));
+ if ($dest_backend_key != $backend_key) {
+ return PEAR::raiseError(_('Renaming across backends is not supported.'));
+ }
+ }
+
+ // Validate and perform permissions checks on the requested backend
+ if (!isset($backends[$backend_key])) {
+ return PEAR::raiseError(sprintf(_("Invalid backend requested: %s"), $backend_key));
+ }
+ //if (!Gollem::canAutoLogin($backend_key)) {
+ // // FIXME: Is it possible to request secondary authentication
+ // // credentials here for backends that require it?
+ // return PEAR::raiseError(_("Additional authentication required."));
+ //}
+ if (!Gollem_Session::createSession($backend_key)) {
+ return PEAR::raiseError(_("Unable to create Gollem session"));
+ }
+ if (!Gollem::checkPermissions('backend', PERMS_EDIT)) {
+ return PEAR::raiseError(_("Permission denied to this backend."));
+ }
+
+ // Trim off the backend_key (and '/') to get the VFS relative path
+ $srcfullpath = substr($path, strlen($backend_key) + 1);
+ $dstfullpath = substr($dest, strlen($backend_key) + 1);
+
+ // Get the VFS-standard $name,$path pair
+ list($srcname, $srcpath) = Gollem::getVFSPath($srcfullpath);
+ list($dstname, $dstpath) = Gollem::getVFSPath($dstfullpath);
+
+ return $GLOBALS['gollem_vfs']->rename($srcpath, $srcname, $dstpath, $dstname);
+ }
+ }
+
+ /**
+ * Removes a file or folder from the VFS
+ *
+ * @param string $path Path of file or folder to delete
+ *
+ * @return mixed True on success; PEAR_Error on failure
+ */
+ public function path_delete($path)
+ {
+ // Gollem does not handle authentication
+ $GLOBALS['gollem_authentication'] = 'none';
+
+ // Include Gollem base libraries
+ require_once dirname(__FILE__) . '/base.php';
+ require GOLLEM_BASE . '/config/backends.php';
+ require GOLLEM_BASE . '/config/credentials.php';
+
+ // Clean off the irrelevant portions of the path
+ $path = Gollem::stripAPIPath($path);
+
+ if ($path == '') {
+ // We are at the root of gollem. Any writes at this level are
+ // disallowed.
+ return PEAR::raiseError(_("The application folder can not be deleted."));
+ } else {
+ // We must be inside one of the VFS areas. Determine which one.
+ // Locate the backend_key in the path
+ if (strchr($path, '/')) {
+ $backend_key = substr($path, 0, strpos($path, '/'));
+ } else {
+ $backend_key = $path;
+ }
+
+ // Validate and perform permissions checks on the requested backend
+ if (!isset($backends[$backend_key])) {
+ return PEAR::raiseError(sprintf(_("Invalid backend requested: %s"), $backend_key));
+ }
+ //if (!Gollem::canAutoLogin($backend_key)) {
+ // // FIXME: Is it possible to request secondary authentication
+ // // credentials here for backends that require it?
+ // return PEAR::raiseError(_("Additional authentication required."));
+ //}
+ if (!Gollem_Session::createSession($backend_key)) {
+ return PEAR::raiseError(_("Unable to create Gollem session"));
+ }
+ if (!Gollem::checkPermissions('backend', PERMS_EDIT)) {
+ return PEAR::raiseError(_("Permission denied to this backend."));
+ }
+
+ // Trim off the backend_key (and '/') to get the VFS relative path
+ $fullpath = substr($path, strlen($backend_key) + 1);
+
+ // Get the VFS-standard $name,$path pair
+ list($name, $path) = Gollem::getVFSPath($fullpath);
+
+ // Apparently Gollem::verifyDir() (called by deleteF* next) needs to
+ // see a path with a leading '/'
+ $path = $backends[$backend_key]['root'] . $path;
+ if ($GLOBALS['gollem_vfs']->isFolder($path, $name)) {
+ return Gollem::deleteFolder($path, $name);
+ } else {
+ return Gollem::deleteFile($path, $name);
+ }
+ }
+ }
+
+ public function perms()
+ {
+ static $perms = array();
+ if (!empty($perms)) {
+ return $perms;
+ }
+
+ require_once dirname(__FILE__) . '/base.load.php';
+ require GOLLEM_BASE . '/config/backends.php';
+
+ $perms['tree']['gollem']['backends'] = false;
+ $perms['title']['gollem:backends'] = _("Backends");
+
+ // Run through every backend.
+ foreach ($backends as $backend => $curBackend) {
+ $perms['tree']['gollem']['backends'][$backend] = false;
+ $perms['title']['gollem:backends:' . $backend] = $curBackend['name'];
+ }
+
+ return $perms;
+ }
+
+ /**
+ * Returns a link to the gollem file preview interface
+ *
+ * @param string $dir File absolute path
+ * @param string $file File basename
+ * @param string $backend Backend key. Defaults to Gollem::getPreferredBackend()
+ *
+ * @return string The URL string.
+ */
+ public function getViewLink($dir, $file, $backend = '')
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ if (empty($backend)) {
+ $backend = Gollem::getPreferredBackend();
+ }
+
+ $url = Horde_Util::addParameter(
+ Horde::applicationUrl('view.php'),
+ array('actionID' => 'view_file',
+ 'type' => substr($file, strrpos($file, '.') + 1),
+ 'file' => $file,
+ 'dir' => $dir,
+ 'driver' => $_SESSION['gollem']['backends'][$backend]['driver']));
+
+ return $url;
+ }
+
+ /**
+ * Creates a link to the gollem file selection window.
+ *
+ * The file section window will return a cache ID value which should be used
+ * (along with the selectListResults and returnFromSelectList functions below)
+ * to obtain the data from a list of selected files.
+ *
+ * There MUST be a form field named 'selectlist_selectid' in the calling
+ * form. This field will be populated with the selection ID when the user
+ * completes file selection.
+ *
+ * There MUST be a form parameter named 'actionID' in the calling form.
+ * This form will be populated with the value 'selectlist_process' when
+ * the user completes file selection. The calling form will be submitted
+ * after the window closes (i.e. the calling form must process the
+ * 'selectlist_process' actionID).
+ *
+ * @param string $link_text The text to use in the link.
+ * @param string $link_style The style to use for the link.
+ * @param string $formid The formid of the calling script.
+ * @param boolean $icon Create the link with an icon instead of text?
+ * @param string $selectid Selection ID.
+ *
+ * @return string The URL string.
+ */
+ public function selectlistLink($link_text, $link_style, $formid,
+ $icon = false, $selectid = '')
+ {
+ $link = Horde::link('#', $link_text, $link_style, '_blank', Horde::popupJs(Horde::applicationUrl('selectlist.php'), array('params' => array('formid' => $formid, 'cacheid' => $selectid), 'height' => 500, 'width' => 300, 'urlencode' => true)) . 'return false;');
+ if ($icon) {
+ $link_text = Horde::img('gollem.png', $link_text);
+ }
+ return '<script type="text/javascript">document.write(\''
+ . addslashes($link . $link_text) . '<\' + \'/a>\');</script>';
+ }
+
+ /**
+ * Returns the list of files selected by the user for a given selection ID.
+ *
+ * @param string $selectid The selection ID.
+ *
+ * @param array An array with each file entry stored in its own array, with
+ * the key as the directory name and the value as the filename.
+ */
+ public function selectlistResults($selectid)
+ {
+ if (!isset($_SESSION['gollem']['selectlist'][$selectid]['files'])) {
+ return null;
+ } else {
+ $list = array();
+ foreach ($_SESSION['gollem']['selectlist'][$selectid]['files'] as $val) {
+ list($dir, $filename) = explode('|', $val);
+ $list[] = array($dir => $filename);
+ }
+ return $list;
+ }
+ }
+
+ /**
+ * Returns the data for a given selection ID and index.
+ *
+ * @param string $selectid The selection ID.
+ * @param integer $index The index of the file data to return.
+ *
+ * @return string The file data.
+ */
+ public function returnFromSelectlist($selectid, $index)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ if (!isset($_SESSION['gollem']['selectlist'][$selectid]['files'][$index])) {
+ return null;
+ }
+
+ list($dir, $filename) = explode('|', $_SESSION['gollem']['selectlist'][$selectid]['files'][$index]);
+ return $GLOBALS['gollem_vfs']->read($dir, $filename);
+ }
+
+ /**
+ * Sets the files selected for a given selection ID.
+ *
+ * @param string $selectid The selection ID to use.
+ * @param array $files An array with each file entry stored in its own
+ * array, with the key as the directory name and the
+ * value as the filename.
+ *
+ * @return string The selection ID.
+ */
+ public function setSelectlist($selectid = '', $files = array())
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ if (empty($selectid)) {
+ $selectid = uniqid(mt_rand(), true);
+ }
+
+ if (count($files) > 0) {
+ $list = array();
+ foreach ($files as $file) {
+ $list[] = key($file) . '|' . current($file);
+ }
+ $_SESSION['gollem']['selectlist'][$selectid]['files'] = $list;
+ }
+
+ return $selectid;
+ }
+
+}
+++ /dev/null
-<?php
-/**
- * Gollem external API interface.
- *
- * This file defines Gollem's external API interface. Other
- * applications can interact with Gollem through this API.
- *
- * @author Amith Varghese (amith@xalan.com)
- * @author Michael Slusarz (slusarz@curecanti.org)
- * @author Ben Klang (bklang@alkaloid.net)
- * @package Gollem
- */
-
-$_services['browse'] = array(
- 'args' => array('path' => 'string'),
- 'type' => '{urn:horde}hashHash',
-);
-
-$_services['put'] = array(
- 'args' => array('path' => 'string', 'content' => 'string', 'content_type' => 'string'),
- 'type' => 'int',
-);
-
-$_services['mkcol'] = array(
- 'args' => array('path' => 'string'),
- 'type' => 'int',
-);
-
-$_services['move'] = array(
- 'args' => array('path' => 'string', 'dest' => 'string'),
- 'type' => 'int',
-);
-
-$_services['path_delete'] = array(
- 'args' => array('path' => 'string'),
- 'type' => 'int',
-);
-
-$_services['perms'] = array(
- 'args' => array(),
- 'type' => '{urn:horde}stringArray');
-
-$_services['selectlistLink'] = array(
- 'args' => array('link_text' => 'string', 'link_style' => 'string', 'formid' => 'string', 'icon' => 'boolean', 'selectid' => 'string'),
- 'type' => 'string');
-
-$_services['selectlistResults'] = array(
- 'args' => array('selectid' => 'string'),
- 'type' => 'array');
-
-$_services['returnFromSelectlist'] = array(
- 'args' => array('selectid' => 'string', 'index' => 'string'),
- 'type' => 'string');
-
-$_services['setSelectList'] = array(
- 'args' => array('selectid' => 'string', 'files' => 'array'),
- 'type' => 'string');
-
-$_services['getViewLink'] = array(
- 'args' => array('dir' => 'string', 'file' => 'string', 'backend' => 'string'),
- 'type' => 'string');
-
-/**
- * Browses through the VFS tree.
- *
- * Each VFS backend is listed as a directory at the top level. No modify
- * operations are allowed outside any VFS area.
- *
- * @param string $path The level of the tree to browse.
- * @param array $properties The item properties to return. Defaults to 'name',
- * 'icon', and 'browseable'.
- *
- * @return array The contents of $path.
- */
-function _gollem_browse($path = '', $properties = array())
-{
- $GLOBALS['gollem_authentication'] = 'none';
- require_once dirname(__FILE__) . '/base.php';
- require GOLLEM_BASE . '/config/backends.php';
- require GOLLEM_BASE . '/config/credentials.php';
-
- $path = Gollem::stripAPIPath($path);
-
- // Default properties.
- if (!$properties) {
- $properties = array('name', 'icon', 'browseable');
- }
-
- $results = array();
- if ($path == '') {
- // We are at the root of gollem. Return a set of folders, one for
- // each backend available.
- foreach ($backends as $backend => $curBackend) {
- if (Gollem::checkPermissions('backend', PERMS_SHOW, $backend)) {
- $results['gollem/' . $backend]['name'] = $curBackend['name'];
- $results['gollem/' . $backend]['browseable'] = true;
- }
- }
- } else {
- // A file or directory has been requested.
-
- // Locate the backend_key in the path.
- if (strchr($path, '/')) {
- $backend_key = substr($path, 0, strpos($path, '/'));
- } else {
- $backend_key = $path;
- }
-
- // Validate and perform permissions checks on the requested backend
- if (!isset($backends[$backend_key])) {
- return PEAR::raiseError(sprintf(_("Invalid backend requested: %s"), $backend_key));
- }
- //if (!Gollem::canAutoLogin($backend_key)) {
- // // FIXME: Is it possible to request secondary authentication
- // // credentials here for backends that require it?
- // return PEAR::raiseError(_("Additional authentication required."));
- //}
- if (!Gollem_Session::createSession($backend_key)) {
- return PEAR::raiseError(_("Unable to create Gollem session"));
- }
- if (!Gollem::checkPermissions('backend', PERMS_READ)) {
- return PEAR::raiseError(_("Permission denied to this backend."));
- }
-
- // Trim off the backend_key (and '/') to get the VFS relative path
- $fullpath = substr($path, strlen($backend_key) + 1);
-
- // Get the VFS-standard $name,$path pair
- list($name, $path) = Gollem::getVFSPath($fullpath);
-
- // Check to see if the request is a file or folder
- if ($GLOBALS['gollem_vfs']->isFolder($path, $name)) {
- // This is a folder request. Return a directory listing.
- $list = Gollem::listFolder($path . '/' . $name);
- if (is_a($list, 'PEAR_Error')) {
- return $list;
- }
-
- // Iterate over the directory contents
- if (is_array($list) && count($list)) {
- $index = 'gollem/' . $backend_key . '/' . $fullpath;
- foreach ($list as $key => $val) {
- $entry = Gollem::pathEncode($index . '/' . $val['name']);
- $results[$entry]['name'] = $val['name'];
- $results[$entry]['modified'] = $val['date'];
- if ($val['type'] == '**dir') {
- $results[$entry]['browseable'] = true;
- } else {
- $results[$entry]['browseable'] = false;
- $results[$entry]['contentlength'] = $val['size'];
- }
- }
- }
- } else {
- // A file has been requested. Return the contents of the file.
-
- // Get the file meta-data
- $list = Gollem::listFolder($path);
- $i = false;
- foreach ($list as $key => $file) {
- if ($file['name'] == $name) {
- $i = $key;
- break;
- }
- }
- if ($i === false) {
- // File not found
- return $i;
- }
-
- // Read the file contents
- $data = $GLOBALS['gollem_vfs']->read($path, $name);
- if (is_a($data, 'PEAR_Error')) {
- return false;
- }
-
- // Send the file
- $results['name'] = $name;
- $results['data'] = $data;
- $results['contentlength'] = $list[$i]['size'];
- $results['mtime'] = $list[$i]['date'];
- }
- }
-
- return $results;
-}
-
-/**
- * Accepts a file for storage into the VFS
- *
- * @param string $path Path to store file
- * @param string $content Contents of file
- * @param string $content_type MIME type of file
- *
- * @return mixed True on success; PEAR_Error on failure
- */
-function _gollem_put($path, $content, $content_type)
-{
- // Gollem does not handle authentication
- $GLOBALS['gollem_authentication'] = 'none';
-
- // Include Gollem base libraries
- require_once dirname(__FILE__) . '/base.php';
- require GOLLEM_BASE . '/config/backends.php';
- require GOLLEM_BASE . '/config/credentials.php';
-
- // Clean off the irrelevant portions of the path
- $path = Gollem::stripAPIPath($path);
-
- if ($path == '') {
- // We are at the root of gollem. Any writes at this level are
- // disallowed.
- return PEAR::raiseError(_("Files must be written inside a VFS backend."));
- } else {
- // We must be inside one of the VFS areas. Determine which one.
- // Locate the backend_key in the path
- if (strchr($path, '/')) {
- $backend_key = substr($path, 0, strpos($path, '/'));
- } else {
- $backend_key = $path;
- }
-
- // Validate and perform permissions checks on the requested backend
- if (!isset($backends[$backend_key])) {
- return PEAR::raiseError(sprintf(_("Invalid backend requested: %s"), $backend_key));
- }
- //if (!Gollem::canAutoLogin($backend_key)) {
- // // FIXME: Is it possible to request secondary authentication
- // // credentials here for backends that require it?
- // return PEAR::raiseError(_("Additional authentication required."));
- //}
- if (!Gollem_Session::createSession($backend_key)) {
- return PEAR::raiseError(_("Unable to create Gollem session"));
- }
- if (!Gollem::checkPermissions('backend', PERMS_EDIT)) {
- return PEAR::raiseError(_("Permission denied to this backend."));
- }
-
- // Trim off the backend_key (and '/') to get the VFS relative path
- $fullpath = substr($path, strlen($backend_key) + 1);
-
- // Get the VFS-standard $name,$path pair
- list($name, $path) = Gollem::getVFSPath($fullpath);
-
- return $GLOBALS['gollem_vfs']->writeData($path, $name, $content);
- }
-}
-
-/**
- * Creates a directory ("collection" in WebDAV-speak) within the VFS
- *
- * @param string $path Path of directory to create
- *
- * @return mixed True on success; PEAR_Error on failure
- */
-function _gollem_mkcol($path)
-{
- // Gollem does not handle authentication
- $GLOBALS['gollem_authentication'] = 'none';
-
- // Include Gollem base libraries
- require_once dirname(__FILE__) . '/base.php';
- require GOLLEM_BASE . '/config/backends.php';
- require GOLLEM_BASE . '/config/credentials.php';
-
- // Clean off the irrelevant portions of the path
- $path = Gollem::stripAPIPath($path);
-
- if ($path == '') {
- // We are at the root of gollem. Any writes at this level are
- // disallowed.
- return PEAR::raiseError(_('Folders must be created inside a VFS backend.'));
- } else {
- // We must be inside one of the VFS areas. Determine which one.
- // Locate the backend_key in the path
- if (!strchr($path, '/')) {
- // Disallow attempts to create a share-level directory.
- return PEAR::raiseError(_('Folders must be created inside a VFS backend.'));
- } else {
- $backend_key = substr($path, 0, strpos($path, '/'));
- }
-
- // Validate and perform permissions checks on the requested backend
- if (!isset($backends[$backend_key])) {
- return PEAR::raiseError(sprintf(_("Invalid backend requested: %s"), $backend_key));
- }
- //if (!Gollem::canAutoLogin($backend_key)) {
- // // FIXME: Is it possible to request secondary authentication
- // // credentials here for backends that require it?
- // return PEAR::raiseError(_("Additional authentication required."));
- //}
- if (!Gollem_Session::createSession($backend_key)) {
- return PEAR::raiseError(_("Unable to create Gollem session"));
- }
- if (!Gollem::checkPermissions('backend', PERMS_EDIT)) {
- return PEAR::raiseError(_("Permission denied to this backend."));
- }
-
- // Trim off the backend_key (and '/') to get the VFS relative path
- $fullpath = substr($path, strlen($backend_key) + 1);
-
- // Get the VFS-standard $name,$path pair
- list($name, $path) = Gollem::getVFSPath($fullpath);
-
- return $GLOBALS['gollem_vfs']->createFolder($path, $name);
- }
-}
-
-/**
- * Renames a file or directory
- *
- * @param string $path Path to source object to be renamed
- * @param string $dest Path to new name
- *
- * @return mixed True on success; PEAR_Error on failure
- */
-function _gollem_move($path, $dest)
-{
- // Gollem does not handle authentication
- $GLOBALS['gollem_authentication'] = 'none';
-
- // Include Gollem base libraries
- require_once dirname(__FILE__) . '/base.php';
- require GOLLEM_BASE . '/config/backends.php';
- require GOLLEM_BASE . '/config/credentials.php';
-
- // Clean off the irrelevant portions of the path
- $path = Gollem::stripAPIPath($path);
- $dest = Gollem::stripAPIPath($dest);
-
- if ($path == '') {
- // We are at the root of gollem. Any writes at this level are
- // disallowed.
- return PEAR::raiseError(_('Folders must be created inside a VFS backend.'));
- } else {
- // We must be inside one of the VFS areas. Determine which one.
- // Locate the backend_key in the path
- if (!strchr($path, '/')) {
- // Disallow attempts to rename a share-level directory.
- return PEAR::raiseError(_('Renaming of backends is not allowed.'));
- } else {
- $backend_key = substr($path, 0, strpos($path, '/'));
- }
-
- // Ensure that the destination is within the same backend
- if (!strchr($dest, '/')) {
- // Disallow attempts to rename a share-level directory.
- return PEAR::raiseError(_('Renaming of backends is not allowed.'));
- } else {
- $dest_backend_key = substr($path, 0, strpos($path, '/'));
- if ($dest_backend_key != $backend_key) {
- return PEAR::raiseError(_('Renaming across backends is not supported.'));
- }
- }
-
- // Validate and perform permissions checks on the requested backend
- if (!isset($backends[$backend_key])) {
- return PEAR::raiseError(sprintf(_("Invalid backend requested: %s"), $backend_key));
- }
- //if (!Gollem::canAutoLogin($backend_key)) {
- // // FIXME: Is it possible to request secondary authentication
- // // credentials here for backends that require it?
- // return PEAR::raiseError(_("Additional authentication required."));
- //}
- if (!Gollem_Session::createSession($backend_key)) {
- return PEAR::raiseError(_("Unable to create Gollem session"));
- }
- if (!Gollem::checkPermissions('backend', PERMS_EDIT)) {
- return PEAR::raiseError(_("Permission denied to this backend."));
- }
-
- // Trim off the backend_key (and '/') to get the VFS relative path
- $srcfullpath = substr($path, strlen($backend_key) + 1);
- $dstfullpath = substr($dest, strlen($backend_key) + 1);
-
- // Get the VFS-standard $name,$path pair
- list($srcname, $srcpath) = Gollem::getVFSPath($srcfullpath);
- list($dstname, $dstpath) = Gollem::getVFSPath($dstfullpath);
-
- return $GLOBALS['gollem_vfs']->rename($srcpath, $srcname, $dstpath, $dstname);
- }
-}
-
-/**
- * Removes a file or folder from the VFS
- *
- * @param string $path Path of file or folder to delete
- *
- * @return mixed True on success; PEAR_Error on failure
- */
-function _gollem_path_delete($path)
-{
- // Gollem does not handle authentication
- $GLOBALS['gollem_authentication'] = 'none';
-
- // Include Gollem base libraries
- require_once dirname(__FILE__) . '/base.php';
- require GOLLEM_BASE . '/config/backends.php';
- require GOLLEM_BASE . '/config/credentials.php';
-
- // Clean off the irrelevant portions of the path
- $path = Gollem::stripAPIPath($path);
-
- if ($path == '') {
- // We are at the root of gollem. Any writes at this level are
- // disallowed.
- return PEAR::raiseError(_("The application folder can not be deleted."));
- } else {
- // We must be inside one of the VFS areas. Determine which one.
- // Locate the backend_key in the path
- if (strchr($path, '/')) {
- $backend_key = substr($path, 0, strpos($path, '/'));
- } else {
- $backend_key = $path;
- }
-
- // Validate and perform permissions checks on the requested backend
- if (!isset($backends[$backend_key])) {
- return PEAR::raiseError(sprintf(_("Invalid backend requested: %s"), $backend_key));
- }
- //if (!Gollem::canAutoLogin($backend_key)) {
- // // FIXME: Is it possible to request secondary authentication
- // // credentials here for backends that require it?
- // return PEAR::raiseError(_("Additional authentication required."));
- //}
- if (!Gollem_Session::createSession($backend_key)) {
- return PEAR::raiseError(_("Unable to create Gollem session"));
- }
- if (!Gollem::checkPermissions('backend', PERMS_EDIT)) {
- return PEAR::raiseError(_("Permission denied to this backend."));
- }
-
- // Trim off the backend_key (and '/') to get the VFS relative path
- $fullpath = substr($path, strlen($backend_key) + 1);
-
- // Get the VFS-standard $name,$path pair
- list($name, $path) = Gollem::getVFSPath($fullpath);
-
- // Apparently Gollem::verifyDir() (called by deleteF* next) needs to
- // see a path with a leading '/'
- $path = $backends[$backend_key]['root'] . $path;
- if ($GLOBALS['gollem_vfs']->isFolder($path, $name)) {
- return Gollem::deleteFolder($path, $name);
- } else {
- return Gollem::deleteFile($path, $name);
- }
- }
-}
-
-function _gollem_perms()
-{
- static $perms = array();
- if (!empty($perms)) {
- return $perms;
- }
-
- require_once dirname(__FILE__) . '/base.load.php';
- require GOLLEM_BASE . '/config/backends.php';
-
- $perms['tree']['gollem']['backends'] = false;
- $perms['title']['gollem:backends'] = _("Backends");
-
- // Run through every backend.
- foreach ($backends as $backend => $curBackend) {
- $perms['tree']['gollem']['backends'][$backend] = false;
- $perms['title']['gollem:backends:' . $backend] = $curBackend['name'];
- }
-
- return $perms;
-}
-
-/**
- * Returns a link to the gollem file preview interface
- *
- * @param string $dir File absolute path
- * @param string $file File basename
- * @param string $backend Backend key. Defaults to Gollem::getPreferredBackend()
- *
- * @return string The URL string.
- */
-function _gollem_getViewLink($dir, $file, $backend = '')
-{
- require_once dirname(__FILE__) . '/base.php';
-
- if (empty($backend)) {
- $backend = Gollem::getPreferredBackend();
- }
-
- $url = Horde_Util::addParameter(
- Horde::applicationUrl('view.php'),
- array('actionID' => 'view_file',
- 'type' => substr($file, strrpos($file, '.') + 1),
- 'file' => $file,
- 'dir' => $dir,
- 'driver' => $_SESSION['gollem']['backends'][$backend]['driver']));
-
- return $url;
-}
-
-/**
- * Creates a link to the gollem file selection window.
- *
- * The file section window will return a cache ID value which should be used
- * (along with the selectListResults and returnFromSelectList functions below)
- * to obtain the data from a list of selected files.
- *
- * There MUST be a form field named 'selectlist_selectid' in the calling
- * form. This field will be populated with the selection ID when the user
- * completes file selection.
- *
- * There MUST be a form parameter named 'actionID' in the calling form.
- * This form will be populated with the value 'selectlist_process' when
- * the user completes file selection. The calling form will be submitted
- * after the window closes (i.e. the calling form must process the
- * 'selectlist_process' actionID).
- *
- * @param string $link_text The text to use in the link.
- * @param string $link_style The style to use for the link.
- * @param string $formid The formid of the calling script.
- * @param boolean $icon Create the link with an icon instead of text?
- * @param string $selectid Selection ID.
- *
- * @return string The URL string.
- */
-function _gollem_selectlistLink($link_text, $link_style, $formid,
- $icon = false, $selectid = '')
-{
- $link = Horde::link('#', $link_text, $link_style, '_blank', Horde::popupJs(Horde::applicationUrl('selectlist.php'), array('params' => array('formid' => $formid, 'cacheid' => $selectid), 'height' => 500, 'width' => 300, 'urlencode' => true)) . 'return false;');
- if ($icon) {
- $link_text = Horde::img('gollem.png', $link_text);
- }
- return '<script type="text/javascript">document.write(\''
- . addslashes($link . $link_text) . '<\' + \'/a>\');</script>';
-}
-
-/**
- * Returns the list of files selected by the user for a given selection ID.
- *
- * @param string $selectid The selection ID.
- *
- * @param array An array with each file entry stored in its own array, with
- * the key as the directory name and the value as the filename.
- */
-function _gollem_selectlistResults($selectid)
-{
- if (!isset($_SESSION['gollem']['selectlist'][$selectid]['files'])) {
- return null;
- } else {
- $list = array();
- foreach ($_SESSION['gollem']['selectlist'][$selectid]['files'] as $val) {
- list($dir, $filename) = explode('|', $val);
- $list[] = array($dir => $filename);
- }
- return $list;
- }
-}
-
-/**
- * Returns the data for a given selection ID and index.
- *
- * @param string $selectid The selection ID.
- * @param integer $index The index of the file data to return.
- *
- * @return string The file data.
- */
-function _gollem_returnFromSelectlist($selectid, $index)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- if (!isset($_SESSION['gollem']['selectlist'][$selectid]['files'][$index])) {
- return null;
- }
-
- list($dir, $filename) = explode('|', $_SESSION['gollem']['selectlist'][$selectid]['files'][$index]);
- return $GLOBALS['gollem_vfs']->read($dir, $filename);
-}
-
-/**
- * Sets the files selected for a given selection ID.
- *
- * @param string $selectid The selection ID to use.
- * @param array $files An array with each file entry stored in its own
- * array, with the key as the directory name and the
- * value as the filename.
- *
- * @return string The selection ID.
- */
-function _gollem_setSelectlist($selectid = '', $files = array())
-{
- require_once dirname(__FILE__) . '/base.php';
-
- if (empty($selectid)) {
- $selectid = uniqid(mt_rand(), true);
- }
-
- if (count($files) > 0) {
- $list = array();
- foreach ($files as $file) {
- $list[] = key($file) . '|' . current($file);
- }
- $_SESSION['gollem']['selectlist'][$selectid]['files'] = $list;
- }
-
- return $selectid;
-}
+++ /dev/null
-<?php define('GOLLEM_VERSION', 'H4 (2.0-git)') ?>
/* Gollem version. */
$module = 'Gollem';
-require_once GOLLEM_BASE . '/lib/version.php';
-$module_version = GOLLEM_VERSION;
+require_once dirname(__FILE__) . '/lib/Api.php';
+$api = new Gollem_Api();
+$module_version = $api->version;
/* Gollem configuration files. */
$file_list = array(
if (empty($_SESSION['imp']['admin'])) {
$new_user_field = '<input id="new_user" type="text" name="new_user"/>';
} else {
- require_once IMP_BASE . '/lib/api.php';
$current_users = array_keys($curr_acl);
$new_user_field = '<select id="new_user" name="new_user">';
- foreach (_imp_userList() as $user) {
+ foreach ($registry->callByPackage('userList', 'imp') as $user) {
if (in_array($user, $current_users)) {
continue;
}
--- /dev/null
+<?php
+/**
+ * IMP external API interface.
+ *
+ * This file defines IMP's external API interface. Other applications
+ * can interact with IMP through this API.
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @package IMP
+ */
+class IMP_Api extends Horde_Registry_Api
+{
+ /**
+ * Does this application support a mobile view?
+ *
+ * @var boolean
+ */
+ public $mobileView = true;
+
+ /**
+ * The application's version.
+ *
+ * @var string
+ */
+ public $version = 'H4 (5.0-git)';
+
+ /**
+ * The services provided by this application.
+ *
+ * @var array
+ */
+ public $services = array(
+ 'perms' => array(
+ 'args' => array(),
+ 'type' => '{urn:horde}hashHash'
+ ),
+
+ 'authCredentials' => array(
+ 'args' => array(),
+ 'type' => '{urn:horde}hashHash'
+ ),
+
+ 'compose' => array(
+ 'args' => array(
+ 'args' => '{urn:horde}hash',
+ 'extra' => '{urn:horde}hash'
+ ),
+ 'type' => 'string'
+ ),
+
+ 'batchCompose' => array(
+ 'args' => array(
+ 'args' => '{urn:horde}hash',
+ 'extra' => '{urn:horde}hash'
+ ),
+ 'type' => 'string'
+ ),
+
+ 'folderlist' => array(
+ 'args' => array(),
+ 'type' => '{urn:horde}stringArray'
+ ),
+
+ 'createFolder' => array(
+ 'args' => array('folder' => 'string'),
+ 'type' => 'string'
+ ),
+
+ 'deleteMessages' => array(
+ 'args' => array(
+ 'mailbox' => 'string',
+ 'indices' => '{urn:horde}integerArray'
+ ),
+ 'type' => 'integer'
+ ),
+
+ 'copyMessages' => array(
+ 'args' => array(
+ 'mailbox' => 'string',
+ 'indices' => '{urn:horde}integerArray',
+ 'target' => 'string'
+ ),
+ 'type' => 'boolean'
+ ),
+
+ 'moveMessages' => array(
+ 'args' => array(
+ 'mailbox' => 'string',
+ 'indices' => '{urn:horde}integerArray',
+ 'target' => 'string'
+ ),
+ 'type' => 'boolean'
+ ),
+
+ 'flagMessages' => array(
+ 'args' => array(
+ 'mailbox' => 'string',
+ 'indices' => '{urn:horde}integerArray',
+ 'flags' => '{urn:horde}stringArray',
+ 'set' => 'boolean'
+ ),
+ 'type' => 'boolean'
+ ),
+
+ 'msgEnvelope' => array(
+ 'args' => array(
+ 'mailbox' => 'string',
+ 'indices' => '{urn:horde}integerArray'
+ ),
+ 'type' => '{urn:horde}hashHash'
+ ),
+
+ 'searchMailbox' => array(
+ 'args' => array(
+ 'mailbox' => 'string',
+ 'query' => 'object'
+ ),
+ 'type' => '{urn:horde}integerArray'
+ ),
+
+ 'mailboxCacheId' => array(
+ 'args' => array(
+ 'mailbox' => 'string'
+ ),
+ 'type' => 'string'
+ ),
+
+ 'server' => array(
+ 'args' => array(),
+ 'type' => '{urn:horde}hashHash'
+ ),
+
+ 'favouriteRecipients' => array(
+ 'args' => array(
+ 'limit' => 'int'
+ ),
+ 'type' => '{urn:horde}stringArray'
+ ),
+
+ 'changeLanguage' => array(
+ 'args' => array(),
+ 'type' => 'boolean'
+ ),
+
+ /* Cache display method. */
+ 'cacheOutput' => array(
+ 'args' => array(
+ '{urn:horde}hashHash'
+ ),
+ 'type' => '{urn:horde}hashHash'
+ ),
+
+ /* Horde_Auth_Application methods. */
+ 'authLoginParams' => array(
+ 'args' => array(),
+ 'checkperms' => false,
+ 'type' => '{urn:horde}hashHash'
+ ),
+
+ 'authAuthenticate' => array(
+ 'args' => array(
+ 'userID' => 'string',
+ 'credentials' => '{urn:horde}hash',
+ 'params' => '{urn:horde}hash'
+ ),
+ 'checkperms' => false,
+ 'type' => 'boolean'
+ ),
+
+ 'authAuthenticateCallback' => array(
+ 'args' => array(),
+ 'checkperms' => false
+ ),
+
+ 'authTransparent' => array(
+ 'args' => array(),
+ 'checkperms' => false,
+ 'type' => 'boolean'
+ ),
+
+ 'authAddUser' => array(
+ 'args' => array(
+ 'userId' => 'string',
+ 'credentials' => '{urn:horde}stringArray'
+ )
+ ),
+ 'authRemoveUser' => array(
+ 'args' => array(
+ 'userId' => 'string'
+ )
+ ),
+ 'authUserList' => array(
+ 'type' => '{urn:horde}stringArray'
+ )
+ );
+
+ /**
+ * Constructor.
+ */
+ public function __construct()
+ {
+ /* Only available if admin config is set for this server/login. */
+ if (empty($_SESSION['imp']['admin'])) {
+ unset($this->services['authAddUser'], $this->services['authRemoveUser'], $this->services['authUserList']);
+ }
+ }
+
+ /* Horde-defined functions. */
+
+ /**
+ * Returns a list of available permissions.
+ *
+ * @return array The permissions list.
+ */
+ public function perms()
+ {
+ return array(
+ 'tree' => array(
+ 'imp' => array(
+ 'create_folders' => false,
+ 'max_folders' => false,
+ 'max_recipients' => false,
+ 'max_timelimit' => false,
+ ),
+ ),
+ 'title' => array(
+ 'imp:create_folders' => _("Allow Folder Creation?"),
+ 'imp:max_folders' => _("Maximum Number of Folders"),
+ 'imp:max_recipients' => _("Maximum Number of Recipients per Message"),
+ 'imp:max_timelimit' => _("Maximum Number of Recipients per Time Period"),
+ ),
+ 'type' => array(
+ 'imp:create_folders' => 'boolean',
+ 'imp:max_folders' => 'int',
+ 'imp:max_recipients' => 'int',
+ 'imp:max_timelimit' => 'int',
+ )
+ );
+ }
+
+ /**
+ * Performs tasks necessary when the language is changed during the
+ * session.
+ */
+ public function changeLanguage()
+ {
+ try {
+ $GLOBALS['imp_authentication'] = 'throw';
+ require_once dirname(__FILE__) . '/base.php';
+ } catch (Horde_Exception $e) {
+ return;
+ }
+
+ $imp_folder = IMP_Folder::singleton();
+ $imp_folder->clearFlistCache();
+ $imaptree = IMP_Imap_Tree::singleton();
+ $imaptree->init();
+ $GLOBALS['imp_search']->initialize(true);
+ }
+
+ /**
+ * Returns a list of authentication credentials, i.e. server settings that
+ * can be specified by the user on the login screen.
+ *
+ * @return array A hash with credentials, suited for the preferences
+ * interface.
+ */
+ public function authCredentials()
+ {
+ $app_name = $GLOBALS['registry']->get('name');
+
+ $servers = IMP_Imap::loadServerConfig();
+ $server_list = array();
+ foreach ($servers as $key => $val) {
+ $server_list[$key] = $val['name'];
+ }
+ reset($server_list);
+
+ $credentials = array(
+ 'username' => array(
+ 'desc' => sprintf(_("%s for %s"), _("Username"), $app_name),
+ 'type' => 'text'
+ ),
+ 'password' => array(
+ 'desc' => sprintf(_("%s for %s"), _("Password"), $app_name),
+ 'type' => 'password'
+ ),
+ 'server' => array(
+ 'desc' => sprintf(_("%s for %s"), _("Server"), $app_name),
+ 'type' => 'enum',
+ 'enum' => $server_list,
+ 'value' => key($server_list)
+ )
+ );
+
+ return $credentials;
+ }
+
+ /**
+ * Application-specific cache output driver.
+ *
+ * @param array $params A list of params needed (USED: 'id').
+ *
+ * @return array See Horde::getCacheUrl().
+ * @throws Horde_Exception
+ */
+ public function cacheOutput($params)
+ {
+ try {
+ $GLOBALS['imp_authentication'] = 'throw';
+ require_once dirname(__FILE__) . '/base.php';
+ } catch (Horde_Exception $e) {
+ throw new Horde_Exception('No cache data available');
+ }
+
+ switch ($params['id']) {
+ case 'fckeditor':
+ return array(
+ 'data' =>
+ 'FCKConfig.ToolbarSets["ImpToolbar"] = ' . $GLOBALS['prefs']->getValue('fckeditor_buttons') . ";\n" .
+ /* To more closely match "normal" textarea behavior, send
+ * send <BR> on enter instead of <P>. */
+ "FCKConfig.EnterMode = 'br';\n" .
+ 'FCKConfig.ShiftEnterMode = \'p\';',
+ 'type' => 'text/javascript'
+ );
+ }
+ }
+
+ /* Horde_Auth_Application defined functions. */
+
+ /**
+ * Return login parameters used on the login page.
+ *
+ * @return array TODO
+ */
+ public function authLoginParams()
+ {
+ $params = array();
+
+ if ($GLOBALS['conf']['server']['server_list'] == 'shown') {
+ $servers = IMP_Imap::loadServerConfig();
+ $server_list = array();
+ $selected = Horde_Util::getFormData('imp_server_key', IMP_Auth::getAutoLoginServer());
+ foreach ($servers as $key => $val) {
+ $server_list[$key] = array(
+ 'name' => $val['name'],
+ 'selected' => ($selected == $key)
+ );
+ }
+ $params['imp_server_key'] = array(
+ 'label' => _("Server"),
+ 'type' => 'select',
+ 'value' => $server_list
+ );
+ }
+
+ /* Show selection of alternate views. */
+ if (!empty($GLOBALS['conf']['user']['select_view'])) {
+ $views = array();
+ if (!($view_cookie = Horde_Util::getFormData('imp_select_view'))) {
+ if (isset($_COOKIE['default_imp_view'])) {
+ $view_cookie = $_COOKIE['default_imp_view'];
+ } else {
+ $browser = Horde_Browser::singleton();
+ $view_cookie = $browser->isMobile() ? 'mimp' : 'imp';
+ }
+ }
+
+ $params['imp_select_view'] = array(
+ 'label' => _("Mode"),
+ 'type' => 'select',
+ 'value' => array(
+ 'imp' => array(
+ 'name' => _("Traditional"),
+ 'selected' => $view_cookie == 'imp'
+ ),
+ 'dimp' => array(
+ 'hidden' => true,
+ 'name' => _("Dynamic")
+ // Dimp selected is handled by javascript (dimp_sel)
+ ),
+ 'mimp' => array(
+ 'name' => _("Minimalist"),
+ 'selected' => $view_cookie == 'mimp'
+ )
+ )
+ );
+ }
+
+ return array(
+ 'js_code' => array(
+ 'ImpLogin.dimp_sel=' . intval($view_cookie == 'dimp'),
+ 'ImpLogin.server_key_error=' . Horde_Serialize::serialize(_("Please choose a mail server."), Horde_Serialize::JSON)
+ ),
+ 'js_files' => array(
+ array('login.js', 'imp')
+ ),
+ 'params' => $params
+ );
+ }
+
+ /**
+ * Tries to authenticate with the mail server and create a mail session.
+ *
+ * @param string $userId The username of the user.
+ * @param array $credentials Credentials of the user. Allowed keys:
+ * 'imp_select_view', 'imp_server_key',
+ * 'password'.
+ *
+ * @throws Horde_Auth_Exception
+ */
+ public function authAuthenticate($userId, $credentials)
+ {
+ $GLOBALS['imp_authentication'] = 'none';
+ require_once dirname(__FILE__) . '/base.php';
+
+ $new_session = IMP_Auth::authenticate(array(
+ 'password' => $credentials['password'],
+ 'server' => empty($credentials['imp_server_key']) ? IMP_Auth::getAutoLoginServer() : $credentials['imp_server_key'],
+ 'userId' => $userId
+ ));
+
+ if ($new_session) {
+ $_SESSION['imp']['cache']['select_view'] = empty($credentials['imp_select_view']) ? '' : $credentials['imp_select_view'];
+
+ /* Set the Horde ID, since it may have been altered by the 'realm'
+ * setting. */
+ $credentials['auth_ob']->setCredential('userId', $_SESSION['imp']['uniquser']);
+ }
+ }
+
+ /**
+ * Tries to transparently authenticate with the mail server and create a
+ * mail session.
+ *
+ * @return boolean Whether transparent login is supported.
+ * @throws Horde_Auth_Exception
+ */
+ public function authTransparent()
+ {
+ /* Transparent auth is a bit goofy - we most likely have reached this
+ * code from the pushApp() call in base.php already. As such, some of
+ * the IMP init has not yet been done, so we need to do the necessary
+ * init here or else things will fail in IMP_Auth. */
+ $GLOBALS['imp_authentication'] = 'none';
+ require_once dirname(__FILE__) . '/base.php';
+ IMP::initialize();
+ return IMP_Auth::transparent();
+ }
+
+ /**
+ * Does necessary authentication tasks reliant on a full IMP environment.
+ *
+ * @throws Horde_Auth_Exception
+ */
+ public function authAuthenticateCallback()
+ {
+ if (Horde_Auth::getAuth()) {
+ require_once dirname(__FILE__) . '/base.php';
+ IMP_Auth::authenticateCallback();
+ }
+ }
+
+ /**
+ * Adds a user defined by authentication credentials.
+ *
+ * @param string $userId The userId to add.
+ * @param array $credentials An array of login credentials. For IMAP,
+ * this must contain a password entry.
+ *
+ * @throws Horde_Exception
+ */
+ public function authAddUser($userId, $credentials)
+ {
+ if (($params = $this->server()) === null) {
+ return;
+ }
+
+ $params = array_merge($params, $_SESSION['imp']['admin']['params']);
+ if (isset($params['admin_password'])) {
+ $params['admin_password'] = Horde_Secret::read(Horde_Secret::getKey('imp'), $params['admin_password']);
+ }
+ $auth = Horde_Auth::singleton('imap', $params);
+ $auth->addUser($userId, $credentials);
+ }
+
+ /**
+ * Deletes a user defined by authentication credentials.
+ *
+ * @param string $userId The userId to delete.
+ *
+ * @throws Horde_Exception
+ */
+ public function authRemoveUser($userId)
+ {
+ if (($params = $this->server()) === null) {
+ return;
+ }
+
+ $params = array_merge($params, $_SESSION['imp']['admin']['params']);
+ if (isset($params['admin_password'])) {
+ $params['admin_password'] = Horde_Secret::read(Horde_Secret::getKey('imp'), $params['admin_password']);
+ }
+ $auth = Horde_Auth::singleton('imap', $params);
+ $auth->removeUser($userId);
+ }
+
+ /**
+ * Lists all users in the system.
+ *
+ * @return array The array of userIds.
+ * @throws Horde_Exception
+ */
+ public function authUserList()
+ {
+ if (($params = $this->server()) === null) {
+ return;
+ }
+
+ $params = array_merge($params, $_SESSION['imp']['admin']['params']);
+ if (isset($params['admin_password'])) {
+ $params['admin_password'] = Horde_Secret::read(Horde_Secret::getKey('imp'), $params['admin_password']);
+ }
+ $auth = Horde_Auth::singleton('imap', $params);
+ return $auth->listUsers();
+ }
+
+ /* IMP-specific functions. */
+
+ /**
+ * Returns a compose window link.
+ *
+ * @param string|array $args List of arguments to pass to compose.php.
+ * If this is passed in as a string, it will be
+ * parsed as a
+ * toaddress?subject=foo&cc=ccaddress
+ * (mailto-style) string.
+ * @param array $extra Hash of extra, non-standard arguments to
+ * pass to compose.php.
+ *
+ * @return string The link to the message composition screen.
+ */
+ public function compose($args = array(), $extra = array())
+ {
+ $link = $this->batchCompose(array($args), array($extra));
+ return $link[0];
+ }
+
+ /**
+ * Return a list of compose window links.
+ *
+ * @param string|array $args List of arguments to pass to compose.php.
+ * If this is passed in as a string, it will be
+ * parsed as a
+ * toaddress?subject=foo&cc=ccaddress
+ * (mailto-style) string.
+ * @param array $extra List of hashes of extra, non-standard
+ * arguments to pass to compose.php.
+ *
+ * @return string The list of links to the message composition screen.
+ */
+ public function batchCompose($args = array(), $extra = array())
+ {
+ $GLOBALS['imp_authentication'] = 'none';
+ require_once dirname(__FILE__) . '/base.php';
+
+ $links = array();
+ foreach ($args as $i => $arg) {
+ $links[$i] = IMP::composeLink($arg, !empty($extra[$i]) ? $extra[$i] : array());
+ }
+
+ return $links;
+ }
+
+ /**
+ * Returns the list of folders.
+ *
+ * @return array The list of IMAP folders or false if not available.
+ */
+ public function folderlist()
+ {
+ try {
+ $GLOBALS['imp_authentication'] = 'throw';
+ require_once dirname(__FILE__) . '/base.php';
+ } catch (Horde_Exception $e) {
+ return false;
+ }
+
+ $imp_folder = IMP_Folder::singleton();
+ return $imp_folder->flist();
+ }
+
+ /**
+ * Creates a new folder.
+ *
+ * @param string $folder The name of the folder to create (UTF7-IMAP).
+ *
+ * @return string The full folder name created or false on failure.
+ */
+ public function createFolder($folder)
+ {
+ try {
+ $GLOBALS['imp_authentication'] = 'throw';
+ require_once dirname(__FILE__) . '/base.php';
+ } catch (Horde_Exception $e) {
+ return false;
+ }
+
+ $imp_folder = IMP_Folder::singleton();
+ return $imp_folder->create(IMP::appendNamespace($folder), $GLOBALS['prefs']->getValue('subscribe'));
+ }
+
+ /**
+ * Deletes messages from a mailbox.
+ *
+ * @param string $mailbox The name of the mailbox (UTF7-IMAP).
+ * @param array $indices The list of UIDs to delete.
+ *
+ * @return integer|boolean The number of messages deleted if successful,
+ * false if not.
+ */
+ public function deleteMessages($mailbox, $indices)
+ {
+ try {
+ $GLOBALS['imp_authentication'] = 'throw';
+ require_once dirname(__FILE__) . '/base.php';
+ } catch (Horde_Exception $e) {
+ return false;
+ }
+
+ $imp_message = IMP_Message::singleton();
+ return $imp_message->delete(array($mailbox => $indices), array('nuke' => true));
+ }
+
+ /**
+ * Copies messages to a mailbox.
+ *
+ * @param string $mailbox The name of the source mailbox (UTF7-IMAP).
+ * @param array $indices The list of UIDs to copy.
+ * @param string $target The name of the target mailbox (UTF7-IMAP).
+ *
+ * @return boolean True if successful, false if not.
+ */
+ public function copyMessages($mailbox, $indices, $target)
+ {
+ try {
+ $GLOBALS['imp_authentication'] = 'throw';
+ require_once dirname(__FILE__) . '/base.php';
+ } catch (Horde_Exception $e) {
+ return false;
+ }
+
+ $imp_message = IMP_Message::singleton();
+ return $imp_message->copy($target, 'copy', array($mailbox => $indices), true);
+ }
+
+ /**
+ * Moves messages to a mailbox.
+ *
+ * @param string $mailbox The name of the source mailbox (UTF7-IMAP).
+ * @param array $indices The list of UIDs to move.
+ * @param string $target The name of the target mailbox (UTF7-IMAP).
+ *
+ * @return boolean True if successful, false if not.
+ */
+ public function moveMessages($mailbox, $indices, $target)
+ {
+ try {
+ $GLOBALS['imp_authentication'] = 'throw';
+ require_once dirname(__FILE__) . '/base.php';
+ } catch (Horde_Exception $e) {
+ return false;
+ }
+
+ $imp_message = IMP_Message::singleton();
+ return $imp_message->copy($target, 'move', array($mailbox => $indices), true);
+ }
+
+ /**
+ * Flag messages.
+ *
+ * @param string $mailbox The name of the source mailbox (UTF7-IMAP).
+ * @param array $indices The list of UIDs to flag.
+ * @param array $flags The flags to set.
+ * @param boolean $set True to set flags, false to clear flags.
+ *
+ * @return boolean True if successful, false if not.
+ */
+ public function flagMessages($mailbox, $indices, $flags, $set)
+ {
+ try {
+ $GLOBALS['imp_authentication'] = 'throw';
+ require_once dirname(__FILE__) . '/base.php';
+ } catch (Horde_Exception $e) {
+ return false;
+ }
+
+ $imp_message = IMP_Message::singleton();
+ return $imp_message->flag($flags, 'move', array($mailbox => $indices), $set);
+ }
+
+ /**
+ * Return envelope information for the given list of indices.
+ *
+ * @param string $mailbox The name of the mailbox (UTF7-IMAP).
+ * @param array $indices The list of UIDs.
+ *
+ * @return array|boolean TODO if successful, false if not.
+ */
+ public function msgEnvelope($mailbox, $indices)
+ {
+ try {
+ $GLOBALS['imp_authentication'] = 'throw';
+ require_once dirname(__FILE__) . '/base.php';
+ } catch (Horde_Exception $e) {
+ return false;
+ }
+
+ return $GLOBALS['imp_imap']->ob->fetch($mailbox, array(Horde_Imap_Client::FETCH_ENVELOPE => true), array('ids' => $indices));
+ }
+
+ /**
+ * Perform a search query on the remote IMAP server.
+ *
+ * @param string $mailbox The name of the source
+ * mailbox (UTF7-IMAP).
+ * @param Horde_Imap_Client_Search_Query $query The query object.
+ *
+ * @return array|boolean The search results (UID list) or false.
+ */
+ public function searchMailbox($mailbox, $query)
+ {
+ try {
+ $GLOBALS['imp_authentication'] = 'throw';
+ require_once dirname(__FILE__) . '/base.php';
+ } catch (Horde_Exception $e) {
+ return false;
+ }
+
+ return $GLOBALS['imp_search']->runSearchQuery($query, $mailbox);
+ }
+
+ /**
+ * Returns the cache ID value for a mailbox
+ *
+ * @param string $mailbox The name of the source mailbox (UTF7-IMAP).
+ *
+ * @return string|boolean The cache ID value, or false if not
+ * authenticated.
+ */
+ public function mailboxCacheId($mailbox)
+ {
+ try {
+ $GLOBALS['imp_authentication'] = 'throw';
+ require_once dirname(__FILE__) . '/base.php';
+ } catch (Horde_Exception $e) {
+ return false;
+ }
+
+ return $GLOBALS['imp_imap']->ob->getCacheId($mailbox);
+ }
+
+ /**
+ * Returns information on the currently logged on IMAP server.
+ *
+ * @return mixed Returns null if the user has not authenticated into IMP
+ * yet Otherwise, an array with the following entries:
+ * <pre>
+ * 'hostspec' - (string) The server hostname.
+ * 'port' - (integer) The server port.
+ * 'protocol' - (string) Either 'imap' or 'pop'.
+ * 'secure' - (string) Either 'none', 'ssl', or 'tls'.
+ * </pre>
+ */
+ public function server()
+ {
+ try {
+ $GLOBALS['imp_authentication'] = 'throw';
+ require_once dirname(__FILE__) . '/base.php';
+ } catch (Horde_Exception $e) {
+ return null;
+ }
+
+ $imap_obj = unserialize($_SESSION['imp']['imap_ob']);
+ return array(
+ 'hostspec' => $imap_obj->getParam('hostspec'),
+ 'port' => $imap_obj->getParam('port'),
+ 'protocol' => $_SESSION['imp']['protocol'],
+ 'secure' => $imap_obj->getParam('secure')
+ );
+ }
+
+ /**
+ * Returns the list of favorite recipients.
+ *
+ * @param integer $limit Return this number of recipients.
+ * @param array $filter A list of messages types that should be returned.
+ * A value of null returns all message types.
+ *
+ * @return array A list with the $limit most favourite recipients.
+ */
+ public function favouriteRecipients($limit,
+ $filter = array('new', 'forward', 'reply', 'redirect'))
+ {
+ $GLOBALS['imp_authentication'] = 'none';
+ require_once dirname(__FILE__) . '/base.php';
+
+ if ($GLOBALS['conf']['sentmail']['driver'] != 'none') {
+ $sentmail = IMP_Sentmail::factory();
+ return $sentmail->favouriteRecipients($limit, $filter);
+ }
+
+ return array();
+ }
+
+}
+++ /dev/null
-<?php
-/**
- * IMP external API interface.
- *
- * This file defines IMP's external API interface. Other applications
- * can interact with IMP through this API.
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
- *
- * @package IMP
- */
-
-$_services = array(
- 'perms' => array(
- 'args' => array(),
- 'type' => '{urn:horde}stringArray'
- ),
-
- 'authCredentials' => array(
- 'args' => array(),
- 'type' => '{urn:horde}hashHash'
- ),
-
- 'compose' => array(
- 'args' => array(
- 'args' => '{urn:horde}hash',
- 'extra' => '{urn:horde}hash'
- ),
- 'type' => 'string'
- ),
-
- 'batchCompose' => array(
- 'args' => array(
- 'args' => '{urn:horde}hash',
- 'extra' => '{urn:horde}hash'
- ),
- 'type' => 'string'
- ),
-
- 'folderlist' => array(
- 'args' => array(),
- 'type' => '{urn:horde}stringArray'
- ),
-
- 'createFolder' => array(
- 'args' => array('folder' => 'string'),
- 'type' => 'string'
- ),
-
- 'deleteMessages' => array(
- 'args' => array(
- 'mailbox' => 'string',
- 'indices' => '{urn:horde}integerArray'
- ),
- 'type' => 'integer'
- ),
-
- 'copyMessages' => array(
- 'args' => array(
- 'mailbox' => 'string',
- 'indices' => '{urn:horde}integerArray',
- 'target' => 'string'
- ),
- 'type' => 'boolean'
- ),
-
- 'moveMessages' => array(
- 'args' => array(
- 'mailbox' => 'string',
- 'indices' => '{urn:horde}integerArray',
- 'target' => 'string'
- ),
- 'type' => 'boolean'
- ),
-
- 'flagMessages' => array(
- 'args' => array(
- 'mailbox' => 'string',
- 'indices' => '{urn:horde}integerArray',
- 'flags' => '{urn:horde}stringArray',
- 'set' => 'boolean'
- ),
- 'type' => 'boolean'
- ),
-
- 'msgEnvelope' => array(
- 'args' => array(
- 'mailbox' => 'string',
- 'indices' => '{urn:horde}integerArray'
- ),
- 'type' => '{urn:horde}hashHash'
- ),
-
- 'searchMailbox' => array(
- 'args' => array(
- 'mailbox' => 'string',
- 'query' => 'object'
- ),
- 'type' => '{urn:horde}integerArray'
- ),
-
- 'mailboxCacheId' => array(
- 'args' => array(
- 'mailbox' => 'string'
- ),
- 'type' => 'string'
- ),
-
- 'server' => array(
- 'args' => array(),
- 'type' => '{urn:horde}hashHash'
- ),
-
- 'favouriteRecipients' => array(
- 'args' => array(
- 'limit' => 'int'
- ),
- 'type' => '{urn:horde}stringArray'
- ),
-
- 'changeLanguage' => array(
- 'args' => array(),
- 'type' => 'boolean'
- ),
-
- /* Indicate that mobile view is available. */
- 'mobileView' => array(
- 'args' => array(),
- 'type' => 'boolean'
- ),
-
- /* Cache display method. */
- 'cacheOutput' => array(
- 'args' => array(
- '{urn:horde}hashHash'
- ),
- 'type' => '{urn:horde}hashHash'
- ),
-
- /* Horde_Auth_Application methods. */
- 'authLoginParams' => array(
- 'args' => array(),
- 'checkperms' => false,
- 'type' => '{urn:horde}hashHash'
- ),
-
- 'authAuthenticate' => array(
- 'args' => array(
- 'userID' => 'string',
- 'credentials' => '{urn:horde}hash',
- 'params' => '{urn:horde}hash'
- ),
- 'checkperms' => false,
- 'type' => 'boolean'
- ),
-
- 'authAuthenticateCallback' => array(
- 'args' => array(),
- 'checkperms' => false
- ),
-
- 'authTransparent' => array(
- 'args' => array(),
- 'checkperms' => false,
- 'type' => 'boolean'
- )
-);
-
-/* Only available if admin config is set for this server/login. */
-if (!empty($_SESSION['imp']['admin'])) {
- $_services = array_merge($_services, array(
- 'authAddUser' => array(
- 'args' => array(
- 'userId' => 'string',
- 'credentials' => '{urn:horde}stringArray'
- )
- ),
- 'authRemoveUser' => array(
- 'args' => array(
- 'userId' => 'string'
- )
- ),
- 'authUserList' => array(
- 'type' => '{urn:horde}stringArray'
- )
- ));
-}
-
-/**
- * Returns a list of available permissions.
- */
-function _imp_perms()
-{
- return array(
- 'tree' => array(
- 'imp' => array(
- 'create_folders' => false,
- 'max_folders' => false,
- 'max_recipients' => false,
- 'max_timelimit' => false,
- ),
- ),
- 'title' => array(
- 'imp:create_folders' => _("Allow Folder Creation?"),
- 'imp:max_folders' => _("Maximum Number of Folders"),
- 'imp:max_recipients' => _("Maximum Number of Recipients per Message"),
- 'imp:max_timelimit' => _("Maximum Number of Recipients per Time Period"),
- ),
- 'type' => array(
- 'imp:create_folders' => 'boolean',
- 'imp:max_folders' => 'int',
- 'imp:max_recipients' => 'int',
- 'imp:max_timelimit' => 'int',
- )
- );
-}
-
-/**
- * Returns a list of authentication credentials, i.e. server settings that can
- * be specified by the user on the login screen.
- *
- * @return array A hash with credentials, suited for the preferences
- * interface.
- */
-function _imp_authCredentials()
-{
- $app_name = $GLOBALS['registry']->get('name');
-
- $servers = IMP_Imap::loadServerConfig();
- $server_list = array();
- foreach ($servers as $key => $val) {
- $server_list[$key] = $val['name'];
- }
- reset($server_list);
-
- $credentials = array(
- 'username' => array(
- 'desc' => sprintf(_("%s for %s"), _("Username"), $app_name),
- 'type' => 'text'
- ),
- 'password' => array(
- 'desc' => sprintf(_("%s for %s"), _("Password"), $app_name),
- 'type' => 'password'
- ),
- 'server' => array(
- 'desc' => sprintf(_("%s for %s"), _("Server"), $app_name),
- 'type' => 'enum',
- 'enum' => $server_list,
- 'value' => key($server_list)
- )
- );
-
- return $credentials;
-}
-
-/**
- * Returns a compose window link.
- *
- * @param string|array $args List of arguments to pass to compose.php.
- * If this is passed in as a string, it will be
- * parsed as a toaddress?subject=foo&cc=ccaddress
- * (mailto-style) string.
- * @param array $extra Hash of extra, non-standard arguments to pass to
- * compose.php.
- *
- * @return string The link to the message composition screen.
- */
-function _imp_compose($args = array(), $extra = array())
-{
- $link = _imp_batchCompose(array($args), array($extra));
- return $link[0];
-}
-
-/**
- * Return a list of compose window links.
- *
- * @param mixed $args List of lists of arguments to pass to compose.php. If
- * the lists are passed in as strings, they will be parsed
- * as toaddress?subject=foo&cc=ccaddress (mailto-style)
- * strings.
- * @param array $extra List of hashes of extra, non-standard arguments to pass
- * to compose.php.
- *
- * @return string The list of links to the message composition screen.
- */
-function _imp_batchCompose($args = array(), $extra = array())
-{
- $GLOBALS['imp_authentication'] = 'none';
- require_once dirname(__FILE__) . '/base.php';
-
- $links = array();
- foreach ($args as $i => $arg) {
- $links[$i] = IMP::composeLink($arg, !empty($extra[$i]) ? $extra[$i] : array());
- }
-
- return $links;
-}
-
-/**
- * Returns the list of folders.
- *
- * @return array The list of IMAP folders or false if not available.
- */
-function _imp_folderlist()
-{
- try {
- $GLOBALS['imp_authentication'] = 'throw';
- require_once dirname(__FILE__) . '/base.php';
- } catch (Horde_Exception $e) {
- return false;
- }
-
- $imp_folder = IMP_Folder::singleton();
- return $imp_folder->flist();
-}
-
-/**
- * Creates a new folder.
- *
- * @param string $folder The name of the folder to create (UTF7-IMAP).
- *
- * @return string The full folder name created or false on failure.
- */
-function _imp_createFolder($folder)
-{
- try {
- $GLOBALS['imp_authentication'] = 'throw';
- require_once dirname(__FILE__) . '/base.php';
- } catch (Horde_Exception $e) {
- return false;
- }
-
- $imp_folder = IMP_Folder::singleton();
- return $imp_folder->create(IMP::appendNamespace($folder), $GLOBALS['prefs']->getValue('subscribe'));
-}
-
-/**
- * Deletes messages from a mailbox.
- *
- * @param string $mailbox The name of the mailbox (UTF7-IMAP).
- * @param array $indices The list of UIDs to delete.
- *
- * @return integer|boolean The number of messages deleted if successful,
- * false if not.
- */
-function _imp_deleteMessages($mailbox, $indices)
-{
- try {
- $GLOBALS['imp_authentication'] = 'throw';
- require_once dirname(__FILE__) . '/base.php';
- } catch (Horde_Exception $e) {
- return false;
- }
-
- $imp_message = IMP_Message::singleton();
- return $imp_message->delete(array($mailbox => $indices), array('nuke' => true));
-}
-
-/**
- * Copies messages to a mailbox.
- *
- * @param string $mailbox The name of the source mailbox (UTF7-IMAP).
- * @param array $indices The list of UIDs to copy.
- * @param string $target The name of the target mailbox (UTF7-IMAP).
- *
- * @return boolean True if successful, false if not.
- */
-function _imp_copyMessages($mailbox, $indices, $target)
-{
- try {
- $GLOBALS['imp_authentication'] = 'throw';
- require_once dirname(__FILE__) . '/base.php';
- } catch (Horde_Exception $e) {
- return false;
- }
-
- $imp_message = IMP_Message::singleton();
- return $imp_message->copy($target, 'copy', array($mailbox => $indices), true);
-}
-
-/**
- * Moves messages to a mailbox.
- *
- * @param string $mailbox The name of the source mailbox (UTF7-IMAP).
- * @param array $indices The list of UIDs to move.
- * @param string $target The name of the target mailbox (UTF7-IMAP).
- *
- * @return boolean True if successful, false if not.
- */
-function _imp_moveMessages($mailbox, $indices, $target)
-{
- try {
- $GLOBALS['imp_authentication'] = 'throw';
- require_once dirname(__FILE__) . '/base.php';
- } catch (Horde_Exception $e) {
- return false;
- }
-
- $imp_message = IMP_Message::singleton();
- return $imp_message->copy($target, 'move', array($mailbox => $indices), true);
-}
-
-/**
- * Flag messages.
- *
- * @param string $mailbox The name of the source mailbox (UTF7-IMAP).
- * @param array $indices The list of UIDs to flag.
- * @param array $flags The flags to set.
- * @param boolean $set True to set flags, false to clear flags.
- *
- * @return boolean True if successful, false if not.
- */
-function _imp_flagMessages($mailbox, $indices, $flags, $set)
-{
- try {
- $GLOBALS['imp_authentication'] = 'throw';
- require_once dirname(__FILE__) . '/base.php';
- } catch (Horde_Exception $e) {
- return false;
- }
-
- $imp_message = IMP_Message::singleton();
- return $imp_message->flag($flags, 'move', array($mailbox => $indices), $set);
-}
-
-/**
- * Return envelope information for the given list of indices.
- *
- * @param string $mailbox The name of the mailbox (UTF7-IMAP).
- * @param array $indices The list of UIDs.
- *
- * @return array|boolean TODO if successful, false if not.
- */
-function _imp_msgEnvelope($mailbox, $indices)
-{
- try {
- $GLOBALS['imp_authentication'] = 'throw';
- require_once dirname(__FILE__) . '/base.php';
- } catch (Horde_Exception $e) {
- return false;
- }
-
- return $GLOBALS['imp_imap']->ob->fetch($mailbox, array(Horde_Imap_Client::FETCH_ENVELOPE => true), array('ids' => $indices));
-}
-
-/**
- * Perform a search query on the remote IMAP server.
- *
- * @param string $mailbox The name of the source mailbox
- * (UTF7-IMAP).
- * @param Horde_Imap_Client_Search_Query $query The query object.
- *
- * @return array|boolean The search results (UID list) or false.
- */
-function _imp_searchMailbox($mailbox, $query)
-{
- try {
- $GLOBALS['imp_authentication'] = 'throw';
- require_once dirname(__FILE__) . '/base.php';
- } catch (Horde_Exception $e) {
- return false;
- }
-
- return $GLOBALS['imp_search']->runSearchQuery($query, $mailbox);
-}
-
-/**
- * Returns the cache ID value for a mailbox
- *
- * @param string $mailbox The name of the source mailbox (UTF7-IMAP).
- *
- * @return string|boolean The cache ID value, or false if not authenticated.
- */
-function _imp_mailboxCacheId($mailbox)
-{
- try {
- $GLOBALS['imp_authentication'] = 'throw';
- require_once dirname(__FILE__) . '/base.php';
- } catch (Horde_Exception $e) {
- return false;
- }
-
- return $GLOBALS['imp_imap']->ob->getCacheId($mailbox);
-}
-
-/**
- * Returns information on the currently logged on IMAP server.
- *
- * @return mixed Returns null if the user has not authenticated into IMP yet.
- * Otherwise, an array with the following entries:
- * <pre>
- * 'hostspec' - (string) The server hostname.
- * 'port' - (integer) The server port.
- * 'protocol' - (string) Either 'imap' or 'pop'.
- * 'secure' - (string) Either 'none', 'ssl', or 'tls'.
- * </pre>
- */
-function _imp_server()
-{
- try {
- $GLOBALS['imp_authentication'] = 'throw';
- require_once dirname(__FILE__) . '/base.php';
- } catch (Horde_Exception $e) {
- return null;
- }
-
- $imap_obj = unserialize($_SESSION['imp']['imap_ob']);
- return array(
- 'hostspec' => $imap_obj->getParam('hostspec'),
- 'port' => $imap_obj->getParam('port'),
- 'protocol' => $_SESSION['imp']['protocol'],
- 'secure' => $imap_obj->getParam('secure')
- );
-}
-
-/**
- * Returns the list of favorite recipients.
- *
- * @param integer $limit Return this number of recipients.
- * @param array $filter A list of messages types that should be returned.
- * A value of null returns all message types.
- *
- * @return array A list with the $limit most favourite recipients.
- */
-function _imp_favouriteRecipients($limit,
- $filter = array('new', 'forward', 'reply', 'redirect'))
-{
- $GLOBALS['imp_authentication'] = 'none';
- require_once dirname(__FILE__) . '/base.php';
-
- if ($GLOBALS['conf']['sentmail']['driver'] != 'none') {
- $sentmail = IMP_Sentmail::factory();
- return $sentmail->favouriteRecipients($limit, $filter);
- }
-
- return array();
-}
-
-/**
- * Performs tasks necessary when the language is changed during the session.
- */
-function _imp_changeLanguage()
-{
- try {
- $GLOBALS['imp_authentication'] = 'throw';
- require_once dirname(__FILE__) . '/base.php';
- } catch (Horde_Exception $e) {
- return;
- }
-
- $imp_folder = IMP_Folder::singleton();
- $imp_folder->clearFlistCache();
- $imaptree = IMP_Imap_Tree::singleton();
- $imaptree->init();
- $GLOBALS['imp_search']->initialize(true);
-}
-
-/**
- * Application-specific cache output driver.
- *
- * @param array $params A list of params needed (USED: 'id').
- *
- * @return array See Horde::getCacheUrl().
- * @throws Horde_Exception
- */
-function _imp_cacheOutput($params)
-{
- try {
- $GLOBALS['imp_authentication'] = 'throw';
- require_once dirname(__FILE__) . '/base.php';
- } catch (Horde_Exception $e) {
- throw new Horde_Exception('No cache data available');
- }
-
- switch ($params['id']) {
- case 'fckeditor':
- return array(
- 'data' =>
- 'FCKConfig.ToolbarSets["ImpToolbar"] = ' . $GLOBALS['prefs']->getValue('fckeditor_buttons') . ";\n" .
- /* To more closely match "normal" textarea behavior, send
- * send <BR> on enter instead of <P>. */
- "FCKConfig.EnterMode = 'br';\n" .
- 'FCKConfig.ShiftEnterMode = \'p\';',
- 'type' => 'text/javascript'
- );
- }
-}
-
-/**
- * Indicate that IMP supports a mobile view.
- *
- * @return boolean True.
- */
-function _imp_mobileView()
-{
- return true;
-}
-
-/**
- * Return login parameters used on the login page.
- *
- * @return array TODO
- */
-function _imp_authLoginParams()
-{
- $params = array();
-
- if ($GLOBALS['conf']['server']['server_list'] == 'shown') {
- $servers = IMP_Imap::loadServerConfig();
- $server_list = array();
- $selected = Horde_Util::getFormData('imp_server_key', IMP_Auth::getAutoLoginServer());
- foreach ($servers as $key => $val) {
- $server_list[$key] = array(
- 'name' => $val['name'],
- 'selected' => ($selected == $key)
- );
- }
- $params['imp_server_key'] = array(
- 'label' => _("Server"),
- 'type' => 'select',
- 'value' => $server_list
- );
- }
-
- /* If dimp/mimp are available, show selection of alternate views. */
- if (!empty($GLOBALS['conf']['user']['select_view'])) {
- $views = array();
- if (!($view_cookie = Horde_Util::getFormData('imp_select_view'))) {
- if (isset($_COOKIE['default_imp_view'])) {
- $view_cookie = $_COOKIE['default_imp_view'];
- } else {
- $browser = Horde_Browser::singleton();
- $view_cookie = $browser->isMobile() ? 'mimp' : 'imp';
- }
- }
-
- $params['imp_select_view'] = array(
- 'label' => _("Mode"),
- 'type' => 'select',
- 'value' => array(
- 'imp' => array(
- 'name' => _("Traditional"),
- 'selected' => $view_cookie == 'imp'
- ),
- 'dimp' => array(
- 'hidden' => true,
- 'name' => _("Dynamic")
- // Dimp selected is handled by javascript (dimp_sel)
- ),
- 'mimp' => array(
- 'name' => _("Minimalist"),
- 'selected' => $view_cookie == 'mimp'
- )
- )
- );
- }
-
- return array(
- 'js_code' => array(
- 'ImpLogin.dimp_sel=' . intval($view_cookie == 'dimp'),
- 'ImpLogin.server_key_error=' . Horde_Serialize::serialize(_("Please choose a mail server."), Horde_Serialize::JSON)
- ),
- 'js_files' => array(
- array('login.js', 'imp')
- ),
- 'params' => $params
- );
-}
-
-/**
- * Tries to authenticate with the mail server and create a mail session.
- *
- * @param string $userId The username of the user.
- * @param array $credentials Credentials of the user. Allowed keys:
- * 'imp_select_view', 'imp_server_key',
- * 'password'.
- *
- * @throws Horde_Auth_Exception
- */
-function _imp_authAuthenticate($userId, $credentials)
-{
- $GLOBALS['imp_authentication'] = 'none';
- require_once dirname(__FILE__) . '/base.php';
-
- $new_session = IMP_Auth::authenticate(array(
- 'password' => $credentials['password'],
- 'server' => empty($credentials['imp_server_key']) ? IMP_Auth::getAutoLoginServer() : $credentials['imp_server_key'],
- 'userId' => $userId
- ));
-
- if ($new_session) {
- $_SESSION['imp']['cache']['select_view'] = empty($credentials['imp_select_view']) ? '' : $credentials['imp_select_view'];
-
- /* Set the Horde ID, since it may have been altered by the 'realm'
- * setting. */
- $credentials['auth_ob']->setCredential('userId', $_SESSION['imp']['uniquser']);
- }
-}
-
-/**
- * Tries to transparently authenticate with the mail server and create a mail
- * session.
- *
- * @return boolean Whether transparent login is supported.
- * @throws Horde_Auth_Exception
- */
-function _imp_authTransparent()
-{
- /* Transparent auth is a bit goofy - we most likely have reached this
- * code from the pushApp() call in base.php already. As such, some of the
- * IMP init has not yet been done, so we need to do the necessary init
- * here or else things will fail in IMP_Auth. */
- $GLOBALS['imp_authentication'] = 'none';
- require_once dirname(__FILE__) . '/base.php';
- IMP::initialize();
- return IMP_Auth::transparent();
-}
-
-/**
- * Does necessary authentication tasks reliant on a full IMP environment.
- *
- * @throws Horde_Auth_Exception
- */
-function _imp_authAuthenticateCallback()
-{
- if (Horde_Auth::getAuth()) {
- require_once dirname(__FILE__) . '/base.php';
- IMP_Auth::authenticateCallback();
- }
-}
-
-/**
- * Adds a user defined by authentication credentials.
- *
- * @param string $userId The userId to add.
- * @param array $credentials An array of login credentials. For IMAP,
- * this must contain a password entry.
- *
- * @throws Horde_Exception
- */
-function _imp_authAddUser($userId, $credentials)
-{
- if (($params = _imp_server()) === null) {
- return;
- }
-
- $params = array_merge($params, $_SESSION['imp']['admin']['params']);
- if (isset($params['admin_password'])) {
- $params['admin_password'] = Horde_Secret::read(Horde_Secret::getKey('imp'), $params['admin_password']);
- }
- $auth = Horde_Auth::singleton('imap', $params);
- $auth->addUser($userId, $credentials);
-}
-
-/**
- * Deletes a user defined by authentication credentials.
- *
- * @param string $userId The userId to delete.
- *
- * @throws Horde_Exception
- */
-function _imp_authRemoveUser($userId)
-{
- if (($params = _imp_server()) === null) {
- return;
- }
-
- $params = array_merge($params, $_SESSION['imp']['admin']['params']);
- if (isset($params['admin_password'])) {
- $params['admin_password'] = Horde_Secret::read(Horde_Secret::getKey('imp'), $params['admin_password']);
- }
- $auth = Horde_Auth::singleton('imap', $params);
- $auth->removeUser($userId);
-}
-
-/**
- * Lists all users in the system.
- *
- * @return array The array of userIds.
- * @throws Horde_Exception
- */
-function _imp_authUserList()
-{
- if (($params = _imp_server()) === null) {
- return;
- }
-
- $params = array_merge($params, $_SESSION['imp']['admin']['params']);
- if (isset($params['admin_password'])) {
- $params['admin_password'] = Horde_Secret::read(Horde_Secret::getKey('imp'), $params['admin_password']);
- }
- $auth = Horde_Auth::singleton('imap', $params);
- return $auth->listUsers();
-}
+++ /dev/null
-<?php define('IMP_VERSION', 'H4 (5.0-git)') ?>
/* IMP version. */
$module = 'IMP';
-require_once IMP_BASE .'/lib/version.php';
-$module_version = IMP_VERSION;
+require_once dirname(__FILE__) . '/lib/Api.php';
+$api = new IMP_Api();
+$module_version = $api->version;
require TEST_TEMPLATES . 'header.inc';
require TEST_TEMPLATES . 'version.inc';
--- /dev/null
+<?php
+/**
+ * Ingo external API interface.
+ *
+ * This file defines Ingo's external API interface. Other applications
+ * can interact with Ingo through this API.
+ *
+ * See the enclosed file LICENSE for license information (ASL). If you
+ * did not receive this file, see http://www.horde.org/licenses/asl.php.
+ */
+class Ingo_Api extends Horde_Registry_Api
+{
+ /**
+ * The application's version.
+ *
+ * @var string
+ */
+ public $version = 'H4 (2.0-git)';
+
+ /**
+ * The services provided by this application.
+ * TODO: Describe structure.
+ *
+ * @var array
+ */
+ public $services = array(
+ 'perms' => array(
+ 'args' => array(),
+ 'type' => '{urn:horde}stringArray'
+ ),
+
+ 'removeUserData' => array(
+ 'args' => array('user' => 'string'),
+ 'type' => 'boolean'
+ ),
+
+ 'blacklistFrom' => array(
+ 'args' => array('addresses' => '{urn:horde}stringArray'),
+ 'type' => 'boolean',
+ ),
+
+ 'showBlacklist' => array(
+ 'link' => '%application%/blacklist.php',
+ ),
+
+ 'whitelistFrom' => array(
+ 'args' => array('addresses' => '{urn:horde}stringArray'),
+ 'type' => 'boolean',
+ ),
+
+ 'showWhitelist' => array(
+ 'link' => '%application%/whitelist.php',
+ ),
+
+ 'canApplyFilters' => array(
+ 'args' => array(),
+ 'type' => 'boolean',
+ ),
+
+ 'applyFilters' => array(
+ 'args' => array('params' => '{urn:horde}stringArray'),
+ 'type' => 'boolean',
+ ),
+
+ 'showFilters' => array(
+ 'link' => '%application%/filters.php',
+ ),
+
+ 'showVacation' => array(
+ 'link' => '%application%/vacation.php',
+ ),
+
+ 'setVacation' => array(
+ 'args' => array('info' => '{urn:horde}stringArray'),
+ 'type' => 'boolean',
+ ),
+
+ 'disableVacation' => array(
+ 'args' => array(),
+ 'type' => 'boolean',
+ ),
+ );
+
+ /**
+ * Returns a list of available permissions.
+ *
+ * @return array An array describing all available permissions.
+ */
+ public function perms()
+ {
+ return array(
+ 'title' => array(
+ 'ingo:allow_rules' => _("Allow Rules"),
+ 'ingo:max_rules' => _("Maximum Number of Rules")
+ ),
+ 'tree' => array(
+ 'ingo' => array(
+ 'allow_rules' => false,
+ 'max_rules' => false
+ )
+ ),
+ 'type' => array(
+ 'ingo:allow_rules' => 'boolean',
+ 'ingo:max_rules' => 'int'
+ )
+ );
+ }
+
+ /**
+ * Removes user data.
+ *
+ * @param string $user Name of user to remove data for.
+ *
+ * @return mixed true on success | PEAR_Error on failure
+ */
+ public function removeUserData($user)
+ {
+ if (!Horde_Auth::isAdmin() && $user != Horde_Auth::getAuth()) {
+ return PEAR::raiseError(_("You are not allowed to remove user data."));
+ }
+
+ require_once dirname(__FILE__) . '/../lib/base.php';
+
+ /* Remove all filters/rules owned by the user. */
+ $result = $GLOBALS['ingo_storage']->removeUserData($user);
+ if (is_a($result, 'PEAR_Error')) {
+ Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return $result;
+ }
+
+ /* Now remove all shares owned by the user. */
+ if (!empty($GLOBALS['ingo_shares'])) {
+ /* Get the user's default share. */
+ $share = $GLOBALS['ingo_shares']->getShare($user);
+ if (is_a($share, 'PEAR_Error')) {
+ Horde::logMessage($share, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return $share;
+ } else {
+ $result = $GLOBALS['ingo_shares']->removeShare($share);
+ if (is_a($result, 'PEAR_Error')) {
+ Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return $result;
+ }
+ }
+
+ /* Get a list of all shares this user has perms to and remove the
+ * perms. */
+ $shares = $GLOBALS['ingo_shares']->listShares($user);
+ if (is_a($shares, 'PEAR_Error')) {
+ Horde::logMessage($shares, __FILE__, __LINE__, PEAR_LOG_ERR);
+ }
+ foreach ($shares as $share) {
+ $share->removeUser($user);
+ }
+
+ /* Get a list of all shares this user owns and has perms to delete
+ * and remove them. */
+ $shares = $GLOBALS['ingo_shares']->listShares($user, PERMS_DELETE, $user);
+ if (is_a($shares, 'PEAR_Error')) {
+ Horde::logMessage($shares, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return $shares;
+ }
+ foreach ($shares as $share) {
+ $GLOBALS['ingo_shares']->removeShare($share);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Add addresses to the blacklist.
+ *
+ * @param string $addresses The addresses to add to the blacklist.
+ */
+ public function blacklistFrom($addresses)
+ {
+ require_once dirname(__FILE__) . '/../lib/base.php';
+ if (!empty($GLOBALS['ingo_shares'])) {
+ $_SESSION['ingo']['current_share'] = $signature;
+ }
+
+ /* Check for '@' entries in $addresses - this would call all mail to
+ * be blacklisted which is most likely not what is desired. */
+ $addresses = array_unique($addresses);
+ $key = array_search('@', $addresses);
+ if ($key !== false) {
+ unset($addresses[$key]);
+ }
+
+ if (!empty($addresses)) {
+ $blacklist = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_BLACKLIST);
+ $ret = $blacklist->setBlacklist(array_merge($blacklist->getBlacklist(), $addresses));
+ if (is_a($ret, 'PEAR_Error')) {
+ $GLOBALS['notification']->push($ret, $ret->getCode());
+ } else {
+ $GLOBALS['ingo_storage']->store($blacklist);
+ Ingo::updateScript();
+ foreach ($addresses as $from) {
+ $GLOBALS['notification']->push(sprintf(_("The address \"%s\" has been added to your blacklist."), $from));
+ }
+ }
+ }
+ }
+
+ /**
+ * Add addresses to the whitelist.
+ *
+ * @param string $addresses The addresses to add to the whitelist.
+ */
+ public function whitelistFrom($addresses)
+ {
+ require_once dirname(__FILE__) . '/../lib/base.php';
+ if (!empty($GLOBALS['ingo_shares'])) {
+ $_SESSION['ingo']['current_share'] = $signature;
+ }
+
+ $whitelist = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_WHITELIST);
+ $ret = $whitelist->setWhitelist(array_merge($whitelist->getWhitelist(), $addresses));
+ if (is_a($ret, 'PEAR_Error')) {
+ $GLOBALS['notification']->push($ret, $ret->getCode());
+ } else {
+ $GLOBALS['ingo_storage']->store($whitelist);
+ Ingo::updateScript();
+ foreach ($addresses as $from) {
+ $GLOBALS['notification']->push(sprintf(_("The address \"%s\" has been added to your whitelist."), $from));
+ }
+ }
+ }
+
+ /**
+ * Can this driver perform on-demand filtering?
+ *
+ * @return boolean True if perform() is available, false if not.
+ */
+ public function canApplyFilters()
+ {
+ require_once dirname(__FILE__) . '/../lib/base.php';
+
+ $ingo_script = Ingo::loadIngoScript();
+ return $ingo_script
+ ? $ingo_script->performAvailable()
+ : false;
+ }
+
+ /**
+ * Perform the filtering specified in the rules.
+ *
+ * @param array $params The parameter array.
+ *
+ * @return boolean True if filtering was performed, false if not.
+ */
+ public function applyFilters($params = array())
+ {
+ require_once dirname(__FILE__) . '/../lib/base.php';
+ if (!empty($GLOBALS['ingo_shares'])) {
+ $_SESSION['ingo']['current_share'] = $signature;
+ }
+
+ $ingo_script = Ingo::loadIngoScript();
+ return $ingo_script
+ ? $ingo_script->perform($params)
+ : false;
+ }
+
+ /**
+ * Set vacation
+ *
+ * @param array $info Vacation details.
+ *
+ * @return boolean True on success.
+ */
+ public function setVacation($info)
+ {
+ require_once dirname(__FILE__) . '/../lib/base.php';
+ if (!empty($GLOBALS['ingo_shares'])) {
+ $_SESSION['ingo']['current_share'] = $signature;
+ }
+
+ if (empty($info)) {
+ return true;
+ }
+
+ /* Get vacation filter. */
+ $filters = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_FILTERS);
+ $vacation_rule_id = $filters->findRuleId(Ingo_Storage::ACTION_VACATION);
+
+ /* Set vacation object and rules. */
+ $vacation = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_VACATION);
+
+ /* Make sure we have at least one address. */
+ if (empty($info['addresses'])) {
+ require_once 'Horde/Identity.php';
+ $identity = Identity::singleton('none');
+ $info['addresses'] = implode("\n", $identity->getAll('from_addr'));
+ /* Remove empty lines. */
+ $info['addresses'] = preg_replace('/\n+/', "\n", $info['addresses']);
+ if (empty($addresses)) {
+ $info['addresses'] = Horde_Auth::getAuth();
+ }
+ }
+
+ $vacation->setVacationAddresses($addresses);
+
+ if (isset($info['days'])) {
+ $vacation->setVacationDays($info['days']);
+ }
+ if (isset($info['excludes'])) {
+ $vacation->setVacationExcludes($info['excludes']);
+ }
+ if (isset($info['ignorelist'])) {
+ $vacation->setVacationIgnorelist(($info['ignorelist'] == 'on'));
+ }
+ if (isset($info['reason'])) {
+ $vacation->setVacationReason($info['reason']);
+ }
+ if (isset($info['subject'])) {
+ $vacation->setVacationSubject($info['subject']);
+ }
+ if (isset($info['start'])) {
+ $vacation->setVacationStart($info['start']);
+ }
+ if (isset($info['end'])) {
+ $vacation->setVacationEnd($info['end']);
+ }
+
+ $filters->ruleEnable($vacation_rule_id);
+ $result = $GLOBALS['ingo_storage']->store($filters);
+ if (!is_a($result, 'PEAR_Error')) {
+ if ($GLOBALS['prefs']->getValue('auto_update')) {
+ Ingo::updateScript();
+ }
+
+ /* Update the timestamp for the rules. */
+ $_SESSION['ingo']['change'] = time();
+ }
+
+ return $result;
+ }
+
+ /**
+ * Disable vacation
+ *
+ * @return boolean True on success.
+ */
+ public function disableVacation()
+ {
+ require_once dirname(__FILE__) . '/../lib/base.php';
+ if (!empty($GLOBALS['ingo_shares'])) {
+ $_SESSION['ingo']['current_share'] = $signature;
+ }
+
+ /* Get vacation filter. */
+ $filters = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_FILTERS);
+ $vacation_rule_id = $filters->findRuleId(Ingo_Storage::ACTION_VACATION);
+
+ $filters->ruleDisable($vacation_rule_id);
+ $result = $GLOBALS['ingo_storage']->store($filters);
+ if (!is_a($result, 'PEAR_Error')) {
+ if ($GLOBALS['prefs']->getValue('auto_update')) {
+ Ingo::updateScript();
+ }
+
+ /* Update the timestamp for the rules. */
+ $_SESSION['ingo']['change'] = time();
+ }
+
+ return $result;
+ }
+
+}
+++ /dev/null
-<?php
-/**
- * Ingo external API interface.
- *
- * This file defines Ingo's external API interface. Other applications
- * can interact with Ingo through this API.
- *
- * See the enclosed file LICENSE for license information (ASL). If you
- * did not receive this file, see http://www.horde.org/licenses/asl.php.
- */
-
-$_services['perms'] = array(
- 'args' => array(),
- 'type' => '{urn:horde}stringArray');
-
-$_services['removeUserData'] = array(
- 'args' => array('user' => 'string'),
- 'type' => 'boolean'
-);
-
-$_services['blacklistFrom'] = array(
- 'args' => array('addresses' => '{urn:horde}stringArray'),
- 'type' => 'boolean',
-);
-
-$_services['showBlacklist'] = array(
- 'link' => '%application%/blacklist.php',
-);
-
-$_services['whitelistFrom'] = array(
- 'args' => array('addresses' => '{urn:horde}stringArray'),
- 'type' => 'boolean',
-);
-
-$_services['showWhitelist'] = array(
- 'link' => '%application%/whitelist.php',
-);
-
-$_services['canApplyFilters'] = array(
- 'args' => array(),
- 'type' => 'boolean',
-);
-
-$_services['applyFilters'] = array(
- 'args' => array('params' => '{urn:horde}stringArray'),
- 'type' => 'boolean',
-);
-
-$_services['showFilters'] = array(
- 'link' => '%application%/filters.php',
-);
-
-$_services['showVacation'] = array(
- 'link' => '%application%/vacation.php',
-);
-
-$_services['setVacation'] = array(
- 'args' => array('info' => '{urn:horde}stringArray'),
- 'type' => 'boolean',
-);
-
-$_services['disableVacation'] = array(
- 'args' => array(),
- 'type' => 'boolean',
-);
-
-/**
- * Returns a list of available permissions.
- *
- * @return array An array describing all available permissions.
- */
-function _ingo_perms()
-{
- return array(
- 'title' => array(
- 'ingo:allow_rules' => _("Allow Rules"),
- 'ingo:max_rules' => _("Maximum Number of Rules")
- ),
- 'tree' => array(
- 'ingo' => array(
- 'allow_rules' => false,
- 'max_rules' => false
- )
- ),
- 'type' => array(
- 'ingo:allow_rules' => 'boolean',
- 'ingo:max_rules' => 'int'
- )
- );
-}
-
-/**
- * Removes user data.
- *
- * @param string $user Name of user to remove data for.
- *
- * @return mixed true on success | PEAR_Error on failure
- */
-function _ingo_removeUserData($user)
-{
- if (!Horde_Auth::isAdmin() && $user != Horde_Auth::getAuth()) {
- return PEAR::raiseError(_("You are not allowed to remove user data."));
- }
-
- require_once dirname(__FILE__) . '/../lib/base.php';
-
- /* Remove all filters/rules owned by the user. */
- $result = $GLOBALS['ingo_storage']->removeUserData($user);
- if (is_a($result, 'PEAR_Error')) {
- Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
- return $result;
- }
-
- /* Now remove all shares owned by the user. */
- if (!empty($GLOBALS['ingo_shares'])) {
- /* Get the user's default share. */
- $share = $GLOBALS['ingo_shares']->getShare($user);
- if (is_a($share, 'PEAR_Error')) {
- Horde::logMessage($share, __FILE__, __LINE__, PEAR_LOG_ERR);
- return $share;
- } else {
- $result = $GLOBALS['ingo_shares']->removeShare($share);
- if (is_a($result, 'PEAR_Error')) {
- Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
- return $result;
- }
- }
-
- /* Get a list of all shares this user has perms to and remove the
- * perms. */
- $shares = $GLOBALS['ingo_shares']->listShares($user);
- if (is_a($shares, 'PEAR_Error')) {
- Horde::logMessage($shares, __FILE__, __LINE__, PEAR_LOG_ERR);
- }
- foreach ($shares as $share) {
- $share->removeUser($user);
- }
-
- /* Get a list of all shares this user owns and has perms to delete and
- * remove them. */
- $shares = $GLOBALS['ingo_shares']->listShares($user, PERMS_DELETE, $user);
- if (is_a($shares, 'PEAR_Error')) {
- Horde::logMessage($shares, __FILE__, __LINE__, PEAR_LOG_ERR);
- return $shares;
- }
- foreach ($shares as $share) {
- $GLOBALS['ingo_shares']->removeShare($share);
- }
- }
-
- return true;
-}
-
-/**
- * Add addresses to the blacklist.
- *
- * @param string $addresses The addresses to add to the blacklist.
- */
-function _ingo_blacklistFrom($addresses)
-{
- require_once dirname(__FILE__) . '/../lib/base.php';
- if (!empty($GLOBALS['ingo_shares'])) {
- $_SESSION['ingo']['current_share'] = $signature;
- }
-
- /* Check for '@' entries in $addresses - this would call all mail to
- * be blacklisted which is most likely not what is desired. */
- $addresses = array_unique($addresses);
- $key = array_search('@', $addresses);
- if ($key !== false) {
- unset($addresses[$key]);
- }
-
- if (!empty($addresses)) {
- $blacklist = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_BLACKLIST);
- $ret = $blacklist->setBlacklist(array_merge($blacklist->getBlacklist(), $addresses));
- if (is_a($ret, 'PEAR_Error')) {
- $GLOBALS['notification']->push($ret, $ret->getCode());
- } else {
- $GLOBALS['ingo_storage']->store($blacklist);
- Ingo::updateScript();
- foreach ($addresses as $from) {
- $GLOBALS['notification']->push(sprintf(_("The address \"%s\" has been added to your blacklist."), $from));
- }
- }
- }
-}
-
-/**
- * Add addresses to the whitelist.
- *
- * @param string $addresses The addresses to add to the whitelist.
- */
-function _ingo_whitelistFrom($addresses)
-{
- require_once dirname(__FILE__) . '/../lib/base.php';
- if (!empty($GLOBALS['ingo_shares'])) {
- $_SESSION['ingo']['current_share'] = $signature;
- }
-
- $whitelist = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_WHITELIST);
- $ret = $whitelist->setWhitelist(array_merge($whitelist->getWhitelist(), $addresses));
- if (is_a($ret, 'PEAR_Error')) {
- $GLOBALS['notification']->push($ret, $ret->getCode());
- } else {
- $GLOBALS['ingo_storage']->store($whitelist);
- Ingo::updateScript();
- foreach ($addresses as $from) {
- $GLOBALS['notification']->push(sprintf(_("The address \"%s\" has been added to your whitelist."), $from));
- }
- }
-}
-
-/**
- * Can this driver perform on-demand filtering?
- *
- * @return boolean True if perform() is available, false if not.
- */
-function _ingo_canApplyFilters()
-{
- require_once dirname(__FILE__) . '/../lib/base.php';
-
- $ingo_script = Ingo::loadIngoScript();
- return $ingo_script
- ? $ingo_script->performAvailable()
- : false;
-}
-
-/**
- * Perform the filtering specified in the rules.
- *
- * @param array $params The parameter array.
- *
- * @return boolean True if filtering was performed, false if not.
- */
-function _ingo_applyFilters($params = array())
-{
- require_once dirname(__FILE__) . '/../lib/base.php';
- if (!empty($GLOBALS['ingo_shares'])) {
- $_SESSION['ingo']['current_share'] = $signature;
- }
-
- $ingo_script = Ingo::loadIngoScript();
- return $ingo_script
- ? $ingo_script->perform($params)
- : false;
-}
-
-/**
- * Set vacation
- *
- * @param array $info Vacation details
- *
- * @return boolean True on success.
- */
-function _ingo_setVacation($info)
-{
- require_once dirname(__FILE__) . '/../lib/base.php';
- if (!empty($GLOBALS['ingo_shares'])) {
- $_SESSION['ingo']['current_share'] = $signature;
- }
-
- if (empty($info)) {
- return true;
- }
-
- /* Get vacation filter. */
- $filters = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_FILTERS);
- $vacation_rule_id = $filters->findRuleId(Ingo_Storage::ACTION_VACATION);
-
- /* Set vacation object and rules. */
- $vacation = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_VACATION);
-
- /* Make sure we have at least one address. */
- if (empty($info['addresses'])) {
- require_once 'Horde/Identity.php';
- $identity = &Identity::singleton('none');
- $info['addresses'] = implode("\n", $identity->getAll('from_addr'));
- /* Remove empty lines. */
- $info['addresses'] = preg_replace('/\n+/', "\n", $info['addresses']);
- if (empty($addresses)) {
- $info['addresses'] = Horde_Auth::getAuth();
- }
- }
-
- $vacation->setVacationAddresses($addresses);
-
- if (isset($info['days'])) {
- $vacation->setVacationDays($info['days']);
- }
- if (isset($info['excludes'])) {
- $vacation->setVacationExcludes($info['excludes']);
- }
- if (isset($info['ignorelist'])) {
- $vacation->setVacationIgnorelist(($info['ignorelist'] == 'on'));
- }
- if (isset($info['reason'])) {
- $vacation->setVacationReason($info['reason']);
- }
- if (isset($info['subject'])) {
- $vacation->setVacationSubject($info['subject']);
- }
- if (isset($info['start'])) {
- $vacation->setVacationStart($info['start']);
- }
- if (isset($info['end'])) {
- $vacation->setVacationEnd($info['end']);
- }
-
- $filters->ruleEnable($vacation_rule_id);
- $result = $GLOBALS['ingo_storage']->store($filters);
- if (!is_a($result, 'PEAR_Error')) {
- if ($GLOBALS['prefs']->getValue('auto_update')) {
- Ingo::updateScript();
- }
-
- /* Update the timestamp for the rules. */
- $_SESSION['ingo']['change'] = time();
- }
-
- return $result;
-}
-
-/**
- * Disable vacation
- *
- * @return boolean True on success.
- */
-function _ingo_disableVacation()
-{
- require_once dirname(__FILE__) . '/../lib/base.php';
- if (!empty($GLOBALS['ingo_shares'])) {
- $_SESSION['ingo']['current_share'] = $signature;
- }
-
- /* Get vacation filter. */
- $filters = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_FILTERS);
- $vacation_rule_id = $filters->findRuleId(Ingo_Storage::ACTION_VACATION);
-
- $filters->ruleDisable($vacation_rule_id);
- $result = $GLOBALS['ingo_storage']->store($filters);
- if (!is_a($result, 'PEAR_Error')) {
- if ($GLOBALS['prefs']->getValue('auto_update')) {
- Ingo::updateScript();
- }
-
- /* Update the timestamp for the rules. */
- $_SESSION['ingo']['change'] = time();
- }
-
- return $result;
-}
+++ /dev/null
-<?php define('INGO_VERSION', 'H4 (2.0-git)') ?>
/* Ingo version. */
$module = 'Ingo';
-require_once INGO_BASE . '/lib/version.php';
-$module_version = INGO_VERSION;
+require_once dirname(__FILE__) . '/lib/Api.php';
+$api = new Ingo_Api();
+$module_version = $api->version;
require TEST_TEMPLATES . 'header.inc';
require TEST_TEMPLATES . 'version.inc';
--- /dev/null
+<?php
+class Jeta_Api extends Horde_Registry_Api
+{
+ public $version = 'H4 (2.0-git)';
+}
+++ /dev/null
-<?php define('JETA_VERSION', 'H4 (2.0-git)') ?>
/* Jeta version. */
$module = 'Jeta';
-require_once JETA_BASE . '/lib/version.php';
-$module_version = JETA_VERSION;
+require_once dirname(__FILE__) . '/lib/Api.php';
+$api = new Jeta_Api();
+$module_version = $api->version;
/* Jeta configuration files. */
$file_list = array(
--- /dev/null
+<?php
+class Kastalia_Api extends Horde_Registry_Api
+{
+ public $version = '1.0.1';
+}
+++ /dev/null
-<?php define('KASTALIA_VERSION', '1.0.1') ?>
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * Kronolith external API interface.
+ *
+ * This file defines Kronolith's external API interface. Other applications
+ * can interact with Kronolith through this API.
+ *
+ * @package Kronolith
+ */
+class Kronolith_Api extends Horde_Registry_Api
+{
+ public $version = 'H3 (3.0-git)';
+
+ public $services = array(
+ 'show' => array(
+ 'link' => '%application%/event.php?calendar=|calendar|&eventID=|event|&uid=|uid|'
+ ),
+
+ 'perms' => array(
+ 'args' => array(),
+ 'type' => '{urn:horde}stringArray'
+ ),
+
+ 'removeUserData' => array(
+ 'args' => array('user' => 'string'),
+ 'type' => 'boolean'
+ ),
+
+ 'shareHelp' => array(
+ 'args' => array(),
+ 'type' => 'string'
+ ),
+
+ 'modified' => array(
+ 'args' => array('uid' => 'string'),
+ 'type' => 'int',
+ ),
+
+ 'browse' => array(
+ 'args' => array('path' => 'string', 'properties' => '{urn:horde}stringArray'),
+ 'type' => '{urn:horde}hashHash',
+ ),
+
+ 'put' => array(
+ 'args' => array('path' => 'string', 'content' => 'string', 'content_type' => 'string'),
+ 'type' => '{urn:horde}stringArray',
+ ),
+
+ 'path_delete' => array(
+ 'args' => array('path' => 'string'),
+ 'type' => 'boolean',
+ ),
+
+ 'getFreeBusy' => array(
+ 'args' => array('startstamp' => 'int', 'endstamp' => 'int', 'calendar' => 'string'),
+ 'type' => '{urn:horde}stringArray'
+ ),
+
+ 'listCalendars' => array(
+ 'args' => array('owneronly' => 'boolean', 'permission' => 'int'),
+ 'type' => '{urn:horde}stringArray'
+ ),
+
+ 'listEvents' => array(
+ 'args' => array('startstamp' => 'int', 'endstamp' => 'int', 'calendar' => 'string', 'showRecurrence' => 'string', 'alarmsOnly' => 'boolean'),
+ 'type' => '{urn:horde}hashHash'
+ ),
+
+ 'listAlarms' => array(
+ 'args' => array('time' => 'int', 'user' => 'string'),
+ 'type' => '{urn:horde}hashHash'
+ ),
+
+ 'list' => array(
+ 'args' => array(),
+ 'type' => '{urn:horde}stringArray'
+ ),
+
+ 'listBy' => array(
+ 'args' => array('action' => 'string', 'timestamp' => 'int'),
+ 'type' => '{urn:horde}stringArray'
+ ),
+
+ 'getActionTimestamp' => array(
+ 'args' => array('uid' => 'string', 'action' => 'string', 'calendar' => 'string'),
+ 'type' => 'int',
+ ),
+
+ 'import' => array(
+ 'args' => array('content' => 'string', 'contentType' => 'string', 'calendar' => 'string'),
+ 'type' => 'string'
+ ),
+
+ 'export' => array(
+ 'args' => array('uid' => 'string', 'contentType' => 'string'),
+ 'type' => 'string'
+ ),
+
+ 'exportCalendar' => array(
+ 'args' => array('calendar' => 'string', 'contentType' => 'string'),
+ 'type' => 'string'
+ ),
+
+ 'delete' => array(
+ 'args' => array('uid' => 'string'),
+ 'type' => 'boolean'
+ ),
+
+ 'replace' => array(
+ 'args' => array('uid' => 'string', 'content' => 'string', 'contentType' => 'string'),
+ 'type' => 'boolean'
+ ),
+
+ // FIXME: create complex type definition for SOAP calls.
+ 'eventFromUID' => array(
+ 'args' => array('uid' => 'string'),
+ 'type' => 'object'
+ ),
+
+ // FIXME: create complex type definition for SOAP calls.
+ 'updateAttendee' => array(
+ 'args' => array('response' => 'object', 'sender' => 'string'),
+ 'type' => 'boolean'
+ ),
+
+ 'subscribe' => array(
+ 'args' => array('calendar' => '{urn:horde}stringArray'),
+ 'type' => 'boolean',
+ ),
+
+ 'unsubscribe' => array(
+ 'args' => array('calendar' => '{urn:horde}stringArray'),
+ 'type' => 'boolean',
+ ),
+
+ 'lock' => array(
+ 'args' => array('calendar' => 'string', 'event' => 'string'),
+ 'type' => 'string'
+ ),
+
+ 'unlock' => array(
+ 'args' => array('calendar' => 'string', 'lockid' => 'string'),
+ 'type' => 'string'
+ ),
+
+ 'checkLocks' => array(
+ 'args' => array('calendar' => 'string', 'event' => 'string'),
+ 'type' => 'string'
+ ),
+
+ 'getFbCalendars' => array(
+ 'args' => array(),
+ 'type' => '{urn:horde}stringArray'
+ )
+ );
+
+ /**
+ * Returns a list of available permissions.
+ *
+ * @return array An array describing all available permissions.
+ */
+ public function perms()
+ {
+ $perms = array();
+ $perms['tree']['kronolith']['max_events'] = false;
+ $perms['title']['kronolith:max_events'] = _("Maximum Number of Events");
+ $perms['type']['kronolith:max_events'] = 'int';
+
+ return $perms;
+ }
+
+ /**
+ * Removes user data.
+ *
+ * @param string $user Name of user to remove data for.
+ *
+ * @return mixed true on success | PEAR_Error on failure
+ */
+ public function removeUserData($user)
+ {
+ if (!Horde_Auth::isAdmin() && $user != Horde_Auth::getAuth()) {
+ return PEAR::raiseError(_("You are not allowed to remove user data."));
+ }
+
+ $no_maint = true;
+ require_once dirname(__FILE__) . '/base.php';
+
+ /* Remove all events owned by the user in all calendars. */
+ $result = Kronolith::getDriver()->removeUserData($user);
+
+ /* Now delete history as well. */
+ $history = &Horde_History::singleton();
+ if (method_exists($history, 'removeByParent')) {
+ $histories = $history->removeByParent('kronolith:' . $user);
+ } else {
+ /* Remove entries 100 at a time. */
+ $all = $history->getByTimestamp('>', 0, array(), 'kronolith:' . $user);
+ if (is_a($all, 'PEAR_Error')) {
+ Horde::logMessage($all, __FILE__, __LINE__, PEAR_LOG_ERR);
+ } else {
+ $all = array_keys($all);
+ while (count($d = array_splice($all, 0, 100)) > 0) {
+ $history->removebyNames($d);
+ }
+ }
+ }
+
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ /* Get the user's default share */
+ $share = $GLOBALS['kronolith_shares']->getShare($user);
+ if (is_a($share, 'PEAR_Error')) {
+ Horde::logMessage($share, __FILE__, __LINE__, PEAR_LOG_ERR);
+ } else {
+ $result = $GLOBALS['kronolith_shares']->removeShare($share);
+ if (is_a($result, 'PEAR_Error')) {
+ $hasError = true;
+ Horde::logMessage($result->getMessage(), __FILE__, __LINE__, PEAR_LOG_ERR);
+ }
+ }
+
+ /* Get a list of all shares this user has perms to and remove the perms */
+ $shares = $GLOBALS['kronolith_shares']->listShares($user);
+ if (is_a($shares, 'PEAR_Error')) {
+ Horde::logMessage($shares, __FILE__, __LINE__, PEAR_LOG_ERR);
+ }
+ foreach ($shares as $share) {
+ $share->removeUser($user);
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the share helper prefix
+ *
+ * @return string
+ */
+ public function shareHelp()
+ {
+ return 'shares';
+ }
+
+ /**
+ * Returns the last modification timestamp for the given uid.
+ *
+ * @param string $uid The uid to look for.
+ *
+ * @return integer The timestamp for the last modification of $uid.
+ */
+ public function modified($uid)
+ {
+ $modified = $this->getActionTimestamp($uid, 'modify');
+ if (empty($modified)) {
+ $modified = $this->getActionTimestamp($uid, 'add');
+ }
+ return $modified;
+ }
+
+ /**
+ * Browse through Kronolith's object tree.
+ *
+ * @param string $path The level of the tree to browse.
+ * @param array $properties The item properties to return. Defaults to 'name',
+ * 'icon', and 'browseable'.
+ *
+ * @return array The contents of $path
+ */
+ public function browse($path = '', $properties = array())
+ {
+ $no_maint = true;
+ require_once dirname(__FILE__) . '/base.php';
+ global $registry;
+
+ // Default properties.
+ if (!$properties) {
+ $properties = array('name', 'icon', 'browseable');
+ }
+
+ if (substr($path, 0, 9) == 'kronolith') {
+ $path = substr($path, 9);
+ }
+ $path = trim($path, '/');
+ $parts = explode('/', $path);
+
+ if (empty($path)) {
+ //
+ // This request is for a list of all users who have calendars visible
+ // to the requesting user.
+ //
+ $calendars = Kronolith::listCalendars(false, PERMS_READ);
+ $owners = array();
+ foreach ($calendars as $calendar) {
+ $owners[$calendar->get('owner')] = true;
+ }
+
+ $results = array();
+ foreach (array_keys($owners) as $owner) {
+ $path = 'kronolith/' . $owner;
+ if (in_array('name', $properties)) {
+ $results[$path]['name'] = $owner;
+ }
+ if (in_array('icon', $properties)) {
+ $results[$path]['icon'] =
+ $registry->getImageDir('horde') . '/user.png';
+ }
+ if (in_array('browseable', $properties)) {
+ $results[$path]['browseable'] = true;
+ }
+ if (in_array('contenttype', $properties)) {
+ $results[$path]['contenttype'] =
+ 'httpd/unix-directory';
+ }
+ if (in_array('contentlength', $properties)) {
+ $results[$path]['contentlength'] = 0;
+ }
+ if (in_array('modified', $properties)) {
+ $results[$path]['modified'] =
+ $_SERVER['REQUEST_TIME'];
+ }
+ if (in_array('created', $properties)) {
+ $results[$path]['created'] = 0;
+ }
+
+ // CalDAV Properties from RFC 4791 and
+ // draft-desruisseaux-caldav-sched-03
+ $caldavns = 'urn:ietf:params:xml:ns:caldav';
+ $kronolith_rpc_base = $GLOBALS['registry']->get('webroot', 'horde') . '/rpc/kronolith/';
+ if (in_array($caldavns . ':calendar-home-set', $properties)) {
+ $results[$path][$caldavns . ':calendar-home-set'] = Horde::url($kronolith_rpc_base . urlencode($owner), true);
+ }
+
+ if (in_array($caldavns . ':calendar-user-address-set', $properties)) {
+ // FIXME: Add the calendar owner's email address from their Horde Identity
+ }
+ }
+ return $results;
+
+ } elseif (count($parts) == 1) {
+ //
+ // This request is for all calendars owned by the requested user
+ //
+ $calendars = $GLOBALS['kronolith_shares']->listShares(Horde_Auth::getAuth(),
+ PERMS_SHOW,
+ $parts[0]);
+ $results = array();
+ foreach ($calendars as $calendarId => $calendar) {
+ $retpath = 'kronolith/' . $parts[0] . '/' . $calendarId;
+ if (in_array('name', $properties)) {
+ $results[$retpath]['name'] = sprintf(_("Events from %s"), $calendar->get('name'));
+ $results[$retpath . '.ics']['name'] = $calendar->get('name');
+ }
+ if (in_array('displayname', $properties)) {
+ $results[$retpath]['displayname'] = rawurlencode($calendar->get('name'));
+ $results[$retpath . '.ics']['displayname'] = rawurlencode($calendar->get('name')) . '.ics';
+ }
+ if (in_array('icon', $properties)) {
+ $results[$retpath]['icon'] = $registry->getImageDir() . '/kronolith.png';
+ $results[$retpath . '.ics']['icon'] = $registry->getImageDir() . '/mime/icalendar.png';
+ }
+ if (in_array('browseable', $properties)) {
+ $results[$retpath]['browseable'] = $calendar->hasPermission(Horde_Auth::getAuth(), PERMS_READ);
+ $results[$retpath . '.ics']['browseable'] = false;
+ }
+ if (in_array('contenttype', $properties)) {
+ $results[$retpath]['contenttype'] = 'httpd/unix-directory';
+ $results[$retpath . '.ics']['contenttype'] = 'text/calendar';
+ }
+ if (in_array('contentlength', $properties)) {
+ $results[$retpath]['contentlength'] = 0;
+ // FIXME: This is a hack. If the content length is longer
+ // than the actual data then some WebDAV clients will report
+ // an error when the file EOF is received. Ideally we should
+ // determine the actual size of the .ics and report it here, but
+ // the performance hit may be prohibitive. This requires
+ // further investigation.
+ $results[$retpath . '.ics']['contentlength'] = 1;
+ }
+ if (in_array('modified', $properties)) {
+ $results[$retpath]['modified'] = $_SERVER['REQUEST_TIME'];
+ $results[$retpath . '.ics']['modified'] = $_SERVER['REQUEST_TIME'];
+ }
+ if (in_array('created', $properties)) {
+ $results[$retpath]['created'] = 0;
+ $results[$retpath . '.ics']['created'] = 0;
+ }
+ }
+ return $results;
+
+ } elseif (count($parts) == 2 &&
+ array_key_exists($parts[1],
+ Kronolith::listCalendars(false, PERMS_READ))) {
+ //
+ // This request is browsing into a specific calendar. Generate the list
+ // of items and represent them as files within the directory.
+ //
+ $kronolith_driver = Kronolith::getDriver(null, $parts[1]);
+ $events = $kronolith_driver->listEvents();
+ if (is_a($events, 'PEAR_Error')) {
+ return $events;
+ }
+
+ $icon = $registry->getImageDir('horde') . '/mime/icalendar.png';
+ $results = array();
+ foreach ($events as $dayevents) {
+ foreach ($dayevents as $event) {
+ $key = 'kronolith/' . $path . '/' . $event->getId();
+ if (in_array('name', $properties)) {
+ $results[$key]['name'] = $event->getTitle();
+ }
+ if (in_array('icon', $properties)) {
+ $results[$key]['icon'] = $icon;
+ }
+ if (in_array('browseable', $properties)) {
+ $results[$key]['browseable'] = false;
+ }
+ if (in_array('contenttype', $properties)) {
+ $results[$key]['contenttype'] = 'text/calendar';
+ }
+ if (in_array('contentlength', $properties)) {
+ // FIXME: This is a hack. If the content length is longer
+ // than the actual data then some WebDAV clients will report
+ // an error when the file EOF is received. Ideally we should
+ // determine the actual size of the data and report it here, but
+ // the performance hit may be prohibitive. This requires
+ // further investigation.
+ $results[$key]['contentlength'] = 1;
+ }
+ if (in_array('modified', $properties)) {
+ $results[$key]['modified'] = $this->modified($event->getUID());
+ }
+ if (in_array('created', $properties)) {
+ $results[$key]['created'] = $this->getActionTimestamp($event->getUID(), 'add');
+ }
+ }
+ }
+ return $results;
+ } else {
+ //
+ // The only valid request left is for either a specific event
+ // or for the entire calendar.
+ //
+ if (count($parts) == 3 &&
+ array_key_exists($parts[1],
+ Kronolith::listCalendars(false, PERMS_READ))) {
+ //
+ // This request is for a specific item within a given calendar.
+ //
+ $event = Kronolith::getCalendar(null, $parts[1])->getEvent($parts[2]);
+ if (is_a($event, 'PEAR_Error')) {
+ return $event;
+ }
+
+ $result = array(
+ 'data' => $this->export($event->getUID(), 'text/calendar'),
+ 'mimetype' => 'text/calendar');
+ $modified = $this->modified($event->getUID());
+ if (!empty($modified)) {
+ $result['mtime'] = $modified;
+ }
+ return $result;
+ } elseif (count($parts) == 2 &&
+ substr($parts[1], -4, 4) == '.ics' &&
+ array_key_exists(
+ substr($parts[1], 0, -4),
+ Kronolith::listCalendars(false, PERMS_READ))) {
+ //
+ // This request is for an entire calendar (calendar.ics).
+ //
+ $ical_data = $this->exportCalendar(substr($parts[1], 0, -4), 'text/calendar');
+ $result = array('data' => $ical_data,
+ 'mimetype' => 'text/calendar',
+ 'contentlength' => strlen($ical_data),
+ 'mtime' => $_SERVER['REQUEST_TIME']);
+
+ return $result;
+ } else {
+ //
+ // All other requests are a 404: Not Found
+ //
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Saves a file into the Kronolith tree.
+ *
+ * @param string $path The path where to PUT the file.
+ * @param string $content The file content.
+ * @param string $content_type The file's content type.
+ *
+ * @return array The event UIDs, or a PEAR_Error on failure.
+ */
+ public function put($path, $content, $content_type)
+ {
+ $no_maint = true;
+ require_once dirname(__FILE__) . '/base.php';
+
+ if (substr($path, 0, 9) == 'kronolith') {
+ $path = substr($path, 9);
+ }
+ $path = trim($path, '/');
+ $parts = explode('/', $path);
+
+ if (count($parts) == 2 &&
+ substr($parts[1], -4) == '.ics') {
+
+ // Workaround for WebDAV clients that are not smart enough to send
+ // the right content type. Assume text/calendar.
+ if ($content_type == 'application/octet-stream') {
+ $content_type = 'text/calendar';
+ }
+ $calendar = substr($parts[1], 0, -4);
+ } elseif (count($parts) == 3) {
+ $calendar = $parts[1];
+
+ // Workaround for WebDAV clients that are not smart enough to send
+ // the right content type. Assume text/calendar.
+ if ($content_type == 'application/octet-stream') {
+ $content_type = 'text/calendar';
+ }
+ } else {
+ return PEAR::raiseError("Invalid calendar data supplied.");
+ }
+
+ if (!array_key_exists($calendar, Kronolith::listCalendars(false, PERMS_EDIT))) {
+ // FIXME: Should we attempt to create a calendar based on the filename
+ // in the case that the requested calendar does not exist?
+ return PEAR::raiseError("Calendar does not exist or no permission to edit");
+ }
+
+ // Store all currently existings UIDs. Use this info to delete UIDs not
+ // present in $content after processing.
+ $ids = array();
+ $uids_remove = array_flip($this->listUids($calendar));
+
+ switch ($content_type) {
+ case 'text/calendar':
+ case 'text/x-vcalendar':
+ $iCal = new Horde_iCalendar();
+ if (!is_a($content, 'Horde_iCalendar_vevent')) {
+ if (!$iCal->parsevCalendar($content)) {
+ return PEAR::raiseError(_("There was an error importing the iCalendar data."));
+ }
+ } else {
+ $iCal->addComponent($content);
+ }
+
+ $kronolith_driver = Kronolith::getDriver();
+ foreach ($iCal->getComponents() as $content) {
+ if (is_a($content, 'Horde_iCalendar_vevent')) {
+ $event = $kronolith_driver->getEvent();
+ $event->fromiCalendar($content);
+ $event->setCalendar($calendar);
+ $uid = $event->getUID();
+ // Remove from uids_remove list so we won't delete in
+ // the end.
+ if (isset($uids_remove[$uid])) {
+ unset($uids_remove[$uid]);
+ }
+ $existing_event = $kronolith_driver->getByUID($uid, array($calendar));
+ if (!is_a($existing_event, 'PEAR_Error')) {
+ // Check if our event is newer then the existing - get the
+ // event's history.
+ $history = &Horde_History::singleton();
+ $created = $modified = null;
+ $log = $history->getHistory('kronolith:' . $calendar . ':'
+ . $uid);
+ if ($log && !is_a($log, 'PEAR_Error')) {
+ foreach ($log->getData() as $entry) {
+ switch ($entry['action']) {
+ case 'add':
+ $created = $entry['ts'];
+ break;
+
+ case 'modify':
+ $modified = $entry['ts'];
+ break;
+ }
+ }
+ }
+ if (empty($modified) && !empty($created)) {
+ $modified = $created;
+ }
+ if (!empty($modified) &&
+ $modified >= $content->getAttribute('LAST-MODIFIED')) {
+ // LAST-MODIFIED timestamp of existing entry is newer:
+ // don't replace it.
+ continue;
+ }
+
+ // Don't change creator/owner.
+ $event->setCreatorId($existing_event->getCreatorId());
+ }
+
+ // Save entry.
+ $saved = $event->save();
+ if (is_a($saved, 'PEAR_Error')) {
+ return $saved;
+ }
+ $ids[] = $event->getUID();
+ }
+ }
+ break;
+
+ default:
+ return PEAR::raiseError(sprintf(_("Unsupported Content-Type: %s"), $content_type));
+ }
+
+ if (array_key_exists($calendar, Kronolith::listCalendars(false, PERMS_DELETE))) {
+ foreach (array_keys($uids_remove) as $uid) {
+ $this->delete($uid);
+ }
+ }
+
+ return $ids;
+ }
+
+ /**
+ * Deletes a file from the Kronolith tree.
+ *
+ * @param string $path The path to the file.
+ *
+ * @return mixed The event's UID, or a PEAR_Error on failure.
+ */
+ public function path_delete($path)
+ {
+ $no_maint = true;
+ require_once dirname(__FILE__) . '/base.php';
+
+ if (substr($path, 0, 9) == 'kronolith') {
+ $path = substr($path, 9);
+ }
+ $path = trim($path, '/');
+ $parts = explode('/', $path);
+
+ if (substr($parts[1], -4) == '.ics') {
+ $calendarId = substr($parts[1], 0, -4);
+ } else {
+ $calendarId = $parts[1];
+ }
+
+ if (!(count($parts) == 2 || count($parts) == 3) ||
+ !array_key_exists($calendarId, Kronolith::listCalendars(false, PERMS_DELETE))) {
+ return PEAR::raiseError("Calendar does not exist or no permission to delete");
+ }
+
+ if (count($parts) == 3) {
+ // Delete just a single entry
+ return Kronolith::getDriver(null, $calendarId)->deleteEvent($parts[2]);
+ } else {
+ // Delete the entire calendar
+ $result = Kronolith::getDriver()->delete($calendarId);
+ if (is_a($result, 'PEAR_Error')) {
+ return PEAR::raiseError(sprintf(_("Unable to delete calendar \"%s\": %s"), $calendarId, $result->getMessage()));
+ } else {
+ // Remove share and all groups/permissions.
+ $share = $GLOBALS['kronolith_shares']->getShare($calendarId);
+ $result = $GLOBALS['kronolith_shares']->removeShare($share);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns all calendars a user has access to, according to several
+ * parameters/permission levels.
+ *
+ * @param boolean $owneronly Only return calenders that this user owns?
+ * Defaults to false.
+ * @param integer $permission The permission to filter calendars by.
+ *
+ * @return array The calendar list.
+ */
+ public function listCalendars($owneronly = false, $permission = null)
+ {
+ $no_maint = true;
+ require_once dirname(__FILE__) . '/base.php';
+ if (is_null($permission)) {
+ $permission = PERMS_SHOW;
+ }
+ return array_keys(Kronolith::listCalendars($owneronly, $permission));
+ }
+
+ /**
+ * Returns the ids of all the events that happen within a time period.
+ *
+ * @param string $calendar The calendar to check for events.
+ * @param object $startstamp The start of the time range.
+ * @param object $endstamp The end of the time range.
+ *
+ * @return array The event ids happening in this time period.
+ */
+ public function listUids($calendar = null, $startstamp = 0, $endstamp = 0)
+ {
+ $no_maint = true;
+ require_once dirname(__FILE__) . '/base.php';
+
+ if (empty($calendar)) {
+ $calendar = Kronolith::getDefaultCalendar();
+ }
+ if (!array_key_exists($calendar,
+ Kronolith::listCalendars(false, PERMS_READ))) {
+ return PEAR::raiseError(_("Permission Denied"));
+ }
+
+ $driver = Kronolith::getDriver(null, $calendar);
+ if (is_a($driver, 'PEAR_Error')) {
+ return $driver;
+ }
+ $events = $driver->listEvents(new Horde_Date($startstamp),
+ new Horde_Date($endstamp));
+ if (is_a($events, 'PEAR_Error')) {
+ return $events;
+ }
+
+ $uids = array();
+ foreach ($events as $dayevents) {
+ foreach ($dayevents as $event) {
+ $uids[] = $event->getUID();
+ }
+ }
+
+ return $uids;
+ }
+
+ /**
+ * Returns an array of UIDs for events that have had $action happen since
+ * $timestamp.
+ *
+ * @param string $action The action to check for - add, modify, or delete.
+ * @param integer $timestamp The time to start the search.
+ * @param string $calendar The calendar to search in.
+ *
+ * @return array An array of UIDs matching the action and time criteria.
+ */
+ public function listBy($action, $timestamp, $calendar = null)
+ {
+ $no_maint = true;
+ require_once dirname(__FILE__) . '/base.php';
+
+ if (empty($calendar)) {
+ $calendar = Kronolith::getDefaultCalendar();
+ }
+
+ if (!array_key_exists($calendar,
+ Kronolith::listCalendars(false, PERMS_READ))) {
+ return PEAR::raiseError(_("Permission Denied"));
+ }
+
+ $history = &Horde_History::singleton();
+ $histories = $history->getByTimestamp('>', $timestamp, array(array('op' => '=', 'field' => 'action', 'value' => $action)), 'kronolith:' . $calendar);
+ if (is_a($histories, 'PEAR_Error')) {
+ return $histories;
+ }
+
+ // Strip leading kronolith:username:.
+ return preg_replace('/^([^:]*:){2}/', '', array_keys($histories));
+ }
+
+ /**
+ * Returns the timestamp of an operation for a given uid an action
+ *
+ * @param string $uid The uid to look for.
+ * @param string $action The action to check for - add, modify, or delete.
+ * @param string $calendar The calendar to search in.
+ *
+ * @return integer The timestamp for this action.
+ */
+ public function getActionTimestamp($uid, $action, $calendar = null)
+ {
+ $no_maint = true;
+ require_once dirname(__FILE__) . '/base.php';
+
+ if (empty($calendar)) {
+ $calendar = Kronolith::getDefaultCalendar();
+ }
+
+ if (!array_key_exists($calendar,
+ Kronolith::listCalendars(false, PERMS_READ))) {
+ return PEAR::raiseError(_("Permission Denied"));
+ }
+
+ $history = &Horde_History::singleton();
+ return $history->getActionTimestamp('kronolith:' . $calendar . ':' .
+ $uid, $action);
+ }
+
+ /**
+ * Imports an event represented in the specified content type.
+ *
+ * @param string $content The content of the event.
+ * @param string $contentType What format is the data in? Currently supports:
+ * <pre>
+ * text/calendar
+ * text/x-vcalendar
+ * </pre>
+ * @param string $calendar What calendar should the event be added to?
+ *
+ * @return mixed The event's UID, or a PEAR_Error on failure.
+ */
+ public function import($content, $contentType, $calendar = null)
+ {
+ $no_maint = true;
+ require_once dirname(__FILE__) . '/base.php';
+
+ if (!isset($calendar)) {
+ $calendar = Kronolith::getDefaultCalendar(PERMS_EDIT);
+ }
+ if (!array_key_exists($calendar,
+ Kronolith::listCalendars(false, PERMS_EDIT))) {
+ return PEAR::raiseError(_("Permission Denied"));
+ }
+
+ switch ($contentType) {
+ case 'text/calendar':
+ case 'text/x-vcalendar':
+ $iCal = new Horde_iCalendar();
+ if (!is_a($content, 'Horde_iCalendar_vevent')) {
+ if (!$iCal->parsevCalendar($content)) {
+ return PEAR::raiseError(_("There was an error importing the iCalendar data."));
+ }
+ } else {
+ $iCal->addComponent($content);
+ }
+
+ $components = $iCal->getComponents();
+ if (count($components) == 0) {
+ return PEAR::raiseError(_("No iCalendar data was found."));
+ }
+
+ $kronolith_driver = Kronolith::getDriver(null, $calendar);
+ $ids = array();
+ foreach ($components as $content) {
+ if (is_a($content, 'Horde_iCalendar_vevent')) {
+ $event = $kronolith_driver->getEvent();
+ $event->fromiCalendar($content);
+ $event->setCalendar($calendar);
+ // Check if the entry already exists in the data source, first
+ // by UID.
+ $uid = $event->getUID();
+ $existing_event = $kronolith_driver->getByUID($uid, array($calendar));
+ if (!is_a($existing_event, 'PEAR_Error')) {
+ return PEAR::raiseError(_("Already Exists"),
+ 'horde.message', null, null, $uid);
+ }
+ $result = $kronolith_driver->search($event);
+ // Check if the match really is an exact match:
+ if (is_array($result) && count($result) > 0) {
+ foreach($result as $match) {
+ if ($match->start == $event->start &&
+ $match->end == $event->end &&
+ $match->title == $event->title &&
+ $match->location == $event->location &&
+ $match->hasPermission(PERMS_EDIT)) {
+ return PEAR::raiseError(_("Already Exists"), 'horde.message', null, null, $match->getUID());
+ }
+ }
+ }
+
+ $eventId = $event->save();
+ if (is_a($eventId, 'PEAR_Error')) {
+ return $eventId;
+ }
+ $ids[] = $event->getUID();
+ }
+ }
+ if (count($ids) == 0) {
+ return PEAR::raiseError(_("No iCalendar data was found."));
+ } else if (count($ids) == 1) {
+ return $ids[0];
+ }
+ return $ids;
+ }
+
+ return PEAR::raiseError(sprintf(_("Unsupported Content-Type: %s"), $contentType));
+ }
+
+ /**
+ * Exports an event, identified by UID, in the requested content type.
+ *
+ * @param string $uid Identify the event to export.
+ * @param string $contentType What format should the data be in?
+ * A string with one of:
+ * <pre>
+ * text/calendar (VCALENDAR 2.0. Recommended as
+ * this is specified in rfc2445)
+ * text/x-vcalendar (old VCALENDAR 1.0 format.
+ * Still in wide use)
+ * </pre>
+ *
+ * @return string The requested data.
+ */
+ public function export($uid, $contentType)
+ {
+ $no_maint = true;
+ require_once dirname(__FILE__) . '/base.php';
+ global $kronolith_shares;
+
+ $event = Kronolith::getDriver()->getByUID($uid);
+ if (is_a($event, 'PEAR_Error')) {
+ return $event;
+ }
+ if (!$event->hasPermission(PERMS_READ)) {
+ return PEAR::raiseError(_("Permission Denied"));
+ }
+
+ $version = '2.0';
+ switch ($contentType) {
+ case 'text/x-vcalendar':
+ $version = '1.0';
+ case 'text/calendar':
+ $share = &$kronolith_shares->getShare($event->getCalendar());
+
+ $iCal = new Horde_iCalendar($version);
+ $iCal->setAttribute('X-WR-CALNAME', Horde_String::convertCharset($share->get('name'), Horde_Nls::getCharset(), 'utf-8'));
+
+ // Create a new vEvent.
+ $vEvent = &$event->toiCalendar($iCal);
+ $iCal->addComponent($vEvent);
+
+ return $iCal->exportvCalendar();
+
+ }
+
+ return PEAR::raiseError(sprintf(_("Unsupported Content-Type: %s"), $contentType));
+ }
+
+ /**
+ * Exports a calendar in the requested content type.
+ *
+ * @param string $calendar The calendar to export.
+ * @param string $contentType What format should the data be in?
+ * A string with one of:
+ * <pre>
+ * text/calendar (VCALENDAR 2.0. Recommended as
+ * this is specified in rfc2445)
+ * text/x-vcalendar (old VCALENDAR 1.0 format.
+ * Still in wide use)
+ * </pre>
+ *
+ * @return string The iCalendar representation of the calendar.
+ */
+ public function exportCalendar($calendar, $contentType)
+ {
+ $no_maint = true;
+ require_once dirname(__FILE__) . '/base.php';
+ global $kronolith_shares;
+
+ if (!array_key_exists($calendar,
+ Kronolith::listCalendars(false, PERMS_READ))) {
+ return PEAR::raiseError(_("Permission Denied"));
+ }
+
+ $kronolith_driver = Kronolith::getDriver(null, $calendar);
+ $events = $kronolith_driver->listEvents();
+
+ $version = '2.0';
+ switch ($contentType) {
+ case 'text/x-vcalendar':
+ $version = '1.0';
+ case 'text/calendar':
+ $share = &$kronolith_shares->getShare($calendar);
+
+ $iCal = new Horde_iCalendar($version);
+ $iCal->setAttribute('X-WR-CALNAME', Horde_String::convertCharset($share->get('name'), Horde_Nls::getCharset(), 'utf-8'));
+
+ foreach ($events as $dayevents) {
+ foreach ($dayevents as $event) {
+ $vEvent = &$event->toiCalendar($iCal);
+ $iCal->addComponent($vEvent);
+ }
+ }
+
+ return $iCal->exportvCalendar();
+ }
+
+ return PEAR::raiseError(sprintf(_("Unsupported Content-Type: %s"), $contentType));
+ }
+
+ /**
+ * Deletes an event identified by UID.
+ *
+ * @param string|array $uid A single UID or an array identifying the event(s)
+ * to delete.
+ *
+ * @return boolean Success or failure.
+ */
+ public function delete($uid)
+ {
+ // Handle an array of UIDs for convenience of deleting multiple events at
+ // once.
+ if (is_array($uid)) {
+ foreach ($uid as $g) {
+ $result = $this->delete($g);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ }
+
+ return true;
+ }
+
+ $no_maint = true;
+ require_once dirname(__FILE__) . '/base.php';
+
+ $kronolith_driver = Kronolith::getDriver();
+ $events = $kronolith_driver->getByUID($uid, null, true);
+ if (is_a($events, 'PEAR_Error')) {
+ return $events;
+ }
+
+ $event = null;
+ if (Horde_Auth::isAdmin()) {
+ $event = $events[0];
+ }
+
+ // First try the user's own calendars.
+ if (empty($event)) {
+ $ownerCalendars = Kronolith::listCalendars(true, PERMS_DELETE);
+ foreach ($events as $ev) {
+ if (Horde_Auth::isAdmin() || isset($ownerCalendars[$ev->getCalendar()])) {
+ $event = $ev;
+ break;
+ }
+ }
+ }
+
+ // If not successful, try all calendars the user has access to.
+ if (empty($event)) {
+ $deletableCalendars = Kronolith::listCalendars(false, PERMS_DELETE);
+ foreach ($events as $ev) {
+ if (isset($deletableCalendars[$ev->getCalendar()])) {
+ $kronolith_driver->open($ev->getCalendar());
+ $event = $ev;
+ break;
+ }
+ }
+ }
+
+ if (empty($event)) {
+ return PEAR::raiseError(_("Permission Denied"));
+ }
+
+ return $kronolith_driver->deleteEvent($event->getId());
+ }
+
+ /**
+ * Replaces the event identified by UID with the content represented in the
+ * specified contentType.
+ *
+ * @param string $uid Idenfity the event to replace.
+ * @param mixed $content The content of the event. String or
+ * Horde_iCalendar_vevent
+ * @param string $contentType What format is the data in? Currently supports:
+ * text/calendar
+ * text/x-vcalendar
+ * (Ignored if content is Horde_iCalendar_vevent)
+ *
+ * @return mixed True on success, PEAR_Error otherwise.
+ */
+ public function replace($uid, $content, $contentType)
+ {
+ $no_maint = true;
+ require_once dirname(__FILE__) . '/base.php';
+
+ $event = Kronolith::getDriver()->getByUID($uid);
+ if (is_a($event, 'PEAR_Error')) {
+ return $event;
+ }
+
+ if (!$event->hasPermission(PERMS_EDIT) ||
+ ($event->isPrivate() && $event->getCreatorId() != Horde_Auth::getAuth())) {
+ return PEAR::raiseError(_("Permission Denied"));
+ }
+
+ if (is_a($content, 'Horde_iCalendar_vevent')) {
+ $component = $content;
+ } else {
+ switch ($contentType) {
+ case 'text/calendar':
+ case 'text/x-vcalendar':
+ if (!is_a($content, 'Horde_iCalendar_vevent')) {
+ $iCal = new Horde_iCalendar();
+ if (!$iCal->parsevCalendar($content)) {
+ return PEAR::raiseError(_("There was an error importing the iCalendar data."));
+ }
+
+ $components = $iCal->getComponents();
+ $component = null;
+ foreach ($components as $content) {
+ if (is_a($content, 'Horde_iCalendar_vevent')) {
+ if ($component !== null) {
+ return PEAR::raiseError(_("Multiple iCalendar components found; only one vEvent is supported."));
+ }
+ $component = $content;
+ }
+
+ }
+ if ($component === null) {
+ return PEAR::raiseError(_("No iCalendar data was found."));
+ }
+ }
+ break;
+
+ default:
+ return PEAR::raiseError(sprintf(_("Unsupported Content-Type: %s"), $contentType));
+ }
+ }
+
+ $event->fromiCalendar($component);
+ // Ensure we keep the original UID, even when content does not
+ // contain one and fromiCalendar creates a new one.
+ $event->setUID($uid);
+ $eventId = $event->save();
+
+ return is_a($eventId, 'PEAR_Error') ? $eventId : true;
+ }
+
+ /**
+ * Generates free/busy information for a given time period.
+ *
+ * @param integer $startstamp The start of the time period to retrieve.
+ * @param integer $endstamp The end of the time period to retrieve.
+ * @param string $calendar The calendar to view free/busy slots for.
+ * Defaults to the user's default calendar.
+ *
+ * @return Horde_iCalendar_vfreebusy A freebusy object that covers the
+ * specified time period.
+ */
+ public function getFreeBusy($startstamp = null, $endstamp = null,
+ $calendar = null)
+ {
+ $no_maint = true;
+ require_once dirname(__FILE__) . '/base.php';
+
+ if (is_null($calendar)) {
+ $calendar = Kronolith::getDefaultCalendar();
+ }
+ // Free/Busy information is globally available; no permission
+ // check is needed.
+
+ return Kronolith_FreeBusy::generate($calendar, $startstamp, $endstamp, true);
+ }
+
+ /**
+ * Retrieves a Kronolith_Event object, given an event UID.
+ *
+ * @param string $uid The event's UID.
+ *
+ * @return Kronolith_Event A valid Kronolith_Event on success, or a PEAR_Error
+ * on failure.
+ */
+ public function eventFromUID($uid)
+ {
+ $no_maint = true;
+ require_once dirname(__FILE__) . '/base.php';
+
+ $event = Kronolith::getDriver()->getByUID($uid);
+ if (is_a($event, 'PEAR_Error')) {
+ return $event;
+ }
+ if (!$event->hasPermission(PERMS_SHOW)) {
+ return PEAR::raiseError(_("Permission Denied"));
+ }
+
+ return $event;
+ }
+
+ /**
+ * Updates an attendee's response status for a specified event.
+ *
+ * @param Horde_iCalender_vevent $response A Horde_iCalender_vevent object,
+ * with a valid UID attribute that
+ * points to an existing event.
+ * This is typically the vEvent
+ * portion of an iTip meeting-request
+ * response, with the attendee's
+ * response in an ATTENDEE parameter.
+ * @param string $sender The email address of the person
+ * initiating the update. Attendees
+ * are only updated if this address
+ * matches.
+ *
+ * @return mixed True on success, PEAR_Error on failure.
+ */
+ public function updateAttendee($response, $sender = null)
+ {
+ $no_maint = true;
+ require_once dirname(__FILE__) . '/base.php';
+
+ $uid = $response->getAttribute('UID');
+ if (is_a($uid, 'PEAR_Error')) {
+ return $uid;
+ }
+
+ $events = Kronolith::getDriver()->getByUID($uid, null, true);
+ if (is_a($events, 'PEAR_Error')) {
+ return $events;
+ }
+
+ /* First try the user's own calendars. */
+ $ownerCalendars = Kronolith::listCalendars(true, PERMS_EDIT);
+ $event = null;
+ foreach ($events as $ev) {
+ if (isset($ownerCalendars[$ev->getCalendar()])) {
+ $event = $ev;
+ break;
+ }
+ }
+
+ /* If not successful, try all calendars the user has access to. */
+ if (empty($event)) {
+ $editableCalendars = Kronolith::listCalendars(false, PERMS_EDIT);
+ foreach ($events as $ev) {
+ if (isset($editableCalendars[$ev->getCalendar()])) {
+ $event = $ev;
+ break;
+ }
+ }
+ }
+
+ if (empty($event) ||
+ ($event->isPrivate() && $event->getCreatorId() != Horde_Auth::getAuth())) {
+ return PEAR::raiseError(_("Permission Denied"));
+ }
+
+ $atnames = $response->getAttribute('ATTENDEE');
+ if (!is_array($atnames)) {
+ $atnames = array($atnames);
+ }
+ $atparms = $response->getAttribute('ATTENDEE', true);
+
+ $found = false;
+ $error = _("No attendees have been updated because none of the provided email addresses have been found in the event's attendees list.");
+ $sender_lcase = Horde_String::lower($sender);
+ foreach ($atnames as $index => $attendee) {
+ $attendee = str_replace('mailto:', '', Horde_String::lower($attendee));
+ $name = isset($atparms[$index]['CN']) ? $atparms[$index]['CN'] : null;
+ if ($event->hasAttendee($attendee)) {
+ if (is_null($sender) || $sender_lcase == $attendee) {
+ $event->addAttendee($attendee, Kronolith::PART_IGNORE, Kronolith::responseFromICal($atparms[$index]['PARTSTAT']), $name);
+ $found = true;
+ } else {
+ $error = _("The attendee hasn't been updated because the update was not sent from the attendee.");
+ }
+ }
+ }
+
+ $result = $event->save();
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ if (!$found) {
+ return PEAR::raiseError($error);
+ }
+
+ return true;
+ }
+
+ /**
+ * Lists events for a given time period.
+ *
+ * @param integer $startstamp The start of the time period to retrieve.
+ * @param integer $endstamp The end of the time period to retrieve.
+ * @param array $calendars The calendars to view events from.
+ * Defaults to the user's default calendar.
+ * @param boolean $showRecurrence Return every instance of a recurring event?
+ * If false, will only return recurring events
+ * once inside the $startDate - $endDate range.
+ * @param boolean $alarmsOnly Filter results for events with alarms.
+ * Defaults to false.
+ *
+ * @return array A list of event hashes.
+ */
+ public function listEvents($startstamp = null, $endstamp = null,
+ $calendars = null, $showRecurrence = true,
+ $alarmsOnly = false)
+ {
+ $no_maint = true;
+ require_once dirname(__FILE__) . '/base.php';
+
+ if (!isset($calendars)) {
+ $calendars = array($GLOBALS['prefs']->getValue('default_share'));
+ } elseif (!is_array($calendars)) {
+ $calendars = array($calendars);
+ }
+ $allowed_calendars = Kronolith::listCalendars(false, PERMS_READ);
+ foreach ($calendars as $calendar) {
+ if (!array_key_exists($calendar, $allowed_calendars)) {
+ return PEAR::raiseError(_("Permission Denied"));
+ }
+ }
+
+ return Kronolith::listEvents(new Horde_Date($startstamp),
+ new Horde_Date($endstamp),
+ $calendars, $showRecurrence, $alarmsOnly);
+ }
+
+ /**
+ * Lists alarms for a given moment.
+ *
+ * @param integer $time The time to retrieve alarms for.
+ * @param string $user The user to retrieve alarms for. All users if null.
+ *
+ * @return array An array of UIDs
+ */
+ public function listAlarms($time, $user = null)
+ {
+ $no_maint = true;
+ require_once dirname(__FILE__) . '/base.php';
+ require_once 'Horde/Group.php';
+
+ $current_user = Horde_Auth::getAuth();
+ if ((empty($user) || $user != $current_user) && !Horde_Auth::isAdmin()) {
+ return PEAR::raiseError(_("Permission Denied"));
+ }
+
+ $group = &Group::singleton();
+ $alarm_list = array();
+ $time = new Horde_Date($time);
+ $calendars = is_null($user) ? array_keys($GLOBALS['kronolith_shares']->listAllShares()) : $GLOBALS['display_calendars'];
+ $alarms = Kronolith::listAlarms($time, $calendars, true);
+ if (is_a($alarms, 'PEAR_Error')) {
+ return $alarms;
+ }
+ foreach ($alarms as $calendar => $cal_alarms) {
+ $share = $GLOBALS['kronolith_shares']->getShare($calendar);
+ if (is_a($share, 'PEAR_Error')) {
+ continue;
+ }
+ if (empty($user)) {
+ $users = $share->listUsers(PERMS_READ);
+ $groups = $share->listGroups(PERMS_READ);
+ foreach ($groups as $gid) {
+ $group_users = $group->listUsers($gid);
+ if (!is_a($group_users, 'PEAR_Error')) {
+ $users = array_merge($users, $group_users);
+ }
+ }
+ $users = array_unique($users);
+ } else {
+ $users = array($user);
+ }
+ $owner = $share->get('owner');
+ foreach ($cal_alarms as $event) {
+ foreach ($users as $alarm_user) {
+ if ($alarm_user == $current_user) {
+ $prefs = &$GLOBALS['prefs'];
+ } else {
+ $prefs = &Prefs::singleton($GLOBALS['conf']['prefs']['driver'],
+ 'kronolith', $alarm_user, null,
+ null, false);
+ }
+ $shown_calendars = unserialize($prefs->getValue('display_cals'));
+ $reminder = $prefs->getValue('event_reminder');
+ if (($reminder == 'owner' && $alarm_user == $owner) ||
+ ($reminder == 'show' && in_array($calendar, $shown_calendars)) ||
+ $reminder == 'read') {
+ Horde_Nls::setLanguageEnvironment($prefs->getValue('language'));
+ $alarm = $event->toAlarm($time, $alarm_user, $prefs);
+ if ($alarm) {
+ $alarm_list[] = $alarm;
+ }
+ }
+ }
+ }
+ }
+
+ return $alarm_list;
+ }
+
+ /**
+ * Subscribe to a calendar.
+ *
+ * @param array $calendar Calendar description hash, with required 'type'
+ * parameter. Currently supports 'http' and 'webcal'
+ * for remote calendars.
+ */
+ public function subscribe($calendar)
+ {
+ if (!isset($calendar['type'])) {
+ return PEAR::raiseError(_("Unknown calendar protocol"));
+ }
+
+ switch ($calendar['type']) {
+ case 'http':
+ case 'webcal':
+ $username = isset($calendar['username']) ? $calendar['username'] : null;
+ $password = isset($calendar['password']) ? $calendar['password'] : null;
+
+ $cals = unserialize($GLOBALS['prefs']->getValue('remote_cals'));
+ if (!is_array($cals)) {
+ $cals = array();
+ }
+ $array_key = count($cals);
+ foreach ($cals as $key => $cal) {
+ if ($cal['url'] == $calendar['url']) {
+ $array_key = $key;
+ break;
+ }
+ }
+
+ $cals[$array_key] = array('name' => $calendar['name'],
+ 'url' => $calendar['url'],
+ 'user' => $username,
+ 'password' => $password);
+ $GLOBALS['prefs']->setValue('remote_cals', serialize($cals));
+ break;
+
+ case 'external':
+ $cals = unserialize($GLOBALS['prefs']->getValue('display_external_cals'));
+ if (array_search($calendar['name'], $cals) === false) {
+ $cals[] = $calendar['name'];
+ $GLOBALS['prefs']->setValue('display_external_cals', serialize($cals));
+ }
+
+ default:
+ return PEAR::raiseError(_("Unknown calendar protocol"));
+ }
+ }
+
+ /**
+ * Unsubscribe from a calendar.
+ *
+ * @param array $calendar Calendar description array, with required 'type'
+ * parameter. Currently supports 'http' and 'webcal'
+ * for remote calendars.
+ */
+ public function unsubscribe($calendar)
+ {
+ if (!isset($calendar['type'])) {
+ return PEAR::raiseError('Unknown calendar specification');
+ }
+
+ switch ($calendar['type']) {
+ case 'http':
+ case 'webcal':
+ $cals = unserialize($GLOBALS['prefs']->getValue('remote_cals'));
+ foreach ($cals as $key => $cal) {
+ if ($cal['url'] == $calendar['url']) {
+ unset($cals[$key]);
+ break;
+ }
+ }
+
+ $GLOBALS['prefs']->setValue('remote_cals', serialize($cals));
+ break;
+
+ case 'external':
+ $cals = unserialize($GLOBALS['prefs']->getValue('display_external_cals'));
+ if (($key = array_search($calendar['name'], $cals)) !== false) {
+ unset($cals[$key]);
+ $GLOBALS['prefs']->setValue('display_external_cals', serialize($cals));
+ }
+
+ default:
+ return PEAR::raiseError('Unknown calendar specification');
+ }
+ }
+
+
+ /**
+ * Places an exclusive lock for a calendar or an event.
+ *
+ * @param array $calendar The calendar to lock
+ * @param array $event The event to lock
+ *
+ * @return mixed A lock ID on success, PEAR_Error on failure, false if:
+ * - The calendar is already locked
+ * - The event is already locked
+ * - A calendar lock was requested and an event is already
+ * locked in the calendar
+ */
+ public function lock($calendar, $event = null)
+ {
+ $no_maint = true;
+ require_once dirname(__FILE__) . '/base.php';
+
+ if (!array_key_exists($calendar,
+ Kronolith::listCalendars(false, PERMS_EDIT))) {
+ return PEAR::raiseError(_("Permission Denied"));
+ }
+ $share = &$GLOBALS['kronolith_shares']->getShare($calendar);
+
+ return $share->lock($calendar, $event);
+ }
+
+ /**
+ * Releases a lock.
+ *
+ * @param array $calendar The event to lock.
+ * @param array $lockid The lock id to unlock.
+ */
+ public function unlock($calendar, $lockid)
+ {
+ $no_maint = true;
+ require_once dirname(__FILE__) . '/base.php';
+
+ if (!array_key_exists($calendar,
+ Kronolith::listCalendars(false, PERMS_EDIT))) {
+ return PEAR::raiseError(_("Permission Denied"));
+ }
+ $share = &$GLOBALS['kronolith_shares']->getShare($calendar);
+
+ return $share->unlock($lockid);
+ }
+
+ /**
+ * Check for existing calendar or event locks.
+ *
+ * @param array $calendar The calendar to check locks for.
+ * @param array $event The event to check locks for.
+ */
+ public function checkLocks($calendar, $event = null)
+ {
+ $no_maint = true;
+ require_once dirname(__FILE__) . '/base.php';
+
+ if (!array_key_exists($calendar,
+ Kronolith::listCalendars(false, PERMS_READ))) {
+ return PEAR::raiseError(_("Permission Denied"));
+ }
+ $share = &$GLOBALS['kronolith_shares']->getShare($calendar);
+
+ return $share->checkLocks($event);
+ }
+
+ /**
+ *
+ * @return array A list of calendars used to display free/busy information
+ */
+ public function getFbCalendars()
+ {
+ return (unserialize($GLOBALS['prefs']->getValue('fb_cals')));
+ }
+
+}
+++ /dev/null
-<?php
-/**
- * Kronolith external API interface.
- *
- * This file defines Kronolith's external API interface. Other applications
- * can interact with Kronolith through this API.
- *
- * @package Kronolith
- */
-
-$_services['show'] = array(
- 'link' => '%application%/event.php?calendar=|calendar|' .
- '&eventID=|event|&uid=|uid|'
-);
-
-$_services['perms'] = array(
- 'args' => array(),
- 'type' => '{urn:horde}stringArray'
-);
-
-$_services['removeUserData'] = array(
- 'args' => array('user' => 'string'),
- 'type' => 'boolean'
-);
-
-$_services['shareHelp'] = array(
- 'args' => array(),
- 'type' => 'string');
-
-$_services['modified'] = array(
- 'args' => array('uid' => 'string'),
- 'type' => 'int',
-);
-
-$_services['browse'] = array(
- 'args' => array('path' => 'string', 'properties' => '{urn:horde}stringArray'),
- 'type' => '{urn:horde}hashHash',
-);
-
-$_services['put'] = array(
- 'args' => array('path' => 'string', 'content' => 'string', 'content_type' => 'string'),
- 'type' => '{urn:horde}stringArray',
-);
-
-$_services['path_delete'] = array(
- 'args' => array('path' => 'string'),
- 'type' => 'boolean',
-);
-
-$_services['getFreeBusy'] = array(
- 'args' => array('startstamp' => 'int', 'endstamp' => 'int', 'calendar' => 'string'),
- 'type' => '{urn:horde}stringArray'
-);
-
-$_services['listCalendars'] = array(
- 'args' => array('owneronly' => 'boolean', 'permission' => 'int'),
- 'type' => '{urn:horde}stringArray'
-);
-
-$_services['listEvents'] = array(
- 'args' => array('startstamp' => 'int', 'endstamp' => 'int', 'calendar' => 'string', 'showRecurrence' => 'string', 'alarmsOnly' => 'boolean'),
- 'type' => '{urn:horde}hashHash'
-);
-
-$_services['listAlarms'] = array(
- 'args' => array('time' => 'int', 'user' => 'string'),
- 'type' => '{urn:horde}hashHash'
-);
-
-$_services['list'] = array(
- 'args' => array(),
- 'type' => '{urn:horde}stringArray'
-);
-
-$_services['listBy'] = array(
- 'args' => array('action' => 'string', 'timestamp' => 'int'),
- 'type' => '{urn:horde}stringArray'
-);
-
-$_services['getActionTimestamp'] = array(
- 'args' => array('uid' => 'string', 'action' => 'string', 'calendar' => 'string'),
- 'type' => 'int',
-);
-
-$_services['import'] = array(
- 'args' => array('content' => 'string', 'contentType' => 'string', 'calendar' => 'string'),
- 'type' => 'string'
-);
-
-$_services['export'] = array(
- 'args' => array('uid' => 'string', 'contentType' => 'string'),
- 'type' => 'string'
-);
-
-$_services['exportCalendar'] = array(
- 'args' => array('calendar' => 'string', 'contentType' => 'string'),
- 'type' => 'string'
-);
-
-$_services['delete'] = array(
- 'args' => array('uid' => 'string'),
- 'type' => 'boolean'
-);
-
-$_services['replace'] = array(
- 'args' => array('uid' => 'string', 'content' => 'string', 'contentType' => 'string'),
- 'type' => 'boolean'
-);
-
-// FIXME: create complex type definition for SOAP calls.
-$_services['eventFromUID'] = array(
- 'args' => array('uid' => 'string'),
- 'type' => 'object'
-);
-
-// FIXME: create complex type definition for SOAP calls.
-$_services['updateAttendee'] = array(
- 'args' => array('response' => 'object', 'sender' => 'string'),
- 'type' => 'boolean'
-);
-
-$_services['subscribe'] = array(
- 'args' => array('calendar' => '{urn:horde}stringArray'),
- 'type' => 'boolean',
-);
-
-$_services['unsubscribe'] = array(
- 'args' => array('calendar' => '{urn:horde}stringArray'),
- 'type' => 'boolean',
-);
-
-$_services['lock'] = array(
- 'args' => array('calendar' => 'string', 'event' => 'string'),
- 'type' => 'string'
-);
-
-$_services['unlock'] = array(
- 'args' => array('calendar' => 'string', 'lockid' => 'string'),
- 'type' => 'string'
-);
-
-$_services['checkLocks'] = array(
- 'args' => array('calendar' => 'string', 'event' => 'string'),
- 'type' => 'string'
-);
-
-$_services['getFbCalendars'] = array(
- 'args' => array(),
- 'type' => '{urn:horde}stringArray'
-);
-
-/**
- * Returns a list of available permissions.
- *
- * @return array An array describing all available permissions.
- */
-function _kronolith_perms()
-{
- $perms = array();
- $perms['tree']['kronolith']['max_events'] = false;
- $perms['title']['kronolith:max_events'] = _("Maximum Number of Events");
- $perms['type']['kronolith:max_events'] = 'int';
-
- return $perms;
-}
-
-/**
- * Removes user data.
- *
- * @param string $user Name of user to remove data for.
- *
- * @return mixed true on success | PEAR_Error on failure
- */
-function _kronolith_removeUserData($user)
-{
- if (!Horde_Auth::isAdmin() && $user != Horde_Auth::getAuth()) {
- return PEAR::raiseError(_("You are not allowed to remove user data."));
- }
-
- $no_maint = true;
- require_once dirname(__FILE__) . '/base.php';
-
- /* Remove all events owned by the user in all calendars. */
- $result = Kronolith::getDriver()->removeUserData($user);
-
- /* Now delete history as well. */
- $history = &Horde_History::singleton();
- if (method_exists($history, 'removeByParent')) {
- $histories = $history->removeByParent('kronolith:' . $user);
- } else {
- /* Remove entries 100 at a time. */
- $all = $history->getByTimestamp('>', 0, array(), 'kronolith:' . $user);
- if (is_a($all, 'PEAR_Error')) {
- Horde::logMessage($all, __FILE__, __LINE__, PEAR_LOG_ERR);
- } else {
- $all = array_keys($all);
- while (count($d = array_splice($all, 0, 100)) > 0) {
- $history->removebyNames($d);
- }
- }
- }
-
- if (is_a($result, 'PEAR_Error')) {
- return $result;
- }
-
- /* Get the user's default share */
- $share = $GLOBALS['kronolith_shares']->getShare($user);
- if (is_a($share, 'PEAR_Error')) {
- Horde::logMessage($share, __FILE__, __LINE__, PEAR_LOG_ERR);
- } else {
- $result = $GLOBALS['kronolith_shares']->removeShare($share);
- if (is_a($result, 'PEAR_Error')) {
- $hasError = true;
- Horde::logMessage($result->getMessage(), __FILE__, __LINE__, PEAR_LOG_ERR);
- }
- }
-
- /* Get a list of all shares this user has perms to and remove the perms */
- $shares = $GLOBALS['kronolith_shares']->listShares($user);
- if (is_a($shares, 'PEAR_Error')) {
- Horde::logMessage($shares, __FILE__, __LINE__, PEAR_LOG_ERR);
- }
- foreach ($shares as $share) {
- $share->removeUser($user);
- }
-
- return true;
-}
-
-/**
- * Returns the share helper prefix
- *
- * @return string
- */
-function _kronolith_shareHelp()
-{
- return 'shares';
-}
-
-/**
- * Returns the last modification timestamp for the given uid.
- *
- * @param string $uid The uid to look for.
- *
- * @return integer The timestamp for the last modification of $uid.
- */
-function _kronolith_modified($uid)
-{
- $modified = _kronolith_getActionTimestamp($uid, 'modify');
- if (empty($modified)) {
- $modified = _kronolith_getActionTimestamp($uid, 'add');
- }
- return $modified;
-}
-
-/**
- * Browse through Kronolith's object tree.
- *
- * @param string $path The level of the tree to browse.
- * @param array $properties The item properties to return. Defaults to 'name',
- * 'icon', and 'browseable'.
- *
- * @return array The contents of $path
- */
-function _kronolith_browse($path = '', $properties = array())
-{
- $no_maint = true;
- require_once dirname(__FILE__) . '/base.php';
- global $registry;
-
- // Default properties.
- if (!$properties) {
- $properties = array('name', 'icon', 'browseable');
- }
-
- if (substr($path, 0, 9) == 'kronolith') {
- $path = substr($path, 9);
- }
- $path = trim($path, '/');
- $parts = explode('/', $path);
-
- if (empty($path)) {
- //
- // This request is for a list of all users who have calendars visible
- // to the requesting user.
- //
- $calendars = Kronolith::listCalendars(false, PERMS_READ);
- $owners = array();
- foreach ($calendars as $calendar) {
- $owners[$calendar->get('owner')] = true;
- }
-
- $results = array();
- foreach (array_keys($owners) as $owner) {
- $path = 'kronolith/' . $owner;
- if (in_array('name', $properties)) {
- $results[$path]['name'] = $owner;
- }
- if (in_array('icon', $properties)) {
- $results[$path]['icon'] =
- $registry->getImageDir('horde') . '/user.png';
- }
- if (in_array('browseable', $properties)) {
- $results[$path]['browseable'] = true;
- }
- if (in_array('contenttype', $properties)) {
- $results[$path]['contenttype'] =
- 'httpd/unix-directory';
- }
- if (in_array('contentlength', $properties)) {
- $results[$path]['contentlength'] = 0;
- }
- if (in_array('modified', $properties)) {
- $results[$path]['modified'] =
- $_SERVER['REQUEST_TIME'];
- }
- if (in_array('created', $properties)) {
- $results[$path]['created'] = 0;
- }
-
- // CalDAV Properties from RFC 4791 and
- // draft-desruisseaux-caldav-sched-03
- $caldavns = 'urn:ietf:params:xml:ns:caldav';
- $kronolith_rpc_base = $GLOBALS['registry']->get('webroot', 'horde') . '/rpc/kronolith/';
- if (in_array($caldavns . ':calendar-home-set', $properties)) {
- $results[$path][$caldavns . ':calendar-home-set'] = Horde::url($kronolith_rpc_base . urlencode($owner), true);
- }
-
- if (in_array($caldavns . ':calendar-user-address-set', $properties)) {
- // FIXME: Add the calendar owner's email address from their Horde Identity
- }
- }
- return $results;
-
- } elseif (count($parts) == 1) {
- //
- // This request is for all calendars owned by the requested user
- //
- $calendars = $GLOBALS['kronolith_shares']->listShares(Horde_Auth::getAuth(),
- PERMS_SHOW,
- $parts[0]);
- $results = array();
- foreach ($calendars as $calendarId => $calendar) {
- $retpath = 'kronolith/' . $parts[0] . '/' . $calendarId;
- if (in_array('name', $properties)) {
- $results[$retpath]['name'] = sprintf(_("Events from %s"), $calendar->get('name'));
- $results[$retpath . '.ics']['name'] = $calendar->get('name');
- }
- if (in_array('displayname', $properties)) {
- $results[$retpath]['displayname'] = rawurlencode($calendar->get('name'));
- $results[$retpath . '.ics']['displayname'] = rawurlencode($calendar->get('name')) . '.ics';
- }
- if (in_array('icon', $properties)) {
- $results[$retpath]['icon'] = $registry->getImageDir() . '/kronolith.png';
- $results[$retpath . '.ics']['icon'] = $registry->getImageDir() . '/mime/icalendar.png';
- }
- if (in_array('browseable', $properties)) {
- $results[$retpath]['browseable'] = $calendar->hasPermission(Horde_Auth::getAuth(), PERMS_READ);
- $results[$retpath . '.ics']['browseable'] = false;
- }
- if (in_array('contenttype', $properties)) {
- $results[$retpath]['contenttype'] = 'httpd/unix-directory';
- $results[$retpath . '.ics']['contenttype'] = 'text/calendar';
- }
- if (in_array('contentlength', $properties)) {
- $results[$retpath]['contentlength'] = 0;
- // FIXME: This is a hack. If the content length is longer
- // than the actual data then some WebDAV clients will report
- // an error when the file EOF is received. Ideally we should
- // determine the actual size of the .ics and report it here, but
- // the performance hit may be prohibitive. This requires
- // further investigation.
- $results[$retpath . '.ics']['contentlength'] = 1;
- }
- if (in_array('modified', $properties)) {
- $results[$retpath]['modified'] = $_SERVER['REQUEST_TIME'];
- $results[$retpath . '.ics']['modified'] = $_SERVER['REQUEST_TIME'];
- }
- if (in_array('created', $properties)) {
- $results[$retpath]['created'] = 0;
- $results[$retpath . '.ics']['created'] = 0;
- }
- }
- return $results;
-
- } elseif (count($parts) == 2 &&
- array_key_exists($parts[1],
- Kronolith::listCalendars(false, PERMS_READ))) {
- //
- // This request is browsing into a specific calendar. Generate the list
- // of items and represent them as files within the directory.
- //
- $kronolith_driver = Kronolith::getDriver(null, $parts[1]);
- $events = $kronolith_driver->listEvents();
- if (is_a($events, 'PEAR_Error')) {
- return $events;
- }
-
- $icon = $registry->getImageDir('horde') . '/mime/icalendar.png';
- $results = array();
- foreach ($events as $dayevents) {
- foreach ($dayevents as $event) {
- $key = 'kronolith/' . $path . '/' . $event->getId();
- if (in_array('name', $properties)) {
- $results[$key]['name'] = $event->getTitle();
- }
- if (in_array('icon', $properties)) {
- $results[$key]['icon'] = $icon;
- }
- if (in_array('browseable', $properties)) {
- $results[$key]['browseable'] = false;
- }
- if (in_array('contenttype', $properties)) {
- $results[$key]['contenttype'] = 'text/calendar';
- }
- if (in_array('contentlength', $properties)) {
- // FIXME: This is a hack. If the content length is longer
- // than the actual data then some WebDAV clients will report
- // an error when the file EOF is received. Ideally we should
- // determine the actual size of the data and report it here, but
- // the performance hit may be prohibitive. This requires
- // further investigation.
- $results[$key]['contentlength'] = 1;
- }
- if (in_array('modified', $properties)) {
- $results[$key]['modified'] = _kronolith_modified($event->getUID());
- }
- if (in_array('created', $properties)) {
- $results[$key]['created'] = _kronolith_getActionTimestamp($event->getUID(), 'add');
- }
- }
- }
- return $results;
- } else {
- //
- // The only valid request left is for either a specific event
- // or for the entire calendar.
- //
- if (count($parts) == 3 &&
- array_key_exists($parts[1],
- Kronolith::listCalendars(false, PERMS_READ))) {
- //
- // This request is for a specific item within a given calendar.
- //
- $event = Kronolith::getCalendar(null, $parts[1])->getEvent($parts[2]);
- if (is_a($event, 'PEAR_Error')) {
- return $event;
- }
-
- $result = array(
- 'data' => _kronolith_export($event->getUID(), 'text/calendar'),
- 'mimetype' => 'text/calendar');
- $modified = _kronolith_modified($event->getUID());
- if (!empty($modified)) {
- $result['mtime'] = $modified;
- }
- return $result;
- } elseif (count($parts) == 2 &&
- substr($parts[1], -4, 4) == '.ics' &&
- array_key_exists(
- substr($parts[1], 0, -4),
- Kronolith::listCalendars(false, PERMS_READ))) {
- //
- // This request is for an entire calendar (calendar.ics).
- //
- $ical_data = _kronolith_exportCalendar(substr($parts[1], 0, -4), 'text/calendar');
- $result = array('data' => $ical_data,
- 'mimetype' => 'text/calendar',
- 'contentlength' => strlen($ical_data),
- 'mtime' => $_SERVER['REQUEST_TIME']);
-
- return $result;
- } else {
- //
- // All other requests are a 404: Not Found
- //
- return false;
- }
- }
-}
-
-/**
- * Saves a file into the Kronolith tree.
- *
- * @param string $path The path where to PUT the file.
- * @param string $content The file content.
- * @param string $content_type The file's content type.
- *
- * @return array The event UIDs, or a PEAR_Error on failure.
- */
-function _kronolith_put($path, $content, $content_type)
-{
- $no_maint = true;
- require_once dirname(__FILE__) . '/base.php';
-
- if (substr($path, 0, 9) == 'kronolith') {
- $path = substr($path, 9);
- }
- $path = trim($path, '/');
- $parts = explode('/', $path);
-
- if (count($parts) == 2 &&
- substr($parts[1], -4) == '.ics') {
-
- // Workaround for WebDAV clients that are not smart enough to send
- // the right content type. Assume text/calendar.
- if ($content_type == 'application/octet-stream') {
- $content_type = 'text/calendar';
- }
- $calendar = substr($parts[1], 0, -4);
- } elseif (count($parts) == 3) {
- $calendar = $parts[1];
-
- // Workaround for WebDAV clients that are not smart enough to send
- // the right content type. Assume text/calendar.
- if ($content_type == 'application/octet-stream') {
- $content_type = 'text/calendar';
- }
- } else {
- return PEAR::raiseError("Invalid calendar data supplied.");
- }
-
- if (!array_key_exists($calendar, Kronolith::listCalendars(false, PERMS_EDIT))) {
- // FIXME: Should we attempt to create a calendar based on the filename
- // in the case that the requested calendar does not exist?
- return PEAR::raiseError("Calendar does not exist or no permission to edit");
- }
-
- // Store all currently existings UIDs. Use this info to delete UIDs not
- // present in $content after processing.
- $ids = array();
- $uids_remove = array_flip(_kronolith_list($calendar));
-
- switch ($content_type) {
- case 'text/calendar':
- case 'text/x-vcalendar':
- $iCal = new Horde_iCalendar();
- if (!is_a($content, 'Horde_iCalendar_vevent')) {
- if (!$iCal->parsevCalendar($content)) {
- return PEAR::raiseError(_("There was an error importing the iCalendar data."));
- }
- } else {
- $iCal->addComponent($content);
- }
-
- $kronolith_driver = Kronolith::getDriver();
- foreach ($iCal->getComponents() as $content) {
- if (is_a($content, 'Horde_iCalendar_vevent')) {
- $event = $kronolith_driver->getEvent();
- $event->fromiCalendar($content);
- $event->setCalendar($calendar);
- $uid = $event->getUID();
- // Remove from uids_remove list so we won't delete in
- // the end.
- if (isset($uids_remove[$uid])) {
- unset($uids_remove[$uid]);
- }
- $existing_event = $kronolith_driver->getByUID($uid, array($calendar));
- if (!is_a($existing_event, 'PEAR_Error')) {
- // Check if our event is newer then the existing - get the
- // event's history.
- $history = &Horde_History::singleton();
- $created = $modified = null;
- $log = $history->getHistory('kronolith:' . $calendar . ':'
- . $uid);
- if ($log && !is_a($log, 'PEAR_Error')) {
- foreach ($log->getData() as $entry) {
- switch ($entry['action']) {
- case 'add':
- $created = $entry['ts'];
- break;
-
- case 'modify':
- $modified = $entry['ts'];
- break;
- }
- }
- }
- if (empty($modified) && !empty($created)) {
- $modified = $created;
- }
- if (!empty($modified) &&
- $modified >= $content->getAttribute('LAST-MODIFIED')) {
- // LAST-MODIFIED timestamp of existing entry is newer:
- // don't replace it.
- continue;
- }
-
- // Don't change creator/owner.
- $event->setCreatorId($existing_event->getCreatorId());
- }
-
- // Save entry.
- $saved = $event->save();
- if (is_a($saved, 'PEAR_Error')) {
- return $saved;
- }
- $ids[] = $event->getUID();
- }
- }
- break;
-
- default:
- return PEAR::raiseError(sprintf(_("Unsupported Content-Type: %s"), $content_type));
- }
-
- if (array_key_exists($calendar, Kronolith::listCalendars(false, PERMS_DELETE))) {
- foreach (array_keys($uids_remove) as $uid) {
- _kronolith_delete($uid);
- }
- }
-
- return $ids;
-}
-
-/**
- * Deletes a file from the Kronolith tree.
- *
- * @param string $path The path to the file.
- *
- * @return mixed The event's UID, or a PEAR_Error on failure.
- */
-function _kronolith_path_delete($path)
-{
- $no_maint = true;
- require_once dirname(__FILE__) . '/base.php';
-
- if (substr($path, 0, 9) == 'kronolith') {
- $path = substr($path, 9);
- }
- $path = trim($path, '/');
- $parts = explode('/', $path);
-
- if (substr($parts[1], -4) == '.ics') {
- $calendarId = substr($parts[1], 0, -4);
- } else {
- $calendarId = $parts[1];
- }
-
- if (!(count($parts) == 2 || count($parts) == 3) ||
- !array_key_exists($calendarId, Kronolith::listCalendars(false, PERMS_DELETE))) {
- return PEAR::raiseError("Calendar does not exist or no permission to delete");
- }
-
- if (count($parts) == 3) {
- // Delete just a single entry
- return Kronolith::getDriver(null, $calendarId)->deleteEvent($parts[2]);
- } else {
- // Delete the entire calendar
- $result = Kronolith::getDriver()->delete($calendarId);
- if (is_a($result, 'PEAR_Error')) {
- return PEAR::raiseError(sprintf(_("Unable to delete calendar \"%s\": %s"), $calendarId, $result->getMessage()));
- } else {
- // Remove share and all groups/permissions.
- $share = $GLOBALS['kronolith_shares']->getShare($calendarId);
- $result = $GLOBALS['kronolith_shares']->removeShare($share);
- if (is_a($result, 'PEAR_Error')) {
- return $result;
- }
- }
- }
-}
-
-/**
- * Returns all calendars a user has access to, according to several
- * parameters/permission levels.
- *
- * @param boolean $owneronly Only return calenders that this user owns?
- * Defaults to false.
- * @param integer $permission The permission to filter calendars by.
- *
- * @return array The calendar list.
- */
-function _kronolith_listCalendars($owneronly = false, $permission = null)
-{
- $no_maint = true;
- require_once dirname(__FILE__) . '/base.php';
- if (is_null($permission)) {
- $permission = PERMS_SHOW;
- }
- return array_keys(Kronolith::listCalendars($owneronly, $permission));
-}
-
-/**
- * Returns the ids of all the events that happen within a time period.
- *
- * @param string $calendar The calendar to check for events.
- * @param object $startstamp The start of the time range.
- * @param object $endstamp The end of the time range.
- *
- * @return array The event ids happening in this time period.
- */
-function _kronolith_list($calendar = null, $startstamp = 0, $endstamp = 0)
-{
- $no_maint = true;
- require_once dirname(__FILE__) . '/base.php';
-
- if (empty($calendar)) {
- $calendar = Kronolith::getDefaultCalendar();
- }
- if (!array_key_exists($calendar,
- Kronolith::listCalendars(false, PERMS_READ))) {
- return PEAR::raiseError(_("Permission Denied"));
- }
-
- $driver = Kronolith::getDriver(null, $calendar);
- if (is_a($driver, 'PEAR_Error')) {
- return $driver;
- }
- $events = $driver->listEvents(new Horde_Date($startstamp),
- new Horde_Date($endstamp));
- if (is_a($events, 'PEAR_Error')) {
- return $events;
- }
-
- $uids = array();
- foreach ($events as $dayevents) {
- foreach ($dayevents as $event) {
- $uids[] = $event->getUID();
- }
- }
-
- return $uids;
-}
-
-/**
- * Returns an array of UIDs for events that have had $action happen since
- * $timestamp.
- *
- * @param string $action The action to check for - add, modify, or delete.
- * @param integer $timestamp The time to start the search.
- * @param string $calendar The calendar to search in.
- *
- * @return array An array of UIDs matching the action and time criteria.
- */
-function _kronolith_listBy($action, $timestamp, $calendar = null)
-{
- $no_maint = true;
- require_once dirname(__FILE__) . '/base.php';
-
- if (empty($calendar)) {
- $calendar = Kronolith::getDefaultCalendar();
- }
-
- if (!array_key_exists($calendar,
- Kronolith::listCalendars(false, PERMS_READ))) {
- return PEAR::raiseError(_("Permission Denied"));
- }
-
- $history = &Horde_History::singleton();
- $histories = $history->getByTimestamp('>', $timestamp, array(array('op' => '=', 'field' => 'action', 'value' => $action)), 'kronolith:' . $calendar);
- if (is_a($histories, 'PEAR_Error')) {
- return $histories;
- }
-
- // Strip leading kronolith:username:.
- return preg_replace('/^([^:]*:){2}/', '', array_keys($histories));
-}
-
-/**
- * Returns the timestamp of an operation for a given uid an action
- *
- * @param string $uid The uid to look for.
- * @param string $action The action to check for - add, modify, or delete.
- * @param string $calendar The calendar to search in.
- *
- * @return integer The timestamp for this action.
- */
-function _kronolith_getActionTimestamp($uid, $action, $calendar = null)
-{
- $no_maint = true;
- require_once dirname(__FILE__) . '/base.php';
-
- if (empty($calendar)) {
- $calendar = Kronolith::getDefaultCalendar();
- }
-
- if (!array_key_exists($calendar,
- Kronolith::listCalendars(false, PERMS_READ))) {
- return PEAR::raiseError(_("Permission Denied"));
- }
-
- $history = &Horde_History::singleton();
- return $history->getActionTimestamp('kronolith:' . $calendar . ':' .
- $uid, $action);
-}
-
-/**
- * Imports an event represented in the specified content type.
- *
- * @param string $content The content of the event.
- * @param string $contentType What format is the data in? Currently supports:
- * <pre>
- * text/calendar
- * text/x-vcalendar
- * </pre>
- * @param string $calendar What calendar should the event be added to?
- *
- * @return mixed The event's UID, or a PEAR_Error on failure.
- */
-function _kronolith_import($content, $contentType, $calendar = null)
-{
- $no_maint = true;
- require_once dirname(__FILE__) . '/base.php';
-
- if (!isset($calendar)) {
- $calendar = Kronolith::getDefaultCalendar(PERMS_EDIT);
- }
- if (!array_key_exists($calendar,
- Kronolith::listCalendars(false, PERMS_EDIT))) {
- return PEAR::raiseError(_("Permission Denied"));
- }
-
- switch ($contentType) {
- case 'text/calendar':
- case 'text/x-vcalendar':
- $iCal = new Horde_iCalendar();
- if (!is_a($content, 'Horde_iCalendar_vevent')) {
- if (!$iCal->parsevCalendar($content)) {
- return PEAR::raiseError(_("There was an error importing the iCalendar data."));
- }
- } else {
- $iCal->addComponent($content);
- }
-
- $components = $iCal->getComponents();
- if (count($components) == 0) {
- return PEAR::raiseError(_("No iCalendar data was found."));
- }
-
- $kronolith_driver = Kronolith::getDriver(null, $calendar);
- $ids = array();
- foreach ($components as $content) {
- if (is_a($content, 'Horde_iCalendar_vevent')) {
- $event = $kronolith_driver->getEvent();
- $event->fromiCalendar($content);
- $event->setCalendar($calendar);
- // Check if the entry already exists in the data source, first
- // by UID.
- $uid = $event->getUID();
- $existing_event = $kronolith_driver->getByUID($uid, array($calendar));
- if (!is_a($existing_event, 'PEAR_Error')) {
- return PEAR::raiseError(_("Already Exists"),
- 'horde.message', null, null, $uid);
- }
- $result = $kronolith_driver->search($event);
- // Check if the match really is an exact match:
- if (is_array($result) && count($result) > 0) {
- foreach($result as $match) {
- if ($match->start == $event->start &&
- $match->end == $event->end &&
- $match->title == $event->title &&
- $match->location == $event->location &&
- $match->hasPermission(PERMS_EDIT)) {
- return PEAR::raiseError(_("Already Exists"), 'horde.message', null, null, $match->getUID());
- }
- }
- }
-
- $eventId = $event->save();
- if (is_a($eventId, 'PEAR_Error')) {
- return $eventId;
- }
- $ids[] = $event->getUID();
- }
- }
- if (count($ids) == 0) {
- return PEAR::raiseError(_("No iCalendar data was found."));
- } else if (count($ids) == 1) {
- return $ids[0];
- }
- return $ids;
- }
-
- return PEAR::raiseError(sprintf(_("Unsupported Content-Type: %s"), $contentType));
-}
-
-/**
- * Exports an event, identified by UID, in the requested content type.
- *
- * @param string $uid Identify the event to export.
- * @param string $contentType What format should the data be in?
- * A string with one of:
- * <pre>
- * text/calendar (VCALENDAR 2.0. Recommended as
- * this is specified in rfc2445)
- * text/x-vcalendar (old VCALENDAR 1.0 format.
- * Still in wide use)
- * </pre>
- *
- * @return string The requested data.
- */
-function _kronolith_export($uid, $contentType)
-{
- $no_maint = true;
- require_once dirname(__FILE__) . '/base.php';
- global $kronolith_shares;
-
- $event = Kronolith::getDriver()->getByUID($uid);
- if (is_a($event, 'PEAR_Error')) {
- return $event;
- }
- if (!$event->hasPermission(PERMS_READ)) {
- return PEAR::raiseError(_("Permission Denied"));
- }
-
- $version = '2.0';
- switch ($contentType) {
- case 'text/x-vcalendar':
- $version = '1.0';
- case 'text/calendar':
- $share = &$kronolith_shares->getShare($event->getCalendar());
-
- $iCal = new Horde_iCalendar($version);
- $iCal->setAttribute('X-WR-CALNAME', Horde_String::convertCharset($share->get('name'), Horde_Nls::getCharset(), 'utf-8'));
-
- // Create a new vEvent.
- $vEvent = &$event->toiCalendar($iCal);
- $iCal->addComponent($vEvent);
-
- return $iCal->exportvCalendar();
-
- }
-
- return PEAR::raiseError(sprintf(_("Unsupported Content-Type: %s"), $contentType));;
-}
-
-/**
- * Exports a calendar in the requested content type.
- *
- * @param string $calendar The calendar to export.
- * @param string $contentType What format should the data be in?
- * A string with one of:
- * <pre>
- * text/calendar (VCALENDAR 2.0. Recommended as
- * this is specified in rfc2445)
- * text/x-vcalendar (old VCALENDAR 1.0 format.
- * Still in wide use)
- * </pre>
- *
- * @return string The iCalendar representation of the calendar.
- */
-function _kronolith_exportCalendar($calendar, $contentType)
-{
- $no_maint = true;
- require_once dirname(__FILE__) . '/base.php';
- global $kronolith_shares;
-
- if (!array_key_exists($calendar,
- Kronolith::listCalendars(false, PERMS_READ))) {
- return PEAR::raiseError(_("Permission Denied"));
- }
-
- $kronolith_driver = Kronolith::getDriver(null, $calendar);
- $events = $kronolith_driver->listEvents();
-
- $version = '2.0';
- switch ($contentType) {
- case 'text/x-vcalendar':
- $version = '1.0';
- case 'text/calendar':
- $share = &$kronolith_shares->getShare($calendar);
-
- $iCal = new Horde_iCalendar($version);
- $iCal->setAttribute('X-WR-CALNAME', Horde_String::convertCharset($share->get('name'), Horde_Nls::getCharset(), 'utf-8'));
-
- foreach ($events as $dayevents) {
- foreach ($dayevents as $event) {
- $vEvent = &$event->toiCalendar($iCal);
- $iCal->addComponent($vEvent);
- }
- }
-
- return $iCal->exportvCalendar();
- }
-
- return PEAR::raiseError(sprintf(_("Unsupported Content-Type: %s"), $contentType));
-}
-
-/**
- * Deletes an event identified by UID.
- *
- * @param string|array $uid A single UID or an array identifying the event(s)
- * to delete.
- *
- * @return boolean Success or failure.
- */
-function _kronolith_delete($uid)
-{
- // Handle an array of UIDs for convenience of deleting multiple events at
- // once.
- if (is_array($uid)) {
- foreach ($uid as $g) {
- $result = _kronolith_delete($g);
- if (is_a($result, 'PEAR_Error')) {
- return $result;
- }
- }
-
- return true;
- }
-
- $no_maint = true;
- require_once dirname(__FILE__) . '/base.php';
-
- $kronolith_driver = Kronolith::getDriver();
- $events = $kronolith_driver->getByUID($uid, null, true);
- if (is_a($events, 'PEAR_Error')) {
- return $events;
- }
-
- $event = null;
- if (Horde_Auth::isAdmin()) {
- $event = $events[0];
- }
-
- // First try the user's own calendars.
- if (empty($event)) {
- $ownerCalendars = Kronolith::listCalendars(true, PERMS_DELETE);
- foreach ($events as $ev) {
- if (Horde_Auth::isAdmin() || isset($ownerCalendars[$ev->getCalendar()])) {
- $event = $ev;
- break;
- }
- }
- }
-
- // If not successful, try all calendars the user has access to.
- if (empty($event)) {
- $deletableCalendars = Kronolith::listCalendars(false, PERMS_DELETE);
- foreach ($events as $ev) {
- if (isset($deletableCalendars[$ev->getCalendar()])) {
- $kronolith_driver->open($ev->getCalendar());
- $event = $ev;
- break;
- }
- }
- }
-
- if (empty($event)) {
- return PEAR::raiseError(_("Permission Denied"));
- }
-
- return $kronolith_driver->deleteEvent($event->getId());
-}
-
-/**
- * Replaces the event identified by UID with the content represented in the
- * specified contentType.
- *
- * @param string $uid Idenfity the event to replace.
- * @param mixed $content The content of the event. String or
- * Horde_iCalendar_vevent
- * @param string $contentType What format is the data in? Currently supports:
- * text/calendar
- * text/x-vcalendar
- * (Ignored if content is Horde_iCalendar_vevent)
- *
- * @return mixed True on success, PEAR_Error otherwise.
- */
-function _kronolith_replace($uid, $content, $contentType)
-{
- $no_maint = true;
- require_once dirname(__FILE__) . '/base.php';
-
- $event = Kronolith::getDriver()->getByUID($uid);
- if (is_a($event, 'PEAR_Error')) {
- return $event;
- }
-
- if (!$event->hasPermission(PERMS_EDIT) ||
- ($event->isPrivate() && $event->getCreatorId() != Horde_Auth::getAuth())) {
- return PEAR::raiseError(_("Permission Denied"));
- }
-
- if (is_a($content, 'Horde_iCalendar_vevent')) {
- $component = $content;
- } else {
- switch ($contentType) {
- case 'text/calendar':
- case 'text/x-vcalendar':
- if (!is_a($content, 'Horde_iCalendar_vevent')) {
- $iCal = new Horde_iCalendar();
- if (!$iCal->parsevCalendar($content)) {
- return PEAR::raiseError(_("There was an error importing the iCalendar data."));
- }
-
- $components = $iCal->getComponents();
- $component = null;
- foreach ($components as $content) {
- if (is_a($content, 'Horde_iCalendar_vevent')) {
- if ($component !== null) {
- return PEAR::raiseError(_("Multiple iCalendar components found; only one vEvent is supported."));
- }
- $component = $content;
- }
-
- }
- if ($component === null) {
- return PEAR::raiseError(_("No iCalendar data was found."));
- }
- }
- break;
- default:
- return PEAR::raiseError(sprintf(_("Unsupported Content-Type: %s"), $contentType));
- }
- }
-
- $event->fromiCalendar($component);
- // Ensure we keep the original UID, even when content does not
- // contain one and fromiCalendar creates a new one.
- $event->setUID($uid);
- $eventId = $event->save();
-
- return is_a($eventId, 'PEAR_Error') ? $eventId : true;
-}
-
-/**
- * Generates free/busy information for a given time period.
- *
- * @param integer $startstamp The start of the time period to retrieve.
- * @param integer $endstamp The end of the time period to retrieve.
- * @param string $calendar The calendar to view free/busy slots for.
- * Defaults to the user's default calendar.
- *
- * @return Horde_iCalendar_vfreebusy A freebusy object that covers the
- * specified time period.
- */
-function _kronolith_getFreeBusy($startstamp = null, $endstamp = null,
- $calendar = null)
-{
- $no_maint = true;
- require_once dirname(__FILE__) . '/base.php';
-
- if (is_null($calendar)) {
- $calendar = Kronolith::getDefaultCalendar();
- }
- // Free/Busy information is globally available; no permission
- // check is needed.
-
- return Kronolith_FreeBusy::generate($calendar, $startstamp, $endstamp, true);
-}
-
-/**
- * Retrieves a Kronolith_Event object, given an event UID.
- *
- * @param string $uid The event's UID.
- *
- * @return Kronolith_Event A valid Kronolith_Event on success, or a PEAR_Error
- * on failure.
- */
-function &_kronolith_eventFromUID($uid)
-{
- $no_maint = true;
- require_once dirname(__FILE__) . '/base.php';
-
- $event = Kronolith::getDriver()->getByUID($uid);
- if (is_a($event, 'PEAR_Error')) {
- return $event;
- }
- if (!$event->hasPermission(PERMS_SHOW)) {
- return PEAR::raiseError(_("Permission Denied"));
- }
-
- return $event;
-}
-
-/**
- * Updates an attendee's response status for a specified event.
- *
- * @param Horde_iCalender_vevent $response A Horde_iCalender_vevent object,
- * with a valid UID attribute that
- * points to an existing event.
- * This is typically the vEvent
- * portion of an iTip meeting-request
- * response, with the attendee's
- * response in an ATTENDEE parameter.
- * @param string $sender The email address of the person
- * initiating the update. Attendees
- * are only updated if this address
- * matches.
- *
- * @return mixed True on success, PEAR_Error on failure.
- */
-function _kronolith_updateAttendee($response, $sender = null)
-{
- $no_maint = true;
- require_once dirname(__FILE__) . '/base.php';
-
- $uid = $response->getAttribute('UID');
- if (is_a($uid, 'PEAR_Error')) {
- return $uid;
- }
-
- $events = Kronolith::getDriver()->getByUID($uid, null, true);
- if (is_a($events, 'PEAR_Error')) {
- return $events;
- }
-
- /* First try the user's own calendars. */
- $ownerCalendars = Kronolith::listCalendars(true, PERMS_EDIT);
- $event = null;
- foreach ($events as $ev) {
- if (isset($ownerCalendars[$ev->getCalendar()])) {
- $event = $ev;
- break;
- }
- }
-
- /* If not successful, try all calendars the user has access to. */
- if (empty($event)) {
- $editableCalendars = Kronolith::listCalendars(false, PERMS_EDIT);
- foreach ($events as $ev) {
- if (isset($editableCalendars[$ev->getCalendar()])) {
- $event = $ev;
- break;
- }
- }
- }
-
- if (empty($event) ||
- ($event->isPrivate() && $event->getCreatorId() != Horde_Auth::getAuth())) {
- return PEAR::raiseError(_("Permission Denied"));
- }
-
- $atnames = $response->getAttribute('ATTENDEE');
- if (!is_array($atnames)) {
- $atnames = array($atnames);
- }
- $atparms = $response->getAttribute('ATTENDEE', true);
-
- $found = false;
- $error = _("No attendees have been updated because none of the provided email addresses have been found in the event's attendees list.");
- $sender_lcase = Horde_String::lower($sender);
- foreach ($atnames as $index => $attendee) {
- $attendee = str_replace('mailto:', '', Horde_String::lower($attendee));
- $name = isset($atparms[$index]['CN']) ? $atparms[$index]['CN'] : null;
- if ($event->hasAttendee($attendee)) {
- if (is_null($sender) || $sender_lcase == $attendee) {
- $event->addAttendee($attendee, Kronolith::PART_IGNORE, Kronolith::responseFromICal($atparms[$index]['PARTSTAT']), $name);
- $found = true;
- } else {
- $error = _("The attendee hasn't been updated because the update was not sent from the attendee.");
- }
- }
- }
-
- $result = $event->save();
- if (is_a($result, 'PEAR_Error')) {
- return $result;
- }
-
- if (!$found) {
- return PEAR::raiseError($error);
- }
-
- return true;
-}
-
-/**
- * Lists events for a given time period.
- *
- * @param integer $startstamp The start of the time period to retrieve.
- * @param integer $endstamp The end of the time period to retrieve.
- * @param array $calendars The calendars to view events from.
- * Defaults to the user's default calendar.
- * @param boolean $showRecurrence Return every instance of a recurring event?
- * If false, will only return recurring events
- * once inside the $startDate - $endDate range.
- * @param boolean $alarmsOnly Filter results for events with alarms.
- * Defaults to false.
- *
- * @return array A list of event hashes.
- */
-function _kronolith_listEvents($startstamp = null, $endstamp = null,
- $calendars = null, $showRecurrence = true,
- $alarmsOnly = false)
-{
- $no_maint = true;
- require_once dirname(__FILE__) . '/base.php';
-
- if (!isset($calendars)) {
- $calendars = array($GLOBALS['prefs']->getValue('default_share'));
- } elseif (!is_array($calendars)) {
- $calendars = array($calendars);
- }
- $allowed_calendars = Kronolith::listCalendars(false, PERMS_READ);
- foreach ($calendars as $calendar) {
- if (!array_key_exists($calendar, $allowed_calendars)) {
- return PEAR::raiseError(_("Permission Denied"));
- }
- }
-
- return Kronolith::listEvents(new Horde_Date($startstamp),
- new Horde_Date($endstamp),
- $calendars, $showRecurrence, $alarmsOnly);
-}
-
-/**
- * Lists alarms for a given moment.
- *
- * @param integer $time The time to retrieve alarms for.
- * @param string $user The user to retrieve alarms for. All users if null.
- *
- * @return array An array of UIDs
- */
-function _kronolith_listAlarms($time, $user = null)
-{
- $no_maint = true;
- require_once dirname(__FILE__) . '/base.php';
- require_once 'Horde/Group.php';
-
- $current_user = Horde_Auth::getAuth();
- if ((empty($user) || $user != $current_user) && !Horde_Auth::isAdmin()) {
- return PEAR::raiseError(_("Permission Denied"));
- }
-
- $group = &Group::singleton();
- $alarm_list = array();
- $time = new Horde_Date($time);
- $calendars = is_null($user) ? array_keys($GLOBALS['kronolith_shares']->listAllShares()) : $GLOBALS['display_calendars'];
- $alarms = Kronolith::listAlarms($time, $calendars, true);
- if (is_a($alarms, 'PEAR_Error')) {
- return $alarms;
- }
- foreach ($alarms as $calendar => $cal_alarms) {
- $share = $GLOBALS['kronolith_shares']->getShare($calendar);
- if (is_a($share, 'PEAR_Error')) {
- continue;
- }
- if (empty($user)) {
- $users = $share->listUsers(PERMS_READ);
- $groups = $share->listGroups(PERMS_READ);
- foreach ($groups as $gid) {
- $group_users = $group->listUsers($gid);
- if (!is_a($group_users, 'PEAR_Error')) {
- $users = array_merge($users, $group_users);
- }
- }
- $users = array_unique($users);
- } else {
- $users = array($user);
- }
- $owner = $share->get('owner');
- foreach ($cal_alarms as $event) {
- foreach ($users as $alarm_user) {
- if ($alarm_user == $current_user) {
- $prefs = &$GLOBALS['prefs'];
- } else {
- $prefs = &Prefs::singleton($GLOBALS['conf']['prefs']['driver'],
- 'kronolith', $alarm_user, null,
- null, false);
- }
- $shown_calendars = unserialize($prefs->getValue('display_cals'));
- $reminder = $prefs->getValue('event_reminder');
- if (($reminder == 'owner' && $alarm_user == $owner) ||
- ($reminder == 'show' && in_array($calendar, $shown_calendars)) ||
- $reminder == 'read') {
- Horde_Nls::setLanguageEnvironment($prefs->getValue('language'));
- $alarm = $event->toAlarm($time, $alarm_user, $prefs);
- if ($alarm) {
- $alarm_list[] = $alarm;
- }
- }
- }
- }
- }
-
- return $alarm_list;
-}
-
-/**
- * Subscribe to a calendar.
- *
- * @param array $calendar Calendar description hash, with required 'type'
- * parameter. Currently supports 'http' and 'webcal'
- * for remote calendars.
- */
-function _kronolith_subscribe($calendar)
-{
- if (!isset($calendar['type'])) {
- return PEAR::raiseError(_("Unknown calendar protocol"));
- }
-
- switch ($calendar['type']) {
- case 'http':
- case 'webcal':
- $username = isset($calendar['username']) ? $calendar['username'] : null;
- $password = isset($calendar['password']) ? $calendar['password'] : null;
-
- $cals = unserialize($GLOBALS['prefs']->getValue('remote_cals'));
- if (!is_array($cals)) {
- $cals = array();
- }
- $array_key = count($cals);
- foreach ($cals as $key => $cal) {
- if ($cal['url'] == $calendar['url']) {
- $array_key = $key;
- break;
- }
- }
-
- $cals[$array_key] = array('name' => $calendar['name'],
- 'url' => $calendar['url'],
- 'user' => $username,
- 'password' => $password);
- $GLOBALS['prefs']->setValue('remote_cals', serialize($cals));
- break;
-
- case 'external':
- $cals = unserialize($GLOBALS['prefs']->getValue('display_external_cals'));
- if (array_search($calendar['name'], $cals) === false) {
- $cals[] = $calendar['name'];
- $GLOBALS['prefs']->setValue('display_external_cals', serialize($cals));
- }
-
- default:
- return PEAR::raiseError(_("Unknown calendar protocol"));
- }
-}
-
-/**
- * Unsubscribe from a calendar.
- *
- * @param array $calendar Calendar description array, with required 'type'
- * parameter. Currently supports 'http' and 'webcal'
- * for remote calendars.
- */
-function _kronolith_unsubscribe($calendar)
-{
- if (!isset($calendar['type'])) {
- return PEAR::raiseError('Unknown calendar specification');
- }
-
- switch ($calendar['type']) {
- case 'http':
- case 'webcal':
- $cals = unserialize($GLOBALS['prefs']->getValue('remote_cals'));
- foreach ($cals as $key => $cal) {
- if ($cal['url'] == $calendar['url']) {
- unset($cals[$key]);
- break;
- }
- }
-
- $GLOBALS['prefs']->setValue('remote_cals', serialize($cals));
- break;
-
- case 'external':
- $cals = unserialize($GLOBALS['prefs']->getValue('display_external_cals'));
- if (($key = array_search($calendar['name'], $cals)) !== false) {
- unset($cals[$key]);
- $GLOBALS['prefs']->setValue('display_external_cals', serialize($cals));
- }
-
- default:
- return PEAR::raiseError('Unknown calendar specification');
- }
-}
-
-
-/**
- * Places an exclusive lock for a calendar or an event.
- *
- * @param array $calendar The calendar to lock
- * @param array $event The event to lock
- *
- * @return mixed A lock ID on success, PEAR_Error on failure, false if:
- * - The calendar is already locked
- * - The event is already locked
- * - A calendar lock was requested and an event is already
- * locked in the calendar
- */
-function _kronolith_lock($calendar, $event = null)
-{
- $no_maint = true;
- require_once dirname(__FILE__) . '/base.php';
-
- if (!array_key_exists($calendar,
- Kronolith::listCalendars(false, PERMS_EDIT))) {
- return PEAR::raiseError(_("Permission Denied"));
- }
- $share = &$GLOBALS['kronolith_shares']->getShare($calendar);
-
- return $share->lock($calendar, $event);
-}
-
-/**
- * Releases a lock.
- *
- * @param array $calendar The event to lock.
- * @param array $lockid The lock id to unlock.
- */
-function _kronolith_unlock($calendar, $lockid)
-{
- $no_maint = true;
- require_once dirname(__FILE__) . '/base.php';
-
- if (!array_key_exists($calendar,
- Kronolith::listCalendars(false, PERMS_EDIT))) {
- return PEAR::raiseError(_("Permission Denied"));
- }
- $share = &$GLOBALS['kronolith_shares']->getShare($calendar);
-
- return $share->unlock($lockid);
-}
-
-/**
- * Check for existing calendar or event locks.
- *
- * @param array $calendar The calendar to check locks for.
- * @param array $event The event to check locks for.
- */
-function _kronolith_checkLocks($calendar, $event = null)
-{
- $no_maint = true;
- require_once dirname(__FILE__) . '/base.php';
-
- if (!array_key_exists($calendar,
- Kronolith::listCalendars(false, PERMS_READ))) {
- return PEAR::raiseError(_("Permission Denied"));
- }
- $share = &$GLOBALS['kronolith_shares']->getShare($calendar);
-
- return $share->checkLocks($event);
-}
-
-/**
- *
- * @return array A list of calendars used to display free/busy information
- */
-function _kronolith_getFbCalendars()
-{
- return (unserialize($GLOBALS['prefs']->getValue('fb_cals')));
-}
+++ /dev/null
-<?php define('KRONOLITH_VERSION', 'H3 (3.0-git)') ?>
$horde_test = new Horde_Test;
$module = 'Kronolith';
-require_once KRONOLITH_BASE . '/lib/version.php';
-$module_version = KRONOLITH_VERSION;
+require_once dirname(__FILE__) . '/lib/Api.php';
+$api = new Kronolith_Api();
+$module_version = $api->version;
require TEST_TEMPLATES . 'header.inc';
require TEST_TEMPLATES . 'version.inc';
--- /dev/null
+<?php
+/**
+ * Nag external API interface.
+ *
+ * This file defines Nag's external API interface. Other applications can
+ * interact with Nag through this API.
+ *
+ * @package Nag
+ */
+class Nag_Api extends Horde_Registry_Api
+{
+ /**
+ * The application's version.
+ *
+ * @var string
+ */
+ public $version = 'H4 (3.0-git)';
+
+ /**
+ * The services provided by this application.
+ *
+ * @var array
+ */
+ public $services = array(
+ 'perms' => array(
+ 'args' => array(),
+ 'type' => '{urn:horde}hashHash'
+ ),
+
+ 'removeUserData' => array(
+ 'args' => array('user' => 'string'),
+ 'type' => 'boolean'
+ ),
+
+ 'show' => array(
+ 'link' => '%application%/view.php?tasklist=|tasklist|&task=|task|&uid=|uid|',
+ ),
+
+ 'browse' => array(
+ 'args' => array('path' => 'string'),
+ 'type' => '{urn:horde}hashHash',
+ ),
+
+ 'put' => array(
+ 'args' => array(
+ 'path' => 'string',
+ 'content' => 'string',
+ 'content_type' => 'string'
+ ),
+ 'type' => 'int',
+ ),
+
+ 'path_delete' => array(
+ 'args' => array('path' => 'string'),
+ 'type' => 'boolean',
+ ),
+
+ 'addTasklist' => array(
+ 'args' => array(
+ 'name' => 'string',
+ 'description' => 'string'
+ ),
+ 'type' => 'string',
+ ),
+
+ 'listTasklists' => array(
+ 'args' => array(
+ 'owneronly' => 'boolean',
+ 'permission' => 'int'
+ ),
+ 'type' => '{urn:horde}stringArray',
+ ),
+
+ 'listTasks' => array(
+ 'args' => array(
+ 'sortby' => 'string',
+ 'sortdir' => 'int',
+ 'altsortby' => 'string',
+ 'tasklists' => '{urn:horde}stringArray',
+ 'completed' => 'int',
+ 'json' => 'boolean'
+ ),
+ 'type' => '{urn:horde}stringArray',
+ ),
+
+ 'listAlarms' => array(
+ 'args' => array(
+ 'time' => 'int',
+ 'user' => 'string'
+ ),
+ 'type' => '{urn:horde}hashHash'
+ ),
+
+ 'list' => array(
+ 'args' => array(),
+ 'type' => '{urn:horde}stringArray',
+ ),
+
+ 'listBy' => array(
+ 'args' => array(
+ 'action' => 'string',
+ 'timestamp' => 'int'
+ ),
+ 'type' => '{urn:horde}stringArray',
+ ),
+
+ 'getActionTimestamp' => array(
+ 'args' => array(
+ 'uid' => 'string',
+ 'action' => 'string',
+ 'tasklist' => 'string'
+ ),
+ 'type' => 'int',
+ ),
+
+ 'import' => array(
+ 'args' => array(
+ 'content' => 'string',
+ 'contentType' => 'string',
+ 'tasklist' => 'string'
+ ),
+ 'type' => 'string',
+ ),
+
+ 'quickAdd' => array(
+ 'args' => array(
+ 'content' => 'string',
+ 'tasklist' => 'string'
+ ),
+ 'type' => '{urn:horde}stringArray',
+ ),
+
+ 'export' => array(
+ 'args' => array(
+ 'uid' => 'string',
+ 'contentType' => '{urn:horde}stringArray'
+ ),
+ 'type' => 'string',
+ ),
+
+ 'exportTasklist' => array(
+ 'args' => array(
+ 'tasklist' => 'string',
+ 'contentType' => 'string'
+ ),
+ 'type' => 'string'
+ ),
+
+ 'delete' => array(
+ 'args' => array('uid' => '{urn:horde}stringArray'),
+ 'type' => 'boolean',
+ ),
+
+ 'replace' => array(
+ 'args' => array(
+ 'uid' => 'string',
+ 'content' => 'string',
+ 'contentType' => 'string'
+ ),
+ 'type' => 'boolean',
+ ),
+
+ 'listCostObjects' => array(
+ 'args' => array('criteria' => '{urn:horde}hash'),
+ 'type' => '{urn:horde}stringArray'
+ ),
+
+ 'listTimeObjectCategories' => array(
+ 'type' => '{urn:horde}stringArray'
+ ),
+
+ 'listTimeObjects' => array(
+ 'args' => array(
+ 'start' => 'int',
+ 'end' => 'int'
+ ),
+ 'type' => '{urn:horde}hashHash'
+ ),
+
+ 'toggleCompletion' => array(
+ 'args' => array(
+ 'task_id' => 'string',
+ 'tasklist_id' => 'string'
+ ),
+ 'type' => 'boolean'
+ )
+ );
+
+ /**
+ * Returns a list of available permissions.
+ *
+ * @return array An array describing all available permissions.
+ */
+ public function perms()
+ {
+ $perms = array();
+ $perms['tree']['nag']['max_tasks'] = false;
+ $perms['title']['nag:max_tasks'] = _("Maximum Number of Tasks");
+ $perms['type']['nag:max_tasks'] = 'int';
+
+ return $perms;
+ }
+
+ /**
+ * Removes user data.
+ *
+ * @param string $user Name of user to remove data for.
+ *
+ * @return mixed true on success | PEAR_Error on failure
+ */
+ public function removeUserData($user)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ if (!Horde_Auth::isAdmin() && $user != Horde_Auth::getAuth()) {
+ return PEAR::raiseError(_("You are not allowed to remove user data."));
+ }
+
+ /* Error flag */
+ $hasError = false;
+
+ /* Get the share for later deletion */
+ $share = $GLOBALS['nag_shares']->getShare($user);
+ if(is_a($share, 'PEAR_Error')) {
+ Horde::logMessage($share->getMessage(), __FILE__, __LINE__, PEAR_LOG_ERR);
+ unset($share);
+ } else {
+ /* Get the list of all tasks */
+ $tasks = Nag::listTasks(null, null, null, $user, 1);
+ if (is_a($tasks, 'PEAR_Error')) {
+ $hasError = true;
+ Horde::logMessage($share->getMessage(), __FILE__, __LINE__, PEAR_LOG_ERR);
+ } else {
+ $uids = array();
+ $tasks->reset();
+ while ($task = $tasks->each()) {
+ $uids[] = $task->uid;
+ }
+
+ /* ... and delete them. */
+ foreach ($uids as $uid) {
+ $this->delete($uid);
+ }
+ }
+ }
+
+ /* Now delete history as well. */
+ $history = Horde_History::singleton();
+ if (method_exists($history, 'removeByParent')) {
+ $histories = $history->removeByParent('nag:' . $user);
+ } else {
+ /* Remove entries 100 at a time. */
+ $all = $history->getByTimestamp('>', 0, array(), 'nag:' . $user);
+ if (is_a($all, 'PEAR_Error')) {
+ Horde::logMessage($all, __FILE__, __LINE__, PEAR_LOG_ERR);
+ } else {
+ $all = array_keys($all);
+ while (count($d = array_splice($all, 0, 100)) > 0) {
+ $history->removebyNames($d);
+ }
+ }
+ }
+
+ /* ...and finally, delete the actual share */
+ if (!empty($share)) {
+ $result = $GLOBALS['nag_shares']->removeShare($share);
+ if (is_a($result, 'PEAR_Error')) {
+ $hasError = true;
+ Horde::logMessage($result->getMessage(), __FILE__, __LINE__, PEAR_LOG_ERR);
+ }
+ }
+
+ /* Now remove perms for this user from all other shares */
+ $shares = $GLOBALS['nag_shares']->listShares($user);
+ if (is_a($shares, 'PEAR_Error')) {
+ $hasError = true;
+ Horde::logMessage($shares, __FILE__, __LINE__, PEAR_LOG_ERR);
+ }
+ foreach ($shares as $share) {
+ $share->removeUser($user);
+ }
+
+ if ($hasError) {
+ return PEAR::raiseError(sprintf(_("There was an error removing tasks for %s. Details have been logged."), $user));
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Retrieves the current user's task list from storage.
+ *
+ * This function will also sort the resulting list, if requested.
+ *
+ * @param string $sortby The field by which to sort
+ * (NAG_SORT_PRIORITY, NAG_SORT_NAME
+ * NAG_SORT_DUE, NAG_SORT_COMPLETION).
+ * @param integer $sortdir The direction by which to sort
+ * @param string $altsortby The secondary sort field.
+ * @param array $tasklists An array of tasklist to display or
+ * null/empty to display taskslists
+ * $GLOBALS['display_tasklists'].
+ * @param integer $completed Which tasks to retrieve (1 = all tasks,
+ * 0 = incomplete tasks, 2 = complete tasks,
+ * 3 = future tasks, 4 = future and incomplete
+ * tasks).
+ * @param boolean $json Retrieve the results of the tasks in
+ * 'json format'
+ *
+ * @return Nag_Task A list of the requested tasks.
+ */
+ public function listTasks($sortby = null, $sortdir = null, $altsortby = null,
+ $tasklists = null, $completed = null, $json = false)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ if (!isset($sortby)) {
+ $sortby = $GLOBALS['prefs']->getValue('sortby');
+ }
+ if (!isset($sortdir)) {
+ $sortdir = $GLOBALS['prefs']->getValue('sortdir');
+ }
+ if (is_null($altsortby)) {
+ $altsortby = $GLOBALS['prefs']->getValue('altsortby');
+ }
+ if (is_null($tasklists)) {
+ $tasklists = $GLOBALS['display_tasklists'];
+ }
+ if (is_null($completed)) {
+ $completed = $GLOBALS['prefs']->getValue('show_completed');
+ }
+
+ $tasks = Nag::listTasks($sortby, $sortdir, $altsortby, $tasklists);
+ $tasks->reset();
+ $list = array();
+ while ($task = $tasks->each()) {
+ $list[$task->id] = $json ? $task->toJson() : $task->toHash();
+ }
+
+ return $list;
+ }
+
+ /**
+ * Add a new task list
+ *
+ * @param string $name Task list name
+ * @param string $description Task list description
+ *
+ * @return integer The new tasklist's id.
+ */
+ public function addTasklist($name, $description = '')
+ {
+ if (!Horde_Auth::getAuth()) {
+ return PEAR::raiseError(_("Permission denied"));
+ }
+
+ require_once dirname(__FILE__) . '/base.php';
+ global $nag_shares;
+
+ $tasklistId = md5(microtime());
+ $tasklist = $nag_shares->newShare($tasklistId);
+
+ if (is_a($tasklist, 'PEAR_Error')) {
+ return $tasklist;
+ }
+
+ $tasklist->set('name', $name, false);
+ $tasklist->set('desc', $description, false);
+ $result = $nag_shares->addShare($tasklist);
+
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ return $tasklistId;
+ }
+
+ /**
+ * Returns the last modification timestamp of a given uid.
+ *
+ * @param string $uid The uid to look for.
+ * @param string $tasklist The tasklist to look in.
+ *
+ * @return integer The timestamp for the last modification of $uid.
+ */
+ public function modified($uid, $tasklist = null)
+ {
+ $modified = $this->getActionTimestamp($uid, 'modify', $tasklist);
+ if (empty($modified)) {
+ $modified = $this->getActionTimestamp($uid, 'add', $tasklist);
+ }
+ return $modified;
+ }
+
+ /**
+ * Browse through Nag's object tree.
+ *
+ * @param string $path The level of the tree to browse.
+ * @param array $properties The item properties to return. Defaults to 'name',
+ * 'icon', and 'browseable'.
+ *
+ * @return array The contents of $path
+ */
+ public function browse($path = '', $properties = array())
+ {
+ require_once dirname(__FILE__) . '/base.php';
+ global $registry;
+
+ function _getTasklistSize($tasklistID)
+ {
+ // This ugly and performance-heavy hack is required to set the content
+ // length. Some clients (at least OS X) respect the content-length
+ // header a little too exactly. If we send a content-length that is
+ // longer than the actual data it will complain that the connection
+ // broke. If we specify one that is too short it will truncate the
+ // downlaoded file. To make matters worse it seems to respect the
+ // content-length from the PROPFIND request used to enumerate objects
+ // rather than the actual content-length sent at the time the file is
+ // downloaded. Way to go, Apple.
+ return strlen($thisd->exportTasklist($tasklistID, 'text/calendar'));
+ }
+
+ // Default properties.
+ if (!$properties) {
+ $properties = array('name', 'icon', 'browseable');
+ }
+
+ if (substr($path, 0, 3) == 'nag') {
+ $path = substr($path, 3);
+ }
+ $path = trim($path, '/');
+ $parts = explode('/', $path);
+
+ if (empty($path)) {
+ //
+ // This request is for a list of all users who have tasklists visible
+ // to the requesting user.
+ //
+ $tasklists = Nag::listTasklists(false, PERMS_READ);
+ $owners = array();
+ foreach ($tasklists as $tasklist) {
+ $owners[$tasklist->get('owner')] = true;
+ }
+
+ $results = array();
+ foreach (array_keys($owners) as $owner) {
+ if (in_array('name', $properties)) {
+ $results['nag/' . $owner]['name'] = $owner;
+ }
+ if (in_array('icon', $properties)) {
+ $results['nag/' . $owner]['icon'] =
+ $registry->getImageDir('horde') . '/user.png';
+ }
+ if (in_array('browseable', $properties)) {
+ $results['nag/' . $owner]['browseable'] = true;
+ }
+ if (in_array('contenttype', $properties)) {
+ $results['nag/' . $owner]['contenttype'] =
+ 'httpd/unix-directory';
+ }
+ if (in_array('contentlength', $properties)) {
+ $results['nag/' . $owner]['contentlength'] = 0;
+ }
+ if (in_array('modified', $properties)) {
+ $results['nag/' . $owner]['modified'] =
+ $_SERVER['REQUEST_TIME'];
+ }
+ if (in_array('created', $properties)) {
+ $results['nag/' . $owner]['created'] = 0;
+ }
+ }
+ return $results;
+
+ } elseif (count($parts) == 1) {
+ //
+ // This request is for all tasklists owned by the requested user
+ //
+ $tasklists = $GLOBALS['nag_shares']->listShares($parts[0],
+ PERMS_SHOW,
+ $parts[0]);
+
+ // The last check returns all addressbooks for the requested user,
+ // but that does not mean the requesting user has access to them.
+ // Filter out those address books for which the requesting user has
+ // no access.
+ $tasklists = Nag::permissionsFilter($tasklists);
+
+ $results = array();
+ foreach ($tasklists as $tasklistId => $tasklist) {
+ $retpath = 'nag/' . $parts[0] . '/' . $tasklistId;
+ if (in_array('name', $properties)) {
+ $results[$retpath]['name'] = sprintf(_("Tasks from %s"), $tasklist->get('name'));
+ $results[$retpath . '.ics']['name'] = $tasklist->get('name');
+ }
+ if (in_array('icon', $properties)) {
+ $results[$retpath]['icon'] = $registry->getImageDir() . '/nag.png';
+ $results[$retpath . '.ics']['icon'] = $registry->getImageDir() . '/mime/icalendar.png';
+ }
+ if (in_array('browseable', $properties)) {
+ $results[$retpath]['browseable'] = $tasklist->hasPermission(Horde_Auth::getAuth(), PERMS_READ);
+ $results[$retpath . '.ics']['browseable'] = false;
+ }
+ if (in_array('contenttype', $properties)) {
+ $results[$retpath]['contenttype'] = 'httpd/unix-directory';
+ $results[$retpath . '.ics']['contenttype'] = 'text/calendar';
+ }
+ if (in_array('contentlength', $properties)) {
+ $results[$retpath]['contentlength'] = 0;
+ $results[$retpath . '.ics']['contentlength'] = _getTasklistSize($tasklistId);
+ }
+ if (in_array('modified', $properties)) {
+ // @TODO Find a way to get the actual modification times
+ $results[$retpath]['modified'] = $_SERVER['REQUEST_TIME'];
+ $results[$retpath . '.ics']['modified'] = $_SERVER['REQUEST_TIME'];
+ }
+ if (in_array('created', $properties)) {
+ // @TODO Find a way to get the actual creation times
+ $results[$retpath]['created'] = 0;
+ $results[$retpath . '.ics']['created'] = 0;
+ }
+ }
+ return $results;
+
+ } elseif (count($parts) == 2 && substr($parts[1], -4) == '.ics') {
+ //
+ // This is a request for the entire tasklist in iCalendar format.
+ //
+ $tasklist = substr($parts[1], 0, -4);
+ if (!array_key_exists($tasklist, Nag::listTasklists(false, PERMS_READ))) {
+ return PEAR::raiseError(_("Invalid tasklist file requested."), 404);
+ }
+ $ical_data = $this->exportTasklist($tasklist, 'text/calendar');
+ $result = array('data' => $ical_data,
+ 'mimetype' => 'text/calendar',
+ 'contentlength' => strlen($ical_data),
+ 'mtime' => $_SERVER['REQUEST_TIME']);
+
+ return $result;
+
+ } elseif (count($parts) == 2) {
+ //
+ // This request is browsing into a specific tasklist. Generate the list
+ // of items and represent them as files within the directory.
+ //
+ if (!array_key_exists($parts[1], Nag::listTasklists(false, PERMS_READ))) {
+ return PEAR::raiseError(_("Invalid tasklist requested."), 404);
+ }
+ $storage = Nag_Driver::singleton($parts[1]);
+ $result = $storage->retrieve();
+ if (is_a($result, 'PEAR_Error')) {
+ $result->code = 500;
+ return $result;
+ }
+
+ $icon = $registry->getImageDir() . '/nag.png';
+ $results = array();
+ $storage->tasks->reset();
+ while ($task = $storage->tasks->each()) {
+ $key = 'nag/' . $parts[0] . '/' . $parts[1] . '/' . $task->id;
+ if (in_array('name', $properties)) {
+ $results[$key]['name'] = $task->name;
+ }
+ if (in_array('icon', $properties)) {
+ $results[$key]['icon'] = $icon;
+ }
+ if (in_array('browseable', $properties)) {
+ $results[$key]['browseable'] = false;
+ }
+ if (in_array('contenttype', $properties)) {
+ $results[$key]['contenttype'] = 'text/calendar';
+ }
+ if (in_array('contentlength', $properties)) {
+ // FIXME: This is a hack. If the content length is longer
+ // than the actual data then some WebDAV clients will report
+ // an error when the file EOF is received. Ideally we should
+ // determine the actual size of the data and report it here, but
+ // the performance hit may be prohibitive. This requires
+ // further investigation.
+ $results[$key]['contentlength'] = 1;
+ }
+ if (in_array('modified', $properties)) {
+ $results[$key]['modified'] = $this->modified($task->uid, $path);
+ }
+ if (in_array('created', $properties)) {
+ $results[$key]['created'] = $this->getActionTimestamp($task->uid, 'add', $path);
+ }
+ }
+ return $results;
+ } else {
+ //
+ // The only valid request left is for either a specific task item.
+ //
+ if (count($parts) == 3 &&
+ array_key_exists($parts[1], Nag::listTasklists(false,
+ PERMS_READ))) {
+ //
+ // This request is for a specific item within a given task list.
+ //
+ /* Create a Nag storage instance. */
+ $storage = Nag_Driver::singleton($parts[1]);
+ if (is_a($storage, 'PEAR_Error')) {
+ return PEAR::raiseError(sprintf(_("Connection failed: %s"), $storage->getMessage()));
+ }
+ $storage->retrieve();
+
+ $task = $storage->get($parts[2]);
+ if (is_a($task, 'PEAR_Error')) {
+ $task->code = 500;
+ return $task;
+ }
+
+ $result = array('data' => $this->export($task->uid, 'text/calendar'),
+ 'mimetype' => 'text/calendar');
+ $modified = $this->modified($task->uid, $parts[1]);
+ if (!empty($modified)) {
+ $result['mtime'] = $modified;
+ }
+ return $result;
+ } elseif (count($parts) == 2 &&
+ substr($parts[1], -4) == '.ics' &&
+ array_key_exists(substr($parts[1], 0, -4), Nag::listTasklists(false, PERMS_READ))) {
+ } else {
+ //
+ // All other requests are a 404: Not Found
+ //
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Saves a file into the Nag tree.
+ *
+ * @param string $path The path where to PUT the file.
+ * @param string $content The file content.
+ * @param string $content_type The file's content type.
+ *
+ * @return array The event UIDs, or a PEAR_Error on failure.
+ */
+ public function put($path, $content, $content_type)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ if (substr($path, 0, 3) == 'nag') {
+ $path = substr($path, 3);
+ }
+ $path = trim($path, '/');
+ $parts = explode('/', $path);
+
+ if (count($parts) == 2 &&
+ substr($parts[1], -4) == '.ics') {
+
+ // Workaround for WebDAV clients that are not smart enough to send
+ // the right content type. Assume text/calendar.
+ if ($content_type == 'application/octet-stream') {
+ $content_type = 'text/calendar';
+ }
+ $tasklist = substr($parts[1], 0, -4);
+ } elseif (count($parts) == 3) {
+ $tasklist = $parts[1];
+
+ // Workaround for WebDAV clients that are not smart enough to send
+ // the right content type. Assume the same format we send individual
+ // tasklist items: text/calendar
+ if ($content_type == 'application/octet-stream') {
+ $content_type = 'text/calendar';
+ }
+ } else {
+ return PEAR::raiseError(_("Invalid tasklist name supplied."), 403);
+ }
+
+ if (!array_key_exists($tasklist, Nag::listTasklists(false, PERMS_EDIT))) {
+ // FIXME: Should we attempt to create a tasklist based on the filename
+ // in the case that the requested tasklist does not exist?
+ return PEAR::raiseError(_("Tasklist does not exist or no permission to edit"), 403);
+ }
+
+ // Store all currently existings UIDs. Use this info to delete UIDs not
+ // present in $content after processing.
+ $ids = array();
+ $uids_remove = array_flip($this->listTaskUids($tasklist));
+
+ $storage = Nag_Driver::singleton($tasklist);
+
+ switch ($content_type) {
+ case 'text/calendar':
+ case 'text/x-vcalendar':
+ $iCal = new Horde_iCalendar();
+ if (!is_a($content, 'Horde_iCalendar_vtodo')) {
+ if (!$iCal->parsevCalendar($content)) {
+ return PEAR::raiseError(_("There was an error importing the iCalendar data."), 400);
+ }
+ } else {
+ $iCal->addComponent($content);
+ }
+
+ foreach ($iCal->getComponents() as $content) {
+ if (is_a($content, 'Horde_iCalendar_vtodo')) {
+ $task = new Nag_Task();
+ $task->fromiCalendar($content);
+ $task->tasklist = $tasklist;
+ if (isset($task->uid) &&
+ !is_a(($existing = $storage->getByUID($task->uid)), 'PEAR_Error')) {
+ // Entry exists, remove from uids_remove list so we won't
+ // delete in the end.
+ if (isset($uids_remove[$task->uid])) {
+ unset($uids_remove[$task->uid]);
+ }
+ if ($existing->private &&
+ $existing->owner != Horde_Auth::getAuth()) {
+ continue;
+ }
+ // Check if our task is newer then the existing - get the
+ // task's history.
+ $history = Horde_History::singleton();
+ $created = $modified = null;
+ $log = $history->getHistory('nag:' . $tasklist . ':' . $task->uid);
+ if ($log && !is_a($log, 'PEAR_Error')) {
+ foreach ($log->getData() as $entry) {
+ switch ($entry['action']) {
+ case 'add':
+ $created = $entry['ts'];
+ break;
+
+ case 'modify':
+ $modified = $entry['ts'];
+ break;
+ }
+ }
+ }
+ if (empty($modified) && !empty($add)) {
+ $modified = $add;
+ }
+ if (!empty($modified) &&
+ $modified >= $content->getAttribute('LAST-MODIFIED')) {
+ // LAST-MODIFIED timestamp of existing entry is newer:
+ // don't replace it.
+ continue;
+ }
+
+ // Don't change creator/owner.
+ $owner = $existing->owner;
+ $taskId = $existing->id;
+ $result = $storage->modify(
+ $taskId,
+ isset($task->name) ? $task->name : $existing->name,
+ isset($task->desc) ? $task->desc : $existing->desc,
+ isset($task->start) ? $task->start : $existing->start,
+ isset($task->due) ? $task->due : $existing->due,
+ isset($task->priority) ? $task->priority : $existing->priority,
+ isset($task->estimate) ? $task->estimate : 0,
+ isset($task->completed) ? (int)$task->completed : $existing->completed,
+ isset($task->category) ? $task->category : $existing->category,
+ isset($task->alarm) ? $task->alarm : $existing->alarm,
+ isset($task->parent_id) ? $task->parent_id : $existing->parent_id,
+ isset($task->private) ? $task->private : $existing->private,
+ $owner,
+ isset($task->assignee) ? $task->assignee : $existing->assignee);
+
+ if (is_a($result, 'PEAR_Error')) {
+ $result->code = 500;
+ return $result;
+ }
+ $ids[] = $task->uid;
+ } else {
+ $newTask = $storage->add(
+ isset($task->name) ? $task->name : '',
+ isset($task->desc) ? $task->desc : '',
+ isset($task->start) ? $task->start : 0,
+ isset($task->due) ? $task->due : 0,
+ isset($task->priority) ? $task->priority : 3,
+ isset($task->estimate) ? $task->estimate : 0,
+ !empty($task->completed),
+ isset($task->category) ? $task->category : '',
+ isset($task->alarm) ? $task->alarm : 0,
+ isset($task->uid) ? $task->uid : null,
+ isset($task->parent_id) ? $task->parent_id : '',
+ !empty($task->private),
+ Horde_Auth::getAuth(),
+ isset($task->assignee) ? $task->assignee : null);
+ if (is_a($newTask, 'PEAR_Error')) {
+ $newtask->code = 500;
+ return $newTask;
+ }
+ // use UID rather than ID
+ $ids[] = $newTask[1];
+ }
+ }
+ }
+ break;
+
+ default:
+ return PEAR::raiseError(sprintf(_("Unsupported Content-Type: %s"), $content_type), 400);
+ }
+
+ if (array_key_exists($tasklist, Nag::listTasklists(false, PERMS_DELETE))) {
+ foreach (array_keys($uids_remove) as $uid) {
+ $this->delete($uid);
+ }
+ }
+
+ return $ids;
+ }
+
+ /**
+ * Deletes a file from the Nag tree.
+ *
+ * @param string $path The path to the file.
+ *
+ * @return mixed The event's UID, or a PEAR_Error on failure.
+ */
+ public function path_delete($path)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ if (substr($path, 0, 3) == 'nag') {
+ $path = substr($path, 3);
+ }
+ $path = trim($path, '/');
+ $parts = explode('/', $path);
+
+ if (count($parts) == 2) {
+ // @TODO Deny deleting of the entire tasklist for now.
+ // Allow users to delete tasklists but not create them via WebDAV will
+ // be more confusing than helpful. They are, however, still able to
+ // delete individual task items within the tasklist folder.
+ return PEAR::raiseError(_("Deleting entire tasklists is not supported."), 403);
+ // To re-enable the functionality just remove this if {} block.
+ }
+
+ if (substr($parts[1], -4) == '.ics') {
+ $tasklistID = substr($parts[1], 0, -4);
+ } else {
+ $tasklistID = $parts[1];
+ }
+
+ if (!(count($parts) == 2 || count($parts) == 3) ||
+ !array_key_exists($tasklistID, Nag::listTasklists(false, PERMS_DELETE))) {
+ return PEAR::raiseError(_("Tasklist does not exist or no permission to delete"), 403);
+ }
+
+ /* Create a Nag storage instance. */
+ $storage = Nag_Driver::singleton($tasklistID);
+ if (is_a($storage, 'PEAR_Error')) {
+ return PEAR::raiseError(sprintf(_("Connection failed: %s"), $storage->getMessage()), 500);
+ }
+ $retrieved = $storage->retrieve();
+ if (is_a($retrieved, 'PEAR_Error')) {
+ $retrieved->code = 500;
+ return $retrieved;
+ }
+
+ if (count($parts) == 3) {
+ // Delete just a single entry
+ return $storage->delete($parts[2]);
+ } else {
+ // Delete the entire task list
+ $result = $storage->deleteAll();
+ if (is_a($result, 'PEAR_Error')) {
+ return PEAR::raiseError(sprintf(_("Unable to delete tasklist \"%s\": %s"), $tasklistID, $result->getMessage()), 500);
+ } else {
+ // Remove share and all groups/permissions.
+ $share = $GLOBALS['nag_shares']->getShare($tasklistID);
+ $result = $GLOBALS['nag_shares']->removeShare($share);
+ if (is_a($result, 'PEAR_Error')) {
+ $result->code = 500;
+ return $result;
+ }
+ }
+ }
+ }
+
+ /**
+ * @param boolean $owneronly Only return tasklists that this user owns?
+ * Defaults to false.
+ * @param integer $permission The permission to filter tasklists by.
+ *
+ * @return array The task lists.
+ */
+ public function listTasklists($owneronly, $permission)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ return Nag::listTasklists($owneronly, $permission);
+ }
+
+ /**
+ * Returns an array of UIDs for all tasks that the current user is authorized
+ * to see.
+ *
+ * @param variant $tasklist The tasklist or an array of taskslists to list.
+ *
+ * @return array An array of UIDs for all tasks
+ * the user can access.
+ */
+ public function listTaskUids($tasklist = null)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ if (!isset($GLOBALS['conf']['storage']['driver'])) {
+ return PEAR::raiseError(_("Not configured"));
+ }
+
+ if ($tasklist === null) {
+ $tasklist = Nag::getDefaultTasklist(PERMS_READ);
+ }
+
+ if (!array_key_exists($tasklist,
+ Nag::listTasklists(false, PERMS_READ))) {
+ return PEAR::raiseError(_("Permission Denied"));
+ }
+
+ $tasks = Nag::listTasks(null, null, null, $tasklist, 1);
+ if (is_a($tasks, 'PEAR_Error')) {
+ return $tasks;
+ }
+
+ $uids = array();
+ $tasks->reset();
+ while ($task = $tasks->each()) {
+ $uids[] = $task->uid;
+ }
+
+ return $uids;
+ }
+
+ /**
+ * Returns an array of UIDs for tasks that have had $action happen since
+ * $timestamp.
+ *
+ * @param string $action The action to check for - add, modify, or delete.
+ * @param integer $timestamp The time to start the search.
+ * @param string $tasklist The tasklist to be used. If 'null', the
+ * user's default tasklist will be used.
+ *
+ * @return array An array of UIDs matching the action and time criteria.
+ */
+ public function listBy($action, $timestamp, $tasklist = null)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ if ($tasklist === null) {
+ $tasklist = Nag::getDefaultTasklist(PERMS_READ);
+ }
+
+ if (!array_key_exists($tasklist,
+ Nag::listTasklists(false, PERMS_READ))) {
+ return PEAR::raiseError(_("Permission Denied"));
+ }
+
+ $history = Horde_History::singleton();
+ $histories = $history->getByTimestamp('>', $timestamp, array(array('op' => '=', 'field' => 'action', 'value' => $action)), 'nag:' . $tasklist);
+ if (is_a($histories, 'PEAR_Error')) {
+ return $histories;
+ }
+
+ // Strip leading nag:username:.
+ return preg_replace('/^([^:]*:){2}/', '', array_keys($histories));
+ }
+
+ /**
+ * Returns the timestamp of an operation for a given uid an action.
+ *
+ * @param string $uid The uid to look for.
+ * @param string $action The action to check for - add, modify, or delete.
+ * @param string $tasklist The tasklist to be used. If 'null', the
+ * user's default tasklist will be used.
+ *
+ * @return integer The timestamp for this action.
+ */
+ public function getActionTimestamp($uid, $action, $tasklist = null)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ if ($tasklist === null) {
+ $tasklist = Nag::getDefaultTasklist(PERMS_READ);
+ }
+
+ if (!array_key_exists($tasklist,
+ Nag::listTasklists(false, PERMS_READ))) {
+ return PEAR::raiseError(_("Permission Denied"));
+ }
+
+ $history = Horde_History::singleton();
+ return $history->getActionTimestamp('nag:' . $tasklist . ':' . $uid, $action);
+ }
+
+ /**
+ * Imports one or more tasks represented in the specified content type.
+ *
+ * If a UID is present in the content and the task is already in the
+ * database, a replace is performed rather than an add.
+ *
+ * @param string $content The content of the task.
+ * @param string $contentType What format is the data in? Currently supports:
+ * text/calendar
+ * text/x-vcalendar
+ * @param string $tasklist The tasklist into which the task will be
+ * imported. If 'null', the user's default
+ * tasklist will be used.
+ *
+ * @return string The new UID on one import, an array of UIDs on multiple imports,
+ * or PEAR_Error on failure.
+ */
+ public function import($content, $contentType, $tasklist = null)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ if ($tasklist === null) {
+ $tasklist = Nag::getDefaultTasklist(PERMS_EDIT);
+ }
+
+ if (!array_key_exists($tasklist, Nag::listTasklists(false, PERMS_EDIT))) {
+ return PEAR::raiseError(_("Permission Denied"));
+ }
+
+ /* Create a Nag_Driver instance. */
+ $storage = Nag_Driver::singleton($tasklist);
+
+ switch ($contentType) {
+ case 'text/x-vcalendar':
+ case 'text/calendar':
+ case 'text/x-vtodo':
+ $iCal = new Horde_iCalendar();
+ if (!is_a($content, 'Horde_iCalendar_vtodo')) {
+ if (!$iCal->parsevCalendar($content)) {
+ return PEAR::raiseError(_("There was an error importing the iCalendar data."));
+ }
+ } else {
+ $iCal->addComponent($content);
+ }
+
+ $components = $iCal->getComponents();
+ if (count($components) == 0) {
+ return PEAR::raiseError(_("No iCalendar data was found."));
+ }
+
+ $ids = array();
+ foreach ($components as $content) {
+ if (is_a($content, 'Horde_iCalendar_vtodo')) {
+ $task = new Nag_Task();
+ $task->fromiCalendar($content);
+ if (isset($task->uid) &&
+ !is_a(($existing = $storage->getByUID($task->uid)), 'PEAR_Error')) {
+ $taskId = $existing->id;
+ $result = $storage->modify(
+ $taskId,
+ isset($task->name) ? $task->name : $existing->name,
+ isset($task->desc) ? $task->desc : $existing->desc,
+ isset($task->start) ? $task->start : $existing->start,
+ isset($task->due) ? $task->due : $existing->due,
+ isset($task->priority) ? $task->priority : $existing->priority,
+ isset($task->estimate) ? $task->estimate : 0,
+ isset($task->completed) ? (int)$task->completed : $existing->completed,
+ isset($task->category) ? $task->category : $existing->category,
+ isset($task->alarm) ? $task->alarm : $existing->alarm,
+ isset($task->parent_id) ? $task->parent_id : $existing->parent_id,
+ isset($task->private) ? $task->private : $existing->private,
+ isset($task->owner) ? $task->owner : $existing->owner,
+ isset($task->assignee) ? $task->assignee : $existing->assignee);
+
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ $ids[] = $task->uid;
+ } else {
+ $newTask = $storage->add(
+ isset($task->name) ? $task->name : '',
+ isset($task->desc) ? $task->desc : '',
+ isset($task->start) ? $task->start : 0,
+ isset($task->due) ? $task->due : 0,
+ isset($task->priority) ? $task->priority : 3,
+ isset($task->estimate) ? $task->estimate : 0,
+ !empty($task->completed),
+ isset($task->category) ? $task->category : '',
+ isset($task->alarm) ? $task->alarm : 0,
+ isset($task->methods) ? $task->methods : null,
+ isset($task->uid) ? $task->uid : null,
+ isset($task->parent_id) ? $task->parent_id : '',
+ !empty($task->private),
+ Horde_Auth::getAuth(),
+ isset($task->assignee) ? $task->assignee : null);
+ if (is_a($newTask, 'PEAR_Error')) {
+ return $newTask;
+ }
+ // use UID rather than ID
+ $ids[] = $newTask[1];
+ }
+ }
+ }
+ if (count($ids) == 0) {
+ return PEAR::raiseError(_("No iCalendar data was found."));
+ } else if (count($ids) == 1) {
+ return $ids[0];
+ }
+ return $ids;
+ }
+
+ return PEAR::raiseError(sprintf(_("Unsupported Content-Type: %s"), $contentType));
+ }
+
+ /**
+ * Imports one or more tasks parsed from a string.
+ *
+ * @param string $text The text to parse into
+ * @param string $tasklist The tasklist into which the task will be
+ * imported. If 'null', the user's default
+ * tasklist will be used.
+ *
+ * @return array The UIDs of all tasks that were added.
+ */
+ public function quickAdd($text, $tasklist = null)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ if ($tasklist === null) {
+ $tasklist = Nag::getDefaultTasklist(PERMS_EDIT);
+ }
+ if (!array_key_exists($tasklist, Nag::listTasklists(false, PERMS_EDIT))) {
+ return PEAR::raiseError(_("Permission Denied"));
+ }
+
+ return Nag::createTasksFromText($text, $tasklist);
+ }
+
+ /**
+ * Toggles the task completion flag.
+ *
+ * @param string $task_id The task ID.
+ * @param string $tasklist_id The tasklist that contains the task.
+ */
+ public function toggleCompletion($task_id, $tasklist_id)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ if (!array_key_exists($tasklist_id,
+ Nag::listTasklists(false, PERMS_EDIT))) {
+ return PEAR::raiseError(_("Permission Denied"));
+ }
+
+ $share = $GLOBALS['nag_shares']->getShare($tasklist_id);
+ if (is_a($share, 'PEAR_Error')) {
+ return $share;
+ }
+
+ $task = Nag::getTask($tasklist_id, $task_id);
+ if (is_a($task, 'PEAR_Error')) {
+ return $task;
+ }
+
+ $task->completed = !$task->completed;
+ if ($task->completed) {
+ $task->completed_date = time();
+ } else {
+ $task->completed_date = null;
+ }
+
+ return $task->save();
+ }
+
+ /**
+ * Exports a task, identified by UID, in the requested content type.
+ *
+ * @param string $uid Identify the task to export.
+ * @param string $contentType What format should the data be in?
+ * A string with one of:
+ * <pre>
+ * text/calendar - (VCALENDAR 2.0. Recommended as this is specified in
+ * rfc2445)
+ * text/x-vcalendar - (old VCALENDAR 1.0 format. Still in wide use)
+ * </pre>
+ *
+ * @return string The requested data.
+ */
+ public function export($uid, $contentType)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ $storage = Nag_Driver::singleton();
+ $task = $storage->getByUID($uid);
+ if (is_a($task, 'PEAR_Error')) {
+ return $task;
+ }
+
+ if (!array_key_exists($task->tasklist, Nag::listTasklists(false, PERMS_READ))) {
+ return PEAR::raiseError(_("Permission Denied"));
+ }
+
+ $version = '2.0';
+ switch ($contentType) {
+ case 'text/x-vcalendar':
+ $version = '1.0';
+ case 'text/calendar':
+ // Create the new iCalendar container.
+ $iCal = new Horde_iCalendar($version);
+ $iCal->setAttribute('PRODID', '-//The Horde Project//Nag ' . $GLOBALS['registry']->getVersion() . '//EN');
+ $iCal->setAttribute('METHOD', 'PUBLISH');
+
+ // Create new vTodo object.
+ $vTodo = $task->toiCalendar($iCal);
+ $vTodo->setAttribute('VERSION', $version);
+
+ $iCal->addComponent($vTodo);
+
+ return $iCal->exportvCalendar();
+
+ default:
+ return PEAR::raiseError(sprintf(_("Unsupported Content-Type: %s"), $contentType));
+ }
+ }
+
+ /**
+ * Exports a tasklist in the requested content type.
+ *
+ * @param string $tasklist The tasklist to export.
+ * @param string $contentType What format should the data be in?
+ * A string with one of:
+ * <pre>
+ * text/calendar (VCALENDAR 2.0. Recommended as
+ * this is specified in rfc2445)
+ * text/x-vcalendar (old VCALENDAR 1.0 format.
+ * Still in wide use)
+ * </pre>
+ *
+ * @return string The iCalendar representation of the tasklist.
+ */
+ public function exportTasklist($tasklist, $contentType)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ if (!array_key_exists($tasklist,
+ Nag::listTasklists(false, PERMS_READ))) {
+ return PEAR::raiseError(_("Permission Denied"));
+ }
+
+ $tasks = Nag::listTasks(null, null, null, array($tasklist), 1);
+
+ $version = '2.0';
+ switch ($contentType) {
+ case 'text/x-vcalendar':
+ $version = '1.0';
+ case 'text/calendar':
+ $share = $GLOBALS['nag_shares']->getShare($tasklist);
+
+ $iCal = new Horde_iCalendar($version);
+ $iCal->setAttribute('X-WR-CALNAME', Horde_String::convertCharset($share->get('name'), Horde_Nls::getCharset(), 'utf-8'));
+
+ $tasks->reset();
+ while ($task = $tasks->each()) {
+ $iCal->addComponent($task->toiCalendar($iCal));
+ }
+
+ return $iCal->exportvCalendar();
+ }
+
+ return PEAR::raiseError(sprintf(_("Unsupported Content-Type: %s"), $contentType));
+
+ }
+
+ /**
+ * Deletes a task identified by UID.
+ *
+ * @param string|array $uid Identify the task to delete, either a single UID
+ * or an array.
+ *
+ * @return boolean Success or failure.
+ */
+ public function delete($uid)
+ {
+ // Handle an arrray of UIDs for convenience of deleting multiple tasks at
+ // once.
+ if (is_array($uid)) {
+ foreach ($uid as $g) {
+ $result = $this->delete($g);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ }
+
+ return true;
+ }
+
+ require_once dirname(__FILE__) . '/base.php';
+
+ $storage = Nag_Driver::singleton();
+ $task = $storage->getByUID($uid);
+ if (is_a($task, 'PEAR_Error')) {
+ return $task;
+ }
+
+ if (!Horde_Auth::isAdmin() &&
+ !array_key_exists($task->tasklist,
+ Nag::listTasklists(false, PERMS_DELETE))) {
+ return PEAR::raiseError(_("Permission Denied"));
+ }
+
+ return $storage->delete($task->id);
+ }
+
+ /**
+ * Replaces the task identified by UID with the content represented in the
+ * specified content type.
+ *
+ * If you want to replace multiple tasks with the UID specified in the
+ * VCALENDAR data, you may use $this->import instead. This automatically does a
+ * replace if existings UIDs are found.
+ *
+ *
+ * @param string $uid Identify the task to replace.
+ * @param string $content The content of the task.
+ * @param string $contentType What format is the data in? Currently supports:
+ * - text/x-vcalendar
+ * - text/calendar
+ *
+ * @return boolean Success or failure.
+ */
+ public function replace($uid, $content, $contentType)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ $storage = Nag_Driver::singleton();
+ $existing = $storage->getByUID($uid);
+ if (is_a($existing, 'PEAR_Error')) {
+ return $existing;
+ }
+ $taskId = $existing->id;
+
+ if (!array_key_exists($existing->tasklist, Nag::listTasklists(false, PERMS_EDIT))) {
+ return PEAR::raiseError(_("Permission Denied"));
+ }
+
+ switch ($contentType) {
+ case 'text/calendar':
+ case 'text/x-vcalendar':
+ if (!is_a($content, 'Horde_iCalendar_vtodo')) {
+ $iCal = new Horde_iCalendar();
+ if (!$iCal->parsevCalendar($content)) {
+ return PEAR::raiseError(_("There was an error importing the iCalendar data."));
+ }
+
+ $components = $iCal->getComponents();
+ $component = null;
+ foreach ($components as $content) {
+ if (is_a($content, 'Horde_iCalendar_vtodo')) {
+ if ($component !== null) {
+ return PEAR::raiseError(_("Multiple iCalendar components found; only one vTodo is supported."));
+ }
+ $component = $content;
+ }
+
+ }
+ if ($component === null) {
+ return PEAR::raiseError(_("No iCalendar data was found."));
+ }
+ }
+
+ $task = new Nag_Task();
+ $task->fromiCalendar($content);
+ $result = $storage->modify(
+ $taskId,
+ isset($task->name) ? $task->name : $existing->name,
+ isset($task->desc) ? $task->desc : $existing->desc,
+ isset($task->start) ? $task->start : $existing->start,
+ isset($task->due) ? $task->due : $existing->due,
+ isset($task->priority) ? $task->priority : $existing->priority,
+ isset($task->estimate) ? $task->estimate : 0,
+ isset($task->completed) ? (int)$task->completed : $existing->completed,
+ isset($task->category) ? $task->category : $existing->category,
+ isset($task->alarm) ? $task->alarm : $existing->alarm,
+ isset($task->parent_id) ? $task->parent_id : $existing->parent_id,
+ isset($task->private) ? $task->private : $existing->private,
+ isset($task->owner) ? $task->owner : $existing->owner,
+ isset($task->assignee) ? $task->assignee : $existing->assignee);
+
+ break;
+
+ default:
+ return PEAR::raiseError(sprintf(_("Unsupported Content-Type: %s"), $contentType));
+ }
+
+ return $result;
+ }
+
+ /**
+ * Lists active tasks as cost objects.
+ *
+ * @todo Implement $criteria parameter.
+ *
+ * @param array $criteria Filter attributes
+ */
+ public function listCostObjects($criteria)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ $tasks = Nag::listTasks(null, null, null, null, 1);
+ $result = array();
+ $tasks->reset();
+ while ($task = $tasks->each()) {
+ $result[$task->id] = array('id' => $task->id,
+ 'active' => !$task->completed,
+ 'name' => $task->name);
+ if (!empty($task->estimate)) {
+ $result[$task->id]['estimate'] = $task->estimate;
+ }
+ }
+
+ if (count($result) == 0) {
+ return array();
+ } else {
+ return array(array('category' => _("Tasks"),
+ 'objects' => array_values($result)));
+ }
+ }
+
+ public function listTimeObjectCategories()
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ $categories = array();
+ $tasklists = Nag::listTasklists(false, PERMS_SHOW | PERMS_READ);
+ foreach ($tasklists as $tasklistId => $tasklist) {
+ $categories[$tasklistId] = $tasklist->get('name');
+ }
+ return $categories;
+ }
+
+ /**
+ * Lists active tasks as time objects.
+ *
+ * @param array $categories The time categories (from listTimeObjectCategories) to list.
+ * @param mixed $start The start date of the period.
+ * @param mixed $end The end date of the period.
+ */
+ public function listTimeObjects($categories, $start, $end)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ $allowed_tasklists = Nag::listTasklists(false, PERMS_READ);
+ foreach ($categories as $tasklist) {
+ if (!array_key_exists($tasklist, $allowed_tasklists)) {
+ return PEAR::raiseError(_("Permission Denied"));
+ }
+ }
+
+ $timeobjects = array();
+ $start = new Horde_Date($start);
+ $start_ts = $start->timestamp();
+ $end = new Horde_Date($end);
+ $end_ts = $end->timestamp();
+
+ // List incomplete tasks.
+ $tasks = Nag::listTasks(null, null, null, $categories, 0);
+ $tasks->reset();
+ while ($task = $tasks->each()) {
+ // If there's no due date, it's not a time object.
+ if (!$task->due || $task->due + 1 < $start_ts || $task->due > $end_ts) {
+ continue;
+ }
+ $due_date = date('Y-m-d\TH:i:s', $task->due);
+ $timeobjects[$task->id] = array(
+ 'id' => $task->id,
+ 'title' => $task->name,
+ 'description' => $task->desc,
+ 'start' => $due_date,
+ 'end' => $due_date,
+ 'category' => $task->category,
+ 'params' => array('task' => $task->id,
+ 'tasklist' => $task->tasklist),
+ 'link' => Horde_Util::addParameter(Horde::applicationUrl('view.php', true), array('tasklist' => $task->tasklist, 'task' => $task->id)));
+ }
+
+ return $timeobjects;
+ }
+
+ /**
+ * Lists alarms for a given moment.
+ *
+ * @param integer $time The time to retrieve alarms for.
+ * @param string $user The user to retreive alarms for. All users if null.
+ *
+ * @return array An array of UIDs
+ */
+ public function listAlarms($time, $user = null)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+ require_once 'Horde/Group.php';
+
+ if ((empty($user) || $user != Horde_Auth::getAuth()) && !Horde_Auth::isAdmin()) {
+ return PEAR::raiseError(_("Permission Denied"));
+ }
+
+ $storage = Nag_Driver::singleton();
+ $group = Group::singleton();
+ $alarm_list = array();
+ $tasklists = is_null($user) ? array_keys($GLOBALS['nag_shares']->listAllShares()) : $GLOBALS['display_tasklists'];
+
+ $alarms = Nag::listAlarms($time, $tasklists);
+ if (is_a($alarms, 'PEAR_Error')) {
+ return $alarms;
+ }
+
+ foreach ($alarms as $alarm) {
+ $share = $GLOBALS['nag_shares']->getShare($alarm->tasklist);
+ if (is_a($share, 'PEAR_Error')) {
+ continue;
+ }
+ if (empty($user)) {
+ $users = $share->listUsers(PERMS_READ);
+ $groups = $share->listGroups(PERMS_READ);
+ foreach ($groups as $gid) {
+ $users = array_merge($users, $group->listUsers($gid));
+ }
+ $users = array_unique($users);
+ } else {
+ $users = array($user);
+ }
+ foreach ($users as $alarm_user) {
+ $prefs = Prefs::singleton($GLOBALS['conf']['prefs']['driver'],
+ 'nag', $alarm_user, null, null, false);
+ Horde_Nls::setLanguageEnvironment($prefs->getValue('language'));
+ $alarm_list[] = $alarm->toAlarm($alarm_user, $prefs);
+ }
+ }
+
+ return $alarm_list;
+ }
+
+}
+++ /dev/null
-<?php
-/**
- * Nag external API interface.
- *
- * This file defines Nag's external API interface. Other applications can
- * interact with Nag through this API.
- *
- * @package Nag
- */
-
-$_services['perms'] = array(
- 'args' => array(),
- 'type' => '{urn:horde}hashHash'
-);
-
-$_services['removeUserData'] = array(
- 'args' => array('user' => 'string'),
- 'type' => 'boolean'
-);
-
-$_services['show'] = array(
- 'link' => '%application%/view.php?tasklist=|tasklist|&task=|task|&uid=|uid|',
-);
-
-$_services['browse'] = array(
- 'args' => array('path' => 'string'),
- 'type' => '{urn:horde}hashHash',
-);
-
-$_services['put'] = array(
- 'args' => array('path' => 'string', 'content' => 'string', 'content_type' => 'string'),
- 'type' => 'int',
-);
-
-$_services['path_delete'] = array(
- 'args' => array('path' => 'string'),
- 'type' => 'boolean',
-);
-
-$_services['addTasklist'] = array(
- 'args' => array('name' => 'string', 'description' => 'string'),
- 'type' => 'string',
-);
-
-$_services['listTasklists'] = array(
- 'args' => array('owneronly' => 'boolean', 'permission' => 'int'),
- 'type' => '{urn:horde}stringArray',
-);
-
-$_services['listTasks'] = array(
- 'args' => array('sortby' => 'string', 'sortdir' => 'int', 'altsortby' => 'string', 'tasklists' => '{urn:horde}stringArray', 'completed' => 'int', 'json' => 'boolean'),
- 'type' => '{urn:horde}stringArray',
-);
-
-$_services['listAlarms'] = array(
- 'args' => array('time' => 'int', 'user' => 'string'),
- 'type' => '{urn:horde}hashHash'
-);
-
-$_services['list'] = array(
- 'args' => array(),
- 'type' => '{urn:horde}stringArray',
-);
-
-$_services['listBy'] = array(
- 'args' => array('action' => 'string', 'timestamp' => 'int'),
- 'type' => '{urn:horde}stringArray',
-);
-
-$_services['getActionTimestamp'] = array(
- 'args' => array('uid' => 'string', 'action' => 'string', 'tasklist' => 'string'),
- 'type' => 'int',
-);
-
-$_services['import'] = array(
- 'args' => array('content' => 'string', 'contentType' => 'string', 'tasklist' => 'string'),
- 'type' => 'string',
-);
-
-$_services['quickAdd'] = array(
- 'args' => array('content' => 'string', 'tasklist' => 'string'),
- 'type' => '{urn:horde}stringArray',
-);
-
-$_services['export'] = array(
- 'args' => array('uid' => 'string', 'contentType' => '{urn:horde}stringArray'),
- 'type' => 'string',
-);
-
-$_services['exportTasklist'] = array(
- 'args' => array('tasklist' => 'string', 'contentType' => 'string'),
- 'type' => 'string'
-);
-
-$_services['delete'] = array(
- 'args' => array('uid' => '{urn:horde}stringArray'),
- 'type' => 'boolean',
-);
-
-$_services['replace'] = array(
- 'args' => array('uid' => 'string', 'content' => 'string', 'contentType' => 'string'),
- 'type' => 'boolean',
-);
-
-$_services['listCostObjects'] = array(
- 'args' => array('criteria' => '{urn:horde}hash'),
- 'type' => '{urn:horde}stringArray'
-);
-
-$_services['listTimeObjectCategories'] = array(
- 'type' => '{urn:horde}stringArray'
-);
-
-$_services['listTimeObjects'] = array(
- 'args' => array('start' => 'int', 'end' => 'int'),
- 'type' => '{urn:horde}hashHash'
-);
-
-$_services['toggleCompletion'] = array(
- 'args' => array('task_id' => 'string', 'tasklist_id' => 'string'),
- 'type' => 'boolean'
-);
-
-/**
- * Returns a list of available permissions.
- *
- * @return array An array describing all available permissions.
- */
-function _nag_perms()
-{
- $perms = array();
- $perms['tree']['nag']['max_tasks'] = false;
- $perms['title']['nag:max_tasks'] = _("Maximum Number of Tasks");
- $perms['type']['nag:max_tasks'] = 'int';
-
- return $perms;
-}
-
-/**
- * Removes user data.
- *
- * @param string $user Name of user to remove data for.
- *
- * @return mixed true on success | PEAR_Error on failure
- */
-function _nag_removeUserData($user)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- if (!Horde_Auth::isAdmin() && $user != Horde_Auth::getAuth()) {
- return PEAR::raiseError(_("You are not allowed to remove user data."));
- }
-
- /* Error flag */
- $hasError = false;
-
- /* Get the share for later deletion */
- $share = $GLOBALS['nag_shares']->getShare($user);
- if(is_a($share, 'PEAR_Error')) {
- Horde::logMessage($share->getMessage(), __FILE__, __LINE__, PEAR_LOG_ERR);
- unset($share);
- } else {
- /* Get the list of all tasks */
- $tasks = Nag::listTasks(null, null, null, $user, 1);
- if (is_a($tasks, 'PEAR_Error')) {
- $hasError = true;
- Horde::logMessage($share->getMessage(), __FILE__, __LINE__, PEAR_LOG_ERR);
- } else {
- $uids = array();
- $tasks->reset();
- while ($task = $tasks->each()) {
- $uids[] = $task->uid;
- }
-
- /* ... and delete them. */
- foreach ($uids as $uid) {
- _nag_delete($uid);
- }
- }
- }
-
- /* Now delete history as well. */
- $history = Horde_History::singleton();
- if (method_exists($history, 'removeByParent')) {
- $histories = $history->removeByParent('nag:' . $user);
- } else {
- /* Remove entries 100 at a time. */
- $all = $history->getByTimestamp('>', 0, array(), 'nag:' . $user);
- if (is_a($all, 'PEAR_Error')) {
- Horde::logMessage($all, __FILE__, __LINE__, PEAR_LOG_ERR);
- } else {
- $all = array_keys($all);
- while (count($d = array_splice($all, 0, 100)) > 0) {
- $history->removebyNames($d);
- }
- }
- }
-
- /* ...and finally, delete the actual share */
- if (!empty($share)) {
- $result = $GLOBALS['nag_shares']->removeShare($share);
- if (is_a($result, 'PEAR_Error')) {
- $hasError = true;
- Horde::logMessage($result->getMessage(), __FILE__, __LINE__, PEAR_LOG_ERR);
- }
- }
-
- /* Now remove perms for this user from all other shares */
- $shares = $GLOBALS['nag_shares']->listShares($user);
- if (is_a($shares, 'PEAR_Error')) {
- $hasError = true;
- Horde::logMessage($shares, __FILE__, __LINE__, PEAR_LOG_ERR);
- }
- foreach ($shares as $share) {
- $share->removeUser($user);
- }
-
- if ($hasError) {
- return PEAR::raiseError(sprintf(_("There was an error removing tasks for %s. Details have been logged."), $user));
- } else {
- return true;
- }
-}
-
-/**
- * Retrieves the current user's task list from storage.
- *
- * This function will also sort the resulting list, if requested.
- *
- * @param string $sortby The field by which to sort
- * (NAG_SORT_PRIORITY, NAG_SORT_NAME
- * NAG_SORT_DUE, NAG_SORT_COMPLETION).
- * @param integer $sortdir The direction by which to sort
- * @param string $altsortby The secondary sort field.
- * @param array $tasklists An array of tasklist to display or
- * null/empty to display taskslists
- * $GLOBALS['display_tasklists'].
- * @param integer $completed Which tasks to retrieve (1 = all tasks,
- * 0 = incomplete tasks, 2 = complete tasks,
- * 3 = future tasks, 4 = future and incomplete
- * tasks).
- * @param boolean $json Retrieve the results of the tasks in
- * 'json format'
- *
- * @return Nag_Task A list of the requested tasks.
- */
-function _nag_listTasks($sortby = null, $sortdir = null, $altsortby = null,
- $tasklists = null, $completed = null, $json = false)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- if (!isset($sortby)) {
- $sortby = $GLOBALS['prefs']->getValue('sortby');
- }
- if (!isset($sortdir)) {
- $sortdir = $GLOBALS['prefs']->getValue('sortdir');
- }
- if (is_null($altsortby)) {
- $altsortby = $GLOBALS['prefs']->getValue('altsortby');
- }
- if (is_null($tasklists)) {
- $tasklists = $GLOBALS['display_tasklists'];
- }
- if (is_null($completed)) {
- $completed = $GLOBALS['prefs']->getValue('show_completed');
- }
-
- $tasks = Nag::listTasks($sortby, $sortdir, $altsortby, $tasklists);
- $tasks->reset();
- $list = array();
- while ($task = $tasks->each()) {
- $list[$task->id] = $json ? $task->toJson() : $task->toHash();
- }
-
- return $list;
-}
-
-/**
- * Add a new task list
- *
- * @param string $name Task list name
- * @param string $description Task list description
- *
- * @return integer The new tasklist's id.
- */
-function _nag_addTasklist($name, $description = '')
-{
- if (!Horde_Auth::getAuth()) {
- return PEAR::raiseError(_("Permission denied"));
- }
-
- require_once dirname(__FILE__) . '/base.php';
- global $nag_shares;
-
- $tasklistId = md5(microtime());
- $tasklist = $nag_shares->newShare($tasklistId);
-
- if (is_a($tasklist, 'PEAR_Error')) {
- return $tasklist;
- }
-
- $tasklist->set('name', $name, false);
- $tasklist->set('desc', $description, false);
- $result = $nag_shares->addShare($tasklist);
-
- if (is_a($result, 'PEAR_Error')) {
- return $result;
- }
-
- return $tasklistId;
-}
-
-/**
- * Returns the last modification timestamp of a given uid.
- *
- * @param string $uid The uid to look for.
- * @param string $tasklist The tasklist to look in.
- *
- * @return integer The timestamp for the last modification of $uid.
- */
-function __nag_modified($uid, $tasklist = null)
-{
- $modified = _nag_getActionTimestamp($uid, 'modify', $tasklist);
- if (empty($modified)) {
- $modified = _nag_getActionTimestamp($uid, 'add', $tasklist);
- }
- return $modified;
-}
-
-/**
- * Browse through Nag's object tree.
- *
- * @param string $path The level of the tree to browse.
- * @param array $properties The item properties to return. Defaults to 'name',
- * 'icon', and 'browseable'.
- *
- * @return array The contents of $path
- */
-function _nag_browse($path = '', $properties = array())
-{
- require_once dirname(__FILE__) . '/base.php';
- global $registry;
-
- function _getTasklistSize($tasklistID)
- {
- // This ugly and performance-heavy hack is required to set the content
- // length. Some clients (at least OS X) respect the content-length
- // header a little too exactly. If we send a content-length that is
- // longer than the actual data it will complain that the connection
- // broke. If we specify one that is too short it will truncate the
- // downlaoded file. To make matters worse it seems to respect the
- // content-length from the PROPFIND request used to enumerate objects
- // rather than the actual content-length sent at the time the file is
- // downloaded. Way to go, Apple.
- return strlen(_nag_exportTasklist($tasklistID, 'text/calendar'));
- }
-
- // Default properties.
- if (!$properties) {
- $properties = array('name', 'icon', 'browseable');
- }
-
- if (substr($path, 0, 3) == 'nag') {
- $path = substr($path, 3);
- }
- $path = trim($path, '/');
- $parts = explode('/', $path);
-
- if (empty($path)) {
- //
- // This request is for a list of all users who have tasklists visible
- // to the requesting user.
- //
- $tasklists = Nag::listTasklists(false, PERMS_READ);
- $owners = array();
- foreach ($tasklists as $tasklist) {
- $owners[$tasklist->get('owner')] = true;
- }
-
- $results = array();
- foreach (array_keys($owners) as $owner) {
- if (in_array('name', $properties)) {
- $results['nag/' . $owner]['name'] = $owner;
- }
- if (in_array('icon', $properties)) {
- $results['nag/' . $owner]['icon'] =
- $registry->getImageDir('horde') . '/user.png';
- }
- if (in_array('browseable', $properties)) {
- $results['nag/' . $owner]['browseable'] = true;
- }
- if (in_array('contenttype', $properties)) {
- $results['nag/' . $owner]['contenttype'] =
- 'httpd/unix-directory';
- }
- if (in_array('contentlength', $properties)) {
- $results['nag/' . $owner]['contentlength'] = 0;
- }
- if (in_array('modified', $properties)) {
- $results['nag/' . $owner]['modified'] =
- $_SERVER['REQUEST_TIME'];
- }
- if (in_array('created', $properties)) {
- $results['nag/' . $owner]['created'] = 0;
- }
- }
- return $results;
-
- } elseif (count($parts) == 1) {
- //
- // This request is for all tasklists owned by the requested user
- //
- $tasklists = $GLOBALS['nag_shares']->listShares($parts[0],
- PERMS_SHOW,
- $parts[0]);
-
- // The last check returns all addressbooks for the requested user,
- // but that does not mean the requesting user has access to them.
- // Filter out those address books for which the requesting user has
- // no access.
- $tasklists = Nag::permissionsFilter($tasklists);
-
- $results = array();
- foreach ($tasklists as $tasklistId => $tasklist) {
- $retpath = 'nag/' . $parts[0] . '/' . $tasklistId;
- if (in_array('name', $properties)) {
- $results[$retpath]['name'] = sprintf(_("Tasks from %s"), $tasklist->get('name'));
- $results[$retpath . '.ics']['name'] = $tasklist->get('name');
- }
- if (in_array('icon', $properties)) {
- $results[$retpath]['icon'] = $registry->getImageDir() . '/nag.png';
- $results[$retpath . '.ics']['icon'] = $registry->getImageDir() . '/mime/icalendar.png';
- }
- if (in_array('browseable', $properties)) {
- $results[$retpath]['browseable'] = $tasklist->hasPermission(Horde_Auth::getAuth(), PERMS_READ);
- $results[$retpath . '.ics']['browseable'] = false;
- }
- if (in_array('contenttype', $properties)) {
- $results[$retpath]['contenttype'] = 'httpd/unix-directory';
- $results[$retpath . '.ics']['contenttype'] = 'text/calendar';
- }
- if (in_array('contentlength', $properties)) {
- $results[$retpath]['contentlength'] = 0;
- $results[$retpath . '.ics']['contentlength'] = _getTasklistSize($tasklistId);
- }
- if (in_array('modified', $properties)) {
- // @TODO Find a way to get the actual modification times
- $results[$retpath]['modified'] = $_SERVER['REQUEST_TIME'];
- $results[$retpath . '.ics']['modified'] = $_SERVER['REQUEST_TIME'];
- }
- if (in_array('created', $properties)) {
- // @TODO Find a way to get the actual creation times
- $results[$retpath]['created'] = 0;
- $results[$retpath . '.ics']['created'] = 0;
- }
- }
- return $results;
-
- } elseif (count($parts) == 2 && substr($parts[1], -4) == '.ics') {
- //
- // This is a request for the entire tasklist in iCalendar format.
- //
- $tasklist = substr($parts[1], 0, -4);
- if (!array_key_exists($tasklist, Nag::listTasklists(false, PERMS_READ))) {
- return PEAR::raiseError(_("Invalid tasklist file requested."), 404);
- }
- $ical_data = _nag_exportTasklist($tasklist, 'text/calendar');
- $result = array('data' => $ical_data,
- 'mimetype' => 'text/calendar',
- 'contentlength' => strlen($ical_data),
- 'mtime' => $_SERVER['REQUEST_TIME']);
-
- return $result;
-
- } elseif (count($parts) == 2) {
- //
- // This request is browsing into a specific tasklist. Generate the list
- // of items and represent them as files within the directory.
- //
- if (!array_key_exists($parts[1], Nag::listTasklists(false, PERMS_READ))) {
- return PEAR::raiseError(_("Invalid tasklist requested."), 404);
- }
- $storage = Nag_Driver::singleton($parts[1]);
- $result = $storage->retrieve();
- if (is_a($result, 'PEAR_Error')) {
- $result->code = 500;
- return $result;
- }
-
- $icon = $registry->getImageDir() . '/nag.png';
- $results = array();
- $storage->tasks->reset();
- while ($task = $storage->tasks->each()) {
- $key = 'nag/' . $parts[0] . '/' . $parts[1] . '/' . $task->id;
- if (in_array('name', $properties)) {
- $results[$key]['name'] = $task->name;
- }
- if (in_array('icon', $properties)) {
- $results[$key]['icon'] = $icon;
- }
- if (in_array('browseable', $properties)) {
- $results[$key]['browseable'] = false;
- }
- if (in_array('contenttype', $properties)) {
- $results[$key]['contenttype'] = 'text/calendar';
- }
- if (in_array('contentlength', $properties)) {
- // FIXME: This is a hack. If the content length is longer
- // than the actual data then some WebDAV clients will report
- // an error when the file EOF is received. Ideally we should
- // determine the actual size of the data and report it here, but
- // the performance hit may be prohibitive. This requires
- // further investigation.
- $results[$key]['contentlength'] = 1;
- }
- if (in_array('modified', $properties)) {
- $results[$key]['modified'] = __nag_modified($task->uid, $path);
- }
- if (in_array('created', $properties)) {
- $results[$key]['created'] = _nag_getActionTimestamp($task->uid, 'add', $path);
- }
- }
- return $results;
- } else {
- //
- // The only valid request left is for either a specific task item.
- //
- if (count($parts) == 3 &&
- array_key_exists($parts[1], Nag::listTasklists(false,
- PERMS_READ))) {
- //
- // This request is for a specific item within a given task list.
- //
- /* Create a Nag storage instance. */
- $storage = Nag_Driver::singleton($parts[1]);
- if (is_a($storage, 'PEAR_Error')) {
- return PEAR::raiseError(sprintf(_("Connection failed: %s"), $storage->getMessage()));
- }
- $storage->retrieve();
-
- $task = $storage->get($parts[2]);
- if (is_a($task, 'PEAR_Error')) {
- $task->code = 500;
- return $task;
- }
-
- $result = array('data' => _nag_export($task->uid, 'text/calendar'),
- 'mimetype' => 'text/calendar');
- $modified = __nag_modified($task->uid, $parts[1]);
- if (!empty($modified)) {
- $result['mtime'] = $modified;
- }
- return $result;
- } elseif (count($parts) == 2 &&
- substr($parts[1], -4) == '.ics' &&
- array_key_exists(substr($parts[1], 0, -4), Nag::listTasklists(false, PERMS_READ))) {
- } else {
- //
- // All other requests are a 404: Not Found
- //
- return false;
- }
- }
-}
-
-/**
- * Saves a file into the Nag tree.
- *
- * @param string $path The path where to PUT the file.
- * @param string $content The file content.
- * @param string $content_type The file's content type.
- *
- * @return array The event UIDs, or a PEAR_Error on failure.
- */
-function _nag_put($path, $content, $content_type)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- if (substr($path, 0, 3) == 'nag') {
- $path = substr($path, 3);
- }
- $path = trim($path, '/');
- $parts = explode('/', $path);
-
- if (count($parts) == 2 &&
- substr($parts[1], -4) == '.ics') {
-
- // Workaround for WebDAV clients that are not smart enough to send
- // the right content type. Assume text/calendar.
- if ($content_type == 'application/octet-stream') {
- $content_type = 'text/calendar';
- }
- $tasklist = substr($parts[1], 0, -4);
- } elseif (count($parts) == 3) {
- $tasklist = $parts[1];
-
- // Workaround for WebDAV clients that are not smart enough to send
- // the right content type. Assume the same format we send individual
- // tasklist items: text/calendar
- if ($content_type == 'application/octet-stream') {
- $content_type = 'text/calendar';
- }
- } else {
- return PEAR::raiseError(_("Invalid tasklist name supplied."), 403);
- }
-
- if (!array_key_exists($tasklist, Nag::listTasklists(false, PERMS_EDIT))) {
- // FIXME: Should we attempt to create a tasklist based on the filename
- // in the case that the requested tasklist does not exist?
- return PEAR::raiseError(_("Tasklist does not exist or no permission to edit"), 403);
- }
-
- // Store all currently existings UIDs. Use this info to delete UIDs not
- // present in $content after processing.
- $ids = array();
- $uids_remove = array_flip(_nag_list($tasklist));
-
- $storage = Nag_Driver::singleton($tasklist);
-
- switch ($content_type) {
- case 'text/calendar':
- case 'text/x-vcalendar':
- $iCal = new Horde_iCalendar();
- if (!is_a($content, 'Horde_iCalendar_vtodo')) {
- if (!$iCal->parsevCalendar($content)) {
- return PEAR::raiseError(_("There was an error importing the iCalendar data."), 400);
- }
- } else {
- $iCal->addComponent($content);
- }
-
- foreach ($iCal->getComponents() as $content) {
- if (is_a($content, 'Horde_iCalendar_vtodo')) {
- $task = new Nag_Task();
- $task->fromiCalendar($content);
- $task->tasklist = $tasklist;
- if (isset($task->uid) &&
- !is_a(($existing = $storage->getByUID($task->uid)), 'PEAR_Error')) {
- // Entry exists, remove from uids_remove list so we won't
- // delete in the end.
- if (isset($uids_remove[$task->uid])) {
- unset($uids_remove[$task->uid]);
- }
- if ($existing->private &&
- $existing->owner != Horde_Auth::getAuth()) {
- continue;
- }
- // Check if our task is newer then the existing - get the
- // task's history.
- $history = Horde_History::singleton();
- $created = $modified = null;
- $log = $history->getHistory('nag:' . $tasklist . ':' . $task->uid);
- if ($log && !is_a($log, 'PEAR_Error')) {
- foreach ($log->getData() as $entry) {
- switch ($entry['action']) {
- case 'add':
- $created = $entry['ts'];
- break;
-
- case 'modify':
- $modified = $entry['ts'];
- break;
- }
- }
- }
- if (empty($modified) && !empty($add)) {
- $modified = $add;
- }
- if (!empty($modified) &&
- $modified >= $content->getAttribute('LAST-MODIFIED')) {
- // LAST-MODIFIED timestamp of existing entry is newer:
- // don't replace it.
- continue;
- }
-
- // Don't change creator/owner.
- $owner = $existing->owner;
- $taskId = $existing->id;
- $result = $storage->modify(
- $taskId,
- isset($task->name) ? $task->name : $existing->name,
- isset($task->desc) ? $task->desc : $existing->desc,
- isset($task->start) ? $task->start : $existing->start,
- isset($task->due) ? $task->due : $existing->due,
- isset($task->priority) ? $task->priority : $existing->priority,
- isset($task->estimate) ? $task->estimate : 0,
- isset($task->completed) ? (int)$task->completed : $existing->completed,
- isset($task->category) ? $task->category : $existing->category,
- isset($task->alarm) ? $task->alarm : $existing->alarm,
- isset($task->parent_id) ? $task->parent_id : $existing->parent_id,
- isset($task->private) ? $task->private : $existing->private,
- $owner,
- isset($task->assignee) ? $task->assignee : $existing->assignee);
-
- if (is_a($result, 'PEAR_Error')) {
- $result->code = 500;
- return $result;
- }
- $ids[] = $task->uid;
- } else {
- $newTask = $storage->add(
- isset($task->name) ? $task->name : '',
- isset($task->desc) ? $task->desc : '',
- isset($task->start) ? $task->start : 0,
- isset($task->due) ? $task->due : 0,
- isset($task->priority) ? $task->priority : 3,
- isset($task->estimate) ? $task->estimate : 0,
- !empty($task->completed),
- isset($task->category) ? $task->category : '',
- isset($task->alarm) ? $task->alarm : 0,
- isset($task->uid) ? $task->uid : null,
- isset($task->parent_id) ? $task->parent_id : '',
- !empty($task->private),
- Horde_Auth::getAuth(),
- isset($task->assignee) ? $task->assignee : null);
- if (is_a($newTask, 'PEAR_Error')) {
- $newtask->code = 500;
- return $newTask;
- }
- // use UID rather than ID
- $ids[] = $newTask[1];
- }
- }
- }
- break;
-
- default:
- return PEAR::raiseError(sprintf(_("Unsupported Content-Type: %s"), $content_type), 400);
- }
-
- if (array_key_exists($tasklist, Nag::listTasklists(false, PERMS_DELETE))) {
- foreach (array_keys($uids_remove) as $uid) {
- _nag_delete($uid);
- }
- }
-
- return $ids;
-}
-
-/**
- * Deletes a file from the Nag tree.
- *
- * @param string $path The path to the file.
- *
- * @return mixed The event's UID, or a PEAR_Error on failure.
- */
-function _nag_path_delete($path)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- if (substr($path, 0, 3) == 'nag') {
- $path = substr($path, 3);
- }
- $path = trim($path, '/');
- $parts = explode('/', $path);
-
- if (count($parts) == 2) {
- // @TODO Deny deleting of the entire tasklist for now.
- // Allow users to delete tasklists but not create them via WebDAV will
- // be more confusing than helpful. They are, however, still able to
- // delete individual task items within the tasklist folder.
- return PEAR::raiseError(_("Deleting entire tasklists is not supported."), 403);
- // To re-enable the functionality just remove this if {} block.
- }
-
- if (substr($parts[1], -4) == '.ics') {
- $tasklistID = substr($parts[1], 0, -4);
- } else {
- $tasklistID = $parts[1];
- }
-
- if (!(count($parts) == 2 || count($parts) == 3) ||
- !array_key_exists($tasklistID, Nag::listTasklists(false, PERMS_DELETE))) {
- return PEAR::raiseError(_("Tasklist does not exist or no permission to delete"), 403);
- }
-
- /* Create a Nag storage instance. */
- $storage = Nag_Driver::singleton($tasklistID);
- if (is_a($storage, 'PEAR_Error')) {
- return PEAR::raiseError(sprintf(_("Connection failed: %s"), $storage->getMessage()), 500);
- }
- $retrieved = $storage->retrieve();
- if (is_a($retrieved, 'PEAR_Error')) {
- $retrieved->code = 500;
- return $retrieved;
- }
-
- if (count($parts) == 3) {
- // Delete just a single entry
- return $storage->delete($parts[2]);
- } else {
- // Delete the entire task list
- $result = $storage->deleteAll();
- if (is_a($result, 'PEAR_Error')) {
- return PEAR::raiseError(sprintf(_("Unable to delete tasklist \"%s\": %s"), $tasklistID, $result->getMessage()), 500);
- } else {
- // Remove share and all groups/permissions.
- $share = $GLOBALS['nag_shares']->getShare($tasklistID);
- $result = $GLOBALS['nag_shares']->removeShare($share);
- if (is_a($result, 'PEAR_Error')) {
- $result->code = 500;
- return $result;
- }
- }
- }
-}
-
-/**
- * @param boolean $owneronly Only return tasklists that this user owns?
- * Defaults to false.
- * @param integer $permission The permission to filter tasklists by.
- *
- * @return array The task lists.
- */
-function _nag_listTasklists($owneronly, $permission)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- return Nag::listTasklists($owneronly, $permission);
-}
-
-/**
- * Returns an array of UIDs for all tasks that the current user is authorized
- * to see.
- *
- * @param variant $tasklist The tasklist or an array of taskslists to list.
- *
- * @return array An array of UIDs for all tasks
- * the user can access.
- */
-function _nag_list($tasklist = null)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- if (!isset($GLOBALS['conf']['storage']['driver'])) {
- return PEAR::raiseError(_("Not configured"));
- }
-
- if ($tasklist === null) {
- $tasklist = Nag::getDefaultTasklist(PERMS_READ);
- }
-
- if (!array_key_exists($tasklist,
- Nag::listTasklists(false, PERMS_READ))) {
- return PEAR::raiseError(_("Permission Denied"));
- }
-
- $tasks = Nag::listTasks(null, null, null, $tasklist, 1);
- if (is_a($tasks, 'PEAR_Error')) {
- return $tasks;
- }
-
- $uids = array();
- $tasks->reset();
- while ($task = $tasks->each()) {
- $uids[] = $task->uid;
- }
-
- return $uids;
-}
-
-/**
- * Returns an array of UIDs for tasks that have had $action happen since
- * $timestamp.
- *
- * @param string $action The action to check for - add, modify, or delete.
- * @param integer $timestamp The time to start the search.
- * @param string $tasklist The tasklist to be used. If 'null', the
- * user's default tasklist will be used.
- *
- * @return array An array of UIDs matching the action and time criteria.
- */
-function _nag_listBy($action, $timestamp, $tasklist = null)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- if ($tasklist === null) {
- $tasklist = Nag::getDefaultTasklist(PERMS_READ);
- }
-
- if (!array_key_exists($tasklist,
- Nag::listTasklists(false, PERMS_READ))) {
- return PEAR::raiseError(_("Permission Denied"));
- }
-
- $history = Horde_History::singleton();
- $histories = $history->getByTimestamp('>', $timestamp, array(array('op' => '=', 'field' => 'action', 'value' => $action)), 'nag:' . $tasklist);
- if (is_a($histories, 'PEAR_Error')) {
- return $histories;
- }
-
- // Strip leading nag:username:.
- return preg_replace('/^([^:]*:){2}/', '', array_keys($histories));
-}
-
-/**
- * Returns the timestamp of an operation for a given uid an action.
- *
- * @param string $uid The uid to look for.
- * @param string $action The action to check for - add, modify, or delete.
- * @param string $tasklist The tasklist to be used. If 'null', the
- * user's default tasklist will be used.
- *
- * @return integer The timestamp for this action.
- */
-function _nag_getActionTimestamp($uid, $action, $tasklist = null)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- if ($tasklist === null) {
- $tasklist = Nag::getDefaultTasklist(PERMS_READ);
- }
-
- if (!array_key_exists($tasklist,
- Nag::listTasklists(false, PERMS_READ))) {
- return PEAR::raiseError(_("Permission Denied"));
- }
-
- $history = Horde_History::singleton();
- return $history->getActionTimestamp('nag:' . $tasklist . ':' . $uid, $action);
-}
-
-/**
- * Imports one or more tasks represented in the specified content type.
- *
- * If a UID is present in the content and the task is already in the
- * database, a replace is performed rather than an add.
- *
- * @param string $content The content of the task.
- * @param string $contentType What format is the data in? Currently supports:
- * text/calendar
- * text/x-vcalendar
- * @param string $tasklist The tasklist into which the task will be
- * imported. If 'null', the user's default
- * tasklist will be used.
- *
- * @return string The new UID on one import, an array of UIDs on multiple imports,
- * or PEAR_Error on failure.
- */
-function _nag_import($content, $contentType, $tasklist = null)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- if ($tasklist === null) {
- $tasklist = Nag::getDefaultTasklist(PERMS_EDIT);
- }
-
- if (!array_key_exists($tasklist, Nag::listTasklists(false, PERMS_EDIT))) {
- return PEAR::raiseError(_("Permission Denied"));
- }
-
- /* Create a Nag_Driver instance. */
- $storage = Nag_Driver::singleton($tasklist);
-
- switch ($contentType) {
- case 'text/x-vcalendar':
- case 'text/calendar':
- case 'text/x-vtodo':
- $iCal = new Horde_iCalendar();
- if (!is_a($content, 'Horde_iCalendar_vtodo')) {
- if (!$iCal->parsevCalendar($content)) {
- return PEAR::raiseError(_("There was an error importing the iCalendar data."));
- }
- } else {
- $iCal->addComponent($content);
- }
-
- $components = $iCal->getComponents();
- if (count($components) == 0) {
- return PEAR::raiseError(_("No iCalendar data was found."));
- }
-
- $ids = array();
- foreach ($components as $content) {
- if (is_a($content, 'Horde_iCalendar_vtodo')) {
- $task = new Nag_Task();
- $task->fromiCalendar($content);
- if (isset($task->uid) &&
- !is_a(($existing = $storage->getByUID($task->uid)), 'PEAR_Error')) {
- $taskId = $existing->id;
- $result = $storage->modify(
- $taskId,
- isset($task->name) ? $task->name : $existing->name,
- isset($task->desc) ? $task->desc : $existing->desc,
- isset($task->start) ? $task->start : $existing->start,
- isset($task->due) ? $task->due : $existing->due,
- isset($task->priority) ? $task->priority : $existing->priority,
- isset($task->estimate) ? $task->estimate : 0,
- isset($task->completed) ? (int)$task->completed : $existing->completed,
- isset($task->category) ? $task->category : $existing->category,
- isset($task->alarm) ? $task->alarm : $existing->alarm,
- isset($task->parent_id) ? $task->parent_id : $existing->parent_id,
- isset($task->private) ? $task->private : $existing->private,
- isset($task->owner) ? $task->owner : $existing->owner,
- isset($task->assignee) ? $task->assignee : $existing->assignee);
-
- if (is_a($result, 'PEAR_Error')) {
- return $result;
- }
- $ids[] = $task->uid;
- } else {
- $newTask = $storage->add(
- isset($task->name) ? $task->name : '',
- isset($task->desc) ? $task->desc : '',
- isset($task->start) ? $task->start : 0,
- isset($task->due) ? $task->due : 0,
- isset($task->priority) ? $task->priority : 3,
- isset($task->estimate) ? $task->estimate : 0,
- !empty($task->completed),
- isset($task->category) ? $task->category : '',
- isset($task->alarm) ? $task->alarm : 0,
- isset($task->methods) ? $task->methods : null,
- isset($task->uid) ? $task->uid : null,
- isset($task->parent_id) ? $task->parent_id : '',
- !empty($task->private),
- Horde_Auth::getAuth(),
- isset($task->assignee) ? $task->assignee : null);
- if (is_a($newTask, 'PEAR_Error')) {
- return $newTask;
- }
- // use UID rather than ID
- $ids[] = $newTask[1];
- }
- }
- }
- if (count($ids) == 0) {
- return PEAR::raiseError(_("No iCalendar data was found."));
- } else if (count($ids) == 1) {
- return $ids[0];
- }
- return $ids;
- }
-
- return PEAR::raiseError(sprintf(_("Unsupported Content-Type: %s"), $contentType));
-}
-
-/**
- * Imports one or more tasks parsed from a string.
- *
- * @param string $text The text to parse into
- * @param string $tasklist The tasklist into which the task will be
- * imported. If 'null', the user's default
- * tasklist will be used.
- *
- * @return array The UIDs of all tasks that were added.
- */
-function _nag_quickAdd($text, $tasklist = null)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- if ($tasklist === null) {
- $tasklist = Nag::getDefaultTasklist(PERMS_EDIT);
- }
- if (!array_key_exists($tasklist, Nag::listTasklists(false, PERMS_EDIT))) {
- return PEAR::raiseError(_("Permission Denied"));
- }
-
- return Nag::createTasksFromText($text, $tasklist);
-}
-
-/**
- * Toggles the task completion flag.
- *
- * @param string $task_id The task ID.
- * @param string $tasklist_id The tasklist that contains the task.
- */
-function _nag_toggleCompletion($task_id, $tasklist_id)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- if (!array_key_exists($tasklist_id,
- Nag::listTasklists(false, PERMS_EDIT))) {
- return PEAR::raiseError(_("Permission Denied"));
- }
-
- $share = $GLOBALS['nag_shares']->getShare($tasklist_id);
- if (is_a($share, 'PEAR_Error')) {
- return $share;
- }
-
- $task = Nag::getTask($tasklist_id, $task_id);
- if (is_a($task, 'PEAR_Error')) {
- return $task;
- }
-
- $task->completed = !$task->completed;
- if ($task->completed) {
- $task->completed_date = time();
- } else {
- $task->completed_date = null;
- }
-
- return $task->save();
-}
-
-/**
- * Exports a task, identified by UID, in the requested content type.
- *
- * @param string $uid Identify the task to export.
- * @param string $contentType What format should the data be in?
- * A string with one of:
- * <pre>
- * text/calendar - (VCALENDAR 2.0. Recommended as this is specified in
- * rfc2445)
- * text/x-vcalendar - (old VCALENDAR 1.0 format. Still in wide use)
- * </pre>
- *
- * @return string The requested data.
- */
-function _nag_export($uid, $contentType)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- $storage = Nag_Driver::singleton();
- $task = $storage->getByUID($uid);
- if (is_a($task, 'PEAR_Error')) {
- return $task;
- }
-
- if (!array_key_exists($task->tasklist, Nag::listTasklists(false, PERMS_READ))) {
- return PEAR::raiseError(_("Permission Denied"));
- }
-
- $version = '2.0';
- switch ($contentType) {
- case 'text/x-vcalendar':
- $version = '1.0';
- case 'text/calendar':
- // Create the new iCalendar container.
- $iCal = new Horde_iCalendar($version);
- $iCal->setAttribute('PRODID', '-//The Horde Project//Nag ' . $GLOBALS['registry']->getVersion() . '//EN');
- $iCal->setAttribute('METHOD', 'PUBLISH');
-
- // Create new vTodo object.
- $vTodo = $task->toiCalendar($iCal);
- $vTodo->setAttribute('VERSION', $version);
-
- $iCal->addComponent($vTodo);
-
- return $iCal->exportvCalendar();
-
- default:
- return PEAR::raiseError(sprintf(_("Unsupported Content-Type: %s"), $contentType));
- }
-}
-
-/**
- * Exports a tasklist in the requested content type.
- *
- * @param string $tasklist The tasklist to export.
- * @param string $contentType What format should the data be in?
- * A string with one of:
- * <pre>
- * text/calendar (VCALENDAR 2.0. Recommended as
- * this is specified in rfc2445)
- * text/x-vcalendar (old VCALENDAR 1.0 format.
- * Still in wide use)
- * </pre>
- *
- * @return string The iCalendar representation of the tasklist.
- */
-function _nag_exportTasklist($tasklist, $contentType)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- if (!array_key_exists($tasklist,
- Nag::listTasklists(false, PERMS_READ))) {
- return PEAR::raiseError(_("Permission Denied"));
- }
-
- $tasks = Nag::listTasks(null, null, null, array($tasklist), 1);
-
- $version = '2.0';
- switch ($contentType) {
- case 'text/x-vcalendar':
- $version = '1.0';
- case 'text/calendar':
- $share = $GLOBALS['nag_shares']->getShare($tasklist);
-
- $iCal = new Horde_iCalendar($version);
- $iCal->setAttribute('X-WR-CALNAME', Horde_String::convertCharset($share->get('name'), Horde_Nls::getCharset(), 'utf-8'));
-
- $tasks->reset();
- while ($task = $tasks->each()) {
- $iCal->addComponent($task->toiCalendar($iCal));
- }
-
- return $iCal->exportvCalendar();
- }
-
- return PEAR::raiseError(sprintf(_("Unsupported Content-Type: %s"), $contentType));
-
-}
-
-/**
- * Deletes a task identified by UID.
- *
- * @param string|array $uid Identify the task to delete, either a single UID
- * or an array.
- *
- * @return boolean Success or failure.
- */
-function _nag_delete($uid)
-{
- // Handle an arrray of UIDs for convenience of deleting multiple tasks at
- // once.
- if (is_array($uid)) {
- foreach ($uid as $g) {
- $result = _nag_delete($g);
- if (is_a($result, 'PEAR_Error')) {
- return $result;
- }
- }
-
- return true;
- }
-
- require_once dirname(__FILE__) . '/base.php';
-
- $storage = Nag_Driver::singleton();
- $task = $storage->getByUID($uid);
- if (is_a($task, 'PEAR_Error')) {
- return $task;
- }
-
- if (!Horde_Auth::isAdmin() &&
- !array_key_exists($task->tasklist,
- Nag::listTasklists(false, PERMS_DELETE))) {
- return PEAR::raiseError(_("Permission Denied"));
- }
-
- return $storage->delete($task->id);
-}
-
-/**
- * Replaces the task identified by UID with the content represented in the
- * specified content type.
- *
- * If you want to replace multiple tasks with the UID specified in the
- * VCALENDAR data, you may use _nag_import instead. This automatically does a
- * replace if existings UIDs are found.
- *
- *
- * @param string $uid Identify the task to replace.
- * @param string $content The content of the task.
- * @param string $contentType What format is the data in? Currently supports:
- * - text/x-vcalendar
- * - text/calendar
- *
- * @return boolean Success or failure.
- */
-function _nag_replace($uid, $content, $contentType)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- $storage = Nag_Driver::singleton();
- $existing = $storage->getByUID($uid);
- if (is_a($existing, 'PEAR_Error')) {
- return $existing;
- }
- $taskId = $existing->id;
-
- if (!array_key_exists($existing->tasklist, Nag::listTasklists(false, PERMS_EDIT))) {
- return PEAR::raiseError(_("Permission Denied"));
- }
-
- switch ($contentType) {
- case 'text/calendar':
- case 'text/x-vcalendar':
- if (!is_a($content, 'Horde_iCalendar_vtodo')) {
- $iCal = new Horde_iCalendar();
- if (!$iCal->parsevCalendar($content)) {
- return PEAR::raiseError(_("There was an error importing the iCalendar data."));
- }
-
- $components = $iCal->getComponents();
- $component = null;
- foreach ($components as $content) {
- if (is_a($content, 'Horde_iCalendar_vtodo')) {
- if ($component !== null) {
- return PEAR::raiseError(_("Multiple iCalendar components found; only one vTodo is supported."));
- }
- $component = $content;
- }
-
- }
- if ($component === null) {
- return PEAR::raiseError(_("No iCalendar data was found."));
- }
- }
-
- $task = new Nag_Task();
- $task->fromiCalendar($content);
- $result = $storage->modify(
- $taskId,
- isset($task->name) ? $task->name : $existing->name,
- isset($task->desc) ? $task->desc : $existing->desc,
- isset($task->start) ? $task->start : $existing->start,
- isset($task->due) ? $task->due : $existing->due,
- isset($task->priority) ? $task->priority : $existing->priority,
- isset($task->estimate) ? $task->estimate : 0,
- isset($task->completed) ? (int)$task->completed : $existing->completed,
- isset($task->category) ? $task->category : $existing->category,
- isset($task->alarm) ? $task->alarm : $existing->alarm,
- isset($task->parent_id) ? $task->parent_id : $existing->parent_id,
- isset($task->private) ? $task->private : $existing->private,
- isset($task->owner) ? $task->owner : $existing->owner,
- isset($task->assignee) ? $task->assignee : $existing->assignee);
-
- break;
-
- default:
- return PEAR::raiseError(sprintf(_("Unsupported Content-Type: %s"), $contentType));
- }
-
- return $result;
-}
-
-/**
- * Lists active tasks as cost objects.
- *
- * @todo Implement $criteria parameter.
- *
- * @param array $criteria Filter attributes
- */
-function _nag_listCostObjects($criteria)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- $tasks = Nag::listTasks(null, null, null, null, 1);
- $result = array();
- $tasks->reset();
- while ($task = $tasks->each()) {
- $result[$task->id] = array('id' => $task->id,
- 'active' => !$task->completed,
- 'name' => $task->name);
- if (!empty($task->estimate)) {
- $result[$task->id]['estimate'] = $task->estimate;
- }
- }
-
- if (count($result) == 0) {
- return array();
- } else {
- return array(array('category' => _("Tasks"),
- 'objects' => array_values($result)));
- }
-}
-
-function _nag_listTimeObjectCategories()
-{
- require_once dirname(__FILE__) . '/base.php';
-
- $categories = array();
- $tasklists = Nag::listTasklists(false, PERMS_SHOW | PERMS_READ);
- foreach ($tasklists as $tasklistId => $tasklist) {
- $categories[$tasklistId] = $tasklist->get('name');
- }
- return $categories;
-}
-
-/**
- * Lists active tasks as time objects.
- *
- * @param array $categories The time categories (from listTimeObjectCategories) to list.
- * @param mixed $start The start date of the period.
- * @param mixed $end The end date of the period.
- */
-function _nag_listTimeObjects($categories, $start, $end)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- $allowed_tasklists = Nag::listTasklists(false, PERMS_READ);
- foreach ($categories as $tasklist) {
- if (!array_key_exists($tasklist, $allowed_tasklists)) {
- return PEAR::raiseError(_("Permission Denied"));
- }
- }
-
- $timeobjects = array();
- $start = new Horde_Date($start);
- $start_ts = $start->timestamp();
- $end = new Horde_Date($end);
- $end_ts = $end->timestamp();
-
- // List incomplete tasks.
- $tasks = Nag::listTasks(null, null, null, $categories, 0);
- $tasks->reset();
- while ($task = $tasks->each()) {
- // If there's no due date, it's not a time object.
- if (!$task->due || $task->due + 1 < $start_ts || $task->due > $end_ts) {
- continue;
- }
- $due_date = date('Y-m-d\TH:i:s', $task->due);
- $timeobjects[$task->id] = array(
- 'id' => $task->id,
- 'title' => $task->name,
- 'description' => $task->desc,
- 'start' => $due_date,
- 'end' => $due_date,
- 'category' => $task->category,
- 'params' => array('task' => $task->id,
- 'tasklist' => $task->tasklist),
- 'link' => Horde_Util::addParameter(Horde::applicationUrl('view.php', true), array('tasklist' => $task->tasklist, 'task' => $task->id)));
- }
-
- return $timeobjects;
-}
-
-/**
- * Lists alarms for a given moment.
- *
- * @param integer $time The time to retrieve alarms for.
- * @param string $user The user to retreive alarms for. All users if null.
- *
- * @return array An array of UIDs
- */
-function _nag_listAlarms($time, $user = null)
-{
- require_once dirname(__FILE__) . '/base.php';
- require_once 'Horde/Group.php';
-
- if ((empty($user) || $user != Horde_Auth::getAuth()) && !Horde_Auth::isAdmin()) {
- return PEAR::raiseError(_("Permission Denied"));
- }
-
- $storage = Nag_Driver::singleton();
- $group = Group::singleton();
- $alarm_list = array();
- $tasklists = is_null($user) ? array_keys($GLOBALS['nag_shares']->listAllShares()) : $GLOBALS['display_tasklists'];
-
- $alarms = Nag::listAlarms($time, $tasklists);
- if (is_a($alarms, 'PEAR_Error')) {
- return $alarms;
- }
-
- foreach ($alarms as $alarm) {
- $share = $GLOBALS['nag_shares']->getShare($alarm->tasklist);
- if (is_a($share, 'PEAR_Error')) {
- continue;
- }
- if (empty($user)) {
- $users = $share->listUsers(PERMS_READ);
- $groups = $share->listGroups(PERMS_READ);
- foreach ($groups as $gid) {
- $users = array_merge($users, $group->listUsers($gid));
- }
- $users = array_unique($users);
- } else {
- $users = array($user);
- }
- foreach ($users as $alarm_user) {
- $prefs = Prefs::singleton($GLOBALS['conf']['prefs']['driver'],
- 'nag', $alarm_user, null, null, false);
- Horde_Nls::setLanguageEnvironment($prefs->getValue('language'));
- $alarm_list[] = $alarm->toAlarm($alarm_user, $prefs);
- }
- }
-
- return $alarm_list;
-}
+++ /dev/null
-<?php define('NAG_VERSION', 'H3 (3.0-git)') ?>
--- /dev/null
+<?php
+/**
+ * News api
+ *
+ * $Id: api.php 1260 2009-02-01 23:15:50Z duck $
+ *
+ * Copyright 2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author Duck <duck@obala.net>
+ * @package News
+ */
+class News_Api extends Horde_Registry_Api
+{
+ public $version = 'H4 (0.1-git)';
+
+ public $services = array(
+ 'perms' => array(
+ 'args' => array(),
+ 'type' => '{urn:horde}hashHash'
+ ),
+
+ 'commentCallback' => array(
+ 'args' => array('id' => 'string'),
+ 'type' => 'string'
+ ),
+
+ 'hasComments' => array(
+ 'args' => array(),
+ 'type' => 'boolean'
+ ),
+
+ 'listNews' => array(
+ 'args' => array('perms' => 'string', 'criteria' => 'array', 'from' => 'int', 'count' => 'int'),
+ 'type' => 'array'
+ ),
+
+ 'countNews' => array(
+ 'args' => array('perms' => 'string', 'criteria' => 'array'),
+ 'type' => 'int'
+ )
+ );
+
+ /**
+ * Categories/Permissions
+ */
+ public function perms()
+ {
+ static $perms = array();
+ if (!empty($perms)) {
+ return $perms;
+ }
+
+ $perms['tree']['news']['admin'] = true;
+ $perms['title']['news:admin'] = _("Admin");
+
+ $perms['tree']['news']['editors'] = true;
+ $perms['title']['news:editors'] = _("Editors");
+
+ require_once dirname(__FILE__) . '/base.php';
+ $tree = $GLOBALS['news_cat']->getEnum();
+
+ $perms['title']['news:categories'] = _("Categories");
+ foreach ($tree as $cat_id => $cat_name) {
+ $perms['tree']['news']['categories'][$cat_id] = false;
+ $perms['title']['news:categories:' . $cat_id] = $cat_name;
+ }
+
+ return $perms;
+ }
+
+ /**
+ * Callback for comment API
+ *
+ * @param int $id Internal data identifier
+ * @param string $type Type of data to retreive (title, owner...)
+ * @param array $params Additional parameters
+ */
+ public function commentCallback($id, $type = 'title', $params = null)
+ {
+ static $info;
+
+ if (!empty($info[$id][$type])) {
+ return $info[$id][$type];
+ }
+
+ require_once dirname(__FILE__) . '/base.php';
+
+ $news = $GLOBALS['news']->get($id);
+ if ($news instanceof PEAR_Error) {
+ return $news;
+ }
+
+ switch ($type) {
+
+ case 'owner':
+ return $news['user'];
+
+ case 'link':
+ return News::getUrlFor('news', $id, true, -1);
+
+ case 'messages':
+ $GLOBALS['news']->updateComments($id, $params);
+
+ if ($GLOBALS['registry']->hasMethod('logActivity', 'folks')) {
+ $link = '<a href="' . News::getUrlFor('news', $id) . '">' . $news['title'] . '</a>';
+ $message = sprintf(_("Has commented news \"%s\""), $link);
+ $GLOBALS['registry']->callByPackage('folks', 'logActivity', array($message, 'news'));
+ }
+
+ return true;
+
+ default:
+ $info[$id][$type] = $news['title'];
+ return $news['title'];
+ }
+ }
+
+ /**
+ * Returns if applications allows comments
+ */
+ public function hasComments()
+ {
+ return $GLOBALS['conf']['comments']['allow'];
+ }
+
+ /**
+ * List news
+ *
+ * @param array $criteria Array of news attributes match
+ * @param intiger $from Start fetching from news
+ * @param intiger $count The number of news to fetch
+ * @param intiger $perms News permission access type
+ *
+ * @return array | PEAR_Error True on success, PEAR_Error on failure.
+ */
+ public function listNews($criteria = array(), $from = 0, $count = 0, $perms = PERMS_READ)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ return $GLOBALS['news']->listNews($criteria, $from, $count, $perms);
+ }
+
+ /**
+ * Count news
+ *
+ * @param array $criteria Array of news attributes match
+ * @param integer $perms Permisson level
+ *
+ * @return integer | PEAR_Error True on success, PEAR_Error on failure.
+ */
+ public function countNews($criteria = array(), $perms = PERMS_READ)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ return $GLOBALS['news']->countNews($criteria, $perms);
+ }
+
+}
+++ /dev/null
-<?php
-/**
- * News api
- *
- * $Id: api.php 1260 2009-02-01 23:15:50Z duck $
- *
- * Copyright 2009 The Horde Project (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
- *
- * @author Duck <duck@obala.net>
- * @package News
- */
-
-$_services['perms'] = array(
- 'args' => array(),
- 'type' => '{urn:horde}stringArray'
-);
-
-$_services['commentCallback'] = array(
- 'args' => array('id' => 'string'),
- 'type' => 'string'
-);
-
-$_services['hasComments'] = array(
- 'args' => array(),
- 'type' => 'boolean'
-);
-
-$_services['listNews'] = array(
- 'args' => array('perms' => 'string', 'criteria' => 'array', 'from' => 'int', 'count' => 'int'),
- 'type' => 'array'
-);
-
-$_services['countNews'] = array(
- 'args' => array('perms' => 'string', 'criteria' => 'array'),
- 'type' => 'int'
-);
-
-/**
- * Categories/Permissions
- */
-function _news_perms()
-{
- static $perms = array();
- if (!empty($perms)) {
- return $perms;
- }
-
- $perms['tree']['news']['admin'] = true;
- $perms['title']['news:admin'] = _("Admin");
-
- $perms['tree']['news']['editors'] = true;
- $perms['title']['news:editors'] = _("Editors");
-
- require_once dirname(__FILE__) . '/base.php';
- $tree = $GLOBALS['news_cat']->getEnum();
-
- $perms['title']['news:categories'] = _("Categories");
- foreach ($tree as $cat_id => $cat_name) {
- $perms['tree']['news']['categories'][$cat_id] = false;
- $perms['title']['news:categories:' . $cat_id] = $cat_name;
- }
-
- return $perms;
-}
-
-/**
- * Callback for comment API
- *
- * @param int $id Internal data identifier
- * @param string $type Type of data to retreive (title, owner...)
- * @param array $params Additional parameters
- */
-function _news_commentCallback($id, $type = 'title', $params = null)
-{
- static $info;
-
- if (!empty($info[$id][$type])) {
- return $info[$id][$type];
- }
-
- require_once dirname(__FILE__) . '/base.php';
-
- $news = $GLOBALS['news']->get($id);
- if ($news instanceof PEAR_Error) {
- return $news;
- }
-
- switch ($type) {
-
- case 'owner':
- return $news['user'];
-
- case 'link':
- return News::getUrlFor('news', $id, true, -1);
-
- case 'messages':
- $GLOBALS['news']->updateComments($id, $params);
-
- if ($GLOBALS['registry']->hasMethod('logActivity', 'folks')) {
- $link = '<a href="' . News::getUrlFor('news', $id) . '">' . $news['title'] . '</a>';
- $message = sprintf(_("Has commented news \"%s\""), $link);
- $GLOBALS['registry']->callByPackage('folks', 'logActivity', array($message, 'news'));
- }
-
- return true;
-
- default:
- $info[$id][$type] = $news['title'];
- return $news['title'];
- }
-}
-
-/**
- * Returns if applications allows comments
- */
-function _news_hasComments()
-{
- return $GLOBALS['conf']['comments']['allow'];
-}
-
-/**
- * List news
- *
- * @param array $criteria Array of news attributes match
- * @param intiger $from Start fetching from news
- * @param intiger $count The number of news to fetch
- * @param intiger $perms News permission access type
- *
- * @return array | PEAR_Error True on success, PEAR_Error on failure.
- */
-function _news_listNews($criteria = array(), $from = 0, $count = 0, $perms = PERMS_READ)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- return $GLOBALS['news']->listNews($criteria, $from, $count, $perms);
-}
-
-/**
- * Count news
- *
- * @param array $criteria Array of news attributes match
- * @param integer $perms Permisson level
- *
- * @return integer | PEAR_Error True on success, PEAR_Error on failure.
- */
-function _news_countNews($criteria = array(), $perms = PERMS_READ)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- return $GLOBALS['news']->countNews($criteria, $perms);
-}
-
+++ /dev/null
-<?php define('NEWS_VERSION', 'H4 (0.1-git)') ?>
\ No newline at end of file
--- /dev/null
+<?php
+class Skeleton_Api extends Horde_Registry_Api
+{
+ public $version = 'H4 (0.1-git)';
+}
+++ /dev/null
-<?php define('SKELETON_VERSION', '0.1-git') ?>
--- /dev/null
+<?php
+class Skoli_Api extends Horde_Registry_Api
+{
+ public $version = 'H4 (0.1-git)';
+}
+++ /dev/null
-<?php define('SKOLI_VERSION', '0.1-cvs') ?>
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * API methods for exposing various bits of data via the listTimeObjects API
+ */
+class Timeobjects_Api extends Horde_Registry_Api
+{
+ /**
+ * The services provided by this application.
+ *
+ * @var array
+ */
+ public $services = array(
+ 'listTimeObjectCategories' => array(
+ 'type' => '{urn:horde}stringArray'
+ ),
+
+ 'listTimeObjects' => array(
+ 'args' => array(
+ 'categories' => '{urn:horde}stringArray',
+ 'start' => 'integer',
+ 'end' => 'integer'
+ ),
+ 'type' => '{urn:horde}hashHash'
+ ),
+
+ // @TODO: Probably implement a URL endpoint or something so we can link
+ // to the correct external site depending on what time object category
+ // we are referring to.
+ 'show' => array(
+ 'link' => '#',
+ )
+ );
+
+ /**
+ * Returns the available categories we provide.
+ *
+ * Right now, only providing weather data.
+ *
+ * @return array
+ */
+ public function listTimeObjectCategories()
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ // @TODO: Probably want to iterate the driver directory
+ // and dynamically build this list and/or maybe provide
+ // a $conf[] setting to explicitly disable certain drivers?
+ $drivers = array();
+
+ $drv = TimeObjects_Driver::factory('Weatherdotcom');
+ if ($drv->ensure()) {
+ $drivers['Weatherdotcom'] = _("Weather");
+ }
+
+ $drv = TimeObjects_Driver::factory('FacebookEvents');
+ if ($drv->ensure()) {
+ $drivers['FacebookEvents'] = _("Facebook Events");
+ }
+
+ return $drivers;
+ }
+
+ /**
+ * Obtain the timeObjects for the requested category
+ *
+ * @param array $time_categories An array of categories to list
+ * @param mixed $start The start of the time period to list for
+ * @param mixed $end The end of the time period to list for
+ *
+ * @return An array of timeobject arrays.
+ */
+ public function listTimeObjects($time_categories, $start, $end)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ $return = array();
+ foreach ($time_categories as $category) {
+ $drv = TimeObjects_Driver::factory($category);
+
+ try {
+ $new = $drv->listTimeObjects($start, $end);
+ } catch (TimeObjects_Exception $e) {
+ //@TODO: Log the error, but return an empty array.
+ $new = array();
+ }
+ $return = array_merge($return, $new);
+ }
+
+ return $return;
+ }
+
+}
+++ /dev/null
-<?php
-/**
- * API methods for exposing various bits of data via the listTimeObjects API
- */
-$_services['listTimeObjectCategories'] = array(
- 'type' => '{urn:horde}stringArray'
-);
-
-$_services['listTimeObjects'] = array(
- 'args' => array('categories' => '{urn:horde}stringArray', 'start' => 'int', 'end' => 'int'),
- 'type' => '{urn:horde}hashHash'
-);
-
-// @TODO: Probably implement a URL endpoint or something so we can link
-// to the correct external site depending on what time object category
-// we are referring to.
-$_services['show'] = array(
- 'link' => '#',
-);
-
-/**
- * Returns the available categories we provide.
- *
- * Right now, only providing weather data.
- *
- * @return array
- */
-function _timeobjects_listTimeObjectCategories()
-{
- require_once dirname(__FILE__) . '/base.php';
-
- // @TODO: Probably want to iterate the driver directory
- // and dynamically build this list and/or maybe provide
- // a $conf[] setting to explicitly disable certain drivers?
- $drivers = array();
-
- $drv = TimeObjects_Driver::factory('Weatherdotcom');
- if ($drv->ensure()) {
- $drivers['Weatherdotcom'] = _("Weather");
- }
-
- $drv = TimeObjects_Driver::factory('FacebookEvents');
- if ($drv->ensure()) {
- $drivers['FacebookEvents'] = _("Facebook Events");
- }
-
- return $drivers;
-}
-
-/**
- * Obtain the timeObjects for the requested category
- *
- * @param array $time_categories An array of categories to list
- * @param mixed $start The start of the time period to list for
- * @param mixed $end The end of the time period to list for
- *
- * @return An array of timeobject arrays.
- */
-function _timeobjects_listTimeObjects($time_categories, $start, $end)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- $return = array();
- foreach ($time_categories as $category) {
- $drv = TimeObjects_Driver::factory($category);
-
- try {
- $new = $drv->listTimeObjects($start, $end);
- } catch (TimeObjects_Exception $e) {
- //@TODO: Log the error, but return an empty array.
- $new = array();
- }
- $return = array_merge($return, $new);
- }
-
- return $return;
-}
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * Turba external API interface.
+ *
+ * This file defines Turba's external API interface. Other applications can
+ * interact with Turba through this API.
+ *
+ * @package Turba
+ */
+class Turba_Api extends Horde_Registry_Api
+{
+ /**
+ * The application's version.
+ *
+ * @var string
+ */
+ public $version = 'H3 (3.0-git)';
+
+ /**
+ * The services provided by this application.
+ * TODO: Describe structure.
+ *
+ * @var array
+ */
+ public $services = array(
+ 'perms' => array(
+ 'args' => array(),
+ 'type' => '{urn:horde}hashHash'
+ ),
+
+ 'removeUserData' => array(
+ 'args' => array('user' => 'string'),
+ 'type' => 'boolean'
+ ),
+
+ 'show' => array(
+ 'link' => '%application%/contact.php?source=|source|&key=|key|&uid=|uid|',
+ ),
+
+ 'browse' => array(
+ 'args' => array(
+ 'path' => 'string',
+ 'properties' => '{urn:horde}stringArray'
+ ),
+ 'type' => '{urn:horde}hashHash',
+ ),
+
+ 'path_delete' => array(
+ 'args' => array('path' => 'string'),
+ 'type' => 'boolean',
+ ),
+
+ 'sources' => array(
+ 'args' => array('writeable' => 'boolean'),
+ 'type' => '{urn:horde}stringArray',
+ ),
+
+ 'fields' => array(
+ 'args' => array('source' => '{urn:horde}stringArray'),
+ 'type' => '{urn:horde}stringArray',
+ ),
+
+ 'list' => array(
+ 'args' => array(),
+ 'type' => '{urn:horde}stringArray',
+ ),
+
+ 'listBy' => array(
+ 'args' => array(
+ 'action' => 'string',
+ 'timestamp' => 'int'
+ ),
+ 'type' => '{urn:horde}stringArray',
+ ),
+
+ 'getActionTimestamp' => array(
+ 'args' => array(
+ 'uid' => 'string',
+ 'action' => 'string',
+ 'sources' => '{urn:horde}stringArray'
+ ),
+ 'type' => 'int',
+ ),
+
+ 'import' => array(
+ 'args' => array(
+ 'content' => 'string',
+ 'contentType' => 'string',
+ 'source' => 'string'
+ ),
+ 'type' => 'string',
+ ),
+
+ 'export' => array(
+ 'args' => array(
+ 'uid' => 'string',
+ 'contentType' => 'string'
+ ),
+ 'type' => 'string',
+ ),
+
+ 'ownVCard' => array(
+ 'args' => array(),
+ 'type' => 'string',
+ ),
+
+ 'ownContact' => array(
+ 'args' => array(),
+ 'type' => '{urn:horde}stringArray',
+ ),
+
+ 'delete' => array(
+ 'args' => array('uid' => 'string'),
+ 'type' => 'boolean',
+ ),
+
+ 'replace' => array(
+ 'args' => array(
+ 'uid' => 'string',
+ 'content' => 'string',
+ 'contentType' => 'string'
+ ),
+ 'type' => 'boolean',
+ ),
+
+ 'search' => array(
+ 'args' => array(
+ 'names' => '{urn:horde}stringArray',
+ 'sources' => '{urn:horde}stringArray',
+ 'fields' => '{urn:horde}stringArray',
+ 'matchBegin' => 'boolean',
+ 'forceSource' => 'boolean'
+ ),
+ 'type' => '{urn:horde}stringArray',
+ ),
+
+ 'getContact' => array(
+ 'args' => array(
+ 'source' => 'string',
+ 'objectId' => 'string'
+ ),
+ 'type' => '{urn:horde}stringArray',
+ ),
+
+ 'getContacts' => array(
+ 'args' => array(
+ 'source' => 'string',
+ 'objectIds' => '{urn:horde}stringArray'
+ ),
+ 'type' => '{urn:horde}stringArray',
+ ),
+
+ 'addField' => array(
+ 'args' => array(
+ 'address' => 'string',
+ 'name' => 'string',
+ 'field' => 'string',
+ 'value' => 'string',
+ 'source' => 'string'
+ ),
+ 'type' => '{urn:horde}stringArray',
+ ),
+
+ 'deleteField' => array(
+ 'args' => array(
+ 'address' => 'string',
+ 'field' => 'string',
+ 'sources' => '{urn:horde}stringArray'
+ ),
+ 'type' => '{urn:horde}stringArray',
+ ),
+
+ 'getField' => array(
+ 'args' => array(
+ 'address' => 'string',
+ 'field' => 'string',
+ 'sources' => '{urn:horde}stringArray',
+ 'strict' => 'boolean',
+ 'multiple' => 'boolean'
+ ),
+ 'type' => '{urn:horde}stringArray',
+ ),
+
+ 'getAllAttributeValues' => array(
+ 'args' => array(
+ 'field' => 'string',
+ 'sources' => '{urn:horde}stringArray'
+ ),
+ 'type' => '{urn:horde}stringArray',
+ ),
+
+ 'listTimeObjectCategories' => array(
+ 'type' => '{urn:horde}stringArray'
+ ),
+
+ 'listTimeObjects' => array(
+ 'args' => array(
+ 'categories' => '{urn:horde}stringArray',
+ 'start' => 'int',
+ 'end' => 'int'
+ ),
+ 'type' => '{urn:horde}hashHash'
+ ),
+
+ 'getClientSource' => array(
+ 'checkperms' => false,
+ 'args' => array(),
+ 'type' => 'string',
+ ),
+
+ 'clientFields' => array(
+ 'args' => array(),
+ 'type' => '{urn:horde}stringArray',
+ ),
+
+ 'getClient' => array(
+ 'checkperms' => false,
+ 'args' => array('objectId' => 'string'),
+ 'type' => '{urn:horde}stringArray',
+ ),
+
+ 'getClients' => array(
+ 'checkperms' => false,
+ 'args' => array('objectIds' => '{urn:horde}stringArray'),
+ 'type' => '{urn:horde}stringArray',
+ ),
+
+ 'addClient' => array(
+ 'args' => array('attributes' => '{urn:horde}stringArray'),
+ 'type' => 'string',
+ ),
+
+ 'updateClient' => array(
+ 'args' => array(
+ 'objectId' => 'string',
+ 'attributes' => '{urn:horde}stringArray'
+ ),
+ 'type' => 'string',
+ ),
+
+ 'deleteClient' => array(
+ 'args' => array('objectId' => 'string'),
+ 'type' => '{urn:horde}stringArray',
+ ),
+
+ 'searchClients' => array(
+ 'checkperms' => false,
+ 'args' => array('names' => '{urn:horde}stringArray',
+ 'fields' => '{urn:horde}stringArray',
+ 'matchBegin' => 'boolean'),
+ 'type' => '{urn:horde}stringArray',
+ ),
+
+ 'commentCallback' => array(
+ 'args' => array('id' => 'string'),
+ 'type' => 'string'
+ ),
+
+ 'hasComments' => array(
+ 'args' => array(),
+ 'type' => 'boolean'
+ ),
+
+ 'getDefaultShare' => array(
+ 'args' => array(),
+ 'type' => 'string'
+ )
+ );
+
+ /**
+ * Removes user data.
+ *
+ * @param string $user Name of user to remove data for.
+ *
+ * @return mixed true on success | PEAR_Error on failure
+ */
+ public function removeUserData($user)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ if (!Horde_Auth::isAdmin() && $user != Horde_Auth::getAuth()) {
+ return PEAR::raiseError(_("You are not allowed to remove user data."));
+ }
+
+ /* We need a clean copy of the $cfgSources array here.*/
+ require TURBA_BASE . '/config/sources.php';
+ $hasError = false;
+
+ foreach ($cfgSources as $source) {
+ if (empty($source['use_shares'])) {
+ // Shares not enabled for this source
+ $driver = Turba_Driver::singleton($source);
+ if (is_a($driver, 'PEAR_Error')) {
+ Horde::logMessage($driver, __FILE__, __LINE__, PEAR_LOG_ERR);
+ $hasError = true;
+ } else {
+ $result = $driver->removeUserData($user);
+ if (is_a($result, 'PEAR_Error')) {
+ Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+ }
+ }
+ }
+ }
+
+ /* Only attempt share removal if we have shares configured */
+ if (!empty($_SESSION['turba']['has_share'])) {
+ $shares = &$GLOBALS['turba_shares']->listShares(
+ $user, PERMS_EDIT, $user);
+
+ /* Look for the deleted user's default share and remove it */
+ foreach ($shares as $share) {
+ $params = @unserialize($share->get('params'));
+ /* Only attempt to delete the user's default share */
+ if (!empty($params['default'])) {
+ $config = Turba::getSourceFromShare($share);
+ $driver = Turba_Driver::singleton($config);
+ $result = $driver->removeUserData($user);
+ if (is_a($result, 'PEAR_Error')) {
+ Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+ $hasError = true;
+ }
+ }
+ }
+
+ /* Get a list of all shares this user has perms to and remove the perms */
+ $shares = $GLOBALS['turba_shares']->listShares($user);
+ if (is_a($shares, 'PEAR_Error')) {
+ Horde::logMessage($shares, __FILE__, __LINE__, PEAR_LOG_ERR);
+ }
+ foreach ($shares as $share) {
+ $share->removeUser($user);
+ }
+
+ }
+
+ if ($hasError) {
+ return PEAR::raiseError(sprintf(_("There was an error removing an address book for %s"), $user));
+ }
+
+ return true;
+ }
+
+ /**
+ * Callback for comment API
+ *
+ * @param integer $id Internal data identifier
+ *
+ * @return mixed Name of object on success | false on failure
+ */
+ public function commentCallback($id)
+ {
+ if (!$GLOBALS['conf']['comments']['allow']) {
+ return false;
+ }
+
+ require_once dirname(__FILE__) . '/base.php';
+ global $cfgSources;
+
+ @list($source, $key) = explode('.', $id, 2);
+ if (isset($cfgSources[$source]) && $key) {
+ $driver = Turba_Driver::singleton($source);
+ if (!is_a($driver, 'PEAR_Error')) {
+ $object = $driver->getObject($key);
+ if (!is_a($object, 'PEAR_Error')) {
+ return $object->getValue('name');
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns if applications allows comments
+ *
+ * @return boolean
+ */
+ public function hasComments()
+ {
+ return $GLOBALS['conf']['comments']['allow'];
+ }
+
+ /**
+ * Returns a list of available permissions.
+ *
+ * @return array An array describing all available permissions.
+ */
+ public function perms()
+ {
+ static $perms = array();
+ if (!empty($perms)) {
+ return $perms;
+ }
+
+ require_once dirname(__FILE__) . '/base.php';
+ require TURBA_BASE . '/config/sources.php';
+
+ $perms['tree']['turba']['sources'] = false;
+ $perms['title']['turba:sources'] = _("Sources");
+
+ // Run through every contact source.
+ foreach ($cfgSources as $source => $curSource) {
+ $perms['tree']['turba']['sources'][$source] = false;
+ $perms['title']['turba:sources:' . $source] = $curSource['title'];
+ $perms['tree']['turba']['sources'][$source]['max_contacts'] = false;
+ $perms['title']['turba:sources:' . $source . ':max_contacts'] = _("Maximum Number of Contacts");
+ $perms['type']['turba:sources:' . $source . ':max_contacts'] = 'int';
+ }
+
+ return $perms;
+ }
+
+ /**
+ * Returns a list of available sources.
+ *
+ * @param boolean $writeable Set to true to limit to writeable sources.
+ *
+ * @return array An array of the available sources.
+ */
+ public function sources($writeable = false)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ $addressbooks = Turba::getAddressBooks($writeable ? PERMS_EDIT : PERMS_READ);
+ foreach ($addressbooks as $addressbook => $config) {
+ $addressbooks[$addressbook] = $config['title'];
+ }
+
+ return $addressbooks;
+ }
+
+ /**
+ * Returns a list of fields avaiable in a source.
+ *
+ * @param string $source The name of the source
+ *
+ * @return mixed An array describing the fields | PEAR_Error
+ */
+ public function fields($source = null)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+ global $cfgSources, $attributes;
+
+ if (empty($source) || !isset($cfgSources[$source])) {
+ return PEAR::raiseError(sprintf(_("Invalid address book: %s"), $source), 'horde.error', null, null, $source);
+ }
+
+ $fields = array();
+ foreach ($cfgSources[$source]['map'] as $field_name => $null) {
+ if (substr($field_name, 0, 2) != '__') {
+ $fields[$field_name] = array('name' => $field_name,
+ 'type' => $attributes[$field_name]['type'],
+ 'label' => $attributes[$field_name]['label'],
+ 'search' => in_array($field_name, $cfgSources[$source]['search']));
+ }
+ }
+
+ return $fields;
+ }
+
+ /**
+ * Retrieve the UID for the current user's default Turba share.
+ */
+ public function getDefaultShare()
+ {
+ global $prefs;
+
+ // Bring in turba's base and a clean copy of sources.
+ require_once dirname(__FILE__) . '/base.php';
+ require TURBA_BASE . '/config/sources.php';
+
+ if (!empty($_SESSION['turba']['has_share'])) {
+ $shares = Turba::listShares(true);
+ if (is_a($shares, 'PEAR_Error')) {
+ return false;
+ }
+ foreach ($shares as $uid => $share) {
+ $params = @unserialize($share->get('params'));
+ if (empty($params['source'])) {
+ continue;
+ }
+ $driver = Turba_Driver::factory($params['source'], $cfgSources[$params['source']]);
+ if (is_a($driver, 'PEAR_Error')) {
+ continue;
+ }
+ if ($driver->checkDefaultShare($share, $cfgSources[$params['source']])) {
+ return $uid;
+ }
+ }
+ }
+
+ // Return Turba's default_dir as default
+ return $prefs->getValue('default_dir');
+ }
+
+ /**
+ * Browses through Turba's object tree.
+ *
+ * @param string $path The path of the tree to browse.
+ * @param array $properties The item properties to return. Defaults to 'name',
+ * 'icon', and 'browseable'.
+ *
+ * @return array Content of the specified path.
+ */
+ public function browse($path = '', $properties = array())
+ {
+ function _modified($uid)
+ {
+ $modified = $this->getActionTimestamp($uid, 'modify');
+ if (empty($modified)) {
+ $modified = $this->getActionTimestamp($uid, 'add');
+ }
+ return $modified;
+ }
+
+ require_once dirname(__FILE__) . '/base.php';
+ global $registry, $cfgSources;
+
+ // Default properties.
+ if (!$properties) {
+ $properties = array('name', 'icon', 'browseable');
+ }
+
+ // Strip off the application name if present
+ if (substr($path, 0, 5) == 'turba') {
+ $path = substr($path, 5);
+ }
+ $path = trim($path, '/');
+ $parts = explode('/', $path);
+
+ $now = time();
+ $results = array();
+ if (empty($path)) {
+ // We always provide the "global" folder which contains address book
+ // sources that are shared among all users. Per-user shares are shown
+ // in a folder for each respective user.
+ $results = array();
+ $shares = Turba::listShares();
+ $owners = array('global' => true);
+ foreach ($shares as $share) {
+ $owners[$share->get('owner')] = true;
+ }
+
+ foreach (array_keys($owners) as $owner) {
+ if (in_array('name', $properties)) {
+ $results['turba/' . $owner]['name'] = $owner;
+ }
+ if (in_array('icon', $properties)) {
+ $results['turba/' . $owner]['icon'] = $registry->getImageDir() . '/turba.png';
+ }
+ if (in_array('browseable', $properties)) {
+ $results['turba/' . $owner]['browseable'] = true;
+ }
+ if (in_array('contenttype', $properties)) {
+ $results['turba/' . $owner]['contenttype'] = 'httpd/unix-directory';
+ }
+ if (in_array('contentlength', $properties)) {
+ $results['turba/' . $owner]['contentlength'] = 0;
+ }
+ if (in_array('modified', $properties)) {
+ // @TODO: Get a real modification date
+ $results['turba/' . $owner]['modified'] = $now;
+ }
+ if (in_array('created', $properties)) {
+ // @TODO Get a real creation date
+ $results['turba/' . $owner]['created'] = 0;
+ }
+ }
+ return $results;
+ } elseif (count($parts) == 1) {
+ //
+ // We should either have the username that is a valid share owner or
+ // 'global'
+ //
+ if (empty($parts[0])) {
+ // We need either 'global' or a valid username with shares
+ return array();
+ }
+
+ if ($parts[0] == 'global') {
+ // The client is requesting a list of global address books.
+ $addressbooks = Turba::getAddressBooks();
+ foreach ($addressbooks as $addressbook => $info) {
+ if ($info['type'] == 'share') {
+ // Ignore address book shares in the 'global' folder
+ unset($addressbooks[$addressbook]);
+ }
+ }
+ } else {
+ // Assume $parts[0] is a valid username and we need to list their
+ // shared addressbooks.
+ if (empty($_SESSION['turba']['has_share'])) {
+ // No backends are configured to provide shares
+ return array();
+ }
+ $addressbooks = $GLOBALS['turba_shares']->listShares($parts[0],
+ PERMS_READ,
+ $parts[0]);
+ // The last check returns all addressbooks for the requested user,
+ // but that does not mean the requesting user has access to them.
+ // Filter out those address books for which the requesting user has
+ // no access.
+ $addressbooks = Turba::permissionsFilter($addressbooks);
+ }
+
+ $curpath = 'turba/' . $parts[0] . '/';
+ foreach ($addressbooks as $addressbook => $info) {
+ if (in_array('name', $properties)) {
+ if (is_a($info, 'Horde_Share_Object')) {
+ $name = $info->get('title');
+ } else {
+ $name = $info['title'];
+ }
+ $results[$curpath . $addressbook]['name'] = $name;
+ }
+ if (in_array('icon', $properties)) {
+ $results[$curpath . $addressbook]['icon'] = $registry->getImageDir() . '/turba.png';
+ }
+ if (in_array('browseable', $properties)) {
+ $results[$curpath . $addressbook]['browseable'] = true;
+ }
+ if (in_array('contenttype', $properties)) {
+ $results[$curpath . $addressbook]['contenttype'] = 'httpd/unix-directory';
+ }
+ if (in_array('contentlength', $properties)) {
+ $results[$curpath . $addressbook]['contentlength'] = 0;
+ }
+ if (in_array('modified', $properties)) {
+ // @TODO: Get a real modification date
+ $results[$curpath . $addressbook]['modified'] = $now;
+ }
+ if (in_array('created', $properties)) {
+ // @TODO Get a real creation date
+ $results[$curpath . $addressbook]['created'] = 0;
+ }
+ }
+ return $results;
+
+ } elseif (count($parts) == 2) {
+ //
+ // The client is requesting all contacts from a given addressbook
+ //
+ if (empty($parts[0]) || empty($parts[1])) {
+ // $parts[0] must be either 'global' or a valid user with shares
+ // $parts[1] must be an address book ID
+ return array();
+ }
+
+ $addressbooks = Turba::getAddressBooks();
+ if (!isset($addressbooks[$parts[1]])) {
+ // We must have a valid addressbook to continue.
+ return array();
+ }
+
+ // Load the Turba driver.
+ $driver = Turba_Driver::singleton($parts[1]);
+ if (is_a($driver, 'PEAR_Error')) {
+ return PEAR::raiseError(sprintf(_("Connection failed: %s"), $driver->getMessage()), 'horde.error', null, null, $cfgSources[$parts[1]]);
+ }
+
+ $contacts = $driver->search(array());
+ if (is_a($contacts, 'PEAR_Error')) {
+ return $contacts;
+ }
+
+ $contacts->reset();
+ $curpath = 'turba/' . $parts[0] . '/' . $parts[1] . '/';
+ while ($contact = $contacts->next()) {
+ $key = $curpath . $contact->getValue('__key');
+ if (in_array('name', $properties)) {
+ $results[$key]['name'] = Turba::formatName($contact);
+ }
+ if (in_array('icon', $properties)) {
+ $results[$key]['icon'] = $registry->getImageDir('horde') . '/mime/vcard.png';
+ }
+ if (in_array('browseable', $properties)) {
+ $results[$key]['browseable'] = false;
+ }
+ if (in_array('contenttype', $properties)) {
+ $results[$key]['contenttype'] = 'text/x-vcard';
+ }
+ if (in_array('contentlength', $properties)) {
+ $data = $this->export($contact->getValue('__uid'), 'text/x-vcard', $contact->getSource());
+ if (is_a($data, 'PEAR_Error')) {
+ $data = '';
+ }
+ $results[$key]['contentlength'] = strlen($data);
+ }
+ if (in_array('modified', $properties)) {
+ $results[$key]['modified'] = _modified($contact->getValue('__uid'));
+ }
+ if (in_array('created', $properties)) {
+ $results[$key]['created'] = $this->getActionTimestamp($contact->getValue('__uid'), 'add');
+ }
+ }
+
+ return $results;
+
+ } elseif (count($parts) == 3) {
+ //
+ // The client is requesting an individual contact
+ //
+ $addressbooks = Turba::getAddressBooks();
+ if (!isset($addressbooks[$parts[1]])) {
+ // We must have a valid addressbook to continue.
+ return array();
+ }
+
+ // Load the Turba driver.
+ $driver = Turba_Driver::singleton($parts[1]);
+ if (is_a($driver, 'PEAR_Error')) {
+ return PEAR::raiseError(sprintf(_("Connection failed: %s"), $driver->getMessage()), 'horde.error', null, null, $cfgSources[$parts[1]]);
+ }
+
+ $contact = &$driver->getObject($parts[2]);
+ if (is_a($contact, 'PEAR_Error')) {
+ return $contact;
+ }
+
+ $result = array('data' => $this->export($contact->getValue('__uid'), 'text/x-vcard', $contact->getSource()),
+ 'mimetype' => 'text/x-vcard');
+ $modified = _modified($contact->getValue('__uid'));
+ if (!empty($modified)) {
+ $result['mtime'] = $modified;
+ }
+ return $result;
+ } else {
+ return PEAR::raiseError(_("Malformed request."));
+ }
+ }
+
+ /**
+ * Deletes a file from the Turba tree.
+ *
+ * @param string $path The path to the file.
+ *
+ * @return mixed The event's UID, or a PEAR_Error on failure.
+ */
+ public function path_delete($path)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+ global $registry, $cfgSources;
+
+ // Strip off the application name if present
+ if (substr($path, 0, 5) == 'turba') {
+ $path = substr($path, 5);
+ }
+ $path = trim($path, '/');
+ $parts = explode('/', $path);
+
+ $now = time();
+ $results = array();
+
+ if (count($parts) < 3) {
+ // Deletes must be on individual contacts
+ return PEAR::raiseError(_("Delete denied."), 403);
+ }
+ if (!array_key_exists($parts[1], Turba::getAddressBooks())) {
+ return PEAR::raiseError("Address book does not exist", 404);
+ }
+
+ // Load the Turba driver.
+ $driver = Turba_Driver::singleton($parts[1]);
+ if (is_a($driver, 'PEAR_Error')) {
+ return PEAR::raiseError(sprintf(_("Connection failed: %s"), $driver->getMessage()), 500, null, null, $cfgSources[$parts]);
+ }
+
+ $ret = $driver->delete($parts[2]);
+ if (is_a($ret, 'PEAR_Error')) {
+ // A deeper error occurred. Make sure the code is a valid HTTP response
+ $ret->code = 500;
+ return $ret;
+ }
+ }
+
+ /**
+ * Returns an array of UIDs for all contacts that the current user is
+ * authorized to see.
+ *
+ * @param string|array $sources The name(s) of the source(s) to return
+ * contacts of. If left empty, the current user's
+ * sync sources or default source are used.
+ *
+ * @return array An array of UIDs for all contacts the user can access.
+ */
+ public function listContacts($sources = null)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+
+ global $cfgSources, $prefs;
+
+ /* Get default address book from user preferences. */
+ if (empty($sources)) {
+ $sources = @unserialize($prefs->getValue('sync_books'));
+ } elseif (!is_array($sources)) {
+ $sources = array($sources);
+ }
+ if (empty($sources)) {
+ $sources = array(Turba::getDefaultAddressbook());
+ }
+ if (empty($sources)) {
+ return PEAR::raiseError(_("No address book specified"), 'horde.error');
+ }
+
+ $uids = array();
+ foreach ($sources as $source) {
+ if (empty($source) || !isset($cfgSources[$source])) {
+ return PEAR::raiseError(sprintf(_("Invalid address book: %s"), $source), 'horde.error', null, null, $source);
+ }
+
+ $storage = Turba_Driver::singleton($source);
+ if (is_a($storage, 'PEAR_Error')) {
+ return PEAR::raiseError(sprintf(_("Connection failed: %s"), $storage->getMessage()), 'horde.error', null, null, $source);
+ }
+
+ $results = $storage->search(array());
+
+ if (is_a($results, 'PEAR_Error')) {
+ return PEAR::raiseError(sprintf(_("Error searching the address book: %s"), $results->getMessage()), 'horde.error', null, null, $source);
+ }
+
+ foreach ($results->objects as $o) {
+ $uids[] = $o->getValue('__uid');
+ }
+ }
+
+ return $uids;
+ }
+
+ /**
+ * Returns an array of UIDs for contacts that have had $action happen since
+ * $timestamp.
+ *
+ * @param string $action The action to check for - add, modify, or
+ * delete.
+ * @param integer $timestamp The time to start the search.
+ * @param string|array $sources The source(s) for which to retrieve the
+ * history.
+ *
+ * @return array An array of UIDs matching the action and time criteria.
+ */
+ public function listBy($action, $timestamp, $sources = null)
+ {
+ global $prefs, $cfgSources;
+ require_once dirname(__FILE__) . '/base.php';
+
+ /* Get default address book from user preferences. */
+ if (empty($sources)) {
+ $sources = @unserialize($prefs->getValue('sync_books'));
+ } elseif (!is_array($sources)) {
+ $sources = array($sources);
+ }
+ if (empty($sources)) {
+ $sources = array(Turba::getDefaultAddressbook());
+ }
+ if (empty($sources)) {
+ return PEAR::raiseError(_("No address book specified"), 'horde.error');
+ }
+
+ $uids = array();
+ foreach ($sources as $source) {
+ if (empty($source) || !isset($cfgSources[$source])) {
+ return PEAR::raiseError(sprintf(_("Invalid address book: %s"), $source), 'horde.error', null, null, $source);
+ }
+
+ $driver = Turba_Driver::singleton($source);
+ if (is_a($driver, 'PEAR_Error')) {
+ return PEAR::raiseError(sprintf(_("Connection failed: %s"), $driver->getMessage()), 'horde.error', null, null, $source);
+ }
+
+ $history = Horde_History::singleton();
+ $histories = $history->getByTimestamp('>', $timestamp,
+ array(array('op' => '=',
+ 'field' => 'action',
+ 'value' => $action)),
+ 'turba:' . $driver->getName());
+ if (is_a($histories, 'PEAR_Error')) {
+ return $histories;
+ }
+
+ // Strip leading turba:addressbook:.
+ $uids = array_merge($uids,
+ str_replace('turba:' . $driver->getName() . ':',
+ '',
+ array_keys($histories)));
+ }
+
+ return $uids;
+ }
+
+ /**
+ * Returns the timestamp of an operation for a given uid an action.
+ *
+ * @param string $uid The uid to look for.
+ * @param string $action The action to check for - add, modify, or
+ * delete.
+ * @param string|array $sources The source(s) for which to retrieve the
+ * history.
+ *
+ * @return integer The timestamp for this action.
+ */
+ public function getActionTimestamp($uid, $action, $sources = null)
+ {
+ global $prefs, $cfgSources;
+ require_once dirname(__FILE__) . '/base.php';
+
+ /* Get default address book from user preferences. */
+ if (empty($sources)) {
+ $sources = @unserialize($prefs->getValue('sync_books'));
+ } elseif (!is_array($sources)) {
+ $sources = array($sources);
+ }
+ if (empty($sources)) {
+ $sources = array(Turba::getDefaultAddressbook());
+ }
+ if (empty($sources)) {
+ return PEAR::raiseError(_("No address book specified"), 'horde.error');
+ }
+
+ foreach ($sources as $source) {
+ if (empty($source) || !isset($cfgSources[$source])) {
+ return PEAR::raiseError(sprintf(_("Invalid address book: %s"), $source), 'horde.error', null, null, $source);
+ }
+
+ $driver = Turba_Driver::singleton($source);
+ if (is_a($driver, 'PEAR_Error')) {
+ return PEAR::raiseError(sprintf(_("Connection failed: %s"), $driver->getMessage()), 'horde.error', null, null, $source);
+ }
+
+ $history = Horde_History::singleton();
+ $ts = $history->getActionTimestamp('turba:' . $driver->getName()
+ . ':' . $uid,
+ $action);
+ if (!empty($ts)) {
+ return $ts;
+ }
+ }
+
+ return 0;
+ }
+
+ /**
+ * Import a contact represented in the specified contentType.
+ *
+ * @param string $content The content of the contact.
+ * @param string $contentType What format is the data in? Currently supports
+ * array, text/directory, text/vcard and
+ * text/x-vcard.
+ * @param string $source The source into which the contact will be
+ * imported.
+ *
+ * @return string The new UID, or false on failure.
+ */
+ public function import($content, $contentType = 'array',
+ $import_source = null)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+ global $cfgSources, $prefs;
+
+ /* Get default address book from user preferences. */
+ if (empty($import_source)) {
+ $import_source = $prefs->getValue('default_dir');
+ /* On new installations default_dir is not set, use first source
+ * instead. */
+ if (empty($import_source)) {
+ $import_source = key(Turba::getAddressBooks(PERMS_EDIT));
+ }
+ }
+
+ // Check existance of and permissions on the specified source.
+ if (!isset($cfgSources[$import_source])) {
+ return PEAR::raiseError(sprintf(_("Invalid address book: %s"),
+ $import_source),
+ 'horde.warning');
+ }
+
+ $driver = Turba_Driver::singleton($import_source);
+ if (is_a($driver, 'PEAR_Error')) {
+ return PEAR::raiseError(sprintf(_("Connection failed: %s"), $driver->getMessage()), 'horde.error', null, null, $import_source);
+ }
+
+ if (!$driver->hasPermission(PERMS_EDIT)) {
+ return PEAR::raiseError(_("Permission denied"), 'horde.error', null, null, $import_source);
+ }
+
+ /* Create a category manager. */
+ require_once 'Horde/Prefs/CategoryManager.php';
+ $cManager = new Prefs_CategoryManager();
+ $categories = $cManager->get();
+
+ if (!is_a($content, 'Horde_iCalendar_vcard')) {
+ switch ($contentType) {
+ case 'array':
+ break;
+
+ case 'text/x-vcard':
+ case 'text/vcard':
+ case 'text/directory':
+ $iCal = new Horde_iCalendar();
+ if (!$iCal->parsevCalendar($content)) {
+ return PEAR::raiseError(_("There was an error importing the iCalendar data."));
+ }
+ switch ($iCal->getComponentCount()) {
+ case 0:
+ return PEAR::raiseError(_("No vCard data was found."));
+
+ case 1:
+ $content = $iCal->getComponent(0);
+ break;
+
+ default:
+ $ids = array();
+ foreach ($iCal->getComponents() as $c) {
+ if (is_a($c, 'Horde_iCalendar_vcard')) {
+ $content = $driver->toHash($c);
+ $result = $driver->search($content);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ } elseif ($result->count() > 0) {
+ continue;
+ }
+ $result = $driver->add($content);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ if (!empty($content['category']) &&
+ !in_array($content['category'], $categories)) {
+ $cManager->add($content['category']);
+ $categories[] = $content['category'];
+ }
+ $ids[] = $result;
+ }
+ }
+ return $ids;
+ }
+ break;
+
+ default:
+ return PEAR::raiseError(sprintf(_("Unsupported Content-Type: %s"), $contentType));
+ }
+ }
+
+ if (is_a($content, 'Horde_iCalendar_vcard')) {
+ $content = $driver->toHash($content);
+ }
+
+ // Check if the entry already exists in the data source:
+ $result = $driver->search($content);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ } elseif ($result->count() > 0) {
+ $o = $result->objects[0];
+ return PEAR::raiseError(_("Already Exists"), 'horde.message', null, null, $o->getValue('__uid'));
+ }
+
+ $result = $driver->add($content);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ if (!empty($content['category']) &&
+ !in_array($content['category'], $categories)) {
+ $cManager->add($content['category']);
+ }
+
+ $object = &$driver->getObject($result);
+ return is_a($object, 'PEAR_Error') ? $object : $object->getValue('__uid');
+ }
+
+ /**
+ * Export a contact, identified by UID, in the requested contentType.
+ *
+ * @param string $uid Identify the contact to export.
+ * @param mixed $contentType What format should the data be in?
+ * Either a string with one of:
+ * - text/directory
+ * - text/vcard
+ * - text/x-vcard
+ * The first two produce a vcard3.0 (rfc2426),
+ * the second produces a vcard in old 2.1 format
+ * defined by imc.org
+ * @param string|array $sources The source(s) from which the contact will be
+ * exported.
+ *
+ * @return mixed The requested data | PEAR_Error
+ */
+ public function export($uid, $contentType, $sources = null)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+ global $cfgSources, $prefs;
+
+ /* Get default address book from user preferences. */
+ if (empty($sources)) {
+ $sources = @unserialize($prefs->getValue('sync_books'));
+ } elseif (!is_array($sources)) {
+ $sources = array($sources);
+ }
+ if (empty($sources)) {
+ $sources = array(Turba::getDefaultAddressbook());
+ }
+ if (empty($sources)) {
+ return PEAR::raiseError(_("No address book specified"), 'horde.error');
+ }
+
+ foreach ($sources as $source) {
+ if (empty($source) || !isset($cfgSources[$source])) {
+ return PEAR::raiseError(sprintf(_("Invalid address book: %s"), $source), 'horde.error', null, null, $source);
+ }
+
+ if (empty($uid)) {
+ return PEAR::raiseError(_("Invalid ID"), 'horde.error', null, null, $source);
+ }
+
+ $driver = Turba_Driver::singleton($source);
+ if (is_a($driver, 'PEAR_Error')) {
+ return PEAR::raiseError(sprintf(_("Connection failed: %s"), $driver->getMessage()), 'horde.error', null, null, $source);
+ }
+
+ if (!$driver->hasPermission(PERMS_READ)) {
+ continue;
+ }
+
+ $result = $driver->search(array('__uid' => $uid));
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ } elseif ($result->count() == 0) {
+ continue;
+ } elseif ($result->count() > 1) {
+ return PEAR::raiseError("Internal Horde Error: multiple turba objects with same objectId.", 'horde.error', null, null, $source);
+ }
+
+ $version = '3.0';
+ list($contentType,) = explode(';', $contentType);
+ switch ($contentType) {
+ case 'text/x-vcard':
+ $version = '2.1';
+
+ case 'text/vcard':
+ case 'text/directory':
+ $export = '';
+ foreach ($result->objects as $obj) {
+ $vcard = $driver->tovCard($obj, $version);
+ /* vCards are not enclosed in BEGIN:VCALENDAR..END:VCALENDAR.
+ * Export the individual cards instead. */
+ $export .= $vcard->exportvCalendar();
+ }
+ return $export;
+ }
+
+ return PEAR::raiseError(sprintf(_("Unsupported Content-Type: %s"), $contentType));
+ }
+
+ return PEAR::raiseError(_("Object not found"));
+ }
+
+ /**
+ * Exports the user's own contact as a vCard string.
+ *
+ * @return string The requested vCard data or PEAR_Error.
+ */
+ public function ownVCard()
+ {
+ $contact = $this->getOwnContactObject();
+ if (is_a($contact, 'PEAR_Error')) {
+ return $contact;
+ }
+ $driver = Turba_Driver::singleton($contact['source']);
+ if (is_a($driver, 'PEAR_Error')) {
+ return PEAR::raiseError(sprintf(_("Connection failed: %s"), $driver->getMessage()));
+ }
+ $vcard = $driver->tovCard($contact['contact'], '3.0');
+ $vcard->setAttribute('VERSION', '3.0');
+
+ return $vcard->exportvCalendar();
+ }
+
+ /**
+ * Export the user's own contact as a hash
+ *
+ * @return Array The contact hash or PEAR_Error
+ */
+ public function ownContact()
+ {
+ $contact = $this->getOwnContactObject();
+ if (is_a($contact, 'PEAR_Error')) {
+ return $contact;
+ }
+
+ return $contact['contact']->getAttributes();
+ }
+
+ /**
+ * Helper function to return the user's own contact object
+ *
+ * @return Array A hash containing the Turba_Object representing the user's
+ * own contact and the source that it is from or PEAR_Error.
+ */
+ public function getOwnContactObject()
+ {
+ require_once dirname(__FILE__) . '/base.php';
+ global $cfgSources;
+
+ $own_contact = $GLOBALS['prefs']->getValue('own_contact');
+ if (empty($own_contact)) {
+ return PEAR::raiseError(_("You didn't mark a contact as your own yet."));
+ }
+ @list($source, $id) = explode(';', $own_contact);
+
+ if (!isset($cfgSources[$source])) {
+ return PEAR::raiseError(_("The address book with your own contact doesn't exist anymore."));
+ }
+
+ $driver = Turba_Driver::singleton($source);
+ if (is_a($driver, 'PEAR_Error')) {
+ return PEAR::raiseError(sprintf(_("Connection failed: %s"), $driver->getMessage()));
+ }
+
+ if (!$driver->hasPermission(PERMS_READ)) {
+ return PEAR::raiseError(_("You don't have sufficient permissions to read the address book that contains your own contact."));
+ }
+
+ $contact = $driver->getObject($id);
+ if (is_a($contact, 'PEAR_Error')) {
+ return PEAR::raiseError(_("Your own contact cannot be found in the address book."));
+ }
+
+ $return = array('contact' => $contact,
+ 'source'=> $source);
+
+ return $return;
+ }
+
+ /**
+ * Deletes a contact identified by UID.
+ *
+ * @param string|array $uid Identify the contact to delete, either a
+ * single UID or an array.
+ * @param string|array $sources The source(s) from which the contact will be
+ * deleted.
+ *
+ * @return boolean Success or failure.
+ */
+ public function delete($uid, $sources = null)
+ {
+ // Handle an array of UIDs for convenience of deleting multiple contacts
+ // at once.
+ if (is_array($uid)) {
+ foreach ($uid as $g) {
+ $result = $this->delete($uid, $source);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ }
+
+ return true;
+ }
+
+ require_once dirname(__FILE__) . '/base.php';
+ global $cfgSources, $prefs;
+
+ /* Get default address book from user preferences. */
+ if (empty($sources)) {
+ $sources = @unserialize($prefs->getValue('sync_books'));
+ } elseif (!is_array($sources)) {
+ $sources = array($sources);
+ }
+ if (empty($sources)) {
+ $sources = array(Turba::getDefaultAddressbook());
+ }
+ if (empty($sources)) {
+ return PEAR::raiseError(_("No address book specified"), 'horde.error');
+ }
+
+ foreach ($sources as $source) {
+ if (empty($source) || !isset($cfgSources[$source])) {
+ return PEAR::raiseError(sprintf(_("Invalid address book: %s"), $source), 'horde.error', null, null, $source);
+ }
+
+ if (empty($uid)) {
+ return PEAR::raiseError(_("Invalid ID"), 'horde.error', null, null, $source);
+ }
+
+ $driver = Turba_Driver::singleton($source);
+ if (is_a($driver, 'PEAR_Error')) {
+ return PEAR::raiseError(sprintf(_("Connection failed: %s"), $driver->getMessage()), 'horde.error', null, null, $source);
+ }
+
+ if (!Horde_Auth::isAdmin() && !$driver->hasPermission(PERMS_DELETE)) {
+ continue;
+ }
+
+ // If the objectId isn't in $source in the first place, just return
+ // true. Otherwise, try to delete it and return success or failure.
+ $result = $driver->search(array('__uid' => $uid));
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ } elseif ($result->count() == 0) {
+ continue;
+ } else {
+ $r = $result->objects[0];
+ return $driver->delete($r->getValue('__key'));
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Replaces the contact identified by UID with the content represented in the
+ * specified contentType.
+ *
+ * @param string $uid Idenfity the contact to replace.
+ * @param string $content The content of the contact.
+ * @param string $contentType What format is the data in? Currently supports
+ * array, text/directory, text/vcard and
+ * text/x-vcard.
+ * @param string|array $sources The source(s) where the contact will be
+ * replaced.
+ *
+ * @return boolean Success or failure.
+ */
+ public function replace($uid, $content, $contentType, $sources = null)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+ global $cfgSources, $prefs;
+
+ /* Get default address book from user preferences. */
+ if (empty($sources)) {
+ $sources = @unserialize($prefs->getValue('sync_books'));
+ } elseif (!is_array($sources)) {
+ $sources = array($sources);
+ }
+ if (empty($sources)) {
+ $sources = array(Turba::getDefaultAddressbook());
+ }
+ if (empty($sources)) {
+ return PEAR::raiseError(_("No address book specified"), 'horde.error');
+ }
+
+ foreach ($sources as $source) {
+ if (empty($source) || !isset($cfgSources[$source])) {
+ return PEAR::raiseError(sprintf(_("Invalid address book: %s"), $source), 'horde.error', null, null, $source);
+ }
+
+ if (empty($uid)) {
+ return PEAR::raiseError(_("Invalid contact unique ID"), 'horde.error', null, null, $source);
+ }
+
+ // Check permissions.
+ $driver = Turba_Driver::singleton($source);
+ if (is_a($driver, 'PEAR_Error')) {
+ return PEAR::raiseError(sprintf(_("Connection failed: %s"), $driver->getMessage()), 'horde.error', null, null, $source);
+ }
+ if (!$driver->hasPermission(PERMS_EDIT)) {
+ continue;
+ }
+ $result = $driver->search(array('__uid' => $uid));
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ } elseif (!$result->count()) {
+ continue;
+ } elseif ($result->count() > 1) {
+ return PEAR::raiseError("Multiple contacts found with same unique ID.", 'horde.error', null, null, $source);
+ }
+
+ $object = $result->objects[0];
+
+ switch ($contentType) {
+ case 'array':
+ break;
+
+ case 'text/x-vcard':
+ case 'text/vcard':
+ case 'text/directory':
+ $iCal = new Horde_iCalendar();
+ if (!$iCal->parsevCalendar($content)) {
+ return PEAR::raiseError(_("There was an error importing the iCalendar data."));
+ }
+
+ switch ($iCal->getComponentCount()) {
+ case 0:
+ return PEAR::raiseError(_("No vCard data was found."));
+
+ case 1:
+ $content = $iCal->getComponent(0);
+ $content = $driver->toHash($content);
+ break;
+
+ default:
+ return PEAR::raiseError(_("Only one vcard supported."));
+ }
+ break;
+
+ default:
+ return PEAR::raiseError(sprintf(_("Unsupported Content-Type: %s"), $contentType));
+ }
+
+ foreach ($content as $attribute => $value) {
+ if ($attribute != '__key') {
+ $object->setValue($attribute, $value);
+ }
+ }
+
+ return $object->store();
+ }
+
+ return PEAR::raiseError(_("Object not found"));
+ }
+
+ /**
+ * Returns a contact search result.
+ *
+ * @param array $names The search filter values
+ * @param array $sources The sources to serach in
+ * @param array $fields The fields to serach on
+ * @param boolean $matchBegin Match word boundaries only
+ * @param boolean $forceSource Whether to use the specified sources, even if
+ * they have been disabled in the preferences.
+ *
+ * @return array Hash containing the search results.
+ */
+ public function search($names = array(), $sources = array(),
+ $fields = array(),
+ $matchBegin = false, $forceSource = false)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+ global $cfgSources, $attributes, $prefs;
+
+ if (!isset($cfgSources) || !is_array($cfgSources) || !count($cfgSources)) {
+ return array();
+ }
+
+ if (!is_array($names)) {
+ $names = is_null($names) ? array() : array($names);
+ }
+
+ if (!$forceSource) {
+ // Make sure the selected source is activated in Turba.
+ $addressbooks = array_keys(Turba::getAddressBooks());
+ foreach (array_keys($sources) as $id) {
+ if (!in_array($sources[$id], $addressbooks)) {
+ unset($sources[$id]);
+ }
+ }
+ }
+
+ // ...and ensure the default source is used as a default.
+ if (!count($sources)) {
+ $sources = array(Turba::getDefaultAddressBook());
+ }
+
+ // Read the columns to display from the preferences.
+ $sort_columns = Turba::getColumns();
+
+ $results = array();
+ $seen = array();
+ foreach ($sources as $source) {
+ // Skip invalid sources.
+ if (!isset($cfgSources[$source])) {
+ continue;
+ }
+
+ // Skip sources that aren't browseable if the search is empty.
+ if (empty($cfgSources[$source]['browse']) &&
+ (!count($names) || (count($names) == 1 && empty($names[0])))) {
+ continue;
+ }
+
+ $driver = Turba_Driver::singleton($source);
+ if (is_a($driver, 'PEAR_Error')) {
+ return PEAR::raiseError(sprintf(_("Connection failed: %s"), $driver->getMessage()), 'horde.error', null, null, $source);
+ }
+
+ // Determine the name of the column to sort by.
+ $columns = isset($sort_columns[$source])
+ ? $sort_columns[$source] : array();
+
+ foreach ($names as $name) {
+ $criteria = array();
+ if (isset($fields[$source])) {
+ foreach ($fields[$source] as $field) {
+ $criteria[$field] = trim($name);
+ }
+ }
+ if (count($criteria) == 0) {
+ $criteria['name'] = trim($name);
+ }
+
+ $search = $driver->search($criteria, Turba::getPreferredSortOrder(), 'OR', array(), array(), $matchBegin);
+ if (!is_a($search, 'Turba_List')) {
+ continue;
+ }
+
+ while ($ob = $search->next()) {
+ if (!$ob->isGroup()) {
+ /* Not a group. */
+ $att = array('__key' => $ob->getValue('__key'));
+ foreach ($ob->driver->getCriteria() as $info_key => $info_val) {
+ $att[$info_key] = $ob->getValue($info_key);
+ }
+ $email = array();
+ foreach (array_keys($att) as $key) {
+ if (!$ob->getValue($key) ||
+ !isset($attributes[$key]) ||
+ $attributes[$key]['type'] != 'email') {
+ continue;
+ }
+ $email_val = $ob->getValue($key);
+
+ // Multiple addresses support
+ if (isset($attributes[$key]['params'])
+ && is_array($attributes[$key]['params'])
+ && !empty($attributes[$key]['params']['allow_multi'])) {
+ $addrs = Horde_Mime_Address::explode($email_val);
+ } else {
+ $addrs = array($email_val);
+ }
+
+ foreach ($addrs as $addr) {
+ $email[] = trim($addr);
+ }
+ }
+
+ if ($ob->hasValue('name') ||
+ !isset($ob->driver->alternativeName)) {
+ $display_name = Turba::formatName($ob);
+ } else {
+ $display_name = $ob->getValue($ob->driver->alternativeName);
+ }
+ if (count($email)) {
+ for ($i = 0; $i < count($email); $i++) {
+ $seen_key = trim(Horde_String::lower($display_name)) . '/' . trim(Horde_String::lower($email[$i]));
+ if (!empty($seen[$seen_key])) {
+ continue;
+ }
+ $seen[$seen_key] = true;
+ if (!isset($results[$name])) {
+ $results[$name] = array();
+ }
+ $results[$name][] = array_merge($att,
+ array('id' => $att['__key'],
+ 'name' => $display_name,
+ 'email' => $email[$i],
+ '__type' => 'Object',
+ 'source' => $source));
+ }
+ } else {
+ if (!isset($results[$name])) {
+ $results[$name] = array();
+ }
+ $results[$name][] = array_merge($att,
+ array('id' => $att['__key'],
+ 'name' => $display_name,
+ 'email' => null,
+ '__type' => 'Object',
+ 'source' => $source));
+ }
+ } else {
+ /* Is a distribution list. */
+ $listatt = $ob->getAttributes();
+ $seeninlist = array();
+ $members = $ob->listMembers();
+ $listName = $ob->getValue('name');
+ if (!is_a($members, 'Turba_List')) {
+ continue;
+ }
+ if ($members->count() > 0) {
+ if (!isset($results[$name])) {
+ $results[$name] = array();
+ }
+ $emails = array();
+ while ($ob = $members->next()) {
+ $att = $ob->getAttributes();
+ foreach (array_keys($att) as $key) {
+ $value = $ob->getValue($key);
+ if (empty($value)) {
+ continue;
+ }
+ if (!is_array($value)) {
+ $seen_key = trim(Horde_String::lower($ob->getValue('name')))
+ . trim(Horde_String::lower($value));
+ } else {
+ $seen_key = trim(Horde_String::lower($ob->getValue('name')))
+ . trim(Horde_String::lower($value['load']['file']));
+ }
+ if (isset($attributes[$key]) &&
+ $attributes[$key]['type'] == 'email' &&
+ empty($seeninlist[$seen_key])) {
+ $emails[] = $value;
+ $seeninlist[$seen_key] = true;
+ }
+ }
+ }
+ $results[$name][] = array('name' => $listName,
+ 'email' => implode(', ', $emails),
+ 'id' => $listatt['__key'],
+ 'source' => $source);
+ }
+ }
+ }
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Retrieves a contact.
+ *
+ * @param string $source The source name where the contact is stored
+ * @param string $objectId The unique id of the contact to retrieve
+ *
+ * @return array The retrieved contact.
+ */
+ public function getContact($source = null, $objectId = '')
+ {
+ require_once dirname(__FILE__) . '/base.php';
+ global $cfgSources;
+
+ if (!isset($cfgSources) || !is_array($cfgSources) || !count($cfgSources)) {
+ return array();
+ }
+
+ if (isset($cfgSources[$source])) {
+ $driver = Turba_Driver::singleton($source);
+ if (is_a($driver, 'PEAR_Error')) {
+ return $driver;
+ }
+
+ $object = $driver->getObject($objectId);
+ if (is_a($object, 'PEAR_Error')) {
+ return $object;
+ }
+
+ $attributes = array();
+ foreach ($cfgSources[$source]['map'] as $field => $map) {
+ $attributes[$field] = $object->getValue($field);
+ }
+ return $attributes;
+ }
+
+ return array();
+ }
+
+ /**
+ * Retrieves a set of contacts from a single source.
+ *
+ * @param string $source The source name where the contact is stored
+ * @param array $objectIds The unique ids of the contact to retrieve.
+ *
+ * @return mixed The retrieved contact | PEAR_Error
+ */
+ public function getContacts($source = '', $objectIds = array())
+ {
+ require_once dirname(__FILE__) . '/base.php';
+ global $cfgSources;
+ $results = array();
+ if (!is_array($objectIds)) {
+ $objectIds = array($objectIds);
+ }
+
+ if (!isset($cfgSources) || !is_array($cfgSources) || !count($cfgSources)) {
+ return array();
+ }
+
+ if (isset($cfgSources[$source])) {
+ $driver = Turba_Driver::singleton($source);
+ if (is_a($driver, 'PEAR_Error')) {
+ return $driver;
+ }
+
+ $objects = $driver->getObjects($objectIds);
+ if (is_a($objects, 'PEAR_Error')) {
+ return $objects;
+ }
+
+ foreach ($objects as $object) {
+ $attributes = array();
+ foreach ($cfgSources[$source]['map'] as $field => $map) {
+ $attributes[$field] = $object->getValue($field);
+ }
+ $results[] = $attributes;
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Retrieves a list of all possible values of a field in specified source(s).
+ *
+ * @param string $field Field name to check
+ * @param array $sources Array containing the sources to look in
+ *
+ * @return mixed An array of fields and possible values | PEAR_Error
+ */
+ public function getAllAttributeValues($field = '', $sources = array())
+ {
+ require_once dirname(__FILE__) . '/base.php';
+ global $cfgSources;
+
+ if (!isset($cfgSources) || !is_array($cfgSources) || !count($cfgSources)) {
+ return array();
+ }
+
+ if (!count($sources)) {
+ $sources = array(Turba::getDefaultAddressBook());
+ }
+
+ $results = array();
+ foreach ($sources as $source) {
+ if (isset($cfgSources[$source])) {
+ $driver = Turba_Driver::singleton($source);
+ if (is_a($driver, 'PEAR_Error')) {
+ return PEAR::raiseError(sprintf(_("Connection failed: %s"), $driver->getMessage()), 'horde.error', null, null, $source);
+ }
+
+ $res = $driver->search(array());
+ if (!is_a($res, 'Turba_List')) {
+ return PEAR::raiseError(_("Search failed"), 'horde.error', null, null, $source);
+ }
+
+ while ($ob = $res->next()) {
+ if ($ob->hasValue($field)) {
+ $results[$source . ':' . $ob->getValue('__key')] = array(
+ 'name' => $ob->getValue('name'),
+ 'email' => $ob->getValue('email'),
+ $field => $ob->getValue($field));
+ }
+ }
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Retrieves a list of available time objects categories
+ *
+ * @return array An array of all configured time object categories.
+ */
+ public function listTimeObjectCategories()
+ {
+ include dirname(__FILE__) . '/../config/attributes.php';
+ include dirname(__FILE__) . '/../config/sources.php';
+ $categories = array();
+ foreach ($attributes as $key => $attribute) {
+ if ($attribute['type'] == 'monthdayyear' &&
+ !empty($attribute['time_object_label'])) {
+
+ foreach ($cfgSources as $source) {
+ if (!empty($source['map'][$key])) {
+ $categories[$key] = $attribute['time_object_label'];
+ break;
+ }
+ }
+ }
+ }
+
+
+
+ return $categories;
+ }
+
+ /**
+ * Lists birthdays and/or anniversaries as time objects.
+ *
+ * @param array $time_categories The time categories (from
+ * listTimeObjectCategories) to list.
+ * @param mixed $start The start date of the period.
+ * @param mixed $end The end date of the period.
+ *
+ * @return mixed An array of timeObject results || PEAR_Error
+ */
+ public function listTimeObjects($time_categories, $start, $end)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+ global $cfgSources;
+
+ $start = new Horde_Date($start);
+ $end = new Horde_Date($end);
+
+ if (!isset($cfgSources) || !is_array($cfgSources) || !count($cfgSources)) {
+ return array();
+ }
+
+ $objects = array();
+ foreach ($cfgSources as $name => $source) {
+ // Check if we even have to load the driver.
+ $check = array();
+ foreach ($time_categories as $category) {
+ if (!empty($source['map'][$category])) {
+ $check[] = $category;
+ }
+ }
+ if (!count($check)) {
+ continue;
+ }
+ $driver = Turba_Driver::singleton($name);
+ if (is_a($driver, 'PEAR_Error')) {
+ return PEAR::raiseError(sprintf(_("Connection failed: %s"),
+ $driver->getMessage()), 'horde.error',
+ null, null, $name);
+ }
+ foreach ($check as $category) {
+ $new_objects = $driver->listTimeObjects($start, $end, $category);
+ if (is_a($new_objects, 'PEAR_Error')) {
+ return $new_objects;
+ }
+ $objects = array_merge($objects, $new_objects);
+ }
+ }
+
+ return $objects;
+ }
+
+ /**
+ * Returns the client source name
+ *
+ * @return string The name of the source to use with the clients api.
+ */
+ public function getClientSource()
+ {
+ return !empty($GLOBALS['conf']['client']['addressbook']) ? $GLOBALS['conf']['client']['addressbook'] : false;
+ }
+
+ /**
+ * Returns the availabble client fields
+ *
+ * @return mixed An array describing the fields | PEAR_Error
+ */
+ public function clientFields()
+ {
+ return $this->fields($GLOBALS['conf']['client']['addressbook']);
+ }
+
+ /**
+ * Returns a contact from the client source.
+ *
+ * @param string $objectId Client unique ID
+ *
+ * @return mixed Array of client data | PEAR_Error
+ */
+ public function getClient($objectId = '')
+ {
+ return $this->getContact($GLOBALS['conf']['client']['addressbook'], $objectId);
+ }
+
+ /**
+ * Returns mulitple contacts from the client source
+ *
+ * @param array $objectIds client unique ids
+ *
+ * @return mixed An array of clients data | PEAR_Error
+ */
+ public function getClients($objectIds = array())
+ {
+ return $this->getContacts($GLOBALS['conf']['client']['addressbook'], $objectIds);
+ }
+
+ /**
+ * Adds a client to the client source
+ *
+ * @param array $attributes Array containing the client attributes
+ */
+ public function addClient($attributes = array())
+ {
+ return $this->import($attributes, 'array', $this->getClientSource());
+ }
+
+ /**
+ * Updates client data
+ *
+ * @param string $objectId The unique id of the client
+ * @param array $attributes An array of client attributes
+ *
+ * @return boolean
+ */
+ public function updateClient($objectId = '', $attributes = array())
+ {
+ return $this->replace($this->getClientSource() . ':' . $objectId, $attributes, 'array');
+ }
+
+ /**
+ * Deletes a client
+ *
+ * @param string $objectId The unique id of the client
+ *
+ * @return boolean
+ */
+ public function deleteClient($objectId = '')
+ {
+ return $this->delete($this->getClientSource() . ':' . $objectId);
+ }
+
+ /**
+ * Search for clients
+ *
+ * @param array $names The search filter values
+ * @param array $fields The fields to serach in
+ * @param boolean $matchBegin Match word boundaries only
+ *
+ * @return mixed A hash containing the search results | PEAR_Error
+ */
+ public function searchClients($names = array(), $fields = array(),
+ $matchBegin = false)
+ {
+ return $this->search(
+ $names,
+ array($GLOBALS['conf']['client']['addressbook']),
+ array($GLOBALS['conf']['client']['addressbook'] => $fields),
+ $matchBegin,
+ true);
+ }
+
+ /**
+ * Sets the value of the specified attribute of a contact
+ *
+ * @param string $address Contact email address
+ * @param string $name Contact name
+ * @param string $field Field to update
+ * @param string $value Field value to set
+ * @param string $source Contact source
+ *
+ * @return mixed The new __key value on success | PEAR_Error on failure
+ */
+ public function addField($address = '', $name = '', $field = '',
+ $value = '',
+ $source = '')
+ {
+ require_once dirname(__FILE__) . '/base.php';
+ global $cfgSources;
+
+ if (empty($source) || !isset($cfgSources[$source])) {
+ return PEAR::raiseError(sprintf(_("Invalid address book: %s"), $source), 'horde.error', null, null, $source);
+ }
+
+ if (empty($address)) {
+ return PEAR::raiseError(_("Invalid email"), 'horde.error', null, null, $source);
+ }
+
+ if (empty($name)) {
+ return PEAR::raiseError(_("Invalid name"), 'horde.error', null, null, $source);
+ }
+
+ if (empty($value)) {
+ return PEAR::raiseError(_("Invalid entry"), 'horde.error', null, null, $source);
+ }
+
+ $driver = Turba_Driver::singleton($source);
+ if (is_a($driver, 'PEAR_Error')) {
+ return PEAR::raiseError(sprintf(_("Connection failed: %s"), $driver->getMessage()), 'horde.error', null, null, $source);
+ }
+
+ if (!$driver->hasPermission(PERMS_EDIT)) {
+ return PEAR::raiseError(_("Permission denied"), 'horde.error', null, null, $source);
+ }
+
+ $res = $driver->search(array('email' => trim($address)), null, 'AND');
+ if (is_a($res, 'PEAR_Error')) {
+ return PEAR::raiseError(sprintf(_("Search failed: %s"), $res->getMessage()), 'horde.message', null, null, $source);
+ }
+
+ if ($res->count() > 1) {
+ $res2 = $driver->search(array('email' => trim($address), 'name' => trim($name)), null, 'AND');
+ if (is_a($res2, 'PEAR_Error')) {
+ return PEAR::raiseError(sprintf(_("Search failed: %s"), $res2->getMessage()), 'horde.message', null, null, $source);
+ }
+
+ if (!$res2->count()) {
+ return PEAR::raiseError(sprintf(_("Multiple persons with address [%s], but none with name [%s] already exist"), trim($address), trim($name)), 'horde.message', null, null, $source);
+ }
+
+ $res3 = $driver->search(array('email' => $address, 'name' => $name, $field => $value));
+ if (is_a($res3, 'PEAR_Error')) {
+ return PEAR::raiseError(sprintf(_("Search failed: %s"), $res3->getMessage()), 'horde.message', null, null, $source);
+ }
+
+ if ($res3->count()) {
+ return PEAR::raiseError(sprintf(_("This person already has a %s entry in the address book"), $field), 'horde.message', null, null, $source);
+ }
+
+ $ob = $res2->next();
+ $ob->setValue($field, $value);
+ $ob->store();
+ } elseif ($res->count() == 1) {
+ $res4 = $driver->search(array('email' => $address, $field => $value));
+ if (is_a($res4, 'PEAR_Error')) {
+ return PEAR::raiseError(sprintf(_("Search failed: %s"), $res4->getMessage()), 'horde.message', null, null, $source);
+ }
+
+ if ($res4->count()) {
+ return PEAR::raiseError(sprintf(_("This person already has a %s entry in the address book"), $field), 'horde.message', null, null, $source);
+ }
+
+ $ob = $res->next();
+ $ob->setValue($field, $value);
+ $ob->store();
+ } else {
+ return $driver->add(array('email' => $address, 'name' => $name, $field => $value, '__owner' => Horde_Auth::getAuth()));
+ }
+
+ return;
+ }
+
+ /**
+ * Returns a field value
+ *
+ * @param string $address Contact email address
+ * @param string $field Field to get
+ * @param array $sources Sources to check
+ * @param boolean $strict Match the email address strictly
+ * @param boolean $multiple Return more than one entry if found and true,
+ * return an error if this is false.
+ *
+ * @return mixed An array of field value(s) | PEAR_Error on failure.
+ */
+ public function getField($address = '', $field = '', $sources = array(),
+ $strict = false, $multiple = false)
+ {
+ require_once dirname(__FILE__) . '/base.php';
+ global $cfgSources;
+
+ if (empty($address)) {
+ return PEAR::raiseError(_("Invalid email"), 'horde.error');
+ }
+
+ if (!isset($cfgSources) || !is_array($cfgSources) || !count($cfgSources)) {
+ return array();
+ }
+
+ if (!count($sources)) {
+ $sources = array(Turba::getDefaultAddressbook());
+ }
+
+ $result = array();
+ foreach ($sources as $source) {
+ if (!isset($cfgSources[$source])) {
+ continue;
+ }
+
+ $driver = Turba_Driver::singleton($source);
+ if (is_a($driver, 'PEAR_Error')) {
+ continue;
+ }
+
+ $list = $driver->search(array('email' => $address), null, 'AND', array(), $strict ? array('email') : array());
+ if (!is_a($list, 'Turba_List')) {
+ continue;
+ }
+
+ while ($ob = $list->next()) {
+ if ($ob->hasValue($field)) {
+ $result[] = $ob->getValue($field);
+ }
+ }
+ }
+
+ if (count($result) > 1) {
+ if ($multiple) {
+ return $result;
+ } else {
+ return PEAR::raiseError(_("More than 1 entry found"), 'horde.warning', null, null, $source);
+ }
+ } elseif (empty($result)) {
+ return PEAR::raiseError(sprintf(_("No %s entry found for %s"), $field, $address), 'horde.warning', null, null, $source);
+ }
+ return reset($result);
+ }
+
+ /**
+ * Deletes a field value
+ *
+ * @param string $address Contact email address
+ * @param string $field Field to delete value for
+ * @param array $sources Sources to delete value from
+ *
+ * @return boolean
+ */
+ public function deleteField($address = '', $field = '', $sources = array())
+ {
+ require_once dirname(__FILE__) . '/base.php';
+ global $cfgSources;
+
+ if (empty($address)) {
+ return PEAR::raiseError(_("Invalid email"), 'horde.error');
+ }
+
+ if (!isset($cfgSources) || !is_array($cfgSources) || !count($cfgSources)) {
+ return array();
+ }
+
+ if (count($sources) == 0) {
+ $sources = array(Turba::getDefaultAddressbook());
+ }
+
+ $success = false;
+
+ foreach ($sources as $source) {
+ if (isset($cfgSources[$source])) {
+ $driver = Turba_Driver::singleton($source);
+ if (is_a($driver, 'PEAR_Error')) {
+ continue;
+ }
+ if (!$driver->hasPermission(PERMS_EDIT)) {
+ continue;
+ }
+
+ $res = $driver->search(array('email' => $address));
+ if (is_a($res, 'Turba_List')) {
+ if ($res->count() > 1) {
+ continue;
+ }
+
+ $ob = $res->next();
+ if (is_object($ob) && $ob->hasValue($field)) {
+ $ob->setValue($field, '');
+ $ob->store();
+ $success = true;
+ }
+ }
+ }
+ }
+
+ if (!$success) {
+ return PEAR::raiseError(sprintf(_("No %s entry found for %s"), $field, $address), 'horde.error');
+ }
+
+ return;
+ }
+
+}
+++ /dev/null
-<?php
-/**
- * Turba external API interface.
- *
- * This file defines Turba's external API interface. Other applications can
- * interact with Turba through this API.
- *
- * @package Turba
- */
-
-$_services['perms'] = array(
- 'args' => array(),
- 'type' => '{urn:horde}hashHash'
-);
-
-$_services['removeUserData'] = array(
- 'args' => array('user' => 'string'),
- 'type' => 'boolean'
-);
-
-$_services['show'] = array(
- 'link' => '%application%/contact.php?source=|source|&key=|key|&uid=|uid|',
-);
-
-$_services['browse'] = array(
- 'args' => array('path' => 'string', 'properties' => '{urn:horde}stringArray'),
- 'type' => '{urn:horde}hashHash',
-);
-
-$_services['path_delete'] = array(
- 'args' => array('path' => 'string'),
- 'type' => 'boolean',
-);
-
-$_services['sources'] = array(
- 'args' => array('writeable' => 'boolean'),
- 'type' => '{urn:horde}stringArray',
-);
-
-$_services['fields'] = array(
- 'args' => array('source' => '{urn:horde}stringArray'),
- 'type' => '{urn:horde}stringArray',
-);
-
-$_services['list'] = array(
- 'args' => array(),
- 'type' => '{urn:horde}stringArray',
-);
-
-$_services['listBy'] = array(
- 'args' => array('action' => 'string', 'timestamp' => 'int'),
- 'type' => '{urn:horde}stringArray',
-);
-
-$_services['getActionTimestamp'] = array(
- 'args' => array('uid' => 'string', 'action' => 'string', 'sources' => '{urn:horde}stringArray'),
- 'type' => 'int',
-);
-
-$_services['import'] = array(
- 'args' => array('content' => 'string', 'contentType' => 'string', 'source' => 'string'),
- 'type' => 'string',
-);
-
-$_services['export'] = array(
- 'args' => array('uid' => 'string', 'contentType' => 'string'),
- 'type' => 'string',
-);
-
-$_services['ownVCard'] = array(
- 'args' => array(),
- 'type' => 'string',
-);
-
-$_services['ownContact'] = array(
- 'args' => array(),
- 'type' => '{urn:horde}stringArray',
-);
-
-$_services['delete'] = array(
- 'args' => array('uid' => 'string'),
- 'type' => 'boolean',
-);
-
-$_services['replace'] = array(
- 'args' => array('uid' => 'string', 'content' => 'string', 'contentType' => 'string'),
- 'type' => 'boolean',
-);
-
-$_services['search'] = array(
- 'args' => array('names' => '{urn:horde}stringArray',
- 'sources' => '{urn:horde}stringArray',
- 'fields' => '{urn:horde}stringArray',
- 'matchBegin' => 'boolean',
- 'forceSource' => 'boolean'),
- 'type' => '{urn:horde}stringArray',
-);
-
-$_services['getContact'] = array(
- 'args' => array('source' => 'string', 'objectId' => 'string'),
- 'type' => '{urn:horde}stringArray',
-);
-
-$_services['getContacts'] = array(
- 'args' => array('source' => 'string', 'objectIds' => '{urn:horde}stringArray'),
- 'type' => '{urn:horde}stringArray',
-);
-
-$_services['addField'] = array(
- 'args' => array('address' => 'string', 'name' => 'string', 'field' => 'string', 'value' => 'string', 'source' => 'string'),
- 'type' => '{urn:horde}stringArray',
-);
-
-$_services['deleteField'] = array(
- 'args' => array('address' => 'string', 'field' => 'string', 'sources' => '{urn:horde}stringArray'),
- 'type' => '{urn:horde}stringArray',
-);
-
-$_services['getField'] = array(
- 'args' => array('address' => 'string', 'field' => 'string', 'sources' => '{urn:horde}stringArray', 'strict' => 'boolean', 'multiple' => 'boolean'),
- 'type' => '{urn:horde}stringArray',
-);
-
-$_services['getAllAttributeValues'] = array(
- 'args' => array('field' => 'string', 'sources' => '{urn:horde}stringArray'),
- 'type' => '{urn:horde}stringArray',
-);
-
-$_services['listTimeObjectCategories'] = array(
- 'type' => '{urn:horde}stringArray'
-);
-
-$_services['listTimeObjects'] = array(
- 'args' => array('categories' => '{urn:horde}stringArray', 'start' => 'int', 'end' => 'int'),
- 'type' => '{urn:horde}hashHash'
-);
-
-$_services['getClientSource'] = array(
- 'checkperms' => false,
- 'args' => array(),
- 'type' => 'string',
-);
-
-$_services['clientFields'] = array(
- 'args' => array(),
- 'type' => '{urn:horde}stringArray',
-);
-
-$_services['getClient'] = array(
- 'checkperms' => false,
- 'args' => array('objectId' => 'string'),
- 'type' => '{urn:horde}stringArray',
-);
-
-$_services['getClients'] = array(
- 'checkperms' => false,
- 'args' => array('objectIds' => '{urn:horde}stringArray'),
- 'type' => '{urn:horde}stringArray',
-);
-
-$_services['addClient'] = array(
- 'args' => array('attributes' => '{urn:horde}stringArray'),
- 'type' => 'string',
-);
-
-$_services['updateClient'] = array(
- 'args' => array('objectId' => 'string', 'attributes' => '{urn:horde}stringArray'),
- 'type' => 'string',
-);
-
-$_services['deleteClient'] = array(
- 'args' => array('objectId' => 'string'),
- 'type' => '{urn:horde}stringArray',
-);
-
-$_services['searchClients'] = array(
- 'checkperms' => false,
- 'args' => array('names' => '{urn:horde}stringArray',
- 'fields' => '{urn:horde}stringArray',
- 'matchBegin' => 'boolean'),
- 'type' => '{urn:horde}stringArray',
-);
-
-$_services['commentCallback'] = array(
- 'args' => array('id' => 'string'),
- 'type' => 'string'
-);
-
-$_services['hasComments'] = array(
- 'args' => array(),
- 'type' => 'boolean'
-);
-
-$_services['getDefaultShare'] = array(
- 'args' => array(),
- 'type' => 'string'
-);
-
-/**
- * Removes user data.
- *
- * @param string $user Name of user to remove data for.
- *
- * @return mixed true on success | PEAR_Error on failure
- */
-function _turba_removeUserData($user)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- if (!Horde_Auth::isAdmin() && $user != Horde_Auth::getAuth()) {
- return PEAR::raiseError(_("You are not allowed to remove user data."));
- }
-
- /* We need a clean copy of the $cfgSources array here.*/
- require TURBA_BASE . '/config/sources.php';
- $hasError = false;
-
- foreach ($cfgSources as $source) {
- if (empty($source['use_shares'])) {
- // Shares not enabled for this source
- $driver = &Turba_Driver::singleton($source);
- if (is_a($driver, 'PEAR_Error')) {
- Horde::logMessage($driver, __FILE__, __LINE__, PEAR_LOG_ERR);
- $hasError = true;
- } else {
- $result = $driver->removeUserData($user);
- if (is_a($result, 'PEAR_Error')) {
- Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
- }
- }
- }
- }
-
- /* Only attempt share removal if we have shares configured */
- if (!empty($_SESSION['turba']['has_share'])) {
- $shares = &$GLOBALS['turba_shares']->listShares(
- $user, PERMS_EDIT, $user);
-
- /* Look for the deleted user's default share and remove it */
- foreach ($shares as $share) {
- $params = @unserialize($share->get('params'));
- /* Only attempt to delete the user's default share */
- if (!empty($params['default'])) {
- $config = Turba::getSourceFromShare($share);
- $driver = &Turba_Driver::singleton($config);
- $result = $driver->removeUserData($user);
- if (is_a($result, 'PEAR_Error')) {
- Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
- $hasError = true;
- }
- }
- }
-
- /* Get a list of all shares this user has perms to and remove the perms */
- $shares = $GLOBALS['turba_shares']->listShares($user);
- if (is_a($shares, 'PEAR_Error')) {
- Horde::logMessage($shares, __FILE__, __LINE__, PEAR_LOG_ERR);
- }
- foreach ($shares as $share) {
- $share->removeUser($user);
- }
-
- }
-
- if ($hasError) {
- return PEAR::raiseError(sprintf(_("There was an error removing an address book for %s"), $user));
- }
-
- return true;
-}
-
-/**
- * Callback for comment API
- *
- * @param integer $id Internal data identifier
- *
- * @return mixed Name of object on success | false on failure
- */
-function _turba_commentCallback($id)
-{
- if (!$GLOBALS['conf']['comments']['allow']) {
- return false;
- }
-
- require_once dirname(__FILE__) . '/base.php';
- global $cfgSources;
-
- @list($source, $key) = explode('.', $id, 2);
- if (isset($cfgSources[$source]) && $key) {
- $driver = &Turba_Driver::singleton($source);
- if (!is_a($driver, 'PEAR_Error')) {
- $object = $driver->getObject($key);
- if (!is_a($object, 'PEAR_Error')) {
- return $object->getValue('name');
- }
- }
- }
-
- return false;
-}
-
-/**
- * Returns if applications allows comments
- *
- * @return boolean
- */
-function _turba_hasComments()
-{
- return $GLOBALS['conf']['comments']['allow'];
-}
-
-/**
- * Returns a list of available permissions.
- *
- * @return array An array describing all available permissions.
- */
-function _turba_perms()
-{
- static $perms = array();
- if (!empty($perms)) {
- return $perms;
- }
-
- require_once dirname(__FILE__) . '/base.php';
- require TURBA_BASE . '/config/sources.php';
-
- $perms['tree']['turba']['sources'] = false;
- $perms['title']['turba:sources'] = _("Sources");
-
- // Run through every contact source.
- foreach ($cfgSources as $source => $curSource) {
- $perms['tree']['turba']['sources'][$source] = false;
- $perms['title']['turba:sources:' . $source] = $curSource['title'];
- $perms['tree']['turba']['sources'][$source]['max_contacts'] = false;
- $perms['title']['turba:sources:' . $source . ':max_contacts'] = _("Maximum Number of Contacts");
- $perms['type']['turba:sources:' . $source . ':max_contacts'] = 'int';
- }
-
- return $perms;
-}
-
-/**
- * Returns a list of available sources.
- *
- * @param boolean $writeable Set to true to limit to writeable sources.
- *
- * @return array An array of the available sources.
- */
-function _turba_sources($writeable = false)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- $addressbooks = Turba::getAddressBooks($writeable ? PERMS_EDIT : PERMS_READ);
- foreach ($addressbooks as $addressbook => $config) {
- $addressbooks[$addressbook] = $config['title'];
- }
-
- return $addressbooks;
-}
-
-/**
- * Returns a list of fields avaiable in a source.
- *
- * @param string $source The name of the source
- *
- * @return mixed An array describing the fields | PEAR_Error
- */
-function _turba_fields($source = null)
-{
- require_once dirname(__FILE__) . '/base.php';
- global $cfgSources, $attributes;
-
- if (empty($source) || !isset($cfgSources[$source])) {
- return PEAR::raiseError(sprintf(_("Invalid address book: %s"), $source), 'horde.error', null, null, $source);
- }
-
- $fields = array();
- foreach ($cfgSources[$source]['map'] as $field_name => $null) {
- if (substr($field_name, 0, 2) != '__') {
- $fields[$field_name] = array('name' => $field_name,
- 'type' => $attributes[$field_name]['type'],
- 'label' => $attributes[$field_name]['label'],
- 'search' => in_array($field_name, $cfgSources[$source]['search']));
- }
- }
-
- return $fields;
-}
-
-/**
- * Retrieve the UID for the current user's default Turba share.
- *
- */
-function _turba_getDefaultShare()
-{
- global $prefs;
-
- // Bring in turba's base and a clean copy of sources.
- require_once dirname(__FILE__) . '/base.php';
- require TURBA_BASE . '/config/sources.php';
-
- if (!empty($_SESSION['turba']['has_share'])) {
- $shares = Turba::listShares(true);
- if (is_a($shares, 'PEAR_Error')) {
- return false;
- }
- foreach ($shares as $uid => $share) {
- $params = @unserialize($share->get('params'));
- if (empty($params['source'])) {
- continue;
- }
- $driver = &Turba_Driver::factory($params['source'], $cfgSources[$params['source']]);
- if (is_a($driver, 'PEAR_Error')) {
- continue;
- }
- if ($driver->checkDefaultShare($share, $cfgSources[$params['source']])) {
- return $uid;
- }
- }
- }
-
- // Return Turba's default_dir as default
- return $prefs->getValue('default_dir');
-}
-
-/**
- * Browses through Turba's object tree.
- *
- * @param string $path The path of the tree to browse.
- * @param array $properties The item properties to return. Defaults to 'name',
- * 'icon', and 'browseable'.
- *
- * @return array Content of the specified path.
- */
-function _turba_browse($path = '', $properties = array())
-{
- function _modified($uid)
- {
- $modified = _turba_getActionTimestamp($uid, 'modify');
- if (empty($modified)) {
- $modified = _turba_getActionTimestamp($uid, 'add');
- }
- return $modified;
- }
-
- require_once dirname(__FILE__) . '/base.php';
- global $registry, $cfgSources;
-
- // Default properties.
- if (!$properties) {
- $properties = array('name', 'icon', 'browseable');
- }
-
- // Strip off the application name if present
- if (substr($path, 0, 5) == 'turba') {
- $path = substr($path, 5);
- }
- $path = trim($path, '/');
- $parts = explode('/', $path);
-
- $now = time();
- $results = array();
- if (empty($path)) {
- // We always provide the "global" folder which contains address book
- // sources that are shared among all users. Per-user shares are shown
- // in a folder for each respective user.
- $results = array();
- $shares = Turba::listShares();
- $owners = array('global' => true);
- foreach ($shares as $share) {
- $owners[$share->get('owner')] = true;
- }
-
- foreach (array_keys($owners) as $owner) {
- if (in_array('name', $properties)) {
- $results['turba/' . $owner]['name'] = $owner;
- }
- if (in_array('icon', $properties)) {
- $results['turba/' . $owner]['icon'] = $registry->getImageDir() . '/turba.png';
- }
- if (in_array('browseable', $properties)) {
- $results['turba/' . $owner]['browseable'] = true;
- }
- if (in_array('contenttype', $properties)) {
- $results['turba/' . $owner]['contenttype'] = 'httpd/unix-directory';
- }
- if (in_array('contentlength', $properties)) {
- $results['turba/' . $owner]['contentlength'] = 0;
- }
- if (in_array('modified', $properties)) {
- // @TODO: Get a real modification date
- $results['turba/' . $owner]['modified'] = $now;
- }
- if (in_array('created', $properties)) {
- // @TODO Get a real creation date
- $results['turba/' . $owner]['created'] = 0;
- }
- }
- return $results;
- } elseif (count($parts) == 1) {
- //
- // We should either have the username that is a valid share owner or
- // 'global'
- //
- if (empty($parts[0])) {
- // We need either 'global' or a valid username with shares
- return array();
- }
-
- if ($parts[0] == 'global') {
- // The client is requesting a list of global address books.
- $addressbooks = Turba::getAddressBooks();
- foreach ($addressbooks as $addressbook => $info) {
- if ($info['type'] == 'share') {
- // Ignore address book shares in the 'global' folder
- unset($addressbooks[$addressbook]);
- }
- }
- } else {
- // Assume $parts[0] is a valid username and we need to list their
- // shared addressbooks.
- if (empty($_SESSION['turba']['has_share'])) {
- // No backends are configured to provide shares
- return array();
- }
- $addressbooks = $GLOBALS['turba_shares']->listShares($parts[0],
- PERMS_READ,
- $parts[0]);
- // The last check returns all addressbooks for the requested user,
- // but that does not mean the requesting user has access to them.
- // Filter out those address books for which the requesting user has
- // no access.
- $addressbooks = Turba::permissionsFilter($addressbooks);
- }
-
- $curpath = 'turba/' . $parts[0] . '/';
- foreach ($addressbooks as $addressbook => $info) {
- if (in_array('name', $properties)) {
- if (is_a($info, 'Horde_Share_Object')) {
- $name = $info->get('title');
- } else {
- $name = $info['title'];
- }
- $results[$curpath . $addressbook]['name'] = $name;
- }
- if (in_array('icon', $properties)) {
- $results[$curpath . $addressbook]['icon'] = $registry->getImageDir() . '/turba.png';
- }
- if (in_array('browseable', $properties)) {
- $results[$curpath . $addressbook]['browseable'] = true;
- }
- if (in_array('contenttype', $properties)) {
- $results[$curpath . $addressbook]['contenttype'] = 'httpd/unix-directory';
- }
- if (in_array('contentlength', $properties)) {
- $results[$curpath . $addressbook]['contentlength'] = 0;
- }
- if (in_array('modified', $properties)) {
- // @TODO: Get a real modification date
- $results[$curpath . $addressbook]['modified'] = $now;
- }
- if (in_array('created', $properties)) {
- // @TODO Get a real creation date
- $results[$curpath . $addressbook]['created'] = 0;
- }
- }
- return $results;
-
- } elseif (count($parts) == 2) {
- //
- // The client is requesting all contacts from a given addressbook
- //
- if (empty($parts[0]) || empty($parts[1])) {
- // $parts[0] must be either 'global' or a valid user with shares
- // $parts[1] must be an address book ID
- return array();
- }
-
- $addressbooks = Turba::getAddressBooks();
- if (!isset($addressbooks[$parts[1]])) {
- // We must have a valid addressbook to continue.
- return array();
- }
-
- // Load the Turba driver.
- $driver = &Turba_Driver::singleton($parts[1]);
- if (is_a($driver, 'PEAR_Error')) {
- return PEAR::raiseError(sprintf(_("Connection failed: %s"), $driver->getMessage()), 'horde.error', null, null, $cfgSources[$parts[1]]);
- }
-
- $contacts = $driver->search(array());
- if (is_a($contacts, 'PEAR_Error')) {
- return $contacts;
- }
-
- $contacts->reset();
- $curpath = 'turba/' . $parts[0] . '/' . $parts[1] . '/';
- while ($contact = $contacts->next()) {
- $key = $curpath . $contact->getValue('__key');
- if (in_array('name', $properties)) {
- $results[$key]['name'] = Turba::formatName($contact);
- }
- if (in_array('icon', $properties)) {
- $results[$key]['icon'] = $registry->getImageDir('horde') . '/mime/vcard.png';
- }
- if (in_array('browseable', $properties)) {
- $results[$key]['browseable'] = false;
- }
- if (in_array('contenttype', $properties)) {
- $results[$key]['contenttype'] = 'text/x-vcard';
- }
- if (in_array('contentlength', $properties)) {
- $data = _turba_export($contact->getValue('__uid'), 'text/x-vcard', $contact->getSource());
- if (is_a($data, 'PEAR_Error')) {
- $data = '';
- }
- $results[$key]['contentlength'] = strlen($data);
- }
- if (in_array('modified', $properties)) {
- $results[$key]['modified'] = _modified($contact->getValue('__uid'));
- }
- if (in_array('created', $properties)) {
- $results[$key]['created'] = _turba_getActionTimestamp($contact->getValue('__uid'), 'add');
- }
- }
-
- return $results;
-
- } elseif (count($parts) == 3) {
- //
- // The client is requesting an individual contact
- //
- $addressbooks = Turba::getAddressBooks();
- if (!isset($addressbooks[$parts[1]])) {
- // We must have a valid addressbook to continue.
- return array();
- }
-
- // Load the Turba driver.
- $driver = &Turba_Driver::singleton($parts[1]);
- if (is_a($driver, 'PEAR_Error')) {
- return PEAR::raiseError(sprintf(_("Connection failed: %s"), $driver->getMessage()), 'horde.error', null, null, $cfgSources[$parts[1]]);
- }
-
- $contact = &$driver->getObject($parts[2]);
- if (is_a($contact, 'PEAR_Error')) {
- return $contact;
- }
-
- $result = array('data' => _turba_export($contact->getValue('__uid'), 'text/x-vcard', $contact->getSource()),
- 'mimetype' => 'text/x-vcard');
- $modified = _modified($contact->getValue('__uid'));
- if (!empty($modified)) {
- $result['mtime'] = $modified;
- }
- return $result;
- } else {
- return PEAR::raiseError(_("Malformed request."));
- }
-}
-
-/**
- * Deletes a file from the Turba tree.
- *
- * @param string $path The path to the file.
- *
- * @return mixed The event's UID, or a PEAR_Error on failure.
- */
-function _turba_path_delete($path)
-{
- require_once dirname(__FILE__) . '/base.php';
- global $registry, $cfgSources;
-
- // Strip off the application name if present
- if (substr($path, 0, 5) == 'turba') {
- $path = substr($path, 5);
- }
- $path = trim($path, '/');
- $parts = explode('/', $path);
-
- $now = time();
- $results = array();
-
- if (count($parts) < 3) {
- // Deletes must be on individual contacts
- return PEAR::raiseError(_("Delete denied."), 403);
- }
- if (!array_key_exists($parts[1], Turba::getAddressBooks())) {
- return PEAR::raiseError("Address book does not exist", 404);
- }
-
- // Load the Turba driver.
- $driver = &Turba_Driver::singleton($parts[1]);
- if (is_a($driver, 'PEAR_Error')) {
- return PEAR::raiseError(sprintf(_("Connection failed: %s"), $driver->getMessage()), 500, null, null, $cfgSources[$parts]);
- }
-
- $ret = $driver->delete($parts[2]);
- if (is_a($ret, 'PEAR_Error')) {
- // A deeper error occurred. Make sure the code is a valid HTTP response
- $ret->code = 500;
- return $ret;
- }
-}
-
-/**
- * Returns an array of UIDs for all contacts that the current user is
- * authorized to see.
- *
- * @param string|array $sources The name(s) of the source(s) to return
- * contacts of. If left empty, the current user's
- * sync sources or default source are used.
- *
- * @return array An array of UIDs for all contacts the user can access.
- */
-function _turba_list($sources = null)
-{
- require_once dirname(__FILE__) . '/base.php';
-
- global $cfgSources, $prefs;
-
- /* Get default address book from user preferences. */
- if (empty($sources)) {
- $sources = @unserialize($prefs->getValue('sync_books'));
- } elseif (!is_array($sources)) {
- $sources = array($sources);
- }
- if (empty($sources)) {
- $sources = array(Turba::getDefaultAddressbook());
- }
- if (empty($sources)) {
- return PEAR::raiseError(_("No address book specified"), 'horde.error');
- }
-
- $uids = array();
- foreach ($sources as $source) {
- if (empty($source) || !isset($cfgSources[$source])) {
- return PEAR::raiseError(sprintf(_("Invalid address book: %s"), $source), 'horde.error', null, null, $source);
- }
-
- $storage = &Turba_Driver::singleton($source);
- if (is_a($storage, 'PEAR_Error')) {
- return PEAR::raiseError(sprintf(_("Connection failed: %s"), $storage->getMessage()), 'horde.error', null, null, $source);
- }
-
- $results = $storage->search(array());
-
- if (is_a($results, 'PEAR_Error')) {
- return PEAR::raiseError(sprintf(_("Error searching the address book: %s"), $results->getMessage()), 'horde.error', null, null, $source);
- }
-
- foreach ($results->objects as $o) {
- $uids[] = $o->getValue('__uid');
- }
- }
-
- return $uids;
-}
-
-/**
- * Returns an array of UIDs for contacts that have had $action happen since
- * $timestamp.
- *
- * @param string $action The action to check for - add, modify, or
- * delete.
- * @param integer $timestamp The time to start the search.
- * @param string|array $sources The source(s) for which to retrieve the
- * history.
- *
- * @return array An array of UIDs matching the action and time criteria.
- */
-function _turba_listBy($action, $timestamp, $sources = null)
-{
- global $prefs, $cfgSources;
- require_once dirname(__FILE__) . '/base.php';
-
- /* Get default address book from user preferences. */
- if (empty($sources)) {
- $sources = @unserialize($prefs->getValue('sync_books'));
- } elseif (!is_array($sources)) {
- $sources = array($sources);
- }
- if (empty($sources)) {
- $sources = array(Turba::getDefaultAddressbook());
- }
- if (empty($sources)) {
- return PEAR::raiseError(_("No address book specified"), 'horde.error');
- }
-
- $uids = array();
- foreach ($sources as $source) {
- if (empty($source) || !isset($cfgSources[$source])) {
- return PEAR::raiseError(sprintf(_("Invalid address book: %s"), $source), 'horde.error', null, null, $source);
- }
-
- $driver = &Turba_Driver::singleton($source);
- if (is_a($driver, 'PEAR_Error')) {
- return PEAR::raiseError(sprintf(_("Connection failed: %s"), $driver->getMessage()), 'horde.error', null, null, $source);
- }
-
- $history = &Horde_History::singleton();
- $histories = $history->getByTimestamp('>', $timestamp,
- array(array('op' => '=',
- 'field' => 'action',
- 'value' => $action)),
- 'turba:' . $driver->getName());
- if (is_a($histories, 'PEAR_Error')) {
- return $histories;
- }
-
- // Strip leading turba:addressbook:.
- $uids = array_merge($uids,
- str_replace('turba:' . $driver->getName() . ':',
- '',
- array_keys($histories)));
- }
-
- return $uids;
-}
-
-/**
- * Returns the timestamp of an operation for a given uid an action.
- *
- * @param string $uid The uid to look for.
- * @param string $action The action to check for - add, modify, or
- * delete.
- * @param string|array $sources The source(s) for which to retrieve the
- * history.
- *
- * @return integer The timestamp for this action.
- */
-function _turba_getActionTimestamp($uid, $action, $sources = null)
-{
- global $prefs, $cfgSources;
- require_once dirname(__FILE__) . '/base.php';
-
- /* Get default address book from user preferences. */
- if (empty($sources)) {
- $sources = @unserialize($prefs->getValue('sync_books'));
- } elseif (!is_array($sources)) {
- $sources = array($sources);
- }
- if (empty($sources)) {
- $sources = array(Turba::getDefaultAddressbook());
- }
- if (empty($sources)) {
- return PEAR::raiseError(_("No address book specified"), 'horde.error');
- }
-
- foreach ($sources as $source) {
- if (empty($source) || !isset($cfgSources[$source])) {
- return PEAR::raiseError(sprintf(_("Invalid address book: %s"), $source), 'horde.error', null, null, $source);
- }
-
- $driver = &Turba_Driver::singleton($source);
- if (is_a($driver, 'PEAR_Error')) {
- return PEAR::raiseError(sprintf(_("Connection failed: %s"), $driver->getMessage()), 'horde.error', null, null, $source);
- }
-
- $history = &Horde_History::singleton();
- $ts = $history->getActionTimestamp('turba:' . $driver->getName()
- . ':' . $uid,
- $action);
- if (!empty($ts)) {
- return $ts;
- }
- }
-
- return 0;
-}
-
-/**
- * Import a contact represented in the specified contentType.
- *
- * @param string $content The content of the contact.
- * @param string $contentType What format is the data in? Currently supports
- * array, text/directory, text/vcard and
- * text/x-vcard.
- * @param string $source The source into which the contact will be
- * imported.
- *
- * @return string The new UID, or false on failure.
- */
-function _turba_import($content, $contentType = 'array', $import_source = null)
-{
- require_once dirname(__FILE__) . '/base.php';
- global $cfgSources, $prefs;
-
- /* Get default address book from user preferences. */
- if (empty($import_source)) {
- $import_source = $prefs->getValue('default_dir');
- /* On new installations default_dir is not set, use first source
- * instead. */
- if (empty($import_source)) {
- $import_source = key(Turba::getAddressBooks(PERMS_EDIT));
- }
- }
-
- // Check existance of and permissions on the specified source.
- if (!isset($cfgSources[$import_source])) {
- return PEAR::raiseError(sprintf(_("Invalid address book: %s"),
- $import_source),
- 'horde.warning');
- }
-
- $driver = &Turba_Driver::singleton($import_source);
- if (is_a($driver, 'PEAR_Error')) {
- return PEAR::raiseError(sprintf(_("Connection failed: %s"), $driver->getMessage()), 'horde.error', null, null, $import_source);
- }
-
- if (!$driver->hasPermission(PERMS_EDIT)) {
- return PEAR::raiseError(_("Permission denied"), 'horde.error', null, null, $import_source);
- }
-
- /* Create a category manager. */
- require_once 'Horde/Prefs/CategoryManager.php';
- $cManager = new Prefs_CategoryManager();
- $categories = $cManager->get();
-
- if (!is_a($content, 'Horde_iCalendar_vcard')) {
- switch ($contentType) {
- case 'array':
- break;
-
- case 'text/x-vcard':
- case 'text/vcard':
- case 'text/directory':
- require_once 'Horde/iCalendar.php';
- $iCal = new Horde_iCalendar();
- if (!$iCal->parsevCalendar($content)) {
- return PEAR::raiseError(_("There was an error importing the iCalendar data."));
- }
- switch ($iCal->getComponentCount()) {
- case 0:
- return PEAR::raiseError(_("No vCard data was found."));
-
- case 1:
- $content = $iCal->getComponent(0);
- break;
-
- default:
- $ids = array();
- foreach ($iCal->getComponents() as $c) {
- if (is_a($c, 'Horde_iCalendar_vcard')) {
- $content = $driver->toHash($c);
- $result = $driver->search($content);
- if (is_a($result, 'PEAR_Error')) {
- return $result;
- } elseif ($result->count() > 0) {
- continue;
- }
- $result = $driver->add($content);
- if (is_a($result, 'PEAR_Error')) {
- return $result;
- }
- if (!empty($content['category']) &&
- !in_array($content['category'], $categories)) {
- $cManager->add($content['category']);
- $categories[] = $content['category'];
- }
- $ids[] = $result;
- }
- }
- return $ids;
- }
- break;
-
- default:
- return PEAR::raiseError(sprintf(_("Unsupported Content-Type: %s"), $contentType));
- }
- }
-
- if (is_a($content, 'Horde_iCalendar_vcard')) {
- $content = $driver->toHash($content);
- }
-
- // Check if the entry already exists in the data source:
- $result = $driver->search($content);
- if (is_a($result, 'PEAR_Error')) {
- return $result;
- } elseif ($result->count() > 0) {
- $o = $result->objects[0];
- return PEAR::raiseError(_("Already Exists"), 'horde.message', null, null, $o->getValue('__uid'));
- }
-
- $result = $driver->add($content);
- if (is_a($result, 'PEAR_Error')) {
- return $result;
- }
-
- if (!empty($content['category']) &&
- !in_array($content['category'], $categories)) {
- $cManager->add($content['category']);
- }
-
- $object = &$driver->getObject($result);
- return is_a($object, 'PEAR_Error') ? $object : $object->getValue('__uid');
-}
-
-/**
- * Export a contact, identified by UID, in the requested contentType.
- *
- * @param string $uid Identify the contact to export.
- * @param mixed $contentType What format should the data be in?
- * Either a string with one of:
- * - text/directory
- * - text/vcard
- * - text/x-vcard
- * The first two produce a vcard3.0 (rfc2426),
- * the second produces a vcard in old 2.1 format
- * defined by imc.org
- * @param string|array $sources The source(s) from which the contact will be
- * exported.
- *
- * @return mixed The requested data | PEAR_Error
- */
-function _turba_export($uid, $contentType, $sources = null)
-{
- require_once dirname(__FILE__) . '/base.php';
- global $cfgSources, $prefs;
-
- /* Get default address book from user preferences. */
- if (empty($sources)) {
- $sources = @unserialize($prefs->getValue('sync_books'));
- } elseif (!is_array($sources)) {
- $sources = array($sources);
- }
- if (empty($sources)) {
- $sources = array(Turba::getDefaultAddressbook());
- }
- if (empty($sources)) {
- return PEAR::raiseError(_("No address book specified"), 'horde.error');
- }
-
- foreach ($sources as $source) {
- if (empty($source) || !isset($cfgSources[$source])) {
- return PEAR::raiseError(sprintf(_("Invalid address book: %s"), $source), 'horde.error', null, null, $source);
- }
-
- if (empty($uid)) {
- return PEAR::raiseError(_("Invalid ID"), 'horde.error', null, null, $source);
- }
-
- $driver = &Turba_Driver::singleton($source);
- if (is_a($driver, 'PEAR_Error')) {
- return PEAR::raiseError(sprintf(_("Connection failed: %s"), $driver->getMessage()), 'horde.error', null, null, $source);
- }
-
- if (!$driver->hasPermission(PERMS_READ)) {
- continue;
- }
-
- $result = $driver->search(array('__uid' => $uid));
- if (is_a($result, 'PEAR_Error')) {
- return $result;
- } elseif ($result->count() == 0) {
- continue;
- } elseif ($result->count() > 1) {
- return PEAR::raiseError("Internal Horde Error: multiple turba objects with same objectId.", 'horde.error', null, null, $source);
- }
-
- $version = '3.0';
- list($contentType,) = explode(';', $contentType);
- switch ($contentType) {
- case 'text/x-vcard':
- $version = '2.1';
- case 'text/vcard':
- case 'text/directory':
- require_once 'Horde/iCalendar.php';
- $export = '';
- foreach ($result->objects as $obj) {
- $vcard = $driver->tovCard($obj, $version);
- /* vCards are not enclosed in BEGIN:VCALENDAR..END:VCALENDAR.
- * Export the individual cards instead. */
- $export .= $vcard->exportvCalendar();
- }
- return $export;
- }
-
- return PEAR::raiseError(sprintf(_("Unsupported Content-Type: %s"), $contentType));
- }
-
- return PEAR::raiseError(_("Object not found"));
-}
-
-/**
- * Exports the user's own contact as a vCard string.
- *
- * @return string The requested vCard data or PEAR_Error.
- */
-function _turba_ownVCard()
-{
- $contact = _turba_getOwnContactObject();
- if (is_a($contact, 'PEAR_Error')) {
- return $contact;
- }
- $driver = &Turba_Driver::singleton($contact['source']);
- if (is_a($driver, 'PEAR_Error')) {
- return PEAR::raiseError(sprintf(_("Connection failed: %s"), $driver->getMessage()));
- }
- require_once 'Horde/iCalendar.php';
- $vcard = $driver->tovCard($contact['contact'], '3.0');
- $vcard->setAttribute('VERSION', '3.0');
-
- return $vcard->exportvCalendar();
-}
-
-/**
- * Export the user's own contact as a hash
- *
- * @return Array The contact hash or PEAR_Error
- */
-function _turba_ownContact()
-{
- $contact = _turba_getOwnContactObject();
- if (is_a($contact, 'PEAR_Error')) {
- return $contact;
- }
-
- return $contact['contact']->getAttributes();
-}
-
-/**
- * Helper function to return the user's own contact object
- *
- * @return Array A hash containing the Turba_Object representing the user's
- * own contact and the source that it is from or PEAR_Error.
- */
-function _turba_getOwnContactObject()
-{
- require_once dirname(__FILE__) . '/base.php';
- global $cfgSources;
-
- $own_contact = $GLOBALS['prefs']->getValue('own_contact');
- if (empty($own_contact)) {
- return PEAR::raiseError(_("You didn't mark a contact as your own yet."));
- }
- @list($source, $id) = explode(';', $own_contact);
-
- if (!isset($cfgSources[$source])) {
- return PEAR::raiseError(_("The address book with your own contact doesn't exist anymore."));
- }
-
- $driver = &Turba_Driver::singleton($source);
- if (is_a($driver, 'PEAR_Error')) {
- return PEAR::raiseError(sprintf(_("Connection failed: %s"), $driver->getMessage()));
- }
-
- if (!$driver->hasPermission(PERMS_READ)) {
- return PEAR::raiseError(_("You don't have sufficient permissions to read the address book that contains your own contact."));
- }
-
- $contact = $driver->getObject($id);
- if (is_a($contact, 'PEAR_Error')) {
- return PEAR::raiseError(_("Your own contact cannot be found in the address book."));
- }
-
- $return = array('contact' => $contact,
- 'source'=> $source);
-
- return $return;
-}
-
-
-/**
- * Deletes a contact identified by UID.
- *
- * @param string|array $uid Identify the contact to delete, either a
- * single UID or an array.
- * @param string|array $sources The source(s) from which the contact will be
- * deleted.
- *
- * @return boolean Success or failure.
- */
-function _turba_delete($uid, $sources = null)
-{
- // Handle an array of UIDs for convenience of deleting multiple contacts
- // at once.
- if (is_array($uid)) {
- foreach ($uid as $g) {
- $result = _turba_delete($uid, $source);
- if (is_a($result, 'PEAR_Error')) {
- return $result;
- }
- }
-
- return true;
- }
-
- require_once dirname(__FILE__) . '/base.php';
- global $cfgSources, $prefs;
-
- /* Get default address book from user preferences. */
- if (empty($sources)) {
- $sources = @unserialize($prefs->getValue('sync_books'));
- } elseif (!is_array($sources)) {
- $sources = array($sources);
- }
- if (empty($sources)) {
- $sources = array(Turba::getDefaultAddressbook());
- }
- if (empty($sources)) {
- return PEAR::raiseError(_("No address book specified"), 'horde.error');
- }
-
- foreach ($sources as $source) {
- if (empty($source) || !isset($cfgSources[$source])) {
- return PEAR::raiseError(sprintf(_("Invalid address book: %s"), $source), 'horde.error', null, null, $source);
- }
-
- if (empty($uid)) {
- return PEAR::raiseError(_("Invalid ID"), 'horde.error', null, null, $source);
- }
-
- $driver = &Turba_Driver::singleton($source);
- if (is_a($driver, 'PEAR_Error')) {
- return PEAR::raiseError(sprintf(_("Connection failed: %s"), $driver->getMessage()), 'horde.error', null, null, $source);
- }
-
- if (!Horde_Auth::isAdmin() && !$driver->hasPermission(PERMS_DELETE)) {
- continue;
- }
-
- // If the objectId isn't in $source in the first place, just return
- // true. Otherwise, try to delete it and return success or failure.
- $result = $driver->search(array('__uid' => $uid));
- if (is_a($result, 'PEAR_Error')) {
- return $result;
- } elseif ($result->count() == 0) {
- continue;
- } else {
- $r = $result->objects[0];
- return $driver->delete($r->getValue('__key'));
- }
- }
-
- return true;
-}
-
-/**
- * Replaces the contact identified by UID with the content represented in the
- * specified contentType.
- *
- * @param string $uid Idenfity the contact to replace.
- * @param string $content The content of the contact.
- * @param string $contentType What format is the data in? Currently supports
- * array, text/directory, text/vcard and
- * text/x-vcard.
- * @param string|array $sources The source(s) where the contact will be
- * replaced.
- *
- * @return boolean Success or failure.
- */
-function _turba_replace($uid, $content, $contentType, $sources = null)
-{
- require_once dirname(__FILE__) . '/base.php';
- global $cfgSources, $prefs;
-
- /* Get default address book from user preferences. */
- if (empty($sources)) {
- $sources = @unserialize($prefs->getValue('sync_books'));
- } elseif (!is_array($sources)) {
- $sources = array($sources);
- }
- if (empty($sources)) {
- $sources = array(Turba::getDefaultAddressbook());
- }
- if (empty($sources)) {
- return PEAR::raiseError(_("No address book specified"), 'horde.error');
- }
-
- foreach ($sources as $source) {
- if (empty($source) || !isset($cfgSources[$source])) {
- return PEAR::raiseError(sprintf(_("Invalid address book: %s"), $source), 'horde.error', null, null, $source);
- }
-
- if (empty($uid)) {
- return PEAR::raiseError(_("Invalid contact unique ID"), 'horde.error', null, null, $source);
- }
-
- // Check permissions.
- $driver = &Turba_Driver::singleton($source);
- if (is_a($driver, 'PEAR_Error')) {
- return PEAR::raiseError(sprintf(_("Connection failed: %s"), $driver->getMessage()), 'horde.error', null, null, $source);
- }
- if (!$driver->hasPermission(PERMS_EDIT)) {
- continue;
- }
- $result = $driver->search(array('__uid' => $uid));
- if (is_a($result, 'PEAR_Error')) {
- return $result;
- } elseif (!$result->count()) {
- continue;
- } elseif ($result->count() > 1) {
- return PEAR::raiseError("Multiple contacts found with same unique ID.", 'horde.error', null, null, $source);
- }
-
- $object = $result->objects[0];
-
- switch ($contentType) {
- case 'array':
- break;
-
- case 'text/x-vcard':
- case 'text/vcard':
- case 'text/directory':
- require_once 'Horde/iCalendar.php';
- $iCal = new Horde_iCalendar();
- if (!$iCal->parsevCalendar($content)) {
- return PEAR::raiseError(_("There was an error importing the iCalendar data."));
- }
-
- switch ($iCal->getComponentCount()) {
- case 0:
- return PEAR::raiseError(_("No vCard data was found."));
-
- case 1:
- $content = $iCal->getComponent(0);
- $content = $driver->toHash($content);
- break;
-
- default:
- return PEAR::raiseError(_("Only one vcard supported."));
- }
- break;
-
- default:
- return PEAR::raiseError(sprintf(_("Unsupported Content-Type: %s"), $contentType));
- }
-
- foreach ($content as $attribute => $value) {
- if ($attribute != '__key') {
- $object->setValue($attribute, $value);
- }
- }
-
- return $object->store();
- }
-
- return PEAR::raiseError(_("Object not found"));
-}
-
-/**
- * Returns a contact search result.
- *
- * @param array $names The search filter values
- * @param array $sources The sources to serach in
- * @param array $fields The fields to serach on
- * @param boolean $matchBegin Match word boundaries only
- * @param boolean $forceSource Whether to use the specified sources, even if
- * they have been disabled in the preferences.
- *
- * @return array Hash containing the search results.
- */
-function _turba_search($names = array(), $sources = array(), $fields = array(),
- $matchBegin = false, $forceSource = false)
-{
- require_once dirname(__FILE__) . '/base.php';
- require_once 'Horde/Mime/Address.php';
- global $cfgSources, $attributes, $prefs;
-
- if (!isset($cfgSources) || !is_array($cfgSources) || !count($cfgSources)) {
- return array();
- }
-
- if (!is_array($names)) {
- $names = is_null($names) ? array() : array($names);
- }
-
- if (!$forceSource) {
- // Make sure the selected source is activated in Turba.
- $addressbooks = array_keys(Turba::getAddressBooks());
- foreach (array_keys($sources) as $id) {
- if (!in_array($sources[$id], $addressbooks)) {
- unset($sources[$id]);
- }
- }
- }
-
- // ...and ensure the default source is used as a default.
- if (!count($sources)) {
- $sources = array(Turba::getDefaultAddressBook());
- }
-
- // Read the columns to display from the preferences.
- $sort_columns = Turba::getColumns();
-
- $results = array();
- $seen = array();
- foreach ($sources as $source) {
- // Skip invalid sources.
- if (!isset($cfgSources[$source])) {
- continue;
- }
-
- // Skip sources that aren't browseable if the search is empty.
- if (empty($cfgSources[$source]['browse']) &&
- (!count($names) || (count($names) == 1 && empty($names[0])))) {
- continue;
- }
-
- $driver = &Turba_Driver::singleton($source);
- if (is_a($driver, 'PEAR_Error')) {
- return PEAR::raiseError(sprintf(_("Connection failed: %s"), $driver->getMessage()), 'horde.error', null, null, $source);
- }
-
- // Determine the name of the column to sort by.
- $columns = isset($sort_columns[$source])
- ? $sort_columns[$source] : array();
-
- foreach ($names as $name) {
- $criteria = array();
- if (isset($fields[$source])) {
- foreach ($fields[$source] as $field) {
- $criteria[$field] = trim($name);
- }
- }
- if (count($criteria) == 0) {
- $criteria['name'] = trim($name);
- }
-
- $search = $driver->search($criteria, Turba::getPreferredSortOrder(), 'OR', array(), array(), $matchBegin);
- if (!is_a($search, 'Turba_List')) {
- continue;
- }
-
- while ($ob = $search->next()) {
- if (!$ob->isGroup()) {
- /* Not a group. */
- $att = array('__key' => $ob->getValue('__key'));
- foreach ($ob->driver->getCriteria() as $info_key => $info_val) {
- $att[$info_key] = $ob->getValue($info_key);
- }
- $email = array();
- foreach (array_keys($att) as $key) {
- if (!$ob->getValue($key) ||
- !isset($attributes[$key]) ||
- $attributes[$key]['type'] != 'email') {
- continue;
- }
- $email_val = $ob->getValue($key);
-
- // Multiple addresses support
- if (isset($attributes[$key]['params'])
- && is_array($attributes[$key]['params'])
- && !empty($attributes[$key]['params']['allow_multi'])) {
- $addrs = Horde_Mime_Address::explode($email_val);
- } else {
- $addrs = array($email_val);
- }
-
- foreach ($addrs as $addr) {
- $email[] = trim($addr);
- }
- }
-
- if ($ob->hasValue('name') ||
- !isset($ob->driver->alternativeName)) {
- $display_name = Turba::formatName($ob);
- } else {
- $display_name = $ob->getValue($ob->driver->alternativeName);
- }
- if (count($email)) {
- for ($i = 0; $i < count($email); $i++) {
- $seen_key = trim(Horde_String::lower($display_name)) . '/' . trim(Horde_String::lower($email[$i]));
- if (!empty($seen[$seen_key])) {
- continue;
- }
- $seen[$seen_key] = true;
- if (!isset($results[$name])) {
- $results[$name] = array();
- }
- $results[$name][] = array_merge($att,
- array('id' => $att['__key'],
- 'name' => $display_name,
- 'email' => $email[$i],
- '__type' => 'Object',
- 'source' => $source));
- }
- } else {
- if (!isset($results[$name])) {
- $results[$name] = array();
- }
- $results[$name][] = array_merge($att,
- array('id' => $att['__key'],
- 'name' => $display_name,
- 'email' => null,
- '__type' => 'Object',
- 'source' => $source));
- }
- } else {
- /* Is a distribution list. */
- $listatt = $ob->getAttributes();
- $seeninlist = array();
- $members = $ob->listMembers();
- $listName = $ob->getValue('name');
- if (!is_a($members, 'Turba_List')) {
- continue;
- }
- if ($members->count() > 0) {
- if (!isset($results[$name])) {
- $results[$name] = array();
- }
- $emails = array();
- while ($ob = $members->next()) {
- $att = $ob->getAttributes();
- foreach (array_keys($att) as $key) {
- $value = $ob->getValue($key);
- if (empty($value)) {
- continue;
- }
- if (!is_array($value)) {
- $seen_key = trim(Horde_String::lower($ob->getValue('name')))
- . trim(Horde_String::lower($value));
- } else {
- $seen_key = trim(Horde_String::lower($ob->getValue('name')))
- . trim(Horde_String::lower($value['load']['file']));
- }
- if (isset($attributes[$key]) &&
- $attributes[$key]['type'] == 'email' &&
- empty($seeninlist[$seen_key])) {
- $emails[] = $value;
- $seeninlist[$seen_key] = true;
- }
- }
- }
- $results[$name][] = array('name' => $listName,
- 'email' => implode(', ', $emails),
- 'id' => $listatt['__key'],
- 'source' => $source);
- }
- }
- }
- }
- }
-
- return $results;
-}
-
-/**
- * Retrieves a contact.
- *
- * @param string $source The source name where the contact is stored
- * @param string $objectId The unique id of the contact to retrieve
- *
- * @return array The retrieved contact.
- */
-function _turba_getContact($source = null, $objectId = '')
-{
- require_once dirname(__FILE__) . '/base.php';
- global $cfgSources;
-
- if (!isset($cfgSources) || !is_array($cfgSources) || !count($cfgSources)) {
- return array();
- }
-
- if (isset($cfgSources[$source])) {
- $driver = &Turba_Driver::singleton($source);
- if (is_a($driver, 'PEAR_Error')) {
- return $driver;
- }
-
- $object = $driver->getObject($objectId);
- if (is_a($object, 'PEAR_Error')) {
- return $object;
- }
-
- $attributes = array();
- foreach ($cfgSources[$source]['map'] as $field => $map) {
- $attributes[$field] = $object->getValue($field);
- }
- return $attributes;
- }
-
- return array();
-}
-
-/**
- * Retrieves a set of contacts from a single source.
- *
- * @param string $source The source name where the contact is stored
- * @param array $objectIds The unique ids of the contact to retrieve.
- *
- * @return mixed The retrieved contact | PEAR_Error
- */
-function _turba_getContacts($source = '', $objectIds = array())
-{
- require_once dirname(__FILE__) . '/base.php';
- global $cfgSources;
- $results = array();
- if (!is_array($objectIds)) {
- $objectIds = array($objectIds);
- }
-
- if (!isset($cfgSources) || !is_array($cfgSources) || !count($cfgSources)) {
- return array();
- }
-
- if (isset($cfgSources[$source])) {
- $driver = &Turba_Driver::singleton($source);
- if (is_a($driver, 'PEAR_Error')) {
- return $driver;
- }
-
- $objects = $driver->getObjects($objectIds);
- if (is_a($objects, 'PEAR_Error')) {
- return $objects;
- }
-
- foreach ($objects as $object) {
- $attributes = array();
- foreach ($cfgSources[$source]['map'] as $field => $map) {
- $attributes[$field] = $object->getValue($field);
- }
- $results[] = $attributes;
- }
- }
-
- return $results;
-}
-
-/**
- * Retrieves a list of all possible values of a field in specified source(s).
- *
- * @param string $field Field name to check
- * @param array $sources Array containing the sources to look in
- *
- * @return mixed An array of fields and possible values | PEAR_Error
- */
-function _turba_getAllAttributeValues($field = '', $sources = array())
-{
- require_once dirname(__FILE__) . '/base.php';
- global $cfgSources;
-
- if (!isset($cfgSources) || !is_array($cfgSources) || !count($cfgSources)) {
- return array();
- }
-
- if (!count($sources)) {
- $sources = array(Turba::getDefaultAddressBook());
- }
-
- $results = array();
- foreach ($sources as $source) {
- if (isset($cfgSources[$source])) {
- $driver = &Turba_Driver::singleton($source);
- if (is_a($driver, 'PEAR_Error')) {
- return PEAR::raiseError(sprintf(_("Connection failed: %s"), $driver->getMessage()), 'horde.error', null, null, $source);
- }
-
- $res = $driver->search(array());
- if (!is_a($res, 'Turba_List')) {
- return PEAR::raiseError(_("Search failed"), 'horde.error', null, null, $source);
- }
-
- while ($ob = $res->next()) {
- if ($ob->hasValue($field)) {
- $results[$source . ':' . $ob->getValue('__key')] = array(
- 'name' => $ob->getValue('name'),
- 'email' => $ob->getValue('email'),
- $field => $ob->getValue($field));
- }
- }
- }
- }
-
- return $results;
-}
-
-/**
- * Retrieves a list of available time objects categories
- *
- * @return array An array of all configured time object categories.
- */
-function _turba_listTimeObjectCategories()
-{
- include dirname(__FILE__) . '/../config/attributes.php';
- include dirname(__FILE__) . '/../config/sources.php';
- $categories = array();
- foreach ($attributes as $key => $attribute) {
- if ($attribute['type'] == 'monthdayyear' &&
- !empty($attribute['time_object_label'])) {
-
- foreach ($cfgSources as $source) {
- if (!empty($source['map'][$key])) {
- $categories[$key] = $attribute['time_object_label'];
- break;
- }
- }
- }
- }
-
-
-
- return $categories;
-}
-
-/**
- * Lists birthdays and/or anniversaries as time objects.
- *
- * @param array $time_categories The time categories (from
- * listTimeObjectCategories) to list.
- * @param mixed $start The start date of the period.
- * @param mixed $end The end date of the period.
- *
- * @return mixed An array of timeObject results || PEAR_Error
- */
-function _turba_listTimeObjects($time_categories, $start, $end)
-{
- require_once dirname(__FILE__) . '/base.php';
- global $cfgSources;
-
- $start = new Horde_Date($start);
- $end = new Horde_Date($end);
-
- if (!isset($cfgSources) || !is_array($cfgSources) || !count($cfgSources)) {
- return array();
- }
-
- $objects = array();
- foreach ($cfgSources as $name => $source) {
- // Check if we even have to load the driver.
- $check = array();
- foreach ($time_categories as $category) {
- if (!empty($source['map'][$category])) {
- $check[] = $category;
- }
- }
- if (!count($check)) {
- continue;
- }
- $driver = &Turba_Driver::singleton($name);
- if (is_a($driver, 'PEAR_Error')) {
- return PEAR::raiseError(sprintf(_("Connection failed: %s"),
- $driver->getMessage()), 'horde.error',
- null, null, $name);
- }
- foreach ($check as $category) {
- $new_objects = $driver->listTimeObjects($start, $end, $category);
- if (is_a($new_objects, 'PEAR_Error')) {
- return $new_objects;
- }
- $objects = array_merge($objects, $new_objects);
- }
- }
-
- return $objects;
-}
-
-/**
- * Returns the client source name
- *
- * @return string The name of the source to use with the clients api.
- */
-function _turba_getClientSource()
-{
- return !empty($GLOBALS['conf']['client']['addressbook']) ? $GLOBALS['conf']['client']['addressbook'] : false;
-}
-
-/**
- * Returns the availabble client fields
- *
- * @return mixed An array describing the fields | PEAR_Error
- */
-function _turba_clientFields()
-{
- return _turba_fields($GLOBALS['conf']['client']['addressbook']);
-}
-
-/**
- * Returns a contact from the client source.
- *
- * @param string $objectId Client unique ID
- *
- * @return mixed Array of client data | PEAR_Error
- */
-function _turba_getClient($objectId = '')
-{
- return _turba_getContact($GLOBALS['conf']['client']['addressbook'],
- $objectId);
-}
-
-/**
- * Returns mulitple contacts from the client source
- *
- * @param array $objectIds client unique ids
- *
- * @return mixed An array of clients data | PEAR_Error
- */
-function _turba_getClients($objectIds = array())
-{
- return _turba_getContacts($GLOBALS['conf']['client']['addressbook'],
- $objectIds);
-}
-
-/**
- * Adds a client to the client source
- *
- * @param array $attributes Array containing the client attributes
- */
-function _turba_addClient($attributes = array())
-{
- return _turba_import($attributes, 'array', _turba_getClientSource());
-}
-
-/**
- * Updates client data
- *
- * @param string $objectId The unique id of the client
- * @param array $attributes An array of client attributes
- *
- * @return boolean
- */
-function _turba_updateClient($objectId = '', $attributes = array())
-{
- return _turba_replace(_turba_getClientSource() . ':' . $objectId,
- $attributes, 'array');
-}
-
-/**
- * Deletes a client
- *
- * @param string $objectId The unique id of the client
- *
- * @return boolean
- */
-function _turba_deleteClient($objectId = '')
-{
- return _turba_delete(_turba_getClientSource() . ':' . $objectId);
-}
-
-/**
- * Search for clients
- *
- * @param array $names The search filter values
- * @param array $fields The fields to serach in
- * @param boolean $matchBegin Match word boundaries only
- *
- * @return mixed A hash containing the search results | PEAR_Error
- */
-function _turba_searchClients($names = array(), $fields = array(), $matchBegin = false)
-{
- return _turba_search(
- $names,
- array($GLOBALS['conf']['client']['addressbook']),
- array($GLOBALS['conf']['client']['addressbook'] => $fields),
- $matchBegin,
- true);
-}
-
-/**
- * Sets the value of the specified attribute of a contact
- *
- * @param string $address Contact email address
- * @param string $name Contact name
- * @param string $field Field to update
- * @param string $value Field value to set
- * @param string $source Contact source
- *
- * @return mixed The new __key value on success | PEAR_Error on failure
- */
-function _turba_addField($address = '', $name = '', $field = '', $value = '',
- $source = '')
-{
- require_once dirname(__FILE__) . '/base.php';
- global $cfgSources;
-
- if (empty($source) || !isset($cfgSources[$source])) {
- return PEAR::raiseError(sprintf(_("Invalid address book: %s"), $source), 'horde.error', null, null, $source);
- }
-
- if (empty($address)) {
- return PEAR::raiseError(_("Invalid email"), 'horde.error', null, null, $source);
- }
-
- if (empty($name)) {
- return PEAR::raiseError(_("Invalid name"), 'horde.error', null, null, $source);
- }
-
- if (empty($value)) {
- return PEAR::raiseError(_("Invalid entry"), 'horde.error', null, null, $source);
- }
-
- $driver = &Turba_Driver::singleton($source);
- if (is_a($driver, 'PEAR_Error')) {
- return PEAR::raiseError(sprintf(_("Connection failed: %s"), $driver->getMessage()), 'horde.error', null, null, $source);
- }
-
- if (!$driver->hasPermission(PERMS_EDIT)) {
- return PEAR::raiseError(_("Permission denied"), 'horde.error', null, null, $source);
- }
-
- $res = $driver->search(array('email' => trim($address)), null, 'AND');
- if (is_a($res, 'PEAR_Error')) {
- return PEAR::raiseError(sprintf(_("Search failed: %s"), $res->getMessage()), 'horde.message', null, null, $source);
- }
-
- if ($res->count() > 1) {
- $res2 = $driver->search(array('email' => trim($address), 'name' => trim($name)), null, 'AND');
- if (is_a($res2, 'PEAR_Error')) {
- return PEAR::raiseError(sprintf(_("Search failed: %s"), $res2->getMessage()), 'horde.message', null, null, $source);
- }
-
- if (!$res2->count()) {
- return PEAR::raiseError(sprintf(_("Multiple persons with address [%s], but none with name [%s] already exist"), trim($address), trim($name)), 'horde.message', null, null, $source);
- }
-
- $res3 = $driver->search(array('email' => $address, 'name' => $name, $field => $value));
- if (is_a($res3, 'PEAR_Error')) {
- return PEAR::raiseError(sprintf(_("Search failed: %s"), $res3->getMessage()), 'horde.message', null, null, $source);
- }
-
- if ($res3->count()) {
- return PEAR::raiseError(sprintf(_("This person already has a %s entry in the address book"), $field), 'horde.message', null, null, $source);
- }
-
- $ob = $res2->next();
- $ob->setValue($field, $value);
- $ob->store();
- } elseif ($res->count() == 1) {
- $res4 = $driver->search(array('email' => $address, $field => $value));
- if (is_a($res4, 'PEAR_Error')) {
- return PEAR::raiseError(sprintf(_("Search failed: %s"), $res4->getMessage()), 'horde.message', null, null, $source);
- }
-
- if ($res4->count()) {
- return PEAR::raiseError(sprintf(_("This person already has a %s entry in the address book"), $field), 'horde.message', null, null, $source);
- }
-
- $ob = $res->next();
- $ob->setValue($field, $value);
- $ob->store();
- } else {
- return $driver->add(array('email' => $address, 'name' => $name, $field => $value, '__owner' => Horde_Auth::getAuth()));
- }
-
- return;
-}
-
-/**
- * Returns a field value
- *
- * @param string $address Contact email address
- * @param string $field Field to get
- * @param array $sources Sources to check
- * @param boolean $strict Match the email address strictly
- * @param boolean $multiple Return more than one entry if found and true,
- * return an error if this is false.
- *
- * @return mixed An array of field value(s) | PEAR_Error on failure.
- */
-function _turba_getField($address = '', $field = '', $sources = array(),
- $strict = false, $multiple = false)
-{
- require_once dirname(__FILE__) . '/base.php';
- global $cfgSources;
-
- if (empty($address)) {
- return PEAR::raiseError(_("Invalid email"), 'horde.error');
- }
-
- if (!isset($cfgSources) || !is_array($cfgSources) || !count($cfgSources)) {
- return array();
- }
-
- if (!count($sources)) {
- $sources = array(Turba::getDefaultAddressbook());
- }
-
- $result = array();
- foreach ($sources as $source) {
- if (!isset($cfgSources[$source])) {
- continue;
- }
-
- $driver = &Turba_Driver::singleton($source);
- if (is_a($driver, 'PEAR_Error')) {
- continue;
- }
-
- $list = $driver->search(array('email' => $address), null, 'AND', array(), $strict ? array('email') : array());
- if (!is_a($list, 'Turba_List')) {
- continue;
- }
-
- while ($ob = $list->next()) {
- if ($ob->hasValue($field)) {
- $result[] = $ob->getValue($field);
- }
- }
- }
-
- if (count($result) > 1) {
- if ($multiple) {
- return $result;
- } else {
- return PEAR::raiseError(_("More than 1 entry found"), 'horde.warning', null, null, $source);
- }
- } elseif (empty($result)) {
- return PEAR::raiseError(sprintf(_("No %s entry found for %s"), $field, $address), 'horde.warning', null, null, $source);
- }
- return reset($result);
-}
-
-/**
- * Deletes a field value
- *
- * @param string $address Contact email address
- * @param string $field Field to delete value for
- * @param array $sources Sources to delete value from
- *
- * @return boolean
- */
-function _turba_deleteField($address = '', $field = '', $sources = array())
-{
- require_once dirname(__FILE__) . '/base.php';
- global $cfgSources;
-
- if (empty($address)) {
- return PEAR::raiseError(_("Invalid email"), 'horde.error');
- }
-
- if (!isset($cfgSources) || !is_array($cfgSources) || !count($cfgSources)) {
- return array();
- }
-
- if (count($sources) == 0) {
- $sources = array(Turba::getDefaultAddressbook());
- }
-
- $success = false;
-
- foreach ($sources as $source) {
- if (isset($cfgSources[$source])) {
- $driver = &Turba_Driver::singleton($source);
- if (is_a($driver, 'PEAR_Error')) {
- continue;
- }
- if (!$driver->hasPermission(PERMS_EDIT)) {
- continue;
- }
-
- $res = $driver->search(array('email' => $address));
- if (is_a($res, 'Turba_List')) {
- if ($res->count() > 1) {
- continue;
- }
-
- $ob = $res->next();
- if (is_object($ob) && $ob->hasValue($field)) {
- $ob->setValue($field, '');
- $ob->store();
- $success = true;
- }
- }
- }
- }
-
- if (!$success) {
- return PEAR::raiseError(sprintf(_("No %s entry found for %s"), $field, $address), 'horde.error');
- }
-
- return;
-}
+++ /dev/null
-<?php define('TURBA_VERSION', 'H3 (3.0-git)') ?>
/* Turba version. */
$module = 'Turba';
-require_once TURBA_BASE . '/lib/version.php';
-$module_version = TURBA_VERSION;
+require_once dirname(__FILE__) . '/lib/Api.php';
+$api = new Turba_Api();
+$module_version = $api->version;
require TEST_TEMPLATES . 'header.inc';
require TEST_TEMPLATES . 'version.inc';