2011-08-10 02:50:16 +02:00
< ? php
2016-07-06 11:14:17 +02:00
/* Copyright ( C ) 2010 - 2016 Laurent Destailleur < eldy @ users . sourceforge . net >
2018-10-27 14:43:12 +02:00
* Copyright ( C ) 2010 - 2014 Regis Houssin < regis . houssin @ inodbox . com >
2011-08-10 02:50:16 +02:00
* Copyright ( C ) 2010 - 2011 Juanjo Menent < jmenent @ 2 byte . es >
*
* 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
2013-01-16 15:36:08 +01:00
* the Free Software Foundation ; either version 3 of the License , or
2011-08-10 02:50:16 +02:00
* ( 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
2019-09-23 21:55:30 +02:00
* along with this program . If not , see < https :// www . gnu . org / licenses />.
2011-08-10 02:50:16 +02:00
*/
/**
* \file htdocs / core / class / hookmanager . class . php
* \ingroup core
* \brief File of class to manage hooks
*/
/**
2012-05-06 00:45:15 +02:00
* Class to manage hooks
2011-08-10 02:50:16 +02:00
*/
class HookManager
{
2018-08-22 11:06:34 +02:00
/**
2020-10-07 15:01:28 +02:00
* @ var DoliDB Database handler .
*/
public $db ;
2019-01-21 15:22:35 +01:00
2018-08-22 10:37:16 +02:00
/**
* @ var string Error code ( or message )
*/
2020-04-10 10:59:32 +02:00
public $error = '' ;
2018-08-21 19:08:11 +02:00
2018-08-17 19:31:22 +02:00
/**
* @ var string [] Error codes ( or messages )
*/
2018-08-21 19:08:11 +02:00
public $errors = array ();
2011-08-10 02:50:16 +02:00
2020-10-07 15:01:28 +02:00
// Context hookmanager was created for ('thirdpartycard', 'thirdpartydao', ...)
public $contextarray = array ();
2011-08-10 02:50:16 +02:00
// Array with instantiated classes
2020-10-07 15:01:28 +02:00
public $hooks = array ();
2012-03-15 22:37:42 +01:00
2012-03-02 10:29:11 +01:00
// Array result
2020-10-07 15:01:28 +02:00
public $resArray = array ();
2013-01-22 17:43:41 +01:00
// Printable result
2020-10-07 15:01:28 +02:00
public $resPrint = '' ;
2017-06-06 01:34:25 +02:00
// Nb of qualified hook ran
2020-10-07 15:01:28 +02:00
public $resNbOfHooks = 0 ;
2011-08-10 02:50:16 +02:00
/**
2011-10-14 18:51:20 +02:00
* Constructor
*
2015-01-01 11:24:00 +01:00
* @ param DoliDB $db Database handler
2011-08-10 02:50:16 +02:00
*/
2019-02-26 23:15:28 +01:00
public function __construct ( $db )
2011-08-10 02:50:16 +02:00
{
2012-02-24 08:52:30 +01:00
$this -> db = $db ;
2011-08-10 02:50:16 +02:00
}
/**
2015-01-01 11:24:00 +01:00
* Init array $this -> hooks with instantiated action controlers .
2017-06-08 11:18:41 +02:00
* First , a hook is declared by a module by adding a constant MAIN_MODULE_MYMODULENAME_HOOKS with value 'nameofcontext1:nameofcontext2:...' into $this -> const of module descriptor file .
2015-01-01 11:24:00 +01:00
* This makes $conf -> hooks_modules loaded with an entry ( 'modulename' => array ( nameofcontext1 , nameofcontext2 , ... ))
* When initHooks function is called , with initHooks ( list_of_contexts ), an array $this -> hooks is defined with instance of controler
2011-10-14 18:51:20 +02:00
* class found into file / mymodule / class / actions_mymodule . class . php ( if module has declared the context as a managed context ) .
2015-01-01 11:24:00 +01:00
* Then when a hook executeHooks ( 'aMethod' ... ) is called , the method aMethod found into class will be executed .
2011-10-05 14:29:16 +02:00
*
2016-04-09 19:32:05 +02:00
* @ param string [] $arraycontext Array list of searched hooks tab / features . For example : 'thirdpartycard' ( for hook methods into page card thirdparty ), 'thirdpartydao' ( for hook methods into Societe ), ...
2017-06-08 11:18:41 +02:00
* @ return int Always 1
2011-08-10 02:50:16 +02:00
*/
2019-02-26 23:15:28 +01:00
public function initHooks ( $arraycontext )
2011-08-10 02:50:16 +02:00
{
global $conf ;
2011-10-14 18:51:20 +02:00
// Test if there is hooks to manage
2021-02-23 22:03:23 +01:00
if ( ! is_array ( $conf -> modules_parts [ 'hooks' ]) || empty ( $conf -> modules_parts [ 'hooks' ])) {
return ;
}
2011-08-10 02:50:16 +02:00
2020-10-07 15:01:28 +02:00
// For backward compatibility
2021-02-23 22:03:23 +01:00
if ( ! is_array ( $arraycontext )) {
$arraycontext = array ( $arraycontext );
}
2011-08-10 02:50:16 +02:00
2020-04-10 10:59:32 +02:00
$this -> contextarray = array_unique ( array_merge ( $arraycontext , $this -> contextarray )); // All contexts are concatenated
2011-10-14 18:51:20 +02:00
2019-11-10 17:48:50 +01:00
$arraytolog = array ();
2021-02-23 22:03:23 +01:00
foreach ( $conf -> modules_parts [ 'hooks' ] as $module => $hooks ) { // Loop on each module that brings hooks
if ( empty ( $conf -> $module -> enabled )) {
continue ;
}
2018-04-21 11:24:45 +02:00
//dol_syslog(get_class($this).'::initHooks module='.$module.' arraycontext='.join(',',$arraycontext));
2021-02-23 22:03:23 +01:00
foreach ( $arraycontext as $context ) {
if ( is_array ( $hooks )) {
$arrayhooks = $hooks ; // New system
} else {
$arrayhooks = explode ( ':' , $hooks ); // Old system (for backward compatibility)
}
2018-04-21 11:24:45 +02:00
2021-02-23 22:03:23 +01:00
if ( in_array ( $context , $arrayhooks ) || in_array ( 'all' , $arrayhooks )) { // We instantiate action class only if initialized hook is handled by module
2018-04-21 11:24:45 +02:00
// Include actions class overwriting hooks
2021-02-23 22:03:23 +01:00
if ( empty ( $this -> hooks [ $context ][ $module ]) || ! is_object ( $this -> hooks [ $context ][ $module ])) { // If set to an object value, class was already loaded
2020-04-10 10:59:32 +02:00
$path = '/' . $module . '/class/' ;
2011-08-10 02:50:16 +02:00
$actionfile = 'actions_' . $module . '.class.php' ;
2019-11-10 17:48:50 +01:00
$arraytolog [] = 'context=' . $context . '-path=' . $path . $actionfile ;
2020-04-10 10:59:32 +02:00
$resaction = dol_include_once ( $path . $actionfile );
2021-02-23 22:03:23 +01:00
if ( $resaction ) {
2018-04-21 11:24:45 +02:00
$controlclassname = 'Actions' . ucfirst ( $module );
$actionInstance = new $controlclassname ( $this -> db );
2021-03-08 15:56:41 +01:00
$priority = empty ( $actionInstance -> priority ) ? 50 : $actionInstance -> priority ;
$this -> hooks [ $context ][ $priority . ':' . $module ] = $actionInstance ;
2011-08-10 02:50:16 +02:00
}
}
}
}
}
2021-02-23 22:03:23 +01:00
if ( count ( $arraytolog ) > 0 ) {
2019-11-10 17:48:50 +01:00
dol_syslog ( get_class ( $this ) . " ::initHooks Loading hooks: " . join ( ', ' , $arraytolog ), LOG_DEBUG );
}
2021-03-08 16:37:52 +01:00
if ( ! empty ( $this -> hooks [ $context ])) {
ksort ( $this -> hooks [ $context ], SORT_NATURAL );
}
2021-03-08 15:56:41 +01:00
2011-08-10 02:50:16 +02:00
return 1 ;
}
2020-10-07 15:01:28 +02:00
/**
* Execute hooks ( if they were initialized ) for the given method
*
* @ param string $method Name of method hooked ( 'doActions' , 'printSearchForm' , 'showInputField' , ... )
* @ param array $parameters Array of parameters
* @ param Object $object Object to use hooks on
* @ param string $action Action code on calling page ( 'create' , 'edit' , 'view' , 'add' , 'update' , 'delete' ... )
* @ return mixed For 'addreplace' hooks ( doActions , formConfirm , formObjectOptions , pdf_xxx , ... ) : Return 0 if we want to keep standard actions , > 0 if we want to stop / replace standard actions , < 0 if KO . Things to print are returned into -> resprints and set into -> resPrint . Things to return are returned into -> results by hook and set into -> resArray for caller .
* For 'output' hooks ( printLeftBlock , formAddObjectLine , formBuilddocOptions , ... ) : Return 0 , < 0 if KO . Things to print are returned into -> resprints and set into -> resPrint . Things to return are returned into -> results by hook and set into -> resArray for caller .
* All types can also return some values into an array -> results that will be finaly merged into this -> resArray for caller .
* $this -> error or this -> errors are also defined by class called by this function if error .
*/
2019-02-26 23:15:28 +01:00
public function executeHooks ( $method , $parameters = array (), & $object = '' , & $action = '' )
2011-08-10 02:50:16 +02:00
{
2021-02-23 22:03:23 +01:00
if ( ! is_array ( $this -> hooks ) || empty ( $this -> hooks )) {
return 0 ; // No hook available, do nothing.
}
2011-08-10 02:50:16 +02:00
2020-10-07 15:01:28 +02:00
$parameters [ 'context' ] = join ( ':' , $this -> contextarray );
//dol_syslog(get_class($this).'::executeHooks method='.$method." action=".$action." context=".$parameters['context']);
2011-08-20 19:00:34 +02:00
2020-10-07 15:01:28 +02:00
// Define type of hook ('output' or 'addreplace').
// TODO Remove hooks with type 'output'. All hooks must be converted into 'addreplace' hooks.
$hooktype = 'output' ;
if ( in_array (
2015-06-25 11:10:28 +02:00
$method ,
array (
2018-04-05 13:29:27 +02:00
'addCalendarChoice' ,
2021-01-12 13:59:20 +01:00
'addCalendarView' ,
2018-04-05 13:29:27 +02:00
'addMoreActionsButtons' ,
'addMoreMassActions' ,
'addSearchEntry' ,
2015-06-25 11:10:28 +02:00
'addStatisticLine' ,
2021-02-05 13:33:48 +01:00
'addSectionECMAuto' ,
2018-04-05 13:29:27 +02:00
'createDictionaryFieldlist' ,
2018-02-03 17:32:38 +01:00
'editDictionaryFieldlist' ,
'getFormMail' ,
2018-04-05 13:29:27 +02:00
'deleteFile' ,
2015-06-25 11:10:28 +02:00
'doActions' ,
2018-04-05 13:29:27 +02:00
'doMassActions' ,
'formatEvent' ,
2020-10-28 17:08:09 +01:00
'formConfirm' ,
2015-06-25 11:10:28 +02:00
'formCreateThirdpartyOptions' ,
'formObjectOptions' ,
'formattachOptions' ,
'formBuilddocLineOptions' ,
2018-04-05 13:29:27 +02:00
'formatNotificationMessage' ,
2020-10-07 15:01:28 +02:00
'formConfirm' ,
2019-06-04 12:48:06 +02:00
'getAccessForbiddenMessage' ,
2018-04-05 13:29:27 +02:00
'getDirList' ,
2019-06-04 12:48:06 +02:00
'getFormMail' ,
'getFormatedCustomerRef' ,
2020-10-07 15:01:28 +02:00
'getFormatedSupplierRef' ,
2019-06-04 12:48:06 +02:00
'getIdProfUrl' ,
2020-02-29 12:32:27 +01:00
'getInputIdProf' ,
2015-06-25 11:10:28 +02:00
'moveUploadedFile' ,
2018-02-27 22:06:21 +01:00
'moreHtmlStatus' ,
2018-04-05 13:29:27 +02:00
'pdf_build_address' ,
2015-06-25 11:10:28 +02:00
'pdf_writelinedesc' ,
2020-10-07 15:01:28 +02:00
'pdf_getlinenum' ,
'pdf_getlineref' ,
'pdf_getlineref_supplier' ,
'pdf_getlinevatrate' ,
'pdf_getlineupexcltax' ,
'pdf_getlineupwithtax' ,
'pdf_getlineqty' ,
'pdf_getlineqty_asked' ,
'pdf_getlineqty_shipped' ,
'pdf_getlineqty_keeptoship' ,
'pdf_getlineunit' ,
'pdf_getlineremisepercent' ,
'pdf_getlineprogress' ,
'pdf_getlinetotalexcltax' ,
'pdf_getlinetotalwithtax' ,
2015-06-25 11:10:28 +02:00
'paymentsupplierinvoices' ,
'printAddress' ,
2020-04-12 16:36:25 +02:00
'printEmail' ,
2015-06-25 11:10:28 +02:00
'printSearchForm' ,
2016-11-12 14:36:52 +01:00
'printTabsHead' ,
2018-04-05 13:29:27 +02:00
'printObjectLine' ,
'printObjectSubLine' ,
2018-05-10 11:39:29 +02:00
'restrictedArea' ,
2018-02-03 17:32:38 +01:00
'sendMail' ,
2018-04-05 13:29:27 +02:00
'sendMailAfter' ,
2020-07-10 03:02:49 +02:00
'showOptionals' ,
2018-04-30 14:39:12 +02:00
'showLinkToObjectBlock' ,
2018-06-28 22:59:53 +02:00
'setContentSecurityPolicy' ,
2020-04-09 14:54:47 +02:00
'setHtmlTitle' ,
2020-10-07 15:01:28 +02:00
'completeTabsHead'
2015-06-25 11:10:28 +02:00
)
2021-02-23 22:03:23 +01:00
)) {
$hooktype = 'addreplace' ;
}
2017-01-21 17:30:35 +01:00
2020-10-07 15:01:28 +02:00
// Init return properties
2021-03-01 20:37:16 +01:00
$this -> resPrint = '' ;
$this -> resArray = array ();
$this -> resNbOfHooks = 0 ;
2020-10-07 15:01:28 +02:00
// Loop on each hook to qualify modules that have declared context
$modulealreadyexecuted = array ();
2021-03-01 20:37:16 +01:00
$resaction = 0 ;
$error = 0 ;
2021-02-23 22:03:23 +01:00
foreach ( $this -> hooks as $context => $modules ) { // $this->hooks is an array with context as key and value is an array of modules that handle this context
if ( ! empty ( $modules )) {
2021-03-08 15:56:41 +01:00
// Loop on each active hooks of module for this context
2021-02-23 22:03:23 +01:00
foreach ( $modules as $module => $actionclassinstance ) {
2021-03-08 15:56:41 +01:00
$module = preg_replace ( '/^\d+:/' , '' , $module );
//print "Before hook ".get_class($actionclassinstance)." method=".$method." module=".$module." hooktype=".$hooktype." results=".count($actionclassinstance->results)." resprints=".count($actionclassinstance->resprints)." resaction=".$resaction."<br>\n";
2020-10-07 15:01:28 +02:00
// test to avoid running twice a hook, when a module implements several active contexts
2021-02-23 22:03:23 +01:00
if ( in_array ( $module , $modulealreadyexecuted )) {
continue ;
}
2020-10-07 15:01:28 +02:00
// jump to next module/class if method does not exist
2021-02-23 22:03:23 +01:00
if ( ! method_exists ( $actionclassinstance , $method )) {
continue ;
}
2020-10-07 15:01:28 +02:00
$this -> resNbOfHooks ++ ;
$modulealreadyexecuted [ $module ] = $module ; // Use the $currentcontext in method to avoid running twice
// Clean class (an error may have been set from a previous call of another method for same module/hook)
$actionclassinstance -> error = 0 ;
$actionclassinstance -> errors = array ();
2021-02-17 15:21:52 +01:00
dol_syslog ( get_class ( $this ) . " ::executeHooks Qualified hook found (hooktype= " . $hooktype . " ). We call method " . get_class ( $actionclassinstance ) . '->' . $method . " , context= " . $context . " , module= " . $module . " , action= " . $action . (( is_object ( $object ) && property_exists ( $object , 'id' )) ? ', objectid=' . $object -> id : '' ), LOG_DEBUG );
2020-10-07 15:01:28 +02:00
// Add current context to avoid method execution in bad context, you can add this test in your method : eg if($currentcontext != 'formfile') return;
$parameters [ 'currentcontext' ] = $context ;
// Hooks that must return int (hooks with type 'addreplace')
2021-02-23 22:03:23 +01:00
if ( $hooktype == 'addreplace' ) {
2020-10-07 15:01:28 +02:00
$resaction += $actionclassinstance -> $method ( $parameters , $object , $action , $this ); // $object and $action can be changed by method ($object->id during creation for example or $action to go back to other action for example)
2021-02-23 22:03:23 +01:00
if ( $resaction < 0 || ! empty ( $actionclassinstance -> error ) || ( ! empty ( $actionclassinstance -> errors ) && count ( $actionclassinstance -> errors ) > 0 )) {
2020-10-07 15:01:28 +02:00
$error ++ ;
2021-03-01 20:37:16 +01:00
$this -> error = $actionclassinstance -> error ;
$this -> errors = array_merge ( $this -> errors , ( array ) $actionclassinstance -> errors );
2020-10-07 15:01:28 +02:00
dol_syslog ( " Error on hook module= " . $module . " , method " . $method . " , class " . get_class ( $actionclassinstance ) . " , hooktype= " . $hooktype . ( empty ( $this -> error ) ? '' : " " . $this -> error ) . ( empty ( $this -> errors ) ? '' : " " . join ( " , " , $this -> errors )), LOG_ERR );
}
2021-02-23 22:03:23 +01:00
if ( isset ( $actionclassinstance -> results ) && is_array ( $actionclassinstance -> results )) {
$this -> resArray = array_merge ( $this -> resArray , $actionclassinstance -> results );
}
if ( ! empty ( $actionclassinstance -> resprints )) {
$this -> resPrint .= $actionclassinstance -> resprints ;
}
2021-03-01 20:37:16 +01:00
} else {
// Generic hooks that return a string or array (printLeftBlock, formAddObjectLine, formBuilddocOptions, ...)
2020-10-07 15:01:28 +02:00
// TODO. this test should be done into the method of hook by returning nothing
2021-02-23 22:03:23 +01:00
if ( is_array ( $parameters ) && ! empty ( $parameters [ 'special_code' ]) && $parameters [ 'special_code' ] > 3 && $parameters [ 'special_code' ] != $actionclassinstance -> module_number ) {
continue ;
}
2020-10-07 15:01:28 +02:00
//dol_syslog("Call method ".$method." of class ".get_class($actionclassinstance).", module=".$module.", hooktype=".$hooktype, LOG_DEBUG);
$resaction = $actionclassinstance -> $method ( $parameters , $object , $action , $this ); // $object and $action can be changed by method ($object->id during creation for example or $action to go back to other action for example)
2021-02-23 22:03:23 +01:00
if ( ! empty ( $actionclassinstance -> results ) && is_array ( $actionclassinstance -> results )) {
$this -> resArray = array_merge ( $this -> resArray , $actionclassinstance -> results );
}
if ( ! empty ( $actionclassinstance -> resprints )) {
$this -> resPrint .= $actionclassinstance -> resprints ;
}
if ( is_numeric ( $resaction ) && $resaction < 0 ) {
2020-10-07 15:01:28 +02:00
$error ++ ;
2021-03-01 20:37:16 +01:00
$this -> error = $actionclassinstance -> error ;
$this -> errors = array_merge ( $this -> errors , ( array ) $actionclassinstance -> errors );
2020-10-07 15:01:28 +02:00
dol_syslog ( " Error on hook module= " . $module . " , method " . $method . " , class " . get_class ( $actionclassinstance ) . " , hooktype= " . $hooktype . ( empty ( $this -> error ) ? '' : " " . $this -> error ) . ( empty ( $this -> errors ) ? '' : " " . join ( " , " , $this -> errors )), LOG_ERR );
}
// TODO dead code to remove (do not enable this, but fix hook instead): result must not be a string but an int. you must use $actionclassinstance->resprints to return a string
2021-02-23 22:03:23 +01:00
if ( ! is_array ( $resaction ) && ! is_numeric ( $resaction )) {
2020-10-07 15:01:28 +02:00
dol_syslog ( 'Error: Bug into hook ' . $method . ' of module class ' . get_class ( $actionclassinstance ) . '. Method must not return a string but an int (0=OK, 1=Replace, -1=KO) and set string into ->resprints' , LOG_ERR );
2021-02-23 22:03:23 +01:00
if ( empty ( $actionclassinstance -> resprints )) {
2021-03-01 20:37:16 +01:00
$this -> resPrint .= $resaction ;
$resaction = 0 ;
2021-02-23 22:03:23 +01:00
}
2020-10-07 15:01:28 +02:00
}
}
//print "After hook ".get_class($actionclassinstance)." method=".$method." hooktype=".$hooktype." results=".count($actionclassinstance->results)." resprints=".count($actionclassinstance->resprints)." resaction=".$resaction."<br>\n";
unset ( $actionclassinstance -> results );
unset ( $actionclassinstance -> resprints );
}
}
}
return ( $error ? - 1 : $resaction );
2011-08-10 02:50:16 +02:00
}
}