Rewritten upgrade procedures, added fa_patch class, utf8 collation can be set before...
[fa-stable.git] / admin / includes / fa_patch.class.inc
1 <?php
2 /**********************************************************************
3     Copyright (C) FrontAccounting, LLC.
4         Released under the terms of the GNU General Public License, GPL, 
5         as published by the Free Software Foundation, either version 3 
6         of the License, or (at your option) any later version.
7     This program is distributed in the hope that it will be useful,
8     but WITHOUT ANY WARRANTY; without even the implied warranty of
9     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
10     See the License here <http://www.gnu.org/licenses/gpl-3.0.html>.
11 ***********************************************************************/
12
13 //
14 //      Utility class contains basic database upgrade routines.
15 //
16 class fa_patch {
17         var $previous;                          // previous database version 
18         var $version;                           // version after upgrade
19         var $description;                       // short patch description
20
21         var $sql;                                       // basic sql file
22
23         var $cur_company;
24         var $backup;                            // pre-upgrade backup filename
25
26         var $errors = array();
27         var     $ma_upgrade_time = 300;
28
29         function fa_patch()
30         {
31                 global $path_to_root;
32
33                 include $path_to_root."/config_db.php";
34
35                 $this->companies = $db_connections;
36
37                 return $this->companies;
38         }
39
40         /*
41                 Collect/log messages generated during upgrade process.
42         */
43         function log_error($msg, $type='Error')
44         {
45                 if ($type == 'Error')
46                         $this->errors[] = $msg;
47                 error_log(sprintf('[%s] %s', $type, $msg));
48                 return false;
49         }
50
51         /*
52                 Pre-install maintenance: login to company, open upgrade log, make a backup
53         */
54         function pre_install($company)
55         {
56                 global $SysPrefs;
57
58                 $this->cur_company = $company;
59                 $this->errors = array();
60                 $this->backup = null;
61
62                 $this->save_log = ini_set('error_log', dirname(__FILE__).'/../../tmp/upgrade.'.$this->cur_company.'.log');
63                 $this->log_error(sprintf(_("Upgrade started for company %s."), $this->cur_company), 'Info');
64
65                 if (!set_global_connection($this->cur_company))
66                         return $this->log_error(_("Cannot connect to company database."));
67
68                 $cur_ver = get_company_pref('version_id', true);
69                 if ($cur_ver != $this->previous)
70                         return $this->log_error(sprintf(_("Cannot upgrade company %s: database version is incompatible ('%s' instead of '%s')."),
71                                 $this->cur_company, $cur_ver, $this->previous));
72
73                 if (!$this->prepare())  // fetch params, perform additional checks (if any)
74                   return false;
75
76                 if (!$this->sql)
77                         return true;    // skip security backup if database content is not changed
78
79                 $this->backup = db_backup($this->companies[$this->cur_company], 'no', 'Security backup before upgrade',
80                         $SysPrefs->backup_dir($this->cur_company));
81
82                 if (!$this->backup)
83                   return $this->log_error(_("Security backup failed."));
84
85                 $this->log_error(sprintf(_("Security backup in file %s done."), $this->backup), 'Info');
86                 return true;
87         }
88
89         /*
90                 Basic install procedure using sql file.
91         */
92         function sql_install($company, $force=false) 
93         {
94                 global $path_to_root;
95
96                 if ($this->sql != '')   // perform basic upgrade operations defined in sql file
97                 {
98                         $result = true;
99
100                         if ($result === true)
101                                 $result = db_import($path_to_root. '/sql/'.$this->sql, $this->companies[$company],
102                                         $force, true, false, true);
103
104                         if ($result !== true)
105                         {
106                                 if (is_array($result))
107                                 {
108                                         foreach($result as $err)
109                                                 $this->log_error($err[1] . ':'. $err[0]);
110                                 } else
111                                 {
112                                         $this->log_error($result);
113                                         unset($this->backup); // prevent restore (database was not touched due to other errors)
114                                 }
115                                 return false;
116                         }
117                 }
118                 return true;
119         }
120
121         /*
122                 Post install procedures: update database version, or restore databse from backup file in case of errors
123         */
124         function post_install($result=true)
125         {
126                 global $db_version;
127
128                 if ($result !== true)
129                 {
130                         if ($this->backup)
131                         {
132                                 if (!set_global_connection($this->cur_company)) // reset connection to clear encoding
133                                         return $this->log_error(_("Cannot connect to company database for database restore."));
134
135                                 set_time_limit($this->max_upgrade_time);
136                                 $result = db_import($this->backup, $this->companies[$this->cur_company], true, false);
137                                 if ($result)
138                                         $this->log_error(_("Upgrade failed. Original database content restored successfully."), 'Info');
139                                 else
140                                         $thi->log_error(sprintf(_("Database restore operation failed. Original database content is in %s file."), $this->backup));
141                                 $this->post_fail($this->cur_company);
142                         }
143                 } else {
144                         update_company_prefs(array('version_id' => $this->version));
145                 }
146
147                 $this->log_error(sprintf(_("Upgrade for company %s finished."), $this->cur_company), 'Info');
148
149                 set_global_connection();
150                 ini_set('error_log', $this->save_log);
151
152                 if (db_fixed())
153                         db_set_encoding();
154
155                 return $result;
156         }
157
158         /*
159                 Main routine for single company upgrade.
160         */
161         function upgrade_company($comp, $force=false)
162         {
163                 $result = $this->pre_install($comp) && $this->sql_install($comp, $force) && $this->install($comp, $force);
164
165                 $this->post_install($result);
166
167                 return count($this->errors) == 0;
168         }
169
170         /*
171                 Additional version specific php/sql upgrade procedures.
172                 This procedure is performed after basic sql upgrade script is run.
173         */
174         function install($company, $force=false) 
175         {
176                 return true;
177         }
178         /*
179                 Optional cleanup procedure.
180                 This procedure is run in case of upgrade failure, before the backup is restored.
181         */
182         function post_fail($company)
183         {
184         }
185
186     /*
187                 Present upgrade parameters to administrator
188                 This function presents upgrade choices, after selection company to be upgraded.
189     */
190         function show_params($comp)
191     {
192         }
193
194     /*
195             Fetch & check upgrade parameters, check additional upgrade pre-conditions.
196                 This function is run after successfull switching to target database connection.
197     */
198         function prepare()
199     {
200                 return true;
201         }
202
203 }
204
205 /*
206         Return databases status info.
207 */
208 function get_site_status($connections)
209 {
210                 $info = array();
211
212                 foreach($connections as $i => $conn)
213                 {
214                         $info[$i]['status'] = set_global_connection($i) !== false;
215
216                         $info[$i]['name'] = $conn['name'];
217                         $info[$i]['table_set'] = $conn['host'].'/'.$conn['dbname'].':'.$conn['tbpref'].'*';
218                         if ($info[$i]['status'])
219                         {
220                                 $info[$i]['version'] = get_company_pref('version_id');
221                         }
222                 }
223                 set_global_connection();
224                 refresh_sys_prefs();
225
226                 return $info;
227 }
228
229 /*
230         Creates table of installer objects sorted by applicable db scheme version.
231 */
232 function get_installers()
233 {
234         global $path_to_root;
235
236         $patchdir = $path_to_root."/sql/";
237         $upgrades = array();    
238         $datadir = @opendir($patchdir);
239
240         if ($datadir)
241         {
242                 while(false !== ($fname = readdir($datadir)))
243                 { // check all php files but index.php
244                         if (!is_dir($patchdir . $fname) && ($fname != 'index.php')
245                                 && stristr($fname, '.php') != false && $fname[0] != '.')
246                         {
247                                 unset($install);
248                                 include_once($patchdir . $fname);
249                                 if (isset($install)) // add installer if found
250                                         $upgrades[$install->previous] =  $install;
251                         }
252                 }
253                 ksort($upgrades); // sort by file name
254         }
255         return $upgrades;
256 }