3676f61be9afef57d309d25724785f7149748b34
[tine20] / tine20 / Setup / Update / Abstract.php
1 <?php
2 /**
3  * Tine 2.0
4  * 
5  * @package     Setup
6  * @subpackage  Update
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @copyright   Copyright (c) 2007-2012 Metaways Infosystems GmbH (http://www.metaways.de)
9  * @author      Matthias Greiling <m.greiling@metaways.de>
10  */
11
12 /**
13  * Common class for a Tine 2.0 Update
14  * 
15  * @package     Setup
16  * @subpackage  Update
17  */
18 class Setup_Update_Abstract
19 {
20     /**
21      * backend for databse handling and extended database queries
22      *
23      * @var Setup_Backend_Mysql
24      */
25     protected $_backend;
26     
27     /**
28      * @var Zend_Db_Adapter_Abstract
29      */
30     protected $_db;
31     
32     /** 
33     * the constructor
34     */
35     public function __construct($_backend)
36     {
37         $this->_backend = $_backend;
38         $this->_db = Tinebase_Core::getDb();
39     }
40     
41     /**
42      * get version number of a given application 
43      * version is stored in database table "applications"
44      *
45      * @param string application
46      * @return string version number major.minor release 
47      */
48     public function getApplicationVersion($_application)
49     {
50         $select = $this->_db->select()
51                 ->from(SQL_TABLE_PREFIX . 'applications')
52                 ->where($this->_db->quoteIdentifier('name') . ' = ?', $_application);
53
54         $stmt = $select->query();
55         $version = $stmt->fetchAll();
56         
57         return $version[0]['version'];
58     }
59
60     /**
61      * set version number of a given application 
62      * version is stored in database table "applications"
63      *
64      * @param string $_applicationName
65      * @param string $_version new version number
66      * @return Tinebase_Model_Application
67      */    
68     public function setApplicationVersion($_applicationName, $_version)
69     {
70         $application = Tinebase_Application::getInstance()->getApplicationByName($_applicationName);
71         $application->version = $_version;
72         
73         return Tinebase_Application::getInstance()->updateApplication($application);
74     }
75     
76     /**
77      * get version number of a given table
78      * version is stored in database table "applications_tables"
79      *
80      * @param Tinebase_Application application
81      * @return int version number 
82      */
83     public function getTableVersion($_tableName)
84     {
85         $select = $this->_db->select()
86                 ->from(SQL_TABLE_PREFIX . 'application_tables')
87                 ->where(    $this->_db->quoteIdentifier('name') . ' = ?', $_tableName)
88                 ->orwhere(  $this->_db->quoteIdentifier('name') . ' = ?', SQL_TABLE_PREFIX . $_tableName);
89
90         $stmt = $select->query();
91         $rows = $stmt->fetchAll();
92         
93         //if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . $select->__toString());
94         
95         $result = (count($rows) > 0 && isset($rows[0]['version'])) ? $rows[0]['version'] : 0;
96         
97         return $result;
98     }
99     
100     /**
101      * set version number of a given table
102      * version is stored in database table "applications_tables"
103      *
104      * @param string tableName
105      * @param int|string $_version
106      * @param boolean $_createIfNotExist
107      * @param string $_application
108      * @return void
109      * @throws Setup_Exception_NotFound
110      */     
111     public function setTableVersion($_tableName, $_version, $_createIfNotExist = TRUE, $_application = 'Tinebase')
112     {
113         if ($this->getTableVersion($_tableName) == 0) {
114             if ($_createIfNotExist) {
115                 Tinebase_Application::getInstance()->addApplicationTable(
116                     Tinebase_Application::getInstance()->getApplicationByName($_application), 
117                     $_tableName,
118                     $_version
119                 );
120             } else {
121                 throw new Setup_Exception_NotFound('Table ' . $_tableName . ' not found in application tables or previous version number invalid.');
122             }
123         } else {
124             $applicationsTables = new Tinebase_Db_Table(array('name' =>  SQL_TABLE_PREFIX . 'application_tables'));
125             $where  = array(
126                 $this->_db->quoteInto($this->_db->quoteIdentifier('name') . ' = ?', $_tableName),
127             );
128             $result = $applicationsTables->update(array('version' => $_version), $where);
129         }
130     }
131     
132     /**
133      * set version number of a given table
134      * version is stored in database table "applications_tables"
135      *
136      * @param string tableName
137      * @return int version number 
138      */  
139     public function increaseTableVersion($_tableName)
140     {
141         $currentVersion = $this->getTableVersion($_tableName);
142
143         $version = ++$currentVersion;
144         
145         $applicationsTables = new Tinebase_Db_Table(array('name' =>  SQL_TABLE_PREFIX . 'application_tables'));
146         $where  = array(
147             $this->_db->quoteInto($this->_db->quoteIdentifier('name') . ' = ?', $_tableName),
148         );
149         $result = $applicationsTables->update(array('version' => $version), $where);
150     }
151     
152     /**
153      * compares version numbers of given table and given number
154      *
155      * @param  string $_tableName
156      * @param  int version number
157      * @throws Setup_Exception
158      */     
159     public function validateTableVersion($_tableName, $_version)
160     {
161         $currentVersion = $this->getTableVersion($_tableName);
162         if($_version != $currentVersion) {
163             throw new Setup_Exception("Wrong table version for $_tableName. expected $_version got $currentVersion");
164         }
165     }
166     
167     /**
168      * create new table and add it to application tables
169      * 
170      * @param string $_tableName
171      * @param Setup_Backend_Schema_Table_Abstract $_table
172      * @param string $_application
173      */
174     public function createTable($_tableName, Setup_Backend_Schema_Table_Abstract $_table, $_application = 'Tinebase', $_version = 1)
175     {
176         $app = Tinebase_Application::getInstance()->getApplicationByName($_application);
177         Tinebase_Application::getInstance()->removeApplicationTable($app, $_tableName);
178         
179         $this->_backend->createTable($_table);
180         
181         Tinebase_Application::getInstance()->addApplicationTable($app, $_tableName, $_version);
182         
183         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Created new table ' . $_tableName);
184     }
185     
186     /**
187      * rename table in applications table
188      *
189      * @param string $_oldTableName
190      * @param string $_newTableName
191      */  
192     public function renameTable($_oldTableName, $_newTableName)
193     {
194         $this->_backend->renameTable($_oldTableName, $_newTableName);
195         
196         $applicationsTables = new Tinebase_Db_Table(array('name' =>  SQL_TABLE_PREFIX . 'application_tables'));
197         $where  = array(
198             $this->_db->quoteInto($this->_db->quoteIdentifier('name') . ' = ?', $_oldTableName),
199         );
200         $result = $applicationsTables->update(array('name' => $_newTableName), $where);
201     }
202     
203     /**
204      * drop table
205      *
206      * @param string $_tableName
207      * @param string $_application
208      */  
209     public function dropTable($_tableName, $_application = 'Tinebase')
210     {
211         Tinebase_Application::getInstance()->removeApplicationTable(Tinebase_Application::getInstance()->getApplicationByName($_application), $_tableName);
212         $result = $this->_backend->dropTable($_tableName);
213     }
214     
215     /**
216      * prompts for a username to set as active user on performing updates. this must be an admin user.
217      * the user account will be returned. this method can be called by cli only, so a exception will 
218      * be thrown if not running on cli
219      * 
220      * @throws Tinebase_Exception
221      * @return Tinebase_Model_FullUser
222      */
223     public function promptForUsername()
224     {
225         if (php_sapi_name() == 'cli') {
226             
227             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
228                 . ' Prompting for username on CLI');
229             
230             $userFound = NULL;
231             
232             do {
233                 try {
234                     if ($userFound === FALSE) {
235                         echo PHP_EOL;
236                         echo 'The user "' . $user . '" could not be found!' . PHP_EOL . PHP_EOL;
237                     }
238                     
239                     $user = Tinebase_Server_Cli::promptInput('Please enter an admin username to perform updates ');
240                     $userAccount = Tinebase_User::getInstance()->getFullUserByLoginName($user);
241                     
242                     if (! $userAccount->hasRight('Tinebase', Tinebase_Acl_Rights::ADMIN)) {
243                         $userFound = NULL;
244                         echo PHP_EOL;
245                         echo 'The user "' . $user . '" could be found, but this is not an admin user!' . PHP_EOL . PHP_EOL;
246                     } else {
247                         Tinebase_Core::set(Tinebase_Core::USER, $userAccount);
248                         $userFound = TRUE;
249                     }
250                     
251                 } catch (Tinebase_Exception_NotFound $e) {
252                     $userFound = FALSE;
253                 }
254                 
255             } while (! $userFound);
256             
257         } else {
258             throw new Tinebase_Exception('This update could be run from cli only!');
259         }
260         
261         return $userAccount;
262     }
263
264     /**
265      * get db adapter
266      * 
267      * @return Zend_Db_Adapter_Abstract
268      */
269     public function getDb()
270     {
271         return $this->_db;
272     }
273     
274     /**
275      * Search for text fields that contain a string longer as a specific length and truncate it to this length
276      * 
277      * @param string $table
278      * @param string $field
279      * @param int $length
280      */
281     public function shortenTextValues($table, $field, $length)
282     {
283         $results = $this->_db->query(
284             "SELECT " . $this->_db->quoteIdentifier($field) .
285             ", LEFT(" . $this->_db->quoteIdentifier($field) . "," . $length . ") AS `short`" .
286             " FROM " . $this->_db->quoteIdentifier(SQL_TABLE_PREFIX . $table) .
287             " WHERE CHAR_LENGTH(" . $this->_db->quoteIdentifier($field) . ") > " . $length
288             )->fetchAll();
289         
290         foreach ($results as $result) {
291             $where  = array(
292                 $this->_db->quoteInto($this->_db->quoteIdentifier($field) . ' = ?', $result[$field]),
293             );
294             
295             $newContent = array($field => $result['short']);
296             
297             try {
298                 $this->_db->update(SQL_TABLE_PREFIX . $table, $newContent, $where);
299                 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
300                     . ' Field was shortend: ' . print_r($result, true));
301             } catch (Tinebase_Exception_Record_Validation $terv) {
302                 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
303                     . ' Failed to shorten field: ' . print_r($result, true));
304                 Tinebase_Exception::log($terv);
305             }
306         }
307     }
308     
309     /**
310      * truncate text fields to a specific length
311      * Array needs to contain the table name, field name, and a config option for "<notnull>" ("true", "false")
312      * or use "null" to set default to NULL
313      * 
314      * @param array $columns
315      * @param int $length
316      */
317     public function truncateTextColumn($columns, $length)
318     {
319         foreach ($columns as $table => $fields) {
320             foreach ($fields as $field => $config) {
321                 
322                 $this->shortenTextValues($table, $field, $length);
323                 if (isset($config)) {
324                     $config = ($config == 'null' ? '<default>NULL</default>': '<notnull>' . $config . '</notnull>');
325                 }
326                 $declaration = new Setup_Backend_Schema_Field_Xml('
327                     <field>
328                         <name>' . $field . '</name>
329                         <type>text</type>
330                         <length>' . $length . '</length>'
331                         . $config .
332                     '</field>
333                 ');
334                 
335                 $this->_backend->alterCol($table, $declaration);
336             }
337         }
338     }
339 }