#!/usr/bin/env php * Copyright (C) 2024 Frédéric France * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /** * \file dev/build/generate_filelist_xml.php * \ingroup dev * \brief This script create a xml checksum file */ if (!defined('NOREQUIREDB')) { define('NOREQUIREDB', '1'); // Do not create database handler $db } define('NOREQUIREVIRTUALURL', 1); $sapi_type = php_sapi_name(); $script_file = basename(__FILE__); $path = dirname(__FILE__).'/'; // Test if batch mode if (substr($sapi_type, 0, 3) == 'cgi') { echo "Error: You are using PHP for CGI. To execute ".$script_file." from command line, you must use PHP for CLI mode.\n"; exit(1); } define('DOL_DOCUMENT_ROOT', dirname(dirname($path)).'/htdocs'); require_once $path."../../htdocs/master.inc.php"; require_once DOL_DOCUMENT_ROOT."/core/lib/files.lib.php"; /* * Main */ $includecustom = 0; $includeconstants = array(); $buildzip = 0; $release = ''; $checklock = ''; print '***** '.$script_file.' *****'."\n"; if (empty($argv[1])) { print "Usage: ".$script_file." release=auto[-mybuild]|x.y.z[-mybuild] [includecustom=1] [includeconstant=CC:MY_CONF_NAME:value] [buildzip=1]\n"; print "Usage: ".$script_file." checklock=auto[-mybuild]|x.y.z[-mybuild] unalterable_files\n"; print "\n"; print "Example: ".$script_file." release=6.0.0 includecustom=1 includeconstant=ES:CONST_XX_IS_ON includeconstant=all:MAILING_NO_USING_PHPMAIL:1\n"; print "\n"; print "Generate the file filelist-x.y.z[-mybuild].xml with signature of files. "; print "The file always includes the 3 sections:\n"; print "- dolibarr_htdocs_dir\n"; print "- dolibarr_scripts_dir\n"; print "- dolibarr_unalterable_files (only files inside the scope of the unalterable module)\n"; print "and if a specific setup/parameter need to be included into the signature for check:\n"; print "- dolibarr_constants\n"; print "\n"; print "If used with parameter 'check_unalterable_files', it will validate that the signature generated is the samethan the one found into lockedfiles.txt"; print "\n"; exit(1); } $i = 0; $result = array(); while ($i < $argc) { if (!empty($argv[$i])) { parse_str($argv[$i], $result); // set all params $release, $includecustom, $includeconstant, $buildzip ... } if (!empty($result["release"])) { $release = $result["release"]; } if (!empty($result["checklock"])) { $checklock = $result["checklock"]; } if (!empty($result["includecustom"])) { $includecustom = $result["includecustom"]; } if (preg_match('/unalterable_files/', strval($argv[$i]))) { $checksource = 'unalterable_files'; } if (preg_match('/includeconstant=/', strval($argv[$i]))) { $tmp = explode(':', $result['includeconstant'], 3); // $includeconstant has been set with previous parse_str() if (count($tmp) != 3) { print "Error: Bad parameter includeconstant=".$result['includeconstant'] ."\n"; exit(1); } $includeconstants[$tmp[0]][$tmp[1]] = $tmp[2]; } if (!empty($result["buildzip"])) { $buildzip = 1; } $i++; } if (empty($release) && empty($checklock)) { print "Error: Missing release or checklock parameter\n"; print "Usage: ".$script_file." release=auto[-mybuild]|x.y.z[-mybuild] [includecustom=1] [includeconstant=CC:MY_CONF_NAME:value]\n"; print "Usage: ".$script_file." checklock=auto[-mybuild]|x.y.z[-mybuild] unalterable_files\n"; exit(2); } $savrelease = $release; // If release is auto, we take current version $tmpver = explode('-', $release, 2); if ($tmpver[0] == 'auto') { $release = DOL_VERSION; if (!empty($tmpver[1]) && $tmpver[0] == 'auto') { $release .= '-'.$tmpver[1]; } } // If release is auto, we take current version $tmpver = explode('-', $checklock, 2); if ($tmpver[0] == 'auto') { $checklock = DOL_VERSION; if (!empty($tmpver[1]) && $tmpver[0] == 'auto') { $checklock .= '-'.$tmpver[1]; } } $checklockmajorversion = ''; if ($checklock) { $checklockmajorversion = preg_replace('/-.*$/', '', $checklock); $checklockmajorversion = preg_replace('/\..*/', '', $checklockmajorversion); $checklockmajorversion .= '.0.0'; } if ($release) { if (empty($includecustom)) { $tmpverbis = explode('-', $release, 2); if (empty($tmpverbis[1])) { if (DOL_VERSION != $tmpverbis[0] && $savrelease != 'auto') { print 'Error: When parameter "includecustom" is not set and there is no suffix in release parameter, version declared into filefunc.in.php ('.DOL_VERSION.') must be exactly the same value than "release" parameter ('.$tmpverbis[0].')'."\n"; print "Usage: ".$script_file." release=auto[-mybuild]|x.y.z[-mybuild] [includecustom=1] [includeconstant=CC:MY_CONF_NAME:value]\n"; print "\n"; exit(3); } } else { $tmpverter = explode('-', DOL_VERSION, 2); if ($tmpverter[0] != $tmpverbis[0]) { print 'Error: When parameter "includecustom" is not set, version declared into filefunc.in.php ('.DOL_VERSION.') must have value without prefix ('.$tmpverter[0].') that is exact same value than "release" parameter ('.$tmpverbis[0].')'."\n"; print "Usage: ".$script_file." release=auto[-mybuild]|x.y.z[-mybuild] [includecustom=1] [includeconstant=CC:MY_CONF_NAME:value]\n"; print "\n"; exit(4); } } } else { if (!preg_match('/'.preg_quote(DOL_VERSION, '/').'-/', $release)) { print 'Error: When parameter "includecustom" is set, version declared into filefunc.inc.php ('.DOL_VERSION.') must be used with a suffix into "release" parameter (ex: '.DOL_VERSION.'-mydistrib).'."\n"; print "Usage: ".$script_file." release=auto[-mybuild]|x.y.z[-mybuild] [includecustom=1] [includeconstant=CC:MY_CONF_NAME:value]\n"; print "\n"; exit(5); } } } if ($checklock && empty($checksource)) { print 'Error: When action "checklock" is set, second parameter must be the scope family to check, for example "unalterable_files"'."\n"; print "Usage: ".$script_file." checklock=auto[-mybuild]|x.y.z[-mybuild] unalterable_files\n"; print "\n"; exit(6); } if ($release) { print "Working on files into : ".DOL_DOCUMENT_ROOT."\n"; print "Version of target release : ".$release."\n"; print "Include custom dir in signature : ".(empty($includecustom) ? 'no' : 'yes')."\n"; print "Include constants in signature : ".(empty($includeconstants) ? 'none' : ''); foreach ($includeconstants as $countrycode => $tmp) { foreach ($tmp as $constname => $constvalue) { print $constname.'='.$constvalue." "; } } print "\n"; } if ($checklock) { print "Working on files into : ".DOL_DOCUMENT_ROOT."\n"; print "Version to check in lockedfiles.txt : ".$checklockmajorversion."\n"; print "Check source : ".$checksource."\n"; } if ($release) { //$outputfile=dirname(__FILE__).'/../htdocs/install/filelist-'.$release.'.xml'; $outputdir = dirname(dirname(dirname(__FILE__))).'/htdocs/install'; print 'Delete current files '.$outputdir.'/filelist*.xml*'."\n"; dol_delete_file($outputdir.'/filelist*.xml*', 0, 1, 1); } $needtoclose = 0; // Build the XML file if ($release) { $checksumconcat = array(); $outputfile = $outputdir.'/filelist-'.$release.'.xml'; $fp = fopen($outputfile, 'w'); if (empty($fp)) { print 'Failed to open file '.$outputfile."\n"; exit(7); } $gitcommit = 'seetag'; $branchname = preg_replace('/^(\d+\.\d+)\..*$/', '\1', $release); // Keep only x.y into x.y.z $fileforgit = dirname(dirname(dirname(__FILE__))).'/.git/refs/heads/'.$branchname; print "Try to get last commit ID from file ".$fileforgit."\n"; $fileforgitcontent = ''; if (file_exists($fileforgit)) { $fileforgitcontent = file_get_contents($fileforgit); } if (empty($fileforgitcontent)) { print "Failed to get the last commit ID (are you on the branch for the release branch name ".$branchname." ?). We will use an empty value for gitcommit.\n"; } $gitcommit = trim($fileforgitcontent); fputs($fp, ''."\n"); fputs($fp, ''."\n"); foreach ($includeconstants as $countrycode => $tmp) { fputs($fp, ''."\n"); foreach ($tmp as $constname => $constvalue) { $valueforchecksum = (empty($constvalue) ? '0' : $constvalue); $checksumconcat[] = $valueforchecksum; fputs($fp, ' '.$valueforchecksum.''."\n"); } fputs($fp, ''."\n\n"); } fputs($fp, ''."\n"); // Define qualified files (must be same than into generate_filelist_xml.php and in api_setup.class.php) $regextoinclude = '\.(php|php3|php4|php5|phtml|phps|phar|inc|css|scss|html|xml|js|json|tpl|jpg|jpeg|png|gif|ico|sql|lang|txt|yml|bak|md|mp3|mp4|wav|mkv|z|gz|zip|rar|tar|less|svg|eot|woff|woff2|ttf|manifest)$'; $regextoexclude = '('.($includecustom ? '' : 'custom|').'documents|escpos-php\/doc|escpos-php\/example|escpos-php\/test|conf|install|dejavu-fonts-ttf-.*|public\/test|sabre\/sabre\/.*\/tests|Shared\/PCLZip|nusoap\/lib\/Mail|php\/test|geoip\/sample.*\.php|ckeditor\/samples|ckeditor\/adapters)$'; // Exclude dirs $files = dol_dir_list(DOL_DOCUMENT_ROOT, 'files', 1, $regextoinclude, $regextoexclude, 'fullname'); $dir = ''; foreach ($files as $filetmp) { $file = $filetmp['fullname']; //$newdir = str_replace(dirname(__FILE__).'/../htdocs', '', dirname($file)); $newdir = str_replace(DOL_DOCUMENT_ROOT, '', dirname($file)); if ($newdir != $dir) { if ($needtoclose) { fputs($fp, ' '."\n"); $needtoclose = 0; } fputs($fp, ' '."\n"); $dir = $newdir; $needtoclose = 1; } if (filetype($file) == "file") { $md5 = md5_file($file); $checksumconcat[] = $md5; fputs($fp, ' '.$md5.''."\n"); } } if ($needtoclose) { fputs($fp, ' '."\n"); $needtoclose = 0; } fputs($fp, ''."\n"); asort($checksumconcat); // Sort list of checksum $md5htdocsdir = md5(join(',', $checksumconcat)); fputs($fp, ''."\n"); fputs($fp, $md5htdocsdir."\n"); fputs($fp, ''."\n\n"); // Add the checksum for the part in scripts $checksumconcat = array(); fputs($fp, ''."\n"); $regextoinclude = '\.(php|css|html|js|json|tpl|jpg|png|gif|sql|lang)$'; $regextoexclude = '(custom|documents|conf|install)$'; // Exclude dirs $files = dol_dir_list(dirname(__FILE__).'/../../scripts/', 'files', 1, $regextoinclude, $regextoexclude, 'fullname'); $dir = ''; foreach ($files as $filetmp) { $file = $filetmp['fullname']; $newdir = str_replace(DOL_DOCUMENT_ROOT, '', dirname($file)); $newdir = str_replace(dirname(__FILE__).'/../../scripts', '', dirname($file)); if ($newdir != $dir) { if ($needtoclose) { fputs($fp, ' '."\n"); $needtoclose = 0; } fputs($fp, ' '."\n"); $dir = $newdir; $needtoclose = 1; } if (filetype($file) == "file") { $md5 = md5_file($file); $checksumconcat[] = $md5; fputs($fp, ' '.$md5.''."\n"); } } if ($needtoclose) { fputs($fp, ' '."\n"); $needtoclose = 0; } fputs($fp, ''."\n"); asort($checksumconcat); // Sort list of checksum $md5scriptsdir = md5(join(',', $checksumconcat)); fputs($fp, ''."\n"); fputs($fp, $md5scriptsdir."\n"); fputs($fp, ''."\n\n"); } // Add the checksum for the files into the scope of the unalterable system (record, read, export) $checksumconcat = array(); if ($release) { fputs($fp, ''."\n"); } // Array of dir/files to include in the section $arrayofunalterablefiles = array( array('dir' => dirname(__FILE__).'/../../htdocs/', 'file' => 'version.inc.php'), array('dir' => dirname(__FILE__).'/../../htdocs/blockedlog', 'file' => 'all', 'regextoinclude' => '(\.php|\.sql)$', 'regextoexclude' => ''), array('dir' => dirname(__FILE__).'/../../htdocs/install/mysql/tables', 'file' => 'all', 'regextoinclude' => 'llx_blockedlog.*(\.php|\.sql)$', 'regextoexclude' => ''), array('dir' => dirname(__FILE__).'/../../htdocs/core/triggers', 'file' => 'interface_50_modBlockedlog_ActionsBlockedLog.class.php'), array('dir' => dirname(__FILE__).'/../../htdocs/core/class', 'file' => 'all', 'regextoinclude' => '(interfaces.class.php|commontrigger.class.php)$', 'regextoexclude' => ''), array('dir' => dirname(__FILE__).'/../../htdocs/takepos', 'file' => 'receipt.php') ); foreach ($arrayofunalterablefiles as $entry) { if ($entry['file'] == 'all') { $regextoinclude = $entry['regextoinclude']; $regextoexclude = $entry['regextoexclude']; $files = dol_dir_list($entry['dir'], 'files', 1, $regextoinclude, $regextoexclude, 'fullname'); $dir = ''; foreach ($files as $filetmp) { $file = $filetmp['fullname']; $newdir = str_replace(DOL_DOCUMENT_ROOT, '', dirname($file)); $newdir = str_replace(dirname(__FILE__).'/../../htdocs', '', dirname($file)); if ($newdir != $dir) { if ($needtoclose) { if ($release) { fputs($fp, ' '."\n"); } $needtoclose = 0; } if ($release) { fputs($fp, ' '."\n"); } $dir = $newdir; $needtoclose = 1; } if (filetype($file) == "file") { $md5 = md5_file($file); $checksumconcat[] = $md5; if ($release) { fputs($fp, ' '.$md5.''."\n"); } } } if ($needtoclose) { if ($release) { fputs($fp, ' '."\n"); } $needtoclose = 0; } } else { $file = $entry['dir'].'/'.$entry['file']; $dir = ''; $newdir = str_replace(DOL_DOCUMENT_ROOT, '', dirname($file)); $newdir = str_replace(dirname(__FILE__).'/../../htdocs', '', dirname($file)); if (!file_exists($file)) { print "Error file ".$file." does not exists."; exit(1); } if ($newdir != $dir) { if ($needtoclose) { if ($release) { fputs($fp, ' '."\n"); } $needtoclose = 0; } if ($release) { fputs($fp, ' '."\n"); } $dir = $newdir; $needtoclose = 1; } if (filetype($file) == "file") { $md5 = md5_file($file); $checksumconcat[] = $md5; if ($release) { fputs($fp, ' '.$md5.''."\n"); } } if ($needtoclose) { if ($release) { fputs($fp, ' '."\n"); } $needtoclose = 0; } } } asort($checksumconcat); // Sort list of checksum $md5unalterable_files = md5(join(',', $checksumconcat)); if ($release) { fputs($fp, ''."\n"); fputs($fp, ''."\n"); fputs($fp, $md5unalterable_files."\n"); fputs($fp, ''."\n\n"); // End of file fputs($fp, ''."\n"); fclose($fp); } print "\n"; if ($release) { if (empty($buildzip)) { print "File ".$outputfile." generated.\n"; print "Signature for htdocs files: ".$md5htdocsdir."\n"; print "Signature for scripts files: ".$md5scriptsdir."\n"; print "Signature for the ".count($checksumconcat)." unalterable files: ".$md5unalterable_files."\n"; } else { if ($buildzip == '1' || $buildzip == 'zip') { $result = dol_compress_file($outputfile, $outputfile.'.zip', 'zip'); if ($result > 0) { dol_delete_file($outputfile); print "File ".$outputfile.".zip generated.\n"; } } elseif ($buildzip == '2' || $buildzip == 'gz') { $result = dol_compress_file($outputfile, $outputfile.'.gz', 'gz'); if ($result > 0) { dol_delete_file($outputfile); print "File ".$outputfile.".gz generated.\n"; } } } } if ($checklock) { print "Signature for unalterable files: ".$md5unalterable_files."\n"; $lockedfile = DOL_DOCUMENT_ROOT.'/../dev/lockedfiles.txt'; $checksuminlockedfile = ''; if (!file_exists($lockedfile)) { print "Can't find the file ".$lockedfile.". No checksum to check\n"; } else { // Now we check the content of lockedfiles.txt $arraylocked = file($lockedfile); foreach ($arraylocked as $line) { $tmparray = preg_split("/\s+/", $line, 3); if ($tmparray[0] == $checklockmajorversion) { $checksuminlockedfile = $tmparray[2]; } } if (empty($checksuminlockedfile)) { print "The major version ".$checklockmajorversion." is not locked on the scope ".$checksource." (file found but no matching entry found into dev/lockedfiles.txt).\n"; } elseif ($checksuminlockedfile != $md5unalterable_files) { print "The major version ".$checklockmajorversion." is locked on scope '".$checksource."' to checksum ".$checksuminlockedfile."\n"; if ($checklockmajorversion != $checksource) { print "The checksum now differs from the locked one, so we return an error.\n"; print "\n"; exit(10); } } } } print "\n"; exit(0);