|
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
|
|
|
|