Move Console namespace one level up
[friendica.git/.git] / src / Core / Hook.php
1 <?php
2 /**
3  * @file src/Core/Hook.php
4  */
5 namespace Friendica\Core;
6
7 use Friendica\App;
8 use Friendica\BaseObject;
9 use Friendica\Database\DBA;
10 use Friendica\Util\Strings;
11
12 /**
13  * Some functions to handle hooks
14  */
15 class Hook extends BaseObject
16 {
17         /**
18          * Array of registered hooks
19          *
20          * Format:
21          * [
22          *              ["<hook name>"] => [
23          *                      0 => "<hook file>",
24          *                      1 => "<hook function name>"
25          *              ],
26          *              ...
27          * ]
28          *
29          * @var array
30          */
31         private static $hooks = [];
32
33         /**
34          * Load hooks
35          */
36         public static function loadHooks()
37         {
38                 self::$hooks = [];
39                 $stmt = DBA::select('hook', ['hook', 'file', 'function'], [], ['order' => ['priority' => 'desc', 'file']]);
40
41                 while ($hook = DBA::fetch($stmt)) {
42                         self::add($hook['hook'], $hook['file'], $hook['function']);
43                 }
44                 DBA::close($stmt);
45         }
46
47         /**
48          * @brief Adds a new hook to the hooks array.
49          *
50          * This function is meant to be called by modules on each page load as it works after loadHooks has been called.
51          *
52          * @param string $hook
53          * @param string $file
54          * @param string $function
55          */
56         public static function add($hook, $file, $function)
57         {
58                 if (!array_key_exists($hook, self::$hooks)) {
59                         self::$hooks[$hook] = [];
60                 }
61                 self::$hooks[$hook][] = [$file, $function];
62         }
63
64         /**
65          * @brief Registers a hook.
66          *
67          * This function is meant to be called once when an addon is enabled for example as it doesn't add to the current hooks.
68          *
69          * @param string $hook     the name of the hook
70          * @param string $file     the name of the file that hooks into
71          * @param string $function the name of the function that the hook will call
72          * @param int    $priority A priority (defaults to 0)
73          * @return mixed|bool
74          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
75          */
76         public static function register($hook, $file, $function, $priority = 0)
77         {
78                 $file = str_replace(self::getApp()->getBasePath() . DIRECTORY_SEPARATOR, '', $file);
79
80                 $condition = ['hook' => $hook, 'file' => $file, 'function' => $function];
81                 if (DBA::exists('hook', $condition)) {
82                         return true;
83                 }
84
85                 $result = DBA::insert('hook', ['hook' => $hook, 'file' => $file, 'function' => $function, 'priority' => $priority]);
86
87                 return $result;
88         }
89
90         /**
91          * Unregisters a hook.
92          *
93          * @param string $hook     the name of the hook
94          * @param string $file     the name of the file that hooks into
95          * @param string $function the name of the function that the hook called
96          * @return boolean
97          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
98          */
99         public static function unregister($hook, $file, $function)
100         {
101                 $relative_file = str_replace(self::getApp()->getBasePath() . DIRECTORY_SEPARATOR, '', $file);
102
103                 // This here is only needed for fixing a problem that existed on the develop branch
104                 $condition = ['hook' => $hook, 'file' => $file, 'function' => $function];
105                 DBA::delete('hook', $condition);
106
107                 $condition = ['hook' => $hook, 'file' => $relative_file, 'function' => $function];
108                 $result = DBA::delete('hook', $condition);
109                 return $result;
110         }
111
112         /**
113          * Returns the list of callbacks for a single hook
114          *
115          * @param  string $name Name of the hook
116          * @return array
117          */
118         public static function getByName($name)
119         {
120                 $return = [];
121
122                 if (isset(self::$hooks[$name])) {
123                         $return = self::$hooks[$name];
124                 }
125
126                 return $return;
127         }
128
129         /**
130          * @brief Forks a hook.
131          *
132          * Use this function when you want to fork a hook via the worker.
133          *
134          * @param integer $priority of the hook
135          * @param string  $name     of the hook to call
136          * @param mixed   $data     to transmit to the callback handler
137          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
138          */
139         public static function fork($priority, $name, $data = null)
140         {
141                 if (array_key_exists($name, self::$hooks)) {
142                         foreach (self::$hooks[$name] as $hook) {
143                                 // Call a hook to check if this hook call needs to be forked
144                                 if (array_key_exists('hook_fork', self::$hooks)) {
145                                         $hookdata = ['name' => $name, 'data' => $data, 'execute' => true];
146
147                                         foreach (self::$hooks['hook_fork'] as $fork_hook) {
148                                                 if ($hook[0] != $fork_hook[0]) {
149                                                         continue;
150                                                 }
151                                                 self::callSingle(self::getApp(), 'hook_fork', $fork_hook, $hookdata);
152                                         }
153
154                                         if (!$hookdata['execute']) {
155                                                 continue;
156                                         }
157                                 }
158
159                                 Worker::add($priority, 'ForkHook', $name, $hook, $data);
160                         }
161                 }
162         }
163
164         /**
165          * @brief Calls a hook.
166          *
167          * Use this function when you want to be able to allow a hook to manipulate
168          * the provided data.
169          *
170          * @param string        $name of the hook to call
171          * @param string|array &$data to transmit to the callback handler
172          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
173          */
174         public static function callAll($name, &$data = null)
175         {
176                 if (array_key_exists($name, self::$hooks)) {
177                         foreach (self::$hooks[$name] as $hook) {
178                                 self::callSingle(self::getApp(), $name, $hook, $data);
179                         }
180                 }
181         }
182
183         /**
184          * @brief Calls a single hook.
185          *
186          * @param App             $a
187          * @param string          $name of the hook to call
188          * @param array           $hook Hook data
189          * @param string|array   &$data to transmit to the callback handler
190          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
191          */
192         public static function callSingle(App $a, $name, $hook, &$data = null)
193         {
194                 // Don't run a theme's hook if the user isn't using the theme
195                 if (strpos($hook[0], 'view/theme/') !== false && strpos($hook[0], 'view/theme/' . $a->getCurrentTheme()) === false) {
196                         return;
197                 }
198
199                 @include_once($hook[0]);
200                 if (function_exists($hook[1])) {
201                         $func = $hook[1];
202                         $func($a, $data);
203                 } else {
204                         // remove orphan hooks
205                         $condition = ['hook' => $name, 'file' => $hook[0], 'function' => $hook[1]];
206                         DBA::delete('hook', $condition, ['cascade' => false]);
207                 }
208         }
209
210         /**
211          * Checks if an app_menu hook exist for the provided addon name.
212          * Return true if the addon is an app
213          *
214          * @param string $name Name of the addon
215          * @return boolean
216          */
217         public static function isAddonApp($name)
218         {
219                 $name = Strings::sanitizeFilePathItem($name);
220
221                 if (array_key_exists('app_menu', self::$hooks)) {
222                         foreach (self::$hooks['app_menu'] as $hook) {
223                                 if ($hook[0] == 'addon/' . $name . '/' . $name . '.php') {
224                                         return true;
225                                 }
226                         }
227                 }
228
229                 return false;
230         }
231 }