From 91f1ab6272d3ba44dc1181eb0adf5d32f1fed6f0 Mon Sep 17 00:00:00 2001 From: eileen Date: Wed, 4 Nov 2020 11:07:37 +1300 Subject: [PATCH] dev/core#2165 Test & fix for Handle emojis less fatally where not supported Fix emoji handling by returning always-false when unwelcomely present --- CRM/Core/BAO/SchemaHandler.php | 19 +++++++++++- CRM/Core/DAO.php | 16 ++++++++++ Civi/Test/Api3TestTrait.php | 2 +- .../phpunit/api/v4/Action/ContactGetTest.php | 30 +++++++++++++++++++ 4 files changed, 65 insertions(+), 2 deletions(-) diff --git a/CRM/Core/BAO/SchemaHandler.php b/CRM/Core/BAO/SchemaHandler.php index cb56200c4b..f4e7892607 100644 --- a/CRM/Core/BAO/SchemaHandler.php +++ b/CRM/Core/BAO/SchemaHandler.php @@ -877,7 +877,7 @@ MODIFY {$columnName} varchar( $length ) * * @return string */ - public static function getInUseCollation() { + public static function getInUseCollation(): string { if (!isset(\Civi::$statics[__CLASS__][__FUNCTION__])) { $dao = CRM_Core_DAO::executeQuery('SHOW TABLE STATUS LIKE \'civicrm_contact\''); $dao->fetch(); @@ -886,6 +886,23 @@ MODIFY {$columnName} varchar( $length ) return \Civi::$statics[__CLASS__][__FUNCTION__]; } + /** + * Does the database support utf8mb4. + * + * Utf8mb4 is required to support emojis but older databases may not have it enabled. + * + * This is aggressively cached despite just being a string function + * as it is expected it might be called many times. + * + * @return bool + */ + public static function databaseSupportsUTF8MB4(): bool { + if (!isset(\Civi::$statics[__CLASS__][__FUNCTION__])) { + \Civi::$statics[__CLASS__][__FUNCTION__] = stripos(self::getInUseCollation(), 'utf8mb4') === TRUE; + } + return \Civi::$statics[__CLASS__][__FUNCTION__]; + } + /** * Get the database collation. * diff --git a/CRM/Core/DAO.php b/CRM/Core/DAO.php index 13c0c8c65a..2d40afd073 100644 --- a/CRM/Core/DAO.php +++ b/CRM/Core/DAO.php @@ -2782,6 +2782,22 @@ SELECT contact_id */ public static function createSQLFilter($fieldName, $filter, $type = NULL, $alias = NULL, $returnSanitisedArray = FALSE) { foreach ($filter as $operator => $criteria) { + if (!CRM_Core_BAO_SchemaHandler::databaseSupportsUTF8MB4()) { + foreach ((array) $criteria as $criterion) { + if (!empty($criterion) && !is_numeric($criterion) + // The first 2 criteria are redundant but are added as they + // seem like they would + // be quicker than this 3rd check. + && max(array_map('ord', str_split($criterion))) >= 240) { + // String contains unsupported emojis. + // We return a clause that resolves to false as an emoji string by definition cannot be saved. + // note that if we return just 0 for false if gets lost in empty checks. + // https://stackoverflow.com/questions/16496554/can-php-detect-4-byte-encoded-utf8-chars + return '0 = 1'; + } + } + } + if (in_array($operator, self::acceptedSQLOperators(), TRUE)) { switch ($operator) { // unary operators diff --git a/Civi/Test/Api3TestTrait.php b/Civi/Test/Api3TestTrait.php index 176564a686..f84c4c2f98 100644 --- a/Civi/Test/Api3TestTrait.php +++ b/Civi/Test/Api3TestTrait.php @@ -173,7 +173,7 @@ trait Api3TestTrait { /** * This function exists to wrap api getValue function & check the result - * so we can ensure they succeed & throw exceptions without litterering the test with checks + * so we can ensure they succeed & throw exceptions without littering the test with checks * There is a type check in this * * @param string $entity diff --git a/tests/phpunit/api/v4/Action/ContactGetTest.php b/tests/phpunit/api/v4/Action/ContactGetTest.php index 649928815e..965a8b88ee 100644 --- a/tests/phpunit/api/v4/Action/ContactGetTest.php +++ b/tests/phpunit/api/v4/Action/ContactGetTest.php @@ -102,4 +102,34 @@ class ContactGetTest extends \api\v4\UnitTestCase { $this->assertTrue(!empty($limit1->single()['sort_name'])); } + /** + * Test a lack of fatal errors when the where contains an emoji. + * + * By default our DBs are not 🦉 compliant. This test will age + * out when we are. + * + * @throws \API_Exception + */ + public function testEmoji(): void { + $schemaNeedsAlter = \CRM_Core_BAO_SchemaHandler::databaseSupportsUTF8MB4(); + if ($schemaNeedsAlter) { + \CRM_Core_DAO::executeQuery(" + ALTER TABLE civicrm_contact MODIFY COLUMN + `first_name` VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'First Name.', + CHARSET utf8 + "); + } + Contact::get() + ->setDebug(TRUE) + ->addWhere('first_name', '=', '🦉Claire') + ->execute(); + if ($schemaNeedsAlter) { + \CRM_Core_DAO::executeQuery(" + ALTER TABLE civicrm_contact MODIFY COLUMN + `first_name` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'First Name.', + CHARSET utf8mb4 + "); + } + } + } -- 2.25.1