1: <?php
2: /**
3: * Horde_Share_Kolab:: provides the Kolab backend for the horde share driver.
4: *
5: * PHP version 5
6: *
7: * @category Horde
8: * @package Share
9: * @author Stuart Binge <omicron@mighty.co.za>
10: * @author Gunnar Wrobel <wrobel@pardus.de>
11: * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
12: * @link http://pear.horde.org/index.php?package=Share
13: */
14:
15: /**
16: * Horde_Share_Kolab:: provides the Kolab backend for the horde share driver.
17: *
18: * Copyright 2004-2012 Horde LLC (http://www.horde.org/)
19: *
20: * See the enclosed file COPYING for license information (LGPL). If you
21: * did not receive this file, see http://www.horde.org/licenses/lgpl21.
22: *
23: * @category Horde
24: * @package Share
25: * @author Stuart Binge <omicron@mighty.co.za>
26: * @author Gunnar Wrobel <wrobel@pardus.de>
27: * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
28: * @link http://pear.horde.org/index.php?package=Share
29: */
30: class Horde_Share_Kolab extends Horde_Share_Base
31: {
32: const VERSION = 1;
33:
34: /**
35: * The Kolab storage handler
36: *
37: * @var Horde_Kolab_Storage
38: */
39: private $_storage;
40:
41: /**
42: * The folder type in the storage backend.
43: *
44: * @var string
45: */
46: private $_type;
47:
48: /**
49: * A map of IDs to folder names.
50: *
51: * @var array
52: */
53: private $_id_map = array();
54:
55: /**
56: * Constructor.
57: *
58: * @param string $app The application that the shares belong
59: * to
60: * @param string $user The current user
61: * @param Horde_Perms_Base $perms The permissions object
62: * @param Horde_Group_Base $groups The Horde_Group driver.
63: *
64: */
65: public function __construct($app, $user, Horde_Perms_Base $perms,
66: Horde_Group_Base $groups)
67: {
68: switch ($app) {
69: case 'mnemo':
70: case 'jonah':
71: $this->_type = 'note';
72: break;
73: case 'kronolith':
74: $this->_type = 'event';
75: break;
76: case 'turba':
77: $this->_type = 'contact';
78: break;
79: case 'nag':
80: $this->_type = 'task';
81: break;
82: default:
83: throw new Horde_Share_Exception(sprintf(Horde_Share_Translation::t("The Horde/Kolab integration engine does not support \"%s\""), $app));
84: }
85: parent::__construct($app, $user, $perms, $groups);
86: }
87:
88: /**
89: * Set the Kolab storage backend.
90: *
91: * @param Horde_Kolab_Storage $storage The Kolab storage handler.
92: *
93: * @return NULL
94: */
95: public function setStorage(Horde_Kolab_Storage $storage)
96: {
97: $this->_storage = $storage;
98: }
99:
100: /**
101: * Return the Kolab storage backend associated with this driver.
102: *
103: * @return Horde_Kolab_Storage The Kolab storage driver.
104: */
105: public function getStorage()
106: {
107: if ($this->_storage === null) {
108: throw new Horde_Share_Exception('The storage backend has not yet been set!');
109: }
110: return $this->_storage;
111: }
112:
113: /**
114: * Return the Kolab storage folder list handler.
115: *
116: * @return Horde_Kolab_Storage_List The folder list handler.
117: */
118: public function getList()
119: {
120: return $this->getStorage()->getList();
121: }
122:
123: /**
124: * Return the type of folder this share driver will access in the Kolab
125: * storage backend (depends on the application calling the share driver).
126: *
127: * @return string
128: */
129: public function getType()
130: {
131: return $this->_type;
132: }
133:
134: /**
135: * Encode a share ID.
136: *
137: * @todo: In Horde3 share IDs were not properly escaped everywhere and it
138: * made sense to escape them here just in case they are placed in a
139: * URL. Needs checking in Horde4.
140: *
141: * @param string $id The ID to be encoded.
142: *
143: * @return string The encoded ID.
144: */
145: private function _idEncode($id)
146: {
147: $folder = $this->getList()->getFolder($id);
148: if (!method_exists($folder, 'getPrefix')) {
149: //@todo BC (remove this option later)
150: return $this->constructId($folder->getOwner(), $folder->getSubpath());
151: } else {
152: return $this->constructId($folder->getOwner(), $folder->getSubpath(), $folder->getPrefix());
153: }
154: }
155:
156: /**
157: * Construct the ID from the owner name and the folder subpath.
158: *
159: * @param string $owner The share owner.
160: * @param string $name The name of the folder without the namespace prefix.
161: * @param string $prefix The namespace prefix.
162: *
163: * @return string The encoded ID.
164: */
165: public function constructId($owner, $name, $prefix = null)
166: {
167: return Horde_Url::uriB64Encode(serialize(array($owner, $name, $prefix)));
168: }
169:
170: /**
171: * Construct the Kolab storage folder name based on the share name and owner
172: * attributes.
173: *
174: * @param string $owner The owner of the share.
175: * @param string $subpath The folder subpath.
176: * @param string $prefix The namespace prefix.
177: *
178: * @return string The folder name for the Kolab backend.
179: */
180: public function constructFolderName($owner, $subpath, $prefix = null)
181: {
182: return $this->getList()
183: ->getNamespace()
184: ->constructFolderName($owner, $subpath, $prefix);
185: }
186:
187: /**
188: * Retrieve namespace information for a folder name.
189: *
190: * @param string $folder The folder name.
191: *
192: * @since Horde_Share 1.2.0
193: *
194: * @return array A list of namespace prefix, the delimiter and the folder
195: * subpath.
196: */
197: public function getFolderNameElements($folder)
198: {
199: $ns = $this->getList()->getNamespace()->matchNamespace($folder);
200: return array(
201: $ns->getName(), $ns->getDelimiter(), $ns->getSubpath($folder)
202: );
203: }
204:
205: /**
206: * Decode a share ID.
207: *
208: * @param string $id The ID to be decoded.
209: *
210: * @return string The decoded ID.
211: */
212: private function _idDecode($id)
213: {
214: if (!isset($this->_id_map[$id])) {
215: $result = $this->_idDeconstruct($id);
216: $this->_id_map[$id] = $this->constructFolderName(
217: $result[0],
218: $result[1],
219: isset($result[2]) ? $result[2] : null
220: );
221: }
222: return $this->_id_map[$id];
223: }
224:
225: /**
226: * Deconstruct the ID elements from the ID string
227: *
228: * @param string $id The ID to be deconstructed.
229: *
230: * @return array A tuple of (owner, folder subpath).
231: */
232: private function _idDeconstruct($id)
233: {
234: if (!$decoded_id = Horde_Url::uriB64Decode($id)) {
235: $msg = sprintf('Share id %s is invalid.', $id);
236: $this->_logger->err($msg);
237: throw new Horde_Exception_NotFound($msg);
238: }
239: if (!$sid = @unserialize($decoded_id)) {
240: $msg = sprintf('Share id %s is invalid.', $decoded_id);
241: $this->_logger->err($msg);
242: throw new Horde_Exception_NotFound($msg);
243: }
244: return $sid;
245: }
246:
247:
248: /**
249: * Returns a Horde_Share_Object_Kolab object corresponding to the given
250: * share name, with the details retrieved appropriately.
251: *
252: * @param string $id The id of the share to retrieve.
253: * @param array $data The share data.
254: *
255: * @return Horde_Share_Object The requested share.
256: */
257: private function _createObject($id, array $data = array())
258: {
259: $object = new Horde_Share_Object_Kolab($id, $this->_groups, $data);
260: $this->initShareObject($object);
261: return $object;
262: }
263:
264: /**
265: * Returns a Horde_Share_Object_Kolab object corresponding to the given
266: * share name, with the details retrieved appropriately.
267: *
268: * @param string $name The name of the share to retrieve.
269: *
270: * @return Horde_Share_Object The requested share.
271: * @throws Horde_Exception_NotFound
272: * @throws Horde_Share_Exception
273: */
274: protected function _getShare($name)
275: {
276: $list = $this->getList()
277: ->getQuery()
278: ->dataByType($this->_type);
279:
280: $query = $this->getList()
281: ->getQuery(Horde_Kolab_Storage_List::QUERY_SHARE);
282:
283: foreach ($list as $folder => $folder_data) {
284: $data = $query->getParameters($folder);
285: if (isset($data['share_name']) && $data['share_name'] == $name) {
286: return $this->getShareById(
287: $this->constructId(
288: $folder_data['owner'],
289: $folder_data['subpath'],
290: isset($folder_data['prefix']) ? $folder_data['prefix'] : null
291: )
292: );
293: }
294: }
295: return $this->getShareById($name);
296: }
297:
298: /**
299: * Returns a Horde_Share_Object_sql object corresponding to the given
300: * unique ID, with the details retrieved appropriately.
301: *
302: * @param integer $id The id of the share to retrieve.
303: *
304: * @return Horde_Share_Object_sql The requested share.
305: * @throws Horde_Share_Exception, Horde_Exception_NotFound
306: */
307: protected function _getShareById($id)
308: {
309: $list = $this->getList()
310: ->getQuery()
311: ->dataByType($this->_type);
312:
313: if (!isset($list[$this->_idDecode($id)])) {
314: $msg = sprintf('Share id %s not found', $id);
315: $this->_logger->err($msg);
316: throw new Horde_Exception_NotFound($msg);
317: }
318:
319: $query = $this->getList()
320: ->getQuery(Horde_Kolab_Storage_List::QUERY_SHARE);
321:
322: $data = array_merge(
323: $query->getParameters($this->_idDecode($id)),
324: $list[$this->_idDecode($id)]
325: );
326: $data['desc'] = $query->getDescription($this->_idDecode($id));
327: if (isset($data['parent'])) {
328: try {
329: $data['parent'] = $this->_idEncode($data['parent']);
330: } catch (Horde_Kolab_Storage_Exception $e) {
331: unset($data['parent']);
332: }
333: }
334: return $this->_createObject($id, $data);
335: }
336:
337: /**
338: * Returns an array of Horde_Share_Object_kolab objects corresponding to
339: * the requested folders.
340: *
341: * @param string $ids The ids of the shares to fetch.
342: *
343: * @return array An array of Horde_Share_Object_kolab objects.
344: */
345: protected function _getShares(array $ids)
346: {
347: $objects = array();
348: foreach ($ids as $id) {
349: $share = $this->_getShareById($id);
350: $objects[$share->getName()] = $share;
351: }
352: return $objects;
353: }
354:
355: /**
356: * Checks if a share exists in the system.
357: *
358: * @param string $share The share to check.
359: *
360: * @return boolean True if the share exists.
361: * @throws Horde_Share_Exception
362: */
363: protected function _exists($share)
364: {
365: try {
366: $this->getShare($share);
367: return true;
368: } catch (Horde_Exception_NotFound $e) {
369: return false;
370: }
371: }
372:
373: /**
374: * Check that a share id exists in the system.
375: *
376: * @param integer $id The share id
377: *
378: * @return boolean True if the share exists.
379: */
380: protected function _idExists($id)
381: {
382: return in_array(
383: $this->_idDecode($id),
384: $this->getList()
385: ->getQuery()
386: ->listByType($this->_type)
387: );
388: }
389:
390: /**
391: * Returns an array of all shares that $userid has access to.
392: *
393: * @param string $userid The userid of the user to check access for.
394: * @param array $params See listShares().
395: *
396: * @return array The shares the user has access to.
397: */
398: protected function _listShares($userid, array $params = array())
399: {
400: $stamp = $this->getList()->getQuery()->getStamp();
401: $key = md5(serialize(array($userid, $params, $stamp)));
402:
403: if (!isset($this->_listcache[$key])) {
404: $shares = array_map(
405: array($this, '_idEncode'),
406: $this->getList()
407: ->getQuery()
408: ->listByType($this->_type)
409: );
410: $remove = array();
411: if ($params['perm'] != Horde_Perms::SHOW || empty($userid)) {
412: foreach ($shares as $share) {
413: $object = $this->_getShareById($share);
414: if (!$object->hasPermission($userid, $params['perm'], $object->get('owner'))) {
415: $remove[] = $share;
416: }
417: }
418: }
419: if (isset($params['parent'])) {
420: foreach ($shares as $share) {
421: $object = $this->getShareById($share);
422: if ($params['parent'] instanceOf Horde_Share_Object) {
423: $parent = $params['parent'];
424: } else {
425: $parent = $this->getShare($params['parent']);
426: }
427: if (!$object->getParent() || $object->getParent()->getId() != $parent->getId()) {
428: $remove[] = $share;
429: }
430: }
431: }
432: if (isset($params['all_levels']) && empty($params['all_levels'])) {
433: foreach ($shares as $share) {
434: $object = $this->getShareById($share);
435: $parent = $object->get('parent');
436: if (!empty($parent) && in_array($parent, $shares)) {
437: $remove[] = $share;
438: }
439: }
440: }
441: if (isset($params['attributes'])) {
442: if (!is_array($params['attributes'])) {
443: $attributes = array('owner' => $params['attributes']);
444: } else {
445: $attributes = $params['attributes'];
446: }
447: foreach ($shares as $share) {
448: $object = $this->getShareById($share);
449: foreach ($attributes as $key => $value) {
450: if ($object->get($key) != $value) {
451: $remove[] = $share;
452: }
453: }
454: }
455: }
456: if (!empty($remove)) {
457: $shares = array_diff($shares, $remove);
458: }
459: if (isset($params['sort_by'])) {
460: if ($params['sort_by'] == 'id') {
461: sort($shares);
462: } else {
463: $sorted = array();
464: foreach ($shares as $share) {
465: $object = $this->getShareById($share);
466: $key = $object->get($params['sort_by']);
467: $sorted[$key] = $object->getId();
468: }
469: ksort($sorted);
470: $shares = array_values($sorted);
471: }
472: }
473: if (!empty($params['direction'])) {
474: $shares = array_reverse($shares);
475: }
476: if (isset($params['from']) && !empty($params['from'])) {
477: $shares = array_slice($shares, $params['from']);
478: }
479: if (isset($params['count']) && !empty($params['count'])) {
480: $shares = array_slice($shares, 0, $params['count']);
481: }
482: $this->_listcache[$key] = $shares;
483: }
484: return $this->_listcache[$key];
485: }
486:
487: /**
488: * Returns the count of all shares that $userid has access to.
489: *
490: * @param string $userid The userid of the user to check access for.
491: * @param integer $perm The level of permissions required.
492: * @param mixed $attributes Restrict the shares counted to those
493: * matching $attributes. An array of
494: * attribute/values pairs or a share owner
495: * username.
496: * @param mixed $parent The share to start searching from
497: * (Horde_Share_Object, share_id, or null)
498: * @param boolean $allLevels Return all levels, or just the direct
499: * children of $parent?
500: *
501: * @return integer Number of shares the user has access to.
502: * @throws Horde_Share_Exception
503: */
504: public function countShares($userid, $perm = Horde_Perms::SHOW,
505: $attributes = null, $parent = null, $allLevels = true)
506: {
507: return count(
508: $this->listShares(
509: $userid,
510: array(
511: 'perm' => $perm,
512: 'attributes' => $attributes,
513: 'parent' => $parent,
514: 'all_levels' => $allLevels
515: )
516: )
517: );
518: }
519:
520: /**
521: * Returns an array of all system shares.
522: *
523: * @return array All system shares.
524: */
525: public function listSystemShares()
526: {
527: $shares = array_map(
528: array($this, '_idEncode'),
529: $this->getList()
530: ->getQuery()
531: ->listByType($this->_type)
532: );
533: $result = array();
534: foreach ($shares as $share) {
535: $object = $this->_getShareById($share);
536: //@todo: Remove "null" check as this is only required for BC
537: if ($object->get('owner') === false ||
538: $object->get('owner') === null) {
539: $result[$object->getName()] = $object;
540: }
541: }
542: return $result;
543: }
544:
545: /**
546: * Lists *all* shares for the current app/share, regardless of
547: * permissions.
548: *
549: * For the Kolab backend this cannot work in the same way as for the SQL
550: * based backend. Permissions are always handled by the backend automatically (IMAP ACLs) and cannot be disabled.
551: *
552: * listAllShares() is apparently used during command line scipts where it
553: * represents administrator access. This is possible on Kolab by using the
554: * "manager" user. In that case a standard listShares() authenticated as
555: * "manager" should be sufficient.
556: *
557: * @return array All shares for the current app/share.
558: */
559: protected function _listAllShares()
560: {
561: $shares = array_map(
562: array($this, '_idEncode'),
563: $this->getList()
564: ->getQuery()
565: ->listByType($this->_type)
566: );
567: $result = array();
568: foreach ($shares as $share) {
569: $object = $this->_getShareById($share);
570: $result[$object->getName()] = $object;
571: }
572: return $result;
573: }
574:
575: /**
576: * Returns a new share object.
577: *
578: * @param string $name The share's name.
579: *
580: * @return Horde_Share_Object_kolab A new share object.
581: */
582: protected function _newShare($name)
583: {
584: return $this->_createObject(
585: null,
586: array(
587: 'type' => $this->_type,
588: 'share_name' => $name
589: )
590: );
591: }
592:
593: /**
594: * Adds a share to the shares system.
595: *
596: * The share must first be created with Horde_Share::newShare(),
597: * and have any initial details added to it, before this function is
598: * called.
599: *
600: * @param Horde_Share_Object $share The new share object.
601: */
602: protected function _addShare(Horde_Share_Object $share)
603: {
604: $share->save();
605: }
606:
607: /**
608: * Removes a share from the shares system permanently.
609: *
610: * @param Horde_Share_Object $share The share to remove.
611: */
612: protected function _removeShare(Horde_Share_Object $share)
613: {
614: $this->getList()->deleteFolder($this->_idDecode($share->getId()));
615: }
616:
617: /**
618: * Renames a share in the shares system.
619: *
620: * @param Horde_Share_Object $share The share to rename.
621: * @param string $name The share's new name.
622: *
623: * @throws Horde_Share_Exception
624: */
625: protected function _renameShare(Horde_Share_Object $share, $name)
626: {
627: $share->set('share_name', $name);
628: }
629:
630: /**
631: * Retrieve the Kolab specific access rights for a share.
632: *
633: * @param string $id The share ID.
634: *
635: * @return An array of rights.
636: */
637: public function getAcl($id)
638: {
639: return $this->getList()
640: ->getQuery(Horde_Kolab_Storage_List::QUERY_ACL)
641: ->getAcl(
642: $this->_idDecode($id)
643: );
644: }
645:
646: /**
647: * Set the Kolab specific access rights for a share.
648: *
649: * @param string $id The share ID.
650: * @param string $user The user to set the ACL for.
651: * @param string $acl The ACL.
652: *
653: * @return NULL
654: */
655: public function setAcl($id, $user, $acl)
656: {
657: $this->getList()
658: ->getQuery(Horde_Kolab_Storage_List::QUERY_ACL)
659: ->setAcl(
660: $this->_idDecode($id), $user, $acl
661: );
662: }
663:
664: /**
665: * Delete Kolab specific access rights for a share.
666: *
667: * @param string $id The share ID.
668: * @param string $user The user to delete the ACL for
669: *
670: * @return NULL
671: */
672: public function deleteAcl($id, $user)
673: {
674: $this->getList()
675: ->getQuery(Horde_Kolab_Storage_List::QUERY_ACL)
676: ->deleteAcl(
677: $this->_idDecode($id), $user
678: );
679: }
680:
681: /**
682: * Save share data to the storage backend.
683: *
684: * @param string $id The share id.
685: * @param string $old_id The old share id.
686: * @param array $data The share data.
687: *
688: * @return NULL
689: */
690: public function save($id, $old_id, $data)
691: {
692: if ($old_id === null) {
693: $this->getList()->createFolder(
694: $this->_idDecode($id), $this->_type
695: );
696: } elseif ($id != $old_id) {
697: $this->getList()->renameFolder(
698: $this->_idDecode($old_id), $this->_idDecode($id), $this->_type
699: );
700: }
701:
702: $query = $this->getList()
703: ->getQuery(Horde_Kolab_Storage_List::QUERY_SHARE);
704: if (isset($data['desc'])) {
705: $query->setDescription($this->_idDecode($id), $data['desc']);
706: }
707: unset(
708: $data['desc'],
709: $data['owner'],
710: $data['name'],
711: $data['default'],
712: $data['parent'],
713: $data['type'],
714: $data['delimiter'],
715: $data['prefix'],
716: $data['subpath'],
717: $data['folder']
718: );
719: $query->setParameters($this->_idDecode($id), $data);
720: }
721: }
722: