commit 219d19c41071ef9698f6232c27a8b877a105bd2b
Author: Assos Assos <assos+forge@centrale-marseille.fr>
Date:   Wed Oct 28 23:19:18 2020 +0100

    Weekly update of contrib modules

diff --git a/drupal7/sites/all/modules/ctools/README.txt b/drupal7/sites/all/modules/ctools/README.txt
new file mode 100644
index 00000000..abdd913e
--- /dev/null
+++ b/drupal7/sites/all/modules/ctools/README.txt
@@ -0,0 +1,78 @@
+CONTENTS OF THIS FILE
+---------------------
+
+ * Introduction
+ * Requirements
+ * Recommended Modules
+ * Installation
+ * Configuration
+
+
+INTRODUCTION
+------------
+
+The Chaos tool suite (ctools) module is primarily a set of APIs and tools to
+improve the developer experience. It also contains a module called the Page
+Manager whose job is to manage pages. In particular it manages panel pages, but
+as it grows it will be able to manage far more than just Panels.
+
+The Chaos Tool Suite (ctools) is a series of tools that makes code readily
+available for developers and creates libraries for other modules to use. Modules
+that use ctools include Views and Panels.
+
+End users will use ctools as underlying user interface libraries when operating
+Views and Panels modules and will not need to explore further (ctools is geared
+more toward developer usage). Developers will use the module differently and
+work more with the tools provided.
+
+For the moment, it includes the following tools:
+
+ * Plugins -- tools to make it easy for modules to let other modules implement
+   plugins from .inc files.
+ * Exportables -- tools to make it easier for modules to have objects that live
+   in database or live in code, such as 'default views'.
+ * AJAX responder -- tools to make it easier for the server to handle AJAX
+   requests and tell the client what to do with them.
+ * Form tools -- tools to make it easier for forms to deal with AJAX.
+ * Object caching -- tool to make it easier to edit an object across multiple
+   page requests and cache the editing work.
+ * Contexts -- the notion of wrapping objects in a unified wrapper and providing
+   an API to create and accept these contexts as input.
+ * Modal dialog -- tool to make it simple to put a form in a modal dialog.
+ * Dependent -- a simple form widget to make form items appear and disappear
+   based upon the selections in another item.
+ * Content -- pluggable content types used as panes in Panels and other modules
+   like Dashboard.
+ * Form wizard -- an API to make multi-step forms much easier.
+ * CSS tools -- tools to cache and sanitize CSS easily to make user-input CSS
+   safe.
+
+ * For a full description of the module visit:
+   https://www.drupal.org/project/ctools
+
+ * To submit bug reports and feature suggestions, or to track changes visit:
+   https://www.drupal.org/project/issues/ctools
+
+
+REQUIREMENTS
+------------
+
+This module requires no modules outside of Drupal core.
+
+
+RECOMMENDED MODULES
+-------------------
+
+The Advanced help module provides extended documentation. Once enabled,
+navigate to Administration > Advanced Help and select the Chaos tools link to
+view documentation.
+
+ * Advanced help - https://www.drupal.org/project/advanced_help
+
+
+INSTALLATION
+------------
+
+ * Install the Chaos tool suite module as you would normally install a
+   contributed Drupal module. Visit https://www.drupal.org/node/895232 for
+   further information.
diff --git a/drupal7/sites/all/modules/ctools/bulk_export/bulk_export.info b/drupal7/sites/all/modules/ctools/bulk_export/bulk_export.info
index 9bb33944..7ea99608 100644
--- a/drupal7/sites/all/modules/ctools/bulk_export/bulk_export.info
+++ b/drupal7/sites/all/modules/ctools/bulk_export/bulk_export.info
@@ -4,8 +4,8 @@ core = 7.x
 dependencies[] = ctools
 package = Chaos tool suite
 
-; Information added by Drupal.org packaging script on 2019-02-08
-version = "7.x-1.15"
+; Information added by Drupal.org packaging script on 2020-10-23
+version = "7.x-1.17"
 core = "7.x"
 project = "ctools"
-datestamp = "1549603691"
+datestamp = "1603490551"
diff --git a/drupal7/sites/all/modules/ctools/ctools.api.php b/drupal7/sites/all/modules/ctools/ctools.api.php
index 4481291a..0948e879 100644
--- a/drupal7/sites/all/modules/ctools/ctools.api.php
+++ b/drupal7/sites/all/modules/ctools/ctools.api.php
@@ -30,6 +30,8 @@ function hook_ctools_plugin_type() {
 }
 
 /**
+ * Tells CTools where to find module-defined plugins.
+ *
  * This hook is used to inform the CTools plugin system about the location of a
  * directory that should be searched for files containing plugins of a
  * particular type. CTools invokes this same hook for all plugins, using the
@@ -104,12 +106,12 @@ function hook_ctools_plugin_directory($owner, $plugin_type) {
  * This hook is useful for altering flags or other information that will be
  * used or possibly overriden by the process hook if defined.
  *
- * @param $plugin
+ * @param array $plugin
  *   An associative array defining a plugin.
- * @param $info
+ * @param array $info
  *   An associative array of plugin type info.
  */
