Skip to content

Commit 473f004

Browse files
authored
Progressing GDPR (#1502)
* Progressing GDPR * Addition of new manager function to run job and fixes * pimcore user and finalize manager service * Re update Json event, Handle json error, Parameter, schema, event handling, rephase, non nullable search,Text Parameter fixes * Additional imporovements and new features added in json download * update docs
1 parent 41c1445 commit 473f004

27 files changed

+1682
-1
lines changed

config/gdpr.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
services:
2+
_defaults:
3+
autowire: true
4+
autoconfigure: true
5+
public: false
6+
7+
# controllers are imported separately to make sure they're public
8+
# and have a tag that allows actions to type-hint services
9+
Pimcore\Bundle\StudioBackendBundle\Gdpr\Controller\:
10+
resource: "../src/Gdpr/Controller/*"
11+
public: true
12+
tags: ["controller.service_arguments"]
13+
14+
# --- GDPR Service Layer ---
15+
16+
Pimcore\Bundle\StudioBackendBundle\Gdpr\Service\GdprManagerServiceInterface:
17+
class: Pimcore\Bundle\StudioBackendBundle\Gdpr\Service\GdprManagerService
18+
19+
Pimcore\Bundle\StudioBackendBundle\Gdpr\Service\DataProviderLoaderInterface:
20+
class: Pimcore\Bundle\StudioBackendBundle\Gdpr\Service\Loader\TaggedIteratorDataProviderLoader
21+
22+
# --- GDPR Providers ---
23+
24+
Pimcore\Bundle\StudioBackendBundle\Gdpr\Provider\PimcoreUserProvider:
25+
tags: ["pimcore.studio_backend.gdpr_data_provider"]
26+
arguments:
27+
$logsDir: "%kernel.logs_dir%"

doc/05_Additional_Custom_Attributes.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,5 +158,7 @@ final class AssetEvent extends AbstractPreResponseEvent
158158
- `pre_response.workflow_details`
159159
- `pre_response.notification_recipient`
160160
- `pre_response.php_code_transformer`
161+
- `pre_response.data_provider`
162+
- `pre_response.gdpr_search_result`
161163
- `pre_response.element.usage.item`
162-
- `pre_response.element.usage`
164+
- `pre_response.element.usage`

doc/10_Extending_Studio/11_Gdpr.md

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# Extending GDPR Data Providers
2+
3+
The GDPR Data Provider system provides a centralized interface to find and export personal data from any part of your Pimcore application. You can add new data sources (like Data Objects, Assets, Users, or any custom entity) by creating your own provider.
4+
5+
New providers can be created by implementing the `Pimcore\Bundle\StudioBackendBundle\Gdpr\Provider\DataProviderInterface` and tagging your class as a service with `pimcore.studio_backend.gdpr_data_provider`.
6+
7+
## How does it work
8+
9+
As a developer, you only need to register it with the `pimcore.studio_backend.gdpr_data_provider` tag and implement the `DataProviderInterface`. The Pimcore system will automatically find your provider and use in Searching and Exporting.
10+
11+
### For Searching
12+
13+
This flow happens when a user opens the GDPR Data Extractor page and clicks "Search".
14+
15+
1. **To build the page:** The user needs to create these methods on their newly created provider:
16+
17+
- `getName()`: To get the human-friendly name for the provider list.
18+
- `getKey()`: To get the unique ID.
19+
- `getSortPriority()`: To decide where to place your provider in the list.
20+
- `getAvailableColumns()`: To build the columns for the search results grid.
21+
- `getRequiredPermissions()`: One or more permissions required by user to access the data provider information
22+
- `findData()`: Find the data in the particular provider using the searched terms
23+
- `getDeleteSwaggerOperationId()`: The Operation ID to call to delete item
24+
25+
2. **When the user clicks "Search":**
26+
- The system first calls your `getRequiredPermissions()` method to check if the current user is allowed to use your provider.
27+
- If permission is granted, the system calls your `findData()` method, passing the user's search terms.
28+
- The result you return from `findData()` is then displayed directly in the results grid.
29+
30+
### For Exporting (Direct Download)
31+
32+
This flow happens when a user has already searched and clicks the "Export" button on a single item in your results grid.
33+
34+
1. **When the user clicks "Export" on an item:**
35+
- The system again checks your `getRequiredPermission()` method.
36+
- If permission is granted, the system calls your `getSingleItemForDownload(int $id)` method, passing the ID of the item the user wants to export.
37+
- The `array` or `object` you return from `getSingleItemForDownload()` is then automatically converted by the system into a **downloadable file** for the user.
38+
39+
---
40+
41+
### For Deleting an Item
42+
43+
This flow happens when a user clicks "Delete" on a result row.
44+
45+
Instead of handling deletion logic inside the provider, you simply **point** to the correct API endpoint.
46+
47+
1. You implement `getDeleteSwaggerOperationId()`.
48+
2. This returns the unique **Operation ID** that handles deleting specific type of item.
49+
3. When the user confirms, the frontend calls that API endpoint using the item's ID.
50+
51+
## Example Data Provider
52+
53+
Example below shows some of the important functions with their implementations
54+
55+
```php
56+
57+
final class UserCreatedDataProvider implements DataProviderInterface
58+
{
59+
public function getKey(): string
60+
{
61+
return 'key_value';
62+
}
63+
64+
public function getName(): string
65+
{
66+
return 'Data Provider Name';
67+
}
68+
69+
public function getSortPriority(): int
70+
{
71+
return 10;//set the priority of provider
72+
}
73+
74+
/**
75+
* @return string[]
76+
*/
77+
public function getRequiredPermissions(): array
78+
{
79+
// Return an array of permission strings
80+
return ['permission 1', 'permission 2'];//example : UserPermissions::USERS->value
81+
}
82+
83+
public function getAvailableColumns(): array
84+
{
85+
return [
86+
new GdprDataColumn('column1', 'Column 1 Value'),
87+
new GdprDataColumn('column2', 'Column 2 Value'),
88+
];
89+
}
90+
91+
public function findData(?SearchTerms $terms): array
92+
{
93+
//Find user data using input $terms
94+
95+
//return $results;
96+
}
97+
public function getDeleteSwaggerOperationId(): string
98+
{
99+
return 'data_provider_delete_by_operation_id';
100+
}
101+
102+
public function getSingleItemForDownload(int $id): array|object
103+
{
104+
// return single Item of a Data Provider
105+
}
106+
}
107+
108+
```
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
/**
5+
* This source file is available under the terms of the
6+
* Pimcore Open Core License (POCL)
7+
* Full copyright and license information is available in
8+
* LICENSE.md which is distributed with this source code.
9+
*
10+
* @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com)
11+
* @license Pimcore Open Core License (POCL)
12+
*/
13+
14+
namespace Pimcore\Bundle\StudioBackendBundle\DependencyInjection\CompilerPass;
15+
16+
use Pimcore\Bundle\StudioBackendBundle\Exception\MustImplementInterfaceException;
17+
use Pimcore\Bundle\StudioBackendBundle\Gdpr\Provider\DataProviderInterface;
18+
use Pimcore\Bundle\StudioBackendBundle\Gdpr\Service\Loader\TaggedIteratorDataProviderLoader;
19+
use Pimcore\Bundle\StudioBackendBundle\Util\Trait\MustImplementInterfaceTrait;
20+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
21+
use Symfony\Component\DependencyInjection\ContainerBuilder;
22+
23+
/**
24+
* @internal
25+
*/
26+
final readonly class DataProviderPass implements CompilerPassInterface
27+
{
28+
use MustImplementInterfaceTrait;
29+
30+
/**
31+
* @throws MustImplementInterfaceException
32+
*/
33+
public function process(ContainerBuilder $container): void
34+
{
35+
$taggedServices = array_keys(
36+
[
37+
... $container->findTaggedServiceIds(TaggedIteratorDataProviderLoader::DATA_PROVIDER_TAG),
38+
39+
]
40+
);
41+
42+
foreach ($taggedServices as $environmentType) {
43+
$this->checkInterface($environmentType, DataProviderInterface::class);
44+
}
45+
}
46+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
/**
5+
* This source file is available under the terms of the
6+
* Pimcore Open Core License (POCL)
7+
* Full copyright and license information is available in
8+
* LICENSE.md which is distributed with this source code.
9+
*
10+
* @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com)
11+
* @license Pimcore Open Core License (POCL)
12+
*/
13+
14+
namespace Pimcore\Bundle\StudioBackendBundle\Gdpr\Attribute\Request;
15+
16+
use Attribute;
17+
use OpenApi\Attributes\Items;
18+
use OpenApi\Attributes\JsonContent;
19+
use OpenApi\Attributes\Property;
20+
use OpenApi\Attributes\RequestBody;
21+
22+
/**
23+
* @internal
24+
*/
25+
#[Attribute(Attribute::TARGET_METHOD)]
26+
final class GdprRequestBody extends RequestBody
27+
{
28+
public function __construct()
29+
{
30+
parent::__construct(
31+
required: true,
32+
content: new JsonContent(
33+
34+
required: ['providers', 'searchTerms'],
35+
properties: [
36+
new Property(
37+
property: 'providers',
38+
description: 'A list of provider keys to search',
39+
type: 'array',
40+
items: new Items(
41+
type: 'string',
42+
example: 'pimcore_users'
43+
)
44+
),
45+
46+
new Property(
47+
property: 'searchTerms',
48+
ref: SearchTerms::class,
49+
description: 'The object containing the search values.',
50+
type: 'object'
51+
),
52+
],
53+
type: 'object',
54+
),
55+
);
56+
}
57+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
/**
5+
* This source file is available under the terms of the
6+
* Pimcore Open Core License (POCL)
7+
* Full copyright and license information is available in
8+
* LICENSE.md which is distributed with this source code.
9+
*
10+
* @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com)
11+
* @license Pimcore Open Core License (POCL)
12+
*/
13+
14+
namespace Pimcore\Bundle\StudioBackendBundle\Gdpr\Attribute\Request;
15+
16+
use OpenApi\Attributes\Property;
17+
use OpenApi\Attributes\Schema;
18+
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\InvalidArgumentException;
19+
use Symfony\Component\Validator\Constraints\Type;
20+
21+
/**
22+
* @internal
23+
*/
24+
#[Schema(
25+
title: 'GDPR Search Terms',
26+
description: 'Object containing the values to search for. All fields are optional.',
27+
type: 'object'
28+
)]
29+
final readonly class SearchTerms
30+
{
31+
public function __construct(
32+
#[Property(description: 'The ID to search for.', type: 'string', nullable: true)]
33+
#[Type('string')]
34+
public ?string $id = null,
35+
36+
#[Property(description: 'The first name to search for.', type: 'string', nullable: true)]
37+
#[Type('string')]
38+
public ?string $firstname = null,
39+
40+
#[Property(description: 'The last name to search for.', type: 'string', nullable: true)]
41+
#[Type('string')]
42+
public ?string $lastname = null,
43+
44+
#[Property(description: 'The email address to search for.', type: 'string', nullable: true)]
45+
#[Type('string')]
46+
public ?string $email = null,
47+
) {
48+
if ($this->id === null &&
49+
$this->firstname === null &&
50+
$this->lastname === null &&
51+
$this->email === null
52+
) {
53+
throw new InvalidArgumentException('Provide at least one search term.');
54+
}
55+
}
56+
57+
public function getId(): ?string
58+
{
59+
return $this->id;
60+
}
61+
62+
public function getFirstname(): ?string
63+
{
64+
return $this->firstname;
65+
}
66+
67+
public function getLastname(): ?string
68+
{
69+
return $this->lastname;
70+
}
71+
72+
public function getEmail(): ?string
73+
{
74+
return $this->email;
75+
}
76+
}

0 commit comments

Comments
 (0)