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