1: <?php
2: /**
3: * An Kolab storage mock driver.
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: * An Kolab storage mock driver.
16: *
17: * Copyright 2010-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_Driver_Mock
29: extends Horde_Kolab_Storage_Driver_Base
30: {
31: /**
32: * The data of the folders.
33: *
34: * @var Horde_Kolab_Storage_Driver_Mock_Data
35: */
36: private $_data;
37:
38: /**
39: * The regular expression for converting folder names.
40: *
41: * @var string
42: */
43: private $_conversion_pattern;
44:
45: /**
46: * The data of the folder currently opened
47: *
48: * @var array
49: */
50: private $_mbox = null;
51:
52: /**
53: * The name of the folder currently opened
54: *
55: * @var array
56: */
57: private $_mboxname = null;
58:
59: /**
60: * A list of groups (associates users [key] with an array of group names
61: * [value]).
62: *
63: * @var array
64: */
65: private $_groups = array();
66:
67: /**
68: * Constructor.
69: *
70: * @param Horde_Kolab_Storage_Factory $factory A factory for helper objects.
71: * @param array $params Connection parameters.
72: */
73: public function __construct(Horde_Kolab_Storage_Factory $factory,
74: $params = array())
75: {
76: if (isset($params['data'])) {
77: if (is_array($params['data'])) {
78: $params['data'] = new Horde_Kolab_Storage_Driver_Mock_Data(
79: $params['data']
80: );
81: }
82: $this->_data = $params['data'];
83: unset($params['data']);
84: } else {
85: $this->_data = new Horde_Kolab_Storage_Driver_Mock_Data(array());
86: }
87: parent::__construct($factory, $params);
88: }
89:
90: /**
91: * Convert the external folder id to an internal folder name.
92: *
93: * @param string $folder The external folder name.
94: *
95: * @return string The internal folder id.
96: */
97: private function _convertToInternal($folder)
98: {
99: if (substr($folder, 0, 5) == 'INBOX') {
100: $user = explode('@', $this->getAuth());
101: return 'user/' . $user[0] . substr($folder, 5);
102: }
103: return $folder;
104: }
105:
106: /**
107: * Convert the internal folder name into an external folder id.
108: *
109: * @param string $mbox The internal folder name.
110: *
111: * @return string The external folder id.
112: */
113: private function _convertToExternal($mbox)
114: {
115: if ($this->_conversion_pattern === null) {
116: if ($this->getAuth() != '') {
117: $user = explode('@', $this->getAuth());
118: $this->_conversion_pattern = '#^user/' . $user[0] . '#';
119: } else {
120: /**
121: * @todo: FIXME, this is a hack for the current state of the
122: * Kolab share driver which does not yet know how to properly
123: * deal with system shares.
124: */
125: if ($mbox == 'user/') {
126: return 'INBOX';
127: } else {
128: return preg_replace('#^user//#', 'INBOX/', $mbox);
129: }
130: }
131: }
132: return preg_replace($this->_conversion_pattern, 'INBOX', $mbox);
133: }
134:
135: /**
136: * Set a group list.
137: *
138: * @param array $groups A list of groups. User names are the keys, an array
139: * of group names are the values.
140: *
141: * @return NULL
142: */
143: public function setGroups($groups)
144: {
145: $this->_groups = $groups;
146: }
147:
148: /**
149: * Create the backend driver.
150: *
151: * @return mixed The backend driver.
152: */
153: public function createBackend()
154: {
155: }
156:
157: /**
158: * Return the unique connection id.
159: *
160: * @return string The connection id.
161: */
162: public function getId()
163: {
164: return $this->getAuth() . '@mock:0';
165: }
166:
167: /**
168: * Retrieves a list of folders on the server.
169: *
170: * @return array The list of folders.
171: */
172: public function listFolders()
173: {
174: $result = array();
175: foreach ($this->_data->arrayKeys() as $mbox) {
176: if ($this->_folderVisible($mbox, $this->getAuth())) {
177: $result[] = $this->_convertToExternal($mbox);
178: }
179: }
180: return $result;
181: }
182:
183: /**
184: * Create the specified folder.
185: *
186: * @param string $folder The folder to create.
187: *
188: * @return NULL
189: */
190: public function create($folder)
191: {
192: $folder = $this->_convertToInternal($folder);
193: if (isset($this->_data[$folder])) {
194: throw new Horde_Kolab_Storage_Exception(
195: sprintf("IMAP folder %s does already exist!", $folder)
196: );
197: }
198: $this->_data[$folder] = array(
199: 'status' => array(
200: 'uidvalidity' => time(),
201: 'uidnext' => 1
202: ),
203: 'mails' => array(),
204: 'permissions' => array($this->getAuth() => 'lrswipkxtecda'),
205: 'annotations' => array(),
206: );
207: }
208:
209: /**
210: * Delete the specified folder.
211: *
212: * @param string $folder The folder to delete.
213: *
214: * @return NULL
215: */
216: public function delete($folder)
217: {
218: $folder = $this->_convertToInternal($folder);
219: if (!isset($this->_data[$folder])) {
220: throw new Horde_Kolab_Storage_Exception(
221: sprintf("IMAP folder %s does not exist!", $folder)
222: );
223: }
224: unset($this->_data[$folder]);
225: }
226:
227: /**
228: * Rename the specified folder.
229: *
230: * @param string $old The folder to rename.
231: * @param string $new The new name of the folder.
232: *
233: * @return NULL
234: */
235: public function rename($old, $new)
236: {
237: $old = $this->_convertToInternal($old);
238: $new = $this->_convertToInternal($new);
239: if (!isset($this->_data[$old])) {
240: throw new Horde_Kolab_Storage_Exception(
241: sprintf("IMAP folder %s does not exist!", $old)
242: );
243: }
244: if (isset($this->_data[$new])) {
245: throw new Horde_Kolab_Storage_Exception(
246: sprintf("IMAP folder %s does already exist!", $new)
247: );
248: }
249: $this->_data[$new] = $this->_data[$old];
250: unset($this->_data[$old]);
251: }
252:
253: /**
254: * Does the backend support ACL?
255: *
256: * @return boolean True if the backend supports ACLs.
257: */
258: public function hasAclSupport()
259: {
260: return true;
261: }
262:
263: /**
264: * Retrieve the access rights for a folder.
265: *
266: * @param string $folder The folder to retrieve the ACL for.
267: *
268: * @return array An array of rights.
269: */
270: public function getAcl($folder)
271: {
272: $folder = $this->_convertToInternal($folder);
273: $this->_failOnMissingFolder($folder);
274: $this->_failOnNoAdmin($folder);
275: if ($this->_data->hasPermissions($folder)) {
276: return $this->_data->getPermissions($folder);
277: }
278: return array();
279: }
280:
281: /**
282: * Retrieve the access rights the current user has on a folder.
283: *
284: * @param string $folder The folder to retrieve the user ACL for.
285: *
286: * @return string The user rights.
287: */
288: public function getMyAcl($folder)
289: {
290: $folder = $this->_convertToInternal($folder);
291: $this->_failOnMissingFolder($folder);
292: $myacl = array();
293: $users = array($this->getAuth(), 'anyone', 'anonymous');
294: if (isset($this->_groups[$this->getAuth()])) {
295: foreach ($this->_groups[$this->getAuth()] as $group) {
296: $users[] = 'group:' . $group;
297: }
298: }
299: foreach ($users as $user) {
300: if ($this->_data->hasUserPermissions($folder, $user)) {
301: $myacl = array_merge($myacl, str_split($this->_data->getUserPermissions($folder, $user)));
302: }
303: }
304: return join('', $myacl);
305: }
306:
307: /**
308: * Set the access rights for a folder.
309: *
310: * @param string $folder The folder to act upon.
311: * @param string $user The user to set the ACL for.
312: * @param string $acl The ACL.
313: *
314: * @return NULL
315: */
316: public function setAcl($folder, $user, $acl)
317: {
318: $folder = $this->_convertToInternal($folder);
319: $this->_failOnMissingFolder($folder);
320: $this->_failOnNoAdmin($folder);
321: $this->_data->setUserPermissions($folder, $user, $acl);
322: }
323:
324: /**
325: * Delete the access rights for user on a folder.
326: *
327: * @param string $folder The folder to act upon.
328: * @param string $user The user to delete the ACL for
329: *
330: * @return NULL
331: */
332: public function deleteAcl($folder, $user)
333: {
334: $folder = $this->_convertToInternal($folder);
335: $this->_failOnMissingFolder($folder);
336: $this->_failOnNoAdmin($folder);
337: if ($this->_data->hasUserPermissions($folder, $user)) {
338: $this->_data->deleteUserPermissions($folder, $user);
339: }
340: }
341:
342: /**
343: * Retrieves the specified annotation for the complete list of folders.
344: *
345: * @param string $annotation The name of the annotation to retrieve.
346: *
347: * @return array An associative array combining the folder names as key with
348: * the corresponding annotation value.
349: */
350: public function listAnnotation($annotation)
351: {
352: $result = array();
353: foreach ($this->_data->arrayKeys() as $folder) {
354: if ($this->_data->hasAnnotation($folder, $annotation)) {
355: $result[$this->_convertToExternal($folder)] = $this->_data->getAnnotation($folder, $annotation);
356: }
357: }
358: return $result;
359: }
360:
361: /**
362: * Fetches the annotation from a folder.
363: *
364: * @param string $folder The name of the folder.
365: * @param string $annotation The annotation to get.
366: *
367: * @return string The annotation value.
368: */
369: public function getAnnotation($folder, $annotation)
370: {
371: $folder = $this->_convertToInternal($folder);
372: $this->_failOnMissingFolder($folder);
373: if ($this->_data->hasAnnotation($folder, $annotation)) {
374: return $this->_data->getAnnotation($folder, $annotation);
375: }
376: return '';
377: }
378:
379: /**
380: * Sets the annotation on a folder.
381: *
382: * @param string $folder The name of the folder.
383: * @param string $annotation The annotation to set.
384: * @param array $value The values to set
385: *
386: * @return NULL
387: */
388: public function setAnnotation($folder, $annotation, $value)
389: {
390: $folder = $this->_convertToInternal($folder);
391: $this->_failOnMissingFolder($folder);
392: $this->_data->setAnnotation($folder, $annotation, $value);
393: }
394:
395: /**
396: * Error out in case the provided folder is missing.
397: *
398: * @param string $folder The folder.
399: *
400: * @return NULL
401: *
402: * @throws Horde_Kolab_Storage_Exception In case the folder is missing.
403: */
404: private function _failOnMissingFolder($folder)
405: {
406: if (!isset($this->_data[$folder])
407: || !$this->_folderVisible($folder, $this->getAuth())) {
408: $this->_folderMissing($folder);
409: }
410: }
411:
412: /**
413: * Is the folder visible to the specified user (or a global group)?
414: *
415: * @param string $folder The folder.
416: * @param string $user The user.
417: *
418: * @return boolean True if the folder is visible.
419: */
420: private function _folderVisible($folder, $user)
421: {
422: return empty($user)
423: || $this->_folderVisibleToUnique($folder, $user)
424: || $this->_folderVisibleToGroup($folder, $user)
425: || $this->_folderVisibleToUnique($folder, 'anyone')
426: || $this->_folderVisibleToUnique($folder, 'anonymous');
427: }
428:
429: /**
430: * Is the folder visible to a group the user belongs to?
431: *
432: * @param string $folder The folder.
433: * @param string $user The user.
434: *
435: * @return boolean True if the folder is visible.
436: */
437: private function _folderVisibleToGroup($folder, $user)
438: {
439: if (isset($this->_groups[$user])) {
440: foreach ($this->_groups[$user] as $group) {
441: if ($this->_folderVisibleToUnique($folder, 'group:' . $group)) {
442: return true;
443: }
444: }
445: }
446: return false;
447: }
448:
449: /**
450: * Is the folder visible to exactly the specified user?
451: *
452: * @param string $folder The folder.
453: * @param string $user The user.
454: *
455: * @return boolean True if the folder is visible.
456: */
457: private function _folderVisibleToUnique($folder, $user)
458: {
459: if ($this->_data->hasUserPermissions($folder, $user)) {
460: if (strpos($this->_data->getUserPermissions($folder, $user), 'l') !== false
461: || strpos($this->_data->getUserPermissions($folder, $user), 'r') !== false
462: || strpos($this->_data->getUserPermissions($folder, $user), 'a') !== false) {
463: return true;
464: }
465: }
466: return false;
467: }
468:
469: /**
470: * Error out indicating that the user does not have the required
471: * permissions.
472: *
473: * @param string $folder The folder.
474: *
475: * @return NULL
476: *
477: * @throws Horde_Kolab_Storage_Exception In case the folder is missing.
478: */
479: private function _folderMissing($folder)
480: {
481: throw new Horde_Kolab_Storage_Exception(
482: sprintf('The folder %s does not exist!', $folder)
483: );
484: }
485:
486: /**
487: * Error out in case the user is no admin of the specified folder.
488: *
489: * @param string $folder The folder.
490: *
491: * @return NULL
492: *
493: * @throws Horde_Kolab_Storage_Exception In case the user has no admin rights.
494: */
495: private function _failOnNoAdmin($folder)
496: {
497: if (!isset($this->_data[$folder])
498: || !$this->_folderAdmin($folder, $this->getAuth())) {
499: $this->_permissionDenied();
500: }
501: }
502:
503: /**
504: * Is the user a folder admin (or one of the global groups)?
505: *
506: * @param string $folder The folder.
507: * @param string $user The user.
508: *
509: * @return boolean True if the user has admin rights on the folder.
510: */
511: private function _folderAdmin($folder, $user)
512: {
513: return empty($user)
514: || $this->_folderAdminForUnique($folder, $user)
515: || $this->_folderAdminForGroup($folder, $user)
516: || $this->_folderAdminForUnique($folder, 'anyone')
517: || $this->_folderAdminForUnique($folder, 'anonymous');
518: }
519:
520: /**
521: * Is the folder visible to a group the user belongs to?
522: *
523: * @param string $folder The folder.
524: * @param string $user The user.
525: *
526: * @return boolean True if the folder is visible.
527: */
528: private function _folderAdminForGroup($folder, $user)
529: {
530: if (isset($this->_groups[$user])) {
531: foreach ($this->_groups[$user] as $group) {
532: if ($this->_folderAdminForUnique($folder, 'group:' . $group)) {
533: return true;
534: }
535: }
536: }
537: return false;
538: }
539:
540: /**
541: * Is the exact specified user an admin for the folder?
542: *
543: * @param string $folder The folder.
544: * @param string $user The user.
545: *
546: * @return boolean True if the user has admin rights on the folder.
547: */
548: private function _folderAdminForUnique($folder, $user)
549: {
550: if ($this->_data->hasUserPermissions($folder, $user)
551: && strpos($this->_data->getUserPermissions($folder, $user), 'a') !== false) {
552: return true;
553: }
554: return false;
555: }
556:
557: /**
558: * Error out indicating that the user does not have the required
559: * permissions.
560: *
561: * @return NULL
562: *
563: * @throws Horde_Kolab_Storage_Exception In case the folder is missing.
564: */
565: private function _permissionDenied()
566: {
567: throw new Horde_Kolab_Storage_Exception('Permission denied!');
568: }
569:
570: /**
571: * Opens the given folder.
572: *
573: * @param string $folder The folder to open
574: *
575: * @return NULL
576: */
577: public function select($folder)
578: {
579: $this->_data->select($this->_convertToInternal($folder));
580: }
581:
582: /**
583: * Returns the status of the current folder.
584: *
585: * @param string $folder Check the status of this folder.
586: *
587: * @return array An array that contains 'uidvalidity' and 'uidnext'.
588: */
589: public function status($folder)
590: {
591: return $this->_data->status($this->_convertToInternal($folder));
592: }
593:
594: /**
595: * Returns the message ids of the messages in this folder.
596: *
597: * @param string $folder Check the status of this folder.
598: *
599: * @return array The message ids.
600: */
601: public function getUids($folder)
602: {
603: return $this->_data->getUids($this->_convertToInternal($folder));
604: }
605:
606: /**
607: * Retrieves the messages for the given message ids.
608: *
609: * @param string $folder The folder to fetch the messages from.
610: * @param array $uids The message UIDs.
611: *
612: * @return array An array of message structures parsed into Horde_Mime_Part
613: * instances.
614: */
615: public function fetchStructure($folder, $uids)
616: {
617: return $this->_data->fetchStructure(
618: $this->_convertToInternal($folder),
619: $uids
620: );
621: }
622:
623: /**
624: * Retrieves a complete message.
625: *
626: * @param string $folder The folder to fetch the messages from.
627: * @param array $uid The message UID.
628: *
629: * @return array The message encapsuled as an array that contains a
630: * Horde_Mime_Headers and a Horde_Mime_Part object.
631: */
632: public function fetchComplete($folder, $uid)
633: {
634: return $this->_data->fetchComplete(
635: $this->_convertToInternal($folder),
636: $uid
637: );
638: }
639:
640: /**
641: * Retrieves a bodypart for the given message ID and mime part ID.
642: *
643: * @param string $folder The folder to fetch the messages from.
644: * @param array $uid The message UID.
645: * @param array $id The mime part ID.
646: *
647: * @return resource|string The body part, as a stream resource or string.
648: */
649: public function fetchBodypart($folder, $uid, $id)
650: {
651: return $this->_data->fetchBodypart(
652: $this->_convertToInternal($folder),
653: $uid,
654: $id
655: );
656: }
657:
658: /**
659: * Appends a message to the given folder.
660: *
661: * @param string $folder The folder to append the message(s) to.
662: * @param resource $msg The message to append.
663: *
664: * @return mixed True or the UID of the new message in case the backend
665: * supports UIDPLUS.
666: */
667: public function appendMessage($folder, $msg)
668: {
669: return $this->_data->appendMessage(
670: $this->_convertToInternal($folder),
671: $msg
672: );
673: }
674:
675: /**
676: * Deletes messages from the specified folder.
677: *
678: * @param string $folder The folder to delete messages from.
679: * @param integer $uids IMAP message ids.
680: *
681: * @return NULL
682: */
683: public function deleteMessages($folder, $uids)
684: {
685: $this->_data->deleteMessages(
686: $this->_convertToInternal($folder),
687: $uids
688: );
689: }
690:
691: /**
692: * Moves a message to a new folder.
693: *
694: * @param integer $uid IMAP message id.
695: * @param string $old_folder Source folder.
696: * @param string $new_folder Target folder.
697: *
698: * @return NULL
699: */
700: public function moveMessage($uid, $old_folder, $new_folder)
701: {
702: $this->_data->moveMessage(
703: $uid,
704: $this->_convertToInternal($old_folder),
705: $this->_convertToInternal($new_folder)
706: );
707: }
708:
709: /**
710: * Expunges messages in the current folder.
711: *
712: * @param string $folder The folder to expunge.
713: *
714: * @return NULL
715: */
716: public function expunge($folder)
717: {
718: $this->_data->expunge($this->_convertToInternal($folder));
719: }
720: }
721: