Passed
Push — main ( 7af4bb...131f20 )
by Rafael
58:13
created

HookManager::initHooks()   C

Complexity

Conditions 17
Paths 37

Size

Total Lines 60
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 17
eloc 32
c 0
b 0
f 0
nc 37
nop 1
dl 0
loc 60
rs 5.2166

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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