dolibarr 19.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 0;
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 = null, &$action = '')
157 {
158 if (!is_array($this->hooks) || empty($this->hooks)) {
159 return 0; // No hook available, do nothing.
160 }
161 if (!is_array($parameters)) {
162 dol_syslog('executeHooks was called with a non array $parameters. Surely a bug.', LOG_WARNING);
163 $parameters = array();
164 }
165
166 $parameters['context'] = join(':', $this->contextarray);
167 //dol_syslog(get_class($this).'::executeHooks method='.$method." action=".$action." context=".$parameters['context']);
168
169 // Define type of hook ('output' or 'addreplace').
170 $hooktype = 'addreplace';
171 // TODO Remove hooks with type 'output' (exemple createFrom). All hooks must be converted into 'addreplace' hooks.
172 if (in_array($method, array(
173 'createFrom',
174 'dashboardAccountancy',
175 'dashboardActivities',
176 'dashboardCommercials',
177 'dashboardContracts',
178 'dashboardDonation',
179 'dashboardEmailings',
180 'dashboardExpenseReport',
181 'dashboardHRM',
182 'dashboardInterventions',
183 'dashboardMRP',
184 'dashboardMembers',
185 'dashboardOpensurvey',
186 'dashboardOrders',
187 'dashboardOrdersSuppliers',
188 'dashboardProductServices',
189 'dashboardProjects',
190 'dashboardPropals',
191 'dashboardSpecialBills',
192 'dashboardSupplierProposal',
193 'dashboardThirdparties',
194 'dashboardTickets',
195 'dashboardUsersGroups',
196 'dashboardWarehouse',
197 'dashboardWarehouseReceptions',
198 'dashboardWarehouseSendings',
199 'insertExtraHeader',
200 'insertExtraFooter',
201 'printLeftBlock',
202 'formAddObjectLine',
203 'formBuilddocOptions',
204 'showSocinfoOnPrint'
205 ))) {
206 $hooktype = 'output';
207 }
208
209 // Init return properties
210 $localResPrint = '';
211 $localResArray = array();
212 $this->resArray = array();
213 $this->resNbOfHooks = 0;
214
215 // Here, the value for $method and $hooktype are given.
216 // Loop on each hook to qualify modules that have declared context
217 $modulealreadyexecuted = array();
218 $resaction = 0;
219 $error = 0;
220 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
221 if (!empty($modules)) {
222 // Loop on each active hooks of module for this context
223 foreach ($modules as $module => $actionclassinstance) {
224 $module = preg_replace('/^\d+:/', '', $module);
225 //print "Before hook ".get_class($actionclassinstance)." method=".$method." module=".$module." hooktype=".$hooktype." results=".count($actionclassinstance->results)." resprints=".count($actionclassinstance->resprints)." resaction=".$resaction."<br>\n";
226
227 // test to avoid running twice a hook, when a module implements several active contexts
228 if (in_array($module, $modulealreadyexecuted)) {
229 continue;
230 }
231
232 // jump to next module/class if method does not exist
233 if (!method_exists($actionclassinstance, $method)) {
234 continue;
235 }
236
237 $this->resNbOfHooks++;
238
239 $modulealreadyexecuted[$module] = $module;
240
241 // Clean class (an error may have been set from a previous call of another method for same module/hook)
242 $actionclassinstance->error = 0;
243 $actionclassinstance->errors = array();
244
245 if (getDolGlobalInt('MAIN_HOOK_DEBUG')) {
246 // This his too much verbose, enabled if const enabled only
247 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);
248 }
249
250 // Add current context to avoid method execution in bad context, you can add this test in your method : eg if($currentcontext != 'formfile') return;
251 // Note: The hook can use the $currentcontext in its code to avoid to be ran twice or be ran for one given context only
252 $parameters['currentcontext'] = $context;
253 // Hooks that must return int (hooks with type 'addreplace')
254 if ($hooktype == 'addreplace') {
255 $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)
256 $resaction += $resactiontmp;
257
258 if ($resactiontmp < 0 || !empty($actionclassinstance->error) || (!empty($actionclassinstance->errors) && count($actionclassinstance->errors) > 0)) {
259 $error++;
260 $this->error = $actionclassinstance->error;
261 $this->errors = array_merge($this->errors, (array) $actionclassinstance->errors);
262 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);
263 }
264
265 if (isset($actionclassinstance->results) && is_array($actionclassinstance->results)) {
266 if ($resactiontmp > 0) {
267 $localResArray = $actionclassinstance->results;
268 } else {
269 $localResArray = array_merge($localResArray, $actionclassinstance->results);
270 }
271 }
272 if (!empty($actionclassinstance->resprints)) {
273 if ($resactiontmp > 0) {
274 $localResPrint = $actionclassinstance->resprints;
275 } else {
276 $localResPrint .= $actionclassinstance->resprints;
277 }
278 }
279 } else {
280 // Generic hooks that return a string or array (printLeftBlock, formAddObjectLine, formBuilddocOptions, ...)
281
282 // TODO. this test should be done into the method of hook by returning nothing
283 if (is_array($parameters) && !empty($parameters['special_code']) && $parameters['special_code'] > 3 && $parameters['special_code'] != $actionclassinstance->module_number) {
284 continue;
285 }
286
287 if (getDolGlobalInt('MAIN_HOOK_DEBUG')) {
288 dol_syslog("Call method ".$method." of class ".get_class($actionclassinstance).", module=".$module.", hooktype=".$hooktype, LOG_DEBUG);
289 }
290
291 $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)
292 $resaction += $resactiontmp;
293
294 if (!empty($actionclassinstance->results) && is_array($actionclassinstance->results)) {
295 $localResArray = array_merge($localResArray, $actionclassinstance->results);
296 }
297 if (!empty($actionclassinstance->resprints)) {
298 $localResPrint .= $actionclassinstance->resprints;
299 }
300 if (is_numeric($resactiontmp) && $resactiontmp < 0) {
301 $error++;
302 $this->error = $actionclassinstance->error;
303 $this->errors = array_merge($this->errors, (array) $actionclassinstance->errors);
304 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);
305 }
306
307 // 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
308 if (!is_array($resactiontmp) && !is_numeric($resactiontmp)) {
309 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);
310 if (empty($actionclassinstance->resprints)) {
311 $localResPrint .= $resactiontmp;
312 }
313 }
314 }
315
316 //print "After hook context=".$context." ".get_class($actionclassinstance)." method=".$method." hooktype=".$hooktype." results=".count($actionclassinstance->results)." resprints=".count($actionclassinstance->resprints)." resaction=".$resaction."<br>\n";
317
318 unset($actionclassinstance->results);
319 unset($actionclassinstance->resprints);
320 }
321 }
322 }
323
324 $this->resPrint = $localResPrint;
325 $this->resArray = $localResArray;
326
327 return ($error ? -1 : $resaction);
328 }
329}
Class to manage hooks.
initHooks($arraycontext)
Init array $this->hooks with instantiated action controlers.
__construct($db)
Constructor.
executeHooks($method, $parameters=array(), &$object=null, &$action='')
Execute hooks (if they were initialized) for the given method.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
if(!function_exists( 'dol_getprefix')) dol_include_once($relpath, $classname='')
Make an include_once using default root and alternate root if it fails.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.