Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drupal 7.89 #30

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
Drupal 7.89, 2022-03-02
-----------------------
- Bug fixes for PHP 8.1
- Fix tests for PostgreSQL

Drupal 7.88, 2022-02-15
-----------------------
- Fixed security issues:
Expand Down
4 changes: 2 additions & 2 deletions includes/bootstrap.inc
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
/**
* The current system version.
*/
define('VERSION', '7.88');
define('VERSION', '7.89');

/**
* Core API compatibility.
Expand Down Expand Up @@ -1930,7 +1930,7 @@ function format_string($string, array $args = array()) {
* @ingroup sanitization
*/
function check_plain($text) {
return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
return htmlspecialchars((string) $text, ENT_QUOTES, 'UTF-8');
}

/**
Expand Down
3 changes: 2 additions & 1 deletion includes/database/pgsql/database.inc
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ class DatabaseConnection_pgsql extends DatabaseConnection {
case Database::RETURN_AFFECTED:
return $stmt->rowCount();
case Database::RETURN_INSERT_ID:
return $this->connection->lastInsertId($options['sequence_name']);
$sequence_name = isset($options['sequence_name']) ? $options['sequence_name'] : NULL;
return $this->connection->lastInsertId($sequence_name);
case Database::RETURN_NULL:
return;
default:
Expand Down
4 changes: 2 additions & 2 deletions includes/database/pgsql/query.inc
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class InsertQuery_pgsql extends InsertQuery {
foreach ($this->insertFields as $idx => $field) {
if (isset($table_information->blob_fields[$field])) {
$blobs[$blob_count] = fopen('php://memory', 'a');
fwrite($blobs[$blob_count], $insert_values[$idx]);
fwrite($blobs[$blob_count], (string) $insert_values[$idx]);
rewind($blobs[$blob_count]);

$stmt->bindParam(':db_insert_placeholder_' . $max_placeholder++, $blobs[$blob_count], PDO::PARAM_LOB);
Expand Down Expand Up @@ -182,7 +182,7 @@ class UpdateQuery_pgsql extends UpdateQuery {

if (isset($table_information->blob_fields[$field])) {
$blobs[$blob_count] = fopen('php://memory', 'a');
fwrite($blobs[$blob_count], $value);
fwrite($blobs[$blob_count], (string) $value);
rewind($blobs[$blob_count]);
$stmt->bindParam($placeholder, $blobs[$blob_count], PDO::PARAM_LOB);
++$blob_count;
Expand Down
148 changes: 135 additions & 13 deletions includes/database/pgsql/schema.inc
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,64 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
*/
protected $tableInformation = array();

/**
* The maximum allowed length for index, primary key and constraint names.
*
* Value will usually be set to a 63 chars limit but PostgreSQL allows
* to higher this value before compiling, so we need to check for that.
*
* @var int
*/
protected $maxIdentifierLength;

/**
* Make sure to limit identifiers according to PostgreSQL compiled in length.
*
* PostgreSQL allows in standard configuration identifiers no longer than 63
* chars for table/relation names, indexes, primary keys, and constraints. So
* we map all identifiers that are too long to drupal_base64hash_tag, where
* tag is one of:
* - idx for indexes
* - key for constraints
* - pkey for primary keys
* - seq for sequences
*
* @param string $table_identifier_part
* The first argument used to build the identifier string. This usually
* refers to a table/relation name.
* @param string $column_identifier_part
* The second argument used to build the identifier string. This usually
* refers to one or more column names.
* @param string $tag
* The identifier tag. It can be one of 'idx', 'key', 'pkey' or 'seq'.
*
* @return string
* The index/constraint/pkey identifier.
*/
protected function ensureIdentifiersLength($table_identifier_part, $column_identifier_part, $tag) {
$info = $this->getPrefixInfo($table_identifier_part);
$table_identifier_part = $info['table'];

// Filters out potentially empty $column_identifier_part to ensure
// compatibility with old naming convention (see prefixNonTable()).
$identifiers = array_filter(array($table_identifier_part, $column_identifier_part, $tag));
$identifierName = implode('_', $identifiers);

// Retrieve the max identifier length which is usually 63 characters
// but can be altered before PostgreSQL is compiled so we need to check.
if (empty($this->maxIdentifierLength)) {
$this->maxIdentifierLength = $this->connection->query("SHOW max_identifier_length")->fetchField();
}

if (strlen($identifierName) > $this->maxIdentifierLength) {
$saveIdentifier = 'drupal_' . $this->hashBase64($identifierName) . '_' . $tag;
}
else {
$saveIdentifier = $identifierName;
}
return $saveIdentifier;
}

