Completed
Push — 16.1 ( 7290b8...9f4de6 )
by Ralf
30:36 queued 09:36
created

Hooks::implemented()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 3
nc 4
nop 1
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * EGroupware API - Hooks
4
 *
5
 * @link http://www.egroupware.org
6
 * @author Dan Kuykendall <[email protected]>
7
 * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
8
 * Copyright (C) 2000, 2001 Dan Kuykendall
9
 * New method hooks and docu are written by <[email protected]>
10
 * @license http://opensource.org/licenses/lgpl-license.php LGPL - GNU Lesser General Public License
11
 * @package api
12
 * @version $Id$
13
 */
14
15
namespace EGroupware\Api;
16
17
/**
18
 * Allow applications to set and use hooks to communicate with each other
19
 *
20
 * Hooks need to be declared in the app's setup.inc.php file and
21
 * are cached in instance cache for 1h.
22
 *
23
 * Clearing instance cache or calling Api\Hooks::read(true) forces a new scan.
24
 *
25
 * Hooks can have one of the following formats:
26
 *  - static class method hooks are declared as:
27
 *	  $setup_info['appname']['hooks']['location'] = 'class::method';
28
 *	- method hooks, which are methods of a class. You can pass parameters to the call and
29
 *	  they can return values. They get declared in setup.inc.php as:
30
 *	  $setup_info['appname']['hooks']['location'] = 'app.class.method';
31
 *	- old type, which are included files. Values can only be passed by global values and they cant return anything.
32
 *	  Old declaration in setup.inc.php:
33
 *	  $setup_info['appname']['hooks'][] = 'location';
34
 */
