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