hookmanager.class.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. <?php
  2. /* Copyright (C) 2010-2016 Laurent Destailleur <eldy@users.sourceforge.net>
  3. * Copyright (C) 2010-2014 Regis Houssin <regis.houssin@capnetworks.com>
  4. * Copyright (C) 2010-2011 Juanjo Menent <jmenent@2byte.es>
  5. *
  6. * This program is free software; you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation; either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. /**
  20. * \file htdocs/core/class/hookmanager.class.php
  21. * \ingroup core
  22. * \brief File of class to manage hooks
  23. */
  24. /**
  25. * Class to manage hooks
  26. */
  27. class HookManager
  28. {
  29. var $db;
  30. var $error;
  31. var $errors=array();
  32. // Context hookmanager was created for ('thirdpartycard', 'thirdpartydao', ...)
  33. var $contextarray=array();
  34. // Array with instantiated classes
  35. var $hooks=array();
  36. // Array result
  37. var $resArray=array();
  38. // Printable result
  39. var $resPrint='';
  40. // Nb of qualified hook ran
  41. var $resNbOfHooks=0;
  42. /**
  43. * Constructor
  44. *
  45. * @param DoliDB $db Database handler
  46. */
  47. function __construct($db)
  48. {
  49. $this->db = $db;
  50. }
  51. /**
  52. * Init array $this->hooks with instantiated action controlers.
  53. * 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.
  54. * This makes $conf->hooks_modules loaded with an entry ('modulename'=>array(nameofcontext1,nameofcontext2,...))
  55. * When initHooks function is called, with initHooks(list_of_contexts), an array $this->hooks is defined with instance of controler
  56. * class found into file /mymodule/class/actions_mymodule.class.php (if module has declared the context as a managed context).
  57. * Then when a hook executeHooks('aMethod'...) is called, the method aMethod found into class will be executed.
  58. *
  59. * @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), ...
  60. * @return int Always 1
  61. */
  62. function initHooks($arraycontext)
  63. {
  64. global $conf;
  65. // Test if there is hooks to manage
  66. if (! is_array($conf->modules_parts['hooks']) || empty($conf->modules_parts['hooks'])) return;
  67. // For backward compatibility
  68. if (! is_array($arraycontext)) $arraycontext=array($arraycontext);
  69. $this->contextarray=array_unique(array_merge($arraycontext,$this->contextarray)); // All contexts are concatenated
  70. foreach($conf->modules_parts['hooks'] as $module => $hooks)
  71. {
  72. if ($conf->$module->enabled)
  73. {
  74. foreach($arraycontext as $context)
  75. {
  76. if (is_array($hooks)) $arrayhooks=$hooks; // New system
  77. else $arrayhooks=explode(':',$hooks); // Old system (for backward compatibility)
  78. if (in_array($context,$arrayhooks) || in_array('all',$arrayhooks)) // We instantiate action class only if hook is required
  79. {
  80. $path = '/'.$module.'/class/';
  81. $actionfile = 'actions_'.$module.'.class.php';
  82. $pathroot = '';
  83. // Include actions class overwriting hooks
  84. dol_syslog('Loading hook:' . $actionfile, LOG_INFO);
  85. $resaction=dol_include_once($path.$actionfile);
  86. if ($resaction)
  87. {
  88. $controlclassname = 'Actions'.ucfirst($module);
  89. $actionInstance = new $controlclassname($this->db);
  90. $this->hooks[$context][$module] = $actionInstance;
  91. }
  92. }
  93. }
  94. }
  95. }
  96. return 1;
  97. }
  98. /**
  99. * Execute hooks (if they were initialized) for the given method
  100. *
  101. * @param string $method Name of method hooked ('doActions', 'printSearchForm', 'showInputField', ...)
  102. * @param array $parameters Array of parameters
  103. * @param Object $object Object to use hooks on
  104. * @param string $action Action code on calling page ('create', 'edit', 'view', 'add', 'update', 'delete'...)
  105. * @return mixed For 'addreplace' hooks (doActions,formObjectOptions,pdf_xxx,...): Return 0 if we want to keep standard actions, >0 if we want to stop 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.
  106. * 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.
  107. * All types can also return some values into an array ->results that will be finaly merged into this->resArray for caller.
  108. * $this->error or this->errors are also defined by class called by this function if error.
  109. */
  110. function executeHooks($method, $parameters=array(), &$object='', &$action='')
  111. {
  112. if (! is_array($this->hooks) || empty($this->hooks)) return '';
  113. $parameters['context']=join(':',$this->contextarray);
  114. //dol_syslog(get_class($this).'::executeHooks method='.$method." action=".$action." context=".$parameters['context']);
  115. // Define type of hook ('output' or 'addreplace'. 'returnvalue' is deprecated because a 'addreplace' hook can also return resPrint and resArray).
  116. $hooktype='output';
  117. if (in_array(
  118. $method,
  119. array(
  120. 'addCalendarChoice',
  121. 'addMoreActionsButtons',
  122. 'addMoreMassActions',
  123. 'addSearchEntry',
  124. 'addStatisticLine',
  125. 'deleteFile',
  126. 'doActions',
  127. 'doMassActions',
  128. 'formCreateThirdpartyOptions',
  129. 'formObjectOptions',
  130. 'formattachOptions',
  131. 'formBuilddocLineOptions',
  132. 'formatNotificationMessage',
  133. 'getFormMail',
  134. 'getIdProfUrl',
  135. 'getDirList',
  136. 'moveUploadedFile',
  137. 'pdf_build_address',
  138. 'pdf_writelinedesc',
  139. 'pdf_getlinenum',
  140. 'pdf_getlineref',
  141. 'pdf_getlineref_supplier',
  142. 'pdf_getlinevatrate',
  143. 'pdf_getlineupexcltax',
  144. 'pdf_getlineupwithtax',
  145. 'pdf_getlineqty',
  146. 'pdf_getlineqty_asked',
  147. 'pdf_getlineqty_shipped',
  148. 'pdf_getlineqty_keeptoship',
  149. 'pdf_getlineunit',
  150. 'pdf_getlineremisepercent',
  151. 'pdf_getlineprogress',
  152. 'pdf_getlinetotalexcltax',
  153. 'pdf_getlinetotalwithtax',
  154. 'paymentsupplierinvoices',
  155. 'printAddress',
  156. 'printSearchForm',
  157. 'printTabsHead',
  158. 'formatEvent',
  159. 'printObjectLine',
  160. 'printObjectSubLine',
  161. 'createDictionaryFieldList',
  162. 'editDictionaryFieldlist',
  163. 'getFormMail',
  164. 'showLinkToObjectBlock'
  165. )
  166. )) $hooktype='addreplace';
  167. if ($method == 'insertExtraFields')
  168. {
  169. $hooktype='returnvalue'; // @deprecated. TODO Remove all code with "executeHooks('insertExtraFields'" as soon as there is a trigger available.
  170. dol_syslog("Warning: The hook 'insertExtraFields' is deprecated and must not be used. Use instead trigger on CRUD event (ask it to dev team if not implemented)", LOG_WARNING);
  171. }
  172. // Init return properties
  173. $this->resPrint=''; $this->resArray=array(); $this->resNbOfHooks=0;
  174. // Loop on each hook to qualify modules that have declared context
  175. $modulealreadyexecuted=array();
  176. $resaction=0; $error=0; $result='';
  177. 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
  178. {
  179. if (! empty($modules))
  180. {
  181. foreach($modules as $module => $actionclassinstance)
  182. {
  183. //print "Before hook ".get_class($actionclassinstance)." method=".$method." hooktype=".$hooktype." results=".count($actionclassinstance->results)." resprints=".count($actionclassinstance->resprints)." resaction=".$resaction." result=".$result."<br>\n";
  184. // test to avoid running twice a hook, when a module implements several active contexts
  185. if (in_array($module,$modulealreadyexecuted)) continue;
  186. // jump to next module/class if method does not exist
  187. if (! method_exists($actionclassinstance,$method)) continue;
  188. $this->resNbOfHooks++;
  189. dol_syslog(get_class($this).'::executeHooks a qualified hook was found for method='.$method.' module='.$module." action=".$action." context=".$context);
  190. $modulealreadyexecuted[$module]=$module; // Use the $currentcontext in method to avoid running twice
  191. // Clean class (an error may have been set from a previous call of another method for same module/hook)
  192. $actionclassinstance->error=0;
  193. $actionclassinstance->errors=array();
  194. // Add current context to avoid method execution in bad context, you can add this test in your method : eg if($currentcontext != 'formfile') return;
  195. $parameters['currentcontext'] = $context;
  196. // Hooks that must return int (hooks with type 'addreplace')
  197. if ($hooktype == 'addreplace')
  198. {
  199. dol_syslog("Call method ".$method." of class ".get_class($actionclassinstance).", module=".$module.", hooktype=".$hooktype, LOG_DEBUG);
  200. $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)
  201. if ($resaction < 0 || ! empty($actionclassinstance->error) || (! empty($actionclassinstance->errors) && count($actionclassinstance->errors) > 0))
  202. {
  203. $error++;
  204. $this->error=$actionclassinstance->error; $this->errors=array_merge($this->errors, (array) $actionclassinstance->errors);
  205. 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);
  206. }
  207. if (isset($actionclassinstance->results) && is_array($actionclassinstance->results)) $this->resArray =array_merge($this->resArray, $actionclassinstance->results);
  208. if (! empty($actionclassinstance->resprints)) $this->resPrint.=$actionclassinstance->resprints;
  209. }
  210. // Generic hooks that return a string or array (printLeftBlock, formAddObjectLine, formBuilddocOptions, ...)
  211. else
  212. {
  213. // TODO. this test should be done into the method of hook by returning nothing
  214. if (is_array($parameters) && ! empty($parameters['special_code']) && $parameters['special_code'] > 3 && $parameters['special_code'] != $actionclassinstance->module_number) continue;
  215. //dol_syslog("Call method ".$method." of class ".get_class($actionclassinstance).", module=".$module.", hooktype=".$hooktype, LOG_DEBUG);
  216. $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)
  217. if (! empty($actionclassinstance->results) && is_array($actionclassinstance->results)) $this->resArray =array_merge($this->resArray, $actionclassinstance->results);
  218. if (! empty($actionclassinstance->resprints)) $this->resPrint.=$actionclassinstance->resprints;
  219. // 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
  220. if (! is_array($resaction) && ! is_numeric($resaction))
  221. {
  222. 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);
  223. if (empty($actionclassinstance->resprints)) { $this->resPrint.=$resaction; $resaction=0; }
  224. }
  225. }
  226. //print "After hook ".get_class($actionclassinstance)." method=".$method." hooktype=".$hooktype." results=".count($actionclassinstance->results)." resprints=".count($actionclassinstance->resprints)." resaction=".$resaction." result=".$result."<br>\n";
  227. unset($actionclassinstance->results);
  228. unset($actionclassinstance->resprints);
  229. }
  230. }
  231. }
  232. return ($error?-1:$resaction);
  233. }
  234. }