diff --git a/vendor/magento/module-catalog/Ui/DataProvider/Product/Form/Modifier/Eav/WysiwygConfigDataProcessor.php b/vendor/magento/module-catalog/Ui/DataProvider/Product/Form/Modifier/Eav/WysiwygConfigDataProcessor.php
index d301a45d14ff5..e3c4e8a5b5029 100644
--- a/vendor/magento/module-catalog/Ui/DataProvider/Product/Form/Modifier/Eav/WysiwygConfigDataProcessor.php
+++ b/vendor/magento/module-catalog/Ui/DataProvider/Product/Form/Modifier/Eav/WysiwygConfigDataProcessor.php
@@ -14,16 +14,28 @@
class WysiwygConfigDataProcessor implements WysiwygConfigDataProcessorInterface
{
/**
- * {@inheritdoc}
+ * Build WYSIWYG config data for a product attribute.
+ *
+ * @param \Magento\Catalog\Api\Data\ProductAttributeInterface $attribute
+ * @return array
*/
public function process(\Magento\Catalog\Api\Data\ProductAttributeInterface $attribute)
{
- return [
+ $wysiwygConfigData = [
'add_variables' => false,
'add_widgets' => false,
'add_directives' => true,
'use_container' => true,
'container_class' => 'hor-scroll',
];
+
+ if ((string)$attribute->getBackendType() === 'text' && (string)$attribute->getBackendTable() !== '') {
+ $wysiwygConfigData['utf8mb4Target'] = [
+ 'table' => (string)$attribute->getBackendTable(),
+ 'column' => 'value',
+ ];
+ }
+
+ return $wysiwygConfigData;
}
}
diff --git a/vendor/magento/module-catalog/view/adminhtml/ui_component/category_form.xml b/vendor/magento/module-catalog/view/adminhtml/ui_component/category_form.xml
index 02366e74fb089..81dc09ddf4261 100644
--- a/vendor/magento/module-catalog/view/adminhtml/ui_component/category_form.xml
+++ b/vendor/magento/module-catalog/view/adminhtml/ui_component/category_form.xml
@@ -197,6 +197,10 @@
- true
- true
+ -
+
- catalog_category_entity_text
+ - value
+
- category
diff --git a/vendor/magento/module-cms/view/adminhtml/ui_component/cms_block_form.xml b/vendor/magento/module-cms/view/adminhtml/ui_component/cms_block_form.xml
index fd81373c23c85..cbeed28ddf576 100644
--- a/vendor/magento/module-cms/view/adminhtml/ui_component/cms_block_form.xml
+++ b/vendor/magento/module-cms/view/adminhtml/ui_component/cms_block_form.xml
@@ -143,6 +143,10 @@
-
- block
+ -
+
- cms_block
+ - content
+
diff --git a/vendor/magento/module-cms/view/adminhtml/ui_component/cms_page_form.xml b/vendor/magento/module-cms/view/adminhtml/ui_component/cms_page_form.xml
index 6f531b4fda86c..b768ab85f5213 100644
--- a/vendor/magento/module-cms/view/adminhtml/ui_component/cms_page_form.xml
+++ b/vendor/magento/module-cms/view/adminhtml/ui_component/cms_page_form.xml
@@ -118,6 +118,10 @@
-
- page
+ -
+
- cms_page
+ - content
+
diff --git a/vendor/magento/module-ui/Component/Form/Element/Wysiwyg.php b/vendor/magento/module-ui/Component/Form/Element/Wysiwyg.php
index 040c27d4939ef..419727cd080de 100644
--- a/vendor/magento/module-ui/Component/Form/Element/Wysiwyg.php
+++ b/vendor/magento/module-ui/Component/Form/Element/Wysiwyg.php
@@ -6,11 +6,13 @@
namespace Magento\Ui\Component\Form\Element;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\Data\Form\Element\Editor;
use Magento\Framework\Data\Form;
use Magento\Framework\Data\FormFactory;
use Magento\Framework\View\Element\UiComponent\ContextInterface;
use Magento\Ui\Component\Wysiwyg\ConfigInterface;
+use Magento\Ui\Model\Validation\WysiwygValidationConfigResolver;
/**
* WYSIWYG form element
@@ -20,7 +22,7 @@
*/
class Wysiwyg extends AbstractElement
{
- const NAME = 'wysiwyg';
+ public const NAME = 'wysiwyg';
/**
* @var Form
@@ -34,6 +36,11 @@ class Wysiwyg extends AbstractElement
*/
protected $editor;
+ /**
+ * @var WysiwygValidationConfigResolver
+ */
+ private WysiwygValidationConfigResolver $validationConfigResolver;
+
/**
* @param ContextInterface $context
* @param FormFactory $formFactory
@@ -41,6 +48,7 @@ class Wysiwyg extends AbstractElement
* @param array $components
* @param array $data
* @param array $config
+ * @param WysiwygValidationConfigResolver|null $validationConfigResolver
*/
public function __construct(
ContextInterface $context,
@@ -48,12 +56,16 @@ public function __construct(
ConfigInterface $wysiwygConfig,
array $components = [],
array $data = [],
- array $config = []
+ array $config = [],
+ ?WysiwygValidationConfigResolver $validationConfigResolver = null
) {
+ $this->validationConfigResolver = $validationConfigResolver
+ ?? ObjectManager::getInstance()->get(WysiwygValidationConfigResolver::class);
$wysiwygConfigData = isset($config['wysiwygConfigData']) ? $config['wysiwygConfigData'] : [];
-
$this->form = $formFactory->create();
$wysiwygId = $context->getNamespace() . '_' . $data['name'];
+ $data['config']['validationParams']['allowUtf8mb4'] = $this->validationConfigResolver
+ ->resolveAllowUtf8mb4($config);
$this->editor = $this->form->addField(
$wysiwygId,
\Magento\Framework\Data\Form\Element\Editor::class,
diff --git a/vendor/magento/module-ui/Model/ResourceModel/Utf8mb4Support.php b/vendor/magento/module-ui/Model/ResourceModel/Utf8mb4Support.php
new file mode 100644
index 0000000000000..88c4af86a8fc3
--- /dev/null
+++ b/vendor/magento/module-ui/Model/ResourceModel/Utf8mb4Support.php
@@ -0,0 +1,108 @@
+
+ */
+ private array $columnSupportCache = [];
+
+ /**
+ * @param ResourceConnection $resourceConnection
+ * @param LoggerInterface $logger
+ */
+ public function __construct(
+ ResourceConnection $resourceConnection,
+ LoggerInterface $logger
+ ) {
+ $this->resourceConnection = $resourceConnection;
+ $this->logger = $logger;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function isColumnSupported(string $table, string $column): bool
+ {
+ $cacheKey = $table . '.' . $column;
+
+ if (array_key_exists($cacheKey, $this->columnSupportCache)) {
+ return $this->columnSupportCache[$cacheKey];
+ }
+
+ try {
+ $connection = $this->resourceConnection->getConnection();
+
+ $this->columnSupportCache[$cacheKey] = $this->isConnectionUtf8mb4($connection)
+ && $this->isColumnCharsetUtf8mb4($connection, $table, $column);
+ } catch (\Throwable $exception) {
+ $this->logger->warning($exception->getMessage());
+ $this->columnSupportCache[$cacheKey] = false;
+ }
+
+ return $this->columnSupportCache[$cacheKey];
+ }
+
+ /**
+ * Check if connection is set to utf8mb4.
+ *
+ * @param AdapterInterface $connection
+ * @return bool
+ */
+ private function isConnectionUtf8mb4(AdapterInterface $connection): bool
+ {
+ $row = $connection->fetchRow(
+ 'SELECT @@character_set_connection AS charset, @@collation_connection AS collation'
+ );
+
+ return is_array($row)
+ && isset($row['charset'], $row['collation'])
+ && str_starts_with((string)$row['charset'], 'utf8mb4')
+ && str_starts_with((string)$row['collation'], 'utf8mb4');
+ }
+
+ /**
+ * Check if column to be updated supports utf8mb4 character set.
+ *
+ * @param AdapterInterface $connection
+ * @param string $table
+ * @param string $column
+ * @return bool
+ */
+ private function isColumnCharsetUtf8mb4(AdapterInterface $connection, string $table, string $column): bool
+ {
+ $row = $connection->fetchRow(
+ sprintf(
+ 'SHOW FULL COLUMNS FROM `%s` LIKE %s',
+ $this->resourceConnection->getTableName($table),
+ $connection->quote($column)
+ )
+ );
+
+ // Text/blob columns without collation are not safe for utf8mb4 round-tripping.
+ return is_array($row)
+ && !empty($row['Collation'])
+ && str_starts_with((string)$row['Collation'], 'utf8mb4');
+ }
+}
diff --git a/vendor/magento/module-ui/Model/ResourceModel/Utf8mb4SupportInterface.php b/vendor/magento/module-ui/Model/ResourceModel/Utf8mb4SupportInterface.php
new file mode 100644
index 0000000000000..de51eae06ce10
--- /dev/null
+++ b/vendor/magento/module-ui/Model/ResourceModel/Utf8mb4SupportInterface.php
@@ -0,0 +1,20 @@
+
+ */
+ private array $supportCache = [];
+
+ /**
+ * @param Utf8mb4SupportInterface $utf8mb4Support
+ */
+ public function __construct(Utf8mb4SupportInterface $utf8mb4Support)
+ {
+ $this->utf8mb4Support = $utf8mb4Support;
+ }
+
+ /**
+ * Resolve whether utf8mb4 should be allowed for the current field.
+ *
+ * @param array $config
+ * @return bool
+ */
+ public function resolveAllowUtf8mb4(array $config): bool
+ {
+ $allowUtf8mb4 = $config['allowUtf8mb4'] ?? $config['wysiwygConfigData']['allowUtf8mb4'] ?? null;
+
+ if (is_bool($allowUtf8mb4)) {
+ return $allowUtf8mb4;
+ }
+
+ $target = $this->resolveTarget($config);
+
+ if ($target === null) {
+ return false;
+ }
+
+ $cacheKey = $target['table'] . '.' . $target['column'];
+
+ if (!array_key_exists($cacheKey, $this->supportCache)) {
+ $this->supportCache[$cacheKey] = $this->utf8mb4Support->isColumnSupported(
+ $target['table'],
+ $target['column']
+ );
+ }
+
+ return $this->supportCache[$cacheKey];
+ }
+
+ /**
+ * Resolve the storage target for the current field.
+ *
+ * @param array $config
+ * @return array{table: string, column: string}|null
+ */
+ private function resolveTarget(array $config): ?array
+ {
+ $target = $config['utf8mb4Target'] ?? $config['wysiwygConfigData']['utf8mb4Target'] ?? null;
+
+ if (!is_array($target)) {
+ return null;
+ }
+
+ $table = is_string($target['table'] ?? null) ? $target['table'] : '';
+ $column = is_string($target['column'] ?? null) ? $target['column'] : '';
+
+ if ($table === '' || $column === '') {
+ return null;
+ }
+
+ return [
+ 'table' => $table,
+ 'column' => $column,
+ ];
+ }
+}
diff --git a/vendor/magento/module-ui/etc/di.xml b/vendor/magento/module-ui/etc/di.xml
index 65253ab8279d4..a32afe1f6eb7f 100644
--- a/vendor/magento/module-ui/etc/di.xml
+++ b/vendor/magento/module-ui/etc/di.xml
@@ -17,6 +17,7 @@
+
diff --git a/vendor/magento/module-ui/view/base/ui_component/etc/definition.xml b/vendor/magento/module-ui/view/base/ui_component/etc/definition.xml
index 40d42d834b004..b7ed48245d99d 100644
--- a/vendor/magento/module-ui/view/base/ui_component/etc/definition.xml
+++ b/vendor/magento/module-ui/view/base/ui_component/etc/definition.xml
@@ -126,7 +126,7 @@
ui/content/content
- true
+ true
diff --git a/vendor/magento/module-ui/view/base/web/js/lib/validation/rules.js b/vendor/magento/module-ui/view/base/web/js/lib/validation/rules.js
index 0272acf2a9561..4c41696f396fa 100644
--- a/vendor/magento/module-ui/view/base/web/js/lib/validation/rules.js
+++ b/vendor/magento/module-ui/view/base/web/js/lib/validation/rules.js
@@ -18,6 +18,61 @@ define([
], function ($, _, utils, moment, tinycolor) {
'use strict';
+ function validateNoInvalidUnicodeCharacters(value, validator) {
+ var message = $.mage.__('Please remove invalid characters: {0}.'),
+ matches = [],
+ uniqueMatches = [],
+ seenMatches = {},
+ index,
+ code,
+ nextCode;
+
+ for (index = 0; index < value.length; index++) {
+ code = value.charCodeAt(index);
+
+ if (code >= 0xD800 && code <= 0xDBFF) {
+ nextCode = value.charCodeAt(index + 1);
+
+ // eslint-disable-next-line max-depth
+ if (!(nextCode >= 0xDC00 && nextCode <= 0xDFFF)) {
+ matches.push('U+' + code.toString(16).toUpperCase());
+ } else {
+ index++;
+ }
+ } else if (code >= 0xDC00 && code <= 0xDFFF) {
+ matches.push('U+' + code.toString(16).toUpperCase());
+ }
+ }
+
+ if (matches.length) {
+ matches.forEach(function (match) {
+ if (!seenMatches[match]) {
+ seenMatches[match] = true;
+ uniqueMatches.push(match);
+ }
+ });
+
+ validator.charErrorMessage = message.replace('{0}', uniqueMatches.join(', '));
+
+ return false;
+ }
+
+ return true;
+ }
+
+ function validateNoUtf8mb4Characters(value, validator) {
+ var message = $.mage.__('Please remove invalid characters: {0}.'),
+ matches = value.match(/(?:[\uD800-\uDBFF][\uDC00-\uDFFF])/g);
+
+ if (matches !== null) {
+ validator.charErrorMessage = message.replace('{0}', matches.join());
+
+ return false;
+ }
+
+ return true;
+ }
+
/**
* validate credit card number using mod10
* @param {String} s
@@ -1104,18 +1159,33 @@ define([
},
$.mage.__('The Date of Birth should not be greater than today.')
],
- 'validate-no-utf8mb4-characters': [
- function (value) {
- var validator = this,
- message = $.mage.__('Please remove invalid characters: {0}.'),
- matches = value.match(/(?:[\uD800-\uDBFF][\uDC00-\uDFFF])/g),
- result = matches === null;
+ 'validate-wysiwyg-content-characters': [
+ function (value, params, additionalParams) {
+ var allowUtf8mb4 = additionalParams &&
+ additionalParams.allowUtf8mb4 === true;
- if (!result) {
- validator.charErrorMessage = message.replace('{0}', matches.join());
+ if (!allowUtf8mb4) {
+ allowUtf8mb4 = window.wysiwygValidationConfig &&
+ window.wysiwygValidationConfig.allowUtf8mb4 === true;
}
- return result;
+ return allowUtf8mb4 ?
+ validateNoInvalidUnicodeCharacters(value, this) :
+ validateNoUtf8mb4Characters(value, this);
+ }, function () {
+ return this.charErrorMessage;
+ }
+ ],
+ 'validate-no-invalid-unicode-characters': [
+ function (value) {
+ return validateNoInvalidUnicodeCharacters(value, this);
+ }, function () {
+ return this.charErrorMessage;
+ }
+ ],
+ 'validate-no-utf8mb4-characters': [
+ function (value) {
+ return validateNoUtf8mb4Characters(value, this);
}, function () {
return this.charErrorMessage;
}
diff --git a/vendor/magento/magento2-base/lib/web/mage/validation.js b/vendor/magento/magento2-base/lib/web/mage/validation.js
index 72baf69740c9b..63045a40dbf40 100644
--- a/vendor/magento/magento2-base/lib/web/mage/validation.js
+++ b/vendor/magento/magento2-base/lib/web/mage/validation.js
@@ -177,6 +177,62 @@ define([
return c % 10 === 0;
}
+ /* eslint-disable max-depth */
+ function validateNoInvalidUnicodeCharacters(value, validator) {
+ var message = $.mage.__('Please remove invalid characters: {0}.'),
+ matches = [],
+ uniqueMatches = [],
+ seenMatches = {},
+ index,
+ code,
+ nextCode;
+
+ for (index = 0; index < value.length; index++) {
+ code = value.charCodeAt(index);
+
+ if (code >= 0xD800 && code <= 0xDBFF) {
+ nextCode = value.charCodeAt(index + 1);
+
+ if (!(nextCode >= 0xDC00 && nextCode <= 0xDFFF)) {
+ matches.push('U+' + code.toString(16).toUpperCase());
+ } else {
+ index++;
+ }
+ } else if (code >= 0xDC00 && code <= 0xDFFF) {
+ matches.push('U+' + code.toString(16).toUpperCase());
+ }
+ }
+
+ if (matches.length) {
+ matches.forEach(function (match) {
+ if (!seenMatches[match]) {
+ seenMatches[match] = true;
+ uniqueMatches.push(match);
+ }
+ });
+
+ validator.charErrorMessage = message.replace('{0}', uniqueMatches.join(', '));
+
+ return false;
+ }
+
+ return true;
+ }
+ /* eslint-enable max-depth */
+
+ function validateNoUtf8mb4Characters(value, validator) {
+ var message = $.mage.__('Please remove invalid characters: {0}.'),
+ matches = value.match(/(?:[\uD800-\uDBFF][\uDC00-\uDFFF])/g);
+
+ if (matches !== null) {
+ validator.charErrorMessage = message.replace('{0}', matches.join());
+
+ return false;
+ }
+
+ return true;
+ }
+
/**
* validate all table required inputs at once, using single hidden input
* @param {String} value
@@ -423,20 +479,35 @@ define([
},
$.mage.__('Please enter at least {0} characters')
],
+ 'validate-wysiwyg-content-characters': [
+ function (value, element, params) {
+ var allowUtf8mb4 = params &&
+ params.allowUtf8mb4 === true;
+
+ if (!allowUtf8mb4) {
+ allowUtf8mb4 = window.wysiwygValidationConfig &&
+ window.wysiwygValidationConfig.allowUtf8mb4 === true;
+ }
+
+ return allowUtf8mb4 ?
+ validateNoInvalidUnicodeCharacters(value, this) :
+ validateNoUtf8mb4Characters(value, this);
+ }, function () {
+ return this.charErrorMessage;
+ }
+ ],
+ 'validate-no-invalid-unicode-characters': [
+ function (value) {
+ return validateNoInvalidUnicodeCharacters(value, this);
+ }, function () {
+ return this.charErrorMessage;
+ }
+ ],
/* detect chars that would require more than 3 bytes */
'validate-no-utf8mb4-characters': [
function (value) {
- var validator = this,
- message = $.mage.__('Please remove invalid characters: {0}.'),
- matches = value.match(/(?:[\uD800-\uDBFF][\uDC00-\uDFFF])/g),
- result = matches === null;
-
- if (!result) {
- validator.charErrorMessage = message.replace('{0}', matches.join());
- }
-
- return result;
+ return validateNoUtf8mb4Characters(value, this);
}, function () {
return this.charErrorMessage;
}