35
class Hooks
36
{
37
	/**
38
	 * Hooks by location and appname
39
	 *
40
	 * @var array $location => $app => array($file, ...)
41
	 */
42
	protected static $locations;
43
44
	/**
45
	 * Executes all the hooks (the user has rights to) for a given location
46
	 *
47
	 * If no $order given, hooks are executed in the order of the applications!
48
	 *
49
	 * @param string|array $args location-name as string or array with keys location and
50
	 *	further data to be passed to the hook, if its a new method-hook
51
	 * @param string|array $order appname(s as value), which should be executes first
52
	 * @param boolean $no_permission_check if True execute all hooks, not only the ones a user has rights to
53
	 *	$no_permission_check should *ONLY* be used when it *HAS* to be. (jengo)
54
	 * @return array with results of each hook call (with appname as key) and value:
55
	 *	- False if no hook exists (should no longer be the case),
56
	 *	- True if old hook exists and
57
	 *  - array of return-values, if an app implements more then one hook
58
	 * 	- whatever the new method-hook returns (can be True or False too!)
59
	 */
60
	public static function process($args, $order = array(), $no_permission_check = False)
61
	{
62
		//echo "<p>".__METHOD__.'('.array2string($args).','.array2string($order).','.array2string($no_permission_check).")</p>\n";
63
		$location = is_array($args) ? (isset($args['hook_location']) ? $args['hook_location'] : $args['location']) : $args;
64
65
		if (!isset(self::$locations)) self::read();
66
		$hooks = self::$locations[$location];
67
		if (!isset($hooks) || empty($hooks)) return array();	// not a single app implements that hook
68
69
		$apps = array_keys($hooks);
70
		if (!$no_permission_check)
71
		{
72
			// on install of a new egroupware both hook-apps and user apps may be empty/not set
73
			$apps = array_intersect((array)$apps,array_keys((array)$GLOBALS['egw_info']['user']['apps']));
74
		}
75
		if ($order)
76
		{
77
			$apps = array_unique(array_merge((array)$order,$apps));
78
		}
79
		$results = array();
80
		foreach((array)$apps as $appname)
81
		{
82
			$results[$appname] = self::single($args,$appname,$no_permission_check);
83
		}
84
		return $results;
85
	}
86
87
	/**
88
	 * Executes a single hook of a given location and application
89
	 *
90
	 * @param string|array $args location-name as string or array with keys location, appname and
91
	 *	further data to be passed to the hook, if its a new method-hook
92
	 * @param string $appname name of the app, which's hook to execute, if empty the current app is used
93
	 * @param boolean $no_permission_check =false if True execute all hooks, not only the ones a user has rights to
94
	 *	$no_permission_check should *ONLY* be used when it *HAS* to be. (jengo)
95
	 * @param boolean $try_unregistered =false If true, try to include old file-hook anyway (for setup)
96
	 * @return mixed False if no hook exists, True if old hook exists and whatever the new method-hook returns (can be True or False too!).
97
	 */
98
	public static function single($args, $appname = '', $no_permission_check = False, $try_unregistered = False)
99
	{
100
		//error_log(__METHOD__."(".array2string($args).",'$appname','$no_permission_check','$try_unregistered')");
101
102
		if (!isset(self::$locations)) self::read();
103
104
		if (!is_array($args)) $args = array('location' => $args);
105
		$location = isset($args['hook_location']) ? $args['hook_location'] : $args['location'];
106
107
		if (!$appname)
108
		{
109
			$appname = is_array($args) && isset($args['appname']) ? $args['appname'] : $GLOBALS['egw_info']['flags']['currentapp'];
110
		}
111
		// excute hook only if $no_permission_check or user has run-rights for app
112
		if (!($no_permission_check || isset($GLOBALS['egw_info']['user']['apps'][$appname])))
113
		{
114
			return false;
115
		}
116
117
		$ret = $redirects = array();
118
		foreach((array)self::$locations[$location][$appname] as $hook)
119
		{
120
			try {
121
				// old style file hook
122
				if ($hook[0] == '/')
123
				{
124
					if (!file_exists(EGW_SERVER_ROOT.$hook))
125
					{
126
						error_log(__METHOD__."() old style hook file '$hook' not found --> ignored!");
127
						continue;
128
					}
129
					include(EGW_SERVER_ROOT.$hook);
130
					return true;
131
				}
132
133
				list($class, $method) = explode('::', $hook);
134
135
				// static method of an autoloadable class
136
				if (isset($method) && class_exists($class))
137
				{
138
					if (is_callable($hook)) $ret[] = call_user_func($hook, $args);
139
				}
140
				// app.class.method or not autoloadable class
141
				else
142
				{
143
					$ret[] = ExecMethod2($hook, $args);
0 ignored issues
show
Deprecated Code introduced by
The function ExecMethod2() has been deprecated with message: use autoloadable class-names, instanciate and call method or use static methods

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
144
				}
145
			}
146
			catch (Exception\Redirect $e)
147
			{
148
				$redirects[] = $e;
149
			}
150
			catch (\Exception $e) {
151
				_egw_log_exception($e);
152
			}
153
		}
154
155
		if ($redirects)
156
		{
157
			if (count($redirects) > 1)
158
			{
159
				error_log("Multiple redirects for location '$location', only first one gets executed!");
160
			}
161
			throw $redirects[0];
162
		}
163
164
		// hooks only existing in filesystem used by setup
165
		if (!$ret && $try_unregistered && file_exists(EGW_SERVER_ROOT.($hook='/'.$appname.'/inc/hook_'.$location.'.inc.php')))
166
		{
167
			include(EGW_SERVER_ROOT.$hook);
168
			return true;
169
		}
170
171
		if (!$ret) return false;
172
173
		return count($ret) == 1 ? $ret[0] : $ret;
174
	}
175
176
	/**
177
	 * loop through the applications and count the apps implementing a hooks
178
	 *
179
	 * @param string $location location-name
180
	 * @return int the number of found hooks
181
	 */
182
	public static function count($location)
183
	{
184
		if (!isset(self::$locations)) self::read();
185
186
		return count(self::$locations[$location]);
187
	}
188
189
	/**
190
	 * check if a given hook for an  application is registered
191
	 *
192
	 * @param string $location location-name
193
	 * @param string $app appname
194
	 * @param boolean $return_methods =false true: return hook-method(s)
195
	 * @return int|array the number of found hooks or for $return_methods array with methods
196
	 */
197
	public static function exists($location, $app, $return_methods=false)
198
	{
199
		if (!isset(self::$locations)) self::read();
200
201
		//error_log(__METHOD__.__LINE__.array2string(self::$locations[$location]));
202
		return $return_methods ? self::$locations[$location][$app] : count(self::$locations[$location][$app]);
203
	}
204
205
	/**
206
	 * check which apps implement a given hook
207
	 *
208
	 * @param string $location location-name
209
	 * @return array of apps implementing given hook
210
	 */
211
	public static function implemented($location)
212
	{
213
		if (!isset(self::$locations)) self::read();
214
215
		//error_log(__METHOD__.__LINE__.array2string(self::$locations[$location]));
216
		return isset(self::$locations[$location]) ? array_keys(self::$locations[$location]) : array();
217
	}
218
219
	/**
220
	 * Disable a hook for this request
221
	 *
222
	 * @param string $hook
223
	 * @return boolean true if hook existed, false otherwise
224
	 */
225
	static public function disable($hook)
226
	{
227
		if (!isset(self::$locations)) self::read();
228
229
		$ret = isset(self::$locations[$hook]);
230
231
		unset(self::$locations[$hook]);
232
233
		return $ret;
234
	}
235
236
	/**
237
	 * Read all hooks into self::$locations
238
	 *
239
	 * @param boolan $force_rescan =false true: do not use instance cache
240
	 */
241
	public static function read($force_rescan=false)
242
	{
243
		//$starttime = microtime(true);
244
		if ($force_rescan) Cache::unsetInstance(__CLASS__, 'locations');
245
246
		self::$locations = Cache::getInstance(__CLASS__, 'locations', function()
247
		{
248
			// if we run in setup, we need to read installed apps first
249
			if (!$GLOBALS['egw_info']['apps'])
250
			{
251
				$applications = new Egw\Applications();
252
				$applications->read_installed_apps();
253
			}
254
255
			// read all apps using just filesystem data
256
			$locations = array();
257
			foreach(array_merge(array('api'), array_keys($GLOBALS['egw_info']['apps'])) as $appname)
258
			{
259
				if ($appname[0] == '.' || !is_dir(EGW_SERVER_ROOT.'/'.$appname)) continue;
260
261
				$f = EGW_SERVER_ROOT . '/' . $appname . '/setup/setup.inc.php';
262
				$setup_info = array($appname => array());
263
				if(@file_exists($f)) include($f);
264
265
				// some apps have setup_info for more then themselfs (eg. api for groupdav)
266
				foreach($setup_info as $appname => $data)
267
				{
268
					foreach((array)$data['hooks'] as $location => $methods)
269
					{
270
						if (is_int($location))
271
						{
272
							$location = $methods;
273
							$methods = '/'.$appname.'/inc/hook_'.$methods.'.inc.php';
274
						}
275
						$locations[$location][$appname] = (array)$methods;
276
					}
277
				}
278
			}
279
			return $locations;
280
		}, array(), 3600);
281
282
		//error_log(__METHOD__."() took ".number_format(1000*(microtime(true)-$starttime), 1)."ms, size=".Vfs::hsize(strlen(json_encode(self::$locations))));
283
	}
284
285
	/**
286
	 * Static function to build pgp encryption sidebox menu
287
	 * @param type $appname application name
288
	 */
289
	public static function pgp_encryption_menu($appname)
290
	{
291
		if (Header\UserAgent::mobile()) return;
292
293
		// PGP Encryption (Mailvelope plugin) restore/backup menu
294
		$file = Array(
295
			'Backup/Restore ...' => 'javascript:app.'.$appname.'.mailvelopeCreateBackupRestoreDialog();',
296
			'sendToBottom' => true
297
		);
298
		display_sidebox($appname, lang('PGP Encryption'), $file);
0 ignored issues
show
Deprecated Code introduced by
The function display_sidebox() has been deprecated with message: use $GLOBALS['egw']->framework->sidebox()

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
299
	}
300
}
301