91e13f1ab51048bea228b4af886352a068c1dab8
[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 include_once($path_to_root."/includes/packages.inc");
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     $max_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                 Selectively extends access to selected security areas/sections.
53         */
54         function update_security_roles($sec_updates)
55         {
56                 global $security_areas, $security_sections;
57
58                 $roles = db_query("SELECT * FROM ".TB_PREF."security_roles");
59
60                 while($role = db_fetch($roles))
61                 {
62                         $role['areas'] = explode(';', $role['areas']);
63                         $role['sections'] = explode(';', $role['sections']);
64                         foreach($sec_updates as $has => $add)
65                         {
66                                 if (in_array($security_areas[$has][0], $role['areas']))
67                                 {
68                                         $sections = array();
69                                         foreach($add as $area)
70                                         {
71                                                 $role['areas'][] = $security_areas[$area][0];
72                                                 $role['sections'][] = $security_areas[$area][0]&~0xff;
73                                         }
74                                         sort($role['areas']);
75                                         update_security_role($role['id'], $role['role'], $role['description'], 
76                                                 array_values($role['sections']), array_values($role['areas']));
77                                 }
78                         }
79                 }
80                 return true;
81         }
82
83         /*
84                 Check and disable incompatible extensions.
85         */
86         function update_extensions()
87         {
88                 global $version;
89
90                 $mods = get_company_extensions();
91                 $exts = get_company_extensions($this->cur_company);
92
93                 $fixed = false;
94                 foreach($mods as $key => $ins) {
95                         foreach($exts as $ext)
96                                 if ($ext['name'] == $ins['name'] && (!check_src_ext_version($ins['version']))) {
97                                         $mods[$key]['active'] = false;
98                                         $this->log_error(sprintf(_("Uncompatible extension '%s' disabled for company %d."), $ins['name'], $this->cur_company), 'Notice');
99                                         $fixed = true;
100                                         continue 2;
101                                 }
102                 }
103                 if ($fixed)
104                         write_extensions($mods, $this->cur_company);
105         }
106
107         /*
108                 Pre-install maintenance: login to company, open upgrade log, make a backup
109         */
110         function pre_install($company)
111         {
112                 global $SysPrefs;
113
114                 $this->cur_company = $company;
115                 $this->errors = array();
116                 $this->backup = null;
117
118                 $this->save_log = ini_set('error_log', dirname(__FILE__).'/../../tmp/upgrade.'.$this->cur_company.'.log');
119                 $this->log_error(sprintf(_("Upgrade started for company %s."), $this->cur_company), 'Info');
120
121                 if (!set_global_connection($this->cur_company))
122                         return $this->log_error(_("Cannot connect to company database."));
123
124                 $cur_ver = get_company_pref('version_id', true);
125                 if ($cur_ver != $this->previous)
126                         return $this->log_error(sprintf(_("Cannot upgrade company %s: database version is incompatible ('%s' instead of '%s')."),
127                                 $this->cur_company, $cur_ver, $this->previous));
128
129                 $this->update_extensions();     // disable uncompatible extensions
130
131                 if (!$this->prepare())  // fetch params, perform additional checks (if any)
132                   return false;
133
134                 if (!$this->sql)
135                         return true;    // skip security backup if database content is not changed
136
137                 $this->backup = db_backup($this->companies[$this->cur_company], 'no', 'Security backup before upgrade',
138                         $SysPrefs->backup_dir($this->cur_company));
139
140                 if (!$this->backup)
141                   return $this->log_error(_("Security backup failed."));
142
143                 $this->log_error(sprintf(_("Security backup in file %s done."), $this->backup), 'Info');
144                 return true;
145         }
146
147         /*
148                 Basic install procedure using sql file.
149         */
150         function sql_install($company, $force=false) 
151         {
152                 global $path_to_root;
153
154                 if ($this->sql != '')   // perform basic upgrade operations defined in sql file
155                 {
156                         $result = true;
157
158                         if ($result === true)
159                                 $result = db_import($path_to_root. '/sql/'.$this->sql, $this->companies[$company],
160                                         $force, true, false, true);
161
162                         if ($result !== true)
163                         {
164                                 if (is_array($result))
165                                 {
166                                         foreach($result as $err)
167                                                 $this->log_error($err[1] . ':'. $err[0]);
168                                 } else
169                                 {
170                                         $this->log_error($result);
171                                         unset($this->backup); // prevent restore (database was not touched due to other errors)
172                                 }
173                                 return false;
174                         }
175                 }
176                 return true;
177         }
178
179         /*
180                 Post install procedures: update database version, or restore databse from backup file in case of errors
181         */
182         function post_install($result=true)
183         {
184                 global $db_version;
185
186                 if ($result !== true)
187                 {
188                         if ($this->backup)
189                         {
190                                 if (!set_global_connection($this->cur_company)) // reset connection to clear encoding
191                                         return $this->log_error(_("Cannot connect to company database for database restore."));
192
193                                 set_time_limit($this->max_upgrade_time);
194                                 $result = db_import($this->backup, $this->companies[$this->cur_company], true, false);
195                                 if ($result)
196                                         $this->log_error(_("Upgrade failed. Original database content restored successfully."), 'Info');
197                                 else
198                                         $this->log_error(sprintf(_("Database restore operation failed. Original database content is in %s file."), $this->backup));
199                                 $this->post_fail($this->cur_company);
200                         }
201                 } else {
202                         update_company_prefs(array('version_id' => $this->version));
203                 }
204
205                 $this->log_error(sprintf(_("Upgrade for company %s finished."), $this->cur_company), 'Info');
206
207                 set_global_connection();
208                 ini_set('error_log', $this->save_log);
209
210                 if (db_fixed())
211                         db_set_encoding();
212
213                 return $result;
214         }
215
216         /*
217                 Main routine for single company upgrade.
218         */
219         function upgrade_company($comp, $force=false)
220         {
221                 $result = $this->pre_install($comp) && $this->sql_install($comp, $force) && $this->install($comp, $force);
222
223                 $this->post_install($result);
224
225                 return count($this->errors) == 0;
226         }
227
228         /*
229                 Additional version specific php/sql upgrade procedures.
230                 This procedure is performed after basic sql upgrade script is run.
231         */
232         function install($company, $force=false) 
233         {
234                 return true;
235         }
236         /*
237                 Optional cleanup procedure.
238                 This procedure is run in case of upgrade failure, before the backup is restored.
239         */
240         function post_fail($company)
241         {
242         }
243
244     /*
245                 Present upgrade parameters to administrator
246                 This function presents upgrade choices, after selection company to be upgraded.
247     */
248         function show_params($comp)
249     {
250         }
251
252     /*
253             Fetch & check upgrade parameters, check additional upgrade pre-conditions.
254                 This function is run after successfull switching to target database connection.
255     */
256         function prepare()
257     {
258                 return true;
259         }
260
261 }
262
263 /*
264         Return databases status info.
265 */
266 function get_site_status($connections)
267 {
268                 global $SysPrefs;
269
270                 $info = array();
271
272                 foreach($connections as $i => $conn)
273                 {
274                         $info[$i]['status'] = set_global_connection($i) !== false;
275
276                         $info[$i]['name'] = $conn['name'];
277                         $info[$i]['table_set'] = $conn['host'].'/'.$conn['dbname'].':'.$conn['tbpref'].'*';
278                         if ($info[$i]['status'])
279                         {
280                                 $info[$i]['version'] = get_company_pref('version_id');
281                         }
282                 }
283                 set_global_connection();
284                 $SysPrefs->refresh();
285
286                 return $info;
287 }
288
289 /*
290         Creates table of installer objects sorted by applicable db scheme version.
291 */
292 function get_installers()
293 {
294         global $path_to_root;
295
296         $patchdir = $path_to_root."/sql/";
297         $upgrades = array();    
298         $datadir = @opendir($patchdir);
299
300         if ($datadir)
301         {
302                 while(false !== ($fname = readdir($datadir)))
303                 { // check all php files but index.php
304                         if (!is_dir($patchdir . $fname) && ($fname != 'index.php')
305                                 && stristr($fname, '.php') != false && $fname[0] != '.')
306                         {
307                                 unset($install);
308                                 include_once($patchdir . $fname);
309                                 if (isset($install)) // add installer if found
310                                         $upgrades[$install->previous] =  $install;
311                         }
312                 }
313                 ksort($upgrades); // sort by file name
314         }
315         return $upgrades;
316 }