Files
dolibarr/htdocs/core/class/interfaces.class.php
MDW 42bb4fbf3e Qual: Enable & fix or ignore Invalid DimOffset (#31176)
* Qual: Enable & fix or ignore Invalid DimOffset

# Qual: Enable & fix or ignore Invalid DimOffset

The Invalid DimOffset notices occur when array keys are defined and
the index used is not amongst the known array keys.

This PR enables these notices and fixes array definitions when needed,
or ignores the notices locally if it's a false positive, or in the
baseline.txt when it does not seem to be a false positive so that
it can be fixed later

* Nullable object typing in function signature not ok for 7.0/8.4

Can't user '?User' as argument type for PHP7.0 which is required by PHP8.4.
Therefore, removing the typing specification in the function definition

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2024-09-29 21:52:31 +02:00

410 lines
14 KiB
PHP

<?php
/* Copyright (C) 2005-2009 Laurent Destailleur <eldy@users.sourceforge.net>
* Copyright (C) 2006 Rodolphe Quiedeville <rodolphe@quiedeville.org>
* Copyright (C) 2010 Regis Houssin <regis.houssin@inodbox.com>
* Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
*
* 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 <https://www.gnu.org/licenses/>.
*/
/**
* \file htdocs/core/class/interfaces.class.php
* \ingroup core
* \brief Fichier de la class de gestion des triggers
*/
require_once DOL_DOCUMENT_ROOT.'/core/triggers/dolibarrtriggers.class.php';
/**
* Class to manage triggers
*/
class Interfaces
{
/**
* @var DoliDB Database handler.
*/
public $db;
public $dir; // Directory with all core and external triggers files
/**
* @var string Last module name in error
*/
public $lastmoduleerror;
/**
* @var string[] Error codes (or messages)
*/
public $errors = array();
/**
* Constructor
*
* @param DoliDB $db Database handler
*/
public function __construct($db)
{
$this->db = $db;
}
// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
/**
* Function called when a Dolibarr business event occurs
* This function call all qualified triggers.
*
* @param string $action Trigger event code
* @param Object $object Object concerned. Some context information may also be provided into array property object->context.
* @param User $user Object user
* @param Translate $langs Object lang
* @param Conf $conf Object conf
* @return int Nb of triggers ran if no error, -Nb of triggers with errors otherwise.
*/
public function run_triggers($action, $object, $user, $langs, $conf)
{
// phpcs:enable
if (getDolGlobalInt('MAIN_TRIGGER_DEBUG')) {
// This his too much verbose, enabled if const enabled only
dol_syslog(get_class($this)."::run_triggers action=".$action." Launch run_triggers", LOG_DEBUG);
}
// Check parameters
if (!is_object($object) || !is_object($conf)) { // Error
$error = 'function run_triggers called with wrong parameters action='.$action.' object='.((string) (int) is_object($object)).' user='.((string) (int) is_object($user)).' langs='.((string) (int) is_object($langs)).' conf='.((string) (int) is_object($conf));
dol_syslog(get_class($this).'::run_triggers '.$error, LOG_ERR);
$this->errors[] = $error;
return -1;
}
if (!is_object($langs)) { // Warning
dol_syslog(get_class($this).'::run_triggers was called with wrong parameters action='.$action.' object='.((string) (int) is_object($object)).' user='.((string) (int) is_object($user)).' langs='.((string) (int) is_object($langs)).' conf='.((string) (int) is_object($conf)), LOG_WARNING);
}
if (!is_object($user)) { // Warning
dol_syslog(get_class($this).'::run_triggers was called with wrong parameters action='.$action.' object='.((string) (int) is_object($object)).' user='.((string) (int) is_object($user)).' langs='.((string) (int) is_object($langs)).' conf='.((string) (int) is_object($conf)), LOG_WARNING);
$user = new User($this->db);
}
$nbfile = $nbtotal = $nbok = $nbko = 0;
$this->lastmoduleerror = '';
$files = array();
$modules = array();
$orders = array();
$i = 0;
$dirtriggers = array_merge(array('/core/triggers'), $conf->modules_parts['triggers']);
foreach ($dirtriggers as $reldir) {
$dir = dol_buildpath($reldir, 0);
$newdir = dol_osencode($dir);
//print "xx".$dir;exit;
// Check if directory exists (we do not use dol_is_dir to avoir loading files.lib.php at each call)
if (!is_dir($newdir)) {
continue;
}
$handle = opendir($newdir);
if (is_resource($handle)) {
$fullpathfiles = array();
'@phan-var-force array<string,string> $fullpathfiles';
while (($file = readdir($handle)) !== false) {
$reg = array();
if (is_readable($newdir."/".$file) && preg_match('/^interface_([0-9]+)_([^_]+)_(.+)\.class\.php$/i', $file, $reg)) {
$part1 = $reg[1];
$part2 = $reg[2];
$part3 = $reg[3];
$nbfile++;
// Check if trigger file is disabled by name
if (preg_match('/NORUN$/i', $file)) {
continue;
}
// Check if trigger file is for a particular module
$qualified = true;
if (strtolower($reg[2]) != 'all') {
$module = preg_replace('/^mod/i', '', $reg[2]);
if (!isModEnabled(strtolower($module))) {
$qualified = false;
}
}
if (!$qualified) {
//dol_syslog(get_class($this)."::run_triggers action=".$action." Triggers for file '".$file."' need module to be enabled", LOG_DEBUG);
continue;
}
$modName = "Interface".ucfirst($reg[3]);
//print "file=$file - modName=$modName\n";
if (array_key_exists($modName, $fullpathfiles)) { // $modules = list of modName already loaded, fullpathfiles[$modName] is alsoset
$langs->load("errors");
dol_syslog(get_class($this)."::run_triggers action=".$action." ".$langs->trans("ErrorDuplicateTrigger", $newdir."/".$file, $fullpathfiles[$modName]), LOG_WARNING);
continue;
}
try {
//print 'Todo for '.$modName." : ".$newdir.'/'.$file."\n";
include_once $newdir.'/'.$file;
//print 'Done for '.$modName."\n";
} catch (Exception $e) {
dol_syslog('ko for '.$modName." ".$e->getMessage()."\n", LOG_ERR);
}
$modules[$i] = $modName;
$files[$i] = $file;
$fullpathfiles[$modName] = $newdir.'/'.$file;
$orders[$i] = $part1.'_'.$part2.'_'.$part3; // Set sort criteria value
$i++;
}
}
closedir($handle);
}
}
asort($orders, SORT_NATURAL);
// Loop on each trigger
foreach ($orders as $key => $value) {
$modName = $modules[$key];
if (empty($modName)) {
continue;
}
$objMod = new $modName($this->db);
if ($objMod) {
$dblevelbefore = $this->db->transaction_opened;
$result = 0;
if (method_exists($objMod, 'runTrigger')) { // New method to implement
//dol_syslog(get_class($this)."::run_triggers action=".$action." Launch runTrigger for file '".$files[$key]."'", LOG_DEBUG);
$result = $objMod->runTrigger($action, $object, $user, $langs, $conf);
} elseif (method_exists($objMod, 'run_trigger')) { // Deprecated method
dol_syslog(get_class($this)."::run_triggers action=".$action." Launch old method run_trigger (rename your trigger into runTrigger) for file '".$files[$key]."'", LOG_WARNING);
$result = $objMod->run_trigger($action, $object, $user, $langs, $conf);
} else {
dol_syslog(get_class($this)."::run_triggers action=".$action." A trigger was declared for class ".get_class($objMod)." but method runTrigger was not found", LOG_ERR);
}
$dblevelafter = $this->db->transaction_opened;
if ($dblevelbefore != $dblevelafter) {
$errormessage = "Error, the balance begin/close of db transactions has been broken into trigger ".$modName." with action=".$action." before=".$dblevelbefore." after=".$dblevelafter;
$this->errors[] = $errormessage;
dol_syslog($errormessage, LOG_ERR);
$result = -1;
}
if ($result > 0) {
// Action OK
$nbtotal++;
$nbok++;
}
if ($result == 0) {
// Aucune action faite
$nbtotal++;
}
if ($result < 0) {
// Action KO
//dol_syslog("Error in trigger ".$action." - result = ".$result." - Nb of error string returned = ".count($objMod->errors), LOG_ERR);
$nbtotal++;
$nbko++;
$this->lastmoduleerror = $modName;
if (!empty($objMod->errors)) {
$this->errors = array_merge($this->errors, $objMod->errors);
} elseif (!empty($objMod->error)) {
$this->errors[] = $objMod->error;
}
//dol_syslog("Error in trigger ".$action." - Nb of error string returned = ".count($this->errors), LOG_ERR);
}
} else {
dol_syslog(get_class($this)."::run_triggers action=".$action." Failed to instantiate trigger for file '".$files[$key]."'", LOG_ERR);
}
}
if ($nbko) {
dol_syslog(get_class($this)."::run_triggers action=".$action." Files found: ".$nbfile.", Files launched: ".$nbtotal.", Done: ".$nbok.", Failed: ".$nbko.($this->lastmoduleerror ? " - Last module in error: ".$this->lastmoduleerror : "")." - Nb of error string returned in this->errors = ".count($this->errors), LOG_ERR);
return -$nbko;
} else {
//dol_syslog(get_class($this)."::run_triggers Files found: ".$nbfile.", Files launched: ".$nbtotal.", Done: ".$nbok.", Failed: ".$nbko, LOG_DEBUG);
return $nbok;
}
}
/**
* Return list of triggers. Function used by admin page htdoc/admin/triggers.
* List is sorted by trigger filename so by priority to run.
*
* @param array $forcedirtriggers null=All default directories. This parameter is used by modulebuilder module only.
* @return array Array list of triggers
*/
public function getTriggersList($forcedirtriggers = null)
{
global $conf, $langs, $db;
$files = array();
$fullpath = array();
$relpath = array();
$iscoreorexternal = array();
$modules = array();
$orders = array();
$i = 0;
$dirtriggers = array_merge(array('/core/triggers/'), $conf->modules_parts['triggers']);
if (is_array($forcedirtriggers)) {
$dirtriggers = $forcedirtriggers;
}
foreach ($dirtriggers as $reldir) {
$dir = dol_buildpath($reldir, 0);
$newdir = dol_osencode($dir);
// Check if directory exists (we do not use dol_is_dir to avoid loading files.lib.php at each call)
if (!is_dir($newdir)) {
continue;
}
$handle = opendir($newdir);
if (is_resource($handle)) {
while (($file = readdir($handle)) !== false) {
$reg = array();
if (is_readable($newdir.'/'.$file) && preg_match('/^interface_([0-9]+)_([^_]+)_(.+)\.class\.php/', $file, $reg)) {
if (preg_match('/\.back$/', $file)) {
continue;
}
$part1 = $reg[1];
$part2 = $reg[2];
$part3 = $reg[3];
$modName = 'Interface'.ucfirst($reg[3]);
//print "file=$file"; print "modName=$modName"; exit;
if (in_array($modName, $modules)) {
$langs->load("errors");
print '<div class="error">'.$langs->trans("Error").' : '.$langs->trans("ErrorDuplicateTrigger", $modName, "/htdocs/core/triggers/").'</div>';
} else {
include_once $newdir.'/'.$file;
}
$files[$i] = $file;
$fullpath[$i] = $dir.'/'.$file;
$relpath[$i] = preg_replace('/^\//', '', $reldir).'/'.$file;
$iscoreorexternal[$i] = ($reldir == '/core/triggers/' ? 'internal' : 'external');
$modules[$i] = $modName;
$orders[$i] = $part1.'_'.$part2.'_'.$part3; // Set sort criteria value
$i++;
}
}
closedir($handle);
}
}
asort($orders, SORT_NATURAL);
$triggers = array();
$j = 0;
// Loop on each trigger
foreach ($orders as $key => $value) {
$modName = $modules[$key];
if (empty($modName)) {
continue;
}
if (!class_exists($modName)) {
print 'Error: A trigger file was found but its class "'.$modName.'" was not found.'."<br>\n";
continue;
}
$text = '';
try {
$objMod = new $modName($db);
if (is_subclass_of($objMod, 'DolibarrTriggers')) {
// Define disabledbyname and disabledbymodule
$disabledbyname = 0;
$disabledbymodule = 1;
$module = '';
// Check if trigger file is disabled by name
if (preg_match('/NORUN$/i', $files[$key])) {
$disabledbyname = 1;
}
// Check if trigger file is for a particular module
if (preg_match('/^interface_([0-9]+)_([^_]+)_(.+)\.class\.php/i', $files[$key], $reg)) {
$module = preg_replace('/^mod/i', '', $reg[2]);
if (strtolower($module) == 'all') {
$disabledbymodule = 0;
} elseif (!isModEnabled(strtolower($module))) {
$disabledbymodule = 2;
}
$triggers[$j]['module'] = strtolower($module);
}
// We set info of modules
$triggers[$j]['picto'] = (!empty($objMod->picto)) ? img_object('', $objMod->picto, 'class="valignmiddle pictomodule "') : img_object('', 'generic', 'class="valignmiddle pictomodule "');
$triggers[$j]['file'] = $files[$key];
$triggers[$j]['fullpath'] = $fullpath[$key];
$triggers[$j]['relpath'] = $relpath[$key];
$triggers[$j]['iscoreorexternal'] = $iscoreorexternal[$key];
$triggers[$j]['version'] = $objMod->getVersion();
$triggers[$j]['status'] = img_picto($langs->trans("Active"), 'tick');
if ($disabledbyname > 0 || $disabledbymodule > 1) {
$triggers[$j]['status'] = '';
}
$text = '<b>'.$langs->trans("Description").':</b><br>';
$text .= $objMod->getDesc().'<br>';
$text .= '<br><b>'.$langs->trans("Status").':</b><br>';
if ($disabledbyname == 1) {
$text .= $langs->trans("TriggerDisabledByName").'<br>';
if ($disabledbymodule == 2) {
$text .= $langs->trans("TriggerDisabledAsModuleDisabled", $module).'<br>';
}
} else {
if ($disabledbymodule == 0) {
$text .= $langs->trans("TriggerAlwaysActive").'<br>';
}
if ($disabledbymodule == 1) {
$text .= $langs->trans("TriggerActiveAsModuleActive", $module).'<br>';
}
if ($disabledbymodule == 2) {
$text .= $langs->trans("TriggerDisabledAsModuleDisabled", $module).'<br>';
}
}
} else {
$triggers[$j]['picto'] = (!empty($objMod->picto)) ? img_object('', $objMod->picto, 'class="valignmiddle pictomodule "') : img_object('', 'generic', 'class="valignmiddle pictomodule "');
$triggers[$j]['file'] = $files[$key];
$triggers[$j]['fullpath'] = $fullpath[$key];
$triggers[$j]['relpath'] = $relpath[$key];
$triggers[$j]['status'] = img_picto('Error: Trigger '.$modName.' does not extends DolibarrTriggers', 'warning');
//print 'Error: Trigger '.$modName.' does not extends DolibarrTriggers<br>';
$text = 'Error: Trigger '.$modName.' does not extends DolibarrTriggers';
}
} catch (Exception $e) {
print $e->getMessage();
}
$triggers[$j]['info'] = $text;
$j++;
}
return $triggers;
}
}