From 776fc431cd0d467c3b13baad5a8b7a83ee6ff941 Mon Sep 17 00:00:00 2001 From: JiaJia Ji Date: Tue, 18 Jun 2024 11:08:56 +0200 Subject: [PATCH] [Improvement]: Move grid data related functions from pimcore/pimcore v11.3 (#457) --- composer.json | 2 +- .../Admin/Asset/AssetController.php | 3 +- .../DataObject/DataObjectActionsTrait.php | 7 +- .../DataObject/DataObjectHelperController.php | 3 +- src/GDPR/DataProvider/Assets.php | 3 +- src/GDPR/DataProvider/DataObjects.php | 4 +- src/Service/GridData/Asset.php | 118 ++++++ src/Service/GridData/DataObject.php | 360 ++++++++++++++++++ src/Service/GridData/Document.php | 42 ++ src/Service/GridData/Element.php | 48 +++ 10 files changed, 581 insertions(+), 9 deletions(-) create mode 100644 src/Service/GridData/Asset.php create mode 100644 src/Service/GridData/DataObject.php create mode 100644 src/Service/GridData/Document.php create mode 100644 src/Service/GridData/Element.php diff --git a/composer.json b/composer.json index fd5fc7b0fa..922ed5e307 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "php": "~8.1.0 || ~8.2.0 || ~8.3.0", "cbschuld/browser.php": "^1.9.6", "phpoffice/phpspreadsheet": "^1.24 || ^2.1", - "pimcore/pimcore": "^11.2.0", + "pimcore/pimcore": "^11.3.0", "symfony/webpack-encore-bundle": "^1.13.2" }, "require-dev": { diff --git a/src/Controller/Admin/Asset/AssetController.php b/src/Controller/Admin/Asset/AssetController.php index 6abb13ce50..84af7a38f5 100644 --- a/src/Controller/Admin/Asset/AssetController.php +++ b/src/Controller/Admin/Asset/AssetController.php @@ -23,6 +23,7 @@ use Pimcore\Bundle\AdminBundle\Event\ElementAdminStyleEvent; use Pimcore\Bundle\AdminBundle\Helper\GridHelperService; use Pimcore\Bundle\AdminBundle\Security\CsrfProtectionHandler; +use Pimcore\Bundle\AdminBundle\Service\GridData; use Pimcore\Config; use Pimcore\Controller\KernelControllerEventInterface; use Pimcore\Controller\Traits\ElementEditLockHelperTrait; @@ -2350,7 +2351,7 @@ public function gridProxyAction(Request $request, EventDispatcherInterface $even foreach ($list->getAssets() as $index => $asset) { // Like for treeGetChildrenByIdAction, so we respect isAllowed method which can be extended (object DI) for custom permissions, so relying only users_workspaces_asset is insufficient and could lead security breach if ($asset->isAllowed('list')) { - $a = Asset\Service::gridAssetData($asset, $allParams['fields'], $allParams['language'] ?? ''); + $a = GridData\Asset::getData($asset, $allParams['fields'], $allParams['language'] ?? ''); $assets[] = $a; } } diff --git a/src/Controller/Admin/DataObject/DataObjectActionsTrait.php b/src/Controller/Admin/DataObject/DataObjectActionsTrait.php index db64cb5797..ef7e2e7d2d 100644 --- a/src/Controller/Admin/DataObject/DataObjectActionsTrait.php +++ b/src/Controller/Admin/DataObject/DataObjectActionsTrait.php @@ -18,6 +18,7 @@ use Pimcore\Bundle\AdminBundle\Event\AdminEvents; use Pimcore\Bundle\AdminBundle\Helper\GridHelperService; +use Pimcore\Bundle\AdminBundle\Service\GridData; use Pimcore\Localization\LocaleServiceInterface; use Pimcore\Logger; use Pimcore\Model\DataObject; @@ -92,7 +93,7 @@ protected function gridProxy( return [ 'success' => true, - 'data' => DataObject\Service::gridObjectData($object, $allParams['fields'], $requestedLanguage), + 'data' => GridData\DataObject::getData($object, $allParams['fields'], $requestedLanguage), ]; } catch (\Exception $e) { return [ @@ -122,13 +123,13 @@ protected function gridProxy( $objects = []; foreach ($list->getObjects() as $object) { if ($csvMode) { - $o = DataObject\Service::getCsvDataForObject($object, $requestedLanguage, $request->get('fields'), DataObject\Service::getHelperDefinitions(), $localeService, 'title', false, $allParams['context']); + $o = DataObject\Service::getCsvDataForObject($object, $requestedLanguage, $request->get('fields'), GridData\DataObject::getHelperDefinitions(), $localeService, 'title', false, $allParams['context']); // Like for treeGetChildrenByIdAction, so we respect isAllowed method which can be extended (object DI) for custom permissions, so relying only users_workspaces_object is insufficient and could lead security breach if ($object->isAllowed('list')) { $objects[] = $o; } } else { - $o = DataObject\Service::gridObjectData($object, $allParams['fields'] ?? null, $requestedLanguage, + $o = GridData\DataObject::getData($object, $allParams['fields'] ?? null, $requestedLanguage, ['csvMode' => $csvMode]); if ($o['permissions']['list']) { $objects[] = $o; diff --git a/src/Controller/Admin/DataObject/DataObjectHelperController.php b/src/Controller/Admin/DataObject/DataObjectHelperController.php index a519f3200d..44753e1dbe 100644 --- a/src/Controller/Admin/DataObject/DataObjectHelperController.php +++ b/src/Controller/Admin/DataObject/DataObjectHelperController.php @@ -24,6 +24,7 @@ use Pimcore\Bundle\AdminBundle\Model\GridConfig; use Pimcore\Bundle\AdminBundle\Model\GridConfigFavourite; use Pimcore\Bundle\AdminBundle\Model\GridConfigShare; +use Pimcore\Bundle\AdminBundle\Service\GridData; use Pimcore\Config; use Pimcore\Db; use Pimcore\File; @@ -66,7 +67,7 @@ public function loadObjectDataAction(Request $request): JsonResponse if ($object) { $result['success'] = true; $fields = $request->get('fields'); - $result['fields'] = DataObject\Service::gridObjectData($object, $fields); + $result['fields'] = GridData\DataObject::getData($object, $fields); } else { $result['success'] = false; } diff --git a/src/GDPR/DataProvider/Assets.php b/src/GDPR/DataProvider/Assets.php index 6cb97acef9..087b0792dc 100644 --- a/src/GDPR/DataProvider/Assets.php +++ b/src/GDPR/DataProvider/Assets.php @@ -19,6 +19,7 @@ use Doctrine\DBAL\Exception; use Pimcore\Bundle\AdminBundle\Helper\QueryParams; +use Pimcore\Bundle\AdminBundle\Service\GridData; use Pimcore\Model\Asset; use Pimcore\Model\Element; use Symfony\Component\HttpFoundation\Response; @@ -160,7 +161,7 @@ public function searchData(int $id, string $firstname, string $lastname, string $element = Element\Service::getElementById('asset', $hit['id']); if ($element instanceof Asset) { - $data = Asset\Service::gridAssetData($element); + $data = GridData\Asset::getData($element); $data['permissions'] = $element->getUserPermissions(); $elements[] = $data; } diff --git a/src/GDPR/DataProvider/DataObjects.php b/src/GDPR/DataProvider/DataObjects.php index 198942e591..50f7d15a29 100644 --- a/src/GDPR/DataProvider/DataObjects.php +++ b/src/GDPR/DataProvider/DataObjects.php @@ -18,8 +18,8 @@ namespace Pimcore\Bundle\AdminBundle\GDPR\DataProvider; use Pimcore\Bundle\AdminBundle\Helper\QueryParams; +use Pimcore\Bundle\AdminBundle\Service\GridData; use Pimcore\Model\Asset; -use Pimcore\Model\DataObject; use Pimcore\Model\DataObject\AbstractObject; use Pimcore\Model\DataObject\Concrete; use Pimcore\Model\DataObject\Data\ElementMetadata; @@ -150,7 +150,7 @@ public function searchData(int $id, string $firstname, string $lastname, string foreach ($query->fetchAllAssociative() as $hit) { $element = Element\Service::getElementById($hit['type'], $hit['id']); if ($element instanceof Concrete) { - $data = DataObject\Service::gridObjectData($element); + $data = GridData\DataObject::getData($element); $data['__gdprIsDeletable'] = $this->config['classes'][$element->getClassName()]['allowDelete'] ?? false; $elements[] = $data; } diff --git a/src/Service/GridData/Asset.php b/src/Service/GridData/Asset.php new file mode 100644 index 0000000000..344be0d3f4 --- /dev/null +++ b/src/Service/GridData/Asset.php @@ -0,0 +1,118 @@ + $asset->getId(), + 'id~system' => $asset->getId(), + 'type~system' => $asset->getType(), + 'fullpath~system' => $asset->getRealFullPath(), + 'filename~system' => $asset->getKey(), + 'creationDate~system' => $asset->getCreationDate(), + 'modificationDate~system' => $asset->getModificationDate(), + 'idPath~system' => Service::getIdPath($asset), + ]; + + $requestedLanguage = str_replace('default', '', $requestedLanguage); + + foreach ($fields as $field) { + $fieldDef = explode('~', $field); + if (isset($fieldDef[1]) && $fieldDef[1] === 'system') { + if ($fieldDef[0] === 'preview') { + $data[$field] = self::getPreviewThumbnail($asset, ['treepreview' => true, 'width' => 108, 'height' => 70, 'frame' => true]); + } elseif ($fieldDef[0] === 'size') { + $size = $asset->getFileSize(); + $data[$field] = formatBytes($size); + } + } else { + if (isset($fieldDef[1])) { + $language = ($fieldDef[1] === 'none' ? '' : $fieldDef[1]); + $rawMetaData = $asset->getMetadata($fieldDef[0], $language, true, true); + } else { + $rawMetaData = $asset->getMetadata($field, $requestedLanguage, true, true); + } + + $metaData = $rawMetaData['data'] ?? null; + + if ($rawMetaData) { + $type = $rawMetaData['type']; + if (!$loader) { + $loader = \Pimcore::getContainer()->get('pimcore.implementation_loader.asset.metadata.data'); + } + + $metaData = $rawMetaData['data'] ?? null; + + try { + /** @var Data $instance */ + $instance = $loader->build($type); + $metaData = $instance->getDataForListfolderGrid($rawMetaData['data'] ?? null, $rawMetaData); + } catch (UnsupportedException $e) { + } + } + + $data[$field] = $metaData; + } + } + } + + return $data; + } + + public static function getPreviewThumbnail(Model\Asset $asset, array $params = [], bool $onlyMethod = false): ?string + { + $thumbnailMethod = ''; + $thumbnailUrl = null; + + if ($asset instanceof Model\Asset\Image) { + $thumbnailMethod = 'getThumbnail'; + } elseif ($asset instanceof Model\Asset\Video && \Pimcore\Video::isAvailable()) { + $thumbnailMethod = 'getImageThumbnail'; + } elseif ($asset instanceof Model\Asset\Document && \Pimcore\Document::isAvailable()) { + $thumbnailMethod = 'getImageThumbnail'; + } + + if ($onlyMethod) { + return $thumbnailMethod; + } + + if (!empty($thumbnailMethod)) { + $thumbnailUrl = '/admin/asset/get-' . $asset->getType() . '-thumbnail?id=' . $asset->getId(); + if (count($params) > 0) { + $thumbnailUrl .= '&' . http_build_query($params); + } + } + + return $thumbnailUrl; + } +} diff --git a/src/Service/GridData/DataObject.php b/src/Service/GridData/DataObject.php new file mode 100644 index 0000000000..9757cc95c1 --- /dev/null +++ b/src/Service/GridData/DataObject.php @@ -0,0 +1,360 @@ + $object, + 'purpose' => 'gridview', + 'language' => $requestedLanguage, ]; + $data['classname'] = $object->getClassName(); + $data['idPath'] = Service::getIdPath($object); + $data['inheritedFields'] = []; + $data['permissions'] = $object->getUserPermissions($user); + $data['locked'] = $object->isLocked(); + + if (is_null($fields)) { + $fields = array_keys($object->getclass()->getFieldDefinitions()); + } + + $haveHelperDefinition = false; + + foreach ($fields as $key) { + $brickDescriptor = null; + $brickKey = null; + $brickType = null; + $brickGetter = null; + $dataKey = $key; + $keyParts = explode('~', $key); + + $def = $object->getClass()->getFieldDefinition($key, $context); + + if (str_starts_with($key, '#')) { + if (!$haveHelperDefinition) { + $helperDefinitions = self::getHelperDefinitions(); + $haveHelperDefinition = true; + } + if (!empty($helperDefinitions[$key])) { + $context['fieldname'] = $key; + $data[$key] = \Pimcore\Model\DataObject\Service::calculateCellValue($object, $helperDefinitions, $key, $context); + } + } elseif (str_starts_with($key, '~')) { + $type = $keyParts[1]; + if ($type === 'classificationstore') { + $data[$key] = self::getStoreValueForObject($object, $key, $requestedLanguage); + } + } elseif (count($keyParts) > 1) { + // brick + $brickType = $keyParts[0]; + if (str_contains($brickType, '?')) { + $brickDescriptor = substr($brickType, 1); + $brickDescriptor = json_decode($brickDescriptor, true); + $brickType = $brickDescriptor['containerKey']; + } + + $brickKey = $keyParts[1]; + + $key = Service::getFieldForBrickType($object->getclass(), $brickType); + + $brickClass = Objectbrick\Definition::getByKey($brickType); + $context['outerFieldname'] = $key; + + if ($brickDescriptor) { + $innerContainer = $brickDescriptor['innerContainer'] ?? 'localizedfields'; + /** @var Model\DataObject\ClassDefinition\Data\Localizedfields $localizedFields */ + $localizedFields = $brickClass->getFieldDefinition($innerContainer); + $def = $localizedFields->getFieldDefinition($brickDescriptor['brickfield']); + } elseif ($brickClass instanceof Objectbrick\Definition) { + $def = $brickClass->getFieldDefinition($brickKey, $context); + } + } + + if (!empty($key)) { + // some of the not editable field require a special response + $getter = 'get' . ucfirst($key); + $needLocalizedPermissions = false; + + // if the definition is not set try to get the definition from localized fields + if (!$def) { + /** @var Model\DataObject\ClassDefinition\Data\Localizedfields|null $locFields */ + $locFields = $object->getClass()->getFieldDefinition('localizedfields'); + if ($locFields) { + $def = $locFields->getFieldDefinition($key, $context); + if ($def) { + $needLocalizedPermissions = true; + } + } + } + + //relation type fields with remote owner do not have a getter + if (method_exists($object, $getter)) { + //system columns must not be inherited + if (in_array($key, Concrete::SYSTEM_COLUMN_NAMES)) { + $data[$dataKey] = $object->$getter(); + } else { + $valueObject = self::getValueForObject($object, $key, $brickType, $brickKey, $def, $context, $brickDescriptor, $requestedLanguage); + $data['inheritedFields'][$dataKey] = ['inherited' => $valueObject->objectid != $object->getId(), 'objectid' => $valueObject->objectid]; + + if ($csvMode || method_exists($def, 'getDataForGrid')) { + if ($brickKey) { + $context['containerType'] = 'objectbrick'; + $context['containerKey'] = $brickType; + $context['outerFieldname'] = $key; + } + + $params = array_merge($params, ['context' => $context]); + if (!isset($params['purpose'])) { + $params['purpose'] = 'gridview'; + } + + if ($csvMode) { + $getterParams = ['language' => $requestedLanguage]; + $tempData = $def->getForCsvExport($object, $getterParams); + } elseif (method_exists($def, 'getDataForGrid')) { + $tempData = $def->getDataForGrid($valueObject->value, $object, $params); + } else { + continue; + } + + if ($def instanceof ClassDefinition\Data\Localizedfields) { + $needLocalizedPermissions = true; + foreach ($tempData as $tempKey => $tempValue) { + $data[$tempKey] = $tempValue; + } + } else { + $data[$dataKey] = $tempData; + if ( + $def instanceof Model\DataObject\ClassDefinition\Data\Select + && !$def->useConfiguredOptions() + && $def->getOptionsProviderClass() + ) { + $data[$dataKey . '%options'] = $def->getOptions(); + } + } + } else { + $data[$dataKey] = $valueObject->value; + } + } + } + + // because the key for the classification store has not a direct getter, you have to check separately if the data is inheritable + if (str_starts_with($key, '~') && empty($data[$key])) { + $type = $keyParts[1]; + + if ($type === 'classificationstore') { + if (!empty($inheritedData = self::getInheritedData($object, $key, $requestedLanguage))) { + $data[$dataKey] = $inheritedData['value']; + $data['inheritedFields'][$dataKey] = ['inherited' => $inheritedData['parent']->getId() != $object->getId(), 'objectid' => $inheritedData['parent']->getId()]; + } + } + } + if ($needLocalizedPermissions) { + if (!$user->isAdmin()) { + $locale = \Pimcore::getContainer()->get(LocaleServiceInterface::class)->findLocale(); + + $permissionTypes = ['View', 'Edit']; + foreach ($permissionTypes as $permissionType) { + //TODO, this needs refactoring! Ideally, call it only once! + $languagesAllowed = Service::getLanguagePermissions($object, $user, 'l' . $permissionType); + + if ($languagesAllowed) { + $languagesAllowed = array_keys($languagesAllowed); + + if (!in_array($locale, $languagesAllowed)) { + $data['metadata']['permission'][$key]['no' . $permissionType] = 1; + if ($permissionType === 'View') { + $data[$key] = null; + } + } + } + } + } + } + } + } + } + + return $data; + } + + public static function getHelperDefinitions(): array + { + $stack = \Pimcore::getContainer()->get('request_stack'); + if ($stack->getMainRequest()?->hasSession()) { + $session = $stack->getSession(); + + return Session::useBag($session, function (AttributeBagInterface $session) { + return $session->get('helpercolumns', []); + }, 'pimcore_gridconfig'); + } + + return []; + } + + /** + * gets value for given object and getter, including inherited values + * + * @return \stdClass value and objectid where the value comes from + */ + private static function getValueForObject(Concrete $object, string $key, string $brickType = null, string $brickKey = null, ClassDefinition\Data $fieldDefinition = null, array $context = [], array $brickDescriptor = null, string $requestedLanguage = null): \stdClass + { + $getter = 'get' . ucfirst($key); + $value = null; + + try { + $value = $object->$getter($requestedLanguage ?? AdminTool::getCurrentUser()?->getLanguage()); + } catch (\Throwable) { + } + + if (empty($value)) { + $value = $object->$getter(); + } + if (!empty($value) && !empty($brickType)) { + $getBrickType = 'get' . ucfirst($brickType); + $value = $value->$getBrickType(); + if (!empty($value) && !empty($brickKey)) { + if ($brickDescriptor) { + $innerContainer = $brickDescriptor['innerContainer'] ?? 'localizedfields'; + $localizedFields = $value->{'get' . ucfirst($innerContainer)}(); + $brickDefinition = Model\DataObject\Objectbrick\Definition::getByKey($brickType); + /** @var Model\DataObject\ClassDefinition\Data\Localizedfields $fieldDefinitionLocalizedFields */ + $fieldDefinitionLocalizedFields = $brickDefinition->getFieldDefinition('localizedfields'); + $fieldDefinition = $fieldDefinitionLocalizedFields->getFieldDefinition($brickKey); + $value = $localizedFields->getLocalizedValue($brickDescriptor['brickfield']); + } else { + $brickFieldGetter = 'get' . ucfirst($brickKey); + $value = $value->$brickFieldGetter(); + } + } + } + + if (!$fieldDefinition) { + $fieldDefinition = $object->getClass()->getFieldDefinition($key, $context); + } + + if (!empty($brickType) && !empty($brickKey) && !$brickDescriptor) { + $brickClass = Objectbrick\Definition::getByKey($brickType); + $context = ['object' => $object, 'outerFieldname' => $key]; + $fieldDefinition = $brickClass->getFieldDefinition($brickKey, $context); + } + + if ($fieldDefinition->isEmpty($value)) { + $parent = Service::hasInheritableParentObject($object); + if (!empty($parent)) { + return self::getValueForObject($parent, $key, $brickType, $brickKey, $fieldDefinition, $context, $brickDescriptor); + } + } + + $result = new \stdClass(); + $result->value = $value; + $result->objectid = $object->getId(); + + return $result; + } + + /** + * gets store value for given object and key + */ + private static function getStoreValueForObject(Concrete $object, string $key, ?string $requestedLanguage): mixed + { + $keyParts = explode('~', $key); + + if (str_starts_with($key, '~')) { + $type = $keyParts[1]; + if ($type === 'classificationstore') { + $field = $keyParts[2]; + $groupKeyId = explode('-', $keyParts[3]); + + $groupId = (int) $groupKeyId[0]; + $keyid = (int) $groupKeyId[1]; + $getter = 'get' . ucfirst($field); + + if (method_exists($object, $getter)) { + /** @var Classificationstore $classificationStoreData */ + $classificationStoreData = $object->$getter(); + + /** @var Model\DataObject\ClassDefinition\Data\Classificationstore $csFieldDefinition */ + $csFieldDefinition = $object->getClass()->getFieldDefinition($field); + $csLanguage = $requestedLanguage; + + if (!$csFieldDefinition->isLocalized()) { + $csLanguage = 'default'; + } + + $fielddata = $classificationStoreData->getLocalizedKeyValue($groupId, $keyid, $csLanguage, true, true); + + $keyConfig = Model\DataObject\Classificationstore\KeyConfig::getById($keyid); + $type = $keyConfig->getType(); + $definition = json_decode($keyConfig->getDefinition(), true); + $definition = \Pimcore\Model\DataObject\Classificationstore\Service::getFieldDefinitionFromJson($definition, $type); + + if (method_exists($definition, 'getDataForGrid')) { + $fielddata = $definition->getDataForGrid($fielddata, $object); + } + + return $fielddata; + } + } + } + + return null; + } + + protected static function getInheritedData(Concrete $object, string $key, string $requestedLanguage): array + { + if (!$parent = Service::hasInheritableParentObject($object)) { + return []; + } + + if ($inheritedValue = self::getStoreValueForObject($parent, $key, $requestedLanguage)) { + return [ + 'parent' => $parent, + 'value' => $inheritedValue, + ]; + } + + return self::getInheritedData($parent, $key, $requestedLanguage); + } +} diff --git a/src/Service/GridData/Document.php b/src/Service/GridData/Document.php new file mode 100644 index 0000000000..adef966510 --- /dev/null +++ b/src/Service/GridData/Document.php @@ -0,0 +1,42 @@ +getTitle(); + $data['description'] = $document->getDescription(); + } else { + $data['title'] = ''; + $data['description'] = ''; + $data['name'] = ''; + } + + return $data; + } +} diff --git a/src/Service/GridData/Element.php b/src/Service/GridData/Element.php new file mode 100644 index 0000000000..9ba68bea7d --- /dev/null +++ b/src/Service/GridData/Element.php @@ -0,0 +1,48 @@ + $element->getId(), + 'fullpath' => $element->getRealFullPath(), + 'type' => Service::getElementType($element), + 'subtype' => $element->getType(), + 'filename' => $element->getKey(), + 'creationDate' => $element->getCreationDate(), + 'modificationDate' => $element->getModificationDate(), + ]; + + if (method_exists($element, 'isPublished')) { + $data['published'] = $element->isPublished(); + } else { + $data['published'] = true; + } + + return $data; + } +}