1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* Copyright (C) 2010-2016 Laurent Destailleur <[email protected]> |
4
|
|
|
* Copyright (C) 2010-2014 Regis Houssin <[email protected]> |
5
|
|
|
* Copyright (C) 2010-2011 Juanjo Menent <[email protected]> |
6
|
|
|
* |
7
|
|
|
* This program is free software; you can redistribute it and/or modify |
8
|
|
|
* it under the terms of the GNU General Public License as published by |
9
|
|
|
* the Free Software Foundation; either version 3 of the License, or |
10
|
|
|
* (at your option) any later version. |
11
|
|
|
* |
12
|
|
|
* This program is distributed in the hope that it will be useful, |
13
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
14
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15
|
|
|
* GNU General Public License for more details. |
16
|
|
|
* |
17
|
|
|
* You should have received a copy of the GNU General Public License |
18
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>. |
19
|
|
|
*/ |
20
|
|
|
|
21
|
|
|
namespace DoliCore\Lib; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* \file htdocs/core/class/hookmanager.class.php |
25
|
|
|
* \ingroup core |
26
|
|
|
* \brief File of class to manage hooks |
27
|
|
|
*/ |
28
|
|
|
|
29
|
|
|
|
30
|
|
|
use DoliDB; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* Class to manage hooks |
34
|
|
|
*/ |
35
|
|
|
class HookManager |
36
|
|
|
{ |
37
|
|
|
/** |
38
|
|
|
* @var DoliDB Database handler. |
39
|
|
|
*/ |
40
|
|
|
public $db; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* @var string Error code (or message) |
44
|
|
|
*/ |
45
|
|
|
public $error = ''; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* @var string[] Error codes (or messages) |
49
|
|
|
*/ |
50
|
|
|
public $errors = array(); |
51
|
|
|
|
52
|
|
|
// Context hookmanager was created for ('thirdpartycard', 'thirdpartydao', ...) |
53
|
|
|
public $contextarray = array(); |
54
|
|
|
|
55
|
|
|
// Array with instantiated classes |
56
|
|
|
public $hooks = array(); |
57
|
|
|
|
58
|
|
|
// Array result |
59
|
|
|
public $resArray = array(); |
60
|
|
|
// Printable result |
61
|
|
|
public $resPrint = ''; |
62
|
|
|
// Nb of qualified hook ran |
63
|
|
|
public $resNbOfHooks = 0; |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* Constructor |
67
|
|
|
* |
68
|
|
|
* @param DoliDB $db Database handler |
69
|
|
|
*/ |
70
|
|
|
public function __construct($db) |
71
|
|
|
{ |
72
|
|
|
$this->db = $db; |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* Init array $this->hooks with instantiated action controllers. |
78
|
|
|
* 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. |
79
|
|
|
* This makes $conf->hooks_modules loaded with an entry ('modulename'=>array(nameofcontext1,nameofcontext2,...)) |
80
|
|
|
* When initHooks function is called, with initHooks(list_of_contexts), an array $this->hooks is defined with instance of controller |
81
|
|
|
* class found into file /mymodule/class/actions_mymodule.class.php (if module has declared the context as a managed context). |
82
|
|
|
* Then when a hook executeHooks('aMethod'...) is called, the method aMethod found into class will be executed. |
83
|
|
|
* |
84
|
|
|
* @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), ... |
85
|
|
|
* @return int 0 or 1 |
86
|
|
|
*/ |
87
|
|
|
public function initHooks($arraycontext) |
88
|
|
|
{ |
89
|
|
|
global $conf; |
90
|
|
|
|
91
|
|
|
// Test if there is hooks to manage |
92
|
|
|
if (!is_array($conf->modules_parts['hooks']) || empty($conf->modules_parts['hooks'])) { |
93
|
|
|
return 0; |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
// For backward compatibility |
97
|
|
|
if (!is_array($arraycontext)) { |
98
|
|
|
$arraycontext = array($arraycontext); |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
$this->contextarray = array_unique(array_merge($arraycontext, $this->contextarray)); // All contexts are concatenated |
102
|
|
|
|
103
|
|
|
$arraytolog = array(); |
104
|
|
|
foreach ($conf->modules_parts['hooks'] as $module => $hooks) { // Loop on each module that brings hooks |
105
|
|
|
if (empty($conf->$module->enabled)) { |
106
|
|
|
continue; |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
//dol_syslog(get_class($this).'::initHooks module='.$module.' arraycontext='.join(',',$arraycontext)); |
110
|
|
|
foreach ($arraycontext as $context) { |
111
|
|
|
if (is_array($hooks)) { |
112
|
|
|
$arrayhooks = $hooks; // New system |
113
|
|
|
} else { |
114
|
|
|
$arrayhooks = explode(':', $hooks); // Old system (for backward compatibility) |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
if (in_array($context, $arrayhooks) || in_array('all', $arrayhooks)) { // We instantiate action class only if initialized hook is handled by module |
118
|
|
|
// Include actions class overwriting hooks |
119
|
|
|
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. |
120
|
|
|
$path = '/' . $module . '/class/'; |
121
|
|
|
$actionfile = 'actions_' . $module . '.class.php'; |
122
|
|
|
|
123
|
|
|
$arraytolog[] = 'context=' . $context . '-path=' . $path . $actionfile; |
124
|
|
|
$resaction = dol_include_once($path . $actionfile); |
125
|
|
|
if ($resaction) { |
126
|
|
|
$controlclassname = 'Actions' . ucfirst($module); |
127
|
|
|
$actionInstance = new $controlclassname($this->db); |
128
|
|
|
$priority = empty($actionInstance->priority) ? 50 : $actionInstance->priority; |
129
|
|
|
$this->hooks[$context][$priority . ':' . $module] = $actionInstance; |
130
|
|
|
} |
131
|
|
|
} |
132
|
|
|
} |
133
|
|
|
} |
134
|
|
|
} |
135
|
|
|
// Log the init of hook but only for hooks there are declared to be managed |
136
|
|
|
if (count($arraytolog) > 0) { |
137
|
|
|
dol_syslog(get_class($this) . "::initHooks Loading hooks: " . implode(', ', $arraytolog), LOG_DEBUG); |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
foreach ($arraycontext as $context) { |
141
|
|
|
if (!empty($this->hooks[$context])) { |
142
|
|
|
ksort($this->hooks[$context], SORT_NATURAL); |
143
|
|
|
} |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
return 1; |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
/** |
150
|
|
|
* Execute hooks (if they were initialized) for the given method |
151
|
|
|
* |
152
|
|
|
* @param string $method Name of method hooked ('doActions', 'printSearchForm', 'showInputField', ...) |
153
|
|
|
* @param array $parameters Array of parameters |
154
|
|
|
* @param Object $object Object to use hooks on |
155
|
|
|
* @param string $action Action code on calling page ('create', 'edit', 'view', 'add', 'update', 'delete'...) |
156
|
|
|
* @return int 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. |
157
|
|
|
* For 'output' hooks (printLeftBlock, formAddObjectLine, formBuilddocOptions, ...): Return 0 if we want to keep standard actions, >0 uf we want to stop/replace standard actions (at least one > 0 and replacement will be done), <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. |
158
|
|
|
* All types can also return some values into an array ->results that will be merged into this->resArray for caller. |
159
|
|
|
* $this->error or this->errors are also defined by class called by this function if error. |
160
|
|
|
*/ |
161
|
|
|
public function executeHooks($method, $parameters = array(), &$object = null, &$action = '') |
162
|
|
|
{ |
163
|
|
|
if (!is_array($this->hooks) || empty($this->hooks)) { |
164
|
|
|
return 0; // No hook available, do nothing. |
165
|
|
|
} |
166
|
|
|
if (!is_array($parameters)) { |
167
|
|
|
dol_syslog('executeHooks was called with a non array $parameters. Surely a bug.', LOG_WARNING); |
168
|
|
|
$parameters = array(); |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
$parameters['context'] = implode(':', $this->contextarray); |
172
|
|
|
//dol_syslog(get_class($this).'::executeHooks method='.$method." action=".$action." context=".$parameters['context']); |
173
|
|
|
|
174
|
|
|
// Define type of hook ('output' or 'addreplace'). |
175
|
|
|
$hooktype = 'addreplace'; |
176
|
|
|
// TODO Remove hooks with type 'output' (example createFrom). All hooks must be converted into 'addreplace' hooks. |
177
|
|
|
if ( |
178
|
|
|
in_array($method, array( |
179
|
|
|
'createFrom', |
180
|
|
|
'dashboardAccountancy', |
181
|
|
|
'dashboardActivities', |
182
|
|
|
'dashboardCommercials', |
183
|
|
|
'dashboardContracts', |
184
|
|
|
'dashboardDonation', |
185
|
|
|
'dashboardEmailings', |
186
|
|
|
'dashboardExpenseReport', |
187
|
|
|
'dashboardHRM', |
188
|
|
|
'dashboardInterventions', |
189
|
|
|
'dashboardMRP', |
190
|
|
|
'dashboardMembers', |
191
|
|
|
'dashboardOpensurvey', |
192
|
|
|
'dashboardOrders', |
193
|
|
|
'dashboardOrdersSuppliers', |
194
|
|
|
'dashboardProductServices', |
195
|
|
|
'dashboardProjects', |
196
|
|
|
'dashboardPropals', |
197
|
|
|
'dashboardSpecialBills', |
198
|
|
|
'dashboardSupplierProposal', |
199
|
|
|
'dashboardThirdparties', |
200
|
|
|
'dashboardTickets', |
201
|
|
|
'dashboardUsersGroups', |
202
|
|
|
'dashboardWarehouse', |
203
|
|
|
'dashboardWarehouseReceptions', |
204
|
|
|
'dashboardWarehouseSendings', |
205
|
|
|
'insertExtraHeader', |
206
|
|
|
'insertExtraFooter', |
207
|
|
|
'printLeftBlock', |
208
|
|
|
'formAddObjectLine', |
209
|
|
|
'formBuilddocOptions', |
210
|
|
|
'showSocinfoOnPrint' |
211
|
|
|
)) |
212
|
|
|
) { |
213
|
|
|
$hooktype = 'output'; |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
// Init return properties |
217
|
|
|
$localResPrint = ''; |
218
|
|
|
$localResArray = array(); |
219
|
|
|
|
220
|
|
|
$this->resNbOfHooks = 0; |
221
|
|
|
|
222
|
|
|
// Here, the value for $method and $hooktype are given. |
223
|
|
|
// Loop on each hook to qualify modules that have declared context |
224
|
|
|
$modulealreadyexecuted = array(); |
225
|
|
|
$resaction = 0; |
226
|
|
|
$error = 0; |
227
|
|
|
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 |
228
|
|
|
if (!empty($modules)) { |
229
|
|
|
// Loop on each active hooks of module for this context |
230
|
|
|
foreach ($modules as $module => $actionclassinstance) { |
231
|
|
|
$module = preg_replace('/^\d+:/', '', $module); |
232
|
|
|
//print "Before hook ".get_class($actionclassinstance)." method=".$method." module=".$module." hooktype=".$hooktype." results=".count($actionclassinstance->results)." resprints=".count($actionclassinstance->resprints)." resaction=".$resaction."<br>\n"; |
233
|
|
|
|
234
|
|
|
// test to avoid running twice a hook, when a module implements several active contexts |
235
|
|
|
if (in_array($module, $modulealreadyexecuted)) { |
236
|
|
|
continue; |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
// jump to next module/class if method does not exist |
240
|
|
|
if (!method_exists($actionclassinstance, $method)) { |
241
|
|
|
continue; |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
$this->resNbOfHooks++; |
245
|
|
|
|
246
|
|
|
$modulealreadyexecuted[$module] = $module; |
247
|
|
|
|
248
|
|
|
// Clean class (an error may have been set from a previous call of another method for same module/hook) |
249
|
|
|
$actionclassinstance->error = ''; |
250
|
|
|
$actionclassinstance->errors = array(); |
251
|
|
|
|
252
|
|
|
if (getDolGlobalInt('MAIN_HOOK_DEBUG')) { |
253
|
|
|
// This his too much verbose, enabled if const enabled only |
254
|
|
|
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); |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
// Add current context to avoid method execution in bad context, you can add this test in your method : eg if($currentcontext != 'formfile') return; |
258
|
|
|
// Note: The hook can use the $currentcontext in its code to avoid to be ran twice or be ran for one given context only |
259
|
|
|
$parameters['currentcontext'] = $context; |
260
|
|
|
// Hooks that must return int (hooks with type 'addreplace') |
261
|
|
|
if ($hooktype == 'addreplace') { |
262
|
|
|
$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) |
263
|
|
|
$resaction += $resactiontmp; |
264
|
|
|
|
265
|
|
|
if ($resactiontmp < 0 || !empty($actionclassinstance->error) || (!empty($actionclassinstance->errors) && count($actionclassinstance->errors) > 0)) { |
266
|
|
|
$error++; |
267
|
|
|
$this->error = $actionclassinstance->error; |
268
|
|
|
$this->errors = array_merge($this->errors, (array)$actionclassinstance->errors); |
269
|
|
|
dol_syslog("Error on hook module=" . $module . ", method " . $method . ", class " . get_class($actionclassinstance) . ", hooktype=" . $hooktype . (empty($this->error) ? '' : " " . $this->error) . (empty($this->errors) ? '' : " " . implode(",", $this->errors)), LOG_ERR); |
270
|
|
|
} |
271
|
|
|
|
272
|
|
|
if (isset($actionclassinstance->results) && is_array($actionclassinstance->results)) { |
273
|
|
|
if ($resactiontmp > 0) { |
274
|
|
|
$localResArray = $actionclassinstance->results; |
275
|
|
|
} else { |
276
|
|
|
$localResArray = array_merge_recursive($localResArray, $actionclassinstance->results); |
277
|
|
|
} |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
if (!empty($actionclassinstance->resprints)) { |
281
|
|
|
if ($resactiontmp > 0) { |
282
|
|
|
$localResPrint = $actionclassinstance->resprints; |
283
|
|
|
} else { |
284
|
|
|
$localResPrint .= $actionclassinstance->resprints; |
285
|
|
|
} |
286
|
|
|
} |
287
|
|
|
} else { |
288
|
|
|
// Generic hooks that return a string or array (printLeftBlock, formAddObjectLine, formBuilddocOptions, ...) |
289
|
|
|
|
290
|
|
|
// TODO. this test should be done into the method of hook by returning nothing |
291
|
|
|
if (is_array($parameters) && !empty($parameters['special_code']) && $parameters['special_code'] > 3 && $parameters['special_code'] != $actionclassinstance->module_number) { |
292
|
|
|
continue; |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
if (getDolGlobalInt('MAIN_HOOK_DEBUG')) { |
296
|
|
|
dol_syslog("Call method " . $method . " of class " . get_class($actionclassinstance) . ", module=" . $module . ", hooktype=" . $hooktype, LOG_DEBUG); |
297
|
|
|
} |
298
|
|
|
|
299
|
|
|
$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) |
300
|
|
|
$resaction += $resactiontmp; |
301
|
|
|
|
302
|
|
|
if (!empty($actionclassinstance->results) && is_array($actionclassinstance->results)) { |
303
|
|
|
$localResArray = array_merge_recursive($localResArray, $actionclassinstance->results); |
304
|
|
|
} |
305
|
|
|
if (!empty($actionclassinstance->resprints)) { |
306
|
|
|
$localResPrint .= $actionclassinstance->resprints; |
307
|
|
|
} |
308
|
|
|
if (is_numeric($resactiontmp) && $resactiontmp < 0) { |
309
|
|
|
$error++; |
310
|
|
|
$this->error = $actionclassinstance->error; |
311
|
|
|
$this->errors = array_merge($this->errors, (array)$actionclassinstance->errors); |
312
|
|
|
dol_syslog("Error on hook module=" . $module . ", method " . $method . ", class " . get_class($actionclassinstance) . ", hooktype=" . $hooktype . (empty($this->error) ? '' : " " . $this->error) . (empty($this->errors) ? '' : " " . implode(",", $this->errors)), LOG_ERR); |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
// 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 |
316
|
|
|
if (!is_array($resactiontmp) && !is_numeric($resactiontmp)) { |
317
|
|
|
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); |
318
|
|
|
if (empty($actionclassinstance->resprints)) { |
319
|
|
|
$localResPrint .= $resactiontmp; |
320
|
|
|
} |
321
|
|
|
} |
322
|
|
|
} |
323
|
|
|
|
324
|
|
|
//print "After hook context=".$context." ".get_class($actionclassinstance)." method=".$method." hooktype=".$hooktype." results=".count($actionclassinstance->results)." resprints=".count($actionclassinstance->resprints)." resaction=".$resaction."<br>\n"; |
325
|
|
|
|
326
|
|
|
unset($actionclassinstance->results); |
327
|
|
|
unset($actionclassinstance->resprints); |
328
|
|
|
} |
329
|
|
|
} |
330
|
|
|
} |
331
|
|
|
|
332
|
|
|
$this->resPrint = $localResPrint; |
333
|
|
|
$this->resArray = $localResArray; |
334
|
|
|
|
335
|
|
|
return ($error ? -1 : $resaction); |
336
|
|
|
} |
337
|
|
|
} |
338
|
|
|
|