3 * @copyright Copyright (C) 2010-2022, the Friendica project
5 * @license GNU AGPL version 3 or any later version
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.
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.
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/>.
22 namespace Friendica\Core;
25 use Friendica\Database\DBA;
27 use Friendica\Util\Strings;
30 * Some functions to handle hooks
35 * Array of registered hooks
39 * ["<hook name>"] => [
41 * 1 => "<hook function name>"
48 private static $hooks = [];
55 public static function loadHooks()
58 $stmt = DBA::select('hook', ['hook', 'file', 'function'], [], ['order' => ['priority' => 'desc', 'file']]);
60 while ($hook = DBA::fetch($stmt)) {
61 self::add($hook['hook'], $hook['file'], $hook['function']);
67 * Adds a new hook to the hooks array.
69 * This function is meant to be called by modules on each page load as it works after loadHooks has been called.
73 * @param string $function
76 public static function add(string $hook, string $file, string $function)
78 if (!array_key_exists($hook, self::$hooks)) {
79 self::$hooks[$hook] = [];
81 self::$hooks[$hook][] = [$file, $function];
87 * This function is meant to be called once when an addon is enabled for example as it doesn't add to the current hooks.
89 * @param string $hook the name of the hook
90 * @param string $file the name of the file that hooks into
91 * @param string $function the name of the function that the hook will call
92 * @param int $priority A priority (defaults to 0)
94 * @throws \Friendica\Network\HTTPException\InternalServerErrorException
96 public static function register(string $hook, string $file, string $function, int $priority = 0)
98 $file = str_replace(DI::app()->getBasePath() . DIRECTORY_SEPARATOR, '', $file);
100 $condition = ['hook' => $hook, 'file' => $file, 'function' => $function];
101 if (DBA::exists('hook', $condition)) {
105 return self::insert(['hook' => $hook, 'file' => $file, 'function' => $function, 'priority' => $priority]);
109 * Unregisters a hook.
111 * @param string $hook the name of the hook
112 * @param string $file the name of the file that hooks into
113 * @param string $function the name of the function that the hook called
115 * @throws \Friendica\Network\HTTPException\InternalServerErrorException
117 public static function unregister(string $hook, string $file, string $function): bool
119 $relative_file = str_replace(DI::app()->getBasePath() . DIRECTORY_SEPARATOR, '', $file);
121 // This here is only needed for fixing a problem that existed on the develop branch
122 $condition = ['hook' => $hook, 'file' => $file, 'function' => $function];
123 self::delete($condition);
125 $condition = ['hook' => $hook, 'file' => $relative_file, 'function' => $function];
127 return self::delete($condition);
131 * Returns the list of callbacks for a single hook
133 * @param string $name Name of the hook
136 public static function getByName(string $name): array
140 if (isset(self::$hooks[$name])) {
141 $return = self::$hooks[$name];
150 * Use this function when you want to fork a hook via the worker.
152 * @param integer $priority of the hook
153 * @param string $name of the hook to call
154 * @param mixed $data to transmit to the callback handler
156 * @throws \Friendica\Network\HTTPException\InternalServerErrorException
158 public static function fork(int $priority, string $name, $data = null)
160 if (array_key_exists($name, self::$hooks)) {
161 foreach (self::$hooks[$name] as $hook) {
162 // Call a hook to check if this hook call needs to be forked
163 if (array_key_exists('hook_fork', self::$hooks)) {
164 $hookdata = ['name' => $name, 'data' => $data, 'execute' => true];
166 foreach (self::$hooks['hook_fork'] as $fork_hook) {
167 if ($hook[0] != $fork_hook[0]) {
170 self::callSingle(DI::app(), 'hook_fork', $fork_hook, $hookdata);
173 if (!$hookdata['execute']) {
178 Worker::add($priority, 'ForkHook', $name, $hook, $data);
186 * Use this function when you want to be able to allow a hook to manipulate
189 * @param string $name of the hook to call
190 * @param string|array &$data to transmit to the callback handler
192 * @throws \Friendica\Network\HTTPException\InternalServerErrorException
194 public static function callAll(string $name, &$data = null)
196 if (array_key_exists($name, self::$hooks)) {
197 foreach (self::$hooks[$name] as $hook) {
198 self::callSingle(DI::app(), $name, $hook, $data);
204 * Calls a single hook.
207 * @param string $name of the hook to call
208 * @param array $hook Hook data
209 * @param string|array &$data to transmit to the callback handler
211 * @throws \Friendica\Network\HTTPException\InternalServerErrorException
213 public static function callSingle(App $a, string $name, array $hook, &$data = null)
215 // Don't run a theme's hook if the user isn't using the theme
216 if (strpos($hook[0], 'view/theme/') !== false && strpos($hook[0], 'view/theme/' . $a->getCurrentTheme()) === false) {
220 @include_once($hook[0]);
221 if (function_exists($hook[1])) {
225 // remove orphan hooks
226 $condition = ['hook' => $name, 'file' => $hook[0], 'function' => $hook[1]];
227 self::delete($condition);
232 * Checks if an app_menu hook exist for the provided addon name.
233 * Return true if the addon is an app
235 * @param string $name Name of the addon
238 public static function isAddonApp(string $name): bool
240 $name = Strings::sanitizeFilePathItem($name);
242 if (array_key_exists('app_menu', self::$hooks)) {
243 foreach (self::$hooks['app_menu'] as $hook) {
244 if ($hook[0] == 'addon/' . $name . '/' . $name . '.php') {
254 * Deletes one or more hook records
256 * We have to clear the cached routerDispatchData because addons can provide routes
258 * @param array $condition
262 public static function delete(array $condition): bool
264 $result = DBA::delete('hook', $condition);
267 DI::cache()->delete('routerDispatchData');
274 * Inserts a hook record
276 * We have to clear the cached routerDispatchData because addons can provide routes
278 * @param array $condition
282 private static function insert(array $condition): bool
284 $result = DBA::insert('hook', $condition);
287 DI::cache()->delete('routerDispatchData');