diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 4f438e7b423..f9b010c58cb 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,33 @@ +Drupal 7.78, 2021-01-19 +----------------------- +- Fixed security issues: + - SA-CORE-2021-001 + +Drupal 7.77, 2020-12-03 +----------------------- +- Hotfix for schema.prefixed tables + +Drupal 7.76, 2020-12-02 +----------------------- +- Support for MySQL 8 +- Core tests pass in SQLite +- Better user flood control logging + +Drupal 7.75, 2020-11-26 +----------------------- +- Fixed security issues: + - SA-CORE-2020-013 + +Drupal 7.74, 2020-11-17 +----------------------- +- Fixed security issues: + - SA-CORE-2020-012 + +Drupal 7.73, 2020-09-16 +----------------------- +- Fixed security issues: + - SA-CORE-2020-007 + Drupal 7.72, 2020-06-17 ----------------------- - Fixed security issues: diff --git a/MAINTAINERS.txt b/MAINTAINERS.txt index 4ea5572a7cf..cbc9f51a85a 100644 --- a/MAINTAINERS.txt +++ b/MAINTAINERS.txt @@ -12,7 +12,7 @@ The branch maintainers for Drupal 7 are: - Dries Buytaert 'dries' https://www.drupal.org/u/dries - Fabian Franz 'Fabianx' https://www.drupal.org/u/fabianx -- (provisional) Drew Webber 'mcdruid' https://www.drupal.org/u/mcdruid +- Drew Webber 'mcdruid' https://www.drupal.org/u/mcdruid Component maintainers diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc index efe1dfd171c..e79c5d50942 100644 --- a/includes/bootstrap.inc +++ b/includes/bootstrap.inc @@ -8,7 +8,7 @@ /** * The current system version. */ -define('VERSION', '7.72'); +define('VERSION', '7.78'); /** * Core API compatibility. @@ -1214,19 +1214,21 @@ function variable_initialize($conf = array()) { $variables = $cached->data; } else { - // Cache miss. Avoid a stampede. + // Cache miss. Avoid a stampede by acquiring a lock. If the lock fails to + // acquire, optionally just continue with uncached processing. $name = 'variable_init'; - if (!lock_acquire($name, 1)) { - // Another request is building the variable cache. - // Wait, then re-run this function. + $lock_acquired = lock_acquire($name, 1); + if (!$lock_acquired && variable_get('variable_initialize_wait_for_lock', FALSE)) { lock_wait($name); return variable_initialize($conf); } else { - // Proceed with variable rebuild. + // Load the variables from the table. $variables = array_map('unserialize', db_query('SELECT name, value FROM {variable}')->fetchAllKeyed()); - cache_set('variables', $variables, 'cache_bootstrap'); - lock_release($name); + if ($lock_acquired) { + cache_set('variables', $variables, 'cache_bootstrap'); + lock_release($name); + } } } diff --git a/includes/common.inc b/includes/common.inc index cfb29576f37..5a39201ddcd 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -6670,30 +6670,41 @@ function element_children(&$elements, $sort = FALSE) { $sort = isset($elements['#sorted']) ? !$elements['#sorted'] : $sort; // Filter out properties from the element, leaving only children. - $children = array(); + $count = count($elements); + $child_weights = array(); + $i = 0; $sortable = FALSE; foreach ($elements as $key => $value) { if (is_int($key) || $key === '' || $key[0] !== '#') { - $children[$key] = $value; if (is_array($value) && isset($value['#weight'])) { + $weight = $value['#weight']; $sortable = TRUE; } + else { + $weight = 0; + } + // Support weights with up to three digit precision and conserve the + // insertion order. + $child_weights[$key] = floor($weight * 1000) + $i / $count; } + $i++; } + // Sort the children if necessary. if ($sort && $sortable) { - uasort($children, 'element_sort'); + asort($child_weights); // Put the sorted children back into $elements in the correct order, to // preserve sorting if the same element is passed through // element_children() twice. - foreach ($children as $key => $child) { + foreach ($child_weights as $key => $weight) { + $value = $elements[$key]; unset($elements[$key]); - $elements[$key] = $child; + $elements[$key] = $value; } $elements['#sorted'] = TRUE; } - return array_keys($children); + return array_keys($child_weights); } /** diff --git a/includes/database/database.inc b/includes/database/database.inc index 6879f699162..d4d2d8f0220 100644 --- a/includes/database/database.inc +++ b/includes/database/database.inc @@ -310,6 +310,13 @@ abstract class DatabaseConnection extends PDO { */ protected $escapedAliases = array(); + /** + * List of un-prefixed table names, keyed by prefixed table names. + * + * @var array + */ + protected $unprefixedTablesMap = array(); + function __construct($dsn, $username, $password, $driver_options = array()) { // Initialize and prepare the connection prefix. $this->setPrefix(isset($this->connectionOptions['prefix']) ? $this->connectionOptions['prefix'] : ''); @@ -338,7 +345,9 @@ abstract class DatabaseConnection extends PDO { // Destroy all references to this connection by setting them to NULL. // The Statement class attribute only accepts a new value that presents a // proper callable, so we reset it to PDOStatement. - $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('PDOStatement', array())); + if (!empty($this->statementClass)) { + $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('PDOStatement', array())); + } $this->schema = NULL; } @@ -442,6 +451,13 @@ abstract class DatabaseConnection extends PDO { $this->prefixReplace[] = $this->prefixes['default']; $this->prefixSearch[] = '}'; $this->prefixReplace[] = ''; + + // Set up a map of prefixed => un-prefixed tables. + foreach ($this->prefixes as $table_name => $prefix) { + if ($table_name !== 'default') { + $this->unprefixedTablesMap[$prefix . $table_name] = $table_name; + } + } } /** @@ -477,6 +493,17 @@ abstract class DatabaseConnection extends PDO { } } + /** + * Gets a list of individually prefixed table names. + * + * @return array + * An array of un-prefixed table names, keyed by their fully qualified table + * names (i.e. prefix + table_name). + */ + public function getUnprefixedTablesMap() { + return $this->unprefixedTablesMap; + } + /** * Prepares a query string and returns the prepared statement. * @@ -2840,7 +2867,6 @@ function db_field_exists($table, $field) { * * @param $table_expression * An SQL expression, for example "simpletest%" (without the quotes). - * BEWARE: this is not prefixed, the caller should take care of that. * * @return * Array, both the keys and the values are the matching tables. @@ -2849,6 +2875,23 @@ function db_find_tables($table_expression) { return Database::getConnection()->schema()->findTables($table_expression); } +/** + * Finds all tables that are like the specified base table name. This is a + * backport of the change made to db_find_tables in Drupal 8 to work with + * virtual, un-prefixed table names. The original function is retained for + * Backwards Compatibility. + * @see https://www.drupal.org/node/2552435 + * + * @param $table_expression + * An SQL expression, for example "simpletest%" (without the quotes). + * + * @return + * Array, both the keys and the values are the matching tables. + */ +function db_find_tables_d8($table_expression) { + return Database::getConnection()->schema()->findTablesD8($table_expression); +} + function _db_create_keys_sql($spec) { return Database::getConnection()->schema()->createKeysSql($spec); } diff --git a/includes/database/mysql/database.inc b/includes/database/mysql/database.inc index 356e039f732..00df3c13e91 100644 --- a/includes/database/mysql/database.inc +++ b/includes/database/mysql/database.inc @@ -5,6 +5,11 @@ * Database interface code for MySQL database servers. */ +/** + * The default character for quoting identifiers in MySQL. + */ +define('MYSQL_IDENTIFIER_QUOTE_CHARACTER_DEFAULT', '`'); + /** * @addtogroup database * @{ @@ -19,6 +24,277 @@ class DatabaseConnection_mysql extends DatabaseConnection { */ protected $needsCleanup = FALSE; + /** + * The list of MySQL reserved key words. + * + * @link https://dev.mysql.com/doc/refman/8.0/en/keywords.html + */ + private $reservedKeyWords = array( + 'accessible', + 'add', + 'admin', + 'all', + 'alter', + 'analyze', + 'and', + 'as', + 'asc', + 'asensitive', + 'before', + 'between', + 'bigint', + 'binary', + 'blob', + 'both', + 'by', + 'call', + 'cascade', + 'case', + 'change', + 'char', + 'character', + 'check', + 'collate', + 'column', + 'condition', + 'constraint', + 'continue', + 'convert', + 'create', + 'cross', + 'cube', + 'cume_dist', + 'current_date', + 'current_time', + 'current_timestamp', + 'current_user', + 'cursor', + 'database', + 'databases', + 'day_hour', + 'day_microsecond', + 'day_minute', + 'day_second', + 'dec', + 'decimal', + 'declare', + 'default', + 'delayed', + 'delete', + 'dense_rank', + 'desc', + 'describe', + 'deterministic', + 'distinct', + 'distinctrow', + 'div', + 'double', + 'drop', + 'dual', + 'each', + 'else', + 'elseif', + 'empty', + 'enclosed', + 'escaped', + 'except', + 'exists', + 'exit', + 'explain', + 'false', + 'fetch', + 'first_value', + 'float', + 'float4', + 'float8', + 'for', + 'force', + 'foreign', + 'from', + 'fulltext', + 'function', + 'generated', + 'get', + 'grant', + 'group', + 'grouping', + 'groups', + 'having', + 'high_priority', + 'hour_microsecond', + 'hour_minute', + 'hour_second', + 'if', + 'ignore', + 'in', + 'index', + 'infile', + 'inner', + 'inout', + 'insensitive', + 'insert', + 'int', + 'int1', + 'int2', + 'int3', + 'int4', + 'int8', + 'integer', + 'interval', + 'into', + 'io_after_gtids', + 'io_before_gtids', + 'is', + 'iterate', + 'join', + 'json_table', + 'key', + 'keys', + 'kill', + 'lag', + 'last_value', + 'lead', + 'leading', + 'leave', + 'left', + 'like', + 'limit', + 'linear', + 'lines', + 'load', + 'localtime', + 'localtimestamp', + 'lock', + 'long', + 'longblob', + 'longtext', + 'loop', + 'low_priority', + 'master_bind', + 'master_ssl_verify_server_cert', + 'match', + 'maxvalue', + 'mediumblob', + 'mediumint', + 'mediumtext', + 'middleint', + 'minute_microsecond', + 'minute_second', + 'mod', + 'modifies', + 'natural', + 'not', + 'no_write_to_binlog', + 'nth_value', + 'ntile', + 'null', + 'numeric', + 'of', + 'on', + 'optimize', + 'optimizer_costs', + 'option', + 'optionally', + 'or', + 'order', + 'out', + 'outer', + 'outfile', + 'over', + 'partition', + 'percent_rank', + 'persist', + 'persist_only', + 'precision', + 'primary', + 'procedure', + 'purge', + 'range', + 'rank', + 'read', + 'reads', + 'read_write', + 'real', + 'recursive', + 'references', + 'regexp', + 'release', + 'rename', + 'repeat', + 'replace', + 'require', + 'resignal', + 'restrict', + 'return', + 'revoke', + 'right', + 'rlike', + 'row', + 'rows', + 'row_number', + 'schema', + 'schemas', + 'second_microsecond', + 'select', + 'sensitive', + 'separator', + 'set', + 'show', + 'signal', + 'smallint', + 'spatial', + 'specific', + 'sql', + 'sqlexception', + 'sqlstate', + 'sqlwarning', + 'sql_big_result', + 'sql_calc_found_rows', + 'sql_small_result', + 'ssl', + 'starting', + 'stored', + 'straight_join', + 'system', + 'table', + 'terminated', + 'then', + 'tinyblob', + 'tinyint', + 'tinytext', + 'to', + 'trailing', + 'trigger', + 'true', + 'undo', + 'union', + 'unique', + 'unlock', + 'unsigned', + 'update', + 'usage', + 'use', + 'using', + 'utc_date', + 'utc_time', + 'utc_timestamp', + 'values', + 'varbinary', + 'varchar', + 'varcharacter', + 'varying', + 'virtual', + 'when', + 'where', + 'while', + 'window', + 'with', + 'write', + 'xor', + 'year_month', + 'zerofill', + ); + public function __construct(array $connection_options = array()) { // This driver defaults to transaction support, except if explicitly passed FALSE. $this->transactionSupport = !isset($connection_options['transactions']) || ($connection_options['transactions'] !== FALSE); @@ -86,15 +362,95 @@ class DatabaseConnection_mysql extends DatabaseConnection { $connection_options += array( 'init_commands' => array(), ); + + $sql_mode = 'REAL_AS_FLOAT,PIPES_AS_CONCAT,ANSI_QUOTES,IGNORE_SPACE,STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO'; + // NO_AUTO_CREATE_USER was removed in MySQL 8.0.11 + // https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-11.html#mysqld-8-0-11-deprecation-removal + if (version_compare($this->getAttribute(PDO::ATTR_SERVER_VERSION), '8.0.11', '<')) { + $sql_mode .= ',NO_AUTO_CREATE_USER'; + } $connection_options['init_commands'] += array( - 'sql_mode' => "SET sql_mode = 'REAL_AS_FLOAT,PIPES_AS_CONCAT,ANSI_QUOTES,IGNORE_SPACE,STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER'", + 'sql_mode' => "SET sql_mode = '$sql_mode'", ); + // Execute initial commands. foreach ($connection_options['init_commands'] as $sql) { $this->exec($sql); } } + /** + * {@inheritdoc}} + */ + protected function setPrefix($prefix) { + parent::setPrefix($prefix); + // Successive versions of MySQL have become increasingly strict about the + // use of reserved keywords as table names. Drupal 7 uses at least one such + // table (system). Therefore we surround all table names with quotes. + $quote_char = variable_get('mysql_identifier_quote_character', MYSQL_IDENTIFIER_QUOTE_CHARACTER_DEFAULT); + foreach ($this->prefixSearch as $i => $prefixSearch) { + if (substr($prefixSearch, 0, 1) === '{') { + // If the prefix already contains one or more quotes remove them. + // This can happen when - for example - DrupalUnitTestCase sets up a + // "temporary prefixed database". Also if there's a dot in the prefix, + // wrap it in quotes to cater for schema names in prefixes. + $search = array($quote_char, '.'); + $replace = array('', $quote_char . '.' . $quote_char); + $this->prefixReplace[$i] = $quote_char . str_replace($search, $replace, $this->prefixReplace[$i]); + } + if (substr($prefixSearch, -1) === '}') { + $this->prefixReplace[$i] .= $quote_char; + } + } + } + + /** + * {@inheritdoc} + */ + public function escapeField($field) { + $field = parent::escapeField($field); + return $this->quoteIdentifier($field); + } + + public function escapeFields(array $fields) { + foreach ($fields as &$field) { + $field = $this->escapeField($field); + } + return $fields; + } + + /** + * {@inheritdoc} + */ + public function escapeAlias($field) { + $field = parent::escapeAlias($field); + return $this->quoteIdentifier($field); + } + + /** + * Quotes an identifier if it matches a MySQL reserved keyword. + * + * @param string $identifier + * The field to check. + * + * @return string + * The identifier, quoted if it matches a MySQL reserved keyword. + */ + private function quoteIdentifier($identifier) { + // Quote identifiers so that MySQL reserved words like 'function' can be + // used as column names. Sometimes the 'table.column_name' format is passed + // in. For example, menu_load_links() adds a condition on "ml.menu_name". + if (strpos($identifier, '.') !== FALSE) { + list($table, $identifier) = explode('.', $identifier, 2); + } + if (in_array(strtolower($identifier), $this->reservedKeyWords, TRUE)) { + // Quote the string for MySQL reserved keywords. + $quote_char = variable_get('mysql_identifier_quote_character', MYSQL_IDENTIFIER_QUOTE_CHARACTER_DEFAULT); + $identifier = $quote_char . $identifier . $quote_char; + } + return isset($table) ? $table . '.' . $identifier : $identifier; + } + public function __destruct() { if ($this->needsCleanup) { $this->nextIdDelete(); diff --git a/includes/database/mysql/query.inc b/includes/database/mysql/query.inc index d3d2d9eecfe..3f0bcb7968a 100644 --- a/includes/database/mysql/query.inc +++ b/includes/database/mysql/query.inc @@ -48,6 +48,10 @@ class InsertQuery_mysql extends InsertQuery { // Default fields are always placed first for consistency. $insert_fields = array_merge($this->defaultFields, $this->insertFields); + if (method_exists($this->connection, 'escapeFields')) { + $insert_fields = $this->connection->escapeFields($insert_fields); + } + // If we're selecting from a SelectQuery, finish building the query and // pass it back, as any remaining options are irrelevant. if (!empty($this->fromQuery)) { @@ -89,6 +93,20 @@ class InsertQuery_mysql extends InsertQuery { class TruncateQuery_mysql extends TruncateQuery { } +class UpdateQuery_mysql extends UpdateQuery { + public function __toString() { + if (method_exists($this->connection, 'escapeField')) { + $escapedFields = array(); + foreach ($this->fields as $field => $data) { + $field = $this->connection->escapeField($field); + $escapedFields[$field] = $data; + } + $this->fields = $escapedFields; + } + return parent::__toString(); + } +} + /** * @} End of "addtogroup database". */ diff --git a/includes/database/mysql/schema.inc b/includes/database/mysql/schema.inc index 9ba1c733977..7d6e3339506 100644 --- a/includes/database/mysql/schema.inc +++ b/includes/database/mysql/schema.inc @@ -57,6 +57,11 @@ class DatabaseSchema_mysql extends DatabaseSchema { protected function buildTableNameCondition($table_name, $operator = '=', $add_prefix = TRUE) { $info = $this->connection->getConnectionOptions(); + // Ensure the table name is not surrounded with quotes as that is not + // appropriate for schema queries. + $quote_char = variable_get('mysql_identifier_quote_character', MYSQL_IDENTIFIER_QUOTE_CHARACTER_DEFAULT); + $table_name = str_replace($quote_char, '', $table_name); + $table_info = $this->getPrefixInfo($table_name, $add_prefix); $condition = new DatabaseCondition('AND'); @@ -494,11 +499,11 @@ class DatabaseSchema_mysql extends DatabaseSchema { $condition->condition('column_name', $column); $condition->compile($this->connection, $this); // Don't use {} around information_schema.columns table. - return $this->connection->query("SELECT column_comment FROM information_schema.columns WHERE " . (string) $condition, $condition->arguments())->fetchField(); + return $this->connection->query("SELECT column_comment AS column_comment FROM information_schema.columns WHERE " . (string) $condition, $condition->arguments())->fetchField(); } $condition->compile($this->connection, $this); // Don't use {} around information_schema.tables table. - $comment = $this->connection->query("SELECT table_comment FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments())->fetchField(); + $comment = $this->connection->query("SELECT table_comment AS table_comment FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments())->fetchField(); // Work-around for MySQL 5.0 bug http://bugs.mysql.com/bug.php?id=11379 return preg_replace('/; InnoDB free:.*$/', '', $comment); } diff --git a/includes/database/schema.inc b/includes/database/schema.inc index 31862db3940..faa52162373 100644 --- a/includes/database/schema.inc +++ b/includes/database/schema.inc @@ -169,6 +169,11 @@ require_once dirname(__FILE__) . '/query.inc'; */ abstract class DatabaseSchema implements QueryPlaceholderInterface { + /** + * The database connection. + * + * @var DatabaseConnection + */ protected $connection; /** @@ -343,7 +348,70 @@ abstract class DatabaseSchema implements QueryPlaceholderInterface { // couldn't use db_select() here because it would prefix // information_schema.tables and the query would fail. // Don't use {} around information_schema.tables table. - return $this->connection->query("SELECT table_name FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments())->fetchAllKeyed(0, 0); + return $this->connection->query("SELECT table_name AS table_name FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments())->fetchAllKeyed(0, 0); + } + + /** + * Finds all tables that are like the specified base table name. This is a + * backport of the change made to findTables in Drupal 8 to work with virtual, + * un-prefixed table names. The original function is retained for Backwards + * Compatibility. + * @see https://www.drupal.org/node/2552435 + * + * @param string $table_expression + * An SQL expression, for example "cache_%" (without the quotes). + * + * @return array + * Both the keys and the values are the matching tables. + */ + public function findTablesD8($table_expression) { + // Load all the tables up front in order to take into account per-table + // prefixes. The actual matching is done at the bottom of the method. + $condition = $this->buildTableNameCondition('%', 'LIKE'); + $condition->compile($this->connection, $this); + + $individually_prefixed_tables = $this->connection->getUnprefixedTablesMap(); + $default_prefix = $this->connection->tablePrefix(); + $default_prefix_length = strlen($default_prefix); + $tables = array(); + // Normally, we would heartily discourage the use of string + // concatenation for conditionals like this however, we + // couldn't use db_select() here because it would prefix + // information_schema.tables and the query would fail. + // Don't use {} around information_schema.tables table. + $results = $this->connection->query("SELECT table_name AS table_name FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments()); + foreach ($results as $table) { + // Take into account tables that have an individual prefix. + if (isset($individually_prefixed_tables[$table->table_name])) { + $prefix_length = strlen($this->connection->tablePrefix($individually_prefixed_tables[$table->table_name])); + } + elseif ($default_prefix && substr($table->table_name, 0, $default_prefix_length) !== $default_prefix) { + // This table name does not start the default prefix, which means that + // it is not managed by Drupal so it should be excluded from the result. + continue; + } + else { + $prefix_length = $default_prefix_length; + } + + // Remove the prefix from the returned tables. + $unprefixed_table_name = substr($table->table_name, $prefix_length); + + // The pattern can match a table which is the same as the prefix. That + // will become an empty string when we remove the prefix, which will + // probably surprise the caller, besides not being a prefixed table. So + // remove it. + if (!empty($unprefixed_table_name)) { + $tables[$unprefixed_table_name] = $unprefixed_table_name; + } + } + + // Convert the table expression from its SQL LIKE syntax to a regular + // expression and escape the delimiter that will be used for matching. + $table_expression = str_replace(array('%', '_'), array('.*?', '.'), preg_quote($table_expression, '/')); + $tables = preg_grep('/^' . $table_expression . '$/i', $tables); + + return $tables; } /** diff --git a/includes/database/select.inc b/includes/database/select.inc index 8d84460e839..84098bdf7bf 100644 --- a/includes/database/select.inc +++ b/includes/database/select.inc @@ -1520,13 +1520,16 @@ class SelectQuery extends Query implements SelectQueryInterface { $fields = array(); foreach ($this->tables as $alias => $table) { if (!empty($table['all_fields'])) { - $fields[] = $this->connection->escapeTable($alias) . '.*'; + $fields[] = $this->connection->escapeAlias($alias) . '.*'; } } foreach ($this->fields as $alias => $field) { + // Note that $field['table'] holds the table alias. + // @see \SelectQuery::addField + $table = isset($field['table']) ? $this->connection->escapeAlias($field['table']) . '.' : ''; // Always use the AS keyword for field aliases, as some // databases require it (e.g., PostgreSQL). - $fields[] = (isset($field['table']) ? $this->connection->escapeTable($field['table']) . '.' : '') . $this->connection->escapeField($field['field']) . ' AS ' . $this->connection->escapeAlias($field['alias']); + $fields[] = $table . $this->connection->escapeField($field['field']) . ' AS ' . $this->connection->escapeAlias($field['alias']); } foreach ($this->expressions as $alias => $expression) { $fields[] = $expression['expression'] . ' AS ' . $this->connection->escapeAlias($expression['alias']); @@ -1555,7 +1558,7 @@ class SelectQuery extends Query implements SelectQueryInterface { // Don't use the AS keyword for table aliases, as some // databases don't support it (e.g., Oracle). - $query .= $table_string . ' ' . $this->connection->escapeTable($table['alias']); + $query .= $table_string . ' ' . $this->connection->escapeAlias($table['alias']); if (!empty($table['condition'])) { $query .= ' ON ' . $table['condition']; diff --git a/includes/database/sqlite/database.inc b/includes/database/sqlite/database.inc index 589a1728737..c50f08ec537 100644 --- a/includes/database/sqlite/database.inc +++ b/includes/database/sqlite/database.inc @@ -107,6 +107,18 @@ class DatabaseConnection_sqlite extends DatabaseConnection { $this->sqliteCreateFunction('substring_index', array($this, 'sqlFunctionSubstringIndex'), 3); $this->sqliteCreateFunction('rand', array($this, 'sqlFunctionRand')); + // Enable the Write-Ahead Logging (WAL) option for SQLite if supported. + // @see https://www.drupal.org/node/2348137 + // @see https://sqlite.org/wal.html + if (version_compare($version, '3.7') >= 0) { + $connection_options += array( + 'init_commands' => array(), + ); + $connection_options['init_commands'] += array( + 'wal' => "PRAGMA journal_mode=WAL", + ); + } + // Execute sqlite init_commands. if (isset($connection_options['init_commands'])) { $this->exec(implode('; ', $connection_options['init_commands'])); @@ -128,10 +140,10 @@ class DatabaseConnection_sqlite extends DatabaseConnection { $count = $this->query('SELECT COUNT(*) FROM ' . $prefix . '.sqlite_master WHERE type = :type AND name NOT LIKE :pattern', array(':type' => 'table', ':pattern' => 'sqlite_%'))->fetchField(); // We can prune the database file if it doesn't have any tables. - if ($count == 0) { - // Detach the database. - $this->query('DETACH DATABASE :schema', array(':schema' => $prefix)); - // Destroy the database file. + if ($count == 0 && $this->connectionOptions['database'] != ':memory:') { + // Detaching the database fails at this point, but no other queries + // are executed after the connection is destructed so we can simply + // remove the database file. unlink($this->connectionOptions['database'] . '-' . $prefix); } } @@ -143,6 +155,18 @@ class DatabaseConnection_sqlite extends DatabaseConnection { } } + /** + * Gets all the attached databases. + * + * @return array + * An array of attached database names. + * + * @see DatabaseConnection_sqlite::__construct(). + */ + public function getAttachedDatabases() { + return $this->attachedDatabases; + } + /** * SQLite compatibility implementation for the IF() SQL function. */ diff --git a/includes/database/sqlite/query.inc b/includes/database/sqlite/query.inc index c9c028bb079..45c6a30247d 100644 --- a/includes/database/sqlite/query.inc +++ b/includes/database/sqlite/query.inc @@ -23,7 +23,7 @@ class InsertQuery_sqlite extends InsertQuery { if (!$this->preExecute()) { return NULL; } - if (count($this->insertFields)) { + if (count($this->insertFields) || !empty($this->fromQuery)) { return parent::execute(); } else { @@ -36,7 +36,10 @@ class InsertQuery_sqlite extends InsertQuery { $comments = $this->connection->makeComment($this->comments); // Produce as many generic placeholders as necessary. - $placeholders = array_fill(0, count($this->insertFields), '?'); + $placeholders = array(); + if (!empty($this->insertFields)) { + $placeholders = array_fill(0, count($this->insertFields), '?'); + } // If we're selecting from a SelectQuery, finish building the query and // pass it back, as any remaining options are irrelevant. diff --git a/includes/database/sqlite/schema.inc b/includes/database/sqlite/schema.inc index 281d8fc6bf2..43ea6d61c5f 100644 --- a/includes/database/sqlite/schema.inc +++ b/includes/database/sqlite/schema.inc @@ -668,6 +668,9 @@ class DatabaseSchema_sqlite extends DatabaseSchema { $this->alterTable($table, $old_schema, $new_schema); } + /** + * {@inheritdoc} + */ public function findTables($table_expression) { // Don't add the prefix, $table_expression already includes the prefix. $info = $this->getPrefixInfo($table_expression, FALSE); @@ -680,4 +683,32 @@ class DatabaseSchema_sqlite extends DatabaseSchema { )); return $result->fetchAllKeyed(0, 0); } + + /** + * {@inheritdoc} + */ + public function findTablesD8($table_expression) { + $tables = array(); + + // The SQLite implementation doesn't need to use the same filtering strategy + // as the parent one because individually prefixed tables live in their own + // schema (database), which means that neither the main database nor any + // attached one will contain a prefixed table name, so we just need to loop + // over all known schemas and filter by the user-supplied table expression. + $attached_dbs = $this->connection->getAttachedDatabases(); + foreach ($attached_dbs as $schema) { + // Can't use query placeholders for the schema because the query would + // have to be :prefixsqlite_master, which does not work. We also need to + // ignore the internal SQLite tables. + $result = db_query("SELECT name FROM " . $schema . ".sqlite_master WHERE type = :type AND name LIKE :table_name AND name NOT LIKE :pattern", array( + ':type' => 'table', + ':table_name' => $table_expression, + ':pattern' => 'sqlite_%', + )); + $tables += $result->fetchAllKeyed(0, 0); + } + + return $tables; + } + } diff --git a/includes/file.inc b/includes/file.inc index 6c10b6e450d..2ea503df4fe 100644 --- a/includes/file.inc +++ b/includes/file.inc @@ -1151,8 +1151,8 @@ function file_unmanaged_move($source, $destination = NULL, $replace = FILE_EXIST * exploit.php_.pps. * * Specifically, this function adds an underscore to all extensions that are - * between 2 and 5 characters in length, internal to the file name, and not - * included in $extensions. + * between 2 and 5 characters in length, internal to the file name, and either + * included in the list of unsafe extensions, or not included in $extensions. * * Function behavior is also controlled by the Drupal variable * 'allow_insecure_uploads'. If 'allow_insecure_uploads' evaluates to TRUE, no @@ -1161,7 +1161,8 @@ function file_unmanaged_move($source, $destination = NULL, $replace = FILE_EXIST * @param $filename * File name to modify. * @param $extensions - * A space-separated list of extensions that should not be altered. + * A space-separated list of extensions that should not be altered. Note that + * extensions that are unsafe will be altered regardless of this parameter. * @param $alerts * If TRUE, drupal_set_message() will be called to display a message if the * file name was changed. @@ -1179,6 +1180,10 @@ function file_munge_filename($filename, $extensions, $alerts = TRUE) { $whitelist = array_unique(explode(' ', strtolower(trim($extensions)))); + // Remove unsafe extensions from the list of allowed extensions. The list is + // copied from file_save_upload(). + $whitelist = array_diff($whitelist, explode('|', 'php|phar|pl|py|cgi|asp|js')); + // Split the filename up by periods. The first part becomes the basename // the last part the final extension. $filename_parts = explode('.', $filename); @@ -1546,25 +1551,35 @@ function file_save_upload($form_field_name, $validators = array(), $destination $validators['file_validate_extensions'][0] = $extensions; } - if (!empty($extensions)) { - // Munge the filename to protect against possible malicious extension hiding - // within an unknown file type (ie: filename.html.foo). - $file->filename = file_munge_filename($file->filename, $extensions); - } - - // Rename potentially executable files, to help prevent exploits (i.e. will - // rename filename.php.foo and filename.php to filename.php.foo.txt and - // filename.php.txt, respectively). Don't rename if 'allow_insecure_uploads' - // evaluates to TRUE. - if (!variable_get('allow_insecure_uploads', 0) && preg_match('/\.(php|phar|pl|py|cgi|asp|js)(\.|$)/i', $file->filename) && (substr($file->filename, -4) != '.txt')) { - $file->filemime = 'text/plain'; - // The destination filename will also later be used to create the URI. - $file->filename .= '.txt'; - // The .txt extension may not be in the allowed list of extensions. We have - // to add it here or else the file upload will fail. + if (!variable_get('allow_insecure_uploads', 0)) { if (!empty($extensions)) { - $validators['file_validate_extensions'][0] .= ' txt'; - drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', array('%filename' => $file->filename))); + // Munge the filename to protect against possible malicious extension hiding + // within an unknown file type (ie: filename.html.foo). + $file->filename = file_munge_filename($file->filename, $extensions); + } + + // Rename potentially executable files, to help prevent exploits (i.e. will + // rename filename.php.foo and filename.php to filename.php_.foo_.txt and + // filename.php_.txt, respectively). Don't rename if 'allow_insecure_uploads' + // evaluates to TRUE. + if (preg_match('/\.(php|phar|pl|py|cgi|asp|js)(\.|$)/i', $file->filename)) { + // If the file will be rejected anyway due to a disallowed extension, it + // should not be renamed; rather, we'll let file_validate_extensions() + // reject it below. + if (!isset($validators['file_validate_extensions']) || !file_validate_extensions($file, $extensions)) { + $file->filemime = 'text/plain'; + if (substr($file->filename, -4) != '.txt') { + // The destination filename will also later be used to create the URI. + $file->filename .= '.txt'; + } + $file->filename = file_munge_filename($file->filename, $extensions, FALSE); + drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', array('%filename' => $file->filename))); + // The .txt extension may not be in the allowed list of extensions. We have + // to add it here or else the file upload will fail. + if (!empty($validators['file_validate_extensions'][0])) { + $validators['file_validate_extensions'][0] .= ' txt'; + } + } } } @@ -1732,7 +1747,18 @@ function file_validate(stdClass &$file, $validators = array()) { } // Let other modules perform validation on the new file. - return array_merge($errors, module_invoke_all('file_validate', $file)); + $errors = array_merge($errors, module_invoke_all('file_validate', $file)); + + // Ensure the file does not contain a malicious extension. At this point + // file_save_upload() will have munged the file so it does not contain a + // malicious extension. Contributed and custom code that calls this method + // needs to take similar steps if they need to permit files with malicious + // extensions to be uploaded. + if (empty($errors) && !variable_get('allow_insecure_uploads', 0) && preg_match('/\.(php|phar|pl|py|cgi|asp|js)(\.|$)/i', $file->filename)) { + $errors[] = t('For security reasons, your upload has been rejected.'); + } + + return $errors; } /** diff --git a/includes/form.inc b/includes/form.inc index 1158fd031fc..f2557e83233 100644 --- a/includes/form.inc +++ b/includes/form.inc @@ -1361,7 +1361,10 @@ function _form_validate(&$elements, &$form_state, $form_id = NULL) { // The following errors are always shown. if (isset($elements['#needs_validation'])) { // Verify that the value is not longer than #maxlength. - if (isset($elements['#maxlength']) && drupal_strlen($elements['#value']) > $elements['#maxlength']) { + if (isset($elements['#maxlength']) && (isset($elements['#value']) && !is_scalar($elements['#value']))) { + form_error($elements, $t('An illegal value has been detected. Please contact the site administrator.')); + } + elseif (isset($elements['#maxlength']) && drupal_strlen($elements['#value']) > $elements['#maxlength']) { form_error($elements, $t('!name cannot be longer than %max characters but is currently %length characters long.', array('!name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title'], '%max' => $elements['#maxlength'], '%length' => drupal_strlen($elements['#value'])))); } @@ -4124,9 +4127,17 @@ function form_process_weight($element) { $max_elements = variable_get('drupal_weight_select_max', DRUPAL_WEIGHT_SELECT_MAX); if ($element['#delta'] <= $max_elements) { $element['#type'] = 'select'; + $weights = array(); for ($n = (-1 * $element['#delta']); $n <= $element['#delta']; $n++) { $weights[$n] = $n; } + if (isset($element['#default_value'])) { + $default_value = (int) $element['#default_value']; + if (!isset($weights[$default_value])) { + $weights[$default_value] = $default_value; + ksort($weights); + } + } $element['#options'] = $weights; $element += element_info('select'); } diff --git a/includes/mail.inc b/includes/mail.inc index 0e5c17804c3..a97c788f09e 100644 --- a/includes/mail.inc +++ b/includes/mail.inc @@ -12,6 +12,12 @@ */ define('MAIL_LINE_ENDINGS', isset($_SERVER['WINDIR']) || (isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Win32') !== FALSE) ? "\r\n" : "\n"); + +/** + * Special characters, defined in RFC_2822. + */ +define('MAIL_RFC_2822_SPECIALS', '()<>[]:;@\,."'); + /** * Composes and optionally sends an e-mail message. * @@ -148,8 +154,13 @@ function drupal_mail($module, $key, $to, $language, $params = array(), $from = N // Return-Path headers should have a domain authorized to use the originating // SMTP server. $headers['From'] = $headers['Sender'] = $headers['Return-Path'] = $default_from; + + if (variable_get('mail_display_name_site_name', FALSE)) { + $display_name = variable_get('site_name', 'Drupal'); + $headers['From'] = drupal_mail_format_display_name($display_name) . ' <' . $default_from . '>'; + } } - if ($from) { + if ($from && $from != $default_from) { $headers['From'] = $from; } $message['headers'] = $headers; @@ -557,10 +568,59 @@ function drupal_html_to_text($string, $allowed_tags = NULL) { return $output . $footnotes; } +/** + * Return a RFC-2822 compliant "display-name" component. + * + * The "display-name" component is used in mail header "Originator" fields + * (From, Sender, Reply-to) to give a human-friendly description of the + * address, i.e. From: My Display Name . RFC-822 and + * RFC-2822 define its syntax and rules. This method gets as input a string + * to be used as "display-name" and formats it to be RFC compliant. + * + * @param string $string + * A string to be used as "display-name". + * + * @return string + * A RFC compliant version of the string, ready to be used as + * "display-name" in mail originator header fields. + */ +function drupal_mail_format_display_name($string) { + // Make sure we don't process html-encoded characters. They may create + // unneeded trouble if left encoded, besides they will be correctly + // processed if decoded. + $string = decode_entities($string); + + // If string contains non-ASCII characters it must be (short) encoded + // according to RFC-2047. The output of a "B" (Base64) encoded-word is + // always safe to be used as display-name. + $safe_display_name = mime_header_encode($string, TRUE); + + // Encoded-words are always safe to be used as display-name because don't + // contain any RFC 2822 "specials" characters. However + // mimeHeaderEncode() encodes a string only if it contains any + // non-ASCII characters, and leaves its value untouched (un-encoded) if + // ASCII only. For this reason in order to produce a valid display-name we + // still need to make sure there are no "specials" characters left. + if (preg_match('/[' . preg_quote(MAIL_RFC_2822_SPECIALS) . ']/', $safe_display_name)) { + + // If string is already quoted, it may or may not be escaped properly, so + // don't trust it and reset. + if (preg_match('/^"(.+)"$/', $safe_display_name, $matches)) { + $safe_display_name = str_replace(array('\\\\', '\\"'), array('\\', '"'), $matches[1]); + } + + // Transform the string in a RFC-2822 "quoted-string" by wrapping it in + // double-quotes. Also make sure '"' and '\' occurrences are escaped. + $safe_display_name = '"' . str_replace(array('\\', '"'), array('\\\\', '\\"'), $safe_display_name) . '"'; + } + + return $safe_display_name; +} + /** * Wraps words on a single line. * - * Callback for array_walk() winthin drupal_wrap_mail(). + * Callback for array_walk() within drupal_wrap_mail(). */ function _drupal_wrap_mail_line(&$line, $key, $values) { // Use soft-breaks only for purely quoted or unindented text. diff --git a/includes/menu.inc b/includes/menu.inc index 2b489d88645..22e6dba97c8 100644 --- a/includes/menu.inc +++ b/includes/menu.inc @@ -1067,7 +1067,7 @@ function menu_tree_output($tree) { // the active class accordingly. But local tasks do not appear in menu // trees, so if the current path is a local task, and this link is its // tab root, then we have to set the class manually. - if ($data['link']['href'] == $router_item['tab_root_href'] && $data['link']['href'] != $_GET['q']) { + if ($router_item && $data['link']['href'] == $router_item['tab_root_href'] && $data['link']['href'] != $_GET['q']) { $data['link']['localized_options']['attributes']['class'][] = 'active'; } diff --git a/misc/ajax.js b/misc/ajax.js index 0c9579b00d2..79a4e9eb6f5 100644 --- a/misc/ajax.js +++ b/misc/ajax.js @@ -149,7 +149,7 @@ Drupal.ajax = function (base, element, element_settings) { // The 'this' variable will not persist inside of the options object. var ajax = this; ajax.options = { - url: ajax.url, + url: Drupal.sanitizeAjaxUrl(ajax.url), data: ajax.submit, beforeSerialize: function (element_settings, options) { return ajax.beforeSerialize(element_settings, options); @@ -195,6 +195,7 @@ Drupal.ajax = function (base, element, element_settings) { } }, dataType: 'json', + jsonp: false, type: 'POST' }; diff --git a/misc/autocomplete.js b/misc/autocomplete.js index af090713c73..09ceeec0f14 100644 --- a/misc/autocomplete.js +++ b/misc/autocomplete.js @@ -297,8 +297,9 @@ Drupal.ACDB.prototype.search = function (searchString) { // encodeURIComponent to allow autocomplete search terms to contain slashes. $.ajax({ type: 'GET', - url: db.uri + '/' + Drupal.encodePath(searchString), + url: Drupal.sanitizeAjaxUrl(db.uri + '/' + Drupal.encodePath(searchString)), dataType: 'json', + jsonp: false, success: function (matches) { if (typeof matches.status == 'undefined' || matches.status != 0) { db.cache[searchString] = matches; diff --git a/misc/drupal.js b/misc/drupal.js index 19fbc712fdb..7a3f5f5926f 100644 --- a/misc/drupal.js +++ b/misc/drupal.js @@ -424,6 +424,23 @@ Drupal.urlIsLocal = function (url) { return absoluteUrl === baseUrl || absoluteUrl.indexOf(baseUrl + '/') === 0; }; +/** + * Sanitizes a URL for use with jQuery.ajax(). + * + * @param url + * The URL string to be sanitized. + * + * @return + * The sanitized URL. + */ +Drupal.sanitizeAjaxUrl = function (url) { + var regex = /\=\?(&|$)/; + while (url.match(regex)) { + url = url.replace(regex, ''); + } + return url; +} + /** * Generate the themed representation of a Drupal object. * diff --git a/misc/jquery.js b/misc/jquery.js index e900c19a38f..8f3ca2e2daf 100644 --- a/misc/jquery.js +++ b/misc/jquery.js @@ -1,4 +1,3 @@ - /*! * jQuery JavaScript Library v1.4.4 * http://jquery.com/ diff --git a/modules/field/modules/field_sql_storage/field_sql_storage.test b/modules/field/modules/field_sql_storage/field_sql_storage.test index 7c88ac77608..b2eb5065234 100644 --- a/modules/field/modules/field_sql_storage/field_sql_storage.test +++ b/modules/field/modules/field_sql_storage/field_sql_storage.test @@ -313,9 +313,13 @@ class FieldSqlStorageTestCase extends DrupalWebTestCase { $field = array('field_name' => 'test_text', 'type' => 'text', 'settings' => array('max_length' => 255)); $field = field_create_field($field); - // Attempt to update the field in a way that would break the storage. + // Attempt to update the field in a way that would break the storage. The + // parenthesis suffix is needed because SQLite has *very* relaxed rules for + // data types, so we actually need to provide an invalid SQL syntax in order + // to break it. + // @see https://www.sqlite.org/datatype3.html $prior_field = $field; - $field['settings']['max_length'] = -1; + $field['settings']['max_length'] = '-1)'; try { field_update_field($field); $this->fail(t('Update succeeded.')); diff --git a/modules/file/file.module b/modules/file/file.module index eea58470fab..1f1d594475a 100644 --- a/modules/file/file.module +++ b/modules/file/file.module @@ -281,10 +281,11 @@ function file_ajax_upload() { } // Otherwise just add the new content class on a placeholder. else { - $form['#suffix'] .= ''; + $form['#suffix'] = (isset($form['#suffix']) ? $form['#suffix'] : '') . ''; } - $form['#prefix'] .= theme('status_messages'); + $form['#prefix'] = (isset($form['#prefix']) ? $form['#prefix'] : '') . theme('status_messages'); + $output = drupal_render($form); $js = drupal_add_js(); $settings = drupal_array_merge_deep_array($js['settings']['data']); diff --git a/modules/locale/locale.module b/modules/locale/locale.module index 768fead6767..93a4657f0c9 100644 --- a/modules/locale/locale.module +++ b/modules/locale/locale.module @@ -564,6 +564,7 @@ function locale_language_types_info() { * Implements hook_language_negotiation_info(). */ function locale_language_negotiation_info() { + require_once DRUPAL_ROOT . '/includes/locale.inc'; $file = 'includes/locale.inc'; $providers = array(); diff --git a/modules/simpletest/drupal_web_test_case.php b/modules/simpletest/drupal_web_test_case.php index a0872c234a8..c426ba53ab2 100644 --- a/modules/simpletest/drupal_web_test_case.php +++ b/modules/simpletest/drupal_web_test_case.php @@ -1677,15 +1677,12 @@ protected function tearDown() { file_unmanaged_delete_recursive($this->originalFileDirectory . '/simpletest/' . substr($this->databasePrefix, 10)); // Remove all prefixed tables. - $tables = db_find_tables($this->databasePrefix . '%'); - $connection_info = Database::getConnectionInfo('default'); - $tables = db_find_tables($connection_info['default']['prefix']['default'] . '%'); + $tables = db_find_tables_d8('%'); if (empty($tables)) { $this->fail('Failed to find test tables to drop.'); } - $prefix_length = strlen($connection_info['default']['prefix']['default']); foreach ($tables as $table) { - if (db_drop_table(substr($table, $prefix_length))) { + if (db_drop_table($table)) { unset($tables[$table]); } } diff --git a/modules/simpletest/simpletest.module b/modules/simpletest/simpletest.module index cf8304781cd..6a60d594926 100644 --- a/modules/simpletest/simpletest.module +++ b/modules/simpletest/simpletest.module @@ -563,7 +563,7 @@ function simpletest_clean_environment() { * Removed prefixed tables from the database that are left over from crashed tests. */ function simpletest_clean_database() { - $tables = db_find_tables(Database::getConnection()->prefixTables('{simpletest}') . '%'); + $tables = db_find_tables_d8(Database::getConnection()->prefixTables('{simpletest}') . '%'); $schema = drupal_get_schema_unprocessed('simpletest'); $count = 0; foreach (array_diff_key($tables, $schema) as $table) { diff --git a/modules/simpletest/tests/common.test b/modules/simpletest/tests/common.test index e045934ea8f..9b83a5bca4e 100644 --- a/modules/simpletest/tests/common.test +++ b/modules/simpletest/tests/common.test @@ -2120,6 +2120,32 @@ class DrupalRenderTestCase extends DrupalWebTestCase { // The elements should appear in output in the same order as the array. $this->assertTrue(strpos($output, $second) < strpos($output, $first), 'Elements were not sorted.'); + + // The order of children with same weight should be preserved. + $element_mixed_weight = array( + 'child5' => array('#weight' => 10), + 'child3' => array('#weight' => -10), + 'child1' => array(), + 'child4' => array('#weight' => 10), + 'child2' => array(), + 'child6' => array('#weight' => 10), + 'child9' => array(), + 'child8' => array('#weight' => 10), + 'child7' => array(), + ); + + $expected = array( + 'child3', + 'child1', + 'child2', + 'child9', + 'child7', + 'child5', + 'child4', + 'child6', + 'child8', + ); + $this->assertEqual($expected, element_children($element_mixed_weight, TRUE), 'Order of elements with the same weight is preserved.'); } /** diff --git a/modules/simpletest/tests/database_test.install b/modules/simpletest/tests/database_test.install index 11361151f91..44ed5ee0a4d 100644 --- a/modules/simpletest/tests/database_test.install +++ b/modules/simpletest/tests/database_test.install @@ -217,5 +217,24 @@ function database_test_schema() { ), ); + $schema['virtual'] = array( + 'description' => 'Basic test table with a reserved name.', + 'fields' => array( + 'id' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'function' => array( + 'description' => "A column with a reserved name.", + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + 'default' => '', + ), + ), + 'primary key' => array('id'), + ); + return $schema; } diff --git a/modules/simpletest/tests/database_test.test b/modules/simpletest/tests/database_test.test index 59d2e5d620b..04be5c85b84 100644 --- a/modules/simpletest/tests/database_test.test +++ b/modules/simpletest/tests/database_test.test @@ -163,6 +163,12 @@ class DatabaseTestCase extends DrupalWebTestCase { 'priority' => 3, )) ->execute(); + + db_insert('virtual') + ->fields(array( + 'function' => 'Function value 1', + )) + ->execute(); } } @@ -3457,7 +3463,6 @@ class DatabaseQueryTestCase extends DatabaseTestCase { ->fetchField(); $this->assertFalse($result, 'SQL injection attempt did not result in a row being inserted in the database table.'); } - } /** @@ -4033,6 +4038,8 @@ class ConnectionUnitTest extends DrupalUnitTestCase { protected $monitor; protected $originalCount; + protected $skipTest; + public static function getInfo() { return array( 'name' => 'Connection unit tests', @@ -4053,7 +4060,7 @@ class ConnectionUnitTest extends DrupalUnitTestCase { // @todo Make this test driver-agnostic, or find a proper way to skip it. // @see http://drupal.org/node/1273478 $connection_info = Database::getConnectionInfo('default'); - $this->skipTest = (bool) $connection_info['default']['driver'] != 'mysql'; + $this->skipTest = (bool) ($connection_info['default']['driver'] != 'mysql'); if ($this->skipTest) { // Insert an assertion to prevent Simpletest from interpreting the test // as failure. @@ -4238,5 +4245,178 @@ class ConnectionUnitTest extends DrupalUnitTestCase { // Verify that we are back to the original connection count. $this->assertNoConnection($id); } +} + +/** + * Test reserved keyword handling (introduced for MySQL 8+) +*/ +class DatabaseReservedKeywordTestCase extends DatabaseTestCase { + public static function getInfo() { + return array( + 'name' => 'Reserved Keywords', + 'description' => 'Test handling of reserved keywords.', + 'group' => 'Database', + ); + } + + function setUp() { + parent::setUp('database_test'); + } + + public function testTableNameQuoting() { + // Test db_query with {table} pattern. + $record = db_query('SELECT * FROM {system} LIMIT 1')->fetchObject(); + $this->assertTrue(isset($record->filename), 'Successfully queried the {system} table.'); + + $connection = Database::getConnection()->getConnectionOptions(); + if ($connection['driver'] === 'sqlite') { + // In SQLite simpletest's prefixed db tables exist in their own schema + // (e.g. simpletest124904.system), so we cannot test the schema.{table} + // syntax here as the table name will have the schema name prepended to it + // when prefixes are processed. + $this->assert(TRUE, 'Skipping schema.{system} test for SQLite.'); + } + else { + $database = $connection['database']; + // Test db_query with schema.{table} pattern + db_query('SELECT * FROM ' . $database . '.{system} LIMIT 1')->fetchObject(); + $this->assertTrue(isset($record->filename), 'Successfully queried the schema.{system} table.'); + } + } + + public function testSelectReservedWordTableCount() { + $rows = db_select('virtual') + ->countQuery() + ->execute() + ->fetchField(); + $this->assertEqual($rows, 1, 'Successful count query on a table with a reserved name.'); + } + + public function testSelectReservedWordTableSpecificField() { + $record = db_select('virtual') + ->fields('virtual', array('function')) + ->execute() + ->fetchAssoc(); + $this->assertEqual($record['function'], 'Function value 1', 'Successfully read a field from a table with a name and column which are reserved words.'); + } + + public function testSelectReservedWordTableAllFields() { + $record = db_select('virtual') + ->fields('virtual') + ->execute() + ->fetchAssoc(); + $this->assertEqual($record['function'], 'Function value 1', 'Successful all_fields query from a table with a name and column which are reserved words.'); + } + + public function testSelectReservedWordAliasCount() { + $rows = db_select('test', 'character') + ->countQuery() + ->execute() + ->fetchField(); + $this->assertEqual($rows, 4, 'Successful count query using an alias which is a reserved word.'); + } + public function testSelectReservedWordAliasSpecificFields() { + $record = db_select('test', 'high_priority') + ->fields('high_priority', array('name')) + ->condition('age', 27) + ->execute()->fetchAssoc(); + $this->assertEqual($record['name'], 'George', 'Successful query using an alias which is a reserved word.'); + } + + public function testSelectReservedWordAliasAllFields() { + $record = db_select('test', 'high_priority') + ->fields('high_priority') + ->condition('age', 27) + ->execute()->fetchAssoc(); + $this->assertEqual($record['name'], 'George', 'Successful all_fields query using an alias which is a reserved word.'); + } + + public function testInsertReservedWordTable() { + $num_records_before = db_query('SELECT COUNT(*) FROM {virtual}')->fetchField(); + db_insert('virtual') + ->fields(array( + 'function' => 'Inserted function', + )) + ->execute(); + $num_records_after = db_query('SELECT COUNT(*) FROM {virtual}')->fetchField(); + $this->assertIdentical($num_records_before + 1, (int) $num_records_after, 'Successful insert into a table with a name and column which are reserved words.'); + } + + public function testDeleteReservedWordTable() { + $delete = db_delete('virtual') + ->condition('function', 'Function value 1'); + $num_deleted = $delete->execute(); + $this->assertEqual($num_deleted, 1, "Deleted 1 record from a table with a name and column which are reserved words.."); + } + + function testTruncateReservedWordTable() { + db_truncate('virtual')->execute(); + $num_records_after = db_query("SELECT COUNT(*) FROM {virtual}")->fetchField(); + $this->assertEqual(0, $num_records_after, 'Truncated a table with a reserved name.'); + } + + function testUpdateReservedWordTable() { + $num_updated = db_update('virtual') + ->fields(array('function' => 'Updated function')) + ->execute(); + $this->assertIdentical($num_updated, 1, 'Updated 1 record in a table with a name and column which are reserved words.'); + } + + function testMergeReservedWordTable() { + $key = db_query('SELECT id FROM {virtual} LIMIT 1')->fetchField(); + $num_records_before = db_query('SELECT COUNT(*) FROM {virtual}')->fetchField(); + db_merge('virtual') + ->key(array('id' => $key)) + ->fields(array('function' => 'Merged function')) + ->execute(); + $num_records_after = db_query('SELECT COUNT(*) FROM {virtual}')->fetchField(); + $this->assertIdentical($num_records_before, $num_records_after, 'Successful merge query on a table with a name and column which are reserved words.'); + } +} + +/** + * Test table prefix handling. +*/ +class DatabaseTablePrefixTestCase extends DatabaseTestCase { + public static function getInfo() { + return array( + 'name' => 'Table prefixes', + 'description' => 'Test handling of table prefixes.', + 'group' => 'Database', + ); + } + + public function testSchemaDotTablePrefixes() { + // Get a copy of the default connection options. + $db = Database::getConnection('default', 'default'); + $connection_options = $db->getConnectionOptions(); + + if ($connection_options['driver'] === 'sqlite') { + // In SQLite simpletest's prefixed db tables exist in their own schema + // (e.g. simpletest124904.system), so we cannot test the schema.table + // prefix syntax here. + $this->assert(TRUE, 'Skipping schema.table prefixed tables test for SQLite.'); + return; + } + + $db_name = $connection_options['database']; + // This prefix is usually something like simpletest12345 + $test_prefix = $connection_options['prefix']['default']; + + // Set up a new connection with table prefixes in the form "schema.table" + $prefixed = $connection_options; + $prefixed['prefix'] = array( + 'default' => $test_prefix, + 'users' => $db_name . '.' . $test_prefix, + 'role' => $db_name . '.' . $test_prefix, + ); + Database::addConnectionInfo('default', 'prefixed', $prefixed); + + // Test that the prefixed database connection can query the prefixed tables. + $num_users_prefixed = Database::getConnection('prefixed', 'default')->query('SELECT COUNT(1) FROM {users}')->fetchField(); + $this->assertTrue((int) $num_users_prefixed > 0, 'Successfully queried the users table using a schema.table prefix'); + $num_users_default = Database::getConnection('default', 'default')->query('SELECT COUNT(1) FROM {users}')->fetchField(); + $this->assertEqual($num_users_default, $num_users_prefixed, 'Verified results of query using a connection with schema.table prefixed tables'); + } } diff --git a/modules/simpletest/tests/file.test b/modules/simpletest/tests/file.test index 429a55180f6..57b315fb0ee 100644 --- a/modules/simpletest/tests/file.test +++ b/modules/simpletest/tests/file.test @@ -706,7 +706,7 @@ class FileSaveUploadTest extends FileHookTestCase { $edit = array( 'file_test_replace' => FILE_EXISTS_REPLACE, 'files[file_test_upload]' => drupal_realpath($this->image->uri), - 'allow_all_extensions' => TRUE, + 'allow_all_extensions' => 'empty_array', ); $this->drupalPost('file-test/upload', $edit, t('Submit')); $this->assertResponse(200, 'Received a 200 response for posted test file.'); @@ -715,14 +715,35 @@ class FileSaveUploadTest extends FileHookTestCase { // Check that the correct hooks were called. $this->assertFileHooksCalled(array('validate', 'load', 'update')); + + // Reset the hook counters. + file_test_reset(); + + // Now tell file_save_upload() to allow any extension and try and upload a + // malicious file. + $edit = array( + 'file_test_replace' => FILE_EXISTS_REPLACE, + 'files[file_test_upload]' => drupal_realpath($this->phpfile->uri), + 'is_image_file' => FALSE, + 'allow_all_extensions' => 'empty_array', + ); + $this->drupalPost('file-test/upload', $edit, t('Submit')); + $this->assertResponse(200, 'Received a 200 response for posted test file.'); + $message = t('For security reasons, your upload has been renamed to') . ' ' . $this->phpfile->filename . '_.txt' . ''; + $this->assertRaw($message, 'Dangerous file was renamed.'); + $this->assertText('File name is php-2.php_.txt.'); + $this->assertRaw(t('File MIME type is text/plain.'), "Dangerous file's MIME type was changed."); + $this->assertRaw(t('You WIN!'), 'Found the success message.'); + // Check that the correct hooks were called. + $this->assertFileHooksCalled(array('validate', 'insert')); } /** * Test dangerous file handling. */ function testHandleDangerousFile() { - // Allow the .php extension and make sure it gets renamed to .txt for - // safety. Also check to make sure its MIME type was changed. + // Allow the .php extension and make sure it gets munged and given a .txt + // extension for safety. Also check to make sure its MIME type was changed. $edit = array( 'file_test_replace' => FILE_EXISTS_REPLACE, 'files[file_test_upload]' => drupal_realpath($this->phpfile->uri), @@ -732,8 +753,9 @@ class FileSaveUploadTest extends FileHookTestCase { $this->drupalPost('file-test/upload', $edit, t('Submit')); $this->assertResponse(200, 'Received a 200 response for posted test file.'); - $message = t('For security reasons, your upload has been renamed to') . ' ' . $this->phpfile->filename . '.txt' . ''; + $message = t('For security reasons, your upload has been renamed to') . ' ' . $this->phpfile->filename . '_.txt' . ''; $this->assertRaw($message, 'Dangerous file was renamed.'); + $this->assertRaw('File name is php-2.php_.txt.'); $this->assertRaw(t('File MIME type is text/plain.'), "Dangerous file's MIME type was changed."); $this->assertRaw(t('You WIN!'), 'Found the success message.'); @@ -755,8 +777,39 @@ class FileSaveUploadTest extends FileHookTestCase { // Check that the correct hooks were called. $this->assertFileHooksCalled(array('validate', 'insert')); - // Turn off insecure uploads. + // Reset the hook counters. + file_test_reset(); + + // Even with insecure uploads allowed, the .php file should not be uploaded + // if it is not explicitly included in the list of allowed extensions. + $edit['extensions'] = 'foo'; + $this->drupalPost('file-test/upload', $edit, t('Submit')); + $this->assertResponse(200, 'Received a 200 response for posted test file.'); + $message = t('Only files with the following extensions are allowed:') . ' ' . $edit['extensions'] . ''; + $this->assertRaw($message, 'Cannot upload a disallowed extension'); + $this->assertRaw(t('Epic upload FAIL!'), 'Found the failure message.'); + + // Check that the correct hooks were called. + $this->assertFileHooksCalled(array('validate')); + + // Reset the hook counters. + file_test_reset(); + + // Turn off insecure uploads, then try the same thing as above (ensure that + // the .php file is still rejected since it's not in the list of allowed + // extensions). variable_set('allow_insecure_uploads', 0); + $this->drupalPost('file-test/upload', $edit, t('Submit')); + $this->assertResponse(200, 'Received a 200 response for posted test file.'); + $message = t('Only files with the following extensions are allowed:') . ' ' . $edit['extensions'] . ''; + $this->assertRaw($message, 'Cannot upload a disallowed extension'); + $this->assertRaw(t('Epic upload FAIL!'), 'Found the failure message.'); + + // Check that the correct hooks were called. + $this->assertFileHooksCalled(array('validate')); + + // Reset the hook counters. + file_test_reset(); } /** @@ -765,6 +818,7 @@ class FileSaveUploadTest extends FileHookTestCase { function testHandleFileMunge() { // Ensure insecure uploads are disabled for this test. variable_set('allow_insecure_uploads', 0); + $original_image_uri = $this->image->uri; $this->image = file_move($this->image, $this->image->uri . '.foo.' . $this->image_extension); // Reset the hook counters to get rid of the 'move' we just called. @@ -789,13 +843,33 @@ class FileSaveUploadTest extends FileHookTestCase { // Check that the correct hooks were called. $this->assertFileHooksCalled(array('validate', 'insert')); + // Reset the hook counters. + file_test_reset(); + + // Ensure we don't munge the .foo extension if it is in the list of allowed + // extensions. + $extensions = 'foo ' . $this->image_extension; + $edit = array( + 'files[file_test_upload]' => drupal_realpath($this->image->uri), + 'extensions' => $extensions, + ); + + $this->drupalPost('file-test/upload', $edit, t('Submit')); + $this->assertResponse(200, 'Received a 200 response for posted test file.'); + $this->assertNoRaw(t('For security reasons, your upload has been renamed'), 'Found no security message.'); + $this->assertRaw(t('File name is @filename', array('@filename' => 'image-test.png.foo.png')), 'File was not munged when all extensions within it are allowed.'); + $this->assertRaw(t('You WIN!'), 'Found the success message.'); + + // Check that the correct hooks were called. + $this->assertFileHooksCalled(array('validate', 'insert')); + // Ensure we don't munge files if we're allowing any extension. // Reset the hook counters. file_test_reset(); $edit = array( 'files[file_test_upload]' => drupal_realpath($this->image->uri), - 'allow_all_extensions' => TRUE, + 'allow_all_extensions' => 'empty_array', ); $this->drupalPost('file-test/upload', $edit, t('Submit')); @@ -806,6 +880,94 @@ class FileSaveUploadTest extends FileHookTestCase { // Check that the correct hooks were called. $this->assertFileHooksCalled(array('validate', 'insert')); + + // Test that a dangerous extension such as .php is munged even if it is in + // the list of allowed extensions. + $this->image = file_move($this->image, $original_image_uri . '.php.' . $this->image_extension); + // Reset the hook counters. + file_test_reset(); + + $extensions = 'php ' . $this->image_extension; + $edit = array( + 'files[file_test_upload]' => drupal_realpath($this->image->uri), + 'extensions' => $extensions, + ); + + $munged_filename = $this->image->filename; + $munged_filename = substr($munged_filename, 0, strrpos($munged_filename, '.')); + $munged_filename .= '_.' . $this->image_extension; + + $this->drupalPost('file-test/upload', $edit, t('Submit')); + $this->assertResponse(200, 'Received a 200 response for posted test file.'); + $this->assertRaw(t('For security reasons, your upload has been renamed'), 'Found security message.'); + $this->assertRaw(t('File name is @filename', array('@filename' => $munged_filename)), 'File was successfully munged.'); + $this->assertRaw(t('You WIN!'), 'Found the success message.'); + + // Check that the correct hooks were called. + $this->assertFileHooksCalled(array('validate', 'insert')); + + // Reset the hook counters. + file_test_reset(); + + // Dangerous extensions are munged even when all extensions are allowed. + $edit = array( + 'files[file_test_upload]' => drupal_realpath($this->image->uri), + 'allow_all_extensions' => 'empty_array', + ); + + $munged_filename = $this->image->filename; + $munged_filename = substr($munged_filename, 0, strrpos($munged_filename, '.')); + $munged_filename .= '_.' . $this->image_extension; + + $this->drupalPost('file-test/upload', $edit, t('Submit')); + $this->assertResponse(200, 'Received a 200 response for posted test file.'); + $this->assertRaw(t('For security reasons, your upload has been renamed'), 'Found security message.'); + $this->assertRaw(t('File name is @filename.', array('@filename' => 'image-test.png_.php_.png_.txt')), 'File was successfully munged.'); + $this->assertRaw(t('You WIN!'), 'Found the success message.'); + + // Check that the correct hooks were called. + $this->assertFileHooksCalled(array('validate', 'insert')); + + // Dangerous extensions are munged if is renamed to end in .txt. + $this->image = file_move($this->image, $original_image_uri . '.cgi.' . $this->image_extension . '.txt'); + // Reset the hook counters. + file_test_reset(); + + $edit = array( + 'files[file_test_upload]' => drupal_realpath($this->image->uri), + 'allow_all_extensions' => 'empty_array', + ); + + $munged_filename = $this->image->filename; + $munged_filename = substr($munged_filename, 0, strrpos($munged_filename, '.')); + $munged_filename .= '_.' . $this->image_extension; + + $this->drupalPost('file-test/upload', $edit, t('Submit')); + $this->assertResponse(200, 'Received a 200 response for posted test file.'); + $this->assertRaw(t('For security reasons, your upload has been renamed'), 'Found security message.'); + $this->assertRaw(t('File name is @filename.', array('@filename' => 'image-test.png_.cgi_.png_.txt')), 'File was successfully munged.'); + $this->assertRaw(t('You WIN!'), 'Found the success message.'); + + // Check that the correct hooks were called. + $this->assertFileHooksCalled(array('validate', 'insert')); + + // Reset the hook counters. + file_test_reset(); + + // Ensure that setting $validators['file_validate_extensions'] = array('') + // rejects all files without munging or renaming. + $edit = array( + 'files[file_test_upload]' => drupal_realpath($this->image->uri), + 'allow_all_extensions' => 'empty_string', + ); + + $this->drupalPost('file-test/upload', $edit, t('Submit')); + $this->assertResponse(200, 'Received a 200 response for posted test file.'); + $this->assertNoRaw(t('For security reasons, your upload has been renamed'), 'Found security message.'); + $this->assertRaw(t('Epic upload FAIL!'), 'Found the failure message.'); + + // Check that the correct hooks were called. + $this->assertFileHooksCalled(array('validate')); } /** @@ -2192,6 +2354,25 @@ class FileValidateTest extends FileHookTestCase { $this->assertEqual(file_validate($file, $failing), array('Failed', 'Badly', 'Epic fail'), 'Validating returns errors.'); $this->assertFileHooksCalled(array('validate')); } + + /** + * Tests hard-coded security check in file_validate(). + */ + public function testInsecureExtensions() { + $file = $this->createFile('test.php', 'Invalid PHP'); + + // Test that file_validate() will check for insecure extensions by default. + $errors = file_validate($file, array()); + $this->assertEqual('For security reasons, your upload has been rejected.', $errors[0]); + $this->assertFileHooksCalled(array('validate')); + file_test_reset(); + + // Test that the 'allow_insecure_uploads' is respected. + variable_set('allow_insecure_uploads', 1); + $errors = file_validate($file, array()); + $this->assertEqual(array(), $errors); + $this->assertFileHooksCalled(array('validate')); + } } /** @@ -2561,7 +2742,7 @@ class FileNameMungingTest extends FileTestCase { function setUp() { parent::setUp(); - $this->bad_extension = 'php'; + $this->bad_extension = 'foo'; $this->name = $this->randomName() . '.' . $this->bad_extension . '.txt'; $this->name_with_uc_ext = $this->randomName() . '.' . strtoupper($this->bad_extension) . '.txt'; } @@ -2610,6 +2791,18 @@ class FileNameMungingTest extends FileTestCase { $this->assertIdentical($munged_name, $this->name, format_string('The new filename (%munged) matches the original (%original) also when the whitelisted extension is in uppercase.', array('%munged' => $munged_name, '%original' => $this->name))); } + /** + * Tests unsafe extensions are munged by file_munge_filename(). + */ + public function testMungeUnsafe() { + $prefix = $this->randomName(); + $name = "$prefix.php.txt"; + // Put the php extension in the allowed list, but since it is in the unsafe + // extension list, it should still be munged. + $munged_name = file_munge_filename($name, 'php txt'); + $this->assertIdentical($munged_name, "$prefix.php_.txt", format_string('The filename (%munged) has been modified from the original (%original) if the allowed extension is also on the unsafe list.', array('%munged' => $munged_name, '%original' => $name))); + } + /** * Ensure that unmunge gets your name back. */ diff --git a/modules/simpletest/tests/file_test.module b/modules/simpletest/tests/file_test.module index 1b11316f9f0..b7d6fd0c2ff 100644 --- a/modules/simpletest/tests/file_test.module +++ b/modules/simpletest/tests/file_test.module @@ -76,9 +76,13 @@ function _file_test_form($form, &$form_state) { ); $form['allow_all_extensions'] = array( - '#type' => 'checkbox', - '#title' => t('Allow all extensions?'), - '#default_value' => FALSE, + '#type' => 'radios', + '#options' => array( + 'false' => 'No', + 'empty_array' => 'Empty array', + 'empty_string' => 'Empty string', + ), + '#default_value' => 'false', ); $form['is_image_file'] = array( @@ -114,9 +118,13 @@ function _file_test_form_submit(&$form, &$form_state) { $validators['file_validate_is_image'] = array(); } - if ($form_state['values']['allow_all_extensions']) { + $allow = $form_state['values']['allow_all_extensions']; + if ($allow === 'empty_array') { $validators['file_validate_extensions'] = array(); } + elseif ($allow === 'empty_string') { + $validators['file_validate_extensions'] = array(''); + } elseif (!empty($form_state['values']['extensions'])) { $validators['file_validate_extensions'] = array($form_state['values']['extensions']); } diff --git a/modules/simpletest/tests/form.test b/modules/simpletest/tests/form.test index d1be69d7272..49b561a63f4 100644 --- a/modules/simpletest/tests/form.test +++ b/modules/simpletest/tests/form.test @@ -591,6 +591,19 @@ class FormElementTestCase extends DrupalWebTestCase { ))); } } + + /** + * Tests Weight form element #default_value behavior. + */ + public function testWeightDefaultValue() { + $element = array( + '#type' => 'weight', + '#delta' => 10, + '#default_value' => 15, + ); + $element = form_process_weight($element); + $this->assertTrue(isset($element['#options'][$element['#default_value']]), 'Default value exists in #options list'); + } } /** diff --git a/modules/simpletest/tests/mail.test b/modules/simpletest/tests/mail.test index 3e40e13a89f..307c77b29fb 100644 --- a/modules/simpletest/tests/mail.test +++ b/modules/simpletest/tests/mail.test @@ -59,6 +59,81 @@ class MailTestCase extends DrupalWebTestCase implements MailSystemInterface { $this->assertNull(self::$sent_message, 'Message was canceled.'); } + /** + * Checks for the site name in an auto-generated From: header. + */ + function testFromHeader() { + global $language; + $default_from = variable_get('site_mail', ini_get('sendmail_from')); + $site_name = variable_get('site_name', 'Drupal'); + + // Reset the class variable holding a copy of the last sent message. + self::$sent_message = NULL; + // Send an e-mail with a sender address specified. + $from_email = 'someone_else@example.com'; + $message = drupal_mail('simpletest', 'from_test', 'from_test@example.com', $language, array(), $from_email); + // Test that the from e-mail is just the e-mail and not the site name and + // default sender e-mail. + $this->assertEqual($from_email, self::$sent_message['headers']['From']); + + // Check default behavior is only email in FROM header. + self::$sent_message = NULL; + // Send an e-mail and check that the From-header contains only default mail address. + variable_del('mail_display_name_site_name'); + $message = drupal_mail('simpletest', 'from_test', 'from_test@example.com', $language); + $this->assertEqual($default_from, self::$sent_message['headers']['From']); + + self::$sent_message = NULL; + // Send an e-mail and check that the From-header contains the site name. + variable_set('mail_display_name_site_name', TRUE); + $message = drupal_mail('simpletest', 'from_test', 'from_test@example.com', $language); + $this->assertEqual($site_name . ' <' . $default_from . '>', self::$sent_message['headers']['From']); + } + + /** + * Checks for the site name in an auto-generated From: header. + */ + function testFromHeaderRfc2822Compliant() { + global $language; + $default_from = variable_get('site_mail', ini_get('sendmail_from')); + + // Enable adding a site name to From. + variable_set('mail_display_name_site_name', TRUE); + + $site_names = array( + // Simple ASCII characters. + 'Test site' => 'Test site', + // ASCII with html entity. + 'Test & site' => 'Test & site', + // Non-ASCII characters. + 'Tést site' => '=?UTF-8?B?VMOpc3Qgc2l0ZQ==?=', + // Non-ASCII with special characters. + 'Tést; site' => '=?UTF-8?B?VMOpc3Q7IHNpdGU=?=', + // Non-ASCII with html entity. + 'Tést; site' => '=?UTF-8?B?VMOpc3Q7IHNpdGU=?=', + // ASCII with special characters. + 'Test; site' => '"Test; site"', + // ASCII with special characters as html entity. + 'Test < site' => '"Test < site"', + // ASCII with special characters and '\'. + 'Test; \ "site"' => '"Test; \\\\ \"site\""', + // String already RFC-2822 compliant. + '"Test; site"' => '"Test; site"', + // String already RFC-2822 compliant. + '"Test; \\\\ \"site\""' => '"Test; \\\\ \"site\""', + ); + + foreach ($site_names as $original_name => $safe_string) { + variable_set('site_name', $original_name); + + // Reset the class variable holding a copy of the last sent message. + self::$sent_message = NULL; + // Send an e-mail and check that the From-header contains is RFC-2822 compliant. + drupal_mail('simpletest', 'from_test', 'from_test@example.com', $language); + $this->assertEqual($safe_string . ' <' . $default_from . '>', self::$sent_message['headers']['From']); + } + } + /** * Concatenate and wrap the e-mail body for plain-text mails. * diff --git a/modules/simpletest/tests/schema.test b/modules/simpletest/tests/schema.test index 41994284ee9..070b2911ed0 100644 --- a/modules/simpletest/tests/schema.test +++ b/modules/simpletest/tests/schema.test @@ -381,4 +381,60 @@ class SchemaTestCase extends DrupalWebTestCase { db_drop_field($table_name, $field_name); } + + /** + * Tests the findTables() method. + */ + public function testFindTables() { + // We will be testing with three tables, two of them using the default + // prefix and the third one with an individually specified prefix. + + // Set up a new connection with different connection info. + $connection_info = Database::getConnectionInfo(); + + // Add per-table prefix to the second table. + $new_connection_info = $connection_info['default']; + $new_connection_info['prefix']['test_2_table'] = $new_connection_info['prefix']['default'] . '_shared_'; + Database::addConnectionInfo('test', 'default', $new_connection_info); + + Database::setActiveConnection('test'); + + // Create the tables. + $table_specification = array( + 'description' => 'Test table.', + 'fields' => array( + 'id' => array( + 'type' => 'int', + 'default' => NULL, + ), + ), + ); + Database::getConnection()->schema()->createTable('test_1_table', $table_specification); + Database::getConnection()->schema()->createTable('test_2_table', $table_specification); + Database::getConnection()->schema()->createTable('the_third_table', $table_specification); + + // Check the "all tables" syntax. + $tables = Database::getConnection()->schema()->findTablesD8('%'); + sort($tables); + $expected = array( + 'test_1_table', + // This table uses a per-table prefix, yet it is returned as un-prefixed. + 'test_2_table', + 'the_third_table', + ); + + $this->assertTrue(!array_diff($expected, $tables), 'All tables were found.'); + + // Check the restrictive syntax. + $tables = Database::getConnection()->schema()->findTablesD8('test_%'); + sort($tables); + $expected = array( + 'test_1_table', + 'test_2_table', + ); + $this->assertEqual($tables, $expected, 'Two tables were found.'); + + // Go back to the initial connection. + Database::setActiveConnection('default'); + } } diff --git a/modules/system/system.tar.inc b/modules/system/system.tar.inc index 6e3ae4238f2..0af6275b401 100644 --- a/modules/system/system.tar.inc +++ b/modules/system/system.tar.inc @@ -1788,7 +1788,7 @@ class Archive_Tar // ----- Extract the properties $v_header['filename'] = rtrim($v_data['filename'], "\0"); - if ($this->_maliciousFilename($v_header['filename'])) { + if ($this->_isMaliciousFilename($v_header['filename'])) { $this->_error( 'Malicious .tar detected, file "' . $v_header['filename'] . '" will not install in desired directory tree' @@ -1858,9 +1858,9 @@ class Archive_Tar * * @return bool */ - private function _maliciousFilename($file) + private function _isMaliciousFilename($file) { - if (strpos($file, 'phar://') === 0) { + if (strpos($file, '://') !== false) { return true; } if (strpos($file, '../') !== false || strpos($file, '..\\') !== false) { @@ -1896,7 +1896,7 @@ class Archive_Tar $v_filename = rtrim(substr($v_filename, 0, $v_filesize), "\0"); $v_header['filename'] = $v_filename; - if ($this->_maliciousFilename($v_filename)) { + if ($this->_isMaliciousFilename($v_filename)) { $this->_error( 'Malicious .tar detected, file "' . $v_filename . '" will not install in desired directory tree' @@ -2178,6 +2178,14 @@ class Archive_Tar } } } elseif ($v_header['typeflag'] == "2") { + if (strpos(realpath(dirname($v_header['link'])), realpath($p_path)) !== 0) { + $this->_error( + 'Out-of-path file extraction {' + . $v_header['filename'] . ' --> ' . + $v_header['link'] . '}' + ); + return false; + } if (!$p_symlinks) { $this->_warning('Symbolic links are not allowed. ' . 'Unable to extract {' diff --git a/modules/system/system.test b/modules/system/system.test index 270311ecb06..45c6648c437 100644 --- a/modules/system/system.test +++ b/modules/system/system.test @@ -28,7 +28,7 @@ class ModuleTestCase extends DrupalWebTestCase { * specified base table. Defaults to TRUE. */ function assertTableCount($base_table, $count = TRUE) { - $tables = db_find_tables(Database::getConnection()->prefixTables('{' . $base_table . '}') . '%'); + $tables = db_find_tables_d8($base_table . '%'); if ($count) { return $this->assertTrue($tables, format_string('Tables matching "@base_table" found.', array('@base_table' => $base_table))); @@ -779,14 +779,14 @@ class IPAddressBlockingTestCase extends DrupalWebTestCase { $submit_ip = $_SERVER['REMOTE_ADDR'] = '192.168.1.1'; system_block_ip_action(); system_block_ip_action(); - $ip_count = db_query("SELECT iid from {blocked_ips} WHERE ip = :ip", array(':ip' => $submit_ip))->rowCount(); + $ip_count = db_query("SELECT COUNT(*) from {blocked_ips} WHERE ip = :ip", array(':ip' => $submit_ip))->fetchColumn(); $this->assertEqual('1', $ip_count); drupal_static_reset('ip_address'); $submit_ip = $_SERVER['REMOTE_ADDR'] = ' '; system_block_ip_action(); system_block_ip_action(); system_block_ip_action(); - $ip_count = db_query("SELECT iid from {blocked_ips} WHERE ip = :ip", array(':ip' => $submit_ip))->rowCount(); + $ip_count = db_query("SELECT COUNT(*) from {blocked_ips} WHERE ip = :ip", array(':ip' => $submit_ip))->fetchColumn(); $this->assertEqual('1', $ip_count); } } diff --git a/modules/user/tests/user_flood_test.info b/modules/user/tests/user_flood_test.info new file mode 100644 index 00000000000..909997a771c --- /dev/null +++ b/modules/user/tests/user_flood_test.info @@ -0,0 +1,6 @@ +name = "User module flood control tests" +description = "Support module for user flood control testing." +package = Testing +version = VERSION +core = 7.x +hidden = TRUE diff --git a/modules/user/tests/user_flood_test.module b/modules/user/tests/user_flood_test.module new file mode 100644 index 00000000000..f7388690f56 --- /dev/null +++ b/modules/user/tests/user_flood_test.module @@ -0,0 +1,18 @@ + $username, '%ip' => $ip)); + } + else { + watchdog('user_flood_test', 'hook_user_flood_control was passed IP %ip.', array('%ip' => $ip)); + } +} diff --git a/modules/user/tests/user_form_test.module b/modules/user/tests/user_form_test.module index 382bc57b821..2af15cb83b6 100644 --- a/modules/user/tests/user_form_test.module +++ b/modules/user/tests/user_form_test.module @@ -35,7 +35,7 @@ function user_form_test_current_password($form, &$form_state, $account) { '#description' => t('A field that would require a correct password to change.'), '#required' => TRUE, ); - + $form['current_pass'] = array( '#type' => 'password', '#title' => t('Current password'), diff --git a/modules/user/user.api.php b/modules/user/user.api.php index f205a85b58f..b9dc95f15dc 100644 --- a/modules/user/user.api.php +++ b/modules/user/user.api.php @@ -472,6 +472,36 @@ function hook_user_role_delete($role) { ->execute(); } +/** + * Respond to user flood control events. + * + * This hook allows you act when an unsuccessful user login has triggered + * flood control. This means that either an IP address or a specific user + * account has been temporarily blocked from logging in. + * + * @param $ip + * The IP address that triggered flood control. + * @param $username + * The username that has been temporarily blocked. + * + * @see user_login_final_validate() + */ +function hook_user_flood_control($ip, $username = FALSE) { + if (!empty($username)) { + // Do something with the blocked $username and $ip. For example, send an + // e-mail to the user and/or site administrator. + + // Drupal core uses this hook to log the event: + watchdog('user', 'Flood control blocked login attempt for %user from %ip.', array('%user' => $username, '%ip' => $ip)); + } + else { + // Do something with the blocked $ip. For example, add it to a block-list. + + // Drupal core uses this hook to log the event: + watchdog('user', 'Flood control blocked login attempt from %ip.', array('%ip' => $ip)); + } +} + /** * @} End of "addtogroup hooks". */ diff --git a/modules/user/user.module b/modules/user/user.module index 2309aa92961..dfa05978cbb 100644 --- a/modules/user/user.module +++ b/modules/user/user.module @@ -2225,11 +2225,17 @@ function user_login_final_validate($form, &$form_state) { if (isset($form_state['flood_control_triggered'])) { if ($form_state['flood_control_triggered'] == 'user') { form_set_error('name', format_plural(variable_get('user_failed_login_user_limit', 5), 'Sorry, there has been more than one failed login attempt for this account. It is temporarily blocked. Try again later or request a new password.', 'Sorry, there have been more than @count failed login attempts for this account. It is temporarily blocked. Try again later or request a new password.', array('@url' => url('user/password')))); + module_invoke_all('user_flood_control', ip_address(), $form_state['values']['name']); } else { // We did not find a uid, so the limit is IP-based. form_set_error('name', t('Sorry, too many failed login attempts from your IP address. This IP address is temporarily blocked. Try again later or request a new password.', array('@url' => url('user/password')))); + module_invoke_all('user_flood_control', ip_address()); } + // We cannot call drupal_access_denied() here as that can result in an + // infinite loop if the login form is rendered on the 403 page (e.g. in a + // block). So add the 403 header and allow form processing to finish. + drupal_add_http_header('Status', '403 Forbidden'); } else { // Use $form_state['input']['name'] here to guarantee that we send @@ -2247,6 +2253,23 @@ function user_login_final_validate($form, &$form_state) { } } +/** + * Implements hook_user_flood_control(). + */ +function user_user_flood_control($ip, $username = FALSE) { + if (variable_get('log_user_flood_control', TRUE)) { + if (!empty($username)) { + watchdog('user', 'Flood control blocked login attempt for %user from %ip.', array( + '%user' => $username, + '%ip' => $ip + )); + } + else { + watchdog('user', 'Flood control blocked login attempt from %ip.', array('%ip' => $ip)); + } + } +} + /** * Try to validate the user's login credentials locally. * diff --git a/modules/user/user.pages.inc b/modules/user/user.pages.inc index 2a1b291b134..6f997a62ea3 100644 --- a/modules/user/user.pages.inc +++ b/modules/user/user.pages.inc @@ -66,6 +66,22 @@ function user_pass() { * @see user_pass_submit() */ function user_pass_validate($form, &$form_state) { + if (isset($form_state['values']['name']) && !is_scalar($form_state['values']['name'])) { + form_set_error('name', t('An illegal value has been detected. Please contact the site administrator.')); + return; + } + $user_pass_reset_ip_window = variable_get('user_pass_reset_ip_window', 3600); + // Do not allow any password reset from the current user's IP if the limit + // has been reached. Default is 50 attempts allowed in one hour. This is + // independent of the per-user limit to catch attempts from one IP to request + // resets for many different user accounts. We have a reasonably high limit + // since there may be only one apparent IP for all users at an institution. + if (!flood_is_allowed('pass_reset_ip', variable_get('user_pass_reset_ip_limit', 50), $user_pass_reset_ip_window)) { + form_set_error('name', t('Sorry, too many password reset attempts from your IP address. This IP address is temporarily blocked. Try again later or request a new password.', array('@url' => url('user/password')))); + return; + } + // Always register an per-IP event. + flood_register_event('pass_reset_ip', $user_pass_reset_ip_window); $name = trim($form_state['values']['name']); // Try to load by email. $users = user_load_multiple(array(), array('mail' => $name, 'status' => '1')); @@ -76,6 +92,19 @@ function user_pass_validate($form, &$form_state) { $account = reset($users); } if (isset($account->uid)) { + // Register user flood events based on the uid only, so they can be cleared + // when a password is reset successfully. + $identifier = $account->uid; + $user_pass_reset_user_window = variable_get('user_pass_reset_user_window', 21600); + $user_pass_reset_user_limit = variable_get('user_pass_reset_user_limit', 5); + // Don't allow password reset if the limit for this user has been reached. + // Default is to allow 5 passwords resets every 6 hours. + if (!flood_is_allowed('pass_reset_user', $user_pass_reset_user_limit, $user_pass_reset_user_window, $identifier)) { + form_set_error('name', format_plural($user_pass_reset_user_limit, 'Sorry, there has been more than one password reset attempt for this account. It is temporarily blocked. Try again later or login with your password.', 'Sorry, there have been more than @count password reset attempts for this account. It is temporarily blocked. Try again later or login with your password.', array('@url' => url('user/login')))); + return; + } + // Register a per-user event. + flood_register_event('pass_reset_user', $user_pass_reset_user_window, $identifier); form_set_value(array('#parents' => array('account')), $account, $form_state); } else { @@ -161,6 +190,8 @@ function user_pass_reset($form, &$form_state, $uid, $timestamp, $hashed_pass, $a // user_login_finalize() also updates the login timestamp of the // user, which invalidates further use of the one-time login link. user_login_finalize(); + // Clear any password reset flood events for this user. + flood_clear_event('pass_reset_user', $account->uid); watchdog('user', 'User %name used one-time login link at time %timestamp.', array('%name' => $account->name, '%timestamp' => $timestamp)); drupal_set_message(t('You have just used your one-time login link. It is no longer necessary to use this link to log in. Please change your password.')); // Let the user's password be changed without the current password check. diff --git a/modules/user/user.test b/modules/user/user.test index 835154b25f7..4c16b531c3c 100644 --- a/modules/user/user.test +++ b/modules/user/user.test @@ -322,7 +322,7 @@ class UserLoginTestCase extends DrupalWebTestCase { } function setUp() { - parent::setUp('user_session_test'); + parent::setUp('user_session_test', 'user_flood_test'); } /** @@ -453,12 +453,19 @@ class UserLoginTestCase extends DrupalWebTestCase { $this->drupalPost('user', $edit, t('Log in')); $this->assertNoFieldByXPath("//input[@name='pass' and @value!='']", NULL, 'Password value attribute is blank.'); if (isset($flood_trigger)) { + $this->assertResponse(403); + $user_log = db_query_range('SELECT message FROM {watchdog} WHERE type = :type ORDER BY wid DESC', 0, 1, array(':type' => 'user'))->fetchField(); + $user_flood_test_log = db_query_range('SELECT message FROM {watchdog} WHERE type = :type ORDER BY wid DESC', 0, 1, array(':type' => 'user_flood_test'))->fetchField(); if ($flood_trigger == 'user') { - $this->assertRaw(format_plural(variable_get('user_failed_login_user_limit', 5), 'Sorry, there has been more than one failed login attempt for this account. It is temporarily blocked. Try again later or request a new password.', 'Sorry, there have been more than @count failed login attempts for this account. It is temporarily blocked. Try again later or request a new password.', array('@url' => url('user/password')))); + $this->assertRaw(t('Sorry, there have been more than @count failed login attempts for this account. It is temporarily blocked. Try again later or request a new password.', array('@url' => url('user/password'), '@count' => variable_get('user_failed_login_user_limit', 5)))); + $this->assertEqual('Flood control blocked login attempt for %user from %ip.', $user_log, 'A watchdog message was logged for the login attempt blocked by flood control per user'); + $this->assertEqual('hook_user_flood_control was passed username %username and IP %ip.', $user_flood_test_log, 'hook_user_flood_control was invoked by flood control per user'); } else { // No uid, so the limit is IP-based. $this->assertRaw(t('Sorry, too many failed login attempts from your IP address. This IP address is temporarily blocked. Try again later or request a new password.', array('@url' => url('user/password')))); + $this->assertEqual('Flood control blocked login attempt from %ip.', $user_log, 'A watchdog message was logged for the login attempt blocked by flood control per IP'); + $this->assertEqual('hook_user_flood_control was passed IP %ip.', $user_flood_test_log, 'hook_user_flood_control was invoked by flood control per IP'); } } else { @@ -507,6 +514,8 @@ class UserPasswordResetTestCase extends DrupalWebTestCase { $this->drupalPost('user/password', $edit, t('E-mail new password')); // Confirm the password reset. $this->assertText(t('Further instructions have been sent to your e-mail address.'), 'Password reset instructions mailed message displayed.'); + // Ensure that flood control was not triggered. + $this->assertNoText(t('is temporarily blocked. Try again later'), 'Flood control was not triggered by single password reset.'); // Create an image field to enable an Ajax request on the user profile page. $field = array( @@ -552,6 +561,84 @@ class UserPasswordResetTestCase extends DrupalWebTestCase { $this->assertText(t('The changes have been saved.'), 'Forgotten password changed.'); } + /** + * Test user-based flood control on password reset. + */ + function testPasswordResetFloodControlPerUser() { + // Set a very low limit for testing. + variable_set('user_pass_reset_user_limit', 2); + + // Create a user. + $account = $this->drupalCreateUser(); + $this->drupalLogin($account); + $this->drupalLogout(); + + $edit = array('name' => $account->name); + + // Try 2 requests that should not trigger flood control. + for ($i = 0; $i < 2; $i++) { + $this->drupalPost('user/password', $edit, t('E-mail new password')); + // Confirm the password reset. + $this->assertText(t('Further instructions have been sent to your e-mail address.'), 'Password reset instructions mailed message displayed.'); + // Ensure that flood control was not triggered. + $this->assertNoText(t('is temporarily blocked. Try again later'), 'Flood control was not triggered by password reset.'); + } + + // A successful password reset should clear flood events. + $resetURL = $this->getResetURL(); + $this->drupalGet($resetURL); + + // Check successful login. + $this->drupalPost(NULL, NULL, t('Log in')); + $this->drupalLogout(); + + // Try 2 requests that should not trigger flood control. + for ($i = 0; $i < 2; $i++) { + $this->drupalPost('user/password', $edit, t('E-mail new password')); + // Confirm the password reset. + $this->assertText(t('Further instructions have been sent to your e-mail address.'), 'Password reset instructions mailed message displayed.'); + // Ensure that flood control was not triggered. + $this->assertNoText(t('is temporarily blocked. Try again later'), 'Flood control was not triggered by password reset.'); + } + + // The next request should trigger flood control + $this->drupalPost('user/password', $edit, t('E-mail new password')); + // Confirm the password reset was blocked. + $this->assertNoText(t('Further instructions have been sent to your e-mail address.'), 'Password reset instructions mailed message not displayed for excessive password resets.'); + // Ensure that flood control was triggered. + $this->assertText(t('Sorry, there have been more than 2 password reset attempts for this account. It is temporarily blocked.'), 'Flood control was triggered by excessive password resets for one user.'); + } + + /** + * Test IP-based flood control on password reset. + */ + function testPasswordResetFloodControlPerIp() { + // Set a very low limit for testing. + variable_set('user_pass_reset_ip_limit', 2); + + // Try 2 requests that should not trigger flood control. + for ($i = 0; $i < 2; $i++) { + $name = $this->randomName(); + $edit = array('name' => $name); + $this->drupalPost('user/password', $edit, t('E-mail new password')); + // Confirm the password reset was not blocked. Note that @name is used + // instead of %name as assertText() works with plain text not HTML. + $this->assertText(t('Sorry, @name is not recognized as a user name or an e-mail address.', array('@name' => $name)), 'User name not recognized message displayed.'); + // Ensure that flood control was not triggered. + $this->assertNoText(t('is temporarily blocked. Try again later'), 'Flood control was not triggered by password reset.'); + } + + // The next request should trigger flood control + $name = $this->randomName(); + $edit = array('name' => $name); + $this->drupalPost('user/password', $edit, t('E-mail new password')); + // Confirm the password reset was blocked early. Note that @name is used + // instead of %name as assertText() works with plain text not HTML. + $this->assertNoText(t('Sorry, @name is not recognized as a user name or an e-mail address.', array('@name' => $name)), 'User name not recognized message not displayed.'); + // Ensure that flood control was triggered. + $this->assertText(t('Sorry, too many password reset attempts from your IP address. This IP address is temporarily blocked.'), 'Flood control was triggered by excessive password resets from one IP.'); + } + /** * Test user password reset while logged in. */ diff --git a/profiles/commerce_kickstart/commerce_kickstart.info b/profiles/commerce_kickstart/commerce_kickstart.info index 6687d8012b9..961f6ce6b60 100644 --- a/profiles/commerce_kickstart/commerce_kickstart.info +++ b/profiles/commerce_kickstart/commerce_kickstart.info @@ -117,8 +117,8 @@ dependencies[] = commerce_kickstart_migrate ; System Requirements. php_memory_limit = 128M -; Information added by Drupal.org packaging script on 2020-06-18 -version = "7.x-2.66" +; Information added by Drupal.org packaging script on 2021-02-03 +version = "7.x-2.70" core = "7.x" project = "commerce_kickstart" -datestamp = "1592498718" +datestamp = "1612329185" diff --git a/profiles/commerce_kickstart/drupal-org-core.make b/profiles/commerce_kickstart/drupal-org-core.make index 1508e65ea0e..cc89a38427b 100644 --- a/profiles/commerce_kickstart/drupal-org-core.make +++ b/profiles/commerce_kickstart/drupal-org-core.make @@ -1,6 +1,6 @@ api = 2 core = 7.x -projects[drupal][version] = 7.72 +projects[drupal][version] = 7.78 ; Patches for Core ; This patch will not apply, and there shouldn't be any new installs happening, so skip this patch. diff --git a/profiles/commerce_kickstart/drupal-org.make b/profiles/commerce_kickstart/drupal-org.make index 96c29b4fc61..bc0e815e72b 100644 --- a/profiles/commerce_kickstart/drupal-org.make +++ b/profiles/commerce_kickstart/drupal-org.make @@ -5,12 +5,12 @@ api = 2 defaults[projects][subdir] = contrib ; Basic contributed modules. -projects[ctools][version] = 1.15 +projects[ctools][version] = 1.19 projects[entity][version] = 1.9 projects[entityreference][version] = 1.5 projects[rules][version] = 2.12 -projects[views][version] = 3.23 -projects[views_bulk_operations][version] = 3.5 +projects[views][version] = 3.24 +projects[views_bulk_operations][version] = 3.6 projects[addressfield][version] = 1.3 projects[features][version] = 2.11 projects[features][patch][2143765] = "http://drupal.org/files/issues/features-fix-modules-enabled-2143765-1.patch" @@ -124,7 +124,7 @@ projects[distro_update][version] = 1.0-beta4 ; Internationalization projects[variable][version] = 2.5 -projects[i18n][version] = 1.26 +projects[i18n][version] = 1.27 projects[lingotek][version] = 7.33 ; Base theme. diff --git a/profiles/commerce_kickstart/libraries/colorbox/jquery.colorbox.js b/profiles/commerce_kickstart/libraries/colorbox/jquery.colorbox.js index a4e2d15650f..927e2064c29 100644 --- a/profiles/commerce_kickstart/libraries/colorbox/jquery.colorbox.js +++ b/profiles/commerce_kickstart/libraries/colorbox/jquery.colorbox.js @@ -80,7 +80,7 @@ return this.rel; }, href: function() { - // using this.href would give the absolute url, when the href may have been inteded as a selector (e.g. '#container') + // using this.href would give the absolute url, when the href may have been intended as a selector (e.g. '#container') return $(this).attr('href'); }, title: function() { diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_block/commerce_kickstart_block.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_block/commerce_kickstart_block.info index 7414a2288e3..1c5b19e3eca 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_block/commerce_kickstart_block.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_block/commerce_kickstart_block.info @@ -32,8 +32,8 @@ features[menu_links][] = secondary-navigation_contact:node/1 features[variable][] = menu_secondary_links_source files[] = commerce_kickstart_block.module -; Information added by Drupal.org packaging script on 2020-06-18 -version = "7.x-2.66" +; Information added by Drupal.org packaging script on 2021-02-03 +version = "7.x-2.70" core = "7.x" project = "commerce_kickstart" -datestamp = "1592498718" +datestamp = "1612329185" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_blog/commerce_kickstart_blog.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_blog/commerce_kickstart_blog.info index 41fbbb40e40..f3cd482f67d 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_blog/commerce_kickstart_blog.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_blog/commerce_kickstart_blog.info @@ -51,8 +51,8 @@ features_exclude[field_base][field_image] = field_image features_exclude[field_base][title_field] = title_field files[] = commerce_kickstart_blog.migrate.inc -; Information added by Drupal.org packaging script on 2020-06-18 -version = "7.x-2.66" +; Information added by Drupal.org packaging script on 2021-02-03 +version = "7.x-2.70" core = "7.x" project = "commerce_kickstart" -datestamp = "1592498718" +datestamp = "1612329185" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_comment/commerce_kickstart_comment.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_comment/commerce_kickstart_comment.info index 90df5588708..55fad627db5 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_comment/commerce_kickstart_comment.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_comment/commerce_kickstart_comment.info @@ -4,8 +4,8 @@ package = Commerce Kickstart core = 7.x dependencies[] = comment -; Information added by Drupal.org packaging script on 2020-06-18 -version = "7.x-2.66" +; Information added by Drupal.org packaging script on 2021-02-03 +version = "7.x-2.70" core = "7.x" project = "commerce_kickstart" -datestamp = "1592498718" +datestamp = "1612329185" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_help/commerce_kickstart_help.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_help/commerce_kickstart_help.info index 3f27fa112e9..dce83c77839 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_help/commerce_kickstart_help.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_help/commerce_kickstart_help.info @@ -4,8 +4,8 @@ core = 7.x package = Commerce Kickstart dependencies[] = advanced_help -; Information added by Drupal.org packaging script on 2020-06-18 -version = "7.x-2.66" +; Information added by Drupal.org packaging script on 2021-02-03 +version = "7.x-2.70" core = "7.x" project = "commerce_kickstart" -datestamp = "1592498718" +datestamp = "1612329185" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_help/commerce_kickstart_inline_help/commerce_kickstart_inline_help.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_help/commerce_kickstart_inline_help/commerce_kickstart_inline_help.info index 789392f1a0b..5e5588ed2a5 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_help/commerce_kickstart_inline_help/commerce_kickstart_inline_help.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_help/commerce_kickstart_inline_help/commerce_kickstart_inline_help.info @@ -5,8 +5,8 @@ package = Commerce Kickstart dependencies[] = advanced_help dependencies[] = commerce_kickstart_help -; Information added by Drupal.org packaging script on 2020-06-18 -version = "7.x-2.66" +; Information added by Drupal.org packaging script on 2021-02-03 +version = "7.x-2.70" core = "7.x" project = "commerce_kickstart" -datestamp = "1592498718" +datestamp = "1612329185" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_lite_product/commerce_kickstart_lite_product.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_lite_product/commerce_kickstart_lite_product.info index 7476e1d82cb..d84990e7f72 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_lite_product/commerce_kickstart_lite_product.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_lite_product/commerce_kickstart_lite_product.info @@ -55,8 +55,8 @@ features[variable][] = pathauto_taxonomy_term_pattern features_exclude[taxonomy][product_category] = product_category files[] = commerce_kickstart_lite_product.migrate.inc -; Information added by Drupal.org packaging script on 2020-06-18 -version = "7.x-2.66" +; Information added by Drupal.org packaging script on 2021-02-03 +version = "7.x-2.70" core = "7.x" project = "commerce_kickstart" -datestamp = "1592498718" +datestamp = "1612329185" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_menus/commerce_kickstart_menus.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_menus/commerce_kickstart_menus.info index d8133d14d48..4599bd9c84d 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_menus/commerce_kickstart_menus.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_menus/commerce_kickstart_menus.info @@ -6,8 +6,8 @@ dependencies[] = toolbar_megamenu stylesheets[all][] = commerce_kickstart_menus.css -; Information added by Drupal.org packaging script on 2020-06-18 -version = "7.x-2.66" +; Information added by Drupal.org packaging script on 2021-02-03 +version = "7.x-2.70" core = "7.x" project = "commerce_kickstart" -datestamp = "1592498718" +datestamp = "1612329185" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_merchandising/commerce_kickstart_merchandising.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_merchandising/commerce_kickstart_merchandising.info index 73d766cf8ee..763c866cacc 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_merchandising/commerce_kickstart_merchandising.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_merchandising/commerce_kickstart_merchandising.info @@ -41,8 +41,8 @@ features_exclude[field_base][field_image] = field_image features_exclude[field_base][title_field] = title_field files[] = commerce_kickstart_merchandising.migrate.inc -; Information added by Drupal.org packaging script on 2020-06-18 -version = "7.x-2.66" +; Information added by Drupal.org packaging script on 2021-02-03 +version = "7.x-2.70" core = "7.x" project = "commerce_kickstart" -datestamp = "1592498718" +datestamp = "1612329185" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_migrate/commerce_kickstart_migrate.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_migrate/commerce_kickstart_migrate.info index 57f02815128..6ff69e09fec 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_migrate/commerce_kickstart_migrate.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_migrate/commerce_kickstart_migrate.info @@ -8,8 +8,8 @@ dependencies[] = commerce_migrate files[] = commerce_kickstart_migrate.migrate.inc files[] = plugins/destinations/fields.inc -; Information added by Drupal.org packaging script on 2020-06-18 -version = "7.x-2.66" +; Information added by Drupal.org packaging script on 2021-02-03 +version = "7.x-2.70" core = "7.x" project = "commerce_kickstart" -datestamp = "1592498718" +datestamp = "1612329185" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_order/commerce_kickstart_order.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_order/commerce_kickstart_order.info index 21065c2acc8..408c9019e63 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_order/commerce_kickstart_order.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_order/commerce_kickstart_order.info @@ -8,8 +8,8 @@ dependencies[] = commerce_message dependencies[] = commerce_cart dependencies[] = ctools -; Information added by Drupal.org packaging script on 2020-06-18 -version = "7.x-2.66" +; Information added by Drupal.org packaging script on 2021-02-03 +version = "7.x-2.70" core = "7.x" project = "commerce_kickstart" -datestamp = "1592498718" +datestamp = "1612329185" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_product/commerce_kickstart_product.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_product/commerce_kickstart_product.info index fabba4ef346..1e41e17afb1 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_product/commerce_kickstart_product.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_product/commerce_kickstart_product.info @@ -230,8 +230,8 @@ features_exclude[field_base][field_image] = field_image features_exclude[field_base][title_field] = title_field files[] = commerce_kickstart_product.migrate.inc -; Information added by Drupal.org packaging script on 2020-06-18 -version = "7.x-2.66" +; Information added by Drupal.org packaging script on 2021-02-03 +version = "7.x-2.70" core = "7.x" project = "commerce_kickstart" -datestamp = "1592498718" +datestamp = "1612329185" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_product_ui/commerce_kickstart_product_ui.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_product_ui/commerce_kickstart_product_ui.info index e21e835341c..e24044aa265 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_product_ui/commerce_kickstart_product_ui.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_product_ui/commerce_kickstart_product_ui.info @@ -7,8 +7,8 @@ dependencies[] = commerce_product dependencies[] = views dependencies[] = libraries -; Information added by Drupal.org packaging script on 2020-06-18 -version = "7.x-2.66" +; Information added by Drupal.org packaging script on 2021-02-03 +version = "7.x-2.70" core = "7.x" project = "commerce_kickstart" -datestamp = "1592498718" +datestamp = "1612329185" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_reset/commerce_kickstart_reset.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_reset/commerce_kickstart_reset.info index b0ac148c1c9..afba6fbc2e6 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_reset/commerce_kickstart_reset.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_reset/commerce_kickstart_reset.info @@ -2,8 +2,8 @@ name = Commerce Kickstart Reset core = 7.x package = Commerce Kickstart -; Information added by Drupal.org packaging script on 2020-06-18 -version = "7.x-2.66" +; Information added by Drupal.org packaging script on 2021-02-03 +version = "7.x-2.70" core = "7.x" project = "commerce_kickstart" -datestamp = "1592498718" +datestamp = "1612329185" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_search/commerce_kickstart_search.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_search/commerce_kickstart_search.info index 2545543b6be..37fc6e478f1 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_search/commerce_kickstart_search.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_search/commerce_kickstart_search.info @@ -15,8 +15,8 @@ dependencies[] = search_api_views dependencies[] = views scripts[] = commerce_kickstart_search.js -; Information added by Drupal.org packaging script on 2020-06-18 -version = "7.x-2.66" +; Information added by Drupal.org packaging script on 2021-02-03 +version = "7.x-2.70" core = "7.x" project = "commerce_kickstart" -datestamp = "1592498718" +datestamp = "1612329185" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_slideshow/commerce_kickstart_slideshow.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_slideshow/commerce_kickstart_slideshow.info index adf15f312f1..1c4f5271267 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_slideshow/commerce_kickstart_slideshow.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_slideshow/commerce_kickstart_slideshow.info @@ -50,8 +50,8 @@ features_exclude[field_base][field_image] = field_image features_exclude[field_base][title_field] = title_field files[] = commerce_kickstart_slideshow.migrate.inc -; Information added by Drupal.org packaging script on 2020-06-18 -version = "7.x-2.66" +; Information added by Drupal.org packaging script on 2021-02-03 +version = "7.x-2.70" core = "7.x" project = "commerce_kickstart" -datestamp = "1592498718" +datestamp = "1612329185" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_social/commerce_kickstart_social.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_social/commerce_kickstart_social.info index c86660bbd1c..c3e249471b3 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_social/commerce_kickstart_social.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_social/commerce_kickstart_social.info @@ -32,8 +32,8 @@ features[variable][] = service_links_show features[variable][] = service_links_style features[variable][] = service_links_visibility_for_node -; Information added by Drupal.org packaging script on 2020-06-18 -version = "7.x-2.66" +; Information added by Drupal.org packaging script on 2021-02-03 +version = "7.x-2.70" core = "7.x" project = "commerce_kickstart" -datestamp = "1592498718" +datestamp = "1612329185" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_taxonomy/commerce_kickstart_taxonomy.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_taxonomy/commerce_kickstart_taxonomy.info index e1f46b49a48..10635855552 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_taxonomy/commerce_kickstart_taxonomy.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_taxonomy/commerce_kickstart_taxonomy.info @@ -9,8 +9,8 @@ dependencies[] = views ; Views files files[] = includes/views/handlers/commerce_kickstart_taxonomy_handler_field_text.inc -; Information added by Drupal.org packaging script on 2020-06-18 -version = "7.x-2.66" +; Information added by Drupal.org packaging script on 2021-02-03 +version = "7.x-2.70" core = "7.x" project = "commerce_kickstart" -datestamp = "1592498718" +datestamp = "1612329185" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_user/commerce_kickstart_user.info b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_user/commerce_kickstart_user.info index c0d7cc533dd..87551d6b9cc 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_user/commerce_kickstart_user.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/commerce_kickstart_user/commerce_kickstart_user.info @@ -39,8 +39,8 @@ features[variable][] = user_email_verification features[variable][] = user_pictures features[variable][] = user_register -; Information added by Drupal.org packaging script on 2020-06-18 -version = "7.x-2.66" +; Information added by Drupal.org packaging script on 2021-02-03 +version = "7.x-2.70" core = "7.x" project = "commerce_kickstart" -datestamp = "1592498718" +datestamp = "1612329185" diff --git a/profiles/commerce_kickstart/modules/commerce_kickstart/toolbar_megamenu/toolbar_megamenu.info b/profiles/commerce_kickstart/modules/commerce_kickstart/toolbar_megamenu/toolbar_megamenu.info index 502690fb881..d2c5b31c1cc 100644 --- a/profiles/commerce_kickstart/modules/commerce_kickstart/toolbar_megamenu/toolbar_megamenu.info +++ b/profiles/commerce_kickstart/modules/commerce_kickstart/toolbar_megamenu/toolbar_megamenu.info @@ -3,8 +3,8 @@ description = "Extension of Toolbar to support dropdown megamenus." core = 7.x dependencies[] = toolbar -; Information added by Drupal.org packaging script on 2020-06-18 -version = "7.x-2.66" +; Information added by Drupal.org packaging script on 2021-02-03 +version = "7.x-2.70" core = "7.x" project = "commerce_kickstart" -datestamp = "1592498718" +datestamp = "1612329185" diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/README.txt b/profiles/commerce_kickstart/modules/contrib/ctools/README.txt new file mode 100644 index 00000000000..abdd913e9f3 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/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/profiles/commerce_kickstart/modules/contrib/ctools/bulk_export/bulk_export.info b/profiles/commerce_kickstart/modules/contrib/ctools/bulk_export/bulk_export.info index 9bb3394450f..db788bed934 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/bulk_export/bulk_export.info +++ b/profiles/commerce_kickstart/modules/contrib/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 2021-01-30 +version = "7.x-1.19" core = "7.x" project = "ctools" -datestamp = "1549603691" +datestamp = "1611988843" diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools.api.php b/profiles/commerce_kickstart/modules/contrib/ctools/ctools.api.php index 4481291a8b8..0948e8798ad 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools.api.php +++ b/profiles/commerce_kickstart/modules/contrib/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/profiles/commerce_kickstart/modules/contrib/ctools/ctools.info b/profiles/commerce_kickstart/modules/contrib/ctools/ctools.info index 188b55dde8b..3a6169ca433 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools.info +++ b/profiles/commerce_kickstart/modules/contrib/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 2021-01-30 +version = "7.x-1.19" core = "7.x" project = "ctools" -datestamp = "1549603691" +datestamp = "1611988843" diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools.module b/profiles/commerce_kickstart/modules/contrib/ctools/ctools.module index 62f29192304..2ec0ab73f00 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools.module +++ b/profiles/commerce_kickstart/modules/contrib/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/profiles/commerce_kickstart/modules/contrib/ctools/ctools_access_ruleset/ctools_access_ruleset.info b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_access_ruleset/ctools_access_ruleset.info index 3fc355649a5..380658093f9 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_access_ruleset/ctools_access_ruleset.info +++ b/profiles/commerce_kickstart/modules/contrib/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 2021-01-30 +version = "7.x-1.19" core = "7.x" project = "ctools" -datestamp = "1549603691" +datestamp = "1611988843" diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_ajax_sample/ctools_ajax_sample.info b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_ajax_sample/ctools_ajax_sample.info index c2a52df408b..b230b44a04a 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_ajax_sample/ctools_ajax_sample.info +++ b/profiles/commerce_kickstart/modules/contrib/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 2021-01-30 +version = "7.x-1.19" core = "7.x" project = "ctools" -datestamp = "1549603691" +datestamp = "1611988843" diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_ajax_sample/ctools_ajax_sample.module b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_ajax_sample/ctools_ajax_sample.module index 7cb1ff1c652..c022fdb6160 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_ajax_sample/ctools_ajax_sample.module +++ b/profiles/commerce_kickstart/modules/contrib/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 .= '

