From 389ec16318e6140fb0aac8443466998df4a7e65d Mon Sep 17 00:00:00 2001 From: Divesh Pahuja Date: Fri, 21 Jul 2023 14:32:05 +0200 Subject: [PATCH 01/29] Revert "[Bug] - Deadlock found when trying to get lock on DataObject import (#15391)" (#15591) This reverts commit c11a19aa --- .../Migrations/Version20230616085142.php | 95 ------------------- models/DataObject/Data/ObjectMetadata/Dao.php | 9 +- 2 files changed, 2 insertions(+), 102 deletions(-) delete mode 100644 bundles/CoreBundle/Migrations/Version20230616085142.php diff --git a/bundles/CoreBundle/Migrations/Version20230616085142.php b/bundles/CoreBundle/Migrations/Version20230616085142.php deleted file mode 100644 index 7ef56278c34..00000000000 --- a/bundles/CoreBundle/Migrations/Version20230616085142.php +++ /dev/null @@ -1,95 +0,0 @@ -fetchAllAssociative( - "SHOW FULL TABLES - WHERE `Tables_in_{$db->getDatabase()}` LIKE 'object_metadata_%' AND Table_type = 'BASE TABLE'" - ); - foreach ($metaDataTables as $table) { - $tableName = current($table); - $metaDataTable = $schema->getTable($tableName); - - if (!$metaDataTable->hasColumn(self::AUTO_ID)) { - $metaDataTable->addColumn(self::AUTO_ID, 'integer', [ - 'autoincrement' => true, - ]); - - $fkName = AbstractDao::getForeignKeyName($tableName, self::ID_COLUMN); - $metaDataTable->removeForeignKey($fkName); - $metaDataTable->dropPrimaryKey(); - $metaDataTable->setPrimaryKey([self::AUTO_ID]); - $metaDataTable->addUniqueIndex(self::PK_COLUMNS, self::UNIQUE_INDEX_NAME); - - $metaDataTable->addForeignKeyConstraint( - 'objects', - [self::ID_COLUMN], - [self::ID_COLUMN], - ['onDelete' => 'CASCADE'], - $fkName - ); - } - } - } - - public function down(Schema $schema): void - { - $db = Db::get(); - - $metaDataTables = $db->fetchAllAssociative( - "SHOW FULL TABLES - WHERE `Tables_in_{$db->getDatabase()}` LIKE 'object_metadata_%' AND Table_type = 'BASE TABLE'" - ); - foreach ($metaDataTables as $table) { - $metaDataTable = $schema->getTable(current($table)); - - if ($metaDataTable->hasColumn(self::AUTO_ID)) { - $metaDataTable->dropPrimaryKey(); - $metaDataTable->dropColumn(self::AUTO_ID); - $metaDataTable->setPrimaryKey(self::PK_COLUMNS); - $metaDataTable->dropIndex(self::UNIQUE_INDEX_NAME); - } - } - - } -} diff --git a/models/DataObject/Data/ObjectMetadata/Dao.php b/models/DataObject/Data/ObjectMetadata/Dao.php index 5ff15189f61..19b7f92c6cb 100644 --- a/models/DataObject/Data/ObjectMetadata/Dao.php +++ b/models/DataObject/Data/ObjectMetadata/Dao.php @@ -118,7 +118,6 @@ public function createOrUpdateTable(DataObject\ClassDefinition $class) $table = 'object_metadata_' . $classId; $this->db->executeQuery('CREATE TABLE IF NOT EXISTS `' . $table . "` ( - `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, `o_id` int(11) UNSIGNED NOT NULL default '0', `dest_id` int(11) NOT NULL default '0', `type` VARCHAR(50) NOT NULL DEFAULT '', @@ -129,10 +128,7 @@ public function createOrUpdateTable(DataObject\ClassDefinition $class) `ownername` VARCHAR(70) NOT NULL DEFAULT '', `position` VARCHAR(70) NOT NULL DEFAULT '0', `index` int(11) unsigned NOT NULL DEFAULT '0', - PRIMARY KEY (`id`), - UNIQUE KEY `metadata_un` ( - `o_id`, `dest_id`, `type`, `fieldname`, `column`, `ownertype`, `ownername`, `position`, `index` - ), + PRIMARY KEY (`o_id`, `dest_id`, `type`, `fieldname`, `column`, `ownertype`, `ownername`, `position`, `index`), INDEX `dest_id` (`dest_id`), INDEX `fieldname` (`fieldname`), INDEX `column` (`column`), @@ -140,8 +136,7 @@ public function createOrUpdateTable(DataObject\ClassDefinition $class) INDEX `ownername` (`ownername`), INDEX `position` (`position`), INDEX `index` (`index`), - CONSTRAINT `".self::getForeignKeyName($table, 'o_id').'` FOREIGN KEY (`o_id`) - REFERENCES objects (`o_id`) ON DELETE CASCADE + CONSTRAINT `".self::getForeignKeyName($table, 'o_id').'` FOREIGN KEY (`o_id`) REFERENCES objects (`o_id`) ON DELETE CASCADE ) DEFAULT CHARSET=utf8mb4;'); $this->handleEncryption($class, [$table]); From 6f7572490b2e2f589ee91c35f8e8fd95b9f29065 Mon Sep 17 00:00:00 2001 From: Fracsi Date: Fri, 21 Jul 2023 15:09:38 +0200 Subject: [PATCH 02/29] Fix `extended_valid_elements` order in TinyMCE config (#15584) --- bundles/TinymceBundle/public/js/editor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/TinymceBundle/public/js/editor.js b/bundles/TinymceBundle/public/js/editor.js index db5f409233c..a07b1b7c728 100644 --- a/bundles/TinymceBundle/public/js/editor.js +++ b/bundles/TinymceBundle/public/js/editor.js @@ -88,7 +88,7 @@ pimcore.bundle.tinymce.editor = Class.create({ base_url: '/bundles/pimcoretinymce/build/tinymce', suffix: '.min', convert_urls: false, - extended_valid_elements: 'a[name|href|target|title|pimcore_type|pimcore_id],img[style|longdesc|usemap|src|border|alt=|title|hspace|vspace|width|height|align|pimcore_type|pimcore_id]', + extended_valid_elements: 'a[name|href|target|title|pimcore_id|pimcore_type],img[style|longdesc|usemap|src|border|alt=|title|hspace|vspace|width|height|align|pimcore_id|pimcore_type]', init_instance_callback: function (editor) { editor.on('input', function (eChange) { const charCount = tinymce.activeEditor.plugins.wordcount.body.getCharacterCount(); From 882098ced2ba8be44c25df11ba601debca2ba395 Mon Sep 17 00:00:00 2001 From: Divesh Pahuja Date: Fri, 21 Jul 2023 14:58:08 +0200 Subject: [PATCH 03/29] Revert "Fix: o_id and id (#15543)" prtially This reverts commit a8201b546574093322a1444276da8f8db2632784 368d2c5512434158fedbf9929dbca77025287b30 --- .../DataObject/Data/AbstractMetadata/Dao.php | 13 ++--- .../DataObject/Data/ElementMetadata/Dao.php | 43 ++++----------- models/DataObject/Data/ObjectMetadata/Dao.php | 54 +++++-------------- 3 files changed, 26 insertions(+), 84 deletions(-) diff --git a/models/DataObject/Data/AbstractMetadata/Dao.php b/models/DataObject/Data/AbstractMetadata/Dao.php index 6996d9db830..af1e8dc0ad6 100644 --- a/models/DataObject/Data/AbstractMetadata/Dao.php +++ b/models/DataObject/Data/AbstractMetadata/Dao.php @@ -26,8 +26,6 @@ class Dao extends Model\Dao\AbstractDao { use DataObject\ClassDefinition\Helper\Dao; - const TYPE_QUERY = " AND (`type` = 'object' or `type` = '')"; - protected ?array $tableDefinitions = null; public function save(DataObject\Concrete $object, string $ownertype, string $ownername, string $position, int $index, string $type = 'object'): void @@ -51,8 +49,7 @@ public function createOrUpdateTable(DataObject\ClassDefinition $class): void $table = 'object_metadata_' . $classId; $this->db->executeQuery('CREATE TABLE IF NOT EXISTS `' . $table . "` ( - `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, - `o_id` int(11) UNSIGNED NOT NULL default '0', + `id` int(11) UNSIGNED NOT NULL default '0', `dest_id` int(11) NOT NULL default '0', `type` VARCHAR(50) NOT NULL DEFAULT '', `fieldname` varchar(71) NOT NULL, @@ -62,10 +59,7 @@ public function createOrUpdateTable(DataObject\ClassDefinition $class): void `ownername` VARCHAR(70) NOT NULL DEFAULT '', `position` VARCHAR(70) NOT NULL DEFAULT '0', `index` int(11) unsigned NOT NULL DEFAULT '0', - PRIMARY KEY (`id`), - UNIQUE KEY `metadata_un` ( - `o_id`, `dest_id`, `type`, `fieldname`, `column`, `ownertype`, `ownername`, `position`, `index` - ), + PRIMARY KEY (`id`, `dest_id`, `type`, `fieldname`, `column`, `ownertype`, `ownername`, `position`, `index`), INDEX `dest_id` (`dest_id`), INDEX `fieldname` (`fieldname`), INDEX `column` (`column`), @@ -73,8 +67,7 @@ public function createOrUpdateTable(DataObject\ClassDefinition $class): void INDEX `ownername` (`ownername`), INDEX `position` (`position`), INDEX `index` (`index`), - CONSTRAINT `".self::getForeignKeyName($table, 'o_id').'` FOREIGN KEY (`o_id`) - REFERENCES objects (`id`) ON DELETE CASCADE + CONSTRAINT `".self::getForeignKeyName($table, 'id').'` FOREIGN KEY (`id`) REFERENCES objects (`id`) ON DELETE CASCADE ) DEFAULT CHARSET=utf8mb4;'); $this->handleEncryption($class, [$table]); diff --git a/models/DataObject/Data/ElementMetadata/Dao.php b/models/DataObject/Data/ElementMetadata/Dao.php index 4ec04cb64a5..c34a088c40e 100644 --- a/models/DataObject/Data/ElementMetadata/Dao.php +++ b/models/DataObject/Data/ElementMetadata/Dao.php @@ -25,45 +25,29 @@ */ class Dao extends DataObject\Data\AbstractMetadata\Dao { - public function save( - DataObject\Concrete $object, - string $ownertype, - string $ownername, - string $position, - int $index, - string $type = 'object'): void + public function save(DataObject\Concrete $object, string $ownertype, string $ownername, string $position, int $index, string $type = 'object'): void { $table = $this->getTablename($object); - $dataTemplate = [ - 'o_id' => $object->getId(), + $dataTemplate = ['id' => $object->getId(), 'dest_id' => $this->model->getElement()->getId(), 'fieldname' => $this->model->getFieldname(), 'ownertype' => $ownertype, - 'ownername' => $ownername ?: '', - 'index' => $index ?: '0', - 'position' => $position ?: '0', - 'type' => $type ?: 'object', - ]; + 'ownername' => $ownername ? $ownername : '', + 'index' => $index ? $index : '0', + 'position' => $position ? $position : '0', + 'type' => $type ? $type : 'object', ]; foreach ($this->model->getColumns() as $column) { $getter = 'get' . ucfirst($column); $data = $dataTemplate; $data['column'] = $column; $data['data'] = $this->model->$getter(); - Helper::upsert($this->db, $table, $data, array_keys(array_diff_key($data, ['data' => null]))); + Helper::upsert($this->db, $table, $data, $this->getPrimaryKey($table)); } } - public function load( - DataObject\Concrete $source, - int $destinationId, - string $fieldname, - string $ownertype, - string $ownername, - string $position, - int $index, - string $destinationType = 'object'): ?DataObject\Data\ElementMetadata + public function load(DataObject\Concrete $source, int $destinationId, string $fieldname, string $ownertype, string $ownername, string $position, int $index, string $destinationType = 'object'): ?DataObject\Data\ElementMetadata { if ($destinationType == 'object') { $typeQuery = " AND (`type` = 'object' or `type` = '')"; @@ -72,16 +56,7 @@ public function load( } $dataRaw = $this->db->fetchAllAssociative('SELECT * FROM ' . - $this->getTablename($source) . - ' WHERE ' . $this->getTablename($source) .'.o_id = ? AND ' . - 'dest_id = ? AND ' . - 'fieldname = ? AND ' . - 'ownertype = ? AND ' . - 'ownername = ? AND ' . - 'position = ? AND ' . - '`index` = ? ' . $typeQuery, - [$source->getId(), $destinationId, $fieldname, $ownertype, $ownername, $position, $index]); - + $this->getTablename($source) . ' WHERE ' . $this->getTablename($source) .'.id = ? AND dest_id = ? AND fieldname = ? AND ownertype = ? AND ownername = ? and position = ? and `index` = ? ' . $typeQuery, [$source->getId(), $destinationId, $fieldname, $ownertype, $ownername, $position, $index]); if (!empty($dataRaw)) { $this->model->setElementTypeAndId($destinationType, $destinationId); $this->model->setFieldname($fieldname); diff --git a/models/DataObject/Data/ObjectMetadata/Dao.php b/models/DataObject/Data/ObjectMetadata/Dao.php index 5adebc3e2a5..3f509913ee6 100644 --- a/models/DataObject/Data/ObjectMetadata/Dao.php +++ b/models/DataObject/Data/ObjectMetadata/Dao.php @@ -29,33 +29,25 @@ class Dao extends DataObject\Data\AbstractMetadata\Dao protected ?array $tableDefinitions = null; - public function save( - DataObject\Concrete $object, - string $ownertype, - string $ownername, - string $position, - int $index, - string $type = 'object'): void + public function save(DataObject\Concrete $object, string $ownertype, string $ownername, string $position, int $index, string $type = 'object'): void { $table = $this->getTablename($object); - $dataTemplate = [ - 'o_id' => $object->getId(), + $dataTemplate = ['id' => $object->getId(), 'dest_id' => $this->model->getElement()->getId(), 'fieldname' => $this->model->getFieldname(), 'ownertype' => $ownertype, - 'ownername' => $ownername ?: '', - 'index' => $index ?: '0', - 'position' => $position ?: '0', - 'type' => $type ?: 'object', - ]; + 'ownername' => $ownername ? $ownername : '', + 'index' => $index ? $index : '0', + 'position' => $position ? $position : '0', + 'type' => $type ? $type : 'object', ]; foreach ($this->model->getColumns() as $column) { $getter = 'get' . ucfirst($column); $data = $dataTemplate; $data['column'] = $column; $data['data'] = $this->model->$getter(); - Helper::upsert($this->db, $table, $data, array_keys(array_diff_key($data, ['data' => null]))); + Helper::upsert($this->db, $table, $data, $this->getPrimaryKey($table)); } } @@ -64,30 +56,12 @@ protected function getTablename(DataObject\Concrete $object): string return 'object_metadata_' . $object->getClassId(); } - public function load( - DataObject\Concrete $source, - int $destinationId, - string $fieldname, - string $ownertype, - string $ownername, - string $position, - int $index, - string $destinationType = 'object'): ?DataObject\Data\ObjectMetadata + public function load(DataObject\Concrete $source, int $destinationId, string $fieldname, string $ownertype, string $ownername, string $position, int $index, string $destinationType = 'object'): ?DataObject\Data\ObjectMetadata { - $query = 'SELECT * FROM ' . $this->getTablename($source) . - ' WHERE o_id = ? AND ' . - 'dest_id = ? AND ' . - 'fieldname = ? AND' . - ' ownertype = ? AND ' . - 'ownername = ? AND ' . - 'position = ? AND ' . - '`index` = ? ' . self::TYPE_QUERY; - - $dataRaw = $this->db->fetchAllAssociative( - $query, - [$source->getId(), $destinationId, $fieldname, $ownertype, $ownername, $position, $index] - ); + $typeQuery = " AND (`type` = 'object' or `type` = '')"; + $query = 'SELECT * FROM ' . $this->getTablename($source) . ' WHERE id = ? AND dest_id = ? AND fieldname = ? AND ownertype = ? AND ownername = ? and position = ? and `index` = ? ' . $typeQuery; + $dataRaw = $this->db->fetchAllAssociative($query, [$source->getId(), $destinationId, $fieldname, $ownertype, $ownername, $position, $index]); if (!empty($dataRaw)) { $this->model->setObjectId($destinationId); $this->model->setFieldname($fieldname); @@ -111,7 +85,7 @@ public function createOrUpdateTable(DataObject\ClassDefinition $class): void $table = 'object_metadata_' . $classId; $this->db->executeQuery('CREATE TABLE IF NOT EXISTS `' . $table . "` ( - `o_id` int(11) UNSIGNED NOT NULL default '0', + `id` int(11) UNSIGNED NOT NULL default '0', `dest_id` int(11) NOT NULL default '0', `type` VARCHAR(50) NOT NULL DEFAULT '', `fieldname` varchar(71) NOT NULL, @@ -121,7 +95,7 @@ public function createOrUpdateTable(DataObject\ClassDefinition $class): void `ownername` VARCHAR(70) NOT NULL DEFAULT '', `position` VARCHAR(70) NOT NULL DEFAULT '0', `index` int(11) unsigned NOT NULL DEFAULT '0', - PRIMARY KEY (`o_id`, `dest_id`, `type`, `fieldname`, `column`, `ownertype`, `ownername`, `position`, `index`), + PRIMARY KEY (`id`, `dest_id`, `type`, `fieldname`, `column`, `ownertype`, `ownername`, `position`, `index`), INDEX `dest_id` (`dest_id`), INDEX `fieldname` (`fieldname`), INDEX `column` (`column`), @@ -129,7 +103,7 @@ public function createOrUpdateTable(DataObject\ClassDefinition $class): void INDEX `ownername` (`ownername`), INDEX `position` (`position`), INDEX `index` (`index`), - CONSTRAINT `".self::getForeignKeyName($table, 'o_id').'` FOREIGN KEY (`o_id`) REFERENCES objects (`o_id`) ON DELETE CASCADE + CONSTRAINT `".self::getForeignKeyName($table, 'id').'` FOREIGN KEY (`id`) REFERENCES objects (`id`) ON DELETE CASCADE ) DEFAULT CHARSET=utf8mb4;'); $this->handleEncryption($class, [$table]); From 5bf261cdbb00ff0251dbee156caf6937cbc33973 Mon Sep 17 00:00:00 2001 From: Sebastian Blank Date: Thu, 20 Jul 2023 15:54:36 +0200 Subject: [PATCH 04/29] [SeoBundle] Fix deprecation: Passing id as string to method Pimcore\Model\Document::getById is deprecated --- .../SeoBundle/src/Controller/Document/DocumentController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/SeoBundle/src/Controller/Document/DocumentController.php b/bundles/SeoBundle/src/Controller/Document/DocumentController.php index ce80380c37c..cc2e84edc90 100644 --- a/bundles/SeoBundle/src/Controller/Document/DocumentController.php +++ b/bundles/SeoBundle/src/Controller/Document/DocumentController.php @@ -98,7 +98,7 @@ public function seopanelTreeAction( // make sure document routes are also built for unpublished documents $documentRouteHandler->setForceHandleUnpublishedDocuments(true); - $document = Document::getById($allParams['node']); + $document = Document::getById((int) $allParams['node']); $documents = []; if ($document->hasChildren()) { From 30977f45c58913aa4f06453b69b7a5b3ad032c20 Mon Sep 17 00:00:00 2001 From: Fracsi Date: Mon, 24 Jul 2023 11:50:07 +0200 Subject: [PATCH 05/29] Allow images and relative media in HTMLSanitizer (#15585) --- bundles/CoreBundle/config/pimcore/default.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bundles/CoreBundle/config/pimcore/default.yaml b/bundles/CoreBundle/config/pimcore/default.yaml index 143b27b5443..5e65aaf326e 100644 --- a/bundles/CoreBundle/config/pimcore/default.yaml +++ b/bundles/CoreBundle/config/pimcore/default.yaml @@ -61,6 +61,7 @@ framework: pimcore_type: '*' pimcore_id: '*' allow_relative_links: true + allow_relative_medias: true allow_elements: span : [ 'class', 'style', 'id' ] div : [ 'class', 'style', 'id' ] @@ -79,6 +80,7 @@ framework: li: ['class', 'style', 'id'] ol: ['class', 'style', 'id'] br: '' + img: ['alt', 'style', 'src'] pimcore.translation_sanitizer: allow_attributes: pimcore_type: '*' From 89d0af7753f242378c22aab19ec00f678be3d780 Mon Sep 17 00:00:00 2001 From: JiaJia Ji Date: Tue, 25 Jul 2023 16:39:28 +0200 Subject: [PATCH 06/29] [Bug]: WYSIWYG editor crashing due `component.up` error (#15613) * fix container up error * remove from document wysiwyg --- .../js/pimcore/document/editables/wysiwyg.js | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/bundles/AdminBundle/Resources/public/js/pimcore/document/editables/wysiwyg.js b/bundles/AdminBundle/Resources/public/js/pimcore/document/editables/wysiwyg.js index 2f6cb4a11e4..87ee79efcee 100644 --- a/bundles/AdminBundle/Resources/public/js/pimcore/document/editables/wysiwyg.js +++ b/bundles/AdminBundle/Resources/public/js/pimcore/document/editables/wysiwyg.js @@ -107,23 +107,6 @@ pimcore.document.editables.wysiwyg = Class.create(pimcore.document.editable, { this.ckeditor = CKEDITOR.inline(this.textarea, eConfig); - let panelComponent = this.component.up('panel'); - let timeoutId; - do{ - if(panelComponent?.scrollable){ - panelComponent.body.dom.onscroll = function () { - if (typeof timeoutId === "number") { - clearTimeout(timeoutId); - } - - timeoutId = setTimeout(function(){ - CKEDITOR.document.getWindow().fire('scroll'); - }, 100); - } - } - panelComponent = panelComponent.up('panel'); - } while(panelComponent) - this.ckeditor.on('change', this.checkValue.bind(this, true)); // disable URL field in image dialog From 46377fa45c50783f77364eea12be3153167473dc Mon Sep 17 00:00:00 2001 From: Divesh Pahuja Date: Wed, 26 Jul 2023 17:12:35 +0200 Subject: [PATCH 07/29] [Translations] Consider only registered in available domains (#15624) * [Translations] Consider only registered in available domains - resolves #15623 * Add docs --- .../13_Bundle_Developers_Guide/README.md | 11 +++++++++++ models/Translation/Dao.php | 7 +++++-- models/Translation/Listing.php | 8 +++++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/doc/20_Extending_Pimcore/13_Bundle_Developers_Guide/README.md b/doc/20_Extending_Pimcore/13_Bundle_Developers_Guide/README.md index 0cd48b0a9d8..7cd89941054 100644 --- a/doc/20_Extending_Pimcore/13_Bundle_Developers_Guide/README.md +++ b/doc/20_Extending_Pimcore/13_Bundle_Developers_Guide/README.md @@ -56,6 +56,17 @@ For bundles, translations should be stored in the `Resources/translations/` dire Example: admin.en.yml or messages.en.yml +#### Translations Domain +A translation domain is only considered valid when it is registered as follows: +```yaml +pimcore: + translations: + domains: + - site_1 + - site_2 +```` + +Then only translations stored in a dedicated domain table e.g. `translations_DOMAIN` are used by the Pimcore translation service. ### Security / Authentication diff --git a/models/Translation/Dao.php b/models/Translation/Dao.php index 93a73934af0..27f82dfd4a4 100644 --- a/models/Translation/Dao.php +++ b/models/Translation/Dao.php @@ -143,7 +143,7 @@ public function getAvailableLanguages(): array } /** - * Returns a array containing all available domains + * Returns a array containing all available (registered) domains * * @return array */ @@ -153,7 +153,10 @@ public function getAvailableDomains(): array $domains = []; foreach ($domainTables as $domainTable) { - $domains[] = str_replace('translations_', '', $domainTable[array_key_first($domainTable)]); + $domain = str_replace('translations_', '', $domainTable[array_key_first($domainTable)]); + if ($this->isAValidDomain($domain)) { + $domains[] = $domain; + } } return $domains; diff --git a/models/Translation/Listing.php b/models/Translation/Listing.php index 2bcc8afb0d3..eee52df9fb5 100644 --- a/models/Translation/Listing.php +++ b/models/Translation/Listing.php @@ -68,7 +68,13 @@ public function getDomain(): string public function setDomain(string $domain): void { if (!Model\Translation::isAValidDomain($domain)) { - throw new NotFoundException(sprintf('Translation domain table "translations_%s" does not exist', $domain)); + throw new NotFoundException( + sprintf( + 'Either translation domain %s is not registered in config `pimcore.translations.domains` or table "%s" does not exist', + 'translations_' . $domain, + $domain + ) + ); } $this->domain = $domain; From 67b064e83c439b72d9d90bbff22ebc82cd56afc2 Mon Sep 17 00:00:00 2001 From: Melissa Kittl Date: Thu, 27 Jul 2023 10:41:18 +0200 Subject: [PATCH 08/29] [Bug]: regex for image thumbnails media queries high resolution (#15594) * fix(img-thumbnails): regex for image thumbnails media queries did not match with new media query generation ~-~media--cac93fe4--query@2x.226c46a.webp * Update models/Asset/Service.php Regex for high resolution thumbnail generation Co-authored-by: Bernhard Rusch --------- Co-authored-by: Bernhard Rusch --- models/Asset/Service.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/Asset/Service.php b/models/Asset/Service.php index 1a2d8ec8bcc..c053090ba67 100644 --- a/models/Asset/Service.php +++ b/models/Asset/Service.php @@ -597,7 +597,7 @@ public static function getImageThumbnailByArrayConfig(array $config): null|Image //check if high res image is called - preg_match("@([^\@]+)(\@[0-9.]+x)?\.([a-zA-Z]{2,5})@", $config['filename'], $matches); + preg_match("@([^\@]+)(\@[0-9.]+x)?\.([^\.]+)\.([a-zA-Z]{2,5})@", $config['filename'], $matches); if (empty($matches) || !isset($matches[1])) { return null; From babc2cf561ac2f44884effab33887811814a669a Mon Sep 17 00:00:00 2001 From: JiaJia Ji Date: Thu, 27 Jul 2023 14:19:20 +0200 Subject: [PATCH 09/29] [Docs]: Improve explanation about recycle bin (#15633) --- .../22_Administration_of_Pimcore/02_Cleanup_Data_Storage.md | 3 +++ .../22_Administration_of_Pimcore/07_Users_and_Roles.md | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/doc/Development_Documentation/22_Administration_of_Pimcore/02_Cleanup_Data_Storage.md b/doc/Development_Documentation/22_Administration_of_Pimcore/02_Cleanup_Data_Storage.md index 42659798562..be9f7dca3d9 100644 --- a/doc/Development_Documentation/22_Administration_of_Pimcore/02_Cleanup_Data_Storage.md +++ b/doc/Development_Documentation/22_Administration_of_Pimcore/02_Cleanup_Data_Storage.md @@ -71,3 +71,6 @@ amount of items in your recycle bin: mysql -e "TRUNCATE TABLE ###.recyclebin;" rm -r var/recyclebin ``` + +**WARNING: The recycle bin is an administrative tool that displays any user's deleted elements. +Due to the nature and complexity of the elements deletion and restoration process, this tool should be reserved for administrator and advanced users** \ No newline at end of file diff --git a/doc/Development_Documentation/22_Administration_of_Pimcore/07_Users_and_Roles.md b/doc/Development_Documentation/22_Administration_of_Pimcore/07_Users_and_Roles.md index ab3d9ddfe7e..b9099d0a511 100644 --- a/doc/Development_Documentation/22_Administration_of_Pimcore/07_Users_and_Roles.md +++ b/doc/Development_Documentation/22_Administration_of_Pimcore/07_Users_and_Roles.md @@ -2,7 +2,7 @@ ## General -User permissions in Pimcore are based on a users and roles concept. Each user can have serveral roles and both - users +User permissions in Pimcore are based on a users and roles concept. Each user can have several roles and both - users and roles - can have permissions. Users and roles are configured in Pimcore backend UI at *Settings* > *Users & Roles* > *Users* and @@ -42,7 +42,7 @@ The following list outlines what the different system permissions (available for * **Notes & Events**: Notes & Events are visible * **Objects**: objects tree is visible * **Predefined Properties**: User can create and modify predefined properties -* **Recycle Bin**: User has access to recycle bin +* **Recycle Bin**: User has access to the recycle bin and see all the deleted elements (even by other users). * **Redirects**: User can create and modify redirects * **Reports**: User has access to reports module * **Seemode**: Seemode available/not available for user From c485cbe3007aa8e67ef8f882be80cf1311aa45fb Mon Sep 17 00:00:00 2001 From: Sebastian Blank Date: Thu, 27 Jul 2023 17:14:36 +0200 Subject: [PATCH 10/29] Fix: readDimensionsFromFile() doesn't return dimensions (#15630) undefined --- models/Asset/Folder.php | 8 ++++++- .../Asset/Thumbnail/ImageThumbnailTrait.php | 21 ++++++++++++------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/models/Asset/Folder.php b/models/Asset/Folder.php index e03c9bbbfb7..9febf1efc83 100644 --- a/models/Asset/Folder.php +++ b/models/Asset/Folder.php @@ -189,8 +189,14 @@ public function getPreviewImage(bool $force = false) break; } + $width = $tileThumb->getWidth(); + $height = $tileThumb->getHeight(); + if (!$width || !$height) { + break; + } + $tile = imagecreatefromstring(stream_get_contents($stream)); - imagecopyresampled($collage, $tile, $offsetLeft, $offsetTop, 0, 0, $squareDimension, $squareDimension, $tileThumb->getWidth(), $tileThumb->getHeight()); + imagecopyresampled($collage, $tile, $offsetLeft, $offsetTop, 0, 0, $squareDimension, $squareDimension, $width, $height); $count++; if ($count % $colums === 0) { diff --git a/models/Asset/Thumbnail/ImageThumbnailTrait.php b/models/Asset/Thumbnail/ImageThumbnailTrait.php index b90dafa9bdf..49ea5d0c932 100644 --- a/models/Asset/Thumbnail/ImageThumbnailTrait.php +++ b/models/Asset/Thumbnail/ImageThumbnailTrait.php @@ -133,7 +133,7 @@ public function reset() } /** - * @return int + * @return int|null */ public function getWidth() { @@ -145,7 +145,7 @@ public function getWidth() } /** - * @return int + * @return int|null */ public function getHeight() { @@ -157,7 +157,7 @@ public function getHeight() } /** - * @return int + * @return int|null */ public function getRealWidth() { @@ -169,7 +169,7 @@ public function getRealWidth() } /** - * @return int + * @return int|null */ public function getRealHeight() { @@ -193,11 +193,18 @@ public function readDimensionsFromFile(): array try { $localFile = $this->getLocalFile(); if (null !== $localFile && isset($pathReference['storagePath']) && $config = $this->getConfig()) { - $this->getAsset()->addThumbnailFileToCache( + $asset = $this->getAsset(); + $filename = basename($pathReference['storagePath']); + $asset->addThumbnailFileToCache( $localFile, - basename($pathReference['storagePath']), + $filename, $config ); + $thumbnail = $asset->getDao()->getCachedThumbnail($config->getName(), $filename); + if (isset($thumbnail['width'], $thumbnail['height'])) { + $dimensions['width'] = $thumbnail['width']; + $dimensions['height'] = $thumbnail['height']; + } } } catch (\Exception $e) { // noting to do @@ -208,7 +215,7 @@ public function readDimensionsFromFile(): array } /** - * @return array + * @return array{width: ?int, height: ?int} */ public function getDimensions() { From 0d55997f857aa308f3073fe97c87f60745ade337 Mon Sep 17 00:00:00 2001 From: Manon Cassier <127942915+mcassier31@users.noreply.github.com> Date: Mon, 31 Jul 2023 08:15:40 +0200 Subject: [PATCH 11/29] [Docs] Details behavior for workspace permissions (#15606) * details permissions behavior for workspaces * Update doc/22_Administration_of_Pimcore/07_Users_and_Roles.md Co-authored-by: Christian Fasching * Update doc/22_Administration_of_Pimcore/07_Users_and_Roles.md Co-authored-by: Matthias Schuhmayer <38959016+mattamon@users.noreply.github.com> * Update 07_Users_and_Roles.md * adapt wording --------- Co-authored-by: Christian Fasching Co-authored-by: Matthias Schuhmayer <38959016+mattamon@users.noreply.github.com> --- .../07_Users_and_Roles.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/doc/22_Administration_of_Pimcore/07_Users_and_Roles.md b/doc/22_Administration_of_Pimcore/07_Users_and_Roles.md index 6e346372965..547d4451eab 100644 --- a/doc/22_Administration_of_Pimcore/07_Users_and_Roles.md +++ b/doc/22_Administration_of_Pimcore/07_Users_and_Roles.md @@ -21,7 +21,7 @@ In Pimcore, there are two levels of user permissions: 1) Permissions on system components, 2) Permissions on data elements (Assets, Data Objects, and Documents). -Permissions can be granted to roles or individual users. +Permissions can be granted to individual users or groups of users (called "roles" in Pimcore). The following paragraphs describe how and where permissions can be set and how they will or will not affect each other. It is not mandatory to use roles, permissions can be granted to users directly. However, it is advised to use roles if there is a larger user group to manage. In the `Users/Roles` settings tab it can be decided which permissions are granted to that user or role. An individual user has a few more general settings than the role. @@ -95,7 +95,15 @@ The user permissions on element basis are summed up as follows: Individual users are granted access to all defined workspaces for any role they incorporate. In addition to that, users can have their own workspaces. These are added to the permissions granted by roles. For example, a role `myRole` has been granted list and view access to `/home/myPath`. The user editor incorporates the role `myRole` and thereby inherits all workspace settings from the role. -In case the editor has his own workspace settings on `/home/myPath`, these permissions are added to permissions from any role they incorporate. A permission granted by any role can not be rescinded for a single user. +In case the editor has his own workspace settings on `/home/myPath`, these permissions are added to permissions from any role they incorporate. + +:::caution + +Be aware that if individual permissions granted to a user are different from the ones granted by its role for the same workspace, user permissions win over the ones attributed to the role. This implies that if the user has fewer permissions granted as an individual than through its role, the role's permissions will also be overruled. + +For example, a user has only `List` permissions on a workspace, but its role defines `List`, `View`, `Save`, and `Publish` permissions for the same workspace. Because its individual permissions are restricted, this user will only have the possibility to see the workspace tree allowed by its `List` permissions. + +::: It is also possible to restrict access to localized fields on a language level. By default, users can view and edit (as long as they also have sufficient object permissions) all localized fields. This can now be restricted to a subset of languages. From 93bc1d9dce832df24e5bc13bf5a40f4b01669f24 Mon Sep 17 00:00:00 2001 From: Manon Cassier <127942915+mcassier31@users.noreply.github.com> Date: Mon, 31 Jul 2023 08:21:14 +0200 Subject: [PATCH 12/29] [Docs] Moves symfony messenger section (#15637) * Moves Symfony Messenger Section * update readme * update links * update link * update links * update link * update links * update link * update links --- .../00_Docker_Based_Installation.md | 2 +- .../00_Installation/01_Webserver_Installation.md | 8 ++++---- .../01_Symfony_Messenger.md | 0 .../README.md} | 14 +++++++------- doc/01_Getting_Started/README.md | 12 ++++++------ 5 files changed, 18 insertions(+), 18 deletions(-) rename doc/01_Getting_Started/{ => 02_Advanced_Installation_Topics}/01_Symfony_Messenger.md (100%) rename doc/01_Getting_Started/{02_Advanced_Installation_Topics.md => 02_Advanced_Installation_Topics/README.md} (83%) diff --git a/doc/01_Getting_Started/00_Installation/00_Docker_Based_Installation.md b/doc/01_Getting_Started/00_Installation/00_Docker_Based_Installation.md index f217fcd3a08..58453d0fba4 100644 --- a/doc/01_Getting_Started/00_Installation/00_Docker_Based_Installation.md +++ b/doc/01_Getting_Started/00_Installation/00_Docker_Based_Installation.md @@ -57,4 +57,4 @@ If you would like to know more about the installation process or if you are havi ## Automating the Installation Process -For more information about ways to automate the installation process, have a look on [Advanced Installation Topics](../02_Advanced_Installation_Topics.md). +For more information about ways to automate the installation process, have a look on [Advanced Installation Topics](../02_Advanced_Installation_Topics/README.md). diff --git a/doc/01_Getting_Started/00_Installation/01_Webserver_Installation.md b/doc/01_Getting_Started/00_Installation/01_Webserver_Installation.md index 9c9419c2854..d24c5d2c8ff 100644 --- a/doc/01_Getting_Started/00_Installation/01_Webserver_Installation.md +++ b/doc/01_Getting_Started/00_Installation/01_Webserver_Installation.md @@ -65,7 +65,7 @@ This launches the interactive installer with a few questions. Make sure that you :::info - Pimcore allows a fully automated installation process. Read more here: [Advanced Installation Topics](../02_Advanced_Installation_Topics.md) + Pimcore allows a fully automated installation process. Read more here: [Advanced Installation Topics](../02_Advanced_Installation_Topics/README.md) ::: @@ -98,7 +98,7 @@ messages to the bus and run them afterward immediately from the queue. However, Keep in mind that the cron job has to run as the same user as the web interface to avoid permission issues (eg. `www-data`). -For information about how to handle failed jobs, see this [section](../01_Symfony_Messenger.md). +For information about how to handle failed jobs, see this [section](../02_Advanced_Installation_Topics/01_Symfony_Messenger.md). ## Caching Make sure to use any sort of [caching](https://pimcore.com/docs/platform/Pimcore/Development_Tools_and_Details/Cache/) to improve performance. We recommend Redis cache storage. @@ -109,8 +109,8 @@ If you would like to know more about the installation process or if you are havi ## 7. Further Reading -- [Symfony Messenger](../01_Symfony_Messenger.md) -- [Advanced Installation Topics](../02_Advanced_Installation_Topics.md) +- [Symfony Messenger](../02_Advanced_Installation_Topics/01_Symfony_Messenger.md) +- [Advanced Installation Topics](../02_Advanced_Installation_Topics/README.md) - [Apache Configuration](../../23_Installation_and_Upgrade/03_System_Setup_and_Hosting/01_Apache_Configuration.md) - [Nginx Configuration](../../23_Installation_and_Upgrade/03_System_Setup_and_Hosting/02_Nginx_Configuration.md) - [Database Setup](../../23_Installation_and_Upgrade/03_System_Setup_and_Hosting/05_DB_Setup.md) diff --git a/doc/01_Getting_Started/01_Symfony_Messenger.md b/doc/01_Getting_Started/02_Advanced_Installation_Topics/01_Symfony_Messenger.md similarity index 100% rename from doc/01_Getting_Started/01_Symfony_Messenger.md rename to doc/01_Getting_Started/02_Advanced_Installation_Topics/01_Symfony_Messenger.md diff --git a/doc/01_Getting_Started/02_Advanced_Installation_Topics.md b/doc/01_Getting_Started/02_Advanced_Installation_Topics/README.md similarity index 83% rename from doc/01_Getting_Started/02_Advanced_Installation_Topics.md rename to doc/01_Getting_Started/02_Advanced_Installation_Topics/README.md index 01facbb8763..e9198204ac2 100644 --- a/doc/01_Getting_Started/02_Advanced_Installation_Topics.md +++ b/doc/01_Getting_Started/02_Advanced_Installation_Topics/README.md @@ -51,14 +51,14 @@ Attention: The bundles will be added to `config/bundles.php` automatically. Available bundles for installation: -- [PimcoreApplicationLoggerBundle](../18_Tools_and_Features/17_Application_Logger.md) -- [PimcoreCustomReportsBundle](../18_Tools_and_Features/29_Custom_Reports.md) -- [PimcoreGlossaryBundle](../18_Tools_and_Features/21_Glossary.md) -- PimcoreSeoBundle (for SEO-related topics: [Robots.txt](../18_Tools_and_Features/38_Robots.txt.md), [Sitemaps](../18_Tools_and_Features/39_Sitemaps.md) and [Redirects](../02_MVC/04_Routing_and_URLs/04_Redirects.md)) +- [PimcoreApplicationLoggerBundle](../../18_Tools_and_Features/17_Application_Logger.md) +- [PimcoreCustomReportsBundle](../../18_Tools_and_Features/29_Custom_Reports.md) +- [PimcoreGlossaryBundle](../../18_Tools_and_Features/21_Glossary.md) +- PimcoreSeoBundle (for SEO-related topics: [Robots.txt](../../18_Tools_and_Features/38_Robots.txt.md), [Sitemaps](../../18_Tools_and_Features/39_Sitemaps.md) and [Redirects](../../02_MVC/04_Routing_and_URLs/04_Redirects.md)) - PimcoreSimpleBackendSearchBundle (for default search functionality in Backend UI interface) -- [PimcoreStaticRoutesBundle](../02_MVC/04_Routing_and_URLs/02_Custom_Routes.md) +- [PimcoreStaticRoutesBundle](../../02_MVC/04_Routing_and_URLs/02_Custom_Routes.md) - [PimcoreTinymceBundle](https://github.com/pimcore/pimcore/blob/11.x/bundles/TinymceBundle/README.md) (for default WYSIWYG editor) -- [PimcoreUuidBundle](../19_Development_Tools_and_Details/19_UUID_Support.md) +- [PimcoreUuidBundle](../../19_Development_Tools_and_Details/19_UUID_Support.md) - PimcoreWordExportBundle (for import/export functionality for translations in Word format) - PimcoreXliffBundle (for import/export functionality for translations in Xliff format) @@ -156,4 +156,4 @@ pimcore: ## Office document preview -The feature for displaying a [preview of documents](../04_Assets/03_Working_with_Thumbnails/05_Document_Thumbnails.md) directly in Pimcore is optional. To use it, you must install either [Gotenberg](../23_Installation_and_Upgrade/03_System_Setup_and_Hosting/06_Additional_Tools_Installation.md#gotenberg) or [LibreOffice](../23_Installation_and_Upgrade/03_System_Setup_and_Hosting/06_Additional_Tools_Installation.md#libreoffice-pdftotext-inkscape-) according to your preference. +The feature for displaying a [preview of documents](../../04_Assets/03_Working_with_Thumbnails/05_Document_Thumbnails.md) directly in Pimcore is optional. To use it, you must install either [Gotenberg](../../23_Installation_and_Upgrade/03_System_Setup_and_Hosting/06_Additional_Tools_Installation.md#gotenberg) or [LibreOffice](../../23_Installation_and_Upgrade/03_System_Setup_and_Hosting/06_Additional_Tools_Installation.md#libreoffice-pdftotext-inkscape-) according to your preference. diff --git a/doc/01_Getting_Started/README.md b/doc/01_Getting_Started/README.md index 6cb9d368739..b44c3da24d6 100644 --- a/doc/01_Getting_Started/README.md +++ b/doc/01_Getting_Started/README.md @@ -5,10 +5,10 @@ This section provides a quick getting started tutorial for Pimcore and covers th 1. Installation of Pimcore: - via [Docker](../01_Getting_Started/00_Installation/00_Docker_Based_Installation.md) - via [Webserver](../01_Getting_Started/00_Installation/01_Webserver_Installation.md) -2. [Symfony Messenger and How to Handle Failed Jobs](./01_Symfony_Messenger.md) -3. [Advanced Installation Topics](./02_Advanced_Installation_Topics.md) -4. [Directory Structure of Pimcore](./03_Directory_Structure.md) -5. [Configuration](./04_Configuration.md) -6. [Architecture Overview](./05_Architecture_Overview.md) -7. [Creating your First Project](./06_Create_a_First_Project.md) +2. [Advanced Installation Topics](./02_Advanced_Installation_Topics/README.md): + - [Symfony Messenger and How to Handle Failed Jobs](./02_Advanced_Installation_Topics/01_Symfony_Messenger.md) +3. [Directory Structure of Pimcore](./03_Directory_Structure.md) +4. [Configuration](./04_Configuration.md) +5. [Architecture Overview](./05_Architecture_Overview.md) +6. [Creating your First Project](./06_Create_a_First_Project.md) From bc3373464a6690a77d2d19b189a4e38ea86e7432 Mon Sep 17 00:00:00 2001 From: aryaantony92 Date: Mon, 31 Jul 2023 13:31:35 +0200 Subject: [PATCH 13/29] Remove unrelated file --- .../js/pimcore/document/editables/wysiwyg.js | 324 ------------------ 1 file changed, 324 deletions(-) delete mode 100644 bundles/AdminBundle/Resources/public/js/pimcore/document/editables/wysiwyg.js diff --git a/bundles/AdminBundle/Resources/public/js/pimcore/document/editables/wysiwyg.js b/bundles/AdminBundle/Resources/public/js/pimcore/document/editables/wysiwyg.js deleted file mode 100644 index 87ee79efcee..00000000000 --- a/bundles/AdminBundle/Resources/public/js/pimcore/document/editables/wysiwyg.js +++ /dev/null @@ -1,324 +0,0 @@ -/** - * Pimcore - * - * This source file is available under two different licenses: - * - GNU General Public License version 3 (GPLv3) - * - Pimcore Commercial License (PCL) - * Full copyright and license information is available in - * LICENSE.md which is distributed with this source code. - * - * @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org) - * @license http://www.pimcore.org/license GPLv3 and PCL - */ - -/*global CKEDITOR*/ -pimcore.registerNS("pimcore.document.editables.wysiwyg"); -pimcore.document.editables.wysiwyg = Class.create(pimcore.document.editable, { - - type: "wysiwyg", - - initialize: function($super, id, name, config, data, inherited) { - $super(id, name, config, data, inherited); - - if (!data) { - data = ""; - } - this.data = data ?? ""; - - if (config["required"]) { - this.required = config["required"]; - } - }, - - render: function () { - this.setupWrapper(); - - this.textarea = document.createElement("div"); - this.textarea.setAttribute("contenteditable","true"); - - Ext.get(this.id).appendChild(this.textarea); - Ext.get(this.id).insertHtml("beforeEnd",'
'); - - this.textarea.id = this.id + "_textarea"; - this.textarea.innerHTML = this.data; - - let textareaHeight = 100; - if (this.config.height) { - textareaHeight = this.config.height; - } - if (this.config.placeholder) { - this.textarea.setAttribute('data-placeholder', this.config["placeholder"]); - } - - let inactiveContainerWidth = this.config.width + "px"; - if (typeof this.config.width == "string" && this.config.width.indexOf("%") >= 0) { - inactiveContainerWidth = this.config.width; - } - - Ext.get(this.textarea).addCls("pimcore_wysiwyg"); - Ext.get(this.textarea).applyStyles("width: " + inactiveContainerWidth + "; min-height: " + textareaHeight - + "px;"); - - // register at global DnD manager - if (typeof dndManager !== 'undefined') { - dndManager.addDropTarget(Ext.get(this.id), this.onNodeOver.bind(this), this.onNodeDrop.bind(this)); - } - - this.startCKeditor(); - this.checkValue(); - }, - - startCKeditor: function () { - - try { - CKEDITOR.config.language = pimcore.globalmanager.get("user").language; - var eConfig = {}; - var specificConfig = Object.assign({}, this.config); - - // if there is no toolbar defined use Full which is defined in CKEDITOR.config.toolbar_Full, possible - // is also Basic - if(!this.config["toolbarGroups"] && this.config['toolbarGroups'] !== false){ - eConfig.toolbarGroups = [ - { name: 'basicstyles', groups: [ 'undo', "find", 'basicstyles', 'list'] }, - '/', - { name: 'paragraph', groups: [ 'align', 'indent'] }, - { name: 'blocks' }, - { name: 'links' }, - { name: 'insert' }, - "/", - { name: 'styles' }, - { name: 'tools', groups: ['colors', "tools", 'cleanup', 'mode', "others"] } - ]; - } - - delete specificConfig.width; - - eConfig.language = pimcore.settings["language"]; - eConfig.entities = false; - eConfig.entities_greek = false; - eConfig.entities_latin = false; - eConfig.extraAllowedContent = "*[pimcore_type,pimcore_id]"; - - if(typeof(pimcore.document.editables.wysiwyg.defaultEditorConfig) == 'object'){ - eConfig = mergeObject(eConfig, pimcore.document.editables.wysiwyg.defaultEditorConfig); - } - - eConfig = mergeObject(eConfig, specificConfig); - - this.ckeditor = CKEDITOR.inline(this.textarea, eConfig); - - this.ckeditor.on('change', this.checkValue.bind(this, true)); - - // disable URL field in image dialog - this.ckeditor.on("dialogShow", function (e) { - var urlField = e.data.getElement().findOne("input"); - if(urlField && urlField.getValue()) { - if(urlField.getValue().indexOf("/image-thumbnails/") > 1) { - urlField.getParent().getParent().getParent().hide(); - } - } else if (urlField) { - urlField.getParent().getParent().getParent().show(); - } - }); - - // force paste dialog to prevent security message on various browsers - this.ckeditor.on('beforeCommandExec', function(event) { - if (event.data.name === 'paste') { - event.editor._.forcePasteDialog = true; - } - - if (event.data.name === 'pastetext' && event.data.commandData.from === 'keystrokeHandler') { - event.cancel(); - } - }); - - this.ckeditorReady = false; - this.ckeditor.on("instanceReady", function() { - this.ckeditorReady = true; - }.bind(this)); - } - catch (e) { - console.log(e); - } - }, - - onNodeDrop: function (target, dd, e, data) { - - if(!pimcore.helpers.dragAndDropValidateSingleItem(data)) { - return false; - } - - var record = data.records[0]; - data = record.data; - - if (!this.ckeditor || !this.dndAllowed(data) || this.inherited) { - return; - } - - // we have to foxus the editor otherwise an error is thrown in the case the editor wasn't opend before a drop element - this.ckeditor.focus(); - - var wrappedText = data.text; - var textIsSelected = false; - - try { - var selection = this.ckeditor.getSelection(); - var bookmarks = selection.createBookmarks(); - var range = selection.getRanges()[ 0 ]; - var fragment = range.clone().cloneContents(); - - selection.selectBookmarks(bookmarks); - var retval = ""; - var childList = fragment.getChildren(); - var childCount = childList.count(); - - for (var i = 0; i < childCount; i++) { - var child = childList.getItem(i); - retval += ( child.getOuterHtml ? - child.getOuterHtml() : child.getText() ); - } - - if (retval.length > 0) { - wrappedText = retval; - textIsSelected = true; - } - } - catch (e2) { - } - - // remove existing links out of the wrapped text - wrappedText = wrappedText.replace(/<\/?([a-z][a-z0-9]*)\b[^>]*>/gi, function ($0, $1) { - if($1.toLowerCase() == "a") { - return ""; - } - return $0; - }); - - var insertEl = null; - var id = data.id; - var uri = data.path; - var browserPossibleExtensions = ["jpg","jpeg","gif","png"]; - - if (data.elementType == "asset") { - if (data.type == "image" && textIsSelected == false) { - // images bigger than 600px or formats which cannot be displayed by the browser directly will be - // converted by the pimcore thumbnailing service so that they can be displayed in the editor - var defaultWidth = 600; - var additionalAttributes = ""; - - if(typeof data.imageWidth != "undefined") { - var route = 'pimcore_admin_asset_getimagethumbnail'; - var params = { - id: id, - width: defaultWidth, - aspectratio: true - }; - - uri = Routing.generate(route, params); - - if(data.imageWidth < defaultWidth - && in_arrayi(pimcore.helpers.getFileExtension(data.text), - browserPossibleExtensions)) { - uri = data.path; - additionalAttributes += ' pimcore_disable_thumbnail="true"'; - } - - if(data.imageWidth < defaultWidth) { - defaultWidth = data.imageWidth; - } - - additionalAttributes += ' style="width:' + defaultWidth + 'px;"'; - } - - insertEl = CKEDITOR.dom.element.createFromHtml(''); - this.ckeditor.insertElement(insertEl); - return true; - } - else { - insertEl = CKEDITOR.dom.element.createFromHtml('' + wrappedText + ''); - this.ckeditor.insertElement(insertEl); - return true; - } - } - - if (data.elementType == "document" && (data.type=="page" - || data.type=="hardlink" || data.type=="link")){ - insertEl = CKEDITOR.dom.element.createFromHtml('' + wrappedText + ''); - this.ckeditor.insertElement(insertEl); - return true; - } - - if (data.elementType == "object"){ - insertEl = CKEDITOR.dom.element.createFromHtml('' + wrappedText + ''); - this.ckeditor.insertElement(insertEl); - return true; - } - - }, - - checkValue: function (mark) { - var value = this.getValue(); - var textarea = Ext.get(this.textarea); - - // Sync DOM class names with ExtJs (CKEditor may have added classes in the meantime) - textarea.setCls(textarea.dom.className); - - if (trim(strip_tags(value)).length < 1) { - textarea.addCls("empty"); - } else { - textarea.removeCls("empty"); - } - - if (this.required) { - this.validateRequiredValue(value, textarea, this, mark); - } - }, - - onNodeOver: function(target, dd, e, data) { - if (data.records.length === 1 && this.dndAllowed(data.records[0].data) && !this.inherited) { - return Ext.dd.DropZone.prototype.dropAllowed; - } - else { - return Ext.dd.DropZone.prototype.dropNotAllowed; - } - }, - - - dndAllowed: function(data) { - - if (data.elementType == "document" && (data.type=="page" - || data.type=="hardlink" || data.type=="link")){ - return true; - } else if (data.elementType=="asset" && data.type != "folder"){ - return true; - } else if (data.elementType=="object" && data.type != "folder"){ - return true; - } - - return false; - }, - - - getValue: function () { - - var value = this.data; - - if (this.ckeditorReady && this.ckeditor) { - value = this.ckeditor.getData(); - } - - this.data = value; - - return value; - }, - - getType: function () { - return "wysiwyg"; - } -}); - -CKEDITOR.disableAutoInline = true; From 3cc2f9429e1d3046cddd784effd28b0e62440c6e Mon Sep 17 00:00:00 2001 From: aryaantony92 Date: Mon, 31 Jul 2023 13:33:28 +0200 Subject: [PATCH 14/29] Remove unrelated file --- .../07_Users_and_Roles.md | 116 ------------------ 1 file changed, 116 deletions(-) delete mode 100644 doc/Development_Documentation/22_Administration_of_Pimcore/07_Users_and_Roles.md diff --git a/doc/Development_Documentation/22_Administration_of_Pimcore/07_Users_and_Roles.md b/doc/Development_Documentation/22_Administration_of_Pimcore/07_Users_and_Roles.md deleted file mode 100644 index b9099d0a511..00000000000 --- a/doc/Development_Documentation/22_Administration_of_Pimcore/07_Users_and_Roles.md +++ /dev/null @@ -1,116 +0,0 @@ -# Users and Roles - -## General - -User permissions in Pimcore are based on a users and roles concept. Each user can have several roles and both - users - and roles - can have permissions. - -Users and roles are configured in Pimcore backend UI at *Settings* > *Users & Roles* > *Users* and - *Settings* > *Users & Roles* > *Roles*. - - -## Permissions -In Pimcore there are two levels of user permissions. Firstly, the permissions on system components and secondly -permissions on data elements (assets, objects and documents). Permissions can be granted to roles or individual users. -The following paragraphs describe how and where permissions can be set and how they will or will not affect each other. - -It is not mandatory to use roles, permissions can be granted to users directly. However, it is advised to use roles if -there is a larger user group to manage. In the user/role settings tab it can be decided which permissions are granted -to that user or role. An individual user has a few more general settings than the role. - -### System Permissions - -* Admin - if checked, all permissions on all system components are granted to that user -* Show welcome screen on startup -* Show close warning -* Roles - Select all roles incorporated by the user -* Perspectives - Which [perspectives](../18_Tools_and_Features/13_Perspectives.md) are available for this user - -The following list outlines what the different system permissions (available for users and roles) mean: - -* **Assets**: assets tree is visible -* **Classes**: Object classes editor visible (user can create and modify object classes) -* **Clear Cache**: defines if a user may clear Pimcore cache (internal cache and response cache if configured) -* **Clear Temporary Files**: defines if user may delete temporary system files (e.g thumbnails) -* **Dashboards**: User can create Dashboards -* **Documents**: documents tree is visible -* **Document Types**: User can create and modify predefined document types -* **Emails**: User sees E-Mail history -* **Extensions**: specifies if a user is allowed to download install and manage extension -* **Glossary**: Glossary entries visible -* **HTTP Errors**: HTTP Errors are visible -* **Notes & Events**: Notes & Events are visible -* **Objects**: objects tree is visible -* **Predefined Properties**: User can create and modify predefined properties -* **Recycle Bin**: User has access to the recycle bin and see all the deleted elements (even by other users). -* **Redirects**: User can create and modify redirects -* **Reports**: User has access to reports module -* **Seemode**: Seemode available/not available for user -* **SEO Document Editor**: User has access to SEO document editor -* **System Settings**: User has access to system settings -* **Tag & Snippet Management**: User can create and modify entries in tag & snippet management -* **Targeting**: User has access to targeting module -* **Thumbnails** User can create and modify thumbnail configurations -* **Translations**: defines whether a user may view and edit website translations -* **Users**: defines whether a user may manage other users' settings and system permissions -* **Website Settings**: User can create and modify website settings - -A user will be granted any system permission that is granted to them directly or to any role they incorporate. -A permission granted to a role incorporated by an individual user, can not be rescinded for that individual user. So it -does not matter if the checkbox in the user's individual permissions settings is unchecked once a permission is granted -through a role. - -### Element Permissions - Workspaces - -Beyond the permissions mentioned above, a user's access can be restricted on element basis. This can be done by defining -workspaces for a user or role. Provided that a user may generally access documents, it can be specified what a user / role -may do or not do with each document or workspace. The same is true for objects and assets. These settings are manipulated -in the "Workspaces" tab of a user / role. - -A user needs to be granted access to one or more workspaces. The user can not access any resources outside of his workspace(s). - -However, there are a few general rules on element permissions which need to be regarded: -* if a user does not have the right to list an element, all other permissions are obsolete -* if a user does not have the list permission on an element, all permissions on this element's children are obsolete - -![User permission workspaces](../img/permissions1.png) - -The user permissions on element basis are summed up as follows: - -* **list**: element can be listed in tree -* **view**: element can be opened -* **save**: element can be saved (save button visible) -* **publish**: element can be published (publish button visible) -* **unpublish**: element can be unpublished (unpublish button visible); does not exist for assets -* **create**: new child elements can be created (does not exist for assets) -* **delete**: element can be deleted -* **rename**: name of the element can be changed -* **settings**: element's settings can be managed i.e. the settings tab is visible; the settings permission also the path and thereby the right to move the element in tree -* **versions**: versions tab available -* **properties**: properties tab available and can be managed - -An individual user is granted access to all defined workspaces for any role they incorporate. In addition to that, users -can have their own workspaces. These are added to the permissions granted by roles. - -For example, a role `myRole` has been granted list and view access to `/home/myPath`. The user editor incorporates the -role `myRole` and thereby inherits all workspace settings from the role. -In case the editor has his own workspace settings on `/home/myPath`, these permissions are added to permissions from any -role they incorporate. A permission granted by any role can not be rescinded for a single user. - - -It is also possible to restrict access to localized fields on a language-level. By default, a user can view and edit -(as long as they also have sufficient object permissions) all localized fields. This can now be restricted to a subset of -languages. - -The configuration panel is accessible via the Special Settings column. The same dialog also allows to specify available -custom layouts for the user. - -![User permission workspaces](../img/permissions2.png) - -#### Dynamically control permissions on elements - -By using the event `Pimcore\Event\AdminEvents::ELEMENT_PERMISSION_IS_ALLOWED` it is possible to dynamically manipulate -the permissions of a user on a specific element on request. - -Please note: When listing (tree view,search etc..), this event is fired afterwards on each element of the filtered result list, - therefore in the case of granting a `list` permission on a disallowed element (list=0), it DOES NOT affect the listing conditions nor results (as if the element would be considered as allowed for the filtering purposes). From b0e22dcd3643f33ace1c7d42904ad8c48090c922 Mon Sep 17 00:00:00 2001 From: JiaJia Ji Date: Mon, 31 Jul 2023 13:54:59 +0200 Subject: [PATCH 15/29] [Task]: PhpStan bump July (#15593) * bump stan in composer * fix CommitOrderProcessor.php see https://github.com/pimcore/ecommerce-framework-bundle/blob/349de4cd1e7fe66b8ec7f1019372edcfa48b80d1/src/CheckoutManager/V7/CommitOrderProcessor.php#L357 * fix <0, max> change in baseline * fix wrong var * fix set children int 0 max * fix typo and ternary in tool requirement see also https://github.com/pimcore/pimcore/pull/14799/files?w=1#diff-995edee38ad4f8387e58ebd52c31bcc04c56cc2448d331b1cf5e0b35c57b9efaR343-R347 * Update composer.json * fix stan * fix tab/space --- bundles/CoreBundle/src/Command/Bundle/ListCommand.php | 4 +--- composer.json | 4 ++-- phpstan-baseline.neon | 4 ++++ 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/bundles/CoreBundle/src/Command/Bundle/ListCommand.php b/bundles/CoreBundle/src/Command/Bundle/ListCommand.php index 48cf1d03f1f..32b1d83742a 100644 --- a/bundles/CoreBundle/src/Command/Bundle/ListCommand.php +++ b/bundles/CoreBundle/src/Command/Bundle/ListCommand.php @@ -99,9 +99,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } if ($input->getOption('json')) { - $jsonData = array_map(static function ($row) use ($returnData) { - return array_combine($returnData['headers'], $row); - }, $returnData['rows']); + $jsonData = array_map(fn($row) => array_combine($returnData['headers'], $row), $returnData['rows']); $output->write(\json_encode($jsonData, \JSON_PRETTY_PRINT)); } else { $table = new Table($output); diff --git a/composer.json b/composer.json index 27e42848da4..9d83bdadc21 100644 --- a/composer.json +++ b/composer.json @@ -139,8 +139,8 @@ "codeception/codeception": "^5.0.3", "codeception/module-symfony": "^3.1.0", "codeception/phpunit-wrapper": "^9", - "phpstan/phpstan": "1.10.5", - "phpstan/phpstan-symfony": "^1.2.20", + "phpstan/phpstan": "1.10.26", + "phpstan/phpstan-symfony": "^1.3.2", "phpunit/phpunit": "^9.3", "gotenberg/gotenberg-php": "^1.1", "composer/composer": "*", diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 9a752d304d0..e8f552f2315 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -113,6 +113,10 @@ parameters: message: "#^Call to an undefined method COM\\:\\:Run\\(\\)\\.$#" count: 1 path: lib/Tool/Console.php + - + message: "#^Ternary operator condition is always true\\.$#" + count: 1 + path: lib/Tool/Requirements.php - message: "#^Dead catch \\- Throwable is never thrown in the try block\\.$#" From 8c69d42a16c7f4f78844fb5252da1e792e411516 Mon Sep 17 00:00:00 2001 From: dvesh3 Date: Mon, 31 Jul 2023 11:56:05 +0000 Subject: [PATCH 16/29] Apply php-cs-fixer changes --- bundles/CoreBundle/src/Command/Bundle/ListCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/CoreBundle/src/Command/Bundle/ListCommand.php b/bundles/CoreBundle/src/Command/Bundle/ListCommand.php index 32b1d83742a..4586e305900 100644 --- a/bundles/CoreBundle/src/Command/Bundle/ListCommand.php +++ b/bundles/CoreBundle/src/Command/Bundle/ListCommand.php @@ -99,7 +99,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } if ($input->getOption('json')) { - $jsonData = array_map(fn($row) => array_combine($returnData['headers'], $row), $returnData['rows']); + $jsonData = array_map(fn ($row) => array_combine($returnData['headers'], $row), $returnData['rows']); $output->write(\json_encode($jsonData, \JSON_PRETTY_PRINT)); } else { $table = new Table($output); From d0689c3abed31bbfd9d22925eda5b00886a57e5a Mon Sep 17 00:00:00 2001 From: aryaantony92 Date: Mon, 31 Jul 2023 13:45:00 +0200 Subject: [PATCH 17/29] Update Recycle Bin doc --- doc/22_Administration_of_Pimcore/07_Users_and_Roles.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/22_Administration_of_Pimcore/07_Users_and_Roles.md b/doc/22_Administration_of_Pimcore/07_Users_and_Roles.md index 547d4451eab..7209f76b31a 100644 --- a/doc/22_Administration_of_Pimcore/07_Users_and_Roles.md +++ b/doc/22_Administration_of_Pimcore/07_Users_and_Roles.md @@ -50,7 +50,7 @@ The following list outlines what the different system permissions (available for * **Notes & Events**: Notes & Events are visible * **Objects**: Objects tree is visible * **Predefined Properties**: User can create and modify predefined properties -* **Recycle Bin**: User has access to recycle bin +* **Recycle Bin**: User has access to the recycle bin and see all the deleted elements (even by other users). * **Redirects**: User can create and modify redirects * **Reports**: User has access to reports module * **Seemode**: Seemode available/not available for user From 81df1fd80a27bd239e9674cc72b68bd853ac4f4f Mon Sep 17 00:00:00 2001 From: Sebastian Blank Date: Mon, 31 Jul 2023 15:34:59 +0200 Subject: [PATCH 18/29] Fix: Ceil():Argument #1 ($num) must be of type int|float, null given when uploading video (#15621) * Fix: Ceil():Argument #1 ($num) must be of type int|float, null given when uploading video * Fix wrong php doc. * move ceil to getDurationString() * Remove unnecessary int cast * Make getDurationString non static --- models/Asset/Video.php | 2 +- models/Document/Editable/Video.php | 51 +++++++++++++++++------------- 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/models/Asset/Video.php b/models/Asset/Video.php index f9254c478c5..2a66b2cb2b3 100644 --- a/models/Asset/Video.php +++ b/models/Asset/Video.php @@ -221,7 +221,7 @@ public function getDimensionsFromBackend() } /** - * @return int|null + * @return float|null */ public function getDuration() { diff --git a/models/Document/Editable/Video.php b/models/Document/Editable/Video.php index 509e48fe2e6..53ca8a4bc47 100644 --- a/models/Document/Editable/Video.php +++ b/models/Document/Editable/Video.php @@ -927,27 +927,6 @@ private function getHtml5Code($urls = [], $thumbnail = null) $code = ''; $video = $this->getVideoAsset(); if ($video) { - $duration = ceil($video->getDuration()); - - $durationParts = ['PT']; - - // hours - if ($duration / 3600 >= 1) { - $hours = floor($duration / 3600); - $durationParts[] = $hours . 'H'; - $duration = $duration - $hours * 3600; - } - - // minutes - if ($duration / 60 >= 1) { - $minutes = floor($duration / 60); - $durationParts[] = $minutes . 'M'; - $duration = $duration - $minutes * 60; - } - - $durationParts[] = $duration . 'S'; - $durationString = implode('', $durationParts); - $code .= '
' . "\n"; $uploadDate = new \DateTime(); @@ -959,11 +938,15 @@ private function getHtml5Code($urls = [], $thumbnail = null) 'name' => $this->getTitle(), 'description' => $this->getDescription(), 'uploadDate' => $uploadDate->format('Y-m-d\TH:i:sO'), - 'duration' => $durationString, //'contentUrl' => Tool::getHostUrl() . $urls['mp4'], //"embedUrl" => "http://www.example.com/videoplayer.swf?video=123", //"interactionCount" => "1234", ]; + $duration = $video->getDuration(); + + if ($duration !== null) { + $jsonLd['duration'] = $this->getDurationString($duration); + } if (!$thumbnail) { $thumbnail = $video->getImageThumbnail([]); @@ -1045,6 +1028,30 @@ private function getHtml5Code($urls = [], $thumbnail = null) return $code; } + private function getDurationString(float $duration): string + { + $duration = ceil($duration); + $durationParts = ['PT']; + + // hours + if ($duration / 3600 >= 1) { + $hours = floor($duration / 3600); + $durationParts[] = $hours . 'H'; + $duration = $duration - $hours * 3600; + } + + // minutes + if ($duration / 60 >= 1) { + $minutes = floor($duration / 60); + $durationParts[] = $minutes . 'M'; + $duration = $duration - $minutes * 60; + } + + $durationParts[] = $duration . 'S'; + + return implode('', $durationParts); + } + /** * @param string|null $thumbnail * From 373a09646a4697ec2cd677a9b955e6fb2a0d3b74 Mon Sep 17 00:00:00 2001 From: robertSt7 Date: Mon, 31 Jul 2023 17:35:29 +0200 Subject: [PATCH 19/29] Fix: type --- models/Asset/Video.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/Asset/Video.php b/models/Asset/Video.php index 052e26e83f6..3c821daf2ed 100644 --- a/models/Asset/Video.php +++ b/models/Asset/Video.php @@ -213,7 +213,7 @@ public function getDimensionsFromBackend(): ?array } /** - * @return float|null + * @return float|int|null * * @throws \Exception */ From ce618aaa83b6c818dc01f8764c521de3f6829f27 Mon Sep 17 00:00:00 2001 From: Sebastian Blank Date: Tue, 1 Aug 2023 11:48:57 +0200 Subject: [PATCH 20/29] [DataObject] Link: Attributes can be null (#15577) * Add upgrade notes * Make only properties nullable * Make target nullable --- .../09_Upgrade_Notes/README.md | 22 +-- models/DataObject/Data/Link.php | 130 ++++++++++++------ 2 files changed, 104 insertions(+), 48 deletions(-) diff --git a/doc/23_Installation_and_Upgrade/09_Upgrade_Notes/README.md b/doc/23_Installation_and_Upgrade/09_Upgrade_Notes/README.md index 41779fc7017..ae835bede57 100644 --- a/doc/23_Installation_and_Upgrade/09_Upgrade_Notes/README.md +++ b/doc/23_Installation_and_Upgrade/09_Upgrade_Notes/README.md @@ -1,6 +1,10 @@ -# Upgrade Notes - Pimcore 11.0.0 +# Upgrade Notes -## API +## Pimcore 11.0.6 +- Properties of `Pimcore\Model\DataObject\Data\Link` are nullable now. + +## Pimcore 11.0.0 +### API #### [General] : - **Attention:** Added native php types for argument types, property types, return types and strict type declaration where possible. Double check your classes which are extending from Pimcore classes and adapt if necessary. @@ -66,7 +70,7 @@ - Bumped `league/flysystem-bundle` minimum requirement to ^3.0 (which introduces `directoryExists()`,`has()` methods and fixes support for `directory_visibility` configuration option). Please bump the Flysystem Adapters requirement accordingly to `^3.0` in your project `composer.json`. ----------------- -## Admin UI +### Admin UI #### [General] : - Removed `adminer` as built-in database management tool. @@ -99,7 +103,7 @@ ----------------- -## Bundles +### Bundles #### [Bundles General] : - Removed support for loading bundles through `extensions.php`. @@ -181,7 +185,7 @@ - Service ids changed from `pimcore.newsletter` to `pimcore_newsletter` e.g. `pimcore_newsletter.document.newsletter.factory.default` -## Core +### Core #### [Commands] : @@ -262,7 +266,7 @@ pimcore: - `EcommerceFrameworkBundle\Tracking\TrackingManager` requires session from request stack. ----------------- -## Ecommerce +### Ecommerce #### [Ecommerce General] : - Ecommerce bundle has been moved into a package `pimcore/ecommerce-bundle`. If you wish to continue using the ecommerce framework, then please require the package in your composer.json and install it after enabling in `config/bundles.php`. @@ -287,7 +291,7 @@ pimcore: - Changed return type-hints of `CheckoutableInterface` methods `getOSPrice`, `getOSPriceInfo`, `getOSAvailabilityInfo`, `getPriceSystemName`, `getAvailabilitySystemName`, `getPriceSystemImplementation`, `getAvailabilitySystemImplementation` to be non-nullable. ----------------- -## Elements +### Elements #### [All] : @@ -476,7 +480,7 @@ pimcore_seo: - Removed [deprecated and legacy `