diff --git a/composer.json b/composer.json index 9245728cc..5651b4096 100644 --- a/composer.json +++ b/composer.json @@ -18,6 +18,8 @@ "minimum-stability": "dev", "require": { "php": "~8.1.0 || ~8.2.0", + "pimcore/static-resolver-bundle": "^1.3", + "pimcore/generic-data-index-bundle": "1.x-dev", "pimcore/pimcore": "^11.0", "api-platform/core": "^3.2" }, diff --git a/config/api_platform/resources/asset.yaml b/config/api_platform/resources/asset.yaml new file mode 100644 index 000000000..fb2a8fd30 --- /dev/null +++ b/config/api_platform/resources/asset.yaml @@ -0,0 +1,26 @@ +resources: + Pimcore\Bundle\StudioApiBundle\Dto\Asset: + operations: + ApiPlatform\Metadata\GetCollection: + filters: [ Pimcore\Bundle\StudioApiBundle\Filter\AssetParentIdFilter ] + paginationClientItemsPerPage: true + ApiPlatform\Metadata\Get: + normalizationContext: + groups: [ 'asset:read', 'asset:item:get', 'dependency:read', 'property:read'] + ApiPlatform\Metadata\Put: + ApiPlatform\Metadata\Post: + ApiPlatform\Metadata\Delete: + ApiPlatform\Metadata\Patch: + provider: Pimcore\Bundle\StudioApiBundle\State\AssetProvider + normalizationContext: + groups: ['asset:read', 'dependency:read', 'property:read'] + denormalizationContext: + groups: ['asset:write'] + properties: + id: + identifier: true + +properties: + Pimcore\Bundle\StudioApiBundle\Dto\Asset: + dependencies: + genId: false \ No newline at end of file diff --git a/config/api_platform/resources/asset/image.yaml b/config/api_platform/resources/asset/image.yaml new file mode 100644 index 000000000..c2763f008 --- /dev/null +++ b/config/api_platform/resources/asset/image.yaml @@ -0,0 +1,10 @@ +resources: + Pimcore\Bundle\StudioApiBundle\Dto\Asset\Image: + provider: Pimcore\Bundle\StudioApiBundle\State\AssetProvider + properties: + id: + identifier: true + normalizationContext: + groups: ['image:read', 'asset:read', 'task:read', 'dependency:read', 'property:read', 'version:read'] + denormalizationContext: + groups: ['image:write', 'asset:write'] \ No newline at end of file diff --git a/config/api_platform/resources/schedule/task.yaml b/config/api_platform/resources/schedule/task.yaml new file mode 100644 index 000000000..6f6f2ecd0 --- /dev/null +++ b/config/api_platform/resources/schedule/task.yaml @@ -0,0 +1,10 @@ +resources: + Pimcore\Bundle\StudioApiBundle\Dto\Task: + provider: Pimcore\Bundle\StudioApiBundle\State\ScheduledTaskProvider + normalizationContext: + groups: ['task:read'] + denormalizationContext: + groups: ['task:write'] + properties: + id: + identifier: true \ No newline at end of file diff --git a/config/api_platform/resources/user.yaml b/config/api_platform/resources/user.yaml new file mode 100644 index 000000000..52d7888de --- /dev/null +++ b/config/api_platform/resources/user.yaml @@ -0,0 +1,14 @@ +resources: + Pimcore\Model\User: + operations: + ApiPlatform\Metadata\Post: + status: 202 + processor: Pimcore\Bundle\StudioApiBundle\State\ResetPasswordProcessor + input: Pimcore\Bundle\StudioApiBundle\Dto\ResetPasswordRequest + output: false + uriTemplate: '/users/reset-password' + + normalizationContext: + groups: [ 'get' ] + denormalizationContext: + groups: [ 'set' ] \ No newline at end of file diff --git a/config/api_platform/resources/version.yaml b/config/api_platform/resources/version.yaml new file mode 100644 index 000000000..15478a634 --- /dev/null +++ b/config/api_platform/resources/version.yaml @@ -0,0 +1,16 @@ +resources: + Pimcore\Bundle\StudioApiBundle\Dto\Version: + operations: + ApiPlatform\Metadata\Get: + ApiPlatform\Metadata\Put: + ApiPlatform\Metadata\Post: + ApiPlatform\Metadata\Delete: + ApiPlatform\Metadata\Patch: + provider: Pimcore\Bundle\StudioApiBundle\State\VersionProvider + normalizationContext: + groups: ['version:read'] + denormalizationContext: + groups: ['version:write'] + properties: + id: + identifier: true \ No newline at end of file diff --git a/config/pimcore/routing.yaml b/config/pimcore/routing.yaml new file mode 100644 index 000000000..f6b934190 --- /dev/null +++ b/config/pimcore/routing.yaml @@ -0,0 +1,4 @@ +api_platform: + resource: . + type: api_platform + prefix: /api \ No newline at end of file diff --git a/config/serialization/asset.yaml b/config/serialization/asset.yaml new file mode 100644 index 000000000..19f50f772 --- /dev/null +++ b/config/serialization/asset.yaml @@ -0,0 +1,48 @@ +Pimcore\Bundle\StudioApiBundle\Dto\Asset: + attributes: + id: + groups: ['asset:read'] + parentId: + groups: ['asset:read'] + permissions: + groups: [ 'asset:read' ] + type: + groups: ['asset:read'] + children: + groups: ['asset:read'] + filename: + groups: ['asset:read'] + path: + groups: ['asset:read'] + mimetype: + groups: ['asset:read'] + creationDate: + groups: ['asset:read'] + modificationDate: + groups: ['asset:read'] + userOwner: + groups: ['asset:read'] + userModification: + groups: ['asset:read'] + properties: + groups: ['asset:item:get'] + versions: + groups: ['asset:read'] + metadata: + groups: ['asset:read'] + locked: + groups: ['asset:read'] + lock: + groups: ['asset:read'] + customSettings: + groups: ['asset:item:get'] + hasMetaData: + groups: ['asset:read'] + dependencies: + groups: ['asset:item:get'] + scheduledTasks: + groups: ['asset:item:get'] + versionCount: + groups: ['asset:item:get'] + fullPath: + groups: ['asset:read'] \ No newline at end of file diff --git a/config/serialization/asset/image.yaml b/config/serialization/asset/image.yaml new file mode 100644 index 000000000..7209d8484 --- /dev/null +++ b/config/serialization/asset/image.yaml @@ -0,0 +1,20 @@ +Pimcore\Bundle\StudioApiBundle\Dto\Asset\Image: + attributes: + thumbnail: + groups: ['image:read'] + format: + groups: ['image:read'] + dimensions: + groups: ['image:read'] + width: + groups: ['image:read'] + height: + groups: ['image:read'] + animated: + groups: ['image:read'] + vectorGraphic: + groups: ['image:read'] + lowQualityPreviewDataUri: + groups: ['image:read'] + lowQualityPreviewPath: + groups: ['image:read'] \ No newline at end of file diff --git a/config/serialization/asset/permissions.yaml b/config/serialization/asset/permissions.yaml new file mode 100644 index 000000000..5f452b976 --- /dev/null +++ b/config/serialization/asset/permissions.yaml @@ -0,0 +1,20 @@ +Pimcore\Bundle\StudioApiBundle\Dto\Asset\Permissions: + attributes: + list: + groups: ['asset:read'] + view: + groups: ['asset:read'] + publish: + groups: ['asset:read'] + delete: + groups: ['asset:read'] + rename: + groups: ['asset:read'] + create: + groups: ['asset:read'] + settings: + groups: ['asset:read'] + versions: + groups: ['asset:read'] + properties: + groups: ['asset:read'] \ No newline at end of file diff --git a/config/serialization/dependency.yaml b/config/serialization/dependency.yaml new file mode 100644 index 000000000..94ca83435 --- /dev/null +++ b/config/serialization/dependency.yaml @@ -0,0 +1,16 @@ +Pimcore\Model\Dependency: + attributes: + sourceId: + groups: ['dependency:read'] + sourceType: + groups: ['dependency:read'] + required: + groups: ['dependency:read'] + requiredByTotalCount: + groups: ['dependency:read'] + requiresTotalCount: + groups: ['dependency:read'] + requires: + groups: ['dependency:read'] + requiredBy: + groups: ['dependency:read'] \ No newline at end of file diff --git a/config/serialization/property.yaml b/config/serialization/property.yaml new file mode 100644 index 000000000..1c58e48e5 --- /dev/null +++ b/config/serialization/property.yaml @@ -0,0 +1,18 @@ +Pimcore\Bundle\StudioApiBundle\Dto\Property: + attributes: + name: + groups: ['property:read'] + data: + groups: ['property:read'] + type: + groups: ['property:read'] + ctype: + groups: ['property:read'] + cpath: + groups: ['property:read'] + cid: + groups: ['property:read'] + inheritable: + groups: ['property:read'] + inherited: + groups: ['property:read'] \ No newline at end of file diff --git a/config/serialization/schedule/task.yaml b/config/serialization/schedule/task.yaml new file mode 100644 index 000000000..0c512851d --- /dev/null +++ b/config/serialization/schedule/task.yaml @@ -0,0 +1,18 @@ +Pimcore\Bundle\StudioApiBundle\Dto\Task: + attributes: + id: + groups: ['task:read', 'task:write'] + cid: + groups: ['task:read', 'task:write'] + ctype: + groups: ['task:read', 'task:write'] + date: + groups: ['task:read', 'task:write'] + action: + groups: ['task:read', 'task:write'] + version: + groups: ['task:read', 'task:write'] + active: + groups: ['task:read', 'task:write'] + userId: + groups: ['task:read', 'task:write'] \ No newline at end of file diff --git a/config/serialization/user.yaml b/config/serialization/user.yaml new file mode 100644 index 000000000..4346a156a --- /dev/null +++ b/config/serialization/user.yaml @@ -0,0 +1,4 @@ +Pimcore\Bundle\StudioApiBundle\Dto\ResetPasswordRequest: + attributes: + username: + groups: ['get', 'set'] \ No newline at end of file diff --git a/config/serialization/version.yaml b/config/serialization/version.yaml new file mode 100644 index 000000000..9ab5c5729 --- /dev/null +++ b/config/serialization/version.yaml @@ -0,0 +1,32 @@ +Pimcore\Bundle\StudioApiBundle\Dto\Version: + attributes: + id: + groups: ['version:read', 'asset:item:get'] + cid: + groups: ['version:read', 'asset:item:get'] + ctype: + groups: ['version:read', 'asset:item:get'] + userId: + groups: ['version:read', 'asset:item:get'] + user: + groups: ['version:read', 'asset:item:get'] + note: + groups: ['version:read', 'asset:item:get'] + date: + groups: ['version:read', 'asset:item:get'] + data: + groups: ['version:read'] + public: + groups: ['version:read', 'asset:item:get'] + serialized: + groups: ['version:read', 'asset:item:get'] + versionCount: + groups: ['version:read', 'asset:item:get'] + binaryFileHash: + groups: ['version:read', 'asset:item:get'] + binaryFileId: + groups: ['version:read', 'asset:item:get'] + autosave: + groups: ['version:read', 'asset:item:get'] + storageType: + groups: ['version:read', 'asset:item:get'] diff --git a/config/services.yaml b/config/services.yaml index 8c98cca77..6375b4f7c 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -17,4 +17,21 @@ services: Pimcore\Bundle\StudioApiBundle\Controller\: resource: '../src/Controller' public: true - tags: [ 'controller.service_arguments' ] \ No newline at end of file + tags: [ 'controller.service_arguments' ] + + # Providers + Pimcore\Bundle\StudioApiBundle\State\AssetProvider: ~ + Pimcore\Bundle\StudioApiBundle\State\ScheduledTaskProvider: ~ + Pimcore\Bundle\StudioApiBundle\State\VersionProvider: ~ + + # Processors + Pimcore\Bundle\StudioApiBundle\State\ResetPasswordProcessor: ~ + + # Filters + Pimcore\Bundle\StudioApiBundle\Filter\AssetParentIdFilter: + tags: [ 'api_platform.filter' ] + + # Normalizers + Pimcore\Bundle\StudioApiBundle\Serializer\AssetNormalizer: + tags: + - { name: 'serializer.normalizer' } diff --git a/src/DependencyInjection/PimcoreStudioApiExtension.php b/src/DependencyInjection/PimcoreStudioApiExtension.php new file mode 100644 index 000000000..6ba89f3b7 --- /dev/null +++ b/src/DependencyInjection/PimcoreStudioApiExtension.php @@ -0,0 +1,71 @@ +processConfiguration($configuration, $configs); + + // Load services and configuration + $loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../../config')); + $loader->load('services.yaml'); + + // Set default serializer mapping if not provided in the app's config + if (!isset($config['serializer']['mapping']['paths'])) { + $config['serializer']['mapping']['paths'] = [__DIR__ . '/../../config/serialization']; + } + + // Pass the configuration to the custom normalizer + $container->setParameter( + 'pimcore_studio_api.serializer.mapping.paths', + $config['serializer']['mapping']['paths'] + ); + } + + public function prepend(ContainerBuilder $container): void + { + $apiPlatformConfig = [ + 'mapping'=>[ + 'paths'=> [ + __DIR__ . '/../../config/api_platform/', + ], + ], + ]; + $container->prependExtensionConfig('api_platform', $apiPlatformConfig); + } +} diff --git a/src/Dto/Asset.php b/src/Dto/Asset.php new file mode 100644 index 000000000..ba8aaf6ca --- /dev/null +++ b/src/Dto/Asset.php @@ -0,0 +1,187 @@ +permission; + } + + public function getId(): ?int + { + return $this->asset->getId(); + } + + public function getParentId(): ?int + { + return $this->asset->getParentId(); + } + + public function hasChildren(): bool + { + return $this->asset->hasChildren(); + } + + public function getUserModification(): ?int + { + return $this->asset->getUserModification(); + } + + public function getCreationDate(): ?int + { + return $this->asset->getCreationDate(); + } + + public function getModificationDate(): ?int + { + return $this->asset->getModificationDate(); + } + + public function getUserOwner(): ?int + { + return $this->asset->getUserOwner(); + } + + /** + * enum('self','propagate') nullable + * + */ + public function getLock(): ?string + { + return $this->asset->getLocked(); + } + + public function isLocked(): bool + { + return $this->asset->isLocked(); + } + + /** + * @return Property[] the $properties + */ + public function getProperties(): array + { + $properties = []; + foreach ($this->asset->getProperties() as $property) { + $properties[] = new Property($property); + } + + return $properties; + } + + public function getVersionCount(): int + { + return $this->asset->getVersionCount(); + } + + public function getFilename(): ?string + { + return $this->asset->getFilename(); + } + + public function getKey(): ?string + { + return $this->asset->getKey(); + } + + public function getType(): string + { + return $this->asset->getType(); + } + + /** + * @return Version[] the $versions + */ + public function getVersions(): array + { + $versions = []; + foreach ($this->asset->getVersions() as $version) { + $versions[] = new Version($version); + } + + return $versions; + } + + public function getCustomSettings(): array + { + return $this->asset->getCustomSettings(); + } + + public function getMimeType(): ?string + { + return $this->asset->getMimeType(); + } + + /** + * @return Task[] the $scheduledTasks + */ + public function getScheduledTasks(): array + { + $tasks = []; + foreach ($this->asset->getScheduledTasks() as $task) { + $tasks[] = new Task($task); + } + + return $tasks; + } + + public function getPath(): ?string + { + return $this->asset->getPath(); + } + + public function getMetadata(): array + { + return $this->asset->getMetadata(); + } + + #[ApiProperty(genId: false)] + public function getDependencies(): Dependency + { + return $this->asset->getDependencies(); + } + + public function getHasMetaData(): bool + { + return $this->asset->getHasMetaData(); + } + + public function getFullPath(): string + { + return $this->asset->getFullPath(); + } + + public function getFrontendFullPath(): string + { + return $this->asset->getFrontendFullPath(); + } + + public function getUserPermissions(?User $user = null): array + { + return $this->asset->getUserPermissions($user); + } +} diff --git a/src/Dto/Asset/Image.php b/src/Dto/Asset/Image.php new file mode 100644 index 000000000..6924438cb --- /dev/null +++ b/src/Dto/Asset/Image.php @@ -0,0 +1,71 @@ +asset->getLowQualityPreviewPath(); + } + + public function getLowQualityPreviewDataUri(): ?string + { + return $this->asset->getLowQualityPreviewDataUri(); + } + + public function getThumbnail(array|string|ModelImage\Thumbnail\Config|null $config = null, bool $deferred = true): ThumbnailInterface + { + return $this->asset->getThumbnail($config, $deferred); + } + + public function getFormat(): string + { + return $this->asset->getFormat(); + } + + public function getDimensions(string $path = null, bool $force = false): ?array + { + return $this->asset->getDimensions($path, $force); + } + + public function getWidth(): int + { + return $this->asset->getWidth(); + } + + public function getHeight(): int + { + return $this->asset->getHeight(); + } + + public function isVectorGraphic(): bool + { + return $this->asset->isVectorGraphic(); + } + + public function isAnimated(): bool + { + return $this->asset->isAnimated(); + } +} diff --git a/src/Dto/Asset/Permissions.php b/src/Dto/Asset/Permissions.php new file mode 100644 index 000000000..2eab8e350 --- /dev/null +++ b/src/Dto/Asset/Permissions.php @@ -0,0 +1,76 @@ +list; + } + + public function isView(): bool + { + return $this->view; + } + + public function isPublish(): bool + { + return $this->publish; + } + + public function isDelete(): bool + { + return $this->delete; + } + + public function isRename(): bool + { + return $this->rename; + } + + public function isCreate(): bool + { + return $this->create; + } + + public function isSettings(): bool + { + return $this->settings; + } + + public function isVersions(): bool + { + return $this->versions; + } + + public function isProperties(): bool + { + return $this->properties; + } +} diff --git a/src/Dto/Property.php b/src/Dto/Property.php new file mode 100644 index 000000000..4b29a0afc --- /dev/null +++ b/src/Dto/Property.php @@ -0,0 +1,67 @@ +property->getCid(); + } + + /** + * enum('document','asset','object') + */ + public function getCtype(): ?string + { + return $this->property->getCtype(); + } + + public function getData(): mixed + { + return $this->property->getData(); + } + + public function getName(): ?string + { + return $this->property->getName(); + } + + /** + * enum('text','document','asset','object','bool','select') + */ + public function getType(): ?string + { + return $this->property->getType(); + } + + public function getCpath(): ?string + { + return $this->property->getCpath(); + } + + public function isInherited(): bool + { + return $this->property->isInherited(); + } + + public function getInheritable(): bool + { + return $this->property->getInheritable(); + } +} diff --git a/src/Dto/ResetPasswordRequest.php b/src/Dto/ResetPasswordRequest.php new file mode 100644 index 000000000..31b1cd9e2 --- /dev/null +++ b/src/Dto/ResetPasswordRequest.php @@ -0,0 +1,27 @@ +username; + } +} diff --git a/src/Dto/Task.php b/src/Dto/Task.php new file mode 100644 index 000000000..8daf7cde3 --- /dev/null +++ b/src/Dto/Task.php @@ -0,0 +1,51 @@ +task->getId(); + } + + public function getCid(): ?int + { + return $this->task->getCid(); + } + + public function getCtype(): ?string + { + return $this->task->getCtype(); + } + + public function getDate(): ?int + { + return $this->task->getDate(); + } + + public function getAction(): ?string + { + return $this->task->getAction(); + } + + public function getVersion(): ?int + { + return $this->task->getVersion(); + } +} diff --git a/src/Dto/Version.php b/src/Dto/Version.php new file mode 100644 index 000000000..77e07b48c --- /dev/null +++ b/src/Dto/Version.php @@ -0,0 +1,120 @@ +version->getBinaryFileStream(); + } + + public function getId(): ?int + { + return $this->version->getId(); + } + + public function getCid(): int + { + return $this->version->getCid(); + } + + public function getCtype(): string + { + return $this->version->getCtype(); + } + + public function getDate(): int + { + return $this->version->getDate(); + } + + public function getFileStream(): mixed + { + return $this->version->getFileStream(); + } + + public function getNote(): string + { + return $this->version->getNote(); + } + + public function getUserId(): int + { + return $this->version->getUserId(); + } + + /** + * + * @return mixed + */ + public function getData(): mixed + { + $data = $this->version->getData(); + if ($data instanceof Image) { + return new \Pimcore\Bundle\StudioApiBundle\Dto\Asset\Image($data, new Permissions()); + } + + return $data; + } + + public function getSerialized(): bool + { + return $this->version->getSerialized(); + } + + public function getUser(): ?User + { + return $this->version->getUser(); + } + + public function isPublic(): bool + { + return $this->version->isPublic(); + } + + public function getVersionCount(): int + { + return $this->version->getVersionCount(); + } + + public function getBinaryFileHash(): ?string + { + return $this->version->getBinaryFileHash(); + } + + public function getBinaryFileId(): ?int + { + return $this->version->getBinaryFileId(); + } + + public function isAutoSave(): bool + { + return $this->version->isAutoSave(); + } + + public function getStorageType(): ?string + { + return $this->version->getStorageType(); + } +} diff --git a/src/Filter/AssetParentIdFilter.php b/src/Filter/AssetParentIdFilter.php new file mode 100644 index 000000000..b6a08e9e6 --- /dev/null +++ b/src/Filter/AssetParentIdFilter.php @@ -0,0 +1,51 @@ +query->get('parentId'); + + if (!$parentId) { + return; + } + + $context[self::ASSET_PARENT_ID_FILTER_CONTEXT] = $parentId; + } + + public function getDescription(string $resourceClass): array + { + return [ + 'parentId' => [ + 'property' => Asset::class, + 'type' => 'int', + 'required' => true, + 'is_collection' => false, + 'description' => 'Filters assets by parent id.', + 'openapi' => [ + 'description' => 'Filters assets by parent id.', + ], + ], + ]; + } +} diff --git a/src/PimcoreStudioApiBundle.php b/src/PimcoreStudioApiBundle.php index 73062b08f..afe2e2ba5 100644 --- a/src/PimcoreStudioApiBundle.php +++ b/src/PimcoreStudioApiBundle.php @@ -13,10 +13,14 @@ namespace Pimcore\Bundle\StudioApiBundle; +use ApiPlatform\Symfony\Bundle\ApiPlatformBundle; use Pimcore\Extension\Bundle\AbstractPimcoreBundle; use Pimcore\Extension\Bundle\Installer\InstallerInterface; +use Pimcore\HttpKernel\Bundle\DependentBundleInterface; +use Pimcore\HttpKernel\BundleCollection\BundleCollection; -class PimcoreStudioApiBundle extends AbstractPimcoreBundle +class PimcoreStudioApiBundle extends AbstractPimcoreBundle implements + DependentBundleInterface { public function getPath(): string { @@ -40,4 +44,9 @@ public function getInstaller(): ?InstallerInterface /** @var InstallerInterface|null */ return $this->container->get(Installer::class); } + + public static function registerDependentBundles(BundleCollection $collection): void + { + $collection->addBundle(new ApiPlatformBundle()); + } } diff --git a/src/Serializer/AssetNormalizer.php b/src/Serializer/AssetNormalizer.php new file mode 100644 index 000000000..ced9ed95f --- /dev/null +++ b/src/Serializer/AssetNormalizer.php @@ -0,0 +1,60 @@ +normalizer->normalize($object, $format, $context); + + if (isset($data['data']) && $data['data']) { + $data['data'] = base64_encode($data['data']); + } + + if ($object instanceof Asset\Image) { + $data['thumbnailXXX'] = $object->getThumbnail()->getPath(['frontend' => true]); + } + + return $data; + } + + public function supportsNormalization($data, $format = null, array $context = []): bool + { + return false; + /*if (isset($context[self::ALREADY_CALLED])) { + return false; + } + + return $data instanceof Asset;*/ + } + + public function getSupportedTypes(?string $format): array + { + return [ + Asset::class => false, + ]; + } +} diff --git a/src/State/AssetProvider.php b/src/State/AssetProvider.php new file mode 100644 index 000000000..aec2d7a5a --- /dev/null +++ b/src/State/AssetProvider.php @@ -0,0 +1,79 @@ +assetTreeService->fetchTreeItems( + $parentId, + $this->pagination->getPage($context), + $this->pagination->getLimit($operation, $context) + ); + $totalItems = $items->getPagination()->getTotalItems(); + foreach ($items->getItems() as $item) { + $assetModel = $this->assetResolver->getById($item->getId()); + if ($assetModel === null) { + continue; + } + $result[] = new Asset($assetModel, new Asset\Permissions()); + } + } + + return new TraversablePaginator( + new ArrayIterator($result), + $this->pagination->getPage($context), + $this->pagination->getLimit($operation, $context), + $totalItems + ); + } + $assetModel = $this->assetResolver->getById($uriVariables['id']); + + if ($assetModel === null) { + return null; + } + + if ($assetModel instanceof ImageModel) { + return new Image($assetModel, new Asset\Permissions()); + } + + return new Asset($assetModel, new Asset\Permissions()); + } +} diff --git a/src/State/ResetPasswordProcessor.php b/src/State/ResetPasswordProcessor.php new file mode 100644 index 000000000..dc9473f79 --- /dev/null +++ b/src/State/ResetPasswordProcessor.php @@ -0,0 +1,131 @@ +getUriTemplate() !== '/users/reset-password' + ) { + // wrong operation + throw new OperationNotFoundException(); + } + + $user = User::getByName($data->getUsername()); + if (!$user instanceof User) { + return $data; + } + + $currentRequest = $this->requestStack->getCurrentRequest(); + $limiter = $this->resetPasswordLimiter->create($currentRequest->getClientIp()); + + if (false === $limiter->consume()->isAccepted()) { + throw new InvalidValueException('Rate limit exceeded'); + } + + if (!$user->isActive()) { + throw new InvalidValueException('User is inactive'); + } + + if (!$user->getEmail()) { + throw new InvalidValueException('User has no email address'); + } + + if (!$user->getPassword()) { + throw new InvalidValueException('User has no password'); + } + + $error = null; + $token = Authentication::generateTokenByUser($user); + + try { + $domain = SystemSettingsConfig::get()['general']['domain']; + if (!$domain) { + throw new Exception('No main domain set in system settings, unable to generate reset password link'); + } + + $routerContext = $this->router->getContext(); + $routerContext->setHost($domain); + + $loginUrl = $this->router->generate( + 'pimcore_admin_login_check', + [ + 'token' => $token, + 'reset' => 'true', + ], + UrlGeneratorInterface::ABSOLUTE_URL + ); + + //@TODO: get rid off admin ui dependencies -> move to core (?) + $event = new LostPasswordEvent($user, $loginUrl); + $this->eventDispatcher->dispatch($event, AdminEvents::LOGIN_LOSTPASSWORD); + + // only send mail if it wasn't prevented in event + if ($event->getSendMail()) { + $mail = Tool::getMail([$user->getEmail()], 'Pimcore lost password service'); + $mail->setIgnoreDebugMode(true); + $mail->text( + 'Login to pimcore and change your password using the following link. '. + "This temporary login link will expire in 24 hours: \r\n\r\n" . $loginUrl + ); + $mail->send(); + } + + // directly return event response + if ($event->hasResponse()) { + return $event->getResponse(); + } + } catch (Exception $e) { + Logger::error('Error sending password recovery email: ' . $e->getMessage()); + $error = 'lost_password_email_error'; + } + + if ($error) { + Logger::error('Lost password service: ' . $error); + } + + return $data; + } +} diff --git a/src/State/ScheduledTaskProvider.php b/src/State/ScheduledTaskProvider.php new file mode 100644 index 000000000..8caec692e --- /dev/null +++ b/src/State/ScheduledTaskProvider.php @@ -0,0 +1,42 @@ +taskResolver->getById($uriVariables['id']); + if ($task === null) { + return null; + } + + return new Task($task); + } +} diff --git a/src/State/VersionProvider.php b/src/State/VersionProvider.php new file mode 100644 index 000000000..9aa2ed110 --- /dev/null +++ b/src/State/VersionProvider.php @@ -0,0 +1,41 @@ +versionResolver->getById($uriVariables['id']); + if ($version === null) { + return null; + } + + return new Version($version); + } +}