7b086a80 |
1 | <?php |
2 | |
35586184 |
3 | /** |
4 | * plugin.php |
5 | * |
35586184 |
6 | * This file provides the framework for a plugin architecture. |
7 | * |
8 | * Documentation on how to write plugins might show up some time. |
9 | * |
4b5049de |
10 | * @copyright © 1999-2007 The SquirrelMail Project Team |
4b4abf93 |
11 | * @license http://opensource.org/licenses/gpl-license.php GNU Public License |
31841a9e |
12 | * @version $Id$ |
d6c32258 |
13 | * @package squirrelmail |
35586184 |
14 | */ |
15 | |
d6c32258 |
16 | /** |
17 | * This function adds a plugin. |
18 | * @param string $name Internal plugin name (ie. delete_move_next) |
19 | * @return void |
20 | */ |
0606ca1f |
21 | function use_plugin ($name) { |
bd9c880b |
22 | if (file_exists(SM_PATH . "plugins/$name/setup.php")) { |
23 | include_once(SM_PATH . "plugins/$name/setup.php"); |
8b56e282 |
24 | |
25 | /** |
26 | * As of SM 1.5.2, plugin hook registration is statically |
27 | * accomplished using the configuration utility (config/conf.pl). |
28 | * And this code is deprecated (but let's keep it until |
29 | * the new registration system is proven). |
30 | * |
31 | */ |
32 | //$function = "squirrelmail_plugin_init_$name"; |
33 | //if (function_exists($function)) { |
34 | // $function(); |
35 | //} |
2d367c68 |
36 | } |
0606ca1f |
37 | } |
2d367c68 |
38 | |
d6c32258 |
39 | /** |
d849b570 |
40 | * This function executes a plugin hook. |
41 | * |
42 | * It includes an arbitrary return value that is managed by |
43 | * all plugins on the same hook and returned to the core hook |
44 | * location. |
45 | * |
46 | * The desired format of the return value should be defined |
47 | * by the context in which the hook is called. |
48 | * |
49 | * Note that the master return value for this hook is passed |
50 | * to each plugin after the main argument(s) value/array as a |
51 | * convenience only - to show what the current return value is |
52 | * even though it is liable to be changed by other plugins. |
53 | * |
54 | * If any plugin on this hook wants to modify the $args |
55 | * plugin parameter, it simply has to use call-by-reference |
56 | * syntax in the hook function that it has registered for the |
57 | * current hook. Note that this is in addition to (entirely |
58 | * independent of) the return value for this hook. |
59 | * |
60 | * @param string $name Name of hook being executed |
466bb28d |
61 | * @param mixed &$args A single value or an array of arguments |
d849b570 |
62 | * that are to be passed to all plugins |
63 | * operating off the hook being called. |
64 | * Note that this argument is passed by |
65 | * reference thus it is liable to be |
66 | * changed after the hook completes. |
67 | * |
68 | * @return mixed The return value that is managed by the plugins |
69 | * on the current hook. |
8b096f0a |
70 | * |
8b096f0a |
71 | */ |
d849b570 |
72 | function do_hook($name, &$args) { |
73 | |
eb1f02bc |
74 | global $squirrelmail_plugin_hooks, $currentHookName; |
eb1f02bc |
75 | $currentHookName = $name; |
d849b570 |
76 | $ret = NULL; |
31524bcd |
77 | |
78 | if (isset($squirrelmail_plugin_hooks[$name]) |
79 | && is_array($squirrelmail_plugin_hooks[$name])) { |
8b56e282 |
80 | foreach ($squirrelmail_plugin_hooks[$name] as $plugin_name => $function) { |
81 | use_plugin($plugin_name); |
31524bcd |
82 | if (function_exists($function)) { |
0fe6826d |
83 | $ret = $function($args, $ret); |
1f45d1b5 |
84 | |
85 | // each plugin can call additional hooks, so need |
86 | // to make sure the current hook name is accurate |
87 | // again after each plugin has finished |
88 | // |
89 | $currentHookName = $name; |
dd389be5 |
90 | } |
2d367c68 |
91 | } |
2d367c68 |
92 | } |
7b086a80 |
93 | |
eb1f02bc |
94 | $currentHookName = ''; |
2586d588 |
95 | return $ret; |
d849b570 |
96 | |
0606ca1f |
97 | } |
7b086a80 |
98 | |
8b096f0a |
99 | /** |
d849b570 |
100 | * This function executes a hook that allows for an arbitrary |
101 | * return value from each plugin that will be merged into one |
102 | * array (or one string if all return values are strings) and |
103 | * returned to the core hook location. |
7edd8ad6 |
104 | * |
105 | * Note that unlike PHP's array_merge function, matching array keys |
106 | * will not overwrite each other, instead, values under such keys |
107 | * will be concatenated if they are both strings, or merged if they |
108 | * are arrays (in the same (non-overwrite) manner recursively). |
109 | * |
110 | * Plugins returning non-arrays (strings, objects, etc) will have |
111 | * their output added to the end of the ultimate return array, |
112 | * unless ALL values returned are strings, in which case one string |
0db60ced |
113 | * with all returned strings concatenated together is returned |
114 | * (unless $force_array is TRUE). |
8b096f0a |
115 | * |
d849b570 |
116 | * If any plugin on this hook wants to modify the $args |
117 | * plugin parameter, it simply has to use call-by-reference |
118 | * syntax in the hook function that it has registered for the |
119 | * current hook. Note that this is in addition to (entirely |
120 | * independent of) the return value for this hook. |
121 | * |
0db60ced |
122 | * @param string $name Name of hook being executed |
466bb28d |
123 | * @param mixed &$args A single value or an array of arguments |
0db60ced |
124 | * that are to be passed to all plugins |
125 | * operating off the hook being called. |
126 | * Note that this argument is passed by |
127 | * reference thus it is liable to be |
128 | * changed after the hook completes. |
129 | * @param boolean $force_array When TRUE, guarantees the return |
130 | * value will ALWAYS be an array, |
131 | * (simple strings will be forced |
132 | * into a one-element array). |
133 | * When FALSE, behavior is as |
134 | * described above (OPTIONAL; |
135 | * default behavior is to return |
136 | * mixed - array or string). |
7edd8ad6 |
137 | * |
138 | * @return mixed the merged return arrays or strings of each |
d849b570 |
139 | * plugin on this hook. |
7edd8ad6 |
140 | * |
8b096f0a |
141 | */ |
0db60ced |
142 | function concat_hook_function($name, &$args, $force_array=FALSE) { |
d849b570 |
143 | |
eb1f02bc |
144 | global $squirrelmail_plugin_hooks, $currentHookName; |
eb1f02bc |
145 | $currentHookName = $name; |
d849b570 |
146 | $ret = ''; |
31524bcd |
147 | |
09ac2863 |
148 | if (isset($squirrelmail_plugin_hooks[$name]) |
149 | && is_array($squirrelmail_plugin_hooks[$name])) { |
8b56e282 |
150 | foreach ($squirrelmail_plugin_hooks[$name] as $plugin_name => $function) { |
151 | use_plugin($plugin_name); |
09ac2863 |
152 | if (function_exists($function)) { |
d849b570 |
153 | $plugin_ret = $function($args); |
5fba72db |
154 | if (!empty($plugin_ret)) { |
155 | $ret = sqm_array_merge($ret, $plugin_ret); |
156 | } |
1f45d1b5 |
157 | |
158 | // each plugin can call additional hooks, so need |
159 | // to make sure the current hook name is accurate |
160 | // again after each plugin has finished |
161 | // |
162 | $currentHookName = $name; |
09ac2863 |
163 | } |
164 | } |
165 | } |
166 | |
0db60ced |
167 | if ($force_array && is_string($ret)) { |
168 | $ret = array($ret); |
169 | } |
170 | |
eb1f02bc |
171 | $currentHookName = ''; |
09ac2863 |
172 | return $ret; |
d849b570 |
173 | |
09ac2863 |
174 | } |
cd7fc9e6 |
175 | |
5576644b |
176 | /** |
177 | * This function is used for hooks which are to return true or |
178 | * false. If $priority is > 0, any one or more trues will override |
179 | * any falses. If $priority < 0, then one or more falses will |
180 | * override any trues. |
8b096f0a |
181 | * Priority 0 means majority rules. Ties will be broken with $tie |
182 | * |
d849b570 |
183 | * If any plugin on this hook wants to modify the $args |
184 | * plugin parameter, it simply has to use call-by-reference |
185 | * syntax in the hook function that it has registered for the |
186 | * current hook. Note that this is in addition to (entirely |
187 | * independent of) the return value for this hook. |
188 | * |
189 | * @param string $name The hook name |
466bb28d |
190 | * @param mixed &$args A single value or an array of arguments |
d849b570 |
191 | * that are to be passed to all plugins |
192 | * operating off the hook being called. |
193 | * Note that this argument is passed by |
194 | * reference thus it is liable to be |
195 | * changed after the hook completes. |
196 | * @param int $priority See explanation above |
197 | * @param boolean $tie See explanation above |
198 | * |
199 | * @return boolean The result of the function |
200 | * |
8b096f0a |
201 | */ |
d849b570 |
202 | function boolean_hook_function($name, &$args, $priority=0, $tie=false) { |
203 | |
eb1f02bc |
204 | global $squirrelmail_plugin_hooks, $currentHookName; |
5576644b |
205 | $yea = 0; |
206 | $nay = 0; |
207 | $ret = $tie; |
208 | |
209 | if (isset($squirrelmail_plugin_hooks[$name]) && |
210 | is_array($squirrelmail_plugin_hooks[$name])) { |
211 | |
212 | /* Loop over the plugins that registered the hook */ |
eb1f02bc |
213 | $currentHookName = $name; |
8b56e282 |
214 | foreach ($squirrelmail_plugin_hooks[$name] as $plugin_name => $function) { |
215 | use_plugin($plugin_name); |
5576644b |
216 | if (function_exists($function)) { |
d849b570 |
217 | $ret = $function($args); |
5576644b |
218 | if ($ret) { |
219 | $yea++; |
220 | } else { |
221 | $nay++; |
222 | } |
1f45d1b5 |
223 | |
224 | // each plugin can call additional hooks, so need |
225 | // to make sure the current hook name is accurate |
226 | // again after each plugin has finished |
227 | // |
228 | $currentHookName = $name; |
5576644b |
229 | } |
230 | } |
eb1f02bc |
231 | $currentHookName = ''; |
5576644b |
232 | |
233 | /* Examine the aftermath and assign the return value appropriately */ |
234 | if (($priority > 0) && ($yea)) { |
235 | $ret = true; |
236 | } elseif (($priority < 0) && ($nay)) { |
237 | $ret = false; |
238 | } elseif ($yea > $nay) { |
239 | $ret = true; |
240 | } elseif ($nay > $yea) { |
241 | $ret = false; |
242 | } else { |
243 | // There's a tie, no action needed. |
244 | } |
245 | return $ret; |
246 | } |
247 | // If the code gets here, there was a problem - no hooks, etc. |
248 | return NULL; |
d849b570 |
249 | |
5576644b |
250 | } |
251 | |
cd7fc9e6 |
252 | /** |
141105fc |
253 | * Do not use, use checkForJavascript() instead. |
254 | * |
cd7fc9e6 |
255 | * This function checks whether the user's USER_AGENT is known to |
256 | * be broken. If so, returns true and the plugin is invisible to the |
257 | * offending browser. |
7349fa12 |
258 | * *** THIS IS A TEST FOR JAVASCRIPT SUPPORT *** |
8b096f0a |
259 | * |
260 | * @return bool whether this browser properly supports JavaScript |
ccacde36 |
261 | * @deprecated use checkForJavascript() since 1.5.1 |
cd7fc9e6 |
262 | */ |
263 | function soupNazi(){ |
7349fa12 |
264 | return !checkForJavascript(); |
cd7fc9e6 |
265 | } |
fe0aa536 |
266 | |
267 | /** |
268 | * Check if plugin is enabled |
269 | * @param string $plugin_name plugin name |
270 | * @since 1.5.1 |
271 | * @return boolean |
272 | */ |
273 | function is_plugin_enabled($plugin_name) { |
274 | global $plugins; |
275 | |
e83cfcef |
276 | /** |
202bcbcc |
277 | * check if variable is empty. if var is not set, php empty |
e495bd68 |
278 | * returns true without error notice. |
279 | * |
280 | * then check if it is an array |
e83cfcef |
281 | */ |
e495bd68 |
282 | if (empty($plugins) || ! is_array($plugins)) |
fe0aa536 |
283 | return false; |
284 | |
285 | if ( in_array($plugin_name,$plugins) ) { |
286 | return true; |
287 | } else { |
288 | return false; |
289 | } |
290 | } |
d99b6c56 |
291 | |
d95b10b3 |
292 | /** |
293 | * Get a plugin's version. |
294 | * |
295 | * Determines and returns a plugin's version. |
296 | * |
2cbaf68d |
297 | * By default, the desired plugin must be currently |
298 | * activated, and if it is not, this function will |
299 | * return FALSE. By overriding the default value |
300 | * of $force_inclusion, this function will attempt |
301 | * to grab versioning information from the given |
302 | * plugin even if it is not activated (plugin still |
303 | * has to be unpackaged and set in place in the |
d95b10b3 |
304 | * plugins directory). Use with care - some plugins |
305 | * might break SquirrelMail when this is used. |
2cbaf68d |
306 | * |
9c2f2d2e |
307 | * By turning on the $do_parse argument, the version |
2cbaf68d |
308 | * string will be parsed by SquirrelMail into a |
9c2f2d2e |
309 | * SquirrelMail-compatible version string (such as |
2cbaf68d |
310 | * "1.2.3") if it is not already. |
9c2f2d2e |
311 | * |
2cbaf68d |
312 | * Note that this assumes plugin versioning is |
313 | * consistently applied in the same fashion that |
314 | * SquirrelMail versions are, with the exception that |
315 | * an applicable SquirrelMail version may be appended |
316 | * to the version number (which will be ignored herein). |
317 | * That is, plugin version number schemes are expected |
318 | * in the following format: 1.2.3, or 1.2.3-1.4.0. |
9c2f2d2e |
319 | * |
2cbaf68d |
320 | * Any characters after the third version number |
321 | * indicating things such as beta or release candidate |
322 | * versions are discarded, so formats such as the |
323 | * following will also work, although extra information |
324 | * about beta versions can possibly confuse the desired |
325 | * results of the version check: 1.2.3-beta4, 1.2.3.RC2, |
9c2f2d2e |
326 | * and so forth. |
2cbaf68d |
327 | * |
d95b10b3 |
328 | * @since 1.5.2 |
329 | * |
330 | * @param string plugin_name name of the plugin to |
331 | * check; must precisely |
332 | * match the plugin |
333 | * directory name |
334 | * @param bool force_inclusion try to get version info |
335 | * for plugins not activated? |
336 | * (default FALSE) |
9c2f2d2e |
337 | * @param bool do_parse return the plugin version |
338 | * in SquirrelMail-compatible |
339 | * format (default FALSE) |
d95b10b3 |
340 | * |
341 | * @return mixed The plugin version string if found, otherwise, |
342 | * boolean FALSE is returned indicating that no |
343 | * version information could be found for the plugin. |
344 | * |
345 | */ |
9c2f2d2e |
346 | function get_plugin_version($plugin_name, $force_inclusion = FALSE, $do_parse = FALSE) |
d95b10b3 |
347 | { |
348 | |
349 | $info_function = $plugin_name . '_info'; |
350 | $version_function = $plugin_name . '_version'; |
351 | $plugin_info = array(); |
352 | $plugin_version = FALSE; |
353 | |
354 | |
355 | // first attempt to find the plugin info function, wherein |
356 | // the plugin version should be available |
357 | // |
358 | if (function_exists($info_function)) |
359 | $plugin_info = $info_function(); |
2cbaf68d |
360 | else if ($force_inclusion |
d95b10b3 |
361 | && file_exists(SM_PATH . 'plugins/' . $plugin_name . '/setup.php')) |
362 | { |
fc6228e4 |
363 | |
364 | /* --- Old code, keeping just in case... problem with it is, for example, |
365 | if it is used, but later we are checking if the same plugin is |
366 | activated (because it SHOULD be), this code having run will possibly |
367 | create a false positive. |
d95b10b3 |
368 | include_once(SM_PATH . 'plugins/' . $plugin_name . '/setup.php'); |
369 | if (function_exists($info_function)) |
370 | $plugin_info = $info_function(); |
fc6228e4 |
371 | --- */ |
372 | |
373 | // so what we need to do is process this plugin without |
374 | // it polluting our environment |
375 | // |
376 | // we *could* just use the above code, which is more of a |
377 | // sure thing than some regular expressions, and then test |
378 | // the contents of the $plugins array to see if this plugin |
379 | // is actually activated, and that might be good enough, but |
380 | // for now, we'll use the following approach, because of two |
381 | // concerns: other plugins and other templates might force |
382 | // the inclusion of a plugin (which SHOULD also add it to |
383 | // the $plugins array, but am not 100% sure at this time (FIXME)), |
384 | // and because the regexps below should work just fine with |
385 | // any resonably formatted plugin setup file. |
386 | // |
387 | // read the target plugin's setup.php file into a string, |
388 | // then use a regular expression to try to find the version... |
389 | // this of course can break if plugin authors do funny things |
390 | // with their file formatting |
391 | // |
392 | $setup_file = ''; |
393 | $file_contents = file(SM_PATH . 'plugins/' . $plugin_name . '/setup.php'); |
394 | foreach ($file_contents as $line) |
395 | $setup_file .= $line; |
396 | |
397 | |
398 | // this regexp grabs a version number from a standard |
399 | // <plugin>_info() function |
400 | // |
401 | if (preg_match('/[\'"]version[\'"]\s*=>\s*[\'"](.+?)[\'"]/is', $setup_file, $matches)) |
402 | $plugin_info = array('version' => $matches[1]); |
403 | |
404 | |
405 | // this regexp grabs a version number from a standard |
406 | // (deprecated) <plugin>_version() function |
407 | // |
408 | else if (preg_match('/function\s+.*?' . $plugin_name . '_version.*?\(.*?\).*?\{.*?return\s+[\'"](.+?)[\'"]/is', $setup_file, $matches)) |
409 | $plugin_info = array('version' => $matches[1]); |
410 | |
d95b10b3 |
411 | } |
412 | if (!empty($plugin_info['version'])) |
413 | $plugin_version = $plugin_info['version']; |
414 | |
415 | |
2cbaf68d |
416 | // otherwise, look for older version function |
d95b10b3 |
417 | // |
418 | if (!$plugin_version && function_exists($version_function)) |
419 | $plugin_version = $version_function(); |
420 | |
421 | |
9c2f2d2e |
422 | if ($plugin_version && $do_parse) |
423 | { |
424 | |
425 | // massage version number into something we understand |
426 | // |
427 | // the first regexp strips everything and anything that follows |
428 | // the first occurance of a non-digit (or non decimal point), so |
429 | // beware that putting letters in the middle of a version string |
430 | // will effectively truncate the version string right there (but |
431 | // this also just helps remove the SquirrelMail version part off |
432 | // of versions such as "1.2.3-1.4.4") |
433 | // |
434 | // the second regexp just strips out non-digits/non-decimal points |
435 | // (and might be redundant(?)) |
436 | // |
437 | // the regexps are wrapped in a trim that makes sure the version |
438 | // does not start or end with a decimal point |
439 | // |
2cbaf68d |
440 | $plugin_version = trim(preg_replace(array('/[^0-9.]+.*$/', '/[^0-9.]/'), |
441 | '', $plugin_version), |
9c2f2d2e |
442 | '.'); |
443 | |
444 | } |
445 | |
d95b10b3 |
446 | return $plugin_version; |
447 | |
448 | } |
449 | |
d99b6c56 |
450 | /** |
451 | * Check a plugin's version. |
452 | * |
453 | * Returns TRUE if the given plugin is installed, |
454 | * activated and is at minimum version $a.$b.$c. |
455 | * If any one of those conditions fails, FALSE |
456 | * will be returned (careful of plugins that are |
457 | * sufficiently versioned but are not activated). |
458 | * |
459 | * By overriding the default value of $force_inclusion, |
460 | * this function will attempt to grab versioning |
461 | * information from the given plugin even if it |
9c2f2d2e |
462 | * is not activated (the plugin still has to be |
d99b6c56 |
463 | * unpackaged and set in place in the plugins |
464 | * directory). Use with care - some plugins |
465 | * might break SquirrelMail when this is used. |
466 | * |
467 | * Note that this function assumes plugin |
468 | * versioning is consistently applied in the same |
469 | * fashion that SquirrelMail versions are, with the |
470 | * exception that an applicable SquirrelMail |
471 | * version may be appended to the version number |
472 | * (which will be ignored herein). That is, plugin |
473 | * version number schemes are expected in the following |
474 | * format: 1.2.3, or 1.2.3-1.4.0. |
475 | * |
9c2f2d2e |
476 | * Any characters after the third number indicating |
477 | * things such as beta or release candidate versions |
478 | * are discarded, so formats such as the following |
479 | * will also work, although extra information about |
480 | * beta versions can possibly confuse the desired results |
481 | * of the version check: 1.2.3-beta4, 1.2.3.RC2, and so forth. |
d99b6c56 |
482 | * |
b2a6a14c |
483 | * @since 1.5.2 |
d99b6c56 |
484 | * |
fc6228e4 |
485 | * @param string plugin_name Name of the plugin to |
d99b6c56 |
486 | * check; must precisely |
487 | * match the plugin |
488 | * directory name |
fc6228e4 |
489 | * @param int a Major version number |
490 | * @param int b Minor version number |
491 | * @param int c Release number |
492 | * @param bool force_inclusion Try to get version info |
d99b6c56 |
493 | * for plugins not activated? |
494 | * (default FALSE) |
495 | * |
496 | * @return bool |
497 | * |
498 | */ |
499 | function check_plugin_version($plugin_name, |
500 | $a = 0, $b = 0, $c = 0, |
501 | $force_inclusion = FALSE) |
502 | { |
503 | |
9c2f2d2e |
504 | $plugin_version = get_plugin_version($plugin_name, $force_inclusion, TRUE); |
d99b6c56 |
505 | if (!$plugin_version) return FALSE; |
506 | |
507 | |
9c2f2d2e |
508 | // split the version string into sections delimited by |
509 | // decimal points, and make sure we have three sections |
d99b6c56 |
510 | // |
d99b6c56 |
511 | $plugin_version = explode('.', $plugin_version); |
512 | if (!isset($plugin_version[0])) $plugin_version[0] = 0; |
513 | if (!isset($plugin_version[1])) $plugin_version[1] = 0; |
514 | if (!isset($plugin_version[2])) $plugin_version[2] = 0; |
515 | // sm_print_r($plugin_version); |
516 | |
517 | |
518 | // now test the version number |
519 | // |
520 | if ($plugin_version[0] < $a || |
521 | ($plugin_version[0] == $a && $plugin_version[1] < $b) || |
522 | ($plugin_version[0] == $a && $plugin_version[1] == $b && $plugin_version[2] < $c)) |
523 | return FALSE; |
524 | |
525 | |
526 | return TRUE; |
527 | |
528 | } |
529 | |
fc6228e4 |
530 | /** |
531 | * Get a certain plugin requirement. |
532 | * |
533 | * Attempts to find the given plugin requirement value |
534 | * in the given plugin's informational array, and returns |
535 | * it or NULL if it was not found. |
536 | * |
537 | * Some plugins have different values for the same |
538 | * requirement depending on the SquirrelMail version, |
539 | * and this function is smart enough to take that into |
540 | * account. |
541 | * |
542 | * By default, the desired plugin must be currently |
543 | * activated, and if it is not, this function will |
544 | * return NULL. By overriding the default value |
545 | * of $force_inclusion, this function will attempt |
546 | * to grab requirement information from the given |
547 | * plugin even if it is not activated (plugin still |
548 | * has to be unpackaged and set in place in the |
549 | * plugins directory). Use with care - some plugins |
550 | * might break SquirrelMail when this is used. |
551 | * |
552 | * @since 1.5.2 |
553 | * |
7587094b |
554 | * @param string $plugin_name Name of the plugin to |
555 | * check; must precisely |
556 | * match the plugin |
557 | * directory name |
558 | * @param string $requirement The desired requirement name |
559 | * @param boolean $ignore_incompatible When TRUE, version incompatibility |
560 | * information will NOT be returned |
561 | * if found; when FALSE, it will be |
562 | * (OPTIONAL; default TRUE) |
563 | * @param boolean $force_inclusion Try to get requirement info |
564 | * for plugins not activated? |
565 | * (OPTIONAL; default FALSE) |
fc6228e4 |
566 | * |
567 | * @return mixed NULL is returned if the plugin could not be |
568 | * found or does not include the given requirement, |
f258865c |
569 | * the constant SQ_INCOMPATIBLE is returned if the |
03a69f57 |
570 | * given plugin is entirely incompatible with the |
7587094b |
571 | * current SquirrelMail version (unless |
572 | * $ignore_incompatible is TRUE), otherwise the |
03a69f57 |
573 | * value of the requirement is returned, whatever |
574 | * that may be (varies per requirement type). |
fc6228e4 |
575 | * |
576 | */ |
577 | function get_plugin_requirement($plugin_name, $requirement, |
7587094b |
578 | $ignore_incompatible = TRUE, |
fc6228e4 |
579 | $force_inclusion = FALSE) |
580 | { |
581 | |
582 | $info_function = $plugin_name . '_info'; |
583 | $plugin_info = array(); |
584 | $requirement_value = NULL; |
585 | |
586 | |
587 | // first attempt to find the plugin info function, wherein |
588 | // the plugin requirements should be available |
589 | // |
590 | if (function_exists($info_function)) |
591 | $plugin_info = $info_function(); |
592 | else if ($force_inclusion |
593 | && file_exists(SM_PATH . 'plugins/' . $plugin_name . '/setup.php')) |
594 | { |
595 | |
596 | /* --- Old code, keeping just in case... problem with it is, for example, |
597 | if it is used, but later we are checking if the same plugin is |
598 | activated (because it SHOULD be), this code having run will possibly |
599 | create a false positive. |
600 | include_once(SM_PATH . 'plugins/' . $plugin_name . '/setup.php'); |
601 | if (function_exists($info_function)) |
602 | $plugin_info = $info_function(); |
603 | --- */ |
604 | |
605 | // so what we need to do is process this plugin without |
606 | // it polluting our environment |
607 | // |
608 | // we *could* just use the above code, which is more of a |
609 | // sure thing than a regular expression, and then test |
610 | // the contents of the $plugins array to see if this plugin |
611 | // is actually activated, and that might be good enough, but |
612 | // for now, we'll use the following approach, because of two |
613 | // concerns: other plugins and other templates might force |
614 | // the inclusion of a plugin (which SHOULD also add it to |
615 | // the $plugins array, but am not 100% sure at this time (FIXME)), |
616 | // and because the regexp below should work just fine with |
617 | // any resonably formatted plugin setup file. |
618 | // |
619 | // read the target plugin's setup.php file into a string, |
620 | // then use a regular expression to try to find the needed |
621 | // requirement information... |
622 | // this of course can break if plugin authors do funny things |
623 | // with their file formatting |
624 | // |
625 | $setup_file = ''; |
626 | $file_contents = file(SM_PATH . 'plugins/' . $plugin_name . '/setup.php'); |
627 | foreach ($file_contents as $line) |
628 | $setup_file .= $line; |
629 | |
630 | |
631 | // this regexp grabs the full plugin info array from a standard |
632 | // <plugin>_info() function... determining the end of the info |
633 | // array can fail, but if authors end the array with ");\n" |
634 | // (without quotes), then it should work well, especially because |
635 | // newlines shouldn't be found inside the array after any ");" |
636 | // (without quotes) |
637 | // |
638 | if (preg_match('/function\s+.*?' . $plugin_name . '_info.*?\(.*?\).*?\{.*?(array.+?\)\s*;)\s*' . "\n" . '/is', $setup_file, $matches)) |
639 | eval('$plugin_info = ' . $matches[1]); |
640 | |
641 | } |
642 | |
643 | |
644 | // attempt to get the requirement from the "global" scope |
645 | // of the plugin information array |
646 | // |
647 | if (isset($plugin_info[$requirement]) |
648 | && !is_null($plugin_info[$requirement])) |
649 | $requirement_value = $plugin_info[$requirement]; |
650 | |
651 | |
652 | // now, if there is a series of per-version requirements, |
653 | // check there too |
654 | // |
655 | if (!empty($plugin_info['per_version_requirements']) |
656 | && is_array($plugin_info['per_version_requirements'])) |
657 | { |
658 | |
659 | // iterate through requirements, where keys are version |
660 | // numbers -- tricky part is knowing the difference between |
661 | // more than one version for which the current SM installation |
662 | // passes the check_sm_version() test... we want the highest one |
663 | // |
664 | $requirement_value_override = NULL; |
665 | $highest_version_array = array(); |
666 | foreach ($plugin_info['per_version_requirements'] as $version => $requirement_overrides) |
667 | { |
668 | |
03a69f57 |
669 | $version_array = explode('.', $version); |
670 | if (sizeof($version_array) != 3) continue; |
671 | |
672 | $a = $version_array[0]; |
673 | $b = $version_array[1]; |
674 | $c = $version_array[2]; |
675 | |
7587094b |
676 | // complicated way to say we are interested in these overrides |
677 | // if the version is applicable to us and if the overrides include |
678 | // the requirement we are looking for, or if the plugin is not |
679 | // compatible with this version of SquirrelMail (unless we are |
680 | // told to ignore such) |
681 | // |
03a69f57 |
682 | if (check_sm_version($a, $b, $c) |
7587094b |
683 | && ((!$ignore_incompatible |
684 | && (!empty($requirement_overrides[SQ_INCOMPATIBLE]) |
685 | || $requirement_overrides === SQ_INCOMPATIBLE)) |
686 | || (is_array($requirement_overrides) |
687 | && isset($requirement_overrides[$requirement]) |
688 | && !is_null($requirement_overrides[$requirement])))) |
03a69f57 |
689 | { |
690 | |
691 | if (empty($highest_version_array) |
692 | || $highest_version_array[0] < $a |
693 | || ($highest_version_array[0] == $a |
694 | && $highest_version_array[1] < $b) |
695 | || ($highest_version_array[0] == $a |
696 | && $highest_version_array[1] == $b |
697 | && $highest_version_array[2] < $c)) |
698 | { |
699 | $highest_version_array = $version_array; |
7587094b |
700 | if (!empty($requirement_overrides[SQ_INCOMPATIBLE]) |
701 | || $requirement_overrides === SQ_INCOMPATIBLE) |
f258865c |
702 | $requirement_value_override = SQ_INCOMPATIBLE; |
03a69f57 |
703 | else |
704 | $requirement_value_override = $requirement_overrides[$requirement]; |
705 | } |
fc6228e4 |
706 | |
707 | } |
708 | |
709 | } |
710 | |
711 | // now grab override if one is available |
712 | // |
713 | if (!is_null($requirement_value_override)) |
714 | $requirement_value = $requirement_value_override; |
715 | |
716 | } |
717 | |
718 | return $requirement_value; |
719 | |
720 | } |
721 | |
2cbaf68d |
722 | /** |
723 | * Get a plugin's other plugin dependencies. |
724 | * |
725 | * Determines and returns all the other plugins |
726 | * that a given plugin requires, as well as the |
fc6228e4 |
727 | * minimum version numbers of the required plugins |
728 | * and whether or not they need to be activated. |
2cbaf68d |
729 | * |
730 | * By default, the desired plugin must be currently |
731 | * activated, and if it is not, this function will |
732 | * return FALSE. By overriding the default value |
733 | * of $force_inclusion, this function will attempt |
734 | * to grab dependency information from the given |
735 | * plugin even if it is not activated (plugin still |
736 | * has to be unpackaged and set in place in the |
737 | * plugins directory). Use with care - some plugins |
738 | * might break SquirrelMail when this is used. |
739 | * |
740 | * By turning on the $do_parse argument (it is on by |
741 | * default), the version string for each required |
742 | * plugin will be parsed by SquirrelMail into a |
743 | * SquirrelMail-compatible version string (such as |
744 | * "1.2.3") if it is not already. See notes about |
fc6228e4 |
745 | * version formatting under the get_plugin_version() |
746 | * function documentation. |
2cbaf68d |
747 | * |
748 | * @since 1.5.2 |
749 | * |
750 | * @param string plugin_name name of the plugin to |
751 | * check; must precisely |
752 | * match the plugin |
753 | * directory name |
754 | * @param bool force_inclusion try to get version info |
755 | * for plugins not activated? |
756 | * (default FALSE) |
757 | * @param bool do_parse return the version numbers |
758 | * for required plugins in |
759 | * SquirrelMail-compatible |
760 | * format (default FALSE) |
761 | * |
762 | * @return mixed Boolean FALSE is returned if the plugin |
763 | * could not be found or does not indicate |
764 | * whether it has other plugin dependencies, |
f258865c |
765 | * the constant SQ_INCOMPATIBLE is returned if |
03a69f57 |
766 | * the given plugin is entirely incompatible |
767 | * with the current SquirrelMail version, |
2cbaf68d |
768 | * otherwise an array is returned where keys |
03a69f57 |
769 | * are the names of required plugin |
770 | * dependencies, and values are arrays again, |
771 | * where at least the following keys (and |
772 | * corresponding values) will be available: |
773 | * 'version' - value is the minimum version |
774 | * required for that plugin (the format of |
15a8ad6b |
775 | * which might vary per the value of $do_parse |
776 | * as well as if the plugin requires a SquirrelMail |
777 | * core plugin, in which case it is "CORE" or |
e1a125cd |
778 | * "CORE:1.5.2" or similar, or, if the plugin is |
779 | * actually incompatible (not required) with this |
780 | * one, the constant SQ_INCOMPATIBLE will be found |
781 | * here), 'activate' - value is boolean: TRUE |
782 | * indicates that the plugin must also be activated, |
783 | * FALSE means that it only needs to be present, |
784 | * but does not need to be activated. Note that |
785 | * the return value might be an empty array, |
786 | * indicating that the plugin has no dependencies. |
2cbaf68d |
787 | * |
788 | */ |
fc6228e4 |
789 | function get_plugin_dependencies($plugin_name, $force_inclusion = FALSE, |
790 | $do_parse = TRUE) |
2cbaf68d |
791 | { |
792 | |
fc6228e4 |
793 | $plugin_dependencies = get_plugin_requirement($plugin_name, |
794 | 'required_plugins', |
7587094b |
795 | FALSE, |
fc6228e4 |
796 | $force_inclusion); |
2cbaf68d |
797 | |
03a69f57 |
798 | // the plugin is simply incompatible, no need to continue here |
799 | // |
f258865c |
800 | if ($plugin_dependencies === SQ_INCOMPATIBLE) |
03a69f57 |
801 | return $plugin_dependencies; |
802 | |
2cbaf68d |
803 | |
fc6228e4 |
804 | // not an array of requirements? wrong format, just return FALSE |
2cbaf68d |
805 | // |
fc6228e4 |
806 | if (!is_array($plugin_dependencies)) |
807 | return FALSE; |
2cbaf68d |
808 | |
809 | |
fc6228e4 |
810 | // make sure everything is in order... |
811 | // |
812 | if (!empty($plugin_dependencies)) |
2cbaf68d |
813 | { |
814 | |
fc6228e4 |
815 | $new_plugin_dependencies = array(); |
816 | foreach ($plugin_dependencies as $plugin_name => $plugin_requirements) |
2cbaf68d |
817 | { |
818 | |
fc6228e4 |
819 | // if $plugin_requirements isn't an array, this is old-style, |
820 | // where only the version number was given... |
2cbaf68d |
821 | // |
fc6228e4 |
822 | if (is_string($plugin_requirements)) |
823 | $plugin_requirements = array('version' => $plugin_requirements, |
824 | 'activate' => FALSE); |
825 | |
826 | |
827 | // trap badly formatted requirements arrays that don't have |
828 | // needed info |
2cbaf68d |
829 | // |
fc6228e4 |
830 | if (!is_array($plugin_requirements) |
831 | || !isset($plugin_requirements['version'])) |
832 | continue; |
833 | if (!isset($plugin_requirements['activate'])) |
834 | $plugin_requirements['activate'] = FALSE; |
835 | |
836 | |
837 | // parse version into something we understand? |
2cbaf68d |
838 | // |
e1a125cd |
839 | if ($do_parse && $plugin_requirements['version'] != SQ_INCOMPATIBLE) |
fc6228e4 |
840 | { |
841 | |
842 | // massage version number into something we understand |
843 | // |
844 | // the first regexp strips everything and anything that follows |
845 | // the first occurance of a non-digit (or non decimal point), so |
846 | // beware that putting letters in the middle of a version string |
847 | // will effectively truncate the version string right there (but |
848 | // this also just helps remove the SquirrelMail version part off |
849 | // of versions such as "1.2.3-1.4.4") |
850 | // |
851 | // the second regexp just strips out non-digits/non-decimal points |
852 | // (and might be redundant(?)) |
853 | // |
854 | // the regexps are wrapped in a trim that makes sure the version |
855 | // does not start or end with a decimal point |
856 | // |
15a8ad6b |
857 | if (strpos(strtoupper($plugin_requirements['version']), 'CORE') === 0) |
858 | { |
859 | if (strpos($plugin_requirements['version'], ':') === FALSE) |
860 | $plugin_requirements['version'] = 'CORE'; |
861 | else |
862 | $plugin_requirements['version'] |
863 | = 'CORE:' . trim(preg_replace(array('/[^0-9.]+.*$/', '/[^0-9.]/'), |
864 | '', substr($plugin_requirements['version'], strpos($plugin_requirements['version'], ':') + 1)), |
865 | '.'); |
866 | } |
867 | else |
868 | $plugin_requirements['version'] |
869 | = trim(preg_replace(array('/[^0-9.]+.*$/', '/[^0-9.]/'), |
870 | '', $plugin_requirements['version']), |
871 | '.'); |
fc6228e4 |
872 | |
873 | } |
874 | |
875 | $new_plugin_dependencies[$plugin_name] = $plugin_requirements; |
2cbaf68d |
876 | |
877 | } |
878 | |
879 | $plugin_dependencies = $new_plugin_dependencies; |
880 | |
881 | } |
882 | |
883 | return $plugin_dependencies; |
884 | |
885 | } |
886 | |
887 | /** |
888 | * Check a plugin's other plugin dependencies. |
889 | * |
890 | * Determines whether or not all of the given |
891 | * plugin's required plugins are installed and |
fc6228e4 |
892 | * up to the proper version, and if they are |
893 | * activated if required. |
2cbaf68d |
894 | * |
895 | * By default, the desired plugin must be currently |
896 | * activated, and if it is not, this function will |
897 | * return FALSE. By overriding the default value |
898 | * of $force_inclusion, this function will attempt |
899 | * to grab dependency information from the given |
900 | * plugin even if it is not activated (plugin still |
901 | * has to be unpackaged and set in place in the |
902 | * plugins directory). Use with care - some plugins |
903 | * might break SquirrelMail when this is used. |
904 | * |
905 | * NOTE that if a plugin does not report whether or |
906 | * not it has other plugin dependencies, this function |
907 | * will return TRUE, although that is possibly incorrect |
908 | * or misleading. |
909 | * |
2cbaf68d |
910 | * @since 1.5.2 |
911 | * |
912 | * @param string plugin_name name of the plugin to |
913 | * check; must precisely |
914 | * match the plugin |
915 | * directory name |
916 | * @param bool force_inclusion try to get version info |
917 | * for plugins not activated? |
918 | * (default FALSE) |
919 | * |
920 | * @return mixed Boolean TRUE if all of the plugin's |
921 | * required plugins are correctly installed, |
f258865c |
922 | * the constant SQ_INCOMPATIBLE is returned if |
03a69f57 |
923 | * the given plugin is entirely incompatible |
924 | * with the current SquirrelMail version, |
2cbaf68d |
925 | * otherwise an array of the required plugins |
926 | * that are either not installed or not up to |
927 | * the minimum required version. The array is |
fc6228e4 |
928 | * keyed by plugin name where values are arrays |
929 | * again, where at least the following keys (and |
930 | * corresponding values) will be available: |
931 | * 'version' - value is the minimum version |
932 | * required for that plugin (in printable, non- |
e1a125cd |
933 | * parsed format) or the constant SQ_INCOMPATIBLE, |
934 | * which indicates that the plugin is actually |
935 | * incompatible (not required), 'activate' - value |
936 | * is boolean: TRUE indicates that the plugin must |
937 | * also be activated, FALSE means that it only needs |
938 | * to be present, but does not need to be activated. |
2cbaf68d |
939 | * |
940 | */ |
941 | function check_plugin_dependencies($plugin_name, $force_inclusion = FALSE) |
942 | { |
943 | |
944 | $dependencies = get_plugin_dependencies($plugin_name, $force_inclusion); |
945 | if (!$dependencies) return TRUE; |
f258865c |
946 | if ($dependencies === SQ_INCOMPATIBLE) return $dependencies; |
2cbaf68d |
947 | $missing_or_bad = array(); |
948 | |
fc6228e4 |
949 | foreach ($dependencies as $depend_name => $depend_requirements) |
2cbaf68d |
950 | { |
15a8ad6b |
951 | |
952 | // check for core plugins first |
953 | // |
954 | if (strpos(strtoupper($depend_requirements['version']), 'CORE') === 0) |
955 | { |
956 | |
957 | // see if the plugin is in the core (just check if the directory exists) |
958 | // |
959 | if (!file_exists(SM_PATH . 'plugins/' . $depend_name)) |
960 | $missing_or_bad[$depend_name] = $depend_requirements; |
961 | |
962 | |
963 | // check if it is activated if need be |
964 | // |
965 | else if ($depend_requirements['activate'] && !is_plugin_enabled($depend_name)) |
966 | $missing_or_bad[$depend_name] = $depend_requirements; |
967 | |
968 | |
969 | // check if this is the right core version if one is given |
970 | // (note this is pretty useless - a plugin should specify |
971 | // whether or not it itself is compatible with this version |
972 | // of SM in the first place) |
973 | // |
974 | else if (strpos($depend_requirements['version'], ':') !== FALSE) |
975 | { |
976 | $version = explode('.', substr($depend_requirements['version'], strpos($depend_requirements['version'], ':') + 1), 3); |
977 | $version[0] = intval($version[0]); |
978 | $version[1] = intval($version[1]); |
979 | $version[2] = intval($version[2]); |
980 | |
981 | if (!check_sm_version($version[0], $version[1], $version[2])) |
982 | $missing_or_bad[$depend_name] = $depend_requirements; |
983 | } |
984 | |
985 | continue; |
e1a125cd |
986 | |
987 | } |
988 | |
989 | // if the plugin is actually incompatible; check that it |
990 | // is not activated |
991 | // |
992 | if ($depend_requirements['version'] == SQ_INCOMPATIBLE) |
993 | { |
994 | |
995 | if (is_plugin_enabled($depend_name)) |
996 | $missing_or_bad[$depend_name] = $depend_requirements; |
997 | |
998 | continue; |
15a8ad6b |
999 | |
1000 | } |
1001 | |
1002 | // check for normal plugins |
1003 | // |
a895042a |
1004 | $version = explode('.', $depend_requirements['version'], 3); |
fc6228e4 |
1005 | $version[0] = intval($version[0]); |
1006 | $version[1] = intval($version[1]); |
2cbaf68d |
1007 | $version[2] = intval($version[2]); |
fc6228e4 |
1008 | |
1009 | $force_dependency_inclusion = !$depend_requirements['activate']; |
1010 | |
1011 | if (!check_plugin_version($depend_name, $version[0], $version[1], |
1012 | $version[2], $force_dependency_inclusion)) |
1013 | $missing_or_bad[$depend_name] = $depend_requirements; |
2cbaf68d |
1014 | } |
1015 | |
1016 | if (empty($missing_or_bad)) return TRUE; |
1017 | |
1018 | |
1019 | // get non-parsed required versions |
1020 | // |
fc6228e4 |
1021 | $non_parsed_dependencies = get_plugin_dependencies($plugin_name, |
1022 | $force_inclusion, |
1023 | FALSE); |
2cbaf68d |
1024 | $return_array = array(); |
1025 | foreach ($missing_or_bad as $depend_name => $ignore) |
1026 | $return_array[$depend_name] = $non_parsed_dependencies[$depend_name]; |
1027 | |
1028 | return $return_array; |
1029 | |
1030 | } |
1031 | |