]> git.mxchange.org Git - friendica.git/blob - src/Core/Hook.php
Merge pull request #11506 from annando/featured-worker
[friendica.git] / src / Core / Hook.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2010-2022, the Friendica project
4  *
5  * @license GNU AGPL version 3 or any later version
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU Affero General Public License as
9  * published by the Free Software Foundation, either version 3 of the
10  * License, or (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 Affero General Public License for more details.
16  *
17  * You should have received a copy of the GNU Affero General Public License
18  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19  *
20  */
21
22 namespace Friendica\Core;
23
24 use Friendica\App;
25 use Friendica\Database\DBA;
26 use Friendica\DI;
27 use Friendica\Util\Strings;
28
29 /**
30  * Some functions to handle hooks
31  */
32 class Hook
33 {
34         /**
35          * Array of registered hooks
36          *
37          * Format:
38          * [
39          *              ["<hook name>"] => [
40          *                      0 => "<hook file>",
41          *                      1 => "<hook function name>"
42          *              ],
43          *              ...
44          * ]
45          *
46          * @var array
47          */
48         private static $hooks = [];
49
50         /**
51          * Load hooks
52          */
53         public static function loadHooks()
54         {
55                 self::$hooks = [];
56                 $stmt = DBA::select('hook', ['hook', 'file', 'function'], [], ['order' => ['priority' => 'desc', 'file']]);
57
58                 while ($hook = DBA::fetch($stmt)) {
59                         self::add($hook['hook'], $hook['file'], $hook['function']);
60                 }
61                 DBA::close($stmt);
62         }
63
64         /**
65          * Adds a new hook to the hooks array.
66          *
67          * This function is meant to be called by modules on each page load as it works after loadHooks has been called.
68          *
69          * @param string $hook
70          * @param string $file
71          * @param string $function
72          */
73         public static function add($hook, $file, $function)
74         {
75                 if (!array_key_exists($hook, self::$hooks)) {
76                         self::$hooks[$hook] = [];
77                 }
78                 self::$hooks[$hook][] = [$file, $function];
79         }
80
81         /**
82          * Registers a hook.
83          *
84          * This function is meant to be called once when an addon is enabled for example as it doesn't add to the current hooks.
85          *
86          * @param string $hook     the name of the hook
87          * @param string $file     the name of the file that hooks into
88          * @param string $function the name of the function that the hook will call
89          * @param int    $priority A priority (defaults to 0)
90          * @return mixed|bool
91          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
92          */
93         public static function register($hook, $file, $function, $priority = 0)
94         {
95                 $file = str_replace(DI::app()->getBasePath() . DIRECTORY_SEPARATOR, '', $file);
96
97                 $condition = ['hook' => $hook, 'file' => $file, 'function' => $function];
98                 if (DBA::exists('hook', $condition)) {
99                         return true;
100                 }
101
102                 return self::insert(['hook' => $hook, 'file' => $file, 'function' => $function, 'priority' => $priority]);
103         }
104
105         /**
106          * Unregisters a hook.
107          *
108          * @param string $hook     the name of the hook
109          * @param string $file     the name of the file that hooks into
110          * @param string $function the name of the function that the hook called
111          * @return boolean
112          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
113          */
114         public static function unregister($hook, $file, $function)
115         {
116                 $relative_file = str_replace(DI::app()->getBasePath() . DIRECTORY_SEPARATOR, '', $file);
117
118                 // This here is only needed for fixing a problem that existed on the develop branch
119                 $condition = ['hook' => $hook, 'file' => $file, 'function' => $function];
120                 self::delete($condition);
121
122                 $condition = ['hook' => $hook, 'file' => $relative_file, 'function' => $function];
123                 $result = self::delete($condition);
124                 return $result;
125         }
126
127         /**
128          * Returns the list of callbacks for a single hook
129          *
130          * @param  string $name Name of the hook
131          * @return array
132          */
133         public static function getByName($name)
134         {
135                 $return = [];
136
137                 if (isset(self::$hooks[$name])) {
138                         $return = self::$hooks[$name];
139                 }
140
141                 return $return;
142         }
143
144         /**
145          * Forks a hook.
146          *
147          * Use this function when you want to fork a hook via the worker.
148          *
149          * @param integer $priority of the hook
150          * @param string  $name     of the hook to call
151          * @param mixed   $data     to transmit to the callback handler
152          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
153          */
154         public static function fork($priority, $name, $data = null)
155         {
156                 if (array_key_exists($name, self::$hooks)) {
157                         foreach (self::$hooks[$name] as $hook) {
158                                 // Call a hook to check if this hook call needs to be forked
159                                 if (array_key_exists('hook_fork', self::$hooks)) {
160                                         $hookdata = ['name' => $name, 'data' => $data, 'execute' => true];
161
162                                         foreach (self::$hooks['hook_fork'] as $fork_hook) {
163                                                 if ($hook[0] != $fork_hook[0]) {
164                                                         continue;
165                                                 }
166                                                 self::callSingle(DI::app(), 'hook_fork', $fork_hook, $hookdata);
167                                         }
168
169                                         if (!$hookdata['execute']) {
170                                                 continue;
171                                         }
172                                 }
173
174                                 Worker::add($priority, 'ForkHook', $name, $hook, $data);
175                         }
176                 }
177         }
178
179         /**
180          * Calls a hook.
181          *
182          * Use this function when you want to be able to allow a hook to manipulate
183          * the provided data.
184          *
185          * @param string        $name of the hook to call
186          * @param string|array &$data to transmit to the callback handler
187          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
188          */
189         public static function callAll($name, &$data = null)
190         {
191                 if (array_key_exists($name, self::$hooks)) {
192                         foreach (self::$hooks[$name] as $hook) {
193                                 self::callSingle(DI::app(), $name, $hook, $data);
194                         }
195                 }
196         }
197
198         /**
199          * Calls a single hook.
200          *
201          * @param App             $a
202          * @param string          $name of the hook to call
203          * @param array           $hook Hook data
204          * @param string|array   &$data to transmit to the callback handler
205          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
206          */
207         public static function callSingle(App $a, $name, $hook, &$data = null)
208         {
209                 // Don't run a theme's hook if the user isn't using the theme
210                 if (strpos($hook[0], 'view/theme/') !== false && strpos($hook[0], 'view/theme/' . $a->getCurrentTheme()) === false) {
211                         return;
212                 }
213
214                 @include_once($hook[0]);
215                 if (function_exists($hook[1])) {
216                         $func = $hook[1];
217                         $func($a, $data);
218                 } else {
219                         // remove orphan hooks
220                         $condition = ['hook' => $name, 'file' => $hook[0], 'function' => $hook[1]];
221                         self::delete($condition);
222                 }
223         }
224
225         /**
226          * Checks if an app_menu hook exist for the provided addon name.
227          * Return true if the addon is an app
228          *
229          * @param string $name Name of the addon
230          * @return boolean
231          */
232         public static function isAddonApp($name)
233         {
234                 $name = Strings::sanitizeFilePathItem($name);
235
236                 if (array_key_exists('app_menu', self::$hooks)) {
237                         foreach (self::$hooks['app_menu'] as $hook) {
238                                 if ($hook[0] == 'addon/' . $name . '/' . $name . '.php') {
239                                         return true;
240                                 }
241                         }
242                 }
243
244                 return false;
245         }
246
247         /**
248          * Deletes one or more hook records
249          *
250          * We have to clear the cached routerDispatchData because addons can provide routes
251          *
252          * @param array $condition
253          * @return bool
254          * @throws \Exception
255          */
256         public static function delete(array $condition)
257         {
258                 $result = DBA::delete('hook', $condition);
259
260                 if ($result) {
261                         DI::cache()->delete('routerDispatchData');
262                 }
263
264                 return $result;
265         }
266
267         /**
268          * Inserts a hook record
269          *
270          * We have to clear the cached routerDispatchData because addons can provide routes
271          *
272          * @param array $condition
273          * @return bool
274          * @throws \Exception
275          */
276         private static function insert(array $condition)
277         {
278                 $result = DBA::insert('hook', $condition);
279
280                 if ($result) {
281                         DI::cache()->delete('routerDispatchData');
282                 }
283
284                 return $result;
285         }
286 }