PageRenderTime 6ms CodeModel.GetById 27ms app.highlight 18ms RepoModel.GetById 1ms app.codeStats 1ms

/trunk/squirrelmail/functions/plugin.php

#
PHP | 1035 lines | 307 code | 125 blank | 603 comment | 111 complexity | 274d67e42ef83ecfd7a86ee59e9e7fa4 MD5 | raw file
   1<?php
   2
   3/**
   4 * plugin.php
   5 *
   6 * This file provides the framework for a plugin architecture.
   7 *
   8 * Documentation on how to write plugins might show up some time.
   9 *
  10 * @copyright 1999-2012 The SquirrelMail Project Team
  11 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  12 * @version $Id: plugin.php 14249 2012-01-02 02:09:17Z pdontthink $
  13 * @package squirrelmail
  14 */
  15
  16/**
  17 * This function adds a plugin.
  18 * @param string $name Internal plugin name (ie. delete_move_next)
  19 * @return void
  20 */
  21function use_plugin ($name) {
  22    if (file_exists(SM_PATH . "plugins/$name/setup.php")) {
  23        include_once(SM_PATH . "plugins/$name/setup.php");
  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        //}
  36    }
  37}
  38
  39/**
  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
  61 * @param mixed &$args A single value or an array of arguments 
  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.
  70 *
  71 */
  72function do_hook($name, &$args) {
  73
  74    global $squirrelmail_plugin_hooks, $currentHookName;
  75    $currentHookName = $name;
  76    $ret = NULL;
  77
  78    if (isset($squirrelmail_plugin_hooks[$name])
  79          && is_array($squirrelmail_plugin_hooks[$name])) {
  80        foreach ($squirrelmail_plugin_hooks[$name] as $plugin_name => $function) {
  81            use_plugin($plugin_name);
  82            if (function_exists($function)) {
  83                $ret = $function($args, $ret);
  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;
  90            }
  91        }
  92    }
  93
  94    $currentHookName = '';
  95    return $ret;
  96
  97}
  98
  99/**
 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.
 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
 113 * with all returned strings concatenated together is returned 
 114 * (unless $force_array is TRUE).
 115 *
 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 *
 122 * @param string  $name Name of hook being executed
 123 * @param mixed &$args A single value or an array of arguments 
 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).
 137 *
 138 * @return mixed the merged return arrays or strings of each
 139 *               plugin on this hook.
 140 *
 141 */
 142function concat_hook_function($name, &$args, $force_array=FALSE) {
 143
 144    global $squirrelmail_plugin_hooks, $currentHookName;
 145    $currentHookName = $name;
 146    $ret = '';
 147
 148    if (isset($squirrelmail_plugin_hooks[$name])
 149          && is_array($squirrelmail_plugin_hooks[$name])) {
 150        foreach ($squirrelmail_plugin_hooks[$name] as $plugin_name => $function) {
 151            use_plugin($plugin_name);
 152            if (function_exists($function)) {
 153                $plugin_ret = $function($args);
 154                if (!empty($plugin_ret)) {
 155                    $ret = sqm_array_merge($ret, $plugin_ret);
 156                }
 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;
 163            }
 164        }
 165    }
 166
 167    if ($force_array && is_string($ret)) {
 168        $ret = array($ret);
 169    }
 170
 171    $currentHookName = '';
 172    return $ret;
 173
 174}
 175
 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.
 181 * Priority 0 means majority rules.  Ties will be broken with $tie
 182 *
 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
 190 * @param mixed   &$args    A single value or an array of arguments 
 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 *
 201 */
 202function boolean_hook_function($name, &$args, $priority=0, $tie=false) {
 203
 204    global $squirrelmail_plugin_hooks, $currentHookName;
 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 */
 213        $currentHookName = $name;
 214        foreach ($squirrelmail_plugin_hooks[$name] as $plugin_name => $function) {
 215            use_plugin($plugin_name);
 216            if (function_exists($function)) {
 217                $ret = $function($args);
 218                if ($ret) {
 219                    $yea++;
 220                } else {
 221                    $nay++;
 222                }
 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;
 229            }
 230        }
 231        $currentHookName = '';
 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;
 249
 250}
 251
 252/**
 253 * Do not use, use checkForJavascript() instead.
 254 *
 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.
 258 * *** THIS IS A TEST FOR JAVASCRIPT SUPPORT ***
 259 *
 260 * @return bool whether this browser properly supports JavaScript
 261 * @deprecated use checkForJavascript() since 1.5.1
 262 */
 263function soupNazi(){
 264    return !checkForJavascript();
 265}
 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
 276  /**
 277   * check if variable is empty. if var is not set, php empty
 278   * returns true without error notice.
 279   *
 280   * then check if it is an array
 281   */
 282  if (empty($plugins) || ! is_array($plugins))
 283    return false;
 284
 285  if ( in_array($plugin_name,$plugins) ) {
 286    return true;
 287  } else {
 288    return false;
 289  }
 290}
 291
 292/**
 293  * Get a plugin's version.
 294  *
 295  * Determines and returns a plugin's version.
 296  *
 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
 304  * plugins directory).  Use with care - some plugins
 305  * might break SquirrelMail when this is used.
 306  *
 307  * By turning on the $do_parse argument, the version
 308  * string will be parsed by SquirrelMail into a
 309  * SquirrelMail-compatible version string (such as
 310  * "1.2.3") if it is not already.
 311  *
 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.
 319  *
 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,
 326  * and so forth.
 327  *
 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)
 337  * @param bool do_parse        return the plugin version
 338  *                             in SquirrelMail-compatible
 339  *                             format (default FALSE)
 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  */
 346function get_plugin_version($plugin_name, $force_inclusion = FALSE, $do_parse = FALSE)
 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();
 360   else if ($force_inclusion
 361    && file_exists(SM_PATH . 'plugins/' . $plugin_name . '/setup.php'))
 362   {
 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. 
 368      include_once(SM_PATH . 'plugins/' . $plugin_name . '/setup.php');
 369      if (function_exists($info_function))
 370         $plugin_info = $info_function();
 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
 411   }
 412   if (!empty($plugin_info['version']))
 413      $plugin_version = $plugin_info['version'];
 414
 415
 416   // otherwise, look for older version function
 417   //
 418   if (!$plugin_version && function_exists($version_function))
 419       $plugin_version = $version_function();
 420
 421
 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      //
 440      $plugin_version = trim(preg_replace(array('/[^0-9.]+.*$/', '/[^0-9.]/'),
 441                                          '', $plugin_version),
 442                             '.');
 443
 444   }
 445
 446   return $plugin_version;
 447
 448}
 449
 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
 462  * is not activated (the plugin still has to be 
 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  *
 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.
 482  * 
 483  * @since 1.5.2
 484  *
 485  * @param string plugin_name   Name of the plugin to
 486  *                             check; must precisely
 487  *                             match the plugin
 488  *                             directory name
 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
 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
 504   $plugin_version = get_plugin_version($plugin_name, $force_inclusion, TRUE);
 505   if (!$plugin_version) return FALSE;
 506
 507
 508   // split the version string into sections delimited by 
 509   // decimal points, and make sure we have three sections
 510   //
 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
 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  *
 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)
 566  *
 567  * @return mixed NULL is returned if the plugin could not be 
 568  *               found or does not include the given requirement,
 569  *               the constant SQ_INCOMPATIBLE is returned if the
 570  *               given plugin is entirely incompatible with the
 571  *               current SquirrelMail version (unless
 572  *               $ignore_incompatible is TRUE), otherwise the 
 573  *               value of the requirement is returned, whatever 
 574  *               that may be (varies per requirement type).
 575  *
 576  */
 577function get_plugin_requirement($plugin_name, $requirement, 
 578                                $ignore_incompatible = TRUE,
 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
 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
 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         // 
 682         if (check_sm_version($a, $b, $c) 
 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]))))
 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;
 700               if (!empty($requirement_overrides[SQ_INCOMPATIBLE])
 701                || $requirement_overrides === SQ_INCOMPATIBLE)
 702                  $requirement_value_override = SQ_INCOMPATIBLE;
 703               else
 704                  $requirement_value_override = $requirement_overrides[$requirement];
 705            }
 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
 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
 727  * minimum version numbers of the required plugins
 728  * and whether or not they need to be activated.
 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 
 745  * version formatting under the get_plugin_version()
 746  * function documentation.
 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, 
 765  *               the constant SQ_INCOMPATIBLE is returned if 
 766  *               the given plugin is entirely incompatible 
 767  *               with the current SquirrelMail version, 
 768  *               otherwise an array is returned where keys 
 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 
 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
 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.
 787  *
 788  */
 789function get_plugin_dependencies($plugin_name, $force_inclusion = FALSE, 
 790                                 $do_parse = TRUE)
 791{
 792
 793   $plugin_dependencies = get_plugin_requirement($plugin_name, 
 794                                                 'required_plugins', 
 795                                                 FALSE,
 796                                                 $force_inclusion);
 797
 798   // the plugin is simply incompatible, no need to continue here
 799   //
 800   if ($plugin_dependencies === SQ_INCOMPATIBLE)
 801      return $plugin_dependencies;
 802
 803
 804   // not an array of requirements?  wrong format, just return FALSE
 805   //
 806   if (!is_array($plugin_dependencies))
 807      return FALSE;
 808
 809
 810   // make sure everything is in order...
 811   //
 812   if (!empty($plugin_dependencies))
 813   {
 814
 815      $new_plugin_dependencies = array();
 816      foreach ($plugin_dependencies as $plugin_name => $plugin_requirements)
 817      {
 818
 819         // if $plugin_requirements isn't an array, this is old-style,
 820         // where only the version number was given...
 821         //
 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
 829         //
 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?
 838         //
 839         if ($do_parse && $plugin_requirements['version'] != SQ_INCOMPATIBLE)
 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            //
 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                                      '.');
 872
 873         }
 874
 875         $new_plugin_dependencies[$plugin_name] = $plugin_requirements;
 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
 892  * up to the proper version, and if they are 
 893  * activated if required.
 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  *
 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,
 922  *               the constant SQ_INCOMPATIBLE is returned if 
 923  *               the given plugin is entirely incompatible 
 924  *               with the current SquirrelMail version, 
 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
 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-
 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.  
 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;
 946   if ($dependencies === SQ_INCOMPATIBLE) return $dependencies;
 947   $missing_or_bad = array();
 948
 949   foreach ($dependencies as $depend_name => $depend_requirements)
 950   {
 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            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;
 982
 983            if (!check_sm_version($version[0], $version[1], $version[2]))
 984               $missing_or_bad[$depend_name] = $depend_requirements;
 985         }
 986
 987         continue;
 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;
1001
1002      }
1003
1004      // check for normal plugins
1005      //
1006      $version = explode('.', $depend_requirements['version'], 3);
1007      $version[0] = intval($version[0]);
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;
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;
1018   }
1019
1020   if (empty($missing_or_bad)) return TRUE;
1021
1022
1023   // get non-parsed required versions
1024   //
1025   $non_parsed_dependencies = get_plugin_dependencies($plugin_name, 
1026                                                      $force_inclusion, 
1027                                                      FALSE);
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