eeae07aa8337f66254c3a5d10da5928b239f3089
[tine20] / tine20 / Tinebase / EmailUser / Sql.php
1 <?php
2 /**
3  * Tine 2.0
4  * 
5  * @package     Tinebase
6  * @subpackage  EmailUser
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @copyright   Copyright (c) 2012-2015 Metaways Infosystems GmbH (http://www.metaways.de)
9  * @author      Philipp Schüle
10  * */
11
12 /**
13  * plugin to handle sql email accounts
14  * 
15  * @package    Tinebase
16  * @subpackage EmailUser
17  */
18 abstract class Tinebase_EmailUser_Sql extends Tinebase_User_Plugin_Abstract
19 {
20     /**
21      * user table name with prefix
22      *
23      * @var string
24      */
25     protected $_userTable = NULL;
26     
27     /**
28      * email user config
29      * 
30      * @var array 
31      */
32      protected $_config = array();
33     
34     /**
35      * user properties mapping
36      *
37      * @var array
38      */
39     protected $_propertyMapping = array();
40
41     /**
42      * config key (IMAP/SMTP)
43      * 
44      * @var string
45      */
46     protected $_configKey = NULL;
47     
48     /**
49      * subconfig for user email backend (for example: dovecot)
50      * 
51      * @var string
52      */
53     protected $_subconfigKey =  NULL;
54     
55     /**
56     * client id
57     *
58     * @var string
59     */
60     protected $_clientId = NULL;
61     
62     /**
63      * the constructor
64      * 
65      * @param array $_options
66      */
67     public function __construct(array $_options = array())
68     {
69         if ($this instanceof Tinebase_EmailUser_Smtp_Interface) {
70             $this->_configKey = Tinebase_Config::SMTP;
71         } else if ($this instanceof Tinebase_EmailUser_Imap_Interface) {
72             $this->_configKey = Tinebase_Config::IMAP;
73         } else {
74             throw new Tinebase_Exception_UnexpectedValue('Plugin must be instance of Tinebase_EmailUser_Smtp_Interface or Tinebase_EmailUser_Imap_Interface');
75         }
76         
77         // get email user backend config options (host, dbname, username, password, port)
78         $emailConfig = Tinebase_Config::getInstance()->get($this->_configKey, new Tinebase_Config_Struct())->toArray();
79         
80         // merge _config and email backend config
81         if ($this->_subconfigKey) {
82             // flatten array
83             $emailConfig = array_merge($emailConfig[$this->_subconfigKey], $emailConfig);
84         }
85         // merge _config and email backend config
86         $this->_config = array_merge($this->_config, $emailConfig);
87         
88         // _tablename (for example "dovecot_users")
89         $this->_userTable = $this->_config['prefix'] . $this->_config['userTable'];
90         
91         // connect to DB
92         $this->_getDB();
93         $this->_dbCommand = Tinebase_Backend_Sql_Command::factory($this->_db);
94         
95         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($this->_config, TRUE));
96     }
97     
98     /**
99      * get new email user
100      * 
101      * @param  Tinebase_Model_FullUser   $_user
102      * @return Tinebase_Model_EmailUser
103      */
104     public function getNewUser(Tinebase_Model_FullUser $_user)
105     {
106         $result = new Tinebase_Model_EmailUser(array(
107             'emailUserId'     => $_user->getId(),
108             'emailUsername' => $this->_appendDomain($_user->accountLoginName)
109         ));
110         
111         return $result;
112     }
113     
114     /**
115     * delete user by id
116     *
117     * @param  Tinebase_Model_FullUser  $_user
118     */
119     public function inspectDeleteUser(Tinebase_Model_FullUser $_user)
120     {
121         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ 
122             . ' Delete ' . $this->_configKey . ' email settings for user ' . $_user->accountLoginName);
123         
124         $this->_deleteUserById($_user->getId());
125     }
126     
127     /**
128      * delete user by id
129      * 
130      * @param string $id
131      */
132     protected function _deleteUserById($id)
133     {
134         $where = array(
135             $this->_db->quoteInto($this->_db->quoteIdentifier($this->_propertyMapping['emailUserId']) . ' = ?', $id)
136         );
137         $this->_appendClientIdOrDomain($where);
138         
139         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ 
140             . ' ' . print_r($where, TRUE));
141         
142         $this->_db->delete($this->_userTable, $where);
143     }
144     
145     /**
146      * append domain if set or domain IS NULL
147      * 
148      * @param array $where
149      * @return string
150      * 
151      * @todo check if user table has domain or client_idnr field and use mapping for the field identifier
152      */
153     protected function _appendClientIdOrDomain(&$where = NULL)
154     {
155         if ($this->_clientId !== NULL) {
156             $cond = $this->_db->quoteInto($this->_db->quoteIdentifier($this->_userTable . '.' . 'client_idnr') . ' = ?', $this->_clientId);
157         } else {
158             if ((isset($this->_config['domain']) || array_key_exists('domain', $this->_config)) && ! empty($this->_config['domain'])) {
159                 $cond = $this->_db->quoteInto($this->_db->quoteIdentifier($this->_userTable . '.' . 'domain') . ' = ?',   $this->_config['domain']);
160             } else {
161                 $cond = $this->_db->quoteIdentifier($this->_userTable . '.' . 'domain') . " =''";
162             }
163         }
164         
165         if ($where !== NULL) {
166             $where[] = $cond;
167         }
168         
169         return $cond;
170     }
171
172     /**
173      * inspect get user by property
174      * 
175      * @param Tinebase_Model_User  $_user  the user object
176      */
177     public function inspectGetUserByProperty(Tinebase_Model_User $_user)
178     {
179         if (! $_user instanceof Tinebase_Model_FullUser) {
180             return;
181         }
182         
183         $userId = $_user->getId();
184         
185         $select = $this->_getSelect()
186             ->where($this->_db->quoteIdentifier($this->_userTable . '.' . $this->_propertyMapping['emailUserId']) . ' = ?',   $userId);
187         
188         // Perform query - retrieve user from database
189         $stmt = $this->_db->query($select);
190         $queryResult = $stmt->fetch();
191         $stmt->closeCursor();
192         
193         if (!$queryResult) {
194             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
195                 . ' ' . $this->_subconfigKey . ' config for user ' . $userId . ' not found!');
196         }
197         
198         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($queryResult, TRUE));
199         
200         // convert data to Tinebase_Model_EmailUser
201         $emailUser = $this->_rawDataToRecord((array)$queryResult);
202         
203         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($emailUser->toArray(), TRUE));
204         
205         // modify/correct user name
206         // set emailUsername to Tine 2.0 account login name and append domain for login purposes if set
207         if (empty($emailUser->emailUsername)) {
208             $emailUser->emailUsername = $this->_getEmailUserName($_user);
209         }
210         
211         if ($this instanceof Tinebase_EmailUser_Smtp_Interface) {
212             $_user->smtpUser  = $emailUser;
213             $_user->emailUser = Tinebase_EmailUser::merge($_user->emailUser, clone $_user->smtpUser);
214         } else {
215             $_user->imapUser  = $emailUser;
216             $_user->emailUser = Tinebase_EmailUser::merge(clone $_user->imapUser, $_user->emailUser);
217         }
218     }
219     
220     protected function _getConfiguredSystemDefaults()
221     {
222         $systemDefaults = array();
223         
224         $hostAttribute = ($this instanceof Tinebase_EmailUser_Imap_Interface) ? 'host' : 'hostname';
225         if (!empty($this->_config[$hostAttribute])) {
226             $systemDefaults['emailHost'] = $this->_config[$hostAttribute];
227         }
228         
229         if (!empty($this->_config['port'])) {
230             $systemDefaults['emailPort'] = $this->_config['port'];
231         }
232         
233         if (!empty($this->_config['ssl'])) {
234             $systemDefaults['emailSecure'] = $this->_config['ssl'];
235         }
236         
237         if (!empty($this->_config['auth'])) {
238             $systemDefaults['emailAuth'] = $this->_config['auth'];
239         }
240         
241         return $systemDefaults;
242     }
243     
244     /**
245      * update/set email user password
246      * 
247      * @param  string  $_userId
248      * @param  string  $_password
249      * @param  bool    $_encrypt encrypt password
250      */
251     public function inspectSetPassword($_userId, $_password, $_encrypt = TRUE)
252     {
253         if (!isset($this->_propertyMapping['emailPassword'])) {
254             return;
255         }
256         
257         $imapConfig = Tinebase_Config::getInstance()->get(Tinebase_Config::IMAP, new Tinebase_Config_Struct())->toArray();
258         if ((isset($imapConfig['pwsuffix']) || array_key_exists('pwsuffix', $imapConfig))) {
259             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
260                 ' Appending configured pwsuffix to new email account password.');
261             $password = $_password . $imapConfig['pwsuffix'];
262         } else {
263             $password = $_password;
264         }
265         
266         $values = array(
267             $this->_propertyMapping['emailPassword'] => ($_encrypt) ? Hash_Password::generate($this->_config['emailScheme'], $password) : $password
268         );
269         
270         $where = array(
271             $this->_db->quoteInto($this->_db->quoteIdentifier($this->_propertyMapping['emailUserId']) . ' = ?', $_userId)
272         );
273         $this->_appendClientIdOrDomain($where);
274         
275         $this->_db->update($this->_userTable, $values, $where);
276     }
277     
278     /*********  protected functions  *********/
279     
280     /**
281      * get the basic select object to fetch records from the database
282      *  
283      * @param  array|string|Zend_Db_Expr  $_cols        columns to get, * per default
284      * @param  boolean                    $_getDeleted  get deleted records (if modlog is active)
285      * @return Zend_Db_Select
286      */
287     abstract protected function _getSelect($_cols = '*', $_getDeleted = FALSE);
288     
289     /**
290      * adds email properties for a new user
291      * 
292      * @param  Tinebase_Model_FullUser  $_addedUser
293      * @param  Tinebase_Model_FullUser  $_newUserProperties
294      */
295     protected function _addUser(Tinebase_Model_FullUser $_addedUser, Tinebase_Model_FullUser $_newUserProperties)
296     {
297         if (! $_addedUser->accountEmailAddress) {
298             if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
299             . ' User ' . $_addedUser->accountDisplayName . ' has no email address defined. Skipping email user creation.');
300             return;
301         }
302         
303         $emailUserData = $this->_recordToRawData($_addedUser, $_newUserProperties);
304
305         $emailUsername = $emailUserData[$this->_propertyMapping['emailUsername']];
306         
307         $this->_checkEmailExistance($emailUsername);
308         
309         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
310             . ' Adding new ' . $this->_configKey . ' email user ' . $emailUsername);
311         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' 
312             . print_r($emailUserData, TRUE));
313         
314         try {
315             $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction($this->_db);
316             
317             // generate random password if not set
318             if (isset($this->_propertyMapping['emailPassword']) && empty($emailUserData[$this->_propertyMapping['emailPassword']])) {
319                 $emailUserData[$this->_propertyMapping['emailPassword']] = Hash_Password::generate($this->_config['emailScheme'], Tinebase_Record_Abstract::generateUID());
320             }
321             
322             $insertData = $emailUserData;
323             $this->_beforeAddOrUpdate($insertData);
324             
325             $this->_db->insert($this->_userTable, $insertData);
326             
327             $this->_afterAddOrUpdate($emailUserData);
328             
329             Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
330             
331             $this->inspectGetUserByProperty($_addedUser);
332             
333         } catch (Zend_Db_Statement_Exception $zdse) {
334             Tinebase_TransactionManager::getInstance()->rollBack();
335             Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' Error while creating email user: ' . $zdse);
336         }
337     }
338     
339     /**
340      * interceptor before add
341      * 
342      * @param array $emailUserData
343      */
344     protected function _beforeAddOrUpdate(&$emailUserData)
345     {
346         
347     }
348     
349     /**
350      * interceptor after add
351      * 
352      * @param array $emailUserData
353      */
354     protected function _afterAddOrUpdate(&$emailUserData)
355     {
356         
357     }
358     
359     /**
360      * check if user email already exists in table
361      * 
362      * @param  string  $email
363      */
364     protected function _checkEmailExistance($email)
365     {
366         $select = $this->_getSelect()
367             ->where($this->_db->quoteIdentifier($this->_userTable . '.' . $this->_propertyMapping['emailUsername']) . ' = ?',   $email)
368             ->where($this->_appendClientIdOrDomain());
369         
370         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . $select);
371         
372         $stmt = $this->_db->query($select);
373         $queryResult = $stmt->fetch();
374         $stmt->closeCursor();
375         
376         if (! $queryResult) {
377             return;
378         }
379         
380         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($queryResult, TRUE));
381         
382         $userId = $queryResult[$this->_propertyMapping['emailUserId']];
383         
384         try {
385             Tinebase_User::getInstance()->getUserByPropertyFromSqlBackend('accountId', $userId);
386             throw new Tinebase_Exception_SystemGeneric('Could not overwrite existing email user.');
387         } catch (Tinebase_Exception_NotFound $tenf) {
388             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Delete obsolete email user ' .$userId);
389             $this->_deleteUserById($userId);
390         }
391     }
392     
393     /**
394      * updates email properties for an existing user
395      * 
396      * @param  Tinebase_Model_FullUser  $_updatedUser
397      * @param  Tinebase_Model_FullUser  $_newUserProperties
398      */
399     protected function _updateUser(Tinebase_Model_FullUser $_updatedUser, Tinebase_Model_FullUser $_newUserProperties)
400     {
401         $emailUserData = $this->_recordToRawData($_updatedUser, $_newUserProperties);
402
403         if (Tinebase_Core::isLogLevel(Zend_Log::INFO))  Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Updating Dovecot user ' . $emailUserData[$this->_propertyMapping['emailUsername']]);
404         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($emailUserData, TRUE));
405         
406         $where = array(
407             $this->_db->quoteInto($this->_db->quoteIdentifier($this->_propertyMapping['emailUserId']) . ' = ?', $emailUserData[$this->_propertyMapping['emailUserId']])
408         );
409         $this->_appendClientIdOrDomain($where);
410         
411         try {
412             $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction($this->_db);
413             
414             $updateData = $emailUserData;
415             
416             $this->_beforeAddOrUpdate($updateData);
417             
418             $this->_db->update($this->_userTable, $updateData, $where);
419             
420             $this->_afterAddOrUpdate($emailUserData);
421             
422             Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
423             
424             $this->inspectGetUserByProperty($_updatedUser);
425             
426         } catch (Zend_Db_Statement_Exception $zdse) {
427             Tinebase_TransactionManager::getInstance()->rollBack();
428             Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' Error while updating email user: ' . $zdse->getMessage());
429         }
430     }
431     
432     /**
433      * check if user exists already in email backjend user table
434      * 
435      * @param  Tinebase_Model_FullUser  $_user
436      * @throws Tinebase_Exception_Backend_Database
437      */
438     protected function _userExists(Tinebase_Model_FullUser $_user)
439     {
440         $select = $this->_getSelect();
441         
442         $select
443           ->where($this->_db->quoteIdentifier($this->_userTable . '.' . $this->_propertyMapping['emailUserId']) . ' = ?',   $_user->getId());
444         
445         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . $select->__toString());
446         
447         // Perform query - retrieve user from database
448         try {
449             $stmt = $this->_db->query($select);
450         } catch (Zend_Db_Statement_Exception $zdse) {
451             if (Tinebase_Core::isLogLevel(Zend_Log::ERR)) Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' ' . $zdse);
452             throw new Tinebase_Exception_Backend_Database($zdse->getMessage());
453         }
454         $queryResult = $stmt->fetch();
455         $stmt->closeCursor();
456         
457         if (!$queryResult) {
458             return false;
459         }
460         
461         return true;
462     }
463     
464     /**
465      * converts raw data from adapter into a single record / do mapping
466      *
467      * @param  array                    $_data
468      * @return Tinebase_Model_EmailUser
469      */
470     abstract protected function _rawDataToRecord(array $_rawdata);
471      
472     /**
473      * returns array of raw user data
474      *
475      * @param  Tinebase_Model_FullUser  $_user
476      * @param  Tinebase_Model_FullUser  $_newUserProperties
477      * @return array
478      */
479     abstract protected function _recordToRawData(Tinebase_Model_FullUser $_user, Tinebase_Model_FullUser $_newUserProperties);
480 }