dolibarr  9.0.0
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 <http://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  var $contextarray=array();
49 
50  // Array with instantiated classes
51  var $hooks=array();
52 
53  // Array result
54  var $resArray=array();
55  // Printable result
56  var $resPrint='';
57  // Nb of qualified hook ran
58  var $resNbOfHooks=0;
59 
65  function __construct($db)
66  {
67  $this->db = $db;
68  }
69 
70 
82  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'])) return;
88 
89  // For backward compatibility
90  if (! is_array($arraycontext)) $arraycontext=array($arraycontext);
91 
92  $this->contextarray=array_unique(array_merge($arraycontext,$this->contextarray)); // All contexts are concatenated
93 
94  foreach($conf->modules_parts['hooks'] as $module => $hooks) // Loop on each module that brings hooks
95  {
96  if (empty($conf->$module->enabled)) continue;
97 
98  //dol_syslog(get_class($this).'::initHooks module='.$module.' arraycontext='.join(',',$arraycontext));
99  foreach($arraycontext as $context)
100  {
101  if (is_array($hooks)) $arrayhooks=$hooks; // New system
102  else $arrayhooks=explode(':',$hooks); // Old system (for backward compatibility)
103 
104  if (in_array($context, $arrayhooks) || in_array('all', $arrayhooks)) // We instantiate action class only if initialized hook is handled by module
105  {
106  // Include actions class overwriting hooks
107  if (! is_object($this->hooks[$context][$module])) // If set, class was already loaded
108  {
109  $path = '/'.$module.'/class/';
110  $actionfile = 'actions_'.$module.'.class.php';
111 
112  dol_syslog(get_class($this).'::initHooks Loading hook class for context '.$context.": ".$actionfile, LOG_INFO);
113  $resaction=dol_include_once($path.$actionfile);
114  if ($resaction)
115  {
116  $controlclassname = 'Actions'.ucfirst($module);
117  $actionInstance = new $controlclassname($this->db);
118  $this->hooks[$context][$module] = $actionInstance;
119  }
120  }
121  }
122  }
123  }
124  return 1;
125  }
126 
139  function executeHooks($method, $parameters=array(), &$object='', &$action='')
140  {
141  if (! is_array($this->hooks) || empty($this->hooks)) return '';
142 
143  $parameters['context']=join(':',$this->contextarray);
144  //dol_syslog(get_class($this).'::executeHooks method='.$method." action=".$action." context=".$parameters['context']);
145 
146  // Define type of hook ('output' or 'addreplace'. 'returnvalue' is deprecated because a 'addreplace' hook can also return resPrint and resArray).
147  $hooktype='output';
148  if (in_array(
149  $method,
150  array(
151  'addCalendarChoice',
152  'addMoreActionsButtons',
153  'addMoreMassActions',
154  'addSearchEntry',
155  'addStatisticLine',
156  'createDictionaryFieldlist',
157  'editDictionaryFieldlist',
158  'getFormMail',
159  'deleteFile',
160  'doActions',
161  'doMassActions',
162  'formatEvent',
163  'formCreateThirdpartyOptions',
164  'formObjectOptions',
165  'formattachOptions',
166  'formBuilddocLineOptions',
167  'formatNotificationMessage',
168  'getFormMail',
169  'getIdProfUrl',
170  'getDirList',
171  'moveUploadedFile',
172  'moreHtmlStatus',
173  'pdf_build_address',
174  'pdf_writelinedesc',
175  'pdf_getlinenum',
176  'pdf_getlineref',
177  'pdf_getlineref_supplier',
178  'pdf_getlinevatrate',
179  'pdf_getlineupexcltax',
180  'pdf_getlineupwithtax',
181  'pdf_getlineqty',
182  'pdf_getlineqty_asked',
183  'pdf_getlineqty_shipped',
184  'pdf_getlineqty_keeptoship',
185  'pdf_getlineunit',
186  'pdf_getlineremisepercent',
187  'pdf_getlineprogress',
188  'pdf_getlinetotalexcltax',
189  'pdf_getlinetotalwithtax',
190  'paymentsupplierinvoices',
191  'printAddress',
192  'printSearchForm',
193  'printTabsHead',
194  'printObjectLine',
195  'printObjectSubLine',
196  'restrictedArea',
197  'sendMail',
198  'sendMailAfter',
199  'showLinkToObjectBlock',
200  'setContentSecurityPolicy',
201  'setHtmlTitle'
202  )
203  )) $hooktype='addreplace';
204 
205  if ($method == 'insertExtraFields')
206  {
207  $hooktype='returnvalue'; // @deprecated. TODO Remove all code with "executeHooks('insertExtraFields'" as soon as there is a trigger available.
208  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);
209  }
210 
211  // Init return properties
212  $this->resPrint=''; $this->resArray=array(); $this->resNbOfHooks=0;
213 
214  // Loop on each hook to qualify modules that have declared context
215  $modulealreadyexecuted=array();
216  $resaction=0; $error=0; $result='';
217  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
218  {
219  if (! empty($modules))
220  {
221  foreach($modules as $module => $actionclassinstance)
222  {
223  //print "Before hook ".get_class($actionclassinstance)." method=".$method." hooktype=".$hooktype." results=".count($actionclassinstance->results)." resprints=".count($actionclassinstance->resprints)." resaction=".$resaction." result=".$result."<br>\n";
224 
225  // test to avoid running twice a hook, when a module implements several active contexts
226  if (in_array($module,$modulealreadyexecuted)) continue;
227 
228  // jump to next module/class if method does not exist
229  if (! method_exists($actionclassinstance,$method)) continue;
230 
231  $this->resNbOfHooks++;
232 
233  $modulealreadyexecuted[$module]=$module; // Use the $currentcontext in method to avoid running twice
234 
235  // Clean class (an error may have been set from a previous call of another method for same module/hook)
236  $actionclassinstance->error=0;
237  $actionclassinstance->errors=array();
238 
239  dol_syslog(get_class($this)."::executeHooks Qualified hook found (hooktype=".$hooktype."). We call method ".$method." of class ".get_class($actionclassinstance).", module=".$module.", action=".$action." context=".$context, LOG_DEBUG);
240 
241  // Add current context to avoid method execution in bad context, you can add this test in your method : eg if($currentcontext != 'formfile') return;
242  $parameters['currentcontext'] = $context;
243  // Hooks that must return int (hooks with type 'addreplace')
244  if ($hooktype == 'addreplace')
245  {
246  $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)
247  if ($resaction < 0 || ! empty($actionclassinstance->error) || (! empty($actionclassinstance->errors) && count($actionclassinstance->errors) > 0))
248  {
249  $error++;
250  $this->error=$actionclassinstance->error; $this->errors=array_merge($this->errors, (array) $actionclassinstance->errors);
251  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);
252  }
253 
254  if (isset($actionclassinstance->results) && is_array($actionclassinstance->results)) $this->resArray =array_merge($this->resArray, $actionclassinstance->results);
255  if (! empty($actionclassinstance->resprints)) $this->resPrint.=$actionclassinstance->resprints;
256  }
257  // Generic hooks that return a string or array (printLeftBlock, formAddObjectLine, formBuilddocOptions, ...)
258  else
259  {
260  // TODO. this test should be done into the method of hook by returning nothing
261  if (is_array($parameters) && ! empty($parameters['special_code']) && $parameters['special_code'] > 3 && $parameters['special_code'] != $actionclassinstance->module_number) continue;
262 
263  //dol_syslog("Call method ".$method." of class ".get_class($actionclassinstance).", module=".$module.", hooktype=".$hooktype, LOG_DEBUG);
264  $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)
265 
266  if (! empty($actionclassinstance->results) && is_array($actionclassinstance->results)) $this->resArray =array_merge($this->resArray, $actionclassinstance->results);
267  if (! empty($actionclassinstance->resprints)) $this->resPrint.=$actionclassinstance->resprints;
268  // 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
269  if (! is_array($resaction) && ! is_numeric($resaction))
270  {
271  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);
272  if (empty($actionclassinstance->resprints)) { $this->resPrint.=$resaction; $resaction=0; }
273  }
274  }
275 
276  //print "After hook ".get_class($actionclassinstance)." method=".$method." hooktype=".$hooktype." results=".count($actionclassinstance->results)." resprints=".count($actionclassinstance->resprints)." resaction=".$resaction." result=".$result."<br>\n";
277 
278  unset($actionclassinstance->results);
279  unset($actionclassinstance->resprints);
280  }
281  }
282  }
283 
284  return ($error?-1:$resaction);
285  }
286 }
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.
Class to manage hooks.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='')
Write log message into outputs.
if(! function_exists('dol_getprefix')) dol_include_once($relpath, $classname='')
Make an include_once using default root and alternate root if it fails.
__construct($db)
Constructor.