1: <?php
2: /**
3: * The basic handler for data objects in a Kolab storage folder.
4: *
5: * PHP version 5
6: *
7: * @category Kolab
8: * @package Kolab_Storage
9: * @author Gunnar Wrobel <wrobel@pardus.de>
10: * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
11: * @link http://pear.horde.org/index.php?package=Kolab_Storage
12: */
13:
14: /**
15: * The basic handler for data objects in a Kolab storage folder.
16: *
17: * Copyright 2011-2012 Horde LLC (http://www.horde.org/)
18: *
19: * See the enclosed file COPYING for license information (LGPL). If you
20: * did not receive this file, see http://www.horde.org/licenses/lgpl21.
21: *
22: * @category Kolab
23: * @package Kolab_Storage
24: * @author Gunnar Wrobel <wrobel@pardus.de>
25: * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
26: * @link http://pear.horde.org/index.php?package=Kolab_Storage
27: */
28: class Horde_Kolab_Storage_Data_Base
29: implements Horde_Kolab_Storage_Data, Horde_Kolab_Storage_Data_Query
30: {
31: /**
32: * The link to the parent folder object.
33: *
34: * @var Horde_Kolab_Folder
35: */
36: private $_folder;
37:
38: /**
39: * The driver for accessing the Kolab storage system.
40: *
41: * @var Horde_Kolab_Storage_Driver
42: */
43: private $_driver;
44:
45: /**
46: * The factory for generating additional resources.
47: *
48: * @var Horde_Kolab_Storage_Factory
49: */
50: private $_factory;
51:
52: /**
53: * The folder type.
54: *
55: * @var string
56: */
57: private $_type;
58:
59: /**
60: * The version of the data.
61: *
62: * @var int
63: */
64: private $_version;
65:
66: /**
67: * The list of registered queries.
68: *
69: * @var array
70: */
71: private $_queries = array();
72:
73: /**
74: * Constructor.
75: *
76: * @param Horde_Kolab_Storage_Folder $folder The folder to retrieve the
77: * data from.
78: * @param Horde_Kolab_Storage_Driver $driver The primary connection driver.
79: * @param Horde_Kolab_Storage_Factory $factory The factory.
80: * @param string $type The type of data we want to
81: * access in the folder.
82: * @param int $version Format version of the object
83: * data.
84: */
85: public function __construct(Horde_Kolab_Storage_Folder $folder,
86: Horde_Kolab_Storage_Driver $driver,
87: Horde_Kolab_Storage_Factory $factory,
88: $type = null,
89: $version = 1)
90: {
91: $this->_folder = $folder;
92: $this->_driver = $driver;
93: $this->_factory = $factory;
94: $this->_type = $type;
95: $this->_version = $version;
96: }
97:
98: /**
99: * Return the folder path for this data handler.
100: *
101: * @since Horde_Kolab_Storage 1.1.0
102: *
103: * @return string The folder path.
104: */
105: public function getPath()
106: {
107: return $this->_folder->getPath();
108: }
109:
110: /**
111: * Return the ID of the current user.
112: *
113: * @since Horde_Kolab_Storage 1.1.0
114: *
115: * @return string The current user.
116: */
117: public function getAuth()
118: {
119: return $this->_driver->getAuth();
120: }
121:
122: /**
123: * Return the ID of this data handler.
124: *
125: * @return string The ID.
126: */
127: public function getId()
128: {
129: $id = $this->_driver->getParameters();
130: unset($id['user']);
131: $id['owner'] = $this->_folder->getOwner();
132: $id['prefix'] = $this->_folder->getPrefix();
133: $id['folder'] = $this->_folder->getSubpath();
134: $id['type'] = $this->getType();
135: ksort($id);
136: return md5(serialize($id));
137: }
138:
139: /**
140: * Return the ID parameters for this data handler.
141: *
142: * @return array The ID parameters.
143: */
144: public function getIdParameters()
145: {
146: $id = $this->_driver->getParameters();
147: unset($id['user']);
148: $id['owner'] = $this->_folder->getOwner();
149: $id['prefix'] = $this->_folder->getPrefix();
150: $id['folder'] = $this->_folder->getSubpath();
151: $id['type'] = $this->getType();
152: return $id;
153: }
154:
155: /**
156: * Return the data type represented by this object.
157: *
158: * @return string The type of data this instance handles.
159: */
160: public function getType()
161: {
162: if ($this->_type === null) {
163: $this->_type = $this->_folder->getType();
164: }
165: return $this->_type;
166: }
167:
168: /**
169: * Return the data version.
170: *
171: * @return string The data version.
172: */
173: public function getVersion()
174: {
175: return $this->_version;
176: }
177:
178: /**
179: * Report the status of this folder.
180: *
181: * @return Horde_Kolab_Storage_Folder_Stamp The stamp that can be used for
182: * detecting folder changes.
183: */
184: public function getStamp()
185: {
186: return $this->_driver->getStamp($this->_folder->getPath());
187: }
188:
189: /**
190: * Create a new object.
191: *
192: * @param array &$object The array that holds the object data.
193: * @param boolean $raw True if the data to be stored has been provided in
194: * raw format.
195: *
196: * @return string The ID of the new object or true in case the backend does
197: * not support this return value.
198: *
199: * @throws Horde_Kolab_Storage_Exception In case an error occured while
200: * saving the data.
201: */
202: public function create(&$object, $raw = false)
203: {
204: if (!isset($object['uid'])) {
205: $object['uid'] = $this->generateUid();
206: }
207: $result = $this->_driver->getParser()
208: ->create(
209: $this->_folder->getPath(),
210: $object,
211: array(
212: 'type' => $this->getType(),
213: 'version' => $this->_version,
214: 'raw' => $raw
215: )
216: );
217: if ($result === true) {
218: $params = array();
219: } else {
220: $params = array(
221: 'changes' => array(
222: Horde_Kolab_Storage_Folder_Stamp::ADDED => array(
223: $result => $object
224: ),
225: Horde_Kolab_Storage_Folder_Stamp::DELETED => array()
226: )
227: );
228: }
229: $this->synchronize($params);
230: return $result;
231: }
232:
233: /**
234: * Modify an existing object.
235: *
236: * @param array $object The array that holds the updated object data.
237: * @param boolean $raw True if the data to be stored has been provided in
238: * raw format.
239: *
240: * @return string The new backend ID of the modified object or true in case
241: * the backend does not support this return value.
242: *
243: * @throws Horde_Kolab_Storage_Exception In case an error occured while
244: * saving the data.
245: */
246: public function modify($object, $raw = false)
247: {
248: if (!isset($object['uid'])) {
249: throw new Horde_Kolab_Storage_Exception(
250: 'The provided object data contains no ID value!'
251: );
252: }
253: try {
254: $obid = $this->getBackendId($object['uid']);
255: } catch (Horde_Kolab_Storage_Exception $e) {
256: throw new Horde_Kolab_Storage_Exception(
257: sprintf(
258: Horde_Kolab_Storage_Translation::t(
259: 'The message with ID %s does not exist. This probably means that the Kolab object has been modified by somebody else since you retrieved the object from the backend. Original error: %s'
260: ),
261: $object['uid'],
262: 0,
263: $e
264: )
265: );
266: }
267: $result = $this->_driver->getParser()
268: ->modify(
269: $this->_folder->getPath(),
270: $object,
271: $obid,
272: array(
273: 'type' => $this->getType(),
274: 'version' => $this->_version,
275: 'raw' => $raw
276: )
277: );
278: if ($result === true) {
279: $params = array();
280: } else {
281: $params = array(
282: 'changes' => array(
283: Horde_Kolab_Storage_Folder_Stamp::ADDED => array(
284: $result => $object
285: ),
286: Horde_Kolab_Storage_Folder_Stamp::DELETED => array()
287: )
288: );
289: }
290: $this->synchronize($params);
291: return $result;
292: }
293:
294: /**
295: * Retrieves the complete message for the given UID.
296: *
297: * @param string $uid The message UID.
298: *
299: * @return array The message encapsuled as an array that contains a
300: * Horde_Mime_Headers and a Horde_Mime_Part object.
301: */
302: public function fetchComplete($uid)
303: {
304: return $this->_driver->fetchComplete($this->_folder->getPath(), $uid);
305: }
306:
307: /**
308: * Retrieves the body part for the given UID and mime part ID.
309: *
310: * @param string $uid The message UID.
311: * @param string $id The mime part ID.
312: *
313: * @return resource The message part as stream resource.
314: */
315: public function fetchPart($uid, $id)
316: {
317: return $this->_driver->fetchBodypart(
318: $this->_folder->getPath(), $uid, $id
319: );
320: }
321:
322: /**
323: * Retrieves the objects for the given UIDs.
324: *
325: * @param array $uids The message UIDs.
326: * @param boolean $raw True if the raw format should be returned rather than
327: * the parsed data.
328: *
329: * @return array An array of objects.
330: */
331: public function fetch($uids, $raw = false)
332: {
333: if (!empty($uids)) {
334: return $this->_driver->fetch(
335: $this->_folder->getPath(),
336: $uids,
337: array(
338: 'type' => $this->getType(),
339: 'version' => $this->_version,
340: 'raw' => $raw
341: )
342: );
343: } else {
344: return array();
345: }
346: }
347:
348: /**
349: * Return the backend ID for the given object ID.
350: *
351: * @param string $object_uid The object ID.
352: *
353: * @return string The backend ID for the object.
354: */
355: public function getBackendId($object_id)
356: {
357: $by_obid = $this->fetch($this->getStamp()->ids());
358: foreach ($by_obid as $obid => $object) {
359: if ($object['uid'] == $object_id) {
360: return $obid;
361: }
362: }
363: throw new Horde_Kolab_Storage_Exception(
364: sprintf('Object ID %s does not exist!', $object_id)
365: );
366: }
367:
368: /**
369: * Generate a unique object ID.
370: *
371: * @return string The unique ID.
372: */
373: public function generateUid()
374: {
375: return strval(new Horde_Support_Uuid());
376: }
377:
378: /**
379: * Check if the given object ID exists.
380: *
381: * @param string $object_id The object ID.
382: *
383: * @return boolean True if the ID was found, false otherwise.
384: */
385: public function objectIdExists($object_id)
386: {
387: return array_key_exists(
388: $object_id, $this->getObjects()
389: );
390: }
391:
392: /**
393: * Return the specified object.
394: *
395: * @param string $object_id The object id.
396: *
397: * @return array The object data as an array.
398: */
399: public function getObject($object_id)
400: {
401: $objects = $this->getObjects();
402: if (isset($objects[$object_id])) {
403: return $objects[$object_id];
404: } else {
405: throw new Horde_Kolab_Storage_Exception(
406: sprintf('Object ID %s does not exist!', $object_id)
407: );
408: }
409: }
410:
411: /**
412: * Return the specified attachment.
413: *
414: * @param string $attachment_id The attachment id.
415: *
416: * @return resource An open stream to the attachment data.
417: */
418: public function getAttachment($attachment_id)
419: {
420: //@todo: implement
421: }
422:
423: /**
424: * Retrieve all object ids in the current folder.
425: *
426: * @return array The object ids.
427: */
428: public function getObjectIds()
429: {
430: return array_keys($this->getObjects());
431: }
432:
433: /**
434: * Retrieve all objects in the current folder.
435: *
436: * @return array An array of all objects.
437: */
438: public function getObjects()
439: {
440: $by_oid = array();
441: $by_obid = $this->fetch($this->getStamp()->ids());
442: foreach ($by_obid as $obid => $object) {
443: $by_oid[$object['uid']] = $object;
444: }
445: return $by_oid;
446: }
447:
448: /**
449: * Retrieve all objects in the current folder by backend id.
450: *
451: * @since Horde_Kolab_Storage 1.1.0
452: *
453: * @return array An array of all objects.
454: */
455: public function getObjectsByBackendId()
456: {
457: return $this->fetch($this->getStamp()->ids());
458: }
459:
460: /**
461: * Retrieve an object in the current folder by backend id.
462: *
463: * @since Horde_Kolab_Storage 1.1.0
464: *
465: * @param string $uid Backend id of the object to be returned.
466: *
467: * @return array An array of all objects.
468: */
469: public function getObjectByBackendId($uid)
470: {
471: $fetched = $this->fetch(array($uid));
472: return array_pop($fetched);
473: }
474:
475: /**
476: * Return the mapping of object IDs to backend IDs.
477: *
478: * @since Horde_Kolab_Storage 1.1.0
479: *
480: * @return array The object to backend mapping.
481: */
482: public function getObjectToBackend()
483: {
484: $bid = array();
485: $by_obid = $this->fetch($this->getStamp()->ids());
486: foreach ($by_obid as $obid => $object) {
487: $bid[$object['uid']] = $obid;
488: }
489: return $bid;
490: }
491:
492: /**
493: * Retrieve the list of object duplicates.
494: *
495: * @since Horde_Kolab_Storage 1.1.0
496: *
497: * @return array The list of duplicates.
498: */
499: public function getDuplicates()
500: {
501: $existing = array();
502: $duplicates = array();
503: $by_obid = $this->fetch($this->getStamp()->ids());
504: foreach ($by_obid as $obid => $object) {
505: if (isset($existing[$object['uid']])) {
506: if (!isset($duplicates[$object['uid']])) {
507: $duplicates[$object['uid']][] = $existing[$object['uid']];
508: }
509: $duplicates[$object['uid']][] = $obid;
510: } else {
511: $existing[$object['uid']] = $obid;
512: }
513: }
514: return $duplicates;
515: }
516:
517: /**
518: * Retrieve the list of object errors.
519: *
520: * @since Horde_Kolab_Storage 1.1.0
521: *
522: * @return array The list of errors.
523: */
524: public function getErrors()
525: {
526: $errors = array();
527: $by_obid = $this->fetch($this->getStamp()->ids());
528: foreach ($by_obid as $obid => $object) {
529: if ($object === false) {
530: $errors[] = $obid;
531: }
532: }
533: return $errors;
534: }
535:
536: /**
537: * Move the specified message from the current folder into a new
538: * folder.
539: *
540: * @param string $object_id ID of the message to be moved.
541: * @param string $new_folder Target folder.
542: *
543: * @return NULL
544: */
545: public function move($object_id, $new_folder)
546: {
547: if ($this->objectIdExists($object_id)) {
548: $uid = $this->getBackendId($object_id);
549: } else {
550: throw new Horde_Kolab_Storage_Exception(
551: sprintf('No such object %s!', $id)
552: );
553: }
554: $this->_driver->moveMessage(
555: $uid, $this->_folder->getPath(), $new_folder
556: );
557: }
558:
559: /**
560: * Delete the specified objects from this data set.
561: *
562: * @param array|string $object_ids Id(s) of the object to be deleted.
563: *
564: * @return NULL
565: */
566: public function delete($object_ids)
567: {
568: if (!is_array($object_ids)) {
569: $object_ids = array($object_ids);
570: }
571:
572: $uids = array();
573: foreach ($object_ids as $id) {
574: if ($this->objectIdExists($id)) {
575: $uids[$this->getBackendId($id)] = $id;
576: } else {
577: throw new Horde_Kolab_Storage_Exception(
578: sprintf('No such object %s!', $id)
579: );
580: }
581: }
582: $this->deleteBackendIds(array_keys($uids));
583: $this->synchronize(
584: array(
585: 'changes' => array(
586: Horde_Kolab_Storage_Folder_Stamp::ADDED => array(),
587: Horde_Kolab_Storage_Folder_Stamp::DELETED => $uids
588: )
589: )
590: );
591: }
592:
593: /**
594: * Delete all objects from this data set.
595: *
596: * @return NULL
597: */
598: public function deleteAll()
599: {
600: $this->delete($this->getObjectIds());
601: }
602:
603: /**
604: * Delete the specified messages from this folder.
605: *
606: * @since Horde_Kolab_Storage 1.1.0
607: *
608: * @param array|string $uids Backend id(s) of the message to be deleted.
609: *
610: * @return NULL
611: */
612: public function deleteBackendIds($uids)
613: {
614: if (!is_array($uids)) {
615: $uids = array($uids);
616: }
617: $this->_driver->deleteMessages($this->_folder->getPath(), $uids);
618: $this->_driver->expunge($this->_folder->getPath());
619: }
620:
621: /**
622: * Register a query to be updated if the underlying data changes.
623: *
624: * @param string $name The query name.
625: * @param Horde_Kolab_Storage_Query $query The query to register.
626: *
627: * @return NULL
628: */
629: public function registerQuery($name, Horde_Kolab_Storage_Query $query)
630: {
631: if (!$query instanceOf Horde_Kolab_Storage_Data_Query) {
632: throw new Horde_Kolab_Storage_Exception(
633: 'The provided query is no data query.'
634: );
635: }
636: $this->_queries[$name] = $query;
637: }
638:
639: /**
640: * Synchronize the data information with the information from the backend.
641: *
642: * @param array $params Additional parameters.
643: *
644: * @return NULL
645: */
646: public function synchronize($params = array())
647: {
648: foreach ($this->_queries as $name => $query) {
649: $query->synchronize($params);
650: }
651: }
652:
653: /**
654: * Return a registered query.
655: *
656: * @param string $name The query name.
657: *
658: * @return Horde_Kolab_Storage_Query The requested query.
659: *
660: * @throws Horde_Kolab_Storage_Exception In case the requested query does
661: * not exist.
662: */
663: public function getQuery($name = null)
664: {
665: if (isset($this->_queries[$name])) {
666: return $this->_queries[$name];
667: } else {
668: throw new Horde_Kolab_Storage_Exception('No such query!');
669: }
670: }
671: }
672: