1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:
14:
15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28:
29: class Horde_Kolab_Storage_Folder_Base
30: implements Horde_Kolab_Storage_Folder
31: {
32: 33: 34: 35: 36:
37: private $_list;
38:
39: 40: 41: 42: 43:
44: private $_path;
45:
46: 47: 48: 49: 50:
51: private $_data;
52:
53: 54: 55: 56: 57: 58: 59:
60: public function __construct(Horde_Kolab_Storage_List $list, $path)
61: {
62: $this->_list = $list;
63: $this->_path = $path;
64: }
65:
66: 67: 68: 69: 70:
71: private function _init()
72: {
73: if ($this->_data === null) {
74: $this->_data = $this->_list->getQuery()->folderData($this->_path);
75: }
76: }
77:
78: 79: 80: 81: 82: 83: 84:
85: public function get($key)
86: {
87: $this->_init();
88: if (isset($this->_data[$key])) {
89: return $this->_data[$key];
90: }
91: throw new Horde_Kolab_Storage_Exception(
92: sprintf('No "%s" information available!', $key)
93: );
94: }
95:
96: 97: 98: 99: 100: 101: 102:
103: public function getWithNull($key)
104: {
105: $this->_init();
106: if (isset($this->_data[$key])) {
107: return $this->_data[$key];
108: }
109: }
110:
111: 112: 113: 114: 115:
116: public function getPath()
117: {
118: return $this->_path;
119: }
120:
121: 122: 123: 124: 125:
126: public function getNamespace()
127: {
128: return $this->get('namespace');
129: }
130:
131: 132: 133: 134: 135: 136: 137:
138: public function getPrefix()
139: {
140: return $this->get('prefix');
141: }
142:
143: 144: 145: 146: 147:
148: public function getTitle()
149: {
150: return $this->get('name');
151: }
152:
153: 154: 155: 156: 157:
158: public function getOwner()
159: {
160: return $this->getWithNull('owner');
161: }
162:
163: 164: 165: 166: 167:
168: public function getSubpath()
169: {
170: return $this->get('subpath');
171: }
172:
173: 174: 175: 176: 177:
178: public function getParent()
179: {
180: return $this->get('parent');
181: }
182:
183: 184: 185: 186: 187:
188: public function isDefault()
189: {
190: return $this->get('default');
191: }
192:
193: 194: 195: 196: 197:
198: public function getType()
199: {
200: return $this->get('type');
201: }
202:
203:
204:
205:
206:
207:
208:
209:
210:
211:
212:
213:
214:
215: 216: 217: 218:
219: const ANNOT_ROOT = '/shared/vendor/kolab/';
220:
221: 222: 223: 224:
225: const ANNOT_FOLDER_TYPE = '/shared/vendor/kolab/folder-type';
226:
227: 228: 229:
230: const ANNOT_SHARE_ATTR = '/shared/vendor/horde/share-';
231:
232: 233: 234:
235: const FBRELEVANCE_ADMINS = 0;
236: const FBRELEVANCE_READERS = 1;
237: const FBRELEVANCE_NOBODY = 2;
238:
239: 240: 241: 242: 243:
244: private $_attributes;
245:
246: 247: 248: 249: 250:
251: private $_kolab_attributes;
252:
253: 254: 255: 256: 257:
258: var $_perms;
259:
260: 261: 262: 263: 264:
265:
266:
267: 268: 269: 270: 271:
272: var $_annotation_data;
273:
274: 275: 276: 277: 278: 279:
280: var $tainted = false;
281:
282:
283:
284:
285:
286:
287:
288:
289:
290:
291: 292: 293: 294: 295: 296: 297: 298: 299: 300:
301: public function save($attributes = null)
302: {
303: if (!isset($this->_path)) {
304:
305: if (!isset($this->_new_path)) {
306: throw new Horde_Kolab_Storage_Exception('Cannot create this folder! The name has not yet been set.',
307: Horde_Kolab_Storage_Exception::FOLDER_NAME_UNSET);
308: }
309:
310: if (isset($attributes['type'])) {
311: $this->_type = $attributes['type'];
312: unset($attributes['type']);
313: } else {
314: $this->_type = 'mail';
315: }
316:
317: if (isset($attributes['default'])) {
318: $this->_default = $attributes['default'];
319: unset($attributes['default']);
320: } else {
321: $this->_default = false;
322: }
323:
324: $result = $this->_driver->exists($this->_new_path);
325: if ($result) {
326: throw new Horde_Kolab_Storage_Exception(sprintf("Unable to add %s: destination folder already exists",
327: $this->_new_path),
328: Horde_Kolab_Storage_Exception::FOLDER_EXISTS);
329: }
330:
331: $this->_driver->create($this->_new_path);
332:
333: $this->_path = $this->_new_path;
334: $this->_new_path = null;
335:
336:
337: if (empty($this->_perms)) {
338: $this->getPermission();
339: }
340: } else {
341:
342: $type = $this->getType();
343:
344: if (isset($attributes['type'])) {
345: if ($attributes['type'] != $type) {
346: Horde::logMessage(sprintf('Cannot modify the type of a folder from %s to %s!',
347: $type, $attributes['type']), 'ERR');
348: }
349: unset($attributes['type']);
350: }
351:
352: if (isset($attributes['default'])) {
353: $this->_default = $attributes['default'];
354: unset($attributes['default']);
355: } else {
356: $this->_default = $this->isDefault();
357: }
358:
359: if (isset($this->_new_path)
360: && $this->_new_path != $this->_path) {
361:
362: $result = $this->_driver->exists($this->_new_path);
363: if ($result) {
364: throw new Horde_Kolab_Storage_Exception(sprintf(Horde_Kolab_Storage_Translation::t("Unable to rename %s to %s: destination folder already exists"),
365: $name, $new_name));
366: }
367:
368: $result = $this->_driver->rename($this->_path, $this->_new_path);
369: $this->_storage->removeFromCache($this);
370:
371: $this->_path = $this->_new_path;
372: $this->_new_path = null;
373: $this->_title = null;
374: $this->_owner = null;
375: }
376: }
377:
378: if (isset($attributes['owner'])) {
379: if ($attributes['owner'] != $this->getOwner()) {
380: Horde::logMessage(sprintf('Cannot modify the owner of a folder from %s to %s!',
381: $this->getOwner(), $attributes['owner']), 'ERR');
382: }
383: unset($attributes['owner']);
384: }
385:
386:
387: $folder_type = $this->_type . ($this->_default ? '.default' : '');
388: if ($this->_type_annotation != $folder_type) {
389: try {
390: $result = $this->_setAnnotation(self::ANNOT_FOLDER_TYPE, $folder_type);
391: } catch (Exception $e) {
392: $this->_type = null;
393: $this->_default = false;
394: $this->_type_annotation = null;
395: throw $e;
396: }
397: }
398:
399: if (!empty($attributes)) {
400: if (!is_array($attributes)) {
401: $attributes = array($attributes);
402: }
403: foreach ($attributes as $key => $value) {
404: if ($key == 'params') {
405: $params = unserialize($value);
406: if (isset($params['xfbaccess'])) {
407: $result = $this->setXfbAccess($params['xfbaccess']);
408: if (is_a($result, 'PEAR_Error')) {
409: return $result;
410: }
411: }
412: if (isset($params['fbrelevance'])) {
413: $result = $this->setFbrelevance($params['fbrelevance']);
414: if (is_a($result, 'PEAR_Error')) {
415: return $result;
416: }
417: }
418: }
419:
420:
421: $store = base64_encode($value);
422: if ($key == 'desc') {
423: $entry = '/shared/comment';
424: } else {
425: $entry = self::ANNOT_SHARE_ATTR . $key;
426: }
427: $result = $this->_setAnnotation($entry, $store);
428: if (is_a($result, 'PEAR_Error')) {
429: return $result;
430: }
431: }
432: $this->_attributes = $attributes;
433: }
434:
435:
436: if (isset($this->_perms)) {
437: $this->_perms->save();
438: $this->_perms = null;
439: }
440:
441: $this->_storage->addToCache($this);
442:
443: return true;
444: }
445:
446: 447: 448: 449: 450:
451: function delete()
452: {
453: $this->_driver->delete($this->_path);
454: $this->_storage->removeFromCache($this);
455: return true;
456: }
457:
458: 459: 460: 461: 462: 463: 464: 465: 466:
467: function getAttribute($attribute)
468: {
469: if (!isset($this->_attributes[$attribute])) {
470: if ($attribute == 'desc') {
471: $entry = '/comment';
472: } else {
473: $entry = self::ANNOT_SHARE_ATTR . $attribute;
474: }
475: $annotation = $this->_getAnnotation($entry, $this->_path);
476: if (is_a($annotation, 'PEAR_Error')) {
477: return $annotation;
478: }
479: if (empty($annotation)) {
480: $this->_attributes[$attribute] = '';
481: } else {
482: $this->_attributes[$attribute] = base64_decode($annotation);
483: }
484: }
485: return $this->_attributes[$attribute];
486: }
487:
488: 489: 490: 491: 492: 493: 494: 495: 496:
497: function getKolabAttribute($attribute)
498: {
499: if (!isset($this->_kolab_attributes[$attribute])) {
500: $entry = KOLAB_ANNOT_ROOT . $attribute;
501: $annotation = $this->_getAnnotation($entry, $this->_path);
502: if (is_a($annotation, 'PEAR_Error')) {
503: return $annotation;
504: }
505: if (empty($annotation)) {
506: $this->_kolab_attributes[$attribute] = '';
507: } else {
508: $this->_kolab_attributes[$attribute] = $annotation;
509: }
510: }
511: return $this->_kolab_attributes[$attribute];
512: }
513:
514:
515: 516: 517: 518: 519:
520: function exists()
521: {
522: if ($this->_path === null) {
523: return false;
524: }
525: try {
526: return $this->_driver->exists($this->_path);
527: } catch (Horde_Imap_Client_Exception $e) {
528: return false;
529: }
530: }
531:
532: 533: 534: 535: 536: 537: 538:
539: public function getData($object_type = null, $data_version = 1)
540: {
541: if (empty($object_type)) {
542: $object_type = $this->getType();
543: if (is_a($object_type, 'PEAR_Error')) {
544: return $object_type;
545: }
546: }
547:
548: if ($this->tainted) {
549: foreach ($this->_data as $data) {
550: $data->synchronize();
551: }
552: $this->tainted = false;
553: }
554:
555: $key = $object_type . '|' . $data_version;
556: if (!isset($this->_data[$key])) {
557: if ($object_type != 'annotation') {
558: $type = $this->getType();
559: } else {
560: $type = 'annotation';
561: }
562: $data = new Horde_Kolab_Storage_Data($type, $object_type, $data_version);
563: $data->setFolder($this);
564: $data->setCache($this->_storage->getDataCache());
565: $data->synchronize();
566: $this->_data[$key] = &$data;
567: }
568: return $this->_data[$key];
569: }
570:
571: 572: 573: 574: 575: 576: 577: 578:
579: public function deleteMessage($id, $trigger = true)
580: {
581:
582: $this->_driver->deleteMessages($this->_path, $id);
583: $this->_driver->expunge($this->_path);
584: }
585:
586: 587: 588: 589: 590: 591: 592: 593:
594: public function moveMessage($id, $folder)
595: {
596: $this->_driver->select($this->_path);
597: $this->_driver->moveMessage($this->_path, $id, $folder);
598: $this->_driver->expunge($this->_path);
599: }
600:
601: 602: 603: 604: 605: 606: 607: 608:
609: public function moveMessageToShare($id, $share)
610: {
611: $folder = $this->_storage->getByShare($share, $this->getType());
612: $folder->tainted = true;
613:
614: $success = $this->moveMessage($id, $folder->name);
615: }
616:
617: 618: 619: 620: 621:
622: function getFormats()
623: {
624: global $conf;
625:
626: if (empty($conf['kolab']['misc']['formats'])) {
627: $formats = array('XML');
628: } else {
629: $formats = $conf['kolab']['misc']['formats'];
630: }
631: if (!is_array($formats)) {
632: $formats = array($formats);
633: }
634: if (!in_array('XML', $formats)) {
635: $formats[] = 'XML';
636: }
637: return $formats;
638: }
639:
640: 641: 642: 643: 644: 645: 646: 647: 648: 649: 650: 651: 652:
653: public function saveObject(&$object, $data_version, $object_type, $id = null,
654: &$old_object = null)
655: {
656:
657: $this->_driver->select($this->_path);
658:
659: $new_headers = new Horde_Mime_Headers();
660: $new_headers->setEOL("\r\n");
661:
662: $formats = $this->getFormats();
663:
664: $handlers = array();
665: foreach ($formats as $type) {
666: $handlers[$type] = &Horde_Kolab_Format::factory($type, $object_type,
667: $data_version);
668: if (is_a($handlers[$type], 'PEAR_Error')) {
669: if ($type == 'XML') {
670: return $handlers[$type];
671: }
672: Horde::logMessage(sprintf('Loading format handler "%s" failed: %s',
673: $type, $handlers[$type]->getMessage()), 'ERR');
674: continue;
675: }
676: }
677:
678: if ($id != null) {
679:
680: if (!in_array($id, $this->_driver->getUids($this->_path))) {
681: return PEAR::raiseError(sprintf(Horde_Kolab_Storage_Translation::t("The message with ID %s does not exist. This probably means that the Kolab object has been modified by somebody else while you were editing it. Your edits have been lost."),
682: $id));
683: }
684:
685:
686: $result = $this->parseMessage($id, $handlers['XML']->getMimeType(),
687: true, $formats);
688: if (is_a($result, 'PEAR_Error')) {
689: return $result;
690: }
691: list($old_message, $part_ids, $mime_message, $mime_headers) = $result;
692: if (is_a($old_message, 'PEAR_Error')) {
693: return $old_message;
694: }
695:
696: if (isset($object['_attachments']) && isset($old_object['_attachments'])) {
697: $attachments = array_keys($object['_attachments']);
698: foreach (array_keys($old_object['_attachments']) as $attachment) {
699: if (!in_array($attachment, $attachments)) {
700: foreach ($mime_message->getParts() as $part) {
701: if ($part->getName() === $attachment) {
702: foreach (array_keys($mime_message->_parts) as $key) {
703: if ($mime_message->_parts[$key]->getMimeId() == $part->getMimeId()) {
704: unset($mime_message->_parts[$key]);
705: break;
706: }
707: }
708: $mime_message->_generateIdMap($mime_message->_parts);
709: }
710: }
711: }
712: }
713: }
714: $object = array_merge($old_object, $object);
715:
716: if (isset($attachments)) {
717: foreach ($mime_message->getParts() as $part) {
718: $name = $part->getName();
719: foreach ($attachments as $attachment) {
720: if ($name === $attachment) {
721: $object['_attachments'][$attachment]['id'] = $part->getMimeId();
722: }
723: }
724: }
725: }
726:
727:
728: if (!empty($mime_headers) && !$mime_headers === false) {
729: foreach ($mime_headers as $header => $value) {
730: $new_headers->addheader($header, $value);
731: }
732: }
733: } else {
734: $mime_message = $this->_prepareNewMessage($new_headers);
735: $mime_part_id = false;
736: }
737:
738: if (isset($object['_attachments'])) {
739: $attachments = array_keys($object['_attachments']);
740: foreach ($attachments as $attachment) {
741: $data = $object['_attachments'][$attachment];
742:
743: if (!isset($data['content']) && !isset($data['path'])) {
744: 745: 746: 747:
748: continue;
749: }
750:
751: $part = new Horde_Mime_Part();
752: $part->setType(isset($data['type']) ? $data['type'] : null);
753: $part->setContents(isset($data['content']) ? $data['content'] : file_get_contents($data['path']));
754: $part->setCharset('UTF-8');
755: $part->setTransferEncoding('quoted-printable');
756: $part->setDisposition('attachment');
757: $part->setName($attachment);
758:
759: if (!isset($data['id'])) {
760: $mime_message->addPart($part);
761: } else {
762: $mime_message->alterPart($data['id'], $part);
763: }
764: }
765: }
766:
767: foreach ($formats as $type) {
768: $new_content = $handlers[$type]->save($object);
769: if (is_a($new_content, 'PEAR_Error')) {
770: return $new_content;
771: }
772:
773:
774: $part = new Horde_Mime_Part();
775: $part->setType($handlers[$type]->getMimeType());
776: $part->setContents($new_content);
777: $part->setCharset('UTF-8');
778: $part->setTransferEncoding('quoted-printable');
779: $part->setDisposition($handlers[$type]->getDisposition());
780: $part->setDispositionParameter('x-kolab-type', $type);
781: $part->setName($handlers[$type]->getName());
782:
783: if (!isset($part_ids) || $part_ids[$type] === false) {
784: $mime_message->addPart($part);
785: } else {
786: $mime_message->alterPart($part_ids[$type], $part);
787: }
788: }
789:
790:
791: $new_headers->addHeader('From', $this->_driver->getAuth());
792: $new_headers->addHeader('To', $this->_driver->getAuth());
793: $new_headers->addHeader('Date', date('r'));
794: $new_headers->addHeader('X-Kolab-Type', $handlers['XML']->getMimeType());
795: $new_headers->addHeader('Subject', $object['uid']);
796: $new_headers->addHeader('User-Agent', 'Horde::Kolab::Storage v0.2');
797: $new_headers->addHeader('MIME-Version', '1.0');
798: $mime_message->addMimeHeaders(array('headers' => $new_headers));
799:
800: $msg = $new_headers->toString() . $mime_message->toString(array('canonical' => true,
801: 'headers' => false));
802:
803:
804: if ($id != null) {
805: $this->_driver->deleteMessages($this->_path, $id);
806: }
807:
808:
809: try {
810: $result = $this->_driver->appendMessage($this->_path, $msg);
811: } catch (Horde_Kolab_Storage_Exception $e) {
812: if ($id != null) {
813: $this->_driver->undeleteMessages($id);
814: }
815: }
816:
817:
818: if ($id != null) {
819: $this->_driver->expunge($this->_path);
820: }
821: }
822:
823: 824: 825: 826: 827: 828: 829: 830: 831: 832: 833: 834: 835: 836:
837: function parseMessage($id, $mime_type, $parse_headers = true,
838: $formats = array('XML'))
839: {
840: $raw_headers = $this->_driver->getMessageHeader($this->_path, $id);
841: if (is_a($raw_headers, 'PEAR_Error')) {
842: return PEAR::raiseError(sprintf(Horde_Kolab_Storage_Translation::t("Failed retrieving the message with ID %s. Original error: %s."),
843: $id, $raw_headers->getMessage()));
844: }
845:
846: $body = $this->_driver->getMessageBody($this->_path, $id);
847: if (is_a($body, 'PEAR_Error')) {
848: return PEAR::raiseError(sprintf(Horde_Kolab_Storage_Translation::t("Failed retrieving the message with ID %s. Original error: %s."),
849: $id, $body->getMessage()));
850: }
851:
852:
853: $mime_message = Horde_Mime_Part::parseMessage($raw_headers . "\r" . $body, array('forcemime' => true));
854: $parts = $mime_message->contentTypeMap();
855:
856: $mime_headers = false;
857: $xml = false;
858:
859:
860: $part_ids['XML'] = array_search($mime_type, $parts);
861: if ($part_ids['XML'] !== false) {
862: if ($parse_headers) {
863: $mime_headers = Horde_Mime_Headers::parseHeaders($raw_headers);
864: $mime_headers->setEOL("\r\n");
865: }
866:
867: $part = $mime_message->getPart($part_ids['XML']);
868:
869:
870: $xml = $part->getContents();
871: }
872:
873: $alternate_formats = array_diff(array('XML'), $formats);
874: if (!empty($alternate_formats)) {
875: foreach ($alternate_formats as $type) {
876: $part_ids[$type] = false;
877: }
878: foreach ($mime_message->getParts() as $part) {
879: $params = $part->getDispositionParameters();
880: foreach ($alternate_formats as $type) {
881: if (isset($params['x-kolab-format'])
882: && $params['x-kolab-format'] == $type) {
883: $part_ids[$type] = $part->getMimeId();
884: }
885: }
886: }
887: }
888:
889: $result = array($xml, $part_ids, $mime_message, $mime_headers);
890: return $result;
891: }
892:
893: 894: 895: 896: 897: 898:
899: function _getAnnotationData()
900: {
901: $this->_annotation_data = $this->getData('annotation');
902: }
903:
904:
905: 906: 907: 908: 909: 910: 911:
912: function _getAnnotation($key)
913: {
914: global $conf;
915:
916: if (empty($conf['kolab']['imap']['no_annotations'])) {
917: return $this->_driver->getAnnotation($key, $this->_path);
918: }
919:
920: if (!isset($this->_annotation_data)) {
921: $this->_getAnnotationData();
922: }
923: $data = $this->_annotation_data->getObject('KOLAB_FOLDER_CONFIGURATION');
924: if (is_a($data, 'PEAR_Error')) {
925: Horde::logMessage(sprintf('Error retrieving annotation data on folder %s: %s',
926: $this->_path, $data->getMessage()), 'ERR');
927: return '';
928: }
929: if (isset($data[$key])) {
930: return $data[$key];
931: } else {
932: return '';
933: }
934: }
935:
936: 937: 938: 939: 940: 941: 942: 943:
944: function _setAnnotation($key, $value)
945: {
946: if (empty($conf['kolab']['imap']['no_annotations'])) {
947: return $this->_driver->setAnnotation($key, $value, $this->_path);
948: }
949:
950: if (!isset($this->_annotation_data)) {
951: $this->_getAnnotationData();
952: }
953: $data = $this->_annotation_data->getObject('KOLAB_FOLDER_CONFIGURATION');
954: if (is_a($data, 'PEAR_Error')) {
955: Horde::logMessage(sprintf('Error retrieving annotation data on folder %s: %s',
956: $this->_path, $data->getMessage()), 'ERR');
957: $data = array();
958: $uid = null;
959: } else {
960: $uid = 'KOLAB_FOLDER_CONFIGURATION';
961: }
962: $data[$key] = $value;
963: $data['uid'] = 'KOLAB_FOLDER_CONFIGURATION';
964: return $this->_annotation_data->save($data, $uid);
965: }
966:
967:
968:
969: 970: 971: 972: 973:
974: function getFbrelevance()
975: {
976: $result = $this->getKolabAttribute('incidences-for');
977: if (is_a($result, 'PEAR_Error') || empty($result)) {
978: return KOLAB_FBRELEVANCE_ADMINS;
979: }
980: switch ($result) {
981: case 'admins':
982: return KOLAB_FBRELEVANCE_ADMINS;
983: case 'readers':
984: return KOLAB_FBRELEVANCE_READERS;
985: case 'nobody':
986: return KOLAB_FBRELEVANCE_NOBODY;
987: default:
988: return KOLAB_FBRELEVANCE_ADMINS;
989: }
990: }
991:
992: 993: 994: 995: 996: 997: 998:
999: function setFbrelevance($relevance)
1000: {
1001: switch ($relevance) {
1002: case KOLAB_FBRELEVANCE_ADMINS:
1003: $value = 'admins';
1004: break;
1005: case KOLAB_FBRELEVANCE_READERS:
1006: $value = 'readers';
1007: break;
1008: case KOLAB_FBRELEVANCE_NOBODY:
1009: $value = 'nobody';
1010: break;
1011: default:
1012: $value = 'admins';
1013: }
1014:
1015: return $this->_setAnnotation(KOLAB_ANNOT_ROOT . 'incidences-for',
1016: $value);
1017: }
1018:
1019: 1020: 1021: 1022: 1023: 1024:
1025: function getXfbaccess()
1026: {
1027: $result = $this->getKolabAttribute('pxfb-readable-for');
1028: if (is_a($result, 'PEAR_Error') || empty($result)) {
1029: return array();
1030: }
1031: return explode(' ', $result);
1032: }
1033:
1034: 1035: 1036: 1037: 1038: 1039: 1040: 1041:
1042: function setXfbaccess($access)
1043: {
1044: $value = join(' ', $access);
1045: return $this->_setAnnotation(KOLAB_ANNOT_ROOT . 'pxfb-readable-for',
1046: $value);
1047: }
1048: }
1049: