Total Complexity | 281 |
Total Lines | 1433 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like Utils often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Utils, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
39 | class Utils |
||
40 | { |
||
41 | /** |
||
42 | * @var DoliDB Database handler |
||
43 | */ |
||
44 | public $db; |
||
45 | |||
46 | /** |
||
47 | * @var string Error message |
||
48 | * @see $errors |
||
49 | */ |
||
50 | public $error; |
||
51 | |||
52 | /** |
||
53 | * @var string[] Array of error messages |
||
54 | */ |
||
55 | public $errors; |
||
56 | |||
57 | /** |
||
58 | * @var string Used by Cron method to return message |
||
59 | */ |
||
60 | public $output; |
||
61 | |||
62 | /** |
||
63 | * @var array{commandbackuplastdone:string,commandbackuptorun:string} Used by Cron method to return data |
||
64 | */ |
||
65 | public $result; |
||
66 | |||
67 | /** |
||
68 | * Constructor |
||
69 | * |
||
70 | * @param DoliDB $db Database handler |
||
71 | */ |
||
72 | public function __construct($db) |
||
75 | } |
||
76 | |||
77 | |||
78 | /** |
||
79 | * Purge files into directory of data files. |
||
80 | * CAN BE A CRON TASK |
||
81 | * |
||
82 | * @param string $choices Choice of purge mode ('tempfiles', 'tempfilesold' to purge temp older than $nbsecondsold seconds, 'logfiles', or mix of this). Note that 'allfiles' is also possible but very dangerous. |
||
83 | * @param int $nbsecondsold Nb of seconds old to accept deletion of a directory if $choice is 'tempfilesold', or deletion of file if $choice is 'allfiles' |
||
84 | * @return int 0 if OK, < 0 if KO (this function is used also by cron so only 0 is OK) |
||
85 | */ |
||
86 | public function purgeFiles($choices = 'tempfilesold+logfiles', $nbsecondsold = 86400) |
||
87 | { |
||
88 | global $conf, $langs, $user; |
||
89 | global $dolibarr_main_data_root; |
||
90 | |||
91 | $langs->load("admin"); |
||
92 | |||
93 | require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php'; |
||
94 | |||
95 | if (empty($choices)) { |
||
96 | $choices = 'tempfilesold+logfiles'; |
||
97 | } |
||
98 | if ($choices == 'allfiles' && $nbsecondsold > 0) { |
||
99 | $choices = 'allfilesold'; |
||
100 | } |
||
101 | |||
102 | dol_syslog("Utils::purgeFiles choice=" . $choices, LOG_DEBUG); |
||
103 | |||
104 | // For dangerous action, we check the user is admin |
||
105 | if (in_array($choices, array('allfiles', 'allfilesold'))) { |
||
106 | if (empty($user->admin)) { |
||
107 | $this->output = 'Error: to erase data files, user running the batch (currently ' . $user->login . ') must be an admin user'; |
||
108 | return 1; |
||
109 | } |
||
110 | } |
||
111 | |||
112 | $count = 0; |
||
113 | $countdeleted = 0; |
||
114 | $counterror = 0; |
||
115 | $filelog = ''; |
||
116 | |||
117 | $choicesarray = preg_split('/[\+,]/', $choices); |
||
118 | foreach ($choicesarray as $choice) { |
||
119 | $now = dol_now(); |
||
120 | $filesarray = array(); |
||
121 | |||
122 | if ($choice == 'tempfiles' || $choice == 'tempfilesold') { |
||
123 | // Delete temporary files |
||
124 | if ($dolibarr_main_data_root) { |
||
125 | $filesarray = dol_dir_list($dolibarr_main_data_root, "directories", 1, '^temp$', '', 'name', SORT_ASC, 2, 0, '', 1); // Do not follow symlinks |
||
126 | |||
127 | if ($choice == 'tempfilesold') { |
||
128 | foreach ($filesarray as $key => $val) { |
||
129 | if ($val['date'] > ($now - ($nbsecondsold))) { |
||
130 | unset($filesarray[$key]); // Discard temp dir not older than $nbsecondsold |
||
131 | } |
||
132 | } |
||
133 | } |
||
134 | } |
||
135 | } |
||
136 | |||
137 | if ($choice == 'allfiles') { |
||
138 | // Delete all files (except .lock and .unlock files, do not follow symbolic links) |
||
139 | if ($dolibarr_main_data_root) { |
||
140 | $filesarray = dol_dir_list($dolibarr_main_data_root, "all", 0, '', '(\.lock|\.unlock)$', 'name', SORT_ASC, 0, 0, '', 1); // No need to use recursive, we will delete directory |
||
141 | } |
||
142 | } |
||
143 | |||
144 | if ($choice == 'allfilesold') { |
||
145 | // Delete all files (except .lock and .unlock files, do not follow symbolic links) |
||
146 | if ($dolibarr_main_data_root) { |
||
147 | $filesarray = dol_dir_list($dolibarr_main_data_root, "files", 1, '', '(\.lock|\.unlock)$', 'name', SORT_ASC, 0, 0, '', 1, $nbsecondsold); // No need to use recursive, we will delete directory |
||
148 | } |
||
149 | } |
||
150 | |||
151 | if ($choice == 'logfile' || $choice == 'logfiles') { |
||
152 | // Define files log |
||
153 | if ($dolibarr_main_data_root) { |
||
154 | $filesarray = dol_dir_list($dolibarr_main_data_root, "files", 0, '.*\.log[\.0-9]*(\.gz)?$', '(\.lock|\.unlock)$', 'name', SORT_ASC, 0, 0, '', 1); |
||
155 | } |
||
156 | |||
157 | if (isModEnabled('syslog')) { |
||
158 | $filelog = getDolGlobalString('SYSLOG_FILE'); |
||
159 | $filelog = preg_replace('/DOL_DATA_ROOT/i', DOL_DATA_ROOT, $filelog); |
||
160 | |||
161 | $alreadyincluded = false; |
||
162 | foreach ($filesarray as $tmpcursor) { |
||
163 | if ($tmpcursor['fullname'] == $filelog) { |
||
164 | $alreadyincluded = true; |
||
165 | } |
||
166 | } |
||
167 | if (!$alreadyincluded) { |
||
168 | $filesarray[] = array('fullname' => $filelog, 'type' => 'file'); |
||
169 | } |
||
170 | } |
||
171 | } |
||
172 | |||
173 | if (is_array($filesarray) && count($filesarray)) { |
||
174 | foreach ($filesarray as $key => $value) { |
||
175 | //print "x ".$filesarray[$key]['fullname']."-".$filesarray[$key]['type']."<br>\n"; |
||
176 | if ($filesarray[$key]['type'] == 'dir') { |
||
177 | $startcount = 0; |
||
178 | $tmpcountdeleted = 0; |
||
179 | |||
180 | $result = dol_delete_dir_recursive($filesarray[$key]['fullname'], $startcount, 1, 0, $tmpcountdeleted); |
||
181 | $excluded = [ |
||
182 | $conf->user->dir_temp, |
||
183 | ]; |
||
184 | if (isModEnabled('api')) { |
||
185 | $excluded[] = $conf->api->dir_temp; |
||
186 | } |
||
187 | // The 2 directories $conf->api->dir_temp and $conf->user->dir_temp are recreated at end, so we do not count them |
||
188 | if (!in_array($filesarray[$key]['fullname'], $excluded)) { |
||
189 | $count += $result; |
||
190 | $countdeleted += $tmpcountdeleted; |
||
191 | } |
||
192 | } elseif ($filesarray[$key]['type'] == 'file') { |
||
193 | if ($choice != 'allfilesold' || $filesarray[$key]['date'] < ($now - $nbsecondsold)) { |
||
194 | // If (file that is not logfile) or (if mode is logfile) |
||
195 | if ($filesarray[$key]['fullname'] != $filelog || $choice == 'logfile' || $choice == 'logfiles') { |
||
196 | $result = dol_delete_file($filesarray[$key]['fullname'], 1, 1); |
||
197 | if ($result) { |
||
198 | $count++; |
||
199 | $countdeleted++; |
||
200 | } else { |
||
201 | $counterror++; |
||
202 | } |
||
203 | } |
||
204 | } |
||
205 | } |
||
206 | } |
||
207 | |||
208 | // Update cachenbofdoc |
||
209 | if (isModEnabled('ecm') && $choice == 'allfiles') { |
||
210 | $ecmdirstatic = new EcmDirectory($this->db); |
||
211 | $result = $ecmdirstatic->refreshcachenboffile(1); |
||
212 | } |
||
213 | } |
||
214 | } |
||
215 | |||
216 | if ($count > 0) { |
||
217 | $langs->load("admin"); |
||
218 | $this->output = $langs->trans("PurgeNDirectoriesDeleted", $countdeleted); |
||
219 | if ($count > $countdeleted) { |
||
220 | $this->output .= '<br>' . $langs->trans("PurgeNDirectoriesFailed", ($count - $countdeleted)); |
||
221 | } |
||
222 | } else { |
||
223 | $this->output = $langs->trans("PurgeNothingToDelete") . (in_array('tempfilesold', $choicesarray) ? ' (older than 24h for temp files)' : ''); |
||
224 | } |
||
225 | |||
226 | // Recreate temp dir that are not automatically recreated by core code for performance purpose, we need them |
||
227 | if (isModEnabled('api')) { |
||
228 | dol_mkdir($conf->api->dir_temp); |
||
229 | } |
||
230 | dol_mkdir($conf->user->dir_temp); |
||
231 | |||
232 | //return $count; |
||
233 | return 0; // This function can be called by cron so must return 0 if OK |
||
234 | } |
||
235 | |||
236 | |||
237 | /** |
||
238 | * Make a backup of database |
||
239 | * CAN BE A CRON TASK |
||
240 | * |
||
241 | * @param string $compression 'gz' or 'bz' or 'none' |
||
242 | * @param string $type 'mysql', 'postgresql', ... |
||
243 | * @param int $usedefault 1=Use default backup profile (Set this to 1 when used as cron) |
||
244 | * @param string $file 'auto' or filename to build |
||
245 | * @param int $keeplastnfiles Keep only last n files (not used yet) |
||
246 | * @param int $execmethod 0=Use default method (that is 1 by default), 1=Use the PHP 'exec' - need size of dump in memory, but low memory method is used if GETPOST('lowmemorydump') is set, 2=Use the 'popen' method (low memory method) |
||
247 | * @param int $lowmemorydump 1=Use the low memory method. If $lowmemorydump is set, it means we want to make the compression using an external pipe instead retrieving the content of the dump in PHP memory array $output_arr and then print it into the PHP pipe open with xopen(). |
||
248 | * @return int 0 if OK, < 0 if KO (this function is used also by cron so only 0 is OK) |
||
249 | */ |
||
250 | public function dumpDatabase($compression = 'none', $type = 'auto', $usedefault = 1, $file = 'auto', $keeplastnfiles = 0, $execmethod = 0, $lowmemorydump = 0) |
||
251 | { |
||
252 | global $db, $conf, $langs, $dolibarr_main_data_root; |
||
253 | global $dolibarr_main_db_name, $dolibarr_main_db_host, $dolibarr_main_db_user, $dolibarr_main_db_port, $dolibarr_main_db_pass; |
||
254 | global $dolibarr_main_db_character_set; |
||
255 | |||
256 | $langs->load("admin"); |
||
257 | |||
258 | dol_syslog("Utils::dumpDatabase type=" . $type . " compression=" . $compression . " file=" . $file, LOG_DEBUG); |
||
259 | require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php'; |
||
260 | |||
261 | // Clean data |
||
262 | $file = dol_sanitizeFileName($file); |
||
263 | |||
264 | // Check compression parameter |
||
265 | if (!in_array($compression, array('none', 'gz', 'bz', 'zip', 'zstd'))) { |
||
266 | $langs->load("errors"); |
||
267 | $this->error = $langs->transnoentitiesnoconv("ErrorBadValueForParameter", $compression, "Compression"); |
||
268 | return -1; |
||
269 | } |
||
270 | |||
271 | // Check type parameter |
||
272 | if ($type == 'auto') { |
||
273 | $type = $this->db->type; |
||
274 | } |
||
275 | if (!in_array($type, array('postgresql', 'pgsql', 'mysql', 'mysqli', 'mysqlnobin'))) { |
||
276 | $langs->load("errors"); |
||
277 | $this->error = $langs->transnoentitiesnoconv("ErrorBadValueForParameter", $type, "Basetype"); |
||
278 | return -1; |
||
279 | } |
||
280 | |||
281 | // Check file parameter |
||
282 | if ($file == 'auto') { |
||
283 | $prefix = 'dump'; |
||
284 | $ext = 'sql'; |
||
285 | if (in_array($type, array('mysql', 'mysqli'))) { |
||
286 | $prefix = 'mysqldump'; |
||
287 | } |
||
288 | //if ($label == 'PostgreSQL') { $prefix='pg_dump'; $ext='dump'; } |
||
289 | if (in_array($type, array('pgsql'))) { |
||
290 | $prefix = 'pg_dump'; |
||
291 | } |
||
292 | $file = $prefix . '_' . $dolibarr_main_db_name . '_' . dol_sanitizeFileName(DOL_VERSION) . '_' . dol_print_date(dol_now('gmt'), "dayhourlogsmall", 'tzuser') . '.' . $ext; |
||
293 | } |
||
294 | |||
295 | $outputdir = $conf->admin->dir_output . '/backup'; |
||
296 | $result = dol_mkdir($outputdir); |
||
297 | $errormsg = ''; |
||
298 | |||
299 | // MYSQL |
||
300 | if ($type == 'mysql' || $type == 'mysqli') { |
||
301 | if (!getDolGlobalString('SYSTEMTOOLS_MYSQLDUMP')) { |
||
302 | $cmddump = $db->getPathOfDump(); |
||
303 | } else { |
||
304 | $cmddump = getDolGlobalString('SYSTEMTOOLS_MYSQLDUMP'); |
||
305 | } |
||
306 | if (empty($cmddump)) { |
||
307 | $this->error = "Failed to detect command to use for mysqldump. Try a manual backup before to set path of command."; |
||
308 | return -1; |
||
309 | } |
||
310 | |||
311 | $outputfile = $outputdir . '/' . $file; |
||
312 | // for compression format, we add extension |
||
313 | $compression = $compression ? $compression : 'none'; |
||
314 | if ($compression == 'gz') { |
||
315 | $outputfile .= '.gz'; |
||
316 | } elseif ($compression == 'bz') { |
||
317 | $outputfile .= '.bz2'; |
||
318 | } elseif ($compression == 'zstd') { |
||
319 | $outputfile .= '.zst'; |
||
320 | } |
||
321 | $outputerror = $outputfile . '.err'; |
||
322 | dol_mkdir($conf->admin->dir_output . '/backup'); |
||
323 | |||
324 | // Parameters execution |
||
325 | $command = $cmddump; |
||
326 | $command = preg_replace('/(\$|%)/', '', $command); // We removed chars that can be used to inject vars that contains space inside path of command without seeing there is a space to bypass the escapeshellarg. |
||
327 | if (preg_match("/\s/", $command)) { |
||
328 | $command = escapeshellarg($command); // If there is spaces, we add quotes on command to be sure $command is only a program and not a program+parameters |
||
329 | } |
||
330 | |||
331 | //$param=escapeshellarg($dolibarr_main_db_name)." -h ".escapeshellarg($dolibarr_main_db_host)." -u ".escapeshellarg($dolibarr_main_db_user)." -p".escapeshellarg($dolibarr_main_db_pass); |
||
332 | $param = $dolibarr_main_db_name . " -h " . $dolibarr_main_db_host; |
||
333 | $param .= " -u " . $dolibarr_main_db_user; |
||
334 | if (!empty($dolibarr_main_db_port)) { |
||
335 | $param .= " -P " . $dolibarr_main_db_port . " --protocol=tcp"; |
||
336 | } |
||
337 | if (GETPOST("use_transaction", "alpha")) { |
||
338 | $param .= " --single-transaction"; |
||
339 | } |
||
340 | if (GETPOST("disable_fk", "alpha") || $usedefault) { |
||
341 | $param .= " -K"; |
||
342 | } |
||
343 | if (GETPOST("sql_compat", "alpha") && GETPOST("sql_compat", "alpha") != 'NONE') { |
||
344 | $param .= " --compatible=" . escapeshellarg(GETPOST("sql_compat", "alpha")); |
||
345 | } |
||
346 | if (GETPOST("drop_database", "alpha")) { |
||
347 | $param .= " --add-drop-database"; |
||
348 | } |
||
349 | if (GETPOST("use_mysql_quick_param", "alpha")) { |
||
350 | $param .= " --quick"; |
||
351 | } |
||
352 | if (GETPOST("use_force", "alpha")) { |
||
353 | $param .= " -f"; |
||
354 | } |
||
355 | if (GETPOST("sql_structure", "alpha") || $usedefault) { |
||
356 | if (GETPOST("drop", "alpha") || $usedefault) { |
||
357 | $param .= " --add-drop-table=TRUE"; |
||
358 | } else { |
||
359 | $param .= " --add-drop-table=FALSE"; |
||
360 | } |
||
361 | } else { |
||
362 | $param .= " -t"; |
||
363 | } |
||
364 | if (GETPOST("disable-add-locks", "alpha")) { |
||
365 | $param .= " --add-locks=FALSE"; |
||
366 | } |
||
367 | if (GETPOST("sql_data", "alpha") || $usedefault) { |
||
368 | $param .= " --tables"; |
||
369 | if (GETPOST("showcolumns", "alpha") || $usedefault) { |
||
370 | $param .= " -c"; |
||
371 | } |
||
372 | if (GETPOST("extended_ins", "alpha") || $usedefault) { |
||
373 | $param .= " -e"; |
||
374 | } else { |
||
375 | $param .= " --skip-extended-insert"; |
||
376 | } |
||
377 | if (GETPOST("delayed", "alpha")) { |
||
378 | $param .= " --delayed-insert"; |
||
379 | } |
||
380 | if (GETPOST("sql_ignore", "alpha")) { |
||
381 | $param .= " --insert-ignore"; |
||
382 | } |
||
383 | if (GETPOST("hexforbinary", "alpha") || $usedefault) { |
||
384 | $param .= " --hex-blob"; |
||
385 | } |
||
386 | } else { |
||
387 | $param .= " -d"; // No row information (no data) |
||
388 | } |
||
389 | if ($dolibarr_main_db_character_set == 'utf8mb4') { |
||
390 | // We save output into utf8mb4 charset |
||
391 | $param .= " --default-character-set=utf8mb4 --no-tablespaces"; |
||
392 | } else { |
||
393 | $param .= " --default-character-set=utf8 --no-tablespaces"; // We always save output into utf8 charset |
||
394 | } |
||
395 | $paramcrypted = $param; |
||
396 | $paramclear = $param; |
||
397 | if (!empty($dolibarr_main_db_pass)) { |
||
398 | $paramcrypted .= ' -p"' . preg_replace('/./i', '*', $dolibarr_main_db_pass) . '"'; |
||
399 | $paramclear .= ' -p"' . str_replace(array('"', '`', '$'), array('\"', '\`', '\$'), $dolibarr_main_db_pass) . '"'; |
||
400 | } |
||
401 | |||
402 | $handle = ''; |
||
403 | |||
404 | // Start call method to execute dump |
||
405 | $fullcommandcrypted = $command . " " . $paramcrypted . " 2>&1"; |
||
406 | $fullcommandclear = $command . " " . $paramclear . " 2>&1"; |
||
407 | if (!$lowmemorydump) { |
||
408 | if ($compression == 'none') { |
||
409 | $handle = fopen($outputfile, 'w'); |
||
410 | } elseif ($compression == 'gz') { |
||
411 | $handle = gzopen($outputfile, 'w'); |
||
412 | } elseif ($compression == 'bz') { |
||
413 | $handle = bzopen($outputfile, 'w'); |
||
414 | } elseif ($compression == 'zstd') { |
||
415 | $handle = fopen($outputfile, 'w'); |
||
416 | } |
||
417 | } else { |
||
418 | if ($compression == 'none') { |
||
419 | $fullcommandclear .= ' | grep -v "Warning: Using a password on the command line interface can be insecure." > "' . dol_sanitizePathName($outputfile) . '"'; |
||
420 | $fullcommandcrypted .= ' | grep -v "Warning: Using a password on the command line interface can be insecure." > "' . dol_sanitizePathName($outputfile) . '"'; |
||
421 | $handle = 1; |
||
422 | } elseif ($compression == 'gz') { |
||
423 | $fullcommandclear .= ' | grep -v "Warning: Using a password on the command line interface can be insecure." | gzip > "' . dol_sanitizePathName($outputfile) . '"'; |
||
424 | $fullcommandcrypted .= ' | grep -v "Warning: Using a password on the command line interface can be insecure." | gzip > "' . dol_sanitizePathName($outputfile) . '"'; |
||
425 | $paramcrypted .= ' | grep -v "Warning: Using a password on the command line interface can be insecure." | gzip'; |
||
426 | $handle = 1; |
||
427 | } elseif ($compression == 'bz') { |
||
428 | $fullcommandclear .= ' | grep -v "Warning: Using a password on the command line interface can be insecure." | bzip2 > "' . dol_sanitizePathName($outputfile) . '"'; |
||
429 | $fullcommandcrypted .= ' | grep -v "Warning: Using a password on the command line interface can be insecure." | bzip2 > "' . dol_sanitizePathName($outputfile) . '"'; |
||
430 | $paramcrypted .= ' | grep -v "Warning: Using a password on the command line interface can be insecure." | bzip2'; |
||
431 | $handle = 1; |
||
432 | } elseif ($compression == 'zstd') { |
||
433 | $fullcommandclear .= ' | grep -v "Warning: Using a password on the command line interface can be insecure." | zstd > "' . dol_sanitizePathName($outputfile) . '"'; |
||
434 | $fullcommandcrypted .= ' | grep -v "Warning: Using a password on the command line interface can be insecure." | zstd > "' . dol_sanitizePathName($outputfile) . '"'; |
||
435 | $paramcrypted .= ' | grep -v "Warning: Using a password on the command line interface can be insecure." | zstd'; |
||
436 | $handle = 1; |
||
437 | } |
||
438 | } |
||
439 | |||
440 | $ok = 0; |
||
441 | if ($handle) { |
||
442 | if (getDolGlobalString('MAIN_EXEC_USE_POPEN')) { |
||
443 | $execmethod = getDolGlobalString('MAIN_EXEC_USE_POPEN'); |
||
444 | } |
||
445 | if (empty($execmethod)) { |
||
446 | $execmethod = 1; |
||
447 | } |
||
448 | |||
449 | dol_syslog("Utils::dumpDatabase execmethod=" . $execmethod . " command:" . $fullcommandcrypted, LOG_INFO); |
||
450 | |||
451 | |||
452 | /* If value has been forced with a php_admin_value, this has no effect. Example of value: '512M' */ |
||
453 | $MemoryLimit = getDolGlobalString('MAIN_MEMORY_LIMIT_DUMP'); |
||
454 | if (!empty($MemoryLimit)) { |
||
455 | @ini_set('memory_limit', $MemoryLimit); |
||
|
|||
456 | } |
||
457 | |||
458 | |||
459 | if ($execmethod == 1) { |
||
460 | $output_arr = array(); |
||
461 | $retval = null; |
||
462 | |||
463 | exec($fullcommandclear, $output_arr, $retval); |
||
464 | // TODO Replace this exec with Utils->executeCLI() function. |
||
465 | // We must check that the case for $lowmemorydump works too... |
||
466 | //$utils = new Utils($db); |
||
467 | //$outputfile = $conf->admin->dir_temp.'/dump.tmp'; |
||
468 | //$utils->executeCLI($fullcommandclear, $outputfile, 0); |
||
469 | |||
470 | if ($retval != 0) { |
||
471 | $langs->load("errors"); |
||
472 | dol_syslog("Datadump retval after exec=" . $retval, LOG_ERR); |
||
473 | $errormsg = 'Error ' . $retval; |
||
474 | $ok = 0; |
||
475 | } else { |
||
476 | $i = 0; |
||
477 | if (!empty($output_arr)) { |
||
478 | foreach ($output_arr as $key => $read) { |
||
479 | $i++; // output line number |
||
480 | if ($i == 1 && preg_match('/Warning.*Using a password/i', $read)) { |
||
481 | continue; |
||
482 | } |
||
483 | // Now check into the result file, that the file end with "-- Dump completed" |
||
484 | // This is possible only if $output_arr is the clear dump file, so not possible with $lowmemorydump set because file is already compressed. |
||
485 | if (!$lowmemorydump) { |
||
486 | fwrite($handle, $read . ($execmethod == 2 ? '' : "\n")); |
||
487 | if (preg_match('/' . preg_quote('-- Dump completed', '/') . '/i', $read)) { |
||
488 | $ok = 1; |
||
489 | } elseif (preg_match('/' . preg_quote('SET SQL_NOTES=@OLD_SQL_NOTES', '/') . '/i', $read)) { |
||
490 | $ok = 1; |
||
491 | } |
||
492 | } else { |
||
493 | // If we have a result here in lowmemorydump mode, something is strange |
||
494 | } |
||
495 | } |
||
496 | } elseif ($lowmemorydump) { |
||
497 | $ok = 1; |
||
498 | } |
||
499 | } |
||
500 | } |
||
501 | |||
502 | if ($execmethod == 2) { // With this method, there is no way to get the return code, only output |
||
503 | $handlein = popen($fullcommandclear, 'r'); |
||
504 | $i = 0; |
||
505 | if ($handlein) { |
||
506 | while (!feof($handlein)) { |
||
507 | $i++; // output line number |
||
508 | $read = fgets($handlein); |
||
509 | // Exclude warning line we don't want |
||
510 | if ($i == 1 && preg_match('/Warning.*Using a password/i', $read)) { |
||
511 | continue; |
||
512 | } |
||
513 | fwrite($handle, $read); |
||
514 | if (preg_match('/' . preg_quote('-- Dump completed') . '/i', $read)) { |
||
515 | $ok = 1; |
||
516 | } elseif (preg_match('/' . preg_quote('SET SQL_NOTES=@OLD_SQL_NOTES') . '/i', $read)) { |
||
517 | $ok = 1; |
||
518 | } |
||
519 | } |
||
520 | pclose($handlein); |
||
521 | } |
||
522 | } |
||
523 | |||
524 | if (!$lowmemorydump) { |
||
525 | if ($compression == 'none') { |
||
526 | fclose($handle); |
||
527 | } elseif ($compression == 'gz') { |
||
528 | gzclose($handle); |
||
529 | } elseif ($compression == 'bz') { |
||
530 | fclose($handle); |
||
531 | } elseif ($compression == 'zstd') { |
||
532 | fclose($handle); |
||
533 | } |
||
534 | } |
||
535 | |||
536 | dolChmod($outputfile); |
||
537 | } else { |
||
538 | $langs->load("errors"); |
||
539 | dol_syslog("Failed to open file " . $outputfile, LOG_ERR); |
||
540 | $errormsg = $langs->trans("ErrorFailedToWriteInDir"); |
||
541 | } |
||
542 | |||
543 | // Get errorstring |
||
544 | if ($compression == 'none') { |
||
545 | $handle = fopen($outputfile, 'r'); |
||
546 | } elseif ($compression == 'gz') { |
||
547 | $handle = gzopen($outputfile, 'r'); |
||
548 | } elseif ($compression == 'bz') { |
||
549 | $handle = bzopen($outputfile, 'r'); |
||
550 | } elseif ($compression == 'zstd') { |
||
551 | $handle = fopen($outputfile, 'r'); |
||
552 | } |
||
553 | if ($handle) { |
||
554 | // Get 2048 first chars of error message. |
||
555 | $errormsg = fgets($handle, 2048); |
||
556 | //$ok=0;$errormsg=''; To force error |
||
557 | |||
558 | // Close file |
||
559 | if ($compression == 'none') { |
||
560 | fclose($handle); |
||
561 | } elseif ($compression == 'gz') { |
||
562 | gzclose($handle); |
||
563 | } elseif ($compression == 'bz') { |
||
564 | fclose($handle); |
||
565 | } elseif ($compression == 'zstd') { |
||
566 | fclose($handle); |
||
567 | } |
||
568 | if ($ok && preg_match('/^-- (MySql|MariaDB)/i', $errormsg)) { // No error |
||
569 | $errormsg = ''; |
||
570 | } else { |
||
571 | // Renommer fichier sortie en fichier erreur |
||
572 | //print "$outputfile -> $outputerror"; |
||
573 | @dol_delete_file($outputerror, 1, 0, 0, null, false, 0); |
||
574 | @rename($outputfile, $outputerror); |
||
575 | // Si safe_mode on et command hors du parameter exec, on a un fichier out vide donc errormsg vide |
||
576 | if (!$errormsg) { |
||
577 | $langs->load("errors"); |
||
578 | $errormsg = $langs->trans("ErrorFailedToRunExternalCommand"); |
||
579 | } |
||
580 | } |
||
581 | } |
||
582 | // Fin execution commande |
||
583 | |||
584 | $this->output = $errormsg; |
||
585 | $this->error = $errormsg; |
||
586 | $this->result = array("commandbackuplastdone" => $command . " " . $paramcrypted, "commandbackuptorun" => ""); |
||
587 | //if (empty($this->output)) $this->output=$this->result['commandbackuplastdone']; |
||
588 | } |
||
589 | |||
590 | // MYSQL NO BIN |
||
591 | if ($type == 'mysqlnobin') { |
||
592 | $outputfile = $outputdir . '/' . $file; |
||
593 | $outputfiletemp = $outputfile . '-TMP.sql'; |
||
594 | // for compression format, we add extension |
||
595 | $compression = $compression ? $compression : 'none'; |
||
596 | if ($compression == 'gz') { |
||
597 | $outputfile .= '.gz'; |
||
598 | } |
||
599 | if ($compression == 'bz') { |
||
600 | $outputfile .= '.bz2'; |
||
601 | } |
||
602 | $outputerror = $outputfile . '.err'; |
||
603 | dol_mkdir($conf->admin->dir_output . '/backup'); |
||
604 | |||
605 | if ($compression == 'gz' or $compression == 'bz') { |
||
606 | $this->backupTables($outputfiletemp); |
||
607 | dol_compress_file($outputfiletemp, $outputfile, $compression); |
||
608 | unlink($outputfiletemp); |
||
609 | } else { |
||
610 | $this->backupTables($outputfile); |
||
611 | } |
||
612 | |||
613 | $this->output = ""; |
||
614 | $this->result = array("commandbackuplastdone" => "", "commandbackuptorun" => ""); |
||
615 | } |
||
616 | |||
617 | // POSTGRESQL |
||
618 | if ($type == 'postgresql' || $type == 'pgsql') { |
||
619 | $cmddump = getDolGlobalString('SYSTEMTOOLS_POSTGRESQLDUMP'); |
||
620 | |||
621 | $outputfile = $outputdir . '/' . $file; |
||
622 | // for compression format, we add extension |
||
623 | $compression = $compression ? $compression : 'none'; |
||
624 | if ($compression == 'gz') { |
||
625 | $outputfile .= '.gz'; |
||
626 | } |
||
627 | if ($compression == 'bz') { |
||
628 | $outputfile .= '.bz2'; |
||
629 | } |
||
630 | $outputerror = $outputfile . '.err'; |
||
631 | dol_mkdir($conf->admin->dir_output . '/backup'); |
||
632 | |||
633 | // Parameters execution |
||
634 | $command = $cmddump; |
||
635 | $command = preg_replace('/(\$|%)/', '', $command); // We removed chars that can be used to inject vars that contains space inside path of command without seeing there is a space to bypass the escapeshellarg. |
||
636 | if (preg_match("/\s/", $command)) { |
||
637 | $command = escapeshellarg($command); // If there is spaces, we add quotes on command to be sure $command is only a program and not a program+parameters |
||
638 | } |
||
639 | |||
640 | //$param=escapeshellarg($dolibarr_main_db_name)." -h ".escapeshellarg($dolibarr_main_db_host)." -u ".escapeshellarg($dolibarr_main_db_user)." -p".escapeshellarg($dolibarr_main_db_pass); |
||
641 | //$param="-F c"; |
||
642 | $param = "-F p"; |
||
643 | $param .= " --no-tablespaces --inserts -h " . $dolibarr_main_db_host; |
||
644 | $param .= " -U " . $dolibarr_main_db_user; |
||
645 | if (!empty($dolibarr_main_db_port)) { |
||
646 | $param .= " -p " . $dolibarr_main_db_port; |
||
647 | } |
||
648 | if (GETPOST("sql_compat") && GETPOST("sql_compat") == 'ANSI') { |
||
649 | $param .= " --disable-dollar-quoting"; |
||
650 | } |
||
651 | if (GETPOST("drop_database")) { |
||
652 | $param .= " -c -C"; |
||
653 | } |
||
654 | if (GETPOST("sql_structure")) { |
||
655 | if (GETPOST("drop")) { |
||
656 | $param .= " --add-drop-table"; |
||
657 | } |
||
658 | if (!GETPOST("sql_data")) { |
||
659 | $param .= " -s"; |
||
660 | } |
||
661 | } |
||
662 | if (GETPOST("sql_data")) { |
||
663 | if (!GETPOST("sql_structure")) { |
||
664 | $param .= " -a"; |
||
665 | } |
||
666 | if (GETPOST("showcolumns")) { |
||
667 | $param .= " -c"; |
||
668 | } |
||
669 | } |
||
670 | $param .= ' -f "' . $outputfile . '"'; |
||
671 | //if ($compression == 'none') |
||
672 | if ($compression == 'gz') { |
||
673 | $param .= ' -Z 9'; |
||
674 | } |
||
675 | //if ($compression == 'bz') |
||
676 | $paramcrypted = $param; |
||
677 | $paramclear = $param; |
||
678 | /*if (!empty($dolibarr_main_db_pass)) |
||
679 | { |
||
680 | $paramcrypted.=" -W".preg_replace('/./i','*',$dolibarr_main_db_pass); |
||
681 | $paramclear.=" -W".$dolibarr_main_db_pass; |
||
682 | }*/ |
||
683 | $paramcrypted .= " -w " . $dolibarr_main_db_name; |
||
684 | $paramclear .= " -w " . $dolibarr_main_db_name; |
||
685 | |||
686 | $this->output = ""; |
||
687 | $this->result = array("commandbackuplastdone" => "", "commandbackuptorun" => $command . " " . $paramcrypted); |
||
688 | } |
||
689 | |||
690 | // Clean old files |
||
691 | if (!$errormsg && $keeplastnfiles > 0) { |
||
692 | $tmpfiles = dol_dir_list($conf->admin->dir_output . '/backup', 'files', 0, '', '(\.err|\.old|\.sav)$', 'date', SORT_DESC); |
||
693 | $i = 0; |
||
694 | if (is_array($tmpfiles)) { |
||
695 | foreach ($tmpfiles as $key => $val) { |
||
696 | $i++; |
||
697 | if ($i <= $keeplastnfiles) { |
||
698 | continue; |
||
699 | } |
||
700 | dol_delete_file($val['fullname'], 0, 0, 0, null, false, 0); |
||
701 | } |
||
702 | } |
||
703 | } |
||
704 | |||
705 | return ($errormsg ? -1 : 0); |
||
706 | } |
||
707 | |||
708 | /** Backup the db OR just a table without mysqldump binary, with PHP only (does not require any exec permission) |
||
709 | * Author: David Walsh (http://davidwalsh.name/backup-mysql-database-php) |
||
710 | * Updated and enhanced by Stephen Larroque (lrq3000) and by the many commentators from the blog |
||
711 | * Note about foreign keys constraints: for Dolibarr, since there are a lot of constraints and when imported the tables will be inserted in the dumped order, not in constraints order, then we ABSOLUTELY need to use SET FOREIGN_KEY_CHECKS=0; when importing the sql dump. |
||
712 | * Note2: db2SQL by Howard Yeend can be an alternative, by using SHOW FIELDS FROM and SHOW KEYS FROM we could generate a more precise dump (eg: by getting the type of the field and then precisely outputting the right formatting - in quotes, numeric or null - instead of trying to guess like we are doing now). |
||
713 | * |
||
714 | * @param string $outputfile Output file name |
||
715 | * @param string $tables Table name or '*' for all |
||
716 | * @return int Return integer <0 if KO, >0 if OK |
||
717 | */ |
||
718 | public function backupTables($outputfile, $tables = '*') |
||
719 | { |
||
720 | global $db, $langs; |
||
721 | global $errormsg; |
||
722 | |||
723 | // Set to UTF-8 |
||
724 | if (is_a($db, 'DoliDBMysqli')) { |
||
725 | /** @var DoliDBMysqli $db */ |
||
726 | $db->db->set_charset('utf8'); |
||
727 | } else { |
||
728 | /** @var DoliDB $db */ |
||
729 | $db->query('SET NAMES utf8'); |
||
730 | $db->query('SET CHARACTER SET utf8'); |
||
731 | } |
||
732 | |||
733 | //get all of the tables |
||
734 | if ($tables == '*') { |
||
735 | $tables = array(); |
||
736 | $result = $db->query('SHOW FULL TABLES WHERE Table_type = \'BASE TABLE\''); |
||
737 | while ($row = $db->fetch_row($result)) { |
||
738 | $tables[] = $row[0]; |
||
739 | } |
||
740 | } else { |
||
741 | $tables = is_array($tables) ? $tables : explode(',', $tables); |
||
742 | } |
||
743 | |||
744 | //cycle through |
||
745 | $handle = fopen($outputfile, 'w+'); |
||
746 | if (fwrite($handle, '') === false) { |
||
747 | $langs->load("errors"); |
||
748 | dol_syslog("Failed to open file " . $outputfile, LOG_ERR); |
||
749 | $errormsg = $langs->trans("ErrorFailedToWriteInDir"); |
||
750 | return -1; |
||
751 | } |
||
752 | |||
753 | // Print headers and global mysql config vars |
||
754 | $sqlhead = ''; |
||
755 | $sqlhead .= "-- " . $db::LABEL . " dump via php with Dolibarr " . DOL_VERSION . " |
||
756 | -- |
||
757 | -- Host: " . $db->db->host_info . " Database: " . $db->database_name . " |
||
758 | -- ------------------------------------------------------ |
||
759 | -- Server version " . $db->db->server_info . " |
||
760 | |||
761 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; |
||
762 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; |
||
763 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; |
||
764 | /*!40101 SET NAMES utf8 */; |
||
765 | /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; |
||
766 | /*!40103 SET TIME_ZONE='+00:00' */; |
||
767 | /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; |
||
768 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; |
||
769 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; |
||
770 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; |
||
771 | |||
772 | "; |
||
773 | |||
774 | if (GETPOST("nobin_disable_fk")) { |
||
775 | $sqlhead .= "SET FOREIGN_KEY_CHECKS=0;\n"; |
||
776 | } |
||
777 | //$sqlhead .= "SET SQL_MODE=\"NO_AUTO_VALUE_ON_ZERO\";\n"; |
||
778 | if (GETPOST("nobin_use_transaction")) { |
||
779 | $sqlhead .= "SET AUTOCOMMIT=0;\nSTART TRANSACTION;\n"; |
||
780 | } |
||
781 | |||
782 | fwrite($handle, $sqlhead); |
||
783 | |||
784 | $ignore = ''; |
||
785 | if (GETPOST("nobin_sql_ignore")) { |
||
786 | $ignore = 'IGNORE '; |
||
787 | } |
||
788 | $delayed = ''; |
||
789 | if (GETPOST("nobin_delayed")) { |
||
790 | $delayed = 'DELAYED '; |
||
791 | } |
||
792 | |||
793 | // Process each table and print their definition + their datas |
||
794 | foreach ($tables as $table) { |
||
795 | // Saving the table structure |
||
796 | fwrite($handle, "\n--\n-- Table structure for table `" . $table . "`\n--\n"); |
||
797 | |||
798 | if (GETPOST("nobin_drop")) { |
||
799 | fwrite($handle, "DROP TABLE IF EXISTS `" . $table . "`;\n"); // Dropping table if exists prior to re create it |
||
800 | } |
||
801 | fwrite($handle, "/*!40101 SET @saved_cs_client = @@character_set_client */;\n"); |
||
802 | fwrite($handle, "/*!40101 SET character_set_client = utf8 */;\n"); |
||
803 | $resqldrop = $db->query('SHOW CREATE TABLE ' . $table); |
||
804 | $row2 = $db->fetch_row($resqldrop); |
||
805 | if (empty($row2[1])) { |
||
806 | fwrite($handle, "\n-- WARNING: Show create table " . $table . " return empty string when it should not.\n"); |
||
807 | } else { |
||
808 | fwrite($handle, $row2[1] . ";\n"); |
||
809 | //fwrite($handle,"/*!40101 SET character_set_client = @saved_cs_client */;\n\n"); |
||
810 | |||
811 | // Dumping the data (locking the table and disabling the keys check while doing the process) |
||
812 | fwrite($handle, "\n--\n-- Dumping data for table `" . $table . "`\n--\n"); |
||
813 | if (!GETPOST("nobin_nolocks")) { |
||
814 | fwrite($handle, "LOCK TABLES `" . $table . "` WRITE;\n"); // Lock the table before inserting data (when the data will be imported back) |
||
815 | } |
||
816 | if (GETPOST("nobin_disable_fk")) { |
||
817 | fwrite($handle, "ALTER TABLE `" . $table . "` DISABLE KEYS;\n"); |
||
818 | } else { |
||
819 | fwrite($handle, "/*!40000 ALTER TABLE `" . $table . "` DISABLE KEYS */;\n"); |
||
820 | } |
||
821 | |||
822 | $sql = "SELECT * FROM " . $table; // Here SELECT * is allowed because we don't have definition of columns to take |
||
823 | $result = $db->query($sql); |
||
824 | while ($row = $db->fetch_row($result)) { |
||
825 | // For each row of data we print a line of INSERT |
||
826 | fwrite($handle, "INSERT " . $delayed . $ignore . "INTO " . $table . " VALUES ("); |
||
827 | $columns = count($row); |
||
828 | for ($j = 0; $j < $columns; $j++) { |
||
829 | // Processing each columns of the row to ensure that we correctly save the value (eg: add quotes for string - in fact we add quotes for everything, it's easier) |
||
830 | if ($row[$j] == null && !is_string($row[$j])) { |
||
831 | // IMPORTANT: if the field is NULL we set it NULL |
||
832 | $row[$j] = 'NULL'; |
||
833 | } elseif (is_string($row[$j]) && $row[$j] == '') { |
||
834 | // if it's an empty string, we set it as an empty string |
||
835 | $row[$j] = "''"; |
||
836 | } elseif (is_numeric($row[$j]) && !strcmp((string)$row[$j], (string)((float)$row[$j] + 0))) { // test if it's a numeric type and the numeric version ($nb+0) == string version (eg: if we have 01, it's probably not a number but rather a string, else it would not have any leading 0) |
||
837 | // if it's a number, we return it as-is |
||
838 | // $row[$j] = $row[$j]; |
||
839 | } else { // else for all other cases we escape the value and put quotes around |
||
840 | $row[$j] = addslashes($row[$j]); |
||
841 | $row[$j] = preg_replace("#\n#", "\\n", $row[$j]); |
||
842 | $row[$j] = "'" . $row[$j] . "'"; |
||
843 | } |
||
844 | } |
||
845 | fwrite($handle, implode(',', $row) . ");\n"); |
||
846 | } |
||
847 | if (GETPOST("nobin_disable_fk")) { |
||
848 | fwrite($handle, "ALTER TABLE `" . $table . "` ENABLE KEYS;\n"); // Enabling back the keys/index checking |
||
849 | } |
||
850 | if (!GETPOST("nobin_nolocks")) { |
||
851 | fwrite($handle, "UNLOCK TABLES;\n"); // Unlocking the table |
||
852 | } |
||
853 | fwrite($handle, "\n\n\n"); |
||
854 | } |
||
855 | } |
||
856 | |||
857 | /* Backup Procedure structure*/ |
||
858 | /* |
||
859 | $result = $db->query('SHOW PROCEDURE STATUS'); |
||
860 | if ($db->num_rows($result) > 0) |
||
861 | { |
||
862 | while ($row = $db->fetch_row($result)) { $procedures[] = $row[1]; } |
||
863 | foreach($procedures as $proc) |
||
864 | { |
||
865 | fwrite($handle,"DELIMITER $$\n\n"); |
||
866 | fwrite($handle,"DROP PROCEDURE IF EXISTS '$name'.'$proc'$$\n"); |
||
867 | $resqlcreateproc=$db->query("SHOW CREATE PROCEDURE '$proc'"); |
||
868 | $row2 = $db->fetch_row($resqlcreateproc); |
||
869 | fwrite($handle,"\n".$row2[2]."$$\n\n"); |
||
870 | fwrite($handle,"DELIMITER ;\n\n"); |
||
871 | } |
||
872 | } |
||
873 | */ |
||
874 | /* Backup Procedure structure*/ |
||
875 | |||
876 | // Write the footer (restore the previous database settings) |
||
877 | $sqlfooter = "\n\n"; |
||
878 | if (GETPOST("nobin_use_transaction")) { |
||
879 | $sqlfooter .= "COMMIT;\n"; |
||
880 | } |
||
881 | if (GETPOST("nobin_disable_fk")) { |
||
882 | $sqlfooter .= "SET FOREIGN_KEY_CHECKS=1;\n"; |
||
883 | } |
||
884 | $sqlfooter .= "\n\n-- Dump completed on " . date('Y-m-d G-i-s'); |
||
885 | fwrite($handle, $sqlfooter); |
||
886 | |||
887 | fclose($handle); |
||
888 | |||
889 | return 1; |
||
890 | } |
||
891 | |||
892 | /** |
||
893 | * Generate documentation of a Module |
||
894 | * |
||
895 | * @param string $module Module name |
||
896 | * @return int Return integer <0 if KO, >0 if OK |
||
897 | */ |
||
898 | public function generateDoc($module) |
||
899 | { |
||
900 | global $conf, $langs, $user, $mysoc; |
||
901 | global $dirins; |
||
902 | |||
903 | $error = 0; |
||
904 | |||
905 | $modulelowercase = strtolower($module); |
||
906 | $now = dol_now(); |
||
907 | |||
908 | // Dir for module |
||
909 | $dir = $dirins . '/' . $modulelowercase; |
||
910 | // Zip file to build |
||
911 | $FILENAMEDOC = ''; |
||
912 | |||
913 | // Load module |
||
914 | dol_include_once($modulelowercase . '/core/modules/mod' . $module . '.class.php'); |
||
915 | $class = 'mod' . $module; |
||
916 | |||
917 | if (class_exists($class)) { |
||
918 | try { |
||
919 | $moduleobj = new $class($this->db); |
||
920 | } catch (Exception $e) { |
||
921 | $error++; |
||
922 | dol_print_error(null, $e->getMessage()); |
||
923 | } |
||
924 | } else { |
||
925 | $error++; |
||
926 | $langs->load("errors"); |
||
927 | dol_print_error(null, $langs->trans("ErrorFailedToLoadModuleDescriptorForXXX", $module)); |
||
928 | exit; |
||
929 | } |
||
930 | |||
931 | $arrayversion = explode('.', $moduleobj->version, 3); |
||
932 | if (count($arrayversion)) { |
||
933 | $FILENAMEASCII = strtolower($module) . '.asciidoc'; |
||
934 | $FILENAMEDOC = strtolower($module) . '.html'; |
||
935 | $FILENAMEDOCPDF = strtolower($module) . '.pdf'; |
||
936 | |||
937 | $dirofmodule = dol_buildpath(strtolower($module), 0); |
||
938 | $dirofmoduledoc = dol_buildpath(strtolower($module), 0) . '/doc'; |
||
939 | $dirofmoduletmp = dol_buildpath(strtolower($module), 0) . '/doc/temp'; |
||
940 | $outputfiledoc = $dirofmoduledoc . '/' . $FILENAMEDOC; |
||
941 | if ($dirofmoduledoc) { |
||
942 | if (!dol_is_dir($dirofmoduledoc)) { |
||
943 | dol_mkdir($dirofmoduledoc); |
||
944 | } |
||
945 | if (!dol_is_dir($dirofmoduletmp)) { |
||
946 | dol_mkdir($dirofmoduletmp); |
||
947 | } |
||
948 | if (!is_writable($dirofmoduletmp)) { |
||
949 | $this->error = 'Dir ' . $dirofmoduletmp . ' does not exists or is not writable'; |
||
950 | return -1; |
||
951 | } |
||
952 | |||
953 | if (!getDolGlobalString('MODULEBUILDER_ASCIIDOCTOR') && !getDolGlobalString('MODULEBUILDER_ASCIIDOCTORPDF')) { |
||
954 | $this->error = 'Setup of module ModuleBuilder not complete'; |
||
955 | return -1; |
||
956 | } |
||
957 | |||
958 | // Copy some files into temp directory, so instruction include::ChangeLog.md[] will works inside the asciidoc file. |
||
959 | dol_copy($dirofmodule . '/README.md', $dirofmoduletmp . '/README.md', 0, 1); |
||
960 | dol_copy($dirofmodule . '/ChangeLog.md', $dirofmoduletmp . '/ChangeLog.md', 0, 1); |
||
961 | |||
962 | // Replace into README.md and ChangeLog.md (in case they are included into documentation with tag __README__ or __CHANGELOG__) |
||
963 | $arrayreplacement = array(); |
||
964 | $arrayreplacement['/^#\s.*/m'] = ''; // Remove first level of title into .md files |
||
965 | $arrayreplacement['/^#/m'] = '##'; // Add on # to increase level |
||
966 | |||
967 | dolReplaceInFile($dirofmoduletmp . '/README.md', $arrayreplacement, '', 0, 0, 1); |
||
968 | dolReplaceInFile($dirofmoduletmp . '/ChangeLog.md', $arrayreplacement, '', 0, 0, 1); |
||
969 | |||
970 | |||
971 | $destfile = $dirofmoduletmp . '/' . $FILENAMEASCII; |
||
972 | |||
973 | $fhandle = fopen($destfile, 'w+'); |
||
974 | if ($fhandle) { |
||
975 | $specs = dol_dir_list(dol_buildpath(strtolower($module) . '/doc', 0), 'files', 1, '(\.md|\.asciidoc)$', array('\/temp\/')); |
||
976 | |||
977 | $i = 0; |
||
978 | foreach ($specs as $spec) { |
||
979 | if (preg_match('/notindoc/', $spec['relativename'])) { |
||
980 | continue; // Discard file |
||
981 | } |
||
982 | if (preg_match('/example/', $spec['relativename'])) { |
||
983 | continue; // Discard file |
||
984 | } |
||
985 | if (preg_match('/disabled/', $spec['relativename'])) { |
||
986 | continue; // Discard file |
||
987 | } |
||
988 | |||
989 | $pathtofile = strtolower($module) . '/doc/' . $spec['relativename']; |
||
990 | $format = 'asciidoc'; |
||
991 | if (preg_match('/\.md$/i', $spec['name'])) { |
||
992 | $format = 'markdown'; |
||
993 | } |
||
994 | |||
995 | $filecursor = @file_get_contents($spec['fullname']); |
||
996 | if ($filecursor) { |
||
997 | fwrite($fhandle, ($i ? "\n<<<\n\n" : "") . $filecursor . "\n"); |
||
998 | } else { |
||
999 | $this->error = 'Failed to concat content of file ' . $spec['fullname']; |
||
1000 | return -1; |
||
1001 | } |
||
1002 | |||
1003 | $i++; |
||
1004 | } |
||
1005 | |||
1006 | fclose($fhandle); |
||
1007 | |||
1008 | $contentreadme = file_get_contents($dirofmoduletmp . '/README.md'); |
||
1009 | $contentchangelog = file_get_contents($dirofmoduletmp . '/ChangeLog.md'); |
||
1010 | |||
1011 | include DOL_DOCUMENT_ROOT . '/core/lib/parsemd.lib.php'; |
||
1012 | |||
1013 | //var_dump($phpfileval['fullname']); |
||
1014 | $arrayreplacement = array( |
||
1015 | 'mymodule' => strtolower($module), |
||
1016 | 'MyModule' => $module, |
||
1017 | 'MYMODULE' => strtoupper($module), |
||
1018 | 'My module' => $module, |
||
1019 | 'my module' => $module, |
||
1020 | 'Mon module' => $module, |
||
1021 | 'mon module' => $module, |
||
1022 | 'htdocs/modulebuilder/template' => strtolower($module), |
||
1023 | '__MYCOMPANY_NAME__' => $mysoc->name, |
||
1024 | '__KEYWORDS__' => $module, |
||
1025 | '__USER_FULLNAME__' => $user->getFullName($langs), |
||
1026 | '__USER_EMAIL__' => $user->email, |
||
1027 | '__YYYY-MM-DD__' => dol_print_date($now, 'dayrfc'), |
||
1028 | '---Put here your own copyright and developer email---' => dol_print_date($now, 'dayrfc') . ' ' . $user->getFullName($langs) . ($user->email ? ' <' . $user->email . '>' : ''), |
||
1029 | '__DATA_SPECIFICATION__' => 'Not yet available', |
||
1030 | '__README__' => dolMd2Asciidoc($contentreadme), |
||
1031 | '__CHANGELOG__' => dolMd2Asciidoc($contentchangelog), |
||
1032 | ); |
||
1033 | |||
1034 | // @phan-suppress-next-line PhanPluginSuspiciousParamPosition |
||
1035 | dolReplaceInFile($destfile, $arrayreplacement); |
||
1036 | } |
||
1037 | |||
1038 | // Launch doc generation |
||
1039 | $currentdir = getcwd(); |
||
1040 | chdir($dirofmodule); |
||
1041 | |||
1042 | require_once constant('DOL_DOCUMENT_ROOT') . '/core/class/utils.class.php'; |
||
1043 | $utils = new Utils($this->db); |
||
1044 | |||
1045 | // Build HTML doc |
||
1046 | $command = getDolGlobalString('MODULEBUILDER_ASCIIDOCTOR') . ' ' . $destfile . ' -n -o ' . $dirofmoduledoc . '/' . $FILENAMEDOC; |
||
1047 | $outfile = $dirofmoduletmp . '/out.tmp'; |
||
1048 | |||
1049 | $resarray = $utils->executeCLI($command, $outfile); |
||
1050 | if ($resarray['result'] != '0') { |
||
1051 | $this->error = $resarray['error'] . ' ' . $resarray['output']; |
||
1052 | $this->errors[] = $this->error; |
||
1053 | } |
||
1054 | $result = ($resarray['result'] == 0) ? 1 : 0; |
||
1055 | if ($result < 0 && empty($this->errors)) { |
||
1056 | $this->error = $langs->trans("ErrorFailToGenerateFile", $FILENAMEDOC); |
||
1057 | $this->errors[] = $this->error; |
||
1058 | } |
||
1059 | |||
1060 | // Build PDF doc |
||
1061 | $command = getDolGlobalString('MODULEBUILDER_ASCIIDOCTORPDF') . ' ' . $destfile . ' -n -o ' . $dirofmoduledoc . '/' . $FILENAMEDOCPDF; |
||
1062 | $outfile = $dirofmoduletmp . '/outpdf.tmp'; |
||
1063 | $resarray = $utils->executeCLI($command, $outfile); |
||
1064 | if ($resarray['result'] != '0') { |
||
1065 | $this->error = $resarray['error'] . ' ' . $resarray['output']; |
||
1066 | $this->errors[] = $this->error; |
||
1067 | } |
||
1068 | $result = ($resarray['result'] == 0) ? 1 : 0; |
||
1069 | if ($result < 0 && empty($this->errors)) { |
||
1070 | $this->error = $langs->trans("ErrorFailToGenerateFile", $FILENAMEDOCPDF); |
||
1071 | $this->errors[] = $this->error; |
||
1072 | } |
||
1073 | |||
1074 | chdir($currentdir); |
||
1075 | } else { |
||
1076 | $result = 0; |
||
1077 | } |
||
1078 | |||
1079 | if ($result > 0) { |
||
1080 | return 1; |
||
1081 | } else { |
||
1082 | $error++; |
||
1083 | } |
||
1084 | } else { |
||
1085 | $error++; |
||
1086 | $langs->load("errors"); |
||
1087 | $this->error = $langs->trans("ErrorCheckVersionIsDefined"); |
||
1088 | } |
||
1089 | |||
1090 | return -1; |
||
1091 | } |
||
1092 | |||
1093 | /** |
||
1094 | * Execute a CLI command. |
||
1095 | * |
||
1096 | * @param string $command Command line to execute. |
||
1097 | * Warning: The command line is sanitize by escapeshellcmd(), except if $noescapecommand set, so can't contains any redirection char '>'. Use param $redirectionfile if you need it. |
||
1098 | * @param string $outputfile A path for an output file (used only when method is 2). For example: $conf->admin->dir_temp.'/out.tmp'; |
||
1099 | * @param int $execmethod 0=Use default method (that is 1 by default), 1=Use the PHP 'exec', 2=Use the 'popen' method |
||
1100 | * @param string $redirectionfile If defined, a redirection of output to this file is added. |
||
1101 | * @param int $noescapecommand 1=Do not escape command. Warning: Using this parameter needs you already have sanitized the $command parameter. If not, it will lead to security vulnerability. |
||
1102 | * This parameter is provided for backward compatibility with external modules. Always use 0 in core. |
||
1103 | * @param string $redirectionfileerr If defined, a redirection of error is added to this file instead of to channel 1. |
||
1104 | * @return array array('result'=>...,'output'=>...,'error'=>...). result = 0 means OK. |
||
1105 | */ |
||
1106 | public function executeCLI($command, $outputfile, $execmethod = 0, $redirectionfile = null, $noescapecommand = 0, $redirectionfileerr = null) |
||
1107 | { |
||
1108 | global $conf, $langs; |
||
1109 | |||
1110 | $result = 0; |
||
1111 | $output = ''; |
||
1112 | $error = ''; |
||
1113 | |||
1114 | if (empty($noescapecommand)) { |
||
1115 | $command = escapeshellcmd($command); |
||
1116 | } |
||
1117 | |||
1118 | if ($redirectionfile) { |
||
1119 | $command .= " > " . dol_sanitizePathName($redirectionfile); |
||
1120 | } |
||
1121 | |||
1122 | if ($redirectionfileerr && ($redirectionfileerr != $redirectionfile)) { |
||
1123 | // If we ask a redirect of stderr on a given file not already used for stdout |
||
1124 | $command .= " 2> " . dol_sanitizePathName($redirectionfileerr); |
||
1125 | } else { |
||
1126 | $command .= " 2>&1"; |
||
1127 | } |
||
1128 | |||
1129 | if (getDolGlobalString('MAIN_EXEC_USE_POPEN')) { |
||
1130 | $execmethod = getDolGlobalString('MAIN_EXEC_USE_POPEN'); |
||
1131 | } |
||
1132 | if (empty($execmethod)) { |
||
1133 | $execmethod = 1; |
||
1134 | } |
||
1135 | //$execmethod=1; |
||
1136 | dol_syslog("Utils::executeCLI execmethod=" . $execmethod . " command=" . $command, LOG_DEBUG); |
||
1137 | $output_arr = array(); |
||
1138 | |||
1139 | if ($execmethod == 1) { |
||
1140 | $retval = null; |
||
1141 | exec($command, $output_arr, $retval); |
||
1142 | $result = $retval; |
||
1143 | if ($retval != 0) { |
||
1144 | $langs->load("errors"); |
||
1145 | dol_syslog("Utils::executeCLI retval after exec=" . $retval, LOG_ERR); |
||
1146 | $error = 'Error ' . $retval; |
||
1147 | } |
||
1148 | } |
||
1149 | if ($execmethod == 2) { // With this method, there is no way to get the return code, only output |
||
1150 | $handle = fopen($outputfile, 'w+b'); |
||
1151 | if ($handle) { |
||
1152 | dol_syslog("Utils::executeCLI run command " . $command); |
||
1153 | $handlein = popen($command, 'r'); |
||
1154 | while (!feof($handlein)) { |
||
1155 | $read = fgets($handlein); |
||
1156 | fwrite($handle, $read); |
||
1157 | $output_arr[] = $read; |
||
1158 | } |
||
1159 | pclose($handlein); |
||
1160 | fclose($handle); |
||
1161 | } |
||
1162 | dolChmod($outputfile); |
||
1163 | } |
||
1164 | |||
1165 | // Update with result |
||
1166 | if (is_array($output_arr) && count($output_arr) > 0) { |
||
1167 | foreach ($output_arr as $val) { |
||
1168 | $output .= $val . ($execmethod == 2 ? '' : "\n"); |
||
1169 | } |
||
1170 | } |
||
1171 | |||
1172 | dol_syslog("Utils::executeCLI result=" . $result . " output=" . $output . " error=" . $error, LOG_DEBUG); |
||
1173 | |||
1174 | return array('result' => $result, 'output' => $output, 'error' => $error); |
||
1175 | } |
||
1176 | |||
1177 | /** |
||
1178 | * This saves syslog files and compresses older ones. |
||
1179 | * Nb of archive to keep is defined into $conf->global->SYSLOG_FILE_SAVES |
||
1180 | * CAN BE A CRON TASK |
||
1181 | * |
||
1182 | * @return int 0 if OK, < 0 if KO |
||
1183 | */ |
||
1184 | public function compressSyslogs() |
||
1185 | { |
||
1186 | global $conf; |
||
1187 | |||
1188 | if (empty($conf->loghandlers['mod_syslog_file'])) { // File Syslog disabled |
||
1189 | return 0; |
||
1190 | } |
||
1191 | |||
1192 | if (!function_exists('gzopen')) { |
||
1193 | $this->error = 'Support for gzopen not available in this PHP'; |
||
1194 | return -1; |
||
1195 | } |
||
1196 | |||
1197 | require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php'; |
||
1198 | |||
1199 | $nbSaves = intval(getDolGlobalString('SYSLOG_FILE_SAVES', 10)); |
||
1200 | |||
1201 | if (!getDolGlobalString('SYSLOG_FILE')) { |
||
1202 | $mainlogdir = DOL_DATA_ROOT; |
||
1203 | $mainlog = 'alixar.log'; |
||
1204 | } else { |
||
1205 | $mainlogfull = str_replace('DOL_DATA_ROOT', DOL_DATA_ROOT, $conf->global->SYSLOG_FILE); |
||
1206 | $mainlogdir = dirname($mainlogfull); |
||
1207 | $mainlog = basename($mainlogfull); |
||
1208 | } |
||
1209 | |||
1210 | $tabfiles = dol_dir_list(DOL_DATA_ROOT, 'files', 0, '^(dolibarr_.+|odt2pdf)\.log$'); // Also handle other log files like dolibarr_install.log |
||
1211 | $tabfiles[] = array('name' => $mainlog, 'path' => $mainlogdir); |
||
1212 | |||
1213 | foreach ($tabfiles as $file) { |
||
1214 | $logname = $file['name']; |
||
1215 | $logpath = $file['path']; |
||
1216 | |||
1217 | if (dol_is_file($logpath . '/' . $logname) && dol_filesize($logpath . '/' . $logname) > 0) { // If log file exists and is not empty |
||
1218 | // Handle already compressed files to rename them and add +1 |
||
1219 | |||
1220 | $filter = '^' . preg_quote($logname, '/') . '\.([0-9]+)\.gz$'; |
||
1221 | |||
1222 | $gzfilestmp = dol_dir_list($logpath, 'files', 0, $filter); |
||
1223 | $gzfiles = array(); |
||
1224 | |||
1225 | foreach ($gzfilestmp as $gzfile) { |
||
1226 | $tabmatches = array(); |
||
1227 | preg_match('/' . $filter . '/i', $gzfile['name'], $tabmatches); |
||
1228 | |||
1229 | $numsave = intval($tabmatches[1]); |
||
1230 | |||
1231 | $gzfiles[$numsave] = $gzfile; |
||
1232 | } |
||
1233 | |||
1234 | krsort($gzfiles, SORT_NUMERIC); |
||
1235 | |||
1236 | foreach ($gzfiles as $numsave => $dummy) { |
||
1237 | if (dol_is_file($logpath . '/' . $logname . '.' . ($numsave + 1) . '.gz')) { |
||
1238 | return -2; |
||
1239 | } |
||
1240 | |||
1241 | if ($numsave >= $nbSaves) { |
||
1242 | dol_delete_file($logpath . '/' . $logname . '.' . $numsave . '.gz', 0, 0, 0, null, false, 0); |
||
1243 | } else { |
||
1244 | dol_move($logpath . '/' . $logname . '.' . $numsave . '.gz', $logpath . '/' . $logname . '.' . ($numsave + 1) . '.gz', 0, 1, 0, 0); |
||
1245 | } |
||
1246 | } |
||
1247 | |||
1248 | // Compress current file and recreate it |
||
1249 | |||
1250 | if ($nbSaves > 0) { // If $nbSaves is 1, we keep 1 archive .gz file, If 2, we keep 2 .gz files |
||
1251 | $gzfilehandle = gzopen($logpath . '/' . $logname . '.1.gz', 'wb9'); |
||
1252 | |||
1253 | if (empty($gzfilehandle)) { |
||
1254 | $this->error = 'Failted to open file ' . $logpath . '/' . $logname . '.1.gz'; |
||
1255 | return -3; |
||
1256 | } |
||
1257 | |||
1258 | $sourcehandle = fopen($logpath . '/' . $logname, 'r'); |
||
1259 | |||
1260 | if (empty($sourcehandle)) { |
||
1261 | $this->error = 'Failed to open file ' . $logpath . '/' . $logname; |
||
1262 | return -4; |
||
1263 | } |
||
1264 | |||
1265 | while (!feof($sourcehandle)) { |
||
1266 | gzwrite($gzfilehandle, fread($sourcehandle, 512 * 1024)); // Read 512 kB at a time |
||
1267 | } |
||
1268 | |||
1269 | fclose($sourcehandle); |
||
1270 | gzclose($gzfilehandle); |
||
1271 | |||
1272 | dolChmod($logpath . '/' . $logname . '.1.gz'); |
||
1273 | } |
||
1274 | |||
1275 | dol_delete_file($logpath . '/' . $logname, 0, 0, 0, null, false, 0); |
||
1276 | |||
1277 | // Create empty file |
||
1278 | $newlog = fopen($logpath . '/' . $logname, 'a+'); |
||
1279 | fclose($newlog); |
||
1280 | |||
1281 | //var_dump($logpath.'/'.$logname." - ".octdec(empty($conf->global->MAIN_UMASK)?'0664':$conf->global->MAIN_UMASK)); |
||
1282 | dolChmod($logpath . '/' . $logname); |
||
1283 | } |
||
1284 | } |
||
1285 | |||
1286 | $this->output = 'Archive log files (keeping last SYSLOG_FILE_SAVES=' . $nbSaves . ' files) done.'; |
||
1287 | return 0; |
||
1288 | } |
||
1289 | |||
1290 | /** |
||
1291 | * Make a send last backup of database or fil in param |
||
1292 | * CAN BE A CRON TASK |
||
1293 | * |
||
1294 | * @param string $sendto Recipients emails |
||
1295 | * @param string $from Sender email |
||
1296 | * @param string $subject Topic/Subject of mail |
||
1297 | * @param string $message Message |
||
1298 | * @param string $filename List of files to attach (full path of filename on file system) |
||
1299 | * @param string $filter Filter file send |
||
1300 | * @param int $sizelimit Limit size to send file |
||
1301 | * @return int 0 if OK, < 0 if KO (this function is used also by cron so only 0 is OK) |
||
1302 | */ |
||
1303 | public function sendBackup($sendto = '', $from = '', $subject = '', $message = '', $filename = '', $filter = '', $sizelimit = 100000000) |
||
1402 | } |
||
1403 | } |
||
1404 | |||
1405 | /** |
||
1406 | * Clean unfinished cronjob in processing when pid is no longer present in the system |
||
1407 | * CAN BE A CRON TASK |
||
1408 | * |
||
1409 | * @return int 0 if OK, < 0 if KO (this function is used also by cron so only 0 is OK) |
||
1410 | * @throws Exception |
||
1411 | */ |
||
1412 | public function cleanUnfinishedCronjob() |
||
1472 | } |
||
1473 | } |
||
1474 |
If you suppress an error, we recommend checking for the error condition explicitly: