Happy New Year
[squirrelmail.git] / functions / plugin.php
CommitLineData
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 *
8ed19238 10 * @copyright 1999-2019 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 21function 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 72function 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 142function 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 202function 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 */
263function 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 */
273function 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 346function 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 */
499function 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 */
577function 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 789function 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 */
941function 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]);
9fee3012 978 if (isset($version[1])) $version[1] = intval($version[1]);
979 else $version[1] = 0;
980 if (isset($version[2])) $version[2] = intval($version[2]);
981 else $version[2] = 0;
15a8ad6b 982
983 if (!check_sm_version($version[0], $version[1], $version[2]))
984 $missing_or_bad[$depend_name] = $depend_requirements;
985 }
986
987 continue;
e1a125cd 988
989 }
990
991 // if the plugin is actually incompatible; check that it
992 // is not activated
993 //
994 if ($depend_requirements['version'] == SQ_INCOMPATIBLE)
995 {
996
997 if (is_plugin_enabled($depend_name))
998 $missing_or_bad[$depend_name] = $depend_requirements;
999
1000 continue;
15a8ad6b 1001
1002 }
1003
1004 // check for normal plugins
1005 //
a895042a 1006 $version = explode('.', $depend_requirements['version'], 3);
fc6228e4 1007 $version[0] = intval($version[0]);
f135ba35 1008 if (isset($version[1])) $version[1] = intval($version[1]);
1009 else $version[1] = 0;
1010 if (isset($version[2])) $version[2] = intval($version[2]);
1011 else $version[2] = 0;
fc6228e4 1012
1013 $force_dependency_inclusion = !$depend_requirements['activate'];
1014
1015 if (!check_plugin_version($depend_name, $version[0], $version[1],
1016 $version[2], $force_dependency_inclusion))
1017 $missing_or_bad[$depend_name] = $depend_requirements;
2cbaf68d 1018 }
1019
1020 if (empty($missing_or_bad)) return TRUE;
1021
1022
1023 // get non-parsed required versions
1024 //
fc6228e4 1025 $non_parsed_dependencies = get_plugin_dependencies($plugin_name,
1026 $force_inclusion,
1027 FALSE);
2cbaf68d 1028 $return_array = array();
1029 foreach ($missing_or_bad as $depend_name => $ignore)
1030 $return_array[$depend_name] = $non_parsed_dependencies[$depend_name];
1031
1032 return $return_array;
1033
1034}
1035