dolibarr  17.0.4
hookmanager.class.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (C) 2010-2016 Laurent Destailleur <eldy@users.sourceforge.net>
3  * Copyright (C) 2010-2014 Regis Houssin <regis.houssin@inodbox.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 <https://www.gnu.org/licenses/>.
18  */
19 
31 {
35  public $db;
36 
40  public $error = '';
41 
45  public $errors = array();
46 
47  // Context hookmanager was created for ('thirdpartycard', 'thirdpartydao', ...)
48  public $contextarray = array();
49 
50  // Array with instantiated classes
51  public $hooks = array();
52 
53  // Array result
54  public $resArray = array();
55  // Printable result
56  public $resPrint = '';
57  // Nb of qualified hook ran
58  public $resNbOfHooks = 0;
59 
65  public function __construct($db)
66  {
67  $this->db = $db;
68  }
69 
70 
82  public function initHooks($arraycontext)
83  {
84  global $conf;
85 
86  // Test if there is hooks to manage
87  if (!is_array($conf->modules_parts['hooks']) || empty($conf->modules_parts['hooks'])) {
88  return;
89  }
90 
91  // For backward compatibility
92  if (!is_array($arraycontext)) {
93  $arraycontext = array($arraycontext);
94  }
95 
96  $this->contextarray = array_unique(array_merge($arraycontext, $this->contextarray)); // All contexts are concatenated
97 
98  $arraytolog = array();
99  foreach ($conf->modules_parts['hooks'] as $module => $hooks) { // Loop on each module that brings hooks
100  if (empty($conf->$module->enabled)) {
101  continue;
102  }
103 
104  //dol_syslog(get_class($this).'::initHooks module='.$module.' arraycontext='.join(',',$arraycontext));
105  foreach ($arraycontext as $context) {
106  if (is_array($hooks)) {
107  $arrayhooks = $hooks; // New system
108  } else {
109  $arrayhooks = explode(':', $hooks); // Old system (for backward compatibility)
110  }
111 
112  if (in_array($context, $arrayhooks) || in_array('all', $arrayhooks)) { // We instantiate action class only if initialized hook is handled by module
113  // Include actions class overwriting hooks
114  if (empty($this->hooks[$context][$module]) || !is_object($this->hooks[$context][$module])) { // If set to an object value, class was already loaded so we do nothing.
115  $path = '/'.$module.'/class/';
116  $actionfile = 'actions_'.$module.'.class.php';
117 
118  $arraytolog[] = 'context='.$context.'-path='.$path.$actionfile;
119  $resaction = dol_include_once($path.$actionfile);
120  if ($resaction) {
121  $controlclassname = 'Actions'.ucfirst($module);
122  $actionInstance = new $controlclassname($this->db);
123  $priority = empty($actionInstance->priority) ? 50 : $actionInstance->priority;
124  $this->hooks[$context][$priority.':'.$module] = $actionInstance;
125  }
126  }
127  }
128  }
129  }
130  // Log the init of hook but only for hooks thare are declared to be managed
131  if (count($arraytolog) > 0) {
132  dol_syslog(get_class($this)."::initHooks Loading hooks: ".join(', ', $arraytolog), LOG_DEBUG);
133  }
134 
135  foreach ($arraycontext as $context) {
136  if (!empty($this->hooks[$context])) {
137  ksort($this->hooks[$context], SORT_NATURAL);
138  }
139  }
140 
141  return 1;
142  }
143 
156  public function executeHooks($method, $parameters = array(), &$object = '', &$action = '')
157  {
158  if (!is_array($this->hooks) || empty($this->hooks)) {
159  return 0; // No hook available, do nothing.
160  }
161 
162  $parameters['context'] = join(':', $this->contextarray);
163  //dol_syslog(get_class($this).'::executeHooks method='.$method." action=".$action." context=".$parameters['context']);
164 
165  // Define type of hook ('output' or 'addreplace').
166  // TODO Remove hooks with type 'output' (exemple getNomUrl). All hooks must be converted into 'addreplace' hooks.
167  $hooktype = 'output';
168  if (in_array(
169  $method,
170  array(
171  'addCalendarChoice',
172  'addCalendarView',
173  'addMoreActionsButtons',
174  'addMoreMassActions',
175  'addSearchEntry',
176  'addStatisticLine',
177  'addSectionECMAuto',
178  'checkSecureAccess',
179  'createDictionaryFieldlist',
180  'editDictionaryFieldlist',
181  'getFormMail',
182  'deleteFile',
183  'doActions',
184  'doMassActions',
185  'formatEvent',
186  'formConfirm',
187  'formCreateThirdpartyOptions',
188  'formObjectOptions',
189  'formattachOptions',
190  'formBuilddocLineOptions',
191  'formatNotificationMessage',
192  'formConfirm',
193  'getAccessForbiddenMessage',
194  'getDirList',
195  'hookGetEntity',
196  'getFormMail',
197  'getFormatedCustomerRef',
198  'getFormatedSupplierRef',
199  'getIdProfUrl',
200  'getInputIdProf',
201  'menuDropdownQuickaddItems',
202  'menuLeftMenuItems',
203  'moveUploadedFile',
204  'moreHtmlStatus',
205  'pdf_build_address',
206  'pdf_writelinedesc',
207  'pdf_getlinenum',
208  'pdf_getlineref',
209  'pdf_getlineref_supplier',
210  'pdf_getlinevatrate',
211  'pdf_getlineupexcltax',
212  'pdf_getlineupwithtax',
213  'pdf_getlineqty',
214  'pdf_getlineqty_asked',
215  'pdf_getlineqty_shipped',
216  'pdf_getlineqty_keeptoship',
217  'pdf_getlineunit',
218  'pdf_getlineremisepercent',
219  'pdf_getlineprogress',
220  'pdf_getlinetotalexcltax',
221  'pdf_getlinetotalwithtax',
222  'paymentsupplierinvoices',
223  'printAddress',
224  'printEmail',
225  'printSearchForm',
226  'printTabsHead',
227  'printObjectLine',
228  'printObjectSubLine',
229  'restrictedArea',
230  'sendMail',
231  'sendMailAfter',
232  'showOptionals',
233  'showLinkToObjectBlock',
234  'setContentSecurityPolicy',
235  'setHtmlTitle',
236  'completeTabsHead',
237  'formDolBanner',
238  'displayMarginInfos',
239  )
240  )) {
241  $hooktype = 'addreplace';
242  }
243 
244  // Init return properties
245  $this->resPrint = '';
246  $this->resArray = array();
247  $this->resNbOfHooks = 0;
248 
249  // Here, the value for $method and $hooktype are given.
250  // Loop on each hook to qualify modules that have declared context
251  $modulealreadyexecuted = array();
252  $resaction = 0;
253  $error = 0;
254  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
255  if (!empty($modules)) {
256  // Loop on each active hooks of module for this context
257  foreach ($modules as $module => $actionclassinstance) {
258  $module = preg_replace('/^\d+:/', '', $module);
259  //print "Before hook ".get_class($actionclassinstance)." method=".$method." module=".$module." hooktype=".$hooktype." results=".count($actionclassinstance->results)." resprints=".count($actionclassinstance->resprints)." resaction=".$resaction."<br>\n";
260 
261  // test to avoid running twice a hook, when a module implements several active contexts
262  if (in_array($module, $modulealreadyexecuted)) {
263  continue;
264  }
265 
266  // jump to next module/class if method does not exist
267  if (!method_exists($actionclassinstance, $method)) {
268  continue;
269  }
270 
271  $this->resNbOfHooks++;
272 
273  $modulealreadyexecuted[$module] = $module;
274 
275  // Clean class (an error may have been set from a previous call of another method for same module/hook)
276  $actionclassinstance->error = 0;
277  $actionclassinstance->errors = array();
278 
279  if (getDolGlobalInt('MAIN_DEBUG_SHOW_EACH_QUALIFIED_HOOK_CALL') >= 2) {
280  // This his too much verbose, enabled in develop only
281  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')) ? ', object id='.$object->id : '').((is_object($object) && property_exists($object, 'element')) ? ', object element='.$object->element : ''), LOG_DEBUG);
282  }
283 
284  // Add current context to avoid method execution in bad context, you can add this test in your method : eg if($currentcontext != 'formfile') return;
285  // Note: The hook can use the $currentcontext in its code to avoid to be ran twice or be ran for one given context only
286  $parameters['currentcontext'] = $context;
287  // Hooks that must return int (hooks with type 'addreplace')
288  if ($hooktype == 'addreplace') {
289  $resactiontmp = $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)
290  $resaction += $resactiontmp;
291 
292  if ($resactiontmp < 0 || !empty($actionclassinstance->error) || (!empty($actionclassinstance->errors) && count($actionclassinstance->errors) > 0)) {
293  $error++;
294  $this->error = $actionclassinstance->error;
295  $this->errors = array_merge($this->errors, (array) $actionclassinstance->errors);
296  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);
297  }
298 
299  if (isset($actionclassinstance->results) && is_array($actionclassinstance->results)) {
300  if ($resactiontmp > 0) {
301  $this->resArray = $actionclassinstance->results;
302  } else {
303  $this->resArray = array_merge($this->resArray, $actionclassinstance->results);
304  }
305  }
306  if (!empty($actionclassinstance->resprints)) {
307  if ($resactiontmp > 0) {
308  $this->resPrint = $actionclassinstance->resprints;
309  } else {
310  $this->resPrint .= $actionclassinstance->resprints;
311  }
312  }
313  } else {
314  // Generic hooks that return a string or array (printLeftBlock, formAddObjectLine, formBuilddocOptions, ...)
315 
316  // TODO. this test should be done into the method of hook by returning nothing
317  if (is_array($parameters) && !empty($parameters['special_code']) && $parameters['special_code'] > 3 && $parameters['special_code'] != $actionclassinstance->module_number) {
318  continue;
319  }
320 
321  //dol_syslog("Call method ".$method." of class ".get_class($actionclassinstance).", module=".$module.", hooktype=".$hooktype, LOG_DEBUG);
322  $resactiontmp = $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)
323  $resaction += $resactiontmp;
324 
325  if (!empty($actionclassinstance->results) && is_array($actionclassinstance->results)) {
326  $this->resArray = array_merge($this->resArray, $actionclassinstance->results);
327  }
328  if (!empty($actionclassinstance->resprints)) {
329  $this->resPrint .= $actionclassinstance->resprints;
330  }
331  if (is_numeric($resactiontmp) && $resactiontmp < 0) {
332  $error++;
333  $this->error = $actionclassinstance->error;
334  $this->errors = array_merge($this->errors, (array) $actionclassinstance->errors);
335  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);
336  }
337 
338  // TODO dead code to remove (do not disable this, but fix your hook instead): result must not be a string but an int. you must use $actionclassinstance->resprints to return a string
339  if (!is_array($resactiontmp) && !is_numeric($resactiontmp)) {
340  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);
341  if (empty($actionclassinstance->resprints)) {
342  $this->resPrint .= $resactiontmp;
343  }
344  }
345  }
346 
347  //print "After hook context=".$context." ".get_class($actionclassinstance)." method=".$method." hooktype=".$hooktype." results=".count($actionclassinstance->results)." resprints=".count($actionclassinstance->resprints)." resaction=".$resaction."<br>\n";
348 
349  unset($actionclassinstance->results);
350  unset($actionclassinstance->resprints);
351  }
352  }
353  }
354 
355  return ($error ? -1 : $resaction);
356  }
357 }
Class to manage hooks.
initHooks($arraycontext)
Init array $this->hooks with instantiated action controlers.
executeHooks($method, $parameters=array(), &$object='', &$action='')
Execute hooks (if they were initialized) for the given method.
__construct($db)
Constructor.
if(!function_exists('dol_getprefix')) dol_include_once($relpath, $classname='')
Make an include_once using default root and alternate root if it fails.
getDolGlobalInt($key, $default=0)
Return dolibarr global constant int value.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
$conf db
API class for accounts.
Definition: inc.php:41