diff --git a/core/config/schema/core.data_types.schema.yml b/core/config/schema/core.data_types.schema.yml index 1b06282d665a7682083fa92eb4d9dc6b7ef4531d..0c9ad70569adde8e914aa466da7ea41493f0c772 100644 --- a/core/config/schema/core.data_types.schema.yml +++ b/core/config/schema/core.data_types.schema.yml @@ -449,6 +449,9 @@ field.storage_settings.string: max_length: type: integer label: 'Maximum length' + case_sensitive: + type: boolean + label: 'Case sensitive' field.field_settings.string: type: mapping @@ -467,6 +470,10 @@ field.value.string: field.storage_settings.string_long: type: mapping label: 'String (long) settings' + mapping: + case_sensitive: + type: boolean + label: 'Case sensitive' field.field_settings.string_long: type: mapping @@ -489,6 +496,9 @@ field.storage_settings.uri: max_length: type: integer label: 'Maximum length' + case_sensitive: + type: boolean + label: 'Case sensitive' field.field_settings.uri: type: mapping diff --git a/core/config/schema/core.entity.schema.yml b/core/config/schema/core.entity.schema.yml index 69e1dd9a0442610874854184662e0ec0182aa97e..8de3f33ca38db81a09f69e52779b8488440de4b6 100644 --- a/core/config/schema/core.entity.schema.yml +++ b/core/config/schema/core.entity.schema.yml @@ -271,3 +271,4 @@ field.formatter.settings.uri_link: field.formatter.settings.timestamp_ago: type: mapping label: 'Timestamp ago display format settings' + diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php index 98b7a6b050d8a3e532c5ef54877de130558adf98..cacae24efca4e85d462657d94320535162e2a0a6 100644 --- a/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php +++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php @@ -229,6 +229,7 @@ public function mapConditionOperator($operator) { // In PostgreSQL, 'LIKE' is case-sensitive. For case-insensitive LIKE // statements, we need to use ILIKE instead. 'LIKE' => array('operator' => 'ILIKE'), + 'LIKE BINARY' => array('operator' => 'LIKE'), 'NOT LIKE' => array('operator' => 'NOT ILIKE'), 'REGEXP' => array('operator' => '~*'), ); diff --git a/core/lib/Drupal/Core/Database/Query/ConditionInterface.php b/core/lib/Drupal/Core/Database/Query/ConditionInterface.php index 551f087f2ac19561a8bb22842ba292d8e1747bbb..4af00ab88e6628d3cbb04eff2b3a339754f5cc9c 100644 --- a/core/lib/Drupal/Core/Database/Query/ConditionInterface.php +++ b/core/lib/Drupal/Core/Database/Query/ConditionInterface.php @@ -24,6 +24,19 @@ interface ConditionInterface { * Do not use this method to test for NULL values. Instead, use * QueryConditionInterface::isNull() or QueryConditionInterface::isNotNull(). * + * Drupal considers LIKE case insensitive and the following is often used + * to tell the database that case insensitive equivalence is desired: + * @code + * db_select('users') + * ->condition('name', db_like($name), 'LIKE') + * @endcode + * Use 'LIKE BINARY' instead of 'LIKE' for case sensitive queries. + * + * Note: When using MySQL, the exact behavior also depends on the used + * collation. if the field is set to binary, then a LIKE condition will also + * be case sensitive and when a case insensitive collation is used, the = + * operator will also be case insensitive. + * * @param $field * The name of the field to check. If you would like to add a more complex * condition involving operators or functions, use where(). @@ -33,8 +46,8 @@ interface ConditionInterface { * the array is dependent on the $operator. * @param $operator * The comparison operator, such as =, <, or >=. It also accepts more - * complex options such as IN, LIKE, or BETWEEN. Defaults to IN if $value is - * an array, and = otherwise. + * complex options such as IN, LIKE, LIKE BINARY, or BETWEEN. Defaults to IN + * if $value is an array, and = otherwise. * * @return \Drupal\Core\Database\Query\ConditionInterface * The called object. diff --git a/core/lib/Drupal/Core/Database/Query/Select.php b/core/lib/Drupal/Core/Database/Query/Select.php index 3c687da74d21e42cef27301422339a1d5700e87b..d369b6415d174a6768c30c3ef574096a7d7295ff 100644 --- a/core/lib/Drupal/Core/Database/Query/Select.php +++ b/core/lib/Drupal/Core/Database/Query/Select.php @@ -498,6 +498,13 @@ public function &getUnion() { return $this->union; } + /** + * {@inheritdoc} + */ + public function escapeLike($string) { + return $this->connection->escapeLike($string); + } + /** * {@inheritdoc} */ @@ -984,4 +991,5 @@ public function __clone() { $this->union[$key]['query'] = clone($aggregate['query']); } } + } diff --git a/core/lib/Drupal/Core/Database/Query/SelectExtender.php b/core/lib/Drupal/Core/Database/Query/SelectExtender.php index 9a378d23617cc57eb83082690b3d11d2f5744e8d..dec70fd185badf35b3a2671a96ae366089f9c481 100644 --- a/core/lib/Drupal/Core/Database/Query/SelectExtender.php +++ b/core/lib/Drupal/Core/Database/Query/SelectExtender.php @@ -171,6 +171,13 @@ public function &getUnion() { return $this->query->getUnion(); } + /** + * {@inheritdoc} + */ + public function escapeLike($string) { + return $this->query->escapeLike($string); + } + public function getArguments(PlaceholderInterface $queryPlaceholder = NULL) { return $this->query->getArguments($queryPlaceholder); } diff --git a/core/lib/Drupal/Core/Database/Query/SelectInterface.php b/core/lib/Drupal/Core/Database/Query/SelectInterface.php index b784610bf8e3381c4d7fe607bb7738cbf518ba9b..0d98285075120809f80b3b960dd6fec60805b8ae 100644 --- a/core/lib/Drupal/Core/Database/Query/SelectInterface.php +++ b/core/lib/Drupal/Core/Database/Query/SelectInterface.php @@ -125,6 +125,19 @@ public function &getTables(); */ public function &getUnion(); + /** + * Escapes characters that work as wildcard characters in a LIKE pattern. + * + * @param $string + * The string to escape. + * + * @return string + * The escaped string. + * + * @see \Drupal\Core\Database\Connection::escapeLike() + */ + public function escapeLike($string); + /** * Compiles and returns an associative array of the arguments for this prepared statement. * diff --git a/core/lib/Drupal/Core/Entity/Query/QueryInterface.php b/core/lib/Drupal/Core/Entity/Query/QueryInterface.php index caadc37f6edda09f21185e21b307fb686dd94565..0fad28ca0d911e81e59b2e3dcec5e152546f9d28 100644 --- a/core/lib/Drupal/Core/Entity/Query/QueryInterface.php +++ b/core/lib/Drupal/Core/Entity/Query/QueryInterface.php @@ -54,7 +54,7 @@ public function getEntityTypeId(); * same delta within that field. * @param $value * The value for $field. In most cases, this is a scalar and it's treated as - * case-insensitive. For more complex options, it is an array. The meaning + * case-insensitive. For more complex operators, it is an array. The meaning * of each element in the array is dependent on $operator. * @param $operator * Possible values: diff --git a/core/lib/Drupal/Core/Entity/Query/Sql/Condition.php b/core/lib/Drupal/Core/Entity/Query/Sql/Condition.php index 1067db3adb622d19d76181112deb1bd78c42905f..f34df0e93cdc8976e2f76a203f90ff6c02328605 100644 --- a/core/lib/Drupal/Core/Entity/Query/Sql/Condition.php +++ b/core/lib/Drupal/Core/Entity/Query/Sql/Condition.php @@ -33,20 +33,20 @@ public function compile($conditionContainer) { // SQL query object is only necessary to pass to Query::addField() so it // can join tables as necessary. On the other hand, conditions need to be // added to the $conditionContainer object to keep grouping. - $sqlQuery = $conditionContainer instanceof SelectInterface ? $conditionContainer : $conditionContainer->sqlQuery; - $tables = $this->query->getTables($sqlQuery); + $sql_query = $conditionContainer instanceof SelectInterface ? $conditionContainer : $conditionContainer->sqlQuery; + $tables = $this->query->getTables($sql_query); foreach ($this->conditions as $condition) { if ($condition['field'] instanceOf ConditionInterface) { - $sqlCondition = new SqlCondition($condition['field']->getConjunction()); + $sql_condition = new SqlCondition($condition['field']->getConjunction()); // Add the SQL query to the object before calling this method again. - $sqlCondition->sqlQuery = $sqlQuery; - $condition['field']->compile($sqlCondition); - $sqlQuery->condition($sqlCondition); + $sql_condition->sqlQuery = $sql_query; + $condition['field']->compile($sql_condition); + $sql_query->condition($sql_condition); } else { $type = strtoupper($this->conjunction) == 'OR' || $condition['operator'] == 'IS NULL' ? 'LEFT' : 'INNER'; - $this->translateCondition($condition); $field = $tables->addField($condition['field'], $type, $condition['langcode']); + static::translateCondition($condition, $sql_query, $tables->isFieldCaseSensitive($condition['field'])); $conditionContainer->condition($field, $condition['value'], $condition['operator']); } } @@ -70,24 +70,70 @@ public function notExists($field, $langcode = NULL) { * Translates the string operators to SQL equivalents. * * @param array $condition + * The condition array. + * @param \Drupal\Core\Database\Query\SelectInterface $sql_query + * Select query instance. + * @param bool|null $case_sensitive + * If the condition should be case sensitive or not, NULL if the field does + * not define it. + * + * @see \Drupal\Core\Database\Query\ConditionInterface::condition() */ - protected function translateCondition(&$condition) { + public static function translateCondition(&$condition, SelectInterface $sql_query, $case_sensitive) { + // There is nothing we can do for IN (). + if (is_array($condition['value'])) { + return; + } + // Ensure that the default operator is set to simplify the cases below. + if (empty($condition['operator'])) { + $condition['operator'] = '='; + } switch ($condition['operator']) { + case '=': + // If a field explicitly requests that queries should not be case + // sensitive, use the LIKE operator, otherwise keep =. + if ($case_sensitive === FALSE) { + $condition['value'] = $sql_query->escapeLike($condition['value']); + $condition['operator'] = 'LIKE'; + } + break; + case '<>': + // If a field explicitly requests that queries should not be case + // sensitive, use the NOT LIKE operator, otherwise keep <>. + if ($case_sensitive === FALSE) { + $condition['value'] = $sql_query->escapeLike($condition['value']); + $condition['operator'] = 'NOT LIKE'; + } + break; case 'STARTS_WITH': - $condition['value'] .= '%'; - $condition['operator'] = 'LIKE'; + if ($case_sensitive) { + $condition['operator'] = 'LIKE BINARY'; + } + else { + $condition['operator'] = 'LIKE'; + } + $condition['value'] = $sql_query->escapeLike($condition['value']) . '%'; break; case 'CONTAINS': - $condition['value'] = '%' . $condition['value'] . '%'; - $condition['operator'] = 'LIKE'; + if ($case_sensitive) { + $condition['operator'] = 'LIKE BINARY'; + } + else { + $condition['operator'] = 'LIKE'; + } + $condition['value'] = '%' . $sql_query->escapeLike($condition['value']) . '%'; break; case 'ENDS_WITH': - $condition['value'] = '%' . $condition['value']; - $condition['operator'] = 'LIKE'; + if ($case_sensitive) { + $condition['operator'] = 'LIKE BINARY'; + } + else { + $condition['operator'] = 'LIKE'; + } + $condition['value'] = '%' . $sql_query->escapeLike($condition['value']); break; - } } diff --git a/core/lib/Drupal/Core/Entity/Query/Sql/ConditionAggregate.php b/core/lib/Drupal/Core/Entity/Query/Sql/ConditionAggregate.php index cd836917d7a9359ac9f4794ac5bf553428205f4f..584a883c6e354c3b96950f1f94558bcd91f7047e 100644 --- a/core/lib/Drupal/Core/Entity/Query/Sql/ConditionAggregate.php +++ b/core/lib/Drupal/Core/Entity/Query/Sql/ConditionAggregate.php @@ -10,6 +10,7 @@ use Drupal\Core\Database\Query\SelectInterface; use Drupal\Core\Entity\Query\ConditionAggregateBase; use Drupal\Core\Entity\Query\ConditionAggregateInterface; +use Drupal\Core\Database\Query\Condition as SqlCondition; /** * Defines the aggregate condition for sql based storage. @@ -29,7 +30,7 @@ public function compile($conditionContainer) { $tables = new Tables($sql_query); foreach ($this->conditions as $condition) { if ($condition['field'] instanceOf ConditionAggregateInterface) { - $sql_condition = new Condition($condition['field']->getConjunction()); + $sql_condition = new SqlCondition($condition['field']->getConjunction()); // Add the SQL query to the object before calling this method again. $sql_condition->sqlQuery = $sql_query; $condition['field']->compile($sql_condition); @@ -37,8 +38,8 @@ public function compile($conditionContainer) { } else { $type = ((strtoupper($this->conjunction) == 'OR') || ($condition['operator'] == 'IS NULL')) ? 'LEFT' : 'INNER'; - $this->translateCondition($condition); $field = $tables->addField($condition['field'], $type, $condition['langcode']); + Condition::translateCondition($condition, $sql_query, $tables->isFieldCaseSensitive($condition['field'])); $function = $condition['function']; $placeholder = ':db_placeholder_' . $conditionContainer->nextPlaceholder(); $conditionContainer->having("$function($field) {$condition['operator']} $placeholder", array($placeholder => $condition['value'])); @@ -60,29 +61,4 @@ public function notExists($field, $function, $langcode = NULL) { return $this->condition($field, $function, NULL, 'IS NULL', $langcode); } - /** - * Translates the string operators to SQL equivalents. - * - * @param array $condition - * An associative array containing the following keys: - * - value: The value to filter by - * - operator: The operator to use for comparison, for example "=". - */ - protected function translateCondition(&$condition) { - switch ($condition['operator']) { - case 'STARTS_WITH': - $condition['value'] .= '%'; - $condition['operator'] = 'LIKE'; - break; - case 'CONTAINS': - $condition['value'] = '%' . $condition['value'] . '%'; - $condition['operator'] = 'LIKE'; - break; - case 'ENDS_WITH': - $condition['value'] = '%' . $condition['value']; - $condition['operator'] = 'LIKE'; - break; - } - } - } diff --git a/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php b/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php index 270c3123fe7eae23ebb768e77b4d76d222263ba6..ffd84d4f6e5e076b73840d455c4d29bb792e81b7 100644 --- a/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php +++ b/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php @@ -48,6 +48,13 @@ class Tables implements TablesInterface { */ protected $entityManager; + /** + * List of case sensitive fields. + * + * @var array + */ + protected $caseSensitiveFields = array(); + /** * @param \Drupal\Core\Database\Query\SelectInterface $sql_query */ @@ -139,6 +146,10 @@ public function addField($field, $type, $langcode) { } $table = $this->ensureFieldTable($index_prefix, $field_storage, $type, $langcode, $base_table, $entity_id_field, $field_id_field); $sql_column = $table_mapping->getFieldColumnName($field_storage, $column); + $property_definitions = $field_storage->getPropertyDefinitions(); + if (isset($property_definitions[$column])) { + $this->caseSensitiveFields[$field] = $property_definitions[$column]->getSetting('case_sensitive'); + } } // The field is stored in a shared table. else { @@ -155,6 +166,17 @@ public function addField($field, $type, $langcode) { $entity_tables[$entity_base_table] = $this->getTableMapping($entity_base_table, $entity_type_id); $sql_column = $specifier; $table = $this->ensureEntityTable($index_prefix, $specifier, $type, $langcode, $base_table, $entity_id_field, $entity_tables); + + // If there is a field storage (some specifiers are not, like + // default_langcode), check for case sensitivity. + if ($field_storage) { + $column = $field_storage->getMainPropertyName(); + $base_field_property_definitions = $field_storage->getPropertyDefinitions(); + if (isset($base_field_property_definitions[$column])) { + $this->caseSensitiveFields[$field] = $base_field_property_definitions[$column]->getSetting('case_sensitive'); + } + } + } // If there are more specifiers to come, it's a relationship. if ($field_storage && $key < $count) { @@ -186,6 +208,15 @@ public function addField($field, $type, $langcode) { return "$table.$sql_column"; } + /** + * {@inheritdoc} + */ + public function isFieldCaseSensitive($field_name) { + if (isset($this->caseSensitiveFields[$field_name])) { + return $this->caseSensitiveFields[$field_name]; + } + } + /** * Join entity table if necessary and return the alias for it. * diff --git a/core/lib/Drupal/Core/Entity/Query/Sql/TablesInterface.php b/core/lib/Drupal/Core/Entity/Query/Sql/TablesInterface.php index 50e56552223dc116572f2532e428afc15f983505..0301396a9e8cb7cb23da51fc90d3e70d22579751 100644 --- a/core/lib/Drupal/Core/Entity/Query/Sql/TablesInterface.php +++ b/core/lib/Drupal/Core/Entity/Query/Sql/TablesInterface.php @@ -20,8 +20,8 @@ interface TablesInterface { * then entity property name. * @param string $type * Join type, can either be INNER or LEFT. - * @param $langcode - * The language code the field values are to be shown in. + * @param string $langcode + * The language code the field values are to be queried in. * * @throws \Drupal\Core\Entity\Query\QueryException * If $field specifies an invalid relationship. @@ -33,4 +33,18 @@ interface TablesInterface { */ public function addField($field, $type, $langcode); + /** + * Returns whether the given field is case sensitive. + * + * This information can only be provided after it was added with addField(). + * + * @param string $field_name + * The name of the field. + * + * @return bool|null + * TRUE if the field is case sensitive, FALSE if not. Returns NULL when the + * field did not define if it is case sensitive or not. + */ + public function isFieldCaseSensitive($field_name); + } diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/StringItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/StringItem.php index 3f641e53700c20522c7f89128b670b786bb26ffb..c7641ffe242e72edb4ac64e8d12b98a213e30169 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/StringItem.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/StringItem.php @@ -44,6 +44,7 @@ public static function schema(FieldStorageDefinitionInterface $field_definition) 'type' => 'varchar', 'length' => (int) $field_definition->getSetting('max_length'), 'not null' => FALSE, + 'binary' => $field_definition->getSetting('case_sensitive'), ), ), ); diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/StringItemBase.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/StringItemBase.php index c1ff41bad8d408fc06d16087924d98cd238d8982..2a587ed4757c4e9a81c3c35839264c8ecd3a9a19 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/StringItemBase.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/StringItemBase.php @@ -17,6 +17,15 @@ */ abstract class StringItemBase extends FieldItemBase { + /** + * {@inheritdoc} + */ + public static function defaultStorageSettings() { + return array( + 'case_sensitive' => FALSE, + ) + parent::defaultStorageSettings(); + } + /** * {@inheritdoc} */ @@ -24,7 +33,8 @@ public static function propertyDefinitions(FieldStorageDefinitionInterface $fiel // This is called very early by the user entity roles field. Prevent // early t() calls by using the TranslationWrapper. $properties['value'] = DataDefinition::create('string') - ->setLabel(new TranslationWrapper('Text value')); + ->setLabel(new TranslationWrapper('Text value')) + ->setSetting('case_sensitive', $field_definition->getSetting('case_sensitive')); return $properties; } diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/StringLongItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/StringLongItem.php index d8d833c27e75c5bf5ac27f67a37b3f5532951d1c..f8bfda6fe7f081f268cbccc3691b6b7e6e6a5229 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/StringLongItem.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/StringLongItem.php @@ -31,7 +31,7 @@ public static function schema(FieldStorageDefinitionInterface $field_definition) return array( 'columns' => array( 'value' => array( - 'type' => 'text', + 'type' => $field_definition->getSetting('case_sensitive') ? 'blob' : 'text', 'size' => 'big', ), ), diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/UriItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/UriItem.php index a50696b2ee7608e4e80f4063eb1289b7c5f26166..7a81f40b2df242c82165c7cd4d87eb9242d6df65 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/UriItem.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/UriItem.php @@ -41,7 +41,8 @@ public static function defaultStorageSettings() { */ public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) { $properties['value'] = DataDefinition::create('uri') - ->setLabel(t('URI value')); + ->setLabel(t('URI value')) + ->setSetting('case_sensitive', $field_definition->getSetting('case_sensitive')); return $properties; } @@ -56,6 +57,7 @@ public static function schema(FieldStorageDefinitionInterface $field_definition) 'type' => 'varchar', 'length' => (int) $field_definition->getSetting('max_length'), 'not null' => TRUE, + 'binary' => $field_definition->getSetting('case_sensitive'), ), ), ); diff --git a/core/modules/file/src/Entity/File.php b/core/modules/file/src/Entity/File.php index b21a18297259ee0a5c4737ea182a9d17a372d46c..d1ae4f6ef6b35f60cc0c95b38a71e215d64f04cd 100644 --- a/core/modules/file/src/Entity/File.php +++ b/core/modules/file/src/Entity/File.php @@ -260,7 +260,8 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { $fields['uri'] = BaseFieldDefinition::create('uri') ->setLabel(t('URI')) ->setDescription(t('The URI to access the file (either local or remote).')) - ->setSetting('max_length', 255); + ->setSetting('max_length', 255) + ->setSetting('case_sensitive', TRUE); $fields['filemime'] = BaseFieldDefinition::create('string') ->setLabel(t('File MIME type')) diff --git a/core/modules/file/src/FileStorageSchema.php b/core/modules/file/src/FileStorageSchema.php index 9c66e66aab9e46ea153f98a6dfb815072255b3a8..f253020bc5c66596c05c2e1e770693daf46e0e31 100644 --- a/core/modules/file/src/FileStorageSchema.php +++ b/core/modules/file/src/FileStorageSchema.php @@ -31,9 +31,6 @@ protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $st case 'uri': $this->addSharedTableFieldUniqueKey($storage_definition, $schema, TRUE); - // @todo There should be a 'binary' field type or setting: - // https://www.drupal.org/node/2068655. - $schema['fields'][$field_name]['binary'] = TRUE; break; } } diff --git a/core/modules/file/src/Tests/SaveTest.php b/core/modules/file/src/Tests/SaveTest.php index ee6c1a34c2b7bcd65d4eb68e640889465ea29d9f..cc12515b4c7ca3527205708da499525e17ec155d 100644 --- a/core/modules/file/src/Tests/SaveTest.php +++ b/core/modules/file/src/Tests/SaveTest.php @@ -7,7 +7,7 @@ namespace Drupal\file\Tests; -use Drupal\Core\Language\LanguageInterface; +use Drupal\file\Entity\File; /** * File saving tests. @@ -17,7 +17,7 @@ class SaveTest extends FileManagedUnitTestBase { function testFileSave() { // Create a new file entity. - $file = entity_create('file', array( + $file = File::create(array( 'uid' => 1, 'filename' => 'druplicon.txt', 'uri' => 'public://druplicon.txt', @@ -59,7 +59,7 @@ function testFileSave() { // Try to insert a second file with the same name apart from case insensitivity // to ensure the 'uri' index allows for filenames with different cases. - $file = entity_create('file', array( + $uppercase_file = File::create(array( 'uid' => 1, 'filename' => 'DRUPLICON.txt', 'uri' => 'public://DRUPLICON.txt', @@ -68,7 +68,16 @@ function testFileSave() { 'changed' => 1, 'status' => FILE_STATUS_PERMANENT, )); - file_put_contents($file->getFileUri(), 'hello world'); - $file->save(); + file_put_contents($uppercase_file->getFileUri(), 'hello world'); + $uppercase_file->save(); + + // Ensure that file URI entity queries are case sensitive. + $fids = \Drupal::entityQuery('file') + ->condition('uri', $uppercase_file->getFileUri()) + ->execute(); + + $this->assertEqual(1, count($fids)); + $this->assertEqual(array($uppercase_file->id() => $uppercase_file->id()), $fids); + } } diff --git a/core/modules/system/src/Tests/Entity/EntityQueryAggregateTest.php b/core/modules/system/src/Tests/Entity/EntityQueryAggregateTest.php index 1df6c88d6ccd1b969e16754af7b7db6938072f79..24ee9d5e83b9f3b1aacfe2beb289ae5e8291fffd 100644 --- a/core/modules/system/src/Tests/Entity/EntityQueryAggregateTest.php +++ b/core/modules/system/src/Tests/Entity/EntityQueryAggregateTest.php @@ -45,7 +45,7 @@ class EntityQueryAggregateTest extends EntityUnitTestBase { protected function setUp() { parent::setUp(); - $this->entityStorage = $this->container->get('entity.manager')->getStorage('entity_test'); + $this->entityStorage = $this->entityManager->getStorage('entity_test'); $this->factory = $this->container->get('entity.query'); // Add some fieldapi fields to be used in the test. diff --git a/core/modules/system/src/Tests/Entity/EntityQueryTest.php b/core/modules/system/src/Tests/Entity/EntityQueryTest.php index 422bfa7c1f0512ee1cf4896197f8efb5581cd0b8..7062af24cf70fd07bf76a5f0be456650d8f5185c 100644 --- a/core/modules/system/src/Tests/Entity/EntityQueryTest.php +++ b/core/modules/system/src/Tests/Entity/EntityQueryTest.php @@ -9,6 +9,9 @@ use Drupal\Component\Utility\Unicode; use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\entity_test\Entity\EntityTestMulRev; +use Drupal\field\Entity\FieldConfig; +use Drupal\field\Entity\FieldStorageConfig; use Drupal\language\Entity\ConfigurableLanguage; use Symfony\Component\HttpFoundation\Request; @@ -490,7 +493,7 @@ protected function assertBundleOrder($order) { * * The tags and metadata should propagate to the SQL query object. */ - function testMetaData() { + public function testMetaData() { $query = \Drupal::entityQuery('entity_test_mulrev'); $query ->addTag('efq_metadata_test') @@ -500,4 +503,167 @@ function testMetaData() { global $efq_test_metadata; $this->assertEqual($efq_test_metadata, 'bar', 'Tag and metadata propagated to the SQL query object.'); } + + /** + * Test case sensitive and in-sensitive query conditions. + */ + public function testCaseSensitivity() { + $bundle = $this->randomMachineName(); + + $field_storage = FieldStorageConfig::create(array( + 'field_name' => 'field_ci', + 'entity_type' => 'entity_test_mulrev', + 'type' => 'string', + 'cardinality' => 1, + 'translatable' => FALSE, + 'settings' => array( + 'case_sensitive' => FALSE, + ) + )); + $field_storage->save(); + + FieldConfig::create(array( + 'field_storage' => $field_storage, + 'bundle' => $bundle, + ))->save(); + + $field_storage = FieldStorageConfig::create(array( + 'field_name' => 'field_cs', + 'entity_type' => 'entity_test_mulrev', + 'type' => 'string', + 'cardinality' => 1, + 'translatable' => FALSE, + 'settings' => array( + 'case_sensitive' => TRUE, + ), + )); + $field_storage->save(); + + FieldConfig::create(array( + 'field_storage' => $field_storage, + 'bundle' => $bundle, + ))->save(); + + $fixtures = array(); + + for ($i = 0; $i < 2; $i++) { + $string = $this->randomMachineName(); + $fixtures[] = array( + 'original' => $string, + 'uppercase' => Unicode::strtoupper($string), + 'lowercase' => Unicode::strtolower($string), + ); + } + + EntityTestMulRev::create(array( + 'type' => $bundle, + 'name' => $this->randomMachineName(), + 'langcode' => 'en', + 'field_ci' => $fixtures[0]['uppercase'] . $fixtures[1]['lowercase'], + 'field_cs' => $fixtures[0]['uppercase'] . $fixtures[1]['lowercase'] + ))->save(); + + // Check the case insensitive field, = operator. + $result = \Drupal::entityQuery('entity_test_mulrev')->condition( + 'field_ci', $fixtures[0]['lowercase'] . $fixtures[1]['lowercase'] + )->execute(); + $this->assertIdentical(count($result), 1, 'Case insensitive, lowercase'); + + $result = \Drupal::entityQuery('entity_test_mulrev')->condition( + 'field_ci', $fixtures[0]['uppercase'] . $fixtures[1]['uppercase'] + )->execute(); + $this->assertIdentical(count($result), 1, 'Case insensitive, uppercase'); + + $result = \Drupal::entityQuery('entity_test_mulrev')->condition( + 'field_ci', $fixtures[0]['uppercase'] . $fixtures[1]['lowercase'] + )->execute(); + $this->assertIdentical(count($result), 1, 'Case insensitive, mixed.'); + + // Check the case sensitive field, = operator. + $result = \Drupal::entityQuery('entity_test_mulrev')->condition( + 'field_cs', $fixtures[0]['lowercase'] . $fixtures[1]['lowercase'] + )->execute(); + $this->assertIdentical(count($result), 0, 'Case sensitive, lowercase.'); + + $result = \Drupal::entityQuery('entity_test_mulrev')->condition( + 'field_cs', $fixtures[0]['uppercase'] . $fixtures[1]['uppercase'] + )->execute(); + $this->assertIdentical(count($result), 0, 'Case sensitive, uppercase.'); + + $result = \Drupal::entityQuery('entity_test_mulrev')->condition( + 'field_cs', $fixtures[0]['uppercase'] . $fixtures[1]['lowercase'] + )->execute(); + $this->assertIdentical(count($result), 1, 'Case sensitive, exact match.'); + + // Check the case insensitive field, STARTS_WITH operator. + $result = \Drupal::entityQuery('entity_test_mulrev')->condition( + 'field_ci', $fixtures[0]['lowercase'], 'STARTS_WITH' + )->execute(); + $this->assertIdentical(count($result), 1, 'Case sensitive, lowercase.'); + + $result = \Drupal::entityQuery('entity_test_mulrev')->condition( + 'field_ci', $fixtures[0]['uppercase'], 'STARTS_WITH' + )->execute(); + $this->assertIdentical(count($result), 1, 'Case sensitive, exact match.'); + + // Check the case sensitive field, STARTS_WITH operator. + $result = \Drupal::entityQuery('entity_test_mulrev')->condition( + 'field_cs', $fixtures[0]['lowercase'], 'STARTS_WITH' + )->execute(); + $this->assertIdentical(count($result), 0, 'Case sensitive, lowercase.'); + + $result = \Drupal::entityQuery('entity_test_mulrev')->condition( + 'field_cs', $fixtures[0]['uppercase'], 'STARTS_WITH' + )->execute(); + $this->assertIdentical(count($result), 1, 'Case sensitive, exact match.'); + + + // Check the case insensitive field, ENDS_WITH operator. + $result = \Drupal::entityQuery('entity_test_mulrev')->condition( + 'field_ci', $fixtures[1]['lowercase'], 'ENDS_WITH' + )->execute(); + $this->assertIdentical(count($result), 1, 'Case sensitive, lowercase.'); + + $result = \Drupal::entityQuery('entity_test_mulrev')->condition( + 'field_ci', $fixtures[1]['uppercase'], 'ENDS_WITH' + )->execute(); + $this->assertIdentical(count($result), 1, 'Case sensitive, exact match.'); + + // Check the case sensitive field, ENDS_WITH operator. + $result = \Drupal::entityQuery('entity_test_mulrev')->condition( + 'field_cs', $fixtures[1]['lowercase'], 'ENDS_WITH' + )->execute(); + $this->assertIdentical(count($result), 1, 'Case sensitive, lowercase.'); + + $result = \Drupal::entityQuery('entity_test_mulrev')->condition( + 'field_cs', $fixtures[1]['uppercase'], 'ENDS_WITH' + )->execute(); + $this->assertIdentical(count($result), 0, 'Case sensitive, exact match.'); + + + // Check the case insensitive field, CONTAINS operator, use the inner 8 + // characters of the uppercase and lowercase strings. + $result = \Drupal::entityQuery('entity_test_mulrev')->condition( + 'field_ci', Unicode::substr($fixtures[0]['uppercase'] . $fixtures[1]['lowercase'], 4, 8), 'CONTAINS' + )->execute(); + $this->assertIdentical(count($result), 1, 'Case sensitive, lowercase.'); + + $result = \Drupal::entityQuery('entity_test_mulrev')->condition( + 'field_ci', Unicode::strtolower(Unicode::substr($fixtures[0]['uppercase'] . $fixtures[1]['lowercase'], 4, 8)), 'CONTAINS' + )->execute(); + $this->assertIdentical(count($result), 1, 'Case sensitive, exact match.'); + + // Check the case sensitive field, CONTAINS operator. + $result = \Drupal::entityQuery('entity_test_mulrev')->condition( + 'field_cs', Unicode::substr($fixtures[0]['uppercase'] . $fixtures[1]['lowercase'], 4, 8), 'CONTAINS' + )->execute(); + $this->assertIdentical(count($result), 1, 'Case sensitive, lowercase.'); + + $result = \Drupal::entityQuery('entity_test_mulrev')->condition( + 'field_cs', Unicode::strtolower(Unicode::substr($fixtures[0]['uppercase'] . $fixtures[1]['lowercase'], 4, 8)), 'CONTAINS' + )->execute(); + $this->assertIdentical(count($result), 0, 'Case sensitive, exact match.'); + + } + } diff --git a/core/modules/taxonomy/src/TermStorage.php b/core/modules/taxonomy/src/TermStorage.php index c686f1142042534ec4a300fbdea45adc2de13a04..32c364317994e21ee241944a99637c192e1e2534 100644 --- a/core/modules/taxonomy/src/TermStorage.php +++ b/core/modules/taxonomy/src/TermStorage.php @@ -81,17 +81,6 @@ public function create(array $values = array()) { return $entity; } - /** - * {@inheritdoc} - */ - protected function buildPropertyQuery(QueryInterface $entity_query, array $values) { - if (isset($values['name'])) { - $entity_query->condition('name', $values['name'], 'LIKE'); - unset($values['name']); - } - parent::buildPropertyQuery($entity_query, $values); - } - /** * {@inheritdoc} */ diff --git a/core/modules/text/src/Plugin/Field/FieldType/TextItem.php b/core/modules/text/src/Plugin/Field/FieldType/TextItem.php index 25851770d5a3e3e560aeb456de729972a05bc77a..379222025e48a87b4681b4867c50826db614dd5a 100644 --- a/core/modules/text/src/Plugin/Field/FieldType/TextItem.php +++ b/core/modules/text/src/Plugin/Field/FieldType/TextItem.php @@ -91,6 +91,7 @@ public function storageSettingsForm(array &$form, FormStateInterface $form_state '#min' => 1, '#disabled' => $has_data, ); + $element += parent::storageSettingsForm($form, $form_state, $has_data); return $element; }