9d550ecdafa36d69dde2aa7e6e66a8a80d293d66
[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 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->_configKey === NULL || $this->_subconfigKey === NULL) {
70             throw new Tinebase_Exception_UnexpectedValue('Need config keys for this backend');
71         }
72         
73         // get email user backend config options (host, dbname, username, password, port)
74         $emailConfig = Tinebase_Config::getInstance()->get($this->_configKey, new Tinebase_Config_Struct())->toArray();
75         
76         // merge _config and email backend config
77         $this->_config = array_merge($emailConfig[$this->_subconfigKey], $this->_config);
78         
79         // _tablename (for example "dovecot_users")
80         $this->_userTable = $this->_config['prefix'] . $this->_config['userTable'];
81         
82         // connect to DB
83         $this->_getDB($this->_config);
84         $this->_dbCommand = Tinebase_Backend_Sql_Command::factory($this->_db);
85         
86         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($this->_config, TRUE));
87     }
88     
89     /**
90      * get new email user
91      * 
92      * @param  Tinebase_Model_FullUser   $_user
93      * @return Tinebase_Model_EmailUser
94      */
95     public function getNewUser(Tinebase_Model_FullUser $_user)
96     {
97         $result = new Tinebase_Model_EmailUser(array(
98             'emailUserId'     => $_user->getId(),
99             'emailUsername' => $this->_appendDomain($_user->accountLoginName)
100         ));
101         
102         return $result;
103     }
104     
105     /**
106     * delete user by id
107     *
108     * @param  Tinebase_Model_FullUser  $_user
109     */
110     public function inspectDeleteUser(Tinebase_Model_FullUser $_user)
111     {
112         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ 
113             . ' Delete ' . $this->_configKey . ' email settings for user ' . $_user->accountLoginName);
114         
115         $this->_deleteUserById($_user->getId());
116     }
117     
118     /**
119      * delete user by id
120      * 
121      * @param string $id
122      */
123     protected function _deleteUserById($id)
124     {
125         $where = array(
126             $this->_db->quoteInto($this->_db->quoteIdentifier($this->_propertyMapping['emailUserId']) . ' = ?', $id)
127         );
128         $this->_appendClientIdOrDomain($where);
129         
130         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ 
131             . ' ' . print_r($where, TRUE));
132     
133         $this->_db->delete($this->_userTable, $where);
134     }
135     
136     /**
137      * append domain if set or domain IS NULL
138      * 
139      * @param array $where
140      * @return string
141      * 
142      * @todo check if user table has domain or client_idnr field and use mapping for the field identifier
143      */
144     protected function _appendClientIdOrDomain(&$where = NULL)
145     {
146         if ($this->_clientId !== NULL) {
147             $cond = $this->_db->quoteInto($this->_db->quoteIdentifier($this->_userTable . '.' . 'client_idnr') . ' = ?', $this->_clientId);
148         } else {
149             if (array_key_exists('domain', $this->_config) && ! empty($this->_config['domain'])) {
150                 $cond = $this->_db->quoteInto($this->_db->quoteIdentifier($this->_userTable . '.' . 'domain') . ' = ?',   $this->_config['domain']);
151             } else {
152                 $cond = $this->_db->quoteIdentifier($this->_userTable . '.' . 'domain') . " =''";
153             }
154         }
155         
156         if ($where !== NULL) {
157             $where[] = $cond;
158         }
159         
160         return $cond;
161     }
162     
163     /**
164      * inspect get user by property
165      * 
166      * @param Tinebase_Model_User  $_user  the user object
167      */
168     public function inspectGetUserByProperty(Tinebase_Model_User $_user)
169     {
170         if (! $_user instanceof Tinebase_Model_FullUser) {
171             return;
172         }
173         
174         $userId = $_user->getId();
175         
176         $select = $this->_getSelect()
177             ->where($this->_db->quoteIdentifier($this->_userTable . '.' . $this->_propertyMapping['emailUserId']) . ' = ?',   $userId);
178         
179         // Perform query - retrieve user from database
180         $stmt = $this->_db->query($select);
181         $queryResult = $stmt->fetch();
182         $stmt->closeCursor();
183         
184         if (!$queryResult) {
185             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
186                 . ' ' . $this->_subconfigKey . ' config for user ' . $userId . ' not found!');
187             return;
188         }
189         
190         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($queryResult, TRUE));
191         
192         // convert data to Tinebase_Model_EmailUser       
193         $emailUser = $this->_rawDataToRecord($queryResult);
194         
195         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($emailUser->toArray(), TRUE));
196         
197         // modify/correct user name
198         // set emailUsername to Tine accout login name and append domain for login purposes if set
199         $emailUser->emailUsername = $this->_appendDomain($_user->accountLoginName);
200
201         $userProperty = $this->_configKey . 'User';
202         $_user->{$userProperty} = $emailUser;
203         
204         $_user->emailUser = isset($_user->emailUser) ? $_user->emailUser : null;
205         $imapUser = ($this->_configKey === Tinebase_Config::SMTP) ? $_user->emailUser : clone($emailUser);
206         $smtpUser = ($this->_configKey === Tinebase_Config::SMTP) ? clone($emailUser) : $_user->emailUser;
207         $_user->emailUser = Tinebase_EmailUser::merge($imapUser, $smtpUser);
208     }
209     
210     /**
211      * update/set email user password
212      * 
213      * @param  string  $_userId
214      * @param  string  $_password
215      * @param  bool    $_encrypt encrypt password
216      */
217     public function inspectSetPassword($_userId, $_password, $_encrypt = TRUE)
218     {
219         $imapConfig = Tinebase_Config::getInstance()->get(Tinebase_Config::IMAP, new Tinebase_Config_Struct())->toArray();
220         if (array_key_exists('pwsuffix', $imapConfig)) {
221             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
222                 ' Appending configured pwsuffix to new email account password.');
223             $password = $_password . $imapConfig['pwsuffix'];
224         } else {
225             $password = $_password;
226         }
227         
228         $values = array(
229             $this->_propertyMapping['emailPassword'] => ($_encrypt) ? Hash_Password::generate($this->_config['emailScheme'], $password) : $password
230         );
231         
232         $where = array(
233             $this->_db->quoteInto($this->_db->quoteIdentifier($this->_propertyMapping['emailUserId']) . ' = ?', $_userId)
234         );
235         $this->_appendClientIdOrDomain($where);
236         
237         $this->_db->update($this->_userTable, $values, $where);
238     }
239     
240     /*********  protected functions  *********/
241     
242     /**
243      * get the basic select object to fetch records from the database
244      *  
245      * @param  array|string|Zend_Db_Expr  $_cols        columns to get, * per default
246      * @param  boolean                    $_getDeleted  get deleted records (if modlog is active)
247      * @return Zend_Db_Select
248      */
249     abstract protected function _getSelect($_cols = '*', $_getDeleted = FALSE);
250     
251     /**
252      * adds email properties for a new user
253      * 
254      * @param  Tinebase_Model_FullUser  $_addedUser
255      * @param  Tinebase_Model_FullUser  $_newUserProperties
256      */
257     protected function _addUser(Tinebase_Model_FullUser $_addedUser, Tinebase_Model_FullUser $_newUserProperties)
258     {
259         if (! $_addedUser->accountEmailAddress) {
260             if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
261             . ' User ' . $_addedUser->accountDisplayName . ' has no email address defined. Skipping email user creation.');
262             return;
263         }
264         
265         $emailUserData = $this->_recordToRawData($_addedUser, $_newUserProperties);
266
267         $emailUsername = $emailUserData[$this->_propertyMapping['emailUsername']];
268         
269         $this->_checkEmailExistance($emailUsername);
270         
271         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
272             . ' Adding new ' . $this->_configKey . ' email user ' . $emailUsername);
273         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' 
274                 . print_r($emailUserData, TRUE));
275
276         try {
277             $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction($this->_db);
278             
279             // generate random password if not set
280             if (empty($emailUserData[$this->_propertyMapping['emailPassword']])) {
281                 $emailUserData[$this->_propertyMapping['emailPassword']] = Hash_Password::generate($this->_config['emailScheme'], Tinebase_Record_Abstract::generateUID());
282             }
283             
284             $insertData = $emailUserData;
285             $this->_beforeAddOrUpdate($insertData);
286             
287             $this->_db->insert($this->_userTable, $insertData);
288             
289             $this->_afterAddOrUpdate($emailUserData);
290             
291             Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
292             
293             $this->inspectGetUserByProperty($_addedUser);
294             
295         } catch (Zend_Db_Statement_Exception $zdse) {
296             Tinebase_TransactionManager::getInstance()->rollBack();
297             Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' Error while creating email user: ' . $zdse);
298         }
299     }
300     
301     /**
302      * interceptor before add
303      * 
304      * @param array $emailUserData
305      */
306     protected function _beforeAddOrUpdate(&$emailUserData)
307     {
308         
309     }
310
311     /**
312      * interceptor after add
313      * 
314      * @param array $emailUserData
315      */
316     protected function _afterAddOrUpdate(&$emailUserData)
317     {
318         
319     }
320
321     /**
322      * check if user email already exists in table
323      * 
324      * @param  string  $email
325      */
326     protected function _checkEmailExistance($email)
327     {
328         $select = $this->_getSelect()
329             ->where($this->_db->quoteIdentifier($this->_userTable . '.' . $this->_propertyMapping['emailUsername']) . ' = ?',   $email)
330             ->where($this->_appendClientIdOrDomain());
331         
332         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . $select);
333         
334         $stmt = $this->_db->query($select);
335         $queryResult = $stmt->fetch();
336         $stmt->closeCursor();
337         
338         if (! $queryResult) {
339             return;
340         }
341         
342         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($queryResult, TRUE));
343         
344         $userId = $queryResult['userid'];
345         try {
346             Tinebase_User::getInstance()->getFullUserById($userId);
347             throw new Tinebase_Exception_SystemGeneric('Could not overwrite existing email user.');
348         } catch (Tinebase_Exception_NotFound $tenf) {
349             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Delete obsolete email user ' .$userId);
350             $this->_deleteUserById($userId);
351         }
352     }
353     
354     /**
355      * updates email properties for an existing user
356      * 
357      * @param  Tinebase_Model_FullUser  $_updatedUser
358      * @param  Tinebase_Model_FullUser  $_newUserProperties
359      */
360     protected function _updateUser(Tinebase_Model_FullUser $_updatedUser, Tinebase_Model_FullUser $_newUserProperties)
361     {
362         $emailUserData = $this->_recordToRawData($_updatedUser, $_newUserProperties);
363
364         if (Tinebase_Core::isLogLevel(Zend_Log::INFO))  Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Updating Dovecot user ' . $emailUserData[$this->_propertyMapping['emailUsername']]);
365         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($emailUserData, TRUE));
366         
367         $where = array(
368             $this->_db->quoteInto($this->_db->quoteIdentifier($this->_propertyMapping['emailUserId']) . ' = ?', $emailUserData[$this->_propertyMapping['emailUserId']])
369         );
370         $this->_appendClientIdOrDomain($where);
371         
372         try {
373             $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction($this->_db);
374             
375             $updateData = $emailUserData;
376             
377             $this->_beforeAddOrUpdate($updateData);
378             
379             $this->_db->update($this->_userTable, $updateData, $where);
380             
381             $this->_afterAddOrUpdate($emailUserData);
382
383             Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
384             
385             $this->inspectGetUserByProperty($_updatedUser);
386             
387         } catch (Zend_Db_Statement_Exception $zdse) {
388             Tinebase_TransactionManager::getInstance()->rollBack();
389             Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' Error while updating email user: ' . $zdse->getMessage());
390         }
391     }
392     
393     /**
394      * check if user exists already in email backjend user table
395      * 
396      * @param  Tinebase_Model_FullUser  $_user
397      * @throws Tinebase_Exception_Backend_Database
398      */
399     protected function _userExists(Tinebase_Model_FullUser $_user)
400     {
401         $select = $this->_getSelect();
402         
403         $select
404           ->where($this->_db->quoteIdentifier($this->_userTable . '.' . $this->_propertyMapping['emailUserId']) . ' = ?',   $_user->getId());
405           
406         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . $select->__toString());
407
408         // Perform query - retrieve user from database
409         try {
410             $stmt = $this->_db->query($select);
411         } catch (Zend_Db_Statement_Exception $zdse) {
412             if (Tinebase_Core::isLogLevel(Zend_Log::ERR)) Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' ' . $zdse);
413             throw new Tinebase_Exception_Backend_Database($zdse->getMessage());
414         }
415         $queryResult = $stmt->fetch();
416         $stmt->closeCursor();
417         
418         if (!$queryResult) {
419             return false;
420         }
421         
422         return true;
423     }
424     
425     /**
426      * converts raw data from adapter into a single record / do mapping
427      *
428      * @param  array                    $_data
429      * @return Tinebase_Model_EmailUser
430      */
431     abstract protected function _rawDataToRecord(array $_rawdata);
432      
433     /**
434      * returns array of raw user data
435      *
436      * @param  Tinebase_Model_FullUser  $_user
437      * @param  Tinebase_Model_FullUser  $_newUserProperties
438      * @return array
439      */
440     abstract protected function _recordToRawData(Tinebase_Model_FullUser $_user, Tinebase_Model_FullUser $_newUserProperties);
441 }