1<?php
2/**
3 * Zend Framework
4 *
5 * LICENSE
6 *
7 * This source file is subject to the new BSD license that is bundled
8 * with this package in the file LICENSE.txt.
9 * It is also available through the world-wide-web at this URL:
10 * http://framework.zend.com/license/new-bsd
11 * If you did not receive a copy of the license and are unable to
12 * obtain it through the world-wide-web, please send an email
13 * to license@zend.com so we can send you a copy immediately.
14 *
15 * @category   Zend
16 * @package    Zend_Mail
17 * @subpackage Storage
18 * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
19 * @license    http://framework.zend.com/license/new-bsd     New BSD License
20 * @version    $Id$
21 */
22
23
24/**
25 * @see Zend_Mail_Storage_Folder
26 */
27
28/**
29 * @see Zend_Mail_Storage_Folder_Interface
30 */
31
32/**
33 * @see Zend_Mail_Storage_Maildir
34 */
35
36
37/**
38 * @category   Zend
39 * @package    Zend_Mail
40 * @subpackage Storage
41 * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
42 * @license    http://framework.zend.com/license/new-bsd     New BSD License
43 */
44class Zend_Mail_Storage_Folder_Maildir extends Zend_Mail_Storage_Maildir implements Zend_Mail_Storage_Folder_Interface
45{
46    /**
47     * Zend_Mail_Storage_Folder root folder for folder structure
48     * @var Zend_Mail_Storage_Folder
49     */
50    protected $_rootFolder;
51
52    /**
53     * rootdir of folder structure
54     * @var string
55     */
56    protected $_rootdir;
57
58    /**
59     * name of current folder
60     * @var string
61     */
62    protected $_currentFolder;
63
64    /**
65     * delim char for subfolders
66     * @var string
67     */
68    protected $_delim;
69
70    /**
71     * Create instance with parameters
72     * Supported parameters are:
73     *   - dirname rootdir of maildir structure
74     *   - delim   delim char for folder structur, default is '.'
75     *   - folder intial selected folder, default is 'INBOX'
76     *
77     * @param array $params mail reader specific parameters
78     * @throws Zend_Mail_Storage_Exception
79     */
80    public function __construct($params)
81    {
82        if (is_array($params)) {
83            $params = (object)$params;
84        }
85
86        if (!isset($params->dirname) || !is_dir($params->dirname)) {
87            /**
88             * @see Zend_Mail_Storage_Exception
89             */
90            throw new Zend_Mail_Storage_Exception('no valid dirname given in params');
91        }
92
93        $this->_rootdir = rtrim($params->dirname, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
94
95        $this->_delim = isset($params->delim) ? $params->delim : '.';
96
97        $this->_buildFolderTree();
98        $this->selectFolder(!empty($params->folder) ? $params->folder : 'INBOX');
99        $this->_has['top'] = true;
100        $this->_has['flags'] = true;
101    }
102
103    /**
104     * find all subfolders and mbox files for folder structure
105     *
106     * Result is save in Zend_Mail_Storage_Folder instances with the root in $this->_rootFolder.
107     * $parentFolder and $parentGlobalName are only used internally for recursion.
108     *
109     * @return null
110     * @throws Zend_Mail_Storage_Exception
111     */
112    protected function _buildFolderTree()
113    {
114        $this->_rootFolder = new Zend_Mail_Storage_Folder('/', '/', false);
115        $this->_rootFolder->INBOX = new Zend_Mail_Storage_Folder('INBOX', 'INBOX', true);
116
117        $dh = @opendir($this->_rootdir);
118        if (!$dh) {
119            /**
120             * @see Zend_Mail_Storage_Exception
121             */
122            throw new Zend_Mail_Storage_Exception("can't read folders in maildir");
123        }
124        $dirs = array();
125        while (($entry = readdir($dh)) !== false) {
126            // maildir++ defines folders must start with .
127            if ($entry[0] != '.' || $entry == '.' || $entry == '..') {
128                continue;
129            }
130            if ($this->_isMaildir($this->_rootdir . $entry)) {
131                $dirs[] = $entry;
132            }
133        }
134        closedir($dh);
135
136        sort($dirs);
137        $stack = array(null);
138        $folderStack = array(null);
139        $parentFolder = $this->_rootFolder;
140        $parent = '.';
141
142        foreach ($dirs as $dir) {
143            do {
144                if (strpos($dir, $parent) === 0) {
145                    $local = substr($dir, strlen($parent));
146                    if (strpos($local, $this->_delim) !== false) {
147                        /**
148                         * @see Zend_Mail_Storage_Exception
149                         */
150                        throw new Zend_Mail_Storage_Exception('error while reading maildir');
151                    }
152                    array_push($stack, $parent);
153                    $parent = $dir . $this->_delim;
154                    $folder = new Zend_Mail_Storage_Folder($local, substr($dir, 1), true);
155                    $parentFolder->$local = $folder;
156                    array_push($folderStack, $parentFolder);
157                    $parentFolder = $folder;
158                    break;
159                } else if ($stack) {
160                    $parent = array_pop($stack);
161                    $parentFolder = array_pop($folderStack);
162                }
163            } while ($stack);
164            if (!$stack) {
165                /**
166                 * @see Zend_Mail_Storage_Exception
167                 */
168                throw new Zend_Mail_Storage_Exception('error while reading maildir');
169            }
170        }
171    }
172
173    /**
174     * get root folder or given folder
175     *
176     * @param string $rootFolder get folder structure for given folder, else root
177     * @return Zend_Mail_Storage_Folder root or wanted folder
178     * @throws Zend_Mail_Storage_Exception
179     */
180    public function getFolders($rootFolder = null)
181    {
182        if (!$rootFolder || $rootFolder == 'INBOX') {
183            return $this->_rootFolder;
184        }
185
186        // rootdir is same as INBOX in maildir
187        if (strpos($rootFolder, 'INBOX' . $this->_delim) === 0) {
188            $rootFolder = substr($rootFolder, 6);
189        }
190        $currentFolder = $this->_rootFolder;
191        $subname = trim($rootFolder, $this->_delim);
192        while ($currentFolder) {
193            @list($entry, $subname) = @explode($this->_delim, $subname, 2);
194            $currentFolder = $currentFolder->$entry;
195            if (!$subname) {
196                break;
197            }
198        }
199
200        if ($currentFolder->getGlobalName() != rtrim($rootFolder, $this->_delim)) {
201            /**
202             * @see Zend_Mail_Storage_Exception
203             */
204            throw new Zend_Mail_Storage_Exception("folder $rootFolder not found");
205        }
206        return $currentFolder;
207    }
208
209    /**
210     * select given folder
211     *
212     * folder must be selectable!
213     *
214     * @param Zend_Mail_Storage_Folder|string $globalName global name of folder or instance for subfolder
215     * @return null
216     * @throws Zend_Mail_Storage_Exception
217     */
218    public function selectFolder($globalName)
219    {
220        $this->_currentFolder = (string)$globalName;
221
222        // getting folder from folder tree for validation
223        $folder = $this->getFolders($this->_currentFolder);
224
225        try {
226            $this->_openMaildir($this->_rootdir . '.' . $folder->getGlobalName());
227        } catch(Zend_Mail_Storage_Exception $e) {
228            // check what went wrong
229            if (!$folder->isSelectable()) {
230                /**
231                 * @see Zend_Mail_Storage_Exception
232                 */
233                throw new Zend_Mail_Storage_Exception("{$this->_currentFolder} is not selectable", 0, $e);
234            }
235            // seems like file has vanished; rebuilding folder tree - but it's still an exception
236            $this->_buildFolderTree($this->_rootdir);
237            /**
238             * @see Zend_Mail_Storage_Exception
239             */
240            throw new Zend_Mail_Storage_Exception('seems like the maildir has vanished, I\'ve rebuild the ' .
241                                                         'folder tree, search for an other folder and try again', 0, $e);
242        }
243    }
244
245    /**
246     * get Zend_Mail_Storage_Folder instance for current folder
247     *
248     * @return Zend_Mail_Storage_Folder instance of current folder
249     * @throws Zend_Mail_Storage_Exception
250     */
251    public function getCurrentFolder()
252    {
253        return $this->_currentFolder;
254    }
255}
256