/**
* Fetch the list of blobs and sequences used on a table.
*
Expand Down Expand Up @@ -124,11 +182,11 @@ class DatabaseSchema_pgsql extends DatabaseSchema {

$sql_keys = array();
if (isset($table['primary key']) && is_array($table['primary key'])) {
$sql_keys[] = 'PRIMARY KEY (' . implode(', ', $table['primary key']) . ')';
$sql_keys[] = 'CONSTRAINT ' . $this->ensureIdentifiersLength($name, '', 'pkey') . ' PRIMARY KEY (' . implode(', ', $table['primary key']) . ')';
}
if (isset($table['unique keys']) && is_array($table['unique keys'])) {
foreach ($table['unique keys'] as $key_name => $key) {
$sql_keys[] = 'CONSTRAINT ' . $this->prefixNonTable($name, $key_name, 'key') . ' UNIQUE (' . implode(', ', $key) . ')';
$sql_keys[] = 'CONSTRAINT ' . $this->ensureIdentifiersLength($name, $key_name, 'key') . ' UNIQUE (' . implode(', ', $key) . ')';
}
}

Expand Down Expand Up @@ -328,10 +386,31 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
// rename them when renaming the table.
$indexes = $this->connection->query('SELECT indexname FROM pg_indexes WHERE schemaname = :schema AND tablename = :table', array(':schema' => $old_schema, ':table' => $old_table_name));
foreach ($indexes as $index) {
if (preg_match('/^' . preg_quote($old_full_name) . '_(.*)$/', $index->indexname, $matches)) {
// Get the index type by suffix, e.g. idx/key/pkey
$index_type = substr($index->indexname, strrpos($index->indexname, '_') + 1);

// If the index is already rewritten by ensureIdentifiersLength() to not
// exceed the 63 chars limit of PostgreSQL, we need to take care of that.
// Example (drupal_Gk7Su_T1jcBHVuvSPeP22_I3Ni4GrVEgTYlIYnBJkro_idx).
if (strpos($index->indexname, 'drupal_') !== FALSE) {
preg_match('/^drupal_(.*)_' . preg_quote($index_type) . '/', $index->indexname, $matches);
$index_name = $matches[1];
$this->connection->query('ALTER INDEX ' . $index->indexname . ' RENAME TO {' . $new_name . '}_' . $index_name);
}
else {
if ($index_type == 'pkey') {
// Primary keys do not have a specific name in D7.
$index_name = '';
}
else {
// Make sure to remove the suffix from index names, because
// ensureIdentifiersLength() will add the suffix again and thus
// would result in a wrong index name.
preg_match('/^' . preg_quote($old_full_name) . '_(.*)_' . preg_quote($index_type) . '/', $index->indexname, $matches);
$index_name = $matches[1];
}
}

$this->connection->query('ALTER INDEX ' . $index->indexname . ' RENAME TO ' . $this->ensureIdentifiersLength($new_name, $index_name, $index_type));
}

// Now rename the table.
Expand Down Expand Up @@ -415,8 +494,8 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
}

public function indexExists($table, $name) {
// Details http://www.postgresql.org/docs/8.3/interactive/view-pg-indexes.html
$index_name = '{' . $table . '}_' . $name . '_idx';
// Details https://www.postgresql.org/docs/10/view-pg-indexes.html
$index_name = $this->ensureIdentifiersLength($table, $name, 'idx');
return (bool) $this->connection->query("SELECT 1 FROM pg_indexes WHERE indexname = '$index_name'")->fetchField();
}

Expand All @@ -429,7 +508,18 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
* The name of the constraint (typically 'pkey' or '[constraint]_key').
*/
protected function constraintExists($table, $name) {
$constraint_name = '{' . $table . '}_' . $name;
// ensureIdentifiersLength() expects three parameters, thus we split our
// constraint name in a proper name and a suffix.
if ($name == 'pkey') {
$suffix = $name;
$name = '';
}
else {
$pos = strrpos($name, '_');
$suffix = substr($name, $pos + 1);
$name = substr($name, 0, $pos);
}
$constraint_name = $this->ensureIdentifiersLength($table, $name, $suffix);
return (bool) $this->connection->query("SELECT 1 FROM pg_constraint WHERE conname = '$constraint_name'")->fetchField();
}

