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_Mbox
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_Mbox extends Zend_Mail_Storage_Mbox 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     * Create instance with parameters
66     *
67     * Disallowed parameters are:
68     *   - filename use Zend_Mail_Storage_Mbox for a single file
69     * Supported parameters are:
70     *   - dirname rootdir of mbox structure
71     *   - folder intial selected folder, default is 'INBOX'
72     *
73     * @param array $params mail reader specific parameters
74     * @throws Zend_Mail_Storage_Exception
75     */
76    public function __construct($params)
77    {
78        if (is_array($params)) {
79            $params = (object)$params;
80        }
81
82        if (isset($params->filename)) {
83            /**
84             * @see Zend_Mail_Storage_Exception
85             */
86            throw new Zend_Mail_Storage_Exception('use Zend_Mail_Storage_Mbox for a single file');
87        }
88
89        if (!isset($params->dirname) || !is_dir($params->dirname)) {
90            /**
91             * @see Zend_Mail_Storage_Exception
92             */
93            throw new Zend_Mail_Storage_Exception('no valid dirname given in params');
94        }
95
96        $this->_rootdir = rtrim($params->dirname, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
97
98        $this->_buildFolderTree($this->_rootdir);
99        $this->selectFolder(!empty($params->folder) ? $params->folder : 'INBOX');
100        $this->_has['top']      = true;
101        $this->_has['uniqueid'] = false;
102    }
103
104    /**
105     * find all subfolders and mbox files for folder structure
106     *
107     * Result is save in Zend_Mail_Storage_Folder instances with the root in $this->_rootFolder.
108     * $parentFolder and $parentGlobalName are only used internally for recursion.
109     *
110     * @param string $currentDir call with root dir, also used for recursion.
111     * @param Zend_Mail_Storage_Folder|null $parentFolder used for recursion
112     * @param string $parentGlobalName used for rescursion
113     * @return null
114     * @throws Zend_Mail_Storage_Exception
115     */
116    protected function _buildFolderTree($currentDir, $parentFolder = null, $parentGlobalName = '')
117    {
118        if (!$parentFolder) {
119            $this->_rootFolder = new Zend_Mail_Storage_Folder('/', '/', false);
120            $parentFolder = $this->_rootFolder;
121        }
122
123        $dh = @opendir($currentDir);
124        if (!$dh) {
125            /**
126             * @see Zend_Mail_Storage_Exception
127             */
128            throw new Zend_Mail_Storage_Exception("can't read dir $currentDir");
129        }
130        while (($entry = readdir($dh)) !== false) {
131            // ignore hidden files for mbox
132            if ($entry[0] == '.') {
133                continue;
134            }
135            $absoluteEntry = $currentDir . $entry;
136            $globalName = $parentGlobalName . DIRECTORY_SEPARATOR . $entry;
137            if (is_file($absoluteEntry) && $this->_isMboxFile($absoluteEntry)) {
138                $parentFolder->$entry = new Zend_Mail_Storage_Folder($entry, $globalName);
139                continue;
140            }
141            if (!is_dir($absoluteEntry) /* || $entry == '.' || $entry == '..' */) {
142                continue;
143            }
144            $folder = new Zend_Mail_Storage_Folder($entry, $globalName, false);
145            $parentFolder->$entry = $folder;
146            $this->_buildFolderTree($absoluteEntry . DIRECTORY_SEPARATOR, $folder, $globalName);
147        }
148
149        closedir($dh);
150    }
151
152    /**
153     * get root folder or given folder
154     *
155     * @param string $rootFolder get folder structure for given folder, else root
156     * @return Zend_Mail_Storage_Folder root or wanted folder
157     * @throws Zend_Mail_Storage_Exception
158     */
159    public function getFolders($rootFolder = null)
160    {
161        if (!$rootFolder) {
162            return $this->_rootFolder;
163        }
164
165        $currentFolder = $this->_rootFolder;
166        $subname = trim($rootFolder, DIRECTORY_SEPARATOR);
167        while ($currentFolder) {
168            @list($entry, $subname) = @explode(DIRECTORY_SEPARATOR, $subname, 2);
169            $currentFolder = $currentFolder->$entry;
170            if (!$subname) {
171                break;
172            }
173        }
174
175        if ($currentFolder->getGlobalName() != DIRECTORY_SEPARATOR . trim($rootFolder, DIRECTORY_SEPARATOR)) {
176            /**
177             * @see Zend_Mail_Storage_Exception
178             */
179            throw new Zend_Mail_Storage_Exception("folder $rootFolder not found");
180        }
181        return $currentFolder;
182    }
183
184    /**
185     * select given folder
186     *
187     * folder must be selectable!
188     *
189     * @param Zend_Mail_Storage_Folder|string $globalName global name of folder or instance for subfolder
190     * @return null
191     * @throws Zend_Mail_Storage_Exception
192     */
193    public function selectFolder($globalName)
194    {
195        $this->_currentFolder = (string)$globalName;
196
197        // getting folder from folder tree for validation
198        $folder = $this->getFolders($this->_currentFolder);
199
200        try {
201            $this->_openMboxFile($this->_rootdir . $folder->getGlobalName());
202        } catch(Zend_Mail_Storage_Exception $e) {
203            // check what went wrong
204            if (!$folder->isSelectable()) {
205                /**
206                 * @see Zend_Mail_Storage_Exception
207                 */
208                throw new Zend_Mail_Storage_Exception("{$this->_currentFolder} is not selectable", 0, $e);
209            }
210            // seems like file has vanished; rebuilding folder tree - but it's still an exception
211            $this->_buildFolderTree($this->_rootdir);
212            /**
213             * @see Zend_Mail_Storage_Exception
214             */
215            throw new Zend_Mail_Storage_Exception('seems like the mbox file has vanished, I\'ve rebuild the ' .
216                                                         'folder tree, search for an other folder and try again', 0, $e);
217        }
218    }
219
220    /**
221     * get Zend_Mail_Storage_Folder instance for current folder
222     *
223     * @return Zend_Mail_Storage_Folder instance of current folder
224     * @throws Zend_Mail_Storage_Exception
225     */
226    public function getCurrentFolder()
227    {
228        return $this->_currentFolder;
229    }
230
231    /**
232     * magic method for serialize()
233     *
234     * with this method you can cache the mbox class
235     *
236     * @return array name of variables
237     */
238    public function __sleep()
239    {
240        return array_merge(parent::__sleep(), array('_currentFolder', '_rootFolder', '_rootdir'));
241    }
242
243    /**
244     * magic method for unserialize()
245     *
246     * with this method you can cache the mbox class
247     *
248     * @return null
249     */
250    public function __wakeup()
251    {
252        // if cache is stall selectFolder() rebuilds the tree on error
253        parent::__wakeup();
254    }
255}
256