diff --git a/bundles/ApplicationLoggerBundle/src/Handler/ApplicationLoggerDb.php b/bundles/ApplicationLoggerBundle/src/Handler/ApplicationLoggerDb.php index 9af58901ffd..df49eb13e46 100644 --- a/bundles/ApplicationLoggerBundle/src/Handler/ApplicationLoggerDb.php +++ b/bundles/ApplicationLoggerBundle/src/Handler/ApplicationLoggerDb.php @@ -42,7 +42,7 @@ public function write(LogRecord $record): void 'pid' => getmypid(), 'priority' => $record->level->toPsrLogLevel(), 'message' => $record->message, - 'timestamp' => $record->datetime->format('Y-m-d H:i:s'), + 'timestamp' => $record->datetime->setTimezone(new \DateTimeZone('UTC'))->format('Y-m-d H:i:s'), 'component' => $record->context['component'] ?? $record->channel, 'fileobject' => $record->context['fileObject'] ?? null, 'relatedobject' => $record->context['relatedObject'] ?? null, diff --git a/bundles/CoreBundle/src/Migrations/Version20230616085142.php b/bundles/CoreBundle/src/Migrations/Version20230616085142.php index 2af56af2be5..b31591b50aa 100644 --- a/bundles/CoreBundle/src/Migrations/Version20230616085142.php +++ b/bundles/CoreBundle/src/Migrations/Version20230616085142.php @@ -25,6 +25,8 @@ final class Version20230616085142 extends AbstractMigration { private const ID_COLUMN = 'id'; + private const O_PREFIX = 'o_'; + private const PK_COLUMNS = '`' . self::ID_COLUMN . '`,`dest_id`, `type`, `fieldname`, `column`, `ownertype`, `ownername`, `position`, `index`'; @@ -51,10 +53,13 @@ public function up(Schema $schema): void $tableName = current($table); $metaDataTable = $schema->getTable($tableName); $foreignKeyName = AbstractDao::getForeignKeyName($tableName, self::ID_COLUMN); + $foreignKeyNameWithOPrefix = AbstractDao::getForeignKeyName($tableName, self::O_PREFIX . self::ID_COLUMN); if (!$metaDataTable->hasColumn(self::AUTO_ID)) { if ($recreateForeignKey = $metaDataTable->hasForeignKey($foreignKeyName)) { $this->addSql('ALTER TABLE `' . $tableName . '` DROP FOREIGN KEY `' . $foreignKeyName . '`'); + } elseif ($recreateForeignKey = $metaDataTable->hasForeignKey($foreignKeyNameWithOPrefix)) { + $this->addSql('ALTER TABLE `' . $tableName . '` DROP FOREIGN KEY `' . $foreignKeyNameWithOPrefix . '`'); } if ($metaDataTable->hasPrimaryKey()) { diff --git a/bundles/CoreBundle/src/Migrations/Version20240222143211.php b/bundles/CoreBundle/src/Migrations/Version20240222143211.php new file mode 100644 index 00000000000..295099f5f1e --- /dev/null +++ b/bundles/CoreBundle/src/Migrations/Version20240222143211.php @@ -0,0 +1,90 @@ +addSql('SET foreign_key_checks = 0'); + + foreach($schema->getTables() as $table) { + if($table->hasColumn('id')) { + $tableName = $table->getName(); + + if ( + str_starts_with($tableName, 'object_brick_') || + str_starts_with($tableName, 'object_classificationstore_') || + str_starts_with($tableName, 'object_collection_') || + str_starts_with($tableName, 'object_metadata_') + ) { + $foreignKeyWithoutOPrefix = AbstractDao::getForeignKeyName($tableName, 'id'); + $foreignKeyWithOPrefix = AbstractDao::getForeignKeyName($tableName, 'o_id'); + + if ($table->hasForeignKey($foreignKeyWithOPrefix)) { + $this->addSql("ALTER TABLE {$tableName} DROP FOREIGN KEY {$foreignKeyWithOPrefix}"); + $this->addSql("ALTER TABLE {$tableName} ADD CONSTRAINT {$foreignKeyWithoutOPrefix} FOREIGN KEY (id) REFERENCES objects(id) ON DELETE CASCADE"); + } + } + } + } + + $this->addSql('SET foreign_key_checks = 1'); + } + + public function down(Schema $schema): void + { + $this->addSql('SET foreign_key_checks = 0'); + + foreach($schema->getTables() as $table) { + if($table->hasColumn('id')) { + $tableName = $table->getName(); + + if ( + str_starts_with($tableName, 'object_brick_') || + str_starts_with($tableName, 'object_classificationstore_') || + str_starts_with($tableName, 'object_collection_') || + str_starts_with($tableName, 'object_metadata_') + ) { + $foreignKeyWithoutOPrefix = AbstractDao::getForeignKeyName($tableName, 'id'); + $foreignKeyWithOPrefix = AbstractDao::getForeignKeyName($tableName, 'o_id'); + + if ($table->hasForeignKey($foreignKeyWithoutOPrefix)) { + $this->addSql("ALTER TABLE {$tableName} DROP FOREIGN KEY {$foreignKeyWithoutOPrefix}"); + $this->addSql("ALTER TABLE {$tableName} ADD CONSTRAINT {$foreignKeyWithOPrefix} FOREIGN KEY (id) REFERENCES objects(id) ON DELETE CASCADE"); + } + } + } + } + + $this->addSql('SET foreign_key_checks = 1'); + } +} diff --git a/bundles/TinymceBundle/public/js/editor.js b/bundles/TinymceBundle/public/js/editor.js index 6e033660ca3..950d1a1ad40 100644 --- a/bundles/TinymceBundle/public/js/editor.js +++ b/bundles/TinymceBundle/public/js/editor.js @@ -96,7 +96,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_id|pimcore_type],img[style|longdesc|usemap|src|border|alt=|title|hspace|vspace|width|height|align|pimcore_id|pimcore_type]', + extended_valid_elements: 'a[class|name|href|target|title|pimcore_id|pimcore_type],img[class|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(); diff --git a/composer.json b/composer.json index b5a9a8c9561..0d34d2993f0 100644 --- a/composer.json +++ b/composer.json @@ -138,7 +138,7 @@ "codeception/codeception": "^5.0.3", "codeception/module-symfony": "^3.1.0", "ergebnis/phpstan-rules": "^2.0", - "phpstan/phpstan": "1.10.56", + "phpstan/phpstan": "1.10.59", "phpstan/phpstan-symfony": "^1.3.5", "phpunit/phpunit": "^9.3", "gotenberg/gotenberg-php": "^1.1", diff --git a/doc/19_Development_Tools_and_Details/09_Cache/README.md b/doc/19_Development_Tools_and_Details/09_Cache/README.md index aa3c43b085e..5a368c5994c 100644 --- a/doc/19_Development_Tools_and_Details/09_Cache/README.md +++ b/doc/19_Development_Tools_and_Details/09_Cache/README.md @@ -113,8 +113,7 @@ if(!$data = \Pimcore\Cache::load($cacheKey)) { #### Disable the Cache for a Single Request Sometimes it's useful to deactivate the cache for testing purposes for a single request. You -can do this by passing the URL parameter `pimcore_nocache=true`. Note: This is only possible if you have -enabled the `DEBUG MODE` in *Settings* > *System* +can do this by passing the URL parameter `pimcore_nocache=true`. Note: This is only possible if you are in [DEBUG MODE](../13_Debugging.md#debug-mode) For example: `http://www.pimcore.org/download?pimcore_nocache=true` diff --git a/doc/21_Deployment/03_Configuration_Environments.md b/doc/21_Deployment/03_Configuration_Environments.md index 99fb3be3b97..28456febe10 100644 --- a/doc/21_Deployment/03_Configuration_Environments.md +++ b/doc/21_Deployment/03_Configuration_Environments.md @@ -59,37 +59,37 @@ pimcore: config_location: image_thumbnails: write_target: - type: 'symfony-config' - options: + type: 'symfony-config' + options: directory: '/var/www/html/var/config/image-thumbnails' video_thumbnails: write_target: - type: 'disabled' + type: 'disabled' document_types: write_target: - type: 'disabled' + type: 'disabled' predefined_properties: write_target: - type: 'settings-store' + type: 'settings-store' predefined_asset_metadata: write_target: - type: 'symfony-config' - options: + type: 'symfony-config' + options: directory: '/var/www/html/var/config/predefined_asset_metadata' perspectives: write_target: - type: 'symfony-config' - options: + type: 'symfony-config' + options: directory: '/var/www/html/var/config/perspectives' custom_views: write_target: - type: 'symfony-config' - options: + type: 'symfony-config' + options: directory: '/var/www/html/var/config/custom_views' object_custom_layouts: write_target: - type: 'symfony-config' - options: + type: 'symfony-config' + options: directory: '/var/www/html/var/config/object_custom_layouts' select_options: write_target: @@ -98,6 +98,23 @@ pimcore: directory: '/var/www/html/var/config/select_options' ``` +and for some specific optional bundles are: + +```yaml +pimcore_custom_reports: + config_location: + custom_reports: + write_target: + type: 'symfony-config' + +pimcore_static_routes: + config_location: + staticroutes: + write_target: + type: 'symfony-config' + ... +``` + #### Production environment with `symfony-config` When using `symfony-config` write target, configs are written to Symfony Config files (`yaml`), which are only getting revalidated in debug mode. So if you're changing configs in production you won't see any update, because these configs are read only. @@ -107,9 +124,9 @@ You can do so by adding the following to your `symfony-config`. e.g.: ```yaml pimcore: config_location: - custom_reports: + predefined_properties: write_target: - type: 'settings-store' + type: 'settings-store' ``` #### Revalidate existing configuration on production diff --git a/lib/Image/Adapter.php b/lib/Image/Adapter.php index c25cca26292..eca60378aca 100644 --- a/lib/Image/Adapter.php +++ b/lib/Image/Adapter.php @@ -41,6 +41,9 @@ abstract class Adapter protected mixed $resource = null; + /** + * @return $this + */ public function setHeight(int $height): static { $this->height = $height; @@ -53,6 +56,9 @@ public function getHeight(): int return $this->height; } + /** + * @return $this + */ public function setWidth(int $width): static { $this->width = $width; @@ -89,11 +95,17 @@ public function colorhex2colorarray(string $colorhex): array return [$r, $g, $b, 'type' => 'RGB']; } + /** + * @return $this + */ public function resize(int $width, int $height): static { return $this; } + /** + * @return $this + */ public function scaleByWidth(int $width, bool $forceResize = false): static { if ($forceResize || $width <= $this->getWidth() || $this->isVectorGraphic()) { @@ -104,6 +116,9 @@ public function scaleByWidth(int $width, bool $forceResize = false): static return $this; } + /** + * @return $this + */ public function scaleByHeight(int $height, bool $forceResize = false): static { if ($forceResize || $height < $this->getHeight() || $this->isVectorGraphic()) { @@ -114,6 +129,9 @@ public function scaleByHeight(int $height, bool $forceResize = false): static return $this; } + /** + * @return $this + */ public function contain(int $width, int $height, bool $forceResize = false): static { $x = $this->getWidth() / $width; @@ -129,9 +147,12 @@ public function contain(int $width, int $height, bool $forceResize = false): sta return $this; } - public function cover(int $width, int $height, array|string $orientation = 'center', bool $forceResize = false): static + /** + * @return $this + */ + public function cover(int $width, int $height, array|string|null $orientation = 'center', bool $forceResize = false): static { - if (empty($orientation)) { + if (!$orientation) { $orientation = 'center'; // if not set (from GUI for instance) - default value in getByLegacyConfig method of Config object too } $ratio = $this->getWidth() / $this->getHeight(); @@ -195,36 +216,57 @@ public function cover(int $width, int $height, array|string $orientation = 'cent return $this; } + /** + * @return $this + */ public function frame(int $width, int $height, bool $forceResize = false): static { return $this; } + /** + * @return $this + */ public function trim(int $tolerance): static { return $this; } + /** + * @return $this + */ public function rotate(int $angle): static { return $this; } + /** + * @return $this + */ public function crop(int $x, int $y, int $width, int $height): static { return $this; } + /** + * @return $this + */ public function setBackgroundColor(string $color): static { return $this; } + /** + * @return $this + */ public function setBackgroundImage(string $image): static { return $this; } + /** + * @return $this + */ public function roundCorners(int $width, int $height): static { return $this; @@ -240,16 +282,25 @@ public function addOverlay(mixed $image, int $x = 0, int $y = 0, int $alpha = 10 return $this; } + /** + * @return $this + */ public function addOverlayFit(string $image, string $composite = 'COMPOSITE_DEFAULT'): static { return $this; } + /** + * @return $this + */ public function applyMask(string $image): static { return $this; } + /** + * @return $this + */ public function cropPercent(int $width, int $height, int $x, int $y): static { if ($this->isVectorGraphic()) { @@ -269,39 +320,55 @@ public function cropPercent(int $width, int $height, int $x, int $y): static return $this->crop($xPixel, $yPixel, $widthPixel, $heightPixel); } + /** + * @return $this + */ public function grayscale(): static { return $this; } + /** + * @return $this + */ public function sepia(): static { return $this; } + /** + * @return $this + */ public function sharpen(): static { return $this; } + /** + * @return $this + */ public function mirror(string $mode): static { return $this; } + /** + * @return $this + */ public function gaussianBlur(int $radius = 0, float $sigma = 1.0): static { return $this; } + /** + * @return $this + */ public function brightnessSaturation(int $brightness = 100, int $saturation = 100, int $hue = 100): static { return $this; } /** - * - * * @return $this|false */ abstract public function load(string $imagePath, array $options = []): static|false; @@ -374,6 +441,9 @@ protected function getVectorRasterDimensions(): array ]; } + /** + * @return $this + */ public function setColorspace(string $type = 'RGB'): static { return $this; diff --git a/lib/Tool/Authentication.php b/lib/Tool/Authentication.php index 9d59c9b6500..8e4c853dbc3 100644 --- a/lib/Tool/Authentication.php +++ b/lib/Tool/Authentication.php @@ -119,11 +119,9 @@ protected static function refreshUser(TokenInterface $token, UserProvider $provi public static function authenticateToken(string $token, bool $adminRequired = false): ?User { - $timestamp = null; - try { [$timestamp, $user] = self::tokenDecrypt($token); - } catch (CryptoException $e) { + } catch (CryptoException|NotFoundException) { return null; } diff --git a/models/Asset.php b/models/Asset.php index 8c61aa2f4f6..3c432fb93f8 100644 --- a/models/Asset.php +++ b/models/Asset.php @@ -410,7 +410,7 @@ private static function checkMaxPixels(string $localPath, array $data): void if ($imagePixels > $maxPixels) { Logger::error("Image to be created {$localPath} (temp. path) exceeds max pixel size of {$maxPixels}, you can change the value in config pimcore.assets.image.max_pixels"); - $diff = sqrt(1 + ($maxPixels / $imagePixels)); + $diff = sqrt(1 + $imagePixels / $maxPixels); $suggestion_0 = (int)round($size[0] / $diff, -2, PHP_ROUND_HALF_DOWN); $suggestion_1 = (int)round($size[1] / $diff, -2, PHP_ROUND_HALF_DOWN); diff --git a/models/Asset/Dao.php b/models/Asset/Dao.php index 4f3dfe91206..af91709ed1f 100644 --- a/models/Asset/Dao.php +++ b/models/Asset/Dao.php @@ -247,9 +247,12 @@ public function getProperties(bool $onlyInherited = false): array foreach ($propertiesRaw as $propertyRaw) { try { + $id = $this->model->getId(); $property = new Model\Property(); $property->setType($propertyRaw['type']); - $property->setCid($this->model->getId()); + if ($id !== null) { + $property->setCid($id); + } $property->setName($propertyRaw['name']); $property->setCtype('asset'); $property->setDataFromResource($propertyRaw['data']); diff --git a/models/DataObject/AbstractObject.php b/models/DataObject/AbstractObject.php index 9f9781a529d..2fee91bfa47 100644 --- a/models/DataObject/AbstractObject.php +++ b/models/DataObject/AbstractObject.php @@ -20,7 +20,6 @@ use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use Pimcore\Cache; use Pimcore\Cache\RuntimeCache; -use Pimcore\Db; use Pimcore\Event\DataObjectEvents; use Pimcore\Event\Model\DataObjectEvent; use Pimcore\Logger; @@ -1045,7 +1044,7 @@ public static function __callStatic(string $method, array $arguments) $offset = $arguments[2] ?? 0; $objectTypes = $arguments[3] ?? null; - $defaultCondition = $propertyName.' = '.Db::get()->quote($value).' '; + $defaultCondition = $db->quoteIdentifier($propertyName).' = '.$db->quote($value).' '; $listConfig = [ 'condition' => $defaultCondition, diff --git a/models/DataObject/AbstractObject/Dao.php b/models/DataObject/AbstractObject/Dao.php index e07c1005925..a87784a10ca 100644 --- a/models/DataObject/AbstractObject/Dao.php +++ b/models/DataObject/AbstractObject/Dao.php @@ -245,9 +245,12 @@ public function getProperties(bool $onlyInherited = false): array foreach ($propertiesRaw as $propertyRaw) { try { + $id = $this->model->getId(); $property = new Model\Property(); $property->setType($propertyRaw['type']); - $property->setCid($this->model->getId()); + if ($id !== null) { + $property->setCid($id); + } $property->setName($propertyRaw['name']); $property->setCtype('object'); $property->setDataFromResource($propertyRaw['data']); diff --git a/models/DataObject/ClassDefinition/Data/Block.php b/models/DataObject/ClassDefinition/Data/Block.php index 57e18b8640a..9aa110b998a 100644 --- a/models/DataObject/ClassDefinition/Data/Block.php +++ b/models/DataObject/ClassDefinition/Data/Block.php @@ -306,14 +306,14 @@ public function getDataFromEditmode(mixed $data, DataObject\Concrete $object = n foreach ($blockElementDefinition as $elementName => $fd) { $elementType = $fd->getFieldtype(); $invisible = $fd->getInvisible(); - if ($invisible && !is_null($oIndex)) { + if ((!array_key_exists($elementName, $blockElement) || $invisible) && !is_null($oIndex)) { $blockGetter = 'get' . ucfirst($this->getname()); if (empty($context['containerType']) && method_exists($object, $blockGetter)) { $language = $params['language'] ?? null; $items = $object->$blockGetter($language); - if (isset($items[$oIndex])) { + if (isset($items[$oIndex][$elementName])) { $item = $items[$oIndex][$elementName]; - $blockData = $blockElement[$elementName] ?: $item->getData(); + $blockData = $blockElement[$elementName] ?? $item->getData(); $resultElement[$elementName] = new DataObject\Data\BlockElement($elementName, $elementType, $blockData); } } else { diff --git a/models/DataObject/ClassDefinition/Data/Fieldcollections.php b/models/DataObject/ClassDefinition/Data/Fieldcollections.php index 85b14c9f3f4..76372d050ac 100644 --- a/models/DataObject/ClassDefinition/Data/Fieldcollections.php +++ b/models/DataObject/ClassDefinition/Data/Fieldcollections.php @@ -107,9 +107,7 @@ public function getDataForEditmode(mixed $data, DataObject\Concrete $object = nu foreach ($collectionDef->getFieldDefinitions() as $fd) { if (!$fd instanceof CalculatedValue) { $value = $item->{'get' . $fd->getName()}(); - if (isset($params['context']['containerKey']) === false) { - $params['context']['containerKey'] = $idx; - } + $params['context']['containerKey'] = $idx; $collectionData[$fd->getName()] = $fd->getDataForEditmode($value, $object, $params); } } diff --git a/models/Document/Dao.php b/models/Document/Dao.php index 20fd464a293..eea83a18109 100644 --- a/models/Document/Dao.php +++ b/models/Document/Dao.php @@ -286,7 +286,7 @@ public function getProperties(bool $onlyInherited = false, bool $onlyDirect = fa $id = $this->model->getId(); $property = new Model\Property(); $property->setType($propertyRaw['type']); - if (isset($id)) { + if ($id !== null) { $property->setCid($id); } $property->setName($propertyRaw['name']); diff --git a/models/Element/ElementInterface.php b/models/Element/ElementInterface.php index 96f055b266f..0262980be66 100644 --- a/models/Element/ElementInterface.php +++ b/models/Element/ElementInterface.php @@ -150,6 +150,8 @@ public function getVersionCount(): int; * - versionNote: Optional. Descriptive text saved alongside versioned data * * @return $this + * + * @throws DuplicateFullPathException */ public function save(array $parameters = []): static; diff --git a/models/Element/Tag.php b/models/Element/Tag.php index 647f428d2b8..e408b096ffa 100644 --- a/models/Element/Tag.php +++ b/models/Element/Tag.php @@ -292,7 +292,7 @@ public function __toString(): string */ public function getChildren(): array { - if ($this->children == null) { + if ($this->children === null) { if ($this->getId()) { $listing = new Tag\Listing(); $listing->setCondition('parentId = ?', $this->getId()); @@ -308,7 +308,19 @@ public function getChildren(): array public function hasChildren(): bool { - return count($this->getChildren()) > 0; + if ($this->children) { + return true; + } + + //skip getTotalCount if array is empty + if (is_array($this->children)) { + return false; + } + + $listing = new Tag\Listing(); + $listing->setCondition('parentId = ?', $this->getId()); + + return $listing->getTotalCount() > 0; } public function correctPath(): void