9c2fb52fff8fa44b35d71b740a5727caf31eb7b9
[tine20] / tine20 / Felamimail / Model / Account.php
1 <?php
2 /**
3  * Tine 2.0
4  * 
5  * @package     Felamimail
6  * @subpackage  Model
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @author      Philipp Schüle <p.schuele@metaways.de>
9  * @copyright   Copyright (c) 2009-2017 Metaways Infosystems GmbH (http://www.metaways.de)
10  * 
11  * @todo        update account credentials if user password changed
12  * @todo        use generic (JSON encoded) field for 'other' settings like folder names
13  */
14
15 /**
16  * class to hold Account data
17  * 
18  * @property  string    trash_folder
19  * @property  string    sent_folder
20  * @property  string    drafts_folder
21  * @property  string    templates_folder
22  * @property  string    sieve_vacation_active
23  * @property  string    display_format
24  * @property  string    delimiter
25  * @property  string    type
26  * @property  string    signature_position
27  * @property  string    email
28  * @property  string    user_id
29  * @property  string    sieve_notification_email
30  *
31  * @package   Felamimail
32  * @subpackage    Model
33  */
34 class Felamimail_Model_Account extends Tinebase_EmailUser_Model_Account
35 {
36     /**
37      * key in $_validators/$_properties array for the field which 
38      * represents the identifier
39      * 
40      * @var string
41      */    
42     protected $_identifier = 'id';
43     
44     /**
45      * application the record belongs to
46      *
47      * @var string
48      */
49     protected $_application = 'Felamimail';
50     
51     /**
52      * list of zend validator
53      * 
54      * this validators get used when validating user generated content with Zend_Input_Filter
55      *
56      * @var array
57      */
58     protected $_validators = array(
59         'id'                    => array(Zend_Filter_Input::ALLOW_EMPTY => true),
60         'user_id'               => array(Zend_Filter_Input::ALLOW_EMPTY => true),
61         'name'                  => array(Zend_Filter_Input::ALLOW_EMPTY => true),
62     // account type (system/user defined)
63         'type'        => array(
64             Zend_Filter_Input::ALLOW_EMPTY => true, 
65             Zend_Filter_Input::DEFAULT_VALUE => self::TYPE_USER,
66             array('InArray', array(self::TYPE_USER, self::TYPE_SYSTEM)),
67         ),
68     // imap server config
69         'host'                  => array(Zend_Filter_Input::ALLOW_EMPTY => true),
70         'port'                  => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => 143),
71         'ssl'                   => array(
72             Zend_Filter_Input::ALLOW_EMPTY => true, 
73             Zend_Filter_Input::DEFAULT_VALUE => self::SECURE_TLS,
74             array('InArray', array(self::SECURE_NONE, self::SECURE_SSL, self::SECURE_TLS)),
75         ),
76         'credentials_id'        => array(Zend_Filter_Input::ALLOW_EMPTY => true),
77         'user'                  => array(Zend_Filter_Input::ALLOW_EMPTY => true),
78         'password'              => array(Zend_Filter_Input::ALLOW_EMPTY => true),
79     // other settings (@todo add single JSON encoded field or keyfield for that?)
80         'sent_folder'           => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => 'Sent'),
81         'trash_folder'          => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => 'Trash'),
82         'drafts_folder'         => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => 'Drafts'),
83         'templates_folder'      => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => 'Templates'),
84         'has_children_support'  => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => 1),
85         'delimiter'             => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => '/'),
86         'display_format'        => array(
87             Zend_Filter_Input::ALLOW_EMPTY => true, 
88             Zend_Filter_Input::DEFAULT_VALUE => self::DISPLAY_HTML,
89             array('InArray', array(self::DISPLAY_HTML, self::DISPLAY_PLAIN, self::DISPLAY_CONTENT_TYPE)),
90         ),
91         'compose_format'        => array(
92             Zend_Filter_Input::ALLOW_EMPTY => true,
93             Zend_Filter_Input::DEFAULT_VALUE => self::DISPLAY_HTML,
94             array('InArray', array(self::DISPLAY_HTML, self::DISPLAY_PLAIN)),
95         ),
96         'preserve_format'        => array(
97             Zend_Filter_Input::ALLOW_EMPTY => true,
98             Zend_Filter_Input::DEFAULT_VALUE => 1,
99             array('InArray', array(0,1)),
100         ),
101         'reply_to'              => array(Zend_Filter_Input::ALLOW_EMPTY => true),
102     // namespaces
103         'ns_personal'           => array(Zend_Filter_Input::ALLOW_EMPTY => true),
104         'ns_other'              => array(Zend_Filter_Input::ALLOW_EMPTY => true),
105         'ns_shared'             => array(Zend_Filter_Input::ALLOW_EMPTY => true),
106     // user data
107         'email'                 => array(Zend_Filter_Input::ALLOW_EMPTY => true),
108         'from'                  => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => ''),
109         'organization'          => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => ''),
110         'signature'             => array(Zend_Filter_Input::ALLOW_EMPTY => true),
111         'signature_position'    => array(
112             Zend_Filter_Input::ALLOW_EMPTY => true, 
113             Zend_Filter_Input::DEFAULT_VALUE => self::SIGNATURE_BELOW_QUOTE,
114             array('InArray', array(self::SIGNATURE_ABOVE_QUOTE, self::SIGNATURE_BELOW_QUOTE)),
115         ),
116         // smtp config
117         'smtp_port'             => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => 25),
118         'smtp_hostname'         => array(Zend_Filter_Input::ALLOW_EMPTY => true),
119         'smtp_auth'             => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => 'login'),
120         'smtp_ssl'              => array(
121             Zend_Filter_Input::ALLOW_EMPTY => true, 
122             Zend_Filter_Input::DEFAULT_VALUE => self::SECURE_TLS,
123             array('InArray', array(self::SECURE_NONE, self::SECURE_SSL, self::SECURE_TLS)),
124         ),
125         'smtp_credentials_id'   => array(Zend_Filter_Input::ALLOW_EMPTY => true),
126         'smtp_user'             => array(Zend_Filter_Input::ALLOW_EMPTY => true),
127         'smtp_password'         => array(Zend_Filter_Input::ALLOW_EMPTY => true),
128     // sieve config
129         'sieve_port'            => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => 2000),
130         'sieve_hostname'        => array(Zend_Filter_Input::ALLOW_EMPTY => true),
131         'sieve_ssl'=> array(
132             Zend_Filter_Input::ALLOW_EMPTY => true, 
133             Zend_Filter_Input::DEFAULT_VALUE => self::SECURE_TLS,
134             array('InArray', array(self::SECURE_NONE, self::SECURE_TLS)),
135         ),
136         'sieve_vacation_active' => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => 0),
137         'sieve_notification_email' => array(Zend_Filter_Input::ALLOW_EMPTY => true),
138         //'sieve_credentials_id'  => array(Zend_Filter_Input::ALLOW_EMPTY => true),
139         //'sieve_user'            => array(Zend_Filter_Input::ALLOW_EMPTY => true),
140         //'sieve_password'        => array(Zend_Filter_Input::ALLOW_EMPTY => true),
141     // modlog information
142         'created_by'            => array(Zend_Filter_Input::ALLOW_EMPTY => true),
143         'creation_time'         => array(Zend_Filter_Input::ALLOW_EMPTY => true),
144         'last_modified_by'      => array(Zend_Filter_Input::ALLOW_EMPTY => true),
145         'last_modified_time'    => array(Zend_Filter_Input::ALLOW_EMPTY => true),
146         'is_deleted'            => array(Zend_Filter_Input::ALLOW_EMPTY => true),
147         'deleted_time'          => array(Zend_Filter_Input::ALLOW_EMPTY => true),
148         'deleted_by'            => array(Zend_Filter_Input::ALLOW_EMPTY => true),
149         'seq'                   => array(Zend_Filter_Input::ALLOW_EMPTY => true),
150     );
151     
152     /**
153      * name of fields containing datetime or an array of datetime information
154      *
155      * @var array list of datetime fields
156      */    
157     protected $_datetimeFields = array(
158         'creation_time',
159         'last_modified_time',
160         'deleted_time'
161     );
162     
163     /**
164      * name of fields that should be omited from modlog
165      *
166      * @var array list of modlog omit fields
167      */
168     protected $_modlogOmitFields = array(
169         'user',
170         'password',
171         'smtp_user',
172         'smtp_password',
173         'credentials_id',
174         'smtp_credentials_id'
175     );
176     
177     /**
178      * overwrite constructor to add more filters
179      *
180      * @param mixed $_data
181      * @param bool $_bypassFilters
182      * @param mixed $_convertDates
183      * @return void
184      */
185     public function __construct($_data = NULL, $_bypassFilters = false, $_convertDates = true)
186     {
187         // set some fields to default if not set
188         $this->_filters['ssl']              = array(new Zend_Filter_Empty(self::SECURE_TLS),   'StringTrim', 'StringToLower');
189         $this->_filters['smtp_ssl']         = array(new Zend_Filter_Empty(self::SECURE_TLS),   'StringTrim', 'StringToLower');
190         $this->_filters['sieve_ssl']        = array(new Zend_Filter_Empty(self::SECURE_TLS),   'StringTrim', 'StringToLower');
191         $this->_filters['display_format']   = array(new Zend_Filter_Empty(self::DISPLAY_HTML), 'StringTrim', 'StringToLower');
192         $this->_filters['port']             = new Zend_Filter_Empty(NULL);
193         $this->_filters['smtp_port']        = new Zend_Filter_Empty(NULL);
194         $this->_filters['sieve_port']       = new Zend_Filter_Empty(NULL);
195         
196         return parent::__construct($_data, $_bypassFilters, $_convertDates);
197     }
198     
199     /**
200      * get imap config array
201      * - decrypt pwd/user with user password
202      *
203      * @return array
204      */
205     public function getImapConfig()
206     {
207         $this->resolveCredentials(FALSE);
208         
209         $result = array();
210         foreach (array('host', 'port', 'user', 'password') as $field) {
211             $result[$field] = $this->{$field};
212         }
213         
214         if ($this->ssl && $this->ssl !== Felamimail_Model_Account::SECURE_NONE) {
215             $result['ssl'] = strtoupper($this->ssl);
216         }
217         
218         //if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . print_r($result, true));
219         
220         return $result;
221     }
222     
223     /**
224      * get smtp config
225      *
226      * @return array
227      */
228     public function getSmtpConfig()
229     {
230         $this->resolveCredentials(FALSE, TRUE, TRUE);
231         
232         $result = array();
233         
234         // get values from account
235         if ($this->smtp_hostname) {
236             $result['hostname'] = $this->smtp_hostname;
237         }
238         if ($this->smtp_user) {
239             $result['username'] = $this->smtp_user;
240         }
241         if ($this->smtp_password) {
242             $result['password'] = $this->smtp_password;
243         }
244         if ($this->smtp_auth) {
245             $result['auth'] = $this->smtp_auth;
246         }
247         if ($this->smtp_ssl) {
248             $result['ssl'] = $this->smtp_ssl;
249         }
250         if ($this->smtp_port) {
251             $result['port'] = $this->smtp_port;
252         }
253         
254         if (isset($result['auth']) && $result['auth'] === 'none') {
255             unset($result['username']);
256             unset($result['password']);
257             unset($result['auth']);
258         }
259         if ((isset($result['ssl']) || array_key_exists('ssl', $result)) && $result['ssl'] == 'none') {
260             unset($result['ssl']);
261         }
262         
263         //if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . print_r($result, true));
264         
265         return $result;
266     }
267
268     /**
269      * get sieve config array
270      *
271      * @return array
272      * 
273      * @todo add sieve credentials? this uses imap credentials atm.
274      */
275     public function getSieveConfig()
276     {
277         $this->resolveCredentials(FALSE);
278         
279         $result = array(
280             'host'      => $this->sieve_hostname,
281             'port'      => $this->sieve_port, 
282             'ssl'       => ($this->sieve_ssl && $this->sieve_ssl !== self::SECURE_NONE) ? $this->sieve_ssl : FALSE,
283             'username'  => $this->user,
284             'password'  => $this->password,
285         );
286         
287         return $result;
288     }
289     
290     /**
291      * to array
292      *
293      * @param boolean $_recursive
294      */
295     public function toArray($_recursive = TRUE)
296     {
297         $result = parent::toArray($_recursive);
298
299         // don't show password
300         unset($result['password']);
301         unset($result['smtp_password']);
302         
303         return $result;
304     }
305
306     /**
307      * resolve imap or smtp credentials
308      *
309      * @param boolean $_onlyUsername
310      * @param boolean $_throwException
311      * @param boolean $_smtp
312      * @return boolean
313      */
314     public function resolveCredentials($_onlyUsername = TRUE, $_throwException = FALSE, $_smtp = FALSE)
315     {
316         if ($_smtp) {
317             $passwordField      = 'smtp_password';
318             $userField          = 'smtp_user';
319             $credentialsField   = 'smtp_credentials_id';
320         } else {
321             $passwordField      = 'password';
322             $userField          = 'user';
323             $credentialsField   = 'credentials_id';
324         }
325         
326         if (! $this->{$userField} || ! ($this->{$passwordField} && ! $_onlyUsername)) {
327             
328             $credentialsBackend = Tinebase_Auth_CredentialCache::getInstance();
329             $userCredentialCache = Tinebase_Core::getUserCredentialCache();
330             
331             if ($userCredentialCache !== NULL) {
332                 try {
333                     $credentialsBackend->getCachedCredentials($userCredentialCache);
334                 } catch (Exception $e) {
335                     return FALSE;
336                 }
337             } else {
338                 Tinebase_Core::getLogger()->crit(__METHOD__ . '::' . __LINE__ 
339                     . ' Something went wrong with the CredentialsCache');
340                 return FALSE;
341             }
342             
343             if ($this->type == self::TYPE_USER) {
344                 if (! $this->{$credentialsField}) {
345                     if ($_throwException) {
346                         throw new Felamimail_Exception('Could not get credentials, no ' . $credentialsField . ' given.');
347                     } else {
348                         return FALSE;
349                     }
350                 }
351                 
352                 try {
353                     // NOTE: cache cleanup process might have removed the cache
354                     $credentials = $credentialsBackend->get($this->{$credentialsField});
355                     $credentials->key = substr($userCredentialCache->password, 0, 24);
356                     $credentialsBackend->getCachedCredentials($credentials);
357                 } catch (Tinebase_Exception_NotFound $tenf) {
358                     // try to use imap credentials & reset smtp credentials if different
359                     if ($_smtp) {
360                         // TODO ask user for smtp creds if this fails
361                         if ($this->smtp_credentials_id !== $this->credentials_id) {
362                             $this->smtp_credentials_id = $this->credentials_id;
363                             Felamimail_Controller_Account::getInstance()->update($this);
364                             return $this->resolveCredentials($_onlyUsername, $_throwExceptio, $_smtp);
365                         }
366                     }
367
368                     if ($_throwException) {
369                         throw $tenf;
370                     } else {
371                         return FALSE;
372                     }
373                 } catch (Exception $e) {
374                     if ($_throwException) {
375                         throw $e;
376                     } else {
377                         return FALSE;
378                     }
379                 }
380             } else {
381                 // just use tine user credentials to connect to mailserver / or use credentials from config if set
382                 $imapConfig = Tinebase_Config::getInstance()->get(Tinebase_Config::IMAP, new Tinebase_Config_Struct())->toArray();
383                 
384                 $credentials = $userCredentialCache;
385                 
386                 // allow to set credentials in config
387                 if ((isset($imapConfig['user']) || array_key_exists('user', $imapConfig)) && (isset($imapConfig['password']) || array_key_exists('password', $imapConfig)) && ! empty($imapConfig['user'])) {
388                     if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
389                         ' Using credentials from config for system account.');
390                     $credentials->username = $imapConfig['user'];
391                     $credentials->password = $imapConfig['password'];
392                 }
393                 
394                 // allow to set pw suffix in config
395                 if ((isset($imapConfig['pwsuffix']) || array_key_exists('pwsuffix', $imapConfig)) && ! preg_match('/' . preg_quote($imapConfig['pwsuffix']) . '$/', $credentials->password)) {
396                     if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
397                         ' Appending configured pwsuffix to system account password.');
398                     $credentials->password .= $imapConfig['pwsuffix'];
399                 }
400
401                 if (! empty($imapConfig['domain']) && strpos($credentials->username, $imapConfig['domain']) === false) {
402                     $credentials->username .= '@' . $imapConfig['domain'];
403                 }
404             }
405             
406             if (! $this->{$userField}) {
407                 $this->{$userField} = $credentials->username;
408             }
409             
410             $this->{$passwordField} = $credentials->password;
411         }
412         
413         return TRUE;
414     }
415
416     /**
417      * returns TRUE if account has capability (i.e. QUOTA, CONDSTORE, ...)
418      * 
419      * @param $_capability
420      * @return boolean
421      */
422     public function hasCapability($_capability)
423     {
424         $capabilities = Felamimail_Controller_Account::getInstance()->updateCapabilities($this);
425         
426         return (in_array($_capability, $capabilities['capabilities']));
427     }
428 }