Expand All @@ -441,15 +531,15 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
throw new DatabaseSchemaObjectExistsException(t("Cannot add primary key to table @table: primary key already exists.", array('@table' => $table)));
}

$this->connection->query('ALTER TABLE {' . $table . '} ADD PRIMARY KEY (' . implode(',', $fields) . ')');
$this->connection->query('ALTER TABLE {' . $table . '} ADD CONSTRAINT ' . $this->ensureIdentifiersLength($table, '', 'pkey') . ' PRIMARY KEY (' . implode(',', $fields) . ')');
}

public function dropPrimaryKey($table) {
if (!$this->constraintExists($table, 'pkey')) {
return FALSE;
}

$this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT ' . $this->prefixNonTable($table, 'pkey'));
$this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT ' . $this->ensureIdentifiersLength($table, '', 'pkey'));
return TRUE;
}

Expand All @@ -461,15 +551,15 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
throw new DatabaseSchemaObjectExistsException(t("Cannot add unique key @name to table @table: unique key already exists.", array('@table' => $table, '@name' => $name)));
}

$this->connection->query('ALTER TABLE {' . $table . '} ADD CONSTRAINT "' . $this->prefixNonTable($table, $name, 'key') . '" UNIQUE (' . implode(',', $fields) . ')');
$this->connection->query('ALTER TABLE {' . $table . '} ADD CONSTRAINT "' . $this->ensureIdentifiersLength($table, $name, 'key') . '" UNIQUE (' . implode(',', $fields) . ')');
}

public function dropUniqueKey($table, $name) {
if (!$this->constraintExists($table, $name . '_key')) {
return FALSE;
}

$this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT "' . $this->prefixNonTable($table, $name, 'key') . '"');
$this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT "' . $this->ensureIdentifiersLength($table, $name, 'key') . '"');
return TRUE;
}

Expand All @@ -489,7 +579,7 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
return FALSE;
}

$this->connection->query('DROP INDEX ' . $this->prefixNonTable($table, $name, 'idx'));
$this->connection->query('DROP INDEX ' . $this->ensureIdentifiersLength($table, $name, 'idx'));
return TRUE;
}

Expand Down Expand Up @@ -580,7 +670,7 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
}

protected function _createIndexSql($table, $name, $fields) {
$query = 'CREATE INDEX "' . $this->prefixNonTable($table, $name, 'idx') . '" ON {' . $table . '} (';
$query = 'CREATE INDEX "' . $this->ensureIdentifiersLength($table, $name, 'idx') . '" ON {' . $table . '} (';
$query .= $this->_createKeySql($fields) . ')';
return $query;
}
Expand Down Expand Up @@ -614,4 +704,36 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
return $this->connection->query('SELECT obj_description(oid, ?) FROM pg_class WHERE relname = ?', array('pg_class', $info['table']))->fetchField();
}
}

/**
* Calculates a base-64 encoded, PostgreSQL-safe sha-256 hash per PostgreSQL
* documentation: 4.1. Lexical Structure.
*
* @param $data
* String to be hashed.
*
* @return string
* A base-64 encoded sha-256 hash, with + and / replaced with _ and any =
* padding characters removed.
*/
protected function hashBase64($data) {
// Ensure lowercase as D7's pgsql driver does not quote identifiers
// consistently, and they are therefore folded to lowercase by PostgreSQL.
$hash = strtolower(base64_encode(hash('sha256', $data, TRUE)));
// Modify the hash so it's safe to use in PostgreSQL identifiers.
return strtr($hash, array('+' => '_', '/' => '_', '=' => ''));
}

/**
* Build a condition to match a table name against a standard information_schema.
*
* In PostgreSQL "unquoted names are always folded to lower case." The pgsql
* driver does not quote table names, so they are therefore always lowercase.
*
* @see https://www.postgresql.org/docs/14/sql-syntax-lexical.html
*/
protected function buildTableNameCondition($table_name, $operator = '=', $add_prefix = TRUE) {
return parent::buildTableNameCondition(strtolower($table_name), $operator, $add_prefix);
}

}
1 change: 1 addition & 0 deletions modules/field/field.crud.inc
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,7 @@ function field_read_instances($params = array(), $include_additional = array())
}

$instances = array();
$query->orderBy('fci.id');
$results = $query->execute();

