dolibarr 24.0.0-beta
interfaces.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2005-2009 Laurent Destailleur <eldy@users.sourceforge.net>
3 * Copyright (C) 2006 Rodolphe Quiedeville <rodolphe@quiedeville.org>
4 * Copyright (C) 2010 Regis Houssin <regis.houssin@inodbox.com>
5 * Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
6 * Copyright (C) 2025 Frédéric France <frederic.france@free.fr>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 */
21
28require_once DOL_DOCUMENT_ROOT.'/core/triggers/dolibarrtriggers.class.php';
29
30
35{
39 public $db;
40
44 public $dir; // Directory with all core and external triggers files
45
49 public $lastmoduleerror;
50
54 public $errors = array();
55
61 public function __construct($db)
62 {
63 $this->db = $db;
64 }
65
66 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
78 public function run_triggers($action, $object, $user, $langs, $conf)
79 {
80 // phpcs:enable
81
82 if (getDolGlobalInt('MAIN_TRIGGER_DEBUG')) {
83 // This his too much verbose, enabled if const enabled only
84 dol_syslog(get_class($this)."::run_triggers action=".$action." Launch run_triggers", LOG_DEBUG);
85 }
86
87 // Check parameters
88 if (!is_object($object) || !($conf instanceof Conf)) { // Error
89 $error = 'function run_triggers called with wrong parameters object or conf. 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));
90 dol_syslog(get_class($this).'::run_triggers '.$error, LOG_ERR);
91 $this->errors[] = $error;
92 return -1;
93 }
94 if (!($langs instanceof Translate)) { // Warning
95 dol_syslog(get_class($this).'::run_triggers was called with wrong parameters langs. action='.$action.' object='.((string) (int) is_object($object)).' user='.((string) (int) is_object($user)).' langs='.((string) (int) is_object($langs)).' conf=1', LOG_WARNING);
96 $langs = new Translate('', $conf);
97 }
98 if (!($user instanceof User)) { // Warning
99 dol_syslog(get_class($this).'::run_triggers was called with wrong parameters user. action='.$action.' object='.((string) (int) is_object($object)).' user='.((string) (int) is_object($user)).' langs=1 conf=1', LOG_WARNING);
100 $user = new User($this->db);
101 }
102
103 $nbfile = $nbtotal = $nbok = $nbko = 0;
104 $this->lastmoduleerror = '';
105
106 $files = array();
107 $modules = array();
108 $orders = array();
109 $i = 0;
110
111 // Special cases
112 global $mysoc;
113 if (
114 getDolGlobalString('MAIN_FRANCE_TODO_LOI_FINANCE')
115 && $object instanceof Facture && $action == 'BILL_VALIDATE' // If we try to validate an invoice
116 && in_array($mysoc->country_code, array('FR')) && $mysoc->tva_assuj // If country is France and is using VAT
117 && $object->thirdparty instanceof Societe
118 && !$object->thirdparty->isACompany() // thirdparty is individual
119 && !isModEnabled('blockedlog')
120 ) {
121 $langs->load("errors");
122 $error = 'You try to validate an invoice in the following situation:<br>';
123 $error .= 'Your country: '.$mysoc->country_code.'<br>';
124 $error .= 'Your are using VAT: '.yn($mysoc->tva_assuj).'<br>';
125 // $error .= 'The invoice is intended for a thirdparty with type : '.($object->thirdparty->isACompany() ? 'company' : 'individual').'<br>';
126 $error .= 'The invoice is intended for a thirdparty with type : individual<br>';
127 $error .= 'Customer VAT number = '.$object->thirdparty->tva_intra.'<br>';
128 $error .= 'Customer Id prof = '.$object->thirdparty->idprof1.' '.$object->thirdparty->idprof2.' '.$object->thirdparty->idprof3.' '.$object->thirdparty->idprof4.' '.$object->thirdparty->idprof5.' '.$object->thirdparty->idprof6.'<br>';
129 $error .= 'Customer Business entity type = '.$object->thirdparty->typent_code.'<br>';
130 $error .= '<br>';
131 $error .= 'This means you are eligible the to the French Loi Finance 2025 and need to enable the module Unalterable Archives to be allowed to use a software to record payments.<br>';
132 $error .= 'Please enable the module Unalterable Archives first...';
133 dol_syslog(get_class($this).'::run_triggers '.$error, LOG_ERR);
134 $this->errors[] = $error;
135 return -1;
136 }
137
138
139 $dirtriggers = array_merge(array('/core/triggers'), $conf->modules_parts['triggers']);
140 foreach ($dirtriggers as $reldir) {
141 $dir = dol_buildpath($reldir, 0);
142 $newdir = dol_osencode($dir);
143 //print "xx".$dir;exit;
144
145 // Check if directory exists (we do not use dol_is_dir to avoir loading files.lib.php at each call)
146 if (!is_dir($newdir)) {
147 continue;
148 }
149
150 $handle = opendir($newdir);
151 if (is_resource($handle)) {
152 $fullpathfiles = array();
153 '@phan-var-force array<string,string> $fullpathfiles';
154 while (($file = readdir($handle)) !== false) {
155 $reg = array();
156 if (is_readable($newdir."/".$file) && preg_match('/^interface_([0-9]+)_([^_]+)_(.+)\.class\.php$/i', $file, $reg)) {
157 $part1 = $reg[1];
158 $part2 = $reg[2];
159 $part3 = $reg[3];
160
161 $nbfile++;
162
163 // Check if trigger file is disabled by name
164 if (preg_match('/NORUN$/i', $file)) {
165 continue;
166 }
167 // Check if trigger file is for a particular module
168 $qualified = true;
169 if (strtolower($reg[2]) != 'all') {
170 $module = preg_replace('/^mod/i', '', $reg[2]);
171 if (!isModEnabled(strtolower($module))) {
172 $qualified = false;
173 }
174 }
175
176 if (!$qualified) {
177 //dol_syslog(get_class($this)."::run_triggers action=".$action." Triggers for file '".$file."' need module to be enabled", LOG_DEBUG);
178 continue;
179 }
180
181 $modName = "Interface".ucfirst($reg[3]);
182 //print "file=$file - modName=$modName\n";
183 if (array_key_exists($modName, $fullpathfiles)) { // $modules = list of modName already loaded, fullpathfiles[$modName] is alsoset
184 $langs->load("errors");
185 dol_syslog(get_class($this)."::run_triggers action=".$action." ".$langs->trans("ErrorDuplicateTrigger", $newdir."/".$file, $fullpathfiles[$modName]), LOG_WARNING);
186 continue;
187 }
188
189 try {
190 //print 'Todo for '.$modName." : ".$newdir.'/'.$file."\n";
191 include_once $newdir.'/'.$file;
192 //print 'Done for '.$modName."\n";
193 } catch (Exception $e) {
194 dol_syslog('ko for '.$modName." ".$e->getMessage()."\n", LOG_ERR);
195 }
196
197 $modules[$i] = $modName;
198 $files[$i] = $file;
199 $fullpathfiles[$modName] = $newdir.'/'.$file;
200 $orders[$i] = $part1.'_'.$part2.'_'.$part3; // Set sort criteria value
201
202 $i++;
203 }
204 }
205
206 closedir($handle);
207 }
208 }
209
210 asort($orders, SORT_NATURAL);
211
212 // Loop on each trigger
213 foreach ($orders as $key => $value) {
214 $modName = $modules[$key];
215 if (empty($modName)) {
216 continue;
217 }
218 if (!class_exists($modName)) {
219 dol_syslog(get_class($this)."::run_triggers action=".$action." A trigger file was found with a name interfaces_*_*_".preg_replace('/^interface/', '', strtolower($modName)).".class.php, but the class ".$modName." seems to not exists even after the include of this interface file. Surely a bug in the trigger file or in its name.", LOG_ERR);
220 continue;
221 }
222
223 $objMod = new $modName($this->db);
224 '@phan-var-force DolibarrTriggers $objMod';
225 if ($objMod) {
226 $dblevelbefore = $this->db->transaction_opened;
227
228 $result = 0;
229
230 if (method_exists($objMod, 'runTrigger')) { // New method to implement
231 //dol_syslog(get_class($this)."::run_triggers action=".$action." Launch runTrigger for file '".$files[$key]."'", LOG_DEBUG);
232 $result = $objMod->runTrigger($action, $object, $user, $langs, $conf);
233 } else {
234 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);
235 }
236
237 $dblevelafter = $this->db->transaction_opened;
238
239 if ($dblevelbefore != $dblevelafter) {
240 $errormessage = "Error, the balance begin/close of db transactions has been broken into trigger ".$modName." with action=".$action." before=".$dblevelbefore." after=".$dblevelafter;
241 $this->errors[] = $errormessage;
242 dol_syslog($errormessage, LOG_ERR);
243 $result = -1;
244 }
245
246 if ($result > 0) {
247 // Action OK
248 $nbtotal++;
249 $nbok++;
250 }
251 if ($result == 0) {
252 // Aucune action faite
253 $nbtotal++;
254 }
255 if ($result < 0) {
256 // Action KO
257 //dol_syslog("Error in trigger ".$action." - result = ".$result." - Nb of error string returned = ".count($objMod->errors), LOG_ERR);
258 $nbtotal++;
259 $nbko++;
260 $this->lastmoduleerror = $modName;
261 if (!empty($objMod->errors)) {
262 $this->errors = array_merge($this->errors, $objMod->errors);
263 } elseif (!empty($objMod->error)) {
264 $this->errors[] = $objMod->error;
265 }
266 //dol_syslog("Error in trigger ".$action." - Nb of error string returned = ".count($this->errors), LOG_ERR);
267 }
268 } else {
269 dol_syslog(get_class($this)."::run_triggers action=".$action." Failed to instantiate trigger for file '".$files[$key]."'", LOG_ERR);
270 }
271 }
272
273 if ($nbko) {
274 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);
275 return -$nbko;
276 } else {
277 //dol_syslog(get_class($this)."::run_triggers Files found: ".$nbfile.", Files launched: ".$nbtotal.", Done: ".$nbok.", Failed: ".$nbko, LOG_DEBUG);
278 return $nbok;
279 }
280 }
281
289 public function getTriggersList($forcedirtriggers = null)
290 {
291 global $conf, $langs, $db;
292
293 $files = array();
294 $fullpath = array();
295 $relpath = array();
296 $iscoreorexternal = array();
297 $modules = array();
298 $orders = array();
299 $i = 0;
300
301 $dirtriggers = array_merge(array('/core/triggers/'), $conf->modules_parts['triggers']);
302 if (is_array($forcedirtriggers)) {
303 $dirtriggers = $forcedirtriggers;
304 }
305
306 foreach ($dirtriggers as $reldir) {
307 $dir = dol_buildpath($reldir, 0);
308 $newdir = dol_osencode($dir);
309
310 // Check if directory exists (we do not use dol_is_dir to avoid loading files.lib.php at each call)
311 if (!is_dir($newdir)) {
312 continue;
313 }
314
315 $handle = opendir($newdir);
316 if (is_resource($handle)) {
317 while (($file = readdir($handle)) !== false) {
318 $reg = array();
319 if (is_readable($newdir.'/'.$file) && preg_match('/^interface_([0-9]+)_([^_]+)_(.+)\.class\.php/', $file, $reg)) {
320 if (preg_match('/\.back$/', $file)) {
321 continue;
322 }
323
324 $part1 = $reg[1];
325 $part2 = $reg[2];
326 $part3 = $reg[3];
327
328 $modName = 'Interface'.ucfirst($reg[3]);
329 //print "file=$file"; print "modName=$modName"; exit;
330 if (in_array($modName, $modules)) {
331 $langs->load("errors");
332 print '<div class="error">'.$langs->trans("Error").' : '.$langs->trans("ErrorDuplicateTrigger", $modName, "/htdocs/core/triggers/").'</div>';
333 } else {
334 include_once $newdir.'/'.$file;
335 }
336
337 $files[$i] = $file;
338 $fullpath[$i] = $dir.'/'.$file;
339 $relpath[$i] = preg_replace('/^\//', '', $reldir).'/'.$file;
340 $iscoreorexternal[$i] = ($reldir == '/core/triggers/' ? 'internal' : 'external');
341 $modules[$i] = $modName;
342 $orders[$i] = $part1.'_'.$part2.'_'.$part3; // Set sort criteria value
343
344 $i++;
345 }
346 }
347 closedir($handle);
348 }
349 }
350
351 asort($orders, SORT_NATURAL);
352
353 $triggers = array();
354 $j = 0;
355
356 // Loop on each trigger
357 foreach ($orders as $key => $value) {
358 $modName = $modules[$key];
359 if (empty($modName)) {
360 continue;
361 }
362
363 if (!class_exists($modName)) {
364 print 'Error: A trigger file was found but its class "'.$modName.'" was not found.'."<br>\n";
365 continue;
366 }
367
368 $text = '';
369
370 try {
371 $objMod = new $modName($db);
372 '@phan-var-force DolibarrTriggers $objMod';
373
374 if (is_subclass_of($objMod, 'DolibarrTriggers')) {
375 // Define disabledbyname and disabledbymodule
376 $disabledbyname = 0;
377 $disabledbymodule = 1;
378 $module = '';
379
380 // Check if trigger file is disabled by name
381 if (preg_match('/NORUN$/i', $files[$key])) {
382 $disabledbyname = 1;
383 }
384 // Check if trigger file is for a particular module
385 if (preg_match('/^interface_([0-9]+)_([^_]+)_(.+)\.class\.php/i', $files[$key], $reg)) {
386 $module = preg_replace('/^mod/i', '', $reg[2]);
387 if (strtolower($module) == 'all') {
388 $disabledbymodule = 0;
389 } elseif (!isModEnabled(strtolower($module))) {
390 $disabledbymodule = 2;
391 }
392 $triggers[$j]['module'] = strtolower($module);
393 }
394
395 // We set info of modules
396 $triggers[$j]['picto'] = (!empty($objMod->picto)) ? img_object('', $objMod->picto, 'class="valignmiddle pictomodule pictofixedwidth"') : img_object('', 'generic', 'class="valignmiddle pictomodule pictofixedwidth"');
397 $triggers[$j]['file'] = $files[$key];
398 $triggers[$j]['fullpath'] = $fullpath[$key];
399 $triggers[$j]['relpath'] = $relpath[$key];
400 $triggers[$j]['iscoreorexternal'] = $iscoreorexternal[$key];
401 $triggers[$j]['version'] = $objMod->getVersion();
402 $triggers[$j]['status'] = img_picto($langs->trans("Active"), 'tick');
403 if ($disabledbyname > 0 || $disabledbymodule > 1) {
404 $triggers[$j]['status'] = '';
405 }
406
407 $text = '<b>'.$langs->trans("Description").':</b><br>';
408 $text .= $objMod->getDesc().'<br>';
409 $text .= '<br><b>'.$langs->trans("Status").':</b><br>';
410 if ($disabledbyname == 1) {
411 $text .= $langs->trans("TriggerDisabledByName").'<br>';
412 if ($disabledbymodule == 2) {
413 $text .= $langs->trans("TriggerDisabledAsModuleDisabled", $module).'<br>';
414 }
415 } else {
416 if ($disabledbymodule == 0) {
417 $text .= $langs->trans("TriggerAlwaysActive").'<br>';
418 }
419 if ($disabledbymodule == 1) {
420 $text .= $langs->trans("TriggerActiveAsModuleActive", $module).'<br>';
421 }
422 if ($disabledbymodule == 2) {
423 $text .= $langs->trans("TriggerDisabledAsModuleDisabled", $module).'<br>';
424 }
425 }
426 } else {
427 $triggers[$j]['picto'] = (!empty($objMod->picto)) ? img_object('', $objMod->picto, 'class="valignmiddle pictomodule "') : img_object('', 'generic', 'class="valignmiddle pictomodule "');
428 $triggers[$j]['file'] = $files[$key];
429 $triggers[$j]['fullpath'] = $fullpath[$key];
430 $triggers[$j]['relpath'] = $relpath[$key];
431 $triggers[$j]['status'] = img_picto('Error: Trigger '.$modName.' does not extends DolibarrTriggers', 'warning');
432
433 //print 'Error: Trigger '.$modName.' does not extends DolibarrTriggers<br>';
434 $text = 'Error: Trigger '.$modName.' does not extend DolibarrTriggers';
435 }
436 } catch (Exception $e) {
437 print $e->getMessage();
438 }
439
440 $triggers[$j]['info'] = $text;
441 $j++;
442 }
443 return $triggers;
444 }
445}
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
Class to stock current configuration.
Class to manage invoices.
Class to manage triggers.
__construct($db)
Constructor.
run_triggers($action, $object, $user, $langs, $conf)
Function called when a Dolibarr business event occurs This function call all qualified triggers.
getTriggersList($forcedirtriggers=null)
Return list of triggers.
Class to manage third parties objects (customers, suppliers, prospects...)
isACompany()
Check if third party is a company (Business) or an end user (Consumer)
Class to manage translations.
Class to manage Dolibarr users.
global $mysoc
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $db
API class for accounts.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
dol_osencode($str)
Return a string encoded into OS filesystem encoding.
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $allowothertags=array())
Show a picto called object_picto (generic function)
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
print $langs trans("Show") . '< td style="' . $timeColor . '" align="center"> s</td > badge status0 badge status4 badge status3 Error badge status8< td align="center">< span class="badge ' . $badge . '"></span ></td >< td align="center">< a href="#" class="button button-small" onclick="openLogModal(this)" data-req="' . dol_escape_htmltag($reqSafe) . '" data-res="' . dol_escape_htmltag($resSafe) . '" data-err="' . dol_escape_htmltag($errSafe) . '">< span class="fa fa-search-plus"></span ></a ></td ></tr >< tr >< td colspan="' . $colspan . '" class="opacitymedium"></td ></tr ></table ></div ></form > logModal none logModal none s a JSON string
buildzip.php