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