1 | <?php |
||
2 | /* Copyright (C) 2018 Alxarafe <[email protected]> |
||
3 | * |
||
4 | * This program is free software; you can redistribute it and/or modify |
||
5 | * it under the terms of the GNU General Public License as published by |
||
6 | * the Free Software Foundation; either version 3 of the License, or |
||
7 | * (at your option) any later version. |
||
8 | * |
||
9 | * This program is distributed in the hope that it will be useful, |
||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
12 | * GNU General Public License for more details. |
||
13 | * |
||
14 | * You should have received a copy of the GNU General Public License |
||
15 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
||
16 | */ |
||
17 | namespace Alixar\Base; |
||
18 | |||
19 | Use Alixar\Helpers\Globals; |
||
20 | |||
21 | class HookManager |
||
22 | { |
||
23 | |||
24 | /** |
||
25 | * @var string Error code (or message) |
||
26 | */ |
||
27 | public $error = ''; |
||
28 | |||
29 | /** |
||
30 | * @var string[] Error codes (or messages) |
||
31 | */ |
||
32 | public $errors = array(); |
||
33 | // Context hookmanager was created for ('thirdpartycard', 'thirdpartydao', ...) |
||
34 | var $contextarray = array(); |
||
35 | // Array with instantiated classes |
||
36 | var $hooks = array(); |
||
37 | // Array result |
||
38 | var $resArray = array(); |
||
39 | // Printable result |
||
40 | var $resPrint = ''; |
||
41 | // Nb of qualified hook ran |
||
42 | var $resNbOfHooks = 0; |
||
43 | |||
44 | /** |
||
45 | * Constructor |
||
46 | * |
||
47 | * @param DoliDB $db Database handler |
||
48 | */ |
||
49 | function __construct() |
||
50 | { |
||
51 | // Nothing to do |
||
52 | } |
||
53 | |||
54 | /** |
||
55 | * Init array $this->hooks with instantiated action controlers. |
||
56 | * 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. |
||
57 | * This makes Globals::$conf->hooks_modules loaded with an entry ('modulename'=>array(nameofcontext1,nameofcontext2,...)) |
||
58 | * When initHooks function is called, with initHooks(list_of_contexts), an array $this->hooks is defined with instance of controler |
||
59 | * class found into file /mymodule/class/actions_mymodule.class.php (if module has declared the context as a managed context). |
||
60 | * Then when a hook executeHooks('aMethod'...) is called, the method aMethod found into class will be executed. |
||
61 | * |
||
62 | * @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), ... |
||
63 | * @return int Always 1 |
||
64 | */ |
||
65 | function initHooks($arraycontext) |
||
66 | { |
||
67 | // Test if there is hooks to manage |
||
68 | if (!is_array(Globals::$conf->modules_parts['hooks']) || empty(Globals::$conf->modules_parts['hooks'])) { |
||
69 | return; |
||
70 | } |
||
71 | |||
72 | // For backward compatibility |
||
73 | if (!is_array($arraycontext)) { |
||
74 | $arraycontext = array($arraycontext); |
||
75 | } |
||
76 | |||
77 | $this->contextarray = array_unique(array_merge($arraycontext, $this->contextarray)); // All contexts are concatenated |
||
78 | |||
79 | foreach (Globals::$conf->modules_parts['hooks'] as $module => $hooks) { // Loop on each module that brings hooks |
||
80 | if (empty(Globals::$conf->$module->enabled)) { |
||
81 | continue; |
||
82 | } |
||
83 | |||
84 | //dol_syslog(get_class($this).'::initHooks module='.$module.' arraycontext='.join(',',$arraycontext)); |
||
85 | foreach ($arraycontext as $context) { |
||
86 | if (is_array($hooks)) { |
||
87 | $arrayhooks = $hooks; // New system |
||
88 | } else { |
||
89 | $arrayhooks = explode(':', $hooks); // Old system (for backward compatibility) |
||
90 | } |
||
91 | if (in_array($context, $arrayhooks) || in_array('all', $arrayhooks)) { // We instantiate action class only if initialized hook is handled by module |
||
92 | // Include actions class overwriting hooks |
||
93 | if (!is_object($this->hooks[$context][$module])) { // If set, class was already loaded |
||
94 | $path = '/' . $module . '/class/'; |
||
95 | $actionfile = 'actions_' . $module . '.class.php'; |
||
96 | |||
97 | dol_syslog(get_class($this) . '::initHooks Loading hook class for context ' . $context . ": " . $actionfile, LOG_INFO); |
||
98 | $resaction = dol_include_once($path . $actionfile); |
||
99 | if ($resaction) { |
||
100 | $controlclassname = 'Actions' . ucfirst($module); |
||
101 | $actionInstance = new $controlclassname($this->db); |
||
0 ignored issues
–
show
Bug
Best Practice
introduced
by
Loading history...
|
|||
102 | $this->hooks[$context][$module] = $actionInstance; |
||
103 | } |
||
104 | } |
||
105 | } |
||
106 | } |
||
107 | } |
||
108 | return 1; |
||
109 | } |
||
110 | |||
111 | /** |
||
112 | * Execute hooks (if they were initialized) for the given method |
||
113 | * |
||
114 | * @param string $method Name of method hooked ('doActions', 'printSearchForm', 'showInputField', ...) |
||
115 | * @param array $parameters Array of parameters |
||
116 | * @param Object $object Object to use hooks on |
||
117 | * @param string $action Action code on calling page ('create', 'edit', 'view', 'add', 'update', 'delete'...) |
||
118 | * @return mixed For 'addreplace' hooks (doActions,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. |
||
119 | * For 'output' hooks (printLeftBlock, formAddObjectLine, formBuilddocOptions, ...): Return 0, <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. |
||
120 | * All types can also return some values into an array ->results that will be finaly merged into this->resArray for caller. |
||
121 | * $this->error or this->errors are also defined by class called by this function if error. |
||
122 | */ |
||
123 | function executeHooks($method, $parameters = array(), &$object = '', &$action = '') |
||
124 | { |
||
125 | if (!is_array($this->hooks) || empty($this->hooks)) |
||
126 | return ''; |
||
127 | |||
128 | $parameters['context'] = join(':', $this->contextarray); |
||
129 | //dol_syslog(get_class($this).'::executeHooks method='.$method." action=".$action." context=".$parameters['context']); |
||
130 | // Define type of hook ('output' or 'addreplace'. 'returnvalue' is deprecated because a 'addreplace' hook can also return resPrint and resArray). |
||
131 | $hooktype = 'output'; |
||
132 | if (in_array( |
||
133 | $method, array( |
||
134 | 'addCalendarChoice', |
||
135 | 'addMoreActionsButtons', |
||
136 | 'addMoreMassActions', |
||
137 | 'addSearchEntry', |
||
138 | 'addStatisticLine', |
||
139 | 'createDictionaryFieldlist', |
||
140 | 'editDictionaryFieldlist', |
||
141 | 'getFormMail', |
||
142 | 'deleteFile', |
||
143 | 'doActions', |
||
144 | 'doMassActions', |
||
145 | 'formatEvent', |
||
146 | 'formCreateThirdpartyOptions', |
||
147 | 'formObjectOptions', |
||
148 | 'formattachOptions', |
||
149 | 'formBuilddocLineOptions', |
||
150 | 'formatNotificationMessage', |
||
151 | 'getFormMail', |
||
152 | 'getIdProfUrl', |
||
153 | 'getDirList', |
||
154 | 'moveUploadedFile', |
||
155 | 'moreHtmlStatus', |
||
156 | 'pdf_build_address', |
||
157 | 'pdf_writelinedesc', |
||
158 | 'pdf_getlinenum', |
||
159 | 'pdf_getlineref', |
||
160 | 'pdf_getlineref_supplier', |
||
161 | 'pdf_getlinevatrate', |
||
162 | 'pdf_getlineupexcltax', |
||
163 | 'pdf_getlineupwithtax', |
||
164 | 'pdf_getlineqty', |
||
165 | 'pdf_getlineqty_asked', |
||
166 | 'pdf_getlineqty_shipped', |
||
167 | 'pdf_getlineqty_keeptoship', |
||
168 | 'pdf_getlineunit', |
||
169 | 'pdf_getlineremisepercent', |
||
170 | 'pdf_getlineprogress', |
||
171 | 'pdf_getlinetotalexcltax', |
||
172 | 'pdf_getlinetotalwithtax', |
||
173 | 'paymentsupplierinvoices', |
||
174 | 'printAddress', |
||
175 | 'printSearchForm', |
||
176 | 'printTabsHead', |
||
177 | 'printObjectLine', |
||
178 | 'printObjectSubLine', |
||
179 | 'restrictedArea', |
||
180 | 'sendMail', |
||
181 | 'sendMailAfter', |
||
182 | 'showLinkToObjectBlock', |
||
183 | 'setContentSecurityPolicy', |
||
184 | 'setHtmlTitle' |
||
185 | ) |
||
186 | )) |
||
187 | $hooktype = 'addreplace'; |
||
188 | |||
189 | if ($method == 'insertExtraFields') { |
||
190 | $hooktype = 'returnvalue'; // @deprecated. TODO Remove all code with "executeHooks('insertExtraFields'" as soon as there is a trigger available. |
||
191 | 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); |
||
192 | } |
||
193 | |||
194 | // Init return properties |
||
195 | $this->resPrint = ''; |
||
196 | $this->resArray = array(); |
||
197 | $this->resNbOfHooks = 0; |
||
198 | |||
199 | // Loop on each hook to qualify modules that have declared context |
||
200 | $modulealreadyexecuted = array(); |
||
201 | $resaction = 0; |
||
202 | $error = 0; |
||
203 | $result = ''; |
||
204 | 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 |
||
205 | if (!empty($modules)) { |
||
206 | foreach ($modules as $module => $actionclassinstance) { |
||
207 | //print "Before hook ".get_class($actionclassinstance)." method=".$method." hooktype=".$hooktype." results=".count($actionclassinstance->results)." resprints=".count($actionclassinstance->resprints)." resaction=".$resaction." result=".$result."<br>\n"; |
||
208 | // test to avoid running twice a hook, when a module implements several active contexts |
||
209 | if (in_array($module, $modulealreadyexecuted)) |
||
210 | continue; |
||
211 | |||
212 | // jump to next module/class if method does not exist |
||
213 | if (!method_exists($actionclassinstance, $method)) |
||
214 | continue; |
||
215 | |||
216 | $this->resNbOfHooks++; |
||
217 | |||
218 | $modulealreadyexecuted[$module] = $module; // Use the $currentcontext in method to avoid running twice |
||
219 | // Clean class (an error may have been set from a previous call of another method for same module/hook) |
||
220 | $actionclassinstance->error = 0; |
||
221 | $actionclassinstance->errors = array(); |
||
222 | |||
223 | 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); |
||
224 | |||
225 | // Add current context to avoid method execution in bad context, you can add this test in your method : eg if($currentcontext != 'formfile') return; |
||
226 | $parameters['currentcontext'] = $context; |
||
227 | // Hooks that must return int (hooks with type 'addreplace') |
||
228 | if ($hooktype == 'addreplace') { |
||
229 | $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) |
||
230 | if ($resaction < 0 || !empty($actionclassinstance->error) || (!empty($actionclassinstance->errors) && count($actionclassinstance->errors) > 0)) { |
||
231 | $error++; |
||
232 | $this->error = $actionclassinstance->error; |
||
233 | $this->errors = array_merge($this->errors, (array) $actionclassinstance->errors); |
||
234 | 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); |
||
235 | } |
||
236 | |||
237 | if (isset($actionclassinstance->results) && is_array($actionclassinstance->results)) |
||
238 | $this->resArray = array_merge($this->resArray, $actionclassinstance->results); |
||
239 | if (!empty($actionclassinstance->resprints)) |
||
240 | $this->resPrint .= $actionclassinstance->resprints; |
||
241 | } |
||
242 | // Generic hooks that return a string or array (printLeftBlock, formAddObjectLine, formBuilddocOptions, ...) |
||
243 | else { |
||
244 | // TODO. this test should be done into the method of hook by returning nothing |
||
245 | if (is_array($parameters) && !empty($parameters['special_code']) && $parameters['special_code'] > 3 && $parameters['special_code'] != $actionclassinstance->module_number) |
||
246 | continue; |
||
247 | |||
248 | //dol_syslog("Call method ".$method." of class ".get_class($actionclassinstance).", module=".$module.", hooktype=".$hooktype, LOG_DEBUG); |
||
249 | $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) |
||
250 | |||
251 | if (!empty($actionclassinstance->results) && is_array($actionclassinstance->results)) |
||
252 | $this->resArray = array_merge($this->resArray, $actionclassinstance->results); |
||
253 | if (!empty($actionclassinstance->resprints)) |
||
254 | $this->resPrint .= $actionclassinstance->resprints; |
||
255 | // 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 |
||
256 | if (!is_array($resaction) && !is_numeric($resaction)) { |
||
257 | 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); |
||
258 | if (empty($actionclassinstance->resprints)) { |
||
259 | $this->resPrint .= $resaction; |
||
260 | $resaction = 0; |
||
261 | } |
||
262 | } |
||
263 | } |
||
264 | |||
265 | //print "After hook ".get_class($actionclassinstance)." method=".$method." hooktype=".$hooktype." results=".count($actionclassinstance->results)." resprints=".count($actionclassinstance->resprints)." resaction=".$resaction." result=".$result."<br>\n"; |
||
266 | |||
267 | unset($actionclassinstance->results); |
||
268 | unset($actionclassinstance->resprints); |
||
269 | } |
||
270 | } |
||
271 | } |
||
272 | |||
273 | return ($error ? -1 : $resaction); |
||
274 | } |
||
275 | } |
||
276 |