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