utils.class.php 50 KB

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