1<?php
2/**
3 * Copyright Intermesh
4 *
5 * This file is part of Group-Office. You should have received a copy of the
6 * Group-Office license along with Group-Office. See the file /LICENSE.TXT
7 *
8 * If you have questions write an e-mail to info@intermesh.nl
9 *
10 * @version $Id: LinkedEmail.php 7607 2011-09-01 15:38:01Z <<USERNAME>> $
11 * @copyright Copyright Intermesh
12 * @author <<FIRST_NAME>> <<LAST_NAME>> <<EMAIL>>@intermesh.nl
13 */
14
15namespace GO\Email\Model;
16
17use GO;
18
19/**
20 * The LinkedEmail model
21 *
22 * @property boolean $ignore_sent_folder
23 * @property int $password_encrypted
24 * @property string $smtp_password
25 * @property string $smtp_username
26 * @property string $smtp_encryption 'ssl' or 'tls'
27 * @property boolean $smtp_allow_self_signed Allow SSL/TLS and STARTTLS connection with self signed certificates. Enabling this will not check the identity of the server
28 * @property boolean $imap_allow_self_signed Allow SSL/TLS and STARTTLS connection with self signed certificates. Enabling this will not check the identity of the server
29 * @property string $imap_encryption 'ssl' or 'tls'
30 * @property int $smtp_port
31 * @property StringHelper $smtp_host
32 * @property StringHelper $spam
33 * @property StringHelper $trash
34 * @property StringHelper $drafts
35 * @property StringHelper $sent
36 * @property StringHelper $mbroot
37 * @property StringHelper $password
38 * @property StringHelper $username
39 * @property boolean $novalidate_cert
40 * @property boolean $deprecated_use_ssl
41 * @property boolean $do_not_mark_as_read
42 * @property int $port
43 * @property StringHelper $host
44 * @property StringHelper $type
45 * @property int $acl_id
46 * @property int $user_id
47 * @property int $id
48 * @property StringHelper $check_mailboxes
49 * @property boolean $signature_below_reply
50 * @property int $sieve_port
51 * @property boolean $sieve_tls
52 * @property boolean $sieve_usetls
53 */
54class Account extends \GO\Base\Db\ActiveRecord {
55
56	const ACL_DELEGATED_PERMISSION=15;
57
58	/**
59	 * Set to false if you don't want the IMAP connection on save.
60	 *
61	 * @var boolean
62	 */
63	public $checkImapConnectionOnSave=true;
64
65
66	private $_imap;
67
68	/**
69	 * Set to false if you want to keep the password in the session only.
70	 *
71	 * @var boolean
72	 */
73	public $store_password=true;
74
75	/**
76	 * Set to false if, for example from the imapauth module, the smtp password
77	 * should not be stored in the database, only in the session.
78	 * @var boolean
79	 */
80	public $store_smtp_password=true;
81
82
83	/**
84	 * Holds the password temporaily while saving the account model without storing it in the database. ($this->store_password=false)
85	 *
86	 * @var boolean
87	 */
88	private $_session_password='';
89	private $_session_smtp_password='';
90
91
92	/**
93	 * Returns a static model of itself
94	 *
95	 * @param String $className
96	 * @return Account
97	 */
98	public static function model($className=__CLASS__) {
99		return parent::model($className);
100	}
101
102	/**
103	 * Enable this function if you want this model to check the acl's automatically.
104	 */
105	public function aclField() {
106		return 'acl_id';
107	}
108
109	/**
110	 * Returns the table name
111	 */
112	public function tableName() {
113		return 'em_accounts';
114	}
115
116	protected function init() {
117
118		$this->columns['host']['required']=true;
119		$this->columns['username']['required']=true;
120		$this->columns['password']['required']=true;
121		parent::init();
122	}
123
124	public function attributeLabels() {
125		$attr = parent::attributeLabels();
126
127		$attr['username']=\GO::t("Username");
128		$attr['password']=\GO::t("Password");
129
130		return $attr;
131	}
132
133	/**
134	 * Here you can define the relations of this model with other models.
135	 * See the parent class for a more detailed description of the relations.
136	 */
137	public function relations() {
138		return array(
139				'aliases' => array('type'=>self::HAS_MANY, 'model'=>'GO\Email\Model\Alias', 'field'=>'account_id','delete'=>true),
140				'filters' => array('type'=>self::HAS_MANY, 'model'=>'GO\Email\Model\Filter', 'field'=>'account_id','delete'=>true, 'findParams'=>  \GO\Base\Db\FindParams::newInstance()->order("priority")),
141				'portletFolders' => array('type'=>self::HAS_MANY, 'model'=>'GO\Email\Model\PortletFolder', 'field'=>'account_id','delete'=>true)
142		);
143	}
144
145	protected function _trimSpacesFromAttributes() {
146		if(!static::$trimOnSave)
147			return;
148		foreach($this->columns as $field=>$col){
149
150			// For passwords it is allowed to apply spaces at the begin or end.
151			if($field == 'password' || $field == 'smtp_password'){
152				return;
153			}
154
155			if(isset($this->_attributes[$field]) && $col['type'] == \PDO::PARAM_STR){
156				$this->_attributes[$field] = trim($this->_attributes[$field]);
157			}
158		}
159	}
160
161	protected function beforeSave() {
162		if($this->isModified('password')){
163//			$decrypted = \GO\Base\Util\Crypt::decrypt($this->getOldAttributeValue('password'));
164//
165//			if($decrypted==$this->password){
166//				$this->resetAttribute('password');
167//			}else
168//			{
169				$encrypted = \GO\Base\Util\Crypt::encrypt($this->password);
170				if($encrypted){
171					$this->password = $encrypted;
172					$this->password_encrypted=2;//deprecated. remove when email is mvc style.
173				}
174//			}
175
176			unset(GO::session()->values['emailModule']['accountPasswords'][$this->id]);
177		}
178
179//		if (!empty($this->id) && !empty(GO::session()->values['emailModule']['accountPasswords'][$this->id]))
180//			unset(GO::session()->values['emailModule']['accountPasswords'][$this->id]);
181
182		if($this->isModified('smtp_password')){
183			$encrypted = \GO\Base\Util\Crypt::encrypt($this->smtp_password);
184			if($encrypted)
185				$this->smtp_password = $encrypted;
186		}
187
188		if(
189				($this->isNew || $this->isModified("mbroot") || $this->isModified("host") || $this->isModified("port") || $this->isModified("username")  || $this->isModified("password") || $this->isModified("imap_encryption"))
190				&& $this->checkImapConnectionOnSave
191			){
192
193			$imap = $this->openImapConnection();
194			$this->mbroot=$imap->check_mbroot($this->mbroot);
195
196			$this->_createDefaultFolder('sent');
197			$this->_createDefaultFolder('trash');
198			$this->_createDefaultFolder('spam');
199			$this->_createDefaultFolder('drafts');
200		}
201
202		if (empty($this->store_password)) {
203			$this->_session_password = $this->password;
204			$this->password = '';
205			$this->password_encrypted = 0;
206		}
207
208		if (empty($this->store_smtp_password)) {
209			$this->_session_smtp_password = $this->smtp_password;
210			$this->smtp_password = '';
211		}
212
213		return parent::beforeSave();
214	}
215
216	protected function afterLoad() {
217		$this->store_smtp_password = $this->store_password = !isset(\GO::session()->values['emailModule']['accountPasswords'][$this->id]);
218
219		return parent::afterLoad();
220	}
221
222	protected function afterSave($wasNew) {
223		if (!empty($this->_session_password)) {
224
225			if (!isset(\GO::session()->values['emailModule']) || !isset(\GO::session()->values['emailModule']['accountPasswords']) || !is_array(\GO::session()->values['emailModule']['accountPasswords'])) {
226				\GO::session()->values['emailModule']['accountPasswords'] = array();
227			}
228			\GO::session()->values['emailModule']['accountPasswords'][$this->id] = $this->_session_password;
229		}
230		if (!empty($this->_session_smtp_password)) {
231			if (!isset(\GO::session()->values['emailModule']) || !isset(\GO::session()->values['emailModule']['smtpPasswords']) || !is_array(\GO::session()->values['emailModule']['smtpPasswords'])) {
232				\GO::session()->values['emailModule']['smtpPasswords'] = array();
233			}
234			\GO::session()->values['emailModule']['smtpPasswords'][$this->id] = $this->_session_smtp_password;
235		}
236
237		if ($wasNew) {
238			Label::model()->createDefaultLabels($this->id);
239		}
240
241		if($wasNew) {
242			$user = \go\core\model\User::findById($this->user_id);
243			if($user->isAdmin()) {
244				//add admin group
245				$group = \go\core\model\Group::find()->where(['isUserGroupFor' => $user->id])->single();
246				$this->getAcl()->addGroup($group->id, \go\core\model\Acl::LEVEL_MANAGE);
247			}
248		}
249
250		return parent::afterSave($wasNew);
251	}
252
253	protected function afterDelete() {
254		Label::model()->deleteAccountLabels($this->id);
255		return true;
256	}
257
258	private $_mailboxes;
259
260	/**
261	 * Get all mailboxes on the active connection in a namesapce
262	 *
263	 * @return array All mailboxes
264	 */
265	public function getMailboxes(){
266		if(!isset($this->_mailboxes)){
267			$this->_mailboxes= $this->openImapConnection()->get_folders($this->mbroot);
268		}
269		return $this->_mailboxes;
270	}
271
272	private $_subscribed;
273
274	/**
275	 * Get the mailboxes the user is subscribed to in a namespace
276	 *
277	 * @return array Subscribed mailboxes
278	 */
279	public function getSubscribed(){
280		if(!isset($this->_subscribed)){
281			$this->_subscribed= $this->openImapConnection()->get_folders($this->mbroot, true);
282		}
283		return $this->_subscribed;
284	}
285
286
287	private function _createDefaultFolder($name){
288
289		if(empty($this->$name))
290			return false;
291
292		$mailboxes = $this->getMailboxes();
293
294		if(!isset($mailboxes[$this->$name])){
295			$imap = $this->openImapConnection();
296			if(!$imap->create_folder($this->$name)){
297				//clear errors like:
298				//A5 NO Client tried to access nonexistent namespace. ( Mailbox name should probably be prefixed with: INBOX. )
299				$imap->clear_errors();
300				$this->mbroot= $this->openImapConnection()->check_mbroot("INBOX");
301
302				$this->$name = $this->mbroot.$this->$name;
303
304				if(!isset($mailboxes[$this->$name])){
305					$imap->create_folder($this->$name);
306				}
307			}
308		}
309	}
310
311
312	public function decryptPassword(){
313
314		if (!empty(GO::session()->values['emailModule']['accountPasswords'][$this->id])) {
315			$decrypted = \GO\Base\Util\Crypt::decrypt(GO::session()->values['emailModule']['accountPasswords'][$this->id]);
316		} else {
317
318			//support for z-push without storing passwords
319			if (empty($this->password) &&	method_exists('Request','GetAuthPassword') && Request::GetAuthUser()==$this->username) {
320
321				$decrypted = Request::GetAuthPassword();
322			}else
323			{
324				if(empty($this->password)) {
325					return "";
326				}
327				$decrypted = \GO\Base\Util\Crypt::decrypt($this->password);
328			}
329		}
330
331		return $decrypted ? $decrypted : $this->password;
332	}
333
334	public function decryptSmtpPassword(){
335		if (!empty(\GO::session()->values['emailModule']['smtpPasswords'][$this->id])) {
336			$decrypted = \GO\Base\Util\Crypt::decrypt(\GO::session()->values['emailModule']['smtpPasswords'][$this->id]);
337		} else {
338
339			//support for z-push without storing passwords
340			if (empty($this->smtp_password) &&	method_exists('Request','GetAuthPassword') && Request::GetAuthUser()==$this->smtp_username) {
341
342				$decrypted = Request::GetAuthPassword();
343			}else
344			{
345				$decrypted = \GO\Base\Util\Crypt::decrypt($this->smtp_password);
346			}
347		}
348
349		return $decrypted ? $decrypted : $this->smtp_password;
350	}
351
352	/**
353	 * Open a connection to the imap server.
354	 *
355	 * @param StringHelper $mailbox
356	 * @return \GO\Base\Mail\Imap
357	 */
358	public function openImapConnection($mailbox = 'INBOX')
359	{
360
361		if (empty($mailbox)) {
362			$mailbox = "INBOX";
363		}
364
365		$imap = $this->justConnect();
366
367		if (!$imap->select_mailbox($mailbox)) {
368			throw new \GO\Base\Mail\Exception\MailboxNotFound($mailbox, $imap);
369			//throw new \Exception ("Could not open IMAP mailbox $mailbox\nIMAP error: ".$imap->last_error());
370		}
371		return $imap;
372	}
373
374	/**
375	 * Connect to the IMAP server without selecting a mailbox
376	 *
377	 * @return \GO\Base\Mail\Imap
378	 */
379	public function justConnect(){
380		if(empty($this->_imap)){
381			$this->_imap = new \GO\Base\Mail\Imap();
382			$this->_imap->ignoreInvalidCertificates = $this->imap_allow_self_signed;
383			$useTLS = $this->imap_encryption == 'tls'?true:false;
384			$useSSL = $this->imap_encryption == 'ssl'?true:false;
385			$this->_imap->connect($this->host, $this->port, $this->username, $this->decryptPassword(), $useSSL, $useTLS);
386		}else
387		{
388			$this->_imap->checkConnection();
389		}
390
391		return $this->_imap;
392	}
393
394	/**
395	 * Close the connection to imap
396	 */
397	public function closeImapConnection(){
398		if(!empty($this->_imap)){
399			$this->_imap->disconnect();
400			$this->_imap=null;
401		}
402	}
403
404	public function __wakeup() {
405		//reestablish imap connection after deserialization
406		$this->_imap=null;
407	}
408
409	/**
410	 * Get the imap connection if it's open.
411	 *
412	 * @return \GO\Base\Mail\Imap
413	 */
414	public function getImapConnection(){
415		if(isset($this->_imap)){
416			return $this->_imap;
417		}else
418			return false;
419	}
420
421//	private function _getCacheKey(){
422//		$user_id = \GO::user() ? \GO::user()->id : 0;
423//		return $user_id.':'.$this->id.':uidnext';
424//	}
425
426//	protected function getHasNewMessages(){
427//
428//		\GO::debug("getHasNewMessages UIDNext ".(isset($this->_imap->selected_mailbox['uidnext']) ? $this->_imap->selected_mailbox['uidnext'] : ""));
429//
430//		if(isset($this->_imap->selected_mailbox['name']) && $this->_imap->selected_mailbox['name']=='INBOX' && !empty($this->_imap->selected_mailbox['uidnext'])){
431//
432//			$cacheKey = $this->_getCacheKey();
433//
434//			$uidnext = $value = \GO::cache()->get($cacheKey);
435//
436//			\GO::cache()->set($cacheKey, $this->_imap->selected_mailbox['uidnext']);
437//
438//			if($uidnext!==false && $uidnext!=$this->_imap->selected_mailbox['uidnext']){
439//				return true;
440//			}
441//		}
442//
443//		return false;
444//	}
445
446
447	/**
448	 * Find an account by e-mail address.
449	 *
450	 * @param StringHelper $email
451	 * @return Account
452	 */
453	public function findByEmail($email){
454
455		$joinCriteria = \GO\Base\Db\FindCriteria::newInstance()
456						->addRawCondition('t.id', 'a.account_id');
457
458		$findParams = \GO\Base\Db\FindParams::newInstance()
459						->single()
460						->join(Alias::model()->tableName(), $joinCriteria,'a')
461						->criteria(\GO\Base\Db\FindCriteria::newInstance()->addCondition('email', $email,'=','a'));
462
463		return $this->find($findParams);
464	}
465
466	/**
467	 * Get the default alias for this account.
468	 *
469	 * @return Alias
470	 */
471	public function getDefaultAlias(){
472		return Alias::model()->findSingleByAttributes(array(
473				'default'=>1,
474				'account_id'=>$this->id
475		));
476	}
477
478	/**
479	 * Get an array of mailboxes that should be checked periodically for new mail
480	 *
481	 * @return array
482	 */
483	public function getAutoCheckMailboxes(){
484		$checkMailboxArray = empty($this->check_mailboxes) ? array() : explode(',',$this->check_mailboxes);
485//		if(!in_array("INBOX", $checkMailboxArray))
486//			$checkMailboxArray[]="INBOX";
487		return $checkMailboxArray;
488	}
489
490
491	public function addAlias($email, $name, $signature='', $default=1){
492		$a = new Alias();
493		$a->account_id=$this->id;
494		$a->email=$email;
495		$a->name=$name;
496		$a->signature=$signature;
497		$a->default=$default;
498		$a->save(true);
499
500		return $a;
501	}
502
503	/**
504	 *
505	 * @return \ImapMailbox
506	 */
507	public function getRootMailboxes($withStatus=false, $subscribed=false){
508		$imap = $this->openImapConnection();
509
510		$rootMailboxes = array();
511
512		$folders = $imap->list_folders($subscribed,$withStatus,"","{$this->mbroot}%", true);
513//		\GO::debug($folders);
514		foreach($folders as $folder){
515			$mailbox = new ImapMailbox($this,$folder);
516			$rootMailboxes[]=$mailbox;
517		}
518
519		$namespaces = $imap ->get_namespaces();
520
521		foreach($namespaces as $namespace){
522			if($namespace['name']!=''){
523				$namespace['noselect']=  strtoupper($namespace['name'])!='INBOX';
524				$namespace['subscribed']=true;
525				$rootMailboxes[]=new ImapMailbox($this, $namespace);
526			}
527		}
528
529		return $rootMailboxes;
530	}
531
532
533	/**
534	 *
535	 * @return \ImapMailbox
536	 */
537	public function getAllMailboxes($hierarchy=true, $withStatus=false){
538		$imap = $this->openImapConnection();
539
540		$folders = $imap->list_folders(true, $withStatus,'','*',true);
541
542		//$node= array('name'=>'','children'=>array());
543
544		$rootMailboxes = array();
545
546		$mailboxModels =array();
547
548		foreach($folders as $folder){
549			$mailbox = new ImapMailbox($this,$folder);
550			if($hierarchy){
551				$mailboxModels[$folder['name']]=$mailbox;
552				$parentName = $mailbox->getParentName();
553				if($parentName===false){
554					$rootMailboxes[]=$mailbox;
555				}else{
556					$mailboxModels[$parentName]->addChild($mailbox);
557				}
558			}else
559			{
560				$rootMailboxes[]=$mailbox;
561			}
562
563		}
564
565		return $rootMailboxes;
566	}
567
568	public function defaultAttributes() {
569		$attr = parent::defaultAttributes();
570
571		$attr['check_mailboxes']="INBOX";
572//		if (\GO::modules()->isInstalled('sieve')) {
573			$attr['sieve_port'] = !empty(\GO::config()->sieve_port) ? \GO::config()->sieve_port : '4190';
574			if (isset(\GO::config()->sieve_usetls))
575				$attr['sieve_usetls'] = !empty(\GO::config()->sieve_usetls);
576			else
577				$attr['sieve_usetls'] = true;
578//		}
579		return $attr;
580	}
581
582	public function getDefaultTemplate() {
583//		if (\GO::modules()->addressbook) {
584			$defaultAccountTemplateModel = \GO\Email\Model\DefaultTemplateForAccount::model()->findByPk($this->id);
585			if (!$defaultAccountTemplateModel) {
586				$defaultUserTemplateModel = \GO\Email\Model\DefaultTemplate::model()->findByPk(\GO::user()->id);
587				if (!$defaultUserTemplateModel)
588					return false;
589				else
590					return $defaultUserTemplateModel;
591			} else {
592				return $defaultAccountTemplateModel;
593			}
594
595	}
596
597}
598