foreach ($results as $record) {
Expand Down
2 changes: 1 addition & 1 deletion modules/field/field.info.class.inc
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ class FieldInfo {

$map = array();

$query = db_query('SELECT fc.type, fci.field_name, fci.entity_type, fci.bundle FROM {field_config_instance} fci INNER JOIN {field_config} fc ON fc.id = fci.field_id WHERE fc.active = 1 AND fc.storage_active = 1 AND fc.deleted = 0 AND fci.deleted = 0');
$query = db_query('SELECT fc.type, fci.field_name, fci.entity_type, fci.bundle FROM {field_config_instance} fci INNER JOIN {field_config} fc ON fc.id = fci.field_id WHERE fc.active = 1 AND fc.storage_active = 1 AND fc.deleted = 0 AND fci.deleted = 0 ORDER BY bundle, entity_type');
foreach ($query as $row) {
$map[$row->field_name]['bundles'][$row->entity_type][] = $row->bundle;
$map[$row->field_name]['type'] = $row->type;
Expand Down
2 changes: 1 addition & 1 deletion modules/file/file.field.inc
Original file line number Diff line number Diff line change
Expand Up @@ -943,7 +943,7 @@ function theme_file_upload_help($variables) {

$descriptions = array();

if (strlen($description)) {
if (!empty($description)) {
$descriptions[] = $description;
}
if (isset($upload_validators['file_validate_size'])) {
Expand Down
49 changes: 42 additions & 7 deletions modules/file/tests/file.test
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,23 @@ class FileFieldWidgetTestCase extends FileFieldTestCase {
foreach (array($field_name2, $field_name) as $each_field_name) {
for ($delta = 0; $delta < 3; $delta++) {
$edit = array('files[' . $each_field_name . '_' . LANGUAGE_NONE . '_' . $delta . ']' => drupal_realpath($test_file->uri));
// drupalPost() takes a $submit parameter that is the value of the
// button whose click we want to emulate. Since we have multiple
// buttons with the value "Upload", and want to control which one we
// use, we change the value of the other ones to something else.
// Since non-clicked buttons aren't included in the submitted POST
// data, and since drupalPost() will result in $this being updated
// with a newly rebuilt form, this doesn't cause problems. Note that
// $buttons is an array of SimpleXMLElement objects passed by
// reference so modifications to each button will affect
// \DrupalWebTestCase::handleForm().
$buttons = $this->xpath('//input[@type="submit" and @value="Upload"]');
$button_name = $each_field_name . '_' . LANGUAGE_NONE . '_' . $delta . '_upload_button';
foreach ($buttons as $button) {
if ($button['name'] != $button_name) {
$button['value'] = 'DUMMY';
}
}
// If the Upload button doesn't exist, drupalPost() will automatically
// fail with an assertion message.
$this->drupalPost(NULL, $edit, t('Upload'));
Expand Down Expand Up @@ -786,13 +803,8 @@ class FileFieldWidgetTestCase extends FileFieldTestCase {
$button_name = $current_field_name . '_' . LANGUAGE_NONE . '_' . $delta . '_remove_button';
switch ($type) {
case 'nojs':
// drupalPost() takes a $submit parameter that is the value of the
// button whose click we want to emulate. Since we have multiple
// buttons with the value "Remove", and want to control which one we
// use, we change the value of the other ones to something else.
// Since non-clicked buttons aren't included in the submitted POST
// data, and since drupalPost() will result in $this being updated
// with a newly rebuilt form, this doesn't cause problems.
// Same workaround for multiple buttons with the value "Remove" as
// we did for the "Upload" buttons above.
foreach ($buttons as $button) {
if ($button['name'] != $button_name) {
$button['value'] = 'DUMMY';
Expand Down Expand Up @@ -1933,3 +1945,26 @@ class FileScanDirectory extends FileFieldTestCase {
}

}

/**
* Test theme implementations declared in file_theme().
*/
class FileThemeImplementationsTestCase extends DrupalUnitTestCase {

public static function getInfo() {
return array(
'name' => 'Theme implementations declared in file_theme()',
'description' => 'Unit tests theme functions in the file module.',
'group' => 'File',
);
}

function testThemeFileUploadHelp() {
$variables = array(
'description' => NULL,
'upload_validators' => NULL,
);
$this->assertEqual('', theme_file_upload_help($variables), 'Empty string returned by theme_file_upload_help() with NULL inputs.');
}

}
Loading