1<?php
2
3/**
4 * @var string $name
5 * @var boolean $noinferiors
6 * @var boolean $marked
7 * @var boolean $haschildren
8 * @var boolean $hasnochildren
9 * @var boolean $noselect
10 * @var boolean $nonexistent
11 * @var int $unseen
12 * @var int $messages
13 * @var string $delimiter
14
15 */
16
17namespace GO\Email\Model;
18
19use GO;
20
21class ImapMailbox extends \GO\Base\Model {
22
23	/**
24	 *
25	 * @var Account
26	 */
27	private $_account;
28	private $_children;
29
30	/**
31	 *
32	 * @var StringHelper
33	 */
34	private $_attributes;
35
36	public function __construct(Account $account, $attributes) {
37		$this->_account = $account;
38
39		//\GO::debug("GO\Email\Model\ImapMailbox:".var_export($attributes,true));
40
41		$this->_attributes = $attributes;
42
43//		if(isset($this->_attributes['name']))
44//			$this->_attributes['name']=\GO\Base\Mail\Utils::utf7_decode($this->_attributes["name"]);
45
46		//throw new \Exception(var_export($attributes, true));
47
48		//$this->_children = array();
49	}
50
51	public function __isset($name) {
52		$var = $this->__get($name);
53		return isset($var);
54	}
55
56	public function __get($name) {
57
58		$getter = "get".$name;
59		if(method_exists($this, $getter))
60			return $this->$getter();
61
62		return isset($this->_attributes[$name]) ? $this->_attributes[$name] : null;
63	}
64
65	public function getHasChildren($asSubscribedMailbox=false)
66	{
67		if($this->isRootMailbox()) {
68			return false;
69		}
70
71		//todo make compatible with servers that can't return subscribed flag
72
73		if(isset($this->_attributes['haschildren']) && $this->_attributes['haschildren']) {
74			return true;
75		}
76		if(isset($this->_attributes['hasnochildren']) && $this->_attributes['hasnochildren']) {
77			return false;
78		}
79
80		if(isset($this->_attributes['noinferiors']) && $this->_attributes['noinferiors']) {
81			return false;
82		}
83
84		//\GO::debug($this->_attributes['haschildren'])	;
85
86		//oh oh, bad mailserver can't tell us if it has children. Let's find out the expensive way
87		$folders = $this->getAccount()->openImapConnection()->list_folders($asSubscribedMailbox, false,"",$this->name.$this->delimiter.'%');
88		//store values for caching
89		$this->_attributes['haschildren']= count($folders)>0;
90		$this->_attributes['hasnochildren']= count($folders)==0;
91		return $this->_attributes['haschildren'];
92
93	}
94
95	public function getSubscribed(){
96
97		//todo make compatible with servers that can't return subscribed flag
98
99		return !empty($this->_attributes['subscribed']);
100
101	}
102
103	public function areFlagsPermitted()
104	{
105		return !empty(GO::config()->email_enable_labels);
106
107		/**
108		 * Use config until we found better way how detect flags from IMAP headers
109		 */
110		if(!isset($this->_attributes["permittedFlags"])) {
111			$this->_attributes["permittedFlags"]=$this->getAccount()->openImapConnection ()->permittedFlags;
112		}
113		return $this->_attributes["permittedFlags"];
114	}
115
116	public function getDelimiter(){
117		if(!isset($this->_attributes["delimiter"]))
118			$this->_attributes["delimiter"]=$this->getAccount()->openImapConnection ()->get_mailbox_delimiter ();
119
120		return $this->_attributes["delimiter"];
121	}
122
123	public function getParentName() {
124		$pos = strrpos($this->name, $this->delimiter);
125
126		if ($pos === false)
127			return false;
128
129		return substr($this->name, 0, $pos);
130	}
131
132//	public function getName($decode=false){
133//		return $decode ? \GO\Base\Mail\Utils::utf7_decode($this->_attributes["name"]) : $this->_attributes["name"];
134//	}
135
136	public function getBaseName() {
137		$name = $this->name;
138		$pos = strrpos($name, $this->delimiter);
139
140		if ($pos !== false)
141			$name= substr($this->name, $pos + 1);
142
143
144		return $name;
145	}
146
147	public function getDisplayName() {
148		switch ($this->name) {
149			case 'INBOX':
150				return \GO::t("Inbox", "email");
151				break;
152			case $this->getAccount()->sent:
153				return \GO::t("Sent items", "email");
154				break;
155			case $this->getAccount()->trash:
156				return \GO::t("Trash", "email");
157				break;
158			case $this->getAccount()->drafts:
159				return \GO::t("Drafts", "email");
160				break;
161			case 'Spam':
162				return \GO::t("Spam", "email");
163			default:
164				return $this->getBaseName(true);
165				break;
166		}
167	}
168
169	public function addChild(ImapMailbox $mailbox) {
170		if(!isset($this->_children)){
171			$this->_children = array();
172		}
173		$this->_children[] = $mailbox;
174	}
175
176	public function isRootMailbox(){
177		//throw new \Exception($this->name.$this->delimiter.' = '.$this->getAccount()->mbroot);
178		return $this->name.$this->delimiter==$this->getAccount()->mbroot;
179	}
180
181	/**
182	 * Get all child nodes of mailbox
183	 * @param boolean $subscribed Only get subscribed folders
184	 * @param boolean $withStatus Get the status of the folder (Unseen and message count)
185	 * @return ImapMailbox[] the mailbox folders
186	 */
187	public function getChildren($subscribed=false, $withStatus=true) {
188		if(!isset($this->_children)){
189
190			$imap = $this->getAccount()->openImapConnection();
191
192			$this->_children = array();
193
194			if(!$this->isRootMailbox())
195			{
196				$folders = $imap->list_folders($subscribed,$withStatus,"","$this->name$this->delimiter%");
197				foreach($folders as $folder){
198					if (rtrim($folder['name'], $this->delimiter) != $this->name) {
199						$mailbox = new ImapMailbox($this->account,$folder);
200						$this->_children[]=$mailbox;
201					}
202				}
203			}
204
205		}
206
207		return $this->_children;
208	}
209
210	/**
211	 *
212	 * @return Account
213	 */
214	public function getAccount() {
215		return $this->_account;
216	}
217
218//	public function isSent(){
219//		return $this->name==$this->_account->sent;
220//	}
221
222	public function rename($name){
223
224	  if($this->getAccount()->getPermissionLevel() <= \GO\Base\Model\Acl::READ_PERMISSION)
225		  throw new \GO\Base\Exception\AccessDenied();
226
227		$name = trim($name);
228		$this->_validateName($name);
229
230		$parentName = $this->getParentName();
231		$newMailbox = empty($parentName) ? $name : $parentName.$this->delimiter.$name;
232
233//		throw new \Exception($this->name." -> ".$newMailbox);
234
235		if($this->getAccount()->openImapConnection()->rename_folder($this->name, $newMailbox)) {
236			$this->_attributes['name'] = $newMailbox;
237
238			return true;
239		}
240		return false;
241	}
242
243	public function delete(){
244	  if($this->getAccount()->getPermissionLevel() <= \GO\Base\Model\Acl::READ_PERMISSION)
245		 throw new \GO\Base\Exception\AccessDenied();
246
247		if($this->getHasChildren()) {
248
249			foreach ($this->getChildren() as $mailBox) {
250
251
252				if(!$mailBox->delete()) {
253					return false;
254				}
255			}
256
257		}
258
259	  return $this->getAccount()->openImapConnection()->delete_folder($this->name);
260	}
261
262	public function truncate()
263	{
264		if ($this->getAccount()->getPermissionLevel() <= \GO\Base\Model\Acl::READ_PERMISSION) {
265			throw new \GO\Base\Exception\AccessDenied();
266		}
267		$imap = $this->getAccount()->openImapConnection($this->name);
268		$success = true;
269		foreach ($imap->get_folders($this->_account->trash) as $folder) {
270			if($folder['name'] == $this->_account->trash || empty($folder['name'])) {
271				continue;
272			}
273			$success = $success &&$imap->delete_folder($folder['name']);
274		}
275		$sort = $imap->sort_mailbox();
276		return $imap->delete($sort);
277	}
278
279	private function _validateName($name){
280		$illegalChars = '/';
281
282		if($this->getDelimiter()!='/'){
283			$illegalChars .=$this->getDelimiter();
284		}
285
286		if(preg_match('/['.preg_quote($illegalChars,'/').']/', $name)){
287			throw new \Exception(sprintf(\GO::t("The name contained one of the following illegal characters %s"),': '.$illegalChars));
288		}else
289		{
290			return true;
291		}
292	}
293
294	public function createChild($name, $subscribe=true){
295	  if($this->getAccount()->getPermissionLevel() <= \GO\Base\Model\Acl::READ_PERMISSION)
296		  throw new \GO\Base\Exception\AccessDenied();
297		$name = trim($name);
298		$newMailbox = empty($this->name) ? $name : $this->name.$this->delimiter.$name;
299
300		$this->_validateName($name);
301
302		//throw new \Exception($newMailbox);
303
304		return $this->getAccount()->openImapConnection()->create_folder($newMailbox, $subscribe);
305	}
306
307	public function move(ImapMailbox $targetMailbox){
308		if($this->getAccount()->getPermissionLevel() <= \GO\Base\Model\Acl::READ_PERMISSION) {
309			throw new \GO\Base\Exception\AccessDenied();
310		}
311		$newMailbox = "";
312
313		if(!empty($targetMailbox->name)) {
314			$newMailbox .= $targetMailbox->name . $this->delimiter;
315		}
316
317		$newMailbox .= $this->getBaseName();
318
319		$success = $this->getAccount()->openImapConnection()->rename_folder($this->name, $newMailbox);
320		if(!$success) {
321			return false;
322		}
323		$this->_attributes['name'] = $newMailbox;
324
325		return true;
326	}
327
328	public function setSubscribed($value){
329		if($value)
330			$this->_attributes['subscribed'] =  $this->getAccount()->openImapConnection()->subscribe($this->name);
331		else
332			$this->_attributes['subscribed'] = !$this->getAccount()->openImapConnection()->unsubscribe($this->name);
333	}
334
335	public function subscribe(){
336		$this->subscribed=true;
337		return $this->subscribed;
338	}
339
340	public function unsubscribe(){
341		$this->subscribed=false;
342		return !$this->subscribed;
343	}
344
345	public function __toString() {
346		return $this->_attributes['name'];
347	}
348
349	private function _getCacheKey(){
350		$user_id = \GO::user() ? \GO::user()->id : 0;
351		return $user_id.':'.$this->_account->id.':'.$this->name;
352	}
353
354
355	public function getUnseen(){
356		if(!isset($this->_attributes['unseen'])){
357			try {
358			if(!$this->noselect){
359				$unseen=$this->getAccount()->openImapConnection($this->name)->get_unseen();
360				$this->_attributes['unseen']=$unseen['count'];
361			}  else {
362				$this->_attributes['unseen']=0;
363			}}
364			catch(\Exception $e) {
365				$this->_attributes['unseen'] = 0;
366				\GO::debug($e);
367			}
368		}
369		return $this->_attributes['unseen'];
370	}
371	/**
372	 * Check if this mailbox exists
373	 * @return boolean
374	 */
375	public function exists(){
376		$imap = $this->getAccount()->justConnect();
377
378		$exists = $imap->select_mailbox($this->name);
379
380		if(!$exists)
381			$imap->last_error(); //clear the not exist error
382
383		return $exists;
384	}
385
386	public function hasAlarm(){
387		//caching is required. We don't use the session because we need to close
388		//session writing when checking email accounts. Otherwise it can block the
389		//session to long.
390		if(\GO::cache() instanceof \GO\Base\Cache\None)
391			return false;
392
393		$cached = \GO::cache()->get($this->_getCacheKey());
394		return ($cached != $this->unseen && $this->unseen>0);
395	}
396
397	/**
398	 * Set's the cache to the number of unseen messages
399	 */
400	public function snoozeAlarm(){
401		GO::cache()->set($this->_getCacheKey(), $this->unseen);
402	}
403
404	/**
405	 * Returns true if this is the sent, trash or drafts folder.
406	 *
407	 * @return boolean
408	 */
409	public function isSpecial(){
410		return (
411						$this->name==$this->account->sent ||
412						$this->name==$this->account->trash ||
413						$this->name==$this->account->drafts
414						);
415	}
416
417	/**
418	 * Return true if this mailbox should be displayed in the main tree.
419	 * Only subscribed folders should be visible. But some folders can't be subscribed like shared namespaces.
420	 * If they have children then they must be displayd too.
421	 * @return boolean
422	 */
423	public function isVisible(){
424			return $this->subscribed ||  $this->getHasChildren(true);
425	}
426}
427