diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index 34e45a3bcc0..784fd368f99 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -1,3 +1,9 @@
+Drupal 7.90, 2022-06-01
+-----------------------
+- Improved support for PHP 8.1
+- Improved support for PostgreSQL
+- Various bug fixes, optimizations and improvements
+
Drupal 7.89, 2022-03-02
-----------------------
- Bug fixes for PHP 8.1
diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc
index 74204b3def2..29a5fffa884 100644
--- a/includes/bootstrap.inc
+++ b/includes/bootstrap.inc
@@ -8,7 +8,7 @@
/**
* The current system version.
*/
-define('VERSION', '7.89');
+define('VERSION', '7.90');
/**
* Core API compatibility.
@@ -1627,7 +1627,7 @@ function drupal_page_header() {
*/
function drupal_serve_page_from_cache(stdClass $cache) {
// Negotiate whether to use compression.
- $page_compression = !empty($cache->data['page_compressed']);
+ $page_compression = !empty($cache->data['page_compressed']) && !empty($cache->data['body']);
$return_compressed = $page_compression && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE;
// Get headers set in hook_boot(). Keys are lower-case.
@@ -1958,7 +1958,7 @@ function check_plain($text) {
* TRUE if the text is valid UTF-8, FALSE if not.
*/
function drupal_validate_utf8($text) {
- if (strlen($text) == 0) {
+ if (strlen((string) $text) == 0) {
return TRUE;
}
// With the PCRE_UTF8 modifier 'u', preg_match() fails silently on strings
diff --git a/includes/common.inc b/includes/common.inc
index 766ceff8cdd..904a77ad0e3 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -1500,7 +1500,7 @@ function filter_xss($string, $allowed_tags = array('a', 'em', 'strong', 'cite',
// Store the text format.
_filter_xss_split($allowed_tags, TRUE);
// Remove NULL characters (ignored by some browsers).
- $string = str_replace(chr(0), '', $string);
+ $string = str_replace(chr(0), '', (string) $string);
// Remove Netscape 4 JS entities.
$string = preg_replace('%&\s*\{[^}]*(\}\s*;?|$)%', '', $string);
@@ -2696,6 +2696,7 @@ function drupal_deliver_html_page($page_callback_result) {
if ($frame_options && is_null(drupal_get_http_header('X-Frame-Options'))) {
drupal_add_http_header('X-Frame-Options', $frame_options);
}
+ drupal_add_http_header('X-Content-Type-Options', 'nosniff');
if (variable_get('block_interest_cohort', TRUE)) {
$permissions_policy = drupal_get_http_header('Permissions-Policy');
@@ -8051,8 +8052,14 @@ function entity_extract_ids($entity_type, $entity) {
$info = entity_get_info($entity_type);
// Objects being created might not have id/vid yet.
- $id = isset($entity->{$info['entity keys']['id']}) ? $entity->{$info['entity keys']['id']} : NULL;
- $vid = ($info['entity keys']['revision'] && isset($entity->{$info['entity keys']['revision']})) ? $entity->{$info['entity keys']['revision']} : NULL;
+ if (!empty($info)) {
+ $id = isset($entity->{$info['entity keys']['id']}) ? $entity->{$info['entity keys']['id']} : NULL;
+ $vid = ($info['entity keys']['revision'] && isset($entity->{$info['entity keys']['revision']})) ? $entity->{$info['entity keys']['revision']} : NULL;
+ }
+ else {
+ $id = NULL;
+ $vid = NULL;
+ }
if (!empty($info['entity keys']['bundle'])) {
// Explicitly fail for malformed entities missing the bundle property.
diff --git a/includes/database/pgsql/schema.inc b/includes/database/pgsql/schema.inc
index db8266f8cbf..8d1b388c11a 100644
--- a/includes/database/pgsql/schema.inc
+++ b/includes/database/pgsql/schema.inc
@@ -12,6 +12,13 @@
class DatabaseSchema_pgsql extends DatabaseSchema {
+ /**
+ * PostgreSQL's temporary namespace name.
+ *
+ * @var string
+ */
+ protected $tempNamespaceName;
+
/**
* A cache of information about blob columns and sequences of tables.
*
@@ -97,23 +104,47 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
public function queryTableInformation($table) {
// Generate a key to reference this table's information on.
$key = $this->connection->prefixTables('{' . $table . '}');
- if (!strpos($key, '.')) {
+
+ // Take into account that temporary tables are stored in a different schema.
+ // \DatabaseConnection::generateTemporaryTableName() sets 'db_temporary_'
+ // prefix to all temporary tables.
+ if (strpos($key, '.') === FALSE && strpos($table, 'db_temporary_') === FALSE) {
$key = 'public.' . $key;
}
+ else {
+ $key = $this->getTempNamespaceName() . '.' . $key;
+ }
if (!isset($this->tableInformation[$key])) {
- // Split the key into schema and table for querying.
- list($schema, $table_name) = explode('.', $key);
$table_information = (object) array(
'blob_fields' => array(),
'sequences' => array(),
);
- // Don't use {} around information_schema.columns table.
- $result = $this->connection->query("SELECT column_name, data_type, column_default FROM information_schema.columns WHERE table_schema = :schema AND table_name = :table AND (data_type = 'bytea' OR (numeric_precision IS NOT NULL AND column_default LIKE :default))", array(
- ':schema' => $schema,
- ':table' => $table_name,
- ':default' => '%nextval%',
+
+ // The bytea columns and sequences for a table can be found in
+ // pg_attribute, which is significantly faster than querying the
+ // information_schema. The data type of a field can be found by lookup
+ // of the attribute ID, and the default value must be extracted from the
+ // node tree for the attribute definition instead of the historical
+ // human-readable column, adsrc.
+ $sql = <<<'EOD'
+SELECT pg_attribute.attname AS column_name, format_type(pg_attribute.atttypid, pg_attribute.atttypmod) AS data_type, pg_get_expr(pg_attrdef.adbin, pg_attribute.attrelid) AS column_default
+FROM pg_attribute
+LEFT JOIN pg_attrdef ON pg_attrdef.adrelid = pg_attribute.attrelid AND pg_attrdef.adnum = pg_attribute.attnum
+WHERE pg_attribute.attnum > 0
+AND NOT pg_attribute.attisdropped
+AND pg_attribute.attrelid = :key::regclass
+AND (format_type(pg_attribute.atttypid, pg_attribute.atttypmod) = 'bytea'
+OR pg_get_expr(pg_attrdef.adbin, pg_attribute.attrelid) LIKE 'nextval%')
+EOD;
+ $result = $this->connection->query($sql, array(
+ ':key' => $key,
));
+
+ if (empty($result)) {
+ return $table_information;
+ }
+
foreach ($result as $column) {
if ($column->data_type == 'bytea') {
$table_information->blob_fields[$column->column_name] = TRUE;
@@ -131,6 +162,19 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
return $this->tableInformation[$key];
}
+ /**
+ * Gets PostgreSQL's temporary namespace name.
+ *
+ * @return string
+ * PostgreSQL's temporary namespace anme.
+ */
+ protected function getTempNamespaceName() {
+ if (!isset($this->tempNamespaceName)) {
+ $this->tempNamespaceName = $this->connection->query('SELECT nspname FROM pg_namespace WHERE oid = pg_my_temp_schema()')->fetchField();
+ }
+ return $this->tempNamespaceName;
+ }
+
/**
* Fetch the list of CHECK constraints used on a field.
*
@@ -370,6 +414,68 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
return implode(', ', $return);
}
+ /**
+ * {@inheritdoc}
+ */
+ public function tableExists($table) {
+ // In PostgreSQL "unquoted names are always folded to lower case."
+ // @see DatabaseSchema_pgsql::buildTableNameCondition().
+ $prefixInfo = $this->getPrefixInfo(strtolower($table), TRUE);
+
+ return (bool) $this->connection->query("SELECT 1 FROM pg_tables WHERE schemaname = :schema AND tablename = :table", array(':schema' => $prefixInfo['schema'], ':table' => $prefixInfo['table']))->fetchField();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function findTables($table_expression) {
+ $individually_prefixed_tables = $this->connection->getUnprefixedTablesMap();
+ $default_prefix = $this->connection->tablePrefix();
+ $default_prefix_length = strlen($default_prefix);
+ $tables = array();
+
+ // 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.
+ $results = $this->connection->query("SELECT tablename FROM pg_tables WHERE schemaname = :schema", array(':schema' => $this->defaultSchema));
+ foreach ($results as $table) {
+ // Take into account tables that have an individual prefix.
+ if (isset($individually_prefixed_tables[$table->tablename])) {
+ $prefix_length = strlen($this->connection->tablePrefix($individually_prefixed_tables[$table->tablename]));
+ }
+ elseif ($default_prefix && substr($table->tablename, 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->tablename, $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;
+ }
+ }
+
+ // Need to use strtolower on the table name as it was used previously by
+ // DatabaseSchema_pgsql::buildTableNameCondition().
+ // @see https://www.drupal.org/project/drupal/issues/3262341
+ $table_expression = strtolower($table_expression);
+
+ // 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;
+ }
+
function renameTable($table, $new_name) {
if (!$this->tableExists($table)) {
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot rename @table to @table_new: table @table doesn't exist.", array('@table' => $table, '@table_new' => $new_name)));
@@ -493,6 +599,17 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
$this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN "' . $field . '" DROP DEFAULT');
}
+ /**
+ * {@inheritdoc}
+ */
+ public function fieldExists($table, $column) {
+ // In PostgreSQL "unquoted names are always folded to lower case."
+ // @see DatabaseSchema_pgsql::buildTableNameCondition().
+ $prefixInfo = $this->getPrefixInfo(strtolower($table));
+
+ return (bool) $this->connection->query("SELECT 1 FROM pg_attribute WHERE attrelid = :key::regclass AND attname = :column AND NOT attisdropped AND attnum > 0", array(':key' => $prefixInfo['schema'] . '.' . $prefixInfo['table'], ':column' => $column))->fetchField();
+ }
+
public function indexExists($table, $name) {
// Details https://www.postgresql.org/docs/10/view-pg-indexes.html
$index_name = $this->ensureIdentifiersLength($table, $name, 'idx');
diff --git a/includes/entity.inc b/includes/entity.inc
index e80ce3b89fd..2500e383cc7 100644
--- a/includes/entity.inc
+++ b/includes/entity.inc
@@ -254,7 +254,10 @@ class DrupalDefaultEntityController implements DrupalEntityControllerInterface {
* Callback for array_filter that removes non-integer IDs.
*/
protected function filterId($id) {
- return is_numeric($id) && $id == (int) $id;
+ // ctype_digit() is used here instead of a strict comparison as sometimes
+ // the id is passed as a string containing '0' which may represent a bug
+ // elsewhere but would fail with a strict comparison.
+ return is_numeric($id) && $id == (int) $id && ctype_digit((string) $id);
}
/**
diff --git a/includes/file.inc b/includes/file.inc
index 741fd8380ce..c55a0577321 100644
--- a/includes/file.inc
+++ b/includes/file.inc
@@ -539,6 +539,10 @@ SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006
php_flag engine off
+# From PHP 8 there is no number in the module name.
+
+ php_flag engine off
+
EOF;
if ($private) {
diff --git a/includes/locale.inc b/includes/locale.inc
index 11f1413eec6..b0287faedd1 100644
--- a/includes/locale.inc
+++ b/includes/locale.inc
@@ -1603,7 +1603,7 @@ function _locale_parse_js_file($filepath) {
if ($source) {
// We already have this source string and now have to add the location
// to the location column, if this file is not yet present in there.
- $locations = preg_split('~\s*;\s*~', $source->location);
+ $locations = preg_split('~\s*;\s*~', (string) $source->location);
if (!in_array($filepath, $locations)) {
$locations[] = $filepath;
diff --git a/modules/comment/comment.module b/modules/comment/comment.module
index 23fb2f5d3bc..786be42de94 100644
--- a/modules/comment/comment.module
+++ b/modules/comment/comment.module
@@ -1918,7 +1918,6 @@ function comment_form($form, &$form_state, $comment) {
if ($is_admin) {
$author = (!$comment->uid && $comment->name ? $comment->name : $comment->registered_name);
$status = (isset($comment->status) ? $comment->status : COMMENT_NOT_PUBLISHED);
- $date = (!empty($comment->date) ? $comment->date : format_date($comment->created, 'custom', 'Y-m-d H:i O'));
}
else {
if ($user->uid) {
@@ -1928,7 +1927,11 @@ function comment_form($form, &$form_state, $comment) {
$author = ($comment->name ? $comment->name : '');
}
$status = (user_access('skip comment approval') ? COMMENT_PUBLISHED : COMMENT_NOT_PUBLISHED);
- $date = '';
+ }
+
+ $date = '';
+ if ($comment->cid) {
+ $date = !empty($comment->date) ? $comment->date : format_date($comment->created, 'custom', 'Y-m-d H:i:s O');
}
// Add the author name field depending on the current user.
@@ -2176,7 +2179,7 @@ function comment_submit($comment) {
if (empty($comment->date)) {
$comment->date = 'now';
}
- $comment->created = strtotime($comment->date);
+ $comment->created = strtotime($comment->date, REQUEST_TIME);
$comment->changed = REQUEST_TIME;
// If the comment was posted by a registered user, assign the author's ID.
diff --git a/modules/comment/comment.test b/modules/comment/comment.test
index b70fa26c387..f87560bdf94 100644
--- a/modules/comment/comment.test
+++ b/modules/comment/comment.test
@@ -1003,7 +1003,7 @@ class CommentPreviewTest extends CommentHelperCase {
*/
function testCommentEditPreviewSave() {
$langcode = LANGUAGE_NONE;
- $web_user = $this->drupalCreateUser(array('access comments', 'post comments', 'skip comment approval'));
+ $web_user = $this->drupalCreateUser(array('access comments', 'post comments', 'skip comment approval', 'edit own comments'));
$this->drupalLogin($this->admin_user);
$this->setCommentPreview(DRUPAL_OPTIONAL);
$this->setCommentForm(TRUE);
@@ -1017,7 +1017,7 @@ class CommentPreviewTest extends CommentHelperCase {
$edit['date'] = '2008-03-02 17:23 +0300';
$raw_date = strtotime($edit['date']);
$expected_text_date = format_date($raw_date);
- $expected_form_date = format_date($raw_date, 'custom', 'Y-m-d H:i O');
+ $expected_form_date = format_date($raw_date, 'custom', 'Y-m-d H:i:s O');
$comment = $this->postComment($this->node, $edit['subject'], $edit['comment_body[' . $langcode . '][0][value]'], TRUE);
$this->drupalPost('comment/' . $comment->id . '/edit', $edit, t('Preview'));
@@ -1059,7 +1059,16 @@ class CommentPreviewTest extends CommentHelperCase {
$this->assertEqual($comment_loaded->comment_body[$langcode][0]['value'], $edit['comment_body[' . $langcode . '][0][value]'], 'Comment body loaded.');
$this->assertEqual($comment_loaded->name, $edit['name'], 'Name loaded.');
$this->assertEqual($comment_loaded->created, $raw_date, 'Date loaded.');
+ $this->drupalLogout();
+ // Check that the date and time of the comment are correct when edited by
+ // non-admin users.
+ $user_edit = array();
+ $expected_created_time = $comment_loaded->created;
+ $this->drupalLogin($web_user);
+ $this->drupalPost('comment/' . $comment->id . '/edit', $user_edit, t('Save'));
+ $comment_loaded = comment_load($comment->id, TRUE);
+ $this->assertEqual($comment_loaded->created, $expected_created_time, 'Expected date and time for comment edited.');
}
}
diff --git a/modules/dblog/dblog.admin.inc b/modules/dblog/dblog.admin.inc
index f8a00c26bb0..df9f6a87199 100644
--- a/modules/dblog/dblog.admin.inc
+++ b/modules/dblog/dblog.admin.inc
@@ -286,13 +286,19 @@ function theme_dblog_message($variables) {
$event = $variables['event'];
// Check for required properties.
if (isset($event->message) && isset($event->variables)) {
+ $event_variables = @unserialize($event->variables);
// Messages without variables or user specified text.
- if ($event->variables === 'N;') {
+ if ($event_variables === NULL) {
$output = $event->message;
}
+ elseif (!is_array($event_variables)) {
+ $output = t('Log data is corrupted and cannot be unserialized: @message', array(
+ '@message' => $event->message,
+ ));
+ }
// Message to translate with injected variables.
else {
- $output = t($event->message, unserialize($event->variables));
+ $output = t($event->message, $event_variables);
}
// If the output is expected to be a link, strip all the tags and
// special characters by using filter_xss() without any allowed tags.
diff --git a/modules/dblog/dblog.test b/modules/dblog/dblog.test
index b0a58ba4543..9c266656fd8 100644
--- a/modules/dblog/dblog.test
+++ b/modules/dblog/dblog.test
@@ -58,12 +58,42 @@ class DBLogTestCase extends DrupalWebTestCase {
$this->verifyCron($row_limit);
$this->verifyEvents();
$this->verifyReports();
+ $this->testDBLogCorrupted();
// Login the regular user.
$this->drupalLogin($this->any_user);
$this->verifyReports(403);
}
+ /**
+ * Tests corrupted log entries can still display available data.
+ */
+ private function testDBLogCorrupted() {
+ global $base_root;
+
+ // Prepare the fields to be logged
+ $log = array(
+ 'type' => 'custom',
+ 'message' => 'Log entry added to test the unserialize failure.',
+ 'variables' => 'BAD SERIALIZED DATA',
+ 'severity' => WATCHDOG_NOTICE,
+ 'link' => '',
+ 'user' => $this->big_user,
+ 'uid' => isset($this->big_user->uid) ? $this->big_user->uid : 0,
+ 'request_uri' => $base_root . request_uri(),
+ 'referer' => $_SERVER['HTTP_REFERER'],
+ 'ip' => ip_address(),
+ 'timestamp' => REQUEST_TIME,
+ );
+ dblog_watchdog($log);
+
+ // View the database log report page.
+ $this->drupalGet('admin/reports/dblog');
+ $this->assertResponse(200);
+ $output = truncate_utf8(filter_xss(t('Log data is corrupted and cannot be unserialized: Log entry added to test unserialize failure.'), array()), 56, TRUE, TRUE);
+ $this->assertText($output, 'Log data is corrupted and cannot be unserialized.');
+ }
+
/**
* Verifies setting of the database log row limit.
*
diff --git a/modules/field/modules/field_sql_storage/field_sql_storage.module b/modules/field/modules/field_sql_storage/field_sql_storage.module
index deb08d0dac6..63161ab7d1d 100644
--- a/modules/field/modules/field_sql_storage/field_sql_storage.module
+++ b/modules/field/modules/field_sql_storage/field_sql_storage.module
@@ -212,6 +212,18 @@ function _field_sql_storage_schema($field) {
),
);
+ // If the target entity type uses a string for its entity ID then update
+ // the fields entity_id and revision_id columns from INT to VARCHAR.
+ if (!empty($field['entity_id_type']) && $field['entity_id_type'] === 'string') {
+ $current['fields']['entity_id']['type'] = 'varchar';
+ $current['fields']['entity_id']['length'] = 128;
+ unset($current['fields']['entity_id']['unsigned']);
+
+ $current['fields']['revision_id']['type'] = 'varchar';
+ $current['fields']['revision_id']['length'] = 128;
+ unset($current['fields']['revision_id']['unsigned']);
+ }
+
$field += array('columns' => array(), 'indexes' => array(), 'foreign keys' => array());
// Add field columns.
foreach ($field['columns'] as $column_name => $attributes) {
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 e46677be9c2..ad8d74926b9 100644
--- a/modules/field/modules/field_sql_storage/field_sql_storage.test
+++ b/modules/field/modules/field_sql_storage/field_sql_storage.test
@@ -104,6 +104,29 @@ class FieldSqlStorageTestCase extends DrupalWebTestCase {
$this->assertFalse(array_key_exists($unavailable_language, $entity->{$this->field_name}), 'Field translation in an unavailable language ignored');
}
+ /**
+ * Tests adding a field with an entity ID type of string.
+ */
+ function testFieldSqlSchemaForEntityWithStringIdentifier() {
+ // Test programmatically adding field with string ID.
+ $field_name = 'string_id_example';
+ $field = array('field_name' => $field_name, 'type' => 'text', 'settings' => array('max_length' => 255), 'entity_id_type' => 'string');
+ field_create_field($field);
+ $schema = drupal_get_schema('field_data_' . $field_name);
+
+ $this->assertEqual($schema['fields']['entity_id']['type'], 'varchar');
+ $this->assertEqual($schema['fields']['revision_id']['type'], 'varchar');
+
+ // Test programmatically adding field with default ID(int).
+ $field_name = 'default_id_example';
+ $field = array('field_name' => $field_name, 'type' => 'text', 'settings' => array('max_length' => 255));
+ field_create_field($field);
+ $schema = drupal_get_schema('field_data_' . $field_name);
+
+ $this->assertEqual($schema['fields']['entity_id']['type'], 'int');
+ $this->assertEqual($schema['fields']['revision_id']['type'], 'int');
+ }
+
/**
* Reads mysql to verify correct data is
* written when using insert and update.
diff --git a/modules/field/modules/text/text.module b/modules/field/modules/text/text.module
index bf0d29d5a1f..d64eef9a681 100644
--- a/modules/field/modules/text/text.module
+++ b/modules/field/modules/text/text.module
@@ -348,6 +348,11 @@ function _text_sanitize($instance, $langcode, $item, $column) {
*/
function text_summary($text, $format = NULL, $size = NULL) {
+ // If the input text is NULL, return unchanged.
+ if (is_null($text)) {
+ return NULL;
+ }
+
if (!isset($size)) {
// What used to be called 'teaser' is now called 'summary', but
// the variable 'teaser_length' is preserved for backwards compatibility.
diff --git a/modules/field/modules/text/text.test b/modules/field/modules/text/text.test
index ad803cf46d9..0802c711947 100644
--- a/modules/field/modules/text/text.test
+++ b/modules/field/modules/text/text.test
@@ -378,6 +378,14 @@ class TextSummaryTestCase extends DrupalWebTestCase {
}
}
+ /**
+ * Test for the NULL value.
+ */
+ function testNullSentence() {
+ $summary = text_summary(NULL);
+ $this->assertNull($summary, 'text_summary() casts returned null');
+ }
+
/**
* Calls text_summary() and asserts that the expected teaser is returned.
*/
diff --git a/modules/field/tests/field.test b/modules/field/tests/field.test
index 5312f2d4557..c334661aa10 100644
--- a/modules/field/tests/field.test
+++ b/modules/field/tests/field.test
@@ -3788,4 +3788,16 @@ class EntityPropertiesTestCase extends FieldTestCase {
}
}
}
+
+ /**
+ * Tests entity_extract_ids() with an empty entity info.
+ */
+ function testEntityKeys(){
+ $entity_type = 'test_entity2';
+ $entity = field_test_create_stub_entity();
+ list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
+
+ $this->assertNull($id, 'Entity id for test_entity2 returned NULL.');
+ $this->assertNull($vid, 'Entity vid for test_entity2 returned NULL.');
+ }
}
diff --git a/modules/field_ui/field_ui.admin.inc b/modules/field_ui/field_ui.admin.inc
index 1bdaa45ddce..8ae489f3036 100644
--- a/modules/field_ui/field_ui.admin.inc
+++ b/modules/field_ui/field_ui.admin.inc
@@ -795,6 +795,14 @@ function field_ui_field_overview_form_submit($form, &$form_state) {
$destinations = array();
+ // Check if the target entity uses a non numeric ID.
+ $entity_info = entity_get_info($entity_type);
+ if (!empty($entity_info['entity_id_type']) && $entity_info['entity_id_type'] === 'string') {
+ $entity_id_type = 'string';
+ } else {
+ $entity_id_type = NULL;
+ }
+
// Create new field.
$field = array();
if (!empty($form_values['_add_new_field']['field_name'])) {
@@ -804,6 +812,7 @@ function field_ui_field_overview_form_submit($form, &$form_state) {
'field_name' => $values['field_name'],
'type' => $values['type'],
'translatable' => $values['translatable'],
+ 'entity_id_type' => $entity_id_type,
);
$instance = array(
'field_name' => $field['field_name'],
diff --git a/modules/file/file.install b/modules/file/file.install
index 47ee4fd0014..e0d33a04e20 100644
--- a/modules/file/file.install
+++ b/modules/file/file.install
@@ -53,18 +53,34 @@ function file_requirements($phase) {
// Check the server's ability to indicate upload progress.
if ($phase == 'runtime') {
- $implementation = file_progress_implementation();
- $apache = strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') !== FALSE;
- $fastcgi = strpos($_SERVER['SERVER_SOFTWARE'], 'mod_fastcgi') !== FALSE || strpos($_SERVER["SERVER_SOFTWARE"], 'mod_fcgi') !== FALSE;
$description = NULL;
- if (!$apache) {
+ $implementation = file_progress_implementation();
+ // Test the web server identity.
+ $server_software = $_SERVER['SERVER_SOFTWARE'];
+ if (preg_match("/Nginx/i", $server_software)) {
+ $is_nginx = TRUE;
+ $is_apache = FALSE;
+ $fastcgi = FALSE;
+ }
+ elseif (preg_match("/Apache/i", $server_software)) {
+ $is_nginx = FALSE;
+ $is_apache = TRUE;
+ $fastcgi = strpos($server_software, 'mod_fastcgi') !== FALSE || strpos($server_software, 'mod_fcgi') !== FALSE;
+ }
+ else {
+ $is_nginx = FALSE;
+ $is_apache = FALSE;
+ $fastcgi = FALSE;
+ }
+
+ if (!$is_apache && !$is_nginx) {
$value = t('Not enabled');
- $description = t('Your server is not capable of displaying file upload progress. File upload progress requires an Apache server running PHP with mod_php.');
+ $description = t('Your server is not capable of displaying file upload progress. File upload progress requires an Apache server running PHP with mod_php or Nginx with PHP-FPM.');
$severity = REQUIREMENT_INFO;
}
elseif ($fastcgi) {
$value = t('Not enabled');
- $description = t('Your server is not capable of displaying file upload progress. File upload progress requires PHP be run with mod_php and not as FastCGI.');
+ $description = t('Your server is not capable of displaying file upload progress. File upload progress requires PHP be run with mod_php or PHP-FPM and not as FastCGI.');
$severity = REQUIREMENT_INFO;
}
elseif (!$implementation && extension_loaded('apc')) {
diff --git a/modules/image/image.module b/modules/image/image.module
index 5af749779cd..728fad87b5f 100644
--- a/modules/image/image.module
+++ b/modules/image/image.module
@@ -588,8 +588,18 @@ function image_styles() {
$style['storage'] = IMAGE_STORAGE_DEFAULT;
foreach ($style['effects'] as $key => $effect) {
$definition = image_effect_definition_load($effect['name']);
- $effect = array_merge($definition, $effect);
- $style['effects'][$key] = $effect;
+ if ($definition) {
+ $effect = array_merge($definition, $effect);
+ $style['effects'][$key] = $effect;
+ }
+ else {
+ watchdog('image', 'Image style %style_name has an effect %effect_name with no definition.',
+ array(
+ '%style_name' => $style_name,
+ '%effect_name' => $effect['name'],
+ ),
+ WATCHDOG_ERROR);
+ }
}
$styles[$style_name] = $style;
}
diff --git a/modules/image/image.test b/modules/image/image.test
index 07eb4cedc1a..87f4168ae4d 100644
--- a/modules/image/image.test
+++ b/modules/image/image.test
@@ -31,7 +31,12 @@ class ImageFieldTestCase extends DrupalWebTestCase {
protected $admin_user;
function setUp() {
- parent::setUp('image');
+ $modules = func_get_args();
+ if (isset($modules[0]) && is_array($modules[0])) {
+ $modules = $modules[0];
+ }
+ $modules[] = 'image';
+ parent::setUp($modules);
$this->admin_user = $this->drupalCreateUser(array('access content', 'access administration pages', 'administer site configuration', 'administer content types', 'administer nodes', 'create article content', 'edit any article content', 'delete any article content', 'administer image styles', 'administer fields'));
$this->drupalLogin($this->admin_user);
}
@@ -573,6 +578,10 @@ class ImageAdminStylesUnitTest extends ImageFieldTestCase {
);
}
+ function setUp() {
+ parent::setUp('image_module_test', 'image_module_styles_test');
+ }
+
/**
* Given an image style, generate an image.
*/
@@ -893,6 +902,18 @@ class ImageAdminStylesUnitTest extends ImageFieldTestCase {
$this->drupalGet('node/' . $nid);
$this->assertRaw(check_plain(image_style_url('thumbnail', $node->{$field_name}[LANGUAGE_NONE][0]['uri'])), format_string('Image displayed using style replacement style.'));
}
+
+ /**
+ * Test disabling a module providing an effect in use by an image style.
+ */
+ function testOrphanedEffect() {
+ // This will not check whether anything depends on the module.
+ module_disable(array('image_module_test'), FALSE);
+ $this->drupalGet('admin/config/media/image-styles');
+ $this->assertText('Test Image Style', 'Image style with an orphaned effect displayed in the list of styles.');
+ $image_log = db_query_range('SELECT message FROM {watchdog} WHERE type = :type ORDER BY wid DESC', 0, 1, array(':type' => 'image'))->fetchField();
+ $this->assertEqual('Image style %style_name has an effect %effect_name with no definition.', $image_log, 'A watchdog message was logged for the broken image style effect');
+ }
}
/**
diff --git a/modules/image/tests/image_module_styles_test.info b/modules/image/tests/image_module_styles_test.info
new file mode 100644
index 00000000000..b2acc9b3d8e
--- /dev/null
+++ b/modules/image/tests/image_module_styles_test.info
@@ -0,0 +1,8 @@
+name = Image Styles test
+description = Provides additional hook implementations for testing Image Styles functionality.
+package = Core
+version = VERSION
+core = 7.x
+files[] = image_module_styles_test.module
+dependencies[] = image_module_test
+hidden = TRUE
diff --git a/modules/image/tests/image_module_styles_test.module b/modules/image/tests/image_module_styles_test.module
new file mode 100644
index 00000000000..79929d93992
--- /dev/null
+++ b/modules/image/tests/image_module_styles_test.module
@@ -0,0 +1,29 @@
+ 'Test Image Style',
+ 'effects' => array(
+ array(
+ 'name' => 'image_scale',
+ 'data' => array('width' => 100, 'height' => 100, 'upscale' => 1),
+ 'weight' => 0,
+ ),
+ array(
+ 'name' => 'image_module_test_null',
+ ),
+ )
+ );
+
+ return $styles;
+}
diff --git a/modules/image/tests/image_module_test.module b/modules/image/tests/image_module_test.module
index fc66d9b8b7c..e7ae716e7a7 100644
--- a/modules/image/tests/image_module_test.module
+++ b/modules/image/tests/image_module_test.module
@@ -20,7 +20,8 @@ function image_module_test_file_download($uri) {
function image_module_test_image_effect_info() {
$effects = array(
'image_module_test_null' => array(
- 'effect callback' => 'image_module_test_null_effect',
+ 'label' => 'image_module_test_null',
+ 'effect callback' => 'image_module_test_null_effect',
),
);
@@ -38,7 +39,7 @@ function image_module_test_image_effect_info() {
* @return
* TRUE
*/
-function image_module_test_null_effect(array &$image, array $data) {
+function image_module_test_null_effect(&$image, array $data) {
return TRUE;
}
diff --git a/modules/locale/locale.test b/modules/locale/locale.test
index b890b06147c..4f6fd6c3038 100644
--- a/modules/locale/locale.test
+++ b/modules/locale/locale.test
@@ -297,6 +297,28 @@ class LocaleJavascriptTranslationTest extends DrupalWebTestCase {
$this->assertEqual(count($source_strings), count($test_strings), 'Found correct number of source strings.');
}
+
+ /**
+ * Test handling of null values in JS parsing for PHP8.0+ deprecations.
+ */
+ function testNullValuesLocalesSource() {
+ db_insert('locales_source')
+ ->fields(array(
+ 'location' => NULL,
+ 'source' => 'Standard Call t',
+ 'context' => '',
+ 'textgroup' => 'default',
+ ))
+ ->execute();
+
+ $filename = drupal_get_path('module', 'locale_test') . '/locale_test.js';
+
+ // Parse the file to look for source strings.
+ _locale_parse_js_file($filename);
+
+ $num_records = db_select('locales_source')->fields(NULL, array('lid'))->countQuery()->execute()->fetchField();
+ $this->assertEqual($num_records, 32, 'Correct number of strings parsed from JS file');
+ }
}
/**
* Functional test for string translation and validation.
diff --git a/modules/simpletest/tests/bootstrap.test b/modules/simpletest/tests/bootstrap.test
index 61caf53caeb..6bee03e12f4 100644
--- a/modules/simpletest/tests/bootstrap.test
+++ b/modules/simpletest/tests/bootstrap.test
@@ -187,6 +187,7 @@ class BootstrapPageCacheTestCase extends DrupalWebTestCase {
$this->assertEqual($this->drupalGetHeader('Cache-Control'), 'public, max-age=0', 'Cache-Control header was sent.');
$this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', 'Expires header was sent.');
$this->assertEqual($this->drupalGetHeader('Foo'), 'bar', 'Custom header was sent.');
+ $this->assertEqual($this->drupalGetHeader('X-Content-Type-Options'), 'nosniff', 'X-Content-Type-Options header was sent.');
// Check replacing default headers.
$this->drupalGet('system-test/set-header', array('query' => array('name' => 'Expires', 'value' => 'Fri, 19 Nov 2008 05:00:00 GMT')));
@@ -236,6 +237,9 @@ class BootstrapPageCacheTestCase extends DrupalWebTestCase {
$this->assertTitle(t('Welcome to @site-name | @site-name', array('@site-name' => variable_get('site_name', 'Drupal'))), 'Site title matches.');
$this->assertRaw('