-function hook_ctools_plugin_pre_alter(&$plugin, &$info) {
+function hook_ctools_plugin_pre_alter(array &$plugin, array &$info) {
   // Override a function defined by the plugin.
   if ($info['type'] == 'my_type') {
     $plugin['my_flag'] = 'new_value';
@@ -122,12 +124,12 @@ function hook_ctools_plugin_pre_alter(&$plugin, &$info) {
  * This hook is useful for overriding the final values for a plugin after it
  * has been processed.
  *
- * @param $plugin
+ * @param array $plugin
  *   An associative array defining a plugin.
- * @param $info
+ * @param array $info
  *   An associative array of plugin type info.
  */
-function hook_ctools_plugin_post_alter(&$plugin, &$info) {
+function hook_ctools_plugin_post_alter(array &$plugin, array &$info) {
   // Override a function defined by the plugin.
   if ($info['type'] == 'my_type') {
     $plugin['my_function'] = 'new_function';
@@ -144,7 +146,7 @@ function hook_ctools_plugin_post_alter(&$plugin, &$info) {
  *   An array of informations about the implementors of a certain api.
  *   The key of this array are the module names/theme names.
  */
-function hook_ctools_api_hook_alter(&$list) {
+function hook_ctools_api_hook_alter(array &$list) {
   // Alter the path of the node implementation.
   $list['node']['path'] = drupal_get_path('module', 'node');
 }
@@ -152,30 +154,55 @@ function hook_ctools_api_hook_alter(&$list) {
 /**
  * Alter the available functions to be used in ctools math expression api.
  *
- * One usecase would be to create your own function in your module and
+ * One use case would be to create your own function in your module and
  * allow to use it in the math expression api.
  *
- * @param $functions
+ * @param array $functions
  *   An array which has the functions as value.
+ * @param array $context
+ *   An array containing an item 'final' whose value is a reference to the
+ *   definitions for multiple-arg functions. Use this to add in functions that
+ *   require more than one arg.
  */
-function hook_ctools_math_expression_functions_alter(&$functions) {
-  // Allow to convert from degrees to radiant.
+function hook_ctools_math_expression_functions_alter(array &$functions, array $context) {
+  // Allow to convert from degrees to radians.
   $functions[] = 'deg2rad';
+
+  $multiarg = $context['final'];
+  $multiarg['pow'] = array(
+    'function' => 'pow',
+    'arguments' => 2,
+  );
+}
+
+/**
+ * Alter the available functions to be used in ctools math expression api.
+ *
+ * One usecase would be to create your own function in your module and
+ * allow to use it in the math expression api.
+ *
+ * @param array $constants
+ *   An array of name:value pairs, one for each named constant. Values added
+ *   to this array become read-only variables with the value assigned here.
+ */
+function hook_ctools_math_expression_constants_alter(array &$constants) {
+  // Add the speed of light as constant 'c':
+  $constants['c'] = 299792458;
 }
 
 /**
  * Alter everything.
  *
- * @param $info
+ * @param array $info
  *   An associative array containing the following keys:
  *   - content: The rendered content.
  *   - title: The content's title.
  *   - no_blocks: A boolean to decide if blocks should be displayed.
- * @param $page
+ * @param bool $page
  *   If TRUE then this renderer owns the page and can use theme('page')
  *   for no blocks; if false, output is returned regardless of any no
  *   blocks settings.
- * @param $context
+ * @param array $context
  *   An associative array containing the following keys:
  *   - args: The raw arguments behind the contexts.
  *   - contexts: The context objects in use.
@@ -183,7 +210,7 @@ function hook_ctools_math_expression_functions_alter(&$functions) {
  *   - subtask: The subtask object in use.
  *   - handler: The handler object in use.
  */
-function hook_ctools_render_alter(&$info, &$page, &$context) {
+function hook_ctools_render_alter(array &$info, &$page, array &$context) {
   if ($context['handler']->name == 'my_handler') {
     ctools_add_css('my_module.theme', 'my_module');
   }
@@ -219,7 +246,7 @@ function hook_ctools_content_subtype_alter($subtype, $plugin) {
  * @param string $plugin_id
  *   The plugin ID, in the format NAME:KEY.
  */
-function hook_ctools_entity_context_alter(&$plugin, &$entity, $plugin_id) {
+function hook_ctools_entity_context_alter(array &$plugin, array &$entity, $plugin_id) {
   ctools_include('context');
   switch ($plugin_id) {
     case 'entity_id:taxonomy_term':
@@ -242,13 +269,13 @@ function hook_ctools_entity_context_alter(&$plugin, &$entity, $plugin_id) {
  *   A string associated with the plugin type, identifying the operation.
  * @param string $value
  *   The value being converted; this is the only return from the function.
- * @param $converter_options
+ * @param array $converter_options
  *   Array of key-value pairs to pass to a converter function from higher
  *   levels.
  *
  * @see ctools_context_convert_context()
  */
-function hook_ctools_context_converter_alter($context, $converter, &$value, $converter_options) {
+function hook_ctools_context_converter_alter(ctools_context $context, $converter, &$value, array $converter_options) {
   if ($converter === 'mystring') {
     $value = 'fixed';
   }
@@ -262,7 +289,7 @@ function hook_ctools_context_converter_alter($context, $converter, &$value, $con
  *
  * @see hook_ctools_entity_context_alter()
  */
-function hook_ctools_entity_contexts_alter(&$plugins) {
+function hook_ctools_entity_contexts_alter(array &$plugins) {
   $plugins['entity_id:taxonomy_term']['no ui'] = TRUE;
 }
 
@@ -274,7 +301,7 @@ function hook_ctools_entity_contexts_alter(&$plugins) {
  *
  * @see ctools_cleanstring()
  */
-function hook_ctools_cleanstring_alter(&$settings) {
+function hook_ctools_cleanstring_alter(array &$settings) {
   // Convert all strings to lower case.
   $settings['lower case'] = TRUE;
 }
@@ -287,7 +314,7 @@ function hook_ctools_cleanstring_alter(&$settings) {
  *
  * @see ctools_cleanstring()
  */
-function hook_ctools_cleanstring_CLEAN_ID_alter(&$settings) {
+function hook_ctools_cleanstring_CLEAN_ID_alter(array &$settings) {
   // Convert all strings to lower case.
   $settings['lower case'] = TRUE;
 }
@@ -304,7 +331,7 @@ function hook_ctools_cleanstring_CLEAN_ID_alter(&$settings) {
  *
  * @see ctools_context_handler_pre_render()
  */
-function ctools_context_handler_pre_render($handler, $contexts, $args) {
+function ctools_context_handler_pre_render($handler, array $contexts, array $args) {
   $handler->conf['css_id'] = 'my-id';
 }
 
diff --git a/drupal7/sites/all/modules/ctools/ctools.info b/drupal7/sites/all/modules/ctools/ctools.info
index 188b55dd..49850462 100644
--- a/drupal7/sites/all/modules/ctools/ctools.info
+++ b/drupal7/sites/all/modules/ctools/ctools.info
@@ -19,8 +19,8 @@ files[] = tests/object_cache.test
 files[] = tests/object_cache_unit.test
 files[] = tests/page_tokens.test
 
-; Information added by Drupal.org packaging script on 2019-02-08
-version = "7.x-1.15"
+; Information added by Drupal.org packaging script on 2020-10-23
+version = "7.x-1.17"
 core = "7.x"
 project = "ctools"
-datestamp = "1549603691"
+datestamp = "1603490551"
diff --git a/drupal7/sites/all/modules/ctools/ctools.module b/drupal7/sites/all/modules/ctools/ctools.module
index 62f29192..2ec0ab73 100644
--- a/drupal7/sites/all/modules/ctools/ctools.module
+++ b/drupal7/sites/all/modules/ctools/ctools.module
@@ -479,7 +479,7 @@ function ctools_uuid_generate() {
   if (!module_exists('uuid')) {
     ctools_include('uuid');
 
-    $callback = drupal_static(__FUNCTION__);
+    $callback = &drupal_static(__FUNCTION__);
 
     if (empty($callback)) {
       if (function_exists('uuid_create') && !function_exists('uuid_make')) {
@@ -903,13 +903,14 @@ function ctools_process(&$variables, $hook) {
  */
 
 function ctools_access_menu($access) {
+  $func_args = func_get_args();
   // Short circuit everything if there are no access tests.
   if (empty($access['plugins'])) {
     return TRUE;
   }
 
   $contexts = array();
-  foreach (func_get_args() as $arg) {
+  foreach ($func_args as $arg) {
     if (is_object($arg) && get_class($arg) == 'ctools_context') {
       $contexts[$arg->id] = $arg;
     }
diff --git a/drupal7/sites/all/modules/ctools/ctools_access_ruleset/ctools_access_ruleset.info b/drupal7/sites/all/modules/ctools/ctools_access_ruleset/ctools_access_ruleset.info
index 3fc35564..076bad0c 100644
--- a/drupal7/sites/all/modules/ctools/ctools_access_ruleset/ctools_access_ruleset.info
+++ b/drupal7/sites/all/modules/ctools/ctools_access_ruleset/ctools_access_ruleset.info
@@ -4,8 +4,8 @@ core = 7.x
 package = Chaos tool suite
 dependencies[] = ctools
 
-; Information added by Drupal.org packaging script on 2019-02-08
-version = "7.x-1.15"
+; Information added by Drupal.org packaging script on 2020-10-23
+version = "7.x-1.17"
 core = "7.x"
 project = "ctools"
-datestamp = "1549603691"
+datestamp = "1603490551"
diff --git a/drupal7/sites/all/modules/ctools/ctools_ajax_sample/ctools_ajax_sample.info b/drupal7/sites/all/modules/ctools/ctools_ajax_sample/ctools_ajax_sample.info
index c2a52df4..f52d44ef 100644
--- a/drupal7/sites/all/modules/ctools/ctools_ajax_sample/ctools_ajax_sample.info
+++ b/drupal7/sites/all/modules/ctools/ctools_ajax_sample/ctools_ajax_sample.info
@@ -4,8 +4,8 @@ package = Chaos tool suite
 dependencies[] = ctools
 core = 7.x
 
-; Information added by Drupal.org packaging script on 2019-02-08
-version = "7.x-1.15"
+; Information added by Drupal.org packaging script on 2020-10-23
+version = "7.x-1.17"
 core = "7.x"
 project = "ctools"
-datestamp = "1549603691"
+datestamp = "1603490551"
diff --git a/drupal7/sites/all/modules/ctools/ctools_ajax_sample/ctools_ajax_sample.module b/drupal7/sites/all/modules/ctools/ctools_ajax_sample/ctools_ajax_sample.module
index 7cb1ff1c..c022fdb6 100644
--- a/drupal7/sites/all/modules/ctools/ctools_ajax_sample/ctools_ajax_sample.module
+++ b/drupal7/sites/all/modules/ctools/ctools_ajax_sample/ctools_ajax_sample.module
@@ -187,7 +187,7 @@ function ctools_ajax_sample_page() {
     );
   }
 
-  $output .= theme('table', array('header' => $header, 'rows' => $rows, array('class' => array('ajax-sample-table'))));
+  $output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('class' => array('ajax-sample-table'))));
 
   // Show examples of ctools javascript widgets.
   $output .= '<h2>' . t('CTools Javascript Widgets') . '</h2>';
diff --git a/drupal7/sites/all/modules/ctools/ctools_custom_content/ctools_custom_content.info b/drupal7/sites/all/modules/ctools/ctools_custom_content/ctools_custom_content.info
index 2bc73489..6340319b 100644
--- a/drupal7/sites/all/modules/ctools/ctools_custom_content/ctools_custom_content.info
+++ b/drupal7/sites/all/modules/ctools/ctools_custom_content/ctools_custom_content.info
@@ -4,8 +4,8 @@ core = 7.x
 package = Chaos tool suite
 dependencies[] = ctools
 
-; Information added by Drupal.org packaging script on 2019-02-08
-version = "7.x-1.15"
+; Information added by Drupal.org packaging script on 2020-10-23
+version = "7.x-1.17"
 core = "7.x"
 project = "ctools"
-datestamp = "1549603691"
+datestamp = "1603490551"
diff --git a/drupal7/sites/all/modules/ctools/ctools_plugin_example/ctools_plugin_example.info b/drupal7/sites/all/modules/ctools/ctools_plugin_example/ctools_plugin_example.info
index 3f268dbc..c2f41b33 100644
--- a/drupal7/sites/all/modules/ctools/ctools_plugin_example/ctools_plugin_example.info
+++ b/drupal7/sites/all/modules/ctools/ctools_plugin_example/ctools_plugin_example.info
@@ -7,8 +7,8 @@ dependencies[] = page_manager
 dependencies[] = advanced_help
 core = 7.x
 
-; Information added by Drupal.org packaging script on 2019-02-08
-version = "7.x-1.15"
+; Information added by Drupal.org packaging script on 2020-10-23
+version = "7.x-1.17"
 core = "7.x"
 project = "ctools"
-datestamp = "1549603691"
+datestamp = "1603490551"
diff --git a/drupal7/sites/all/modules/ctools/includes/math-expr.inc b/drupal7/sites/all/modules/ctools/includes/math-expr.inc
index d82281e9..eb90719d 100644
--- a/drupal7/sites/all/modules/ctools/includes/math-expr.inc
+++ b/drupal7/sites/all/modules/ctools/includes/math-expr.inc
@@ -1,391 +1,933 @@
 <?php
 
-/*
-================================================================================
-
-ctools_math_expr - PHP Class to safely evaluate math expressions
-Copyright (C) 2005 Miles Kaufmann <http://www.twmagic.com/>
-
-================================================================================
-
-NAME
-    ctools_math_expr - safely evaluate math expressions
-
-SYNOPSIS
-      include('ctools_math_expr.class.php');
-      $m = new ctools_math_expr;
-      // basic evaluation:
-      $result = $m->evaluate('2+2');
-      // supports: order of operation; parentheses; negation; built-in functions
-      $result = $m->evaluate('-8(5/2)^2*(1-sqrt(4))-8');
-      // create your own variables
-      $m->evaluate('a = e^(ln(pi))');
-      // or functions
-      $m->evaluate('f(x,y) = x^2 + y^2 - 2x*y + 1');
-      // and then use them
-      $result = $m->evaluate('3*f(42,a)');
-
-DESCRIPTION
-    Use the ctools_math_expr class when you want to evaluate mathematical expressions
-    from untrusted sources.  You can define your own variables and functions,
-    which are stored in the object.  Try it, it's fun!
-
-METHODS
-    $m->evalute($expr)
-        Evaluates the expression and returns the result.  If an error occurs,
-        prints a warning and returns false.  If $expr is a function assignment,
-        returns true on success.
-
-    $m->e($expr)
-        A synonym for $m->evaluate().
-
-    $m->vars()
-        Returns an associative array of all user-defined variables and values.
-
-    $m->funcs()
-        Returns an array of all user-defined functions.
-
-PARAMETERS
-    $m->suppress_errors
-        Set to true to turn off warnings when evaluating expressions
-
-    $m->last_error
-        If the last evaluation failed, contains a string describing the error.
-        (Useful when suppress_errors is on).
-
-AUTHOR INFORMATION
-    Copyright 2005, Miles Kaufmann.
-
-LICENSE
-    Redistribution and use in source and binary forms, with or without
-    modification, are permitted provided that the following conditions are
-    met:
-
-    1   Redistributions of source code must retain the above copyright
-        notice, this list of conditions and the following disclaimer.
-    2.  Redistributions in binary form must reproduce the above copyright
-        notice, this list of conditions and the following disclaimer in the
-        documentation and/or other materials provided with the distribution.
-    3.  The name of the author may not be used to endorse or promote
-        products derived from this software without specific prior written
-        permission.
-
-    THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
-    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-    DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
-    INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-    SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
-    STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
-    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-    POSSIBILITY OF SUCH DAMAGE.
-
-*/
-
+/**
+ * @file
+ * =============================================================================.
+ *
+ * ctools_math_expr - PHP Class to safely evaluate math expressions
+ * Copyright (C) 2005 Miles Kaufmann <http://www.twmagic.com/>
+ *
+ * =============================================================================
+ *
+ * NAME
+ *     ctools_math_expr - safely evaluate math expressions
+ *
+ * SYNOPSIS
+ *     $m = new ctools_math_expr();
+ *     // basic evaluation:
+ *     $result = $m->evaluate('2+2');
+ *     // supports: order of operation; parentheses; negation; built-in
+ *     // functions.
+ *     $result = $m->evaluate('-8(5/2)^2*(1-sqrt(4))-8');
+ *     // create your own variables
+ *     $m->evaluate('a = e^(ln(pi))');
+ *     // or functions
+ *     $m->evaluate('f(x,y) = x^2 + y^2 - 2x*y + 1');
+ *     // and then use them
+ *     $result = $m->evaluate('3*f(42,a)');
+ *
+ * DESCRIPTION
+ *     Use the ctools_math_expr class when you want to evaluate mathematical
+ *     expressions from untrusted sources.  You can define your own variables
+ *     and functions, which are stored in the object.  Try it, it's fun!
+ *
+ * AUTHOR INFORMATION
+ *     Copyright 2005, Miles Kaufmann.
+ *     Enhancements, 2005 onwards, Drupal Community.
+ *
+ * LICENSE
+ *     Redistribution and use in source and binary forms, with or without
+ *     modification, are permitted provided that the following conditions are
+ *     met:
+ *
+ *     1   Redistributions of source code must retain the above copyright
+ *         notice, this list of conditions and the following disclaimer.
+ *     2.  Redistributions in binary form must reproduce the above copyright
+ *         notice, this list of conditions and the following disclaimer in the
+ *         documentation and/or other materials provided with the distribution.
+ *     3.  The name of the author may not be used to endorse or promote
+ *         products derived from this software without specific prior written
+ *         permission.
+ *
+ *     THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ *     IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ *     WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ *     DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ *     INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ *     SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ *     HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ *     STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ *     ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ *     POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * ctools_math_expr Class.
+ */
 class ctools_math_expr {
-    var $suppress_errors = false;
-    var $last_error = null;
-
-    var $v = array('e'=>2.71,'pi'=>3.14); // variables (and constants)
-    var $f = array(); // user-defined functions
-    var $vb = array('e', 'pi'); // constants
-    var $fb = array(  // built-in functions
-        'sin','sinh','arcsin','asin','arcsinh','asinh',
-        'cos','cosh','arccos','acos','arccosh','acosh',
-        'tan','tanh','arctan','atan','arctanh','atanh',
-        'pow', 'exp',
-        'sqrt','abs','ln','log',
-        'time', 'ceil', 'floor', 'min', 'max', 'round');
 
   /**
-   * ctools_math_expr constructor.
+   * If TRUE do not call trigger_error on error other wise do.
+   *
+   * @var bool
    */
-  function __construct() {
-        // make the variables a little more accurate
-        $this->v['pi'] = pi();
-        $this->v['e'] = exp(1);
-        drupal_alter('ctools_math_expression_functions', $this->fb);
-    }
+  public $suppress_errors = FALSE;
+
+  /**
+   * The last error message reported.
+   *
+   * @var string
+   */
+  public $last_error = NULL;
+
+  /**
+   * List of all errors reported.
+   *
+   * @var array
+   */
+  public $errors = array();
+
+  /**
+   * Variable and constant values.
+   *
+   * @var array
+   */
+  protected $vars;
+
+  /**
+   * User defined functions.
+   *
+   * @var array
+   */
+  protected $userfuncs;
+
+  /**
+   * The names of constants, used to make constants read-only.
+   *
+   * @var array
+   */
+  protected $constvars;
+
+  /**
+   * Built in simple (one arg) functions.
+   * Merged into $this->funcs in constructor.
+   *
+   * @var array
+   */
+  protected $simplefuncs;
+
+  /**
+   * Definitions of all built-in functions.
+   *
+   * @var array
+   */
+  protected $funcs;
+
+  /**
+   * Operators and their precedence.
+   *
+   * @var array
+   */
+  protected $ops;
+
+  /**
+   * The set of operators using two arguments.
+   *
+   * @var array
+   */
+  protected $binaryops;
 
-    function e($expr) {
-        return $this->evaluate($expr);
+  /**
+   * Public constructor.
+   */
+  public function __construct() {
+    $this->userfuncs = array();
+    $this->simplefuncs = array(
+      'sin',
+      'sinh',
+      'asin',
+      'asinh',
+      'cos',
+      'cosh',
+      'acos',
+      'acosh',
+      'tan',
+      'tanh',
+      'atan',
+      'atanh',
+      'exp',
+      'sqrt',
+      'abs',
+      'log',
+      'ceil',
+      'floor',
+      'round',
+    );
+
+    $this->ops = array(
+      '+' => array('precedence' => 0),
+      '-' => array('precedence' => 0),
+      '*' => array('precedence' => 1),
+      '/' => array('precedence' => 1),
+      '^' => array('precedence' => 2, 'right' => TRUE),
+      '_' => array('precedence' => 1),
+      '==' => array('precedence' => -1),
+      '!=' => array('precedence' => -1),
+      '>=' => array('precedence' => -1),
+      '<=' => array('precedence' => -1),
+      '>' => array('precedence' => -1),
+      '<' => array('precedence' => -1),
+    );
+
+    $this->binaryops = array(
+      '+', '-', '*', '/', '^', '==', '!=', '<', '<=', '>=', '>',
+    );
+
+    $this->funcs = array(
+      'ln' => array(
+        'function' => 'log',
+        'arguments' => 1,
+      ),
+      'arcsin' => array(
+        'function' => 'asin',
+        'arguments' => 1,
+      ),
+      'arcsinh' => array(
+        'function' => 'asinh',
+        'arguments' => 1,
+      ),
+      'arccos' => array(
+        'function' => 'acos',
+        'arguments' => 1,
+      ),
+      'arccosh' => array(
+        'function' => 'acosh',
+        'arguments' => 1,
+      ),
+      'arctan' => array(
+        'function' => 'atan',
+        'arguments' => 1,
+      ),
+      'arctanh' => array(
+        'function' => 'atanh',
+        'arguments' => 1,
+      ),
+      'min' => array(
+        'function' => 'min',
+        'arguments' => 2,
+        'max arguments' => 99,
+      ),
+      'max' => array(
+        'function' => 'max',
+        'arguments' => 2,
+        'max arguments' => 99,
+      ),
+      'pow' => array(
+        'function' => 'pow',
+        'arguments' => 2,
+      ),
+      'if' => array(
+        'function' => 'ctools_math_expr_if',
+        'arguments' => 2,
+        'max arguments' => 3,
+      ),
+      'number' => array(
+        'function' => 'ctools_math_expr_number',
+        'arguments' => 1,
+      ),
+      'time' => array(
+        'function' => 'time',
+        'arguments' => 0,
+      ),
+    );
+
+    // Allow modules to add custom functions.
+    $context = array('final' => &$this->funcs);
+    drupal_alter('ctools_math_expression_functions', $this->simplefuncs, $context);
+
+    // Set up the initial constants and mark them read-only.
+    $this->vars = array('e' => exp(1), 'pi' => pi());
+    drupal_alter('ctools_math_expression_constants', $this->vars);
+    $this->constvars = array_keys($this->vars);
+
+    // Translate the older, simpler style into the newer, richer style.
+    foreach ($this->simplefuncs as $function) {
+      $this->funcs[$function] = array(
+        'function' => $function,
+        'arguments' => 1,
+      );
     }
+  }
 
-    function evaluate($expr) {
-        $this->last_error = null;
-        $expr = trim($expr);
-        if (substr($expr, -1, 1) == ';') $expr = substr($expr, 0, strlen($expr)-1); // strip semicolons at the end
-        //===============
-        // is it a variable assignment?
-        if (preg_match('/^\s*([a-z]\w*)\s*=\s*(.+)$/', $expr, $matches)) {
-            if (in_array($matches[1], $this->vb)) { // make sure we're not assigning to a constant
-                return $this->trigger("cannot assign to constant '$matches[1]'");
-            }
-            if (($tmp = $this->pfx($this->nfx($matches[2]))) === false) return false; // get the result and make sure it's good
-            $this->v[$matches[1]] = $tmp; // if so, stick it in the variable array
-            return $this->v[$matches[1]]; // and return the resulting value
-        //===============
-        // is it a function assignment?
-        } elseif (preg_match('/^\s*([a-z]\w*)\s*\(\s*([a-z]\w*(?:\s*,\s*[a-z]\w*)*)\s*\)\s*=\s*(.+)$/', $expr, $matches)) {
-            $fnn = $matches[1]; // get the function name
-            if (in_array($matches[1], $this->fb)) { // make sure it isn't built in
-                return $this->trigger("cannot redefine built-in function '$matches[1]()'");
-            }
-            $args = explode(",", preg_replace("/\s+/", "", $matches[2])); // get the arguments
-            if (($stack = $this->nfx($matches[3])) === false) return false; // see if it can be converted to postfix
-            for ($i = 0; $i<count($stack); $i++) { // freeze the state of the non-argument variables
-                $token = $stack[$i];
-                if (preg_match('/^[a-z]\w*$/', $token) and !in_array($token, $args)) {
-                    if (array_key_exists($token, $this->v)) {
-                        $stack[$i] = $this->v[$token];
-                    } else {
-                        return $this->trigger("undefined variable '$token' in function definition");
-                    }
-                }
-            }
-            $this->f[$fnn] = array('args'=>$args, 'func'=>$stack);
-            return true;
-        //===============
-        } else {
-            return $this->pfx($this->nfx($expr)); // straight up evaluation, woo
-        }
+  /**
+   * Change the suppress errors flag.
+   *
+   * When errors are not suppressed, trigger_error is used to cause a PHP error
+   * when an evaluation error occurs, as a result of calling trigger(). With
+   * errors suppressed this doesn't happen.
+   *
+   * @param bool $enable
+   *   If FALSE, enable triggering of php errors when expression errors occurs.
+   *   otherwise, suppress triggering the errors.
+   *
+   * @return bool
+   *   The new (current) state of the flag.
+   *
+   * @see ctools_math_expr::trigger()
+   */
+  public function set_suppress_errors($enable) {
+    return $this->suppress_errors = (bool) $enable;
+  }
+
+  /**
+   * Backwards compatible wrapper for evaluate().
+   *
+   * @see ctools_math_expr::evaluate()
+   */
+  public function e($expr) {
+    return $this->evaluate($expr);
+  }
+
+  /**
+   * Evaluate the expression.
+   *
+   * @param string $expr
+   *   The expression to evaluate.
+   *
+   * @return string|bool
+   *   The result of the expression, or FALSE if an error occurred, or TRUE if
+   *   an user-defined function was created.
+   */
+  public function evaluate($expr) {
+    $this->last_error = NULL;
+    $expr = trim($expr);
+
+    // Strip possible semicolons at the end.
+    if (substr($expr, -1, 1) == ';') {
+      $expr = substr($expr, 0, -1);
     }
 
-    function vars() {
-        $output = $this->v;
-        unset($output['pi']);
-        unset($output['e']);
-        return $output;
+    // Is it a variable assignment?
+    if (preg_match('/^\s*([a-z]\w*)\s*=\s*(.+)$/', $expr, $matches)) {
+
+      // Make sure we're not assigning to a constant.
+      if (in_array($matches[1], $this->constvars)) {
+        return $this->trigger("cannot assign to constant '$matches[1]'");
+      }
+
+      // Get the result and make sure it's good:
+      if (($tmp = $this->pfx($this->nfx($matches[2]))) === FALSE) {
+        return FALSE;
+      }
+      // If so, stick it in the variable array...
+      $this->vars[$matches[1]] = $tmp;
+      // ...and return the resulting value:
+      return $this->vars[$matches[1]];
+
     }
+    // Is it a function assignment?
+    elseif (preg_match('/^\s*([a-z]\w*)\s*\(\s*([a-z]\w*(?:\s*,\s*[a-z]\w*)*)\s*\)\s*=\s*(.+)$/', $expr, $matches)) {
+      // Get the function name.
+      $fnn = $matches[1];
+      // Make sure it isn't built in:
+      if (isset($this->funcs[$matches[1]])) {
+        return $this->trigger("cannot redefine built-in function '$matches[1]()'");
+      }
+
+      // Get the arguments.
+      $args = explode(",", preg_replace("/\s+/", "", $matches[2]));
+      // See if it can be converted to postfix.
+      $stack = $this->nfx($matches[3]);
+      if ($stack === FALSE) {
+        return FALSE;
+      }
+
+      // Freeze the state of the non-argument variables.
+      for ($i = 0; $i < count($stack); $i++) {
+        $token = $stack[$i];
+        if (preg_match('/^[a-z]\w*$/', $token) and !in_array($token, $args)) {
+          if (array_key_exists($token, $this->vars)) {
+            $stack[$i] = $this->vars[$token];
+          }
+          else {
+            return $this->trigger("undefined variable '$token' in function definition");
+          }
+        }
+      }
+      $this->userfuncs[$fnn] = array('args' => $args, 'func' => $stack);
 
-    function funcs() {
-        $output = array();
-        foreach ($this->f as $fnn=>$dat)
-            $output[] = $fnn . '(' . implode(',', $dat['args']) . ')';
-        return $output;
+      return TRUE;
     }
+    else {
+      // Straight up evaluation.
+      return trim($this->pfx($this->nfx($expr)), '"');
+    }
+  }
 
-    //===================== HERE BE INTERNAL METHODS ====================\\
+  /**
+   * Fetch an array of variables used in the expression.
+   *
+   * @return array
+   *   Array of name : value pairs, one for each variable defined.
+   */
+  public function vars() {
+    $output = $this->vars;
 
-    // Convert infix to postfix notation
-    function nfx($expr) {
+    // @todo: Is this supposed to remove all constants? we should remove all
+    // those in $this->constvars!
+    unset($output['pi']);
+    unset($output['e']);
 
-        $index = 0;
-        $stack = new ctools_math_expr_stack;
-        $output = array(); // postfix form of expression, to be passed to pfx()
-        $expr = trim(strtolower($expr));
+    return $output;
+  }
 
-        $ops   = array('+', '-', '*', '/', '^', '_');
-        $ops_r = array('+'=>0,'-'=>0,'*'=>0,'/'=>0,'^'=>1); // right-associative operator?
-        $ops_p = array('+'=>0,'-'=>0,'*'=>1,'/'=>1,'_'=>1,'^'=>2); // operator precedence
+  /**
+   * Fetch all user defined functions in the expression.
+   *
+   * @return array
+   *   Array of name : string pairs, one for each function defined. The string
+   *   will be of the form fname(arg1,arg2). The function body is not returned.
+   */
+  public function funcs() {
+    $output = array();
+    foreach ($this->userfuncs as $fnn => $dat) {
+      $output[] = $fnn . '(' . implode(',', $dat['args']) . ')';
+    }
 
-        $expecting_op = false; // we use this in syntax-checking the expression
-                               // and determining when a - is a negation
+    return $output;
+  }
 
-        if (preg_match("/[^\w\s+*^\/()\.,-]/", $expr, $matches)) { // make sure the characters are all good
-            return $this->trigger("illegal character '{$matches[0]}'");
+  /**
+   * Convert infix to postfix notation.
+   *
+   * @param string $expr
+   *   The expression to convert.
+   *
+   * @return array|bool
+   *   The expression as an ordered list of postfix action tokens.
+   */
+  private function nfx($expr) {
+
+    $index = 0;
+    $stack = new ctools_math_expr_stack();
+    // Postfix form of expression, to be passed to pfx().
+    $output = array();
+
+    // @todo: Because the expr can contain string operands, using strtolower here is a bug.
+    $expr = trim(strtolower($expr));
+
+    // We use this in syntax-checking the expression and determining when
+    // '-' is a negation.
+    $expecting_op = FALSE;
+
+    while (TRUE) {
+      $op = substr($expr, $index, 1);
+      // Get the first character at the current index, and if the second
+      // character is an =, add it to our op as well (accounts for <=).
+      if (substr($expr, $index + 1, 1) === '=') {
+        $op = substr($expr, $index, 2);
+        $index++;
+      }
+
+      // Find out if we're currently at the beginning of a number/variable/
+      // function/parenthesis/operand.
+      $ex = preg_match('/^([a-z]\w*\(?|\d+(?:\.\d*)?|\.\d+|\()/', substr($expr, $index), $match);
+
+      // Is it a negation instead of a minus?
+      if ($op === '-' and !$expecting_op) {
+        // Put a negation on the stack.
+        $stack->push('_');
+        $index++;
+      }
+      // We have to explicitly deny this, because it's legal on the stack but
+      // not in the input expression.
+      elseif ($op == '_') {
+        return $this->trigger("illegal character '_'");
+
+      }
+      // Are we putting an operator on the stack?
+      elseif ((isset($this->ops[$op]) || $ex) && $expecting_op) {
+        // Are we expecting an operator but have a num, var, func, or
+        // open-paren?
+        if ($ex) {
+          $op = '*';
+          // It's an implicit multiplication.
+          $index--;
+        }
+        // Heart of the algorithm:
+        while ($stack->count() > 0 &&
+          ($o2 = $stack->last()) &&
+          isset($this->ops[$o2]) &&
+          (!empty($this->ops[$op]['right']) ?
+            $this->ops[$op]['precedence'] < $this->ops[$o2]['precedence'] :
+            $this->ops[$op]['precedence'] <= $this->ops[$o2]['precedence'])) {
+
+          // Pop stuff off the stack into the output.
+          $output[] = $stack->pop();
+        }
+        // Many thanks: http://en.wikipedia.org/wiki/Reverse_Polish_notation#The_algorithm_in_detail
+        // finally put OUR operator onto the stack.
+        $stack->push($op);
+        $index++;
+        $expecting_op = FALSE;
+
+      }
+      // Ready to close a parenthesis?
+      elseif ($op === ')') {
+
+        // Pop off the stack back to the last '('.
+        while (($o2 = $stack->pop()) !== '(') {
+          if (is_null($o2)) {
+            return $this->trigger("unexpected ')'");
+          }
+          else {
+            $output[] = $o2;
+          }
         }
 
-        while(1) { // 1 Infinite Loop ;)
-            $op = substr($expr, $index, 1); // get the first character at the current index
-            // find out if we're currently at the beginning of a number/variable/function/parenthesis/operand
-            $ex = preg_match('/^([a-z]\w*\(?|\d+(?:\.\d*)?|\.\d+|\()/', substr($expr, $index), $match);
-            //===============
-            if ($op == '-' and !$expecting_op) { // is it a negation instead of a minus?
-                $stack->push('_'); // put a negation on the stack
-                $index++;
-            } elseif ($op == '_') { // we have to explicitly deny this, because it's legal on the stack
-                return $this->trigger("illegal character '_'"); // but not in the input expression
-            //===============
-            } elseif ((in_array($op, $ops) or $ex) and $expecting_op) { // are we putting an operator on the stack?
-                if ($ex) { // are we expecting an operator but have a number/variable/function/opening parethesis?
-                    $op = '*'; $index--; // it's an implicit multiplication
-                }
-                // heart of the algorithm:
-                while($stack->count > 0 and ($o2 = $stack->last()) and in_array($o2, $ops) and ($ops_r[$op] ? $ops_p[$op] < $ops_p[$o2] : $ops_p[$op] <= $ops_p[$o2])) {
-                    $output[] = $stack->pop(); // pop stuff off the stack into the output
-                }
-                // many thanks: http://en.wikipedia.org/wiki/Reverse_Polish_notation#The_algorithm_in_detail
-                $stack->push($op); // finally put OUR operator onto the stack
-                $index++;
-                $expecting_op = false;
-            //===============
-            } elseif ($op == ')' and $expecting_op) { // ready to close a parenthesis?
-                while (($o2 = $stack->pop()) != '(') { // pop off the stack back to the last (
-                    if (is_null($o2)) return $this->trigger("unexpected ')'");
-                    else $output[] = $o2;
-                }
-                if (preg_match("/^([a-z]\w*)\($/", $stack->last(2), $matches)) { // did we just close a function?
-                    $fnn = $matches[1]; // get the function name
-                    $arg_count = $stack->pop(); // see how many arguments there were (cleverly stored on the stack, thank you)
-                    $output[] = $stack->pop(); // pop the function and push onto the output
-                    if (in_array($fnn, $this->fb)) { // check the argument count
-                        if($arg_count > 1)
-                            return $this->trigger("too many arguments ($arg_count given, 1 expected)");
-                    } elseif (array_key_exists($fnn, $this->f)) {
-                        if ($arg_count != count($this->f[$fnn]['args']))
-                            return $this->trigger("wrong number of arguments ($arg_count given, " . count($this->f[$fnn]['args']) . " expected)");
-                    } else { // did we somehow push a non-function on the stack? this should never happen
-                        return $this->trigger("internal error");
-                    }
-                }
-                $index++;
-            //===============
-            } elseif ($op == ',' and $expecting_op) { // did we just finish a function argument?
-                while (($o2 = $stack->pop()) != '(') {
-                    if (is_null($o2)) return $this->trigger("unexpected ','"); // oops, never had a (
-                    else $output[] = $o2; // pop the argument expression stuff and push onto the output
-                }
-                // make sure there was a function
-                if (!preg_match("/^([a-z]\w*)\($/", $stack->last(2), $matches))
-                    return $this->trigger("unexpected ','");
-                $stack->push($stack->pop()+1); // increment the argument count
-                $stack->push('('); // put the ( back on, we'll need to pop back to it again
-                $index++;
-                $expecting_op = false;
-            //===============
-            } elseif ($op == '(' and !$expecting_op) {
-                $stack->push('('); // that was easy
-                $index++;
-                $allow_neg = true;
-            //===============
-            } elseif ($ex and !$expecting_op) { // do we now have a function/variable/number?
-                $expecting_op = true;
-                $val = $match[1];
-                if (preg_match("/^([a-z]\w*)\($/", $val, $matches)) { // may be func, or variable w/ implicit multiplication against parentheses...
-                    if (in_array($matches[1], $this->fb) or array_key_exists($matches[1], $this->f)) { // it's a func
-                        $stack->push($val);
-                        $stack->push(1);
-                        $stack->push('(');
-                        $expecting_op = false;
-                    } else { // it's a var w/ implicit multiplication
-                        $val = $matches[1];
-                        $output[] = $val;
-                    }
-                } else { // it's a plain old var or num
-                    $output[] = $val;
-                }
-                $index += strlen($val);
-            //===============
-            } elseif ($op == ')') { // miscellaneous error checking
-                return $this->trigger("unexpected ')'");
-            } elseif (in_array($op, $ops) and !$expecting_op) {
-                return $this->trigger("unexpected operator '$op'");
-            } else { // I don't even want to know what you did to get here
-                return $this->trigger("an unexpected error occurred");
+        // Did we just close a function?
+        if (preg_match("/^([a-z]\w*)\($/", $stack->last(2), $matches)) {
+
+          // Get the function name.
+          $fnn = $matches[1];
+          // See how many arguments there were (cleverly stored on the stack,
+          // thank you).
+          $arg_count = $stack->pop();
+          // Pop the function and push onto the output.
+          $output[] = $stack->pop();
+
+          // Check the argument count:
+          if (isset($this->funcs[$fnn])) {
+            $fdef = $this->funcs[$fnn];
+            $max_arguments = isset($fdef['max arguments']) ? $fdef['max arguments'] : $fdef['arguments'];
+            if ($arg_count > $max_arguments) {
+              return $this->trigger("too many arguments ($arg_count given, $max_arguments expected)");
             }
-            if ($index == strlen($expr)) {
-                if (in_array($op, $ops)) { // did we end with an operator? bad.
-                    return $this->trigger("operator '$op' lacks operand");
-                } else {
-                    break;
-                }
+          }
+          elseif (array_key_exists($fnn, $this->userfuncs)) {
+            $fdef = $this->userfuncs[$fnn];
+            if ($arg_count !== count($fdef['args'])) {
+              return $this->trigger("wrong number of arguments ($arg_count given, " . count($fdef['args']) . ' expected)');
+            }
+          }
+          else {
+            // Did we somehow push a non-function on the stack? this should
+            // never happen.
+            return $this->trigger('internal error');
+          }
+        }
+        $index++;
+
+      }
+      // Did we just finish a function argument?
+      elseif ($op === ',' && $expecting_op) {
+        $index++;
+        $expecting_op = FALSE;
+      }
+      elseif ($op === '(' && !$expecting_op) {
+        $stack->push('(');
+        $index++;
+
+      }
+      elseif ($ex && !$expecting_op) {
+        // Make sure there was a function.
+        if (preg_match("/^([a-z]\w*)\($/", $stack->last(3), $matches)) {
+          // Pop the argument expression stuff and push onto the output:
+          while (($o2 = $stack->pop()) !== '(') {
+            // Oops, never had a '('.
+            if (is_null($o2)) {
+              return $this->trigger("unexpected argument in $expr $o2");
             }
-            while (substr($expr, $index, 1) == ' ') { // step the index past whitespace (pretty much turns whitespace
-                $index++;                             // into implicit multiplication if no operator is there)
+            else {
+              $output[] = $o2;
             }
+          }
 
+          // Increment the argument count.
+          $stack->push($stack->pop() + 1);
+          // Put the ( back on, we'll need to pop back to it again.
+          $stack->push('(');
+        }
+
+        // Do we now have a function/variable/number?
+        $expecting_op = TRUE;
+        $val = $match[1];
+        if (preg_match("/^([a-z]\w*)\($/", $val, $matches)) {
+          // May be func, or variable w/ implicit multiplication against
+          // parentheses...
+          if (isset($this->funcs[$matches[1]]) or array_key_exists($matches[1], $this->userfuncs)) {
+            $stack->push($val);
+            $stack->push(0);
+            $stack->push('(');
+            $expecting_op = FALSE;
+          }
+          // it's a var w/ implicit multiplication.
+          else {
+            $val = $matches[1];
+            $output[] = $val;
+          }
+        }
+        // it's a plain old var or num.
+        else {
+          $output[] = $val;
         }
-        while (!is_null($op = $stack->pop())) { // pop everything off the stack and push onto output
-            if ($op == '(') return $this->trigger("expecting ')'"); // if there are (s on the stack, ()s were unbalanced
-            $output[] = $op;
+        $index += strlen($val);
+
+      }
+      elseif ($op === ')') {
+        // Miscellaneous error checking.
+        return $this->trigger("unexpected ')'");
+      }
+      elseif (isset($this->ops[$op]) and !$expecting_op) {
+        return $this->trigger("unexpected operator '$op'");
+      }
+      elseif ($op === '"') {
+        // Fetch a quoted string.
+        $string = substr($expr, $index);
+        if (preg_match('/"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"/s', $string, $matches)) {
+          $string = $matches[0];
+          // Trim the quotes off:
+          $output[] = $string;
+          $index += strlen($string);
+          $expecting_op = TRUE;
         }
-        return $output;
+        else {
+          return $this->trigger('open quote without close quote.');
+        }
+      }
+      else {
+        // I don't even want to know what you did to get here.
+        return $this->trigger("an unexpected error occurred at $op");
+      }
+      if ($index === strlen($expr)) {
+        if (isset($this->ops[$op])) {
+          // Did we end with an operator? bad.
+          return $this->trigger("operator '$op' lacks operand");
+        }
+        else {
+          break;
+        }
+      }
+
+      // Step the index past whitespace (pretty much turns whitespace into
+      // implicit multiplication if no operator is there).
+      while (substr($expr, $index, 1) === ' ') {
+        $index++;
+      }
+    }
+
+    // Pop everything off the stack and push onto output:
+    while (!is_null($op = $stack->pop())) {
+
+      // If there are (s on the stack, ()s were unbalanced.
+      if ($op === '(') {
+        return $this->trigger("expecting ')'");
+      }
+      $output[] = $op;
+    }
+
+    return $output;
+  }
+
+  /**
+   * Evaluate a prefix-operator stack expression.
+   *
+   * @param array $tokens
+   *   The array of token values to evaluate. A token is a string value
+   *   representing either an operation to perform, a variable, or a value.
+   *   Literal values are checked using is_numeric(), or a value that starts
+   *   with a double-quote; functions and variables by existence in the
+   *   appropriate tables.
+   *   If FALSE is passed in the function terminates immediately, returning
+   *   FALSE.
+   * @param array $vars
+   *   Additional variable values to use when evaluating the expression. These
+   *   variables do not override internal variables with the same name.
+   *
+   * @return bool|mixed
+   *   The expression's value, otherwise FALSE is returned if there is an error
+   *   detected unless php error handling intervenes: see suppress_error.
+   */
+  public function pfx(array $tokens, array $vars = array()) {
+
+    if ($tokens == FALSE) {
+      return FALSE;
     }
 
-    // evaluate postfix notation
-    function pfx($tokens, $vars = array()) {
-
-        if ($tokens == false) return false;
-
-        $stack = new ctools_math_expr_stack;
-
-        foreach ($tokens as $token) { // nice and easy
-            // if the token is a binary operator, pop two values off the stack, do the operation, and push the result back on
-            if (in_array($token, array('+', '-', '*', '/', '^'))) {
-                if (is_null($op2 = $stack->pop())) return $this->trigger("internal error");
-                if (is_null($op1 = $stack->pop())) return $this->trigger("internal error");
-                switch ($token) {
-                    case '+':
-                        $stack->push($op1+$op2); break;
-                    case '-':
-                        $stack->push($op1-$op2); break;
-                    case '*':
-                        $stack->push($op1*$op2); break;
-                    case '/':
-                        if ($op2 == 0) return $this->trigger("division by zero");
-                        $stack->push($op1/$op2); break;
-                    case '^':
-                        $stack->push(pow($op1, $op2)); break;
-                }
-            // if the token is a unary operator, pop one value off the stack, do the operation, and push it back on
-            } elseif ($token == "_") {
-                $stack->push(-1*$stack->pop());
-            // if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on
-            } elseif (preg_match("/^([a-z]\w*)\($/", $token, $matches)) { // it's a function!
-                $fnn = $matches[1];
-                if (in_array($fnn, $this->fb)) { // built-in function:
-                    if (is_null($op1 = $stack->pop())) return $this->trigger("internal error");
-                    $fnn = preg_replace("/^arc/", "a", $fnn); // for the 'arc' trig synonyms
-                    if ($fnn == 'ln') $fnn = 'log';
-                    eval('$stack->push(' . $fnn . '($op1));'); // perfectly safe eval()
-                } elseif (array_key_exists($fnn, $this->f)) { // user function
-                    // get args
-                    $args = array();
-                    for ($i = count($this->f[$fnn]['args'])-1; $i >= 0; $i--) {
-                        if (is_null($args[$this->f[$fnn]['args'][$i]] = $stack->pop())) return $this->trigger("internal error");
-                    }
-                    $stack->push($this->pfx($this->f[$fnn]['func'], $args)); // yay... recursion!!!!
-                }
-            // if the token is a number or variable, push it on the stack
-            } else {
-                if (is_numeric($token)) {
-                    $stack->push($token);
-                } elseif (array_key_exists($token, $this->v)) {
-                    $stack->push($this->v[$token]);
-                } elseif (array_key_exists($token, $vars)) {
-                    $stack->push($vars[$token]);
-                } else {
-                    return $this->trigger("undefined variable '$token'");
-                }
+    $stack = new ctools_math_expr_stack();
+
+    foreach ($tokens as $token) {
+      // If the token is a binary operator, pop two values off the stack, do
+      // the operation, and push the result back on again.
+      if (in_array($token, $this->binaryops)) {
+        if (is_null($op2 = $stack->pop())) {
+          return $this->trigger('internal error');
+        }
+        if (is_null($op1 = $stack->pop())) {
+          return $this->trigger('internal error');
+        }
+        switch ($token) {
+          case '+':
+            $stack->push($op1 + $op2);
+            break;
+
+          case '-':
+            $stack->push($op1 - $op2);
+            break;
+
+          case '*':
+            $stack->push($op1 * $op2);
+            break;
+
+          case '/':
+            if ($op2 == 0) {
+              return $this->trigger('division by zero');
+            }
+            $stack->push($op1 / $op2);
+            break;
+
+          case '^':
+            $stack->push(pow($op1, $op2));
+            break;
+
+          case '==':
+            $stack->push((int) ($op1 == $op2));
+            break;
+
+          case '!=':
+            $stack->push((int) ($op1 != $op2));
+            break;
+
+          case '<=':
+            $stack->push((int) ($op1 <= $op2));
+            break;
+
+          case '<':
+            $stack->push((int) ($op1 < $op2));
+            break;
+
+          case '>=':
+            $stack->push((int) ($op1 >= $op2));
+            break;
+
+          case '>':
+            $stack->push((int) ($op1 > $op2));
+            break;
+        }
+      }
+      // If the token is a unary operator, pop one value off the stack, do the
+      // operation, and push it back on again.
+      elseif ($token === "_") {
+        $stack->push(-1 * $stack->pop());
+      }
+      // If the token is a function, pop arguments off the stack, hand them to
+      // the function, and push the result back on again.
+      elseif (preg_match("/^([a-z]\w*)\($/", $token, $matches)) {
+        $fnn = $matches[1];
+
+        // Check for a built-in function.
+        if (isset($this->funcs[$fnn])) {
+          $args = array();
+          // Collect all required args from the stack.
+          for ($i = 0; $i < $this->funcs[$fnn]['arguments']; $i++) {
+            if (is_null($op1 = $stack->pop())) {
+              return $this->trigger("function $fnn missing argument $i");
+            }
+            $args[] = $op1;
+          }
+          // If func allows additional args, collect them too, stopping on a
+          // NULL arg.
+          if (!empty($this->funcs[$fnn]['max arguments'])) {
+            for (; $i < $this->funcs[$fnn]['max arguments']; $i++) {
+              $arg = $stack->pop();
+              if (!isset($arg)) {
+                break;
+              }
+              $args[] = $arg;
+            }
+          }
+          $stack->push(
+            call_user_func_array($this->funcs[$fnn]['function'], array_reverse($args))
+          );
+        }
+
+        // Check for a user function.
+        elseif (isset($fnn, $this->userfuncs)) {
+          $args = array();
+          for ($i = count($this->userfuncs[$fnn]['args']) - 1; $i >= 0; $i--) {
+            $value = $stack->pop();
+            $args[$this->userfuncs[$fnn]['args'][$i]] = $value;
+            if (is_null($value)) {
+              return $this->trigger('internal error');
             }
+          }
+          // yay... recursion!!!!
+          $stack->push($this->pfx($this->userfuncs[$fnn]['func'], $args));
+        }
+      }
+      // If the token is a number or variable, push it on the stack.
+      else {
+        if (is_numeric($token) || $token[0] == '"') {
+          $stack->push($token);
+        }
+        elseif (array_key_exists($token, $this->vars)) {
+          $stack->push($this->vars[$token]);
+        }
+        elseif (array_key_exists($token, $vars)) {
+          $stack->push($vars[$token]);
         }
-        // when we're out of tokens, the stack should have a single element, the final result
-        if ($stack->count != 1) return $this->trigger("internal error");
-        return $stack->pop();
+        else {
+          return $this->trigger("undefined variable '$token'");
+        }
+      }
+    }
+    // When we're out of tokens, the stack should have a single element, the
+    // final result:
+    if ($stack->count() !== 1) {
+      return $this->trigger('internal error');
     }
 
-    // trigger an error, but nicely, if need be
-    function trigger($msg) {
-        $this->last_error = $msg;
-        if (!$this->suppress_errors) trigger_error($msg, E_USER_WARNING);
-        return false;
+    return $stack->pop();
+  }
+
+  /**
+   * Trigger an error, but nicely, if need be.
+   *
+   * @param string $msg
+   *   Message to add to trigger error.
+   *
+   * @return bool
+   *   Can trigger error, then returns FALSE.
+   */
+  protected function trigger($msg) {
+    $this->errors[] = $msg;
+    $this->last_error = $msg;
+    if (!$this->suppress_errors) {
+      trigger_error($msg, E_USER_WARNING);
     }
+
+    return FALSE;
+  }
+
 }
 
-// for internal use
+/**
+ * Class implementing a simple stack structure, used by ctools_math_expr.
+ */
 class ctools_math_expr_stack {
 
-    var $stack = array();
-    var $count = 0;
+  /**
+   * The stack.
+   *
+   * @var array
+   */
+  private $stack;
+  /**
+   * The stack pointer, points at the first empty space.
+   *
+   * @var int
+   */
+  private $count;
 
-    function push($val) {
-        $this->stack[$this->count] = $val;
-        $this->count++;
-    }
+  /**
+   * Ctools_math_expr_stack constructor.
+   */
+  public function __construct() {
+    $this->stack = array();
+    $this->count = 0;
+  }
 
-    function pop() {
-        if ($this->count > 0) {
-            $this->count--;
-            return $this->stack[$this->count];
-        }
-        return null;
-    }
+  /**
+   * Push the value onto the stack.
+   *
+   * @param mixed $val
+   */
+  public function push($val) {
+    $this->stack[$this->count] = $val;
+    $this->count++;
+  }
+
+  /**
+   * Remove the most recently pushed value and return it.
+   *
+   * @return mixed|null
+   *   The most recently pushed value, or NULL if the stack was empty.
+   */
+  public function pop() {
+    if ($this->count > 0) {
+      $this->count--;
 
-    function last($n=1) {
-        return !empty($this->stack[$this->count-$n]) ? $this->stack[$this->count-$n] : NULL;
+      return $this->stack[$this->count];
     }
+    return NULL;
+  }
+
+  /**
+   * "Peek" the stack, or Return a value from the stack without removing it.
+   *
+   * @param int $n
+   *   Integer indicating which value to return. 1 is the topmost (i.e. the
+   *   value that pop() would return), 2 indicates the next, 3 the third, etc.
+   *
+   * @return mixed|null
+   *   A value pushed onto the stack at the nth position, or NULL if the stack
+   *   was empty.
+   */
+  public function last($n = 1) {
+    return !empty($this->stack[$this->count - $n]) ? $this->stack[$this->count - $n] : NULL;
+  }
+
+  /**
+   * Return the number of items on the stack.
+   *
+   * @return int
+   *   The number of items.
+   */
+  public function count() {
+    return $this->count;
+  }
+
 }
 
+/**
+ * Helper function for evaluating 'if' condition.
+ *
+ * @param int $expr
+ *   The expression to test: if <> 0 then the $if expression is returned.
+ * @param mixed $if
+ *   The expression returned if the condition is true.
+ * @param mixed $else
+ *   Optional. The expression returned if the expression is false.
+ *
+ * @return mixed|null
+ *   The result. NULL is returned when an If condition is False and no Else
+ *   expression is provided.
+ */
+function ctools_math_expr_if($expr, $if, $else = NULL) {
+  return $expr ? $if : $else;
+}
+
+/**
+ * Remove any non-digits so that numbers like $4,511.23 still work.
+ *
+ * It might be good for those using the 12,345.67 format, but is awful for
+ * those using other conventions.
+ * Use of the php 'intl' module might work here, if the correct locale can be
+ * derived, but that seems unlikely to be true in all cases.
+ *
+ * @todo: locale could break this since in some locales that's $4.512,33 so
+ * there needs to be a way to detect that and make it work properly.
+ *
+ * @param mixed $arg
+ *   A number string with possible leading chars.
+ *
+ * @return mixed
+ *   Returns a number string.
+ */
+function ctools_math_expr_number($arg) {
+  // @todo: A really bad idea: It might be good for those using the 12,345.67
+  // format, but is awful for those using other conventions.
+  // $arg = preg_replace("/[^0-9\.]/", '', $arg);.
+  return $arg;
+}
diff --git a/drupal7/sites/all/modules/ctools/includes/modal.inc b/drupal7/sites/all/modules/ctools/includes/modal.inc
index 18b216fc..30eadbf4 100644
--- a/drupal7/sites/all/modules/ctools/includes/modal.inc
+++ b/drupal7/sites/all/modules/ctools/includes/modal.inc
@@ -74,7 +74,7 @@ function ctools_modal_add_js() {
 }
 
 /**
- * @todo this is deprecated
+ * @todo Deprecated this.
  */
 function ctools_modal_add_plugin_js($plugins) {
   $css = array();
@@ -85,7 +85,7 @@ function ctools_modal_add_plugin_js($plugins) {
         if (file_exists($file)) {
           $js[$file] = TRUE;
         }
-        elseif (file(exists($subtype['path'] . '/' . $file))) {
+        elseif (file_exists($subtype['path'] . '/' . $file)) {
           $js[$subtype['path'] . '/' . $file] = TRUE;
         }
       }
@@ -95,7 +95,7 @@ function ctools_modal_add_plugin_js($plugins) {
         if (file_exists($file)) {
           $css[$file] = TRUE;
         }
-        elseif (file(exists($subtype['path'] . '/' . $file))) {
+        elseif (file_exists($subtype['path'] . '/' . $file)) {
           $css[$subtype['path'] . '/' . $file] = TRUE;
         }
       }
diff --git a/drupal7/sites/all/modules/ctools/includes/object-cache.inc b/drupal7/sites/all/modules/ctools/includes/object-cache.inc
index ef52f9ad..34c26623 100644
--- a/drupal7/sites/all/modules/ctools/includes/object-cache.inc
+++ b/drupal7/sites/all/modules/ctools/includes/object-cache.inc
@@ -21,13 +21,12 @@
  * @param $name
  *   The name of the object being stored.
  * @param $skip_cache
+ *   Deprecated in favor of drupal_static*
  *   Skip the memory cache, meaning this must be read from the db again.
  * @param $sid
  *   The session id, allowing someone to use Session API or their own solution;
  *   defaults to session_id().
  *
- * @deprecated $skip_cache is deprecated in favor of drupal_static*
- *
  * @return
  *   The data that was cached.
  */
diff --git a/drupal7/sites/all/modules/ctools/js/ajax-responder.js b/drupal7/sites/all/modules/ctools/js/ajax-responder.js
index 1cad618e..6c30b26d 100644
--- a/drupal7/sites/all/modules/ctools/js/ajax-responder.js
+++ b/drupal7/sites/all/modules/ctools/js/ajax-responder.js
@@ -98,29 +98,30 @@
   };
 
   // Hide these in a ready to ensure that Drupal.ajax is set up first.
-  $(function() {
-    Drupal.ajax.prototype.commands.attr = function(ajax, data, status) {
-      $(data.selector).attr(data.name, data.value);
-    };
+  Drupal.behaviors.ctools_add_ajax_responder_commands = {
+    attach: function () {
+      Drupal.ajax.prototype.commands.attr = function (ajax, data, status) {
+        $(data.selector).attr(data.name, data.value);
+      };
 
-
-    Drupal.ajax.prototype.commands.redirect = function(ajax, data, status) {
-      if (data.delay > 0) {
-        setTimeout(function () {
+      Drupal.ajax.prototype.commands.redirect = function (ajax, data, status) {
+        if (data.delay > 0) {
+          setTimeout(function () {
+            location.href = data.url;
+          }, data.delay);
+        }
+        else {
           location.href = data.url;
-        }, data.delay);
-      }
-      else {
-        location.href = data.url;
-      }
-    };
+        }
+      };
 
-    Drupal.ajax.prototype.commands.reload = function(ajax, data, status) {
-      location.reload();
-    };
+      Drupal.ajax.prototype.commands.reload = function (ajax, data, status) {
+        location.reload();
+      };
 
-    Drupal.ajax.prototype.commands.submit = function(ajax, data, status) {
-      $(data.selector).submit();
+      Drupal.ajax.prototype.commands.submit = function (ajax, data, status) {
+        $(data.selector).submit();
+      }
     }
-  });
+  };
 })(jQuery);
diff --git a/drupal7/sites/all/modules/ctools/js/collapsible-div.js b/drupal7/sites/all/modules/ctools/js/collapsible-div.js
index 134151c3..4e21a225 100644
--- a/drupal7/sites/all/modules/ctools/js/collapsible-div.js
+++ b/drupal7/sites/all/modules/ctools/js/collapsible-div.js
@@ -123,7 +123,7 @@
       var cookie = '';
 
       // Get a list of IDs, saparated by comma
-      for (i in this.state) {
+      for (var i in this.state) {
         if (cookie != '') {
           cookie += ',';
         }
@@ -190,7 +190,7 @@
 
       var afterToggle = function () {
         if (Drupal.CTools.CollapsibleCallbacksAfterToggle) {
-          for (i in Drupal.CTools.CollapsibleCallbacksAfterToggle) {
+          for (var i in Drupal.CTools.CollapsibleCallbacksAfterToggle) {
             Drupal.CTools.CollapsibleCallbacksAfterToggle[i]($container, handle, content, toggle);
           }
         }
@@ -198,7 +198,7 @@
 
       var clickMe = function () {
         if (Drupal.CTools.CollapsibleCallbacks) {
-          for (i in Drupal.CTools.CollapsibleCallbacks) {
+          for (var i in Drupal.CTools.CollapsibleCallbacks) {
             Drupal.CTools.CollapsibleCallbacks[i]($container, handle, content, toggle);
           }
         }
diff --git a/drupal7/sites/all/modules/ctools/js/dependent.js b/drupal7/sites/all/modules/ctools/js/dependent.js
index a60fc120..77777e38 100644
--- a/drupal7/sites/all/modules/ctools/js/dependent.js
+++ b/drupal7/sites/all/modules/ctools/js/dependent.js
@@ -39,7 +39,7 @@
 
   Drupal.CTools.dependent.autoAttach = function() {
     // Clear active bindings and triggers.
-    for (i in Drupal.CTools.dependent.activeTriggers) {
+    for (var i in Drupal.CTools.dependent.activeTriggers) {
       $(Drupal.CTools.dependent.activeTriggers[i]).unbind('change.ctools-dependent');
     }
     Drupal.CTools.dependent.activeTriggers = [];
@@ -51,7 +51,7 @@
     }
 
     // Iterate through all relationships
-    for (id in Drupal.settings.CTools.dependent) {
+    for (var id in Drupal.settings.CTools.dependent) {
       // Test to make sure the id even exists; this helps clean up multiple
       // AJAX calls with multiple forms.
 
@@ -59,7 +59,7 @@
       // whether the binding is active or not.  Defaults to no.
       Drupal.CTools.dependent.activeBindings[id] = 0;
       // Iterate through all possible values
-      for(bind_id in Drupal.settings.CTools.dependent[id].values) {
+      for (var bind_id in Drupal.settings.CTools.dependent[id].values) {
         // This creates a backward relationship.  The bind_id is the ID
         // of the element which needs to change in order for the id to hide or become shown.
         // The id is the ID of the item which will be conditionally hidden or shown.
@@ -87,7 +87,7 @@
         }
 
         var getValue = function(item, trigger) {
-          if ($(trigger).size() == 0) {
+          if ($(trigger).length == 0) {
             return null;
           }
 
@@ -129,7 +129,7 @@
               return;
             }
 
-            for (i in Drupal.CTools.dependent.bindings[bind_id]) {
+            for (var i in Drupal.CTools.dependent.bindings[bind_id]) {
               var id = Drupal.CTools.dependent.bindings[bind_id][i];
               // Fix numerous errors
               if (typeof id != 'string') {
@@ -150,7 +150,7 @@
               }
 
               var len = 0;
-              for (i in Drupal.CTools.dependent.activeBindings[id]) {
+              for (var i in Drupal.CTools.dependent.activeBindings[id]) {
                 len++;
               }
 
diff --git a/drupal7/sites/all/modules/ctools/js/modal.js b/drupal7/sites/all/modules/ctools/js/modal.js
index ec7b02ae..71b4b6aa 100644
--- a/drupal7/sites/all/modules/ctools/js/modal.js
+++ b/drupal7/sites/all/modules/ctools/js/modal.js
@@ -299,6 +299,7 @@
     // content. This is helpful for allowing users to see error messages at the
     // top of a form, etc.
     $('#modal-content').html(response.output).scrollTop(0);
+    $(document).trigger('CToolsAttachBehaviors', $('#modalContent'));
 
     // Attach behaviors within a modal dialog.
     var settings = response.settings || ajax.settings || Drupal.settings;
diff --git a/drupal7/sites/all/modules/ctools/js/stylizer.js b/drupal7/sites/all/modules/ctools/js/stylizer.js
index 12ab7204..d48f6454 100644
--- a/drupal7/sites/all/modules/ctools/js/stylizer.js
+++ b/drupal7/sites/all/modules/ctools/js/stylizer.js
@@ -5,7 +5,7 @@
 
   Drupal.CTools.Stylizer.addFarbtastic = function(context) {
     // This behavior attaches by ID, so is only valid once on a page.
-    if ($('#ctools_stylizer_color_scheme_form .color-form.Stylizer-processed').size()) {
+    if ($('#ctools_stylizer_color_scheme_form .color-form.Stylizer-processed').length) {
       return;
     }
 
@@ -21,7 +21,7 @@
 
     // Decode reference colors to HSL
   /*var reference = Drupal.settings.Stylizer.reference.clone();
-    for (i in reference) {
+    for (var i in reference) {
       reference[i] = farb.RGBToHSL(farb.unpack(reference[i]));
     } */
 
@@ -30,7 +30,7 @@
       var colors = this.options[this.selectedIndex].value;
       if (colors != '') {
         colors = colors.split(',');
-        for (i in colors) {
+        for (var i in colors) {
           callback(inputs[i], colors[i], false, true);
         }
       }
diff --git a/drupal7/sites/all/modules/ctools/page_manager/page_manager.info b/drupal7/sites/all/modules/ctools/page_manager/page_manager.info
index a56663b8..77c9815b 100644
--- a/drupal7/sites/all/modules/ctools/page_manager/page_manager.info
+++ b/drupal7/sites/all/modules/ctools/page_manager/page_manager.info
@@ -6,8 +6,8 @@ package = Chaos tool suite
 
 files[] = tests/head_links.test
 
-; Information added by Drupal.org packaging script on 2019-02-08
-version = "7.x-1.15"
+; Information added by Drupal.org packaging script on 2020-10-23
+version = "7.x-1.17"
 core = "7.x"
 project = "ctools"
-datestamp = "1549603691"
+datestamp = "1603490551"
diff --git a/drupal7/sites/all/modules/ctools/page_manager/page_manager.module b/drupal7/sites/all/modules/ctools/page_manager/page_manager.module
index c614eff7..ed2066b4 100644
--- a/drupal7/sites/all/modules/ctools/page_manager/page_manager.module
+++ b/drupal7/sites/all/modules/ctools/page_manager/page_manager.module
@@ -297,7 +297,7 @@ function page_manager_theme() {
  * Get the cached changes to a given task handler.
  */
 function page_manager_get_page_cache($task_name) {
-  $caches = drupal_static(__FUNCTION__, array());
+  $caches = &drupal_static(__FUNCTION__, array());
   if (!isset($caches[$task_name])) {
     ctools_include('object-cache');
     $cache = ctools_object_cache_get('page_manager_page', $task_name);
diff --git a/drupal7/sites/all/modules/ctools/page_manager/plugins/tasks/node_edit.inc b/drupal7/sites/all/modules/ctools/page_manager/plugins/tasks/node_edit.inc
index 66bf00da..2d274491 100644
--- a/drupal7/sites/all/modules/ctools/page_manager/plugins/tasks/node_edit.inc
+++ b/drupal7/sites/all/modules/ctools/page_manager/plugins/tasks/node_edit.inc
@@ -95,13 +95,28 @@ function page_manager_node_edit($node) {
   ctools_include('context-task-handler');
   $contexts = ctools_context_handler_get_task_contexts($task, '', array($node));
 
+  // Set the default title for the node add/edit form. If the page has a custom
+  // title it'll override this.
+  $types = node_type_get_types();
+  $context = reset($contexts);
+  if (empty($context->data->nid)) {
+    drupal_set_title(t('Create @name', array(
+      '@name' => $types[$context->data->type]->name
+    )), PASS_THROUGH);
+  }
+  else {
+    drupal_set_title(t('<em>Edit @type</em> @title', array(
+      '@type' => $types[$context->node_type]->name,
+      '@title' => $context->data->title
+    )), PASS_THROUGH);
+  }
+
   $arg = array(isset($node->nid) ? $node->nid : $node->type);
   $output = ctools_context_handler_render($task, '', $contexts, $arg);
   if ($output === FALSE) {
     // Fall back!
     // We've already built the form with the context, so we can't build it again, or
     // form_clean_id will mess up our ids. But we don't really need to, either:
-    $context = reset($contexts);
     $output = $context->form;
   }
 
diff --git a/drupal7/sites/all/modules/ctools/page_manager/plugins/tasks/search.inc b/drupal7/sites/all/modules/ctools/page_manager/plugins/tasks/search.inc
index 172d963a..e561c717 100644
--- a/drupal7/sites/all/modules/ctools/page_manager/plugins/tasks/search.inc
+++ b/drupal7/sites/all/modules/ctools/page_manager/plugins/tasks/search.inc
@@ -97,9 +97,9 @@ function page_manager_search_menu_alter(&$items, $task) {
  * Entry point for our overridden search page.
  */
 function page_manager_search_page($type) {
-  ctools_include('menu');
   // Get the arguments and construct a keys string out of them.
   $args = func_get_args();
+  ctools_include('menu');
 
   // We have to remove the $type.
   array_shift($args);
diff --git a/drupal7/sites/all/modules/ctools/page_manager/plugins/tasks/term_view.inc b/drupal7/sites/all/modules/ctools/page_manager/plugins/tasks/term_view.inc
index 72fb7dbc..101b6744 100644
--- a/drupal7/sites/all/modules/ctools/page_manager/plugins/tasks/term_view.inc
+++ b/drupal7/sites/all/modules/ctools/page_manager/plugins/tasks/term_view.inc
@@ -21,7 +21,7 @@ function page_manager_term_view_page_manager_tasks() {
 
       'title' => t('Taxonomy term template'),
       'admin title' => t('Taxonomy term template'),
-      'admin description' => t('When enabled, this overrides the default Drupal behavior for displaying taxonomy terms at <em>taxonomy/term/%term</em>. If you add variants, you may use selection criteria such as vocabulary or user access to provide different displays of the taxonomy term and associated nodes. If no variant is selected, the default Drupal taxonomy term display will be used. This page only affects items actually displayed ad taxonomy/term/%term. Some taxonomy terms, such as forums, have their displays moved elsewhere. Also please note that if you are using pathauto, aliases may make a taxonomy terms appear somewhere else, but as far as Drupal is concerned, they are still at taxonomy/term/%term.'),
+      'admin description' => t('When enabled, this overrides the default Drupal behavior for displaying taxonomy terms at <em>taxonomy/term/%term</em>. If you add variants, you may use selection criteria such as vocabulary or user access to provide different displays of the taxonomy term and associated nodes. If no variant is selected, the default Drupal taxonomy term display will be used. This page only affects items actually displayed at taxonomy/term/%term. Some taxonomy terms, such as forums, have their displays moved elsewhere. Also please note that if you are using pathauto, aliases may make a taxonomy terms appear somewhere else, but as far as Drupal is concerned, they are still at taxonomy/term/%term.'),
       'admin path' => 'taxonomy/term/%taxonomy_term',
       'admin summary' => 'page_manager_term_view_admin_summary',
 
diff --git a/drupal7/sites/all/modules/ctools/plugins/access/node.inc b/drupal7/sites/all/modules/ctools/plugins/access/node.inc
deleted file mode 100644
index e69de29b..00000000
diff --git a/drupal7/sites/all/modules/ctools/plugins/access/term.inc b/drupal7/sites/all/modules/ctools/plugins/access/term.inc
index 50ec0d90..224a00c6 100644
--- a/drupal7/sites/all/modules/ctools/plugins/access/term.inc
+++ b/drupal7/sites/all/modules/ctools/plugins/access/term.inc
@@ -15,7 +15,7 @@ $plugin = array(
   'callback' => 'ctools_term_ctools_access_check',
   'default' => array('vids' => array()),
   'settings form' => 'ctools_term_ctools_access_settings',
-  'settings form validation' => 'ctools_term_ctools_access_settings_validate',
+  'settings form validate' => 'ctools_term_ctools_access_settings_validate',
   'settings form submit' => 'ctools_term_ctools_access_settings_submit',
   'summary' => 'ctools_term_ctools_access_summary',
   'required context' => new ctools_context_required(t('Term'), array('taxonomy_term', 'terms')),
diff --git a/drupal7/sites/all/modules/ctools/plugins/access/term_has_parent.inc b/drupal7/sites/all/modules/ctools/plugins/access/term_has_parent.inc
index 16f4fbf6..fd9351d9 100644
--- a/drupal7/sites/all/modules/ctools/plugins/access/term_has_parent.inc
+++ b/drupal7/sites/all/modules/ctools/plugins/access/term_has_parent.inc
@@ -38,7 +38,7 @@ function ctools_term_has_parent_ctools_access_settings($form, &$form_state, $con
     '#title' => t('Vocabulary'),
     '#type' => 'select',
     '#options' => array(),
-    '#description' => t('Select the vocabulary for this form.'),
+    '#description' => t('Select the vocabulary your parent term belongs to.'),
     '#id' => 'ctools-select-vid',
     '#default_value' => $conf['vid'],
     '#required' => TRUE,
@@ -57,7 +57,7 @@ function ctools_term_has_parent_ctools_access_settings($form, &$form_state, $con
     $options[$vid] = $vocabulary->name;
     $form['settings']['vid_' . $vid] = array(
       '#title' => t('Terms'),
-      '#description' => t('Select a term or terms from @vocabulary.', array('@vocabulary' => $vocabulary->name)),
+      '#description' => t('Select a parent term (or terms) from the @vocabulary vocabulary.', array('@vocabulary' => $vocabulary->name)),
       '#dependency' => array('ctools-select-vid' => array($vocabulary->vid)),
       '#default_value' => !empty($conf['vid_' . $vid]) ? $conf['vid_' . $vid] : '',
       '#size' => 10,
@@ -77,8 +77,8 @@ function ctools_term_has_parent_ctools_access_settings($form, &$form_state, $con
   }
   $form['settings']['vid']['#options'] = $options;
   $form['settings']['include_self'] = array(
-    '#title' => t('Include these term(s) as candidates?'),
-    '#description' => t('When this rule is evaluated, should the term(s) you select be included as candidates for access?'),
+    '#title' => t('Include these parent term(s)?'),
+    '#description' => t('Should the term(s) you selected above be included in addition to their children?'),
     '#default_value' => !empty($conf['include_self']) ? $conf['include_self'] : FALSE,
     '#type' => 'checkbox',
   );
diff --git a/drupal7/sites/all/modules/ctools/plugins/access/term_parent.inc b/drupal7/sites/all/modules/ctools/plugins/access/term_parent.inc
index 27375dfa..0fddc020 100644
--- a/drupal7/sites/all/modules/ctools/plugins/access/term_parent.inc
+++ b/drupal7/sites/all/modules/ctools/plugins/access/term_parent.inc
@@ -15,7 +15,7 @@ $plugin = array(
   'callback' => 'ctools_term_parent_ctools_access_check',
   'default' => array('vid' => array(), 'negate' => 0),
   'settings form' => 'ctools_term_parent_ctools_access_settings',
-  'settings form validation' => 'ctools_term_parent_ctools_access_settings_validate',
+  'settings form validate' => 'ctools_term_parent_ctools_access_settings_validate',
   'settings form submit' => 'ctools_term_parent_ctools_access_settings_submit',
   'summary' => 'ctools_term_parent_ctools_access_summary',
   'required context' => new ctools_context_required(t('Term'), array('taxonomy_term', 'terms')),
diff --git a/drupal7/sites/all/modules/ctools/plugins/content_types/block/block.inc b/drupal7/sites/all/modules/ctools/plugins/content_types/block/block.inc
index ff75c750..ee5ce576 100644
--- a/drupal7/sites/all/modules/ctools/plugins/content_types/block/block.inc
+++ b/drupal7/sites/all/modules/ctools/plugins/content_types/block/block.inc
@@ -352,7 +352,7 @@ function block_ctools_block_info($module, $delta, &$info) {
   // The title of custom blocks from the block module is stored in the
   // {block} table. Look for it in the default theme as a reasonable
   // default value for the title.
-  $block_info_cache = drupal_static(__FUNCTION__);
+  $block_info_cache = &drupal_static(__FUNCTION__);
   if (!isset($block_info_cache)) {
     $block_info_cache = db_select('block', 'b')
       ->fields('b')
diff --git a/drupal7/sites/all/modules/ctools/stylizer/stylizer.info b/drupal7/sites/all/modules/ctools/stylizer/stylizer.info
index 90204a3d..a86ada60 100644
--- a/drupal7/sites/all/modules/ctools/stylizer/stylizer.info
+++ b/drupal7/sites/all/modules/ctools/stylizer/stylizer.info
@@ -5,8 +5,8 @@ package = Chaos tool suite
 dependencies[] = ctools
 dependencies[] = color
 
-; Information added by Drupal.org packaging script on 2019-02-08
-version = "7.x-1.15"
+; Information added by Drupal.org packaging script on 2020-10-23
+version = "7.x-1.17"
 core = "7.x"
 project = "ctools"
-datestamp = "1549603691"
+datestamp = "1603490551"
diff --git a/drupal7/sites/all/modules/ctools/term_depth/plugins/access/term_depth.inc b/drupal7/sites/all/modules/ctools/term_depth/plugins/access/term_depth.inc
index d63183f3..eb302b62 100644
--- a/drupal7/sites/all/modules/ctools/term_depth/plugins/access/term_depth.inc
+++ b/drupal7/sites/all/modules/ctools/term_depth/plugins/access/term_depth.inc
@@ -15,7 +15,7 @@ $plugin = array(
   'callback' => 'term_depth_term_depth_ctools_access_check',
   'default' => array('vid' => array(), 'depth' => 0),
   'settings form' => 'term_depth_term_depth_ctools_access_settings',
-  'settings form validation' => 'term_depth_term_depth_ctools_access_settings_validate',
+  'settings form validate' => 'term_depth_term_depth_ctools_access_settings_validate',
   'settings form submit' => 'term_depth_term_depth_ctools_access_settings_submit',
   'summary' => 'term_depth_term_depth_ctools_access_summary',
   'required context' => new ctools_context_required(t('Term'), array('taxonomy_term', 'terms')),
diff --git a/drupal7/sites/all/modules/ctools/term_depth/term_depth.info b/drupal7/sites/all/modules/ctools/term_depth/term_depth.info
index 4baee51d..4f05954d 100644
--- a/drupal7/sites/all/modules/ctools/term_depth/term_depth.info
+++ b/drupal7/sites/all/modules/ctools/term_depth/term_depth.info
@@ -4,8 +4,8 @@ core = 7.x
 dependencies[] = ctools
 package = Chaos tool suite
 
-; Information added by Drupal.org packaging script on 2019-02-08
-version = "7.x-1.15"
+; Information added by Drupal.org packaging script on 2020-10-23
+version = "7.x-1.17"
 core = "7.x"
 project = "ctools"
-datestamp = "1549603691"
+datestamp = "1603490551"
diff --git a/drupal7/sites/all/modules/ctools/tests/ctools_export_test/ctools_export_test.info b/drupal7/sites/all/modules/ctools/tests/ctools_export_test/ctools_export_test.info
index a2d0efbd..bc231dbf 100644
--- a/drupal7/sites/all/modules/ctools/tests/ctools_export_test/ctools_export_test.info
+++ b/drupal7/sites/all/modules/ctools/tests/ctools_export_test/ctools_export_test.info
@@ -7,8 +7,8 @@ hidden = TRUE
 
 files[] = ctools_export.test
 
-; Information added by Drupal.org packaging script on 2019-02-08
-version = "7.x-1.15"
+; Information added by Drupal.org packaging script on 2020-10-23
+version = "7.x-1.17"
 core = "7.x"
 project = "ctools"
-datestamp = "1549603691"
+datestamp = "1603490551"
diff --git a/drupal7/sites/all/modules/ctools/tests/ctools_plugin_test.info b/drupal7/sites/all/modules/ctools/tests/ctools_plugin_test.info
index d51a5261..bdb624c7 100644
--- a/drupal7/sites/all/modules/ctools/tests/ctools_plugin_test.info
+++ b/drupal7/sites/all/modules/ctools/tests/ctools_plugin_test.info
@@ -5,8 +5,8 @@ core = 7.x
 dependencies[] = ctools
 hidden = TRUE
 
-; Information added by Drupal.org packaging script on 2019-02-08
-version = "7.x-1.15"
+; Information added by Drupal.org packaging script on 2020-10-23
+version = "7.x-1.17"
 core = "7.x"
 project = "ctools"
-datestamp = "1549603691"
+datestamp = "1603490551"
diff --git a/drupal7/sites/all/modules/ctools/tests/math_expression.test b/drupal7/sites/all/modules/ctools/tests/math_expression.test
index b34fafb9..023e46a4 100644
--- a/drupal7/sites/all/modules/ctools/tests/math_expression.test
+++ b/drupal7/sites/all/modules/ctools/tests/math_expression.test
@@ -27,121 +27,308 @@ class CtoolsMathExpressionTestCase extends DrupalWebTestCase {
   }
 
   /**
-   * Returns a random double between 0 and 1.
+   * Return the sign of the numeric arg $n as an integer -1, 0, 1.
+   *
+   * Note: Not defined when $n is Infinity or NaN (or NULL or ...)!
+   *
+   * @param int|float $n
+   *   The number to test.
+   *
+   * @return int
+   *   -1 if the $n is negative, 0 if $n is zero or 1 if $n is positive.
+   *
+   * @see gmp_sign()
+   */
+  protected static function sign($n) {
+    return ($n > 0) - ($n < 0);
+  }
+
+  /**
+   * Returns a random number between 0 and 1.
+   *
+   * @return float
+   *   A random number between 0 and 1 inclusive.
    */
   protected function rand01() {
-    return rand(0, PHP_INT_MAX) / PHP_INT_MAX;
+    return mt_rand(0, PHP_INT_MAX) / PHP_INT_MAX;
   }
 
   /**
    * A custom assertion with checks the values in a certain range.
+   *
+   * @param float $first
+   *   A value to check for equality.
+   * @param float $second
+   *   A value to check for equality.
+   * @param string $message
+   *   The message describing the correct behaviour, eg. "2/4 equals 1/2". The
+   *   default message is used if this value is empty.
+   * @param float $delta
+   *   The precision with which values must match. This accounts for rounding
+   *   errors and imprecise representation errors in the floating point format.
+   *   The value passed in should ideally be proportional to the values being
+   *   compared.
+   * @param string $group
+   *   Which group this assert belongs to.
+   *
+   * @return bool
+   *   TRUE if the assertion was correct (that is, $first == $second within the
+   *   given limits), FALSE otherwise.
    */
-  protected function assertFloat($first, $second, $delta = 0.0000001, $message = '', $group = 'Other') {
-    return $this->assert(abs($first - $second) <= $delta, $message ? $message : t('Value @first is equal to value @second.', array('@first' => var_export($first, TRUE), '@second' => var_export($second, TRUE))), $group);
+  protected function assertFloat($first, $second, $message = '', $delta = 0.00000001, $group = 'Other') {
+    // Check for NaN and Inf because the abs() and sign() code won't like those.
+    $equal = FALSE
+      // Equal if both an infinity.
+      || (is_infinite($first) && is_infinite($second))
+
+      // Equal if both NaN.
+      || (is_nan($first) && is_nan($second))
+
+      // Equal if same absolute value (within limits) and same sign.
+      || ((abs($first - $second) <= $delta) && (self::sign($first) === self::sign($second)));
+
+    if (empty($message)) {
+      $default = t('Value !first is equal to value !second.',
+        array(
+          '!first' => var_export($first, TRUE),
+          '!second' => var_export($second, TRUE),
+        ));
+      $message = $default;
+    }
+
+    return $this->assert($equal, $message, $group);
   }
 
   /**
    * Test some arithmetic handling.
    */
   public function testArithmetic() {
-    $math_expression = new ctools_math_expr();
-
-    // Test constant expressions.
-    $this->assertEqual($math_expression->e('2'), 2);
-    $random_number = rand(0, 10);
-    $this->assertEqual($random_number, $math_expression->e((string) $random_number));
-
-    // Test simple arithmetic.
-    $random_number_a = rand(5, 10);
-    $random_number_b = rand(5, 10);
-    $this->assertEqual($random_number_a + $random_number_b, $math_expression->e("$random_number_a + $random_number_b"));
-    $this->assertEqual($random_number_a - $random_number_b, $math_expression->e("$random_number_a - $random_number_b"));
-    $this->assertEqual($random_number_a * $random_number_b, $math_expression->e("$random_number_a * $random_number_b"));
-    $this->assertEqual($random_number_a / $random_number_b, $math_expression->e("$random_number_a / $random_number_b"));
-
-    // Test Associative property.
-    $random_number_c = rand(5, 10);
-    $this->assertEqual($math_expression->e("$random_number_a + ($random_number_b + $random_number_c)"), $math_expression->e("($random_number_a + $random_number_b) + $random_number_c"));
-    $this->assertEqual($math_expression->e("$random_number_a * ($random_number_b * $random_number_c)"), $math_expression->e("($random_number_a * $random_number_b) * $random_number_c"));
-
-    // Test Commutative property.
-    $this->assertEqual($math_expression->e("$random_number_a + $random_number_b"), $math_expression->e("$random_number_b + $random_number_a"));
-    $this->assertEqual($math_expression->e("$random_number_a * $random_number_b"), $math_expression->e("$random_number_b * $random_number_a"));
-
-    // Test Distributive property.
-    $this->assertEqual($math_expression->e("($random_number_a + $random_number_b) * $random_number_c"), $math_expression->e("($random_number_a * $random_number_c + $random_number_b * $random_number_c)"));
-
-    $this->assertEqual(pow($random_number_a, $random_number_b), $math_expression->e("$random_number_a ^ $random_number_b"));
+    $math_expr = new ctools_math_expr();
+
+    $this->assertEqual($math_expr->evaluate('2'), 2, 'Check Literal 2');
+
+    $this->assertEqual($math_expr->e('2+1'), $math_expr->evaluate('2+1'), 'Check that e() and evaluate() are equivalent.');
+
+    foreach (range(1, 4) as $n) {
+      // Test constant expressions.
+      $random_number = mt_rand(0, 20);
+      $this->assertEqual($random_number, $math_expr->evaluate((string) $random_number), "Literal $random_number");
+
+      // Test simple arithmetic.
+      $number_a = mt_rand(-55, 777);
+      $number_b = mt_rand(-555, 77);
+      $this->assertEqual(
+        $number_a + $number_b,
+        $math_expr->evaluate("$number_a + $number_b"),
+        "Addition: $number_a + $number_b");
+      $this->assertEqual(
+        $number_a - $number_b,
+        $math_expr->evaluate("$number_a - $number_b"),
+        "Subtraction: $number_a + $number_b");
+      $this->assertFloat(
+        ($number_a * $number_b),
+        $math_expr->evaluate("$number_a * $number_b"),
+        "Multiplication: $number_a * $number_b = " . ($number_a * $number_b));
+      $this->assertFloat(
+        ($number_a / $number_b),
+        $math_expr->evaluate("$number_a / $number_b"),
+        "Division: $number_a / $number_b = " . ($number_a / $number_b));
+
+      // Test Associative property.
+      $number_c = mt_rand(-99, 77);
+      $this->assertEqual(
+        $math_expr->evaluate("$number_a + ($number_b + $number_c)"),
+        $math_expr->evaluate("($number_a + $number_b) + $number_c"),
+        "Associative: $number_a + ($number_b + $number_c)");
+      $this->assertEqual(
+        $math_expr->evaluate("$number_a * ($number_b * $number_c)"),
+        $math_expr->evaluate("($number_a * $number_b) * $number_c"),
+        "Associative: $number_a * ($number_b * $number_c)");
+
+      // Test Commutative property.
+      $this->assertEqual(
+        $math_expr->evaluate("$number_a + $number_b"),
+        $math_expr->evaluate("$number_b + $number_a"),
+        "Commutative: $number_a + $number_b");
+      $this->assertEqual(
+        $math_expr->evaluate("$number_a * $number_b"),
+        $math_expr->evaluate("$number_b * $number_a"),
+        "Commutative: $number_a * $number_b");
+
+      // Test Distributive property.
+      $this->assertEqual(
+        $math_expr->evaluate("($number_a + $number_b) * $number_c"),
+        $math_expr->evaluate("($number_a * $number_c + $number_b * $number_c)"),
+        "Distributive: ($number_a + $number_b) * $number_c");
+
+      // @todo: Doesn't work with zero or negative powers when number is zero or negative, e.g. 0^0, 0^-2, -2^0, -2^-2.
+      $random_number = mt_rand(1, 15);
+      $random_power = mt_rand(-15, 15);
+
+      $this->assertFloat(
+        pow($random_number, $random_power),
+        $math_expr->evaluate("$random_number ^ $random_power"),
+        "$random_number ^ $random_power");
+
+      $this->assertFloat(
+        pow($random_number, $random_power),
+        $math_expr->evaluate("pow($random_number, $random_power)"),
+        "pow($random_number, $random_power)");
+    }
   }
 
   /**
-   * Test the basic built-in functions in the math expression library.
+   * Test various built-in transcendental and extended functions.
    */
   public function testBuildInFunctions() {
-    $math_expression = new ctools_math_expr();
-
-    // @todo Maybe run this code multiple times to test different values.
-    $random_double = $this->rand01();
-    $random_int = rand(5, 10);
-    $this->assertFloat(sin($random_double), $math_expression->e("sin($random_double)"));
-    $this->assertFloat(cos($random_double), $math_expression->e("cos($random_double)"));
-    $this->assertFloat(tan($random_double), $math_expression->e("tan($random_double)"));
-    $this->assertFloat(exp($random_double), $math_expression->e("exp($random_double)"));
-    $this->assertFloat(sqrt($random_double), $math_expression->e("sqrt($random_double)"));
-    $this->assertFloat(log($random_double), $math_expression->e("ln($random_double)"));
-    $this->assertFloat(round($random_double), $math_expression->e("round($random_double)"));
-    $this->assertFloat(abs($random_double + $random_int), $math_expression->e('abs(' . ($random_double + $random_int) . ')'));
-    $this->assertEqual(round($random_double + $random_int), $math_expression->e('round(' . ($random_double + $random_int) . ')'));
-    $this->assertEqual(ceil($random_double + $random_int), $math_expression->e('ceil(' . ($random_double + $random_int) . ')'));
-    $this->assertEqual(floor($random_double + $random_int), $math_expression->e('floor(' . ($random_double + $random_int) . ')'));
-
-    // @fixme: you can't run time without an argument.
-    $this->assertFloat(time(), $math_expression->e('time(1)'));
+    $math_expr = new ctools_math_expr();
+
+    foreach (range(1, 4) as $n) {
+      $random_double = $this->rand01();
+      $random_int = mt_rand(-65535, 65535);
+      $this->assertFloat(sin($random_double), $math_expr->evaluate("sin($random_double)"), "sin($random_double)");
+      $this->assertFloat(cos($random_double), $math_expr->evaluate("cos($random_double)"), "cos($random_double)");
+      $this->assertFloat(tan($random_double), $math_expr->evaluate("tan($random_double)"), "tan($random_double)");
+      $this->assertFloat(exp($random_double), $math_expr->evaluate("exp($random_double)"), "exp($random_double)");
+      $this->assertFloat(sqrt($random_double), $math_expr->evaluate("sqrt($random_double)"), "sqrt($random_double)");
+      $this->assertFloat(log($random_double), $math_expr->evaluate("ln($random_double)"), "ln($random_double)");
+      $this->assertFloat(round($random_double), $math_expr->evaluate("round($random_double)"), "round($random_double)");
+
+      $random_real = $random_double + $random_int;
+      $this->assertFloat(abs($random_real), $math_expr->evaluate('abs(' . $random_real . ')'), "abs($random_real)");
+      $this->assertEqual(round($random_real), $math_expr->evaluate('round(' . $random_real . ')'), "round($random_real)");
+      $this->assertEqual(ceil($random_real), $math_expr->evaluate('ceil(' . $random_real . ')'), "ceil($random_real)");
+      $this->assertEqual(floor($random_real), $math_expr->evaluate('floor(' . $random_real . ')'), "floor($random_real)");
+    }
+
+    $this->assertFloat(time(), $math_expr->evaluate('time()'), "time()");
 
     $random_double_a = $this->rand01();
     $random_double_b = $this->rand01();
-    // @fixme: max/min don't work at the moment.
-//    $this->assertFloat(max($random_double_a, $random_double_b), $math_expression->e("max($random_double_a, $random_double_b)"));
-//    $this->assertFloat(min($random_double_a, $random_double_b), $math_expression->e("min($random_double_a, $random_double_b)"));
+    $this->assertFloat(
+      max($random_double_a, $random_double_b),
+      $math_expr->evaluate("max($random_double_a, $random_double_b)"),
+      "max($random_double_a, $random_double_b)");
+    $this->assertFloat(
+      min($random_double_a, $random_double_b),
+      $math_expr->evaluate("min($random_double_a, $random_double_b)"),
+      "min($random_double_a, $random_double_b)");
   }
 
   /**
    * Test variable handling.
    */
   public function testVariables() {
-    $math_expression = new ctools_math_expr();
+    $math_expr = new ctools_math_expr();
+
+    // We should have a definition of pi:
+    $this->assertFloat(pi(), $math_expr->evaluate('pi'));
+
+    // And a definition of e:
+    $this->assertFloat(exp(1), $math_expr->evaluate('e'));
+
+    $number_a = 5;
+    $number_b = 10;
+
+    // Store the first number and use it on a calculation.
+    $math_expr->evaluate("var = $number_a");
+    $this->assertEqual($number_a + $number_b, $math_expr->evaluate("var + $number_b"));
 
-    $random_number_a = rand(5, 10);
-    $random_number_b = rand(5, 10);
+    // Change the value and check the new value is used.
+    $math_expr->evaluate("var = $number_b");
+    $this->assertEqual(
+      $number_b + $number_b,
+      $math_expr->evaluate("var + $number_b"),
+      "var + $number_b");
 
-    // Store the first random number and use it on calculations.
-    $math_expression->e("var = $random_number_a");
-    $this->assertEqual($random_number_a + $random_number_b, $math_expression->e("var + $random_number_b"));
-    $this->assertEqual($random_number_a * $random_number_b, $math_expression->e("var * $random_number_b"));
-    $this->assertEqual($random_number_a - $random_number_b, $math_expression->e("var - $random_number_b"));
-    $this->assertEqual($random_number_a / $random_number_b, $math_expression->e("var / $random_number_b"));
+    // Store another number and use it on a calculation.
+    $math_expr->evaluate("var = $number_a");
+    $math_expr->evaluate("newvar = $number_a");
+
+    $this->assertEqual(
+      $number_a + $number_a,
+      $math_expr->evaluate('var + newvar'),
+      'var + newvar');
+
+    $this->assertFloat(
+      $number_a / $number_b,
+      $math_expr->evaluate("var / $number_b"),
+      "var / $number_b");
   }
 
   /**
    * Test custom function handling.
    */
   public function testCustomFunctions() {
-    $math_expression = new ctools_math_expr();
+    $math_expr = new ctools_math_expr();
 
-    $random_number_a = rand(5, 10);
-    $random_number_b = rand(5, 10);
+    $number_a = mt_rand(5, 10);
+    $number_b = mt_rand(5, 10);
 
     // Create a one-argument function.
-    $math_expression->e("f(x) = 2 * x");
-    $this->assertEqual($random_number_a * 2, $math_expression->e("f($random_number_a)"));
-    $this->assertEqual($random_number_b * 2, $math_expression->e("f($random_number_b)"));
+    $math_expr->evaluate("f(x) = 2 * x");
+    $this->assertEqual($number_a * 2, $math_expr->evaluate("f($number_a)"));
+    $this->assertEqual($number_b * 2, $math_expr->evaluate("f($number_b)"));
 
     // Create a two-argument function.
-    $math_expression->e("g(x, y) = 2 * x + y");
-    $this->assertEqual($random_number_a * 2 + $random_number_b, $math_expression->e("g($random_number_a, $random_number_b)"));
+    $math_expr->evaluate("g(x, y) = 2 * x + y");
+    $this->assertEqual(
+      $number_a * 2 + $number_b,
+      $math_expr->evaluate("g($number_a, $number_b)"),
+      "g($number_a, $number_b)");
 
     // Use a custom function in another function.
-    $this->assertEqual(($random_number_a * 2 + $random_number_b) * 2, $math_expression->e("f(g($random_number_a, $random_number_b))"));
+    $this->assertEqual(
+      ($number_a * 2 + $number_b) * 2,
+      $math_expr->evaluate("f(g($number_a, $number_b))"),
+      "f(g($number_a, $number_b))");
+  }
+
+  /**
+   * Test conditional handling.
+   */
+  public function testIf() {
+    $math_expr = new ctools_math_expr();
+
+    $number_a = mt_rand(1, 10);
+    $number_b = mt_rand(11, 20);
+
+    foreach (range(1, 4) as $n) {
+      // @todo: Doesn't work with negative numbers.
+      if ($n == 2 || $n == 4) {
+        //$number_a = -$number_a;
+      }
+
+      if ($n == 3 || $n == 4) {
+        //$number_b = -$number_b;
+      }
+
+      $this->assertEqual(
+        $number_a,
+        $math_expr->evaluate("if(1, $number_a, $number_b)"),
+        "if(1, $number_a, $number_b)");
+
+      $this->assertEqual(
+        $number_a,
+        $math_expr->evaluate("if(1, $number_a)",
+          "if(1, $number_a)"));
+
+      $this->assertEqual(
+        $number_b,
+        $math_expr->evaluate("if(0, $number_a, $number_b)"),
+        "if(0, $number_a, $number_b)");
+
+      // Also add an expression so ensure it's evaluated.
+      $this->assertEqual(
+        $number_b,
+        $math_expr->evaluate("if($number_a > $number_b, $number_a, $number_b)"),
+        "if($number_a > $number_b, $number_a, $number_b)");
+
+      $this->assertEqual(
+        $number_b,
+        $math_expr->evaluate("if($number_a < $number_b, $number_b, $number_a)"),
+        "if($number_a < $number_b, $number_b, $number_a)");
+    }
   }
 
 }
diff --git a/drupal7/sites/all/modules/ctools/views_content/plugins/content_types/views.inc b/drupal7/sites/all/modules/ctools/views_content/plugins/content_types/views.inc
index 9ad7728f..0754d426 100644
--- a/drupal7/sites/all/modules/ctools/views_content/plugins/content_types/views.inc
+++ b/drupal7/sites/all/modules/ctools/views_content/plugins/content_types/views.inc
@@ -173,7 +173,7 @@ function views_content_views_content_type_render($subtype, $conf, $panel_args, $
       if ($conf['use_pager'] && ($pager['type'] == 'none' || $pager['type'] == 'some')) {
         $pager['type'] = 'full';
       }
-      elseif (!$conf['use_pager'] && $pager['type'] != 'none' && $pager['type'] != 'some') {
+      elseif (!$conf['use_pager']) {
         $pager['type'] = $view->get_items_per_page() ? 'some' : 'none';
       }
 
diff --git a/drupal7/sites/all/modules/ctools/views_content/plugins/content_types/views_panes.inc b/drupal7/sites/all/modules/ctools/views_content/plugins/content_types/views_panes.inc
index 07e93f7a..afc98136 100644
--- a/drupal7/sites/all/modules/ctools/views_content/plugins/content_types/views_panes.inc
+++ b/drupal7/sites/all/modules/ctools/views_content/plugins/content_types/views_panes.inc
@@ -254,7 +254,7 @@ function views_content_views_panes_content_type_render($subtype, $conf, $panel_a
     if ($conf['use_pager'] && ($pager['type'] == 'none' || $pager['type'] == 'some')) {
       $pager['type'] = 'full';
     }
-    elseif (!$conf['use_pager'] && $pager['type'] != 'none' && $pager['type'] != 'some') {
+    elseif (!$conf['use_pager']) {
       $pager['type'] = $view->get_items_per_page() || !empty($pager['options']['items_per_page']) ? 'some' : 'none';
     }
 
diff --git a/drupal7/sites/all/modules/ctools/views_content/plugins/content_types/views_row.inc b/drupal7/sites/all/modules/ctools/views_content/plugins/content_types/views_row.inc
index 16b97dc7..6678f447 100644
--- a/drupal7/sites/all/modules/ctools/views_content/plugins/content_types/views_row.inc
+++ b/drupal7/sites/all/modules/ctools/views_content/plugins/content_types/views_row.inc
@@ -228,8 +228,9 @@ function views_content_views_row_content_type_admin_info($subtype, $conf, $conte
 
 function views_content_views_row_content_type_admin_title($subtype, $conf, $context) {
   $rows = array_filter($conf['rows']);
+  $row_count = count($rows);
   $rows = empty($rows) ? t('Show all') : implode(', ', $rows);
-  return format_plural(count($rows),
+  return format_plural($row_count,
     '"@context" row @rows',
     '"@context" rows @rows',
     array('@context' => $context->identifier, '@rows' => $rows)
diff --git a/drupal7/sites/all/modules/ctools/views_content/plugins/views/views_content_plugin_display_panel_pane.inc b/drupal7/sites/all/modules/ctools/views_content/plugins/views/views_content_plugin_display_panel_pane.inc
index 05d3478f..801d3d61 100644
--- a/drupal7/sites/all/modules/ctools/views_content/plugins/views/views_content_plugin_display_panel_pane.inc
+++ b/drupal7/sites/all/modules/ctools/views_content/plugins/views/views_content_plugin_display_panel_pane.inc
@@ -4,6 +4,7 @@
  * The plugin that handles a panel_pane.
  */
 class views_content_plugin_display_panel_pane extends views_plugin_display {
+
   /**
    * If this variable is true, this display counts as a panel pane. We use
    * this variable so that other modules can create alternate pane displays.
@@ -11,7 +12,9 @@ class views_content_plugin_display_panel_pane extends views_plugin_display {
   public $panel_pane_display = TRUE;
   public $has_pane_conf = NULL;
 
-
+  /**
+   * {@inheritdoc}
+   */
   public function option_definition() {
     $options = parent::option_definition();
 
@@ -382,7 +385,31 @@ class views_content_plugin_display_panel_pane extends views_plugin_display {
     return (bool) $conf['more_link'];
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function has_path() {
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validate() {
+    // To bypass the validation of the path from Views we temporarily
+    // override the path if one doesn't exist because it will be generated
+    // by panels though we want the rest of the validations to run.
+    $path = $this->get_path();
+    if (!$path) {
+      $this->set_option('path', $_GET['q']);
+    }
+
+    return parent::validate();
+  }
 
+  /**
+   * {@inheritdoc}
+   */
   public function get_path() {
     if (empty($this->view->override_path)) {
       return parent::get_path();
diff --git a/drupal7/sites/all/modules/ctools/views_content/views_content.admin.inc b/drupal7/sites/all/modules/ctools/views_content/views_content.admin.inc
deleted file mode 100644
index e69de29b..00000000
diff --git a/drupal7/sites/all/modules/ctools/views_content/views_content.info b/drupal7/sites/all/modules/ctools/views_content/views_content.info
index 68f4d875..9b348c52 100644
--- a/drupal7/sites/all/modules/ctools/views_content/views_content.info
+++ b/drupal7/sites/all/modules/ctools/views_content/views_content.info
@@ -9,8 +9,8 @@ files[] = plugins/views/views_content_plugin_display_ctools_context.inc
 files[] = plugins/views/views_content_plugin_display_panel_pane.inc
 files[] = plugins/views/views_content_plugin_style_ctools_context.inc
 
-; Information added by Drupal.org packaging script on 2019-02-08
-version = "7.x-1.15"
+; Information added by Drupal.org packaging script on 2020-10-23
+version = "7.x-1.17"
 core = "7.x"
 project = "ctools"
-datestamp = "1549603691"
+datestamp = "1603490551"
