1: <?php
2: /**
3: * Handles data objects in a Kolab storage folder.
4: *
5: * PHP version 5
6: *
7: * @category Kolab
8: * @package Kolab_Storage
9: * @author Stuart Binge <omicron@mighty.co.za>
10: * @author Thomas Jarosch <thomas.jarosch@intra2net.com>
11: * @author Gunnar Wrobel <wrobel@pardus.de>
12: * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
13: * @link http://pear.horde.org/index.php?package=Kolab_Storage
14: */
15:
16: /**
17: * The Kolab_Data class represents a data type in a Kolab storage
18: * folder on the Kolab server.
19: *
20: * Copyright 2009-2012 Horde LLC (http://www.horde.org/)
21: *
22: * See the enclosed file COPYING for license information (LGPL). If you
23: * did not receive this file, see http://www.horde.org/licenses/lgpl21.
24: *
25: * @category Kolab
26: * @package Kolab_Storage
27: * @author Stuart Binge <omicron@mighty.co.za>
28: * @author Thomas Jarosch <thomas.jarosch@intra2net.com>
29: * @author Gunnar Wrobel <wrobel@pardus.de>
30: * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
31: * @link http://pear.horde.org/index.php?package=Kolab_Storage
32: */
33: class Horde_Kolab_Storage_Data_Old
34: {
35: /**
36: * The link to the parent folder object.
37: *
38: * @var Kolab_Folder
39: */
40: private $_folder;
41:
42: /**
43: * The folder type.
44: *
45: * @var string
46: */
47: private $_type;
48:
49: /**
50: * The driver for accessing the Kolab storage system.
51: *
52: * @var Horde_Kolab_Storage_Driver
53: */
54: private $_driver;
55:
56: /**
57: * The factory for generating additional resources.
58: *
59: * @var Horde_Kolab_Storage_Factory
60: */
61: private $_factory;
62:
63: /**
64: * The object type of the data.
65: *
66: * @var string
67: */
68: private $_object_type;
69:
70: /**
71: * The version of the data.
72: *
73: * @var int
74: */
75: private $_data_version;
76:
77: /**
78: * The data cache.
79: *
80: * @var Kolab_Cache
81: */
82: private $_cache;
83:
84: /**
85: * The Id of this data object in the cache.
86: *
87: * @var string
88: */
89: private $_cache_key;
90:
91: /**
92: * An addition to the cache key in case we are operating on
93: * something other than the default type.
94: *
95: * @var string
96: */
97: private $_type_key;
98:
99: /**
100: * Do we optimize for cyrus IMAPD?
101: *
102: * @var boolean
103: */
104: private $_cache_cyrus_optimize = true;
105:
106: /**
107: * Creates a Kolab Folder Data representation.
108: *
109: * @param string $folder Name/ID of the folder.
110: * @param string $type Type of the folder.
111: * @param Horde_Kolab_Storage_Driver $driver The primary connection driver.
112: * @param Horde_Kolab_Storage_Factory $factory The factory.
113: * @param string $object_type Type of the objects we want to read.
114: * @param int $data_version Format version of the object data.
115: */
116: public function __construct($folder, $type, $driver, $factory, $object_type = null, $data_version = 1)
117: {
118: $this->_type = $type;
119: if (!empty($object_type)) {
120: $this->_object_type = $object_type;
121: } else {
122: $this->_object_type = $type;
123: }
124: $this->_data_version = $data_version;
125:
126: if ($this->_object_type != $this->_type) {
127: $this->_type_key = '@' . $this->_object_type;
128: } else {
129: $this->_type_key = '';
130: }
131: }
132:
133: /**
134: * Delete the specified message from this folder.
135: *
136: * @param string $object_uid Id of the message to be deleted.
137: *
138: * @return boolean|PEAR_Error True is successful, false if the
139: * message does not exist.
140: */
141: public function delete($object_uid)
142: {
143: if (!$this->objectUidExists($object_uid)) {
144: return false;
145: }
146:
147: // Find the storage ID
148: $id = $this->getStorageId($object_uid);
149: if ($id === false) {
150: return false;
151: }
152:
153: $result = $this->_folder->deleteMessage($id);
154: if (is_a($result, 'PEAR_Error')) {
155: return $result;
156: }
157:
158: unset($this->_cache->objects[$object_uid]);
159: unset($this->_cache->uids[$id]);
160: $this->_cache->save();
161: return true;
162: }
163:
164: /**
165: * Delete all messages from the current folder.
166: *
167: * @return boolean|PEAR_Error True if successful.
168: */
169: public function deleteAll()
170: {
171: $this->_cache->load($this->_cache_key, $this->_data_version);
172:
173: if (empty($this->_cache->uids)) {
174: return true;
175: }
176: foreach ($this->_cache->uids as $id => $object_uid) {
177: $this->_folder->deleteMessage($id, false);
178:
179: unset($this->_cache->objects[$object_uid]);
180: unset($this->_cache->uids[$id]);
181: }
182: $this->_cache->save();
183:
184: $result = $this->_folder->trigger();
185: if (is_a($result, 'PEAR_Error')) {
186: Horde::logMessage(sprintf('Failed triggering folder %s!',
187: $this->_folder->name), 'ERR');
188: }
189:
190: return true;
191: }
192:
193: /**
194: * Move the specified message from the current folder into a new
195: * folder.
196: *
197: * @param string $object_uid ID of the message to be deleted.
198: * @param string $new_share ID of the target share.
199: *
200: * @return boolean|PEAR_Error True is successful, false if the
201: * object does not exist.
202: */
203: public function move($object_uid, $new_share)
204: {
205: if (!$this->objectUidExists($object_uid)) {
206: return false;
207: }
208:
209: // Find the storage ID
210: $id = $this->getStorageId($object_uid);
211: if ($id === false) {
212: return false;
213: }
214:
215: $result = $this->_folder->moveMessageToShare($id, $new_share);
216:
217: unset($this->_cache->objects[$object_uid]);
218: unset($this->_cache->uids[$id]);
219: $this->_cache->save();
220: return true;
221: }
222:
223: /**
224: * Save an object.
225: *
226: * @param array $object The array that holds the data object.
227: * @param string $old_object_id The id of the object if it existed before.
228: *
229: * @return boolean True on success.
230: *
231: * @throws Horde_Kolab_Storage_Exception In case the given old object id
232: * is invalid or an error occured
233: * while saving the data.
234: */
235: public function save($object, $old_object_id = null)
236: {
237: // update existing kolab object
238: if ($old_object_id != null) {
239: // check if object really exists
240: if (!$this->objectUidExists($old_object_id)) {
241: throw new Horde_Kolab_Storage_Exception(sprintf(Horde_Kolab_Storage_Translation::t("Old object %s does not exist."),
242: $old_object_id));
243: }
244:
245: // get the storage ID
246: $id = $this->getStorageId($old_object_id);
247: if ($id === false) {
248: throw new Horde_Kolab_Storage_Exception(sprintf(Horde_Kolab_Storage_Translation::t("Old object %s does not map to a uid."),
249: $old_object_id));
250: }
251:
252: $old_object = $this->getObject($old_object_id);
253: } else {
254: $id = null;
255: $old_object = null;
256: }
257:
258: $this->_folder->saveObject($object, $this->_data_version,
259: $this->_object_type, $id, $old_object);
260:
261: $this->synchronize($old_object_id);
262: return true;
263: }
264:
265: /**
266: * Synchronize the data cache for the current folder.
267: *
268: * @param string $history_ignore Object uid that should not be
269: * updated in the History
270: *
271: * @return NULL
272: */
273: public function synchronize($history_ignore = null)
274: {
275: $this->_cache->load($this->_cache_key, $this->_data_version);
276:
277: $result = $this->_folder->getStatus();
278:
279: list($validity, $nextid, $ids) = $result;
280:
281: $changes = $this->_folderChanged($validity, $nextid,
282: array_keys($this->_cache->uids), $ids);
283: if ($changes) {
284: $modified = array();
285:
286: $recent_uids = array_diff($ids, array_keys($this->_cache->uids));
287:
288: $formats = $this->_folder->getFormats();
289:
290: $handler = Horde_Kolab_Format::factory('Xml', $this->_object_type,
291: $this->_data_version);
292:
293: $count = 0;
294: foreach ($recent_uids as $id) {
295:
296: if ($this->_type == 'annotation' && $id != 1) {
297: continue;
298: }
299:
300: try {
301: $mime = $this->_folder->parseMessage($id,
302: $handler->getMimeType(),
303: false);
304: $text = $mime[0];
305: } catch (Horde_Kolab_Storage_Exception $e) {
306: Horde::logMessage($mime, 'WARN');
307: $text = false;
308: }
309:
310: if ($text) {
311: $object = $handler->load($text);
312: if (is_a($object, 'PEAR_Error')) {
313: $this->_cache->ignore($id);
314: $object->addUserInfo('STORAGE ID: ' . $id);
315: Horde::logMessage($object, 'WARN');
316: continue;
317: }
318: } else {
319: $object = false;
320: }
321:
322: if ($object !== false) {
323: $message = &$mime[2];
324: $handler_type = $handler->getMimeType();
325: foreach ($message->getParts() as $part) {
326: $name = $part->getName();
327: $type = $part->getType();
328: $dp = $part->getDispositionParameter('x-kolab-type');
329: if (!empty($name) && $type != $handler_type
330: || (!empty($dp) && in_array($dp, $formats))) {
331: $object['_attachments'][$name]['type'] = $type;
332: $object['_attachments'][$name]['key'] = $this->_cache_key . '/' . $object['uid'] . ':' . $name;
333: //@todo: Check what to do with this call
334: //$part->transferDecodeContents();
335: $result = $this->_cache->storeAttachment($object['_attachments'][$name]['key'],
336: $part->getContents());
337: if (is_a($result, 'PEAR_Error')) {
338: Horde::logMessage(sprintf('Failed storing attachment of object %s: %s',
339: $id,
340: $result->getMessage()), 'ERR');
341: $object = false;
342: break;
343: }
344: }
345: }
346: }
347:
348: if ($object !== false) {
349: $this->_cache->store($id, $object['uid'], $object);
350: $mod_ts = time();
351: if (is_array($changes) && in_array($object['uid'], $changes)
352: && $object['uid'] != $history_ignore) {
353: $this->_updateHistory($object['uid'], $mod_ts, 'modify');
354: $modified[] = $object['uid'];
355: } else {
356: $this->_updateHistory($object['uid'], $mod_ts, 'add');
357: }
358: } else {
359: $this->_cache->ignore($id);
360: }
361:
362: // write out cache once in a while so if the browser times out
363: // we don't have to start from the beginning.
364: if ($count > 500) {
365: $count = 0;
366: $this->_cache->save();
367: }
368: $count++;
369: }
370:
371: $this->_cache->save();
372:
373: if (is_array($changes)) {
374: $deleted = array_diff($changes, $modified);
375: foreach ($deleted as $deleted_oid) {
376: if ($deleted_oid != $history_ignore) {
377: $this->_updateHistory($deleted_oid, time(), 'delete');
378: }
379: }
380: }
381: }
382: }
383:
384: /**
385: * Update the Horde history in case an element was modified
386: * outside of Horde.
387: *
388: * @param string $object_uid Object uid that should be updated.
389: * @param int $mod_ts Timestamp of the modification.
390: * @param string $action The action that was performed.
391: *
392: * @return NULL
393: */
394: private function _updateHistory($object_uid, $mod_ts, $action)
395: {
396: global $registry;
397:
398: if (!isset($registry)) {
399: return;
400: }
401:
402: $app = $registry->getApp();
403: if (empty($app) || is_a($app, 'PEAR_Error')) {
404: /**
405: * Ignore the history if we are not in application
406: * context.
407: */
408: return $app;
409: }
410:
411: if (!class_exists('Horde_History')) {
412: return;
413: }
414:
415: /* Log the action on this item in the history log. */
416: try {
417: $GLOBALS['injector']->getInstance('Horde_History')
418: ->log($app . ':' . $this->_folder->getShareId() . ':' . $object_uid,
419: array('action' => $action, 'ts' => $mod_ts),
420: true);
421: } catch (Horde_Exception $e) {
422: }
423: }
424:
425:
426: /**
427: * Test if the storage ID exists.
428: *
429: * @param int $uid The storage ID.
430: *
431: * @return boolean True if the ID exists.
432: */
433: public function storageIdExists($uid)
434: {
435: $this->_cache->load($this->_cache_key, $this->_data_version);
436:
437: return array_key_exists($uid, $this->_cache->uids);
438: }
439:
440: /**
441: * Generate a unique object id.
442: *
443: * @return string The unique id.
444: */
445: public function generateUID()
446: {
447: do {
448: $key = md5(uniqid(mt_rand(), true));
449: } while ($this->objectUidExists($key));
450:
451: return $key;
452: }
453:
454: /**
455: * Return the specified attachment.
456: *
457: * @param string $attachment_id The attachment id.
458: *
459: * @return string|PEAR_Error The attachment data as a string.
460: */
461: public function getAttachment($attachment_id)
462: {
463: $this->_cache->load($this->_cache_key, $this->_data_version);
464:
465: return $this->_cache->loadAttachment($attachment_id);
466: }
467:
468: /**
469: * Retrieve all objects in the current folder as an array.
470: *
471: * @return array The object data array.
472: */
473: public function getObjectArray()
474: {
475: $this->_cache->load($this->_cache_key, $this->_data_version);
476:
477: return $this->_cache->objects;
478: }
479: }
480: