#!/usr/bin/env php * Copyright (C) 2024 MDW * 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/tools/apstats.php * \brief Script to report Advanced Statistics and Status on a PHP project */ $sapi_type = php_sapi_name(); $script_file = basename(__FILE__); $path = dirname(__FILE__) . '/'; // Test si mode batch $sapi_type = php_sapi_name(); 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(); } error_reporting(E_ALL & ~E_DEPRECATED); define('PRODUCT', "apstats"); define('VERSION', "1.0"); // Include Dolibarr environment if (!is_readable("{$path}../../htdocs/config/config.php")) { define("DOL_DOCUMENT_ROOT", __DIR__."/../../htdocs"); } else { require_once $path.'../../htdocs/master.inc.php'; } require_once $path.'../../htdocs/core/lib/files.lib.php'; require_once $path.'../../htdocs/core/lib/functions.lib.php'; require_once $path.'../../htdocs/core/lib/geturl.lib.php'; print '***** '.constant('PRODUCT').' - '.constant('VERSION').' *****'."\n"; if (empty($argv[1])) { print 'You must run this tool at the root of the project.'."\n"; print 'Usage: '.constant('PRODUCT').'.php pathto/outputfile.html [--dir-scc=pathtoscc|disabled] [--dir-phpstan=pathtophpstan|disabled] [--dir-phan=path/to/phan|disabled]'."\n"; print 'Example: '.constant('PRODUCT').'.php documents/apstats/index.html --dir-scc=/snap/bin --dir-phpstan=~/git/phpstan/htdocs/includes/bin --dir-phan=~/vendor/bin/phan --url-site=https://www.dolibarr.org'; exit(0); } $outputpath = $argv[1]; $outputdir = dirname($outputpath); $outputfile = basename($outputpath); $outputfilerss = preg_replace('/\.\w+$/i', '', $outputfile).'-security.rss'; if (!is_dir($outputdir)) { print 'Error: dir '.$outputdir.' does not exists or is not writable'."\n"; exit(1); } $dirscc = ''; $dirphpstan = ''; $dir_phan = ''; $datatable_script = ''; $url_root = ''; $url_site = ''; $url_flux = ''; $project = ''; $i = 0; while ($i < $argc) { $reg = array(); if (preg_match('/^--dir-scc=(.*)$/', $argv[$i], $reg)) { $dirscc = $reg[1]; } elseif (preg_match('/^--dir-phpstan=(.*)$/', $argv[$i], $reg)) { $dirphpstan = $reg[1]; } elseif (preg_match('/^--dir-phan=(.*)$/', $argv[$i], $reg)) { $dir_phan = $reg[1]; } elseif (preg_match('/^--url-root=(.*)$/', $argv[$i], $reg)) { $url_root = $reg[1]; } elseif (preg_match('/^--url-site=(.*)$/', $argv[$i], $reg)) { $url_site = $reg[1]; } elseif (preg_match('/^--project-name=(.*)$/', $argv[$i], $reg)) { $project = $reg[1]; } $i++; } // PHPSTAN setup $PHPSTANLEVEL = 9; // PHAN setup. Configuration is required, otherwise phan is disabled. $PHAN_CONFIG = "{$path}phan/config_extended.php"; $PHAN_BASELINE = "{$path}phan/baseline_extended.txt"; // BASELINE is ignored if it does not exist $PHAN_MIN_PHP = "7.0"; $PHAN_MEMORY_OPT = "--memory-limit 5G"; if (!is_readable($PHAN_CONFIG)) { print "Skipping phan - configuration not found\n"; // Disable phan while not integrated yet $dir_phan = 'disabled'; } // Start getting data $timestart = time(); // Retrieve the .git information $urlgit = 'https://github.com/Dolibarr/dolibarr/blob/develop/'; // Count lines of code of application $output_arrproj = array(); $output_arrdep = array(); if ($dirscc != 'disabled') { $commandcheck = ($dirscc ? $dirscc.'/' : '').'scc . --exclude-dir=htdocs/includes,htdocs/custom,htdocs/theme/common/fontawesome-5,htdocs/theme/common/octicons'; print 'Execute SCC to count lines of code in project: '.$commandcheck."\n"; $resexecproj = 0; exec($commandcheck, $output_arrproj, $resexecproj); // Count lines of code of dependencies $commandcheck = ($dirscc ? $dirscc.'/' : '').'scc htdocs/includes htdocs/theme/common/fontawesome-5 htdocs/theme/common/octicons'; print 'Execute SCC to count lines of code in dependencies: '.$commandcheck."\n"; $resexecdep = 0; exec($commandcheck, $output_arrdep, $resexecdep); } // Get technical debt with PHPStan $output_arrtd = array(); if ($dirphpstan != 'disabled') { $commandcheck = ($dirphpstan ? $dirphpstan.'/' : '').'phpstan --version'; print 'Execute PHPStan to get the version: '.$commandcheck."\n"; $resexectd = 0; exec($commandcheck, $output_arrtd, $resexectd); } $phpstanversion = $output_arrtd[0]; $output_arrtd = array(); if ($dirphpstan != 'disabled') { $commandcheck = ($dirphpstan ? $dirphpstan.'/' : '').'phpstan --level='.$PHPSTANLEVEL.' -v analyze -a build/phpstan/bootstrap.php --memory-limit 8G --error-format=github -c ~/preview.dolibarr.org/dolibarr/dev/tools/phpstan/phpstan_v1_apstats.neon'; print 'Execute PHPStan to get the technical debt: '.$commandcheck."\n"; $resexectd = 0; exec($commandcheck, $output_arrtd, $resexectd); } // Get technical debt with Phan $output_phan_json = array(); $res_exec_phan = 0; if ($dir_phan != 'disabled') { if (is_readable($PHAN_BASELINE)) { $PHAN_BASELINE_OPT = "-B '${PHAN_BASELINE}'"; } else { $PHAN_BASELINE_OPT = ''; } // Get technical debt (phan) $commandcheck = ($dir_phan ? $dir_phan.DIRECTORY_SEPARATOR : '') ."phan --output-mode json $PHAN_MEMORY_OPT -k '$PHAN_CONFIG' $PHAN_BASELINE_OPT --analyze-twice --minimum-target-php-version $PHAN_MIN_PHP"; print 'Execute Phan to get the technical debt: '.$commandcheck."\n"; exec($commandcheck, $output_phan_json, $res_exec_phan); } $arrayoflineofcode = array(); $arraycocomo = array(); $arrayofmetrics = array( 'proj' => array('Bytes' => 0, 'Files' => 0, 'Lines' => 0, 'Blanks' => 0, 'Comments' => 0, 'Code' => 0, 'Complexity' => 0), 'dep' => array('Bytes' => 0, 'Files' => 0, 'Lines' => 0, 'Blanks' => 0, 'Comments' => 0, 'Code' => 0, 'Complexity' => 0) ); // Analyse $output_arrproj foreach (array('proj', 'dep') as $source) { print 'Analyze SCC result for lines of code for '.$source."\n"; if ($source == 'proj') { $output_arr = &$output_arrproj; } elseif ($source == 'dep') { $output_arr = &$output_arrdep; } else { print 'Bad value for $source'; die(); } foreach ($output_arr as $line) { if (preg_match('/^(───|Language|Total)/', $line)) { continue; } //print $line."
\n"; if (preg_match('/^Estimated Cost.*\$(.*)/i', $line, $reg)) { $arraycocomo[$source]['currency'] = preg_replace('/[^\d\.]/', '', str_replace(array(',', ' '), array('', ''), $reg[1])); } if (preg_match('/^Estimated Schedule Effort.*\s([\d\s,]+)/i', $line, $reg)) { $arraycocomo[$source]['effort'] = str_replace(array(',', ' '), array('.', ''), $reg[1]); } if (preg_match('/^Estimated People.*\s([\d\s,]+)/i', $line, $reg)) { $arraycocomo[$source]['people'] = str_replace(array(',', ' '), array('.', ''), $reg[1]); } if (preg_match('/^Processed\s(\d+)\s/i', $line, $reg)) { $arrayofmetrics[$source]['Bytes'] = $reg[1]; } if (preg_match('/^(.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)$/', $line, $reg)) { $arrayoflineofcode[$source][$reg[1]]['Files'] = $reg[2]; $arrayoflineofcode[$source][$reg[1]]['Lines'] = $reg[3]; $arrayoflineofcode[$source][$reg[1]]['Blanks'] = $reg[4]; $arrayoflineofcode[$source][$reg[1]]['Comments'] = $reg[5]; $arrayoflineofcode[$source][$reg[1]]['Code'] = $reg[6]; $arrayoflineofcode[$source][$reg[1]]['Complexity'] = $reg[7]; } } if (!empty($arrayoflineofcode[$source])) { foreach ($arrayoflineofcode[$source] as $key => $val) { $arrayofmetrics[$source]['Files'] += $val['Files']; $arrayofmetrics[$source]['Lines'] += $val['Lines']; $arrayofmetrics[$source]['Blanks'] += $val['Blanks']; $arrayofmetrics[$source]['Comments'] += $val['Comments']; $arrayofmetrics[$source]['Code'] += $val['Code']; $arrayofmetrics[$source]['Complexity'] += $val['Complexity']; } } } // Search the max $arrayofmax = array('Lines' => 0); foreach (array('proj', 'dep') as $source) { if (!empty($arrayoflineofcode[$source])) { foreach ($arrayoflineofcode[$source] as $val) { $arrayofmax['Lines'] = max($arrayofmax['Lines'], $val['Lines']); } } } $nbofmonth = 2; $delay = (3600 * 24 * 30 * $nbofmonth); // Get stats on nb of commits $commandcheck = "git log --all --shortstat --no-renames --no-merges --use-mailmap --pretty=".escapeshellarg('format:%cI;%H;%aN;%aE;%ce;%s')." --since=".dol_print_date(dol_now() - $delay, '%Y-%m-%d'); // --since= --until=... print 'Execute git log to get list of commits: '.$commandcheck."\n"; $output_arrglpu = array(); $resexecglpu = 0; //exec($commandcheck, $output_arrglpu, $resexecglpu); // Get git information for security alerts $nbofmonth = 3; $delay = (3600 * 24 * 30 * $nbofmonth); $arrayofalerts = array(); $commandcheck = "git log --all --shortstat --no-renames --use-mailmap --pretty=".escapeshellarg('format:%cI;%H;%aN;%aE;%ce;%s')." --since=".escapeshellarg(dol_print_date(dol_now() - $delay, '%Y-%m-%d'))." | grep -i -E ".escapeshellarg("(#yogosha|CVE|Sec:|Sec )"); print 'Execute git log to get commits related to security: '.$commandcheck."\n"; $output_arrglpu = array(); $resexecglpu = 0; exec($commandcheck, $output_arrglpu, $resexecglpu); foreach ($output_arrglpu as $val) { // Parse the line to split interesting data $tmpval = cleanVal2($val); if (preg_match('/(#yogosha|CVE|Sec:|Sec\s)/i', $tmpval['title'])) { $alreadyfound = ''; $alreadyfoundcommitid = ''; foreach ($arrayofalerts as $val) { if ($val['issueidyogosha'] && $val['issueidyogosha'] == $tmpval['issueidyogosha']) { // Already in list $alreadyfound = 'yogosha'; $alreadyfoundcommitid = $val['commitid']; break; } if ($val['issueid'] && $val['issueid'] == $tmpval['issueid']) { // Already in list $alreadyfound = 'git'; $alreadyfoundcommitid = $val['commitid']; break; } if ($val['issueidcve'] && $val['issueidcve'] == $tmpval['issueidcve']) { // Already in list $alreadyfound = 'cve'; $alreadyfoundcommitid = $val['commitid']; break; } if ($val['title'] && $val['title'] == $tmpval['title']) { // Already in list $alreadyfound = 'title'; $alreadyfoundcommitid = $val['commitid']; break; } } //$alreadyfound=0; if (!$alreadyfound) { // Get branch names $commandgetbranch = "git branch -r --contains '".$tmpval['commitid']."'"; print 'Execute git branch to get the name of branches for the commit: '.$commandgetbranch."\n"; $output_arrgetbranch = array(); $resexecgetbranch = 0; exec($commandgetbranch, $output_arrgetbranch, $resexecgetbranch); foreach ($output_arrgetbranch as $valbranch) { if (empty($tmpval['branch'])) { $tmpval['branch'] = array(); } if (preg_match('/^\s*origin\/(develop|\d)/', $valbranch)) { $tmpval['branch'][] = preg_replace('/^\s*origin\//', '', $valbranch); } } $arrayofalerts[$tmpval['commitid']] = $tmpval; } else { if (empty($arrayofalerts[$alreadyfoundcommitid]['commitidbis'])) { $arrayofalerts[$alreadyfoundcommitid]['commitidbis'] = array(); } // Get branch names $commandgetbranch = "git branch -r --contains '".$tmpval['commitid']."'"; print 'Execute git branch to get the name of branches for the commit: '.$commandgetbranch."\n"; $output_arrgetbranch = array(); $resexecgetbranch = 0; exec($commandgetbranch, $output_arrgetbranch, $resexecgetbranch); foreach ($output_arrgetbranch as $valbranch) { if (empty($tmpval['branch'])) { $tmpval['branch'] = array(); } if (preg_match('/^\s*origin\/(develop|\d)/', $valbranch)) { $tmpval['branch'][] = preg_replace('/^\s*origin\//', '', $valbranch); } } /*var_dump($tmpval['commitid'].' '.$alreadyfoundcommitid); var_dump($arrayofalerts[$alreadyfoundcommitid]['branch']); var_dump($tmpval);*/ $arrayofalerts[$alreadyfoundcommitid]['branch'] = array_merge($arrayofalerts[$alreadyfoundcommitid]['branch'], $tmpval['branch']); $arrayofalerts[$alreadyfoundcommitid]['commitidbis'][] = $tmpval['commitid']; } } } /* //$urlgit = 'https://api.github.com/search/issues?q=is:pr+repo:Dolibarr/dolibarr+created:>'.dol_print_date(dol_now() - $delay, "%Y-%m"); $urlgit = 'https://api.github.com/search/commits?q=repo:Dolibarr/dolibarr+yogosha+created:>'.dol_print_date(dol_now() - $delay, "%Y-%m"); // Count lines of code of application $newurl = $urlgit.'+CVE'; $result = getURLContent($newurl); print 'Execute GET on github for '.$newurl."\n"; if ($result && $result['http_code'] == 200) { $arrayofalerts1 = json_decode($result['content']); foreach ($arrayofalerts1->items as $val) { $tmpval = cleanVal($val); if (preg_match('/CVE/i', $tmpval['title'])) { $arrayofalerts[$tmpval['number']] = $tmpval; } } } else { print 'Error: failed to get github response'; exit(-1); } $newurl = $urlgit.'+yogosha'; $result = getURLContent($newurl); print 'Execute GET on github for '.$newurl."\n"; if ($result && $result['http_code'] == 200) { $arrayofalerts2 = json_decode($result['content']); foreach ($arrayofalerts2->items as $val) { $tmpval = cleanVal($val); if (preg_match('/yogosha:/i', $tmpval['title'])) { $arrayofalerts[$tmpval['number']] = $tmpval; } } } else { print 'Error: failed to get github response'; exit(-1); } $newurl = $urlgit.'+Sec:'; $result = getURLContent($newurl); print 'Execute GET on github for '.$newurl."\n"; if ($result && $result['http_code'] == 200) { $arrayofalerts3 = json_decode($result['content']); foreach ($arrayofalerts3->items as $val) { $tmpval = cleanVal($val); if (preg_match('/Sec:/i', $tmpval['title'])) { $arrayofalerts[$tmpval['number']] = $tmpval; } } } else { print 'Error: failed to get github response'; exit(-1); } */ $timeend = time(); /* * View */ $html = ''."\n"; $html .= ''."\n"; $html .= ''."\n"; $html .= ''."\n"; $html .= ''."\n"; $html .= ''."\n"; $html .= ''."\n"; $html .= ''."\n"; $html .= ''."\n"; $html .= ''; $html .= ''; $html .= ' '."\n"; $html .= ''."\n"; // Header $html .= '
'."\n"; $html .= '

Advanced Project Status

'."\n"; $currentDate = date("Y-m-d H:i:s"); // Format: Year-Month-Day Hour:Minute:Second $html .= 'Generated on '.$currentDate.' in '.($timeend - $timestart).' seconds by apstats'."\n"; $html .= '
'."\n"; // Lines of code $html .= '
'."\n"; $html .= '

Lines of code

'."\n"; $html .= '
'."\n"; $html .= '
'."\n"; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; //$html .= ''; $html .= ''; foreach (array('proj', 'dep') as $source) { $html .= ''; if ($source == 'proj') { $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; //$html .= ''; $html .= ''; if (!empty($arrayoflineofcode[$source])) { foreach ($arrayoflineofcode[$source] as $key => $val) { $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; //$html .= ''; /*$html .= ''; */ $html .= ''; } } } $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; //$html .= ''; //$html .= ''; $html .= ''; $html .= '
LanguageBytesFilesLinesBlanksCommentsCode'.$val['Complexity'].'
All files without dependencies'; } elseif ($source == 'dep') { $html .= 'All files of dependencies only'; } $html .= '    
See detail per file type...'; $html .= '
'.formatNumber($arrayofmetrics[$source]['Bytes']).''.formatNumber($arrayofmetrics[$source]['Files']).''.formatNumber($arrayofmetrics[$source]['Lines']).''.formatNumber($arrayofmetrics[$source]['Blanks']).''.formatNumber($arrayofmetrics[$source]['Comments']).''.formatNumber($arrayofmetrics[$source]['Code']).'
Total'.formatNumber($arrayofmetrics['proj']['Bytes'] + $arrayofmetrics['dep']['Bytes']).''.formatNumber($arrayofmetrics['proj']['Files'] + $arrayofmetrics['dep']['Files']).''.formatNumber($arrayofmetrics['proj']['Lines'] + $arrayofmetrics['dep']['Lines']).''.formatNumber($arrayofmetrics['proj']['Blanks'] + $arrayofmetrics['dep']['Blanks']).''.formatNumber($arrayofmetrics['proj']['Comments'] + $arrayofmetrics['dep']['Comments']).''.formatNumber($arrayofmetrics['proj']['Code'] + $arrayofmetrics['dep']['Code']).''.$arrayofmetrics['Complexity'].'
'; $html .= '
'; $html .= '
'; // OSSINSIGHT graph /* $html .= << Lines of Code Changes of Dolibarr/dolibarr END; */ $html .= '
'."\n"; // Contributions $html .= '
'."\n"; $html .= '

Contributions

'."\n"; $html .= '
'."\n"; $html .= << Pushes and Commits of Dolibarr/dolibarr Pull Request Size of Dolibarr/dolibarr END; $html .= ''; $html .= '
'; $html .= '
'."\n"; // Community - Contributors $html .= '
'."\n"; $html .= '

Contributors

'."\n"; $html .= '
'."\n"; $html .= <<
Thumbs of most active contributors

Dolibarr
END; /* $html .= << Contributors of Dolibarr/dolibarr END; */ $html .= << Star History of Dolibarr/dolibarr END; $html .= '
'; $html .= '
'."\n"; // Project value $html .= '
'."\n"; $html .= '

Project value

'."\n"; $html .= '
'."\n"; $html .= '
'; $html .= 'COCOMO value
(Basic organic model)
'; $html .= '$'.formatNumber((empty($arraycocomo['proj']['currency']) ? 0 : $arraycocomo['proj']['currency']) + (empty($arraycocomo['dep']['currency']) ? 0 : $arraycocomo['dep']['currency']), 2).''; $html .= '
'; if (array_key_exists('proj', $arraycocomo)) { $html .= '
'; $html .= 'COCOMO effort
(Basic organic model)
'; $html .= ''.formatNumber($arraycocomo['proj']['people'] * $arraycocomo['proj']['effort'] + $arraycocomo['dep']['people'] * $arraycocomo['dep']['effort']); $html .= ' months people'; $html .= '
'; } $html .= '
'; $html .= '
'."\n"; $tmpstan = ''; $nblines = 0; if (!empty($output_arrtd)) { foreach ($output_arrtd as $line) { $reg = array(); //print $line."\n"; preg_match('/^::error file=(.*),line=(\d+),col=(\d+)::(.*)$/', $line, $reg); if (!empty($reg[1])) { if ($nblines < 20) { $tmpstan .= ''; } else { $tmpstan .= ''; } $tmpstan .= ''.dolPrintLabel($reg[1]).''; $tmpstan .= ''; $tmpstan .= ''.dolPrintLabel($reg[2]).''; $tmpstan .= ''; $tmpstan .= ''.dolPrintLabel($reg[4]).''; $tmpstan .= ''."\n"; $nblines++; } } } $tmpphan = ''; $phan_nblines = 0; if (count($output_phan_json) != 0) { $phan_notices = json_decode($output_phan_json[count($output_phan_json) - 1], true); // Info: result code is $res_exec_phan '@phan-var-force array $phan_notices'; $phan_items = []; foreach ($phan_notices as $notice) { if (!empty($notice['location'])) { $path = $notice['location']['path']; if ($path == 'internal') { continue; } $line_start = $notice['location']['lines']['begin']; $line_end = $notice['location']['lines']['end']; if ($line_start == $line_end) { $line_range = "#L{$line_start}"; $line_range_txt = $line_start; } else { $line_range = "#L{$line_start}-L{$line_end}"; $line_range_txt = "{$line_start}-{$line_end}"; } $code_url_attr = dol_escape_htmltag($urlgit.$path.$line_range); if ($phan_nblines < 20) { $tmpphan .= ''; } else { $tmpphan .= ''; } $tmpphan .= ''.dolPrintLabel($path).''; $tmpphan .= ''; $tmpphan .= ''.$line_range_txt.''; $tmpphan .= ''; $tmpphan .= ''.dolPrintLabel($notice['description']).''; $tmpphan .= ''; $tmpphan .= "\n"; $phan_nblines++; } } } // Last security errors $title_security_short = "Last security issues"; $title_security = ($project ? "[".$project."] " : "").$title_security_short; $html .= '
'."\n"; $html .= '

'.$title_security_short.' (last '.($nbofmonth != 1 ? $nbofmonth.' months' : 'month').')

'."\n"; $html .= '
'."\n"; $html .= '
'."\n"; $html .= ''."\n"; $html .= ''."\n"; foreach ($arrayofalerts as $key => $alert) { $cve = ''; $yogosha = empty($alert['issueidyogosha']) ? '' : $alert['issueidyogosha']; $arrayofalerts[$key]['url_commit'] = 'https://github.com/Dolibarr/dolibarr/commit/'.$alert['commitid']; if (!empty($alert['issueid'])) { $arrayofalerts[$key]['url_issue'] = 'https://github.com/Dolibarr/dolibarr/issues/'.$alert['issueid']; } if (!empty($alert['issueidcve'])) { $cve = preg_replace('/\s+/', '-', trim($alert['issueidcve'])); $arrayofalerts[$key]['url_cve'] = 'https://nvd.nist.gov/vuln/detail/CVE-'.$cve; } $arrayofalerts[$key]['title'] = ($project ? "[".$project."] " : "").'Security alert - '.($yogosha ? ' Yogosha #'.$yogosha.' - ' : '').($cve ? 'CVE-'.$cve.' - ' : ''); $arrayofalerts[$key]['title'] .= 'Fix committed as: '.dol_trunc($alert['commitid'], 8); $arrayofalerts[$key]['description'] = ''; $html .= ''; // Commits ID - Add link to Github $html .= ''; // Date creation $html .= ''; // Yogosha ID $html .= ''; // GIT Issue/PR ID $html .= ''; // CVE ID $html .= ''; // Description $html .= ''; // Branches $html .= ''; $arrayofalerts[$key]['description'] .= ']]>'; $html .= ''; } $html .= '
Commit IDDateReported on
Yogosha
Reported on
GIT
Reported on
CVE
TitleBranch of fix
'; $html .= ''.dol_trunc($alert['commitid'], 8).''; $arrayofalerts[$key]['description'] .= "\n
".'Commit ID: '.dol_trunc($alert['commitid'], 8).''; if (!empty($alert['commitidbis'])) { $html .= '
+
'; } $html .= '
'; $html .= preg_replace('/T.*$/', '', $alert['created_at']); $html .= ''; if (!empty($alert['issueidyogosha'])) { //$html .= ''; $html .= '#yogosha'.$alert['issueidyogosha']; $arrayofalerts[$key]['description'] .= "\n
".'Yogosha ID #'.$alert['issueidyogosha']; //$html .= '
'; } else { //$html .= 'public issue'; } $html .= '
'; if (!empty($alert['issueid'])) { $html .= '#'.$alert['issueid'].''; $arrayofalerts[$key]['description'] .= "\n
".'GitHub ID #'.$alert['issueid'].''; } else { //$html .= 'private'; } $html .= '
'; if (!empty($alert['issueidcve'])) { $cve = preg_replace('/\s+/', '-', trim($alert['issueidcve'])); $html .= 'CVE-'.$cve.''; $arrayofalerts[$key]['description'] .= "\n
".'CVE: CVE-'.$cve.''; } $html .= '
'.dol_escape_htmltag($alert['title']).''; if (!empty($alert['branch'])) { $listofbranchnames = implode(', ', array_unique($alert['branch'])); $html .= $listofbranchnames; $arrayofalerts[$key]['description'] .= "\n

".'Branches of fix: '.$listofbranchnames; } $html .= '
'; $html .= '
'; $html .= '
'; $html .= '
'; $html .= 'You can use this URL for RSS notifications: '.$outputfilerss.'

'; $html .= '
'; // Generate the RSS file $fh = fopen($outputdir.'/'.$outputfilerss, 'w'); if ($fh) { if ($url_root && empty($url_site)) { $url_site = $url_root; } if ($url_root && empty($url_flux)) { $url_flux = $url_root.'/'.$outputfilerss; } // Generation of the feed fwrite($fh, ''."\n"); fwrite($fh, ''."\n"); fwrite($fh, ''."\n"); fwrite($fh, '' . htmlspecialchars($title_security) . ''."\n"); fwrite($fh, '' . htmlspecialchars("Feed of the latest security reports on the project") . ''."\n"); //fwrite($fh, ''."\n"); fwrite($fh, 'en-US'."\n"); fwrite($fh, ''.date('r').''."\n"); /* Mon, 29 Apr 2024 11:33:54 +0000 */ if ($url_site) { fwrite($fh, '' . htmlspecialchars($url_site) . ''."\n"); } // Image fwrite($fh, ''."\n"); fwrite($fh, 'https://www.dolibarr.org/medias/image/www.dolibarr.org/badge-openssf.png'."\n"); fwrite($fh, '' . htmlspecialchars($title_security) . ''."\n"); if ($url_site) { fwrite($fh, '' . htmlspecialchars($url_site) . ''."\n"); } fwrite($fh, ''."\n"); foreach ($arrayofalerts as $alert) { $alert['url_commit'] = 'https://github.com/Dolibarr/dolibarr/commit/'.$alert['commitid']; fwrite($fh, ''."\n"); fwrite($fh, '' . htmlspecialchars($alert['title']) . ''."\n"); // Description of alert, list of links to sources and branches of fixes fwrite($fh, '' . $alert['description'] . ''."\n"); // no htmlspeciachars here fwrite($fh, '' . htmlspecialchars($alert['url_commit']) . ''."\n"); $tmpdate = strtotime($alert['created_at']); fwrite($fh, '' . htmlspecialchars(date('r', $tmpdate)) . ''."\n"); fwrite($fh, ''."\n"); // A hidden unique ID fwrite($fh, ''."\n"); } fwrite($fh, ''."\n"); fwrite($fh, ''."\n"); fclose($fh); print 'Generation of RSS output file '.$outputdir.'/'.$outputfilerss.' done.'."\n"; } else { print 'Failed to generate the RSS file '.$outputdir.'/'.$outputfilerss."\n"; } // Technical debt PHPstan if ($dirphpstan != 'disabled') { $datatable_script .= ' if (typeof(DataTable)==="function") {jQuery(".sourcephpstan").toggle(true);} let phpstantable = new DataTable("#technicaldebt table", { lengthMenu: [ [10, 25, 50, 100, -1], [10, 25, 50, 100, \'All\'] ]}); '; $html .= '
'."\n"; $html .= '

Technical debt ('.$phpstanversion.' - level '.$PHPSTANLEVEL.' -> '.$nblines.' warnings)

'."\n"; $html .= '
'."\n"; $html .= '
'."\n"; $html .= ''."\n"; $html .= ''."\n"; $html .= $tmpstan; $html .= '
FileLineType
'; // Disabled, no more required as list is managed with datatable //$html .= '
Show all...
'; $html .= '
'; $html .= '
'."\n"; } // Technical debt Phan if ($dir_phan != 'disabled') { $datatable_script .= ' if (typeof(DataTable)==="function") {jQuery(".sourcephan").toggle(true);} let phantable = new DataTable("#technicaldebtphan table", { lengthMenu: [ [10, 25, 50, 100, -1], [10, 25, 50, 100, \'All\'] ]}); '; $html .= '
'."\n"; $html .= '

Technical debt (PHAN '.$phan_nblines.' warnings)

'."\n"; $html .= '
'."\n"; $html .= '
'."\n"; $html .= ''."\n"; $html .= ''."\n"; $html .= $tmpphan; $html .= '
FileLineDetail
'; // Disabled, no more required as list is managed with datatable //$html .= '
Show all...
'; $html .= '
'; $html .= '
'."\n"; } // JS code to allow to expand/collapse $html .= ' '; $html .= ''; $html .= ''; // Output report into a HTML file $fh = fopen($outputpath, 'w'); if ($fh) { fwrite($fh, $html); fclose($fh); print 'Generation of output file '.$outputpath.' done.'."\n"; } else { print 'Failed to open '.$outputpath.' for output.'."\n"; } /** * function to format a number * * @param string|int $number Number to format * @param int $nbdec Number of decimal digits * @return string Formatted string */ function formatNumber($number, $nbdec = 0) { return number_format($number, 0, '.', ' '); } /** * cleanVal * * @param array $val Array of a PR * @return Array of a PR */ function cleanVal($val) { $tmpval = array(); $tmpval['url'] = $val->url; $tmpval['number'] = $val->number; $tmpval['title'] = $val->title; $tmpval['created_at'] = $val->created_at; $tmpval['updated_at'] = $val->updated_at; return $tmpval; } /** * cleanVal2 * * @param array $val Array of a PR * @return Array of a PR */ function cleanVal2($val) { $tmp = explode(';', $val); $tmpval = array(); $tmpval['commitid'] = $tmp[1]; $tmpval['url'] = ''; $tmpval['issueid'] = ''; $tmpval['issueidyogosha'] = ''; $tmpval['issueidcve'] = ''; $tmpval['title'] = array_key_exists(5, $tmp) ? $tmp[5] : ''; $tmpval['created_at'] = array_key_exists(0, $tmp) ? $tmp[0] : ''; $tmpval['updated_at'] = ''; $reg = array(); if (preg_match('/#(\d+)/', $tmpval['title'], $reg)) { $tmpval['issueid'] = $reg[1]; } if (preg_match('/CVE([0-9\-\s]+)/', $tmpval['title'], $reg)) { $tmpval['issueidcve'] = preg_replace('/^\-/', '', trim($reg[1])); } if (preg_match('/#yogosha(\d+)/i', $tmpval['title'], $reg)) { $tmpval['issueidyogosha'] = $reg[1]; } return $tmpval; }