' . t('CTools Javascript Widgets') . '

'; diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_custom_content/ctools_custom_content.info b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_custom_content/ctools_custom_content.info index 2bc73489c77..c960e774aee 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_custom_content/ctools_custom_content.info +++ b/profiles/commerce_kickstart/modules/contrib/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 2021-01-30 +version = "7.x-1.19" core = "7.x" project = "ctools" -datestamp = "1549603691" +datestamp = "1611988843" diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/ctools_plugin_example.info b/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/ctools_plugin_example.info index 3f268dbc0ca..19574e5000f 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/ctools_plugin_example/ctools_plugin_example.info +++ b/profiles/commerce_kickstart/modules/contrib/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 2021-01-30 +version = "7.x-1.19" core = "7.x" project = "ctools" -datestamp = "1549603691" +datestamp = "1611988843" diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/context.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/context.inc index 0fc62080670..e933bec5e06 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/context.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/context.inc @@ -1416,16 +1416,12 @@ function ctools_context_get_context_from_relationships($relationships, &$context if (is_array($rdata['context'])) { $rcontexts = array(); foreach ($rdata['context'] as $cid) { - if (empty($contexts[$cid])) { - continue 2; + if (!empty($contexts[$cid])) { + $rcontexts[] = $contexts[$cid]; } - $rcontexts[] = $contexts[$cid]; } } - else { - if (empty($contexts[$rdata['context']])) { - continue; - } + elseif (!empty($contexts[$rdata['context']])) { $rcontexts = $contexts[$rdata['context']]; } diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/export-ui.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/export-ui.inc index 72e5a2fdf16..1158ac8f626 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/export-ui.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/export-ui.inc @@ -456,18 +456,20 @@ function ctools_export_ui_switcher_page($plugin_name, $op) { // Load the $plugin information. $plugin = ctools_get_export_ui($plugin_name); - $handler = ctools_export_ui_get_handler($plugin); - - if ($handler) { - $method = $op . '_page'; - if (method_exists($handler, $method)) { - // Replace the first two arguments: - $args[0] = $js; - $args[1] = $_POST; - return call_user_func_array(array($handler, $method), $args); - } + if (!$plugin) { + return t('Configuration error. No plugin found: %plugin_name.', array('%plugin_name' => $plugin_name)); } - else { + + $handler = ctools_export_ui_get_handler($plugin); + if (!$handler) { return t('Configuration error. No handler found.'); } + + $method = $op . '_page'; + if (method_exists($handler, $method)) { + // Replace the first two arguments: + $args[0] = $js; + $args[1] = $_POST; + return call_user_func_array(array($handler, $method), $args); + } } diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/export.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/export.inc index d2fff2fdc02..efc356efb0a 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/export.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/export.inc @@ -637,11 +637,19 @@ function ctools_get_default_object($table, $name) { } /** + * Get export object defaults. + * * Call the hook to get all default objects of the given type from the * export. If configured properly, this could include loading up an API * to get default objects. + * + * @param string $table + * The name of the table to be loaded. Data is expected to be in the + * schema to make all this work. + * @param array $export + * The export definition from the table's hook_schema() definition. */ -function _ctools_export_get_defaults($table, $export) { +function _ctools_export_get_defaults($table, array $export) { $cache = &drupal_static(__FUNCTION__, array()); // If defaults may be cached, first see if we can load from cache. @@ -684,7 +692,8 @@ function _ctools_export_get_defaults($table, $export) { $cache[$table][$name] = $object; } else { - // If version checking is enabled, ensure that the object can be used. + // If version checking is enabled, ensure that the object can be + // used. if (isset($object->api_version) && version_compare($object->api_version, $export['api']['minimum_version']) >= 0 && version_compare($object->api_version, $export['api']['current_version']) <= 0) { @@ -866,6 +875,7 @@ function ctools_var_export($var, $prefix = '') { } else { $output = "array(\n"; + ksort($var); foreach ($var as $key => $value) { $output .= $prefix . " " . ctools_var_export($key) . " => " . ctools_var_export($value, $prefix . ' ') . ",\n"; } diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/fields.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/fields.inc index 0d3256f2df3..75e964f1de8 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/fields.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/fields.inc @@ -238,8 +238,6 @@ function ctools_field_label($field_name) { function ctools_field_invoke_field($field_name, $op, $entity_type, $entity, &$a = NULL, &$b = NULL, $options = array()) { if (is_array($field_name)) { $instance = $field_name; - $field = empty($field_name['field']) ? field_info_field($instance['field_name']) : $field_name['field']; - $field_name = $instance['field_name']; } else { list(, , $bundle) = entity_extract_ids($entity_type, $entity); @@ -250,6 +248,11 @@ function ctools_field_invoke_field($field_name, $op, $entity_type, $entity, &$a return; } + // Keep the variables consistent regardless if we retrieve the field instance + // ourself, or if one is provided to us via the $field_name variable. + $field = field_info_field($instance['field_name']); + $field_name = $instance['field_name']; + // Merge default options. $default_options = array( 'default' => FALSE, diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/includes/math-expr.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/math-expr.inc index d82281e9f3c..eb90719d617 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/math-expr.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/math-expr.inc @@ -1,391 +1,933 @@ - -================================================================================ - -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 + * + * ============================================================================= + * + * 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; $iv)) { - $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/profiles/commerce_kickstart/modules/contrib/ctools/includes/modal.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/modal.inc index 18b216fc458..30eadbf40b7 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/modal.inc +++ b/profiles/commerce_kickstart/modules/contrib/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/profiles/commerce_kickstart/modules/contrib/ctools/includes/object-cache.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/object-cache.inc index ef52f9aded1..34c26623a0b 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/object-cache.inc +++ b/profiles/commerce_kickstart/modules/contrib/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/profiles/commerce_kickstart/modules/contrib/ctools/includes/page-wizard.inc b/profiles/commerce_kickstart/modules/contrib/ctools/includes/page-wizard.inc index 9690aae07ef..d45d80421e1 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/includes/page-wizard.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/includes/page-wizard.inc @@ -97,7 +97,7 @@ function page_manager_page_wizard($name, $step = NULL) { } // Check for possibly more complex access callback on plugin. - if ($function = ctools_plugin_get_function($plugin, 'access callback') && !$function($plugin)) { + if (($function = ctools_plugin_get_function($plugin, 'access callback')) && !$function($plugin)) { return MENU_ACCESS_DENIED; } diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/js/ajax-responder.js b/profiles/commerce_kickstart/modules/contrib/ctools/js/ajax-responder.js index 1cad618efbd..116d4744f56 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/js/ajax-responder.js +++ b/profiles/commerce_kickstart/modules/contrib/ctools/js/ajax-responder.js @@ -26,7 +26,7 @@ // Grab all the links that match this url and add the fetching class. // This allows the caching system to grab each url once and only once // instead of grabbing the url once per . - var $objects = $('a[href="' + old_url + '"]') + var $objects = $('a[href="' + old_url + '"]'); $objects.addClass('ctools-fetching'); try { url = old_url.replace(/\/nojs(\/|$)/g, '/ajax$1'); @@ -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/profiles/commerce_kickstart/modules/contrib/ctools/js/auto-submit.js b/profiles/commerce_kickstart/modules/contrib/ctools/js/auto-submit.js index b658577a472..0e97e2eb5a8 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/js/auto-submit.js +++ b/profiles/commerce_kickstart/modules/contrib/ctools/js/auto-submit.js @@ -98,5 +98,5 @@ Drupal.behaviors.CToolsAutoSubmit = { }); }); } -} +}; })(jQuery); diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/js/collapsible-div.js b/profiles/commerce_kickstart/modules/contrib/ctools/js/collapsible-div.js index 134151c3d09..4719d7cc4f4 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/js/collapsible-div.js +++ b/profiles/commerce_kickstart/modules/contrib/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,15 +190,15 @@ 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); } } - } + }; 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); } } @@ -222,7 +222,7 @@ } return false; - } + }; // Let both the toggle and the handle be clickable. toggle.click(clickMe); @@ -237,5 +237,5 @@ attach: function(context) { $('.ctools-collapsible-container', context).once('ctools-collapsible', Drupal.CTools.bindCollapsible); } - } + }; })(jQuery); diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/js/dependent.js b/profiles/commerce_kickstart/modules/contrib/ctools/js/dependent.js index a60fc1201cc..6e4b796706f 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/js/dependent.js +++ b/profiles/commerce_kickstart/modules/contrib/ctools/js/dependent.js @@ -14,7 +14,7 @@ * - Checkboxes don't have their own id, so you need to add one in a div * around the checkboxes via #prefix and #suffix. You actually need to add TWO * divs because it's the parent that gets hidden. Also be sure to retain the - * 'expand_checkboxes' in the #process array, because the CTools process will + * 'form_process_checkboxes' in the #process array, because the CTools process will * override it. */ @@ -34,12 +34,12 @@ } } return false; - } + }; 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; } @@ -118,7 +118,7 @@ } } return val; - } + }; var setChangeTrigger = function(trigger_id, bind_id) { // Triggered when change() is clicked. @@ -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++; } @@ -205,7 +205,7 @@ } } } - } + }; $(trigger_id).bind('change.ctools-dependent', function() { // Trigger the internal change function @@ -214,11 +214,11 @@ }); // Trigger initial reaction changeTrigger(trigger_id, bind_id); - } + }; setChangeTrigger(trigger_id, bind_id); } } - } + }; Drupal.behaviors.CToolsDependent = { attach: function (context) { @@ -240,5 +240,5 @@ }) .trigger('change.ctools-dependent'); } - } + }; })(jQuery); diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/js/dropbutton.js b/profiles/commerce_kickstart/modules/contrib/ctools/js/dropbutton.js index f505550b6cc..6d08d05a59e 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/js/dropbutton.js +++ b/profiles/commerce_kickstart/modules/contrib/ctools/js/dropbutton.js @@ -69,7 +69,7 @@ $secondaryActions.animate({height: "show", opacity: "show"}, 100); $dropbutton.addClass('open'); } - } + }; // Hide the secondary actions initially. $secondaryActions.hide(); @@ -90,5 +90,5 @@ ); }); } - } + }; })(jQuery); diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/js/dropdown.js b/profiles/commerce_kickstart/modules/contrib/ctools/js/dropdown.js index c829ae2fe13..e2488b1ea46 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/js/dropdown.js +++ b/profiles/commerce_kickstart/modules/contrib/ctools/js/dropdown.js @@ -61,7 +61,7 @@ $("div.ctools-dropdown-container", $dropdown) .animate({height: "show", opacity: "show"}, 100); } - } + }; $("a.ctools-dropdown-link", $dropdown).click(function() { toggle(); return false; @@ -83,5 +83,5 @@ ); }); } - } + }; })(jQuery); diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/js/jump-menu.js b/profiles/commerce_kickstart/modules/contrib/ctools/js/jump-menu.js index 7b0928a68e7..14852d5adf8 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/js/jump-menu.js +++ b/profiles/commerce_kickstart/modules/contrib/ctools/js/jump-menu.js @@ -38,5 +38,5 @@ return false; }); } - } + }; })(jQuery); diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/js/modal.js b/profiles/commerce_kickstart/modules/contrib/ctools/js/modal.js index ec7b02aea37..92f8d78608b 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/js/modal.js +++ b/profiles/commerce_kickstart/modules/contrib/ctools/js/modal.js @@ -86,7 +86,7 @@ 'width': (width - Drupal.CTools.Modal.currentSettings.modalSize.contentRight) + 'px', 'height': (height - Drupal.CTools.Modal.currentSettings.modalSize.contentBottom) + 'px' }); - } + }; if (!Drupal.CTools.Modal.modal) { Drupal.CTools.Modal.modal = $(Drupal.theme(settings.modalTheme)); @@ -120,9 +120,9 @@ * Provide the HTML to create the modal dialog. */ Drupal.theme.prototype.CToolsModalDialog = function () { - var html = '' - html += '
' - html += '
' // panels-modal-content + var html = ''; + html += '
'; + html += '
'; // panels-modal-content html += ' '; return html; - } + }; /** * Provide the HTML to create the throbber. @@ -159,7 +159,7 @@ if (match) { return match[1]; } - } + }; /** * Click function for modals that can be cached. @@ -186,7 +186,7 @@ setTimeout(function() { Drupal.CTools.AJAX.ajaxSubmit($form, url); }, 1); return false; - } + }; /** * Bind links that will open modals to the appropriate function. @@ -250,7 +250,7 @@ element_settings.url = $this.attr('action'); element_settings.event = 'submit'; - element_settings.progress = { 'type': 'throbber' } + element_settings.progress = { 'type': 'throbber' }; var base = $this.attr('id'); Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings); @@ -291,17 +291,23 @@ * AJAX responder command to place HTML within the modal. */ Drupal.CTools.Modal.modal_display = function(ajax, response, status) { + var settings = response.settings || ajax.settings || Drupal.settings; + // If the modal does not exist yet, create it. if ($('#modalContent').length == 0) { Drupal.CTools.Modal.show(Drupal.CTools.Modal.getSettings(ajax.element)); } + // If the modal exists run detachBehaviors before removing existing content. + else { + Drupal.detachBehaviors($('#modalContent'), settings, 'unload'); + } $('#modal-title').html(response.title); // Simulate an actual page load by scrolling to the top after adding the // 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; Drupal.attachBehaviors($('#modalContent'), settings); if ($('#modal-content').hasClass('ctools-modal-loading')) { @@ -314,7 +320,7 @@ // button by the show() function called above.) $('#modal-content :focusable:first').focus(); } - } + }; /** * AJAX responder command to dismiss the modal. @@ -322,7 +328,7 @@ Drupal.CTools.Modal.modal_dismiss = function(command) { Drupal.CTools.Modal.dismiss(); $('link.ctools-temporary-css').remove(); - } + }; /** * Display loading @@ -333,7 +339,7 @@ output: Drupal.theme(Drupal.CTools.Modal.currentSettings.throbberTheme), title: Drupal.CTools.Modal.currentSettings.loadingText }); - } + }; /** * Find a URL for an AJAX button. @@ -589,6 +595,7 @@ $('body').unbind( 'keydown', modalTabTrapHandler ); $('.close', $modalHeader).unbind('click', modalContentClose); $(document).unbind('keydown', modalEventEscapeCloseHandler); + $(document).trigger('CToolsCloseModalBehaviors', $('#modalContent')); $(document).trigger('CToolsDetachBehaviors', $('#modalContent')); // Closing animation. @@ -674,7 +681,7 @@ var $modalContent = $('#modalContent'); var $modalHeader = $modalContent.find('.modal-header'); $('.close', $modalHeader).unbind('click', modalContentClose); - $('body').unbind('keypress', modalEventEscapeCloseHandler); + $(document).unbind('keydown', modalEventEscapeCloseHandler); $(document).trigger('CToolsDetachBehaviors', $modalContent); // jQuery magic loop through the instances and run the animations or removal. diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/js/stylizer.js b/profiles/commerce_kickstart/modules/contrib/ctools/js/stylizer.js index 12ab7204808..227d4f4be80 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/js/stylizer.js +++ b/profiles/commerce_kickstart/modules/contrib/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); } } @@ -180,7 +180,7 @@ ); $(this).after(lock); locks.push(lock); - }; + } // Add hook var $this = $(this); @@ -216,5 +216,5 @@ $widget.attr('checked', !$widget.attr('checked') || $widget.is('input[type=radio]')); }); } - } + }; })(jQuery); diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/page_manager/page_manager.info b/profiles/commerce_kickstart/modules/contrib/ctools/page_manager/page_manager.info index a56663b8d31..06720486aa6 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/page_manager/page_manager.info +++ b/profiles/commerce_kickstart/modules/contrib/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 2021-01-30 +version = "7.x-1.19" core = "7.x" project = "ctools" -datestamp = "1549603691" +datestamp = "1611988843" diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/page_manager/page_manager.module b/profiles/commerce_kickstart/modules/contrib/ctools/page_manager/page_manager.module index c614eff7ddf..ed2066b46fc 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/page_manager/page_manager.module +++ b/profiles/commerce_kickstart/modules/contrib/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/profiles/commerce_kickstart/modules/contrib/ctools/page_manager/plugins/tasks/node_edit.inc b/profiles/commerce_kickstart/modules/contrib/ctools/page_manager/plugins/tasks/node_edit.inc index 66bf00da977..2d27449182e 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/page_manager/plugins/tasks/node_edit.inc +++ b/profiles/commerce_kickstart/modules/contrib/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('Edit @type @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/profiles/commerce_kickstart/modules/contrib/ctools/page_manager/plugins/tasks/search.inc b/profiles/commerce_kickstart/modules/contrib/ctools/page_manager/plugins/tasks/search.inc index 172d963ab2b..e561c71791a 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/page_manager/plugins/tasks/search.inc +++ b/profiles/commerce_kickstart/modules/contrib/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/profiles/commerce_kickstart/modules/contrib/ctools/page_manager/plugins/tasks/term_view.inc b/profiles/commerce_kickstart/modules/contrib/ctools/page_manager/plugins/tasks/term_view.inc index 72fb7dbc95d..101b6744259 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/page_manager/plugins/tasks/term_view.inc +++ b/profiles/commerce_kickstart/modules/contrib/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 taxonomy/term/%term. 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 taxonomy/term/%term. 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/profiles/commerce_kickstart/modules/contrib/ctools/plugins/access/node.inc b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/access/node.inc deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/plugins/access/node_status.inc b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/access/node_status.inc index 137f2826c6f..519619bec0a 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/plugins/access/node_status.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/access/node_status.inc @@ -21,7 +21,7 @@ $plugin = array( * Check for access. */ function ctools_node_status_ctools_access_check($conf, $context) { - return (!empty($context->data) && $context->data->status); + return (!empty($context->data->status) && $context->data->status); } /** diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/plugins/access/term.inc b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/access/term.inc index 50ec0d90f66..224a00c6f84 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/plugins/access/term.inc +++ b/profiles/commerce_kickstart/modules/contrib/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/profiles/commerce_kickstart/modules/contrib/ctools/plugins/access/term_has_parent.inc b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/access/term_has_parent.inc index 16f4fbf6929..fd9351d9cac 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/plugins/access/term_has_parent.inc +++ b/profiles/commerce_kickstart/modules/contrib/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/profiles/commerce_kickstart/modules/contrib/ctools/plugins/access/term_parent.inc b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/access/term_parent.inc index 27375dfaf11..0fddc020df1 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/plugins/access/term_parent.inc +++ b/profiles/commerce_kickstart/modules/contrib/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/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/block/block.inc b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/block/block.inc index ff75c750458..ee5ce576417 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/block/block.inc +++ b/profiles/commerce_kickstart/modules/contrib/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/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/form/entity_form_field.inc b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/form/entity_form_field.inc index b4d83381384..f2f1e7bd6a0 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/form/entity_form_field.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/form/entity_form_field.inc @@ -152,6 +152,12 @@ function ctools_entity_form_field_content_type_render($subtype, $conf, $panel_ar * Returns the administrative title for a type. */ function ctools_entity_form_field_content_type_admin_title($subtype, $conf, $context) { + // Return early because we don't have context to build this field from. + if (!$context || !isset($context->identifier)) { + watchdog('ctools_entity_form_field_content_type_admin_title', 'Context is missing for field: @name', array('@name' => $subtype), WATCHDOG_NOTICE); + return t('Deleted/missing field @name', array('@name' => $subtype)); + } + list($entity_type, $field_name) = explode(':', $subtype, 2); if (!empty($context->restrictions)) { diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/icon_user_form.png b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/icon_user_form.png new file mode 100644 index 00000000000..f0417cb6515 Binary files /dev/null and b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/icon_user_form.png differ diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/user_form_actions.inc b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/user_form_actions.inc new file mode 100644 index 00000000000..e11587d81f7 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/user_form_actions.inc @@ -0,0 +1,65 @@ + TRUE, + 'icon' => 'icon_user_form.png', + 'title' => t('User form actions / buttons'), + 'description' => t('The user form actions / buttons.'), + 'required context' => new ctools_context_required(t('Form'), 'form'), + 'category' => t('Form'), +); + +/** + * Ctools plugin content type render for the picture form field. + */ +function ctools_user_form_actions_content_type_render($subtype, $conf, $panel_args, &$context) { + $block = new stdClass(); + $block->module = t('user-form'); + + $block->delta = 'title-options'; + + if (isset($context->form)) { + if (!empty($context->form['actions'])) { + $block->content['actions'] = $context->form['actions']; + unset($context->form['actions']); + } + // Because we are adding the submit buttons outside the General form + // we can assume the necessary hidden components should be added as well. + if (!empty($context->form['form_build_id'])) { + $block->content['form_build_id'] = $context->form['form_build_id']; + unset($context->form['form_build_id']); + } + if (!empty($context->form['form_token'])) { + $block->content['form_token'] = $context->form['form_token']; + unset($context->form['form_token']); + } + if (!empty($context->form['form_id'])) { + $block->content['form_id'] = $context->form['form_id']; + unset($context->form['form_id']); + } + } + else { + $block->content = t('User actions / buttons form components.'); + } + return $block; +} + +/** + * Ctools plugin admin title function for the actions form field. + */ +function ctools_user_form_actions_content_type_admin_title($subtype, $conf, $context) { + return t('"@s" user form actions / buttons field', array('@s' => $context->identifier)); +} + +/** + * Ctools plugin configuration edit form for the actions form field. + * + * Provide a blank form so we have a place to have context setting. + */ +function ctools_user_form_actions_content_type_edit_form($form, &$form_state) { + return $form; +} diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/user_form_component.inc b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/user_form_component.inc new file mode 100644 index 00000000000..b4963f99b76 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/user_form_component.inc @@ -0,0 +1,72 @@ + TRUE, + 'icon' => 'icon_user_form.png', + 'title' => t('User form: add a specific component'), + 'description' => t('The user form component by selection.'), + 'required context' => new ctools_context_required(t('Form'), 'form'), + 'category' => t('Form'), +); + +/** + * Ctools plugin content type render for the picture form field. + */ +function ctools_user_form_component_content_type_render($subtype, $conf, $panel_args, &$context) { + $block = new stdClass(); + $block->module = t('user-form'); + + $block->delta = 'title-options'; + + if (isset($context->form)) { + if (!empty($context->form[$conf['field']])) { + $block->content[$conf['field']] = $context->form[$conf['field']]; + unset($context->form[$conf['field']]); + } + } + else { + $block->content = t('User form edit components.'); + } + return $block; +} + +/** + * Ctools plugin admin title function for the a selectable form field. + */ +function ctools_user_form_component_content_type_admin_title($subtype, $conf, $context) { + return t('"@s" user form @field field', array('@s' => $context->identifier, '@field' => $conf['field'])); +} + +/** + * Ctools plugin configuration edit form for the selectable form field. + * + * Provide the list of fields in the user profile edit form to select from the + * plugin configuration. + */ +function ctools_user_form_component_content_type_edit_form($form, &$form_state) { + $conf = $form_state['conf']; + $user_form = drupal_get_form('user_profile_form'); + + $field_keys = element_children($user_form); + $options = array_combine($field_keys, $field_keys); + + $form['field'] = array( + '#type' => 'select', + '#title' => t('User form field'), + '#options' => $options, + '#description' => t('Select a form field from the current user form to display in this pane.'), + '#default_value' => !empty($conf['field']) ? $conf['field'] : '', + ); + return $form; +} + +/** + * Ctools plugin configuration edit form submit handler. + */ +function ctools_user_form_component_content_type_edit_form_submit($form, &$form_state) { + $form_state['conf']['field'] = $form_state['values']['field']; +} diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/user_form_email.inc b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/user_form_email.inc new file mode 100644 index 00000000000..346555d0937 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/user_form_email.inc @@ -0,0 +1,56 @@ + TRUE, + 'icon' => 'icon_user_form.png', + 'title' => t('User form email field'), + 'description' => t('The user email form.'), + 'required context' => new ctools_context_required(t('Form'), 'form'), + 'category' => t('Form'), +); + +/** + * Ctools plugin content type render for the email form field. + */ +function ctools_user_form_email_content_type_render($subtype, $conf, $panel_args, &$context) { + $block = new stdClass(); + $block->module = t('user-form'); + + $block->delta = 'title-options'; + + if (isset($context->form)) { + // The current password is required to change the email. + if (!empty($context->form['account']['current_pass'])) { + $block->content['current_pass'] = $context->form['account']['current_pass']; + unset($context->form['account']['current_pass']); + } + if (!empty($context->form['account']['mail'])) { + $block->content['mail'] = $context->form['account']['mail']; + unset($context->form['account']['mail']); + } + } + else { + $block->content = t('User email form.'); + } + return $block; +} + +/** + * Ctools plugin admin title function for the email form field. + */ +function ctools_user_form_email_content_type_admin_title($subtype, $conf, $context) { + return t('"@s" user form email field', array('@s' => $context->identifier)); +} + +/** + * Ctools plugin configuration edit form for the email form field. + * + * Provide a blank form so we have a place to have context setting. + */ +function ctools_user_form_email_content_type_edit_form($form, &$form_state) { + return $form; +} diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/user_form_notify.inc b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/user_form_notify.inc new file mode 100644 index 00000000000..bb74963c192 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/user_form_notify.inc @@ -0,0 +1,51 @@ + TRUE, + 'icon' => 'icon_user_form.png', + 'title' => t('User form notify field'), + 'description' => t('The user notify form.'), + 'required context' => new ctools_context_required(t('Form'), 'form'), + 'category' => t('Form'), +); + +/** + * Ctools plugin content type render for the notify form field. + */ +function ctools_user_form_notify_content_type_render($subtype, $conf, $panel_args, &$context) { + $block = new stdClass(); + $block->module = t('user-form'); + + $block->delta = 'title-options'; + + if (isset($context->form)) { + if (!empty($context->form['account']['notify'])) { + $block->content['notify'] = $context->form['account']['notify']; + unset($context->form['account']['notify']); + } + } + else { + $block->content = t('User notify form.'); + } + return $block; +} + +/** + * Ctools plugin admin title function for the notify form field. + */ +function ctools_user_form_notify_content_type_admin_title($subtype, $conf, $context) { + return t('"@s" user form notify field', array('@s' => $context->identifier)); +} + +/** + * Ctools plugin configuration edit form for the notify form field. + * + * Provide a blank form so we have a place to have context setting. + */ +function ctools_user_form_notify_content_type_edit_form($form, &$form_state) { + return $form; +} diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/user_form_password.inc b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/user_form_password.inc new file mode 100644 index 00000000000..89bab949cbc --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/user_form_password.inc @@ -0,0 +1,56 @@ + TRUE, + 'icon' => 'icon_user_form.png', + 'title' => t('User form password field'), + 'description' => t('The user password form.'), + 'required context' => new ctools_context_required(t('Form'), 'form'), + 'category' => t('Form'), +); + +/** + * Ctools plugin content type render for the password form field. + */ +function ctools_user_form_password_content_type_render($subtype, $conf, $panel_args, &$context) { + $block = new stdClass(); + $block->module = t('user-form'); + + $block->delta = 'title-options'; + + if (isset($context->form)) { + // The current password is required to change the password. + if (!empty($context->form['account']['current_pass'])) { + $block->content['current_pass'] = $context->form['account']['current_pass']; + unset($context->form['account']['current_pass']); + } + if (!empty($context->form['account']['pass'])) { + $block->content['pass'] = $context->form['account']['pass']; + unset($context->form['account']['pass']); + } + } + else { + $block->content = t('User password form.'); + } + return $block; +} + +/** + * Ctools plugin admin title function for the password form field. + */ +function ctools_user_form_password_content_type_admin_title($subtype, $conf, $context) { + return t('"@s" user form password field', array('@s' => $context->identifier)); +} + +/** + * Ctools plugin configuration edit form for the password form field. + * + * Provide a blank form so we have a place to have context setting. + */ +function ctools_user_form_password_content_type_edit_form($form, &$form_state) { + return $form; +} diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/user_form_picture.inc b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/user_form_picture.inc new file mode 100644 index 00000000000..ac310bed785 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/user_form_picture.inc @@ -0,0 +1,51 @@ + TRUE, + 'icon' => 'icon_user_form.png', + 'title' => t('User form picture field'), + 'description' => t('The user picture form.'), + 'required context' => new ctools_context_required(t('Form'), 'form'), + 'category' => t('Form'), +); + +/** + * Ctools plugin content type render for the picture form field. + */ +function ctools_user_form_picture_content_type_render($subtype, $conf, $panel_args, &$context) { + $block = new stdClass(); + $block->module = t('user-form'); + + $block->delta = 'title-options'; + + if (isset($context->form)) { + if (!empty($context->form['picture'])) { + $block->content['picture'] = $context->form['picture']; + unset($context->form['picture']); + } + } + else { + $block->content = t('User picture form.'); + } + return $block; +} + +/** + * Ctools plugin admin title function for the picture form field. + */ +function ctools_user_form_picture_content_type_admin_title($subtype, $conf, $context) { + return t('"@s" user form picture field', array('@s' => $context->identifier)); +} + +/** + * Ctools plugin configuration edit form for the picture form field. + * + * Provide a blank form so we have a place to have context setting. + */ +function ctools_user_form_picture_content_type_edit_form($form, &$form_state) { + return $form; +} diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/user_form_roles.inc b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/user_form_roles.inc new file mode 100644 index 00000000000..47a169f7fdb --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/user_form_roles.inc @@ -0,0 +1,51 @@ + TRUE, + 'icon' => 'icon_user_form.png', + 'title' => t('User form roles field'), + 'description' => t('The user roles form.'), + 'required context' => new ctools_context_required(t('Form'), 'form'), + 'category' => t('Form'), +); + +/** + * Ctools plugin content type render for the roles form field. + */ +function ctools_user_form_roles_content_type_render($subtype, $conf, $panel_args, &$context) { + $block = new stdClass(); + $block->module = t('user-form'); + + $block->delta = 'title-options'; + + if (isset($context->form)) { + if (!empty($context->form['account']['roles'])) { + $block->content['roles'] = $context->form['account']['roles']; + unset($context->form['account']['roles']); + } + } + else { + $block->content = t('User roles form.'); + } + return $block; +} + +/** + * Ctools plugin admin title function for the roles form field. + */ +function ctools_user_form_roles_content_type_admin_title($subtype, $conf, $context) { + return t('"@s" user form roles field', array('@s' => $context->identifier)); +} + +/** + * Ctools plugin configuration edit form for the roles form field. + * + * Provide a blank form so we have a place to have context setting. + */ +function ctools_user_form_roles_content_type_edit_form($form, &$form_state) { + return $form; +} diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/user_form_signature_settings.inc b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/user_form_signature_settings.inc new file mode 100644 index 00000000000..2a69ea09eaa --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/user_form_signature_settings.inc @@ -0,0 +1,51 @@ + TRUE, + 'icon' => 'icon_user_form.png', + 'title' => t('User form signature settings field'), + 'description' => t('The user signature settings form.'), + 'required context' => new ctools_context_required(t('Form'), 'form'), + 'category' => t('Form'), +); + +/** + * Ctools plugin content type render for the signature settings form field. + */ +function ctools_user_form_signature_settings_content_type_render($subtype, $conf, $panel_args, &$context) { + $block = new stdClass(); + $block->module = t('user-form'); + + $block->delta = 'title-options'; + + if (isset($context->form)) { + if (!empty($context->form['signature_settings'])) { + $block->content['signature_settings'] = $context->form['signature_settings']; + unset($context->form['signature_settings']); + } + } + else { + $block->content = t('User signature settings form.'); + } + return $block; +} + +/** + * Ctools plugin admin title function for the signature settings form field. + */ +function ctools_user_form_signature_settings_content_type_admin_title($subtype, $conf, $context) { + return t('"@s" user form signature settings field', array('@s' => $context->identifier)); +} + +/** + * Ctools plugin configuration edit form for the signature settings form field. + * + * Provide a blank form so we have a place to have context setting. + */ +function ctools_user_form_signature_settings_content_type_edit_form($form, &$form_state) { + return $form; +} diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/user_form_status.inc b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/user_form_status.inc new file mode 100644 index 00000000000..7c7f872474f --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/user_form_status.inc @@ -0,0 +1,51 @@ + TRUE, + 'icon' => 'icon_user_form.png', + 'title' => t('User form status field'), + 'description' => t('The user status form.'), + 'required context' => new ctools_context_required(t('Form'), 'form'), + 'category' => t('Form'), +); + +/** + * Ctools plugin content type render for the status form field. + */ +function ctools_user_form_status_content_type_render($subtype, $conf, $panel_args, &$context) { + $block = new stdClass(); + $block->module = t('user-form'); + + $block->delta = 'title-options'; + + if (isset($context->form)) { + if (!empty($context->form['account']['status'])) { + $block->content['status'] = $context->form['account']['status']; + unset($context->form['account']['status']); + } + } + else { + $block->content = t('User status form.'); + } + return $block; +} + +/** + * Ctools plugin admin title function for the status form field. + */ +function ctools_user_form_status_content_type_admin_title($subtype, $conf, $context) { + return t('"@s" user form status field', array('@s' => $context->identifier)); +} + +/** + * Ctools plugin configuration edit form for the status form field. + * + * Provide a blank form so we have a place to have context setting. + */ +function ctools_user_form_status_content_type_edit_form($form, &$form_state) { + return $form; +} diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/user_form_timezone.inc b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/user_form_timezone.inc new file mode 100644 index 00000000000..1a0066488fb --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/user_form_timezone.inc @@ -0,0 +1,51 @@ + TRUE, + 'icon' => 'icon_user_form.png', + 'title' => t('User form timezone field'), + 'description' => t('The user timezone form.'), + 'required context' => new ctools_context_required(t('Form'), 'form'), + 'category' => t('Form'), +); + +/** + * Ctools plugin content type render for the timezone form field. + */ +function ctools_user_form_timezone_content_type_render($subtype, $conf, $panel_args, &$context) { + $block = new stdClass(); + $block->module = t('user-form'); + + $block->delta = 'title-options'; + + if (isset($context->form)) { + if (!empty($context->form['timezone'])) { + $block->content['timezone'] = $context->form['timezone']; + unset($context->form['timezone']); + } + } + else { + $block->content = t('User timezone form.'); + } + return $block; +} + +/** + * Ctools plugin admin title function for the timezone form field. + */ +function ctools_user_form_timezone_content_type_admin_title($subtype, $conf, $context) { + return t('"@s" user form timezone field', array('@s' => $context->identifier)); +} + +/** + * Ctools plugin configuration edit form for the timezone form field. + * + * Provide a blank form so we have a place to have context setting. + */ +function ctools_user_form_timezone_content_type_edit_form($form, &$form_state) { + return $form; +} diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/user_form_username.inc b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/user_form_username.inc new file mode 100644 index 00000000000..d6495308508 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/content_types/user_form/user_form_username.inc @@ -0,0 +1,51 @@ + TRUE, + 'icon' => 'icon_user_form.png', + 'title' => t('User form username field'), + 'description' => t('The user username form.'), + 'required context' => new ctools_context_required(t('Form'), 'form'), + 'category' => t('Form'), +); + +/** + * Ctools plugin content type render for the username form field. + */ +function ctools_user_form_username_content_type_render($subtype, $conf, $panel_args, &$context) { + $block = new stdClass(); + $block->module = t('user-form'); + + $block->delta = 'title-options'; + + if (isset($context->form)) { + if (!empty($context->form['account']['name'])) { + $block->content['name'] = $context->form['account']['name']; + unset($context->form['account']['name']); + } + } + else { + $block->content = t('User username form.'); + } + return $block; +} + +/** + * Ctools plugin admin title function for the username form field. + */ +function ctools_user_form_username_content_type_admin_title($subtype, $conf, $context) { + return t('"@s" user form username field', array('@s' => $context->identifier)); +} + +/** + * Ctools plugin configuration edit form for the username form field. + * + * Provide a blank form so we have a place to have context setting. + */ +function ctools_user_form_username_content_type_edit_form($form, &$form_state) { + return $form; +} diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/plugins/contexts/user.inc b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/contexts/user.inc index bbdbd1a274e..9cd73085e47 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/plugins/contexts/user.inc +++ b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/contexts/user.inc @@ -41,10 +41,13 @@ function ctools_context_create_user($empty, $data = NULL, $conf = FALSE) { if ($conf) { if ($data['type'] == 'current') { global $user; - $data = user_load($user->uid); if (user_is_logged_in()) { + $data = user_load($user->uid); $data->logged_in_user = TRUE; } + else { + $data = drupal_anonymous_user(); + } } else { $data = user_load($data['uid']); diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/plugins/export_ui/ctools_export_ui.class.php b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/export_ui/ctools_export_ui.class.php index 0c8145e4bfb..f78795aadc4 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/plugins/export_ui/ctools_export_ui.class.php +++ b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/export_ui/ctools_export_ui.class.php @@ -310,7 +310,7 @@ public function list_form(&$form, &$form_state) { $form['bottom row']['reset'] = array( '#type' => 'submit', - '#id' => 'ctools-export-ui-list-items-apply', + '#id' => 'ctools-export-ui-list-items-reset', '#value' => t('Reset'), '#attributes' => array('class' => array('use-ajax-submit')), ); diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/plugins/relationships/comment_parent.inc b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/relationships/comment_parent.inc new file mode 100644 index 00000000000..ae29a08ceae --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/ctools/plugins/relationships/comment_parent.inc @@ -0,0 +1,32 @@ + t('Parent comment'), + 'keyword' => 'parent_comment', + 'description' => t('Adds a parent comment from a comment context.'), + 'required context' => new ctools_context_required(t('Comment'), 'entity:comment'), + 'context' => 'ctools_comment_parent_context', +); + +/** + * Return a new context based on an existing context. + */ +function ctools_comment_parent_context($context, $conf) { + if (empty($context->data)) { + return ctools_context_create_empty('entity:comment'); + } + + if (isset($context->data->pid) && ($context->data->pid !== 0)) { + $parent_comment = comment_load($context->data->pid); + return ctools_context_create('entity:comment', $parent_comment); + } +} diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/stylizer/stylizer.info b/profiles/commerce_kickstart/modules/contrib/ctools/stylizer/stylizer.info index 90204a3def5..6b7f70f5e18 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/stylizer/stylizer.info +++ b/profiles/commerce_kickstart/modules/contrib/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 2021-01-30 +version = "7.x-1.19" core = "7.x" project = "ctools" -datestamp = "1549603691" +datestamp = "1611988843" diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/term_depth/plugins/access/term_depth.inc b/profiles/commerce_kickstart/modules/contrib/ctools/term_depth/plugins/access/term_depth.inc index d63183f3283..eb302b6258d 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/term_depth/plugins/access/term_depth.inc +++ b/profiles/commerce_kickstart/modules/contrib/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/profiles/commerce_kickstart/modules/contrib/ctools/term_depth/term_depth.info b/profiles/commerce_kickstart/modules/contrib/ctools/term_depth/term_depth.info index 4baee51d898..5cceeda7a78 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/term_depth/term_depth.info +++ b/profiles/commerce_kickstart/modules/contrib/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 2021-01-30 +version = "7.x-1.19" core = "7.x" project = "ctools" -datestamp = "1549603691" +datestamp = "1611988843" diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/tests/ctools_export_test/ctools_export_test.info b/profiles/commerce_kickstart/modules/contrib/ctools/tests/ctools_export_test/ctools_export_test.info index a2d0efbd92e..6bb6063a39d 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/tests/ctools_export_test/ctools_export_test.info +++ b/profiles/commerce_kickstart/modules/contrib/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 2021-01-30 +version = "7.x-1.19" core = "7.x" project = "ctools" -datestamp = "1549603691" +datestamp = "1611988843" diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/tests/ctools_plugin_test.info b/profiles/commerce_kickstart/modules/contrib/ctools/tests/ctools_plugin_test.info index d51a526148b..5c955bb4130 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/tests/ctools_plugin_test.info +++ b/profiles/commerce_kickstart/modules/contrib/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 2021-01-30 +version = "7.x-1.19" core = "7.x" project = "ctools" -datestamp = "1549603691" +datestamp = "1611988843" diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/tests/math_expression.test b/profiles/commerce_kickstart/modules/contrib/ctools/tests/math_expression.test index b34fafb9caf..023e46a4555 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/tests/math_expression.test +++ b/profiles/commerce_kickstart/modules/contrib/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/profiles/commerce_kickstart/modules/contrib/ctools/views_content/plugins/content_types/views.inc b/profiles/commerce_kickstart/modules/contrib/ctools/views_content/plugins/content_types/views.inc index 9ad7728f4b8..0754d426146 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/views_content/plugins/content_types/views.inc +++ b/profiles/commerce_kickstart/modules/contrib/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/profiles/commerce_kickstart/modules/contrib/ctools/views_content/plugins/content_types/views_panes.inc b/profiles/commerce_kickstart/modules/contrib/ctools/views_content/plugins/content_types/views_panes.inc index 07e93f7a5b0..afc98136b33 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/views_content/plugins/content_types/views_panes.inc +++ b/profiles/commerce_kickstart/modules/contrib/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/profiles/commerce_kickstart/modules/contrib/ctools/views_content/plugins/content_types/views_row.inc b/profiles/commerce_kickstart/modules/contrib/ctools/views_content/plugins/content_types/views_row.inc index 16b97dc73a1..6678f447d0b 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/views_content/plugins/content_types/views_row.inc +++ b/profiles/commerce_kickstart/modules/contrib/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/profiles/commerce_kickstart/modules/contrib/ctools/views_content/plugins/views/views_content_plugin_display_panel_pane.inc b/profiles/commerce_kickstart/modules/contrib/ctools/views_content/plugins/views/views_content_plugin_display_panel_pane.inc index 05d3478f061..af0704f30d8 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/views_content/plugins/views/views_content_plugin_display_panel_pane.inc +++ b/profiles/commerce_kickstart/modules/contrib/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,24 @@ class views_content_plugin_display_panel_pane extends views_plugin_display { return (bool) $conf['more_link']; } + /** + * {@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/profiles/commerce_kickstart/modules/contrib/ctools/views_content/tests/modules/views_content_test.info b/profiles/commerce_kickstart/modules/contrib/ctools/views_content/tests/modules/views_content_test.info new file mode 100644 index 00000000000..336876a858e --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/ctools/views_content/tests/modules/views_content_test.info @@ -0,0 +1,11 @@ +name = Views content panes Test +description = Test module for Views content panes. +package = Views +core = 7.x +dependencies[] = views_content +hidden = TRUE +; Information added by Drupal.org packaging script on 2021-01-30 +version = "7.x-1.19" +core = "7.x" +project = "ctools" +datestamp = "1611988843" diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/views_content/tests/modules/views_content_test.module b/profiles/commerce_kickstart/modules/contrib/ctools/views_content/tests/modules/views_content_test.module new file mode 100644 index 00000000000..52998650a3d --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/ctools/views_content/tests/modules/views_content_test.module @@ -0,0 +1,15 @@ + 3.0, + ); +} diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/views_content/tests/modules/views_content_test.views_default.inc b/profiles/commerce_kickstart/modules/contrib/ctools/views_content/tests/modules/views_content_test.views_default.inc new file mode 100644 index 00000000000..6dd08494c8a --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/ctools/views_content/tests/modules/views_content_test.views_default.inc @@ -0,0 +1,82 @@ +name = 'views_content_more_link'; + $view->description = ''; + $view->tag = 'default'; + $view->base_table = 'node'; + $view->human_name = 'views_content_more_link'; + $view->core = 7; + $view->api_version = '3.0'; + $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */ + + /* Display: Master */ + $handler = $view->new_display('default', 'Master', 'default'); + $handler->display->display_options['title'] = 'views_content_more_link'; + $handler->display->display_options['use_more'] = TRUE; + $handler->display->display_options['use_more_always'] = FALSE; + $handler->display->display_options['access']['type'] = 'perm'; + $handler->display->display_options['cache']['type'] = 'none'; + $handler->display->display_options['query']['type'] = 'views_query'; + $handler->display->display_options['exposed_form']['type'] = 'basic'; + $handler->display->display_options['pager']['type'] = 'none'; + $handler->display->display_options['pager']['options']['offset'] = '0'; + $handler->display->display_options['style_plugin'] = 'table'; + $handler->display->display_options['style_options']['columns'] = array( + 'title' => 'title', + ); + $handler->display->display_options['style_options']['class'] = ''; + $handler->display->display_options['style_options']['default'] = '-1'; + $handler->display->display_options['style_options']['info'] = array( + 'title' => array( + 'sortable' => 0, + 'default_sort_order' => 'asc', + 'align' => '', + 'separator' => '', + 'empty_column' => 0, + ), + ); + /* Field: Content: Title */ + $handler->display->display_options['fields']['title']['id'] = 'title'; + $handler->display->display_options['fields']['title']['table'] = 'node'; + $handler->display->display_options['fields']['title']['field'] = 'title'; + $handler->display->display_options['fields']['title']['label'] = ''; + $handler->display->display_options['fields']['title']['alter']['word_boundary'] = FALSE; + $handler->display->display_options['fields']['title']['alter']['ellipsis'] = FALSE; + /* Sort criterion: Content: Post date */ + $handler->display->display_options['sorts']['created']['id'] = 'created'; + $handler->display->display_options['sorts']['created']['table'] = 'node'; + $handler->display->display_options['sorts']['created']['field'] = 'created'; + $handler->display->display_options['sorts']['created']['order'] = 'DESC'; + /* Filter criterion: Content: Published status */ + $handler->display->display_options['filters']['status']['id'] = 'status'; + $handler->display->display_options['filters']['status']['table'] = 'node'; + $handler->display->display_options['filters']['status']['field'] = 'status'; + $handler->display->display_options['filters']['status']['value'] = 1; + $handler->display->display_options['filters']['status']['group'] = 1; + $handler->display->display_options['filters']['status']['expose']['operator'] = FALSE; + + /* Display: Page */ + $handler = $view->new_display('page', 'Page', 'page'); + $handler->display->display_options['path'] = 'views-content-more-link'; + + /* Display: Content pane */ + $handler = $view->new_display('panel_pane', 'Content pane', 'panel_pane_1'); + $handler->display->display_options['defaults']['pager'] = FALSE; + $handler->display->display_options['pager']['type'] = 'some'; + $handler->display->display_options['pager']['options']['items_per_page'] = '3'; + $handler->display->display_options['pager']['options']['offset'] = '0'; + + $views[$view->name] = $view; + + return $views; +} diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/views_content/tests/src/views_content.test b/profiles/commerce_kickstart/modules/contrib/ctools/views_content/tests/src/views_content.test new file mode 100644 index 00000000000..25fee392b40 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/ctools/views_content/tests/src/views_content.test @@ -0,0 +1,79 @@ + 'Views content panes tests', + 'description' => 'Tests rendering views content pane displays.', + 'group' => 'ctools', + 'dependencies' => array('ctools', 'views'), + ); + } + + /** + * {@inheritdoc} + */ + public function setUp(array $modules = array()) { + $modules[] = 'ctools'; + $modules[] = 'views_content'; + $modules[] = 'views_content_test'; + parent::setUp($modules); + } + + /** + * Tests rendering a content pane with a more link. + */ + public function testViewMoreLink() { + // The view "views_content_more_link" has two displays: a content pane + // display and a page display. The content pane display displays 3 nodes, + // the page display displays all items. + // On the content pane display a "more" link is configured that should link + // to the page. + $view = views_get_view('views_content_more_link'); + + // Create four nodes that the view will display. The first three + // will get displayed on the content pane display and the remaining + // on the page display. + for ($i = 0; $i < 4; $i++) { + $this->drupalCreateNode(); + } + + // Render the content pane display and assert that a more link is shown. + $view->set_display('panel_pane_1'); + $rendered_output = $view->preview(); + $this->verbose($rendered_output); + $this->storeViewPreview($rendered_output); + + $this->assertLink('more'); + $this->assertLinkByHref('views-content-more-link'); + } + + /** + * Stores a view output in the elements. + * + * @param string $output + * The Views HTML output. + */ + protected function storeViewPreview($output) { + $html_dom = new DOMDocument(); + @$html_dom->loadHTML($output); + if ($html_dom) { + // It's much easier to work with simplexml than DOM, luckily enough + // we can just simply import our DOM tree. + $this->elements = simplexml_import_dom($html_dom); + } + } + +} diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/views_content/views_content.admin.inc b/profiles/commerce_kickstart/modules/contrib/ctools/views_content/views_content.admin.inc deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/profiles/commerce_kickstart/modules/contrib/ctools/views_content/views_content.info b/profiles/commerce_kickstart/modules/contrib/ctools/views_content/views_content.info index 68f4d875688..3ab45e665be 100644 --- a/profiles/commerce_kickstart/modules/contrib/ctools/views_content/views_content.info +++ b/profiles/commerce_kickstart/modules/contrib/ctools/views_content/views_content.info @@ -8,9 +8,10 @@ package = Chaos tool suite 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 +files[] = tests/src/views_content.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 2021-01-30 +version = "7.x-1.19" core = "7.x" project = "ctools" -datestamp = "1549603691" +datestamp = "1611988843" diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n.info b/profiles/commerce_kickstart/modules/contrib/i18n/i18n.info index 46f21698599..ec7ac18244b 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n.info +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n.info @@ -8,8 +8,8 @@ files[] = i18n_object.inc files[] = i18n.test configure = admin/config/regional/i18n -; Information added by Drupal.org packaging script on 2018-08-17 -version = "7.x-1.26" +; Information added by Drupal.org packaging script on 2020-06-17 +version = "7.x-1.27" core = "7.x" project = "i18n" -datestamp = "1534531985" +datestamp = "1592373390" diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_block/i18n_block.info b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_block/i18n_block.info index 89cf38b8e40..0a32676e7d7 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_block/i18n_block.info +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_block/i18n_block.info @@ -8,8 +8,8 @@ files[] = i18n_block.inc files[] = i18n_block.test -; Information added by Drupal.org packaging script on 2018-08-17 -version = "7.x-1.26" +; Information added by Drupal.org packaging script on 2020-06-17 +version = "7.x-1.27" core = "7.x" project = "i18n" -datestamp = "1534531985" +datestamp = "1592373390" diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_contact/i18n_contact.info b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_contact/i18n_contact.info index 76978ff1864..e1084bdf10f 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_contact/i18n_contact.info +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_contact/i18n_contact.info @@ -5,8 +5,8 @@ dependencies[] = i18n_string package = Multilingual - Internationalization core = 7.x -; Information added by Drupal.org packaging script on 2018-08-17 -version = "7.x-1.26" +; Information added by Drupal.org packaging script on 2020-06-17 +version = "7.x-1.27" core = "7.x" project = "i18n" -datestamp = "1534531985" +datestamp = "1592373390" diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_field/i18n_field.info b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_field/i18n_field.info index b3596f9c1ab..2c126d89c91 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_field/i18n_field.info +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_field/i18n_field.info @@ -6,8 +6,8 @@ package = Multilingual - Internationalization core = 7.x files[] = i18n_field.inc files[] = i18n_field.test -; Information added by Drupal.org packaging script on 2018-08-17 -version = "7.x-1.26" +; Information added by Drupal.org packaging script on 2020-06-17 +version = "7.x-1.27" core = "7.x" project = "i18n" -datestamp = "1534531985" +datestamp = "1592373390" diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_forum/i18n_forum.info b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_forum/i18n_forum.info index 17e7f0e5537..249fb66a055 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_forum/i18n_forum.info +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_forum/i18n_forum.info @@ -7,8 +7,8 @@ package = Multilingual - Internationalization core = 7.x files[] = i18n_forum.test -; Information added by Drupal.org packaging script on 2018-08-17 -version = "7.x-1.26" +; Information added by Drupal.org packaging script on 2020-06-17 +version = "7.x-1.27" core = "7.x" project = "i18n" -datestamp = "1534531985" +datestamp = "1592373390" diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_menu/i18n_menu.info b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_menu/i18n_menu.info index 6b03ca65b10..5b210b9c3b5 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_menu/i18n_menu.info +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_menu/i18n_menu.info @@ -10,8 +10,8 @@ core = 7.x files[] = i18n_menu.inc files[] = i18n_menu.test -; Information added by Drupal.org packaging script on 2018-08-17 -version = "7.x-1.26" +; Information added by Drupal.org packaging script on 2020-06-17 +version = "7.x-1.27" core = "7.x" project = "i18n" -datestamp = "1534531985" +datestamp = "1592373390" diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_node/i18n_node.info b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_node/i18n_node.info index 45c78290602..ba222a1e47d 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_node/i18n_node.info +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_node/i18n_node.info @@ -9,8 +9,8 @@ configure = admin/config/regional/i18n/node files[]=i18n_node.test files[]=i18n_node.variable.inc -; Information added by Drupal.org packaging script on 2018-08-17 -version = "7.x-1.26" +; Information added by Drupal.org packaging script on 2020-06-17 +version = "7.x-1.27" core = "7.x" project = "i18n" -datestamp = "1534531985" +datestamp = "1592373390" diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_path/i18n_path.info b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_path/i18n_path.info index cba9b1fa9ce..bb3519e0727 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_path/i18n_path.info +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_path/i18n_path.info @@ -6,8 +6,8 @@ core = 7.x files[] = i18n_path.inc files[] = i18n_path.test -; Information added by Drupal.org packaging script on 2018-08-17 -version = "7.x-1.26" +; Information added by Drupal.org packaging script on 2020-06-17 +version = "7.x-1.27" core = "7.x" project = "i18n" -datestamp = "1534531985" +datestamp = "1592373390" diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_redirect/i18n_redirect.info b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_redirect/i18n_redirect.info index 7d6567deefa..2ceeb19c571 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_redirect/i18n_redirect.info +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_redirect/i18n_redirect.info @@ -4,8 +4,8 @@ dependencies[] = i18n package = Multilingual - Internationalization core = 7.x -; Information added by Drupal.org packaging script on 2018-08-17 -version = "7.x-1.26" +; Information added by Drupal.org packaging script on 2020-06-17 +version = "7.x-1.27" core = "7.x" project = "i18n" -datestamp = "1534531985" +datestamp = "1592373390" diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_select/i18n_select.info b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_select/i18n_select.info index 25902957536..5c72eab563f 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_select/i18n_select.info +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_select/i18n_select.info @@ -6,8 +6,8 @@ core = 7.x configure = admin/config/regional/i18n/select files[] = i18n_select.test -; Information added by Drupal.org packaging script on 2018-08-17 -version = "7.x-1.26" +; Information added by Drupal.org packaging script on 2020-06-17 +version = "7.x-1.27" core = "7.x" project = "i18n" -datestamp = "1534531985" +datestamp = "1592373390" diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_string/i18n_string.info b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_string/i18n_string.info index fec3d9481d4..327539ee2dc 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_string/i18n_string.info +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_string/i18n_string.info @@ -10,8 +10,8 @@ files[] = i18n_string.inc files[] = i18n_string.test configure = admin/config/regional/i18n/strings -; Information added by Drupal.org packaging script on 2018-08-17 -version = "7.x-1.26" +; Information added by Drupal.org packaging script on 2020-06-17 +version = "7.x-1.27" core = "7.x" project = "i18n" -datestamp = "1534531985" +datestamp = "1592373390" diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_sync/i18n_sync.info b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_sync/i18n_sync.info index b42a9d92819..ec5912f5898 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_sync/i18n_sync.info +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_sync/i18n_sync.info @@ -10,8 +10,8 @@ files[] = i18n_sync.install files[] = i18n_sync.module.inc files[] = i18n_sync.node.inc files[] = i18n_sync.test -; Information added by Drupal.org packaging script on 2018-08-17 -version = "7.x-1.26" +; Information added by Drupal.org packaging script on 2020-06-17 +version = "7.x-1.27" core = "7.x" project = "i18n" -datestamp = "1534531985" +datestamp = "1592373390" diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_taxonomy/i18n_taxonomy.admin.inc b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_taxonomy/i18n_taxonomy.admin.inc index bbc35a06796..c48612b10f2 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_taxonomy/i18n_taxonomy.admin.inc +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_taxonomy/i18n_taxonomy.admin.inc @@ -48,7 +48,7 @@ function i18n_taxonomy_translation_term_form($form, $form_state, $vocabulary, $t $form['translations'][$lang] = array( '#title' => $langname, '#type' => 'item', - '#markup' => $source->name, + '#markup' => check_plain($source->name), '#langcode' => $lang, ); } diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_taxonomy/i18n_taxonomy.info b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_taxonomy/i18n_taxonomy.info index 4e5af53e6cf..fd5113e270f 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_taxonomy/i18n_taxonomy.info +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_taxonomy/i18n_taxonomy.info @@ -11,8 +11,8 @@ files[] = i18n_taxonomy.pages.inc files[] = i18n_taxonomy.admin.inc files[] = i18n_taxonomy.test -; Information added by Drupal.org packaging script on 2018-08-17 -version = "7.x-1.26" +; Information added by Drupal.org packaging script on 2020-06-17 +version = "7.x-1.27" core = "7.x" project = "i18n" -datestamp = "1534531985" +datestamp = "1592373390" diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_translation/i18n_translation.info b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_translation/i18n_translation.info index 72ec9feadc8..b903330b0ad 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_translation/i18n_translation.info +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_translation/i18n_translation.info @@ -6,8 +6,8 @@ core = 7.x files[] = i18n_translation.inc -; Information added by Drupal.org packaging script on 2018-08-17 -version = "7.x-1.26" +; Information added by Drupal.org packaging script on 2020-06-17 +version = "7.x-1.27" core = "7.x" project = "i18n" -datestamp = "1534531985" +datestamp = "1592373390" diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_user/i18n_user.info b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_user/i18n_user.info index 6fccd5f1786..b6c8fc01fe6 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_user/i18n_user.info +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_user/i18n_user.info @@ -4,8 +4,8 @@ core = 7.x package = Multilingual - Internationalization dependencies[] = i18n_variable -; Information added by Drupal.org packaging script on 2018-08-17 -version = "7.x-1.26" +; Information added by Drupal.org packaging script on 2020-06-17 +version = "7.x-1.27" core = "7.x" project = "i18n" -datestamp = "1534531985" +datestamp = "1592373390" diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_variable/i18n_variable.info b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_variable/i18n_variable.info index 362772f0f39..517b843e8fd 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/i18n_variable/i18n_variable.info +++ b/profiles/commerce_kickstart/modules/contrib/i18n/i18n_variable/i18n_variable.info @@ -10,8 +10,8 @@ configure = admin/config/regional/i18n/variable files[] = i18n_variable.class.inc files[] = i18n_variable.test -; Information added by Drupal.org packaging script on 2018-08-17 -version = "7.x-1.26" +; Information added by Drupal.org packaging script on 2020-06-17 +version = "7.x-1.27" core = "7.x" project = "i18n" -datestamp = "1534531985" +datestamp = "1592373390" diff --git a/profiles/commerce_kickstart/modules/contrib/i18n/tests/i18n_test.info b/profiles/commerce_kickstart/modules/contrib/i18n/tests/i18n_test.info index efb7c348f47..e196b5c1bbd 100644 --- a/profiles/commerce_kickstart/modules/contrib/i18n/tests/i18n_test.info +++ b/profiles/commerce_kickstart/modules/contrib/i18n/tests/i18n_test.info @@ -7,8 +7,8 @@ package = Testing core = 6.x hidden = TRUE -; Information added by Drupal.org packaging script on 2018-08-17 -version = "7.x-1.26" +; Information added by Drupal.org packaging script on 2020-06-17 +version = "7.x-1.27" core = "7.x" project = "i18n" -datestamp = "1534531985" +datestamp = "1592373390" diff --git a/profiles/commerce_kickstart/modules/contrib/views/handlers/views_handler_area_result.inc b/profiles/commerce_kickstart/modules/contrib/views/handlers/views_handler_area_result.inc index 6c98c0e744c..dd51bf17a52 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/handlers/views_handler_area_result.inc +++ b/profiles/commerce_kickstart/modules/contrib/views/handlers/views_handler_area_result.inc @@ -102,7 +102,10 @@ class views_handler_area_result extends views_handler_area { } // Send the output. if (!empty($total) || !empty($this->options['empty'])) { - $output .= filter_xss_admin(str_replace(array_keys($replacements), array_values($replacements), $format)); + // We don't want to sanitize with filter_xss_admin() here because Views + // administrators are trusted users and should be allowed to insert + // arbitrary markup. + $output .= str_replace(array_keys($replacements), array_values($replacements), $format); } return $output; } diff --git a/profiles/commerce_kickstart/modules/contrib/views/handlers/views_handler_argument_string.inc b/profiles/commerce_kickstart/modules/contrib/views/handlers/views_handler_argument_string.inc index 86000c6caab..c4c02a41d41 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/handlers/views_handler_argument_string.inc +++ b/profiles/commerce_kickstart/modules/contrib/views/handlers/views_handler_argument_string.inc @@ -225,9 +225,11 @@ class views_handler_argument_string extends views_handler_argument { if ($formula) { $placeholder = $this->placeholder(); if (count($this->value) > 1) { - $placeholder = "($placeholder)"; + $field .= " $operator ($placeholder)"; + } + else { + $field .= " $operator $placeholder"; } - $field .= " $operator $placeholder"; $placeholders = array( $placeholder => $argument, ); diff --git a/profiles/commerce_kickstart/modules/contrib/views/handlers/views_handler_field.inc b/profiles/commerce_kickstart/modules/contrib/views/handlers/views_handler_field.inc index 4b690dca8c8..d592ef2ac9a 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/handlers/views_handler_field.inc +++ b/profiles/commerce_kickstart/modules/contrib/views/handlers/views_handler_field.inc @@ -159,7 +159,7 @@ class views_handler_field extends views_handler { '@identifier' => $identifier, '@table' => $info['table'], ); - debug(t('Handler @handler tried to add additional_field @identifier but @table could not be added!', $t_args)); + debug(t('Handler @handler tried to add additional field @identifier but @table could not be added.', $t_args)); $this->aliases[$identifier] = 'broken'; continue; } @@ -1249,6 +1249,12 @@ If you would like to have the characters \'[\' and \']\' please use the html ent // displays such as XML where we should not mess with tags. $value = $alter['text']; $value = strtr($value, $tokens); + // User might already used '%5B' and '%5D' instead of literal [ and ]. + // After token replacements, we need to convert those codes to literal + // square bracket characters. Otherwise problems like comment #5 and #6 of + // https://www.drupal.org/node/578772 will happen. + // We could have used rawurldecode() also, but not sure about the consequences. + $value = strtr($value, array('%5B' => '[', '%5D' => ']')); return $value; } diff --git a/profiles/commerce_kickstart/modules/contrib/views/handlers/views_handler_filter.inc b/profiles/commerce_kickstart/modules/contrib/views/handlers/views_handler_filter.inc index 6ffb10d128a..b6e06df6721 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/handlers/views_handler_filter.inc +++ b/profiles/commerce_kickstart/modules/contrib/views/handlers/views_handler_filter.inc @@ -1387,7 +1387,9 @@ class views_handler_filter extends views_handler { // Various ways to check for the absence of non-required input. if (empty($this->options['expose']['required'])) { if ($this->operator == 'empty' || $this->operator == 'not empty') { - $value = is_array($value) ? $value['value'] : $value; + if (is_array($value) && array_key_exists('value', $value)) { + $value = $value['value']; + } $this->operator = ($this->operator == 'empty' && empty($value)) || ($this->operator == 'not empty' && !empty($value)) ? 'not empty' : 'empty'; } diff --git a/profiles/commerce_kickstart/modules/contrib/views/handlers/views_handler_filter_combine.inc b/profiles/commerce_kickstart/modules/contrib/views/handlers/views_handler_filter_combine.inc index 35bfb20fe6a..e7abb4e5131 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/handlers/views_handler_filter_combine.inc +++ b/profiles/commerce_kickstart/modules/contrib/views/handlers/views_handler_filter_combine.inc @@ -129,7 +129,7 @@ class views_handler_filter_combine extends views_handler_filter_string { foreach ($matches as $match) { $phrase = FALSE; // Strip off phrase quotes. - if ($match[2]{0} == '"') { + if ($match[2][0] == '"') { $match[2] = substr($match[2], 1, -1); $phrase = TRUE; } diff --git a/profiles/commerce_kickstart/modules/contrib/views/handlers/views_handler_filter_numeric.inc b/profiles/commerce_kickstart/modules/contrib/views/handlers/views_handler_filter_numeric.inc index a338374148a..95d23d65a3d 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/handlers/views_handler_filter_numeric.inc +++ b/profiles/commerce_kickstart/modules/contrib/views/handlers/views_handler_filter_numeric.inc @@ -379,6 +379,13 @@ class views_handler_filter_numeric extends views_handler_filter { 'value' => $value, ); } + if (!empty($this->options['expose']['use_operator']) && !empty($this->options['expose']['operator_id']) && isset($input[$this->options['expose']['operator_id']])) { + if ($input[$this->options['expose']['operator_id']] === 'empty' || $input[$this->options['expose']['operator_id']] === 'not empty') { + // Parent method will expect a boolean value. We don't ask for a value + // so we'll force "Yes". + $value['value'] = 1; + } + } } $rc = parent::accept_exposed_input($input); diff --git a/profiles/commerce_kickstart/modules/contrib/views/handlers/views_handler_filter_string.inc b/profiles/commerce_kickstart/modules/contrib/views/handlers/views_handler_filter_string.inc index 85e3814f23a..8d2f7345040 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/handlers/views_handler_filter_string.inc +++ b/profiles/commerce_kickstart/modules/contrib/views/handlers/views_handler_filter_string.inc @@ -300,7 +300,7 @@ class views_handler_filter_string extends views_handler_filter { foreach ($matches as $match) { $phrase = FALSE; // Strip off phrase quotes - if ($match[2]{0} == '"') { + if ($match[2][0] == '"') { $match[2] = substr($match[2], 1, -1); $phrase = TRUE; } diff --git a/profiles/commerce_kickstart/modules/contrib/views/includes/ajax.inc b/profiles/commerce_kickstart/modules/contrib/views/includes/ajax.inc index 37eee9c75df..8f560977401 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/includes/ajax.inc +++ b/profiles/commerce_kickstart/modules/contrib/views/includes/ajax.inc @@ -26,7 +26,9 @@ function views_ajax() { $commands = array(); // Remove all of this stuff from $_GET so it doesn't end up in pagers and - // tablesort URLs. + // tablesort URLs; do not modify $_POST itself but make a new "clean" + // copy to merge it with $_GET later. + $cleaned_post = $_POST; foreach (array('view_name', 'view_display_id', 'view_args', 'view_path', 'view_dom_id', 'pager_element', 'view_base_path', 'ajax_html_ids', 'ajax_page_state') as $key) { if (isset($_GET[$key])) { unset($_GET[$key]); @@ -34,8 +36,8 @@ function views_ajax() { if (isset($_REQUEST[$key])) { unset($_REQUEST[$key]); } - if (isset($_POST[$key])) { - unset($_POST[$key]); + if (isset($cleaned_post[$key])) { + unset($cleaned_post[$key]); } } @@ -52,7 +54,7 @@ function views_ajax() { $exclude = isset($_POST['page']) ? array('page') : array(); // Add all $_POST data to $_GET as many things, such as tablesorts, // exposed filters and paging assume $_GET. - $_GET = $_POST + drupal_get_query_parameters($_GET, $exclude); + $_GET = $cleaned_post + drupal_get_query_parameters($_GET, $exclude); // Overwrite the destination. // @see drupal_get_destination() diff --git a/profiles/commerce_kickstart/modules/contrib/views/includes/analyze.inc b/profiles/commerce_kickstart/modules/contrib/views/includes/analyze.inc index 5dd58de2f55..47042028ffc 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/includes/analyze.inc +++ b/profiles/commerce_kickstart/modules/contrib/views/includes/analyze.inc @@ -113,7 +113,7 @@ function views_ui_views_analyze($view) { if ($display->handler->has_path() && $path = $display->handler->get_option('path')) { $normal_path = drupal_get_normal_path($path); if ($path != $normal_path) { - $ret[] = views_ui_analysis(t('You have configured display %display with a path which is an path alias as well. This might lead to unwanted effects so better use an internal path.', array('%display' => $display->display_title)), 'warning'); + $ret[] = views_ui_analysis(t('You have configured display %display with a path which is a path alias as well. This might lead to unwanted effects so you should use an internal path.', array('%display' => $display->display_title)), 'warning'); } } } diff --git a/profiles/commerce_kickstart/modules/contrib/views/includes/view.inc b/profiles/commerce_kickstart/modules/contrib/views/includes/view.inc index 4f26650b69f..4541f80b97f 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/includes/view.inc +++ b/profiles/commerce_kickstart/modules/contrib/views/includes/view.inc @@ -1000,6 +1000,7 @@ class view extends views_db_object { // Let the handlers interact with each other if they really want. $this->_pre_query(); + $exposed_form = FALSE; if ($this->display_handler->uses_exposed()) { $exposed_form = $this->display_handler->get_plugin('exposed_form'); // (1) Record the errors before rendering the exposed form widgets. @@ -1080,7 +1081,7 @@ class view extends views_db_object { $this->style_plugin->query($this->display_handler->use_group_by()); // Allow exposed form to affect the query. - if (isset($exposed_form)) { + if ($exposed_form) { $exposed_form->query(); } diff --git a/profiles/commerce_kickstart/modules/contrib/views/js/ajax_view.js b/profiles/commerce_kickstart/modules/contrib/views/js/ajax_view.js index 5012c33ec33..1d235d3b676 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/js/ajax_view.js +++ b/profiles/commerce_kickstart/modules/contrib/views/js/ajax_view.js @@ -80,7 +80,10 @@ // Add a trigger to update this view specifically. var self_settings = this.element_settings; self_settings.event = 'RefreshView'; - this.refreshViewAjax = new Drupal.ajax(this.selector, this.$view, self_settings); + var self = this; + this.$view.once('refresh', function () { + self.refreshViewAjax = new Drupal.ajax(self.selector, self.$view, self_settings); + }); }; Drupal.views.ajaxView.prototype.attachExposedFormAjax = function() { @@ -101,21 +104,22 @@ * Attach the ajax behavior to each link. */ Drupal.views.ajaxView.prototype.attachPagerAjax = function() { - this.$view.find('ul.pager > li > a, th.views-field a, .attachment .views-summary a') + this.$view.find('ul.pager > li > a, ol.pager > li > a, th.views-field a, .attachment .views-summary a') .each(jQuery.proxy(this.attachPagerLinkAjax, this)); }; /** - * Attach the ajax behavior to a singe link. + * Attach the ajax behavior to a single link. */ Drupal.views.ajaxView.prototype.attachPagerLinkAjax = function(id, link) { var $link = $(link); + var viewData = {}; + var href = $link.attr('href'); // Don't attach to pagers inside nested views. - if ($link.closest('.view')[0] !== this.$view[0]) { + if ($link.closest('.view')[0] !== this.$view[0] && + $link.closest('.view').parent().hasClass('attachment') === false) { return; } - var viewData = {}; - var href = $link.attr('href'); // Provide a default page if none has been set. This must be done // prior to merging with settings to avoid accidentally using the @@ -127,11 +131,11 @@ // Construct an object using the settings defaults and then overriding // with data specific to the link. $.extend( - viewData, - this.settings, - Drupal.Views.parseQueryString(href), - // Extract argument data from the URL. - Drupal.Views.parseViewArgs(href, this.settings.view_base_path) + viewData, + this.settings, + Drupal.Views.parseQueryString(href), + // Extract argument data from the URL. + Drupal.Views.parseViewArgs(href, this.settings.view_base_path) ); // For anchor tags, these will go to the target of the anchor rather diff --git a/profiles/commerce_kickstart/modules/contrib/views/js/exposed-form-ajax.js b/profiles/commerce_kickstart/modules/contrib/views/js/exposed-form-ajax.js new file mode 100644 index 00000000000..6322ba41610 --- /dev/null +++ b/profiles/commerce_kickstart/modules/contrib/views/js/exposed-form-ajax.js @@ -0,0 +1,26 @@ +/** + * @file + * Handles Views' exposed form AJAX data submission. + */ + +(function ($) { + 'use strict'; + + /** + * Gets Form build info from settings and adds it to ajax data. + * + * @see views_exposed_form_ajax_enable(). + */ + Drupal.behaviors.ViewsExposedFormAjax = { + attach: function (context, settings) { + for (var ajaxObject in Drupal.ajax) { + for (var name in Drupal.settings.ViewsExposedFormInfo) { + if (Drupal.ajax[ajaxObject].options && Drupal.ajax[ajaxObject].options.data._triggering_element_name === name) { + jQuery.extend(Drupal.ajax[ajaxObject].options.data, Drupal.settings.ViewsExposedFormInfo[name]); + } + } + } + } + }; + +})(jQuery); diff --git a/profiles/commerce_kickstart/modules/contrib/views/js/views-admin.js b/profiles/commerce_kickstart/modules/contrib/views/js/views-admin.js index 807d7f7b185..f3aaf63b5d4 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/js/views-admin.js +++ b/profiles/commerce_kickstart/modules/contrib/views/js/views-admin.js @@ -231,7 +231,7 @@ Drupal.behaviors.viewsUiRenderAddViewButton.attach = function (context, settings return; } var $addDisplayDropdown = $('
  • ' + Drupal.t('Add') + '
  • '); - var $displayButtons = $menu.nextAll('input.add-display').detach(); + var $displayButtons = $menu.nextAll('.add-display').detach(); $displayButtons.appendTo($addDisplayDropdown.find('.action-list')).wrap('
  • ') .parent().first().addClass('first').end().last().addClass('last'); // Remove the 'Add ' prefix from the button labels since they're being palced diff --git a/profiles/commerce_kickstart/modules/contrib/views/modules/comment.views.inc b/profiles/commerce_kickstart/modules/contrib/views/modules/comment.views.inc index 3cecfcc2604..5cde8bc718e 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/modules/comment.views.inc +++ b/profiles/commerce_kickstart/modules/contrib/views/modules/comment.views.inc @@ -419,20 +419,6 @@ function comment_views_data() { ), ); - $data['comment']['cid'] = array( - 'title' => t('Comment id'), - 'help' => t('Unique identifier for the comment.'), - 'filter' => array( - 'handler' => 'views_handler_filter_numeric', - ), - 'argument' => array( - 'handler' => 'views_handler_argument_numeric', - ), - 'field' => array( - 'handler' => 'views_handler_field_numeric', - ), - ); - $data['comment']['uid'] = array( 'title' => t('Author uid'), 'help' => t('If you need more fields than the uid add the comment: author relationship'), diff --git a/profiles/commerce_kickstart/modules/contrib/views/modules/locale/views_handler_field_locale_language.inc b/profiles/commerce_kickstart/modules/contrib/views/modules/locale/views_handler_field_locale_language.inc index 552ccf2ecb4..c8dd9e36491 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/modules/locale/views_handler_field_locale_language.inc +++ b/profiles/commerce_kickstart/modules/contrib/views/modules/locale/views_handler_field_locale_language.inc @@ -39,7 +39,7 @@ class views_handler_field_locale_language extends views_handler_field { * {@inheritdoc} */ public function render($values) { - $languages = locale_language_list(empty($this->options['native_language']) ? 'name' : 'native'); + $languages = views_language_list(empty($this->options['native_language']) ? 'name' : 'native'); $value = $this->get_value($values); return isset($languages[$value]) ? $languages[$value] : ''; } diff --git a/profiles/commerce_kickstart/modules/contrib/views/modules/system.views.inc b/profiles/commerce_kickstart/modules/contrib/views/modules/system.views.inc index b4c93c581d1..058463bbde3 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/modules/system.views.inc +++ b/profiles/commerce_kickstart/modules/contrib/views/modules/system.views.inc @@ -112,7 +112,7 @@ function system_views_data() { $data['file_managed']['extension'] = array( 'title' => t('Extension'), 'help' => t('The extension of the file.'), - 'real field' => 'filename', + 'real field' => 'uri', 'field' => array( 'handler' => 'views_handler_field_file_extension', 'click sortable' => FALSE, diff --git a/profiles/commerce_kickstart/modules/contrib/views/modules/taxonomy/views_handler_field_term_link_edit.inc b/profiles/commerce_kickstart/modules/contrib/views/modules/taxonomy/views_handler_field_term_link_edit.inc index 3bd32123250..a026d3e6335 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/modules/taxonomy/views_handler_field_term_link_edit.inc +++ b/profiles/commerce_kickstart/modules/contrib/views/modules/taxonomy/views_handler_field_term_link_edit.inc @@ -76,6 +76,7 @@ class views_handler_field_term_link_edit extends views_handler_field { $term->vocabulary_machine_name = $values->{$this->aliases['vocabulary_machine_name']}; if ($data && taxonomy_term_edit_access($term)) { $text = !empty($this->options['text']) ? $this->options['text'] : t('edit'); + $this->options['alter']['make_link'] = TRUE; $this->options['alter']['path'] = "taxonomy/term/$data/edit"; $this->options['alter']['query'] = drupal_get_destination(); return $text; diff --git a/profiles/commerce_kickstart/modules/contrib/views/modules/taxonomy/views_handler_filter_term_node_tid.inc b/profiles/commerce_kickstart/modules/contrib/views/modules/taxonomy/views_handler_filter_term_node_tid.inc index 03b8d11e4f5..3909e2112bc 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/modules/taxonomy/views_handler_filter_term_node_tid.inc +++ b/profiles/commerce_kickstart/modules/contrib/views/modules/taxonomy/views_handler_filter_term_node_tid.inc @@ -152,8 +152,10 @@ class views_handler_filter_term_node_tid extends views_handler_filter_many_to_on } } else { + $options = array(); if (!empty($this->options['hierarchy']) && $this->options['limit']) { $tree = taxonomy_get_tree($vocabulary->vid, 0, NULL, TRUE); + $options = array(); if (!empty($tree)) { if (!empty($this->options['optgroups'])) { foreach ($tree as $term) { @@ -174,7 +176,6 @@ class views_handler_filter_term_node_tid extends views_handler_filter_many_to_on } } else { - $options = array(); $query = db_select('taxonomy_term_data', 'td'); $query->innerJoin('taxonomy_vocabulary', 'tv', 'td.vid = tv.vid'); $query->fields('td'); diff --git a/profiles/commerce_kickstart/modules/contrib/views/modules/translation/views_handler_relationship_translation.inc b/profiles/commerce_kickstart/modules/contrib/views/modules/translation/views_handler_relationship_translation.inc index 7601c25f05e..88f373cc0ff 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/modules/translation/views_handler_relationship_translation.inc +++ b/profiles/commerce_kickstart/modules/contrib/views/modules/translation/views_handler_relationship_translation.inc @@ -34,7 +34,7 @@ class views_handler_relationship_translation extends views_handler_relationship 'current' => t('Current language'), 'default' => t('Default language'), ); - $options = array_merge($options, locale_language_list()); + $options = array_merge($options, views_language_list()); $form['language'] = array( '#type' => 'select', '#options' => $options, diff --git a/profiles/commerce_kickstart/modules/contrib/views/modules/user/views_handler_field_user_roles.inc b/profiles/commerce_kickstart/modules/contrib/views/modules/user/views_handler_field_user_roles.inc index 86a0b7a04f7..8d9eb325b40 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/modules/user/views_handler_field_user_roles.inc +++ b/profiles/commerce_kickstart/modules/contrib/views/modules/user/views_handler_field_user_roles.inc @@ -20,6 +20,28 @@ class views_handler_field_user_roles extends views_handler_field_prerender_list $this->additional_fields['uid'] = array('table' => 'users', 'field' => 'uid'); } + function option_definition() { + $options = parent::option_definition(); + $options['display_roles'] = array('default' => array()); + + return $options; + } + + function options_form(&$form, &$form_state) { + $roles = user_roles(TRUE); + unset($roles[DRUPAL_AUTHENTICATED_RID]); + if (!empty($roles)) { + $form['display_roles'] = array( + '#type' => 'checkboxes', + '#title' => t('Only display the selected roles'), + '#description' => t("If no roles are selected, all of the user's roles will be shown."), + '#options' => $roles, + '#default_value' => $this->options['display_roles'], + ); + } + parent::options_form($form, $form_state); + } + /** * {@inheritdoc} */ @@ -40,8 +62,15 @@ class views_handler_field_user_roles extends views_handler_field_prerender_list } if ($uids) { - $result = db_query("SELECT u.uid, u.rid, r.name FROM {role} r INNER JOIN {users_roles} u ON u.rid = r.rid WHERE u.uid IN (:uids) ORDER BY r.name", - array(':uids' => $uids)); + $rids = array_filter($this->options['display_roles']); + if (!empty($rids)) { + $result = db_query("SELECT u.uid, u.rid, r.name FROM {role} r INNER JOIN {users_roles} u ON u.rid = r.rid WHERE u.uid IN (:uids) AND r.rid IN (:rids) ORDER BY r.name", + array(':uids' => $uids, ':rids' => $rids)); + } + else { + $result = db_query("SELECT u.uid, u.rid, r.name FROM {role} r INNER JOIN {users_roles} u ON u.rid = r.rid WHERE u.uid IN (:uids) ORDER BY r.name", + array(':uids' => $uids)); + } foreach ($result as $role) { $this->items[$role->uid][$role->rid]['role'] = check_plain($role->name); $this->items[$role->uid][$role->rid]['rid'] = $role->rid; @@ -53,7 +82,7 @@ class views_handler_field_user_roles extends views_handler_field_prerender_list * {@inheritdoc} */ public function render_item($count, $item) { - return $item['role']; + return t($item['role']); } /** diff --git a/profiles/commerce_kickstart/modules/contrib/views/plugins/views_plugin_display.inc b/profiles/commerce_kickstart/modules/contrib/views/plugins/views_plugin_display.inc index 7383ac98c30..3d47a14a6b6 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/plugins/views_plugin_display.inc +++ b/profiles/commerce_kickstart/modules/contrib/views/plugins/views_plugin_display.inc @@ -314,7 +314,7 @@ class views_plugin_display extends views_plugin { } } $pager = $this->get_plugin('pager'); - if (isset($pager) && $pager->uses_exposed()) { + if ($pager && $pager->uses_exposed()) { $this->has_exposed = TRUE; return TRUE; } @@ -1400,9 +1400,8 @@ class views_plugin_display extends views_plugin { '***DEFAULT_LANGUAGE***' => t("Default site language"), LANGUAGE_NONE => t('Language neutral'), ); - if (module_exists('locale')) { - $languages = array_merge($languages, locale_language_list()); - } + $languages = array_merge($languages, views_language_list()); + $field_language = array(); $options['field_language'] = array( 'category' => 'other', diff --git a/profiles/commerce_kickstart/modules/contrib/views/plugins/views_plugin_display_page.inc b/profiles/commerce_kickstart/modules/contrib/views/plugins/views_plugin_display_page.inc index 9b9e900bb7d..d743836d5ba 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/plugins/views_plugin_display_page.inc +++ b/profiles/commerce_kickstart/modules/contrib/views/plugins/views_plugin_display_page.inc @@ -87,7 +87,7 @@ class views_plugin_display_page extends views_plugin_display { $path = implode('/', $bits); $access_plugin = $this->get_plugin('access'); - if (!isset($access_plugin)) { + if (!$access_plugin) { $access_plugin = views_get_plugin('access', 'none'); } diff --git a/profiles/commerce_kickstart/modules/contrib/views/plugins/views_plugin_style.inc b/profiles/commerce_kickstart/modules/contrib/views/plugins/views_plugin_style.inc index 1433f319890..ed5d3c0205f 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/plugins/views_plugin_style.inc +++ b/profiles/commerce_kickstart/modules/contrib/views/plugins/views_plugin_style.inc @@ -71,7 +71,7 @@ class views_plugin_style extends views_plugin { public function destroy() { parent::destroy(); - if (isset($this->row_plugin)) { + if ($this->row_plugin) { $this->row_plugin->destroy(); } } @@ -625,7 +625,7 @@ class views_plugin_style extends views_plugin { */ public function query() { parent::query(); - if (isset($this->row_plugin)) { + if ($this->row_plugin) { $this->row_plugin->query(); } } diff --git a/profiles/commerce_kickstart/modules/contrib/views/tests/comment/views_handler_argument_comment_user_uid.test b/profiles/commerce_kickstart/modules/contrib/views/tests/comment/views_handler_argument_comment_user_uid.test index 9599499054c..9564450dbc2 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/tests/comment/views_handler_argument_comment_user_uid.test +++ b/profiles/commerce_kickstart/modules/contrib/views/tests/comment/views_handler_argument_comment_user_uid.test @@ -35,8 +35,11 @@ class viewsHandlerArgumentCommentUserUidTest extends ViewsSqlTest { return comment_save((object) $comment); } - function setUp() { - parent::setUp(); + /** + * {@inheritdoc} + */ + public function setUp(array $modules = array()) { + parent::setUp($modules); // Add two users, create a node with the user1 as author and another node // with user2 as author. For the second node add a comment from user1. diff --git a/profiles/commerce_kickstart/modules/contrib/views/tests/field/views_fieldapi.test b/profiles/commerce_kickstart/modules/contrib/views/tests/field/views_fieldapi.test index 0d049e1092e..4ac71bc8f7a 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/tests/field/views_fieldapi.test +++ b/profiles/commerce_kickstart/modules/contrib/views/tests/field/views_fieldapi.test @@ -119,8 +119,11 @@ class viewsFieldApiDataTest extends ViewsFieldApiTestHelper { ); } - function setUp() { - parent::setUp(); + /** + * {@inheritdoc} + */ + public function setUp(array $modules = array()) { + parent::setUp($modules); $langcode = LANGUAGE_NONE; @@ -285,8 +288,11 @@ class viewsHandlerFieldFieldTest extends ViewsFieldApiTestHelper { ); } - protected function setUp() { - parent::setUp(); + /** + * {@inheritdoc} + */ + public function setUp(array $modules = array()) { + parent::setUp($modules); // Setup basic fields. $this->setUpFields(3); diff --git a/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_argument_string.test b/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_argument_string.test deleted file mode 100644 index 4c76ff6a2e9..00000000000 --- a/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_argument_string.test +++ /dev/null @@ -1,98 +0,0 @@ - 'Argument: String', - 'description' => 'Test the core views_handler_argument_string handler.', - 'group' => 'Views Handlers', - ); - } - - /** - * Tests the glossary feature. - */ - function testGlossary() { - // Setup some nodes, one with a, two with b and three with c. - $counter = 1; - foreach (array('a', 'b', 'c') as $char) { - for ($i = 0; $i < $counter; $i++) { - $edit = array( - 'title' => $char . $this->randomName(), - ); - $this->drupalCreateNode($edit); - } - } - - $view = $this->viewGlossary(); - $view->init_display(); - $this->executeView($view); - - $count_field = 'nid'; - foreach ($view->result as &$row) { - if (strpos($row->node_title, 'a') === 0) { - $this->assertEqual(1, $row->{$count_field}); - } - if (strpos($row->node_title, 'b') === 0) { - $this->assertEqual(2, $row->{$count_field}); - } - if (strpos($row->node_title, 'c') === 0) { - $this->assertEqual(3, $row->{$count_field}); - } - } - } - - /** - * Provide a test view for testGlossary. - * - * @see testGlossary - * - * @return view - */ - function viewGlossary() { - $view = new view(); - $view->name = 'test_glossary'; - $view->description = ''; - $view->tag = 'default'; - $view->base_table = 'node'; - $view->human_name = 'test_glossary'; - $view->core = 7; - $view->api_version = '3.0'; - $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */ - - /* Display: Master */ - $handler = $view->new_display('default', 'Master', 'default'); - $handler->display->display_options['access']['type'] = 'perm'; - $handler->display->display_options['cache']['type'] = 'none'; - $handler->display->display_options['query']['type'] = 'views_query'; - $handler->display->display_options['exposed_form']['type'] = 'basic'; - $handler->display->display_options['pager']['type'] = 'full'; - $handler->display->display_options['style_plugin'] = 'default'; - $handler->display->display_options['row_plugin'] = 'fields'; - /* Field: Content: Title */ - $handler->display->display_options['fields']['title']['id'] = 'title'; - $handler->display->display_options['fields']['title']['table'] = 'node'; - $handler->display->display_options['fields']['title']['field'] = 'title'; - $handler->display->display_options['fields']['title']['label'] = ''; - /* Contextual filter: Content: Title */ - $handler->display->display_options['arguments']['title']['id'] = 'title'; - $handler->display->display_options['arguments']['title']['table'] = 'node'; - $handler->display->display_options['arguments']['title']['field'] = 'title'; - $handler->display->display_options['arguments']['title']['default_argument_type'] = 'fixed'; - $handler->display->display_options['arguments']['title']['summary']['number_of_records'] = '0'; - $handler->display->display_options['arguments']['title']['summary']['format'] = 'default_summary'; - $handler->display->display_options['arguments']['title']['summary_options']['items_per_page'] = '25'; - $handler->display->display_options['arguments']['title']['glossary'] = TRUE; - $handler->display->display_options['arguments']['title']['limit'] = '1'; - - return $view; - } - -} diff --git a/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_field.test b/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_field.test index df9574f89ef..aa44666fccc 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_field.test +++ b/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_field.test @@ -24,10 +24,11 @@ class ViewsHandlerFieldTest extends ViewsSqlTest { } /** - * + * {@inheritdoc} */ - protected function setUp() { - parent::setUp(); + public function setUp(array $modules = array()) { + parent::setUp($modules); + $this->column_map = array( 'views_test_name' => 'name', ); diff --git a/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_field_xss.test b/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_field_xss.test index c12a4088b56..37bb2f21522 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_field_xss.test +++ b/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_field_xss.test @@ -29,7 +29,6 @@ class ViewsHandlerTestXss extends ViewsSqlTest { return $map; } - function viewsData() { $data = parent::viewsData(); $data['views_test']['name']['field']['handler'] = 'views_handler_field_xss'; diff --git a/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_filter_combine.test b/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_filter_combine.test index 57d5e6f8f19..d81b4564f3b 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_filter_combine.test +++ b/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_filter_combine.test @@ -19,8 +19,12 @@ class ViewsHandlerFilterCombineTest extends ViewsSqlTest { ); } - function setUp() { - parent::setUp(); + /** + * {@inheritdoc} + */ + public function setUp(array $modules = array()) { + parent::setUp($modules); + $this->column_map = array( 'views_test_name' => 'name', 'views_test_job' => 'job', diff --git a/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_filter_date.test b/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_filter_date.test index ad6a98bc521..5e8f34be2db 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_filter_date.test +++ b/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_filter_date.test @@ -17,8 +17,12 @@ class ViewsHandlerFilterDateTest extends ViewsSqlTest { ); } - function setUp() { - parent::setUp(); + /** + * {@inheritdoc} + */ + public function setUp(array $modules = array()) { + parent::setUp($modules); + // Add some basic test nodes. $this->nodes = array(); $this->nodes[] = $this->drupalCreateNode(array('created' => 100000)); diff --git a/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_filter_equality.test b/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_filter_equality.test index a1dbc4e4f6d..1c3abd6f93f 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_filter_equality.test +++ b/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_filter_equality.test @@ -17,8 +17,12 @@ class ViewsHandlerFilterEqualityTest extends ViewsSqlTest { ); } - function setUp() { - parent::setUp(); + /** + * {@inheritdoc} + */ + public function setUp(array $modules = array()) { + parent::setUp($modules); + $this->column_map = array( 'views_test_name' => 'name', ); diff --git a/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_filter_numeric.test b/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_filter_numeric.test index a18e20ac945..6faf9d15e30 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_filter_numeric.test +++ b/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_filter_numeric.test @@ -19,8 +19,36 @@ class ViewsHandlerFilterNumericTest extends ViewsSqlTest { ); } - function setUp() { - parent::setUp(); + /** + * {@inheritdoc} + */ + protected function dataSet() { + $data_set = parent::dataSet(); + $data_set[] = array( + 'name' => 'Charles', + 'age' => NULL, + 'job' => 'Bassist', + 'created' => gmmktime(6, 30, 10, 1, 1, 2001), + ); + return $data_set; + } + + /** + * {@inheritdoc} + */ + protected function schemaDefinition() { + $schema = parent::schemaDefinition(); + $schema['views_test']['fields']['age']['not null'] = FALSE; + $schema['views_test']['indexes'] = array(); + return $schema; + } + + /** + * {@inheritdoc} + */ + public function setUp(array $modules = array()) { + parent::setUp($modules); + $this->column_map = array( 'views_test_name' => 'name', 'views_test_age' => 'age', @@ -114,7 +142,7 @@ class ViewsHandlerFilterNumericTest extends ViewsSqlTest { ); $this->assertIdenticalResultset($view, $resultset, $this->column_map); - // test not between. + // Test not between. $view->delete(); $view = $this->getBasicView(); @@ -182,7 +210,7 @@ class ViewsHandlerFilterNumericTest extends ViewsSqlTest { $filters = $this->getGroupedExposedFilters(); $view = $this->getBasicPageView(); - // Filter: Age, Operator: between, Value: 26 and 29 + // Filter: Age, Operator: not between, Value: 26 and 29 $filters['age']['group_info']['default_group'] = 3; $view->set_display('page_1'); $view->display['page_1']->handler->override_option('filters', $filters); @@ -221,7 +249,12 @@ class ViewsHandlerFilterNumericTest extends ViewsSqlTest { )); $this->executeView($view); - $resultset = array(); + $resultset = array( + array( + 'name' => 'Charles', + 'age' => NULL, + ), + ); $this->assertIdenticalResultset($view, $resultset, $this->column_map); $view->delete(); @@ -275,7 +308,12 @@ class ViewsHandlerFilterNumericTest extends ViewsSqlTest { $view->display['page_1']->handler->override_option('filters', $filters); $this->executeView($view); - $resultset = array(); + $resultset = array( + array( + 'name' => 'Charles', + 'age' => NULL, + ), + ); $this->assertIdenticalResultset($view, $resultset, $this->column_map); } @@ -346,6 +384,238 @@ class ViewsHandlerFilterNumericTest extends ViewsSqlTest { } } + /** + * Tests exposed numeric filter with exposed operator. + */ + public function testFilterNumericExposedOperator() { + $this->applyFilterNumericExposedOperator('=', array('value' => '27'), array( + array( + 'name' => 'George', + 'age' => 27, + ), + )); + $this->applyFilterNumericExposedOperator('<', array('value' => '27'), array( + array( + 'name' => 'John', + 'age' => 25, + ), + array( + 'name' => 'Paul', + 'age' => 26, + ), + )); + $this->applyFilterNumericExposedOperator('<=', array( + 'value' => '27', + ), array( + array( + 'name' => 'John', + 'age' => 25, + ), + array( + 'name' => 'George', + 'age' => 27, + ), + array( + 'name' => 'Paul', + 'age' => 26, + ), + )); + $this->applyFilterNumericExposedOperator('!=', array( + 'value' => '27', + ), array( + array( + 'name' => 'John', + 'age' => 25, + ), + array( + 'name' => 'Ringo', + 'age' => 28, + ), + array( + 'name' => 'Paul', + 'age' => 26, + ), + array( + 'name' => 'Meredith', + 'age' => 30, + ), + )); + $this->applyFilterNumericExposedOperator('>=', array( + 'value' => '27', + ), array( + array( + 'name' => 'George', + 'age' => 27, + ), + array( + 'name' => 'Ringo', + 'age' => 28, + ), + array( + 'name' => 'Meredith', + 'age' => 30, + ), + )); + $this->applyFilterNumericExposedOperator('>', array('value' => '27'), array( + array( + 'name' => 'Ringo', + 'age' => 28, + ), + array( + 'name' => 'Meredith', + 'age' => 30, + ), + )); + $this->applyFilterNumericExposedOperator('between', array( + 'min' => '28', + 'max' => '31', + ), array( + array( + 'name' => 'Ringo', + 'age' => 28, + ), + array( + 'name' => 'Meredith', + 'age' => 30, + ), + )); + $this->applyFilterNumericExposedOperator('not between', array( + 'min' => '28', + 'max' => '31', + ), array( + array( + 'name' => 'John', + 'age' => 25, + ), + array( + 'name' => 'George', + 'age' => 27, + ), + array( + 'name' => 'Ringo', + 'age' => 28, + ), + array( + 'name' => 'Paul', + 'age' => 26, + ), + )); + $this->applyFilterNumericExposedOperator('empty', array(), array( + array( + 'name' => 'Charles', + 'age' => NULL, + ), + )); + $this->applyFilterNumericExposedOperator('not empty', array(), array( + array( + 'name' => 'John', + 'age' => 25, + ), + array( + 'name' => 'George', + 'age' => 27, + ), + array( + 'name' => 'Ringo', + 'age' => 28, + ), + array( + 'name' => 'Paul', + 'age' => 26, + ), + array( + 'name' => 'Meredith', + 'age' => 30, + ), + )); + $this->applyFilterNumericExposedOperator('regular_expression', array( + 'value' => '^(0|[1-9][0-9]*)$', + ), array( + array( + 'name' => 'John', + 'age' => 25, + ), + array( + 'name' => 'George', + 'age' => 27, + ), + array( + 'name' => 'Ringo', + 'age' => 28, + ), + array( + 'name' => 'Paul', + 'age' => 26, + ), + array( + 'name' => 'Meredith', + 'age' => 30, + ), + )); + $this->applyFilterNumericExposedOperator('not_regular_expression', array( + 'value' => '^(0|[1-9][0-9]*)$', + ), array()); + } + + /** + * Tests exposed numeric filter with an individual exposed operator. + * + * @param string $operator + * Operator to test. + * @param array $value + * Filter value to use in exposed input. Keys might be 'value', 'min' or + * 'max'. If one of those keys doesn't exist, an empty string is used as the + * key's value. + * @param array $resultset + * The expected result set. + */ + protected function applyFilterNumericExposedOperator($operator, array $value, array $resultset) { + $exposed_input = array( + 'age' => ($value += array( + 'value' => '', + 'min' => '', + 'max' => '', + )), + 'age_op' => $operator, + ); + $filters = array( + 'age' => array( + 'id' => 'age', + 'table' => 'views_test', + 'field' => 'age', + 'relationship' => 'none', + 'exposed' => TRUE, + 'expose' => array( + 'operator' => 'age_op', + 'label' => 'age', + 'identifier' => 'age', + 'use_operator' => TRUE, + ), + ), + ); + $view = $this->getBasicPageView(); + $view->set_display('page_1'); + $view->display['page_1']->handler->override_option('filters', $filters); + $view->set_exposed_input($exposed_input); + $this->executeView($view); + $this->assertIdenticalResultset($view, $resultset, $this->column_map, 'Identical result set for ' . $operator . ' with untouched values.'); + $view->destroy(); + + // Min, max and value fields are shown/hidden only via JS, so they might + // still be set from a previous operation. Assert that this doesn't change + // the expected result set. + $exposed_input['age'] += array( + 'value' => '25', + 'min' => '28', + 'max' => '30', + ); + $view = $this->getBasicPageView(); + $view->set_display('page_1'); + $view->display['page_1']->handler->override_option('filters', $filters); + $view->set_exposed_input($exposed_input); + $this->executeView($view); + $this->assertIdenticalResultset($view, $resultset, $this->column_map, 'Identical result set for ' . $operator . ' with leftover values from previous operation.'); + } public function testAllowEmpty() { $view = $this->getBasicView(); diff --git a/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_filter_string.test b/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_filter_string.test index ab6af92af50..e2551562399 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_filter_string.test +++ b/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_filter_string.test @@ -19,8 +19,12 @@ class ViewsHandlerFilterStringTest extends ViewsSqlTest { ); } - function setUp() { - parent::setUp(); + /** + * {@inheritdoc} + */ + public function setUp(array $modules = array()) { + parent::setUp($modules); + $this->column_map = array( 'views_test_name' => 'name', ); diff --git a/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_manytoone.test b/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_manytoone.test index fb06645eb39..5b08669474e 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_manytoone.test +++ b/profiles/commerce_kickstart/modules/contrib/views/tests/handlers/views_handler_manytoone.test @@ -83,8 +83,8 @@ class ViewsHandlerManyToOneTest extends ViewsSqlTest { /** * {@inheritdoc} */ - public function setUp() { - parent::setUp(); + public function setUp(array $modules = array()) { + parent::setUp($modules); // Create boolean field. $this->fields[0] = array( diff --git a/profiles/commerce_kickstart/modules/contrib/views/tests/styles/views_plugin_style_jump_menu.test b/profiles/commerce_kickstart/modules/contrib/views/tests/styles/views_plugin_style_jump_menu.test index aadaa56b097..3be7858cbdb 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/tests/styles/views_plugin_style_jump_menu.test +++ b/profiles/commerce_kickstart/modules/contrib/views/tests/styles/views_plugin_style_jump_menu.test @@ -29,10 +29,11 @@ class viewsPluginStyleJumpMenuTest extends ViewsSqlTest { } /** - * + * {@inheritdoc} */ - public function setUp() { - parent::setUp(); + public function setUp(array $modules = array()) { + parent::setUp($modules); + $this->nodes = array(); $this->nodes['page'][] = $this->drupalCreateNode(array('type' => 'page')); $this->nodes['page'][] = $this->drupalCreateNode(array('type' => 'page')); diff --git a/profiles/commerce_kickstart/modules/contrib/views/tests/styles/views_plugin_style_mapping.test b/profiles/commerce_kickstart/modules/contrib/views/tests/styles/views_plugin_style_mapping.test index c73cb575442..57ffefd9ac4 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/tests/styles/views_plugin_style_mapping.test +++ b/profiles/commerce_kickstart/modules/contrib/views/tests/styles/views_plugin_style_mapping.test @@ -18,8 +18,11 @@ class ViewsPluginStyleMappingTest extends ViewsPluginStyleTestBase { ); } - public function setUp() { - parent::setUp(); + /** + * {@inheritdoc} + */ + public function setUp(array $modules = array()) { + parent::setUp($modules); // Reset the plugin data. views_fetch_plugin_data(NULL, NULL, TRUE); diff --git a/profiles/commerce_kickstart/modules/contrib/views/tests/taxonomy/views_handler_relationship_node_term_data.test b/profiles/commerce_kickstart/modules/contrib/views/tests/taxonomy/views_handler_relationship_node_term_data.test index 7bce9804b12..954a3cb7f6d 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/tests/taxonomy/views_handler_relationship_node_term_data.test +++ b/profiles/commerce_kickstart/modules/contrib/views/tests/taxonomy/views_handler_relationship_node_term_data.test @@ -31,8 +31,11 @@ class ViewsHandlerRelationshipNodeTermDataTest extends ViewsSqlTest { return $term; } - function setUp() { - parent::setUp(); + /** + * {@inheritdoc} + */ + public function setUp(array $modules = array()) { + parent::setUp($modules); // $web_user = $this->drupalCreateUser(array('create article content')); // $this->drupalLogin($web_user); diff --git a/profiles/commerce_kickstart/modules/contrib/views/tests/user/views_user.test b/profiles/commerce_kickstart/modules/contrib/views/tests/user/views_user.test index 2f0e2cd59e2..8f917aa25b0 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/tests/user/views_user.test +++ b/profiles/commerce_kickstart/modules/contrib/views/tests/user/views_user.test @@ -34,8 +34,8 @@ class ViewsUserTestCase extends ViewsSqlTest { /** * */ - protected function setUp() { - parent::setUp(); + public function setUp(array $modules = array()) { + parent::setUp($modules); $this->users[] = $this->drupalCreateUser(); $this->users[] = user_load(1); diff --git a/profiles/commerce_kickstart/modules/contrib/views/tests/user/views_user_argument_validate.test b/profiles/commerce_kickstart/modules/contrib/views/tests/user/views_user_argument_validate.test index 91aebba9f60..78c24fa013a 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/tests/user/views_user_argument_validate.test +++ b/profiles/commerce_kickstart/modules/contrib/views/tests/user/views_user_argument_validate.test @@ -17,8 +17,13 @@ class ViewsUserArgumentValidate extends ViewsSqlTest { ); } - function setUp() { - parent::setUp('views'); + /** + * {@inheritdoc} + */ + public function setUp(array $modules = array()) { + $modules[] = 'views'; + parent::setUp($modules); + $this->account = $this->drupalCreateUser(); } diff --git a/profiles/commerce_kickstart/modules/contrib/views/tests/views_access.test b/profiles/commerce_kickstart/modules/contrib/views/tests/views_access.test index 73b23efa917..d05436be20f 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/tests/views_access.test +++ b/profiles/commerce_kickstart/modules/contrib/views/tests/views_access.test @@ -20,8 +20,8 @@ class ViewsAccessTest extends ViewsSqlTest { /** * {@inheritdoc} */ - public function setUp() { - parent::setUp(); + public function setUp(array $modules = array()) { + parent::setUp($modules); $this->admin_user = $this->drupalCreateUser(array('access all views')); $this->web_user = $this->drupalCreateUser(); @@ -161,6 +161,26 @@ class ViewsAccessTest extends ViewsSqlTest { $this->assertTrue(views_access($expected_hook_menu, $argument1, $argument2)); } + /** + * Tests access for a view with a missing access plugin. + */ + public function testMissingAccessPlugin() { + $view = $this->getMissingAccessPluginTestView(); + + $view->set_display('default'); + $access_plugin = $view->display_handler->get_plugin('access'); + $this->assertFalse($access_plugin); + + $this->assertTrue($view->display_handler->access($this->admin_user), t('Admin-Account should be able to access the view everytime')); + $this->assertTrue($view->display_handler->access($this->web_user)); + $this->assertTrue($view->display_handler->access($this->normal_user)); + + $hook_menu = $view->execute_hook_menu('page_1'); + $this->assertTrue($hook_menu['test_access_missing']['access arguments'][0]); + + $this->assertTrue(views_access(TRUE)); + } + function view_access_none() { $view = new view; $view->name = 'test_access_none'; @@ -285,4 +305,34 @@ class ViewsAccessTest extends ViewsSqlTest { return $view; } + + /** + * Generates a view with an access plugin that doesn't exist. + */ + protected function getMissingAccessPluginTestView() { + $view = new view(); + $view->name = 'test_access_missing'; + $view->description = ''; + $view->tag = ''; + $view->view_php = ''; + $view->base_table = 'node'; + $view->is_cacheable = FALSE; + $view->api_version = 2; + $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */ + + /* Display: Master */ + $handler = $view->new_display('default', 'Master', 'default'); + $handler->display->display_options['access']['type'] = 'does_not_exist'; + $handler->display->display_options['cache']['type'] = 'none'; + $handler->display->display_options['exposed_form']['type'] = 'basic'; + $handler->display->display_options['pager']['type'] = 'full'; + $handler->display->display_options['style_plugin'] = 'default'; + $handler->display->display_options['row_plugin'] = 'fields'; + + $handler = $view->new_display('page', 'Page', 'page_1'); + $handler->display->display_options['path'] = 'test_access_missing'; + + return $view; + } + } diff --git a/profiles/commerce_kickstart/modules/contrib/views/tests/views_ajax.test b/profiles/commerce_kickstart/modules/contrib/views/tests/views_ajax.test index 685d09ef163..6395044d315 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/tests/views_ajax.test +++ b/profiles/commerce_kickstart/modules/contrib/views/tests/views_ajax.test @@ -17,8 +17,12 @@ class ViewsAjaxTest extends ViewsSqlTest { ); } - public function setUp() { - parent::setUp('views', 'views_test'); + /** + * {@inheritdoc} + */ + public function setUp(array $modules = array()) { + parent::setUp($modules); + // Create a second node. $this->drupalCreateNode(array('type' => 'article', 'status' => NODE_PUBLISHED)); } diff --git a/profiles/commerce_kickstart/modules/contrib/views/tests/views_analyze.test b/profiles/commerce_kickstart/modules/contrib/views/tests/views_analyze.test index d16103ef85a..02ea2d596b8 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/tests/views_analyze.test +++ b/profiles/commerce_kickstart/modules/contrib/views/tests/views_analyze.test @@ -17,9 +17,12 @@ class ViewsAnalyzeTest extends ViewsSqlTest { ); } - public function setUp() { - parent::setUp('views_ui'); - module_enable(array('views_ui')); + /** + * {@inheritdoc} + */ + public function setUp(array $modules = array()) { + parent::setUp($modules); + // @todo Figure out why it's required to clear the cache here. views_module_include('views_default', TRUE); views_get_all_views(TRUE); diff --git a/profiles/commerce_kickstart/modules/contrib/views/tests/views_argument_default.test b/profiles/commerce_kickstart/modules/contrib/views/tests/views_argument_default.test index f6e1282e335..7dea3805e9d 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/tests/views_argument_default.test +++ b/profiles/commerce_kickstart/modules/contrib/views/tests/views_argument_default.test @@ -9,6 +9,7 @@ * Basic test for pluggable argument default. */ class ViewsArgumentDefaultTest extends ViewsSqlTest { + public static function getInfo() { return array( 'name' => 'Argument_default', @@ -17,8 +18,11 @@ class ViewsArgumentDefaultTest extends ViewsSqlTest { ); } - public function setUp() { - parent::setUp('views'); + /** + * {@inheritdoc} + */ + public function setUp(array $modules = array()) { + parent::setUp($modules); $this->random = $this->randomString(); } diff --git a/profiles/commerce_kickstart/modules/contrib/views/tests/views_clone.test b/profiles/commerce_kickstart/modules/contrib/views/tests/views_clone.test index 7ac10aba10f..44ba3cf3173 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/tests/views_clone.test +++ b/profiles/commerce_kickstart/modules/contrib/views/tests/views_clone.test @@ -33,8 +33,8 @@ class ViewsCloneTest extends ViewsSqlTest { /** * {@inheritdoc} */ - public function setUp() { - parent::setUp(); + public function setUp(array $modules = array()) { + parent::setUp($modules); $vocabulary = taxonomy_vocabulary_machine_name_load('tags'); $this->term = $this->createTerm($vocabulary); diff --git a/profiles/commerce_kickstart/modules/contrib/views/tests/views_exposed_form.test b/profiles/commerce_kickstart/modules/contrib/views/tests/views_exposed_form.test index b202564b53b..d0a87fa62db 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/tests/views_exposed_form.test +++ b/profiles/commerce_kickstart/modules/contrib/views/tests/views_exposed_form.test @@ -22,11 +22,11 @@ class ViewsExposedFormTest extends ViewsSqlTest { } /** - * + * {@inheritdoc} */ - public function setUp() { - parent::setUp('views_ui'); - module_enable(array('views_ui')); + public function setUp(array $modules = array()) { + parent::setUp($modules); + // @todo Figure out why it's required to clear the cache here. views_module_include('views_default', TRUE); views_get_all_views(TRUE); diff --git a/profiles/commerce_kickstart/modules/contrib/views/tests/views_glossary.test b/profiles/commerce_kickstart/modules/contrib/views/tests/views_glossary.test index d7d7c64b1e1..56d4a9791e3 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/tests/views_glossary.test +++ b/profiles/commerce_kickstart/modules/contrib/views/tests/views_glossary.test @@ -6,9 +6,13 @@ */ /** - * Tests glossary view ( summary of arguments ). + * Tests the glossary feature. */ class ViewsGlossaryTestCase extends ViewsSqlTest { + + /** + * {@inheritdoc} + */ public static function getInfo() { return array( 'name' => 'Glossary Test', @@ -17,17 +21,13 @@ class ViewsGlossaryTestCase extends ViewsSqlTest { ); } - public function setUp() { - parent::setUp('views'); - } - /** - * Tests the default glossary view. + * Tests the glossary feature. */ public function testGlossaryView() { - // create a contentype and add some nodes, with a non random title. + // Create a content type and add some nodes, with a non random title. $type = $this->drupalCreateContentType(); - $nodes_per_char = array( + $nodes_per_character = array( 'd' => 1, 'r' => 4, 'u' => 10, @@ -35,27 +35,146 @@ class ViewsGlossaryTestCase extends ViewsSqlTest { 'a' => 3, 'l' => 6, ); - foreach ($nodes_per_char as $char => $count) { + $nodes = array(); + foreach ($nodes_per_character as $character => $count) { $setting = array( 'type' => $type->type, ); for ($i = 0; $i < $count; $i++) { $node = $setting; - $node['title'] = $char . $this->randomString(3); - $this->drupalCreateNode($node); + $node['title'] = $character . strtolower($this->randomName()); + $nodes[$character][] = $this->drupalCreateNode($node); } } + // Sort created nodes the same way the view does, so that we can assert + // correct node ids for each row in the result set later. + foreach ($nodes_per_character as $character => $count) { + usort($nodes[$character], function ($a, $b) { + return strcmp($a->title, $b->title); + }); + } + // Execute glossary view. $view = views_get_view('glossary'); $view->set_display('attachment'); $view->execute_display('attachment'); - // Check that the amount of nodes per char. - $result_nodes_per_char = array(); + // Check the amount of nodes per character. foreach ($view->result as $item) { - $this->assertEqual($nodes_per_char[$item->title_truncated], $item->num_records); + $this->assertEqual($nodes_per_character[$item->title_truncated], $item->num_records); + } + $view->destroy(); + + // Checks that a glossary view with an argument containing one letter + // returns only and all the nodes that start with that letter. + $view = views_get_view('glossary'); + $view->init_display(); + $this->executeView($view, array('a')); + $result_count = isset($view->result) && is_array($view->result) ? count($view->result) : 0; + $this->assertIdentical($result_count, 3, 'View has 3 results.'); + foreach ($view->result as $delta => $item) { + $nid = isset($view->result[$delta]->nid) ? $view->result[$delta]->nid : '0'; + $this->assertIdentical($nid, $nodes['a'][$delta]->nid, 'View result ' . (string) (int) $delta . ' has correct node id.'); + } + $view->destroy(); + + // Checks that a glossary view with an argument containing multiple values + // returns only and all nodes that start with these values. + $view = views_get_view('glossary'); + $view->init_display(); + $arguments = $view->display_handler->get_option('arguments'); + $arguments['title']['break_phrase'] = TRUE; + $view->display_handler->set_option('arguments', $arguments); + $this->executeView($view, array('d+p')); + $expected_result_count = $nodes_per_character['d'] + $nodes_per_character['p']; + $result_count = isset($view->result) && is_array($view->result) ? count($view->result) : 0; + $this->assertIdentical($result_count, 3, 'View has 3 results.'); + $nid = isset($view->result[0]->nid) ? $view->result[0]->nid : '0'; + $this->assertIdentical($nid, $nodes['d'][0]->nid, 'View result 0 has correct node id.'); + $nid = isset($view->result[1]->nid) ? $view->result[1]->nid : '0'; + $this->assertIdentical($nid, $nodes['p'][0]->nid, 'View result 1 has correct node id.'); + $nid = isset($view->result[2]->nid) ? $view->result[2]->nid : '0'; + $this->assertIdentical($nid, $nodes['p'][1]->nid, 'View result 2 has correct node id.'); + $view->destroy(); + + // Checks that a glossary view with a phrase as an argument does not + // interpret that phrase as multiple values. + $view = views_get_view('glossary'); + $view->init_display(); + $arguments = $view->display_handler->get_option('arguments'); + $view->display_handler->set_option('arguments', $arguments); + $this->executeView($view, array('d+p')); + $result_count = isset($view->result) && is_array($view->result) ? count($view->result) : 1; + $this->assertIdentical($result_count, 0, 'View has zero results.'); + $view->destroy(); + + // Checks that a glossary view with an argument containing one letter + // excludes all nodes that start with that letter. + $view = views_get_view('glossary'); + $view->init_display(); + $arguments = $view->display_handler->get_option('arguments'); + $arguments['title']['not'] = TRUE; + $view->display_handler->set_option('arguments', $arguments); + $this->executeView($view, array('a')); + $result_count = isset($view->result) && is_array($view->result) ? count($view->result) : 0; + $this->assertIdentical($result_count, 23, 'View has 23 results.'); + $character = 'd'; + $character_delta = 0; + foreach ($view->result as $delta => $item) { + if ($delta === 1) { + $character = 'l'; + $character_delta = 0; + } + elseif ($delta === 7) { + $character = 'p'; + $character_delta = 0; + } + elseif ($delta === 9) { + $character = 'r'; + $character_delta = 0; + } + elseif ($delta === 13) { + $character = 'u'; + $character_delta = 0; + } + $nid = isset($view->result[$delta]->nid) ? $view->result[$delta]->nid : '0'; + $this->assertIdentical($nid, $nodes[$character][$character_delta]->nid, 'View result ' . (string) (int) $delta . ' has correct node id.'); + $character_delta++; + } + $view->destroy(); + + // Checks that a glossary view with an argument containing multiple values + // excludes all nodes that start with these values. + $view = views_get_view('glossary'); + $view->init_display(); + $arguments = $view->display_handler->get_option('arguments'); + $arguments['title']['break_phrase'] = TRUE; + $arguments['title']['not'] = TRUE; + $view->display_handler->set_option('arguments', $arguments); + $this->executeView($view, array('a+p')); + $result_count = isset($view->result) && is_array($view->result) ? count($view->result) : 0; + $this->assertIdentical($result_count, 21, 'View has 21 results.'); + $character = 'd'; + $character_delta = 0; + foreach ($view->result as $delta => $item) { + if ($delta === 1) { + $character = 'l'; + $character_delta = 0; + } + elseif ($delta === 7) { + $character = 'r'; + $character_delta = 0; + } + elseif ($delta === 11) { + $character = 'u'; + $character_delta = 0; + } + $nid = isset($view->result[$delta]->nid) ? $view->result[$delta]->nid : '0'; + $this->assertIdentical($nid, $nodes[$character][$character_delta]->nid, 'View result ' . (string) (int) $delta . ' has correct node id.'); + $character_delta++; } + $view->destroy(); } } diff --git a/profiles/commerce_kickstart/modules/contrib/views/tests/views_groupby.test b/profiles/commerce_kickstart/modules/contrib/views/tests/views_groupby.test index 364e61b3847..2c873ad8f40 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/tests/views_groupby.test +++ b/profiles/commerce_kickstart/modules/contrib/views/tests/views_groupby.test @@ -435,9 +435,11 @@ class ViewsUiGroupbyTestCase extends DrupalWebTestCase { /** * {@inheritdoc} */ - function setUp() { - // Enable views_ui. - parent::setUp('views_ui', 'views_test'); + public function setUp(array $modules = array()) { + $modules[] = 'views'; + $modules[] = 'views_ui'; + $modules[] = 'views_test'; + parent::setUp($modules); // Create and log in a user with administer views permission. $views_admin = $this->drupalCreateUser(array('administer views', 'administer blocks', 'bypass node access', 'access user profiles', 'view revisions')); diff --git a/profiles/commerce_kickstart/modules/contrib/views/tests/views_handler_filter.test b/profiles/commerce_kickstart/modules/contrib/views/tests/views_handler_filter.test index e41b35a39fd..69fdb822f43 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/tests/views_handler_filter.test +++ b/profiles/commerce_kickstart/modules/contrib/views/tests/views_handler_filter.test @@ -24,9 +24,8 @@ class ViewsHandlerFilterTest extends ViewsSqlTest { /** * {@inheritdoc} */ - protected function setUp() { - // The Views and Views UI modules will be enabled with this. - parent::setUp(); + public function setUp(array $modules = array()) { + parent::setUp($modules); // Assign vocabulary 'tag' to user entity. $field_definition = field_read_field('field_tags'); diff --git a/profiles/commerce_kickstart/modules/contrib/views/tests/views_handlers.test b/profiles/commerce_kickstart/modules/contrib/views/tests/views_handlers.test index e0ace985034..ee19d197ed1 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/tests/views_handlers.test +++ b/profiles/commerce_kickstart/modules/contrib/views/tests/views_handlers.test @@ -17,11 +17,6 @@ class ViewsHandlersTest extends ViewsSqlTest { ); } - protected function setUp() { - parent::setUp('views', 'views_ui'); - module_enable(array('views_ui')); - } - function testFilterInOperatorUi() { $admin_user = $this->drupalCreateUser(array('administer views', 'administer site configuration')); $this->drupalLogin($admin_user); diff --git a/profiles/commerce_kickstart/modules/contrib/views/tests/views_module.test b/profiles/commerce_kickstart/modules/contrib/views/tests/views_module.test index c3dd99478c7..cfa93ab7cbf 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/tests/views_module.test +++ b/profiles/commerce_kickstart/modules/contrib/views/tests/views_module.test @@ -17,8 +17,12 @@ class ViewsModuleTest extends ViewsSqlTest { ); } - public function setUp() { - parent::setUp(); + /** + * {@inheritdoc} + */ + public function setUp(array $modules = array()) { + parent::setUp($modules); + drupal_theme_rebuild(); } diff --git a/profiles/commerce_kickstart/modules/contrib/views/tests/views_pager.test b/profiles/commerce_kickstart/modules/contrib/views/tests/views_pager.test index 7a99fb67d97..ac7658ed8d0 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/tests/views_pager.test +++ b/profiles/commerce_kickstart/modules/contrib/views/tests/views_pager.test @@ -21,13 +21,6 @@ class ViewsPagerTest extends ViewsSqlTest { ); } - /** - * - */ - public function setUp() { - parent::setUp('views', 'views_ui', 'views_test'); - } - /** * Pagers was sometimes not stored. * diff --git a/profiles/commerce_kickstart/modules/contrib/views/tests/views_query.test b/profiles/commerce_kickstart/modules/contrib/views/tests/views_query.test index ed10f3bed23..b2f0029133e 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/tests/views_query.test +++ b/profiles/commerce_kickstart/modules/contrib/views/tests/views_query.test @@ -179,8 +179,10 @@ abstract class ViewsSqlTest extends ViewsTestCase { /** * {@inheritdoc} */ - protected function setUp() { - parent::setUp('views', 'views_ui'); + public function setUp(array $modules = array()) { + $modules[] = 'views'; + $modules[] = 'views_ui'; + parent::setUp($modules); // Define the schema and views data variable before enabling the test // module. diff --git a/profiles/commerce_kickstart/modules/contrib/views/tests/views_test.info b/profiles/commerce_kickstart/modules/contrib/views/tests/views_test.info index d7e79211ed3..d3ce59ddd59 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/tests/views_test.info +++ b/profiles/commerce_kickstart/modules/contrib/views/tests/views_test.info @@ -5,8 +5,8 @@ core = 7.x dependencies[] = views hidden = TRUE -; Information added by Drupal.org packaging script on 2019-05-10 -version = "7.x-3.23" +; Information added by Drupal.org packaging script on 2020-03-07 +version = "7.x-3.24" core = "7.x" project = "views" -datestamp = "1557505389" +datestamp = "1583615732" diff --git a/profiles/commerce_kickstart/modules/contrib/views/tests/views_translatable.test b/profiles/commerce_kickstart/modules/contrib/views/tests/views_translatable.test index 9c3422c750e..7e68acc59c9 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/tests/views_translatable.test +++ b/profiles/commerce_kickstart/modules/contrib/views/tests/views_translatable.test @@ -37,8 +37,11 @@ class ViewsTranslatableTest extends ViewsSqlTest { ); } - public function setUp() { - parent::setUp(); + /** + * {@inheritdoc} + */ + public function setUp(array $modules = array()) { + parent::setUp($modules); variable_set('views_localization_plugin', 'test'); // Reset the plugin data. diff --git a/profiles/commerce_kickstart/modules/contrib/views/tests/views_ui.test b/profiles/commerce_kickstart/modules/contrib/views/tests/views_ui.test index 8a7e0e16c92..81a662c58a1 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/tests/views_ui.test +++ b/profiles/commerce_kickstart/modules/contrib/views/tests/views_ui.test @@ -11,11 +11,11 @@ class ViewsUIWizardHelper extends DrupalWebTestCase { /** - * + * {@inheritdoc} */ - function setUp() { - // Enable views_ui. - parent::setUp('views_ui'); + public function setUp(array $modules = array()) { + $modules[] = 'views_ui'; + parent::setUp($modules); // Create and log in a user with administer views permission. $views_admin = $this->drupalCreateUser(array('administer views', 'administer blocks', 'bypass node access', 'access user profiles', 'view revisions')); @@ -317,8 +317,8 @@ class ViewsUIWizardTaggedWithTestCase extends ViewsUIWizardHelper { /** * {@inheritdoc} */ - function setUp() { - parent::setUp(); + public function setUp(array $modules = array()) { + parent::setUp($modules); // Create two content types. One will have an autocomplete tagging field, // and one won't. diff --git a/profiles/commerce_kickstart/modules/contrib/views/tests/views_upgrade.test b/profiles/commerce_kickstart/modules/contrib/views/tests/views_upgrade.test index 0c4da8fb7c1..0b3c17f2b9f 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/tests/views_upgrade.test +++ b/profiles/commerce_kickstart/modules/contrib/views/tests/views_upgrade.test @@ -26,11 +26,11 @@ class ViewsUpgradeTestCase extends ViewsSqlTest { /** * {@inheritdoc} */ - protected function setUp() { + public function setUp(array $modules = array()) { + parent::setUp($modules); + // To import a view the user needs use PHP for settings rights, so enable // PHP module. - parent::setUp(); - module_enable(array('php')); $this->resetAll(); } diff --git a/profiles/commerce_kickstart/modules/contrib/views/views.info b/profiles/commerce_kickstart/modules/contrib/views/views.info index a857b233f98..df73eacc681 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/views.info +++ b/profiles/commerce_kickstart/modules/contrib/views/views.info @@ -268,7 +268,6 @@ files[] = plugins/views_plugin_style_table.inc files[] = tests/handlers/views_handlers.test files[] = tests/handlers/views_handler_area_text.test files[] = tests/handlers/views_handler_argument_null.test -files[] = tests/handlers/views_handler_argument_string.test files[] = tests/handlers/views_handler_field.test files[] = tests/handlers/views_handler_field_boolean.test files[] = tests/handlers/views_handler_field_custom.test @@ -331,8 +330,8 @@ files[] = tests/views_clone.test files[] = tests/views_view.test files[] = tests/views_ui.test -; Information added by Drupal.org packaging script on 2019-05-10 -version = "7.x-3.23" +; Information added by Drupal.org packaging script on 2020-03-07 +version = "7.x-3.24" core = "7.x" project = "views" -datestamp = "1557505389" +datestamp = "1583615732" diff --git a/profiles/commerce_kickstart/modules/contrib/views/views.module b/profiles/commerce_kickstart/modules/contrib/views/views.module index aa407f028a7..d37bfda9a1a 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/views.module +++ b/profiles/commerce_kickstart/modules/contrib/views/views.module @@ -2197,6 +2197,97 @@ function views_form_views_exposed_form_alter(&$form, &$form_state) { $form['form_build_id']['#access'] = FALSE; $form['form_token']['#access'] = FALSE; $form['form_id']['#access'] = FALSE; + + // AJAX behaviors need these data, so we add it back in #after_build. + $form['#after_build'][] = 'views_exposed_form_ajax_enable'; +} + +/** + * Checks whether the exposed form will use AJAX. + * + * Passes required form information removed in + * views_form_views_exposed_form_alter(). + * + * @param array $form + * The form being processed. + * @param array $form_state + * The form state. + * + * @return array + * The form being processed. + */ +function views_exposed_form_ajax_enable(array &$form, array &$form_state) { + // In order for AJAX to work the form build info is needed. Here check if + // #ajax has been added to any form elements, and if so, pass this info as + // settings via JavaScript, which get attached to the submitted form on AJAX + // form submissions. #ajax property can be set not only for the first level of + // the form, so look for it in the whole form. + $ajax_info = array(); + $ajax_elements = views_exposed_form_ajax_lookup_recursive($form); + // Determine if form is being processed. + $triggering_element_name = ''; + if (!empty($form_state['input']['_triggering_element_name'])) { + $triggering_element_name = $form_state['input']['_triggering_element_name']; + } + + // When there are multiple elements with #ajax set on exposed form, pass only + // triggering element name to JavaScript, otherwise #ajax will work only for + // the first element. + if (!empty($triggering_element_name) && !empty($ajax_elements)) { + // Check if element has #ajax property set. + if (in_array($triggering_element_name, $ajax_elements)) { + $ajax_elements = array( + $triggering_element_name => $triggering_element_name, + ); + } + else { + $ajax_elements = array(); + } + } + + if (!empty($ajax_elements)) { + $form_info = array( + 'form_id' => $form['#form_id'], + 'form_build_id' => $form['#build_id'], + ); + // Anonymous users don't get a token. + if (!empty($form['#token'])) { + $form_info['form_token'] = drupal_get_token($form['#token']); + } + foreach ($ajax_elements as $element_name) { + $ajax_info[$element_name] = $form_info; + } + drupal_add_js(array('ViewsExposedFormInfo' => $ajax_info), 'setting'); + + // Add the javascript behavior that will handle this data. + $form['#attached']['js'][] = array( + 'weight' => 100, + 'data' => drupal_get_path('module', 'views') . '/js/exposed-form-ajax.js', + ); + } + + return $form; +} + +/** + * Recursively looks for the #ajax property for every form elemet. + * + * @param array $elements + * The element array to look for #ajax property. + * + * @return array + * Array of the elements names where #ajax was found. + */ +function views_exposed_form_ajax_lookup_recursive(array $elements) { + $ajax_elements = array(); + foreach (element_children($elements) as $key) { + if (!empty($elements[$key]['#name']) && !empty($elements[$key]['#ajax'])) { + $ajax_elements[$elements[$key]['#name']] = $elements[$key]['#name']; + } + // Recursive call to look for #ajax in element's childrens. + $ajax_elements += views_exposed_form_ajax_lookup_recursive($elements[$key]); + } + return $ajax_elements; } /** diff --git a/profiles/commerce_kickstart/modules/contrib/views/views_ui.info b/profiles/commerce_kickstart/modules/contrib/views/views_ui.info index 7a8e5383d81..88549716392 100644 --- a/profiles/commerce_kickstart/modules/contrib/views/views_ui.info +++ b/profiles/commerce_kickstart/modules/contrib/views/views_ui.info @@ -8,8 +8,8 @@ dependencies[] = views files[] = views_ui.module files[] = plugins/views_wizard/views_ui_base_views_wizard.class.php -; Information added by Drupal.org packaging script on 2019-05-10 -version = "7.x-3.23" +; Information added by Drupal.org packaging script on 2020-03-07 +version = "7.x-3.24" core = "7.x" project = "views" -datestamp = "1557505389" +datestamp = "1583615732" diff --git a/profiles/commerce_kickstart/modules/contrib/views_bulk_operations/actions/modify.action.inc b/profiles/commerce_kickstart/modules/contrib/views_bulk_operations/actions/modify.action.inc index e46268d1fd1..e7fc38f84cd 100644 --- a/profiles/commerce_kickstart/modules/contrib/views_bulk_operations/actions/modify.action.inc +++ b/profiles/commerce_kickstart/modules/contrib/views_bulk_operations/actions/modify.action.inc @@ -570,7 +570,7 @@ function _views_bulk_operations_modify_action_get_bundles($entity_type, $context $has_enabled_fields = FALSE; foreach ($display_values as $key) { - if (strpos($key, $bundle_name . '::') !== FALSE) { + if (strpos($key, $bundle_name . '::') === 0) { $has_enabled_fields = TRUE; } } @@ -588,7 +588,7 @@ function _views_bulk_operations_modify_action_get_bundles($entity_type, $context * Helper function that recursively strips #required from field widgets. */ function _views_bulk_operations_modify_action_unset_required(&$element) { - unset($element['#required']); + $element['#required'] = FALSE; foreach (element_children($element) as $key) { _views_bulk_operations_modify_action_unset_required($element[$key]); } diff --git a/profiles/commerce_kickstart/modules/contrib/views_bulk_operations/actions_permissions.info b/profiles/commerce_kickstart/modules/contrib/views_bulk_operations/actions_permissions.info index 1f97bd510a3..d1d1f499352 100644 --- a/profiles/commerce_kickstart/modules/contrib/views_bulk_operations/actions_permissions.info +++ b/profiles/commerce_kickstart/modules/contrib/views_bulk_operations/actions_permissions.info @@ -3,8 +3,8 @@ description = Provides permission-based access control for actions. Used by View package = Administration core = 7.x -; Information added by Drupal.org packaging script on 2018-05-08 -version = "7.x-3.5" +; Information added by Drupal.org packaging script on 2020-06-03 +version = "7.x-3.6" core = "7.x" project = "views_bulk_operations" -datestamp = "1525821486" +datestamp = "1591196779" diff --git a/profiles/commerce_kickstart/modules/contrib/views_bulk_operations/views/views_bulk_operations.views.inc b/profiles/commerce_kickstart/modules/contrib/views_bulk_operations/views/views_bulk_operations.views.inc index e6c685a07bc..805e45f6ac2 100644 --- a/profiles/commerce_kickstart/modules/contrib/views_bulk_operations/views/views_bulk_operations.views.inc +++ b/profiles/commerce_kickstart/modules/contrib/views_bulk_operations/views/views_bulk_operations.views.inc @@ -17,6 +17,10 @@ function views_bulk_operations_views_data_alter(&$data) { 'click sortable' => FALSE, ), ); + // Check that the base table has the entity type key set. + if (!isset($data[$info['base table']]['table']['entity type'])) { + $data[$info['base table']]['table']['entity type'] = $entity_type; + } } if (isset($info['revision table']) && isset($data[$info['revision table']]['table'])) { $data[$info['revision table']]['views_bulk_operations'] = array( diff --git a/profiles/commerce_kickstart/modules/contrib/views_bulk_operations/views/views_bulk_operations_handler_field_operations.inc b/profiles/commerce_kickstart/modules/contrib/views_bulk_operations/views/views_bulk_operations_handler_field_operations.inc index 9b1f88ec39c..d1bd382c20b 100644 --- a/profiles/commerce_kickstart/modules/contrib/views_bulk_operations/views/views_bulk_operations_handler_field_operations.inc +++ b/profiles/commerce_kickstart/modules/contrib/views_bulk_operations/views/views_bulk_operations_handler_field_operations.inc @@ -65,6 +65,7 @@ class views_bulk_operations_handler_field_operations extends views_handler_field 'force_single' => array('default' => FALSE), 'entity_load_capacity' => array('default' => 10), 'skip_batching' => array('default' => 0), + 'save_view_object_when_batching' => array('default' => 0), ), ); $options['vbo_operations'] = array( @@ -153,6 +154,12 @@ class views_bulk_operations_handler_field_operations extends views_handler_field '#default_value' => $this->options['vbo_settings']['skip_batching'], '#description' => '' . t('Warning:') . ' ' . t('This will cause timeouts for larger amounts of selected items.'), ); + $form['vbo_settings']['save_view_object_when_batching'] = array( + '#type' => 'checkbox', + '#title' => t('Save the whole view object when batching'), + '#default_value' => $this->options['vbo_settings']['save_view_object_when_batching'], + '#description' => '' . t('Warning:') . ' ' . t('Use this option when your view contains query conditions which are not defined as arguments.'), + ); // Display operations and their settings. $form['vbo_operations'] = array( diff --git a/profiles/commerce_kickstart/modules/contrib/views_bulk_operations/views_bulk_operations.drush.inc b/profiles/commerce_kickstart/modules/contrib/views_bulk_operations/views_bulk_operations.drush.inc index e9ca1929e45..70b1d7d05fa 100644 --- a/profiles/commerce_kickstart/modules/contrib/views_bulk_operations/views_bulk_operations.drush.inc +++ b/profiles/commerce_kickstart/modules/contrib/views_bulk_operations/views_bulk_operations.drush.inc @@ -75,6 +75,7 @@ function views_bulk_operations_drush_list() { * Implementation of 'vbo execute' command. */ function views_bulk_operations_drush_execute($vid = NULL, $operation_id = NULL) { + $args = func_get_args(); // Parse arguments. if (is_null($vid)) { drush_set_error('VIEWS_BULK_OPERATIONS_MISSING_VID', dt('Please specify a view ID to execute.')); @@ -84,7 +85,6 @@ function views_bulk_operations_drush_execute($vid = NULL, $operation_id = NULL) drush_set_error('VIEWS_BULK_OPERATIONS_MISSING_OPERATION', dt('Please specify an operation to execute.')); return; } - $args = func_get_args(); $view_exposed_input = array(); $operation_arguments = array(); $view_arguments = array(); diff --git a/profiles/commerce_kickstart/modules/contrib/views_bulk_operations/views_bulk_operations.info b/profiles/commerce_kickstart/modules/contrib/views_bulk_operations/views_bulk_operations.info index 058bef7adee..dc0fcc14045 100644 --- a/profiles/commerce_kickstart/modules/contrib/views_bulk_operations/views_bulk_operations.info +++ b/profiles/commerce_kickstart/modules/contrib/views_bulk_operations/views_bulk_operations.info @@ -9,8 +9,8 @@ php = 5.2.9 files[] = plugins/operation_types/base.class.php files[] = views/views_bulk_operations_handler_field_operations.inc -; Information added by Drupal.org packaging script on 2018-05-08 -version = "7.x-3.5" +; Information added by Drupal.org packaging script on 2020-06-03 +version = "7.x-3.6" core = "7.x" project = "views_bulk_operations" -datestamp = "1525821486" +datestamp = "1591196779" diff --git a/profiles/commerce_kickstart/modules/contrib/views_bulk_operations/views_bulk_operations.module b/profiles/commerce_kickstart/modules/contrib/views_bulk_operations/views_bulk_operations.module index 081e5407225..d6e27496596 100644 --- a/profiles/commerce_kickstart/modules/contrib/views_bulk_operations/views_bulk_operations.module +++ b/profiles/commerce_kickstart/modules/contrib/views_bulk_operations/views_bulk_operations.module @@ -791,6 +791,7 @@ function views_bulk_operations_execute($vbo, $operation, $selection, $select_all // Determine if the operation needs to be executed directly. $aggregate = $operation->aggregate(); $skip_batching = $vbo->get_vbo_option('skip_batching'); + $save_view = $vbo->get_vbo_option('save_view_object_when_batching'); $force_single = $vbo->get_vbo_option('force_single'); $execute_directly = ($aggregate || $skip_batching || $force_single); // Try to load all rows without a batch if needed. @@ -812,6 +813,10 @@ function views_bulk_operations_execute($vbo, $operation, $selection, $select_all 'exposed_input' => $vbo->view->get_exposed_input(), ), ); + // If defined, save the whole view object. + if ($save_view) { + $options['view_info']['view'] = $vbo->view; + } // Create an array of rows in the needed format. $rows = array(); $current = 1; @@ -911,10 +916,18 @@ function views_bulk_operations_adjust_selection($queue_name, $operation, $option } $view_info = $options['view_info']; - $view = views_get_view($view_info['name']); - $view->set_exposed_input($view_info['exposed_input']); - $view->set_arguments($view_info['arguments']); - $view->set_display($view_info['display']); + if (isset($view_info['view'])) { + $view = $view_info['view']; + // Because of the offset, we want our view to be re-build and re-executed. + $view->built = FALSE; + $view->executed = FALSE; + } + else { + $view = views_get_view($view_info['name']); + $view->set_exposed_input($view_info['exposed_input']); + $view->set_arguments($view_info['arguments']); + $view->set_display($view_info['display']); + } $view->set_offset($context['sandbox']['progress']); $view->build(); $view->execute($view_info['display']); diff --git a/profiles/commerce_kickstart/modules/contrib/views_bulk_operations/views_bulk_operations.rules.inc b/profiles/commerce_kickstart/modules/contrib/views_bulk_operations/views_bulk_operations.rules.inc index 78fae2063ed..8ac28802daf 100644 --- a/profiles/commerce_kickstart/modules/contrib/views_bulk_operations/views_bulk_operations.rules.inc +++ b/profiles/commerce_kickstart/modules/contrib/views_bulk_operations/views_bulk_operations.rules.inc @@ -114,8 +114,8 @@ function views_bulk_operations_rules_action_info() { function views_bulk_operations_views_list() { $selectable_displays = array(); foreach (views_get_enabled_views() as $name => $base_view) { - $view = $base_view->clone_view(); foreach ($base_view->display as $display_name => $display) { + $view = $base_view->clone_view(); if (!$view->set_display($display_name)) { continue; } diff --git a/profiles/commerce_kickstart/tests/commerce_kickstart_overrides_test/commerce_kickstart_overrides_test.info b/profiles/commerce_kickstart/tests/commerce_kickstart_overrides_test/commerce_kickstart_overrides_test.info index 1d9587701cf..22de3b7bda7 100644 --- a/profiles/commerce_kickstart/tests/commerce_kickstart_overrides_test/commerce_kickstart_overrides_test.info +++ b/profiles/commerce_kickstart/tests/commerce_kickstart_overrides_test/commerce_kickstart_overrides_test.info @@ -27,8 +27,8 @@ features[features_overrides][] = field.node-blog_post-title_field.field_instance features[features_overrides][] = field.node-blog_post-title_field.field_instance|display|teaser|weight features[features_overrides][] = variable.field_bundle_settings_node__blog_post.value|view_modes|product_list|custom_settings -; Information added by Drupal.org packaging script on 2020-06-18 -version = "7.x-2.66" +; Information added by Drupal.org packaging script on 2021-02-03 +version = "7.x-2.70" core = "7.x" project = "commerce_kickstart" -datestamp = "1592498718" +datestamp = "1612329185" diff --git a/profiles/commerce_kickstart/themes/commerce_kickstart_admin/commerce_kickstart_admin.info b/profiles/commerce_kickstart/themes/commerce_kickstart_admin/commerce_kickstart_admin.info index 4ec704f9aa5..e4a690581c1 100644 --- a/profiles/commerce_kickstart/themes/commerce_kickstart_admin/commerce_kickstart_admin.info +++ b/profiles/commerce_kickstart/themes/commerce_kickstart_admin/commerce_kickstart_admin.info @@ -16,8 +16,8 @@ regions[footer] = Footer regions_hidden[] = sidebar_first -; Information added by Drupal.org packaging script on 2020-06-18 -version = "7.x-2.66" +; Information added by Drupal.org packaging script on 2021-02-03 +version = "7.x-2.70" core = "7.x" project = "commerce_kickstart" -datestamp = "1592498718" +datestamp = "1612329185" diff --git a/profiles/commerce_kickstart/themes/commerce_kickstart_theme/commerce_kickstart_theme.info b/profiles/commerce_kickstart/themes/commerce_kickstart_theme/commerce_kickstart_theme.info index f27f093ed6e..215b9191823 100644 --- a/profiles/commerce_kickstart/themes/commerce_kickstart_theme/commerce_kickstart_theme.info +++ b/profiles/commerce_kickstart/themes/commerce_kickstart_theme/commerce_kickstart_theme.info @@ -420,8 +420,8 @@ settings[alpha_region_footer2_second_suffix] = '' settings[alpha_region_footer2_second_weight] = '2' settings[alpha_region_footer2_second_css] = '' -; Information added by Drupal.org packaging script on 2020-06-18 -version = "7.x-2.66" +; Information added by Drupal.org packaging script on 2021-02-03 +version = "7.x-2.70" core = "7.x" project = "commerce_kickstart" -datestamp = "1592498718" +datestamp = "1612329185" diff --git a/scripts/run-tests.sh b/scripts/run-tests.sh index 8c0be40ef34..f5c4a144f9f 100755 --- a/scripts/run-tests.sh +++ b/scripts/run-tests.sh @@ -264,6 +264,10 @@ function simpletest_script_init($server_software) { // '_' is an environment variable set by the shell. It contains the command that was executed. $php = $php_env; } + elseif (defined('PHP_BINARY') && $php_env = PHP_BINARY) { + // 'PHP_BINARY' specifies the PHP binary path during script execution. Available since PHP 5.4. + $php = $php_env; + } elseif ($sudo = getenv('SUDO_COMMAND')) { // 'SUDO_COMMAND' is an environment variable set by the sudo program. // Extract only the PHP interpreter, not the rest of the command. diff --git a/sites/default/default.settings.php b/sites/default/default.settings.php index da45fc36129..713662df69f 100644 --- a/sites/default/default.settings.php +++ b/sites/default/default.settings.php @@ -246,6 +246,24 @@ */ $databases = array(); +/** + * Quoting of identifiers in MySQL. + * + * To allow compatibility with newer versions of MySQL, Drupal will quote table + * names and some other identifiers. The ANSI standard character for identifier + * quoting is the double quote (") and that can be used by MySQL along with the + * sql_mode setting of ANSI_QUOTES. However, MySQL's own default is to use + * backticks (`). Drupal 7 uses backticks for compatibility. If you need to + * change this, you can do so with this variable. It's possible to switch off + * identifier quoting altogether by setting this variable to an empty string. + * + * @see https://www.drupal.org/project/drupal/issues/2978575 + * @see https://dev.mysql.com/doc/refman/8.0/en/identifiers.html + * @see \DatabaseConnection_mysql::setPrefix + * @see \DatabaseConnection_mysql::quoteIdentifier + */ +# $conf['mysql_identifier_quote_character'] = '"'; + /** * Access control for update.php script. * @@ -668,3 +686,42 @@ 'node_modules', 'bower_components', ); + +/** + * Logging of user flood control events. + * + * Drupal's user module will place a temporary block on a given IP address or + * user account if there are excessive failed login attempts. By default these + * flood control events will be logged. This can be useful for identifying + * brute force login attacks. Set this variable to FALSE to disable logging, for + * example if you are using the dblog module and want to avoid database writes. + * + * @see user_login_final_validate() + * @see user_user_flood_control() + */ +# $conf['log_user_flood_control'] = FALSE; + +/** + * Opt out of variable_initialize() locking optimization. + * + * After lengthy discussion in https://www.drupal.org/node/973436 a change was + * made in variable_initialize() in order to avoid excessive waiting under + * certain conditions. Set this variable to TRUE in order to opt out of this + * optimization and revert to the original behaviour. + */ +# $conf['variable_initialize_wait_for_lock'] = FALSE; + +/** + * Use site name as display-name in outgoing mail. + * + * Drupal can use the site name (i.e. the value of the site_name variable) as + * the display-name when sending e-mail. For example this would mean the sender + * might be "Acme Website" as opposed to just the e-mail + * address alone. In order to avoid disruption this is not enabled by default + * for existing sites. The feature can be enabled by setting this variable to + * TRUE. + * + * @see https://tools.ietf.org/html/rfc2822 + * @see drupal_mail() + */ +$conf['mail_display_name_site_name'] = TRUE;