Passed
Push — master ( 65bdac...4e88da )
by Alxarafe
32:38
created

Base/HookManager.php (2 issues)

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
0 ignored issues
show
The type Alixar\Base\DoliDB was not found. Did you mean DoliDB? If so, make sure to prefix the type with \.
Loading history...
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 $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
The property db does not exist on Alixar\Base\HookManager. Did you maybe forget to declare it?
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